Skip to content

Commit f73ca7d

Browse files
docs: clean stale documentation state
Harness: docs-release Scope: docs-only Evidence: python3 contract id consistency check Evidence: python3 markdown relative link check Evidence: git diff --check Evidence: ctest --test-dir build -N
1 parent e779503 commit f73ca7d

22 files changed

Lines changed: 216 additions & 114 deletions

README.md

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11

22

3-
# MiniSolver: High-Performance Embedded NMPC Library
3+
# MiniSolver: High-Performance NMPC Library
44

55
![C++17](https://img.shields.io/badge/C++-17-blue.svg)
66
![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)
7-
![Platform](https://img.shields.io/badge/Platform-Linux%20|%20macOS%20|%20Embedded-lightgrey)
7+
![Platform](https://img.shields.io/badge/Platform-Linux%20|%20macOS%20|%20Embedded--Oriented-lightgrey)
88
![Zero-Malloc](https://img.shields.io/badge/Memory-Zero--Malloc-brightgreen)
99

1010
**MiniSolver** is a professional-grade, header-only C++17 library for solving Nonlinear Model Predictive Control (NMPC) problems.
1111

12-
Engineered specifically for **embedded robotics** and **autonomous driving**, it combines the flexibility of Python-based symbolic modeling with the raw performance of highly optimized, template-generated C++ code. The default real-time configuration is designed for **Zero Dynamic Memory Allocation (Zero-Malloc)** during the solve phase; profiling and iteration logging are opt-in diagnostics and should stay disabled in hard real-time loops.
12+
Designed for real-time robotics and autonomous-driving research, it combines the flexibility of Python-based symbolic modeling with the raw performance of highly optimized, template-generated C++ code. The default real-time configuration is designed for **Zero Dynamic Memory Allocation (Zero-Malloc)** during the solve phase; profiling and iteration logging are opt-in diagnostics and should stay disabled in hard real-time loops.
1313

1414
---
1515

@@ -21,7 +21,7 @@ Engineered specifically for **embedded robotics** and **autonomous driving**, it
2121
* **Analytical Derivatives**: Uses SymPy to generate flattened, algebraically simplified C++ code for Jacobians and Hessians at compile-time, including true Gauss-Newton Hessians for explicit least-squares residuals.
2222
* **🔥 Fused Riccati Kernels**: Unlike solvers that use generic matrix libraries, MiniSolver uses Python (SymPy) to symbolically fuse the Riccati backward pass (`Q + A'PA`) into a single, flattened C++ function. This eliminates all loop overhead and explicitly bypasses multiplication by zero, achieving **perfect sparsity exploitation** for small-to-medium systems ($N_x < 20$).
2323

24-
### 🛡️ Embedded Safety & Robustness
24+
### 🛡️ Real-Time Robustness
2525
* **Zero-Malloc Solve Path**: The default solver configuration performs no `new`/`malloc` calls during `solve()`. Keep profiling and iteration logging disabled for hard real-time use.
2626
* **Robust Interior Point Method (IPM)**:
2727
* **Mehrotra Predictor-Corrector**: Drastically reduces iteration counts by utilizing higher-order corrections.
@@ -37,7 +37,11 @@ Engineered specifically for **embedded robotics** and **autonomous driving**, it
3737

3838
## 📊 Performance Benchmarks
3939

40-
Benchmarks performed on an Intel Core i7 (Single Thread) for a **Kinematic Bicycle Model with Obstacle Avoidance** ($N_x=6, N_u=2, N=50$).
40+
Historical local benchmark snapshot on an Intel Core i7 (single thread) for a
41+
**Kinematic Bicycle Model with Obstacle Avoidance** ($N_x=6, N_u=2, N=50$).
42+
For release claims or solver-to-solver comparisons, use the dated reports in
43+
the benchmark repository with the exact MiniSolver commit, command line,
44+
backend, and hardware recorded.
4145

4246
| Strategy | Integrator | Line Search | Avg Time | Iterations | Status |
4347
| :--- | :--- | :--- | :--- | :--- | :--- |
@@ -129,12 +133,19 @@ MiniSolver includes a one-click build script that handles dependency checking, c
129133
./build.sh
130134
```
131135

132-
## 📦 Embedded Deployment
133-
134-
1. Generate your model headers on a PC: `python3 generate_model.py`
135-
2. Copy the `minisolver/` include folder and your generated headers to your MCU project.
136-
3. Define `USE_CUSTOM_MATRIX` to remove `Eigen3` dependency.
137-
4. Compile with `-O3`. **No external libraries required.**
136+
## 📦 Embedded-Oriented Deployment Notes
137+
138+
1. Generate your model headers on a development machine:
139+
`python3 generate_model.py`.
140+
2. Copy the `minisolver/` include folder and your generated headers into the
141+
target project.
142+
3. Define `USE_CUSTOM_MATRIX` to remove the Eigen dependency when the
143+
built-in fixed-size matrix backend is appropriate.
144+
4. Keep profiling, iteration logging, snapshots, and code generation out of
145+
hard real-time loops.
146+
5. Validate the target toolchain and runtime profile. MiniSolver is
147+
embedded-oriented, but it is not yet documented as a freestanding C++ or
148+
no-exceptions embedded profile.
138149

139150
-----
140151

docs/architecture/globalization-mehrotra-theory-plan.md

Lines changed: 48 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
Date: 2026-05-03
44

5-
Status: N-THEORY-3, N-THEORY-6, and N-THEORY-1 implemented; N-THEORY-2 deferred
5+
Status: current-state ledger. N-THEORY-3, N-THEORY-6, and N-THEORY-1 are
6+
implemented; N-THEORY-2 is deferred.
67

78
Related:
89

@@ -12,17 +13,21 @@ Related:
1213

1314
## Purpose
1415

15-
The deep review groups four remaining theory items:
16+
The deep review originally grouped four theory items:
1617

17-
- N-THEORY-1: Filter line-search gaps against the Waechter-Biegler route.
18-
- N-THEORY-2: Fixed ring-buffer filter history instead of a Pareto frontier.
19-
- N-THEORY-3: Mehrotra affine step uses one combined fraction-to-boundary
20-
instead of separate primal and dual affine step lengths.
21-
- N-THEORY-6: Merit line search uses finite-difference directional derivative
22-
instead of an analytic directional derivative.
18+
- N-THEORY-1: Filter line-search gaps against the Waechter-Biegler route
19+
(implemented except Pareto-frontier history).
20+
- N-THEORY-2: Fixed ring-buffer filter history instead of a Pareto frontier
21+
(deferred).
22+
- N-THEORY-3: Mehrotra affine step used one combined fraction-to-boundary
23+
instead of separate primal and dual affine step lengths (implemented).
24+
- N-THEORY-6: Merit line search used finite-difference directional derivative
25+
instead of an analytic directional derivative (implemented for the default
26+
multiple-shooting merit path).
2327

24-
These are not one bug. They touch different solver phases and need separate red
25-
tests and commits.
28+
These were not one bug. They touched different solver phases and were handled
29+
with separate tests and commits. This file now records the current state rather
30+
than an open implementation queue.
2631

2732
## Source Survey
2833

@@ -39,7 +44,7 @@ Local source survey:
3944
`sigma`, `alpha_prim`, and `alpha_dual`, matching the standard primal-dual IPM
4045
distinction between primal and dual step lengths.
4146

42-
MiniSolver previously had:
47+
Historical baseline before the follow-up work:
4348

4449
- `FilterLineSearch::is_acceptable()` with sufficient decrease plus filter
4550
entries, but no `theta_max` sentinel, no f-type/h-type switch, and no f-type
@@ -62,15 +67,15 @@ MiniSolver previously had:
6267

6368
### N-THEORY-3: Split Mehrotra Affine Step Lengths
6469

65-
Classification: theory-standard.
70+
Status: implemented.
6671

67-
Why it is real:
72+
Current behavior:
6873

69-
- Standard Mehrotra computes separate affine primal and dual steps, then uses
70-
them to estimate the affine complementarity gap.
71-
- MiniSolver currently uses one scalar fraction-to-boundary for `s`, `lambda`,
72-
`soft_s`, and `w-lambda`. This is safe but can be conservative because a
73-
primal-limited direction also shortens the dual affine step and vice versa.
74+
- MiniSolver computes separate affine primal and dual step lengths.
75+
- `compute_affine_barrier_mu_()` evaluates primal quantities with
76+
`alpha_primal_aff` and dual quantities with `alpha_dual_aff`.
77+
- Accepted primal-dual damping may still use a combined alpha when a single
78+
interior step bound is required.
7479

7580
Implementation shape:
7681

@@ -82,61 +87,50 @@ struct FractionToBoundaryResult {
8287
};
8388
```
8489
85-
Use:
90+
Current use:
8691
8792
- `primal` for `s` and L1 `soft_s`.
8893
- `dual` for `lambda` and L1 `w-lambda`.
8994
- `combined()` for accepted primal-dual step damping where one scalar is still
9095
required.
91-
- `compute_affine_barrier_mu_()` should evaluate `s` and `soft_s` with
96+
- `compute_affine_barrier_mu_()` evaluates `s` and `soft_s` with
9297
`alpha_primal`, and `lambda` / `w-lambda` with `alpha_dual`.
9398
94-
Red test:
99+
Evidence:
95100
96-
- Construct a small solver state where the affine primal step is strongly
97-
limited but the dual step is not.
98-
- Verify `mu_aff` from split steps is lower than the current combined-step
99-
value and matches a direct recomputation using separate alphas.
100-
- Verify L1 soft pair uses `soft_s + alpha_p * dsoft_s` and
101-
`w - (lambda + alpha_d * dlambda)`.
101+
- `tests/test_bugfixes.cpp::BugfixTest.MehrotraUsesSeparateAffinePrimalAndDualStepLengths`.
102+
- `tests/test_bugfixes.cpp::BugfixTest.MehrotraMuAffIncludesL1SoftPair`.
102103
103104
### N-THEORY-6: Analytic Merit Directional Derivative
104105
105-
Classification: theory-standard but implementation-sensitive.
106+
Status: implemented for the default multiple-shooting merit path.
106107
107-
Why it is real:
108+
Current behavior:
108109
109-
- Current finite-difference `dphi` costs an extra trial construction and model
110-
evaluation per search.
111-
- It is also sensitive to `eps_alpha` and can misclassify nearly flat merit
112-
directions.
113-
- acados computes the directional derivative analytically from cost,
114-
dynamics, and inequality contributions.
110+
- Merit line search computes the directional derivative analytically from
111+
already available cost, dynamics, and constraint quantities.
112+
- The default hot path does not construct an extra finite-difference probe to
113+
estimate `dphi`.
115114
116115
Implementation shape:
117116
118-
- Add an internal `MeritDirectionalDerivative` helper, not a public API.
117+
- Keep the derivative helper internal, not a public API.
119118
- Compute directional derivative of:
120119
- cost: `q^T dx + r^T du` using already evaluated gradients;
121120
- hard/L1/L2 residual penalties using signs of current true residuals and
122121
linearized residual directions;
123122
- dynamics defect penalties using signs of current defects and
124123
`dx_next - A dx - B du`;
125124
- barrier terms for `s`, `soft_s`, and `w-lambda`.
126-
- Keep finite-difference derivative behind a debug comparison flag only if a
127-
red test shows useful coverage; do not keep both paths in default hot logic.
128125
129-
Red tests:
126+
Evidence:
130127
131-
- Compare analytic derivative against finite difference on a fixed small model
132-
for hard, L1, and L2 constraints.
133-
- Cover rollout disabled first. Rollout-enabled merit derivative can be
134-
deferred unless the feature is used in benchmarks.
135-
- Verify no extra model evaluation is needed to compute `dphi`.
128+
- `tests/test_line_search.cpp::LineSearchTest.MeritArmijoDoesNotBuildFiniteDifferenceProbe`.
129+
- `tests/test_line_search.cpp::LineSearchTest.MeritNonFiniteDphiReturnsNumericalError`.
136130
137131
### N-THEORY-1: Filter Theory Completion
138132
139-
Classification: implemented except Pareto-frontier history.
133+
Status: implemented except Pareto-frontier history.
140134
141135
Sub-items:
142136
@@ -145,15 +139,14 @@ Sub-items:
145139
accepted steps.
146140
3. Done: switching condition using the filter objective directional derivative.
147141
148-
Implementation shape:
142+
Current implementation shape:
149143
150-
- Add `FilterAcceptanceKind { HType, FType }` as an internal result, not a user
151-
API.
144+
- `FilterAcceptanceKind { HType, FType }` is an internal result, not a user API.
152145
- Keep `LineSearchType::FILTER` as the user-facing selection.
153-
- Add `filter_theta_max_factor` / `filter_theta_min_factor` only if needed for
154-
behavior tuning; otherwise keep defaults internal first.
146+
- `filter_theta_max_factor` is a config field for the implemented theta gate;
147+
`theta_min` remains an internal constant.
155148
156-
Red tests:
149+
Evidence:
157150
158151
- `LineSearchTest.FilterRejectsTrialAboveThetaMax`.
159152
- `LineSearchTest.FilterFTypeUsesArmijoAndDoesNotAugmentFilter`.
@@ -186,10 +179,10 @@ Red test:
186179
187180
## Recommended Order
188181
189-
1. N-THEORY-3 split affine primal/dual step lengths. It is the smallest
190-
standard-route correctness/performance improvement.
191-
2. N-THEORY-6 analytic merit directional derivative for merit line search.
192-
3. N-THEORY-1a `theta_max` gate.
182+
1. Done: N-THEORY-3 split affine primal/dual step lengths.
183+
2. Done: N-THEORY-6 analytic merit directional derivative for merit line
184+
search.
185+
3. Done: N-THEORY-1a `theta_max` gate.
193186
4. Done: N-THEORY-1b f-type/h-type acceptance and filter augmentation rules.
194187
5. Done: N-THEORY-1c switching condition using derivative machinery from
195188
N-THEORY-6.

docs/architecture/soft-constraint-weight-api-plan.md

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
# Soft Constraint Weight API Plan
22

3+
Status: implemented design record. Current normative behavior lives in
4+
[`../contracts/soft-constraints-contract.md`](../contracts/soft-constraints-contract.md)
5+
and the MiniModel/codegen contracts.
6+
37
## Problem
48

5-
MiniModel currently stores one static `(type, weight)` pair per constraint row.
6-
That makes two user-facing cases awkward:
9+
Before the per-knot weight work, MiniModel stored one static `(type, weight)`
10+
pair per constraint row. That made two user-facing cases awkward:
711

812
1. a soft weight cannot be a model parameter updated at runtime;
913
2. one physical constraint cannot carry both L1 and L2 soft penalties without
@@ -14,7 +18,7 @@ That makes two user-facing cases awkward:
1418
MiniModel owns the modeling semantics. C++ must not decide whether a row is L1
1519
or L2 by inspecting the runtime weight value.
1620

17-
The generated model should expose fixed structure:
21+
The generated model exposes fixed structure:
1822

1923
```cpp
2024
static constexpr std::array<bool, NC> constraint_has_l1 = {...};
@@ -30,10 +34,17 @@ MSVec<T, NC> l1_weight;
3034
MSVec<T, NC> l2_weight;
3135
```
3236

33-
Together, the trajectory stores two `(N + 1) x NC` weight tables. A generated
34-
model function refreshes both tables for one knot:
37+
Together, the trajectory stores two `(N + 1) x NC` weight tables. Generated
38+
models may split L1 and L2 weight updates to avoid redundant clears, with a
39+
wrapper that refreshes the declared soft-weight tables for one knot:
3540

3641
```cpp
42+
template <typename T>
43+
static void update_l1_soft_constraint_weights(KnotPoint<T, NX, NU, NC, NP>& kp);
44+
45+
template <typename T>
46+
static void update_l2_soft_constraint_weights(KnotPoint<T, NX, NU, NC, NP>& kp);
47+
3748
template <typename T>
3849
static void update_soft_constraint_weights(KnotPoint<T, NX, NU, NC, NP>& kp);
3950
```
@@ -105,23 +116,25 @@ so the L1 and L2 penalties act on the same relaxation variable.
105116

106117
## Solver Integration
107118

108-
The solver reads structure and values separately:
119+
The solver reads structure and values separately. The row route is hard first,
120+
then mixed L1+L2, then pure L1, then L2 or the regularized weak-L2 relaxation:
109121

110122
```cpp
111-
if constexpr (Model::any_l1_constraints) {
112-
if (Model::constraint_has_l1[i]) {
113-
const double w = kp.l1_weight(i);
114-
// existing L1 path
115-
}
116-
}
117-
if constexpr (Model::any_l2_constraints) {
118-
if (Model::constraint_has_l2[i]) {
119-
const double w = kp.l2_weight(i);
120-
// existing L2 path
121-
}
123+
if (hard_constraint_row<Model>(i)) {
124+
// hard path
125+
} else if (active_mixed_l1_l2_soft_constraint<Model>(kp, i, config)) {
126+
// shared soft_s with z = w1 + w2 * soft_s - lambda
127+
} else if (active_l1_soft_constraint<Model>(kp, i, config)) {
128+
// pure L1 path
129+
} else {
130+
// active L2 or declared-soft weak L2 relaxation
131+
const double w = effective_l2_soft_weight<Model>(kp, i, config);
122132
}
123133
```
124134

135+
Mixed L1+L2 is not implemented as "run pure L1, then run pure L2"; it has a
136+
single relaxation variable and a single mixed soft-dual expression.
137+
125138
Hand-written test or example models must use the same structure arrays and
126139
weight updater. The solver core does not infer L1/L2 semantics from legacy
127140
`constraint_types` or `constraint_weights`.
@@ -133,5 +146,7 @@ weight updater. The solver core does not infer L1/L2 semantics from legacy
133146
- MiniModel test: one constraint row can have both L1 and L2 soft weights.
134147
- Solver test: changing a parameter weight at runtime changes the per-stage
135148
stored weight before solving.
149+
- Solver test: same-row L1+L2 uses the mixed KKT route and matches the intended
150+
shared-relaxation penalty behavior.
136151
- Regression tests: existing L1/L2 soft constraint behavior remains unchanged
137152
after internal test models migrate to the new structure/updater contract.

docs/modules/alg-barrier.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,7 @@ No owned state.
6767

6868
## Known Gaps
6969

70-
- Barrier update contract IDs are not assigned yet.
70+
- Current barrier-mu contracts live in
71+
[`../contracts/barrier-mu-contract.md`](../contracts/barrier-mu-contract.md).
72+
Remaining tuning or replay evidence should be tracked in
73+
[`../testing/contract-coverage-matrix.md`](../testing/contract-coverage-matrix.md).

docs/modules/alg-initialization.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,9 @@ No owned state; mutates caller-owned knot and solver scalar state.
7474

7575
## Known Gaps
7676

77-
- Initialization contract IDs for mixed soft central path are not assigned yet.
77+
- Current initialization and mixed soft central-path contracts live in
78+
[`../contracts/initialization-contract.md`](../contracts/initialization-contract.md)
79+
and
80+
[`../contracts/soft-constraints-contract.md`](../contracts/soft-constraints-contract.md).
81+
Remaining evidence gaps should stay in the coverage matrix, not this module
82+
summary.

docs/modules/alg-line-search.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,9 @@ accepted-step refresh, SOC integration points, and barrier-update history reset.
8080

8181
## Known Gaps
8282

83-
- Separate contracts for merit and filter globalization are not assigned yet.
83+
- Current globalization contracts are split across
84+
[`../contracts/line-search-contract.md`](../contracts/line-search-contract.md),
85+
[`../contracts/merit-line-search-contract.md`](../contracts/merit-line-search-contract.md),
86+
and
87+
[`../contracts/filter-line-search-contract.md`](../contracts/filter-line-search-contract.md).
88+
Remaining merit/filter evidence is tracked per contract row.

docs/modules/alg-model-evaluation.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,8 @@ constraint evaluation, scaling application, and residual helpers.
8080

8181
## Known Gaps
8282

83-
- Packet ownership and scaling contract IDs are not assigned yet.
83+
- Packet ownership and scaling contracts live in
84+
[`../contracts/constraint-packets-contract.md`](../contracts/constraint-packets-contract.md),
85+
[`../contracts/generated-packets-contract.md`](../contracts/generated-packets-contract.md),
86+
[`../contracts/scaling-contract.md`](../contracts/scaling-contract.md), and
87+
[`../contracts/row-scaling-contract.md`](../contracts/row-scaling-contract.md).

docs/modules/alg-termination.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,9 @@ and residual stagnation monitoring.
8282

8383
## Known Gaps
8484

85-
- Termination contract IDs are not assigned yet.
85+
- Termination, postsolve, status, and tolerance semantics are covered by
86+
[`../contracts/termination-contract.md`](../contracts/termination-contract.md),
87+
[`../contracts/postsolve-contract.md`](../contracts/postsolve-contract.md),
88+
[`../contracts/status-semantics-contract.md`](../contracts/status-semantics-contract.md),
89+
and
90+
[`../contracts/tolerance-contract.md`](../contracts/tolerance-contract.md).

0 commit comments

Comments
 (0)