Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
solve_impl.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 <memory>
17#include <optional>
18#include <utility>
19
20#include "absl/functional/any_invocable.h"
21#include "absl/log/check.h"
22#include "absl/memory/memory.h"
23#include "absl/status/status.h"
24#include "absl/status/statusor.h"
25#include "absl/synchronization/mutex.h"
39
41namespace {
42
43absl::StatusOr<SolveResult> CallSolve(
44 BaseSolver& solver, const ModelStorage* const expected_storage,
45 const SolveArguments& arguments, SolveInterrupter& local_canceller) {
46 RETURN_IF_ERROR(arguments.CheckModelStorageAndCallback(expected_storage));
47
48 BaseSolver::Callback cb = nullptr;
49 absl::Mutex mutex;
50 absl::Status cb_status; // Guarded by `mutex`.
51 if (arguments.callback != nullptr) {
52 cb = [&](const CallbackDataProto& callback_data_proto) {
53 const CallbackData data(expected_storage, callback_data_proto);
54 const CallbackResult result = arguments.callback(data);
55 if (const absl::Status status =
56 result.CheckModelStorage(expected_storage);
57 !status.ok()) {
58 // Note that we use util::StatusBuilder() here as util::Annotate() is
59 // not available in open-source code.
60 util::StatusBuilder builder(status);
61 builder << "invalid CallbackResult returned by user callback";
62
63 { // Limit `lock` scope.
64 const absl::MutexLock lock(&mutex);
65 cb_status.Update(builder);
66 }
67
68 // Trigger subprocess cancellation.
69 local_canceller.Interrupt();
70
71 // Trigger early termination of the solve if cancellation is not
72 // supported (i.e. in in-process solve).
73 CallbackResultProto result_proto;
74 result_proto.set_terminate(true);
75 return result_proto;
76 }
77 return result.Proto();
78 };
79 }
80
81 ASSIGN_OR_RETURN(ModelSolveParametersProto model_parameters,
82 arguments.model_parameters.Proto());
83 const absl::StatusOr<SolveResultProto> solve_result_proto = solver.Solve(
84 {.parameters = arguments.parameters.Proto(),
85 .model_parameters = std::move(model_parameters),
86 .message_callback = arguments.message_callback,
87 .callback_registration = arguments.callback_registration.Proto(),
88 .user_cb = std::move(cb),
89 .interrupter = arguments.interrupter});
90
91 // solver.Solve() returns an error on cancellation by local_canceller but in
92 // that case we want to ignore this error and return status generated in the
93 // callback instead.
94 { // Limit `lock` scope.
95 const absl::MutexLock lock(&mutex);
96 RETURN_IF_ERROR(cb_status);
97 }
98
99 if (!solve_result_proto.ok()) {
100 return solve_result_proto.status();
101 }
102
103 return SolveResult::FromProto(expected_storage, solve_result_proto.value());
104}
105
106absl::StatusOr<ComputeInfeasibleSubsystemResult> CallComputeInfeasibleSubsystem(
107 BaseSolver& solver, const ModelStorage* const expected_storage,
108 const ComputeInfeasibleSubsystemArguments& arguments,
109 SolveInterrupter& local_canceller) {
111 const ComputeInfeasibleSubsystemResultProto compute_result_proto,
112 solver.ComputeInfeasibleSubsystem(
113 {.parameters = arguments.parameters.Proto(),
114 .message_callback = arguments.message_callback,
115 .interrupter = arguments.interrupter}));
116
117 return ComputeInfeasibleSubsystemResult::FromProto(expected_storage,
118 compute_result_proto);
119}
120
121} // namespace
122
123absl::StatusOr<SolveResult> SolveImpl(
124 const BaseSolverFactory solver_factory, const Model& model,
125 const SolverType solver_type, const SolveArguments& solve_args,
126 const SolveInterrupter* const user_canceller, const bool remove_names) {
127 SolveInterrupter local_canceller;
128 const ScopedSolveInterrupterCallback user_canceller_cb(
129 user_canceller, [&]() { local_canceller.Interrupt(); });
131 const std::unique_ptr<BaseSolver> solver,
132 solver_factory(EnumToProto(solver_type), model.ExportModel(remove_names),
133 &local_canceller));
134 return CallSolve(*solver, model.storage(), solve_args, local_canceller);
135}
136
137absl::StatusOr<ComputeInfeasibleSubsystemResult> ComputeInfeasibleSubsystemImpl(
138 const BaseSolverFactory solver_factory, const Model& model,
139 const SolverType solver_type,
140 const ComputeInfeasibleSubsystemArguments& compute_args,
141 const SolveInterrupter* const user_canceller, const bool remove_names) {
142 SolveInterrupter local_canceller;
143 const ScopedSolveInterrupterCallback user_canceller_cb(
144 user_canceller, [&]() { local_canceller.Interrupt(); });
146 const std::unique_ptr<BaseSolver> subprocess_solver,
147 solver_factory(EnumToProto(solver_type), model.ExportModel(remove_names),
148 &local_canceller));
149 return CallComputeInfeasibleSubsystem(*subprocess_solver, model.storage(),
150 compute_args, local_canceller);
151}
152
153absl::StatusOr<std::unique_ptr<IncrementalSolverImpl>>
156 const SolveInterrupter* const user_canceller,
157 const bool remove_names) {
158 if (model == nullptr) {
159 return absl::InvalidArgumentError("input model can't be null");
160 }
161 auto local_canceller = std::make_shared<SolveInterrupter>();
162 auto user_canceller_cb =
163 std::make_unique<const ScopedSolveInterrupterCallback>(
164 user_canceller,
165 [local_canceller]() { local_canceller->Interrupt(); });
166 std::unique_ptr<UpdateTracker> update_tracker = model->NewUpdateTracker();
167 ASSIGN_OR_RETURN(ModelProto model_proto,
168 update_tracker->ExportModel(remove_names));
170 std::unique_ptr<BaseSolver> solver,
171 solver_factory(EnumToProto(solver_type), std::move(model_proto),
172 local_canceller.get()));
173 return absl::WrapUnique<IncrementalSolverImpl>(new IncrementalSolverImpl(
174 std::move(solver_factory), solver_type, remove_names,
175 std::move(local_canceller), std::move(user_canceller_cb),
176 model->storage(), std::move(update_tracker), std::move(solver)));
177}
178
179IncrementalSolverImpl::IncrementalSolverImpl(
180 BaseSolverFactory solver_factory, SolverType solver_type,
181 const bool remove_names, std::shared_ptr<SolveInterrupter> local_canceller,
182 std::unique_ptr<const ScopedSolveInterrupterCallback> user_canceller_cb,
183 const ModelStorage* const expected_storage,
184 std::unique_ptr<UpdateTracker> update_tracker,
185 std::unique_ptr<BaseSolver> solver)
186 : solver_factory_(std::move(solver_factory)),
187 solver_type_(solver_type),
188 remove_names_(remove_names),
189 local_canceller_(std::move(local_canceller)),
190 user_canceller_cb_(std::move(user_canceller_cb)),
191 expected_storage_(expected_storage),
192 update_tracker_(std::move(update_tracker)),
193 solver_(std::move(solver)) {}
194
195absl::StatusOr<SolveResult> IncrementalSolverImpl::Solve(
196 const SolveArguments& arguments) {
197 // TODO: b/260337466 - Add permanent errors and concurrency protection.
198 RETURN_IF_ERROR(Update().status());
199 return SolveWithoutUpdate(arguments);
200}
201
202absl::StatusOr<ComputeInfeasibleSubsystemResult>
204 const ComputeInfeasibleSubsystemArguments& arguments) {
205 // TODO: b/260337466 - Add permanent errors and concurrency protection.
206 RETURN_IF_ERROR(Update().status());
208}
209
210absl::StatusOr<UpdateResult> IncrementalSolverImpl::Update() {
211 // TODO: b/260337466 - Add permanent errors and concurrency protection.
212 ASSIGN_OR_RETURN(std::optional<ModelUpdateProto> model_update,
213 update_tracker_->ExportModelUpdate(remove_names_));
214 if (!model_update.has_value()) {
215 return UpdateResult(true);
216 }
217
218 OR_ASSIGN_OR_RETURN3(const bool did_update,
219 solver_->Update(*std::move(model_update)),
220 _ << "update failed");
221 RETURN_IF_ERROR(update_tracker_->AdvanceCheckpoint());
222
223 if (did_update) {
224 return UpdateResult(true);
225 }
226
227 ASSIGN_OR_RETURN(ModelProto model_proto,
228 update_tracker_->ExportModel(remove_names_));
230 solver_,
231 solver_factory_(EnumToProto(solver_type_), std::move(model_proto),
232 local_canceller_.get()),
233 _ << "solver re-creation failed");
234
235 return UpdateResult(false);
236}
237
239 const SolveArguments& arguments) const {
240 // TODO: b/260337466 - Add permanent errors and concurrency protection.
241 return CallSolve(*solver_, expected_storage_, arguments, *local_canceller_);
242}
243
244absl::StatusOr<ComputeInfeasibleSubsystemResult>
246 const ComputeInfeasibleSubsystemArguments& arguments) const {
247 // TODO: b/260337466 - Add permanent errors and concurrency protection.
248 return CallComputeInfeasibleSubsystem(*solver_, expected_storage_, arguments,
249 *local_canceller_);
250}
251
252} // namespace operations_research::math_opt::internal
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
std::function< CallbackResultProto(const CallbackDataProto &)> Callback
Callback function type for MIP/LP callbacks.
Definition base_solver.h:54
absl::StatusOr< SolveResult > SolveWithoutUpdate() const
absl::StatusOr< ComputeInfeasibleSubsystemResult > ComputeInfeasibleSubsystemWithoutUpdate() const
absl::StatusOr< ComputeInfeasibleSubsystemResult > ComputeInfeasibleSubsystem()
const ModelStorage * storage() const
Definition model.h:916
ModelProto ExportModel(bool remove_names=false) const
Definition model.cc:298
std::unique_ptr< UpdateTracker > NewUpdateTracker()
Definition model.cc:302
absl::StatusOr< UpdateResult > Update() override
SolverType solver_type() const override
Returns the underlying solver used.
Definition solve_impl.h:98
static absl::StatusOr< std::unique_ptr< IncrementalSolverImpl > > New(BaseSolverFactory solver_factory, Model *model, SolverType solver_type, const SolveInterrupter *user_canceller, bool remove_names)
absl::StatusOr< ComputeInfeasibleSubsystemResult > ComputeInfeasibleSubsystemImpl(const BaseSolverFactory solver_factory, const Model &model, const SolverType solver_type, const ComputeInfeasibleSubsystemArguments &compute_args, const SolveInterrupter *const user_canceller, const bool remove_names)
absl::AnyInvocable< absl::StatusOr< std::unique_ptr< BaseSolver > >( SolverTypeProto solver_type, ModelProto model, SolveInterrupter *local_canceller) const > BaseSolverFactory
Definition solve_impl.h:50
absl::StatusOr< SolveResult > SolveImpl(const BaseSolverFactory solver_factory, const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolveInterrupter *const user_canceller, const bool remove_names)
SolverType
The solvers supported by MathOpt.
Definition parameters.h:42
Enum< E >::Proto EnumToProto(std::optional< E > value)
Definition enums.h:270
STL namespace.
Arguments passed to ComputeInfeasibleSubsystem() to control the solver.
static absl::StatusOr< ComputeInfeasibleSubsystemResult > FromProto(const ModelStorage *model, const ComputeInfeasibleSubsystemResultProto &result_proto)
Result of the Update() on an incremental solver.
#define OR_ASSIGN_OR_RETURN3(lhs, rexpr, error_expression)