CCG 06-05:完整运行、结果解释与测试
本页只解决一个问题
怎样区分求解残差、几何质量和程序正确性?
本页是原长篇讲义的单概念版本。第一次阅读只追踪本页定义的对象;看见未掌握的前置符号时,先沿链接退回,不要跳过定义硬背结论。
开始前
- 模块前置入口:先修页面
- 本页所属模块:Python 网格参数化实验
- 集中复习版:原长篇参考讲义
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 能量 | 角度通常改善;若有负权重,检查翻转 |
| LSCM | Cauchy–Riemann 残差 | 边界可自由变形, 通常较低;不保证无翻转 |
如果三个方法给出完全相同结果,可能是测试网格太平坦/太对称,无法区分算法。可增大曲面弯曲、改变采样密度或换成带强凹边界的圆盘网格。
14.1 本文代码的实测结果
在 nx=24, ny=16 的默认网格上共有 425 个顶点、1192 条边、768 个三角形, 且只有一条边界。实际运行得到:
| 方法 | 负边权数 | 线性残差 | 翻转 | 中位数 | 95% | 最大值 | 95% | |---|---😐---😐---😐---😐---😐---😐---😐 | Uniform Tutte | 0 | | 0 | 1.318 | 2.475 | 6.151 | 0.424 | | Cotangent harmonic | 188 | | 0 | 1.251 | 2.283 | 6.190 | 0.391 | | LSCM | 不适用 | | 0 | 1.017 | 1.024 | 1.026 | 0.0116 |
这里能看到三点:
- 三种线性系统都被高精度求解,几何差异不是由“方程没解准”造成的。
- Cotangent 权重存在负值,但本例仍没有翻转;负权重表示 Tutte 的理论保证失效,不表示结果必然失败。
- LSCM 允许边界自由变化,因此能显著降低角度畸变;固定为圆的两种方法承担了额外边界约束。这个比较不能简单解读为“LSCM 在任何任务都更好”。
15. 单元测试
15.1 相似变换的共形误差应为 0
对单三角形局部坐标,令
应得到、、无翻转。
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
把一个三角形施加任意相似变换,代入,残差应接近 0。若不为 0,通常是:
u/v自由度排列错误;- 局部坐标朝向错误;
- 基函数梯度符号错误;
- CR 方程行的正负号不一致。
读完必须能回答
- 不看公式,用自己的话回答开头的核心问题。
- 指出本页最重要公式中每个符号的类型:标量、向量、矩阵、映射还是集合。
- 找出定理成立所需的假设,并举一个删除某条假设后可能失败的例子。
- 对最小例子手算一遍;若本页含代码,再比较手算与程序输出。
前后课程
许可协议:CC BY
作者:Davids
本文链接:https://hustjjd.github.io/ccg/06-05-lesson/
更新于:2026年10月12日