Skip to content

Commit bcde1df

Browse files
Manage benders subproblems memory in the master scip instance (#1111)
* Manage benders subproblems memory in the master scip instance * Update src/pyscipopt/scip.pxi Co-authored-by: João Dionísio <57299939+Joao-Dionisio@users.noreply.github.com> * Fix changelog --------- Co-authored-by: João Dionísio <57299939+Joao-Dionisio@users.noreply.github.com>
1 parent 45fd139 commit bcde1df

File tree

5 files changed

+58
-30
lines changed

5 files changed

+58
-30
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
- Implemented all binary operations between MatrixExpr and GenExpr
1616
- Fixed the type of @ matrix operation result from MatrixVariable to MatrixExpr.
1717
- Fixed the case for returning None from the nodeselect callback in Node Selector plugins.
18+
- Fixed segmentation fault during Benders decomposition cleanup caused by double-free bug
1819
### Changed
1920
- Add package extras for test dependencies in `pyproject.toml`
2021
- Speed up MatrixVariable.sum(axis=None) via quicksum
2122
- MatrixVariable now supports comparison with Expr
23+
- Benders subproblem memory is now automatically managed by the master Model - `freeBendersSubproblems()` is deprecated and no longer needed
2224
### Removed
2325

26+
2427
## 5.6.0 - 2025.08.26
2528
### Added
2629
- More support for AND-Constraints

src/pyscipopt/scip.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2119,6 +2119,8 @@ cdef class Model:
21192119
cdef _modelvars
21202120
# used to keep track of the number of event handlers generated
21212121
cdef int _generated_event_handlers_count
2122+
# store references to Benders subproblem Models for proper cleanup
2123+
cdef _benders_subproblems
21222124

21232125
@staticmethod
21242126
cdef create(SCIP* scip)

src/pyscipopt/scip.pxi

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2597,6 +2597,7 @@ cdef class Model:
25972597
self._freescip = True
25982598
self._modelvars = {}
25992599
self._generated_event_handlers_count = 0
2600+
self._benders_subproblems = [] # Keep references to Benders subproblem Models
26002601

26012602
if not createscip:
26022603
# if no SCIP instance should be created, then an empty Model object is created.
@@ -2668,10 +2669,29 @@ cdef class Model:
26682669
self.includeEventhdlr(event_handler, name, description)
26692670

26702671
def __dealloc__(self):
2672+
# Declare all C variables at the beginning for Cython compatibility
2673+
cdef SCIP_BENDERS** benders
2674+
cdef int nbenders
2675+
cdef int nsubproblems
2676+
cdef int i, j
2677+
26712678
# call C function directly, because we can no longer call this object's methods, according to
26722679
# http://docs.cython.org/src/reference/extension_types.html#finalization-dealloc
26732680
if self._scip is not NULL and self._freescip and PY_SCIP_CALL:
2674-
PY_SCIP_CALL( SCIPfree(&self._scip) )
2681+
# Free Benders subproblems before freeing the main SCIP instance
2682+
if self._benders_subproblems:
2683+
benders = SCIPgetBenders(self._scip)
2684+
nbenders = SCIPgetNActiveBenders(self._scip)
2685+
2686+
for i in range(nbenders):
2687+
nsubproblems = SCIPbendersGetNSubproblems(benders[i])
2688+
for j in range(nsubproblems):
2689+
PY_SCIP_CALL(SCIPfreeBendersSubproblem(self._scip, benders[i], j))
2690+
2691+
# Clear the references to allow Python GC to clean up the Model objects
2692+
self._benders_subproblems = []
2693+
2694+
PY_SCIP_CALL( SCIPfree(&self._scip) )
26752695

26762696
def __hash__(self):
26772697
return hash(<size_t>self._scip)
@@ -2700,6 +2720,7 @@ cdef class Model:
27002720
model = Model(createscip=False)
27012721
model._scip = scip
27022722
model._bestSol = Solution.create(scip, SCIPgetBestSol(scip))
2723+
model._benders_subproblems = [] # Initialize Benders subproblems list
27032724
return model
27042725

27052726
@property
@@ -8353,6 +8374,21 @@ cdef class Model:
83538374
PY_SCIP_CALL(SCIPcreateBendersDefault(self._scip, subprobs, nsubproblems))
83548375
benders = SCIPfindBenders(self._scip, "default")
83558376

8377+
# Free the temporary array (SCIPcreateBendersDefault copies the pointers internally)
8378+
free(subprobs)
8379+
8380+
# Store references to subproblem Models and transfer ownership to master.
8381+
# The master will free the Benders subproblems in its __dealloc__ method.
8382+
# Set _freescip = False on the subproblem Model(s) to prevent double-free
8383+
# if they are deallocated before the master.
8384+
if isdict:
8385+
self._benders_subproblems = list(subproblems.values())
8386+
for subprob in self._benders_subproblems:
8387+
(<Model>subprob)._freescip = False
8388+
else:
8389+
self._benders_subproblems = [subproblems]
8390+
(<Model>subproblems)._freescip = False
8391+
83568392
# activating the Benders' decomposition constraint handlers
83578393
self.setBoolParam("constraints/benderslp/active", True)
83588394
self.setBoolParam("constraints/benders/active", True)
@@ -8383,20 +8419,22 @@ cdef class Model:
83838419
benders[i], self._bestSol.sol, j, &infeasible, solvecip, NULL))
83848420

83858421
def freeBendersSubproblems(self):
8386-
"""Calls the free subproblem function for the Benders' decomposition.
8387-
This will free all subproblems for all decompositions. """
8388-
cdef SCIP_BENDERS** benders = SCIPgetBenders(self._scip)
8389-
cdef int nbenders = SCIPgetNActiveBenders(self._scip)
8390-
cdef int nsubproblems
8391-
cdef int i
8392-
cdef int j
8393-
8394-
# solving all subproblems from all Benders' decompositions
8395-
for i in range(nbenders):
8396-
nsubproblems = SCIPbendersGetNSubproblems(benders[i])
8397-
for j in range(nsubproblems):
8398-
PY_SCIP_CALL(SCIPfreeBendersSubproblem(self._scip, benders[i],
8399-
j))
8422+
"""Deprecated: This method is no longer needed.
8423+
8424+
Benders subproblems are now automatically managed and freed by the master
8425+
Model when it is deallocated. Calling this method explicitly has no effect.
8426+
8427+
.. deprecated:: 5.7.0
8428+
This method is deprecated and will be removed in a future version.
8429+
Subproblem memory management is now handled automatically.
8430+
"""
8431+
warnings.warn(
8432+
"freeBendersSubproblems() is deprecated and no longer needed. "
8433+
"Benders subproblems are automatically freed when the master Model "
8434+
"is deallocated.",
8435+
DeprecationWarning,
8436+
stacklevel=2
8437+
)
84008438

84018439
def updateBendersLowerbounds(self, lowerbounds, Benders benders=None):
84028440
"""

tests/test_benders.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,4 @@ def test_flpbenders():
101101

102102
master.printStatistics()
103103

104-
# since computeBestSolSubproblems() was called above, we need to free the
105-
# subproblems. This must happen after the solution is extracted, otherwise
106-
# the solution will be lost
107-
master.freeBendersSubproblems()
108-
109104
assert 5.61e+03 - 10e-6 < master.getObjVal() < 5.61e+03 + 10e-6

tests/test_customizedbenders.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,6 @@ def flpbenders_defcuts_test():
228228

229229
master.printStatistics()
230230

231-
# since the subproblems were setup and then solved, we need to free the
232-
# subproblems. This must happen after the solution is extracted, otherwise
233-
# the solution will be lost
234-
master.freeBendersSubproblems()
235-
236231
return master.getObjVal()
237232

238233
def flpbenders_customcuts_test():
@@ -278,11 +273,6 @@ def flpbenders_customcuts_test():
278273

279274
master.printStatistics()
280275

281-
# since the subproblems were setup and then solved, we need to free the
282-
# subproblems. This must happen after the solution is extracted, otherwise
283-
# the solution will be lost
284-
master.freeBendersSubproblems()
285-
286276
return master.getObjVal()
287277

288278
def flp_test():

0 commit comments

Comments
 (0)