mixsqpx 0.2.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ # misc other
2
+ # scrap/*
3
+ wheelhouse/
4
+ .codex
5
+
6
+ # pixi environments
7
+ .pixi
8
+ *.egg-info
9
+
10
+ # Byte-compiled / optimized / DLL files
11
+ __pycache__/
12
+ *.py[cod]
13
+ *$py.class
14
+
15
+ # C extensions
16
+ *.so
17
+
18
+ # Distribution / packaging
19
+ .Python
20
+ build/
21
+ develop-eggs/
22
+ dist/
23
+ downloads/
24
+ eggs/
25
+ .eggs/
26
+ # lib/
27
+ lib64/
28
+ parts/
29
+ sdist/
30
+ var/
31
+ wheels/
32
+ share/python-wheels/
33
+ *.egg-info/
34
+ .installed.cfg
35
+ *.egg
36
+ MANIFEST
37
+
38
+ # PyInstaller
39
+ # Usually these files are written by a python script from a template
40
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
41
+ *.manifest
42
+ *.spec
43
+
44
+ # Installer logs
45
+ pip-log.txt
46
+ pip-delete-this-directory.txt
47
+
48
+ # Unit test / coverage reports
49
+ htmlcov/
50
+ .tox/
51
+ .nox/
52
+ .coverage
53
+ .coverage.*
54
+ .cache
55
+ nosetests.xml
56
+ coverage.xml
57
+ *.cover
58
+ *.py,cover
59
+ .hypothesis/
60
+ .pytest_cache/
61
+ cover/
62
+
63
+ # Translations
64
+ *.mo
65
+ *.pot
66
+
67
+ # Django stuff:
68
+ *.log
69
+ local_settings.py
70
+ db.sqlite3
71
+ db.sqlite3-journal
72
+
73
+ # Flask stuff:
74
+ instance/
75
+ .webassets-cache
76
+
77
+ # Scrapy stuff:
78
+ .scrapy
79
+
80
+ # Sphinx documentation
81
+ docs/_build/
82
+
83
+ # PyBuilder
84
+ .pybuilder/
85
+ target/
86
+ Cargo.lock
87
+
88
+ # Jupyter Notebook
89
+ .ipynb_checkpoints
90
+
91
+ # IPython
92
+ profile_default/
93
+ ipython_config.py
94
+
95
+ # pyenv
96
+ # For a library or package, you might want to ignore these files since the code is
97
+ # intended to run in multiple environments; otherwise, check them in:
98
+ .python-version
99
+
100
+ # pipenv
101
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
102
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
103
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
104
+ # install all needed dependencies.
105
+ #Pipfile.lock
106
+
107
+ # poetry
108
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
109
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
110
+ # commonly ignored for libraries.
111
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
112
+ #poetry.lock
113
+
114
+ # pdm
115
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
116
+ #pdm.lock
117
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
118
+ # in version control.
119
+ # https://pdm.fming.dev/#use-with-ide
120
+ .pdm.toml
121
+
122
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
123
+ __pypackages__/
124
+
125
+ # Celery stuff
126
+ celerybeat-schedule
127
+ celerybeat.pid
128
+
129
+ # SageMath parsed files
130
+ *.sage.py
131
+
132
+ # Environments
133
+ .env
134
+ .venv
135
+ env/
136
+ venv/
137
+ ENV/
138
+ env.bak/
139
+ venv.bak/
140
+
141
+ # Spyder project settings
142
+ .spyderproject
143
+ .spyproject
144
+
145
+ # Rope project settings
146
+ .ropeproject
147
+
148
+ # mkdocs documentation
149
+ /site
150
+
151
+ # mypy
152
+ .mypy_cache/
153
+ .dmypy.json
154
+ dmypy.json
155
+
156
+ # Pyre type checker
157
+ .pyre/
158
+
159
+ # pytype static type analyzer
160
+ .pytype/
161
+
162
+ # Cython debug symbols
163
+ cython_debug/
164
+
@@ -0,0 +1,3 @@
1
+ [workspace]
2
+ members = ["rust/mixsqpx_rust"]
3
+ resolver = "2"
@@ -0,0 +1,41 @@
1
+ # Legacy C++ Reference Workflow
2
+
3
+ This branch is Rust-first. The legacy C++/Armadillo backend is preserved only as
4
+ an archival benchmark and reference target.
5
+
6
+ ## Recommended Archival Setup
7
+
8
+ Create a sibling worktree from the frozen legacy branch:
9
+
10
+ ```bash
11
+ git worktree add ../mixsqpx-legacy legacy-cpp-final
12
+ ```
13
+
14
+ Set up a separate environment in that worktree and build/install the legacy
15
+ package there. Keep that environment separate from the Rust-first `uv` workflow
16
+ used on this branch.
17
+
18
+ ## Benchmarking
19
+
20
+ On the Rust-first branch:
21
+
22
+ ```bash
23
+ uv run --group test python scripts/benchmark_mixsolve.py
24
+ ```
25
+
26
+ If you want to compare against the legacy backend, do that from the archival
27
+ checkout using its own build and benchmark tooling. The merged branch should not
28
+ carry the Armadillo/OpenBLAS/CMake dependency stack as part of its default
29
+ install path.
30
+
31
+ ## Exact Legacy Reference Tests
32
+
33
+ The first-step exact legacy parity test is disabled by default on the Rust-first
34
+ branch. Re-enable it only in an environment that actually has the legacy C++
35
+ backend available:
36
+
37
+ ```bash
38
+ MIXSQPX_BACKEND=cpp \
39
+ MIXSQPX_RUN_LEGACY_CPP_REFERENCE_TESTS=1 \
40
+ uv run --group test pytest tests/test_first_sqp_reference.py
41
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jackson Bunting, Paul Diegert, and Arnaud Maurel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.4
2
+ Name: mixsqpx
3
+ Version: 0.2.0a1
4
+ Summary: JAX interface to the Rust implementation of the mixSQP algorithm
5
+ Author-email: Paul Diegert <pdiegert@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Jackson Bunting, Paul Diegert, and Arnaud Maurel
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ License-File: LICENSE
28
+ Requires-Python: <3.14,>=3.12
29
+ Requires-Dist: jax<=0.9.2,>=0.9.2
30
+ Requires-Dist: jaxlib<=0.9.2,>=0.9.2
31
+ Description-Content-Type: text/markdown
32
+
33
+ # MIXSQPX: A JAX-compatible interface to the mixSQP algorithm
34
+
35
+ This package provides a `jax`-compatible interface to the mixSQP algorithm described in,
36
+
37
+ > Kim, Y., Carbonetto, P., Stephens, M., & Anitescu, M. (2020). A Fast Algorithm for Maximum Likelihood Estimation of Mixture Proportions Using Sequential Quadratic Programming. Journal of Computational and Graphical Statistics, 29(2), 261–273.
38
+
39
+ The implementation of the mixSQP algorithm in this package was ported from the [R
40
+ implementation](https://github.com/stephenslab/mixSQP) by
41
+ the authors of this paper which uses the Rarmadillo package as an interface to the
42
+ Armadillo C++ library.
43
+
44
+ This package provides a function `mixsolve`, which can be transformed with
45
+ [JAX](https://github.com/jax-ml/jax) transformations such as `jit`.
46
+
47
+ ## Installation
48
+
49
+ This branch uses [`uv`](https://docs.astral.sh/uv/) as the project frontend and
50
+ `hatchling` as the build backend. Building the package requires a working Rust
51
+ toolchain because the default backend is a bundled Rust shared library.
52
+
53
+ ### Local Development
54
+
55
+ ```bash
56
+ # Install uv if you don't have it already.
57
+ curl -LsSf https://astral.sh/uv/install.sh | sh
58
+
59
+ # Clone the repository.
60
+ git clone https://github.com/pdiegert/mixsqpx.git
61
+ cd mixsqpx
62
+
63
+ # Install Rust if you don't have it already.
64
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
65
+
66
+ # Sync the development environment and install the package editable.
67
+ uv sync --group test --group examples
68
+ ```
69
+
70
+ `uv sync` builds the Rust library through the Hatch build hook and installs the
71
+ package in editable mode.
72
+
73
+ ### Verification
74
+
75
+ Run the test suite to verify your installation:
76
+
77
+ ```bash
78
+ uv run --group test pytest tests
79
+ ```
80
+
81
+ Build a wheel locally with:
82
+
83
+ ```bash
84
+ uv build
85
+ ```
86
+
87
+ Benchmark the Rust backend explicitly with:
88
+
89
+ ```bash
90
+ uv run --group test python scripts/benchmark_mixsolve.py
91
+ ```
92
+
93
+ ## Legacy C++ Reference Path
94
+
95
+ This branch is intended to merge as a Rust-first package. The legacy
96
+ C++/Armadillo backend is kept only as an archival benchmark/reference path.
97
+
98
+ - Keep the legacy implementation in a separate checkout or worktree.
99
+ - Use a separate environment for the legacy package.
100
+ - Do not treat the legacy C++ build as part of the default developer install on
101
+ this branch.
102
+
103
+ A recommended setup is:
104
+
105
+ ```bash
106
+ git worktree add ../mixsqpx-legacy legacy-cpp-final
107
+ ```
108
+
109
+ Additional details for benchmarking and the opt-in exact legacy reference test
110
+ are documented in [LEGACY_CPP_REFERENCE.md](LEGACY_CPP_REFERENCE.md).
111
+
112
+ ## License
113
+
114
+ This project is licensed under the MIT License - see the [LICENCE](LICENCE) file for details.
115
+
116
+ ## Acknowledgements
117
+
118
+ This project includes code adapted from the [mixSQP](https://github.com/stephenslab/mixSQP) project, which is available under the MIT license. We thank the original authors (Youngseok Kim, Peter Carbonetto,
119
+ Matthew Stephens and Mihai Anitescu) for their work.
@@ -0,0 +1,87 @@
1
+ # MIXSQPX: A JAX-compatible interface to the mixSQP algorithm
2
+
3
+ This package provides a `jax`-compatible interface to the mixSQP algorithm described in,
4
+
5
+ > Kim, Y., Carbonetto, P., Stephens, M., & Anitescu, M. (2020). A Fast Algorithm for Maximum Likelihood Estimation of Mixture Proportions Using Sequential Quadratic Programming. Journal of Computational and Graphical Statistics, 29(2), 261–273.
6
+
7
+ The implementation of the mixSQP algorithm in this package was ported from the [R
8
+ implementation](https://github.com/stephenslab/mixSQP) by
9
+ the authors of this paper which uses the Rarmadillo package as an interface to the
10
+ Armadillo C++ library.
11
+
12
+ This package provides a function `mixsolve`, which can be transformed with
13
+ [JAX](https://github.com/jax-ml/jax) transformations such as `jit`.
14
+
15
+ ## Installation
16
+
17
+ This branch uses [`uv`](https://docs.astral.sh/uv/) as the project frontend and
18
+ `hatchling` as the build backend. Building the package requires a working Rust
19
+ toolchain because the default backend is a bundled Rust shared library.
20
+
21
+ ### Local Development
22
+
23
+ ```bash
24
+ # Install uv if you don't have it already.
25
+ curl -LsSf https://astral.sh/uv/install.sh | sh
26
+
27
+ # Clone the repository.
28
+ git clone https://github.com/pdiegert/mixsqpx.git
29
+ cd mixsqpx
30
+
31
+ # Install Rust if you don't have it already.
32
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
33
+
34
+ # Sync the development environment and install the package editable.
35
+ uv sync --group test --group examples
36
+ ```
37
+
38
+ `uv sync` builds the Rust library through the Hatch build hook and installs the
39
+ package in editable mode.
40
+
41
+ ### Verification
42
+
43
+ Run the test suite to verify your installation:
44
+
45
+ ```bash
46
+ uv run --group test pytest tests
47
+ ```
48
+
49
+ Build a wheel locally with:
50
+
51
+ ```bash
52
+ uv build
53
+ ```
54
+
55
+ Benchmark the Rust backend explicitly with:
56
+
57
+ ```bash
58
+ uv run --group test python scripts/benchmark_mixsolve.py
59
+ ```
60
+
61
+ ## Legacy C++ Reference Path
62
+
63
+ This branch is intended to merge as a Rust-first package. The legacy
64
+ C++/Armadillo backend is kept only as an archival benchmark/reference path.
65
+
66
+ - Keep the legacy implementation in a separate checkout or worktree.
67
+ - Use a separate environment for the legacy package.
68
+ - Do not treat the legacy C++ build as part of the default developer install on
69
+ this branch.
70
+
71
+ A recommended setup is:
72
+
73
+ ```bash
74
+ git worktree add ../mixsqpx-legacy legacy-cpp-final
75
+ ```
76
+
77
+ Additional details for benchmarking and the opt-in exact legacy reference test
78
+ are documented in [LEGACY_CPP_REFERENCE.md](LEGACY_CPP_REFERENCE.md).
79
+
80
+ ## License
81
+
82
+ This project is licensed under the MIT License - see the [LICENCE](LICENCE) file for details.
83
+
84
+ ## Acknowledgements
85
+
86
+ This project includes code adapted from the [mixSQP](https://github.com/stephenslab/mixSQP) project, which is available under the MIT license. We thank the original authors (Youngseok Kim, Peter Carbonetto,
87
+ Matthew Stephens and Mihai Anitescu) for their work.
@@ -0,0 +1,256 @@
1
+ # Rust Port Plan
2
+
3
+ ## Goal
4
+
5
+ Replace the current C++/Armadillo/OpenBLAS/CMake extension with a Rust implementation of the solver and a simpler build path, while keeping the Python/JAX API stable.
6
+
7
+ The target end state is:
8
+
9
+ - numeric core implemented in Rust
10
+ - dense linear algebra implemented with a Rust-native stack
11
+ - Python package built without CMake, pybind11, Armadillo, or OpenBLAS
12
+ - JAX custom call registered from Python by loading a Rust-built shared library
13
+
14
+ ## Current Status
15
+
16
+ ### Milestone Status
17
+
18
+ - Milestone 0: complete
19
+ - Milestone 1: complete
20
+ - Milestone 2: complete
21
+ - Milestone 3: complete
22
+ - Milestone 4: accepted as complete
23
+ - Milestone 5: mostly complete
24
+ - Milestone 6: partially started
25
+
26
+ ### Current Technical Direction
27
+
28
+ The current implementation differs slightly from the original proposal:
29
+
30
+ - Linear algebra currently uses `nalgebra` and this is the accepted backend for now
31
+ - Shared library build is Cargo `cdylib`
32
+ - Python integration loads the Rust shared library with `ctypes`
33
+ - JAX FFI registration is done from Python through `jax.ffi.register_ffi_target`
34
+ - Near-term packaging direction is `uv` as the frontend and `hatchling` with a cargo build hook as the backend
35
+ - Long-term packaging direction is to consider a cleaner `maturin`-based layout after a deliberate binding refactor
36
+
37
+ The next packaging milestone can still replace the legacy C++ build, but the present Rust port is no longer just a scaffold: it is an end-to-end backend. A short `faer` spike was completed and retained on the `faer-spike` branch for reference, but `nalgebra` remains the preferred backend for the current implementation.
38
+
39
+ ## Implemented Work
40
+
41
+ ### Milestone 0: Tooling and Repository Setup
42
+
43
+ Completed:
44
+
45
+ - Rust toolchain installed and verified locally
46
+ - dedicated `rust-port` branch and worktree created
47
+ - Rust crate added under `rust/mixsqpx_rust`
48
+ - Cargo build produces a shared library artifact for the JAX FFI path
49
+
50
+ ### Milestone 1: Characterize Current Behavior
51
+
52
+ Completed:
53
+
54
+ - characterization fixtures added for representative solver runs
55
+ - first-SQP-step reference fixture added for the tuned Gaussian case
56
+ - benchmark harness added for repeatable timing runs
57
+ - parity tests added around solver outputs instead of relying only on smoke tests
58
+
59
+ Artifacts:
60
+
61
+ - `tests/fixtures/characterization/*.npz`
62
+ - `tests/fixtures/reference/gaussian_tuned_first_sqp_step.npz`
63
+ - `tests/test_characterization.py`
64
+ - `tests/test_first_sqp_reference.py`
65
+ - `scripts/benchmark_mixsolve.py`
66
+
67
+ ### Milestone 2: Rust Scaffolding and FFI Path
68
+
69
+ Completed:
70
+
71
+ - Rust `cdylib` exposes a JAX-compatible entry point
72
+ - Python can load and register the Rust shared library
73
+ - Rust backend can be selected from Python/JAX without changing the public solver interface
74
+ - notebook and test workflows can exercise the Rust backend directly, while legacy C++ comparison remains archival/optional
75
+
76
+ Artifacts:
77
+
78
+ - `rust/mixsqpx_rust/src/lib.rs`
79
+ - `src/mixsqpx/rust_backend.py`
80
+ - `tests/test_rust_backend.py`
81
+
82
+ ### Milestone 3: Port Low-Risk Numeric Pieces
83
+
84
+ Completed:
85
+
86
+ - helper numeric routines ported to Rust
87
+ - objective evaluation ported to Rust
88
+ - EM update path ported to Rust
89
+
90
+ Artifacts:
91
+
92
+ - `rust/mixsqpx_rust/src/numeric.rs`
93
+ - `rust/mixsqpx_rust/src/objective.rs`
94
+ - `rust/mixsqpx_rust/src/mixem.rs`
95
+
96
+ ### Milestone 4: Port Core SQP Solver
97
+
98
+ Completed and accepted.
99
+
100
+ Completed work:
101
+
102
+ - exact SQP solver path ported to Rust
103
+ - truncated-SVD solver path ported to Rust
104
+ - active-set QP routine ported to Rust
105
+ - backtracking line search ported to Rust
106
+ - existing Python/JAX API controls wired through to the Rust solver options
107
+
108
+ Artifacts:
109
+
110
+ - `rust/mixsqpx_rust/src/solver.rs`
111
+ - `rust/mixsqpx_rust/src/lib.rs`
112
+
113
+ ## Milestone 4 Outcome
114
+
115
+ ### What Matched Well
116
+
117
+ The Rust backend matches the legacy implementation closely in the easier and default regimes:
118
+
119
+ - default characterization cases pass the current parity thresholds
120
+ - first-SQP reference diagnostics show that the SQP point itself matches extremely closely before the active-set update
121
+ - output plots for the Gaussian example are visually indistinguishable between C++ and Rust
122
+
123
+ ### What Did Not Match Exactly
124
+
125
+ We were not able to achieve full numerical equality with the C++/Armadillo implementation in the tuned Gaussian characterization case.
126
+
127
+ What the diagnostics showed:
128
+
129
+ - EM behavior matched
130
+ - the SQP point derivatives matched closely
131
+ - the first meaningful drift appeared inside the active-set / linear-solve numerics
132
+ - later SQP iterations amplified that drift in the tuned case
133
+
134
+ The remaining differences appear to come from solver numerics rather than wiring, preprocessing, or objective/gradient construction.
135
+
136
+ ### Acceptance Decision
137
+
138
+ We are accepting the current Rust implementation for Milestone 4.
139
+
140
+ Reasoning:
141
+
142
+ - the Rust backend produces equal or better practical results for the problem configurations we care about right now
143
+ - the outputs are visually indistinguishable in the notebook comparisons for the main Gaussian example
144
+ - the tuned-case differences are numerically real, but they are not currently considered product-blocking
145
+ - further work to force Armadillo-level numerical equality does not appear justified relative to the cost
146
+
147
+ This means Milestone 4 is considered complete under an acceptance criterion of practical parity rather than bitwise or solver-path equality.
148
+
149
+ ## Testing and Benchmark Results
150
+
151
+ ### Functional Testing
152
+
153
+ Completed testing includes:
154
+
155
+ - characterization tests against saved C++ outputs
156
+ - first-SQP diagnostic tests on the tuned Gaussian case
157
+ - Rust backend smoke tests through JAX FFI
158
+ - notebook comparisons for both the default Gaussian example and the tuned Gaussian fixture
159
+
160
+ ### Performance Results
161
+
162
+ Initial notebook timing on a Rust debug build was misleadingly poor.
163
+ After switching the notebooks to use Rust release builds, the Rust backend performed slightly better than the C++ backend in current local runs.
164
+
165
+ Observed notebook result for the Gaussian example:
166
+
167
+ - Rust speedup of roughly `1.2x` to `1.5x` versus the C++ backend, depending on the run
168
+
169
+ Current conclusion:
170
+
171
+ - the Rust implementation is at least competitive for the tested `4000 x 101` workload
172
+ - no immediate performance blocker is preventing the packaging switch
173
+ - more systematic benchmarking is still appropriate in Milestone 6
174
+
175
+ ### `faer` Spike Result
176
+
177
+ A short follow-up spike tested `faer` as a drop-in replacement for the current `nalgebra` touchpoints.
178
+
179
+ Outcome:
180
+
181
+ - the spike branch is kept as `faer-spike` for reference
182
+ - the default Gaussian case remained strong and was about `1.5x` faster than the C++ backend in the quick notebook-style comparison
183
+ - the tuned Gaussian case regressed badly, running at about `0.2x` of the C++ backend in the same quick comparison
184
+ - the spike also introduced a small Hessian-tolerance regression in one strict first-SQP test
185
+
186
+ Decision:
187
+
188
+ - continue with `nalgebra` for now
189
+ - treat `faer` as an optional future optimization experiment rather than the planned default backend
190
+
191
+ ## Remaining Work
192
+
193
+ ### Milestone 5: Switch Packaging and Archive the C++ Build
194
+
195
+ Implemented:
196
+
197
+ - replaced the primary `scikit-build-core`/CMake native build path with `hatchling`
198
+ - switched developer/frontend workflows from `pixi` to `uv`
199
+ - added a cargo-driven Hatch build hook that builds the Rust `cdylib` and bundles it into the wheel
200
+ - updated package metadata and install instructions around the Rust backend
201
+ - removed Armadillo/OpenBLAS logic from the default package build path
202
+ - updated the primary wheel-building workflow to use `uv build` and a Rust toolchain directly
203
+ - created a frozen archival reference branch locally as `legacy-cpp-final`
204
+ - documented the archival benchmark/reference workflow in `LEGACY_CPP_REFERENCE.md`
205
+ - kept the characterization fixtures in-repo as the lightweight always-available reference
206
+ - made the exact first-step legacy parity test opt-in rather than part of the default Rust test path
207
+
208
+ Remaining tasks:
209
+
210
+ - verify the updated CI workflow in GitHub rather than only locally
211
+ - finish cleaning up notebook/dev tooling that still assumes older local environment layouts
212
+ - decide whether to keep any additional legacy-comparison helpers in-tree beyond the documented archival path
213
+
214
+ Design intent:
215
+
216
+ - the branch that eventually merges should be Rust-only by default
217
+ - the legacy C++ backend should be preserved for benchmarking and archaeology, but not carried as an always-supported mainline dependency stack
218
+ - benchmarking against the C++ implementation should remain possible without forcing Armadillo/OpenBLAS/CMake into the everyday development and install path
219
+ - the immediate packaging cutover should minimize binding churn by keeping the current `ctypes` + shared-library loading model
220
+ - a later refactor can revisit the Python/Rust boundary and move toward `maturin` if the team wants a more idiomatic long-term Rust packaging model
221
+
222
+ Exit criteria:
223
+
224
+ - clean install works without system BLAS or Armadillo
225
+ - local package build path is materially simpler than the current one
226
+ - default package/backend path uses Rust rather than the legacy C++ extension
227
+ - a documented archival path exists for reproducing legacy C++ benchmarks when needed
228
+
229
+ ### Milestone 6: Performance Validation and Cleanup
230
+
231
+ Remaining tasks:
232
+
233
+ - run more systematic benchmarks across representative matrix sizes
234
+ - profile allocation hotspots in the Rust SQP path
235
+ - keep `nalgebra` as the current backend unless future profiling justifies another spike
236
+ - document the accepted numerical differences from the legacy backend
237
+ - remove dead compatibility code once the packaging transition is complete
238
+
239
+ Exit criteria:
240
+
241
+ - clear documented performance story for target workloads
242
+ - accepted numerical behavior documented
243
+ - remaining legacy-only code removed or explicitly retained as transitional support
244
+
245
+ ## Risks and Open Questions
246
+
247
+ - The main unresolved product question is packaging, not solver feasibility.
248
+ - Larger dense problems may still reveal performance or numeric tradeoffs that do not show up in the `4000 x 101` case.
249
+ - A `faer` spike was completed and archived on its own branch; it did not beat the current `nalgebra` implementation consistently enough to justify switching now.
250
+
251
+ ## Immediate Next Steps
252
+
253
+ 1. Update packaging so the Rust backend becomes the primary build path.
254
+ 2. Preserve the current parity fixtures and notebooks as acceptance evidence for the Rust backend.
255
+ 3. Expand benchmarks across a few larger matrix sizes before final cleanup.
256
+ 4. Implement the `uv` + `hatchling` + cargo-hook packaging path first, and treat a future `maturin` migration as a separate refactor once the Rust-only package path is stable.