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

solve problem with multi-threads #918

Merged
merged 7 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
- Added additional tests to test_nodesel, test_heur, and test_strong_branching
- Migrated documentation to Readthedocs
- `attachEventHandlerCallback` method to Model for a more ergonomic way to attach event handlers
- Added Model method: optimizeNogil
- Added Solution method: getOrigin, retransform, translate
- Added SCIP.pxd: SCIP_SOLORIGIN, SCIPcopyOrigVars, SCIPcopyOrigConss, SCIPsolve nogil, SCIPretransformSol, SCIPtranslateSubSol, SCIPsolGetOrigin, SCIPhashmapCreate, SCIPhashmapFree
- Added additional tests to test_multi_threads, test_solution, and test_copy
### Fixed
- Fixed too strict getObjVal, getVal check
### Changed
Expand Down
1 change: 1 addition & 0 deletions src/pyscipopt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@
from pyscipopt.scip import PY_SCIP_BRANCHDIR as SCIP_BRANCHDIR
from pyscipopt.scip import PY_SCIP_BENDERSENFOTYPE as SCIP_BENDERSENFOTYPE
from pyscipopt.scip import PY_SCIP_ROWORIGINTYPE as SCIP_ROWORIGINTYPE
from pyscipopt.scip import PY_SCIP_SOLORIGIN as SCIP_SOLORIGIN
23 changes: 22 additions & 1 deletion src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,17 @@ cdef extern from "scip/scip.h":
SCIP_ROWORIGINTYPE SCIP_ROWORIGINTYPE_SEPA
SCIP_ROWORIGINTYPE SCIP_ROWORIGINTYPE_REOPT

ctypedef int SCIP_SOLORIGIN
cdef extern from "scip/type_sol.h":
SCIP_SOLORIGIN SCIP_SOLORIGIN_ORIGINAL
SCIP_SOLORIGIN SCIP_SOLORIGIN_ZERO
SCIP_SOLORIGIN SCIP_SOLORIGIN_LPSOL
SCIP_SOLORIGIN SCIP_SOLORIGIN_NLPSOL
SCIP_SOLORIGIN SCIP_SOLORIGIN_RELAXSOL
SCIP_SOLORIGIN SCIP_SOLORIGIN_PSEUDOSOL
SCIP_SOLORIGIN SCIP_SOLORIGIN_PARTIAL
SCIP_SOLORIGIN SCIP_SOLORIGIN_UNKNOWN

ctypedef bint SCIP_Bool

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

# Solve Methods
SCIP_RETCODE SCIPsolve(SCIP* scip)
SCIP_RETCODE SCIPsolve(SCIP* scip) noexcept nogil
SCIP_RETCODE SCIPsolveConcurrent(SCIP* scip)
SCIP_RETCODE SCIPfreeTransform(SCIP* scip)
SCIP_RETCODE SCIPpresolve(SCIP* scip)
Expand Down Expand Up @@ -871,7 +885,9 @@ cdef extern from "scip/scip.h":
SCIP_RETCODE SCIPreadSolFile(SCIP* scip, const char* filename, SCIP_SOL* sol, SCIP_Bool xml, SCIP_Bool* partial, SCIP_Bool* error)
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)
SCIP_RETCODE SCIPcheckSolOrig(SCIP* scip, SCIP_SOL* sol, SCIP_Bool* feasible, SCIP_Bool printreason, SCIP_Bool completely)

SCIP_RETCODE SCIPretransformSol(SCIP* scip, SCIP_SOL* sol)
SCIP_RETCODE SCIPtranslateSubSol(SCIP* scip, SCIP* subscip, SCIP_SOL* subsol, SCIP_HEUR* heur, SCIP_VAR** subvars, SCIP_SOL** newsol)
SCIP_SOLORIGIN SCIPsolGetOrigin(SCIP_SOL* sol)
SCIP_Real SCIPgetSolTime(SCIP* scip, SCIP_SOL* sol)

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

BMS_BLKMEM* SCIPblkmem(SCIP* scip)

# pub_misc.h
SCIP_RETCODE SCIPhashmapCreate(SCIP_HASHMAP** hashmap, BMS_BLKMEM* blkmem, int mapsize)
void SCIPhashmapFree(SCIP_HASHMAP** hashmap)


cdef extern from "scip/tree.h":
int SCIPnodeGetNAddedConss(SCIP_NODE* node)

Expand Down
52 changes: 52 additions & 0 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,21 @@
SEPA = SCIP_ROWORIGINTYPE_SEPA
REOPT = SCIP_ROWORIGINTYPE_REOPT

cdef class PY_SCIP_SOLORIGIN:
ORIGINAL = SCIP_SOLORIGIN_ORIGINAL
ZERO = SCIP_SOLORIGIN_ZERO
LPSOL = SCIP_SOLORIGIN_LPSOL
NLPSOL = SCIP_SOLORIGIN_NLPSOL
RELAXSOL = SCIP_SOLORIGIN_RELAXSOL
PSEUDOSOL = SCIP_SOLORIGIN_PSEUDOSOL
PARTIAL = SCIP_SOLORIGIN_PARTIAL
UNKNOWN = SCIP_SOLORIGIN_UNKNOWN

def PY_SCIP_CALL(SCIP_RETCODE rc):
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 / 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 / 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 / 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.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 / 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.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.11)

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.12)

SCIP: unspecified error!
elif rc == SCIP_NOMEMORY:
raise MemoryError('SCIP: insufficient memory error!')
elif rc == SCIP_READERROR:
Expand Down Expand Up @@ -1009,6 +1019,40 @@
if not stage_check or self.sol == NULL and SCIPgetStage(self.scip) != SCIP_STAGE_SOLVING:
raise Warning(f"{method} can only be called with a valid solution or in stage SOLVING (current stage: {SCIPgetStage(self.scip)})")

def getOrigin(self):
"""
Returns origin of solution: where to retrieve uncached elements.

Returns
-------
PY_SCIP_SOLORIGIN
"""
return SCIPsolGetOrigin(self.sol)

def retransform(self):
""" retransforms solution to original problem space """
PY_SCIP_CALL(SCIPretransformSol(self.scip, self.sol))

def translate(self, Model target):
"""
translate solution to a target model solution

Parameters
----------
target : Model

Returns
-------
targetSol: Solution
"""
if self.getOrigin() != SCIP_SOLORIGIN_ORIGINAL:
PY_SCIP_CALL(SCIPretransformSol(self.scip, self.sol))
cdef Solution targetSol = Solution.create(target._scip, NULL)
cdef SCIP_VAR** source_vars = SCIPgetOrigVars(self.scip)

PY_SCIP_CALL(SCIPtranslateSubSol(target._scip, self.scip, self.sol, NULL, source_vars, &(targetSol.sol)))
return targetSol


cdef class BoundChange:
"""Bound change."""
Expand Down Expand Up @@ -6170,6 +6214,14 @@
"""Optimize the problem."""
PY_SCIP_CALL(SCIPsolve(self._scip))
self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip))

def optimizeNogil(self):
"""Optimize the problem without GIL."""
cdef SCIP_RETCODE rc;
with nogil:
rc = SCIPsolve(self._scip)
PY_SCIP_CALL(rc)
self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip))

def solveConcurrent(self):
"""Transforms, presolves, and solves problem using additional solvers which emphasize on
Expand Down
2 changes: 2 additions & 0 deletions tests/test_copy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pyscipopt import Model
from helpers.utils import random_mip_1

def test_copy():
# create solver instance
Expand All @@ -18,3 +19,4 @@ def test_copy():
s2.optimize()

assert s.getObjVal() == s2.getObjVal()

23 changes: 23 additions & 0 deletions tests/test_nogil.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from concurrent.futures import ThreadPoolExecutor, as_completed
from pyscipopt import Model
from helpers.utils import random_mip_1

N_Threads = 4


def test_optimalNogil():
ori_model = random_mip_1(disable_sepa=False, disable_huer=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)

ori_model.optimize()

with ThreadPoolExecutor(max_workers=N_Threads) as executor:
futures = [executor.submit(Model.optimizeNogil, model) for model in models]
for future in as_completed(futures):
pass
for model in models:
assert model.getStatus() == "optimal"
assert abs(ori_model.getObjVal() - model.getObjVal()) < 1e-6

28 changes: 27 additions & 1 deletion tests/test_solution.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re
import pytest
from pyscipopt import Model, scip, SCIP_PARAMSETTING, quicksum, quickprod
from pyscipopt import Model, scip, SCIP_PARAMSETTING, quicksum, quickprod, SCIP_SOLORIGIN
from helpers.utils import random_mip_1


def test_solution_getbest():
Expand Down Expand Up @@ -193,3 +194,28 @@ def test_getSols():

assert len(m.getSols()) >= 1
assert any(m.isEQ(sol[x], 0.0) for sol in m.getSols())


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

sol = m.getBestSol()
assert sol.getOrigin() == SCIP_SOLORIGIN.ZERO

sol.retransform()
assert sol.getOrigin() == SCIP_SOLORIGIN.ORIGINAL


def test_translate():
m = random_mip_1(disable_sepa=False, disable_huer=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)
sol1 = sol.translate(m1)
assert m1.addSol(sol1) == True
assert m1.getNSols() == 1
m1.optimize()
assert m.getObjVal() == m1.getObjVal()

Loading