Skip to content

Commit

Permalink
Merge branch 'master' into issue-735
Browse files Browse the repository at this point in the history
  • Loading branch information
Joao-Dionisio authored Nov 10, 2023
2 parents 7411844 + 11f8f8f commit 590df0f
Show file tree
Hide file tree
Showing 35 changed files with 10,854 additions and 150 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ jobs:
- name: Upload to codecov.io
uses: codecov/codecov-action@v2
with:
fail_ci_if_error: true
fail_ci_if_error: false
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@

## 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
- Add SCIP functions hasPrimalRay, getPrimalRay, getPrimalRayVal
### Fixed
- Fixed typo in documentation of chgRhs
- 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
- Removed double declaration of SCIPfindEventhdlr

## 4.3.0 - 2023-03-17
### Added
Expand All @@ -18,7 +28,6 @@

### Fixed
### Changed
- Pricer plugin fundamental callbacks now raise an error if not implemented
### Removed
- Removed function rowGetNNonz

Expand Down
9 changes: 4 additions & 5 deletions src/pyscipopt/branchrule.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,17 @@ cdef class Branchrule:

def branchexeclp(self, allowaddcons):
'''executes branching rule for fractional LP solution'''
# this method needs to be implemented by the user
return {}
raise NotImplementedError("branchexeclp() is a fundamental callback and should be implemented in the derived "
"class")

def branchexecext(self, allowaddcons):
'''executes branching rule for external branching candidates '''
# this method needs to be implemented by the user
return {}
raise NotImplementedError("branchexecext() is a fundamental callback and should be implemented in the derived class")

def branchexecps(self, allowaddcons):
'''executes branching rule for not completely fixed pseudo solution '''
# this method needs to be implemented by the user
return {}
raise NotImplementedError("branchexecps() is a fundamental callback and should be implemented in the derived class")



Expand Down
5 changes: 4 additions & 1 deletion 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 Expand Up @@ -804,6 +806,8 @@ cdef extern from "scip/scip.h":
SCIP_Real SCIPgetPrimalbound(SCIP* scip)
SCIP_Real SCIPgetGap(SCIP* scip)
int SCIPgetDepth(SCIP* scip)
SCIP_Bool SCIPhasPrimalRay(SCIP * scip)
SCIP_Real SCIPgetPrimalRayVal(SCIP * scip, SCIP_VAR * var)
SCIP_RETCODE SCIPaddSolFree(SCIP* scip, SCIP_SOL** sol, SCIP_Bool* stored)
SCIP_RETCODE SCIPaddSol(SCIP* scip, SCIP_SOL* sol, SCIP_Bool* stored)
SCIP_RETCODE SCIPreadSol(SCIP* scip, const char* filename)
Expand Down Expand Up @@ -874,7 +878,6 @@ cdef extern from "scip/scip.h":
SCIP_RETCODE (*eventdelete) (SCIP* scip, SCIP_EVENTHDLR* eventhdlr, SCIP_EVENTDATA** eventdata),
SCIP_RETCODE (*eventexec) (SCIP* scip, SCIP_EVENTHDLR* eventhdlr, SCIP_EVENT* event, SCIP_EVENTDATA* eventdata),
SCIP_EVENTHDLRDATA* eventhdlrdata)
SCIP_EVENTHDLR* SCIPfindEventhdlr(SCIP* scip, const char* name)
SCIP_EVENTHDLRDATA* SCIPeventhdlrGetData(SCIP_EVENTHDLR* eventhdlr)

# Variable pricer plugin
Expand Down
113 changes: 103 additions & 10 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -535,11 +535,16 @@ cdef class NLRow:
cdef class Solution:
"""Base class holding a pointer to corresponding SCIP_SOL"""

# We are raising an error here to avoid creating a solution without an associated model. See Issue #625
def __init__(self, raise_error = False):
if not raise_error:
raise ValueError("To create a solution you should use the createSol method of the Model class.")

@staticmethod
cdef create(SCIP* scip, SCIP_SOL* scip_sol):
if scip == NULL:
raise Warning("cannot create Solution with SCIP* == NULL")
sol = Solution()
sol = Solution(True)
sol.sol = scip_sol
sol.scip = scip
return sol
Expand Down Expand Up @@ -567,8 +572,8 @@ cdef class Solution:

vals = {}
self._checkStage("SCIPgetSolVal")
for i in range(SCIPgetNVars(self.scip)):
scip_var = SCIPgetVars(self.scip)[i]
for i in range(SCIPgetNOrigVars(self.scip)):
scip_var = SCIPgetOrigVars(self.scip)[i]

# extract name
cname = bytes(SCIPvarGetName(scip_var))
Expand All @@ -579,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 @@ -1333,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 @@ -1610,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 @@ -1724,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 @@ -1734,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 @@ -2121,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 @@ -2819,7 +2883,7 @@ cdef class Model:
"""Change right hand side value of a constraint.
:param Constraint cons: linear or quadratic constraint
:param rhs: new ride hand side (set to None for +infinity)
:param rhs: new right hand side (set to None for +infinity)
"""

Expand Down Expand Up @@ -3988,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 @@ -4557,6 +4621,35 @@ cdef class Model:
if not self.getStage() >= SCIP_STAGE_SOLVING:
raise Warning("method cannot be called before problem is solved")
return self.getSolVal(self._bestSol, expr)

def hasPrimalRay(self):
"""
Returns whether a primal ray is stored that proves unboundedness of the LP relaxation
"""
return SCIPhasPrimalRay(self._scip)

def getPrimalRayVal(self, Variable var):
"""
Gets value of given variable in primal ray causing unboundedness of the LP relaxation
"""
assert SCIPhasPrimalRay(self._scip), "The problem does not have a primal ray."

return SCIPgetPrimalRayVal(self._scip, var.scip_var)

def getPrimalRay(self):
"""
Gets primal ray causing unboundedness of the LP relaxation
"""
assert SCIPhasPrimalRay(self._scip), "The problem does not have a primal ray."

cdef int _nvars = SCIPgetNVars(self._scip)
cdef SCIP_VAR ** _vars = SCIPgetVars(self._scip)

ray = []
for i in range(_nvars):
ray.append(float(SCIPgetPrimalRayVal(self._scip, _vars[i])))

return ray

def getPrimalbound(self):
"""Retrieve the best primal bound."""
Expand Down Expand Up @@ -5026,4 +5119,4 @@ def is_memory_freed():
return BMSgetMemoryUsed() == 0

def print_memory_in_use():
BMScheckEmptyMemory()
BMScheckEmptyMemory()
Loading

0 comments on commit 590df0f

Please sign in to comment.