1##@file scip.pyx 2#@brief holding functions in python that reference the SCIP public functions included in scip.pxd 3import weakref 4from os.path import abspath 5from os.path import splitext 6import sys 7import warnings 8 9cimport cython 10from cpython cimport Py_INCREF, Py_DECREF 11from cpython.pycapsule cimport PyCapsule_New, PyCapsule_IsValid, PyCapsule_GetPointer 12from libc.stdlib cimport malloc, free 13from libc.stdio cimport fdopen 14 15include "expr.pxi" 16include "lp.pxi" 17include "benders.pxi" 18include "benderscut.pxi" 19include "branchrule.pxi" 20include "conshdlr.pxi" 21include "event.pxi" 22include "heuristic.pxi" 23include "presol.pxi" 24include "pricer.pxi" 25include "propagator.pxi" 26include "sepa.pxi" 27include "relax.pxi" 28include "nodesel.pxi" 29 30# recommended SCIP version; major version is required 31MAJOR = 7 32MINOR = 0 33PATCH = 1 34 35# for external user functions use def; for functions used only inside the interface (starting with _) use cdef 36# todo: check whether this is currently done like this 37 38if sys.version_info >= (3, 0): 39 str_conversion = lambda x:bytes(x,'utf-8') 40else: 41 str_conversion = lambda x:x 42 43_SCIP_BOUNDTYPE_TO_STRING = {SCIP_BOUNDTYPE_UPPER: '<=', 44 SCIP_BOUNDTYPE_LOWER: '>='} 45 46# Mapping the SCIP_RESULT enum to a python class 47# This is required to return SCIP_RESULT in the python code 48# In __init__.py this is imported as SCIP_RESULT to keep the 49# original naming scheme using capital letters 50cdef class PY_SCIP_RESULT: 51 DIDNOTRUN = SCIP_DIDNOTRUN 52 DELAYED = SCIP_DELAYED 53 DIDNOTFIND = SCIP_DIDNOTFIND 54 FEASIBLE = SCIP_FEASIBLE 55 INFEASIBLE = SCIP_INFEASIBLE 56 UNBOUNDED = SCIP_UNBOUNDED 57 CUTOFF = SCIP_CUTOFF 58 SEPARATED = SCIP_SEPARATED 59 NEWROUND = SCIP_NEWROUND 60 REDUCEDDOM = SCIP_REDUCEDDOM 61 CONSADDED = SCIP_CONSADDED 62 CONSCHANGED = SCIP_CONSCHANGED 63 BRANCHED = SCIP_BRANCHED 64 SOLVELP = SCIP_SOLVELP 65 FOUNDSOL = SCIP_FOUNDSOL 66 SUSPENDED = SCIP_SUSPENDED 67 SUCCESS = SCIP_SUCCESS 68 69cdef class PY_SCIP_PARAMSETTING: 70 DEFAULT = SCIP_PARAMSETTING_DEFAULT 71 AGGRESSIVE = SCIP_PARAMSETTING_AGGRESSIVE 72 FAST = SCIP_PARAMSETTING_FAST 73 OFF = SCIP_PARAMSETTING_OFF 74 75cdef class PY_SCIP_PARAMEMPHASIS: 76 DEFAULT = SCIP_PARAMEMPHASIS_DEFAULT 77 CPSOLVER = SCIP_PARAMEMPHASIS_CPSOLVER 78 EASYCIP = SCIP_PARAMEMPHASIS_EASYCIP 79 FEASIBILITY = SCIP_PARAMEMPHASIS_FEASIBILITY 80 HARDLP = SCIP_PARAMEMPHASIS_HARDLP 81 OPTIMALITY = SCIP_PARAMEMPHASIS_OPTIMALITY 82 COUNTER = SCIP_PARAMEMPHASIS_COUNTER 83 PHASEFEAS = SCIP_PARAMEMPHASIS_PHASEFEAS 84 PHASEIMPROVE = SCIP_PARAMEMPHASIS_PHASEIMPROVE 85 PHASEPROOF = SCIP_PARAMEMPHASIS_PHASEPROOF 86 87cdef class PY_SCIP_STATUS: 88 UNKNOWN = SCIP_STATUS_UNKNOWN 89 USERINTERRUPT = SCIP_STATUS_USERINTERRUPT 90 NODELIMIT = SCIP_STATUS_NODELIMIT 91 TOTALNODELIMIT = SCIP_STATUS_TOTALNODELIMIT 92 STALLNODELIMIT = SCIP_STATUS_STALLNODELIMIT 93 TIMELIMIT = SCIP_STATUS_TIMELIMIT 94 MEMLIMIT = SCIP_STATUS_MEMLIMIT 95 GAPLIMIT = SCIP_STATUS_GAPLIMIT 96 SOLLIMIT = SCIP_STATUS_SOLLIMIT 97 BESTSOLLIMIT = SCIP_STATUS_BESTSOLLIMIT 98 RESTARTLIMIT = SCIP_STATUS_RESTARTLIMIT 99 OPTIMAL = SCIP_STATUS_OPTIMAL 100 INFEASIBLE = SCIP_STATUS_INFEASIBLE 101 UNBOUNDED = SCIP_STATUS_UNBOUNDED 102 INFORUNBD = SCIP_STATUS_INFORUNBD 103 104cdef class PY_SCIP_STAGE: 105 INIT = SCIP_STAGE_INIT 106 PROBLEM = SCIP_STAGE_PROBLEM 107 TRANSFORMING = SCIP_STAGE_TRANSFORMING 108 TRANSFORMED = SCIP_STAGE_TRANSFORMED 109 INITPRESOLVE = SCIP_STAGE_INITPRESOLVE 110 PRESOLVING = SCIP_STAGE_PRESOLVING 111 EXITPRESOLVE = SCIP_STAGE_EXITPRESOLVE 112 PRESOLVED = SCIP_STAGE_PRESOLVED 113 INITSOLVE = SCIP_STAGE_INITSOLVE 114 SOLVING = SCIP_STAGE_SOLVING 115 SOLVED = SCIP_STAGE_SOLVED 116 EXITSOLVE = SCIP_STAGE_EXITSOLVE 117 FREETRANS = SCIP_STAGE_FREETRANS 118 FREE = SCIP_STAGE_FREE 119 120cdef class PY_SCIP_NODETYPE: 121 FOCUSNODE = SCIP_NODETYPE_FOCUSNODE 122 PROBINGNODE = SCIP_NODETYPE_PROBINGNODE 123 SIBLING = SCIP_NODETYPE_SIBLING 124 CHILD = SCIP_NODETYPE_CHILD 125 LEAF = SCIP_NODETYPE_LEAF 126 DEADEND = SCIP_NODETYPE_DEADEND 127 JUNCTION = SCIP_NODETYPE_JUNCTION 128 PSEUDOFORK = SCIP_NODETYPE_PSEUDOFORK 129 FORK = SCIP_NODETYPE_FORK 130 SUBROOT = SCIP_NODETYPE_SUBROOT 131 REFOCUSNODE = SCIP_NODETYPE_REFOCUSNODE 132 133 134cdef class PY_SCIP_PROPTIMING: 135 BEFORELP = SCIP_PROPTIMING_BEFORELP 136 DURINGLPLOOP = SCIP_PROPTIMING_DURINGLPLOOP 137 AFTERLPLOOP = SCIP_PROPTIMING_AFTERLPLOOP 138 AFTERLPNODE = SCIP_PROPTIMING_AFTERLPNODE 139 140cdef class PY_SCIP_PRESOLTIMING: 141 NONE = SCIP_PRESOLTIMING_NONE 142 FAST = SCIP_PRESOLTIMING_FAST 143 MEDIUM = SCIP_PRESOLTIMING_MEDIUM 144 EXHAUSTIVE = SCIP_PRESOLTIMING_EXHAUSTIVE 145 146cdef class PY_SCIP_HEURTIMING: 147 BEFORENODE = SCIP_HEURTIMING_BEFORENODE 148 DURINGLPLOOP = SCIP_HEURTIMING_DURINGLPLOOP 149 AFTERLPLOOP = SCIP_HEURTIMING_AFTERLPLOOP 150 AFTERLPNODE = SCIP_HEURTIMING_AFTERLPNODE 151 AFTERPSEUDONODE = SCIP_HEURTIMING_AFTERPSEUDONODE 152 AFTERLPPLUNGE = SCIP_HEURTIMING_AFTERLPPLUNGE 153 AFTERPSEUDOPLUNGE = SCIP_HEURTIMING_AFTERPSEUDOPLUNGE 154 DURINGPRICINGLOOP = SCIP_HEURTIMING_DURINGPRICINGLOOP 155 BEFOREPRESOL = SCIP_HEURTIMING_BEFOREPRESOL 156 DURINGPRESOLLOOP = SCIP_HEURTIMING_DURINGPRESOLLOOP 157 AFTERPROPLOOP = SCIP_HEURTIMING_AFTERPROPLOOP 158 159cdef class PY_SCIP_EVENTTYPE: 160 DISABLED = SCIP_EVENTTYPE_DISABLED 161 VARADDED = SCIP_EVENTTYPE_VARADDED 162 VARDELETED = SCIP_EVENTTYPE_VARDELETED 163 VARFIXED = SCIP_EVENTTYPE_VARFIXED 164 VARUNLOCKED = SCIP_EVENTTYPE_VARUNLOCKED 165 OBJCHANGED = SCIP_EVENTTYPE_OBJCHANGED 166 GLBCHANGED = SCIP_EVENTTYPE_GLBCHANGED 167 GUBCHANGED = SCIP_EVENTTYPE_GUBCHANGED 168 LBTIGHTENED = SCIP_EVENTTYPE_LBTIGHTENED 169 LBRELAXED = SCIP_EVENTTYPE_LBRELAXED 170 UBTIGHTENED = SCIP_EVENTTYPE_UBTIGHTENED 171 UBRELAXED = SCIP_EVENTTYPE_UBRELAXED 172 GHOLEADDED = SCIP_EVENTTYPE_GHOLEADDED 173 GHOLEREMOVED = SCIP_EVENTTYPE_GHOLEREMOVED 174 LHOLEADDED = SCIP_EVENTTYPE_LHOLEADDED 175 LHOLEREMOVED = SCIP_EVENTTYPE_LHOLEREMOVED 176 IMPLADDED = SCIP_EVENTTYPE_IMPLADDED 177 PRESOLVEROUND = SCIP_EVENTTYPE_PRESOLVEROUND 178 NODEFOCUSED = SCIP_EVENTTYPE_NODEFOCUSED 179 NODEFEASIBLE = SCIP_EVENTTYPE_NODEFEASIBLE 180 NODEINFEASIBLE = SCIP_EVENTTYPE_NODEINFEASIBLE 181 NODEBRANCHED = SCIP_EVENTTYPE_NODEBRANCHED 182 FIRSTLPSOLVED = SCIP_EVENTTYPE_FIRSTLPSOLVED 183 LPSOLVED = SCIP_EVENTTYPE_LPSOLVED 184 LPEVENT = SCIP_EVENTTYPE_LPEVENT 185 POORSOLFOUND = SCIP_EVENTTYPE_POORSOLFOUND 186 BESTSOLFOUND = SCIP_EVENTTYPE_BESTSOLFOUND 187 ROWADDEDSEPA = SCIP_EVENTTYPE_ROWADDEDSEPA 188 ROWDELETEDSEPA = SCIP_EVENTTYPE_ROWDELETEDSEPA 189 ROWADDEDLP = SCIP_EVENTTYPE_ROWADDEDLP 190 ROWDELETEDLP = SCIP_EVENTTYPE_ROWDELETEDLP 191 ROWCOEFCHANGED = SCIP_EVENTTYPE_ROWCOEFCHANGED 192 ROWCONSTCHANGED = SCIP_EVENTTYPE_ROWCONSTCHANGED 193 ROWSIDECHANGED = SCIP_EVENTTYPE_ROWSIDECHANGED 194 SYNC = SCIP_EVENTTYPE_SYNC 195 NODESOLVED = SCIP_EVENTTYPE_NODEFEASIBLE | SCIP_EVENTTYPE_NODEINFEASIBLE | SCIP_EVENTTYPE_NODEBRANCHED 196 197cdef class PY_SCIP_LPSOLSTAT: 198 NOTSOLVED = SCIP_LPSOLSTAT_NOTSOLVED 199 OPTIMAL = SCIP_LPSOLSTAT_OPTIMAL 200 INFEASIBLE = SCIP_LPSOLSTAT_INFEASIBLE 201 UNBOUNDEDRAY = SCIP_LPSOLSTAT_UNBOUNDEDRAY 202 OBJLIMIT = SCIP_LPSOLSTAT_OBJLIMIT 203 ITERLIMIT = SCIP_LPSOLSTAT_ITERLIMIT 204 TIMELIMIT = SCIP_LPSOLSTAT_TIMELIMIT 205 ERROR = SCIP_LPSOLSTAT_ERROR 206 207cdef class PY_SCIP_BRANCHDIR: 208 DOWNWARDS = SCIP_BRANCHDIR_DOWNWARDS 209 UPWARDS = SCIP_BRANCHDIR_UPWARDS 210 FIXED = SCIP_BRANCHDIR_FIXED 211 AUTO = SCIP_BRANCHDIR_AUTO 212 213cdef class PY_SCIP_BENDERSENFOTYPE: 214 LP = SCIP_BENDERSENFOTYPE_LP 215 RELAX = SCIP_BENDERSENFOTYPE_RELAX 216 PSEUDO = SCIP_BENDERSENFOTYPE_PSEUDO 217 CHECK = SCIP_BENDERSENFOTYPE_CHECK 218 219cdef class PY_SCIP_ROWORIGINTYPE: 220 UNSPEC = SCIP_ROWORIGINTYPE_UNSPEC 221 CONS = SCIP_ROWORIGINTYPE_CONS 222 SEPA = SCIP_ROWORIGINTYPE_SEPA 223 REOPT = SCIP_ROWORIGINTYPE_REOPT 224 225def PY_SCIP_CALL(SCIP_RETCODE rc): 226 if rc == SCIP_OKAY: 227 pass 228 elif rc == SCIP_ERROR: 229 raise Exception('SCIP: unspecified error!') 230 elif rc == SCIP_NOMEMORY: 231 raise MemoryError('SCIP: insufficient memory error!') 232 elif rc == SCIP_READERROR: 233 raise IOError('SCIP: read error!') 234 elif rc == SCIP_WRITEERROR: 235 raise IOError('SCIP: write error!') 236 elif rc == SCIP_NOFILE: 237 raise IOError('SCIP: file not found error!') 238 elif rc == SCIP_FILECREATEERROR: 239 raise IOError('SCIP: cannot create file!') 240 elif rc == SCIP_LPERROR: 241 raise Exception('SCIP: error in LP solver!') 242 elif rc == SCIP_NOPROBLEM: 243 raise Exception('SCIP: no problem exists!') 244 elif rc == SCIP_INVALIDCALL: 245 raise Exception('SCIP: method cannot be called at this time' 246 + ' in solution process!') 247 elif rc == SCIP_INVALIDDATA: 248 raise Exception('SCIP: error in input data!') 249 elif rc == SCIP_INVALIDRESULT: 250 raise Exception('SCIP: method returned an invalid result code!') 251 elif rc == SCIP_PLUGINNOTFOUND: 252 raise Exception('SCIP: a required plugin was not found !') 253 elif rc == SCIP_PARAMETERUNKNOWN: 254 raise KeyError('SCIP: the parameter with the given name was not found!') 255 elif rc == SCIP_PARAMETERWRONGTYPE: 256 raise LookupError('SCIP: the parameter is not of the expected type!') 257 elif rc == SCIP_PARAMETERWRONGVAL: 258 raise ValueError('SCIP: the value is invalid for the given parameter!') 259 elif rc == SCIP_KEYALREADYEXISTING: 260 raise KeyError('SCIP: the given key is already existing in table!') 261 elif rc == SCIP_MAXDEPTHLEVEL: 262 raise Exception('SCIP: maximal branching depth level exceeded!') 263 else: 264 raise Exception('SCIP: unknown return code!') 265 266cdef class Event: 267 """Base class holding a pointer to corresponding SCIP_EVENT""" 268 269 @staticmethod 270 cdef create(SCIP_EVENT* scip_event): 271 if scip_event == NULL: 272 raise Warning("cannot create Event with SCIP_EVENT* == NULL") 273 event = Event() 274 event.event = scip_event 275 return event 276 277 def getType(self): 278 """gets type of event""" 279 return SCIPeventGetType(self.event) 280 281 def __repr__(self): 282 return self.getType() 283 284 def getNewBound(self): 285 """gets new bound for a bound change event""" 286 return SCIPeventGetNewbound(self.event) 287 288 def getOldBound(self): 289 """gets old bound for a bound change event""" 290 return SCIPeventGetOldbound(self.event) 291 292 def getVar(self): 293 """gets variable for a variable event (var added, var deleted, var fixed, objective value or domain change, domain hole added or removed)""" 294 cdef SCIP_VAR* var = SCIPeventGetVar(self.event) 295 return Variable.create(var) 296 297 def getNode(self): 298 """gets node for a node or LP event""" 299 cdef SCIP_NODE* node = SCIPeventGetNode(self.event) 300 return Node.create(node) 301 302 def getRow(self): 303 """gets row for a row event""" 304 cdef SCIP_ROW* row = SCIPeventGetRow(self.event) 305 return Row.create(row) 306 307 def __hash__(self): 308 return hash(<size_t>self.event) 309 310 def __eq__(self, other): 311 return (self.__class__ == other.__class__ 312 and self.event == (<Event>other).event) 313 314cdef class Column: 315 """Base class holding a pointer to corresponding SCIP_COL""" 316 317 @staticmethod 318 cdef create(SCIP_COL* scipcol): 319 if scipcol == NULL: 320 raise Warning("cannot create Column with SCIP_COL* == NULL") 321 col = Column() 322 col.scip_col = scipcol 323 return col 324 325 def getLPPos(self): 326 """gets position of column in current LP, or -1 if it is not in LP""" 327 return SCIPcolGetLPPos(self.scip_col) 328 329 def getBasisStatus(self): 330 """gets the basis status of a column in the LP solution, Note: returns basis status `zero` for columns not in the current SCIP LP""" 331 cdef SCIP_BASESTAT stat = SCIPcolGetBasisStatus(self.scip_col) 332 if stat == SCIP_BASESTAT_LOWER: 333 return "lower" 334 elif stat == SCIP_BASESTAT_BASIC: 335 return "basic" 336 elif stat == SCIP_BASESTAT_UPPER: 337 return "upper" 338 elif stat == SCIP_BASESTAT_ZERO: 339 return "zero" 340 else: 341 raise Exception('SCIP returned unknown base status!') 342 343 def isIntegral(self): 344 """returns whether the associated variable is of integral type (binary, integer, implicit integer)""" 345 return SCIPcolIsIntegral(self.scip_col) 346 347 def getVar(self): 348 """gets variable this column represents""" 349 cdef SCIP_VAR* var = SCIPcolGetVar(self.scip_col) 350 return Variable.create(var) 351 352 def getPrimsol(self): 353 """gets the primal LP solution of a column""" 354 return SCIPcolGetPrimsol(self.scip_col) 355 356 def getLb(self): 357 """gets lower bound of column""" 358 return SCIPcolGetLb(self.scip_col) 359 360 def getUb(self): 361 """gets upper bound of column""" 362 return SCIPcolGetUb(self.scip_col) 363 364 def __hash__(self): 365 return hash(<size_t>self.scip_col) 366 367 def __eq__(self, other): 368 return (self.__class__ == other.__class__ 369 and self.scip_col == (<Column>other).scip_col) 370 371cdef class Row: 372 """Base class holding a pointer to corresponding SCIP_ROW""" 373 374 @staticmethod 375 cdef create(SCIP_ROW* sciprow): 376 if sciprow == NULL: 377 raise Warning("cannot create Row with SCIP_ROW* == NULL") 378 row = Row() 379 row.scip_row = sciprow 380 return row 381 382 property name: 383 def __get__(self): 384 cname = bytes( SCIProwGetName(self.scip_row) ) 385 return cname.decode('utf-8') 386 387 def getLhs(self): 388 """returns the left hand side of row""" 389 return SCIProwGetLhs(self.scip_row) 390 391 def getRhs(self): 392 """returns the right hand side of row""" 393 return SCIProwGetRhs(self.scip_row) 394 395 def getConstant(self): 396 """gets constant shift of row""" 397 return SCIProwGetConstant(self.scip_row) 398 399 def getLPPos(self): 400 """gets position of row in current LP, or -1 if it is not in LP""" 401 return SCIProwGetLPPos(self.scip_row) 402 403 def getBasisStatus(self): 404 """gets the basis status of a row in the LP solution, Note: returns basis status `basic` for rows not in the current SCIP LP""" 405 cdef SCIP_BASESTAT stat = SCIProwGetBasisStatus(self.scip_row) 406 if stat == SCIP_BASESTAT_LOWER: 407 return "lower" 408 elif stat == SCIP_BASESTAT_BASIC: 409 return "basic" 410 elif stat == SCIP_BASESTAT_UPPER: 411 return "upper" 412 elif stat == SCIP_BASESTAT_ZERO: 413 # this shouldn't happen! 414 raise Exception('SCIP returned base status zero for a row!') 415 else: 416 raise Exception('SCIP returned unknown base status!') 417 418 def isIntegral(self): 419 """returns TRUE iff the activity of the row (without the row's constant) is always integral in a feasible solution """ 420 return SCIProwIsIntegral(self.scip_row) 421 422 def isModifiable(self): 423 """returns TRUE iff row is modifiable during node processing (subject to column generation) """ 424 return SCIProwIsModifiable(self.scip_row) 425 426 def isRemovable(self): 427 """returns TRUE iff row is removable from the LP (due to aging or cleanup)""" 428 return SCIProwIsRemovable(self.scip_row) 429 430 def getOrigintype(self): 431 """returns type of origin that created the row""" 432 return SCIProwGetOrigintype(self.scip_row) 433 434 def getNNonz(self): 435 """get number of nonzero entries in row vector""" 436 return SCIProwGetNNonz(self.scip_row) 437 438 def getNLPNonz(self): 439 """get number of nonzero entries in row vector that correspond to columns currently in the SCIP LP""" 440 return SCIProwGetNLPNonz(self.scip_row) 441 442 def getCols(self): 443 """gets list with columns of nonzero entries""" 444 cdef SCIP_COL** cols = SCIProwGetCols(self.scip_row) 445 return [Column.create(cols[i]) for i in range(self.getNNonz())] 446 447 def getVals(self): 448 """gets list with coefficients of nonzero entries""" 449 cdef SCIP_Real* vals = SCIProwGetVals(self.scip_row) 450 return [vals[i] for i in range(self.getNNonz())] 451 452 def __hash__(self): 453 return hash(<size_t>self.scip_row) 454 455 def __eq__(self, other): 456 return (self.__class__ == other.__class__ 457 and self.scip_row == (<Row>other).scip_row) 458 459cdef class NLRow: 460 """Base class holding a pointer to corresponding SCIP_NLROW""" 461 462 @staticmethod 463 cdef create(SCIP_NLROW* scipnlrow): 464 if scipnlrow == NULL: 465 raise Warning("cannot create NLRow with SCIP_NLROW* == NULL") 466 nlrow = NLRow() 467 nlrow.scip_nlrow = scipnlrow 468 return nlrow 469 470 property name: 471 def __get__(self): 472 cname = bytes( SCIPnlrowGetName(self.scip_nlrow) ) 473 return cname.decode('utf-8') 474 475 def getConstant(self): 476 """returns the constant of a nonlinear row""" 477 return SCIPnlrowGetConstant(self.scip_nlrow) 478 479 def getLinearTerms(self): 480 """returns a list of tuples (var, coef) representing the linear part of a nonlinear row""" 481 cdef SCIP_VAR** linvars = SCIPnlrowGetLinearVars(self.scip_nlrow) 482 cdef SCIP_Real* lincoefs = SCIPnlrowGetLinearCoefs(self.scip_nlrow) 483 cdef int nlinvars = SCIPnlrowGetNLinearVars(self.scip_nlrow) 484 return [(Variable.create(linvars[i]), lincoefs[i]) for i in range(nlinvars)] 485 486 def getQuadraticTerms(self): 487 """returns a list of tuples (var1, var2, coef) representing the quadratic part of a nonlinear row""" 488 cdef int nquadvars 489 cdef SCIP_VAR** quadvars 490 cdef int nquadelems 491 cdef SCIP_QUADELEM* quadelems 492 493 SCIPnlrowGetQuadData(self.scip_nlrow, &nquadvars, &quadvars, &nquadelems, &quadelems) 494 495 quadterms = [] 496 for i in range(nquadelems): 497 x = Variable.create(quadvars[quadelems[i].idx1]) 498 y = Variable.create(quadvars[quadelems[i].idx2]) 499 coef = quadelems[i].coef 500 quadterms.append((x,y,coef)) 501 return quadterms 502 503 def hasExprtree(self): 504 """returns whether there exists an expression tree in a nonlinear row""" 505 cdef SCIP_EXPRTREE* exprtree 506 507 exprtree = SCIPnlrowGetExprtree(self.scip_nlrow) 508 return exprtree != NULL 509 510 def getLhs(self): 511 """returns the left hand side of a nonlinear row""" 512 return SCIPnlrowGetLhs(self.scip_nlrow) 513 514 def getRhs(self): 515 """returns the right hand side of a nonlinear row""" 516 return SCIPnlrowGetRhs(self.scip_nlrow) 517 518 def getDualsol(self): 519 """gets the dual NLP solution of a nonlinear row""" 520 return SCIPnlrowGetDualsol(self.scip_nlrow) 521 522 def __hash__(self): 523 return hash(<size_t>self.scip_nlrow) 524 525 def __eq__(self, other): 526 return (self.__class__ == other.__class__ 527 and self.scip_nlrow == (<NLRow>other).scip_nlrow) 528 529cdef class Solution: 530 """Base class holding a pointer to corresponding SCIP_SOL""" 531 532 @staticmethod 533 cdef create(SCIP* scip, SCIP_SOL* scip_sol): 534 if scip == NULL: 535 raise Warning("cannot create Solution with SCIP* == NULL") 536 sol = Solution() 537 sol.sol = scip_sol 538 sol.scip = scip 539 return sol 540 541 def __getitem__(self, Variable var): 542 return SCIPgetSolVal(self.scip, self.sol, var.scip_var) 543 544 def __setitem__(self, Variable var, value): 545 PY_SCIP_CALL(SCIPsetSolVal(self.scip, self.sol, var.scip_var, value)) 546 547 def __repr__(self): 548 cdef SCIP_VAR* scip_var 549 550 vals = {} 551 for i in range(SCIPgetNVars(self.scip)): 552 scip_var = SCIPgetVars(self.scip)[i] 553 554 # extract name 555 cname = bytes(SCIPvarGetName(scip_var)) 556 name = cname.decode('utf-8') 557 558 vals[name] = SCIPgetSolVal(self.scip, self.sol, scip_var) 559 return str(vals) 560 561cdef class BoundChange: 562 """Bound change.""" 563 564 @staticmethod 565 cdef create(SCIP_BOUNDCHG* scip_boundchg): 566 if scip_boundchg == NULL: 567 raise Warning("cannot create BoundChange with SCIP_BOUNDCHG* == NULL") 568 boundchg = BoundChange() 569 boundchg.scip_boundchg = scip_boundchg 570 return boundchg 571 572 def getNewBound(self): 573 """Returns the new value of the bound in the bound change.""" 574 return SCIPboundchgGetNewbound(self.scip_boundchg) 575 576 def getVar(self): 577 """Returns the variable of the bound change.""" 578 return Variable.create(SCIPboundchgGetVar(self.scip_boundchg)) 579 580 def getBoundchgtype(self): 581 """Returns the bound change type of the bound change.""" 582 return SCIPboundchgGetBoundchgtype(self.scip_boundchg) 583 584 def getBoundtype(self): 585 """Returns the bound type of the bound change.""" 586 return SCIPboundchgGetBoundtype(self.scip_boundchg) 587 588 def isRedundant(self): 589 """Returns whether the bound change is redundant due to a more global bound that is at least as strong.""" 590 return SCIPboundchgIsRedundant(self.scip_boundchg) 591 592 def __repr__(self): 593 return "{} {} {}".format(self.getVar(), 594 _SCIP_BOUNDTYPE_TO_STRING[self.getBoundtype()], 595 self.getNewBound()) 596 597cdef class DomainChanges: 598 """Set of domain changes.""" 599 600 @staticmethod 601 cdef create(SCIP_DOMCHG* scip_domchg): 602 if scip_domchg == NULL: 603 raise Warning("cannot create DomainChanges with SCIP_DOMCHG* == NULL") 604 domchg = DomainChanges() 605 domchg.scip_domchg = scip_domchg 606 return domchg 607 608 def getBoundchgs(self): 609 """Returns the bound changes in the domain change.""" 610 nboundchgs = SCIPdomchgGetNBoundchgs(self.scip_domchg) 611 return [BoundChange.create(SCIPdomchgGetBoundchg(self.scip_domchg, i)) 612 for i in range(nboundchgs)] 613 614cdef class Node: 615 """Base class holding a pointer to corresponding SCIP_NODE""" 616 617 @staticmethod 618 cdef create(SCIP_NODE* scipnode): 619 if scipnode == NULL: 620 return None 621 node = Node() 622 node.scip_node = scipnode 623 return node 624 625 def getParent(self): 626 """Retrieve parent node (or None if the node has no parent node).""" 627 return Node.create(SCIPnodeGetParent(self.scip_node)) 628 629 def getNumber(self): 630 """Retrieve number of node.""" 631 return SCIPnodeGetNumber(self.scip_node) 632 633 def getDepth(self): 634 """Retrieve depth of node.""" 635 return SCIPnodeGetDepth(self.scip_node) 636 637 def getType(self): 638 """Retrieve type of node.""" 639 return SCIPnodeGetType(self.scip_node) 640 641 def getLowerbound(self): 642 """Retrieve lower bound of node.""" 643 return SCIPnodeGetLowerbound(self.scip_node) 644 645 def getEstimate(self): 646 """Retrieve the estimated value of the best feasible solution in subtree of the node""" 647 return SCIPnodeGetEstimate(self.scip_node) 648 649 def getAddedConss(self): 650 """Retrieve all constraints added at this node.""" 651 cdef int addedconsssize = SCIPnodeGetNAddedConss(self.scip_node) 652 if addedconsssize == 0: 653 return [] 654 cdef SCIP_CONS** addedconss = <SCIP_CONS**> malloc(addedconsssize * sizeof(SCIP_CONS*)) 655 cdef int nconss 656 SCIPnodeGetAddedConss(self.scip_node, addedconss, &nconss, addedconsssize) 657 assert nconss == addedconsssize 658 constraints = [Constraint.create(addedconss[i]) for i in range(nconss)] 659 free(addedconss) 660 return constraints 661 662 def getNAddedConss(self): 663 """Retrieve number of added constraints at this node""" 664 return SCIPnodeGetNAddedConss(self.scip_node) 665 666 def isActive(self): 667 """Is the node in the path to the current node?""" 668 return SCIPnodeIsActive(self.scip_node) 669 670 def isPropagatedAgain(self): 671 """Is the node marked to be propagated again?""" 672 return SCIPnodeIsPropagatedAgain(self.scip_node) 673 674 def getNParentBranchings(self): 675 """Retrieve the number of variable branchings that were performed in the parent node to create this node.""" 676 cdef SCIP_VAR* dummy_branchvars 677 cdef SCIP_Real dummy_branchbounds 678 cdef SCIP_BOUNDTYPE dummy_boundtypes 679 cdef int nbranchvars 680 # This is a hack: the SCIP interface has no function to directly get the 681 # number of parent branchings, i.e., SCIPnodeGetNParentBranchings() does 682 # not exist. 683 SCIPnodeGetParentBranchings(self.scip_node, &dummy_branchvars, 684 &dummy_branchbounds, &dummy_boundtypes, 685 &nbranchvars, 0) 686 return nbranchvars 687 688 def getParentBranchings(self): 689 """Retrieve the set of variable branchings that were performed in the parent node to create this node.""" 690 cdef int nbranchvars = self.getNParentBranchings() 691 if nbranchvars == 0: 692 return None 693 694 cdef SCIP_VAR** branchvars = <SCIP_VAR**> malloc(nbranchvars * sizeof(SCIP_VAR*)) 695 cdef SCIP_Real* branchbounds = <SCIP_Real*> malloc(nbranchvars * sizeof(SCIP_Real)) 696 cdef SCIP_BOUNDTYPE* boundtypes = <SCIP_BOUNDTYPE*> malloc(nbranchvars * sizeof(SCIP_BOUNDTYPE)) 697 698 SCIPnodeGetParentBranchings(self.scip_node, branchvars, branchbounds, 699 boundtypes, &nbranchvars, nbranchvars) 700 701 py_variables = [Variable.create(branchvars[i]) for i in range(nbranchvars)] 702 py_branchbounds = [branchbounds[i] for i in range(nbranchvars)] 703 py_boundtypes = [boundtypes[i] for i in range(nbranchvars)] 704 705 free(boundtypes) 706 free(branchbounds) 707 free(branchvars) 708 return py_variables, py_branchbounds, py_boundtypes 709 710 def getNDomchg(self): 711 """Retrieve the number of bound changes due to branching, constraint propagation, and propagation.""" 712 cdef int nbranchings 713 cdef int nconsprop 714 cdef int nprop 715 SCIPnodeGetNDomchg(self.scip_node, &nbranchings, &nconsprop, &nprop) 716 return nbranchings, nconsprop, nprop 717 718 def getDomchg(self): 719 """Retrieve domain changes for this node.""" 720 cdef SCIP_DOMCHG* domchg = SCIPnodeGetDomchg(self.scip_node) 721 if domchg == NULL: 722 return None 723 return DomainChanges.create(domchg) 724 725 def __hash__(self): 726 return hash(<size_t>self.scip_node) 727 728 def __eq__(self, other): 729 return (self.__class__ == other.__class__ 730 and self.scip_node == (<Node>other).scip_node) 731 732cdef class Variable(Expr): 733 """Is a linear expression and has SCIP_VAR*""" 734 735 @staticmethod 736 cdef create(SCIP_VAR* scipvar): 737 if scipvar == NULL: 738 raise Warning("cannot create Variable with SCIP_VAR* == NULL") 739 var = Variable() 740 var.scip_var = scipvar 741 Expr.__init__(var, {Term(var) : 1.0}) 742 return var 743 744 property name: 745 def __get__(self): 746 cname = bytes( SCIPvarGetName(self.scip_var) ) 747 return cname.decode('utf-8') 748 749 def ptr(self): 750 """ """ 751 return <size_t>(self.scip_var) 752 753 def __repr__(self): 754 return self.name 755 756 def vtype(self): 757 """Retrieve the variables type (BINARY, INTEGER, IMPLINT or CONTINUOUS)""" 758 vartype = SCIPvarGetType(self.scip_var) 759 if vartype == SCIP_VARTYPE_BINARY: 760 return "BINARY" 761 elif vartype == SCIP_VARTYPE_INTEGER: 762 return "INTEGER" 763 elif vartype == SCIP_VARTYPE_CONTINUOUS: 764 return "CONTINUOUS" 765 elif vartype == SCIP_VARTYPE_IMPLINT: 766 return "IMPLINT" 767 768 def isOriginal(self): 769 """Retrieve whether the variable belongs to the original problem""" 770 return SCIPvarIsOriginal(self.scip_var) 771 772 def isInLP(self): 773 """Retrieve whether the variable is a COLUMN variable that is member of the current LP""" 774 return SCIPvarIsInLP(self.scip_var) 775 776 777 def getIndex(self): 778 """Retrieve the unique index of the variable.""" 779 return SCIPvarGetIndex(self.scip_var) 780 781 def getCol(self): 782 """Retrieve column of COLUMN variable""" 783 cdef SCIP_COL* scip_col 784 scip_col = SCIPvarGetCol(self.scip_var) 785 return Column.create(scip_col) 786 787 def getLbOriginal(self): 788 """Retrieve original lower bound of variable""" 789 return SCIPvarGetLbOriginal(self.scip_var) 790 791 def getUbOriginal(self): 792 """Retrieve original upper bound of variable""" 793 return SCIPvarGetUbOriginal(self.scip_var) 794 795 def getLbGlobal(self): 796 """Retrieve global lower bound of variable""" 797 return SCIPvarGetLbGlobal(self.scip_var) 798 799 def getUbGlobal(self): 800 """Retrieve global upper bound of variable""" 801 return SCIPvarGetUbGlobal(self.scip_var) 802 803 def getLbLocal(self): 804 """Retrieve current lower bound of variable""" 805 return SCIPvarGetLbLocal(self.scip_var) 806 807 def getUbLocal(self): 808 """Retrieve current upper bound of variable""" 809 return SCIPvarGetUbLocal(self.scip_var) 810 811 def getObj(self): 812 """Retrieve current objective value of variable""" 813 return SCIPvarGetObj(self.scip_var) 814 815 def getLPSol(self): 816 """Retrieve the current LP solution value of variable""" 817 return SCIPvarGetLPSol(self.scip_var) 818 819cdef class Constraint: 820 """Base class holding a pointer to corresponding SCIP_CONS""" 821 822 @staticmethod 823 cdef create(SCIP_CONS* scipcons): 824 if scipcons == NULL: 825 raise Warning("cannot create Constraint with SCIP_CONS* == NULL") 826 cons = Constraint() 827 cons.scip_cons = scipcons 828 return cons 829 830 property name: 831 def __get__(self): 832 cname = bytes( SCIPconsGetName(self.scip_cons) ) 833 return cname.decode('utf-8') 834 835 def __repr__(self): 836 return self.name 837 838 def isOriginal(self): 839 """Retrieve whether the constraint belongs to the original problem""" 840 return SCIPconsIsOriginal(self.scip_cons) 841 842 def isInitial(self): 843 """Retrieve True if the relaxation of the constraint should be in the initial LP""" 844 return SCIPconsIsInitial(self.scip_cons) 845 846 def isSeparated(self): 847 """Retrieve True if constraint should be separated during LP processing""" 848 return SCIPconsIsSeparated(self.scip_cons) 849 850 def isEnforced(self): 851 """Retrieve True if constraint should be enforced during node processing""" 852 return SCIPconsIsEnforced(self.scip_cons) 853 854 def isChecked(self): 855 """Retrieve True if constraint should be checked for feasibility""" 856 return SCIPconsIsChecked(self.scip_cons) 857 858 def isPropagated(self): 859 """Retrieve True if constraint should be propagated during node processing""" 860 return SCIPconsIsPropagated(self.scip_cons) 861 862 def isLocal(self): 863 """Retrieve True if constraint is only locally valid or not added to any (sub)problem""" 864 return SCIPconsIsLocal(self.scip_cons) 865 866 def isModifiable(self): 867 """Retrieve True if constraint is modifiable (subject to column generation)""" 868 return SCIPconsIsModifiable(self.scip_cons) 869 870 def isDynamic(self): 871 """Retrieve True if constraint is subject to aging""" 872 return SCIPconsIsDynamic(self.scip_cons) 873 874 def isRemovable(self): 875 """Retrieve True if constraint's relaxation should be removed from the LP due to aging or cleanup""" 876 return SCIPconsIsRemovable(self.scip_cons) 877 878 def isStickingAtNode(self): 879 """Retrieve True if constraint is only locally valid or not added to any (sub)problem""" 880 return SCIPconsIsStickingAtNode(self.scip_cons) 881 882 def isActive(self): 883 """returns True iff constraint is active in the current node""" 884 return SCIPconsIsActive(self.scip_cons) 885 886 def isLinear(self): 887 """Retrieve True if constraint is linear""" 888 constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.scip_cons))).decode('UTF-8') 889 return constype == 'linear' 890 891 def isQuadratic(self): 892 """Retrieve True if constraint is quadratic""" 893 constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.scip_cons))).decode('UTF-8') 894 return constype == 'quadratic' 895 896 def __hash__(self): 897 return hash(<size_t>self.scip_cons) 898 899 def __eq__(self, other): 900 return (self.__class__ == other.__class__ 901 and self.scip_cons == (<Constraint>other).scip_cons) 902 903 904cdef void relayMessage(SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg): 905 sys.stdout.write(msg.decode('UTF-8')) 906 907cdef void relayErrorMessage(void *messagehdlr, FILE *file, const char *msg): 908 sys.stderr.write(msg.decode('UTF-8')) 909 910# - remove create(), includeDefaultPlugins(), createProbBasic() methods 911# - replace free() by "destructor" 912# - interface SCIPfreeProb() 913## 914#@anchor Model 915## 916cdef class Model: 917 """Main class holding a pointer to SCIP for managing most interactions""" 918 919 def __init__(self, problemName='model', defaultPlugins=True, Model sourceModel=None, origcopy=False, globalcopy=True, enablepricing=False, createscip=True, threadsafe=False): 920 """ 921 :param problemName: name of the problem (default 'model') 922 :param defaultPlugins: use default plugins? (default True) 923 :param sourceModel: create a copy of the given Model instance (default None) 924 :param origcopy: whether to call copy or copyOrig (default False) 925 :param globalcopy: whether to create a global or a local copy (default True) 926 :param enablepricing: whether to enable pricing in copy (default False) 927 :param createscip: initialize the Model object and creates a SCIP instance 928 :param threadsafe: False if data can be safely shared between the source and target problem 929 """ 930 if self.version() < MAJOR: 931 raise Exception("linked SCIP is not compatible to this version of PySCIPOpt - use at least version", MAJOR) 932 if self.version() < MAJOR + MINOR/10.0 + PATCH/100.0: 933 warnings.warn("linked SCIP {} is not recommended for this version of PySCIPOpt - use version {}.{}.{}".format(self.version(), MAJOR, MINOR, PATCH)) 934 935 self._freescip = True 936 self._modelvars = {} 937 938 if not createscip: 939 # if no SCIP instance should be created, then an empty Model object is created. 940 self._scip = NULL 941 self._bestSol = None 942 self._freescip = False 943 elif sourceModel is None: 944 PY_SCIP_CALL(SCIPcreate(&self._scip)) 945 self._bestSol = None 946 if defaultPlugins: 947 self.includeDefaultPlugins() 948 self.createProbBasic(problemName) 949 else: 950 PY_SCIP_CALL(SCIPcreate(&self._scip)) 951 self._bestSol = <Solution> sourceModel._bestSol 952 n = str_conversion(problemName) 953 if origcopy: 954 PY_SCIP_CALL(SCIPcopyOrig(sourceModel._scip, self._scip, NULL, NULL, n, enablepricing, threadsafe, True, self._valid)) 955 else: 956 PY_SCIP_CALL(SCIPcopy(sourceModel._scip, self._scip, NULL, NULL, n, globalcopy, enablepricing, threadsafe, True, self._valid)) 957 958 def __dealloc__(self): 959 # call C function directly, because we can no longer call this object's methods, according to 960 # http://docs.cython.org/src/reference/extension_types.html#finalization-dealloc 961 if self._scip is not NULL and self._freescip and PY_SCIP_CALL: 962 PY_SCIP_CALL( SCIPfree(&self._scip) ) 963 964 def __hash__(self): 965 return hash(<size_t>self._scip) 966 967 def __eq__(self, other): 968 return (self.__class__ == other.__class__ 969 and self._scip == (<Model>other)._scip) 970 971 @staticmethod 972 cdef create(SCIP* scip): 973 """Creates a model and appropriately assigns the scip and bestsol parameters 974 """ 975 if scip == NULL: 976 raise Warning("cannot create Model with SCIP* == NULL") 977 model = Model(createscip=False) 978 model._scip = scip 979 model._bestSol = Solution.create(scip, SCIPgetBestSol(scip)) 980 return model 981 982 @property 983 def _freescip(self): 984 """Return whether the underlying Scip pointer gets deallocted when the current 985 object is deleted. 986 """ 987 return self._freescip 988 989 @_freescip.setter 990 def _freescip(self, val): 991 """Set whether the underlying Scip pointer gets deallocted when the current 992 object is deleted. 993 """ 994 self._freescip = val 995 996 @cython.always_allow_keywords(True) 997 @staticmethod 998 def from_ptr(capsule, take_ownership): 999 """Create a Model from a given pointer. 1000 1001 :param cpasule: The PyCapsule containing the SCIP pointer under the name "scip". 1002 :param take_ownership: Whether the newly created Model assumes ownership of the 1003 underlying Scip pointer (see `_freescip`). 1004 """ 1005 if not PyCapsule_IsValid(capsule, "scip"): 1006 raise ValueError("The given capsule does not contain a valid scip pointer") 1007 model = Model.create(<SCIP*>PyCapsule_GetPointer(capsule, "scip")) 1008 model._freescip = take_ownership 1009 return model 1010 1011 @cython.always_allow_keywords(True) 1012 def to_ptr(self, give_ownership): 1013 """Return the underlying Scip pointer to the current Model. 1014 1015 :param give_ownership: Whether the current Model gives away ownership of the 1016 underlying Scip pointer (see `_freescip`). 1017 :return capsule: The underlying pointer to the current Model, wrapped in a 1018 PyCapsule under the name "scip". 1019 """ 1020 capsule = PyCapsule_New(<void*>self._scip, "scip", NULL) 1021 if give_ownership: 1022 self._freescip = False 1023 return capsule 1024 1025 def includeDefaultPlugins(self): 1026 """Includes all default plug-ins into SCIP""" 1027 PY_SCIP_CALL(SCIPincludeDefaultPlugins(self._scip)) 1028 1029 def createProbBasic(self, problemName='model'): 1030 """Create new problem instance with given name 1031 1032 :param problemName: name of model or problem (Default value = 'model') 1033 1034 """ 1035 n = str_conversion(problemName) 1036 PY_SCIP_CALL(SCIPcreateProbBasic(self._scip, n)) 1037 1038 def freeProb(self): 1039 """Frees problem and solution process data""" 1040 PY_SCIP_CALL(SCIPfreeProb(self._scip)) 1041 1042 def freeTransform(self): 1043 """Frees all solution process data including presolving and transformed problem, only original problem is kept""" 1044 PY_SCIP_CALL(SCIPfreeTransform(self._scip)) 1045 1046 def version(self): 1047 """Retrieve SCIP version""" 1048 return SCIPversion() 1049 1050 def printVersion(self): 1051 """Print version, copyright information and compile mode""" 1052 SCIPprintVersion(self._scip, NULL) 1053 1054 def getProbName(self): 1055 """Retrieve problem name""" 1056 return bytes(SCIPgetProbName(self._scip)).decode('UTF-8') 1057 1058 def getTotalTime(self): 1059 """Retrieve the current total SCIP time in seconds, i.e. the total time since the SCIP instance has been created""" 1060 return SCIPgetTotalTime(self._scip) 1061 1062 def getSolvingTime(self): 1063 """Retrieve the current solving time in seconds""" 1064 return SCIPgetSolvingTime(self._scip) 1065 1066 def getReadingTime(self): 1067 """Retrieve the current reading time in seconds""" 1068 return SCIPgetReadingTime(self._scip) 1069 1070 def getPresolvingTime(self): 1071 """Retrieve the curernt presolving time in seconds""" 1072 return SCIPgetPresolvingTime(self._scip) 1073 1074 def getNLPIterations(self): 1075 """Retrieve the total number of LP iterations so far.""" 1076 return SCIPgetNLPIterations(self._scip) 1077 1078 def getNNodes(self): 1079 """Retrieve the total number of processed nodes.""" 1080 return SCIPgetNNodes(self._scip) 1081 1082 def getNFeasibleLeaves(self): 1083 """Retrieve number of leaf nodes processed with feasible relaxation solution.""" 1084 return SCIPgetNFeasibleLeaves(self._scip) 1085 1086 def getNInfeasibleLeaves(self): 1087 """gets number of infeasible leaf nodes processed.""" 1088 return SCIPgetNInfeasibleLeaves(self._scip) 1089 1090 def getNLeaves(self): 1091 """gets number of leaves in the tree.""" 1092 return SCIPgetNLeaves(self._scip) 1093 1094 def getNChildren(self): 1095 """gets number of children of focus node.""" 1096 return SCIPgetNChildren(self._scip) 1097 1098 def getNSiblings(self): 1099 """gets number of siblings of focus node.""" 1100 return SCIPgetNSiblings(self._scip) 1101 1102 def getCurrentNode(self): 1103 """Retrieve current node.""" 1104 return Node.create(SCIPgetCurrentNode(self._scip)) 1105 1106 def getGap(self): 1107 """Retrieve the gap, i.e. |(primalbound - dualbound)/min(|primalbound|,|dualbound|)|.""" 1108 return SCIPgetGap(self._scip) 1109 1110 def getDepth(self): 1111 """Retrieve the depth of the current node""" 1112 return SCIPgetDepth(self._scip) 1113 1114 def infinity(self): 1115 """Retrieve SCIP's infinity value""" 1116 return SCIPinfinity(self._scip) 1117 1118 def epsilon(self): 1119 """Retrieve epsilon for e.g. equality checks""" 1120 return SCIPepsilon(self._scip) 1121 1122 def feastol(self): 1123 """Retrieve feasibility tolerance""" 1124 return SCIPfeastol(self._scip) 1125 1126 def feasFrac(self, value): 1127 """returns fractional part of value, i.e. x - floor(x) in feasible tolerance: x - floor(x+feastol)""" 1128 return SCIPfeasFrac(self._scip, value) 1129 1130 def frac(self, value): 1131 """returns fractional part of value, i.e. x - floor(x) in epsilon tolerance: x - floor(x+eps)""" 1132 return SCIPfrac(self._scip, value) 1133 1134 def isZero(self, value): 1135 """returns whether abs(value) < eps""" 1136 return SCIPisZero(self._scip, value) 1137 1138 def isFeasZero(self, value): 1139 """returns whether abs(value) < feastol""" 1140 return SCIPisFeasZero(self._scip, value) 1141 1142 def isInfinity(self, value): 1143 """returns whether value is SCIP's infinity""" 1144 return SCIPisInfinity(self._scip, value) 1145 1146 def isFeasNegative(self, value): 1147 """returns whether value < -feastol""" 1148 return SCIPisFeasNegative(self._scip, value) 1149 1150 def isFeasIntegral(self, value): 1151 """returns whether value is integral within the LP feasibility bounds""" 1152 return SCIPisFeasIntegral(self._scip, value) 1153 1154 def isEQ(self, val1, val2): 1155 """checks, if values are in range of epsilon""" 1156 return SCIPisEQ(self._scip, val1, val2) 1157 1158 def isFeasEQ(self, val1, val2): 1159 """checks, if relative difference of values is in range of feasibility tolerance""" 1160 return SCIPisFeasEQ(self._scip, val1, val2) 1161 1162 def isLE(self, val1, val2): 1163 """returns whether val1 <= val2 + eps""" 1164 return SCIPisLE(self._scip, val1, val2) 1165 1166 def isLT(self, val1, val2): 1167 """returns whether val1 < val2 - eps""" 1168 return SCIPisLT(self._scip, val1, val2) 1169 1170 def isGE(self, val1, val2): 1171 """returns whether val1 >= val2 - eps""" 1172 return SCIPisGE(self._scip, val1, val2) 1173 1174 def isGT(self, val1, val2): 1175 """returns whether val1 > val2 + eps""" 1176 return SCIPisGT(self._scip, val1, val2) 1177 1178 def getCondition(self, exact=False): 1179 """Get the current LP's condition number 1180 1181 :param exact: whether to get an estimate or the exact value (Default value = False) 1182 1183 """ 1184 cdef SCIP_LPI* lpi 1185 PY_SCIP_CALL(SCIPgetLPI(self._scip, &lpi)) 1186 cdef SCIP_Real quality = 0 1187 if exact: 1188 PY_SCIP_CALL(SCIPlpiGetRealSolQuality(lpi, SCIP_LPSOLQUALITY_EXACTCONDITION, &quality)) 1189 else: 1190 PY_SCIP_CALL(SCIPlpiGetRealSolQuality(lpi, SCIP_LPSOLQUALITY_ESTIMCONDITION, &quality)) 1191 1192 return quality 1193 1194 def enableReoptimization(self, enable=True): 1195 """include specific heuristics and branching rules for reoptimization""" 1196 PY_SCIP_CALL(SCIPenableReoptimization(self._scip, enable)) 1197 1198 def lpiGetIterations(self): 1199 """Get the iteration count of the last solved LP""" 1200 cdef SCIP_LPI* lpi 1201 PY_SCIP_CALL(SCIPgetLPI(self._scip, &lpi)) 1202 cdef int iters = 0 1203 PY_SCIP_CALL(SCIPlpiGetIterations(lpi, &iters)) 1204 return iters 1205 1206 # Objective function 1207 1208 def setMinimize(self): 1209 """Set the objective sense to minimization.""" 1210 PY_SCIP_CALL(SCIPsetObjsense(self._scip, SCIP_OBJSENSE_MINIMIZE)) 1211 1212 def setMaximize(self): 1213 """Set the objective sense to maximization.""" 1214 PY_SCIP_CALL(SCIPsetObjsense(self._scip, SCIP_OBJSENSE_MAXIMIZE)) 1215 1216 def setObjlimit(self, objlimit): 1217 """Set a limit on the objective function. 1218 Only solutions with objective value better than this limit are accepted. 1219 1220 :param objlimit: limit on the objective function 1221 1222 """ 1223 PY_SCIP_CALL(SCIPsetObjlimit(self._scip, objlimit)) 1224 1225 def getObjlimit(self): 1226 """returns current limit on objective function.""" 1227 return SCIPgetObjlimit(self._scip) 1228 1229 def setObjective(self, coeffs, sense = 'minimize', clear = 'true'): 1230 """Establish the objective function as a linear expression. 1231 1232 :param coeffs: the coefficients 1233 :param sense: the objective sense (Default value = 'minimize') 1234 :param clear: set all other variables objective coefficient to zero (Default value = 'true') 1235 1236 """ 1237 cdef SCIP_VAR** _vars 1238 cdef int _nvars 1239 1240 # turn the constant value into an Expr instance for further processing 1241 if not isinstance(coeffs, Expr): 1242 assert(_is_number(coeffs)), "given coefficients are neither Expr or number but %s" % coeffs.__class__.__name__ 1243 coeffs = Expr() + coeffs 1244 1245 if coeffs.degree() > 1: 1246 raise ValueError("Nonlinear objective functions are not supported!") 1247 if coeffs[CONST] != 0.0: 1248 self.addObjoffset(coeffs[CONST]) 1249 1250 if clear: 1251 # clear existing objective function 1252 _vars = SCIPgetOrigVars(self._scip) 1253 _nvars = SCIPgetNOrigVars(self._scip) 1254 for i in range(_nvars): 1255 PY_SCIP_CALL(SCIPchgVarObj(self._scip, _vars[i], 0.0)) 1256 1257 for term, coef in coeffs.terms.items(): 1258 # avoid CONST term of Expr 1259 if term != CONST: 1260 assert len(term) == 1 1261 var = <Variable>term[0] 1262 PY_SCIP_CALL(SCIPchgVarObj(self._scip, var.scip_var, coef)) 1263 1264 if sense == "minimize": 1265 self.setMinimize() 1266 elif sense == "maximize": 1267 self.setMaximize() 1268 else: 1269 raise Warning("unrecognized optimization sense: %s" % sense) 1270 1271 def getObjective(self): 1272 """Retrieve objective function as Expr""" 1273 variables = self.getVars() 1274 objective = Expr() 1275 for var in variables: 1276 coeff = var.getObj() 1277 if coeff != 0: 1278 objective += coeff * var 1279 objective.normalize() 1280 return objective 1281 1282 def addObjoffset(self, offset, solutions = False): 1283 """Add constant offset to objective 1284 1285 :param offset: offset to add 1286 :param solutions: add offset also to existing solutions (Default value = False) 1287 1288 """ 1289 if solutions: 1290 PY_SCIP_CALL(SCIPaddObjoffset(self._scip, offset)) 1291 else: 1292 PY_SCIP_CALL(SCIPaddOrigObjoffset(self._scip, offset)) 1293 1294 def getObjoffset(self, original = True): 1295 """Retrieve constant objective offset 1296 1297 :param original: offset of original or transformed problem (Default value = True) 1298 1299 """ 1300 if original: 1301 return SCIPgetOrigObjoffset(self._scip) 1302 else: 1303 return SCIPgetTransObjoffset(self._scip) 1304 1305 1306 def setObjIntegral(self): 1307 """informs SCIP that the objective value is always integral in every feasible solution 1308 Note: This function should be used to inform SCIP that the objective function is integral, helping to improve the 1309 performance. This is useful when using column generation. If no column generation (pricing) is used, SCIP 1310 automatically detects whether the objective function is integral or can be scaled to be integral. However, in 1311 any case, the user has to make sure that no variable is added during the solving process that destroys this 1312 property. 1313 """ 1314 PY_SCIP_CALL(SCIPsetObjIntegral(self._scip)) 1315 1316 def getLocalEstimate(self, original = False): 1317 """gets estimate of best primal solution w.r.t. original or transformed problem contained in current subtree 1318 1319 :param original: estimate of original or transformed problem (Default value = False) 1320 """ 1321 if original: 1322 return SCIPgetLocalOrigEstimate(self._scip) 1323 else: 1324 return SCIPgetLocalTransEstimate(self._scip) 1325 1326 # Setting parameters 1327 def setPresolve(self, setting): 1328 """Set presolving parameter settings. 1329 1330 :param setting: the parameter settings (SCIP_PARAMSETTING) 1331 1332 """ 1333 PY_SCIP_CALL(SCIPsetPresolving(self._scip, setting, True)) 1334 1335 def setProbName(self, name): 1336 """Set problem name""" 1337 n = str_conversion(name) 1338 PY_SCIP_CALL(SCIPsetProbName(self._scip, n)) 1339 1340 def setSeparating(self, setting): 1341 """Set separating parameter settings. 1342 1343 :param setting: the parameter settings (SCIP_PARAMSETTING) 1344 1345 """ 1346 PY_SCIP_CALL(SCIPsetSeparating(self._scip, setting, True)) 1347 1348 def setHeuristics(self, setting): 1349 """Set heuristics parameter settings. 1350 1351 :param setting: the parameter setting (SCIP_PARAMSETTING) 1352 1353 """ 1354 PY_SCIP_CALL(SCIPsetHeuristics(self._scip, setting, True)) 1355 1356 def disablePropagation(self, onlyroot=False): 1357 """Disables propagation in SCIP to avoid modifying the original problem during transformation. 1358 1359 :param onlyroot: use propagation when root processing is finished (Default value = False) 1360 1361 """ 1362 self.setIntParam("propagating/maxroundsroot", 0) 1363 if not onlyroot: 1364 self.setIntParam("propagating/maxrounds", 0) 1365 1366 def writeProblem(self, filename='model.cip', trans=False): 1367 """Write current model/problem to a file. 1368 1369 :param filename: the name of the file to be used (Default value = 'model.cip') 1370 :param trans: indicates whether the transformed problem is written to file (Default value = False) 1371 1372 """ 1373 fn = str_conversion(filename) 1374 fn, ext = splitext(fn) 1375 if len(ext) == 0: 1376 ext = str_conversion('.cip') 1377 fn = fn + ext 1378 ext = ext[1:] 1379 if trans: 1380 PY_SCIP_CALL(SCIPwriteTransProblem(self._scip, fn, ext, False)) 1381 else: 1382 PY_SCIP_CALL(SCIPwriteOrigProblem(self._scip, fn, ext, False)) 1383 print('wrote problem to file ' + str(fn)) 1384 1385 # Variable Functions 1386 1387 def addVar(self, name='', vtype='C', lb=0.0, ub=None, obj=0.0, pricedVar = False): 1388 """Create a new variable. Default variable is non-negative and continuous. 1389 1390 :param name: name of the variable, generic if empty (Default value = '') 1391 :param vtype: type of the variable: 'C' continuous, 'I' integer, 'B' binary, and 'M' implicit integer 1392 (see https://www.scipopt.org/doc/html/FAQ.php#implicitinteger) (Default value = 'C') 1393 :param lb: lower bound of the variable, use None for -infinity (Default value = 0.0) 1394 :param ub: upper bound of the variable, use None for +infinity (Default value = None) 1395 :param obj: objective value of variable (Default value = 0.0) 1396 :param pricedVar: is the variable a pricing candidate? (Default value = False) 1397 1398 """ 1399 cdef SCIP_VAR* scip_var 1400 1401 # replace empty name with generic one 1402 if name == '': 1403 name = 'x'+str(SCIPgetNVars(self._scip)+1) 1404 cname = str_conversion(name) 1405 1406 # replace None with corresponding infinity 1407 if lb is None: 1408 lb = -SCIPinfinity(self._scip) 1409 if ub is None: 1410 ub = SCIPinfinity(self._scip) 1411 1412 vtype = vtype.upper() 1413 if vtype in ['C', 'CONTINUOUS']: 1414 PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_CONTINUOUS)) 1415 elif vtype in ['B', 'BINARY']: 1416 if ub > 1.0: 1417 ub = 1.0 1418 if lb < 0.0: 1419 lb = 0.0 1420 PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_BINARY)) 1421 elif vtype in ['I', 'INTEGER']: 1422 PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_INTEGER)) 1423 elif vtype in ['M', 'IMPLINT']: 1424 PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_IMPLINT)) 1425 else: 1426 raise Warning("unrecognized variable type") 1427 1428 if pricedVar: 1429 PY_SCIP_CALL(SCIPaddPricedVar(self._scip, scip_var, 1.0)) 1430 else: 1431 PY_SCIP_CALL(SCIPaddVar(self._scip, scip_var)) 1432 1433 pyVar = Variable.create(scip_var) 1434 1435 # store variable in the model to avoid creating new python variable objects in getVars() 1436 assert not pyVar.ptr() in self._modelvars 1437 self._modelvars[pyVar.ptr()] = pyVar 1438 1439 #setting the variable data 1440 SCIPvarSetData(scip_var, <SCIP_VARDATA*>pyVar) 1441 PY_SCIP_CALL(SCIPreleaseVar(self._scip, &scip_var)) 1442 return pyVar 1443 1444 def getTransformedVar(self, Variable var): 1445 """Retrieve the transformed variable. 1446 1447 :param Variable var: original variable to get the transformed of 1448 1449 """ 1450 cdef SCIP_VAR* _tvar 1451 PY_SCIP_CALL(SCIPtransformVar(self._scip, var.scip_var, &_tvar)) 1452 return Variable.create(_tvar) 1453 1454 def addVarLocks(self, Variable var, nlocksdown, nlocksup): 1455 """adds given values to lock numbers of variable for rounding 1456 1457 :param Variable var: variable to adjust the locks for 1458 :param nlocksdown: new number of down locks 1459 :param nlocksup: new number of up locks 1460 1461 """ 1462 PY_SCIP_CALL(SCIPaddVarLocks(self._scip, var.scip_var, nlocksdown, nlocksup)) 1463 1464 def fixVar(self, Variable var, val): 1465 """Fixes the variable var to the value val if possible. 1466 1467 :param Variable var: variable to fix 1468 :param val: float, the fix value 1469 :return: tuple (infeasible, fixed) of booleans 1470 1471 """ 1472 cdef SCIP_Bool infeasible 1473 cdef SCIP_Bool fixed 1474 PY_SCIP_CALL(SCIPfixVar(self._scip, var.scip_var, val, &infeasible, &fixed)) 1475 return infeasible, fixed 1476 1477 def delVar(self, Variable var): 1478 """Delete a variable. 1479 1480 :param var: the variable which shall be deleted 1481 :return: bool, was deleting succesful 1482 1483 """ 1484 cdef SCIP_Bool deleted 1485 PY_SCIP_CALL(SCIPdelVar(self._scip, var.scip_var, &deleted)) 1486 return deleted 1487 1488 def tightenVarLb(self, Variable var, lb, force=False): 1489 """Tighten the lower bound in preprocessing or current node, if the bound is tighter. 1490 1491 :param var: SCIP variable 1492 :param lb: possible new lower bound 1493 :param force: force tightening even if below bound strengthening tolerance 1494 :return: tuple of bools, (infeasible, tightened) 1495 infeasible: whether new domain is empty 1496 tightened: whether the bound was tightened 1497 1498 """ 1499 cdef SCIP_Bool infeasible 1500 cdef SCIP_Bool tightened 1501 PY_SCIP_CALL(SCIPtightenVarLb(self._scip, var.scip_var, lb, force, &infeasible, &tightened)) 1502 return infeasible, tightened 1503 1504 1505 def tightenVarUb(self, Variable var, ub, force=False): 1506 """Tighten the upper bound in preprocessing or current node, if the bound is tighter. 1507 1508 :param var: SCIP variable 1509 :param ub: possible new upper bound 1510 :param force: force tightening even if below bound strengthening tolerance 1511 :return: tuple of bools, (infeasible, tightened) 1512 infeasible: whether new domain is empty 1513 tightened: whether the bound was tightened 1514 1515 """ 1516 cdef SCIP_Bool infeasible 1517 cdef SCIP_Bool tightened 1518 PY_SCIP_CALL(SCIPtightenVarUb(self._scip, var.scip_var, ub, force, &infeasible, &tightened)) 1519 return infeasible, tightened 1520 1521 1522 def tightenVarUbGlobal(self, Variable var, ub, force=False): 1523 """Tighten the global upper bound, if the bound is tighter. 1524 1525 :param var: SCIP variable 1526 :param ub: possible new upper bound 1527 :param force: force tightening even if below bound strengthening tolerance 1528 :return: tuple of bools, (infeasible, tightened) 1529 infeasible: whether new domain is empty 1530 tightened: whether the bound was tightened 1531 1532 """ 1533 cdef SCIP_Bool infeasible 1534 cdef SCIP_Bool tightened 1535 PY_SCIP_CALL(SCIPtightenVarUbGlobal(self._scip, var.scip_var, ub, force, &infeasible, &tightened)) 1536 return infeasible, tightened 1537 1538 def tightenVarLbGlobal(self, Variable var, lb, force=False): 1539 """Tighten the global upper bound, if the bound is tighter. 1540 1541 :param var: SCIP variable 1542 :param lb: possible new upper bound 1543 :param force: force tightening even if below bound strengthening tolerance 1544 :return: tuple of bools, (infeasible, tightened) 1545 infeasible: whether new domain is empty 1546 tightened: whether the bound was tightened 1547 1548 """ 1549 cdef SCIP_Bool infeasible 1550 cdef SCIP_Bool tightened 1551 PY_SCIP_CALL(SCIPtightenVarLbGlobal(self._scip, var.scip_var, lb, force, &infeasible, &tightened)) 1552 return infeasible, tightened 1553 1554 def chgVarLb(self, Variable var, lb): 1555 """Changes the lower bound of the specified variable. 1556 1557 :param Variable var: variable to change bound of 1558 :param lb: new lower bound (set to None for -infinity) 1559 1560 """ 1561 if lb is None: 1562 lb = -SCIPinfinity(self._scip) 1563 PY_SCIP_CALL(SCIPchgVarLb(self._scip, var.scip_var, lb)) 1564 1565 def chgVarUb(self, Variable var, ub): 1566 """Changes the upper bound of the specified variable. 1567 1568 :param Variable var: variable to change bound of 1569 :param ub: new upper bound (set to None for +infinity) 1570 1571 """ 1572 if ub is None: 1573 ub = SCIPinfinity(self._scip) 1574 PY_SCIP_CALL(SCIPchgVarUb(self._scip, var.scip_var, ub)) 1575 1576 1577 def chgVarLbGlobal(self, Variable var, lb): 1578 """Changes the global lower bound of the specified variable. 1579 1580 :param Variable var: variable to change bound of 1581 :param lb: new lower bound (set to None for -infinity) 1582 1583 """ 1584 if lb is None: 1585 lb = -SCIPinfinity(self._scip) 1586 PY_SCIP_CALL(SCIPchgVarLbGlobal(self._scip, var.scip_var, lb)) 1587 1588 def chgVarUbGlobal(self, Variable var, ub): 1589 """Changes the global upper bound of the specified variable. 1590 1591 :param Variable var: variable to change bound of 1592 :param ub: new upper bound (set to None for +infinity) 1593 1594 """ 1595 if ub is None: 1596 ub = SCIPinfinity(self._scip) 1597 PY_SCIP_CALL(SCIPchgVarUbGlobal(self._scip, var.scip_var, ub)) 1598 1599 def chgVarLbNode(self, Node node, Variable var, lb): 1600 """Changes the lower bound of the specified variable at the given node. 1601 1602 :param Variable var: variable to change bound of 1603 :param lb: new lower bound (set to None for -infinity) 1604 """ 1605 1606 if lb is None: 1607 lb = -SCIPinfinity(self._scip) 1608 PY_SCIP_CALL(SCIPchgVarLbNode(self._scip, node.scip_node, var.scip_var, lb)) 1609 1610 def chgVarUbNode(self, Node node, Variable var, ub): 1611 """Changes the upper bound of the specified variable at the given node. 1612 1613 :param Variable var: variable to change bound of 1614 :param ub: new upper bound (set to None for +infinity) 1615 1616 """ 1617 if ub is None: 1618 ub = SCIPinfinity(self._scip) 1619 PY_SCIP_CALL(SCIPchgVarUbNode(self._scip, node.scip_node, var.scip_var, ub)) 1620 1621 def chgVarType(self, Variable var, vtype): 1622 """Changes the type of a variable 1623 1624 :param Variable var: variable to change type of 1625 :param vtype: new variable type 1626 1627 """ 1628 cdef SCIP_Bool infeasible 1629 if vtype in ['C', 'CONTINUOUS']: 1630 PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_CONTINUOUS, &infeasible)) 1631 elif vtype in ['B', 'BINARY']: 1632 PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_BINARY, &infeasible)) 1633 elif vtype in ['I', 'INTEGER']: 1634 PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_INTEGER, &infeasible)) 1635 elif vtype in ['M', 'IMPLINT']: 1636 PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_IMPLINT, &infeasible)) 1637 else: 1638 raise Warning("unrecognized variable type") 1639 if infeasible: 1640 print('could not change variable type of variable %s' % var) 1641 1642 def getVars(self, transformed=False): 1643 """Retrieve all variables. 1644 1645 :param transformed: get transformed variables instead of original (Default value = False) 1646 1647 """ 1648 cdef SCIP_VAR** _vars 1649 cdef SCIP_VAR* _var 1650 cdef int _nvars 1651 vars = [] 1652 1653 if transformed: 1654 _vars = SCIPgetVars(self._scip) 1655 _nvars = SCIPgetNVars(self._scip) 1656 else: 1657 _vars = SCIPgetOrigVars(self._scip) 1658 _nvars = SCIPgetNOrigVars(self._scip) 1659 1660 for i in range(_nvars): 1661 ptr = <size_t>(_vars[i]) 1662 1663 # check whether the corresponding variable exists already 1664 if ptr in self._modelvars: 1665 vars.append(self._modelvars[ptr]) 1666 else: 1667 # create a new variable 1668 var = Variable.create(_vars[i]) 1669 assert var.ptr() == ptr 1670 self._modelvars[ptr] = var 1671 vars.append(var) 1672 1673 return vars 1674 1675 def getNVars(self): 1676 """Retrieve number of variables in the problems""" 1677 return SCIPgetNVars(self._scip) 1678 1679 def getNConss(self): 1680 """Retrieve the number of constraints.""" 1681 return SCIPgetNConss(self._scip) 1682 1683 def updateNodeLowerbound(self, Node node, lb): 1684 """if given value is larger than the node's lower bound (in transformed problem), 1685 sets the node's lower bound to the new value 1686 1687 :param node: Node, the node to update 1688 :param newbound: float, new bound (if greater) for the node 1689 1690 """ 1691 PY_SCIP_CALL(SCIPupdateNodeLowerbound(self._scip, node.scip_node, lb)) 1692 1693 # Node methods 1694 def getBestChild(self): 1695 """gets the best child of the focus node w.r.t. the node selection strategy.""" 1696 return Node.create(SCIPgetBestChild(self._scip)) 1697 1698 def getBestSibling(self): 1699 """gets the best sibling of the focus node w.r.t. the node selection strategy.""" 1700 return Node.create(SCIPgetBestSibling(self._scip)) 1701 1702 def getBestLeaf(self): 1703 """gets the best leaf from the node queue w.r.t. the node selection strategy.""" 1704 return Node.create(SCIPgetBestLeaf(self._scip)) 1705 1706 def getBestNode(self): 1707 """gets the best node from the tree (child, sibling, or leaf) w.r.t. the node selection strategy.""" 1708 return Node.create(SCIPgetBestNode(self._scip)) 1709 1710 def getBestboundNode(self): 1711 """gets the node with smallest lower bound from the tree (child, sibling, or leaf).""" 1712 return Node.create(SCIPgetBestboundNode(self._scip)) 1713 1714 def getOpenNodes(self): 1715 """access to all data of open nodes (leaves, children, and siblings) 1716 1717 :return: three lists containing open leaves, children, siblings 1718 """ 1719 cdef SCIP_NODE** _leaves 1720 cdef SCIP_NODE** _children 1721 cdef SCIP_NODE** _siblings 1722 cdef int _nleaves 1723 cdef int _nchildren 1724 cdef int _nsiblings 1725 1726 PY_SCIP_CALL(SCIPgetOpenNodesData(self._scip, &_leaves, &_children, &_siblings, &_nleaves, &_nchildren, &_nsiblings)) 1727 1728 leaves = [Node.create(_leaves[i]) for i in range(_nleaves)] 1729 children = [Node.create(_children[i]) for i in range(_nchildren)] 1730 siblings = [Node.create(_siblings[i]) for i in range(_nsiblings)] 1731 1732 return leaves, children, siblings 1733 1734 def repropagateNode(self, Node node): 1735 """marks the given node to be propagated again the next time a node of its subtree is processed""" 1736 PY_SCIP_CALL(SCIPrepropagateNode(self._scip, node.scip_node)) 1737 1738 1739 # LP Methods 1740 def getLPSolstat(self): 1741 """Gets solution status of current LP""" 1742 return SCIPgetLPSolstat(self._scip) 1743 1744 1745 def constructLP(self): 1746 """makes sure that the LP of the current node is loaded and 1747 may be accessed through the LP information methods 1748 1749 :return: bool cutoff, i.e. can the node be cut off? 1750 1751 """ 1752 cdef SCIP_Bool cutoff 1753 PY_SCIP_CALL(SCIPconstructLP(self._scip, &cutoff)) 1754 return cutoff 1755 1756 def getLPObjVal(self): 1757 """gets objective value of current LP (which is the sum of column and loose objective value)""" 1758 1759 return SCIPgetLPObjval(self._scip) 1760 1761 def getLPColsData(self): 1762 """Retrieve current LP columns""" 1763 cdef SCIP_COL** cols 1764 cdef int ncols 1765 1766 PY_SCIP_CALL(SCIPgetLPColsData(self._scip, &cols, &ncols)) 1767 return [Column.create(cols[i]) for i in range(ncols)] 1768 1769 def getLPRowsData(self): 1770 """Retrieve current LP rows""" 1771 cdef SCIP_ROW** rows 1772 cdef int nrows 1773 1774 PY_SCIP_CALL(SCIPgetLPRowsData(self._scip, &rows, &nrows)) 1775 return [Row.create(rows[i]) for i in range(nrows)] 1776 1777 def getNLPRows(self): 1778 """Retrieve the number of rows currently in the LP""" 1779 return SCIPgetNLPRows(self._scip) 1780 1781 def getNLPCols(self): 1782 """Retrieve the number of cols currently in the LP""" 1783 return SCIPgetNLPCols(self._scip) 1784 1785 def getLPBasisInd(self): 1786 """Gets all indices of basic columns and rows: index i >= 0 corresponds to column i, index i < 0 to row -i-1""" 1787 cdef int nrows = SCIPgetNLPRows(self._scip) 1788 cdef int* inds = <int *> malloc(nrows * sizeof(int)) 1789 1790 PY_SCIP_CALL(SCIPgetLPBasisInd(self._scip, inds)) 1791 result = [inds[i] for i in range(nrows)] 1792 free(inds) 1793 return result 1794 1795 def getLPBInvRow(self, row): 1796 """gets a row from the inverse basis matrix B^-1""" 1797 # TODO: sparsity information 1798 cdef int nrows = SCIPgetNLPRows(self._scip) 1799 cdef SCIP_Real* coefs = <SCIP_Real*> malloc(nrows * sizeof(SCIP_Real)) 1800 1801 PY_SCIP_CALL(SCIPgetLPBInvRow(self._scip, row, coefs, NULL, NULL)) 1802 result = [coefs[i] for i in range(nrows)] 1803 free(coefs) 1804 return result 1805 1806 def getLPBInvARow(self, row): 1807 """gets a row from B^-1 * A""" 1808 # TODO: sparsity information 1809 cdef int ncols = SCIPgetNLPCols(self._scip) 1810 cdef SCIP_Real* coefs = <SCIP_Real*> malloc(ncols * sizeof(SCIP_Real)) 1811 1812 PY_SCIP_CALL(SCIPgetLPBInvARow(self._scip, row, NULL, coefs, NULL, NULL)) 1813 result = [coefs[i] for i in range(ncols)] 1814 free(coefs) 1815 return result 1816 1817 def isLPSolBasic(self): 1818 """returns whether the current LP solution is basic, i.e. is defined by a valid simplex basis""" 1819 return SCIPisLPSolBasic(self._scip) 1820 1821 #TODO: documentation!! 1822 # LP Row Methods 1823 def createEmptyRowSepa(self, Sepa sepa, name="row", lhs = 0.0, rhs = None, local = True, modifiable = False, removable = True): 1824 """creates and captures an LP row without any coefficients from a separator 1825 1826 :param sepa: separator that creates the row 1827 :param name: name of row (Default value = "row") 1828 :param lhs: left hand side of row (Default value = 0) 1829 :param rhs: right hand side of row (Default value = None) 1830 :param local: is row only valid locally? (Default value = True) 1831 :param modifiable: is row modifiable during node processing (subject to column generation)? (Default value = False) 1832 :param removable: should the row be removed from the LP due to aging or cleanup? (Default value = True) 1833 """ 1834 cdef SCIP_ROW* row 1835 lhs = -SCIPinfinity(self._scip) if lhs is None else lhs 1836 rhs = SCIPinfinity(self._scip) if rhs is None else rhs 1837 scip_sepa = SCIPfindSepa(self._scip, str_conversion(sepa.name)) 1838 PY_SCIP_CALL(SCIPcreateEmptyRowSepa(self._scip, &row, scip_sepa, str_conversion(name), lhs, rhs, local, modifiable, removable)) 1839 PyRow = Row.create(row) 1840 return PyRow 1841 1842 def createEmptyRowUnspec(self, name="row", lhs = 0.0, rhs = None, local = True, modifiable = False, removable = True): 1843 """creates and captures an LP row without any coefficients from an unspecified source 1844 1845 :param name: name of row (Default value = "row") 1846 :param lhs: left hand side of row (Default value = 0) 1847 :param rhs: right hand side of row (Default value = None) 1848 :param local: is row only valid locally? (Default value = True) 1849 :param modifiable: is row modifiable during node processing (subject to column generation)? (Default value = False) 1850 :param removable: should the row be removed from the LP due to aging or cleanup? (Default value = True) 1851 """ 1852 cdef SCIP_ROW* row 1853 lhs = -SCIPinfinity(self._scip) if lhs is None else lhs 1854 rhs = SCIPinfinity(self._scip) if rhs is None else rhs 1855 PY_SCIP_CALL(SCIPcreateEmptyRowUnspec(self._scip, &row, str_conversion(name), lhs, rhs, local, modifiable, removable)) 1856 PyRow = Row.create(row) 1857 return PyRow 1858 1859 def getRowActivity(self, Row row): 1860 """returns the activity of a row in the last LP or pseudo solution""" 1861 return SCIPgetRowActivity(self._scip, row.scip_row) 1862 1863 def getRowLPActivity(self, Row row): 1864 """returns the activity of a row in the last LP solution""" 1865 return SCIPgetRowLPActivity(self._scip, row.scip_row) 1866 1867 # TODO: do we need this? (also do we need release var??) 1868 def releaseRow(self, Row row not None): 1869 """decreases usage counter of LP row, and frees memory if necessary""" 1870 PY_SCIP_CALL(SCIPreleaseRow(self._scip, &row.scip_row)) 1871 1872 def cacheRowExtensions(self, Row row not None): 1873 """informs row, that all subsequent additions of variables to the row should be cached and not directly applied; 1874 after all additions were applied, flushRowExtensions() must be called; 1875 while the caching of row extensions is activated, information methods of the row give invalid results; 1876 caching should be used, if a row is build with addVarToRow() calls variable by variable to increase the performance""" 1877 PY_SCIP_CALL(SCIPcacheRowExtensions(self._scip, row.scip_row)) 1878 1879 def flushRowExtensions(self, Row row not None): 1880 """flushes all cached row extensions after a call of cacheRowExtensions() and merges coefficients with equal columns into a single coefficient""" 1881 PY_SCIP_CALL(SCIPflushRowExtensions(self._scip, row.scip_row)) 1882 1883 def addVarToRow(self, Row row not None, Variable var not None, value): 1884 """resolves variable to columns and adds them with the coefficient to the row""" 1885 PY_SCIP_CALL(SCIPaddVarToRow(self._scip, row.scip_row, var.scip_var, value)) 1886 1887 def printRow(self, Row row not None): 1888 """Prints row.""" 1889 PY_SCIP_CALL(SCIPprintRow(self._scip, row.scip_row, NULL)) 1890 1891 # Cutting Plane Methods 1892 def addPoolCut(self, Row row not None): 1893 """if not already existing, adds row to global cut pool""" 1894 PY_SCIP_CALL(SCIPaddPoolCut(self._scip, row.scip_row)) 1895 1896 def getCutEfficacy(self, Row cut not None, Solution sol = None): 1897 """returns efficacy of the cut with respect to the given primal solution or the current LP solution: e = -feasibility/norm""" 1898 return SCIPgetCutEfficacy(self._scip, NULL if sol is None else sol.sol, cut.scip_row) 1899 1900 def isCutEfficacious(self, Row cut not None, Solution sol = None): 1901 """ returns whether the cut's efficacy with respect to the given primal solution or the current LP solution is greater than the minimal cut efficacy""" 1902 return SCIPisCutEfficacious(self._scip, NULL if sol is None else sol.sol, cut.scip_row) 1903 1904 def addCut(self, Row cut not None, forcecut = False): 1905 """adds cut to separation storage and returns whether cut has been detected to be infeasible for local bounds""" 1906 cdef SCIP_Bool infeasible 1907 PY_SCIP_CALL(SCIPaddRow(self._scip, cut.scip_row, forcecut, &infeasible)) 1908 return infeasible 1909 1910 def getNCuts(self): 1911 """Retrieve total number of cuts in storage""" 1912 return SCIPgetNCuts(self._scip) 1913 1914 def getNCutsApplied(self): 1915 """Retrieve number of currently applied cuts""" 1916 return SCIPgetNCutsApplied(self._scip) 1917 1918 def separateSol(self, Solution sol = None, pretendroot = False, allowlocal = True, onlydelayed = False): 1919 """separates the given primal solution or the current LP solution by calling the separators and constraint handlers' 1920 separation methods; 1921 the generated cuts are stored in the separation storage and can be accessed with the methods SCIPgetCuts() and 1922 SCIPgetNCuts(); 1923 after evaluating the cuts, you have to call SCIPclearCuts() in order to remove the cuts from the 1924 separation storage; 1925 it is possible to call SCIPseparateSol() multiple times with different solutions and evaluate the found cuts 1926 afterwards 1927 :param Solution sol: solution to separate, None to use current lp solution (Default value = None) 1928 :param pretendroot: should the cut separators be called as if we are at the root node? (Default value = "False") 1929 :param allowlocal: should the separator be asked to separate local cuts (Default value = True) 1930 :param onlydelayed: should only separators be called that were delayed in the previous round? (Default value = False) 1931 returns 1932 delayed -- whether a separator was delayed 1933 cutoff -- whether the node can be cut off 1934 """ 1935 cdef SCIP_Bool delayed 1936 cdef SCIP_Bool cutoff 1937 1938 PY_SCIP_CALL( SCIPseparateSol(self._scip, NULL if sol is None else sol.sol, pretendroot, allowlocal, onlydelayed, &delayed, &cutoff) ) 1939 return delayed, cutoff 1940 1941 # Constraint functions 1942 def addCons(self, cons, name='', initial=True, separate=True, 1943 enforce=True, check=True, propagate=True, local=False, 1944 modifiable=False, dynamic=False, removable=False, 1945 stickingatnode=False): 1946 """Add a linear or quadratic constraint. 1947 1948 :param cons: list of coefficients 1949 :param name: the name of the constraint, generic name if empty (Default value = '') 1950 :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) 1951 :param separate: should the constraint be separated during LP processing? (Default value = True) 1952 :param enforce: should the constraint be enforced during node processing? (Default value = True) 1953 :param check: should the constraint be checked during for feasibility? (Default value = True) 1954 :param propagate: should the constraint be propagated during node processing? (Default value = True) 1955 :param local: is the constraint only valid locally? (Default value = False) 1956 :param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False) 1957 :param dynamic: is the constraint subject to aging? (Default value = False) 1958 :param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False) 1959 :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) 1960 1961 """ 1962 assert isinstance(cons, ExprCons), "given constraint is not ExprCons but %s" % cons.__class__.__name__ 1963 1964 # replace empty name with generic one 1965 if name == '': 1966 name = 'c'+str(SCIPgetNConss(self._scip)+1) 1967 1968 kwargs = dict(name=name, initial=initial, separate=separate, 1969 enforce=enforce, check=check, 1970 propagate=propagate, local=local, 1971 modifiable=modifiable, dynamic=dynamic, 1972 removable=removable, 1973 stickingatnode=stickingatnode) 1974 kwargs['lhs'] = -SCIPinfinity(self._scip) if cons._lhs is None else cons._lhs 1975 kwargs['rhs'] = SCIPinfinity(self._scip) if cons._rhs is None else cons._rhs 1976 1977 deg = cons.expr.degree() 1978 if deg <= 1: 1979 return self._addLinCons(cons, **kwargs) 1980 elif deg <= 2: 1981 return self._addQuadCons(cons, **kwargs) 1982 elif deg == float('inf'): # general nonlinear 1983 return self._addGenNonlinearCons(cons, **kwargs) 1984 else: 1985 return self._addNonlinearCons(cons, **kwargs) 1986 1987 def _addLinCons(self, ExprCons lincons, **kwargs): 1988 assert isinstance(lincons, ExprCons), "given constraint is not ExprCons but %s" % lincons.__class__.__name__ 1989 1990 assert lincons.expr.degree() <= 1, "given constraint is not linear, degree == %d" % lincons.expr.degree() 1991 terms = lincons.expr.terms 1992 1993 cdef SCIP_CONS* scip_cons 1994 1995 cdef int nvars = len(terms.items()) 1996 1997 vars_array = <SCIP_VAR**> malloc(nvars * sizeof(SCIP_VAR*)) 1998 coeffs_array = <SCIP_Real*> malloc(nvars * sizeof(SCIP_Real)) 1999 2000 for i, (key, coeff) in enumerate(terms.items()): 2001 vars_array[i] = <SCIP_VAR*>(<Variable>key[0]).scip_var 2002 coeffs_array[i] = <SCIP_Real>coeff 2003 2004 PY_SCIP_CALL(SCIPcreateConsLinear( 2005 self._scip, &scip_cons, str_conversion(kwargs['name']), nvars, vars_array, coeffs_array, 2006 kwargs['lhs'], kwargs['rhs'], kwargs['initial'], 2007 kwargs['separate'], kwargs['enforce'], kwargs['check'], 2008 kwargs['propagate'], kwargs['local'], kwargs['modifiable'], 2009 kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode'])) 2010 2011 PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) 2012 PyCons = Constraint.create(scip_cons) 2013 PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) 2014 2015 free(vars_array) 2016 free(coeffs_array) 2017 2018 return PyCons 2019 2020 def _addQuadCons(self, ExprCons quadcons, **kwargs): 2021 terms = quadcons.expr.terms 2022 assert quadcons.expr.degree() <= 2, "given constraint is not quadratic, degree == %d" % quadcons.expr.degree() 2023 2024 cdef SCIP_CONS* scip_cons 2025 PY_SCIP_CALL(SCIPcreateConsQuadratic( 2026 self._scip, &scip_cons, str_conversion(kwargs['name']), 2027 0, NULL, NULL, # linear 2028 0, NULL, NULL, NULL, # quadratc 2029 kwargs['lhs'], kwargs['rhs'], 2030 kwargs['initial'], kwargs['separate'], kwargs['enforce'], 2031 kwargs['check'], kwargs['propagate'], kwargs['local'], 2032 kwargs['modifiable'], kwargs['dynamic'], kwargs['removable'])) 2033 2034 for v, c in terms.items(): 2035 if len(v) == 1: # linear 2036 var = <Variable>v[0] 2037 PY_SCIP_CALL(SCIPaddLinearVarQuadratic(self._scip, scip_cons, var.scip_var, c)) 2038 else: # quadratic 2039 assert len(v) == 2, 'term length must be 1 or 2 but it is %s' % len(v) 2040 var1, var2 = <Variable>v[0], <Variable>v[1] 2041 PY_SCIP_CALL(SCIPaddBilinTermQuadratic(self._scip, scip_cons, var1.scip_var, var2.scip_var, c)) 2042 2043 PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) 2044 PyCons = Constraint.create(scip_cons) 2045 PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) 2046 return PyCons 2047 2048 def _addNonlinearCons(self, ExprCons cons, **kwargs): 2049 cdef SCIP_EXPR* expr 2050 cdef SCIP_EXPR** varexprs 2051 cdef SCIP_EXPRDATA_MONOMIAL** monomials 2052 cdef int* idxs 2053 cdef SCIP_EXPRTREE* exprtree 2054 cdef SCIP_VAR** vars 2055 cdef SCIP_CONS* scip_cons 2056 2057 terms = cons.expr.terms 2058 2059 # collect variables 2060 variables = {var.ptr():var for term in terms for var in term} 2061 variables = list(variables.values()) 2062 varindex = {var.ptr():idx for (idx,var) in enumerate(variables)} 2063 2064 # create variable expressions 2065 varexprs = <SCIP_EXPR**> malloc(len(varindex) * sizeof(SCIP_EXPR*)) 2066 for idx in varindex.values(): 2067 PY_SCIP_CALL( SCIPexprCreate(SCIPblkmem(self._scip), &expr, SCIP_EXPR_VARIDX, <int>idx) ) 2068 varexprs[idx] = expr 2069 2070 # create monomials for terms 2071 monomials = <SCIP_EXPRDATA_MONOMIAL**> malloc(len(terms) * sizeof(SCIP_EXPRDATA_MONOMIAL*)) 2072 for i, (term, coef) in enumerate(terms.items()): 2073 idxs = <int*> malloc(len(term) * sizeof(int)) 2074 for j, var in enumerate(term): 2075 idxs[j] = varindex[var.ptr()] 2076 PY_SCIP_CALL( SCIPexprCreateMonomial(SCIPblkmem(self._scip), &monomials[i], <SCIP_Real>coef, <int>len(term), idxs, NULL) ) 2077 free(idxs) 2078 2079 # create polynomial from monomials 2080 PY_SCIP_CALL( SCIPexprCreatePolynomial(SCIPblkmem(self._scip), &expr, 2081 <int>len(varindex), varexprs, 2082 <int>len(terms), monomials, 0.0, <SCIP_Bool>True) ) 2083 2084 # create expression tree 2085 PY_SCIP_CALL( SCIPexprtreeCreate(SCIPblkmem(self._scip), &exprtree, expr, <int>len(variables), 0, NULL) ) 2086 vars = <SCIP_VAR**> malloc(len(variables) * sizeof(SCIP_VAR*)) 2087 for idx, var in enumerate(variables): # same as varindex 2088 vars[idx] = (<Variable>var).scip_var 2089 PY_SCIP_CALL( SCIPexprtreeSetVars(exprtree, <int>len(variables), vars) ) 2090 2091 # create nonlinear constraint for exprtree 2092 PY_SCIP_CALL( SCIPcreateConsNonlinear( 2093 self._scip, &scip_cons, str_conversion(kwargs['name']), 2094 0, NULL, NULL, # linear 2095 1, &exprtree, NULL, # nonlinear 2096 kwargs['lhs'], kwargs['rhs'], 2097 kwargs['initial'], kwargs['separate'], kwargs['enforce'], 2098 kwargs['check'], kwargs['propagate'], kwargs['local'], 2099 kwargs['modifiable'], kwargs['dynamic'], kwargs['removable'], 2100 kwargs['stickingatnode']) ) 2101 PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) 2102 PyCons = Constraint.create(scip_cons) 2103 PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) 2104 PY_SCIP_CALL( SCIPexprtreeFree(&exprtree) ) 2105 free(vars) 2106 free(monomials) 2107 free(varexprs) 2108 return PyCons 2109 2110 def _addGenNonlinearCons(self, ExprCons cons, **kwargs): 2111 cdef SCIP_EXPR** childrenexpr 2112 cdef SCIP_EXPR** scipexprs 2113 cdef SCIP_EXPRTREE* exprtree 2114 cdef SCIP_CONS* scip_cons 2115 cdef int nchildren 2116 2117 # get arrays from python's expression tree 2118 expr = cons.expr 2119 nodes = expr_to_nodes(expr) 2120 op2idx = Operator.operatorIndexDic 2121 2122 # in nodes we have a list of tuples: each tuple is of the form 2123 # (operator, [indices]) where indices are the indices of the tuples 2124 # that are the children of this operator. This is sorted, 2125 # so we are going to do is: 2126 # loop over the nodes and create the expression of each 2127 # Note1: when the operator is SCIP_EXPR_CONST, [indices] stores the value 2128 # Note2: we need to compute the number of variable operators to find out 2129 # how many variables are there. 2130 nvars = 0 2131 for node in nodes: 2132 if op2idx[node[0]] == SCIP_EXPR_VARIDX: 2133 nvars += 1 2134 vars = <SCIP_VAR**> malloc(nvars * sizeof(SCIP_VAR*)) 2135 2136 varpos = 0 2137 scipexprs = <SCIP_EXPR**> malloc(len(nodes) * sizeof(SCIP_EXPR*)) 2138 for i,node in enumerate(nodes): 2139 op = node[0] 2140 opidx = op2idx[op] 2141 if opidx == SCIP_EXPR_VARIDX: 2142 assert len(node[1]) == 1 2143 pyvar = node[1][0] # for vars we store the actual var! 2144 PY_SCIP_CALL( SCIPexprCreate(SCIPblkmem(self._scip), &scipexprs[i], opidx, <int>varpos) ) 2145 vars[varpos] = (<Variable>pyvar).scip_var 2146 varpos += 1 2147 continue 2148 if opidx == SCIP_EXPR_CONST: 2149 assert len(node[1]) == 1 2150 value = node[1][0] 2151 PY_SCIP_CALL( SCIPexprCreate(SCIPblkmem(self._scip), &scipexprs[i], opidx, <SCIP_Real>value) ) 2152 continue 2153 if opidx == SCIP_EXPR_SUM or opidx == SCIP_EXPR_PRODUCT: 2154 nchildren = len(node[1]) 2155 childrenexpr = <SCIP_EXPR**> malloc(nchildren * sizeof(SCIP_EXPR*)) 2156 for c, pos in enumerate(node[1]): 2157 childrenexpr[c] = scipexprs[pos] 2158 PY_SCIP_CALL( SCIPexprCreate(SCIPblkmem(self._scip), &scipexprs[i], opidx, nchildren, childrenexpr) ) 2159 2160 free(childrenexpr) 2161 continue 2162 if opidx == SCIP_EXPR_REALPOWER: 2163 # the second child is the exponent which is a const 2164 valuenode = nodes[node[1][1]] 2165 assert op2idx[valuenode[0]] == SCIP_EXPR_CONST 2166 exponent = valuenode[1][0] 2167 if float(exponent).is_integer(): 2168 PY_SCIP_CALL( SCIPexprCreate(SCIPblkmem(self._scip), &scipexprs[i], SCIP_EXPR_INTPOWER, scipexprs[node[1][0]], <int>exponent) ) 2169 else: 2170 PY_SCIP_CALL( SCIPexprCreate(SCIPblkmem(self._scip), &scipexprs[i], opidx, scipexprs[node[1][0]], <SCIP_Real>exponent) ) 2171 continue 2172 if opidx == SCIP_EXPR_EXP or opidx == SCIP_EXPR_LOG or opidx == SCIP_EXPR_SQRT or opidx == SCIP_EXPR_ABS: 2173 assert len(node[1]) == 1 2174 PY_SCIP_CALL( SCIPexprCreate(SCIPblkmem(self._scip), &scipexprs[i], opidx, scipexprs[node[1][0]]) ) 2175 continue 2176 # default: 2177 raise NotImplementedError 2178 assert varpos == nvars 2179 2180 # create expression tree 2181 PY_SCIP_CALL( SCIPexprtreeCreate(SCIPblkmem(self._scip), &exprtree, scipexprs[len(nodes) - 1], nvars, 0, NULL) ) 2182 PY_SCIP_CALL( SCIPexprtreeSetVars(exprtree, <int>nvars, vars) ) 2183 2184 # create nonlinear constraint for exprtree 2185 PY_SCIP_CALL( SCIPcreateConsNonlinear( 2186 self._scip, &scip_cons, str_conversion(kwargs['name']), 2187 0, NULL, NULL, # linear 2188 1, &exprtree, NULL, # nonlinear 2189 kwargs['lhs'], kwargs['rhs'], 2190 kwargs['initial'], kwargs['separate'], kwargs['enforce'], 2191 kwargs['check'], kwargs['propagate'], kwargs['local'], 2192 kwargs['modifiable'], kwargs['dynamic'], kwargs['removable'], 2193 kwargs['stickingatnode']) ) 2194 PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) 2195 PyCons = Constraint.create(scip_cons) 2196 PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) 2197 PY_SCIP_CALL( SCIPexprtreeFree(&exprtree) ) 2198 2199 # free more memory 2200 free(scipexprs) 2201 free(vars) 2202 2203 return PyCons 2204 2205 def addConsCoeff(self, Constraint cons, Variable var, coeff): 2206 """Add coefficient to the linear constraint (if non-zero). 2207 2208 :param Constraint cons: constraint to be changed 2209 :param Variable var: variable to be added 2210 :param coeff: coefficient of new variable 2211 2212 """ 2213 PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, cons.scip_cons, var.scip_var, coeff)) 2214 2215 def addConsNode(self, Node node, Constraint cons, Node validnode=None): 2216 """Add a constraint to the given node 2217 2218 :param Node node: node to add the constraint to 2219 :param Constraint cons: constraint to add 2220 :param Node validnode: more global node where cons is also valid 2221 2222 """ 2223 if isinstance(validnode, Node): 2224 PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, cons.scip_cons, validnode.scip_node)) 2225 else: 2226 PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, cons.scip_cons, NULL)) 2227 Py_INCREF(cons) 2228 2229 def addConsLocal(self, Constraint cons, Node validnode=None): 2230 """Add a constraint to the current node 2231 2232 :param Constraint cons: constraint to add 2233 :param Node validnode: more global node where cons is also valid 2234 2235 """ 2236 if isinstance(validnode, Node): 2237 PY_SCIP_CALL(SCIPaddConsLocal(self._scip, cons.scip_cons, validnode.scip_node)) 2238 else: 2239 PY_SCIP_CALL(SCIPaddConsLocal(self._scip, cons.scip_cons, NULL)) 2240 Py_INCREF(cons) 2241 2242 def addConsSOS1(self, vars, weights=None, name="SOS1cons", 2243 initial=True, separate=True, enforce=True, check=True, 2244 propagate=True, local=False, dynamic=False, 2245 removable=False, stickingatnode=False): 2246 """Add an SOS1 constraint. 2247 2248 :param vars: list of variables to be included 2249 :param weights: list of weights (Default value = None) 2250 :param name: name of the constraint (Default value = "SOS1cons") 2251 :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) 2252 :param separate: should the constraint be separated during LP processing? (Default value = True) 2253 :param enforce: should the constraint be enforced during node processing? (Default value = True) 2254 :param check: should the constraint be checked for feasibility? (Default value = True) 2255 :param propagate: should the constraint be propagated during node processing? (Default value = True) 2256 :param local: is the constraint only valid locally? (Default value = False) 2257 :param dynamic: is the constraint subject to aging? (Default value = False) 2258 :param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False) 2259 :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) 2260 2261 """ 2262 cdef SCIP_CONS* scip_cons 2263 cdef int _nvars 2264 2265 PY_SCIP_CALL(SCIPcreateConsSOS1(self._scip, &scip_cons, str_conversion(name), 0, NULL, NULL, 2266 initial, separate, enforce, check, propagate, local, dynamic, removable, stickingatnode)) 2267 2268 if weights is None: 2269 for v in vars: 2270 var = <Variable>v 2271 PY_SCIP_CALL(SCIPappendVarSOS1(self._scip, scip_cons, var.scip_var)) 2272 else: 2273 nvars = len(vars) 2274 for i in range(nvars): 2275 var = <Variable>vars[i] 2276 PY_SCIP_CALL(SCIPaddVarSOS1(self._scip, scip_cons, var.scip_var, weights[i])) 2277 2278 PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) 2279 return Constraint.create(scip_cons) 2280 2281 def addConsSOS2(self, vars, weights=None, name="SOS2cons", 2282 initial=True, separate=True, enforce=True, check=True, 2283 propagate=True, local=False, dynamic=False, 2284 removable=False, stickingatnode=False): 2285 """Add an SOS2 constraint. 2286 2287 :param vars: list of variables to be included 2288 :param weights: list of weights (Default value = None) 2289 :param name: name of the constraint (Default value = "SOS2cons") 2290 :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) 2291 :param separate: should the constraint be separated during LP processing? (Default value = True) 2292 :param enforce: should the constraint be enforced during node processing? (Default value = True) 2293 :param check: should the constraint be checked for feasibility? (Default value = True) 2294 :param propagate: is the constraint only valid locally? (Default value = True) 2295 :param local: is the constraint only valid locally? (Default value = False) 2296 :param dynamic: is the constraint subject to aging? (Default value = False) 2297 :param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False) 2298 :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) 2299 2300 """ 2301 cdef SCIP_CONS* scip_cons 2302 cdef int _nvars 2303 2304 PY_SCIP_CALL(SCIPcreateConsSOS2(self._scip, &scip_cons, str_conversion(name), 0, NULL, NULL, 2305 initial, separate, enforce, check, propagate, local, dynamic, removable, stickingatnode)) 2306 2307 if weights is None: 2308 for v in vars: 2309 var = <Variable>v 2310 PY_SCIP_CALL(SCIPappendVarSOS2(self._scip, scip_cons, var.scip_var)) 2311 else: 2312 nvars = len(vars) 2313 for i in range(nvars): 2314 var = <Variable>vars[i] 2315 PY_SCIP_CALL(SCIPaddVarSOS2(self._scip, scip_cons, var.scip_var, weights[i])) 2316 2317 PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) 2318 return Constraint.create(scip_cons) 2319 2320 def addConsAnd(self, vars, resvar, name="ANDcons", 2321 initial=True, separate=True, enforce=True, check=True, 2322 propagate=True, local=False, modifiable=False, dynamic=False, 2323 removable=False, stickingatnode=False): 2324 """Add an AND-constraint. 2325 :param vars: list of BINARY variables to be included (operators) 2326 :param resvar: BINARY variable (resultant) 2327 :param name: name of the constraint (Default value = "ANDcons") 2328 :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) 2329 :param separate: should the constraint be separated during LP processing? (Default value = True) 2330 :param enforce: should the constraint be enforced during node processing? (Default value = True) 2331 :param check: should the constraint be checked for feasibility? (Default value = True) 2332 :param propagate: should the constraint be propagated during node processing? (Default value = True) 2333 :param local: is the constraint only valid locally? (Default value = False) 2334 :param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False) 2335 :param dynamic: is the constraint subject to aging? (Default value = False) 2336 :param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False) 2337 :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) 2338 """ 2339 cdef SCIP_CONS* scip_cons 2340 2341 nvars = len(vars) 2342 2343 _vars = <SCIP_VAR**> malloc(len(vars) * sizeof(SCIP_VAR*)) 2344 for idx, var in enumerate(vars): 2345 _vars[idx] = (<Variable>var).scip_var 2346 _resVar = (<Variable>resvar).scip_var 2347 2348 PY_SCIP_CALL(SCIPcreateConsAnd(self._scip, &scip_cons, str_conversion(name), _resVar, nvars, _vars, 2349 initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) 2350 2351 PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) 2352 pyCons = Constraint.create(scip_cons) 2353 PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) 2354 2355 free(_vars) 2356 2357 return pyCons 2358 2359 def addConsOr(self, vars, resvar, name="ORcons", 2360 initial=True, separate=True, enforce=True, check=True, 2361 propagate=True, local=False, modifiable=False, dynamic=False, 2362 removable=False, stickingatnode=False): 2363 """Add an OR-constraint. 2364 :param vars: list of BINARY variables to be included (operators) 2365 :param resvar: BINARY variable (resultant) 2366 :param name: name of the constraint (Default value = "ORcons") 2367 :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) 2368 :param separate: should the constraint be separated during LP processing? (Default value = True) 2369 :param enforce: should the constraint be enforced during node processing? (Default value = True) 2370 :param check: should the constraint be checked for feasibility? (Default value = True) 2371 :param propagate: should the constraint be propagated during node processing? (Default value = True) 2372 :param local: is the constraint only valid locally? (Default value = False) 2373 :param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False) 2374 :param dynamic: is the constraint subject to aging? (Default value = False) 2375 :param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False) 2376 :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) 2377 """ 2378 cdef SCIP_CONS* scip_cons 2379 2380 nvars = len(vars) 2381 2382 _vars = <SCIP_VAR**> malloc(len(vars) * sizeof(SCIP_VAR*)) 2383 for idx, var in enumerate(vars): 2384 _vars[idx] = (<Variable>var).scip_var 2385 _resVar = (<Variable>resvar).scip_var 2386 2387 PY_SCIP_CALL(SCIPcreateConsOr(self._scip, &scip_cons, str_conversion(name), _resVar, nvars, _vars, 2388 initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) 2389 2390 PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) 2391 pyCons = Constraint.create(scip_cons) 2392 PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) 2393 2394 free(_vars) 2395 2396 return pyCons 2397 2398 def addConsXor(self, vars, rhsvar, name="XORcons", 2399 initial=True, separate=True, enforce=True, check=True, 2400 propagate=True, local=False, modifiable=False, dynamic=False, 2401 removable=False, stickingatnode=False): 2402 """Add a XOR-constraint. 2403 :param vars: list of BINARY variables to be included (operators) 2404 :param rhsvar: BOOLEAN value, explicit True, False or bool(obj) is needed (right-hand side) 2405 :param name: name of the constraint (Default value = "XORcons") 2406 :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) 2407 :param separate: should the constraint be separated during LP processing? (Default value = True) 2408 :param enforce: should the constraint be enforced during node processing? (Default value = True) 2409 :param check: should the constraint be checked for feasibility? (Default value = True) 2410 :param propagate: should the constraint be propagated during node processing? (Default value = True) 2411 :param local: is the constraint only valid locally? (Default value = False) 2412 :param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False) 2413 :param dynamic: is the constraint subject to aging? (Default value = False) 2414 :param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False) 2415 :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) 2416 """ 2417 cdef SCIP_CONS* scip_cons 2418 2419 nvars = len(vars) 2420 2421 assert type(rhsvar) is type(bool()), "Provide BOOLEAN value as rhsvar, you gave %s." % type(rhsvar) 2422 _vars = <SCIP_VAR**> malloc(len(vars) * sizeof(SCIP_VAR*)) 2423 for idx, var in enumerate(vars): 2424 _vars[idx] = (<Variable>var).scip_var 2425 2426 PY_SCIP_CALL(SCIPcreateConsXor(self._scip, &scip_cons, str_conversion(name), rhsvar, nvars, _vars, 2427 initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) 2428 2429 PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) 2430 pyCons = Constraint.create(scip_cons) 2431 PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) 2432 2433 free(_vars) 2434 2435 return pyCons 2436 2437 def addConsCardinality(self, consvars, cardval, indvars=None, weights=None, name="CardinalityCons", 2438 initial=True, separate=True, enforce=True, check=True, 2439 propagate=True, local=False, dynamic=False, 2440 removable=False, stickingatnode=False): 2441 """Add a cardinality constraint that allows at most 'cardval' many nonzero variables. 2442 2443 :param consvars: list of variables to be included 2444 :param cardval: nonnegative integer 2445 :param indvars: indicator variables indicating which variables may be treated as nonzero in cardinality constraint, or None if new indicator variables should be introduced automatically (Default value = None) 2446 :param weights: weights determining the variable order, or None if variables should be ordered in the same way they were added to the constraint (Default value = None) 2447 :param name: name of the constraint (Default value = "CardinalityCons") 2448 :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) 2449 :param separate: should the constraint be separated during LP processing? (Default value = True) 2450 :param enforce: should the constraint be enforced during node processing? (Default value = True) 2451 :param check: should the constraint be checked for feasibility? (Default value = True) 2452 :param propagate: should the constraint be propagated during node processing? (Default value = True) 2453 :param local: is the constraint only valid locally? (Default value = False) 2454 :param dynamic: is the constraint subject to aging? (Default value = False) 2455 :param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False) 2456 :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) 2457 2458 """ 2459 cdef SCIP_CONS* scip_cons 2460 cdef SCIP_VAR* indvar 2461 2462 PY_SCIP_CALL(SCIPcreateConsCardinality(self._scip, &scip_cons, str_conversion(name), 0, NULL, cardval, NULL, NULL, 2463 initial, separate, enforce, check, propagate, local, dynamic, removable, stickingatnode)) 2464 2465 # circumvent an annoying bug in SCIP 4.0.0 that does not allow uninitialized weights 2466 if weights is None: 2467 weights = list(range(1, len(consvars) + 1)) 2468 2469 for i, v in enumerate(consvars): 2470 var = <Variable>v 2471 if indvars: 2472 indvar = (<Variable>indvars[i]).scip_var 2473 else: 2474 indvar = NULL 2475 if weights is None: 2476 PY_SCIP_CALL(SCIPappendVarCardinality(self._scip, scip_cons, var.scip_var, indvar)) 2477 else: 2478 PY_SCIP_CALL(SCIPaddVarCardinality(self._scip, scip_cons, var.scip_var, indvar, <SCIP_Real>weights[i])) 2479 2480 PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) 2481 pyCons = Constraint.create(scip_cons) 2482 2483 PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) 2484 2485 return pyCons 2486 2487 2488 def addConsIndicator(self, cons, binvar=None, name="IndicatorCons", 2489 initial=True, separate=True, enforce=True, check=True, 2490 propagate=True, local=False, dynamic=False, 2491 removable=False, stickingatnode=False): 2492 """Add an indicator constraint for the linear inequality 'cons'. 2493 2494 The 'binvar' argument models the redundancy of the linear constraint. A solution for which 2495 'binvar' is 1 must satisfy the constraint. 2496 2497 :param cons: a linear inequality of the form "<=" 2498 :param binvar: binary indicator variable, or None if it should be created (Default value = None) 2499 :param name: name of the constraint (Default value = "IndicatorCons") 2500 :param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True) 2501 :param separate: should the constraint be separated during LP processing? (Default value = True) 2502 :param enforce: should the constraint be enforced during node processing? (Default value = True) 2503 :param check: should the constraint be checked for feasibility? (Default value = True) 2504 :param propagate: should the constraint be propagated during node processing? (Default value = True) 2505 :param local: is the constraint only valid locally? (Default value = False) 2506 :param dynamic: is the constraint subject to aging? (Default value = False) 2507 :param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False) 2508 :param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False) 2509 2510 """ 2511 assert isinstance(cons, ExprCons), "given constraint is not ExprCons but %s" % cons.__class__.__name__ 2512 cdef SCIP_CONS* scip_cons 2513 cdef SCIP_VAR* _binVar 2514 if cons._lhs is not None and cons._rhs is not None: 2515 raise ValueError("expected inequality that has either only a left or right hand side") 2516 2517 if cons.expr.degree() > 1: 2518 raise ValueError("expected linear inequality, expression has degree %d" % cons.expr.degree()) 2519 2520 2521 if cons._rhs is not None: 2522 rhs = cons._rhs 2523 negate = False 2524 else: 2525 rhs = -cons._lhs 2526 negate = True 2527 2528 _binVar = (<Variable>binvar).scip_var if binvar is not None else NULL 2529 2530 PY_SCIP_CALL(SCIPcreateConsIndicator(self._scip, &scip_cons, str_conversion(name), _binVar, 0, NULL, NULL, rhs, 2531 initial, separate, enforce, check, propagate, local, dynamic, removable, stickingatnode)) 2532 terms = cons.expr.terms 2533 2534 for key, coeff in terms.items(): 2535 var = <Variable>key[0] 2536 if negate: 2537 coeff = -coeff 2538 PY_SCIP_CALL(SCIPaddVarIndicator(self._scip, scip_cons, var.scip_var, <SCIP_Real>coeff)) 2539 2540 PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) 2541 pyCons = Constraint.create(scip_cons) 2542 2543 PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) 2544 2545 return pyCons 2546 2547 def addPyCons(self, Constraint cons): 2548 """Adds a customly created cons. 2549 2550 :param Constraint cons: constraint to add 2551 2552 """ 2553 PY_SCIP_CALL(SCIPaddCons(self._scip, cons.scip_cons)) 2554 Py_INCREF(cons) 2555 2556 def addVarSOS1(self, Constraint cons, Variable var, weight): 2557 """Add variable to SOS1 constraint. 2558 2559 :param Constraint cons: SOS1 constraint 2560 :param Variable var: new variable 2561 :param weight: weight of new variable 2562 2563 """ 2564 PY_SCIP_CALL(SCIPaddVarSOS1(self._scip, cons.scip_cons, var.scip_var, weight)) 2565 2566 def appendVarSOS1(self, Constraint cons, Variable var): 2567 """Append variable to SOS1 constraint. 2568 2569 :param Constraint cons: SOS1 constraint 2570 :param Variable var: variable to append 2571 2572 """ 2573 PY_SCIP_CALL(SCIPappendVarSOS1(self._scip, cons.scip_cons, var.scip_var)) 2574 2575 def addVarSOS2(self, Constraint cons, Variable var, weight): 2576 """Add variable to SOS2 constraint. 2577 2578 :param Constraint cons: SOS2 constraint 2579 :param Variable var: new variable 2580 :param weight: weight of new variable 2581 2582 """ 2583 PY_SCIP_CALL(SCIPaddVarSOS2(self._scip, cons.scip_cons, var.scip_var, weight)) 2584 2585 def appendVarSOS2(self, Constraint cons, Variable var): 2586 """Append variable to SOS2 constraint. 2587 2588 :param Constraint cons: SOS2 constraint 2589 :param Variable var: variable to append 2590 2591 """ 2592 PY_SCIP_CALL(SCIPappendVarSOS2(self._scip, cons.scip_cons, var.scip_var)) 2593 2594 def setInitial(self, Constraint cons, newInit): 2595 """Set "initial" flag of a constraint. 2596 2597 Keyword arguments: 2598 cons -- constraint 2599 newInit -- new initial value 2600 """ 2601 PY_SCIP_CALL(SCIPsetConsInitial(self._scip, cons.scip_cons, newInit)) 2602 2603 def setRemovable(self, Constraint cons, newRem): 2604 """Set "removable" flag of a constraint. 2605 2606 Keyword arguments: 2607 cons -- constraint 2608 newRem -- new removable value 2609 """ 2610 PY_SCIP_CALL(SCIPsetConsRemovable(self._scip, cons.scip_cons, newRem)) 2611 2612 def setEnforced(self, Constraint cons, newEnf): 2613 """Set "enforced" flag of a constraint. 2614 2615 Keyword arguments: 2616 cons -- constraint 2617 newEnf -- new enforced value 2618 """ 2619 PY_SCIP_CALL(SCIPsetConsEnforced(self._scip, cons.scip_cons, newEnf)) 2620 2621 def setCheck(self, Constraint cons, newCheck): 2622 """Set "check" flag of a constraint. 2623 2624 Keyword arguments: 2625 cons -- constraint 2626 newCheck -- new check value 2627 """ 2628 PY_SCIP_CALL(SCIPsetConsChecked(self._scip, cons.scip_cons, newCheck)) 2629 2630 def chgRhs(self, Constraint cons, rhs): 2631 """Change right hand side value of a constraint. 2632 2633 :param Constraint cons: linear or quadratic constraint 2634 :param rhs: new ride hand side (set to None for +infinity) 2635 2636 """ 2637 2638 if rhs is None: 2639 rhs = SCIPinfinity(self._scip) 2640 2641 constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') 2642 if constype == 'linear': 2643 PY_SCIP_CALL(SCIPchgRhsLinear(self._scip, cons.scip_cons, rhs)) 2644 elif constype == 'quadratic': 2645 PY_SCIP_CALL(SCIPchgRhsQuadratic(self._scip, cons.scip_cons, rhs)) 2646 else: 2647 raise Warning("method cannot be called for constraints of type " + constype) 2648 2649 def chgLhs(self, Constraint cons, lhs): 2650 """Change left hand side value of a constraint. 2651 2652 :param Constraint cons: linear or quadratic constraint 2653 :param lhs: new left hand side (set to None for -infinity) 2654 2655 """ 2656 2657 if lhs is None: 2658 lhs = -SCIPinfinity(self._scip) 2659 2660 constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') 2661 if constype == 'linear': 2662 PY_SCIP_CALL(SCIPchgLhsLinear(self._scip, cons.scip_cons, lhs)) 2663 elif constype == 'quadratic': 2664 PY_SCIP_CALL(SCIPchgLhsQuadratic(self._scip, cons.scip_cons, lhs)) 2665 else: 2666 raise Warning("method cannot be called for constraints of type " + constype) 2667 2668 def getRhs(self, Constraint cons): 2669 """Retrieve right hand side value of a constraint. 2670 2671 :param Constraint cons: linear or quadratic constraint 2672 2673 """ 2674 constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') 2675 if constype == 'linear': 2676 return SCIPgetRhsLinear(self._scip, cons.scip_cons) 2677 elif constype == 'quadratic': 2678 return SCIPgetRhsQuadratic(self._scip, cons.scip_cons) 2679 else: 2680 raise Warning("method cannot be called for constraints of type " + constype) 2681 2682 def getLhs(self, Constraint cons): 2683 """Retrieve left hand side value of a constraint. 2684 2685 :param Constraint cons: linear or quadratic constraint 2686 2687 """ 2688 constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') 2689 if constype == 'linear': 2690 return SCIPgetLhsLinear(self._scip, cons.scip_cons) 2691 elif constype == 'quadratic': 2692 return SCIPgetLhsQuadratic(self._scip, cons.scip_cons) 2693 else: 2694 raise Warning("method cannot be called for constraints of type " + constype) 2695 2696 def getActivity(self, Constraint cons, Solution sol = None): 2697 """Retrieve activity of given constraint. 2698 Can only be called after solving is completed. 2699 2700 :param Constraint cons: linear or quadratic constraint 2701 :param Solution sol: solution to compute activity of, None to use current node's solution (Default value = None) 2702 2703 """ 2704 cdef SCIP_Real activity 2705 cdef SCIP_SOL* scip_sol 2706 2707 if not self.getStage() >= SCIP_STAGE_SOLVING: 2708 raise Warning("method cannot be called before problem is solved") 2709 2710 if isinstance(sol, Solution): 2711 scip_sol = sol.sol 2712 else: 2713 scip_sol = NULL 2714 2715 constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') 2716 if constype == 'linear': 2717 activity = SCIPgetActivityLinear(self._scip, cons.scip_cons, scip_sol) 2718 elif constype == 'quadratic': 2719 PY_SCIP_CALL(SCIPgetActivityQuadratic(self._scip, cons.scip_cons, scip_sol, &activity)) 2720 else: 2721 raise Warning("method cannot be called for constraints of type " + constype) 2722 2723 return activity 2724 2725 2726 def getSlack(self, Constraint cons, Solution sol = None, side = None): 2727 """Retrieve slack of given constraint. 2728 Can only be called after solving is completed. 2729 2730 2731 :param Constraint cons: linear or quadratic constraint 2732 :param Solution sol: solution to compute slack of, None to use current node's solution (Default value = None) 2733 :param side: whether to use 'lhs' or 'rhs' for ranged constraints, None to return minimum (Default value = None) 2734 2735 """ 2736 cdef SCIP_Real activity 2737 cdef SCIP_SOL* scip_sol 2738 2739 2740 if not self.getStage() >= SCIP_STAGE_SOLVING: 2741 raise Warning("method cannot be called before problem is solved") 2742 2743 if isinstance(sol, Solution): 2744 scip_sol = sol.sol 2745 else: 2746 scip_sol = NULL 2747 2748 constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') 2749 if constype == 'linear': 2750 lhs = SCIPgetLhsLinear(self._scip, cons.scip_cons) 2751 rhs = SCIPgetRhsLinear(self._scip, cons.scip_cons) 2752 activity = SCIPgetActivityLinear(self._scip, cons.scip_cons, scip_sol) 2753 elif constype == 'quadratic': 2754 lhs = SCIPgetLhsQuadratic(self._scip, cons.scip_cons) 2755 rhs = SCIPgetRhsQuadratic(self._scip, cons.scip_cons) 2756 PY_SCIP_CALL(SCIPgetActivityQuadratic(self._scip, cons.scip_cons, scip_sol, &activity)) 2757 else: 2758 raise Warning("method cannot be called for constraints of type " + constype) 2759 2760 lhsslack = activity - lhs 2761 rhsslack = rhs - activity 2762 2763 if side == 'lhs': 2764 return lhsslack 2765 elif side == 'rhs': 2766 return rhsslack 2767 else: 2768 return min(lhsslack, rhsslack) 2769 2770 def getTransformedCons(self, Constraint cons): 2771 """Retrieve transformed constraint. 2772 2773 :param Constraint cons: constraint 2774 2775 """ 2776 cdef SCIP_CONS* transcons 2777 PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.scip_cons, &transcons)) 2778 return Constraint.create(transcons) 2779 2780 def isNLPConstructed(self): 2781 """returns whether SCIP's internal NLP has been constructed""" 2782 return SCIPisNLPConstructed(self._scip) 2783 2784 def getNNlRows(self): 2785 """gets current number of nonlinear rows in SCIP's internal NLP""" 2786 return SCIPgetNNLPNlRows(self._scip) 2787 2788 def getNlRows(self): 2789 """returns a list with the nonlinear rows in SCIP's internal NLP""" 2790 cdef SCIP_NLROW** nlrows 2791 2792 nlrows = SCIPgetNLPNlRows(self._scip) 2793 return [NLRow.create(nlrows[i]) for i in range(self.getNNlRows())] 2794 2795 def getNlRowSolActivity(self, NLRow nlrow, Solution sol = None): 2796 """gives the activity of a nonlinear row for a given primal solution 2797 Keyword arguments: 2798 nlrow -- nonlinear row 2799 solution -- a primal solution, if None, then the current LP solution is used 2800 """ 2801 cdef SCIP_Real activity 2802 cdef SCIP_SOL* solptr 2803 2804 solptr = sol.sol if not sol is None else NULL 2805 PY_SCIP_CALL( SCIPgetNlRowSolActivity(self._scip, nlrow.scip_nlrow, solptr, &activity) ) 2806 return activity 2807 2808 def getNlRowSolFeasibility(self, NLRow nlrow, Solution sol = None): 2809 """gives the feasibility of a nonlinear row for a given primal solution 2810 Keyword arguments: 2811 nlrow -- nonlinear row 2812 solution -- a primal solution, if None, then the current LP solution is used 2813 """ 2814 cdef SCIP_Real feasibility 2815 cdef SCIP_SOL* solptr 2816 2817 solptr = sol.sol if not sol is None else NULL 2818 PY_SCIP_CALL( SCIPgetNlRowSolFeasibility(self._scip, nlrow.scip_nlrow, solptr, &feasibility) ) 2819 return feasibility 2820 2821 def getNlRowActivityBounds(self, NLRow nlrow): 2822 """gives the minimal and maximal activity of a nonlinear row w.r.t. the variable's bounds""" 2823 cdef SCIP_Real minactivity 2824 cdef SCIP_Real maxactivity 2825 2826 PY_SCIP_CALL( SCIPgetNlRowActivityBounds(self._scip, nlrow.scip_nlrow, &minactivity, &maxactivity) ) 2827 return (minactivity, maxactivity) 2828 2829 def printNlRow(self, NLRow nlrow): 2830 """prints nonlinear row""" 2831 PY_SCIP_CALL( SCIPprintNlRow(self._scip, nlrow.scip_nlrow, NULL) ) 2832 2833 def getTermsQuadratic(self, Constraint cons): 2834 """Retrieve bilinear, quadratic, and linear terms of a quadratic constraint. 2835 2836 :param Constraint cons: constraint 2837 2838 """ 2839 cdef SCIP_QUADVARTERM* _quadterms 2840 cdef SCIP_BILINTERM* _bilinterms 2841 cdef SCIP_VAR** _linvars 2842 cdef SCIP_Real* _lincoefs 2843 cdef int _nbilinterms 2844 cdef int _nquadterms 2845 cdef int _nlinvars 2846 2847 assert cons.isQuadratic(), "constraint is not quadratic" 2848 2849 bilinterms = [] 2850 quadterms = [] 2851 linterms = [] 2852 2853 # bilinear terms 2854 _bilinterms = SCIPgetBilinTermsQuadratic(self._scip, cons.scip_cons) 2855 _nbilinterms = SCIPgetNBilinTermsQuadratic(self._scip, cons.scip_cons) 2856 2857 for i in range(_nbilinterms): 2858 var1 = Variable.create(_bilinterms[i].var1) 2859 var2 = Variable.create(_bilinterms[i].var2) 2860 bilinterms.append((var1,var2,_bilinterms[i].coef)) 2861 2862 # quadratic terms 2863 _quadterms = SCIPgetQuadVarTermsQuadratic(self._scip, cons.scip_cons) 2864 _nquadterms = SCIPgetNQuadVarTermsQuadratic(self._scip, cons.scip_cons) 2865 2866 for i in range(_nquadterms): 2867 var = Variable.create(_quadterms[i].var) 2868 quadterms.append((var,_quadterms[i].sqrcoef,_quadterms[i].lincoef)) 2869 2870 # linear terms 2871 _linvars = SCIPgetLinearVarsQuadratic(self._scip, cons.scip_cons) 2872 _lincoefs = SCIPgetCoefsLinearVarsQuadratic(self._scip, cons.scip_cons) 2873 _nlinvars = SCIPgetNLinearVarsQuadratic(self._scip, cons.scip_cons) 2874 2875 for i in range(_nlinvars): 2876 var = Variable.create(_linvars[i]) 2877 linterms.append((var,_lincoefs[i])) 2878 2879 return (bilinterms, quadterms, linterms) 2880 2881 def setRelaxSolVal(self, Variable var, val): 2882 """sets the value of the given variable in the global relaxation solution""" 2883 PY_SCIP_CALL(SCIPsetRelaxSolVal(self._scip, NULL, var.scip_var, val)) 2884 2885 def getConss(self): 2886 """Retrieve all constraints.""" 2887 cdef SCIP_CONS** _conss 2888 cdef int _nconss 2889 conss = [] 2890 2891 _conss = SCIPgetConss(self._scip) 2892 _nconss = SCIPgetNConss(self._scip) 2893 return [Constraint.create(_conss[i]) for i in range(_nconss)] 2894 2895 def getNConss(self): 2896 """Retrieve number of all constraints""" 2897 return SCIPgetNConss(self._scip) 2898 2899 def delCons(self, Constraint cons): 2900 """Delete constraint from the model 2901 2902 :param Constraint cons: constraint to be deleted 2903 2904 """ 2905 PY_SCIP_CALL(SCIPdelCons(self._scip, cons.scip_cons)) 2906 2907 def delConsLocal(self, Constraint cons): 2908 """Delete constraint from the current node and it's children 2909 2910 :param Constraint cons: constraint to be deleted 2911 2912 """ 2913 PY_SCIP_CALL(SCIPdelConsLocal(self._scip, cons.scip_cons)) 2914 2915 def getValsLinear(self, Constraint cons): 2916 """Retrieve the coefficients of a linear constraint 2917 2918 :param Constraint cons: linear constraint to get the coefficients of 2919 2920 """ 2921 cdef SCIP_Real* _vals 2922 cdef SCIP_VAR** _vars 2923 2924 constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') 2925 if not constype == 'linear': 2926 raise Warning("coefficients not available for constraints of type ", constype) 2927 2928 _vals = SCIPgetValsLinear(self._scip, cons.scip_cons) 2929 _vars = SCIPgetVarsLinear(self._scip, cons.scip_cons) 2930 2931 valsdict = {} 2932 for i in range(SCIPgetNVarsLinear(self._scip, cons.scip_cons)): 2933 valsdict[bytes(SCIPvarGetName(_vars[i])).decode('utf-8')] = _vals[i] 2934 return valsdict 2935 2936 def getDualsolLinear(self, Constraint cons): 2937 """Retrieve the dual solution to a linear constraint. 2938 2939 :param Constraint cons: linear constraint 2940 2941 """ 2942 constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') 2943 if not constype == 'linear': 2944 raise Warning("dual solution values not available for constraints of type ", constype) 2945 if cons.isOriginal(): 2946 transcons = <Constraint>self.getTransformedCons(cons) 2947 else: 2948 transcons = cons 2949 return SCIPgetDualsolLinear(self._scip, transcons.scip_cons) 2950 2951 def getDualMultiplier(self, Constraint cons): 2952 """DEPRECATED: Retrieve the dual solution to a linear constraint. 2953 2954 :param Constraint cons: linear constraint 2955 2956 """ 2957 raise Warning("model.getDualMultiplier(cons) is deprecated: please use model.getDualsolLinear(cons)") 2958 return self.getDualsolLinear(cons) 2959 2960 def getDualfarkasLinear(self, Constraint cons): 2961 """Retrieve the dual farkas value to a linear constraint. 2962 2963 :param Constraint cons: linear constraint 2964 2965 """ 2966 # TODO this should ideally be handled on the SCIP side 2967 if cons.isOriginal(): 2968 transcons = <Constraint>self.getTransformedCons(cons) 2969 return SCIPgetDualfarkasLinear(self._scip, transcons.scip_cons) 2970 else: 2971 return SCIPgetDualfarkasLinear(self._scip, cons.scip_cons) 2972 2973 def getVarRedcost(self, Variable var): 2974 """Retrieve the reduced cost of a variable. 2975 2976 :param Variable var: variable to get the reduced cost of 2977 2978 """ 2979 redcost = None 2980 try: 2981 redcost = SCIPgetVarRedcost(self._scip, var.scip_var) 2982 if self.getObjectiveSense() == "maximize": 2983 redcost = -redcost 2984 except: 2985 raise Warning("no reduced cost available for variable " + var.name) 2986 return redcost 2987 2988 def optimize(self): 2989 """Optimize the problem.""" 2990 PY_SCIP_CALL(SCIPsolve(self._scip)) 2991 self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip)) 2992 2993 def presolve(self): 2994 """Presolve the problem.""" 2995 PY_SCIP_CALL(SCIPpresolve(self._scip)) 2996 2997 # Benders' decomposition methods 2998 def initBendersDefault(self, subproblems): 2999 """initialises the default Benders' decomposition with a dictionary of subproblems 3000 3001 Keyword arguments: 3002 subproblems -- a single Model instance or dictionary of Model instances 3003 """ 3004 cdef SCIP** subprobs 3005 cdef SCIP_BENDERS* benders 3006 3007 # checking whether subproblems is a dictionary 3008 if isinstance(subproblems, dict): 3009 isdict = True 3010 nsubproblems = len(subproblems) 3011 else: 3012 isdict = False 3013 nsubproblems = 1 3014 3015 # create array of SCIP instances for the subproblems 3016 subprobs = <SCIP**> malloc(nsubproblems * sizeof(SCIP*)) 3017 3018 # if subproblems is a dictionary, then the dictionary is turned into a c array 3019 if isdict: 3020 for idx, subprob in enumerate(subproblems.values()): 3021 subprobs[idx] = (<Model>subprob)._scip 3022 else: 3023 subprobs[0] = (<Model>subproblems)._scip 3024 3025 # creating the default Benders' decomposition 3026 PY_SCIP_CALL(SCIPcreateBendersDefault(self._scip, subprobs, nsubproblems)) 3027 benders = SCIPfindBenders(self._scip, "default") 3028 3029 # activating the Benders' decomposition constraint handlers 3030 self.setBoolParam("constraints/benderslp/active", True) 3031 self.setBoolParam("constraints/benders/active", True) 3032 #self.setIntParam("limits/maxorigsol", 0) 3033 3034 def computeBestSolSubproblems(self): 3035 """Solves the subproblems with the best solution to the master problem. 3036 Afterwards, the best solution from each subproblem can be queried to get 3037 the solution to the original problem. 3038 3039 If the user wants to resolve the subproblems, they must free them by 3040 calling freeBendersSubproblems() 3041 """ 3042 cdef SCIP_BENDERS** _benders 3043 cdef SCIP_Bool _infeasible 3044 cdef int nbenders 3045 cdef int nsubproblems 3046 3047 solvecip = True 3048 3049 nbenders = SCIPgetNActiveBenders(self._scip) 3050 _benders = SCIPgetBenders(self._scip) 3051 3052 # solving all subproblems from all Benders' decompositions 3053 for i in range(nbenders): 3054 nsubproblems = SCIPbendersGetNSubproblems(_benders[i]) 3055 for j in range(nsubproblems): 3056 PY_SCIP_CALL(SCIPsetupBendersSubproblem(self._scip, 3057 _benders[i], self._bestSol.sol, j, SCIP_BENDERSENFOTYPE_CHECK)) 3058 PY_SCIP_CALL(SCIPsolveBendersSubproblem(self._scip, 3059 _benders[i], self._bestSol.sol, j, &_infeasible, solvecip, NULL)) 3060 3061 def freeBendersSubproblems(self): 3062 """Calls the free subproblem function for the Benders' decomposition. 3063 This will free all subproblems for all decompositions. 3064 """ 3065 cdef SCIP_BENDERS** _benders 3066 cdef int nbenders 3067 cdef int nsubproblems 3068 3069 nbenders = SCIPgetNActiveBenders(self._scip) 3070 _benders = SCIPgetBenders(self._scip) 3071 3072 # solving all subproblems from all Benders' decompositions 3073 for i in range(nbenders): 3074 nsubproblems = SCIPbendersGetNSubproblems(_benders[i]) 3075 for j in range(nsubproblems): 3076 PY_SCIP_CALL(SCIPfreeBendersSubproblem(self._scip, _benders[i], 3077 j)) 3078 3079 def updateBendersLowerbounds(self, lowerbounds, Benders benders=None): 3080 """"updates the subproblem lower bounds for benders using 3081 the lowerbounds dict. If benders is None, then the default 3082 Benders' decomposition is updated 3083 """ 3084 cdef SCIP_BENDERS* _benders 3085 3086 assert type(lowerbounds) is dict 3087 3088 if benders is None: 3089 _benders = SCIPfindBenders(self._scip, "default") 3090 else: 3091 _benders = benders._benders 3092 3093 for d in lowerbounds.keys(): 3094 SCIPbendersUpdateSubproblemLowerbound(_benders, d, lowerbounds[d]) 3095 3096 def activateBenders(self, Benders benders, int nsubproblems): 3097 """Activates the Benders' decomposition plugin with the input name 3098 3099 Keyword arguments: 3100 benders -- the Benders' decomposition to which the subproblem belongs to 3101 nsubproblems -- the number of subproblems in the Benders' decomposition 3102 """ 3103 PY_SCIP_CALL(SCIPactivateBenders(self._scip, benders._benders, nsubproblems)) 3104 3105 def addBendersSubproblem(self, Benders benders, subproblem): 3106 """adds a subproblem to the Benders' decomposition given by the input 3107 name. 3108 3109 Keyword arguments: 3110 benders -- the Benders' decomposition to which the subproblem belongs to 3111 subproblem -- the subproblem to add to the decomposition 3112 isconvex -- can be used to specify whether the subproblem is convex 3113 """ 3114 PY_SCIP_CALL(SCIPaddBendersSubproblem(self._scip, benders._benders, (<Model>subproblem)._scip)) 3115 3116 def setBendersSubproblemIsConvex(self, Benders benders, probnumber, isconvex = True): 3117 """sets a flag indicating whether the subproblem is convex 3118 3119 Keyword arguments: 3120 benders -- the Benders' decomposition which contains the subproblem 3121 probnumber -- the problem number of the subproblem that the convexity will be set for 3122 isconvex -- flag to indicate whether the subproblem is convex 3123 """ 3124 SCIPbendersSetSubproblemIsConvex(benders._benders, probnumber, isconvex) 3125 3126 def setupBendersSubproblem(self, probnumber, Benders benders = None, Solution solution = None, checktype = PY_SCIP_BENDERSENFOTYPE.LP): 3127 """ sets up the Benders' subproblem given the master problem solution 3128 3129 Keyword arguments: 3130 probnumber -- the index of the problem that is to be set up 3131 benders -- the Benders' decomposition to which the subproblem belongs to 3132 solution -- the master problem solution that is used for the set up, if None, then the LP solution is used 3133 checktype -- the type of solution check that prompted the solving of the Benders' subproblems, either 3134 PY_SCIP_BENDERSENFOTYPE: LP, RELAX, PSEUDO or CHECK. Default is LP 3135 """ 3136 cdef SCIP_BENDERS* scip_benders 3137 cdef SCIP_SOL* scip_sol 3138 3139 if isinstance(solution, Solution): 3140 scip_sol = solution.sol 3141 else: 3142 scip_sol = NULL 3143 3144 if benders is None: 3145 scip_benders = SCIPfindBenders(self._scip, "default") 3146 else: 3147 scip_benders = benders._benders 3148 3149 retcode = SCIPsetupBendersSubproblem(self._scip, scip_benders, scip_sol, probnumber, checktype) 3150 3151 PY_SCIP_CALL(retcode) 3152 3153 def solveBendersSubproblem(self, probnumber, solvecip, Benders benders = None, Solution solution = None): 3154 """ solves the Benders' decomposition subproblem. The convex relaxation will be solved unless 3155 the parameter solvecip is set to True. 3156 3157 Keyword arguments: 3158 probnumber -- the index of the problem that is to be set up 3159 solvecip -- should the CIP of the subproblem be solved, if False, then only the convex relaxation is solved 3160 benders -- the Benders' decomposition to which the subproblem belongs to 3161 solution -- the master problem solution that is used for the set up, if None, then the LP solution is used 3162 """ 3163 3164 cdef SCIP_BENDERS* scip_benders 3165 cdef SCIP_SOL* scip_sol 3166 cdef SCIP_Real objective 3167 cdef SCIP_Bool infeasible 3168 3169 if isinstance(solution, Solution): 3170 scip_sol = solution.sol 3171 else: 3172 scip_sol = NULL 3173 3174 if benders is None: 3175 scip_benders = SCIPfindBenders(self._scip, "default") 3176 else: 3177 scip_benders = benders._benders 3178 3179 PY_SCIP_CALL(SCIPsolveBendersSubproblem(self._scip, scip_benders, scip_sol, 3180 probnumber, &infeasible, solvecip, &objective)) 3181 3182 return infeasible, objective 3183 3184 def getBendersSubproblem(self, probnumber, Benders benders = None): 3185 """Returns a Model object that wraps around the SCIP instance of the subproblem. 3186 NOTE: This Model object is just a place holder and SCIP instance will not be freed when the object is destroyed. 3187 3188 Keyword arguments: 3189 probnumber -- the problem number for subproblem that is required 3190 benders -- the Benders' decomposition object for the that the subproblem belongs to (Default = None) 3191 """ 3192 cdef SCIP_BENDERS* scip_benders 3193 cdef SCIP* scip_subprob 3194 3195 if benders is None: 3196 scip_benders = SCIPfindBenders(self._scip, "default") 3197 else: 3198 scip_benders = benders._benders 3199 3200 scip_subprob = SCIPbendersSubproblem(scip_benders, probnumber) 3201 3202 return Model.create(scip_subprob) 3203 3204 def getBendersVar(self, Variable var, Benders benders = None, probnumber = -1): 3205 """Returns the variable for the subproblem or master problem 3206 depending on the input probnumber 3207 3208 Keyword arguments: 3209 var -- the source variable for which the target variable is requested 3210 benders -- the Benders' decomposition to which the subproblem variables belong to 3211 probnumber -- the problem number for which the target variable belongs, -1 for master problem 3212 """ 3213 cdef SCIP_BENDERS* _benders 3214 cdef SCIP_VAR* _mappedvar 3215 3216 if benders is None: 3217 _benders = SCIPfindBenders(self._scip, "default") 3218 else: 3219 _benders = benders._benders 3220 3221 if probnumber == -1: 3222 PY_SCIP_CALL(SCIPgetBendersMasterVar(self._scip, _benders, var.scip_var, &_mappedvar)) 3223 else: 3224 PY_SCIP_CALL(SCIPgetBendersSubproblemVar(self._scip, _benders, var.scip_var, &_mappedvar, probnumber)) 3225 3226 if _mappedvar == NULL: 3227 mappedvar = None 3228 else: 3229 mappedvar = Variable.create(_mappedvar) 3230 3231 return mappedvar 3232 3233 def getBendersAuxiliaryVar(self, probnumber, Benders benders = None): 3234 """Returns the auxiliary variable that is associated with the input problem number 3235 3236 Keyword arguments: 3237 probnumber -- the problem number for which the target variable belongs, -1 for master problem 3238 benders -- the Benders' decomposition to which the subproblem variables belong to 3239 """ 3240 cdef SCIP_BENDERS* _benders 3241 cdef SCIP_VAR* _auxvar 3242 3243 if benders is None: 3244 _benders = SCIPfindBenders(self._scip, "default") 3245 else: 3246 _benders = benders._benders 3247 3248 _auxvar = SCIPbendersGetAuxiliaryVar(_benders, probnumber) 3249 auxvar = Variable.create(_auxvar) 3250 3251 return auxvar 3252 3253 def checkBendersSubproblemOptimality(self, Solution solution, probnumber, Benders benders = None): 3254 """Returns whether the subproblem is optimal w.r.t the master problem auxiliary variables. 3255 3256 Keyword arguments: 3257 solution -- the master problem solution that is being checked for optimamlity 3258 probnumber -- the problem number for which optimality is being checked 3259 benders -- the Benders' decomposition to which the subproblem belongs to 3260 """ 3261 cdef SCIP_BENDERS* _benders 3262 cdef SCIP_SOL* scip_sol 3263 cdef SCIP_Bool optimal 3264 3265 if benders is None: 3266 _benders = SCIPfindBenders(self._scip, "default") 3267 else: 3268 _benders = benders._benders 3269 3270 if isinstance(solution, Solution): 3271 scip_sol = solution.sol 3272 else: 3273 scip_sol = NULL 3274 3275 PY_SCIP_CALL( SCIPcheckBendersSubproblemOptimality(self._scip, _benders, 3276 scip_sol, probnumber, &optimal) ) 3277 3278 return optimal 3279 3280 def includeBendersDefaultCuts(self, Benders benders): 3281 """includes the default Benders' decomposition cuts to the custom Benders' decomposition plugin 3282 3283 Keyword arguments: 3284 benders -- the Benders' decomposition that the default cuts will be applied to 3285 """ 3286 PY_SCIP_CALL( SCIPincludeBendersDefaultCuts(self._scip, benders._benders) ) 3287 3288 3289 def includeEventhdlr(self, Eventhdlr eventhdlr, name, desc): 3290 """Include an event handler. 3291 3292 Keyword arguments: 3293 eventhdlr -- event handler 3294 name -- name of event handler 3295 desc -- description of event handler 3296 """ 3297 n = str_conversion(name) 3298 d = str_conversion(desc) 3299 PY_SCIP_CALL(SCIPincludeEventhdlr(self._scip, n, d, 3300 PyEventCopy, 3301 PyEventFree, 3302 PyEventInit, 3303 PyEventExit, 3304 PyEventInitsol, 3305 PyEventExitsol, 3306 PyEventDelete, 3307 PyEventExec, 3308 <SCIP_EVENTHDLRDATA*>eventhdlr)) 3309 eventhdlr.model = <Model>weakref.proxy(self) 3310 eventhdlr.name = name 3311 Py_INCREF(eventhdlr) 3312 3313 def includePricer(self, Pricer pricer, name, desc, priority=1, delay=True): 3314 """Include a pricer. 3315 3316 :param Pricer pricer: pricer 3317 :param name: name of pricer 3318 :param desc: description of pricer 3319 :param priority: priority of pricer (Default value = 1) 3320 :param delay: should the pricer be delayed until no other pricers or already existing problem variables with negative reduced costs are found? (Default value = True) 3321 3322 """ 3323 n = str_conversion(name) 3324 d = str_conversion(desc) 3325 PY_SCIP_CALL(SCIPincludePricer(self._scip, n, d, 3326 priority, delay, 3327 PyPricerCopy, PyPricerFree, PyPricerInit, PyPricerExit, PyPricerInitsol, PyPricerExitsol, PyPricerRedcost, PyPricerFarkas, 3328 <SCIP_PRICERDATA*>pricer)) 3329 cdef SCIP_PRICER* scip_pricer 3330 scip_pricer = SCIPfindPricer(self._scip, n) 3331 PY_SCIP_CALL(SCIPactivatePricer(self._scip, scip_pricer)) 3332 pricer.model = <Model>weakref.proxy(self) 3333 Py_INCREF(pricer) 3334 3335 def includeConshdlr(self, Conshdlr conshdlr, name, desc, sepapriority=0, 3336 enfopriority=0, chckpriority=0, sepafreq=-1, propfreq=-1, 3337 eagerfreq=100, maxprerounds=-1, delaysepa=False, 3338 delayprop=False, needscons=True, 3339 proptiming=PY_SCIP_PROPTIMING.BEFORELP, 3340 presoltiming=PY_SCIP_PRESOLTIMING.MEDIUM): 3341 """Include a constraint handler 3342 3343 :param Conshdlr conshdlr: constraint handler 3344 :param name: name of constraint handler 3345 :param desc: description of constraint handler 3346 :param sepapriority: priority for separation (Default value = 0) 3347 :param enfopriority: priority for constraint enforcing (Default value = 0) 3348 :param chckpriority: priority for checking feasibility (Default value = 0) 3349 :param sepafreq: frequency for separating cuts; 0 = only at root node (Default value = -1) 3350 :param propfreq: frequency for propagating domains; 0 = only preprocessing propagation (Default value = -1) 3351 :param eagerfreq: frequency for using all instead of only the useful constraints in separation, propagation and enforcement; -1 = no eager evaluations, 0 = first only (Default value = 100) 3352 :param maxprerounds: maximal number of presolving rounds the constraint handler participates in (Default value = -1) 3353 :param delaysepa: should separation method be delayed, if other separators found cuts? (Default value = False) 3354 :param delayprop: should propagation method be delayed, if other propagators found reductions? (Default value = False) 3355 :param needscons: should the constraint handler be skipped, if no constraints are available? (Default value = True) 3356 :param proptiming: positions in the node solving loop where propagation method of constraint handlers should be executed (Default value = SCIP_PROPTIMING.BEFORELP) 3357 :param presoltiming: timing mask of the constraint handler's presolving method (Default value = SCIP_PRESOLTIMING.MEDIUM) 3358 3359 """ 3360 n = str_conversion(name) 3361 d = str_conversion(desc) 3362 PY_SCIP_CALL(SCIPincludeConshdlr(self._scip, n, d, sepapriority, enfopriority, chckpriority, sepafreq, propfreq, eagerfreq, 3363 maxprerounds, delaysepa, delayprop, needscons, proptiming, presoltiming, 3364 PyConshdlrCopy, PyConsFree, PyConsInit, PyConsExit, PyConsInitpre, PyConsExitpre, 3365 PyConsInitsol, PyConsExitsol, PyConsDelete, PyConsTrans, PyConsInitlp, PyConsSepalp, PyConsSepasol, 3366 PyConsEnfolp, PyConsEnforelax, PyConsEnfops, PyConsCheck, PyConsProp, PyConsPresol, PyConsResprop, PyConsLock, 3367 PyConsActive, PyConsDeactive, PyConsEnable, PyConsDisable, PyConsDelvars, PyConsPrint, PyConsCopy, 3368 PyConsParse, PyConsGetvars, PyConsGetnvars, PyConsGetdivebdchgs, 3369 <SCIP_CONSHDLRDATA*>conshdlr)) 3370 conshdlr.model = <Model>weakref.proxy(self) 3371 conshdlr.name = name 3372 Py_INCREF(conshdlr) 3373 3374 def createCons(self, Conshdlr conshdlr, name, initial=True, separate=True, enforce=True, check=True, propagate=True, 3375 local=False, modifiable=False, dynamic=False, removable=False, stickingatnode=False): 3376 """Create a constraint of a custom constraint handler 3377 3378 :param Conshdlr conshdlr: constraint handler 3379 :param name: name of constraint 3380 :param initial: (Default value = True) 3381 :param separate: (Default value = True) 3382 :param enforce: (Default value = True) 3383 :param check: (Default value = True) 3384 :param propagate: (Default value = True) 3385 :param local: (Default value = False) 3386 :param modifiable: (Default value = False) 3387 :param dynamic: (Default value = False) 3388 :param removable: (Default value = False) 3389 :param stickingatnode: (Default value = False) 3390 3391 """ 3392 3393 n = str_conversion(name) 3394 cdef SCIP_CONSHDLR* scip_conshdlr 3395 scip_conshdlr = SCIPfindConshdlr(self._scip, str_conversion(conshdlr.name)) 3396 constraint = Constraint() 3397 PY_SCIP_CALL(SCIPcreateCons(self._scip, &(constraint.scip_cons), n, scip_conshdlr, <SCIP_CONSDATA*>constraint, 3398 initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode)) 3399 return constraint 3400 3401 def includePresol(self, Presol presol, name, desc, priority, maxrounds, timing=SCIP_PRESOLTIMING_FAST): 3402 """Include a presolver 3403 3404 :param Presol presol: presolver 3405 :param name: name of presolver 3406 :param desc: description of presolver 3407 :param priority: priority of the presolver (>= 0: before, < 0: after constraint handlers) 3408 :param maxrounds: maximal number of presolving rounds the presolver participates in (-1: no limit) 3409 :param timing: timing mask of presolver (Default value = SCIP_PRESOLTIMING_FAST) 3410 3411 """ 3412 n = str_conversion(name) 3413 d = str_conversion(desc) 3414 PY_SCIP_CALL(SCIPincludePresol(self._scip, n, d, priority, maxrounds, timing, PyPresolCopy, PyPresolFree, PyPresolInit, 3415 PyPresolExit, PyPresolInitpre, PyPresolExitpre, PyPresolExec, <SCIP_PRESOLDATA*>presol)) 3416 presol.model = <Model>weakref.proxy(self) 3417 Py_INCREF(presol) 3418 3419 def includeSepa(self, Sepa sepa, name, desc, priority=0, freq=10, maxbounddist=1.0, usessubscip=False, delay=False): 3420 """Include a separator 3421 3422 :param Sepa sepa: separator 3423 :param name: name of separator 3424 :param desc: description of separator 3425 :param priority: priority of separator (>= 0: before, < 0: after constraint handlers) 3426 :param freq: frequency for calling separator 3427 :param maxbounddist: maximal relative distance from current node's dual bound to primal bound compared to best node's dual bound for applying separation 3428 :param usessubscip: does the separator use a secondary SCIP instance? (Default value = False) 3429 :param delay: should separator be delayed, if other separators found cuts? (Default value = False) 3430 3431 """ 3432 n = str_conversion(name) 3433 d = str_conversion(desc) 3434 PY_SCIP_CALL(SCIPincludeSepa(self._scip, n, d, priority, freq, maxbounddist, usessubscip, delay, PySepaCopy, PySepaFree, 3435 PySepaInit, PySepaExit, PySepaInitsol, PySepaExitsol, PySepaExeclp, PySepaExecsol, <SCIP_SEPADATA*>sepa)) 3436 sepa.model = <Model>weakref.proxy(self) 3437 sepa.name = name 3438 Py_INCREF(sepa) 3439 3440 def includeProp(self, Prop prop, name, desc, presolpriority, presolmaxrounds, 3441 proptiming, presoltiming=SCIP_PRESOLTIMING_FAST, priority=1, freq=1, delay=True): 3442 """Include a propagator. 3443 3444 :param Prop prop: propagator 3445 :param name: name of propagator 3446 :param desc: description of propagator 3447 :param presolpriority: presolving priority of the propgator (>= 0: before, < 0: after constraint handlers) 3448 :param presolmaxrounds: maximal number of presolving rounds the propagator participates in (-1: no limit) 3449 :param proptiming: positions in the node solving loop where propagation method of constraint handlers should be executed 3450 :param presoltiming: timing mask of the constraint handler's presolving method (Default value = SCIP_PRESOLTIMING_FAST) 3451 :param priority: priority of the propagator (Default value = 1) 3452 :param freq: frequency for calling propagator (Default value = 1) 3453 :param delay: should propagator be delayed if other propagators have found reductions? (Default value = True) 3454 3455 """ 3456 n = str_conversion(name) 3457 d = str_conversion(desc) 3458 PY_SCIP_CALL(SCIPincludeProp(self._scip, n, d, 3459 priority, freq, delay, 3460 proptiming, presolpriority, presolmaxrounds, 3461 presoltiming, PyPropCopy, PyPropFree, PyPropInit, PyPropExit, 3462 PyPropInitpre, PyPropExitpre, PyPropInitsol, PyPropExitsol, 3463 PyPropPresol, PyPropExec, PyPropResProp, 3464 <SCIP_PROPDATA*> prop)) 3465 prop.model = <Model>weakref.proxy(self) 3466 Py_INCREF(prop) 3467 3468 def includeHeur(self, Heur heur, name, desc, dispchar, priority=10000, freq=1, freqofs=0, 3469 maxdepth=-1, timingmask=SCIP_HEURTIMING_BEFORENODE, usessubscip=False): 3470 """Include a primal heuristic. 3471 3472 :param Heur heur: heuristic 3473 :param name: name of heuristic 3474 :param desc: description of heuristic 3475 :param dispchar: display character of heuristic 3476 :param priority: priority of the heuristic (Default value = 10000) 3477 :param freq: frequency for calling heuristic (Default value = 1) 3478 :param freqofs: frequency offset for calling heuristic (Default value = 0) 3479 :param maxdepth: maximal depth level to call heuristic at (Default value = -1) 3480 :param timingmask: positions in the node solving loop where heuristic should be executed (Default value = SCIP_HEURTIMING_BEFORENODE) 3481 :param usessubscip: does the heuristic use a secondary SCIP instance? (Default value = False) 3482 3483 """ 3484 nam = str_conversion(name) 3485 des = str_conversion(desc) 3486 dis = ord(str_conversion(dispchar)) 3487 PY_SCIP_CALL(SCIPincludeHeur(self._scip, nam, des, dis, 3488 priority, freq, freqofs, 3489 maxdepth, timingmask, usessubscip, 3490 PyHeurCopy, PyHeurFree, PyHeurInit, PyHeurExit, 3491 PyHeurInitsol, PyHeurExitsol, PyHeurExec, 3492 <SCIP_HEURDATA*> heur)) 3493 heur.model = <Model>weakref.proxy(self) 3494 heur.name = name 3495 Py_INCREF(heur) 3496 3497 def includeRelax(self, Relax relax, name, desc, priority=10000, freq=1): 3498 """Include a relaxation handler. 3499 3500 :param Relax relax: relaxation handler 3501 :param name: name of relaxation handler 3502 :param desc: description of relaxation handler 3503 :param priority: priority of the relaxation handler (negative: after LP, non-negative: before LP, Default value = 10000) 3504 :param freq: frequency for calling relaxation handler 3505 3506 """ 3507 nam = str_conversion(name) 3508 des = str_conversion(desc) 3509 PY_SCIP_CALL(SCIPincludeRelax(self._scip, nam, des, priority, freq, PyRelaxCopy, PyRelaxFree, PyRelaxInit, PyRelaxExit, 3510 PyRelaxInitsol, PyRelaxExitsol, PyRelaxExec, <SCIP_RELAXDATA*> relax)) 3511 relax.model = <Model>weakref.proxy(self) 3512 relax.name = name 3513 3514 Py_INCREF(relax) 3515 3516 def includeBranchrule(self, Branchrule branchrule, name, desc, priority, maxdepth, maxbounddist): 3517 """Include a branching rule. 3518 3519 :param Branchrule branchrule: branching rule 3520 :param name: name of branching rule 3521 :param desc: description of branching rule 3522 :param priority: priority of branching rule 3523 :param maxdepth: maximal depth level up to which this branching rule should be used (or -1) 3524 :param maxbounddist: maximal relative distance from current node's dual bound to primal bound compared to best node's dual bound for applying branching rule (0.0: only on current best node, 1.0: on all nodes) 3525 3526 """ 3527 nam = str_conversion(name) 3528 des = str_conversion(desc) 3529 PY_SCIP_CALL(SCIPincludeBranchrule(self._scip, nam, des, 3530 priority, maxdepth, maxbounddist, 3531 PyBranchruleCopy, PyBranchruleFree, PyBranchruleInit, PyBranchruleExit, 3532 PyBranchruleInitsol, PyBranchruleExitsol, PyBranchruleExeclp, PyBranchruleExecext, 3533 PyBranchruleExecps, <SCIP_BRANCHRULEDATA*> branchrule)) 3534 branchrule.model = <Model>weakref.proxy(self) 3535 Py_INCREF(branchrule) 3536 3537 def includeNodesel(self, Nodesel nodesel, name, desc, stdpriority, memsavepriority): 3538 """Include a node selector. 3539 3540 :param Nodesel nodesel: node selector 3541 :param name: name of node selector 3542 :param desc: description of node selector 3543 :param stdpriority: priority of the node selector in standard mode 3544 :param memsavepriority: priority of the node selector in memory saving mode 3545 3546 """ 3547 nam = str_conversion(name) 3548 des = str_conversion(desc) 3549 PY_SCIP_CALL(SCIPincludeNodesel(self._scip, nam, des, 3550 stdpriority, memsavepriority, 3551 PyNodeselCopy, PyNodeselFree, PyNodeselInit, PyNodeselExit, 3552 PyNodeselInitsol, PyNodeselExitsol, PyNodeselSelect, PyNodeselComp, 3553 <SCIP_NODESELDATA*> nodesel)) 3554 nodesel.model = <Model>weakref.proxy(self) 3555 Py_INCREF(nodesel) 3556 3557 def includeBenders(self, Benders benders, name, desc, priority=1, cutlp=True, cutpseudo=True, cutrelax=True, 3558 shareaux=False): 3559 """Include a Benders' decomposition. 3560 3561 Keyword arguments: 3562 benders -- the Benders decomposition 3563 name -- the name 3564 desc -- the description 3565 priority -- priority of the Benders' decomposition 3566 cutlp -- should Benders' cuts be generated from LP solutions 3567 cutpseudo -- should Benders' cuts be generated from pseudo solutions 3568 cutrelax -- should Benders' cuts be generated from relaxation solutions 3569 shareaux -- should the Benders' decomposition share the auxiliary variables of the highest priority Benders' decomposition 3570 """ 3571 n = str_conversion(name) 3572 d = str_conversion(desc) 3573 PY_SCIP_CALL(SCIPincludeBenders(self._scip, n, d, 3574 priority, cutlp, cutrelax, cutpseudo, shareaux, 3575 PyBendersCopy, PyBendersFree, PyBendersInit, PyBendersExit, PyBendersInitpre, 3576 PyBendersExitpre, PyBendersInitsol, PyBendersExitsol, PyBendersGetvar, 3577 PyBendersCreatesub, PyBendersPresubsolve, PyBendersSolvesubconvex, 3578 PyBendersSolvesub, PyBendersPostsolve, PyBendersFreesub, 3579 <SCIP_BENDERSDATA*>benders)) 3580 cdef SCIP_BENDERS* scip_benders 3581 scip_benders = SCIPfindBenders(self._scip, n) 3582 benders.model = <Model>weakref.proxy(self) 3583 benders.name = name 3584 benders._benders = scip_benders 3585 Py_INCREF(benders) 3586 3587 def includeBenderscut(self, Benders benders, Benderscut benderscut, name, desc, priority=1, islpcut=True): 3588 """ Include a Benders' decomposition cutting method 3589 3590 Keyword arguments: 3591 benders -- the Benders' decomposition that this cutting method is attached to 3592 benderscut --- the Benders' decomposition cutting method 3593 name -- the name 3594 desc -- the description 3595 priority -- priority of the Benders' decomposition 3596 islpcut -- is this cutting method suitable for generating cuts for convex relaxations? 3597 """ 3598 cdef SCIP_BENDERS* _benders 3599 3600 _benders = benders._benders 3601 3602 n = str_conversion(name) 3603 d = str_conversion(desc) 3604 PY_SCIP_CALL(SCIPincludeBenderscut(self._scip, _benders, n, d, priority, islpcut, 3605 PyBenderscutCopy, PyBenderscutFree, PyBenderscutInit, PyBenderscutExit, 3606 PyBenderscutInitsol, PyBenderscutExitsol, PyBenderscutExec, 3607 <SCIP_BENDERSCUTDATA*>benderscut)) 3608 3609 cdef SCIP_BENDERSCUT* scip_benderscut 3610 scip_benderscut = SCIPfindBenderscut(_benders, n) 3611 benderscut.model = <Model>weakref.proxy(self) 3612 benderscut.benders = benders 3613 benderscut.name = name 3614 # TODO: It might be necessary in increment the reference to benders i.e Py_INCREF(benders) 3615 Py_INCREF(benderscut) 3616 3617 3618 def getLPBranchCands(self): 3619 """gets branching candidates for LP solution branching (fractional variables) along with solution values, 3620 fractionalities, and number of branching candidates; The number of branching candidates does NOT account 3621 for fractional implicit integer variables which should not be used for branching decisions. Fractional 3622 implicit integer variables are stored at the positions *nlpcands to *nlpcands + *nfracimplvars - 1 3623 branching rules should always select the branching candidate among the first npriolpcands of the candidate list 3624 3625 :return tuple (lpcands, lpcandssol, lpcadsfrac, nlpcands, npriolpcands, nfracimplvars) where 3626 3627 lpcands: list of variables of LP branching candidates 3628 lpcandssol: list of LP candidate solution values 3629 lpcandsfrac list of LP candidate fractionalities 3630 nlpcands: number of LP branching candidates 3631 npriolpcands: number of candidates with maximal priority 3632 nfracimplvars: number of fractional implicit integer variables 3633 3634 """ 3635 cdef int ncands 3636 cdef int nlpcands 3637 cdef int npriolpcands 3638 cdef int nfracimplvars 3639 3640 cdef SCIP_VAR** lpcands 3641 cdef SCIP_Real* lpcandssol 3642 cdef SCIP_Real* lpcandsfrac 3643 3644 PY_SCIP_CALL(SCIPgetLPBranchCands(self._scip, &lpcands, &lpcandssol, &lpcandsfrac, 3645 &nlpcands, &npriolpcands, &nfracimplvars)) 3646 3647 return ([Variable.create(lpcands[i]) for i in range(nlpcands)], [lpcandssol[i] for i in range(nlpcands)], 3648 [lpcandsfrac[i] for i in range(nlpcands)], nlpcands, npriolpcands, nfracimplvars) 3649 3650 3651 def branchVar(self, variable): 3652 """Branch on a non-continuous variable. 3653 3654 :param variable: Variable to branch on 3655 :return: tuple(downchild, eqchild, upchild) of Nodes of the left, middle and right child. Middle child only exists 3656 if branch variable is integer (it is None otherwise) 3657 3658 """ 3659 cdef SCIP_NODE* downchild 3660 cdef SCIP_NODE* eqchild 3661 cdef SCIP_NODE* upchild 3662 3663 PY_SCIP_CALL(SCIPbranchVar(self._scip, (<Variable>variable).scip_var, &downchild, &eqchild, &upchild)) 3664 return Node.create(downchild), Node.create(eqchild), Node.create(upchild) 3665 3666 3667 def branchVarVal(self, variable, value): 3668 """Branches on variable using a value which separates the domain of the variable. 3669 3670 :param variable: Variable to branch on 3671 :param value: float, value to branch on 3672 :return: tuple(downchild, eqchild, upchild) of Nodes of the left, middle and right child. Middle child only exists 3673 if branch variable is integer (it is None otherwise) 3674 3675 """ 3676 cdef SCIP_NODE* downchild 3677 cdef SCIP_NODE* eqchild 3678 cdef SCIP_NODE* upchild 3679 3680 PY_SCIP_CALL(SCIPbranchVarVal(self._scip, (<Variable>variable).scip_var, value, &downchild, &eqchild, &upchild)) 3681 3682 return Node.create(downchild), Node.create(eqchild), Node.create(upchild) 3683 3684 def calcNodeselPriority(self, Variable variable, branchdir, targetvalue): 3685 """calculates the node selection priority for moving the given variable's LP value 3686 to the given target value; 3687 this node selection priority can be given to the SCIPcreateChild() call 3688 3689 :param variable: variable on which the branching is applied 3690 :param branchdir: type of branching that was performed 3691 :param targetvalue: new value of the variable in the child node 3692 :return: node selection priority for moving the given variable's LP value to the given target value 3693 3694 """ 3695 return SCIPcalcNodeselPriority(self._scip, variable.scip_var, branchdir, targetvalue) 3696 3697 def calcChildEstimate(self, Variable variable, targetvalue): 3698 """Calculates an estimate for the objective of the best feasible solution 3699 contained in the subtree after applying the given branching; 3700 this estimate can be given to the SCIPcreateChild() call 3701 3702 :param variable: Variable to compute the estimate for 3703 :param targetvalue: new value of the variable in the child node 3704 :return: objective estimate of the best solution in the subtree after applying the given branching 3705 3706 """ 3707 return SCIPcalcChildEstimate(self._scip, variable.scip_var, targetvalue) 3708 3709 def createChild(self, nodeselprio, estimate): 3710 """Create a child node of the focus node. 3711 3712 :param nodeselprio: float, node selection priority of new node 3713 :param estimate: float, estimate for(transformed) objective value of best feasible solution in subtree 3714 :return: Node, the child which was created 3715 3716 """ 3717 cdef SCIP_NODE* child 3718 PY_SCIP_CALL(SCIPcreateChild(self._scip, &child, nodeselprio, estimate)) 3719 return Node.create(child) 3720 3721 # Diving methods (Diving is LP related) 3722 def startDive(self): 3723 """Initiates LP diving 3724 It allows the user to change the LP in several ways, solve, change again, etc, without affecting the actual LP that has. When endDive() is called, 3725 SCIP will undo all changes done and recover the LP it had before startDive 3726 """ 3727 PY_SCIP_CALL(SCIPstartDive(self._scip)) 3728 3729 def endDive(self): 3730 """Quits probing and resets bounds and constraints to the focus node's environment""" 3731 PY_SCIP_CALL(SCIPendDive(self._scip)) 3732 3733 def chgVarObjDive(self, Variable var, newobj): 3734 """changes (column) variable's objective value in current dive""" 3735 PY_SCIP_CALL(SCIPchgVarObjDive(self._scip, var.scip_var, newobj)) 3736 3737 def chgVarLbDive(self, Variable var, newbound): 3738 """changes variable's current lb in current dive""" 3739 PY_SCIP_CALL(SCIPchgVarLbDive(self._scip, var.scip_var, newbound)) 3740 3741 def chgVarUbDive(self, Variable var, newbound): 3742 """changes variable's current ub in current dive""" 3743 PY_SCIP_CALL(SCIPchgVarUbDive(self._scip, var.scip_var, newbound)) 3744 3745 def getVarLbDive(self, Variable var): 3746 """returns variable's current lb in current dive""" 3747 return SCIPgetVarLbDive(self._scip, var.scip_var) 3748 3749 def getVarUbDive(self, Variable var): 3750 """returns variable's current ub in current dive""" 3751 return SCIPgetVarUbDive(self._scip, var.scip_var) 3752 3753 def chgRowLhsDive(self, Row row, newlhs): 3754 """changes row lhs in current dive, change will be undone after diving 3755 ends, for permanent changes use SCIPchgRowLhs() 3756 """ 3757 PY_SCIP_CALL(SCIPchgRowLhsDive(self._scip, row.scip_row, newlhs)) 3758 3759 def chgRowRhsDive(self, Row row, newrhs): 3760 """changes row rhs in current dive, change will be undone after diving 3761 ends, for permanent changes use SCIPchgRowLhs() 3762 """ 3763 PY_SCIP_CALL(SCIPchgRowRhsDive(self._scip, row.scip_row, newrhs)) 3764 3765 def addRowDive(self, Row row): 3766 """adds a row to the LP in current dive""" 3767 PY_SCIP_CALL(SCIPaddRowDive(self._scip, row.scip_row)) 3768 3769 def solveDiveLP(self, itlim = -1): 3770 """solves the LP of the current dive no separation or pricing is applied 3771 no separation or pricing is applied 3772 :param itlim: maximal number of LP iterations to perform (Default value = -1, that is, no limit) 3773 returns two booleans: 3774 lperror -- if an unresolved lp error occured 3775 cutoff -- whether the LP was infeasible or the objective limit was reached 3776 """ 3777 cdef SCIP_Bool lperror 3778 cdef SCIP_Bool cutoff 3779 3780 PY_SCIP_CALL(SCIPsolveDiveLP(self._scip, itlim, &lperror, &cutoff)) 3781 return lperror, cutoff 3782 3783 def inRepropagation(self): 3784 """returns if the current node is already solved and only propagated again.""" 3785 return SCIPinRepropagation(self._scip) 3786 3787 # Probing methods (Probing is tree based) 3788 def startProbing(self): 3789 """Initiates probing, making methods SCIPnewProbingNode(), SCIPbacktrackProbing(), SCIPchgVarLbProbing(), 3790 SCIPchgVarUbProbing(), SCIPfixVarProbing(), SCIPpropagateProbing(), SCIPsolveProbingLP(), etc available 3791 """ 3792 PY_SCIP_CALL( SCIPstartProbing(self._scip) ) 3793 3794 def endProbing(self): 3795 """Quits probing and resets bounds and constraints to the focus node's environment""" 3796 PY_SCIP_CALL( SCIPendProbing(self._scip) ) 3797 3798 def newProbingNode(self): 3799 """creates a new probing sub node, whose changes can be undone by backtracking to a higher node in the 3800 probing path with a call to backtrackProbing() 3801 """ 3802 PY_SCIP_CALL( SCIPnewProbingNode(self._scip) ) 3803 3804 def backtrackProbing(self, probingdepth): 3805 """undoes all changes to the problem applied in probing up to the given probing depth 3806 :param probingdepth: probing depth of the node in the probing path that should be reactivated 3807 """ 3808 PY_SCIP_CALL( SCIPbacktrackProbing(self._scip, probingdepth) ) 3809 3810 def getProbingDepth(self): 3811 """returns the current probing depth""" 3812 return SCIPgetProbingDepth(self._scip) 3813 3814 def chgVarObjProbing(self, Variable var, newobj): 3815 """changes (column) variable's objective value during probing mode""" 3816 PY_SCIP_CALL( SCIPchgVarObjProbing(self._scip, var.scip_var, newobj) ) 3817 3818 def chgVarLbProbing(self, Variable var, lb): 3819 """changes the variable lower bound during probing mode 3820 3821 :param Variable var: variable to change bound of 3822 :param lb: new lower bound (set to None for -infinity) 3823 """ 3824 if lb is None: 3825 lb = -SCIPinfinity(self._scip) 3826 PY_SCIP_CALL(SCIPchgVarLbProbing(self._scip, var.scip_var, lb)) 3827 3828 def chgVarUbProbing(self, Variable var, ub): 3829 """changes the variable upper bound during probing mode 3830 3831 :param Variable var: variable to change bound of 3832 :param ub: new upper bound (set to None for +infinity) 3833 """ 3834 if ub is None: 3835 ub = SCIPinfinity(self._scip) 3836 PY_SCIP_CALL(SCIPchgVarUbProbing(self._scip, var.scip_var, ub)) 3837 3838 def fixVarProbing(self, Variable var, fixedval): 3839 """Fixes a variable at the current probing node.""" 3840 PY_SCIP_CALL( SCIPfixVarProbing(self._scip, var.scip_var, fixedval) ) 3841 3842 def isObjChangedProbing(self): 3843 """returns whether the objective function has changed during probing mode""" 3844 return SCIPisObjChangedProbing(self._scip) 3845 3846 def inProbing(self): 3847 """returns whether we are in probing mode; probing mode is activated via startProbing() and stopped via endProbing()""" 3848 return SCIPinProbing(self._scip) 3849 3850 def solveProbingLP(self, itlim = -1): 3851 """solves the LP at the current probing node (cannot be applied at preprocessing stage) 3852 no separation or pricing is applied 3853 :param itlim: maximal number of LP iterations to perform (Default value = -1, that is, no limit) 3854 returns two booleans: 3855 lperror -- if an unresolved lp error occured 3856 cutoff -- whether the LP was infeasible or the objective limit was reached 3857 """ 3858 cdef SCIP_Bool lperror 3859 cdef SCIP_Bool cutoff 3860 3861 PY_SCIP_CALL( SCIPsolveProbingLP(self._scip, itlim, &lperror, &cutoff) ) 3862 return lperror, cutoff 3863 3864 def applyCutsProbing(self): 3865 """applies the cuts in the separation storage to the LP and clears the storage afterwards; 3866 this method can only be applied during probing; the user should resolve the probing LP afterwards 3867 in order to get a new solution 3868 returns: 3869 cutoff -- whether an empty domain was created 3870 """ 3871 cdef SCIP_Bool cutoff 3872 3873 PY_SCIP_CALL( SCIPapplyCutsProbing(self._scip, &cutoff) ) 3874 return cutoff 3875 3876 def propagateProbing(self, maxproprounds): 3877 """applies domain propagation on the probing sub problem, that was changed after SCIPstartProbing() was called; 3878 the propagated domains of the variables can be accessed with the usual bound accessing calls SCIPvarGetLbLocal() 3879 and SCIPvarGetUbLocal(); the propagation is only valid locally, i.e. the local bounds as well as the changed 3880 bounds due to SCIPchgVarLbProbing(), SCIPchgVarUbProbing(), and SCIPfixVarProbing() are used for propagation 3881 :param maxproprounds: maximal number of propagation rounds (Default value = -1, that is, no limit) 3882 returns: 3883 cutoff -- whether the probing node can be cutoff 3884 ndomredsfound -- number of domain reductions found 3885 """ 3886 cdef SCIP_Bool cutoff 3887 cdef SCIP_Longint ndomredsfound 3888 3889 PY_SCIP_CALL( SCIPpropagateProbing(self._scip, maxproprounds, &cutoff, &ndomredsfound) ) 3890 return cutoff, ndomredsfound 3891 3892 def interruptSolve(self): 3893 """Interrupt the solving process as soon as possible.""" 3894 PY_SCIP_CALL(SCIPinterruptSolve(self._scip)) 3895 3896 def restartSolve(self): 3897 """Restarts the solving process as soon as possible.""" 3898 PY_SCIP_CALL(SCIPrestartSolve(self._scip)) 3899 3900 # Solution functions 3901 3902 def writeLP(self, filename="LP.lp"): 3903 """writes current LP to a file 3904 :param filename: file name (Default value = "LP.lp") 3905 """ 3906 PY_SCIP_CALL( SCIPwriteLP(self._scip, str_conversion(filename)) ) 3907 3908 def createSol(self, Heur heur = None): 3909 """Create a new primal solution. 3910 3911 :param Heur heur: heuristic that found the solution (Default value = None) 3912 3913 """ 3914 cdef SCIP_HEUR* _heur 3915 cdef SCIP_SOL* _sol 3916 3917 if isinstance(heur, Heur): 3918 n = str_conversion(heur.name) 3919 _heur = SCIPfindHeur(self._scip, n) 3920 else: 3921 _heur = NULL 3922 PY_SCIP_CALL(SCIPcreateSol(self._scip, &_sol, _heur)) 3923 solution = Solution.create(self._scip, _sol) 3924 return solution 3925 3926 def printBestSol(self, write_zeros=False): 3927 """Prints the best feasible primal solution.""" 3928 PY_SCIP_CALL(SCIPprintBestSol(self._scip, NULL, write_zeros)) 3929 3930 def printSol(self, Solution solution=None, write_zeros=False): 3931 """Print the given primal solution. 3932 3933 Keyword arguments: 3934 solution -- solution to print 3935 write_zeros -- include variables that are set to zero 3936 """ 3937 if solution is None: 3938 PY_SCIP_CALL(SCIPprintSol(self._scip, NULL, NULL, write_zeros)) 3939 else: 3940 PY_SCIP_CALL(SCIPprintSol(self._scip, solution.sol, NULL, write_zeros)) 3941 3942 def writeBestSol(self, filename="origprob.sol", write_zeros=False): 3943 """Write the best feasible primal solution to a file. 3944 3945 Keyword arguments: 3946 filename -- name of the output file 3947 write_zeros -- include variables that are set to zero 3948 """ 3949 # use this doubled opening pattern to ensure that IOErrors are 3950 # triggered early and in Python not in C,Cython or SCIP. 3951 with open(filename, "w") as f: 3952 cfile = fdopen(f.fileno(), "w") 3953 PY_SCIP_CALL(SCIPprintBestSol(self._scip, cfile, write_zeros)) 3954 3955 def writeSol(self, Solution solution, filename="origprob.sol", write_zeros=False): 3956 """Write the given primal solution to a file. 3957 3958 Keyword arguments: 3959 solution -- solution to write 3960 filename -- name of the output file 3961 write_zeros -- include variables that are set to zero 3962 """ 3963 # use this doubled opening pattern to ensure that IOErrors are 3964 # triggered early and in Python not in C,Cython or SCIP. 3965 with open(filename, "w") as f: 3966 cfile = fdopen(f.fileno(), "w") 3967 PY_SCIP_CALL(SCIPprintSol(self._scip, solution.sol, cfile, write_zeros)) 3968 3969 # perhaps this should not be included as it implements duplicated functionality 3970 # (as does it's namesake in SCIP) 3971 def readSol(self, filename): 3972 """Reads a given solution file, problem has to be transformed in advance. 3973 3974 Keyword arguments: 3975 filename -- name of the input file 3976 """ 3977 fn = str_conversion(filename) 3978 PY_SCIP_CALL(SCIPreadSol(self._scip, fn)) 3979 3980 def readSolFile(self, filename): 3981 """Reads a given solution file. 3982 3983 Solution is created but not added to storage/the model. 3984 Use 'addSol' OR 'trySol' to add it. 3985 3986 Keyword arguments: 3987 filename -- name of the input file 3988 """ 3989 cdef SCIP_Bool partial 3990 cdef SCIP_Bool error 3991 cdef SCIP_Bool stored 3992 cdef Solution solution 3993 3994 fn = str_conversion(filename) 3995 solution = self.createSol() 3996 PY_SCIP_CALL(SCIPreadSolFile(self._scip, fn, solution.sol, False, &partial, &error)) 3997 if error: 3998 raise Exception("SCIP: reading solution from file failed!") 3999 4000 return solution 4001 4002 def setSolVal(self, Solution solution, Variable var, val): 4003 """Set a variable in a solution. 4004 4005 :param Solution solution: solution to be modified 4006 :param Variable var: variable in the solution 4007 :param val: value of the specified variable 4008 4009 """ 4010 cdef SCIP_SOL* _sol 4011 _sol = <SCIP_SOL*>solution.sol 4012 PY_SCIP_CALL(SCIPsetSolVal(self._scip, _sol, var.scip_var, val)) 4013 4014 def trySol(self, Solution solution, printreason=True, completely=False, checkbounds=True, checkintegrality=True, checklprows=True, free=True): 4015 """Check given primal solution for feasibility and try to add it to the storage. 4016 4017 :param Solution solution: solution to store 4018 :param printreason: should all reasons of violations be printed? (Default value = True) 4019 :param completely: should all violation be checked? (Default value = False) 4020 :param checkbounds: should the bounds of the variables be checked? (Default value = True) 4021 :param checkintegrality: has integrality to be checked? (Default value = True) 4022 :param checklprows: have current LP rows (both local and global) to be checked? (Default value = True) 4023 :param free: should solution be freed? (Default value = True) 4024 4025 """ 4026 cdef SCIP_Bool stored 4027 if free: 4028 PY_SCIP_CALL(SCIPtrySolFree(self._scip, &solution.sol, printreason, completely, checkbounds, checkintegrality, checklprows, &stored)) 4029 else: 4030 PY_SCIP_CALL(SCIPtrySol(self._scip, solution.sol, printreason, completely, checkbounds, checkintegrality, checklprows, &stored)) 4031 return stored 4032 4033 def checkSol(self, Solution solution, printreason=True, completely=False, checkbounds=True, checkintegrality=True, checklprows=True, original=False): 4034 """Check given primal solution for feasibility without adding it to the storage. 4035 4036 :param Solution solution: solution to store 4037 :param printreason: should all reasons of violations be printed? (Default value = True) 4038 :param completely: should all violation be checked? (Default value = False) 4039 :param checkbounds: should the bounds of the variables be checked? (Default value = True) 4040 :param checkintegrality: has integrality to be checked? (Default value = True) 4041 :param checklprows: have current LP rows (both local and global) to be checked? (Default value = True) 4042 :param original: must the solution be checked against the original problem (Default value = False) 4043 4044 """ 4045 cdef SCIP_Bool feasible 4046 if original: 4047 PY_SCIP_CALL(SCIPcheckSolOrig(self._scip, solution.sol, &feasible, printreason, completely)) 4048 else: 4049 PY_SCIP_CALL(SCIPcheckSol(self._scip, solution.sol, printreason, completely, checkbounds, checkintegrality, checklprows, &feasible)) 4050 return feasible 4051 4052 def addSol(self, Solution solution, free=True): 4053 """Try to add a solution to the storage. 4054 4055 :param Solution solution: solution to store 4056 :param free: should solution be freed afterwards? (Default value = True) 4057 4058 """ 4059 cdef SCIP_Bool stored 4060 if free: 4061 PY_SCIP_CALL(SCIPaddSolFree(self._scip, &solution.sol, &stored)) 4062 else: 4063 PY_SCIP_CALL(SCIPaddSol(self._scip, solution.sol, &stored)) 4064 return stored 4065 4066 def freeSol(self, Solution solution): 4067 """Free given solution 4068 4069 :param Solution solution: solution to be freed 4070 4071 """ 4072 PY_SCIP_CALL(SCIPfreeSol(self._scip, &solution.sol)) 4073 4074 def getSols(self): 4075 """Retrieve list of all feasible primal solutions stored in the solution storage.""" 4076 cdef SCIP_SOL** _sols 4077 cdef SCIP_SOL* _sol 4078 _sols = SCIPgetSols(self._scip) 4079 nsols = SCIPgetNSols(self._scip) 4080 sols = [] 4081 4082 for i in range(nsols): 4083 sols.append(Solution.create(self._scip, _sols[i])) 4084 4085 return sols 4086 4087 def getBestSol(self): 4088 """Retrieve currently best known feasible primal solution.""" 4089 self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip)) 4090 return self._bestSol 4091 4092 def getSolObjVal(self, Solution sol, original=True): 4093 """Retrieve the objective value of the solution. 4094 4095 :param Solution sol: solution 4096 :param original: objective value in original space (Default value = True) 4097 4098 """ 4099 if sol == None: 4100 sol = Solution.create(self._scip, NULL) 4101 if original: 4102 objval = SCIPgetSolOrigObj(self._scip, sol.sol) 4103 else: 4104 objval = SCIPgetSolTransObj(self._scip, sol.sol) 4105 return objval 4106 4107 def getObjVal(self, original=True): 4108 """Retrieve the objective value of value of best solution. 4109 Can only be called after solving is completed. 4110 4111 :param original: objective value in original space (Default value = True) 4112 4113 """ 4114 if not self.getStage() >= SCIP_STAGE_SOLVING: 4115 raise Warning("method cannot be called before problem is solved") 4116 return self.getSolObjVal(self._bestSol, original) 4117 4118 def getSolVal(self, Solution sol, Expr expr): 4119 """Retrieve value of given variable or expression in the given solution or in 4120 the LP/pseudo solution if sol == None 4121 4122 :param Solution sol: solution 4123 :param Expr expr: polynomial expression to query the value of 4124 4125 Note: a variable is also an expression 4126 """ 4127 if sol == None: 4128 sol = Solution.create(self._scip, NULL) 4129 if isinstance(expr, Variable): 4130 var = <Variable> expr 4131 return SCIPgetSolVal(self._scip, sol.sol, var.scip_var) 4132 else: 4133 return expr._evaluate(sol) 4134 4135 def getVal(self, Expr expr): 4136 """Retrieve the value of the given variable or expression in the best known solution. 4137 Can only be called after solving is completed. 4138 4139 :param Expr expr: polynomial expression to query the value of 4140 4141 Note: a variable is also an expression 4142 """ 4143 if not self.getStage() >= SCIP_STAGE_SOLVING: 4144 raise Warning("method cannot be called before problem is solved") 4145 return self.getSolVal(self._bestSol, expr) 4146 4147 def getPrimalbound(self): 4148 """Retrieve the best primal bound.""" 4149 return SCIPgetPrimalbound(self._scip) 4150 4151 def getDualbound(self): 4152 """Retrieve the best dual bound.""" 4153 return SCIPgetDualbound(self._scip) 4154 4155 def getDualboundRoot(self): 4156 """Retrieve the best root dual bound.""" 4157 return SCIPgetDualboundRoot(self._scip) 4158 4159 def writeName(self, Variable var): 4160 """Write the name of the variable to the std out. 4161 4162 :param Variable var: variable 4163 4164 """ 4165 PY_SCIP_CALL(SCIPwriteVarName(self._scip, NULL, var.scip_var, False)) 4166 4167 def getStage(self): 4168 """Retrieve current SCIP stage""" 4169 return SCIPgetStage(self._scip) 4170 4171 def getStatus(self): 4172 """Retrieve solution status.""" 4173 cdef SCIP_STATUS stat = SCIPgetStatus(self._scip) 4174 if stat == SCIP_STATUS_OPTIMAL: 4175 return "optimal" 4176 elif stat == SCIP_STATUS_TIMELIMIT: 4177 return "timelimit" 4178 elif stat == SCIP_STATUS_INFEASIBLE: 4179 return "infeasible" 4180 elif stat == SCIP_STATUS_UNBOUNDED: 4181 return "unbounded" 4182 elif stat == SCIP_STATUS_USERINTERRUPT: 4183 return "userinterrupt" 4184 elif stat == SCIP_STATUS_INFORUNBD: 4185 return "inforunbd" 4186 elif stat == SCIP_STATUS_NODELIMIT: 4187 return "nodelimit" 4188 elif stat == SCIP_STATUS_TOTALNODELIMIT: 4189 return "totalnodelimit" 4190 elif stat == SCIP_STATUS_STALLNODELIMIT: 4191 return "stallnodelimit" 4192 elif stat == SCIP_STATUS_GAPLIMIT: 4193 return "gaplimit" 4194 elif stat == SCIP_STATUS_MEMLIMIT: 4195 return "memlimit" 4196 elif stat == SCIP_STATUS_SOLLIMIT: 4197 return "sollimit" 4198 elif stat == SCIP_STATUS_BESTSOLLIMIT: 4199 return "bestsollimit" 4200 elif stat == SCIP_STATUS_RESTARTLIMIT: 4201 return "restartlimit" 4202 else: 4203 return "unknown" 4204 4205 def getObjectiveSense(self): 4206 """Retrieve objective sense.""" 4207 cdef SCIP_OBJSENSE sense = SCIPgetObjsense(self._scip) 4208 if sense == SCIP_OBJSENSE_MAXIMIZE: 4209 return "maximize" 4210 elif sense == SCIP_OBJSENSE_MINIMIZE: 4211 return "minimize" 4212 else: 4213 return "unknown" 4214 4215 def catchEvent(self, eventtype, Eventhdlr eventhdlr): 4216 """catches a global (not variable or row dependent) event""" 4217 cdef SCIP_EVENTHDLR* _eventhdlr 4218 if isinstance(eventhdlr, Eventhdlr): 4219 n = str_conversion(eventhdlr.name) 4220 _eventhdlr = SCIPfindEventhdlr(self._scip, n) 4221 else: 4222 raise Warning("event handler not found") 4223 PY_SCIP_CALL(SCIPcatchEvent(self._scip, eventtype, _eventhdlr, NULL, NULL)) 4224 4225 def dropEvent(self, eventtype, Eventhdlr eventhdlr): 4226 """drops a global event (stops to track event)""" 4227 cdef SCIP_EVENTHDLR* _eventhdlr 4228 if isinstance(eventhdlr, Eventhdlr): 4229 n = str_conversion(eventhdlr.name) 4230 _eventhdlr = SCIPfindEventhdlr(self._scip, n) 4231 else: 4232 raise Warning("event handler not found") 4233 PY_SCIP_CALL(SCIPdropEvent(self._scip, eventtype, _eventhdlr, NULL, -1)) 4234 4235 def catchVarEvent(self, Variable var, eventtype, Eventhdlr eventhdlr): 4236 """catches an objective value or domain change event on the given transformed variable""" 4237 cdef SCIP_EVENTHDLR* _eventhdlr 4238 if isinstance(eventhdlr, Eventhdlr): 4239 n = str_conversion(eventhdlr.name) 4240 _eventhdlr = SCIPfindEventhdlr(self._scip, n) 4241 else: 4242 raise Warning("event handler not found") 4243 PY_SCIP_CALL(SCIPcatchVarEvent(self._scip, var.scip_var, eventtype, _eventhdlr, NULL, NULL)) 4244 4245 def dropVarEvent(self, Variable var, eventtype, Eventhdlr eventhdlr): 4246 """drops an objective value or domain change event (stops to track event) on the given transformed variable""" 4247 cdef SCIP_EVENTHDLR* _eventhdlr 4248 if isinstance(eventhdlr, Eventhdlr): 4249 n = str_conversion(eventhdlr.name) 4250 _eventhdlr = SCIPfindEventhdlr(self._scip, n) 4251 else: 4252 raise Warning("event handler not found") 4253 PY_SCIP_CALL(SCIPdropVarEvent(self._scip, var.scip_var, eventtype, _eventhdlr, NULL, -1)) 4254 4255 def catchRowEvent(self, Row row, eventtype, Eventhdlr eventhdlr): 4256 """catches a row coefficient, constant, or side change event on the given row""" 4257 cdef SCIP_EVENTHDLR* _eventhdlr 4258 if isinstance(eventhdlr, Eventhdlr): 4259 n = str_conversion(eventhdlr.name) 4260 _eventhdlr = SCIPfindEventhdlr(self._scip, n) 4261 else: 4262 raise Warning("event handler not found") 4263 PY_SCIP_CALL(SCIPcatchRowEvent(self._scip, row.scip_row, eventtype, _eventhdlr, NULL, NULL)) 4264 4265 def dropRowEvent(self, Row row, eventtype, Eventhdlr eventhdlr): 4266 """drops a row coefficient, constant, or side change event (stops to track event) on the given row""" 4267 cdef SCIP_EVENTHDLR* _eventhdlr 4268 if isinstance(eventhdlr, Eventhdlr): 4269 n = str_conversion(eventhdlr.name) 4270 _eventhdlr = SCIPfindEventhdlr(self._scip, n) 4271 else: 4272 raise Warning("event handler not found") 4273 PY_SCIP_CALL(SCIPdropRowEvent(self._scip, row.scip_row, eventtype, _eventhdlr, NULL, -1)) 4274 4275 # Statistic Methods 4276 4277 def printStatistics(self): 4278 """Print statistics.""" 4279 PY_SCIP_CALL(SCIPprintStatistics(self._scip, NULL)) 4280 4281 def writeStatistics(self, filename="origprob.stats"): 4282 """Write statistics to a file. 4283 4284 Keyword arguments: 4285 filename -- name of the output file 4286 """ 4287 # use this doubled opening pattern to ensure that IOErrors are 4288 # triggered early and in Python not in C,Cython or SCIP. 4289 with open(filename, "w") as f: 4290 cfile = fdopen(f.fileno(), "w") 4291 PY_SCIP_CALL(SCIPprintStatistics(self._scip, cfile)) 4292 4293 def getNLPs(self): 4294 """gets total number of LPs solved so far""" 4295 return SCIPgetNLPs(self._scip) 4296 4297 # Verbosity Methods 4298 4299 def hideOutput(self, quiet = True): 4300 """Hide the output. 4301 4302 :param quiet: hide output? (Default value = True) 4303 4304 """ 4305 SCIPsetMessagehdlrQuiet(self._scip, quiet) 4306 4307 # Output Methods 4308 4309 def redirectOutput(self): 4310 """Send output to python instead of terminal.""" 4311 4312 cdef SCIP_MESSAGEHDLR *myMessageHandler 4313 4314 PY_SCIP_CALL(SCIPmessagehdlrCreate(&myMessageHandler, False, NULL, False, relayMessage, relayMessage, relayMessage, NULL, NULL)) 4315 PY_SCIP_CALL(SCIPsetMessagehdlr(self._scip, myMessageHandler)) 4316 SCIPmessageSetErrorPrinting(relayErrorMessage, NULL) 4317 4318 # Parameter Methods 4319 4320 def setBoolParam(self, name, value): 4321 """Set a boolean-valued parameter. 4322 4323 :param name: name of parameter 4324 :param value: value of parameter 4325 4326 """ 4327 n = str_conversion(name) 4328 PY_SCIP_CALL(SCIPsetBoolParam(self._scip, n, value)) 4329 4330 def setIntParam(self, name, value): 4331 """Set an int-valued parameter. 4332 4333 :param name: name of parameter 4334 :param value: value of parameter 4335 4336 """ 4337 n = str_conversion(name) 4338 PY_SCIP_CALL(SCIPsetIntParam(self._scip, n, value)) 4339 4340 def setLongintParam(self, name, value): 4341 """Set a long-valued parameter. 4342 4343 :param name: name of parameter 4344 :param value: value of parameter 4345 4346 """ 4347 n = str_conversion(name) 4348 PY_SCIP_CALL(SCIPsetLongintParam(self._scip, n, value)) 4349 4350 def setRealParam(self, name, value): 4351 """Set a real-valued parameter. 4352 4353 :param name: name of parameter 4354 :param value: value of parameter 4355 4356 """ 4357 n = str_conversion(name) 4358 PY_SCIP_CALL(SCIPsetRealParam(self._scip, n, value)) 4359 4360 def setCharParam(self, name, value): 4361 """Set a char-valued parameter. 4362 4363 :param name: name of parameter 4364 :param value: value of parameter 4365 4366 """ 4367 n = str_conversion(name) 4368 PY_SCIP_CALL(SCIPsetCharParam(self._scip, n, ord(value))) 4369 4370 def setStringParam(self, name, value): 4371 """Set a string-valued parameter. 4372 4373 :param name: name of parameter 4374 :param value: value of parameter 4375 4376 """ 4377 n = str_conversion(name) 4378 v = str_conversion(value) 4379 PY_SCIP_CALL(SCIPsetStringParam(self._scip, n, v)) 4380 4381 def setParam(self, name, value): 4382 """Set a parameter with value in int, bool, real, long, char or str. 4383 4384 :param name: name of parameter 4385 :param value: value of parameter 4386 """ 4387 cdef SCIP_PARAM* param 4388 4389 n = str_conversion(name) 4390 param = SCIPgetParam(self._scip, n) 4391 4392 if param == NULL: 4393 raise KeyError("Not a valid parameter name") 4394 4395 paramtype = SCIPparamGetType(param) 4396 4397 if paramtype == SCIP_PARAMTYPE_BOOL: 4398 PY_SCIP_CALL(SCIPsetBoolParam(self._scip, n, bool(int(value)))) 4399 elif paramtype == SCIP_PARAMTYPE_INT: 4400 PY_SCIP_CALL(SCIPsetIntParam(self._scip, n, int(value))) 4401 elif paramtype == SCIP_PARAMTYPE_LONGINT: 4402 PY_SCIP_CALL(SCIPsetLongintParam(self._scip, n, int(value))) 4403 elif paramtype == SCIP_PARAMTYPE_REAL: 4404 PY_SCIP_CALL(SCIPsetRealParam(self._scip, n, float(value))) 4405 elif paramtype == SCIP_PARAMTYPE_CHAR: 4406 PY_SCIP_CALL(SCIPsetCharParam(self._scip, n, ord(value))) 4407 elif paramtype == SCIP_PARAMTYPE_STRING: 4408 v = str_conversion(value) 4409 PY_SCIP_CALL(SCIPsetStringParam(self._scip, n, v)) 4410 4411 4412 def getParam(self, name): 4413 """Get the value of a parameter of type 4414 int, bool, real, long, char or str. 4415 4416 :param name: name of parameter 4417 """ 4418 cdef SCIP_PARAM* param 4419 4420 n = str_conversion(name) 4421 param = SCIPgetParam(self._scip, n) 4422 4423 if param == NULL: 4424 raise KeyError("Not a valid parameter name") 4425 4426 paramtype = SCIPparamGetType(param) 4427 4428 if paramtype == SCIP_PARAMTYPE_BOOL: 4429 return SCIPparamGetBool(param) 4430 elif paramtype == SCIP_PARAMTYPE_INT: 4431 return SCIPparamGetInt(param) 4432 elif paramtype == SCIP_PARAMTYPE_LONGINT: 4433 return SCIPparamGetLongint(param) 4434 elif paramtype == SCIP_PARAMTYPE_REAL: 4435 return SCIPparamGetReal(param) 4436 elif paramtype == SCIP_PARAMTYPE_CHAR: 4437 return chr(SCIPparamGetChar(param)) 4438 elif paramtype == SCIP_PARAMTYPE_STRING: 4439 return SCIPparamGetString(param).decode('utf-8') 4440 4441 def getParams(self): 4442 """Gets the values of all parameters as a dict mapping parameter names 4443 to their values.""" 4444 cdef SCIP_PARAM** params 4445 4446 params = SCIPgetParams(self._scip) 4447 result = {} 4448 for i in range(SCIPgetNParams(self._scip)): 4449 name = SCIPparamGetName(params[i]).decode('utf-8') 4450 result[name] = self.getParam(name) 4451 return result 4452 4453 def setParams(self, params): 4454 """Sets multiple parameters at once. 4455 4456 :param params: dict mapping parameter names to their values. 4457 """ 4458 for name, value in params.items(): 4459 self.setParam(name, value) 4460 4461 def readParams(self, file): 4462 """Read an external parameter file. 4463 4464 :param file: file to be read 4465 4466 """ 4467 absfile = str_conversion(abspath(file)) 4468 PY_SCIP_CALL(SCIPreadParams(self._scip, absfile)) 4469 4470 def writeParams(self, filename='param.set', comments = True, onlychanged = True): 4471 """Write parameter settings to an external file. 4472 4473 :param filename: file to be written (Default value = 'param.set') 4474 :param comments: write parameter descriptions as comments? (Default value = True) 4475 :param onlychanged: write only modified parameters (Default value = True) 4476 4477 """ 4478 fn = str_conversion(filename) 4479 PY_SCIP_CALL(SCIPwriteParams(self._scip, fn, comments, onlychanged)) 4480 print('wrote parameter settings to file ' + filename) 4481 4482 def resetParam(self, name): 4483 """Reset parameter setting to its default value 4484 4485 :param name: parameter to reset 4486 4487 """ 4488 n = str_conversion(name) 4489 PY_SCIP_CALL(SCIPresetParam(self._scip, n)) 4490 4491 def resetParams(self): 4492 """Reset parameter settings to their default values""" 4493 PY_SCIP_CALL(SCIPresetParams(self._scip)) 4494 4495 def setEmphasis(self, paraemphasis, quiet = True): 4496 """Set emphasis settings 4497 4498 :param paraemphasis: emphasis to set 4499 :param quiet: hide output? (Default value = True) 4500 4501 """ 4502 PY_SCIP_CALL(SCIPsetEmphasis(self._scip, paraemphasis, quiet)) 4503 4504 def readProblem(self, filename, extension = None): 4505 """Read a problem instance from an external file. 4506 4507 :param filename: problem file name 4508 :param extension: specify file extension/type (Default value = None) 4509 4510 """ 4511 absfile = str_conversion(abspath(filename)) 4512 if extension is None: 4513 PY_SCIP_CALL(SCIPreadProb(self._scip, absfile, NULL)) 4514 else: 4515 extension = str_conversion(extension) 4516 PY_SCIP_CALL(SCIPreadProb(self._scip, absfile, extension)) 4517 4518 # Counting functions 4519 4520 def count(self): 4521 """Counts the number of feasible points of problem.""" 4522 PY_SCIP_CALL(SCIPcount(self._scip)) 4523 4524 def getNCountedSols(self): 4525 """Get number of feasible solution.""" 4526 cdef SCIP_Bool valid 4527 cdef SCIP_Longint nsols 4528 4529 nsols = SCIPgetNCountedSols(self._scip, &valid) 4530 if not valid: 4531 print('total number of solutions found is not valid!') 4532 return nsols 4533 4534 def setParamsCountsols(self): 4535 """Sets SCIP parameters such that a valid counting process is possible.""" 4536 PY_SCIP_CALL(SCIPsetParamsCountsols(self._scip)) 4537 4538 def freeReoptSolve(self): 4539 """Frees all solution process data and prepares for reoptimization""" 4540 PY_SCIP_CALL(SCIPfreeReoptSolve(self._scip)) 4541 4542 def chgReoptObjective(self, coeffs, sense = 'minimize'): 4543 """Establish the objective function as a linear expression. 4544 4545 :param coeffs: the coefficients 4546 :param sense: the objective sense (Default value = 'minimize') 4547 4548 """ 4549 4550 cdef SCIP_OBJSENSE objsense 4551 4552 if sense == "minimize": 4553 objsense = SCIP_OBJSENSE_MINIMIZE 4554 elif sense == "maximize": 4555 objsense = SCIP_OBJSENSE_MAXIMIZE 4556 else: 4557 raise Warning("unrecognized optimization sense: %s" % sense) 4558 4559 assert isinstance(coeffs, Expr), "given coefficients are not Expr but %s" % coeffs.__class__.__name__ 4560 4561 if coeffs.degree() > 1: 4562 raise ValueError("Nonlinear objective functions are not supported!") 4563 if coeffs[CONST] != 0.0: 4564 raise ValueError("Constant offsets in objective are not supported!") 4565 4566 cdef SCIP_VAR** _vars 4567 cdef int _nvars 4568 _vars = SCIPgetOrigVars(self._scip) 4569 _nvars = SCIPgetNOrigVars(self._scip) 4570 _coeffs = <SCIP_Real*> malloc(_nvars * sizeof(SCIP_Real)) 4571 4572 for i in range(_nvars): 4573 _coeffs[i] = 0.0 4574 4575 for term, coef in coeffs.terms.items(): 4576 # avoid CONST term of Expr 4577 if term != CONST: 4578 assert len(term) == 1 4579 var = <Variable>term[0] 4580 for i in range(_nvars): 4581 if _vars[i] == var.scip_var: 4582 _coeffs[i] = coef 4583 4584 PY_SCIP_CALL(SCIPchgReoptObjective(self._scip, objsense, _vars, &_coeffs[0], _nvars)) 4585 4586 free(_coeffs) 4587 4588 def chgVarBranchPriority(self, Variable var, priority): 4589 """Sets the branch priority of the variable. 4590 Variables with higher branch priority are always preferred to variables with lower priority in selection of branching variable. 4591 4592 :param Variable var: variable to change priority of 4593 :param priority: the new priority of the variable (the default branching priority is 0) 4594 """ 4595 assert isinstance(var, Variable), "The given variable is not a pyvar, but %s" % var.__class__.__name__ 4596 PY_SCIP_CALL(SCIPchgVarBranchPriority(self._scip, var.scip_var, priority)) 4597 4598# debugging memory management 4599def is_memory_freed(): 4600 return BMSgetMemoryUsed() == 0 4601 4602def print_memory_in_use(): 4603 BMScheckEmptyMemory() 4604