Skip to content

Commit bc24757

Browse files
improve chgReoptObjective performance (#1178)
* improve chgReoptObjective performance * add type check * use _VarArray and minimize changes compares to master --------- Co-authored-by: João Dionísio <57299939+Joao-Dionisio@users.noreply.github.com>
1 parent 16580f6 commit bc24757

File tree

3 files changed

+85
-18
lines changed

3 files changed

+85
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
- MatrixExpr and MatrixExprCons use `__array_ufunc__` protocol to control all numpy.ufunc inputs and outputs
2626
- Set `__array_priority__` for MatrixExpr and MatrixExprCons
2727
- changed addConsNode() and addConsLocal() to mirror addCons() and accept ExprCons instead of Constraint
28+
- Improved `chgReoptObjective()` performance
2829
### Removed
2930

3031
## 6.0.0 - 2025.11.28

src/pyscipopt/scip.pxi

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11852,12 +11852,12 @@ cdef class Model:
1185211852

1185311853
def chgReoptObjective(self, coeffs, sense = 'minimize'):
1185411854
"""
11855-
Establish the objective function as a linear expression.
11855+
Change the objective function for reoptimization.
1185611856
1185711857
Parameters
1185811858
----------
11859-
coeffs : list of float
11860-
the coefficients
11859+
coeffs : Expr
11860+
the coefficients as a linear expression
1186111861
sense : str
1186211862
the objective sense (Default value = 'minimize')
1186311863
@@ -11866,7 +11866,6 @@ cdef class Model:
1186611866
cdef int nvars
1186711867
cdef SCIP_Real* _coeffs
1186811868
cdef SCIP_OBJSENSE objsense
11869-
cdef SCIP_Real coef
1187011869
cdef int i
1187111870
cdef _VarArray wrapper
1187211871

@@ -11884,24 +11883,27 @@ cdef class Model:
1188411883
if coeffs[CONST] != 0.0:
1188511884
raise ValueError("Constant offsets in objective are not supported!")
1188611885

11887-
vars = SCIPgetOrigVars(self._scip)
11888-
nvars = SCIPgetNOrigVars(self._scip)
11889-
_coeffs = <SCIP_Real*> malloc(nvars * sizeof(SCIP_Real))
11886+
nvars = len(coeffs.terms) - (CONST in coeffs.terms)
1189011887

11891-
for i in range(nvars):
11892-
_coeffs[i] = 0.0
11888+
if nvars == 0:
11889+
PY_SCIP_CALL(SCIPchgReoptObjective(self._scip, objsense, NULL, NULL, 0))
11890+
return
1189311891

11892+
_coeffs = <SCIP_Real*> malloc(nvars * sizeof(SCIP_Real))
11893+
vars = <SCIP_VAR**> malloc(nvars * sizeof(SCIP_VAR*))
11894+
11895+
i = 0
1189411896
for term, coef in coeffs.terms.items():
1189511897
# avoid CONST term of Expr
1189611898
if term != CONST:
11897-
assert len(term) == 1
11898-
for i in range(nvars):
11899-
wrapper = _VarArray(term[0])
11900-
if vars[i] == wrapper.ptr[0]:
11901-
_coeffs[i] = coef
11899+
wrapper = _VarArray(term[0])
11900+
vars[i] = wrapper.ptr[0]
11901+
_coeffs[i] = coef
11902+
i += 1
1190211903

11903-
PY_SCIP_CALL(SCIPchgReoptObjective(self._scip, objsense, vars, &_coeffs[0], nvars))
11904+
PY_SCIP_CALL(SCIPchgReoptObjective(self._scip, objsense, vars, _coeffs, nvars))
1190411905

11906+
free(vars)
1190511907
free(_coeffs)
1190611908

1190711909
def chgVarBranchPriority(self, Variable var, priority):

tests/test_reopt.py

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import unittest
2-
from pyscipopt import Model
2+
from pyscipopt import Model, quicksum, Expr
3+
34

45
class ReoptimizationTest(unittest.TestCase):
56

67
def test_reopt(self):
7-
8+
"""Test basic reoptimization."""
89
m = Model()
910
m.enableReoptimization()
1011

1112
x = m.addVar(name="x", ub=5)
1213
y = m.addVar(name="y", lb=-2, ub=10)
1314

14-
1515
m.addCons(2 * x + y >= 8)
1616
m.setObjective(x + y)
1717
m.optimize()
@@ -31,6 +31,70 @@ def test_reopt(self):
3131
self.assertEqual(m.getVal(x), 3.0)
3232
self.assertEqual(m.getVal(y), 3.0)
3333

34+
def test_reopt_maximize(self):
35+
"""Test reoptimization with maximize sense."""
36+
m = Model()
37+
m.enableReoptimization()
38+
m.hideOutput()
39+
40+
x = m.addVar(name="x", lb=0, ub=10)
41+
y = m.addVar(name="y", lb=0, ub=10)
42+
43+
m.addCons(x + y <= 15)
44+
m.setObjective(x + y, sense="maximize")
45+
m.optimize()
46+
47+
self.assertAlmostEqual(m.getObjVal(), 15.0)
48+
49+
m.freeReoptSolve()
50+
m.chgReoptObjective(x, sense="maximize")
51+
m.optimize()
52+
53+
self.assertAlmostEqual(m.getVal(x), 10.0)
54+
55+
def test_reopt_many_variables_sparse_objective(self):
56+
"""Test with many variables but only few in the new objective."""
57+
m = Model()
58+
m.enableReoptimization()
59+
m.hideOutput()
60+
61+
n_vars = 100
62+
vars = [m.addVar(name=f"x_{i}", lb=0, ub=10) for i in range(n_vars)]
63+
64+
m.addCons(quicksum(vars) >= 50)
65+
m.setObjective(quicksum(vars))
66+
m.optimize()
67+
68+
m.freeReoptSolve()
69+
m.chgReoptObjective(vars[0] + vars[50] + vars[99])
70+
m.optimize()
71+
72+
self.assertAlmostEqual(m.getVal(vars[0]), 0.0)
73+
self.assertAlmostEqual(m.getVal(vars[50]), 0.0)
74+
self.assertAlmostEqual(m.getVal(vars[99]), 0.0)
75+
76+
def test_reopt_zero_objective(self):
77+
"""Test reoptimization with zero objective (no variables, all coefficients zero)."""
78+
m = Model()
79+
m.enableReoptimization()
80+
m.hideOutput()
81+
82+
x = m.addVar(name="x", lb=0, ub=10)
83+
y = m.addVar(name="y", lb=0, ub=10)
84+
85+
m.addCons(x + y >= 5)
86+
m.setObjective(x + y)
87+
m.optimize()
88+
89+
self.assertAlmostEqual(m.getObjVal(), 5.0)
90+
91+
m.freeReoptSolve()
92+
m.chgReoptObjective(Expr())
93+
m.optimize()
94+
95+
self.assertGreaterEqual(m.getVal(x) + m.getVal(y), 5.0)
96+
self.assertAlmostEqual(m.getObjVal(), 0.0)
97+
3498

3599
if __name__ == '__main__':
36100
unittest.main()

0 commit comments

Comments
 (0)