Skip to content

Commit 1932131

Browse files
Strong Branching (#873)
* Add strong branching wrappers * Update CHANGELOG * Add new wrappers and unfinished tests * Update strong branching test * Fix typo * Update CHANGELOG * Add test for other features in branching rule * Fix spelling errors * Remove commented out line * Add changes from joao * Add assert to ensure safe branching * Change names to copy those of SCIp interface --------- Co-authored-by: João Dionísio <[email protected]>
1 parent c0f7100 commit 1932131

File tree

4 files changed

+365
-2
lines changed

4 files changed

+365
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
### Added
55
- Created Statistics class
66
- Added parser to read .stats file
7+
- Added Python definitions and wrappers for SCIPstartStrongbranch, SCIPendStrongbranch SCIPgetBranchScoreMultiple,
8+
SCIPgetVarStrongbranchInt, SCIPupdateVarPseudocost, SCIPgetVarStrongbranchFrac, SCIPcolGetAge,
9+
SCIPgetVarStrongbranchLast, SCIPgetVarStrongbranchNode, SCIPallColsInLP, SCIPcolGetAge
710
### Fixed
811
- Fixed too strict getObjVal, getVal check
912
### Changed

src/pyscipopt/scip.pxd

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,7 @@ cdef extern from "scip/scip.h":
788788
int SCIPgetNLPCols(SCIP* scip)
789789
SCIP_COL** SCIPgetLPCols(SCIP *scip)
790790
SCIP_ROW** SCIPgetLPRows(SCIP *scip)
791+
SCIP_Bool SCIPallColsInLP(SCIP* scip)
791792

792793
# Cutting Plane Methods
793794
SCIP_RETCODE SCIPaddPoolCut(SCIP* scip, SCIP_ROW* row)
@@ -1258,7 +1259,15 @@ cdef extern from "scip/scip.h":
12581259
SCIP_RETCODE SCIPgetLPBranchCands(SCIP* scip, SCIP_VAR*** lpcands, SCIP_Real** lpcandssol,
12591260
SCIP_Real** lpcandsfrac, int* nlpcands, int* npriolpcands, int* nfracimplvars)
12601261
SCIP_RETCODE SCIPgetPseudoBranchCands(SCIP* scip, SCIP_VAR*** pseudocands, int* npseudocands, int* npriopseudocands)
1261-
1262+
SCIP_RETCODE SCIPstartStrongbranch(SCIP* scip, SCIP_Bool enablepropogation)
1263+
SCIP_RETCODE SCIPendStrongbranch(SCIP* scip)
1264+
SCIP_RETCODE SCIPgetVarStrongbranchLast(SCIP* scip, SCIP_VAR* var, SCIP_Real* down, SCIP_Real* up, SCIP_Bool* downvalid, SCIP_Bool* upvalid, SCIP_Real* solval, SCIP_Real* lpobjval)
1265+
SCIP_Longint SCIPgetVarStrongbranchNode(SCIP* scip, SCIP_VAR* var)
1266+
SCIP_Real SCIPgetBranchScoreMultiple(SCIP* scip, SCIP_VAR* var, int nchildren, SCIP_Real* gains)
1267+
SCIP_RETCODE SCIPgetVarStrongbranchWithPropagation(SCIP* scip, SCIP_VAR* var, SCIP_Real solval, SCIP_Real lpobjval, int itlim, int maxproprounds, SCIP_Real* down, SCIP_Real* up, SCIP_Bool* downvalid, SCIP_Bool* upvalid, SCIP_Longint* ndomredsdown, SCIP_Longint* ndomredsup, SCIP_Bool* downinf, SCIP_Bool* upinf, SCIP_Bool* downconflict, SCIP_Bool* upconflict, SCIP_Bool* lperror, SCIP_Real* newlbs, SCIP_Real* newubs)
1268+
SCIP_RETCODE SCIPgetVarStrongbranchInt(SCIP* scip, SCIP_VAR* var, int itlim, SCIP_Bool idempotent, SCIP_Real* down, SCIP_Real* up, SCIP_Bool* downvalid, SCIP_Bool* upvalid, SCIP_Bool* downinf, SCIP_Bool* upinf, SCIP_Bool* downconflict, SCIP_Bool* upconflict, SCIP_Bool* lperror)
1269+
SCIP_RETCODE SCIPupdateVarPseudocost(SCIP* scip, SCIP_VAR* var, SCIP_Real solvaldelta, SCIP_Real objdelta, SCIP_Real weight)
1270+
SCIP_RETCODE SCIPgetVarStrongbranchFrac(SCIP* scip, SCIP_VAR* var, int itlim, SCIP_Bool idempotent, SCIP_Real* down, SCIP_Real* up, SCIP_Bool* downvalid, SCIP_Bool* upvalid, SCIP_Bool* downinf, SCIP_Bool* upinf, SCIP_Bool* downconflict, SCIP_Bool* upconflict, SCIP_Bool* lperror)
12621271

12631272
# Numerical Methods
12641273
SCIP_Real SCIPinfinity(SCIP* scip)
@@ -1841,6 +1850,7 @@ cdef extern from "scip/pub_lp.h":
18411850
int SCIPcolGetNNonz(SCIP_COL* col)
18421851
SCIP_ROW** SCIPcolGetRows(SCIP_COL* col)
18431852
SCIP_Real* SCIPcolGetVals(SCIP_COL* col)
1853+
int SCIPcolGetAge(SCIP_COL* col)
18441854
int SCIPcolGetIndex(SCIP_COL* col)
18451855
SCIP_Real SCIPcolGetObj(SCIP_COL *col)
18461856

src/pyscipopt/scip.pxi

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,11 @@ cdef class Column:
417417
"""gets objective value coefficient of a column"""
418418
return SCIPcolGetObj(self.scip_col)
419419

420+
def getAge(self):
421+
"""Gets the age of the column, i.e., the total number of successive times a column was in the LP
422+
and was 0.0 in the solution"""
423+
return SCIPcolGetAge(self.scip_col)
424+
420425
def __hash__(self):
421426
return hash(<size_t>self.scip_col)
422427

@@ -518,6 +523,10 @@ cdef class Row:
518523
cdef SCIP_Real* vals = SCIProwGetVals(self.scip_row)
519524
return [vals[i] for i in range(self.getNNonz())]
520525

526+
def getAge(self):
527+
"""Gets the age of the row. (The consecutive times the row has been non-active in the LP)"""
528+
return SCIProwGetAge(self.scip_row)
529+
521530
def getNorm(self):
522531
"""gets Euclidean norm of row vector """
523532
return SCIProwGetNorm(self.scip_row)
@@ -891,6 +900,10 @@ cdef class Variable(Expr):
891900
"""Retrieve the current LP solution value of variable"""
892901
return SCIPvarGetLPSol(self.scip_var)
893902

903+
def getAvgSol(self):
904+
"""Get the weighted average solution of variable in all feasible primal solutions found"""
905+
return SCIPvarGetAvgSol(self.scip_var)
906+
894907
cdef class Constraint:
895908
"""Base class holding a pointer to corresponding SCIP_CONS"""
896909

@@ -1960,6 +1973,20 @@ cdef class Model:
19601973
"""returns whether the current LP solution is basic, i.e. is defined by a valid simplex basis"""
19611974
return SCIPisLPSolBasic(self._scip)
19621975

1976+
def allColsInLP(self):
1977+
"""checks if all columns, i.e. every variable with non-empty column is present in the LP.
1978+
This is not True when performing pricing for instance."""
1979+
1980+
return SCIPallColsInLP(self._scip)
1981+
1982+
# LP Col Methods
1983+
def getColRedCost(self, Column col):
1984+
"""gets the reduced cost of the column in the current LP
1985+
1986+
:param Column col: the column of the LP for which the reduced cost will be retrieved
1987+
"""
1988+
return SCIPgetColRedcost(self._scip, col.scip_col)
1989+
19631990
#TODO: documentation!!
19641991
# LP Row Methods
19651992
def createEmptyRowSepa(self, Sepa sepa, name="row", lhs = 0.0, rhs = None, local = True, modifiable = False, removable = True):
@@ -5553,6 +5580,119 @@ cdef class Model:
55535580
assert isinstance(var, Variable), "The given variable is not a pyvar, but %s" % var.__class__.__name__
55545581
PY_SCIP_CALL(SCIPchgVarBranchPriority(self._scip, var.scip_var, priority))
55555582

5583+
def startStrongbranch(self):
5584+
"""Start strong branching. Needs to be called before any strong branching. Must also later end strong branching.
5585+
TODO: Propagation option has currently been disabled via Python.
5586+
If propagation is enabled then strong branching is not done on the LP, but on additionally created nodes (has some overhead)"""
5587+
5588+
PY_SCIP_CALL(SCIPstartStrongbranch(self._scip, False))
5589+
5590+
def endStrongbranch(self):
5591+
"""End strong branching. Needs to be called if startStrongBranching was called previously.
5592+
Between these calls the user can access all strong branching functionality. """
5593+
5594+
PY_SCIP_CALL(SCIPendStrongbranch(self._scip))
5595+
5596+
def getVarStrongbranchLast(self, Variable var):
5597+
"""Get the results of the last strong branching call on this variable (potentially was called
5598+
at another node).
5599+
5600+
down - The dual bound of the LP after branching down on the variable
5601+
up - The dual bound of the LP after branchign up on the variable
5602+
downvalid - Whether down stores a valid dual bound or is NULL
5603+
upvalid - Whether up stores a valid dual bound or is NULL
5604+
solval - The solution value of the variable at the last strong branching call
5605+
lpobjval - The LP objective value at the time of the last strong branching call
5606+
5607+
:param Variable var: variable to get the previous strong branching information from
5608+
"""
5609+
5610+
cdef SCIP_Real down
5611+
cdef SCIP_Real up
5612+
cdef SCIP_Real solval
5613+
cdef SCIP_Real lpobjval
5614+
cdef SCIP_Bool downvalid
5615+
cdef SCIP_Bool upvalid
5616+
5617+
PY_SCIP_CALL(SCIPgetVarStrongbranchLast(self._scip, var.scip_var, &down, &up, &downvalid, &upvalid, &solval, &lpobjval))
5618+
5619+
return down, up, downvalid, upvalid, solval, lpobjval
5620+
5621+
def getVarStrongbranchNode(self, Variable var):
5622+
"""Get the node number from the last time strong branching was called on the variable
5623+
5624+
:param Variable var: variable to get the previous strong branching node from
5625+
"""
5626+
5627+
cdef SCIP_Longint node_num
5628+
node_num = SCIPgetVarStrongbranchNode(self._scip, var.scip_var)
5629+
5630+
return node_num
5631+
5632+
def getVarStrongbranch(self, Variable var, itlim, idempotent=False, integral=False):
5633+
""" Strong branches and gets information on column variable.
5634+
5635+
:param Variable var: Variable to get strong branching information on
5636+
:param itlim: LP iteration limit for total strong branching calls
5637+
:param idempotent: Should SCIP's state remain the same after the call?
5638+
:param integral: Boolean on whether the variable is currently integer.
5639+
"""
5640+
5641+
cdef SCIP_Real down
5642+
cdef SCIP_Real up
5643+
cdef SCIP_Bool downvalid
5644+
cdef SCIP_Bool upvalid
5645+
cdef SCIP_Bool downinf
5646+
cdef SCIP_Bool upinf
5647+
cdef SCIP_Bool downconflict
5648+
cdef SCIP_Bool upconflict
5649+
cdef SCIP_Bool lperror
5650+
5651+
if integral:
5652+
PY_SCIP_CALL(SCIPgetVarStrongbranchInt(self._scip, var.scip_var, itlim, idempotent, &down, &up, &downvalid,
5653+
&upvalid, &downinf, &upinf, &downconflict, &upconflict, &lperror))
5654+
else:
5655+
PY_SCIP_CALL(SCIPgetVarStrongbranchFrac(self._scip, var.scip_var, itlim, idempotent, &down, &up, &downvalid,
5656+
&upvalid, &downinf, &upinf, &downconflict, &upconflict, &lperror))
5657+
5658+
return down, up, downvalid, upvalid, downinf, upinf, downconflict, upconflict, lperror
5659+
5660+
def updateVarPseudocost(self, Variable var, valdelta, objdelta, weight):
5661+
"""Updates the pseudo costs of the given variable and the global pseudo costs after a change of valdelta
5662+
in the variable's solution value and resulting change of objdelta in the LP's objective value.
5663+
Update is ignored if objdelts is infinite. Weight is in range (0, 1], and affects how it updates
5664+
the global weighted sum.
5665+
5666+
:param Variable var: Variable whos pseudo cost will be updated
5667+
:param valdelta: The change in variable value (e.g. the fractional amount removed or added by branching)
5668+
:param objdelta: The change in objective value of the LP after valdelta change of the variable
5669+
:param weight: the weight in range (0,1] of how the update affects the stored weighted sum.
5670+
"""
5671+
5672+
PY_SCIP_CALL(SCIPupdateVarPseudocost(self._scip, var.scip_var, valdelta, objdelta, weight))
5673+
5674+
def getBranchScoreMultiple(self, Variable var, gains):
5675+
"""Calculates the branching score out of the gain predictions for a branching with
5676+
arbitrary many children.
5677+
5678+
:param Variable var: variable to calculate the score for
5679+
:param gains: list of gains for each child.
5680+
"""
5681+
5682+
assert isinstance(gains, list)
5683+
nchildren = len(gains)
5684+
5685+
cdef int _nchildren = nchildren
5686+
_gains = <SCIP_Real*> malloc(_nchildren * sizeof(SCIP_Real))
5687+
for i in range(_nchildren):
5688+
_gains[i] = gains[i]
5689+
5690+
score = SCIPgetBranchScoreMultiple(self._scip, var.scip_var, _nchildren, _gains)
5691+
5692+
free(_gains)
5693+
5694+
return score
5695+
55565696
def getTreesizeEstimation(self):
55575697
"""Get an estimation of the final tree size """
55585698
return SCIPgetTreesizeEstimation(self._scip)
@@ -5697,7 +5837,7 @@ class Statistics:
56975837
@property
56985838
def n_presolved_maximal_cons(self):
56995839
return self._presolved_constraints["maximal"]
5700-
5840+
57015841
# debugging memory management
57025842
def is_memory_freed():
57035843
return BMSgetMemoryUsed() == 0

0 commit comments

Comments
 (0)