Google OR-Tools v9.15
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-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 <limits>
18#include <utility>
19
20#include "absl/status/status.h"
21#include "absl/status/statusor.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(),
101 variable_ids, variable_ids))
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(),
129 variable_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 if (objectives.objective_updates().contains(id)) {
152 << "objective update on new auxiliary objective with "
153 "id: "
154 << id;
155 }
156 RETURN_IF_ERROR(ObjectiveValid(new_objective, variable_ids))
157 << "bad new auxiliary objective with id: " << id;
158 }
159 for (const auto& id : objectives.deleted_objective_ids()) {
160 if (objectives.objective_updates().contains(id)) {
162 << "objective update on deleted auxiliary objective with "
163 "id: "
164 << id;
165 }
166 }
167 for (const auto& [id, objective_update] : objectives.objective_updates()) {
168 if (!objective_ids.HasId(id)) {
170 << "objective update on auxiliary objective not present in model "
171 "with id: "
172 << id;
173 }
174 RETURN_IF_ERROR(ObjectiveUpdatesValid(objective_update, variable_ids));
175 }
176 return absl::OkStatus();
177}
178
179absl::Status LinearConstraintsValid(
180 const LinearConstraintsProto& linear_constraints) {
181 RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(linear_constraints.ids()))
182 << "Bad linear constraint ids";
184 MakeView(linear_constraints.ids(), linear_constraints.lower_bounds()),
185 {.allow_positive_infinity = false}, "lower_bounds"));
187 MakeView(linear_constraints.ids(), linear_constraints.upper_bounds()),
188 {.allow_negative_infinity = false}, "upper_bounds"));
189 return absl::OkStatus();
190}
191
192absl::Status LinearConstraintUpdatesValid(
193 const LinearConstraintUpdatesProto& linear_constraint_updates,
194 const IdNameBiMap& linear_constraint_ids, const int64_t old_lin_con_id_ub) {
196 CheckIdsAndValues(MakeView(linear_constraint_updates.lower_bounds()),
197 {.allow_positive_infinity = false}))
198 << "Bad lower bounds";
200 CheckIdsAndValues(MakeView(linear_constraint_updates.upper_bounds()),
201 {.allow_negative_infinity = false}))
202 << "Bad upper bounds";
203 RETURN_IF_ERROR(CheckIdsSubset(linear_constraint_updates.lower_bounds().ids(),
204 linear_constraint_ids, old_lin_con_id_ub))
205 << "lower bound update on invalid linear constraint id";
206 RETURN_IF_ERROR(CheckIdsSubset(linear_constraint_updates.upper_bounds().ids(),
207 linear_constraint_ids, old_lin_con_id_ub))
208 << "upper bound update on invalid linear constraint id";
209 return absl::OkStatus();
210}
211
212absl::Status LinearConstraintMatrixIdsValidForUpdate(
213 const SparseDoubleMatrixProto& matrix,
214 const IdNameBiMap& linear_constraint_ids, const IdNameBiMap& variable_ids) {
215 RETURN_IF_ERROR(CheckIdsSubset(matrix.row_ids(), linear_constraint_ids))
216 << "Unknown linear_constraint_id";
217 RETURN_IF_ERROR(CheckIdsSubset(matrix.column_ids(), variable_ids))
218 << "Unknown variable_id";
219 return absl::OkStatus();
220}
221
222// To use this helper, you must implement an overload for:
223// ValidateConstraint(const MyConstraintProto& constraint,
224// const IdNameBiMap& variable_universe);
225template <typename ConstraintType>
226absl::Status ValidateConstraintMap(
227 const google::protobuf::Map<int64_t, ConstraintType>& constraints,
228 const IdNameBiMap& variable_universe) {
229 for (const auto& [id, constraint] : constraints) {
230 RETURN_IF_ERROR(ValidateConstraint(constraint, variable_universe))
231 << "invalid constraint with id: " << id;
232 }
233 return absl::OkStatus();
234}
235
236} // namespace
237
238// /////////////////////////////////////////////////////////////////////////////
239// Model
240// /////////////////////////////////////////////////////////////////////////////
241
242absl::StatusOr<ModelSummary> ValidateModel(const ModelProto& model,
243 const bool check_names) {
244 ASSIGN_OR_RETURN(const auto model_summary,
245 ModelSummary::Create(model, check_names));
246 RETURN_IF_ERROR(VariablesValid(model.variables()))
247 << "ModelProto.variables are invalid.";
248 RETURN_IF_ERROR(ObjectiveValid(model.objective(), model_summary.variables))
249 << "ModelProto.objective is invalid";
250 for (const auto& [id, objective] : model.auxiliary_objectives()) {
251 RETURN_IF_ERROR(ObjectiveValid(objective, model_summary.variables))
252 << "ModelProto.auxiliary_objectives is invalid with objective id: "
253 << id;
254 }
255 RETURN_IF_ERROR(LinearConstraintsValid(model.linear_constraints()))
256 << "ModelProto.linear_constraints are invalid";
257 RETURN_IF_ERROR(SparseMatrixValid(model.linear_constraint_matrix()))
258 << "ModelProto.linear_constraint_matrix invalid";
259 RETURN_IF_ERROR(SparseMatrixIdsAreKnown(model.linear_constraint_matrix(),
260 model_summary.linear_constraints,
261 model_summary.variables))
262 << "ModelProto.linear_constraint_matrix ids are inconsistent";
263
264 RETURN_IF_ERROR(ValidateConstraintMap(model.quadratic_constraints(),
265 model_summary.variables))
266 << "ModelProto.quadratic_constraints invalid";
267 RETURN_IF_ERROR(ValidateConstraintMap(model.second_order_cone_constraints(),
268 model_summary.variables))
269 << "ModelProto.second_order_cone_constraints invalid";
271 ValidateConstraintMap(model.sos1_constraints(), model_summary.variables))
272 << "ModelProto.sos1_constraints invalid";
274 ValidateConstraintMap(model.sos2_constraints(), model_summary.variables))
275 << "ModelProto.sos2_constraints invalid";
276 RETURN_IF_ERROR(ValidateConstraintMap(model.indicator_constraints(),
277 model_summary.variables))
278 << "ModelProto.indicator_constraints invalid";
279
280 return model_summary;
281}
282
284// Model Update
286
287absl::Status ValidateModelUpdate(const ModelUpdateProto& model_update,
288 ModelSummary& model_summary) {
289 RETURN_IF_ERROR(model_summary.Update(model_update));
290 const int64_t old_var_id_ub = model_update.new_variables().ids_size() > 0
291 ? model_update.new_variables().ids(0)
292 : model_summary.variables.next_free_id();
293 const int64_t old_lin_con_id_ub =
294 model_update.new_linear_constraints().ids_size() > 0
295 ? model_update.new_linear_constraints().ids(0)
296 : model_summary.linear_constraints.next_free_id();
297 RETURN_IF_ERROR(VariableUpdatesValid(model_update.variable_updates(),
298 model_summary.variables, old_var_id_ub))
299 << "ModelUpdateProto.variable_updates invalid";
300 RETURN_IF_ERROR(ObjectiveUpdatesValid(model_update.objective_updates(),
301 model_summary.variables))
302 << "ModelUpdateProto.objective_update invalid";
303 RETURN_IF_ERROR(AuxiliaryObjectivesUpdatesValid(
304 model_update.auxiliary_objectives_updates(), model_summary.variables,
305 model_summary.auxiliary_objectives))
306 << "ModelUpdateProto.auxiliary_objectives_updates invalid";
307 RETURN_IF_ERROR(LinearConstraintUpdatesValid(
308 model_update.linear_constraint_updates(),
309 model_summary.linear_constraints, old_lin_con_id_ub))
310 << "ModelUpdateProto.linear_constraint_updates invalid";
311 RETURN_IF_ERROR(VariablesValid(model_update.new_variables()))
312 << "ModelUpdateProto.new_variables invalid";
313 RETURN_IF_ERROR(LinearConstraintsValid(model_update.new_linear_constraints()))
314 << "ModelUpdateProto.new_linear_constraints invalid";
316 SparseMatrixValid(model_update.linear_constraint_matrix_updates()))
317 << "ModelUpdateProto.linear_constraint_matrix_updates invalid";
318
319 RETURN_IF_ERROR(LinearConstraintMatrixIdsValidForUpdate(
320 model_update.linear_constraint_matrix_updates(),
321 model_summary.linear_constraints, model_summary.variables))
322 << "invalid linear constraint matrix update";
323
324 RETURN_IF_ERROR(ValidateConstraintMap(
325 model_update.quadratic_constraint_updates().new_constraints(),
326 model_summary.variables))
327 << "ModelUpdateProto.quadratic_constraint_updates.new_constraints "
328 "invalid";
329 RETURN_IF_ERROR(ValidateConstraintMap(
330 model_update.second_order_cone_constraint_updates().new_constraints(),
331 model_summary.variables))
332 << "ModelUpdateProto.second_order_cone_constraint_updates.new_"
333 "constraints invalid";
334 RETURN_IF_ERROR(ValidateConstraintMap(
335 model_update.sos1_constraint_updates().new_constraints(),
336 model_summary.variables))
337 << "ModelUpdateProto.sos1_constraint_updates.new_constraints invalid";
338 RETURN_IF_ERROR(ValidateConstraintMap(
339 model_update.sos2_constraint_updates().new_constraints(),
340 model_summary.variables))
341 << "ModelUpdateProto.sos2_constraint_updates.new_constraints invalid";
342 RETURN_IF_ERROR(ValidateConstraintMap(
343 model_update.indicator_constraint_updates().new_constraints(),
344 model_summary.variables))
345 << "ModelUpdateProto.indicator_constraint_updates.new_constraints "
346 "invalid";
347
348 return absl::OkStatus();
349}
350
351} // namespace math_opt
352} // namespace operations_research
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
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)
OR-Tools root namespace.
StatusBuilder InvalidArgumentErrorBuilder()
static absl::StatusOr< ModelSummary > Create(const ModelProto &model, bool check_names=true)