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.
- mixsqpx-0.2.0a1/.gitignore +164 -0
- mixsqpx-0.2.0a1/Cargo.toml +3 -0
- mixsqpx-0.2.0a1/LEGACY_CPP_REFERENCE.md +41 -0
- mixsqpx-0.2.0a1/LICENSE +21 -0
- mixsqpx-0.2.0a1/PKG-INFO +119 -0
- mixsqpx-0.2.0a1/README.md +87 -0
- mixsqpx-0.2.0a1/RUST_PORT_PLAN.md +256 -0
- mixsqpx-0.2.0a1/hatch_build.py +45 -0
- mixsqpx-0.2.0a1/pyproject.toml +52 -0
- mixsqpx-0.2.0a1/rust/mixsqpx_rust/Cargo.toml +12 -0
- mixsqpx-0.2.0a1/rust/mixsqpx_rust/src/lib.rs +426 -0
- mixsqpx-0.2.0a1/rust/mixsqpx_rust/src/mixem.rs +139 -0
- mixsqpx-0.2.0a1/rust/mixsqpx_rust/src/numeric.rs +164 -0
- mixsqpx-0.2.0a1/rust/mixsqpx_rust/src/objective.rs +100 -0
- mixsqpx-0.2.0a1/rust/mixsqpx_rust/src/solver.rs +796 -0
- mixsqpx-0.2.0a1/src/mixsqpx/__init__.py +22 -0
- mixsqpx-0.2.0a1/src/mixsqpx/_version.py +21 -0
- mixsqpx-0.2.0a1/src/mixsqpx/mixsqp_jax.py +189 -0
- mixsqpx-0.2.0a1/src/mixsqpx/rust_backend.py +75 -0
- mixsqpx-0.2.0a1/tests/fixtures/characterization/gaussian_default.npz +0 -0
- mixsqpx-0.2.0a1/tests/fixtures/characterization/gaussian_tuned.npz +0 -0
- mixsqpx-0.2.0a1/tests/fixtures/characterization/small_manual_default.npz +0 -0
- mixsqpx-0.2.0a1/tests/fixtures/reference/gaussian_tuned_first_sqp_step.npz +0 -0
|
@@ -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,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
|
+
```
|
mixsqpx-0.2.0a1/LICENSE
ADDED
|
@@ -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.
|
mixsqpx-0.2.0a1/PKG-INFO
ADDED
|
@@ -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.
|