Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
dualizer.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
14// Let L be a matrix and b a vector so that a(w) = L * w + b. Then
15//
16// max_w{ a(w) * x : w in W} = max_w{ w' * L' * x : w in W} + b * x
17//
18// where ' is the transpose operation. Because of this we can focus on
19// max_w{ l(w) * x : w in W}.
20//
21// We need the dual to be an LP even when uncertainty_model contains ranged
22// constraints, so we use the LP reformulation of go/mathopt-dual from
23// go/mathopt-traditional-dual#lp-reformulation-split. Using
24// that reformulation, for any fixed x the dual of max_w{ w' * L' * x : w in W}
25// is
26//
27// min_{y, yp, yn, r, rp, rn} obj(yp, yn, rp, rn)
28//
29// A' y + r == L' * x
30// sign constraints on y and r
31// yp + yn == y
32// rp + rn == r
33// yp, rp >= 0
34// yn, rn <= 0
35//
36// where
37//
38// obj(yp, yn, rp, rn) = uc * yp + lc * yn + uv * rp + lv * rn
39//
40// with the convention 0 * infinity = 0 * -infinity = 0.
41//
42// In this dual form x is not multiplied with w so we can consider x a variable
43// instead of a fixed value.
44//
45// Then max_w{ a(w) * x : w in W} <= rhs is equivalent to
46//
47// obj(yp, yn, rp, rn) + b * x <= rhs
48// A' y + r == L' * x
49// sign constraints on y and r
50// yp + yn == y
51// rp + rn == r
52// yp, rp >= 0
53// yn, rn <= 0
54//
55// Note that we can use the equalities yp + yn == y and rp + rn == r to
56// eliminate variables y and r to reduce the number of constraints and variables
57// in the reformulation.
58
60
61#include <limits>
62#include <utility>
63#include <vector>
64
65#include "absl/container/flat_hash_map.h"
66#include "absl/types/span.h"
69
70namespace operations_research {
71namespace math_opt {
72
73namespace {
74
75constexpr double kInf = std::numeric_limits<double>::infinity();
76
77class RobustConstraintDualizer {
78 public:
79 RobustConstraintDualizer(
80 const Model& uncertainty_model, Variable rhs,
81 absl::Span<const std::pair<LinearExpression, Variable>>
82 uncertain_coefficients,
83 Model& main_model);
84
85 private:
86 LinearExpression AddDualizedVariable(double lower_bound, double upper_bound);
87 Model& main_model_;
88
89 // Over the variables of main_model
90 LinearExpression objective_expression_;
91 // The keys are from the uncertain model, the values are from main_model
92 absl::flat_hash_map<LinearConstraint, LinearExpression> y_;
93 // The keys are from the uncertain model, the values are from main_model
94 absl::flat_hash_map<Variable, LinearExpression> r_;
95};
96
97// Let
98// (var, varp, varn, lower_bound, upper_bound) = (y_i, yp_i, yn_i, lc_i, uc_i)
99// or
100// (var, varp, varn, lower_bound, upper_bound) = (r_j, rp_j, rn_j, lv_j, uv_j).
101//
102// The constraints from go/mathopt-traditional-dual#lp-reformulation-split that
103// only involve var, varp and varn are (note that our dual has a max objective):
104//
105// var >= 0 if lower_bound = -infinity
106// var <= 0 if upper_bound = +infinity
107// varp + varn == var
108// varp >= 0
109// varn <= 0
110//
111// and the corresponding term in obj(yp, yn, rp, rn) is
112//
113// upper_bound * varp + lower_bound * varn
114//
115// The following function adds varp and varn, updates the expression for
116// obj(yp, yn, rp, rn) with the associated term and returns the expression for
117// var. The function uses the sign constraints on var, varp and varn and the
118// values of lower_bound and upper_bound to minimize the number of created
119// variables.
120LinearExpression RobustConstraintDualizer::AddDualizedVariable(
121 const double lower_bound, const double upper_bound) {
122 if ((lower_bound <= -kInf) && (upper_bound >= kInf)) {
123 return 0.0;
124 } else if (lower_bound <= -kInf) {
125 const Variable varp = main_model_.AddContinuousVariable(0.0, kInf);
126 objective_expression_ += upper_bound * varp;
127 return varp;
128 } else if (upper_bound >= kInf) {
129 const Variable varn = main_model_.AddContinuousVariable(-kInf, 0.0);
130 objective_expression_ += lower_bound * varn;
131 return varn;
132 } else if (lower_bound == upper_bound) {
133 const Variable var = main_model_.AddContinuousVariable(-kInf, kInf);
134 objective_expression_ += lower_bound * var;
135 return var;
136 } else {
137 const Variable varp = main_model_.AddContinuousVariable(0.0, kInf);
138 const Variable varn = main_model_.AddContinuousVariable(-kInf, 0.0);
139 objective_expression_ += upper_bound * varp + lower_bound * varn;
140 return varp + varn;
141 }
142}
143
144// L' * x
145absl::flat_hash_map<Variable, LinearExpression> TransposeUncertainCoefficients(
146 absl::Span<const std::pair<LinearExpression, Variable>>
147 uncertain_coefficients) {
148 absl::flat_hash_map<Variable, LinearExpression> result;
149 for (const auto& [expression, main_model_variable] : uncertain_coefficients) {
150 for (const auto [v, coefficient] : expression.terms()) {
151 result[v] += coefficient * main_model_variable;
152 }
153 }
154 return result;
155}
156
157RobustConstraintDualizer::RobustConstraintDualizer(
158 const Model& uncertainty_model, const Variable rhs,
159 absl::Span<const std::pair<LinearExpression, Variable>>
160 uncertain_coefficients,
161 Model& main_model)
162 : main_model_(main_model) {
163 const std::vector<Variable> uncertainty_variables =
164 uncertainty_model.SortedVariables();
165 const std::vector<LinearConstraint> uncertainty_constraints =
166 uncertainty_model.SortedLinearConstraints();
167 for (const LinearConstraint c : uncertainty_constraints) {
168 y_.insert({c, AddDualizedVariable(c.lower_bound(), c.upper_bound())});
169 }
170 for (const Variable v : uncertainty_variables) {
171 r_.insert({v, AddDualizedVariable(v.lower_bound(), v.upper_bound())});
172 }
173
174 // Add obj(yp, yn, rp, rn) + b * x <= rhs
175 {
176 LinearExpression offset_expression;
177 for (const auto& [expression, variable] : uncertain_coefficients) {
178 offset_expression += expression.offset() * variable;
179 }
180 main_model_.AddLinearConstraint(objective_expression_ + offset_expression <=
181 rhs);
182 }
183 { // Add A' y + r = L' * x
184 const absl::flat_hash_map<Variable, LinearExpression>
185 equality_rhs_expressions =
186 TransposeUncertainCoefficients(uncertain_coefficients);
187 for (const Variable v : uncertainty_variables) {
188 LinearExpression equality_lhs_expression = r_.at(v);
189 for (const LinearConstraint c : uncertainty_model.ColumnNonzeros(v)) {
190 equality_lhs_expression += c.coefficient(v) * y_.at(c);
191 }
192 main_model_.AddLinearConstraint(
193 equality_lhs_expression ==
194 gtl::FindWithDefault(equality_rhs_expressions, v));
195 }
196 }
197}
198
199} // namespace
200
201void AddRobustConstraint(const Model& uncertainty_model, const Variable rhs,
202 absl::Span<const std::pair<LinearExpression, Variable>>
203 uncertain_coefficients,
204 Model& main_model) {
205 RobustConstraintDualizer dualizer(uncertainty_model, rhs,
206 uncertain_coefficients, main_model);
207}
208
209} // namespace math_opt
210} // namespace operations_research
double coefficient(Variable variable) const
Returns 0.0 if the variable is not used in the constraint.
Variable AddContinuousVariable(double lower_bound, double upper_bound, absl::string_view name="")
Adds a variable to the model with domain [lower_bound, upper_bound].
Definition model.h:963
IntVar * var
double upper_bound
double lower_bound
const MapUtilMappedT< Collection > & FindWithDefault(const Collection &collection, const KeyType &key, const MapUtilMappedT< Collection > &value)
Definition map_util.h:36
void AddRobustConstraint(const Model &uncertainty_model, const Variable rhs, absl::Span< const std::pair< LinearExpression, Variable > > uncertain_coefficients, Model &main_model)
Definition dualizer.cc:202
In SWIG mode, we don't want anything besides these top-level includes.
int64_t coefficient