Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b6bd2d7
Fix: prevent lp_problem/result leak on exception in solve_once
LucasBoTang Jul 9, 2026
ff0d6d0
Fix: map FEAS_POLISH_SUCCESS status code and add missing constants
LucasBoTang Jul 9, 2026
d781d9c
Fix: avoid mutating caller's matrix and support scipy sparse arrays
LucasBoTang Jul 9, 2026
1fe25e4
Fix: validate param names and fix setWarmStart semantics
LucasBoTang Jul 9, 2026
15eeab2
Cleanup: drop dead code in optimize() and fix pytest config
LucasBoTang Jul 10, 2026
59a6da9
Feat: make Status an int code
LucasBoTang Jul 10, 2026
db0cabf
Docs: align comment/docstring style
LucasBoTang Jul 10, 2026
75147da
Fix: validate sparse matrix structure in solve_once
LucasBoTang Jul 10, 2026
277afcc
Feat: expose model data as validated properties
LucasBoTang Jul 10, 2026
1f27b84
Fix: update warm start test
LucasBoTang Jul 10, 2026
7fcf62d
Docs: fix stale warm start and default-bound descriptions
LucasBoTang Jul 10, 2026
e5ed0f2
Docs: clean comments
LucasBoTang Jul 10, 2026
b662b06
Fix: drop test use of nonexistent InfeasibleTol param
LucasBoTang Jul 10, 2026
6128b21
Test: add API-surface coverage and ignore coverage artifacts
LucasBoTang Jul 10, 2026
d5e9ce3
Feat: model setter validation
LucasBoTang Jul 10, 2026
16a5acf
Fix: expose documented Python API exports
LucasBoTang Jul 10, 2026
1755180
Fix: validate solver result buffers
LucasBoTang Jul 10, 2026
f4e74a0
Feat: validate Python solver params
LucasBoTang Jul 10, 2026
a48788b
Fix: prevent in-place model data mutation
LucasBoTang Jul 10, 2026
eac407c
Feat: add Python param reset helper
LucasBoTang Jul 10, 2026
9b1bc8c
Fix: disable presolve in limit test
LucasBoTang Jul 10, 2026
6a65b9a
Fix: validate core solver params
LucasBoTang Jul 10, 2026
2d5f799
Fix: validate core model data
LucasBoTang Jul 10, 2026
51e7cc9
Fix: reject NaN model bounds
LucasBoTang Jul 10, 2026
0a284a8
Feat: return model from optimize
LucasBoTang Jul 10, 2026
b952dfc
Docs: update Python API docs
LucasBoTang Jul 10, 2026
64fb294
Feat: extend Params mapping helpers
LucasBoTang Jul 10, 2026
5d756d8
Fix: accept numpy scalars and 0/1 for solver params
LucasBoTang Jul 10, 2026
49c6dd3
Fix: guard param validation and init ordering
LucasBoTang Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,18 @@ dkms.conf
# ignored files
build/*
test/*
!test/*.py
/.vscode
/.venv
/_b
*.whl
*.pyc

# coverage / pytest artifacts
.coverage
coverage.xml
htmlcov/
.pytest_cache/

*.txt
*.sh
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ test = [
]

[tool.pytest.ini_options]
testpaths = ["tests"]
testpaths = ["test"]
addopts = """
-q
-ra
Expand Down
2 changes: 0 additions & 2 deletions pytest.ini

This file was deleted.

21 changes: 12 additions & 9 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ m.setParams(OutputFlag=True, OptimalityTol=1e-8)
m.optimize()

# Retrieve results
print("Status:", m.Status)
print("Status:", m.StatusName)
print("Objective:", m.ObjVal)
print("Primal solution:", m.X)
print("Dual solution:", m.Pi)
Expand All @@ -98,7 +98,7 @@ $$
- **constraint_matrix** (`A`): Coefficient matrix for the constraints. Both dense (`numpy.ndarray`) and sparse (`scipy.sparse.csr_matrix`) inputs are supported. Internally stored in double precision (`float64`).
- **constraint_lower_bound** (`l`): Lower bounds for each constraint. Use `-np.inf` or `None` for no lower bound.
- **constraint_upper_bound** (`u`): Upper bounds for each constraint. Use `+np.inf` or `None` for no upper bound.
- **variable_lower_bound** (`lb`, optional): Lower bounds for the decision variables. Defaults to `0` for all variables if not provided.
- **variable_lower_bound** (`lb`, optional): Lower bounds for the decision variables. Defaults to `-np.inf` for all variables if not provided.
- **variable_upper_bound** (`ub`, optional): Upper bounds for the decision variables. Defaults to `+np.inf` for all variables if not provided.
- **objective_constant** (`c0`, optional): Constant offset in the objective function. Defaults to `0.0`.

Expand All @@ -125,7 +125,7 @@ m = Model(objective_vector=c,

### Reading from MPS Files

A `Model` can also be created directly from an MPS file (plain or gzip-compressed) with `cupdlpx.read`, similar to `gurobipy.read`:
A `Model` can also be created directly from an MPS file (plain or gzip-compressed) with `cupdlpx.read`:

```python
import cupdlpx
Expand Down Expand Up @@ -174,7 +174,7 @@ Below is a list of commonly used parameters, their internal keys, and descriptio
| `ReflectionCoeff` | `reflection_coefficient` | float | `1.0` | Reflection coefficient. |
| `SVMaxIter` | `sv_max_iter` | int | 5000 | Maximum number of iterations for the power method |
| `SVTol`| `sv_tol` | float | `1e-4` | Termination tolerance for the power method |
| `Presolve`| `presolve` | float | `True` | Whether to use presolve. |
| `Presolve`| `presolve` | bool | `True` | Whether to use presolve. |
| `FeasibilityPolishing` | `feasibility_polishing` | bool | `False` | Run feasibility polishing process.|
| `FeasibilityPolishingTol` | `eps_feas_polish_relative` | float | `1e-6` | Relative tolerance for primal/dual residual. |

Expand All @@ -191,18 +191,21 @@ m.setParams(TimeLimit=300, FeasibilityTol=1e-6)
# Method 3: attribute-style access
m.Params.TimeLimit = 300
m.Params.FeasibilityTol = 1e-6

# Reset all parameters to backend defaults
m.resetParams()
```

## Solution Attributes

After calling `m.optimize()`, the solver stores results in a set of read-only attributes. These attributes provide access to primal/dual solutions, objective values, residuals, and runtime statistics.
After calling `m.optimize()`, the solver stores results in a set of read-only attributes. `optimize()` returns the model itself, so chained access like `m.optimize().Status` is also supported. These attributes provide access to primal/dual solutions, objective values, residuals, and runtime statistics.

### Attribute Reference

| Attribute | Type | Description |
|---|---|---|
| `Status` | str | Human-readable solver status (`"OPTIMAL"`, `"INFEASIBLE"`, `"UNBOUNDED"`, `"TIME_LIMIT"`, etc.). |
| `StatusCode` | int | Numeric status code (`OPTIMAL=1`, `INFEASIBLE=2`, `UNBOUNDED=3`, `ITERATION_LIMIT=4`, `TIME_LIMIT=5`, `UNSPECIFIED=-1`). |
| `Status` | int | Integer termination status code; compare against `cupdlpx.PDLP` constants: `OPTIMAL=0`, `PRIMAL_INFEASIBLE=1`, `DUAL_INFEASIBLE=2`, `TIME_LIMIT=3`, `ITERATION_LIMIT=4`, `INFEASIBLE_OR_UNBOUNDED=5`, `FEAS_POLISH_SUCCESS=6`, `UNSPECIFIED=-1`. |
| `StatusName` | str | Human-readable status name, e.g. `"OPTIMAL"`, `"PRIMAL_INFEASIBLE"`. |
| `ObjVal` | float | Primal objective value at termination (sign-adjusted according to `ModelSense`). |
| `DualObj` | float | Dual objective value at termination. |
| `Gap` | float | Absolute primal-dual gap. |
Expand All @@ -225,7 +228,7 @@ All solution-related information can then be queried directly from the `Model` o
```python
m.optimize()

print("Status:", m.Status, "(code:", m.StatusCode, ")")
print("Status:", m.StatusName, "(code:", m.Status, ")")
print("Primal objective:", m.ObjVal)
print("Dual objective:", m.DualObj)
print("Relative gap:", m.RelGap)
Expand Down Expand Up @@ -268,7 +271,7 @@ m.setWarmStart(primal=x_init)
m.setWarmStart(dual=pi_init)
```

If the warm-start vectors have incorrect dimensions, the solver automatically falls back to a cold start and issues a warning.
If the warm-start vectors have incorrect dimensions, `setWarmStart` raises a `ValueError`. Omitting an argument leaves that side unchanged; passing `None` clears it.

To clear existing warm-start values:

Expand Down
20 changes: 12 additions & 8 deletions python/cupdlpx/PDLP.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# Objective
"""Objective-sense and termination-status constants, plus parameter-name aliases."""

# Objective
MINIMIZE = 1
MAXIMIZE = -1

# Status codes
OPTIMAL = 0
PRIMAL_INFEASIBLE = 1
DUAL_INFEASIBLE = 2
TIME_LIMIT = 3
ITERATION_LIMIT = 4
UNSPECIFIED = -1
# Status codes (must match status_to_code in python_bindings/_core_bindings.cpp)
OPTIMAL = 0
PRIMAL_INFEASIBLE = 1
DUAL_INFEASIBLE = 2
TIME_LIMIT = 3
ITERATION_LIMIT = 4
INFEASIBLE_OR_UNBOUNDED = 5
FEAS_POLISH_SUCCESS = 6
UNSPECIFIED = -1


# parameter name alias
Expand Down
10 changes: 6 additions & 4 deletions python/cupdlpx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""cuPDLPx: Python bindings for the GPU-accelerated first-order LP solver."""

import os
import platform

# Windows only: register CUDA bin for dependent DLL loading.
if platform.system() == "Windows":
if platform.system() == "Windows": # pragma: no cover
cuda_path = os.environ.get("CUDA_PATH")
if cuda_path:
bin_path = os.path.join(cuda_path, "bin")
Expand All @@ -26,12 +28,12 @@
from .model import Model, read
from . import PDLP

__all__ = ["Model", "read"]

# versioning
from importlib.metadata import version, PackageNotFoundError
# get version from package metadata (toml file)
try:
__version__ = version("cupdlpx")
except PackageNotFoundError:
except PackageNotFoundError: # pragma: no cover
__version__ = "0.0.0"

__all__ = ["Model", "PDLP", "read", "__version__"]
2 changes: 2 additions & 0 deletions python/cupdlpx/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""Thin re-export of the compiled cuPDLPx core extension (_cupdlpx_core)."""

from ._cupdlpx_core import solve_once, get_default_params, read_mps
Loading
Loading