From 9d4146728ea8c1a891873c6c4d274cc490e26845 Mon Sep 17 00:00:00 2001 From: Ishika Roy Date: Wed, 27 May 2026 04:51:18 +0000 Subject: [PATCH] add mip start attribute --- .../cuopt/cuopt/linear_programming/problem.py | 23 +++++++++++ .../linear_programming/test_python_API.py | 41 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/python/cuopt/cuopt/linear_programming/problem.py b/python/cuopt/cuopt/linear_programming/problem.py index 15b540313..5de10410c 100644 --- a/python/cuopt/cuopt/linear_programming/problem.py +++ b/python/cuopt/cuopt/linear_programming/problem.py @@ -106,6 +106,9 @@ class Variable: Value of the variable after solving. ReducedCost : float Reduced Cost after solving an LP problem. + MIPStart : float + Initial value (warm-start hint) for the variable. Defaults to NaN + (unset). Only used for a MIP problem. """ def __init__( @@ -124,6 +127,7 @@ def __init__( self.ReducedCost = float("nan") self.VariableType = vtype self.VariableName = vname + self.MIPStart = float("nan") def getIndex(self): """ @@ -199,6 +203,20 @@ def getVariableName(self): """ return self.VariableName + def setMIPStart(self, val): + """ + Sets the MIP start (initial primal solution hint) value for the + variable. Use ``float("nan")`` to unset. + """ + self.MIPStart = float(val) + + def getMIPStart(self): + """ + Returns the MIP start (initial primal solution hint) value of the + variable. Defaults to NaN when unset. + """ + return self.MIPStart + def __neg__(self): return LinearExpression([self], [-1.0], 0.0) @@ -1428,6 +1446,7 @@ def _to_data_model(self): self.lower_bound, self.upper_bound = np.zeros(n), np.zeros(n) self.var_type = np.empty(n, dtype="S1") self.var_names = [] + self.mip_start = np.empty(n, dtype=np.float64) for j in range(n): self.objective[j] = self.vars[j].getObjectiveCoefficient() @@ -1438,6 +1457,7 @@ def _to_data_model(self): if var_name == "": var_name = "C" + str(self.vars[j].index) self.var_names.append(var_name) + self.mip_start[j] = self.vars[j].MIPStart # Initialize datamodel dm.set_csr_constraint_matrix( @@ -1464,6 +1484,9 @@ def _to_data_model(self): dm.set_row_names(self.row_names) dm.set_problem_name(self.Name) + if self.mip_start.size > 0 and not np.all(np.isnan(self.mip_start)): + dm.set_initial_primal_solution(self.mip_start) + self.model = dm def update(self): diff --git a/python/cuopt/cuopt/tests/linear_programming/test_python_API.py b/python/cuopt/cuopt/tests/linear_programming/test_python_API.py index afc1ec699..5e7ab6e94 100644 --- a/python/cuopt/cuopt/tests/linear_programming/test_python_API.py +++ b/python/cuopt/cuopt/tests/linear_programming/test_python_API.py @@ -482,6 +482,47 @@ def test_warm_start(): ) +def test_mip_start(): + # Build a small MIP and feed it an (already-optimal) MIP start through + # the new Variable.MIPStart attribute. We verify: + # - the attribute defaults to NaN + # - setMIPStart / direct assignment both work + # - the values reach the data model via set_initial_primal_solution + # - solving still produces the optimal result + # - leaving every MIPStart as NaN does not set an initial primal sol + prob = Problem("MIP_start") + x = prob.addVariable(lb=0, ub=10, vtype=INTEGER, name="x") + y = prob.addVariable(lb=0, ub=10, vtype=INTEGER, name="y") + + assert math.isnan(x.getMIPStart()) + assert math.isnan(y.MIPStart) + + prob.addConstraint(x + y <= 10, name="c1") + prob.addConstraint(x - y >= 0, name="c2") + prob.setObjective(x + 2 * y, sense=MAXIMIZE) + + x.setMIPStart(5) + y.MIPStart = 5.0 + + assert x.getMIPStart() == 5.0 + assert y.getMIPStart() == 5.0 + + prob._to_data_model() + initial_primal = prob.model.get_initial_primal_solution() + assert len(initial_primal) == 2 + assert initial_primal[0] == pytest.approx(5.0) + assert initial_primal[1] == pytest.approx(5.0) + + settings = SolverSettings() + settings.set_parameter("time_limit", 5) + prob.solve(settings) + + assert prob.Status.name == "Optimal" + assert prob.ObjValue == pytest.approx(15) + assert x.Value == pytest.approx(5) + assert y.Value == pytest.approx(5) + + def test_problem_update(): prob = Problem() x1 = prob.addVariable(vtype=INTEGER, lb=0, name="x1")