39#define CFT_BOUND_EPSILON .999
40#define CFT_MAX_MULTIPLIER 1e9
41#define CFT_MEASURE_TIME
48#ifdef CFT_MEASURE_TIME
49#define CFT_MEASURE_SCOPE_DURATION(Timer) \
51 Defer pause_timer = [&] { Timer.Stop(); };
58#define CFT_MEASURE_SCOPE_DURATION(Timer)
65 Defer(T&& t) : T(std::forward<T>(t)) {}
66 ~Defer() { T::operator()(); }
69template <
typename IndexT = ElementIndex>
70class CoverCountersImpl {
72 CoverCountersImpl(
BaseInt nelems = 0) : cov_counters(nelems, 0) {}
73 void Reset(
BaseInt nelems) { cov_counters.assign(nelems, 0); }
74 BaseInt Size()
const {
return cov_counters.size(); }
75 BaseInt operator[](IndexT i)
const {
76 assert(i < IndexT(cov_counters.size()));
77 return cov_counters[
i];
80 template <
typename IterableT>
81 BaseInt Cover(
const IterableT& subset) {
83 for (IndexT i : subset) {
84 covered += cov_counters[
i] == 0 ? 1ULL : 0ULL;
90 template <
typename IterableT>
91 BaseInt Uncover(
const IterableT& subset) {
93 for (IndexT i : subset) {
95 uncovered += cov_counters[
i] == 0 ? 1ULL : 0ULL;
101 template <
typename IterableT>
102 bool IsRedundantCover(IterableT
const& subset)
const {
103 return absl::c_all_of(subset,
104 [&](IndexT i) {
return cov_counters[
i] > 0; });
108 template <
typename IterableT>
109 bool IsRedundantUncover(IterableT
const& subset)
const {
110 return absl::c_all_of(subset,
111 [&](IndexT i) {
return cov_counters[
i] > 1; });
115 util_intops::StrongVector<IndexT, BaseInt> cov_counters;
118using CoverCounters = CoverCountersImpl<ElementIndex>;
119using FullCoverCounters = CoverCountersImpl<FullElementIndex>;
123 const std::vector<SubsetIndex>& core_subsets)
124 : cost_(), subsets_() {
125 subsets_.reserve(core_subsets.size() + model.fixed_columns().size());
126 cost_ = model.fixed_cost();
127 for (FullSubsetIndex full_j : model.fixed_columns()) {
128 subsets_.push_back(full_j);
130 for (SubsetIndex core_j : core_subsets) {
131 FullSubsetIndex full_j = model.MapCoreToFullSubsetIndex(core_j);
132 AddSubset(full_j, model.subset_costs()[core_j]);
141 : squared_norm_(static_cast<
Cost>(model.num_elements())),
143 prev_best_lb_(
std::numeric_limits<
Cost>::lowest()),
144 max_iter_countdown_(10 *
145 model.num_focus_elements()),
146 exit_test_countdown_(300),
147 exit_test_period_(300),
148 unfixed_run_extension_(0),
150 last_min_lb_seen_(
std::numeric_limits<
Cost>::max()),
151 last_max_lb_seen_(.0),
152 step_size_update_countdown_(20),
153 step_size_update_period_(20)
159 if (--max_iter_countdown_ <= 0 || squared_norm_ <=
kTol) {
162 if (--exit_test_countdown_ > 0) {
168 exit_test_countdown_ = exit_test_period_;
169 Cost abs_improvement = best_lb - prev_best_lb_;
171 prev_best_lb_ = best_lb;
173 if (abs_improvement >= 1.0 || rel_improvement >= .001) {
179 VLOG(3) <<
"[SUBG] First iteration extension " << unfixed_run_extension_;
181 return unfixed_run_extension_++ >= extension;
186 direction_ = context.subgradient;
187 BaseInt extension = (context.model.fixed_columns().empty() ? 2 : 1);
188 if (unfixed_run_extension_ < extension) {
189 MakeMinimalCoverageSubgradient(context, direction_);
193 for (ElementIndex i : context.model.ElementRange()) {
194 Cost curr_i_mult = context.current_dual_state.multipliers()[i];
195 if ((curr_i_mult <= .0 && direction_[i] < .0) ||
200 squared_norm_ += direction_[i] * direction_[i];
203 if (squared_norm_ <=
kTol) {
204 delta_mults.assign(context.model.num_elements(), .0);
208 UpdateStepSize(context);
209 Cost upper_bound = context.best_solution.cost() - context.model.fixed_cost();
210 Cost lower_bound = context.current_dual_state.lower_bound();
211 Cost delta = upper_bound - lower_bound;
212 Cost step_constant = (step_size_ * delta) / squared_norm_;
214 for (ElementIndex i : context.model.ElementRange()) {
215 delta_mults[
i] = step_constant * direction_[
i];
216 DCHECK(std::isfinite(delta_mults[i]));
221 Cost lower_bound = context.current_dual_state.lower_bound();
222 last_min_lb_seen_ = std::min(last_min_lb_seen_, lower_bound);
223 last_max_lb_seen_ = std::max(last_max_lb_seen_, lower_bound);
225 if (--step_size_update_countdown_ <= 0) {
226 step_size_update_countdown_ = step_size_update_period_;
228 Cost delta = last_max_lb_seen_ - last_min_lb_seen_;
232 VLOG(4) <<
"[SUBG] Sep size set at " << step_size_;
233 }
else if (gap > .01) {
235 VLOG(4) <<
"[SUBG] Sep size set at " << step_size_;
237 last_min_lb_seen_ = std::numeric_limits<Cost>::max();
238 last_max_lb_seen_ = .0;
240 step_size_ = std::clamp(step_size_, 1e-6, 10.0);
246 lagrangian_solution_.clear();
247 const auto& reduced_costs = context.current_dual_state.reduced_costs();
248 for (SubsetIndex j : context.model.SubsetRange()) {
249 if (reduced_costs[j] < .0) {
250 lagrangian_solution_.push_back(j);
254 absl::c_sort(lagrangian_solution_, [&](SubsetIndex j1, SubsetIndex j2) {
255 return reduced_costs[j1] > reduced_costs[j2];
258 const auto& cols = context.model.columns();
259 for (SubsetIndex j : lagrangian_solution_) {
260 if (absl::c_all_of(cols[j], [&](
auto i) {
return subgradient[
i] < 0; })) {
261 absl::c_for_each(cols[j], [&](
auto i) { subgradient[
i] += 1.0; });
268 if (core_model.UpdateCore(context.best_lower_bound, context.best_multipliers,
269 context.best_solution, force)) {
270 prev_best_lb_ = std::numeric_limits<Cost>::lowest();
272 constexpr BaseInt min_iters = 10;
273 exit_test_countdown_ = std::max<BaseInt>(exit_test_countdown_, min_iters);
274 max_iter_countdown_ = std::max<BaseInt>(max_iter_countdown_, min_iters);
281 PrimalDualState& best_state) {
286 DualState dual_state = best_state.dual_state;
293 .current_dual_state = dual_state,
294 .best_lower_bound = best_lower_bound,
295 .best_multipliers = best_multipliers,
296 .best_solution = best_state.solution,
297 .subgradient = subgradient};
299 for (
size_t iter = 1; !cbs.ExitCondition(context); ++iter) {
304 VLOG(4) <<
"[SUBG] Dividing multipliers by 10";
306 model, [&](ElementIndex i,
Cost& i_mult) { i_mult /= 10.0; });
310 subgradient.
assign(model.num_elements(), 1.0);
311 for (SubsetIndex j : model.SubsetRange()) {
313 for (ElementIndex i : model.columns()[j]) {
314 subgradient[
i] -= 1.0;
319 cbs.ComputeMultipliersDelta(context, multipliers_delta);
321 i_mult = std::clamp<Cost>(i_mult + multipliers_delta[i], .0,
329 cbs.RunHeuristic(context,
solution);
331 solution.cost() < best_state.solution.cost()) {
335 VLOG_EVERY_N(4, 100) <<
"[SUBG] " << iter <<
": Bounds: Lower "
337 << best_lower_bound <<
" - Upper "
338 << best_state.solution.cost() - model.fixed_cost()
339 <<
", global " << best_state.solution.cost();
341 if (cbs.UpdateCoreModel(context, model)) {
343 i_mult = best_multipliers[
i];
349 if (cbs.UpdateCoreModel(context, model,
true)) {
351 i_mult = best_multipliers[
i];
355 best_state.dual_state.DualUpdate(model, [&](ElementIndex i,
Cost& i_mult) {
356 i_mult = best_multipliers[
i];
358 DCHECK_EQ(best_state.dual_state.lower_bound(), best_lower_bound);
360 VLOG(3) <<
"[SUBG] End - Bounds: Lower " << dual_state.
lower_bound()
361 <<
", best " << best_lower_bound <<
" - Upper "
362 << best_state.solution.cost() - model.fixed_cost() <<
", global "
363 << best_state.solution.cost();
377 static constexpr BaseInt removed_idx = -1;
378 static constexpr Cost max_score = std::numeric_limits<Cost>::max();
380 GreedyScores(
const SubModel& model,
const DualState& dual_state)
382 worst_good_score_(std::numeric_limits<
Cost>::lowest()),
384 reduced_costs_(dual_state.reduced_costs()),
385 covering_counts_(model.num_subsets()),
386 score_map_(model.num_subsets()) {
388 for (SubsetIndex j : model.SubsetRange()) {
389 DCHECK(model.column_size(j) > 0);
390 covering_counts_[j] = model.column_size(j);
391 Cost j_score = ComputeScore(reduced_costs_[j], covering_counts_[j]);
392 scores_.push_back({j_score, j});
394 DCHECK(std::isfinite(reduced_costs_[j]));
395 DCHECK(std::isfinite(j_score));
397 bad_size_ = scores_.size();
400 SubsetIndex FindMinScoreColumn(
const SubModel& model,
403 if (bad_size_ == scores_.size()) {
404 if (bad_size_ > model.num_focus_elements()) {
405 bad_size_ = bad_size_ - model.num_focus_elements();
406 absl::c_nth_element(scores_, scores_.begin() + bad_size_,
407 [](Score a, Score b) { return a.score > b.score; });
408 worst_good_score_ = scores_[bad_size_].score;
409 for (
BaseInt s = 0; s < scores_.size(); ++s) {
410 score_map_[scores_[s].idx] = s;
414 worst_good_score_ = max_score;
416 DCHECK(bad_size_ > 0 || worst_good_score_ == max_score);
420 *std::min_element(scores_.begin() + bad_size_, scores_.end(),
421 [](Score a, Score b) { return a.score < b.score; });
422 SubsetIndex j_star = min_score.idx;
423 DCHECK_LT(min_score.score, max_score);
430 template <
typename RowT,
typename ElementSpanT,
typename CondT>
431 BaseInt UpdateColumnsScoreOfRowsIf(
const RowT& rows,
433 const ElementSpanT& row_idxs, CondT cond) {
434 BaseInt processed_rows_count = 0;
435 for (ElementIndex i : row_idxs) {
440 ++processed_rows_count;
441 for (SubsetIndex j : rows[i]) {
442 covering_counts_[j] -= 1;
443 reduced_costs_[j] += multipliers[
i];
446 DCHECK_NE(s, removed_idx) <<
"Column is not in the score map";
447 scores_[s].score = ComputeScore(reduced_costs_[j], covering_counts_[j]);
449 if (covering_counts_[j] == 0) {
453 SwapScores(s, bad_size_ - 1);
456 SwapScores(s, scores_.size() - 1);
458 }
else if (s >= bad_size_ && scores_[s].score > worst_good_score_) {
460 SwapScores(s, bad_size_++);
464 return processed_rows_count;
469 SubsetIndex j1 = scores_[s1].idx, j2 = scores_[s2].idx;
470 std::swap(scores_[s1], scores_[s2]);
471 std::swap(score_map_[j1], score_map_[j2]);
475 static Cost ComputeScore(
Cost adjusted_reduced_cost,
477 DCHECK(std::isfinite(adjusted_reduced_cost)) <<
"Gamma is not finite";
478 return num_rows_covered == 0
480 : (adjusted_reduced_cost > .0
481 ? adjusted_reduced_cost / num_rows_covered
482 : adjusted_reduced_cost * num_rows_covered);
490 Cost worst_good_score_;
493 std::vector<Score> scores_;
506class RedundancyRemover {
508 RedundancyRemover(
const SubModel& model, CoverCounters& total_coverage)
510 total_coverage_(total_coverage),
511 partial_coverage_(model.num_elements()),
514 partial_cov_count_(0),
518 std::vector<SubsetIndex>& sol_subsets) {
519 for (SubsetIndex j : sol_subsets) {
520 if (total_coverage_.IsRedundantUncover(model.columns()[j]))
521 redund_set_.push_back({model.subset_costs()[j], j});
524 partial_cost_ += model.subset_costs()[j];
525 partial_cov_count_ += partial_coverage_.Cover(model.columns()[j]);
527 if (partial_cost_ >= cost_cutoff) {
528 return partial_cost_;
531 if (redund_set_.empty()) {
532 return partial_cost_;
534 absl::c_sort(redund_set_,
535 [](Score a, Score b) {
return a.score <
b.score; });
537 if (partial_cov_count_ < model.num_focus_elements()) {
539 HeuristicRedundancyRemoval(model, cost_cutoff);
542 for (Score redund_col : redund_set_) {
543 cols_to_remove_.push_back(redund_col.idx);
553 if (partial_cost_ < cost_cutoff) {
555 return absl::c_any_of(cols_to_remove_,
556 [j](
auto j_r) {
return j_r == j; });
559 return partial_cost_;
564 void HeuristicRedundancyRemoval(
const SubModel& model,
Cost cost_cutoff) {
565 while (!redund_set_.empty()) {
566 if (partial_cov_count_ == model.num_focus_elements()) {
569 SubsetIndex j = redund_set_.back().idx;
570 const auto& j_col = model.columns()[j];
571 redund_set_.pop_back();
573 if (total_coverage_.IsRedundantUncover(j_col)) {
574 total_coverage_.Uncover(j_col);
575 cols_to_remove_.push_back(j);
577 partial_cost_ += model.subset_costs()[j];
578 partial_cov_count_ += partial_coverage_.Cover(j_col);
585 std::vector<Score> redund_set_;
588 CoverCounters total_coverage_;
591 CoverCounters partial_coverage_;
600 Cost partial_cov_count_;
603 std::vector<SubsetIndex> cols_to_remove_;
609 const DualState& dual_state,
611 std::vector<SubsetIndex> sol_subsets;
613 std::numeric_limits<BaseInt>::max(), sol_subsets);
617Cost CoverGreedly(
const SubModel& model,
const DualState& dual_state,
619 std::vector<SubsetIndex>& sol_subsets) {
623 for (SubsetIndex j : sol_subsets) {
624 sol_cost += model.subset_costs()[j];
626 if (sol_cost >= cost_cutoff) {
628 return std::numeric_limits<Cost>::max();
630 if (sol_subsets.size() >= stop_size) {
636 BaseInt num_rows_to_cover = model.num_focus_elements();
637 CoverCounters covered_rows(model.num_elements());
638 for (SubsetIndex j : sol_subsets) {
639 num_rows_to_cover -= covered_rows.Cover(model.columns()[j]);
640 if (num_rows_to_cover == 0) {
646 GreedyScores scores(model, dual_state);
647 if (!sol_subsets.empty()) {
648 scores.UpdateColumnsScoreOfRowsIf(
649 model.rows(), dual_state.multipliers(), model.ElementRange(),
650 [&](
auto i) { return covered_rows[i] > 0; });
654 while (num_rows_to_cover > 0 && sol_subsets.size() < stop_size) {
655 SubsetIndex j_star = scores.FindMinScoreColumn(model, dual_state);
656 const auto& column = model.columns()[j_star];
657 num_rows_to_cover -= scores.UpdateColumnsScoreOfRowsIf(
658 model.rows(), dual_state.multipliers(), column,
659 [&](
auto i) { return covered_rows[i] == 0; });
660 sol_subsets.push_back(j_star);
661 covered_rows.Cover(column);
665 RedundancyRemover remover(model, covered_rows);
667 return remover.TryRemoveRedundantCols(model, cost_cutoff, sol_subsets);
675DualState MakeTentativeDualState(
const SubModel& model) {
676 DualState tentative_dual_state(model);
677 tentative_dual_state.DualUpdate(
678 model, [&](ElementIndex i,
Cost& i_multiplier) {
679 i_multiplier = std::numeric_limits<Cost>::max();
680 for (SubsetIndex j : model.rows()[i]) {
681 Cost candidate = model.subset_costs()[j] / model.column_size(j);
682 i_multiplier = std::min(i_multiplier, candidate);
685 return tentative_dual_state;
688void FixBestColumns(SubModel& model, PrimalDualState& state) {
689 auto& [best_sol, dual_state] = state;
700 for (ElementIndex core_i : model.ElementRange()) {
701 FullElementIndex full_i = model.MapCoreToFullElementIndex(core_i);
702 full_multipliers[full_i] = dual_state.multipliers()[core_i];
705 std::vector<SubsetIndex> cols_to_fix;
706 CoverCounters row_coverage(model.num_elements());
707 for (SubsetIndex j : model.SubsetRange()) {
708 if (dual_state.reduced_costs()[j] < -0.001) {
709 cols_to_fix.push_back(j);
710 row_coverage.Cover(model.columns()[j]);
716 return absl::c_any_of(model.columns()[j],
717 [&](ElementIndex i) { return row_coverage[i] > 1; });
722 cols_to_fix.size() + std::max<BaseInt>(1, model.num_elements() / 200);
723 CoverGreedly(model, state.dual_state, std::numeric_limits<Cost>::max(),
724 fix_at_least, cols_to_fix);
727 Cost fixed_cost_delta = model.FixMoreColumns(cols_to_fix);
729 VLOG(3) <<
"[3FIX] Fixed " << cols_to_fix.size()
730 <<
" new columns with cost: " << fixed_cost_delta;
731 VLOG(3) <<
"[3FIX] Globally fixed " << model.fixed_columns().size()
732 <<
" columns, with cost " << model.fixed_cost();
735 dual_state.DualUpdate(model, [&](ElementIndex core_i,
Cost& multiplier) {
738 multiplier = full_multipliers[model.MapCoreToFullElementIndex(core_i)];
742void RandomizeDualState(
const SubModel& model, DualState& dual_state,
747 dual_state.DualUpdate(model, [&](ElementIndex,
Cost& i_multiplier) {
748 i_multiplier *= absl::Uniform(rnd, 0.9, 1.1);
753void HeuristicCBs::RunHeuristic(
const SubgradientContext& context,
756 context.model, context.current_dual_state,
757 context.best_solution.cost() - context.model.fixed_cost());
760void HeuristicCBs::ComputeMultipliersDelta(
const SubgradientContext& context,
762 Cost squared_norm = .0;
763 for (ElementIndex i : context.model.ElementRange()) {
764 squared_norm += context.subgradient[i] * context.subgradient[i];
767 Cost lower_bound = context.current_dual_state.lower_bound();
768 Cost upper_bound = context.best_solution.cost() - context.model.fixed_cost();
769 DCHECK_GE(upper_bound, lower_bound);
770 Cost delta = upper_bound - lower_bound;
771 Cost step_constant = step_size_ * delta / squared_norm;
772 for (ElementIndex i : context.model.ElementRange()) {
773 delta_mults[i] = step_constant * context.subgradient[i];
777PrimalDualState RunThreePhase(SubModel& model,
const Solution& init_solution) {
779 DCHECK(ValidateSubModel(model));
781 PrimalDualState best_state = {.solution = init_solution,
782 .dual_state = MakeTentativeDualState(model)};
783 if (best_state.solution.Empty()) {
784 best_state.solution =
787 VLOG(2) <<
"[3PHS] Initial lower bound: "
788 << best_state.dual_state.lower_bound()
789 <<
", Initial solution cost: " << best_state.solution.cost()
790 <<
", Starting 3-phase algorithm";
792 PrimalDualState curr_state = best_state;
794 std::mt19937 rnd(0xcf7);
795 while (model.num_focus_elements() > 0) {
797 VLOG(2) <<
"[3PHS] 3Phase iteration: " << iter_count;
798 VLOG(2) <<
"[3PHS] Active size: rows " << model.num_focus_elements() <<
"/"
799 << model.num_elements() <<
", columns " << model.num_focus_subsets()
800 <<
"/" << model.num_subsets();
803 BoundCBs dual_bound_cbs(model);
804 VLOG(2) <<
"[3PHS] Subgradient Phase:";
805 SubgradientOptimization(model, dual_bound_cbs, curr_state);
806 if (iter_count == 1) {
807 best_state.dual_state = curr_state.dual_state;
809 if (curr_state.dual_state.lower_bound() >=
815 HeuristicCBs heuristic_cbs;
816 heuristic_cbs.set_step_size(dual_bound_cbs.step_size());
817 VLOG(2) <<
"[3PHS] Heuristic Phase:";
819 if (iter_count == 1 && best_state.dual_state.lower_bound() <
820 curr_state.dual_state.lower_bound()) {
821 best_state.dual_state = curr_state.dual_state;
823 if (curr_state.solution.cost() < best_state.solution.cost()) {
824 best_state.solution = curr_state.solution;
826 if (curr_state.dual_state.lower_bound() >=
831 VLOG(2) <<
"[3PHS] 3Phase Bounds: Residual Lower "
832 << curr_state.dual_state.lower_bound() + model.fixed_cost()
833 <<
", Upper " << best_state.solution.cost();
836 FixBestColumns(model, curr_state);
837 RandomizeDualState(model, curr_state.dual_state, rnd);
840 VLOG(2) <<
"[3PHS] 3Phase End: ";
841 VLOG(2) <<
"[3PHS] Bounds: Residual Lower "
842 << curr_state.dual_state.lower_bound() + model.fixed_cost()
843 <<
", Upper " << best_state.solution.cost();
854struct GapContribution {
859std::vector<FullSubsetIndex> SelectColumnByGapContribution(
860 const SubModel& model,
const PrimalDualState& best_state,
862 const auto& [
solution, dual_state] = best_state;
864 FullCoverCounters row_coverage(model.num_elements());
865 auto full_model = model.StrongTypedFullModelView();
867 for (FullSubsetIndex j :
solution.subsets()) {
868 row_coverage.Cover(full_model.columns()[j]);
871 std::vector<GapContribution> gap_contributions;
872 for (FullSubsetIndex j :
solution.subsets()) {
874 Cost reduced_cost = dual_state.reduced_costs()[
static_cast<SubsetIndex
>(j)];
875 for (FullElementIndex i : full_model.columns()[j]) {
876 Cost i_mult = dual_state.multipliers()[
static_cast<ElementIndex
>(
i)];
877 j_gap += i_mult * (1.0 - 1.0 / row_coverage[
i]);
878 reduced_cost -= i_mult;
880 j_gap += std::max<Cost>(reduced_cost, 0.0);
881 gap_contributions.push_back({j_gap, j});
883 absl::c_sort(gap_contributions, [](GapContribution g1, GapContribution g2) {
884 return g1.gap < g2.gap;
888 row_coverage.Reset(model.num_elements());
889 std::vector<FullSubsetIndex> cols_to_fix;
890 for (
auto [j_gap, j] : gap_contributions) {
891 covered_rows += row_coverage.Cover(full_model.columns()[j]);
892 if (covered_rows > nrows_to_fix) {
895 cols_to_fix.push_back(
static_cast<FullSubsetIndex
>(j));
902 const Solution& init_solution) {
905 PrimalDualState best_state = {.solution = init_solution,
906 .dual_state = MakeTentativeDualState(model)};
907 if (best_state.solution.Empty()) {
908 best_state.solution =
912 Cost cost_cutoff = std::numeric_limits<Cost>::lowest();
913 double fix_minimum = .3;
914 double fix_increment = 1.1;
915 double fix_fraction = fix_minimum;
917 for (
BaseInt iter_counter = 0; model.num_elements() > 0; ++iter_counter) {
918 VLOG(1) <<
"[CFTH] Refinement iteration: " << iter_counter;
919 Cost fixed_cost_before = model.fixed_cost();
921 if (iter_counter == 0) {
922 best_state.dual_state = std::move(dual_state);
924 if (
solution.cost() < best_state.solution.cost()) {
926 fix_fraction = fix_minimum;
928 cost_cutoff = std::max<Cost>(cost_cutoff,
929 fixed_cost_before + dual_state.lower_bound());
930 VLOG(1) <<
"[CFTH] Refinement Bounds: Residual Lower " << cost_cutoff
931 <<
", Real Lower " << best_state.dual_state.lower_bound()
932 <<
", Upper " << best_state.solution.cost();
937 fix_fraction = std::min(1.0, fix_fraction * fix_increment);
938 std::vector<FullSubsetIndex> cols_to_fix = SelectColumnByGapContribution(
939 model, best_state, model.num_elements() * fix_fraction);
941 if (!cols_to_fix.empty()) {
942 model.ResetColumnFixing(cols_to_fix, best_state.dual_state);
944 VLOG(1) <<
"[CFTH] Fixed " << cols_to_fix.size()
945 <<
" new columns with cost: " << model.fixed_cost();
946 VLOG(1) <<
"[CFTH] Model sizes: rows " << model.num_focus_elements() <<
"/"
947 << model.num_elements() <<
", columns " << model.num_focus_subsets()
948 <<
"/" << model.num_subsets();
951#ifdef CFT_MEASURE_TIME
952 double subg_t = subgradient_time.
Get();
953 double greedy_t = greedy_time.
Get();
954 double three_phase_t = three_phase_time.
Get();
955 double refinement_t = refinement_time.
Get();
957 printf(
"Subgradient time: %8.2f (%.1f%%)\n", subg_t,
958 100 * subg_t / refinement_t);
959 printf(
"Greedy Heur time: %8.2f (%.1f%%)\n", greedy_t,
960 100 * greedy_t / refinement_t);
961 printf(
"SubG - Greedy time: %8.2f (%.1f%%)\n", subg_t - greedy_t,
962 100 * (subg_t - greedy_t) / refinement_t);
963 printf(
"3Phase time: %8.2f (%.1f%%)\n", three_phase_t,
964 100 * three_phase_t / refinement_t);
965 printf(
"3Phase - Subg time: %8.2f (%.1f%%)\n", three_phase_t - subg_t,
966 100 * (three_phase_t - subg_t) / refinement_t);
967 printf(
"Total CFT time: %8.2f (%.1f%%)\n", refinement_t, 100.0);
978std::vector<FullSubsetIndex> ComputeTentativeFocus(
StrongModelView full_model) {
979 std::vector<FullSubsetIndex> columns_focus;
981 if (full_model.num_subsets() <= 2 * kMinCov * full_model.num_elements()) {
982 columns_focus.resize(full_model.num_subsets());
983 absl::c_iota(columns_focus, FullSubsetIndex(0));
984 return columns_focus;
987 columns_focus.reserve(full_model.num_elements() * kMinCov);
991 for (
const auto& row : full_model.rows()) {
993 for (FullSubsetIndex j : row) {
994 if (--countdown <= 0) {
999 columns_focus.push_back(j);
1004 absl::c_sort(columns_focus);
1005 return columns_focus;
1009const std::vector<FullSubsetIndex>&
1010FullToCoreModel::ColumnSelector::ComputeNewSelection(
1012 const std::vector<FullSubsetIndex>& forced_columns,
1014 selected_.assign(full_model.num_subsets(),
false);
1015 row_cover_counts_.assign(full_model.num_elements(), 0);
1016 rows_left_to_cover_ = full_model.num_focus_elements();
1018 candidates_.clear();
1021 for (FullSubsetIndex full_j : forced_columns) {
1022 SubsetIndex j =
static_cast<SubsetIndex
>(full_j);
1023 if (full_model.IsFocusCol(j)) {
1024 SelectColumn(full_model, j);
1028 auto subset_range = full_model.SubsetRange();
1029 candidates_ = {subset_range.begin(), subset_range.end()};
1030 absl::c_sort(candidates_, [&](
auto j1,
auto j2) {
1031 return reduced_costs[j1] < reduced_costs[j2];
1033 first_unselected_ = candidates_.begin();
1035 SelecteMinRedCostColumns(full_model, reduced_costs);
1036 SelectMinRedCostByRow(full_model, reduced_costs);
1037 absl::c_sort(selection_);
1041bool FullToCoreModel::ColumnSelector::SelectColumn(
FilterModelView full_model,
1046 for (ElementIndex i : full_model.columns()[j]) {
1047 selected_[j] =
true;
1048 if (++row_cover_counts_[i] == kMinCov) {
1049 --rows_left_to_cover_;
1053 selection_.push_back(
static_cast<FullSubsetIndex
>(j));
1055 return selected_[j];
1058void FullToCoreModel::ColumnSelector::SelecteMinRedCostColumns(
1061 BaseInt max_size = kMinCov * full_model.num_elements();
1062 auto it = first_unselected_;
1063 while (it != candidates_.end() && reduced_costs[*it] < 0.1 &&
1064 selected_size < max_size) {
1065 selected_size += SelectColumn(full_model, *it++) ? 1 : 0;
1067 first_unselected_ = it;
1070void FullToCoreModel::ColumnSelector::SelectMinRedCostByRow(
1072 auto it = first_unselected_;
1073 while (it != candidates_.end() && rows_left_to_cover_ > 0) {
1074 SubsetIndex j = *it++;
1078 for (ElementIndex i : full_model.columns()[j]) {
1079 ++row_cover_counts_[
i];
1080 rows_left_to_cover_ += (row_cover_counts_[
i] == kMinCov ? 1 : 0);
1081 selected_[j] = selected_[j] || (row_cover_counts_[
i] <= kMinCov);
1084 selection_.push_back(
static_cast<FullSubsetIndex
>(j));
1089FullToCoreModel::FullToCoreModel(
const Model* full_model)
1091 full_model_(full_model),
1092 is_focus_col_(full_model->num_subsets(),
true),
1093 is_focus_row_(full_model->num_elements(),
true),
1094 prev_best_lower_bound_(std::numeric_limits<
Cost>::lowest()),
1095 full_dual_state_(*full_model),
1096 best_dual_state_(full_dual_state_) {
1097 ResetPricingPeriod();
1099 DCHECK(FullToSubModelInvariantCheck());
1103 update_countdown_ = 10;
1104 update_period_ = 10;
1105 update_max_period_ = std::min<BaseInt>(1000, full_model_->num_elements() / 3);
1109 const std::vector<SubsetIndex>& columns_to_fix) {
1111 for (SubsetIndex core_j : columns_to_fix) {
1113 DCHECK(IsFocusCol(full_j));
1115 bool any_active =
false;
1116 for (FullElementIndex full_i : typed_full_model.
columns()[full_j]) {
1122 for (FullSubsetIndex full_j : typed_full_model.
SubsetRange()) {
1123 if (!IsFocusCol(full_j)) {
1126 IsFocusCol(full_j) =
false;
1127 for (FullElementIndex full_i : typed_full_model.
columns()[full_j]) {
1128 if (IsFocusRow(full_i)) {
1129 IsFocusCol(full_j) =
true;
1135 ResetPricingPeriod();
1136 Cost fixed_cost = base::FixMoreColumns(columns_to_fix);
1137 DCHECK(FullToSubModelInvariantCheck());
1141void FullToCoreModel::ResetColumnFixing(
1142 const std::vector<FullSubsetIndex>& full_columns_to_fix,
1143 const DualState& dual_state) {
1144 is_focus_col_.assign(full_model_->num_subsets(),
true);
1145 is_focus_row_.assign(full_model_->num_elements(),
true);
1147 full_dual_state_ = dual_state;
1153 const auto& selection = col_selector_.ComputeNewSelection(
1155 full_dual_state_.reduced_costs());
1160 std::vector<SubsetIndex> columns_to_fix;
1162 for (FullSubsetIndex full_j : full_columns_to_fix) {
1164 columns_to_fix.push_back(core_j);
1169 DCHECK_EQ(columns_to_fix.size(), full_columns_to_fix.size());
1170 FixMoreColumns(columns_to_fix);
1171 DCHECK(FullToSubModelInvariantCheck());
1174void FullToCoreModel::SizeUpdate() {
1175 is_focus_col_.resize(full_model_->num_subsets(),
true);
1178bool FullToCoreModel::IsTimeToUpdate(
Cost best_lower_bound,
bool force) {
1179 if (!force && --update_countdown_ > 0) {
1182 if (best_lower_bound == prev_best_lower_bound_) {
1185 prev_best_lower_bound_ = best_lower_bound;
1191 const auto& selection = col_selector_.ComputeNewSelection(
1193 full_dual_state_.reduced_costs());
1197 VLOG(3) <<
"[F2CU] Core-update: Lower bounds: real "
1198 << full_dual_state_.lower_bound() <<
", core " << best_lower_bound
1205 const Solution& best_solution,
bool force) {
1206 if (!IsTimeToUpdate(best_lower_bound, force)) {
1209 UpdateMultipliers(best_multipliers);
1210 ComputeAndSetFocus(best_lower_bound, best_solution);
1211 DCHECK(FullToSubModelInvariantCheck());
1216 Cost core_lower_bound,
1217 Cost core_upper_bound) {
1218 DCHECK_GE(core_lower_bound + 1e-6, full_dual_state.
lower_bound());
1219 DCHECK_GE(core_upper_bound, .0);
1223 if (ratio <= 1e-6) {
1224 update_period_ = std::min<BaseInt>(update_max_period_, 10 * update_period_);
1225 }
else if (ratio <= 0.02) {
1226 update_period_ = std::min<BaseInt>(update_max_period_, 5 * update_period_);
1227 }
else if (ratio <= 0.2) {
1228 update_period_ = std::min<BaseInt>(update_max_period_, 2 * update_period_);
1230 update_period_ = 10;
1232 update_countdown_ = update_period_;
1235Cost FullToCoreModel::UpdateMultipliers(
1237 auto fixing_full_model = FixingFullModelView();
1238 full_dual_state_.DualUpdate(
1239 fixing_full_model, [&](ElementIndex full_i,
Cost& i_mult) {
1240 ElementIndex core_i =
1241 MapFullToCoreElementIndex(
static_cast<FullElementIndex
>(full_i));
1242 i_mult = core_multipliers[core_i];
1257 full_dual_state_.lower_bound() > best_dual_state_.lower_bound()) {
1258 best_dual_state_ = full_dual_state_;
1260 return full_dual_state_.lower_bound();
1263bool FullToCoreModel::FullToSubModelInvariantCheck() {
1264 const SubModel& sub_model = *
this;
1267 if (typed_full_model.
num_subsets() < sub_model.num_subsets()) {
1268 std::cerr << absl::StrCat(
"SubModelView has ", sub_model.num_subsets(),
1269 " subsets, but the full model has ",
1273 if (typed_full_model.
num_elements() != sub_model.num_elements()) {
1274 std::cerr << absl::StrCat(
"SubModelView has ", sub_model.num_elements(),
1275 " elements, but the full model has ",
1279 for (SubsetIndex core_j : sub_model.SubsetRange()) {
1280 FullSubsetIndex full_j = sub_model.MapCoreToFullSubsetIndex(core_j);
1281 if (!is_focus_col_[
static_cast<SubsetIndex
>(full_j)]) {
1282 std::cerr << absl::StrCat(
"Subset ", core_j,
1283 " in sub-model but its mapped subset ", full_j,
1284 " not found in full model view.\n");
1288 const auto& core_column = sub_model.columns()[core_j];
1289 if (core_column.begin() == core_column.end()) {
1290 std::cerr << absl::StrCat(
"Core subset ", core_j,
" empty.\n");
1294 const auto& full_column = typed_full_model.
columns()[full_j];
1295 if (full_column.begin() == full_column.end()) {
1296 std::cerr << absl::StrCat(
"Full subset ", full_j,
" empty.\n");
1301 auto core_it = core_column.begin();
1302 for (FullElementIndex full_i : typed_full_model.
columns()[full_j]) {
1303 if (sub_model.MapFullToCoreElementIndex(full_i) != *core_it) {
1306 if (sub_model.MapCoreToFullElementIndex(*core_it) != full_i) {
1307 std::cerr << absl::StrCat(
1308 "Subset ", core_j,
" in sub-model has mapped element ", *core_it,
1309 " but it is not the same as the full model.\n");
1312 if (++core_it == core_column.end()) {
1316 if (core_it != core_column.end()) {
1317 std::cerr << absl::StrCat(
"Subset ", core_j,
1318 " in sub-model has no mapped element ",
1319 *core_it,
" in full model view.\n");
1324 for (ElementIndex core_i : sub_model.ElementRange()) {
1325 const auto& core_row = sub_model.rows()[core_i];
1326 if (core_row.begin() == core_row.end()) {
1327 std::cerr << absl::StrCat(
"Core row ", core_i,
" empty.\n");
1331 FullElementIndex full_i = sub_model.MapCoreToFullElementIndex(core_i);
1332 if (!is_focus_row_[
static_cast<ElementIndex
>(full_i)]) {
1333 std::cerr << absl::StrCat(
"Element ", core_i,
1334 " in sub-model but its mapped element ", full_i,
1335 " not found in full model view.\n");
1340 for (FullElementIndex full_i : typed_full_model.
ElementRange()) {
1341 if (!IsFocusRow(full_i)) {
1344 ElementIndex core_i = sub_model.MapFullToCoreElementIndex(full_i);
1345 if (core_i < ElementIndex() ||
1346 ElementIndex(sub_model.num_elements()) < core_i) {
1347 std::cerr << absl::StrCat(
1349 " in full model view but has no mapped element in sub-model.\n");