diff --git a/CHANGELOG.md b/CHANGELOG.md index 83daa0b28..3390a6d94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased ### Added +- Wrapped SCIPgetChildren and added getChildren and test (also test getOpenNodes) +- Wrapped SCIPgetLeaves, SCIPgetNLeaves, and added getLeaves, getNLeaves and test +- Wrapped SCIPgetSiblings, SCIPgetNSiblings, and added getSiblings, getNSiblings and test +- Wrapped SCIPdeactivatePricer, SCIPsetConsModifiable, and added deactivatePricer, setModifiable and test - Added getLinearConsIndicator - Added SCIP_LPPARAM, setIntParam, setRealParam, getIntParam, getRealParam, isOptimal, getObjVal, getRedcost for lpi - Added isFeasPositive diff --git a/src/pyscipopt/pricer.pxi b/src/pyscipopt/pricer.pxi index a218b254c..b6f0b02d7 100644 --- a/src/pyscipopt/pricer.pxi +++ b/src/pyscipopt/pricer.pxi @@ -2,6 +2,7 @@ #@brief Base class of the Pricers Plugin cdef class Pricer: cdef public Model model + cdef SCIP_PRICER* scip_pricer def pricerfree(self): '''calls destructor and frees memory of variable pricer ''' diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index aaf38a77c..2180d05f1 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -875,6 +875,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPsetConsChecked(SCIP *scip, SCIP_CONS *cons, SCIP_Bool check) SCIP_RETCODE SCIPsetConsRemovable(SCIP *scip, SCIP_CONS *cons, SCIP_Bool removable) SCIP_RETCODE SCIPsetConsInitial(SCIP *scip, SCIP_CONS *cons, SCIP_Bool initial) + SCIP_RETCODE SCIPsetConsModifiable(SCIP *scip, SCIP_CONS *cons, SCIP_Bool modifiable) SCIP_RETCODE SCIPsetConsEnforced(SCIP *scip, SCIP_CONS *cons, SCIP_Bool enforce) # Primal Solution Methods @@ -998,6 +999,7 @@ cdef extern from "scip/scip.h": SCIP_PRICERDATA* pricerdata) SCIP_PRICER* SCIPfindPricer(SCIP* scip, const char* name) SCIP_RETCODE SCIPactivatePricer(SCIP* scip, SCIP_PRICER* pricer) + SCIP_RETCODE SCIPdeactivatePricer(SCIP* scip, SCIP_PRICER* pricer) SCIP_PRICERDATA* SCIPpricerGetData(SCIP_PRICER* pricer) # Constraint handler plugin @@ -1934,10 +1936,13 @@ cdef extern from "scip/pub_lp.h": cdef extern from "scip/scip_tree.h": SCIP_RETCODE SCIPgetOpenNodesData(SCIP* scip, SCIP_NODE*** leaves, SCIP_NODE*** children, SCIP_NODE*** siblings, int* nleaves, int* nchildren, int* nsiblings) - SCIP_Longint SCIPgetNLeaves(SCIP* scip) + SCIP_RETCODE SCIPgetChildren(SCIP* scip, SCIP_NODE*** children, int* nchildren) SCIP_Longint SCIPgetNChildren(SCIP* scip) - SCIP_Longint SCIPgetNSiblings(SCIP* scip) SCIP_NODE* SCIPgetBestChild(SCIP* scip) + SCIP_RETCODE SCIPgetSiblings(SCIP* scip, SCIP_NODE*** siblings, int* nsiblings) + SCIP_RETCODE SCIPgetNSiblings(SCIP* scip) + SCIP_RETCODE SCIPgetLeaves(SCIP* scip, SCIP_NODE*** leaves, int* nleaves) + SCIP_Longint SCIPgetNLeaves(SCIP* scip) SCIP_NODE* SCIPgetBestSibling(SCIP* scip) SCIP_NODE* SCIPgetBestLeaf(SCIP* scip) SCIP_NODE* SCIPgetPrioChild(SCIP* scip) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index e8f39a63c..67e019387 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -4178,7 +4178,58 @@ cdef class Model: """ return Node.create(SCIPgetBestChild(self._scip)) + + def getChildren(self): + """ + Gets the children of the focus node. + + Returns + ------- + list of Nodes + + """ + cdef SCIP_NODE** _children + cdef int n_children + cdef int i + + PY_SCIP_CALL(SCIPgetChildren(self._scip, &_children, &n_children)) + + return [Node.create(_children[i]) for i in range(n_children)] + + def getSiblings(self): + """ + Gets the siblings of the focus node. + Returns + ------- + list of Nodes + + """ + cdef SCIP_NODE** _siblings + cdef int n_siblings + cdef int i + + PY_SCIP_CALL(SCIPgetSiblings(self._scip, &_siblings, &n_siblings)) + + return [Node.create(_siblings[i]) for i in range(n_siblings)] + + def getLeaves(self): + """ + Gets the leaves of the tree along with number of leaves. + + Returns + ------- + list of Nodes + + """ + cdef SCIP_NODE** _leaves + cdef int n_leaves + cdef int i + + PY_SCIP_CALL(SCIPgetLeaves(self._scip, &_leaves, &n_leaves)) + + return [Node.create(_leaves[i]) for i in range(n_leaves)] + def getBestSibling(self): """ Gets the best sibling of the focus node w.r.t. the node selection strategy. @@ -6410,6 +6461,18 @@ cdef class Model: """ PY_SCIP_CALL(SCIPsetConsInitial(self._scip, cons.scip_cons, newInit)) + + def setModifiable(self, Constraint cons, newMod): + """ + Set "modifiable" flag of a constraint. + + Parameters + ---------- + cons : Constraint + newMod : bool + + """ + PY_SCIP_CALL(SCIPsetConsModifiable(self._scip, cons.scip_cons, newMod)) def setRemovable(self, Constraint cons, newRem): """ @@ -7615,6 +7678,7 @@ cdef class Model: PY_SCIP_CALL(SCIPactivatePricer(self._scip, scip_pricer)) pricer.model = weakref.proxy(self) Py_INCREF(pricer) + pricer.scip_pricer = scip_pricer def includeConshdlr(self, Conshdlr conshdlr, name, desc, sepapriority=0, enfopriority=0, chckpriority=0, sepafreq=-1, propfreq=-1, @@ -7676,6 +7740,18 @@ cdef class Model: conshdlr.name = name Py_INCREF(conshdlr) + def deactivatePricer(self, Pricer pricer): + """ + Deactivate the given pricer. + + Parameters + ---------- + pricer : Pricer + the pricer to deactivate + """ + cdef SCIP_PRICER* scip_pricer + PY_SCIP_CALL(SCIPdeactivatePricer(self._scip, pricer.scip_pricer)) + def copyLargeNeighborhoodSearch(self, to_fix, fix_vals) -> Model: """ Creates a configured copy of the transformed problem and applies provided fixings intended for LNS heuristics. diff --git a/tests/test_node.py b/tests/test_node.py index 91e6139ae..9565f0de8 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -1,4 +1,4 @@ -from pyscipopt import SCIP_RESULT, Eventhdlr, SCIP_EVENTTYPE +from pyscipopt import SCIP_RESULT, Eventhdlr, SCIP_EVENTTYPE, scip from helpers.utils import random_mip_1 class cutoffEventHdlr(Eventhdlr): @@ -18,4 +18,43 @@ def test_cutoffNode(): m.optimize() + assert m.getNSols() == 0 + +class focusEventHdlr(Eventhdlr): + def eventinit(self): + self.model.catchEvent(SCIP_EVENTTYPE.NODEFOCUSED, self) + + def eventexec(self, event): + assert self.model.getNSiblings() in [0,1] + assert len(self.model.getSiblings()) == self.model.getNSiblings() + for node in self.model.getSiblings(): + assert isinstance(node, scip.Node) + + assert self.model.getNLeaves() >= 0 + assert len(self.model.getLeaves()) == self.model.getNLeaves() + for node in self.model.getLeaves(): + assert isinstance(node, scip.Node) + + assert self.model.getNChildren() >= 0 + assert len(self.model.getChildren()) == self.model.getNChildren() + for node in self.model.getChildren(): + assert isinstance(node, scip.Node) + + leaves, children, siblings = self.model.getOpenNodes() + assert leaves == self.model.getLeaves() + assert children == self.model.getChildren() + assert siblings == self.model.getSiblings() + + return {'result': SCIP_RESULT.SUCCESS} + +def test_tree_methods(): + m = random_mip_1(disable_heur=True, disable_presolve=True, disable_sepa=True) + m.setParam("limits/nodes", 10) + + hdlr = focusEventHdlr() + + m.includeEventhdlr(hdlr, "test", "test") + + m.optimize() + assert m.getNSols() == 0 \ No newline at end of file diff --git a/tests/test_pricer.py b/tests/test_pricer.py index 73cb438d8..647e26e90 100644 --- a/tests/test_pricer.py +++ b/tests/test_pricer.py @@ -67,6 +67,12 @@ def pricerredcost(self): self.data['patterns'].append(newPattern) self.data['var'].append(newVar) + if self.data["deactivate"]: + # Testing deactivatePricer + self.model.deactivatePricer(self) + for c in self.model.getConss(): + self.model.setModifiable(c, False) + return {'result':SCIP_RESULT.SUCCESS} # The initialisation function for the variable pricer to retrieve the transformed constraints of the problem @@ -124,6 +130,7 @@ def test_cuttingstock(): pricer.data['rollLength'] = rollLength pricer.data['patterns'] = patterns pricer.data['redcosts'] = [] + pricer.data["deactivate"] = False # solve problem s.optimize() @@ -174,4 +181,67 @@ class IncompletePricer(Pricer): model.includePricer(pricer, "", "") with pytest.raises(Exception): - model.optimize() \ No newline at end of file + model.optimize() + +def test_deactivate_pricer(): + # create solver instance + s = Model("CuttingStock") + + s.setPresolve(0) + s.data = {} + s.data["nSols"] = 0 + + # creating a pricer + pricer = CutPricer() + s.includePricer(pricer, "CuttingStockPricer", "Pricer to identify new cutting stock patterns") + + # item widths + widths = [14, 31, 36, 45] + # width demand + demand = [211, 395, 610, 97] + # roll length + rollLength = 100 + assert len(widths) == len(demand) + + # adding the initial variables + cutPatternVars = [] + varNames = [] + varBaseName = "Pattern" + patterns = [] + + for i in range(len(widths)): + varNames.append(varBaseName + "_" + str(i)) + cutPatternVars.append(s.addVar(varNames[i], obj = 1.0)) + + # adding a linear constraint for the knapsack constraint + demandCons = [] + for i in range(len(widths)): + numWidthsPerRoll = float(int(rollLength/widths[i])) + demandCons.append(s.addCons(numWidthsPerRoll*cutPatternVars[i] >= demand[i], + separate = False, modifiable = True)) + newPattern = [0]*len(widths) + newPattern[i] = numWidthsPerRoll + patterns.append(newPattern) + + # Setting the pricer_data for use in the init and redcost functions + pricer.data = {} + pricer.data['var'] = cutPatternVars + pricer.data['cons'] = demandCons + pricer.data['widths'] = widths + pricer.data['demand'] = demand + pricer.data['rollLength'] = rollLength + pricer.data['patterns'] = patterns + pricer.data['redcosts'] = [] + pricer.data["deactivate"] = True + + for c in s.getConss(): + c.isModifiable() + + # solve problem + s.optimize() + + for c in s.getConss(): + assert not c.isModifiable() + + # the optimal solution with normal pricing + assert s.isGT(s.getObjVal(), 452.25)