Skip to content

Commit 6876af9

Browse files
liangbugLight1LeeJoao-Dionisio
authored
solve problem with multi-threads (#918)
* 1. model optimize without gil 2. solveFirstInterruptOthers 3. add copy model method 4. add copy model solution 5. add some Solution methods * 1. model optimize without gil 2. model addOrigVarsConssObjectiveFrom 3. add some Solution methods * Update src/pyscipopt/scip.pxi Co-authored-by: João Dionísio <[email protected]> * remove addOrigVarsConssObjectiveFrom * rename getSolOrigin to getOrigin --------- Co-authored-by: Light1_Lee <[email protected]> Co-authored-by: João Dionísio <[email protected]>
1 parent c24e9df commit 6876af9

File tree

7 files changed

+131
-2
lines changed

7 files changed

+131
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
- Added additional tests to test_nodesel, test_heur, and test_strong_branching
2626
- Migrated documentation to Readthedocs
2727
- `attachEventHandlerCallback` method to Model for a more ergonomic way to attach event handlers
28+
- Added Model method: optimizeNogil
29+
- Added Solution method: getOrigin, retransform, translate
30+
- Added SCIP.pxd: SCIP_SOLORIGIN, SCIPcopyOrigVars, SCIPcopyOrigConss, SCIPsolve nogil, SCIPretransformSol, SCIPtranslateSubSol, SCIPsolGetOrigin, SCIPhashmapCreate, SCIPhashmapFree
31+
- Added additional tests to test_multi_threads, test_solution, and test_copy
2832
### Fixed
2933
- Fixed too strict getObjVal, getVal check
3034
### Changed

src/pyscipopt/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@
4646
from pyscipopt.scip import PY_SCIP_BRANCHDIR as SCIP_BRANCHDIR
4747
from pyscipopt.scip import PY_SCIP_BENDERSENFOTYPE as SCIP_BENDERSENFOTYPE
4848
from pyscipopt.scip import PY_SCIP_ROWORIGINTYPE as SCIP_ROWORIGINTYPE
49+
from pyscipopt.scip import PY_SCIP_SOLORIGIN as SCIP_SOLORIGIN

src/pyscipopt/scip.pxd

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,17 @@ cdef extern from "scip/scip.h":
320320
SCIP_ROWORIGINTYPE SCIP_ROWORIGINTYPE_SEPA
321321
SCIP_ROWORIGINTYPE SCIP_ROWORIGINTYPE_REOPT
322322

323+
ctypedef int SCIP_SOLORIGIN
324+
cdef extern from "scip/type_sol.h":
325+
SCIP_SOLORIGIN SCIP_SOLORIGIN_ORIGINAL
326+
SCIP_SOLORIGIN SCIP_SOLORIGIN_ZERO
327+
SCIP_SOLORIGIN SCIP_SOLORIGIN_LPSOL
328+
SCIP_SOLORIGIN SCIP_SOLORIGIN_NLPSOL
329+
SCIP_SOLORIGIN SCIP_SOLORIGIN_RELAXSOL
330+
SCIP_SOLORIGIN SCIP_SOLORIGIN_PSEUDOSOL
331+
SCIP_SOLORIGIN SCIP_SOLORIGIN_PARTIAL
332+
SCIP_SOLORIGIN SCIP_SOLORIGIN_UNKNOWN
333+
323334
ctypedef bint SCIP_Bool
324335

325336
ctypedef long long SCIP_Longint
@@ -532,6 +543,8 @@ cdef extern from "scip/scip.h":
532543
SCIP_Bool threadsafe,
533544
SCIP_Bool passmessagehdlr,
534545
SCIP_Bool* valid)
546+
SCIP_RETCODE SCIPcopyOrigVars(SCIP* sourcescip, SCIP* targetscip, SCIP_HASHMAP* varmap, SCIP_HASHMAP* consmap, SCIP_VAR** fixedvars, SCIP_Real* fixedvals, int nfixedvars )
547+
SCIP_RETCODE SCIPcopyOrigConss(SCIP* sourcescip, SCIP* targetscip, SCIP_HASHMAP* varmap, SCIP_HASHMAP* consmap, SCIP_Bool enablepricing, SCIP_Bool* valid)
535548
SCIP_RETCODE SCIPmessagehdlrCreate(SCIP_MESSAGEHDLR **messagehdlr,
536549
SCIP_Bool bufferedoutput,
537550
const char *filename,
@@ -669,6 +682,7 @@ cdef extern from "scip/scip.h":
669682

670683
# Solve Methods
671684
SCIP_RETCODE SCIPsolve(SCIP* scip)
685+
SCIP_RETCODE SCIPsolve(SCIP* scip) noexcept nogil
672686
SCIP_RETCODE SCIPsolveConcurrent(SCIP* scip)
673687
SCIP_RETCODE SCIPfreeTransform(SCIP* scip)
674688
SCIP_RETCODE SCIPpresolve(SCIP* scip)
@@ -871,7 +885,9 @@ cdef extern from "scip/scip.h":
871885
SCIP_RETCODE SCIPreadSolFile(SCIP* scip, const char* filename, SCIP_SOL* sol, SCIP_Bool xml, SCIP_Bool* partial, SCIP_Bool* error)
872886
SCIP_RETCODE SCIPcheckSol(SCIP* scip, SCIP_SOL* sol, SCIP_Bool printreason, SCIP_Bool completely, SCIP_Bool checkbounds, SCIP_Bool checkintegrality, SCIP_Bool checklprows, SCIP_Bool* feasible)
873887
SCIP_RETCODE SCIPcheckSolOrig(SCIP* scip, SCIP_SOL* sol, SCIP_Bool* feasible, SCIP_Bool printreason, SCIP_Bool completely)
874-
888+
SCIP_RETCODE SCIPretransformSol(SCIP* scip, SCIP_SOL* sol)
889+
SCIP_RETCODE SCIPtranslateSubSol(SCIP* scip, SCIP* subscip, SCIP_SOL* subsol, SCIP_HEUR* heur, SCIP_VAR** subvars, SCIP_SOL** newsol)
890+
SCIP_SOLORIGIN SCIPsolGetOrigin(SCIP_SOL* sol)
875891
SCIP_Real SCIPgetSolTime(SCIP* scip, SCIP_SOL* sol)
876892

877893
SCIP_RETCODE SCIPsetRelaxSolVal(SCIP* scip, SCIP_RELAX* relax, SCIP_VAR* var, SCIP_Real val)
@@ -1367,6 +1383,11 @@ cdef extern from "scip/scip.h":
13671383

13681384
BMS_BLKMEM* SCIPblkmem(SCIP* scip)
13691385

1386+
# pub_misc.h
1387+
SCIP_RETCODE SCIPhashmapCreate(SCIP_HASHMAP** hashmap, BMS_BLKMEM* blkmem, int mapsize)
1388+
void SCIPhashmapFree(SCIP_HASHMAP** hashmap)
1389+
1390+
13701391
cdef extern from "scip/tree.h":
13711392
int SCIPnodeGetNAddedConss(SCIP_NODE* node)
13721393

src/pyscipopt/scip.pxi

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,16 @@ cdef class PY_SCIP_ROWORIGINTYPE:
258258
SEPA = SCIP_ROWORIGINTYPE_SEPA
259259
REOPT = SCIP_ROWORIGINTYPE_REOPT
260260

261+
cdef class PY_SCIP_SOLORIGIN:
262+
ORIGINAL = SCIP_SOLORIGIN_ORIGINAL
263+
ZERO = SCIP_SOLORIGIN_ZERO
264+
LPSOL = SCIP_SOLORIGIN_LPSOL
265+
NLPSOL = SCIP_SOLORIGIN_NLPSOL
266+
RELAXSOL = SCIP_SOLORIGIN_RELAXSOL
267+
PSEUDOSOL = SCIP_SOLORIGIN_PSEUDOSOL
268+
PARTIAL = SCIP_SOLORIGIN_PARTIAL
269+
UNKNOWN = SCIP_SOLORIGIN_UNKNOWN
270+
261271
def PY_SCIP_CALL(SCIP_RETCODE rc):
262272
if rc == SCIP_OKAY:
263273
pass
@@ -1009,6 +1019,40 @@ cdef class Solution:
10091019
if not stage_check or self.sol == NULL and SCIPgetStage(self.scip) != SCIP_STAGE_SOLVING:
10101020
raise Warning(f"{method} can only be called with a valid solution or in stage SOLVING (current stage: {SCIPgetStage(self.scip)})")
10111021

1022+
def getOrigin(self):
1023+
"""
1024+
Returns origin of solution: where to retrieve uncached elements.
1025+
1026+
Returns
1027+
-------
1028+
PY_SCIP_SOLORIGIN
1029+
"""
1030+
return SCIPsolGetOrigin(self.sol)
1031+
1032+
def retransform(self):
1033+
""" retransforms solution to original problem space """
1034+
PY_SCIP_CALL(SCIPretransformSol(self.scip, self.sol))
1035+
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.getOrigin() != 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+
10121056

10131057
cdef class BoundChange:
10141058
"""Bound change."""
@@ -6170,6 +6214,14 @@ cdef class Model:
61706214
"""Optimize the problem."""
61716215
PY_SCIP_CALL(SCIPsolve(self._scip))
61726216
self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip))
6217+
6218+
def optimizeNogil(self):
6219+
"""Optimize the problem without GIL."""
6220+
cdef SCIP_RETCODE rc;
6221+
with nogil:
6222+
rc = SCIPsolve(self._scip)
6223+
PY_SCIP_CALL(rc)
6224+
self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip))
61736225

61746226
def solveConcurrent(self):
61756227
"""Transforms, presolves, and solves problem using additional solvers which emphasize on

tests/test_copy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from pyscipopt import Model
2+
from helpers.utils import random_mip_1
23

34
def test_copy():
45
# create solver instance
@@ -18,3 +19,4 @@ def test_copy():
1819
s2.optimize()
1920

2021
assert s.getObjVal() == s2.getObjVal()
22+

tests/test_nogil.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from concurrent.futures import ThreadPoolExecutor, as_completed
2+
from pyscipopt import Model
3+
from helpers.utils import random_mip_1
4+
5+
N_Threads = 4
6+
7+
8+
def test_optimalNogil():
9+
ori_model = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, node_lim=2000, small=True)
10+
models = [Model(sourceModel=ori_model) for _ in range(N_Threads)]
11+
for i in range(N_Threads):
12+
models[i].setParam("randomization/permutationseed", i)
13+
14+
ori_model.optimize()
15+
16+
with ThreadPoolExecutor(max_workers=N_Threads) as executor:
17+
futures = [executor.submit(Model.optimizeNogil, model) for model in models]
18+
for future in as_completed(futures):
19+
pass
20+
for model in models:
21+
assert model.getStatus() == "optimal"
22+
assert abs(ori_model.getObjVal() - model.getObjVal()) < 1e-6
23+

tests/test_solution.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import re
22
import pytest
3-
from pyscipopt import Model, scip, SCIP_PARAMSETTING, quicksum, quickprod
3+
from pyscipopt import Model, scip, SCIP_PARAMSETTING, quicksum, quickprod, SCIP_SOLORIGIN
4+
from helpers.utils import random_mip_1
45

56

67
def test_solution_getbest():
@@ -193,3 +194,28 @@ def test_getSols():
193194

194195
assert len(m.getSols()) >= 1
195196
assert any(m.isEQ(sol[x], 0.0) for sol in m.getSols())
197+
198+
199+
def test_getOrigin_retrasform():
200+
m = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, small=True)
201+
m.optimize()
202+
203+
sol = m.getBestSol()
204+
assert sol.getOrigin() == SCIP_SOLORIGIN.ZERO
205+
206+
sol.retransform()
207+
assert sol.getOrigin() == SCIP_SOLORIGIN.ORIGINAL
208+
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)