diff --git a/CHANGELOG.md b/CHANGELOG.md index 05dbcd0aa..4ad446436 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ - 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 +- 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 diff --git a/src/pyscipopt/iisfinder.pxi b/src/pyscipopt/iisfinder.pxi new file mode 100644 index 000000000..d2945fb9a --- /dev/null +++ b/src/pyscipopt/iisfinder.pxi @@ -0,0 +1,37 @@ +##@file iisfinder.pxi +#@brief Base class of the IIS finder 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) + PyIIS = iisfinderdata + PyIIS.iisfinderfree() + 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_IISFINDERDATA* iisfinderdata + iisfinderdata = SCIPiisfinderGetData(iisfinder) + 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 diff --git a/src/pyscipopt/reader.pxi b/src/pyscipopt/reader.pxi index 13fc13d1b..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,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 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, SCIP_Bool genericnames, SCIP_RESULT* result) noexcept with gil: cdef SCIP_READERDATA* readerdata = SCIPreaderGetData(reader) @@ -58,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/relax.pxi b/src/pyscipopt/relax.pxi index 81695e8bb..db799bf0a 100644 --- a/src/pyscipopt/relax.pxi +++ b/src/pyscipopt/relax.pxi @@ -25,10 +25,9 @@ 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''' + 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/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 9453750e3..df28c8c97 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 @@ -304,6 +308,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": @@ -360,6 +370,9 @@ cdef extern from "scip/scip.h": ctypedef double SCIP_Real + ctypedef struct SCIP_RATIONAL: + pass + ctypedef struct SCIP: pass @@ -371,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 @@ -426,6 +445,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 @@ -802,6 +830,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) @@ -961,9 +994,10 @@ 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_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars, - SCIP_VAR** fixedvars, int nfixedvars, int startnvars, + 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, SCIP_Bool genericnames, SCIP_RESULT* result), SCIP_READERDATA* readerdata) @@ -1156,6 +1190,20 @@ 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) + + SCIP_IISFINDERDATA* SCIPiisfinderGetData(SCIP_IISFINDER* iisfinder) + SCIP_RETCODE SCIPincludeIISfinderGreedy(SCIP* scip) + SCIP_RETCODE SCIPiisGreedyMinimize(SCIP_IIS* iis); + #Relaxation plugin SCIP_RETCODE SCIPincludeRelax(SCIP* scip, const char* name, @@ -1355,8 +1403,33 @@ 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) SCIP_Longint SCIPgetNNodes(SCIP* scip) SCIP_Longint SCIPgetNTotalNodes(SCIP* scip) SCIP_Longint SCIPgetNFeasibleLeaves(SCIP* scip) @@ -1429,11 +1502,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) @@ -1591,7 +1659,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, @@ -1689,6 +1756,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, @@ -1843,7 +1911,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, @@ -2016,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 @@ -2024,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 3b2ee7db7..eb69ef12d 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" @@ -41,9 +42,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 @@ -259,6 +260,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 @@ -1513,7 +1519,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 ------- @@ -1530,6 +1536,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 SCIPvarIsIntegral(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 SCIPvarIsNonimpliedIntegral(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): """ @@ -8489,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) @@ -8507,6 +8566,52 @@ 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, 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): """ @@ -10165,12 +10270,49 @@ 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") - PY_SCIP_CALL(SCIPprintStatistics(self._scip, NULL)) + if not filename: + PY_SCIP_CALL(SCIPprintStatistics(self._scip, NULL)) + else: + 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) + + 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") + + if not filename: + PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, NULL)) + else: + 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) @@ -10866,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): """ 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() 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() diff --git a/tests/test_reader.py b/tests/test_reader.py index 93d10c84b..759c9b7fe 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_lp_1 class SudokuReader(Reader): @@ -136,4 +138,16 @@ 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(): + + model = random_lp_1() + model.optimize() + model.printStatisticsJson("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..8be02ef5b 100644 --- a/tests/test_vars.py +++ b/tests/test_vars.py @@ -58,10 +58,22 @@ 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" - 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