Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
shaving_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <cstddef>
17#include <cstdint>
18#include <functional>
19#include <memory>
20#include <string>
21#include <utility>
22#include <vector>
23
24#include "absl/base/thread_annotations.h"
25#include "absl/flags/flag.h"
26#include "absl/log/check.h"
27#include "absl/log/log.h"
28#include "absl/log/vlog_is_on.h"
29#include "absl/random/distributions.h"
30#include "absl/strings/str_cat.h"
31#include "absl/synchronization/mutex.h"
32#include "google/protobuf/arena.h"
40#include "ortools/sat/model.h"
44#include "ortools/sat/util.h"
47
48namespace operations_research {
49namespace sat {
50
52 const SatParameters& local_parameters, NeighborhoodGeneratorHelper* helper,
53 SharedClasses* shared)
54 : SubSolver(local_parameters.name(), FULL_PROBLEM),
55 local_params_(local_parameters),
56 helper_(helper),
57 shared_(shared) {
58 ResetModel();
59}
60
62 shared_->stat_tables->AddTimingStat(*this);
63}
64
66 if (shared_->SearchIsDone()) return false;
67
68 // We only support one task at the time.
69 absl::MutexLock mutex_lock(mutex_);
70 return !task_in_flight_;
71}
72
73std::function<void()> ObjectiveShavingSolver::GenerateTask(int64_t task_id) {
74 {
75 absl::MutexLock mutex_lock(mutex_);
76 stop_current_chunk_.store(false);
77 task_in_flight_ = true;
78 objective_lb_ = shared_->response->GetInnerObjectiveLowerBound();
79 objective_ub_ = shared_->response->GetInnerObjectiveUpperBound();
80 }
81 return [this, task_id]() {
82 if (ResetAndSolveModel(task_id)) {
83 const CpSolverResponse local_response =
84 local_sat_model_->GetOrCreate<SharedResponseManager>()->GetResponse();
85
86 if (local_response.status() == CpSolverStatus::OPTIMAL ||
87 local_response.status() == CpSolverStatus::FEASIBLE) {
88 std::vector<int64_t> solution_values(local_response.solution().begin(),
89 local_response.solution().end());
90 if (local_params_.cp_model_presolve()) {
91 const int num_original_vars = shared_->model_proto.variables_size();
92 PostsolveResponseWrapper(local_params_, num_original_vars,
93 mapping_proto_, postsolve_mapping_,
94 &solution_values);
95 }
96 shared_->response->NewSolution(solution_values, Info());
97 } else if (local_response.status() == CpSolverStatus::INFEASIBLE) {
98 absl::MutexLock mutex_lock(mutex_);
99 shared_->response->UpdateInnerObjectiveBounds(
100 Info(), current_objective_target_ub_ + 1, objective_ub_);
101 }
102 }
103
104 absl::MutexLock mutex_lock(mutex_);
105 task_in_flight_ = false;
106 if (local_sat_model_ != nullptr) {
107 const double dtime = local_sat_model_->GetOrCreate<TimeLimit>()
108 ->GetElapsedDeterministicTime();
110 shared_->time_limit->AdvanceDeterministicTime(dtime);
111 }
112 };
113}
114
116 absl::MutexLock mutex_lock(mutex_);
117 if (!task_in_flight_) return;
118
119 // We are just waiting for the inner code to check the time limit or
120 // to return nicely.
121 if (stop_current_chunk_) return;
122
123 // TODO(user): Also stop if we have enough newly fixed / improved root level
124 // bounds so that we think it is worth represolving and restarting.
125 if (shared_->SearchIsDone()) {
126 stop_current_chunk_.store(true);
127 }
128
129 // The current objective lower bound has been improved, restarting.
130 if (shared_->response->GetInnerObjectiveLowerBound() > objective_lb_) {
131 stop_current_chunk_.store(true);
132 }
133
134 // A solution has been found that is better than the current target
135 // objective upper bound. Restarting to use a smaller delta.
136 if (shared_->response->GetInnerObjectiveUpperBound() <=
137 current_objective_target_ub_ &&
138 current_objective_target_ub_ != objective_lb_) {
139 stop_current_chunk_.store(true);
140 }
141
142 // If the range has been reduced enough to warrant a delta of 1, while the
143 // current search uses a delta > 1. Restarting to switch to the delta of 1.
144 if (current_objective_target_ub_ != objective_lb_ &&
145 shared_->response->GetInnerObjectiveUpperBound() -
146 shared_->response->GetInnerObjectiveLowerBound() <=
147 local_params_.shaving_search_threshold()) {
148 stop_current_chunk_.store(true);
149 }
150}
151
152std::string ObjectiveShavingSolver::Info() {
153 return absl::StrCat(name(), " (vars=", local_proto_->variables().size(),
154 " csts=", local_proto_->constraints().size(), ")");
155}
156
157void ObjectiveShavingSolver::ResetModel() {
158 if (shared_->model_proto.GetArena() != nullptr) {
159 arena_ = std::make_unique<google::protobuf::Arena>(
160 google::protobuf::ArenaOptions(
161 {.start_block_size = static_cast<size_t>(
162 shared_->model_proto.GetArena()->SpaceUsed())}));
163 } else {
164 arena_ = std::make_unique<google::protobuf::Arena>();
165 }
166 local_proto_ = google::protobuf::Arena::Create<CpModelProto>(arena_.get());
167 *local_proto_ = shared_->model_proto;
168}
169
170bool ObjectiveShavingSolver::ResetAndSolveModel(int64_t task_id) {
171 local_sat_model_ = std::make_unique<Model>(name());
172 *local_sat_model_->GetOrCreate<SatParameters>() = local_params_;
173 local_sat_model_->GetOrCreate<SatParameters>()->set_random_seed(
174 CombineSeed(local_params_.random_seed(), task_id));
175
176 auto* time_limit = local_sat_model_->GetOrCreate<TimeLimit>();
177 shared_->time_limit->UpdateLocalLimit(time_limit);
178 time_limit->RegisterSecondaryExternalBooleanAsLimit(&stop_current_chunk_);
179
180 auto* random = local_sat_model_->GetOrCreate<ModelRandomGenerator>();
181
182 // We copy the model.
183 ResetModel();
184 *local_proto_->mutable_variables() =
185 helper_->FullNeighborhood().delta.variables();
186
187 // Store the current lb in local variable.
188 IntegerValue objective_lb;
189 IntegerValue chosen_objective_ub;
190 {
191 absl::MutexLock mutex_lock(mutex_);
192 objective_lb = objective_lb_;
193 if (objective_ub_ - objective_lb <=
194 local_params_.shaving_search_threshold()) {
195 current_objective_target_ub_ = objective_lb;
196 } else {
197 const IntegerValue mid = (objective_ub_ - objective_lb) / 2;
198 current_objective_target_ub_ =
199 objective_lb + absl::LogUniform<int64_t>(*random, 0, mid.value());
200 }
201 chosen_objective_ub = current_objective_target_ub_;
202 VLOG(2) << name() << ": from [" << objective_lb.value() << ".."
203 << objective_ub_.value() << "] <= " << chosen_objective_ub.value();
204 }
205
206 // We replace the objective by a constraint, objective in [lb, target_ub].
207 // We modify local_proto_ to a pure feasibility problem.
208 // Not having the objective open up more presolve reduction.
209 Domain obj_domain = Domain(objective_lb.value(), chosen_objective_ub.value());
210 if (local_proto_->objective().domain_size() > 1) {
211 // Intersect with the first interval of the objective domain.
212 obj_domain = obj_domain.IntersectionWith(
213 Domain(local_proto_->objective().domain(0),
214 local_proto_->objective().domain(1)));
215 }
216 if (local_proto_->objective().vars().size() == 1 &&
217 local_proto_->objective().coeffs(0) == 1) {
218 auto* obj_var =
219 local_proto_->mutable_variables(local_proto_->objective().vars(0));
220 const Domain reduced_var_domain = obj_domain.IntersectionWith(
221 Domain(obj_var->domain(0), obj_var->domain(1)));
222 if (reduced_var_domain.IsEmpty()) {
223 return false;
224 }
225 FillDomainInProto(reduced_var_domain, obj_var);
226 } else {
227 auto* obj = local_proto_->add_constraints()->mutable_linear();
228 *obj->mutable_vars() = local_proto_->objective().vars();
229 *obj->mutable_coeffs() = local_proto_->objective().coeffs();
230 if (obj_domain.IsEmpty()) {
231 return false;
232 }
233 FillDomainInProto(obj_domain, obj);
234 }
235
236 // Clear the objective.
237 local_proto_->clear_objective();
238 local_proto_->set_name(absl::StrCat(local_proto_->name(), "_obj_shaving_",
239 objective_lb.value()));
240
241 // Dump?
242 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
243 const std::string name =
244 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
245 "objective_shaving_", objective_lb.value(), ".pb.txt");
246 LOG(INFO) << "Dumping objective shaving model to '" << name << "'.";
247 CHECK(WriteModelProtoToFile(*local_proto_, name));
248 }
249
250 // Presolve if asked.
251 if (local_params_.cp_model_presolve()) {
252 mapping_proto_.Clear();
253 postsolve_mapping_.clear();
254 auto context = std::make_unique<PresolveContext>(
255 local_sat_model_.get(), local_proto_, &mapping_proto_);
256 const CpSolverStatus presolve_status =
257 PresolveCpModel(context.get(), &postsolve_mapping_);
258 if (presolve_status == CpSolverStatus::INFEASIBLE) {
259 absl::MutexLock mutex_lock(mutex_);
260 shared_->response->UpdateInnerObjectiveBounds(
261 Info(), chosen_objective_ub + 1, kMaxIntegerValue);
262 return false;
263 }
264 }
265
266 // Tricky: If we aborted during the presolve above, some constraints might
267 // be in a non-canonical form (like having duplicates, etc...) and it seem
268 // not all our propagator code deal with that properly. So it is important
269 // to abort right away here.
270 //
271 // We had a bug when the LoadCpModel() below was returning infeasible on
272 // such non fully-presolved model.
273 if (time_limit->LimitReached()) return false;
274
275 LoadCpModel(*local_proto_, local_sat_model_.get());
276 return true;
277}
278
280 const SatParameters& local_parameters, NeighborhoodGeneratorHelper* helper,
281 SharedClasses* shared)
282 : SubSolver(local_parameters.name(), INCOMPLETE),
283 local_params_(local_parameters),
284 shared_(shared),
285 stop_current_chunk_(false),
286 model_proto_(shared->model_proto) {
287 if (shared_->bounds != nullptr) {
288 shared_bounds_id_ = shared_->bounds->RegisterNewId();
289 }
290
291 absl::MutexLock mutex_lock(mutex_);
292 for (const IntegerVariableProto& var_proto : model_proto_.variables()) {
293 var_domains_.push_back(ReadDomainFromProto(var_proto));
294 }
295}
296
298 shared_->stat_tables->AddTimingStat(*this);
299
300 if (!VLOG_IS_ON(1)) return;
301 if (shared_ == nullptr || shared_->stats == nullptr) return;
302 std::vector<std::pair<std::string, int64_t>> stats;
303 absl::MutexLock mutex_lock(mutex_);
304 stats.push_back({"variable_shaving/num_vars_tried", num_vars_tried_});
305 stats.push_back({"variable_shaving/num_vars_shaved", num_vars_shaved_});
306 stats.push_back(
307 {"variable_shaving/num_infeasible_found", num_infeasible_found_});
308 shared_->stats->AddStats(stats);
309}
310
312 return !shared_->SearchIsDone();
313}
314
316 const CpSolverResponse& local_response, const State& state) {
317 if (shared_->bounds == nullptr) return;
318 if (state.shave_using_objective) {
319 if (local_response.status() == CpSolverStatus::INFEASIBLE) return;
320 const int64_t obj_lb = local_response.inner_objective_lower_bound();
321
322 absl::MutexLock lock(mutex_);
323 const Domain domain = var_domains_[state.var_index];
324 if (state.minimize) {
325 const int64_t lb = obj_lb;
326 if (lb > domain.Min()) {
327 ++num_vars_shaved_;
328 shared_->bounds->ReportPotentialNewBounds(name(), {state.var_index},
329 {lb}, {domain.Max()});
330 VLOG(1) << name() << ": var(" << state.var_index << ") " << domain
331 << " >= " << lb;
332 }
333 } else {
334 const int64_t ub = -obj_lb;
335 if (ub < domain.Max()) {
336 ++num_vars_shaved_;
337 shared_->bounds->ReportPotentialNewBounds(name(), {state.var_index},
338 {domain.Min()}, {ub});
339 VLOG(1) << name() << ": var(" << state.var_index << ") " << domain
340 << " <= " << ub;
341 }
342 }
343 return;
344 }
345
346 if (local_response.status() != CpSolverStatus::INFEASIBLE) return;
347 absl::MutexLock lock(mutex_);
348 const Domain domain = var_domains_[state.var_index];
349 Domain new_domain = domain;
350 ++num_infeasible_found_;
351 new_domain = domain.IntersectionWith(state.reduced_domain.Complement());
352 VLOG(1) << name() << ": var(" << state.var_index << ") " << domain << " ==> "
353 << new_domain;
354
355 if (domain != new_domain) {
356 ++num_vars_shaved_;
357 if (!new_domain.IsEmpty()) {
358 shared_->bounds->ReportPotentialNewBounds(
359 name(), {state.var_index}, {new_domain.Min()}, {new_domain.Max()});
360 }
361 var_domains_[state.var_index] = new_domain;
362 if (var_domains_[state.var_index].IsEmpty()) {
363 shared_->response->NotifyThatImprovingProblemIsInfeasible(
364 "Unsat during variables shaving");
365 return;
366 }
367 }
368}
369
370std::function<void()> VariablesShavingSolver::GenerateTask(int64_t task_id) {
371 return [this, task_id]() mutable {
372 Model local_sat_model;
373 CpModelProto shaving_proto;
374 State state;
375 if (ResetAndSolveModel(task_id, &state, &local_sat_model, &shaving_proto)) {
376 const CpSolverResponse local_response =
377 local_sat_model.GetOrCreate<SharedResponseManager>()->GetResponse();
378 ProcessLocalResponse(local_response, state);
379 }
380
381 absl::MutexLock mutex_lock(mutex_);
382 const double dtime =
383 local_sat_model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime();
385 shared_->time_limit->AdvanceDeterministicTime(dtime);
386 };
387}
388
390 absl::MutexLock mutex_lock(mutex_);
391 // We are just waiting for the inner code to check the time limit or
392 // to return nicely.
393 if (stop_current_chunk_) return;
394
395 if (shared_->SearchIsDone()) {
396 stop_current_chunk_.store(true);
397 }
398
399 if (shared_->bounds != nullptr) {
400 std::vector<int> model_variables;
401 std::vector<int64_t> new_lower_bounds;
402 std::vector<int64_t> new_upper_bounds;
403 shared_->bounds->GetChangedBounds(shared_bounds_id_, &model_variables,
404 &new_lower_bounds, &new_upper_bounds);
405
406 for (int i = 0; i < model_variables.size(); ++i) {
407 const int var = model_variables[i];
408 const int64_t new_lb = new_lower_bounds[i];
409 const int64_t new_ub = new_upper_bounds[i];
410 const Domain& old_domain = var_domains_[var];
411 const Domain new_domain =
412 old_domain.IntersectionWith(Domain(new_lb, new_ub));
413 if (new_domain.IsEmpty()) {
414 shared_->response->NotifyThatImprovingProblemIsInfeasible(
415 "Unsat during variables shaving");
416 continue;
417 }
418 var_domains_[var] = new_domain;
419 }
420 }
421}
422
423std::string VariablesShavingSolver::Info() {
424 return absl::StrCat(name(), " (vars=", model_proto_.variables().size(),
425 " csts=", model_proto_.constraints().size(), ")");
426}
427
428int64_t VariablesShavingSolver::DomainSize(int var) const
429 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
430 return var_domains_[var].Size();
431}
432
433bool VariablesShavingSolver::VarIsFixed(int int_var) const
434 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
435 return var_domains_[int_var].IsFixed();
436}
437
438bool VariablesShavingSolver::ConstraintIsInactive(int c) const
439 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
440 for (const int ref : model_proto_.constraints(c).enforcement_literal()) {
441 const int var = PositiveRef(ref);
442 if (VarIsFixed(var) && var_domains_[var].Min() == (var == ref ? 0 : 1)) {
443 return true;
444 }
445 }
446 return false;
447}
448
449bool VariablesShavingSolver::FindNextVar(State* state)
450 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
451 const int num_vars = var_domains_.size();
452 ++current_index_;
453
454 // We start by shaving the objective in order to increase the lower bound.
455 if (model_proto_.has_objective()) {
456 const int num_obj_vars = model_proto_.objective().vars().size();
457 if (num_obj_vars > 1) {
458 while (current_index_ < num_obj_vars) {
459 const int var = model_proto_.objective().vars(current_index_);
460 if (VarIsFixed(var)) {
461 ++current_index_;
462 continue;
463 }
464 state->var_index = var;
465 state->minimize = model_proto_.objective().coeffs(current_index_) > 0;
466 state->shave_using_objective = true;
467 return true;
468 }
469 }
470 }
471
472 // Otherwise loop over all variables.
473 // TODO(user): maybe we should just order all possible State, putting the
474 // objective first, and just loop.
475 for (int i = 0; i < num_vars; ++i) {
476 const int var = (current_index_ / 2 + i) % num_vars;
477 if (VarIsFixed(var)) {
478 ++current_index_;
479 continue;
480 }
481
482 // Let's not shave the single var objective. There are enough workers
483 // looking at it.
484 if (model_proto_.has_objective() &&
485 model_proto_.objective().vars_size() == 1 &&
486 var == model_proto_.objective().vars(0)) {
487 continue;
488 }
489
490 state->var_index = var;
491 state->minimize = current_index_ % 2 == 0;
492 state->shave_using_objective = current_index_ / num_vars < 2;
493 return true;
494 }
495 return false;
496}
497
498void VariablesShavingSolver::CopyModelConnectedToVar(
499 State* state, Model* local_model, CpModelProto* shaving_proto,
500 bool* has_no_overlap_2d) ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
501 auto var_to_node = [](int var) { return var; };
502 auto ct_to_node = [num_vars = model_proto_.variables_size()](int c) {
503 return c + num_vars;
504 };
505
506 // Heuristic: we will ignore some complex constraint and "RELAX" them.
507 const int root_index = var_to_node(state->var_index);
508 std::vector<int> ignored_constraints;
509
510 // Build the connected component graph.
511 //
512 // TODO(user): Add some kind of difficulty, and do a BFS instead so that
513 // we don't pull in the full model when everything is connected. We can
514 // reuse the helper_ graph for this.
515 DenseConnectedComponentsFinder cc_finder;
516 cc_finder.SetNumberOfNodes(model_proto_.constraints_size() +
517 model_proto_.variables_size());
518 for (int c = 0; c < model_proto_.constraints_size(); ++c) {
519 if (ConstraintIsInactive(c)) continue;
520
521 const ConstraintProto& ct = model_proto_.constraints(c);
522 if (ct.constraint_case() == ConstraintProto::kNoOverlap2D) {
523 // Make sure x and y part are connected.
524 *has_no_overlap_2d = true;
525 const int num_intervals = ct.no_overlap_2d().x_intervals().size();
526 for (int i = 0; i < num_intervals; ++i) {
527 const int x_interval = ct.no_overlap_2d().x_intervals(i);
528 const int y_interval = ct.no_overlap_2d().y_intervals(i);
529 cc_finder.AddEdge(ct_to_node(x_interval), ct_to_node(y_interval));
530 }
531 ignored_constraints.push_back(c);
532 continue;
533 }
534
535 const int ct_node = ct_to_node(c);
536 for (const int var : UsedVariables(ct)) {
537 if (VarIsFixed(var)) continue;
538 cc_finder.AddEdge(ct_node, var_to_node(var));
539 }
540 for (const int interval : UsedIntervals(ct)) {
541 cc_finder.AddEdge(ct_node, ct_to_node(interval));
542 }
543 }
544
545 DCHECK(shaving_proto->variables().empty());
546 DCHECK(shaving_proto->constraints().empty());
547
548 const auto active_constraints = [&cc_finder, root_index, &ct_to_node](int c) {
549 return cc_finder.Connected(root_index, ct_to_node(c));
550 };
551
552 PresolveContext context(local_model, shaving_proto, nullptr);
553 std::vector<int> interval_mapping;
555 model_proto_, var_domains_, active_constraints, &context,
556 &interval_mapping);
557
558 // Now copy the ignored constraints "partially".
559 for (const int c : ignored_constraints) {
560 CHECK(!active_constraints(c));
561 const ConstraintProto& ct = model_proto_.constraints(c);
562
563 // We will only include non-ignored intervals there !
564 if (ct.constraint_case() == ConstraintProto::kNoOverlap2D) {
565 NoOverlap2DConstraintProto* new_no_overlap_2d =
566 shaving_proto->add_constraints()->mutable_no_overlap_2d();
567 const int num_intervals = ct.no_overlap_2d().x_intervals().size();
568 for (int i = 0; i < num_intervals; ++i) {
569 const int x_interval = ct.no_overlap_2d().x_intervals(i);
570 const int y_interval = ct.no_overlap_2d().y_intervals(i);
571 if (!active_constraints(x_interval)) continue;
572 if (!active_constraints(y_interval)) continue;
573
574 new_no_overlap_2d->add_x_intervals(interval_mapping[x_interval]);
575 new_no_overlap_2d->add_y_intervals(interval_mapping[y_interval]);
576 }
577 }
578 }
579
580 if (VLOG_IS_ON(2)) {
581 int num_active_variables = 0;
582 for (int i = 0; i < model_proto_.variables().size(); ++i) {
583 if (cc_finder.Connected(root_index, var_to_node(i))) {
584 ++num_active_variables;
585 }
586 }
587 int num_active_constraints = 0;
588 for (int c = 0; c < model_proto_.constraints().size(); ++c) {
589 if (cc_finder.Connected(root_index, ct_to_node(c))) {
590 ++num_active_constraints;
591 }
592 }
593
594 LOG(INFO) << "#shaving_constraints:" << shaving_proto->constraints_size()
595 << " #active_constraints:" << num_active_constraints << "/"
596 << model_proto_.constraints_size()
597 << " #active_variables:" << num_active_variables << "/"
598 << model_proto_.variables_size();
599 }
600
601 shaving_proto->clear_objective();
602
603 if (state->shave_using_objective) {
604 if (state->minimize) {
605 shaving_proto->mutable_objective()->add_vars(state->var_index);
606 shaving_proto->mutable_objective()->add_coeffs(1);
607 } else {
608 shaving_proto->mutable_objective()->add_vars(state->var_index);
609 shaving_proto->mutable_objective()->add_coeffs(-1);
610 }
611 } else {
612 const Domain domain =
613 ReadDomainFromProto(shaving_proto->variables(state->var_index));
614
615 int64_t delta = 0;
616 if (domain.Size() > local_params_.shaving_search_threshold()) {
617 const int64_t mid_range = (domain.Max() - domain.Min()) / 2;
618 auto* random = local_model->GetOrCreate<ModelRandomGenerator>();
619 delta = absl::LogUniform<int64_t>(*random, 0, mid_range);
620 }
621
622 if (state->minimize) {
623 state->reduced_domain =
624 domain.IntersectionWith({domain.Min(), domain.Min() + delta});
625 } else {
626 state->reduced_domain =
627 domain.IntersectionWith({domain.Max() - delta, domain.Max()});
628 }
629 FillDomainInProto(state->reduced_domain,
630 shaving_proto->mutable_variables(state->var_index));
631 }
632}
633
634bool VariablesShavingSolver::ResetAndSolveModel(int64_t task_id, State* state,
635 Model* local_model,
636 CpModelProto* shaving_proto) {
637 SatParameters* params = local_model->GetOrCreate<SatParameters>();
638 *params = local_params_;
639 params->set_random_seed(CombineSeed(local_params_.random_seed(), task_id));
640
641 bool has_no_overlap_2d = false;
642 {
643 absl::MutexLock lock(mutex_);
644 if (!FindNextVar(state)) return false;
645 CopyModelConnectedToVar(state, local_model, shaving_proto,
646 &has_no_overlap_2d);
647 ++num_vars_tried_;
648 }
649
650 // Use the current best solution as hint.
651 {
652 auto sol = shared_->response->SolutionPool().BestSolutions().GetSolution(0);
653 if (sol != nullptr) {
654 const std::vector<int64_t>& solution = sol->variable_values;
655 auto* hint = shaving_proto->mutable_solution_hint();
656 hint->clear_vars();
657 hint->clear_values();
658 for (int var = 0; var < solution.size(); ++var) {
659 hint->add_vars(var);
660 hint->add_values(solution[var]);
661 }
662 }
663 }
664
665 auto* time_limit = local_model->GetOrCreate<TimeLimit>();
666 shared_->time_limit->UpdateLocalLimit(time_limit);
667 time_limit->RegisterSecondaryExternalBooleanAsLimit(&stop_current_chunk_);
668 time_limit->ChangeDeterministicLimit(
669 time_limit->GetElapsedDeterministicTime() +
670 local_params_.shaving_search_deterministic_time());
671
672 shaving_proto->set_name(absl::StrCat("shaving_var_", state->var_index,
673 (state->minimize ? "_min" : "_max")));
674
675 // Presolve if asked.
676 if (local_params_.cp_model_presolve()) {
677 std::vector<int> postsolve_mapping;
678 CpModelProto mapping_proto;
679 auto context = std::make_unique<PresolveContext>(local_model, shaving_proto,
680 &mapping_proto);
681 const CpSolverStatus presolve_status =
682 PresolveCpModel(context.get(), &postsolve_mapping);
683 if (presolve_status == CpSolverStatus::INFEASIBLE) {
684 CpSolverResponse tmp_response;
685 tmp_response.set_status(CpSolverStatus::INFEASIBLE);
686 ProcessLocalResponse(tmp_response, *state);
687 return false;
688 }
689 }
690
691 // Hack: remove "non useful interval" from scheduling constraints.
692 // For now we only do that for no-overlap 2d, but we should generalize.
693 if (has_no_overlap_2d) {
694 std::vector<int> postsolve_mapping;
695 CpModelProto mapping_proto;
696 auto context = std::make_unique<PresolveContext>(local_model, shaving_proto,
697 &mapping_proto);
698 context->InitializeNewDomains();
699 context->UpdateNewConstraintsVariableUsage();
700 const int num_constraints = shaving_proto->constraints().size();
701 std::vector<bool> useful_interval(num_constraints, false);
702 std::vector<int> no_overalp_2d;
703 for (int c = 0; c < num_constraints; ++c) {
704 const ConstraintProto& ct = shaving_proto->constraints(c);
705 if (ct.constraint_case() == ConstraintProto::kInterval) {
706 for (const int var : UsedVariables(ct)) {
707 if (context->VarToConstraints(var).size() > 1) {
708 useful_interval[c] = true;
709 break;
710 }
711 }
712 } else if (ct.constraint_case() == ConstraintProto::kNoOverlap2D) {
713 no_overalp_2d.push_back(c);
714 }
715 }
716 for (const int c : no_overalp_2d) {
717 NoOverlap2DConstraintProto* data =
718 shaving_proto->mutable_constraints(c)->mutable_no_overlap_2d();
719 int new_size = 0;
720 const int num_intervals = data->x_intervals().size();
721 for (int i = 0; i < num_intervals; ++i) {
722 const int x = data->x_intervals(i);
723 const int y = data->y_intervals(i);
724 if (!useful_interval[x] && !useful_interval[y]) continue;
725 data->set_x_intervals(new_size, x);
726 data->set_y_intervals(new_size, y);
727 ++new_size;
728 }
729 data->mutable_x_intervals()->Truncate(new_size);
730 data->mutable_y_intervals()->Truncate(new_size);
731 }
732 }
733
734 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
735 const std::string shaving_name = absl::StrCat(
736 absl::GetFlag(FLAGS_cp_model_dump_prefix), "shaving_var_",
737 state->var_index, (state->minimize ? "_min" : "_max"), ".pb.txt");
738 LOG(INFO) << "Dumping shaving model to '" << shaving_name << "'.";
739 CHECK(WriteModelProtoToFile(*shaving_proto, shaving_name));
740 }
741
742 auto* local_response_manager =
743 local_model->GetOrCreate<SharedResponseManager>();
744 local_response_manager->InitializeObjective(*shaving_proto);
745 local_response_manager->SetSynchronizationMode(true);
746
747 // Tricky: If we aborted during the presolve above, some constraints might
748 // be in a non-canonical form (like having duplicates, etc...) and it seem
749 // not all our propagator code deal with that properly. So it is important
750 // to abort right away here.
751 //
752 // We had a bug when the LoadCpModel() below was returning infeasible on
753 // such non fully-presolved model.
754 if (time_limit->LimitReached()) return false;
755
756 LoadCpModel(*shaving_proto, local_model);
757 QuickSolveWithHint(*shaving_proto, local_model);
758 SolveLoadedCpModel(*shaving_proto, local_model);
759 return true;
760}
761
762} // namespace sat
763} // namespace operations_research
bool Connected(int node1, int node2)
bool AddEdge(int node1, int node2)
Domain IntersectionWith(const Domain &domain) const
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
::operations_research::sat::CpSolverStatus status() const
ObjectiveShavingSolver(const SatParameters &local_parameters, NeighborhoodGeneratorHelper *helper, SharedClasses *shared)
std::function< void()> GenerateTask(int64_t task_id) override
SubSolver(absl::string_view name, SubsolverType type)
Definition subsolver.h:48
void AddTaskDeterministicDuration(double deterministic_duration)
Definition subsolver.h:115
std::function< void()> GenerateTask(int64_t task_id) override
VariablesShavingSolver(const SatParameters &local_parameters, NeighborhoodGeneratorHelper *helper, SharedClasses *shared)
void ProcessLocalResponse(const CpSolverResponse &local_response, const State &state)
void SolveLoadedCpModel(const CpModelProto &model_proto, Model *model)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
int CombineSeed(int base_seed, int64_t delta)
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
std::vector< int > UsedVariables(const ConstraintProto &ct)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void QuickSolveWithHint(const CpModelProto &model_proto, Model *model)
bool ImportModelAndDomainsWithBasicPresolveIntoContext(const CpModelProto &in_model, absl::Span< const Domain > domains, std::function< bool(int)> active_constraints, PresolveContext *context, std::vector< int > *interval_mapping)
void PostsolveResponseWrapper(const SatParameters &params, int num_variable_in_original_model, const CpModelProto &mapping_proto, absl::Span< const int > postsolve_mapping, std::vector< int64_t > *solution)
void LoadCpModel(const CpModelProto &model_proto, Model *model)
OR-Tools root namespace.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
int64_t Max() const
Definition model.cc:346
int64_t Min() const
Definition model.cc:340