Skip to content

Commit b89b565

Browse files
authored
Interface SCIP methods missing from PySCIPOpt (#1236)
* Add getNNodesLeft method * add getNNodesLeft signature to stub * add getNRuns and refactor statistics tests * add getNReoptRuns * add addNNodes * fix SCIPgetNNodesLeft dtype and reorder methods defintions
1 parent 2a9ebd3 commit b89b565

6 files changed

Lines changed: 97 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44
### Added
5+
- Added methods: `getNNodesLeft()`, `getNRuns()`, `getNReoptRuns()`, `addNNodes()` with tests
56
- Added `addConsCumulative()` for SCIP cumulative constraints (#1222)
67
- `Expr` and `GenExpr` support `__pos__` magic method like `+Expr` or `+GenExpr`
78
- Added type annotations to most methods on the `Model` class

src/pyscipopt/scip.pxd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,9 @@ cdef extern from "scip/scip.h":
14711471
SCIP_Bool SCIPlpExactDiving(SCIP_LPEXACT* lpexact);
14721472

14731473
# Statistic Methods
1474+
int SCIPgetNRuns(SCIP* scip)
1475+
int SCIPgetNReoptRuns(SCIP* scip)
1476+
void SCIPaddNNodes(SCIP* scip, SCIP_Longint nnodes)
14741477
SCIP_RETCODE SCIPprintStatistics(SCIP* scip, FILE* outfile)
14751478
SCIP_RETCODE SCIPprintStatisticsJson(SCIP* scip, FILE* file)
14761479
SCIP_Longint SCIPgetNNodes(SCIP* scip)
@@ -2134,6 +2137,7 @@ cdef extern from "scip/scip_tree.h":
21342137
SCIP_RETCODE SCIPgetNSiblings(SCIP* scip)
21352138
SCIP_RETCODE SCIPgetLeaves(SCIP* scip, SCIP_NODE*** leaves, int* nleaves)
21362139
SCIP_Longint SCIPgetNLeaves(SCIP* scip)
2140+
int SCIPgetNNodesLeft(SCIP* scip)
21372141
SCIP_NODE* SCIPgetBestSibling(SCIP* scip)
21382142
SCIP_NODE* SCIPgetBestLeaf(SCIP* scip)
21392143
SCIP_NODE* SCIPgetPrioChild(SCIP* scip)

src/pyscipopt/scip.pxi

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3296,6 +3296,39 @@ cdef class Model:
32963296
"""
32973297
return SCIPgetNLPIterations(self._scip)
32983298

3299+
def getNRuns(self):
3300+
"""
3301+
Gets number of branch and bound runs performed, including the current run
3302+
3303+
Returns
3304+
-------
3305+
int
3306+
3307+
"""
3308+
return SCIPgetNRuns(self._scip)
3309+
3310+
def getNReoptRuns(self):
3311+
"""
3312+
Gets number of reoptimization runs performed, including the current run
3313+
3314+
Returns
3315+
-------
3316+
int
3317+
3318+
"""
3319+
return SCIPgetNReoptRuns(self._scip)
3320+
3321+
def addNNodes(self, nnodes):
3322+
"""
3323+
Add given number to the number of processed nodes in current run and in all runs, including the focus node
3324+
3325+
Parameters
3326+
----------
3327+
nnodes : int
3328+
3329+
"""
3330+
SCIPaddNNodes(self._scip, nnodes)
3331+
32993332
def getNNodes(self):
33003333
"""
33013334
Gets number of processed nodes in current run, including the focus node.
@@ -3351,6 +3384,17 @@ cdef class Model:
33513384
"""
33523385
return SCIPgetNLeaves(self._scip)
33533386

3387+
def getNNodesLeft(self):
3388+
"""
3389+
Gets number of nodes left in the tree (children + siblings + leaves)
3390+
3391+
Returns
3392+
-------
3393+
int
3394+
3395+
"""
3396+
return SCIPgetNNodesLeft(self._scip)
3397+
33543398
def getNChildren(self):
33553399
"""
33563400
Gets number of children of focus node.

src/pyscipopt/scip.pyi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,9 +1262,13 @@ class Model:
12621262
def getMemTotal(self) -> int: ...
12631263
def getMemExternEstim(self) -> int: ...
12641264
def getNLeaves(self) -> int: ...
1265+
def getNNodesLeft(self) -> int: ...
12651266
def getNLimSolsFound(self) -> int: ...
12661267
def getNNlRows(self) -> int: ...
12671268
def getNNodeLPIterations(self) -> int: ...
1269+
def getNRuns(self) -> int: ...
1270+
def getNReoptRuns(self) -> int: ...
1271+
def addNNodes(self, nnodes: int) -> None: ...
12681272
def getNNodes(self) -> int: ...
12691273
def getNReaders(self) -> int: ...
12701274
def getNSepaRounds(self) -> int: ...

tests/test_node.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ def eventexec(self, event):
4545
assert children == self.model.getChildren()
4646
assert siblings == self.model.getSiblings()
4747

48+
nodes_left = self.model.getNNodesLeft()
49+
assert (
50+
nodes_left
51+
== self.model.getNLeaves()
52+
+ self.model.getNChildren()
53+
+ self.model.getNSiblings()
54+
)
55+
4856
return {'result': SCIP_RESULT.SUCCESS}
4957

5058
def test_tree_methods():

tests/test_statistics.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,49 @@
11
import os
22
from helpers.utils import random_mip_1
33
from json import load
4+
import pytest
45

5-
def test_statistics_json():
6-
model = random_mip_1()
6+
7+
@pytest.fixture
8+
def optimized_model():
9+
model = random_mip_1(small=True) # Using small=True for speed across tests
710
model.optimize()
8-
model.writeStatisticsJson("statistics.json")
11+
return model
12+
13+
14+
def test_statistics_json(optimized_model):
15+
optimized_model.writeStatisticsJson("statistics.json")
916

1017
with open("statistics.json", "r") as f:
1118
data = load(f)
1219
assert data["origprob"]["problem_name"] == "model"
13-
20+
1421
os.remove("statistics.json")
1522

16-
def test_getPrimalDualIntegral():
17-
model = random_mip_1(small=True)
18-
model.optimize()
19-
primal_dual_integral = model.getPrimalDualIntegral()
23+
24+
def test_getPrimalDualIntegral(optimized_model):
25+
primal_dual_integral = optimized_model.getPrimalDualIntegral()
2026

2127
assert isinstance(primal_dual_integral, float)
28+
29+
30+
def test_getNRuns(optimized_model):
31+
n_runs = optimized_model.getNRuns()
32+
33+
assert isinstance(n_runs, int)
34+
assert n_runs >= 1
35+
36+
37+
def test_getNReoptRuns(optimized_model):
38+
n_reopt_runs = optimized_model.getNReoptRuns()
39+
40+
assert isinstance(n_reopt_runs, int)
41+
assert n_reopt_runs >= 0
42+
43+
44+
def test_addNNodes(optimized_model):
45+
initial_n_nodes = optimized_model.getNTotalNodes()
46+
optimized_model.addNNodes(5)
47+
new_n_nodes = optimized_model.getNTotalNodes()
48+
49+
assert new_n_nodes == initial_n_nodes + 5

0 commit comments

Comments
 (0)