Google OR-Tools v9.12
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-2025 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 <memory>
23#include <random>
24#include <string>
25#include <tuple>
26#include <utility>
27#include <vector>
28
29#include "absl/algorithm/container.h"
30#include "absl/base/log_severity.h"
31#include "absl/container/flat_hash_map.h"
32#include "absl/container/flat_hash_set.h"
33#include "absl/flags/flag.h"
34#include "absl/log/check.h"
35#include "absl/meta/type_traits.h"
36#include "absl/random/bit_gen_ref.h"
37#include "absl/random/distributions.h"
38#include "absl/strings/str_cat.h"
39#include "absl/strings/str_join.h"
40#include "absl/synchronization/mutex.h"
41#include "absl/types/span.h"
42#include "google/protobuf/arena.h"
46#include "ortools/sat/cp_model.pb.h"
54#include "ortools/sat/model.h"
56#include "ortools/sat/rins.h"
57#include "ortools/sat/sat_parameters.pb.h"
60#include "ortools/sat/util.h"
62#include "ortools/util/bitset.h"
68
69namespace operations_research {
70namespace sat {
71
73 CpModelProto const* model_proto, SatParameters const* parameters,
75 : SubSolver("neighborhood_helper", HELPER),
76 parameters_(*parameters),
77 model_proto_(*model_proto),
78 shared_bounds_(shared_bounds),
79 shared_response_(shared_response) {
80 // Initialize proto memory.
81 local_arena_storage_.assign(Neighborhood::kDefaultArenaSizePerVariable *
82 model_proto_.variables_size(),
83 0);
84 local_arena_ = std::make_unique<google::protobuf::Arena>(
85 local_arena_storage_.data(), local_arena_storage_.size());
86 simplified_model_proto_ =
87 google::protobuf::Arena::Create<CpModelProto>(local_arena_.get());
88
89 CHECK(shared_response_ != nullptr);
90 if (shared_bounds_ != nullptr) {
91 shared_bounds_id_ = shared_bounds_->RegisterNewId();
92 }
93 *model_proto_with_only_variables_.mutable_variables() =
94 model_proto_.variables();
95 InitializeHelperData();
96 RecomputeHelperData();
98}
99
101 if (shared_bounds_ != nullptr) {
102 std::vector<int> model_variables;
103 std::vector<int64_t> new_lower_bounds;
104 std::vector<int64_t> new_upper_bounds;
105 shared_bounds_->GetChangedBounds(shared_bounds_id_, &model_variables,
106 &new_lower_bounds, &new_upper_bounds);
107
108 bool new_variables_have_been_fixed = false;
109
110 if (!model_variables.empty()) {
111 absl::MutexLock domain_lock(&domain_mutex_);
112
113 for (int i = 0; i < model_variables.size(); ++i) {
114 const int var = model_variables[i];
115 const int64_t new_lb = new_lower_bounds[i];
116 const int64_t new_ub = new_upper_bounds[i];
117 if (VLOG_IS_ON(3)) {
118 const auto& domain =
119 model_proto_with_only_variables_.variables(var).domain();
120 const int64_t old_lb = domain.Get(0);
121 const int64_t old_ub = domain.Get(domain.size() - 1);
122 VLOG(3) << "Variable: " << var << " old domain: [" << old_lb << ", "
123 << old_ub << "] new domain: [" << new_lb << ", " << new_ub
124 << "]";
125 }
126 const Domain old_domain = ReadDomainFromProto(
127 model_proto_with_only_variables_.variables(var));
128 const Domain new_domain =
129 old_domain.IntersectionWith(Domain(new_lb, new_ub));
130 if (new_domain.IsEmpty()) {
131 // This can mean two things:
132 // 1/ This variable is a normal one and the problem is UNSAT or
133 // 2/ This variable is optional, and its associated literal must be
134 // set to false.
135 //
136 // Currently, we wait for any full solver to pick the crossing bounds
137 // and do the correct stuff on their own. We do not want to have empty
138 // domain in the proto as this would means INFEASIBLE. So we just
139 // ignore such bounds here.
140 //
141 // TODO(user): We could set the optional literal to false directly in
142 // the bound sharing manager. We do have to be careful that all the
143 // different solvers have the same optionality definition though.
144 continue;
145 }
147 new_domain,
148 model_proto_with_only_variables_.mutable_variables(var));
149 new_variables_have_been_fixed |= new_domain.IsFixed();
150 }
151 }
152
153 // Only trigger the computation if needed.
154 if (new_variables_have_been_fixed) {
155 RecomputeHelperData();
156 }
157 }
158}
159
160bool NeighborhoodGeneratorHelper::ObjectiveDomainIsConstraining() const {
161 if (!model_proto_.has_objective()) return false;
162 if (model_proto_.objective().domain().empty()) return false;
163
164 int64_t min_activity = 0;
165 int64_t max_activity = 0;
166 const int num_terms = model_proto_.objective().vars().size();
167 for (int i = 0; i < num_terms; ++i) {
168 const int var = PositiveRef(model_proto_.objective().vars(i));
169 const int64_t coeff = model_proto_.objective().coeffs(i);
170 const auto& var_domain =
171 model_proto_with_only_variables_.variables(var).domain();
172 const int64_t v1 = coeff * var_domain[0];
173 const int64_t v2 = coeff * var_domain[var_domain.size() - 1];
174 min_activity += std::min(v1, v2);
175 max_activity += std::max(v1, v2);
176 }
177
178 const Domain obj_domain = ReadDomainFromProto(model_proto_.objective());
179 const Domain inferred_domain =
180 Domain(min_activity, max_activity)
182 Domain(std::numeric_limits<int64_t>::min(), obj_domain.Max()));
183 return !inferred_domain.IsIncludedIn(obj_domain);
184}
185
186void NeighborhoodGeneratorHelper::InitializeHelperData() {
187 type_to_constraints_.clear();
188 const int num_constraints = model_proto_.constraints_size();
189 for (int c = 0; c < num_constraints; ++c) {
190 const int type = model_proto_.constraints(c).constraint_case();
191 if (type >= type_to_constraints_.size()) {
192 type_to_constraints_.resize(type + 1);
193 }
194 type_to_constraints_[type].push_back(c);
195 }
196
197 const int num_variables = model_proto_.variables().size();
198 is_in_objective_.resize(num_variables, false);
199 if (model_proto_.has_objective()) {
200 for (const int ref : model_proto_.objective().vars()) {
201 is_in_objective_[PositiveRef(ref)] = true;
202 }
203 }
204}
205
206// Recompute all the data when new variables have been fixed. Note that this
207// shouldn't be called if there is no change as it is in O(problem size).
208void NeighborhoodGeneratorHelper::RecomputeHelperData() {
209 absl::MutexLock graph_lock(&graph_mutex_);
210 absl::ReaderMutexLock domain_lock(&domain_mutex_);
211
212 // Do basic presolving to have a more precise graph.
213 // Here we just remove trivially true constraints.
214 //
215 // Note(user): We do that each time a new variable is fixed. It might be too
216 // much, but on the miplib and in 1200s, we do that only about 1k time on the
217 // worst case problem.
218 //
219 // TODO(user): Change API to avoid a few copy?
220 // TODO(user): We could keep the context in the class.
221 // TODO(user): We can also start from the previous simplified model instead.
222 {
223 Model local_model;
224 CpModelProto mapping_proto;
225 // We want to replace the simplified_model_proto_ by a new one. Since
226 // deleting an object in the arena doesn't free the memory, we also delete
227 // and recreate the arena, but reusing the same storage.
228 int64_t new_size = local_arena_->SpaceUsed();
229 new_size += new_size / 2;
230 simplified_model_proto_->Clear();
231 local_arena_.reset();
232 local_arena_storage_.resize(new_size);
233 local_arena_ = std::make_unique<google::protobuf::Arena>(
234 local_arena_storage_.data(), local_arena_storage_.size());
235 simplified_model_proto_ =
236 google::protobuf::Arena::Create<CpModelProto>(local_arena_.get());
237 *simplified_model_proto_->mutable_variables() =
238 model_proto_with_only_variables_.variables();
239 PresolveContext context(&local_model, simplified_model_proto_,
240 &mapping_proto);
241 ModelCopy copier(&context);
242
243 // TODO(user): Not sure what to do if the model is UNSAT.
244 // This shouldn't matter as it should be dealt with elsewhere.
245 copier.ImportAndSimplifyConstraints(model_proto_, {});
246 }
247
248 // Compute the constraint <-> variable graph.
249 //
250 // TODO(user): Remove duplicate constraints?
251 const auto& constraints = simplified_model_proto_->constraints();
252 constraint_to_var_.clear();
253 constraint_to_var_.reserve(constraints.size());
254 for (int ct_index = 0; ct_index < constraints.size(); ++ct_index) {
255 // We remove the interval constraints since we should have an equivalent
256 // linear constraint somewhere else. This is not the case if we have a fixed
257 // size optional interval variable. But it should not matter as the
258 // intervals are replaced by their underlying variables in the scheduling
259 // constraints.
260 if (constraints[ct_index].constraint_case() == ConstraintProto::kInterval) {
261 continue;
262 }
263
264 tmp_row_.clear();
265 for (const int var : UsedVariables(constraints[ct_index])) {
266 if (IsConstant(var)) continue;
267 tmp_row_.push_back(var);
268 }
269
270 // We replace intervals by their underlying integer variables. Note that
271 // this is needed for a correct decomposition into independent part.
272 bool need_sort = false;
273 for (const int interval : UsedIntervals(constraints[ct_index])) {
274 need_sort = true;
275 for (const int var : UsedVariables(constraints[interval])) {
276 if (IsConstant(var)) continue;
277 tmp_row_.push_back(var);
278 }
279 }
280
281 // We remove constraint of size 0 and 1 since they are not useful for LNS
282 // based on this graph.
283 if (tmp_row_.size() <= 1) {
284 continue;
285 }
286
287 // Keep this constraint.
288 if (need_sort) {
290 }
291 constraint_to_var_.Add(tmp_row_);
292 }
293
294 // Initialize var to constraints, and make sure it has an entry for all
295 // variables.
296 var_to_constraint_.ResetFromTranspose(
297 constraint_to_var_,
298 /*min_transpose_size=*/model_proto_.variables().size());
299
300 // We mark as active all non-constant variables.
301 // Non-active variable will never be fixed in standard LNS fragment.
302 active_variables_.clear();
303 const int num_variables = model_proto_.variables_size();
304 active_variables_set_.assign(num_variables, false);
305 for (int i = 0; i < num_variables; ++i) {
306 if (!IsConstant(i)) {
307 active_variables_.push_back(i);
308 active_variables_set_[i] = true;
309 }
310 }
311
312 active_objective_variables_.clear();
313 for (const int var : model_proto_.objective().vars()) {
314 DCHECK(RefIsPositive(var));
315 if (active_variables_set_[var]) {
316 active_objective_variables_.push_back(var);
317 }
318 }
319
320 // Compute connected components.
321 // Note that fixed variable are just ignored.
322 DenseConnectedComponentsFinder union_find;
323 union_find.SetNumberOfNodes(num_variables);
324 for (int c = 0; c < constraint_to_var_.size(); ++c) {
325 const auto row = constraint_to_var_[c];
326 if (row.size() <= 1) continue;
327 for (int i = 1; i < row.size(); ++i) {
328 union_find.AddEdge(row[0], row[i]);
329 }
330 }
331
332 // If we have a lower bound on the objective, then this "objective constraint"
333 // might link components together.
334 if (ObjectiveDomainIsConstraining()) {
335 const auto& refs = model_proto_.objective().vars();
336 const int num_terms = refs.size();
337 for (int i = 1; i < num_terms; ++i) {
338 union_find.AddEdge(PositiveRef(refs[0]), PositiveRef(refs[i]));
339 }
340 }
341
342 // Compute all components involving non-fixed variables.
343 //
344 // TODO(user): If a component has no objective, we can fix it to any feasible
345 // solution. This will automatically be done by LNS fragment covering such
346 // component though.
347 components_.clear();
348 var_to_component_index_.assign(num_variables, -1);
349 for (int var = 0; var < num_variables; ++var) {
350 if (IsConstant(var)) continue;
351 const int root = union_find.FindRoot(var);
352 DCHECK_LT(root, var_to_component_index_.size());
353 int& index = var_to_component_index_[root];
354 if (index == -1) {
355 index = components_.size();
356 components_.push_back({});
357 }
358 var_to_component_index_[var] = index;
359 components_[index].push_back(var);
360 }
361
362 // Display information about the reduced problem.
363 //
364 // TODO(user): Exploit connected component while generating fragments.
365 // TODO(user): Do not generate fragment not touching the objective.
366 if (!shared_response_->LoggingIsEnabled()) return;
367
368 std::vector<int> component_sizes;
369 for (const std::vector<int>& component : components_) {
370 component_sizes.push_back(component.size());
371 }
372 std::sort(component_sizes.begin(), component_sizes.end(),
373 std::greater<int>());
374 std::string compo_message;
375 if (component_sizes.size() > 1) {
376 if (component_sizes.size() <= 10) {
377 compo_message =
378 absl::StrCat(" compo:", absl::StrJoin(component_sizes, ","));
379 } else {
380 component_sizes.resize(10);
381 compo_message =
382 absl::StrCat(" compo:", absl::StrJoin(component_sizes, ","), ",...");
383 }
384 }
385
386 // TODO(user): This is not ideal, as if two reductions appears in a row and
387 // nothing else is done for a while, we will never see the "latest" size
388 // in the log until it is reduced again.
389 shared_response_->LogMessageWithThrottling(
390 "Model", absl::StrCat("var:", active_variables_.size(), "/",
391 num_variables, " constraints:",
392 simplified_model_proto_->constraints().size(), "/",
393 model_proto_.constraints().size(), compo_message));
394}
395
397 return active_variables_set_[var];
398}
399
400bool NeighborhoodGeneratorHelper::IsConstant(int var) const {
401 const auto& var_proto = model_proto_with_only_variables_.variables(var);
402 return var_proto.domain_size() == 2 &&
403 var_proto.domain(0) == var_proto.domain(1);
404}
405
407 Neighborhood neighborhood;
408 neighborhood.is_reduced = false;
409 neighborhood.is_generated = true;
410 {
411 absl::ReaderMutexLock lock(&domain_mutex_);
412 *neighborhood.delta.mutable_variables() =
413 model_proto_with_only_variables_.variables();
414 }
415 return neighborhood;
416}
417
419 Neighborhood neighborhood;
420 neighborhood.is_generated = false;
421 return neighborhood;
422}
423
424bool NeighborhoodGeneratorHelper::IntervalIsActive(
425 int index, const CpSolverResponse& initial_solution) const {
426 const ConstraintProto& interval_ct = ModelProto().constraints(index);
427 // We only look at intervals that are performed in the solution. The
428 // unperformed intervals should be automatically freed during the generation
429 // phase.
430 if (interval_ct.enforcement_literal().size() == 1) {
431 const int enforcement_ref = interval_ct.enforcement_literal(0);
432 const int enforcement_var = PositiveRef(enforcement_ref);
433 const int value = initial_solution.solution(enforcement_var);
434 if (RefIsPositive(enforcement_ref) == (value == 0)) return false;
435 }
436
437 for (const int v : interval_ct.interval().start().vars()) {
438 if (!IsConstant(v)) return true;
439 }
440 for (const int v : interval_ct.interval().size().vars()) {
441 if (!IsConstant(v)) return true;
442 }
443 for (const int v : interval_ct.interval().end().vars()) {
444 if (!IsConstant(v)) return true;
445 }
446 return false;
447}
448
450 absl::Span<const int> unfiltered_intervals,
451 const CpSolverResponse& initial_solution) const {
452 std::vector<int> filtered_intervals;
453 filtered_intervals.reserve(unfiltered_intervals.size());
454 absl::ReaderMutexLock lock(&domain_mutex_);
455 for (const int i : unfiltered_intervals) {
456 if (IntervalIsActive(i, initial_solution)) filtered_intervals.push_back(i);
457 }
458 return filtered_intervals;
459}
460
462 const CpSolverResponse& initial_solution) const {
463 return KeepActiveIntervals(TypeToConstraints(ConstraintProto::kInterval),
464 initial_solution);
465}
466
467std::vector<NeighborhoodGeneratorHelper::ActiveRectangle>
469 const CpSolverResponse& initial_solution) const {
470 const std::vector<int> active_intervals =
471 GetActiveIntervals(initial_solution);
472 const absl::flat_hash_set<int> active_intervals_set(active_intervals.begin(),
473 active_intervals.end());
474
475 absl::flat_hash_map<std::pair<int, int>, std::vector<int>> active_rectangles;
476 for (const int ct_index : TypeToConstraints(ConstraintProto::kNoOverlap2D)) {
477 const NoOverlap2DConstraintProto& ct =
478 model_proto_.constraints(ct_index).no_overlap_2d();
479 for (int i = 0; i < ct.x_intervals_size(); ++i) {
480 const int x_i = ct.x_intervals(i);
481 const int y_i = ct.y_intervals(i);
482 if (active_intervals_set.contains(x_i) ||
483 active_intervals_set.contains(y_i)) {
484 active_rectangles[{x_i, y_i}].push_back(ct_index);
485 }
486 }
487 }
488
489 std::vector<ActiveRectangle> results;
490 results.reserve(active_rectangles.size());
491 for (const auto& [rectangle, no_overlap_2d_constraints] : active_rectangles) {
492 ActiveRectangle& result = results.emplace_back();
493 result.x_interval = rectangle.first;
494 result.y_interval = rectangle.second;
495 result.no_overlap_2d_constraints = {no_overlap_2d_constraints.begin(),
496 no_overlap_2d_constraints.end()};
497 }
498 return results;
499}
500
501std::vector<std::vector<int>>
503 std::vector<std::vector<int>> intervals_in_constraints;
504 absl::flat_hash_set<std::vector<int>> added_intervals_sets;
505 const auto add_interval_list_only_once =
506 [&intervals_in_constraints,
507 &added_intervals_sets](const auto& intervals) {
508 std::vector<int> candidate({intervals.begin(), intervals.end()});
510 if (added_intervals_sets.insert(candidate).second) {
511 intervals_in_constraints.push_back(candidate);
512 }
513 };
514 for (const int ct_index : TypeToConstraints(ConstraintProto::kNoOverlap)) {
515 add_interval_list_only_once(
516 model_proto_.constraints(ct_index).no_overlap().intervals());
517 }
518 for (const int ct_index : TypeToConstraints(ConstraintProto::kCumulative)) {
519 add_interval_list_only_once(
520 model_proto_.constraints(ct_index).cumulative().intervals());
521 }
522 for (const int ct_index : TypeToConstraints(ConstraintProto::kNoOverlap2D)) {
523 add_interval_list_only_once(
524 model_proto_.constraints(ct_index).no_overlap_2d().x_intervals());
525 add_interval_list_only_once(
526 model_proto_.constraints(ct_index).no_overlap_2d().y_intervals());
527 }
528 return intervals_in_constraints;
529}
530
531namespace {
532
533int64_t GetLinearExpressionValue(const LinearExpressionProto& expr,
534 const CpSolverResponse& initial_solution) {
535 int64_t result = expr.offset();
536 for (int i = 0; i < expr.vars_size(); ++i) {
537 result += expr.coeffs(i) * initial_solution.solution(expr.vars(i));
538 }
539 return result;
540}
541
542void RestrictAffineExpression(const LinearExpressionProto& expr,
543 const Domain& restriction,
544 CpModelProto* mutable_proto) {
545 CHECK_LE(expr.vars().size(), 1);
546 if (expr.vars().empty()) return;
547 const Domain implied_domain = restriction.AdditionWith(Domain(-expr.offset()))
548 .InverseMultiplicationBy(expr.coeffs(0));
549 const Domain domain =
550 ReadDomainFromProto(mutable_proto->variables(expr.vars(0)))
551 .IntersectionWith(implied_domain);
552 if (!domain.IsEmpty()) {
553 FillDomainInProto(domain, mutable_proto->mutable_variables(expr.vars(0)));
554 }
555}
556
557struct StartEndIndex {
558 int64_t start;
559 int64_t end;
560 int index_in_input_vector;
561 double noise;
562 bool operator<(const StartEndIndex& o) const {
563 return std::tie(start, end, noise, index_in_input_vector) <
564 std::tie(o.start, o.end, o.noise, o.index_in_input_vector);
565 }
566};
567
568struct TimePartition {
569 std::vector<int> indices_before_selected;
570 std::vector<int> selected_indices;
571 std::vector<int> indices_after_selected;
572};
573
574// Selects all intervals in a random time window to meet the difficulty
575// requirement.
576TimePartition PartitionIndicesAroundRandomTimeWindow(
577 absl::Span<const int> intervals, const CpModelProto& model_proto,
578 const CpSolverResponse& initial_solution, double difficulty,
579 absl::BitGenRef random) {
580 std::vector<StartEndIndex> start_end_indices;
581 for (int index = 0; index < intervals.size(); ++index) {
582 const int interval = intervals[index];
583 const ConstraintProto& interval_ct = model_proto.constraints(interval);
584 const int64_t start_value = GetLinearExpressionValue(
585 interval_ct.interval().start(), initial_solution);
586 const int64_t end_value = GetLinearExpressionValue(
587 interval_ct.interval().end(), initial_solution);
588 start_end_indices.push_back(
589 {start_value, end_value, index, absl::Uniform(random, 0., 1.0)});
590 }
591
592 if (start_end_indices.empty()) return {};
593
594 std::sort(start_end_indices.begin(), start_end_indices.end());
595 const int relaxed_size = std::floor(difficulty * start_end_indices.size());
596
597 std::uniform_int_distribution<int> random_var(
598 0, start_end_indices.size() - relaxed_size - 1);
599 // TODO(user): Consider relaxing more than one time window
600 // intervals. This seems to help with Giza models.
601 const int random_start_index = random_var(random);
602
603 // We want to minimize the time window relaxed, so we now sort the interval
604 // after the first selected intervals by end value.
605 // TODO(user): We could do things differently (include all tasks <= some
606 // end). The difficulty is that the number of relaxed tasks will differ from
607 // the target. We could also tie break tasks randomly.
608 std::sort(start_end_indices.begin() + random_start_index,
609 start_end_indices.end(),
610 [](const StartEndIndex& a, const StartEndIndex& b) {
611 return std::tie(a.end, a.noise, a.index_in_input_vector) <
612 std::tie(b.end, b.noise, b.index_in_input_vector);
613 });
614 TimePartition result;
615 int i = 0;
616 for (; i < random_start_index; ++i) {
617 result.indices_before_selected.push_back(
618 start_end_indices[i].index_in_input_vector);
619 }
620 for (; i < random_start_index + relaxed_size; ++i) {
621 result.selected_indices.push_back(
622 start_end_indices[i].index_in_input_vector);
623 }
624 for (; i < start_end_indices.size(); ++i) {
625 result.indices_after_selected.push_back(
626 start_end_indices[i].index_in_input_vector);
627 }
628 return result;
629}
630
631struct Demand {
632 int interval_index;
633 int64_t start;
634 int64_t end;
635 int64_t height;
636
637 // Because of the binary splitting of the capacity in the procedure used to
638 // extract precedences out of a cumulative constraint, processing bigger
639 // heights first will decrease its probability of being split across the 2
640 // halves of the current split.
641 bool operator<(const Demand& other) const {
642 return std::tie(start, height, end) <
643 std::tie(other.start, other.height, other.end);
644 }
645
646 std::string DebugString() const {
647 return absl::StrCat("{i=", interval_index, " span=[", start, ",", end, "]",
648 " d=", height, "}");
649 }
650};
651
652void InsertPrecedencesFromSortedListOfNonOverlapingIntervals(
653 const std::vector<Demand>& demands,
654 absl::flat_hash_set<std::pair<int, int>>* precedences) {
655 for (int i = 0; i + 1 < demands.size(); ++i) {
656 DCHECK_LE(demands[i].end, demands[i + 1].start);
657 precedences->insert(
658 {demands[i].interval_index, demands[i + 1].interval_index});
659 }
660}
661
662bool IsPresent(const ConstraintProto& interval_ct,
663 const CpSolverResponse& initial_solution) {
664 if (interval_ct.enforcement_literal().size() != 1) return true;
665
666 const int enforcement_ref = interval_ct.enforcement_literal(0);
667 const int enforcement_var = PositiveRef(enforcement_ref);
668 const int64_t value = initial_solution.solution(enforcement_var);
669 return RefIsPositive(enforcement_ref) == (value == 1);
670}
671
672void InsertNoOverlapPrecedences(
673 const absl::flat_hash_set<int>& ignored_intervals,
674 const CpSolverResponse& initial_solution, const CpModelProto& model_proto,
675 int no_overlap_index,
676 absl::flat_hash_set<std::pair<int, int>>* precedences) {
677 std::vector<Demand> demands;
678 const NoOverlapConstraintProto& no_overlap =
679 model_proto.constraints(no_overlap_index).no_overlap();
680 for (const int interval_index : no_overlap.intervals()) {
681 if (ignored_intervals.contains(interval_index)) continue;
682 const ConstraintProto& interval_ct =
683 model_proto.constraints(interval_index);
684 if (!IsPresent(interval_ct, initial_solution)) continue;
685
686 const int64_t start_value = GetLinearExpressionValue(
687 interval_ct.interval().start(), initial_solution);
688 const int64_t end_value = GetLinearExpressionValue(
689 interval_ct.interval().end(), initial_solution);
690 DCHECK_LE(start_value, end_value);
691 demands.push_back({interval_index, start_value, end_value, 1});
692 }
693
694 // TODO(user): We actually only need interval_index, start.
695 // No need to fill the other fields here.
696 std::sort(demands.begin(), demands.end());
697 InsertPrecedencesFromSortedListOfNonOverlapingIntervals(demands, precedences);
698}
699
700void ProcessDemandListFromCumulativeConstraint(
701 const std::vector<Demand>& demands, int64_t capacity,
702 std::deque<std::pair<std::vector<Demand>, int64_t>>* to_process,
703 absl::BitGenRef random,
704 absl::flat_hash_set<std::pair<int, int>>* precedences) {
705 if (demands.size() <= 1) return;
706
707 // Checks if any pairs of tasks cannot overlap.
708 int64_t sum_of_min_two_capacities = 2;
709 if (capacity > 1) {
710 int64_t min1 = std::numeric_limits<int64_t>::max();
711 int64_t min2 = std::numeric_limits<int64_t>::max();
712 for (const Demand& demand : demands) {
713 if (demand.height <= min1) {
714 min2 = min1;
715 min1 = demand.height;
716 } else if (demand.height < min2) {
717 min2 = demand.height;
718 }
719 }
720 sum_of_min_two_capacities = min1 + min2;
721 }
722
723 DCHECK_GT(sum_of_min_two_capacities, 1);
724 if (sum_of_min_two_capacities > capacity) {
725 InsertPrecedencesFromSortedListOfNonOverlapingIntervals(demands,
726 precedences);
727 return;
728 }
729
730 std::vector<int64_t> unique_starts;
731 for (const Demand& demand : demands) {
732 DCHECK(unique_starts.empty() || demand.start >= unique_starts.back());
733 if (unique_starts.empty() || unique_starts.back() < demand.start) {
734 unique_starts.push_back(demand.start);
735 }
736 }
737 DCHECK(std::is_sorted(unique_starts.begin(), unique_starts.end()));
738 const int num_points = unique_starts.size();
739
740 // Split the capacity in 2 and dispatch all demands on the 2 parts.
741 const int64_t capacity1 = capacity / 2;
742 std::vector<int64_t> usage1(num_points);
743 std::vector<Demand> demands1;
744
745 const int64_t capacity2 = capacity - capacity1;
746 std::vector<int64_t> usage2(num_points);
747 std::vector<Demand> demands2;
748
749 int usage_index = 0;
750 for (const Demand& d : demands) {
751 // Since we process demand by increasing start, the usage_index only
752 // need to increase.
753 while (usage_index < num_points && unique_starts[usage_index] < d.start) {
754 usage_index++;
755 }
756 DCHECK_LT(usage_index, num_points);
757 DCHECK_EQ(unique_starts[usage_index], d.start);
758 const int64_t slack1 = capacity1 - usage1[usage_index];
759 const int64_t slack2 = capacity2 - usage2[usage_index];
760
761 // We differ from the ICAPS article. If it fits in both sub-cumulatives, We
762 // choose the smallest slack. If it fits into at most one, we choose the
763 // biggest slack. If both slacks are equal, we choose randomly.
764 const bool prefer2 =
765 slack1 == slack2
766 ? absl::Bernoulli(random, 0.5)
767 : (d.height <= std::min(slack1, slack2) ? slack2 < slack1
768 : slack2 > slack1);
769
770 auto& selected_usage = prefer2 ? usage2 : usage1;
771 auto& residual_usage = prefer2 ? usage1 : usage2;
772 std::vector<Demand>& selected_demands = prefer2 ? demands2 : demands1;
773 std::vector<Demand>& residual_demands = prefer2 ? demands1 : demands2;
774 const int64_t selected_slack = prefer2 ? slack2 : slack1;
775
776 const int64_t assigned_to_selected = std::min(selected_slack, d.height);
777 DCHECK_GT(assigned_to_selected, 0);
778 for (int i = usage_index; i < num_points; ++i) {
779 if (d.end <= unique_starts[i]) break;
780 selected_usage[i] += assigned_to_selected;
781 }
782 selected_demands.push_back(
783 {d.interval_index, d.start, d.end, assigned_to_selected});
784
785 if (d.height > selected_slack) {
786 const int64_t residual = d.height - selected_slack;
787 DCHECK_GT(residual, 0);
788 DCHECK_LE(residual, prefer2 ? slack1 : slack2);
789 for (int i = usage_index; i < num_points; ++i) {
790 if (d.end <= unique_starts[i]) break;
791 residual_usage[i] += residual;
792 }
793 residual_demands.push_back({d.interval_index, d.start, d.end, residual});
794 }
795 }
796
797 if (demands1.size() > 1) {
798 to_process->emplace_back(std::move(demands1), capacity1);
799 }
800 if (demands2.size() > 1) {
801 to_process->emplace_back(std::move(demands2), capacity2);
802 }
803}
804
805void InsertCumulativePrecedences(
806 const absl::flat_hash_set<int>& ignored_intervals,
807 const CpSolverResponse& initial_solution, const CpModelProto& model_proto,
808 int cumulative_index, absl::BitGenRef random,
809 absl::flat_hash_set<std::pair<int, int>>* precedences) {
810 const CumulativeConstraintProto& cumulative =
811 model_proto.constraints(cumulative_index).cumulative();
812
813 std::vector<Demand> demands;
814 for (int i = 0; i < cumulative.intervals().size(); ++i) {
815 const int interval_index = cumulative.intervals(i);
816 if (ignored_intervals.contains(interval_index)) continue;
817 const ConstraintProto& interval_ct =
818 model_proto.constraints(interval_index);
819 if (!IsPresent(interval_ct, initial_solution)) continue;
820
821 const int64_t start_value = GetLinearExpressionValue(
822 interval_ct.interval().start(), initial_solution);
823 const int64_t end_value = GetLinearExpressionValue(
824 interval_ct.interval().end(), initial_solution);
825 const int64_t demand_value =
826 GetLinearExpressionValue(cumulative.demands(i), initial_solution);
827 if (start_value == end_value || demand_value == 0) continue;
828
829 demands.push_back({interval_index, start_value, end_value, demand_value});
830 }
831 std::sort(demands.begin(), demands.end());
832
833 const int64_t capacity_value =
834 GetLinearExpressionValue(cumulative.capacity(), initial_solution);
835 DCHECK_GT(capacity_value, 0);
836
837 // Copying all these demands is memory intensive. Let's be careful here.
838 std::deque<std::pair<std::vector<Demand>, int64_t>> to_process;
839 to_process.emplace_back(std::move(demands), capacity_value);
840
841 while (!to_process.empty()) {
842 auto& next_task = to_process.front();
843 ProcessDemandListFromCumulativeConstraint(next_task.first,
844 /*capacity=*/next_task.second,
845 &to_process, random, precedences);
846 to_process.pop_front();
847 }
848}
849
850struct IndexedRectangle {
851 int interval_index;
852 Rectangle r;
853
854 bool operator<(const IndexedRectangle& other) const {
855 return std::tie(r.x_min, r.x_max) < std::tie(other.r.x_min, other.r.x_max);
856 }
857};
858
859void InsertRectanglePredecences(
860 absl::Span<const IndexedRectangle> rectangles,
861 absl::flat_hash_set<std::pair<int, int>>* precedences) {
862 // TODO(user): Refine set of interesting points.
863 std::vector<IntegerValue> interesting_points;
864 for (const IndexedRectangle& idx_r : rectangles) {
865 interesting_points.push_back(idx_r.r.y_max - 1);
866 }
867 gtl::STLSortAndRemoveDuplicates(&interesting_points);
868 std::vector<Demand> demands;
869 for (const IntegerValue t : interesting_points) {
870 demands.clear();
871 for (const IndexedRectangle& idx_r : rectangles) {
872 if (idx_r.r.y_min > t || idx_r.r.y_max <= t) continue;
873 demands.push_back({idx_r.interval_index, idx_r.r.x_min.value(),
874 idx_r.r.x_max.value(), 1});
875 }
876 std::sort(demands.begin(), demands.end());
877 InsertPrecedencesFromSortedListOfNonOverlapingIntervals(demands,
878 precedences);
879 }
880}
881
882void InsertNoOverlap2dPrecedences(
883 const absl::flat_hash_set<int>& ignored_intervals,
884 const CpSolverResponse& initial_solution, const CpModelProto& model_proto,
885 int no_overlap_2d_index,
886 absl::flat_hash_set<std::pair<int, int>>* precedences) {
887 std::vector<Demand> demands;
888 const NoOverlap2DConstraintProto& no_overlap_2d =
889 model_proto.constraints(no_overlap_2d_index).no_overlap_2d();
890 std::vector<IndexedRectangle> x_main;
891 std::vector<IndexedRectangle> y_main;
892 for (int i = 0; i < no_overlap_2d.x_intervals_size(); ++i) {
893 // Ignore unperformed rectangles.
894 const int x_interval_index = no_overlap_2d.x_intervals(i);
895 if (ignored_intervals.contains(x_interval_index)) continue;
896 const ConstraintProto& x_interval_ct =
897 model_proto.constraints(x_interval_index);
898 if (!IsPresent(x_interval_ct, initial_solution)) continue;
899
900 const int y_interval_index = no_overlap_2d.y_intervals(i);
901 if (ignored_intervals.contains(y_interval_index)) continue;
902 const ConstraintProto& y_interval_ct =
903 model_proto.constraints(y_interval_index);
904 if (!IsPresent(y_interval_ct, initial_solution)) continue;
905
906 const int64_t x_start_value = GetLinearExpressionValue(
907 x_interval_ct.interval().start(), initial_solution);
908 const int64_t x_end_value = GetLinearExpressionValue(
909 x_interval_ct.interval().end(), initial_solution);
910 const int64_t y_start_value = GetLinearExpressionValue(
911 y_interval_ct.interval().start(), initial_solution);
912 const int64_t y_end_value = GetLinearExpressionValue(
913 y_interval_ct.interval().end(), initial_solution);
914
915 // Ignore rectangles with zero area.
916 if (x_start_value == x_end_value || y_start_value == y_end_value) continue;
917
918 x_main.push_back({.interval_index = x_interval_index,
919 .r = {.x_min = x_start_value,
920 .x_max = x_end_value,
921 .y_min = y_start_value,
922 .y_max = y_end_value}});
923 y_main.push_back({.interval_index = y_interval_index,
924 .r = {.x_min = y_start_value,
925 .x_max = y_end_value,
926 .y_min = x_start_value,
927 .y_max = x_end_value}});
928 }
929
930 if (x_main.empty() || y_main.empty()) return;
931
932 std::sort(x_main.begin(), x_main.end());
933 InsertRectanglePredecences(x_main, precedences);
934 std::sort(y_main.begin(), y_main.end());
935 InsertRectanglePredecences(y_main, precedences);
936}
937
938} // namespace
939
940// TODO(user): We could scan for model precedences and add them to the list
941// of precedences. This could enable more simplifications in the transitive
942// reduction phase.
943std::vector<std::pair<int, int>>
945 const absl::flat_hash_set<int>& ignored_intervals,
946 const CpSolverResponse& initial_solution, absl::BitGenRef random) const {
947 absl::flat_hash_set<std::pair<int, int>> precedences;
948 for (const int c : TypeToConstraints(ConstraintProto::kNoOverlap)) {
949 InsertNoOverlapPrecedences(ignored_intervals, initial_solution,
950 ModelProto(), c, &precedences);
951 }
952 for (const int c : TypeToConstraints(ConstraintProto::kCumulative)) {
953 InsertCumulativePrecedences(ignored_intervals, initial_solution,
954 ModelProto(), c, random, &precedences);
955 }
956 for (const int c : TypeToConstraints(ConstraintProto::kNoOverlap2D)) {
957 InsertNoOverlap2dPrecedences(ignored_intervals, initial_solution,
958 ModelProto(), c, &precedences);
959 }
960
961 // TODO(user): Reduce precedence graph
962 std::vector<std::pair<int, int>> result(precedences.begin(),
963 precedences.end());
964 std::sort(result.begin(), result.end());
965 return result;
966}
967
968std::vector<std::vector<int>>
970 const CpSolverResponse& initial_solution) const {
971 struct HeadAndArcBooleanVariable {
972 int head;
973 int bool_var;
974 };
975
976 std::vector<std::vector<int>> result;
977 absl::flat_hash_map<int, HeadAndArcBooleanVariable>
978 tail_to_head_and_arc_bool_var;
979
980 for (const int i : TypeToConstraints(ConstraintProto::kCircuit)) {
981 const CircuitConstraintProto& ct = ModelProto().constraints(i).circuit();
982
983 // Collect arcs.
984 int min_node = std::numeric_limits<int>::max();
985 tail_to_head_and_arc_bool_var.clear();
986 for (int i = 0; i < ct.literals_size(); ++i) {
987 const int literal = ct.literals(i);
988 const int head = ct.heads(i);
989 const int tail = ct.tails(i);
990 const int bool_var = PositiveRef(literal);
991 const int64_t value = initial_solution.solution(bool_var);
992 // Skip unselected arcs.
993 if (RefIsPositive(literal) == (value == 0)) continue;
994 // Ignore self loops.
995 if (head == tail) continue;
996 tail_to_head_and_arc_bool_var[tail] = {head, bool_var};
997 min_node = std::min(tail, min_node);
998 }
999 if (tail_to_head_and_arc_bool_var.empty()) continue;
1000
1001 // Unroll the path.
1002 int current_node = min_node;
1003 std::vector<int> path;
1004 do {
1005 auto it = tail_to_head_and_arc_bool_var.find(current_node);
1006 CHECK(it != tail_to_head_and_arc_bool_var.end());
1007 current_node = it->second.head;
1008 path.push_back(it->second.bool_var);
1009 } while (current_node != min_node);
1010 result.push_back(std::move(path));
1011 }
1012
1013 std::vector<HeadAndArcBooleanVariable> route_starts;
1014 for (const int i : TypeToConstraints(ConstraintProto::kRoutes)) {
1015 const RoutesConstraintProto& ct = ModelProto().constraints(i).routes();
1016 tail_to_head_and_arc_bool_var.clear();
1017 route_starts.clear();
1018
1019 // Collect route starts and arcs.
1020 for (int i = 0; i < ct.literals_size(); ++i) {
1021 const int literal = ct.literals(i);
1022 const int head = ct.heads(i);
1023 const int tail = ct.tails(i);
1024 const int bool_var = PositiveRef(literal);
1025 const int64_t value = initial_solution.solution(bool_var);
1026 // Skip unselected arcs.
1027 if (RefIsPositive(literal) == (value == 0)) continue;
1028 // Ignore self loops.
1029 if (head == tail) continue;
1030 if (tail == 0) {
1031 route_starts.push_back({head, bool_var});
1032 } else {
1033 tail_to_head_and_arc_bool_var[tail] = {head, bool_var};
1034 }
1035 }
1036
1037 // Unroll all routes.
1038 for (const HeadAndArcBooleanVariable& head_var : route_starts) {
1039 std::vector<int> path;
1040 int current_node = head_var.head;
1041 path.push_back(head_var.bool_var);
1042 do {
1043 auto it = tail_to_head_and_arc_bool_var.find(current_node);
1044 CHECK(it != tail_to_head_and_arc_bool_var.end());
1045 current_node = it->second.head;
1046 path.push_back(it->second.bool_var);
1047 } while (current_node != 0);
1048 result.push_back(std::move(path));
1049 }
1050 }
1051
1052 return result;
1053}
1054
1056 const CpSolverResponse& base_solution,
1057 const Bitset64<int>& variables_to_fix) const {
1058 const int num_variables = variables_to_fix.size();
1059 Neighborhood neighborhood(num_variables);
1060 neighborhood.delta.mutable_variables()->Reserve(num_variables);
1061
1062 // TODO(user): Maybe relax all variables in the objective when the number
1063 // is small or negligible compared to the number of variables.
1064 const int unique_objective_variable =
1065 model_proto_.has_objective() && model_proto_.objective().vars_size() == 1
1066 ? model_proto_.objective().vars(0)
1067 : -1;
1068
1069 // Fill in neighborhood.delta all variable domains.
1070 int num_fixed = 0;
1071 {
1072 absl::ReaderMutexLock domain_lock(&domain_mutex_);
1073 for (int i = 0; i < num_variables; ++i) {
1074 const IntegerVariableProto& current_var =
1075 model_proto_with_only_variables_.variables(i);
1076 IntegerVariableProto* new_var = neighborhood.delta.add_variables();
1077
1078 // We only copy the name in debug mode.
1079 if (DEBUG_MODE) new_var->set_name(current_var.name());
1080
1081 if (variables_to_fix[i] && i != unique_objective_variable) {
1082 ++num_fixed;
1083
1084 // Note the use of DomainInProtoContains() instead of
1085 // ReadDomainFromProto() as the later is slower and allocate memory.
1086 const int64_t base_value = base_solution.solution(i);
1087 if (DomainInProtoContains(current_var, base_value)) {
1088 new_var->add_domain(base_value);
1089 new_var->add_domain(base_value);
1090 } else {
1091 // If under the updated domain, the base solution is no longer valid,
1092 // We should probably regenerate this neighborhood. But for now we
1093 // just do a best effort and take the closest value.
1094 const Domain domain = ReadDomainFromProto(current_var);
1095 int64_t closest_value = domain.Min();
1096 int64_t closest_dist = std::abs(closest_value - base_value);
1097 for (const ClosedInterval interval : domain) {
1098 for (const int64_t value : {interval.start, interval.end}) {
1099 const int64_t dist = std::abs(value - base_value);
1100 if (dist < closest_dist) {
1101 closest_value = value;
1102 closest_dist = dist;
1103 }
1104 }
1105 }
1106 FillDomainInProto(Domain(closest_value, closest_value), new_var);
1107 }
1108 } else {
1109 *new_var->mutable_domain() = current_var.domain();
1110 }
1111 }
1112 }
1113
1114 // Fill some statistic fields and detect if we cover a full component.
1115 //
1116 // TODO(user): If there is just one component, we can skip some computation.
1117 {
1118 absl::ReaderMutexLock graph_lock(&graph_mutex_);
1119 std::vector<int> count(components_.size(), 0);
1120 const int num_variables = neighborhood.delta.variables().size();
1121 for (int var = 0; var < num_variables; ++var) {
1122 const auto& domain = neighborhood.delta.variables(var).domain();
1123 if (domain.size() != 2 || domain[0] != domain[1]) {
1124 ++neighborhood.num_relaxed_variables;
1125 if (is_in_objective_[var]) {
1127 }
1128 const int c = var_to_component_index_[var];
1129 if (c != -1) count[c]++;
1130 }
1131 }
1132
1133 for (int i = 0; i < components_.size(); ++i) {
1134 if (count[i] == components_[i].size()) {
1137 components_[i].begin(), components_[i].end());
1138 }
1139 }
1140 }
1141
1142 // If the objective domain might cut the optimal solution, we cannot exploit
1143 // the connected components. We compute this outside the mutex to avoid
1144 // any deadlock risk.
1145 //
1146 // TODO(user): We could handle some complex domain (size > 2).
1147 if (model_proto_.has_objective() &&
1148 (model_proto_.objective().domain().size() != 2 ||
1149 shared_response_->GetInnerObjectiveLowerBound() <
1150 model_proto_.objective().domain(0))) {
1152 }
1153
1154 const int num_relaxed = num_variables - num_fixed;
1155 neighborhood.delta.mutable_solution_hint()->mutable_vars()->Reserve(
1156 num_relaxed);
1157 neighborhood.delta.mutable_solution_hint()->mutable_values()->Reserve(
1158 num_relaxed);
1159 AddSolutionHinting(base_solution, &neighborhood.delta);
1160
1161 neighborhood.is_generated = true;
1162 neighborhood.is_reduced = num_fixed > 0;
1163 neighborhood.is_simple = true;
1164
1165 // TODO(user): force better objective? Note that this is already done when the
1166 // hint above is successfully loaded (i.e. if it passes the presolve
1167 // correctly) since the solver will try to find better solution than the
1168 // current one.
1169 return neighborhood;
1170}
1171
1173 const CpSolverResponse& initial_solution, CpModelProto* model_proto) const {
1174 // Set the current solution as a hint.
1175 model_proto->clear_solution_hint();
1176 const auto is_fixed = [model_proto](int var) {
1177 const IntegerVariableProto& var_proto = model_proto->variables(var);
1178 return var_proto.domain_size() == 2 &&
1179 var_proto.domain(0) == var_proto.domain(1);
1180 };
1181 for (int var = 0; var < model_proto->variables_size(); ++var) {
1182 if (is_fixed(var)) continue;
1183
1184 model_proto->mutable_solution_hint()->add_vars(var);
1185 model_proto->mutable_solution_hint()->add_values(
1186 initial_solution.solution(var));
1187 }
1188}
1189
1191 const CpSolverResponse& initial_solution,
1192 absl::Span<const int> relaxed_variables) const {
1193 Bitset64<int> fixed_variables(NumVariables());
1194 {
1195 absl::ReaderMutexLock graph_lock(&graph_mutex_);
1196 for (const int i : active_variables_) {
1197 fixed_variables.Set(i);
1198 }
1199 }
1200 for (const int var : relaxed_variables) fixed_variables.Clear(var);
1201 return FixGivenVariables(initial_solution, fixed_variables);
1202}
1203
1205 const CpSolverResponse& initial_solution) const {
1206 Bitset64<int> fixed_variables(NumVariables());
1207 {
1208 absl::ReaderMutexLock graph_lock(&graph_mutex_);
1209 for (const int i : active_variables_) {
1210 fixed_variables.Set(i);
1211 }
1212 }
1213 return FixGivenVariables(initial_solution, fixed_variables);
1214}
1215
1217 CpModelProto updated_model = model_proto_;
1218 {
1219 absl::MutexLock domain_lock(&domain_mutex_);
1220 *updated_model.mutable_variables() =
1221 model_proto_with_only_variables_.variables();
1222 }
1223 return updated_model;
1224}
1225
1227 return (helper_.shared_response().SolutionsRepository().NumSolutions() > 0);
1228}
1229
1230double NeighborhoodGenerator::GetUCBScore(int64_t total_num_calls) const {
1231 absl::ReaderMutexLock mutex_lock(&generator_mutex_);
1232 DCHECK_GE(total_num_calls, num_calls_);
1233 if (num_calls_ <= 10) return std::numeric_limits<double>::infinity();
1234 return current_average_ + sqrt((2 * log(total_num_calls)) / num_calls_);
1235}
1236
1238 absl::MutexLock mutex_lock(&generator_mutex_);
1239
1240 // To make the whole update process deterministic, we currently sort the
1241 // SolveData.
1242 std::sort(solve_data_.begin(), solve_data_.end());
1243
1244 // This will be used to update the difficulty of this neighborhood.
1245 int num_fully_solved_in_batch = 0;
1246 int num_not_fully_solved_in_batch = 0;
1247
1248 double total_dtime = 0.0;
1249 for (const SolveData& data : solve_data_) {
1250 ++num_calls_;
1251
1252 // INFEASIBLE or OPTIMAL means that we "fully solved" the local problem.
1253 // If we didn't, then we cannot be sure that there is no improving solution
1254 // in that neighborhood.
1255 if (data.status == CpSolverStatus::INFEASIBLE ||
1256 data.status == CpSolverStatus::OPTIMAL) {
1257 ++num_fully_solved_calls_;
1258 ++num_fully_solved_in_batch;
1259 } else {
1260 ++num_not_fully_solved_in_batch;
1261 }
1262
1263 // It seems to make more sense to compare the new objective to the base
1264 // solution objective, not the best one. However this causes issue in the
1265 // logic below because on some problems the neighborhood can always lead
1266 // to a better "new objective" if the base solution wasn't the best one.
1267 //
1268 // This might not be a final solution, but it does work ok for now.
1269 const IntegerValue best_objective_improvement = IntegerValue(CapSub(
1270 data.initial_best_objective.value(), data.new_objective.value()));
1271 if (best_objective_improvement > 0) {
1272 num_consecutive_non_improving_calls_ = 0;
1273 next_time_limit_bump_ = 50;
1274 } else {
1275 ++num_consecutive_non_improving_calls_;
1276 }
1277
1278 // Confusing: this one is however comparing to the base solution objective.
1279 if (data.base_objective > data.new_objective) {
1280 ++num_improving_calls_;
1281 }
1282
1283 // TODO(user): Weight more recent data.
1284 // degrade the current average to forget old learnings.
1285 const double gain_per_time_unit =
1286 std::max(0.0, static_cast<double>(best_objective_improvement.value())) /
1287 (1.0 + data.deterministic_time);
1288 if (num_calls_ <= 100) {
1289 current_average_ += (gain_per_time_unit - current_average_) / num_calls_;
1290 } else {
1291 current_average_ = 0.9 * current_average_ + 0.1 * gain_per_time_unit;
1292 }
1293
1294 total_dtime += data.deterministic_time;
1295 }
1296
1297 // Update the difficulty.
1298 difficulty_.Update(/*num_decreases=*/num_not_fully_solved_in_batch,
1299 /*num_increases=*/num_fully_solved_in_batch);
1300
1301 // Bump the time limit if we saw no better solution in the last few calls.
1302 // This means that as the search progress, we likely spend more and more time
1303 // trying to solve individual neighborhood.
1304 //
1305 // TODO(user): experiment with resetting the time limit if a solution is
1306 // found.
1307 if (num_consecutive_non_improving_calls_ > next_time_limit_bump_) {
1308 next_time_limit_bump_ = num_consecutive_non_improving_calls_ + 50;
1309 deterministic_limit_ *= 1.02;
1310
1311 // We do not want the limit to go to high. Intuitively, the goal is to try
1312 // out a lot of neighborhoods, not just spend a lot of time on a few.
1314 }
1315
1316 solve_data_.clear();
1317 return total_dtime;
1318}
1319
1320namespace {
1321
1322template <class T>
1323void GetRandomSubset(double relative_size, std::vector<T>* base,
1324 absl::BitGenRef random) {
1325 if (base->empty()) return;
1326
1327 // TODO(user): we could generate this more efficiently than using random
1328 // shuffle.
1329 std::shuffle(base->begin(), base->end(), random);
1330 const int target_size = std::round(relative_size * base->size());
1331 base->resize(target_size);
1332}
1333
1334} // namespace
1335
1337 const CpSolverResponse& initial_solution, SolveData& data,
1338 absl::BitGenRef random) {
1339 std::vector<int> fixed_variables = helper_.ActiveVariables();
1340 GetRandomSubset(1.0 - data.difficulty, &fixed_variables, random);
1341
1342 Bitset64<int> to_fix(helper_.NumVariables());
1343 for (const int var : fixed_variables) to_fix.Set(var);
1344 return helper_.FixGivenVariables(initial_solution, to_fix);
1345}
1346
1348 const CpSolverResponse& initial_solution, SolveData& data,
1349 absl::BitGenRef random) {
1350 if (helper_.DifficultyMeansFullNeighborhood(data.difficulty)) {
1351 return helper_.FullNeighborhood();
1352 }
1353
1354 std::vector<int> relaxed_variables;
1355 {
1356 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1357 const int num_active_constraints = helper_.ConstraintToVar().size();
1358 std::vector<int> active_constraints(num_active_constraints);
1359 for (int c = 0; c < num_active_constraints; ++c) {
1360 active_constraints[c] = c;
1361 }
1362 std::shuffle(active_constraints.begin(), active_constraints.end(), random);
1363
1364 const int num_model_vars = helper_.ModelProto().variables_size();
1365 std::vector<bool> visited_variables_set(num_model_vars, false);
1366
1367 const int num_active_vars =
1368 helper_.ActiveVariablesWhileHoldingLock().size();
1369 const int target_size = std::ceil(data.difficulty * num_active_vars);
1370 if (target_size == num_active_vars) return helper_.FullNeighborhood();
1371 // TODO(user): Clean-up when target_size == 0.
1372
1373 for (const int constraint_index : active_constraints) {
1374 // TODO(user): randomize order of variable addition when close to the
1375 // limit.
1376 for (const int var : helper_.ConstraintToVar()[constraint_index]) {
1377 if (visited_variables_set[var]) continue;
1378 visited_variables_set[var] = true;
1379 if (helper_.IsActive(var)) {
1380 relaxed_variables.push_back(var);
1381 if (relaxed_variables.size() >= target_size) break;
1382 }
1383 }
1384 if (relaxed_variables.size() >= target_size) break;
1385 }
1386 }
1387
1388 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1389}
1390
1391// Note that even if difficulty means full neighborhood, we go through the
1392// generation process to never get out of a connected components.
1394 const CpSolverResponse& initial_solution, SolveData& data,
1395 absl::BitGenRef random) {
1396 const int num_model_vars = helper_.ModelProto().variables_size();
1397 std::vector<bool> visited_variables_set(num_model_vars, false);
1398 std::vector<int> relaxed_variables;
1399 std::vector<int> visited_variables;
1400
1401 // It is important complexity wise to never scan a constraint twice!
1402 const int num_model_constraints = helper_.ModelProto().constraints_size();
1403 std::vector<bool> scanned_constraints(num_model_constraints, false);
1404
1405 std::vector<int> random_variables;
1406 {
1407 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1408
1409 // The number of active variables can decrease asynchronously.
1410 // We read the exact number while locked.
1411 const int num_active_vars =
1412 helper_.ActiveVariablesWhileHoldingLock().size();
1413 const int num_objective_variables =
1414 helper_.ActiveObjectiveVariablesWhileHoldingLock().size();
1415 const int target_size = std::ceil(data.difficulty * num_active_vars);
1416 if (target_size == num_active_vars) return helper_.FullNeighborhood();
1417
1418 const int first_var =
1419 num_objective_variables > 0 // Prefer objective variables.
1420 ? helper_.ActiveObjectiveVariablesWhileHoldingLock()
1421 [absl::Uniform<int>(random, 0, num_objective_variables)]
1422 : helper_.ActiveVariablesWhileHoldingLock()[absl::Uniform<int>(
1423 random, 0, num_active_vars)];
1424 visited_variables_set[first_var] = true;
1425 visited_variables.push_back(first_var);
1426 relaxed_variables.push_back(first_var);
1427
1428 for (int i = 0; i < visited_variables.size(); ++i) {
1429 random_variables.clear();
1430 // Collect all the variables that appears in the same constraints as
1431 // visited_variables[i].
1432 for (const int ct : helper_.VarToConstraint()[visited_variables[i]]) {
1433 if (scanned_constraints[ct]) continue;
1434 scanned_constraints[ct] = true;
1435 for (const int var : helper_.ConstraintToVar()[ct]) {
1436 if (visited_variables_set[var]) continue;
1437 visited_variables_set[var] = true;
1438 random_variables.push_back(var);
1439 }
1440 }
1441 // We always randomize to change the partial subgraph explored
1442 // afterwards.
1443 std::shuffle(random_variables.begin(), random_variables.end(), random);
1444 for (const int var : random_variables) {
1445 if (relaxed_variables.size() < target_size) {
1446 visited_variables.push_back(var);
1447 if (helper_.IsActive(var)) {
1448 relaxed_variables.push_back(var);
1449 }
1450 } else {
1451 break;
1452 }
1453 }
1454 if (relaxed_variables.size() >= target_size) break;
1455 }
1456 }
1457
1458 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1459}
1460
1461// Note that even if difficulty means full neighborhood, we go through the
1462// generation process to never get out of a connected components.
1464 const CpSolverResponse& initial_solution, SolveData& data,
1465 absl::BitGenRef random) {
1466 const int num_model_vars = helper_.ModelProto().variables_size();
1467 if (num_model_vars == 0) return helper_.NoNeighborhood();
1468
1469 // We copy the full graph var <-> constraints so that we can:
1470 // - reduce it in place
1471 // - not hold the mutex too long.
1472 // TODO(user): should we compress it or use a different representation ?
1473 CompactVectorVector<int, int> vars_to_constraints;
1474 CompactVectorVector<int, int> constraints_to_vars;
1475 int num_active_vars = 0;
1476 std::vector<int> active_objective_vars;
1477 {
1478 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1479 num_active_vars = helper_.ActiveVariablesWhileHoldingLock().size();
1480 active_objective_vars = helper_.ActiveObjectiveVariablesWhileHoldingLock();
1481 constraints_to_vars = helper_.ConstraintToVar();
1482 vars_to_constraints = helper_.VarToConstraint();
1483 }
1484
1485 const int target_size = std::ceil(data.difficulty * num_active_vars);
1486 if (target_size == 0) return helper_.NoNeighborhood();
1487
1488 // We pick a variable from the objective.
1489 const int num_objective_variables = active_objective_vars.size();
1490 if (num_objective_variables == 0) return helper_.NoNeighborhood();
1491 const int first_var = active_objective_vars[absl::Uniform<int>(
1492 random, 0, num_objective_variables)];
1493
1494 std::vector<bool> relaxed_variables_set(num_model_vars, false);
1495 std::vector<int> relaxed_variables;
1496 // Active vars are relaxed variables with some unexplored neighbors.
1497 std::vector<int> active_vars;
1498
1499 relaxed_variables_set[first_var] = true;
1500 relaxed_variables.push_back(first_var);
1501 active_vars.push_back(first_var);
1502
1503 while (relaxed_variables.size() < target_size) {
1504 if (active_vars.empty()) break; // We have exhausted our component.
1505
1506 const int tail_index = absl::Uniform<int>(random, 0, active_vars.size());
1507 const int tail_var = active_vars[tail_index];
1508 int head_var = tail_var;
1509 while (!vars_to_constraints[tail_var].empty() && head_var == tail_var) {
1510 const auto cts = vars_to_constraints[tail_var];
1511 const int pos_ct = absl::Uniform<int>(random, 0, cts.size());
1512 const int ct = cts[pos_ct];
1513 while (!constraints_to_vars[ct].empty() && head_var == tail_var) {
1514 const auto vars = constraints_to_vars[ct];
1515 const int pos_var = absl::Uniform<int>(random, 0, vars.size());
1516 const int candidate = vars[pos_var];
1517
1518 // We remove the variable as it is either already relaxed, or will be
1519 // relaxed.
1520 constraints_to_vars.RemoveBySwap(ct, pos_var);
1521 if (!relaxed_variables_set[candidate]) {
1522 head_var = candidate;
1523 }
1524 }
1525 if (constraints_to_vars[ct].empty()) {
1526 // This constraint has no more un-relaxed variables.
1527 vars_to_constraints.RemoveBySwap(tail_var, pos_ct);
1528 }
1529 }
1530
1531 // Variable is no longer active ?
1532 if (vars_to_constraints[tail_var].empty()) {
1533 std::swap(active_vars[tail_index], active_vars.back());
1534 active_vars.pop_back();
1535 }
1536
1537 if (head_var != tail_var) {
1538 relaxed_variables_set[head_var] = true;
1539 relaxed_variables.push_back(head_var);
1540 active_vars.push_back(head_var);
1541 }
1542 }
1543 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1544}
1545
1546// Note that even if difficulty means full neighborhood, we go through the
1547// generation process to never get out of a connected components.
1549 const CpSolverResponse& initial_solution, SolveData& data,
1550 absl::BitGenRef random) {
1551 const int num_model_constraints = helper_.ModelProto().constraints_size();
1552 if (num_model_constraints == 0) {
1553 return helper_.FullNeighborhood();
1554 }
1555
1556 const int num_model_vars = helper_.ModelProto().variables_size();
1557 std::vector<bool> visited_variables_set(num_model_vars, false);
1558 std::vector<int> relaxed_variables;
1559
1560 std::vector<bool> added_constraints(num_model_constraints, false);
1561 std::vector<int> next_constraints;
1562
1563 std::vector<int> random_variables;
1564 {
1565 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1566 const int num_active_vars =
1567 helper_.ActiveVariablesWhileHoldingLock().size();
1568 const int target_size = std::ceil(data.difficulty * num_active_vars);
1569 if (target_size == num_active_vars) return helper_.FullNeighborhood();
1570
1571 // Start by a random constraint.
1572 const int num_active_constraints = helper_.ConstraintToVar().size();
1573 if (num_active_constraints != 0) {
1574 next_constraints.push_back(
1575 absl::Uniform<int>(random, 0, num_active_constraints));
1576 added_constraints[next_constraints.back()] = true;
1577 }
1578
1579 while (relaxed_variables.size() < target_size) {
1580 // Stop if we have a full connected component.
1581 if (next_constraints.empty()) break;
1582
1583 // Pick a random unprocessed constraint.
1584 const int i = absl::Uniform<int>(random, 0, next_constraints.size());
1585 const int constraint_index = next_constraints[i];
1586 std::swap(next_constraints[i], next_constraints.back());
1587 next_constraints.pop_back();
1588
1589 // Add all the variable of this constraint and increase the set of next
1590 // possible constraints.
1591 DCHECK_LT(constraint_index, num_active_constraints);
1592 random_variables.assign(
1593 helper_.ConstraintToVar()[constraint_index].begin(),
1594 helper_.ConstraintToVar()[constraint_index].end());
1595 std::shuffle(random_variables.begin(), random_variables.end(), random);
1596 for (const int var : random_variables) {
1597 if (visited_variables_set[var]) continue;
1598 visited_variables_set[var] = true;
1599 if (helper_.IsActive(var)) {
1600 relaxed_variables.push_back(var);
1601 }
1602 if (relaxed_variables.size() >= target_size) break;
1603
1604 for (const int ct : helper_.VarToConstraint()[var]) {
1605 if (added_constraints[ct]) continue;
1606 added_constraints[ct] = true;
1607 next_constraints.push_back(ct);
1608 }
1609 }
1610 }
1611 }
1612
1613 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1614}
1615
1617 const CpSolverResponse& initial_solution, SolveData& data,
1618 absl::BitGenRef random) {
1619 int max_width = 0;
1620 int size_at_min_width_after_100;
1621 int min_width_after_100 = std::numeric_limits<int>::max();
1622 int num_zero_score = 0;
1623 std::vector<int> relaxed_variables;
1624
1625 // Note(user): The algo is slower than the other graph generator, so we
1626 // might not want to lock the graph for so long? it is just a reader lock
1627 // though.
1628 {
1629 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1630
1631 const int num_active_vars =
1632 helper_.ActiveVariablesWhileHoldingLock().size();
1633 const int target_size = std::ceil(data.difficulty * num_active_vars);
1634 if (target_size == num_active_vars) return helper_.FullNeighborhood();
1635
1636 const int num_vars = helper_.VarToConstraint().size();
1637 const int num_constraints = helper_.ConstraintToVar().size();
1638 if (num_constraints == 0 || num_vars == 0) {
1639 return helper_.FullNeighborhood();
1640 }
1641
1642 // We will grow this incrementally.
1643 // Index in the graph are first variables then constraints.
1644 const int num_nodes = num_vars + num_constraints;
1645 std::vector<bool> added(num_nodes, false);
1646 std::vector<bool> added_or_connected(num_nodes, false);
1647
1648 // We will process var/constraint node by minimum "score".
1649 struct QueueElement {
1650 int Index() const { return index; }
1651 bool operator<(const QueueElement& o) const {
1652 if (score == o.score) return tie_break < o.tie_break;
1653 return score < o.score;
1654 }
1655
1656 int index;
1657 int score = 0;
1658 double tie_break = 0.0;
1659 };
1660 std::vector<QueueElement> elements(num_nodes);
1662
1663 // Initialize elements.
1664 for (int i = 0; i < num_nodes; ++i) {
1665 elements[i].index = i;
1666 elements[i].tie_break = absl::Uniform<double>(random, 0.0, 1.0);
1667 }
1668
1669 // We start by a random active variable.
1670 //
1671 // Note that while num_vars contains all variables, all the fixed variable
1672 // will have no associated constraint, so we don't want to start from a
1673 // random variable.
1674 //
1675 // TODO(user): Does starting by a constraint make sense too?
1676 const int first_index =
1677 helper_.ActiveVariablesWhileHoldingLock()[absl::Uniform<int>(
1678 random, 0, num_active_vars)];
1679 elements[first_index].score = helper_.VarToConstraint()[first_index].size();
1680 pq.Add(elements[first_index]);
1681 added_or_connected[first_index] = true;
1682
1683 // Pop max-degree from queue and update.
1684 std::vector<int> to_update;
1685 while (!pq.IsEmpty() && relaxed_variables.size() < target_size) {
1686 // Just for logging.
1687 if (relaxed_variables.size() > 100 && pq.Size() < min_width_after_100) {
1688 min_width_after_100 = pq.Size();
1689 size_at_min_width_after_100 = relaxed_variables.size();
1690 }
1691
1692 const int index = pq.Top().index;
1693 const int score = pq.Top().score;
1694 pq.Pop();
1695 added[index] = true;
1696
1697 // When the score is zero, we don't need to update anything since the
1698 // frontier does not grow.
1699 if (score == 0) {
1700 if (index < num_vars) relaxed_variables.push_back(index);
1701 ++num_zero_score;
1702 continue;
1703 }
1704
1705 // Note that while it might looks bad, the overall complexity of this is
1706 // in O(num_edge) since we scan each index once and each newly connected
1707 // vertex once.
1708 int num_added = 0;
1709 to_update.clear();
1710 if (index < num_vars) {
1711 relaxed_variables.push_back(index);
1712 for (const int c : helper_.VarToConstraint()[index]) {
1713 const int c_index = num_vars + c;
1714 if (added_or_connected[c_index]) continue;
1715 ++num_added;
1716 added_or_connected[c_index] = true;
1717 to_update.push_back(c_index);
1718 for (const int v : helper_.ConstraintToVar()[c]) {
1719 if (added[v]) continue;
1720 if (added_or_connected[v]) {
1721 to_update.push_back(v);
1722 elements[v].score--;
1723 } else {
1724 elements[c_index].score++;
1725 }
1726 }
1727 }
1728 } else {
1729 for (const int v : helper_.ConstraintToVar()[index - num_vars]) {
1730 if (added_or_connected[v]) continue;
1731 ++num_added;
1732 added_or_connected[v] = true;
1733 to_update.push_back(v);
1734 for (const int c : helper_.VarToConstraint()[v]) {
1735 if (added[num_vars + c]) continue;
1736 if (added_or_connected[num_vars + c]) {
1737 elements[num_vars + c].score--;
1738 to_update.push_back(num_vars + c);
1739 } else {
1740 elements[v].score++;
1741 }
1742 }
1743 }
1744 }
1745
1746 // The score is exactly the frontier increase in size.
1747 // This is the same as the min-degree heuristic for the elimination order.
1748 // Except we only consider connected nodes.
1749 CHECK_EQ(num_added, score);
1750
1752 for (const int index : to_update) {
1753 DCHECK(!added[index]);
1754 if (pq.Contains(index)) {
1755 pq.ChangePriority(elements[index]);
1756 } else {
1757 pq.Add(elements[index]);
1758 }
1759 }
1760
1761 max_width = std::max(max_width, pq.Size());
1762 }
1763
1764 // Just for logging.
1765 if (pq.Size() < min_width_after_100) {
1766 min_width_after_100 = pq.Size();
1767 size_at_min_width_after_100 = relaxed_variables.size();
1768 }
1769
1770 VLOG(2) << "#relaxed " << relaxed_variables.size() << " #zero_score "
1771 << num_zero_score << " max_width " << max_width
1772 << " (size,min_width)_after_100 (" << size_at_min_width_after_100
1773 << "," << min_width_after_100 << ") "
1774 << " final_width " << pq.Size();
1775 }
1776
1777 return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1778}
1779
1780namespace {
1781
1782// Create a constraint sum (X - LB) + sum (UB - X) <= rhs.
1783ConstraintProto DistanceToBoundsSmallerThanConstraint(
1784 absl::Span<const std::pair<int, int64_t>> dist_to_lower_bound,
1785 absl::Span<const std::pair<int, int64_t>> dist_to_upper_bound,
1786 const int64_t rhs) {
1787 DCHECK_GE(rhs, 0);
1788 ConstraintProto new_constraint;
1789 LinearConstraintProto* linear = new_constraint.mutable_linear();
1790 int64_t lhs_constant_value = 0;
1791 for (const auto [var, lb] : dist_to_lower_bound) {
1792 // We add X - LB
1793 linear->add_coeffs(1);
1794 linear->add_vars(var);
1795 lhs_constant_value -= lb;
1796 }
1797 for (const auto [var, ub] : dist_to_upper_bound) {
1798 // We add UB - X
1799 lhs_constant_value += ub;
1800 linear->add_coeffs(-1);
1801 linear->add_vars(var);
1802 }
1803 linear->add_domain(std::numeric_limits<int64_t>::min());
1804 linear->add_domain(rhs - lhs_constant_value);
1805 return new_constraint;
1806}
1807
1808} // namespace
1809
1811 const CpSolverResponse& initial_solution, SolveData& data,
1812 absl::BitGenRef random) {
1813 const std::vector<int> active_variables = helper_.ActiveVariables();
1814 if (active_variables.empty()) return helper_.NoNeighborhood();
1815
1816 {
1817 // Quick corner case in case the difficulty is too high. This is mainly
1818 // useful when testing with only that kind of LNS to abort early on
1819 // super-easy problems.
1820 const int size = active_variables.size();
1821 if (static_cast<int>(std::ceil(data.difficulty * size)) == size) {
1822 return helper_.FullNeighborhood();
1823 }
1824 }
1825
1826 // These are candidate for relaxation. The score will be filled later. Active
1827 // variable not kept in candidate will be added to other_variables.
1828 std::vector<std::pair<int, double>> candidates_with_score;
1829 std::vector<int> other_variables;
1830
1831 // Our extra relaxation constraint will be: sums of distance to the respective
1832 // bound smaller than a constant that depends on the difficulty.
1833 std::vector<std::pair<int, int64_t>> dist_to_lower_bound;
1834 std::vector<std::pair<int, int64_t>> dist_to_upper_bound;
1835
1836 // For the "easy" part of the extra constraint, we either look only at the
1837 // binary variables. Or we extend that to all variables at their bound.
1838 const bool only_look_at_binary = absl::Bernoulli(random, 0.5);
1839
1840 // We copy the model early to have access to reduced domains.
1841 // TODO(user): that might not be the most efficient if we abort just below.
1842 CpModelProto local_cp_model = helper_.UpdatedModelProtoCopy();
1843
1844 // Loop over active variables.
1845 bool some_non_binary_at_bound = false;
1846 for (const int var : active_variables) {
1847 DCHECK_LT(var, initial_solution.solution().size());
1848 DCHECK_LT(var, local_cp_model.variables().size());
1849 const IntegerVariableProto& var_proto = local_cp_model.variables(var);
1850 const int64_t base_value = initial_solution.solution(var);
1851 const bool is_binary = var_proto.domain_size() == 2 &&
1852 var_proto.domain(0) == 0 && var_proto.domain(1) == 1;
1853 if (only_look_at_binary && !is_binary) {
1854 other_variables.push_back(var);
1855 continue;
1856 }
1857
1858 DCHECK(!var_proto.domain().empty());
1859 const int64_t domain_min = var_proto.domain(0);
1860 const int64_t domain_max = var_proto.domain(var_proto.domain().size() - 1);
1861 if (base_value <= domain_min) {
1862 if (!is_binary) some_non_binary_at_bound = true;
1863 candidates_with_score.push_back({var, 0.0});
1864 dist_to_lower_bound.push_back({var, domain_min});
1865 } else if (base_value >= domain_max) {
1866 if (!is_binary) some_non_binary_at_bound = true;
1867 candidates_with_score.push_back({var, 0.0});
1868 dist_to_upper_bound.push_back({var, domain_max});
1869 } else {
1870 other_variables.push_back(var);
1871 }
1872 }
1873
1874 bool use_hamming_for_others = false;
1875 if (!other_variables.empty() && absl::Bernoulli(random, 0.5)) {
1876 use_hamming_for_others = true;
1877 }
1878 if (!use_hamming_for_others && candidates_with_score.empty()) {
1879 return helper_.NoNeighborhood();
1880 }
1881
1882 // With this option, we will create a bunch of Boolean variable
1883 // and add the constraints : "bool==0 => var == value_in_base_solution".
1884 if (use_hamming_for_others) {
1885 for (const int var : other_variables) {
1886 const int indicator = local_cp_model.variables().size();
1887 auto* var_proto = local_cp_model.add_variables();
1888 var_proto->add_domain(0);
1889 var_proto->add_domain(1);
1890 auto* new_ct = local_cp_model.add_constraints();
1891 new_ct->add_enforcement_literal(NegatedRef(indicator));
1892
1893 const int64_t base_value = initial_solution.solution(var);
1894 new_ct->mutable_linear()->add_domain(base_value);
1895 new_ct->mutable_linear()->add_domain(base_value);
1896 new_ct->mutable_linear()->add_vars(var);
1897 new_ct->mutable_linear()->add_coeffs(1);
1898
1899 // Add it to the distance constraint.
1900 dist_to_lower_bound.push_back({indicator, 0});
1901 candidates_with_score.push_back({var, 0.0});
1902 }
1903
1904 // Clear other_variables so that they are not added at the end.
1905 other_variables.clear();
1906 }
1907
1908 // Constrain the distance to the bounds.
1909 const int size = dist_to_upper_bound.size() + dist_to_lower_bound.size();
1910 const int target_size = static_cast<int>(std::ceil(data.difficulty * size));
1911 DCHECK_LE(target_size, candidates_with_score.size());
1912 *local_cp_model.add_constraints() = DistanceToBoundsSmallerThanConstraint(
1913 dist_to_lower_bound, dist_to_upper_bound, target_size);
1914
1915 Model model("lb_relax_lns_lp");
1916 auto* const params = model.GetOrCreate<SatParameters>();
1917
1918 // Parameters to enable solving the LP only.
1919 params->set_num_workers(1);
1920 params->set_linearization_level(2);
1921 params->set_stop_after_root_propagation(true);
1922 params->set_add_lp_constraints_lazily(false);
1923
1924 // Parameters to attempt to speed up solve.
1925 params->set_cp_model_presolve(false);
1926 params->set_cp_model_probing_level(0);
1927
1928 // Parameters to limit time spent in the solve. The max number of iterations
1929 // is relaxed from the default since we rely more on deterministic time.
1930 params->set_root_lp_iterations(100000);
1931
1932 // TODO(user): This is a lot longer than a normal LNS, so it might cause
1933 // issue with the current round-robbin selection based on number of calls.
1934 params->set_max_deterministic_time(10);
1935 model.GetOrCreate<TimeLimit>()->ResetLimitFromParameters(*params);
1936 if (global_time_limit_ != nullptr) {
1937 global_time_limit_->UpdateLocalLimit(model.GetOrCreate<TimeLimit>());
1938 }
1939
1940 // Tricky: we want the inner_objective_lower_bound in the response to be in
1941 // term of the current problem, not the user facing one.
1942 if (local_cp_model.has_objective()) {
1943 local_cp_model.mutable_objective()->set_integer_before_offset(0);
1944 local_cp_model.mutable_objective()->set_integer_after_offset(0);
1945 local_cp_model.mutable_objective()->set_integer_scaling_factor(0);
1946 }
1947
1948 // Dump?
1949 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
1950 const std::string dump_name =
1951 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
1952 "lb_relax_lns_lp_", data.task_id, ".pb.txt");
1953 LOG(INFO) << "Dumping linear relaxed model to '" << dump_name << "'.";
1954 CHECK(WriteModelProtoToFile(local_cp_model, dump_name));
1955 }
1956
1957 // Solve.
1958 //
1959 // TODO(user): Shall we pass the objective upper bound so we have more
1960 // chance to fix variable via reduced cost fixing.
1961 //
1962 // TODO(user): Does the current solution can provide a warm-start for the
1963 // LP?
1964 auto* response_manager = model.GetOrCreate<SharedResponseManager>();
1965 {
1966 response_manager->InitializeObjective(local_cp_model);
1967 LoadCpModel(local_cp_model, &model);
1968 SolveLoadedCpModel(local_cp_model, &model);
1969 }
1970
1971 // Update dtime.
1972 data.deterministic_time +=
1973 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime();
1974
1975 // Analyze the status of this first "solve".
1976 //
1977 // TODO(user): If we run into this case, it also means that every other LNS
1978 // that tries to more variable than here will never be able to improve.
1979 if (local_cp_model.has_objective()) {
1980 const CpSolverResponse response = response_manager->GetResponse();
1981 if (response.status() == CpSolverStatus::INFEASIBLE) {
1982 data.status = CpSolverStatus::INFEASIBLE;
1983 AddSolveData(data);
1984 return helper_.NoNeighborhood();
1985 }
1986
1987 const int64_t inner_lb = response.inner_objective_lower_bound();
1988 const int64_t current_inner_obj = ComputeInnerObjective(
1989 local_cp_model.objective(), initial_solution.solution());
1990 if (inner_lb >= current_inner_obj) {
1991 // In this case, we cannot improve on the base solution.
1992 // We could try to find a different solution for diversity, but we do have
1993 // other neighborhood for that. Lets abort early.
1994 data.status = CpSolverStatus::OPTIMAL; // We cannot improve.
1995 AddSolveData(data);
1996 return helper_.NoNeighborhood();
1997 }
1998 }
1999
2000 // Compute differences between LP solution and initial solution, with a small
2001 // random noise for tie breaking.
2002 const auto var_mapping = model.GetOrCreate<CpModelMapping>();
2003 const auto lp_solution = model.GetOrCreate<ModelLpValues>();
2004 if (lp_solution->empty()) {
2005 // We likely didn't solve the LP at all, so lets not use this neighborhood.
2006 return helper_.NoNeighborhood();
2007 }
2008 for (auto& [var, score] : candidates_with_score) {
2009 const IntegerVariable integer = var_mapping->Integer(var);
2010 DCHECK_LT(integer, lp_solution->size());
2011 DCHECK_LT(var, initial_solution.solution().size());
2012 const double difference =
2013 std::abs(lp_solution->at(var_mapping->Integer(var)) -
2014 initial_solution.solution(var));
2015 score = difference + absl::Uniform<double>(random, 0.0, 1e-6);
2016 }
2017
2018 // Take the target_size variables with largest differences.
2019 absl::c_sort(candidates_with_score, [](const std::pair<int, double>& a,
2020 const std::pair<int, double>& b) {
2021 return a.second > b.second;
2022 });
2023
2024 std::vector<int> vars_to_relax;
2025 vars_to_relax.reserve(target_size);
2026 DCHECK_LE(target_size, candidates_with_score.size());
2027 for (int i = 0; i < target_size; ++i) {
2028 vars_to_relax.push_back(candidates_with_score[i].first);
2029 }
2030
2031 // We will also relax all "other variables". We assume their values are likely
2032 // tied to the other ones.
2033 vars_to_relax.insert(vars_to_relax.end(), other_variables.begin(),
2034 other_variables.end());
2035 Neighborhood result =
2036 helper_.RelaxGivenVariables(initial_solution, vars_to_relax);
2037
2038 // Lets the name reflect the type.
2039 //
2040 // TODO(user): Unfortunately like this we have a common difficulty for all
2041 // variant, we should probably fix that.
2042 result.source_info = "lb_relax_lns";
2043 absl::StrAppend(&result.source_info,
2044 some_non_binary_at_bound ? "_int" : "_bool");
2045 if (use_hamming_for_others) {
2046 absl::StrAppend(&result.source_info, "_h");
2047 }
2048
2049 return result;
2050}
2051
2052namespace {
2053
2054void AddPrecedence(const LinearExpressionProto& before,
2055 const LinearExpressionProto& after, CpModelProto* model) {
2056 LinearConstraintProto* linear = model->add_constraints()->mutable_linear();
2057 linear->add_domain(std::numeric_limits<int64_t>::min());
2058 linear->add_domain(after.offset() - before.offset());
2059 for (int i = 0; i < before.vars_size(); ++i) {
2060 linear->add_vars(before.vars(i));
2061 linear->add_coeffs(before.coeffs(i));
2062 }
2063 for (int i = 0; i < after.vars_size(); ++i) {
2064 linear->add_vars(after.vars(i));
2065 linear->add_coeffs(-after.coeffs(i));
2066 }
2067}
2068
2069} // namespace
2070
2072 const absl::Span<const std::pair<int, int>> precedences,
2073 const CpSolverResponse& initial_solution,
2074 const NeighborhoodGeneratorHelper& helper) {
2075 Neighborhood neighborhood = helper.FullNeighborhood();
2076
2077 neighborhood.is_reduced = !precedences.empty();
2078 if (!neighborhood.is_reduced) { // Returns the full neighborhood.
2079 helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
2080 neighborhood.is_generated = true;
2081 return neighborhood;
2082 }
2083
2084 // Collect seen intervals.
2085 absl::flat_hash_set<int> seen_intervals;
2086 for (const std::pair<int, int>& prec : precedences) {
2087 seen_intervals.insert(prec.first);
2088 seen_intervals.insert(prec.second);
2089 }
2090
2091 // Fix the presence/absence of unseen intervals.
2092 bool enforcement_literals_fixed = false;
2093 for (const int i : helper.TypeToConstraints(ConstraintProto::kInterval)) {
2094 if (seen_intervals.contains(i)) continue;
2095
2096 const ConstraintProto& interval_ct = helper.ModelProto().constraints(i);
2097 if (interval_ct.enforcement_literal().empty()) continue;
2098
2099 DCHECK_EQ(interval_ct.enforcement_literal().size(), 1);
2100 const int enforcement_ref = interval_ct.enforcement_literal(0);
2101 const int enforcement_var = PositiveRef(enforcement_ref);
2102 const int value = initial_solution.solution(enforcement_var);
2103
2104 // If the interval is not enforced, we just relax it. If it belongs to an
2105 // exactly one constraint, and the enforced interval is not relaxed, then
2106 // propagation will force this interval to stay not enforced. Otherwise,
2107 // LNS will be able to change which interval will be enforced among all
2108 // alternatives.
2109 if (RefIsPositive(enforcement_ref) == (value == 0)) continue;
2110
2111 // Fix the value.
2112 neighborhood.delta.mutable_variables(enforcement_var)->clear_domain();
2113 neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
2114 neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
2115 enforcement_literals_fixed = true;
2116 }
2117
2118 for (const std::pair<int, int>& prec : precedences) {
2119 const LinearExpressionProto& before_end =
2120 helper.ModelProto().constraints(prec.first).interval().end();
2121 const LinearExpressionProto& after_start =
2122 helper.ModelProto().constraints(prec.second).interval().start();
2123 DCHECK_LE(GetLinearExpressionValue(before_end, initial_solution),
2124 GetLinearExpressionValue(after_start, initial_solution));
2125 AddPrecedence(before_end, after_start, &neighborhood.delta);
2126 }
2127
2128 // Set the current solution as a hint.
2129 helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
2130 neighborhood.is_generated = true;
2131
2132 return neighborhood;
2133}
2134
2136 absl::Span<const int> intervals_to_relax,
2137 absl::Span<const int> variables_to_fix,
2138 const CpSolverResponse& initial_solution, absl::BitGenRef random,
2139 const NeighborhoodGeneratorHelper& helper) {
2140 Neighborhood neighborhood = helper.FullNeighborhood();
2141
2142 // We will extend the set with some interval that we cannot fix.
2143 absl::flat_hash_set<int> ignored_intervals(intervals_to_relax.begin(),
2144 intervals_to_relax.end());
2145
2146 // Fix the presence/absence of non-relaxed intervals.
2147 for (const int i : helper.TypeToConstraints(ConstraintProto::kInterval)) {
2148 DCHECK_GE(i, 0);
2149 if (ignored_intervals.contains(i)) continue;
2150
2151 const ConstraintProto& interval_ct = helper.ModelProto().constraints(i);
2152 if (interval_ct.enforcement_literal().empty()) continue;
2153
2154 DCHECK_EQ(interval_ct.enforcement_literal().size(), 1);
2155 const int enforcement_ref = interval_ct.enforcement_literal(0);
2156 const int enforcement_var = PositiveRef(enforcement_ref);
2157 const int value = initial_solution.solution(enforcement_var);
2158
2159 // If the interval is not enforced, we just relax it. If it belongs to an
2160 // exactly one constraint, and the enforced interval is not relaxed, then
2161 // propagation will force this interval to stay not enforced. Otherwise,
2162 // LNS will be able to change which interval will be enforced among all
2163 // alternatives.
2164 if (RefIsPositive(enforcement_ref) == (value == 0)) {
2165 ignored_intervals.insert(i);
2166 continue;
2167 }
2168
2169 // Fix the value.
2170 neighborhood.delta.mutable_variables(enforcement_var)->clear_domain();
2171 neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
2172 neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
2173 }
2174
2175 if (ignored_intervals.size() >=
2176 helper.TypeToConstraints(ConstraintProto::kInterval)
2177 .size()) { // Returns the full neighborhood.
2178 helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
2179 neighborhood.is_generated = true;
2180 return neighborhood;
2181 }
2182
2183 neighborhood.is_reduced = true;
2184
2185 // We differ from the ICAPS05 paper as we do not consider ignored intervals
2186 // when generating the precedence graph, instead of building the full graph,
2187 // then removing intervals, and reconstructing the precedence graph
2188 // heuristically after that.
2189 const std::vector<std::pair<int, int>> precedences =
2190 helper.GetSchedulingPrecedences(ignored_intervals, initial_solution,
2191 random);
2192 for (const std::pair<int, int>& prec : precedences) {
2193 const LinearExpressionProto& before_end =
2194 helper.ModelProto().constraints(prec.first).interval().end();
2195 const LinearExpressionProto& after_start =
2196 helper.ModelProto().constraints(prec.second).interval().start();
2197 DCHECK_LE(GetLinearExpressionValue(before_end, initial_solution),
2198 GetLinearExpressionValue(after_start, initial_solution));
2199 AddPrecedence(before_end, after_start, &neighborhood.delta);
2200 }
2201
2202 // fix the extra variables passed as parameters.
2203 for (const int var : variables_to_fix) {
2204 const int value = initial_solution.solution(var);
2205 neighborhood.delta.mutable_variables(var)->clear_domain();
2206 neighborhood.delta.mutable_variables(var)->add_domain(value);
2207 neighborhood.delta.mutable_variables(var)->add_domain(value);
2208 }
2209
2210 // Set the current solution as a hint.
2211 helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
2212 neighborhood.is_generated = true;
2213
2214 return neighborhood;
2215}
2216
2218 const CpSolverResponse& initial_solution, SolveData& data,
2219 absl::BitGenRef random) {
2220 std::vector<int> intervals_to_relax =
2221 helper_.GetActiveIntervals(initial_solution);
2222 GetRandomSubset(data.difficulty, &intervals_to_relax, random);
2223
2225 intervals_to_relax, {}, initial_solution, random, helper_);
2226}
2227
2229 const CpSolverResponse& initial_solution, SolveData& data,
2230 absl::BitGenRef random) {
2231 std::vector<std::pair<int, int>> precedences =
2232 helper_.GetSchedulingPrecedences({}, initial_solution, random);
2233 GetRandomSubset(1.0 - data.difficulty, &precedences, random);
2235 precedences, initial_solution, helper_);
2236}
2237
2238namespace {
2239void AppendVarsFromAllIntervalIndices(absl::Span<const int> indices,
2240 absl::Span<const int> all_intervals,
2241 const CpModelProto& model_proto,
2242 std::vector<int>* variables) {
2243 for (const int index : indices) {
2244 const std::vector<int> vars =
2245 UsedVariables(model_proto.constraints(all_intervals[index]));
2246 variables->insert(variables->end(), vars.begin(), vars.end());
2247 }
2248}
2249} // namespace
2250
2252 const CpSolverResponse& initial_solution, SolveData& data,
2253 absl::BitGenRef random) {
2254 const std::vector<int> active_intervals =
2255 helper_.GetActiveIntervals(initial_solution);
2256
2257 if (active_intervals.empty()) return helper_.FullNeighborhood();
2258
2259 const TimePartition partition = PartitionIndicesAroundRandomTimeWindow(
2260 active_intervals, helper_.ModelProto(), initial_solution, data.difficulty,
2261 random);
2262 std::vector<int> intervals_to_relax;
2263 intervals_to_relax.reserve(partition.selected_indices.size());
2264 std::vector<int> variables_to_fix;
2265 intervals_to_relax.insert(intervals_to_relax.end(),
2266 partition.selected_indices.begin(),
2267 partition.selected_indices.end());
2268
2269 if (helper_.Parameters().push_all_tasks_toward_start()) {
2270 intervals_to_relax.insert(intervals_to_relax.end(),
2271 partition.indices_before_selected.begin(),
2272 partition.indices_before_selected.end());
2273 AppendVarsFromAllIntervalIndices(partition.indices_before_selected,
2274 active_intervals, helper_.ModelProto(),
2275 &variables_to_fix);
2276 }
2277
2278 gtl::STLSortAndRemoveDuplicates(&intervals_to_relax);
2279 gtl::STLSortAndRemoveDuplicates(&variables_to_fix);
2281 intervals_to_relax, variables_to_fix, initial_solution, random, helper_);
2282}
2283
2285 const CpSolverResponse& initial_solution, SolveData& data,
2286 absl::BitGenRef random) {
2287 std::vector<int> intervals_to_relax;
2288 std::vector<int> variables_to_fix;
2289 std::vector<int> active_intervals;
2290 for (const std::vector<int>& intervals : intervals_in_constraints_) {
2291 active_intervals = helper_.KeepActiveIntervals(intervals, initial_solution);
2292 const TimePartition partition = PartitionIndicesAroundRandomTimeWindow(
2293 active_intervals, helper_.ModelProto(), initial_solution,
2294 data.difficulty, random);
2295 intervals_to_relax.insert(intervals_to_relax.end(),
2296 partition.selected_indices.begin(),
2297 partition.selected_indices.end());
2298
2299 if (helper_.Parameters().push_all_tasks_toward_start()) {
2300 intervals_to_relax.insert(intervals_to_relax.end(),
2301 partition.indices_before_selected.begin(),
2302 partition.indices_before_selected.end());
2303 AppendVarsFromAllIntervalIndices(partition.indices_before_selected,
2304 active_intervals, helper_.ModelProto(),
2305 &variables_to_fix);
2306 }
2307 }
2308
2309 if (intervals_to_relax.empty() && variables_to_fix.empty()) {
2310 return helper_.FullNeighborhood();
2311 }
2312
2313 gtl::STLSortAndRemoveDuplicates(&intervals_to_relax);
2314 gtl::STLSortAndRemoveDuplicates(&variables_to_fix);
2316 intervals_to_relax, variables_to_fix, initial_solution, random, helper_);
2317}
2318
2320 const CpSolverResponse& initial_solution, SolveData& data,
2321 absl::BitGenRef random) {
2322 std::vector<ActiveRectangle> rectangles_to_freeze =
2323 helper_.GetActiveRectangles(initial_solution);
2324 GetRandomSubset(1.0 - data.difficulty, &rectangles_to_freeze, random);
2325
2326 Bitset64<int> variables_to_freeze(helper_.NumVariables());
2327 for (const ActiveRectangle& rectangle : rectangles_to_freeze) {
2328 InsertVariablesFromInterval(helper_.ModelProto(), rectangle.x_interval,
2329 variables_to_freeze);
2330 InsertVariablesFromInterval(helper_.ModelProto(), rectangle.y_interval,
2331 variables_to_freeze);
2332 }
2333 return helper_.FixGivenVariables(initial_solution, variables_to_freeze);
2334}
2335
2337 const CpSolverResponse& initial_solution, SolveData& data,
2338 absl::BitGenRef random) {
2339 // First pick one rectangle.
2340 const std::vector<ActiveRectangle> all_active_rectangles =
2341 helper_.GetActiveRectangles(initial_solution);
2342 if (all_active_rectangles.size() <= 1) return helper_.FullNeighborhood();
2343
2344 const ActiveRectangle& base_rectangle =
2345 all_active_rectangles[absl::Uniform<int>(random, 0,
2346 all_active_rectangles.size())];
2347
2348 const auto get_rectangle = [&initial_solution, helper = &helper_](
2349 const ActiveRectangle& rectangle) {
2350 const int x_interval_idx = rectangle.x_interval;
2351 const int y_interval_idx = rectangle.y_interval;
2352 const ConstraintProto& x_interval_ct =
2353 helper->ModelProto().constraints(x_interval_idx);
2354 const ConstraintProto& y_interval_ct =
2355 helper->ModelProto().constraints(y_interval_idx);
2356 return Rectangle{.x_min = GetLinearExpressionValue(
2357 x_interval_ct.interval().start(), initial_solution),
2358 .x_max = GetLinearExpressionValue(
2359 x_interval_ct.interval().end(), initial_solution),
2360 .y_min = GetLinearExpressionValue(
2361 y_interval_ct.interval().start(), initial_solution),
2362 .y_max = GetLinearExpressionValue(
2363 y_interval_ct.interval().end(), initial_solution)};
2364 };
2365
2366 const Rectangle center_rect = get_rectangle(base_rectangle);
2367
2368 // Now compute a neighborhood around that rectangle. In this neighborhood
2369 // we prefer a "Square" region around the initial rectangle center rather than
2370 // a circle.
2371 //
2372 // Note that we only consider two rectangles as potential neighbors if they
2373 // are part of the same no_overlap_2d constraint.
2374 Bitset64<int> variables_to_freeze(helper_.NumVariables());
2375 std::vector<std::pair<int, double>> distances;
2376 distances.reserve(all_active_rectangles.size());
2377 for (int i = 0; i < all_active_rectangles.size(); ++i) {
2378 const ActiveRectangle& rectangle = all_active_rectangles[i];
2379 InsertVariablesFromInterval(helper_.ModelProto(), rectangle.x_interval,
2380 variables_to_freeze);
2381 InsertVariablesFromInterval(helper_.ModelProto(), rectangle.y_interval,
2382 variables_to_freeze);
2383
2384 const Rectangle rect = get_rectangle(rectangle);
2385 const bool same_no_overlap_as_center_rect = absl::c_any_of(
2386 base_rectangle.no_overlap_2d_constraints, [&rectangle](const int c) {
2387 return rectangle.no_overlap_2d_constraints.contains(c);
2388 });
2389 if (same_no_overlap_as_center_rect) {
2390 distances.push_back(
2391 {i, CenterToCenterLInfinityDistance(center_rect, rect)});
2392 }
2393 }
2394 std::stable_sort(
2395 distances.begin(), distances.end(),
2396 [](const auto& a, const auto& b) { return a.second < b.second; });
2397
2398 const int num_to_sample = data.difficulty * all_active_rectangles.size();
2399 const int num_to_relax = std::min<int>(distances.size(), num_to_sample);
2400 Rectangle relaxed_bounding_box = center_rect;
2401 absl::flat_hash_set<int> boxes_to_relax;
2402 for (int i = 0; i < num_to_relax; ++i) {
2403 const int rectangle_idx = distances[i].first;
2404 const ActiveRectangle& rectangle = all_active_rectangles[rectangle_idx];
2405 relaxed_bounding_box.GrowToInclude(get_rectangle(rectangle));
2406 boxes_to_relax.insert(rectangle_idx);
2407 }
2408
2409 // Heuristic: we relax a bit the bounding box in order to allow some
2410 // movements, this is needed to not have a trivial neighborhood if we relax a
2411 // single box for instance.
2412 const IntegerValue x_size = relaxed_bounding_box.SizeX();
2413 const IntegerValue y_size = relaxed_bounding_box.SizeY();
2414 relaxed_bounding_box.x_min = CapSubI(relaxed_bounding_box.x_min, x_size / 2);
2415 relaxed_bounding_box.x_max = CapAddI(relaxed_bounding_box.x_max, x_size / 2);
2416 relaxed_bounding_box.y_min = CapSubI(relaxed_bounding_box.y_min, y_size / 2);
2417 relaxed_bounding_box.y_max = CapAddI(relaxed_bounding_box.y_max, y_size / 2);
2418
2419 for (const int b : boxes_to_relax) {
2420 const ActiveRectangle& rectangle = all_active_rectangles[b];
2421 RemoveVariablesFromInterval(helper_.ModelProto(), rectangle.x_interval,
2422 variables_to_freeze);
2423 RemoveVariablesFromInterval(helper_.ModelProto(), rectangle.y_interval,
2424 variables_to_freeze);
2425 }
2426 Neighborhood neighborhood =
2427 helper_.FixGivenVariables(initial_solution, variables_to_freeze);
2428
2429 // The call above add the relaxed variables to the neighborhood using the
2430 // current bounds at level 0. For big problems, this might create a hard model
2431 // with a large complicated landscape of fixed boxes with a lot of potential
2432 // places to place the relaxed boxes. Therefore we update the domain so the
2433 // boxes can only stay around the area we decided to relax.
2434 for (const int b : boxes_to_relax) {
2435 {
2436 const IntervalConstraintProto& x_interval =
2437 helper_.ModelProto()
2438 .constraints(all_active_rectangles[b].x_interval)
2439 .interval();
2440 const Domain x_domain = Domain(relaxed_bounding_box.x_min.value(),
2441 relaxed_bounding_box.x_max.value());
2442 RestrictAffineExpression(x_interval.start(), x_domain,
2443 &neighborhood.delta);
2444 RestrictAffineExpression(x_interval.end(), x_domain, &neighborhood.delta);
2445 }
2446 {
2447 const IntervalConstraintProto& y_interval =
2448 helper_.ModelProto()
2449 .constraints(all_active_rectangles[b].y_interval)
2450 .interval();
2451 const Domain y_domain = Domain(relaxed_bounding_box.y_min.value(),
2452 relaxed_bounding_box.y_max.value());
2453 RestrictAffineExpression(y_interval.start(), y_domain,
2454 &neighborhood.delta);
2455 RestrictAffineExpression(y_interval.end(), y_domain, &neighborhood.delta);
2456 }
2457 }
2458 return neighborhood;
2459}
2460
2462 const CpSolverResponse& initial_solution, SolveData& data,
2463 absl::BitGenRef random) {
2464 // First pick a pair of rectangles.
2465 std::vector<ActiveRectangle> all_active_rectangles =
2466 helper_.GetActiveRectangles(initial_solution);
2467 if (all_active_rectangles.size() <= 2) return helper_.FullNeighborhood();
2468
2469 const int first_idx =
2470 absl::Uniform<int>(random, 0, all_active_rectangles.size());
2471 int second_idx =
2472 absl::Uniform<int>(random, 0, all_active_rectangles.size() - 1);
2473 if (second_idx >= first_idx) {
2474 second_idx++;
2475 }
2476
2477 const ActiveRectangle& chosen_rectangle_1 = all_active_rectangles[first_idx];
2478 const ActiveRectangle& chosen_rectangle_2 = all_active_rectangles[second_idx];
2479
2480 const auto get_rectangle = [&initial_solution, helper = &helper_](
2481 const ActiveRectangle& rectangle) {
2482 const int x_interval_idx = rectangle.x_interval;
2483 const int y_interval_idx = rectangle.y_interval;
2484 const ConstraintProto& x_interval_ct =
2485 helper->ModelProto().constraints(x_interval_idx);
2486 const ConstraintProto& y_interval_ct =
2487 helper->ModelProto().constraints(y_interval_idx);
2488 return Rectangle{.x_min = GetLinearExpressionValue(
2489 x_interval_ct.interval().start(), initial_solution),
2490 .x_max = GetLinearExpressionValue(
2491 x_interval_ct.interval().end(), initial_solution),
2492 .y_min = GetLinearExpressionValue(
2493 y_interval_ct.interval().start(), initial_solution),
2494 .y_max = GetLinearExpressionValue(
2495 y_interval_ct.interval().end(), initial_solution)};
2496 };
2497
2498 const Rectangle rect1 = get_rectangle(chosen_rectangle_1);
2499 const Rectangle rect2 = get_rectangle(chosen_rectangle_2);
2500
2501 // Now compute a neighborhood around each rectangle. Note that we only
2502 // consider two rectangles as potential neighbors if they are part of the same
2503 // no_overlap_2d constraint.
2504 //
2505 // TODO(user): This computes the distance between the center of the
2506 // rectangles. We could use the real distance between the closest points, but
2507 // not sure it is worth the extra complexity.
2508 Bitset64<int> variables_to_freeze(helper_.NumVariables());
2509 std::vector<std::pair<int, double>> distances1;
2510 std::vector<std::pair<int, double>> distances2;
2511 distances1.reserve(all_active_rectangles.size());
2512 distances2.reserve(all_active_rectangles.size());
2513 for (int i = 0; i < all_active_rectangles.size(); ++i) {
2514 const ActiveRectangle& rectangle = all_active_rectangles[i];
2515 InsertVariablesFromInterval(helper_.ModelProto(), rectangle.x_interval,
2516 variables_to_freeze);
2517 InsertVariablesFromInterval(helper_.ModelProto(), rectangle.y_interval,
2518 variables_to_freeze);
2519
2520 const Rectangle rect = get_rectangle(rectangle);
2521 const bool same_no_overlap_as_rect1 =
2522 absl::c_any_of(chosen_rectangle_1.no_overlap_2d_constraints,
2523 [&rectangle](const int c) {
2524 return rectangle.no_overlap_2d_constraints.contains(c);
2525 });
2526 const bool same_no_overlap_as_rect2 =
2527 absl::c_any_of(chosen_rectangle_2.no_overlap_2d_constraints,
2528 [&rectangle](const int c) {
2529 return rectangle.no_overlap_2d_constraints.contains(c);
2530 });
2531 if (same_no_overlap_as_rect1) {
2532 distances1.push_back({i, CenterToCenterL2Distance(rect1, rect)});
2533 }
2534 if (same_no_overlap_as_rect2) {
2535 distances2.push_back({i, CenterToCenterL2Distance(rect2, rect)});
2536 }
2537 }
2538 const int num_to_sample_each =
2539 data.difficulty * all_active_rectangles.size() / 2;
2540 std::sort(distances1.begin(), distances1.end(),
2541 [](const auto& a, const auto& b) { return a.second < b.second; });
2542 std::sort(distances2.begin(), distances2.end(),
2543 [](const auto& a, const auto& b) { return a.second < b.second; });
2544 for (auto& samples : {distances1, distances2}) {
2545 const int num_potential_samples = samples.size();
2546 for (int i = 0; i < std::min(num_potential_samples, num_to_sample_each);
2547 ++i) {
2548 const int rectangle_idx = samples[i].first;
2549 const ActiveRectangle& rectangle = all_active_rectangles[rectangle_idx];
2550 RemoveVariablesFromInterval(helper_.ModelProto(), rectangle.x_interval,
2551 variables_to_freeze);
2552 RemoveVariablesFromInterval(helper_.ModelProto(), rectangle.y_interval,
2553 variables_to_freeze);
2554 }
2555 }
2556
2557 return helper_.FixGivenVariables(initial_solution, variables_to_freeze);
2558}
2559
2561 const CpSolverResponse& initial_solution, SolveData& data,
2562 absl::BitGenRef random) {
2563 std::vector<ActiveRectangle> rectangles_to_relax =
2564 helper_.GetActiveRectangles(initial_solution);
2565 GetRandomSubset(data.difficulty, &rectangles_to_relax, random);
2566 std::vector<int> intervals_to_relax;
2567 for (const ActiveRectangle& rect : rectangles_to_relax) {
2568 intervals_to_relax.push_back(rect.x_interval);
2569 intervals_to_relax.push_back(rect.y_interval);
2570 }
2571 gtl::STLSortAndRemoveDuplicates(&intervals_to_relax);
2572
2574 intervals_to_relax, {}, initial_solution, random, helper_);
2575}
2576
2578 const CpSolverResponse& initial_solution, SolveData& data,
2579 absl::BitGenRef random) {
2580 const std::vector<ActiveRectangle> active_rectangles =
2581 helper_.GetActiveRectangles(initial_solution);
2582 const bool use_first_dimension = absl::Bernoulli(random, 0.5);
2583 std::vector<int> projected_intervals;
2584 projected_intervals.reserve(active_rectangles.size());
2585 for (const ActiveRectangle& rect : active_rectangles) {
2586 projected_intervals.push_back(use_first_dimension ? rect.x_interval
2587 : rect.y_interval);
2588 }
2589
2590 const TimePartition partition = PartitionIndicesAroundRandomTimeWindow(
2591 projected_intervals, helper_.ModelProto(), initial_solution,
2592 data.difficulty, random);
2593 std::vector<bool> indices_to_fix(active_rectangles.size(), true);
2594 for (const int index : partition.selected_indices) {
2595 indices_to_fix[index] = false;
2596 }
2597
2598 Bitset64<int> variables_to_freeze(helper_.NumVariables());
2599 for (int index = 0; index < active_rectangles.size(); ++index) {
2600 if (indices_to_fix[index]) {
2602 active_rectangles[index].x_interval,
2603 variables_to_freeze);
2605 active_rectangles[index].y_interval,
2606 variables_to_freeze);
2607 }
2608 }
2609
2610 return helper_.FixGivenVariables(initial_solution, variables_to_freeze);
2611}
2612
2614 const CpSolverResponse& initial_solution, SolveData& data,
2615 absl::BitGenRef random) {
2616 const std::vector<std::vector<int>> all_paths =
2617 helper_.GetRoutingPathBooleanVariables(initial_solution);
2618
2619 // Collect all unique variables.
2620 std::vector<int> variables_to_fix;
2621 for (const auto& path : all_paths) {
2622 variables_to_fix.insert(variables_to_fix.end(), path.begin(), path.end());
2623 }
2624 gtl::STLSortAndRemoveDuplicates(&variables_to_fix);
2625 GetRandomSubset(1.0 - data.difficulty, &variables_to_fix, random);
2626
2627 Bitset64<int> to_fix(helper_.NumVariables());
2628 for (const int var : variables_to_fix) to_fix.Set(var);
2629 return helper_.FixGivenVariables(initial_solution, to_fix);
2630}
2631
2633 const CpSolverResponse& initial_solution, SolveData& data,
2634 absl::BitGenRef random) {
2635 std::vector<std::vector<int>> all_paths =
2636 helper_.GetRoutingPathBooleanVariables(initial_solution);
2637
2638 // Remove a corner case where all paths are empty.
2639 if (all_paths.empty()) {
2640 return helper_.NoNeighborhood();
2641 }
2642
2643 // Collect all unique variables.
2644 std::vector<int> all_path_variables;
2645 int sum_of_path_sizes = 0;
2646 for (const auto& path : all_paths) {
2647 sum_of_path_sizes += path.size();
2648 }
2649 all_path_variables.reserve(sum_of_path_sizes);
2650 for (const auto& path : all_paths) {
2651 all_path_variables.insert(all_path_variables.end(), path.begin(),
2652 path.end());
2653 }
2654 gtl::STLSortAndRemoveDuplicates(&all_path_variables);
2655
2656 // Select target number of variables to relax.
2657 const int num_variables_to_relax =
2658 static_cast<int>(all_path_variables.size() * data.difficulty);
2659 absl::flat_hash_set<int> relaxed_variables;
2660
2661 while (relaxed_variables.size() < num_variables_to_relax) {
2662 DCHECK(!all_paths.empty());
2663 const int path_index = absl::Uniform<int>(random, 0, all_paths.size());
2664 std::vector<int>& path = all_paths[path_index];
2665 const int path_size = path.size();
2666 const int segment_length =
2667 std::min(path_size, absl::Uniform<int>(random, 4, 8));
2668 const int segment_start =
2669 absl::Uniform<int>(random, 0, path_size - segment_length);
2670 for (int i = segment_start; i < segment_start + segment_length; ++i) {
2671 relaxed_variables.insert(path[i]);
2672 }
2673
2674 // Remove segment and clean up empty paths.
2675 path.erase(path.begin() + segment_start,
2676 path.begin() + segment_start + segment_length);
2677 if (path.empty()) {
2678 std::swap(all_paths[path_index], all_paths.back());
2679 all_paths.pop_back();
2680 }
2681 }
2682
2683 // Compute the set of variables to fix.
2684 Bitset64<int> to_fix(helper_.NumVariables());
2685 for (const int var : all_path_variables) {
2686 if (!relaxed_variables.contains(var)) to_fix.Set(var);
2687 }
2688 return helper_.FixGivenVariables(initial_solution, to_fix);
2689}
2690
2692 const CpSolverResponse& initial_solution, SolveData& data,
2693 absl::BitGenRef random) {
2694 std::vector<std::vector<int>> all_paths =
2695 helper_.GetRoutingPathBooleanVariables(initial_solution);
2696
2697 // Remove a corner case where all paths are empty.
2698 if (all_paths.empty()) {
2699 return helper_.NoNeighborhood();
2700 }
2701
2702 // Collect all unique variables.
2703 std::vector<int> all_path_variables;
2704 int sum_of_path_sizes = 0;
2705 for (const auto& path : all_paths) {
2706 sum_of_path_sizes += path.size();
2707 }
2708 all_path_variables.reserve(sum_of_path_sizes);
2709 for (const auto& path : all_paths) {
2710 all_path_variables.insert(all_path_variables.end(), path.begin(),
2711 path.end());
2712 }
2713 gtl::STLSortAndRemoveDuplicates(&all_path_variables);
2714
2715 // Select target number of variables to relax.
2716 const int num_variables_to_relax =
2717 static_cast<int>(all_path_variables.size() * data.difficulty);
2718 absl::flat_hash_set<int> relaxed_variables;
2719
2720 // Relax the start and end of each path to ease relocation.
2721 // TODO(user): Restrict this if the difficulty is very low.
2722 for (const auto& path : all_paths) {
2723 relaxed_variables.insert(path.front());
2724 relaxed_variables.insert(path.back());
2725 }
2726
2727 // Relax all variables, if possible, of one random path.
2728 const int path_index = absl::Uniform<int>(random, 0, all_paths.size());
2729 std::shuffle(all_paths[path_index].begin(), all_paths[path_index].end(),
2730 random);
2731 while (relaxed_variables.size() < num_variables_to_relax &&
2732 !all_paths[path_index].empty()) {
2733 relaxed_variables.insert(all_paths[path_index].back());
2734 all_paths[path_index].pop_back();
2735 }
2736
2737 // Relax more variables until the target is reached.
2738 if (relaxed_variables.size() < num_variables_to_relax) {
2739 std::shuffle(all_path_variables.begin(), all_path_variables.end(), random);
2740 while (relaxed_variables.size() < num_variables_to_relax) {
2741 relaxed_variables.insert(all_path_variables.back());
2742 all_path_variables.pop_back();
2743 }
2744 }
2745
2746 // Compute the set of variables to fix.
2747 Bitset64<int> to_fix(helper_.NumVariables());
2748 for (const int var : all_path_variables) {
2749 if (!relaxed_variables.contains(var)) to_fix.Set(var);
2750 }
2751 return helper_.FixGivenVariables(initial_solution, to_fix);
2752}
2753
2755 return (incomplete_solutions_->HasSolution() ||
2756 lp_solutions_->NumSolutions() > 0);
2757}
2758
2760 const CpSolverResponse& /*initial_solution*/, SolveData& data,
2761 absl::BitGenRef random) {
2762 Neighborhood neighborhood = helper_.FullNeighborhood();
2763 neighborhood.is_generated = false;
2764
2765 const ReducedDomainNeighborhood reduced_domains =
2766 GetRinsRensNeighborhood(response_manager_, lp_solutions_,
2767 incomplete_solutions_, data.difficulty, random);
2768
2769 if (reduced_domains.fixed_vars.empty() &&
2770 reduced_domains.reduced_domain_vars.empty()) {
2771 return neighborhood;
2772 }
2773 neighborhood.source_info = reduced_domains.source_info;
2774
2775 absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
2776 // Fix the variables in the local model.
2777 for (const std::pair</*model_var*/ int, /*value*/ int64_t>& fixed_var :
2778 reduced_domains.fixed_vars) {
2779 const int var = fixed_var.first;
2780 const int64_t value = fixed_var.second;
2781 if (var >= neighborhood.delta.variables_size()) continue;
2782 if (!helper_.IsActive(var)) continue;
2783
2784 if (!DomainInProtoContains(neighborhood.delta.variables(var), value)) {
2785 // TODO(user): Instead of aborting, pick the closest point in the domain?
2786 return neighborhood;
2787 }
2788
2789 neighborhood.delta.mutable_variables(var)->clear_domain();
2790 neighborhood.delta.mutable_variables(var)->add_domain(value);
2791 neighborhood.delta.mutable_variables(var)->add_domain(value);
2792 neighborhood.is_reduced = true;
2793 }
2794
2795 for (const std::pair</*model_var*/ int,
2796 /*domain*/ std::pair<int64_t, int64_t>>& reduced_var :
2797 reduced_domains.reduced_domain_vars) {
2798 const int var = reduced_var.first;
2799 const int64_t lb = reduced_var.second.first;
2800 const int64_t ub = reduced_var.second.second;
2801 if (var >= neighborhood.delta.variables_size()) continue;
2802 if (!helper_.IsActive(var)) continue;
2803 const Domain domain =
2804 ReadDomainFromProto(neighborhood.delta.variables(var));
2805 Domain new_domain = domain.IntersectionWith(Domain(lb, ub));
2806 if (new_domain.IsEmpty()) {
2807 new_domain = Domain::FromValues(
2808 {domain.ClosestValue(lb), domain.ClosestValue(ub)});
2809 }
2810 FillDomainInProto(domain, neighborhood.delta.mutable_variables(var));
2811 neighborhood.is_reduced = true;
2812 }
2813 neighborhood.is_generated = true;
2814 return neighborhood;
2815}
2816
2817} // namespace sat
2818} // namespace operations_research
bool AddEdge(int node1, int node2)
IndexType size() const
Returns how many bits this Bitset64 can hold.
Definition bitset.h:464
void Clear(IndexType i)
Sets the bit at position i to 0.
Definition bitset.h:506
void Set(IndexType i)
Sets the bit at position i to 1.
Definition bitset.h:544
static Domain FromValues(std::vector< int64_t > values)
Domain IntersectionWith(const Domain &domain) 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
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
void RemoveBySwap(K key, int index)
Definition util.h:138
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, 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.
std::vector< std::vector< int > > GetRoutingPathBooleanVariables(const CpSolverResponse &initial_solution) const
Neighborhood FixAllVariables(const CpSolverResponse &initial_solution) const
Neighborhood FixGivenVariables(const CpSolverResponse &base_solution, const Bitset64< int > &variables_to_fix) const
const SharedResponseManager & shared_response() const
Neighborhood RelaxGivenVariables(const CpSolverResponse &initial_solution, absl::Span< const int > relaxed_variables) const
NeighborhoodGeneratorHelper(CpModelProto const *model_proto, SatParameters const *parameters, SharedResponseManager *shared_response, SharedBoundsManager *shared_bounds=nullptr)
std::vector< std::vector< int > > GetUniqueIntervalSets() const
std::vector< ActiveRectangle > GetActiveRectangles(const CpSolverResponse &initial_solution) const
void AddSolutionHinting(const CpSolverResponse &initial_solution, CpModelProto *model_proto) const
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
NeighborhoodGeneratorHelper::ActiveRectangle ActiveRectangle
virtual bool ReadyToGenerate() const
Returns true if the neighborhood generator can generate a neighborhood.
double GetUCBScore(int64_t total_num_calls) const
const NeighborhoodGeneratorHelper & helper_
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, 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, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
SubSolver(absl::string_view name, SubsolverType type)
Definition subsolver.h:48
SubsolverType type() const
Returns the type of the subsolver.
Definition subsolver.h:98
Neighborhood Generate(const CpSolverResponse &initial_solution, SolveData &data, absl::BitGenRef random) final
void reserve(size_type n)
const bool DEBUG_MODE
Definition macros.h:26
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
void SolveLoadedCpModel(const CpModelProto &model_proto, Model *model)
void RemoveVariablesFromInterval(const CpModelProto &model_proto, int index, Bitset64< int > &output)
int64_t ComputeInnerObjective(const CpObjectiveProto &objective, absl::Span< const int64_t > solution)
ReducedDomainNeighborhood GetRinsRensNeighborhood(const SharedResponseManager *response_manager, const SharedLPSolutionRepository *lp_solutions, SharedIncompleteSolutionManager *incomplete_solutions, double difficulty, absl::BitGenRef random)
Definition rins.cc:176
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
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)
double CenterToCenterLInfinityDistance(const Rectangle &a, const Rectangle &b)
Definition diffn_util.h:145
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)
double CenterToCenterL2Distance(const Rectangle &a, const Rectangle &b)
Returns the L2 distance between the centers of the two rectangles.
Definition diffn_util.h:135
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.
IntegerValue CapAddI(IntegerValue a, IntegerValue b)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
void LoadCpModel(const CpModelProto &model_proto, Model *model)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
void InsertVariablesFromInterval(const CpModelProto &model_proto, int index, Bitset64< int > &output)
Insert/Remove variables from an interval constraint into a bitset.
IntegerValue CapSubI(IntegerValue a, IntegerValue b)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapSub(int64_t x, int64_t y)
Definition model.h:51
Adds solve data about one "solved" neighborhood.
double deterministic_time
The time it took to solve this neighborhood.
double difficulty
The difficulty when this neighborhood was generated.
CpSolverStatus status
The status of the sub-solve.
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
static constexpr int kDefaultArenaSizePerVariable
void GrowToInclude(const Rectangle &other)
Definition diffn_util.h:50
std::vector< std::pair< int, std::pair< int64_t, int64_t > > > reduced_domain_vars
Definition rins.h:41
std::vector< std::pair< int, int64_t > > fixed_vars
A variable will appear only once and not in both vectors.
Definition rins.h:38