OR-Tools  9.0
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 <cstdint>
17 #include <limits>
18 #include <numeric>
19 #include <vector>
20 
21 #include "absl/synchronization/mutex.h"
25 #include "ortools/sat/integer.h"
27 #include "ortools/sat/rins.h"
30 
31 namespace operations_research {
32 namespace sat {
33 
35  CpModelProto const* model_proto, SatParameters const* parameters,
36  SharedResponseManager* shared_response, SharedTimeLimit* shared_time_limit,
37  SharedBoundsManager* shared_bounds)
38  : SubSolver(""),
39  parameters_(*parameters),
40  model_proto_(*model_proto),
41  shared_time_limit_(shared_time_limit),
42  shared_bounds_(shared_bounds),
43  shared_response_(shared_response) {
44  CHECK(shared_response_ != nullptr);
45  if (shared_bounds_ != nullptr) {
46  shared_bounds_id_ = shared_bounds_->RegisterNewId();
47  }
48  *model_proto_with_only_variables_.mutable_variables() =
49  model_proto_.variables();
50  RecomputeHelperData();
51  Synchronize();
52 }
53 
55  if (shared_bounds_ != nullptr) {
56  std::vector<int> model_variables;
57  std::vector<int64_t> new_lower_bounds;
58  std::vector<int64_t> new_upper_bounds;
59  shared_bounds_->GetChangedBounds(shared_bounds_id_, &model_variables,
60  &new_lower_bounds, &new_upper_bounds);
61 
62  bool new_variables_have_been_fixed = false;
63 
64  {
65  absl::MutexLock domain_lock(&domain_mutex_);
66 
67  for (int i = 0; i < model_variables.size(); ++i) {
68  const int var = model_variables[i];
69  const int64_t new_lb = new_lower_bounds[i];
70  const int64_t new_ub = new_upper_bounds[i];
71  if (VLOG_IS_ON(3)) {
72  const auto& domain =
73  model_proto_with_only_variables_.variables(var).domain();
74  const int64_t old_lb = domain.Get(0);
75  const int64_t old_ub = domain.Get(domain.size() - 1);
76  VLOG(3) << "Variable: " << var << " old domain: [" << old_lb << ", "
77  << old_ub << "] new domain: [" << new_lb << ", " << new_ub
78  << "]";
79  }
80  const Domain old_domain = ReadDomainFromProto(
81  model_proto_with_only_variables_.variables(var));
82  const Domain new_domain =
83  old_domain.IntersectionWith(Domain(new_lb, new_ub));
84  if (new_domain.IsEmpty()) {
85  // This can mean two things:
86  // 1/ This variable is a normal one and the problem is UNSAT or
87  // 2/ This variable is optional, and its associated literal must be
88  // set to false.
89  //
90  // Currently, we wait for any full solver to pick the crossing bounds
91  // and do the correct stuff on their own. We do not want to have empty
92  // domain in the proto as this would means INFEASIBLE. So we just
93  // ignore such bounds here.
94  //
95  // TODO(user): We could set the optional literal to false directly in
96  // the bound sharing manager. We do have to be careful that all the
97  // different solvers have the same optionality definition though.
98  continue;
99  }
101  new_domain,
102  model_proto_with_only_variables_.mutable_variables(var));
103  new_variables_have_been_fixed |= new_domain.IsFixed();
104  }
105  }
106 
107  // Only trigger the computation if needed.
108  if (new_variables_have_been_fixed) {
109  RecomputeHelperData();
110  }
111  }
112 }
113 
114 void NeighborhoodGeneratorHelper::RecomputeHelperData() {
115  // Recompute all the data in case new variables have been fixed.
116  //
117  // TODO(user): Ideally we should ignore trivially true/false constraint, but
118  // this will duplicate already existing code :-( we should probably still do
119  // at least enforcement literal and clauses? We could maybe run a light
120  // presolve?
121  absl::MutexLock graph_lock(&graph_mutex_);
122  absl::ReaderMutexLock domain_lock(&domain_mutex_);
123 
124  var_to_constraint_.assign(model_proto_.variables_size(), {});
125  constraint_to_var_.assign(model_proto_.constraints_size(), {});
126 
127  for (int ct_index = 0; ct_index < model_proto_.constraints_size();
128  ++ct_index) {
129  for (const int var : UsedVariables(model_proto_.constraints(ct_index))) {
131  if (IsConstant(var)) continue;
132  var_to_constraint_[var].push_back(ct_index);
133  constraint_to_var_[ct_index].push_back(var);
134  }
135 
136  // We replace intervals by their underlying integer variables.
137  if (parameters_.lns_expand_intervals_in_constraint_graph()) {
138  for (const int interval :
139  UsedIntervals(model_proto_.constraints(ct_index))) {
140  for (const int var :
141  UsedVariables(model_proto_.constraints(interval))) {
143  if (IsConstant(var)) continue;
144  var_to_constraint_[var].push_back(ct_index);
145  constraint_to_var_[ct_index].push_back(var);
146  }
147  }
148  }
149  }
150 
151  type_to_constraints_.clear();
152  const int num_constraints = model_proto_.constraints_size();
153  for (int c = 0; c < num_constraints; ++c) {
154  const int type = model_proto_.constraints(c).constraint_case();
155  if (type >= type_to_constraints_.size()) {
156  type_to_constraints_.resize(type + 1);
157  }
158  type_to_constraints_[type].push_back(c);
159  }
160 
161  active_variables_.clear();
162  active_variables_set_.assign(model_proto_.variables_size(), false);
163 
164  if (parameters_.lns_focus_on_decision_variables()) {
165  for (const auto& search_strategy : model_proto_.search_strategy()) {
166  for (const int var : search_strategy.variables()) {
167  const int pos_var = PositiveRef(var);
168  if (!active_variables_set_[pos_var] && !IsConstant(pos_var)) {
169  active_variables_set_[pos_var] = true;
170  active_variables_.push_back(pos_var);
171  }
172  }
173  }
174 
175  // Revert to no focus if active_variables_ is empty().
176  if (!active_variables_.empty()) return;
177  }
178 
179  // Add all non-constant variables.
180  for (int i = 0; i < model_proto_.variables_size(); ++i) {
181  if (!IsConstant(i)) {
182  active_variables_.push_back(i);
183  active_variables_set_[i] = true;
184  }
185  }
186 }
187 
189  return active_variables_set_[var];
190 }
191 
192 bool NeighborhoodGeneratorHelper::IsConstant(int var) const {
193  return model_proto_with_only_variables_.variables(var).domain_size() == 2 &&
194  model_proto_with_only_variables_.variables(var).domain(0) ==
195  model_proto_with_only_variables_.variables(var).domain(1);
196 }
197 
199  const CpModelProto& source_model,
200  const absl::flat_hash_set<int>& fixed_variables_set,
201  const CpSolverResponse& initial_solution,
202  CpModelProto* output_model) const {
203  output_model->mutable_variables()->Clear();
204  output_model->mutable_variables()->Reserve(source_model.variables_size());
205  for (int i = 0; i < source_model.variables_size(); ++i) {
206  IntegerVariableProto* var_proto = output_model->add_variables();
207  const IntegerVariableProto& source_var_proto = source_model.variables(i);
208  // We only copy the variable names in debug mode.
209  if (DEBUG_MODE && !source_var_proto.name().empty()) {
210  var_proto->set_name(source_var_proto.name());
211  }
212  if (fixed_variables_set.contains(i)) {
213  const int64_t value = initial_solution.solution(i);
214  if (!DomainInProtoContains(source_model.variables(i), value)) {
215  return false;
216  }
217  var_proto->add_domain(value);
218  var_proto->add_domain(value);
219  } else {
220  *var_proto->mutable_domain() = source_var_proto.domain();
221  }
222  }
223  return true;
224 }
225 
227  Neighborhood neighborhood;
228  neighborhood.is_reduced = false;
229  neighborhood.is_generated = true;
230  {
231  absl::ReaderMutexLock lock(&domain_mutex_);
232  *neighborhood.delta.mutable_variables() =
233  model_proto_with_only_variables_.variables();
234  }
235  return neighborhood;
236 }
237 
239  const CpSolverResponse& initial_solution) const {
240  std::vector<int> active_intervals;
241  absl::ReaderMutexLock lock(&domain_mutex_);
242  for (const int i : TypeToConstraints(ConstraintProto::kInterval)) {
243  const ConstraintProto& interval_ct = ModelProto().constraints(i);
244  // We only look at intervals that are performed in the solution. The
245  // unperformed intervals should be automatically freed during the generation
246  // phase.
247  if (interval_ct.enforcement_literal().size() == 1) {
248  const int enforcement_ref = interval_ct.enforcement_literal(0);
249  const int enforcement_var = PositiveRef(enforcement_ref);
250  const int value = initial_solution.solution(enforcement_var);
251  if (RefIsPositive(enforcement_ref) == (value == 0)) {
252  continue;
253  }
254  }
255 
256  // We filter out fixed intervals. Because of presolve, if there is an
257  // enforcement literal, it cannot be fixed.
258  if (interval_ct.enforcement_literal().empty() &&
259  IsConstant(PositiveRef(interval_ct.interval().start())) &&
260  IsConstant(PositiveRef(interval_ct.interval().size())) &&
261  IsConstant(PositiveRef(interval_ct.interval().end()))) {
262  continue;
263  }
264 
265  active_intervals.push_back(i);
266  }
267  return active_intervals;
268 }
269 
271  const CpSolverResponse& initial_solution,
272  const std::vector<int>& variables_to_fix) const {
273  Neighborhood neighborhood;
274 
275  const absl::flat_hash_set<int> fixed_variables_set(variables_to_fix.begin(),
276  variables_to_fix.end());
277 
278  bool copy_is_successful = true;
279  {
280  absl::ReaderMutexLock domain_lock(&domain_mutex_);
281  copy_is_successful = CopyAndFixVariables(
282  model_proto_with_only_variables_, fixed_variables_set, initial_solution,
283  &neighborhood.delta);
284  }
285 
286  if (!copy_is_successful) {
287  neighborhood.is_reduced = true;
288  neighborhood.is_generated = false;
289  return neighborhood;
290  }
291 
292  AddSolutionHinting(initial_solution, &neighborhood.delta);
293 
294  neighborhood.is_generated = true;
295  neighborhood.is_reduced = !variables_to_fix.empty();
296  // TODO(user): force better objective? Note that this is already done when the
297  // hint above is successfully loaded (i.e. if it passes the presolve
298  // correctly) since the solver will try to find better solution than the
299  // current one.
300  return neighborhood;
301 }
302 
304  const CpSolverResponse& initial_solution, CpModelProto* model_proto) const {
305  // Set the current solution as a hint.
306  model_proto->clear_solution_hint();
307  const auto is_fixed = [model_proto](int var) {
308  const IntegerVariableProto& var_proto = model_proto->variables(var);
309  return var_proto.domain_size() == 2 &&
310  var_proto.domain(0) == var_proto.domain(1);
311  };
312  for (int var = 0; var < model_proto->variables_size(); ++var) {
313  if (is_fixed(var)) continue;
314 
315  model_proto->mutable_solution_hint()->add_vars(var);
316  model_proto->mutable_solution_hint()->add_values(
317  initial_solution.solution(var));
318  }
319 }
320 
322  const std::vector<int>& constraints_to_remove) const {
323  Neighborhood neighborhood = FullNeighborhood();
324 
325  if (constraints_to_remove.empty()) return neighborhood;
326  neighborhood.is_reduced = false;
327  neighborhood.constraints_to_ignore = constraints_to_remove;
328  return neighborhood;
329 }
330 
332  const CpSolverResponse& initial_solution,
333  const std::vector<int>& relaxed_variables) const {
334  std::vector<bool> relaxed_variables_set(model_proto_.variables_size(), false);
335  for (const int var : relaxed_variables) relaxed_variables_set[var] = true;
336  std::vector<int> fixed_variables;
337  {
338  absl::ReaderMutexLock graph_lock(&graph_mutex_);
339  for (const int i : active_variables_) {
340  if (!relaxed_variables_set[i]) {
341  fixed_variables.push_back(i);
342  }
343  }
344  }
345  return FixGivenVariables(initial_solution, fixed_variables);
346 }
347 
349  const CpSolverResponse& initial_solution) const {
350  const std::vector<int> fixed_variables = ActiveVariables();
351  return FixGivenVariables(initial_solution, fixed_variables);
352 }
353 
356 }
357 
358 double NeighborhoodGenerator::GetUCBScore(int64_t total_num_calls) const {
359  absl::ReaderMutexLock mutex_lock(&generator_mutex_);
360  DCHECK_GE(total_num_calls, num_calls_);
361  if (num_calls_ <= 10) return std::numeric_limits<double>::infinity();
362  return current_average_ + sqrt((2 * log(total_num_calls)) / num_calls_);
363 }
364 
366  absl::MutexLock mutex_lock(&generator_mutex_);
367 
368  // To make the whole update process deterministic, we currently sort the
369  // SolveData.
370  std::sort(solve_data_.begin(), solve_data_.end());
371 
372  // This will be used to update the difficulty of this neighborhood.
373  int num_fully_solved_in_batch = 0;
374  int num_not_fully_solved_in_batch = 0;
375 
376  for (const SolveData& data : solve_data_) {
378  ++num_calls_;
379 
380  // INFEASIBLE or OPTIMAL means that we "fully solved" the local problem.
381  // If we didn't, then we cannot be sure that there is no improving solution
382  // in that neighborhood.
383  if (data.status == CpSolverStatus::INFEASIBLE ||
384  data.status == CpSolverStatus::OPTIMAL) {
385  ++num_fully_solved_calls_;
386  ++num_fully_solved_in_batch;
387  } else {
388  ++num_not_fully_solved_in_batch;
389  }
390 
391  // It seems to make more sense to compare the new objective to the base
392  // solution objective, not the best one. However this causes issue in the
393  // logic below because on some problems the neighborhood can always lead
394  // to a better "new objective" if the base solution wasn't the best one.
395  //
396  // This might not be a final solution, but it does work ok for now.
397  const IntegerValue best_objective_improvement =
399  ? IntegerValue(CapSub(data.new_objective_bound.value(),
400  data.initial_best_objective_bound.value()))
401  : IntegerValue(CapSub(data.initial_best_objective.value(),
402  data.new_objective.value()));
403  if (best_objective_improvement > 0) {
404  num_consecutive_non_improving_calls_ = 0;
405  } else {
406  ++num_consecutive_non_improving_calls_;
407  }
408 
409  // TODO(user): Weight more recent data.
410  // degrade the current average to forget old learnings.
411  const double gain_per_time_unit =
412  std::max(0.0, static_cast<double>(best_objective_improvement.value())) /
413  (1.0 + data.deterministic_time);
414  if (num_calls_ <= 100) {
415  current_average_ += (gain_per_time_unit - current_average_) / num_calls_;
416  } else {
417  current_average_ = 0.9 * current_average_ + 0.1 * gain_per_time_unit;
418  }
419 
420  deterministic_time_ += data.deterministic_time;
421  }
422 
423  // Update the difficulty.
424  difficulty_.Update(/*num_decreases=*/num_not_fully_solved_in_batch,
425  /*num_increases=*/num_fully_solved_in_batch);
426 
427  // Bump the time limit if we saw no better solution in the last few calls.
428  // This means that as the search progress, we likely spend more and more time
429  // trying to solve individual neighborhood.
430  //
431  // TODO(user): experiment with resetting the time limit if a solution is
432  // found.
433  if (num_consecutive_non_improving_calls_ > 50) {
434  num_consecutive_non_improving_calls_ = 0;
435  deterministic_limit_ *= 1.02;
436 
437  // We do not want the limit to go to high. Intuitively, the goal is to try
438  // out a lot of neighborhoods, not just spend a lot of time on a few.
439  deterministic_limit_ = std::min(60.0, deterministic_limit_);
440  }
441 
442  solve_data_.clear();
443 }
444 
445 namespace {
446 
447 void GetRandomSubset(double relative_size, std::vector<int>* base,
448  absl::BitGenRef random) {
449  if (base->empty()) return;
450 
451  // TODO(user): we could generate this more efficiently than using random
452  // shuffle.
453  std::shuffle(base->begin(), base->end(), random);
454  const int target_size = std::round(relative_size * base->size());
455  base->resize(target_size);
456 }
457 
458 } // namespace
459 
461  const CpSolverResponse& initial_solution, double difficulty,
462  absl::BitGenRef random) {
463  std::vector<int> fixed_variables = helper_.ActiveVariables();
464  GetRandomSubset(1.0 - difficulty, &fixed_variables, random);
465  return helper_.FixGivenVariables(initial_solution, fixed_variables);
466 }
467 
469  const CpSolverResponse& initial_solution, double difficulty,
470  absl::BitGenRef random) {
471  std::vector<int> active_constraints;
472  for (int ct = 0; ct < helper_.ModelProto().constraints_size(); ++ct) {
473  if (helper_.ModelProto().constraints(ct).constraint_case() ==
474  ConstraintProto::CONSTRAINT_NOT_SET) {
475  continue;
476  }
477  active_constraints.push_back(ct);
478  }
479 
480  const int num_active_vars = helper_.NumActiveVariables();
481  const int num_model_vars = helper_.ModelProto().variables_size();
482  const int target_size = std::ceil(difficulty * num_active_vars);
483  const int num_constraints = helper_.ModelProto().constraints_size();
484  if (num_constraints == 0 || target_size == num_active_vars) {
485  return helper_.FullNeighborhood();
486  }
487  CHECK_GT(target_size, 0);
488 
489  std::shuffle(active_constraints.begin(), active_constraints.end(), random);
490 
491  std::vector<bool> visited_variables_set(num_model_vars, false);
492  std::vector<int> relaxed_variables;
493 
494  {
495  absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
496  for (const int constraint_index : active_constraints) {
497  CHECK_LT(constraint_index, num_constraints);
498  for (const int var : helper_.ConstraintToVar()[constraint_index]) {
499  if (visited_variables_set[var]) continue;
500  visited_variables_set[var] = true;
501  if (helper_.IsActive(var)) {
502  relaxed_variables.push_back(var);
503  if (relaxed_variables.size() == target_size) break;
504  }
505  }
506  if (relaxed_variables.size() == target_size) break;
507  }
508  }
509  return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
510 }
511 
513  const CpSolverResponse& initial_solution, double difficulty,
514  absl::BitGenRef random) {
515  const int num_active_vars = helper_.NumActiveVariables();
516  const int num_model_vars = helper_.ModelProto().variables_size();
517  const int target_size = std::ceil(difficulty * num_active_vars);
518  if (target_size == num_active_vars) {
519  return helper_.FullNeighborhood();
520  }
521  CHECK_GT(target_size, 0) << difficulty << " " << num_active_vars;
522 
523  std::vector<bool> visited_variables_set(num_model_vars, false);
524  std::vector<int> relaxed_variables;
525  std::vector<int> visited_variables;
526 
527  // It is important complexity wise to never scan a constraint twice!
528  const int num_model_constraints = helper_.ModelProto().constraints_size();
529  std::vector<bool> scanned_constraints(num_model_constraints, false);
530 
531  const int first_var =
532  helper_.ActiveVariables()[absl::Uniform<int>(random, 0, num_active_vars)];
533  visited_variables_set[first_var] = true;
534  visited_variables.push_back(first_var);
535  relaxed_variables.push_back(first_var);
536 
537  std::vector<int> random_variables;
538  {
539  absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
540  for (int i = 0; i < visited_variables.size(); ++i) {
541  random_variables.clear();
542  // Collect all the variables that appears in the same constraints as
543  // visited_variables[i].
544  for (const int ct : helper_.VarToConstraint()[visited_variables[i]]) {
545  if (scanned_constraints[ct]) continue;
546  scanned_constraints[ct] = true;
547  for (const int var : helper_.ConstraintToVar()[ct]) {
548  if (visited_variables_set[var]) continue;
549  visited_variables_set[var] = true;
550  random_variables.push_back(var);
551  }
552  }
553  // We always randomize to change the partial subgraph explored
554  // afterwards.
555  std::shuffle(random_variables.begin(), random_variables.end(), random);
556  for (const int var : random_variables) {
557  if (relaxed_variables.size() < target_size) {
558  visited_variables.push_back(var);
559  if (helper_.IsActive(var)) {
560  relaxed_variables.push_back(var);
561  }
562  } else {
563  break;
564  }
565  }
566  if (relaxed_variables.size() >= target_size) break;
567  }
568  }
569  return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
570 }
571 
573  const CpSolverResponse& initial_solution, double difficulty,
574  absl::BitGenRef random) {
575  const int num_active_vars = helper_.NumActiveVariables();
576  const int num_model_vars = helper_.ModelProto().variables_size();
577  const int target_size = std::ceil(difficulty * num_active_vars);
578  const int num_constraints = helper_.ModelProto().constraints_size();
579  if (num_constraints == 0 || target_size == num_active_vars) {
580  return helper_.FullNeighborhood();
581  }
582  CHECK_GT(target_size, 0);
583 
584  std::vector<bool> visited_variables_set(num_model_vars, false);
585  std::vector<int> relaxed_variables;
586  std::vector<bool> added_constraints(num_constraints, false);
587  std::vector<int> next_constraints;
588 
589  // Start by a random constraint.
590  next_constraints.push_back(absl::Uniform<int>(random, 0, num_constraints));
591  added_constraints[next_constraints.back()] = true;
592 
593  std::vector<int> random_variables;
594  {
595  absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
596  while (relaxed_variables.size() < target_size) {
597  // Stop if we have a full connected component.
598  if (next_constraints.empty()) break;
599 
600  // Pick a random unprocessed constraint.
601  const int i = absl::Uniform<int>(random, 0, next_constraints.size());
602  const int constraint_index = next_constraints[i];
603  std::swap(next_constraints[i], next_constraints.back());
604  next_constraints.pop_back();
605 
606  // Add all the variable of this constraint and increase the set of next
607  // possible constraints.
608  CHECK_LT(constraint_index, num_constraints);
609  random_variables = helper_.ConstraintToVar()[constraint_index];
610  std::shuffle(random_variables.begin(), random_variables.end(), random);
611  for (const int var : random_variables) {
612  if (visited_variables_set[var]) continue;
613  visited_variables_set[var] = true;
614  if (helper_.IsActive(var)) {
615  relaxed_variables.push_back(var);
616  }
617  if (relaxed_variables.size() == target_size) break;
618 
619  for (const int ct : helper_.VarToConstraint()[var]) {
620  if (added_constraints[ct]) continue;
621  added_constraints[ct] = true;
622  next_constraints.push_back(ct);
623  }
624  }
625  }
626  }
627  return helper_.RelaxGivenVariables(initial_solution, relaxed_variables);
628 }
629 
631  const absl::Span<const int> intervals_to_relax,
632  const CpSolverResponse& initial_solution,
633  const NeighborhoodGeneratorHelper& helper) {
634  Neighborhood neighborhood = helper.FullNeighborhood();
635  neighborhood.is_reduced =
636  (intervals_to_relax.size() <
637  helper.TypeToConstraints(ConstraintProto::kInterval).size());
638 
639  // We will extend the set with some interval that we cannot fix.
640  std::set<int> ignored_intervals(intervals_to_relax.begin(),
641  intervals_to_relax.end());
642 
643  // Fix the presence/absence of non-relaxed intervals.
644  for (const int i : helper.TypeToConstraints(ConstraintProto::kInterval)) {
645  if (ignored_intervals.count(i)) continue;
646 
647  const ConstraintProto& interval_ct = helper.ModelProto().constraints(i);
648  if (interval_ct.enforcement_literal().empty()) continue;
649 
650  CHECK_EQ(interval_ct.enforcement_literal().size(), 1);
651  const int enforcement_ref = interval_ct.enforcement_literal(0);
652  const int enforcement_var = PositiveRef(enforcement_ref);
653  const int value = initial_solution.solution(enforcement_var);
654 
655  // If the interval is not enforced, we just relax it. If it belongs to an
656  // exactly one constraint, and the enforced interval is not relaxed, then
657  // propagation will force this interval to stay not enforced. Otherwise,
658  // LNS will be able to change which interval will be enforced among all
659  // alternatives.
660  if (RefIsPositive(enforcement_ref) == (value == 0)) {
661  ignored_intervals.insert(i);
662  continue;
663  }
664 
665  // Fix the value.
666  neighborhood.delta.mutable_variables(enforcement_var)->clear_domain();
667  neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
668  neighborhood.delta.mutable_variables(enforcement_var)->add_domain(value);
669  }
670 
671  for (const int c : helper.TypeToConstraints(ConstraintProto::kNoOverlap)) {
672  // Sort all non-relaxed intervals of this constraint by current start
673  // time.
674  std::vector<std::pair<int64_t, int>> start_interval_pairs;
675  for (const int i :
676  helper.ModelProto().constraints(c).no_overlap().intervals()) {
677  if (ignored_intervals.count(i)) continue;
678  const ConstraintProto& interval_ct = helper.ModelProto().constraints(i);
679 
680  // TODO(user): we ignore size zero for now.
681  const int size_var = interval_ct.interval().size();
682  if (initial_solution.solution(size_var) == 0) continue;
683 
684  const int start_var = interval_ct.interval().start();
685  const int64_t start_value = initial_solution.solution(start_var);
686  start_interval_pairs.push_back({start_value, i});
687  }
688  std::sort(start_interval_pairs.begin(), start_interval_pairs.end());
689 
690  // Add precedence between the remaining intervals, forcing their order.
691  for (int i = 0; i + 1 < start_interval_pairs.size(); ++i) {
692  const int before_var = helper.ModelProto()
693  .constraints(start_interval_pairs[i].second)
694  .interval()
695  .end();
696  const int after_var = helper.ModelProto()
697  .constraints(start_interval_pairs[i + 1].second)
698  .interval()
699  .start();
700  CHECK_LE(initial_solution.solution(before_var),
701  initial_solution.solution(after_var));
702 
703  LinearConstraintProto* linear =
704  neighborhood.delta.add_constraints()->mutable_linear();
705  linear->add_domain(std::numeric_limits<int64_t>::min());
706  linear->add_domain(0);
707  linear->add_vars(before_var);
708  linear->add_coeffs(1);
709  linear->add_vars(after_var);
710  linear->add_coeffs(-1);
711  }
712  }
713 
714  // Set the current solution as a hint.
715  helper.AddSolutionHinting(initial_solution, &neighborhood.delta);
716  neighborhood.is_generated = true;
717 
718  return neighborhood;
719 }
720 
722  const CpSolverResponse& initial_solution, double difficulty,
723  absl::BitGenRef random) {
724  std::vector<int> intervals_to_relax =
725  helper_.GetActiveIntervals(initial_solution);
726  GetRandomSubset(difficulty, &intervals_to_relax, random);
727 
728  return GenerateSchedulingNeighborhoodForRelaxation(intervals_to_relax,
729  initial_solution, helper_);
730 }
731 
733  const CpSolverResponse& initial_solution, double difficulty,
734  absl::BitGenRef random) {
735  std::vector<std::pair<int64_t, int>> start_interval_pairs;
736  const std::vector<int> active_intervals =
737  helper_.GetActiveIntervals(initial_solution);
738  std::vector<int> intervals_to_relax;
739 
740  if (active_intervals.empty()) return helper_.FullNeighborhood();
741 
742  for (const int i : active_intervals) {
743  const ConstraintProto& interval_ct = helper_.ModelProto().constraints(i);
744  const int start_var = interval_ct.interval().start();
745  const int64_t start_value = initial_solution.solution(start_var);
746  start_interval_pairs.push_back({start_value, i});
747  }
748  std::sort(start_interval_pairs.begin(), start_interval_pairs.end());
749  const int relaxed_size = std::floor(difficulty * start_interval_pairs.size());
750 
751  std::uniform_int_distribution<int> random_var(
752  0, start_interval_pairs.size() - relaxed_size - 1);
753  const int random_start_index = random_var(random);
754 
755  // TODO(user,user): Consider relaxing more than one time window
756  // intervals. This seems to help with Giza models.
757  for (int i = random_start_index; i < relaxed_size; ++i) {
758  intervals_to_relax.push_back(start_interval_pairs[i].second);
759  }
760 
761  return GenerateSchedulingNeighborhoodForRelaxation(intervals_to_relax,
762  initial_solution, helper_);
763 }
764 
766  if (incomplete_solutions_ != nullptr) {
767  return incomplete_solutions_->HasNewSolution();
768  }
769 
770  if (response_manager_ != nullptr) {
771  if (response_manager_->SolutionsRepository().NumSolutions() == 0) {
772  return false;
773  }
774  }
775 
776  // At least one relaxation solution should be available to generate a
777  // neighborhood.
778  if (lp_solutions_ != nullptr && lp_solutions_->NumSolutions() > 0) {
779  return true;
780  }
781 
782  if (relaxation_solutions_ != nullptr &&
783  relaxation_solutions_->NumSolutions() > 0) {
784  return true;
785  }
786  return false;
787 }
788 
790  const CpSolverResponse& initial_solution, double difficulty,
791  absl::BitGenRef random) {
792  Neighborhood neighborhood = helper_.FullNeighborhood();
793  neighborhood.is_generated = false;
794 
795  const bool lp_solution_available =
796  (lp_solutions_ != nullptr && lp_solutions_->NumSolutions() > 0);
797 
798  const bool relaxation_solution_available =
799  (relaxation_solutions_ != nullptr &&
800  relaxation_solutions_->NumSolutions() > 0);
801 
802  const bool incomplete_solution_available =
803  (incomplete_solutions_ != nullptr &&
804  incomplete_solutions_->HasNewSolution());
805 
806  if (!lp_solution_available && !relaxation_solution_available &&
807  !incomplete_solution_available) {
808  return neighborhood;
809  }
810 
811  RINSNeighborhood rins_neighborhood;
812  // Randomly select the type of relaxation if both lp and relaxation solutions
813  // are available.
814  // TODO(user): Tune the probability value for this.
815  std::bernoulli_distribution random_bool(0.5);
816  const bool use_lp_relaxation =
817  (lp_solution_available && relaxation_solution_available)
818  ? random_bool(random)
819  : lp_solution_available;
820  if (use_lp_relaxation) {
821  rins_neighborhood =
822  GetRINSNeighborhood(response_manager_,
823  /*relaxation_solutions=*/nullptr, lp_solutions_,
824  incomplete_solutions_, random);
825  neighborhood.source_info =
826  incomplete_solution_available ? "incomplete" : "lp";
827  } else {
828  CHECK(relaxation_solution_available || incomplete_solution_available);
829  rins_neighborhood = GetRINSNeighborhood(
830  response_manager_, relaxation_solutions_,
831  /*lp_solutions=*/nullptr, incomplete_solutions_, random);
832  neighborhood.source_info =
833  incomplete_solution_available ? "incomplete" : "relaxation";
834  }
835 
836  if (rins_neighborhood.fixed_vars.empty() &&
837  rins_neighborhood.reduced_domain_vars.empty()) {
838  return neighborhood;
839  }
840 
841  absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
842  // Fix the variables in the local model.
843  for (const std::pair</*model_var*/ int, /*value*/ int64_t> fixed_var :
844  rins_neighborhood.fixed_vars) {
845  const int var = fixed_var.first;
846  const int64_t value = fixed_var.second;
847  if (var >= neighborhood.delta.variables_size()) continue;
848  if (!helper_.IsActive(var)) continue;
849 
850  if (!DomainInProtoContains(neighborhood.delta.variables(var), value)) {
851  // TODO(user): Instead of aborting, pick the closest point in the domain?
852  return neighborhood;
853  }
854 
855  neighborhood.delta.mutable_variables(var)->clear_domain();
856  neighborhood.delta.mutable_variables(var)->add_domain(value);
857  neighborhood.delta.mutable_variables(var)->add_domain(value);
858  neighborhood.is_reduced = true;
859  }
860 
861  for (const std::pair</*model_var*/ int,
862  /*domain*/ std::pair<int64_t, int64_t>>
863  reduced_var : rins_neighborhood.reduced_domain_vars) {
864  const int var = reduced_var.first;
865  const int64_t lb = reduced_var.second.first;
866  const int64_t ub = reduced_var.second.second;
867  if (var >= neighborhood.delta.variables_size()) continue;
868  if (!helper_.IsActive(var)) continue;
869  Domain domain = ReadDomainFromProto(neighborhood.delta.variables(var));
870  domain = domain.IntersectionWith(Domain(lb, ub));
871  if (domain.IsEmpty()) {
872  // TODO(user): Instead of aborting, pick the closest point in the domain?
873  return neighborhood;
874  }
875  FillDomainInProto(domain, neighborhood.delta.mutable_variables(var));
876  neighborhood.is_reduced = true;
877  }
878  neighborhood.is_generated = true;
879  return neighborhood;
880 }
881 
883  const CpSolverResponse& initial_solution, double difficulty,
884  absl::BitGenRef random) {
885  std::vector<int> removable_constraints;
886  const int num_constraints = helper_.ModelProto().constraints_size();
887  removable_constraints.reserve(num_constraints);
888  for (int c = 0; c < num_constraints; ++c) {
889  // Removing intervals is not easy because other constraint might require
890  // them, so for now, we don't remove them.
891  if (helper_.ModelProto().constraints(c).constraint_case() ==
892  ConstraintProto::kInterval) {
893  continue;
894  }
895  removable_constraints.push_back(c);
896  }
897 
898  const int target_size =
899  std::round((1.0 - difficulty) * removable_constraints.size());
900 
901  const int random_start_index =
902  absl::Uniform<int>(random, 0, removable_constraints.size());
903  std::vector<int> removed_constraints;
904  removed_constraints.reserve(target_size);
905  int c = random_start_index;
906  while (removed_constraints.size() < target_size) {
907  removed_constraints.push_back(removable_constraints[c]);
908  ++c;
909  if (c == removable_constraints.size()) {
910  c = 0;
911  }
912  }
913 
914  return helper_.RemoveMarkedConstraints(removed_constraints);
915 }
916 
919  NeighborhoodGeneratorHelper const* helper, const std::string& name)
920  : NeighborhoodGenerator(name, helper) {
921  std::vector<int> removable_constraints;
922  const int num_constraints = helper_.ModelProto().constraints_size();
923  constraint_weights_.reserve(num_constraints);
924  // TODO(user): Experiment with different starting weights.
925  for (int c = 0; c < num_constraints; ++c) {
926  switch (helper_.ModelProto().constraints(c).constraint_case()) {
927  case ConstraintProto::kCumulative:
928  case ConstraintProto::kAllDiff:
929  case ConstraintProto::kElement:
930  case ConstraintProto::kRoutes:
931  case ConstraintProto::kCircuit:
932  constraint_weights_.push_back(3.0);
933  num_removable_constraints_++;
934  break;
935  case ConstraintProto::kBoolOr:
936  case ConstraintProto::kBoolAnd:
937  case ConstraintProto::kBoolXor:
938  case ConstraintProto::kIntProd:
939  case ConstraintProto::kIntDiv:
940  case ConstraintProto::kIntMod:
941  case ConstraintProto::kIntMax:
942  case ConstraintProto::kLinMax:
943  case ConstraintProto::kIntMin:
944  case ConstraintProto::kLinMin:
945  case ConstraintProto::kNoOverlap:
946  case ConstraintProto::kNoOverlap2D:
947  constraint_weights_.push_back(2.0);
948  num_removable_constraints_++;
949  break;
950  case ConstraintProto::kLinear:
951  case ConstraintProto::kTable:
952  case ConstraintProto::kAutomaton:
953  case ConstraintProto::kInverse:
954  case ConstraintProto::kReservoir:
955  case ConstraintProto::kAtMostOne:
956  case ConstraintProto::kExactlyOne:
957  constraint_weights_.push_back(1.0);
958  num_removable_constraints_++;
959  break;
960  case ConstraintProto::CONSTRAINT_NOT_SET:
961  case ConstraintProto::kInterval:
962  // Removing intervals is not easy because other constraint might require
963  // them, so for now, we don't remove them.
964  constraint_weights_.push_back(0.0);
965  break;
966  }
967  }
968 }
969 
970 void WeightedRandomRelaxationNeighborhoodGenerator::
971  AdditionalProcessingOnSynchronize(const SolveData& solve_data) {
972  const IntegerValue best_objective_improvement =
973  solve_data.new_objective_bound - solve_data.initial_best_objective_bound;
974 
975  const std::vector<int>& removed_constraints =
976  removed_constraints_[solve_data.neighborhood_id];
977 
978  // Heuristic: We change the weights of the removed constraints if the
979  // neighborhood is solved (status is OPTIMAL or INFEASIBLE) or we observe an
980  // improvement in objective bounds. Otherwise we assume that the
981  // difficulty/time wasn't right for us to record feedbacks.
982  //
983  // If the objective bounds are improved, we bump up the weights. If the
984  // objective bounds are worse and the problem status is OPTIMAL, we bump down
985  // the weights. Otherwise if the new objective bounds are same as current
986  // bounds (which happens a lot on some instances), we do not update the
987  // weights as we do not have a clear signal whether the constraints removed
988  // were good choices or not.
989  // TODO(user): We can improve this heuristic with more experiments.
990  if (best_objective_improvement > 0) {
991  // Bump up the weights of all removed constraints.
992  for (int c : removed_constraints) {
993  if (constraint_weights_[c] <= 90.0) {
994  constraint_weights_[c] += 10.0;
995  } else {
996  constraint_weights_[c] = 100.0;
997  }
998  }
999  } else if (solve_data.status == CpSolverStatus::OPTIMAL &&
1000  best_objective_improvement < 0) {
1001  // Bump down the weights of all removed constraints.
1002  for (int c : removed_constraints) {
1003  if (constraint_weights_[c] > 0.5) {
1004  constraint_weights_[c] -= 0.5;
1005  }
1006  }
1007  }
1008  removed_constraints_.erase(solve_data.neighborhood_id);
1009 }
1010 
1012  const CpSolverResponse& initial_solution, double difficulty,
1013  absl::BitGenRef random) {
1014  const int target_size =
1015  std::round((1.0 - difficulty) * num_removable_constraints_);
1016 
1017  std::vector<int> removed_constraints;
1018 
1019  // Generate a random number between (0,1) = u[i] and use score[i] =
1020  // u[i]^(1/w[i]) and then select top k items with largest scores.
1021  // Reference: https://utopia.duth.gr/~pefraimi/research/data/2007EncOfAlg.pdf
1022  std::vector<std::pair<double, int>> constraint_removal_scores;
1023  std::uniform_real_distribution<double> random_var(0.0, 1.0);
1024  for (int c = 0; c < constraint_weights_.size(); ++c) {
1025  if (constraint_weights_[c] <= 0) continue;
1026  const double u = random_var(random);
1027  const double score = std::pow(u, (1 / constraint_weights_[c]));
1028  constraint_removal_scores.push_back({score, c});
1029  }
1030  std::sort(constraint_removal_scores.rbegin(),
1031  constraint_removal_scores.rend());
1032  for (int i = 0; i < target_size; ++i) {
1033  removed_constraints.push_back(constraint_removal_scores[i].second);
1034  }
1035 
1036  Neighborhood result = helper_.RemoveMarkedConstraints(removed_constraints);
1037 
1038  absl::MutexLock mutex_lock(&generator_mutex_);
1039  result.id = next_available_id_;
1040  next_available_id_++;
1041  removed_constraints_.insert({result.id, removed_constraints});
1042  return result;
1043 }
1044 
1045 } // namespace sat
1046 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:498
#define CHECK_LT(val1, val2)
Definition: base/logging.h:708
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:705
#define CHECK_GT(val1, val2)
Definition: base/logging.h:710
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:897
#define DCHECK(condition)
Definition: base/logging.h:892
#define CHECK_LE(val1, val2)
Definition: base/logging.h:707
#define VLOG(verboselevel)
Definition: base/logging.h:986
void Update(int num_decreases, int num_increases)
We call domain any subset of Int64 = [kint64min, kint64max].
bool IsFixed() const
Returns true iff the domain is reduced to a single value.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
bool IsEmpty() const
Returns true if this is the empty set.
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
const std::vector< std::vector< int > > & VarToConstraint() const ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
Definition: cp_model_lns.h:146
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:34
const SharedResponseManager & shared_response() const
Definition: cp_model_lns.h:170
Neighborhood FixAllVariables(const CpSolverResponse &initial_solution) const
Neighborhood FixGivenVariables(const CpSolverResponse &initial_solution, const std::vector< int > &variables_to_fix) const
const absl::Span< const int > TypeToConstraints(ConstraintProto::ConstraintCase type) const
Definition: cp_model_lns.h:152
bool CopyAndFixVariables(const CpModelProto &source_model, const absl::flat_hash_set< int > &fixed_variables_set, const CpSolverResponse &initial_solution, CpModelProto *output_model) const
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:142
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
double GetUCBScore(int64_t total_num_calls) const
virtual void AdditionalProcessingOnSynchronize(const SolveData &solve_data)
Definition: cp_model_lns.h:360
const NeighborhoodGeneratorHelper & helper_
Definition: cp_model_lns.h:363
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
void GetChangedBounds(int id, std::vector< int > *variables, std::vector< int64_t > *new_lower_bounds, std::vector< int64_t > *new_upper_bounds)
const SharedSolutionRepository< int64_t > & SolutionsRepository() const
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
WeightedRandomRelaxationNeighborhoodGenerator(NeighborhoodGeneratorHelper const *helper, const std::string &name)
Neighborhood Generate(const CpSolverResponse &initial_solution, double difficulty, absl::BitGenRef random) final
SatParameters parameters
CpModelProto const * model_proto
const std::string name
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
const bool DEBUG_MODE
Definition: macros.h:24
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
std::vector< int > UsedVariables(const ConstraintProto &ct)
bool RefIsPositive(int ref)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
bool DomainInProtoContains(const ProtoWithDomain &proto, int64_t value)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Neighborhood GenerateSchedulingNeighborhoodForRelaxation(const absl::Span< const int > intervals_to_relax, const CpSolverResponse &initial_solution, const NeighborhoodGeneratorHelper &helper)
RINSNeighborhood GetRINSNeighborhood(const SharedResponseManager *response_manager, const SharedRelaxationSolutionRepository *relaxation_solutions, const SharedLPSolutionRepository *lp_solutions, SharedIncompleteSolutionManager *incomplete_solutions, absl::BitGenRef random)
Definition: rins.cc:100
Collection of objects used to extend the Constraint Solver library.
int64_t CapSub(int64_t x, int64_t y)
IntervalVar * interval
Definition: resource.cc:100
std::vector< int > constraints_to_ignore
Definition: cp_model_lns.h:48
std::vector< std::pair< int, int64_t > > fixed_vars
Definition: rins.h:59
std::vector< std::pair< int, std::pair< int64_t, int64_t > > > reduced_domain_vars
Definition: rins.h:62
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41