Skip to main content

gpuGEM

CI Python CUDA cuOpt License: MIT Status: Alpha

GPU-accelerated Flux Balance Analysis (FBA) for genome-scale metabolic models.

gpuGEM wraps NVIDIA cuOpt PDLP solver with validated default settings derived from systematic benchmarking on whole-body metabolic models, including the Harvey 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.


Why gpuGEM?

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:

Model Reactions Constraints Time Max stoich. residual
Harvey whole-body model 81K 160K 0.76 s 1.8e-5
Microbiome whole-body model 789K 1.66M 27 s 8.4e-5

Installation

pip install gpugem

This installs the full tool: numpy, scipy, NVIDIA cuOpt (the GPU solver) and COBRApy. gpuGEM is a GPU solver, so solving needs an NVIDIA GPU with CUDA 12 and Python 3.11 or later. cuOpt is a large download (about 1.4 GB, mostly libcudf-cu12 and libcuopt-cu12). The cuopt-cu12 entry on PyPI is a stub that fetches the real wheels from NVIDIA's index. Behind a proxy that blocks pypi.nvidia.com it can fail with a confusing build error; if that happens, name the index explicitly:

pip install gpugem --extra-index-url https://pypi.nvidia.com

import gpugem works without a usable GPU; the GPU is checked when you call solve, which raises a DependencyError explaining what is missing.

Optional extras:

pip install "gpugem[gurobi]"       # the Gurobi CPU baseline (needs a licence)
pip install "gpugem[application]"  # the candidate-gene application, see application/
pip install "gpugem[dev]"          # tests and linting

Dependencies

Package / tool Role Needed for Install
numpy, scipy required every function pip install "numpy>=1.24", pip install "scipy>=1.10"
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)
cobra required solve_cobra, loaders.from_cobra pip install "cobra>=0.29"
gurobipy benchmarks, application the Gurobi baseline (needs a licence); also the minimum-norm QP in application/ pip install "gpugem[gurobi]"
pandas application result tables in application/ pip install "gpugem[application]"
highspy benchmarks the HiGHS CPU baseline pip install highspy
pandas, matplotlib benchmarks result tables and figures pip install pandas, pip install matplotlib
pytest development the test suite pip install -e ".[dev]"
nvidia-smi tool GPU name in benchmark metadata; GPU diagnosis install the NVIDIA driver (https://www.nvidia.com/drivers); nvidia-smi ships with it

Troubleshooting

A missing or unusable dependency raises gpugem.DependencyError (benchmark and example scripts print the same message; benchmark scripts exit with status 3). The message always says what is wrong, why gpuGEM needs it, and how to fix it. The original exception is kept as __cause__. err.kind is one of:

kind Meaning What to do
not_installed the package is absent run the pip install command in the message
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')
version installed version below the minimum run the pip install --upgrade command in the message
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
license Gurobi is installed but has no valid licence for the model obtain a licence and set GRB_LICENSE_FILE; check with grbprobe
tool_missing an executable is not on PATH install it or add it to PATH
network a model download failed download the file manually into benchmarks/model_cache/
try:
    result = gpugem.solve_cobra(model)
except gpugem.DependencyError as err:
    print(err.kind, err.dependency, err.remedy)

Quick start

From a COBRApy model:

import cobra
import gpugem

model = cobra.io.read_sbml_model("e_coli_core.xml")
result = gpugem.solve_cobra(model)

print(result)                       # FBAResult(status='Optimal', objective=0.8739, time=0.42s)
print(result.objective)
print(result.fluxes)                # numpy array, same order as model.reactions

From a COBRA .mat file (MATLAB format):

import gpugem

arrays = gpugem.loaders.from_mat("Harvey_1_03c_reduced.mat")
result = gpugem.solve(**arrays)

From raw matrices (scipy sparse):

import gpugem

result = gpugem.solve(
    S=S,           # stoichiometric matrix (sparse)
    b=b,           # RHS equalities
    lb=lb, ub=ub,  # variable bounds
    c=c,           # objective coefficients
    maximize=True,
)

Whole-body models with coupling constraints:

result = gpugem.solve(S=S, b=b, lb=lb, ub=ub, c=c,
                      C=C, d_lb=d_lb, d_ub=d_ub,
                      maximize=True)

Default settings

gpuGEM automatically selects solver settings based on model size:

Small models (≤ 100K reactions) — e.g. Harvey whole-body model:

pdlp_precision             = 1      # mixed FP32/FP64 — double precision diverges on these models
absolute_primal_tolerance  = 1e-8   # tight tolerance: 0 stoich violations at 1e-6 max residual
relative_primal_tolerance  = 1e-8
absolute_dual_tolerance    = 1e-8
relative_dual_tolerance    = 1e-8
per_constraint_residual    = 1      # max-norm convergence check → better accuracy

Large models (> 100K reactions) — e.g. personalised microbiome whole-body models:

presolve                   = 1      # PaPILO — required; default PSLP falsely reports Infeasible
per_constraint_residual    = 1      # max-norm convergence → 44% fewer violations vs default
absolute_primal_tolerance  = 1e-4   # cuOpt's internal default tolerance
relative_primal_tolerance  = 1e-4
absolute_dual_tolerance    = 1e-4
relative_dual_tolerance    = 1e-4

Any cuOpt parameter can be overridden:

# Use Stable1 PDLP mode for maximum accuracy (slower)
result = gpugem.solve(**arrays, pdlp_solver_mode=0, time_limit=300)

# Disable presolve explicitly
result = gpugem.solve(**arrays, presolve=0)

Time limit for large models

time_limit is the wall-clock budget in seconds and defaults to 60. That is enough for genome-scale models such as iML1515 (about 4 s) and the Harvey whole-body model (a few seconds), but not for the million-variable personalised microbiome models, which took roughly 25 to 880 s on an NVIDIA RTX A4500. For those, pass a larger limit, for example time_limit=900. If the limit is reached, result.status is "TimeLimit" and the solution has not converged (its residual can be far above the 1e-4 criterion), so check result.status and result.feasibility["stoich_max_residual"] before using the fluxes.


Stateful solver (repeated solves)

solver = gpugem.FBASolver(S=S, b=b, lb=lb, ub=ub, c=c, maximize=True)

for patient_c in patient_objectives:
    result = solver.solve(c=patient_c)
    print(result.objective)

Result object

result.status       # 'Optimal', 'TimeLimit', 'Infeasible', ...
result.objective    # float
result.fluxes       # np.ndarray (n_vars,)
result.wall_time_s  # float
result.solver_settings  # dict of applied cuOpt parameters
result.feasibility  # dict: stoich_max_residual, stoich_rows_violated_1e6, ...

Known limitations

  • Lifted, extreme-scale (S84/S85-class) models are not reliably solvable by every Gurobi entry point. benchmarks/run_matlab_python_lifting_comparison.py found that Gurobi solves the lifted S84/S85 systems to Optimal via COBRA Toolbox's solveCobraLP (MATLAB) but reports Infeasible for the structurally identical system (confirmed: identical auxiliary-variable and lifted-row counts) via a direct gurobipy call (Python). Checked and ruled out as causes: a Gurobi solver-settings mismatch (Method, TimeLimit, FeasibilityTol/OptimalityTol all confirmed identical), a Gurobi version mismatch (pip install gurobipy initially resolved a different major version, 13.0.3, than MATLAB's licensed 11.0.3; re-run with the versions pinned to match — Infeasible persisted), and a borderline numerical-tolerance flip (persisted after relaxing FeasibilityTol 100x). This reproduces, under a second independent solver pathway, the same S84/S85 scale boundary already found for lifted cuOpt solves (see "Version x lifting runtime comparison" above) — lifting's mathematical correctness is not in question (structural fidelity is exact), but its practical solvability at this scale is entry-point-dependent, for a reason not yet isolated. See specs/015-matlab-python-lifting-comparison/.

  • GPU/driver-absent detection is a heuristic, checked on one kind of machine. gpuGEM reports DependencyError(kind="no_gpu") only when cuOpt raises and there is positive evidence of a GPU problem (nvidia-smi fails or lists no GPU, or it is absent and no /dev/nvidia* device exists). Observed with cuOpt 26.8.0 on a machine with no NVIDIA driver and no nvidia-smi: import cuopt succeeds and the first solve raises RuntimeError: CUDA error ... cudaErrorInsufficientDriver, which gpuGEM reports as no_gpu. A machine with a working nvidia-smi but a broken CUDA setup has not been tested, and its failure may surface as cuOpt's own exception. The Gurobi licence error numbers (10009, 10010) used for kind="license" are unverified. See specs/016-missing-dependency-errors/.

  • The dependency inventory is static. tests/test_dependency_inventory.py finds imports and subprocess / shutil.which calls with literal names; a module or executable whose name is built at run time is not seen (the test prints those it cannot analyse).


Running the tests

pip install -e ".[dev]"
pytest -q -rs

No GPU is required to run the suite. On a CPU-only machine the tests that need cuOpt or a Gurobi licence are reported as skipped rather than failed, so a clean run looks like:

206 passed, 10 skipped

If you see failures rather than skips, that is a genuine problem and we would like to hear about it. The 10 skips become real tests on a machine with an NVIDIA GPU (CUDA 12) and pip install gpugem.

CI runs this suite on Python 3.11 and 3.12 on every push. Because GitHub's hosted runners have no GPU, a green badge means "everything checkable without a GPU is correct"; it does not assert that the GPU solving path was exercised. That path is covered by the benchmark runs reported in the paper.


Support and maintenance

gpuGEM is maintained by the Digital Metabolic Twin Centre at the University of Galway. We commit to keeping the software available and to responding to issues for at least two years following publication. Please report problems through the issue tracker.

Every released version is archived on Zenodo with a permanent DOI, so the exact code behind a published result stays retrievable even if this repository moves.


Citation

If you use gpuGEM in your research, please cite:

gpuGEM: validated GPU solving of genome-scale metabolic LPs
Digital Metabolic Twin Centre, 2026. https://github.com/Digital-Metabolic-Twin-Centre/gpuGEM


License

MIT © 2026 Digital Metabolic Twin Centre

Metadata

Release files for gpugem 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gpugem 0.1.3
File Size Uploaded
gpugem-0.1.3.tar.gz 62.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for gpugem 0.1.3
File Interpreter ABI Platform
gpugem-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 92.1 kB

Release files / gpugem-0.1.3.tar.gz

Download URL gpugem-0.1.3.tar.gz
Size 62.1 kB
Tags Source
SHA-256 checksum
How to use checksums
b37c0109882e74db38f276891529b464324cb8e0155a84bcfdc4d3e47dd463b2
BLAKE2b-256 checksum
How to use checksums
79ce6c197da6f8d98de90f53191d31967e43f84110e9ca6d060815b62f6fe09e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / gpugem-0.1.3-py3-none-any.whl

Download URL gpugem-0.1.3-py3-none-any.whl
Size 30.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
42eaf323963c2fbfacfe71375f2c76d618a98c7232219f81a74504836db6cdc2
BLAKE2b-256 checksum
How to use checksums
575a1a093599c1ff2bf607676cf194b554a47a013587272bbd21a1c79dc49fa2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files

0.1.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page