Skip to content
Merged
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
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
## Unreleased
### Added
- Added more type annotations and deprecation markers to the stubs.
- Added tests for `getObjVal()` and `getVal()`
### Fixed
- Fixed incorrect `getObjVal()` result when called before `getBestSol()` (e.g., in event handlers).
### Changed
- Replaced `format()` with f-strings in examples
### Removed

## 6.3.0 - 2026.10.11
### Added
- Added the following methods with tests: `getNNodesLeft()`, `getNRuns()`, `getNReoptRuns()`, `addNNodes()`, `getDeterministicTime()`, `getAvgDualbound()`, `getMaxTotalDepth()`, `getNBacktracks()`,\
`getFocusNode()`, `getAvgLowerbound()`, `getFirstPrimalBound()`, `getLowerboundRoot()`, `getUpperbound()`, `getNObjlimLeaves()`
- Added the following methods with tests: `getNNodesLeft()`, `getNRuns()`, `getNReoptRuns()`, `addNNodes()`, `getDeterministicTime()`, `getAvgDualbound()`, `getMaxTotalDepth()`, `getNBacktracks()`, `getFocusNode()`, `getAvgLowerbound()`, `getFirstPrimalBound()`, `getLowerboundRoot()`, `getUpperbound()`, `getNObjlimLeaves()`
- Added `addConsCumulative()` for SCIP cumulative constraints (#1222)
- Added `addConsLogicor()`, `addCoefLogicor()`, `getNVarsLogicor()`, `getVarsLogicor()`, `getDualsolLogicor()` and `getDualfarkasLogicor()` for SCIP logicor constraints (#977)
- `Expr` and `GenExpr` support `__pos__` magic method like `+Expr` or `+GenExpr`
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ model.setObjective(x + y)
model.addCons(2*x - y*y >= 0)
model.optimize()
sol = model.getBestSol()
print("x: {}".format(sol[x]))
print("y: {}".format(sol[y]))
print(f"x: {sol[x]}")
print(f"y: {sol[y]}")
```

Writing new plugins
Expand Down
4 changes: 2 additions & 2 deletions docs/tutorials/eventhandler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The following is an example the prints the value of the objective function whene
from pyscipopt import Model, SCIP_EVENTTYPE

def print_obj_value(model, event):
print("New best solution found with objective value: {}".format(model.getObjVal()))
print(f"New best solution found with objective value: {model.getObjVal()}")

m = Model()
m.attachEventHandlerCallback(print_obj_value, [SCIP_EVENTTYPE.BESTSOLFOUND])
Expand Down Expand Up @@ -72,7 +72,7 @@ and then include it in the model using the :code:`Model.includeEventHandler` met

def eventexec(self, event):
self.count += 1
print("!!!![@BestSolCounter] New best solution found. Total best solutions found: {}".format(self.count))
print(f"!!!![@BestSolCounter] New best solution found. Total best solutions found: {self.count}")


m = Model()
Expand Down
7 changes: 3 additions & 4 deletions examples/finished/diet.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def make_inst():

for b["Cal"] in [None, 3500, 3000, 2500]:

print("\nDiet for a maximum of {0} calories".format(b["Cal"] if b["Cal"] != None else "unlimited"))
print(f"\nDiet for a maximum of {b['Cal'] if b['Cal'] != None else 'unlimited'} calories")
model = diet(F, N, a, b, c, d)
model.hideOutput() # silent mode
model.optimize()
Expand All @@ -97,10 +97,9 @@ def make_inst():
x, y, z, v = model.data
for j in x:
if model.getVal(x[j]) > 0:
print("{0:30s}: {1:3.1f} dishes --> {2:4.2f} added to objective".format(j, model.getVal(x[j]),
model.getVal(y[j])))
print(f"{j:30s}: {model.getVal(x[j]):3.1f} dishes --> {model.getVal(y[j]):4.2f} added to objective")
print("amount spent:", model.getObjVal())

print("amount of nutrients:")
for i in z:
print("{0:30s}: {1:4.2f}".format(i, model.getVal(z[i])))
print(f"{i:30s}: {model.getVal(z[i]):4.2f}")
9 changes: 6 additions & 3 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -1255,9 +1255,7 @@ cdef class BoundChange:
return SCIPboundchgIsRedundant(self.scip_boundchg)

def __repr__(self):
return "{} {} {}".format(self.getVar(),
_SCIP_BOUNDTYPE_TO_STRING[self.getBoundtype()],
self.getNewBound())
return f"{self.getVar()} {_SCIP_BOUNDTYPE_TO_STRING[self.getBoundtype()]} {self.getNewBound()}"

cdef class DomainChanges:
"""Set of domain changes."""
Expand Down Expand Up @@ -11626,6 +11624,11 @@ cdef class Model:

"""

# Ensure _bestSol is up-to-date (cheap pointer comparison)
cdef SCIP_SOL* current_best_sol = SCIPgetBestSol(self._scip)
if self._bestSol is None or self._bestSol.sol != current_best_sol:
self._bestSol = Solution.create(self._scip, current_best_sol)

if SCIPgetNSols(self._scip) == 0:
if self.getStage() != SCIP_STAGE_SOLVING:
raise Warning("Without a solution, method can only be called in stage SOLVING.")
Expand Down
76 changes: 76 additions & 0 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,82 @@ def test_getObjVal():
assert m.getObjVal() == 16
assert m.getVal(x) == 0

def test_getObjVal_missing_bestSol():
m = Model()
m.hideOutput()

x = m.addVar("x", obj=2.0)
m.addCons(x >= 1.0)

# Add a solution
sol = m.createSol()
m.setSolVal(sol, x, 10.0)
m.addSol(sol)

# Without the fix from #1272, m._bestSol remained NULL,
# causing an assertion failure: assert self._bestSol.sol != NULL
assert m.getObjVal() == 20.0

def test_getObjVal_outdated_bestSol():
m = Model()
m.hideOutput()

x = m.addVar("x", obj=2.0)
m.addCons(x >= 1.0)

# Add initial solution and populate _bestSol
sol1 = m.createSol()
m.setSolVal(sol1, x, 10.0)
m.addSol(sol1)
m.getBestSol() # populates m._bestSol with sol1

# Add a better solution
sol2 = m.createSol()
m.setSolVal(sol2, x, 3.0)
m.addSol(sol2)

# Without the fix from #1272, _bestSol remained stuck
# on sol1 and incorrectly returned 20.0 instead of 6.0
assert m.getObjVal() == 6.0

def test_getVal_missing_bestSol():
m = Model()
m.hideOutput()

x = m.addVar("x", obj=2.0)
m.addCons(x >= 1.0)

# Add a solution
sol = m.createSol()
m.setSolVal(sol, x, 10.0)
m.addSol(sol)

# Without the fix from #993, m._bestSol was None here
# causing "No solution available" warning
assert m.getVal(x) == 10.0

def test_getVal_outdated_bestSol():
m = Model()
m.hideOutput()

x = m.addVar("x", obj=2.0)
m.addCons(x >= 1.0)

# Add initial solution and populate _bestSol
sol1 = m.createSol()
m.setSolVal(sol1, x, 10.0)
m.addSol(sol1)
m.getBestSol() # populates m._bestSol with sol1

# Add a better solution
sol2 = m.createSol()
m.setSolVal(sol2, x, 3.0)
m.addSol(sol2)

# Without the fix from #993, _bestSol remained stuck
# on sol1 and incorrectly returned 10.0 instead of 3.0
assert m.getVal(x) == 3.0

# tests writeProblem() after redirectOutput()
def test_redirection():

Expand Down
Loading