Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -23,6 +23,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, addOrigVarsConssObjectiveFrom
- Added Solution method: getSolOrigin, 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
75 changes: 75 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 / 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 / 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 / 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.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 / 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 getSolOrigin(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.getSolOrigin() != 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 @@ -2050,6 +2094,29 @@
n = str_conversion(problemName)
PY_SCIP_CALL(SCIPcreateProbBasic(self._scip, n))

def addOrigVarsConssObjectiveFrom(self, Model source):
"""
add original variables and constraints from source model.

Parameters
----------
source : Model
source model copy original variables and constraints to target(self) model
"""
cdef SCIP_Bool valid
cdef SCIP_HASHMAP* localvarmap
cdef SCIP_HASHMAP* localconsmap

PY_SCIP_CALL( SCIPhashmapCreate(&localvarmap, SCIPblkmem(self._scip), SCIPgetNVars(source._scip)) )
PY_SCIP_CALL( SCIPhashmapCreate(&localconsmap, SCIPblkmem(self._scip), SCIPgetNConss(source._scip)) )

PY_SCIP_CALL(SCIPcopyOrigVars(source._scip, self._scip, localvarmap, localconsmap, NULL, NULL, 0))
PY_SCIP_CALL(SCIPcopyOrigConss(source._scip, self._scip, localvarmap, localconsmap, False, &valid))
PY_SCIP_CALL(SCIPsetObjsense(self._scip, SCIPgetObjsense(source._scip)))

SCIPhashmapFree(&localvarmap)
SCIPhashmapFree(&localconsmap)

def freeProb(self):
"""Frees problem and solution process data."""
PY_SCIP_CALL(SCIPfreeProb(self._scip))
Expand Down Expand Up @@ -6161,6 +6228,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
21 changes: 21 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,23 @@ def test_copy():
s2.optimize()

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


def test_addOrigVarsConssObjectiveFrom():
m = Model()
x = m.addVar("x", vtype = 'B')
y = m.addVar("y", vtype = 'B')
m.addCons(x + y >= 1)
m.addCons(x + y <= 2)
m.setObjective(x + y, 'maximize')

m1 = Model()
m1.addOrigVarsConssObjectiveFrom(m)

m.optimize()
m1.optimize()

assert m.getNVars(transformed=False) == m1.getNVars(transformed=False)
assert m.getNConss(transformed=False) == m1.getNConss(transformed=False)
assert m.getObjVal() == m1.getObjVal() == 2

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_getSolOrigin_retrasform():
m = random_mip_1(disable_sepa=False, disable_huer=False, disable_presolve=False, small=True)
m.optimize()

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

sol.retransform()
assert sol.getSolOrigin() == 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