Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
model_validator.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <limits>
18#include <utility>
19
20#include "absl/status/status.h"
21#include "absl/status/statusor.h"
30#include "ortools/math_opt/model.pb.h"
31#include "ortools/math_opt/model_update.pb.h"
32#include "ortools/math_opt/sparse_containers.pb.h"
37
38namespace operations_research {
39namespace math_opt {
40namespace {
41
43// Submessages
45
46absl::Status VariablesValid(const VariablesProto& variables) {
48 << "Bad variable ids";
50 CheckValues(MakeView(variables.ids(), variables.lower_bounds()),
51 {.allow_positive_infinity = false}, "lower_bounds"));
53 CheckValues(MakeView(variables.ids(), variables.upper_bounds()),
54 {.allow_negative_infinity = false}, "upper_bounds"));
56 CheckValues(MakeView(variables.ids(), variables.integers()), "integers"));
57 return absl::OkStatus();
58}
59
60absl::Status VariableUpdatesValid(const VariableUpdatesProto& variable_updates,
61 const IdNameBiMap& variable_ids,
62 const int64_t old_var_id_ub) {
63 RETURN_IF_ERROR(CheckIdsAndValues(MakeView(variable_updates.lower_bounds()),
64 {.allow_positive_infinity = false}))
65 << "Bad lower bounds";
66 RETURN_IF_ERROR(CheckIdsAndValues(MakeView(variable_updates.upper_bounds()),
67 {.allow_negative_infinity = false}))
68 << "Bad upper bounds";
69 RETURN_IF_ERROR(CheckIdsAndValues(MakeView(variable_updates.integers())))
70 << "Bad integers";
71 RETURN_IF_ERROR(CheckIdsSubset(variable_updates.lower_bounds().ids(),
72 variable_ids, old_var_id_ub))
73 << "lower bound update on invalid variable id";
74 RETURN_IF_ERROR(CheckIdsSubset(variable_updates.upper_bounds().ids(),
75 variable_ids, old_var_id_ub))
76 << "upper bound update on invalid variable id";
77 RETURN_IF_ERROR(CheckIdsSubset(variable_updates.integers().ids(),
78 variable_ids, old_var_id_ub))
79 << "integer update on invalid variable id";
80 return absl::OkStatus();
81}
82
83absl::Status ObjectiveValid(const ObjectiveProto& objective,
84 const IdNameBiMap& variable_ids) {
85 // 1. Validate offset
86 RETURN_IF_ERROR(CheckScalarNoNanNoInf(objective.offset()))
87 << "Objective offset invalid";
88 // 2. Validate linear terms
89 const auto linear_coefficients = MakeView(objective.linear_coefficients());
91 linear_coefficients,
92 {.allow_positive_infinity = false, .allow_negative_infinity = false}))
93 << "Linear objective coefficients bad";
94 RETURN_IF_ERROR(CheckIdsSubset(linear_coefficients.ids(), variable_ids))
95 << "Objective.linear_coefficients.ids not found in Variables.ids";
96 // 3. Validate quadratic terms
97 RETURN_IF_ERROR(SparseMatrixValid(objective.quadratic_coefficients(),
98 /*enforce_upper_triangular=*/true))
99 << "Objective.quadratic_coefficients invalid";
100 RETURN_IF_ERROR(SparseMatrixIdsAreKnown(objective.quadratic_coefficients(),
102 << "Objective.quadratic_coefficients invalid";
103 if (const int64_t priority = objective.priority(); priority < 0) {
105 << "expected Objective.priority to be nonnegative but found "
106 "priority: "
107 << priority;
108 }
109 return absl::OkStatus();
110}
111
112// NOTE: This method does not check requirements on the IDs
113absl::Status ObjectiveUpdatesValid(
114 const ObjectiveUpdatesProto& objective_updates,
115 const IdNameBiMap& variable_ids) {
116 // 1. Validate offset
117 RETURN_IF_ERROR(CheckScalarNoNanNoInf(objective_updates.offset_update()))
118 << "Offset update invalid";
119 // 2. Validate linear terms
121 MakeView(objective_updates.linear_coefficients()),
122 {.allow_positive_infinity = false, .allow_negative_infinity = false}))
123 << "Linear objective coefficients invalid";
124 // 3. Validate quadratic terms
125 RETURN_IF_ERROR(SparseMatrixValid(objective_updates.quadratic_coefficients(),
126 /*enforce_upper_triangular=*/true))
127 << "Objective.quadratic_coefficients invalid";
128 RETURN_IF_ERROR(CheckIdsSubset(objective_updates.linear_coefficients().ids(),
130 << "Linear coefficients ids not found in variable ids";
132 objective_updates.quadratic_coefficients(), variable_ids, variable_ids))
133 << "quadratic_coefficients invalid";
134 if (objective_updates.has_priority_update()) {
135 const int64_t priority = objective_updates.priority_update();
136 if (priority < 0) {
138 << "expected Objective.priority to be nonnegative but found "
139 "priority: "
140 << priority;
141 }
142 }
143 return absl::OkStatus();
144}
145
146absl::Status AuxiliaryObjectivesUpdatesValid(
147 const AuxiliaryObjectivesUpdatesProto& objectives,
148 const IdNameBiMap& variable_ids, const IdNameBiMap& objective_ids) {
149 for (const auto& [id, new_objective] : objectives.new_objectives()) {
150 RETURN_IF_ERROR(ObjectiveValid(new_objective, variable_ids))
151 << "bad new auxiliary objective with id: " << id;
152 }
153 for (const auto& [id, objective_update] : objectives.objective_updates()) {
154 if (!objective_ids.HasId(id)) {
156 << "objective update on auxiliary objective not present in model "
157 "with id: "
158 << id;
159 }
160 RETURN_IF_ERROR(ObjectiveUpdatesValid(objective_update, variable_ids));
161 }
162 return absl::OkStatus();
163}
164
165absl::Status LinearConstraintsValid(
166 const LinearConstraintsProto& linear_constraints) {
167 RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(linear_constraints.ids()))
168 << "Bad linear constraint ids";
170 MakeView(linear_constraints.ids(), linear_constraints.lower_bounds()),
171 {.allow_positive_infinity = false}, "lower_bounds"));
173 MakeView(linear_constraints.ids(), linear_constraints.upper_bounds()),
174 {.allow_negative_infinity = false}, "upper_bounds"));
175 return absl::OkStatus();
176}
177
178absl::Status LinearConstraintUpdatesValid(
179 const LinearConstraintUpdatesProto& linear_constraint_updates,
180 const IdNameBiMap& linear_constraint_ids, const int64_t old_lin_con_id_ub) {
182 CheckIdsAndValues(MakeView(linear_constraint_updates.lower_bounds()),
183 {.allow_positive_infinity = false}))
184 << "Bad lower bounds";
186 CheckIdsAndValues(MakeView(linear_constraint_updates.upper_bounds()),
187 {.allow_negative_infinity = false}))
188 << "Bad upper bounds";
189 RETURN_IF_ERROR(CheckIdsSubset(linear_constraint_updates.lower_bounds().ids(),
190 linear_constraint_ids, old_lin_con_id_ub))
191 << "lower bound update on invalid linear constraint id";
192 RETURN_IF_ERROR(CheckIdsSubset(linear_constraint_updates.upper_bounds().ids(),
193 linear_constraint_ids, old_lin_con_id_ub))
194 << "upper bound update on invalid linear constraint id";
195 return absl::OkStatus();
196}
197
198absl::Status LinearConstraintMatrixIdsValidForUpdate(
199 const SparseDoubleMatrixProto& matrix,
200 const IdNameBiMap& linear_constraint_ids, const IdNameBiMap& variable_ids) {
201 RETURN_IF_ERROR(CheckIdsSubset(matrix.row_ids(), linear_constraint_ids))
202 << "Unknown linear_constraint_id";
203 RETURN_IF_ERROR(CheckIdsSubset(matrix.column_ids(), variable_ids))
204 << "Unknown variable_id";
205 return absl::OkStatus();
206}
207
208// To use this helper, you must implement an overload for:
209// ValidateConstraint(const MyConstraintProto& constraint,
210// const IdNameBiMap& variable_universe);
211template <typename ConstraintType>
212absl::Status ValidateConstraintMap(
213 const google::protobuf::Map<int64_t, ConstraintType>& constraints,
214 const IdNameBiMap& variable_universe) {
215 for (const auto& [id, constraint] : constraints) {
216 RETURN_IF_ERROR(ValidateConstraint(constraint, variable_universe))
217 << "invalid constraint with id: " << id;
218 }
219 return absl::OkStatus();
220}
221
222} // namespace
223
224// /////////////////////////////////////////////////////////////////////////////
225// Model
226// /////////////////////////////////////////////////////////////////////////////
227
228absl::StatusOr<ModelSummary> ValidateModel(const ModelProto& model,
229 const bool check_names) {
230 ASSIGN_OR_RETURN(const auto model_summary,
231 ModelSummary::Create(model, check_names));
232 RETURN_IF_ERROR(VariablesValid(model.variables()))
233 << "ModelProto.variables are invalid.";
234 RETURN_IF_ERROR(ObjectiveValid(model.objective(), model_summary.variables))
235 << "ModelProto.objective is invalid";
236 for (const auto& [id, objective] : model.auxiliary_objectives()) {
237 RETURN_IF_ERROR(ObjectiveValid(objective, model_summary.variables))
238 << "ModelProto.auxiliary_objectives is invalid with objective id: "
239 << id;
240 }
241 RETURN_IF_ERROR(LinearConstraintsValid(model.linear_constraints()))
242 << "ModelProto.linear_constraints are invalid";
243 RETURN_IF_ERROR(SparseMatrixValid(model.linear_constraint_matrix()))
244 << "ModelProto.linear_constraint_matrix invalid";
245 RETURN_IF_ERROR(SparseMatrixIdsAreKnown(model.linear_constraint_matrix(),
246 model_summary.linear_constraints,
247 model_summary.variables))
248 << "ModelProto.linear_constraint_matrix ids are inconsistent";
249
250 RETURN_IF_ERROR(ValidateConstraintMap(model.quadratic_constraints(),
251 model_summary.variables))
252 << "ModelProto.quadratic_constraints invalid";
253 RETURN_IF_ERROR(ValidateConstraintMap(model.second_order_cone_constraints(),
254 model_summary.variables))
255 << "ModelProto.second_order_cone_constraints invalid";
257 ValidateConstraintMap(model.sos1_constraints(), model_summary.variables))
258 << "ModelProto.sos1_constraints invalid";
260 ValidateConstraintMap(model.sos2_constraints(), model_summary.variables))
261 << "ModelProto.sos2_constraints invalid";
262 RETURN_IF_ERROR(ValidateConstraintMap(model.indicator_constraints(),
263 model_summary.variables))
264 << "ModelProto.indicator_constraints invalid";
265
266 return model_summary;
267}
268
270// Model Update
272
273absl::Status ValidateModelUpdate(const ModelUpdateProto& model_update,
274 ModelSummary& model_summary) {
275 RETURN_IF_ERROR(model_summary.Update(model_update));
276 const int64_t old_var_id_ub = model_update.new_variables().ids_size() > 0
277 ? model_update.new_variables().ids(0)
278 : model_summary.variables.next_free_id();
279 const int64_t old_lin_con_id_ub =
280 model_update.new_linear_constraints().ids_size() > 0
281 ? model_update.new_linear_constraints().ids(0)
282 : model_summary.linear_constraints.next_free_id();
283 RETURN_IF_ERROR(VariableUpdatesValid(model_update.variable_updates(),
284 model_summary.variables, old_var_id_ub))
285 << "ModelUpdateProto.variable_updates invalid";
286 RETURN_IF_ERROR(ObjectiveUpdatesValid(model_update.objective_updates(),
287 model_summary.variables))
288 << "ModelUpdateProto.objective_update invalid";
289 RETURN_IF_ERROR(AuxiliaryObjectivesUpdatesValid(
290 model_update.auxiliary_objectives_updates(), model_summary.variables,
291 model_summary.auxiliary_objectives))
292 << "ModelUpdateProto.auxiliary_objectives_updates invalid";
293 RETURN_IF_ERROR(LinearConstraintUpdatesValid(
294 model_update.linear_constraint_updates(),
295 model_summary.linear_constraints, old_lin_con_id_ub))
296 << "ModelUpdateProto.linear_constraint_updates invalid";
297 RETURN_IF_ERROR(VariablesValid(model_update.new_variables()))
298 << "ModelUpdateProto.new_variables invalid";
299 RETURN_IF_ERROR(LinearConstraintsValid(model_update.new_linear_constraints()))
300 << "ModelUpdateProto.new_linear_constraints invalid";
302 SparseMatrixValid(model_update.linear_constraint_matrix_updates()))
303 << "ModelUpdateProto.linear_constraint_matrix_updates invalid";
304
305 RETURN_IF_ERROR(LinearConstraintMatrixIdsValidForUpdate(
306 model_update.linear_constraint_matrix_updates(),
307 model_summary.linear_constraints, model_summary.variables))
308 << "invalid linear constraint matrix update";
309
310 RETURN_IF_ERROR(ValidateConstraintMap(
311 model_update.quadratic_constraint_updates().new_constraints(),
312 model_summary.variables))
313 << "ModelUpdateProto.quadratic_constraint_updates.new_constraints "
314 "invalid";
315 RETURN_IF_ERROR(ValidateConstraintMap(
316 model_update.second_order_cone_constraint_updates().new_constraints(),
317 model_summary.variables))
318 << "ModelUpdateProto.second_order_cone_constraint_updates.new_"
319 "constraints invalid";
320 RETURN_IF_ERROR(ValidateConstraintMap(
321 model_update.sos1_constraint_updates().new_constraints(),
322 model_summary.variables))
323 << "ModelUpdateProto.sos1_constraint_updates.new_constraints invalid";
324 RETURN_IF_ERROR(ValidateConstraintMap(
325 model_update.sos2_constraint_updates().new_constraints(),
326 model_summary.variables))
327 << "ModelUpdateProto.sos2_constraint_updates.new_constraints invalid";
328 RETURN_IF_ERROR(ValidateConstraintMap(
329 model_update.indicator_constraint_updates().new_constraints(),
330 model_summary.variables))
331 << "ModelUpdateProto.indicator_constraint_updates.new_constraints "
332 "invalid";
333
334 return absl::OkStatus();
335}
336
337} // namespace math_opt
338} // namespace operations_research
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
absl::Span< const int64_t > variable_ids
GRBmodel * model
absl::Status ValidateModelUpdate(const ModelUpdateProto &model_update, ModelSummary &model_summary)
absl::Status CheckValues(const SparseVectorView< T > &vector_view, absl::string_view value_name="values")
absl::Status SparseMatrixIdsAreKnown(const SparseDoubleMatrixProto &matrix, const IdNameBiMap &row_ids, const IdNameBiMap &column_ids)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
absl::Status CheckIdsAndValues(const SparseVectorView< T > &vector_view, absl::string_view value_name="values")
absl::Status ValidateConstraint(const IndicatorConstraintProto &constraint, const IdNameBiMap &variable_universe)
Definition validator.cc:27
absl::Status CheckIdsSubset(absl::Span< const int64_t > ids, const IdNameBiMap &universe, std::optional< int64_t > upper_bound)
absl::Status CheckScalarNoNanNoInf(const double d)
absl::Status SparseMatrixValid(const SparseDoubleMatrixProto &matrix, const bool enforce_upper_triangular)
absl::Status CheckIdsRangeAndStrictlyIncreasing(absl::Span< const int64_t > ids)
absl::StatusOr< ModelSummary > ValidateModel(const ModelProto &model, const bool check_names)
In SWIG mode, we don't want anything besides these top-level includes.
StatusBuilder InvalidArgumentErrorBuilder()
static absl::StatusOr< ModelSummary > Create(const ModelProto &model, bool check_names=true)