Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
solution_improvement.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 <cmath>
17#include <ios>
18#include <limits>
19#include <tuple>
20#include <vector>
21
22#include "absl/log/check.h"
23#include "absl/status/status.h"
24#include "absl/status/statusor.h"
25#include "absl/types/span.h"
31
33
34namespace {
35
36// Returns an error if:
37// * the solution contains variables not in the correct model
38// * or the solution does not have a value for each variable in the model
39// * or some of the solution values are not finite.
40absl::Status ValidateFullFiniteSolution(const Model& model,
42 for (const auto& [v, value] : solution) {
43 RETURN_IF_ERROR(model.ValidateExistingVariableOfThisModel(v));
44 if (!std::isfinite(value)) {
46 << "the solution contains non-finite value " << value
47 << " for variable " << v;
48 }
49 }
50 for (const Variable v : model.SortedVariables()) {
51 if (!solution.contains(v)) {
53 << "the solution does not contain a value for variable " << v;
54 }
55 }
56 return absl::OkStatus();
57}
58
59// Returns the constraints' value based on the input full-solution.
60//
61// This CHECKs if the input solution does not contain values for every variable
62// in the constraint.
63double ConstraintValue(const LinearConstraint c,
65 const BoundedLinearExpression c_bexpr = c.AsBoundedLinearExpression();
66 CHECK_EQ(c_bexpr.expression.offset(), 0.0);
67 // Evaluate() CHECKs that the input solution has values for each variable.
68 return c_bexpr.expression.Evaluate(solution);
69}
70
71absl::Status ValidateOptions(
72 const MoveVariablesToTheirBestFeasibleValueOptions& options) {
73 if (!std::isfinite(options.integrality_tolerance) ||
74 options.integrality_tolerance < 0.0 ||
75 options.integrality_tolerance > kMaxIntegralityTolerance) {
77 << "integrality_tolerance = "
78 << RoundTripDoubleFormat(options.integrality_tolerance)
79 << " is not in [0, "
80 << RoundTripDoubleFormat(kMaxIntegralityTolerance) << "] range";
81 }
82 return absl::OkStatus();
83}
84
85} // namespace
86
87absl::StatusOr<VariableMap<double>> MoveVariablesToTheirBestFeasibleValue(
88 const Model& model, const VariableMap<double>& input_solution,
89 absl::Span<const Variable> variables,
91 // Validate the inputs.
92 {
93 // TODO(b/193121090): here we build the proto as the APIs of MathOpt only
94 // works with the proto and can't use the C++ Model (or ModelStorage).
95 const ModelProto model_proto = model.ExportModel();
96 RETURN_IF_ERROR(ValidateModel(model_proto).status()) << "invalid model";
98 model_proto,
100 .integer_variables = SupportType::kSupported,
101 },
102 /*solver_name=*/"MoveVariablesToTheirBestFeasibleValue"));
103 }
104 for (const Variable v : variables) {
105 RETURN_IF_ERROR(model.ValidateExistingVariableOfThisModel(v))
106 << "invalid `variables`";
107 if (v.lower_bound() > v.upper_bound()) {
109 << "variable " << v << " bounds ["
110 << RoundTripDoubleFormat(v.lower_bound()) << ", "
111 << RoundTripDoubleFormat(v.upper_bound())
112 << "] integer: " << std::boolalpha << v.is_integer()
113 << " are inverted";
114 }
118 << "integer variable " << v << " has bounds ["
119 << RoundTripDoubleFormat(v.lower_bound()) << ", "
120 << RoundTripDoubleFormat(v.upper_bound())
121 << "] that contain no integer value";
122 }
123 }
124 RETURN_IF_ERROR(ValidateFullFiniteSolution(model, input_solution))
125 << "invalid `input_solution`";
126 RETURN_IF_ERROR(ValidateOptions(options)) << "invalid `options`";
127
128 // We maintain a solution with updated value for each variable in the order of
129 // traversal.
130 //
131 // Invariant: values are finite.
132 VariableMap<double> new_solution = input_solution;
133 // We also maintain the values of each constraint in sync with the values in
134 // new_solution.
135 //
136 // Invariant: constraints_value.at(c) == ConstraintValue(c, new_solution)
137 LinearConstraintMap<double> constraint_values;
138 for (const LinearConstraint c : model.LinearConstraints()) {
139 constraint_values.try_emplace(c, ConstraintValue(c, new_solution));
140 }
141 for (const Variable v : variables) {
142 const double obj_coeff = model.objective_coefficient(v);
143
144 // The variable can't change the objective. We ignore it.
145 if (obj_coeff == 0.0) continue;
146
147 const double v_current_value = new_solution.at(v);
148
149 // We will then compute the best bound of the variable value based on its
150 // own bounds and all the constraints it belongs to. This best bound is
151 // based on the sign of the objective coefficient and the objective
152 // direction (min or max). The positive_v_change value tracks which
153 // direction this is.
154 const bool positive_v_change = model.is_maximize() == (obj_coeff > 0.0);
155
156 // The best_v_bound is the maximum variable's value. We initialize it with
157 // the variable's bounds, we use +/-inf if bounds are infinite.
158 double best_v_bound =
159 positive_v_change ? RoundedUpperBound(v, options.integrality_tolerance)
161
162 // Now iterate on constraints that contain the variable to find the most
163 // limiting one.
164 //
165 // For reason explained below we also keep track of the fact that we found a
166 // limiting constraint, i.e. a constraint with a finite bound in the
167 // direction of improvement of v.
168 bool some_constraints_are_limiting = false;
169 for (const LinearConstraint& c : model.ColumnNonzeros(v)) {
170 const double c_coeff = c.coefficient(v);
171 // The ValidateModel() should have failed.
172 CHECK(std::isfinite(c_coeff)) << v << ": " << c_coeff;
173
174 // The variable has no influence on the constraint.
175 if (c_coeff == 0.0) continue;
176
177 // Based on the constraint coefficient's sign and the variable change
178 // sign, we compute which constraint bound we need to consider.
179 const bool use_constraint_upper_bound =
180 (c_coeff >= 0.0) == positive_v_change;
181
182 // If the bound is not finite, ignore this constraint.
183 const double used_bound =
184 use_constraint_upper_bound ? c.upper_bound() : c.lower_bound();
185 if (!std::isfinite(used_bound)) continue;
186
187 // We have one constraint with a finite bound if we reach this point.
188 some_constraints_are_limiting = true;
189
190 // Compute the bound that the constraint put on the variable.
191 const double c_v_bound = [&]() {
192 const double c_value = constraint_values.at(c);
193
194 // If the constraint value is not finite (could be +/-inf or NaN due to
195 // computation), we consider we can't improve the value of v.
196 //
197 // We could here recompute the constraint value without v and do
198 // something if the value is finite but in practice it is likely to be
199 // an issue.
200 if (!std::isfinite(c_value)) return v_current_value;
201
202 // If we are out of the bounds of the constraint, we can't improve v.
203 //
204 // Note that when use_constraint_upper_bound is false we return when
205 // `c_value < used_bound`, i.e. we use < instead of <=. In practice
206 // though the case c_value == used_bound is covered by the computation
207 // and will return v_current_value.
208 if ((c_value >= used_bound) == use_constraint_upper_bound) {
209 return v_current_value;
210 }
211 // Can be +/-inf; see comment about some_constraints_are_limiting below.
212 return v_current_value + ((used_bound - c_value) / c_coeff);
213 }();
214
215 // Update best_v_bound based on the constraint.
216 if (positive_v_change) {
217 best_v_bound = std::fmin(best_v_bound, c_v_bound);
218 } else {
219 best_v_bound = std::fmax(best_v_bound, c_v_bound);
220 }
221 }
222
223 if (!std::isfinite(best_v_bound)) {
224 if (some_constraints_are_limiting) {
225 // Here we don't fail if constraints have finite bounds but computations
226 // lead to infinite values. This typically occurs when the limiting
227 // constraint has a huge bound and the variable coefficient in the
228 // constraint is small. We could improve the algorithm to pick a finite
229 // value for the variable that does not lead to an overflow but this is
230 // non trivial.
231 continue;
232 }
233 // If there is no limiting constraint with a finite bound and the variable
234 // own bound is infinite, the model is actually unbounded.
236 << "the model is unbounded regarding variable " << v;
237 }
238
239 const double v_improved_value = [&]() {
240 if (!v.is_integer()) {
241 return best_v_bound;
242 }
243 // Make sure the value is integral for integer variables. If we have a
244 // constraint limiting x <= 1.5 we want to use x = 1.
245 //
246 // Note that since best_v_bound is finite, floor or ceil also are.
247 return positive_v_change ? std::floor(best_v_bound)
248 : std::ceil(best_v_bound);
249 }();
250
251 // If we have find no improvement; skip this variable.
252 if (positive_v_change ? v_improved_value <= v_current_value
253 : v_improved_value >= v_current_value)
254 continue;
255
256 // Apply the change to new_solution.
257 //
258 // As v_improved_value is finite the invariant holds.
259 new_solution.at(v) = v_improved_value;
260 // Restore the invariant of constraint_values based on the new_solution.
261 for (const LinearConstraint& c : model.ColumnNonzeros(v)) {
262 // Here we could incrementally update values based on the change of
263 // new_solution.at(v) and the coefficient for (c, v). But since we are
264 // doing floating point computation, we may introduce some errors for each
265 // variable being changed. It is easier to recompute the constraints
266 // values from scratch instead.
267 constraint_values.at(c) = ConstraintValue(c, new_solution);
268 }
269 }
270
271 return new_solution;
272}
273
274} // namespace operations_research::math_opt
#define RETURN_IF_ERROR(expr)
BoundedLinearExpression AsBoundedLinearExpression() const
double coefficient(Variable variable) const
Returns 0.0 if the variable is not used in the constraint.
int64_t value
absl::Status status
Definition g_gurobi.cc:44
GRBmodel * model
double solution
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
absl::flat_hash_map< Variable, V > VariableMap
absl::flat_hash_map< LinearConstraint, V > LinearConstraintMap
double RoundedLowerBound(const Variable v, const double tolerance)
double RoundedUpperBound(const Variable v, const double tolerance)
Same as RoundedLowerBound() but for upper-bound.
absl::StatusOr< ModelSummary > ValidateModel(const ModelProto &model, const bool check_names)
absl::StatusOr< VariableMap< double > > MoveVariablesToTheirBestFeasibleValue(const Model &model, const VariableMap< double > &input_solution, absl::Span< const Variable > variables, const MoveVariablesToTheirBestFeasibleValueOptions &options)
StatusBuilder InvalidArgumentErrorBuilder()
StatusBuilder FailedPreconditionErrorBuilder()