From 6a4140bf62c8cec6dd195e16e344be409dbaf561 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Wed, 28 May 2025 16:10:54 +0200 Subject: [PATCH 01/35] Add printStatisticsJson --- CHANGELOG.md | 1 + src/pyscipopt/scip.pxd | 1 + src/pyscipopt/scip.pxi | 45 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd8ed922..ad841ca57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Added support for knapsack constraints - Added isPositive(), isNegative(), isFeasLE(), isFeasLT(), isFeasGE(), isFeasGT(), isHugeValue(), and tests - Added SCIP_LOCKTYPE, addVarLocksType(), getNLocksDown(), getNLocksUp(), getNLocksDownType(), getNLocksUpType(), and tests +- Wrapped SCIPprintStatisticsJson ### Fixed ### Changed ### Removed diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 9453750e3..323f709ed 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1357,6 +1357,7 @@ cdef extern from "scip/scip.h": # Statistic Methods SCIP_RETCODE SCIPprintStatistics(SCIP* scip, FILE* outfile) + SCIP_RETCODE SCIPprintStatisticsJson(SCIP* scip, FILE* file) SCIP_Longint SCIPgetNNodes(SCIP* scip) SCIP_Longint SCIPgetNTotalNodes(SCIP* scip) SCIP_Longint SCIPgetNFeasibleLeaves(SCIP* scip) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 53bed228f..75cc49965 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -41,9 +41,9 @@ include "nodesel.pxi" include "matrix.pxi" # recommended SCIP version; major version is required -MAJOR = 9 -MINOR = 2 -PATCH = 1 +MAJOR = 10 +MINOR = 0 +PATCH = 0 # for external user functions use def; for functions used only inside the interface (starting with _) use cdef # todo: check whether this is currently done like this @@ -10131,12 +10131,45 @@ cdef class Model: # Statistic Methods - def printStatistics(self): - """Print statistics.""" + def printStatistics(self, filename=None): + """ + Print statistics. + + Parameters + ---------- + filename : str, optional + name of the output file (Default = None) + + """ + + user_locale = locale.getlocale(category=locale.LC_NUMERIC) + locale.setlocale(locale.LC_NUMERIC, "C") + + if not filename: + PY_SCIP_CALL(SCIPprintStatistics(self._scip, NULL)) + else: + PY_SCIP_CALL(SCIPprintStatistics(self._scip, str_conversion(filename))) + + locale.setlocale(locale.LC_NUMERIC,user_locale) + + def printStatisticsJson(self, filename=None): + """ + Print statistics in JSON format. + + Parameters + ---------- + filename : str, optional + name of the output file (Default = None) + + """ + user_locale = locale.getlocale(category=locale.LC_NUMERIC) locale.setlocale(locale.LC_NUMERIC, "C") - PY_SCIP_CALL(SCIPprintStatistics(self._scip, NULL)) + if not filename: + PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, NULL)) + else: + PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, str_conversion(filename))) locale.setlocale(locale.LC_NUMERIC,user_locale) From 3c50d404de05a70590a333ff1cbde50033220c7d Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Sat, 31 May 2025 10:26:37 +0200 Subject: [PATCH 02/35] Implied integer stuff --- src/pyscipopt/scip.pxd | 11 ++++++++ src/pyscipopt/scip.pxi | 57 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 323f709ed..1295e6b0e 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -304,6 +304,12 @@ cdef extern from "scip/scip.h": cdef extern from "scip/type_var.h": SCIP_LOCKTYPE SCIP_LOCKTYPE_MODEL SCIP_LOCKTYPE SCIP_LOCKTYPE_CONFLICT + + ctypedef int SCIP_IMPLINTTYPE + cdef extern from "scip/type_var.h": + SCIP_IMPLINTTYPE SCIP_IMPLINTTYPE_NONE + SCIP_IMPLINTTYPE SCIP_IMPLINTTYPE_WEAK + SCIP_IMPLINTTYPE SCIP_IMPLINTTYPE_STRONG ctypedef int SCIP_BENDERSENFOTYPE cdef extern from "scip/type_benders.h": @@ -802,6 +808,11 @@ cdef extern from "scip/scip.h": int SCIPgetNImplVars(SCIP* scip) int SCIPgetNContVars(SCIP* scip) SCIP_VARTYPE SCIPvarGetType(SCIP_VAR* var) + SCIP_Bool SCIPvarIsBinary(SCIP_VAR* var) + SCIP_Bool SCIPvarIsIntegral(SCIP_VAR* var) + SCIP_Bool SCIPvarIsImpliedIntegral(SCIP_VAR* var) + SCIP_Bool SCIPvarIsNonImpliedIntegral(SCIP_VAR* var) + SCIP_IMPLINTTYPE SCIPvarGetImplType(SCIP_VAR* var) SCIP_Bool SCIPvarIsOriginal(SCIP_VAR* var) SCIP_Bool SCIPvarIsTransformed(SCIP_VAR* var) SCIP_COL* SCIPvarGetCol(SCIP_VAR* var) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 75cc49965..64428e3f0 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -259,6 +259,11 @@ cdef class PY_SCIP_LOCKTYPE: MODEL = SCIP_LOCKTYPE_MODEL CONFLICT = SCIP_LOCKTYPE_CONFLICT +cdef class PY_SCIP_IMPLINTTYPE: + NONE = SCIP_IMPLINTTYPE_NONE + WEAK = SCIP_IMPLINTTYPE_WEAK + STRONG = SCIP_IMPLINTTYPE_STRONG + cdef class PY_SCIP_LPSOLSTAT: NOTSOLVED = SCIP_LPSOLSTAT_NOTSOLVED OPTIMAL = SCIP_LPSOLSTAT_OPTIMAL @@ -1510,7 +1515,7 @@ cdef class Variable(Expr): def vtype(self): """ - Retrieve the variables type (BINARY, INTEGER, IMPLINT or CONTINUOUS) + Retrieve the variables type (BINARY, INTEGER, CONTINUOUS, or IMPLINT) Returns ------- @@ -1527,6 +1532,56 @@ cdef class Variable(Expr): return "CONTINUOUS" elif vartype == SCIP_VARTYPE_IMPLINT: return "IMPLINT" + + def isBinary(self): + """ + Returns whether variable is of BINARY type. + + Returns + ------- + bool + """ + return SCIPvarIsBinary(self.scip_var) + + def isIntegral(self): + """ + Returns whether variable is of INTEGER type. + + Returns + ------- + bool + """ + return SCIPvarIsInteger(self.scip_var) + + def isImpliedIntegral(self): + """ + Returns whether variable is implied integral (weakly or strongly). + + Returns + ------- + bool + """ + return SCIPvarIsImpliedIntegral(self.scip_var) + + def isNonImpliedIntegral(self): + """ + Returns TRUE if the variable is integral, but not implied integral.. + + Returns + ------- + bool + """ + return SCIPvarIsImpliedIntegral(self.scip_var) + + def getImplType(self): + """ + Returns the implied integral type of the variable + + Returns + ------- + PY_SCIP_IMPLINTTYPE + """ + return SCIPvarGetImplType(self.scip_var) def isOriginal(self): """ From 45933b9204f8b7166a122034c701ec68c82995ec Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Sat, 31 May 2025 10:32:33 +0200 Subject: [PATCH 03/35] Add extra event types --- src/pyscipopt/scip.pxd | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 1295e6b0e..7dc98c38d 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -257,12 +257,15 @@ cdef extern from "scip/scip.h": SCIP_EVENTTYPE SCIP_EVENTTYPE_LHOLEADDED SCIP_EVENTTYPE SCIP_EVENTTYPE_LHOLEREMOVED SCIP_EVENTTYPE SCIP_EVENTTYPE_IMPLADDED + SCIP_EVENTTYPE SCIP_EVENTTYPE_TYPECHANGED + SCIP_EVENTTYPE SCIP_EVENTTYPE_IMPLTYPECHANGED SCIP_EVENTTYPE SCIP_EVENTTYPE_PRESOLVEROUND SCIP_EVENTTYPE SCIP_EVENTTYPE_NODEFOCUSED SCIP_EVENTTYPE SCIP_EVENTTYPE_NODEFEASIBLE SCIP_EVENTTYPE SCIP_EVENTTYPE_NODEINFEASIBLE SCIP_EVENTTYPE SCIP_EVENTTYPE_NODEBRANCHED SCIP_EVENTTYPE SCIP_EVENTTYPE_NODEDELETE + SCIP_EVENTTYPE SCIP_EVENTTYPE_DUALBOUNDIMPROVED SCIP_EVENTTYPE SCIP_EVENTTYPE_FIRSTLPSOLVED SCIP_EVENTTYPE SCIP_EVENTTYPE_LPSOLVED SCIP_EVENTTYPE SCIP_EVENTTYPE_POORSOLFOUND @@ -292,6 +295,7 @@ cdef extern from "scip/scip.h": SCIP_EVENTTYPE SCIP_EVENTTYPE_LPEVENT SCIP_EVENTTYPE SCIP_EVENTTYPE_SOLFOUND SCIP_EVENTTYPE SCIP_EVENTTYPE_SOLEVENT + SCIP_EVENTTYPE SCIP_EVENTTYPE_GAPUPDATED SCIP_EVENTTYPE SCIP_EVENTTYPE_ROWCHANGED SCIP_EVENTTYPE SCIP_EVENTTYPE_ROWEVENT From af1645d7365e8b941530d595a856d2f7bb779610 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Sat, 31 May 2025 11:13:57 +0200 Subject: [PATCH 04/35] minor fixes in relax.pxi --- src/pyscipopt/relax.pxi | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pyscipopt/relax.pxi b/src/pyscipopt/relax.pxi index 81695e8bb..f61e648a8 100644 --- a/src/pyscipopt/relax.pxi +++ b/src/pyscipopt/relax.pxi @@ -25,9 +25,8 @@ cdef class Relax: pass def relaxexec(self): - '''callls execution method of relaxation handler''' - print("python error in relaxexec: this method needs to be implemented") - return{} + '''calls execution method of relaxation handler''' + raise NotImplementedError("relaxexec() is a fundamental callback and should be implemented in the derived class") cdef SCIP_RETCODE PyRelaxCopy (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: From a310db6d27886ec4142640e1ce2bda060bdaefff Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Sat, 31 May 2025 11:14:04 +0200 Subject: [PATCH 05/35] start of iisfinder plugin --- src/pyscipopt/iisfinder.pxi | 37 +++++++++++++++++++++++++++++++++++++ src/pyscipopt/scip.pxd | 23 ++++++++++++++++++++--- src/pyscipopt/scip.pxi | 27 +++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 src/pyscipopt/iisfinder.pxi diff --git a/src/pyscipopt/iisfinder.pxi b/src/pyscipopt/iisfinder.pxi new file mode 100644 index 000000000..6869757e1 --- /dev/null +++ b/src/pyscipopt/iisfinder.pxi @@ -0,0 +1,37 @@ +##@file iisfinder.pxi +#@brief Base class of the Relaxator Plugin +cdef class IISFinder: + cdef public Model model + cdef public str name + + def iisfinderfree(self): + '''calls destructor and frees memory of iis finder''' + pass + + def iisfinderexec(self): + '''calls execution method of iis finder''' + raise NotImplementedError("iisfinderexec() is a fundamental callback and should be implemented in the derived class") + + +cdef SCIP_RETCODE PyIISFinderCopy (SCIP* scip, SCIP_IISFINDER* iisfinder) noexcept with gil: + return SCIP_OKAY + +cdef SCIP_RETCODE PyIISFinderFree (SCIP* scip, SCIP_IISFINDER* iisfinder) noexcept with gil: + cdef SCIP_IISFINDERDATA* iisfinderdata + iisfinderdata = SCIPIISfinderGetData(iisfinder) + PyRelax = iisfinderdata + PyRelax.iisfinderfree() + Py_DECREF(PyRelax) + return SCIP_OKAY + +cdef SCIP_RETCODE PyRelaxExec (SCIP* scip, SCIP_IISFINDER* iisfinder, SCIP_Real* lowerbound, SCIP_RESULT* result) noexcept with gil: + cdef SCIP_IISFINDERDATA* iisfinderdata + iisfinderdata = SCIPiisfinderGetData(iisfinder) + PyRelax = iisfinderdata + result_dict = PyRelax.iisfinderexec() + assert isinstance(result_dict, dict), "iisfinderexec() must return a dictionary." + #TODO + assert False + lowerbound[0] = result_dict.get("lowerbound", lowerbound[0]) + result[0] = result_dict.get("result", result[0]) + return SCIP_OKAY \ No newline at end of file diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 7dc98c38d..8333eee31 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -436,6 +436,15 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_HEURDATA: pass + ctypedef struct SCIP_IISFINDER: + pass + + ctypedef struct SCIP_IISFINDERDATA: + pass + + ctypedef struct SCIP_IIS: + pass + ctypedef struct SCIP_RELAX: pass @@ -1171,6 +1180,16 @@ cdef extern from "scip/scip.h": SCIP_HEURTIMING SCIPheurGetTimingmask(SCIP_HEUR* heur) void SCIPheurSetTimingmask(SCIP_HEUR* heur, SCIP_HEURTIMING timingmask) + #IIS finder plugin + SCIP_RETCODE SCIPincludeIISFinder(SCIP* scip, + const char* name, + const char* desc, + int priority, + SCIP_RETCODE (*iisfindercopy) (SCIP* scip, SCIP_IISFINDER* iisfinder), + SCIP_RETCODE (*iisfinderfree) (SCIP* scip, SCIP_IISFINDER* iisfinder), + SCIP_DECL_IISFINDEREXEC (*iisfinderexec) (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_Real timelim, SCIP_Longint nodelim, SCIP_Bool removebounds, SCIP_Bool silent, SCIP_RESULT* result) + SCIP_IISFINDERDATA* iisfinderdata) + #Relaxation plugin SCIP_RETCODE SCIPincludeRelax(SCIP* scip, const char* name, @@ -1449,7 +1468,6 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPhashmapCreate(SCIP_HASHMAP** hashmap, BMS_BLKMEM* blkmem, int mapsize) void SCIPhashmapFree(SCIP_HASHMAP** hashmap) - cdef extern from "scip/tree.h": int SCIPnodeGetNAddedConss(SCIP_NODE* node) @@ -1607,7 +1625,6 @@ cdef extern from "scip/cons_sos1.h": SCIP_CONS* cons, SCIP_VAR* var) - cdef extern from "scip/cons_sos2.h": SCIP_RETCODE SCIPcreateConsSOS2(SCIP* scip, SCIP_CONS** cons, @@ -1705,6 +1722,7 @@ cdef extern from "scip/cons_xor.h": SCIP_Bool dynamic, SCIP_Bool removable, SCIP_Bool stickingatnode) + cdef extern from "scip/scip_cons.h": SCIP_RETCODE SCIPprintCons(SCIP* scip, SCIP_CONS* cons, @@ -1859,7 +1877,6 @@ cdef extern from "scip/scip_nlp.h": SCIP_RETCODE SCIPgetNlRowActivityBounds(SCIP* scip, SCIP_NLROW* nlrow, SCIP_Real* minactivity, SCIP_Real* maxactivity) SCIP_RETCODE SCIPprintNlRow(SCIP* scip, SCIP_NLROW* nlrow, FILE* file) - cdef extern from "scip/cons_cardinality.h": SCIP_RETCODE SCIPcreateConsCardinality(SCIP* scip, SCIP_CONS** cons, diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 64428e3f0..e76a1e654 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -8531,6 +8531,33 @@ cdef class Model: heur.model = weakref.proxy(self) heur.name = name Py_INCREF(heur) + + def includeIISFinder(self, IISfinder iisfinder, name, desc, priority=10000, freq=1): + """ + Include an IIS (Irreducible Infeasible Set) finder handler. + + Parameters + ---------- + iisfinder : IISfinder + IIS finder + name : str + name of IIS finder + desc : str + description of IIS finder + priority : int, optional + priority of the IISfinder (#todo description) + freq : int, optional + frequency for calling IIS finder + + """ + nam = str_conversion(name) + des = str_conversion(desc) + PY_SCIP_CALL(SCIPincludeIISFinder(self._scip, nam, des, priority, freq, PyIISFinderCopy, PyIISFinderFree, + PyIISFinderExec, iisfinder)) + iisfinder.model = weakref.proxy(self) + iisfinder.name = name + + Py_INCREF(iisfinder) def includeRelax(self, Relax relax, name, desc, priority=10000, freq=1): """ From 3db696ef792f45535422918fa17f0db3a25bbe43 Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Tue, 3 Jun 2025 14:44:35 +0200 Subject: [PATCH 06/35] udpate inlcudeReader with the new definition, add printStatisticsJson --- src/pyscipopt/reader.pxi | 3 ++- src/pyscipopt/scip.pxd | 19 ++++++------------- src/pyscipopt/scip.pxi | 31 +++++++++++++++++++++++++++++++ tests/test_statistics.py | 7 +++++++ 4 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 tests/test_statistics.py diff --git a/src/pyscipopt/reader.pxi b/src/pyscipopt/reader.pxi index 13fc13d1b..98743bddf 100644 --- a/src/pyscipopt/reader.pxi +++ b/src/pyscipopt/reader.pxi @@ -40,7 +40,8 @@ cdef SCIP_RETCODE PyReaderRead (SCIP* scip, SCIP_READER* reader, const char* fil cdef SCIP_RETCODE PyReaderWrite (SCIP* scip, SCIP_READER* reader, FILE* file, const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, - SCIP_OBJSENSE objsense, SCIP_Real objscale, SCIP_Real objoffset, + SCIP_OBJSENSE objsense, SCIP_Real objoffset, SCIP_Real objscale, + SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars, SCIP_VAR** fixedvars, int nfixedvars, int startnvars, SCIP_CONS** conss, int nconss, int maxnconss, int startnconss, diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 9453750e3..515c76130 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -408,12 +408,6 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_PROPDATA: pass - ctypedef struct SCIP_PROPTIMING: - pass - - ctypedef struct SCIP_PRESOLTIMING: - pass - ctypedef struct SCIP_PRESOL: pass @@ -456,9 +450,6 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_PRESOL: pass - ctypedef struct SCIP_HEURTIMING: - pass - ctypedef struct SCIP_SEPA: pass @@ -510,9 +501,6 @@ cdef extern from "scip/scip.h": ctypedef struct BMS_BLKMEM: pass - ctypedef struct SCIP_EXPR: - pass - ctypedef struct SCIP_EXPRHDLR: pass @@ -540,6 +528,9 @@ cdef extern from "scip/scip.h": ctypedef union SCIP_DOMCHG: pass + ctypedef struct SCIP_RATIONAL: + pass + ctypedef void (*messagecallback) (SCIP_MESSAGEHDLR* messagehdlr, FILE* file, const char* msg) noexcept ctypedef void (*errormessagecallback) (void* data, FILE* file, const char* msg) ctypedef SCIP_RETCODE (*messagehdlrfree) (SCIP_MESSAGEHDLR* messagehdlr) @@ -961,7 +952,8 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*readerread) (SCIP* scip, SCIP_READER* reader, const char* filename, SCIP_RESULT* result), SCIP_RETCODE (*readerwrite) (SCIP* scip, SCIP_READER* reader, FILE* file, const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, - SCIP_OBJSENSE objsense, SCIP_Real objscale, SCIP_Real objoffset, + SCIP_OBJSENSE objsense, SCIP_Real objoffset, SCIP_Real objscale, + SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars, SCIP_VAR** fixedvars, int nfixedvars, int startnvars, SCIP_CONS** conss, int nconss, int maxnconss, int startnconss, @@ -1357,6 +1349,7 @@ cdef extern from "scip/scip.h": # Statistic Methods SCIP_RETCODE SCIPprintStatistics(SCIP* scip, FILE* outfile) + SCIP_RETCODE SCIPprintStatisticsJson(SCIP* scip, FILE* outfile) SCIP_Longint SCIPgetNNodes(SCIP* scip) SCIP_Longint SCIPgetNTotalNodes(SCIP* scip) SCIP_Longint SCIPgetNFeasibleLeaves(SCIP* scip) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 53bed228f..e6898008b 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -10139,6 +10139,15 @@ cdef class Model: PY_SCIP_CALL(SCIPprintStatistics(self._scip, NULL)) locale.setlocale(locale.LC_NUMERIC,user_locale) + + def printStatisticsJson(self): + """Print statistics in JSON format.""" + user_locale = locale.getlocale(category=locale.LC_NUMERIC) + locale.setlocale(locale.LC_NUMERIC, "C") + + PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, NULL)) + + locale.setlocale(locale.LC_NUMERIC,user_locale) def writeStatistics(self, filename="origprob.stats"): """ @@ -10160,6 +10169,28 @@ cdef class Model: PY_SCIP_CALL(SCIPprintStatistics(self._scip, cfile)) locale.setlocale(locale.LC_NUMERIC,user_locale) + + + def writeStatisticsJson(self, filename="origprob.stats.json"): + """ + Write statistics to a JSON file. + + Parameters + ---------- + filename : str, optional + name of the output file (Default = "origprob.stats.json") + + """ + user_locale = locale.getlocale(category=locale.LC_NUMERIC) + locale.setlocale(locale.LC_NUMERIC, "C") + + # use this doubled opening pattern to ensure that IOErrors are + # triggered early and in Python not in C,Cython or SCIP. + with open(filename, "w") as f: + cfile = fdopen(f.fileno(), "w") + PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, cfile)) + + locale.setlocale(locale.LC_NUMERIC,user_locale) def getNLPs(self): """ diff --git a/tests/test_statistics.py b/tests/test_statistics.py new file mode 100644 index 000000000..ccd1615ee --- /dev/null +++ b/tests/test_statistics.py @@ -0,0 +1,7 @@ +from pyscipopt import Model +from helpers.utils import random_mip_1 + +def test_statistics_json(): + model = random_mip_1() + model.optimize() + json_output = model.writeStatisticsJson("statistics.json") From 40b901a77d10d12b3fa54d6ee1033e209295e8d0 Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Tue, 3 Jun 2025 17:00:33 +0200 Subject: [PATCH 07/35] Add assert to statistics json test --- tests/test_statistics.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/test_statistics.py b/tests/test_statistics.py index ccd1615ee..fb4194547 100644 --- a/tests/test_statistics.py +++ b/tests/test_statistics.py @@ -1,7 +1,14 @@ -from pyscipopt import Model +import os from helpers.utils import random_mip_1 +from json import load def test_statistics_json(): model = random_mip_1() model.optimize() - json_output = model.writeStatisticsJson("statistics.json") + model.writeStatisticsJson("statistics.json") + + with open("statistics.json", "r") as f: + data = load(f) + assert data["origprob"]["problem_name"] == "model" + + os.remove("statistics.json") From 2fe76806db7cf93d8be8708da0c7e7f77a338248 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 3 Jun 2025 22:56:47 +0200 Subject: [PATCH 08/35] compilation, left iis for later --- src/pyscipopt/scip.pxd | 22 ++++++--------- src/pyscipopt/scip.pxi | 63 +++++++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 8333eee31..06aa7d5f2 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1180,15 +1180,15 @@ cdef extern from "scip/scip.h": SCIP_HEURTIMING SCIPheurGetTimingmask(SCIP_HEUR* heur) void SCIPheurSetTimingmask(SCIP_HEUR* heur, SCIP_HEURTIMING timingmask) - #IIS finder plugin - SCIP_RETCODE SCIPincludeIISFinder(SCIP* scip, - const char* name, - const char* desc, - int priority, - SCIP_RETCODE (*iisfindercopy) (SCIP* scip, SCIP_IISFINDER* iisfinder), - SCIP_RETCODE (*iisfinderfree) (SCIP* scip, SCIP_IISFINDER* iisfinder), - SCIP_DECL_IISFINDEREXEC (*iisfinderexec) (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_Real timelim, SCIP_Longint nodelim, SCIP_Bool removebounds, SCIP_Bool silent, SCIP_RESULT* result) - SCIP_IISFINDERDATA* iisfinderdata) + # #IIS finder plugin + # SCIP_RETCODE SCIPincludeIISFinder(SCIP* scip, + # const char* name, + # const char* desc, + # int priority, + # SCIP_RETCODE (*iisfindercopy) (SCIP* scip, SCIP_IISFINDER* iisfinder), + # SCIP_RETCODE (*iisfinderfree) (SCIP* scip, SCIP_IISFINDER* iisfinder), + # SCIP_RETCODE (*iisfinderexec) (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_Real timelim, SCIP_Longint nodelim, SCIP_Bool removebounds, SCIP_Bool silent, SCIP_RESULT* result), + # SCIP_IISFINDERDATA* iisfinderdata) #Relaxation plugin SCIP_RETCODE SCIPincludeRelax(SCIP* scip, @@ -1464,10 +1464,6 @@ cdef extern from "scip/scip.h": BMS_BLKMEM* SCIPblkmem(SCIP* scip) - # pub_misc.h - SCIP_RETCODE SCIPhashmapCreate(SCIP_HASHMAP** hashmap, BMS_BLKMEM* blkmem, int mapsize) - void SCIPhashmapFree(SCIP_HASHMAP** hashmap) - cdef extern from "scip/tree.h": int SCIPnodeGetNAddedConss(SCIP_NODE* node) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index e76a1e654..2d9504d8c 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -31,6 +31,7 @@ include "conshdlr.pxi" include "cutsel.pxi" include "event.pxi" include "heuristic.pxi" +# include "iisfinder.pxi" include "presol.pxi" include "pricer.pxi" include "propagator.pxi" @@ -1551,7 +1552,7 @@ cdef class Variable(Expr): ------- bool """ - return SCIPvarIsInteger(self.scip_var) + return SCIPvarIsIntegral(self.scip_var) def isImpliedIntegral(self): """ @@ -8532,32 +8533,32 @@ cdef class Model: heur.name = name Py_INCREF(heur) - def includeIISFinder(self, IISfinder iisfinder, name, desc, priority=10000, freq=1): - """ - Include an IIS (Irreducible Infeasible Set) finder handler. - - Parameters - ---------- - iisfinder : IISfinder - IIS finder - name : str - name of IIS finder - desc : str - description of IIS finder - priority : int, optional - priority of the IISfinder (#todo description) - freq : int, optional - frequency for calling IIS finder - - """ - nam = str_conversion(name) - des = str_conversion(desc) - PY_SCIP_CALL(SCIPincludeIISFinder(self._scip, nam, des, priority, freq, PyIISFinderCopy, PyIISFinderFree, - PyIISFinderExec, iisfinder)) - iisfinder.model = weakref.proxy(self) - iisfinder.name = name - - Py_INCREF(iisfinder) + # def includeIISFinder(self, IISfinder iisfinder, name, desc, priority=10000, freq=1): + # """ + # Include an IIS (Irreducible Infeasible Set) finder handler. + + # Parameters + # ---------- + # iisfinder : IISfinder + # IIS finder + # name : str + # name of IIS finder + # desc : str + # description of IIS finder + # priority : int, optional + # priority of the IISfinder (#todo description) + # freq : int, optional + # frequency for calling IIS finder + + # """ + # nam = str_conversion(name) + # des = str_conversion(desc) + # PY_SCIP_CALL(SCIPincludeIISFinder(self._scip, nam, des, priority, freq, PyIISFinderCopy, PyIISFinderFree, + # PyIISFinderExec, iisfinder)) + # iisfinder.model = weakref.proxy(self) + # iisfinder.name = name + + # Py_INCREF(iisfinder) def includeRelax(self, Relax relax, name, desc, priority=10000, freq=1): """ @@ -10230,7 +10231,9 @@ cdef class Model: if not filename: PY_SCIP_CALL(SCIPprintStatistics(self._scip, NULL)) else: - PY_SCIP_CALL(SCIPprintStatistics(self._scip, str_conversion(filename))) + with open(filename, "w") as f: + cfile = fdopen(f.fileno(), "w") + PY_SCIP_CALL(SCIPprintStatistics(self._scip, cfile)) locale.setlocale(locale.LC_NUMERIC,user_locale) @@ -10251,7 +10254,9 @@ cdef class Model: if not filename: PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, NULL)) else: - PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, str_conversion(filename))) + with open(filename, "w") as f: + cfile = fdopen(f.fileno(), "w") + PY_SCIP_CALL(SCIPprintStatistics(self._scip, cfile)) locale.setlocale(locale.LC_NUMERIC,user_locale) From a29aa5cc4b95c4c954070eb08762d9b92c85e41d Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Wed, 4 Jun 2025 01:18:34 +0200 Subject: [PATCH 09/35] fix issues with exact scip. still no support --- src/pyscipopt/reader.pxi | 7 ++++--- src/pyscipopt/scip.pxd | 8 ++++++-- tests/test_reader.py | 6 +++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/pyscipopt/reader.pxi b/src/pyscipopt/reader.pxi index 13fc13d1b..c75cab137 100644 --- a/src/pyscipopt/reader.pxi +++ b/src/pyscipopt/reader.pxi @@ -40,9 +40,10 @@ cdef SCIP_RETCODE PyReaderRead (SCIP* scip, SCIP_READER* reader, const char* fil cdef SCIP_RETCODE PyReaderWrite (SCIP* scip, SCIP_READER* reader, FILE* file, const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, - SCIP_OBJSENSE objsense, SCIP_Real objscale, SCIP_Real objoffset, - SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars, - SCIP_VAR** fixedvars, int nfixedvars, int startnvars, + SCIP_OBJSENSE objsense, SCIP_Real objscale, SCIP_Real objoffset, + SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, + SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, + int ncontvars, SCIP_VAR** fixedvars, int nfixedvars, int startnvars, SCIP_CONS** conss, int nconss, int maxnconss, int startnconss, SCIP_Bool genericnames, SCIP_RESULT* result) noexcept with gil: cdef SCIP_READERDATA* readerdata = SCIPreaderGetData(reader) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 06aa7d5f2..b3332d4c0 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -370,6 +370,9 @@ cdef extern from "scip/scip.h": ctypedef double SCIP_Real + ctypedef struct SCIP_RATIONAL: + pass + ctypedef struct SCIP: pass @@ -986,8 +989,9 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*readerwrite) (SCIP* scip, SCIP_READER* reader, FILE* file, const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, SCIP_OBJSENSE objsense, SCIP_Real objscale, SCIP_Real objoffset, - SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars, - SCIP_VAR** fixedvars, int nfixedvars, int startnvars, + SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, + SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, + int ncontvars, SCIP_VAR** fixedvars, int nfixedvars, int startnvars, SCIP_CONS** conss, int nconss, int maxnconss, int startnconss, SCIP_Bool genericnames, SCIP_RESULT* result), SCIP_READERDATA* readerdata) diff --git a/tests/test_reader.py b/tests/test_reader.py index 93d10c84b..c4544a018 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -136,4 +136,8 @@ def test_readStatistics(): m.writeStatistics(os.path.join("tests", "data", "readStatistics.stats")) result = readStatistics(os.path.join("tests", "data", "readStatistics.stats")) assert result.status == "user_interrupt" - assert result.gap == None \ No newline at end of file + assert result.gap == None + +def test_writeStatisticsJson(): + + assert False \ No newline at end of file From 6c126f72c8cd20a2b010b6ca8d22fcaf290a25fb Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 5 Jun 2025 16:34:21 +0100 Subject: [PATCH 10/35] fixed some tests --- src/pyscipopt/relax.pxi | 4 ++-- tests/test_reader.py | 13 ++++++++++++- tests/test_vars.py | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/pyscipopt/relax.pxi b/src/pyscipopt/relax.pxi index f61e648a8..db799bf0a 100644 --- a/src/pyscipopt/relax.pxi +++ b/src/pyscipopt/relax.pxi @@ -26,8 +26,8 @@ cdef class Relax: def relaxexec(self): '''calls execution method of relaxation handler''' - raise NotImplementedError("relaxexec() is a fundamental callback and should be implemented in the derived class") - + print("relaxexec() is a fundamental callback and should be implemented in the derived class") + return {} cdef SCIP_RETCODE PyRelaxCopy (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: return SCIP_OKAY diff --git a/tests/test_reader.py b/tests/test_reader.py index c4544a018..028da1050 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -1,7 +1,9 @@ import pytest import os +from json import load from pyscipopt import Model, quicksum, Reader, SCIP_RESULT, readStatistics +from helpers.utils import random_mip_1 class SudokuReader(Reader): @@ -140,4 +142,13 @@ def test_readStatistics(): def test_writeStatisticsJson(): - assert False \ No newline at end of file + model = random_mip_1() + model.optimize() + json_output = model.writeStatisticsJson("statistics.json") + model.writeStatisticsJson("statistics.json") + + with open("statistics.json", "r") as f: + data = load(f) + assert data["origprob"]["problem_name"] == "model" + + os.remove("statistics.json") \ No newline at end of file diff --git a/tests/test_vars.py b/tests/test_vars.py index d8c92ff40..9c77d634f 100644 --- a/tests/test_vars.py +++ b/tests/test_vars.py @@ -58,7 +58,7 @@ def test_vtype(): assert x.vtype() == "CONTINUOUS" assert y.vtype() == "INTEGER" assert z.vtype() == "BINARY" - assert w.vtype() == "IMPLINT" + assert w.vtype() == "CONTINUOUS" #todo check if this is indeed the expected behavior with SCIP10. Used to be IMPLINT, but deprecation and stuff m.chgVarType(x, 'I') assert x.vtype() == "INTEGER" From b841b95bd260ded5c6f73f8d91db8e91214fdb11 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 5 Jun 2025 16:41:45 +0100 Subject: [PATCH 11/35] fix minor typos --- src/pyscipopt/scip.pxi | 2 +- tests/test_reader.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 6d31003de..2f601f0bc 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -10290,7 +10290,7 @@ cdef class Model: else: with open(filename, "w") as f: cfile = fdopen(f.fileno(), "w") - PY_SCIP_CALL(SCIPprintStatistics(self._scip, cfile)) + PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, cfile)) locale.setlocale(locale.LC_NUMERIC,user_locale) diff --git a/tests/test_reader.py b/tests/test_reader.py index 028da1050..759c9b7fe 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -3,7 +3,7 @@ from json import load from pyscipopt import Model, quicksum, Reader, SCIP_RESULT, readStatistics -from helpers.utils import random_mip_1 +from helpers.utils import random_lp_1 class SudokuReader(Reader): @@ -142,10 +142,9 @@ def test_readStatistics(): def test_writeStatisticsJson(): - model = random_mip_1() + model = random_lp_1() model.optimize() - json_output = model.writeStatisticsJson("statistics.json") - model.writeStatisticsJson("statistics.json") + model.printStatisticsJson("statistics.json") with open("statistics.json", "r") as f: data = load(f) From 79c88a171969e0332cf5c90de9dd386d6fef1ee0 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 5 Jun 2025 16:45:43 +0100 Subject: [PATCH 12/35] changelog so I don't forget --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95b39fe29..4ad446436 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Added isPositive(), isNegative(), isFeasLE(), isFeasLT(), isFeasGE(), isFeasGT(), isHugeValue(), and tests - Added SCIP_LOCKTYPE, addVarLocksType(), getNLocksDown(), getNLocksUp(), getNLocksDownType(), getNLocksUpType(), and tests - Wrapped SCIPprintStatisticsJson +- Added 4 new events: TYPECHANGED, IMPLTYPECHANGED, DUALBOUNDIMPROVED, GAPUPDATED. +- Support for new implied integrality +- Wrapped varIsBinary(), varIsIntegral(), varIsImpliedIntegral(), varIsNonImpliedIntegral(), varGetImplType() +- Added support for IISFinder ### Fixed - Raised an error when an expression is used when a variable is required ### Changed From 703fd3484456a4b81cb4c30650d004f6936e3f24 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 5 Jun 2025 17:01:57 +0100 Subject: [PATCH 13/35] variable type tests --- src/pyscipopt/scip.pxi | 2 +- tests/test_vars.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 2f601f0bc..4139a938c 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -1575,7 +1575,7 @@ cdef class Variable(Expr): ------- bool """ - return SCIPvarIsImpliedIntegral(self.scip_var) + return SCIPvarIsNonImpliedIntegral(self.scip_var) def getImplType(self): """ diff --git a/tests/test_vars.py b/tests/test_vars.py index 9c77d634f..8be02ef5b 100644 --- a/tests/test_vars.py +++ b/tests/test_vars.py @@ -63,5 +63,17 @@ def test_vtype(): m.chgVarType(x, 'I') assert x.vtype() == "INTEGER" - m.chgVarType(y, 'M') - assert y.vtype() == "IMPLINT" \ No newline at end of file + m.chgVarType(y, 'C') + assert y.vtype() == "CONTINUOUS" + + is_int = lambda x: x.isIntegral() == True + is_implint = lambda x: x.isImpliedIntegral() == True + is_nonimplint = lambda x: x.isNonImpliedIntegral() == True + is_bin = lambda x: x.isBinary() == True + + assert not is_int(y) and not is_implint(y) and not is_nonimplint(y) and not is_bin(y) + assert is_int(x) and not is_implint(x) and not is_nonimplint(x) and not is_bin(x) + assert is_int(z) and not is_implint(z) and not is_nonimplint(z) and is_bin(z) + assert w.vtype() == "CONTINUOUS" and is_int(w) and is_implint(w) and is_nonimplint(w) and not is_bin(w) + + assert w.getImplType() == 1 \ No newline at end of file From f352c3027eb6d21eb7d1e4f49286f77c7905a882 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Wed, 11 Jun 2025 11:26:15 +0100 Subject: [PATCH 14/35] fix test_pricer bug --- tests/test_pricer.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/test_pricer.py b/tests/test_pricer.py index 647e26e90..9445c278a 100644 --- a/tests/test_pricer.py +++ b/tests/test_pricer.py @@ -42,8 +42,6 @@ def pricerredcost(self): assert type(self.model.getNSolsFound()) == int assert type(self.model.getNBestSolsFound()) == int assert self.model.getNBestSolsFound() <= self.model.getNSolsFound() - - self.model.data["nSols"] = self.model.getNSolsFound() # Adding the column to the master problem (model.LT because of numerics) if self.model.isLT(objval, 0): @@ -51,7 +49,6 @@ def pricerredcost(self): # Creating new var; must set pricedVar to True newVar = self.model.addVar("NewPattern_" + str(currentNumVar), vtype = "C", obj = 1.0, pricedVar = True) - # Adding the new variable to the constraints of the master problem newPattern = [] for i, c in enumerate(self.data['cons']): @@ -87,7 +84,6 @@ def test_cuttingstock(): s.setPresolve(0) s.data = {} - s.data["nSols"] = 0 # creating a pricer pricer = CutPricer() @@ -164,7 +160,7 @@ def test_cuttingstock(): assert s.getObjVal() == 452.25 assert type(s.getNSols()) == int - assert s.getNSols() == s.data["nSols"] + assert s.getNSols() == s.getNSolsFound() # Testing freeTransform s.freeTransform() @@ -189,7 +185,6 @@ def test_deactivate_pricer(): s.setPresolve(0) s.data = {} - s.data["nSols"] = 0 # creating a pricer pricer = CutPricer() From 81395b2b792e8d25dad3dccb3e3b143766e39ccd Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 17 Jun 2025 16:10:38 +0100 Subject: [PATCH 15/35] typo --- src/pyscipopt/scip.pxd | 2 +- src/pyscipopt/scip.pxi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index b3332d4c0..3cdb90e12 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -827,7 +827,7 @@ cdef extern from "scip/scip.h": SCIP_Bool SCIPvarIsBinary(SCIP_VAR* var) SCIP_Bool SCIPvarIsIntegral(SCIP_VAR* var) SCIP_Bool SCIPvarIsImpliedIntegral(SCIP_VAR* var) - SCIP_Bool SCIPvarIsNonImpliedIntegral(SCIP_VAR* var) + SCIP_Bool SCIPvarIsNonimpliedIntegral(SCIP_VAR* var) SCIP_IMPLINTTYPE SCIPvarGetImplType(SCIP_VAR* var) SCIP_Bool SCIPvarIsOriginal(SCIP_VAR* var) SCIP_Bool SCIPvarIsTransformed(SCIP_VAR* var) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 4139a938c..eaac85d83 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -1575,7 +1575,7 @@ cdef class Variable(Expr): ------- bool """ - return SCIPvarIsNonImpliedIntegral(self.scip_var) + return SCIPvarIsNonimpliedIntegral(self.scip_var) def getImplType(self): """ From ae60d7ee24262a3a5e0bc4d8dccfd971996901ec Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 17 Jun 2025 17:03:32 +0100 Subject: [PATCH 16/35] IISfinder progress --- src/pyscipopt/iisfinder.pxi | 30 +++++++------- src/pyscipopt/scip.pxd | 22 ++++++----- src/pyscipopt/scip.pxi | 78 ++++++++++++++++++++++++------------- tests/test_iis.py | 34 ++++++++++++++++ 4 files changed, 112 insertions(+), 52 deletions(-) create mode 100644 tests/test_iis.py diff --git a/src/pyscipopt/iisfinder.pxi b/src/pyscipopt/iisfinder.pxi index 6869757e1..d2945fb9a 100644 --- a/src/pyscipopt/iisfinder.pxi +++ b/src/pyscipopt/iisfinder.pxi @@ -1,6 +1,6 @@ ##@file iisfinder.pxi -#@brief Base class of the Relaxator Plugin -cdef class IISFinder: +#@brief Base class of the IIS finder Plugin +cdef class IISfinder: cdef public Model model cdef public str name @@ -11,27 +11,27 @@ cdef class IISFinder: def iisfinderexec(self): '''calls execution method of iis finder''' raise NotImplementedError("iisfinderexec() is a fundamental callback and should be implemented in the derived class") - -cdef SCIP_RETCODE PyIISFinderCopy (SCIP* scip, SCIP_IISFINDER* iisfinder) noexcept with gil: + +cdef SCIP_RETCODE PyiisfinderCopy (SCIP* scip, SCIP_IISFINDER* iisfinder) noexcept with gil: return SCIP_OKAY -cdef SCIP_RETCODE PyIISFinderFree (SCIP* scip, SCIP_IISFINDER* iisfinder) noexcept with gil: +cdef SCIP_RETCODE PyiisfinderFree (SCIP* scip, SCIP_IISFINDER* iisfinder) noexcept with gil: cdef SCIP_IISFINDERDATA* iisfinderdata - iisfinderdata = SCIPIISfinderGetData(iisfinder) - PyRelax = iisfinderdata - PyRelax.iisfinderfree() - Py_DECREF(PyRelax) + iisfinderdata = SCIPiisfinderGetData(iisfinder) + PyIIS = iisfinderdata + PyIIS.iisfinderfree() + Py_DECREF(PyIIS) return SCIP_OKAY -cdef SCIP_RETCODE PyRelaxExec (SCIP* scip, SCIP_IISFINDER* iisfinder, SCIP_Real* lowerbound, SCIP_RESULT* result) noexcept with gil: +cdef SCIP_RETCODE PyiisfinderExec (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_Real timelim, SCIP_Longint nodelim, SCIP_Bool removebounds, SCIP_Bool silent, SCIP_RESULT* result) noexcept with gil: cdef SCIP_IISFINDERDATA* iisfinderdata iisfinderdata = SCIPiisfinderGetData(iisfinder) - PyRelax = iisfinderdata - result_dict = PyRelax.iisfinderexec() + PyIIS = iisfinderdata + result_dict = PyIIS.iisfinderexec() assert isinstance(result_dict, dict), "iisfinderexec() must return a dictionary." #TODO assert False - lowerbound[0] = result_dict.get("lowerbound", lowerbound[0]) - result[0] = result_dict.get("result", result[0]) - return SCIP_OKAY \ No newline at end of file + # lowerbound[0] = result_dict.get("lowerbound", lowerbound[0]) + # result[0] = result_dict.get("result", result[0]) + # return SCIP_OKAY \ No newline at end of file diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 3cdb90e12..ba6f3ab28 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1184,15 +1184,19 @@ cdef extern from "scip/scip.h": SCIP_HEURTIMING SCIPheurGetTimingmask(SCIP_HEUR* heur) void SCIPheurSetTimingmask(SCIP_HEUR* heur, SCIP_HEURTIMING timingmask) - # #IIS finder plugin - # SCIP_RETCODE SCIPincludeIISFinder(SCIP* scip, - # const char* name, - # const char* desc, - # int priority, - # SCIP_RETCODE (*iisfindercopy) (SCIP* scip, SCIP_IISFINDER* iisfinder), - # SCIP_RETCODE (*iisfinderfree) (SCIP* scip, SCIP_IISFINDER* iisfinder), - # SCIP_RETCODE (*iisfinderexec) (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_Real timelim, SCIP_Longint nodelim, SCIP_Bool removebounds, SCIP_Bool silent, SCIP_RESULT* result), - # SCIP_IISFINDERDATA* iisfinderdata) + #IIS finder plugin + SCIP_RETCODE SCIPincludeIISfinder(SCIP* scip, + const char* name, + const char* desc, + int priority, + SCIP_RETCODE (*iisfindercopy) (SCIP* scip, SCIP_IISFINDER* iisfinder), + SCIP_RETCODE (*iisfinderfree) (SCIP* scip, SCIP_IISFINDER* iisfinder), + SCIP_RETCODE (*iisfinderexec) (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_Real timelim, SCIP_Longint nodelim, SCIP_Bool removebounds, SCIP_Bool silent, SCIP_RESULT* result), + SCIP_IISFINDERDATA* iisfinderdata) + + SCIP_IISFINDERDATA* SCIPiisfinderGetData(SCIP_IISFINDER* iisfinder) + SCIP_RETCODE SCIPincludeIISfinderGreedy(SCIP* scip) + SCIP_RETCODE SCIPiisGreedyMinimize(SCIP_IIS* iis); #Relaxation plugin SCIP_RETCODE SCIPincludeRelax(SCIP* scip, diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index eaac85d83..86dab9cd5 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -31,7 +31,7 @@ include "conshdlr.pxi" include "cutsel.pxi" include "event.pxi" include "heuristic.pxi" -# include "iisfinder.pxi" +include "iisfinder.pxi" include "presol.pxi" include "pricer.pxi" include "propagator.pxi" @@ -8545,7 +8545,10 @@ cdef class Model: maxdepth : int, optional maximal depth level to call heuristic at (Default value = -1) timingmask : PY_SCIP_HEURTIMING, optional - positions in the node solving loop where heuristic should be executed + positions in the node solvingreturn { + 'result': SCIP_RESULT.SUCCESS, + 'lowerbound': 10e4 + } loop where heuristic should be executed (Default value = SCIP_HEURTIMING_BEFORENODE) usessubscip : bool, optional does the heuristic use a secondary SCIP instance? (Default value = False) @@ -8564,32 +8567,51 @@ cdef class Model: heur.name = name Py_INCREF(heur) - # def includeIISFinder(self, IISfinder iisfinder, name, desc, priority=10000, freq=1): - # """ - # Include an IIS (Irreducible Infeasible Set) finder handler. - - # Parameters - # ---------- - # iisfinder : IISfinder - # IIS finder - # name : str - # name of IIS finder - # desc : str - # description of IIS finder - # priority : int, optional - # priority of the IISfinder (#todo description) - # freq : int, optional - # frequency for calling IIS finder - - # """ - # nam = str_conversion(name) - # des = str_conversion(desc) - # PY_SCIP_CALL(SCIPincludeIISFinder(self._scip, nam, des, priority, freq, PyIISFinderCopy, PyIISFinderFree, - # PyIISFinderExec, iisfinder)) - # iisfinder.model = weakref.proxy(self) - # iisfinder.name = name - - # Py_INCREF(iisfinder) + def includeIISfinder(self, IISfinder iisfinder, name, desc, priority=10000, freq=1): + """ + Include an IIS (Irreducible Infeasible Set) finder handler. + + Parameters + ---------- + iisfinder : IISfinder + IIS finder + name : str + name of IIS finder + desc : str + description of IIS finder + priority : int, optional + priority of the IISfinder (#todo description) + freq : int, optional + frequency for calling IIS finder + + """ + nam = str_conversion(name) + des = str_conversion(desc) + PY_SCIP_CALL(SCIPincludeIISfinder(self._scip, nam, des, priority, PyiisfinderCopy, PyiisfinderFree, + PyiisfinderExec, iisfinder)) + iisfinder.model = weakref.proxy(self) + iisfinder.name = name + + Py_INCREF(iisfinder) + + def includeIISfinderGreedy(self): + """ + Include the default greedy IIS finder. + + Returns + ------- + IISfinder + the greedy IIS finder + + """ + PY_SCIP_CALL(SCIPincludeIISfinderGreedy(self._scip)) + + # def iisGreedyMinimize(self, IISfinder iisfinder): + # """ + # Perform the greedy deletion algorithm with singleton batches to obtain an irreducible infeasible subsystem (IIS) + # """ + + # PY_SCIP_CALL(SCIPiisGreedyMinimize(iisfinder._iisfinder)) def includeRelax(self, Relax relax, name, desc, priority=10000, freq=1): """ diff --git a/tests/test_iis.py b/tests/test_iis.py new file mode 100644 index 000000000..f4fc7e3f2 --- /dev/null +++ b/tests/test_iis.py @@ -0,0 +1,34 @@ +import pytest + +from pyscipopt import Model +from pyscipopt.scip import IISfinder + +calls = [] +class myIISfinder(IISfinder): + def iisfinderexec(self): + calls.append('relaxexec') + +def test_iis_custom(): + from helpers.utils import random_mip_1 + + m = random_mip_1() + x = m.addVar() + m.addCons(x >= 1, "inf1") + m.addCons(x <= 0, "inf2") + + iis = myIISfinder() + m.includeIISfinder(iis, name="custom", desc="test") + m.optimize() + assert calls != [] + +def test_iis_greedy(): + m = Model() + x = m.addVar() + m.addCons(x >= 1, "inf1") + m.addCons(x <= 0, "inf2") + + m.includeIISfinderGreedy() + m.optimize() + +test_iis_greedy() +test_iis_custom() From be3022e2791e9281781b7a1aae823d9d1c555140 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 17 Jun 2025 17:28:45 +0100 Subject: [PATCH 17/35] Start of support for exact scip --- src/pyscipopt/scip.pxd | 46 +++++++++++++++++++++++++++++++++ src/pyscipopt/scip.pxi | 58 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index ba6f3ab28..c64768f36 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -384,12 +384,18 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_ROW: pass + + ctypedef struct SCIP_ROW_EXACT: + pass ctypedef struct SCIP_NLROW: pass ctypedef struct SCIP_COL: pass + + ctypedef struct SCIP_COL_EXACT: + pass ctypedef struct SCIP_SOL: pass @@ -1397,6 +1403,30 @@ cdef extern from "scip/scip.h": SCIP_Bool SCIPisIntegral(SCIP* scip, SCIP_Real val) SCIP_Real SCIPgetTreesizeEstimation(SCIP* scip) + # Exact SCIP methods + SCIP_RETCODE SCIPenableExactSolving(SCIP* scip, SCIP_Bool enable); + SCIP_Bool SCIPisExact(SCIP* scip); + SCIP_Bool SCIPallowNegSlack(SCIP* scip); + SCIP_RETCODE SCIPbranchLPExact(SCIP* scip, SCIP_RESULT* result); + SCIP_RETCODE SCIPaddRowExact(SCIP* scip, SCIP_ROWEXACT* rowexact); + + # Exact LP SCIP methods + SCIP_VAR* SCIPcolExactGetVar(SCIP_COLEXACT* col); + SCIP_RATIONAL* SCIProwExactGetLhs(SCIP_ROWEXACT* row); + SCIP_RATIONAL* SCIProwExactGetRhs(SCIP_ROWEXACT* row); + SCIP_RATIONAL* SCIProwExactGetConstant(SCIP_ROWEXACT* row); + int SCIProwExactGetNNonz(SCIP_ROWEXACT* row); + SCIP_RATIONAL** SCIProwExactGetVals(SCIP_ROWEXACT* row); + SCIP_Bool SCIProwExactIsInLP(SCIP_ROWEXACT* row); + void SCIProwExactSort(SCIP_ROWEXACT* row); + SCIP_COLEXACT** SCIProwExactGetCols(SCIP_ROWEXACT* row); + void SCIProwExactLock(SCIP_ROWEXACT* row); + void SCIProwExactUnlock(SCIP_ROWEXACT* row); + SCIP_ROW* SCIProwExactGetRow(SCIP_ROWEXACT* row); + SCIP_ROW* SCIProwExactGetRowRhs(SCIP_ROWEXACT* row); + SCIP_Bool SCIProwExactHasFpRelax(SCIP_ROWEXACT* row); + SCIP_Bool SCIPlpExactDiving(SCIP_LPEXACT* lpexact); + # Statistic Methods SCIP_RETCODE SCIPprintStatistics(SCIP* scip, FILE* outfile) SCIP_RETCODE SCIPprintStatisticsJson(SCIP* scip, FILE* file) @@ -2053,6 +2083,14 @@ cdef class Column: @staticmethod cdef create(SCIP_COL* scipcol) +cdef class Column: + cdef SCIP_COLEXACT* scip_col_exact + # can be used to store problem data + cdef public object data + + @staticmethod + cdef create(SCIP_COLEXACT* scipcol_exact) + cdef class Row: cdef SCIP_ROW* scip_row # can be used to store problem data @@ -2061,6 +2099,14 @@ cdef class Row: @staticmethod cdef create(SCIP_ROW* sciprow) +cdef class RowExact: + cdef SCIP_ROWEXACT* scip_row_exact + # can be used to store problem data + cdef public object data + + @staticmethod + cdef create(SCIP_ROWEXACT* sciprow_exact) + cdef class NLRow: cdef SCIP_NLROW* scip_nlrow # can be used to store problem data diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 86dab9cd5..eb69ef12d 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -11008,6 +11008,64 @@ cdef class Model: return SCIPgetTreesizeEstimation(self._scip) + # Exact SCIP methods + def enableExactSolving(self, SCIP_Bool enable): + """ + Enables or disables exact solving mode in SCIP. + + Parameters + ---------- + enable : SCIP_Bool + Whether to enable exact solving mode (True) or disable it (False). + """ + + PY_SCIP_CALL(SCIPenableExactSolving(self._scip, enable)) + + def isExact(self): + """ + Returns whether exact solving mode is enabled in SCIP. + + Returns + ------- + bool + """ + + return SCIPisExact(self._scip) + + def allowNegSlack(self): + """ + Returns whether negative slack is allowed in exact solving mode. + + Returns + ------- + bool + """ + + return SCIPallowNegSlack(self._scip) + + def branchLPExact(self): + """ + Performs exact LP branching. + + Returns + ------- + SCIP_RESULT + """ + cdef SCIP_RESULT result + PY_SCIP_CALL(SCIPbranchLPExact(self._scip, &result)) + return result + + def addRowExact(self, rowexact): + """ + Adds an exact row to the LP. + + Parameters + ---------- + rowexact : RowExact + The exact row to add. + """ + PY_SCIP_CALL(SCIPaddRowExact(self._scip, rowexact.scip_row_exact)) + def getBipartiteGraphRepresentation(self, prev_col_features=None, prev_edge_features=None, prev_row_features=None, static_only=False, suppress_warnings=False): """ From dc086540adb40e28fb923bcc5ddadb91e69b5ada Mon Sep 17 00:00:00 2001 From: DominikKamp <130753997+DominikKamp@users.noreply.github.com> Date: Wed, 2 Jul 2025 10:44:26 +0200 Subject: [PATCH 18/35] Fix reader write (#1015) --- src/pyscipopt/reader.pxi | 9 +++++---- src/pyscipopt/scip.pxd | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/pyscipopt/reader.pxi b/src/pyscipopt/reader.pxi index c75cab137..fe02a4443 100644 --- a/src/pyscipopt/reader.pxi +++ b/src/pyscipopt/reader.pxi @@ -12,7 +12,7 @@ cdef class Reader: '''calls read method of reader''' return {} - def readerwrite(self, file, name, transformed, objsense, objscale, objoffset, binvars, intvars, + def readerwrite(self, file, name, transformed, objsense, objoffset, objscale, binvars, intvars, implvars, contvars, fixedvars, startnvars, conss, maxnconss, startnconss, genericnames): '''calls write method of reader''' return {} @@ -40,8 +40,8 @@ cdef SCIP_RETCODE PyReaderRead (SCIP* scip, SCIP_READER* reader, const char* fil cdef SCIP_RETCODE PyReaderWrite (SCIP* scip, SCIP_READER* reader, FILE* file, const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, - SCIP_OBJSENSE objsense, SCIP_Real objscale, SCIP_Real objoffset, - SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, + SCIP_OBJSENSE objsense, SCIP_Real objoffset, SCIP_Real objscale, + SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars, SCIP_VAR** fixedvars, int nfixedvars, int startnvars, SCIP_CONS** conss, int nconss, int maxnconss, int startnconss, @@ -59,7 +59,8 @@ cdef SCIP_RETCODE PyReaderWrite (SCIP* scip, SCIP_READER* reader, FILE* file, PyFixedVars = [Variable.create(fixedvars[i]) for i in range(nfixedvars)] PyConss = [Constraint.create(conss[i]) for i in range(nconss)] PyReader = readerdata - result_dict = PyReader.readerwrite(PyFile, PyName, transformed, objsense, objscale, objoffset, + #TODO: provide rational objoffsetexact and objscaleexact + result_dict = PyReader.readerwrite(PyFile, PyName, transformed, objsense, objoffset, objscale, PyBinVars, PyIntVars, PyImplVars, PyContVars, PyFixedVars, startnvars, PyConss, maxnconss, startnconss, genericnames) result[0] = result_dict.get("result", result[0]) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index c64768f36..df28c8c97 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -994,8 +994,8 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*readerread) (SCIP* scip, SCIP_READER* reader, const char* filename, SCIP_RESULT* result), SCIP_RETCODE (*readerwrite) (SCIP* scip, SCIP_READER* reader, FILE* file, const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, - SCIP_OBJSENSE objsense, SCIP_Real objscale, SCIP_Real objoffset, - SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, + SCIP_OBJSENSE objsense, SCIP_Real objoffset, SCIP_Real objscale, + SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars, SCIP_VAR** fixedvars, int nfixedvars, int startnvars, SCIP_CONS** conss, int nconss, int maxnconss, int startnconss, From ffcf0016b2c05e83324b2d81aa100d2ec3cba795 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Fri, 11 Jul 2025 10:34:26 +0100 Subject: [PATCH 19/35] Fix most compilation issues and warnings --- src/pyscipopt/event.pxi | 2 +- src/pyscipopt/scip.pxd | 23 ++++++------------- src/pyscipopt/scip.pxi | 51 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/pyscipopt/event.pxi b/src/pyscipopt/event.pxi index 914e882ed..559d8c44a 100644 --- a/src/pyscipopt/event.pxi +++ b/src/pyscipopt/event.pxi @@ -39,7 +39,7 @@ cdef class Eventhdlr: # local helper functions for the interface -cdef Eventhdlr getPyEventhdlr(SCIP_EVENTHDLR* eventhdlr) noexcept with gil: +cdef Eventhdlr getPyEventhdlr(SCIP_EVENTHDLR* eventhdlr): cdef SCIP_EVENTHDLRDATA* eventhdlrdata eventhdlrdata = SCIPeventhdlrGetData(eventhdlr) return eventhdlrdata diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index df28c8c97..42da95f93 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -385,7 +385,7 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_ROW: pass - ctypedef struct SCIP_ROW_EXACT: + ctypedef struct SCIP_ROWEXACT: pass ctypedef struct SCIP_NLROW: @@ -394,7 +394,7 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_COL: pass - ctypedef struct SCIP_COL_EXACT: + ctypedef struct SCIP_COLEXACT: pass ctypedef struct SCIP_SOL: @@ -427,12 +427,6 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_PROPDATA: pass - ctypedef struct SCIP_PROPTIMING: - pass - - ctypedef struct SCIP_PRESOLTIMING: - pass - ctypedef struct SCIP_PRESOL: pass @@ -484,9 +478,6 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_PRESOL: pass - ctypedef struct SCIP_HEURTIMING: - pass - ctypedef struct SCIP_SEPA: pass @@ -535,10 +526,10 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_LPI: pass - ctypedef struct BMS_BLKMEM: + ctypedef struct SCIP_LPEXACT: pass - ctypedef struct SCIP_EXPR: + ctypedef struct BMS_BLKMEM: pass ctypedef struct SCIP_EXPRHDLR: @@ -2083,13 +2074,13 @@ cdef class Column: @staticmethod cdef create(SCIP_COL* scipcol) -cdef class Column: +cdef class ColumnExact: cdef SCIP_COLEXACT* scip_col_exact # can be used to store problem data cdef public object data @staticmethod - cdef create(SCIP_COLEXACT* scipcol_exact) + cdef create(SCIP_COLEXACT* scip_col_exact) cdef class Row: cdef SCIP_ROW* scip_row @@ -2105,7 +2096,7 @@ cdef class RowExact: cdef public object data @staticmethod - cdef create(SCIP_ROWEXACT* sciprow_exact) + cdef create(SCIP_ROWEXACT* scip_row_exact) cdef class NLRow: cdef SCIP_NLROW* scip_nlrow diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index eb69ef12d..2f5b47198 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -625,6 +625,31 @@ cdef class Column: return (self.__class__ == other.__class__ and self.scip_col == (other).scip_col) +cdef class ColumnExact: + """Base class holding a pointer to corresponding SCIP_COLEXACT.""" + + @staticmethod + cdef create(SCIP_COLEXACT* scipcolexact): + """ + Main method for creating a ColumnExact class. Is used instead of __init__. + + Parameters + ---------- + scipcolexact : SCIP_COLEXACT* + A pointer to the SCIP_COLEXACT + + Returns + ------- + col : ColumnExact + The Python representative of the SCIP_COLEXACT + + """ + if scipcolexact == NULL: + raise Warning("cannot create ColumnExact with SCIP_COLEXACT* == NULL") + col = ColumnExact() + col.scip_col_exact = scipcolexact + return col + cdef class Row: """Base class holding a pointer to corresponding SCIP_ROW.""" @@ -909,6 +934,31 @@ cdef class Row: return (self.__class__ == other.__class__ and self.scip_row == (other).scip_row) +cdef class RowExact: + """Base class holding a pointer to corresponding SCIP_ROW.""" + + @staticmethod + cdef create(SCIP_ROWEXACT* sciprowexact): + """ + Main method for creating a RowExact class. Is used instead of __init__. + + Parameters + ---------- + sciprow : SCIP_ROWEXACT* + A pointer to the SCIP_ROWEXACT + + Returns + ------- + row : Row + The Python representative of the SCIP_ROWEXACT + + """ + if sciprowexact == NULL: + raise Warning("cannot create Row with SCIP_ROWEXACT* == NULL") + row_exact = RowExact() + row_exact.scip_row_exact = sciprowexact + return row_exact + cdef class NLRow: """Base class holding a pointer to corresponding SCIP_NLROW.""" @@ -7615,7 +7665,6 @@ cdef class Model: """ raise Warning("model.getDualMultiplier(cons) is deprecated: please use model.getDualsolLinear(cons)") - return self.getDualsolLinear(cons) def getDualfarkasLinear(self, Constraint cons): """ From b417a920bdd75e58b8bdba1b76c2ca90f66ceb3e Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Fri, 11 Jul 2025 10:50:09 +0100 Subject: [PATCH 20/35] Update IIS method and remove redeclaration --- src/pyscipopt/scip.pxd | 3 +-- src/pyscipopt/scip.pxi | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 42da95f93..08b2951ed 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -726,7 +726,6 @@ cdef extern from "scip/scip.h": SCIP_Real SCIPgetLocalTransEstimate(SCIP* scip) # Solve Methods - SCIP_RETCODE SCIPsolve(SCIP* scip) SCIP_RETCODE SCIPsolve(SCIP* scip) noexcept nogil SCIP_RETCODE SCIPsolveConcurrent(SCIP* scip) SCIP_RETCODE SCIPfreeTransform(SCIP* scip) @@ -1193,7 +1192,7 @@ cdef extern from "scip/scip.h": SCIP_IISFINDERDATA* SCIPiisfinderGetData(SCIP_IISFINDER* iisfinder) SCIP_RETCODE SCIPincludeIISfinderGreedy(SCIP* scip) - SCIP_RETCODE SCIPiisGreedyMinimize(SCIP_IIS* iis); + SCIP_RETCODE SCIPiisGreedyMakeIrreducible(SCIP_IIS* iis); #Relaxation plugin SCIP_RETCODE SCIPincludeRelax(SCIP* scip, diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 2f5b47198..1e7136505 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -8660,7 +8660,7 @@ cdef class Model: # Perform the greedy deletion algorithm with singleton batches to obtain an irreducible infeasible subsystem (IIS) # """ - # PY_SCIP_CALL(SCIPiisGreedyMinimize(iisfinder._iisfinder)) + # PY_SCIP_CALL(SCIPiisGreedyMakeIrreducible(iisfinder._iisfinder)) def includeRelax(self, Relax relax, name, desc, priority=10000, freq=1): """ From 389ac7499ce9cb6e1eb5628b7f753fce2c921905 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 24 Jul 2025 17:50:56 +0100 Subject: [PATCH 21/35] Fix build error --- 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 c4cdb4796..21ba32f49 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -11479,7 +11479,7 @@ cdef class Model: PY_SCIP_CALL(SCIPbranchLPExact(self._scip, &result)) return result - def addRowExact(self, rowexact): + def addRowExact(self, RowExact rowexact): """ Adds an exact row to the LP. From e2d9038dc377fdefbe3d7afe3de7291699498592 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 24 Jul 2025 18:21:57 +0100 Subject: [PATCH 22/35] little IIS progress --- src/pyscipopt/scip.pxi | 10 ++++---- tests/test_iis.py | 56 ++++++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 21ba32f49..5779b3b84 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -9018,12 +9018,12 @@ cdef class Model: """ PY_SCIP_CALL(SCIPincludeIISfinderGreedy(self._scip)) - # def iisGreedyMinimize(self, IISfinder iisfinder): - # """ - # Perform the greedy deletion algorithm with singleton batches to obtain an irreducible infeasible subsystem (IIS) - # """ + def iisGreedyMakeIrreducible(self, IISfinder iisfinder): + """ + Perform the greedy deletion algorithm with singleton batches to obtain an irreducible infeasible subsystem (IIS) + """ - # PY_SCIP_CALL(SCIPiisGreedyMakeIrreducible(iisfinder._iisfinder)) + PY_SCIP_CALL(SCIPiisGreedyMakeIrreducible(iisfinder._iisfinder)) def includeRelax(self, Relax relax, name, desc, priority=10000, freq=1): """ diff --git a/tests/test_iis.py b/tests/test_iis.py index f4fc7e3f2..e4d7ec2f6 100644 --- a/tests/test_iis.py +++ b/tests/test_iis.py @@ -1,34 +1,42 @@ import pytest -from pyscipopt import Model -from pyscipopt.scip import IISfinder +from pyscipopt import Model, IISfinder -calls = [] -class myIISfinder(IISfinder): - def iisfinderexec(self): - calls.append('relaxexec') +class myIIS(IISfinder): + def __init__(self): + super().__init__() + self._iisfinder = None -def test_iis_custom(): - from helpers.utils import random_mip_1 + def isIISFound(self): + return self._iisfinder is not None - m = random_mip_1() - x = m.addVar() - m.addCons(x >= 1, "inf1") - m.addCons(x <= 0, "inf2") +def test_iis_greedy_make_irreducible(): + m = Model() + x1 = m.addVar("x1") + x2 = m.addVar("x2") + x3 = m.addVar("x3") + + m.addCons(x1 + x2 >= 5) + m.addCons(x2 + x3 >= 5) + m.addCons(x1 + x3 <= 3) + + iisfinder = IISfinder() - iis = myIISfinder() - m.includeIISfinder(iis, name="custom", desc="test") - m.optimize() - assert calls != [] + m.iisGreedyMakeIrreducible(iisfinder) -def test_iis_greedy(): + assert iisfinder.isIISFound() == True + +def test_custom_iis(): m = Model() - x = m.addVar() - m.addCons(x >= 1, "inf1") - m.addCons(x <= 0, "inf2") + x1 = m.addVar("x1") + x2 = m.addVar("x2") + x3 = m.addVar("x3") + + m.addCons(x1 + x2 >= 5) + m.addCons(x2 + x3 >= 5) + m.addCons(x1 + x3 <= 3) + + iisfinder = myIIS() - m.includeIISfinderGreedy() - m.optimize() + pass -test_iis_greedy() -test_iis_custom() From 55c99bbf9b8e788478d82a9887525860394a6027 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 24 Jul 2025 18:31:22 +0100 Subject: [PATCH 23/35] iis compilation --- src/pyscipopt/iisfinder.pxi | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyscipopt/iisfinder.pxi b/src/pyscipopt/iisfinder.pxi index d2945fb9a..5e94dbe98 100644 --- a/src/pyscipopt/iisfinder.pxi +++ b/src/pyscipopt/iisfinder.pxi @@ -3,6 +3,7 @@ cdef class IISfinder: cdef public Model model cdef public str name + cdef SCIP_IIS* _iisfinder def iisfinderfree(self): '''calls destructor and frees memory of iis finder''' From 3914760eabaabbba7d9c8831059231d0103f5996 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 24 Jul 2025 18:39:14 +0100 Subject: [PATCH 24/35] some iis methods --- src/pyscipopt/scip.pxd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index b8890d593..e2c2e0ba2 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1197,6 +1197,9 @@ cdef extern from "scip/scip.h": SCIP_IISFINDERDATA* SCIPiisfinderGetData(SCIP_IISFINDER* iisfinder) SCIP_RETCODE SCIPincludeIISfinderGreedy(SCIP* scip) SCIP_RETCODE SCIPiisGreedyMakeIrreducible(SCIP_IIS* iis); + SCIP_Bool SCIPiisIsSubscipInfeasible(SCIP_IIS* iis); + SCIP_Bool SCIPiisIsSubscipIrreducible(SCIP_IIS* iis); + SCIP* SCIPiisGetSubscip(SCIP_IIS* iis); #Relaxation plugin SCIP_RETCODE SCIPincludeRelax(SCIP* scip, From e9238a1b5fd645d2eececd0d8c86070b9639893c Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Thu, 24 Jul 2025 18:41:51 +0100 Subject: [PATCH 25/35] remove semicolons --- src/pyscipopt/scip.pxd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index e2c2e0ba2..0c293f48d 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1196,10 +1196,10 @@ cdef extern from "scip/scip.h": SCIP_IISFINDERDATA* SCIPiisfinderGetData(SCIP_IISFINDER* iisfinder) SCIP_RETCODE SCIPincludeIISfinderGreedy(SCIP* scip) - SCIP_RETCODE SCIPiisGreedyMakeIrreducible(SCIP_IIS* iis); - SCIP_Bool SCIPiisIsSubscipInfeasible(SCIP_IIS* iis); - SCIP_Bool SCIPiisIsSubscipIrreducible(SCIP_IIS* iis); - SCIP* SCIPiisGetSubscip(SCIP_IIS* iis); + SCIP_RETCODE SCIPiisGreedyMakeIrreducible(SCIP_IIS* iis) + SCIP_Bool SCIPiisIsSubscipInfeasible(SCIP_IIS* iis) + SCIP_Bool SCIPiisIsSubscipIrreducible(SCIP_IIS* iis) + SCIP* SCIPiisGetSubscip(SCIP_IIS* iis) #Relaxation plugin SCIP_RETCODE SCIPincludeRelax(SCIP* scip, From f9a781d215a06cd757395f4c4f34df33aec8be77 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Sat, 26 Jul 2025 12:43:17 +0100 Subject: [PATCH 26/35] Change IIS methods imported --- src/pyscipopt/scip.pxd | 2 +- src/pyscipopt/scip.pxi | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 0c293f48d..3905085c3 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1195,7 +1195,7 @@ cdef extern from "scip/scip.h": SCIP_IISFINDERDATA* iisfinderdata) SCIP_IISFINDERDATA* SCIPiisfinderGetData(SCIP_IISFINDER* iisfinder) - SCIP_RETCODE SCIPincludeIISfinderGreedy(SCIP* scip) + SCIP_RETCODE SCIPgenerateIIS(SCIP* scip) SCIP_RETCODE SCIPiisGreedyMakeIrreducible(SCIP_IIS* iis) SCIP_Bool SCIPiisIsSubscipInfeasible(SCIP_IIS* iis) SCIP_Bool SCIPiisIsSubscipIrreducible(SCIP_IIS* iis) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 5779b3b84..db122f7c9 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -9006,17 +9006,13 @@ cdef class Model: Py_INCREF(iisfinder) - def includeIISfinderGreedy(self): + def generateIIS(self): """ - Include the default greedy IIS finder. - - Returns - ------- - IISfinder - the greedy IIS finder - + Generates an Irreducible Infeasible Subsystem (IIS) from the current + problem. """ - PY_SCIP_CALL(SCIPincludeIISfinderGreedy(self._scip)) + + PY_SCIP_CALL(SCIPgenerateIIS(self._scip)) def iisGreedyMakeIrreducible(self, IISfinder iisfinder): """ From 01757c8ee8eb294cde55811b0129b687d7aa3710 Mon Sep 17 00:00:00 2001 From: Stefan Vigerske Date: Tue, 23 Sep 2025 16:12:17 +0200 Subject: [PATCH 27/35] remove cons_and methods that were removed in SCIP - scipopt/scip@602bc1c810 --- src/pyscipopt/scip.pxd | 2 -- src/pyscipopt/scip.pxi | 32 -------------------------------- 2 files changed, 34 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 3905085c3..3c5e026e2 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1722,8 +1722,6 @@ cdef extern from "scip/cons_and.h": SCIP_VAR* SCIPgetResultantAnd(SCIP* scip, SCIP_CONS* cons) SCIP_Bool SCIPisAndConsSorted(SCIP* scip, SCIP_CONS* cons) SCIP_RETCODE SCIPsortAndCons(SCIP* scip, SCIP_CONS* cons) - SCIP_RETCODE SCIPchgAndConsCheckFlagWhenUpgr(SCIP* scip, SCIP_CONS* cons, SCIP_Bool flag) - SCIP_RETCODE SCIPchgAndConsRemovableFlagWhenUpgr(SCIP* scip, SCIP_CONS* cons, SCIP_Bool flag) cdef extern from "scip/cons_or.h": SCIP_RETCODE SCIPcreateConsOr(SCIP* scip, diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index db122f7c9..900e22a64 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -6263,38 +6263,6 @@ cdef class Model: cdef SCIP_Bool success PY_SCIP_CALL(SCIPsortAndCons(self._scip, and_cons.scip_cons)) - - def chgAndConsCheckFlagWhenUpgr(self, Constraint cons, flag): - """ - when 'upgrading' the given AND-constraint, should the check flag for the upgraded - constraint be set to TRUE, even if the check flag of this AND-constraint is set to FALSE? - - Parameters - ---------- - cons : Constraint - The AND constraint to change. - flag : bool - The new value for the check flag. - - """ - - PY_SCIP_CALL(SCIPchgAndConsCheckFlagWhenUpgr(self._scip, cons.scip_cons, flag)) - - def chgAndConsRemovableFlagWhenUpgr(self, Constraint cons, flag): - """ - when 'upgrading' the given AND-constraint, should the removable flag for the upgraded - constraint be set to TRUE, even if the removable flag of this AND-constraint is set to FALSE? - - Parameters - ---------- - cons : Constraint - The AND constraint to change. - flag : bool - The new value for the removable flag. - - """ - - PY_SCIP_CALL(SCIPchgAndConsRemovableFlagWhenUpgr(self._scip, cons.scip_cons, flag)) def printCons(self, Constraint constraint): """ From c5b72dc05d76936ffbb9f40c5f0363ca85d31a8b Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Wed, 24 Sep 2025 12:08:49 +0300 Subject: [PATCH 28/35] Update callback signatures of IISFinderExec and ReaderWrite to match SCIP 10 --- src/pyscipopt/iisfinder.pxi | 2 +- src/pyscipopt/reader.pxi | 2 +- src/pyscipopt/scip.pxd | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pyscipopt/iisfinder.pxi b/src/pyscipopt/iisfinder.pxi index 5e94dbe98..889cac93c 100644 --- a/src/pyscipopt/iisfinder.pxi +++ b/src/pyscipopt/iisfinder.pxi @@ -25,7 +25,7 @@ cdef SCIP_RETCODE PyiisfinderFree (SCIP* scip, SCIP_IISFINDER* iisfinder) noexce Py_DECREF(PyIIS) return SCIP_OKAY -cdef SCIP_RETCODE PyiisfinderExec (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_Real timelim, SCIP_Longint nodelim, SCIP_Bool removebounds, SCIP_Bool silent, SCIP_RESULT* result) noexcept with gil: +cdef SCIP_RETCODE PyiisfinderExec (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_RESULT* result) noexcept with gil: cdef SCIP_IISFINDERDATA* iisfinderdata iisfinderdata = SCIPiisfinderGetData(iisfinder) PyIIS = iisfinderdata diff --git a/src/pyscipopt/reader.pxi b/src/pyscipopt/reader.pxi index fe02a4443..a9b60bb41 100644 --- a/src/pyscipopt/reader.pxi +++ b/src/pyscipopt/reader.pxi @@ -39,7 +39,7 @@ cdef SCIP_RETCODE PyReaderRead (SCIP* scip, SCIP_READER* reader, const char* fil return SCIP_OKAY cdef SCIP_RETCODE PyReaderWrite (SCIP* scip, SCIP_READER* reader, FILE* file, - const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, + const char* filename, const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, SCIP_OBJSENSE objsense, SCIP_Real objoffset, SCIP_Real objscale, SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index c3df7cdd2..6a4d346de 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -989,7 +989,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE (*readercopy) (SCIP* scip, SCIP_READER* reader), SCIP_RETCODE (*readerfree) (SCIP* scip, SCIP_READER* reader), SCIP_RETCODE (*readerread) (SCIP* scip, SCIP_READER* reader, const char* filename, SCIP_RESULT* result), - SCIP_RETCODE (*readerwrite) (SCIP* scip, SCIP_READER* reader, FILE* file, + SCIP_RETCODE (*readerwrite) (SCIP* scip, SCIP_READER* reader, FILE* file, const char* filename, const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed, SCIP_OBJSENSE objsense, SCIP_Real objoffset, SCIP_Real objscale, SCIP_RATIONAL* objoffsetexact, SCIP_RATIONAL* objscaleexact, @@ -1194,7 +1194,7 @@ cdef extern from "scip/scip.h": int priority, SCIP_RETCODE (*iisfindercopy) (SCIP* scip, SCIP_IISFINDER* iisfinder), SCIP_RETCODE (*iisfinderfree) (SCIP* scip, SCIP_IISFINDER* iisfinder), - SCIP_RETCODE (*iisfinderexec) (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_Real timelim, SCIP_Longint nodelim, SCIP_Bool removebounds, SCIP_Bool silent, SCIP_RESULT* result), + SCIP_RETCODE (*iisfinderexec) (SCIP_IIS* iis, SCIP_IISFINDER* iisfinder, SCIP_RESULT* result), SCIP_IISFINDERDATA* iisfinderdata) SCIP_IISFINDERDATA* SCIPiisfinderGetData(SCIP_IISFINDER* iisfinder) From a1a707f3c18c0f8688c2a7db709491a8a665ab80 Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Wed, 24 Sep 2025 11:52:14 +0200 Subject: [PATCH 29/35] Export IISfinder class --- src/pyscipopt/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 506ecd2a2..72fb12dbd 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -26,6 +26,7 @@ from pyscipopt.scip import Reader from pyscipopt.scip import Sepa from pyscipopt.scip import LP +from pyscipopt.scip import IISfinder from pyscipopt.scip import PY_SCIP_LPPARAM as SCIP_LPPARAM from pyscipopt.scip import readStatistics from pyscipopt.scip import Expr From 3b7fab317e7ed8312864012e80d788c17e356d9a Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Wed, 24 Sep 2025 12:18:57 +0200 Subject: [PATCH 30/35] Add simple iis tests --- tests/test_iis.py | 70 +++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/tests/test_iis.py b/tests/test_iis.py index e4d7ec2f6..11455af68 100644 --- a/tests/test_iis.py +++ b/tests/test_iis.py @@ -2,41 +2,39 @@ from pyscipopt import Model, IISfinder -class myIIS(IISfinder): - def __init__(self): - super().__init__() - self._iisfinder = None - - def isIISFound(self): - return self._iisfinder is not None - -def test_iis_greedy_make_irreducible(): +def infeasible_model(): m = Model() - x1 = m.addVar("x1") - x2 = m.addVar("x2") - x3 = m.addVar("x3") - - m.addCons(x1 + x2 >= 5) - m.addCons(x2 + x3 >= 5) - m.addCons(x1 + x3 <= 3) - - iisfinder = IISfinder() - - m.iisGreedyMakeIrreducible(iisfinder) - - assert iisfinder.isIISFound() == True - -def test_custom_iis(): - m = Model() - x1 = m.addVar("x1") - x2 = m.addVar("x2") - x3 = m.addVar("x3") - - m.addCons(x1 + x2 >= 5) - m.addCons(x2 + x3 >= 5) - m.addCons(x1 + x3 <= 3) - - iisfinder = myIIS() - - pass + x1 = m.addVar("x1", lb=0, ub=1, vtype="B") + x2 = m.addVar("x2", lb=0, ub=1, vtype="B") + x3 = m.addVar("x3", lb=0, ub=1, vtype="B") + + m.addCons(x1 + x2 == 1) + m.addCons(x2 + x3 == 1) + m.addCons(x1 + x3 == 1) + + return m + +def test_generate_iis(): + m = infeasible_model() + + # make sure IIS generation doesn't raise any exceptions + m.generateIIS() + + +def test_custom_iis_finder(): + class MyIIS(IISfinder): + def __init__(self): + super().__init__() + self._iisfinder = None + + + m = infeasible_model() + my_iis = MyIIS() + + m.includeIISfinder(my_iis, "", "") + + # should raise an exception since the custom IIS finder doesn't implement the exec method + with pytest.raises(Exception): + m.generateIIS() + From 5c71a079fc6a74cac30b03f996241262bb04811d Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Wed, 24 Sep 2025 12:21:50 +0200 Subject: [PATCH 31/35] Fix write json statistics test --- tests/test_reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_reader.py b/tests/test_reader.py index 759c9b7fe..d157e9df4 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -144,7 +144,7 @@ def test_writeStatisticsJson(): model = random_lp_1() model.optimize() - model.printStatisticsJson("statistics.json") + model.writeStatisticsJson("statistics.json") with open("statistics.json", "r") as f: data = load(f) From 0f3b26a38049d6126a733217b7485ab95fa698df Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Wed, 24 Sep 2025 13:54:49 +0200 Subject: [PATCH 32/35] Use deprecated implied integer type --- src/pyscipopt/__init__.py | 1 + src/pyscipopt/scip.pxd | 1 + src/pyscipopt/scip.pxi | 6 +++--- tests/test_vars.py | 32 +++++++++++++------------------- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/pyscipopt/__init__.py b/src/pyscipopt/__init__.py index 72fb12dbd..1013a146c 100644 --- a/src/pyscipopt/__init__.py +++ b/src/pyscipopt/__init__.py @@ -55,3 +55,4 @@ from pyscipopt.scip import PY_SCIP_BENDERSENFOTYPE as SCIP_BENDERSENFOTYPE from pyscipopt.scip import PY_SCIP_ROWORIGINTYPE as SCIP_ROWORIGINTYPE from pyscipopt.scip import PY_SCIP_SOLORIGIN as SCIP_SOLORIGIN +from pyscipopt.scip import PY_SCIP_IMPLINTTYPE as SCIP_IMPLINTTYPE diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index a715371ca..d291c57a6 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -28,6 +28,7 @@ cdef extern from "scip/scip.h": SCIP_VARTYPE SCIP_VARTYPE_BINARY SCIP_VARTYPE SCIP_VARTYPE_INTEGER SCIP_VARTYPE SCIP_VARTYPE_IMPLINT + SCIP_VARTYPE SCIP_DEPRECATED_VARTYPE_IMPLINT SCIP_VARTYPE SCIP_VARTYPE_CONTINUOUS ctypedef int SCIP_OBJSENSE diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index d71879fa1..0b8919a9b 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -1591,7 +1591,7 @@ cdef class Variable(Expr): return "INTEGER" elif vartype == SCIP_VARTYPE_CONTINUOUS: return "CONTINUOUS" - elif vartype == SCIP_VARTYPE_IMPLINT: + elif vartype == SCIP_DEPRECATED_VARTYPE_IMPLINT: return "IMPLINT" def isBinary(self): @@ -3998,7 +3998,7 @@ cdef class Model: elif vtype in ['I', 'INTEGER']: PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_INTEGER)) elif vtype in ['M', 'IMPLINT']: - PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_IMPLINT)) + PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_DEPRECATED_VARTYPE_IMPLINT)) else: raise Warning("unrecognized variable type") @@ -4447,7 +4447,7 @@ cdef class Model: elif vtype in ['I', 'INTEGER']: PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_INTEGER, &infeasible)) elif vtype in ['M', 'IMPLINT']: - PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_IMPLINT, &infeasible)) + PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_DEPRECATED_VARTYPE_IMPLINT, &infeasible)) else: raise Warning("unrecognized variable type") if infeasible: diff --git a/tests/test_vars.py b/tests/test_vars.py index 734b103c7..a2055c463 100644 --- a/tests/test_vars.py +++ b/tests/test_vars.py @@ -1,4 +1,4 @@ -from pyscipopt import Model, SCIP_PARAMSETTING, SCIP_BRANCHDIR +from pyscipopt import Model, SCIP_PARAMSETTING, SCIP_BRANCHDIR, SCIP_IMPLINTTYPE from helpers.utils import random_mip_1 def test_variablebounds(): @@ -58,28 +58,22 @@ def test_vtype(): assert x.vtype() == "CONTINUOUS" assert y.vtype() == "INTEGER" assert z.vtype() == "BINARY" - assert w.vtype() == "CONTINUOUS" #todo check if this is indeed the expected behavior with SCIP10. Used to be IMPLINT, but deprecation and stuff + assert w.vtype() == "CONTINUOUS" - m.chgVarType(x, 'I') - assert x.vtype() == "INTEGER" - - m.chgVarType(y, 'C') - assert y.vtype() == "CONTINUOUS" + is_int = lambda x: x.isIntegral() + is_implint = lambda x: x.isImpliedIntegral() + # is_nonimplint = lambda x: x.isNonImpliedIntegral() + is_bin = lambda x: x.isBinary() - is_int = lambda x: x.isIntegral() == True - is_implint = lambda x: x.isImpliedIntegral() == True - is_nonimplint = lambda x: x.isNonImpliedIntegral() == True - is_bin = lambda x: x.isBinary() == True + assert not is_int(x) and not is_implint(x) and not is_bin(x) + assert is_int(y) and not is_implint(y) and not is_bin(y) + assert is_int(z) and not is_implint(z) and is_bin(z) + assert w.vtype() == "CONTINUOUS" and is_int(w) and is_implint(w) and not is_bin(w) - assert not is_int(y) and not is_implint(y) and not is_nonimplint(y) and not is_bin(y) - assert is_int(x) and not is_implint(x) and not is_nonimplint(x) and not is_bin(x) - assert is_int(z) and not is_implint(z) and not is_nonimplint(z) and is_bin(z) - assert w.vtype() == "CONTINUOUS" and is_int(w) and is_implint(w) and is_nonimplint(w) and not is_bin(w) + assert w.getImplType() == SCIP_IMPLINTTYPE.WEAK - assert w.getImplType() == 1 - - m.chgVarType(y, 'M') - assert y.vtype() == "IMPLINT" + m.chgVarType(x, 'I') + assert x.vtype() == "INTEGER" def test_markRelaxationOnly(): m = Model() From ddb4e5de23c5a05bda3ba87aed141b0cc1bc7252 Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Wed, 24 Sep 2025 14:08:43 +0200 Subject: [PATCH 33/35] Raise error when relaxator doesn't implement the exec callback --- src/pyscipopt/relax.pxi | 3 +-- tests/test_relax.py | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/pyscipopt/relax.pxi b/src/pyscipopt/relax.pxi index db799bf0a..5ff2724af 100644 --- a/src/pyscipopt/relax.pxi +++ b/src/pyscipopt/relax.pxi @@ -26,8 +26,7 @@ cdef class Relax: def relaxexec(self): '''calls execution method of relaxation handler''' - print("relaxexec() is a fundamental callback and should be implemented in the derived class") - return {} + raise NotImplementedError("relaxexec() is a fundamental callback and should be implemented in the derived class") cdef SCIP_RETCODE PyRelaxCopy (SCIP* scip, SCIP_RELAX* relax) noexcept with gil: return SCIP_OKAY diff --git a/tests/test_relax.py b/tests/test_relax.py index 400e4ec0d..9b1fb8f83 100644 --- a/tests/test_relax.py +++ b/tests/test_relax.py @@ -41,9 +41,7 @@ def test_relaxator(): assert m.getObjVal() > 10e4 class EmptyRelaxator(Relax): - def relaxexec(self): - pass - # doesn't return anything + pass def test_empty_relaxator(): m = Model() From baac6be2e87c59d15f2dd16c4b4b7a939c3516dc Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Wed, 24 Sep 2025 20:33:16 +0200 Subject: [PATCH 34/35] Fix event tests and add another one for catching variable events --- tests/test_event.py | 51 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/tests/test_event.py b/tests/test_event.py index b4b292ffd..d5a27c166 100644 --- a/tests/test_event.py +++ b/tests/test_event.py @@ -8,6 +8,7 @@ class MyEvent(Eventhdlr): def eventinit(self): calls.append('eventinit') + print("init ", self.event_type) self.model.catchEvent(self.event_type, self) def eventexit(self): @@ -40,11 +41,7 @@ def eventexec(self, event): elif self.event_type == SCIP_EVENTTYPE.HOLECHANGED: assert event.getType() in [SCIP_EVENTTYPE.GHOLECHANGED, SCIP_EVENTTYPE.LHOLECHANGED] elif self.event_type == SCIP_EVENTTYPE.DOMCHANGED: - assert event.getType() in [SCIP_EVENTTYPE.BOUNDCHANGED, SCIP_EVENTTYPE.HOLECHANGED] - elif self.event_type == SCIP_EVENTTYPE.VARCHANGED: - assert event.getType() in [SCIP_EVENTTYPE.VARFIXED, SCIP_EVENTTYPE.VARUNLOCKED, SCIP_EVENTTYPE.OBJCHANGED, SCIP_EVENTTYPE.GBDCHANGED, SCIP_EVENTTYPE.DOMCHANGED, SCIP_EVENTTYPE.IMPLADDED, SCIP_EVENTTYPE.VARDELETED, SCIP_EVENTTYPE.TYPECHANGED] - elif self.event_type == SCIP_EVENTTYPE.VAREVENT: - assert event.getType() in [SCIP_EVENTTYPE.VARADDED, SCIP_EVENTTYPE.VARCHANGED, SCIP_EVENTTYPE.TYPECHANGED] + assert event.getType() in [SCIP_EVENTTYPE.BOUNDCHANGED, SCIP_EVENTTYPE.HOLECHANGED] elif self.event_type == SCIP_EVENTTYPE.NODESOLVED: assert event.getType() in [SCIP_EVENTTYPE.NODEFEASIBLE, SCIP_EVENTTYPE.NODEINFEASIBLE, SCIP_EVENTTYPE.NODEBRANCHED] elif self.event_type == SCIP_EVENTTYPE.NODEEVENT: @@ -62,7 +59,25 @@ def eventexec(self, event): def test_event(): - all_events = [SCIP_EVENTTYPE.DISABLED,SCIP_EVENTTYPE.VARADDED,SCIP_EVENTTYPE.VARDELETED,SCIP_EVENTTYPE.VARFIXED,SCIP_EVENTTYPE.VARUNLOCKED,SCIP_EVENTTYPE.OBJCHANGED,SCIP_EVENTTYPE.GLBCHANGED,SCIP_EVENTTYPE.GUBCHANGED,SCIP_EVENTTYPE.LBTIGHTENED,SCIP_EVENTTYPE.LBRELAXED,SCIP_EVENTTYPE.UBTIGHTENED,SCIP_EVENTTYPE.UBRELAXED,SCIP_EVENTTYPE.GHOLEADDED,SCIP_EVENTTYPE.GHOLEREMOVED,SCIP_EVENTTYPE.LHOLEADDED,SCIP_EVENTTYPE.LHOLEREMOVED,SCIP_EVENTTYPE.IMPLADDED,SCIP_EVENTTYPE.PRESOLVEROUND,SCIP_EVENTTYPE.NODEFOCUSED,SCIP_EVENTTYPE.NODEFEASIBLE,SCIP_EVENTTYPE.NODEINFEASIBLE,SCIP_EVENTTYPE.NODEBRANCHED,SCIP_EVENTTYPE.NODEDELETE,SCIP_EVENTTYPE.FIRSTLPSOLVED,SCIP_EVENTTYPE.LPSOLVED,SCIP_EVENTTYPE.POORSOLFOUND,SCIP_EVENTTYPE.BESTSOLFOUND,SCIP_EVENTTYPE.ROWADDEDSEPA,SCIP_EVENTTYPE.ROWDELETEDSEPA,SCIP_EVENTTYPE.ROWADDEDLP,SCIP_EVENTTYPE.ROWDELETEDLP,SCIP_EVENTTYPE.ROWCOEFCHANGED,SCIP_EVENTTYPE.ROWCONSTCHANGED,SCIP_EVENTTYPE.ROWSIDECHANGED,SCIP_EVENTTYPE.SYNC,SCIP_EVENTTYPE.GBDCHANGED,SCIP_EVENTTYPE.LBCHANGED,SCIP_EVENTTYPE.UBCHANGED,SCIP_EVENTTYPE.BOUNDTIGHTENED,SCIP_EVENTTYPE.BOUNDRELAXED,SCIP_EVENTTYPE.BOUNDCHANGED,SCIP_EVENTTYPE.LHOLECHANGED,SCIP_EVENTTYPE.HOLECHANGED,SCIP_EVENTTYPE.DOMCHANGED,SCIP_EVENTTYPE.VARCHANGED,SCIP_EVENTTYPE.VAREVENT,SCIP_EVENTTYPE.NODESOLVED,SCIP_EVENTTYPE.NODEEVENT,SCIP_EVENTTYPE.LPEVENT,SCIP_EVENTTYPE.SOLFOUND,SCIP_EVENTTYPE.SOLEVENT,SCIP_EVENTTYPE.ROWCHANGED,SCIP_EVENTTYPE.ROWEVENT] + all_events = [ + SCIP_EVENTTYPE.DISABLED, + SCIP_EVENTTYPE.PRESOLVEROUND, + SCIP_EVENTTYPE.NODEFOCUSED, + SCIP_EVENTTYPE.NODEFEASIBLE, + SCIP_EVENTTYPE.NODEINFEASIBLE, + SCIP_EVENTTYPE.NODEBRANCHED, + SCIP_EVENTTYPE.NODEDELETE, + SCIP_EVENTTYPE.FIRSTLPSOLVED, + SCIP_EVENTTYPE.LPSOLVED, + SCIP_EVENTTYPE.POORSOLFOUND, + SCIP_EVENTTYPE.BESTSOLFOUND, + SCIP_EVENTTYPE.SYNC, + SCIP_EVENTTYPE.NODESOLVED, + SCIP_EVENTTYPE.NODEEVENT, + SCIP_EVENTTYPE.LPEVENT, + SCIP_EVENTTYPE.SOLFOUND, + SCIP_EVENTTYPE.SOLEVENT, + ] all_event_hdlrs = [] for event in all_events: @@ -98,3 +113,27 @@ def callback(model, event): m.optimize() assert number_of_calls == 2 + +def test_raise_error_catch_var_event(): + m = Model() + m.hideOutput() + m.setPresolve(SCIP_PARAMSETTING.OFF) + + class MyEventVar(Eventhdlr): + def eventinit(self): + self.model.catchEvent(self.event_type, self) + + def eventexit(self): + self.model.dropEvent(self.event_type, self) + + def eventexec(self, event): + pass + + v = m.addVar("x", vtype="I") + ev = MyEventVar() + ev.var = v + ev.event_type = SCIP_EVENTTYPE.VAREVENT + m.includeEventhdlr(ev, "var_event", "event handler for var events") + + with pytest.raises(Exception): + m.optimize() From 1a5736013652e1d1a09ebd2cc738caed233d616f Mon Sep 17 00:00:00 2001 From: Mohammed Ghannam Date: Wed, 24 Sep 2025 21:01:52 +0200 Subject: [PATCH 35/35] Fix relaxator tests --- tests/test_relax.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/test_relax.py b/tests/test_relax.py index 9b1fb8f83..6f0c8f6b7 100644 --- a/tests/test_relax.py +++ b/tests/test_relax.py @@ -1,4 +1,4 @@ -from pyscipopt import Model, SCIP_RESULT +from pyscipopt import Model, SCIP_RESULT, SCIP_PARAMSETTING from pyscipopt.scip import Relax import pytest from helpers.utils import random_mip_1 @@ -17,7 +17,11 @@ def relaxexec(self): def test_relaxator(): m = Model() - m.hideOutput() + m.setPresolve(SCIP_PARAMSETTING.OFF) + m.setHeuristics(SCIP_PARAMSETTING.OFF) + m.setSeparating(SCIP_PARAMSETTING.OFF) + m.setParam("limits/nodes", 1) + # m.hideOutput() # include relaxator m.includeRelax(SoncRelax(), 'testrelaxator', @@ -38,13 +42,14 @@ def test_relaxator(): assert 'relaxexec' in calls assert len(calls) >= 1 - assert m.getObjVal() > 10e4 + assert m.getDualbound() >= 10e4 class EmptyRelaxator(Relax): pass def test_empty_relaxator(): m = Model() + m.setPresolve(SCIP_PARAMSETTING.OFF) m.hideOutput() m.includeRelax(EmptyRelaxator(), "", "")