Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_sat_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 <atomic>
17#include <cmath>
18#include <cstdint>
19#include <functional>
20#include <limits>
21#include <memory>
22#include <optional>
23#include <string>
24#include <utility>
25#include <vector>
26
27#include "absl/base/attributes.h"
28#include "absl/base/nullability.h"
29#include "absl/base/thread_annotations.h"
30#include "absl/container/flat_hash_set.h"
31#include "absl/functional/any_invocable.h"
32#include "absl/log/check.h"
33#include "absl/log/log.h"
34#include "absl/memory/memory.h"
35#include "absl/status/status.h"
36#include "absl/status/statusor.h"
37#include "absl/strings/match.h"
38#include "absl/strings/str_cat.h"
39#include "absl/strings/str_join.h"
40#include "absl/strings/str_split.h"
41#include "absl/strings/string_view.h"
42#include "absl/synchronization/mutex.h"
43#include "absl/time/clock.h"
44#include "absl/time/time.h"
45#include "absl/types/span.h"
69
70namespace operations_research {
71namespace math_opt {
72
73namespace {
74
75constexpr double kInf = std::numeric_limits<double>::infinity();
76
77constexpr SupportedProblemStructures kCpSatSupportedStructures = {
78 .integer_variables = SupportType::kSupported,
79 .quadratic_objectives = SupportType::kNotImplemented,
80 .quadratic_constraints = SupportType::kNotImplemented,
81 .sos1_constraints = SupportType::kNotImplemented,
82 .sos2_constraints = SupportType::kNotImplemented,
83 .indicator_constraints = SupportType::kNotImplemented};
84
85// Returns true on success.
86bool ApplyCutoff(const double cutoff, MPModelProto* model) {
87 // TODO(b/204083726): we need to be careful here if we support quadratic
88 // objectives
89 if (model->has_quadratic_objective()) {
90 return false;
91 }
92 // CP-SAT detects a constraint parallel to the objective and uses it as
93 // an objective bound, which is the closest we can get to cutoff.
94 // See FindDuplicateConstraints() in CP-SAT codebase.
95 MPConstraintProto* const cutoff_constraint = model->add_constraint();
96 for (int i = 0; i < model->variable_size(); ++i) {
97 const double obj_coef = model->variable(i).objective_coefficient();
98 if (obj_coef != 0) {
99 cutoff_constraint->add_var_index(i);
100 cutoff_constraint->add_coefficient(obj_coef);
101 }
102 }
103 const double cutoff_minus_offset = cutoff - model->objective_offset();
104 if (model->maximize()) {
105 // Add the constraint obj >= cutoff
106 cutoff_constraint->set_lower_bound(cutoff_minus_offset);
107 } else {
108 // Add the constraint obj <= cutoff
109 cutoff_constraint->set_upper_bound(cutoff_minus_offset);
110 }
111 return true;
112}
113
114// Returns a list of warnings from parameter settings that were
115// invalid/unsupported (specific to CP-SAT), one element per bad parameter.
116std::vector<std::string> SetSolveParameters(
117 const SolveParametersProto& parameters, const bool has_message_callback,
118 MPModelRequest& request) {
119 std::vector<std::string> warnings;
120 if (parameters.has_time_limit()) {
121 request.set_solver_time_limit_seconds(absl::ToDoubleSeconds(
122 util_time::DecodeGoogleApiProto(parameters.time_limit()).value()));
123 }
124 if (parameters.has_iteration_limit()) {
125 warnings.push_back(
126 "The iteration_limit parameter is not supported for CP-SAT.");
127 }
128 if (parameters.has_node_limit()) {
129 warnings.push_back("The node_limit parameter is not supported for CP-SAT.");
130 }
131
132 // Build CP SAT parameters by first initializing them from the common
133 // parameters, and then using the values in `solver_specific_parameters` to
134 // overwrite them if necessary.
135 //
136 // We don't need to set max_time_in_seconds since we already pass it in the
137 // `request.solver_time_limit_seconds`. The logic of `SatSolveProto()` will
138 // apply the logic we want here.
139 sat::SatParameters sat_parameters;
140
141 // By default CP-SAT catches SIGINT (Ctrl-C) to interrupt the solve but we
142 // don't want this behavior when the users uses CP-SAT through MathOpt.
143 sat_parameters.set_catch_sigint_signal(false);
144
145 if (parameters.has_random_seed()) {
146 sat_parameters.set_random_seed(parameters.random_seed());
147 }
148 if (parameters.has_threads()) {
149 sat_parameters.set_num_workers(parameters.threads());
150 }
151 if (parameters.has_relative_gap_tolerance()) {
152 sat_parameters.set_relative_gap_limit(parameters.relative_gap_tolerance());
153 }
154
155 if (parameters.has_absolute_gap_tolerance()) {
156 sat_parameters.set_absolute_gap_limit(parameters.absolute_gap_tolerance());
157 }
158 // cutoff_limit is handled outside this function as it modifies the model.
159 if (parameters.has_best_bound_limit()) {
160 warnings.push_back(
161 "The best_bound_limit parameter is not supported for CP-SAT.");
162 }
163 if (parameters.has_objective_limit()) {
164 warnings.push_back(
165 "The objective_limit parameter is not supported for CP-SAT.");
166 }
167 if (parameters.has_solution_limit()) {
168 if (parameters.solution_limit() == 1) {
169 sat_parameters.set_stop_after_first_solution(true);
170 } else {
171 warnings.push_back(absl::StrCat(
172 "The CP-SAT solver only supports value 1 for solution_limit, found: ",
173 parameters.solution_limit()));
174 }
175 }
176 if (parameters.has_solution_pool_size()) {
177 sat_parameters.set_solution_pool_size(parameters.solution_pool_size());
178 sat_parameters.set_fill_additional_solutions_in_response(true);
179 }
180 if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
181 warnings.push_back(
182 absl::StrCat("Setting lp_algorithm (was set to ",
183 ProtoEnumToString(parameters.lp_algorithm()),
184 ") is not supported for CP_SAT solver"));
185 }
186 if (parameters.presolve() != EMPHASIS_UNSPECIFIED) {
187 switch (parameters.presolve()) {
188 case EMPHASIS_OFF:
189 sat_parameters.set_cp_model_presolve(false);
190 break;
191 case EMPHASIS_LOW:
192 case EMPHASIS_MEDIUM:
193 case EMPHASIS_HIGH:
195 sat_parameters.set_cp_model_presolve(true);
196 break;
197 default:
198 LOG(FATAL) << "Presolve emphasis: "
199 << ProtoEnumToString(parameters.presolve())
200 << " unknown, error setting CP-SAT parameters";
201 }
202 }
203 if (parameters.scaling() != EMPHASIS_UNSPECIFIED) {
204 warnings.push_back(absl::StrCat("Setting the scaling (was set to ",
205 ProtoEnumToString(parameters.scaling()),
206 ") is not supported for CP_SAT solver"));
207 }
208 if (parameters.cuts() != EMPHASIS_UNSPECIFIED) {
209 switch (parameters.cuts()) {
210 case EMPHASIS_OFF:
211 // This is not very maintainable, but CP-SAT doesn't expose the
212 // parameters we need.
213 sat_parameters.set_add_cg_cuts(false);
214 sat_parameters.set_add_mir_cuts(false);
215 sat_parameters.set_add_zero_half_cuts(false);
216 sat_parameters.set_add_clique_cuts(false);
217 sat_parameters.set_max_all_diff_cut_size(0);
218 sat_parameters.set_add_lin_max_cuts(false);
219 break;
220 case EMPHASIS_LOW:
221 case EMPHASIS_MEDIUM:
222 case EMPHASIS_HIGH:
224 break;
225 default:
226 LOG(FATAL) << "Cut emphasis: " << ProtoEnumToString(parameters.cuts())
227 << " unknown, error setting CP-SAT parameters";
228 }
229 }
230 if (parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
231 warnings.push_back(absl::StrCat("Setting the heuristics (was set to ",
232 ProtoEnumToString(parameters.heuristics()),
233 ") is not supported for CP_SAT solver"));
234 }
235 sat_parameters.MergeFrom(parameters.cp_sat());
236
237 // We want to override specifically SAT parameters independently from the user
238 // input when a message callback is used to prevent wrongful writes to stdout
239 // or disabling of messages via these parameters.
240 if (has_message_callback) {
241 // When enable_internal_solver_output is used, CP-SAT solver actually has
242 // the same effect as setting log_search_progress to true.
243 sat_parameters.set_log_search_progress(true);
244
245 // Default value of log_to_stdout is true; but even if it was not the case,
246 // we don't want to write to stdout when a message callback is used.
247 sat_parameters.set_log_to_stdout(false);
248 } else {
249 // We only set enable_internal_solver_output when we have no message
250 // callback.
251 request.set_enable_internal_solver_output(parameters.enable_output());
252 }
253
254 request.set_solver_specific_parameters(
255 EncodeParametersAsString(sat_parameters));
256 return warnings;
257}
258
259absl::StatusOr<TerminationProto> GetTermination(
260 const bool is_interrupted, const bool maximize, const bool used_cutoff,
261 const MPSolutionResponse& response) {
262 switch (response.status()) {
263 case MPSOLVER_OPTIMAL:
264 return OptimalTerminationProto(response.objective_value(),
265 response.best_objective_bound(),
266 response.status_str());
267 break;
269 if (used_cutoff) {
270 return CutoffTerminationProto(maximize, response.status_str());
271 } else {
272 // By convention infeasible MIPs are always dual feasible.
274 maximize, /*dual_feasibility_status=*/FEASIBILITY_STATUS_FEASIBLE,
275 response.status_str());
276 }
277 break;
279 // For a basic unbounded problem, CP-SAT internally returns
280 // INFEASIBLE_OR_UNBOUNDED after presolve but MPSolver statuses don't
281 // support that thus it get transformed in MPSOLVER_UNKNOWN_STATUS with
282 // a status_str of
283 //
284 // "Problem proven infeasible or unbounded during MIP presolve"
285 //
286 // There may be some other cases where CP-SAT returns UNKNOWN here so we
287 // only return TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED when the
288 // status_str is detected. Otherwise we return OTHER_ERROR.
289 //
290 // TODO(b/202159173): A better solution would be to use CP-SAT API
291 // directly which may help further improve the statuses.
292 if (absl::StrContains(response.status_str(), "infeasible or unbounded")) {
294 maximize,
295 /*dual_feasibility_status=*/FEASIBILITY_STATUS_UNDETERMINED,
296 response.status_str());
297 } else {
299 response.status_str());
300 }
301 break;
304 maximize, is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
305 response.objective_value(), response.best_objective_bound(),
306 response.status_str());
307 break;
310 maximize, is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
311 /*optional_dual_objective=*/std::nullopt, response.status_str());
312 break;
314 return absl::InternalError(
315 absl::StrCat("cp-sat solver returned MODEL_INVALID, details: ",
316 response.status_str()));
319 << "invalid cp-sat parameters: " << response.status_str();
320 default:
321 return absl::InternalError(
322 absl::StrCat("unexpected solve status: ", response.status()));
323 }
324 return absl::InternalError(
325 absl::StrCat("unimplemented solve status: ", response.status()));
326}
327
328// This class gathers the solution callback and best bound callback together
329// with some solver state that we need to update as the solver progresses.
330class CpSatCallbacks {
331 public:
332 CpSatCallbacks(const absl_nullable SolverInterface::Callback& cb
333 ABSL_ATTRIBUTE_LIFETIME_BOUND,
334 SolveInterrupter* absl_nonnull local_interrupter
335 ABSL_ATTRIBUTE_LIFETIME_BOUND,
336 absl_nonnull absl::AnyInvocable<
337 SparseDoubleVectorProto(absl::Span<const double>) const>
338 extract_solution,
339 absl::flat_hash_set<CallbackEventProto> events,
340 bool is_maximize);
341
342 // CpSatCallbacks is neither copyable nor movable as callbacks point to it.
343 CpSatCallbacks(const CpSatCallbacks&) = delete;
344 CpSatCallbacks& operator=(const CpSatCallbacks&) = delete;
345
346 // Returns a solution callback that wraps the user callback and updates the
347 // state of CpSatCallbacks. Returns nullptr if it is not needed.
348 absl_nullable std::function<void(const MPSolution&)> MakeSolutionCallback();
349
350 // Returns a best bound callback that wraps the user callback and updates the
351 // state of CpSatCallbacks. Returns nullptr if it is not needed.
352 absl_nullable std::function<void(const double)> MakeBestBoundCallback();
353
354 absl::Status error() const {
355 absl::MutexLock lock(mutex_);
356 return error_;
357 }
358
359 private:
360 void ExecuteCallback(const CallbackDataProto& cb_data);
361 void UpdateMipStatsFromNewSolution(const MPSolution& mp_solution)
362 ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
363
364 const SolverInterface::Callback& cb_;
365 SolveInterrupter* absl_nonnull const local_interrupter_;
366 const absl::AnyInvocable<SparseDoubleVectorProto(absl::Span<const double>)
367 const>
368 extract_solution_;
369 const bool has_mip_solution_event_;
370 const bool has_mip_event_;
371 const bool is_maximize_;
372
373 mutable absl::Mutex mutex_;
374 absl::Status error_ ABSL_GUARDED_BY(mutex_) = absl::OkStatus();
375 CallbackDataProto::MipStats current_mip_stats_ ABSL_GUARDED_BY(mutex_);
376};
377
378CpSatCallbacks::CpSatCallbacks(
379 const SolverInterface::Callback& cb ABSL_ATTRIBUTE_LIFETIME_BOUND,
380 SolveInterrupter* absl_nonnull local_interrupter
381 ABSL_ATTRIBUTE_LIFETIME_BOUND,
382 absl_nonnull
383 absl::AnyInvocable<SparseDoubleVectorProto(absl::Span<const double>) const>
384 extract_solution ABSL_ATTRIBUTE_LIFETIME_BOUND,
385 absl::flat_hash_set<CallbackEventProto> events, const bool is_maximize)
386 : cb_(cb),
387 local_interrupter_(local_interrupter),
388 extract_solution_(std::move(extract_solution)),
389 // If there is no user callback, we make sure not calling it.
390 has_mip_solution_event_(cb != nullptr &&
391 events.contains(CALLBACK_EVENT_MIP_SOLUTION)),
392 has_mip_event_(cb != nullptr && events.contains(CALLBACK_EVENT_MIP)),
393 is_maximize_(is_maximize) {
394 current_mip_stats_.set_primal_bound(is_maximize ? -kInf : kInf);
395 current_mip_stats_.set_dual_bound(is_maximize ? kInf : -kInf);
396 current_mip_stats_.set_number_of_solutions_found(0);
397}
398
399std::function<void(const MPSolution&)> absl_nullable
400CpSatCallbacks::MakeSolutionCallback() {
401 if (!has_mip_solution_event_ && !has_mip_event_) {
402 return nullptr;
403 }
404 if (!has_mip_solution_event_) {
405 return [this](const MPSolution& mp_solution) {
406 absl::MutexLock lock(mutex_);
407 UpdateMipStatsFromNewSolution(mp_solution);
408 };
409 }
410 return [this](const MPSolution& mp_solution) {
411 CallbackDataProto cb_data;
412 cb_data.set_event(CALLBACK_EVENT_MIP_SOLUTION);
413 *cb_data.mutable_primal_solution_vector() =
414 extract_solution_(mp_solution.variable_value());
415 {
416 absl::MutexLock lock(mutex_);
417 UpdateMipStatsFromNewSolution(mp_solution);
418 *cb_data.mutable_mip_stats() = current_mip_stats_;
419 }
420 ExecuteCallback(cb_data);
421 };
422}
423
424std::function<void(const double)> absl_nullable
425CpSatCallbacks::MakeBestBoundCallback() {
426 if (!has_mip_solution_event_ && !has_mip_event_) {
427 return nullptr;
428 }
429 if (!has_mip_event_) {
430 return [this](const double best_bound) {
431 absl::MutexLock lock(mutex_);
432 current_mip_stats_.set_dual_bound(best_bound);
433 };
434 }
435 return [this](const double best_bound) {
436 CallbackDataProto cb_data;
437 cb_data.set_event(CALLBACK_EVENT_MIP);
438 {
439 absl::MutexLock lock(mutex_);
440 current_mip_stats_.set_dual_bound(best_bound);
441 *cb_data.mutable_mip_stats() = current_mip_stats_;
442 }
443 ExecuteCallback(cb_data);
444 };
445}
446
447void CpSatCallbacks::ExecuteCallback(const CallbackDataProto& cb_data) {
448 {
449 absl::MutexLock lock(mutex_);
450 if (!error_.ok()) {
451 // A previous callback failed.
452 return;
453 }
454 }
455 const absl::StatusOr<CallbackResultProto> cb_result = cb_(cb_data);
456 // Note cb_result.cuts and cb_result.suggested solutions are not supported
457 // by CP-SAT and we have validated they are empty.
458 if (!cb_result.ok()) {
459 {
460 absl::MutexLock lock(mutex_);
461 error_ = cb_result.status();
462 }
463 // Note: we will be returning a status error, we do not need to worry
464 // about interpreting this as TERMINATION_REASON_INTERRUPTED.
465 local_interrupter_->Interrupt();
466 } else if (cb_result->terminate()) {
467 local_interrupter_->Interrupt();
468 }
469}
470
471void CpSatCallbacks::UpdateMipStatsFromNewSolution(
472 const MPSolution& mp_solution) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
473 if (is_maximize_) {
474 current_mip_stats_.set_primal_bound(std::fmax(
475 current_mip_stats_.primal_bound(), mp_solution.objective_value()));
476 } else {
477 current_mip_stats_.set_primal_bound(std::fmin(
478 current_mip_stats_.primal_bound(), mp_solution.objective_value()));
479 }
480 current_mip_stats_.set_number_of_solutions_found(
481 current_mip_stats_.number_of_solutions_found() + 1);
482}
483
484} // namespace
485
486absl::StatusOr<std::unique_ptr<SolverInterface>> CpSatSolver::New(
487 const ModelProto& model, const InitArgs&) {
488 RETURN_IF_ERROR(ModelIsSupported(model, kCpSatSupportedStructures, "CP-SAT"));
489 ASSIGN_OR_RETURN(MPModelProto cp_sat_model,
491 std::vector variable_ids(model.variables().ids().begin(),
492 model.variables().ids().end());
493 std::vector linear_constraint_ids(model.linear_constraints().ids().begin(),
494 model.linear_constraints().ids().end());
495 return absl::WrapUnique(new CpSatSolver(
496 std::move(cp_sat_model),
497 /*variable_ids=*/std::move(variable_ids),
498 /*linear_constraint_ids=*/std::move(linear_constraint_ids)));
499}
500
501absl::StatusOr<SolveResultProto> CpSatSolver::Solve(
502 const SolveParametersProto& parameters,
503 const ModelSolveParametersProto& model_parameters,
504 const MessageCallback message_cb,
505 const CallbackRegistrationProto& callback_registration, const Callback cb,
506 const SolveInterrupter* absl_nullable interrupter) {
508 model_parameters, kCpSatSupportedStructures, "CP-SAT"));
509 const absl::Time start = absl::Now();
510
512 callback_registration,
513 /*supported_events=*/{CALLBACK_EVENT_MIP_SOLUTION, CALLBACK_EVENT_MIP}));
514 if (callback_registration.add_lazy_constraints()) {
515 return absl::InvalidArgumentError(
516 "CallbackRegistrationProto.add_lazy_constraints=true is not supported "
517 "for CP-SAT.");
518 }
519 // We need not check callback_registration.add_cuts, as cuts can only be added
520 // at event MIP_NODE which we have already validated is not supported.
521
522 SolveResultProto result;
523 MPModelRequest req;
524 // Here we must make a copy since Solve() can be called multiple times with
525 // different parameters. Hence we can't move `cp_sat_model`.
526 *req.mutable_model() = cp_sat_model_;
527
529 bool used_cutoff = false;
530 {
531 std::vector<std::string> param_warnings =
532 SetSolveParameters(parameters,
533 /*has_message_callback=*/message_cb != nullptr, req);
534 if (parameters.has_cutoff_limit()) {
535 used_cutoff = ApplyCutoff(parameters.cutoff_limit(), req.mutable_model());
536 if (!used_cutoff) {
537 param_warnings.push_back(
538 "The cutoff_limit parameter not supported for quadratic objectives "
539 "with CP-SAT.");
540 }
541 }
542 if (!param_warnings.empty()) {
543 return absl::InvalidArgumentError(absl::StrJoin(param_warnings, "; "));
544 }
545 }
546
547 if (!model_parameters.solution_hints().empty()) {
548 int i = 0;
549 for (const auto [id, val] :
550 MakeView(model_parameters.solution_hints(0).variable_values())) {
551 while (variable_ids_[i] < id) {
552 ++i;
553 }
556 }
557 }
558
559 // We need to chain the user interrupter through a local interrupter, because
560 // if we terminate early from a callback request, we don't want to incorrectly
561 // modify the input state.
562 SolveInterrupter local_interrupter;
563 std::atomic<bool> interrupt_solve = false;
564 local_interrupter.AddInterruptionCallback([&]() { interrupt_solve = true; });
565
566 // Setup a callback on the user provided so that we interrupt the solver.
567 const ScopedSolveInterrupterCallback scoped_interrupt_cb(
568 interrupter, [&]() { local_interrupter.Interrupt(); });
569
570 std::function<void(const std::string&)> logging_callback;
571 if (message_cb != nullptr) {
572 logging_callback = [&](absl::string_view message) {
573 message_cb(absl::StrSplit(message, '\n'));
574 };
575 }
576
577 const absl::flat_hash_set<CallbackEventProto> events =
578 EventSet(callback_registration);
579 absl::AnyInvocable<SparseDoubleVectorProto(absl::Span<const double>) const>
580 extract_solution = [&](absl::Span<const double> cp_sat_variable_values) {
581 return ExtractSolution(cp_sat_variable_values,
582 callback_registration.mip_solution_filter());
583 };
584 CpSatCallbacks callbacks(cb, &local_interrupter, std::move(extract_solution),
585 events, cp_sat_model_.maximize());
586
587 // CP-SAT returns "infeasible" for inverted bounds.
588 RETURN_IF_ERROR(ListInvertedBounds().ToStatus());
589
590 const MPSolutionResponse response = SatSolveProto(
591 std::move(req), &interrupt_solve, logging_callback,
592 callbacks.MakeSolutionCallback(), callbacks.MakeBestBoundCallback());
593 RETURN_IF_ERROR(callbacks.error()) << "error in callback";
595 GetTermination(local_interrupter.IsInterrupted(),
596 /*maximize=*/cp_sat_model_.maximize(),
597 /*used_cutoff=*/used_cutoff, response));
598 const SparseVectorFilterProto& var_values_filter =
599 model_parameters.variable_values_filter();
600 auto add_solution =
601 [this, &result, &var_values_filter](
602 const google::protobuf::RepeatedField<double>& variable_values,
603 double objective) {
606 *solution.mutable_variable_values() =
607 ExtractSolution(variable_values, var_values_filter);
608 solution.set_objective_value(objective);
609 solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
610 };
611 if (response.status() == MPSOLVER_OPTIMAL ||
612 response.status() == MPSOLVER_FEASIBLE) {
613 add_solution(response.variable_value(), response.objective_value());
614 for (const MPSolution& extra_solution : response.additional_solutions()) {
615 add_solution(extra_solution.variable_value(),
616 extra_solution.objective_value());
617 }
618 }
619
621 absl::Now() - start, result.mutable_solve_stats()->mutable_solve_time()));
622
623 return result;
624}
625
626absl::StatusOr<bool> CpSatSolver::Update(const ModelUpdateProto&) {
627 return false;
628}
629
630CpSatSolver::CpSatSolver(MPModelProto cp_sat_model,
631 std::vector<int64_t> variable_ids,
632 std::vector<int64_t> linear_constraint_ids)
633 : cp_sat_model_(std::move(cp_sat_model)),
634 variable_ids_(std::move(variable_ids)),
635 linear_constraint_ids_(std::move(linear_constraint_ids)) {}
636
637SparseDoubleVectorProto CpSatSolver::ExtractSolution(
638 const absl::Span<const double> cp_sat_variable_values,
639 const SparseVectorFilterProto& filter) const {
640 // Pre-condition: we assume one-to-one correspondence of input variables to
641 // solution's variables.
642 CHECK_EQ(cp_sat_variable_values.size(), variable_ids_.size());
643
644 SparseVectorFilterPredicate predicate(filter);
645 SparseDoubleVectorProto result;
646 for (int i = 0; i < variable_ids_.size(); ++i) {
647 const int64_t id = variable_ids_[i];
648 const double value = cp_sat_variable_values[i];
649 if (predicate.AcceptsAndUpdate(id, value)) {
650 result.add_ids(id);
651 result.add_values(value);
652 }
653 }
654 return result;
655}
656
657InvertedBounds CpSatSolver::ListInvertedBounds() const {
658 InvertedBounds inverted_bounds;
659 for (int v = 0; v < cp_sat_model_.variable_size(); ++v) {
660 const MPVariableProto& var = cp_sat_model_.variable(v);
661 if (var.lower_bound() > var.upper_bound()) {
662 inverted_bounds.variables.push_back(variable_ids_[v]);
663 }
664 }
665 for (int c = 0; c < cp_sat_model_.constraint_size(); ++c) {
666 const MPConstraintProto& cstr = cp_sat_model_.constraint(c);
667 if (cstr.lower_bound() > cstr.upper_bound()) {
668 inverted_bounds.linear_constraints.push_back(linear_constraint_ids_[c]);
669 }
670 }
671
672 return inverted_bounds;
673}
674
675absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
678 const SolveInterrupter* absl_nullable) {
679 return absl::UnimplementedError(
680 "CPSAT does not provide a method to compute an infeasible subsystem");
681}
682
684
685} // namespace math_opt
686} // namespace operations_research
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
::operations_research::PartialVariableAssignment *PROTOBUF_NONNULL mutable_solution_hint()
static constexpr SolverType SAT_INTEGER_PROGRAMMING
::operations_research::MPModelProto *PROTOBUF_NONNULL mutable_model()
void set_solver_type(::operations_research::MPModelRequest_SolverType value)
::operations_research::MPSolverResponseStatus status() const
const ::operations_research::MPSolution & additional_solutions(int index) const
CallbackId AddInterruptionCallback(absl_nonnull Callback callback) const
const ::operations_research::math_opt::SparseVectorFilterProto & mip_solution_filter() const
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto &parameters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *absl_nullable interrupter) override
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto &parameters, MessageCallback message_cb, const SolveInterrupter *absl_nullable interrupter) override
const ::operations_research::math_opt::VariablesProto & variables() const
Definition model.pb.h:4464
const ::operations_research::math_opt::LinearConstraintsProto & linear_constraints() const
Definition model.pb.h:4694
const ::operations_research::math_opt::SparseVectorFilterProto & variable_values_filter() const
const ::operations_research::math_opt::SolutionHintProto & solution_hints(int index) const
const ::operations_research::math_opt::SparseDoubleVectorProto & variable_values() const
::operations_research::math_opt::PrimalSolutionProto *PROTOBUF_NONNULL mutable_primal_solution()
::operations_research::math_opt::SolutionProto *PROTOBUF_NONNULL add_solutions()
Definition result.pb.h:2791
::operations_research::math_opt::TerminationProto *PROTOBUF_NONNULL mutable_termination()
Definition result.pb.h:2739
::operations_research::math_opt::SolveStatsProto *PROTOBUF_NONNULL mutable_solve_stats()
Definition result.pb.h:2988
::google::protobuf::Duration *PROTOBUF_NONNULL mutable_solve_time()
Definition result.pb.h:1915
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
absl::Status ModelSolveParametersAreSupported(const ModelSolveParametersProto &model_parameters, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
absl::flat_hash_set< CallbackEventProto > EventSet(const CallbackRegistrationProto &callback_registration)
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
TerminationProto InfeasibleOrUnboundedTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
absl::StatusOr<::operations_research::MPModelProto > MathOptModelToMPModelProto(const ::operations_research::math_opt::ModelProto &model)
TerminationProto FeasibleTerminationProto(const bool is_maximize, const LimitProto limit, const double primal_objective, const std::optional< double > optional_dual_objective, const absl::string_view detail)
TerminationProto NoSolutionFoundTerminationProto(const bool is_maximize, const LimitProto limit, const std::optional< double > optional_dual_objective, const absl::string_view detail)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto &registration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
TerminationProto InfeasibleTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
TerminationProto CutoffTerminationProto(bool is_maximize, const absl::string_view detail)
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
MPSolutionResponse SatSolveProto(LazyMutableCopy< MPModelRequest > request, std::atomic< bool > *interrupt_solve, std::function< void(const std::string &)> logging_callback, std::function< void(const MPSolution &)> solution_callback, std::function< void(const double)> best_bound_callback)
std::string ProtoEnumToString(ProtoEnumType enum_value)
Definition proto_utils.h:63
std::string EncodeParametersAsString(const P &parameters)
Definition proto_utils.h:71
STL namespace.
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
Definition protoutil.h:42
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition protoutil.h:27
StatusBuilder InvalidArgumentErrorBuilder()
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)