Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_lns.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <cmath>
18#include <cstdint>
19#include <deque>
20#include <functional>
21#include <limits>
22#include <random>
23#include <string>
24#include <tuple>
25#include <utility>
26#include <vector>
27
28#include "absl/algorithm/container.h"
29#include "absl/base/log_severity.h"
30#include "absl/container/flat_hash_map.h"
31#include "absl/container/flat_hash_set.h"
32#include "absl/log/check.h"
33#include "absl/meta/type_traits.h"
34#include "absl/random/bit_gen_ref.h"
35#include "absl/random/distributions.h"
36#include "absl/strings/str_cat.h"
37#include "absl/strings/str_join.h"
38#include "absl/synchronization/mutex.h"
39#include "absl/types/span.h"
40#include "google/protobuf/arena.h"
44#include "ortools/sat/cp_model.pb.h"
48#include "ortools/sat/integer.h"
51#include "ortools/sat/model.h"
53#include "ortools/sat/rins.h"
54#include "ortools/sat/sat_parameters.pb.h"
57#include "ortools/sat/util.h"
64
65namespace operations_research {
66namespace sat {
67
69 CpModelProto const* model_proto, SatParameters const* parameters,
70 SharedResponseManager* shared_response, SharedBoundsManager* shared_bounds)
71 : SubSolver("neighborhood_helper", HELPER),
72 parameters_(*parameters),
73 model_proto_(*model_proto),
74 shared_bounds_(shared_bounds),
75 shared_response_(shared_response) {
76 // Initialize proto memory.
77 simplified_model_proto_ =
78 google::protobuf::Arena::Create<CpModelProto>(&local_arena_);
79 model_proto_with_only_variables_ =
80 google::protobuf::Arena::Create<CpModelProto>(&local_arena_);
81
82 CHECK(shared_response_ != nullptr);
83 if (shared_bounds_ != nullptr) {
84 shared_bounds_id_ = shared_bounds_->RegisterNewId();
85 }
86 *model_proto_with_only_variables_->mutable_variables() =
87 model_proto_.variables();
88 InitializeHelperData();
89 RecomputeHelperData();
91}
92
94 if (shared_bounds_ != nullptr) {
95 std::vector<int> model_variables;
96 std::vector<int64_t> new_lower_bounds;
97 std::vector<int64_t> new_upper_bounds;
98 shared_bounds_->GetChangedBounds(shared_bounds_id_, &model_variables,
99 &new_lower_bounds, &new_upper_bounds);
100
101 bool new_variables_have_been_fixed = false;
102
103 {
104 absl::MutexLock domain_lock(&domain_mutex_);
105
106 for (int i = 0; i < model_variables.size(); ++i) {
107 const int var = model_variables[i];
108 const int64_t new_lb = new_lower_bounds[i];
109 const int64_t new_ub = new_upper_bounds[i];
110 if (VLOG_IS_ON(3)) {
111 const auto& domain =
112 model_proto_with_only_variables_->variables(var).domain();
113 const int64_t old_lb = domain.Get(0);
114 const int64_t old_ub = domain.Get(domain.size() - 1);
115 VLOG(3) << "Variable: " << var << " old domain: [" << old_lb << ", "
116 << old_ub << "] new domain: [" << new_lb << ", " << new_ub
117 << "]";
118 }
119 const Domain old_domain = ReadDomainFromProto(
120 model_proto_with_only_variables_->variables(var));
121 const Domain new_domain =
122 old_domain.IntersectionWith(Domain(new_lb, new_ub));
123 if (new_domain.IsEmpty()) {
124 // This can mean two things:
125 // 1/ This variable is a normal one and the problem is UNSAT or
126 // 2/ This variable is optional, and its associated literal must be
127 // set to false.
128 //
129 // Currently, we wait for any full solver to pick the crossing bounds
130 // and do the correct stuff on their own. We do not want to have empty
131 // domain in the proto as this would means INFEASIBLE. So we just
132 // ignore such bounds here.
133 //
134 // TODO(user): We could set the optional literal to false directly in
135 // the bound sharing manager. We do have to be careful that all the
136 // different solvers have the same optionality definition though.
137 continue;
138 }
140 new_domain,
141 model_proto_with_only_variables_->mutable_variables(var));
142 new_variables_have_been_fixed |= new_domain.IsFixed();
143 }
144 }
145
146 // Only trigger the computation if needed.
147 if (new_variables_have_been_fixed) {
148 RecomputeHelperData();
149 }
150 }
151}
152
153bool NeighborhoodGeneratorHelper::ObjectiveDomainIsConstraining() const {
154 if (!model_proto_.has_objective()) return false;
155 if (model_proto_.objective().domain().empty()) return false;
156
157 int64_t min_activity = 0;
158 int64_t max_activity = 0;
159 const int num_terms = model_proto_.objective().vars().size();
160 for (int i = 0; i < num_terms; ++i) {
161 const int var = PositiveRef(model_proto_.objective().vars(i));
162 const int64_t coeff = model_proto_.objective().coeffs(i);
163 const auto& var_domain =
164 model_proto_with_only_variables_->variables(var).domain();
165 const int64_t v1 = coeff * var_domain[0];
166 const int64_t v2 = coeff * var_domain[var_domain.size() - 1];
167 min_activity += std::min(v1, v2);
168 max_activity += std::max(v1, v2);
169 }
170
171 const Domain obj_domain = ReadDomainFromProto(model_proto_.objective());
172 const Domain inferred_domain =
173 Domain(min_activity, max_activity)
175 Domain(std::numeric_limits<int64_t>::min(), obj_domain.Max()));
176 return !inferred_domain.IsIncludedIn(obj_domain);
177}
178
179void NeighborhoodGeneratorHelper::InitializeHelperData() {
180 type_to_constraints_.clear();
181 const int num_constraints = model_proto_.constraints_size();
182 for (int c = 0; c < num_constraints; ++c) {
183 const int type = model_proto_.constraints(c).constraint_case();
184 if (type >= type_to_constraints_.size()) {
185 type_to_constraints_.resize(type + 1);
186 }
187 type_to_constraints_[type].push_back(c);
188 }
189
190 const int num_variables = model_proto_.variables().size();
191 is_in_objective_.resize(num_variables, false);
192 if (model_proto_.has_objective()) {
193 for (const int ref : model_proto_.objective().vars()) {
194 is_in_objective_[PositiveRef(ref)] = true;
195 }
196 }
197}
198
199// Recompute all the data when new variables have been fixed. Note that this
200// shouldn't be called if there is no change as it is in O(problem size).
201void NeighborhoodGeneratorHelper::RecomputeHelperData() {
202 absl::MutexLock graph_lock(&graph_mutex_);
203 absl::ReaderMutexLock domain_lock(&domain_mutex_);
204
205 // Do basic presolving to have a more precise graph.
206 // Here we just remove trivially true constraints.
207 //
208 // Note(user): We do that each time a new variable is fixed. It might be too
209 // much, but on the miplib and in 1200s, we do that only about 1k time on the
210 // worst case problem.
211 //
212 // TODO(user): Change API to avoid a few copy?
213 // TODO(user): We could keep the context in the class.
214 // TODO(user): We can also start from the previous simplified model instead.
215 {
216 Model local_model;
217 CpModelProto mapping_proto;
218 simplified_model_proto_->Clear();
219 *simplified_model_proto_->mutable_variables() =
220 model_proto_with_only_variables_->variables();
221 PresolveContext context(&local_model, simplified_model_proto_,
222 &mapping_proto);
223 ModelCopy copier(&context);
224
225 // TODO(user): Not sure what to do if the model is UNSAT.
226 // This shouldn't matter as it should be dealt with elsewhere.
227 copier.ImportAndSimplifyConstraints(model_proto_, {});
228 }
229
230 // Compute the constraint <-> variable graph.
231 //
232 // TODO(user): Remove duplicate constraints?
233 const auto& constraints = simplified_model_proto_->constraints();
234 constraint_to_var_.clear();
235 constraint_to_var_.reserve(constraints.size());
236 for (int ct_index = 0; ct_index < constraints.size(); ++ct_index) {
237 // We remove the interval constraints since we should have an equivalent
238 // linear constraint somewhere else. This is not the case if we have a fixed
239 // size optional interval variable. But it should not matter as the
240 // intervals are replaced by their underlying variables in the scheduling
241 // constraints.
242 if (constraints[ct_index].constraint_case() == ConstraintProto::kInterval) {
243 continue;
244 }
245
246 tmp_row_.clear();
247 for (const int var : UsedVariables(constraints[ct_index])) {
248 if (IsConstant(var)) continue;
249 tmp_row_.push_back(var);
250 }
251
252 // We replace intervals by their underlying integer variables. Note that
253 // this is needed for a correct decomposition into independent part.
254 bool need_sort = false;
255 for (const int interval : UsedIntervals(constraints[ct_index])) {
256 need_sort = true;
257 for (const int var : UsedVariables(constraints[interval])) {
258 if (IsConstant(var)) continue;
259 tmp_row_.push_back(var);
260 }
261 }
262
263 // We remove constraint of size 0 and 1 since they are not useful for LNS
264 // based on this graph.
265 if (tmp_row_.size() <= 1) {
266 continue;
267 }
268
269 // Keep this constraint.
270 if (need_sort) {
272 }
273 constraint_to_var_.Add(tmp_row_);
274 }
275
276 // Initialize var to constraints, and make sure it has an entry for all
277 // variables.
278 var_to_constraint_.ResetFromTranspose(
279 constraint_to_var_,
280 /*min_transpose_size=*/model_proto_.variables().size());
281
282 // We mark as active all non-constant variables.
283 // Non-active variable will never be fixed in standard LNS fragment.
284 active_variables_.clear();
285 const int num_variables = model_proto_.variables_size();
286 active_variables_set_.assign(num_variables, false);
287 for (int i = 0; i < num_variables; ++i) {
288 if (!IsConstant(i)) {
289 active_variables_.push_back(i);
290 active_variables_set_[i] = true;
291 }
292 }
293
294 active_objective_variables_.clear();
295 for (const int var : model_proto_.objective().vars()) {
296 DCHECK(RefIsPositive(var));
297 if (active_variables_set_[var]) {
298 active_objective_variables_.push_back(var);
299 }
300 }
301
302 // Compute connected components.
303 // Note that fixed variable are just ignored.
305 union_find.SetNumberOfNodes(num_variables);
306 for (int c = 0; c < constraint_to_var_.size(); ++c) {
307 const auto row = constraint_to_var_[c];
308 if (row.size() <= 1) continue;
309 for (int i = 1; i < row.size(); ++i) {
310 union_find.AddEdge(row[0], row[i]);
311 }
312 }
313
314 // If we have a lower bound on the objective, then this "objective constraint"
315 // might link components together.
316 if (ObjectiveDomainIsConstraining()) {
317 const auto& refs = model_proto_.objective().vars();
318 const int num_terms = refs.size();
319 for (int i = 1; i < num_terms; ++i) {
320 union_find.AddEdge(PositiveRef(refs[0]), PositiveRef(refs[i]));
321 }
322 }
323
324 // Compute all components involving non-fixed variables.
325 //
326 // TODO(user): If a component has no objective, we can fix it to any feasible
327 // solution. This will automatically be done by LNS fragment covering such
328 // component though.
329 components_.clear();
330 var_to_component_index_.assign(num_variables, -1);
331 for (int var = 0; var < num_variables; ++var) {
332 if (IsConstant(var)) continue;
333 const int root = union_find.FindRoot(var);
334 DCHECK_LT(root, var_to_component_index_.size());
335 int& index = var_to_component_index_[root];
336 if (index == -1) {
337 index = components_.size();
338 components_.push_back({});
339 }
340 var_to_component_index_[var] = index;
341 components_[index].push_back(var);
342 }
343
344 // Display information about the reduced problem.
345 //
346 // TODO(user): Exploit connected component while generating fragments.
347 // TODO(user): Do not generate fragment not touching the objective.
348 if (!shared_response_->LoggingIsEnabled()) return;
349
350 std::vector<int> component_sizes;
351 for (const std::vector<int>& component : components_) {
352 component_sizes.push_back(component.size());
353 }
354 std::sort(component_sizes.begin(), component_sizes.end(),
355 std::greater<int>());
356 std::string compo_message;
357 if (component_sizes.size() > 1) {
358 if (component_sizes.size() <= 10) {
359 compo_message =
360 absl::StrCat(" compo:", absl::StrJoin(component_sizes, ","));
361 } else {
362 component_sizes.resize(10);
363 compo_message =
364 absl::StrCat(" compo:", absl::StrJoin(component_sizes, ","), ",...");
365 }
366 }
367
368 // TODO(user): This is not ideal, as if two reductions appears in a row and
369 // nothing else is done for a while, we will never see the "latest" size
370 // in the log until it is reduced again.
371 shared_response_->LogMessageWithThrottling(
372 "Model", absl::StrCat("var:", active_variables_.size(), "/",
373 num_variables, " constraints:",
374 simplified_model_proto_->constraints().size(), "/",
375 model_proto_.constraints().size(), compo_message));
376}
377
379 return active_variables_set_[var];
380}
381
382bool NeighborhoodGeneratorHelper::IsConstant(int var) const {
383 const auto& var_proto = model_proto_with_only_variables_->variables(var);
384 return var_proto.domain_size() == 2 &&
385 var_proto.domain(0) == var_proto.domain(1);
386}
387
389 Neighborhood neighborhood;
390 neighborhood.is_reduced = false;
391 neighborhood.is_generated = true;
392 {
393 absl::ReaderMutexLock lock(&domain_mutex_);
394 *neighborhood.delta.mutable_variables() =
395 model_proto_with_only_variables_->variables();
396 }
397 return neighborhood;
398}
399
401 Neighborhood neighborhood;
402 neighborhood.is_generated = false;
403 return neighborhood;
404}
405
406bool NeighborhoodGeneratorHelper::IntervalIsActive(
407 int index, const CpSolverResponse& initial_solution) const {
408 const ConstraintProto& interval_ct = ModelProto().constraints(index);
409 // We only look at intervals that are performed in the solution. The
410 // unperformed intervals should be automatically freed during the generation
411 // phase.
412 if (interval_ct.enforcement_literal().size() == 1) {
413 const int enforcement_ref = interval_ct.enforcement_literal(0);
414 const int enforcement_var = PositiveRef(enforcement_ref);
415 const int value = initial_solution.solution(enforcement_var);
416 if (RefIsPositive(enforcement_ref) == (value == 0)) return false;
417 }
418
419 for (const int v : interval_ct.interval().start().vars()) {
420 if (!IsConstant(v)) return true;
421 }
422 for (const int v : interval_ct.interval().size().vars()) {
423 if (!IsConstant(v)) return true;
424 }
425 for (const int v : interval_ct.interval().end().vars()) {
426 if (!IsConstant(v)) return true;
427 }
428 return false;
429}
430
432 absl::Span<const int> unfiltered_intervals,
433 const CpSolverResponse& initial_solution) const {
434 std::vector<int> filtered_intervals;
435 filtered_intervals.reserve(unfiltered_intervals.size());
436 absl::ReaderMutexLock lock(&domain_mutex_);
437 for (const int i : unfiltered_intervals) {
438 if (IntervalIsActive(i, initial_solution)) filtered_intervals.push_back(i);
439 }
440 return filtered_intervals;
441}
442
444 const CpSolverResponse& initial_solution) const {
445 return KeepActiveIntervals(TypeToConstraints(ConstraintProto::kInterval),
446 initial_solution);
447}
448
449std::vector<std::pair<int, int>>
451 const CpSolverResponse& initial_solution) const {
452 const std::vector<int> active_intervals =
453 GetActiveIntervals(initial_solution);
454 const absl::flat_hash_set<int> active_intervals_set(active_intervals.begin(),
455 active_intervals.end());
456
457 std::vector<std::pair<int, int>> active_rectangles;
458 for (const int ct_index : TypeToConstraints(ConstraintProto::kNoOverlap2D)) {
459 const NoOverlap2DConstraintProto& ct =
460 model_proto_.constraints(ct_index).no_overlap_2d();
461 for (int i = 0; i < ct.x_intervals_size(); ++i) {
462 const int x_i = ct.x_intervals(i);
463 const int y_i = ct.y_intervals(i);
464 if (active_intervals_set.contains(x_i) ||
465 active_intervals_set.contains(y_i)) {
466 active_rectangles.push_back({x_i, y_i});
467 }
468 }
469 }
470
471 return active_rectangles;
472}
473
474std::vector<std::vector<int>>
476 std::vector<std::vector<int>> intervals_in_constraints;
477 absl::flat_hash_set<std::vector<int>> added_intervals_sets;
478 const auto add_interval_list_only_once =
479 [&intervals_in_constraints,
480 &added_intervals_sets](const auto& intervals) {
481 std::vector<int> candidate({intervals.begin(), intervals.end()});
483 if (added_intervals_sets.insert(candidate).second) {
484 intervals_in_constraints.push_back(candidate);
485 }
486 };
487 for (const int ct_index : TypeToConstraints(ConstraintProto::kNoOverlap)) {
488 add_interval_list_only_once(
489 model_proto_.constraints(ct_index).no_overlap().intervals());
490 }
491 for (const int ct_index : TypeToConstraints(ConstraintProto::kCumulative)) {
492 add_interval_list_only_once(
493 model_proto_.constraints(ct_index).cumulative().intervals());
494 }
495 for (const int ct_index : TypeToConstraints(ConstraintProto::kNoOverlap2D)) {
496 add_interval_list_only_once(
497 model_proto_.constraints(ct_index).no_overlap_2d().x_intervals());
498 add_interval_list_only_once(
499 model_proto_.constraints(ct_index).no_overlap_2d().y_intervals());
500 }
501 return intervals_in_constraints;
502}
503
504namespace {
505
506int64_t GetLinearExpressionValue(const LinearExpressionProto& expr,
507 const CpSolverResponse& initial_solution) {
508 int64_t result = expr.offset();
509 for (int i = 0; i < expr.vars_size(); ++i) {
510 result += expr.coeffs(i) * initial_solution.solution(expr.vars(i));
511 }
512 return result;
513}
514
515struct StartEndIndex {
516 int64_t start;
517 int64_t end;
519 double noise;
520 bool operator<(const StartEndIndex& o) const {
521 return std::tie(start, end, noise, index_in_input_vector) <
522 std::tie(o.start, o.end, o.noise, o.index_in_input_vector);
523 }
524};
525
526struct TimePartition {
527 std::vector<int> indices_before_selected;
528 std::vector<int> selected_indices;
529 std::vector<int> indices_after_selected;
530};
531
532// Selects all intervals in a random time window to meet the difficulty
533// requirement.
534TimePartition PartitionIndicesAroundRandomTimeWindow(
535 const std::vector<int>& intervals, const CpModelProto& model_proto,
536 const CpSolverResponse& initial_solution, double difficulty,
537 absl::BitGenRef random) {
538 std::vector<StartEndIndex> start_end_indices;
539 for (int index = 0; index < intervals.size(); ++index) {
540 const int interval = intervals[index];
541 const ConstraintProto& interval_ct = model_proto.constraints(interval);
542 const int64_t start_value = GetLinearExpressionValue(
543 interval_ct.interval().start(), initial_solution);
544 const int64_t end_value = GetLinearExpressionValue(
545 interval_ct.interval().end(), initial_solution);
546 start_end_indices.push_back(
547 {start_value, end_value, index, absl::Uniform(random, 0., 1.0)});
548 }
549
550 if (start_end_indices.empty()) return {};
551
552 std::sort(start_end_indices.begin(), start_end_indices.end());
553 const int relaxed_size = std::floor(difficulty * start_end_indices.size());
554
555 std::uniform_int_distribution<int> random_var(
556 0, start_end_indices.size() - relaxed_size - 1);
557 // TODO(user): Consider relaxing more than one time window
558 // intervals. This seems to help with Giza models.
559 const int random_start_index = random_var(random);
560
561 // We want to minimize the time window relaxed, so we now sort the interval
562 // after the first selected intervals by end value.
563 // TODO(user): We could do things differently (include all tasks <= some
564 // end). The difficulty is that the number of relaxed tasks will differ from
565 // the target. We could also tie break tasks randomly.
566 std::sort(start_end_indices.begin() + random_start_index,
567 start_end_indices.end(),
568 [](const StartEndIndex& a, const StartEndIndex& b) {
569 return std::tie(a.end, a.noise, a.index_in_input_vector) <
570 std::tie(b.end, b.noise, b.index_in_input_vector);
571 });
572 TimePartition result;
573 int i = 0;
574 for (; i < random_start_index; ++i) {
575 result.indices_before_selected.push_back(
576 start_end_indices[i].index_in_input_vector);
577 }
578 for (; i < random_start_index + relaxed_size; ++i) {
579 result.selected_indices.push_back(
580 start_end_indices[i].index_in_input_vector);
581 }
582 for (; i < start_end_indices.size(); ++i) {
583 result.indices_after_selected.push_back(
584 start_end_indices[i].index_in_input_vector);
585 }
586 return result;
587}
588
589struct Demand {
591 int64_t start;
592 int64_t end;
593 int64_t height;
594
595 // Because of the binary splitting of the capacity in the procedure used to
596 // extract precedences out of a cumulative constraint, processing bigger
597 // heights first will decrease its probability of being split across the 2
598 // halves of the current split.
599 bool operator<(const Demand& other) const {
600 return std::tie(start, height, end) <
601 std::tie(other.start, other.height, other.end);
602 }
603
604 std::string DebugString() const {
605 return absl::StrCat("{i=", interval_index, " span=[", start, ",", end, "]",
606 " d=", height, "}");
607 }
608};
609
610void InsertPrecedencesFromSortedListOfNonOverlapingIntervals(
611 const std::vector<Demand>& demands,
612 absl::flat_hash_set<std::pair<int, int>>* precedences) {
613 for (int i = 0; i + 1 < demands.size(); ++i) {
614 DCHECK_LE(demands[i].end, demands[i + 1].start);
615 precedences->insert(
616 {demands[i].interval_index, demands[i + 1].interval_index});
617 }
618}
619
620bool IsPresent(const ConstraintProto& interval_ct,
621 const CpSolverResponse& initial_solution) {
622 if (interval_ct.enforcement_literal().size() != 1) return true;
623
624 const int enforcement_ref = interval_ct.enforcement_literal(0);
625 const int enforcement_var = PositiveRef(enforcement_ref);
626 const int64_t value = initial_solution.solution(enforcement_var);
627 return RefIsPositive(enforcement_ref) == (value == 1);
628}
629
630void InsertNoOverlapPrecedences(
631 const absl::flat_hash_set<int>& ignored_intervals,
632 const CpSolverResponse& initial_solution, const CpModelProto& model_proto,
633 int no_overlap_index,
634 absl::flat_hash_set<std::pair<int, int>>* precedences) {
635 std::vector<Demand> demands;
636 const NoOverlapConstraintProto& no_overlap =
637 model_proto.constraints(no_overlap_index).no_overlap();
638 for (const int interval_index : no_overlap.intervals()) {
639 if (ignored_intervals.contains(interval_index)) continue;
640 const ConstraintProto& interval_ct =
641 model_proto.constraints(interval_index);
642 if (!IsPresent(interval_ct, initial_solution)) continue;
643
644 const int64_t start_value = GetLinearExpressionValue(
645 interval_ct.interval().start(), initial_solution);
646 const int64_t end_value = GetLinearExpressionValue(
647 interval_ct.interval().end(), initial_solution);
648 DCHECK_LE(start_value, end_value);
649 demands.push_back({interval_index, start_value, end_value, 1});
650 }
651
652 // TODO(user): We actually only need interval_index, start.
653 // No need to fill the other fields here.
654 std::sort(demands.begin(), demands.end());
655 InsertPrecedencesFromSortedListOfNonOverlapingIntervals(demands, precedences);
656}
657
658void ProcessDemandListFromCumulativeConstraint(
659 const std::vector<Demand>& demands, int64_t capacity,
660 std::deque<std::pair<std::vector<Demand>, int64_t>>* to_process,
661 absl::BitGenRef random,
662 absl::flat_hash_set<std::pair<int, int>>* precedences) {
663 if (demands.size() <= 1) return;
664
665 // Checks if any pairs of tasks cannot overlap.
666 int64_t sum_of_min_two_capacities = 2;
667 if (capacity > 1) {
668 int64_t min1 = std::numeric_limits<int64_t>::max();
669 int64_t min2 = std::numeric_limits<int64_t>::max();
670 for (const Demand& demand : demands) {
671 if (demand.height <= min1) {
672 min2 = min1;
673 min1 = demand.height;
674 } else if (demand.height < min2) {
675 min2 = demand.height;
676 }
677 }
678 sum_of_min_two_capacities = min1 + min2;
679 }
680
681 DCHECK_GT(sum_of_min_two_capacities, 1);
682 if (sum_of_min_two_capacities > capacity) {
683 InsertPrecedencesFromSortedListOfNonOverlapingIntervals(demands,
684 precedences);
685 return;
686 }
687
688 std::vector<int64_t> unique_starts;
689 for (const Demand& demand : demands) {
690 DCHECK(unique_starts.empty() || demand.start >= unique_starts.back());
691 if (unique_starts.empty() || unique_starts.back() < demand.start) {
692 unique_starts.push_back(demand.start);
693 }
694 }
695 DCHECK(std::is_sorted(unique_starts.begin(), unique_starts.end()));
696 const int num_points = unique_starts.size();
697
698 // Split the capacity in 2 and dispatch all demands on the 2 parts.
699 const int64_t capacity1 = capacity / 2;
700 std::vector<int64_t> usage1(num_points);
701 std::vector<Demand> demands1;
702
703 const int64_t capacity2 = capacity - capacity1;
704 std::vector<int64_t> usage2(num_points);
705 std::vector<Demand> demands2;
706
707 int usage_index = 0;
708 for (const Demand& d : demands) {
709 // Since we process demand by increasing start, the usage_index only
710 // need to increase.
711 while (usage_index < num_points && unique_starts[usage_index] < d.start) {
712 usage_index++;
713 }
714 DCHECK_LT(usage_index, num_points);
715 DCHECK_EQ(unique_starts[usage_index], d.start);
716 const int64_t slack1 = capacity1 - usage1[usage_index];
717 const int64_t slack2 = capacity2 - usage2[usage_index];
718
719 // We differ from the ICAPS article. If it fits in both sub-cumulatives, We
720 // choose the smallest slack. If it fits into at most one, we choose the
721 // biggest slack. If both slacks are equal, we choose randomly.
722 const bool prefer2 =
723 slack1 == slack2
724 ? absl::Bernoulli(random, 0.5)
725 : (d.height <= std::min(slack1, slack2) ? slack2 < slack1
726 : slack2 > slack1);
727
728 auto& selected_usage = prefer2 ? usage2 : usage1;
729 auto& residual_usage = prefer2 ? usage1 : usage2;
730 std::vector<Demand>& selected_demands = prefer2 ? demands2 : demands1;
731 std::vector<Demand>& residual_demands = prefer2 ? demands1 : demands2;
732 const int64_t selected_slack = prefer2 ? slack2 : slack1;
733
734 const int64_t assigned_to_selected = std::min(selected_slack, d.height);
735 DCHECK_GT(assigned_to_selected, 0);
736 for (int i = usage_index; i < num_points; ++i) {
737 if (d.end <= unique_starts[i]) break;
738 selected_usage[i] += assigned_to_selected;
739 }
740 selected_demands.push_back(
741 {d.interval_index, d.start, d.end, assigned_to_selected});
742
743 if (d.height > selected_slack) {
744 const int64_t residual = d.height - selected_slack;
745 DCHECK_GT(residual, 0);
746 DCHECK_LE(residual, prefer2 ? slack1 : slack2);
747 for (int i = usage_index; i < num_points; ++i) {
748 if (d.end <= unique_starts[i]) break;
749 residual_usage[i] += residual;
750 }
751 residual_demands.push_back({d.interval_index, d.start, d.end, residual});
752 }
753 }
754
755 if (demands1.size() > 1) {
756 to_process->emplace_back(std::move(demands1), capacity1);
757 }
758 if (demands2.size() > 1) {
759 to_process->emplace_back(std::move(demands2), capacity2);
760 }
761}
762
763void InsertCumulativePrecedences(
764 const absl::flat_hash_set<int>& ignored_intervals,
765 const CpSolverResponse& initial_solution, const CpModelProto& model_proto,
766 int cumulative_index, absl::BitGenRef random,
767 absl::flat_hash_set<std::pair<int, int>>* precedences) {
768 const CumulativeConstraintProto& cumulative =
769 model_proto.constraints(cumulative_index).cumulative();
770
771 std::vector<Demand> demands;
772 for (int i = 0; i < cumulative.intervals().size(); ++i) {
773 const int interval_index = cumulative.intervals(i);
774 if (ignored_intervals.contains(interval_index)) continue;
775 const ConstraintProto& interval_ct =
776 model_proto.constraints(interval_index);
777 if (!IsPresent(interval_ct, initial_solution)) continue;
778
779 const int64_t start_value = GetLinearExpressionValue(
780 interval_ct.interval().start(), initial_solution);
781 const int64_t end_value = GetLinearExpressionValue(
782 interval_ct.interval().end(), initial_solution);
783 const int64_t demand_value =
784 GetLinearExpressionValue(cumulative.demands(i), initial_solution);
785 if (start_value == end_value || demand_value == 0) continue;
786
787 demands.push_back({interval_index, start_value, end_value, demand_value});
788 }
789 std::sort(demands.begin(), demands.end());
790
791 const int64_t capacity_value =
792 GetLinearExpressionValue(cumulative.capacity(), initial_solution);
793 DCHECK_GT(capacity_value, 0);
794
795 // Copying all these demands is memory intensive. Let's be careful here.
796 std::deque<std::pair<std::vector<Demand>, int64_t>> to_process;
797 to_process.emplace_back(std::move(demands), capacity_value);
798
799 while (!to_process.empty()) {
800 auto& next_task = to_process.front();
801 ProcessDemandListFromCumulativeConstraint(next_task.first,
802 /*capacity=*/next_task.second,
803 &to_process, random, precedences);
804 to_process.pop_front();
805 }
806}
807
808struct Rectangle {
809 int interval_index;
810 int64_t x_start;
811 int64_t x_end;
812 int64_t y_start;
813 int64_t y_end;
814
815 bool operator<(const Rectangle& other) const {
816 return std::tie(x_start, x_end) < std::tie(other.x_start, other.x_end);
817 }
818};
819
820void InsertRectanglePredecences(
821 const std::vector<Rectangle>& rectangles,
822 absl::flat_hash_set<std::pair<int, int>>* precedences) {
823 // TODO(user): Refine set of interesting points.
824 std::vector<int64_t> interesting_points;
825 for (const Rectangle& r : rectangles) {
826 interesting_points.push_back(r.y_end - 1);
827 }
828 gtl::STLSortAndRemoveDuplicates(&interesting_points);
829 std::vector<Demand> demands;
830 for (const int64_t t : interesting_points) {
831 demands.clear();
832 for (const Rectangle& r : rectangles) {
833 if (r.y_start > t || r.y_end <= t) continue;
834 demands.push_back({r.interval_index, r.x_start, r.x_end, 1});
835 }
836 std::sort(demands.begin(), demands.end());
837 InsertPrecedencesFromSortedListOfNonOverlapingIntervals(demands,
838 precedences);
839 }
840}
841
842void InsertNoOverlap2dPrecedences(
843 const absl::flat_hash_set<int>& ignored_intervals,
844 const CpSolverResponse& initial_solution, const CpModelProto& model_proto,
845 int no_overlap_2d_index,
846 absl::flat_hash_set<std::pair<int, int>>* precedences) {
847 std::vector<Demand> demands;
848 const NoOverlap2DConstraintProto& no_overlap_2d =
849 model_proto.constraints(no_overlap_2d_index).no_overlap_2d();
850 std::vector<Rectangle> x_main;
851 std::vector<Rectangle> y_main;
852 for (int i = 0; i < no_overlap_2d.x_intervals_size(); ++i) {
853 // Ignore unperformed rectangles.
854 const int x_interval_index = no_overlap_2d.x_intervals(i);
855 if (ignored_intervals.contains(x_interval_index)) continue;
856 const ConstraintProto& x_interval_ct =
857 model_proto.constraints(x_interval_index);
858 if (!IsPresent(x_interval_ct, initial_solution)) continue;
859
860 const int y_interval_index = no_overlap_2d.y_intervals(i);
861 if (ignored_intervals.contains(y_interval_index)) continue;
862 const ConstraintProto& y_interval_ct =
863 model_proto.constraints(y_interval_index);
864 if (!IsPresent(y_interval_ct, initial_solution)) continue;
865
866 const int64_t x_start_value = GetLinearExpressionValue(
867 x_interval_ct.interval().start(), initial_solution);
868 const int64_t x_end_value = GetLinearExpressionValue(
869 x_interval_ct.interval().end(), initial_solution);
870 const int64_t y_start_value = GetLinearExpressionValue(
871 y_interval_ct.interval().start(), initial_solution);
872 const int64_t y_end_value = GetLinearExpressionValue(
873 y_interval_ct.interval().end(), initial_solution);
874
875 // Ignore rectangles with zero area.
876 if (x_start_value == x_end_value || y_start_value == y_end_value) continue;
877
878 x_main.push_back({x_interval_index, x_start_value, x_end_value,
879 y_start_value, y_end_value});
880 y_main.push_back({y_interval_index, y_start_value, y_end_value,
881 x_start_value, x_end_value});
882 }
883
884 if (x_main.empty() || y_main.empty()) return;
885
886 std::sort(x_main.begin(), x_main.end());
887 InsertRectanglePredecences(x_main, precedences);
888 std::sort(y_main.begin(), y_main.end());
889 InsertRectanglePredecences(y_main, precedences);
890}
891
892} // namespace
893
894// TODO(user): We could scan for model precedences and add them to the list
895// of precedences. This could enable more simplifications in the transitive
896// reduction phase.
897std::vector<std::pair<int, int>>
899 const absl::flat_hash_set<int>& ignored_intervals,
900 const CpSolverResponse& initial_solution, absl::BitGenRef random) const {
901 absl::flat_hash_set<std::pair<int, int>> precedences;
902 for (const int c : TypeToConstraints(ConstraintProto::kNoOverlap)) {
903 InsertNoOverlapPrecedences(ignored_intervals, initial_solution,
904 ModelProto(), c, &precedences);
905 }
906 for (const int c : TypeToConstraints(ConstraintProto::kCumulative)) {
907 InsertCumulativePrecedences(ignored_intervals, initial_solution,
908 ModelProto(), c, random, &precedences);
909 }
910 for (const int c : TypeToConstraints(ConstraintProto::kNoOverlap2D)) {
911 InsertNoOverlap2dPrecedences(ignored_intervals, initial_solution,
912 ModelProto(), c, &precedences);
913 }
914
915 // TODO(user): Reduce precedence graph
916 std::vector<std::pair<int, int>> result(precedences.begin(),
917 precedences.end());
918 std::sort(result.begin(), result.end());
919 return result;
920}
921
923 const CpSolverResponse& initial_solution) const {
924 struct HeadAndArcLiteral {
925 int head;
926 int literal;
927 };
928
929 std::vector<std::vector<int>> result;
930 absl::flat_hash_map<int, HeadAndArcLiteral> tail_to_head_and_arc_literal;
931
932 for (const int i : TypeToConstraints(ConstraintProto::kCircuit)) {
933 const CircuitConstraintProto& ct = ModelProto().constraints(i).circuit();
934
935 // Collect arcs.
936 int min_node = std::numeric_limits<int>::max();
937 tail_to_head_and_arc_literal.clear();
938 for (int i = 0; i < ct.literals_size(); ++i) {
939 const int literal = ct.literals(i);
940 const int head = ct.heads(i);
941 const int tail = ct.tails(i);
942 const int bool_var = PositiveRef(literal);
943 const int64_t value = initial_solution.solution(bool_var);
944 // Skip unselected arcs.
945 if (RefIsPositive(literal) == (value == 0)) continue;
946 // Ignore self loops.
947 if (head == tail) continue;
948 tail_to_head_and_arc_literal[tail] = {head, bool_var};
949 min_node = std::min(tail, min_node);
950 }
951 if (tail_to_head_and_arc_literal.empty()) continue;
952
953 // Unroll the path.
954 int current_node = min_node;
955 std::vector<int> path;
956 do {
957 auto it = tail_to_head_and_arc_literal.find(current_node);
958 CHECK(it != tail_to_head_and_arc_literal.end());
959 current_node = it->second.head;
960 path.push_back(it->second.literal);
961 } while (current_node != min_node);
962 result.push_back(std::move(path));
963 }
964
965 std::vector<HeadAndArcLiteral> route_starts;
966 for (const int i : TypeToConstraints(ConstraintProto::kRoutes)) {
967 const RoutesConstraintProto& ct = ModelProto().constraints(i).routes();
968 tail_to_head_and_arc_literal.clear();
969 route_starts.clear();
970
971 // Collect route starts and arcs.
972 for (int i = 0; i < ct.literals_size(); ++i) {
973 const int literal = ct.literals(i);
974 const int head = ct.heads(i);
975 const int tail = ct.tails(i);
976 const int bool_var = PositiveRef(literal);
977 const int64_t value = initial_solution.solution(bool_var);
978 // Skip unselected arcs.
979 if (RefIsPositive(literal) == (value == 0)) continue;
980 // Ignore self loops.
981 if (head == tail) continue;
982 if (tail == 0) {
983 route_starts.push_back({head, bool_var});
984 } else {
985 tail_to_head_and_arc_literal[tail] = {head, bool_var};
986 }
987 }
988
989 // Unroll all routes.
990 for (const HeadAndArcLiteral& head_var : route_starts) {
991 std::vector<int> path;
992 int current_node = head_var.head;
993 path.push_back(head_var.literal);
994 do {
995 auto it = tail_to_head_and_arc_literal.find(current_node);
996 CHECK(it != tail_to_head_and_arc_literal.end());
997 current_node = it->second.head;
998 path.push_back(it->second.literal);
999 } while (current_node != 0);
1000 result.push_back(std::move(path));
1001 }
1002 }
1003
1004 return result;
1005}
1006
1008 const CpSolverResponse& base_solution,
1009 const absl::flat_hash_set<int>& variables_to_fix) const {
1010 Neighborhood neighborhood;
1011
1012 // TODO(user): Maybe relax all variables in the objective when the number
1013 // is small or negligible compared to the number of variables.
1014 const int unique_objective_variable =
1015 model_proto_.has_objective() && model_proto_.objective().vars_size() == 1
1016 ? model_proto_.objective().vars(0)
1017 : -1;
1018
1019 // Fill in neighborhood.delta all variable domains.
1020 {
1021 absl::ReaderMutexLock domain_lock(&domain_mutex_);
1022
1023 const int num_variables =
1024 model_proto_with_only_variables_->variables().size();
1025 neighborhood.delta.mutable_variables()->Reserve(num_variables);
1026 for (int i = 0; i < num_variables; ++i) {
1027 const IntegerVariableProto& current_var =
1028 model_proto_with_only_variables_->variables(i);
1029 IntegerVariableProto* new_var = neighborhood.delta.add_variables();
1030
1031 // We only copy the name in debug mode.
1032 if (DEBUG_MODE) new_var->set_name(current_var.name());
1033
1034 const Domain domain = ReadDomainFromProto(current_var);
1035 const int64_t base_value = base_solution.solution(i);
1036
1037 if (variables_to_fix.contains(i) && i != unique_objective_variable) {
1038 if (domain.Contains(base_value)) {
1039 new_var->add_domain(base_value);
1040 new_var->add_domain(base_value);
1041 } else {
1042 // If under the updated domain, the base solution is no longer valid,
1043 // We should probably regenerate this neighborhood. But for now we
1044 // just do a best effort and take the closest value.
1045 int64_t closest_value = domain.Min();
1046 int64_t closest_dist = std::abs(closest_value - base_value);
1047 for (const ClosedInterval interval : domain) {
1048 for (const int64_t value : {interval.start, interval.end}) {
1049 const int64_t dist = std::abs(value - base_value);
1050 if (dist < closest_dist) {
1051 closest_value = value;
1052 closest_dist = dist;
1053 }
1054 }
1055 }
1056 FillDomainInProto(Domain(closest_value, closest_value), new_var);
1057 }
1058 } else {
1059 FillDomainInProto(domain, new_var);
1060 }
1061 }
1062 }
1063
1064 // Fill some statistic fields and detect if we cover a full component.
1065 //
1066 // TODO(user): If there is just one component, we can skip some computation.
1067 {
1068 absl::ReaderMutexLock graph_lock(&graph_mutex_);
1069 std::vector<int> count(components_.size(), 0);
1070 const int num_variables = neighborhood.delta.variables().size();
1071 for (int var = 0; var < num_variables; ++var) {
1072 const auto& domain = neighborhood.delta.variables(var).domain();
1073 if (domain.size() != 2 || domain[0] != domain[1]) {
1074 ++neighborhood.num_relaxed_variables;
1075 if (is_in_objective_[var]) {
1077 }
1078 const int c = var_to_component_index_[var];
1079 if (c != -1) count[c]++;
1080 }
1081 }
1082
1083 for (int i = 0; i < components_.size(); ++i) {
1084 if (count[i] == components_[i].size()) {
1087 components_[i].begin(), components_[i].end());
1088 }
1089 }
1090 }
1091
1092 // If the objective domain might cut the optimal solution, we cannot exploit
1093 // the connected components. We compute this outside the mutex to avoid
1094 // any deadlock risk.
1095 //
1096 // TODO(user): We could handle some complex domain (size > 2).
1097 if (model_proto_.has_objective() &&
1098 (model_proto_.objective().domain().size() != 2 ||
1099 shared_response_->GetInnerObjectiveLowerBound() <
1100 model_proto_.objective().domain(0))) {
1102 }
1103
1104 AddSolutionHinting(base_solution, &neighborhood.delta);
1105
1106 neighborhood.is_generated = true;
1107 neighborhood.is_reduced = !variables_to_fix.empty();
1108 neighborhood.is_simple = true;
1109
1110 // TODO(user): force better objective? Note that this is already done when the
1111 // hint above is successfully loaded (i.e. if it passes the presolve
1112 // correctly) since the solver will try to find better solution than the
1113 // current one.
1114 return neighborhood;
1115}
1116
1118 const CpSolverResponse& initial_solution, CpModelProto* model_proto) const {
1119 // Set the current solution as a hint.
1120 model_proto->clear_solution_hint();
1121 const auto is_fixed = [model_proto](int var) {
1122 const IntegerVariableProto& var_proto = model_proto->variables(var);
1123 return var_proto.domain_size() == 2 &&
1124 var_proto.domain(0) == var_proto.domain(1);
1125 };
1126 for (int var = 0; var < model_proto->variables_size(); ++var) {
1127 if (is_fixed(var)) continue;
1128
1129 model_proto->mutable_solution_hint()->add_vars(var);
1130 model_proto->mutable_solution_hint()->add_values(
1131 initial_solution.solution(var));
1132 }
1133}
1134
1136 const CpSolverResponse& initial_solution,
1137 const std::vector<int>& relaxed_variables) const {
1138 std::vector<bool> relaxed_variables_set(model_proto_.variables_size(), false);
1139 for (const int var : relaxed_variables) relaxed_variables_set[var] = true;
1140 absl::flat_hash_set<int> fixed_variables;
1141 {
1142 absl::ReaderMutexLock graph_lock(&graph_mutex_);
1143 for (const int i : active_variables_) {
1144 if (!relaxed_variables_set[i]) {
1145 fixed_variables.insert(i);
1146 }
1147 }
1148 }
1149 return FixGivenVariables(initial_solution, fixed_variables);
1150}
1151
1153 const CpSolverResponse& initial_solution) const {
1154 const std::vector<int>& all_variables = ActiveVariables();
1155 const absl::flat_hash_set<int> fixed_variables(all_variables.begin(),
1156 all_variables.end());
1157 return FixGivenVariables(initial_solution, fixed_variables);
1158}
1159
1161 CpModelProto updated_model = model_proto_;
1162 {
1163 absl::MutexLock domain_lock(&domain_mutex_);
1164 *updated_model.mutable_variables() =
1165 model_proto_with_only_variables_->variables();
1166 }
1167 return updated_model;
1168}
1169
1173
1174double NeighborhoodGenerator::GetUCBScore(int64_t total_num_calls) const {
1175 absl::ReaderMutexLock mutex_lock(&generator_mutex_);
1176 DCHECK_GE(total_num_calls, num_calls_);
1177 if (num_calls_ <= 10) return std::numeric_limits<double>::infinity();
1178 return current_average_ + sqrt((2 * log(total_num_calls)) / num_calls_);
1179}
1180
1182 absl::MutexLock mutex_lock(&generator_mutex_);
1183
1184 // To make the whole update process deterministic, we currently sort the
1185 // SolveData.
1186 std::sort(solve_data_.begin(), solve_data_.end());
1187
1188 // This will be used to update the difficulty of this neighborhood.
1189 int num_fully_solved_in_batch = 0;
1190 int num_not_fully_solved_in_batch = 0;
1191
1192 double total_dtime = 0.0;
1193 for (const SolveData& data : solve_data_) {
1194 ++num_calls_;
1195
1196 // INFEASIBLE or OPTIMAL means that we "fully solved" the local problem.
1197 // If we didn't, then we cannot be sure that there is no improving solution
1198 // in that neighborhood.
1199 if (data.status == CpSolverStatus::INFEASIBLE ||
1200 data.status == CpSolverStatus::OPTIMAL) {
1201 ++num_fully_solved_calls_;
1202 ++num_fully_solved_in_batch;
1203 } else {
1204 ++num_not_fully_solved_in_batch;
1205 }
1206
1207 // It seems to make more sense to compare the new objective to the base
1208 // solution objective, not the best one. However this causes issue in the
1209 // logic below because on some problems the neighborhood can always lead
1210 // to a better "new objective" if the base solution wasn't the best one.
1211 //
1212 // This might not be a final solution, but it does work ok for now.
1213 const IntegerValue best_objective_improvement = IntegerValue(CapSub(
1214 data.initial_best_objective.value(), data.new_objective.value()));
1215 if (best_objective_improvement > 0) {
1216 num_consecutive_non_improving_calls_ = 0;
1217 next_time_limit_bump_ = 50;
1218 } else {
1219 ++num_consecutive_non_improving_calls_;
1220 }
1221
1222 // Confusing: this one is however comparing to the base solution objective.
1223 if (data.base_objective > data.new_objective) {
1224 ++num_improving_calls_;
1225 }
1226
1227 // TODO(user): Weight more recent data.
1228 // degrade the current average to forget old learnings.
1229 const double gain_per_time_unit =
1230 std::max(0.0, static_cast<double>(best_objective_improvement.value())) /
1231 (1.0 + data.deterministic_time);
1232 if (num_calls_ <= 100) {
1233 current_average_ += (gain_per_time_unit - current_average_) / num_calls_;
1234 } else {
1235 current_average_ = 0.9 * current_average_ + 0.1 * gain_per_time_unit;
1236 }
1237
1238 total_dtime += data.deterministic_time;
1239 }
1240
1241 // Update the difficulty.
1242 difficulty_.Update(/*num_decreases=*/num_not_fully_solved_in_batch,
1243 /*num_increases=*/num_fully_solved_in_batch);
1244
1245 // Bump the time limit if we saw no better solution in the last few calls.
1246 // This means that as the search progress, we likely spend more and more time
1247 // trying to solve individual neighborhood.
1248 //
1249 // TODO(user): experiment with resetting the time limit if a solution is
1250 // found.
1251 if (num_consecutive_non_improving_calls_ > next_time_limit_bump_) {
1252 next_time_limit_bump_ = num_consecutive_non_improving_calls_ + 50;
1253 deterministic_limit_ *= 1.02;
1254
1255 // We do not want the limit to go to high. Intuitively, the goal is to try
1256 // out a lot of neighborhoods, not just spend a lot of time on a few.
1257 deterministic_limit_ = std::min(60.0, deterministic_limit_);
1258 }
1259
1260 solve_data_.clear();
1261 return total_dtime;
1262}
1263
1264namespace {
1265
1266template <class T>
1267void GetRandomSubset(double relative_size, std::vector<T>* base,
1268 absl::BitGenRef random) {
1269 if (base->empty()) return;
1270
1271 // TODO(user): we could generate this more efficiently than using random
1272 // shuffle.
1273 std::shuffle(base->begin(), base->end(), random);
1274 const int target_size = std::round(relative_size * base->size());
1275 base->resize(target_size);
1276}
1277
1278} // namespace
1279
1281 const CpSolverResponse& initial_solution, double difficulty,
1282 absl::BitGenRef random) {
1283 std::vector<int> fixed_variables = helper_.ActiveVariables();
1284 GetRandomSubset(1.0 - difficulty, &fixed_variables, random);
1286 initial_solution, {fixed_variables.begin(), fixed_variables.end()});
1287}
1288
1290 const CpSolverResponse& initial_solution, double difficulty,
1291 absl::BitGenRef random) {
1293 return helper_.FullNeighborhood();
1294 }
1295
1296 std::vector<int> relaxed_variables;
1297 {
1298 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1299 const int num_active_constraints = helper_.ConstraintToVar().size();
1300 std::vector<int> active_constraints(num_active_constraints);
1301 for (int c = 0; c < num_active_constraints; ++c) {
1302 active_constraints[c] = c;
1303 }
1304 std::shuffle(active_constraints.begin(), active_constraints.end(), random);
1305
1306 const int num_model_vars = helper_.ModelProto().variables_size();
1307 std::vector<bool> visited_variables_set(num_model_vars, false);
1308
1309 const int num_active_vars =
1311 const int target_size = std::ceil(difficulty * num_active_vars);
1312 if (target_size == num_active_vars) return helper_.FullNeighborhood();
1313 // TODO(user): Clean-up when target_size == 0.
1314
1315 for (const int constraint_index : active_constraints) {
1316 // TODO(user): randomize order of variable addition when close to the
1317 // limit.
1318 for (const int var : helper_.ConstraintToVar()[constraint_index]) {
1319 if (visited_variables_set[var]) continue;
1320 visited_variables_set[var] = true;
1321 if (helper_.IsActive(var)) {
1322 relaxed_variables.push_back(var);
1323 if (relaxed_variables.size() >= target_size) break;
1324 }
1325 }
1326 if (relaxed_variables.size() >= target_size) break;
1327 }
1328 }
1329
1330 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1331}
1332
1333// Note that even if difficulty means full neighborhood, we go through the
1334// generation process to never get out of a connected components.
1336 const CpSolverResponse& initial_solution, double difficulty,
1337 absl::BitGenRef random) {
1338 const int num_model_vars = helper_.ModelProto().variables_size();
1339 std::vector<bool> visited_variables_set(num_model_vars, false);
1340 std::vector<int> relaxed_variables;
1341 std::vector<int> visited_variables;
1342
1343 // It is important complexity wise to never scan a constraint twice!
1344 const int num_model_constraints = helper_.ModelProto().constraints_size();
1345 std::vector<bool> scanned_constraints(num_model_constraints, false);
1346
1347 std::vector<int> random_variables;
1348 {
1349 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1350
1351 // The number of active variables can decrease asynchronously.
1352 // We read the exact number while locked.
1353 const int num_active_vars =
1355 const int num_objective_variables =
1357 const int target_size = std::ceil(difficulty * num_active_vars);
1358 if (target_size == num_active_vars) return helper_.FullNeighborhood();
1359
1360 const int first_var =
1361 num_objective_variables > 0 // Prefer objective variables.
1363 [absl::Uniform<int>(random, 0, num_objective_variables)]
1364 : helper_.ActiveVariablesWhileHoldingLock()[absl::Uniform<int>(
1365 random, 0, num_active_vars)];
1366 visited_variables_set[first_var] = true;
1367 visited_variables.push_back(first_var);
1368 relaxed_variables.push_back(first_var);
1369
1370 for (int i = 0; i < visited_variables.size(); ++i) {
1371 random_variables.clear();
1372 // Collect all the variables that appears in the same constraints as
1373 // visited_variables[i].
1374 for (const int ct : helper_.VarToConstraint()[visited_variables[i]]) {
1375 if (scanned_constraints[ct]) continue;
1376 scanned_constraints[ct] = true;
1377 for (const int var : helper_.ConstraintToVar()[ct]) {
1378 if (visited_variables_set[var]) continue;
1379 visited_variables_set[var] = true;
1380 random_variables.push_back(var);
1381 }
1382 }
1383 // We always randomize to change the partial subgraph explored
1384 // afterwards.
1385 std::shuffle(random_variables.begin(), random_variables.end(), random);
1386 for (const int var : random_variables) {
1387 if (relaxed_variables.size() < target_size) {
1388 visited_variables.push_back(var);
1389 if (helper_.IsActive(var)) {
1390 relaxed_variables.push_back(var);
1391 }
1392 } else {
1393 break;
1394 }
1395 }
1396 if (relaxed_variables.size() >= target_size) break;
1397 }
1398 }
1399
1400 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1401}
1402
1403// Note that even if difficulty means full neighborhood, we go through the
1404// generation process to never get out of a connected components.
1406 const CpSolverResponse& initial_solution, double difficulty,
1407 absl::BitGenRef random) {
1408 const int num_model_vars = helper_.ModelProto().variables_size();
1409 if (num_model_vars == 0) return helper_.NoNeighborhood();
1410
1411 // We copy the full graph var <-> constraints so that we can:
1412 // - reduce it in place
1413 // - not hold the mutex too long.
1414 // TODO(user): should we compress it or use a different representation ?
1415 CompactVectorVector<int, int> vars_to_constraints;
1416 CompactVectorVector<int, int> constraints_to_vars;
1417 int num_active_vars = 0;
1418 std::vector<int> active_objective_vars;
1419 {
1420 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1421 num_active_vars = helper_.ActiveVariablesWhileHoldingLock().size();
1422 active_objective_vars = helper_.ActiveObjectiveVariablesWhileHoldingLock();
1423 constraints_to_vars = helper_.ConstraintToVar();
1424 vars_to_constraints = helper_.VarToConstraint();
1425 }
1426
1427 const int target_size = std::ceil(difficulty * num_active_vars);
1428 if (target_size == 0) return helper_.NoNeighborhood();
1429
1430 // We pick a variable from the objective.
1431 const int num_objective_variables = active_objective_vars.size();
1432 if (num_objective_variables == 0) return helper_.NoNeighborhood();
1433 const int first_var = active_objective_vars[absl::Uniform<int>(
1434 random, 0, num_objective_variables)];
1435
1436 std::vector<bool> relaxed_variables_set(num_model_vars, false);
1437 std::vector<int> relaxed_variables;
1438 // Active vars are relaxed variables with some unexplored neighbors.
1439 std::vector<int> active_vars;
1440
1441 relaxed_variables_set[first_var] = true;
1442 relaxed_variables.push_back(first_var);
1443 active_vars.push_back(first_var);
1444
1445 while (relaxed_variables.size() < target_size) {
1446 if (active_vars.empty()) break; // We have exhausted our component.
1447
1448 const int tail_index = absl::Uniform<int>(random, 0, active_vars.size());
1449 const int tail_var = active_vars[tail_index];
1450 int head_var = tail_var;
1451 while (!vars_to_constraints[tail_var].empty() && head_var == tail_var) {
1452 const auto cts = vars_to_constraints[tail_var];
1453 const int pos_ct = absl::Uniform<int>(random, 0, cts.size());
1454 const int ct = cts[pos_ct];
1455 while (!constraints_to_vars[ct].empty() && head_var == tail_var) {
1456 const auto vars = constraints_to_vars[ct];
1457 const int pos_var = absl::Uniform<int>(random, 0, vars.size());
1458 const int candidate = vars[pos_var];
1459
1460 // We remove the variable as it is either already relaxed, or will be
1461 // relaxed.
1462 constraints_to_vars.RemoveBySwap(ct, pos_var);
1463 if (!relaxed_variables_set[candidate]) {
1464 head_var = candidate;
1465 }
1466 }
1467 if (constraints_to_vars[ct].empty()) {
1468 // This constraint has no more un-relaxed variables.
1469 vars_to_constraints.RemoveBySwap(tail_var, pos_ct);
1470 }
1471 }
1472
1473 // Variable is no longer active ?
1474 if (vars_to_constraints[tail_var].empty()) {
1475 std::swap(active_vars[tail_index], active_vars.back());
1476 active_vars.pop_back();
1477 }
1478
1479 if (head_var != tail_var) {
1480 relaxed_variables_set[head_var] = true;
1481 relaxed_variables.push_back(head_var);
1482 active_vars.push_back(head_var);
1483 }
1484 }
1485 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1486}
1487
1488// Note that even if difficulty means full neighborhood, we go through the
1489// generation process to never get out of a connected components.
1491 const CpSolverResponse& initial_solution, double difficulty,
1492 absl::BitGenRef random) {
1493 const int num_model_constraints = helper_.ModelProto().constraints_size();
1494 if (num_model_constraints == 0) {
1495 return helper_.FullNeighborhood();
1496 }
1497
1498 const int num_model_vars = helper_.ModelProto().variables_size();
1499 std::vector<bool> visited_variables_set(num_model_vars, false);
1500 std::vector<int> relaxed_variables;
1501
1502 std::vector<bool> added_constraints(num_model_constraints, false);
1503 std::vector<int> next_constraints;
1504
1505 std::vector<int> random_variables;
1506 {
1507 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1508 const int num_active_vars =
1510 const int target_size = std::ceil(difficulty * num_active_vars);
1511 if (target_size == num_active_vars) return helper_.FullNeighborhood();
1512
1513 // Start by a random constraint.
1514 const int num_active_constraints = helper_.ConstraintToVar().size();
1515 if (num_active_constraints != 0) {
1516 next_constraints.push_back(
1517 absl::Uniform<int>(random, 0, num_active_constraints));
1518 added_constraints[next_constraints.back()] = true;
1519 }
1520
1521 while (relaxed_variables.size() < target_size) {
1522 // Stop if we have a full connected component.
1523 if (next_constraints.empty()) break;
1524
1525 // Pick a random unprocessed constraint.
1526 const int i = absl::Uniform<int>(random, 0, next_constraints.size());
1527 const int constraint_index = next_constraints[i];
1528 std::swap(next_constraints[i], next_constraints.back());
1529 next_constraints.pop_back();
1530
1531 // Add all the variable of this constraint and increase the set of next
1532 // possible constraints.
1533 DCHECK_LT(constraint_index, num_active_constraints);
1534 random_variables.assign(
1537 std::shuffle(random_variables.begin(), random_variables.end(), random);
1538 for (const int var : random_variables) {
1539 if (visited_variables_set[var]) continue;
1540 visited_variables_set[var] = true;
1541 if (helper_.IsActive(var)) {
1542 relaxed_variables.push_back(var);
1543 }
1544 if (relaxed_variables.size() >= target_size) break;
1545
1546 for (const int ct : helper_.VarToConstraint()[var]) {
1547 if (added_constraints[ct]) continue;
1548 added_constraints[ct] = true;
1549 next_constraints.push_back(ct);
1550 }
1551 }
1552 }
1553 }
1554
1555 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1556}
1557
1559 const CpSolverResponse& initial_solution, double difficulty,
1560 absl::BitGenRef random) {
1561 int max_width = 0;
1562 int size_at_min_width_after_100;
1563 int min_width_after_100 = std::numeric_limits<int>::max();
1564 int num_zero_score = 0;
1565 std::vector<int> relaxed_variables;
1566
1567 // Note(user): The algo is slower than the other graph generator, so we
1568 // might not want to lock the graph for so long? it is just a reader lock
1569 // though.
1570 {
1571 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1572
1573 const int num_active_vars =
1575 const int target_size = std::ceil(difficulty * num_active_vars);
1576 if (target_size == num_active_vars) return helper_.FullNeighborhood();
1577
1578 const int num_vars = helper_.VarToConstraint().size();
1579 const int num_constraints = helper_.ConstraintToVar().size();
1580 if (num_constraints == 0 || num_vars == 0) {
1581 return helper_.FullNeighborhood();
1582 }
1583
1584 // We will grow this incrementally.
1585 // Index in the graph are first variables then constraints.
1586 const int num_nodes = num_vars + num_constraints;
1587 std::vector<bool> added(num_nodes, false);
1588 std::vector<bool> added_or_connected(num_nodes, false);
1589
1590 // We will process var/constraint node by minimum "score".
1591 struct QueueElement {
1592 int Index() const { return index; }
1593 bool operator<(const QueueElement& o) const {
1594 if (score == o.score) return tie_break < o.tie_break;
1595 return score < o.score;
1596 }
1597
1598 int index;
1599 int score = 0;
1600 double tie_break = 0.0;
1601 };
1602 std::vector<QueueElement> elements(num_nodes);
1604
1605 // Initialize elements.
1606 for (int i = 0; i < num_nodes; ++i) {
1607 elements[i].index = i;
1608 elements[i].tie_break = absl::Uniform<double>(random, 0.0, 1.0);
1609 }
1610
1611 // We start by a random active variable.
1612 //
1613 // Note that while num_vars contains all variables, all the fixed variable
1614 // will have no associated constraint, so we don't want to start from a
1615 // random variable.
1616 //
1617 // TODO(user): Does starting by a constraint make sense too?
1618 const int first_index =
1619 helper_.ActiveVariablesWhileHoldingLock()[absl::Uniform<int>(
1620 random, 0, num_active_vars)];
1621 elements[first_index].score = helper_.VarToConstraint()[first_index].size();
1622 pq.Add(elements[first_index]);
1623 added_or_connected[first_index] = true;
1624
1625 // Pop max-degree from queue and update.
1626 std::vector<int> to_update;
1627 while (!pq.IsEmpty() && relaxed_variables.size() < target_size) {
1628 // Just for logging.
1629 if (relaxed_variables.size() > 100 && pq.Size() < min_width_after_100) {
1630 min_width_after_100 = pq.Size();
1631 size_at_min_width_after_100 = relaxed_variables.size();
1632 }
1633
1634 const int index = pq.Top().index;
1635 const int score = pq.Top().score;
1636 pq.Pop();
1637 added[index] = true;
1638
1639 // When the score is zero, we don't need to update anything since the
1640 // frontier does not grow.
1641 if (score == 0) {
1642 if (index < num_vars) relaxed_variables.push_back(index);
1643 ++num_zero_score;
1644 continue;
1645 }
1646
1647 // Note that while it might looks bad, the overall complexity of this is
1648 // in O(num_edge) since we scan each index once and each newly connected
1649 // vertex once.
1650 int num_added = 0;
1651 to_update.clear();
1652 if (index < num_vars) {
1653 relaxed_variables.push_back(index);
1654 for (const int c : helper_.VarToConstraint()[index]) {
1655 const int c_index = num_vars + c;
1656 if (added_or_connected[c_index]) continue;
1657 ++num_added;
1658 added_or_connected[c_index] = true;
1659 to_update.push_back(c_index);
1660 for (const int v : helper_.ConstraintToVar()[c]) {
1661 if (added[v]) continue;
1662 if (added_or_connected[v]) {
1663 to_update.push_back(v);
1664 elements[v].score--;
1665 } else {
1666 elements[c_index].score++;
1667 }
1668 }
1669 }
1670 } else {
1671 for (const int v : helper_.ConstraintToVar()[index - num_vars]) {
1672 if (added_or_connected[v]) continue;
1673 ++num_added;
1674 added_or_connected[v] = true;
1675 to_update.push_back(v);
1676 for (const int c : helper_.VarToConstraint()[v]) {
1677 if (added[num_vars + c]) continue;
1678 if (added_or_connected[num_vars + c]) {
1679 elements[num_vars + c].score--;
1680 to_update.push_back(num_vars + c);
1681 } else {
1682 elements[v].score++;
1683 }
1684 }
1685 }
1686 }
1687
1688 // The score is exactly the frontier increase in size.
1689 // This is the same as the min-degree heuristic for the elimination order.
1690 // Except we only consider connected nodes.
1691 CHECK_EQ(num_added, score);
1692
1694 for (const int index : to_update) {
1695 DCHECK(!added[index]);
1696 if (pq.Contains(index)) {
1697 pq.ChangePriority(elements[index]);
1698 } else {
1699 pq.Add(elements[index]);
1700 }
1701 }
1702
1703 max_width = std::max(max_width, pq.Size());
1704 }
1705
1706 // Just for logging.
1707 if (pq.Size() < min_width_after_100) {
1708 min_width_after_100 = pq.Size();
1709 size_at_min_width_after_100 = relaxed_variables.size();
1710 }
1711
1712 VLOG(2) << "#relaxed " << relaxed_variables.size() << " #zero_score "
1713 << num_zero_score << " max_width " << max_width
1714 << " (size,min_width)_after_100 (" << size_at_min_width_after_100
1715 << "," << min_width_after_100 << ") " << " final_width "
1716 << pq.Size();
1717 }
1718
1719 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1720}
1721
1722namespace {
1723
1724// Given a (sub)set of binary variables and their initial solution values,
1725// returns a local branching constraint over these variables, that is:
1726// sum_{i : s[i] == 0} x_i + sum_{i : s[i] == 1} (1 - x_i) <= k
1727// where s is the initial solution and k is the neighborhood size. Requires all
1728// variables and initial solution values to be binary.
1729ConstraintProto LocalBranchingConstraint(
1730 const std::vector<int>& variable_indices,
1731 const std::vector<int64_t>& initial_solution, const int neighborhood_size) {
1732 DCHECK_EQ(variable_indices.size(), initial_solution.size());
1733 DCHECK_GE(neighborhood_size, 0);
1734 ConstraintProto local_branching_constraint;
1735 local_branching_constraint.set_name("local_branching");
1736 LinearConstraintProto* linear = local_branching_constraint.mutable_linear();
1737 int lhs_constant_value = 0;
1738 for (int i = 0; i < variable_indices.size(); ++i) {
1739 if (initial_solution[i] == 0) {
1740 linear->add_coeffs(1);
1741 linear->add_vars(variable_indices[i]);
1742 } else {
1743 DCHECK_EQ(initial_solution[i], 1);
1744 linear->add_coeffs(-1);
1745 linear->add_vars(variable_indices[i]);
1746 lhs_constant_value++;
1747 }
1748 }
1749 linear->add_domain(-lhs_constant_value);
1750 linear->add_domain(-lhs_constant_value + neighborhood_size);
1751 return local_branching_constraint;
1752}
1753
1754} // namespace
1755
1757 const CpSolverResponse& initial_solution, double difficulty,
1758 absl::BitGenRef random) {
1759 std::vector<int> active_variables = helper_.ActiveVariables();
1760
1761 // Collect active binary variables and corresponding initial solution values.
1762 // TODO(user): Extend to integer variables.
1763 std::vector<int> binary_var_indices;
1764 std::vector<int> non_binary_var_indices;
1765 std::vector<int64_t> binary_var_initial_solution;
1766 for (const int active_var_index : active_variables) {
1767 const IntegerVariableProto& var =
1768 helper_.ModelProto().variables(active_var_index);
1769 if (var.domain_size() == 2 && var.domain(0) == 0 && var.domain(1) == 1) {
1770 binary_var_indices.push_back(active_var_index);
1771 binary_var_initial_solution.push_back(
1772 initial_solution.solution(active_var_index));
1773 } else {
1774 non_binary_var_indices.push_back(active_var_index);
1775 }
1776 }
1777 if (binary_var_indices.empty()) {
1778 return helper_.NoNeighborhood();
1779 }
1780
1781 const int target_size =
1782 static_cast<int>(std::ceil(difficulty * binary_var_indices.size()));
1783
1784 // Create and solve local branching LP.
1785 CpModelProto local_branching_model = helper_.UpdatedModelProtoCopy();
1786 *local_branching_model.add_constraints() = LocalBranchingConstraint(
1787 binary_var_indices, binary_var_initial_solution, target_size);
1788 Model model("lb_relax_lns_lp");
1789 auto* const params = model.GetOrCreate<SatParameters>();
1790 // Parameters to enable solving the LP only.
1791 params->set_num_workers(1);
1792 params->set_linearization_level(2);
1793 params->set_stop_after_root_propagation(true);
1794 params->set_add_lp_constraints_lazily(false);
1795 // Parameters to attempt to speed up solve.
1796 params->set_cp_model_presolve(false);
1797 params->set_cp_model_probing_level(0);
1798 // Parameters to limit time spent in the solve. The max number of iterations
1799 // is relaxed from the default since we rely more on deterministic time.
1800 params->set_root_lp_iterations(100000);
1801 params->set_max_deterministic_time(10);
1802 if (global_time_limit_ != nullptr) {
1803 global_time_limit_->UpdateLocalLimit(model.GetOrCreate<TimeLimit>());
1804 }
1805 solve_callback_(local_branching_model, &model);
1806
1807 // Skip LNS if no (full) feasible solution was found for the LP.
1808 const auto lp_constraints =
1810 for (const LinearProgrammingConstraint* lp_constraint : *lp_constraints) {
1811 if (!lp_constraint->HasSolution()) {
1812 return helper_.NoNeighborhood();
1813 }
1814 }
1815
1816 // Compute differences between LP solution and initial solution, with a small
1817 // random noise for tie breaking.
1818 const auto var_mapping = model.GetOrCreate<CpModelMapping>();
1819 const auto lp_solution = model.GetOrCreate<ModelLpValues>();
1820 std::vector<double> differences;
1821 for (int i = 0; i < binary_var_indices.size(); ++i) {
1822 double difference =
1823 std::abs(lp_solution->at(var_mapping->Integer(binary_var_indices[i])) -
1824 binary_var_initial_solution[i]);
1825 differences.push_back(difference +
1826 absl::Uniform<double>(random, 0.0, 1e-6));
1827 }
1828
1829 // Take the target_size variables with largest differences.
1830 std::vector<int> vars_to_relax(binary_var_indices.size());
1831 absl::c_iota(vars_to_relax, 0);
1832 absl::c_sort(vars_to_relax, [&differences](const int i, const int j) {
1833 return differences[i] > differences[j];
1834 });
1835 vars_to_relax.resize(target_size);
1836
1837 // For now, we include all non-binary variables in the relaxation, since their
1838 // values are likely tied to the binary values.
1839 vars_to_relax.insert(vars_to_relax.end(), non_binary_var_indices.begin(),
1840 non_binary_var_indices.end());
1841 return helper_.RelaxGivenVariables(initial_solution, vars_to_relax);
1842}
1843
1844namespace {
1845
1846void AddPrecedence(const LinearExpressionProto& before,
1847 const LinearExpressionProto& after, CpModelProto* model) {
1848 LinearConstraintProto* linear = model->add_constraints()->mutable_linear();
1849 linear->add_domain(std::numeric_limits<int64_t>::min());
1850 linear->add_domain(after.offset() - before.offset());
1851 for (int i = 0; i < before.vars_size(); ++i) {
1852 linear->add_vars(before.vars(i));
1853 linear->add_coeffs(before.coeffs(i));
1854 }
1855 for (int i = 0; i < after.vars_size(); ++i) {
1856 linear->add_vars(after.vars(i));
1857 linear->add_coeffs(-after.coeffs(i));
1858 }
1859}
1860
1861} // namespace
1862
1864 const absl::Span<const std::pair<int, int>> precedences,
1865 const CpSolverResponse& initial_solution,
1866 const NeighborhoodGeneratorHelper& helper) {
1867 Neighborhood neighborhood = helper.FullNeighborhood();
1868
1869 neighborhood.is_reduced = !precedences.empty();
1870 if (!neighborhood.is_reduced) { // Returns the full neighborhood.
1871 helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
1872 neighborhood.is_generated = true;
1873 return neighborhood;
1874 }
1875
1876 // Collect seen intervals.
1877 absl::flat_hash_set<int> seen_intervals;
1878 for (const std::pair<int, int>& prec : precedences) {
1879 seen_intervals.insert(prec.first);
1880 seen_intervals.insert(prec.second);
1881 }
1882
1883 // Fix the presence/absence of unseen intervals.
1884 bool enforcement_literals_fixed = false;
1885 for (const int i : helper.TypeToConstraints(ConstraintProto::kInterval)) {
1886 if (seen_intervals.contains(i)) continue;
1887
1888 const ConstraintProto& interval_ct = helper.ModelProto().constraints(i);
1889 if (interval_ct.enforcement_literal().empty()) continue;
1890
1891 DCHECK_EQ(interval_ct.enforcement_literal().size(), 1);
1892 const int enforcement_ref = interval_ct.enforcement_literal(0);
1893 const int enforcement_var = PositiveRef(enforcement_ref);
1894 const int value = initial_solution.solution(enforcement_var);
1895
1896 // If the interval is not enforced, we just relax it. If it belongs to an
1897 // exactly one constraint, and the enforced interval is not relaxed, then
1898 // propagation will force this interval to stay not enforced. Otherwise,
1899 // LNS will be able to change which interval will be enforced among all
1900 // alternatives.
1901 if (RefIsPositive(enforcement_ref) == (value == 0)) continue;
1902
1903 // Fix the value.
1904 neighborhood.delta.mutable_variables(enforcement_var)->clear_domain();
1905 neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
1906 neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
1907 enforcement_literals_fixed = true;
1908 }
1909
1910 for (const std::pair<int, int>& prec : precedences) {
1911 const LinearExpressionProto& before_end =
1912 helper.ModelProto().constraints(prec.first).interval().end();
1913 const LinearExpressionProto& after_start =
1914 helper.ModelProto().constraints(prec.second).interval().start();
1915 DCHECK_LE(GetLinearExpressionValue(before_end, initial_solution),
1916 GetLinearExpressionValue(after_start, initial_solution));
1917 AddPrecedence(before_end, after_start, &neighborhood.delta);
1918 }
1919
1920 // Set the current solution as a hint.
1921 helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
1922 neighborhood.is_generated = true;
1923
1924 return neighborhood;
1925}
1926
1928 absl::Span<const int> intervals_to_relax,
1929 absl::Span<const int> variables_to_fix,
1930 const CpSolverResponse& initial_solution, absl::BitGenRef random,
1931 const NeighborhoodGeneratorHelper& helper) {
1932 Neighborhood neighborhood = helper.FullNeighborhood();
1933
1934 // We will extend the set with some interval that we cannot fix.
1935 absl::flat_hash_set<int> ignored_intervals(intervals_to_relax.begin(),
1936 intervals_to_relax.end());
1937
1938 // Fix the presence/absence of non-relaxed intervals.
1939 for (const int i : helper.TypeToConstraints(ConstraintProto::kInterval)) {
1940 DCHECK_GE(i, 0);
1941 if (ignored_intervals.contains(i)) continue;
1942
1943 const ConstraintProto& interval_ct = helper.ModelProto().constraints(i);
1944 if (interval_ct.enforcement_literal().empty()) continue;
1945
1946 DCHECK_EQ(interval_ct.enforcement_literal().size(), 1);
1947 const int enforcement_ref = interval_ct.enforcement_literal(0);
1948 const int enforcement_var = PositiveRef(enforcement_ref);
1949 const int value = initial_solution.solution(enforcement_var);
1950
1951 // If the interval is not enforced, we just relax it. If it belongs to an
1952 // exactly one constraint, and the enforced interval is not relaxed, then
1953 // propagation will force this interval to stay not enforced. Otherwise,
1954 // LNS will be able to change which interval will be enforced among all
1955 // alternatives.
1956 if (RefIsPositive(enforcement_ref) == (value == 0)) {
1957 ignored_intervals.insert(i);
1958 continue;
1959 }
1960
1961 // Fix the value.
1962 neighborhood.delta.mutable_variables(enforcement_var)->clear_domain();
1963 neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
1964 neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
1965 }
1966
1967 if (ignored_intervals.size() >=
1968 helper.TypeToConstraints(ConstraintProto::kInterval)
1969 .size()) { // Returns the full neighborhood.
1970 helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
1971 neighborhood.is_generated = true;
1972 return neighborhood;
1973 }
1974
1975 neighborhood.is_reduced = true;
1976
1977 // We differ from the ICAPS05 paper as we do not consider ignored intervals
1978 // when generating the precedence graph, instead of building the full graph,
1979 // then removing intervals, and reconstructing the precedence graph
1980 // heuristically after that.
1981 const std::vector<std::pair<int, int>> precedences =
1982 helper.GetSchedulingPrecedences(ignored_intervals, initial_solution,
1983 random);
1984 for (const std::pair<int, int>& prec : precedences) {
1985 const LinearExpressionProto& before_end =
1986 helper.ModelProto().constraints(prec.first).interval().end();
1987 const LinearExpressionProto& after_start =
1988 helper.ModelProto().constraints(prec.second).interval().start();
1989 DCHECK_LE(GetLinearExpressionValue(before_end, initial_solution),
1990 GetLinearExpressionValue(after_start, initial_solution));
1991 AddPrecedence(before_end, after_start, &neighborhood.delta);
1992 }
1993
1994 // fix the extra variables passed as parameters.
1995 for (const int var : variables_to_fix) {
1996 const int value = initial_solution.solution(var);
1997 neighborhood.delta.mutable_variables(var)->clear_domain();
1998 neighborhood.delta.mutable_variables(var)->add_domain(value);
1999 neighborhood.delta.mutable_variables(var)->add_domain(value);
2000 }
2001
2002 // Set the current solution as a hint.
2003 helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
2004 neighborhood.is_generated = true;
2005
2006 return neighborhood;
2007}
2008
2010 const CpSolverResponse& initial_solution, double difficulty,
2011 absl::BitGenRef random) {
2012 std::vector<int> intervals_to_relax =
2013 helper_.GetActiveIntervals(initial_solution);
2014 GetRandomSubset(difficulty, &intervals_to_relax, random);
2015
2017 intervals_to_relax, {}, initial_solution, random, helper_);
2018}
2019
2021 const CpSolverResponse& initial_solution, double difficulty,
2022 absl::BitGenRef random) {
2023 std::vector<std::pair<int, int>> precedences =
2024 helper_.GetSchedulingPrecedences({}, initial_solution, random);
2025 GetRandomSubset(1.0 - difficulty, &precedences, random);
2027 precedences, initial_solution, helper_);
2028}
2029
2030namespace {
2031void AppendVarsFromAllIntervalIndices(absl::Span<const int> indices,
2032 absl::Span<const int> all_intervals,
2033 const CpModelProto& model_proto,
2034 std::vector<int>* variables) {
2035 for (const int index : indices) {
2036 const std::vector<int> vars =
2037 UsedVariables(model_proto.constraints(all_intervals[index]));
2038 variables->insert(variables->end(), vars.begin(), vars.end());
2039 }
2040}
2041} // namespace
2042
2044 const CpSolverResponse& initial_solution, double difficulty,
2045 absl::BitGenRef random) {
2046 const std::vector<int> active_intervals =
2047 helper_.GetActiveIntervals(initial_solution);
2048
2049 if (active_intervals.empty()) return helper_.FullNeighborhood();
2050
2051 const TimePartition partition = PartitionIndicesAroundRandomTimeWindow(
2052 active_intervals, helper_.ModelProto(), initial_solution, difficulty,
2053 random);
2054 std::vector<int> intervals_to_relax;
2055 intervals_to_relax.reserve(partition.selected_indices.size());
2056 std::vector<int> variables_to_fix;
2057 intervals_to_relax.insert(intervals_to_relax.end(),
2058 partition.selected_indices.begin(),
2059 partition.selected_indices.end());
2060
2061 if (helper_.Parameters().push_all_tasks_toward_start()) {
2062 intervals_to_relax.insert(intervals_to_relax.end(),
2063 partition.indices_before_selected.begin(),
2064 partition.indices_before_selected.end());
2065 AppendVarsFromAllIntervalIndices(partition.indices_before_selected,
2066 active_intervals, helper_.ModelProto(),
2067 &variables_to_fix);
2068 }
2069
2070 gtl::STLSortAndRemoveDuplicates(&intervals_to_relax);
2071 gtl::STLSortAndRemoveDuplicates(&variables_to_fix);
2073 intervals_to_relax, variables_to_fix, initial_solution, random, helper_);
2074}
2075
2077 const CpSolverResponse& initial_solution, double difficulty,
2078 absl::BitGenRef random) {
2079 std::vector<int> intervals_to_relax;
2080 std::vector<int> variables_to_fix;
2081 std::vector<int> active_intervals;
2082 for (const std::vector<int>& intervals : intervals_in_constraints_) {
2083 active_intervals = helper_.KeepActiveIntervals(intervals, initial_solution);
2084 const TimePartition partition = PartitionIndicesAroundRandomTimeWindow(
2085 active_intervals, helper_.ModelProto(), initial_solution, difficulty,
2086 random);
2087 intervals_to_relax.insert(intervals_to_relax.end(),
2088 partition.selected_indices.begin(),
2089 partition.selected_indices.end());
2090
2091 if (helper_.Parameters().push_all_tasks_toward_start()) {
2092 intervals_to_relax.insert(intervals_to_relax.end(),
2093 partition.indices_before_selected.begin(),
2094 partition.indices_before_selected.end());
2095 AppendVarsFromAllIntervalIndices(partition.indices_before_selected,
2096 active_intervals, helper_.ModelProto(),
2097 &variables_to_fix);
2098 }
2099 }
2100
2101 if (intervals_to_relax.empty() && variables_to_fix.empty()) {
2102 return helper_.FullNeighborhood();
2103 }
2104
2105 gtl::STLSortAndRemoveDuplicates(&intervals_to_relax);
2106 gtl::STLSortAndRemoveDuplicates(&variables_to_fix);
2108 intervals_to_relax, variables_to_fix, initial_solution, random, helper_);
2109}
2110
2112 const CpSolverResponse& initial_solution, double difficulty,
2113 absl::BitGenRef random) {
2114 std::vector<std::pair<int, int>> rectangles_to_freeze =
2115 helper_.GetActiveRectangles(initial_solution);
2116 GetRandomSubset(1.0 - difficulty, &rectangles_to_freeze, random);
2117
2118 absl::flat_hash_set<int> variables_to_freeze;
2119 for (const auto& [x, y] : rectangles_to_freeze) {
2120 InsertVariablesFromConstraint(helper_.ModelProto(), x, variables_to_freeze);
2121 InsertVariablesFromConstraint(helper_.ModelProto(), y, variables_to_freeze);
2122 }
2123
2124 return helper_.FixGivenVariables(initial_solution, variables_to_freeze);
2125}
2126
2128 const CpSolverResponse& initial_solution, double difficulty,
2129 absl::BitGenRef random) {
2130 std::vector<std::pair<int, int>> rectangles_to_relax =
2131 helper_.GetActiveRectangles(initial_solution);
2132 GetRandomSubset(difficulty, &rectangles_to_relax, random);
2133 std::vector<int> intervals_to_relax;
2134 for (const auto& [x, y] : rectangles_to_relax) {
2135 intervals_to_relax.push_back(x);
2136 intervals_to_relax.push_back(y);
2137 }
2138 gtl::STLSortAndRemoveDuplicates(&intervals_to_relax);
2139
2141 intervals_to_relax, {}, initial_solution, random, helper_);
2142}
2143
2145 const CpSolverResponse& initial_solution, double difficulty,
2146 absl::BitGenRef random) {
2147 const std::vector<std::pair<int, int>> active_rectangles =
2148 helper_.GetActiveRectangles(initial_solution);
2149 const bool use_first_dimension = absl::Bernoulli(random, 0.5);
2150 std::vector<int> projected_intervals;
2151 projected_intervals.reserve(active_rectangles.size());
2152 for (const auto& [x, y] : active_rectangles) {
2153 projected_intervals.push_back(use_first_dimension ? x : y);
2154 }
2155
2156 const TimePartition partition = PartitionIndicesAroundRandomTimeWindow(
2157 projected_intervals, helper_.ModelProto(), initial_solution, difficulty,
2158 random);
2159 std::vector<bool> indices_to_fix(active_rectangles.size(), true);
2160 for (const int index : partition.selected_indices) {
2161 indices_to_fix[index] = false;
2162 }
2163
2164 absl::flat_hash_set<int> variables_to_freeze;
2165 for (int index = 0; index < active_rectangles.size(); ++index) {
2166 if (indices_to_fix[index]) {
2168 active_rectangles[index].first,
2169 variables_to_freeze);
2171 active_rectangles[index].second,
2172 variables_to_freeze);
2173 }
2174 }
2175
2176 return helper_.FixGivenVariables(initial_solution, variables_to_freeze);
2177}
2178
2180 const CpSolverResponse& initial_solution, double difficulty,
2181 absl::BitGenRef random) {
2182 const std::vector<std::vector<int>> all_paths =
2183 helper_.GetRoutingPaths(initial_solution);
2184
2185 // Collect all unique variables.
2186 absl::flat_hash_set<int> all_path_variables;
2187 for (auto& path : all_paths) {
2188 all_path_variables.insert(path.begin(), path.end());
2189 }
2190 std::vector<int> fixed_variables(all_path_variables.begin(),
2191 all_path_variables.end());
2192 std::sort(fixed_variables.begin(), fixed_variables.end());
2193 GetRandomSubset(1.0 - difficulty, &fixed_variables, random);
2195 initial_solution, {fixed_variables.begin(), fixed_variables.end()});
2196}
2197
2199 const CpSolverResponse& initial_solution, double difficulty,
2200 absl::BitGenRef random) {
2201 std::vector<std::vector<int>> all_paths =
2202 helper_.GetRoutingPaths(initial_solution);
2203
2204 // Collect all unique variables.
2205 absl::flat_hash_set<int> all_path_variables;
2206 for (const auto& path : all_paths) {
2207 all_path_variables.insert(path.begin(), path.end());
2208 }
2209
2210 // Select variables to relax.
2211 const int num_variables_to_relax =
2212 static_cast<int>(all_path_variables.size() * difficulty);
2213 absl::flat_hash_set<int> relaxed_variables;
2214 while (relaxed_variables.size() < num_variables_to_relax) {
2215 DCHECK(!all_paths.empty());
2216 const int path_index = absl::Uniform<int>(random, 0, all_paths.size());
2217 std::vector<int>& path = all_paths[path_index];
2218 const int path_size = path.size();
2219 const int segment_length =
2220 std::min(path_size, absl::Uniform<int>(random, 4, 8));
2221 const int segment_start =
2222 absl::Uniform<int>(random, 0, path_size - segment_length);
2223 for (int i = segment_start; i < segment_start + segment_length; ++i) {
2224 relaxed_variables.insert(path[i]);
2225 }
2226
2227 // Remove segment and clean up empty paths.
2228 path.erase(path.begin() + segment_start,
2229 path.begin() + segment_start + segment_length);
2230 if (path.empty()) {
2231 std::swap(all_paths[path_index], all_paths.back());
2232 all_paths.pop_back();
2233 }
2234 }
2235
2236 // Compute the set of variables to fix.
2237 absl::flat_hash_set<int> fixed_variables;
2238 for (const int var : all_path_variables) {
2239 if (!relaxed_variables.contains(var)) fixed_variables.insert(var);
2240 }
2241 return helper_.FixGivenVariables(initial_solution, fixed_variables);
2242}
2243
2245 const CpSolverResponse& initial_solution, double difficulty,
2246 absl::BitGenRef random) {
2247 std::vector<std::vector<int>> all_paths =
2248 helper_.GetRoutingPaths(initial_solution);
2249 // Remove a corner case where all paths are empty.
2250 if (all_paths.empty()) {
2251 return helper_.NoNeighborhood();
2252 }
2253
2254 // Collect all unique variables.
2255 absl::flat_hash_set<int> all_path_variables;
2256 for (const auto& path : all_paths) {
2257 all_path_variables.insert(path.begin(), path.end());
2258 }
2259
2260 // Select variables to relax.
2261 const int num_variables_to_relax =
2262 static_cast<int>(all_path_variables.size() * difficulty);
2263 absl::flat_hash_set<int> relaxed_variables;
2264
2265 // Relax the start and end of each path to ease relocation.
2266 for (const auto& path : all_paths) {
2267 relaxed_variables.insert(path.front());
2268 relaxed_variables.insert(path.back());
2269 }
2270
2271 // Randomize paths.
2272 for (auto& path : all_paths) {
2273 std::shuffle(path.begin(), path.end(), random);
2274 }
2275
2276 // Relax all variables (if possible) in one random path.
2277 const int path_to_clean = absl::Uniform<int>(random, 0, all_paths.size());
2278 while (relaxed_variables.size() < num_variables_to_relax &&
2279 !all_paths[path_to_clean].empty()) {
2280 relaxed_variables.insert(all_paths[path_to_clean].back());
2281 all_paths[path_to_clean].pop_back();
2282 }
2283 if (all_paths[path_to_clean].empty()) {
2284 std::swap(all_paths[path_to_clean], all_paths.back());
2285 all_paths.pop_back();
2286 }
2287
2288 // Relax more variables until the target is reached.
2289 while (relaxed_variables.size() < num_variables_to_relax) {
2290 DCHECK(!all_paths.empty());
2291 const int path_index = absl::Uniform<int>(random, 0, all_paths.size());
2292 relaxed_variables.insert(all_paths[path_index].back());
2293
2294 // Remove variable and clean up empty paths.
2295 all_paths[path_index].pop_back();
2296 if (all_paths[path_index].empty()) {
2297 std::swap(all_paths[path_index], all_paths.back());
2298 all_paths.pop_back();
2299 }
2300 }
2301
2302 // Compute the set of variables to fix.
2303 absl::flat_hash_set<int> fixed_variables;
2304 for (const int var : all_path_variables) {
2305 if (!relaxed_variables.contains(var)) fixed_variables.insert(var);
2306 }
2307 return helper_.FixGivenVariables(initial_solution, fixed_variables);
2308}
2309
2311 return (incomplete_solutions_->HasSolution() ||
2312 lp_solutions_->NumSolutions() > 0);
2313}
2314
2316 const CpSolverResponse& /*initial_solution*/, double difficulty,
2317 absl::BitGenRef random) {
2318 Neighborhood neighborhood = helper_.FullNeighborhood();
2319 neighborhood.is_generated = false;
2320
2321 const ReducedDomainNeighborhood reduced_domains =
2322 GetRinsRensNeighborhood(response_manager_, lp_solutions_,
2323 incomplete_solutions_, difficulty, random);
2324
2325 if (reduced_domains.fixed_vars.empty() &&
2326 reduced_domains.reduced_domain_vars.empty()) {
2327 return neighborhood;
2328 }
2329 neighborhood.source_info = reduced_domains.source_info;
2330
2331 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
2332 // Fix the variables in the local model.
2333 for (const std::pair</*model_var*/ int, /*value*/ int64_t>& fixed_var :
2334 reduced_domains.fixed_vars) {
2335 const int var = fixed_var.first;
2336 const int64_t value = fixed_var.second;
2337 if (var >= neighborhood.delta.variables_size()) continue;
2338 if (!helper_.IsActive(var)) continue;
2339
2340 if (!DomainInProtoContains(neighborhood.delta.variables(var), value)) {
2341 // TODO(user): Instead of aborting, pick the closest point in the domain?
2342 return neighborhood;
2343 }
2344
2345 neighborhood.delta.mutable_variables(var)->clear_domain();
2346 neighborhood.delta.mutable_variables(var)->add_domain(value);
2347 neighborhood.delta.mutable_variables(var)->add_domain(value);
2348 neighborhood.is_reduced = true;
2349 }
2350
2351 for (const std::pair</*model_var*/ int,
2352 /*domain*/ std::pair<int64_t, int64_t>>& reduced_var :
2353 reduced_domains.reduced_domain_vars) {
2354 const int var = reduced_var.first;
2355 const int64_t lb = reduced_var.second.first;
2356 const int64_t ub = reduced_var.second.second;
2357 if (var >= neighborhood.delta.variables_size()) continue;
2358 if (!helper_.IsActive(var)) continue;
2359 const Domain domain =
2360 ReadDomainFromProto(neighborhood.delta.variables(var));
2361 Domain new_domain = domain.IntersectionWith(Domain(lb, ub));
2362 if (new_domain.IsEmpty()) {
2363 new_domain = Domain::FromValues(
2364 {domain.ClosestValue(lb), domain.ClosestValue(ub)});
2365 }
2366 FillDomainInProto(domain, neighborhood.delta.mutable_variables(var));
2367 neighborhood.is_reduced = true;
2368 }
2369 neighborhood.is_generated = true;
2370 return neighborhood;
2371}
2372
2373} // namespace sat
2374} // namespace operations_research
IntegerValue y
IntegerValue x_start
IntegerValue y_start
IntegerValue size
int64_t min
A connected components finder that only works on dense ints.
bool AddEdge(int node1, int node2)
void Update(int num_decreases, int num_increases)
static Domain FromValues(std::vector< int64_t > values)
Domain IntersectionWith(const Domain &domain) const
bool Contains(int64_t value) const
bool IsIncludedIn(const Domain &domain) const
int64_t ClosestValue(int64_t wanted) const
int Size() const
Returns the number of elements currently present.
Definition integer_pq.h:56
bool Contains(int index) const
Returns true if the element with given index is currently in the queue.
Definition integer_pq.h:67
void UpdateLocalLimit(TimeLimit *local_limit)
Definition time_limit.h:346
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
void RemoveBySwap(K key, int index)
Definition util.h:130
size_t size() const
Size of the "key" space, always in [0, size()).
Definition util.h:819
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
A class that stores the collection of all LP constraints in a model.
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood FullNeighborhood() const
Returns a neighborhood that correspond to the full problem.
bool IsActive(int var) const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
CpModelProto UpdatedModelProtoCopy() const
Returns a copy of the problem proto with updated domains.
Neighborhood RelaxGivenVariables(const CpSolverResponse &initial_solution, const std::vector< int > &relaxed_variables) const
Neighborhood FixAllVariables(const CpSolverResponse &initial_solution) const
Neighborhood FixGivenVariables(const CpSolverResponse &base_solution, const absl::flat_hash_set< int > &variables_to_fix) const
const std::vector< int > & ActiveVariablesWhileHoldingLock() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
const CompactVectorVector< int, int > & VarToConstraint() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
std::vector< std::pair< int, int > > GetActiveRectangles(const CpSolverResponse &initial_solution) const
const SharedResponseManager & shared_response() const
NeighborhoodGeneratorHelper(CpModelProto const *model_proto, SatParameters const *parameters, SharedResponseManager *shared_response, SharedBoundsManager *shared_bounds=nullptr)
std::vector< int > ActiveVariables() const
Returns the list of "active" variables.
std::vector< std::vector< int > > GetUniqueIntervalSets() const
const CompactVectorVector< int, int > & ConstraintToVar() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
bool DifficultyMeansFullNeighborhood(double difficulty) const
void AddSolutionHinting(const CpSolverResponse &initial_solution, CpModelProto *model_proto) const
std::vector< std::vector< int > > GetRoutingPaths(const CpSolverResponse &initial_solution) const
std::vector< int > ActiveObjectiveVariablesWhileHoldingLock() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
std::vector< int > GetActiveIntervals(const CpSolverResponse &initial_solution) const
std::vector< std::pair< int, int > > GetSchedulingPrecedences(const absl::flat_hash_set< int > &ignored_intervals, const CpSolverResponse &initial_solution, absl::BitGenRef random) const
absl::Span< const int > TypeToConstraints(ConstraintProto::ConstraintCase type) const
Returns all the constraints indices of a given type.
std::vector< int > KeepActiveIntervals(absl::Span< const int > unfiltered_intervals, const CpSolverResponse &initial_solution) const
virtual bool ReadyToGenerate() const
Returns true if the neighborhood generator can generate a neighborhood.
double difficulty() const
The current difficulty of this generator.
double GetUCBScore(int64_t total_num_calls) const
const NeighborhoodGeneratorHelper & helper_
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
Both initial solution and difficulty values are ignored.
bool ReadyToGenerate() const override
Returns true if the required solutions are available.
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)
void LogMessageWithThrottling(const std::string &prefix, const std::string &message)
const SharedSolutionRepository< int64_t > & SolutionsRepository() const
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
SubsolverType type() const
Returns the type of the subsolver.
Definition subsolver.h:99
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
int64_t a
Definition table.cc:44
SatParameters parameters
int64_t y_end
int index_in_input_vector
int64_t x_end
std::vector< int > indices_after_selected
int interval_index
int64_t height
std::vector< int > indices_before_selected
double noise
std::vector< int > selected_indices
const Constraint * ct
int64_t value
IntVar * var
GRBmodel * model
GurobiMPCallbackContext * context
int constraint_index
int literal
int ct_index
int index
int tail_index
const bool DEBUG_MODE
Definition macros.h:24
RowIndex row
Definition markowitz.cc:186
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
ReducedDomainNeighborhood GetRinsRensNeighborhood(const SharedResponseManager *response_manager, const SharedLPSolutionRepository *lp_solutions, SharedIncompleteSolutionManager *incomplete_solutions, double difficulty, absl::BitGenRef random)
Definition rins.cc:174
bool DomainInProtoContains(const ProtoWithDomain &proto, int64_t value)
Neighborhood GenerateSchedulingNeighborhoodFromIntervalPrecedences(const absl::Span< const std::pair< int, int > > precedences, const CpSolverResponse &initial_solution, const NeighborhoodGeneratorHelper &helper)
Neighborhood GenerateSchedulingNeighborhoodFromRelaxedIntervals(absl::Span< const int > intervals_to_relax, absl::Span< const int > variables_to_fix, const CpSolverResponse &initial_solution, absl::BitGenRef random, const NeighborhoodGeneratorHelper &helper)
std::vector< int > UsedVariables(const ConstraintProto &ct)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
Returns the sorted list of interval used by a constraint.
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
void InsertVariablesFromConstraint(const CpModelProto &model_proto, int index, Set &output)
Insert variables in a constraint into a set.
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapSub(int64_t x, int64_t y)
STL namespace.
const Variable x
Definition qp_tests.cc:127
int64_t demand
Definition resource.cc:126
IntervalVar * interval
Definition resource.cc:101
int head
int tail
std::optional< int64_t > end
int64_t start
Adds solve data about one "solved" neighborhood.
Neighborhood returned by Neighborhood generators.
std::string source_info
Overwrites the name of the neighborhood generator in the logs.
bool is_simple
True if this neighborhood was just obtained by fixing some variables.
int num_relaxed_variables
Statistic, only filled when is_simple is true.
bool is_generated
True if neighborhood generator was able to generate a neighborhood.
std::vector< int > variables_that_can_be_fixed_to_local_optimum
std::vector< std::pair< int, std::pair< int64_t, int64_t > > > reduced_domain_vars
Definition rins.h:44
std::vector< std::pair< int, int64_t > > fixed_vars
A variable will appear only once and not in both vectors.
Definition rins.h:41