Skip to content

Commit 0bdee6c

Browse files
committed
1. model optimize without gil
2. model addOrigVarsConssObjectiveFrom 3. add some Solution methods
1 parent 6318bf8 commit 0bdee6c

File tree

6 files changed

+62
-126
lines changed

6 files changed

+62
-126
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
- Added additional tests to test_nodesel, test_heur, and test_strong_branching
1818
- Migrated documentation to Readthedocs
1919
- `attachEventHandlerCallback` method to Model for a more ergonomic way to attach event handlers
20-
- Added optimalNogil, getSolOrigin, retransform, copyModel, solveFirstInterruptOthers
21-
- Added SCIPsolve nogil, SCIPretransformSol, SCIPtranslateSubSol, SCIPsolGetOrigin, SCIPcopyOrigProb, SCIPcopyOrigVars, SCIPcopyOrigConss, SCIPhashmapCreate, SCIPhashmapFree
20+
- Added Model method: optimizeNogil, addOrigVarsConssObjectiveFrom
21+
- Added Solution method: getSolOrigin, retransform, translate
22+
- Added SCIP.pxd: SCIP_SOLORIGIN, SCIPcopyOrigVars, SCIPcopyOrigConss, SCIPsolve nogil, SCIPretransformSol, SCIPtranslateSubSol, SCIPsolGetOrigin, SCIPhashmapCreate, SCIPhashmapFree
2223
- Added additional tests to test_multi_threads, test_solution, and test_copy
2324
### Fixed
2425
- Fixed too strict getObjVal, getVal check

src/pyscipopt/scip.pxd

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@ cdef extern from "scip/scip.h":
543543
SCIP_Bool threadsafe,
544544
SCIP_Bool passmessagehdlr,
545545
SCIP_Bool* valid)
546-
SCIP_RETCODE SCIPcopyOrigProb(SCIP* sourcescip, SCIP* targetscip, SCIP_HASHMAP* varmap, SCIP_HASHMAP* consmap, const char* name )
547546
SCIP_RETCODE SCIPcopyOrigVars(SCIP* sourcescip, SCIP* targetscip, SCIP_HASHMAP* varmap, SCIP_HASHMAP* consmap, SCIP_VAR** fixedvars, SCIP_Real* fixedvals, int nfixedvars )
548547
SCIP_RETCODE SCIPcopyOrigConss(SCIP* sourcescip, SCIP* targetscip, SCIP_HASHMAP* varmap, SCIP_HASHMAP* consmap, SCIP_Bool enablepricing, SCIP_Bool* valid)
549548
SCIP_RETCODE SCIPmessagehdlrCreate(SCIP_MESSAGEHDLR **messagehdlr,

src/pyscipopt/scip.pxi

Lines changed: 31 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@ from libc.stdlib cimport malloc, free
1515
from libc.stdio cimport fdopen, fclose
1616
from posix.stdio cimport fileno
1717

18-
from collections.abc import Iterable, Sequence
18+
from collections.abc import Iterable
1919
from itertools import repeat
2020
from dataclasses import dataclass
21-
from concurrent.futures import ThreadPoolExecutor, as_completed
2221

2322
include "expr.pxi"
2423
include "lp.pxi"
@@ -1034,6 +1033,27 @@ cdef class Solution:
10341033
""" retransforms solution to original problem space """
10351034
PY_SCIP_CALL(SCIPretransformSol(self.scip, self.sol))
10361035

1036+
def translate(self, Model target):
1037+
"""
1038+
translate solution to a target model solution
1039+
1040+
Parameters
1041+
----------
1042+
target : Model
1043+
1044+
Returns
1045+
-------
1046+
targetSol: Solution
1047+
"""
1048+
if self.getSolOrigin() != SCIP_SOLORIGIN_ORIGINAL:
1049+
PY_SCIP_CALL(SCIPretransformSol(self.scip, self.sol))
1050+
cdef Solution targetSol = Solution.create(target._scip, NULL)
1051+
cdef SCIP_VAR** source_vars = SCIPgetOrigVars(self.scip)
1052+
1053+
PY_SCIP_CALL(SCIPtranslateSubSol(target._scip, self.scip, self.sol, NULL, source_vars, &(targetSol.sol)))
1054+
return targetSol
1055+
1056+
10371057
cdef class BoundChange:
10381058
"""Bound change."""
10391059

@@ -2074,38 +2094,28 @@ cdef class Model:
20742094
n = str_conversion(problemName)
20752095
PY_SCIP_CALL(SCIPcreateProbBasic(self._scip, n))
20762096

2077-
def copyModel(self, problemName='copy_model'):
2097+
def addOrigVarsConssObjectiveFrom(self, Model source):
20782098
"""
2079-
Create a copy of the model/problem.
2099+
add original variables and constraints from source model.
20802100
20812101
Parameters
20822102
----------
2083-
problemName : str, optional
2084-
name of model or problem (Default value = 'model')
2085-
2103+
source : Model
2104+
source model copy original variables and constraints to target(self) model
20862105
"""
2087-
cdef Model cpy
20882106
cdef SCIP_Bool valid
20892107
cdef SCIP_HASHMAP* localvarmap
20902108
cdef SCIP_HASHMAP* localconsmap
20912109

2092-
cpy = Model(createscip=False)
2093-
PY_SCIP_CALL(SCIPcreate(&cpy._scip))
2094-
cpy._bestSol = None
2095-
cpy.includeDefaultPlugins()
2096-
# cpy._bestSol = <Solution> model._bestSol
2097-
cname = str_conversion(problemName)
2110+
PY_SCIP_CALL( SCIPhashmapCreate(&localvarmap, SCIPblkmem(self._scip), SCIPgetNVars(source._scip)) )
2111+
PY_SCIP_CALL( SCIPhashmapCreate(&localconsmap, SCIPblkmem(self._scip), SCIPgetNConss(source._scip)) )
20982112

2099-
PY_SCIP_CALL( SCIPhashmapCreate(&localvarmap, SCIPblkmem(cpy._scip), SCIPgetNVars(self._scip)) )
2100-
PY_SCIP_CALL( SCIPhashmapCreate(&localconsmap, SCIPblkmem(cpy._scip), SCIPgetNConss(self._scip)) )
2101-
2102-
PY_SCIP_CALL(SCIPcopyOrigProb(self._scip, cpy._scip, localvarmap, localconsmap, cname))
2103-
PY_SCIP_CALL(SCIPcopyOrigVars(self._scip, cpy._scip, localvarmap, localconsmap, NULL, NULL, 0))
2104-
PY_SCIP_CALL(SCIPcopyOrigConss(self._scip, cpy._scip, localvarmap, localconsmap, False, &valid))
2113+
PY_SCIP_CALL(SCIPcopyOrigVars(source._scip, self._scip, localvarmap, localconsmap, NULL, NULL, 0))
2114+
PY_SCIP_CALL(SCIPcopyOrigConss(source._scip, self._scip, localvarmap, localconsmap, False, &valid))
2115+
PY_SCIP_CALL(SCIPsetObjsense(self._scip, SCIPgetObjsense(source._scip)))
21052116

21062117
SCIPhashmapFree(&localvarmap)
21072118
SCIPhashmapFree(&localconsmap)
2108-
return cpy
21092119

21102120
def freeProb(self):
21112121
"""Frees problem and solution process data."""
@@ -6227,36 +6237,6 @@ cdef class Model:
62276237
PY_SCIP_CALL(rc)
62286238
self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip))
62296239

6230-
@staticmethod
6231-
def solveFirstInterruptOthers(executor: ThreadPoolExecutor, models: Sequence[Model]) -> Tuple[int, Model]:
6232-
"""
6233-
Solve models return the first solved model and interrupt other models.
6234-
6235-
Parameters
6236-
----------
6237-
executor : ThreadPoolExecutor
6238-
models: Sequence[Model]
6239-
6240-
Returns
6241-
-------
6242-
first_seq : int
6243-
returns the index of the first resolved model from models
6244-
models[first_idx] : Model
6245-
returns the first resolved model
6246-
"""
6247-
futures = [executor.submit(Model.optimizeNogil, model) for model in models]
6248-
interrupt_callback = lambda model: model.interruptSolve()
6249-
for future in as_completed(futures):
6250-
first_future = future
6251-
break
6252-
for idx, furture_to_cancel in enumerate(futures):
6253-
if furture_to_cancel != first_future:
6254-
interrupt_callback(models[idx])
6255-
furture_to_cancel.cancel()
6256-
else:
6257-
first_idx = idx
6258-
return first_idx, models[first_idx]
6259-
62606240
def solveConcurrent(self):
62616241
"""Transforms, presolves, and solves problem using additional solvers which emphasize on
62626242
finding solutions.
@@ -8121,23 +8101,6 @@ cdef class Model:
81218101
PY_SCIP_CALL(SCIPaddSol(self._scip, solution.sol, &stored))
81228102
return stored
81238103

8124-
def addCopyModelSol(self, Solution solution):
8125-
if solution.getSolOrigin() != SCIP_SOLORIGIN_ORIGINAL:
8126-
PY_SCIP_CALL(SCIPretransformSol(solution.scip, solution.sol))
8127-
cdef Solution newsol = Solution.create(self._scip, NULL)
8128-
cdef SCIP_VAR** subvars = SCIPgetOrigVars(solution.scip)
8129-
SCIPtranslateSubSol(self._scip, solution.scip, solution.sol, NULL, subvars, &(newsol.sol))
8130-
self.addSol(newsol, free=True)
8131-
8132-
def addCopyModelBestSol(self, Model cpy_model):
8133-
solution = cpy_model.getBestSol()
8134-
self.addCopyModelSol(solution)
8135-
8136-
def addCopyModelSols(self, Model cpy_model):
8137-
solutions = cpy_model.getSols()
8138-
for solution in solutions:
8139-
self.addCopyModelSol(solution)
8140-
81418104
def freeSol(self, Solution solution):
81428105
"""
81438106
Free given solution

tests/test_copy.py

Lines changed: 14 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from pyscipopt import Model
22
from helpers.utils import random_mip_1
33

4-
54
def test_copy():
65
# create solver instance
76
s = Model()
@@ -22,47 +21,21 @@ def test_copy():
2221
assert s.getObjVal() == s2.getObjVal()
2322

2423

25-
def test_copyModel():
26-
ori_model = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, node_lim=2000, small=False)
27-
cpy_model = ori_model.copyModel()
28-
sub_model = Model(sourceModel=ori_model)
29-
30-
assert len(ori_model.getParams()) == len(cpy_model.getParams()) > len(sub_model.getParams())
31-
assert ori_model.getNVars() == cpy_model.getNVars()
32-
assert ori_model.getNConss() == cpy_model.getNConss()
33-
34-
ori_model.optimize()
35-
cpy_model.optimize()
36-
assert ori_model.getStatus() == cpy_model.getStatus() == "optimal"
37-
assert ori_model.getObjVal() == cpy_model.getObjVal()
38-
39-
40-
def test_addCopyModelSol_BestSol_Sols():
41-
ori_model = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, node_lim=2000, small=False)
42-
cpy_model0 = ori_model.copyModel()
43-
cpy_model1 = ori_model.copyModel()
44-
cpy_model2 = ori_model.copyModel()
45-
46-
ori_model.optimize()
47-
solution = ori_model.getBestSol()
24+
def test_addOrigVarsConssObjectiveFrom():
25+
m = Model()
26+
x = m.addVar("x", vtype = 'B')
27+
y = m.addVar("y", vtype = 'B')
28+
m.addCons(x + y >= 1)
29+
m.addCons(x + y <= 2)
30+
m.setObjective(x + y, 'maximize')
4831

49-
cpy_model0.addCopyModelSol(solution)
50-
cpy_model1.addCopyModelBestSol(ori_model)
51-
cpy_model2.addCopyModelSols(ori_model)
32+
m1 = Model()
33+
m1.addOrigVarsConssObjectiveFrom(m)
5234

53-
assert cpy_model0.getNSols() == 1
54-
assert cpy_model1.getNSols() == 1
55-
assert cpy_model2.getNSols() == ori_model.getNSols() >= 1
35+
m.optimize()
36+
m1.optimize()
5637

57-
cpy_model0.optimize()
58-
cpy_model1.optimize()
59-
cpy_model2.optimize()
38+
assert m.getNVars(transformed=False) == m1.getNVars(transformed=False)
39+
assert m.getNConss(transformed=False) == m1.getNConss(transformed=False)
40+
assert m.getObjVal() == m1.getObjVal() == 2
6041

61-
assert ori_model.getStatus() == "optimal"
62-
assert cpy_model0.getStatus() == "optimal"
63-
assert cpy_model1.getStatus() == "optimal"
64-
assert cpy_model2.getStatus() == "optimal"
65-
assert abs(ori_model.getObjVal() - cpy_model0.getObjVal()) < 1e-6
66-
assert abs(ori_model.getObjVal() - cpy_model1.getObjVal()) < 1e-6
67-
assert abs(ori_model.getObjVal() - cpy_model2.getObjVal()) < 1e-6
68-

tests/test_multi_threads.py renamed to tests/test_nogil.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
def test_optimalNogil():
99
ori_model = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, node_lim=2000, small=True)
10-
models = [ori_model.copyModel() for _ in range(N_Threads)]
10+
models = [Model(sourceModel=ori_model) for _ in range(N_Threads)]
1111
for i in range(N_Threads):
1212
models[i].setParam("randomization/permutationseed", i)
1313

@@ -21,16 +21,3 @@ def test_optimalNogil():
2121
assert model.getStatus() == "optimal"
2222
assert abs(ori_model.getObjVal() - model.getObjVal()) < 1e-6
2323

24-
25-
def test_solveFirstInterruptOthers():
26-
ori_model = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, node_lim=2000, small=True)
27-
models = [ori_model.copyModel() for _ in range(N_Threads)]
28-
for i in range(N_Threads):
29-
models[i].setParam("randomization/permutationseed", i)
30-
31-
ori_model.optimize()
32-
33-
with ThreadPoolExecutor(max_workers=N_Threads) as executor:
34-
seq, fast_model = Model.solveFirstInterruptOthers(executor=executor, models=models) #
35-
assert fast_model.getStatus() == "optimal"
36-
assert abs(ori_model.getObjVal() - fast_model.getObjVal()) < 1e-6

tests/test_solution.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,16 @@ def test_getSolOrigin_retrasform():
206206
sol.retransform()
207207
assert sol.getSolOrigin() == SCIP_SOLORIGIN.ORIGINAL
208208

209+
210+
def test_translate():
211+
m = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, small=True)
212+
m.optimize()
213+
sol = m.getBestSol()
214+
215+
m1 = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, small=True)
216+
sol1 = sol.translate(m1)
217+
assert m1.addSol(sol1) == True
218+
assert m1.getNSols() == 1
219+
m1.optimize()
220+
assert m.getObjVal() == m1.getObjVal()
221+

0 commit comments

Comments
 (0)