11# #@file scip.pxi
22# @brief holding functions in python that reference the SCIP public functions included in scip.pxd
3- import weakref
43from os.path import abspath
54from os.path import splitext
65import os
@@ -2821,6 +2820,7 @@ cdef class Model:
28212820 self ._modelconss = {}
28222821 self ._generated_event_handlers_count = 0
28232822 self ._benders_subproblems = [] # Keep references to Benders subproblem Models
2823+ self ._plugins = [] # Keep references to plugins to break cycles in __dealloc__
28242824 self ._iis = NULL
28252825
28262826 if not createscip:
@@ -2892,15 +2892,30 @@ cdef class Model:
28922892
28932893 self .includeEventhdlr(event_handler, name, description)
28942894
2895+ def __del__ (self ):
2896+ """ Free SCIP before the cyclic GC clears Python references.
2897+
2898+ __del__ (tp_finalize) runs before tp_clear, so _plugins and other
2899+ Python attributes are still intact. This lets SCIPfree call plugin
2900+ callbacks (consfree, eventexit, etc.) safely. The GC handles breaking
2901+ the circular references afterwards via tp_clear.
2902+ """
2903+ self ._free_scip_instance()
2904+
28952905 def __dealloc__ (self ):
2896- # Declare all C variables at the beginning for Cython compatibility
2906+ # Safety net: if __del__ didn't run (e.g. interpreter shutdown),
2907+ # free SCIP here. Plugin callbacks may not work at this point since
2908+ # tp_clear may have already cleared _plugins.
2909+ if self ._scip is not NULL and self ._freescip and PY_SCIP_CALL:
2910+ PY_SCIP_CALL( SCIPfree(& self ._scip) )
2911+
2912+ cdef _free_scip_instance(self ):
2913+ """ Free the SCIP instance. Does not touch Python object references."""
28972914 cdef SCIP_BENDERS** benders
28982915 cdef int nbenders
28992916 cdef int nsubproblems
29002917 cdef int i, j
29012918
2902- # call C function directly, because we can no longer call this object's methods, according to
2903- # http://docs.cython.org/src/reference/extension_types.html#finalization-dealloc
29042919 if self ._scip is not NULL and self ._freescip and PY_SCIP_CALL:
29052920 # Free Benders subproblems before freeing the main SCIP instance
29062921 if self ._benders_subproblems:
@@ -2912,20 +2927,17 @@ cdef class Model:
29122927 for j in range (nsubproblems):
29132928 PY_SCIP_CALL(SCIPfreeBendersSubproblem(self ._scip, benders[i], j))
29142929
2915- # Clear the references to allow Python GC to clean up the Model objects
2916- self ._benders_subproblems = []
2930+ SCIPfree(& self ._scip)
2931+ self ._scip = NULL
2932+ self ._freescip = False
29172933
2918- # Invalidate all variable and constraint pointers before freeing SCIP. See issue #604.
2934+ # Invalidate all variable and constraint pointers after freeing SCIP. See issue #604.
29192935 if self ._modelvars:
29202936 for var in self ._modelvars.values():
29212937 (< Variable> var).scip_var = NULL
2922- self ._modelvars = {}
29232938 if self ._modelconss:
29242939 for cons in self ._modelconss.values():
29252940 (< Constraint> cons).scip_cons = NULL
2926- self ._modelconss = {}
2927-
2928- PY_SCIP_CALL( SCIPfree(& self ._scip) )
29292941
29302942 def __hash__ (self ):
29312943 return hash (< size_t> self ._scip)
@@ -3070,6 +3082,21 @@ cdef class Model:
30703082 """ Frees problem and solution process data."""
30713083 PY_SCIP_CALL(SCIPfreeProb(self ._scip))
30723084
3085+ def free (self ):
3086+ """ Explicitly free SCIP instance and break circular references with plugins.
3087+
3088+ This method should be called when you want to ensure deterministic cleanup.
3089+ All SCIP callbacks (consfree, eventexit, etc.) are executed during this call.
3090+ After calling this method, the Model object should not be used anymore.
3091+ """
3092+ self ._free_scip_instance()
3093+
3094+ # Break circular references with plugins
3095+ if self ._plugins:
3096+ for plugin in self ._plugins:
3097+ plugin.model = None
3098+ self ._plugins = []
3099+
30733100 def freeTransform (self ):
30743101 """ Frees all solution process data including presolving and
30753102 transformed problem, only original problem is kept."""
@@ -9191,9 +9218,9 @@ cdef class Model:
91919218 PyEventDelete,
91929219 PyEventExec,
91939220 < SCIP_EVENTHDLRDATA* > eventhdlr))
9194- eventhdlr.model = < Model > weakref.proxy( self )
9221+ eventhdlr.model = self
91959222 eventhdlr.name = name
9196- Py_INCREF (eventhdlr)
9223+ self ._plugins.append (eventhdlr)
91979224
91989225 def includePricer (self , Pricer pricer , name , desc , priority = 1 , delay = True ):
91999226 """
@@ -9223,8 +9250,8 @@ cdef class Model:
92239250 cdef SCIP_PRICER* scip_pricer
92249251 scip_pricer = SCIPfindPricer(self ._scip, n)
92259252 PY_SCIP_CALL(SCIPactivatePricer(self ._scip, scip_pricer))
9226- pricer.model = < Model > weakref.proxy( self )
9227- Py_INCREF (pricer)
9253+ pricer.model = self
9254+ self ._plugins.append (pricer)
92289255 pricer.scip_pricer = scip_pricer
92299256
92309257 def includeConshdlr (self , Conshdlr conshdlr , name , desc , sepapriority = 0 ,
@@ -9283,9 +9310,9 @@ cdef class Model:
92839310 PyConsActive, PyConsDeactive, PyConsEnable, PyConsDisable, PyConsDelvars, PyConsPrint, PyConsCopy,
92849311 PyConsParse, PyConsGetvars, PyConsGetnvars, PyConsGetdivebdchgs, PyConsGetPermSymGraph, PyConsGetSignedPermSymGraph,
92859312 < SCIP_CONSHDLRDATA* > conshdlr))
9286- conshdlr.model = < Model > weakref.proxy( self )
9313+ conshdlr.model = self
92879314 conshdlr.name = name
9288- Py_INCREF (conshdlr)
9315+ self ._plugins.append (conshdlr)
92899316
92909317 def deactivatePricer (self , Pricer pricer ):
92919318 """
@@ -9450,8 +9477,8 @@ cdef class Model:
94509477 d = str_conversion(desc)
94519478 PY_SCIP_CALL(SCIPincludePresol(self ._scip, n, d, priority, maxrounds, timing, PyPresolCopy, PyPresolFree, PyPresolInit,
94529479 PyPresolExit, PyPresolInitpre, PyPresolExitpre, PyPresolExec, < SCIP_PRESOLDATA* > presol))
9453- presol.model = < Model > weakref.proxy( self )
9454- Py_INCREF (presol)
9480+ presol.model = self
9481+ self ._plugins.append (presol)
94559482
94569483 def includeSepa (self , Sepa sepa , name , desc , priority = 0 , freq = 10 , maxbounddist = 1.0 , usessubscip = False , delay = False ):
94579484 """
@@ -9493,9 +9520,9 @@ cdef class Model:
94939520 d = str_conversion(desc)
94949521 PY_SCIP_CALL(SCIPincludeSepa(self ._scip, n, d, priority, freq, maxbounddist, usessubscip, delay, PySepaCopy, PySepaFree,
94959522 PySepaInit, PySepaExit, PySepaInitsol, PySepaExitsol, PySepaExeclp, PySepaExecsol, < SCIP_SEPADATA* > sepa))
9496- sepa.model = < Model > weakref.proxy( self )
9523+ sepa.model = self
94979524 sepa.name = name
9498- Py_INCREF (sepa)
9525+ self ._plugins.append (sepa)
94999526
95009527 def includeReader (self , Reader reader , name , desc , ext ):
95019528 """
@@ -9518,9 +9545,9 @@ cdef class Model:
95189545 e = str_conversion(ext)
95199546 PY_SCIP_CALL(SCIPincludeReader(self ._scip, n, d, e, PyReaderCopy, PyReaderFree,
95209547 PyReaderRead, PyReaderWrite, < SCIP_READERDATA* > reader))
9521- reader.model = < Model > weakref.proxy( self )
9548+ reader.model = self
95229549 reader.name = name
9523- Py_INCREF (reader)
9550+ self ._plugins.append (reader)
95249551
95259552 def includeProp (self , Prop prop , name , desc , presolpriority , presolmaxrounds ,
95269553 proptiming , presoltiming = SCIP_PRESOLTIMING_FAST, priority = 1 , freq = 1 , delay = True ):
@@ -9560,8 +9587,8 @@ cdef class Model:
95609587 PyPropInitpre, PyPropExitpre, PyPropInitsol, PyPropExitsol,
95619588 PyPropPresol, PyPropExec, PyPropResProp,
95629589 < SCIP_PROPDATA* > prop))
9563- prop.model = < Model > weakref.proxy( self )
9564- Py_INCREF (prop)
9590+ prop.model = self
9591+ self ._plugins.append (prop)
95659592
95669593 def includeHeur (self , Heur heur , name , desc , dispchar , priority = 10000 , freq = 1 , freqofs = 0 ,
95679594 maxdepth = - 1 , timingmask = SCIP_HEURTIMING_BEFORENODE, usessubscip = False ):
@@ -9605,9 +9632,9 @@ cdef class Model:
96059632 PyHeurCopy, PyHeurFree, PyHeurInit, PyHeurExit,
96069633 PyHeurInitsol, PyHeurExitsol, PyHeurExec,
96079634 < SCIP_HEURDATA* > heur))
9608- heur.model = < Model > weakref.proxy( self )
9635+ heur.model = self
96099636 heur.name = name
9610- Py_INCREF (heur)
9637+ self ._plugins.append (heur)
96119638
96129639 def includeIISfinder (self , IISfinder iisfinder , name , desc , priority = 10000 , freq = 1 ):
96139640 """
@@ -9639,7 +9666,7 @@ cdef class Model:
96399666
96409667 scip_iisfinder = SCIPfindIISfinder(self ._scip, nam)
96419668 iisfinder.name = name
9642- Py_INCREF (iisfinder)
9669+ self ._plugins.append (iisfinder)
96439670 iisfinder.scip_iisfinder = scip_iisfinder
96449671
96459672 def generateIIS (self ):
@@ -9697,10 +9724,9 @@ cdef class Model:
96979724 des = str_conversion(desc)
96989725 PY_SCIP_CALL(SCIPincludeRelax(self ._scip, nam, des, priority, freq, PyRelaxCopy, PyRelaxFree, PyRelaxInit, PyRelaxExit,
96999726 PyRelaxInitsol, PyRelaxExitsol, PyRelaxExec, < SCIP_RELAXDATA* > relax))
9700- relax.model = < Model > weakref.proxy( self )
9727+ relax.model = self
97019728 relax.name = name
9702-
9703- Py_INCREF(relax)
9729+ self ._plugins.append(relax)
97049730
97059731 def includeCutsel (self , Cutsel cutsel , name , desc , priority ):
97069732 """
@@ -9725,8 +9751,8 @@ cdef class Model:
97259751 priority, PyCutselCopy, PyCutselFree, PyCutselInit, PyCutselExit,
97269752 PyCutselInitsol, PyCutselExitsol, PyCutselSelect,
97279753 < SCIP_CUTSELDATA* > cutsel))
9728- cutsel.model = < Model > weakref.proxy( self )
9729- Py_INCREF (cutsel)
9754+ cutsel.model = self
9755+ self ._plugins.append (cutsel)
97309756
97319757 def includeBranchrule (self , Branchrule branchrule , name , desc , priority , maxdepth , maxbounddist ):
97329758 """
@@ -9757,8 +9783,8 @@ cdef class Model:
97579783 PyBranchruleCopy, PyBranchruleFree, PyBranchruleInit, PyBranchruleExit,
97589784 PyBranchruleInitsol, PyBranchruleExitsol, PyBranchruleExeclp, PyBranchruleExecext,
97599785 PyBranchruleExecps, < SCIP_BRANCHRULEDATA* > branchrule))
9760- branchrule.model = < Model > weakref.proxy( self )
9761- Py_INCREF (branchrule)
9786+ branchrule.model = self
9787+ self ._plugins.append (branchrule)
97629788
97639789 def includeNodesel (self , Nodesel nodesel , name , desc , stdpriority , memsavepriority ):
97649790 """
@@ -9785,8 +9811,8 @@ cdef class Model:
97859811 PyNodeselCopy, PyNodeselFree, PyNodeselInit, PyNodeselExit,
97869812 PyNodeselInitsol, PyNodeselExitsol, PyNodeselSelect, PyNodeselComp,
97879813 < SCIP_NODESELDATA* > nodesel))
9788- nodesel.model = < Model > weakref.proxy( self )
9789- Py_INCREF (nodesel)
9814+ nodesel.model = self
9815+ self ._plugins.append (nodesel)
97909816
97919817 def includeBenders (self , Benders benders , name , desc , priority = 1 , cutlp = True , cutpseudo = True , cutrelax = True ,
97929818 shareaux = False ):
@@ -9825,10 +9851,10 @@ cdef class Model:
98259851 < SCIP_BENDERSDATA* > benders))
98269852 cdef SCIP_BENDERS* scip_benders
98279853 scip_benders = SCIPfindBenders(self ._scip, n)
9828- benders.model = < Model > weakref.proxy( self )
9854+ benders.model = self
98299855 benders.name = name
98309856 benders._benders = scip_benders
9831- Py_INCREF (benders)
9857+ self ._plugins.append (benders)
98329858
98339859 def includeBenderscut (self , Benders benders , Benderscut benderscut , name , desc , priority = 1 , islpcut = True ):
98349860 """
@@ -9864,11 +9890,10 @@ cdef class Model:
98649890
98659891 cdef SCIP_BENDERSCUT* scip_benderscut
98669892 scip_benderscut = SCIPfindBenderscut(_benders, n)
9867- benderscut.model = < Model > weakref.proxy( self )
9893+ benderscut.model = self
98689894 benderscut.benders = benders
98699895 benderscut.name = name
9870- # TODO: It might be necessary in increment the reference to benders i.e Py_INCREF(benders)
9871- Py_INCREF(benderscut)
9896+ self ._plugins.append(benderscut)
98729897
98739898 def getLPBranchCands (self ):
98749899 """
0 commit comments