Skip to content

Commit a9cb877

Browse files
authored
Merge pull request #2444 from ERGO-Code/strong-branch-refactor
Strong-branching refactor
2 parents 080579b + 08b4740 commit a9cb877

File tree

2 files changed

+64
-157
lines changed

2 files changed

+64
-157
lines changed

FEATURES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ The irreducible infeasibility system (IIS) facility now detects infeasibility du
1212

1313
Prompted by [#2463](https://github.com/ERGO-Code/HiGHS/issues/2463), the HiGHS solution and basis files now match data to any column and row names in the model, only assuming that the data are aligned with column and row indices if there are no names in the model. This requires a new version (v2) of the HiGHS basis file. Basis files from v1 are still read, but deprecated. Now, when writing out a model, basis or solution, column and row names are added to the model - previously they were created temporarily and inconsistentyly on the fly. If the model has existing names, then distinctive names are created to replace any blank names, but names with spaces or duplicate names yield an error status return.
1414

15+
Refactored strong branching to minimize duplicated code
16+
1517
As per [#2487](https://github.com/ERGO-Code/HiGHS/issues/2487), trivial heuristics now run before feasibility jump (FJ), and FJ will use any existing incumbent. FJ will clip any finite variable values in the incumbent to lower and upper bounds, and falls back to the existing logic (lower bound if finite, else upper bound if finite, else 0) for any infinite values in the incumbent.
18+

highs/mip/HighsSearch.cpp

Lines changed: 61 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -530,18 +530,13 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters,
530530
}
531531
};
532532

533-
if (!downscorereliable[candidate] &&
534-
(upscorereliable[candidate] ||
535-
std::make_pair(downscore[candidate],
536-
pseudocost.getAvgInferencesDown(col)) >=
537-
std::make_pair(upscore[candidate],
538-
pseudocost.getAvgInferencesUp(col)))) {
539-
// evaluate down branch
540-
// if (!mipsolver.submip)
541-
// printf("down eval col=%d fracval=%g\n", col, fracval);
533+
auto strongBranch = [&](bool upbranch) -> bool {
542534
int64_t inferences = -(int64_t)localdom.getDomainChangeStack().size() - 1;
535+
HighsBoundType boundtype =
536+
upbranch ? HighsBoundType::kLower : HighsBoundType::kUpper;
537+
double boundval = upbranch ? upval : downval;
538+
HighsDomainChange domchg{boundval, col, boundtype};
543539

544-
HighsDomainChange domchg{downval, col, HighsBoundType::kUpper};
545540
bool orbitalFixing =
546541
nodestack.back().stabilizerOrbits && orbitsValidInChildNode(domchg);
547542
localdom.changeBound(domchg);
@@ -557,19 +552,23 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters,
557552
inferences += localdom.getDomainChangeStack().size();
558553
if (localdom.infeasible()) {
559554
localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool);
560-
pseudocost.addCutoffObservation(col, false);
555+
pseudocost.addCutoffObservation(col, upbranch);
561556
localdom.backtrack();
562557
localdom.clearChangedCols();
563558

564-
branchUpwards(col, upval, fracval);
559+
if (upbranch) {
560+
branchDownwards(col, downval, fracval);
561+
} else {
562+
branchUpwards(col, upval, fracval);
563+
}
565564
nodestack[nodestack.size() - 2].opensubtrees = 0;
566565
nodestack[nodestack.size() - 2].skipDepthCount = 1;
567566
depthoffset -= 1;
568567

569-
return -1;
568+
return true;
570569
}
571570

572-
pseudocost.addInferenceObservation(col, inferences, false);
571+
pseudocost.addInferenceObservation(col, inferences, upbranch);
573572

574573
int64_t numiters = lp->getNumLpIterations();
575574
HighsLpRelaxation::Status status = playground.solveLp(localdom);
@@ -580,18 +579,23 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters,
580579
if (lp->scaledOptimal(status)) {
581580
lp->performAging();
582581

583-
double delta = downval - fracval;
582+
double delta = upbranch ? upval - fracval : downval - fracval;
584583
bool integerfeasible;
585584
const std::vector<double>& sol = lp->getSolution().col_value;
586585
double solobj = checkSol(sol, integerfeasible);
587586

588587
double objdelta = std::max(solobj - lp->getObjective(), 0.0);
589588
if (objdelta <= mipsolver.mipdata_->epsilon) objdelta = 0.0;
590589

591-
downscore[candidate] = objdelta;
592-
downscorereliable[candidate] = true;
593-
594-
markBranchingVarDownReliableAtNode(col);
590+
if (upbranch) {
591+
upscore[candidate] = objdelta;
592+
upscorereliable[candidate] = true;
593+
markBranchingVarUpReliableAtNode(col);
594+
} else {
595+
downscore[candidate] = objdelta;
596+
downscorereliable[candidate] = true;
597+
markBranchingVarDownReliableAtNode(col);
598+
}
595599
pseudocost.addObservation(col, delta, objdelta);
596600
analyzeSolution(objdelta, sol);
597601

@@ -607,7 +611,11 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters,
607611
}
608612

609613
if (lp->unscaledDualFeasible(status)) {
610-
downbound[candidate] = solobj;
614+
if (upbranch) {
615+
upbound[candidate] = solobj;
616+
} else {
617+
downbound[candidate] = solobj;
618+
}
611619
if (solobj > mipsolver.mipdata_->optimality_limit) {
612620
addBoundExceedingConflict();
613621

@@ -617,13 +625,17 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters,
617625
localdom.backtrack();
618626
lp->flushDomain(localdom);
619627

620-
branchUpwards(col, upval, fracval);
628+
if (upbranch) {
629+
branchDownwards(col, downval, fracval);
630+
} else {
631+
branchUpwards(col, upval, fracval);
632+
}
621633
nodestack[nodestack.size() - 2].opensubtrees = pruned ? 0 : 1;
622634
nodestack[nodestack.size() - 2].other_child_lb = solobj;
623635
nodestack[nodestack.size() - 2].skipDepthCount = 1;
624636
depthoffset -= 1;
625637

626-
return -1;
638+
return true;
627639
}
628640
} else if (solobj > getCutoffBound()) {
629641
addBoundExceedingConflict();
@@ -633,27 +645,35 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters,
633645
localdom.backtrack();
634646
lp->flushDomain(localdom);
635647

636-
branchUpwards(col, upval, fracval);
648+
if (upbranch) {
649+
branchDownwards(col, downval, fracval);
650+
} else {
651+
branchUpwards(col, upval, fracval);
652+
}
637653
nodestack[nodestack.size() - 2].opensubtrees = 0;
638654
nodestack[nodestack.size() - 2].skipDepthCount = 1;
639655
depthoffset -= 1;
640656

641-
return -1;
657+
return true;
642658
}
643659
}
644660
} else if (status == HighsLpRelaxation::Status::kInfeasible) {
645661
mipsolver.mipdata_->debugSolution.nodePruned(localdom);
646662
addInfeasibleConflict();
647-
pseudocost.addCutoffObservation(col, false);
663+
pseudocost.addCutoffObservation(col, upbranch);
648664
localdom.backtrack();
649665
lp->flushDomain(localdom);
650666

651-
branchUpwards(col, upval, fracval);
667+
if (upbranch) {
668+
branchDownwards(col, downval, fracval);
669+
} else {
670+
branchUpwards(col, upval, fracval);
671+
}
652672
nodestack[nodestack.size() - 2].opensubtrees = 0;
653673
nodestack[nodestack.size() - 2].skipDepthCount = 1;
654674
depthoffset -= 1;
655675

656-
return -1;
676+
return true;
657677
} else {
658678
// printf("todo2\n");
659679
// in case of an LP error we set the score of this variable to zero to
@@ -668,140 +688,24 @@ HighsInt HighsSearch::selectBranchingCandidate(int64_t maxSbIters,
668688

669689
localdom.backtrack();
670690
lp->flushDomain(localdom);
691+
return false;
692+
};
693+
694+
if (!downscorereliable[candidate] &&
695+
(upscorereliable[candidate] ||
696+
std::make_pair(downscore[candidate],
697+
pseudocost.getAvgInferencesDown(col)) >=
698+
std::make_pair(upscore[candidate],
699+
pseudocost.getAvgInferencesUp(col)))) {
700+
// evaluate down branch
701+
// if (!mipsolver.submip)
702+
// printf("down eval col=%d fracval=%g\n", col, fracval);
703+
if (strongBranch(false)) return -1;
671704
} else {
672705
// if (!mipsolver.submip)
673706
// printf("up eval col=%d fracval=%g\n", col, fracval);
674707
// evaluate up branch
675-
int64_t inferences = -(int64_t)localdom.getDomainChangeStack().size() - 1;
676-
HighsDomainChange domchg{upval, col, HighsBoundType::kLower};
677-
bool orbitalFixing =
678-
nodestack.back().stabilizerOrbits && orbitsValidInChildNode(domchg);
679-
localdom.changeBound(domchg);
680-
localdom.propagate();
681-
682-
if (!localdom.infeasible()) {
683-
if (orbitalFixing)
684-
nodestack.back().stabilizerOrbits->orbitalFixing(localdom);
685-
else
686-
mipsolver.mipdata_->symmetries.propagateOrbitopes(localdom);
687-
}
688-
689-
inferences += localdom.getDomainChangeStack().size();
690-
if (localdom.infeasible()) {
691-
localdom.conflictAnalysis(mipsolver.mipdata_->conflictPool);
692-
pseudocost.addCutoffObservation(col, true);
693-
localdom.backtrack();
694-
localdom.clearChangedCols();
695-
696-
branchDownwards(col, downval, fracval);
697-
nodestack[nodestack.size() - 2].opensubtrees = 0;
698-
nodestack[nodestack.size() - 2].skipDepthCount = 1;
699-
depthoffset -= 1;
700-
701-
return -1;
702-
}
703-
704-
pseudocost.addInferenceObservation(col, inferences, true);
705-
706-
int64_t numiters = lp->getNumLpIterations();
707-
HighsLpRelaxation::Status status = playground.solveLp(localdom);
708-
numiters = lp->getNumLpIterations() - numiters;
709-
lpiterations += numiters;
710-
sblpiterations += numiters;
711-
712-
if (lp->scaledOptimal(status)) {
713-
lp->performAging();
714-
715-
double delta = upval - fracval;
716-
bool integerfeasible;
717-
718-
const std::vector<double>& sol =
719-
lp->getLpSolver().getSolution().col_value;
720-
double solobj = checkSol(sol, integerfeasible);
721-
722-
double objdelta = std::max(solobj - lp->getObjective(), 0.0);
723-
if (objdelta <= mipsolver.mipdata_->epsilon) objdelta = 0.0;
724-
725-
upscore[candidate] = objdelta;
726-
upscorereliable[candidate] = true;
727-
728-
markBranchingVarUpReliableAtNode(col);
729-
pseudocost.addObservation(col, delta, objdelta);
730-
analyzeSolution(objdelta, sol);
731-
732-
if (lp->unscaledPrimalFeasible(status) && integerfeasible) {
733-
double cutoffbnd = getCutoffBound();
734-
mipsolver.mipdata_->addIncumbent(
735-
lp->getLpSolver().getSolution().col_value, solobj,
736-
inheuristic ? kSolutionSourceHeuristic
737-
: kSolutionSourceBranching);
738-
739-
if (mipsolver.mipdata_->upper_limit < cutoffbnd)
740-
lp->setObjectiveLimit(mipsolver.mipdata_->upper_limit);
741-
}
742-
743-
if (lp->unscaledDualFeasible(status)) {
744-
upbound[candidate] = solobj;
745-
if (solobj > mipsolver.mipdata_->optimality_limit) {
746-
addBoundExceedingConflict();
747-
748-
bool pruned = solobj > getCutoffBound();
749-
if (pruned) mipsolver.mipdata_->debugSolution.nodePruned(localdom);
750-
751-
localdom.backtrack();
752-
lp->flushDomain(localdom);
753-
754-
branchDownwards(col, downval, fracval);
755-
nodestack[nodestack.size() - 2].opensubtrees = pruned ? 0 : 1;
756-
nodestack[nodestack.size() - 2].other_child_lb = solobj;
757-
nodestack[nodestack.size() - 2].skipDepthCount = 1;
758-
depthoffset -= 1;
759-
760-
return -1;
761-
}
762-
} else if (solobj > getCutoffBound()) {
763-
addBoundExceedingConflict();
764-
localdom.propagate();
765-
bool infeas = localdom.infeasible();
766-
if (infeas) {
767-
localdom.backtrack();
768-
lp->flushDomain(localdom);
769-
770-
branchDownwards(col, downval, fracval);
771-
nodestack[nodestack.size() - 2].opensubtrees = 0;
772-
nodestack[nodestack.size() - 2].skipDepthCount = 1;
773-
depthoffset -= 1;
774-
775-
return -1;
776-
}
777-
}
778-
} else if (status == HighsLpRelaxation::Status::kInfeasible) {
779-
mipsolver.mipdata_->debugSolution.nodePruned(localdom);
780-
addInfeasibleConflict();
781-
pseudocost.addCutoffObservation(col, true);
782-
localdom.backtrack();
783-
lp->flushDomain(localdom);
784-
785-
branchDownwards(col, downval, fracval);
786-
nodestack[nodestack.size() - 2].opensubtrees = 0;
787-
nodestack[nodestack.size() - 2].skipDepthCount = 1;
788-
depthoffset -= 1;
789-
790-
return -1;
791-
} else {
792-
// printf("todo2\n");
793-
// in case of an LP error we set the score of this variable to zero to
794-
// avoid choosing it as branching candidate if possible
795-
downscore[candidate] = 0.0;
796-
upscore[candidate] = 0.0;
797-
downscorereliable[candidate] = 1;
798-
upscorereliable[candidate] = 1;
799-
markBranchingVarUpReliableAtNode(col);
800-
markBranchingVarDownReliableAtNode(col);
801-
}
802-
803-
localdom.backtrack();
804-
lp->flushDomain(localdom);
708+
if (strongBranch(true)) return -1;
805709
}
806710
}
807711
}

0 commit comments

Comments
 (0)