Architecture
Module organization, data flow, and core design of the 2D geometric constraint solver.
Architecture
The solver is organized into 35+ source files spanning six major modules. Every module has a single responsibility and a clean public interface, making the codebase straightforward to navigate and extend.
Module Overview
| Module | Files | Responsibility |
|---|---|---|
| entities | entities.rs | 10 geometric entity types and their parameter storage |
| constraints | constraints.rs, residual.rs | 47+ constraint types and residual evaluation |
| types | types.rs | Shared enums, structs, error types |
| system | system.rs, dof.rs, normalize.rs, variable_analysis.rs | Central entry point, DOF analysis, parameter reduction |
| solver | solver/mod.rs, lm.rs, dogleg.rs, bfgs.rs, lbfgs.rs, sparse.rs, propagation.rs, multistart.rs, decomposition.rs | All solver algorithms and decomposition |
| diagnostics | diagnostics.rs, validation.rs, tracing.rs, performance.rs, branch.rs | Verification, profiling, branch detection |
| support | weight.rs, underconstrained.rs, redundancy.rs, undo.rs, cross_validation.rs, snapshot.rs, jacobian.rs | Weights, underconstrained handling, undo, Jacobian |
System Struct — The Central Entry Point
The System struct is the single public API surface. Users create a System, add entities and constraints, then call solve():
let mut sys = System::new(); let p1 = sys.add_point(0.0, 0.0); let p2 = sys.add_point(5.0, 0.0); sys.add_constraint(horizontal_constraint(p1, p2)); let result = sys.solve();
Internally, System holds the flat parameter vector, entity metadata, constraint list, and solver configuration. It orchestrates the entire solve pipeline from reduction through to verification.
Variable Mapping and Parameter Reduction
Each entity maps to a contiguous slice of the global parameter vector:
- Point →
[x, y](2 parameters) - Line →
[x1, y1, x2, y2](4 parameters) - Circle →
[cx, cy, r](3 parameters) - Arc →
[cx, cy, r, start_angle, end_angle](5 parameters) - Ellipse →
[cx, cy, a, b, angle](5 parameters)
Parameter reduction eliminates redundant DOFs before solving. Constraints like Coincident, Horizontal, Vertical, EqualRadius, Concentric, and EqualMinorAxes are applied as substitutions, reducing the parameter count and improving numerical stability. A typical 20-constraint sketch may shrink from 40 parameters to 18 after reduction.
Constraint Graph Decomposition
The solver uses Union-Find to partition the constraint graph into independent connected components. Each component is solved in isolation:
- Build an undirected graph where entities are nodes and constraints are edges.
- Run Union-Find to identify connected components.
- Extract each component's parameter subset and constraint subset.
- Solve components in order (or in parallel for independent ones).
This decomposition means a sketch with two disconnected sub-sketches is solved as two small problems rather than one large one, improving both speed and robustness.
Degrees of Freedom (DOF) Analysis
The dof module computes the DOF count before solving:
DOF = Σ(entity_params) − Σ(constraint_equations) + Σ(reductions)
- DOF > 0: Underconstrained — solver adds
SoftAnchororMinimumNormregularization. - DOF = 0: Well-constrained — standard solve.
- DOF < 0: Overconstrained — solver proceeds with least-squares, reports redundant constraints.
The DOF count is displayed in diagnostics and used to select the appropriate solver strategy.
Data Flow
The complete data flow through the solver:
Entities + Constraints
│
▼
Parameter Reduction (normalize.rs)
│
▼
Constraint Graph Decomposition (Union-Find)
│
▼
Per-Component Solve Loop:
│
├─► Residual Function r(x)
│ residual.rs — evaluates all constraint residuals
│
├─► Jacobian J(x)
│ jacobian.rs — analytical sparse Jacobian (CSC)
│
├─► Solver Algorithm
│ LM / DogLeg / BFGS / L-BFGS / Sparse CG
│
└─► Convergence Check
‖r‖ < tol or max iterations reached
│
▼
Solution Verification (validation.rs)
│
▼
Diagnostics + Result
Each step in this pipeline is independently testable and replaceable. The modular design allows swapping solver algorithms, adding new constraint types, or inserting custom diagnostics without touching unrelated code.