约束求解器原理

第10章 约束求解器原理

10.1 约束求解器概述

10.1.1 什么是约束求解器

约束求解器是参数化CAD的核心引擎。它负责:

  • 将几何约束转换为数学方程
  • 求解非线性方程组
  • 找到满足所有约束的参数值
  • 支持交互式拖动操作

10.1.2 SolveSpace求解器特点

技术特点

  • 基于牛顿-拉夫森方法
  • 使用Levenberg-Marquardt算法改进收敛
  • 支持符号微分
  • 高效的稀疏矩阵运算

设计特点

  • 独立的求解器库 (libslvs)
  • C API便于集成
  • Python绑定
  • 开源可扩展

10.1.3 求解器架构

约束求解器架构:

用户界面
    ↓
约束收集器
    ↓ 收集参数、实体、约束
方程生成器
    ↓ 转换为数学方程
雅可比计算器
    ↓ 计算偏导数矩阵
线性求解器 (Eigen库)
    ↓ 求解线性系统
迭代控制器
    ↓ 牛顿迭代
结果输出

10.2 数学基础

10.2.1 约束方程

每个约束转换为一个或多个方程:

距离约束

两点P1(x1,y1)和P2(x2,y2)之间的距离等于d:
f(x1,y1,x2,y2) = sqrt((x2-x1)² + (y2-y1)²) - d = 0

水平约束

线段的两端点y坐标相等:
f(y1,y2) = y1 - y2 = 0

角度约束

两线夹角为θ:
f = dot(A,B) / (|A||B|) - cos(θ) = 0

10.2.2 方程组形式

给定:
- n个参数: p = (p1, p2, ..., pn)
- m个约束方程: f1(p)=0, f2(p)=0, ..., fm(p)=0

写成向量形式:
F(p) = 0

其中 F: Rⁿ → Rᵐ

10.2.3 雅可比矩阵

雅可比矩阵J是F对p的偏导数矩阵:

J = [∂F/∂p]

J_ij = ∂fi/∂pj

例如距离约束:
∂f/∂x1 = -(x2-x1) / d
∂f/∂y1 = -(y2-y1) / d
∂f/∂x2 = (x2-x1) / d
∂f/∂y2 = (y2-y1) / d

10.2.4 自由度计算

自由度 (DOF) = n - rank(J)

其中:
n = 参数数量
rank(J) = 雅可比矩阵的秩

完全约束: DOF = 0
欠约束: DOF > 0
过约束: rank(J) < m (方程数)

10.3 求解算法

10.3.1 牛顿-拉夫森方法

基本牛顿迭代:

给定初始猜测 p⁰

重复直到收敛:
    计算 F(pᵏ)
    计算 J(pᵏ)
    求解 J(pᵏ) · Δp = -F(pᵏ)
    更新 pᵏ⁺¹ = pᵏ + Δp

收敛条件

  • |F(p)| < ε (约束满足)
  • |Δp| < δ (参数稳定)

10.3.2 Levenberg-Marquardt改进

为改善收敛性,使用阻尼因子:

(JᵀJ + λI) · Δp = -JᵀF

λ = 0: 纯牛顿方法
λ → ∞: 梯度下降

自适应λ

如果 |F(p+Δp)| < |F(p)|:
    接受步进
    减小λ (更接近牛顿)
否则:
    拒绝步进
    增大λ (更保守)

10.3.3 符号微分

SolveSpace使用符号微分而非数值微分:

优势

  • 精确的导数值
  • 避免数值误差
  • 更好的收敛性

实现

// 表达式结构支持自动微分
class Expr {
    double Eval();           // 计算值
    Expr *PartialWrt(hParam p);  // 符号微分
};

10.3.4 稀疏矩阵优化

雅可比矩阵通常是稀疏的:

典型的雅可比矩阵:
[x . . x . .]
[. x x . . .]
[x . x . . x]
[. . . x x .]

大部分元素为0

优化策略

  • 使用Eigen稀疏矩阵库
  • 仅存储非零元素
  • 稀疏LU/QR分解

10.4 约束处理

10.4.1 约束类型映射

每种约束类型对应特定的方程:

约束类型 → 方程数 → 方程形式

POINTS_COINCIDENT:
  2D: 2方程  (ΔX=0, ΔY=0)
  3D: 3方程  (ΔX=0, ΔY=0, ΔZ=0)

PT_PT_DISTANCE:
  1方程  (dist - d = 0)

HORIZONTAL:
  1方程  (ΔY = 0)

PARALLEL:
  2D: 1方程
  3D: 2方程

SAME_ORIENTATION:
  3方程 (四元数约束)

10.4.2 隐式约束

某些约束是隐式的:

四元数归一化

法线使用单位四元数表示:
q² + qx² + qy² + qz² = 1

这是隐式添加的约束

圆弧半径一致

圆弧的起点和终点到圆心距离相等:
dist(center, start) = dist(center, end)

10.4.3 冗余约束检测

检测方法

  1. 计算雅可比矩阵的秩
  2. 如果 rank(J) < m,存在冗余
  3. 通过行约减识别冗余约束

性能考虑

  • 完整冗余检测计算量大 (O(n³))
  • 可配置超时限制
  • 在组属性中可禁用检测

10.5 拖动行为

10.5.1 拖动优先级

拖动实体时,求解器优先保持拖动目标:

拖动点P到新位置P':

添加临时约束:
P.x = P'.x (高优先级)
P.y = P'.y (高优先级)

求解时:
- 优先满足拖动约束
- 调整其他参数

10.5.2 ForceTo方法

// 点的ForceTo实现
void Entity::PointForceTo(Vector pt) {
    // 直接设置参数值(如果可能)
    // 或设置为求解器的目标
    
    switch(type) {
        case POINT_IN_2D:
            // 反算工作平面坐标
            break;
        case POINT_N_TRANS:
            // 反算变换参数
            break;
    }
}

10.5.3 拖动稳定性

为保证拖动平滑:

策略

  • 小步迭代
  • 从上次位置开始求解
  • 拖动参数标记为"dragged"

dragged标记效果

求解时减小dragged参数的权重
使其变化最小化

10.6 求解器实现

10.6.1 核心数据结构

// 系统结构
struct System {
    IdList<Param> param;         // 参数列表
    IdList<Equation> eq;         // 方程列表
    
    IdList<hParam> dragged;      // 拖动参数
    
    // 矩阵
    Matrix J;                    // 雅可比矩阵
    Vector F;                    // 方程值向量
};

10.6.2 求解流程

int System::Solve() {
    // 1. 构建方程系统
    GenerateEquations();
    
    // 2. 初始迭代
    for(int iter = 0; iter < MAX_ITERATIONS; iter++) {
        // 计算方程值和雅可比矩阵
        EvalJacobian();
        
        // 检查收敛
        if(converged()) return OKAY;
        
        // 求解线性系统
        SolveLinearSystem();
        
        // 更新参数
        UpdateParams();
    }
    
    return DIDNT_CONVERGE;
}

10.6.3 表达式系统

// 表达式类型
class Expr {
public:
    enum {
        CONSTANT,
        PARAM,
        PLUS, MINUS, TIMES, DIV,
        SIN, COS, SQRT, ...
    } op;
    
    double v;        // 常量值
    hParam x;        // 参数引用
    Expr *a, *b;     // 操作数
    
    double Eval();           // 求值
    Expr *PartialWrt(hParam p);  // 微分
};

10.7 独立求解器库 (libslvs)

10.7.1 库概述

SolveSpace的约束求解器可作为独立库使用。

构建

cmake .. -DENABLE_GUI=OFF -DENABLE_CLI=OFF
make slvs

生成文件

  • libslvs.so (Linux)
  • slvs.dll (Windows)
  • libslvs.dylib (macOS)

10.7.2 C API概览

// 主要数据结构
typedef struct {
    Slvs_Param *param;
    int params;
    Slvs_Entity *entity;
    int entities;
    Slvs_Constraint *constraint;
    int constraints;
    
    Slvs_hParam *dragged;
    int ndragged;
    
    Slvs_hConstraint *failed;
    int faileds;
    
    int dof;
    int result;
} Slvs_System;

// 求解函数
void Slvs_Solve(Slvs_System *sys, Slvs_hGroup hg);

10.7.3 实体类型

#define SLVS_E_POINT_IN_3D      50000
#define SLVS_E_POINT_IN_2D      50001
#define SLVS_E_NORMAL_IN_3D     60000
#define SLVS_E_NORMAL_IN_2D     60001
#define SLVS_E_DISTANCE         70000
#define SLVS_E_WORKPLANE        80000
#define SLVS_E_LINE_SEGMENT     80001
#define SLVS_E_CUBIC            80002
#define SLVS_E_CIRCLE           80003
#define SLVS_E_ARC_OF_CIRCLE    80004

10.7.4 约束类型

#define SLVS_C_POINTS_COINCIDENT   100000
#define SLVS_C_PT_PT_DISTANCE      100001
#define SLVS_C_HORIZONTAL          100019
#define SLVS_C_VERTICAL            100020
#define SLVS_C_PARALLEL            100025
#define SLVS_C_PERPENDICULAR       100026
// ... 更多约束类型

10.8 求解性能

10.8.1 复杂度分析

时间复杂度

单次迭代: O(n³) 主要来自线性求解
迭代次数: 通常 5-20 次
总复杂度: O(k·n³) k为迭代次数

空间复杂度

雅可比矩阵: O(n·m)
实际使用稀疏存储: O(nnz) nnz为非零元素数

10.8.2 性能优化

3.1版本改进

  • 使用Eigen库替代自定义矩阵
  • 性能提升约8倍
  • 最大未知数从1024增加到2048

优化建议

  • 减少参数数量
  • 避免冗余约束
  • 分组管理复杂设计

10.8.3 性能设置

配置选项:
├── 冗余约束超时: [毫秒]
├── 最大迭代次数: [次数]
└── 抑制DOF计算: [是/否]

10.9 错误处理

10.9.1 求解结果代码

#define SLVS_RESULT_OKAY            0  // 成功
#define SLVS_RESULT_INCONSISTENT    1  // 约束冲突
#define SLVS_RESULT_DIDNT_CONVERGE  2  // 未收敛
#define SLVS_RESULT_TOO_MANY_UNKNOWNS 3  // 参数过多
#define SLVS_RESULT_REDUNDANT_OKAY  4  // 有冗余但成功

10.9.2 失败约束识别

// 启用失败约束检测
sys.calculateFaileds = 1;

// 求解后检查
if(sys.result != SLVS_RESULT_OKAY) {
    for(int i = 0; i < sys.faileds; i++) {
        printf("问题约束: %d\n", sys.failed[i]);
    }
}

10.9.3 调试技巧

诊断步骤

  1. 检查DOF显示
  2. 查看失败约束列表
  3. 临时删除可疑约束
  4. 逐步添加约束定位问题

10.10 总结

本章介绍了SolveSpace约束求解器的原理:

  1. 概述: 求解器架构和特点
  2. 数学基础: 约束方程、雅可比矩阵、自由度
  3. 求解算法: 牛顿法、LM算法、符号微分
  4. 约束处理: 类型映射、隐式约束、冗余检测
  5. 拖动行为: 优先级、ForceTo、稳定性
  6. 实现细节: 数据结构、流程、表达式系统
  7. 独立库: libslvs API
  8. 性能: 复杂度、优化、设置
  9. 错误处理: 结果代码、失败检测

下一章将介绍命令行工具的使用。


导航


posted @ 2026-01-10 13:15  我才是银古  阅读(38)  评论(0)    收藏  举报