Skip to content

Add deactivatePricer #973

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/pyscipopt/pricer.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -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 '''
Expand Down
9 changes: 7 additions & 2 deletions src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
76 changes: 76 additions & 0 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@
if rc == SCIP_OKAY:
pass
elif rc == SCIP_ERROR:
raise Exception('SCIP: unspecified error!')

Check failure on line 301 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / test-coverage (3.11)

SCIP: unspecified error!
elif rc == SCIP_NOMEMORY:
raise MemoryError('SCIP: insufficient memory error!')
elif rc == SCIP_READERROR:
Expand Down Expand Up @@ -4178,7 +4178,58 @@

"""
return Node.create(SCIPgetBestChild(self._scip))

def getChildren(self):

Check warning on line 4182 in src/pyscipopt/scip.pxi

View check run for this annotation

Codecov / codecov/patch

src/pyscipopt/scip.pxi#L4182

Added line #L4182 was not covered by tests
"""
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):

Check warning on line 4199 in src/pyscipopt/scip.pxi

View check run for this annotation

Codecov / codecov/patch

src/pyscipopt/scip.pxi#L4199

Added line #L4199 was not covered by tests
"""
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):

Check warning on line 4216 in src/pyscipopt/scip.pxi

View check run for this annotation

Codecov / codecov/patch

src/pyscipopt/scip.pxi#L4216

Added line #L4216 was not covered by tests
"""
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.
Expand Down Expand Up @@ -6410,6 +6461,18 @@

"""
PY_SCIP_CALL(SCIPsetConsInitial(self._scip, cons.scip_cons, newInit))

def setModifiable(self, Constraint cons, newMod):

Check warning on line 6465 in src/pyscipopt/scip.pxi

View check run for this annotation

Codecov / codecov/patch

src/pyscipopt/scip.pxi#L6465

Added line #L6465 was not covered by tests
"""
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):
"""
Expand Down Expand Up @@ -7615,6 +7678,7 @@
PY_SCIP_CALL(SCIPactivatePricer(self._scip, scip_pricer))
pricer.model = <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,
Expand Down Expand Up @@ -7676,6 +7740,18 @@
conshdlr.name = name
Py_INCREF(conshdlr)

def deactivatePricer(self, Pricer pricer):

Check warning on line 7743 in src/pyscipopt/scip.pxi

View check run for this annotation

Codecov / codecov/patch

src/pyscipopt/scip.pxi#L7743

Added line #L7743 was not covered by tests
"""
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.
Expand Down
41 changes: 40 additions & 1 deletion tests/test_node.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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
72 changes: 71 additions & 1 deletion tests/test_pricer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -174,4 +181,67 @@ class IncompletePricer(Pricer):
model.includePricer(pricer, "", "")

with pytest.raises(Exception):
model.optimize()
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)