diff --git a/CHANGELOG.md b/CHANGELOG.md index c46cf8e74..0113dae47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased ### Added +- 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 6db5be281..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) @@ -2134,6 +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) + int 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..cc2722af9 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -3296,6 +3296,39 @@ 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 getNReoptRuns(self): + """ + Gets number of reoptimization runs performed, including the current run + + Returns + ------- + int + + """ + 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. @@ -3351,6 +3384,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/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 32bc939ce..455bc9b36 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1262,9 +1262,13 @@ 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: ... + 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_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(): diff --git a/tests/test_statistics.py b/tests/test_statistics.py index fe030a385..fef2f4027 100644 --- a/tests/test_statistics.py +++ b/tests/test_statistics.py @@ -1,21 +1,49 @@ 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 + + +def test_getNReoptRuns(optimized_model): + n_reopt_runs = optimized_model.getNReoptRuns() + + 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