constraint-solver
A small, generic constraint-solving core for nonlinear systems. This crate provides:
- A symbolic expression tree (
Exp) with evaluation and differentiation. - A compiler that maps variable names to internal IDs (
Compiler). - A dense matrix type with LU and QR-based least squares (
Matrix). - A Newton-Raphson solver with damping, regularization, and optional line search (
NewtonRaphsonSolver).
This crate is intentionally CAD-agnostic and focuses on the math/solver core. Architected by humans and coded with LLM.
Usage
Add the crate to your Cargo.toml (path or git dependency depending on your setup).
System Types (Square, Under-, Over-Constrained)
The solver supports all three system shapes:
- Square systems (equations == variables): solved via LU on the Jacobian.
- Under-constrained systems (equations < variables): solved via QR least squares, returning the minimum-norm solution among all valid solutions.
- Over-constrained systems (equations > variables): solved via QR least squares, minimizing the residual error.
Square system example
use ;
use HashMap;
let x = var;
let y = var;
let f1 = sub;
let f2 = sub;
let compiled = compile.expect;
let solver = new;
let mut initial = new;
initial.insert;
initial.insert;
let solution = solver.solve.expect;
assert!;
Under-constrained example (minimum-norm)
use ;
use HashMap;
let x = var;
let y = var;
// x + y = 1 has infinitely many solutions; the solver returns x = y = 0.5.
let eq = sub;
let compiled = compile.expect;
let solver = new;
let mut initial = new;
initial.insert;
initial.insert;
let solution = solver.solve.expect;
assert!;
Over-constrained example (least squares)
use ;
use HashMap;
let x = var;
// Consistent over-determined system: x = 1 and 2x = 2.
let eq1 = sub;
let eq2 = sub;
let compiled = compile.expect;
let solver = new;
let mut initial = new;
initial.insert;
let solution = solver.solve.expect;
assert!;
Example: solve a simple system
use ;
use HashMap;
Line search and regularization
use ;
use HashMap;
let x = var;
let y = var;
let f1 = sub;
let f2 = sub;
let compiled = compile.expect;
let solver = new
.with_tolerance
.with_max_iterations
.with_regularization;
let mut initial = new;
initial.insert;
initial.insert;
let solution = solver
.solve_with_line_search
.expect;
assert!;
Examples
The examples/ folder contains small end-to-end programs that show how to build
constraints and run the solver:
cad_circle_intersection: two circle constraints; finds one of the intersection points. Run withcargo run --example cad_circle_intersection.cad_segment_horizontal: fixed-length segment with a horizontal constraint; solves for B while A is fixed. Run withcargo run --example cad_segment_horizontal.circuit_voltage_divider: resistive divider with explicit currents and KCL. Run withcargo run --example circuit_voltage_divider.circuit_diode: diode + resistor using the Shockley equation (nonlinear). Run withcargo run --example circuit_diode.
Testing
Run the unit tests with:
Notes
- The solver supports square, under-constrained, and over-constrained systems.
- Least squares solving uses QR to improve numerical stability.
- Regularization is applied adaptively when the QR solve is ill-conditioned; use
.with_regularization(0.0)to disable it. - Expressions are built by variable name, then compiled into a solver-friendly representation. Provide all variables referenced by equations in the initial guess map; missing variables are treated as errors.
- Internally, the solver caches Jacobian storage between iterations to reduce allocations; this is an implementation detail and does not affect the public API.
License
MIT. See LICENSE for details.