diff --git a/src/pygcgopt/__init__.py b/src/pygcgopt/__init__.py index 32020b8..59cb4a0 100644 --- a/src/pygcgopt/__init__.py +++ b/src/pygcgopt/__init__.py @@ -46,6 +46,7 @@ from pygcgopt.gcg import PricingSolver from pygcgopt.gcg import ConsClassifier from pygcgopt.gcg import VarClassifier +from pygcgopt.gcg import Score from pygcgopt.gcg import PY_GCG_PRICINGSTATUS as GCG_PRICINGSTATUS from pygcgopt.gcg import PY_CONS_DECOMPINFO as CONS_DECOMPINFO from pygcgopt.gcg import PY_VAR_DECOMPINFO as VAR_DECOMPINFO diff --git a/src/pygcgopt/decomposition.pxi b/src/pygcgopt/decomposition.pxi index 5755499..c0cbdff 100644 --- a/src/pygcgopt/decomposition.pxi +++ b/src/pygcgopt/decomposition.pxi @@ -158,42 +158,10 @@ cdef class PartialDecomposition: def setUsergiven(self, USERGIVEN value=COMPLETED_CONSTOMASTER): self.thisptr.setUsergiven(value) - @property - def classic_score(self): - return self.thisptr.getClassicScore() - - @property - def border_area_score(self): - return self.thisptr.getBorderAreaScore() - @property def max_white_score(self): return self.thisptr.getMaxWhiteScore() - @property - def max_for_white_score(self): - return self.thisptr.getMaxForWhiteScore() - - @property - def set_part_for_white_score(self): - return self.thisptr.getSetPartForWhiteScore() - - @property - def max_for_white_agg_score(self): - return self.thisptr.getMaxForWhiteAggScore() - - @property - def set_part_for_white_agg_score(self): - return self.thisptr.getSetPartForWhiteAggScore() - - @property - def benders_score(self): - return self.thisptr.getBendersScore() - - @property - def strong_decomp_score(self): - return self.thisptr.getStrongDecompScore() - # BEGIN AUTOGENERATED BLOCK def addBlock(PartialDecomposition self): @@ -1381,6 +1349,9 @@ cdef class PartialDecomposition: cdef bool cpp_fromorig = fromorig self.thisptr.setStemsFromOrig(cpp_fromorig) + def getScore(PartialDecomposition self, Score score): + return self.thisptr.getScore(GCGfindScore(self.thisptr.getDetprobdata().getScip(), str_conversion(score.scorename))) + # def setUsergiven(PartialDecomposition self, cpp.USERGIVEN usergiven): # """sets whether this partialdec is user given @@ -1630,40 +1601,6 @@ cdef class PartialDecomposition: cdef vector[double] cpp_newvector = newvector self.thisptr.setDetectorClockTimes(cpp_newvector) - @property - def classicScore(PartialDecomposition self): - """gets the classic score - - .. note:: -1 iff not calculated yet, .. seealso:: GCGconshdlrDecompCalcClassicScore - :return: border area score. - """ - cdef double result = self.thisptr.getClassicScore() - return result - - @classicScore.setter - def classicScore(PartialDecomposition self, double score): - """set the classic score. - """ - cdef double cpp_score = score - self.thisptr.setClassicScore(cpp_score) - - @property - def borderAreaScore(PartialDecomposition self): - """gets the border area score - - .. note:: -1 iff not calculated yet, .. seealso:: GCGconshdlrDecompCalcBorderAreaScore - :return: border area score. - """ - cdef double result = self.thisptr.getBorderAreaScore() - return result - - @borderAreaScore.setter - def borderAreaScore(PartialDecomposition self, double score): - """set the border area score. - """ - cdef double cpp_score = score - self.thisptr.setBorderAreaScore(cpp_score) - @property def maxWhiteScore(PartialDecomposition self): """gets the maximum white area score @@ -1675,115 +1612,6 @@ cdef class PartialDecomposition: cdef double result = self.thisptr.getMaxWhiteScore() return result - @maxWhiteScore.setter - def maxWhiteScore(PartialDecomposition self, double score): - """set the maximum white area score. - """ - cdef double cpp_score = score - self.thisptr.setMaxWhiteScore(cpp_score) - - @property - def maxForWhiteScore(PartialDecomposition self): - """gets the maximum foreseeing white area score - - .. note:: -1 iff not calculated yet, .. seealso:: GCGconshdlrDecompCalcMaxForseeingWhiteScore - :return: maximum foreseeing white area score - """ - cdef double result = self.thisptr.getMaxForWhiteScore() - return result - - @maxForWhiteScore.setter - def maxForWhiteScore(PartialDecomposition self, double score): - """set the maximum foreseeing white area score. - """ - cdef double cpp_score = score - self.thisptr.setMaxForWhiteScore(cpp_score) - - @property - def partForWhiteScore(PartialDecomposition self): - """gets the setpartitioning maximum foreseeing white area score - - .. note:: -1 iff not calculated yet, .. seealso:: GGCGconshdlrDecompCalcSetPartForseeingWhiteScore - :return: setpartitioning maximum foreseeing white area score - """ - cdef double result = self.thisptr.getSetPartForWhiteScore() - return result - - @partForWhiteScore.setter - def partForWhiteScore(PartialDecomposition self, double score): - """set the setpartitioning maximum foreseeing white area score. - """ - cdef double cpp_score = score - self.thisptr.setSetPartForWhiteScore(cpp_score) - - @property - def maxForWhiteAggScore(PartialDecomposition self): - """gets the maximum foreseeing white area score with respect to aggregatable blocks - - .. note:: -1 iff not calculated yet, .. seealso:: GCGconshdlrDecompCalcMaxForeseeingWhiteAggScore - :return: maximum foreseeing white area score with respect to aggregatable blocks - """ - cdef double result = self.thisptr.getMaxForWhiteAggScore() - return result - - @maxForWhiteAggScore.setter - def maxForWhiteAggScore(PartialDecomposition self, double score): - """set the maximum foreseeing white area score with respect to aggregatable blocks. - """ - cdef double cpp_score = score - self.thisptr.setMaxForWhiteAggScore(cpp_score) - - @property - def partForWhiteAggScore(PartialDecomposition self): - """gets the setpartitioning maximum foreseeing white area score with respect to aggregateable - - .. note:: -1 iff not calculated yet, .. seealso:: GCGconshdlrDecompCalcSetPartForWhiteAggScore - :return: setpartitioning maximum foreseeing white area score with respect to aggregateable. - """ - cdef double result = self.thisptr.getSetPartForWhiteAggScore() - return result - - @partForWhiteAggScore.setter - def partForWhiteAggScore(PartialDecomposition self, double score): - """set the setpartitioning maximum foreseeing white area score with respect to aggregateable. - """ - cdef double cpp_score = score - self.thisptr.setSetPartForWhiteAggScore(cpp_score) - - @property - def bendersScore(PartialDecomposition self): - """gets the benders score - - .. note:: -1 iff not calculated yet, .. seealso:: GCGconshdlrDecompCalcBendersScore - :return: benders score. - """ - cdef double result = self.thisptr.getBendersScore() - return result - - @bendersScore.setter - def bendersScore(PartialDecomposition self, double score): - """set the benders score. - """ - cdef double cpp_score = score - self.thisptr.setBendersScore(cpp_score) - - @property - def strongDecompScore(PartialDecomposition self): - """gets the strong decomposition score - - .. note:: -1 iff not calculated yet, .. seealso:: GCGconshdlrDecompCalcStrongDecompositionScore - :return: strong decomposition score. - """ - cdef double result = self.thisptr.getStrongDecompScore() - return result - - @strongDecompScore.setter - def strongDecompScore(PartialDecomposition self, double score): - """set the strong decomposition score. - """ - cdef double cpp_score = score - self.thisptr.setStrongDecompScore(cpp_score) - def prepare(PartialDecomposition self): """sorts the partialdec and calculates a its implicit assignments, hashvalue and evaluation diff --git a/src/pygcgopt/gcg.pxd b/src/pygcgopt/gcg.pxd index ff4fbe7..8839839 100644 --- a/src/pygcgopt/gcg.pxd +++ b/src/pygcgopt/gcg.pxd @@ -27,6 +27,12 @@ cdef extern from "gcg/gcg.h": ctypedef struct DEC_DETECTOR: pass + ctypedef struct DEC_SCOREDATA: + pass + + ctypedef struct DEC_SCORE: + pass + ctypedef struct PARTIALDEC_DETECTION_DATA: DETPROBDATA* detprobdata PARTIALDECOMP* workonpartialdec @@ -76,6 +82,13 @@ cdef extern from "gcg/gcg.h": GP_OUTPUT_FORMAT_PNG GP_OUTPUT_FORMAT_SVG + SCIP_RETCODE GCGincludeScore(SCIP* scip, const char* name, const char* shortname,const char* description, DEC_SCOREDATA* scoredata, SCIP_RETCODE (*scorefree) (SCIP* scip, DEC_SCORE* score), SCIP_RETCODE (*scorecalc) (SCIP* scip, DEC_SCORE* score, int partialdecid, SCIP_Real* scorevalue)) + DEC_SCOREDATA* GCGscoreGetData(DEC_SCORE* score) + int GCGgetNScores(SCIP* scip) + DEC_SCORE** GCGgetScores(SCIP* scip) + const char* GCGscoreGetName(DEC_SCORE* score) + DEC_SCORE* GCGfindScore(SCIP* scip, const char* name) + cdef extern from "gcg/pub_gcgsepa.h": SCIP_RETCODE GCGsetSeparators(SCIP* scip, SCIP_PARAMSETTING paramsetting) @@ -321,24 +334,7 @@ cdef extern from "gcg/class_partialdecomp.h" namespace "gcg": void setPctVarsToBlockVector(vector[double] newvector) except + void setPctVarsFromFreeVector(vector[double] newvector) except + void setDetectorClockTimes(vector[double] newvector) except + - double getClassicScore() except + - void setClassicScore(double score) except + - double getBorderAreaScore() except + - void setBorderAreaScore(double score) except + double getMaxWhiteScore() except + - void setMaxWhiteScore(double score) except + - double getMaxForWhiteScore() except + - void setMaxForWhiteScore(double score) except + - double getSetPartForWhiteScore() except + - void setSetPartForWhiteScore(double score) except + - double getMaxForWhiteAggScore() except + - void setMaxForWhiteAggScore(double score) except + - double getSetPartForWhiteAggScore() except + - void setSetPartForWhiteAggScore(double score) except + - double getBendersScore() except + - void setBendersScore(double score) except + - double getStrongDecompScore() except + - void setStrongDecompScore(double score) except + void prepare() except + bool aggInfoCalculated() except + void calcAggregationInformation(bool ignoreDetectionLimits) except + @@ -346,9 +342,9 @@ cdef extern from "gcg/class_partialdecomp.h" namespace "gcg": int getTranslatedpartialdecid() except + void setTranslatedpartialdecid(int decid) except + void buildDecChainString(char * buffer) except + - bool fixConsToBlock(SCIP_CONS* cons, int block) bool fixConsToMaster(SCIP_CONS* cons) + SCIP_Real getScore(DEC_SCORE* score) except + cdef extern from "gcg/class_detprobdata.h" namespace "gcg": diff --git a/src/pygcgopt/gcg.pyx b/src/pygcgopt/gcg.pyx index 610e130..b6fd43d 100644 --- a/src/pygcgopt/gcg.pyx +++ b/src/pygcgopt/gcg.pyx @@ -23,6 +23,7 @@ from collections.abc import Iterable include "detector.pxi" include "pricing_solver.pxi" +include "score.pxi" include "partition.pxi" include "decomposition.pxi" include "detprobdata.pxi" @@ -149,6 +150,16 @@ cdef class Model(SCIPModel): return decomps + def getPartDecompFromID(self, id): + """returns a partial decomposition regarding to the given partialdecomp id + + :param id: patial decomposition id as int + :return: PartialDecomposition object + """ + cdef PartialDecomposition pd = PartialDecomposition.create(GCGconshdlrDecompGetPartialdecFromID(self._scip, id)) + + return pd + def addDecompositionFromConss(self, master_conss, *block_conss): """Adds a user specified decomposition to GCG based on constraints. @@ -370,6 +381,25 @@ cdef class Model(SCIPModel): detector.detectorname = detectorname Py_INCREF(detector) + def includeScore(self, Score score, scorename, shortname, desc): + """includes a score + + :param detector: An object of a subclass of detector#Detector. + :param detectorname: name of the detector + + For an explanation for all arguments, see :meth:`DECincludeDetector()`. + """ + c_scorename = str_conversion(scorename) + c_shortname = str_conversion(shortname) + c_desc = str_conversion(desc) + PY_SCIP_CALL(GCGincludeScore( + self._scip, c_scorename, c_shortname, c_desc, + score, PyScoreFree, PyScoreCalculate)) + + score.model = weakref.proxy(self) + score.scorename = scorename + Py_INCREF(score) + def listDetectors(self): """Lists all detectors that are currently included @@ -386,6 +416,16 @@ cdef class Model(SCIPModel): return [DECdetectorGetName(detectors[i]).decode('utf-8') for i in range(n_detectors)] + def listScores(self): + """Lists all scores that are currently included + + :return: A list of strings of the score names + """ + cdef int n_scores = GCGgetNScores(self._scip) + cdef DEC_SCORE** scores = GCGgetScores(self._scip) + + return [GCGscoreGetName(scores[i]).decode('utf-8') for i in range(n_scores)] + def setDetectorEnabled(self, detector_name, is_enabled=True): """Enables or disables a detector for detecting partial decompositions. diff --git a/src/pygcgopt/score.pxi b/src/pygcgopt/score.pxi new file mode 100644 index 0000000..9c4a8fa --- /dev/null +++ b/src/pygcgopt/score.pxi @@ -0,0 +1,30 @@ +cdef class Score: + """Base class of the Score Plugin""" + + cdef public SCIPModel model + cdef public str scorename + + def scorefree(self): + '''calls destructor and frees memory of score''' + pass + + def scorecalculate(self, partialdec): + '''calls calculate method of score''' + return {} + +cdef SCIP_RETCODE PyScoreFree(SCIP* scip, DEC_SCORE* score) with gil: + cdef DEC_SCOREDATA* scoredata + scoredata = GCGscoreGetData(score) + py_score = scoredata + py_score.scorefree() + Py_DECREF(py_score) + return SCIP_OKAY + +cdef SCIP_RETCODE PyScoreCalculate(SCIP* scip, DEC_SCORE* score, int partialdecid, SCIP_Real* scorevalue) with gil: + cdef DEC_SCOREDATA* scoredata + scoredata = GCGscoreGetData(score) + py_score = scoredata + partialdec = py_score.model.getPartDecompFromID(partialdecid) + result_dict = py_score.scorecalculate(partialdec) + scorevalue[0] = result_dict.get("scorevalue", scorevalue[0]) + return SCIP_OKAY diff --git a/tests/test_pricing_solver.py b/tests/test_pricing_solver.py index 5dff5f3..6b54754 100644 --- a/tests/test_pricing_solver.py +++ b/tests/test_pricing_solver.py @@ -27,7 +27,7 @@ def solve(self, pricingprob, probnr, dualsolconv): if pricingprob.getNBinVars() + pricingprob.getNIntVars() < len(vars): return {"status": GCG_PRICINGSTATUS.NOTAPPLICABLE} for var_name in vars: - if pricingprob.isNegative(vars[var_name].getLbLocal()): + if vars[var_name].getLbLocal() < 0: return {"status": GCG_PRICINGSTATUS.NOTAPPLICABLE} conss = pricingprob.getConss() if len(conss) != 1: @@ -35,13 +35,12 @@ def solve(self, pricingprob, probnr, dualsolconv): cons = conss[0] - conshdlr_name = cons.getConstraintHandlerName() - if conshdlr_name == "linear": - if not pricingprob.isIntegral(pricingprob.getRhs(cons)) or not pricingprob.isInfinity(-pricingprob.getLhs(cons)): + if cons.isLinear(): + if not pricingprob.getRhs(cons).is_integer() or not pricingprob.isInfinity(-pricingprob.getLhs(cons)): return {"status": GCG_PRICINGSTATUS.NOTAPPLICABLE} consvals = pricingprob.getValsLinear(cons) - if not all(pricingprob.isIntegral(v) for v in consvals.values()): + if not all(v.is_integer() for v in consvals.values()): return {"status": GCG_PRICINGSTATUS.NOTAPPLICABLE} consvals = {k: floor(v) for k, v in consvals.items()} @@ -64,8 +63,6 @@ def solve(self, pricingprob, probnr, dualsolconv): ubs[var_name] = floor(abs(prelcapacity / consval)) else: ubs[var_name] = vars[var_name].getUbLocal() - elif conshdlr_name == "knapsack": - raise NotImplementedError("knapsack constraints are not implemented") else: return {"status": GCG_PRICINGSTATUS.NOTAPPLICABLE} @@ -117,12 +114,11 @@ def solve(self, pricingprob, probnr, dualsolconv): solvals = defaultdict(int) for idx in range(len(item_var_map)): var_name = item_var_map[idx] - assert(consvals[var_name] >= 0 or not vars[var_name].isNegated()) if idx in solitems: - if consvals[var_name] >= 0 and not vars[var_name].isNegated(): + if consvals[var_name] >= 0: solvals[var_name] += 1.0 else: - if consvals[var_name] < 0 or vars[var_name].isNegated(): + if consvals[var_name] < 0: solvals[var_name] += 1.0 for var_name, var in vars.items(): @@ -155,7 +151,7 @@ def test_pypricer_fast(lp_file, dec_file): m.optimize() - assert(m.getStatus() == "optimal") + assert m.getStatus() == "optimal" gcg_pricer_sol_obj_val = m.getSolObjVal(m.getBestSol()) @@ -169,10 +165,13 @@ def test_pypricer_fast(lp_file, dec_file): proxyKnapsackSolver = PyKnapsackSolver() m.includePricingSolver(proxyKnapsackSolver, "pyknapsack", "Python ortools knapsack pricing solver", 300, False, True) + assert "pyknapsack" in m.listPricingSolvers() + m.optimize() - assert(m.getStatus() == "optimal") + assert m.getStatus() == "optimal" py_pricer_sol_obj_val = m.getSolObjVal(m.getBestSol()) - assert(gcg_pricer_sol_obj_val == py_pricer_sol_obj_val) + assert gcg_pricer_sol_obj_val == py_pricer_sol_obj_val + diff --git a/tests/test_score.py b/tests/test_score.py new file mode 100644 index 0000000..82a664a --- /dev/null +++ b/tests/test_score.py @@ -0,0 +1,31 @@ +from pygcgopt import Model, Score + +import pytest + +class PyScore(Score): + def scorecalculate(self, partialdec): + nmasterconss = float(partialdec.getNMasterconss()) + nconss = float(partialdec.getNConss()) + score = 1 - nmasterconss/nconss + + return {"scorevalue": score} + +@pytest.mark.parametrize("lp_file", [ + "instances_bpp/N1C1W4_M.BPP.lp", "instances_bpp/N1C2W2_O.BPP.lp" +]) +def test_score(lp_file): + + m = Model() + + proxyScore = PyScore() + m.includeScore(proxyScore, "pyscore", "python", "Python score test") + assert "pyscore" in m.listScores() + + m.readProblem(lp_file) + m.detect() + + partdecs = m.listDecompositions() + + for partdec in partdecs: + assert partdec.getScore(proxyScore) == 1 - float(partdec.getNMasterconss())/float(partdec.getNConss()) +