跳到正文
格致开物MATHWIKI

绕数

绕数(winding number)是闭合曲线围绕一个未被曲线经过的指定点转过的有向圈数。逆时针取正、顺时针取负;重复绕行会累加。曲线的几何图像相同,参数化的绕行次数仍可能不同。

在复平面中,它既可以用连续辐角的总变化定义,也可以用复积分计算。下面先算一条重复绕行的圆,再说明整数性及自交曲线各区域的差异。

同一圆周上的两次绕行

令 γ(t)=e2it,0≤t≤2π,指定点为原点。曲线图像是单位圆,但辐角的连续提升为 θ(t)=2t,总变化为 4π,所以绕数为二。

单位圆围绕原点重复行走两次,原点绕数二,外部点二的绕数零
圆周图像本身无法显示重数。参数式与辐角变化共同规定了两次绕行。

若只走 eit,绕数为一;沿反向 e−it 走一周,绕数为负一。改变速度而保留方向和总绕行次数,不改变绕数。指定点若改为 a=2,它在圆外,绕数则为零。

连续辐角提升theta等于2t从零增长到四倍圆周率
连续提升允许辐角超过主值区间。把每一时刻都截回主辐角,会产生跳跃,无法直接用两端主值之差计算绕数。

积分定义与整数性

设 γ:[0,1]→ℂ 为闭合、分段 C1 曲线,γ(0)=γ(1),且 a∉γ([0,1])。定义 Ind⁡(γ,a)=12πi∫γdzz−a. 曲线可以自交,也可以重复经过同一点,积分按给定的参数化累计。

沿参数区间可以连续选择 γ(t)−a=ρ(t)eiθ(t),ρ(t)>0. 连续辐角可逐段拼接:将紧参数区间细分,使每段的单位方向落在一个不覆盖整圆的短弧中,在该弧选择辐角,并在相邻段加上适当的 2π 整数倍使端点相接。分段光滑时, γ′(t)γ(t)−a=ρ′(t)ρ(t)+iθ′(t). 积分后,闭合性使 ρ(1)=ρ(0),于是 Ind⁡(γ,a)=θ(1)−θ(0)2π. 又因两端单位方向相同,eiθ(1)=eiθ(0),辐角差必为 2π 的整数倍。这证明了积分定义确实给出整数。

主例也可直接代入: 12πi∫02π2ie2ite2itdt=2. 绕数与辐角变化的关系,是辐角原理将复函数的零点、极点计数联系起来的基础。[1]

区域、拼接与自交

绕数对闭曲线的拼接相加,反向遍历则变号。这可以直接从积分的可加性与参数反向换元得到。

对固定曲线,绕数作为 a 的函数,在曲线补集的每个连通分量上常值:当 a 在不碰曲线的小邻域内移动时,分母与零保持正距离,积分连续;连续且取整数值的函数在连通分量上只能常值。在无界分量中,|a|→∞ 时积分绝对值趋于零,故该分量绕数为零。这也给出了主例外部点绕数为零的理由。

自交曲线可能围出多个绕数不同的区域。例如按次序连接 (−2,−1), (2,1), (2,−1), (−2,1) 并回到起点,得到一条交叉折线。右侧小环顺时针,左侧小环逆时针,因此 Ind⁡(γ,−1)=1,Ind⁡(γ,1)=−1. 原点正好在交叉处,绕数在该指定点没有定义。

交叉折线在左侧区域绕数一,在右侧区域绕数负一,原点位于曲线上
箭头按给定顶点次序走。不同区域分别计有向绕行,不能把图上的所有闭合部分统一称为一个内部。

有理坐标折线的计算

对有理坐标多边形,可数一条从指定点向右发出的水平射线与有向边的交叉:向上穿过计一,向下穿过计负一。边的端点采用半开规则,避免射线恰过顶点时重复计数。下面用 Fraction 精确判断所给有理数数据的方向与共线性;返回整数绕数,不改写输入。

Python 3
from fractions import Fraction

def winding_number(vertices, query):
    polygon = [(Fraction(x), Fraction(y)) for x, y in vertices]
    ax, ay = map(Fraction, query)
    if len(polygon) < 3:
        raise ValueError("require at least three vertices")
    if polygon[0] != polygon[-1]:
        polygon.append(polygon[0])
    winding = 0
    for (px, py), (qx, qy) in zip(polygon, polygon[1:]):
        px, py, qx, qy = px - ax, py - ay, qx - ax, qy - ay
        cross = px * qy - qx * py
        if cross == 0 and min(px, qx) <= 0 <= max(px, qx) \
                and min(py, qy) <= 0 <= max(py, qy):
            raise ValueError("query lies on the curve")
        if py <= 0 < qy and cross > 0:
            winding += 1
        elif qy <= 0 < py and cross < 0:
            winding -= 1
    return winding

bow = [(-2, -1), (2, 1), (2, -1), (-2, 1)]
print(winding_number(bow, (-1, 0)))  # 1
print(winding_number(bow, (1, 0)))   # -1
print(winding_number(bow, (3, 0)))   # 0
assert winding_number(list(reversed(bow)), (-1, 0)) == -1

计数的理由。 沿每条不经过指定点的边连续跟踪辐角。只有越过所选射线时,选在 [0,2π) 内的分支需要增加或减去一个 2π 来保持连续。向上穿过正向射线需要增加,向下需要减少;叉积符号判断交点是否在正向射线上。半开端点规则把穿越顶点的贡献恰计一次。因此累计数等于连续辐角总变化除以 2π。

对 n 条边,算法进行 O(n) 次有理数运算与比较,点列规范化占 O(n) 空间;实际位运算代价还随整数位数变化。浮点输入会被转换为其实际二进制数值的精确分数,若原始数据代表精确十进制,宜传十进制字符串或已构造的分数。

连续变形只要保持闭合并始终避开指定点,绕数就保持不变;指定点穿过曲线时则可能跳变。对于开曲线,可以计算辐角变化,但没有闭合性便没有这里的整数结论。

参考文献

  1. ↑ Jeremy Orloff,MIT 18.04,Topic 11: Argument Principle,关于index、绕行方向与总辐角变化的部分。