OR-Tools  9.6
cp_model_lns.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <limits>
21 #include <numeric>
22 #include <random>
23 #include <string>
24 #include <tuple>
25 #include <utility>
26 #include <vector>
27 
28 #include "absl/container/flat_hash_map.h"
29 #include "absl/container/flat_hash_set.h"
30 #include "absl/log/check.h"
31 #include "absl/meta/type_traits.h"
32 #include "absl/random/bit_gen_ref.h"
33 #include "absl/random/distributions.h"
34 #include "absl/strings/str_cat.h"
35 #include "absl/strings/str_join.h"
36 #include "absl/synchronization/mutex.h"
37 #include "absl/time/clock.h"
38 #include "absl/time/time.h"
39 #include "absl/types/span.h"
40 #include "ortools/base/logging.h"
41 #include "ortools/base/stl_util.h"
43 #include "ortools/sat/cp_model.pb.h"
46 #include "ortools/sat/integer.h"
47 #include "ortools/sat/model.h"
49 #include "ortools/sat/rins.h"
50 #include "ortools/sat/sat_parameters.pb.h"
51 #include "ortools/sat/subsolver.h"
58 
59 namespace operations_research {
60 namespace sat {
61 
63  CpModelProto const* model_proto, SatParameters const* parameters,
64  SharedResponseManager* shared_response, SharedBoundsManager* shared_bounds)
65  : SubSolver("neighborhood_helper", HELPER),
66  parameters_(*parameters),
67  model_proto_(*model_proto),
68  shared_bounds_(shared_bounds),
69  shared_response_(shared_response) {
70  CHECK(shared_response_ != nullptr);
71  if (shared_bounds_ != nullptr) {
72  shared_bounds_id_ = shared_bounds_->RegisterNewId();
73  }
74  *model_proto_with_only_variables_.mutable_variables() =
75  model_proto_.variables();
76  InitializeHelperData();
77  RecomputeHelperData();
78  Synchronize();
79  last_logging_time_ = absl::Now();
80 }
81 
83  if (shared_bounds_ != nullptr) {
84  std::vector<int> model_variables;
85  std::vector<int64_t> new_lower_bounds;
86  std::vector<int64_t> new_upper_bounds;
87  shared_bounds_->GetChangedBounds(shared_bounds_id_, &model_variables,
88  &new_lower_bounds, &new_upper_bounds);
89 
90  bool new_variables_have_been_fixed = false;
91 
92  {
93  absl::MutexLock domain_lock(&domain_mutex_);
94 
95  for (int i = 0; i < model_variables.size(); ++i) {
96  const int var = model_variables[i];
97  const int64_t new_lb = new_lower_bounds[i];
98  const int64_t new_ub = new_upper_bounds[i];
99  if (VLOG_IS_ON(3)) {
100  const auto& domain =
101  model_proto_with_only_variables_.variables(var).domain();
102  const int64_t old_lb = domain.Get(0);
103  const int64_t old_ub = domain.Get(domain.size() - 1);
104  VLOG(3) << "Variable: " << var << " old domain: [" << old_lb << ", "
105  << old_ub << "] new domain: [" << new_lb << ", " << new_ub
106  << "]";
107  }
108  const Domain old_domain = ReadDomainFromProto(
109  model_proto_with_only_variables_.variables(var));
110  const Domain new_domain =
111  old_domain.IntersectionWith(Domain(new_lb, new_ub));
112  if (new_domain.IsEmpty()) {
113  // This can mean two things:
114  // 1/ This variable is a normal one and the problem is UNSAT or
115  // 2/ This variable is optional, and its associated literal must be
116  // set to false.
117  //
118  // Currently, we wait for any full solver to pick the crossing bounds
119  // and do the correct stuff on their own. We do not want to have empty
120  // domain in the proto as this would means INFEASIBLE. So we just
121  // ignore such bounds here.
122  //
123  // TODO(user): We could set the optional literal to false directly in
124  // the bound sharing manager. We do have to be careful that all the
125  // different solvers have the same optionality definition though.
126  continue;
127  }
129  new_domain,
130  model_proto_with_only_variables_.mutable_variables(var));
131  new_variables_have_been_fixed |= new_domain.IsFixed();
132  }
133  }
134 
135  // Only trigger the computation if needed.
136  if (new_variables_have_been_fixed) {
137  RecomputeHelperData();
138  }
139  }
140 }
141 
142 bool NeighborhoodGeneratorHelper::ObjectiveDomainIsConstraining() const {
143  if (!model_proto_.has_objective()) return false;
144  if (model_proto_.objective().domain().empty()) return false;
145 
146  int64_t min_activity = 0;
147  int64_t max_activity = 0;
148  const int num_terms = model_proto_.objective().vars().size();
149  for (int i = 0; i < num_terms; ++i) {
150  const int var = PositiveRef(model_proto_.objective().vars(i));
151  const int64_t coeff = model_proto_.objective().coeffs(i);
152  const auto& var_domain =
153  model_proto_with_only_variables_.variables(var).domain();
154  const int64_t v1 = coeff * var_domain[0];
155  const int64_t v2 = coeff * var_domain[var_domain.size() - 1];
156  min_activity += std::min(v1, v2);
157  max_activity += std::max(v1, v2);
158  }
159 
160  const Domain obj_domain = ReadDomainFromProto(model_proto_.objective());
161  const Domain inferred_domain =
162  Domain(min_activity, max_activity)
164  Domain(std::numeric_limits<int64_t>::min(), obj_domain.Max()));
165  return !inferred_domain.IsIncludedIn(obj_domain);
166 }
167 
168 void NeighborhoodGeneratorHelper::InitializeHelperData() {
169  type_to_constraints_.clear();
170  const int num_constraints = model_proto_.constraints_size();
171  for (int c = 0; c < num_constraints; ++c) {
172  const int type = model_proto_.constraints(c).constraint_case();
173  if (type >= type_to_constraints_.size()) {
174  type_to_constraints_.resize(type + 1);
175  }
176  type_to_constraints_[type].push_back(c);
177  }
178 
179  const int num_variables = model_proto_.variables().size();
180  is_in_objective_.resize(num_variables, false);
181  if (model_proto_.has_objective()) {
182  for (const int ref : model_proto_.objective().vars()) {
183  is_in_objective_[PositiveRef(ref)] = true;
184  }
185  }
186 }
187 
188 // Recompute all the data when new variables have been fixed. Note that this
189 // shouldn't be called if there is no change as it is in O(problem size).
190 void NeighborhoodGeneratorHelper::RecomputeHelperData() {
191  absl::MutexLock graph_lock(&graph_mutex_);
192  absl::ReaderMutexLock domain_lock(&domain_mutex_);
193 
194  // Do basic presolving to have a more precise graph.
195  // Here we just remove trivially true constraints.
196  //
197  // Note(user): We do that each time a new variable is fixed. It might be too
198  // much, but on the miplib and in 1200s, we do that only about 1k time on the
199  // worst case problem.
200  //
201  // TODO(user): Change API to avoid a few copy?
202  // TODO(user): We could keep the context in the class.
203  // TODO(user): We can also start from the previous simplified model instead.
204  {
205  Model local_model;
206  CpModelProto mapping_proto;
207  simplied_model_proto_.Clear();
208  *simplied_model_proto_.mutable_variables() =
209  model_proto_with_only_variables_.variables();
210  PresolveContext context(&local_model, &simplied_model_proto_,
211  &mapping_proto);
212  ModelCopy copier(&context);
213 
214  // TODO(user): Not sure what to do if the model is UNSAT.
215  // This shouldn't matter as it should be dealt with elsewhere.
216  copier.ImportAndSimplifyConstraints(model_proto_, {});
217  }
218 
219  // Compute the constraint <-> variable graph.
220  //
221  // TODO(user): Remove duplicate constraints?
222  const auto& constraints = simplied_model_proto_.constraints();
223  var_to_constraint_.assign(model_proto_.variables_size(), {});
224  constraint_to_var_.assign(constraints.size(), {});
225  int reduced_ct_index = 0;
226  for (int ct_index = 0; ct_index < constraints.size(); ++ct_index) {
227  // We remove the interval constraints since we should have an equivalent
228  // linear constraint somewhere else. This is not the case if we have a fixed
229  // size optional interval variable. But it should not matter as the
230  // intervals are replaced by their underlying variables in the scheduling
231  // constrainst.
232  if (constraints[ct_index].constraint_case() == ConstraintProto::kInterval) {
233  continue;
234  }
235 
236  for (const int var : UsedVariables(constraints[ct_index])) {
237  if (IsConstant(var)) continue;
238  constraint_to_var_[reduced_ct_index].push_back(var);
239  }
240 
241  // We replace intervals by their underlying integer variables. Note that
242  // this is needed for a correct decomposition into independent part.
243  for (const int interval : UsedIntervals(constraints[ct_index])) {
244  for (const int var : UsedVariables(constraints[interval])) {
245  if (IsConstant(var)) continue;
246  constraint_to_var_[reduced_ct_index].push_back(var);
247  }
248  }
249 
250  // We remove constraint of size 0 and 1 since they are not useful for LNS
251  // based on this graph.
252  if (constraint_to_var_[reduced_ct_index].size() <= 1) {
253  constraint_to_var_[reduced_ct_index].clear();
254  continue;
255  }
256 
257  // Keep this constraint.
258  for (const int var : constraint_to_var_[reduced_ct_index]) {
259  var_to_constraint_[var].push_back(reduced_ct_index);
260  }
261  ++reduced_ct_index;
262  }
263  constraint_to_var_.resize(reduced_ct_index);
264 
265  // We mark as active all non-constant variables.
266  // Non-active variable will never be fixed in standard LNS fragment.
267  active_variables_.clear();
268  const int num_variables = model_proto_.variables_size();
269  active_variables_set_.assign(num_variables, false);
270  for (int i = 0; i < num_variables; ++i) {
271  if (!IsConstant(i)) {
272  active_variables_.push_back(i);
273  active_variables_set_[i] = true;
274  }
275  }
276 
277  active_objective_variables_.clear();
278  for (const int var : model_proto_.objective().vars()) {
279  DCHECK(RefIsPositive(var));
280  if (active_variables_set_[var]) {
281  active_objective_variables_.push_back(var);
282  }
283  }
284 
285  // Compute connected components.
286  // Note that fixed variable are just ignored.
288  union_find.SetNumberOfNodes(num_variables);
289  for (const std::vector<int>& var_in_constraint : constraint_to_var_) {
290  if (var_in_constraint.size() <= 1) continue;
291  for (int i = 1; i < var_in_constraint.size(); ++i) {
292  union_find.AddEdge(var_in_constraint[0], var_in_constraint[i]);
293  }
294  }
295 
296  // If we have a lower bound on the objective, then this "objective constraint"
297  // might link components together.
298  if (ObjectiveDomainIsConstraining()) {
299  const auto& refs = model_proto_.objective().vars();
300  const int num_terms = refs.size();
301  for (int i = 1; i < num_terms; ++i) {
302  union_find.AddEdge(PositiveRef(refs[0]), PositiveRef(refs[i]));
303  }
304  }
305 
306  // Compute all components involving non-fixed variables.
307  //
308  // TODO(user): If a component has no objective, we can fix it to any feasible
309  // solution. This will automatically be done by LNS fragment covering such
310  // component though.
311  components_.clear();
312  var_to_component_index_.assign(num_variables, -1);
313  for (int var = 0; var < num_variables; ++var) {
314  if (IsConstant(var)) continue;
315  const int root = union_find.FindRoot(var);
316  DCHECK_LT(root, var_to_component_index_.size());
317  int& index = var_to_component_index_[root];
318  if (index == -1) {
319  index = components_.size();
320  components_.push_back({});
321  }
322  var_to_component_index_[var] = index;
323  components_[index].push_back(var);
324  }
325 
326  // Display information about the reduced problem.
327  //
328  // TODO(user): Exploit connected component while generating fragments.
329  // TODO(user): Do not generate fragment not touching the objective.
330  if (!shared_response_->LoggingIsEnabled()) return;
331 
332  std::vector<int> component_sizes;
333  for (const std::vector<int>& component : components_) {
334  component_sizes.push_back(component.size());
335  }
336  std::sort(component_sizes.begin(), component_sizes.end(),
337  std::greater<int>());
338  std::string compo_message;
339  if (component_sizes.size() > 1) {
340  if (component_sizes.size() <= 10) {
341  compo_message =
342  absl::StrCat(" compo:", absl::StrJoin(component_sizes, ","));
343  } else {
344  component_sizes.resize(10);
345  compo_message =
346  absl::StrCat(" compo:", absl::StrJoin(component_sizes, ","), ",...");
347  }
348  }
349 
350  // TODO(user): This is not ideal, as if two reductions appears in a row and
351  // nothing else is done for a while, we will never see the "latest" size
352  // in the log until it is reduced again.
353  shared_response_->LogPeriodicMessage(
354  "Model",
355  absl::StrCat("var:", active_variables_.size(), "/", num_variables,
356  " constraints:", simplied_model_proto_.constraints().size(),
357  "/", model_proto_.constraints().size(), compo_message),
358  parameters_.model_reduction_log_frequency_in_seconds(),
359  &last_logging_time_);
360 }
361 
363  return active_variables_set_[var];
364 }
365 
366 bool NeighborhoodGeneratorHelper::IsConstant(int var) const {
367  return model_proto_with_only_variables_.variables(var).domain_size() == 2 &&
368  model_proto_with_only_variables_.variables(var).domain(0) ==
369  model_proto_with_only_variables_.variables(var).domain(1);
370 }
371 
373  Neighborhood neighborhood;
374  neighborhood.is_reduced = false;
375  neighborhood.is_generated = true;
376  {
377  absl::ReaderMutexLock lock(&domain_mutex_);
378  *neighborhood.delta.mutable_variables() =
379  model_proto_with_only_variables_.variables();
380  }
381  return neighborhood;
382 }
383 
385  Neighborhood neighborhood;
386  neighborhood.is_generated = false;
387  return neighborhood;
388 }
389 
391  const CpSolverResponse& initial_solution) const {
392  std::vector<int> active_intervals;
393  absl::ReaderMutexLock lock(&domain_mutex_);
394  for (const int i : TypeToConstraints(ConstraintProto::kInterval)) {
395  const ConstraintProto& interval_ct = ModelProto().constraints(i);
396  // We only look at intervals that are performed in the solution. The
397  // unperformed intervals should be automatically freed during the generation
398  // phase.
399  if (interval_ct.enforcement_literal().size() == 1) {
400  const int enforcement_ref = interval_ct.enforcement_literal(0);
401  const int enforcement_var = PositiveRef(enforcement_ref);
402  const int value = initial_solution.solution(enforcement_var);
403  if (RefIsPositive(enforcement_ref) == (value == 0)) {
404  continue;
405  }
406  }
407 
408  // We filter out fixed intervals. Because of presolve, if there is an
409  // enforcement literal, it cannot be fixed.
410  if (interval_ct.enforcement_literal().empty()) {
411  bool is_constant = true;
412  for (const int v : interval_ct.interval().start().vars()) {
413  if (!IsConstant(v)) {
414  is_constant = false;
415  break;
416  }
417  }
418  for (const int v : interval_ct.interval().size().vars()) {
419  if (!IsConstant(v)) {
420  is_constant = false;
421  break;
422  }
423  }
424  for (const int v : interval_ct.interval().end().vars()) {
425  if (!IsConstant(v)) {
426  is_constant = false;
427  break;
428  }
429  }
430  if (is_constant) continue;
431  }
432 
433  active_intervals.push_back(i);
434  }
435  return active_intervals;
436 }
437 
438 std::vector<std::vector<int>>
440  std::vector<std::vector<int>> intervals_in_constraints;
441  absl::flat_hash_set<std::vector<int>> added_intervals_sets;
442  const auto add_interval_list_only_once =
443  [&intervals_in_constraints,
444  &added_intervals_sets](const auto& intervals) {
445  std::vector<int> candidate({intervals.begin(), intervals.end()});
447  if (added_intervals_sets.insert(candidate).second) {
448  intervals_in_constraints.push_back(candidate);
449  }
450  };
451  for (const int ct_index : TypeToConstraints(ConstraintProto::kNoOverlap)) {
452  add_interval_list_only_once(
453  model_proto_.constraints(ct_index).no_overlap().intervals());
454  }
455  for (const int ct_index : TypeToConstraints(ConstraintProto::kCumulative)) {
456  add_interval_list_only_once(
457  model_proto_.constraints(ct_index).cumulative().intervals());
458  }
459  for (const int ct_index : TypeToConstraints(ConstraintProto::kNoOverlap2D)) {
460  add_interval_list_only_once(
461  model_proto_.constraints(ct_index).no_overlap_2d().x_intervals());
462  add_interval_list_only_once(
463  model_proto_.constraints(ct_index).no_overlap_2d().y_intervals());
464  }
465  return intervals_in_constraints;
466 }
467 
468 namespace {
469 
470 int64_t GetLinearExpressionValue(const LinearExpressionProto& expr,
471  const CpSolverResponse& initial_solution) {
472  int64_t result = expr.offset();
473  for (int i = 0; i < expr.vars_size(); ++i) {
474  result += expr.coeffs(i) * initial_solution.solution(expr.vars(i));
475  }
476  return result;
477 }
478 
479 struct StartEndInterval {
480  int64_t start;
481  int64_t end;
483  bool operator<(const StartEndInterval& o) const {
484  return std::tie(start, end, interval_index) <
485  std::tie(o.start, o.end, o.interval_index);
486  }
487 };
488 
489 // Selects all intervals in a random time window to meet the difficulty
490 // requirement.
491 std::vector<int> SelectIntervalsInRandomTimeWindow(
492  const std::vector<int>& intervals, const CpModelProto& model_proto,
493  const CpSolverResponse& initial_solution, double difficulty,
494  absl::BitGenRef random) {
495  std::vector<StartEndInterval> start_end_intervals;
496  for (const int i : intervals) {
497  const ConstraintProto& interval_ct = model_proto.constraints(i);
498  // We only look at intervals that are performed in the solution. The
499  // unperformed intervals should be automatically freed during the
500  // generation phase.
501  if (interval_ct.enforcement_literal().size() == 1) {
502  const int enforcement_ref = interval_ct.enforcement_literal(0);
503  const int enforcement_var = PositiveRef(enforcement_ref);
504  const int64_t value = initial_solution.solution(enforcement_var);
505  if (RefIsPositive(enforcement_ref) == (value == 0)) {
506  continue;
507  }
508  }
509  const int64_t start_value = GetLinearExpressionValue(
510  interval_ct.interval().start(), initial_solution);
511  const int64_t end_value = GetLinearExpressionValue(
512  interval_ct.interval().end(), initial_solution);
513  start_end_intervals.push_back({start_value, end_value, i});
514  }
515 
516  if (start_end_intervals.empty()) return {};
517 
518  std::sort(start_end_intervals.begin(), start_end_intervals.end());
519  const int relaxed_size = std::floor(difficulty * start_end_intervals.size());
520 
521  std::uniform_int_distribution<int> random_var(
522  0, start_end_intervals.size() - relaxed_size - 1);
523  // TODO(user): Consider relaxing more than one time window
524  // intervals. This seems to help with Giza models.
525  const int random_start_index = random_var(random);
526 
527  // We want to minimize the time window relaxed, so we now sort the interval
528  // after the first selected intervals by end value.
529  // TODO(user): We could do things differently (include all tasks <= some
530  // end). The difficulty is that the number of relaxed tasks will differ from
531  // the target. We could also tie break tasks randomly.
532  std::sort(start_end_intervals.begin() + random_start_index,
533  start_end_intervals.end(),
534  [](const StartEndInterval& a, const StartEndInterval& b) {
535  return std::tie(a.end, a.interval_index) <
536  std::tie(b.end, b.interval_index);
537  });
538  std::vector<int> result;
539  for (int i = random_start_index; i < random_start_index + relaxed_size; ++i) {
540  result.push_back(start_end_intervals[i].interval_index);
541  }
542  return result;
543 }
544 
545 struct Demand {
546  int interval_index;
547  int64_t start;
548  int64_t end;
549  int64_t height;
550 
551  // Because of the binary splitting of the capacity in the procedure used to
552  // extract precedences out of a cumulative constraint, processing bigger
553  // heigts first will decrease its probability of being split across the 2
554  // halves of the current split.
555  bool operator<(const Demand& other) const {
556  return std::tie(start, height, end) <
557  std::tie(other.start, other.height, other.end);
558  }
559 
560  std::string DebugString() const {
561  return absl::StrCat("{i=", interval_index, " span=[", start, ",", end, "]",
562  " d=", height, "}");
563  }
564 };
565 
566 void InsertPrecedencesFromSortedListOfNonOverlapingIntervals(
567  const std::vector<Demand>& demands,
568  absl::flat_hash_set<std::pair<int, int>>* precedences) {
569  for (int i = 0; i + 1 < demands.size(); ++i) {
570  DCHECK_LE(demands[i].end, demands[i + 1].start);
571  precedences->insert(
572  {demands[i].interval_index, demands[i + 1].interval_index});
573  }
574 }
575 
576 bool IsPresent(const ConstraintProto& interval_ct,
577  const CpSolverResponse& initial_solution) {
578  if (interval_ct.enforcement_literal().size() != 1) return true;
579 
580  const int enforcement_ref = interval_ct.enforcement_literal(0);
581  const int enforcement_var = PositiveRef(enforcement_ref);
582  const int64_t value = initial_solution.solution(enforcement_var);
583  return RefIsPositive(enforcement_ref) == (value == 1);
584 }
585 
586 void InsertNoOverlapPrecedences(
587  const absl::flat_hash_set<int>& ignored_intervals,
588  const CpSolverResponse& initial_solution, const CpModelProto& model_proto,
589  int no_overlap_index,
590  absl::flat_hash_set<std::pair<int, int>>* precedences) {
591  std::vector<Demand> demands;
592  const NoOverlapConstraintProto& no_overlap =
593  model_proto.constraints(no_overlap_index).no_overlap();
594  for (const int interval_index : no_overlap.intervals()) {
595  if (ignored_intervals.contains(interval_index)) continue;
596  const ConstraintProto& interval_ct =
597  model_proto.constraints(interval_index);
598  if (!IsPresent(interval_ct, initial_solution)) continue;
599 
600  const int64_t start_value = GetLinearExpressionValue(
601  interval_ct.interval().start(), initial_solution);
602  const int64_t end_value = GetLinearExpressionValue(
603  interval_ct.interval().end(), initial_solution);
604  DCHECK_LE(start_value, end_value);
605  demands.push_back({interval_index, start_value, end_value, 1});
606  }
607 
608  // TODO(user): We actually only need interval_index, start.
609  // No need to fill the other fields here.
610  std::sort(demands.begin(), demands.end());
611  InsertPrecedencesFromSortedListOfNonOverlapingIntervals(demands, precedences);
612 }
613 
614 void ProcessDemandListFromCumulativeConstraint(
615  const std::vector<Demand>& demands, int64_t capacity,
616  std::deque<std::pair<std::vector<Demand>, int64_t>>* to_process,
617  absl::BitGenRef random,
618  absl::flat_hash_set<std::pair<int, int>>* precedences) {
619  if (demands.size() <= 1) return;
620 
621  // Checks if any pairs of tasks cannot overlap.
622  int64_t sum_of_min_two_capacities = 2;
623  if (capacity > 1) {
624  int64_t min1 = std::numeric_limits<int64_t>::max();
625  int64_t min2 = std::numeric_limits<int64_t>::max();
626  for (const Demand& demand : demands) {
627  if (demand.height <= min1) {
628  min2 = min1;
629  min1 = demand.height;
630  } else if (demand.height < min2) {
631  min2 = demand.height;
632  }
633  }
634  sum_of_min_two_capacities = min1 + min2;
635  }
636 
637  DCHECK_GT(sum_of_min_two_capacities, 1);
638  if (sum_of_min_two_capacities > capacity) {
639  InsertPrecedencesFromSortedListOfNonOverlapingIntervals(demands,
640  precedences);
641  return;
642  }
643 
644  std::vector<int64_t> unique_starts;
645  for (const Demand& demand : demands) {
646  DCHECK(unique_starts.empty() || demand.start >= unique_starts.back());
647  if (unique_starts.empty() || unique_starts.back() < demand.start) {
648  unique_starts.push_back(demand.start);
649  }
650  }
651  DCHECK(std::is_sorted(unique_starts.begin(), unique_starts.end()));
652  const int num_points = unique_starts.size();
653 
654  // Split the capacity in 2 and dispatch all demands on the 2 parts.
655  const int64_t capacity1 = capacity / 2;
656  std::vector<int64_t> usage1(num_points);
657  std::vector<Demand> demands1;
658 
659  const int64_t capacity2 = capacity - capacity1;
660  std::vector<int64_t> usage2(num_points);
661  std::vector<Demand> demands2;
662 
663  int usage_index = 0;
664  for (const Demand& d : demands) {
665  // Since we process demand by increasing start, the usage_index only
666  // need to increase.
667  while (usage_index < num_points && unique_starts[usage_index] < d.start) {
668  usage_index++;
669  }
670  DCHECK_LT(usage_index, num_points);
671  DCHECK_EQ(unique_starts[usage_index], d.start);
672  const int64_t slack1 = capacity1 - usage1[usage_index];
673  const int64_t slack2 = capacity2 - usage2[usage_index];
674 
675  // We differ from the ICAPS article. If it fits in both sub-cumulatives, We
676  // choose the smallest slack. If it fits into at most one, we choose the
677  // biggest slack. If both slacks are equal, we choose randomly.
678  const bool prefer2 =
679  slack1 == slack2
680  ? absl::Bernoulli(random, 0.5)
681  : (d.height <= std::min(slack1, slack2) ? slack2 < slack1
682  : slack2 > slack1);
683 
684  auto& selected_usage = prefer2 ? usage2 : usage1;
685  auto& residual_usage = prefer2 ? usage1 : usage2;
686  std::vector<Demand>& selected_demands = prefer2 ? demands2 : demands1;
687  std::vector<Demand>& residual_demands = prefer2 ? demands1 : demands2;
688  const int64_t selected_slack = prefer2 ? slack2 : slack1;
689 
690  const int64_t assigned_to_selected = std::min(selected_slack, d.height);
691  DCHECK_GT(assigned_to_selected, 0);
692  for (int i = usage_index; i < num_points; ++i) {
693  if (d.end <= unique_starts[i]) break;
694  selected_usage[i] += assigned_to_selected;
695  }
696  selected_demands.push_back(
697  {d.interval_index, d.start, d.end, assigned_to_selected});
698 
699  if (d.height > selected_slack) {
700  const int64_t residual = d.height - selected_slack;
701  DCHECK_GT(residual, 0);
702  DCHECK_LE(residual, prefer2 ? slack1 : slack2);
703  for (int i = usage_index; i < num_points; ++i) {
704  if (d.end <= unique_starts[i]) break;
705  residual_usage[i] += residual;
706  }
707  residual_demands.push_back({d.interval_index, d.start, d.end, residual});
708  }
709  }
710 
711  if (demands1.size() > 1) {
712  to_process->emplace_back(std::move(demands1), capacity1);
713  }
714  if (demands2.size() > 1) {
715  to_process->emplace_back(std::move(demands2), capacity2);
716  }
717 }
718 
719 void InsertCumulativePrecedences(
720  const absl::flat_hash_set<int>& ignored_intervals,
721  const CpSolverResponse& initial_solution, const CpModelProto& model_proto,
722  int cumulative_index, absl::BitGenRef random,
723  absl::flat_hash_set<std::pair<int, int>>* precedences) {
724  const CumulativeConstraintProto& cumulative =
725  model_proto.constraints(cumulative_index).cumulative();
726 
727  std::vector<Demand> demands;
728  for (int i = 0; i < cumulative.intervals().size(); ++i) {
729  const int interval_index = cumulative.intervals(i);
730  if (ignored_intervals.contains(interval_index)) continue;
731  const ConstraintProto& interval_ct =
732  model_proto.constraints(interval_index);
733  if (!IsPresent(interval_ct, initial_solution)) continue;
734 
735  const int64_t start_value = GetLinearExpressionValue(
736  interval_ct.interval().start(), initial_solution);
737  const int64_t end_value = GetLinearExpressionValue(
738  interval_ct.interval().end(), initial_solution);
739  const int64_t demand_value =
740  GetLinearExpressionValue(cumulative.demands(i), initial_solution);
741  if (start_value == end_value || demand_value == 0) continue;
742 
743  demands.push_back({interval_index, start_value, end_value, demand_value});
744  }
745  std::sort(demands.begin(), demands.end());
746 
747  const int64_t capacity_value =
748  GetLinearExpressionValue(cumulative.capacity(), initial_solution);
749  DCHECK_GT(capacity_value, 0);
750 
751  // Copying all these demands is memory intensive. Let's be careful here.
752  std::deque<std::pair<std::vector<Demand>, int64_t>> to_process;
753  to_process.emplace_back(std::move(demands), capacity_value);
754 
755  while (!to_process.empty()) {
756  auto& next_task = to_process.front();
757  ProcessDemandListFromCumulativeConstraint(next_task.first,
758  /*capacity=*/next_task.second,
759  &to_process, random, precedences);
760  to_process.pop_front();
761  }
762 }
763 
764 struct Rectangle {
765  int interval_index;
766  int64_t x_start;
767  int64_t x_end;
768  int64_t y_start;
769  int64_t y_end;
770 
771  bool operator<(const Rectangle& other) const {
772  return std::tie(x_start, x_end) < std::tie(other.x_start, other.x_end);
773  }
774 };
775 
776 void InsertRectanglePredecences(
777  const std::vector<Rectangle>& rectangles,
778  absl::flat_hash_set<std::pair<int, int>>* precedences) {
779  // TODO(user): Refine set of interesting points.
780  std::vector<int64_t> interesting_points;
781  for (const Rectangle& r : rectangles) {
782  interesting_points.push_back(r.y_end - 1);
783  }
784  gtl::STLSortAndRemoveDuplicates(&interesting_points);
785  std::vector<Demand> demands;
786  for (const int64_t t : interesting_points) {
787  demands.clear();
788  for (const Rectangle& r : rectangles) {
789  if (r.y_start > t || r.y_end <= t) continue;
790  demands.push_back({r.interval_index, r.x_start, r.x_end, 1});
791  }
792  std::sort(demands.begin(), demands.end());
793  InsertPrecedencesFromSortedListOfNonOverlapingIntervals(demands,
794  precedences);
795  }
796 }
797 
798 void InsertNoOverlap2dPrecedences(
799  const absl::flat_hash_set<int>& ignored_intervals,
800  const CpSolverResponse& initial_solution, const CpModelProto& model_proto,
801  int no_overlap_2d_index,
802  absl::flat_hash_set<std::pair<int, int>>* precedences) {
803  std::vector<Demand> demands;
804  const NoOverlap2DConstraintProto& no_overlap_2d =
805  model_proto.constraints(no_overlap_2d_index).no_overlap_2d();
806  std::vector<Rectangle> x_main;
807  std::vector<Rectangle> y_main;
808  for (int i = 0; i < no_overlap_2d.x_intervals_size(); ++i) {
809  // Ignore unperformed rectangles.
810  const int x_interval_index = no_overlap_2d.x_intervals(i);
811  if (ignored_intervals.contains(x_interval_index)) continue;
812  const ConstraintProto& x_interval_ct =
813  model_proto.constraints(x_interval_index);
814  if (!IsPresent(x_interval_ct, initial_solution)) continue;
815 
816  const int y_interval_index = no_overlap_2d.y_intervals(i);
817  if (ignored_intervals.contains(y_interval_index)) continue;
818  const ConstraintProto& y_interval_ct =
819  model_proto.constraints(y_interval_index);
820  if (!IsPresent(y_interval_ct, initial_solution)) continue;
821 
822  const int64_t x_start_value = GetLinearExpressionValue(
823  x_interval_ct.interval().start(), initial_solution);
824  const int64_t x_end_value = GetLinearExpressionValue(
825  x_interval_ct.interval().end(), initial_solution);
826  const int64_t y_start_value = GetLinearExpressionValue(
827  y_interval_ct.interval().start(), initial_solution);
828  const int64_t y_end_value = GetLinearExpressionValue(
829  y_interval_ct.interval().end(), initial_solution);
830 
831  // Ignore rectangles with zero area.
832  if (x_start_value == x_end_value || y_start_value == y_end_value) continue;
833 
834  x_main.push_back({x_interval_index, x_start_value, x_end_value,
835  y_start_value, y_end_value});
836  y_main.push_back({y_interval_index, y_start_value, y_end_value,
837  x_start_value, x_end_value});
838  }
839 
840  if (x_main.empty() || y_main.empty()) return;
841 
842  std::sort(x_main.begin(), x_main.end());
843  InsertRectanglePredecences(x_main, precedences);
844  std::sort(y_main.begin(), y_main.end());
845  InsertRectanglePredecences(y_main, precedences);
846 }
847 
848 } // namespace
849 
850 // TODO(user): We could scan for model precedences and add them to the list
851 // of precedences. This could enable more simplifications in the transitive
852 // reduction phase.
853 std::vector<std::pair<int, int>>
855  const absl::flat_hash_set<int>& ignored_intervals,
856  const CpSolverResponse& initial_solution, absl::BitGenRef random) const {
857  absl::flat_hash_set<std::pair<int, int>> precedences;
858  for (const int c : TypeToConstraints(ConstraintProto::kNoOverlap)) {
859  InsertNoOverlapPrecedences(ignored_intervals, initial_solution,
860  ModelProto(), c, &precedences);
861  }
862  for (const int c : TypeToConstraints(ConstraintProto::kCumulative)) {
863  InsertCumulativePrecedences(ignored_intervals, initial_solution,
864  ModelProto(), c, random, &precedences);
865  }
866  for (const int c : TypeToConstraints(ConstraintProto::kNoOverlap2D)) {
867  InsertNoOverlap2dPrecedences(ignored_intervals, initial_solution,
868  ModelProto(), c, &precedences);
869  }
870 
871  // TODO(user): Reduce precedence graph
872  std::vector<std::pair<int, int>> result(precedences.begin(),
873  precedences.end());
874  std::sort(result.begin(), result.end());
875  return result;
876 }
877 
878 std::vector<std::vector<int>> NeighborhoodGeneratorHelper::GetRoutingPaths(
879  const CpSolverResponse& initial_solution) const {
880  struct HeadAndArcLiteral {
881  int head;
882  int literal;
883  };
884 
885  std::vector<std::vector<int>> result;
886  absl::flat_hash_map<int, HeadAndArcLiteral> tail_to_head_and_arc_literal;
887 
888  for (const int i : TypeToConstraints(ConstraintProto::kCircuit)) {
889  const CircuitConstraintProto& ct = ModelProto().constraints(i).circuit();
890 
891  // Collect arcs.
892  int min_node = std::numeric_limits<int>::max();
893  tail_to_head_and_arc_literal.clear();
894  for (int i = 0; i < ct.literals_size(); ++i) {
895  const int literal = ct.literals(i);
896  const int head = ct.heads(i);
897  const int tail = ct.tails(i);
898  const int bool_var = PositiveRef(literal);
899  const int64_t value = initial_solution.solution(bool_var);
900  // Skip unselected arcs.
901  if (RefIsPositive(literal) == (value == 0)) continue;
902  // Ignore self loops.
903  if (head == tail) continue;
904  tail_to_head_and_arc_literal[tail] = {head, bool_var};
905  min_node = std::min(tail, min_node);
906  }
907  if (tail_to_head_and_arc_literal.empty()) continue;
908 
909  // Unroll the path.
910  int current_node = min_node;
911  std::vector<int> path;
912  do {
913  auto it = tail_to_head_and_arc_literal.find(current_node);
914  CHECK(it != tail_to_head_and_arc_literal.end());
915  current_node = it->second.head;
916  path.push_back(it->second.literal);
917  } while (current_node != min_node);
918  result.push_back(std::move(path));
919  }
920 
921  std::vector<HeadAndArcLiteral> route_starts;
922  for (const int i : TypeToConstraints(ConstraintProto::kRoutes)) {
923  const RoutesConstraintProto& ct = ModelProto().constraints(i).routes();
924  tail_to_head_and_arc_literal.clear();
925  route_starts.clear();
926 
927  // Collect route starts and arcs.
928  for (int i = 0; i < ct.literals_size(); ++i) {
929  const int literal = ct.literals(i);
930  const int head = ct.heads(i);
931  const int tail = ct.tails(i);
932  const int bool_var = PositiveRef(literal);
933  const int64_t value = initial_solution.solution(bool_var);
934  // Skip unselected arcs.
935  if (RefIsPositive(literal) == (value == 0)) continue;
936  // Ignore self loops.
937  if (head == tail) continue;
938  if (tail == 0) {
939  route_starts.push_back({head, bool_var});
940  } else {
941  tail_to_head_and_arc_literal[tail] = {head, bool_var};
942  }
943  }
944 
945  // Unroll all routes.
946  for (const HeadAndArcLiteral& head_var : route_starts) {
947  std::vector<int> path;
948  int current_node = head_var.head;
949  path.push_back(head_var.literal);
950  do {
951  auto it = tail_to_head_and_arc_literal.find(current_node);
952  CHECK(it != tail_to_head_and_arc_literal.end());
953  current_node = it->second.head;
954  path.push_back(it->second.literal);
955  } while (current_node != 0);
956  result.push_back(std::move(path));
957  }
958  }
959 
960  return result;
961 }
962 
964  const CpSolverResponse& base_solution,
965  const absl::flat_hash_set<int>& variables_to_fix) const {
966  Neighborhood neighborhood;
967 
968  // Fill in neighborhood.delta all variable domains.
969  {
970  absl::ReaderMutexLock domain_lock(&domain_mutex_);
971 
972  const int num_variables =
973  model_proto_with_only_variables_.variables().size();
974  neighborhood.delta.mutable_variables()->Reserve(num_variables);
975  for (int i = 0; i < num_variables; ++i) {
976  const IntegerVariableProto& current_var =
977  model_proto_with_only_variables_.variables(i);
978  IntegerVariableProto* new_var = neighborhood.delta.add_variables();
979 
980  // We only copy the name in debug mode.
981  if (DEBUG_MODE) new_var->set_name(current_var.name());
982 
983  const Domain domain = ReadDomainFromProto(current_var);
984  const int64_t base_value = base_solution.solution(i);
985 
986  if (variables_to_fix.contains(i)) {
987  if (domain.Contains(base_value)) {
988  new_var->add_domain(base_value);
989  new_var->add_domain(base_value);
990  } else {
991  // If under the updated domain, the base solution is no longer valid,
992  // We should probably regenerate this neighborhood. But for now we
993  // just do a best effort and take the closest value.
994  int64_t closest_value = domain.Min();
995  int64_t closest_dist = std::abs(closest_value - base_value);
996  for (const ClosedInterval interval : domain) {
997  for (const int64_t value : {interval.start, interval.end}) {
998  const int64_t dist = std::abs(value - base_value);
999  if (dist < closest_dist) {
1000  closest_value = value;
1001  closest_dist = dist;
1002  }
1003  }
1004  }
1005  FillDomainInProto(Domain(closest_value, closest_value), new_var);
1006  }
1007  } else {
1008  FillDomainInProto(domain, new_var);
1009  }
1010  }
1011  }
1012 
1013  // Fill some statistic fields and detect if we cover a full component.
1014  //
1015  // TODO(user): If there is just one component, we can skip some computation.
1016  {
1017  absl::ReaderMutexLock graph_lock(&graph_mutex_);
1018  std::vector<int> count(components_.size(), 0);
1019  const int num_variables = neighborhood.delta.variables().size();
1020  for (int var = 0; var < num_variables; ++var) {
1021  const auto& domain = neighborhood.delta.variables(var).domain();
1022  if (domain.size() != 2 || domain[0] != domain[1]) {
1023  ++neighborhood.num_relaxed_variables;
1024  if (is_in_objective_[var]) {
1025  ++neighborhood.num_relaxed_variables_in_objective;
1026  }
1027  const int c = var_to_component_index_[var];
1028  if (c != -1) count[c]++;
1029  }
1030  }
1031 
1032  for (int i = 0; i < components_.size(); ++i) {
1033  if (count[i] == components_[i].size()) {
1036  components_[i].begin(), components_[i].end());
1037  }
1038  }
1039  }
1040 
1041  // If the objective domain might cut the optimal solution, we cannot exploit
1042  // the connected components. We compute this outside the mutex to avoid
1043  // any deadlock risk.
1044  //
1045  // TODO(user): We could handle some complex domain (size > 2).
1046  if (model_proto_.has_objective() &&
1047  (model_proto_.objective().domain().size() != 2 ||
1048  shared_response_->GetInnerObjectiveLowerBound() <
1049  model_proto_.objective().domain(0))) {
1050  neighborhood.variables_that_can_be_fixed_to_local_optimum.clear();
1051  }
1052 
1053  AddSolutionHinting(base_solution, &neighborhood.delta);
1054 
1055  neighborhood.is_generated = true;
1056  neighborhood.is_reduced = !variables_to_fix.empty();
1057  neighborhood.is_simple = true;
1058 
1059  // TODO(user): force better objective? Note that this is already done when the
1060  // hint above is successfully loaded (i.e. if it passes the presolve
1061  // correctly) since the solver will try to find better solution than the
1062  // current one.
1063  return neighborhood;
1064 }
1065 
1067  const CpSolverResponse& initial_solution, CpModelProto* model_proto) const {
1068  // Set the current solution as a hint.
1069  model_proto->clear_solution_hint();
1070  const auto is_fixed = [model_proto](int var) {
1071  const IntegerVariableProto& var_proto = model_proto->variables(var);
1072  return var_proto.domain_size() == 2 &&
1073  var_proto.domain(0) == var_proto.domain(1);
1074  };
1075  for (int var = 0; var < model_proto->variables_size(); ++var) {
1076  if (is_fixed(var)) continue;
1077 
1078  model_proto->mutable_solution_hint()->add_vars(var);
1079  model_proto->mutable_solution_hint()->add_values(
1080  initial_solution.solution(var));
1081  }
1082 }
1083 
1085  const std::vector<int>& constraints_to_remove) const {
1086  Neighborhood neighborhood = FullNeighborhood();
1087 
1088  if (constraints_to_remove.empty()) return neighborhood;
1089  neighborhood.is_reduced = false;
1090  neighborhood.constraints_to_ignore = constraints_to_remove;
1091  return neighborhood;
1092 }
1093 
1095  const CpSolverResponse& initial_solution,
1096  const std::vector<int>& relaxed_variables) const {
1097  std::vector<bool> relaxed_variables_set(model_proto_.variables_size(), false);
1098  for (const int var : relaxed_variables) relaxed_variables_set[var] = true;
1099  absl::flat_hash_set<int> fixed_variables;
1100  {
1101  absl::ReaderMutexLock graph_lock(&graph_mutex_);
1102  for (const int i : active_variables_) {
1103  if (!relaxed_variables_set[i]) {
1104  fixed_variables.insert(i);
1105  }
1106  }
1107  }
1108  return FixGivenVariables(initial_solution, fixed_variables);
1109 }
1110 
1112  const CpSolverResponse& initial_solution) const {
1113  const std::vector<int>& all_variables = ActiveVariables();
1114  const absl::flat_hash_set<int> fixed_variables(all_variables.begin(),
1115  all_variables.end());
1116  return FixGivenVariables(initial_solution, fixed_variables);
1117 }
1118 
1121 }
1122 
1123 double NeighborhoodGenerator::GetUCBScore(int64_t total_num_calls) const {
1124  absl::ReaderMutexLock mutex_lock(&generator_mutex_);
1125  DCHECK_GE(total_num_calls, num_calls_);
1126  if (num_calls_ <= 10) return std::numeric_limits<double>::infinity();
1127  return current_average_ + sqrt((2 * log(total_num_calls)) / num_calls_);
1128 }
1129 
1131  absl::MutexLock mutex_lock(&generator_mutex_);
1132 
1133  // To make the whole update process deterministic, we currently sort the
1134  // SolveData.
1135  std::sort(solve_data_.begin(), solve_data_.end());
1136 
1137  // This will be used to update the difficulty of this neighborhood.
1138  int num_fully_solved_in_batch = 0;
1139  int num_not_fully_solved_in_batch = 0;
1140 
1141  for (const SolveData& data : solve_data_) {
1142  ++num_calls_;
1143 
1144  // INFEASIBLE or OPTIMAL means that we "fully solved" the local problem.
1145  // If we didn't, then we cannot be sure that there is no improving solution
1146  // in that neighborhood.
1147  if (data.status == CpSolverStatus::INFEASIBLE ||
1148  data.status == CpSolverStatus::OPTIMAL) {
1149  ++num_fully_solved_calls_;
1150  ++num_fully_solved_in_batch;
1151  } else {
1152  ++num_not_fully_solved_in_batch;
1153  }
1154 
1155  // It seems to make more sense to compare the new objective to the base
1156  // solution objective, not the best one. However this causes issue in the
1157  // logic below because on some problems the neighborhood can always lead
1158  // to a better "new objective" if the base solution wasn't the best one.
1159  //
1160  // This might not be a final solution, but it does work ok for now.
1161  const IntegerValue best_objective_improvement = IntegerValue(CapSub(
1162  data.initial_best_objective.value(), data.new_objective.value()));
1163  if (best_objective_improvement > 0) {
1164  num_consecutive_non_improving_calls_ = 0;
1165  } else {
1166  ++num_consecutive_non_improving_calls_;
1167  }
1168 
1169  // TODO(user): Weight more recent data.
1170  // degrade the current average to forget old learnings.
1171  const double gain_per_time_unit =
1172  std::max(0.0, static_cast<double>(best_objective_improvement.value())) /
1173  (1.0 + data.deterministic_time);
1174  if (num_calls_ <= 100) {
1175  current_average_ += (gain_per_time_unit - current_average_) / num_calls_;
1176  } else {
1177  current_average_ = 0.9 * current_average_ + 0.1 * gain_per_time_unit;
1178  }
1179 
1180  deterministic_time_ += data.deterministic_time;
1181  }
1182 
1183  // Update the difficulty.
1184  difficulty_.Update(/*num_decreases=*/num_not_fully_solved_in_batch,
1185  /*num_increases=*/num_fully_solved_in_batch);
1186 
1187  // Bump the time limit if we saw no better solution in the last few calls.
1188  // This means that as the search progress, we likely spend more and more time
1189  // trying to solve individual neighborhood.
1190  //
1191  // TODO(user): experiment with resetting the time limit if a solution is
1192  // found.
1193  if (num_consecutive_non_improving_calls_ > 50) {
1194  num_consecutive_non_improving_calls_ = 0;
1195  deterministic_limit_ *= 1.02;
1196 
1197  // We do not want the limit to go to high. Intuitively, the goal is to try
1198  // out a lot of neighborhoods, not just spend a lot of time on a few.
1199  deterministic_limit_ = std::min(60.0, deterministic_limit_);
1200  }
1201 
1202  solve_data_.clear();
1203 }
1204 
1205 namespace {
1206 
1207 template <class T>
1208 void GetRandomSubset(double relative_size, std::vector<T>* base,
1209  absl::BitGenRef random) {
1210  if (base->empty()) return;
1211 
1212  // TODO(user): we could generate this more efficiently than using random
1213  // shuffle.
1214  std::shuffle(base->begin(), base->end(), random);
1215  const int target_size = std::round(relative_size * base->size());
1216  base->resize(target_size);
1217 }
1218 
1219 } // namespace
1220 
1222  const CpSolverResponse& initial_solution, double difficulty,
1223  absl::BitGenRef random) {
1224  std::vector<int> fixed_variables = helper_.ActiveVariables();
1225  GetRandomSubset(1.0 - difficulty, &fixed_variables, random);
1226  return helper_.FixGivenVariables(
1227  initial_solution, {fixed_variables.begin(), fixed_variables.end()});
1228 }
1229 
1231  const CpSolverResponse& initial_solution, double difficulty,
1232  absl::BitGenRef random) {
1234  return helper_.FullNeighborhood();
1235  }
1236 
1237  std::vector<int> relaxed_variables;
1238  {
1239  absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1240  const int num_active_constraints = helper_.ConstraintToVar().size();
1241  std::vector<int> active_constraints(num_active_constraints);
1242  for (int c = 0; c < num_active_constraints; ++c) {
1243  active_constraints[c] = c;
1244  }
1245  std::shuffle(active_constraints.begin(), active_constraints.end(), random);
1246 
1247  const int num_model_vars = helper_.ModelProto().variables_size();
1248  std::vector<bool> visited_variables_set(num_model_vars, false);
1249 
1250  const int num_active_vars =
1252  const int target_size = std::ceil(difficulty * num_active_vars);
1253  DCHECK_GT(target_size, 0);
1254 
1255  for (const int constraint_index : active_constraints) {
1256  for (const int var : helper_.ConstraintToVar()[constraint_index]) {
1257  if (visited_variables_set[var]) continue;
1258  visited_variables_set[var] = true;
1259  if (helper_.IsActive(var)) {
1260  relaxed_variables.push_back(var);
1261  if (relaxed_variables.size() == target_size) break;
1262  }
1263  }
1264  if (relaxed_variables.size() == target_size) break;
1265  }
1266  }
1267 
1268  return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1269 }
1270 
1271 // Note that even if difficulty means full neighborhood, we go through the
1272 // generation process to never get out of a connected components.
1274  const CpSolverResponse& initial_solution, double difficulty,
1275  absl::BitGenRef random) {
1276  const int num_model_vars = helper_.ModelProto().variables_size();
1277  std::vector<bool> visited_variables_set(num_model_vars, false);
1278  std::vector<int> relaxed_variables;
1279  std::vector<int> visited_variables;
1280 
1281  // It is important complexity wise to never scan a constraint twice!
1282  const int num_model_constraints = helper_.ModelProto().constraints_size();
1283  std::vector<bool> scanned_constraints(num_model_constraints, false);
1284 
1285  std::vector<int> random_variables;
1286  {
1287  absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1288 
1289  // The number of active variables can decrease asynchronously.
1290  // We read the exact number while locked.
1291  const int num_active_vars =
1293  const int target_size = std::ceil(difficulty * num_active_vars);
1294  if (target_size == 0) return helper_.FullNeighborhood();
1295 
1296  const int first_var =
1297  helper_.ActiveVariablesWhileHoldingLock()[absl::Uniform<int>(
1298  random, 0, num_active_vars)];
1299 
1300  visited_variables_set[first_var] = true;
1301  visited_variables.push_back(first_var);
1302  relaxed_variables.push_back(first_var);
1303 
1304  for (int i = 0; i < visited_variables.size(); ++i) {
1305  random_variables.clear();
1306  // Collect all the variables that appears in the same constraints as
1307  // visited_variables[i].
1308  for (const int ct : helper_.VarToConstraint()[visited_variables[i]]) {
1309  if (scanned_constraints[ct]) continue;
1310  scanned_constraints[ct] = true;
1311  for (const int var : helper_.ConstraintToVar()[ct]) {
1312  if (visited_variables_set[var]) continue;
1313  visited_variables_set[var] = true;
1314  random_variables.push_back(var);
1315  }
1316  }
1317  // We always randomize to change the partial subgraph explored
1318  // afterwards.
1319  std::shuffle(random_variables.begin(), random_variables.end(), random);
1320  for (const int var : random_variables) {
1321  if (relaxed_variables.size() < target_size) {
1322  visited_variables.push_back(var);
1323  if (helper_.IsActive(var)) {
1324  relaxed_variables.push_back(var);
1325  }
1326  } else {
1327  break;
1328  }
1329  }
1330  if (relaxed_variables.size() >= target_size) break;
1331  }
1332  }
1333 
1334  return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1335 }
1336 
1337 // Note that even if difficulty means full neighborhood, we go through the
1338 // generation process to never get out of a connected components.
1340  const CpSolverResponse& initial_solution, double difficulty,
1341  absl::BitGenRef random) {
1342  const int num_model_constraints = helper_.ModelProto().constraints_size();
1343  if (num_model_constraints == 0) {
1344  return helper_.FullNeighborhood();
1345  }
1346 
1347  const int num_model_vars = helper_.ModelProto().variables_size();
1348  std::vector<bool> visited_variables_set(num_model_vars, false);
1349  std::vector<int> relaxed_variables;
1350 
1351  std::vector<bool> added_constraints(num_model_constraints, false);
1352  std::vector<int> next_constraints;
1353 
1354  std::vector<int> random_variables;
1355  {
1356  absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1357  const int num_active_vars =
1359  const int target_size = std::ceil(difficulty * num_active_vars);
1360  if (target_size == 0) return helper_.FullNeighborhood();
1361 
1362  // Start by a random constraint.
1363  const int num_active_constraints = helper_.ConstraintToVar().size();
1364  if (num_active_constraints != 0) {
1365  next_constraints.push_back(
1366  absl::Uniform<int>(random, 0, num_active_constraints));
1367  added_constraints[next_constraints.back()] = true;
1368  }
1369 
1370  while (relaxed_variables.size() < target_size) {
1371  // Stop if we have a full connected component.
1372  if (next_constraints.empty()) break;
1373 
1374  // Pick a random unprocessed constraint.
1375  const int i = absl::Uniform<int>(random, 0, next_constraints.size());
1376  const int constraint_index = next_constraints[i];
1377  std::swap(next_constraints[i], next_constraints.back());
1378  next_constraints.pop_back();
1379 
1380  // Add all the variable of this constraint and increase the set of next
1381  // possible constraints.
1382  DCHECK_LT(constraint_index, num_active_constraints);
1383  random_variables = helper_.ConstraintToVar()[constraint_index];
1384  std::shuffle(random_variables.begin(), random_variables.end(), random);
1385  for (const int var : random_variables) {
1386  if (visited_variables_set[var]) continue;
1387  visited_variables_set[var] = true;
1388  if (helper_.IsActive(var)) {
1389  relaxed_variables.push_back(var);
1390  }
1391  if (relaxed_variables.size() == target_size) break;
1392 
1393  for (const int ct : helper_.VarToConstraint()[var]) {
1394  if (added_constraints[ct]) continue;
1395  added_constraints[ct] = true;
1396  next_constraints.push_back(ct);
1397  }
1398  }
1399  }
1400  }
1401 
1402  return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
1403 }
1404 
1406  const CpSolverResponse& initial_solution, double difficulty,
1407  absl::BitGenRef random) {
1408  std::vector<int> fixed_variables = helper_.ActiveObjectiveVariables();
1409  GetRandomSubset(1.0 - difficulty, &fixed_variables, random);
1410  return helper_.FixGivenVariables(
1411  initial_solution, {fixed_variables.begin(), fixed_variables.end()});
1412 }
1413 
1414 namespace {
1415 
1416 void AddPrecedence(const LinearExpressionProto& before,
1417  const LinearExpressionProto& after, CpModelProto* model) {
1418  LinearConstraintProto* linear = model->add_constraints()->mutable_linear();
1419  linear->add_domain(std::numeric_limits<int64_t>::min());
1420  linear->add_domain(after.offset() - before.offset());
1421  for (int i = 0; i < before.vars_size(); ++i) {
1422  linear->add_vars(before.vars(i));
1423  linear->add_coeffs(before.coeffs(i));
1424  }
1425  for (int i = 0; i < after.vars_size(); ++i) {
1426  linear->add_vars(after.vars(i));
1427  linear->add_coeffs(-after.coeffs(i));
1428  }
1429 }
1430 
1431 } // namespace
1432 
1434  const absl::Span<const std::pair<int, int>> precedences,
1435  const CpSolverResponse& initial_solution,
1436  const NeighborhoodGeneratorHelper& helper) {
1437  Neighborhood neighborhood = helper.FullNeighborhood();
1438 
1439  neighborhood.is_reduced = !precedences.empty();
1440  if (!neighborhood.is_reduced) { // Returns the full neighborhood.
1441  helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
1442  neighborhood.is_generated = true;
1443  return neighborhood;
1444  }
1445 
1446  // Collect seen intervals.
1447  absl::flat_hash_set<int> seen_intervals;
1448  for (const std::pair<int, int>& prec : precedences) {
1449  seen_intervals.insert(prec.first);
1450  seen_intervals.insert(prec.second);
1451  }
1452 
1453  // Fix the presence/absence of unseen intervals.
1454  bool enforcement_literals_fixed = false;
1455  for (const int i : helper.TypeToConstraints(ConstraintProto::kInterval)) {
1456  if (seen_intervals.contains(i)) continue;
1457 
1458  const ConstraintProto& interval_ct = helper.ModelProto().constraints(i);
1459  if (interval_ct.enforcement_literal().empty()) continue;
1460 
1461  DCHECK_EQ(interval_ct.enforcement_literal().size(), 1);
1462  const int enforcement_ref = interval_ct.enforcement_literal(0);
1463  const int enforcement_var = PositiveRef(enforcement_ref);
1464  const int value = initial_solution.solution(enforcement_var);
1465 
1466  // If the interval is not enforced, we just relax it. If it belongs to an
1467  // exactly one constraint, and the enforced interval is not relaxed, then
1468  // propagation will force this interval to stay not enforced. Otherwise,
1469  // LNS will be able to change which interval will be enforced among all
1470  // alternatives.
1471  if (RefIsPositive(enforcement_ref) == (value == 0)) continue;
1472 
1473  // Fix the value.
1474  neighborhood.delta.mutable_variables(enforcement_var)->clear_domain();
1475  neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
1476  neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
1477  enforcement_literals_fixed = true;
1478  }
1479 
1480  for (const std::pair<int, int>& prec : precedences) {
1481  const LinearExpressionProto& before_end =
1482  helper.ModelProto().constraints(prec.first).interval().end();
1483  const LinearExpressionProto& after_start =
1484  helper.ModelProto().constraints(prec.second).interval().start();
1485  DCHECK_LE(GetLinearExpressionValue(before_end, initial_solution),
1486  GetLinearExpressionValue(after_start, initial_solution));
1487  AddPrecedence(before_end, after_start, &neighborhood.delta);
1488  }
1489 
1490  // Set the current solution as a hint.
1491  helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
1492  neighborhood.is_generated = true;
1493 
1494  return neighborhood;
1495 }
1496 
1498  const absl::Span<const int> intervals_to_relax,
1499  const CpSolverResponse& initial_solution, absl::BitGenRef random,
1500  const NeighborhoodGeneratorHelper& helper) {
1501  Neighborhood neighborhood = helper.FullNeighborhood();
1502 
1503  // We will extend the set with some interval that we cannot fix.
1504  absl::flat_hash_set<int> ignored_intervals(intervals_to_relax.begin(),
1505  intervals_to_relax.end());
1506 
1507  // Fix the presence/absence of non-relaxed intervals.
1508  for (const int i : helper.TypeToConstraints(ConstraintProto::kInterval)) {
1509  DCHECK_GE(i, 0);
1510  if (ignored_intervals.contains(i)) continue;
1511 
1512  const ConstraintProto& interval_ct = helper.ModelProto().constraints(i);
1513  if (interval_ct.enforcement_literal().empty()) continue;
1514 
1515  DCHECK_EQ(interval_ct.enforcement_literal().size(), 1);
1516  const int enforcement_ref = interval_ct.enforcement_literal(0);
1517  const int enforcement_var = PositiveRef(enforcement_ref);
1518  const int value = initial_solution.solution(enforcement_var);
1519 
1520  // If the interval is not enforced, we just relax it. If it belongs to an
1521  // exactly one constraint, and the enforced interval is not relaxed, then
1522  // propagation will force this interval to stay not enforced. Otherwise,
1523  // LNS will be able to change which interval will be enforced among all
1524  // alternatives.
1525  if (RefIsPositive(enforcement_ref) == (value == 0)) {
1526  ignored_intervals.insert(i);
1527  continue;
1528  }
1529 
1530  // Fix the value.
1531  neighborhood.delta.mutable_variables(enforcement_var)->clear_domain();
1532  neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
1533  neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
1534  }
1535 
1536  if (ignored_intervals.size() >=
1537  helper.TypeToConstraints(ConstraintProto::kInterval)
1538  .size()) { // Returns the full neighborhood.
1539  helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
1540  neighborhood.is_generated = true;
1541  return neighborhood;
1542  }
1543 
1544  neighborhood.is_reduced = true;
1545 
1546  // We differ from the ICAPS05 paper as we do not consider ignored intervals
1547  // when generating the precedence graph, instead of building the full graph,
1548  // then removing intervals, and reconstructing the precedence graph
1549  // heuristically after that.
1550  const std::vector<std::pair<int, int>> precedences =
1551  helper.GetSchedulingPrecedences(ignored_intervals, initial_solution,
1552  random);
1553  for (const std::pair<int, int>& prec : precedences) {
1554  const LinearExpressionProto& before_end =
1555  helper.ModelProto().constraints(prec.first).interval().end();
1556  const LinearExpressionProto& after_start =
1557  helper.ModelProto().constraints(prec.second).interval().start();
1558  DCHECK_LE(GetLinearExpressionValue(before_end, initial_solution),
1559  GetLinearExpressionValue(after_start, initial_solution));
1560  AddPrecedence(before_end, after_start, &neighborhood.delta);
1561  }
1562 
1563  // Set the current solution as a hint.
1564  helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
1565  neighborhood.is_generated = true;
1566 
1567  return neighborhood;
1568 }
1569 
1571  const CpSolverResponse& initial_solution, double difficulty,
1572  absl::BitGenRef random) {
1573  std::vector<int> intervals_to_relax =
1574  helper_.GetActiveIntervals(initial_solution);
1575  GetRandomSubset(difficulty, &intervals_to_relax, random);
1576 
1578  intervals_to_relax, initial_solution, random, helper_);
1579 }
1580 
1582  const CpSolverResponse& initial_solution, double difficulty,
1583  absl::BitGenRef random) {
1584  std::vector<std::pair<int, int>> precedences =
1585  helper_.GetSchedulingPrecedences({}, initial_solution, random);
1586  GetRandomSubset(1.0 - difficulty, &precedences, random);
1588  precedences, initial_solution, helper_);
1589 }
1590 
1592  const CpSolverResponse& initial_solution, double difficulty,
1593  absl::BitGenRef random) {
1594  const std::vector<int> active_intervals =
1595  helper_.GetActiveIntervals(initial_solution);
1596 
1597  if (active_intervals.empty()) return helper_.FullNeighborhood();
1598 
1599  const std::vector<int> intervals_to_relax =
1600  SelectIntervalsInRandomTimeWindow(active_intervals, helper_.ModelProto(),
1601  initial_solution, difficulty, random);
1603  intervals_to_relax, initial_solution, random, helper_);
1604 }
1605 
1607  const CpSolverResponse& initial_solution, double difficulty,
1608  absl::BitGenRef random) {
1609  intervals_to_relax_.clear();
1610  for (const std::vector<int>& intervals : intervals_in_constraints_) {
1611  const std::vector<int> selected = SelectIntervalsInRandomTimeWindow(
1612  intervals, helper_.ModelProto(), initial_solution, difficulty, random);
1613  intervals_to_relax_.insert(selected.begin(), selected.end());
1614  }
1615 
1616  if (intervals_to_relax_.empty()) return helper_.FullNeighborhood();
1617 
1618  const std::vector<int> intervals(
1619  {intervals_to_relax_.begin(), intervals_to_relax_.end()});
1621  intervals, initial_solution, random, helper_);
1622 }
1623 
1625  const CpSolverResponse& initial_solution, double difficulty,
1626  absl::BitGenRef random) {
1627  const std::vector<std::vector<int>> all_paths =
1628  helper_.GetRoutingPaths(initial_solution);
1629 
1630  // Collect all unique variables.
1631  absl::flat_hash_set<int> all_path_variables;
1632  for (auto& path : all_paths) {
1633  all_path_variables.insert(path.begin(), path.end());
1634  }
1635  std::vector<int> fixed_variables(all_path_variables.begin(),
1636  all_path_variables.end());
1637  std::sort(fixed_variables.begin(), fixed_variables.end());
1638  GetRandomSubset(1.0 - difficulty, &fixed_variables, random);
1639  return helper_.FixGivenVariables(
1640  initial_solution, {fixed_variables.begin(), fixed_variables.end()});
1641 }
1642 
1644  const CpSolverResponse& initial_solution, double difficulty,
1645  absl::BitGenRef random) {
1646  std::vector<std::vector<int>> all_paths =
1647  helper_.GetRoutingPaths(initial_solution);
1648 
1649  // Collect all unique variables.
1650  absl::flat_hash_set<int> all_path_variables;
1651  for (const auto& path : all_paths) {
1652  all_path_variables.insert(path.begin(), path.end());
1653  }
1654 
1655  // Select variables to relax.
1656  const int num_variables_to_relax =
1657  static_cast<int>(all_path_variables.size() * difficulty);
1658  absl::flat_hash_set<int> relaxed_variables;
1659  while (relaxed_variables.size() < num_variables_to_relax) {
1660  DCHECK(!all_paths.empty());
1661  const int path_index = absl::Uniform<int>(random, 0, all_paths.size());
1662  std::vector<int>& path = all_paths[path_index];
1663  const int path_size = path.size();
1664  const int segment_length =
1665  std::min(path_size, absl::Uniform<int>(random, 4, 8));
1666  const int segment_start =
1667  absl::Uniform<int>(random, 0, path_size - segment_length);
1668  for (int i = segment_start; i < segment_start + segment_length; ++i) {
1669  relaxed_variables.insert(path[i]);
1670  }
1671 
1672  // Remove segment and clean up empty paths.
1673  path.erase(path.begin() + segment_start,
1674  path.begin() + segment_start + segment_length);
1675  if (path.empty()) {
1676  std::swap(all_paths[path_index], all_paths.back());
1677  all_paths.pop_back();
1678  }
1679  }
1680 
1681  // Compute the set of variables to fix.
1682  absl::flat_hash_set<int> fixed_variables;
1683  for (const int var : all_path_variables) {
1684  if (!relaxed_variables.contains(var)) fixed_variables.insert(var);
1685  }
1686  return helper_.FixGivenVariables(initial_solution, fixed_variables);
1687 }
1688 
1690  const CpSolverResponse& initial_solution, double difficulty,
1691  absl::BitGenRef random) {
1692  std::vector<std::vector<int>> all_paths =
1693  helper_.GetRoutingPaths(initial_solution);
1694  // Remove a corner case where all paths are empty.
1695  if (all_paths.empty()) {
1696  return helper_.NoNeighborhood();
1697  }
1698 
1699  // Collect all unique variables.
1700  absl::flat_hash_set<int> all_path_variables;
1701  for (const auto& path : all_paths) {
1702  all_path_variables.insert(path.begin(), path.end());
1703  }
1704 
1705  // Select variables to relax.
1706  const int num_variables_to_relax =
1707  static_cast<int>(all_path_variables.size() * difficulty);
1708  absl::flat_hash_set<int> relaxed_variables;
1709 
1710  // Relax the start and end of each path to ease relocation.
1711  for (const auto& path : all_paths) {
1712  relaxed_variables.insert(path.front());
1713  relaxed_variables.insert(path.back());
1714  }
1715 
1716  // Randomize paths.
1717  for (auto& path : all_paths) {
1718  std::shuffle(path.begin(), path.end(), random);
1719  }
1720 
1721  // Relax all variables (if possible) in one random path.
1722  const int path_to_clean = absl::Uniform<int>(random, 0, all_paths.size());
1723  while (relaxed_variables.size() < num_variables_to_relax &&
1724  !all_paths[path_to_clean].empty()) {
1725  relaxed_variables.insert(all_paths[path_to_clean].back());
1726  all_paths[path_to_clean].pop_back();
1727  }
1728  if (all_paths[path_to_clean].empty()) {
1729  std::swap(all_paths[path_to_clean], all_paths.back());
1730  all_paths.pop_back();
1731  }
1732 
1733  // Relax more variables until the target is reached.
1734  while (relaxed_variables.size() < num_variables_to_relax) {
1735  DCHECK(!all_paths.empty());
1736  const int path_index = absl::Uniform<int>(random, 0, all_paths.size());
1737  relaxed_variables.insert(all_paths[path_index].back());
1738 
1739  // Remove variable and clean up empty paths.
1740  all_paths[path_index].pop_back();
1741  if (all_paths[path_index].empty()) {
1742  std::swap(all_paths[path_index], all_paths.back());
1743  all_paths.pop_back();
1744  }
1745  }
1746 
1747  // Compute the set of variables to fix.
1748  absl::flat_hash_set<int> fixed_variables;
1749  for (const int var : all_path_variables) {
1750  if (!relaxed_variables.contains(var)) fixed_variables.insert(var);
1751  }
1752  return helper_.FixGivenVariables(initial_solution, fixed_variables);
1753 }
1754 
1756  if (incomplete_solutions_ != nullptr) {
1757  return incomplete_solutions_->HasNewSolution();
1758  }
1759 
1760  if (response_manager_ != nullptr) {
1761  if (response_manager_->SolutionsRepository().NumSolutions() == 0) {
1762  return false;
1763  }
1764  }
1765 
1766  // At least one relaxation solution should be available to generate a
1767  // neighborhood.
1768  if (lp_solutions_ != nullptr && lp_solutions_->NumSolutions() > 0) {
1769  return true;
1770  }
1771 
1772  if (relaxation_solutions_ != nullptr &&
1773  relaxation_solutions_->NumSolutions() > 0) {
1774  return true;
1775  }
1776  return false;
1777 }
1778 
1780  const CpSolverResponse& /*initial_solution*/, double /*difficulty*/,
1781  absl::BitGenRef random) {
1782  Neighborhood neighborhood = helper_.FullNeighborhood();
1783  neighborhood.is_generated = false;
1784 
1785  const bool lp_solution_available =
1786  (lp_solutions_ != nullptr && lp_solutions_->NumSolutions() > 0);
1787 
1788  const bool relaxation_solution_available =
1789  (relaxation_solutions_ != nullptr &&
1790  relaxation_solutions_->NumSolutions() > 0);
1791 
1792  const bool incomplete_solution_available =
1793  (incomplete_solutions_ != nullptr &&
1794  incomplete_solutions_->HasNewSolution());
1795 
1796  if (!lp_solution_available && !relaxation_solution_available &&
1797  !incomplete_solution_available) {
1798  return neighborhood;
1799  }
1800 
1801  RINSNeighborhood rins_neighborhood;
1802  // Randomly select the type of relaxation if both lp and relaxation solutions
1803  // are available.
1804  // TODO(user): Tune the probability value for this.
1805  std::bernoulli_distribution random_bool(0.5);
1806  const bool use_lp_relaxation =
1807  (lp_solution_available && relaxation_solution_available)
1808  ? random_bool(random)
1809  : lp_solution_available;
1810  if (use_lp_relaxation) {
1811  rins_neighborhood =
1812  GetRINSNeighborhood(response_manager_,
1813  /*relaxation_solutions=*/nullptr, lp_solutions_,
1814  incomplete_solutions_, random);
1815  neighborhood.source_info =
1816  incomplete_solution_available ? "incomplete" : "lp";
1817  } else {
1818  CHECK(relaxation_solution_available || incomplete_solution_available);
1819  rins_neighborhood = GetRINSNeighborhood(
1820  response_manager_, relaxation_solutions_,
1821  /*lp_solutions=*/nullptr, incomplete_solutions_, random);
1822  neighborhood.source_info =
1823  incomplete_solution_available ? "incomplete" : "relaxation";
1824  }
1825 
1826  if (rins_neighborhood.fixed_vars.empty() &&
1827  rins_neighborhood.reduced_domain_vars.empty()) {
1828  return neighborhood;
1829  }
1830 
1831  absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
1832  // Fix the variables in the local model.
1833  for (const std::pair</*model_var*/ int, /*value*/ int64_t>& fixed_var :
1834  rins_neighborhood.fixed_vars) {
1835  const int var = fixed_var.first;
1836  const int64_t value = fixed_var.second;
1837  if (var >= neighborhood.delta.variables_size()) continue;
1838  if (!helper_.IsActive(var)) continue;
1839 
1840  if (!DomainInProtoContains(neighborhood.delta.variables(var), value)) {
1841  // TODO(user): Instead of aborting, pick the closest point in the domain?
1842  return neighborhood;
1843  }
1844 
1845  neighborhood.delta.mutable_variables(var)->clear_domain();
1846  neighborhood.delta.mutable_variables(var)->add_domain(value);
1847  neighborhood.delta.mutable_variables(var)->add_domain(value);
1848  neighborhood.is_reduced = true;
1849  }
1850 
1851  for (const std::pair</*model_var*/ int,
1852  /*domain*/ std::pair<int64_t, int64_t>>& reduced_var :
1853  rins_neighborhood.reduced_domain_vars) {
1854  const int var = reduced_var.first;
1855  const int64_t lb = reduced_var.second.first;
1856  const int64_t ub = reduced_var.second.second;
1857  if (var >= neighborhood.delta.variables_size()) continue;
1858  if (!helper_.IsActive(var)) continue;
1859  Domain domain = ReadDomainFromProto(neighborhood.delta.variables(var));
1860  domain = domain.IntersectionWith(Domain(lb, ub));
1861  if (domain.IsEmpty()) {
1862  // TODO(user): Instead of aborting, pick the closest point in the
1863  // domain?
1864  return neighborhood;
1865  }
1866  FillDomainInProto(domain, neighborhood.delta.mutable_variables(var));
1867  neighborhood.is_reduced = true;
1868  }
1869  neighborhood.is_generated = true;
1870  return neighborhood;
1871 }
1872 
1873 } // namespace sat
1874 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
bool AddEdge(int node1, int node2)
void Update(int num_decreases, int num_increases)
We call domain any subset of Int64 = [kint64min, kint64max].
bool IsIncludedIn(const Domain &domain) const
Returns true iff D is included in the given domain.
bool Contains(int64_t value) const
Returns true iff value is in Domain.
bool IsFixed() const
Returns true iff the domain is reduced to a single value.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
int64_t Min() const
Returns the min value of the domain.
bool IsEmpty() const
Returns true if this is the empty set.
int64_t Max() const
Returns the max value of the domain.
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
const std::vector< std::vector< int > > & VarToConstraint() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
Definition: cp_model_lns.h:190
const SharedResponseManager & shared_response() const
Definition: cp_model_lns.h:233
std::vector< std::pair< int, int > > GetSchedulingPrecedences(const absl::flat_hash_set< int > &ignored_intervals, const CpSolverResponse &initial_solution, absl::BitGenRef random) const
Neighborhood FixAllVariables(const CpSolverResponse &initial_solution) const
Neighborhood FixGivenVariables(const CpSolverResponse &base_solution, const absl::flat_hash_set< int > &variables_to_fix) const
const absl::Span< const int > TypeToConstraints(ConstraintProto::ConstraintCase type) const
Definition: cp_model_lns.h:196
bool DifficultyMeansFullNeighborhood(double difficulty) const
Definition: cp_model_lns.h:171
Neighborhood RelaxGivenVariables(const CpSolverResponse &initial_solution, const std::vector< int > &relaxed_variables) const
std::vector< int > GetActiveIntervals(const CpSolverResponse &initial_solution) const
const std::vector< std::vector< int > > & ConstraintToVar() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
Definition: cp_model_lns.h:186
std::vector< std::vector< int > > GetUniqueIntervalSets() const
NeighborhoodGeneratorHelper(CpModelProto const *model_proto, SatParameters const *parameters, SharedResponseManager *shared_response, SharedBoundsManager *shared_bounds=nullptr)
Definition: cp_model_lns.cc:62
bool IsActive(int var) const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
Neighborhood RemoveMarkedConstraints(const std::vector< int > &constraints_to_remove) const
void AddSolutionHinting(const CpSolverResponse &initial_solution, CpModelProto *model_proto) const
const std::vector< int > & ActiveVariablesWhileHoldingLock() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
Definition: cp_model_lns.h:179
std::vector< std::vector< int > > GetRoutingPaths(const CpSolverResponse &initial_solution) const
double GetUCBScore(int64_t total_num_calls) const
const NeighborhoodGeneratorHelper & helper_
Definition: cp_model_lns.h:440
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
void GetChangedBounds(int id, std::vector< int > *variables, std::vector< int64_t > *new_lower_bounds, std::vector< int64_t > *new_upper_bounds)
void LogPeriodicMessage(const std::string &prefix, const std::string &message, double frequency_seconds, absl::Time *last_logging_time)
const SharedSolutionRepository< int64_t > & SolutionsRepository() const
SubsolverType type() const
Definition: subsolver.h:92
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
int64_t b
int64_t a
SatParameters parameters
int64_t y_end
int64_t y_start
int64_t x_end
int64_t start
int interval_index
int64_t end
int64_t x_start
int64_t height
CpModelProto const * model_proto
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
GurobiMPCallbackContext * context
int index
const bool DEBUG_MODE
Definition: macros.h:24
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
std::vector< int > UsedVariables(const ConstraintProto &ct)
bool RefIsPositive(int ref)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
Neighborhood GenerateSchedulingNeighborhoodFromRelaxedIntervals(const absl::Span< const int > intervals_to_relax, const CpSolverResponse &initial_solution, absl::BitGenRef random, const NeighborhoodGeneratorHelper &helper)
bool DomainInProtoContains(const ProtoWithDomain &proto, int64_t value)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Neighborhood GenerateSchedulingNeighborhoodFromIntervalPrecedences(const absl::Span< const std::pair< int, int >> precedences, const CpSolverResponse &initial_solution, const NeighborhoodGeneratorHelper &helper)
RINSNeighborhood GetRINSNeighborhood(const SharedResponseManager *response_manager, const SharedRelaxationSolutionRepository *relaxation_solutions, const SharedLPSolutionRepository *lp_solutions, SharedIncompleteSolutionManager *incomplete_solutions, absl::BitGenRef random)
Definition: rins.cc:107
Collection of objects used to extend the Constraint Solver library.
int64_t CapSub(int64_t x, int64_t y)
Literal literal
Definition: optimization.cc:88
int64_t demand
Definition: resource.cc:126
IntervalVar * interval
Definition: resource.cc:101
int64_t capacity
int64_t tail
int64_t head
Represents a closed interval [start, end].
std::vector< int > variables_that_can_be_fixed_to_local_optimum
Definition: cp_model_lns.h:91
std::vector< int > constraints_to_ignore
Definition: cp_model_lns.h:66
std::vector< std::pair< int, int64_t > > fixed_vars
Definition: rins.h:61
std::vector< std::pair< int, std::pair< int64_t, int64_t > > > reduced_domain_vars
Definition: rins.h:64
#define VLOG(verboselevel)
Definition: vlog.h:39
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:47