Skip to content

Commit 14a8581

Browse files
Relax stage for checking getObjVal, getVal (#872)
* 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
1 parent b12f184 commit 14a8581

File tree

6 files changed

+91
-10
lines changed

6 files changed

+91
-10
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@
55
- Created Statistics class
66
- Added parser to read .stats file
77
### Fixed
8+
- Fixed too strict getObjVal, getVal check
9+
### Changed
10+
### Removed
11+
12+
## 5.1.1 - 2024-06-22
13+
### Added
14+
- Added SCIP_STATUS_DUALLIMIT and SCIP_STATUS_PRIMALLIMIT
15+
- Added SCIPprintExternalCodes (retrieves version of linked symmetry, lp solver, nl solver etc)
16+
- Added recipe with reformulation for detecting infeasible constraints
17+
- Wrapped SCIPcreateOrigSol and added tests
18+
- Added verbose option for writeProblem and writeParams
19+
- Expanded locale test
20+
- Added methods for creating expression constraints without adding to problem
21+
- Added methods for creating/adding/appending disjunction constraints
22+
- Added check for pt_PT locale in test_model.py
23+
- Added SCIPgetOrigConss and SCIPgetNOrigConss Cython bindings.
24+
- Added transformed=False option to getConss, getNConss, and getNVars
25+
### Fixed
26+
- Fixed locale errors in reading
827
### Changed
928
### Removed
1029

src/pyscipopt/scip.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,7 @@ cdef extern from "scip/scip.h":
839839
int SCIPgetNBestSolsFound(SCIP* scip)
840840
SCIP_SOL* SCIPgetBestSol(SCIP* scip)
841841
SCIP_Real SCIPgetSolVal(SCIP* scip, SCIP_SOL* sol, SCIP_VAR* var)
842+
SCIP_Bool SCIPsolIsOriginal(SCIP_SOL* sol)
842843
SCIP_RETCODE SCIPwriteVarName(SCIP* scip, FILE* outfile, SCIP_VAR* var, SCIP_Bool vartype)
843844
SCIP_Real SCIPgetSolOrigObj(SCIP* scip, SCIP_SOL* sol)
844845
SCIP_Real SCIPgetSolTransObj(SCIP* scip, SCIP_SOL* sol)

src/pyscipopt/scip.pxi

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,9 @@ cdef class Solution:
627627

628628
def _checkStage(self, method):
629629
if method in ["SCIPgetSolVal", "getSolObjVal"]:
630-
if self.sol == NULL and SCIPgetStage(self.scip) != SCIP_STAGE_SOLVING:
630+
stage_check = SCIPgetStage(self.scip) not in [SCIP_STAGE_INIT, SCIP_STAGE_FREE]
631+
632+
if not stage_check or self.sol == NULL and SCIPgetStage(self.scip) != SCIP_STAGE_SOLVING:
631633
raise Warning(f"{method} can only be called with a valid solution or in stage SOLVING (current stage: {SCIPgetStage(self.scip)})")
632634

633635

@@ -3543,6 +3545,7 @@ cdef class Model:
35433545
def presolve(self):
35443546
"""Presolve the problem."""
35453547
PY_SCIP_CALL(SCIPpresolve(self._scip))
3548+
self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip))
35463549

35473550
# Benders' decomposition methods
35483551
def initBendersDefault(self, subproblems):
@@ -3606,7 +3609,7 @@ cdef class Model:
36063609
PY_SCIP_CALL(SCIPsetupBendersSubproblem(self._scip,
36073610
_benders[i], self._bestSol.sol, j, SCIP_BENDERSENFOTYPE_CHECK))
36083611
PY_SCIP_CALL(SCIPsolveBendersSubproblem(self._scip,
3609-
_benders[i], self._bestSol.sol, j, &_infeasible, solvecip, NULL))
3612+
_benders[i], self._bestSol.sol, j, &_infeasible, solvecip, NULL))
36103613

36113614
def freeBendersSubproblems(self):
36123615
"""Calls the free subproblem function for the Benders' decomposition.
@@ -4838,11 +4841,14 @@ cdef class Model:
48384841
"""
48394842
if sol == None:
48404843
sol = Solution.create(self._scip, NULL)
4844+
48414845
sol._checkStage("getSolObjVal")
4846+
48424847
if original:
48434848
objval = SCIPgetSolOrigObj(self._scip, sol.sol)
48444849
else:
48454850
objval = SCIPgetSolTransObj(self._scip, sol.sol)
4851+
48464852
return objval
48474853

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

48564862
def getObjVal(self, original=True):
48574863
"""Retrieve the objective value of value of best solution.
4858-
Can only be called after solving is completed.
48594864
48604865
:param original: objective value in original space (Default value = True)
4861-
48624866
"""
4863-
if not self.getStage() >= SCIP_STAGE_SOLVING:
4864-
raise Warning("method cannot be called before problem is solved")
4867+
4868+
if SCIPgetNSols(self._scip) == 0:
4869+
if self.getStage() != SCIP_STAGE_SOLVING:
4870+
raise Warning("Without a solution, method can only be called in stage SOLVING.")
4871+
else:
4872+
assert self._bestSol.sol != NULL
4873+
4874+
if SCIPsolIsOriginal(self._bestSol.sol):
4875+
min_stage_requirement = SCIP_STAGE_PROBLEM
4876+
else:
4877+
min_stage_requirement = SCIP_STAGE_TRANSFORMING
4878+
4879+
if not self.getStage() >= min_stage_requirement:
4880+
raise Warning("method cannot be called in stage %i." % self.getStage)
4881+
48654882
return self.getSolObjVal(self._bestSol, original)
48664883

48674884
def getSolVal(self, Solution sol, Expr expr):
@@ -4889,8 +4906,11 @@ cdef class Model:
48894906
48904907
Note: a variable is also an expression
48914908
"""
4892-
if not self.getStage() >= SCIP_STAGE_SOLVING:
4893-
raise Warning("method cannot be called before problem is solved")
4909+
stage_check = SCIPgetStage(self._scip) not in [SCIP_STAGE_INIT, SCIP_STAGE_FREE]
4910+
4911+
if not stage_check or self._bestSol.sol == NULL and SCIPgetStage(self._scip) != SCIP_STAGE_SOLVING:
4912+
raise Warning("Method cannot be called in stage ", self.getStage())
4913+
48944914
return self.getSolVal(self._bestSol, expr)
48954915

48964916
def hasPrimalRay(self):

tests/test_cons.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ def test_cons_logical():
7272
assert m.isEQ(m.getVal(result1), 1)
7373
assert m.isEQ(m.getVal(result2), 0)
7474

75-
7675
def test_SOScons():
7776
m = Model()
7877
x = {}

tests/test_customizedbenders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def bendersgetvar(self, variable, probnumber):
5454
def benderssolvesubconvex(self, solution, probnumber, onlyconvex):
5555
self.model.setupBendersSubproblem(probnumber, self, solution)
5656
self.subprob.solveProbingLP()
57-
subprob = self.model.getBendersSubproblem(probnumber, self)
57+
subprob = self.model.getBendersSubproblem(probnumber, self)
5858
assert self.subprob.getObjVal() == subprob.getObjVal()
5959

6060
result_dict = {}

tests/test_model.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,3 +435,45 @@ def build_scip_model():
435435
scip.setParam("limits/dual", -10)
436436
scip.optimize()
437437
assert (scip.getStatus() == "duallimit"), scip.getStatus()
438+
439+
def test_getObjVal():
440+
m = Model()
441+
442+
x = m.addVar(obj=0)
443+
y = m.addVar(obj = 1)
444+
z = m.addVar(obj = 2)
445+
446+
m.addCons(x+y+z >= 0)
447+
m.addCons(y+z >= 3)
448+
m.addCons(z >= 8)
449+
450+
m.setParam("limits/solutions", 0)
451+
m.optimize()
452+
453+
try:
454+
m.getObjVal()
455+
except Warning:
456+
pass
457+
458+
try:
459+
m.getVal(x)
460+
except Warning:
461+
pass
462+
463+
m.freeTransform()
464+
m.setParam("limits/solutions", 1)
465+
m.presolve()
466+
467+
assert m.getObjVal()
468+
assert m.getVal(x)
469+
470+
m.freeTransform()
471+
m.setParam("limits/solutions", -1)
472+
473+
m.optimize()
474+
475+
assert m.getObjVal() == 16
476+
assert m.getVal(x) == 0
477+
478+
assert m.getObjVal() == 16
479+
assert m.getVal(x) == 0

0 commit comments

Comments
 (0)