Skip to content

Commit

Permalink
Add node cutoff (#953)
Browse files Browse the repository at this point in the history
* Update README with usage request

* small typo

* Add cutoff node and test

* More explicit test, fix typo everywhere

* forgot to save this file...
  • Loading branch information
Joao-Dionisio authored Feb 10, 2025
1 parent 193445f commit b94dbb7
Show file tree
Hide file tree
Showing 9 changed files with 44 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased
### Added
- Added cutoffNode and test
### Fixed
### Changed
### Removed
Expand Down
1 change: 1 addition & 0 deletions src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,7 @@ cdef extern from "scip/scip.h":
SCIP_Real SCIPgetPrimalbound(SCIP* scip)
SCIP_Real SCIPgetGap(SCIP* scip)
int SCIPgetDepth(SCIP* scip)
SCIP_RETCODE SCIPcutoffNode(SCIP* scip, SCIP_NODE* node)
SCIP_Bool SCIPhasPrimalRay(SCIP * scip)
SCIP_Real SCIPgetPrimalRayVal(SCIP * scip, SCIP_VAR * var)
SCIP_RETCODE SCIPaddSolFree(SCIP* scip, SCIP_SOL** sol, SCIP_Bool* stored)
Expand Down
12 changes: 11 additions & 1 deletion src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -1313,7 +1313,7 @@ cdef class Node:
"""
return SCIPnodeIsActive(self.scip_node)

def isPropagatedAgain(self):
"""
Is the node marked to be propagated again?
Expand Down Expand Up @@ -2335,6 +2335,16 @@ cdef class Model:
"""
return SCIPgetDepth(self._scip)

def cutoffNode(self, Node node):
"""
marks node and whole subtree to be cut off from the branch and bound tree.
Parameters
----------
node : Node
"""
PY_SCIP_CALL( SCIPcutoffNode(self._scip, node.scip_node) )

def infinity(self):
"""
Retrieve SCIP's infinity value.
Expand Down
4 changes: 2 additions & 2 deletions tests/helpers/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pyscipopt import Model, quicksum, SCIP_PARAMSETTING, exp, log, sqrt, sin
from typing import List

def random_mip_1(disable_sepa=True, disable_huer=True, disable_presolve=True, node_lim=2000, small=False):
def random_mip_1(disable_sepa=True, disable_heur=True, disable_presolve=True, node_lim=2000, small=False):
model = Model()

x0 = model.addVar(lb=-2, ub=4)
Expand Down Expand Up @@ -41,7 +41,7 @@ def random_mip_1(disable_sepa=True, disable_huer=True, disable_presolve=True, no

if disable_sepa:
model.setSeparating(SCIP_PARAMSETTING.OFF)
if disable_huer:
if disable_heur:
model.setHeuristics(SCIP_PARAMSETTING.OFF)
if disable_presolve:
model.setPresolve(SCIP_PARAMSETTING.OFF)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_heur.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def inner():

def test_simple_round_heur():
# create solver instance
s = random_mip_1(disable_sepa=False, disable_huer=False, node_lim=1)
s = random_mip_1(disable_sepa=False, disable_heur=False, node_lim=1)
heuristic = SimpleRoundingHeuristic()
s.includeHeur(heuristic, "SimpleRounding", "simple rounding heuristic implemented in python", "Y",
timingmask=SCIP_HEURTIMING.DURINGLPLOOP)
Expand Down
21 changes: 21 additions & 0 deletions tests/test_node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from pyscipopt import SCIP_RESULT, Eventhdlr, SCIP_EVENTTYPE
from helpers.utils import random_mip_1

class cutoffEventHdlr(Eventhdlr):
def eventinit(self):
self.model.catchEvent(SCIP_EVENTTYPE.NODEFOCUSED, self)

def eventexec(self, event):
self.model.cutoffNode(self.model.getCurrentNode())
return {'result': SCIP_RESULT.SUCCESS}

def test_cutoffNode():
m = random_mip_1(disable_heur=True, disable_presolve=True, disable_sepa=True)

hdlr = cutoffEventHdlr()

m.includeEventhdlr(hdlr, "test", "test")

m.optimize()

assert m.getNSols() == 0
2 changes: 1 addition & 1 deletion tests/test_nogil.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


def test_optimalNogil():
ori_model = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, node_lim=2000, small=True)
ori_model = random_mip_1(disable_sepa=False, disable_heur=False, disable_presolve=False, node_lim=2000, small=True)
models = [Model(sourceModel=ori_model) for _ in range(N_Threads)]
for i in range(N_Threads):
models[i].setParam("randomization/permutationseed", i)
Expand Down
6 changes: 3 additions & 3 deletions tests/test_solution.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def test_getSols():


def test_getOrigin_retrasform():
m = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, small=True)
m = random_mip_1(disable_sepa=False, disable_heur=False, disable_presolve=False, small=True)
m.optimize()

sol = m.getBestSol()
Expand All @@ -208,11 +208,11 @@ def test_getOrigin_retrasform():


def test_translate():
m = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, small=True)
m = random_mip_1(disable_sepa=False, disable_heur=False, disable_presolve=False, small=True)
m.optimize()
sol = m.getBestSol()

m1 = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, small=True)
m1 = random_mip_1(disable_sepa=False, disable_heur=False, disable_presolve=False, small=True)
sol1 = sol.translate(m1)
assert m1.addSol(sol1) == True
assert m1.getNSols() == 1
Expand Down
6 changes: 3 additions & 3 deletions tests/test_strong_branching.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def branchexeclp(self, allowaddcons):


def test_strong_branching():
scip = random_mip_1(disable_presolve=False, disable_huer=False, small=True, node_lim=500)
scip = random_mip_1(disable_presolve=False, disable_heur=False, small=True, node_lim=500)

strong_branch_rule = StrongBranchingRule(scip, idempotent=False)
scip.includeBranchrule(strong_branch_rule, "strong branch rule", "custom strong branching rule",
Expand All @@ -155,7 +155,7 @@ def test_strong_branching():


def test_strong_branching_idempotent():
scip = random_mip_1(disable_presolve=False, disable_huer=False, small=True, node_lim=500)
scip = random_mip_1(disable_presolve=False, disable_heur=False, small=True, node_lim=500)

strong_branch_rule = StrongBranchingRule(scip, idempotent=True)
scip.includeBranchrule(strong_branch_rule, "strong branch rule", "custom strong branching rule",
Expand All @@ -170,7 +170,7 @@ def test_strong_branching_idempotent():


def test_dummy_feature_selector():
scip = random_mip_1(disable_presolve=False, disable_huer=False, small=True, node_lim=300)
scip = random_mip_1(disable_presolve=False, disable_heur=False, small=True, node_lim=300)

feature_dummy_branch_rule = FeatureSelectorBranchingRule(scip)
scip.includeBranchrule(feature_dummy_branch_rule, "dummy branch rule", "custom feature creation branching rule",
Expand Down

0 comments on commit b94dbb7

Please sign in to comment.