/** adds cut to separation storage and captures it; * if the cut should be forced to enter the LP, an infinite score has to be used */ SCIP_RETCODE SCIPsepastoreAddCut( SCIP_SEPASTORE* sepastore, /**< separation storage */ BMS_BLKMEM* blkmem, /**< block memory */ SCIP_SET* set, /**< global SCIP settings */ SCIP_STAT* stat, /**< problem statistics data */ SCIP_EVENTQUEUE* eventqueue, /**< event queue */ SCIP_EVENTFILTER* eventfilter, /**< event filter for global events */ SCIP_LP* lp, /**< LP data */ SCIP_SOL* sol, /**< primal solution that was separated, or NULL for LP solution */ SCIP_ROW* cut, /**< separated cut */ SCIP_Bool forcecut, /**< should the cut be forced to enter the LP? */ SCIP_Bool root /**< are we at the root node? */ ) { assert(sepastore != NULL); assert(cut != NULL); assert(!SCIProwIsInLP(cut)); assert(!SCIPsetIsInfinity(set, -SCIProwGetLhs(cut)) || !SCIPsetIsInfinity(set, SCIProwGetRhs(cut))); /* debug: check cut for feasibility */ SCIP_CALL( SCIPdebugCheckRow(set, cut) ); /*lint !e506 !e774*/ /* update statistics of total number of found cuts */ if( !sepastore->initiallp ) { sepastore->ncutsfound++; sepastore->ncutsfoundround++; } /* add LP row cut to separation storage */ SCIP_CALL( sepastoreAddCut(sepastore, blkmem, set, stat, eventqueue, eventfilter, lp, sol, cut, forcecut, root) ); return SCIP_OKAY; }
/** computes score for current LP solution and initialized orthogonalities */ static SCIP_RETCODE computeScore( SCIP_SEPASTORE* sepastore, /**< separation storage */ SCIP_SET* set, /**< global SCIP settings */ SCIP_STAT* stat, /**< problem statistics */ SCIP_LP* lp, /**< LP data */ SCIP_Bool handlepool, /**< whether the efficacy of cuts in the pool should be reduced */ int pos /**< position of cut to handle */ ) { SCIP_ROW* cut; SCIP_Real cutefficacy; SCIP_Real cutscore; cut = sepastore->cuts[pos]; /* calculate cut's efficacy */ cutefficacy = SCIProwGetLPEfficacy(cut, set, stat, lp); /* If a cut is not member of the cut pool, we slightly decrease its score to prefer identical * cuts which are in the cut pool. This is because the conversion of cuts into linear * constraints after a restart looks at the cut pool and cannot find tight non-pool cuts. */ if( handlepool && !SCIProwIsInGlobalCutpool(cut) ) cutefficacy *= 0.9999; /* calculate resulting score */ assert( sepastore->objparallelisms[pos] != SCIP_INVALID ); /*lint !e777*/ cutscore = cutefficacy + set->sepa_objparalfac * sepastore->objparallelisms[pos] + set->sepa_orthofac * 1.0; assert( !SCIPsetIsInfinity(set, cutscore) ); sepastore->efficacies[pos] = cutefficacy; sepastore->scores[pos] = cutscore; /* make sure that the orthogonalities are initialized to 1.0 */ sepastore->orthogonalities[pos] = 1.0; return SCIP_OKAY; }
/** update the primal-dual integral statistic. method accepts + and - SCIPsetInfinity() as values for * upper and lower bound, respectively */ void SCIPstatUpdatePrimalDualIntegral( SCIP_STAT* stat, /**< problem statistics data */ SCIP_SET* set, /**< global SCIP settings */ SCIP_PROB* transprob, /**< transformed problem */ SCIP_PROB* origprob, /**< original problem */ SCIP_Real upperbound, /**< current upper bound in transformed problem, or infinity */ SCIP_Real lowerbound /**< current lower bound in transformed space, or -infinity */ ) { SCIP_Real currentgap; SCIP_Real solvingtime; SCIP_Real primalbound; SCIP_Real dualbound; assert(stat != NULL); assert(set != NULL); solvingtime = SCIPclockGetTime(stat->solvingtime); assert(solvingtime >= stat->previntegralevaltime); if( !SCIPsetIsInfinity(set, upperbound) ) /*lint !e777*/ { /* get value in original space for gap calculation */ primalbound = SCIPprobExternObjval(transprob, origprob, set, upperbound); if( SCIPsetIsZero(set, primalbound) ) primalbound = 0.0; } else { /* no new upper bound: use stored values from last update */ upperbound = stat->lastupperbound; primalbound = stat->lastprimalbound; assert(SCIPsetIsZero(set, primalbound) == (primalbound == 0.0)); /*lint !e777*/ } if( !SCIPsetIsInfinity(set, -lowerbound) ) /*lint !e777*/ { /* get value in original space for gap calculation */ dualbound = SCIPprobExternObjval(transprob, origprob, set, lowerbound); if( SCIPsetIsZero(set, dualbound) ) dualbound = 0.0; } else { /* no new lower bound: use stored values from last update */ lowerbound = stat->lastlowerbound; dualbound = stat->lastdualbound; assert(SCIPsetIsZero(set, dualbound) == (dualbound == 0.0)); /*lint !e777*/ } /* computation of the gap, special cases are handled first */ if( primalbound == SCIP_UNKNOWN || dualbound == SCIP_UNKNOWN ) /*lint !e777*/ currentgap = 100.0; /* the gap is 0.0 if bounds coincide */ else if( SCIPsetIsGE(set, lowerbound, upperbound) || SCIPsetIsEQ(set, primalbound, dualbound) ) currentgap = 0.0; /* the gap is 100.0 if bounds have different signs */ else if( primalbound * dualbound <= 0.0 ) /*lint !e777*/ currentgap = 100.0; else if( !SCIPsetIsInfinity(set, REALABS(primalbound)) && !SCIPsetIsInfinity(set, REALABS(dualbound)) ) { SCIP_Real absprim = REALABS(primalbound); SCIP_Real absdual = REALABS(dualbound); /* The gap in the definition of the primal-dual integral differs from the default SCIP gap function. * Here, the MAX(primalbound, dualbound) is taken for gap quotient in order to ensure a gap <= 100. */ currentgap = 100.0 * REALABS(primalbound - dualbound) / MAX(absprim, absdual); assert(SCIPsetIsLE(set, currentgap, 100.0)); } else currentgap = 100.0; /* if primal and dual bound have opposite signs, the gap always evaluates to 100.0% */ assert(currentgap == 0.0 || currentgap == 100.0 || SCIPsetIsGE(set, primalbound * dualbound, 0.0)); assert(SCIPsetIsGE(set, stat->previousgap, currentgap) || (set->stage == SCIP_STAGE_EXITPRESOLVE && SCIPsetIsFeasGE(set, stat->previousgap, currentgap))); /* update the integral based on previous information */ stat->primaldualintegral += (solvingtime - stat->previntegralevaltime) * stat->previousgap; /* update all relevant information for next evaluation */ stat->previousgap = currentgap; stat->previntegralevaltime = solvingtime; stat->lastprimalbound = primalbound; stat->lastdualbound = dualbound; stat->lastlowerbound = lowerbound; stat->lastupperbound = upperbound; }
/** adds cuts to the LP and clears separation storage */ SCIP_RETCODE SCIPsepastoreApplyCuts( SCIP_SEPASTORE* sepastore, /**< separation storage */ BMS_BLKMEM* blkmem, /**< block memory */ SCIP_SET* set, /**< global SCIP settings */ SCIP_STAT* stat, /**< problem statistics */ SCIP_TREE* tree, /**< branch and bound tree */ SCIP_LP* lp, /**< LP data */ SCIP_BRANCHCAND* branchcand, /**< branching candidate storage */ SCIP_EVENTQUEUE* eventqueue, /**< event queue */ SCIP_EVENTFILTER* eventfilter, /**< global event filter */ SCIP_Bool root, /**< are we at the root node? */ SCIP_Bool* cutoff /**< pointer to store whether an empty domain was created */ ) { SCIP_NODE* node; SCIP_Real mincutorthogonality; int depth; int maxsepacuts; int ncutsapplied; int pos; assert(sepastore != NULL); assert(set != NULL); assert(tree != NULL); assert(lp != NULL); assert(cutoff != NULL); *cutoff = FALSE; SCIPdebugMessage("applying %d cuts\n", sepastore->ncuts); node = SCIPtreeGetCurrentNode(tree); assert(node != NULL); /* get maximal number of cuts to add to the LP */ maxsepacuts = SCIPsetGetSepaMaxcuts(set, root); ncutsapplied = 0; /* get depth of current node */ depth = SCIPnodeGetDepth(node); /* calculate minimal cut orthogonality */ mincutorthogonality = (root ? set->sepa_minorthoroot : set->sepa_minortho); mincutorthogonality = MAX(mincutorthogonality, set->num_epsilon); /* Compute scores for all non-forced cuts and initialize orthogonalities - make sure all cuts are initialized again for the current LP solution */ for( pos = sepastore->nforcedcuts; pos < sepastore->ncuts; pos++ ) { SCIP_CALL( computeScore(sepastore, set, stat, lp, TRUE, pos) ); } /* apply all forced cuts */ for( pos = 0; pos < sepastore->nforcedcuts && !(*cutoff); pos++ ) { SCIP_ROW* cut; cut = sepastore->cuts[pos]; assert(SCIPsetIsInfinity(set, sepastore->scores[pos])); /* if the cut is a bound change (i.e. a row with only one variable), add it as bound change instead of LP row */ if( !SCIProwIsModifiable(cut) && SCIProwGetNNonz(cut) == 1 ) { SCIPdebugMessage(" -> applying forced cut <%s> as boundchange\n", SCIProwGetName(cut)); SCIP_CALL( sepastoreApplyBdchg(sepastore, blkmem, set, stat, tree, lp, branchcand, eventqueue, cut, cutoff) ); } else { /* add cut to the LP and update orthogonalities */ SCIPdebugMessage(" -> applying forced cut <%s>\n", SCIProwGetName(cut)); /*SCIPdebug(SCIProwPrint(cut, NULL));*/ SCIP_CALL( sepastoreApplyCut(sepastore, blkmem, set, eventqueue, eventfilter, lp, cut, mincutorthogonality, depth, &ncutsapplied) ); } } /* apply non-forced cuts */ while( ncutsapplied < maxsepacuts && sepastore->ncuts > sepastore->nforcedcuts && !(*cutoff) ) { SCIP_ROW* cut; int bestpos; /* get best non-forced cut */ bestpos = sepastoreGetBestCut(sepastore); assert(sepastore->nforcedcuts <= bestpos && bestpos < sepastore->ncuts); assert(sepastore->scores[bestpos] != SCIP_INVALID ); /*lint !e777*/ assert(sepastore->efficacies[bestpos] != SCIP_INVALID ); /*lint !e777*/ cut = sepastore->cuts[bestpos]; assert(SCIProwIsModifiable(cut) || SCIProwGetNNonz(cut) != 1); /* bound changes are forced cuts */ assert(!SCIPsetIsInfinity(set, sepastore->scores[bestpos])); SCIPdebugMessage(" -> applying cut <%s> (pos=%d/%d, len=%d, efficacy=%g, objparallelism=%g, orthogonality=%g, score=%g)\n", SCIProwGetName(cut), bestpos, sepastore->ncuts, SCIProwGetNNonz(cut), sepastore->efficacies[bestpos], sepastore->objparallelisms[bestpos], sepastore->orthogonalities[bestpos], sepastore->scores[bestpos]); /*SCIPdebug(SCIProwPrint(cut, NULL));*/ /* capture cut such that it is not destroyed in sepastoreDelCut() */ SCIProwCapture(cut); /* release the row and delete the cut (also issuing ROWDELETEDSEPA event) */ SCIP_CALL( sepastoreDelCut(sepastore, blkmem, set, eventqueue, eventfilter, lp, bestpos) ); /* Do not add (non-forced) non-violated cuts. * Note: do not take SCIPsetIsEfficacious(), because constraint handlers often add cuts w.r.t. SCIPsetIsFeasPositive(). */ if( SCIPsetIsFeasPositive(set, sepastore->efficacies[bestpos]) ) { /* add cut to the LP and update orthogonalities */ SCIP_CALL( sepastoreApplyCut(sepastore, blkmem, set, eventqueue, eventfilter, lp, cut, mincutorthogonality, depth, &ncutsapplied) ); } /* release cut */ SCIP_CALL( SCIProwRelease(&cut, blkmem, set, lp) ); } /* clear the separation storage and reset statistics for separation round */ SCIP_CALL( SCIPsepastoreClearCuts(sepastore, blkmem, set, eventqueue, eventfilter, lp) ); return SCIP_OKAY; }
/** applies a cut that is a bound change directly as bound change instead of adding it as row to the LP */ static SCIP_RETCODE sepastoreApplyBdchg( SCIP_SEPASTORE* sepastore, /**< separation storage */ BMS_BLKMEM* blkmem, /**< block memory */ SCIP_SET* set, /**< global SCIP settings */ SCIP_STAT* stat, /**< problem statistics */ SCIP_TREE* tree, /**< branch and bound tree */ SCIP_LP* lp, /**< LP data */ SCIP_BRANCHCAND* branchcand, /**< branching candidate storage */ SCIP_EVENTQUEUE* eventqueue, /**< event queue */ SCIP_ROW* cut, /**< cut with a single variable */ SCIP_Bool* cutoff /**< pointer to store whether an empty domain was created */ ) { SCIP_COL** cols; SCIP_Real* vals; SCIP_VAR* var; SCIP_Real lhs; SCIP_Real rhs; assert(sepastore != NULL); assert(!SCIProwIsModifiable(cut)); assert(SCIProwGetNNonz(cut) == 1); assert(cutoff != NULL); *cutoff = FALSE; /* get the single variable and its coefficient of the cut */ cols = SCIProwGetCols(cut); assert(cols != NULL); var = SCIPcolGetVar(cols[0]); vals = SCIProwGetVals(cut); assert(vals != NULL); assert(!SCIPsetIsZero(set, vals[0])); /* if the coefficient is nearly zero, we better ignore this cut for numerical reasons */ if( SCIPsetIsFeasZero(set, vals[0]) ) return SCIP_OKAY; /* get the left hand side of the cut and convert it to a bound */ lhs = SCIProwGetLhs(cut); if( !SCIPsetIsInfinity(set, -lhs) ) { lhs -= SCIProwGetConstant(cut); if( vals[0] > 0.0 ) { /* coefficient is positive -> lhs corresponds to lower bound */ SCIP_CALL( sepastoreApplyLb(sepastore, blkmem, set, stat, tree, lp, branchcand, eventqueue, var, lhs/vals[0], cutoff) ); } else { /* coefficient is negative -> lhs corresponds to upper bound */ SCIP_CALL( sepastoreApplyUb(sepastore, blkmem, set, stat, tree, lp, branchcand, eventqueue, var, lhs/vals[0], cutoff) ); } } /* get the right hand side of the cut and convert it to a bound */ rhs = SCIProwGetRhs(cut); if( !SCIPsetIsInfinity(set, rhs) ) { rhs -= SCIProwGetConstant(cut); if( vals[0] > 0.0 ) { /* coefficient is positive -> rhs corresponds to upper bound */ SCIP_CALL( sepastoreApplyUb(sepastore, blkmem, set, stat, tree, lp, branchcand, eventqueue, var, rhs/vals[0], cutoff) ); } else { /* coefficient is negative -> rhs corresponds to lower bound */ SCIP_CALL( sepastoreApplyLb(sepastore, blkmem, set, stat, tree, lp, branchcand, eventqueue, var, rhs/vals[0], cutoff) ); } } /* count the bound change as applied cut */ if( !sepastore->initiallp ) sepastore->ncutsapplied++; return SCIP_OKAY; }
/** adds cut stored as LP row to separation storage and captures it; * if the cut should be forced to be used, an infinite score has to be used */ static SCIP_RETCODE sepastoreAddCut( SCIP_SEPASTORE* sepastore, /**< separation storage */ BMS_BLKMEM* blkmem, /**< block memory */ SCIP_SET* set, /**< global SCIP settings */ SCIP_STAT* stat, /**< problem statistics data */ SCIP_EVENTQUEUE* eventqueue, /**< event queue */ SCIP_EVENTFILTER* eventfilter, /**< event filter for global events */ SCIP_LP* lp, /**< LP data */ SCIP_SOL* sol, /**< primal solution that was separated, or NULL for LP solution */ SCIP_ROW* cut, /**< separated cut */ SCIP_Bool forcecut, /**< should the cut be forced to enter the LP? */ SCIP_Bool root /**< are we at the root node? */ ) { SCIP_Real cutefficacy; SCIP_Real cutobjparallelism; SCIP_Real cutscore; int pos; assert(sepastore != NULL); assert(sepastore->nforcedcuts <= sepastore->ncuts); assert(set != NULL); assert(cut != NULL); assert(sol != NULL || !SCIProwIsInLP(cut)); assert(!SCIPsetIsInfinity(set, -SCIProwGetLhs(cut)) || !SCIPsetIsInfinity(set, SCIProwGetRhs(cut))); assert(eventqueue != NULL); assert(eventfilter != NULL); /* in the root node, every local cut is a global cut, and global cuts are nicer in many ways...*/ if( root && SCIProwIsLocal(cut) ) { SCIPdebugMessage("change local flag of cut <%s> to FALSE due to addition in root node\n", SCIProwGetName(cut)); SCIP_CALL( SCIProwChgLocal(cut, FALSE) ); assert(!SCIProwIsLocal(cut)); } /* check cut for redundancy * in each separation round, make sure that at least one (even redundant) cut enters the LP to avoid cycling */ if( !forcecut && sepastore->ncuts > 0 && sepastoreIsCutRedundant(sepastore, set, stat, cut) ) return SCIP_OKAY; /* if only one cut is currently present in the cut store, it could be redundant; in this case, it can now be removed * again, because now a non redundant cut enters the store */ if( sepastore->ncuts == 1 && sepastoreIsCutRedundant(sepastore, set, stat, sepastore->cuts[0]) ) { /* check, if the row deletions from separation storage events are tracked * if so, issue ROWDELETEDSEPA event */ if( eventfilter->len > 0 && (eventfilter->eventmask & SCIP_EVENTTYPE_ROWDELETEDSEPA) != 0 ) { SCIP_EVENT* event; SCIP_CALL( SCIPeventCreateRowDeletedSepa(&event, blkmem, sepastore->cuts[0]) ); SCIP_CALL( SCIPeventqueueAdd(eventqueue, blkmem, set, NULL, NULL, NULL, eventfilter, &event) ); } SCIP_CALL( SCIProwRelease(&sepastore->cuts[0], blkmem, set, lp) ); sepastore->ncuts = 0; sepastore->nforcedcuts = 0; } /* a cut is forced to enter the LP if * - we construct the initial LP, or * - it has infinite score factor, or * - it is a bound change * if it is a non-forced cut and no cuts should be added, abort */ forcecut = forcecut || sepastore->initiallp || sepastore->forcecuts || (!SCIProwIsModifiable(cut) && SCIProwGetNNonz(cut) == 1); if( !forcecut && SCIPsetGetSepaMaxcuts(set, root) == 0 ) return SCIP_OKAY; /* get enough memory to store the cut */ SCIP_CALL( sepastoreEnsureCutsMem(sepastore, set, sepastore->ncuts+1) ); assert(sepastore->ncuts < sepastore->cutssize); if( forcecut ) { cutefficacy = SCIPsetInfinity(set); cutscore = SCIPsetInfinity(set); cutobjparallelism = 1.0; } else { /* initialize values to invalid (will be initialized during cut filtering) */ cutefficacy = SCIP_INVALID; cutscore = SCIP_INVALID; /* initialize parallelism to objective (constant throughout filtering) */ if( set->sepa_objparalfac > 0.0 ) cutobjparallelism = SCIProwGetObjParallelism(cut, set, lp); else cutobjparallelism = 0.0; /* no need to calculate it */ } SCIPdebugMessage("adding cut <%s> to separation storage of size %d (forcecut=%u, len=%d)\n", SCIProwGetName(cut), sepastore->ncuts, forcecut, SCIProwGetNNonz(cut)); /*SCIPdebug(SCIProwPrint(cut, NULL));*/ /* capture the cut */ SCIProwCapture(cut); /* add cut to arrays */ if( forcecut ) { /* make room at the beginning of the array for forced cut */ pos = sepastore->nforcedcuts; sepastore->cuts[sepastore->ncuts] = sepastore->cuts[pos]; sepastore->efficacies[sepastore->ncuts] = sepastore->efficacies[pos]; sepastore->objparallelisms[sepastore->ncuts] = sepastore->objparallelisms[pos]; sepastore->orthogonalities[sepastore->ncuts] = sepastore->orthogonalities[pos]; sepastore->scores[sepastore->ncuts] = sepastore->scores[pos]; sepastore->nforcedcuts++; } else pos = sepastore->ncuts; sepastore->cuts[pos] = cut; sepastore->efficacies[pos] = cutefficacy; sepastore->objparallelisms[pos] = cutobjparallelism; sepastore->orthogonalities[pos] = 1.0; sepastore->scores[pos] = cutscore; sepastore->ncuts++; /* check, if the row addition to separation storage events are tracked * if so, issue ROWADDEDSEPA event */ if( eventfilter->len > 0 && (eventfilter->eventmask & SCIP_EVENTTYPE_ROWADDEDSEPA) != 0 ) { SCIP_EVENT* event; SCIP_CALL( SCIPeventCreateRowAddedSepa(&event, blkmem, cut) ); SCIP_CALL( SCIPeventqueueAdd(eventqueue, blkmem, set, NULL, NULL, NULL, eventfilter, &event) ); } return SCIP_OKAY; }