Google OR-Tools v9.12
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 <cstdint>
17#include <functional>
18#include <memory>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "absl/base/thread_annotations.h"
24#include "absl/flags/flag.h"
25#include "absl/log/check.h"
26#include "absl/random/distributions.h"
27#include "absl/strings/str_cat.h"
28#include "absl/synchronization/mutex.h"
37#include "ortools/sat/model.h"
41#include "ortools/sat/util.h"
44
45namespace operations_research {
46namespace sat {
47
49 const SatParameters& local_parameters, NeighborhoodGeneratorHelper* helper,
50 SharedClasses* shared)
51 : SubSolver(local_parameters.name(), FULL_PROBLEM),
52 local_params_(local_parameters),
53 helper_(helper),
54 shared_(shared),
55 local_proto_(shared->model_proto) {}
56
58 shared_->stat_tables->AddTimingStat(*this);
59}
60
62 if (shared_->SearchIsDone()) return false;
63
64 // We only support one task at the time.
65 absl::MutexLock mutex_lock(&mutex_);
66 return !task_in_flight_;
67}
68
69std::function<void()> ObjectiveShavingSolver::GenerateTask(int64_t task_id) {
70 {
71 absl::MutexLock mutex_lock(&mutex_);
72 stop_current_chunk_.store(false);
73 task_in_flight_ = true;
74 objective_lb_ = shared_->response->GetInnerObjectiveLowerBound();
75 objective_ub_ = shared_->response->GetInnerObjectiveUpperBound();
76 }
77 return [this, task_id]() {
78 if (ResetAndSolveModel(task_id)) {
79 const CpSolverResponse local_response =
80 local_sat_model_->GetOrCreate<SharedResponseManager>()->GetResponse();
81
82 if (local_response.status() == CpSolverStatus::OPTIMAL ||
83 local_response.status() == CpSolverStatus::FEASIBLE) {
84 std::vector<int64_t> solution_values(local_response.solution().begin(),
85 local_response.solution().end());
86 if (local_params_.cp_model_presolve()) {
87 const int num_original_vars = shared_->model_proto.variables_size();
88 PostsolveResponseWrapper(local_params_, num_original_vars,
89 mapping_proto_, postsolve_mapping_,
90 &solution_values);
91 }
92 shared_->response->NewSolution(solution_values, Info());
93 } else if (local_response.status() == CpSolverStatus::INFEASIBLE) {
94 absl::MutexLock mutex_lock(&mutex_);
95 shared_->response->UpdateInnerObjectiveBounds(
96 Info(), current_objective_target_ub_ + 1, objective_ub_);
97 }
98 }
99
100 absl::MutexLock mutex_lock(&mutex_);
101 task_in_flight_ = false;
102 if (local_sat_model_ != nullptr) {
103 const double dtime = local_sat_model_->GetOrCreate<TimeLimit>()
104 ->GetElapsedDeterministicTime();
106 shared_->time_limit->AdvanceDeterministicTime(dtime);
107 }
108 };
109}
110
112 absl::MutexLock mutex_lock(&mutex_);
113 if (!task_in_flight_) return;
114
115 // We are just waiting for the inner code to check the time limit or
116 // to return nicely.
117 if (stop_current_chunk_) return;
118
119 // TODO(user): Also stop if we have enough newly fixed / improved root level
120 // bounds so that we think it is worth represolving and restarting.
121 if (shared_->SearchIsDone()) {
122 stop_current_chunk_.store(true);
123 }
124
125 // The current objective lower bound has been improved, restarting.
126 if (shared_->response->GetInnerObjectiveLowerBound() > objective_lb_) {
127 stop_current_chunk_.store(true);
128 }
129
130 // A solution has been found that is better than the current target
131 // objective upper bound. Restarting to use a smaller delta.
132 if (shared_->response->GetInnerObjectiveUpperBound() <=
133 current_objective_target_ub_ &&
134 current_objective_target_ub_ != objective_lb_) {
135 stop_current_chunk_.store(true);
136 }
137
138 // If the range has been reduced enough to warrant a delta of 1, while the
139 // current search uses a delta > 1. Restarting to switch to the delta of 1.
140 if (current_objective_target_ub_ != objective_lb_ &&
141 shared_->response->GetInnerObjectiveUpperBound() -
142 shared_->response->GetInnerObjectiveLowerBound() <=
143 local_params_.shaving_search_threshold()) {
144 stop_current_chunk_.store(true);
145 }
146}
147
148std::string ObjectiveShavingSolver::Info() {
149 return absl::StrCat(name(), " (vars=", local_proto_.variables().size(),
150 " csts=", local_proto_.constraints().size(), ")");
151}
152
153bool ObjectiveShavingSolver::ResetAndSolveModel(int64_t task_id) {
154 local_sat_model_ = std::make_unique<Model>(name());
155 *local_sat_model_->GetOrCreate<SatParameters>() = local_params_;
156 local_sat_model_->GetOrCreate<SatParameters>()->set_random_seed(
157 CombineSeed(local_params_.random_seed(), task_id));
158
159 auto* time_limit = local_sat_model_->GetOrCreate<TimeLimit>();
161 time_limit->RegisterSecondaryExternalBooleanAsLimit(&stop_current_chunk_);
162
163 auto* random = local_sat_model_->GetOrCreate<ModelRandomGenerator>();
164
165 // We copy the model.
166 local_proto_ = shared_->model_proto;
167 *local_proto_.mutable_variables() =
168 helper_->FullNeighborhood().delta.variables();
169
170 // Store the current lb in local variable.
171 IntegerValue objective_lb;
172 IntegerValue chosen_objective_ub;
173 {
174 absl::MutexLock mutex_lock(&mutex_);
175 objective_lb = objective_lb_;
176 if (objective_ub_ - objective_lb <=
177 local_params_.shaving_search_threshold()) {
178 current_objective_target_ub_ = objective_lb;
179 } else {
180 const IntegerValue mid = (objective_ub_ - objective_lb) / 2;
181 current_objective_target_ub_ =
182 objective_lb + absl::LogUniform<int64_t>(*random, 0, mid.value());
183 }
184 chosen_objective_ub = current_objective_target_ub_;
185 VLOG(2) << name() << ": from [" << objective_lb.value() << ".."
186 << objective_ub_.value() << "] <= " << chosen_objective_ub.value();
187 }
188
189 // We replace the objective by a constraint, objective in [lb, target_ub].
190 // We modify local_proto_ to a pure feasibility problem.
191 // Not having the objective open up more presolve reduction.
192 Domain obj_domain = Domain(objective_lb.value(), chosen_objective_ub.value());
193 if (local_proto_.objective().domain_size() > 1) {
194 // Intersect with the first interval of the objective domain.
195 obj_domain =
196 obj_domain.IntersectionWith(Domain(local_proto_.objective().domain(0),
197 local_proto_.objective().domain(1)));
198 }
199 if (local_proto_.objective().vars().size() == 1 &&
200 local_proto_.objective().coeffs(0) == 1) {
201 auto* obj_var =
202 local_proto_.mutable_variables(local_proto_.objective().vars(0));
203 const Domain reduced_var_domain = obj_domain.IntersectionWith(
204 Domain(obj_var->domain(0), obj_var->domain(1)));
205 if (reduced_var_domain.IsEmpty()) {
206 return false;
207 }
208 FillDomainInProto(reduced_var_domain, obj_var);
209 } else {
210 auto* obj = local_proto_.add_constraints()->mutable_linear();
211 *obj->mutable_vars() = local_proto_.objective().vars();
212 *obj->mutable_coeffs() = local_proto_.objective().coeffs();
213 if (obj_domain.IsEmpty()) {
214 return false;
215 }
216 FillDomainInProto(obj_domain, obj);
217 }
218
219 // Clear the objective.
220 local_proto_.clear_objective();
221 local_proto_.set_name(
222 absl::StrCat(local_proto_.name(), "_obj_shaving_", objective_lb.value()));
223
224 // Dump?
225 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
226 const std::string name =
227 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
228 "objective_shaving_", objective_lb.value(), ".pb.txt");
229 LOG(INFO) << "Dumping objective shaving model to '" << name << "'.";
230 CHECK(WriteModelProtoToFile(local_proto_, name));
231 }
232
233 // Presolve if asked.
234 if (local_params_.cp_model_presolve()) {
235 mapping_proto_.Clear();
236 postsolve_mapping_.clear();
237 auto context = std::make_unique<PresolveContext>(
238 local_sat_model_.get(), &local_proto_, &mapping_proto_);
239 const CpSolverStatus presolve_status =
240 PresolveCpModel(context.get(), &postsolve_mapping_);
241 if (presolve_status == CpSolverStatus::INFEASIBLE) {
242 absl::MutexLock mutex_lock(&mutex_);
243 shared_->response->UpdateInnerObjectiveBounds(
244 Info(), chosen_objective_ub + 1, kMaxIntegerValue);
245 return false;
246 }
247 }
248
249 // Tricky: If we aborted during the presolve above, some constraints might
250 // be in a non-canonical form (like having duplicates, etc...) and it seem
251 // not all our propagator code deal with that properly. So it is important
252 // to abort right away here.
253 //
254 // We had a bug when the LoadCpModel() below was returning infeasible on
255 // such non fully-presolved model.
256 if (time_limit->LimitReached()) return false;
257
258 LoadCpModel(local_proto_, local_sat_model_.get());
259 return true;
260}
261
263 const SatParameters& local_parameters, SharedClasses* shared)
264 : SubSolver(local_parameters.name(), FULL_PROBLEM),
265 local_params_(local_parameters),
266 shared_(shared),
267 stop_current_chunk_(false),
268 model_proto_(shared->model_proto) {
269 if (shared_->bounds != nullptr) {
270 shared_bounds_id_ = shared_->bounds->RegisterNewId();
271 }
272
273 absl::MutexLock mutex_lock(&mutex_);
274 for (const IntegerVariableProto& var_proto : model_proto_.variables()) {
275 var_domains_.push_back(ReadDomainFromProto(var_proto));
276 }
277}
278
280 if (!VLOG_IS_ON(1)) return;
281 if (shared_ == nullptr || shared_->stats == nullptr) return;
282 std::vector<std::pair<std::string, int64_t>> stats;
283 absl::MutexLock mutex_lock(&mutex_);
284 stats.push_back({"variable_shaving/num_vars_tried", num_vars_tried_});
285 stats.push_back({"variable_shaving/num_vars_shaved", num_vars_shaved_});
286 stats.push_back(
287 {"variable_shaving/num_infeasible_found", num_infeasible_found_});
288 shared_->stats->AddStats(stats);
289}
290
292 return !shared_->SearchIsDone();
293}
294
296 const CpSolverResponse& local_response, const State& state) {
297 if (local_response.status() != CpSolverStatus::INFEASIBLE) return;
298
299 absl::MutexLock lock(&mutex_);
300 const Domain domain = var_domains_[state.var_index];
301 Domain new_domain = domain;
302 ++num_infeasible_found_;
303 new_domain = domain.IntersectionWith(state.reduced_domain.Complement());
304 VLOG(1) << name() << ": var(" << state.var_index << ") " << domain << " ==> "
305 << new_domain;
306
307 if (domain != new_domain) {
308 ++num_vars_shaved_;
309 if (shared_->bounds != nullptr && !new_domain.IsEmpty()) {
310 shared_->bounds->ReportPotentialNewBounds(
311 name(), {state.var_index}, {new_domain.Min()}, {new_domain.Max()});
312 }
313 var_domains_[state.var_index] = new_domain;
314 if (var_domains_[state.var_index].IsEmpty()) {
315 shared_->response->NotifyThatImprovingProblemIsInfeasible(
316 "Unsat during variables shaving");
317 return;
318 }
319 }
320}
321
322std::function<void()> VariablesShavingSolver::GenerateTask(int64_t task_id) {
323 return [this, task_id]() mutable {
324 Model local_sat_model;
325 CpModelProto shaving_proto;
326 State state;
327 if (ResetAndSolveModel(task_id, &state, &local_sat_model, &shaving_proto)) {
328 const CpSolverResponse local_response =
329 local_sat_model.GetOrCreate<SharedResponseManager>()->GetResponse();
330 ProcessLocalResponse(local_response, state);
331 }
332
333 absl::MutexLock mutex_lock(&mutex_);
334 const double dtime =
335 local_sat_model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime();
337 shared_->time_limit->AdvanceDeterministicTime(dtime);
338 };
339}
340
342 absl::MutexLock mutex_lock(&mutex_);
343 // We are just waiting for the inner code to check the time limit or
344 // to return nicely.
345 if (stop_current_chunk_) return;
346
347 if (shared_->SearchIsDone()) {
348 stop_current_chunk_.store(true);
349 }
350
351 if (shared_->bounds != nullptr) {
352 std::vector<int> model_variables;
353 std::vector<int64_t> new_lower_bounds;
354 std::vector<int64_t> new_upper_bounds;
355 shared_->bounds->GetChangedBounds(shared_bounds_id_, &model_variables,
356 &new_lower_bounds, &new_upper_bounds);
357
358 for (int i = 0; i < model_variables.size(); ++i) {
359 const int var = model_variables[i];
360 const int64_t new_lb = new_lower_bounds[i];
361 const int64_t new_ub = new_upper_bounds[i];
362 const Domain& old_domain = var_domains_[var];
363 const Domain new_domain =
364 old_domain.IntersectionWith(Domain(new_lb, new_ub));
365 if (new_domain.IsEmpty()) {
366 shared_->response->NotifyThatImprovingProblemIsInfeasible(
367 "Unsat during variables shaving");
368 continue;
369 }
370 var_domains_[var] = new_domain;
371 }
372 }
373}
374
375std::string VariablesShavingSolver::Info() {
376 return absl::StrCat(name(), " (vars=", model_proto_.variables().size(),
377 " csts=", model_proto_.constraints().size(), ")");
378}
379
380int64_t VariablesShavingSolver::DomainSize(int var) const
381 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
382 return var_domains_[var].Size();
383}
384
385bool VariablesShavingSolver::VarIsFixed(int int_var) const
386 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
387 return var_domains_[int_var].IsFixed();
388}
389
390bool VariablesShavingSolver::ConstraintIsInactive(int c) const
391 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
392 for (const int ref : model_proto_.constraints(c).enforcement_literal()) {
393 const int var = PositiveRef(ref);
394 if (VarIsFixed(var) && var_domains_[var].Min() == (var == ref ? 0 : 1)) {
395 return true;
396 }
397 }
398 return false;
399}
400
401bool VariablesShavingSolver::FindNextVar(State* state)
402 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
403 const int num_vars = var_domains_.size();
404 const int max_index = 2 * num_vars;
405 for (int i = 0; i < 2 * num_vars; ++i) {
406 if (++current_index_ == max_index) current_index_ = 0;
407 const int var = current_index_ / 2;
408 if (VarIsFixed(var)) continue;
409 // Let's not shave the single var objective. There are enough workers
410 // looking at it.
411 if (model_proto_.has_objective() &&
412 model_proto_.objective().vars_size() == 1 &&
413 var == model_proto_.objective().vars(0)) {
414 continue;
415 }
416
417 state->var_index = var;
418 state->minimize = current_index_ % 2 == 0;
419 return true;
420 }
421 return false;
422}
423
424void VariablesShavingSolver::CopyModelConnectedToVar(
425 State* state, Model* local_sat_model, CpModelProto* shaving_proto)
426 ABSL_SHARED_LOCKS_REQUIRED(mutex_) {
427 auto var_to_node = [](int var) { return var; };
428 auto ct_to_node = [this](int ct) {
429 return ct + model_proto_.variables_size();
430 };
431
432 // Build the connected component graph.
433 DenseConnectedComponentsFinder cc_finder;
434 cc_finder.SetNumberOfNodes(model_proto_.constraints_size() +
435 model_proto_.variables_size());
436 for (int i = 0; i < model_proto_.constraints_size(); ++i) {
437 if (ConstraintIsInactive(i)) continue;
438 const ConstraintProto& ct = model_proto_.constraints(i);
439 const int ct_node = ct_to_node(i);
440 for (const int var : UsedVariables(ct)) {
441 if (VarIsFixed(var)) continue;
442 cc_finder.AddEdge(ct_node, var_to_node(var));
443 }
444 for (const int interval : UsedIntervals(ct)) {
445 cc_finder.AddEdge(ct_node, ct_to_node(interval));
446 }
447 }
448
449 DCHECK(shaving_proto->variables().empty());
450 DCHECK(shaving_proto->constraints().empty());
451 const int root_index = var_to_node(state->var_index);
452
453 const auto active_constraints = [&cc_finder, root_index,
454 &ct_to_node](int ct) {
455 return cc_finder.Connected(root_index, ct_to_node(ct));
456 };
457
458 PresolveContext context(local_sat_model, shaving_proto, nullptr);
460 model_proto_, var_domains_, active_constraints, &context);
461
462 if (VLOG_IS_ON(2) &&
463 shaving_proto->constraints_size() < model_proto_.constraints_size()) {
464 int num_active_variables = 0;
465 for (int i = 0; i < var_domains_.size(); ++i) {
466 if (cc_finder.Connected(root_index, var_to_node(i))) {
467 ++num_active_variables;
468 }
469 }
470
471 LOG(INFO) << "#constraints:" << shaving_proto->constraints_size() << "/"
472 << model_proto_.constraints_size()
473 << " #variables:" << num_active_variables << "/"
474 << var_domains_.size();
475 }
476
477 const Domain domain =
478 ReadDomainFromProto(shaving_proto->variables(state->var_index));
479 shaving_proto->clear_objective();
480
481 int64_t delta = 0;
482 if (domain.Size() > local_params_.shaving_search_threshold()) {
483 const int64_t mid_range = (domain.Max() - domain.Min()) / 2;
484 auto* random = local_sat_model->GetOrCreate<ModelRandomGenerator>();
485 delta = absl::LogUniform<int64_t>(*random, 0, mid_range);
486 }
487
488 if (state->minimize) {
489 state->reduced_domain =
490 domain.IntersectionWith({domain.Min(), domain.Min() + delta});
491 } else {
492 state->reduced_domain =
493 domain.IntersectionWith({domain.Max() - delta, domain.Max()});
494 }
495
496 FillDomainInProto(state->reduced_domain,
497 shaving_proto->mutable_variables(state->var_index));
498
499 if (absl::GetFlag(FLAGS_cp_model_dump_submodels)) {
500 const std::string shaving_name = absl::StrCat(
501 absl::GetFlag(FLAGS_cp_model_dump_prefix), "shaving_var_",
502 state->var_index, (state->minimize ? "_min" : "_max"), ".pb.txt");
503 LOG(INFO) << "Dumping shaving model to '" << shaving_name << "'.";
504 CHECK(WriteModelProtoToFile(*shaving_proto, shaving_name));
505 }
506}
507
508bool VariablesShavingSolver::ResetAndSolveModel(int64_t task_id, State* state,
509 Model* local_sat_model,
510 CpModelProto* shaving_proto) {
511 *local_sat_model->GetOrCreate<SatParameters>() = local_params_;
512 local_sat_model->GetOrCreate<SatParameters>()->set_random_seed(
513 CombineSeed(local_params_.random_seed(), task_id));
514
515 {
516 absl::MutexLock lock(&mutex_);
517 if (!FindNextVar(state)) return false;
518 CopyModelConnectedToVar(state, local_sat_model, shaving_proto);
519 ++num_vars_tried_;
520 }
521
522 auto* time_limit = local_sat_model->GetOrCreate<TimeLimit>();
523 shared_->time_limit->UpdateLocalLimit(time_limit);
524 time_limit->RegisterSecondaryExternalBooleanAsLimit(&stop_current_chunk_);
525 time_limit->ChangeDeterministicLimit(
526 time_limit->GetElapsedDeterministicTime() +
527 local_params_.shaving_search_deterministic_time());
528
529 shaving_proto->set_name(absl::StrCat("shaving_var_", state->var_index,
530 (state->minimize ? "_min" : "_max")));
531
532 // Presolve if asked.
533 if (local_params_.cp_model_presolve()) {
534 std::vector<int> postsolve_mapping;
535 CpModelProto mapping_proto;
536 auto context = std::make_unique<PresolveContext>(
537 local_sat_model, shaving_proto, &mapping_proto);
538 const CpSolverStatus presolve_status =
539 PresolveCpModel(context.get(), &postsolve_mapping);
540 if (presolve_status == CpSolverStatus::INFEASIBLE) {
541 CpSolverResponse tmp_response;
542 tmp_response.set_status(CpSolverStatus::INFEASIBLE);
543 ProcessLocalResponse(tmp_response, *state);
544 return false;
545 }
546 }
547
548 auto* local_response_manager =
549 local_sat_model->GetOrCreate<SharedResponseManager>();
550 local_response_manager->InitializeObjective(*shaving_proto);
551 local_response_manager->SetSynchronizationMode(true);
552
553 // Tricky: If we aborted during the presolve above, some constraints might
554 // be in a non-canonical form (like having duplicates, etc...) and it seem
555 // not all our propagator code deal with that properly. So it is important
556 // to abort right away here.
557 //
558 // We had a bug when the LoadCpModel() below was returning infeasible on
559 // such non fully-presolved model.
560 if (time_limit->LimitReached()) return false;
561
562 LoadCpModel(*shaving_proto, local_sat_model);
563 SolveLoadedCpModel(*shaving_proto, local_sat_model);
564 return true;
565}
566
567} // namespace sat
568} // namespace operations_research
bool Connected(int node1, int node2)
bool AddEdge(int node1, int node2)
Domain IntersectionWith(const Domain &domain) const
void UpdateLocalLimit(TimeLimit *local_limit)
Definition time_limit.h:346
Neighborhood FullNeighborhood() const
Returns a neighborhood that correspond to the full problem.
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:114
std::string name() const
Returns the name of this SubSolver. Used in logs.
Definition subsolver.h:95
VariablesShavingSolver(const SatParameters &local_parameters, SharedClasses *shared)
std::function< void()> GenerateTask(int64_t task_id) override
void ProcessLocalResponse(const CpSolverResponse &local_response, const State &state)
time_limit
Definition solve.cc:22
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)
We assume delta >= 0 and we only use the low bit of delta.
bool ImportModelAndDomainsWithBasicPresolveIntoContext(const CpModelProto &in_model, absl::Span< const Domain > domains, std::function< bool(int)> active_constraints, PresolveContext *context)
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
Convenient wrapper to call the full presolve.
std::vector< int > UsedVariables(const ConstraintProto &ct)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
Returns the sorted list of interval used by a constraint.
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
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)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t Max() const
Returns the max of the domain.
Definition model.cc:346
int64_t Min() const
Returns the min of the domain.
Definition model.cc:340
const CpModelProto & model_proto
These are never nullptr.