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