gpugem 0.1.2__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.
- gpugem-0.1.2/LICENSE +21 -0
- gpugem-0.1.2/PKG-INFO +328 -0
- gpugem-0.1.2/README.md +292 -0
- gpugem-0.1.2/gpugem/__init__.py +32 -0
- gpugem-0.1.2/gpugem/_defaults.py +80 -0
- gpugem-0.1.2/gpugem/_deps.py +271 -0
- gpugem-0.1.2/gpugem/lifting.py +320 -0
- gpugem-0.1.2/gpugem/loaders.py +205 -0
- gpugem-0.1.2/gpugem/result.py +50 -0
- gpugem-0.1.2/gpugem/scaling.py +257 -0
- gpugem-0.1.2/gpugem/solve_cobra.py +29 -0
- gpugem-0.1.2/gpugem/solver.py +410 -0
- gpugem-0.1.2/gpugem.egg-info/PKG-INFO +328 -0
- gpugem-0.1.2/gpugem.egg-info/SOURCES.txt +34 -0
- gpugem-0.1.2/gpugem.egg-info/dependency_links.txt +1 -0
- gpugem-0.1.2/gpugem.egg-info/requires.txt +18 -0
- gpugem-0.1.2/gpugem.egg-info/top_level.txt +1 -0
- gpugem-0.1.2/pyproject.toml +72 -0
- gpugem-0.1.2/setup.cfg +4 -0
- gpugem-0.1.2/tests/test_block_residuals.py +171 -0
- gpugem-0.1.2/tests/test_cuopt_tuning.py +277 -0
- gpugem-0.1.2/tests/test_dependency_errors.py +481 -0
- gpugem-0.1.2/tests/test_dependency_inventory.py +295 -0
- gpugem-0.1.2/tests/test_gurobi_default_benchmark.py +236 -0
- gpugem-0.1.2/tests/test_lifting.py +279 -0
- gpugem-0.1.2/tests/test_matlab_python_lifting_comparison.py +112 -0
- gpugem-0.1.2/tests/test_model_lifting_validation.py +65 -0
- gpugem-0.1.2/tests/test_objective_panel.py +249 -0
- gpugem-0.1.2/tests/test_ordinal_ramp.py +32 -0
- gpugem-0.1.2/tests/test_residual_tradeoff.py +118 -0
- gpugem-0.1.2/tests/test_s85_objectives.py +123 -0
- gpugem-0.1.2/tests/test_scaling.py +66 -0
- gpugem-0.1.2/tests/test_solver.py +87 -0
- gpugem-0.1.2/tests/test_solver_mode_variants.py +133 -0
- gpugem-0.1.2/tests/test_version_lifting_comparison.py +143 -0
- gpugem-0.1.2/tests/test_violation_histogram.py +176 -0
gpugem-0.1.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Digital Metabolic Twin Centre
|
|
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.
|
gpugem-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gpugem
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: GPU-accelerated Flux Balance Analysis for genome-scale metabolic models
|
|
5
|
+
Author: Digital Metabolic Twin Centre
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Digital-Metabolic-Twin-Centre/gpuGEM
|
|
8
|
+
Project-URL: Repository, https://github.com/Digital-Metabolic-Twin-Centre/gpuGEM
|
|
9
|
+
Project-URL: Issues, https://github.com/Digital-Metabolic-Twin-Centre/gpuGEM/issues
|
|
10
|
+
Keywords: flux balance analysis,FBA,metabolic modelling,GPU,PDLP,cuOpt,COBRA,systems biology
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: numpy>=1.24
|
|
21
|
+
Requires-Dist: scipy>=1.10
|
|
22
|
+
Requires-Dist: cuopt-cu12>=26.6.0
|
|
23
|
+
Requires-Dist: cobra>=0.29
|
|
24
|
+
Provides-Extra: gurobi
|
|
25
|
+
Requires-Dist: gurobipy>=11; extra == "gurobi"
|
|
26
|
+
Provides-Extra: application
|
|
27
|
+
Requires-Dist: pandas>=2.0; extra == "application"
|
|
28
|
+
Requires-Dist: gurobipy>=11; extra == "application"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff; extra == "dev"
|
|
33
|
+
Requires-Dist: pandas>=2.0; extra == "dev"
|
|
34
|
+
Requires-Dist: matplotlib; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
<div align="center">
|
|
38
|
+
|
|
39
|
+
# gpuGEM
|
|
40
|
+
|
|
41
|
+
[](https://github.com/Digital-Metabolic-Twin-Centre/gpuGEM/actions/workflows/ci.yml)
|
|
42
|
+
[](https://www.python.org/)
|
|
43
|
+
[](https://developer.nvidia.com/cuda-toolkit)
|
|
44
|
+
[](https://developer.nvidia.com/cuopt)
|
|
45
|
+
[](LICENSE)
|
|
46
|
+
[]()
|
|
47
|
+
|
|
48
|
+
GPU-accelerated Flux Balance Analysis (FBA) for genome-scale metabolic models.
|
|
49
|
+
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
gpuGEM wraps [NVIDIA cuOpt](https://developer.nvidia.com/cuopt) PDLP solver with validated default settings derived from systematic benchmarking on whole-body metabolic models, including the [Harvey](https://www.vmh.life/) model and personalised microbiome whole-body models. It provides a minimal, COBRA-compatible API so existing modelling workflows can switch to GPU solving with one line of code.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Why gpuGEM?
|
|
57
|
+
|
|
58
|
+
GPU first-order LP solvers (PDLP) offer substantial speedups on genome-scale FBA, but getting correct results requires non-obvious configuration. Out-of-the-box cuOpt settings produce large constraint violations or silently return wrong solutions on whole-body models. gpuGEM ships with settings that have been validated for correctness:
|
|
59
|
+
|
|
60
|
+
| Model | Reactions | Constraints | Time | Max stoich. residual |
|
|
61
|
+
|---|---|---|---|---|
|
|
62
|
+
| Harvey whole-body model | 81K | 160K | **0.76 s** | 1.8e-5 |
|
|
63
|
+
| Microbiome whole-body model | 789K | 1.66M | **27 s** | 8.4e-5 |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install gpugem
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This installs the full tool: numpy, scipy, NVIDIA cuOpt (the GPU solver) and COBRApy. gpuGEM is a
|
|
74
|
+
GPU solver, so solving needs an NVIDIA GPU with CUDA 12 and Python 3.11 or later. cuOpt is a large
|
|
75
|
+
download (about 1.4 GB, mostly `libcudf-cu12` and `libcuopt-cu12`). The `cuopt-cu12` entry on PyPI
|
|
76
|
+
is a stub that fetches the real wheels from NVIDIA's index. Behind a proxy that blocks
|
|
77
|
+
`pypi.nvidia.com` it can fail with a confusing build error; if that happens, name the index
|
|
78
|
+
explicitly:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install gpugem --extra-index-url https://pypi.nvidia.com
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`import gpugem` works without a usable GPU; the GPU is checked when you call `solve`, which raises a
|
|
85
|
+
`DependencyError` explaining what is missing.
|
|
86
|
+
|
|
87
|
+
Optional extras:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pip install "gpugem[gurobi]" # the Gurobi CPU baseline (needs a licence)
|
|
91
|
+
pip install "gpugem[application]" # the candidate-gene application, see application/
|
|
92
|
+
pip install "gpugem[dev]" # tests and linting
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Dependencies
|
|
96
|
+
|
|
97
|
+
| Package / tool | Role | Needed for | Install |
|
|
98
|
+
|---|---|---|---|
|
|
99
|
+
| `numpy`, `scipy` | required | every function | `pip install "numpy>=1.24"`, `pip install "scipy>=1.10"` |
|
|
100
|
+
| `cuopt` | required | the GPU solver; checked when you solve, not at `import gpugem` | `pip install "cuopt-cu12>=26.6.0" --extra-index-url https://pypi.nvidia.com` (other CUDA versions: see the [cuOpt install guide](https://docs.nvidia.com/cuopt/user-guide/latest/introduction.html)) |
|
|
101
|
+
| `cobra` | required | `solve_cobra`, `loaders.from_cobra` | `pip install "cobra>=0.29"` |
|
|
102
|
+
| `gurobipy` | benchmarks, application | the Gurobi baseline (needs a licence); also the minimum-norm QP in `application/` | `pip install "gpugem[gurobi]"` |
|
|
103
|
+
| `pandas` | application | result tables in `application/` | `pip install "gpugem[application]"` |
|
|
104
|
+
| `highspy` | benchmarks | the HiGHS CPU baseline | `pip install highspy` |
|
|
105
|
+
| `pandas`, `matplotlib` | benchmarks | result tables and figures | `pip install pandas`, `pip install matplotlib` |
|
|
106
|
+
| `pytest` | development | the test suite | `pip install -e ".[dev]"` |
|
|
107
|
+
| `nvidia-smi` | tool | GPU name in benchmark metadata; GPU diagnosis | install the NVIDIA driver (https://www.nvidia.com/drivers); nvidia-smi ships with it |
|
|
108
|
+
|
|
109
|
+
### Troubleshooting
|
|
110
|
+
|
|
111
|
+
A missing or unusable dependency raises `gpugem.DependencyError` (benchmark and example scripts
|
|
112
|
+
print the same message; benchmark scripts exit with status 3). The message always says what is wrong, why gpuGEM
|
|
113
|
+
needs it, and how to fix it. The original exception is kept as `__cause__`. `err.kind` is one of:
|
|
114
|
+
|
|
115
|
+
| `kind` | Meaning | What to do |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `not_installed` | the package is absent | run the `pip install` command in the message |
|
|
118
|
+
| `broken` | installed but fails to import, or a model file cannot be read | reinstall; check that Python, the CUDA runtime and the driver match the build. For `.mat` files, re-save with `save(file, 'model', '-v7')` |
|
|
119
|
+
| `version` | installed version below the minimum | run the `pip install --upgrade` command in the message |
|
|
120
|
+
| `no_gpu` | no usable NVIDIA GPU or driver is detected | run `nvidia-smi`; install or update the driver; in a container, start it with GPU access |
|
|
121
|
+
| `license` | Gurobi is installed but has no valid licence for the model | obtain a licence and set `GRB_LICENSE_FILE`; check with `grbprobe` |
|
|
122
|
+
| `tool_missing` | an executable is not on `PATH` | install it or add it to `PATH` |
|
|
123
|
+
| `network` | a model download failed | download the file manually into `benchmarks/model_cache/` |
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
try:
|
|
127
|
+
result = gpugem.solve_cobra(model)
|
|
128
|
+
except gpugem.DependencyError as err:
|
|
129
|
+
print(err.kind, err.dependency, err.remedy)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Quick start
|
|
135
|
+
|
|
136
|
+
**From a COBRApy model:**
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import cobra
|
|
140
|
+
import gpugem
|
|
141
|
+
|
|
142
|
+
model = cobra.io.read_sbml_model("e_coli_core.xml")
|
|
143
|
+
result = gpugem.solve_cobra(model)
|
|
144
|
+
|
|
145
|
+
print(result) # FBAResult(status='Optimal', objective=0.8739, time=0.42s)
|
|
146
|
+
print(result.objective)
|
|
147
|
+
print(result.fluxes) # numpy array, same order as model.reactions
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**From a COBRA `.mat` file (MATLAB format):**
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import gpugem
|
|
154
|
+
|
|
155
|
+
arrays = gpugem.loaders.from_mat("Harvey_1_03c_reduced.mat")
|
|
156
|
+
result = gpugem.solve(**arrays)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**From raw matrices (scipy sparse):**
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import gpugem
|
|
163
|
+
|
|
164
|
+
result = gpugem.solve(
|
|
165
|
+
S=S, # stoichiometric matrix (sparse)
|
|
166
|
+
b=b, # RHS equalities
|
|
167
|
+
lb=lb, ub=ub, # variable bounds
|
|
168
|
+
c=c, # objective coefficients
|
|
169
|
+
maximize=True,
|
|
170
|
+
)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Whole-body models with coupling constraints:**
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
result = gpugem.solve(S=S, b=b, lb=lb, ub=ub, c=c,
|
|
177
|
+
C=C, d_lb=d_lb, d_ub=d_ub,
|
|
178
|
+
maximize=True)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Default settings
|
|
184
|
+
|
|
185
|
+
gpuGEM automatically selects solver settings based on model size:
|
|
186
|
+
|
|
187
|
+
**Small models (≤ 100K reactions) — e.g. Harvey whole-body model:**
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
pdlp_precision = 1 # mixed FP32/FP64 — double precision diverges on these models
|
|
191
|
+
absolute_primal_tolerance = 1e-8 # tight tolerance: 0 stoich violations at 1e-6 max residual
|
|
192
|
+
relative_primal_tolerance = 1e-8
|
|
193
|
+
absolute_dual_tolerance = 1e-8
|
|
194
|
+
relative_dual_tolerance = 1e-8
|
|
195
|
+
per_constraint_residual = 1 # max-norm convergence check → better accuracy
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Large models (> 100K reactions) — e.g. personalised microbiome whole-body models:**
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
presolve = 1 # PaPILO — required; default PSLP falsely reports Infeasible
|
|
202
|
+
per_constraint_residual = 1 # max-norm convergence → 44% fewer violations vs default
|
|
203
|
+
absolute_primal_tolerance = 1e-4 # cuOpt's internal default tolerance
|
|
204
|
+
relative_primal_tolerance = 1e-4
|
|
205
|
+
absolute_dual_tolerance = 1e-4
|
|
206
|
+
relative_dual_tolerance = 1e-4
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Any cuOpt parameter can be overridden:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
# Use Stable1 PDLP mode for maximum accuracy (slower)
|
|
213
|
+
result = gpugem.solve(**arrays, pdlp_solver_mode=0, time_limit=300)
|
|
214
|
+
|
|
215
|
+
# Disable presolve explicitly
|
|
216
|
+
result = gpugem.solve(**arrays, presolve=0)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Stateful solver (repeated solves)
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
solver = gpugem.FBASolver(S=S, b=b, lb=lb, ub=ub, c=c, maximize=True)
|
|
225
|
+
|
|
226
|
+
for patient_c in patient_objectives:
|
|
227
|
+
result = solver.solve(c=patient_c)
|
|
228
|
+
print(result.objective)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Result object
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
result.status # 'Optimal', 'TimeLimit', 'Infeasible', ...
|
|
237
|
+
result.objective # float
|
|
238
|
+
result.fluxes # np.ndarray (n_vars,)
|
|
239
|
+
result.wall_time_s # float
|
|
240
|
+
result.solver_settings # dict of applied cuOpt parameters
|
|
241
|
+
result.feasibility # dict: stoich_max_residual, stoich_rows_violated_1e6, ...
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Known limitations
|
|
247
|
+
|
|
248
|
+
- **Lifted, extreme-scale (`S84`/`S85`-class) models are not reliably solvable by every Gurobi
|
|
249
|
+
entry point.** `benchmarks/run_matlab_python_lifting_comparison.py` found that Gurobi solves the
|
|
250
|
+
lifted `S84`/`S85` systems to `Optimal` via COBRA Toolbox's `solveCobraLP` (MATLAB) but reports
|
|
251
|
+
`Infeasible` for the *structurally identical* system (confirmed: identical auxiliary-variable
|
|
252
|
+
and lifted-row counts) via a direct `gurobipy` call (Python). Checked and ruled out as causes:
|
|
253
|
+
a Gurobi solver-settings mismatch (`Method`, `TimeLimit`, `FeasibilityTol`/`OptimalityTol` all
|
|
254
|
+
confirmed identical), a Gurobi *version* mismatch (`pip install gurobipy` initially resolved a
|
|
255
|
+
different major version, `13.0.3`, than MATLAB's licensed `11.0.3`; re-run with the versions
|
|
256
|
+
pinned to match — `Infeasible` persisted), and a borderline numerical-tolerance flip (persisted
|
|
257
|
+
after relaxing `FeasibilityTol` 100x). This reproduces, under a second independent solver
|
|
258
|
+
pathway, the same `S84`/`S85` scale boundary already found for lifted cuOpt solves (see "Version
|
|
259
|
+
x lifting runtime comparison" above) — lifting's mathematical correctness is not in question
|
|
260
|
+
(structural fidelity is exact), but its practical solvability at this scale is
|
|
261
|
+
entry-point-dependent, for a reason not yet isolated. See
|
|
262
|
+
`specs/015-matlab-python-lifting-comparison/`.
|
|
263
|
+
|
|
264
|
+
- **GPU/driver-absent detection is a heuristic, checked on one kind of machine.** gpuGEM reports
|
|
265
|
+
`DependencyError(kind="no_gpu")` only when cuOpt raises *and* there is positive evidence of a GPU
|
|
266
|
+
problem (`nvidia-smi` fails or lists no GPU, or it is absent and no `/dev/nvidia*` device exists).
|
|
267
|
+
Observed with cuOpt 26.8.0 on a machine with no NVIDIA driver and no `nvidia-smi`: `import cuopt`
|
|
268
|
+
succeeds and the first solve raises `RuntimeError: CUDA error ... cudaErrorInsufficientDriver`,
|
|
269
|
+
which gpuGEM reports as `no_gpu`. A machine with a working `nvidia-smi` but a broken CUDA setup
|
|
270
|
+
has not been tested, and its failure may surface as cuOpt's own exception. The Gurobi licence
|
|
271
|
+
error numbers (10009, 10010) used for `kind="license"` are unverified.
|
|
272
|
+
See `specs/016-missing-dependency-errors/`.
|
|
273
|
+
- **The dependency inventory is static.** `tests/test_dependency_inventory.py` finds imports and
|
|
274
|
+
`subprocess` / `shutil.which` calls with literal names; a module or executable whose name is built
|
|
275
|
+
at run time is not seen (the test prints those it cannot analyse).
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Running the tests
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
pip install -e ".[dev]"
|
|
283
|
+
pytest -q -rs
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
No GPU is required to run the suite. On a CPU-only machine the tests that need
|
|
287
|
+
cuOpt or a Gurobi licence are reported as **skipped** rather than failed, so a
|
|
288
|
+
clean run looks like:
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
190 passed, 10 skipped
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
If you see failures rather than skips, that is a genuine problem and we would
|
|
295
|
+
like to hear about it. The 10 skips become real tests on a machine with an
|
|
296
|
+
NVIDIA GPU (CUDA 12) and `pip install gpugem`.
|
|
297
|
+
|
|
298
|
+
CI runs this suite on Python 3.11 and 3.12 on every push. Because GitHub's
|
|
299
|
+
hosted runners have no GPU, a green badge means "everything checkable without a
|
|
300
|
+
GPU is correct"; it does not assert that the GPU solving path was exercised.
|
|
301
|
+
That path is covered by the benchmark runs reported in the paper.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Support and maintenance
|
|
306
|
+
|
|
307
|
+
gpuGEM is maintained by the Digital Metabolic Twin Centre at the University of
|
|
308
|
+
Galway. We commit to keeping the software available and to responding to issues
|
|
309
|
+
for at least two years following publication. Please report problems through
|
|
310
|
+
the [issue tracker](https://github.com/Digital-Metabolic-Twin-Centre/gpuGEM/issues).
|
|
311
|
+
|
|
312
|
+
Every released version is archived on Zenodo with a permanent DOI, so the exact
|
|
313
|
+
code behind a published result stays retrievable even if this repository moves.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Citation
|
|
318
|
+
|
|
319
|
+
If you use gpuGEM in your research, please cite:
|
|
320
|
+
|
|
321
|
+
> *gpuGEM: validated GPU solving of genome-scale metabolic LPs*
|
|
322
|
+
> Digital Metabolic Twin Centre, 2026. https://github.com/Digital-Metabolic-Twin-Centre/gpuGEM
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## License
|
|
327
|
+
|
|
328
|
+
MIT © 2026 Digital Metabolic Twin Centre
|
gpugem-0.1.2/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# gpuGEM
|
|
4
|
+
|
|
5
|
+
[](https://github.com/Digital-Metabolic-Twin-Centre/gpuGEM/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.python.org/)
|
|
7
|
+
[](https://developer.nvidia.com/cuda-toolkit)
|
|
8
|
+
[](https://developer.nvidia.com/cuopt)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[]()
|
|
11
|
+
|
|
12
|
+
GPU-accelerated Flux Balance Analysis (FBA) for genome-scale metabolic models.
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
gpuGEM wraps [NVIDIA cuOpt](https://developer.nvidia.com/cuopt) PDLP solver with validated default settings derived from systematic benchmarking on whole-body metabolic models, including the [Harvey](https://www.vmh.life/) model and personalised microbiome whole-body models. It provides a minimal, COBRA-compatible API so existing modelling workflows can switch to GPU solving with one line of code.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Why gpuGEM?
|
|
21
|
+
|
|
22
|
+
GPU first-order LP solvers (PDLP) offer substantial speedups on genome-scale FBA, but getting correct results requires non-obvious configuration. Out-of-the-box cuOpt settings produce large constraint violations or silently return wrong solutions on whole-body models. gpuGEM ships with settings that have been validated for correctness:
|
|
23
|
+
|
|
24
|
+
| Model | Reactions | Constraints | Time | Max stoich. residual |
|
|
25
|
+
|---|---|---|---|---|
|
|
26
|
+
| Harvey whole-body model | 81K | 160K | **0.76 s** | 1.8e-5 |
|
|
27
|
+
| Microbiome whole-body model | 789K | 1.66M | **27 s** | 8.4e-5 |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install gpugem
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This installs the full tool: numpy, scipy, NVIDIA cuOpt (the GPU solver) and COBRApy. gpuGEM is a
|
|
38
|
+
GPU solver, so solving needs an NVIDIA GPU with CUDA 12 and Python 3.11 or later. cuOpt is a large
|
|
39
|
+
download (about 1.4 GB, mostly `libcudf-cu12` and `libcuopt-cu12`). The `cuopt-cu12` entry on PyPI
|
|
40
|
+
is a stub that fetches the real wheels from NVIDIA's index. Behind a proxy that blocks
|
|
41
|
+
`pypi.nvidia.com` it can fail with a confusing build error; if that happens, name the index
|
|
42
|
+
explicitly:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install gpugem --extra-index-url https://pypi.nvidia.com
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`import gpugem` works without a usable GPU; the GPU is checked when you call `solve`, which raises a
|
|
49
|
+
`DependencyError` explaining what is missing.
|
|
50
|
+
|
|
51
|
+
Optional extras:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install "gpugem[gurobi]" # the Gurobi CPU baseline (needs a licence)
|
|
55
|
+
pip install "gpugem[application]" # the candidate-gene application, see application/
|
|
56
|
+
pip install "gpugem[dev]" # tests and linting
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Dependencies
|
|
60
|
+
|
|
61
|
+
| Package / tool | Role | Needed for | Install |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| `numpy`, `scipy` | required | every function | `pip install "numpy>=1.24"`, `pip install "scipy>=1.10"` |
|
|
64
|
+
| `cuopt` | required | the GPU solver; checked when you solve, not at `import gpugem` | `pip install "cuopt-cu12>=26.6.0" --extra-index-url https://pypi.nvidia.com` (other CUDA versions: see the [cuOpt install guide](https://docs.nvidia.com/cuopt/user-guide/latest/introduction.html)) |
|
|
65
|
+
| `cobra` | required | `solve_cobra`, `loaders.from_cobra` | `pip install "cobra>=0.29"` |
|
|
66
|
+
| `gurobipy` | benchmarks, application | the Gurobi baseline (needs a licence); also the minimum-norm QP in `application/` | `pip install "gpugem[gurobi]"` |
|
|
67
|
+
| `pandas` | application | result tables in `application/` | `pip install "gpugem[application]"` |
|
|
68
|
+
| `highspy` | benchmarks | the HiGHS CPU baseline | `pip install highspy` |
|
|
69
|
+
| `pandas`, `matplotlib` | benchmarks | result tables and figures | `pip install pandas`, `pip install matplotlib` |
|
|
70
|
+
| `pytest` | development | the test suite | `pip install -e ".[dev]"` |
|
|
71
|
+
| `nvidia-smi` | tool | GPU name in benchmark metadata; GPU diagnosis | install the NVIDIA driver (https://www.nvidia.com/drivers); nvidia-smi ships with it |
|
|
72
|
+
|
|
73
|
+
### Troubleshooting
|
|
74
|
+
|
|
75
|
+
A missing or unusable dependency raises `gpugem.DependencyError` (benchmark and example scripts
|
|
76
|
+
print the same message; benchmark scripts exit with status 3). The message always says what is wrong, why gpuGEM
|
|
77
|
+
needs it, and how to fix it. The original exception is kept as `__cause__`. `err.kind` is one of:
|
|
78
|
+
|
|
79
|
+
| `kind` | Meaning | What to do |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `not_installed` | the package is absent | run the `pip install` command in the message |
|
|
82
|
+
| `broken` | installed but fails to import, or a model file cannot be read | reinstall; check that Python, the CUDA runtime and the driver match the build. For `.mat` files, re-save with `save(file, 'model', '-v7')` |
|
|
83
|
+
| `version` | installed version below the minimum | run the `pip install --upgrade` command in the message |
|
|
84
|
+
| `no_gpu` | no usable NVIDIA GPU or driver is detected | run `nvidia-smi`; install or update the driver; in a container, start it with GPU access |
|
|
85
|
+
| `license` | Gurobi is installed but has no valid licence for the model | obtain a licence and set `GRB_LICENSE_FILE`; check with `grbprobe` |
|
|
86
|
+
| `tool_missing` | an executable is not on `PATH` | install it or add it to `PATH` |
|
|
87
|
+
| `network` | a model download failed | download the file manually into `benchmarks/model_cache/` |
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
try:
|
|
91
|
+
result = gpugem.solve_cobra(model)
|
|
92
|
+
except gpugem.DependencyError as err:
|
|
93
|
+
print(err.kind, err.dependency, err.remedy)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Quick start
|
|
99
|
+
|
|
100
|
+
**From a COBRApy model:**
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
import cobra
|
|
104
|
+
import gpugem
|
|
105
|
+
|
|
106
|
+
model = cobra.io.read_sbml_model("e_coli_core.xml")
|
|
107
|
+
result = gpugem.solve_cobra(model)
|
|
108
|
+
|
|
109
|
+
print(result) # FBAResult(status='Optimal', objective=0.8739, time=0.42s)
|
|
110
|
+
print(result.objective)
|
|
111
|
+
print(result.fluxes) # numpy array, same order as model.reactions
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**From a COBRA `.mat` file (MATLAB format):**
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import gpugem
|
|
118
|
+
|
|
119
|
+
arrays = gpugem.loaders.from_mat("Harvey_1_03c_reduced.mat")
|
|
120
|
+
result = gpugem.solve(**arrays)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**From raw matrices (scipy sparse):**
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import gpugem
|
|
127
|
+
|
|
128
|
+
result = gpugem.solve(
|
|
129
|
+
S=S, # stoichiometric matrix (sparse)
|
|
130
|
+
b=b, # RHS equalities
|
|
131
|
+
lb=lb, ub=ub, # variable bounds
|
|
132
|
+
c=c, # objective coefficients
|
|
133
|
+
maximize=True,
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Whole-body models with coupling constraints:**
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
result = gpugem.solve(S=S, b=b, lb=lb, ub=ub, c=c,
|
|
141
|
+
C=C, d_lb=d_lb, d_ub=d_ub,
|
|
142
|
+
maximize=True)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Default settings
|
|
148
|
+
|
|
149
|
+
gpuGEM automatically selects solver settings based on model size:
|
|
150
|
+
|
|
151
|
+
**Small models (≤ 100K reactions) — e.g. Harvey whole-body model:**
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
pdlp_precision = 1 # mixed FP32/FP64 — double precision diverges on these models
|
|
155
|
+
absolute_primal_tolerance = 1e-8 # tight tolerance: 0 stoich violations at 1e-6 max residual
|
|
156
|
+
relative_primal_tolerance = 1e-8
|
|
157
|
+
absolute_dual_tolerance = 1e-8
|
|
158
|
+
relative_dual_tolerance = 1e-8
|
|
159
|
+
per_constraint_residual = 1 # max-norm convergence check → better accuracy
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Large models (> 100K reactions) — e.g. personalised microbiome whole-body models:**
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
presolve = 1 # PaPILO — required; default PSLP falsely reports Infeasible
|
|
166
|
+
per_constraint_residual = 1 # max-norm convergence → 44% fewer violations vs default
|
|
167
|
+
absolute_primal_tolerance = 1e-4 # cuOpt's internal default tolerance
|
|
168
|
+
relative_primal_tolerance = 1e-4
|
|
169
|
+
absolute_dual_tolerance = 1e-4
|
|
170
|
+
relative_dual_tolerance = 1e-4
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Any cuOpt parameter can be overridden:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
# Use Stable1 PDLP mode for maximum accuracy (slower)
|
|
177
|
+
result = gpugem.solve(**arrays, pdlp_solver_mode=0, time_limit=300)
|
|
178
|
+
|
|
179
|
+
# Disable presolve explicitly
|
|
180
|
+
result = gpugem.solve(**arrays, presolve=0)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Stateful solver (repeated solves)
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
solver = gpugem.FBASolver(S=S, b=b, lb=lb, ub=ub, c=c, maximize=True)
|
|
189
|
+
|
|
190
|
+
for patient_c in patient_objectives:
|
|
191
|
+
result = solver.solve(c=patient_c)
|
|
192
|
+
print(result.objective)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Result object
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
result.status # 'Optimal', 'TimeLimit', 'Infeasible', ...
|
|
201
|
+
result.objective # float
|
|
202
|
+
result.fluxes # np.ndarray (n_vars,)
|
|
203
|
+
result.wall_time_s # float
|
|
204
|
+
result.solver_settings # dict of applied cuOpt parameters
|
|
205
|
+
result.feasibility # dict: stoich_max_residual, stoich_rows_violated_1e6, ...
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Known limitations
|
|
211
|
+
|
|
212
|
+
- **Lifted, extreme-scale (`S84`/`S85`-class) models are not reliably solvable by every Gurobi
|
|
213
|
+
entry point.** `benchmarks/run_matlab_python_lifting_comparison.py` found that Gurobi solves the
|
|
214
|
+
lifted `S84`/`S85` systems to `Optimal` via COBRA Toolbox's `solveCobraLP` (MATLAB) but reports
|
|
215
|
+
`Infeasible` for the *structurally identical* system (confirmed: identical auxiliary-variable
|
|
216
|
+
and lifted-row counts) via a direct `gurobipy` call (Python). Checked and ruled out as causes:
|
|
217
|
+
a Gurobi solver-settings mismatch (`Method`, `TimeLimit`, `FeasibilityTol`/`OptimalityTol` all
|
|
218
|
+
confirmed identical), a Gurobi *version* mismatch (`pip install gurobipy` initially resolved a
|
|
219
|
+
different major version, `13.0.3`, than MATLAB's licensed `11.0.3`; re-run with the versions
|
|
220
|
+
pinned to match — `Infeasible` persisted), and a borderline numerical-tolerance flip (persisted
|
|
221
|
+
after relaxing `FeasibilityTol` 100x). This reproduces, under a second independent solver
|
|
222
|
+
pathway, the same `S84`/`S85` scale boundary already found for lifted cuOpt solves (see "Version
|
|
223
|
+
x lifting runtime comparison" above) — lifting's mathematical correctness is not in question
|
|
224
|
+
(structural fidelity is exact), but its practical solvability at this scale is
|
|
225
|
+
entry-point-dependent, for a reason not yet isolated. See
|
|
226
|
+
`specs/015-matlab-python-lifting-comparison/`.
|
|
227
|
+
|
|
228
|
+
- **GPU/driver-absent detection is a heuristic, checked on one kind of machine.** gpuGEM reports
|
|
229
|
+
`DependencyError(kind="no_gpu")` only when cuOpt raises *and* there is positive evidence of a GPU
|
|
230
|
+
problem (`nvidia-smi` fails or lists no GPU, or it is absent and no `/dev/nvidia*` device exists).
|
|
231
|
+
Observed with cuOpt 26.8.0 on a machine with no NVIDIA driver and no `nvidia-smi`: `import cuopt`
|
|
232
|
+
succeeds and the first solve raises `RuntimeError: CUDA error ... cudaErrorInsufficientDriver`,
|
|
233
|
+
which gpuGEM reports as `no_gpu`. A machine with a working `nvidia-smi` but a broken CUDA setup
|
|
234
|
+
has not been tested, and its failure may surface as cuOpt's own exception. The Gurobi licence
|
|
235
|
+
error numbers (10009, 10010) used for `kind="license"` are unverified.
|
|
236
|
+
See `specs/016-missing-dependency-errors/`.
|
|
237
|
+
- **The dependency inventory is static.** `tests/test_dependency_inventory.py` finds imports and
|
|
238
|
+
`subprocess` / `shutil.which` calls with literal names; a module or executable whose name is built
|
|
239
|
+
at run time is not seen (the test prints those it cannot analyse).
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Running the tests
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
pip install -e ".[dev]"
|
|
247
|
+
pytest -q -rs
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
No GPU is required to run the suite. On a CPU-only machine the tests that need
|
|
251
|
+
cuOpt or a Gurobi licence are reported as **skipped** rather than failed, so a
|
|
252
|
+
clean run looks like:
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
190 passed, 10 skipped
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
If you see failures rather than skips, that is a genuine problem and we would
|
|
259
|
+
like to hear about it. The 10 skips become real tests on a machine with an
|
|
260
|
+
NVIDIA GPU (CUDA 12) and `pip install gpugem`.
|
|
261
|
+
|
|
262
|
+
CI runs this suite on Python 3.11 and 3.12 on every push. Because GitHub's
|
|
263
|
+
hosted runners have no GPU, a green badge means "everything checkable without a
|
|
264
|
+
GPU is correct"; it does not assert that the GPU solving path was exercised.
|
|
265
|
+
That path is covered by the benchmark runs reported in the paper.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Support and maintenance
|
|
270
|
+
|
|
271
|
+
gpuGEM is maintained by the Digital Metabolic Twin Centre at the University of
|
|
272
|
+
Galway. We commit to keeping the software available and to responding to issues
|
|
273
|
+
for at least two years following publication. Please report problems through
|
|
274
|
+
the [issue tracker](https://github.com/Digital-Metabolic-Twin-Centre/gpuGEM/issues).
|
|
275
|
+
|
|
276
|
+
Every released version is archived on Zenodo with a permanent DOI, so the exact
|
|
277
|
+
code behind a published result stays retrievable even if this repository moves.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Citation
|
|
282
|
+
|
|
283
|
+
If you use gpuGEM in your research, please cite:
|
|
284
|
+
|
|
285
|
+
> *gpuGEM: validated GPU solving of genome-scale metabolic LPs*
|
|
286
|
+
> Digital Metabolic Twin Centre, 2026. https://github.com/Digital-Metabolic-Twin-Centre/gpuGEM
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT © 2026 Digital Metabolic Twin Centre
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""gpuGEM — GPU-accelerated Flux Balance Analysis for genome-scale metabolic models."""
|
|
2
|
+
|
|
3
|
+
from gpugem._deps import DependencyError, require as _require
|
|
4
|
+
|
|
5
|
+
# numpy/scipy are hard dependencies; fail here with guidance, not with a bare ImportError below.
|
|
6
|
+
# cuOpt is deliberately NOT checked at import: it is required only when a solve is requested.
|
|
7
|
+
_require("numpy")
|
|
8
|
+
_require("scipy")
|
|
9
|
+
|
|
10
|
+
from gpugem.solver import FBASolver, solve # noqa: E402
|
|
11
|
+
from gpugem.solve_cobra import solve_cobra # noqa: E402
|
|
12
|
+
from gpugem.result import FBAResult # noqa: E402
|
|
13
|
+
from gpugem.scaling import CoefficientScalingMapping, remap_fluxes, scale_model # noqa: E402
|
|
14
|
+
from gpugem.lifting import LiftingMapping, lift_coupling, lift_mass_balance, map_back # noqa: E402
|
|
15
|
+
from gpugem import loaders # noqa: E402
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.2"
|
|
18
|
+
__all__ = [
|
|
19
|
+
"DependencyError",
|
|
20
|
+
"FBASolver",
|
|
21
|
+
"solve",
|
|
22
|
+
"solve_cobra",
|
|
23
|
+
"FBAResult",
|
|
24
|
+
"CoefficientScalingMapping",
|
|
25
|
+
"scale_model",
|
|
26
|
+
"remap_fluxes",
|
|
27
|
+
"LiftingMapping",
|
|
28
|
+
"lift_mass_balance",
|
|
29
|
+
"lift_coupling",
|
|
30
|
+
"map_back",
|
|
31
|
+
"loaders",
|
|
32
|
+
]
|