Skip to content

Recipe for getting local constraints #1014

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased
### Added
- Added recipe for getting local constraints
- More support for AND-Constraints
- Added support for knapsack constraints
- Added isPositive(), isNegative(), isFeasLE(), isFeasLT(), isFeasGE(), isFeasGT(), isHugeValue(), and tests
Expand Down
50 changes: 50 additions & 0 deletions src/pyscipopt/recipes/getLocalConss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from pyscipopt import Model, Constraint

def getLocalConss(model: Model, node = None) -> list[Constraint]:
"""
Returns local constraints.

Parameters
----------
model : Model
The model from which to retrieve the local constraints.
node : Node, optional
The node from which to retrieve the local constraints. If not provided, the current node is used.

Returns
-------
list[Constraint]
A list of local constraints. First entry are global constraints, second entry are all the added constraints.
"""

if not node:
assert model.getStageName() in ["INITPRESOLVE", "PRESOLVING", "EXITPRESOLVE", "SOLVING"], "Model cannot be called in stage %s." % model.getStageName()
cur_node = model.getCurrentNode()
else:
cur_node = node

added_conss = []
while cur_node is not None:
added_conss = cur_node.getAddedConss() + added_conss
cur_node = cur_node.getParent()

return [model.getConss(), added_conss]

def getNLocalConss(model: Model, node = None) -> list[int]:
"""
Returns the number of local constraints of a node.

Parameters
----------
model : Model
The model from which to retrieve the number of local constraints.
node : Node, optional
The node from which to retrieve the number of local constraints. If not provided, the current node is used.

Returns
-------
list[int]
A list of the number of local constraints. First entry is the number of global constraints, second entry is the number of all the added constraints.
"""
local_conss = getLocalConss(model, node)
return [len(local_conss[0]), len(local_conss[1])]
1 change: 0 additions & 1 deletion src/pyscipopt/recipes/nonlinear.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from pyscipopt import Model


def set_nonlinear_objective(model: Model, expr, sense="minimize"):
"""
Takes a nonlinear expression and performs an epigraph reformulation.
Expand Down
1 change: 1 addition & 0 deletions src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,7 @@ cdef extern from "scip/scip.h":
SCIP_RETCODE SCIPwriteOrigProblem(SCIP* scip, char* filename, char* extension, SCIP_Bool genericnames)
SCIP_RETCODE SCIPwriteTransProblem(SCIP* scip, char* filename, char* extension, SCIP_Bool genericnames)
SCIP_RETCODE SCIPwriteLP(SCIP* scip, const char*)
SCIP_RETCODE SCIPwriteMIP(SCIP * scip, const char * filename, SCIP_Bool genericnames, SCIP_Bool origobj, SCIP_Bool lazyconss)
SCIP_STATUS SCIPgetStatus(SCIP* scip)
SCIP_Real SCIPepsilon(SCIP* scip)
SCIP_Real SCIPfeastol(SCIP* scip)
Expand Down
23 changes: 23 additions & 0 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@
if rc == SCIP_OKAY:
pass
elif rc == SCIP_ERROR:
raise Exception('SCIP: unspecified error!')

Check failure on line 304 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 @@ -9392,6 +9392,29 @@
PY_SCIP_CALL( SCIPwriteLP(self._scip, absfile) )

locale.setlocale(locale.LC_NUMERIC,user_locale)

def writeMIP(self, filename, genericnames=False, origobj=False, lazyconss=False):
"""
Writes MIP relaxation of the current branch-and-bound node to a file

Parameters
----------
filename : str
name of the output file
genericnames : bool, optional
should generic names like x_i and row_j be used in order to avoid troubles with reserved symbols? (Default value = False)
origobj : bool, optional
should the original objective function be used (Default value = False)
lazyconss : bool, optional
output removable rows as lazy constraints? (Default value = False)
"""
user_locale = locale.getlocale(category=locale.LC_NUMERIC)
locale.setlocale(locale.LC_NUMERIC, "C")

absfile = str_conversion(abspath(filename))
PY_SCIP_CALL(SCIPwriteMIP(self._scip, absfile, genericnames, origobj, lazyconss))

locale.setlocale(locale.LC_NUMERIC,user_locale)

def createSol(self, Heur heur = None, initlp=False):
"""
Expand Down
38 changes: 38 additions & 0 deletions tests/test_recipe_getLocalConss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from pyscipopt import Model, SCIP_EVENTTYPE
from pyscipopt.recipes.getLocalConss import *
from helpers.utils import random_mip_1

def localconss(model, event):
local_conss = getLocalConss(model)
assert len(local_conss[1]) == getNLocalConss(model)[1]
assert len(local_conss[0]) == len(model.getConss())
assert local_conss[0] == model.getConss()

vars = model.getVars()
if model.getCurrentNode().getNumber() == 1:
pass

elif model.getCurrentNode().getNumber() == 2:
model.data["local_cons1"] = model.addCons(vars[0] + vars[1] <= 1, name="c1", local=True)
assert getNLocalConss(model)[1] == 1
assert getLocalConss(model)[1][0] == model.data["local_cons1"]

elif model.getCurrentNode().getNumber() == 4:
local_conss = getLocalConss(model)
model.data["local_cons2"] = model.addCons(vars[1] + vars[2] <= 1, name="c2", local=True)
model.data["local_cons3"] = model.addCons(vars[2] + vars[3] <= 1, name="c3", local=True)
assert getNLocalConss(model)[1] == 3
assert getLocalConss(model)[1][0] == model.data["local_cons1"]
assert getLocalConss(model)[1][1] == model.data["local_cons2"]
assert getLocalConss(model)[1][2] == model.data["local_cons3"]

elif model.getCurrentNode().getParent().getNumber() not in [2,4]:
assert getLocalConss(model) == [model.getConss(), []]

def test_getLocalConss():
model = random_mip_1(node_lim=4)
model.data = {}

model.attachEventHandlerCallback(localconss, [SCIP_EVENTTYPE.NODEFOCUSED])
model.optimize()
assert len(model.data) == 3