约束求解器原理
第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 冗余约束检测
检测方法
- 计算雅可比矩阵的秩
- 如果 rank(J) < m,存在冗余
- 通过行约减识别冗余约束
性能考虑
- 完整冗余检测计算量大 (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 调试技巧
诊断步骤
- 检查DOF显示
- 查看失败约束列表
- 临时删除可疑约束
- 逐步添加约束定位问题
10.10 总结
本章介绍了SolveSpace约束求解器的原理:
- 概述: 求解器架构和特点
- 数学基础: 约束方程、雅可比矩阵、自由度
- 求解算法: 牛顿法、LM算法、符号微分
- 约束处理: 类型映射、隐式约束、冗余检测
- 拖动行为: 优先级、ForceTo、稳定性
- 实现细节: 数据结构、流程、表达式系统
- 独立库: libslvs API
- 性能: 复杂度、优化、设置
- 错误处理: 结果代码、失败检测
下一章将介绍命令行工具的使用。
导航
- 上一章: 第09章 - 导入导出与数据交换
- 下一章: 第11章 - 命令行工具使用

浙公网安备 33010602011771号