OR-Tools  9.1
cp_model_lns.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cstdint>
18#include <limits>
19#include <numeric>
20#include <vector>
21
22#include "absl/container/flat_hash_set.h"
23#include "absl/synchronization/mutex.h"
27#include "ortools/sat/integer.h"
29#include "ortools/sat/rins.h"
32
33namespace operations_research {
34namespace sat {
35
38 SharedResponseManager* shared_response, SharedTimeLimit* shared_time_limit,
39 SharedBoundsManager* shared_bounds)
40 : SubSolver(""),
41 parameters_(*parameters),
42 model_proto_(*model_proto),
43 shared_time_limit_(shared_time_limit),
44 shared_bounds_(shared_bounds),
45 shared_response_(shared_response) {
46 CHECK(shared_response_ != nullptr);
47 if (shared_bounds_ != nullptr) {
48 shared_bounds_id_ = shared_bounds_->RegisterNewId();
49 }
50 *model_proto_with_only_variables_.mutable_variables() =
51 model_proto_.variables();
52 InitializeHelperData();
53 RecomputeHelperData();
55}
56
58 if (shared_bounds_ != nullptr) {
59 std::vector<int> model_variables;
60 std::vector<int64_t> new_lower_bounds;
61 std::vector<int64_t> new_upper_bounds;
62 shared_bounds_->GetChangedBounds(shared_bounds_id_, &model_variables,
63 &new_lower_bounds, &new_upper_bounds);
64
65 bool new_variables_have_been_fixed = false;
66
67 {
68 absl::MutexLock domain_lock(&domain_mutex_);
69
70 for (int i = 0; i < model_variables.size(); ++i) {
71 const int var = model_variables[i];
72 const int64_t new_lb = new_lower_bounds[i];
73 const int64_t new_ub = new_upper_bounds[i];
74 if (VLOG_IS_ON(3)) {
75 const auto& domain =
76 model_proto_with_only_variables_.variables(var).domain();
77 const int64_t old_lb = domain.Get(0);
78 const int64_t old_ub = domain.Get(domain.size() - 1);
79 VLOG(3) << "Variable: " << var << " old domain: [" << old_lb << ", "
80 << old_ub << "] new domain: [" << new_lb << ", " << new_ub
81 << "]";
82 }
83 const Domain old_domain = ReadDomainFromProto(
84 model_proto_with_only_variables_.variables(var));
85 const Domain new_domain =
86 old_domain.IntersectionWith(Domain(new_lb, new_ub));
87 if (new_domain.IsEmpty()) {
88 // This can mean two things:
89 // 1/ This variable is a normal one and the problem is UNSAT or
90 // 2/ This variable is optional, and its associated literal must be
91 // set to false.
92 //
93 // Currently, we wait for any full solver to pick the crossing bounds
94 // and do the correct stuff on their own. We do not want to have empty
95 // domain in the proto as this would means INFEASIBLE. So we just
96 // ignore such bounds here.
97 //
98 // TODO(user): We could set the optional literal to false directly in
99 // the bound sharing manager. We do have to be careful that all the
100 // different solvers have the same optionality definition though.
101 continue;
102 }
104 new_domain,
105 model_proto_with_only_variables_.mutable_variables(var));
106 new_variables_have_been_fixed |= new_domain.IsFixed();
107 }
108 }
109
110 // Only trigger the computation if needed.
111 if (new_variables_have_been_fixed) {
112 RecomputeHelperData();
113 }
114 }
115}
116
117void NeighborhoodGeneratorHelper::InitializeHelperData() {
118 type_to_constraints_.clear();
119 const int num_constraints = model_proto_.constraints_size();
120 for (int c = 0; c < num_constraints; ++c) {
121 const int type = model_proto_.constraints(c).constraint_case();
122 if (type >= type_to_constraints_.size()) {
123 type_to_constraints_.resize(type + 1);
124 }
125 type_to_constraints_[type].push_back(c);
126 }
127}
128
129void NeighborhoodGeneratorHelper::RecomputeHelperData() {
130 // Recompute all the data in case new variables have been fixed.
131 //
132 // TODO(user): Ideally we should ignore trivially true/false constraint, but
133 // this will duplicate already existing code :-( we should probably still do
134 // at least enforcement literal and clauses? We could maybe run a light
135 // presolve?
136 absl::MutexLock graph_lock(&graph_mutex_);
137 absl::ReaderMutexLock domain_lock(&domain_mutex_);
138
139 var_to_constraint_.assign(model_proto_.variables_size(), {});
140 constraint_to_var_.assign(model_proto_.constraints_size(), {});
141
142 for (int ct_index = 0; ct_index < model_proto_.constraints_size();
143 ++ct_index) {
144 for (const int var : UsedVariables(model_proto_.constraints(ct_index))) {
146 if (IsConstant(var)) continue;
147 var_to_constraint_[var].push_back(ct_index);
148 constraint_to_var_[ct_index].push_back(var);
149 }
150
151 // We replace intervals by their underlying integer variables.
153 for (const int interval :
154 UsedIntervals(model_proto_.constraints(ct_index))) {
155 for (const int var :
156 UsedVariables(model_proto_.constraints(interval))) {
158 if (IsConstant(var)) continue;
159 var_to_constraint_[var].push_back(ct_index);
160 constraint_to_var_[ct_index].push_back(var);
161 }
162 }
163 }
164 }
165
166 active_variables_.clear();
167 active_variables_set_.assign(model_proto_.variables_size(), false);
168
169 if (parameters_.lns_focus_on_decision_variables()) {
170 for (const auto& search_strategy : model_proto_.search_strategy()) {
171 for (const int var : search_strategy.variables()) {
172 const int pos_var = PositiveRef(var);
173 if (!active_variables_set_[pos_var] && !IsConstant(pos_var)) {
174 active_variables_set_[pos_var] = true;
175 active_variables_.push_back(pos_var);
176 }
177 }
178 }
179
180 // Revert to no focus if active_variables_ is empty().
181 if (!active_variables_.empty()) return;
182 }
183
184 // Add all non-constant variables.
185 for (int i = 0; i < model_proto_.variables_size(); ++i) {
186 if (!IsConstant(i)) {
187 active_variables_.push_back(i);
188 active_variables_set_[i] = true;
189 }
190 }
191}
192
194 return active_variables_set_[var];
195}
196
197bool NeighborhoodGeneratorHelper::IsConstant(int var) const {
198 return model_proto_with_only_variables_.variables(var).domain_size() == 2 &&
199 model_proto_with_only_variables_.variables(var).domain(0) ==
200 model_proto_with_only_variables_.variables(var).domain(1);
201}
202
204 const CpModelProto& source_model,
205 const absl::flat_hash_set<int>& fixed_variables_set,
206 const CpSolverResponse& initial_solution,
207 CpModelProto* output_model) const {
208 output_model->mutable_variables()->Clear();
209 output_model->mutable_variables()->Reserve(source_model.variables_size());
210 for (int i = 0; i < source_model.variables_size(); ++i) {
211 IntegerVariableProto* var_proto = output_model->add_variables();
212 const IntegerVariableProto& source_var_proto = source_model.variables(i);
213 // We only copy the variable names in debug mode.
214 if (DEBUG_MODE && !source_var_proto.name().empty()) {
215 var_proto->set_name(source_var_proto.name());
216 }
217 if (fixed_variables_set.contains(i)) {
218 const int64_t value = initial_solution.solution(i);
219 if (!DomainInProtoContains(source_model.variables(i), value)) {
220 return false;
221 }
222 var_proto->add_domain(value);
223 var_proto->add_domain(value);
224 } else {
225 *var_proto->mutable_domain() = source_var_proto.domain();
226 }
227 }
228 return true;
229}
230
232 Neighborhood neighborhood;
233 neighborhood.is_reduced = false;
234 neighborhood.is_generated = true;
235 {
236 absl::ReaderMutexLock lock(&domain_mutex_);
237 *neighborhood.delta.mutable_variables() =
238 model_proto_with_only_variables_.variables();
239 }
240 return neighborhood;
241}
242
244 Neighborhood neighborhood;
245 neighborhood.is_generated = false;
246 return neighborhood;
247}
248
250 const CpSolverResponse& initial_solution) const {
251 std::vector<int> active_intervals;
252 absl::ReaderMutexLock lock(&domain_mutex_);
253 for (const int i : TypeToConstraints(ConstraintProto::kInterval)) {
254 const ConstraintProto& interval_ct = ModelProto().constraints(i);
255 // We only look at intervals that are performed in the solution. The
256 // unperformed intervals should be automatically freed during the generation
257 // phase.
258 if (interval_ct.enforcement_literal().size() == 1) {
259 const int enforcement_ref = interval_ct.enforcement_literal(0);
260 const int enforcement_var = PositiveRef(enforcement_ref);
261 const int value = initial_solution.solution(enforcement_var);
262 if (RefIsPositive(enforcement_ref) == (value == 0)) {
263 continue;
264 }
265 }
266
267 // We filter out fixed intervals. Because of presolve, if there is an
268 // enforcement literal, it cannot be fixed.
269 if (interval_ct.enforcement_literal().empty()) {
270 if (interval_ct.interval().has_start_view()) {
271 bool is_constant = true;
272 for (const int v : interval_ct.interval().start_view().vars()) {
273 if (!IsConstant(v)) {
274 is_constant = false;
275 break;
276 }
277 }
278 for (const int v : interval_ct.interval().size_view().vars()) {
279 if (!IsConstant(v)) {
280 is_constant = false;
281 break;
282 }
283 }
284 for (const int v : interval_ct.interval().end_view().vars()) {
285 if (!IsConstant(v)) {
286 is_constant = false;
287 break;
288 }
289 }
290 if (is_constant) continue;
291 } else {
292 if (IsConstant(PositiveRef(interval_ct.interval().start())) &&
293 IsConstant(PositiveRef(interval_ct.interval().size())) &&
294 IsConstant(PositiveRef(interval_ct.interval().end()))) {
295 continue;
296 }
297 }
298 }
299
300 active_intervals.push_back(i);
301 }
302 return active_intervals;
303}
304
306 const CpSolverResponse& initial_solution) const {
307 struct HeadAndArcLiteral {
308 int head;
309 int literal;
310 };
311
312 std::vector<std::vector<int>> result;
313 absl::flat_hash_map<int, HeadAndArcLiteral> tail_to_head_and_arc_literal;
314
315 for (const int i : TypeToConstraints(ConstraintProto::kCircuit)) {
317
318 // Collect arcs.
319 int min_node = std::numeric_limits<int>::max();
320 tail_to_head_and_arc_literal.clear();
321 for (int i = 0; i < ct.literals_size(); ++i) {
322 const int literal = ct.literals(i);
323 const int head = ct.heads(i);
324 const int tail = ct.tails(i);
325 const int bool_var = PositiveRef(literal);
326 const int64_t value = initial_solution.solution(bool_var);
327 // Skip unselected arcs.
328 if (RefIsPositive(literal) == (value == 0)) continue;
329 // Ignore self loops.
330 if (head == tail) continue;
331 tail_to_head_and_arc_literal[tail] = {head, bool_var};
332 min_node = std::min(tail, min_node);
333 }
334 if (tail_to_head_and_arc_literal.empty()) continue;
335
336 // Unroll the path.
337 int current_node = min_node;
338 std::vector<int> path;
339 do {
340 auto it = tail_to_head_and_arc_literal.find(current_node);
341 CHECK(it != tail_to_head_and_arc_literal.end());
342 current_node = it->second.head;
343 path.push_back(it->second.literal);
344 } while (current_node != min_node);
345 result.push_back(std::move(path));
346 }
347
348 std::vector<HeadAndArcLiteral> route_starts;
349 for (const int i : TypeToConstraints(ConstraintProto::kRoutes)) {
351 tail_to_head_and_arc_literal.clear();
352 route_starts.clear();
353
354 // Collect route starts and arcs.
355 for (int i = 0; i < ct.literals_size(); ++i) {
356 const int literal = ct.literals(i);
357 const int head = ct.heads(i);
358 const int tail = ct.tails(i);
359 const int bool_var = PositiveRef(literal);
360 const int64_t value = initial_solution.solution(bool_var);
361 // Skip unselected arcs.
362 if (RefIsPositive(literal) == (value == 0)) continue;
363 // Ignore self loops.
364 if (head == tail) continue;
365 if (tail == 0) {
366 route_starts.push_back({head, bool_var});
367 } else {
368 tail_to_head_and_arc_literal[tail] = {head, bool_var};
369 }
370 }
371
372 // Unroll all routes.
373 for (const HeadAndArcLiteral& head_var : route_starts) {
374 std::vector<int> path;
375 int current_node = head_var.head;
376 path.push_back(head_var.literal);
377 do {
378 auto it = tail_to_head_and_arc_literal.find(current_node);
379 CHECK(it != tail_to_head_and_arc_literal.end());
380 current_node = it->second.head;
381 path.push_back(it->second.literal);
382 } while (current_node != 0);
383 result.push_back(std::move(path));
384 }
385 }
386
387 return result;
388}
389
391 const CpSolverResponse& initial_solution,
392 const absl::flat_hash_set<int>& variables_to_fix) const {
393 Neighborhood neighborhood;
394
395 bool copy_is_successful = true;
396 {
397 absl::ReaderMutexLock domain_lock(&domain_mutex_);
398 copy_is_successful =
399 CopyAndFixVariables(model_proto_with_only_variables_, variables_to_fix,
400 initial_solution, &neighborhood.delta);
401 }
402
403 if (!copy_is_successful) {
404 return NoNeighborhood();
405 }
406
407 AddSolutionHinting(initial_solution, &neighborhood.delta);
408
409 neighborhood.is_generated = true;
410 neighborhood.is_reduced = !variables_to_fix.empty();
411 // TODO(user): force better objective? Note that this is already done when the
412 // hint above is successfully loaded (i.e. if it passes the presolve
413 // correctly) since the solver will try to find better solution than the
414 // current one.
415 return neighborhood;
416}
417
419 const CpSolverResponse& initial_solution, CpModelProto* model_proto) const {
420 // Set the current solution as a hint.
421 model_proto->clear_solution_hint();
422 const auto is_fixed = [model_proto](int var) {
423 const IntegerVariableProto& var_proto = model_proto->variables(var);
424 return var_proto.domain_size() == 2 &&
425 var_proto.domain(0) == var_proto.domain(1);
426 };
427 for (int var = 0; var < model_proto->variables_size(); ++var) {
428 if (is_fixed(var)) continue;
429
430 model_proto->mutable_solution_hint()->add_vars(var);
431 model_proto->mutable_solution_hint()->add_values(
432 initial_solution.solution(var));
433 }
434}
435
437 const std::vector<int>& constraints_to_remove) const {
438 Neighborhood neighborhood = FullNeighborhood();
439
440 if (constraints_to_remove.empty()) return neighborhood;
441 neighborhood.is_reduced = false;
442 neighborhood.constraints_to_ignore = constraints_to_remove;
443 return neighborhood;
444}
445
447 const CpSolverResponse& initial_solution,
448 const std::vector<int>& relaxed_variables) const {
449 std::vector<bool> relaxed_variables_set(model_proto_.variables_size(), false);
450 for (const int var : relaxed_variables) relaxed_variables_set[var] = true;
451 absl::flat_hash_set<int> fixed_variables;
452 {
453 absl::ReaderMutexLock graph_lock(&graph_mutex_);
454 for (const int i : active_variables_) {
455 if (!relaxed_variables_set[i]) {
456 fixed_variables.insert(i);
457 }
458 }
459 }
460 return FixGivenVariables(initial_solution, fixed_variables);
461}
462
464 const CpSolverResponse& initial_solution) const {
465 const std::vector<int>& all_variables = ActiveVariables();
466 const absl::flat_hash_set<int> fixed_variables(all_variables.begin(),
467 all_variables.end());
468 return FixGivenVariables(initial_solution, fixed_variables);
469}
470
473}
474
475double NeighborhoodGenerator::GetUCBScore(int64_t total_num_calls) const {
476 absl::ReaderMutexLock mutex_lock(&generator_mutex_);
477 DCHECK_GE(total_num_calls, num_calls_);
478 if (num_calls_ <= 10) return std::numeric_limits<double>::infinity();
479 return current_average_ + sqrt((2 * log(total_num_calls)) / num_calls_);
480}
481
483 absl::MutexLock mutex_lock(&generator_mutex_);
484
485 // To make the whole update process deterministic, we currently sort the
486 // SolveData.
487 std::sort(solve_data_.begin(), solve_data_.end());
488
489 // This will be used to update the difficulty of this neighborhood.
490 int num_fully_solved_in_batch = 0;
491 int num_not_fully_solved_in_batch = 0;
492
493 for (const SolveData& data : solve_data_) {
495 ++num_calls_;
496
497 // INFEASIBLE or OPTIMAL means that we "fully solved" the local problem.
498 // If we didn't, then we cannot be sure that there is no improving solution
499 // in that neighborhood.
500 if (data.status == CpSolverStatus::INFEASIBLE ||
501 data.status == CpSolverStatus::OPTIMAL) {
502 ++num_fully_solved_calls_;
503 ++num_fully_solved_in_batch;
504 } else {
505 ++num_not_fully_solved_in_batch;
506 }
507
508 // It seems to make more sense to compare the new objective to the base
509 // solution objective, not the best one. However this causes issue in the
510 // logic below because on some problems the neighborhood can always lead
511 // to a better "new objective" if the base solution wasn't the best one.
512 //
513 // This might not be a final solution, but it does work ok for now.
514 const IntegerValue best_objective_improvement =
516 ? IntegerValue(CapSub(data.new_objective_bound.value(),
517 data.initial_best_objective_bound.value()))
518 : IntegerValue(CapSub(data.initial_best_objective.value(),
519 data.new_objective.value()));
520 if (best_objective_improvement > 0) {
521 num_consecutive_non_improving_calls_ = 0;
522 } else {
523 ++num_consecutive_non_improving_calls_;
524 }
525
526 // TODO(user): Weight more recent data.
527 // degrade the current average to forget old learnings.
528 const double gain_per_time_unit =
529 std::max(0.0, static_cast<double>(best_objective_improvement.value())) /
530 (1.0 + data.deterministic_time);
531 if (num_calls_ <= 100) {
532 current_average_ += (gain_per_time_unit - current_average_) / num_calls_;
533 } else {
534 current_average_ = 0.9 * current_average_ + 0.1 * gain_per_time_unit;
535 }
536
537 deterministic_time_ += data.deterministic_time;
538 }
539
540 // Update the difficulty.
541 difficulty_.Update(/*num_decreases=*/num_not_fully_solved_in_batch,
542 /*num_increases=*/num_fully_solved_in_batch);
543
544 // Bump the time limit if we saw no better solution in the last few calls.
545 // This means that as the search progress, we likely spend more and more time
546 // trying to solve individual neighborhood.
547 //
548 // TODO(user): experiment with resetting the time limit if a solution is
549 // found.
550 if (num_consecutive_non_improving_calls_ > 50) {
551 num_consecutive_non_improving_calls_ = 0;
552 deterministic_limit_ *= 1.02;
553
554 // We do not want the limit to go to high. Intuitively, the goal is to try
555 // out a lot of neighborhoods, not just spend a lot of time on a few.
556 deterministic_limit_ = std::min(60.0, deterministic_limit_);
557 }
558
559 solve_data_.clear();
560}
561
562namespace {
563
564void GetRandomSubset(double relative_size, std::vector<int>* base,
565 absl::BitGenRef random) {
566 if (base->empty()) return;
567
568 // TODO(user): we could generate this more efficiently than using random
569 // shuffle.
570 std::shuffle(base->begin(), base->end(), random);
571 const int target_size = std::round(relative_size * base->size());
572 base->resize(target_size);
573}
574
575} // namespace
576
578 const CpSolverResponse& initial_solution, double difficulty,
579 absl::BitGenRef random) {
580 std::vector<int> fixed_variables = helper_.ActiveVariables();
581 GetRandomSubset(1.0 - difficulty, &fixed_variables, random);
583 initial_solution, {fixed_variables.begin(), fixed_variables.end()});
584}
585
587 const CpSolverResponse& initial_solution, double difficulty,
588 absl::BitGenRef random) {
589 std::vector<int> active_constraints;
590 for (int ct = 0; ct < helper_.ModelProto().constraints_size(); ++ct) {
593 continue;
594 }
595 active_constraints.push_back(ct);
596 }
597
598 if (active_constraints.empty() ||
600 return helper_.FullNeighborhood();
601 }
602
603 std::shuffle(active_constraints.begin(), active_constraints.end(), random);
604
605 const int num_model_vars = helper_.ModelProto().variables_size();
606 const int num_model_constraints = helper_.ModelProto().constraints_size();
607
608 std::vector<bool> visited_variables_set(num_model_vars, false);
609 std::vector<int> relaxed_variables;
610
611 {
612 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
613 const int num_active_vars =
615 const int target_size = std::ceil(difficulty * num_active_vars);
616 CHECK_GT(target_size, 0);
617
618 for (const int constraint_index : active_constraints) {
619 CHECK_LT(constraint_index, num_model_constraints);
620 for (const int var : helper_.ConstraintToVar()[constraint_index]) {
621 if (visited_variables_set[var]) continue;
622 visited_variables_set[var] = true;
623 if (helper_.IsActive(var)) {
624 relaxed_variables.push_back(var);
625 if (relaxed_variables.size() == target_size) break;
626 }
627 }
628 if (relaxed_variables.size() == target_size) break;
629 }
630 }
631 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
632}
633
635 const CpSolverResponse& initial_solution, double difficulty,
636 absl::BitGenRef random) {
638 return helper_.FullNeighborhood();
639 }
640
641 const int num_model_vars = helper_.ModelProto().variables_size();
642 std::vector<bool> visited_variables_set(num_model_vars, false);
643 std::vector<int> relaxed_variables;
644 std::vector<int> visited_variables;
645
646 // It is important complexity wise to never scan a constraint twice!
647 const int num_model_constraints = helper_.ModelProto().constraints_size();
648 std::vector<bool> scanned_constraints(num_model_constraints, false);
649
650 std::vector<int> random_variables;
651 {
652 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
653
654 // The number of active variables can decrease asynchronously.
655 // We read the exact number while locked.
656 const int num_active_vars =
658 const int target_size = std::ceil(difficulty * num_active_vars);
659 CHECK_GT(target_size, 0) << difficulty << " " << num_active_vars;
660
661 const int first_var =
662 helper_.ActiveVariablesWhileHoldingLock()[absl::Uniform<int>(
663 random, 0, num_active_vars)];
664
665 visited_variables_set[first_var] = true;
666 visited_variables.push_back(first_var);
667 relaxed_variables.push_back(first_var);
668
669 for (int i = 0; i < visited_variables.size(); ++i) {
670 random_variables.clear();
671 // Collect all the variables that appears in the same constraints as
672 // visited_variables[i].
673 for (const int ct : helper_.VarToConstraint()[visited_variables[i]]) {
674 if (scanned_constraints[ct]) continue;
675 scanned_constraints[ct] = true;
676 for (const int var : helper_.ConstraintToVar()[ct]) {
677 if (visited_variables_set[var]) continue;
678 visited_variables_set[var] = true;
679 random_variables.push_back(var);
680 }
681 }
682 // We always randomize to change the partial subgraph explored
683 // afterwards.
684 std::shuffle(random_variables.begin(), random_variables.end(), random);
685 for (const int var : random_variables) {
686 if (relaxed_variables.size() < target_size) {
687 visited_variables.push_back(var);
688 if (helper_.IsActive(var)) {
689 relaxed_variables.push_back(var);
690 }
691 } else {
692 break;
693 }
694 }
695 if (relaxed_variables.size() >= target_size) break;
696 }
697 }
698 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
699}
700
702 const CpSolverResponse& initial_solution, double difficulty,
703 absl::BitGenRef random) {
704 const int num_model_constraints = helper_.ModelProto().constraints_size();
705 if (num_model_constraints == 0 ||
707 return helper_.FullNeighborhood();
708 }
709
710 const int num_model_vars = helper_.ModelProto().variables_size();
711 std::vector<bool> visited_variables_set(num_model_vars, false);
712 std::vector<int> relaxed_variables;
713
714 std::vector<bool> added_constraints(num_model_constraints, false);
715 std::vector<int> next_constraints;
716
717 // Start by a random constraint.
718 next_constraints.push_back(
719 absl::Uniform<int>(random, 0, num_model_constraints));
720 added_constraints[next_constraints.back()] = true;
721
722 std::vector<int> random_variables;
723 {
724 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
725 const int num_active_vars =
727 const int target_size = std::ceil(difficulty * num_active_vars);
728 CHECK_GT(target_size, 0);
729
730 while (relaxed_variables.size() < target_size) {
731 // Stop if we have a full connected component.
732 if (next_constraints.empty()) break;
733
734 // Pick a random unprocessed constraint.
735 const int i = absl::Uniform<int>(random, 0, next_constraints.size());
736 const int constraint_index = next_constraints[i];
737 std::swap(next_constraints[i], next_constraints.back());
738 next_constraints.pop_back();
739
740 // Add all the variable of this constraint and increase the set of next
741 // possible constraints.
742 CHECK_LT(constraint_index, num_model_constraints);
743 random_variables = helper_.ConstraintToVar()[constraint_index];
744 std::shuffle(random_variables.begin(), random_variables.end(), random);
745 for (const int var : random_variables) {
746 if (visited_variables_set[var]) continue;
747 visited_variables_set[var] = true;
748 if (helper_.IsActive(var)) {
749 relaxed_variables.push_back(var);
750 }
751 if (relaxed_variables.size() == target_size) break;
752
753 for (const int ct : helper_.VarToConstraint()[var]) {
754 if (added_constraints[ct]) continue;
755 added_constraints[ct] = true;
756 next_constraints.push_back(ct);
757 }
758 }
759 }
760 }
761 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
762}
763
764namespace {
765
767 if (interval.has_start_view()) return interval.start_view();
769 result.add_vars(interval.start());
770 result.add_coeffs(1);
771 return result;
772}
773
774LinearExpressionProto GetSize(const IntervalConstraintProto& interval) {
775 if (interval.has_size_view()) return interval.size_view();
776 LinearExpressionProto result;
777 result.add_vars(interval.size());
778 result.add_coeffs(1);
779 return result;
780}
781
782LinearExpressionProto GetEnd(const IntervalConstraintProto& interval) {
783 if (interval.has_end_view()) return interval.end_view();
784 LinearExpressionProto result;
785 result.add_vars(interval.end());
786 result.add_coeffs(1);
787 return result;
788}
789
790int64_t GetLinearExpressionValue(const LinearExpressionProto& expr,
791 const CpSolverResponse& initial_solution) {
792 int64_t result = expr.offset();
793 for (int i = 0; i < expr.vars_size(); ++i) {
794 result += expr.coeffs(i) * initial_solution.solution(expr.vars(i));
795 }
796 return result;
797}
798
799void AddLinearExpressionToConstraint(const int64_t coeff,
800 const LinearExpressionProto& expr,
801 LinearConstraintProto* constraint,
802 int64_t* rhs_offset) {
803 *rhs_offset -= coeff * expr.offset();
804 for (int i = 0; i < expr.vars_size(); ++i) {
805 constraint->add_vars(expr.vars(i));
806 constraint->add_coeffs(expr.coeffs(i) * coeff);
807 }
808}
809
810void AddPrecedenceConstraints(const absl::Span<const int> intervals,
811 const absl::flat_hash_set<int>& ignored_intervals,
812 const CpSolverResponse& initial_solution,
813 const NeighborhoodGeneratorHelper& helper,
814 Neighborhood* neighborhood) {
815 // Sort all non-relaxed intervals of this constraint by current start
816 // time.
817 std::vector<std::pair<int64_t, int>> start_interval_pairs;
818 for (const int i : intervals) {
819 if (ignored_intervals.contains(i)) continue;
820 const ConstraintProto& interval_ct = helper.ModelProto().constraints(i);
821
822 // TODO(user): we ignore size zero for now.
823 const LinearExpressionProto size_var = GetSize(interval_ct.interval());
824 if (GetLinearExpressionValue(size_var, initial_solution) == 0) continue;
825
826 const LinearExpressionProto start_var = GetStart(interval_ct.interval());
827 const int64_t start_value =
828 GetLinearExpressionValue(start_var, initial_solution);
829
830 start_interval_pairs.push_back({start_value, i});
831 }
832 std::sort(start_interval_pairs.begin(), start_interval_pairs.end());
833
834 // Add precedence between the remaining intervals, forcing their order.
835 for (int i = 0; i + 1 < start_interval_pairs.size(); ++i) {
836 const LinearExpressionProto before_start =
837 GetStart(helper.ModelProto()
838 .constraints(start_interval_pairs[i].second)
839 .interval());
840 const LinearExpressionProto before_end =
841 GetEnd(helper.ModelProto()
842 .constraints(start_interval_pairs[i].second)
843 .interval());
844 const LinearExpressionProto after_start =
845 GetStart(helper.ModelProto()
846 .constraints(start_interval_pairs[i + 1].second)
847 .interval());
848
849 // If the end was smaller we keep it that way, otherwise we just order the
850 // start variables.
851 LinearConstraintProto* linear =
852 neighborhood->delta.add_constraints()->mutable_linear();
853 linear->add_domain(std::numeric_limits<int64_t>::min());
854 int64_t rhs_offset = 0;
855 if (GetLinearExpressionValue(before_end, initial_solution) <=
856 GetLinearExpressionValue(after_start, initial_solution)) {
857 // If the end was smaller than the next start, keep it that way.
858 AddLinearExpressionToConstraint(1, before_end, linear, &rhs_offset);
859 } else {
860 // Otherwise, keep the same minimum separation. This is done in order
861 // to "simplify" the neighborhood.
862 rhs_offset = GetLinearExpressionValue(before_start, initial_solution) -
863 GetLinearExpressionValue(after_start, initial_solution);
864 AddLinearExpressionToConstraint(1, before_start, linear, &rhs_offset);
865 }
866
867 AddLinearExpressionToConstraint(-1, after_start, linear, &rhs_offset);
868 linear->add_domain(rhs_offset);
869
870 // The linear constraint should be satisfied by the current solution.
871 if (DEBUG_MODE) {
872 int64_t activity = 0;
873 for (int i = 0; i < linear->vars().size(); ++i) {
874 activity +=
875 linear->coeffs(i) * initial_solution.solution(linear->vars(i));
876 }
877 CHECK_GE(activity, linear->domain(0));
878 CHECK_LE(activity, linear->domain(1));
879 }
880 }
881}
882} // namespace
883
885 const absl::Span<const int> intervals_to_relax,
886 const CpSolverResponse& initial_solution,
887 const NeighborhoodGeneratorHelper& helper) {
888 Neighborhood neighborhood = helper.FullNeighborhood();
889 neighborhood.is_reduced =
890 (intervals_to_relax.size() <
892
893 // We will extend the set with some interval that we cannot fix.
894 absl::flat_hash_set<int> ignored_intervals(intervals_to_relax.begin(),
895 intervals_to_relax.end());
896
897 // Fix the presence/absence of non-relaxed intervals.
898 for (const int i : helper.TypeToConstraints(ConstraintProto::kInterval)) {
899 DCHECK_GE(i, 0);
900 if (ignored_intervals.contains(i)) continue;
901
902 const ConstraintProto& interval_ct = helper.ModelProto().constraints(i);
903 if (interval_ct.enforcement_literal().empty()) continue;
904
905 CHECK_EQ(interval_ct.enforcement_literal().size(), 1);
906 const int enforcement_ref = interval_ct.enforcement_literal(0);
907 const int enforcement_var = PositiveRef(enforcement_ref);
908 const int value = initial_solution.solution(enforcement_var);
909
910 // If the interval is not enforced, we just relax it. If it belongs to an
911 // exactly one constraint, and the enforced interval is not relaxed, then
912 // propagation will force this interval to stay not enforced. Otherwise,
913 // LNS will be able to change which interval will be enforced among all
914 // alternatives.
915 if (RefIsPositive(enforcement_ref) == (value == 0)) {
916 ignored_intervals.insert(i);
917 continue;
918 }
919
920 // Fix the value.
921 neighborhood.delta.mutable_variables(enforcement_var)->clear_domain();
922 neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
923 neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
924 }
925
926 for (const int c : helper.TypeToConstraints(ConstraintProto::kNoOverlap)) {
927 AddPrecedenceConstraints(
929 ignored_intervals, initial_solution, helper, &neighborhood);
930 }
931 for (const int c : helper.TypeToConstraints(ConstraintProto::kCumulative)) {
932 AddPrecedenceConstraints(
934 ignored_intervals, initial_solution, helper, &neighborhood);
935 }
936 for (const int c : helper.TypeToConstraints(ConstraintProto::kNoOverlap2D)) {
937 AddPrecedenceConstraints(
939 ignored_intervals, initial_solution, helper, &neighborhood);
940 AddPrecedenceConstraints(
942 ignored_intervals, initial_solution, helper, &neighborhood);
943 }
944
945 // Set the current solution as a hint.
946 helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
947 neighborhood.is_generated = true;
948
949 return neighborhood;
950}
951
953 const CpSolverResponse& initial_solution, double difficulty,
954 absl::BitGenRef random) {
955 std::vector<int> intervals_to_relax =
956 helper_.GetActiveIntervals(initial_solution);
957 GetRandomSubset(difficulty, &intervals_to_relax, random);
958
959 return GenerateSchedulingNeighborhoodForRelaxation(intervals_to_relax,
960 initial_solution, helper_);
961}
962
964 const CpSolverResponse& initial_solution, double difficulty,
965 absl::BitGenRef random) {
966 std::vector<std::pair<int64_t, int>> start_interval_pairs;
967 const std::vector<int> active_intervals =
968 helper_.GetActiveIntervals(initial_solution);
969 std::vector<int> intervals_to_relax;
970
971 if (active_intervals.empty()) return helper_.FullNeighborhood();
972
973 for (const int i : active_intervals) {
974 const ConstraintProto& interval_ct = helper_.ModelProto().constraints(i);
975 const LinearExpressionProto start_var = GetStart(interval_ct.interval());
976 const int64_t start_value =
977 GetLinearExpressionValue(start_var, initial_solution);
978 start_interval_pairs.push_back({start_value, i});
979 }
980 std::sort(start_interval_pairs.begin(), start_interval_pairs.end());
981 const int relaxed_size = std::floor(difficulty * start_interval_pairs.size());
982
983 std::uniform_int_distribution<int> random_var(
984 0, start_interval_pairs.size() - relaxed_size - 1);
985 const int random_start_index = random_var(random);
986
987 // TODO(user): Consider relaxing more than one time window
988 // intervals. This seems to help with Giza models.
989 for (int i = random_start_index; i < relaxed_size; ++i) {
990 intervals_to_relax.push_back(start_interval_pairs[i].second);
991 }
992
993 return GenerateSchedulingNeighborhoodForRelaxation(intervals_to_relax,
994 initial_solution, helper_);
995}
996
998 const CpSolverResponse& initial_solution, double difficulty,
999 absl::BitGenRef random) {
1000 const std::vector<std::vector<int>> all_paths =
1001 helper_.GetRoutingPaths(initial_solution);
1002
1003 // Collect all unique variables.
1004 absl::flat_hash_set<int> all_path_variables;
1005 for (auto& path : all_paths) {
1006 all_path_variables.insert(path.begin(), path.end());
1007 }
1008 std::vector<int> fixed_variables(all_path_variables.begin(),
1009 all_path_variables.end());
1010 std::sort(fixed_variables.begin(), fixed_variables.end());
1011 GetRandomSubset(1.0 - difficulty, &fixed_variables, random);
1013 initial_solution, {fixed_variables.begin(), fixed_variables.end()});
1014}
1015
1017 const CpSolverResponse& initial_solution, double difficulty,
1018 absl::BitGenRef random) {
1019 std::vector<std::vector<int>> all_paths =
1020 helper_.GetRoutingPaths(initial_solution);
1021
1022 // Collect all unique variables.
1023 absl::flat_hash_set<int> all_path_variables;
1024 for (const auto& path : all_paths) {
1025 all_path_variables.insert(path.begin(), path.end());
1026 }
1027
1028 // Select variables to relax.
1029 const int num_variables_to_relax =
1030 static_cast<int>(all_path_variables.size() * difficulty);
1031 absl::flat_hash_set<int> relaxed_variables;
1032 while (relaxed_variables.size() < num_variables_to_relax) {
1033 DCHECK(!all_paths.empty());
1034 const int path_index = absl::Uniform<int>(random, 0, all_paths.size());
1035 std::vector<int>& path = all_paths[path_index];
1036 const int path_size = path.size();
1037 const int segment_length =
1038 std::min(path_size, absl::Uniform<int>(random, 4, 8));
1039 const int segment_start =
1040 absl::Uniform<int>(random, 0, path_size - segment_length);
1041 for (int i = segment_start; i < segment_start + segment_length; ++i) {
1042 relaxed_variables.insert(path[i]);
1043 }
1044
1045 // Remove segment and clean up empty paths.
1046 path.erase(path.begin() + segment_start,
1047 path.begin() + segment_start + segment_length);
1048 if (path.empty()) {
1049 std::swap(all_paths[path_index], all_paths.back());
1050 all_paths.pop_back();
1051 }
1052 }
1053
1054 // Compute the set of variables to fix.
1055 absl::flat_hash_set<int> fixed_variables;
1056 for (const int var : all_path_variables) {
1057 if (!relaxed_variables.contains(var)) fixed_variables.insert(var);
1058 }
1059 return helper_.FixGivenVariables(initial_solution, fixed_variables);
1060}
1061
1063 const CpSolverResponse& initial_solution, double difficulty,
1064 absl::BitGenRef random) {
1065 std::vector<std::vector<int>> all_paths =
1066 helper_.GetRoutingPaths(initial_solution);
1067 // Remove a corner case where all paths are empty.
1068 if (all_paths.empty()) {
1069 return helper_.NoNeighborhood();
1070 }
1071
1072 // Collect all unique variables.
1073 absl::flat_hash_set<int> all_path_variables;
1074 for (const auto& path : all_paths) {
1075 all_path_variables.insert(path.begin(), path.end());
1076 }
1077
1078 // Select variables to relax.
1079 const int num_variables_to_relax =
1080 static_cast<int>(all_path_variables.size() * difficulty);
1081 absl::flat_hash_set<int> relaxed_variables;
1082
1083 // Relax the start and end of each path to ease relocation.
1084 for (const auto& path : all_paths) {
1085 relaxed_variables.insert(path.front());
1086 relaxed_variables.insert(path.back());
1087 }
1088
1089 // Randomize paths.
1090 for (auto& path : all_paths) {
1091 std::shuffle(path.begin(), path.end(), random);
1092 }
1093
1094 // Relax all variables (if possible) in one random path.
1095 const int path_to_clean = absl::Uniform<int>(random, 0, all_paths.size());
1096 while (relaxed_variables.size() < num_variables_to_relax &&
1097 !all_paths[path_to_clean].empty()) {
1098 relaxed_variables.insert(all_paths[path_to_clean].back());
1099 all_paths[path_to_clean].pop_back();
1100 }
1101 if (all_paths[path_to_clean].empty()) {
1102 std::swap(all_paths[path_to_clean], all_paths.back());
1103 all_paths.pop_back();
1104 }
1105
1106 // Relax more variables until the target is reached.
1107 while (relaxed_variables.size() < num_variables_to_relax) {
1108 DCHECK(!all_paths.empty());
1109 const int path_index = absl::Uniform<int>(random, 0, all_paths.size());
1110 relaxed_variables.insert(all_paths[path_index].back());
1111
1112 // Remove variable and clean up empty paths.
1113 all_paths[path_index].pop_back();
1114 if (all_paths[path_index].empty()) {
1115 std::swap(all_paths[path_index], all_paths.back());
1116 all_paths.pop_back();
1117 }
1118 }
1119
1120 // Compute the set of variables to fix.
1121 absl::flat_hash_set<int> fixed_variables;
1122 for (const int var : all_path_variables) {
1123 if (!relaxed_variables.contains(var)) fixed_variables.insert(var);
1124 }
1125 return helper_.FixGivenVariables(initial_solution, fixed_variables);
1126}
1127
1129 if (incomplete_solutions_ != nullptr) {
1130 return incomplete_solutions_->HasNewSolution();
1131 }
1132
1133 if (response_manager_ != nullptr) {
1134 if (response_manager_->SolutionsRepository().NumSolutions() == 0) {
1135 return false;
1136 }
1137 }
1138
1139 // At least one relaxation solution should be available to generate a
1140 // neighborhood.
1141 if (lp_solutions_ != nullptr && lp_solutions_->NumSolutions() > 0) {
1142 return true;
1143 }
1144
1145 if (relaxation_solutions_ != nullptr &&
1146 relaxation_solutions_->NumSolutions() > 0) {
1147 return true;
1148 }
1149 return false;
1150}
1151
1153 const CpSolverResponse& initial_solution, double difficulty,
1154 absl::BitGenRef random) {
1155 Neighborhood neighborhood = helper_.FullNeighborhood();
1156 neighborhood.is_generated = false;
1157
1158 const bool lp_solution_available =
1159 (lp_solutions_ != nullptr && lp_solutions_->NumSolutions() > 0);
1160
1161 const bool relaxation_solution_available =
1162 (relaxation_solutions_ != nullptr &&
1163 relaxation_solutions_->NumSolutions() > 0);
1164
1165 const bool incomplete_solution_available =
1166 (incomplete_solutions_ != nullptr &&
1167 incomplete_solutions_->HasNewSolution());
1168
1169 if (!lp_solution_available && !relaxation_solution_available &&
1170 !incomplete_solution_available) {
1171 return neighborhood;
1172 }
1173
1174 RINSNeighborhood rins_neighborhood;
1175 // Randomly select the type of relaxation if both lp and relaxation solutions
1176 // are available.
1177 // TODO(user): Tune the probability value for this.
1178 std::bernoulli_distribution random_bool(0.5);
1179 const bool use_lp_relaxation =
1180 (lp_solution_available && relaxation_solution_available)
1181 ? random_bool(random)
1182 : lp_solution_available;
1183 if (use_lp_relaxation) {
1184 rins_neighborhood =
1185 GetRINSNeighborhood(response_manager_,
1186 /*relaxation_solutions=*/nullptr, lp_solutions_,
1187 incomplete_solutions_, random);
1188 neighborhood.source_info =
1189 incomplete_solution_available ? "incomplete" : "lp";
1190 } else {
1191 CHECK(relaxation_solution_available || incomplete_solution_available);
1192 rins_neighborhood = GetRINSNeighborhood(
1193 response_manager_, relaxation_solutions_,
1194 /*lp_solutions=*/nullptr, incomplete_solutions_, random);
1195 neighborhood.source_info =
1196 incomplete_solution_available ? "incomplete" : "relaxation";
1197 }
1198
1199 if (rins_neighborhood.fixed_vars.empty() &&
1200 rins_neighborhood.reduced_domain_vars.empty()) {
1201 return neighborhood;
1202 }
1203
1204 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1205 // Fix the variables in the local model.
1206 for (const std::pair</*model_var*/ int, /*value*/ int64_t> fixed_var :
1207 rins_neighborhood.fixed_vars) {
1208 const int var = fixed_var.first;
1209 const int64_t value = fixed_var.second;
1210 if (var >= neighborhood.delta.variables_size()) continue;
1211 if (!helper_.IsActive(var)) continue;
1212
1213 if (!DomainInProtoContains(neighborhood.delta.variables(var), value)) {
1214 // TODO(user): Instead of aborting, pick the closest point in the domain?
1215 return neighborhood;
1216 }
1217
1218 neighborhood.delta.mutable_variables(var)->clear_domain();
1219 neighborhood.delta.mutable_variables(var)->add_domain(value);
1220 neighborhood.delta.mutable_variables(var)->add_domain(value);
1221 neighborhood.is_reduced = true;
1222 }
1223
1224 for (const std::pair</*model_var*/ int,
1225 /*domain*/ std::pair<int64_t, int64_t>>
1226 reduced_var : rins_neighborhood.reduced_domain_vars) {
1227 const int var = reduced_var.first;
1228 const int64_t lb = reduced_var.second.first;
1229 const int64_t ub = reduced_var.second.second;
1230 if (var >= neighborhood.delta.variables_size()) continue;
1231 if (!helper_.IsActive(var)) continue;
1232 Domain domain = ReadDomainFromProto(neighborhood.delta.variables(var));
1233 domain = domain.IntersectionWith(Domain(lb, ub));
1234 if (domain.IsEmpty()) {
1235 // TODO(user): Instead of aborting, pick the closest point in the domain?
1236 return neighborhood;
1237 }
1238 FillDomainInProto(domain, neighborhood.delta.mutable_variables(var));
1239 neighborhood.is_reduced = true;
1240 }
1241 neighborhood.is_generated = true;
1242 return neighborhood;
1243}
1244
1246 const CpSolverResponse& initial_solution, double difficulty,
1247 absl::BitGenRef random) {
1248 std::vector<int> removable_constraints;
1249 const int num_constraints = helper_.ModelProto().constraints_size();
1250 removable_constraints.reserve(num_constraints);
1251 for (int c = 0; c < num_constraints; ++c) {
1252 // Removing intervals is not easy because other constraint might require
1253 // them, so for now, we don't remove them.
1256 continue;
1257 }
1258 removable_constraints.push_back(c);
1259 }
1260
1261 const int target_size =
1262 std::round((1.0 - difficulty) * removable_constraints.size());
1263
1264 const int random_start_index =
1265 absl::Uniform<int>(random, 0, removable_constraints.size());
1266 std::vector<int> removed_constraints;
1267 removed_constraints.reserve(target_size);
1268 int c = random_start_index;
1269 while (removed_constraints.size() < target_size) {
1270 removed_constraints.push_back(removable_constraints[c]);
1271 ++c;
1272 if (c == removable_constraints.size()) {
1273 c = 0;
1274 }
1275 }
1276
1277 return helper_.RemoveMarkedConstraints(removed_constraints);
1278}
1279
1282 NeighborhoodGeneratorHelper const* helper, const std::string& name)
1283 : NeighborhoodGenerator(name, helper) {
1284 std::vector<int> removable_constraints;
1285 const int num_constraints = helper_.ModelProto().constraints_size();
1286 constraint_weights_.reserve(num_constraints);
1287 // TODO(user): Experiment with different starting weights.
1288 for (int c = 0; c < num_constraints; ++c) {
1295 constraint_weights_.push_back(3.0);
1296 num_removable_constraints_++;
1297 break;
1310 constraint_weights_.push_back(2.0);
1311 num_removable_constraints_++;
1312 break;
1320 constraint_weights_.push_back(1.0);
1321 num_removable_constraints_++;
1322 break;
1326 // Removing intervals is not easy because other constraint might require
1327 // them, so for now, we don't remove them.
1328 constraint_weights_.push_back(0.0);
1329 break;
1330 }
1331 }
1332}
1333
1334void WeightedRandomRelaxationNeighborhoodGenerator::
1335 AdditionalProcessingOnSynchronize(const SolveData& solve_data) {
1336 const IntegerValue best_objective_improvement =
1337 solve_data.new_objective_bound - solve_data.initial_best_objective_bound;
1338
1339 const std::vector<int>& removed_constraints =
1340 removed_constraints_[solve_data.neighborhood_id];
1341
1342 // Heuristic: We change the weights of the removed constraints if the
1343 // neighborhood is solved (status is OPTIMAL or INFEASIBLE) or we observe an
1344 // improvement in objective bounds. Otherwise we assume that the
1345 // difficulty/time wasn't right for us to record feedbacks.
1346 //
1347 // If the objective bounds are improved, we bump up the weights. If the
1348 // objective bounds are worse and the problem status is OPTIMAL, we bump down
1349 // the weights. Otherwise if the new objective bounds are same as current
1350 // bounds (which happens a lot on some instances), we do not update the
1351 // weights as we do not have a clear signal whether the constraints removed
1352 // were good choices or not.
1353 // TODO(user): We can improve this heuristic with more experiments.
1354 if (best_objective_improvement > 0) {
1355 // Bump up the weights of all removed constraints.
1356 for (int c : removed_constraints) {
1357 if (constraint_weights_[c] <= 90.0) {
1358 constraint_weights_[c] += 10.0;
1359 } else {
1360 constraint_weights_[c] = 100.0;
1361 }
1362 }
1363 } else if (solve_data.status == CpSolverStatus::OPTIMAL &&
1364 best_objective_improvement < 0) {
1365 // Bump down the weights of all removed constraints.
1366 for (int c : removed_constraints) {
1367 if (constraint_weights_[c] > 0.5) {
1368 constraint_weights_[c] -= 0.5;
1369 }
1370 }
1371 }
1372 removed_constraints_.erase(solve_data.neighborhood_id);
1373}
1374
1376 const CpSolverResponse& initial_solution, double difficulty,
1377 absl::BitGenRef random) {
1378 const int target_size =
1379 std::round((1.0 - difficulty) * num_removable_constraints_);
1380
1381 std::vector<int> removed_constraints;
1382
1383 // Generate a random number between (0,1) = u[i] and use score[i] =
1384 // u[i]^(1/w[i]) and then select top k items with largest scores.
1385 // Reference: https://utopia.duth.gr/~pefraimi/research/data/2007EncOfAlg.pdf
1386 std::vector<std::pair<double, int>> constraint_removal_scores;
1387 std::uniform_real_distribution<double> random_var(0.0, 1.0);
1388 for (int c = 0; c < constraint_weights_.size(); ++c) {
1389 if (constraint_weights_[c] <= 0) continue;
1390 const double u = random_var(random);
1391 const double score = std::pow(u, (1 / constraint_weights_[c]));
1392 constraint_removal_scores.push_back({score, c});
1393 }
1394 std::sort(constraint_removal_scores.rbegin(),
1395 constraint_removal_scores.rend());
1396 for (int i = 0; i < target_size; ++i) {
1397 removed_constraints.push_back(constraint_removal_scores[i].second);
1398 }
1399
1400 Neighborhood result = helper_.RemoveMarkedConstraints(removed_constraints);
1401
1402 absl::MutexLock mutex_lock(&generator_mutex_);
1403 result.id = next_available_id_;
1404 next_available_id_++;
1405 removed_constraints_.insert({result.id, removed_constraints});
1406 return result;
1407}
1408
1409} // namespace sat
1410} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:491
#define CHECK_LT(val1, val2)
Definition: base/logging.h:701
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
#define CHECK_GE(val1, val2)
Definition: base/logging.h:702
#define CHECK_GT(val1, val2)
Definition: base/logging.h:703
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:890
#define DCHECK(condition)
Definition: base/logging.h:885
#define CHECK_LE(val1, val2)
Definition: base/logging.h:700
#define VLOG(verboselevel)
Definition: base/logging.h:979
void Update(int num_decreases, int num_increases)
We call domain any subset of Int64 = [kint64min, kint64max].
bool IsFixed() const
Returns true iff the domain is reduced to a single value.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
bool IsEmpty() const
Returns true if this is the empty set.
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
const ::operations_research::sat::CircuitConstraintProto & circuit() const
const ::operations_research::sat::IntervalConstraintProto & interval() const
const ::operations_research::sat::NoOverlap2DConstraintProto & no_overlap_2d() const
const ::operations_research::sat::CumulativeConstraintProto & cumulative() const
const ::operations_research::sat::RoutesConstraintProto & routes() const
::PROTOBUF_NAMESPACE_ID::int32 enforcement_literal(int index) const
Definition: cp_model.pb.h:9380
const ::operations_research::sat::NoOverlapConstraintProto & no_overlap() const
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
const ::operations_research::sat::DecisionStrategyProto & search_strategy(int index) const
::operations_research::sat::IntegerVariableProto * mutable_variables(int index)
::operations_research::sat::IntegerVariableProto * add_variables()
const ::operations_research::sat::ConstraintProto & constraints(int index) const
::PROTOBUF_NAMESPACE_ID::int64 solution(int index) const
::PROTOBUF_NAMESPACE_ID::int32 intervals(int index) const
Definition: cp_model.pb.h:8128
void add_domain(::PROTOBUF_NAMESPACE_ID::int64 value)
Definition: cp_model.pb.h:6904
void set_name(ArgT0 &&arg0, ArgT... args)
PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final
::PROTOBUF_NAMESPACE_ID::int64 domain(int index) const
Definition: cp_model.pb.h:6893
::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::int64 > * mutable_domain()
Definition: cp_model.pb.h:6922
::PROTOBUF_NAMESPACE_ID::int32 end() const
Definition: cp_model.pb.h:7619
const ::operations_research::sat::LinearExpressionProto & size_view() const
Definition: cp_model.pb.h:7850
::PROTOBUF_NAMESPACE_ID::int32 size() const
Definition: cp_model.pb.h:7639
const ::operations_research::sat::LinearExpressionProto & start_view() const
Definition: cp_model.pb.h:7670
::PROTOBUF_NAMESPACE_ID::int32 start() const
Definition: cp_model.pb.h:7599
const ::operations_research::sat::LinearExpressionProto & end_view() const
Definition: cp_model.pb.h:7760
void add_coeffs(::PROTOBUF_NAMESPACE_ID::int64 value)
Definition: cp_model.pb.h:7124
::PROTOBUF_NAMESPACE_ID::int32 vars(int index) const
Definition: cp_model.pb.h:7066
void add_vars(::PROTOBUF_NAMESPACE_ID::int32 value)
Definition: cp_model.pb.h:7077
NeighborhoodGeneratorHelper(CpModelProto const *model_proto, SatParameters const *parameters, SharedResponseManager *shared_response, SharedTimeLimit *shared_time_limit=nullptr, SharedBoundsManager *shared_bounds=nullptr)
Definition: cp_model_lns.cc:36
Neighborhood FixAllVariables(const CpSolverResponse &initial_solution) const
const absl::Span< const int > TypeToConstraints(ConstraintProto::ConstraintCase type) const
Definition: cp_model_lns.h:168
Neighborhood FixGivenVariables(const CpSolverResponse &initial_solution, const absl::flat_hash_set< int > &variables_to_fix) const
bool CopyAndFixVariables(const CpModelProto &source_model, const absl::flat_hash_set< int > &fixed_variables_set, const CpSolverResponse &initial_solution, CpModelProto *output_model) const
bool DifficultyMeansFullNeighborhood(double difficulty) const
Definition: cp_model_lns.h:143
Neighborhood RelaxGivenVariables(const CpSolverResponse &initial_solution, const std::vector< int > &relaxed_variables) const
std::vector< int > GetActiveIntervals(const CpSolverResponse &initial_solution) const
const SharedResponseManager & shared_response() const
Definition: cp_model_lns.h:194
const std::vector< std::vector< int > > & VarToConstraint() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
Definition: cp_model_lns.h:162
bool IsActive(int var) const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
const std::vector< int > & ActiveVariablesWhileHoldingLock() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
Definition: cp_model_lns.h:151
Neighborhood RemoveMarkedConstraints(const std::vector< int > &constraints_to_remove) const
void AddSolutionHinting(const CpSolverResponse &initial_solution, CpModelProto *model_proto) const
const std::vector< std::vector< int > > & ConstraintToVar() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
Definition: cp_model_lns.h:158
std::vector< std::vector< int > > GetRoutingPaths(const CpSolverResponse &initial_solution) const
double GetUCBScore(int64_t total_num_calls) const
virtual void AdditionalProcessingOnSynchronize(const SolveData &solve_data)
Definition: cp_model_lns.h:392
const NeighborhoodGeneratorHelper & helper_
Definition: cp_model_lns.h:395
::PROTOBUF_NAMESPACE_ID::int32 y_intervals(int index) const
Definition: cp_model.pb.h:8037
::PROTOBUF_NAMESPACE_ID::int32 x_intervals(int index) const
Definition: cp_model.pb.h:7990
::PROTOBUF_NAMESPACE_ID::int32 intervals(int index) const
Definition: cp_model.pb.h:7939
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
void GetChangedBounds(int id, std::vector< int > *variables, std::vector< int64_t > *new_lower_bounds, std::vector< int64_t > *new_upper_bounds)
const SharedSolutionRepository< int64_t > & SolutionsRepository() const
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
WeightedRandomRelaxationNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, const std::string &name)
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
SatParameters parameters
CpModelProto const * model_proto
const std::string name
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
const bool DEBUG_MODE
Definition: macros.h:24
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
std::vector< int > UsedVariables(const ConstraintProto &ct)
bool RefIsPositive(int ref)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
bool DomainInProtoContains(const ProtoWithDomain &proto, int64_t value)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Neighborhood GenerateSchedulingNeighborhoodForRelaxation(const absl::Span< const int > intervals_to_relax, const CpSolverResponse &initial_solution, const NeighborhoodGeneratorHelper &helper)
RINSNeighborhood GetRINSNeighborhood(const SharedResponseManager *response_manager, const SharedRelaxationSolutionRepository *relaxation_solutions, const SharedLPSolutionRepository *lp_solutions, SharedIncompleteSolutionManager *incomplete_solutions, absl::BitGenRef random)
Definition: rins.cc:100
Collection of objects used to extend the Constraint Solver library.
int64_t CapSub(int64_t x, int64_t y)
Literal literal
Definition: optimization.cc:85
IntervalVar * interval
Definition: resource.cc:100
int64_t tail
int64_t head
std::vector< int > constraints_to_ignore
Definition: cp_model_lns.h:48
std::vector< std::pair< int, int64_t > > fixed_vars
Definition: rins.h:59
std::vector< std::pair< int, std::pair< int64_t, int64_t > > > reduced_domain_vars
Definition: rins.h:62
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41