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