There is the Python interface to cuPDLPx, a GPU-accelerated first-order solver for large-scale linear programming (LP).
It provides a high-level, Pythonic API for constructing, modifying, and solving LPs using NumPy and SciPy data structures.
- Python ≥ 3.9
- NumPy ≥ 1.21
- SciPy ≥ 1.8
- An NVIDIA GPU with CUDA support (≥12.4 required)
- A C/C++ toolchain with GCC and NVCC
Install from PyPI:
pip install cupdlpxOr build from source:
git clone https://github.com/MIT-Lu-Lab/cuPDLPx.git
cd cuPDLPx
pip install .import numpy as np
from cupdlpx import Model, PDLP
# Example: minimize c^T x
# subject to l <= A x <= u, lb <= x <= ub
c = np.array([1.0, 1.0])
A = np.array([[1.0, 2.0],
[0.0, 1.0],
[3.0, 2.0]])
l = np.array([5.0, -np.inf, -np.inf])
u = np.array([5.0, 2.0, 8.0])
lb = np.zeros(2) # x >= 0
ub = None # no upper bound
# Create LP model
m = Model(objective_vector=c,
constraint_matrix=A,
constraint_lower_bound=l,
constraint_upper_bound=u,
variable_lower_bound=lb,
variable_upper_bound=ub)
# Set model sense
m.ModelSense = PDLP.MAXIMIZE
# Parameters can be set in multiple ways
m.Params.TimeLimit = 60 # attribute style
m.setParam("FeasibilityTol", 1e-6)
m.setParams(OutputFlag=True, OptimalityTol=1e-8)
# Solve
m.optimize()
# Retrieve results
print("Status:", m.Status)
print("Objective:", m.ObjVal)
print("Primal solution:", m.X)
print("Dual solution:", m.Pi)
print("Reduced cost:", m.RC)The Model class represents a linear programming problem of the form:
- objective_vector (
c): Coefficients of the objective function. - 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.inforNonefor no lower bound. - constraint_upper_bound (
u): Upper bounds for each constraint. Use+np.inforNonefor no upper bound. - variable_lower_bound (
lb, optional): Lower bounds for the decision variables. Defaults to0for all variables if not provided. - variable_upper_bound (
ub, optional): Upper bounds for the decision variables. Defaults to+np.inffor all variables if not provided. - objective_constant (
c0, optional): Constant offset in the objective function. Defaults to0.0.
To initialize a linear programming problem, you need to provide the objective vector, constraint matrix, and bounds on both constraints and variables.
c = np.array([1.0, 1.0])
A = np.array([[1.0, 2.0],
[0.0, 1.0],
[3.0, 2.0]])
l = np.array([5.0, -np.inf, -np.inf])
u = np.array([5.0, 2.0, 8.0])
lb = np.zeros(2) # x >= 0
ub = None # no upper bound
# Create LP model
m = Model(objective_vector=c,
constraint_matrix=A,
constraint_lower_bound=l,
constraint_upper_bound=u,
variable_lower_bound=lb,
variable_upper_bound=ub)By default, cupdlpx solves minimization problems.
To switch between minimization and maximization, set the attribute ModelSense:
# Set model sense
m.ModelSense = PDLP.MAXIMIZESolver parameters control termination criteria, logging, scaling, and restart behavior.
Below is a list of commonly used parameters, their internal keys, and descriptions.
| Alias | Internal Key | Type | Default | Description |
|---|---|---|---|---|
TimeLimit |
time_sec_limit |
float | 3600.0 |
Maximum wall-clock time in seconds. The solver terminates if the limit is reached. |
IterationLimit |
iteration_limit |
int | 2147483647 |
Maximum number of iterations. |
OutputFlag, LogToConsole |
verbose |
bool | False |
Enable (True) or disable (False) console logging output. |
TermCheckFreq |
termination_evaluation_frequency |
int | 200 |
Frequency (in iterations) at which termination conditions are evaluated. |
OptimalityNorm |
optimality_norm |
string | "l2" |
Norm for optimality criteria. Use "l2" for L2 norm or "linf" for infinity norm. |
OptimalityTol |
eps_optimal_relative |
float | 1e-4 |
Relative tolerance for optimality gap. Solver stops if the relative primal-dual gap ≤ this value. |
FeasibilityTol |
eps_feasible_relative |
float | 1e-4 |
Relative feasibility tolerance for primal/dual residuals. |
RuizIters |
l_inf_ruiz_iterations |
int | 10 |
Number of iterations for L∞ Ruiz scaling. Improves numerical conditioning. |
UsePCAlpha |
has_pock_chambolle_alpha |
bool | True |
Whether to use the Pock–Chambolle α step size adjustment. |
PCAlpha |
pock_chambolle_alpha |
float | 1.0 |
Value of the Pock–Chambolle α parameter. |
BoundObjRescaling |
bound_objective_rescaling |
bool | True |
Whether to rescale the objective vector during preprocessing. |
RestartArtificialThresh |
artificial_restart_threshold |
float | 0.36 |
Threshold for artificial restart. |
RestartSufficientReduction |
sufficient_reduction_for_restart |
float | 0.2 |
Sufficient reduction factor to justify a restart. |
RestartNecessaryReduction |
necessary_reduction_for_restart |
float | 0.5 |
Necessary reduction factor required for a restart. |
RestartKp |
k_p |
float | 0.99 |
Proportional coefficient for PID-controlled primal weight updates. |
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. |
FeasibilityPolishing |
feasibility_polishing |
bool | False |
Run feasibility polishing process. |
FeasibilityPolishingTol |
eps_feas_polish_relative |
float | 1e-6 |
Relative tolerance for primal/dual residual. |
They can be set in multiple ways:
# Method 1: single parameter
m.setParam("TimeLimit", 300)
m.setParam("FeasibilityTol", 1e-6)
# Method 2: multiple parameters
m.setParams(TimeLimit=300, FeasibilityTol=1e-6)
# Method 3: attribute-style access
m.Params.TimeLimit = 300
m.Params.FeasibilityTol = 1e-6After 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.
| 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). |
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. |
RelGap |
float | Relative primal-dual gap. |
X |
numpy.ndarray | Primal solution vector (x). May be None if no feasible solution was found. |
Pi |
numpy.ndarray | Dual solution vector (Lagrange multipliers). |
RC |
numpy.ndarray | Reduced Cost vector. |
IterCount |
int | Number of iterations performed. |
Runtime |
float | Total wall-clock runtime in seconds. |
RescalingTime |
float | Time spent on preprocessing and rescaling (seconds). |
RelPrimalResidual |
float | Relative primal residual. |
RelDualResidual |
float | Relative dual residual. |
MaxPrimalRayInfeas |
float | Maximum primal ray infeasibility (indicator for infeasibility). |
MaxDualRayInfeas |
float | Maximum dual ray infeasibility. |
PrimalRayLinObj |
float | Linear objective value along a primal ray (used in infeasibility detection). |
DualRayObj |
float | Objective value along a dual ray (used in unboundedness detection). |
All solution-related information can then be queried directly from the Model object:
m.optimize()
print("Status:", m.Status, "(code:", m.StatusCode, ")")
print("Primal objective:", m.ObjVal)
print("Dual objective:", m.DualObj)
print("Relative gap:", m.RelGap)
print("Iterations:", m.IterCount, " Runtime (s):", m.Runtime)
# Access solutions
print("Primal solution:", m.X)
print("Dual solution:", m.Pi)
print("Reduced cost:", m.RC)
# Check residuals
print("Primal residual:", m.RelPrimalResidual)
print("Dual residual:", m.RelDualResidual)cupdlpx supports warm starting from user-provided primal and/or dual solutions.
This allows resuming from a previous iterate or reusing solutions from a related instance, often reducing iterations needed to reach optimality.
# Warm starting solution
x_init = [1.0, 2.0]
pi_init = [1.0, -1.0, 0.0]
# Set warm start
m.setWarmStart(primal=x_init, dual=pi_init)
# Solve
m.optimize()Both primal and dual arguments are optional. You may specify only one of them if desired:
# Only provide primal start
m.setWarmStart(primal=x_init)
# Only provide dual start
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.
To clear existing warm-start values:
m.clearWarmStart()or
m.setWarmStart(primal=None, dual=None)