CCG 06-05:完整运行、结果解释与测试

本页只解决一个问题

怎样区分求解残差、几何质量和程序正确性?

本页是原长篇讲义的单概念版本。第一次阅读只追踪本页定义的对象;看见未掌握的前置符号时,先沿链接退回,不要跳过定义硬背结论。

开始前

13. 完整实验入口

def main():
    vertices, faces = curved_rectangle_mesh(nx=24, ny=16)
    edges, boundary, topology = validate_disk(vertices, faces)
    print("topology:", topology)

    experiments = []
    for name, mode in [
        ("uniform Tutte", "uniform"),
        ("cotangent harmonic", "cotangent"),
    ]:
        uv, diagnostics = harmonic_parameterization(
            vertices, faces, boundary, mode
        )
        metrics = parameterization_metrics(vertices, faces, uv)
        experiments.append((name, uv, metrics, diagnostics))

    uv, diagnostics = lscm_parameterization(vertices, faces, boundary)
    metrics = parameterization_metrics(vertices, faces, uv)
    experiments.append(("LSCM", uv, metrics, diagnostics))

    for name, _, metrics, diagnostics in experiments:
        summary = {
            key: value
            for key, value in metrics.items()
            if not key.startswith("face_")
        }
        print("\n", name)
        print("diagnostics:", diagnostics)
        print("distortion:", summary)

    plot_parameterizations(faces, experiments)


if __name__ == "__main__":
    main()

14. 结果应该怎样解释

不要预设某个方法在所有指标上最好。一般预期:

方法主要优化目标你应检查的现象
Uniform Tutte正权重重心条件凸边界下翻转应为 0,但角度畸变可能较大
Cotangent harmonic固定圆边界的 Dirichlet 能量角度通常改善;若有负权重,检查翻转
LSCMCauchy–Riemann 残差边界可自由变形,KK 通常较低;不保证无翻转

如果三个方法给出完全相同结果,可能是测试网格太平坦/太对称,无法区分算法。可增大曲面弯曲、改变采样密度或换成带强凹边界的圆盘网格。

14.1 本文代码的实测结果

在 nx=24, ny=16 的默认网格上共有 425 个顶点、1192 条边、768 个三角形,χ=1\chi=1 且只有一条边界。实际运行得到:

| 方法 | 负边权数 | 线性残差∥r∥∞\|r\|_\infty | 翻转 |KK 中位数 |KK 95% |KK 最大值 |∣μ∣|\mu| 95% | |---|---😐---😐---😐---😐---😐---😐---😐 | Uniform Tutte | 0 |2.78×10−152.78\times10^{-15} | 0 | 1.318 | 2.475 | 6.151 | 0.424 | | Cotangent harmonic | 188 |1.24×10−151.24\times10^{-15} | 0 | 1.251 | 2.283 | 6.190 | 0.391 | | LSCM | 不适用 |1.78×10−151.78\times10^{-15} | 0 | 1.017 | 1.024 | 1.026 | 0.0116 |

这里能看到三点:

  1. 三种线性系统都被高精度求解,几何差异不是由“方程没解准”造成的。
  2. Cotangent 权重存在负值,但本例仍没有翻转;负权重表示 Tutte 的理论保证失效,不表示结果必然失败。
  3. LSCM 允许边界自由变化,因此能显著降低角度畸变;固定为圆的两种方法承担了额外边界约束。这个比较不能简单解读为“LSCM 在任何任务都更好”。

15. 单元测试

15.1 相似变换的共形误差应为 0

对单三角形局部坐标qq,令

U=sRq+t.U=sRq+t.

应得到σ1=σ2=s\sigma_1=\sigma_2=s、K=1K=1、无翻转。

def test_similarity_distortion():
    points = np.array([
        [0.0, 0.0, 0.0],
        [2.0, 0.0, 0.0],
        [0.4, 1.3, 0.0],
    ])
    q = local_triangle_2d(*points)
    angle, scale = 0.7, 2.3
    R = np.array([
        [np.cos(angle), -np.sin(angle)],
        [np.sin(angle),  np.cos(angle)],
    ])
    uv = scale * (q @ R.T) + np.array([3.0, -1.0])
    J = triangle_jacobian(points, uv)
    sigma = np.linalg.svd(J, compute_uv=False)
    assert np.allclose(sigma, [scale, scale])
    assert np.linalg.det(J) > 0.0


def test_boundary_of_boundary(vertices, faces):
    # 可复用前篇文章的 B1/B2 组装;这里强调算法不变量。
    from numpy.testing import assert_allclose
    edges, B1, B2 = boundary_matrices(len(vertices), faces)
    assert_allclose((B1 @ B2).toarray(), 0.0, atol=1e-12)

15.2 LSCM 局部 patch test

把一个三角形施加任意相似变换,代入CtxtC_tx_t,残差应接近 0。若不为 0,通常是:

  • u/v 自由度排列错误;
  • 局部坐标朝向错误;
  • 基函数梯度符号错误;
  • CR 方程行的正负号不一致。

读完必须能回答

  1. 不看公式,用自己的话回答开头的核心问题。
  2. 指出本页最重要公式中每个符号的类型:标量、向量、矩阵、映射还是集合。
  3. 找出定理成立所需的假设,并举一个删除某条假设后可能失败的例子。
  4. 对最小例子手算一遍;若本页含代码,再比较手算与程序输出。

前后课程

上一课:Jacobian 与畸变热图 | 下一课:数值故障与真实模型管线