Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added utility interface to SCIP for copyLargeNeighborhoodSearch and its prerequisites #942

Merged
merged 15 commits into from
Jan 15, 2025
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Added printProblem to print problem to stdout
- Added stage checks to presolve, freereoptsolve, freetransform
- Added primal_dual_evolution recipe and a plot recipe
- Added python wrappers for usage of SCIPcopyLargeNeighborhoodSearch, SCIPtranslateSubSol and SCIPhashmapCreate
### Fixed
- Added default names to indicator constraints
### Changed
Expand Down
10 changes: 10 additions & 0 deletions src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,16 @@ cdef extern from "scip/cons_indicator.h":

SCIP_VAR* SCIPgetSlackVarIndicator(SCIP_CONS* cons)

cdef extern from "scip/misc.h":
SCIP_RETCODE SCIPhashmapCreate(SCIP_HASHMAP** hashmap, BMS_BLKMEM* blkmem, int mapsize)
void SCIPhashmapFree(SCIP_HASHMAP** hashmap)

cdef extern from "scip/scip_copy.h":
SCIP_RETCODE SCIPtranslateSubSol(SCIP* scip, SCIP* subscip, SCIP_SOL* subsol, SCIP_HEUR* heur, SCIP_VAR** subvars, SCIP_SOL** newsol)

cdef extern from "scip/heuristics.h":
SCIP_RETCODE SCIPcopyLargeNeighborhoodSearch(SCIP* sourcescip, SCIP* subscip, SCIP_HASHMAP* varmap, const char* suffix, SCIP_VAR** fixedvars, SCIP_Real* fixedvals, int nfixedvars, SCIP_Bool uselprows, SCIP_Bool copycuts, SCIP_Bool* success, SCIP_Bool* valid)

cdef extern from "scip/cons_countsols.h":
SCIP_RETCODE SCIPcount(SCIP* scip)
SCIP_RETCODE SCIPsetParamsCountsols(SCIP* scip)
Expand Down
60 changes: 60 additions & 0 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@
if rc == SCIP_OKAY:
pass
elif rc == SCIP_ERROR:
raise Exception('SCIP: unspecified error!')

Check failure on line 275 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / test-coverage (3.11)

SCIP: unspecified error!

Check failure on line 275 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / Windows-test (3.8)

SCIP: unspecified error!

Check failure on line 275 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / Integration-test (3.8)

SCIP: unspecified error!

Check failure on line 275 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / Integration-test (3.9)

SCIP: unspecified error!

Check failure on line 275 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / Windows-test (3.9)

SCIP: unspecified error!

Check failure on line 275 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / Integration-test (3.10)

SCIP: unspecified error!

Check failure on line 275 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / Integration-test (3.11)

SCIP: unspecified error!

Check failure on line 275 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / Windows-test (3.10)

SCIP: unspecified error!

Check failure on line 275 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / Integration-test (3.12)

SCIP: unspecified error!

Check failure on line 275 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / Windows-test (3.11)

SCIP: unspecified error!

Check failure on line 275 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / Windows-test (3.12)

SCIP: unspecified error!
elif rc == SCIP_NOMEMORY:
raise MemoryError('SCIP: insufficient memory error!')
elif rc == SCIP_READERROR:
Expand Down Expand Up @@ -6833,6 +6833,66 @@
conshdlr.model = <Model>weakref.proxy(self)
conshdlr.name = name
Py_INCREF(conshdlr)

def copyLargeNeighborhoodSearch(self, to_fix, fix_vals) -> Model:
""" Creates a subscip

:param to_fix: A List[Variable] of variables to fix in the subscip
:param fix_vals A List[Real] of the values to which to fix the variables in the subscip (care their order)
:return A Model containing the created subscip

"""
orig_vars = SCIPgetVars(self._scip)
vars = <SCIP_VAR**> malloc(len(to_fix) * sizeof(SCIP_VAR*))
vals = <SCIP_Real*> malloc(len(fix_vals) * sizeof(SCIP_Real))
j = 0
name_to_val = {var.name: val for var, val in zip(to_fix, fix_vals)}
for i, var in enumerate(self.getVars()):
if var.name in name_to_val:
vars[j] = orig_vars[i]
vals[j] = <SCIP_Real>name_to_val[var.name]
j+= 1

cdef SCIP_Bool success
cdef SCIP_Bool valid
cdef SCIP* subscip
cdef SCIP_HASHMAP* varmap

PY_SCIP_CALL(SCIPcreate(&subscip))
PY_SCIP_CALL( SCIPhashmapCreate(&varmap, SCIPblkmem(subscip), self.getNVars()) )
PY_SCIP_CALL( SCIPcopyLargeNeighborhoodSearch(self._scip, subscip, varmap, "LNhS_subscip", vars, vals,
<int>len(to_fix), False, False, &success, &valid) )
sub_model = Model.create(subscip)
sub_model._freescip = True
free(vars)
free(vals)
SCIPhashmapFree(&varmap)
return sub_model

def translateSubSol(self, Model sub_model, Solution sol, heur):
""" Translates a solution of a subscip into a solution of the main scip

:param sub_model The python-wrapper of the subscip
:param sol The python-wrapper of the solution of the subscip
:param heur Python-wrapper of the heuristic that found the solution
:return A python-wrapper of the corresponding solution in the main scip

"""
cdef SCIP_SOL* real_sol
cdef SCIP_SOL* subscip_sol
cdef SCIP_Bool success
subscip_sol = sol.sol
vars = <SCIP_VAR**> malloc(self.getNVars() * sizeof(SCIP_VAR*))
for i, var in enumerate(sub_model.getVars()):
vars[i] = (<Variable>var).scip_var

cdef SCIP_HEUR* _heur
name = str_conversion(heur.name)
_heur = SCIPfindHeur(self._scip, name)
PY_SCIP_CALL( SCIPtranslateSubSol(self._scip, sub_model._scip, subscip_sol, _heur, vars, &real_sol) )
solution = Solution.create(self._scip, real_sol)
free(vars)
return solution

def createCons(self, Conshdlr conshdlr, name, initial=True, separate=True, enforce=True, check=True, propagate=True,
local=False, modifiable=False, dynamic=False, removable=False, stickingatnode=False):
Expand Down
59 changes: 59 additions & 0 deletions tests/test_sub_sol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
Tests the usage of sub solutions found in heuristics with copyLargeNeighborhoodSearch()
"""
import pytest
from pyscipopt import Model, Heur, SCIP_HEURTIMING, SCIP_RESULT


class MyHeur(Heur):
def __init__(self, model: Model, fix_vars, fix_vals):
super().__init__()
self.original_model = model
self.used = False
self.fix_vars = fix_vars
self.fix_vals = fix_vals

def heurexec(self, heurtiming, nodeinfeasible):
print("Hello World")
self.used = True
# fix z to 2 and optimize the remaining problem
m2 = self.original_model.copyLargeNeighborhoodSearch(self.fix_vars, self.fix_vals)
m2.optimize()

# translate the solution to the original problem
sub_sol = m2.getBestSol()
sol_translation = self.original_model.translateSubSol(m2, sub_sol, self)

accepted = self.original_model.trySol(sol_translation)
assert accepted
m2.freeProb()
return {"result": SCIP_RESULT.FOUNDSOL}


def test_sub_sol():
m = Model("sub_sol_test")
x = m.addVar(name="x", lb=0, ub=3, obj=1)
y = m.addVar(name="y", lb=0, ub=3, obj=2)
z = m.addVar(name="z", lb=0, ub=3, obj=3)

m.addCons(4 <= x + y + z)

# include the heuristic
my_heur = MyHeur(m, fix_vars= [z], fix_vals = [2])
m.includeHeur(my_heur, "name", "description", "Y", timingmask=SCIP_HEURTIMING.BEFOREPRESOL, usessubscip=True)

#optimize
m.optimize()
# assert the heuristic did run
assert my_heur.used

heur_sol = [2, 0, 2]
opt_sol = [3, 1, 0]

found_solutions = []
for sol in m.getSols():
found_solutions.append([sol[x], sol[y], sol[z]])

# both the sub_solution and the real optimum should be in the solution pool
assert heur_sol in found_solutions
assert opt_sol in found_solutions
Loading