@@ -15,9 +15,10 @@ from libc.stdlib cimport malloc, free
1515from libc.stdio cimport fdopen, fclose
1616from posix.stdio cimport fileno
1717
18- from collections.abc import Iterable
18+ from collections.abc import Iterable, Sequence
1919from itertools import repeat
2020from dataclasses import dataclass
21+ from concurrent.futures import ThreadPoolExecutor, as_completed
2122
2223include " expr.pxi"
2324include " lp.pxi"
@@ -258,6 +259,16 @@ cdef class PY_SCIP_ROWORIGINTYPE:
258259 SEPA = SCIP_ROWORIGINTYPE_SEPA
259260 REOPT = SCIP_ROWORIGINTYPE_REOPT
260261
262+ cdef class PY_SCIP_SOLORIGIN:
263+ ORIGINAL = SCIP_SOLORIGIN_ORIGINAL
264+ ZERO = SCIP_SOLORIGIN_ZERO
265+ LPSOL = SCIP_SOLORIGIN_LPSOL
266+ NLPSOL = SCIP_SOLORIGIN_NLPSOL
267+ RELAXSOL = SCIP_SOLORIGIN_RELAXSOL
268+ PSEUDOSOL = SCIP_SOLORIGIN_PSEUDOSOL
269+ PARTIAL = SCIP_SOLORIGIN_PARTIAL
270+ UNKNOWN = SCIP_SOLORIGIN_UNKNOWN
271+
261272def PY_SCIP_CALL (SCIP_RETCODE rc ):
262273 if rc == SCIP_OKAY:
263274 pass
@@ -1009,6 +1020,19 @@ cdef class Solution:
10091020 if not stage_check or self .sol == NULL and SCIPgetStage(self .scip) != SCIP_STAGE_SOLVING:
10101021 raise Warning (f" {method} can only be called with a valid solution or in stage SOLVING (current stage: {SCIPgetStage(self.scip)})" )
10111022
1023+ def getSolOrigin (self ):
1024+ """
1025+ Returns origin of solution: where to retrieve uncached elements.
1026+
1027+ Returns
1028+ -------
1029+ PY_SCIP_SOLORIGIN
1030+ """
1031+ return SCIPsolGetOrigin(self .sol)
1032+
1033+ def retransform (self ):
1034+ """ retransforms solution to original problem space """
1035+ PY_SCIP_CALL(SCIPretransformSol(self .scip, self .sol))
10121036
10131037cdef class BoundChange:
10141038 """ Bound change."""
@@ -2050,6 +2074,39 @@ cdef class Model:
20502074 n = str_conversion(problemName)
20512075 PY_SCIP_CALL(SCIPcreateProbBasic(self ._scip, n))
20522076
2077+ def copyModel (self , problemName = ' copy_model' ):
2078+ """
2079+ Create a copy of the model/problem.
2080+
2081+ Parameters
2082+ ----------
2083+ problemName : str, optional
2084+ name of model or problem (Default value = 'model')
2085+
2086+ """
2087+ cdef Model cpy
2088+ cdef SCIP_Bool valid
2089+ cdef SCIP_HASHMAP* localvarmap
2090+ cdef SCIP_HASHMAP* localconsmap
2091+
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)
2098+
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))
2105+
2106+ SCIPhashmapFree(& localvarmap)
2107+ SCIPhashmapFree(& localconsmap)
2108+ return cpy
2109+
20532110 def freeProb (self ):
20542111 """ Frees problem and solution process data."""
20552112 PY_SCIP_CALL(SCIPfreeProb(self ._scip))
@@ -6161,6 +6218,44 @@ cdef class Model:
61616218 """ Optimize the problem."""
61626219 PY_SCIP_CALL(SCIPsolve(self ._scip))
61636220 self ._bestSol = Solution.create(self ._scip, SCIPgetBestSol(self ._scip))
6221+
6222+ def optimizeNogil (self ):
6223+ """ Optimize the problem without GIL."""
6224+ cdef SCIP_RETCODE rc;
6225+ with nogil:
6226+ rc = SCIPsolve(self ._scip)
6227+ PY_SCIP_CALL(rc)
6228+ self ._bestSol = Solution.create(self ._scip, SCIPgetBestSol(self ._scip))
6229+
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]
61646259
61656260 def solveConcurrent (self ):
61666261 """ Transforms, presolves, and solves problem using additional solvers which emphasize on
@@ -8026,6 +8121,23 @@ cdef class Model:
80268121 PY_SCIP_CALL(SCIPaddSol(self ._scip, solution.sol, & stored))
80278122 return stored
80288123
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+
80298141 def freeSol (self , Solution solution ):
80308142 """
80318143 Free given solution
0 commit comments