-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
589 lines (403 loc) · 471 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>朝花夕拾</title>
<subtitle>朝花夕拾</subtitle>
<link href="https://pywonderland.com/atom.xml" rel="self"/>
<link href="https://pywonderland.com/"/>
<updated>2024-12-17T15:29:45.518Z</updated>
<id>https://pywonderland.com/</id>
<author>
<name>Zhao Liang</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>IFS 分形揭秘</title>
<link href="https://pywonderland.com/ifs-demystified/"/>
<id>https://pywonderland.com/ifs-demystified/</id>
<published>2024-06-16T00:00:00.000Z</published>
<updated>2024-12-17T15:29:45.518Z</updated>
<content type="html"><![CDATA[<p>本文整理自我 2024 年 6 月 14日在上海科技大学数学所的一个小报告,标题是「GPU涂鸦与数学可视化」。我保留了报告的技术内容,略去了报告中介绍 demoscene文化和分形文化的部分。</p><span id="more"></span><hr><p>在 Shadertoy上有很多效果酷炫,但是代码非常短的分形作品。我挑选了其中三个优秀的例子展示如下:</p><table><colgroup><col style="width: 33%"><col style="width: 33%"><col style="width: 33%"></colgroup><tbody><tr class="odd"><td style="text-align: center;"><a href="https://www.shadertoy.com/view/ltB3DG">Ethereal</a> by Kali</td><td style="text-align: center;"><a href="https://www.shadertoy.com/view/NsVyRz">Apollonian fractal</a> byXor</td><td style="text-align: center;"><a href="https://www.shadertoy.com/view/mdG3Wy">Radiosity</a> by Xor</td></tr><tr class="even"><td style="text-align: center;"><img src="https://www.shadertoy.com/media/shaders/ltB3DG.jpg"></td><td style="text-align: center;"><img src="https://www.shadertoy.com/media/shaders/NsVyRz.jpg"></td><td style="text-align: center;"><img src="https://www.shadertoy.com/media/shaders/mdG3Wy.jpg"></td></tr></tbody></table><p>然而代码短可不代表它们容易看懂。特别是很多作者还喜欢故弄玄虚,把代码作了混淆处理以增加神秘感。对我来说,这种被人秀了一脸结果还没搞明白对面是怎么装的逼的感觉让人很不爽。当然我不是在抱怨,这种炫技的行为本身就是黑客文化的一部分,可以理解。后来分形玩的多了,我也慢慢明白了其中的奥妙,这次上科大之行是一次很好的机会,促使我把这些理解完整的写下来。</p><p>在这篇文章中,我将为大家揭示这些作品背后的奥秘。这些分形作品别看场景千变万化,其实都是用同一个套路制作出来的。这个套路可以简述为:首先将像素的2D 坐标映射为空间中的某个 3D 的点,然后用一个 <code>fold</code>函数,即所谓的 <a href="https://en.wikipedia.org/wiki/Iterated_function_system">迭代函数系统</a>(iteratedfunction system,简称IFS)反复作用在该点上。每次迭代结束后,生成一个颜色并将其添加到当前的颜色<code>color</code> 上。当达到一定的迭代次数后,返回 <code>color</code>的值作为像素最终的颜色。</p><p>如果用伪代码来描述,大概是这样:</p><figure class="highlight glsl"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs glsl"><span class="hljs-type">vec3</span> getPixelColor(<span class="hljs-type">vec2</span> pixel) {<br> <span class="hljs-type">vec3</span> p = screenToSpace(pixel); <span class="hljs-comment">// map 2D pixel to 3D space</span><br> <span class="hljs-type">vec3</span> color = <span class="hljs-type">vec3</span>(<span class="hljs-number">0</span>);<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=<span class="hljs-number">0</span>; i<max_iterations; i++) {<br> p = fold(p); <span class="hljs-comment">// transform p in each iteration</span><br> color += someColorFunc(p); <span class="hljs-comment">// add a bit color</span><br> }<br> <span class="hljs-keyword">return</span> color;<br>}<br></code></pre></td></tr></tbody></table></figure><p>整个过程主要包括三个要素:</p><ol type="1"><li>用 IFS 来构造分形。</li><li>用轨道着色技巧给分形上色。</li><li>在着色器 (shader) 中编程实现。</li></ol><p>下面我来逐一来解释这些步骤。</p><h1 id="ifs-方法">IFS 方法</h1><div id="------------" class="unnumbered statement sta_____ plain"><p><span class="statement-heading"><span class="statement-label">压缩映射</span>.</span><span class="statement-spah"> </span>设 <span class="math inline">\(f:\mathbb{R}^n\to\mathbb{R}^n\)</span>是一个映射,如果存在 <span class="math inline">\(0<r<1\)</span>使得对任何 <span class="math inline">\(x,y\in\mathbb{R}^n\)</span> 有<span class="math display">\[d(f(x),f(y))\leq r\cdot d(x,y)\]</span>成立,我们就称 <span class="math inline">\(f\)</span>是一个压缩映射,<span class="math inline">\(r\)</span> 是压缩比例。这里<span class="math inline">\(d\)</span> 是通常的 Euclidean 距离。</p></div><p>记 <span class="math inline">\(K(\mathbb{R}^n)\)</span> 是 <span class="math inline">\(\mathbb{R}^n\)</span>中所有紧集组成的集合,可以证明 <span class="math inline">\(K(\mathbb{R}^n)\)</span> 在 <a href="https://en.wikipedia.org/wiki/Hausdorff_distance">Hausdorff度量</a> 下构成一个完备度量空间。读者不必关心 Hausdoff度量的具体细节,只要知道它是一个度量,可以衡量两个紧集的接近程度。</p><p>设 <span class="math inline">\(f_1,\ldots,f_N\)</span> 是 <span class="math inline">\(N\)</span> 个压缩映射,<span class="math inline">\(f_i\)</span> 的压缩比例是 <span class="math inline">\(r_i\)</span>。定义映射 <span class="math inline">\(F:K(\mathbb{R}^n)\to K(\mathbb{R}^n)\)</span>如下: <span class="math display">\[F(X) = f_1(X)\cupf_2(X)\cup\cdots\cup f_N(X),\quad X\in K(\mathbb{R}^n).\]</span> 即<span class="math inline">\(F\)</span> 把 <span class="math inline">\(X\)</span> 变成 <span class="math inline">\(N\)</span> 个更小的集合。</p><p>可以证明 <span class="math inline">\(F\)</span> 是空间 <span class="math inline">\(K(\mathbb{R}^n)\)</span> 上的压缩映射,其压缩比例<span class="math inline">\(r=\max\{r_1,\ldots,r_N\}\)</span>。于是根据<a href="https://en.wikipedia.org/wiki/Banach_fixed-point_theorem">Banach不动点定理</a>,存在唯一的紧集 <span class="math inline">\(A\subset\mathbb{R}^n\)</span> 使得 <span class="math inline">\(A\)</span> 是 <span class="math inline">\(F\)</span> 的不动点: <span class="math display">\[F(A) = A.\]</span> 不仅如此,对 <span class="math inline">\(K(\mathbb{R}^n)\)</span> 中的任何一点(注意 <span class="math inline">\(K(\mathbb{R}^n)\)</span>是紧集组成的度量空间,里面的点都是紧集)<span class="math inline">\(B\subset\mathbb{R}^n\)</span>,都有 <span class="math display">\[\lim_{n\to\infty} F^n(B) =A.\]</span> 紧集 <span class="math inline">\(A\)</span> 叫做 <span class="math inline">\(F\)</span> 的极限集,<span class="math inline">\(A\)</span> 是一个分形,它具有自相似的特征。</p><p>我们以著名的 <a href="https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle">Sierpiński三角形</a> 为例来说明这个过程。我们选择的三个压缩映射分别是</p><p><span class="math display">\[\begin{align}f_1(x,y) &= (x/2, y/2),\\f_2(x,y) &= (x/2, y/2) + (0, 1/2),\\f_3(x,y) &= (x/2, y/2) + (1/2, 0).\\\end{align}\]</span></p><p>这三个压缩映射的压缩比都是 1/2。初始的紧集 <span class="math inline">\(K_0\)</span> 可以随便选,比如就取为一个圆: <span class="math display">\[K_0 =\img{/images/ifs/dot0.svg}{-1.75em}{}{4em}.\]</span></p><p>在第 1 次迭代后,它变成</p><p><span class="math display">\[K_1 = f_1(K_0)\cup f_2(K_0)\cup f_3(K_0)= \img{/images/ifs/dot1.svg}{-1.75em}{}{4em}.\]</span></p><p>在第 2 次迭代后,结果是</p><p><span class="math display">\[K_2 = f_1(K_1)\cup f_2(K_1)\cup f_3(K_1)= \img{/images/ifs/dot2.svg}{-1.75em}{}{4em}.\]</span></p><p>第 3 次迭代:</p><p><span class="math display">\[K_3 = f_1(K_2)\cup f_2(K_2)\cup f_3(K_2)= \img{/images/ifs/dot3.svg}{-1.75em}{}{4em}.\]</span></p><p>当迭代次数趋于无穷,就得到了 Sierpiński 分形:</p><p><span class="math display">\[\lim_{n\to\infty} K_n = K = f_1(K)\cupf_2(K)\cup f_3(K) =\img{/images/ifs/dot6.svg}{-1.75em}{}{4em}.\]</span></p><p>你可以很容易看出来为什么初始紧集的选择是不重要的:因为在压缩的过程中,任何紧集都会逐渐缩小到一个单点,所以<span class="math inline">\(K_0\)</span> 甚至取成一个点也是可以的。</p><p>压缩映射是无穷无尽的,所以 IFS给出的分形也是无穷无尽的。为了避免选择困难,我们一般只使用平移、旋转、反射、缩放、球反演这几种变换(球反演变换是将单位球的外部反演到内部,单位球内部保持不动),通过它们的复合变换来实现空间压缩。</p><p>你可能想问,那应该怎样具体选择 <span class="math inline">\(f_1,\ldots,f_N\)</span>?我会在后面介绍,在着色器中实现IFS 是通过「空间折叠」操作进行的,这个操作可以看作是 <span class="math inline">\(F\)</span>的逆映射,它自动包含了多个压缩映射,所以你根本不需要单独指定每个 <span class="math inline">\(f_i\)</span>!</p><h1 id="轨道着色">轨道着色</h1><p>我们希望给分形染上漂亮的颜色,这个染色应该满足如下的条件:</p><ol type="1"><li>在分形上颜色是连续变化的;</li><li>在分形和非分形的交界处(即 <span class="math inline">\(A\)</span> 和<span class="math inline">\(A^c\)</span>的边界上)颜色应该是不连续的,从而产生泾渭分明的效果。</li></ol><p>做到这一点并不难,但是需要在每一次迭代时考虑当前点的位置信息。</p><p>我们首先取一个底色,比如说<code>color=vec3(0)</code>,在每一次迭代中,根据当前位置 <code>p</code>生成一个颜色,并以一定的权重加到 <code>color</code> 上。理论上颜色的 rgb的取值范围应该是 <span class="math inline">\([0,1]\)</span>,但是多数情况下我们要放宽到 <span class="math inline">\([-1,1]\)</span>之间,即颜色可以增加也可以减少。否则如果颜色只增不减的话,那么多次迭代以后rgb值很可能会溢出,变成白色。此外,随着迭代次数的增加,新颜色的权重应该单调下降,这样才能保证突出分形细节的部分。这也符合我们的生活直觉:想象一下,当一位画家作画时,在开始的时候他可以浓墨重彩地画一个轮廓,但是越到后面描绘更加精细的部分时,他就会换用更细的画笔,小心地蘸一点颜料。</p><p>根据 <code>p</code>生成颜色的着色方案无穷无尽,请随便发挥你的创造力。一般来说你需要反复试验各种不同的方案才能找到最合适的。下面的例子使用了一种非常流行的染色方案,它以<code>cos(vec3(0,1,2))</code> 作为底色,并根据当前时间<code>iTime</code> 以及坐标 <code>uv</code> 进行调整:</p><div class="codeAndCanvas" data="void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 uv = fragCoord / iResolution.xy; vec3 col = 0.5 + 0.45 * cos(vec3(0, 1, 2) + iTime + uv.yxy); fragColor = vec4(col, 1.0);}"></div><h1 id="着色器编程基础">着色器编程基础</h1><p>我简单介绍一下着色器编程的基本概念。打开 shadertoy网站,点击右上角的<code>新建</code>按钮,你会看到一个 <a href="https://www.shadertoy.com/new">最简单的动画</a>:</p><p><img style="margin:0px auto;display:block" width="600" src="/images/ifs/shadertoy.png"></p><p>左边的窗口是画布,显示渲染的结果;右边窗口是代码编辑器,你在这里书写着色器代码。</p><p>画布是由若干像素组成的,你需要根据每个像素的位置,即它的<code>fragCoord</code> 值指定一个颜色。这个过程是在<code>mainImage</code> 函数中实现的:</p><figure class="highlight glsl"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs glsl"><span class="hljs-type">void</span> mainImage(<span class="hljs-keyword">out</span> <span class="hljs-type">vec4</span> fragColor, <span class="hljs-keyword">in</span> <span class="hljs-type">vec2</span> fragCoord);<br></code></pre></td></tr></tbody></table></figure><p>其中 <code>fragCoord</code> 是像素的位置,<code>fragColor</code>是需要设置的像素颜色。</p><p>现实生活中有一个很形象的例子可以帮你理解着色器编程:假设你是一场方阵表演的导演,所有演员排成一个<span class="math inline">\(W\times H\)</span>的方阵,每个演员可以改变自己衣服的颜色。你的任务是发出合适的指令让每个演员根据自己的位置计算出正确的颜色,使得整个方阵呈现出漂亮的图案。</p><p><img style="margin:0px auto;display:block" width="600" src="/images/ifs/array.png"></p><p>如果你一个一个地对每个演员下指令,张三你应该显示红色,李四你应该显示蓝色,等等…对成千上万个演员,这么挨个下指令还不得把人累死?正确的做法是,你应该同时对所有演员发出相同的指令,比如:“每个人,根据自己的位置,<span class="math inline">\(x\)</span> 坐标 (即 <code>fragCoord.x</code>)是偶数的显示红色,<span class="math inline">\(x\)</span>坐标是奇数的显示蓝色”。这样你应该会看到红蓝相间、纵向排列的条纹图案。每个演员本质上就是一个单独的GPU计算单元,他们可以根据相同的指令独立计算各自的颜色。这种基于相同指令并行计算的工作方式才是GPU 流水线的机制。</p><p>再举一个例子,比如你希望阵列的中心呈现一个圆盘的图案,那就可以这样下指令:“每个人,把自己的纵坐标转换到区间<span class="math inline">\([-1, 1]\)</span>,然后计算各自到原点 <span class="math inline">\((0,0)\)</span> 的距离。距离大于 0.5的显示黑色,否则就显示红色”。这样一来,每个演员就会首先根据</p><figure class="highlight c"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs c">vec2 p = (<span class="hljs-number">2.0</span> * fragCoord - iResolution.xy) / iResolution.y;<br><span class="hljs-keyword">float</span> d = length(p) - <span class="hljs-number">0.5</span>;<br></code></pre></td></tr></tbody></table></figure><p>将自己的像素坐标 <code>fragCoord</code> 转换为世界坐标下的点<code>p</code>,使得 <code>fragCoord.y</code> 的范围是 <span class="math inline">\([-1,1]\)</span>。然后计算各自到以原点为中心、半径为0.5 的圆的距离 <span class="math inline">\(d\)</span>。最终根据</p><figure class="highlight c"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">if</span> (d > <span class="hljs-number">0.0</span>)<br> fragColor = vec4(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>);<br><span class="hljs-keyword">else</span><br> fragColor = vec4(<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>);<br></code></pre></td></tr></tbody></table></figure><p>来设置各自的颜色。</p><div class="codeAndCanvas" data="void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y; float d = length(p) - 0.5; if (d > 0.0) fragColor = vec4(0, 0, 0, 1); else fragColor = vec4(1, 0, 0, 1);}"></div><p>这个例子其实蕴含了 shader编程的一个非常核心的概念,即<strong>距离场</strong> (distancefield)。当我们想绘制某个图案时,我们通过计算像素到这个图案的距离来对像素进行着色。</p><h1 id="空间折叠">空间折叠</h1><p>在前面关于 IFS 的介绍中我们可以看到,如果有 <span class="math inline">\(N\)</span> 个不同的压缩映射 <span class="math inline">\(f_1,\ldots,f_N\)</span>,那么每次迭代后集合的数目会乘以<span class="math inline">\(N\)</span>,这是指数增长的,10次迭代可能会产生多达 <span class="math inline">\(10^N\)</span>个不同的集合。维护如此数量的集合是计算上不可行的。这该怎么办呢?</p><p>回忆我们之前介绍的,在着色器里面画 IFS就是给每个像素指定一个颜色,这个颜色应该由这个像素对应的空间中的点到分形的距离来决定。假设像素对应的空间中的点是<span class="math inline">\(p\)</span>,初始紧集是 <span class="math inline">\(K\)</span>,我们用迭代 <span class="math inline">\(n\)</span> 次的结果 <span class="math inline">\(F^n(K)\)</span> 作为分形的近似,这里 <span class="math inline">\(n\)</span> 是某个常数,在绝大多数场景下 <span class="math inline">\(n=30\)</span> 就足够了。我们需要计算距离 <span class="math inline">\(d(p,F^n(K))\)</span>并根据这个距离值对像素染色。我们已经看到 <span class="math inline">\(F^n(K)\)</span>是没法计算的,但有个巧妙的做法可以绕过这个困难:把 <span class="math inline">\(F^n\)</span> 挪到另一侧并取逆,转而计算 <span class="math inline">\(d(F^{-n}(p), K)\)</span>!实际上,如果 <span class="math inline">\(F\)</span>只包含旋转、平移、反射、缩放、球反演这些变换的话,<span class="math inline">\(d(p,F^n(K))\)</span> 和 <span class="math inline">\(d(F^{-n}(p), K)\)</span>之间存在非常简单的关系,我们可以通过计算后者来得到前者!这个关系的推导我放在后面介绍。</p><p>所以我们需要将压缩迭代映射的步骤倒过来,采取相反的操作:即将逆映射<span class="math inline">\(F^{-1}\)</span> 迭代作用在 <span class="math inline">\(p\)</span> 上,执行足够的迭代次数后,再绘制紧集<span class="math inline">\(K\)</span>。即我们实际是用一个<strong>空间折叠</strong>的映射作用反复作用在<span class="math inline">\(p\)</span>上,这个映射是<strong>放大距离</strong>的。</p><p>这个先折叠后画图的操作,也可以用一个生活中的例子来形象地解释,即剪纸艺术:</p><p><img style="margin:0px auto;display:block" width="480" src="/images/ifs/papercut.jpg"></p><p>在剪纸过程中,首先把纸张反复折叠,然后在折叠后的纸张上画出某个特定的图形,沿着这个图形裁剪,再将纸张展开得到的就是美丽的图案。</p><p>这里<strong>展开</strong>纸张的操作对应的就是迭代映射 <span class="math inline">\(F\)</span>,它把一个初始的紧集铺开到空间中变成分形,是个「一对多」的映射;<strong>折叠</strong>纸张的操作就是<span class="math inline">\(F^{-1}\)</span>,它把分形折叠回最初的紧集,是个「多对一」的映射。</p><p>在下面的例子中,我们首先用 <code>p = abs(p)</code>将整个空间折叠到第一象限,然后只要在第一象限中画一个圆,就可以同时在其它位置得到总共4 个圆:</p><div class="codeAndCanvas" data="void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y; p = abs(p); //折叠 float d = length(p - vec2(0.5)) - 0.3; // 中心在 (0.5, 0.5), 半径为 0.3 的圆 d = smoothstep(-0.005, 0.005, d); // 平滑一下边缘 vec3 col = mix(vec3(0), vec3(0.9), d); //染色 fragColor = vec4(col, 1);}"></div><p>如果你去看那些分形作品的代码的话,会发现它们几乎都使用了<code>abs</code>函数。这个函数是多对一的,它会把整个空间折叠到第一象限,这对应了 <span class="math inline">\(F\)</span> 包含 4 个不同的 <span class="math inline">\(f_i\)</span> (这是在 2D 的情形;3D 的情形会有 8个不同的 <span class="math inline">\(f_i\)</span>)。进一步,再叠加关于其它平面/球面的折叠可以产生出更多的<span class="math inline">\(f_i\)</span>。这就解释了为什么在着色器中我们不需要显示地写出每个单独的<span class="math inline">\(f_i\)</span>。</p><p>最后我们来推导 <span class="math inline">\(d(p,F^n(K))\)</span> 和<span class="math inline">\(d(F^{-n}(p), K)\)</span> 之间的关系。</p><p>如果 <span class="math inline">\(F\)</span>是平移、旋转、反射这样的保持 Euclidean 距离不变的刚体运动,那么自然有<span class="math display">\[d(p, F(K)) = d(F^{-1}(p), K).\]</span></p><p>但如果 <span class="math inline">\(F\)</span> 是一个缩放变换,比如<span class="math inline">\(F(x) = x/s\,(s>1)\)</span>,那么 <span class="math display">\[d(p, F(K)) = d(p, 1/s\cdot K) = 1/s\cdot d(s\cdotp, K) = 1/s\cdot d(F^{-1}(p), K).\]</span>即我们要对折叠以后算出来的距离值再除以 <span class="math inline">\(s\)</span>。</p><p>对球的反演变换也有类似的结论,这是因为球反演在空间中每个点的局部是一个缩放。</p><p>既然每次迭代 <span class="math inline">\(F\)</span> 以比例 <span class="math inline">\(1/s\)</span> 缩小,那么用 <span class="math inline">\(F^{-1}\)</span> 迭代 <span class="math inline">\(n\)</span> 次以后累积放大的比例就是 <span class="math inline">\(s^n\)</span>,我们要将 <span class="math inline">\(d(F^{-n}(p), K)\)</span> 再除以 <span class="math inline">\(s^n\)</span> 才是最终正确的距离值。</p><p>对于一般的映射 <span class="math inline">\(F\)</span>,我们可以通过计算 <span class="math inline">\(F^{-1}\)</span> 在 <span class="math inline">\(p\)</span> 处 Jacobian 矩阵的行列式的绝对值,作为<span class="math inline">\(p\)</span> 处缩放的近似。</p><h1 id="实战演示">实战演示</h1><p>我以 Shadertoy 上一个混淆过的 <a href="https://www.shadertoy.com/view/WlGyWK">作品</a>为例子来完整展示上面的理论。下面是重新改写后的可读版本,我在注释中解释了每一步的含义:</p><iframe width="640" height="360" frameborder="0" src="https://www.shadertoy.com/embed/l3cXR7?gui=true&t=10&paused=true&muted=false" allowfullscreen=""></iframe>]]></content>
<summary type="html">
<p>本文整理自我 2024 年 6 月 14
日在上海科技大学数学所的一个小报告,标题是「GPU
涂鸦与数学可视化」。我保留了报告的技术内容,略去了报告中介绍 demoscene
文化和分形文化的部分。</p></summary>
<category term="Shadertoy" scheme="https://pywonderland.com/categories/Shadertoy/"/>
</entry>
<entry>
<title>咖啡杯中的焦散线</title>
<link href="https://pywonderland.com/envelope-and-caustics/"/>
<id>https://pywonderland.com/envelope-and-caustics/</id>
<published>2024-02-08T16:00:00.000Z</published>
<updated>2024-11-07T15:12:55.394Z</updated>
<content type="html"><![CDATA[<p>春节的晚上,外面鞭炮喧天,家人在看电视,我躲在屋里看数学,还是挺惬意的。</p><p>我看的是 <a href="https://johncarlosbaez.wordpress.com">John Baez</a>和 <a href="https://www.gregegan.net">Greg Egan</a> 的博客。John Baez是一位在科普方面非常高产的数学家,写过不计其数的科普文章。读他的文章非常让人享受,因为他总是从直观的例子入手,一步步启发读者,展开到更高级的数学。GregEgan是澳大利亚的一位非常高产的科幻小说作家,有不少作品已经被国内引入。他的小说属于硬科幻风格,而且是非常硬的那种。他也有不少有趣的<a href="https://www.gregegan.net/SCIENCE/Science.html">博客文章</a>。不过与John Baez 不同的是,Greg Egan的文章不太会去兼顾不同水平的读者,对我来说,要看懂他在说什么经常不是一件容易的事情。</p><p>John Baez 博客上有一个系列 <a href="https://johncarlosbaez.wordpress.com/2012/08/31/rolling-circles-and-balls-part-1/">Rollingcircles and balls</a> 讨论了圆的外摆线和焦散,Greg Egan 也有一篇 <a href="https://www.gregegan.net/SCIENCE/Catacaustics/Catacaustics.html">文章</a>更深入的讨论了曲线的焦散。这个话题非常有意思,我也一时手痒写代码实验了一番并记录在此。</p><span id="more"></span><h1 id="pov-ray-光学实验">POV-Ray 光学实验</h1><p>有个有趣的物理现象,当光线照在咖啡杯的内壁上时,光线反射以后会形成一个亮斑,术语叫做焦散(caustic)。</p><p><img style="margin:0px auto;display:block" width="400" src="/images/caustics/cup.jpg"></p><p>形成焦散的原因是,光线在杯子内壁反射以后,光子的分布是不均匀的,某些区域经过的光子特别密集,所以亮度就更高。</p><p>焦散是一条曲线,它和所有的反射光线相切,即它是所有反射光线的包络(envelope)。焦散的具体形状和杯子的形状、光源的位置都有关。假设杯子是圆形的,则当光源是一个点光源且恰好位于杯子边缘上某一点时,得到的焦散叫做<a href="https://en.wikipedia.org/wiki/Cardioid">心脏线</a>(cardioid)。当光源位于无穷远时(可以视作平行光源),得到的焦散是 <a href="https://en.wikipedia.org/wiki/Nephroid">肾形线</a>(nephroid)。一般情况下焦散的形状介于心脏线和肾形线之间。</p><p>更有意思的是,如果杯子的外形是心脏线,而且光源正好位于心脏线的尖点时,得到的焦散正好是肾形线。我写了一个<a href="https://github.com/neozhaoliang/pywonderland/tree/master/src/catacaustic">POV-Ray脚本</a>,模拟了这一现象:</p><table><colgroup><col style="width: 50%"><col style="width: 50%"></colgroup><tbody><tr class="odd"><td style="text-align: center;">圆形杯子给出心脏线</td><td style="text-align: center;">心脏线杯子给出肾形线</td></tr><tr class="even"><td style="text-align: center;"><img style="margin:0px auto;display:block" width="400" src="/images/caustics/caustics_cardioid.png"></td><td style="text-align: center;"><img style="margin:0px auto;display:block" width="400" src="/images/caustics/caustics_nephroid.png"></td></tr></tbody></table><p>心脏线和肾形线都是所谓的 <a href="https://en.wikipedia.org/wiki/Epicycloid">外摆线</a>,只是两圆的半径之比不同:</p><table><colgroup><col style="width: 50%"><col style="width: 50%"></colgroup><tbody><tr class="odd"><td style="text-align: center;">心脏线</td><td style="text-align: center;">肾形线</td></tr><tr class="even"><td style="text-align: center;"><video src="/images/caustics/cardioid.mp4" controls=""></video></td><td style="text-align: center;"><video src="/images/caustics/nephroid.mp4" controls=""></video></td></tr></tbody></table><p>所以我们很容易作出如下的猜想:如果在肾形线的内部或者尖点放一个光源,是不是又会得到下一个外摆线?然而根据Greg Egan 在他博客中的实验,这个应该是不成立的。</p><p>理论上这个 POV-Ray 脚本可以渲染任意的参数曲线反射的效果,但是 POV-Ray渲染 parametric surface + radiosity非常慢,所以如果你想试一试的话,最好还是用 POV-Ray 原生的 CSG来构造曲线。</p><h1 id="求解参数曲线的焦散">求解参数曲线的焦散</h1><p>这一节我们来介绍怎样计算一般的参数曲线 <span class="math inline">\(\mathbf{c}(t)=(x(t),y(t))\)</span>的焦散曲线。</p><p>设点光源的位置是 <span class="math inline">\((a,b)\)</span>,则在曲线上的一点 <span class="math inline">\((x,y)\)</span> 处,入射光线的方向是 <span class="math display">\[\mathbf{l}=(x-a,y-b).\]</span> 不需要把 <span class="math inline">\(\mathbf{l}\)</span>单位化,因为我们列方程的时候只需要光线的方向,并不在乎长度。</p><p>同样是在 <span class="math inline">\((x,y)\)</span>处,曲线的法向量是 <span class="math display">\[\mathbf{n}=\frac{(-y',x')}{\sqrt{(x')^2+(y')^2}}=\frac{(-y',x')}{|\mathbf{c}'|}.\]</span> 这里我们用 <span class="math inline">\(x',y'\)</span> 表示 <span class="math inline">\(x,y\)</span> 关于 <span class="math inline">\(t\)</span> 的导数。</p><p>于是 <span class="math inline">\((x,y)\)</span> 处的反射光线的方向<span class="math inline">\(\mathbf{r}\)</span> 由如下反射公式给出:<span class="math display">\[\mathbf{r}= \mathbf{l}- 2(\mathbf{l}\cdot\mathbf{n})\mathbf{n}.\]</span> 设 <span class="math inline">\((X,Y)\)</span> 是反射光线上的任一点,由于 <span class="math inline">\((x,y)\)</span> 是反射光线的起点,所以 <span class="math inline">\((X-x,Y-y)\)</span> 与 <span class="math inline">\(\mathbf{r}\)</span> 平行。记 <span class="math inline">\(\mathbf{r}=(r_x,r_y)\)</span>,则 <span class="math inline">\((X-x,Y-y)\)</span> 与 <span class="math inline">\((-r_y, r_x)\)</span> 垂直,即 <span class="math display">\[(X-x, Y-y)\cdot(-r_y, r_x)=0.\]</span> 记 <span class="math display">\[F(X,Y,t)=(X-x, Y-y)\cdot(-r_y, r_x),\]</span>则我们得到了反射光线 <span class="math inline">\((X(t), Y(t))\)</span>满足的曲线族方程 <span class="math inline">\(F(X,Y,t)=0\)</span>。于是焦散曲线可以通过联立方程组<span class="math display">\[\begin{align}F(X,Y,t)=0,\\\frac{\partialF}{\partial t}F(X,Y,t)=0.\end{align}\]</span> 也就是 <span class="math display">\[\begin{align}(X-x, Y-y)\cdot(-r_y,r_x)&=0,\\-(x',y')\cdot(-r_y, r_x) +(X-x,Y-y)\cdot(-r_y', r_x') &=0.\end{align}\]</span>然后解出 <span class="math inline">\(X,Y\)</span> 得到。</p><p>如果你还记得 2x2矩阵的逆公式的话,这个方程组其实可以目视写出解来。我们把它写成</p><p><span class="math display">\[\begin{pmatrix}-r_y & r_x\\-r_y'&r_x'\end{pmatrix}\cdot\begin{pmatrix}X-x\\Y-y\end{pmatrix}=\begin{pmatrix}0\\r_xy'-r_yx'\end{pmatrix}.\]</span> 于是 <span class="math display">\[\begin{align}\begin{pmatrix}X-x\\Y-y\end{pmatrix}&=\begin{pmatrix}-r_y& r_x\\ -r_y'&r_x'\end{pmatrix}^{-1}\begin{pmatrix}0\\r_xy'- r_yx'\end{pmatrix}\\&=\frac{1}{r_xr_y'-r_yr_x'}\begin{pmatrix}r_x' &-r_x\\ r_y'&-r_y\end{pmatrix}\begin{pmatrix}0\\r_xy'-r_yx'\end{pmatrix}\\&=\frac{r_xy'-r_yx'}{r_yr_x'-r_xr_y'}\begin{pmatrix}r_x\\r_y\end{pmatrix}.\end{align}\]</span> 即 <span class="math display">\[\begin{pmatrix}X\\Y\end{pmatrix}=\begin{pmatrix}x\\y\end{pmatrix} +\frac{r_xy'-r_yx'}{r_yr_x'-r_xr_y'}\begin{pmatrix}r_x\\r_y\end{pmatrix}.\]</span></p><p>按照上面的理论,我写了一个小脚本,用 <code>sympy</code>(version=1.12) 来计算圆的焦散线。其中圆的中心在原点,半径为 1,光源在<span class="math inline">\((1,0)\)</span> 处。</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> sympy <span class="hljs-keyword">import</span> *<br><br>t, X, Y = symbols(<span class="hljs-string">"t X Y"</span>)<br>C = Matrix([cos(t), sin(t)]) <span class="hljs-comment"># curve</span><br>light = Matrix([<span class="hljs-number">1</span>, <span class="hljs-number">0</span>]) <span class="hljs-comment"># light source</span><br>l = C - light <span class="hljs-comment"># incident ray</span><br>dx, dy = diff(C, t)<br>n = Matrix([dy, -dx]) <span class="hljs-comment"># normal vector</span><br>r = simplify(l - <span class="hljs-number">2</span> * l.dot(n) * n / n.dot(n)) <span class="hljs-comment"># reflected ray</span><br>F = (Y - y) * r[<span class="hljs-number">0</span>] - (X - x) * r[<span class="hljs-number">1</span>]<br>dF = diff(F, t)<br>result = solve((F, dF), X, Y) <span class="hljs-comment"># solve the envelope</span><br>print(<span class="hljs-string">f"X(t)=<span class="hljs-subst">{trigsimp(result[X], method=<span class="hljs-string">'groebner'</span>)}</span>"</span>)<br>print(<span class="hljs-string">f"Y(t)=<span class="hljs-subst">{trigsimp(result[Y], method=<span class="hljs-string">'groebner'</span>)}</span>"</span>)<br></code></pre></td></tr></tbody></table></figure><p><code>sympy</code> 给出的结果是:</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python">X(t)=<span class="hljs-number">2</span>*cos(t)/<span class="hljs-number">3</span> + cos(<span class="hljs-number">2</span>*t)/<span class="hljs-number">3</span><br>Y(t)=<span class="hljs-number">2</span>*sin(t)/<span class="hljs-number">3</span> + sin(<span class="hljs-number">2</span>*t)/<span class="hljs-number">3</span><br></code></pre></td></tr></tbody></table></figure><p>这正是喜闻乐见的心脏线的参数表示: <span class="math display">\[\left\{\begin{align}x(t)&=\frac{\cos(2t) +2\cos(t)}{3},\\ y(t)&=\frac{\sin(2t) +2\sin(t)}{3}.\end{align}\right.\]</span></p><p>使用这个参数表示,我们继续计算当光源放在心脏线的尖点,即 <span class="math inline">\(t=\pi\)</span> 对应的点 <span class="math inline">\((-\frac{1}{3},0)\)</span> 时得到的焦散:</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> sympy <span class="hljs-keyword">import</span> *<br><br>t, X, Y = symbols(<span class="hljs-string">"t X Y"</span>)<br>C = Matrix([(<span class="hljs-number">2</span>*cos(t) + cos(<span class="hljs-number">2</span>*t)) / <span class="hljs-number">3</span>, (<span class="hljs-number">2</span>*sin(t) + sin(<span class="hljs-number">2</span>*t)) / <span class="hljs-number">3</span>])<br>light = Matrix([S(<span class="hljs-string">'-1/3'</span>, evaluate=<span class="hljs-literal">False</span>), <span class="hljs-number">0</span>])<br>l = C - light <span class="hljs-comment"># incident ray</span><br>dx, dy = diff(C, t)<br>n = Matrix([dy, -dx]) <span class="hljs-comment"># normal vector</span><br>r = simplify(l - <span class="hljs-number">2</span> * l.dot(n) * n / n.dot(n)) <span class="hljs-comment"># reflected ray</span><br>F = (Y - y) * r[<span class="hljs-number">0</span>] - (X - x) * r[<span class="hljs-number">1</span>]<br>dF = diff(F, t)<br>result = solve((F, dF), X, Y) <span class="hljs-comment"># solve the envelope</span><br>print(<span class="hljs-string">f"X(t)=<span class="hljs-subst">{trigsimp(result[X], method=<span class="hljs-string">'groebner'</span>)}</span>"</span>)<br>print(<span class="hljs-string">f"Y(t)=<span class="hljs-subst">{trigsimp(result[Y], method=<span class="hljs-string">'groebner'</span>)}</span>"</span>)<br></code></pre></td></tr></tbody></table></figure><p><code>sympy</code> 很快算出了正确的结果:</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python">X(t)=sin(t)*sin(<span class="hljs-number">2</span>*t)/<span class="hljs-number">3</span> + cos(t)/<span class="hljs-number">3</span><br>Y(t)=-sin(t)*cos(<span class="hljs-number">2</span>*t)/<span class="hljs-number">3</span> + sin(t)/<span class="hljs-number">3</span><br></code></pre></td></tr></tbody></table></figure><p>不难验证</p><p><span class="math display">\[\begin{align}\frac{\sin(t)\sin(2t) +\cos(t)}{3}&=\frac{3\cos(t) - \cos(3t)}{6},\\ \frac{-\sin(t)\cos(2t)+ \sin(t)}{3}&=\frac{3\sin(t) -\sin(3t)}{6}.\end{align}\]</span></p><p>这正是 <a href="https://en.wikipedia.org/wiki/Nephroid#Parametric">维基百科</a>中所列的肾形线的参数方程中取 <span class="math inline">\(a=1/6\)</span>的结果。</p><p>把上面的曲线画出来是这样的:</p><p><img style="margin:0px auto;display:block" width="400" src="/images/caustics/caustics_matplotlib.svg"></p><h1 id="求解多项式曲线的焦散">求解多项式曲线的焦散</h1><p>很多时候曲线的方程是通过隐函数 <span class="math inline">\(P(x,y)=0\)</span> 的形式给出的,其中 <span class="math inline">\(P(x,y)\)</span> 是关于两个变元 <span class="math inline">\(x,y\)</span>的多项式。这样的曲线叫做平面代数曲线。这时求解焦散要用到 Gröbner基的工具。</p><p>让我们回到参数方程的情形,我们已经看到,这时焦散 <span class="math inline">\((X,Y)\)</span> 有显式解</p><p><span class="math display">\[\begin{pmatrix}X\\Y\end{pmatrix}=\begin{pmatrix}x\\y\end{pmatrix} +\frac{r_xy'-r_yx'}{r_yr_x'-r_xr_y'}\begin{pmatrix}r_x\\r_y\end{pmatrix}.\]</span></p><p>其中 <span class="math inline">\(x,y,r_x,r_y\)</span> 都是关于 <span class="math inline">\(t\)</span>的函数,它们的导数也是可计算的,所以可以算出 <span class="math inline">\((X,Y)\)</span> 来。</p><p>但是在隐函数的情形,我们没有 <span class="math inline">\(x,y\)</span>的某种关于 <span class="math inline">\(t\)</span>的表达式。不过没关系,我们先假设有这样的参数表达式,看看能得到什么结论。设<span class="math inline">\(x=x(t),y=y(t)\)</span> 是某个参变元 <span class="math inline">\(t\)</span> 的函数,在 <span class="math inline">\(P(x,y)=0\)</span> 两边对 <span class="math inline">\(t\)</span> 求导可得 <span class="math display">\[\frac{\partial P}{\partial t}=\frac{\partialP}{\partial x}x'(t) + \frac{\partial P}{\partialy}y'(t)=0.\]</span> 记 <span class="math inline">\(k=-\frac{\partialP}{\partial x}/\frac{\partial P}{\partial y}\)</span>,则 <span class="math inline">\(y'(t)=kx'(t)\)</span>。</p><p>对反射光线 <span class="math inline">\(\mathbf{r}\)</span> 的两个分量<span class="math inline">\(r_x,r_y\)</span> 也分别使用链式求导,我们有<span class="math display">\[\begin{align}\frac{\partial r_x}{\partialt}&=\frac{\partial r_x}{\partial x}x'(t) + \frac{\partialr_x}{\partial y}y'(t),\\\frac{\partial r_y}{\partial t}&=\frac{\partial r_y}{\partialx}x'(t) + \frac{\partial r_y}{\partialy}y'(t).\end{align}\]</span> 于是我们发现比值 <span class="math display">\[\begin{align}\frac{r_xy'-r_yx'}{r_yr_x'-r_xr_y'}&=\frac{r_xk-r_y}{r_y(\frac{\partialr_x}{\partial x}+\frac{\partial r_x}{\partial y}k)-r_x(\frac{\partialr_y}{\partial x} + \frac{\partial r_y}{\partial y}k)}\\&=-\frac{r_x\frac{\partial P}{\partial x}+r_y\frac{\partialP}{\partial y}}{r_y(\frac{\partial r_x}{\partial x}\frac{\partialP}{\partial y}-\frac{\partial r_x}{\partial y}\frac{\partial P}{\partialx})-r_x(\frac{\partial r_y}{\partial x}\frac{\partial P}{\partial y} -\frac{\partial r_y}{\partial y}\frac{\partial P}{\partial x})}.\end{align}\]</span> 变成了一个不需要显式用到 <span class="math inline">\(t\)</span> 的量,即变量 <span class="math inline">\(t\)</span> “消掉”了。代入上面焦散的表达式中,我们得到 <span class="math display">\[\begin{pmatrix}X\\Y\end{pmatrix}=\begin{pmatrix}x\\y\end{pmatrix}-\frac{r_x\frac{\partialP}{\partial x}+r_y\frac{\partial P}{\partial y}}{r_y(\frac{\partialr_x}{\partial x}\frac{\partial P}{\partial y}-\frac{\partialr_x}{\partial y}\frac{\partial P}{\partial x})-r_x(\frac{\partialr_y}{\partial x}\frac{\partial P}{\partial y} - \frac{\partialr_y}{\partial y}\frac{\partial P}{\partial x})}\begin{pmatrix}r_x\\r_y\end{pmatrix}.\]</span> 这个式子还可以再简化一点:注意到曲线在 <span class="math inline">\((x,y)\)</span> 处的法向量由 <span class="math inline">\(\mathbf{n}=\frac{\nabla P}{|\nabla P|}\)</span>给出,其中 <span class="math inline">\(\nabla P=(\frac{\partialP}{\partial x},\frac{\partial P}{\partial y})\)</span>。于是由 <span class="math display">\[\mathbf{r}= \mathbf{l}- 2(\mathbf{l}\cdot\mathbf{n})\mathbf{n}\]</span> 可得 <span class="math display">\[\mathbf{r}\cdot \nabla P=-\mathbf{l}\cdot \nablaP.\]</span> 从而 <span class="math display">\[\begin{pmatrix}X\\Y\end{pmatrix}=\begin{pmatrix}x\\y\end{pmatrix}+\frac{\mathbf{l}\cdot\nablaP}{r_y(\frac{\partial r_x}{\partial x}\frac{\partial P}{\partialy}-\frac{\partial r_x}{\partial y}\frac{\partial P}{\partialx})-r_x(\frac{\partial r_y}{\partial x}\frac{\partial P}{\partial y} -\frac{\partial r_y}{\partial y}\frac{\partial P}{\partialx})}\begin{pmatrix}r_x\\ r_y\end{pmatrix}.\]</span></p><p>这是四个变量 <span class="math inline">\(x,y,X,Y\)</span>满足的两个方程,形如 <span class="math inline">\(F(X,x,y)=0\)</span> 和<span class="math inline">\(G(Y,x,y)=0\)</span>。记住我们还有已知的方程<span class="math inline">\(P(x,y)=0\)</span>。为了从这三个方程中消掉<span class="math inline">\(x,y\)</span>,得到一个仅包含 <span class="math inline">\((X,Y)\)</span> 的表达式,我们可以尝试用 <a href="https://en.wikipedia.org/wiki/Gr%C3%B6bner_basis">Gröbnerbasis</a> 方法。Gröbner 基方法会把多项式方程组 <span class="math display">\[F=G=P=0\]</span> 转化为一组等价的新方程组 <span class="math display">\[g_1=g_2=\cdots=g_m=0.\]</span>即它们有完全相同的解集。</p><p><span class="math inline">\(\{g_1,\ldots,g_m\}\)</span> 是 <span class="math inline">\(F,G,P\)</span> 在多项式环 <span class="math inline">\(\mathbb{R}[x,y,X,Y]\)</span> 中生成的理想 <span class="math inline">\(I=\langle F,G,P\rangle\)</span>的一组生成元,<span class="math inline">\(\{g_1,\ldots,g_m\}\)</span>叫做 <span class="math inline">\(I\)</span> 的约化的 Gröbner基。在字典序 <span class="math inline">\(x\succ y\succ X\succ Y\)</span>下,约化的 Gröbner 基会有一个好的属性,即从 <span class="math inline">\(g_1\)</span> 到 <span class="math inline">\(g_m\)</span>,其中的变元会按照从 <span class="math inline">\(x\to y\to X\to Y\)</span>的先后顺序被消除掉。注意这是个不太严格的说法,我们并不是总能消掉顺序靠前的变元,但是如果消除发生的话,它就会按照这个顺序来。这样我们就可以执行类似高斯消元法中的回代操作,从而新方程组的求解会更加简单。</p><p>我们来用 <code>sympy</code> 实验一下:</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> sympy <span class="hljs-keyword">import</span> *<br><br>x, y, X, Y = symbols(<span class="hljs-string">"x y X Y"</span>)<br>P = x**<span class="hljs-number">2</span> + y**<span class="hljs-number">2</span> - <span class="hljs-number">1</span><br>dx = diff(P, x) <span class="hljs-comment"># gradient of P</span><br>dy = diff(P, y)<br>curve = Matrix([x, y])<br>light_source = Matrix([<span class="hljs-number">1</span>, <span class="hljs-number">0</span>])<br>l = curve - light_source <span class="hljs-comment"># the incident ray</span><br>n = Matrix([dx, dy]) <span class="hljs-comment"># the normal vector</span><br>r = simplify(l - <span class="hljs-number">2</span> * l.dot(n) * n / n.dot(n)) <span class="hljs-comment"># the reflected ray</span><br>rx, ry = r<br>dxrx = diff(rx, x)<br>dyrx = diff(rx, y)<br>dxry = diff(ry, x)<br>dyry = diff(ry, y)<br>denominator = ry * (dxrx * dy - dyrx * dx) - rx * (dxry * dy - dyry * dx)<br>nominator = dx * l[<span class="hljs-number">0</span>] + dy * l[<span class="hljs-number">1</span>]<br>F = (X - x) * denominator - nominator * rx<br>G = (Y - y) * denominator - nominator * ry<br>eqs = [eq.as_numer_denom()[<span class="hljs-number">0</span>] <span class="hljs-keyword">for</span> eq <span class="hljs-keyword">in</span> [F, G, P]]<br>gb = groebner(eqs, [x, y, X, Y])<br>print(gb)<br></code></pre></td></tr></tbody></table></figure><p><code>sympy</code> 给出的结果的最后一项是</p><p><span class="math display">\[27 X^{4} y^{2} + 54 X^{2} Y^{2} y^{2} -18 X^{2} y^{2} - 8 X y^{2} + 27 Y^{4} y^{2} - 18 Y^{2} y^{2} -y^{2}.\]</span></p><p><span class="math inline">\(x\)</span> 被消掉了!原方程组 <span class="math inline">\(F=G=P\)</span>的解必然是上面这个方程的解的子集。观察它的每一项都带有一个 <span class="math inline">\(y^2\)</span>,这显然不是我们要的解。把 <span class="math inline">\(y^2\)</span> 去掉,剩下的因子</p><p><span class="math display">\[27X^{4}+54X^{2}Y^{2}-18X^{2} -8X +27Y^{4}-18Y^{2}-1=0.\]</span></p><p>就是心脏线的隐函数表示。不信?在 <a href="https://www.desmos.com/geometry/vtjbq3ete1">Desmos</a>里面画出来看看!</p><p><img style="margin:0px auto;display:block" width="300" src="/images/caustics/desmos_cardioid.png"></p><h1 id="注记">注记</h1><p>这篇文章主要覆盖了 Greg Egan博文的前半部分,他的后半部分内容我觉得有点放飞自我,也没怎么仔细看。</p><p>虽然我们用 <code>sympy</code>的实验很成功,但注意并不是所有情况下都能得到焦散曲线(比如光源位于抛物线的焦点时,反射光线都是平行的),而且对复杂的曲线<code>sympy</code> 算起来非常慢。</p><p>我研究生的时候上过计算机代数的课程,当时用的教学软件是 Maple。Maple编程是很不方便的,所以我其实没有多少计算机代数的编程经验。我之前一直觉得<code>sympy</code>运行又慢,输出的表达式也不够简化,所以不太愿意用它。这次实验有点刷新我对<code>sympy</code>的认知。我还记得当时课程要求每人提交一份读书报告,我写的是 <a href="https://link.springer.com/book/10.1007/978-3-319-16721-3">Ideals,Varieties, and Algorithms</a> 的笔记,但毕业多年以来这还是我第一次用到Gröbner 基!</p><p>我写这篇文章的时候正好临近情人节,所以我在想有没有什么曲线的焦散能给出<a href="https://www.desmos.com/geometry/ngdpq0zrei">爱心曲线</a>:</p><p><img style="margin:0px auto;display:block" width="300" src="/images/caustics/heart.png"></p><p>于是我找到了 <a href="https://www.tandfonline.com/doi/full/10.1080/00029890.2020.1722019">这篇文章</a>。不过看起来里面给出的结论计算量很大,很难用在爱心线上(也许是我错了)。</p>]]></content>
<summary type="html">
<p>春节的晚上,外面鞭炮喧天,家人在看电视,我躲在屋里看数学,还是挺惬意的。</p>
<p>我看的是 <a href="https://johncarlosbaez.wordpress.com">John Baez</a>
和 <a href="https://www.gregegan.net">Greg Egan</a> 的博客。John Baez
是一位在科普方面非常高产的数学家,写过不计其数的科普文章。读他的文章非常让人享受,因为他总是从直观的例子入手,一步步启发读者,展开到更高级的数学。Greg
Egan
是澳大利亚的一位非常高产的科幻小说作家,有不少作品已经被国内引入。他的小说属于硬科幻风格,而且是非常硬的那种。他也有不少有趣的
<a href="https://www.gregegan.net/SCIENCE/Science.html">博客文章</a>。不过与
John Baez 不同的是,Greg Egan
的文章不太会去兼顾不同水平的读者,对我来说,要看懂他在说什么经常不是一件容易的事情。</p>
<p>John Baez 博客上有一个系列 <a href="https://johncarlosbaez.wordpress.com/2012/08/31/rolling-circles-and-balls-part-1/">Rolling
circles and balls</a> 讨论了圆的外摆线和焦散,Greg Egan 也有一篇 <a href="https://www.gregegan.net/SCIENCE/Catacaustics/Catacaustics.html">文章</a>
更深入的讨论了曲线的焦散。这个话题非常有意思,我也一时手痒写代码实验了一番并记录在此。</p></summary>
<category term="pywonderland 项目" scheme="https://pywonderland.com/categories/pywonderland-%E9%A1%B9%E7%9B%AE/"/>
</entry>
<entry>
<title>Möbius 变换与球的刚体运动</title>
<link href="https://pywonderland.com/mobius-ball-rigid-motion/"/>
<id>https://pywonderland.com/mobius-ball-rigid-motion/</id>
<published>2022-05-07T16:00:00.000Z</published>
<updated>2024-08-10T10:22:21.061Z</updated>
<content type="html"><![CDATA[<p>五一期间我写了一个 shadertoy 小动画,演示 Möbius变换与球的刚体运动之间的关系:</p><span id="more"></span><iframe width="640" height="360" frameborder="0" src="https://www.shadertoy.com/embed/fljfRm?gui=true&t=10&paused=true&muted=false" allowfullscreen=""></iframe><p>这个动画的名字叫做 Möbius transformation revealed,想法源自 DouglasN. Arnold 和 Jonathan Rogness 于 2007 年发布的 <a href="https://www-users.cse.umn.edu/~arnold/moebius/">同名视频</a>。这是一个很有名的视频,它表达的核心思想是,扩充复平面<span class="math inline">\(\overline{\mathbb{C}}\)</span> 上的 Möbius变换可以由球在三维空间中的刚体运动给出:</p><ol type="1"><li>我们称一个球 <span class="math inline">\(S\)</span>是<strong>容许</strong>的,如果 <span class="math inline">\(S\)</span>的最高点,也就是北极点位于半空间 <span class="math inline">\(\{z>0\}\)</span> 中。</li><li>取任何一个可容许的球 <span class="math inline">\(S\)</span>,将<span class="math inline">\(\overline{\mathbb{C}}\)</span>在逆球极投影下对应到 <span class="math inline">\(S\)</span>的球面上。</li><li>对 <span class="math inline">\(S\)</span> 作刚体变换 (平移和旋转)<span class="math inline">\(S\to T(S)\)</span>,使得 <span class="math inline">\(T(S)\)</span> 也是一个容许的球,即 <span class="math inline">\(T(S)\)</span> 的最高点也在半空间 <span class="math inline">\(\{z>0\}\)</span> 中。</li><li>将 <span class="math inline">\(T(S)\)</span>的表面通过球极投影再映射回 <span class="math inline">\(\overline{\mathbb{C}}\)</span>,我们就得到了一个<span class="math inline">\(\overline{\mathbb{C}}\to\overline{\mathbb{C}}\)</span>的变换,此变换是一个 Möbius 变换,且所有 Möbius变换都可以通过此种方式得到。</li></ol><p>整个过程如下所示:</p><p><span class="math display">\[\underbrace{\overline{\mathbb{C}}\xrightarrow{\text{inversestereographic projection}} S\xrightarrow{\text{rigid motion}}T(S)\xrightarrow{\text{stereographicprojection}}\overline{\mathbb{C}}}_{\text{Möbiustransformation}}.\]</span></p><div class="statement note definition unnumbered"><p><span class="statement-heading"><span class="statement-label">注</span>:</span><span class="statement-spah"></span>球极投影使用的北极点始终是球面的最高点。</p></div><p>详细的解释可以见原视频的解释 <a href="https://www-users.cse.umn.edu/~arnold/papers/moebius.pdf">文章</a>。但是从直观上理解也不难:</p><ul><li><p><span class="math inline">\(S\)</span> 在 <span class="math inline">\(xy\)</span> 平面内的平移给出的是 <span class="math inline">\(\overline{\mathbb{C}}\)</span> 上的平移。<video src="/images/mobius/translation.mp4" height="300" controls=""></video></p></li><li><p><span class="math inline">\(S\)</span> 在 <span class="math inline">\(z\)</span> 方向上的平移给出的是 <span class="math inline">\(\overline{\mathbb{C}}\)</span> 上的缩放。<video src="/images/mobius/scale.mp4" height="300" controls=""></video></p></li><li><p>保持 <span class="math inline">\(S\)</span>的北极点不动的旋转给出的是 <span class="math inline">\(\overline{\mathbb{C}}\)</span> 上的旋转。<video src="/images/mobius/rotation.mp4" height="300" controls=""></video></p></li><li><p>绕 <span class="math inline">\(x\)</span> 轴旋转 180 度给出的是<span class="math inline">\(\overline{\mathbb{C}}\)</span> 上的逆变换<span class="math inline">\(z\to 1/z\)</span>。<video src="/images/mobius/inversion.mp4" height="300" controls=""></video></p></li></ul><p>以上几种运动方式的复合可以给出可容许球的任何刚体运动,而任何 Möbius变换都是平移、缩放、旋转、逆变换的复合,所以 Möbius变换确实与可容许球体的刚体运动是对应的。</p><p>反过来对给定的 Möbius 变换 <span class="math inline">\(M\)</span>和容许的球 <span class="math inline">\(S\)</span>,当 <span class="math inline">\(S\)</span> 的初始位置确定以后,给出 <span class="math inline">\(M\)</span> 的刚体运动 <span class="math inline">\(T\)</span> 也是唯一确定的。证明见 <a href="https://scholar.rose-hulman.edu/cgi/viewcontent.cgi?article=1218&context=rhumj">这个论文</a>。</p>]]></content>
<summary type="html">
<p>五一期间我写了一个 shadertoy 小动画,演示 Möbius
变换与球的刚体运动之间的关系:</p></summary>
<category term="可视化复分析" scheme="https://pywonderland.com/categories/%E5%8F%AF%E8%A7%86%E5%8C%96%E5%A4%8D%E5%88%86%E6%9E%90/"/>
</entry>
<entry>
<title>Möbius 变换与二维 Poincaré 双曲空间的等距</title>
<link href="https://pywonderland.com/mobius-poincare/"/>
<id>https://pywonderland.com/mobius-poincare/</id>
<published>2022-03-21T16:00:00.000Z</published>
<updated>2024-11-23T04:55:27.885Z</updated>
<content type="html"><![CDATA[<p>这几天因为疫情居家观察难得多出点时间(不用健步挤地铁),可以写点小文章。我之前写过一篇介绍<a href="/mobius-h3space">Möbius变换分类的文章</a>,今天继续那里的讨论,介绍一个 Möbius 变换 <span class="math inline">\(M\)</span> 作为二维 Poincaré 双曲圆盘 <span class="math inline">\(\mathbb{D}\)</span> 中等距的两种构造方法:</p><ol type="1"><li>指定 <span class="math inline">\(M\)</span>的不动点的个数和位置,不动点的个数和位置可以决定变换的类型。</li><li>指定两个反射镜面的位置:取两条测地线作为镜面,则关于这两个镜面的反演变换的复合变换就是一个Möbius 变换。两个镜面的相对位置可以决定变换的类型。</li></ol><p>这两种方法分别对应在 <span class="math inline">\(M\)</span>作用下保持不变的两个圆族。</p><p>本文的插图使用 <a href="https://matplotlib.org/">matplotlib</a>绘制,代码在 <a href="https://github.com/neozhaoliang/pywonderland/tree/master/src/mobius">Github</a>上。</p><span id="more"></span><p>记号约定:</p><ul><li><span class="math inline">\(\mathbb{D}\)</span> 为二维 Poincaré双曲圆盘。</li><li><span class="math inline">\(M\)</span> 为一个 Möbius 变换。</li><li><span class="math inline">\(S^1\)</span> 为单位圆周 <span class="math inline">\(|z|=1\)</span>。</li></ul><h1 id="用不动点构造双曲等距">用不动点构造双曲等距</h1><p>在复分析课程中,我们学过 <span class="math inline">\(\mathbb{D}\)</span> 的保持定向的等距同构是 Möbius群 <span class="math inline">\({\rm PSL}_2(\mathbb{C})\)</span>的一个子群,其元素具有如下的形式: <span class="math display">\[M(z) =\mathrm{e}^{i\theta}\frac{z - a}{1-\overline{a}z},\quad\theta\in\mathbb{R},\, |a|<1.\]</span> 可见 <span class="math inline">\(M\)</span> 的迹 <span class="math inline">\(\mathrm{tr}(M)=\mathrm{e}^{i\theta}+1\)</span>总是满足 <span class="math inline">\(0\leq|\mathrm{tr}(M)|\leq2\)</span>,所以 <span class="math inline">\(M\)</span> 不可能是斜航型的 (loxodromic),从而<span class="math inline">\(\mathbb{D}\)</span>的保持等向的等距只能是椭圆、抛物、双曲三种。</p><p>也可以这样解释:<span class="math inline">\(\mathbb{D}\)</span>的任何等距变换必然将 <span class="math inline">\(S^1\)</span> 仍然映射为<span class="math inline">\(S^1\)</span>,但斜航型的变换没有不变圆,所以不可能是<span class="math inline">\(\mathbb{D}\)</span> 上的等距变换。</p><p>下面分别介绍这三种情形。</p><h2 id="椭圆型">椭圆型</h2><p>椭圆型变换共轭于旋转 <span class="math inline">\(z\to\mathrm{e}^{i\theta}z\)</span>,它们总是有两个不动点。如果<span class="math inline">\(M\)</span> 是 <span class="math inline">\(\mathbb{D}\)</span> 上的椭圆型等距,则其两个不动点<span class="math inline">\(p_1,\,p_2\)</span> 必然一个在 <span class="math inline">\(\mathbb{D}\)</span> 内,一个在 <span class="math inline">\(\mathbb{D}\)</span>外,且它们关于单位圆互为反演点。设 <span class="math inline">\(p_1\in\mathbb{D}\)</span>,则 <span class="math inline">\(M\)</span> 在 <span class="math inline">\(\mathbb{D}\)</span> 上的作用是一个绕着 <span class="math inline">\(p_1\)</span> 的旋转:</p><p><img style="margin:0px auto;display:block" width="400" src="/images/mobius/elliptic.svg"></p><p>图中画出了两族圆:</p><ol type="1"><li>第一族圆是彩色的,它们在双曲度量下的中心都是 <span class="math inline">\(p_1\)</span>,<span class="math inline">\(M\)</span> 保持每个圆不变,同时将圆上的每个点绕着<span class="math inline">\(p_1\)</span> 旋转。</li><li>第二族圆用虚线标注,这些圆都和第一族圆正交、同时经过 <span class="math inline">\(p_1\)</span> 和 <span class="math inline">\(p_2\)</span>,并且都与单位圆正交。<span class="math inline">\(M\)</span>作用在这些圆上会把一个圆变成同族中的另一个。</li></ol><p>双曲空间中的圆也是欧氏空间中的圆,但是它们的圆心未必重合。一个 <span class="math inline">\(\mathbb{D}\)</span>中的圆越靠近边界,它的双曲度量下的圆心也会被「吸引」到靠近边界的位置。</p><h2 id="抛物型">抛物型</h2><p>抛物型变换共轭于平移 <span class="math inline">\(z\toz+1\)</span>,它们总是只有一个不动点。如果 <span class="math inline">\(M\)</span> 是 <span class="math inline">\(\mathbb{D}\)</span>上的抛物型等距,则其唯一的不动点 <span class="math inline">\(p\)</span>必然位于单位圆周 <span class="math inline">\(S^1\)</span> 上,<span class="math inline">\(M\)</span> 在 <span class="math inline">\(\mathbb{D}\)</span>上的作用是一个双曲空间中的平移:</p><p><img style="margin:0px auto;display:block" width="400" src="/images/mobius/parabolic.svg"></p><p>图中也画出了两族圆:</p><ol type="1"><li>第一族圆是彩色的,它们都和 <span class="math inline">\(S^1\)</span>在 <span class="math inline">\(p\)</span> 点处相切。<span class="math inline">\(M\)</span>作用在它们上面保持每个圆不变,同时将圆上的每个点沿着测地线向着 <span class="math inline">\(p\)</span> 移动。这些圆叫做horocycle,它们在双曲度量下的圆心是 <span class="math inline">\(p\)</span>。</li><li>第二族圆用虚线标注,这些圆都和第一族圆正交、互相之间也在 <span class="math inline">\(p\)</span> 点相切,并且都与单位圆正交。<span class="math inline">\(M\)</span>作用在这些圆上会把一个圆变成同族中的另一个。</li></ol><h2 id="双曲型">双曲型</h2><p>双曲型变换共轭于缩放 <span class="math inline">\(z\tocz,\,c\in\mathbb{R}^+\)</span>,它们总是有两个不动点。如果 <span class="math inline">\(M\)</span> 是 <span class="math inline">\(\mathbb{D}\)</span> 上的双曲型等距,则其两个不动点<span class="math inline">\(p_1,p_2\)</span> 必然都位于单位圆周 <span class="math inline">\(S^1\)</span> 上。<span class="math inline">\(M\)</span> 在 <span class="math inline">\(\mathbb{D}\)</span>上的作用以其中一个为源点,另一个为汇点:</p><p><img style="margin:0px auto;display:block" width="400" src="/images/mobius/hyperbolic.svg"></p><p>图中的两族圆中,</p><ol type="1"><li>第一族圆是彩色的,它们同时经过 <span class="math inline">\(p_1\)</span> 和 <span class="math inline">\(p_2\)</span>,<span class="math inline">\(M\)</span>作用在它们上面保持每个圆不变,同时将圆上的每个点沿着圆给出的测地线从源点移动到汇点。</li><li>第二族圆用虚线标注,这些圆都和第一族圆正交、<span class="math inline">\(p_1\)</span> 和 <span class="math inline">\(p_2\)</span>关于它们中的每一个都互为反演点,并且都与单位圆正交。<span class="math inline">\(M\)</span>作用在这些圆上会把一个圆变成同族中的另一个。</li></ol><h1 id="用反演变换的复合构造双曲等距">用反演变换的复合构造双曲等距</h1><p>在上面的三张图中,彩色的圆对应的是 <span class="math inline">\(\mathbb{D}\)</span> 中的点在 <span class="math inline">\(M\)</span>作用下的轨迹,它们都是测地线。我似乎有意冷落了虚线的圆族,把它们画的很不起眼。其实通过它们的反演变换也可以给出<span class="math inline">\(M\)</span> 的构造:</p><table><colgroup><col style="width: 33%"><col style="width: 33%"><col style="width: 33%"></colgroup><tbody><tr class="odd"><td style="text-align: center;">椭圆型</td><td style="text-align: center;">抛物型</td><td style="text-align: center;">双曲型</td></tr><tr class="even"><td style="text-align: center;"><img src="/images/mobius/elliptic2.svg" width="180"></td><td style="text-align: center;"><img src="/images/mobius/parabolic2.svg" width="180"></td><td style="text-align: center;"><img src="/images/mobius/hyperbolic2.svg" width="180"></td></tr></tbody></table><div class="statement simple plain unnumbered"><ul><li><span class="math inline">\(M\)</span>是椭圆型的,当且仅当它是两个在 <span class="math inline">\(\mathbb{D}\)</span>中<strong>相交</strong>的圆的反演变换的复合。</li><li><span class="math inline">\(M\)</span>是抛物型的,当且仅当它是两个在 <span class="math inline">\(\mathbb{D}\)</span>中<strong>平行</strong>的圆的反演变换的复合。这里平行的意思是两圆相切,且切点位于无穷远边界上。</li><li><span class="math inline">\(M\)</span>是双曲型的,当且仅当它是两个在 <span class="math inline">\(\mathbb{D}\)</span>中<strong>超平行</strong>的圆的反演变换的复合。这里超平行的意思是它们要么不相交,要么一个完全位于另一个的内部。</li></ul></div><p>放到上半空间模型中,这些就都不难理解了:</p><ul><li>椭圆型变换共轭于绕原点的角度为 <span class="math inline">\(\theta\)</span> 的旋转,此旋转是关于两个夹角为<span class="math inline">\(\theta/2\)</span> 的直线反射的复合。</li><li>抛物型变换共轭于平移 <span class="math inline">\(T:z\to z +1\)</span>,<span class="math inline">\(T\)</span>是上半双曲空间模型中的等距,它可以表示为关于两平行直线 <span class="math inline">\(x=0\)</span> 和 <span class="math inline">\(x=1/2\)</span>的反射的复合。我们可以用一个上半空间到 <span class="math inline">\(\mathbb{D}\)</span>的等距变换把实轴变成单位圆周,并把 <span class="math inline">\(\infty\)</span> 点变到边界上的指定点 <span class="math inline">\(p\)</span>。这时所有形如 <span class="math inline">\(x=k\)</span> 的直线,由于它们都经过 <span class="math inline">\(\infty\)</span>、互相平行、与实轴正交,所以都会变成过<span class="math inline">\(p\)</span> 点、两两相切于 <span class="math inline">\(p\)</span>、且与单位圆正交的圆。在这些圆中任取两个圆,作它们反演变换的复合,给出的就是一个抛物型变换。</li><li>双曲型变换共轭于缩放 <span class="math inline">\(S:z\tocz,\,c>0\)</span>,<span class="math inline">\(S\)</span>是上半空间模型中两个同心圆 <span class="math inline">\(|z|=1\)</span> 和<span class="math inline">\(|z|=\sqrt{c}\)</span>的反演变换的复合。<span class="math inline">\(S\)</span>的两个不动点是原点和无穷远点,它们关于这两个同心圆互为反演点。同理我们可以用一个从上半空间到<span class="math inline">\(\mathbb{D}\)</span>的等距变换把实轴变成单位圆,并把原点和无穷远点分别映射到单位圆周上指定的两个不动点。这两个同心圆会被映射为<span class="math inline">\(\mathbb{D}\)</span>中的两条测地线。关于这两条测地线的反演的复合就共轭于上半空间模型中的缩放。</li></ul><div class="statement note definition unnumbered"><p><span class="statement-heading"><span class="statement-label">注</span>:</span><span class="statement-spah"></span>为什么两个圆的反演变换的复合是一个 Möbius 变换?你可以用一个Möbius 变换 <span class="math inline">\(T\)</span> 将任意给定的圆 <span class="math inline">\(C\)</span> 变为实直线 <span class="math inline">\(\mathbb{R}^1\)</span>,关于 <span class="math inline">\(\mathbb{R}^1\)</span> 的反演就是复共轭 <span class="math inline">\(\mathrm{conj}(z)=\overline{z}\)</span>,所以关于<span class="math inline">\(C\)</span> 的反演为 <span class="math inline">\(S=T^{-1}\cdot\mathrm{conj}\cdot T\)</span>,从而<span class="math inline">\(S\)</span> 形如 <span class="math display">\[S(z) = \frac{a\overline{z} +b}{c\overline{z}+d},\quada,b,c,d\in\mathbb{C},\,z\in\mathbb{C}_{\infty}.\]</span>里面包含了复共轭,所以 <span class="math inline">\(S\)</span> 不是Möbius 变换,但两个反演变换的复合就是 Möbius 变换。</p></div>]]></content>
<summary type="html">
<p>这几天因为疫情居家观察难得多出点时间(不用健步挤地铁),可以写点小文章。我之前写过一篇介绍
<a href="/mobius-h3space">Möbius
变换分类的文章</a>,今天继续那里的讨论,介绍一个 Möbius 变换 <span class="math inline">\(M\)</span> 作为二维 Poincaré 双曲圆盘 <span class="math inline">\(\mathbb{D}\)</span> 中等距的两种构造方法:</p>
<ol type="1">
<li>指定 <span class="math inline">\(M\)</span>
的不动点的个数和位置,不动点的个数和位置可以决定变换的类型。</li>
<li>指定两个反射镜面的位置:取两条测地线作为镜面,则关于这两个镜面的反演变换的复合变换就是一个
Möbius 变换。两个镜面的相对位置可以决定变换的类型。</li>
</ol>
<p>这两种方法分别对应在 <span class="math inline">\(M\)</span>
作用下保持不变的两个圆族。</p>
<p>本文的插图使用 <a href="https://matplotlib.org/">matplotlib</a>
绘制,代码在 <a href="https://github.com/neozhaoliang/pywonderland/tree/master/src/mobius">Github</a>
上。</p></summary>
<category term="可视化复分析" scheme="https://pywonderland.com/categories/%E5%8F%AF%E8%A7%86%E5%8C%96%E5%A4%8D%E5%88%86%E6%9E%90/"/>
</entry>
<entry>
<title>遛狗中的数学:曲线的环绕数、Rouché 定理和开映射定理</title>
<link href="https://pywonderland.com/Rouche-theorem-winding-number/"/>
<id>https://pywonderland.com/Rouche-theorem-winding-number/</id>
<published>2021-08-31T16:00:00.000Z</published>
<updated>2024-12-23T13:46:08.670Z</updated>
<content type="html"><![CDATA[<p>我写了一个 <a href="https://www.shadertoy.com/view/fdK3RD">shadertoy小动画</a>,演示 <span class="citation" data-cites="Needham1997">(<a href="#ref-Needham1997" role="doc-biblioref">Needham 1997</a>)</span>书中第 7 章 “Winding numbers and topology” 中的结论:</p><span id="more"></span><div class="statement simple plain unnumbered"><p>一个人和他的狗在公园里绕着一棵树散步,人和狗各自走的路径都是闭曲线,即经过一段时间后都会回到起点。如果人把狗绳抓的紧一些,使得整个过程中狗<strong>无法接触</strong>到树,则结束后人和狗绕着树走的圈数是一样的,这就是下面这个动画演示的:(树的位置是原点,用一个表盘标记)</p><iframe width="640" height="360" frameborder="0" src="https://www.shadertoy.com/embed/fdK3RD?gui=true&t=10&paused=true&muted=false" allowfullscreen=""></iframe></div><p>对应的数学结论是:两条闭曲线 <span class="math inline">\(\gamma_1,\gamma_2\)</span> 如果都不经过原点,且<span class="math inline">\(\gamma_1\)</span>可以在不碰触到原点的前提下通过连续的形变变为 <span class="math inline">\(\gamma_2\)</span>(同伦),则 <span class="math inline">\(\gamma_1,\gamma_2\)</span>关于原点的环绕数相等。</p><p>注意这个结论只要求 <span class="math inline">\(\gamma_1,\gamma_2\colon\[0,1]\to\mathbb{R}^2\)</span>是两条连续曲线,并不涉及解析性。因为环绕数和同伦都是拓扑概念,只涉及连续性。</p><p>当 <span class="math inline">\(\gamma_1=f(S^1),\gamma_2=g(S^1)\)</span>分别是单位圆 <span class="math inline">\(S^1\colon\\{z\in\mathbb{C}:|z|=1\}\)</span> 在两个解析函数 <span class="math inline">\(f,g\)</span> 下的像时,<a href="https://en.wikipedia.org/wiki/Argument_principle">幅角原理</a>告诉我们 <span class="math inline">\(\gamma_1,\gamma_2\)</span>关于原点的环绕数分别等于 <span class="math inline">\(f,g\)</span> 在<span class="math inline">\(S^1\)</span> 内部的零点个数。进一步 <a href="https://en.wikipedia.org/wiki/Rouch%C3%A9%27s_theorem">Rouché定理</a> 告诉我们,如果对任何 <span class="math inline">\(z\inS^1\)</span> 都有 <span class="math inline">\(|f(z)|>|f(z)-g(z)|\)</span> 成立,即可保证<span class="math inline">\(\gamma_1,\gamma_2\)</span>关于原点有相同的环绕数,从而 <span class="math inline">\(f,g\)</span> 在<span class="math inline">\(S^1\)</span>内部的零点个数也是相同的。Rouché 定理的条件说的就是,假设人的位置是<span class="math inline">\(f(z)\)</span>,狗的位置是 <span class="math inline">\(g(z)\)</span>,绳子 <span class="math inline">\(l(z)=f(z)-g(z)\)</span> 的长度 <span class="math inline">\(|l(z)|\)</span> 始终小于人到原点的距离 <span class="math inline">\(|f(z)|\)</span>,就可以保证狗始终够不到原点。</p><p>动画中左下角的圆周是 <span class="math inline">\(S^1\)</span>,动画右边红、绿两条路径分别是 <span class="math inline">\(f(S^1)\)</span> 和 <span class="math inline">\(g(S^1)\)</span>。这里的 <span class="math inline">\(f\)</span> 我取的形如 <span class="math display">\[f(z)=\frac{z-a}{1-\overline{a}z}\frac{z-b}{1-\overline{b}z}\frac{z-c}{1-\overline{c}z}(z-2-2i),\quad |a|,|b|,|c|<1.\]</span> <span class="math inline">\(f(z)\)</span> 在 <span class="math inline">\(S^1\)</span> 的内部有 3 个根(我用红点标出来了),在 <span class="math inline">\(S^1\)</span> 上不为0,在 <span class="math inline">\(S^1\)</span>外部有一个根(图中没有画)。<span class="math inline">\(f(z)\)</span>的前三个因子构成一个 Blaschke 乘积,它把 <span class="math inline">\(S^1\)</span> 的内部仍然映射为内部,把 <span class="math inline">\(S^1\)</span> 仍然映射为 <span class="math inline">\(S^1\)</span>,于是对任何 <span class="math inline">\(z\in S^1\)</span> 有 <span class="math display">\[|f(z)| = |z - 2 - 2i| \geq 2\sqrt{2} - 1,\quadz\in S^1.\]</span> 所以只要绳子 <span class="math inline">\(l(z)\)</span> 满足 <span class="math inline">\(|l(S^1)| < 2\sqrt{2}-1\)</span>,则狗走的路径<span class="math inline">\(g(S^1)=f(S^1)+l(S^1)\)</span>就不可能接触到原点。我这里取了 <span class="math inline">\(l(z) =cz\)</span>,其中 <span class="math inline">\(c\)</span> 是一个小于<span class="math inline">\(2\sqrt{2}-1\)</span> 的正实数。</p><p>Needham 的书中还介绍了曲线 <span class="math inline">\(\gamma\)</span> 的环绕数在 <span class="math inline">\(\mathbb{C}\setminus\gamma\)</span>的每个连通分支上都是常数。对不在 <span class="math inline">\(\gamma\)</span> 上的一点 <span class="math inline">\(z\)</span>,我们可以稍稍移动 <span class="math inline">\(z\)</span> 到另一个点 <span class="math inline">\(z'\)</span>,只要保持 <span class="math inline">\(z'\)</span> 仍然位于 <span class="math inline">\(z\)</span> 所在的连通分支内,<span class="math inline">\(\gamma\)</span> 关于 <span class="math inline">\(z\)</span> 和 <span class="math inline">\(z'\)</span>的环绕数就一定相同。利用这个事实并结合幅角原理不难得出下面的结论:</p><div id="connected-component" class="unnumbered statement sta___ plain"><p><span class="statement-heading"><span class="statement-label">推论</span>.</span><span class="statement-spah"></span>设 <span class="math inline">\(\gamma\)</span>是一条简单闭曲线,内部围的区域为 <span class="math inline">\(\Omega\)</span>,<span class="math inline">\(f(z)\)</span> 是一个非常数的解析函数,<span class="math inline">\(f\)</span> 在包含 <span class="math inline">\(\gamma\)</span>的某个区域内解析。假设有两棵树分别位于 <span class="math inline">\(w_0,\,w_1\)</span> 两点,且人行走的路线 <span class="math inline">\(f(\gamma)\)</span> 到 <span class="math inline">\(w_0\)</span> 的距离始终大于两棵树之间的距离:<span class="math display">\[|f(z)-w_0| > |w_0-w_1|,\quad\forallz\in\gamma.\]</span></p><p><img style="margin:0px auto;display:block" src="/images/rouche/winding_number.svg" width="250"></p><p>则 <span class="math inline">\(f(\gamma)\)</span> 关于 <span class="math inline">\(w_0,w_1\)</span> 的环绕数相等,从而 <span class="math inline">\(w_0,w_1\)</span> 在 <span class="math inline">\(\gamma\)</span> 内部的原像个数相同: <span class="math display">\[\sharp\{z\in \Omega: f(z)=w_0\} = \sharp\{z\in\Omega: f(z)=w_1\}.\]</span></p></div><p><strong>证明</strong>:这是因为根据条件,从 <span class="math inline">\(w_0\)</span> 沿着线段 <span class="math inline">\([w_0,w_1]\)</span> 移动到 <span class="math inline">\(w_1\)</span> 的过程中始终不会碰触到曲线 <span class="math inline">\(f(\gamma)\)</span>,所以 <span class="math inline">\(w_0,w_1\)</span> 必然位于同一连通分支内。<span class="math inline">\(\blacksquare\)</span></p><p>利用此推论我们不难得出复分析中的 <a href="https://en.wikipedia.org/wiki/Open_mapping_theorem_(complex_analysis)">开映射定理</a>:</p><div class="unnumbered statement theorem-unnumbered plain"><p><span class="statement-heading"><span class="statement-label">定理</span>.</span><span class="statement-spah"></span>如果 <span class="math inline">\(U\subseteq\mathbb{C}\)</span>是开集,<span class="math inline">\(f\colon\ U\to\mathbb{C}\)</span>是非常数的解析函数,则 <span class="math inline">\(f(U)\)</span>也是开集。</p></div><p><strong>证明</strong>:任取 <span class="math inline">\(z_0\inU\)</span>,记 <span class="math inline">\(w_0=f(z_0)\)</span>。由于<span class="math inline">\(f\)</span> 不是常数,所以 <span class="math inline">\(f(z)-w_0\)</span> 的零点都是孤立的。我们可以取<span class="math inline">\(z_0\)</span> 的一个充分小的闭圆盘 <span class="math inline">\(B_\delta=\{z\in U\mid |z-z_0|\leq\delta\}\)</span>使得 <span class="math inline">\(f(z)-w_0\)</span> 在 <span class="math inline">\(B_\delta\)</span> 中除了 <span class="math inline">\(z_0\)</span> 以外没有其它零点。特别地,<span class="math inline">\(f(z)-w_0\)</span> 在 <span class="math inline">\(B_\delta\)</span> 的边界 <span class="math inline">\(\gamma =\{|z-z_0|=\delta\}\)</span> 上恒不为0,从而 <span class="math inline">\(|f(z)-w_0|\)</span> 在 <span class="math inline">\(\gamma\)</span> 上有正的极小值 <span class="math inline">\(e\)</span>,即对任何 <span class="math inline">\(z\in\gamma\)</span> 有 <span class="math inline">\(|f(z)-w_0|\geq e\)</span>。</p><p>现在我们考虑 <span class="math inline">\(w_0\)</span> 的邻域 <span class="math inline">\(V_e=\{|w-w_0|<e\}\)</span>。则任何 <span class="math inline">\(w_1\in V_e\)</span> 都满足 <a href="#connected-component" title="推论">推论</a> 中的条件:</p><p><span class="math display">\[|f(z)- w_0| \geq e > |w_1-w_0|,\quadz\in \gamma.\]</span></p><p>所以 <span class="math inline">\(w_1\)</span> 在 <span class="math inline">\(\gamma\)</span> 内部至少有一个原像。由 <span class="math inline">\(w_1\)</span> 的任意性可得 <span class="math inline">\(V_e\subset f(U)\)</span> 是 <span class="math inline">\(w_0\)</span> 在 <span class="math inline">\(f(U)\)</span> 中的开邻域,从而 <span class="math inline">\(f(U)\)</span> 是开集。<span class="math inline">\(\blacksquare\)</span></p><h1 class="unnumbered" id="bibliography">References</h1><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0" role="list"><div id="ref-Needham1997" class="csl-entry" role="listitem">Needham, Tristan. 1997. <em>Visual Complex Analysis</em>. The ClarendonPress, Oxford University Press, New York.</div></div>]]></content>
<summary type="html">
<p>我写了一个 <a href="https://www.shadertoy.com/view/fdK3RD">shadertoy
小动画</a>,演示 <span class="citation" data-cites="Needham1997">(<a href="#ref-Needham1997" role="doc-biblioref">Needham 1997</a>)</span>
书中第 7 章 “Winding numbers and topology” 中的结论:</p></summary>
<category term="可视化复分析" scheme="https://pywonderland.com/categories/%E5%8F%AF%E8%A7%86%E5%8C%96%E5%A4%8D%E5%88%86%E6%9E%90/"/>
</entry>
<entry>
<title>静电场与 Marden 定理</title>
<link href="https://pywonderland.com/Marden-theorem/"/>
<id>https://pywonderland.com/Marden-theorem/</id>
<published>2021-05-23T16:00:00.000Z</published>
<updated>2024-08-17T12:50:17.357Z</updated>
<content type="html"><![CDATA[<p>我昨晚刚完成了一个 <a href="https://www.shadertoy.com/view/7lf3Wn">shadertoy小动画</a>,演示平面几何中的 <a href="https://en.wikipedia.org/wiki/Marden%27s_theorem">Marden定理</a>、复分析中的 <a href="https://en.wikipedia.org/wiki/Gauss%E2%80%93Lucas_theorem">Gauss-Lucas定理</a> 和静电场之间的关系:</p><span id="more"></span><iframe width="640" height="360" frameborder="0" src="https://www.shadertoy.com/embed/7lf3Wn?gui=true&t=10&paused=true&muted=false" allowfullscreen=""></iframe><p>这个动画的含义如下:</p><ol type="1"><li><p>在复平面上三角形 <span class="math inline">\(\Delta ABC\)</span>的三个顶点处各自放置一个单位正电荷,则平面上电场强度为 0的点有两个(这两个点可能重合),它们位于 <span class="math inline">\(\Delta ABC\)</span> 的内部,并且是三次复多项式<span class="math inline">\(P(z) = (z-A)(z-B)(z-C)\)</span> 的导数 <span class="math inline">\(P'(z)\)</span> 的零点。</p></li><li><p>不仅如此,这两个零点还是一个内切于 <span class="math inline">\(\Delta ABC\)</span>的椭圆的两个焦点,此椭圆是所有内切于 <span class="math inline">\(\DeltaABC\)</span> 的椭圆中面积最大者,并且其与 <span class="math inline">\(\Delta ABC\)</span>的三边的切点均为各边中点。这个椭圆叫做 <a href="https://en.wikipedia.org/wiki/Steiner_inellipse">Steiner内切椭圆</a>。</p></li></ol><p>这个动画是受几天前 Albert Chern 的 <a href="https://twitter.com/theAlbertChern/status/1395468792788967428?s=20">一篇推文</a>启发所作,John Baez 也写了一篇关于这个话题的 <a href="https://johncarlosbaez.wordpress.com/2021/05/24/electrostatics-and-the-gauss-lucas-theorem/">文章</a>。我是由此才了解到Marden 定理还有如此有趣的物理学解释,的确大开眼界!</p><hr><p>在平面上不全共线的 <span class="math inline">\(n\)</span> 个点 <span class="math inline">\(a_1,\ldots,a_n\)</span>处放置若干单位正电荷,这规定了一个平面上的电势函数 <span class="math inline">\(V(z)\)</span> (标量) 和一个电场 <span class="math inline">\(\mathbf{E}(z)\)</span>(二维向量场)。电学知识告诉我们,在忽略物理常数意义下有 <span class="math display">\[V(z)=\sum_{i=1}^n\ln|z-a_i|=\ln\prod_{i=1}^n|z-a_i|=\ln|P(z)|.\]</span> 其中<span class="math inline">\(P(z)=(z-a_1)(z-a_2)\cdots(z-a_n)\)</span>是以 <span class="math inline">\(a_1,\ldots,a_n\)</span>为根的多项式。</p><p>此外 <span class="math inline">\(\mathbf{E}(z) = -\nablaV(z)\)</span> 为电势的梯度向量取负。</p><blockquote><p><strong>问题</strong>:怎样确定平面上场强为 0 的点呢?</p></blockquote><p>场强为 0的点也叫做<strong>平衡点</strong>、<strong>鞍点</strong>,因为在这一点处的电荷不受电场的库仑力。</p><p>答案有点出人意料:平衡点必然是 <span class="math inline">\(P'(z)\)</span> 的零点,而且这些点都属于 <span class="math inline">\(a_1,\ldots,a_n\)</span> 的凸包!</p><p>注意 <span class="math inline">\(V(z)\)</span> 是 <span class="math inline">\(\ln P(z) = \ln |P(z)| + i\arg{P(z)}\)</span>的实部,由 Cauchy-Riemann 方程不难看出使得亚纯函数实部梯度为 0的点一定是其导数的零点,即满足 <span class="math inline">\(\nablaV=0\)</span> 的点都是 <span class="math inline">\((\lnP(z))'=P'(z)/P(z)\)</span> 的零点,所以平衡点都是 <span class="math inline">\(P'(z)\)</span> 的零点。平衡点属于 <span class="math inline">\(\{a_1,\ldots,a_n\}\)</span> 的凸包是根据 <a href="https://en.wikipedia.org/wiki/Gauss%E2%80%93Lucas_theorem">Gauss-Lucas定理</a>:任何复多项式 <span class="math inline">\(f\)</span>的导数的零点都属于 <span class="math inline">\(f\)</span>的零点构成的凸包!</p><p>John Baez 的文章中利用凸集分离定理给出了 Gauss-Lucas定理的一个简洁证明。</p><p>需要注意的是,反过来 <span class="math inline">\(P'(z)\)</span>的零点未必都是电场的平衡点,当 <span class="math inline">\(P(z)\)</span>有重根时,重根也是 <span class="math inline">\(P'(z)\)</span>的零点,但不是 <span class="math inline">\(P'(z)/P(z)\)</span>的零点,所以不是平衡点。</p><p>此外平衡点是鞍点 (saddle point) 是由于 <span class="math inline">\(V(z)\)</span>的调和性质,其不存在局部的极大极小值,所以使得 <span class="math inline">\(\nabla V=0\)</span> 的点都是鞍点。</p><p>在三个点电荷 <span class="math inline">\(A,B,C\)</span>的情形,平衡点有两个,它们位于 <span class="math inline">\(\DeltaABC\)</span> 的内部,且是多项式 <span class="math inline">\(P(z) =(z-A)(z-B)(z-C)\)</span> 的导数 <span class="math inline">\(P'(z)\)</span>的零点。那关于这两个点的具体位置我们可以说什么吗?这就是优美的 Marden定理,要表述这个定理,我们需要先介绍 Steiner 内切椭圆的概念:</p><div id="steiner-inellipse" class="unnumbered statement sta_steiner_inellipse plain"><p><span class="statement-heading"><span class="statement-label">Steinerinellipse</span>.</span><span class="statement-spah"></span>在所有内切于 <span class="math inline">\(\Delta ABC\)</span>的椭圆中,存在唯一的一个面积最大者,叫做 Steiner inellipse,此椭圆与<span class="math inline">\(\Delta ABC\)</span>三边的切点为各边的中点。</p></div><p>Marden 定理断言 <span class="math inline">\(P'(z)\)</span>的两个根正是 Steiner 内切椭圆的两个焦点:</p><div id="marden-------" class="unnumbered statement sta_marden___ plain"><p><span class="statement-heading"><span class="statement-label">Marden定理</span>.</span><span class="statement-spah"> </span>复多项式 <span class="math inline">\(P(z)=(z-A)(z-B)(z-C)\)</span> 的导数 <span class="math inline">\(P'(z)\)</span> 的两个零点正是 <span class="math inline">\(\Delta ABC\)</span> 的 Steiner inellipse的两个焦点。</p></div><p>Steiner inellipse 和 Marden定理的证明并不复杂,美国数学月刊上出现过两篇介绍其证明的文章,都非常值得一读:</p><ol type="1"><li><p><a href="https://www.researchgate.net/publication/228698127_Triangles_Ellipses_and_Cubic_Polynomials">Triangles,Ellipses, and Cubic Polynomials</a>.</p></li><li><p><a href="https://www.researchgate.net/publication/263136028_An_Elementary_Proof_of_Marden%27s_Theorem">AnElementary Proof of Marden’s Theorem</a>.</p></li></ol><p>其中第一篇文章采用了复数和仿射变换的途径,第二篇使用了椭圆的光学性质。</p>]]></content>
<summary type="html">
<p>我昨晚刚完成了一个 <a href="https://www.shadertoy.com/view/7lf3Wn">shadertoy
小动画</a>,演示平面几何中的 <a href="https://en.wikipedia.org/wiki/Marden%27s_theorem">Marden
定理</a>、复分析中的 <a href="https://en.wikipedia.org/wiki/Gauss%E2%80%93Lucas_theorem">Gauss-Lucas
定理</a> 和静电场之间的关系:</p></summary>
<category term="可视化复分析" scheme="https://pywonderland.com/categories/%E5%8F%AF%E8%A7%86%E5%8C%96%E5%A4%8D%E5%88%86%E6%9E%90/"/>
</entry>
<entry>
<title>Todd-Coxeter 算法和 3D/4D 均匀多胞体</title>
<link href="https://pywonderland.com/Todd-Coxeter-and-uniform-polytopes/"/>
<id>https://pywonderland.com/Todd-Coxeter-and-uniform-polytopes/</id>
<published>2018-05-21T00:00:00.000Z</published>
<updated>2024-11-28T14:52:06.018Z</updated>
<content type="html"><![CDATA[<p>本文介绍我写的一个高颜值的、脱离了低级趣味的小程序:用 Python 和POV-Ray 绘制各种三维多面体和四维多胞体,代码在 <a href="https://github.com/neozhaoliang/pywonderland/tree/master/src/polytopes">Github</a>上。</p><p>以下是用这个程序渲染的一些例子,其中不同颜色的顶点/边/面表示它们在对称群的作用下位于不同的轨道中,具体解释见后。</p><span id="more"></span><h1 id="例子">例子</h1><ul><li><p>所有的 Platonic 多面体,Archimedean 多面体,比如 snubdodecahedron:</p><p><video src="/images/polytopes/snub-dodecahedron.mp4" controls=""></video></p></li><li><p>所有的 Kepler-Poinsot 多面体,比如 great icosahedron:</p><p><video src="/images/polytopes/great-icosahedron.mp4" controls=""></video></p></li><li><p>所有的四维均匀多胞体 (除去一个特例 <a href="https://en.wikipedia.org/wiki/Grand_antiprism">The grandantiprism</a>),比如我的 Github 头像 (runcinated 120-cell):</p><p><img style="margin:0px auto;display:block" width="500" src="/images/polytopes/github-favicon.png"></p></li><li><p>截断的四维正方体 truncated tesseract:</p><p><video src="/images/polytopes/truncated-tesseract.mp4" controls=""></video></p></li><li><p>4d cube:</p><p><img style="margin:0px auto;display:block" width="500" src="/images/polytopes/4-cube.png"></p></li><li><p>也可以是非凸的,比如星状正多胞体中的 grand stellated120-cell:</p><p><video src="/images/polytopes/grand-stellated-120-cell.mp4" controls=""></video></p></li><li><p>甚至是 5 维欧氏空间中的均匀多胞体,如 5-cube:</p><p><video src="/images/polytopes/5-cube.mp4" controls=""></video></p></li></ul><p>等等,可玩的效果是非常多的。</p><p>以上这些都是在 Python 中做好计算以后,将多胞体的数据导出到 POV-Ray中渲染得到的。你完全可以通过改写代码中的 POV-Ray的部分来渲染得出不同的效果,当然前提是需要掌握 POV-Ray的场景描述语言,不过这属于另一段故事,就不在本文的讨论范围内了。</p><p>下面介绍程序背后的数学原理。</p><h1 id="这些图画的都是什么">这些图画的都是什么?</h1><p>这些图都是三维或者四维<strong>欧氏空间</strong>中<strong>凸/非凸</strong>的<strong>均匀</strong>多胞体(polytope),三维的情形更常用的称呼是多面体。这里有几个关键词需要注意:凸/非凸、均匀。</p><p>凸比较好理解,就是指多胞体上任意两点间的连线仍然属于此多胞体,否则就是非凸。上面的例子中Platonic 多面体、Archimedean 多面体都是凸的,Kepler-Poinsot多面体、星状正多胞体都是非凸的。</p><p>均匀这个词就不太好理解了。简单说就是:多胞体的所有顶点都一样,且每个面都是正多边形,每个胞腔都是三维的均匀多面体(这是个递归的定义)。</p><p>要准确解释什么叫所有顶点都一样,就要用到群论的语言:一个多胞体 <span class="math inline">\(P\)</span> 的对称群 <span class="math inline">\(G\)</span>是欧氏空间中一组正交变换构成的有限群,<span class="math inline">\(G\)</span> 作用在 <span class="math inline">\(P\)</span> 上保持 <span class="math inline">\(P\)</span> 不变。所有顶点都一样的严格说法是 <span class="math inline">\(G\)</span> 传递地作用在 <span class="math inline">\(P\)</span> 的顶点集上,即对 <span class="math inline">\(P\)</span> 的任何两个顶点 <span class="math inline">\(u,v\)</span>,都存在 <span class="math inline">\(g\in G\)</span>,<span class="math inline">\(g\)</span> 把 <span class="math inline">\(u\)</span> 映射为 <span class="math inline">\(v\)</span>。</p><h1 id="这些图是怎么画出来的">这些图是怎么画出来的?</h1><p>这些多胞体看起来样子大不相同,但它们都可以用同一种方法计算出来,叫做<a href="https://en.wikipedia.org/wiki/Wythoff_construction">Wythoff构造法</a>,也称万花筒构造法。它的原理跟我们小时候玩的万花筒的原理是一样的:在空间中放置若干过原点的反射平面(镜子),镜面之间的夹角是精心设计好的,都形如 <span class="math inline">\(\pi-\pi/p\)</span>,其中 <span class="math inline">\(p\)</span> 为有理数。在空间中选定一个初始顶点<span class="math inline">\(v_0\)</span>,将 <span class="math inline">\(v_0\)</span>关于这些镜子反复作反射变换,得到的全部镜像就是多胞体的顶点。如果 <span class="math inline">\(v_0\)</span> 关于第 <span class="math inline">\(i\)</span> 面镜子反射后得到的镜像是 <span class="math inline">\(v_1\)</span>,则 <span class="math inline">\((v_0,v_1)\)</span> 构成一条类型为 <span class="math inline">\(i\)</span>的边,我们把它以及在对称群作用下同轨道的所有边都染成 <span class="math inline">\(i\)</span> 号色。如果 <span class="math inline">\(v_0\)</span> 先关于镜面 <span class="math inline">\(i\)</span> 作反射,再关于镜面 <span class="math inline">\(j\)</span>作反射,则由于两个反射变换的复合是一个旋转变换,<span class="math inline">\(v_0\)</span>实际上是绕着某个面的中心和原点的连线作了一次旋转,旋转的角度为 <span class="math inline">\(2\pi/m\)</span> (假设镜面 <span class="math inline">\(i\)</span> 和镜面 <span class="math inline">\(j\)</span> 的法向量夹角是 <span class="math inline">\(\pi-\pi/m\)</span>),重复此旋转 <span class="math inline">\(m\)</span> 次即可得到多胞体的一个类型为 <span class="math inline">\((i,j)\)</span>的面,我们把它在对称群作用下同轨道的所有面都染成同一颜色。</p><p>这里的关键问题有两个:</p><ol type="1"><li>对于不同的均匀多胞体,应该如何放置这些镜面,并选择初始顶点?</li><li>摆好镜面和初始顶点以后,怎样不重复不遗漏地计算初始顶点的所有镜像?</li></ol><p>第一个问题的答案叫做 <a href="https://en.wikipedia.org/wiki/Coxeter%E2%80%93Dynkin_diagram">Coxeter-Dynkin图</a>,Coxeter-Dynkin图是一个带标记信息的无向图,它编码了多胞体的全部信息,即只要知道了多胞体对应的Coxeter-Dynkin 图,就可以求出该多胞体的所有数据(仅缩放大小和在空间中的摆放位置除外)。每个均匀多胞体都有一个Coxeter-Dynkin 图与之对应,但是不同的 Coxeter-Dynkin图可能描述的是相同的多胞体。</p><p>比如正方体的 Coxeter-Dynkin 图为:</p><p><img style="margin:0px auto;display:block" src="/images/polytopes/cube43.svg" width="200"></p><p>我们来解释这个图的含义:</p><p>在一个 Coxeter-Dynkin图中,每个顶点代表一面镜子,在上图中有三个顶点,所以有三面镜子。将这三面镜子从左到右依次记作<span class="math inline">\(m_0, m_1,m_2\)</span>,顶点之间的边记录了镜子间的夹角:</p><ol type="1"><li>若两个镜面之间的夹角为 <span class="math inline">\(\pi/2\)</span>则它们之间没有边相连。</li><li>若两个镜面之间的夹角为 <span class="math inline">\(\pi-\pi/3\)</span>则它们之间用一条无标记的边相连。</li><li>若两个镜面之间的夹角为 <span class="math inline">\(\pi-\pi/m\)</span> (<span class="math inline">\(m\)</span> 为有理数且 <span class="math inline">\(m>2, m\ne3\)</span>),则它们之间用一条标号为<span class="math inline">\(m\)</span> 的边相连。</li></ol><p>此外用圈出的镜面来标记初始顶点的位置,<strong>一个镜面被圈出当且仅当初始顶点不在这个镜面上</strong>。</p><p>从而在正方形的情形 <span class="math inline">\(\langlem_0,m_1\rangle=\pi-\pi/4\)</span>,<span class="math inline">\(\langlem_1,m_2\rangle=\pi-\pi/3\)</span>,<span class="math inline">\(\langlem_0,m_2\rangle=\pi/2\)</span>。初始顶点落在 <span class="math inline">\(m_1\)</span> 和 <span class="math inline">\(m_2\)</span> 上但是不属于 <span class="math inline">\(m_0\)</span>。</p><p>于是这三面镜子可以这样摆放:</p><ol type="1"><li>镜子 <span class="math inline">\(m_0\)</span> 的法向量可以随意,比如<span class="math inline">\(n_0=(1, 0, 0)\)</span>。</li><li>镜子 <span class="math inline">\(m_1\)</span> 的法向量 <span class="math inline">\(n_1\)</span> 与 <span class="math inline">\(n_0\)</span> 夹角为 <span class="math inline">\(3\pi/4\)</span>,于是 <span class="math inline">\(n_1\)</span> 可以取为 <span class="math inline">\((\cos\dfrac{3\pi}{4}, \sin\dfrac{3\pi}{4},0)\)</span>。</li><li>镜子 <span class="math inline">\(m_2\)</span> 的法向量 <span class="math inline">\(n_2\)</span> 与 <span class="math inline">\(n_0\)</span> 垂直,所以 <span class="math inline">\(n_2\)</span> 形如 <span class="math inline">\((0,y_3,z_3)\)</span>,它与 <span class="math inline">\(n_1\)</span> 夹角是 <span class="math inline">\(2\pi/3\)</span>,所以 <span class="math inline">\(y_3\sin\dfrac{3\pi}{4}=\cos\dfrac{2\pi}{3}\)</span>,再结合 <span class="math inline">\(n_2\)</span> 是单位向量,<span class="math inline">\(z_3=\sqrt{1-y_3^2}\)</span>,解出 <span class="math inline">\(y_3, z_3\)</span> 即可。</li></ol><p>要选择一个落在 <span class="math inline">\(m_1\)</span> 和 <span class="math inline">\(m_2\)</span> 上但是不落在 <span class="math inline">\(m_0\)</span> 上的初始点 <span class="math inline">\(v_0\)</span>,我们可以让 <span class="math inline">\(v_0\)</span> 到平面 <span class="math inline">\(m_1\)</span> 和 <span class="math inline">\(m_2\)</span> 的距离为 0,到平面 <span class="math inline">\(m_0\)</span> 的距离为 1,即</p><p><span class="math display">\[\langle v_0, n_0\rangle=1,\quad \langlev_0, n_1\rangle=0,\quad\langle v_0, n_2\rangle=0.\]</span></p><p>求解这个线性方程组即可。</p><p>我们前面提到过,要使得初始顶点的所有镜像恰好构成一个均匀多胞体,镜子之间的夹角必须精心设置,这实际上只有有限种可能。换句话说,只有有限个Coxeter-Dynkin 图可以给出 3D/4D 的均匀多胞体。在 <a href="https://en.wikipedia.org/wiki/Uniform_polytope">维基百科</a>上完整的列出了每种均匀多胞体对应的 Coxeter-Dynkin图,这里就不再专门列举了,但是特别指出两点:</p><ol type="1"><li>Coxeter-Dynkin图的标号完全决定了多胞体的对称性,而初始顶点的位置则决定了多胞体的截断类型。</li><li>对偶的多胞体具有相同的 Coxeter-Dynkin图,只不过要把边的标号从右到左反过来。比如正八面体和正方体的Coxeter-Dynkin 图是一样的,但是边的标号是 (3, 4)。</li></ol><p>第二个问题的答案叫做 Todd-Coxeter算法,展开讲的话比较复杂,我们单列一节专门谈谈。</p><h1 id="有限表现群和-todd-coxeter-算法">有限表现群和 Todd-Coxeter算法</h1><p>怎样求出初始顶点在所有镜子中的镜像?有个简单的办法:只要反复地将初始顶点关于每个镜面作反射,算出得到的镜像点的坐标,并与之前得到的点的坐标相比较(浮点数比较需要在一定的误差范围内考虑),直到不再有新的镜像点出现为止,不就得到全部顶点集吗?这个方法确实可行,但是既笨又丑陋:它完全没有用到多胞体具有对称性这一事实!</p><p>这个程序采用的是基于符号计算的途径,这个方法可以精准地得出所有顶点/边/面的信息,代价就是涉及的数学略复杂。我们先回忆一下群在集合上的作用的轨道—稳定化子定理:</p><div id="-----------------------------" class="unnumbered statement sta____________ plain"><p><span class="statement-heading"><span class="statement-label">轨道 —稳定化子定理</span>.</span><span class="statement-spah"> </span>设群<span class="math inline">\(G\)</span> 传递地作用在集合 <span class="math inline">\(S\)</span> 上,设 <span class="math inline">\(x\inS\)</span> 的稳定化子群是 <span class="math inline">\(H\)</span>,则集合<span class="math inline">\(S\)</span> 与 <span class="math inline">\(G/H\)</span> 中的右陪集之间有一一对应:<span class="math inline">\(x\cdot g\leftrightarrow Hg\)</span>。</p></div><div class="statement note definition unnumbered"><p><span class="statement-heading"><span class="statement-label">注</span>:</span><span class="statement-spah"></span>和一般的约定不同,这里群在集合上的作用为作用在右边,主要是为了编程方便,实际上左边右边都一样。</p></div><p>这个定理告诉我们,如果群 <span class="math inline">\(G\)</span>传递地作用在一个集合 <span class="math inline">\(S\)</span> 上,而且对<span class="math inline">\(S\)</span> 中某个元素 <span class="math inline">\(x\)</span> 我们知道了它的稳定化子群 <span class="math inline">\(H\)</span>,则只要对 <span class="math inline">\(G/H\)</span> 的每个陪集代表元 <span class="math inline">\(g\)</span>,将 <span class="math inline">\(g\)</span> 作用在 <span class="math inline">\(x\)</span> 上就可以得到整个 <span class="math inline">\(S\)</span>。</p><p>于是给定一个均匀多胞体 <span class="math inline">\(P\)</span>,要求出其全部顶点集合,我们只要:</p><ol type="1"><li>根据 <span class="math inline">\(P\)</span> 的 Coxeter-Dynkin图确定其对称群 <span class="math inline">\(G\)</span> 和初始顶点 <span class="math inline">\(v_0\)</span>。</li><li>定出 <span class="math inline">\(v_0\)</span> 的稳定化子群 <span class="math inline">\(H\)</span> 并求出 <span class="math inline">\(G/H\)</span> 的一组陪集代表元。</li><li>将 <span class="math inline">\(G/H\)</span> 中的每个陪集代表元作用在<span class="math inline">\(v_0\)</span> 上即得 <span class="math inline">\(P\)</span> 的全部顶点。</li></ol><p>我们仍然以正方体为例来讲解:正方体的 Coxeter-Dynkin 图是</p><p><img style="margin:0px auto;display:block" src="/images/polytopes/cube43.svg" width="200"></p><p>仍然记三个镜面为 <span class="math inline">\(m_0,m_1,m_2\)</span>,其法向量分别为 <span class="math inline">\(n_0,n_1,n_2\)</span>,设 <span class="math inline">\(\rho_0,\rho_1,\rho_2\)</span>分别是关于它们的反射变换,<span class="math inline">\(\rho_i\)</span>对应的矩阵为 <span class="math inline">\(M_i=I-2n_in_i^T\)</span>(见 <a href="https://en.wikipedia.org/wiki/Householder_transformation">Householder变换</a>)。</p><p>正方体的对称群 <span class="math inline">\(G\)</span> 由 <span class="math inline">\(\rho_0,\rho_1,\rho_2\)</span>这三个基本反射生成,其表现为: <span class="math display">\[G =\langle\rho_0,\rho_1,\rho_2\ |\\rho_0^2=\rho_1^2=\rho_2^2=(\rho_0\rho_1)^4=(\rho_1\rho_2)^3=(\rho_0\rho_2)^2=1\rangle.\]</span>这是因为反射的平方总是恒等变换,所以 <span class="math inline">\(\rho_i^2=1\)</span>。<span class="math inline">\(\rho_0,\rho_1\)</span> 是两个夹角为 <span class="math inline">\(3\pi/4\)</span> 的反射,所以 <span class="math inline">\(\rho_0\rho_1\)</span> 是一个角度为 <span class="math inline">\(3\pi/2\)</span> 的旋转,旋转轴为 <span class="math inline">\(m_0\)</span> 和 <span class="math inline">\(m_1\)</span> 的交线,从而 <span class="math inline">\((\rho_0\rho_1)^4=1\)</span>。<span class="math inline">\(\rho_1\rho_2,\rho_0\rho_2\)</span>的情形是类似的。<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a></p><p>利用 Todd-Coxeter 算法 (后面有解释) 不难求出这个群包含 48个元素,罗列如下: <span class="math display">\[\begin{array}{lll}e&\rho_{0}&\rho_{0}\rho_{1}\\\rho_{0}\rho_{1}\rho_{0}&\rho_{0}\rho_{1}\rho_{0}\rho_{1}&\rho_{1}\rho_{0}\rho_{1}\\\rho_{1}\rho_{0}&\rho_{1}&\rho_{0}\rho_{2}\\\rho_{2}&\rho_{1}\rho_{2}&\rho_{1}\rho_{2}\rho_{1}\\\rho_{2}\rho_{1}&\rho_{0}\rho_{1}\rho_{2}&\rho_{0}\rho_{1}\rho_{2}\rho_{1}\\\rho_{0}\rho_{2}\rho_{1}&\rho_{0}\rho_{1}\rho_{0}\rho_{2}&\rho_{0}\rho_{1}\rho_{0}\rho_{1}\rho_{2}\\\rho_{0}\rho_{1}\rho_{0}\rho_{1}\rho_{2}\rho_{1}&\rho_{0}\rho_{1}\rho_{0}\rho_{2}\rho_{1}&\rho_{1}\rho_{0}\rho_{1}\rho_{2}\\\rho_{1}\rho_{0}\rho_{2}&\rho_{1}\rho_{0}\rho_{2}\rho_{1}&\rho_{1}\rho_{0}\rho_{1}\rho_{2}\rho_{1}\\\rho_{2}\rho_{1}\rho_{0}&\rho_{2}\rho_{1}\rho_{0}\rho_{1}&\rho_{0}\rho_{2}\rho_{1}\rho_{0}\rho_{1}\\\rho_{0}\rho_{2}\rho_{1}\rho_{0}&\rho_{1}\rho_{0}\rho_{2}\rho_{1}\rho_{0}&\rho_{1}\rho_{0}\rho_{2}\rho_{1}\rho_{0}\rho_{1}\\\rho_{1}\rho_{2}\rho_{1}\rho_{0}\rho_{1}&\rho_{1}\rho_{2}\rho_{1}\rho_{0}&\rho_{0}\rho_{1}\rho_{0}\rho_{2}\rho_{1}\rho_{0}\\\rho_{0}\rho_{1}\rho_{0}\rho_{2}\rho_{1}\rho_{0}\rho_{1}&\rho_{0}\rho_{1}\rho_{2}\rho_{1}\rho_{0}\rho_{1}&\rho_{0}\rho_{1}\rho_{2}\rho_{1}\rho_{0}\\\rho_{1}\rho_{0}\rho_{1}\rho_{2}\rho_{1}\rho_{0}&\rho_{1}\rho_{0}\rho_{1}\rho_{2}\rho_{1}\rho_{0}\rho_{1}&\rho_{0}\rho_{1}\rho_{0}\rho_{1}\rho_{2}\rho_{1}\rho_{0}\rho_{1}\\\rho_{0}\rho_{1}\rho_{0}\rho_{1}\rho_{2}\rho_{1}\rho_{0}&\rho_{2}\rho_{1}\rho_{0}\rho_{1}\rho_{2}&\rho_{1}\rho_{2}\rho_{1}\rho_{0}\rho_{1}\rho_{2}\\\rho_{0}\rho_{2}\rho_{1}\rho_{0}\rho_{1}\rho_{2}&\rho_{0}\rho_{1}\rho_{2}\rho_{1}\rho_{0}\rho_{1}\rho_{2}&\rho_{1}\rho_{0}\rho_{2}\rho_{1}\rho_{0}\rho_{1}\rho_{2}\\\rho_{1}\rho_{0}\rho_{1}\rho_{2}\rho_{1}\rho_{0}\rho_{1}\rho_{2}&\rho_{0}\rho_{1}\rho_{0}\rho_{2}\rho_{1}\rho_{0}\rho_{1}\rho_{2}&\rho_{0}\rho_{1}\rho_{0}\rho_{1}\rho_{2}\rho_{1}\rho_{0}\rho_{1}\rho_{2}\end{array}\]</span> 由于在正方形的 Coxeter-Dynkin 图中只有镜面 <span class="math inline">\(m_0\)</span> 是被圈出的,即初始顶点 <span class="math inline">\(v_0\)</span> 落在 <span class="math inline">\(m_1\)</span> 和 <span class="math inline">\(m_2\)</span> 上,但不属于 <span class="math inline">\(m_0\)</span>,所以反射 <span class="math inline">\(\rho_1,\rho_2\)</span> 保持 <span class="math inline">\(v_0\)</span> 不动,<span class="math inline">\(\rho_0\)</span> 将 <span class="math inline">\(v_0\)</span> 映射为其关于 <span class="math inline">\(m_0\)</span> 的镜像,于是 <span class="math inline">\(v_0\)</span> 的稳定化子群是 <a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a><span class="math display">\[H=\langle \rho_1, \rho_2\ |\\rho_1^2=\rho_2^2=(\rho_1\rho_2)^3=e\rangle.\]</span> 显然 <span class="math inline">\(H\)</span> 就是二面体群 <span class="math inline">\(D_3\)</span>,所以 <span class="math inline">\(|H|=6\)</span>,从而 <span class="math inline">\(|G/H|=8\)</span>。利用 Todd-Coxeter算法可得其一组右陪集代表元为 <span class="math display">\[\begin{array}{llll}e&\rho_{0}&\rho_{0}\rho_{1}&\rho_{0}\rho_{1}\rho_{0}\\\rho_{0}\rho_{1}\rho_{2}&\rho_{0}\rho_{1}\rho_{0}\rho_{2}&\rho_{0}\rho_{1}\rho_{0}\rho_{2}\rho_{1}&\rho_{0}\rho_{1}\rho_{0}\rho_{2}\rho_{1}\rho_{0}\end{array}\]</span> 将它们作用在 <span class="math inline">\(v_0\)</span>上即可得到正方体的 8 个顶点。例如 <span class="math inline">\(\rho_0\rho_1\)</span> 作用在 <span class="math inline">\(v_0\)</span> 上为 <span class="math display">\[v_0(\rho_0\rho_1)=(v_0\rho_0)\rho_1=(v_0M_0)\rho_1=v_0M_0M_1.\]</span>其中 <span class="math inline">\(v_0\)</span> 写成行向量的形式,每个<span class="math inline">\(M_i\)</span> 是对称矩阵。</p><p>计算边/面/胞腔的原理是类似的,但考虑的细节要多一些。比如我们想求出所有关于第<span class="math inline">\(i\,(i=0,1,2)\)</span> 个镜面 <span class="math inline">\(m_i\)</span> 反射生成的类型为 <span class="math inline">\(i\)</span> 的边,可以这样做:</p><ol type="1"><li>检查初始顶点 <span class="math inline">\(v_0\)</span> 是否落在 <span class="math inline">\(m_i\)</span>上。如果答案为是,那么关于此镜面的反射保持 <span class="math inline">\(v_0\)</span> 不变,此多面体不含类型 <span class="math inline">\(i\)</span> 的边。否则设 <span class="math inline">\(v_0\)</span> 关于 <span class="math inline">\(m_i\)</span> 的镜像为 <span class="math inline">\(v_1\)</span>,则 <span class="math inline">\((v_0,v_1)\)</span> 构成一条类型为 <span class="math inline">\(i\)</span> 的边<span class="math inline">\(e\)</span>。</li><li>关于 <span class="math inline">\(m_i\)</span> 的反射 <span class="math inline">\(\rho_i\)</span> 把 <span class="math inline">\(v_0\)</span> 和 <span class="math inline">\(v_1\)</span> 互换,从而保持 <span class="math inline">\(e\)</span> 不变。<strong>注意其它任何与 <span class="math inline">\(m_i\)</span> 垂直并且包含初始点 <span class="math inline">\(v_0\)</span> 的镜面反射也会保持 <span class="math inline">\(e\)</span> 不变</strong>。在正方形的情形中,反射<span class="math inline">\(\rho_0\)</span> 互换 <span class="math inline">\(e\)</span> 的两端因而保持 <span class="math inline">\(e\)</span> 不变,此外镜面 <span class="math inline">\(m_0\)</span> 和 <span class="math inline">\(m_2\)</span> 是垂直的,且 <span class="math inline">\(v_0\)</span> 包含在 <span class="math inline">\(m_2\)</span> 中,所以反射 <span class="math inline">\(\rho_2\)</span> 保持 <span class="math inline">\(e\)</span> 上的每个点不变,于是 <span class="math inline">\(e\)</span> 的稳定化子群为 <span class="math inline">\(H=\langle \rho_0,\rho_2 \rangle\)</span>。显然<span class="math inline">\(H\)</span> 同构于 <span class="math inline">\(\mathbb{Z}_2\times\mathbb{Z}_2\)</span>,所以<span class="math inline">\(|H|=4\)</span>,从而 <span class="math inline">\(|G/H|=12\)</span>,即正方体有 12 条边 <a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>。</li><li>求出 <span class="math inline">\(G/H\)</span>的一组陪集代表元并作用在 <span class="math inline">\(e\)</span>上得出全部类型为 <span class="math inline">\(i\)</span> 的边。</li></ol><p>求面的情形复杂一些,基本原理是这样的:</p><ol type="1"><li>对 <span class="math inline">\(i\ne j\)</span>,如果初始顶点 <span class="math inline">\(v_0\)</span> 不同时属于镜面 <span class="math inline">\(i\)</span> 和镜面 <span class="math inline">\(j\)</span>,则旋转 <span class="math inline">\(\rho_i\rho_j\)</span> 就可以生成一个面 <span class="math inline">\(f\)</span>。需要注意的是,如果这两个镜面恰好垂直,则必须二者均不包含<span class="math inline">\(v_0\)</span>才能得到一个非退化的面,这个面是个正方形。在正方体的情形,<span class="math inline">\(\rho_0\rho_1\)</span> 可以生成一个面,<span class="math inline">\(\rho_0\rho_2\)</span>(两镜面垂直但只有一个镜面包含<span class="math inline">\(v_0\)</span>)和 <span class="math inline">\(\rho_1\rho_2\)</span>(两镜面均包含 <span class="math inline">\(v_0\)</span>)都不能给出面。</li><li><span class="math inline">\(f\)</span> 的稳定化子群是由 <span class="math inline">\(\rho_i,\rho_j\)</span> 和那些包含 <span class="math inline">\(v_0\)</span> 且与 <span class="math inline">\(m_i,m_j\)</span>均垂直的镜面反射生成。在正方形的情形是 <span class="math inline">\(H=\langle \rho_0,\rho_1 \rangle\)</span> <a href="#fn4" class="footnote-ref" id="fnref4" role="doc-noteref"><sup>4</sup></a>,显然 <span class="math inline">\(H\)</span> 同构于二面体群 <span class="math inline">\(D_8\)</span>,因此 <span class="math inline">\(|H|=8\)</span>,<span class="math inline">\(|G/H|=6\)</span>,即正方体有 6 个面。</li></ol><p>总之关键的步骤都是给定群 <span class="math inline">\(G\)</span>和某个子群 <span class="math inline">\(H\)</span>,求 <span class="math inline">\(G/H\)</span> 的一组陪集代表元。</p><p>这里用到的算法叫做 <a href="https://en.wikipedia.org/wiki/Todd%E2%80%93Coxeter_algorithm">Todd-Coxeter算法</a>。</p><p>Todd-Coxeter算法在许多抽象代数或者群论的教材都有,用到的数学知识并不复杂。但完整描述并证明一份程序实现的细节还是很费功夫的,恐怕要好几页纸才能说清楚。限于篇幅,我这里仅用正方体的情形为例说明算法的步骤,具体的证明和更多的细节读者请参考</p><blockquote><p>Handbook of Computational Group Theory, Holt, D., Eick, B., O’Brien,E.</p></blockquote><p>中的 coset enumeration 一章。我个人认为这是讲解 Todd-Coxeter算法最棒的文献。</p><p>Todd-Coxeter 算法非常类似玩数独游戏,这里要填的表是一个变化的二维数组<span class="math inline">\(T\)</span>,<span class="math inline">\(T\)</span> 的行 <span class="math inline">\(i\)</span> 代表第 <span class="math inline">\(i\)</span> 个右陪集,<span class="math inline">\(T\)</span> 的列 <span class="math inline">\(j\)</span> 代表第 <span class="math inline">\(j\)</span> 个生成元 <span class="math inline">\(\rho_j\)</span>,<span class="math inline">\(T[i][j]\)</span> 的值等于 <span class="math inline">\(\rho_j\)</span> 右乘以第 <span class="math inline">\(i\)</span>个陪集后得到的陪集。初始时,我们知道肯定有一个陪集,就是 <span class="math inline">\(H\)</span>自身,还有没有其它的陪集我们不清楚。算法的主要流程就是根据 <span class="math inline">\(G\)</span> 和 <span class="math inline">\(H\)</span>的表现中包含的关系来发现新的陪集并填入表中,直到无法找到新的陪集为止。最终得到的<span class="math inline">\(T\)</span> 实际上是 <span class="math inline">\(G/H\)</span> 的 schreier 图的邻接矩阵,它记录了<span class="math inline">\(G/H\)</span> 的陪集间的乘法关系,由 <span class="math inline">\(T\)</span> 出发我们很容易求出这些陪集的 word表示。</p><div class="unnumbered statement example definition"><p><span class="statement-heading"><span class="statement-label">例</span>:</span><span class="statement-spah"></span>设 <span class="math inline">\(G\)</span>是正方体的对称群,其表现为 <span class="math display">\[G =\langle\rho_0,\rho_1,\rho_2\ |\\rho_0^2=\rho_1^2=\rho_2^2=(\rho_0\rho_1)^4=(\rho_1\rho_2)^3=(\rho_0\rho_2)^2=1\rangle.\]</span>子群 <span class="math inline">\(H=\langle \rho_1,\rho_2\rangle\)</span> 是初始顶点的稳定化子群,求 <span class="math inline">\(G/H\)</span> 的一组右陪集代表元。</p></div><p>我们先罗列一下这个数独游戏已知的关系:</p><p><strong>已知关系</strong>:</p><ol type="1"><li>对 <span class="math inline">\(H\)</span> 的任何生成字 <span class="math inline">\(w\)</span> 有 <span class="math inline">\(H\cdotw=H\)</span>,即 <span class="math inline">\(H\rho_1=H\rho_2=H\)</span>。注意此关系仅要求对<span class="math inline">\(H\)</span> 成立。</li><li>对任何陪集 <span class="math inline">\(K\)</span> 和 <span class="math inline">\(G\)</span> 的任何生成关系 <span class="math inline">\(r\)</span> 有 <span class="math inline">\(K\cdotr=K\)</span>,即 <span class="math inline">\(K\rho_i^2=K,i=0,1,2\)</span> 以及 <span class="math inline">\(K(\rho_0\rho_1)^4=K(\rho_1\rho_2)^3=K(\rho_0\rho_2)^2=K\)</span>。注意此关系要求对所有陪集成立。</li></ol><p>这些关系可以存储在两个列表里面,每个关系用一个数组表示。</p><p>第一个列表存储的是 <span class="math inline">\(H\)</span>的生成字,即</p><blockquote><p><strong><span class="math inline">\(H\)</span>的生成字列表</strong>:</p><ol start="0" type="1"><li>(1,)</li><li>(2,)</li></ol></blockquote><p>第二个列表存储的是 <span class="math inline">\(G\)</span>的生成关系,即</p><blockquote><p><strong><span class="math inline">\(G\)</span>的生成关系列表</strong>:</p><ol start="2" type="1"><li>(0, 0)</li><li>(1, 1)</li><li>(2, 2)</li><li>(0, 1, 0, 1, 0, 1, 0, 1)</li><li>(1, 2, 1, 2, 1, 2)</li><li>(0, 2, 0, 2)</li></ol></blockquote><p>其中每条关系前面的数字是我们加上的编号以便于称呼。</p><div class="statement note definition unnumbered"><p><span class="statement-heading"><span class="statement-label">注</span>:</span><span class="statement-spah"></span>在非 Coxeter 群的情形还要把每个生成元的逆也作为生成元加入,其在<span class="math inline">\(T\)</span> 中也占据一列,所以实际上 <span class="math inline">\(T\)</span> 的列的个数要 <span class="math inline">\(\times2\)</span>。但是在 Coxeter群的情形每个生成元是 2阶的,其逆元素等于自身,所以不需要额外考虑逆元素。</p></div><p>初始时刻表格 <span class="math inline">\(T\)</span> 是这样的:</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr></tbody></table><p>其中 <span class="math inline">\(H_0\)</span> 代表 <span class="math inline">\(H\)</span> 对应的陪集。程序首先验证 <span class="math inline">\(H_0\)</span> 所在的行满足第一个关系列表 (<span class="math inline">\(H\)</span>的生成字列表,随后此列表可以被丢弃),然后依次从上到下扫描 <span class="math inline">\(T\)</span> 的每一行,假设当前扫描的是第 <span class="math inline">\(i\)</span> 行,对应的陪集为 <span class="math inline">\(H_i\)</span>,程序验证确保对第二个列表 (<span class="math inline">\(G\)</span> 的生成关系列表) 中的每条关系 <span class="math inline">\(w\)</span>,<span class="math inline">\(H_i\)</span> 满足 <span class="math inline">\(H_iw=H_i\)</span>,这个过程中可能发现新的陪集,也可能发现已有的某些陪集是重复的,也有可能需要强行定义新的陪集来使得这个验证能够完成。</p><hr><p>我们来开始扫描 <span class="math inline">\(H_0\)</span>所在的行:首先检查第一个列表中的关系,<strong>这个列表仅在扫描 <span class="math inline">\(H_0\)</span>时使用一次,扫描完就可以丢弃</strong>。</p><p>(1). 对第 0 条关系 <span class="math inline">\(H_0\rho_1=H_0\)</span>,即 <span class="math inline">\(T[0][1]=0\)</span>。对第 1 条关系 <span class="math inline">\(H_0\rho_2=H_0\)</span>,即 <span class="math inline">\(T[0][2]=0\)</span>,这时 <span class="math inline">\(T\)</span> 变成了</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr></tbody></table><p>第一个列表扫描完毕,接下来扫描第二个列表。</p><p>(2). 对第 2 条关系 <span class="math inline">\(H_0\rho_0^2=H_0\)</span>,由于 <span class="math inline">\(H_0\rho_0\)</span> 还不知道,我们将其定义为新陪集<span class="math inline">\(H_1\)</span> 并将 1 填入 <span class="math inline">\(T[0][0]\)</span> 位置,此外还要为 <span class="math inline">\(H_1\)</span> 开辟新的一行:</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_1\)</span></td><td style="text-align: center;">0</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr></tbody></table><div class="statement note definition unnumbered"><p><span class="statement-heading"><span class="statement-label">注</span>:</span><span class="statement-spah"></span>每次定义新陪集时,比如定义 <span class="math inline">\(H_i\rho_j=H_k\)</span>,我们同时自动得到了与之对称的关系<span class="math inline">\(H_k\rho_j=H_i\rho_j^2=H_i\)</span>,因此每次填表时我们都填写一对数字而不是一个,这样可以保证表格<span class="math inline">\(T\)</span> 的 “对称性”。</p></div><p>(3). 第 3 条和第 4 条关系已经满足,继续。</p><p>(4). 第 5 条关系,<span class="math inline">\(H_0\rho_0\rho_1\rho_0\rho_1\rho_0\rho_1\rho_0\rho_1=H_0\)</span>,我们已经知道<span class="math inline">\(H_0\rho_0=H_1\)</span> 但是 <span class="math inline">\(H_1\rho_1\)</span> 还不知道,将其定义为 <span class="math inline">\(H_2\)</span>,于是 <span class="math inline">\(T\)</span> 中又添两项,并开辟新的一行给 <span class="math inline">\(H_2\)</span>:</p><table><thead><tr class="header"><th style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_1\)</span></td><td style="text-align: center;">0</td><td style="text-align: center;">2</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_2\)</span></td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td><td style="text-align: center;">1</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr></tbody></table><p>但是 <span class="math inline">\(H_2\rho_0\)</span>还是不知道,所以继续定义 <span class="math inline">\(H_2\rho_0=H_3\)</span>,于是 <span class="math inline">\(T\)</span> 变成</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_1\)</span></td><td style="text-align: center;">0</td><td style="text-align: center;">2</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_2\)</span></td><td style="text-align: center;">3</td><td style="text-align: center;">1</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_3\)</span></td><td style="text-align: center;">2</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr></tbody></table><p>于是现在关系变成了</p><p><span class="math display">\[\underbrace{\overbrace{\underbrace{H_0\rho_0}_{=H_1}\,\rho_1}^{=H_2}\,\rho_0}_{=H_3}\,\rho_1\rho_0\rho_1\rho_0\rho_1=H_0.\]</span></p><p>但是 <span class="math inline">\(H_3\rho_1\)</span>还是不知道,你可能会想把它继续定义为新的陪集 <span class="math inline">\(H_4\)</span>,然后继续扫描。<strong>这样做不是不可以,但是每次都定义新陪集会生成大量重复的陪集,导致<span class="math inline">\(T\)</span>增长的非常快,对更复杂的群非常耗费计算资源</strong>。我们采用更聪明的办法:倒着扫描整个关系,即从右到左扫描<span class="math inline">\(H_0\rho_0\rho_1\rho_0\rho_1\rho_0\rho_1\rho_0\rho_1=H_0\)</span>这条关系。记住我们现在已经正向 (从左到右) 扫描到了下面的位置: <span class="math display">\[\underbrace{H_0\rho_0\rho_1\rho_0}_{=H_3}\,\rho_1|\rho_0\rho_1\rho_0\rho_1=H_0.\]</span>反向扫描意味着我们把上式左边末尾的 <span class="math inline">\(\rho_0\rho_1\rho_0\rho_1\)</span>挪到右边去,变形为 <span class="math display">\[\underbrace{H_0\rho_0\rho_1\rho_0}_{=H_3}\,\rho_1=\underbrace{H_0\rho_1}_{=H_0}\rho_0\rho_1\rho_0=H_0\rho_0\rho_1\rho_0=H_3.\]</span> 从而 <span class="math inline">\(H_3\rho_1=H_3\)</span>。这样通过反向扫描我们就推断出了<span class="math inline">\(H_3\rho_1\)</span>的值,避免了定义冗余的陪集。按照 Holt 书中的说法这叫做一个deduction。这时 <span class="math inline">\(T\)</span> 为</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_1\)</span></td><td style="text-align: center;">0</td><td style="text-align: center;">2</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_2\)</span></td><td style="text-align: center;">3</td><td style="text-align: center;">1</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_3\)</span></td><td style="text-align: center;">2</td><td style="text-align: center;">3</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr></tbody></table><div class="statement note definition unnumbered"><p><span class="statement-heading"><span class="statement-label">注</span>:</span><span class="statement-spah"></span>在实际的程序实现中,我们总是从关系的两头同时开始扫描,直到它们相遇为止。</p></div><p>(5). 关系 6 已经满足,继续。</p><p>(6). 对关系 7 <span class="math inline">\(H_0\rho_0\rho_2\rho_0\rho_2=H_0\)</span>,从两头扫描我们得到<span class="math display">\[\underbrace{H_0\rho_0}_{=H_1}\,\rho_2=\underbrace{\overbrace{H_0\rho_2}^{=H_0}\rho_0}_{=H_1}.\]</span>即 <span class="math inline">\(H_1\rho_2=H_1\)</span>,我们又得到了一个deduction,从而 <span class="math inline">\(T\)</span> 变成</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_1\)</span></td><td style="text-align: center;">0</td><td style="text-align: center;">2</td><td style="text-align: center;">1</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_2\)</span></td><td style="text-align: center;">3</td><td style="text-align: center;">1</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_3\)</span></td><td style="text-align: center;">2</td><td style="text-align: center;">3</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr></tbody></table><p>至此对 <span class="math inline">\(H_0\)</span>的扫描全部完成,我们转入扫描 <span class="math inline">\(H_1\)</span>所在的行。</p><hr><p><strong>注意:从现在起至程序结束,我们不再使用第一个列表</strong>。</p><p>下面开始扫描 <span class="math inline">\(H_1\)</span> 所在的行。</p><p>(1). 经检查关系 2, 3, 4, 5 已经满足,继续。</p><p>(2). 对关系 6 有 <span class="math inline">\(H_1\rho_1\rho_2\rho_1\rho_2\rho_1\rho_2=H_1\)</span>,其中<span class="math inline">\(H_1\rho_1=H_2\)</span> 已知但 <span class="math inline">\(H_2\rho_2\)</span> 未知。反向的扫描也会卡在这里:<span class="math display">\[\underbrace{H_1\rho_1}_{=H_2}\rho_2\rho_1=H_1\rho_2\rho_1\rho_2=H_2\rho_2.\]</span>所以我们定义新陪集 <span class="math inline">\(H_2\rho_2=H_4\)</span>,于是 <span class="math inline">\(H_4\rho_1=H_4\)</span>,从而此时 <span class="math inline">\(T\)</span> 为</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_1\)</span></td><td style="text-align: center;">0</td><td style="text-align: center;">2</td><td style="text-align: center;">1</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_2\)</span></td><td style="text-align: center;">3</td><td style="text-align: center;">1</td><td style="text-align: center;">4</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_3\)</span></td><td style="text-align: center;">2</td><td style="text-align: center;">3</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_4\)</span></td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td><td style="text-align: center;">4</td><td style="text-align: center;">2</td></tr></tbody></table><p>(3). 关系 7 已经满足,从而 <span class="math inline">\(H_1\)</span>检查完毕,接下来开始扫描 <span class="math inline">\(H_2\)</span>的行。</p><hr><p>下面开始扫描 <span class="math inline">\(H_2\)</span> 的行。</p><p>(1). 经检查关系 2, 3, 4, 5, 6 都已经满足,继续。</p><p>(2). 对关系 7 有 <span class="math inline">\(H_2\rho_0\rho_2\rho_0\rho_2=H_2\)</span>,两边同时扫描的结果为:<span class="math display">\[\underbrace{H_2\rho_0}_{=H_3}\rho_2\rho_0=H_2\rho_2=H_4.\]</span>即 <span class="math inline">\(H_3\rho_2\rho_0=H_4\)</span>,但是继续正向扫描<span class="math inline">\(H_3\rho_2\)</span> 不知道,继续反向扫描<span class="math inline">\(H_4\rho_0\)</span> 不知道。定义新陪集 <span class="math inline">\(H_3\rho_2=H_5\)</span>,于是 <span class="math inline">\(H_5\rho_0=H_4\)</span>,我们又可以填入两对 4个数字,此时 <span class="math inline">\(T\)</span> 为:</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_1\)</span></td><td style="text-align: center;">0</td><td style="text-align: center;">2</td><td style="text-align: center;">1</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_2\)</span></td><td style="text-align: center;">3</td><td style="text-align: center;">1</td><td style="text-align: center;">4</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_3\)</span></td><td style="text-align: center;">2</td><td style="text-align: center;">3</td><td style="text-align: center;">5</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_4\)</span></td><td style="text-align: center;">5</td><td style="text-align: center;">4</td><td style="text-align: center;">2</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_5\)</span></td><td style="text-align: center;">4</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td><td style="text-align: center;">3</td></tr></tbody></table><p><span class="math inline">\(H_2\)</span> 扫描完毕,下面扫描 <span class="math inline">\(H_3\)</span> 的行。</p><hr><p>我把 <span class="math inline">\(H_3, H_4, H_5\)</span>的扫描过程留给你作为练习,<span class="math inline">\(H_3\)</span>扫描结束后你得到的 <span class="math inline">\(T\)</span>应该如下图:</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_1\)</span></td><td style="text-align: center;">0</td><td style="text-align: center;">2</td><td style="text-align: center;">1</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_2\)</span></td><td style="text-align: center;">3</td><td style="text-align: center;">1</td><td style="text-align: center;">4</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_3\)</span></td><td style="text-align: center;">2</td><td style="text-align: center;">3</td><td style="text-align: center;">5</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_4\)</span></td><td style="text-align: center;">5</td><td style="text-align: center;">4</td><td style="text-align: center;">2</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_5\)</span></td><td style="text-align: center;">4</td><td style="text-align: center;">6</td><td style="text-align: center;">3</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_6\)</span></td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td><td style="text-align: center;">5</td><td style="text-align: center;">6</td></tr></tbody></table><p><span class="math inline">\(H_4\)</span> 扫描结束后你得到的 <span class="math inline">\(T\)</span> 应该如下图:</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_1\)</span></td><td style="text-align: center;">0</td><td style="text-align: center;">2</td><td style="text-align: center;">1</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_2\)</span></td><td style="text-align: center;">3</td><td style="text-align: center;">1</td><td style="text-align: center;">4</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_3\)</span></td><td style="text-align: center;">2</td><td style="text-align: center;">3</td><td style="text-align: center;">5</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_4\)</span></td><td style="text-align: center;">5</td><td style="text-align: center;">4</td><td style="text-align: center;">2</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_5\)</span></td><td style="text-align: center;">4</td><td style="text-align: center;">6</td><td style="text-align: center;">3</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_6\)</span></td><td style="text-align: center;">7</td><td style="text-align: center;">5</td><td style="text-align: center;">6</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_7\)</span></td><td style="text-align: center;">6</td><td style="text-align: center;">7</td><td style="text-align: center;"><span class="math inline">\(\phantom{}\)</span></td></tr></tbody></table><p><span class="math inline">\(H_5\)</span> 的扫描给不出新的信息。</p><p>扫描 <span class="math inline">\(H_6\)</span> 时,关系 2, 3, 4, 5, 6都已经满足,由关系 7 <span class="math inline">\(H_6\rho_0\rho_2\rho_0\rho_2=H_6\)</span> 可得deduction <span class="math inline">\(H_7\rho_2=H_7\)</span>,于是 <span class="math inline">\(T\)</span> 可以补全为</p><table><thead><tr class="header"><th style="text-align: center;"></th><th style="text-align: center;"><span class="math inline">\(\rho_0\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_1\)</span></th><th style="text-align: center;"><span class="math inline">\(\rho_2\)</span></th></tr></thead><tbody><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_0\)</span></td><td style="text-align: center;">1</td><td style="text-align: center;">0</td><td style="text-align: center;">0</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_1\)</span></td><td style="text-align: center;">0</td><td style="text-align: center;">2</td><td style="text-align: center;">1</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_2\)</span></td><td style="text-align: center;">3</td><td style="text-align: center;">1</td><td style="text-align: center;">4</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_3\)</span></td><td style="text-align: center;">2</td><td style="text-align: center;">3</td><td style="text-align: center;">5</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_4\)</span></td><td style="text-align: center;">5</td><td style="text-align: center;">4</td><td style="text-align: center;">2</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_5\)</span></td><td style="text-align: center;">4</td><td style="text-align: center;">6</td><td style="text-align: center;">3</td></tr><tr class="odd"><td style="text-align: center;"><span class="math inline">\(H_6\)</span></td><td style="text-align: center;">7</td><td style="text-align: center;">5</td><td style="text-align: center;">6</td></tr><tr class="even"><td style="text-align: center;"><span class="math inline">\(H_7\)</span></td><td style="text-align: center;">6</td><td style="text-align: center;">7</td><td style="text-align: center;">7</td></tr></tbody></table><p>扫描 <span class="math inline">\(H_7\)</span>发现所有关系都已经满足。</p><p>至此 <span class="math inline">\(T\)</span>的空白位置都已经填满,没有新的陪集可以发现,数独游戏结束,这时得到的<span class="math inline">\(T\)</span> 就是 <span class="math inline">\(G/H\)</span> 的最终乘法表。</p><p>由此利用宽度优先搜索不难得出陪集间的关系为: <span class="math display">\[\begin{array}{l}H_0 = H_0\cdot e,\\H_1=H_0\cdot\rho_0,\\H_2=H_1\cdot\rho_1=H_0\cdot\rho_0\rho_1,\\H_3=H_2\cdot\rho_0=H_0\cdot\rho_0\rho_1\rho_0,\\H_4=H_2\cdot\rho_2=H_0\cdot\rho_0\rho_1\rho_2,\\H_5=H_3\cdot\rho_2=H_0\cdot \rho_0\rho_1\rho_0\rho_2,\\H_6=H_5\cdot\rho_1=H_0\cdot \rho_0\rho_1\rho_0\rho_2\rho_1,\\H_7=H_6\cdot\rho_0=H_0\cdot\rho_0\rho_1\rho_0\rho_2\rho_1\rho_0.\end{array}\]</span></p><p>从而其一组陪集代表元可以选为 <span class="math display">\[\begin{array}{llll}e&\rho_{0}&\rho_{0}\rho_{1}&\rho_{0}\rho_{1}\rho_{0}\\\rho_{0}\rho_{1}\rho_{2}&\rho_{0}\rho_{1}\rho_{0}\rho_{2}&\rho_{0}\rho_{1}\rho_{0}\rho_{2}\rho_{1}&\rho_{0}\rho_{1}\rho_{0}\rho_{2}\rho_{1}\rho_{0}\end{array}\]</span></p><p>这正是我们前面看到过的。</p><div class="statement note definition unnumbered"><p><span class="statement-heading"><span class="statement-label">注</span>:</span><span class="statement-spah"></span>这个例子看似有点长,但还是一个比较简单的例子,其中并没有出现已知陪集重复的情形(Holt的书中称之为coincidence)。这种情形的处理麻烦一些,因为一旦出现重复的陪集,就有可能顺藤摸瓜找到更多重复的陪集。这时就要立刻暂停扫描,流程跳转到处理coincidence:开辟一个栈来存放已知的coincidence,每次弹出一对,将它们合并,然后把新发现的 coincidence压入栈中。</p></div><h1 id="星状多胞体的计算">星状多胞体的计算</h1><p>星状多胞体也可以使用 Wythoff构造法来生成,但是直接套用上面的方法一般是行不通的,我们需要在生成元中加入额外的生成关系。</p><p>这里以 <a href="https://en.wikipedia.org/wiki/Great_dodecahedron">Greatdodecahedron</a> 为例来说明:其 Coxeter-Dynkin 图为</p><p><img style="margin:0px auto;display:block" src="/images/polytopes/coxeter552.svg" width="200"></p><p>于是三面镜子的法向量夹角分别为 <span class="math inline">\(\pi-2\pi/5, \pi/2,\pi-\pi/5\)</span>。如果我们仍然沿用以前的分析,会得出其对称群的表现为<span class="math display">\[K=\langle\tau_0,\tau_1,\tau_2 \|\ \tau_0^2=\tau_1^2=\tau_2^2=(\tau_0\tau_1)^5=(\tau_1\tau_2)^5=(\tau_0\tau_2)^2=1\rangle.\]</span></p><p>这是一个无限群,而且顶点的稳定化子的商群也是无限的,所以还想按以前的方法计算就行不通了。</p><p>实际上我们只要在生成关系中再加上一条 <span class="math inline">\((\tau_0\tau_1\tau_2\tau_1)^3=1\)</span>即可,即对称群的表现为</p><p><span class="math display">\[\begin{align*}K = \langle\tau_0,\tau_1,\tau_2 \|\ &\tau_0^2=\tau_1^2=\tau_2^2=(\tau_0\tau_1)^5=(\tau_1\tau_2)^5=\\&(\tau_0\tau_2)^2=(\tau_0\tau_1\tau_2\tau_1)^3=1\rangle.\end{align*}\]</span></p><p>注意到我使用了字母 <span class="math inline">\(\tau\)</span>来表示反射,<span class="math inline">\(K\)</span> 表示 Greatdodecahedron的对称群,这个记号选择是有意的。这是怎么回事呢?先看视频:</p><video src="/images/polytopes/great-dodecahedron.mp4" width="480" controls=""></video><p>(请忽略左边错误的 Coxeter 图,这个 ui 界面我改不动)</p><p>由视频可见,Great dodecahedron 与正二十面体 (icosahedron)共用相同的顶点,并且看起来 Great dodecahedron 可以通过在 icosahedron表面挖一些三角形的洞得到。这个结论也可以推广:一般地如果星状多面体的洞是一个有<span class="math inline">\(h\)</span>条边的多边形,则对应的额外生成关系就是 <span class="math inline">\((\tau_0\tau_1\tau_2\tau_1)^h=1\)</span>。</p><p><img style="margin:0px auto;display:block" src="/images/polytopes/star.png" width="600"></p><p>在上图中,<span class="math inline">\(\Delta ABC\)</span>是正二十面体的基本区域,三个内角分别是 <span class="math inline">\(\angle CAB=\pi/5\)</span>,<span class="math inline">\(\angle CBA=\pi/2\)</span>,<span class="math inline">\(\angle ACB=\pi/3\)</span>,<span class="math inline">\(\rho_0,\rho_1,\rho_2\)</span> 分别是关于弧 <span class="math inline">\(BC, AC, AB\)</span>的反射。正二十面体的对称群的表现为 <span class="math display">\[G =\langle\rho_0,\rho_1,\rho_2\ |\\rho_0^2=\rho_1^2=\rho_2^2=(\rho_0\rho_1)^3=(\rho_1\rho_2)^5=(\rho_0\rho_2)^2=1\rangle.\]</span></p><p>Great dodecahedron 可以这样得到:沿着正二十面体的边从顶点 <span class="math inline">\(Q\)</span> 走到 <span class="math inline">\(A\)</span>,右手边的面是三角形 <span class="math inline">\(\Delta OAQ\)</span>,接下来的第一条边应该是 <span class="math inline">\(AO\)</span>,我们跳过这条边,选择第二条边 <span class="math inline">\(AK\)</span>,到达 <span class="math inline">\(K\)</span> 后继续选择右手边的第二条边,这样绕着<span class="math inline">\(O\)</span> 一圈下来共经过 5条边,它们正好围成 Great dodecahedron的一个面。对正二十面体的其它边也如此操作会得到 Great dodecahedron其它的面。</p><p>像这样对一个多面体,保持它的顶点和边的集合不变,但是每次选择右手边的第<span class="math inline">\(k\)</span>个边走下去绕一圈获得一个面,这样构造新多面体的方法叫做 <strong>Facetting手术</strong>。在我们这个项目中 <span class="math inline">\(k\)</span>总是等于 2。</p><p>我们来导出正二十面体的对称群 <span class="math inline">\(G\)</span>和 Great dodecahedron 的对称群 <span class="math inline">\(K\)</span>之间的关系。</p><p>来看三角形 <span class="math inline">\(\DeltaOAB\)</span>,它的三个内角分别是 <span class="math inline">\(\angleOAB=2\pi/5\)</span>,<span class="math inline">\(\angleOBA=\pi/2\)</span>,<span class="math inline">\(\angleAOB=\pi/5\)</span>,它包含三个与 <span class="math inline">\(\DeltaABC\)</span> 全等的三角形,关于其三条边 <span class="math inline">\(OA,OB,AB\)</span> 的反射分别是 <span class="math inline">\(\tau_1=\rho_1\rho_2\rho_1,\tau_0=\rho_0,\tau_2=\rho_2\)</span><a href="#fn5" class="footnote-ref" id="fnref5" role="doc-noteref"><sup>5</sup></a>。</p><p>Facetting 操作 <span class="math inline">\(\varphi_k\)</span>用群的语言来描述就是(记住 <span class="math inline">\(k=2\)</span>)<span class="math display">\[G=\langle\rho_0,\rho_1,\rho_2\rangle\xrightarrow{\ \varphi_k\}\langle\rho_0,\rho_1(\rho_2\rho_1)^{k-1},\rho_2\rangle=\langle\tau_0,\tau_1,\tau_2\rangle=K.\]</span>一般来说 <span class="math inline">\(K\)</span> 是 <span class="math inline">\(G\)</span> 的子群,但在这里 <span class="math inline">\(G\)</span> 和 <span class="math inline">\(K\)</span> 就是同一个群。我们不解释为什么 <span class="math inline">\(G=K\)</span>,这里只承认这一点,然后借助这个事实来说明<span class="math inline">\(K\)</span> 就是 Great dodecahedron的对称群。</p><p>首先 <span class="math inline">\(\langle \tau_1,\tau_2\rangle=\langle\rho_1,\rho_2\rangle\)</span> 是顶点 <span class="math inline">\(A\)</span> 的稳定化子群,所以 Great dodecahedron和正二十面体的顶点集是一样的。但 <span class="math inline">\(\tau_1\tau_2\)</span> 是一个角度为 144度的旋转,这一点和 <span class="math inline">\(\rho_1\rho_2\)</span>是一个 72 度的旋转不同,所以 Great dodecahedron 的 vertex configure是一个五角星,而不像正二十面体那样是一个五边形。</p><p>其次 <span class="math inline">\(\langle\tau_0,\tau_2\rangle=\langle\rho_0,\rho_2\rangle\)</span>为边 <span class="math inline">\(AQ\)</span> 的稳定化子群,所以 Greatdodecahedron 的边集和正二十面体也是一样的。</p><p>它俩的区别在于边组成面的方式不一样。<span class="math inline">\(\langle\tau_0,\tau_1\rangle\)</span> 是 Greatdodecahedron 面的稳定化子群,注意到 <span class="math inline">\(\tau_1\)</span> 是关于 <span class="math inline">\(AO\)</span> 的反射,它会把 <span class="math inline">\(AQ\)</span> 映射为 <span class="math inline">\(AK\)</span>,这正对应选择第 <span class="math inline">\(k\)</span> 条边的操作。<span class="math inline">\(\tau_0\tau_1\)</span> 是一个绕着顶点 <span class="math inline">\(O\)</span> 的角度为 <span class="math inline">\(2\pi/5\)</span> 的旋转,所以 <span class="math inline">\(AQ\)</span> 在子群 <span class="math inline">\(\langle \tau_0,\tau_1\rangle\)</span> 作用下会绕<span class="math inline">\(O\)</span> 转一圈,正对应 Facetting操作得到的一个面。</p><p>我们来找出 <span class="math inline">\(\tau_0,\tau_1,\tau_2\)</span>之间隐藏的一条生成关系:</p><p>注意到 <span class="math inline">\(\tau_1\tau_2\tau_1=\tau_1\rho_2\tau_1\)</span>是关于 <span class="math inline">\(AP\)</span> 的反射,它和 <span class="math inline">\(\tau_0=\rho_0\)</span> 的复合是绕着 <span class="math inline">\(P\)</span> 点角度为 <span class="math inline">\(2\pi/3\)</span> 的旋转,所以 <span class="math inline">\((\tau_0\tau_1\tau_2\tau_1)^3=1\)</span>。加入这个额外的生成关系得到的就是<span class="math inline">\(K\)</span> 的正确的表现: <span class="math display">\[\begin{align*}K = \langle\tau_0,\tau_1,\tau_2 \|\ &\tau_0^2=\tau_1^2=\tau_2^2=(\tau_0\tau_1)^5=(\tau_1\tau_2)^5=\\&(\tau_0\tau_2)^2=(\tau_0\tau_1\tau_2\tau_1)^3=1\rangle.\end{align*}\]</span></p><p>所以我们就可以对 <span class="math inline">\(K\)</span>照搬之前的绘制步骤了。</p><p>这个额外的生成关系其实也有背后的解释:对 Faceting手术得到的新多面体再进行一次 Faceting 手术是可以回到原来的多面体的。对great dodecahedron每次沿着它的边,选择当前离开的边的右手第二个边走下去,即从 <span class="math inline">\(Q\)</span> 走到 <span class="math inline">\(A\)</span> 时,不是选择 <span class="math inline">\(AK\)</span> 继续走下去,而是选择 <span class="math inline">\(AO\)</span>,这样走下去又会得到正二十面体的三角形的面,这对应的就是额外的生成关系中的指数3。</p><p>这一点从群上也可以得到验证。</p><p><span class="math display">\[K=\langle\tau_0,\tau_1,\tau_2\rangle\xrightarrow{\ \varphi_2\}\langle\tau_0,\tau_1\tau_2\tau_1,\tau_2\rangle=\langle\rho_0,\rho_2\rho_1\rho_2,\rho_2\rangle=G.\]</span></p><blockquote><p>关于 Faceting 手术可以在 McMullen 和 Schulte 所著的 <a href="https://doi.org/10.1017/CBO9780511546686">Abstract RegularPolytopes</a> 一书中找到。</p></blockquote><h1 id="snub-cube-的计算">Snub cube 的计算</h1><p>如果你理解了上面的内容,snub 多面体的情形也是不难理解的。我以 snubcube 来说明:</p><p>Snub cube 和 cube 的区别在于它的对称群只包含旋转,我们已经看到 cube的对称群 <span class="math inline">\(G\)</span> 的表现为 <span class="math display">\[G = \langle\rho_0,\rho_1,\rho_2\ |\\rho_0^2=\rho_1^2=\rho_2^2=(\rho_0\rho_1)^4=(\rho_1\rho_2)^3=(\rho_0\rho_2)^2=1\rangle.\]</span>它有 48 个元素,其中 24 个是旋转。这些旋转可以由 <span class="math inline">\(r_0=\rho_0\rho_1,r_1=\rho_1\rho_2,r_2=\rho_0\rho_2\)</span> 生成 (由于 <span class="math inline">\(r_0r_1=r_2\)</span> 因此实际上可以由 <span class="math inline">\(r_0\)</span> 和 <span class="math inline">\(r_1\)</span> 生成)。这 24 个旋转就构成了 Snubcube 的对称群 <span class="math inline">\(\widetilde{G}\)</span>。</p><p>不难写出 <span class="math inline">\(\widetilde{G}\)</span> 的表现为<span class="math display">\[\widetilde{G}=\langle r_0,r_1\ |\r_0^4=r_1^3=(r_0r_1)^2=1\rangle.\]</span></p><p>利用 Todd-Coxeter 算法不难求出这个群的所有 24 个元素:</p><p><span class="math display">\[\begin{array}{lll}e&r_{0}&r_{0}r_{0}\\r_{0}r_{0}r_{0}&r_{1}&r_{1}r_{1}\\r_{0}r_{1}&r_{0}r_{1}r_{1}&r_{0}r_{0}r_{1}\\r_{0}r_{0}r_{1}r_{1}&r_{0}r_{0}r_{0}r_{1}&r_{1}r_{0}\\r_{1}r_{0}r_{0}&r_{1}r_{0}r_{0}r_{0}&r_{1}r_{1}r_{0}\\r_{1}r_{1}r_{0}r_{0}&r_{0}r_{1}r_{1}r_{0}&r_{0}r_{1}r_{1}r_{0}r_{0}\\r_{0}r_{0}r_{1}r_{1}r_{0}&r_{1}r_{0}r_{0}r_{1}&r_{1}r_{0}r_{0}r_{1}r_{1}\\r_{1}r_{0}r_{0}r_{0}r_{1}&r_{1}r_{1}r_{0}r_{0}r_{1}&r_{0}r_{1}r_{1}r_{0}r_{0}r_{1}\end{array}\]</span></p><p>注意在 snub 的情形初始顶点 <span class="math inline">\(v_0\)</span>不属于任何镜面,所以其稳定化子群只有单位元 1,即每个 <span class="math inline">\(g\in\widetilde{G}\)</span> 把 <span class="math inline">\(v_0\)</span> 变换为不同的顶点。将它们作用在 <span class="math inline">\(v_0\)</span> 上即得 snub cube 的所有顶点。</p><p>我们现在利用轨道—稳定化子的理论来求 snub cube 的边。snub cube的边也是分类型的,每个 <span class="math inline">\(r_i(i=0,1,2)\)</span>作用在 <span class="math inline">\(v_0\)</span> 上可得一个类型为 <span class="math inline">\(i\)</span> 的边 <span class="math inline">\(e_i=(v_0, v_0\cdot r_i)\)</span> <a href="#fn6" class="footnote-ref" id="fnref6" role="doc-noteref"><sup>6</sup></a>,我们来定出 <span class="math inline">\(e_i\)</span> 的稳定化子群 <span class="math inline">\(H\)</span>。</p><p>首先注意到任何 <span class="math inline">\(g\in G\)</span> 如果保持<span class="math inline">\(e_i\)</span>不变,则只有两种可能,要么它保持 <span class="math inline">\(e_i\)</span> 上每个点不变,要么它将 <span class="math inline">\(e_i\)</span> 关于其中点进行翻转。这一点对 <span class="math inline">\(g\in\widetilde{G}\)</span> 自然也成立。所以若<span class="math inline">\(g\in\widetilde{G}\)</span> 保持 <span class="math inline">\(e_i\)</span> 不变,则要么 <span class="math inline">\(v_0g = v_0, v_0r_i=v_0r_ig\)</span>,要么 <span class="math inline">\(v_0g = v_0r_i,v_0r_ig=v_0\)</span>。前一种情形说明<span class="math inline">\(g\)</span> 属于 <span class="math inline">\(v_0\)</span>的稳定化子群从而只能是单位元;后一种情形说明 <span class="math inline">\(r_ig\)</span> 和 <span class="math inline">\(r_ig^{-1}\)</span> 都属于 <span class="math inline">\(v_0\)</span> 的稳定化子群从而 <span class="math inline">\(r_ig=r_ig^{-1}=1\)</span>,即 <span class="math inline">\(g=r_i\)</span> 且 <span class="math inline">\(r_i^2=1\)</span>。总之我们证明了只有在 <span class="math inline">\(r_i^2=1\)</span> 时 <span class="math inline">\(e_i\)</span>才有非平凡的稳定化子群,这时稳定化子群是二阶循环群 <span class="math inline">\(\langle r_i\rangle\)</span>。</p><p>于是 snub cube 的类型为 <span class="math inline">\(r_0\)</span> 和<span class="math inline">\(r_1\)</span> 的边的个数都是 24/1=24个;类型为 <span class="math inline">\(r_2\)</span> 的边的个数为 24/2=12个,从而 snub cube 总共有 24+24+12=60 条边。</p><p>snub cube 的面可以这样求:由于 <span class="math inline">\(r_0^4=1\)</span> 所以 <span class="math inline">\(r_0\)</span> 可以生成一个正四边形的面,类似地由于<span class="math inline">\(r_1^3=1\)</span> 所以 <span class="math inline">\(r_1\)</span> 可以生成一个正三角形的面,而由于<span class="math inline">\(r_2^2=1\)</span> 所以 <span class="math inline">\(r_2\)</span>生成的面是退化的。这种由单个旋转生成的面的稳定化子群是很好求的:若 <span class="math inline">\(g\)</span> 保持 <span class="math inline">\(r_i\)</span> 生成的面不变,则其必然把某个形如<span class="math inline">\(v_0r_i^k\)</span> 的顶点变换为 <span class="math inline">\(v_0\)</span>,即 <span class="math inline">\(g=r_i^{-k}\)</span> 是 <span class="math inline">\(r_i\)</span> 的某次幂,反之易见 <span class="math inline">\(r_i\)</span>的任何幂都保持此面不变,所以其稳定化子群即为循环群 <span class="math inline">\(\langle r_i\rangle\)</span>。</p><p>于是 <span class="math inline">\(r_0\)</span> 生成的面的个数为24/4=6,<span class="math inline">\(r_1\)</span> 生成的面的个数为24/3=8,<span class="math inline">\(r_2\)</span>生成的面都退化因而个数是 0,总计 14 个面。</p><p>小心!我们还漏掉了一种三角面,它源自 <span class="math inline">\(r_0r_1=r_2\)</span> 这个关系。考虑 <span class="math inline">\(\{v_0, v_0r_1, v_0r_2\}\)</span>这三个顶点,这三个顶点中 <span class="math inline">\((v_0,v_0r_1)\)</span> 构成一条类型为 <span class="math inline">\(r_1\)</span> 的边, <span class="math inline">\((v_0,v_0r_2)\)</span> 构成一条类型为 <span class="math inline">\(r_2\)</span> 的边,而 <span class="math inline">\(r_0r_1=r_2\)</span> 这个关系告诉我们 <span class="math display">\[(v_0, v_0r_0)\xrightarrow{\ r_1\ }(v_0r_1,v_0r_0r_1) = (v_0r_1, v_0r_2).\]</span> 即 <span class="math inline">\((v_0r_1, v_0r_2)\)</span> 是一条类型为 <span class="math inline">\(r_0\)</span> 的边,它是由将 <span class="math inline">\(r_1\)</span> 作用在类型为 <span class="math inline">\(r_0\)</span> 的初始边 <span class="math inline">\((v_0, v_0r_0)\)</span> 上得到的,于是 <span class="math inline">\(\{v_0, v_0r_1, v_0r_2\}\)</span>构成一个三角形的三个顶点,其三条边在对称群作用下属于不同的轨道,所以这个三角形的稳定化子必然保持每条边不变,从而只能是恒等元,从而这样的面有24/1=24 个。</p><p>于是 snub cube 一共有 14+24=38 个不同的面。</p><p>这里介绍的方法也适用于其它的 snub 多面体以及 snub 24-cell。</p><h1 id="多面体的顶点投影到-coxeter-平面">多面体的顶点投影到 Coxeter平面</h1><p>项目中还实现了一个<code>draw_on_coxeter_plane(*args, **kwargs)</code>方法,用于绘制将多面体的顶点投影到其 Coxeter平面上后得到的图案,例如下图显示的是将 600-cell 的 120 个顶点投影到其Coxeter 平面上的结果:</p><p><img style="margin:0px auto;display:block" src="/images/polytopes/600-cell.svg" width="350"></p><p>你可以和 <a href="https://en.wikipedia.org/wiki/600-cell#2D_projections">wikipedia上的效果</a> 比较一下。</p><h1 id="附录手算-todd-coxeter">附录:手算 Todd-Coxeter</h1><p>对简单的群,Todd-Coxeter 算法完全可以用手算快速得出结果。我非常推荐Borcherds 的视频,他的演示非常精彩:</p><iframe width="560" height="315" src="https://www.youtube.com/embed/BHezLvEH1DU?si=fw7X2z37wZEYcIG-" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe><p>仿照 Borcherds 的方法,前面正方形的例子可以很快写出来:</p><p><img style="margin:0px auto;display:block" src="/images/polytopes/todd-coxeter.svg" width="600"></p><p>我来解释一下步骤:我们将画出一个有限图,图的每个顶点代表 <span class="math inline">\(H=\langle s_0,s_1\rangle\)</span>的一个陪集,每个顶点有三条不同颜色的边,表示此陪集在生成元 <span class="math inline">\(s_i\)</span> 作用下的结果。</p><ol type="1"><li>首先我们在空白纸上画出第一个顶点,它对应的陪集是 <span class="math inline">\(H=H_0\)</span> 自身。<span class="math inline">\(H\)</span> 包含 <span class="math inline">\(s_0,s_1\)</span>,所以红、绿边是自边。<span class="math inline">\(s_2\)</span>,即蓝色的边,会把它映射为一个新顶点<span class="math inline">\(H_1\)</span>。</li><li>从 <span class="math inline">\(H_0\)</span>出发,利用红蓝交换,可得红色保持 <span class="math inline">\(H_1\)</span> 不动。但是绿蓝不交换,所以绿色将<span class="math inline">\(H_1\)</span> 映射为新顶点 <span class="math inline">\(H_2\)</span>。</li><li><span class="math inline">\((\text{绿蓝})^3=1\)</span>,即 <span class="math inline">\(H_2\xrightarrow{(\text{绿蓝})^3}H_2\)</span>,所以<span class="math display">\[H_2\xrightarrow{\text{绿}}H_1\xrightarrow{\text{蓝}} H_0\xrightarrow{\text{绿}}H_0\xrightarrow{\text{蓝}} H_1\xrightarrow{\text{绿}}H_2\xrightarrow{\text{蓝}} H_2.\]</span> 所以蓝色保持 <span class="math inline">\(H_2\)</span> 不动。红绿不交换,所以红色将 <span class="math inline">\(H_2\)</span> 映射为新顶点 <span class="math inline">\(H_3\)</span>。</li><li>仿照上面的分析继续下去,可以发现到 <span class="math inline">\(H_5\)</span> 时,三种颜色的边不会给出新顶点。所以<span class="math inline">\(\{H_0,\ldots,H_5\}\)</span> 就是 <span class="math inline">\(G/H\)</span> 的全部陪集。</li></ol><aside id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes"><hr><ol><li id="fn1"><p>你可能要问了,你怎么就敢肯定这个群的表现恰好就包含这些生成关系,而不会包含其它什么隐藏的生成关系呢?这是个好问题,回答起来并不容易,答案是对凸的多胞体而言这些生成关系确实给出了其对称群的一个表现,但是对星状多面体而言则未必。事实上星状多面体都和某个凸多面体有相同的对称群,但是群表现是不一样的(需要补上额外的生成关系)。这其中的根本原因是凸多面体的镜面法向量之间的夹角都形如<span class="math inline">\(\pi-\pi/m\)</span>,这里 <span class="math inline">\(m\)</span>是整数,这保证了所有镜面围成的凸锥构成一个基本区域。而星状多面体的镜面所夹的二面角至少有一个形如<span class="math inline">\(\pi-\pi/p\)</span>,其中 <span class="math inline">\(p\)</span>是一个非整数的有理数,这时所有镜面围成的凸锥并不是基本区域,在对称群的作用下这个凸锥会被覆盖若干次。见Vinberg 的文章 “Discrete linear groups generated by reflections” 和Coxeter 的著作 “The beauty of geometry: twelve essays”.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li><li id="fn2"><p>以本文介绍的知识,这里似乎应该说 <span class="math inline">\(H\)</span> 保持 <span class="math inline">\(v_0\)</span> 不变,从而 <span class="math inline">\(v_0\)</span> 的稳定化子群包含 <span class="math inline">\(H\)</span>,怎么能断言 <span class="math inline">\(v_0\)</span> 的稳定化子群就等于 <span class="math inline">\(H\)</span> 呢?这实际上是 Coxeter 群的一个性质:在Coxeter 群 <span class="math inline">\(W\)</span>的标准几何实现中,对其基本区域闭包中的任何一点 <span class="math inline">\(v\)</span>,<span class="math inline">\(v\)</span>的稳定化子群是一个标准椭圆子群,其生成元恰好由超平面包含 <span class="math inline">\(v\)</span> 的那些单反射组成。<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li><li id="fn3"><p>道理与注解 2 类似。易见边 <span class="math inline">\(e\)</span> 的稳定化子群 <span class="math inline">\(H\)</span> 就是 <span class="math inline">\(e\)</span>的中点的稳定化子群,这也是一个标准椭圆子群,由那些包含 <span class="math inline">\(e\)</span>的中点的镜面对应的单反射生成。这样的镜面只能是 <span class="math inline">\(\rho_i\)</span> 和那些与 <span class="math inline">\(m_i\)</span> 垂直且包含 <span class="math inline">\(v_0\)</span> 的镜面。<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li><li id="fn4"><p>解释与注解 2, 3 类似。<a href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li><li id="fn5"><p>有这么一个结论:如果 <span class="math inline">\(s_\alpha\)</span> 是关于镜面 <span class="math inline">\(\alpha\)</span> 的反射,镜面 <span class="math inline">\(\beta=g\alpha\)</span>,这里 <span class="math inline">\(g\)</span> 是空间中的可逆线性变换,则关于 <span class="math inline">\(\beta\)</span> 的反射 <span class="math inline">\(s_\beta=gs_\alpha g^{-1}\)</span>。令 <span class="math inline">\(\alpha=AB\)</span>,<span class="math inline">\(g=\rho_1\)</span>,<span class="math inline">\(\beta=AD\)</span> 即为结论。<a href="#fnref5" class="footnote-back" role="doc-backlink">↩︎</a></p></li><li id="fn6"><p>注意本文没有解释为什么这些边确实是不同类型的,即它们在对称群的作用下处于不同的轨道。严格说明这一点也要用到Coxeter 群的几何实现。<a href="#fnref6" class="footnote-back" role="doc-backlink">↩︎</a></p></li></ol></aside>]]></content>
<summary type="html">
<p>本文介绍我写的一个高颜值的、脱离了低级趣味的小程序:用 Python 和
POV-Ray 绘制各种三维多面体和四维多胞体,代码在 <a href="https://github.com/neozhaoliang/pywonderland/tree/master/src/polytopes">Github</a>
上。</p>
<p>以下是用这个程序渲染的一些例子,其中不同颜色的顶点/边/面表示它们在对称群的作用下位于不同的轨道中,具体解释见后。</p></summary>
<category term="pywonderland 项目" scheme="https://pywonderland.com/categories/pywonderland-%E9%A1%B9%E7%9B%AE/"/>
</entry>
<entry>
<title>Möbius 变换的分类与上半双曲空间的等距</title>
<link href="https://pywonderland.com/mobius-h3space/"/>
<id>https://pywonderland.com/mobius-h3space/</id>
<published>2018-05-08T16:00:00.000Z</published>
<updated>2024-11-26T14:47:28.067Z</updated>
<content type="html"><![CDATA[<p>本文的想法源自 Roice Nelson 的 <a href="https://www.shadertoy.com/view/MstcWr">shadertoy项目</a>,我觉得他的创意很棒,就是效果有点糙,于是 <a href="https://www.shadertoy.com/view/4scfR2">动手改进了一番</a>。乍一看,这个动画的场景很简单,其实它背后的数学并不平凡。</p><p>这个动画从三个角度了演示 Möbius 变换,这三个角度是密切相关的:</p><ol type="1"><li>Möbius 变换作为扩充复平面 <span class="math inline">\(\hat{\mathbb{C}}\)</span> 到自身的全纯函数。</li><li>Möbius 变换作为 Riemann 球面 <span class="math inline">\(S^2\)</span> 到自身的全纯函数。</li><li>Möbius 变换作为上半双曲空间中的等距变换。</li></ol><p>本文只作概括性的介绍,并不展开详细的数学证明。读者可以参考下面的资料:</p><blockquote><ol type="1"><li><a href="https://en.wikipedia.org/wiki/M%C3%B6bius_transformation">维基百科</a>.</li><li><span class="citation" data-cites="Needham1997">Needham (<a href="#ref-Needham1997" role="doc-biblioref">1997</a>)</span> .</li><li><span class="citation" data-cites="indra">Mumford, Series, andWright (<a href="#ref-indra" role="doc-biblioref">2002</a>)</span>,chapter 3.</li><li><span class="citation" data-cites="palka1991">Palka (<a href="#ref-palka1991" role="doc-biblioref">1991</a>)</span>, chapter IX,section 2.</li></ol></blockquote><p>本文的动画应该可以帮助你更好地理解这些资料中的内容。</p><span id="more"></span><h1 id="预备知识之正交圆族">预备知识之正交圆族</h1><p>设 <span class="math inline">\(z_1,z_2\)</span>是复平面上的两点,我们考虑两个不同的圆族 <span class="math inline">\(\mathcal{C}_1\)</span> 和 <span class="math inline">\(\mathcal{C}_2\)</span>:</p><ol type="1"><li><span class="math inline">\(\mathcal{C}_1\)</span> 由所有同时过<span class="math inline">\(z_1\)</span> 和 <span class="math inline">\(z_2\)</span> 的圆组成(包含过 <span class="math inline">\(z_1,z_2\)</span> 的直线)。</li><li><span class="math inline">\(\mathcal{C}_2\)</span> 由所有使得 <span class="math inline">\(z_1\)</span> 和 <span class="math inline">\(z_2\)</span> 关于 <span class="math inline">\(C\)</span> 互为反演点的圆 <span class="math inline">\(C\)</span> 组成(包含线段 <span class="math inline">\([z_1,z_2]\)</span> 的垂直平分线)。</li></ol><p>则圆族 <span class="math inline">\(\mathcal{C}_1\)</span> 中的任何圆<span class="math inline">\(C_1\)</span> 与 <span class="math inline">\(\mathcal{C}_2\)</span> 中的任何圆 <span class="math inline">\(C_2\)</span> 正交(交点处的切线互相垂直)。</p><p>我们将考察当 <span class="math inline">\(z_1,z_2\)</span> 是一个Möbius 变换 <span class="math inline">\(M\)</span> 的两个不动点时,<span class="math inline">\(M\)</span> 作用在 <span class="math inline">\(\mathcal{C}_1\)</span> 和 <span class="math inline">\(\mathcal{C}_2\)</span> 上的效果。</p><h1 id="möbius-变换的共轭分类">Möbius 变换的共轭分类</h1><p>一个 Möbius 变换 <span class="math inline">\(M\)</span>是一个分式线性变换,它将扩充复平面 <span class="math inline">\(\hat{\mathbb{C}}\)</span> 一对一地映射为自身:<span class="math display">\[M(z) = \frac{az+b}{cz+d},\quada,b,c,d\in\mathbb{C},ad-bc\ne0, z\in \hat{\mathbb{C}}.\]</span> 所有的Möbius 变换构成一个群 <span class="math inline">\({\rmPSL}_2(\mathbb{C})\)</span>。</p><p>我们称两个 Möbius 变换 <span class="math inline">\(M_1,M_2\)</span>是共轭的,当且仅当存在 <span class="math inline">\(g\in{\rmPSL}_2(\mathbb{C})\)</span> 使得 <span class="math display">\[M_1=gM_2g^{-1}.\]</span></p><p>可以证明,在共轭的意义下,任何非恒等元的 Möbius变换都属于下面四种类型之一:</p><ol type="1"><li><span class="math inline">\(M\)</span> 称作是抛物型的(parabolic),如果它共轭于平移:<span class="math inline">\(z\toz+1\)</span>。这时 <span class="math inline">\(M\)</span> 在 <span class="math inline">\(\hat{\mathbb{C}}\)</span> 上仅有一个不动点。</li><li><span class="math inline">\(M\)</span> 称作是椭圆型的(elliptic),如果它共轭于旋转:<span class="math inline">\(z\toe^{i\theta}z\)</span>,其中 <span class="math inline">\(\theta\in\mathbb{R}\)</span> 且 <span class="math inline">\(\theta\ne0\)</span>。这时 <span class="math inline">\(M\)</span> 在 <span class="math inline">\(\hat{\mathbb{C}}\)</span> 上有两个不动点。</li><li><span class="math inline">\(M\)</span> 称作是双曲型的(hyperbolic),如果它共轭于缩放:<span class="math inline">\(z\to\lambdaz\)</span>,其中 <span class="math inline">\(\lambda>0\)</span>是实数且不为 1。这时 <span class="math inline">\(M\)</span> 在 <span class="math inline">\(\hat{\mathbb{C}}\)</span> 上有两个不动点。</li><li><span class="math inline">\(M\)</span> 称作是斜航型的(loxodromic),如果它共轭于一个缩放和一个旋转的复合:<span class="math inline">\(z\to cz\)</span>,其中 <span class="math inline">\(c\in\mathbb{C}\setminus \mathbb{R}\)</span>。这时<span class="math inline">\(M\)</span> 在 <span class="math inline">\(\hat{\mathbb{C}}\)</span> 上有两个不动点。</li></ol><p>我们把形如 <span class="math inline">\(z\to z+a\)</span> 和 <span class="math inline">\(z\to\lambda z\)</span>的变换称作<strong>特殊位置</strong>的变换,前者是抛物型的,以 <span class="math inline">\(\infty\)</span>为唯一不动点,后者包含了所有非抛物型的变换,它的两个不动点是 <span class="math inline">\(0\)</span> 和 <span class="math inline">\(\infty\)</span>。这两种 Möbius变换具有简单的表现形式,并且它们在两个圆族上的作用也很容易分析,所以在研究每种类型的变换时,我们都先考虑这种简单的形式,然后再通过取共轭扩展到一般的情形。</p><h2 id="抛物型">1. 抛物型</h2><p>我们先考察最简单的抛物型变换 <span class="math inline">\(z\toz+1\)</span>。</p><p>这时唯一的不动点是 <span class="math inline">\(\infty\)</span>,<span class="math inline">\(\mathcal{C}_1\)</span> 是直线族 <span class="math inline">\(\{y=k\mid k\in\mathbb{R}\}\)</span>,<span class="math inline">\(\mathcal{C}_2\)</span> 是直线族 <span class="math inline">\(\{x=l\mid l\in\mathbb{R}\}\)</span>。<span class="math inline">\(M\)</span> 保持 <span class="math inline">\(\mathcal{C}_1\)</span> 中的每条直线不变,把 <span class="math inline">\(\mathcal{C}_2\)</span>中的每条直线变成同族中的另一条直线:</p><object data="/images/mobius/parabolic-plane.svg"></object><p>由动画可见所有点都向着不动点 <span class="math inline">\(\infty\)</span> 的方向「前进」。</p><p>对一般的抛物型变换 <span class="math inline">\(M\)</span> 且 <span class="math inline">\(M\)</span> 的唯一不动点 <span class="math inline">\(z_0\)</span> 有限的情形,结论仍然类似:圆族 <span class="math inline">\(\mathcal{C}_1\)</span> 中的圆都在 <span class="math inline">\(z_0\)</span> 处相切且共用同一条切线 <span class="math inline">\(\ell_1\)</span>,圆族 <span class="math inline">\(\mathcal{C}_2\)</span> 也都在 <span class="math inline">\(z_0\)</span> 处相切且共用同一条切线 <span class="math inline">\(\ell_2\)</span>,<span class="math inline">\(\ell_1\)</span> 和 <span class="math inline">\(\ell_2\)</span> 互相垂直:</p><object data="/images/mobius/parabolic-plane2.svg"></object><p>由动画可见左右两侧的圆在旋转,但是圆本身保持不变,它们是圆族 <span class="math inline">\(\mathcal{C}_1\)</span>;上下两侧的圆在「扩散」,它们是圆族<span class="math inline">\(\mathcal{C}_2\)</span>。可以证明对任何 <span class="math inline">\(z\in\mathbb{C}\)</span> 都有 <span class="math inline">\(\lim\limits_{n\to\infty}M^n(z)=z_0\)</span>,即任何点在<span class="math inline">\(M\)</span> 反复作用下的轨迹都朝着 <span class="math inline">\(z_0\)</span> 的位置移动。</p><p>在 <a href="http://klein.math.okstate.edu/IndrasPearls/">Indra’spearls</a> 一书中,把 <span class="math inline">\(\mathcal{C}_1\)</span>和 <span class="math inline">\(\mathcal{C_2}\)</span>形象地比喻为两组「扇贝壳」:</p><p><img style="margin:0px auto;display:block" width="200" src="/images/mobius/seashell.jpeg"></p><h2 id="椭圆型">2. 椭圆型</h2><p>我们先考察最简单的椭圆型变换 <span class="math inline">\(z\toe^{i\theta}z\)</span>。</p><p>这时两个不动点是 <span class="math inline">\(0\)</span> 和 <span class="math inline">\(\infty\)</span>。圆族 <span class="math inline">\(\mathcal{C}_1\)</span> 由所有过 <span class="math inline">\(0\)</span> 的直线组成,<span class="math inline">\(\mathcal{C}_2\)</span> 由所有以 <span class="math inline">\(0\)</span> 为中心的同心圆组成。<span class="math inline">\(M\)</span> 是个旋转,所以会把 <span class="math inline">\(\mathcal{C}_1\)</span>中的直线变成同族的另一条直线,同时保持 <span class="math inline">\(\mathcal{C}_2\)</span> 中的每个圆不变:</p><object data="/images/mobius/elliptic-plane.svg"></object><p>对两个不动点 <span class="math inline">\(z_1\)</span> 和 <span class="math inline">\(z_2\)</span> 都有限的情形,结论同样成立,<span class="math inline">\(M\)</span> 保持 <span class="math inline">\(\mathcal{C}_2\)</span> 中的每个圆不变,把 <span class="math inline">\(\mathcal{C}_1\)</span>中的每个圆变为同族中的另一个圆:</p><object data="/images/mobius/elliptic-plane2.svg"></object><h2 id="双曲型">3. 双曲型</h2><p>与椭圆变换 <span class="math inline">\(z\to e^{i\theta}z\)</span>的情形类似,<span class="math inline">\(z\to\lambda z\)</span> 也以<span class="math inline">\(0\)</span> 和 <span class="math inline">\(\infty\)</span> 为不动点,但是圆族 <span class="math inline">\(\mathcal{C}_1\)</span> 和 <span class="math inline">\(\mathcal{C}_2\)</span> 的运动方式发生了互换:这次<span class="math inline">\(\mathcal{C}_1\)</span>中的每条直线在放缩下保持不变,<span class="math inline">\(\mathcal{C}_2\)</span>中的每个圆在放缩下变成同族的另一个圆:</p><object data="/images/mobius/hyperbolic-plane.svg"></object><p>对两个不动点 <span class="math inline">\(z_1\)</span> 和 <span class="math inline">\(z_2\)</span> 都有限的双曲变换,结论仍然成立,圆族<span class="math inline">\(\mathcal{C}_1\)</span>中的每个圆保持不变,<span class="math inline">\(\mathcal{C}_2\)</span>中的每个圆被变成同族的另一个:</p><object data="/images/mobius/hyperbolic-plane2.svg"></object><p>这时在 <span class="math inline">\(M\)</span> 的作用下 <span class="math inline">\(z_1\)</span> 和 <span class="math inline">\(z_2\)</span>一个是「源点」,另一个是「汇点」,轨迹从源点源源不竭地发出,汇聚到汇点中。</p><h2 id="斜航型">4. 斜航型</h2><p>仍然先看 <span class="math inline">\(z\to cz\)</span> 的情形。</p><p>这时不动点是 <span class="math inline">\(0\)</span> 和 <span class="math inline">\(\infty\)</span>,<span class="math inline">\(M\)</span> 把圆族 <span class="math inline">\(\mathcal{C}_1\)</span>中的每个圆变为同族中的另一个圆,同样地也把圆族 <span class="math inline">\(\mathcal{C}_2\)</span>中的每个圆变为同族中的另一个圆,<strong>所以斜航型的变换没有不变圆</strong>:</p><object data="/images/mobius/loxodromic-plane.svg"></object><p>这时平面上一点 <span class="math inline">\(z\)</span> 在 <span class="math inline">\(M\)</span>的反复作用下的轨迹是一条形如「旋臂」的曲线,方程为 <span class="math inline">\(\gamma(t)=c^tz\)</span>,这是一条对数螺线(spiral),其与 <span class="math inline">\(\mathcal{C}_1\)</span> 和<span class="math inline">\(\mathcal{C}_2\)</span> 的夹角都是常数。</p><p>这个结论对一般的斜航型变换也成立:</p><object data="/images/mobius/loxodromic-plane2.svg"></object><p>由于斜航型变换包含双曲变换作为组成成分,因此看起来它也有一个源点和一个汇点。这时任意一点<span class="math inline">\(z\)</span> 在 <span class="math inline">\(M\)</span> 的反复作用下的轨迹是一条双螺线 (doublespiral),其与 <span class="math inline">\(\mathcal{C}_1\)</span> 和<span class="math inline">\(\mathcal{C}_2\)</span> 的夹角仍然都是常数(Möbius 变换是保角的)。</p><p>判断 Möbius 变换 <span class="math inline">\(M\)</span>具体属于哪一类可以根据其迹的平方 <span class="math inline">\(\mu=(a+d)^2\)</span> 来判断(当然,需要把 <span class="math inline">\(M\)</span> 归一化使得 <span class="math inline">\(ad-bc=1\)</span>):</p><ol type="1"><li><span class="math inline">\(M\)</span> 是抛物型的当且仅当 <span class="math inline">\(\mu=4\)</span>。</li><li><span class="math inline">\(M\)</span> 是椭圆型的当且仅当 <span class="math inline">\(0\leq\mu<4\)</span>。</li><li><span class="math inline">\(M\)</span> 是双曲型的当且仅当 <span class="math inline">\(\mu>4\)</span>。</li><li><span class="math inline">\(M\)</span> 是斜航型的当且仅当 <span class="math inline">\(\mu<0\)</span> 或者 <span class="math inline">\(\mu\notin\mathbb{R}\)</span>。</li></ol><h2 id="斜航-loxodromic-是什么意思">斜航 (loxodromic) 是什么意思?</h2><p>斜航这个词听起来好像和船的航行有关,怎么就用来给 Möbius变换分类了呢?这里面肯定有故事,值得扒一扒。</p><p>斜航线 (loxodrome)指的是地球上的一条航行路径,其在每个点处的切线与过该点的经线的夹角为定值。比如说,如果船始终朝着东北方向30 度行驶,走过的轨迹就是一条斜航线。Loxodrome 最初是一个希腊词,loxos的意思是 oblique,即倾斜的,dromos 意为bearing,方位的意思,后来拉丁化以后成为现在的样子。葡萄牙数学家 PedroNunes (1492-1577)第一个认识到斜航线并非两点之间最短路径,而且它无限接近但永不可能到达极点。</p><p><img style="margin:0px auto;display:block" width="300" src="/images/mobius/loxodrome.png"></p><p>在大航海的时代,没有卫星导航,只能靠罗盘或者星座来标识船的航向,而星座的方法在遇到恶劣天气的时候又不能使用,只有罗盘是最可靠的方法。理论上地球表面两点之间的最短路径是过球心的大圆,但罗盘只能定出经线的方向(原理是地球的磁极和南北极近似重合),这二者的夹角不是固定的,要保持沿着大圆的弧走就必须不停调整船的航向,但现实中的船不可能一直有人守在船舵处调整方向,一般是事先定好航向以后接下来的若干天都沿着这个方向走,所以在一定路程内船实际上走的是斜航线。</p><p>荷兰地图学家墨卡托 (Mercator) 据此于 1569年提出了墨卡托地图,将地球投影至墨卡托地图是一个保角变换,即曲线的夹角保持不变。不仅如此,球面上的斜航线在墨卡托地图中成为一条直线:</p><p><img style="margin:0px auto;display:block" width="500" src="/images/mobius/mercator.jpg"></p><p>所以要从地球上的 <span class="math inline">\(A\)</span> 点航向到<span class="math inline">\(B\)</span>点,只要找到它们在墨卡托地图上的对应点 <span class="math inline">\(A',B'\)</span>,算出地图上的直线 <span class="math inline">\(A'B'\)</span> 与经线的夹角 <span class="math inline">\(\theta\)</span>,航行时只要让罗盘与经线一直保持角度为<span class="math inline">\(\theta\)</span> 就可以按照斜航线从 <span class="math inline">\(A\)</span> 航行到 <span class="math inline">\(B\)</span>了。这个路径虽不是最短,但是好在不容易迷失航向。</p><p>那这和 Möbius 变换有什么关系呢?</p><h1 id="möbius-变换作用在-riemann-球面上">Möbius 变换作用在 Riemann球面上</h1><p>由于 Möbius 变换都是扩充复平面 <span class="math inline">\(\hat{\mathbb{C}}\)</span> 到 <span class="math inline">\(\hat{\mathbb{C}}\)</span> 的自同构,而 <span class="math inline">\(\hat{\mathbb{C}}\)</span> 在球极投影下等同于Riemann 球面 <span class="math inline">\(S^2\)</span>,所以 Möbius变换也都是 Riemann 球面的自同构。我们来看看 Möbius 变换作用在 Riemann球面上是什么样子的。</p><p>这是一个作用在 Riemann 球上的斜航型变换:</p><object data="/images/mobius/loxodromic-sphere.svg"></object><p>我来解释一下这个动画的含义:从动画可见 Riemann球面上有一对源点和汇点,这对源点和汇点可以理解为球面的「北极」和「南极」,它们在球极投影下对应于<span class="math inline">\(M\)</span>在扩充复平面上的两个不动点。当这两个不动点分别是原点和无穷远点时,这两个极点就是通常意义下的北极和南极。这时球面上的「经线」是所有过两个极点的大圆,在球极投影下它们对应于同时过两个不动点的圆族<span class="math inline">\(\mathcal{C}_1\)</span>;球面上的「纬线」是所有与经线正交的圆,在球极投影下它们对应于反演圆族<span class="math inline">\(\mathcal{C}_2\)</span>,球面上每个点的轨迹是对数螺线轨迹在逆球极投影下在球面上的对应曲线,这条曲线与经线纬线的夹角都是常数(因为球极投影是保角的),从而是一条斜航线!</p><div class="unnumbered statement exercise-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">练习</span>:</span><span class="statement-spah"> </span>解释下面这个动画的含义:</p><object data="/images/mobius/parabolic-sphere.svg"></object></div><p>提示:这个动画有两种解释,它既可以看作是一个抛物型变换在 Riemann球面上作用的效果,也可以看作是一个上半双曲空间中的 horosphere在抛物型变换下作用的效果。horosphere 是指上半双曲空间中与无穷远平面<span class="math inline">\(z=0\)</span>相切的球,其半径为无穷大。Möbius变换在上半双曲空间上的作用见下一节。</p><h1 id="möbius-变换作为上半双曲空间的等距">Möbius变换作为上半双曲空间的等距</h1><p>上半双曲空间 <span class="math inline">\(\mathbb{H}_3\)</span>的定义为 <span class="math display">\[\mathbb{H}_3 =\{(x,y,t)\in\mathbb{R}^3\ |\ t>0\}.\]</span>这个空间中的度量是双曲度量:</p><p><span class="math display">\[\mathrm{d}s=\frac{(\mathrm{d}x)^2+(\mathrm{d}y)^2+(\mathrm{d}t)^2}{t}.\]</span></p><p>注意 <span class="math inline">\(xy\)</span> 平面,即复平面不属于<span class="math inline">\(\mathbb{H}_3\)</span>,它是 <span class="math inline">\(\mathbb{H}_3\)</span> 的无穷远边界,<span class="math inline">\(\mathbb{H}_3\)</span> 中任何一点到 <span class="math inline">\(xy\)</span> 平面的距离是无穷大。</p><p>一个复分析教材中不太常讲到的事实是:<strong>任何 Möbius变换都可以唯一地扩展为 <span class="math inline">\(\mathbb{H}_3\)</span>的一个等距变换 <span class="math inline">\(\overline{M}\)</span></strong>,此即所谓的Poincaré Extension。这个扩展用四元数来描述的话很简单:设 <span class="math display">\[M\colon\ z\to\frac{az+b}{cz+d},\quadad-bc=1.\]</span> 是任一 Möbius 变换。注意我们这里要求 <span class="math inline">\(ad-bc\)</span> 的值是1,其实任何非零实数都可以。这是可以做到的,因为给 Möbius 变换中的 <span class="math inline">\(a,b,c,d\)</span>同时乘以一个非零复数并不改变这个变换,所以同时乘以 <span class="math inline">\(1/\sqrt{ad-bc}\)</span> 就可以使得 <span class="math inline">\(ad-bc=1\)</span>。</p><p>对 <span class="math inline">\(p=(x,y,t)\in\mathbb{H}^3\)</span>,令<span class="math inline">\(q=x+yi+tj\)</span> 为与 <span class="math inline">\(p\)</span> 对应的四元数,定义 <span class="math display">\[\overline{M}(p) = (aq+b)(cq+d)^{-1}.\]</span>这里的运算都是在四元数体中进行。则 <span class="math inline">\(\overline{M}(p)\)</span> 是 <span class="math inline">\(\mathbb{H}_3\)</span> 到自身的等距,并且它限制在<span class="math inline">\(xy\)</span> 平面上与 <span class="math inline">\(M\)</span> 的作用一致。</p><p>关于 Poincaré Extension 读者可以参考 <span class="citation" data-cites="Bea95">(<a href="#ref-Bea95" role="doc-biblioref">Beardon1995, vol. 91, secs. 3.3, 4.1</a>)</span>。</p><p>我们还能像上面那样用动画演示 Möbius 变换 <span class="math inline">\(\overline{M}\)</span> 在 <span class="math inline">\(\mathbb{H}_3\)</span>上的作用吗?可以!比如下图是一个形如 <span class="math inline">\(z\tocz\)</span> 的斜航型变换扩展到 <span class="math inline">\(\mathbb{H}_3\)</span>后作用在一个<strong>圆柱</strong>体上的效果:</p><object data="/images/mobius/loxodromic-cone.svg"></object><p>你可能要问了:这明明是个圆锥体,你怎么说它是圆柱体呢?是不是笔误了啊?</p><p>其实是因为我们现在是在双曲空间里看待它,在双曲度量下,锥面上所有点到<span class="math inline">\(t\)</span>轴的距离都是一样的,实际上如果设锥的顶角为 <span class="math inline">\(2\alpha\)</span>,则锥面上任何一点到 <span class="math inline">\(t\)</span> 轴的双曲距离 <span class="math inline">\(d\)</span> 满足(见 <span class="citation" data-cites="Bea95">(<a href="#ref-Bea95" role="doc-biblioref">Beardon1995, vol. 91, sec. 7.9.1</a>)</span>) <span class="math display">\[\sinh d\cdot \cot\alpha = 1.\]</span> 即 <span class="math inline">\(d\)</span> 是定值。所以虽然在 <span class="math inline">\(\mathbb{R}^3\)</span> 中它是一个锥体,但是在 <span class="math inline">\(\mathbb{H}_3\)</span> 中它其实是圆柱体。</p><p>你可以看到这时 <span class="math inline">\(\overline{M}\)</span>有两个不动点,都位于无穷远边界上,这样的点叫做「理想点」。两个不动点之间的连线构成圆柱的轴。两个不动点一个是源点,一个是汇点,空间中的点在变换的作用下远离源点,趋向汇点。</p><p>对于一般的斜航型变换 <span class="math inline">\(M\)</span>,且其两个不动点都是扩充复平面上的有限点时,<span class="math inline">\(M\)</span> 在 <span class="math inline">\(\mathbb{H}_3\)</span> 上的扩展 <span class="math inline">\(\overline{M}\)</span> 仍然保持一个 <span class="math inline">\(\mathbb{H}_3\)</span> 中的圆柱体不变:</p><object data="/images/mobius/loxodromic-dupin.svg"></object><p>这个曲面叫做 <a href="https://www.maths.ox.ac.uk/about-us/departmental-art/dupin-cyclides">Dupincyclide</a>,它的两个端点恰好是 <span class="math inline">\(M\)</span>的两个不动点。但在双曲空间中它其实是一个圆柱体,由于其两端落在无穷远平面上,因此也是无限长的。圆柱体的轴是连接两个端点的测地线。如果<span class="math inline">\(M\)</span> 是椭圆型的话,那么 <span class="math inline">\(\overline{M}\)</span> 将该圆柱绕着轴旋转:</p><object data="/images/mobius/elliptic-dupin.svg"></object><h1 id="这些动画是怎么生成的">这些动画是怎么生成的?</h1><p>我以 Dupin cyclide 的动画为例子来说明动画的绘制过程。</p><p>一个非抛物型的变换 <span class="math inline">\(M\)</span>总是可以表示为 <span class="math inline">\(M = gM_\lambdag^{-1}\)</span> 的形式,其中 <span class="math inline">\(M_\lambda=z\to\lambda z\)</span>。我这里的 <span class="math inline">\(g\)</span> 取的是 <span class="math display">\[g^{-1}(z)=\frac{z-1}{z+1}.\]</span>注意我这里写的是 <span class="math inline">\(g^{-1}\)</span>,原因是我们不需要 <span class="math inline">\(g\)</span> 的显式表达式,相反我们只需要 <span class="math inline">\(g^{-1}\)</span>。</p><p>不难验证 <span class="math inline">\(g^{-1}(1)=0\)</span> 和 <span class="math inline">\(g^{-1}(-1)=\infty\)</span>,从而 <span class="math inline">\(g(0)=1\)</span> 和 <span class="math inline">\(g(\infty)=-1\)</span>,即 <span class="math inline">\(g\)</span> 将 <span class="math inline">\(M_\lambda\)</span> 的不动点 <span class="math inline">\(\{0,\infty\}\)</span> 分别映射为 <span class="math inline">\(M\)</span> 的不动点 <span class="math inline">\(\{1,-1\}\)</span>。<span class="math inline">\(g\)</span> 同时将 <span class="math inline">\(M_\lambda\)</span> 对应的圆族 <span class="math inline">\(\{\mathcal{C}_i,i=1,2\}\)</span> 映射为 <span class="math inline">\(M\)</span> 的圆族 <span class="math inline">\(\{g(\mathcal{C}_i),i=1,2\}\)</span>。</p><p><span class="math inline">\(g\)</span> 当然也可以扩展为 <span class="math inline">\(\mathbb{H}_3\)</span> 的等距,我们把扩展以后的<span class="math inline">\(g\)</span> 仍然记作 <span class="math inline">\(g\)</span>。</p><p>由 <span class="math inline">\(M = gM_\lambda g^{-1}\)</span> 可得<span class="math display">\[Mg(\mathcal{C}_i) = gM_\lambda(\mathcal{C}_i),\quad i=1,2.\]</span> 左边的 <span class="math inline">\(Mg(\mathcal{C}_i)\)</span> 是我们真正想绘制的<span class="math inline">\(M\)</span> 在其自己的圆族 <span class="math inline">\(g(\mathcal{C}_i)\)</span>上的作用,这等价于绘制右边的 <span class="math inline">\(gM_\lambda(\mathcal{C})_i\)</span>。<span class="math inline">\(M_\lambda(\mathcal{C}_i)\)</span>很好画,就是把一些同心圆和过原点的直线旋转或者放缩一下;但是它前面加了一个畸变<span class="math inline">\(g\)</span>。为此我们只要用 <span class="math inline">\(g^{-1}\)</span>作用在当前场景的物体上,把它们「去畸变」即可。所以在动画中,我其实根本没有计算Dupin cyclide 的任何显式或者隐式的曲面方程,而是直接用 <span class="math inline">\(g^{-1}\)</span> 作用在场景上。由于 <span class="math inline">\(g^{-1}\)</span> 是 <span class="math inline">\(\mathbb{H}_3\)</span> 上的等距,它一定会把连接<span class="math inline">\(\{\pm1\}\)</span> 的测地线映射为连接 <span class="math inline">\(\{0,\infty\}\)</span>的测地线,即锥面。换言之,判断场景中的一个点 <span class="math inline">\(p\)</span> 是不是落在 Dupin cyclide 上,只要判断<span class="math inline">\(g^{-1}(p)\)</span>是不是落在锥面上。这就好办了。</p><p>抛物的情形更简单一些,可以用 <span class="math inline">\(g(z)=1/z\)</span>把位于无穷远的不动点变到原点。</p><p>动画使用的是 GLSL 语言和 raymarching 的技术。我在 Roice的代码基础上作了许多优化,但肯定还可以更精炼。限于我写 shader的能力不足,做出更美轮美奂的效果就不指望了 …</p><p>严格讲,这些动画其实还是尝试在 Euclidean空间中去观察双曲空间中的对象,因为 raymarching技术假定的是光走直线,但在双曲空间中光一般不走直线,所以我们这里看到的效果与真实的生活在双曲空间中的“外星人”所看到的还是有差别的。</p><h1 class="unnumbered" id="bibliography">References</h1><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0" role="list"><div id="ref-Bea95" class="csl-entry" role="listitem">Beardon, Alan F. 1995. <em>The Geometry of Discrete Groups</em>. Vol.91. Graduate Texts in Mathematics. Springer-Verlag, New York.</div><div id="ref-indra" class="csl-entry" role="listitem">Mumford, David, Caroline Series, and David J. Wright. 2002. <em>Indra’sPearls: An Atlas of Kleinian Groups</em>. Cambridge University Press.</div><div id="ref-Needham1997" class="csl-entry" role="listitem">Needham, Tristan. 1997. <em>Visual Complex Analysis</em>. The ClarendonPress, Oxford University Press, New York.</div><div id="ref-palka1991" class="csl-entry" role="listitem">Palka, B. P. 1991. <em>An Introduction to Complex Function Theory</em>.An Introduction to Complex Function Theory. World PublishingCorporation.</div></div>]]></content>
<summary type="html">
<p>本文的想法源自 Roice Nelson 的 <a href="https://www.shadertoy.com/view/MstcWr">shadertoy
项目</a>,我觉得他的创意很棒,就是效果有点糙,于是 <a href="https://www.shadertoy.com/view/4scfR2">动手改进了一番</a>。乍一看,这个动画的场景很简单,其实它背后的数学并不平凡。</p>
<p>这个动画从三个角度了演示 Möbius 变换,这三个角度是密切相关的:</p>
<ol type="1">
<li>Möbius 变换作为扩充复平面 <span class="math inline">\(\hat{\mathbb{C}}\)</span> 到自身的全纯函数。</li>
<li>Möbius 变换作为 Riemann 球面 <span class="math inline">\(S^2\)</span> 到自身的全纯函数。</li>
<li>Möbius 变换作为上半双曲空间中的等距变换。</li>
</ol>
<p>本文只作概括性的介绍,并不展开详细的数学证明。读者可以参考下面的资料:</p>
<blockquote>
<ol type="1">
<li><a href="https://en.wikipedia.org/wiki/M%C3%B6bius_transformation">维基百科</a>.</li>
<li><span class="citation" data-cites="Needham1997">Needham (<a href="#ref-Needham1997" role="doc-biblioref">1997</a>)</span> .</li>
<li><span class="citation" data-cites="indra">Mumford, Series, and
Wright (<a href="#ref-indra" role="doc-biblioref">2002</a>)</span>,
chapter 3.</li>
<li><span class="citation" data-cites="palka1991">Palka (<a href="#ref-palka1991" role="doc-biblioref">1991</a>)</span>, chapter IX,
section 2.</li>
</ol>
</blockquote>
<p>本文的动画应该可以帮助你更好地理解这些资料中的内容。</p></summary>
<category term="可视化复分析" scheme="https://pywonderland.com/categories/%E5%8F%AF%E8%A7%86%E5%8C%96%E5%A4%8D%E5%88%86%E6%9E%90/"/>
</entry>
<entry>
<title>碉堡的小程序:用 Python 制作演示各种算法的 GIF 动画</title>
<link href="https://pywonderland.com/gifmaze-cn/"/>
<id>https://pywonderland.com/gifmaze-cn/</id>
<published>2017-10-01T16:00:00.000Z</published>
<updated>2024-08-10T05:04:15.705Z</updated>
<content type="html"><![CDATA[<p>本文要介绍的是我写的一个有趣的 Python小程序,一个脱离了低级趣味的程序,一个有益于广大人民了解算法的程序。代码在<a href="https://github.com/neozhaoliang/pywonderland/tree/master/src/gifmaze">Github</a>上。</p><p>这个程序可以用来制作各种各样的算法动画,包含但不限于:</p><span id="more"></span><ul><li><p>Wilson 均匀生成树算法:</p><p><img style="margin:0px auto;display:block" src="/images/gifmaze/wilson-bfs.gif"></p></li><li><p>Prim 算法:</p><p><img style="margin:0px auto;display:block" src="/images/gifmaze/prim.gif"></p></li><li><p>Kruskal 算法:</p><p><img style="margin:0px auto;display:block" src="/images/gifmaze/kruskal.gif"></p></li><li><p>Langton 蚂蚁:</p><p><img style="margin:0px auto;display:block" src="/images/gifmaze/langton-ant.gif"></p></li><li><p>Hilbert 曲线:</p><p><img style="margin:0px auto;display:block" src="/images/gifmaze/hilbert.gif"></p></li><li><p>Conway 的生命游戏 (gosper glider gun):</p><p><img style="margin:0px auto;display:block" src="/images/gifmaze/gosperglidergun.gif"></p></li></ul><p>以上这些动画有一个共同特点:它们都位于二维的网格图上,这也是这个程序的一个限制。</p><p>这个程序有如下特点:</p><ol type="1"><li><p>所有代码全部由纯 Python写成,没有用到任何第三方库或者外部软件,也不包含任何 <code>draw</code>,<code>fill</code> 之类的函数调用,仅使用了内置的 <code>struct</code>,<code>random</code> 模块和一些内置函数。后来的版本中为了显示进度条引入了<code>tqdm</code>;为了把整个动画嵌入一张背景图片引入了<code>pillow</code>,这些都属于特效,本质不需要。</p></li><li><p>实现了一个小型但高效的 GIF编码器,<strong>通过直接将动画过程编码为字节流</strong>,可以在数秒之内生成高度优化的动态图。比如前面那张Langton ant 的动图,它包含 2300 帧,但是大小只有158KB,而且只需要一秒多一点就可以生成。这是这个程序最让人意外的一点:Python生成图像的慢是出了名的,它居然能在几秒内生成一张包含几千帧的 GIF动图?这是个大新闻啊!</p></li><li><p>严格遵循 GIF89a 协议,生成的图片在 chrome, firefox, IE 和 Eog中都可以正常显示。</p></li></ol><p>程序运行的相当快,生成一副 600x400 像素,演示 Wilson算法的动图只要数秒,得到的文件包含 1000~3000 帧,但大小不超过 1M左右。没想到吧?<span class="emoji" data-alias="astonished" style="" data-fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f632.png?v8">😲</span></p><p>这个程序是怎么来的呢?许多年前我在网上闲逛的时候,偶然发现了 d3.js <a href="https://bl.ocks.org/mbostock">作者的网站</a>(原链接已重定向,作者现在已经创业搞<a href="https://observablehq.com/">observablehq</a>去了),当时我对上面展示的各种丰富炫酷的动态效果惊羡不已,尤其是其中 <a href="https://bl.ocks.org/mbostock/11357811">Wilson算法的演示</a>,让我对此算法有了更直观和深入的理解。我立刻萌发了用Python 制作一个 GIF版本动画演示的想法,但是思考了许久也不知道从何入手。这里困难的地方在于Wilson算法是一个随机算法,其运行时间是不确定的,一个动画里面可能包含数千帧,如果采用把每一帧保存为图像再合并到一起的话,最终得到的文件会非常庞大。而且这种纯暴力的做法逼格不高,我实在不屑于采用。限于能力不足,这个想法只好被暂时压在心底,但是一直念念不忘。过了几年后,一个偶然的机会我接触到了GIF图像的编码协议,豁然开朗:为什么不直接把动画过程编码为字节流呢?通过精确定位每一帧的位置,控制LZW压缩过程的编码长度,文件过大的问题是可以解决的!前后捣鼓了半个月,反复研究协议细节,debug了无数次后,这才作出了上面的效果。后来慢慢又加上了其它迷宫算法和元胞自动机的演示。</p><p>关键的地方有这么几个:</p><ol type="1"><li><p>由于 GIF图像的每一帧占据的是整个图像窗口的一个矩形子区域,在一个包含很多帧的动图中,相邻的两帧之间的变动可能很小,没有必要每次都将整个图像全部编码。我们只需要记录帧和帧之间的变化情况,得出每一帧所占的矩形子区域,每次编码时只针对这个子区域编码即可,这样就大大减小了生成的文件体积。</p></li><li><p>采用变长的 LZW 压缩算法。GIF89a协议允许每个打包的数据块指明其所使用的最小码字的长度,如果你事先知道这一帧图像用到的颜色数目,比如4 种颜色,那么 2 个比特就足以表示这 4 种颜色,从而最小编码长度可以设置为2。这样根据具体情况采用不同的编码长度能有效减少文件体积。</p></li><li><p>因为要频繁的进行字节流的操作,所以每次将编码后的数据先写入一个<code>BytesIO</code> 对象中,放在内存里,最后一次性输出到硬盘。</p></li></ol><p>代码的组织结构是简单的三层论:顶层是抽象的 <code>Maze</code>类,其本质就是一个 2D网格图,用来跑各种图算法,它不关心动图的任何细节。底层是<code>GIFSurface</code> 类,负责维护 GIF图片的全局信息,比如图片宽高,循环次数,背景颜色,全局调色板等。中间层是<code>Animation</code>类,用来控制帧的信息,在算法运行过程中它按照一定的频率将<code>Maze</code> 染色并编码写入 <code>GIFSurface</code>。</p><p>目前程序的核心代码加起来大约在 1000行左右,但是如果牺牲一些可读性和功能的话,是可以压缩到 500行以内的。我曾经把这个项目投稿到 Github 上的 <a href="https://github.com/aosabook/500lines">500lines</a>上,可惜未能入选。但是我始终觉得它的优雅、奇妙并不逊色于那些大神们的作品。</p>]]></content>
<summary type="html">
<p>本文要介绍的是我写的一个有趣的 Python
小程序,一个脱离了低级趣味的程序,一个有益于广大人民了解算法的程序。代码在
<a href="https://github.com/neozhaoliang/pywonderland/tree/master/src/gifmaze">Github</a>
上。</p>
<p>这个程序可以用来制作各种各样的算法动画,包含但不限于:</p></summary>
<category term="pywonderland 项目" scheme="https://pywonderland.com/categories/pywonderland-%E9%A1%B9%E7%9B%AE/"/>
</entry>
<entry>
<title>模任何素数都可约的整系数不可约多项式</title>
<link href="https://pywonderland.com/reducible-mod-p/"/>
<id>https://pywonderland.com/reducible-mod-p/</id>
<published>2017-03-02T00:00:00.000Z</published>
<updated>2024-10-10T14:18:52.283Z</updated>
<content type="html"><![CDATA[<p>几年前在知乎上有这么 <a href="https://www.zhihu.com/question/38156113/answer/139354565">一个问题</a>:</p><div class="unnumbered statement question-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>有哪些 <span class="math inline">\(\mathbb{Z}[x]\)</span> 中的多项式,它们在有理数域<span class="math inline">\(\mathbb{Q}\)</span>上是不可约的,而对任意素数 <span class="math inline">\(p\)</span>,模<span class="math inline">\(p\)</span> 以后在 <span class="math inline">\(\mathbb{Z}_p[x]\)</span> 上都是可约的?</p></div><p>当时我给了回答,后来账号注销了,答案也一并删除了。现在把我的原答案贴在这里:</p><span id="more"></span><p>我所知道的有两大类多项式:</p><p>第一类是所有的 Swinnerdon-Dyer 多项式,它们形如 <span class="math display">\[f(x)=\prod(x\pm\sqrt{p_1}\pm\sqrt{p_2}\cdots\pm\sqrt{p_n}),\]</span>其中 <span class="math inline">\(p_1,\ldots,p_n\)</span>是互不相同的素数,乘积跑遍所有 <span class="math inline">\(2^n\)</span>种不同的组合。这种多项式都是不可约的整系数多项式,但是模任何素数 <span class="math inline">\(p\)</span>以后都分解为一次或者二次因式的乘积。</p><p>第二类来自分圆多项式,分圆多项式 <span class="math inline">\(\Phi_n(x)\)</span> 是本原 <span class="math inline">\(n\)</span> 次单位根在 <span class="math inline">\(\mathbb{Q}\)</span> 上的首 1 极小多项式,其次数为<span class="math inline">\(\phi(n)\)</span>,这里 <span class="math inline">\(\phi(\cdot)\)</span> 是 Euler totient函数。绝大多数分圆多项式模任何素数 <span class="math inline">\(p\)</span> 都是可约的!实际上我们有如下结论:</p><div class="unnumbered statement theorem-unnumbered plain"><p><span class="statement-heading"><span class="statement-label">定理</span>.</span><span class="statement-spah"></span>分圆多项式 <span class="math inline">\(\Phi_n(x)\)</span>模任何素数 <span class="math inline">\(p\)</span> 都可约当且仅当 <span class="math inline">\(n\ne1,2,p,2p^k\)</span>,其中 <span class="math inline">\(p\)</span> 是奇素数,<span class="math inline">\(k\)</span> 是正整数。</p></div><p>你可以看到知乎那个问题下的回答中举的例子都是最简单的 Swinnerdon-Dyer多项式或者分圆多项式。</p><p>我知道这个结论还是研究生时上夏壁灿老师的符号计算课程,讲到分解整系数多项式的Zassenhaus算法,这两类多项式被用来分析算法的最差复杂度。我还在校园书摊上淘到了一本破损的<a href="https://www.cambridge.org/core/books/modern-computer-algebra/DB3563D4013401734851CF683D2F03F0">ModernComputer Algebra</a>,在 15.3 节 “Frobenius’ and Chebotarev’s densitytheorems” 中有介绍。</p><p>这两类多项式属于比较容易分析的,还有别的例子吗?也是有的,<a href="https://projecteuclid.org/euclid.rmjm/1289916905">这篇文章</a>给出了更多不那么显然的构造。</p>]]></content>
<summary type="html">
<p>几年前在知乎上有这么 <a href="https://www.zhihu.com/question/38156113/answer/139354565">一个问题</a>:</p>
<div class="unnumbered statement question-unnumbered definition">
<p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>有哪些 <span class="math inline">\(\mathbb{Z}[x]\)</span> 中的多项式,它们在有理数域
<span class="math inline">\(\mathbb{Q}\)</span>
上是不可约的,而对任意素数 <span class="math inline">\(p\)</span>,模
<span class="math inline">\(p\)</span> 以后在 <span class="math inline">\(\mathbb{Z}_p[x]\)</span> 上都是可约的?</p>
</div>
<p>当时我给了回答,后来账号注销了,答案也一并删除了。现在把我的原答案贴在这里:</p></summary>
<category term="代数" scheme="https://pywonderland.com/categories/%E4%BB%A3%E6%95%B0/"/>
</entry>
<entry>
<title>Coupling from the past</title>
<link href="https://pywonderland.com/coupling-from-the-past/"/>
<id>https://pywonderland.com/coupling-from-the-past/</id>
<published>2016-07-01T16:00:00.000Z</published>
<updated>2024-11-23T12:15:50.152Z</updated>
<content type="html"><![CDATA[<p>今天我要介绍一个 Markov 链采样中的精彩算法,叫做 coupling from thepast(CFTP)。这个算法看似简单,实则充满玄机。我相信你可以在五分钟内理解算法的步骤,然后再花五分钟左右看懂算法的证明,但是我打赌你需要几个星期甚至更久的时间来细细回味其中奥妙。</p><p>作为启发,我们从一个计数问题开始:</p><div class="unnumbered statement question-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>下图是一个边长分别为 <span class="math inline">\(a,b,c\)</span> 的平行六边形,其中 <span class="math inline">\(a,b,c\)</span> 都是正整数,内角均为 120 度:</p><p><img style="margin:0px auto;display:block" width="400" src="/images/cftp/hexagon.svg"></p><p>请问:用边长为 1 的菱形密铺它,有多少种不同的方法?</p></div><span id="more"></span><p>比如下图就是一种密铺的示例:</p><p><img style="margin:0px auto;display:block" width="400" src="/images/cftp/random_lozenge_tiling.svg"></p><p>图中三种不同摆放角度的菱形被染成了不同的颜色。</p><p>这个问题的答案很不容易猜到,叫做 Macmahon 公式:</p><div id="macmahon-------" class="unnumbered statement sta_macmahon___ plain"><p><span class="statement-heading"><span class="statement-label">Macmahon 公式</span>.</span><span class="statement-spah"> </span>记 <span class="math inline">\(H(a,b,c)\)</span>为所求的六边形的不同菱形密铺的个数,则 <span class="math display">\[H(a,b,c)=\prod_{i=1}^a\prod_{j=1}^b\prod_{k=1}^c\frac{i+j+k-1}{i+j+k-2}.\]</span></p></div><p>关于 Macmahon 公式,以及它背后的 plane partition理论是另一段精彩的故事,这里不作介绍。需要注意的是,<span class="math inline">\(H(a,b,c)\)</span> 的值是指数级增长的,比如对 <span class="math inline">\(a=b=c=10\)</span> 这种比较小的情形 <span class="math inline">\(H(a,b,c)\approx9.265\times10^{33}\)</span>,已经是一个天文数字了。</p><p>真正的问题来了:</p><div class="unnumbered statement question-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>怎样在全部 <span class="math inline">\(H(a,b,c)\)</span>种不同的密铺中完全随机地任选一种?(即按照均匀分布采样)</p></div><p>由于 <span class="math inline">\(H(a,b,c)\)</span>太太太大了,我们不可能先把所有密铺都列出来然后再挑选,那样的话全世界的计算机内存加起来也装不下。所以得设计一个聪明点的方法,这就是CFTP 要做的。</p><h1 id="markov-链的随机取样">Markov 链的随机取样</h1><p>设 <span class="math inline">\(M\)</span> 是一个有限遍历的 Markov链,其状态空间为 <span class="math inline">\(S\)</span>,平稳分布为<span class="math inline">\(\pi\)</span>,我们希望以分布 <span class="math inline">\(\pi\)</span> 从 <span class="math inline">\(S\)</span> 中随机地取样,即对任何 <span class="math inline">\(s\in S\)</span>,取样抽到 <span class="math inline">\(s\)</span> 的概率为 <span class="math inline">\(\pi(s)\)</span>。这在许多实际应用中都有重要意义。通常的方法是任选一个初始状态<span class="math inline">\(s_0\)</span> 然后从 <span class="math inline">\(s_0\)</span> 出发跑这个 Markov链。可以证明只要运行的时间 <span class="math inline">\(n\)</span>足够大,其 <span class="math inline">\(n\)</span> 时刻的状态 <span class="math inline">\(s_n\)</span> 服从的分布就可以任意逼近平稳分布:<span class="math display">\[|\mathbb{P}(s_n=s) - \pi(s)| <\epsilon,\quad\forall s\in S,\ \forall\epsilon>0.\]</span>这个方法非常简单易行,但是它有两个缺陷:首先它只是一个近似算法,不管<span class="math inline">\(n\)</span> 取得多么大,返回的 <span class="math inline">\(s_n\)</span> 的分布只是近似而非严格等于平稳分布<span class="math inline">\(\pi\)</span>;其次为了获得足够的精度采样所需的时间<span class="math inline">\(n\)</span> (叫做 mixingtime)也不总是那么容易估计的。那么有没有什么办法可以获得精确地服从 <span class="math inline">\(\pi\)</span> 的采样呢?</p><p>Propp 和 Wilson 提出了如下的想法:既然从初始状态出发向未来 (<span class="math inline">\(+\infty\)</span> 方向) 跑 Markov链得不到真正的平稳分布,我们何不从无穷远的过去 (<span class="math inline">\(-\infty\)</span> 方向) 向现在 (时刻 0)跑呢?可以想象当这个链经过了无穷次迭代后,其 0 时刻的状态 <span class="math inline">\(s^\ast\)</span> 服从的分布就是 <span class="math inline">\(\pi\)</span>。当然,一个可行的算法必须在有限时间内输出结果,我们不可能做到真的从无穷远的过去出发。我们能做的只是选择一个足够大的<span class="math inline">\(n\)</span> 然后从 <span class="math inline">\(-n\)</span> 时刻出发向时刻 0 跑,但是这种做法和从0 时刻向时刻 <span class="math inline">\(n\)</span>跑没有什么区别。Propp 和 Wilson的观察的关键之处在于,只跑一个链是不行的,我们需要从每个 <span class="math inline">\(s\in S\)</span> 出发,同时跑 <span class="math inline">\(M\)</span> 的 <span class="math inline">\(|S|\)</span> 个不同的版本,并且观察它们是否在时刻0 时耦合在一起 (coupled together),即相遇到了相同的状态 <span class="math inline">\(s^\ast\)</span>。一旦这件事情发生的话,那么假设我们还有一个额外的从无穷远出发、初始分布是<span class="math inline">\(\pi\)</span> 的链,由于它来到 0时刻必然也处于状态 <span class="math inline">\(s^\ast\)</span>,所以<span class="math inline">\(s^\ast\)</span> 就服从分布 <span class="math inline">\(\pi\)</span>。如果没有相遇呢?那就从某个更久远的位置开始再来一遍,直到耦合出现为止,这就是coupling from the past 的由来。</p><p>用不太准确的话说,我们是在时间 <span class="math inline">\(-n\)</span> 处设置了 <span class="math inline">\(|S|\)</span>个不同的链,封死了从无穷远过去出发的链在 <span class="math inline">\(-n\)</span>处的所有可能状态,然后通过将所有链在时刻 0“坍缩”为单个状态来获得采样。</p><p>其实我上面的描述仍然遗漏了 CFTP 的一些关键细节。为了准确的描述CFTP,我们首先引入 Markov 链的随机映射表示 (random mappingrepresentation)。</p><h1 id="markov-链的随机映射表示">Markov 链的随机映射表示</h1><p>随机映射表示能够让我们用计算机程序来模拟 Markov链,它是一个由随机数流驱动的更新函数 <span class="math inline">\(f:S\times [0, 1]\to S\)</span>。<span class="math inline">\(f\)</span>本身是确定的,对任何状态 <span class="math inline">\(s\in S\)</span> 和<span class="math inline">\(u\in [0,1]\)</span>,<span class="math inline">\(s'=f(s,u)\)</span> 给出 Markov链更新后的状态。我们要求 <span class="math inline">\(f\)</span> 满足当<span class="math inline">\(U\)</span> 是服从 <span class="math inline">\([0, 1]\)</span> 上的均匀分布的随机变量时,<span class="math inline">\(\mathbb{P}(f(s,U)=s')=P_{s,s'}\)</span>。这里 <span class="math inline">\(P_{s,s'}\)</span> 是 Markov 链从 <span class="math inline">\(s\)</span> 到 <span class="math inline">\(s'\)</span> 的转移概率。任何有限 Markov链都存在随机映射表示,而且表示方法不是唯一的。最简单的构造方式是用一个阶梯函数:<span class="math display">\[f(s_i, u) =\begin{cases}\begin{array}{ll}s_1, &\text{for } u\in[0,P_{i,1}),\\s_2, &\text{for } u\in[P_{i,1}, P_{i,1}+P_{i,2}),\\\vdots&\vdots\\s_j, &\text{for } u\in\left[\sum_{k=1}^{j-1}P_{i,k},\sum_{k=1}^jP_{i,k}\right),\\\vdots &\vdots\\s_n, &\text{for }u\in\left[\sum_{k=1}^{n-1}P_{i,k},1\right].\end{array}\end{cases}\]</span></p><p>假设有一个随机数发生器可以产生独立且服从 <span class="math inline">\([0,1]\)</span> 上均匀分布的随机变量序列 <span class="math inline">\(U_0,U_{-1},U_{-2},\ldots\)</span>,则我们可以由此来驱动Markov 链 <span class="math inline">\(M\)</span>从过去的某个时刻向现在运行: <span class="math display">\[s_{-n}\xrightarrow{f(s_{-n+1},\,U_{-n+1})}s_{-n+1}\xrightarrow{f(s_{-n+2},\,U_{-n+2})}\cdots\xrightarrow{f(s_0,\,U_0)}s_0.\]</span></p><h1 id="coupling-from-the-past-算法">Coupling from the past 算法</h1><p>现在我们可以来表述 coupling from the past 算法了。</p><p>设 <span class="math inline">\(M\)</span> 是一个有限遍历的 Markov链,状态空间为 <span class="math inline">\(S\)</span>,<span class="math inline">\(f: S\times [0, 1]\to S\)</span>是其随机映射表示。<span class="math inline">\(U_0,U_{-1},\ldots\)</span>是一列随机数,它们分别来自一列独立且服从 <span class="math inline">\([0,1]\)</span> 上均匀分布的随机变量。记 <span class="math inline">\((N_1,N_2,\ldots)=(1,2,4,8,\ldots)\)</span>,<span class="math inline">\(-N_{m}\)</span> 将作为我们第 <span class="math inline">\(m\)</span> 次重启的出发时间。</p><blockquote><p><strong>Coupling from the past 算法</strong>:</p><ol type="1"><li>令 <span class="math inline">\(m=1\)</span>。</li><li>对每个 <span class="math inline">\(s\in S\)</span>,以 <span class="math inline">\(s\)</span> 为初始状态,以 <span class="math inline">\(-N_m\)</span> 为初始时刻向时刻 0 的方向运行 Markov链 <span class="math inline">\(M\)</span>,所有 <span class="math inline">\(|S|\)</span> 个链使用的随机数流是一样的,都是<span class="math inline">\((U_{-N_m+1},\ldots,U_{-1},U_0)\)</span>。</li><li>如果步骤 2 中的 <span class="math inline">\(|S|\)</span> 个链在时刻0 给出的状态相同,记此状态为 <span class="math inline">\(s^\ast\)</span>,则输出 <span class="math inline">\(s^\ast\)</span> 并退出程序。否则将 <span class="math inline">\(m\)</span> 的值加 1 并重复步骤 2。</li></ol><p>下图显示了算法的每个重启时刻,相同颜色的随机数是在同一批中生成的。</p><p><img style="margin:0px auto;display:block" src="/images/cftp/random_numbers.svg"></p><p><strong>断言</strong>:如果上述步骤以概率 1在有限时间内结束,则其返回值 <span class="math inline">\(s^\ast\)</span>服从平稳分布 <span class="math inline">\(\pi\)</span>: <span class="math display">\[\mathbb{P}(s^\ast = s) = \pi(s),\quad \foralls\in S.\]</span></p></blockquote><p>注意这里的两个细节:</p><ol type="1"><li>我们强调了前提<strong>如果算法以概率 1在有限时间内结束,则返回值服从平稳分布</strong>。为了保证这个前提成立更新函数<span class="math inline">\(f\)</span>的选择就不能是任意的,特别地在后面的 monotone CFTP 中更新函数还要与<span class="math inline">\(S\)</span>上的偏序相容,更不能是任意的。</li><li>当第 <span class="math inline">\(m\)</span> 次执行步骤 2时,使用的随机数为 <span class="math inline">\((U_{-N_m+1},U_{-N_m+2},\ldots,U_{-1},U_0)\)</span>,其中的后半部分<span class="math inline">\((U_{-N_{m-1}+1},U_{-N_{m-1}+2},\ldots,U_{-1},U_0)\)</span>需要与上一次使用的相同,<strong>即每一次都重复使用上一次的随机数作为后半段的随机源</strong>,否则每次都重新生成一列新的随机数的话得到的最终状态未必服从平稳分布。</li></ol><p><strong>证明</strong>:任取 <span class="math inline">\(s_i\inS\)</span>,只要证明对任何 <span class="math inline">\(\epsilon>0\)</span> 都有 <span class="math display">\[|\mathbb{P}(s^\ast=s_i) -\pi(s_i)|<\epsilon.\]</span> 设 <span class="math inline">\(\Omega=\{(U_{-1},U_{-2},\ldots)\mid U_i \text{i.i.d on } [0,1]\}\)</span> 是所有随机数流组成的样本空间, <span class="math display">\[A=\{\text{the algorithm terminates in finitetime}\}.\]</span> 即 <span class="math inline">\(A\)</span>为那些可以使得算法在有限时间内结束的序列组成的集合,则 <span class="math inline">\(\mathbb{P}(A)=1\)</span>。</p><p>又记 <span class="math display">\[A_i = \{ \text{the algorithm doesnot need to try starting times earlier than} -N_i\}.\]</span> 即 <span class="math inline">\(A_i\)</span> 为事件「算法从 <span class="math inline">\(-N_i\)</span> 或者更早的时间出发可以结束」。</p><p>显然我们有 <span class="math inline">\(A_i\uparrow A\)</span>,<span class="math inline">\(\mathbb{P}(A_i)\uparrow\mathbb{P}(A)=1\)</span>。因此对充分大的<span class="math inline">\(K\)</span> 有 <span class="math inline">\(\mathbb{P}(A_K) \geq1-\epsilon\)</span>。取定这样的 <span class="math inline">\(K\)</span>,则在事件 <span class="math inline">\(A_K\)</span> 上,所有的链在时刻 0 耦合到相同的状态<span class="math inline">\(s^\ast\)</span>。</p><p>除了以上 <span class="math inline">\(|S|\)</span>条链之外,我们再额外跑一条单独的链 <span class="math inline">\(Y\)</span>,这条链的初始状态选自平稳分布 <span class="math inline">\(\pi\)</span>,也从时刻 <span class="math inline">\(-N_K\)</span> 出发,也使用相同的随机数 <span class="math inline">\((U_{-N_K+1},\ldots,U_0)\)</span> 运行至时刻0,并设这个链在时刻 0 的状态为 <span class="math inline">\(Y_0\)</span>,则 <span class="math inline">\(Y_0\)</span> 服从平稳分布。</p><p>在事件 <span class="math inline">\(A_K\)</span>上,不管这条单独的链初始状态是什么,由于它使用了同样的随机数序列,所以它最后一定会和其余<span class="math inline">\(|S|\)</span> 条链一起耦合,所以 <span class="math display">\[\mathbb{P}(s^\ast=Y_0) \geq \mathbb{P}(A_K)\geq 1- \epsilon.\]</span> 从而对任何 <span class="math inline">\(s_i\inS\)</span>, <span class="math display">\[\begin{aligned}\mathbb{P}(s^\ast =s_i)-\pi(s_i)&= \mathbb{P}(s^\ast = s_i)-\mathbb{P}(Y_0 =s_i)\\&\leq\mathbb{P}(s^\ast=s_i, Y_0\ne s_i)\\&\leq\mathbb{P}(Y_0\nes^\ast)\\&\leq\epsilon.\end{aligned}\]</span> 类似地 <span class="math display">\[\begin{aligned}\pi(s_i)-\mathbb{P}(s^\ast =s_i)&=\mathbb{P}(Y_0 = s_i)-\mathbb{P}(s^\ast =s_i)\\&\leq\mathbb{P}(Y_0=s_i, s^\ast\ne s_i)\\&\leq\mathbb{P}(Y_0\nes^\ast)\\&\leq\epsilon.\end{aligned}\]</span> 从而 <span class="math display">\[|\mathbb{P}(s^\ast = s_i)-\pi(s_i)| \leq\epsilon.\]</span> 令 <span class="math inline">\(K\to\infty\)</span>,则 <span class="math inline">\(\epsilon\downarrow0\)</span>。注意到对任何样本点<span class="math inline">\(\omega\in A_K\)</span>,如果 <span class="math inline">\(\omega\)</span> 给出的所有链的耦合状态是 <span class="math inline">\(s^\ast=s_i\)</span>,则从更久远的时刻出发,<span class="math inline">\(\omega\)</span> 给出的耦合状态仍然是 <span class="math inline">\(s_i\)</span>,即 <span class="math inline">\(\omega\)</span> 输出的采样结果 <span class="math inline">\(s^\ast\)</span> 是不会随着 <span class="math inline">\(K\)</span> 增大而改变的,所以由 <span class="math inline">\(\epsilon\)</span> 的任意性即得 <span class="math inline">\(s^\ast\)</span> 服从平稳分布。</p><h1 id="算法中的若干陷阱">算法中的若干陷阱</h1><p>CFTP算法的证明看似不难,但其实微妙之处不少,值得细细品味。最主要的地方有三个:</p><blockquote><p><strong>问题 1</strong>:为什么说更新函数 <span class="math inline">\(f\)</span> 的选择不能是任意的?</p><p><strong>问题 2</strong>:既然 「coupling from the past」 可以,那「coupling to the future」 可不可以?从时刻 0 开始从每个 <span class="math inline">\(s\in S\)</span> 出发跑 <span class="math inline">\(|S|\)</span> 个不同的链,直到它们在未来某个时刻<span class="math inline">\(n\)</span>耦合为止,然后输出第一次耦合时的状态不行吗?</p><p><strong>问题 3</strong>:每次重启步骤 2时需要复用之前的随机数,这一点在证明中哪里用到了?使用一列新的随机数为什么不可以?</p></blockquote><p>我们用几个例子来说明这三个问题。</p><h2 id="为什么更新函数不能是任意的">为什么更新函数不能是任意的</h2><p>考虑含有两个状态 <span class="math inline">\(S=\{s_1, s_2\}\)</span>的 Markov 链,其转移矩阵为 <span class="math inline">\(P=\begin{bmatrix}0.5 & 0.5\\0.5 &0.5\end{bmatrix}\)</span>,更新函数为 <span class="math display">\[f(s_1, u) =\begin{cases}\begin{array}{ll}s_1& \text{for } u \in [0, 0.5)\\s_2 & \text{for } u \in [0.5,1]\end{array}\end{cases}\]</span> 和 <span class="math display">\[f(s_2,u) =\begin{cases}\begin{array}{ll}s_2 & \text{for } u \in [0,0.5)\\s_1 & \text{for } u \in [0.5,1]\end{array}\end{cases}\]</span> 于是若从 <span class="math inline">\(s_1,s_2\)</span>分别出发跑两个不同的链,但是每次使用相同的随机数,则它们要么保持不动,要么交换状态,永不耦合。</p><h2 id="为什么-coupling-into-the-future-不行">为什么 Coupling into thefuture 不行</h2><p>我打赌任何看到 CFTP算法的人都会想到同样的问题:为什么不能向未来耦合呢?</p><blockquote><p><strong>Coupling into the future</strong>: 从时刻 0 出发同时跑 <span class="math inline">\(|S|\)</span> 个不同的链,其中链 <span class="math inline">\(i\)</span> 的初始状态是 <span class="math inline">\(s_i\)</span>。当所有链首次耦合到同一状态 <span class="math inline">\(s^\ast\)</span> 时,终止算法并输出 <span class="math inline">\(s^\ast\)</span> 作为采样状态。</p></blockquote><p>向未来耦合与 CFTP有一个根本不同:向未来耦合的结束时间是一个随机时间,而在 CFTP中,我们总是在固定的时刻 0 观察所有链是否耦合。</p><p>我们来试试把上面 CFTP 的证明照抄在这里:设 <span class="math inline">\(\tau\)</span> 是所有 <span class="math inline">\(|S|\)</span> 条链首次耦合的时间,<span class="math inline">\(Y\)</span> 是额外的从时刻 0出发的、初始分布为平稳分布的链,并且使用相同的随机数流,则对任何时刻<span class="math inline">\(n\ge0\)</span>,<span class="math inline">\(Y_n\)</span>都服从平稳分布。但是当把下标换成随机时间 <span class="math inline">\(\tau\)</span> 时,<span class="math inline">\(Y_\tau\)</span>未必仍然服从平稳分布,所以之前的证明不再可用。</p><p>我们用一个反例来说明:仍然考虑两个状态 <span class="math inline">\(S=\{s_1, s_2\}\)</span> 的 Markov 链,其转移矩阵为<span class="math inline">\(P=\begin{bmatrix}0.5 & 0.5\\1 &0\end{bmatrix}\)</span>,即从 <span class="math inline">\(s_1\)</span>出发的话以 0.5 的概率待在原地,以 0.5 的概率跳到 <span class="math inline">\(s_2\)</span>,从 <span class="math inline">\(s_2\)</span> 出发的话则总是跳到 <span class="math inline">\(s_1\)</span>。</p><p><img style="margin:0px auto;display:block" width="250" src="/images/cftp/counter_example.svg"></p><p>这个链的平稳分布为 <span class="math inline">\(\pi=(\frac{2}{3},\frac{1}{3})\)</span>。现在假设从<span class="math inline">\(s_1,s_2\)</span> 分别出发,从时刻 0 开始向<span class="math inline">\(+\infty\)</span> 方向跑两个不同的链,<span class="math inline">\(\tau\)</span> 是它们首次耦合的时间,则 <span class="math inline">\(\tau-1\)</span> 时刻它俩必然一个位于 <span class="math inline">\(s_1\)</span>,一个位于 <span class="math inline">\(s_2\)</span>。但是位于 <span class="math inline">\(s_2\)</span> 的状态只能转移到 <span class="math inline">\(s_1\)</span>,所以 <span class="math inline">\(\tau\)</span> 时刻的输出永远是 <span class="math inline">\(s_1\)</span>,从而得到的采样 <span class="math inline">\(Y_\tau\)</span> 不满足平稳分布。</p><h2 id="为什么每次不能重新生成随机数">为什么每次不能重新生成随机数</h2><p>思考一下,在算法的证明当中,如果在每次迭代中都使用全新的随机数序列的话,那么事件<span class="math inline">\(A\)</span> 的定义会变成什么?难道是 <span class="math inline">\(\Omega\)</span>的某个有限子集,使得其包含一个可以耦合的序列?Hmm,这就不太对劲了。直观上看,在第<span class="math inline">\(m\)</span>次迭代时,由于生成的序列是全新的,有可能它实际上对某个 <span class="math inline">\(i<m\)</span>,从 <span class="math inline">\(-N_i\)</span>出发就可以耦合,这会导致算法过度采样那些很快就可以耦合的短链,从而使得最终的分布不服从平稳分布。</p><p>我们继续用上一小节中的例子来说明。我们指定其更新函数 <span class="math inline">\(f\)</span>为随机映射表示一节中给出的阶梯函数形式。假设算法每次都使用一列新的随机数,其最终输出为<span class="math inline">\(s^\ast\)</span>。定义随机变量 <span class="math inline">\(\tau\)</span> 为正整数 <span class="math inline">\(m\)</span> 使得算法中使用的最早的出发时间为 <span class="math inline">\(-N_m\)</span>,则 <span class="math display">\[\begin{aligned}\mathbb{P}(s^\ast=s_1)&=\sum_{m=1}^\infty\mathbb{P}(s^\ast=s_1,\tau=m)\\&\geq\mathbb{P}(s^\ast=s_1,\tau=1)+\mathbb{P}(s^\ast=s_1,\tau=2)\\&=\mathbb{P}(\tau=1)\mathbb{P}(s^\ast=s_1|\tau=1)+\mathbb{P}(\tau=2)\mathbb{P}(s^\ast=s_1|\tau=2)\end{aligned}\]</span> 注意事件 <span class="math inline">\(\{\tau=1\}\)</span> 包含两种不同的演化路径: <span class="math display">\[\begin{aligned}(1)\quad & s_1\to s_1,\quads_2\to s_1.\\(2)\quad & s_1\to s_2,\quad s_2\to s_1.\end{aligned}\]</span>其中只有前者能成功耦合,所以 <span class="math inline">\(\mathbb{P}(\tau=1)=\frac{1}{2}\)</span>,这时输出的状态只能是<span class="math inline">\(s_1\)</span>,所以<span class="math inline">\(\mathbb{P}(s^\ast=s_1|\tau=1)=1\)</span>。</p><p>可以看到这个长度是 1 的短链的耦合只发生在状态 <span class="math inline">\(s_1\)</span> 上,它非常偏爱 <span class="math inline">\(s_1\)</span>。</p><p>事件 <span class="math inline">\(\{\tau=2\}\)</span>包含四种不同的演化路径: <span class="math display">\[\begin{align*}(1)\quad & s_1\to s_1\tos_1,\quad s_2\to s_1 \to s_1.\\(2)\quad & s_1\to s_2\to s_1,\quad s_2\to s_1 \to s_1.\\(3)\quad & s_1\to s_1\to s_2,\quad s_2\to s_1 \to s_2.\\(4)\quad & s_1\to s_2\to s_1,\quad s_2\to s_1 \tos_2.\end{align*}\]</span>注意以下两种演化路径是非法的,因为每个时刻两个链使用的随机数一样,不可能在某个时刻同时出现一个链<span class="math inline">\(s_1\to s_2\)</span>,另一个 <span class="math inline">\(s_1\to s_1\)</span> 的情况: <span class="math display">\[\begin{array}{ll}(*)\quad & s_1\to s_1\to s_2,\quad &s_2\to s_1 \to s_1.\\(**)\quad & s_1\to s_1\to s_1,\quad &s_2\to s_1 \to s_2.\\\end{array}\]</span></p><p>在我们现在这个错误的版本中,由于使用了全新的随机数流,四种路径都是合法的。这四个路径中前三种都成功耦合,两个耦合于<span class="math inline">\(s_1\)</span> 一个耦合于 <span class="math inline">\(s_2\)</span>,所以 <span class="math inline">\(\mathbb{P}(s^\ast=s_1|\tau=2)=\frac{2}{3}\)</span>。</p><p>注意到其中第二条路径 <span class="math display">\[(2)\quad s_1\tos_2\to s_1,\quad s_2\to s_1 \to s_1.\]</span> 从时刻 <span class="math inline">\(-1\)</span> 出发就可以耦合,它不应该属于事件 <span class="math inline">\(\{\tau=2\}\)</span>。每次使用全新的随机数流会导致偏爱<span class="math inline">\(s_1\)</span> 的短链被过度采样。</p><p>我们来具体验证一下: <span class="math display">\[\mathbb{P}(\tau=2)=\mathbb{P}(\tau\ne1)\cdot\mathbb{P}(\tau=2\\text{时耦合})=\frac{1}{2}\cdot\frac{3}{4}=\frac{3}{8}.\]</span></p><p>所以 <span class="math display">\[\begin{align*}\mathbb{P}(s^\ast=s_1)&\geq\mathbb{P}(\tau=1)\mathbb{P}(s^\ast=s_1|\tau=1)+\mathbb{P}(\tau=2)\mathbb{P}(s^\ast=s_1|\tau=2)\\&=\frac{1}{2}\cdot1+ \frac{3}{8}\cdot\frac{2}{3}\\&=\frac{3}{4}>\pi(s_1).\end{align*}\]</span></p><p>确实如我们的预言,<span class="math inline">\(s_1\)</span>被过度采样了。</p><h1 id="monotone-coupling-from-the-past">Monotone coupling from thepast</h1><p>在 CFTP 算法中,我们需要同时跑 <span class="math inline">\(|S|\)</span> 个不同的链并要求它们在时刻 0处耦合,当 <span class="math inline">\(|S|\)</span>很大时所耗的时间和计算量都很不划算,所以这个算法在应用中是有限制的。但是有一种情形它是非常好用的:如果<span class="math inline">\(S\)</span> 是一个偏序集 <span class="math inline">\((S, \preceq)\)</span>,有最大最小元 <span class="math inline">\(s_\max, s_\min\)</span>,并且更新函数 <span class="math inline">\(f\)</span> 与偏序 <span class="math inline">\(\preceq\)</span> 相容,即对任何 <span class="math inline">\(s,s'\in S\)</span>,<span class="math inline">\(u\in[0,1]\)</span>, <span class="math display">\[s\preceq s' \Rightarrow f(s, u) \preceqf(s', u),\]</span> 则我们只要对 <span class="math inline">\(s_\max,s_\min\)</span>这两个状态跑两个不同的链即可,当它俩耦合时,所有其它的链也会被“挤压”到相同的状态。这就是前面六边形的菱形密铺取样所采取的方法。</p><p>我们在所有菱形密铺组成的集合 <span class="math inline">\(S\)</span>上定义一个偏序 <span class="math inline">\(\preceq\)</span>,这个偏序的定义颇有技巧性,它需要将任一密铺对应到一个不相交的格点路径组,如下图所示:</p><p><img style="margin:0px auto;display:block" width="500" src="/images/cftp/non-intersecting_paths_lozenge.svg"></p><p>图中一共出现了 <span class="math inline">\(c+2\)</span>条不相交的路径,其中最上方和最下方两条路径对任何密铺都是固定的(它俩是用来约束中间的 <span class="math inline">\(c\)</span>条路径,让它们在翻转的过程不要越界),中间的 <span class="math inline">\(c\)</span>条路径,每条路径的起点和终点也是固定的,它们从菱形最左边的边的每个单位线段中点出发,每一步分别向右上或者右下走一步,经过<span class="math inline">\(a+b\)</span>步后到达最右边的边的对应位置。</p><p><img style="margin:0px auto;display:block" width="500" src="/images/cftp/two_paths.svg"></p><p>上图中从菱形的最左边到最右边共有 <span class="math inline">\(a+b+1\)</span>条竖直的网格线,每一步向右上或者右下走一步会向右移动到下一个网格线,所以总共需要<span class="math inline">\(a + b\)</span>次到达最右边。不同的路径互不相交,所以它们的终点必须互不相同,因此这些终点必然分别依次是菱形最右边的单位线段的中点。</p><p>不难说明所有的菱形密铺和所有不相交路径组之间的一一对应关系:当密铺给定时,从左边每个起点出发开始,根据当前菱形的倾斜方向依次描出路径即可;反之当路径组给定时,可以沿着每条路径铺砖,这样确定所有的“斜”菱形的位置,余下的空白位置只有唯一的方式可以被水平的菱形填充。</p><p>我们在所有不相交的路径组之间定义一个偏序:两个路径组 <span class="math inline">\(\mathcal{P}\preceq\mathcal{P}'\)</span>当且仅当对任何 <span class="math inline">\(1\leq i\leqc+2\)</span>,<span class="math inline">\(\mathcal{P}\)</span> 中的第<span class="math inline">\(i\)</span> 条路径 <span class="math inline">\(p_i\)</span> 整体地位于 <span class="math inline">\(\mathcal{P}'\)</span> 中第 <span class="math inline">\(i\)</span> 条路径 <span class="math inline">\(p_i'\)</span>的下方。在这个偏序下的最大元就是所有路径尽可能地「向上拱」:</p><p><img style="margin:0px auto;display:block" width="500" src="/images/cftp/max_state.svg"></p><p>而最小元则是所有路径尽可能地「向下走」:</p><p><img style="margin:0px auto;display:block" width="500" src="/images/cftp/min_state.svg"></p><p>有了偏序,我们还要定义一个与之相容的更新函数 <span class="math inline">\(f\)</span>。<span class="math inline">\(f\)</span>的定义是这样的:对一个不相交路径组 <span class="math inline">\(\mathcal{P}\)</span>,我们每次在 <span class="math inline">\(\mathcal{P}\)</span> 的中间 <span class="math inline">\(c\)</span> 条路径中,在路径内部 (两头端点除外)任选一个顶点 <span class="math inline">\(v\)</span>:</p><ol type="1"><li>如果 <span class="math inline">\(v\)</span> 是一个「山峰」,即形如<span class="math inline">\(\wedge\)</span>,则我们以 1/2 的概率保持<span class="math inline">\(\mathcal{P}\)</span> 不变,以 1/2的概率尝试将 <span class="math inline">\(\mathcal{P}\)</span> 在 <span class="math inline">\(v\)</span> 处翻转为一个「山谷」 <span class="math inline">\(\vee\)</span>,如果翻转之后得到的路径组 <span class="math inline">\(\mathcal{P}'\)</span>仍然满足路径之间不相交的约束,则规定 <span class="math inline">\(\mathcal{P}'=f(\mathcal{P})\)</span>,否则仍然保持<span class="math inline">\(\mathcal{P}\)</span> 不变。</li><li>如果 <span class="math inline">\(v\)</span> 是一个「山谷」,即形如<span class="math inline">\(\vee\)</span>,则与上面的情形类似,我们以1/2 的概率保持 <span class="math inline">\(\mathcal{P}\)</span> 不变,以1/2 的概率尝试将 <span class="math inline">\(\mathcal{P}\)</span> 在<span class="math inline">\(v\)</span> 处翻转为一个「山峰」 <span class="math inline">\(\wedge\)</span>,如果翻转之后得到的路径组满足不相交的约束,则规定<span class="math inline">\(\mathcal{P}'=f(\mathcal{P})\)</span>,否则仍然保持<span class="math inline">\(\mathcal{P}\)</span> 不变。</li><li>如果 <span class="math inline">\(v\)</span>既不是「山峰」也不是「山谷」,则保持 <span class="math inline">\(\mathcal{P}\)</span> 不变。</li></ol><p>菱形密铺在三维空间中看起来像是「堆箱子」,这个翻转路径的操作就相当于从中添加/移除一个箱子,并且必须保证这个箱子有三个面可见:</p><p><img style="margin:0px auto;display:block" width="400" src="/images/cftp/flip_lozenge.svg"></p><p>我们来验证 <span class="math inline">\(f\)</span>是和路径组之间的偏序 <span class="math inline">\(\preceq\)</span>相容的:设 <span class="math inline">\(\mathcal{P}\preceq\mathcal{P}'\)</span>是两个不相交路径组,对给定的随机操作 <span class="math inline">\(u\)</span>,<span class="math inline">\(f(\mathcal{P}, u)\)</span> 和 <span class="math inline">\(f(\mathcal{P}', u)\)</span> 就是对 <span class="math inline">\(\mathcal{P}\)</span> 和 <span class="math inline">\(\mathcal{P}'\)</span> 的同一个位置 <span class="math inline">\((k, j)\)</span>(即第 <span class="math inline">\(k\)</span> 条路径中的第 <span class="math inline">\(j\)</span> 个顶点)同时尝试进行一个 <span class="math inline">\(\vee\to\wedge\)</span> 或者 <span class="math inline">\(\wedge\to\vee\)</span> 的操作。不妨假设这个操作是<span class="math inline">\(\vee\to\wedge\)</span>,则有四种可能的结果:<span class="math inline">\(\mathcal{P}\)</span> 和 <span class="math inline">\(\mathcal{P}'\)</span>都操作成功,都保持不变或者一个操作成功另一个保持不变。不难验证这四种情况下都有<span class="math inline">\(f(\mathcal{P},u)\preceq f(\mathcal{P}',u)\)</span>。</p><p>由于每个不相交的路径组都可以通过适当操作变为最大元或者最小元,所以这个链是个互通的Markov 链。并且由于 <span class="math inline">\(\mathcal{P}\)</span>以至少 1/2 的概率在 <span class="math inline">\(f\)</span>下保持不变,这个链还是非周期的,因此是一个遍历的 Markov链,所以有唯一的平稳分布。但是不难看到这个链还是对称的,所以这个唯一的平稳分布是均匀分布。即从最大元和最小元出发跑CFTP,最终得到的样本服从全体菱形密铺上的均匀分布。</p><p>Monotone CFTP 也可以应用在其它许多密铺问题的均匀采样中,例如下图是在<span class="math inline">\(20\times 20\)</span>的矩形区域的所有多米诺骨牌密铺中均匀采样,同样可以把密铺一一对应到不相交的路径组:</p><p><img style="margin:0px auto;display:block" width="600" src="/images/cftp/domino_tiling_example.svg"></p><h1 id="参考文献">参考文献</h1><ol type="1"><li>Finite Markov chains and algorithmic applications, OlleHäggström.</li><li><a href="https://pages.uoregon.edu/dlevin/MARKOV/mcmt2e.pdf">Markovchains and mixing times</a>, Yuval Peres, Elizabeth L. Wilmer, David A.Levin.</li><li><a href="https://www.researchgate.net/publication/2455641_Markov_Chain_Algorithms_for_Planar_Lattice_Structures">MarkovChain Algorithms for Planar Lattice Structures</a>, Michael Luby, DanaRandall, Alistair Sinclair.</li><li><a href="https://arxiv.org/abs/math/0102193">Mixing times of lozengetiling and card shuffling Markov chains</a>, David B. Wilson.</li></ol>]]></content>
<summary type="html">
<p>今天我要介绍一个 Markov 链采样中的精彩算法,叫做 coupling from the
past
(CFTP)。这个算法看似简单,实则充满玄机。我相信你可以在五分钟内理解算法的步骤,然后再花五分钟左右看懂算法的证明,但是我打赌你需要几个星期甚至更久的时间来细细回味其中奥妙。</p>
<p>作为启发,我们从一个计数问题开始:</p>
<div class="unnumbered statement question-unnumbered definition">
<p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>下图是一个边长分别为 <span class="math inline">\(a,b,c\)</span> 的平行六边形,其中 <span class="math inline">\(a,b,c\)</span> 都是正整数,内角均为 120 度:</p>
<p><img style="margin:0px auto;display:block" width="400" src="/images/cftp/hexagon.svg"></p>
<p>请问:用边长为 1 的菱形密铺它,有多少种不同的方法?</p>
</div></summary>
<category term="完美采样" scheme="https://pywonderland.com/categories/%E5%AE%8C%E7%BE%8E%E9%87%87%E6%A0%B7/"/>
</entry>
<entry>
<title>Wilson 均匀生成树算法</title>
<link href="https://pywonderland.com/wilson-algorithm/"/>
<id>https://pywonderland.com/wilson-algorithm/</id>
<published>2014-04-03T16:00:00.000Z</published>
<updated>2024-12-08T04:41:47.004Z</updated>
<content type="html"><![CDATA[<div class="unnumbered statement question-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>给定一个有限、无向的连通图 <span class="math inline">\(G= ( V,E )\)</span>,设 <span class="math inline">\(\mathcal{T}\)</span> 是 <span class="math inline">\(G\)</span> 的所有生成树组成的集合,怎样在 <span class="math inline">\(\mathcal{T}\)</span>中按照均匀分布进行采样?即设计一个算法,能够随机地给出 <span class="math inline">\(G\)</span> 的一个生成树,并且 <span class="math inline">\(\mathcal{T}\)</span>中每个生成树被取到的概率是相等的。</p></div><p>常见的生成树算法如 DFS/BFS 算法、Prim 算法、Kruskal算法等给出的生成树都不是完全随机的。例如,取 <span class="math inline">\(G\)</span> 为 <span class="math inline">\(\mathbb{Z}^2\)</span> 中 <span class="math inline">\(m\times n\)</span> 的网格图,<span class="math inline">\(G\)</span>的任何生成树都是一个迷宫,把背景平面涂黑,把生成树的边涂白,就可以清楚地看到迷宫的结构。迷宫的任何两个房间( 即顶点 ) 可以通过生成树中唯一的路径相连,这样的迷宫叫做完美迷宫。</p><p>DFS 算法 ( 每次将新顶点的顺序打乱再入栈 )倾向于尽可能深地探索整个图,因此得到的迷宫往往包含长且蜿蜒的路径,死角 (即叶节点 ) 是很少的:</p><p><img style="margin:0px auto;display: block" width="500" src="/images/gifmaze/random_dfs.gif"></p><span id="more"></span><p>与之相反,Prim算法由于每次是在当前树上随机添加一个叶节点,因此得到的迷宫往往包含很多死角:</p><p><img style="margin:0px auto;display: block" width="500" src="/images/gifmaze/prim.gif"></p><p>总之从直观上就可以看出这两个算法得到的生成树都不是完全随机的。</p><p>目前最快的生成均匀生成树的算法是 Wilson算法,它借助于擦圈的随机游动来实现。</p><div id="wilson-algo" class="statement sta_wilson___ plain unnumbered"><p><span class="statement-heading"><span class="statement-label">Wilson算法</span>.</span><span class="statement-spah"> </span>设 <span class="math inline">\(G\)</span> 是一个有限简单连通图。</p><ol type="1"><li>任取一个顶点 <span class="math inline">\(r\)</span>,维护一个树<span class="math inline">\(T\)</span>,初始时 <span class="math inline">\(T=\{r\}\)</span>。</li><li>任取一个不属于 <span class="math inline">\(T\)</span> 的顶点 <span class="math inline">\(v\)</span>,从 <span class="math inline">\(v\)</span>出发作图上的随机游动,一边走一边随时擦掉路径中出现的圈 ( 此谓之 looperased random walk ) ,即每当走到一个以前访问过的顶点 <span class="math inline">\(x\)</span>,则两次访问 <span class="math inline">\(x\)</span>之间的路径都被擦掉。按此规则持续行走直到与 <span class="math inline">\(T\)</span> 相遇为止,这时得到一条从 <span class="math inline">\(v\)</span> 到 <span class="math inline">\(T\)</span> 的不含圈的路径 <span class="math inline">\(p\)</span>,把 <span class="math inline">\(p\)</span> 加入到 <span class="math inline">\(T\)</span> 中,将 <span class="math inline">\(T\)</span> 更新为 <span class="math inline">\(T=T\cup p\)</span>。</li><li>重复步骤 2 直到 <span class="math inline">\(T\)</span> 包含 <span class="math inline">\(G\)</span> 的所有顶点,这时 <span class="math inline">\(T\)</span> 是一个服从均匀分布的生成树。</li></ol></div><p>下面是 Wilson 算法的 Javascript演示,你可以随时单击鼠标来重启动画。</p><script type="text/javascript" src="/code/wilson.js"></script><canvas id="wilson" width="600" height="600"></canvas><div id="two-arbitrary"><p>注意 Wilson 算法的描述中有两个<strong>任意</strong>:</p><ol type="1"><li>初始时可以任选一个初始根节点 <span class="math inline">\(r\)</span>。</li><li>每次可以任选一个不属于 <span class="math inline">\(T\)</span>的顶点出发作随机游动。</li></ol></div><p>Wilson 的 <a href="https://dl.acm.org/doi/10.1145/237814.237880">论文</a>中给出的证明相当有技巧性,而且有一些晦涩的部分,我是花了很久才真正理解。本文就来介绍这个证明。</p><h1 id="证明思路">证明思路</h1><p>先不管均匀分布的事情,我们来说明 Wilson 算法以概率 1会在有限时间内结束。</p><div id="algo-success" class="statement proposition plain"><p><span class="statement-heading"><span class="statement-label">命题1.1</span>.</span><span class="statement-spah"> </span>Wilson 算法以概率1 在有限时间内返回一个生成树。</p></div><p><strong>证明</strong>:由于 <span class="math inline">\(G\)</span>有限且连通,所以其上的随机游动是常返的,在算法第 2步中,每次从一个新顶点 <span class="math inline">\(v\)</span>出发的随机游动以概率 1 在有限时间内撞到 <span class="math inline">\(T\)</span>。这样的循环只能执行有限多次 ( 次数以<span class="math inline">\(|V|\)</span> 为上界 ) ,所以算法以概率 1在有限时间内结束。<span class="math inline">\(\blacksquare\)</span></p><p>所以真正有挑战性的地方在于论证得到的生成树服从均匀分布。</p><p>证明的大致想法是这样的:我们构造概率空间 <span class="math inline">\(( \Omega,\mathbb{P} )\)</span> 和映射 <span class="math inline">\(\phi:\Omega\to\mathcal{T}\)</span>,使得它们满足如下的条件:</p><div id="requirements" class="statement simple plain unnumbered"><ol type="1"><li><span class="math inline">\(\phi\)</span> 对几乎处处的 <span class="math inline">\(\omega\in\Omega\)</span> 有定义 ( 不是所有的 <span class="math inline">\(\omega\)</span>都对应一个生成树,但这种例外发生的概率是 0 ) 。</li><li><span class="math inline">\(\phi\)</span> 是满射。 ( 不能漏掉任何树)</li><li>对任何树 <span class="math inline">\(T\in\mathcal{T}\)</span>,其在<span class="math inline">\(\Omega\)</span> 中的原像 <span class="math inline">\(\phi^{-1} ( T )\)</span> 的测度是一个与 <span class="math inline">\(T\)</span> 无关的常数。</li></ol></div><p>一旦找到了这样的概率空间 <span class="math inline">\((\Omega,\mathbb{P} )\)</span> 和映射 <span class="math inline">\(\phi\)</span>,则 <span class="math inline">\(\phi( \omega )\)</span> 以概率 1 是一个生成树,且服从 <span class="math inline">\(\mathcal{T}\)</span> 上的均匀分布。</p><p>构造 <span class="math inline">\(( \Omega,\mathbb{P} )\)</span> 和<span class="math inline">\(\phi\)</span> 的关键,是把 <span class="math inline">\(( \Omega,\mathbb{P} )\)</span>看作一个游戏的系统随机性,Wilson算法看作玩家的一种操作策略,但是这个策略对结果没有影响,即实际上任何游戏策略都会得到相同的结果,从而游戏结果完全由系统随机性<span class="math inline">\(\omega\in\Omega\)</span> 决定,这就是映射<span class="math inline">\(\phi\)</span>!</p><p>直接介绍 Wilson算法背后的游戏可能有点难以理解。作为热身,我们先来看看大家都熟悉的Tetris游戏(俄罗斯方块)。我希望你能从中理解「系统随机性」与「玩家策略」的区别。</p><p>经典的 Tetris 游戏是这样的,系统每次会随机从屏幕顶端落下 <span class="math inline">\(\{I,L,J,O,S,T,Z\}\)</span> 七种四方块 ( tetromino)中的一个。玩家可以在方块下落的过程中移动或者旋转它,尽可能地形成完整的水平行。每当出现完整的水平行时,这些行会被立刻消掉,同时玩家获得一定的分数。玩家的目的是获得尽可能高的分数。</p><p><img style="margin:0px auto;display: block" width="500" src="/images/wilson/tetris.gif"></p><p>Tetris游戏的结果由两个因素决定:系统的随机性和玩家的操作。这里系统的随机性是指每次落下的方块的随机性。</p><p>系统的随机性可以用一个概率空间 <span class="math inline">\((\Omega,\mathbb{P} )\)</span> 来描述:任何样本点 <span class="math inline">\(\omega\in\Omega\)</span> 是一个无穷序列 <span class="math inline">\(\omega=\{X_i\}_{i=1}^\infty\)</span>,其中 <span class="math inline">\(X_i\)</span> 表示第 <span class="math inline">\(i\)</span> 个落下的方块的类型,它来自对集合 <span class="math inline">\(\{I,L,J,O,S,T,Z\}\)</span>的独立且服从均匀分布的采样。<span class="math inline">\(\Omega\)</span>上的概率测度 <span class="math inline">\(\mathbb{P}\)</span>是无穷乘积测度。</p><p>例如,一个样本点 <span class="math inline">\(\omega\)</span>可能是这样的 <span class="math display">\[\omega=\{J, S, I, O, J, I, T,T, S, Z, Z, Z,\ldots\}.\]</span> 即第一个落下的方块是 <span class="math inline">\(J\)</span>,第二个是 <span class="math inline">\(S\)</span>,第三个是 <span class="math inline">\(I\)</span>,等等。</p><p>一旦给定了 <span class="math inline">\(\omega\)</span>,游戏的结果将只依赖于玩家的操作。</p><p>现在我要告诉你,Wilson 算法背后是一个类似 Tetris 的游戏,但又和Tetris 游戏有一个关键不同:Wilson算法的结果只依赖于系统的随机性,不依赖于玩家的操作。换句话说:对给定的<span class="math inline">\(\omega\)</span>,要么玩家的任何操作都会得到同一个生成树;要么任何操作都不能。即<span class="math inline">\(T\)</span> 是由 <span class="math inline">\(\omega\)</span>完全决定的。于是我们有一个确定的映射 <span class="math inline">\(\phi (\omega ) =T\)</span>!并且根据 <a href="#algo-success" title="命题 1.1">命题 1.1</a>, Wilson 算法以概率 1成功得到一个生成树,所以 <span class="math inline">\(\phi\)</span>对几乎处处的 <span class="math inline">\(\omega\)</span>是有定义的!</p><p>下面来具体介绍这个游戏。</p><h1 id="wilson-算法作为游戏策略">Wilson 算法作为游戏策略</h1><p>我们来玩一个叫做回路弹出 ( cycle popping )的游戏。我先介绍这个游戏背后的系统随机性 <span class="math inline">\((\Omega,\mathbb{P} )\)</span>。</p><div id="----------------omega--mathbb-p-------------" class="statement sta_________omega__mathbb_p_______ plain unnumbered"><p><span class="statement-heading"><span class="statement-label">概率空间 <span class="math inline">\((\Omega,\mathbb{P} )\)</span> 的构造</span>.</span></p><ol type="1"><li>固定一个顶点 <span class="math inline">\(r\)</span>。对每个 <span class="math inline">\(v\ne r\)</span>,定义栈 <span class="math inline">\(S_v=\{S_{v,1},S_{v,2},\ldots\}\)</span>。<span class="math inline">\(S_v\)</span> 的长度是无穷,其元素 <span class="math inline">\(S_{v,i}\)</span> 都是来自 <span class="math inline">\(v\)</span>的邻居的均匀采样。所有栈元素都是独立的。顶点 <span class="math inline">\(r\)</span> 的栈是空栈:<span class="math inline">\(S_r=\emptyset\)</span>。</li><li>概率空间 <span class="math inline">\(\Omega\)</span> 是所有栈 <span class="math inline">\(\{S_v\mid v\ne r\}\)</span>的所有可能的状态组成的集合。这是一个无穷离散的概率空间,其上的测度 <span class="math inline">\(\mathbb{P}\)</span> 为乘积测度。</li></ol></div><p>为了方便,我们称 <span class="math inline">\(S_v\)</span> 的第 <span class="math inline">\(i\)</span> 个元素 <span class="math inline">\(S_{v,i}\)</span> 的颜色是 <span class="math inline">\(i\)</span>。</p><p>在任何时刻,这些栈 <span class="math inline">\(\{S_v,v\ner\}\)</span> 的栈顶元素都定义了一个有向图 <span class="math inline">\(\overrightarrow{G}_S\)</span>:在 <span class="math inline">\(\overrightarrow{G}_S\)</span> 中 <span class="math inline">\(v\rightarrow u\)</span> 当且仅当 <span class="math inline">\(u\)</span> 是 <span class="math inline">\(S_v\)</span> 的栈顶元素。每个 <span class="math inline">\(v\ne r\)</span> 的出度都恰好是 1,顶点 <span class="math inline">\(r\)</span> 的出度是 0。于是若 <span class="math inline">\(\overrightarrow{G}_S\)</span>不含回路的话它就是一个以 <span class="math inline">\(r\)</span>为根的生成树。</p><div id="------------------" class="statement sta_______ plain unnumbered"><p><span class="statement-heading"><span class="statement-label">回路弹出游戏</span>.</span><span class="statement-spah"> </span>给定栈的一个状态 <span class="math inline">\(\omega\in\Omega\)</span>,<span class="math inline">\(\overrightarrow{G}_S\)</span> 是 <span class="math inline">\(\omega\)</span> 对应的栈顶图,若 <span class="math inline">\(\overrightarrow{G}_S\)</span>不含回路的话则它已经是一个生成树,游戏结束;否则设 <span class="math inline">\(C\)</span> 是 <span class="math inline">\(\overrightarrow{G}_S\)</span>中的一个回路,我们可以将其「弹出」:对每个 <span class="math inline">\(v\in C\)</span>,弹出 <span class="math inline">\(S_v\)</span> 的栈顶元素 ( 于是若当前 <span class="math inline">\(S_v\)</span> 的栈顶元素为 <span class="math inline">\(S_{v,i}\)</span>,则弹出 <span class="math inline">\(S_{v,i}\)</span> 以后栈顶元素变为 <span class="math inline">\(S_{v,i+1}\)</span> ) ,这样得到更新的 <span class="math inline">\(\overrightarrow{G}_S\)</span>。玩家每次可以任选<span class="math inline">\(\overrightarrow{G}_S\)</span>中的一个回路并将其弹出。如果玩家能够经过有限多次弹出操作后使得 <span class="math inline">\(\overrightarrow{G}_S\)</span> 中不含任何回路,即<span class="math inline">\(\overrightarrow{G}_S\)</span>是一个生成树,则玩家获胜。</p></div><p>在回路弹出游戏中,玩家能做的就是每次选择一个需要弹出的回路,别的什么也做不了。游戏开始之前,<span class="math inline">\(\overrightarrow{G}_S\)</span> 中所有顶点的颜色都是1,但是随着游戏的进行,<span class="math inline">\(\overrightarrow{G}_S\)</span>会变得「五颜六色」。一个回路中可以包含不同颜色的顶点。</p><div id="wilson-------------------------" class="statement sta_wilson_________ plain unnumbered"><p><span class="statement-heading"><span class="statement-label">Wilson算法作为游戏策略</span>.</span><span class="statement-spah"></span>每次任选一个不属于 <span class="math inline">\(T\)</span> 的顶点<span class="math inline">\(v\)</span>,从 <span class="math inline">\(v\)</span> 出发按照 <span class="math inline">\(\overrightarrow{G}_S\)</span>的边的指引来搜索下一个要弹出的回路。</p></div><p>树 <span class="math inline">\(T\)</span>的作用是维护那些已经完全确定下来、必然属于最终生成树的那些边。这是因为,<span class="math inline">\(T\subset\overrightarrow{G}_S\)</span> 始终是一个以<span class="math inline">\(r\)</span> 为根节点的有向树,从 <span class="math inline">\(T\)</span> 中的任何顶点出发沿着 <span class="math inline">\(\overrightarrow{G}_S\)</span>的有向边都会走到根节点 <span class="math inline">\(r\)</span>。而 <span class="math inline">\(r\)</span> 是个死胡同 ( <span class="math inline">\(r\)</span> 的出度是 0 ) ,所以 <span class="math inline">\(T\)</span> 中的顶点不可能属于任何回路。</p><p>我们前面剧透过,回路弹出游戏的结果不依赖于玩家的操作。我们把这个事实的证明放在后面,先承认它是正确的,于是我们可以定义映射<span class="math inline">\(\phi\)</span>:</p><div id="-phi----------" class="statement sta__phi____ plain unnumbered"><p><span class="statement-heading"><span class="statement-label"><span class="math inline">\(\phi\)</span> 的构造</span>.</span><span class="statement-spah"> </span>设使用 Wilson 算法对 <span class="math inline">\(\omega\)</span> 执行操作以后得到的生成树为 <span class="math inline">\(T\)</span>,定义 <span class="math inline">\(\phi( \omega ) =T\)</span>。</p></div><div class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理2.1</span>.</span><span class="statement-spah"> </span><span class="math inline">\(T\)</span> 服从所有生成树组成的集合 <span class="math inline">\(\mathcal{T}\)</span> 上的均匀分布。</p></div><p>这个结论解释了为什么 Wilson 算法中的 <a href="#two-arbitrary">两个任意</a> 对最终结果是没有影响的。</p><p><strong>证明</strong>:我们来计算如下事件的概率:依次弹出回路 <span class="math inline">\(\mathcal{C}= ( C_1,\ldots,C_n )\)</span>后得到的生成树是 <span class="math inline">\(T\)</span>。注意 <span class="math inline">\(\mathcal{C}\)</span> 和 <span class="math inline">\(T\)</span>的顶点必然<strong>无缝隙</strong>地填满栈 <span class="math inline">\(\{S_v\}\)</span> 的上面的部分,所以这个概率就是<span class="math inline">\(\mathcal C\)</span> 和 <span class="math inline">\(T\)</span> 中的边各自指向正确位置的概率: <span class="math display">\[\mathbb{P} ( \mathcal{C},T ) =\prod_{e\in\mathcal{C}\cup T} p_e=\Phi ( T ) \cdot \Phi ( \mathcal{C} ).\]</span> 这里 <span class="math inline">\(\Phi ( \bullet )\)</span>返回集合 <span class="math inline">\(\bullet\)</span>中所有边的概率的乘积。</p><p>设 <span class="math inline">\(\mathcal{C}_T\)</span> 是所有可能得到<span class="math inline">\(T\)</span> 的那些 <span class="math inline">\(\mathcal{C}\)</span> 组成的集合,在上式两边对<span class="math inline">\(\mathcal{C}_T\)</span> 求和,则 <span class="math display">\[\mathbb{P} ( T ) =\left (\sum_{\mathcal{C}\in\mathcal{C}_T}\Phi ( \mathcal C ) \right ) \cdot\Phi( T ) .\]</span> 然而 <span class="math inline">\(\mathcal{C}_T\)</span>是一个与 <span class="math inline">\(T\)</span>无关的集合,这是因为在给定 <span class="math inline">\(\mathcal{C}\)</span> 后,任何生成树 <span class="math inline">\(T\)</span> 都有可能出现 ( 解释见后面 ) ,因此<span class="math display">\[\mathbb{P} ( T ) ={\rm const}\cdot \Phi ( T) .\]</span> 而 <span class="math inline">\(\Phi ( T ) =\prod\limits_{v\ne r} ( 1/d_v )\)</span> 是与 <span class="math inline">\(T\)</span> 无关的量,从而 <span class="math inline">\(\mathbb{P} ( T )\)</span> 是常数,这就证明了 <span class="math inline">\(\phi\)</span> 满足 <a href="#requirements" title="条件 3">条件 3</a> 。</p><p>为什么给定 <span class="math inline">\(\mathcal{C}\)</span> 以后任何<span class="math inline">\(T\)</span>都可能出现?打个比方,想象一个向弹夹里面压子弹的过程:把树 <span class="math inline">\(T\)</span> 放在栈顶,然后依次用 <span class="math inline">\(C_n,\ldots,C_1\)</span> 将 <span class="math inline">\(T\)</span> 往下压,得到一个栈的状态 <span class="math inline">\(\{S_v\}\)</span>,对这个状态执行回路弹出,显然依次弹出的就是<span class="math inline">\(C_1,\ldots,C_n\)</span>,最终得到的树是<span class="math inline">\(T\)</span>。这顺便也说明了 <span class="math inline">\(\phi\)</span> 是满射的。</p><p>现在 <span class="math inline">\(\phi\)</span> 满足前面提到的全部 <a href="#requirements" title="三个条件">三个条件</a> ,这就证明了 Wilson算法的正确性。</p><h1 id="游戏结果与策略无关">游戏结果与策略无关</h1><p>最后我们证明最关键的部分:Wilson算法的结果与每次选择弹出的回路无关。</p><p>假设有若干玩家分别玩回路弹出的游戏,每个人采取的策略是不同的。我们想知道,对给定的系统随机性<span class="math inline">\(\omega\)</span>,这些玩家都能获胜吗?他们最终得到的生成树一样吗?需要的操作次数相同吗?</p><p>答案是:不管这些玩家采取怎样的策略,只有两种可能的结果出现:</p><ol type="1"><li>所有人都不能获胜。</li><li>所有人都能获胜,而且每个人使用的操作次数也相同,最终得到的栈顶图<span class="math inline">\(\overrightarrow{G}_S\)</span>也相同。不仅如此,每个人弹出的回路组成的集合 <span class="math inline">\(\{C_1,\ldots,C_n\}\)</span>也都是相同的。注意这里的回路 <span class="math inline">\(C_i\)</span>是带有颜色标记的,两个回路相同不仅要求包含的顶点相同,也要求对应顶点的颜色相同。仅仅弹出的顺序可能不同。</li></ol><p>我们只要证明如下的结论即可:</p><div class="unnumbered statement lemma-unnumbered plain"><p><span class="statement-heading"><span class="statement-label">引理</span>.</span><span class="statement-spah"></span>对任一栈状态 <span class="math inline">\(\omega\in\Omega\)</span>,若玩家 <span class="math inline">\(A\)</span> 可以经过 <span class="math inline">\(n\)</span> 次操作后获胜,其弹出的回路依次为 <span class="math inline">\(C_1,\ldots,C_n\)</span>,则不论玩家 <span class="math inline">\(B\)</span> 的策略如何,其必然也经过 <span class="math inline">\(n\)</span> 次操作后获胜,其弹出的回路集合 <span class="math inline">\(\{D_1,\ldots,D_n\}\)</span> 与 <span class="math inline">\(\{C_1,\ldots,C_n\}\)</span> 是相同的,即适当重排<span class="math inline">\(\{D_1,\ldots,D_n\}\)</span> 后有 <span class="math inline">\(D_i=C_i\)</span>。</p></div><p><strong>证明</strong>:对玩家 <span class="math inline">\(A\)</span>的操作次数 <span class="math inline">\(n\)</span> 归纳。<span class="math inline">\(n=0\)</span> 时结论显然成立 ( 双方均无任何操作 ),下面设 <span class="math inline">\(n\geq1\)</span> 且结论对所有小于<span class="math inline">\(n\)</span> 的情形成立。</p><p>设 <span class="math inline">\(B\)</span> 第一次弹出的回路是 <span class="math inline">\(D_1\)</span>,如果 <span class="math inline">\(C_1=D_1\)</span> 则这一步操作后 <span class="math inline">\(A,B\)</span> 到达了相同的状态,而 <span class="math inline">\(A\)</span> 可以继续经过 <span class="math inline">\(n-1\)</span> 次操作后获胜,于是根据归纳假设 <span class="math inline">\(B\)</span> 也一定经过 <span class="math inline">\(n-1\)</span> 次操作获胜且后续操作 <span class="math inline">\(\{D_2,\ldots,D_n\}=\{C_2,\ldots,C_n\}\)</span>。</p><p>如果 <span class="math inline">\(C_1\ne D_1\)</span>,我们断言 <span class="math inline">\(C_1\)</span> 和 <span class="math inline">\(D_1\)</span> 没有公共的顶点。否则若 <span class="math inline">\(v\in C_1\cap D_1\)</span>,由于第一次操作时 <span class="math inline">\(C_1,D_1\)</span> 属于同一个栈顶图中,以及 <span class="math inline">\(v\)</span> 的出度是 1,所以 <span class="math inline">\(v\)</span> 在 <span class="math inline">\(G_S\)</span> 中的后继 <span class="math inline">\(v_1\)</span> 也同时属于 <span class="math inline">\(C_1\)</span> 和 <span class="math inline">\(D_1\)</span>,进而 <span class="math inline">\(v_1\)</span> 的后继 <span class="math inline">\(v_2\)</span> 也是如此,这样一直下去回到 <span class="math inline">\(v\)</span> 就会有 <span class="math inline">\(C_1=D_1\)</span>,矛盾。</p><p>既然 <span class="math inline">\(C_1\)</span> 和 <span class="math inline">\(D_1\)</span> 没有相同顶点,那说明不论先弹 <span class="math inline">\(C_1\)</span> 后弹<span class="math inline">\(D_1\)</span>,或是先弹 <span class="math inline">\(D_1\)</span> 后弹 <span class="math inline">\(C_1\)</span>,得到的栈顶图是一样的。</p><p>接下来的论述是钻石引理 ( diamond lemma )的典型操作:我们引入两个新玩家 <span class="math inline">\(A'\)</span> 和 <span class="math inline">\(B'\)</span>:<span class="math inline">\(A'\)</span> 的前两步操作是先弹出 <span class="math inline">\(C_1\)</span> 后弹出 <span class="math inline">\(D_1\)</span>,<span class="math inline">\(B'\)</span> 的前两步操作是先弹出 <span class="math inline">\(D_1\)</span> 后弹出 <span class="math inline">\(C_1\)</span>。</p><ol type="1"><li><span class="math inline">\(A\)</span> 和 <span class="math inline">\(A'\)</span> 第一步操作相同,因此由归纳假设<span class="math inline">\(A'\)</span> 可以经过 <span class="math inline">\(n-2\)</span> 步后获胜;</li><li><span class="math inline">\(A'\)</span> 和 <span class="math inline">\(B'\)</span> 前两步操作后到达相同的状态,而已知<span class="math inline">\(A'\)</span> 可以在 <span class="math inline">\(n-2\)</span> 步后获胜,所以由归纳假设 <span class="math inline">\(B'\)</span> 也可以在 <span class="math inline">\(n-2\)</span> 步后获胜;</li><li><span class="math inline">\(B'\)</span> 和 <span class="math inline">\(B\)</span> 第一步操作相同,而已知 <span class="math inline">\(B'\)</span> 可以在 <span class="math inline">\(n-1\)</span> 步后获胜,所以由归纳假设 <span class="math inline">\(B\)</span> 也可以在 <span class="math inline">\(n-1\)</span> 步后获胜。</li></ol><p><span class="math inline">\(A,B,A',B'\)</span>弹出的回路集合相同是显然的。</p><p>至此我们就说明了 <span class="math inline">\(\phi\)</span>的定义是合理的,它是一个确定的映射。</p><p>对没有接触过钻石引理的读者,我这个论述比 Wilson的原证明的论述要繁琐,但是这个角度更本质地揭示了为什么游戏的结果不依赖于具体的策略。</p>]]></content>
<summary type="html">
<div class="unnumbered statement question-unnumbered definition">
<p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>给定一个有限、无向的连通图 <span class="math inline">\(G= ( V,E )\)</span>,设 <span class="math inline">\(\mathcal{T}\)</span> 是 <span class="math inline">\(G\)</span> 的所有生成树组成的集合,怎样在 <span class="math inline">\(\mathcal{T}\)</span>
中按照均匀分布进行采样?即设计一个算法,能够随机地给出 <span class="math inline">\(G\)</span> 的一个生成树,并且 <span class="math inline">\(\mathcal{T}\)</span>
中每个生成树被取到的概率是相等的。</p>
</div>
<p>常见的生成树算法如 DFS/BFS 算法、Prim 算法、Kruskal
算法等给出的生成树都不是完全随机的。例如,取 <span class="math inline">\(G\)</span> 为 <span class="math inline">\(\mathbb{Z}^2\)</span> 中 <span class="math inline">\(m\times n\)</span> 的网格图,<span class="math inline">\(G\)</span>
的任何生成树都是一个迷宫,把背景平面涂黑,把生成树的边涂白,就可以清楚地看到迷宫的结构。迷宫的任何两个房间
( 即顶点 ) 可以通过生成树中唯一的路径相连,这样的迷宫叫做完美迷宫。</p>
<p>DFS 算法 ( 每次将新顶点的顺序打乱再入栈 )
倾向于尽可能深地探索整个图,因此得到的迷宫往往包含长且蜿蜒的路径,死角 (
即叶节点 ) 是很少的:</p>
<p><img style="margin:0px auto;display: block" width="500" src="/images/gifmaze/random_dfs.gif"></p></summary>
<category term="完美采样" scheme="https://pywonderland.com/categories/%E5%AE%8C%E7%BE%8E%E9%87%87%E6%A0%B7/"/>
</entry>
<entry>
<title>Coxeter element</title>
<link href="https://pywonderland.com/coxeter-element/"/>
<id>https://pywonderland.com/coxeter-element/</id>
<published>2013-10-10T16:00:00.000Z</published>
<updated>2024-11-15T16:15:39.449Z</updated>
<content type="html"><![CDATA[<p>如果你对 Lie 代数有所了解的话,相信很大概率你会见过下面的图案: (参考维基百科的 <a href="https://en.wikipedia.org/wiki/Lie_algebra">Liealgebra 词条</a> )</p><p><img style="margin:0px auto;display: block" src="/images/coxeter-element/e8.svg" width="350"></p><p>它展示的是 Lie 代数 <span class="math inline">\(E_8\)</span>的根系图。<span class="math inline">\(E_8\)</span> 的根系由 8 维欧式空间<span class="math inline">\(\mathbb{R}^8\)</span> 中的 240个向量组成,将这 240 个向量投影到一个特殊的 2 维平面 ( 叫做 Coxeter 平面) 上就会呈现出一个具有旋转对称的图案。在上图中可以看到,240个投影点分布在 8 个圆周上,每个圆周包含 30个均匀分布的点,整个图案在角度为 <span class="math inline">\(\frac{2\pi}{30}\)</span> 的旋转下是不变的。<span class="math inline">\(h=30\)</span> 正是 <span class="math inline">\(E_8\)</span> 的 Coxeter 数。</p><p>本文目的是介绍 Coxeter 元的一些基础知识,然后教大家怎样在 Python中编写一个程序绘制上面的投影图案。我主要参考了 <span class="citation" data-cites="Humphreys90">(<a href="#ref-Humphreys90" role="doc-biblioref">Humphreys 1990</a>)</span> 和 <span class="citation" data-cites="CasselmanCoxeterElement">(<a href="#ref-CasselmanCoxeterElement" role="doc-biblioref">Casselman2017</a>)</span>。虽然这里面涉及的数学并不复杂,但是真正动手编程实现的时候会有一些魔鬼藏在细节中,而这些细节是仅凭念书很难发现的。</p><p>本文的代码在 <a href="https://github.com/neozhaoliang/pywonderland/raw/master/src/misc/E8.py">Github上</a> 。David Madore 也有一个很棒的 <a href="http://www.madore.org/~david/math/e8w.html">交互式网页</a>可以绘制 <span class="math inline">\(E_8\)</span>的多种不同风格的图案。</p><span id="more"></span><h1 id="coxeter-元">Coxeter 元</h1><p>在本文中,<span class="math inline">\((W,S)\)</span>总代表一个有限不可约 Coxeter 群,其中 <span class="math inline">\(|S|=n\)</span>。<span class="math inline">\(S\)</span> 中的生成元满足关系</p><ol type="1"><li>对任何 <span class="math inline">\(s\in S\)</span> 有 <span class="math inline">\(s^2=1\)</span>。</li><li>对任何 <span class="math inline">\(s_i,s_j\in S\)</span> 有 <span class="math inline">\((s_is_j)^{m_{ij}}=1\)</span>。其中 <span class="math inline">\(m_{ij}\geq 2\)</span> 是正整数。</li></ol><p>设 <span class="math inline">\(V\)</span> 是 <span class="math inline">\(n\)</span> 维实向量空间,<span class="math inline">\(\Delta=\{\alpha_1,\ldots,\alpha_n\}\)</span> 是<span class="math inline">\(V\)</span> 的一组基,<span class="math inline">\(\Delta\)</span> 叫做一组单根。定义 <span class="math inline">\(V\)</span> 上的内积 <span class="math inline">\(\bullet\)</span> 如下: <span class="math display">\[\alpha_i\bullet\alpha_j=-\cos\frac{\pi}{m_{ij}}.\]</span><span class="citation" data-cites="Humphreys90">(<a href="#ref-Humphreys90" role="doc-biblioref">Humphreys 1990, secs.6.2–6.4</a>)</span> 中证明了 <span class="math inline">\(( W,S)\)</span> 是有限群当且仅当内积 <span class="math inline">\(\bullet\)</span> 是正定的。</p><p>矩阵 <span class="math inline">\(A= ( a_{ij} )_{1\leq i,j\leq n}= (\alpha_i\bullet\alpha_j )_{1\leq i,j\leq n}\)</span> 叫做 Cartan矩阵。</p><p>规定每个生成元 <span class="math inline">\(s_i\in S\)</span> 在 <span class="math inline">\(V\)</span> 上的作用为 <span class="math display">\[s_i ( v ) = v - 2 ( v\bullet\alpha_i )\alpha_i,\quad v\in V.\]</span> 即 <span class="math inline">\(s_i\)</span> 是关于以 <span class="math inline">\(\alpha_i\)</span>为法向量的超平面的反射。这个作用将 <span class="math inline">\(W\)</span> 同构地映射为 <span class="math inline">\(O ( V )\)</span> 的一个有限反射子群。</p><div class="statement definition plain"><p><span class="statement-heading"><span class="statement-label">定义1.1</span>.</span><span class="statement-spah"> </span>设 <span class="math inline">\(\{i_1,i_2,\ldots,i_n\}\)</span> 是集合 <span class="math inline">\(\{1,2,\ldots,n\}\)</span> 的一个置换,乘积 <span class="math inline">\(s_{i_1}s_{i_2}\cdots s_{i_n}\)</span> 叫做<strong>Coxeter 元</strong>。</p></div><p>换句话说,Coxeter 元就是把 <span class="math inline">\(W\)</span>的生成元 <span class="math inline">\(s_1,\ldots,s_n\)</span>按照任意顺序排列,然后相乘得到的群元素。</p><div id="coxeter-conjugate" class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理1.2</span> <span class="statement-info">(<span class="citation" data-cites="Humphreys90">(<a href="#ref-Humphreys90" role="doc-biblioref">Humphreys 1990, sec.3.16</a>)</span>)</span>.</span><span class="statement-spah"></span>所有 Coxeter 元都是互相共轭的。</p></div><p>由于 Coxeter元都是共轭的,所以它们有相同的阶、特征多项式和特征值。任一 Coxeter元的阶叫做 <span class="math inline">\(W\)</span> 的 <strong>Coxeter数</strong>,记作 <span class="math inline">\(h\)</span>。</p><p>设 <span class="math inline">\(\gamma\)</span> 是一个 Coxeter元,由于 <span class="math inline">\(\gamma\)</span> 满足 <span class="math inline">\(\gamma^h=1\)</span>,所以 <span class="math inline">\(\gamma\)</span> 的特征值必然都是 <span class="math inline">\(h\)</span>- 次单位根,而且复特征值成对共轭出现:<span class="math display">\[\{\zeta^{m_1},\ldots,\zeta^{m_n},0\leqm_i<h\}.\]</span> 其中 <span class="math inline">\(\zeta\)</span>是本原 <span class="math inline">\(h\)</span>- 次单位根。在 <span class="citation" data-cites="Humphreys90">(<a href="#ref-Humphreys90" role="doc-biblioref">Humphreys 1990, sec. 3.16</a>)</span> 中证明了 1不可能是 <span class="math inline">\(\gamma\)</span>的特征值,所以实际上每个指数 <span class="math inline">\(1\leqm_i<h\)</span>。<span class="math inline">\(\gamma\)</span>如果有实特征值的话只可能是 <span class="math inline">\(-1\)</span>( 对应<span class="math inline">\(\zeta^{h/2}\)</span> ) 。</p><p>我们选择一个特殊的 Coxeter 元如下:任取 <span class="math inline">\(\Gamma\)</span> 的一个顶点作为 <span class="math inline">\(s_n\)</span>,将 <span class="math inline">\(\Gamma\)</span> 的顶点按照与 <span class="math inline">\(s_n\)</span> 的图距离分成两个不相交的集合 <span class="math inline">\(\Gamma=I\sqcup J\)</span>:<span class="math inline">\(I\)</span> 由所有与 <span class="math inline">\(s_n\)</span> 的距离为偶数的顶点组成(包含 <span class="math inline">\(s_n\)</span>);<span class="math inline">\(J\)</span> 由所有与 <span class="math inline">\(s_n\)</span> 的距离为奇数的顶点组成。于是 <span class="math inline">\(I\)</span> 中的顶点两两不相邻,从而 <span class="math inline">\(\{s_i,i\in I\}\)</span> 中的生成元两两交换。<span class="math inline">\(J\)</span> 也是如此。记 <span class="math display">\[x=\prod_{i\in I}s_i,\quad y=\prod_{j\inJ}s_j.\]</span> 取 Coxeter 元 <span class="math inline">\(\gamma=xy\)</span>。我们下面对 <span class="math inline">\(\gamma\)</span> 进行分析。</p><h1 id="coxeter-平面">Coxeter 平面</h1><p>设 <span class="math inline">\(\{\omega_i\}_{i=1}^n\)</span> 是 <span class="math inline">\(\{\alpha_i\}_{i=1}^n\)</span> 在内积 <span class="math inline">\(\bullet\)</span> 下的对偶基: <span class="math display">\[(\alpha_i\bullet\omega_j)=\delta_{ij}.\]</span><span class="math inline">\({\bf A}\)</span> 是把每个 <span class="math inline">\(\omega_i\)</span> 映射为 <span class="math inline">\(\alpha_i\)</span> 的线性变换: <span class="math display">\[{\bf A}\omega_i=\alpha_i,\quad \forall 1\leqi\leq n.\]</span> 则 <span class="math inline">\({\bf A}\)</span> 在<span class="math inline">\(\{\omega_i\}_{i=1}^n\)</span>这组基下的矩阵就是 Cartan 矩阵 <span class="math inline">\(A\)</span>。</p><p>在 <span class="citation" data-cites="Humphreys90">(<a href="#ref-Humphreys90" role="doc-biblioref">Humphreys 1990, sec.2.6</a>)</span> 中证明了矩阵 <span class="math inline">\(A\)</span>的极小特征值 <span class="math inline">\(c>0\)</span> 的重数是1,并且对应的特征向量 <span class="math inline">\({\bfc}=(c_1,\ldots,c_n)\)</span> 的所有分量都是正的。于是 <span class="math display">\[{\bf A}\sum_{i=1}^nc_i\omega_i =c\left(\sum_{i=1}^nc_i\omega_i\right).\]</span> 特征向量 <span class="math inline">\(\sum_{i=1}^nc_i\omega_i\)</span> 可以写成两个向量<span class="math inline">\(\lambda,\mu\)</span> 的和,其中 <span class="math display">\[\lambda=\sum_{i\in I} c_i\omega_i,\quad\mu=\sum_{j\in J}c_j\omega_j.\]</span> <span class="citation" data-cites="Humphreys90">(<a href="#ref-Humphreys90" role="doc-biblioref">Humphreys 1990, sec. 3.17</a>)</span> 中证明了<span class="math inline">\(\lambda,\mu\)</span> 张成一个二维子空间<span class="math inline">\(P\)</span>,<span class="math inline">\(x\)</span> 和 <span class="math inline">\(y\)</span> 限制在 <span class="math inline">\(P\)</span> 上分别是保持直线 <span class="math inline">\(\mathbb{R}\mu\)</span> 和 <span class="math inline">\(\mathbb{R}\lambda\)</span> 不动的反射,从而 <span class="math inline">\(\gamma=xy\)</span> 限制在 <span class="math inline">\(P\)</span> 上是一个旋转,并且这个旋转的角度就是<span class="math inline">\(2\pi/h\)</span>。由于 <span class="math inline">\(\gamma\)</span> 置换根系 <span class="math inline">\(\Phi\)</span>,所以如果我们把 <span class="math inline">\(\Phi\)</span> 投影到 <span class="math inline">\(P\)</span> 上,就会看到一个具有 <span class="math inline">\(h\)</span> 阶旋转对称性的图案。</p><p>不过直接使用上面 <span class="math inline">\(\lambda,\mu\)</span>的定义来计算 <span class="math inline">\(P\)</span>是很不方便的,因为其中涉及了对偶基 <span class="math inline">\(\{\omega_i\}\)</span>。我们可以绕开对偶基的计算,这一点其实隐藏在<span class="citation" data-cites="Humphreys90">(<a href="#ref-Humphreys90" role="doc-biblioref">Humphreys 1990,78</a>)</span> 中,那里证明了 <span class="math display">\[\begin{aligned}(c-1)\mu+\lambda &=\sum_{i\in I}c_i\alpha_i,\\(c-1)\lambda+\mu &=\sum_{j\in J}c_j\alpha_j.\end{aligned}\]</span></p><p>由于 <span class="math inline">\(c\ne 1\)</span>(1 不是 Coxeter元的特征值),上式告诉我们 <span class="math display">\[P=\mathop{\mathrm{span}}\{\lambda,\,\mu\}=\mathop{\mathrm{span}}\left\{\sum_{i\inI}c_i\alpha_i,\,\sum_{j\inJ}c_j\alpha_j\right\}.\tag{$\ast$}\label{eq:alpha}\]</span>所以我们完全可以通过特征向量 <span class="math inline">\({\bfc}\)</span> 和 <span class="math inline">\(\Delta\)</span> 得出 <span class="math inline">\(P\)</span> 的一组基。</p><h1 id="进一步改进">进一步改进</h1><p>如果你去看 <a href="https://github.com/neozhaoliang/pywonderland/blob/master/src/misc/E8.py">Github代码</a>的话,会发现那里并不是完全按上面的逻辑写的。这是怎么回事呢?</p><p>前面的计算有个美中不足之处,就是我们需要显式地将 <span class="math inline">\(S\)</span> 的划分为两个不相交的子集 <span class="math inline">\(S=I\sqcup J\)</span>,使得 <span class="math inline">\(I,J\)</span>各自的生成元之间互相交换。这一步是可以避免的,下面的方法我是从 <span class="citation" data-cites="CasselmanCoxeterElement">(<a href="#ref-CasselmanCoxeterElement" role="doc-biblioref">Casselman2017</a>)</span> 中学到的。</p><div class="statement proposition plain"><p><span class="statement-heading"><span class="statement-label">命题3.1</span> <span class="statement-info">(<span class="citation" data-cites="CasselmanCoxeterElement">(<a href="#ref-CasselmanCoxeterElement" role="doc-biblioref">Casselman 2017,lemma. 3.3</a>)</span>)</span>.</span><span class="statement-spah"></span><span class="math inline">\(2I + \gamma + \gamma^{-1}= ( 2I-A)^2\)</span>。</p></div><p>根据这个结论,如果 <span class="math inline">\(V_s,V_{\bar{s}}\)</span> 分别是 <span class="math inline">\(\gamma\)</span> 的一对共轭的复特征值 <span class="math inline">\(s=e^{i\theta}\)</span> 和 <span class="math inline">\(\bar{s}=e^{-i\theta}\)</span> 对应的特征子空间,记<span class="math inline">\(U=V_s\oplus V_{\bar{s}}\)</span>,则对 <span class="math inline">\(v\in U\)</span> 有 <span class="math display">\[(2I-A )^2 ( v ) = ( 2+e^{i\theta}+e^{-i\theta} ) v=4\cos^2\frac{\theta}{2} v.\]</span> 即 <span class="math inline">\(U\)</span> 是 <span class="math inline">\(( 2I-A)^2\)</span> 的特征值为 <span class="math inline">\(4\cos^2\frac{\theta}{2}\)</span>的特征子空间。</p><p>我们想把这个结论中的平方去掉,即证明 <span class="math inline">\(U\)</span> 是 <span class="math inline">\(2I-A\)</span> 的特征值为 <span class="math inline">\(\pm 2\cos\frac{\theta}{2}\)</span>的特征子空间的直和。</p><div id="u-pm" class="statement proposition plain"><p><span class="statement-heading"><span class="statement-label">命题3.2</span>.</span><span class="statement-spah"> </span><span class="math inline">\(U=U_+\oplus U_-\)</span>,其中 <span class="math inline">\(U_{\pm}\)</span> 分别是 <span class="math inline">\(2I-A\)</span> 的 <span class="math inline">\(\pm2\cos\frac{\theta}{2}\)</span> 特征子空间,并且<span class="math inline">\(\dim U_+=\dim U_-\)</span>。</p></div><p><strong>证明</strong>:显然 <span class="math inline">\(U_+\oplusU_-\subseteq U\)</span>。下面证明反向包含。</p><p><span class="math inline">\(U\)</span> 当然是 <span class="math inline">\(2I-A\)</span> 的不变子空间。由于 <span class="math inline">\(2I-A\)</span> 是可对角化的,从而 <span class="math inline">\(U\)</span> 是 <span class="math inline">\(2I-A\)</span> 的特征子空间的直和。显然 <span class="math inline">\(2I-A\)</span> 在 <span class="math inline">\(U\)</span> 上的特征值只有可能是 <span class="math inline">\(\pm 2\cos\frac{\theta}{2}\)</span>,从而 <span class="math inline">\(U\subseteq U_+\oplus U_-\)</span>,即 <span class="math inline">\(U=U_+\oplus U_-\)</span>。</p><p>为了说明 <span class="math inline">\(\dim U_+=\dimU_-\)</span>,我们需要下面的引理:</p><div id="same-characteristic" class="statement lemma plain"><p><span class="statement-heading"><span class="statement-label">引理3.3</span> <span class="statement-info">(<span class="citation" data-cites="CasselmanCoxeterElement">(<a href="#ref-CasselmanCoxeterElement" role="doc-biblioref">Casselman 2017,lemma. 3.5</a>)</span>)</span>.</span><span class="statement-spah"></span><span class="math inline">\(2I-A\)</span> 和 <span class="math inline">\(A-2I\)</span> 有同样的特征多项式。</p></div><p>这个引理告诉我们,若 <span class="math inline">\(\lambda\)</span> 是<span class="math inline">\(A-2I\)</span> 的特征值,则 <span class="math inline">\(-\lambda\)</span>也是,并且二者对应的特征子空间的维数相同(注意 <span class="math inline">\(A-2I\)</span> 是可对角化的)。于是 <span class="math inline">\(\dim U_+=\dim U_-\)</span>。<span class="math inline">\(\blacksquare\)</span>。</p><p>现在我们抛弃 <span class="math inline">\(2I-A\)</span>,注意 <span class="math inline">\(U\)</span> 是 <span class="math inline">\(A\)</span> 的特征值为 <span class="math inline">\(2\pm2\cos\frac{\theta}{2}\)</span>的特征子空间的直和。</p><p>一般来说 <span class="math inline">\(U\)</span> 的维数未必是 2,所以<span class="math inline">\(U\)</span> 未必是一个平面。但是假设 <span class="math inline">\(c=2-2\cos\frac{\theta_0}{2}\)</span> 是 <span class="math inline">\(A\)</span> 的极小特征值,我们知道 <span class="math inline">\(c\)</span> 的重数是 1,从而 <span class="math inline">\(A\)</span> 的极大特征值 <span class="math inline">\(d=2+2\cos\frac{\theta_0}{2}\)</span> 的重数也是1,这时 <span class="math inline">\(U\)</span> 是二维平面。<span class="math inline">\(\gamma\)</span> 在 <span class="math inline">\(U\)</span> 上的作用是角度为 <span class="math inline">\(\theta_0\)</span> 的旋转。这个 <span class="math inline">\(U\)</span> 就是 Coxeter 平面 <span class="math inline">\(P\)</span>。</p><p>于是计算 <span class="math inline">\(P\)</span> 归结为计算线性变换<span class="math inline">\({\bf A}\)</span> 在 <span class="math inline">\(\{\omega_i\}_{i=1}^n\)</span>这组基下极小/极大特征值对应的特征向量。设 <span class="math inline">\((c_1,\ldots,c_n)\)</span> 和 <span class="math inline">\((d_1,\ldots,d_n)\)</span> 分别是矩阵 <span class="math inline">\(A\)</span> 对应 <span class="math inline">\(c,d\)</span> 的特征向量,不难验证 <span class="math inline">\(\sum_{i=1}^nc_i\alpha_i\)</span> 和 <span class="math inline">\(\sum_{i=1}^nd_i\alpha_i\)</span>就是所求。比如由于 <span class="math display">\[\sum_{i=1}^nc_i\alpha_i={\bfA}\left(\sum_{i=1}^nc_i\omega_i\right)=c\sum_{i=1}^nc_i\omega_i.\]</span>所以 <span class="math display">\[{\bfA}\left(\sum_{i=1}^nc_i\alpha_i\right)={\bfA}\left(c\sum_{i=1}^nc_i\omega_i\right)=c\sum_{i=1}^nc_i\alpha_i.\]</span>同理 <span class="math inline">\({\bfA}\left(\sum\limits_{i=1}^nd_i\alpha_i\right)=d\sum\limits_{i=1}^nd_i\alpha_i\)</span>。所以<span class="math display">\[P=\mathop{\mathrm{span}}\left\{\sum_{i=1}^nc_i\alpha_i,\,\sum_{i=1}^nd_i\alpha_i\right\}.\]</span>这样我们就找到了只用 Cartan 矩阵和 <span class="math inline">\(\Delta\)</span> 计算 <span class="math inline">\(P\)</span> 的方法。</p><h1 id="e_8-的例子"><span class="math inline">\(E_8\)</span> 的例子</h1><p><span class="math inline">\(E_8\)</span> 的 Coxeter 图如下:</p><p><img style="margin:0px auto;display:block" src="/images/coxeter-element/e8-dynkin.svg" width="400"></p><p>单根系 <span class="math inline">\(\Delta\)</span>的选择不是唯一的,我们采用如下的选择,使得 <span class="math inline">\(\bullet\)</span> 就是 Euclidean 标准内积: <span class="math display">\[\Delta=\begin{bmatrix}1&-1&0&0&0&0&0&0\\0&1&-1&0&0&0&0&0\\0&0&1&-1&0&0&0&0\\0&0&0&1&-1&0&0&0\\0&0&0&0&1&-1&0&0\\0&0&0&0&0&1&1&0\\-\frac{1}{2}&-\frac{1}{2}&-\frac{1}{2}&-\frac{1}{2}&-\frac{1}{2}&-\frac{1}{2}&-\frac{1}{2}&-\frac{1}{2}\\0&0&0&0&0&1&-1&0\end{bmatrix}.\]</span>Coxeter 图中编号为 <span class="math inline">\(i\)</span>的顶点对应的单根 <span class="math inline">\(\alpha_i\)</span>是矩阵的第 <span class="math inline">\(i\)</span>行。注意每个单根的长度是 <span class="math inline">\(\sqrt{2}\)</span>,并不是单位向量,这样主要是为了书写代码更方便。</p><p>Cartan 矩阵 <span class="math inline">\(A\)</span>由单根之间两两内积给出: <span class="math display">\[A=(\alpha_i\bullet\alpha_j)_{1\leq i,j\leq8}=\begin{pmatrix}2&-1&0&0&0&0&0&0\\-1&2&-1&0&0&0&0&0\\0&-1&2&-1&0&0&0&0\\0&0&-1&2&-1&0&0&0\\0&0&0&-1&2&-1&0&-1\\0&0&0&0&-1&2&-1&0\\0&0&0&0&0&-1&2&0\\0&0&0&0&-1&0&0&2\end{pmatrix}.\]</span></p><p>用代码来写的话,就是</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np<br><br><span class="hljs-comment"># A set of simple roots listed by rows of 'delta'</span><br>delta = np.array([<br> [<span class="hljs-number">1</span>, -<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, -<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, -<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, -<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, -<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>],<br> [-<span class="hljs-number">0.5</span>, -<span class="hljs-number">0.5</span>, -<span class="hljs-number">0.5</span>, -<span class="hljs-number">0.5</span>, -<span class="hljs-number">0.5</span>, -<span class="hljs-number">0.5</span>, -<span class="hljs-number">0.5</span>, -<span class="hljs-number">0.5</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, -<span class="hljs-number">1</span>, <span class="hljs-number">0</span>]<br>])<br><br><span class="hljs-comment"># The cartan matrix:</span><br>cartan = np.dot(delta, delta.transpose())<br></code></pre></td></tr></tbody></table></figure><p>所有单反射 <span class="math inline">\(\{s_i\}_{i=1}^8\)</span>生成的群 <span class="math inline">\(W\)</span> 叫做 <span class="math inline">\(E_8\)</span> 的 Weyl 群,这个群包含 696729600个元素。单根系在 <span class="math inline">\(W\)</span> 作用下生成的集合<span class="math inline">\(\Phi = \{w\cdot\alpha\,|\, w\in W,\alpha\in\Delta\}\)</span> 叫做 <span class="math inline">\(E_8\)</span>的根系,<span class="math inline">\(\Phi\)</span> 中包含 240个不同的向量,<span class="math inline">\(W\)</span> 置换 <span class="math inline">\(\Phi\)</span> 中的向量。</p><p><span class="math inline">\(\Phi\)</span>中的向量从形式上看分为两类:</p><ol type="1"><li>第一类包含 <span class="math inline">\((\pm1,\pm1,0,0,0,0,0,0)\)</span>的所有置换,即有两个分量是 <span class="math inline">\(+1\)</span> 或者<span class="math inline">\(-1\)</span>,其余6个分量都是 0的向量。这样的向量有 112 个。</li><li>第二类包含所有形如 <span class="math inline">\(1/2\times(\pm1,\pm1,\cdots,\pm1)\)</span>的向量,其中 <span class="math inline">\(-1\)</span>的个数是偶数。这样的向量有 128 个。</li></ol><p>为了编程方便,我们可以把所有根都乘以2,使得它们都是整数向量。于是生成根系 <span class="math inline">\(\Phi\)</span> 的代码可以这样写:</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> itertools<br><br>roots = []<br><br><span class="hljs-comment"># Roots of the form (+-1, +-1, 0, 0, 0, 0, 0, 0),</span><br><span class="hljs-comment"># signs can be chosen independently and the two non-zeros can be anywhere.</span><br><span class="hljs-keyword">for</span> i, j <span class="hljs-keyword">in</span> combinations(<span class="hljs-built_in">range</span>(<span class="hljs-number">8</span>), <span class="hljs-number">2</span>):<br> <span class="hljs-keyword">for</span> x, y <span class="hljs-keyword">in</span> product([-<span class="hljs-number">2</span>, <span class="hljs-number">2</span>], repeat=<span class="hljs-number">2</span>):<br> v = np.zeros(<span class="hljs-number">8</span>)<br> v[i] = x<br> v[j] = y<br> roots.append(v)<br><br><span class="hljs-comment"># Roots of the form 1/2 * (+-1, +-1, ..., +-1), signs can be chosen</span><br><span class="hljs-comment"># indenpendently except that there must be an even numer of -1s.</span><br><span class="hljs-keyword">for</span> v <span class="hljs-keyword">in</span> product([-<span class="hljs-number">1</span>, <span class="hljs-number">1</span>], repeat=<span class="hljs-number">8</span>):<br> <span class="hljs-keyword">if</span> <span class="hljs-built_in">sum</span>(v) % <span class="hljs-number">4</span> == <span class="hljs-number">0</span>:<br> roots.append(v)<br>roots = np.array(roots).astype(<span class="hljs-built_in">int</span>)<br><br><span class="hljs-comment"># Connect a root to its nearest neighbors,</span><br><span class="hljs-comment"># two roots are connected if and only if they form an angle of pi/3.</span><br>edges = []<br><span class="hljs-keyword">for</span> i, r <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(roots):<br> <span class="hljs-keyword">for</span> j, s <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(roots[i + <span class="hljs-number">1</span> :], i + <span class="hljs-number">1</span>):<br> <span class="hljs-keyword">if</span> np.<span class="hljs-built_in">sum</span>((r - s) ** <span class="hljs-number">2</span>) == <span class="hljs-number">8</span>:<br> edges.append([i, j])<br></code></pre></td></tr></tbody></table></figure><p>我们来计算 Coxeter 平面 <span class="math inline">\(P\)</span>的一组基。</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs python">eigenvals, eigenvecs = np.linalg.eigh(cartan)<br>u = eigenvecs[:, <span class="hljs-number">0</span>]<br>v = eigenvecs[:, -<span class="hljs-number">1</span>]<br><br>u = np.dot(u, delta)<br>v = np.dot(v, delta)<br></code></pre></td></tr></tbody></table></figure><p>其中 <code>eigenvals</code> 返回 Cartan矩阵的特征值,按照从小到大排列;<code>eigenvecs</code> 的列向量是 Cartan矩阵的特征向量,也是按照特征值递增的顺序排列,第一列<code>u = eigenvecs[:, 0]</code> 和最后一列<code>v = eigenvecs[:, -1]</code>就是最小和最大特征值对应的特征向量。</p><p>把 <code>eigenvals</code> 中的特征值打印出来:</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs python">[<span class="hljs-number">0.01095621</span> <span class="hljs-number">0.51371035</span> <span class="hljs-number">1.18652671</span> <span class="hljs-number">1.58417662</span> <span class="hljs-number">2.41582338</span> <span class="hljs-number">2.81347329</span> <span class="hljs-number">3.48628965</span> <span class="hljs-number">3.98904379</span>]<br></code></pre></td></tr></tbody></table></figure><p>可以看到最小的特征值 <span class="math display">\[0.01095621 \approx2-2\cos\frac{\theta_0}{2},\quad \theta_0\approx2\arccos0.994521895\approx\frac{2\pi}{30}.\]</span> 所以我们验证了 <span class="math inline">\(\gamma\)</span> 在 <span class="math inline">\(P\)</span> 上的作用是一个 30 阶的旋转,即 <span class="math inline">\(W\)</span> 的 Coxeter 数是 30。</p><p>根据上一节末尾的介绍,<span class="math inline">\(\sum_{i=1}^8u_i\alpha_i\)</span> 和 <span class="math inline">\(\sum_{i=1}^8v_i\alpha_i\)</span> 构成 <span class="math inline">\(P\)</span> 的一组基。作为对称矩阵 <span class="math inline">\(A\)</span> 的不同特征值对应的特征向量,它们在<span class="math inline">\(\bullet\)</span>(即标准内积)下是正交的。把它们归一化为单位向量,然后计算根系<code>roots</code> 到 <span class="math inline">\(P\)</span>的投影:</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs python">u /= np.linalg.norm(u)<br>v /= np.linalg.norm(v)<br><br>roots_2d = [(np.dot(u, x), np.dot(v, x)) <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> roots]<br></code></pre></td></tr></tbody></table></figure><p>剩下的就是具体的绘图过程了,这里不再赘述。</p><h1 class="unnumbered" id="bibliography">References</h1><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0" role="list"><div id="ref-CasselmanCoxeterElement" class="csl-entry" role="listitem">Casselman, Bill. 2017. <span>“Coxeter Elements in Finite CoxeterGroups”</span> Essays on Coxeter groups. <a href="https://personal.math.ubc.ca/~cass/research/pdf/Element.pdf">https://personal.math.ubc.ca/~cass/research/pdf/Element.pdf</a>.</div><div id="ref-Humphreys90" class="csl-entry" role="listitem">Humphreys, James E. 1990. <em>Reflection Groups and Coxeter Groups</em>.Cambridge Studies in Advanced Mathematics. Cambridge University Press.<a href="https://doi.org/10.1017/CBO9780511623646">https://doi.org/10.1017/CBO9780511623646</a>.</div></div>]]></content>
<summary type="html">
<p>如果你对 Lie 代数有所了解的话,相信很大概率你会见过下面的图案: (
参考维基百科的 <a href="https://en.wikipedia.org/wiki/Lie_algebra">Lie
algebra 词条</a> )</p>
<p><img style="margin:0px auto;display: block" src="/images/coxeter-element/e8.svg" width="350"></p>
<p>它展示的是 Lie 代数 <span class="math inline">\(E_8\)</span>
的根系图。<span class="math inline">\(E_8\)</span> 的根系由 8 维欧式空间
<span class="math inline">\(\mathbb{R}^8\)</span> 中的 240
个向量组成,将这 240 个向量投影到一个特殊的 2 维平面 ( 叫做 Coxeter 平面
) 上就会呈现出一个具有旋转对称的图案。在上图中可以看到,240
个投影点分布在 8 个圆周上,每个圆周包含 30
个均匀分布的点,整个图案在角度为 <span class="math inline">\(\frac{2\pi}{30}\)</span> 的旋转下是不变的。<span class="math inline">\(h=30\)</span> 正是 <span class="math inline">\(E_8\)</span> 的 Coxeter 数。</p>
<p>本文目的是介绍 Coxeter 元的一些基础知识,然后教大家怎样在 Python
中编写一个程序绘制上面的投影图案。我主要参考了 <span class="citation" data-cites="Humphreys90">(<a href="#ref-Humphreys90" role="doc-biblioref">Humphreys 1990</a>)</span> 和 <span class="citation" data-cites="CasselmanCoxeterElement">(<a href="#ref-CasselmanCoxeterElement" role="doc-biblioref">Casselman
2017</a>)</span>。虽然这里面涉及的数学并不复杂,但是真正动手编程实现的时候会有一些魔鬼藏在细节中,而这些细节是仅凭念书很难发现的。</p>
<p>本文的代码在 <a href="https://github.com/neozhaoliang/pywonderland/raw/master/src/misc/E8.py">Github
上</a> 。David Madore 也有一个很棒的 <a href="http://www.madore.org/~david/math/e8w.html">交互式网页</a>
可以绘制 <span class="math inline">\(E_8\)</span>
的多种不同风格的图案。</p></summary>
<category term="pywonderland 项目" scheme="https://pywonderland.com/categories/pywonderland-%E9%A1%B9%E7%9B%AE/"/>
</entry>
<entry>
<title>Birkhoff 遍历定理</title>
<link href="https://pywonderland.com/Birkhoff-ergodic-theorem/"/>
<id>https://pywonderland.com/Birkhoff-ergodic-theorem/</id>
<published>2013-04-20T16:00:00.000Z</published>
<updated>2024-11-25T12:57:06.704Z</updated>
<content type="html"><![CDATA[<p>我念研究生时的高等概率论课用的是 Durrett 的教材 “Probability: TheoryandExamples”。这本书的好处我就不再介绍了,院长陈大岳老师在世图影印版的前言中已经夸了一遍。我个人的体会是,Durrett的书在讲解证明的时候非常简练,很少写为什么要这样证,我有时候读了半天也没搞明白思路。Birkhoff遍历定理算是其中一个,于是我重新整理了一下书中的证明,作此文留念。</p><p>Birkhoff 遍历定理最初由 Birkhoff 本人在 1931 年发表,原文长达 50页。随后在 1939 年 K.Yosida (吉田耕作) 和 S.Kakutani (角谷)利用极大遍历定理给出了一个 10页的简洁证明,不过他们关于极大遍历定理的证明还是啰嗦了点,后来 Garsia给出了极大遍历定理的一个仅有寥寥数行的惊人证明,这也是当前大多数教材采用的途径,本文就来介绍这一证明。</p><span id="more"></span><h1 id="准备工作">准备工作</h1><p>给定一个概率空间 <span class="math inline">\((\Omega,\mathcal{F},\mu)\)</span>,我们称两个可测集<span class="math inline">\(A,B\in F\)</span>几乎处处相等,是指它们的示性函数 <span class="math inline">\(\mathbb{1}_A,\mathbb{1}_B\)</span>几乎处处相等,记作 <span class="math inline">\(A\stackrel{\mathrm{a.e.}}{=}B\)</span>。等价的说法是<span class="math inline">\(A,B\)</span> 只差一个零测集,或者差集 <span class="math inline">\(A\Delta B\)</span> 是零测集。</p><p>设 <span class="math inline">\(T:\Omega\rightarrow \Omega\)</span>是一个可测变换,即对任何 <span class="math inline">\(E\in\mathcal{F}\)</span> 有 <span class="math inline">\(T^{-1}E\in\mathcal{F}\)</span>。</p><div id="def-1" class="statement definition plain"><p><span class="statement-heading"><span class="statement-label">定义1.1</span>.</span><span class="statement-spah"> </span>如果可测集 <span class="math inline">\(E\)</span> 满足 <span class="math inline">\(T^{-1}E\stackrel{\mathrm{a.e.}}{=}E\)</span>,就称<span class="math inline">\(E\)</span> 是一个 <span class="math inline">\(T-\)</span> 不变集合。不难验证所有的 <span class="math inline">\(T-\)</span> 不变集合 <span class="math display">\[\mathcal{I}=\{E\in\mathcal{F}\ |\T^{-1}E\stackrel{\mathrm{a.e.}}{=}E\}\]</span> 构成 <span class="math inline">\(\mathcal{F}\)</span> 的一个子 <span class="math inline">\(\sigma-\)</span> 代数。</p></div><div id="def-2" class="statement definition plain"><p><span class="statement-heading"><span class="statement-label">定义1.2</span>.</span><span class="statement-spah"> </span>如果对任何可测集<span class="math inline">\(E\in\mathcal{F}\)</span> 有 <span class="math inline">\(\mu(T^{-1}E)=\mu(E)\)</span>,就称 <span class="math inline">\(T\)</span> 是一个保测变换。</p></div><p>在本文中,<span class="math inline">\(T\)</span>始终代表一个保测变换。</p><p>保测变换有如下性质:</p><div id="lemma-1" class="statement lemma plain"><p><span class="statement-heading"><span class="statement-label">引理1.3</span>.</span><span class="statement-spah"> </span>如果 <span class="math inline">\(f\in L^1(\Omega)\)</span>是一个可积的随机变量,<span class="math inline">\(T\)</span>是保测变换,则 <span class="math display">\[\int_\Omegaf\,\mathrm{d}\mu=\int_\Omega f\circ T\,\mathrm{d}\mu.\]</span></p></div><p><strong>证明</strong>:若 <span class="math inline">\(E\in\mathcal{F}\)</span> 是可测集,由于 <span class="math display">\[\omega\in T^{-1}E\Leftrightarrow T(\omega)\inE\Leftrightarrow(\mathbb{1}_E\circ T) (\omega)=1.\]</span> 所以 <span class="math inline">\(\mathbb{1}_{E}\circT=\mathbb{1}_{\{T^{-1}E\}}\)</span>,因此 <span class="math display">\[\int_\Omega\mathbb{1}_E\,\mathrm{d}\mu=\mu(E)=\mu(T^{-1}E)=\int_\Omega\mathbb{1}_{\{T^{-1}E\}}\,\mathrm{d}\mu=\int_\Omega \mathbb{1}_E\circT\,\mathrm{d}\mu.\]</span>从而结论对集合的示性函数成立,进一步由积分的线性性质对任何简单函数也成立,再取极限即得对一般的可积函数结论成立。</p><div id="lemma-2" class="statement lemma plain"><p><span class="statement-heading"><span class="statement-label">引理1.4</span>.</span><span class="statement-spah"> </span>一个 <span class="math inline">\(\Omega\)</span> 上的随机变量 <span class="math inline">\(X\)</span> 关于 <span class="math inline">\(\mathcal{I}\)</span> 可测,当且仅当有 <span class="math display">\[X\circ T=X\quad \text{a.e.}\]</span>成立。这时我们称 <span class="math inline">\(X\)</span> 是 <span class="math inline">\(T-\)</span> 不变的随机变量。</p></div><p>这是 Durrett书中的一道习题,我一直觉得它很平凡,其实之前文章中的处理有问题,这个结论还是需要论证一番的。</p><p><strong>证明</strong>:</p><p><span class="math inline">\(\Rightarrow\)</span>:如果 <span class="math inline">\(X\)</span> 关于 <span class="math inline">\(\mathcal{I}\)</span> 可测,则对任何 Borel 集 <span class="math inline">\(B\in\mathcal{B}(\mathbb{R}^1)\)</span> 有 <span class="math inline">\(X^{-1}B\in\mathcal{I}\)</span>,即 <span class="math inline">\(T^{-1}(X^{-1}B)\stackrel{\mathrm{a.e.}}{=}X^{-1}B\)</span>,这说明<span class="math inline">\(\{X\circ T\inB\}\stackrel{\mathrm{a.e.}}{=}\{X\in B\}\)</span>。特别地取 <span class="math inline">\(B=(-\infty, t)\)</span> 我们得到 <span class="math inline">\(\{X\circT<t\}\stackrel{\mathrm{a.e.}}{=}\{X<t\}\)</span>。我们来证明如果<span class="math inline">\(\xi,\,\eta\)</span>是两个可测函数且对任何实数 <span class="math inline">\(t\)</span> 有<span class="math inline">\(\{\xi<t\}\stackrel{\mathrm{a.e.}}{=}\{\eta<t\}\)</span>,则<span class="math inline">\(\xi=\eta,\,\mathrm{a.e.}\)</span>。然后对<span class="math inline">\(\xi=X\circ T,\,\eta=T\)</span>应用此结论即可。若不然,不妨设 <span class="math inline">\(\{\xi>\eta\}\)</span> 具有正测度,则存在有理数<span class="math inline">\(c\)</span> 使得集合 <span class="math inline">\(\{\xi>c>\eta\}\)</span>具有正测度,这个集合在 <span class="math inline">\(\{\eta<c\}\)</span> 中,但是不在 <span class="math inline">\(\{\xi<c\}\)</span> 中,这与 <span class="math inline">\(\{\eta<c\}\)</span> 和 <span class="math inline">\(\{\xi<c\}\)</span> 只差一个零测集矛盾。</p><p><span class="math inline">\(\Leftarrow\)</span>:如果 <span class="math inline">\(X\circ T=X\)</span> 几乎处处成立,则对任何 <span class="math inline">\(B\in\mathcal{B}(\mathbb{R}^1)\)</span> 有 <span class="math inline">\(X^{-1}B\stackrel{\mathrm{a.e.}}{=}T^{-1}X^{-1}B\)</span>,这说明<span class="math inline">\(X^{-1}B\in\mathcal{I}\)</span>,即 <span class="math inline">\(X\)</span> 关于 <span class="math inline">\(\mathcal{I}\)</span> 可测。</p><h1 id="birkhoff-遍历定理">Birkhoff 遍历定理</h1><p>设 <span class="math inline">\(f\)</span> 是 <span class="math inline">\(\Omega\)</span> 上的随机变量,对每个整数 <span class="math inline">\(n\geq 1\)</span>,令 <span class="math display">\[S_n(\omega)= \sum_{k=0}^{n-1}f(T^k(\omega)).\]</span> 我们有如下的定理:</p><div id="birkhoff" class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理2.1</span>.</span><span class="statement-spah"> </span>(Birkhoff遍历定理)</p><p>设 <span class="math inline">\(T\)</span> 是概率空间 <span class="math inline">\((\Omega,\mathcal{F},\mu)\)</span>上的保测变换,则对任何 <span class="math inline">\(f\inL^1(\Omega)\)</span> 有 <span class="math display">\[\lim_{n\to\infty}\frac{S_n}{n}\rightarrow\mathbb{E}[f\,|\,\mathcal{I}]\quad\text{a.e.}\]</span></p></div><p>证明 Birkhoff遍历定理定理的关键是证明如下的极大遍历定理:(极大遍历定理这个名字来源于分析中的Hardy-Littlewood 极大函数,这一类的不等式统称为极大不等式)</p><div id="max-ergodic" class="statement sta_______ plain unnumbered"><p><span class="statement-heading"><span class="statement-label">极大遍历定理</span>.</span><span class="statement-spah"> </span>定义极大算子 <span class="math display">\[M_f(\omega)=\sup_{n\geq1}\frac{1}{n}S_n(\omega),\]</span> 则对 <span class="math inline">\(f\inL^1(\Omega)\)</span> 和任一 <span class="math inline">\(a\in\mathbb{R}\)</span>,有 <span class="math display">\[\int_{\{M_f>a\}} f\,\mathrm{d}\mu\geqa\mu(\{M_f>a\}).\]</span></p></div><p>极大遍历定理是整个 Birkhoff遍历定理的证明中最不直观的部分,而且我也确实不知道怎么解释引入它的动机。我第一次看到这个式子的时候是很懵的。一个直观的理解是,观察下面这个显然成立的不等式:<span class="math display">\[\int_{\{M_f>a\}} M_f\,\mathrm{d}\mu\geqa\mu(\{M_f>a\}).\]</span> 极大遍历定理是说把其中的积分函数换成 <span class="math inline">\(f\)</span>,积分范围保持不变的话,不等式仍然成立。</p><p>我把极大遍历定理的证明放在最后,先用它来证明 Birkhoff 遍历定理。</p><h1 id="birkhoff-遍历定理的证明">Birkhoff 遍历定理的证明</h1><p>首先可以假定条件期望 <span class="math inline">\(\mathbb{E}[f\,|\,\mathcal{I}]=0\)</span>,否则我们可以用<span class="math inline">\(f-\mathbb{E}[f\,|\,\mathcal{I}]\)</span>代替 <span class="math inline">\(f\)</span>,注意到 <span class="math inline">\(\mathbb{E}[f\,|\,\mathcal{I}]\)</span> 是 <span class="math inline">\(T-\)</span> 不变的,所以根据上面的 <a href="#lemma-2" title="引理 1.4">引理 1.4</a> 有 <span class="math display">\[\mathbb{E}[f\,|\,\mathcal{I}]\circ T^k =\mathbb{E}[f\,|\,\mathcal{I}],\quad \mathrm{a.e.}\]</span>对所有的正整数 <span class="math inline">\(k\)</span> 都成立,这时 <a href="#birkhoff" title="定理 2.1">定理 2.1</a> 的左边 <span class="math inline">\(S_n\)</span> 中每一项都会多出来一个 <span class="math inline">\(\mathbb{E}[f\,|\,\mathcal{I}]\)</span>,除以 <span class="math inline">\(n\)</span> 正好和右边的 <span class="math inline">\(\mathbb{E}[f\,|\,\mathcal{I}]\)</span>抵消掉。</p><p>这样问题变成在 <span class="math inline">\(\mathbb{E}[f\,|\,\mathcal{I}]=0\)</span>的前提下证明 <span class="math display">\[\lim\limits_{n\to\infty}\frac{S_n}{n}=0.\quad\text{a.e.}\]</span> 设 <span class="math inline">\(a\)</span>是任一正数,考虑集合 <span class="math display">\[A= \left\{\omega \mid\varlimsup_{n\to\infty}\frac{S_n}{n}>a\right\}.\]</span> 我们想证明<span class="math inline">\(\mu(A)=0\)</span>。若真如此,则有 <span class="math inline">\(\varlimsup\limits_{n\to\infty}S_n/n\leq a\)</span>几乎处处成立,根据 <span class="math inline">\(a\)</span> 的任意性就得到<span class="math inline">\(\varlimsup\limits_{n\to\infty}S_n/n\leq0\)</span> 几乎处处成立。再把这个结果用在 <span class="math inline">\(-f\)</span> 上就得到 <span class="math inline">\(\varliminf\limits_{n\to\infty}S_n/n\geq 0\)</span>也几乎处处成立,这样就证明了 <span class="math inline">\(\lim\limits_{n\to\infty}S_n/n=0\)</span>几乎处处成立。(拗口)</p><p>为了证明 <span class="math inline">\(\mu(A)=0\)</span>,我们希望对函数 <span class="math inline">\(f\)</span> 和集合 <span class="math inline">\(A\)</span> 应用极大不等式: <span class="math display">\[\int_A f\,\mathrm{d}\mu\geq a\mu(A).\]</span>这是因为,<span class="math inline">\(A\)</span> 其实是一个 <span class="math inline">\(T-\)</span> 不变的集合,即 <span class="math inline">\(A\in\mathcal{I}\)</span>,我们会在证明末尾再验证这一点。于是根据条件期望的性质,<span class="math display">\[\int_A f\,\mathrm{d}\mu = \int_A\mathbb{E}[f\,|\,\mathcal{I}]\,\mathrm{d}\mu =0.\]</span> 即 <span class="math inline">\(0\geq a\mu(A)\)</span>,结合 <span class="math inline">\(a>0\)</span> 即得 <span class="math inline">\(\mu(A)=0\)</span>。</p><p>但是,我们能对 <span class="math inline">\(A\)</span>使用极大不等式吗?请注意 <span class="math inline">\(\varlimsup\limits_{n\to\infty}\)</span> 和 <span class="math inline">\(\sup\limits_{n\geq 1}\)</span>的区别,它们定义的是两个不同的随机变量。<span class="math inline">\(A\)</span> 是用 <span class="math inline">\(\varlimsup\limits_{n\to\infty}\)</span>定义的,而极大遍历定理中说的是 <span class="math inline">\(\sup\limits_{n\geq 1}\)</span>。注意到 <span class="math display">\[A=\left\{\varlimsup_{n\to\infty}\frac{S_n}{n}>a\right\}\subseteq\left\{\sup_{n\geq 1}\frac{S_n}{n}>a\right\}=\left\{M_f>a\right\},\]</span>所以我们只需要证明下面的结论就好了:</p><div class="statement lemma plain"><p><span class="statement-heading"><span class="statement-label">引理3.1</span>.</span><span class="statement-spah"> </span>设 <span class="math inline">\(A\subseteq \{M_f>a\}\)</span> 而且 <span class="math inline">\(A\)</span> 是一个 <span class="math inline">\(T-\)</span> 不变集合,那么极大遍历定理仍然成立:<span class="math display">\[\int_A f\,\mathrm{d}\mu\geqa\mu(A).\]</span></p></div><p><strong>引理的证明</strong>:对函数 <span class="math inline">\(g=f\cdot\mathbb{1}_A\)</span> 应用极大遍历定理:<span class="math display">\[\int_{\{M_g>a\}}f\cdot\mathbb{1}_A\,\mathrm{d}\mu\geq a\mu(\{M_g>a\}).\]</span> 但是<span class="math inline">\(M_g=M_f\cdot\mathbb{1}_A\)</span>,这一点要用到<span class="math inline">\(A\)</span> 是 <span class="math inline">\(T-\)</span> 不变集合这个条件,因此 <span class="math display">\[\{M_g>a\}=\{M_f>a\}\cap A=A.\]</span>因此确实有 <span class="math display">\[\int_Af\,\mathrm{d}\mu\geq a \mu(A).\]</span> 这样就证明了 Birkhoff遍历定理。</p><p>实际上定理中的收敛也是一个依 <span class="math inline">\(L^1\)</span>范数的收敛,这点的证明相比几乎处处收敛就容易多了,这里不再赘述。</p><p>最后我们来补上证明中遗漏的部分,即验证集合 <span class="math inline">\(A=\left\{\varlimsup\limits_{n\to\infty}S_n/n>a\right\}\)</span>确实是 <span class="math inline">\(T-\)</span> 不变的:</p><p>利用 <span class="math inline">\(S_{n+1} = f + S_n\circ T\)</span>可得 <span class="math display">\[\frac{S_{n+1}}{n+1} = \frac{f}{n+1} +\frac{S_n\circ T}{n}\cdot \frac{n}{n+1}.\]</span>对两边同时取上极限,注意由于 <span class="math inline">\(f\inL^1(\Omega)\)</span> 所以 <span class="math inline">\(f\)</span>几乎处处有限,从而 <span class="math inline">\(\lim\limits_{n\to\infty}f/(n+1)=0,\,\text{a.e.}\)</span>。所以<span class="math display">\[\varlimsup_{n\to\infty} \frac{S_{n+1}}{n+1}= \varlimsup_{n\to\infty}\frac{S_{n}\circ T}{n}.\]</span> 这正说的是<span class="math inline">\(\varlimsup\limits_{n\to\infty}S_n/n\)</span>是 <span class="math inline">\(T-\)</span> 不变的随机变量,从而 <span class="math inline">\(A\)</span> 是 <span class="math inline">\(T-\)</span> 不变的集合。</p><p>最后来证明极大遍历定理。</p><h1 id="极大遍历定理的证明">极大遍历定理的证明</h1><p>只要证明 <span class="math inline">\(a=0\)</span>的情形,然后对一般的 <span class="math inline">\(a\)</span>,将结论应用在函数 <span class="math inline">\(f-a\)</span> 上即可。定义 <span class="math inline">\(S_0=0\)</span> 以及 <span class="math inline">\(M_n =\max\{S_0,S_1,\cdots,S_n\}\)</span>。对每个<span class="math inline">\(k=1,\ldots,n\)</span> 有 <span class="math display">\[S_k=f+S_{k-1}\circ T\leq f+M_n \circ T.\]</span>从而 <span class="math display">\[\max_{1\leq k\leq n}S_k\leq f+M_n\circ T.\]</span></p><p>但是在集合 <span class="math inline">\(\{M_n>0\}\)</span>上,<span class="math inline">\(M_n\)</span> 作为 <span class="math inline">\(S_0,S_1,\ldots,S_n\)</span> 中的最大者肯定不能来自<span class="math inline">\(S_0=0\)</span>,所以 <span class="math inline">\(M_n=\max\limits_{1\leq k\leq n}S_k\)</span>,因此<span class="math display">\[M_n\leq f+M_n \circ T,\quad \omega\in\{M_n>0\}.\]</span> 注意 <span class="math inline">\(M_n\)</span>总是非负的随机变量,从而 <span class="math display">\[\begin{align*}\int_{\{M_n>0\}} f &\geq\int_{\{M_n>0\}}M_n -\int_{\{M_n>0\}}M_n\circ T\\ & =\int_\Omega M_n- \int_{\{M_n>0\}}M_n\circ T\\&\geq \int_\OmegaM_n-\int_\Omega M_n\circ T\\&=0.\end{align*}\]</span> 最后由于 <span class="math inline">\(\{M_n>0\}\uparrow\{M_f>0\}\)</span>,所以由控制收敛定理即可得到 <span class="math display">\[\int_{\{M_f>0\}}f\geq 0.\]</span>极大遍历定理得证。</p>]]></content>
<summary type="html">
<p>我念研究生时的高等概率论课用的是 Durrett 的教材 “Probability: Theory
and
Examples”。这本书的好处我就不再介绍了,院长陈大岳老师在世图影印版的前言中已经夸了一遍。我个人的体会是,Durrett
的书在讲解证明的时候非常简练,很少写为什么要这样证,我有时候读了半天也没搞明白思路。Birkhoff
遍历定理算是其中一个,于是我重新整理了一下书中的证明,作此文留念。</p>
<p>Birkhoff 遍历定理最初由 Birkhoff 本人在 1931 年发表,原文长达 50
页。随后在 1939 年 K.Yosida (吉田耕作) 和 S.Kakutani (角谷)
利用极大遍历定理给出了一个 10
页的简洁证明,不过他们关于极大遍历定理的证明还是啰嗦了点,后来 Garsia
给出了极大遍历定理的一个仅有寥寥数行的惊人证明,这也是当前大多数教材采用的途径,本文就来介绍这一证明。</p></summary>
<category term="Durrett 概率论批判" scheme="https://pywonderland.com/categories/Durrett-%E6%A6%82%E7%8E%87%E8%AE%BA%E6%89%B9%E5%88%A4/"/>
</entry>
<entry>
<title>实表示和复表示</title>
<link href="https://pywonderland.com/real-and-complex-representations/"/>
<id>https://pywonderland.com/real-and-complex-representations/</id>
<published>2012-10-20T16:00:00.000Z</published>
<updated>2024-12-06T15:29:55.980Z</updated>
<content type="html"><![CDATA[<p>在数学中有许多「三分天下」的例子,比如:</p><ol type="1"><li>常曲率空间只有 Euclidean、球面、双曲三种。</li><li>三类典型的偏微分方程:热方程 (抛物)、Laplace 方程 (椭圆)、波方程(双曲)。</li><li>复平面上全纯等价下只有三种单连通区域:单位圆 <span class="math inline">\(\mathbb{D}\)</span>、复平面 <span class="math inline">\(\mathbb{C}\)</span>、扩充复平面 <span class="math inline">\(\overline{\mathbb{C}}\)</span>。</li><li>不可约代数簇 (素理想) 在扩张下的三种行为:分解、惯性、分歧。</li><li>随机游动可以分为零常返、正常返、暂态。</li><li>实数域 <span class="math inline">\(\mathbb{R}\)</span>上的有限维结合可除代数只有三种:<span class="math inline">\(\mathbb{R}\)</span>、复数域 <span class="math inline">\(\mathbb{C}\)</span>、四元数 <span class="math inline">\(\mathbb{H}\)</span>。</li></ol><p>本文要介绍的是另外两个三分天下的例子,它们来自群表示论,即有限群的不可约实表示在复数域上的分解,和不可约复表示在实数域上的实现。这两个例子是紧密相关的。</p><span id="more"></span><h1 id="不可约实表示在复数域上的分解">不可约实表示在复数域上的分解</h1><p>在有限群的表示论中,第一个遇到的重要结论大概非 Schur 引理莫属:</p><div id="schur-------" class="statement sta_schur___ plain unnumbered"><p><span class="statement-heading"><span class="statement-label">Schur引理</span>.</span><span class="statement-spah"> </span>设 <span class="math inline">\(G\)</span> 是一个有限群,<span class="math inline">\(k\)</span> 是一个特征为 0 的域,<span class="math inline">\(V\)</span> 是一个不可约左 <span class="math inline">\(kG\)</span>- 模。则 <span class="math inline">\(D=\mathrm{Hom}_{kG}(V,V)\)</span> 是 <span class="math inline">\(k\)</span> 上的有限维结合可除代数。</p></div><p>我记得还是本科生的时候,在自学到这个结论时就曾经想过这个问题: 如果<span class="math inline">\(k=\mathbb{R}\)</span> 是实数域的话,那么<span class="math inline">\(D\)</span> 作为 <span class="math inline">\(\mathbb{R}\)</span>上的有限维结合可除代数只有三种:实数域 <span class="math inline">\(\mathbb{R}\)</span>,复数域 <span class="math inline">\(\mathbb{C}\)</span>,四元数体 <span class="math inline">\(\mathbb{H}\)</span>。这三种可能性分别对应 <span class="math inline">\(\mathbb{R}G\)</span>- 模 <span class="math inline">\(V\)</span> 的什么性质呢?</p><p>答案是,<span class="math inline">\(D\)</span> 决定了 <span class="math inline">\(V\)</span>作为复表示时分解为不可约表示的方式。等等,把 <span class="math inline">\(V\)</span> 看作复表示是什么意思来着?</p><p>有两种途径可以解释怎样把 <span class="math inline">\(V\)</span>看作复表示,第一种方式使用纯矩阵的语言,第二种则使用张量积的语言。它们本质是一回事。</p><p>使用矩阵的语言最为直观。让我们回忆,<span class="math inline">\(V\)</span> 是 <span class="math inline">\(G\)</span> 的实表示意味着有群同态 <span class="math display">\[G\overset{\rho}{\longrightarrow} {\rmGL}(n,\mathbb{R}),\quad n=\dim V.\]</span> 但实矩阵也是复矩阵,所以<span class="math inline">\(\rho\)</span> 也是 <span class="math inline">\(G\to {\rm GL}(n,\mathbb{C})\)</span> 的同态。所以<span class="math inline">\(\rho\)</span> 自然也是 <span class="math inline">\(G\)</span> 的复表示。</p><p>使用张量积的语言就很不直观了,但是我更偏好这种方式,因为这可以让我们在更高的抽象层次上进行计算。为了把<span class="math inline">\(V\)</span> 看作 <span class="math inline">\(\mathbb{C}G\)</span>- 模,我们需要把 <span class="math inline">\(V\)</span> 中数和向量的乘法扩展到 <span class="math inline">\(\mathbb{C}\)</span> 上。所以我们考虑 <span class="math inline">\(V\)</span> 的 <a href="https://en.wikipedia.org/wiki/Complexification">复化</a> <span class="math inline">\(V_\mathbb{C}=\mathbb{C}\otimes_\mathbb{R}V\)</span>。<span class="math inline">\(V_\mathbb{C}\)</span> 中复数与向量的乘法由 <span class="math display">\[z(c\otimes v)=zc\otimes v,\quadz,c\in\mathbb{C},v\in V.\]</span> 给出。<span class="math inline">\(V\)</span> 可以等同于 <span class="math inline">\(V_\mathbb{C}\)</span> 的实子空间 <span class="math inline">\(1\otimes V\)</span>。在这个等同下,<span class="math inline">\(V\)</span> 的一组 <span class="math inline">\(\mathbb{R}\)</span>- 基也是 <span class="math inline">\(V_\mathbb{C}\)</span> 的一组 <span class="math inline">\(\mathbb{C}\)</span>- 基。任何 <span class="math inline">\(T\in\mathrm{End}_\mathbb{R}(V)\)</span> 通过 <span class="math inline">\(T\to 1_\mathbb{C}\otimes T\)</span> 变成 <span class="math inline">\(V_\mathbb{C}\)</span> 上的 <span class="math inline">\(\mathbb{C}\)</span>- 线性变换。<span class="math inline">\(T\)</span> 和 <span class="math inline">\(1_\mathbb{C}\otimes T\)</span>在同一组基下具有相同的矩阵。</p><p>特别地,<span class="math inline">\(G\)</span> 在 <span class="math inline">\(V_\mathbb{C}\)</span> 上的作用定义为 <span class="math inline">\(1_\mathbb{C}\otimes g\)</span> 在 <span class="math inline">\(V_\mathbb{C}\)</span> 上的作用: <span class="math display">\[g(c\otimes v) = c\otimes gv.\]</span> 这个作用是<span class="math inline">\(\mathbb{C}\)</span>- 线性的,在此作用下<span class="math inline">\(V_\mathbb{C}\)</span> 成为一个 <span class="math inline">\(\mathbb{C}G\)</span>- 模。</p><p>怎么计算 <span class="math inline">\(V_\mathbb{C}\)</span>的分解呢?这就要用到群表示论中关于半单代数结构的一个基本结论:</p><div class="statement proposition plain"><p><span class="statement-heading"><span class="statement-label">命题1.1</span>.</span><span class="statement-spah"> </span>设 <span class="math inline">\(W\)</span> 是一个 <span class="math inline">\(\mathbb{C}G\)</span>- 模,<span class="math inline">\(W = n_1W_1\oplus n_2W_2\oplus\cdots\oplusn_rW_r\)</span>,其中 <span class="math inline">\(W_i\)</span>是互不同构的 <span class="math inline">\(\mathbb{C}G\)</span>- 模,<span class="math inline">\(n_i\)</span> 是 <span class="math inline">\(W_i\)</span> 出现在 <span class="math inline">\(W\)</span> 中的重数,则有 <span class="math inline">\(\mathbb{C}\)</span>- 代数同构 <span class="math display">\[\mathrm{End}_{\mathbb{C}G}(W) \cong\mathrm{Mat}_{n_1}(\mathbb{C})\times\mathrm{Mat}_{n_2}(\mathbb{C})\times\cdots\times\mathrm{Mat}_{n_r}(\mathbb{C}).\]</span></p></div><p>用 <span class="math inline">\(V_\mathbb{C}\)</span> 代替 <span class="math inline">\(W\)</span>,上面的结论告诉我们可以通过将 <span class="math inline">\(\mathrm{End}_{\mathbb{C}G}(V_\mathbb{C})\)</span>表示为矩阵代数的直积来获得 <span class="math inline">\(V_\mathbb{C}\)</span> 的分解。根据自然同构 <span class="math display">\[\mathrm{End}_{\mathbb{C}G}(V_\mathbb{C})\cong\mathbb{C}\otimes_{\mathbb{R}}\mathrm{End}_{\mathbb{R}G}(V)=\mathbb{C}\otimes_\mathbb{R}D.\]</span>问题最终归结为将 <span class="math inline">\(\mathbb{C}\otimes_{\mathbb{R}}D\)</span>表示为矩阵代数的直积。</p><p>我们来论证 <span class="math inline">\(V_\mathbb{C}\)</span>的分解具有如下的「三分性质」:</p><ol type="1"><li><span class="math inline">\(D=\mathbb{R}\)</span>:由 <span class="math inline">\(\mathbb{R}\otimes_\mathbb{R}\mathbb{C}\cong\mathbb{C}\)</span>,因此<span class="math inline">\(V_\mathbb{C}\)</span> 仍然是不可约的。</li><li><span class="math inline">\(D=\mathbb{C}\)</span>:根据 <span class="math inline">\(\mathbb{C}\)</span>- 代数同构 <span class="math inline">\(\mathbb{C}\otimes_\mathbb{R}\mathbb{C}\cong\mathbb{C}\times\mathbb{C}\)</span><a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>,因此 <span class="math inline">\(V_\mathbb{C}\)</span> 的分解为 <span class="math inline">\(V_\mathbb{C}=V_1\oplus V_2\)</span>,即 <span class="math inline">\(V_\mathbb{C}\)</span>是两个不同构的复表示的直和。注意 <span class="math inline">\(V_\mathbb{C}\)</span> 的特征与 <span class="math inline">\(V\)</span> 相同,是个实特征,所以 <span class="math inline">\(V_1\)</span> 和 <span class="math inline">\(V_2\)</span> 是共轭的,即 <span class="math inline">\(V_2=V_1^\ast\)</span>。</li><li><span class="math inline">\(D=\mathbb{H}\)</span>:根据 <span class="math inline">\(\mathbb{C}\)</span>- 代数同构 <span class="math inline">\(\mathbb{H}\otimes_\mathbb{R}\mathbb{C}\cong\mathrm{Mat}_2(\mathbb{C})\)</span><a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>,因此 <span class="math inline">\(V_\mathbb{C}\)</span> 的分解为 <span class="math inline">\(V_\mathbb{C}=2V_1\)</span>,即 <span class="math inline">\(V_\mathbb{C}\)</span>是某个不可约模的二重和。</li></ol><h1 id="不可约复表示在实数域上的实现">不可约复表示在实数域上的实现</h1><div class="unnumbered statement question-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>设 <span class="math inline">\(G\)</span>是一个有限群,<span class="math inline">\(V\)</span> 是一个不可约的<span class="math inline">\(\mathbb{C}G-\)</span> 模,其特征为 <span class="math inline">\(\chi\)</span>。我们想知道这个表示是否能在实数域上实现?即是否存在一个<span class="math inline">\(\mathbb{R}G\)</span>- 模 <span class="math inline">\(W\)</span>,使得 <span class="math inline">\(W\)</span> 的特征也是 <span class="math inline">\(\chi\)</span>?</p></div><p>显然 <span class="math inline">\(V\)</span>可以在实数域上可以实现的一个必要条件是 <span class="math inline">\(\chi\)</span> 是实特征,即对任何 <span class="math inline">\(g\in G\)</span>,<span class="math inline">\(\chi(g)\)</span>都是实数,但这个条件是否也是充分的呢?</p><p>答案是否定的。</p><div id="------" class="statement sta___ definition unnumbered"><p><span class="statement-heading"><span class="statement-label">反例</span>:</span><span class="statement-spah"> </span>四元数群 <span class="math inline">\(Q_8=\{\bf \pm1, \pm i,\pm j, \pm k\}\)</span>是一个 8 阶群,它只有一个次数大于 1 的不可约复表示,其次数为2,但是它没有次数为 2的不可约实表示,所以这个复表示肯定不能在实数域上实现。</p><p>具体讲,<span class="math inline">\(Q_8\)</span>有四个不可约一维复表示,这四个复表示同时也是实表示。此外 <span class="math inline">\(Q_8\)</span> 在四元数体 <span class="math inline">\(\mathbb{H}\)</span> 上的左乘给出其一个不可约 4维实表示(除环作为自己的左正则模必然是不可约的),因此我们有群代数分解<span class="math display">\[\mathbb{R}Q_8 \cong4\mathbb{R}\oplus\mathbb{H}.\]</span> 另一方面 <span class="math inline">\(Q_8\)</span> 有一个不可约的二维复表示,这个表示由Pauli 矩阵给出: <span class="math display">\[{\bfi}\to\begin{pmatrix}0&i\\i&0\end{pmatrix},\quad {\bfj}\to\begin{pmatrix}0&-1\\1&0\end{pmatrix},\quad {\bfk}\to\begin{pmatrix}i&0\\0&-i\end{pmatrix}.\]</span>这三个矩阵和恒等矩阵一起构成 <span class="math inline">\(\mathrm{Mat}_2(\mathbb{C})\)</span>的一组基,因此有群代数分解 <span class="math display">\[\mathbb{C}Q_8\cong4\mathbb{C}\oplus\mathrm{Mat}_2(\mathbb{C}).\]</span>由于 <span class="math inline">\(Q_8\)</span>没有二维不可约实表示,因此这个二维不可约复表示不能在实数域上实现。</p></div><p>那要使得一个不可约复表示可以在实数域上实现,它应该满足怎样的条件?假定你对实向量空间的复化有足够的了解<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>,那就可以直接看到本质:这等价于存在一个与<span class="math inline">\(G\)</span> 交换的 <a href="https://en.wikipedia.org/wiki/Complex_conjugate">复共轭</a> <span class="math inline">\(J\colon\ V\to V\)</span>。即 <span class="math inline">\(J\)</span> 是 <a href="https://en.wikipedia.org/wiki/Antilinear_map">共轭线性</a>的,满足 <span class="math inline">\(J^2=1\)</span> 并且对任何 <span class="math inline">\(g\in G\)</span> 有 <span class="math inline">\(gJ=Jg\)</span>。我们会看到,存在三种互斥的情形:</p><ol type="1"><li>不存在与 <span class="math inline">\(G\)</span> 交换的共轭线性变换<span class="math inline">\(J\)</span></li><li>或者如果存在的话,这样的 <span class="math inline">\(J\)</span>在只差一个常数的意义下是唯一确定的,在适当缩放以后有两种可能:<span class="math inline">\(J^2=\pm1\)</span>。</li></ol><p>1 和 2 互斥是显然的,但是 2中的两种情形为什么互斥就不那么显然了。注意不要以为 <span class="math inline">\(J^2= 1\)</span> 的话会有 <span class="math inline">\((iJ)^2=-1\)</span>。实际上由于 <span class="math inline">\(iJ=\overline{i}J\)</span>,所以 <span class="math inline">\((iJ)^2=iJiJ=J^2\)</span>。</p><p>记 <span class="math inline">\(V^\ast\)</span> 为 <span class="math inline">\(V\)</span> 的对偶表示。<span class="math inline">\(V^\ast\)</span> 的特征是 <span class="math inline">\(\overline{\chi}\)</span>。</p><div class="statement definition plain"><p><span class="statement-heading"><span class="statement-label">定义2.1</span>.</span></p><ol type="1"><li>如果 <span class="math inline">\(V\ncong V^\ast\)</span>,就称 <span class="math inline">\(V\)</span> 是复的 (complex type);</li><li>如果 <span class="math inline">\(V\cong V^\ast\)</span> 且 <span class="math inline">\(V\)</span> 是某个不可约 <span class="math inline">\(\mathbb{R}G\)</span>- 模 <span class="math inline">\(W\)</span> 的复化:<span class="math inline">\(V\congW\otimes_{\mathbb{R}}\mathbb{C}\)</span>,就称 <span class="math inline">\(V\)</span> 是实的 (real type);</li><li>如果 <span class="math inline">\(V\cong V^\ast\)</span> 且 <span class="math inline">\(V\)</span> 是某个不可约 <span class="math inline">\(\mathbb{H}G\)</span>- 模在复数域上的限制,就称<span class="math inline">\(V\)</span> 是四元数的 (quaterniontype)。</li></ol></div><p>我们将论证 <span class="math inline">\(V\)</span>具有如下的「三分性质」:</p><div class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理2.2</span>.</span><span class="statement-spah"> </span><span class="math inline">\(V\)</span>必然恰好属于复、实、四元数三种类型之一,即这三种类型是互斥的。</p></div><p>复类型显然与后面两种类型是互斥的。所以我们只需要讨论 <span class="math inline">\(V\cong V^\ast\)</span> 时会发生什么。</p><p>根据 Schur 引理,<span class="math inline">\(\mathrm{Hom}_G(V,V^\ast)\cong\mathbb{C}\)</span>是一维的。但是我们有 <span class="math inline">\(G\)</span>- 模同构<span class="math display">\[\mathrm{Hom}_G(V,V^\ast)\cong\mathrm{Bil}(V) \cong V^\ast\otimes V^\ast.\]</span></p><p>所以 <span class="math inline">\(V^\ast\otimes V^\ast\)</span> 作为<span class="math inline">\(\mathbb{C}G\)</span>- 模也是一维的。但是<span class="math inline">\(V^\ast\otimes V^\ast\)</span>总是可以分解为两个 <span class="math inline">\(G\)</span>-不变子空间的直和,即对称和反对称的双线性形式:</p><p><span class="math display">\[V^\ast\otimesV^\ast=S^2V^\ast\oplus\Lambda^2V^\ast.\]</span> 其中 <span class="math inline">\(S^2V^\ast\)</span> 是二阶对称张量组成的空间,<span class="math inline">\(\Lambda^2V^\ast\)</span>是二阶反对称张量组成的空间。</p><p>于是 <span class="math inline">\(V\)</span> 上的任何 <span class="math inline">\(G\)</span>- 不变双线性型 <span class="math inline">\(B\)</span> 要么是对称的: <span class="math display">\[B(v,w)=B(w,v).\]</span> 要么是反对称的: <span class="math display">\[B(v,w)=-B(w,v).\]</span> 二者必居其一。</p><p>取 <span class="math inline">\(\langle\,,\,\rangle\)</span> 为 <span class="math inline">\(V\)</span> 上的任一 <span class="math inline">\(G\)</span>- 不变的正定 Hermite 内积。其中 <span class="math inline">\(\langle\,,\,\rangle\)</span>关于第一个分量是共轭线性的,关于第二个分量是线性的。从而在一组标准正交基<span class="math inline">\(\{e_i\}_{i=1}^n\)</span>下 <span class="math inline">\(\langle\,,\,\rangle\)</span> 形如 <span class="math display">\[\langle\sum_{i=1}^n x_ie_i,\,\sum_{i=1}^ny_ie_i\rangle=\sum_{i=1}^n\overline{x}_i y_i.\]</span></p><p>由于 <span class="math inline">\(\langle\,,\,\rangle\)</span>是正定的当然非退化,从而对任何 <span class="math inline">\(V\)</span>上的 <span class="math inline">\(\mathbb{C}\)</span>- 线性泛函 <span class="math inline">\(f\)</span>,都存在唯一的 <span class="math inline">\(z\in V\)</span> 使得 <span class="math inline">\(f=\langle z,\,\cdot\rangle\)</span>。特别地,固定<span class="math inline">\(v\in V\)</span>,对线性泛函 <span class="math inline">\(\phi_v\colon\ w\mapsto B(v,w)\)</span>,存在唯一的<span class="math inline">\(z\in V\)</span> 使得 <span class="math display">\[\langle z,\, w\rangle = \phi_v(w) =B(v,w).\]</span> 这里 <span class="math inline">\(z\)</span> 依赖于<span class="math inline">\(v\)</span>。我们记这个从 <span class="math inline">\(v\)</span> 到 <span class="math inline">\(z\)</span> 的映射为 <span class="math inline">\(J\colon\ v\mapsto z\)</span>。从而 <span class="math display">\[B(v, w) = \langle Jv, w\rangle.\]</span> 由于<span class="math inline">\(B\)</span> 和 <span class="math inline">\(\langle\,,\,\rangle\)</span> 都是 <span class="math inline">\(G\)</span>- 不变的,因此 <span class="math inline">\(J\)</span> 必然与 <span class="math inline">\(G\)</span> 的作用交换 <a href="#fn4" class="footnote-ref" id="fnref4" role="doc-noteref"><sup>4</sup></a>。</p><p>注意到 <span class="math inline">\(B\)</span>关于第一个分量是线性的,但是 <span class="math inline">\(\langle\,,\,\rangle\)</span>关于第一个分量是共轭线性的,所以 <span class="math inline">\(J\)</span>必然也是共轭线性的,即对任何 <span class="math inline">\(c\in\mathbb{C}\)</span> 有 <span class="math display">\[J(cv) = \overline{c}z.\]</span></p><p>由于共轭线性变换的平方是 <span class="math inline">\(\mathbb{C}\)</span>- 线性的,所以 <span class="math inline">\(J^2\)</span> 是 <span class="math inline">\(\mathbb{C}\)</span>- 线性的且与 <span class="math inline">\(G\)</span> 的作用交换。由 Schur 引理存在 <span class="math inline">\(c\in\mathbb{C}\)</span> 使得 <span class="math inline">\(J^2=cI\)</span>,<span class="math inline">\(c\ne0\)</span>。我们来说明必有 <span class="math inline">\(c>0\)</span> 或者 <span class="math inline">\(c<0\)</span>。这两种情形分别取决于 <span class="math inline">\(B\)</span> 是对称的或者反对称的。</p><ol type="1"><li><p>如果 <span class="math inline">\(B\)</span> 是对称的,则 <span class="math display">\[\langle Jv, w\rangle= \langle Jw,v\rangle.\]</span> 取 <span class="math inline">\(v=Jw\)</span>带入上式得到 <span class="math display">\[\langle J^2w, w\rangle=\langleJw, Jw\rangle\geq0.\]</span> 由于 <span class="math inline">\(J^2=cI\)</span>,所以必然有 <span class="math inline">\(c>0\)</span>。</p></li><li><p>类似地当 <span class="math inline">\(B\)</span> 反对称时可得<span class="math inline">\(c<0\)</span>。</p></li></ol><p>于是通过给 <span class="math inline">\(J\)</span> 除以 <span class="math inline">\(\sqrt{|c|}\)</span>,我们就得到了一个新的共轭线性变换,把它仍然记作<span class="math inline">\(J\)</span>,则 <span class="math inline">\(J\)</span> 满足 <span class="math inline">\(J^2=\pm1\)</span>。我们来说明这两种情形分别对应<span class="math inline">\(V\)</span> 是实类型或者四元数类型。</p><ol type="1"><li>如果 <span class="math inline">\(J^2=1\)</span>,由于 <span class="math inline">\(J\)</span> 是共轭线性的,<span class="math inline">\(J\)</span> 实际上给出了 <span class="math inline">\(V\)</span> 上的一个共轭结构。记 <span class="math inline">\(W=\{w\in V\mid J(w)=w\}\)</span> 是 <span class="math inline">\(J\)</span> 的不动点,则 <span class="math inline">\(V\)</span> 是 <span class="math inline">\(W\)</span> 的复化:<span class="math inline">\(V\cong\mathbb{C}\otimes_{\mathbb{R}}W\)</span>。由于 <span class="math inline">\(G\)</span> 的作用与 <span class="math inline">\(J\)</span> 交换,所以 <span class="math inline">\(G\)</span> 在 <span class="math inline">\(V\)</span> 上的作用 <span class="math inline">\(\rho(g)\)</span> 都形如 <span class="math inline">\(\rho(g)=1\otimes\rho_W(g)\)</span>,其中 <span class="math inline">\(\rho_W(g)\)</span> 是 <span class="math inline">\(W\)</span> 上的 <span class="math inline">\(\mathbb{R}\)</span>- 线性变换。即表示 <span class="math inline">\(\rho\)</span> 是表示 <span class="math inline">\(\rho_W\)</span> 的复化。所以 <span class="math inline">\(V\)</span> 是实的。</li><li>如果 <span class="math inline">\(J^2=-1\)</span>,那么 <span class="math inline">\(1,\, I=i,\,J,\, K=IJ\)</span>满足通常的四元数乘法,它们都和 <span class="math inline">\(G\)</span>的作用交换,所以 <span class="math inline">\(V\)</span> 可以变成一个左<span class="math inline">\(\mathbb{H}G\)</span>-模,将此模限制在复数域上即为表示 <span class="math inline">\(V\)</span>,从而表示 <span class="math inline">\(V\)</span> 是四元数的。</li></ol><p>实类型和四元数类型是互斥的,否则 <span class="math inline">\(V\)</span>上将同时存在一个对称双线性型和一个反对称双线性型,与 <span class="math inline">\(\dim_\mathbb{C}(V^\ast\otimes V^\ast)=1\)</span>矛盾。</p><p>总之我们证明了表示 <span class="math inline">\(V\)</span>必然恰好属于实、复、四元数三种类型之一,其中只有实类型可以在实数域上实现。而且由上面的分析不难得出下面的推论:</p><div class="statement corollary plain"><p><span class="statement-heading"><span class="statement-label">推论2.3</span>.</span></p><ol type="1"><li><span class="math inline">\(V\)</span> 是复的当且仅当 <span class="math inline">\(V\)</span> 上不存在任何非零的 <span class="math inline">\(G\)</span>- 不变双线性型。</li><li><span class="math inline">\(V\)</span> 是实的当且仅当 <span class="math inline">\(V\)</span> 上存在一个非零的 <span class="math inline">\(G\)</span>- 不变对称双线性型。</li><li><span class="math inline">\(V\)</span> 是四元数的当且仅当 <span class="math inline">\(V\)</span> 上存在一个非零的 <span class="math inline">\(G\)</span>- 不变反对称双线性型。</li></ol></div><p>这三种情形分别对应 <span class="math inline">\(\dim_{\mathbb{C}}(S^2V^\ast)-\dim_{\mathbb{C}}(\Lambda^2V^\ast)\)</span>的值是 0, +1 和 -1。通过一些简单的计算不难得到 <span class="math display">\[\dim_{\mathbb{C}}(S^2V^\ast)-\dim_{\mathbb{C}}(\Lambda^2V^\ast)=\frac{1}{|G|}\sum_{g\inG}\chi(g^2).\]</span></p><p><span class="math inline">\(F(\chi)=\dfrac{1}{|G|}\sum\limits_{g\inG}\chi(g^2)\)</span> 叫做 <span class="math inline">\(\chi\)</span> 的Frobenius-Schur 指标,于是我们有</p><div class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理2.4</span>.</span><span class="statement-spah"> </span>(Frobenius-Schur指标)</p><ol type="1"><li><span class="math inline">\(V\)</span> 是复的当且仅当 <span class="math inline">\(F(\chi)=0\)</span>。</li><li><span class="math inline">\(V\)</span> 是实的当且仅当 <span class="math inline">\(F(\chi)=1\)</span>。</li><li><span class="math inline">\(V\)</span> 是四元数的当且仅当 <span class="math inline">\(F(\chi)=-1\)</span>。</li></ol></div><aside id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes"><hr><ol><li id="fn1"><p>一个同构由 <span class="math inline">\(z_1\otimes z_2\to(z_1z_2, z_1\overline{z}_2)\)</span> 给出。首先此映射是一个 <span class="math inline">\(\mathbb{C}\)</span>- 代数同态。此同态是满射,因为<span class="math inline">\(\frac{1}{2}(1\otimes1+ i\otimes i)\)</span>被映射为 <span class="math inline">\((1,0)\)</span>,<span class="math inline">\(\frac{1}{2}(1\otimes1 - i\otimes i)\)</span>被映射为 <span class="math inline">\((0, 1)\)</span>。此外由于两边作为<span class="math inline">\(\mathbb{C}\)</span>- 代数的维数相同,都是2,所以是一个同构。<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li><li id="fn2"><p>有个一般性的结论:设 <span class="math inline">\(D\)</span> 是域 <span class="math inline">\(F\)</span> 上的有限维可除代数且 <span class="math inline">\(D\)</span> 的中心为 <span class="math inline">\(F\)</span>,<span class="math inline">\(K\)</span>是 <span class="math inline">\(D\)</span> 的极大子域,<span class="math inline">\(n=[D:K]\)</span>。则有 <span class="math inline">\(K\)</span>- 代数同构 <span class="math inline">\(K\otimes_FD\cong\mathrm{Mat}_n(K)\)</span>。将此结论应用于 <span class="math inline">\(D=\mathbb{H}\)</span>,<span class="math inline">\(F=\mathbb{R}\)</span>,<span class="math inline">\(K=\mathbb{C}\)</span> 即可。</p><p>不过针对四元数 <span class="math inline">\(\mathbb{H}\)</span>我们可以给一个直接的证明:把 <span class="math inline">\(\mathbb{H}\)</span> 看作 <span class="math inline">\(\mathbb{C}\)</span>上的<strong>二维右向量空间</strong>(即 <span class="math inline">\(\mathbb{C}\)</span> 的数乘写在右边),则 <span class="math inline">\(\mathbb{H}=1\mathbb{C}\oplusj\mathbb{C}\)</span>。任何 <span class="math inline">\(a\in\mathbb{H}\)</span> 在自身上的左乘给出了一个<span class="math inline">\(\mathbb{C}\)</span>- 线性变换:<span class="math display">\[a(xc) = (ax)c\quada,x\in\mathbb{H},\,c\in\mathbb{C}.\]</span> 这是因为 <span class="math inline">\(\mathbb{H}\)</span> 是结合的,所以 <span class="math inline">\(a\)</span> 在 <span class="math inline">\(\mathbb{H}\)</span> 上的左乘与 <span class="math inline">\(\mathbb{C}\)</span> 在 <span class="math inline">\(\mathbb{H}\)</span> 上的右乘交换。于是这给出了一个<span class="math inline">\(\mathbb{H}\to\mathrm{Mat}_2(\mathbb{C})\)</span>的嵌入。</p><p>设 <span class="math inline">\(a=z+jw\in\mathbb{H}\)</span>,我们来计算 <span class="math inline">\(a\)</span> 的左乘在 <span class="math inline">\(\{1,j\}\)</span> 这组基下的矩阵: <span class="math display">\[\begin{aligned}(z+jw)\cdot 1 &= z+jw,\\(z+jw)\cdot j&= zj+jwj=-\overline{w}+j\overline{z}.\end{aligned}\]</span> 所以 <span class="math inline">\(z+jw\)</span> 在 <span class="math inline">\(\mathrm{Mat}_2(\mathbb{C})\)</span> 中对应的矩阵为<span class="math inline">\(\begin{pmatrix}z&-\overline{w} \\ w& \overline{z}\end{pmatrix}\)</span>。由于 <span class="math inline">\(\mathbb{H}\)</span> 是 <span class="math inline">\(\mathbb{C}\)</span> 上的二维右向量空间,所以它嵌入<span class="math inline">\(\mathrm{Mat}_2(\mathbb{C})\)</span>以后的像也是二维的,并不构成整个 <span class="math inline">\(\mathrm{Mat}_2(\mathbb{C})\)</span>。</p><p>但是对 <span class="math inline">\(D=\mathbb{H}\otimes_\mathbb{R}\mathbb{C}\)</span>,事情就不一样了:<span class="math inline">\(D\)</span> 是 <span class="math inline">\(\mathbb{C}\)</span> 上的 4维右向量空间,并且它也作用在二维的右 <span class="math inline">\(\mathbb{C}\)</span>- 向量空间 <span class="math inline">\(\mathbb{H}\)</span> 上:对任何 <span class="math inline">\(a,x\in\mathbb{H}\)</span> 和 <span class="math inline">\(c\in\mathbb{C}\)</span> 我们定义 <span class="math inline">\(a\otimes c\)</span> 在 <span class="math inline">\(\mathbb{H}\)</span> 上的作用为 <span class="math display">\[(a\otimes c)x=axc,\quada,x\in\mathbb{H},c\in\mathbb{C}.\]</span> 这个作用是右 <span class="math inline">\(\mathbb{C}\)</span>- 线性的,从而给出了 <span class="math inline">\(D\)</span> 到 <span class="math inline">\(\mathrm{Mat}_2(\mathbb{C})\)</span>的同态。你可以用 <span class="math inline">\(D\)</span>是单代数来说明这是一个单射,然后再比较维数来说明它是同构,但这需要一些关于中心单代数的知识;也可以换一种方法来验证:<span class="math inline">\(D\)</span> 在 <span class="math inline">\(\mathbb{H}\)</span> 上的作用,相比前面 <span class="math inline">\(\mathbb{H}\)</span> 在自身上的左乘,还额外包含了<span class="math inline">\(i\)</span> 的右乘。这个右乘在 <span class="math inline">\(\{1,j\}\)</span> 这组基下的矩阵是对角矩阵 <span class="math inline">\(\begin{pmatrix}i&\\ &i\end{pmatrix}\)</span>。所以 <span class="math inline">\(D\)</span> 在<span class="math inline">\(\mathbb{H}\)</span>上的作用包含了所有如下形式的矩阵: <span class="math display">\[\begin{pmatrix}z&-\overline{w} \\ w &\overline{z}\end{pmatrix}\quad\text{and}\quad\begin{pmatrix}z&-\overline{w}\\ w & \overline{z}\end{pmatrix}\cdot\begin{pmatrix}i&\\ &i\end{pmatrix}.\]</span> 不难验证这两种矩阵的线性组合生成了整个 <span class="math inline">\(\mathrm{Mat}_2(\mathbb{C})\)</span>,从而这是一个<span class="math inline">\(D\to\mathrm{Mat}_2(\mathbb{C})\)</span>的满同态,再比较维数即可。<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li><li id="fn3"><p>如果你对复化不够熟悉的话,可以参考我的一篇 <a href="/real-complex">旧文</a>。<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li><li id="fn4"><p>对任何 <span class="math inline">\(g\inG\)</span>,我们有 <span class="math display">\[\langle Jv,w\rangle=B(v,w)=B(gv,gw)= \langle Jgv, gw\rangle = \langle g^{-1}Jgv,w\rangle.\]</span> 即 <span class="math inline">\(g^{-1}Jg =J\)</span>,从而 <span class="math inline">\(J\)</span> 与 <span class="math inline">\(G\)</span> 的作用交换。<a href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li></ol></aside>]]></content>
<summary type="html">
<p>在数学中有许多「三分天下」的例子,比如:</p>
<ol type="1">
<li>常曲率空间只有 Euclidean、球面、双曲三种。</li>
<li>三类典型的偏微分方程:热方程 (抛物)、Laplace 方程 (椭圆)、波方程
(双曲)。</li>
<li>复平面上全纯等价下只有三种单连通区域:单位圆 <span class="math inline">\(\mathbb{D}\)</span>、复平面 <span class="math inline">\(\mathbb{C}\)</span>、扩充复平面 <span class="math inline">\(\overline{\mathbb{C}}\)</span>。</li>
<li>不可约代数簇 (素理想) 在扩张下的三种行为:分解、惯性、分歧。</li>
<li>随机游动可以分为零常返、正常返、暂态。</li>
<li>实数域 <span class="math inline">\(\mathbb{R}\)</span>
上的有限维结合可除代数只有三种:<span class="math inline">\(\mathbb{R}\)</span>、复数域 <span class="math inline">\(\mathbb{C}\)</span>、四元数 <span class="math inline">\(\mathbb{H}\)</span>。</li>
</ol>
<p>本文要介绍的是另外两个三分天下的例子,它们来自群表示论,即有限群的不可约实表示在复数域上的分解,和不可约复表示在实数域上的实现。这两个例子是紧密相关的。</p></summary>
<category term="有限群表示与结合代数" scheme="https://pywonderland.com/categories/%E6%9C%89%E9%99%90%E7%BE%A4%E8%A1%A8%E7%A4%BA%E4%B8%8E%E7%BB%93%E5%90%88%E4%BB%A3%E6%95%B0/"/>
</entry>
<entry>
<title>相亲问题与倒向归纳法</title>
<link href="https://pywonderland.com/optimal-stopping-and-backward-induction/"/>
<id>https://pywonderland.com/optimal-stopping-and-backward-induction/</id>
<published>2012-07-08T16:00:00.000Z</published>
<updated>2024-11-25T13:03:50.186Z</updated>
<content type="html"><![CDATA[<div class="unnumbered statement question-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>假设你是一位大龄男士,准备参加 100 场相亲(别介意具体数字)。你打算依次与每个女士 <span class="math inline">\(i\)</span> 约会,然后根据印象给她打一个分数 <span class="math inline">\(X_i\)</span>,<span class="math inline">\(X_i\)</span> 的值介于 <span class="math inline">\([0,1]\)</span> 之间。如果你对女士 <span class="math inline">\(i\)</span>很满意,那么就和她结婚,否则就放弃她,参加下一场相亲,当然拒绝了人家可就没有回头的机会了。如果你拒绝了前99 位女士,那么不论第 100次相亲结果如何你都只能和最后这位女士结婚。在相亲之前,你对这些女士的情况一无所知,所以姑且假定她们的分数<span class="math inline">\(X_i\)</span> 都是 <span class="math inline">\([0,1]\)</span>上均匀分布的独立的随机变量。问题是:应该采取怎样的相亲策略,才能娶到你最中意的女士?</p></div><span id="more"></span><p>再费点笔墨解释下。每次相亲结束以后,你可以选择和当前的女士结婚,或者继续见下一位女士,这依赖于你已经相亲的结果:如果你挑挑拣拣到了90号女士还拿不定主意,最后发现「糟了,我快要变剩男了!」,那很可能接下来你就会放低择偶标准,遇到一个还凑合的就结婚了,即使她比你前面拒绝过的很多女士都有不如。当然也不排除你对第一位女士就一见钟情的可能,因此你最终选择的女士的编号<span class="math inline">\(\tau\)</span>是一个随机变量,你要做的就是让你的未来太太的期望分数 <span class="math inline">\(\mathbb{E}X_\tau\)</span> 尽可能的高。</p><p>那么应该采取怎样的策略为好呢?就像买东西讨价还价时总有一个心理价位一样,似乎可以先设定一个心理的期望值,如果遇到的女士的分数大于等于这个值,那就和她结婚;否则就继续下一位女士。这个思路很合理,但是问题是,期望值应该设定为多少呢?</p><p>在概率论里面我们学过如下关于顺序统计量的经典结论:设 <span class="math inline">\(X_1,\cdots,X_N\)</span> 是 <span class="math inline">\([0,1]\)</span> 上独立同分布的均匀随机变量,则<span class="math inline">\(Y=\max_{1\leq i\leq N}X_i\)</span> 的期望是<span class="math inline">\(\frac{N}{N+1}\)</span>。所以如果你把 100次相亲全部进行完,得分最高的女士的期望值理论上应该是 <span class="math inline">\(\frac{100}{101}\)</span>,于是你应该把心理门槛设置在<span class="math inline">\(\frac{100}{101}\)</span>,是这样吗?</p><p>答案是NO!首先门槛值应该是一个随着相亲的进行而逐渐降低的数列,这才符合实际的情形:如果前面太挑剔,为了不当剩男你后面的标准就会放低。其次我们会用倒向归纳法计算出最优策略下初始的门槛值并不是最中意的女士的期望值<span class="math inline">\(\frac{100}{101}\)</span>,实际上它更接近于得分第二高的女士的期望值<span class="math inline">\(\frac{99}{101}\)</span>。正如梅艳芳在《似是故人来》中唱的那样:「但凡未得到,但凡是过去,总是最登对」— 得不到的才是最好的。</p><h1 id="倒向归纳法">倒向归纳法</h1><p>相亲问题是应用倒向归纳法的一个典型例子。</p><p>我们从最后的情形开始分析,假设只剩一位女士可选,那么你只能去和她结婚,而她的期望值是1/2,我们记作 <span class="math inline">\(a_1=1/2\)</span>。</p><p>假设还剩两位女士可选呢?这种情况下应该先和其中一个相亲,如果她的分数大于等于1/2,那就应该和她结婚 (后者的期望只有 1/2,很可能不如她),而小于 1/2的话则去和第二位女士相亲 (后者的期望是1/2,所以我没道理现在娶一个分数小于 1/2 的)。而第一位女士分数大于等于1/2 的概率是 1/2,在大于等于 1/2 的条件下她的分数服从 <span class="math inline">\([1/2,1]\)</span> 上的均匀分布,期望值是3/4;第二位女士的期望分数就是 1/2。所以你以 1/2 的概率娶到一个期望值为3/4 的女士,以 1/2 的概率娶到一个期望值为 1/2的女士。因此有两位女士可选时这个策略的期望分数为 <span class="math display">\[a_2=\frac{1}{2}\cdot\frac{3}{4}+\frac{1}{2}\cdot\frac{1}{2}=\frac{5}{8}.\]</span></p><p>一般地,假设还剩下 <span class="math inline">\(i\)</span>位女士的时候你的心理期望值是 <span class="math inline">\(a_i\)</span>,我们来导出 <span class="math inline">\(a_i\)</span> 和 <span class="math inline">\(a_{i+1}\)</span>之间的递推关系:首先和其中一位女士相亲,如果她的分数大于等于 <span class="math inline">\(a_i\)</span> 那么就和她结婚,否则就拒绝她(因为后面 <span class="math inline">\(i\)</span> 个人的心理期望是 <span class="math inline">\(a_i\)</span>,我没道理现在娶一个分数小于 <span class="math inline">\(a_i\)</span> 的)。前一种情形的期望是 <span class="math inline">\(\frac{1+a_i}{2}\)</span> 但是发生的概率是 <span class="math inline">\(1-a_i\)</span>,后一种情形的期望是 <span class="math inline">\(a_i\)</span> 发生的概率也是 <span class="math inline">\(a_i\)</span>,因此 <span class="math display">\[a_{i+1}=(1-a_i)\left(\frac{1+a_i}{2}\right)+a_i\cdota_i=\frac{1+a_i^2}{2}.\]</span> 结合初值 <span class="math inline">\(a_1=\frac{1}{2}\)</span>就可以算出整个序列来,因此我们的相亲策略应该是:</p><blockquote><p>假设当前还剩 <span class="math inline">\(i\)</span>位女士。就把心里期望设定在 <span class="math inline">\(a_i\)</span>,然后进行一次相亲。如果相亲结果大于等于<span class="math inline">\(a_i\)</span>,那就和这位女士结婚;否则就把心里期望降低为<span class="math inline">\(a_{i-1}\)</span>,然后继续去见下一位女士。这里序列<span class="math inline">\(\{a_i\}\)</span> 由 <span class="math inline">\(a_1=1/2\)</span>,<span class="math inline">\(a_{i+1}=\frac{1+a_i^2}{2}\)</span> 给出。</p></blockquote><p>在这个策略下,你最终娶到的女士得分期望是 <span class="math inline">\(a_{100}\)</span>。</p><p>序列 <span class="math inline">\((a_n)_{n\geq1}\)</span> 是所谓的QuadraticMap,它的通项公式是求不出来的,只能用计算机来算。不过可以用归纳法证明<span class="math inline">\(\frac{N-1}{N+1}<a_{N}<\frac{N-0.5}{N+1}\)</span>,即<span class="math inline">\(a_N\)</span> 的值更接近于次优女士的期望值<span class="math inline">\(\frac{N-1}{N+1}\)</span>。当 <span class="math inline">\(N=100\)</span> 时,<span class="math inline">\(a_{100}\approx0.981\)</span>,<span class="math inline">\(\frac{N-1}{N+1}\approx 0.98\)</span> 而 <span class="math inline">\(\frac{N}{N+1}=100/101\approx 0.99\)</span>,可见<span class="math inline">\(a_{100}\)</span> 与 <span class="math inline">\(\frac{N-1}{N+1}\)</span>更接近。这印证了之前说过的:和你结婚的往往不是你最中意的那个。</p><p>请注意,虽然我们已经设计出了一个不错的策略,但这个策略到底是不是最优的呢?我们还没有严格证明。而且就算这个策略是最优的,是否只有这一种最优策略呢?没准还有其它最优策略能让你更省时省心地娶到好太太呢!要严格的解释这些,就要用到鞅的理论。</p><h1 id="翻译为鞅的语言">翻译为鞅的语言</h1><p>用鞅的语言重新表述上面的问题,会给人一种画风突变的感觉,看起来非常晦涩,不像是在说人话:</p><div class="unnumbered statement question-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>设 <span class="math inline">\(N\)</span>是一个给定的正整数,<span class="math inline">\(\{\mathcal{F}_n\}_{n=0}^N\)</span>是某概率空间上的递增的 <span class="math inline">\(\sigma\)</span>-域流,<span class="math inline">\(\{X_n\}_{n=0}^N\)</span>是一列可积的随机变量且 <span class="math inline">\(X_k\in\mathcal{F}_k\)</span>。设 <span class="math inline">\(\mathcal{M}\)</span> 是所有满足 <span class="math inline">\(0\leq\tau\leq N\)</span> 的停时 <span class="math inline">\(\tau\)</span> 组成的集合。我们想求出值函数 <span class="math display">\[V= \sup_{\tau\in\mathcal{M}}\mathbb{E}X_\tau\]</span> 以及使得这个最大值取到的停时 <span class="math inline">\(\tau\)</span>。</p></div><p>这里我把下标改成了 从 0 开始到 <span class="math inline">\(N\)</span>,以符合大多数文献的习惯。<span class="math inline">\(\mathcal{F}_n=\{X_0,\ldots,X_n\}\)</span> 是前<span class="math inline">\(n+1\)</span> 位女士得分生成的 <span class="math inline">\(\sigma\)</span>- 域,它包含了所有你第 <span class="math inline">\(n+1\)</span> 次相亲后可能知道的信息。<span class="math inline">\(\mathcal{F}_n\)</span>显然是递增的。你的一个相亲策略对应一个停时 <span class="math inline">\(\tau\)</span>,所有停时构成的集合 <span class="math inline">\(\mathcal{M}\)</span> 就是你所有可能的策略。</p><p>定义 <span class="math inline">\(\{X_n\}\)</span> 的 <strong>Snell包络</strong>为 <span class="math display">\[S_n=\begin{cases}X_N&n=N,\\\max\{X_n,\,\mathbb{E}[S_{n+1}|\mathcal{F}_n]\}&n=N-1,\ldots,0.\end{cases}\]</span> 这里的 <span class="math inline">\(S_n\)</span> 是从 <span class="math inline">\(N\)</span> 开始倒向递归定义的。注意 <span class="math inline">\(S_n\)</span> 关于 <span class="math inline">\(\mathcal{F}_n\)</span> 可测。</p><p><span class="math inline">\(S_n\)</span>的直观意义很好理解,它就是在时刻 <span class="math inline">\(n\)</span>对当下女士的分数和后面所有女士期望分数的比较:如果和当前的女士 <span class="math inline">\(n\)</span> 结婚,那么分数就是 <span class="math inline">\(X_n\)</span>;否则继续前进到下一个时刻 <span class="math inline">\(n+1\)</span>,未来太太的期望分数就是 <span class="math inline">\(\mathbb{E}[S_{n+1}|\mathcal{F}_n]\)</span>,二者取最大值<span class="math inline">\(\max\{X_n,\mathbb{E}[S_{n+1}|\mathcal{F}_n]\}\)</span>即为 <span class="math inline">\(n\)</span>时刻对最佳分数的估计。注意这里 <span class="math inline">\(\mathbb{E}[S_{n+1}|\mathcal{F}_n]\)</span>要取条件期望,因为一般情况下 <span class="math inline">\(\{X_n\}\)</span>之间不是独立的,从而对未来最佳收益的估计依赖于历史信息。</p><div class="statement example definition unnumbered"><p><span class="statement-heading"><span class="statement-label">例</span>:</span><span class="statement-spah"></span>在相亲问题中,<span class="math inline">\(\{X_n\}\)</span> 是<span class="math inline">\(\mathrm{i.i.d}\)</span> 序列,<span class="math inline">\(S_{n+1}\)</span> 与 <span class="math inline">\(\mathcal{F}_n\)</span> 独立(你可以倒着从 <span class="math inline">\(S_N\)</span> 开始验证 <span class="math inline">\(S_{n+1}\)</span> 完全由 <span class="math inline">\(X_{n+1},\ldots,X_N\)</span> 决定),从而 <span class="math inline">\(\mathbb{E}[S_{n+1}|\mathcal{F}_n]=\mathbb{E}S_{n+1}\)</span>,所以</p><p><span class="math display">\[S_n=\begin{cases}X_N&n=N,\\\max\{X_n,\,\mathbb{E}S_{n+1}\}&n=N-1,\ldots,0.\end{cases}\]</span></p><p>记事件 <span class="math inline">\(A_n=\{X_n>\mathbb{E}S_{n+1}\}\)</span>,于是序列<span class="math inline">\(\{\mathbb{E}S_n\}\)</span> 满足倒向递推关系<span class="math display">\[\mathbb{E}S_n=\mathbb{E}\max\{X_n,\,\mathbb{E}S_{n+1}\}=\mathbb{E}[X_n\mathrm{1}_{A_n}]+ \mathbb{E}S_{n+1}\cdot(1-\mathbb{P}(A_n)).\]</span>这正是我们前一节中看到的递推关系。</p></div><p>设 <span class="math inline">\(\tau=\inf\,\{n:\,S_n=X_n\}\)</span>,则 <span class="math inline">\(\tau\)</span>是停时。由于 <span class="math inline">\(S_N=X_N\)</span>,因此 <span class="math inline">\(0\leq\tau\leq N\)</span>。<span class="math inline">\(\tau\)</span> 就是我们采取的相亲策略:在 <span class="math inline">\(\tau\)</span> 时刻,由于这时 <span class="math inline">\(\max\{X_n,\mathbb{E}[S_{n+1}|\mathcal{F}_n]\}=X_n\)</span>,即<span class="math inline">\(n\)</span> 号女士的分数 <span class="math inline">\(X_n\)</span>大于等于后面继续相亲所能获得的最佳收益 <span class="math inline">\(\mathbb{E}[S_{n+1}|\mathcal{F}_n]\)</span>,剩下的相亲就不必再进行了。</p><p>到目前为止,我们已经把相亲问题完整地翻译成了鞅的语言。我们来证明<span class="math inline">\(\tau\)</span>确实是最优策略。为此我们需要做一些准备:</p><div id="super" class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理2.1</span>.</span><span class="statement-spah"> </span><span class="math inline">\(\{S_n\}\)</span> 是控制 <span class="math inline">\(\{X_n\}\)</span> 的最小上鞅。</p></div><p><strong>证明</strong>:由 <span class="math inline">\(S_n=\max\{X_n,\mathbb{E}[S_{n+1}|\mathcal{F}_n]\}\)</span>直接可见 <span class="math inline">\(S_n\geq X_n\)</span> 并且 <span class="math inline">\(\{S_n\}\)</span> 是上鞅。设 <span class="math inline">\(\{Y_n\}\)</span> 是任意满足 <span class="math inline">\(Y_n\geq X_n\)</span> 的上鞅序列,我们要证明必有<span class="math inline">\(Y_n\geq S_n\)</span>。这只要从最后一项 <span class="math inline">\(n=N\)</span> 开始逐项验证即可。由定义 <span class="math inline">\(Y_N\geq X_N=S_N\)</span>,这一项没问题。假设 <span class="math inline">\(Y_n\geq S_n\)</span>,两边对 <span class="math inline">\(\mathcal{F}_{n-1}\)</span> 取条件期望可得 <span class="math display">\[Y_{n-1}\geq \mathbb{E}[Y_n|\mathcal{F}_{n-1}]\geq \mathbb{E}[S_n|\mathcal{F}_{n-1}].\]</span>其中第一个不等号是根据上鞅的定义,第二个不等号是根据条件期望的单调性。再结合<span class="math inline">\(Y_{n-1}\geq X_{n-1}\)</span> 可得 <span class="math inline">\(Y_{n-1}\geq\max\{X_{n-1},\mathbb{E}[S_n|\mathcal{F}_{n-1}]\}=S_{n-1}\)</span>,所以<span class="math inline">\(n-1\)</span>项也没有问题。这样倒着向前递推即得结论成立。<span class="math inline">\(\blacksquare\)</span></p><div id="martingale" class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理2.2</span>.</span><span class="statement-spah"> </span><span class="math inline">\(\{S_{n\wedge\tau}\}\)</span> 是一个鞅。</p></div><p><strong>证明</strong>:<span class="math display">\[S_{(n+1)\wedge\tau}-S_{n\wedge\tau}=1_{\{\tau>n\}}(S_{n+1}-S_n).\]</span></p><p>对上式右边求条件期望: <span class="math display">\[\mathbb{E}[1_{\{\tau>n\}}(S_{n+1}-S_n)|\mathcal{F}_n]=1_{\{\tau>n\}}\mathbb{E}[(S_{n+1}-S_n)|\mathcal{F}_n]=0.\]</span></p><p>这是因为如果 <span class="math inline">\(\tau>n\)</span>的话那么由 <span class="math inline">\(\tau\)</span> 的定义 <span class="math inline">\(S_n=\mathbb{E}[S_{n+1}|\mathcal{F}_n]\)</span>。<span class="math inline">\(\blacksquare\)</span></p><div id="expectation-tau" class="statement corollary plain"><p><span class="statement-heading"><span class="statement-label">推论2.3</span>.</span><span class="statement-spah"> </span><span class="math inline">\(\mathbb{E}X_\tau=\mathbb{E}S_0\)</span>。</p></div><p><strong>证明</strong>:注意 <span class="math inline">\(X_\tau=S_\tau\)</span>,利用 <span class="math inline">\(0\leq\tau\leq N\)</span> 和鞅性质即可: <span class="math display">\[\mathbb{E}X_\tau=\mathbb{E}S_\tau=\mathbb{E}S_{\tau\wedgeN}=\mathbb{E}S_{\tau\wedge0}=\mathbb{E}S_0.\]</span> <span class="math inline">\(\blacksquare\)</span></p><div id="optimal" class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理2.4</span>.</span><span class="statement-spah"> </span>设 <span class="math inline">\(\mathcal{M}\)</span> 是所有满足 <span class="math inline">\(0\leq T\leq N\)</span> 的停时 <span class="math inline">\(T\)</span> 组成的集合,则 <span class="math inline">\(\tau\)</span> 是其中最优的: <span class="math display">\[\mathbb{E}S_0=\mathbb{E}X_\tau=\sup_{T\in\mathcal{M}}\mathbb{E}X_T.\]</span></p></div><p><strong>证明</strong>:设 <span class="math inline">\(T\)</span>是任意停时,我们要证明 <span class="math inline">\(\mathbb{E}X_\tau\geq\mathbb{E}X_T\)</span>。为此只要注意到 <span class="math display">\[\mathbb{E}X_\tau =\mathbb{E}S_0\geq \mathbb{E}S_T\geq\mathbb{E}X_T.\]</span> 第一个等号是根据 <a href="#expectation-tau" title="推论 2.3">推论 2.3</a>,中间的不等号是因为 <span class="math inline">\(\{S_n\}\)</span> 是上鞅,所以对任何停时 <span class="math inline">\(T\)</span> 都有 <span class="math inline">\(\mathbb{E}S_T\leq\mathbb{E}S_0\)</span>。最后的不等号是因为 <span class="math inline">\(S_n\)</span> 控制 <span class="math inline">\(X_n\)</span>。<span class="math inline">\(\blacksquare\)</span></p><p>至此我们就从数学上严格论证了前面的相亲策略确实是最优的。</p><p>回顾上面的分析,可以发现我们实际上使用了 <span class="math inline">\(\tau\)</span> 的两个性质:<span class="math inline">\(S_\tau=X_\tau\)</span> 和 <span class="math inline">\(\{S_{n\wedge\tau}\}\)</span>是鞅。这两个性质保证了 <span class="math inline">\(\tau\)</span>是最优策略。那这是不是说明还有其它最优的策略呢?</p><div id="suff-nece" class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理2.5</span>.</span><span class="statement-spah"> </span><span class="math inline">\(\nu\in\mathcal{M}\)</span>是最优停时的充要条件是:</p><ol type="1"><li><span class="math inline">\(S_\nu=X_\nu\)</span>。</li><li><span class="math inline">\(\{S_{n\wedge\nu}\}\)</span> 是鞅。</li></ol></div><p><a href="#suff-nece" title="定理 2.5">定理 2.5</a>告诉我们,前面采用的相亲策略是所有最优策略中时间成本最低的:即若 <span class="math inline">\(\nu\)</span> 是任意最优停时,则 <span class="math inline">\(\tau\leq\nu\)</span> (回顾一下 <span class="math inline">\(\tau\)</span> 的定义)。</p><p>我们还可以给出最优策略中最大的一个来:</p><div id="largest" class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理2.6</span>.</span><span class="statement-spah"> </span>设 <span class="math inline">\(S_n=M_n-A_n\)</span> 是 Snell 包络 <span class="math inline">\(\{S_n\}\)</span> 的 Doob-Meyer 分解,其中 <span class="math inline">\(\{M_n\}\)</span> 是鞅,<span class="math inline">\(A_n\)</span> 是可料递增过程,由 <span class="math display">\[A_n=\sum_{k=1}^n(S_{k-1}-\mathbb{E}[S_k|\mathcal{F}_{k-1}]) \]</span> 给出。定义</p><p><span class="math display">\[\tau_\max = \begin{cases}N & A_N=0,\\\min\{n\geq0 \mid A_{n+1}>0\} & A_N>0.\end{cases}\]</span></p><p>则 <span class="math inline">\(\tau_\max\)</span>是所有最优停时中最大的。</p></div><p>这个策略在现实很有用,它是以最优方式行使美式期权的最大停时。</p><p><a href="#suff-nece" title="定理 2.5">定理 2.5</a> 和 <a href="#largest" title="定理 2.6">定理 2.6</a>的证明都不难,这里就省略了。读者可以参考 <span class="citation" data-cites="RiskNeutralValuation">(<a href="#ref-RiskNeutralValuation" role="doc-biblioref">Bingham and Kiesel 2004, sec. 3.6</a>)</span>。</p><p>我猜某些读者可能会对 <span class="math inline">\(\tau_\max\)</span>对应的相亲策略感兴趣,因为这个策略有点渣男:它会在保证娶到最优女士的前提下和尽可能多的女士相亲。不过在<span class="math inline">\(\{X_n\}\)</span> 是 <span class="math inline">\(\mathrm{i.i.d}\)</span>的情形,你还是死了这份心吧。因为这时 <span class="math display">\[A_n=\sum_{k=1}^n(S_{k-1}-\mathbb{E}S_k)=\sum_{k=1}^n\max\{X_{k-1}-\mathbb{E}S_k,0\}.\]</span>所以使得 <span class="math inline">\(A_{n+1}>0\)</span> 成立的最小<span class="math inline">\(n\)</span> 正是使得 <span class="math inline">\(X_n>\mathbb{E}S_{n+1}\)</span> 成立的最小 <span class="math inline">\(n\)</span>,即使得 <span class="math inline">\(X_n=S_n\)</span> 成立的最小 <span class="math inline">\(n\)</span>。这不就是前面见好就收的策略 <span class="math inline">\(\tau\)</span> 嘛!换句话说,在 <span class="math inline">\(\{X_n\}\)</span> 是 <span class="math inline">\(\mathrm{i.i.d}\)</span>的情形,只有一种最优相亲策略!</p><h1 class="unnumbered" id="bibliography">References</h1><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0" role="list"><div id="ref-RiskNeutralValuation" class="csl-entry" role="listitem">Bingham, N. H., and Rüdiger Kiesel. 2004. <em>Risk-Neutral Valuation :Pricing and Hedging of Financial Derivatives / n.h. Bingham and r.Kiesel.</em> Second edition. Springer Finance. London: Springer.</div></div>]]></content>
<summary type="html">
<div class="unnumbered statement question-unnumbered definition">
<p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>假设你是一位大龄男士,准备参加 100 场相亲
(别介意具体数字)。你打算依次与每个女士 <span class="math inline">\(i\)</span> 约会,然后根据印象给她打一个分数 <span class="math inline">\(X_i\)</span>,<span class="math inline">\(X_i\)</span> 的值介于 <span class="math inline">\([0,1]\)</span> 之间。如果你对女士 <span class="math inline">\(i\)</span>
很满意,那么就和她结婚,否则就放弃她,参加下一场相亲,当然拒绝了人家可就没有回头的机会了。如果你拒绝了前
99 位女士,那么不论第 100
次相亲结果如何你都只能和最后这位女士结婚。在相亲之前,你对这些女士的情况一无所知,所以姑且假定她们的分数
<span class="math inline">\(X_i\)</span> 都是 <span class="math inline">\([0,1]\)</span>
上均匀分布的独立的随机变量。问题是:应该采取怎样的相亲策略,才能娶到你最中意的女士?</p>
</div></summary>
<category term="Williams 概率和鞅" scheme="https://pywonderland.com/categories/Williams-%E6%A6%82%E7%8E%87%E5%92%8C%E9%9E%85/"/>
</entry>
<entry>
<title>三质点弹簧系统的简正模式</title>
<link href="https://pywonderland.com/three-mass-spring-system/"/>
<id>https://pywonderland.com/three-mass-spring-system/</id>
<published>2011-08-21T16:00:00.000Z</published>
<updated>2024-11-25T05:57:04.317Z</updated>
<content type="html"><![CDATA[<p>今天的问题是群表示论在物理中的一个小应用:</p><div class="unnumbered statement question-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>平面上有三个质量均为 <span class="math inline">\(m\)</span> 的质点 <span class="math inline">\(A,B,C\)</span>,它们位于正三角形的三个顶点。质点之间两两由弹簧相连,三个弹簧完全一样。弹簧质量忽略不计。</p><p><img style="margin:0px auto;display:block" width="250" src="/images/mass-spring/abc.png"></p><p>初始时所有质点都处于静止状态,弹簧之间没有张力。假设给这三个质点分别施加一个初始速度,使这三个质点在平面内作刚体运动,不考虑任何摩擦力和空气阻力,那么这个系统的简正模式(normal mode) 是什么?</p></div><p>这里 <a href="https://en.wikipedia.org/wiki/Normal_mode">简正模式</a>的含义是所有质点按照一个共同的频率和固定的相位关系相对于各自的平衡位置作简谐振动。</p><span id="more"></span><p>比较容易发现的简正模式有:</p><ol type="1"><li><p>平移。初始时给所有质点以同样的速度,它们会继续以相同的速度移动。</p><p><img style="margin:0px auto;display:block" width="250" src="/images/mass-spring/trans.gif"></p></li><li><p>旋转。初始时给所有质点以相同的切向速度,它们会继续绕中心旋转。</p><p><img style="margin:0px auto;display:block" width="250" src="/images/mass-spring/rot.gif"></p></li><li><p>呼吸。初始时给每个质点相同的径向速度,系统会重复膨胀 —收缩的过程。</p><p><img style="margin:0px auto;display:block" width="250" src="/images/mass-spring/breath.gif"></p></li></ol><p>但是要找出其它的简正模式,并保证没有遗漏,就不能只靠想象了。</p><p>我们把这个物理问题转换为一个线性代数问题,然后用一些群表示论的知识解决它。</p><p>这个质点弹簧系统有 6 个自由度 <span class="math inline">\(\{q_1,\ldots,q_6\}\)</span>,其中 <span class="math inline">\((q_1,q_2)\)</span>,<span class="math inline">\((q_3,q_4)\)</span>,<span class="math inline">\((q_5,q_6)\)</span> 分别是质点 <span class="math inline">\(A,B,C\)</span> 在 <span class="math inline">\(x,y\)</span>方向上相对于其平衡位置的位移,如下图所示:</p><p><img style="margin:0px auto;display:block" width="300" src="/images/mass-spring/coords.svg"></p><p>设弹簧的弹性系数为 <span class="math inline">\(k\)</span>,原点在三角形中心,<span class="math inline">\(x\)</span>轴水平向右,于是三个弹簧所含的弹性势能为 (将形变投影到弹簧所在的方向)<span class="math display">\[\begin{align*}V=&\frac{1}{2}k(q_3-q_1)^2+\frac{1}{2}k\left[\frac{-1}{2}(q_5-q_3) +\frac{\sqrt{3}}{2}(q_6-q_4)\right]^2+\\&\frac{1}{2}k\left[\frac{1}{2}(q_1-q_5)+ \frac{\sqrt{3}}{2}(q_2-q_6)\right]^2.\end{align*}\]</span> 这是一个关于 <span class="math inline">\(\mathbf{q}=(q_1,q_2,\ldots,q_6)^T\)</span>的二次型: <span class="math display">\[V=\frac{1}{2}k\,\mathbf{q}^TU\mathbf{q}.\]</span> 其中 <span class="math display">\[U=\frac{1}{4}\begin{pmatrix}5&\sqrt{3}&-4&0&-1&-\sqrt{3}\\\sqrt{3}&3&0&0&-\sqrt{3}&3\\-4&0&5&-\sqrt{3}&-1&\sqrt{3}\\0&0&-\sqrt{3}&3&\sqrt{3}&-3\\-1&-\sqrt{3}&-1&\sqrt{3}&2&0\\-\sqrt{3}&-3&\sqrt{3}&-3&0&6\end{pmatrix}.\]</span></p><p>由牛顿第二定律我们有 <span class="math display">\[m\frac{\mathrm{d}^2q_i}{\mathrm{d}t^2}=-\frac{\partialV}{\partial q_i}=-k\sum_{j=1}^6U_{ij}q_j.\]</span>系统的简正模式就是所有 <span class="math inline">\(q_i\)</span>按照同一个频率 <span class="math inline">\(\omega\)</span>作简谐振动,但振幅可以不同,故而可以设 <span class="math inline">\(q_i=\overline{q}_i\mathrm{e}^{i\omegat}\)</span>,其中 <span class="math inline">\(\overline{q}_i\)</span>是振幅,与时间 <span class="math inline">\(t\)</span> 无关。代入上式得到<span class="math display">\[\frac{m\omega^2}{k}\overline{q}_i =\sum_{j=1}^6U_{ij}\overline{q}_j.\]</span> 即 <span class="math inline">\(U\overline{\mathbf{q}}=\lambda\overline{\mathbf{q}}\)</span>,<span class="math inline">\(\lambda=m\omega^2/k\)</span>,于是简正模式的振幅<span class="math inline">\(\overline{\mathbf{q}}=(\overline{q}_1,\overline{q}_2,\ldots,\overline{q}_6)\)</span>是 <span class="math inline">\(U\)</span> 的特征向量,频率可以从特征值<span class="math inline">\(\lambda\)</span> 得出,从而问题转化为求矩阵<span class="math inline">\(U\)</span> 的特征值和特征向量。由于 <span class="math inline">\(U\)</span> 是一个实对称矩阵,所以一定存在 6个线性无关的特征向量。</p><p>你当然可以直接硬算,但是手动求一个 6阶矩阵的特征值还是一件挺麻烦的事情,鉴于搞数学的人一般比较懒,我们可以换个思路想一想。</p><p>注意到这个系统具有对称性,其对称群是 <span class="math inline">\(S_3\)</span>。<span class="math inline">\(S_3\)</span> 这个群是集合 <span class="math inline">\(\{1,2,3\}\)</span>的置换群,同时也是平面上正三角形的对称群,在后者的情形通常称为二面体群<span class="math inline">\(D_3\)</span>。</p><p>回忆 <span class="math inline">\(S_3\)</span> 的一个表现为 <span class="math display">\[S_3=\{a,b\mid a^2=b^3=(ab)^2=1\}.\]</span>当作为置换群时,<span class="math inline">\(a=(12),\,b=(123)\)</span>。当作为二面体群时,<span class="math inline">\(a=\left(\begin{smallmatrix}-1&0\\0&1\end{smallmatrix}\right)\)</span>是关于 <span class="math inline">\(y\)</span> 轴的反射,<span class="math inline">\(b=\left(\begin{smallmatrix}\cos2\pi/3&-\sin2\pi/3\\\sin2\pi/3&\cos2\pi/3\end{smallmatrix}\right)\)</span>是关于原点角度为 <span class="math inline">\(2\pi/3\)</span>的旋转。</p><p>系统状态 <span class="math inline">\(\mathbf{q}\)</span> 所在的空间<span class="math inline">\(\mathbb{R}^6\)</span> 是三个 <span class="math inline">\(\mathbb{R}^2\)</span> 的直和:<span class="math inline">\(\mathbb{R}^6=\mathbb{R}^2_A\oplus\mathbb{R}^2_B\oplus\mathbb{R}^2_C\)</span>。其中<span class="math inline">\(\mathbb{R}^2_A,\mathbb{R}^2_B,\mathbb{R}^2_C\)</span>分别是 <span class="math inline">\(A,B,C\)</span> 的坐标空间。<span class="math inline">\(S_3\)</span> 在置换这三个直和项的同时,又以 <span class="math inline">\(D_3\)</span>的方式作用在每个直和项上,所以这个表示是 <span class="math inline">\(S_3\)</span> 的置换表示和在 <span class="math inline">\(\mathbb{R}^2\)</span> 上二维表示的张量积。</p><p>例如,对换 <span class="math inline">\((12)\)</span> 交换 <span class="math inline">\(A,B\)</span>,同时将它们的坐标关于 <span class="math inline">\(y\)</span> 轴作反射: <span class="math display">\[\begin{aligned}A\otimes (q_1,q_2) &\xrightarrow{(12)} B\otimes(-q_1,q_2),\\B\otimes (q_3,q_4) &\xrightarrow{(12)} A\otimes(-q_3,q_4).\end{aligned}\]</span></p><p>我们把这个张量积表示记作 <span class="math inline">\(\rho\)</span>,并记 <span class="math display">\[R=\begin{pmatrix}\cos\frac{2\pi}{3}&-\sin\frac{2\pi}{3}\\\sin\frac{2\pi}{3}&\cos\frac{\pi}{3}\end{pmatrix},\quadS=\begin{pmatrix}-1&0\\0&1\end{pmatrix}.\]</span></p><p>我们可以明确写出 <span class="math inline">\(a=(12)\)</span>在这个张量积表示下的矩阵:<span class="math inline">\(a\)</span>作为置换对应的矩阵是 <span class="math display">\[A=\begin{pmatrix}0&1&0\\1&0&0\\0&0&1\end{pmatrix}.\]</span>在二面体群 <span class="math inline">\(D_3\)</span> 中,<span class="math inline">\(a\)</span> 是关于 <span class="math inline">\(y\)</span> 轴的反射,其矩阵为 <span class="math inline">\(S\)</span>,所以 <span class="math inline">\(a\)</span> 在 <span class="math inline">\(\mathbb{R}^3\otimes\mathbb{R}^2\)</span>上的作用对应的矩阵是 Kronecker 乘积 <span class="math inline">\(A\otimesS\)</span>。</p><p>下面列出了 <span class="math inline">\(S_3\)</span> 的各个元素在<span class="math inline">\(\rho\)</span> 下对应的矩阵:</p><p><span class="math display">\[\begin{align*}\rho(e)&=I_3\otimesI_2=\begin{pmatrix}I_2&0&0\\0&I_2&0\\0&0&I_2\end{pmatrix},\\\rho(a)&=\begin{pmatrix}0&1&0\\1&0&0\\0&0&1\end{pmatrix}\otimesS=\begin{pmatrix}0&S&0\\S&0&0\\0&0&S\end{pmatrix},\\\rho(b)&=\begin{pmatrix}0&1&0\\0&0&1\\1&0&0\end{pmatrix}\otimesR=\begin{pmatrix}0&R&0\\0&0&R\\R&0&0\end{pmatrix},\\\rho(b^2)&=\begin{pmatrix}0&0&1\\1&0&0\\0&1&0\end{pmatrix}\otimesR^2=\begin{pmatrix}0&0&R^2\\R^2&0&0\\0&R^2&0\end{pmatrix},\\\rho(ab)&=\begin{pmatrix}0&0&1\\0&1&0\\1&0&0\end{pmatrix}\otimesSR=\begin{pmatrix}0&0&SR\\0&SR&0\\SR&0&0\end{pmatrix},\\\rho(ab^2)&=\begin{pmatrix}1&0&0\\0&0&1\\0&1&0\end{pmatrix}\otimesSR^2=\begin{pmatrix}SR^2&0&0\\0&0&SR^2\\0&SR^2&0\end{pmatrix}.\end{align*}\]</span></p><p><span class="math inline">\(S_3\)</span> 在表示 <span class="math inline">\(\rho(g)\)</span>下显然都是正交矩阵,而且保持系统的势能不变,所以对任何状态 <span class="math inline">\(\mathbf{q}\)</span> 有 <span class="math display">\[V(\rho(g)\mathbf{q})=V(\mathbf{q}),\]</span> 即<span class="math display">\[{\bf q^T}\rho(g)^TU\rho(g){\bfq}=U.\]</span> 由于 <span class="math inline">\(\rho(g)\)</span>正交所以 <span class="math inline">\(\rho(g)U=U\rho(g)\)</span>,从而<span class="math inline">\(U\)</span> 是表示 <span class="math inline">\(\rho\)</span> 的一个自同态。</p><p>在数学上,我们非常喜欢这种与一个表示 <span class="math inline">\(\rho\)</span>交换的矩阵,因为这种矩阵的任何特征子空间都是 <span class="math inline">\(\rho\)</span> 的不变子空间,即 <span class="math inline">\(\rho\)</span> 的子表示,从而可以帮助我们将 <span class="math inline">\(\rho\)</span>分解为一些更简单表示的直和。不过在这个问题中,我们要反过来借助 <span class="math inline">\(\rho\)</span> 的分解来分析 <span class="math inline">\(U\)</span> 的特征子空间的结构。</p><p>我们来确定表示 <span class="math inline">\(\rho\)</span>的结构。注意到 <span class="math inline">\(S_3\)</span> 在 <span class="math inline">\(\{1,2,3\}\)</span>上的置换表示,其特征只有在单位元 <span class="math inline">\(e\)</span>处不为 0(值是 3),在任何非单位元 <span class="math inline">\(g\inS_3\)</span> 处都是 0。这个表示是张量积表示 <span class="math inline">\(\rho\)</span> 的分量,所以 <span class="math inline">\(\rho\)</span> 的特征 <span class="math inline">\(\chi\)</span> 也具有此性质。显然 <span class="math inline">\(\chi(e)=6\)</span>,于是 <span class="math inline">\(\chi\)</span> 与 <span class="math inline">\(S_3\)</span>的正则表示的特征完全相同,从而同构于正则表示,从而 <span class="math inline">\(\chi\)</span>可以分解为两个一次表示和两个二次不可约表示的和: <span class="math display">\[\chi = \chi_1 + \chi_2 + \chi_3+\chi_4.\]</span>其中 <span class="math inline">\(\chi_1\)</span> 是平凡表示的特征,<span class="math inline">\(\chi_2\)</span> 是符号表示的特征(偶置换为+1,奇置换为 -1),这两个特征都是一次的。<span class="math inline">\(\chi_3=\chi_4\)</span> 是 <span class="math inline">\(S_3\)</span>作为二面体群的二维不可约表示的特征。</p><p>记 <span class="math inline">\(\chi_i\)</span> 对应的不可约模是 <span class="math inline">\(V_i\)</span>,则 <span class="math inline">\(\dimV_1=\dim V_2=1\)</span>,<span class="math inline">\(\dim V_3=\dimV_4=2\)</span> 并且 <span class="math display">\[\mathbb{R}^6=V_1\oplusV_2\oplus V_3\oplus V_4.\]</span></p><p><span class="math inline">\(\mathbb{R}^6\)</span> 是 <span class="math inline">\(U\)</span> 的特征子空间的直和,而 <span class="math inline">\(U\)</span> 的特征子空间都是 <span class="math inline">\(\rho\)</span> 的不变子空间,从而可以分解为 <span class="math inline">\(\rho\)</span> 的不可约模的直和。这说明每个 <span class="math inline">\(V_i\)</span> 都包含在 <span class="math inline">\(U\)</span> 的某个特征值 <span class="math inline">\(\lambda_i\)</span> 的特征子空间中。即 <span class="math inline">\(U\)</span> 在 <span class="math inline">\(V_i\)</span> 上的作用是数乘 <span class="math inline">\(\lambda_i\)</span>。</p><p>从 <span class="math inline">\(V_1,\ldots,V_4\)</span>中分别选择一组基,它们合起来构成 <span class="math inline">\(\mathbb{R}^6\)</span> 的一组基。在这组基下 <span class="math inline">\(U\)</span> 是对角矩阵,形如 <span class="math display">\[U=\begin{pmatrix}\lambda_1&0&0&0\\0&\lambda_2&0&0\\0&0&\lambda_3I_2&0\\0&0&0&\lambda_4I_2\end{pmatrix}.\]</span>同时 <span class="math inline">\(\rho(g)\)</span> 形如 <span class="math display">\[\rho(g)=\begin{pmatrix}D_1&0&0&0\\0&D_2&0&0\\0&0&D_3&0\\0&0&0&D_4\end{pmatrix}.\]</span></p><p>我们来计算 <span class="math inline">\(\rho(g)U\)</span> 对不同 <span class="math inline">\(g\in S_3\)</span> 的迹。首先注意到 <span class="math display">\[\mathop{\mathrm{tr}}{\rho(g)U}=\mathop{\mathrm{tr}}{\begin{pmatrix}\lambda_1D_1&0&0&0\\0&\lambda_2D_2&0&0\\0&0&\lambda_3D_3&0\\0&0&0&\lambda_4D_4\end{pmatrix}}=\sum_{i=1}^4\lambda_i\chi_i(g).\]</span></p><p>另一方面,我们对 <span class="math inline">\(S_3\)</span> 中的 <span class="math inline">\(e,a,b\)</span> 这三个元素,根据前面列出的 <span class="math inline">\(\rho(g)\)</span> 矩阵,计算其与 <span class="math inline">\(U\)</span>的乘积,再求迹得到(这是本文计算量最大的部分!) <span class="math display">\[\begin{align*}\mathop{\mathrm{tr}}{\rho(e)U}&=\mathop{\mathrm{tr}}{U}=\frac{1}{4}(5+3+5+3+2+6)=6,\\\mathop{\mathrm{tr}}{\rho(a)U}&=\mathop{\mathrm{tr}}{\begin{pmatrix}0&S&0\\S&0&0\\0&0&S\end{pmatrix}\frac{1}{4}\begin{pmatrix}5&\sqrt{3}&-4&0&-1&-\sqrt{3}\\\sqrt{3}&3&0&0&-\sqrt{3}&3\\-4&0&5&-\sqrt{3}&-1&\sqrt{3}\\0&0&-\sqrt{3}&3&\sqrt{3}&-3\\-1&-\sqrt{3}&-1&\sqrt{3}&2&0\\-\sqrt{3}&-3&\sqrt{3}&-3&0&6\end{pmatrix}}\\&=\frac{1}{4}(4+0+4+0-2+6)=3,\\\mathop{\mathrm{tr}}{\rho(b)U}&=\mathop{\mathrm{tr}}{\begin{pmatrix}0&R&0\\0&0&R\\R&0&0\end{pmatrix}\frac{1}{4}\begin{pmatrix}5&\sqrt{3}&-4&0&-1&-\sqrt{3}\\\sqrt{3}&3&0&0&-\sqrt{3}&3\\-4&0&5&-\sqrt{3}&-1&\sqrt{3}\\0&0&-\sqrt{3}&3&\sqrt{3}&-3\\-1&-\sqrt{3}&-1&\sqrt{3}&2&0\\-\sqrt{3}&-3&\sqrt{3}&-3&0&6\end{pmatrix}}\\&=\frac{1}{4}(2+0-1+3+2+0)=\frac{3}{2}.\end{align*}\]</span> 于是我们得到三个方程 <span class="math display">\[\begin{align*}6&=\lambda_1+\lambda_2+2(\lambda_3+\lambda_4),\\3&=\lambda_1-\lambda_2,\\3/2&=\lambda_1+\lambda_2-(\lambda_3+\lambda_4).\end{align*}\]</span> 于是 <span class="math inline">\(\lambda_1=3\)</span>,<span class="math inline">\(\lambda_2=0\)</span>,<span class="math inline">\(\lambda_3+\lambda_4=3/2\)</span>。</p><p>我们还缺一个方程!可以用 <span class="math inline">\(\mathop{\mathrm{tr}}{U^2}=\lambda_1^2+\lambda_2^2+2(\lambda_3^2+\lambda_4^2)\)</span>来做,但手算 <span class="math inline">\(U^2\)</span>的话计算量还是不小的。更简单的办法是用物理直观:我们已经看到平移和旋转是两种简正模式,这两种模式下质点的振动频率是0,而平移包含了 <span class="math inline">\(x\)</span> 轴和 <span class="math inline">\(y\)</span> 轴两个线性无关的方向上的平移,所以<strong>0 作为 <span class="math inline">\(U\)</span>的特征值至少是三重的</strong>。我们已经解得 <span class="math inline">\(\lambda_2=0\)</span> 是一个,所以 <span class="math inline">\(\lambda_3\)</span> 和 <span class="math inline">\(\lambda_4\)</span> 中必然还有一个是 0,不妨设<span class="math inline">\(\lambda_3=0\)</span>,则 <span class="math inline">\(\lambda_4=3/2\)</span>。</p><p>得到了 <span class="math inline">\(\lambda_1,\lambda_2,\lambda_3,\lambda_4\)</span>,对应的频率就可以用<span class="math inline">\(\omega_i=\sqrt{\frac{\lambda_ik}{m}}\)</span>算出来。</p><p>总结一下,系统总共有 4 种简正模式:</p><ol type="1"><li><p>平凡表示 (<span class="math inline">\(\lambda_1=3\)</span>)对应的简正模式是呼吸,其频率为 <span class="math inline">\(\omega=\sqrt{\frac{3k}{m}}\)</span>:</p><p><img style="margin:0px auto;display:block" width="250" src="/images/mass-spring/breath.gif"></p></li><li><p>符号表示 (<span class="math inline">\(\lambda_2=0\)</span>)对应的简正模式是旋转,其频率 <span class="math inline">\(\omega=0\)</span>:</p><p><img style="margin:0px auto;display:block" width="250" src="/images/mass-spring/rot.gif"></p></li><li><p>第一个二维不可约表示 (<span class="math inline">\(\lambda_3=0\)</span>)包含了两种简正模式,它们分别是沿着 <span class="math inline">\(x\)</span> 方向和 <span class="math inline">\(y\)</span> 方向的平移,其频率 <span class="math inline">\(\omega=0\)</span>。</p><p><img style="margin:0px auto;display:block" width="250" src="/images/mass-spring/trans.gif"></p></li><li><p>第二个二维不可约表示 (<span class="math inline">\(\lambda_4=3/2\)</span>)也包含了两种简正模式,它们分别是两个不同方向上的“鼓掌”:</p><p><img style="margin:0px auto;display:block" width="250" src="/images/mass-spring/clap.gif"></p><p>另一种可以由上面的旋转 <span class="math inline">\(2\pi/3\)</span>后得到。这两个简正模式的频率都是 <span class="math inline">\(\omega=\sqrt{\frac{3k}{2m}}\)</span>。</p></li></ol><p>至此我们就求出了系统的全部简正模式。</p>]]></content>
<summary type="html">
<p>今天的问题是群表示论在物理中的一个小应用:</p>
<div class="unnumbered statement question-unnumbered definition">
<p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>平面上有三个质量均为 <span class="math inline">\(m\)</span> 的质点 <span class="math inline">\(A,B,C\)</span>,它们位于正三角形的三个顶点。质点之间两两由弹簧相连,三个弹簧完全一样。弹簧质量忽略不计。</p>
<p><img style="margin:0px auto;display:block" width="250" src="/images/mass-spring/abc.png"></p>
<p>初始时所有质点都处于静止状态,弹簧之间没有张力。假设给这三个质点分别施加一个初始速度,使这三个质点在平面内作刚体运动,不考虑任何摩擦力和空气阻力,那么这个系统的简正模式
(normal mode) 是什么?</p>
</div>
<p>这里 <a href="https://en.wikipedia.org/wiki/Normal_mode">简正模式</a>
的含义是所有质点按照一个共同的频率和固定的相位关系相对于各自的平衡位置作简谐振动。</p></summary>
<category term="有限群表示与结合代数" scheme="https://pywonderland.com/categories/%E6%9C%89%E9%99%90%E7%BE%A4%E8%A1%A8%E7%A4%BA%E4%B8%8E%E7%BB%93%E5%90%88%E4%BB%A3%E6%95%B0/"/>
</entry>
<entry>
<title>模式的等待时间与反直觉概率</title>
<link href="https://pywonderland.com/pattern-occurrence/"/>
<id>https://pywonderland.com/pattern-occurrence/</id>
<published>2011-06-02T16:00:00.000Z</published>
<updated>2024-11-27T12:21:17.303Z</updated>
<content type="html"><![CDATA[<p>著名概率学家 Feller 在他的名著 “An introduction to probability andits applications” 中提到了这样一个实验:</p><div class="unnumbered statement question-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>重复抛掷一枚均匀的硬币,用 <code>H</code>代表正面向上,<code>T</code> 代表背面向上,一直到连续出现 6 次<code>H</code> 为止。这里连续 6 个 <code>H</code> 组成的模式记作<code>HHHHHH</code>,所需要抛掷硬币的次数叫做等待时间。等待时间是一个随机变量,最小值是6,最大值可以是无限。Feller 问:等待时间的均值是多少?</p></div><p>这个问题可以用 Markov链来解,但是非常繁琐。香港中文大学李硕彦教授在他的论文 <span class="citation" data-cites="Li1980">(<a href="#ref-Li1980" role="doc-biblioref">Li 1980</a>)</span>中用离散鞅的知识给出了一个简洁而巧妙的解法,本文就来介绍他的方法。</p><span id="more"></span><h1 id="鞅和赌博序列">鞅和赌博序列</h1><p>我们用 <code>HTHT</code>这个模式为例子,来演示如何求出它的平均等待时间。</p><p>假设有一个赌徒,怀里揣着 1 元钱来到一家赌场,他的目标是赌中<code>HTHT</code> 这个序列。</p><ul><li><p>第一天,他押上这 1 元钱,赌第一次掷硬币的结果是<code>H</code>。如果他赌错了就得空手走人,而赌对的话则可以赢得 2 元(资金翻番) 并留在赌场。</p></li><li><p>第二天,他押上全部的 2 元,赌第二次掷硬币的结果是<code>T</code>。跟以前一样,赌错了空手走人,赌对了的话则资金翻番变成 4元并留在赌场。</p></li><li><p>第三天,他押上全部的 4 元,赌第三次掷硬币的结果是<code>H</code>。赌错了空手走人,赌对了的话则资金翻番变成 8元并留在赌场。</p></li><li><p>第四天,他押上全部的 8 元,赌第四次掷硬币的结果是<code>T</code>。赌错了空手走人,赌对了资金翻番变成 16元,赌局结束。</p></li></ul><p>这个赌局很像电视节目里的闯关游戏,赌局一共有 4关,赌徒要一关一关的闯,中间任何一关输了都要空手走人。</p><p>赌局对赌徒和庄家来讲都是公平的:大家在期望的意义下都是不赔不赚。每一天,赌徒都以1/2 的概率输光赌本,也以 1/2 的概率将赌本翻番。</p><p>现在假设有一个赌博团伙,他们每天都派一个人到赌场赌博,每个赌徒的赌局与上面的描述相同,不同的赌徒的赌局互相独立。我们用<span class="math inline">\(\{X_n,n=0,1,2,\ldots\}\)</span> 表示第 <span class="math inline">\(n\)</span> 天结束以后这个团伙的「净收益」,其中<span class="math inline">\(X_0=0\)</span>。由于赌局是公平的,因此 <span class="math inline">\(\{X_n\}\)</span> 是一个鞅。</p><p>设 <span class="math inline">\(\tau\)</span> 是模式 <code>HTHT</code>的等待时间,则 <span class="math inline">\(\tau\)</span>是一个停时。不难验证 <span class="math inline">\(\{X_n\}\)</span> 和<span class="math inline">\(\tau\)</span> 满足 <a href="https://en.wikipedia.org/wiki/Optional_stopping_theorem">Doob可料停时定理</a> 的条件</p><div id="doob-------------------" class="statement sta_doob_______ plain unnumbered"><p><span class="statement-heading"><span class="statement-label">Doob可料停时定理</span>.</span><span class="statement-spah"> </span>设 <span class="math inline">\(\{X_n, n=0,1,2,\ldots\}\)</span> 是一个鞅,<span class="math inline">\(\tau\)</span> 是停时且满足</p><ol type="1"><li><span class="math inline">\(\mathbb{E}\tau <\infty\)</span>,</li><li>存在常数 <span class="math inline">\(M\)</span> 使得 <span class="math inline">\(|X_{n+1}-X_{n}|\leq M\)</span> 对任何 <span class="math inline">\(n\)</span> 都几乎处处成立。</li></ol><p>则 <span class="math inline">\(\mathbb{E}X_\tau =\mathbb{E}X_0\)</span>。</p></div><p>因此在我们的问题中 <span class="math inline">\(\mathbb{E}X_\tau =\mathbb{E}X_0 = 0\)</span>。</p><p>显然 <span class="math inline">\(X_\tau=W-\tau\)</span>,这里 <span class="math inline">\(W\)</span> 表示第 <span class="math inline">\(\tau\)</span>天结束时留在赌场内的赌徒的资金之和,<span class="math inline">\(\tau\)</span> 表示第 <span class="math inline">\(\tau\)</span> 天结束时赌博团伙总共派出了 <span class="math inline">\(\tau\)</span> 个赌徒,他们带入赌场的赌本总共是<span class="math inline">\(\tau\)</span> 元。根据上面的讨论,有 <span class="math inline">\(\mathbb{E}W=\mathbb{E}\tau\)</span>成立。这里的关键在于 <span class="math inline">\(W\)</span>不是一个随机变量,而是一个可以算出来的常数!</p><p>我们仔细分析一下当第 <span class="math inline">\(\tau\)</span>天结束的时候,哪些赌徒还在赌场内,他们各自有多少钱。由于 <span class="math inline">\(\tau\)</span>是首次有某个赌徒闯关成功的时刻,所以只有第 <span class="math inline">\(\tau-3\)</span> 到第 <span class="math inline">\(\tau\)</span> 天来赌场的这 4个赌徒还有可能留在赌场,更早的赌徒都输光走人了。其中第 <span class="math inline">\(\tau-3\)</span>天来的赌徒最幸运,赌对了全部的序列,他还有 16 元;第 <span class="math inline">\(\tau-1\)</span> 天来的赌徒也不错,他赌对了<code>HT</code>,他还有 4 元,因此赌徒们总共有 <span class="math inline">\(W=16+4=20\)</span> 元,即 <span class="math inline">\(\mathbb{E}\tau=20\)</span>。</p><p>这里第 <span class="math inline">\(\tau-1\)</span>天来的赌徒最有趣:他赌的明明是 <code>HTHT</code> 的前缀<code>HT</code>,但是由于 <code>HT</code> 恰好也是 <code>HTHT</code>的后缀,因此他也能赢到钱!</p><p>这个推理完全适用于一般的情形:设 <span class="math inline">\(P=(a_1,a_2,\cdots,a_m)\)</span> 是一个给定的由<code>T</code> 和 <code>H</code>组成的模式,我们计算它的全部既是前缀又是后缀的子序列的长度,设为 <span class="math inline">\(l_1,\cdots,l_r\)</span>,则 <span class="math inline">\(P\)</span> 的等待时间 <span class="math inline">\(\tau\)</span> 的期望为 <span class="math display">\[\mathbb{E}\tau =2^{l_1}+\cdots+2^{l_r}.\]</span></p><p>回到开头的例子:<code>HHHHHH</code>的每一个前缀都同时是它的后缀,因此它的平均等待时间为 <span class="math display">\[2^6+2^5+2^4+2^3+2^2+2^1 = 126.\]</span>所以一个模式的平均等待时间完全由它的自匹配的程度决定。</p><p>把上面的方法稍作修改,还可以用来计算 <span class="math inline">\(\tau\)</span> 的生成函数 <span class="math display">\[ \mathbb{E}[s^\tau] =\sum_{n=1}^\infty\mathbb{P}(\tau=n)s^n.\]</span> 为此只要假设第 <span class="math inline">\(n\)</span> 天来的赌徒怀里揣的钱是 <span class="math inline">\(s^n(0<s<1)\)</span> 即可,这里不再赘述。</p><h1 id="多个模式的等待时间与获胜概率">多个模式的等待时间与获胜概率</h1><p>假设同时有多个模式 <span class="math inline">\(A_1,\ldots,A_m\)</span>加入「赛跑」,我们想计算它们各自胜出的概率。用数学公式表示,就是设 <span class="math inline">\(A_i\)</span> 的等待时间为 <span class="math inline">\(\tau_i\)</span>,令 <span class="math display">\[\tau=\min\{\tau_1,\ldots,\tau_m\},\]</span> 则<span class="math inline">\(\tau\)</span>表示赛跑过程中冠军「撞线」的时刻,又令 <span class="math inline">\(p_i=\mathbb{P}(\tau=\tau_i)\)</span>,则 <span class="math inline">\(p_i\)</span> 表示模式 <span class="math inline">\(A_i\)</span> 「夺冠」 的概率。我们想计算出每个<span class="math inline">\(p_i\)</span> 的值来。</p><p>为此先给一个定义:</p><div class="unnumbered statement definition-unnumbered plain"><p><span class="statement-heading"><span class="statement-label">定义</span>.</span><span class="statement-spah"></span>设 <span class="math inline">\(A\)</span> 和 <span class="math inline">\(B\)</span> 是两个给定的模式,且 <span class="math inline">\(A\)</span> 和 <span class="math inline">\(B\)</span>都不是对方的连续子序列。我们计算所有既是 <span class="math inline">\(A\)</span> 的后缀又是 <span class="math inline">\(B\)</span> 的前缀的全部子序列,设它们的长度为<span class="math inline">\(l_1,\cdots,l_r\)</span>,定义 <span class="math inline">\(A\)</span> 和 <span class="math inline">\(B\)</span> 的匹配指数为 <span class="math display">\[A\ast B = 2^{l_1}+\cdots+2^{l_r}.\]</span> 如果<span class="math inline">\(A\)</span> 的任何后缀都不是 <span class="math inline">\(B\)</span> 的前缀则 <span class="math inline">\(A\ast B\)</span> 定义为 0。特别当 <span class="math inline">\(A=B\)</span> 时,<span class="math inline">\(A\astA\)</span> 就是前面计算的 <span class="math inline">\(A\)</span>的平均等待时间,这个值又叫做 <span class="math inline">\(A\)</span>的自匹配指数。</p></div><p>我们要证明这样一个引理 :</p><div class="unnumbered statement lemma-unnumbered plain"><p><span class="statement-heading"><span class="statement-label">引理</span>.</span><span class="statement-spah"></span>如果已知掷硬币的结果是以模式 <span class="math inline">\(A\)</span> 开头的,那么距离模式 <span class="math inline">\(B\)</span> 出现还需要等待的时间的期望为 <span class="math display">\[\mathbb{E}\tau_{AB} = B\ast B -A\astB.\]</span></p></div><p>引理的证明:仍然是采用赌博序列的方法,每天来的赌徒赌的是模式 <span class="math inline">\(B\)</span>,只不过这个时候我们已经知道了前 <span class="math inline">\(k\)</span> 次赌博的结果是模式 <span class="math inline">\(A\)</span>(假设序列 <span class="math inline">\(A\)</span> 的长度为 <span class="math inline">\(k\)</span>),所以不难算出前 <span class="math inline">\(k\)</span> 天赌博团伙的总资金为 <span class="math inline">\(A\ast B\)</span> 元。由于赌局始终是公平的,所以从<span class="math inline">\(k+1\)</span> 天起,直到模式 <span class="math inline">\(B\)</span> 出现的这 <span class="math inline">\(\tau_{AB}\)</span> 天里,赌徒们的净收益期望应该是0。到模式 <span class="math inline">\(B\)</span>出现时,赌徒们的资金将变成 <span class="math inline">\(B\ast B\)</span>元,所以这 <span class="math inline">\(\tau_{AB}\)</span>天中赌徒们的资金增加了 <span class="math inline">\(B\ast B-A\astB\)</span> 元,扣除他们的投入 <span class="math inline">\(\tau_{AB}\)</span>元,就是这段时间的净收益,其期望为 0: <span class="math display">\[\mathbb{E} [B\ast B-A\ast B-\tau_{AB}]=0.\]</span> 引理证毕。</p><p>接下来叙述并证明一个一般的结论:</p><div class="unnumbered statement theorem-unnumbered plain"><p><span class="statement-heading"><span class="statement-label">定理</span>.</span><span class="statement-spah"></span>设 <span class="math inline">\(A_1\)</span>, <span class="math inline">\(A_2\)</span>, <span class="math inline">\(\cdots\)</span>, <span class="math inline">\(A_m\)</span> 是 <span class="math inline">\(m\)</span>个事先给定的且两两互不嵌套的模式,记矩阵 <span class="math display">\[M=\begin{pmatrix} A_1\ast A_1 & A_1\astA_2&\cdots& A_1\ast A_m\\ A_2\ast A_1&A_2\astA_2&\cdots& A_2\ast A_m\\\cdots&\cdots&\cdots&\cdots\\ A_m\ast A_1&A_m\astA_2&\cdots&A_m\ast A_m\end{pmatrix},\]</span> <span class="math display">\[\pi = (p_1,p_2,\cdots,p_m)^T,\quad\mathbf{1}=(1,1,\cdots,1)^T,\]</span> 则 <span class="math inline">\(M\)</span> 是可逆矩阵,并且 <span class="math display">\[M\pi =\mathbb{E}[\tau]\mathbf{1}.\]</span></p></div><p>在证明定理之前,先说说怎样根据定理的结论来计算 <span class="math inline">\(\mathbb{E}[\tau]\)</span> 和概率分布向量 <span class="math inline">\(\pi\)</span>。首先解出 <span class="math inline">\(MY=\mathbf{1}\)</span> 的解 <span class="math inline">\(Y=(y_1,y_2,\cdots,y_m)^T\)</span>来。根据可逆矩阵解的唯一性,必然有 <span class="math inline">\(\pi=\mathbb{E}[\tau]Y\)</span>。但是 <span class="math inline">\(\pi\)</span> 是一个概率分布,它的所有分量之和为1,因此 <span class="math display">\[\mathbb{E}[\tau]=\frac{1}{y_1+y_2+\cdots+y_m}.\]</span></p><p><span class="math inline">\(M\)</span>是可逆矩阵这一点是需要证明的,本文就省略了。事实上 <span class="math inline">\(A_i\)</span> 之间两两互不嵌套这个条件就可以保证<span class="math inline">\(M\)</span> 是可逆的。</p><p>有了 <span class="math inline">\(Y\)</span> 和 <span class="math inline">\(\mathbb{E}[\tau]\)</span> 自然立刻就得到了 <span class="math inline">\(\pi\)</span>。</p><p>定理的证明:我们有 <span class="math display">\[\mathbb{E}[\tau_i] =\mathbb{E}[\tau] + \mathbb{E}[\tau_i-\tau] =\mathbb{E}[\tau]+\sum_{j=1}^m p_j\mathbb{E}[\tau_i-\tau|\tau=\tau_j].\]</span>根据引理, <span class="math display">\[\mathbb{E}[\tau_i-\tau|\tau=\tau_j] = A_i\astA_i-A_j\ast A_i,\]</span> 因此 <span class="math display">\[A_i\astA_i=\mathbb{E}[\tau]+A_i\ast A_i-\sum_{j=1}^np_j A_j\ast A_i.\]</span>这就证明了定理。</p><h1 id="penney-游戏">Penney 游戏</h1><p>Penney 游戏是两个玩家 Bob 和 Alice的博弈游戏,它以掷硬币为工具:游戏开始前,两人各自选择一个长度为 3 的由<code>H</code> 和 <code>T</code> 组成的模式,比如说 Bob 选择<code>HHH</code>,Alice 选择<code>THH</code>,然后掷硬币直到其中一人选择的模式首先出现,先出现的一方获胜。</p><p>Penney 游戏有一个独特之处:</p><blockquote><p>假设 Bob 先选择他的序列,则不论 Bob 怎样选,Alice总可以「针锋相对」地选一个合适的序列,使得自己的获胜概率更高。总而言之,Penney游戏是所谓的「后发制人,先发者制于人」。</p></blockquote><p>这个有点类似于我们都熟悉的「剪子,石头,布」游戏。在 Penney游戏中,各种策略循环相克,<a href="http://en.wikipedia.org/wiki/Penney's_game">维基百科</a>中给出了各种情形下二人的获胜概率之比。在这个例子中,Alice 的获胜概率为7/8。</p><p>Penney 游戏是非传递博弈的典型例子:策略 <span class="math inline">\(A\)</span> 优于 <span class="math inline">\(B\)</span>,<span class="math inline">\(B\)</span>优于 <span class="math inline">\(C\)</span> 并不能推出 <span class="math inline">\(A\)</span> 优于 <span class="math inline">\(C\)</span>。</p><p>在只有两个模式 <span class="math inline">\(A\)</span> 和 <span class="math inline">\(B\)</span>的情形,二者各自获胜的概率有一个很简单的表达式:</p><div class="unnumbered statement corollary-unnumbered plain"><p><span class="statement-heading"><span class="statement-label">推论</span>.</span><span class="statement-spah"></span>设 <span class="math inline">\(A,B\)</span>是两个给定的序列,它们互不为对方的连续子序列,则序列 <span class="math inline">\(B\)</span> 和序列 <span class="math inline">\(A\)</span> 的获胜概率之比为 <span class="math inline">\(p_B:p_A = (A\ast A-A\ast B):(B\ast B-B\astA)\)</span>。</p></div><h1 id="还有更不可思议的事情">还有更不可思议的事情</h1><p>我们已经介绍了怎样计算一个模式的平均等待时间,以及多个模式同时「赛跑」时各自的获胜概率。我们现在来看看<code>THTH</code> 和 <code>HTHH</code> 比赛的结果如何。</p><p>首先不难算出 <code>THTH</code> 的平均等待时间是 20,<code>HTHH</code>的平均等待时间是 18,也就是说 <code>THTH</code>跑的慢一些,<code>HTHH</code>跑的快一些,那这是不是意味着让它俩赛跑的话,<code>HTHH</code>获胜的概率更大啊?</p><p>答案是否定的,这其实是一个一面倒的竞赛:</p><blockquote><p>看起来慢一些的模式 <code>THTH</code>,其与 <code>HTHH</code>的胜算之比为 9 比 5,即平均每 14 场赛跑,<code>THTH</code> 会赢 9场,而貌似快一些的 <code>HTHH</code> 倒只赢 5 场。</p></blockquote><p>为什么会出现这种反直觉的现象呢?其实「跑得慢」和「赢得多」并不矛盾。我们随便看一个由<code>H</code> 和 <code>T</code> 组成的随机序列:</p><p>HHTHT<font color="red">H</font><font color="green">H</font>THT<font color="red">H</font><font color="green">H</font>TTTHHTH<font color="green">H</font>HHTTHHTTHTTHT<font color="red">H</font><font color="green">H</font>TH<font color="green">H</font>TH<font color="green">H</font>TH<font color="green">H</font>HHTTHTTTTTTHTTHT<font color="red">H</font>TTHHTH<font color="green">H</font>HHHHTTHT<font color="red">H</font><font color="green">H</font>THT<font color="red">H</font><font color="green">H</font>TTTTTHTTHHHHHHTH<font color="green">H</font>HTTTHTTTHTTTHHHTH<font color="green">H</font>THT<font color="red">H</font>TTTHTTHTTHT<font color="red">H</font><font color="green">H</font>TH<font color="green">H</font>TTHT<font color="red">H</font><font color="green">H</font>HTTHH…</p><p>其中分别用红色和绿色标记了模式 <code>THTH</code> 和 <code>HTHH</code>的出现的位置。注意到任何绿色 <code>H</code>后面连续的三个字符中绝对不会出现红色 <code>H</code>,而大约一半的红色的<code>H</code> 后面紧跟一个绿色的<code>H</code>。所以从一个随机序列中任选一点作为起点开始比赛,那么在红色<code>H</code> 先撞线的比赛中,第二个撞线的绿色 <code>H</code>往往会只落后一个身位,但是在绿色 <code>H</code> 先撞线的比赛中,红色的<code>H</code> 至少要落后四个身位以上。</p><p>用足球来类比,领先一个身位就好比取得一个净胜球。净胜球少和积分领先并不矛盾。<code>THTH</code>击败 <code>HTHH</code> 的方法类似「一比零主义」:虽然平均下来<code>THTH</code>耗时长一些(每场净胜球少),但积分是领先的(赢的场数多)。</p><h1 class="unnumbered" id="bibliography">References</h1><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0" role="list"><div id="ref-Li1980" class="csl-entry" role="listitem">Li, Shuo-Yen Robert. 1980. <span>“<span class="nocase">A MartingaleApproach to the Study of Occurrence of Sequence Patterns in RepeatedExperiments</span>.”</span> <em>The Annals of Probability</em> 8 (6):1171–76. <a href="https://doi.org/10.1214/aop/1176994578">https://doi.org/10.1214/aop/1176994578</a>.</div></div>]]></content>
<summary type="html">
<p>著名概率学家 Feller 在他的名著 “An introduction to probability and
its applications” 中提到了这样一个实验:</p>
<div class="unnumbered statement question-unnumbered definition">
<p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>重复抛掷一枚均匀的硬币,用 <code>H</code>
代表正面向上,<code>T</code> 代表背面向上,一直到连续出现 6 次
<code>H</code> 为止。这里连续 6 个 <code>H</code> 组成的模式记作
<code>HHHHHH</code>,所需要抛掷硬币的次数叫做等待时间。等待时间是一个随机变量,最小值是
6,最大值可以是无限。Feller 问:等待时间的均值是多少?</p>
</div>
<p>这个问题可以用 Markov
链来解,但是非常繁琐。香港中文大学李硕彦教授在他的论文 <span class="citation" data-cites="Li1980">(<a href="#ref-Li1980" role="doc-biblioref">Li 1980</a>)</span>
中用离散鞅的知识给出了一个简洁而巧妙的解法,本文就来介绍他的方法。</p></summary>
<category term="Williams 概率和鞅" scheme="https://pywonderland.com/categories/Williams-%E6%A6%82%E7%8E%87%E5%92%8C%E9%9E%85/"/>
</entry>
<entry>
<title>洛奇绵羊问题</title>
<link href="https://pywonderland.com/mabinogion-sheep-problem/"/>
<id>https://pywonderland.com/mabinogion-sheep-problem/</id>
<published>2011-04-08T00:00:00.000Z</published>
<updated>2024-11-25T13:14:06.351Z</updated>
<content type="html"><![CDATA[<p><img style="margin:0px auto;display:block" src="/images/midjourney/mabinogion.png" width="350"></p><p>今天的问题源自中世纪威尔士人的故事集《Mabinogion》中的一段:</p><blockquote><p>一个男孩来到了一个美丽的山谷,有一条小河在谷中流淌。他看到河一边的草地上有一群黑绵羊,另一边的草地上有一群白绵羊。羊群被施以一种魔法:每个时刻都恰有一只绵羊发出咩咩的叫声。如果发出叫声的是白绵羊,就会有一只黑绵羊趟过小河跑过来并且变成白绵羊;如果发出叫声的是黑绵羊,则会有一只白绵羊趟过小河跑过去并且变成黑绵羊。每个时刻发出叫声的绵羊是完全随机的,整个过程没有绵羊出生或者死亡,一直持续到所有绵羊都变成同一种颜色为止。</p></blockquote><p>问题是这样的:</p><div class="unnumbered statement question-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>如果男孩可以选择在初始时刻 <span class="math inline">\(0\)</span>,或者是每个魔法时刻 <span class="math inline">\(1,2,\ldots\)</span>结束后将任意数量的白绵羊赶出山谷,那么为了最终得到尽可能多的黑绵羊,他应该采取怎样的策略?</p></div><span id="more"></span><p>洛奇绵羊问题出自 <span class="citation" data-cites="Williams1991">(<a href="#ref-Williams1991" role="doc-biblioref">Williams1991</a>)</span>,是一个很有趣的问题。这种在随机的环境中施加一个控制的力,以最大化期望收益的问题属于随机控制的范畴。</p><p>我们首先说明不论男孩采取怎样的策略,最终羊群都会以概率 1全部变成同一种颜色。</p><p>设 <span class="math inline">\(\Omega=\{(w,b)\in\mathbb{Z}_{\geq0}\times\mathbb{Z}_{\geq0}\}\)</span>是羊群所有可能的状态组成的集合,其中 <span class="math inline">\(w\)</span> 和 <span class="math inline">\(b\)</span>分别表示白绵羊和黑绵羊的数目。男孩采取的一个策略 <span class="math inline">\(S\)</span> 就是从一个状态 <span class="math inline">\((w,b)\)</span> 移动到另一个状态 <span class="math inline">\((w',b')\)</span> 的规则:根据当前 <span class="math inline">\((w,b)\)</span> 的值,男孩决定到底是按兵不动(不做任何干预),还是赶走 <span class="math inline">\(c\)</span>只白绵羊,把状态 <span class="math inline">\((w,b)\)</span> 变成状态<span class="math inline">\((w-c,b)\)</span>,这里 <span class="math inline">\(0<c\leq w\)</span>是一个正整数。如果男孩始终不做任何干预的话,那么羊群状态将始终保持在线段<span class="math display">\[\{ (x,y)\mid x\geq0, y\geq0,x+y=w+b\}\]</span> 上,这是一个互通的 Markov 链,因此以概率 1撞到吸收状态 <span class="math inline">\((0,w+b)\)</span> 或 <span class="math inline">\((w+b,0)\)</span>,即最终变成同一种颜色。如果男孩在某个时刻移走了<span class="math inline">\(c\)</span>只白绵羊,那么系统将会被强制转移到线段 <span class="math display">\[\{(x,y)\mid x\geq0, y\geq0, x+y=w+b-c\}\]</span>上,如此下去。由于男孩只能进行有限次移走绵羊的操作,可见不论男孩策略如何,羊群总是会最终变成同色的。</p><p>对任何策略 <span class="math inline">\(S\)</span>,我们用 <span class="math inline">\(V_S(w,b)\)</span> 表示从 <span class="math inline">\((w,b)\)</span> 状态出发,在策略 <span class="math inline">\(S\)</span> 下最终得到的黑绵羊数量的期望值。这里<span class="math inline">\(V_S\)</span> 是一个由 <span class="math inline">\(S\)</span> 决定的确定的函数,它不包含随机性。<span class="math inline">\(V_S\)</span> 叫做策略 <span class="math inline">\(S\)</span> 的值函数。显然 <span class="math inline">\(V_S\)</span> 总是满足边界条件 <span class="math display">\[V_S(0,b)=b,\quadV_S(w,0)=0\label{eq:boundary}\tag{$\ast$}.\]</span></p><p>假设我们能够找到这样一个策略 <span class="math inline">\(A\)</span>,它的值函数 <span class="math inline">\(V_A\)</span> 有如下性质,那么它就是最佳策略:</p><blockquote><p><strong>最优策略的充分条件</strong>:如果策略 <span class="math inline">\(A\)</span> 的值函数 <span class="math inline">\(V_A\)</span> 满足如下条件:对任何初始状态 <span class="math inline">\((w,b)\)</span> 和任何的策略 <span class="math inline">\(S\)</span>,设羊群在策略 <span class="math inline">\(S\)</span> 下第 <span class="math inline">\(n\)</span> 个魔法时刻结束后的状态为 <span class="math inline">\((W_n,B_n)\)</span>,序列 <span class="math inline">\(\{V_A(W_n,B_n),n=0,1,\ldots\}\)</span>是上鞅,则策略 <span class="math inline">\(A\)</span> 就是最优的。</p></blockquote><p>注意这里是把任一策略 <span class="math inline">\(S\)</span>下的状态序列 <span class="math inline">\((W_n, B_n)\)</span> 代入策略<span class="math inline">\(A\)</span> 的值函数中。</p><p>其中的道理非常简单:对任何策略 <span class="math inline">\(S\)</span>,由于其吸收状态 <span class="math inline">\((W_\infty,B_\infty)\)</span> 中必有一个分量是0,从而由值函数边界条件 <span class="math inline">\((\ref{eq:boundary})\)</span> 有 <span class="math inline">\(B_\infty=V_A(W_\infty,B_\infty)\)</span>,所以<span class="math display">\[\mathbb{E}[B_\infty]=\mathbb{E}[V_A(W_\infty,B_\infty)]\leq\mathbb{E}[V_A(w,b)]=V_A(w,b).\]</span> 其中最后一个等号是因为 <span class="math inline">\(V_A(w,b)\)</span>是一个常数,常数的期望等于自身。</p><p>在教材中,Williams 直接「猜出」了策略 <span class="math inline">\(A\)</span>:</p><blockquote><p><strong>策略 <span class="math inline">\(A\)</span></strong>:如果当前黑绵羊的数量多于白绵羊,则什么也不做;否则就把白绵羊的数量变为黑绵羊的数量减1。</p></blockquote><p>显然 <span class="math inline">\(V_A\)</span> 有如下性质:</p><div id="recurrence" class="statement sta_v_a______ plain unnumbered"><p><span class="statement-heading"><span class="statement-label"><span class="math inline">\(V_A\)</span> 的递推关系</span>.</span></p><ol type="1"><li>边界条件 <span class="math inline">\(V_A(0,b)=b\)</span>,<span class="math inline">\(V_A(w,0)=0\)</span>。</li><li><span class="math inline">\(V_A(w,b)=V_A(w-1,b), w\geqb>0\)</span>。</li><li><span class="math inline">\(V_A(w,b)=\frac{w}{w+b}V_A(w+1,b-1)+\frac{b}{w+b}V_A(w-1,b+1)\)</span>,<span class="math inline">\(b>w>0\)</span>。</li></ol></div><p><span class="math inline">\(V_A\)</span> 由边界条件 1 和递推关系 2, 3完全决定。</p><p>从定义上看,关系 2 只在一半的区域上成立,而关系 3则在另一半的区域上成立。但是花费一番功夫,我们其实可以证明它们各自的「弱形式」在整个区域上都是对的:</p><div class="unnumbered statement lemma-unnumbered plain"><p><span class="statement-heading"><span class="statement-label">引理</span>.</span><span class="statement-spah"></span>在区域 <span class="math inline">\(\Omega\)</span> 上,<span class="math inline">\(V_A\)</span> 函数满足如下的不等式:</p><ol start="4" type="1"><li><span class="math inline">\(V_A(w,b)\geq V_A(w-1,b),w>0\)</span>。</li><li><span class="math inline">\(V_A(w,b)\geq\frac{w}{w+b}V_A(w+1,b-1)+\frac{b}{w+b}V_A(w-1,b+1),w>0,b>0\)</span>。</li></ol></div><p>4, 5 合起来说的就是无论男孩策略如何,<span class="math inline">\(\{V_A(W_n,B_n)\}\)</span> 总是一个上鞅!因此策略 <span class="math inline">\(A\)</span> 确实是最优的。</p><p>引理的证明是纯粹的分析,过程比较繁琐,我把它留给 Williams 的教材第15.3 节。写出 <span class="math inline">\(V(w,b)\)</span>的显式表达式来是很难的,Williams 证明了 <span class="math display">\[\lim_{k\to\infty}V(k,k)-(2k+\frac{\pi}{4}-\sqrt{\pik})=0.\label{eq:vkk}\tag{$\ast\ast$}\]</span> 因此如果开始有黑、白绵羊各10000 只,则策略 <span class="math inline">\(A\)</span>下黑绵羊的期望数目大约是 19824 只。</p><p>我对 Williams给出的估计不太放心,于是用书中给出的递推关系写了一段代码验证了一下:</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> decimal <span class="hljs-keyword">import</span> *<br><br>pi = <span class="hljs-number">3.14159265358979</span><br>getcontext().prec = <span class="hljs-number">20</span><br><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">solve_sheep</span>(<span class="hljs-params">n</span>):</span><br> p = [<span class="hljs-number">0</span> <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(n + <span class="hljs-number">1</span>)]<br> v = [<span class="hljs-number">0</span> <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(n + <span class="hljs-number">1</span>)]<br> v[<span class="hljs-number">1</span>] = <span class="hljs-number">1</span><br> p[<span class="hljs-number">1</span>] = Decimal(<span class="hljs-number">0.5</span>)<br> <span class="hljs-keyword">for</span> k <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">2</span>, n + <span class="hljs-number">1</span>):<br> p[k] = (<span class="hljs-number">1</span> - <span class="hljs-number">1</span> / Decimal(<span class="hljs-number">2</span> * k)) * p[k - <span class="hljs-number">1</span>]<br> <span class="hljs-keyword">for</span> k <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">1</span>, n):<br> w = (<span class="hljs-number">1</span> - p[k]) / (<span class="hljs-number">1</span> + p[k])<br> v[k + <span class="hljs-number">1</span>] = w * v[k] + (<span class="hljs-number">1</span> - w) * (<span class="hljs-number">2</span> * k + <span class="hljs-number">1</span>)<br><br> <span class="hljs-keyword">return</span> v[n]<br><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">estimate_sheep</span>(<span class="hljs-params">n</span>):</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * n + pi / <span class="hljs-number">4</span> - (pi * n)**<span class="hljs-number">0.5</span><br><br>print(solve_sheep(<span class="hljs-number">10000</span>))<br>print(estimate_sheep(<span class="hljs-number">10000</span>))<br></code></pre></td></tr></tbody></table></figure><p>递推公式给出的真实值结果是 19823.5422285701,渐进公式给出的结果是19823.540013,准确的有点离谱啊!这真的有点刷新我对 Stirling公式的认知。</p><p>猜出最优策略、证明弱化的不等式、给出渐进公式,每一步都是神操作啊。</p><h1 id="附录">附录</h1><p>Williams 书中对渐进公式 <span class="math inline">\((\ref{eq:vkk})\)</span>的证明比较难读,我这里解释下其中的想法。关键是用对角线上的值 <span class="math inline">\(v_k=V(k,k)\)</span> 来表示出所有的 <span class="math inline">\(V(w, b),b>w>0\)</span>:</p><p><span class="math display">\[\begin{cases}V(k-c,k+c)=v_k+(2k-v_k)a_c,\\ V(k+1-c,k+c)=v_k+(2k+1-v_k)b_c.\end{cases}\]</span></p><p>其中</p><p><span class="math display">\[\begin{cases}a_c=2^{-(2k-2)}\sum\limits_{j=k}^{k+c-1}\dbinom{2k-1}{j},\\b_c=\left(2^{2k-1}+\frac{1}{2}\dbinom{2k}{k}\right)^{-1}\sum\limits_{j=k}^{k+c-1}\dbinom{2k}{j}.\end{cases}\]</span></p><p>Williams没有解释这组公式是怎么求出来的,它看起来很吓人,其实道理不复杂。我们用<span class="math inline">\(V(k-c,k+c)\)</span> 为例子来说明:</p><p>记 <span class="math inline">\(g(c) = V(k-c, k+c),0\leq c\leqk\)</span>,则 <span class="math inline">\(g(0)=v_k,\,g(k)=2k\)</span>,由前面 <a href="#recurrence" title="V_A 的递推关系"><span class="math inline">\(V_A\)</span> 的递推关系</a> 中的 3知其满足递推关系</p><p><span class="math display">\[g(c) = \frac{k-c}{2k}g(c-1) +\frac{k+c}{2k}g(c+1),\quad 1\leq c \leq k-1.\]</span> 这是一个 <span class="math inline">\([0,k]\)</span> 上的递推序列,并且已知边界条件<span class="math inline">\(g(0)\)</span> 和 <span class="math inline">\(g(k)\)</span>,我们来求解这个序列。</p><p>记</p><p><span class="math display">\[h(c) = \frac{g(c) - g(0)}{g(k)-g(0)} =\frac{g(c) - v_k}{g(k)-v_k}.\label{eq:hc}\tag{1}\]</span></p><p>则 <span class="math inline">\(h(c)\)</span>同样满足上述递推关系,但是边界条件为 <span class="math inline">\(h(0)=0\)</span>,<span class="math inline">\(h(k)=1\)</span>。于是</p><p><span class="math display">\[\begin{align}h(c+1)-h(c)&=\frac{k-c}{k+c}(h(c)-h(c-1))\\&=\cdots\\&=\frac{(k-c)\cdots(k-1)}{(k+c)\cdots(k+1)}(h(1)-h(0))\\&=\frac{\dbinom{2k-1}{k+c}}{\dbinom{2k-1}{k}}h(1).\label{eq:hrec}\tag{2}\end{align}\]</span></p><p>利用 <span class="math inline">\((\ref{eq:hrec})\)</span>我们可以解出 <span class="math inline">\(h(1)\)</span> 来:</p><p><span class="math display">\[1=h(k)=\sum_{c=0}^{k-1}\big(h(c+1)-h(c)\big)=h(1)\frac{\sum_{c=0}^{k-1}\dbinom{2k-1}{k+c}}{\dbinom{2k-1}{k}}=h(1)\dfrac{2^{2k-2}}{\dbinom{2k-1}{k}}.\]</span></p><p>即 <span class="math inline">\(h(1)=2^{-(2k-2)}\binom{2k-1}{k}\)</span>。再次利用<span class="math inline">\((\ref{eq:hrec})\)</span> 可得</p><p><span class="math display">\[h(c)=\sum_{j=0}^{c-1}\big(h(j)-h(j-1)\big)=\sum_{j=0}^{c-1}\frac{\dbinom{2k-1}{k+j}}{\dbinom{2k-1}{k}}h(1)=2^{-(2k-2)}\sum_{j=k}^{k+c-1}\dbinom{2k-1}{j}.\]</span></p><p>将上式代入 <span class="math inline">\((\ref{eq:hc})\)</span>即得结论。</p><h1 class="unnumbered" id="bibliography">References</h1><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0" role="list"><div id="ref-Williams1991" class="csl-entry" role="listitem">Williams, David. 1991. <em>Probability with Martingales</em>. CambridgeUniversity Press.</div></div>]]></content>
<summary type="html">
<p><img style="margin:0px auto;display:block" src="/images/midjourney/mabinogion.png" width="350"></p>
<p>今天的问题源自中世纪威尔士人的故事集《Mabinogion》中的一段:</p>
<blockquote>
<p>一个男孩来到了一个美丽的山谷,有一条小河在谷中流淌。他看到河一边的草地上有一群黑绵羊,另一边的草地上有一群白绵羊。羊群被施以一种魔法:每个时刻都恰有一只绵羊发出咩咩的叫声。如果发出叫声的是白绵羊,就会有一只黑绵羊趟过小河跑过来并且变成白绵羊;如果发出叫声的是黑绵羊,则会有一只白绵羊趟过小河跑过去并且变成黑绵羊。每个时刻发出叫声的绵羊是完全随机的,整个过程没有绵羊出生或者死亡,一直持续到所有绵羊都变成同一种颜色为止。</p>
</blockquote>
<p>问题是这样的:</p>
<div class="unnumbered statement question-unnumbered definition">
<p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"> </span>如果男孩可以选择在初始时刻 <span class="math inline">\(0\)</span>,或者是每个魔法时刻 <span class="math inline">\(1,2,\ldots\)</span>
结束后将任意数量的白绵羊赶出山谷,那么为了最终得到尽可能多的黑绵羊,他应该采取怎样的策略?</p>
</div></summary>
<category term="Williams 概率和鞅" scheme="https://pywonderland.com/categories/Williams-%E6%A6%82%E7%8E%87%E5%92%8C%E9%9E%85/"/>
</entry>
<entry>
<title>飞船空间跳跃问题</title>
<link href="https://pywonderland.com/spaceship-jump/"/>
<id>https://pywonderland.com/spaceship-jump/</id>
<published>2011-04-05T00:00:00.000Z</published>
<updated>2024-11-26T15:30:59.940Z</updated>
<content type="html"><![CDATA[<p>本文的问题出自 Williams 的教材 <a href="https://www.cambridge.org/highereducation/books/probability-with-martingales/B4CFCE0D08930FB46C6E93E775503926#overview">ProbabilitywithMartingales</a>,虽然不算很难但是综合使用了许多知识,展示了抽象的鞅理论其实有着丰富多彩的应用。</p><div class="unnumbered statement question-unnumbered definition"><p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah"></span>一艘太空船正在宇宙中做星际航行时,飞船的控制系统出了故障,飞船不能正常地进行空间跳跃,而是只能预先设定一个距离,然后以此距离进行一次方向完全随机的跳跃。现在飞船想要返回太阳系。假设太阳系的半径是<span class="math inline">\(r\)</span>,发生故障时飞船与太阳的距离为<span class="math inline">\(R>r\)</span>。好消息是在每个时刻,飞船能够知道自身与太阳系的距离。</p><p>求证:不论采用怎样的跳跃策略,飞船返回太阳系的概率都小于 <span class="math inline">\(r/R\)</span>;但是对任何 <span class="math inline">\(\epsilon>0\)</span>,可以采取适当的策略,使得飞船返回太阳系的概率大于<span class="math inline">\((r-\epsilon)/R\)</span>,即 <span class="math inline">\(r/R\)</span> 是最优概率。这个最优策略是什么?</p></div><span id="more"></span><h1 id="预备知识">预备知识</h1><h2 id="条件期望的预备知识">条件期望的预备知识</h2><p>设 <span class="math inline">\(X,Y\)</span> 是两个随机变量,<span class="math inline">\(\varphi\)</span> 是可测函数。我们考虑条件期望<span class="math inline">\(\mathop{\mathrm{\mathbb{E}}}[\varphi(X,Y)|X]\)</span>,这是一个关于<span class="math inline">\(\sigma(X)\)</span> 可测的随机变量,根据Doob-Dynkin 引理,它可以写成 <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[\varphi(X,Y)|X]=g(X)\]</span>的形式,其中 <span class="math inline">\(g\)</span> 是一个 Borel可测函数。但是这个 <span class="math inline">\(g\)</span>具体是什么呢?下面的 freezing lemma <span class="citation" data-cites="Williams1991">(<a href="#ref-Williams1991" role="doc-biblioref">Williams 1991, sec. 9.10</a>)</span> or <span class="citation" data-cites="Durrett2019">(<a href="#ref-Durrett2019" role="doc-biblioref">Durrett 2019, sec. 4.1</a>)</span>告诉我们,在一定条件下我们可以先将 <span class="math inline">\(X\)</span> 冻结为一个实数值 <span class="math inline">\(x\)</span>,上式的右边变成 <span class="math inline">\(g(x)\)</span>,左边变成 <span class="math inline">\(\{X=x\}\)</span> 条件下的期望 <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[\varphi(X,Y)|X=x]=\mathop{\mathrm{\mathbb{E}}}\varphi(x,Y).\]</span>即 <span class="math inline">\(g(x)=\mathop{\mathrm{\mathbb{E}}}\varphi(x,Y)\)</span>。这就找到了<span class="math inline">\(g\)</span> 的表达式。</p><div id="freeze" class="statement lemma plain"><p><span class="statement-heading"><span class="statement-label">引理1.1</span>.</span><span class="statement-spah"> </span>设 <span class="math inline">\((\Omega,\mathcal{F},\mu)\)</span>是一个概率空间,<span class="math inline">\(X, Y\)</span>是两个取值在某可测空间 <span class="math inline">\((S,\mathcal{S})\)</span> 中的随机变量,子 <span class="math inline">\(\sigma\)</span> 域 <span class="math inline">\(\mathcal{G}\subseteq\mathcal{F}\)</span> 满足<span class="math inline">\(X\in\mathcal{G}\)</span> 且 <span class="math inline">\(\mathcal{G}\)</span> 与 <span class="math inline">\(Y\)</span> 独立。可测函数 <span class="math inline">\(\varphi: S\times S\to\mathbb{R}\)</span> 满足<span class="math inline">\(\varphi\)</span> 非负或者 <span class="math inline">\(\mathop{\mathrm{\mathbb{E}}}|\varphi(X,Y)|<\infty\)</span>。令 <span class="math inline">\(g(x)=\mathop{\mathrm{\mathbb{E}}}\varphi(x,Y)\)</span>,则 <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[\varphi(X,Y)|\mathcal{G}]=g(X).\]</span></p></div><p>在进入证明之前,我们来看个例子:</p><div class="statement example definition unnumbered"><p><span class="statement-heading"><span class="statement-label">例</span>:</span><span class="statement-spah"></span>设 <span class="math inline">\(X, Y\)</span>是两个独立的随机变量,<span class="math inline">\(Y\)</span> 服从的是<span class="math inline">\([0, 1]\)</span> 上的均匀分布,<span class="math inline">\(X\)</span> 服从的分布我们可以不用关心。问条件期望<span class="math inline">\(\mathop{\mathrm{\mathbb{E}}}[\sin(XY)|X]\)</span>是什么?</p><p>这相当于在 <a href="#freeze" title="引理 1.1">引理 1.1</a> 中取 <span class="math inline">\(\varphi(X,Y)=\sin(XY)\)</span> 和 <span class="math inline">\(\mathcal{G}=\sigma(X)\)</span>。<a href="#freeze" title="引理 1.1">引理 1.1</a> 告诉我们可以把 <span class="math inline">\(\sin(XY)\)</span> 中的 <span class="math inline">\(X\)</span> 冻结为常数 <span class="math inline">\(X=x\)</span>,把 <span class="math inline">\(\mathop{\mathrm{\mathbb{E}}}[\sin(XY)|X]\)</span>视作关于常数 <span class="math inline">\(x\)</span> 的积分 <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[\sin(xY)]=\int_0^1\sin(xy)\,\mathrm{d}y = \frac{1}{x}\int_0^x\sin(z)\,\mathrm{d}z=\frac{1-\cos x}{x}.\]</span> 然后把 <span class="math inline">\(x\)</span> 解冻为 <span class="math inline">\(X\)</span> 即得 <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[\sin(XY)|X] =\frac{1-\cos X}{X}.\]</span></p></div><p><a href="#freeze" title="引理 1.1">引理 1.1</a> 中的可测空间 <span class="math inline">\((S,\mathcal{S})\)</span> 可以是多维空间 <span class="math inline">\((\mathbb{R}^d,\mathcal{B}(\mathbb{R}^d))\)</span>,<span class="math inline">\(X,Y\)</span> 也可以是独立的随机向量。即如果 <span class="math inline">\(\varphi(X_1,\ldots,X_n,Y_1,\ldots,Y_m)\)</span>是关于随机变量的可测函数,<span class="math inline">\(\sigma(X_1,\ldots,X_n)\subset\mathcal{G}\)</span>并且 <span class="math inline">\(\mathcal{G}\)</span> 和 <span class="math inline">\(\sigma(Y_1,\ldots,Y_m)\)</span> 独立,那么条件期望<span class="math inline">\(\mathop{\mathrm{\mathbb{E}}}[\varphi(X,Y)|\mathcal{G}]\)</span>就是一个以 <span class="math inline">\((x_1,\ldots,x_n)\)</span>为参变元的多重积分 <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[\varphi(x_1,\ldots,x_n,Y_1,\ldots,Y_m)]=g(x_1,\ldots,x_n).\]</span></p><p><strong>证明</strong>:我们要证明对任何可测集 <span class="math inline">\(C\in\mathcal{G}\)</span> 有</p><p><span class="math display">\[\int_C \varphi(X, Y)\mathrm{d}\mu =\int_C g(X)\mathrm{d}\mu.\]</span> 当 <span class="math inline">\(\varphi(x,y)=\mathbb{1}_A(x)\mathbb{1}_B(y)\)</span> 时,<span class="math inline">\(g(x)=\mathbb{1}_A(x)\mathbb{P}(\{Y\inB\})\)</span>,从而 <span class="math display">\[\begin{align*}\int_C\mathbb{1}_A(X)\mathbb{1}_B(Y)\mathrm{d}\mu&=\mathbb{P}(\{X\inA\}\cap C\cap\{Y\in B\})\\&=\mathbb{P}(\{X\in A\}\capC)\cdot\mathbb{P}(\{Y\inB\})\\&=\int_C\mathbb{1}_A(X)\mathrm{d}\mu\cdot\mathbb{P}(\{Y\inB\})\\&=\int_C g(X)\mathrm{d}\mu.\end{align*}\]</span></p><p>于是结论对所有形如 <span class="math inline">\(A\times B\)</span>的集合的示性函数成立。这些函数构成一个 <span class="math inline">\(\pi-\)</span> 系。根据可测函数的单调类定理(monotone class theorem),结论对所有非负或者可积函数都成立。<span class="math inline">\(\blacksquare\)</span></p><h2 id="分析的预备知识">分析的预备知识</h2><div id="newton-potential" class="statement lemma plain"><p><span class="statement-heading"><span class="statement-label">引理1.2</span>.</span><span class="statement-spah"> </span><span class="math inline">\(B=B(A,R)\)</span> 是 <span class="math inline">\(\mathbb{R}^3\)</span> 中以点 <span class="math inline">\(A\)</span> 为中心,半径为 <span class="math inline">\(R\)</span> 的球,<span class="math inline">\(X\)</span> 是球面上均匀分布的随机点,则 <span class="math inline">\(X\)</span> 与原点 <span class="math inline">\(O\)</span> 之间距离倒数的期望为 <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}\frac{1}{|X|}=\begin{cases}1/a& a>R,\\ 1/R & a\leq R.\end{cases}\]</span> 其中 <span class="math inline">\(a=|A|\)</span> 是 <span class="math inline">\(A\)</span> 与原点之间的距离。</p></div><p>这个引理其实是我们都熟悉的高中物理知识:假设以 <span class="math inline">\(A\)</span> 为中心,半径为 <span class="math inline">\(R\)</span> 的球壳上有总量为 1的均匀分布的电荷,则球壳表面和内部的电势处处等于 <span class="math inline">\(1/R\)</span>,球壳外部任意一点 <span class="math inline">\(P\)</span> 的电势等于 <span class="math inline">\(P\)</span> 和球心距离的倒数,即 <span class="math inline">\(1/|P-A|\)</span>(不计物理常数),此即为结论。</p><p>当然这不是一个严格的证明,实际上这个积分正是 Newton势函数的简单情形。由于这不是本文的重点,就不再展开讲了,读者可以参考<span class="citation" data-cites="Donoghue2014">(<a href="#ref-Donoghue2014" role="doc-biblioref">Donoghue 2014, chap.8</a>)</span>。</p><h1 id="建立模型">建立模型</h1><p>我们开始正式求解本文开头的问题。</p><ol type="1"><li><p>初始时刻为 0,太阳系是以原点为圆心,半径为 <span class="math inline">\(r\)</span> 的球,飞船初始位置在 <span class="math inline">\((R,0,0)\)</span> 处。</p></li><li><p>设 <span class="math inline">\(\{U_n\}_{n\geq 1}\)</span>是定义在某个概率空间 <span class="math inline">\((\Omega,\mathcal{F},\mathbb{P})\)</span>上的一组独立同分布的、在单位球面上均匀分布的随机向量,它们表示飞船每次空间跳跃的随机方向。并设<span class="math inline">\(\mathcal{F}_n=\sigma(U_1,\ldots,U_n)\)</span> 以及<span class="math inline">\(\mathcal{F}_0=(\Omega,\emptyset)\)</span>。</p></li><li><p>设第 <span class="math inline">\(n\)</span> 次空间跳跃的距离为<span class="math inline">\(l_n(n\geq1)\)</span>,由于 <span class="math inline">\(l_n\)</span> 是根据 <span class="math inline">\(n\)</span> 时刻之前的信息决定的,所以 <span class="math inline">\(l_n\)</span> 关于 <span class="math inline">\(\mathcal{F}_{n-1}\)</span> 可测。</p></li><li><p>设第 <span class="math inline">\(n\)</span>次空间跳跃后飞船的坐标为 <span class="math inline">\(X_n\)</span>,那么<span class="math display">\[X_n=X_{n-1} + l_n U_n.\quadn=1,2,\ldots.\]</span> 其中 <span class="math inline">\(X_0=(R,0,0)\)</span> 是飞船的初始位置。</p></li><li><p>设 <span class="math inline">\(T\)</span>是飞船首次返回太阳系的时间: <span class="math display">\[T = \inf\,\{n\mid X_n\in B(0,r)\},\]</span>则 <span class="math inline">\(T\)</span>的取值范围是 <span class="math inline">\(\mathbb{N^+}\cup\{+\infty\}\)</span>。我们要估算的是事件<span class="math inline">\(\{T<+\infty\}\)</span>的概率,这正是飞船能够在有限时间内回到太阳系的概率。</p></li></ol><p>现在我们着手研究一下飞船的运动规律。</p><p>设 <span class="math inline">\(R_n\)</span> 为第 <span class="math inline">\(n\)</span> 次跳跃以后飞船与太阳系的距离,<span class="math inline">\(R_0=R\)</span>,我们想知道 <span class="math inline">\(R_n\)</span> 和 <span class="math inline">\(R_{n+1}\)</span> 之间的关系。</p><p>对 <span class="math inline">\(\mathcal{F}=\mathcal{F}_n,\,\mathcal{G}=\mathcal{F}_{n-1},\,X=(X_{n-1},l_n),\,Y=U_n,\,\varphi(X,Y)=1/|X_{n-1}+l_nY|\)</span> 应用前面的关于条件期望的 <a href="#freeze" title="引理 1.1">引理 1.1</a> 和 <a href="#newton-potential" title="引理 1.2">引理 1.2</a> 得到</p><p><span class="math display">\[\mathop{\mathrm{\mathbb{E}}}\left[\left.\frac{1}{R_n}\right|\mathcal{F}_{n-1}\right]=\mathop{\mathrm{\mathbb{E}}}\left.\frac{1}{\left|X_{n-1}+l_nU_n\right|}\right|_{X_{n-1}=A}\leq\frac{1}{|A|}=\frac{1}{R_{n-1}}.\]</span></p><p>这里由于 <span class="math inline">\(X_{n-1}\)</span> 和 <span class="math inline">\(l_n\)</span> 都是关于 <span class="math inline">\(\mathcal{F}_{n-1}\)</span> 可测的,而 <span class="math inline">\(U_n\)</span> 和 <span class="math inline">\(\mathcal{F}_{n-1}\)</span> 是独立的,所以应用 <a href="#freeze" title="引理 1.1">引理 1.1</a> 的条件是满足的。</p><p>总结一下:</p><div id="supermartingale" class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理2.1</span>.</span><span class="statement-spah"> </span><span class="math inline">\(\{1/R_n\}\)</span> 关于 <span class="math inline">\(\{\mathcal{F}_n\}\)</span> 构成一个上鞅(与策略无关)。特别地如果跳跃距离总是不超过当前飞船与太阳系的距离,即对任何<span class="math inline">\(n\geq1\)</span> 有 <span class="math inline">\(l_n\leq R_{n-1}\)</span>,则 <span class="math inline">\(\{1/R_n\}\)</span> 还是一个鞅。</p></div><p><strong>证明</strong>:只要再说明每个 <span class="math inline">\(1/R_n\)</span> 是可积的随机变量即可。由于 <span class="math inline">\(1/R_n\)</span> 是非负的随机变量因此 <span class="math inline">\(\mathop{\mathrm{\mathbb{E}}}[1/R_n|\mathcal{F}_{n-1}]\)</span>是有定义的且已经证明其小于等于 <span class="math inline">\(1/R_{n-1}\)</span>,于是 <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[1/R_n]=\mathop{\mathrm{\mathbb{E}}}[\mathop{\mathrm{\mathbb{E}}}[1/R_n|\mathcal{F}_{n-1}]]\leq\mathop{\mathrm{\mathbb{E}}}[1/R_{n-1}].\]</span>对 <span class="math inline">\(n\)</span> 归纳即可。<span class="math inline">\(\blacksquare\)</span></p><p><a href="#supermartingale" title="定理 2.1">定理 2.1</a>是解决整个问题最关键的一步,有了它就海阔天空,没有它就寸步难行。由它我们立刻可以导出一个有趣的观察:由于非负上鞅一定是几乎处处收敛的,因此<a href="#supermartingale" title="定理 2.1">定理 2.1</a> 的结论蕴含<span class="math inline">\(\lim\limits_{n\to\infty}R_n(\omega)\)</span>几乎处处存在。这有两种可能:<span class="math inline">\(\lim\limits_{n\to\infty}R_n(\omega)=+\infty\)</span>或者 <span class="math inline">\(\lim\limits_{n\to\infty}R_n(\omega)=a<+\infty\)</span>。所以飞船要么飞向无穷远,即迷失在宇宙的深处,要么被吸引到某个有限的位置。</p><p>现在我们可以证明:</p><div id="lessthan" class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理2.2</span>.</span><span class="statement-spah"></span>不论飞船采取怎样的策略,返回太阳系的概率都严格小于 <span class="math inline">\(r/R\)</span>。</p></div><p>证明只用到非常基础的鞅的知识:</p><p>设 <span class="math inline">\(Z_n=1/R_n\)</span>,则 <span class="math inline">\(\{Z_n\}\)</span> 是一个非负上鞅,所以 <span class="math inline">\(Z_\infty=\lim\limits_{n\to\infty}Z_n\)</span>是几乎处处存在的。考虑由停时 <span class="math inline">\(T\)</span>截断得到的非负上鞅序列 <span class="math inline">\(\{Z_{T\wedgen}\}\)</span>,这个上鞅序列也是几乎处处收敛的,其中 <span class="math display">\[\lim_{n\to\infty}Z_{T\wedge n} =\begin{cases}\lim_{n\to\infty}Z_n& T=\infty,\\ Z_T&T<\infty.\end{cases}\]</span> 一方面根据非负可积函数列的 Fatou 引理有<span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[\lim_{n\to\infty}Z_{T\wedgen}]\leq\varliminf_{n\to\infty}\mathop{\mathrm{\mathbb{E}}}[Z_{T\wedgen}]\leq \mathop{\mathrm{\mathbb{E}}}[Z_0]=\frac{1}{R}.\]</span>另一方面</p><p><span class="math display">\[\begin{align*}\mathop{\mathrm{\mathbb{E}}}[\lim_{n\to\infty}Z_{T\wedgen}]&=\mathop{\mathrm{\mathbb{E}}}[Z_T\mathbb{1}_{\{T<\infty\}}]+\mathop{\mathrm{\mathbb{E}}}[\lim_{n\to\infty}Z_n\mathbb{1}_{\{T=\infty\}}]\\&\geq \mathop{\mathrm{\mathbb{E}}}[Z_T\mathbb{1}_{\{T<\infty\}}]\geq\frac{\mathbb{P}(T<\infty)}{r}.\end{align*}\]</span></p><p>其中最后一个不等号是因为 <span class="math inline">\(Z_T\geq1/r\)</span>。综合这两个不等式就得到了 <span class="math inline">\(\mathbb{P}(T<\infty)\leqr/R\)</span>,即任何策略下飞船最终返回太阳系的概率不大于 <span class="math inline">\(r/R\)</span>。</p><p>要证明这个概率是严格小于 <span class="math inline">\(r/R\)</span>的,我们只要证明上面式子的最后一个不等号是严格成立的: <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[Z_T\mathbb{1}_{\{T<\infty\}}]>\frac{\mathbb{P}(T<\infty)}{r}.\]</span></p><p>当然需要假定这里的 <span class="math inline">\(\{T<\infty\}\)</span> 有正概率 (返回概率是 0的话当然小于 <span class="math inline">\(r/R\)</span>,没什么好证的)。为此只要证明在事件<span class="math inline">\(\{T<\infty\}\)</span> 上几乎处处有 <span class="math inline">\(Z_T>1/r\)</span>,即 <span class="math inline">\(R_T<r\)</span> 即可。</p><p>我们来证明对每个 <span class="math inline">\(n\geq0\)</span>,事件<span class="math inline">\(A_n=\{R_n=0 \text{ or } R_n=r\}\)</span>都是零概率事件。</p><p>对 <span class="math inline">\(n\)</span> 归纳:<span class="math inline">\(n=0\)</span> 时 <span class="math inline">\(R_0=R>r\)</span>,结论成立。设结论在小于 <span class="math inline">\(n\)</span> 时都成立,来看 <span class="math inline">\(n\)</span> 的情形。</p><p>由于 <span class="math inline">\(\mathbb{P}(A_n)=\mathop{\mathrm{\mathbb{E}}}[\mathbb{1}_{A_n}]=\mathop{\mathrm{\mathbb{E}}}[\mathop{\mathrm{\mathbb{E}}}[\mathbb{1}_{A_n}|\mathcal{F}_{n-1}]]\)</span>,我们只要证明<span class="math inline">\(\mathop{\mathrm{\mathbb{E}}}[\mathbb{1}_{A_n}|\mathcal{F}_{n-1}]\)</span>是几乎处处为 0 的随机变量即可。</p><p>在 <a href="#freeze" title="引理 1.1">引理 1.1</a> 中取 <span class="math inline">\(X=(X_{n-1},l_n),\,Y=U_n,\,\mathcal{G}=\mathcal{F}_{n-1}\)</span>,我们得到<span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[\mathbb{1}_{A_n}|\mathcal{F}_{n-1}]=\mathop{\mathrm{\mathbb{E}}}[\mathbb{1}_{\{0,r\}}\circ|X_{n-1}+l_nU_n|\bigg|\mathcal{F}_{n-1}]=\mathop{\mathrm{\mathbb{E}}}\left[\mathbb{1}_{\{0,r\}}\circ|X_{n-1}+l_nU_n|\right].\]</span>其中上式最右边的期望是对单位球面上均匀分布的 <span class="math inline">\(U_n\)</span> 进行积分,结果是一个关于 <span class="math inline">\((X_{n-1},l_n)\)</span>的函数。显然无论如何上式右边作为一个只取 <span class="math inline">\(\{0,1\}\)</span> 两个值的函数的积分,结果必然在<span class="math inline">\([0,1]\)</span> 中。</p><ul><li>如果 <span class="math inline">\(|X_{n-1}|\in\{0,r\}\)</span>,我们已经知道结果在<span class="math inline">\([0,1]\)</span> 中。</li><li>如果 <span class="math inline">\(|X_{n-1}|\notin\{0,r\}\)</span>,这时以 <span class="math inline">\(X_{n-1}\)</span> 为中心,<span class="math inline">\(l_n\)</span> 为半径的球面上,与原点之间的距离为<span class="math inline">\(0\)</span> 或者 <span class="math inline">\(r\)</span> 的点的测度为 0,即积分项 <span class="math inline">\(\mathbb{1}_{\{0,r\}}\circ|X_{n-1}+l_nU_n|\)</span> 对几乎处处的 <span class="math inline">\(U_n\)</span> 都是 0,当然积分值 <span class="math inline">\(\mathop{\mathrm{\mathbb{E}}}\left[\mathbb{1}_{\{0,r\}}\circ|X_{n-1}+l_nU_n|\right]=0\)</span>。</li></ul><p>于是我们有 <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}\left[\mathbb{1}_{\{0,r\}}\circ|X_{n-1}+l_nU_n|\right]=\begin{cases}\in[0,1], & |X_{n-1}|\in\{0,r\}\\0, & |X_{n-1}|\notin\{0,r\}\end{cases}\]</span></p><p>根据归纳假设,<span class="math inline">\(|X_{n-1}|\in\{0,r\}\)</span> 的概率是 0,即 <span class="math inline">\(\mathop{\mathrm{\mathbb{E}}}\left[\mathbb{1}_{\{0,r\}}\circ|X_{n-1}+l_nU_n|\right]\)</span> 是一个几乎处处为 0 的函数,从而 <span class="math inline">\(\mathop{\mathrm{\mathbb{E}}}[\mathbb{1}_{A_n}|\mathcal{F}_{n-1}]\)</span>也几乎处处为 0,即得所证。</p><h1 id="对策略的进一步分析">对策略的进一步分析</h1><p>现在我们把注意力转移到飞船不能返回太阳系这个事件上来。前面已经说过,飞船的运动只有两种可能,迷失在无穷远处或者被禁锢在一个有限的区域内,所以如果飞船不能返回太阳系,则飞船要么飞向无穷远,要么在太阳系之外的一个有限区域内打转。我们想知道,怎么判断这两种情形哪一种会发生呢?</p><p>举个例子,考虑这样一个明显不合理的策略:第 <span class="math inline">\(n\)</span> 次的跳跃距离总是设定为 <span class="math inline">\(1/2^n\)</span>,在这个策略下飞船永远飞不出一个半径为1 的空间,所以这种策略是应该避免的。</p><p>你可以注意到这个糟糕的策略的问题出在跳跃距离之和是收敛的。如果我们强迫每次跳跃的距离都大于一个固定的值<span class="math inline">\(\epsilon\)</span>(<span class="math inline">\(\epsilon\)</span>可以是任意的正数),就可以避免这种情形出现,这就是下面的定理:</p><div id="goinf" class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理3.1</span>.</span><span class="statement-spah"> </span>设 <span class="math inline">\(\epsilon\)</span> 是任一正数 <span class="math display">\[E:=\{\omega:\T(\omega)=\infty,\ l_n(\omega)\geq\epsilon,\ \forall n\geq1\},\]</span>则我们有 <span class="math display">\[\lim_{n\to\infty}R_n=+\infty,\quad \text{for a.e.}\ \omega\in E.\]</span></p></div><p><strong>证明</strong>:设 <span class="math inline">\(\theta_n\)</span> 为 <span class="math inline">\(X_{n-1}\)</span> 与 <span class="math inline">\(U_n\)</span> 之间的夹角,利用关系 <span class="math inline">\(X_n=X_{n-1}+l_nU_n\)</span>以及三角形的余弦公式可得 <span class="math display">\[R_n^2=R_{n-1}^2+l_n^2+2R_{n-1}l_n\cos\theta_n.\]</span>令 <span class="math inline">\(B_n=\{\cos\theta_n\geq1/2\}\)</span>,则在事件 <span class="math inline">\(B_n\)</span>上我们有</p><p><span class="math display">\[R_n^2\geqR_{n-1}^2+l_n^2+R_{n-1}l_n\geq(R_{n-1}+l_n/2)^2.\]</span> 结合在事件<span class="math inline">\(E\)</span> 上有 <span class="math inline">\(l_n\geq\epsilon\)</span>,于是在事件 <span class="math inline">\(B_n\cap E\)</span> 上有 <span class="math display">\[R_n\geq R_{n-1}+l_n/2\geqR_{n-1}+\epsilon/2,\quad\omega\in B_n\cap E.\]</span> 如果我们能证明<span class="math inline">\(\mathbb{P}(\{B_n\\text{i.o.}\})=1\)</span>,再排除掉使得 <span class="math inline">\(R_n(\omega)\)</span>不收敛的零测集,则对几乎处处的 <span class="math inline">\(\omega\inE\)</span> 都有 <span class="math inline">\(R_n\geqR_{n-1}+\epsilon/2\)</span> 对无穷多个 <span class="math inline">\(n\)</span> 成立。对这些 <span class="math inline">\(\omega\)</span>,<span class="math inline">\(R_n\)</span> 是不可能收敛到一个有限的点的,只能是<span class="math inline">\(\lim\limits_{n\to\infty}R_n(\omega)=\infty\)</span>,这就说明飞船在<span class="math inline">\(E\)</span> 上几乎处处飞向无穷远。</p><p>为了证明 <span class="math inline">\(\mathbb{P}(\{B_n\\text{i.o.}\})=1\)</span>,我们只要证明 <span class="math inline">\(\{B_n\}\)</span> 是独立的事件列,且对每个 <span class="math inline">\(n\)</span> 有 <span class="math inline">\(\mathbb{P}(B_n)=\frac{1}{4}\)</span>,这样由Borel-Cantelli 第二引理就得到了结论。</p><p>注意到(又用到 <a href="#freeze" title="引理 1.1">引理 1.1</a>啦)</p><p><span class="math display">\[\begin{align*}\mathbb{P}[B_n| \mathcal{F}_{n-1}] &= \mathbb{P}[\cos(U_n, v)\geq1/2] \bigg|_{v=X_{n-1}} \\&= \mathbb{P}[U_n\in\{(x,y,z)\in\mathbb{R}^3:\ z\geq 1/2\}]\\&=\frac{1}{4}.\end{align*}\]</span></p><p>于是对任何一组下标 <span class="math inline">\(n_1<n_2<\cdots<n_k\)</span>,记 <span class="math inline">\(A=B_{n_1}\cap\cdots\cap{B_{n_{k-1}}}\)</span> 以及<span class="math inline">\(B=B_{n_k}\)</span>,并注意到由于 <span class="math inline">\(n_{k-1}\leq n_k-1\)</span> 因此 <span class="math inline">\(A\in\mathcal{F}_{n_k-1}\)</span>,所以</p><p><span class="math display">\[\begin{align*}\mathbb{P}[B_{n_1}\cap\cdots\capB_{n_k}] &= \mathop{\mathrm{\mathbb{E}}}[\mathbb{1}_A\mathbb{1}_B] =\mathop{\mathrm{\mathbb{E}}}[\mathop{\mathrm{\mathbb{E}}}[\mathbb{1}_A\mathbb{1}_B|\mathcal{F}_{n_k-1}]]\\&=\mathop{\mathrm{\mathbb{E}}}[\mathbb{1}_A\mathop{\mathrm{\mathbb{E}}}[\mathbb{1}_B|\mathcal{F}_{n_k-1}]]\\&=\frac{1}{4}\mathop{\mathrm{\mathbb{E}}}[\mathbb{1}_A]\\&=\frac{1}{4}\mathbb{P}(B_{n_1}\cap\cdots\capB_{n_{k-1}}).\end{align*}\]</span></p><p>从而由递推可见对每个 <span class="math inline">\(n\)</span> 都有<span class="math inline">\(\mathbb{P}(B_n) =\frac{1}{4}\)</span>且它们是互相独立的。</p><h1 id="最优策略">最优策略</h1><p>现在我们已经知道飞船返回太阳系的概率总是小于 <span class="math inline">\(r/R\)</span>,也知道只要策略得当,就可以避免飞船在原地打转的糟糕情况。接下来的问题是:最好的策略到底是什么?</p><div class="statement theorem plain"><p><span class="statement-heading"><span class="statement-label">定理4.1</span>.</span><span class="statement-spah"></span>定义如下的跳跃策略:在准备第 <span class="math inline">\(n\)</span> 次跳跃时,如果飞船已经在太阳系内,则令<span class="math inline">\(l_n=0\)</span>,否则令 <span class="math inline">\(l_n=R_{n-1}-r+\epsilon\)</span>,这里 <span class="math inline">\(0<\epsilon<r\)</span>。在这个跳跃策略下,飞船返回太阳系的概率大于<span class="math inline">\((r-\epsilon)/R\)</span>。</p></div><p>注意在这个策略中总是有 <span class="math inline">\(l_n<R_{n-1}\)</span>,因此 <span class="math inline">\(\{Z_n=1/R_n\}\)</span>实际上是一个鞅。此外由于总是有 <span class="math inline">\(R_n\geqr-\epsilon\)</span>,所以 <span class="math inline">\(Z_n\leq1/(r-\epsilon)\)</span>,即 <span class="math inline">\(\{Z_n\}\)</span> 被常数 <span class="math inline">\(1/(r-\epsilon)\)</span> 所控制。</p><p>接下来的证明不过是 <a href="#lessthan" title="定理 2.2">定理 2.2</a>证明的重复:</p><p>这次根据控制收敛定理有 <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[\lim_{n\to\infty}Z_{T\wedgen}]=\lim_{n\to\infty}\mathop{\mathrm{\mathbb{E}}}[Z_{T\wedgen}]=\mathop{\mathrm{\mathbb{E}}}[Z_0]=\frac{1}{R}.\]</span> 另一方面<span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[\lim_{n\to\infty}Z_{T\wedgen}]=\mathop{\mathrm{\mathbb{E}}}[Z_T\mathbb{1}_{\{T<\infty\}}]+\mathop{\mathrm{\mathbb{E}}}[\lim_{n\to\infty}Z_n\mathbb{1}_{\{T=\infty\}}].\]</span>这个时候要注意到在 <span class="math inline">\(\{T=\infty\}\)</span>上总是有 <span class="math inline">\(l_n\geq\epsilon\)</span>,因此根据<a href="#goinf" title="定理 3.1">定理 3.1</a>的结论,飞船几乎必然飞向无穷远,即 <span class="math display">\[\lim_{n\to\infty}Z_n=0,\quad\omega\in\{T=\infty\}.\]</span> 所以 <span class="math display">\[\mathop{\mathrm{\mathbb{E}}}[\lim_{n\to\infty}Z_{T\wedgen}]=\mathop{\mathrm{\mathbb{E}}}[Z_T\mathbb{1}_{\{T<\infty\}}]\leq\frac{1}{r-\epsilon}\mathbb{P}(T<\infty).\]</span>综合两个式子就证明了 <span class="math inline">\(\mathbb{P}(T<\infty)\geq(r-\epsilon)/R\)</span>。</p><h1 class="unnumbered" id="bibliography">References</h1><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0" role="list"><div id="ref-Donoghue2014" class="csl-entry" role="listitem">Donoghue, W. F. 2014. <em>Distributions and Fourier Transforms</em>.ISSN. Elsevier Science. <a href="https://books.google.com/books?id=P30Y7daiGvQC">https://books.google.com/books?id=P30Y7daiGvQC</a>.</div><div id="ref-Durrett2019" class="csl-entry" role="listitem">Durrett, Rick. 2019. <em>Probability: Theory and Examples</em>. 5th ed.Cambridge Series in Statistical and Probabilistic Mathematics. CambridgeUniversity Press.</div><div id="ref-Williams1991" class="csl-entry" role="listitem">Williams, David. 1991. <em>Probability with Martingales</em>. CambridgeUniversity Press.</div></div>]]></content>
<summary type="html">
<p>本文的问题出自 Williams 的教材 <a href="https://www.cambridge.org/highereducation/books/probability-with-martingales/B4CFCE0D08930FB46C6E93E775503926#overview">Probability
with
Martingales</a>,虽然不算很难但是综合使用了许多知识,展示了抽象的鞅理论其实有着丰富多彩的应用。</p>
<div class="unnumbered statement question-unnumbered definition">
<p><span class="statement-heading"><span class="statement-label">问题</span>:</span><span class="statement-spah">
</span>一艘太空船正在宇宙中做星际航行时,飞船的控制系统出了故障,飞船不能正常地进行空间跳跃,而是只能预先设定一个距离,然后以此距离进行一次方向完全随机的跳跃。现在飞船想要返回太阳系。假设太阳系的半径是
<span class="math inline">\(r\)</span>,发生故障时飞船与太阳的距离为
<span class="math inline">\(R&gt;r\)</span>。好消息是在每个时刻,飞船能够知道自身与太阳系的距离。</p>
<p>求证:不论采用怎样的跳跃策略,飞船返回太阳系的概率都小于 <span class="math inline">\(r/R\)</span>;但是对任何 <span class="math inline">\(\epsilon&gt;0\)</span>,可以采取适当的策略,使得飞船返回太阳系的概率大于
<span class="math inline">\((r-\epsilon)/R\)</span>,即 <span class="math inline">\(r/R\)</span> 是最优概率。这个最优策略是什么?</p>
</div></summary>
<category term="Williams 概率和鞅" scheme="https://pywonderland.com/categories/Williams-%E6%A6%82%E7%8E%87%E5%92%8C%E9%9E%85/"/>
</entry>
</feed>