From 036fa3e9c84ef125aa23ba889961aba249d8430a Mon Sep 17 00:00:00 2001 From: nu Date: Sun, 19 Jul 2026 18:24:10 +0200 Subject: [PATCH 1/6] Add getNNodesLeft method --- CHANGELOG.md | 1 + src/pyscipopt/scip.pxd | 1 + src/pyscipopt/scip.pxi | 11 +++++++++++ tests/test_node.py | 8 ++++++++ 4 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c46cf8e74..1c83c0431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased ### Added +- Added `getNNodesLeft()` method - Added `addConsCumulative()` for SCIP cumulative constraints (#1222) - `Expr` and `GenExpr` support `__pos__` magic method like `+Expr` or `+GenExpr` - Added type annotations to most methods on the `Model` class diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 6db5be281..7361a3587 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -2134,6 +2134,7 @@ cdef extern from "scip/scip_tree.h": SCIP_RETCODE SCIPgetNSiblings(SCIP* scip) SCIP_RETCODE SCIPgetLeaves(SCIP* scip, SCIP_NODE*** leaves, int* nleaves) SCIP_Longint SCIPgetNLeaves(SCIP* scip) + SCIP_Longint SCIPgetNNodesLeft(SCIP* scip) SCIP_NODE* SCIPgetBestSibling(SCIP* scip) SCIP_NODE* SCIPgetBestLeaf(SCIP* scip) SCIP_NODE* SCIPgetPrioChild(SCIP* scip) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index d047e584b..cd7cf86d8 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -3351,6 +3351,17 @@ cdef class Model: """ return SCIPgetNLeaves(self._scip) + def getNNodesLeft(self): + """ + Gets number of nodes left in the tree (children + siblings + leaves) + + Returns + ------- + int + + """ + return SCIPgetNNodesLeft(self._scip) + def getNChildren(self): """ Gets number of children of focus node. diff --git a/tests/test_node.py b/tests/test_node.py index 9565f0de8..ce901eb8e 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -45,6 +45,14 @@ def eventexec(self, event): assert children == self.model.getChildren() assert siblings == self.model.getSiblings() + nodes_left = self.model.getNNodesLeft() + assert ( + nodes_left + == self.model.getNLeaves() + + self.model.getNChildren() + + self.model.getNSiblings() + ) + return {'result': SCIP_RESULT.SUCCESS} def test_tree_methods(): From 12c93e0bef65e2a8b018aca411895b5711bfe83b Mon Sep 17 00:00:00 2001 From: nu Date: Sun, 19 Jul 2026 22:27:11 +0200 Subject: [PATCH 2/6] add getNNodesLeft signature to stub --- src/pyscipopt/scip.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 32bc939ce..a0afb7574 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1262,6 +1262,7 @@ class Model: def getMemTotal(self) -> int: ... def getMemExternEstim(self) -> int: ... def getNLeaves(self) -> int: ... + def getNNodesLeft(self) -> int: ... def getNLimSolsFound(self) -> int: ... def getNNlRows(self) -> int: ... def getNNodeLPIterations(self) -> int: ... From 7788eb71d3486851f69cd5df10bf7c791a3dcade Mon Sep 17 00:00:00 2001 From: nu Date: Mon, 20 Jul 2026 22:26:35 +0200 Subject: [PATCH 3/6] add getNRuns and refactor statistics tests --- CHANGELOG.md | 2 +- src/pyscipopt/scip.pxd | 1 + src/pyscipopt/scip.pxi | 11 +++++++++++ src/pyscipopt/scip.pyi | 1 + tests/test_statistics.py | 29 +++++++++++++++++++++-------- 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c83c0431..2bff109f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased ### Added -- Added `getNNodesLeft()` method +- Added methods: `getNNodesLeft()`, `getNRuns()` - Added `addConsCumulative()` for SCIP cumulative constraints (#1222) - `Expr` and `GenExpr` support `__pos__` magic method like `+Expr` or `+GenExpr` - Added type annotations to most methods on the `Model` class diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 7361a3587..397a90a03 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1487,6 +1487,7 @@ cdef extern from "scip/scip.h": SCIP_Longint SCIPgetNNodeLPIterations(SCIP* scip) SCIP_Longint SCIPgetNStrongbranchLPIterations(SCIP* scip) SCIP_Real SCIPgetPrimalDualIntegral(SCIP* scip) + int SCIPgetNRuns(SCIP* scip) # Parameter Functions SCIP_RETCODE SCIPsetBoolParam(SCIP* scip, char* name, SCIP_Bool value) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index cd7cf86d8..4d29deb35 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -3296,6 +3296,17 @@ cdef class Model: """ return SCIPgetNLPIterations(self._scip) + def getNRuns(self): + """ + Gets number of branch and bound runs performed, including the current run + + Returns + ------- + int + + """ + return SCIPgetNRuns(self._scip) + def getNNodes(self): """ Gets number of processed nodes in current run, including the focus node. diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index a0afb7574..49ebd4b25 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1266,6 +1266,7 @@ class Model: def getNLimSolsFound(self) -> int: ... def getNNlRows(self) -> int: ... def getNNodeLPIterations(self) -> int: ... + def getNRuns(self) -> int: ... def getNNodes(self) -> int: ... def getNReaders(self) -> int: ... def getNSepaRounds(self) -> int: ... diff --git a/tests/test_statistics.py b/tests/test_statistics.py index fe030a385..0e195d067 100644 --- a/tests/test_statistics.py +++ b/tests/test_statistics.py @@ -1,21 +1,34 @@ import os from helpers.utils import random_mip_1 from json import load +import pytest -def test_statistics_json(): - model = random_mip_1() + +@pytest.fixture +def optimized_model(): + model = random_mip_1(small=True) # Using small=True for speed across tests model.optimize() - model.writeStatisticsJson("statistics.json") + return model + + +def test_statistics_json(optimized_model): + optimized_model.writeStatisticsJson("statistics.json") with open("statistics.json", "r") as f: data = load(f) assert data["origprob"]["problem_name"] == "model" - + os.remove("statistics.json") -def test_getPrimalDualIntegral(): - model = random_mip_1(small=True) - model.optimize() - primal_dual_integral = model.getPrimalDualIntegral() + +def test_getPrimalDualIntegral(optimized_model): + primal_dual_integral = optimized_model.getPrimalDualIntegral() assert isinstance(primal_dual_integral, float) + + +def test_getNRuns(optimized_model): + n_runs = optimized_model.getNRuns() + + assert isinstance(n_runs, int) + assert n_runs >= 1 From 48fdaf262ad8bd65af67697b1647f0981d0d9775 Mon Sep 17 00:00:00 2001 From: nu Date: Mon, 20 Jul 2026 22:38:44 +0200 Subject: [PATCH 4/6] add getNReoptRuns --- CHANGELOG.md | 2 +- src/pyscipopt/scip.pxd | 1 + src/pyscipopt/scip.pxi | 11 +++++++++++ src/pyscipopt/scip.pyi | 1 + tests/test_statistics.py | 7 +++++++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bff109f8..30a2441e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased ### Added -- Added methods: `getNNodesLeft()`, `getNRuns()` +- Added methods: `getNNodesLeft()`, `getNRuns()`, `getNReoptRuns` - Added `addConsCumulative()` for SCIP cumulative constraints (#1222) - `Expr` and `GenExpr` support `__pos__` magic method like `+Expr` or `+GenExpr` - Added type annotations to most methods on the `Model` class diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 397a90a03..cf95c0728 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1488,6 +1488,7 @@ cdef extern from "scip/scip.h": SCIP_Longint SCIPgetNStrongbranchLPIterations(SCIP* scip) SCIP_Real SCIPgetPrimalDualIntegral(SCIP* scip) int SCIPgetNRuns(SCIP* scip) + int SCIPgetNReoptRuns(SCIP* scip) # Parameter Functions SCIP_RETCODE SCIPsetBoolParam(SCIP* scip, char* name, SCIP_Bool value) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 4d29deb35..d1a1ca5cc 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -3307,6 +3307,17 @@ cdef class Model: """ return SCIPgetNRuns(self._scip) + def getNReoptRuns(self): + """ + Gets number of reoptimization runs performed, including the current run + + Returns + ------- + int + + """ + return SCIPgetNReoptRuns(self._scip) + def getNNodes(self): """ Gets number of processed nodes in current run, including the focus node. diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 49ebd4b25..f39407b67 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1267,6 +1267,7 @@ class Model: def getNNlRows(self) -> int: ... def getNNodeLPIterations(self) -> int: ... def getNRuns(self) -> int: ... + def getNReoptRuns(self) -> int: ... def getNNodes(self) -> int: ... def getNReaders(self) -> int: ... def getNSepaRounds(self) -> int: ... diff --git a/tests/test_statistics.py b/tests/test_statistics.py index 0e195d067..dd3cfa994 100644 --- a/tests/test_statistics.py +++ b/tests/test_statistics.py @@ -32,3 +32,10 @@ def test_getNRuns(optimized_model): assert isinstance(n_runs, int) assert n_runs >= 1 + + +def test_getNReoptRuns(optimized_model): + n_reopt_runs = optimized_model.getNReoptRuns() + + assert isinstance(n_reopt_runs, int) + assert n_reopt_runs >= 0 From 667d62c34516f6cad444dd0827f88f13a2c04369 Mon Sep 17 00:00:00 2001 From: nu Date: Mon, 20 Jul 2026 23:27:48 +0200 Subject: [PATCH 5/6] add addNNodes --- CHANGELOG.md | 2 +- src/pyscipopt/scip.pxd | 1 + src/pyscipopt/scip.pxi | 11 +++++++++++ src/pyscipopt/scip.pyi | 1 + tests/test_statistics.py | 8 ++++++++ 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30a2441e1..0113dae47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased ### Added -- Added methods: `getNNodesLeft()`, `getNRuns()`, `getNReoptRuns` +- Added methods: `getNNodesLeft()`, `getNRuns()`, `getNReoptRuns()`, `addNNodes()` with tests - Added `addConsCumulative()` for SCIP cumulative constraints (#1222) - `Expr` and `GenExpr` support `__pos__` magic method like `+Expr` or `+GenExpr` - Added type annotations to most methods on the `Model` class diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index cf95c0728..7ab5f5f6c 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1489,6 +1489,7 @@ cdef extern from "scip/scip.h": SCIP_Real SCIPgetPrimalDualIntegral(SCIP* scip) int SCIPgetNRuns(SCIP* scip) int SCIPgetNReoptRuns(SCIP* scip) + void SCIPaddNNodes(SCIP* scip, SCIP_Longint nnodes) # Parameter Functions SCIP_RETCODE SCIPsetBoolParam(SCIP* scip, char* name, SCIP_Bool value) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index d1a1ca5cc..cc2722af9 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -3318,6 +3318,17 @@ cdef class Model: """ return SCIPgetNReoptRuns(self._scip) + def addNNodes(self, nnodes): + """ + Add given number to the number of processed nodes in current run and in all runs, including the focus node + + Parameters + ---------- + nnodes : int + + """ + SCIPaddNNodes(self._scip, nnodes) + def getNNodes(self): """ Gets number of processed nodes in current run, including the focus node. diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index f39407b67..455bc9b36 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1268,6 +1268,7 @@ class Model: def getNNodeLPIterations(self) -> int: ... def getNRuns(self) -> int: ... def getNReoptRuns(self) -> int: ... + def addNNodes(self, nnodes: int) -> None: ... def getNNodes(self) -> int: ... def getNReaders(self) -> int: ... def getNSepaRounds(self) -> int: ... diff --git a/tests/test_statistics.py b/tests/test_statistics.py index dd3cfa994..fef2f4027 100644 --- a/tests/test_statistics.py +++ b/tests/test_statistics.py @@ -39,3 +39,11 @@ def test_getNReoptRuns(optimized_model): assert isinstance(n_reopt_runs, int) assert n_reopt_runs >= 0 + + +def test_addNNodes(optimized_model): + initial_n_nodes = optimized_model.getNTotalNodes() + optimized_model.addNNodes(5) + new_n_nodes = optimized_model.getNTotalNodes() + + assert new_n_nodes == initial_n_nodes + 5 From 7e4cdc78bd064a365669779f3cdd559a88b51bb6 Mon Sep 17 00:00:00 2001 From: nu Date: Tue, 21 Jul 2026 19:08:43 +0200 Subject: [PATCH 6/6] fix SCIPgetNNodesLeft dtype and reorder methods defintions --- src/pyscipopt/scip.pxd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 7ab5f5f6c..3a126f4dc 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1471,6 +1471,9 @@ cdef extern from "scip/scip.h": SCIP_Bool SCIPlpExactDiving(SCIP_LPEXACT* lpexact); # Statistic Methods + int SCIPgetNRuns(SCIP* scip) + int SCIPgetNReoptRuns(SCIP* scip) + void SCIPaddNNodes(SCIP* scip, SCIP_Longint nnodes) SCIP_RETCODE SCIPprintStatistics(SCIP* scip, FILE* outfile) SCIP_RETCODE SCIPprintStatisticsJson(SCIP* scip, FILE* file) SCIP_Longint SCIPgetNNodes(SCIP* scip) @@ -1487,9 +1490,6 @@ cdef extern from "scip/scip.h": SCIP_Longint SCIPgetNNodeLPIterations(SCIP* scip) SCIP_Longint SCIPgetNStrongbranchLPIterations(SCIP* scip) SCIP_Real SCIPgetPrimalDualIntegral(SCIP* scip) - int SCIPgetNRuns(SCIP* scip) - int SCIPgetNReoptRuns(SCIP* scip) - void SCIPaddNNodes(SCIP* scip, SCIP_Longint nnodes) # Parameter Functions SCIP_RETCODE SCIPsetBoolParam(SCIP* scip, char* name, SCIP_Bool value) @@ -2137,7 +2137,7 @@ cdef extern from "scip/scip_tree.h": SCIP_RETCODE SCIPgetNSiblings(SCIP* scip) SCIP_RETCODE SCIPgetLeaves(SCIP* scip, SCIP_NODE*** leaves, int* nleaves) SCIP_Longint SCIPgetNLeaves(SCIP* scip) - SCIP_Longint SCIPgetNNodesLeft(SCIP* scip) + int SCIPgetNNodesLeft(SCIP* scip) SCIP_NODE* SCIPgetBestSibling(SCIP* scip) SCIP_NODE* SCIPgetBestLeaf(SCIP* scip) SCIP_NODE* SCIPgetPrioChild(SCIP* scip)