Python Interface for cuPDLPx

March 17, 2026 · View on GitHub

License GitHub release PyPI version arXiv arXiv

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.

Installation

Requirements

  • 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

SpMV backend is selected automatically at compile time based on cuSPARSE version:

  • cusparseSpMV — CUDA 12.4 – 13.1 (cuSPARSE < 12.7.3)
  • cusparseSpMVOp — CUDA 13.1 Update 1+ (cuSPARSE ≥ 12.7.3)

Install

Install from PyPI:

pip install cupdlpx

Or build from source:

git clone https://github.com/MIT-Lu-Lab/cuPDLPx.git
cd cuPDLPx
pip install .

Quick Start

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)

Modeling

The Model class represents a linear programming problem of the form:

mincx+c0s.t.  Axu,lbxub.\min c^\top x + c_0 \quad \text{s.t.} \; \ell \le A x \le u, \quad \text{lb} \le x \le \text{ub}.

Arguments

  • 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.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_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.

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)

Model Sense

By default, cupdlpx solves minimization problems.

To switch between minimization and maximization, set the attribute ModelSense:

# Set model sense
m.ModelSense = PDLP.MAXIMIZE

Parameters

Solver parameters control termination criteria, logging, scaling, and restart behavior.

Below is a list of commonly used parameters, their internal keys, and descriptions.

AliasInternal KeyTypeDefaultDescription
TimeLimittime_sec_limitfloat3600.0Maximum wall-clock time in seconds. The solver terminates if the limit is reached.
IterationLimititeration_limitint2147483647Maximum number of iterations.
OutputFlag, LogToConsoleverboseboolFalseEnable (True) or disable (False) console logging output.
TermCheckFreqtermination_evaluation_frequencyint200Frequency (in iterations) at which termination conditions are evaluated.
OptimalityNormoptimality_normstring"l2"Norm for optimality criteria. Use "l2" for L2 norm or "linf" for infinity norm.
OptimalityToleps_optimal_relativefloat1e-4Relative tolerance for optimality gap. Solver stops if the relative primal-dual gap ≤ this value.
FeasibilityToleps_feasible_relativefloat1e-4Relative feasibility tolerance for primal/dual residuals.
RuizItersl_inf_ruiz_iterationsint10Number of iterations for L∞ Ruiz scaling. Improves numerical conditioning.
UsePCAlphahas_pock_chambolle_alphaboolTrueWhether to use the Pock–Chambolle α step size adjustment.
PCAlphapock_chambolle_alphafloat1.0Value of the Pock–Chambolle α parameter.
BoundObjRescalingbound_objective_rescalingboolTrueWhether to rescale the objective vector during preprocessing.
RestartArtificialThreshartificial_restart_thresholdfloat0.36Threshold for artificial restart.
RestartSufficientReductionsufficient_reduction_for_restartfloat0.2Sufficient reduction factor to justify a restart.
RestartNecessaryReductionnecessary_reduction_for_restartfloat0.5Necessary reduction factor required for a restart.
RestartKpk_pfloat0.99Proportional coefficient for PID-controlled primal weight updates.
ReflectionCoeffreflection_coefficientfloat1.0Reflection coefficient.
SVMaxItersv_max_iterint5000Maximum number of iterations for the power method
SVTolsv_tolfloat1e-4Termination tolerance for the power method
PresolvepresolvefloatTrueWhether to use presolve.
FeasibilityPolishingfeasibility_polishingboolFalseRun feasibility polishing process.
FeasibilityPolishingToleps_feas_polish_relativefloat1e-6Relative 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-6

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.

Attribute Reference

AttributeTypeDescription
StatusstrHuman-readable solver status ("OPTIMAL", "INFEASIBLE", "UNBOUNDED", "TIME_LIMIT", etc.).
StatusCodeintNumeric status code (OPTIMAL=1, INFEASIBLE=2, UNBOUNDED=3, ITERATION_LIMIT=4, TIME_LIMIT=5, UNSPECIFIED=-1).
ObjValfloatPrimal objective value at termination (sign-adjusted according to ModelSense).
DualObjfloatDual objective value at termination.
GapfloatAbsolute primal-dual gap.
RelGapfloatRelative primal-dual gap.
Xnumpy.ndarrayPrimal solution vector (x). May be None if no feasible solution was found.
Pinumpy.ndarrayDual solution vector (Lagrange multipliers).
RCnumpy.ndarrayReduced Cost vector.
IterCountintNumber of iterations performed.
RuntimefloatTotal wall-clock runtime in seconds.
RescalingTimefloatTime spent on preprocessing and rescaling (seconds).
RelPrimalResidualfloatRelative primal residual.
RelDualResidualfloatRelative dual residual.
MaxPrimalRayInfeasfloatMaximum primal ray infeasibility (indicator for infeasibility).
MaxDualRayInfeasfloatMaximum dual ray infeasibility.
PrimalRayLinObjfloatLinear objective value along a primal ray (used in infeasibility detection).
DualRayObjfloatObjective 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)

Warm Start

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)