Skip to content

Commit

Permalink
Relax stage for checking getObjVal, getVal (#872)
Browse files Browse the repository at this point in the history
* Set correct stages. Add SCIPsolIsOriginal

* Add test for getObjVal

* Remove noexcept

* Update changelog

* Add noexcept again

* Add everything back

* Segfault commit

* Update CHANGELOG

* Add test

* Relax stage checks

* Update test_customizedbenders.py

* Update variable name
  • Loading branch information
Joao-Dionisio authored Jul 5, 2024
1 parent b12f184 commit 14a8581
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 10 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@
- Created Statistics class
- Added parser to read .stats file
### Fixed
- Fixed too strict getObjVal, getVal check
### Changed
### Removed

## 5.1.1 - 2024-06-22
### Added
- Added SCIP_STATUS_DUALLIMIT and SCIP_STATUS_PRIMALLIMIT
- Added SCIPprintExternalCodes (retrieves version of linked symmetry, lp solver, nl solver etc)
- Added recipe with reformulation for detecting infeasible constraints
- Wrapped SCIPcreateOrigSol and added tests
- Added verbose option for writeProblem and writeParams
- Expanded locale test
- Added methods for creating expression constraints without adding to problem
- Added methods for creating/adding/appending disjunction constraints
- Added check for pt_PT locale in test_model.py
- Added SCIPgetOrigConss and SCIPgetNOrigConss Cython bindings.
- Added transformed=False option to getConss, getNConss, and getNVars
### Fixed
- Fixed locale errors in reading
### Changed
### Removed

Expand Down
1 change: 1 addition & 0 deletions src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,7 @@ cdef extern from "scip/scip.h":
int SCIPgetNBestSolsFound(SCIP* scip)
SCIP_SOL* SCIPgetBestSol(SCIP* scip)
SCIP_Real SCIPgetSolVal(SCIP* scip, SCIP_SOL* sol, SCIP_VAR* var)
SCIP_Bool SCIPsolIsOriginal(SCIP_SOL* sol)
SCIP_RETCODE SCIPwriteVarName(SCIP* scip, FILE* outfile, SCIP_VAR* var, SCIP_Bool vartype)
SCIP_Real SCIPgetSolOrigObj(SCIP* scip, SCIP_SOL* sol)
SCIP_Real SCIPgetSolTransObj(SCIP* scip, SCIP_SOL* sol)
Expand Down
36 changes: 28 additions & 8 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,9 @@ cdef class Solution:

def _checkStage(self, method):
if method in ["SCIPgetSolVal", "getSolObjVal"]:
if self.sol == NULL and SCIPgetStage(self.scip) != SCIP_STAGE_SOLVING:
stage_check = SCIPgetStage(self.scip) not in [SCIP_STAGE_INIT, SCIP_STAGE_FREE]

if not stage_check or 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)})")


Expand Down Expand Up @@ -3543,6 +3545,7 @@ cdef class Model:
def presolve(self):
"""Presolve the problem."""
PY_SCIP_CALL(SCIPpresolve(self._scip))
self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip))

# Benders' decomposition methods
def initBendersDefault(self, subproblems):
Expand Down Expand Up @@ -3606,7 +3609,7 @@ cdef class Model:
PY_SCIP_CALL(SCIPsetupBendersSubproblem(self._scip,
_benders[i], self._bestSol.sol, j, SCIP_BENDERSENFOTYPE_CHECK))
PY_SCIP_CALL(SCIPsolveBendersSubproblem(self._scip,
_benders[i], self._bestSol.sol, j, &_infeasible, solvecip, NULL))
_benders[i], self._bestSol.sol, j, &_infeasible, solvecip, NULL))

def freeBendersSubproblems(self):
"""Calls the free subproblem function for the Benders' decomposition.
Expand Down Expand Up @@ -4838,11 +4841,14 @@ cdef class Model:
"""
if sol == None:
sol = Solution.create(self._scip, NULL)

sol._checkStage("getSolObjVal")

if original:
objval = SCIPgetSolOrigObj(self._scip, sol.sol)
else:
objval = SCIPgetSolTransObj(self._scip, sol.sol)

return objval

def getSolTime(self, Solution sol):
Expand All @@ -4855,13 +4861,24 @@ cdef class Model:

def getObjVal(self, original=True):
"""Retrieve the objective value of value of best solution.
Can only be called after solving is completed.
:param original: objective value in original space (Default value = True)
"""
if not self.getStage() >= SCIP_STAGE_SOLVING:
raise Warning("method cannot be called before problem is solved")

if SCIPgetNSols(self._scip) == 0:
if self.getStage() != SCIP_STAGE_SOLVING:
raise Warning("Without a solution, method can only be called in stage SOLVING.")
else:
assert self._bestSol.sol != NULL

if SCIPsolIsOriginal(self._bestSol.sol):
min_stage_requirement = SCIP_STAGE_PROBLEM
else:
min_stage_requirement = SCIP_STAGE_TRANSFORMING

if not self.getStage() >= min_stage_requirement:
raise Warning("method cannot be called in stage %i." % self.getStage)

return self.getSolObjVal(self._bestSol, original)

def getSolVal(self, Solution sol, Expr expr):
Expand Down Expand Up @@ -4889,8 +4906,11 @@ cdef class Model:
Note: a variable is also an expression
"""
if not self.getStage() >= SCIP_STAGE_SOLVING:
raise Warning("method cannot be called before problem is solved")
stage_check = SCIPgetStage(self._scip) not in [SCIP_STAGE_INIT, SCIP_STAGE_FREE]

if not stage_check or self._bestSol.sol == NULL and SCIPgetStage(self._scip) != SCIP_STAGE_SOLVING:
raise Warning("Method cannot be called in stage ", self.getStage())

return self.getSolVal(self._bestSol, expr)

def hasPrimalRay(self):
Expand Down
1 change: 0 additions & 1 deletion tests/test_cons.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ def test_cons_logical():
assert m.isEQ(m.getVal(result1), 1)
assert m.isEQ(m.getVal(result2), 0)


def test_SOScons():
m = Model()
x = {}
Expand Down
2 changes: 1 addition & 1 deletion tests/test_customizedbenders.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def bendersgetvar(self, variable, probnumber):
def benderssolvesubconvex(self, solution, probnumber, onlyconvex):
self.model.setupBendersSubproblem(probnumber, self, solution)
self.subprob.solveProbingLP()
subprob = self.model.getBendersSubproblem(probnumber, self)
subprob = self.model.getBendersSubproblem(probnumber, self)
assert self.subprob.getObjVal() == subprob.getObjVal()

result_dict = {}
Expand Down
42 changes: 42 additions & 0 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,45 @@ def build_scip_model():
scip.setParam("limits/dual", -10)
scip.optimize()
assert (scip.getStatus() == "duallimit"), scip.getStatus()

def test_getObjVal():
m = Model()

x = m.addVar(obj=0)
y = m.addVar(obj = 1)
z = m.addVar(obj = 2)

m.addCons(x+y+z >= 0)
m.addCons(y+z >= 3)
m.addCons(z >= 8)

m.setParam("limits/solutions", 0)
m.optimize()

try:
m.getObjVal()
except Warning:
pass

try:
m.getVal(x)
except Warning:
pass

m.freeTransform()
m.setParam("limits/solutions", 1)
m.presolve()

assert m.getObjVal()
assert m.getVal(x)

m.freeTransform()
m.setParam("limits/solutions", -1)

m.optimize()

assert m.getObjVal() == 16
assert m.getVal(x) == 0

assert m.getObjVal() == 16
assert m.getVal(x) == 0

0 comments on commit 14a8581

Please sign in to comment.