Skip to content

Commit

Permalink
Merge pull request #732 from Joao-Dionisio/delete-double-declaration
Browse files Browse the repository at this point in the history
Add getConsNVars. Add two convenience functions. Add tests. Incomplete getConsVars.
  • Loading branch information
mmghannam authored Oct 27, 2023
2 parents 80e4077 + d0ef86c commit b84ce3a
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 16 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

## Unreleased
### Added
- Add SCIP functions SCIPconsGetNVars, SCIPconsGetVars
- Add SCIP functions SCIPchgCoefLinear, SCIPaddCoefLinear and SCIPdelCoefLinear
- Add SCIP function SCIPgetSolTime and wrapper getSolTime
- Add convenience methods relax and getVarDict
### Fixed
- Pricer plugin fundamental callbacks now raise an error if not implemented
- Brachrule plugin fundamental callbacks now raise an error if not implemented
- Fixed segmentation fault when accessing the Solution class directly
- Changed getSols so that it prints solutions in terms of the original variables
- Fixed error message in _checkStage
### Changed
- Improved error message when using < or > instead of <= or >=
### Removed
Expand Down
2 changes: 2 additions & 0 deletions src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,8 @@ cdef extern from "scip/scip.h":
SCIP_RETCODE SCIPreleaseCons(SCIP* scip, SCIP_CONS** cons)
SCIP_RETCODE SCIPtransformCons(SCIP* scip, SCIP_CONS* cons, SCIP_CONS** transcons)
SCIP_RETCODE SCIPgetTransformedCons(SCIP* scip, SCIP_CONS* cons, SCIP_CONS** transcons)
SCIP_RETCODE SCIPgetConsVars(SCIP* scip, SCIP_CONS* cons, SCIP_VAR** vars, int varssize, SCIP_Bool* success)
SCIP_RETCODE SCIPgetConsNVars(SCIP* scip, SCIP_CONS* cons, int* nvars, SCIP_Bool* success)
SCIP_CONS** SCIPgetConss(SCIP* scip)
const char* SCIPconsGetName(SCIP_CONS* cons)
int SCIPgetNConss(SCIP* scip)
Expand Down
71 changes: 65 additions & 6 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -584,8 +584,8 @@ cdef class Solution:

def _checkStage(self, method):
if method in ["SCIPgetSolVal", "getSolObjVal"]:
if self.sol == NULL and not SCIPgetStage(self.scip) == SCIP_STAGE_SOLVING:
raise Warning(f"{method} can only be called in stage SOLVING with a valid solution (current stage: {SCIPgetStage(self.scip)})")
if self.sol == NULL and SCIPgetStage(self.scip) != SCIP_STAGE_SOLVING:
raise Warning(f"{method} can only be called with a valid solution or in stage SOLVING (current stage: {SCIPgetStage(self.scip)})")


cdef class BoundChange:
Expand Down Expand Up @@ -1338,7 +1338,6 @@ cdef class Model:
else:
return SCIPgetTransObjoffset(self._scip)


def setObjIntegral(self):
"""informs SCIP that the objective value is always integral in every feasible solution
Note: This function should be used to inform SCIP that the objective function is integral, helping to improve the
Expand Down Expand Up @@ -1615,7 +1614,6 @@ cdef class Model:
ub = SCIPinfinity(self._scip)
PY_SCIP_CALL(SCIPchgVarUb(self._scip, var.scip_var, ub))


def chgVarLbGlobal(self, Variable var, lb):
"""Changes the global lower bound of the specified variable.
Expand Down Expand Up @@ -1729,6 +1727,13 @@ cdef class Model:
def getNBinVars(self):
"""gets number of binary active problem variables"""
return SCIPgetNBinVars(self._scip)

def getVarDict(self):
"""gets dictionary with variables names as keys and current variable values as items"""
var_dict = {}
for var in self.getVars():
var_dict[var.name] = self.getVal(var)
return var_dict

def updateNodeLowerbound(self, Node node, lb):
"""if given value is larger than the node's lower bound (in transformed problem),
Expand All @@ -1739,6 +1744,14 @@ cdef class Model:
"""
PY_SCIP_CALL(SCIPupdateNodeLowerbound(self._scip, node.scip_node, lb))

def relax(self):
"""Relaxes the integrality restrictions of the model"""
if self.getStage() != SCIP_STAGE_PROBLEM:
raise Warning("method can only be called in stage PROBLEM")

for var in self.getVars():
self.chgVarType(var, "C")

# Node methods
def getBestChild(self):
Expand Down Expand Up @@ -2126,6 +2139,52 @@ cdef class Model:

return constraints

def getConsNVars(self, Constraint constraint):
"""
Gets number of variables in a constraint.
:param constraint: Constraint to get the number of variables from.
"""
cdef int nvars
cdef SCIP_Bool success

PY_SCIP_CALL(SCIPgetConsNVars(self._scip, constraint.scip_cons, &nvars, &success))

if not success:
conshdlr = SCIPconsGetHdlr(constraint.scip_cons)
conshdrlname = SCIPconshdlrGetName(conshdlr)
raise TypeError("The constraint handler %s does not have this functionality." % conshdrlname)

return nvars

def getConsVars(self, Constraint constraint):
"""
Gets variables in a constraint.
:param constraint: Constraint to get the variables from.
"""
cdef SCIP_Bool success
cdef int _nvars

SCIPgetConsNVars(self._scip, constraint.scip_cons, &_nvars, &success)

cdef SCIP_VAR** _vars = <SCIP_VAR**> malloc(_nvars * sizeof(SCIP_VAR*))
SCIPgetConsVars(self._scip, constraint.scip_cons, _vars, _nvars*sizeof(SCIP_VAR), &success)

vars = []
for i in range(_nvars):
ptr = <size_t>(_vars[i])
# check whether the corresponding variable exists already
if ptr in self._modelvars:
vars.append(self._modelvars[ptr])
else:
# create a new variable
var = Variable.create(_vars[i])
assert var.ptr() == ptr
self._modelvars[ptr] = var
vars.append(var)
return vars

def printCons(self, Constraint constraint):
return PY_SCIP_CALL(SCIPprintCons(self._scip, constraint.scip_cons, NULL))

Expand Down Expand Up @@ -3993,7 +4052,7 @@ cdef class Model:

return ([Variable.create(pseudocands[i]) for i in range(npseudocands)], npseudocands, npriopseudocands)

def branchVar(self, variable):
def branchVar(self, Variable variable):
"""Branch on a non-continuous variable.
:param variable: Variable to branch on
Expand Down Expand Up @@ -5031,4 +5090,4 @@ def is_memory_freed():
return BMSgetMemoryUsed() == 0

def print_memory_in_use():
BMScheckEmptyMemory()
BMScheckEmptyMemory()
27 changes: 27 additions & 0 deletions tests/test_cons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pyscipopt import Model, quicksum
import random


def test_getConsNVars():
n_vars = random.randint(100,1000)
m = Model()
x = {}
for i in range(n_vars):
x[i] = m.addVar("%i"%i)

c = m.addCons(quicksum(x[i] for i in x) <= 10)
assert m.getConsNVars(c) == n_vars

m.optimize()
assert m.getConsNVars(c) == n_vars

def test_getConsVars():
n_vars = random.randint(100,1000)
m = Model()
x = {}
for i in range(n_vars):
x[i] = m.addVar("%i"%i)

c = m.addCons(quicksum(x[i] for i in x) <= 1)
assert m.getConsVars(c) == [x[i] for i in x]

7 changes: 5 additions & 2 deletions tests/test_memory.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pytest
from pyscipopt.scip import Model, is_memory_freed
from pyscipopt.scip import Model, is_memory_freed, print_memory_in_use
from util import is_optimized_mode

def test_not_freed():
Expand All @@ -15,6 +15,9 @@ def test_freed():
del m
assert is_memory_freed()

def test_print_memory_in_use():
print_memory_in_use()

if __name__ == "__main__":
test_not_freed()
test_freed()
test_freed()
42 changes: 34 additions & 8 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,37 @@ def test_model_ptr():
with pytest.raises(ValueError):
Model.from_ptr("some gibberish", take_ownership=False)


if __name__ == "__main__":
test_model()
test_solve_concurrent()
test_multiple_cons_simple()
test_multiple_cons_names()
test_multiple_cons_params()
test_model_ptr()
def test_model_relax():
model = Model()
x = {}
for i in range(5):
x[i] = model.addVar(lb = -i, ub = i, vtype="C")
for i in range(10,15):
x[i] = model.addVar(lb = -i, ub = i, vtype="I")
for i in range(20,25):
x[i] = model.addVar(vtype="B")

model.relax()
for v in x.values():
var_lb = v.getLbGlobal()
var_ub = v.getUbGlobal()
assert v.getLbGlobal() == var_lb
assert v.getUbGlobal() == var_ub
assert v.vtype() == "CONTINUOUS"

def test_getVarsDict():
model = Model()
x = {}
for i in range(5):
x[i] = model.addVar(lb = -i, ub = i, vtype="C")
for i in range(10,15):
x[i] = model.addVar(lb = -i, ub = i, vtype="I")
for i in range(20,25):
x[i] = model.addVar(vtype="B")

model.hideOutput()
model.optimize()
var_dict = model.getVarDict()
for v in x.values():
assert v.name in var_dict
assert model.getVal(v) == var_dict[v.name]

0 comments on commit b84ce3a

Please sign in to comment.