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