Skip to content

Commit 067e630

Browse files
Raise error when freeReoptSolve is called without reoptimization enabled (#1209)
* Raise clear error when freeReoptSolve is called without reoptimization enabled (#624) * Add test for freeReoptSolve requiring reoptimization enabled
1 parent e3b2892 commit 067e630

4 files changed

Lines changed: 42 additions & 0 deletions

File tree

src/pyscipopt/scip.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,6 +1550,7 @@ cdef extern from "scip/scip.h":
15501550
SCIP_RETCODE SCIPfreeReoptSolve(SCIP* scip)
15511551
SCIP_RETCODE SCIPchgReoptObjective(SCIP* scip, SCIP_OBJSENSE objsense, SCIP_VAR** vars, SCIP_Real* coefs, int nvars)
15521552
SCIP_RETCODE SCIPenableReoptimization(SCIP* scip, SCIP_Bool enable)
1553+
SCIP_Bool SCIPisReoptEnabled(SCIP* scip)
15531554

15541555
BMS_BLKMEM* SCIPblkmem(SCIP* scip)
15551556

src/pyscipopt/scip.pxi

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3973,6 +3973,17 @@ cdef class Model:
39733973
"""
39743974
PY_SCIP_CALL(SCIPenableReoptimization(self._scip, enable))
39753975

3976+
def isReoptEnabled(self):
3977+
"""
3978+
Returns whether reoptimization is enabled.
3979+
3980+
Returns
3981+
-------
3982+
bool
3983+
3984+
"""
3985+
return SCIPisReoptEnabled(self._scip)
3986+
39763987
def lpiGetIterations(self):
39773988
"""
39783989
Get the iteration count of the last solved LP.
@@ -11950,6 +11961,10 @@ cdef class Model:
1195011961
def freeReoptSolve(self):
1195111962
"""Frees all solution process data and prepares for reoptimization."""
1195211963

11964+
if not SCIPisReoptEnabled(self._scip):
11965+
raise ValueError("freeReoptSolve requires reoptimization to be enabled. "
11966+
"Call enableReoptimization() before solving.")
11967+
1195311968
if self.getStage() not in [SCIP_STAGE_INIT,
1195411969
SCIP_STAGE_PROBLEM,
1195511970
SCIP_STAGE_TRANSFORMED,

src/pyscipopt/scip.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,6 +1454,7 @@ class Model:
14541454
def isObjChangedProbing(self) -> Incomplete: ...
14551455
def isObjIntegral(self) -> Incomplete: ...
14561456
def isPositive(self, val: Incomplete) -> Incomplete: ...
1457+
def isReoptEnabled(self) -> bool: ...
14571458
def isZero(self, value: Incomplete) -> Incomplete: ...
14581459
def lpiGetIterations(self) -> Incomplete: ...
14591460
def markDoNotAggrVar(self, var: Incomplete) -> Incomplete: ...

tests/test_reopt.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,31 @@ def test_reopt_many_variables_sparse_objective(self):
7373
self.assertAlmostEqual(m.getVal(vars[50]), 0.0)
7474
self.assertAlmostEqual(m.getVal(vars[99]), 0.0)
7575

76+
def test_freeReoptSolve_requires_reoptimization_enabled(self):
77+
"""freeReoptSolve must raise ValueError instead of crashing when
78+
reoptimization was not enabled (see issue #624)."""
79+
m = Model()
80+
m.hideOutput()
81+
self.assertFalse(m.isReoptEnabled())
82+
83+
x = m.addVar(name="x", vtype="I", ub=10)
84+
m.addCons(x >= 3)
85+
m.setObjective(x)
86+
m.optimize()
87+
88+
with self.assertRaises(ValueError):
89+
m.freeReoptSolve()
90+
91+
m2 = Model()
92+
m2.enableReoptimization()
93+
self.assertTrue(m2.isReoptEnabled())
94+
m2.hideOutput()
95+
y = m2.addVar(name="y", vtype="I", ub=10)
96+
m2.addCons(y >= 3)
97+
m2.setObjective(y)
98+
m2.optimize()
99+
m2.freeReoptSolve()
100+
76101
def test_reopt_zero_objective(self):
77102
"""Test reoptimization with zero objective (no variables, all coefficients zero)."""
78103
m = Model()

0 commit comments

Comments
 (0)