From b8c0821261e50f9d48a622e86559f0deebd11f14 Mon Sep 17 00:00:00 2001 From: Adam Jemielita Date: Sat, 31 Oct 2026 19:09:31 +0100 Subject: [PATCH 1/4] extract duplicated code blocks to helper functions --- src/pyscipopt/scip.pxd | 6 ++ src/pyscipopt/scip.pxi | 183 ++++++++++++++--------------------------- 2 files changed, 70 insertions(+), 119 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index c5053ebba..21bb7d325 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -2330,6 +2330,12 @@ cdef class Model: cdef _getOrCreateCons(self, SCIP_CONS* scip_cons) cdef _getOrCreateVar(self, SCIP_VAR* scip_var) cdef _free_scip_instance(self) + + cdef _ensureFreshBestSol(self) + cdef SCIP_CONS* _getTransformedLogicorCons(self, Constraint logicor_cons) + cdef SCIP_EVENTHDLR* _getEventhdlr(self, object eventhdlr) + cdef SCIP_BENDERS* _getBenders(self, object benders) + cdef SCIP_HEUR* _getHeur(self, object heur) @staticmethod cdef create(SCIP* scip) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 214b8bad5..0f49f5997 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -7083,6 +7083,20 @@ cdef class Model: PY_SCIP_CALL(SCIPaddCoefLogicor(self._scip, logicor_cons.scip_cons, var.scip_var)) + cdef SCIP_CONS* _getTransformedLogicorCons(self, Constraint logicor_cons): + """Retrieve the transformed logicor constraint from the original constraint.""" + cdef SCIP_CONS* transcons + + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(logicor_cons.scip_cons))).decode('UTF-8') + if not constype == 'logicor': + raise Warning(f"dual solution values not available for constraints of type {constype}") + + if logicor_cons.isOriginal(): + PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, logicor_cons.scip_cons, &transcons)) + return transcons + + return logicor_cons.scip_cons + def getDualsolLogicor(self, Constraint logicor_cons): """ Gets the dual solution of a logicor constraint in the current LP. @@ -7098,18 +7112,9 @@ cdef class Model: Zero if presolve removed the transformed constraint. """ - cdef SCIP_CONS* transcons - - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(logicor_cons.scip_cons))).decode('UTF-8') - if not constype == 'logicor': - raise Warning("dual solution values not available for constraints of type ", constype) - - if logicor_cons.isOriginal(): - PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, logicor_cons.scip_cons, &transcons)) - if transcons == NULL: - return 0.0 - else: - transcons = logicor_cons.scip_cons + cdef SCIP_CONS* transcons = self._getTransformedLogicorCons(logicor_cons) + if transcons == NULL: + return 0.0 return SCIPgetDualsolLogicor(self._scip, transcons) @@ -7128,18 +7133,9 @@ cdef class Model: Zero if presolve removed the transformed constraint. """ - cdef SCIP_CONS* transcons - - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(logicor_cons.scip_cons))).decode('UTF-8') - if not constype == 'logicor': - raise Warning("dual solution values not available for constraints of type ", constype) - - if logicor_cons.isOriginal(): - PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, logicor_cons.scip_cons, &transcons)) - if transcons == NULL: - return 0.0 - else: - transcons = logicor_cons.scip_cons + cdef SCIP_CONS* transcons = self._getTransformedLogicorCons(logicor_cons) + if transcons == NULL: + return 0.0 return SCIPgetDualfarkasLogicor(self._scip, transcons) @@ -9504,6 +9500,12 @@ cdef class Model: stacklevel=2 ) + cdef SCIP_BENDERS* _getBenders(self, object benders): + """Return the selected Benders' decomposition or the default one.""" + if benders is None: + return SCIPfindBenders(self._scip, "default") + return (benders)._benders + def updateBendersLowerbounds(self, lowerbounds, Benders benders=None): """ Updates the subproblem lower bounds for benders using @@ -9516,15 +9518,10 @@ cdef class Model: benders : Benders or None, optional """ - cdef SCIP_BENDERS* _benders + cdef SCIP_BENDERS* _benders = self._getBenders(benders) assert type(lowerbounds) is dict - if benders is None: - _benders = SCIPfindBenders(self._scip, "default") - else: - _benders = benders._benders - for d in lowerbounds.keys(): SCIPbendersUpdateSubproblemLowerbound(_benders, d, lowerbounds[d]) @@ -9590,7 +9587,7 @@ cdef class Model: PY_SCIP_BENDERSENFOTYPE: LP, RELAX, PSEUDO or CHECK. Default is LP. """ - cdef SCIP_BENDERS* scip_benders + cdef SCIP_BENDERS* scip_benders = self._getBenders(benders) cdef SCIP_SOL* scip_sol if isinstance(solution, Solution): @@ -9598,11 +9595,6 @@ cdef class Model: else: scip_sol = NULL - if benders is None: - scip_benders = SCIPfindBenders(self._scip, "default") - else: - scip_benders = benders._benders - retcode = SCIPsetupBendersSubproblem(self._scip, scip_benders, scip_sol, probnumber, checktype) PY_SCIP_CALL(retcode) @@ -9632,7 +9624,7 @@ cdef class Model: """ - cdef SCIP_BENDERS* scip_benders + cdef SCIP_BENDERS* scip_benders = self._getBenders(benders) cdef SCIP_SOL* scip_sol cdef SCIP_Real objective cdef SCIP_Bool infeasible @@ -9642,11 +9634,6 @@ cdef class Model: else: scip_sol = NULL - if benders is None: - scip_benders = SCIPfindBenders(self._scip, "default") - else: - scip_benders = benders._benders - PY_SCIP_CALL(SCIPsolveBendersSubproblem(self._scip, scip_benders, scip_sol, probnumber, &infeasible, solvecip, &objective)) @@ -9670,14 +9657,9 @@ cdef class Model: Model """ - cdef SCIP_BENDERS* scip_benders + cdef SCIP_BENDERS* scip_benders = self._getBenders(benders) cdef SCIP* scip_subprob - if benders is None: - scip_benders = SCIPfindBenders(self._scip, "default") - else: - scip_benders = benders._benders - scip_subprob = SCIPbendersSubproblem(scip_benders, probnumber) return Model.create(scip_subprob) @@ -9701,14 +9683,9 @@ cdef class Model: Variable or None """ - cdef SCIP_BENDERS* _benders + cdef SCIP_BENDERS* _benders = self._getBenders(benders) cdef SCIP_VAR* _mappedvar - if benders is None: - _benders = SCIPfindBenders(self._scip, "default") - else: - _benders = benders._benders - if probnumber == -1: PY_SCIP_CALL(SCIPgetBendersMasterVar(self._scip, _benders, var.scip_var, &_mappedvar)) else: @@ -9734,14 +9711,9 @@ cdef class Model: Variable """ - cdef SCIP_BENDERS* _benders + cdef SCIP_BENDERS* _benders = self._getBenders(benders) cdef SCIP_VAR* _auxvar - if benders is None: - _benders = SCIPfindBenders(self._scip, "default") - else: - _benders = benders._benders - _auxvar = SCIPbendersGetAuxiliaryVar(_benders, probnumber) return self._getOrCreateVar(_auxvar) @@ -9764,15 +9736,10 @@ cdef class Model: flag to indicate whether the current subproblem is optimal for the master """ - cdef SCIP_BENDERS* _benders + cdef SCIP_BENDERS* _benders = self._getBenders(benders) cdef SCIP_SOL* scip_sol cdef SCIP_Bool optimal - if benders is None: - _benders = SCIPfindBenders(self._scip, "default") - else: - _benders = benders._benders - if isinstance(solution, Solution): scip_sol = solution.sol else: @@ -11097,6 +11064,13 @@ cdef class Model: locale.setlocale(locale.LC_NUMERIC,user_locale) + cdef SCIP_HEUR* _getHeur(self, object heur): + """Resolve a heuristic to its ptr.""" + if isinstance(heur, Heur): + n = str_conversion(heur.name) + return SCIPfindHeur(self._scip, n) + return NULL + def createSol(self, Heur heur = None, initlp=False): """ Create a new primal solution in the transformed space. @@ -11116,11 +11090,7 @@ cdef class Model: cdef SCIP_HEUR* _heur cdef SCIP_SOL* _sol - if isinstance(heur, Heur): - n = str_conversion(heur.name) - _heur = SCIPfindHeur(self._scip, n) - else: - _heur = NULL + _heur = self._getHeur(heur) if not initlp: PY_SCIP_CALL(SCIPcreateSol(self._scip, &_sol, _heur)) else: @@ -11145,11 +11115,7 @@ cdef class Model: cdef SCIP_HEUR* _heur cdef SCIP_SOL* _sol - if isinstance(heur, Heur): - n = str_conversion(heur.name) - _heur = SCIPfindHeur(self._scip, n) - else: - _heur = NULL + _heur = self._getHeur(heur) PY_SCIP_CALL(SCIPcreatePartialSol(self._scip, &_sol, _heur)) partialsolution = Solution.create(self._scip, _sol) return partialsolution @@ -11171,11 +11137,7 @@ cdef class Model: cdef SCIP_HEUR* _heur cdef SCIP_SOL* _sol - if isinstance(heur, Heur): - n = str_conversion(heur.name) - _heur = SCIPfindHeur(self._scip, n) - else: - _heur = NULL + _heur = self._getHeur(heur) PY_SCIP_CALL(SCIPcreateOrigSol(self._scip, &_sol, _heur)) solution = Solution.create(self._scip, _sol) @@ -11626,6 +11588,12 @@ cdef class Model: raise ValueError("Cannot get solution time: solution is None or NULL") return SCIPgetSolTime(self._scip, sol.sol) + cdef _ensureFreshBestSol(self): + """Refresh the cached _bestSol if the underlying SCIP pointer has changed.""" + cdef SCIP_SOL* current_best_sol = SCIPgetBestSol(self._scip) + if self._bestSol is None or self._bestSol.sol != current_best_sol: + self._bestSol = Solution.create(self._scip, current_best_sol) + def getObjVal(self, original=True): """ Retrieve the objective value of the best solution. @@ -11641,10 +11609,7 @@ cdef class Model: """ - # Ensure _bestSol is up-to-date (cheap pointer comparison) - cdef SCIP_SOL* current_best_sol = SCIPgetBestSol(self._scip) - if self._bestSol is None or self._bestSol.sol != current_best_sol: - self._bestSol = Solution.create(self._scip, current_best_sol) + self._ensureFreshBestSol() if SCIPgetNSols(self._scip) == 0: if self.getStage() != SCIP_STAGE_SOLVING: @@ -11713,10 +11678,7 @@ cdef class Model: if SCIPgetStage(self._scip) in {SCIP_STAGE_INIT, SCIP_STAGE_FREE}: raise Warning("Method cannot be called in stage ", self.getStage()) - # Ensure _bestSol is up-to-date (cheap pointer comparison) - cdef SCIP_SOL* current_best_sol = SCIPgetBestSol(self._scip) - if self._bestSol is None or self._bestSol.sol != current_best_sol: - self._bestSol = Solution.create(self._scip, current_best_sol) + self._ensureFreshBestSol() if self._bestSol.sol == NULL and SCIPgetStage(self._scip) != SCIP_STAGE_SOLVING: raise Warning("No solution available") @@ -11925,6 +11887,13 @@ cdef class Model: else: return "unknown" + cdef SCIP_EVENTHDLR* _getEventhdlr(self, object eventhdlr): + """Resolve an event handler to its ptr.""" + if isinstance(eventhdlr, Eventhdlr): + n = str_conversion(eventhdlr.name) + return SCIPfindEventhdlr(self._scip, n) + raise Warning("event handler not found") + def catchEvent(self, eventtype, Eventhdlr eventhdlr): """ Catches a global (not variable or row dependent) event. @@ -11936,11 +11905,7 @@ cdef class Model: """ cdef SCIP_EVENTHDLR* _eventhdlr - if isinstance(eventhdlr, Eventhdlr): - n = str_conversion(eventhdlr.name) - _eventhdlr = SCIPfindEventhdlr(self._scip, n) - else: - raise Warning("event handler not found") + _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPcatchEvent(self._scip, eventtype, _eventhdlr, NULL, NULL)) eventhdlr._caught_events.append(eventtype) @@ -11956,11 +11921,7 @@ cdef class Model: """ cdef SCIP_EVENTHDLR* _eventhdlr - if isinstance(eventhdlr, Eventhdlr): - n = str_conversion(eventhdlr.name) - _eventhdlr = SCIPfindEventhdlr(self._scip, n) - else: - raise Warning("event handler not found") + _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPdropEvent(self._scip, eventtype, _eventhdlr, NULL, -1)) if eventtype in eventhdlr._caught_events: @@ -11978,11 +11939,7 @@ cdef class Model: """ cdef SCIP_EVENTHDLR* _eventhdlr - if isinstance(eventhdlr, Eventhdlr): - n = str_conversion(eventhdlr.name) - _eventhdlr = SCIPfindEventhdlr(self._scip, n) - else: - raise Warning("event handler not found") + _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPcatchVarEvent(self._scip, var.scip_var, eventtype, _eventhdlr, NULL, NULL)) def dropVarEvent(self, Variable var, eventtype, Eventhdlr eventhdlr): @@ -11997,11 +11954,7 @@ cdef class Model: """ cdef SCIP_EVENTHDLR* _eventhdlr - if isinstance(eventhdlr, Eventhdlr): - n = str_conversion(eventhdlr.name) - _eventhdlr = SCIPfindEventhdlr(self._scip, n) - else: - raise Warning("event handler not found") + _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPdropVarEvent(self._scip, var.scip_var, eventtype, _eventhdlr, NULL, -1)) def catchRowEvent(self, Row row, eventtype, Eventhdlr eventhdlr): @@ -12016,11 +11969,7 @@ cdef class Model: """ cdef SCIP_EVENTHDLR* _eventhdlr - if isinstance(eventhdlr, Eventhdlr): - n = str_conversion(eventhdlr.name) - _eventhdlr = SCIPfindEventhdlr(self._scip, n) - else: - raise Warning("event handler not found") + _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPcatchRowEvent(self._scip, row.scip_row, eventtype, _eventhdlr, NULL, NULL)) def dropRowEvent(self, Row row, eventtype, Eventhdlr eventhdlr): @@ -12035,11 +11984,7 @@ cdef class Model: """ cdef SCIP_EVENTHDLR* _eventhdlr - if isinstance(eventhdlr, Eventhdlr): - n = str_conversion(eventhdlr.name) - _eventhdlr = SCIPfindEventhdlr(self._scip, n) - else: - raise Warning("event handler not found") + _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPdropRowEvent(self._scip, row.scip_row, eventtype, _eventhdlr, NULL, -1)) # Statistic Methods From 92d23517e5d904cc725022d31e163621c0598c76 Mon Sep 17 00:00:00 2001 From: Adam Jemielita Date: Sat, 31 Oct 2026 19:40:45 +0100 Subject: [PATCH 2/4] generalize _getTransformedCons function --- src/pyscipopt/scip.pxd | 2 +- src/pyscipopt/scip.pxi | 74 ++++++++++++++---------------------------- 2 files changed, 25 insertions(+), 51 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 21bb7d325..277789ce9 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -2332,7 +2332,7 @@ cdef class Model: cdef _free_scip_instance(self) cdef _ensureFreshBestSol(self) - cdef SCIP_CONS* _getTransformedLogicorCons(self, Constraint logicor_cons) + cdef SCIP_CONS* _getTransformedCons(self, Constraint cons, str expected_cons_type) cdef SCIP_EVENTHDLR* _getEventhdlr(self, object eventhdlr) cdef SCIP_BENDERS* _getBenders(self, object benders) cdef SCIP_HEUR* _getHeur(self, object heur) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 0f49f5997..90417af0b 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -7083,19 +7083,19 @@ cdef class Model: PY_SCIP_CALL(SCIPaddCoefLogicor(self._scip, logicor_cons.scip_cons, var.scip_var)) - cdef SCIP_CONS* _getTransformedLogicorCons(self, Constraint logicor_cons): - """Retrieve the transformed logicor constraint from the original constraint.""" + cdef SCIP_CONS* _getTransformedCons(self, Constraint cons, str expected_cons_type): + """Validate constraint type and retrieve its transformed representation.""" cdef SCIP_CONS* transcons - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(logicor_cons.scip_cons))).decode('UTF-8') - if not constype == 'logicor': + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') + if not constype == expected_cons_type: raise Warning(f"dual solution values not available for constraints of type {constype}") - if logicor_cons.isOriginal(): - PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, logicor_cons.scip_cons, &transcons)) + if cons.isOriginal(): + PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.scip_cons, &transcons)) return transcons - return logicor_cons.scip_cons + return cons.scip_cons def getDualsolLogicor(self, Constraint logicor_cons): """ @@ -7112,7 +7112,7 @@ cdef class Model: Zero if presolve removed the transformed constraint. """ - cdef SCIP_CONS* transcons = self._getTransformedLogicorCons(logicor_cons) + cdef SCIP_CONS* transcons = self._getTransformedCons(logicor_cons, "logicor") if transcons == NULL: return 0.0 @@ -7133,7 +7133,7 @@ cdef class Model: Zero if presolve removed the transformed constraint. """ - cdef SCIP_CONS* transcons = self._getTransformedLogicorCons(logicor_cons) + cdef SCIP_CONS* transcons = self._getTransformedCons(logicor_cons, "logicor") if transcons == NULL: return 0.0 @@ -9207,17 +9207,10 @@ cdef class Model: Zero if presolve removed the transformed constraint. """ - cdef SCIP_CONS* transcons + cdef SCIP_CONS* transcons = self._getTransformedCons(cons, "linear") + if transcons == NULL: + return 0.0 - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') - if not constype == 'linear': - raise Warning("dual solution values not available for constraints of type ", constype) - if cons.isOriginal(): - PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.scip_cons, &transcons)) - if transcons == NULL: - return 0.0 - else: - transcons = cons.scip_cons return SCIPgetDualsolLinear(self._scip, transcons) def getDualsolKnapsack(self, Constraint cons): @@ -9234,18 +9227,10 @@ cdef class Model: float """ - cdef SCIP_CONS* transcons + cdef SCIP_CONS* transcons = self._getTransformedCons(cons, "knapsack") + if transcons == NULL: + return 0.0 - constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') - if not constype == 'knapsack': - raise Warning("dual solution values not available for constraints of type ", constype) - if cons.isOriginal(): - # presolve may have removed it, and then there's no dual to read - PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.scip_cons, &transcons)) - if transcons == NULL: - return 0.0 - else: - transcons = cons.scip_cons return SCIPgetDualsolKnapsack(self._scip, transcons) def getDualMultiplier(self, Constraint cons): @@ -9279,16 +9264,11 @@ cdef class Model: Zero if presolve removed the transformed constraint. """ - cdef SCIP_CONS* transcons + cdef SCIP_CONS* transcons = self._getTransformedCons(cons, "linear") + if transcons == NULL: + return 0.0 - # TODO this should ideally be handled on the SCIP side - if cons.isOriginal(): - PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.scip_cons, &transcons)) - if transcons == NULL: - return 0.0 - return SCIPgetDualfarkasLinear(self._scip, transcons) - else: - return SCIPgetDualfarkasLinear(self._scip, cons.scip_cons) + return SCIPgetDualfarkasLinear(self._scip, transcons) def getDualfarkasKnapsack(self, Constraint cons): """ @@ -9304,17 +9284,11 @@ cdef class Model: float """ - cdef SCIP_CONS* transcons - - # TODO this should ideally be handled on the SCIP side - if cons.isOriginal(): - # presolve may have removed it, and then there's no Farkas value to read - PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.scip_cons, &transcons)) - if transcons == NULL: - return 0.0 - return SCIPgetDualfarkasKnapsack(self._scip, transcons) - else: - return SCIPgetDualfarkasKnapsack(self._scip, cons.scip_cons) + cdef SCIP_CONS* transcons = self._getTransformedCons(cons, "knapsack") + if transcons == NULL: + return 0.0 + + return SCIPgetDualfarkasKnapsack(self._scip, transcons) def getVarRedcost(self, Variable var): """ From 418fa345828028e3064036bced1b905bc955f859 Mon Sep 17 00:00:00 2001 From: Adam Jemielita Date: Sun, 1 Nov 2026 11:18:44 +0100 Subject: [PATCH 3/4] update docstrings --- src/pyscipopt/scip.pxi | 102 ++++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 21 deletions(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 90417af0b..f6a2e089f 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -7084,7 +7084,27 @@ cdef class Model: PY_SCIP_CALL(SCIPaddCoefLogicor(self._scip, logicor_cons.scip_cons, var.scip_var)) cdef SCIP_CONS* _getTransformedCons(self, Constraint cons, str expected_cons_type): - """Validate constraint type and retrieve its transformed representation.""" + """ + Validates constraint type and retrieves its transformed representation. + + Parameters + ---------- + cons : Constraint + Constraint to get the transformed representation for. + expected_cons_type : str + Expected constraint type name (e.g. 'linear', 'logicor', 'knapsack'). + + Returns + ------- + SCIP_CONS* + Pointer to the transformed SCIP constraint, or NULL if transformed constraint does not exist. + + Raises + ------ + Warning + If the constraint handler type does not match expected_cons_type. + + """ cdef SCIP_CONS* transcons constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') @@ -9264,6 +9284,7 @@ cdef class Model: Zero if presolve removed the transformed constraint. """ + # TODO this should ideally be handled on the SCIP side cdef SCIP_CONS* transcons = self._getTransformedCons(cons, "linear") if transcons == NULL: return 0.0 @@ -9284,6 +9305,7 @@ cdef class Model: float """ + # TODO this should ideally be handled on the SCIP side cdef SCIP_CONS* transcons = self._getTransformedCons(cons, "knapsack") if transcons == NULL: return 0.0 @@ -9475,7 +9497,20 @@ cdef class Model: ) cdef SCIP_BENDERS* _getBenders(self, object benders): - """Return the selected Benders' decomposition or the default one.""" + """ + Retrieves the SCIP_BENDERS pointer for the given Benders object or the default decomposition. + + Parameters + ---------- + benders : Benders or None + The Benders' decomposition object, or None to use the default Benders' decomposition. + + Returns + ------- + SCIP_BENDERS* + Pointer to the underlying SCIP_BENDERS structure, or NULL if not found. + + """ if benders is None: return SCIPfindBenders(self._scip, "default") return (benders)._benders @@ -11039,7 +11074,20 @@ cdef class Model: locale.setlocale(locale.LC_NUMERIC,user_locale) cdef SCIP_HEUR* _getHeur(self, object heur): - """Resolve a heuristic to its ptr.""" + """ + Retrieves the SCIP_HEUR pointer corresponding to a heuristic object. + + Parameters + ---------- + heur : Heur or None + The heuristic object to resolve. + + Returns + ------- + SCIP_HEUR* + Pointer to the SCIP heuristic, or NULL if heur is not a Heur instance or not found. + + """ if isinstance(heur, Heur): n = str_conversion(heur.name) return SCIPfindHeur(self._scip, n) @@ -11061,10 +11109,9 @@ cdef class Model: Solution """ - cdef SCIP_HEUR* _heur cdef SCIP_SOL* _sol + cdef SCIP_HEUR* _heur = self._getHeur(heur) - _heur = self._getHeur(heur) if not initlp: PY_SCIP_CALL(SCIPcreateSol(self._scip, &_sol, _heur)) else: @@ -11086,10 +11133,9 @@ cdef class Model: Solution """ - cdef SCIP_HEUR* _heur cdef SCIP_SOL* _sol + cdef SCIP_HEUR* _heur = self._getHeur(heur) - _heur = self._getHeur(heur) PY_SCIP_CALL(SCIPcreatePartialSol(self._scip, &_sol, _heur)) partialsolution = Solution.create(self._scip, _sol) return partialsolution @@ -11563,7 +11609,9 @@ cdef class Model: return SCIPgetSolTime(self._scip, sol.sol) cdef _ensureFreshBestSol(self): - """Refresh the cached _bestSol if the underlying SCIP pointer has changed.""" + """ + Refreshes the internal _bestSol attribute if the underlying SCIP pointer has changed. + """ cdef SCIP_SOL* current_best_sol = SCIPgetBestSol(self._scip) if self._bestSol is None or self._bestSol.sol != current_best_sol: self._bestSol = Solution.create(self._scip, current_best_sol) @@ -11862,7 +11910,25 @@ cdef class Model: return "unknown" cdef SCIP_EVENTHDLR* _getEventhdlr(self, object eventhdlr): - """Resolve an event handler to its ptr.""" + """ + Resolves an event handler object to its underlying SCIP_EVENTHDLR pointer. + + Parameters + ---------- + eventhdlr : Eventhdlr + The event handler object to resolve. + + Returns + ------- + SCIP_EVENTHDLR* + Pointer to the SCIP event handler, or NULL if not found. + + Raises + ------ + Warning + If eventhdlr is not an instance of Eventhdlr. + + """ if isinstance(eventhdlr, Eventhdlr): n = str_conversion(eventhdlr.name) return SCIPfindEventhdlr(self._scip, n) @@ -11878,8 +11944,7 @@ cdef class Model: eventhdlr : Eventhdlr """ - cdef SCIP_EVENTHDLR* _eventhdlr - _eventhdlr = self._getEventhdlr(eventhdlr) + cdef SCIP_EVENTHDLR* _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPcatchEvent(self._scip, eventtype, _eventhdlr, NULL, NULL)) eventhdlr._caught_events.append(eventtype) @@ -11894,8 +11959,7 @@ cdef class Model: eventhdlr : Eventhdlr """ - cdef SCIP_EVENTHDLR* _eventhdlr - _eventhdlr = self._getEventhdlr(eventhdlr) + cdef SCIP_EVENTHDLR* _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPdropEvent(self._scip, eventtype, _eventhdlr, NULL, -1)) if eventtype in eventhdlr._caught_events: @@ -11912,8 +11976,7 @@ cdef class Model: eventhdlr : Eventhdlr """ - cdef SCIP_EVENTHDLR* _eventhdlr - _eventhdlr = self._getEventhdlr(eventhdlr) + cdef SCIP_EVENTHDLR* _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPcatchVarEvent(self._scip, var.scip_var, eventtype, _eventhdlr, NULL, NULL)) def dropVarEvent(self, Variable var, eventtype, Eventhdlr eventhdlr): @@ -11927,8 +11990,7 @@ cdef class Model: eventhdlr : Eventhdlr """ - cdef SCIP_EVENTHDLR* _eventhdlr - _eventhdlr = self._getEventhdlr(eventhdlr) + cdef SCIP_EVENTHDLR* _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPdropVarEvent(self._scip, var.scip_var, eventtype, _eventhdlr, NULL, -1)) def catchRowEvent(self, Row row, eventtype, Eventhdlr eventhdlr): @@ -11942,8 +12004,7 @@ cdef class Model: eventhdlr : Eventhdlr """ - cdef SCIP_EVENTHDLR* _eventhdlr - _eventhdlr = self._getEventhdlr(eventhdlr) + cdef SCIP_EVENTHDLR* _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPcatchRowEvent(self._scip, row.scip_row, eventtype, _eventhdlr, NULL, NULL)) def dropRowEvent(self, Row row, eventtype, Eventhdlr eventhdlr): @@ -11957,8 +12018,7 @@ cdef class Model: eventhdlr : Eventhdlr """ - cdef SCIP_EVENTHDLR* _eventhdlr - _eventhdlr = self._getEventhdlr(eventhdlr) + cdef SCIP_EVENTHDLR* _eventhdlr = self._getEventhdlr(eventhdlr) PY_SCIP_CALL(SCIPdropRowEvent(self._scip, row.scip_row, eventtype, _eventhdlr, NULL, -1)) # Statistic Methods From 823e8766ca9ae8ae66dfca852490a3cb50d4de59 Mon Sep 17 00:00:00 2001 From: Adam Jemielita Date: Sun, 1 Nov 2026 17:38:43 +0100 Subject: [PATCH 4/4] Improve error message for invalid event handler type --- src/pyscipopt/scip.pxi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index f6a2e089f..a849a30b3 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -11932,7 +11932,7 @@ cdef class Model: if isinstance(eventhdlr, Eventhdlr): n = str_conversion(eventhdlr.name) return SCIPfindEventhdlr(self._scip, n) - raise Warning("event handler not found") + raise Warning(f"Expected an Eventhdlr instance, got {type(eventhdlr).__name__}") def catchEvent(self, eventtype, Eventhdlr eventhdlr): """