Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
rays.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 <optional>
17#include <string>
18#include <utility>
19#include <vector>
20
21#include "absl/log/check.h"
22#include "absl/status/status.h"
23#include "absl/status/statusor.h"
24#include "absl/strings/str_cat.h"
29
31namespace {
32
33absl::StatusOr<GlpkRay> ComputePrimalRay(glp_prob* const problem,
34 const int non_basic_variable) {
35 const int num_cstrs = glp_get_num_rows(problem);
36
37 // Check that the non_basic_variable is indeed non basic.
38 const int non_basic_variable_status =
40 /*num_cstrs=*/num_cstrs,
41 /*k=*/non_basic_variable);
42 CHECK_NE(non_basic_variable_status, GLP_BS);
43
44 // When we perform the (primal) simplex algorithm, we detect the primal
45 // unboundness when we have a non-basic variable (here variable can be a
46 // structural or an auxiliary variable) which contributes to increase (for
47 // maximization, decrease for minimization) the objective but none of the
48 // basic variables bounds are limiting its growth. GLPK returns the index of
49 // this non-basic tableau variable.
50 //
51 // To be more precise, here we will use the conventions used in
52 // glpk-5.0/doc/glpk.pdf available from glpk-5.0.tar.gz.
53 //
54 // From (glpk eq. 3.13) we know that the values of the basic variables are
55 // dependent on the values of the non-basic ones:
56 //
57 // x_B = 𝚵 x_N
58 //
59 // where 𝚵 is the tableau defined by (glpk eq. 3.12):
60 //
61 // 𝚵 = -B^-1 N
62 //
63 // Thus if the c-th non basic variable is changed:
64 //
65 // x'_N = x_N + t e_c , e_c ∈ R^n is the c-th standard unit vector
66 // t ∈ R is the change
67 //
68 // Then to keep the primal feasible we must have:
69 //
70 // x'_B = 𝚵 x'_N
71 // = 𝚵 x_N + t 𝚵 e_c
72 // = 𝚵 x_N + t 𝚵 e_c
73 // = x_B + t 𝚵 e_c
74 //
75 // We thus have the primal ray:
76 //
77 // x'_N - x_N = t e_c
78 // x'_B - x_B = t 𝚵 e_c
79 //
80 // From (glpk eq. 3.34) we know that the primal objective is:
81 //
82 // z = d^T x_N + c_0
83 //
84 // I.e. reduced cost d_j shows how the non-basic variable x_j influences the
85 // objective.
86 //
87 // Thus if the problem is a minimization we know that:
88 //
89 // t > 0 , if d_c < 0
90 // t < 0 , if d_c > 0
91 //
92 // Since if it was not the case, the primal simplex algorithm would not have
93 // picked this variable.
94 //
95 // The signs for a maximization are reversed:
96 //
97 // t < 0 , if d_c < 0
98 // t > 0 , if d_c > 0
99 const double reduced_cost = ComputeFormVarReducedCost(
100 problem, /*num_cstrs=*/num_cstrs, /*k=*/non_basic_variable);
101 const double t = (glp_get_obj_dir(problem) == GLP_MAX ? 1.0 : -1.0) *
102 (reduced_cost >= 0 ? 1.0 : -1.0);
103
104 // In case of bounded variables, we can check that the result agrees with the
105 // current active bound. We can't do so for free variables though.
106 switch (non_basic_variable_status) {
107 case GLP_NL: // At lower-bound.
108 if (t == -1.0) {
109 return absl::InternalError(
110 "a non-basic variable at its lower-bound is reported as cause of "
111 "unboundness but the reduced cost's sign indicates that the solver "
112 "considered making it smaller");
113 }
114 break;
115 case GLP_NU: // At upper-bound.
116 if (t == 1.0) {
117 return absl::InternalError(
118 "a non-basic variable at its upper-bound is reported as cause of "
119 "unboundness but the reduced cost's sign indicates that the solver "
120 "considered making it bigger");
121 }
122 break;
123 case GLP_NF: // Free (unbounded).
124 break;
125 default: // GLP_BS (basic), GLP_NS (fixed) or invalid value
126 return absl::InternalError(absl::StrCat(
127 "unexpected ", BasisStatusString(non_basic_variable_status),
128 " reported as cause of unboundness"));
129 }
130
131 GlpkRay::SparseVector ray_non_zeros;
132
133 // As seen in the maths above:
134 //
135 // x'_N - x_N = t e_c
136 //
137 ray_non_zeros.emplace_back(non_basic_variable, t);
138
139 // As seen in the maths above:
140 //
141 // x'_B - x_B = t 𝚵 e_c
142 //
143 // Here 𝚵 e_c is the c-th column of the tableau. We thus use the GLPK function
144 // that returns this column.
145 std::vector<int> inds(num_cstrs + 1);
146 std::vector<double> vals(num_cstrs + 1);
147 const int non_zeros =
148 glp_eval_tab_col(problem, non_basic_variable, inds.data(), vals.data());
149 for (int i = 1; i <= non_zeros; ++i) {
150 ray_non_zeros.emplace_back(inds[i], t * vals[i]);
151 }
152
153 return GlpkRay(GlpkRayType::kPrimal, std::move(ray_non_zeros));
154}
155
156absl::StatusOr<GlpkRay> ComputeDualRay(glp_prob* const problem,
157 const int basic_variable) {
158 const int num_cstrs = glp_get_num_rows(problem);
159
160 // Check that the basic_variable is indeed basic.
161 {
162 const int status = ComputeFormVarStatus(problem,
163 /*num_cstrs=*/num_cstrs,
164 /*k=*/basic_variable);
165 CHECK_EQ(status, GLP_BS) << BasisStatusString(status);
166 }
167
168 // The dual simplex proceeds by repeatedly finding basic variables (here
169 // variable includes structural and auxiliary variables) that are primal
170 // infeasible and replacing them in the basis with a non-basic variable whose
171 // growth is limited by their reduced cost.
172 //
173 // This algorithm detects dual unboundness when we have a basic variable is
174 // primal infeasible (out of its bounds) but there are no non-basic variable
175 // that would limit the growth of its reduced cost, and thus the growth of the
176 // dual objective.
177 //
178 // To be more precise, here we will use the conventions used in
179 // glpk-5.0/doc/glpk.pdf available from glpk-5.0.tar.gz. The dual simplex
180 // algorithm is defined by (https://d-nb.info/978580478/34): Koberstein,
181 // Achim. "The dual simplex method, techniques for a fast and stable
182 // implementation." Unpublished doctoral thesis, Universität Paderborn,
183 // Paderborn, Germany (2005).
184 //
185 // In the following reasoning, we will considering the dual after the
186 // permutation of the basis (glpk eq. 3.27):
187 //
188 // B^T π + λ_B = c_B
189 // N^T π + λ_N = c_N
190 //
191 // We will now see what happens when we relax a basic variable that would
192 // leave the base. See (Koberstein §3.1.2) for details.
193 //
194 // Let's assume we have (π, λ_B, λ_N) that is a basic dual feasible
195 // solution. By definition:
196 //
197 // λ_B = 0
198 //
199 // If we relax the equality constraint of the basic variable r that is primal
200 // infeasible, that is if we relax λ_B_r and get another solution (π', λ'_B,
201 // λ'_N). By definition, all other basic variables stays at equality and thus:
202 //
203 // λ'_B = t e_r , e_r ∈ R^m is the standard unit vector
204 // t ∈ R is the relaxation
205 //
206 // From (glpk eq. 3.30) we have:
207 //
208 // λ'_N = N^T B^-T λ'_B + (c_N - N^T B^-T c_B)
209 // λ'_N = t (B^-1 N)^T e_r + λ_N
210 //
211 // Using the (glpk eq. 3.12) definition of the tableau:
212 //
213 // 𝚵 = -B^-1 N
214 //
215 // We have:
216 //
217 // λ'_N = -t 𝚵^T e_r + λ_N
218 //
219 // That is that the change of the reduced cost of the basic variable r has to
220 // be compensated by the change of the reduced costs the non-basic variables.
221 //
222 // We can write the new dual objective:
223 //
224 // Z' = l^T λ'_l + u^T λ'_u
225 //
226 // If the problem is a minimization we have:
227 //
228 // Z' = sum_{j:λ'_N_j >= 0} l_N_j λ'_N_j +
229 // sum_{j:λ'_N_j <= 0} u_N_j λ'_N_j +
230 // {l_B_r, if t >= 0, u_B_r, else} t
231 //
232 // Here we assume the signs of λ'_N are identical to the ones of λ_N (this is
233 // not an issue with dual simplex since we want to make one non-basic tight to
234 // use it in the basis) we can replace λ'_N with the value computed above and
235 // considering the initial solution was basic which implied that non-basic
236 // where at their bound we can rewrite the objective as:
237 //
238 // Z' = Z - t e_r^T 𝚵 x_N + {l_B_r, if t >= 0, u_B_r, else} t
239 //
240 // We have, using (glpk eq. 3.13):
241 //
242 // e_r^T 𝚵 x_N = e_r^T x_B = x_B_r
243 //
244 // And thus, for a minimization we have:
245 //
246 // Z' - Z = t * {l_B_r - x_B_r, if t >= 0,
247 // u_B_r - x_B_r, if t <= 0}
248 //
249 // Depending on the type of constraint, i.e. depending on whether l_B_r and/or
250 // u_B_r are finite), we have constraints on the sign of `t`. But we can see
251 // that since we pick the basic variable r because it was primal infeasible,
252 // then it should break one of its finite bounds.
253 //
254 // either x_B_r < l_B_r
255 // or u_B_r < x_B_r
256 //
257 // If l_B_r is finite and x_B_r < l_B_r, then choosing:
258 //
259 // t >= 0
260 //
261 // leads to:
262 //
263 // Z' - Z >= 0
264 //
265 // and we see from (glpk eq. 3.17) and the "rule of signs" table (glpk page
266 // 101) that we keep the solution dual feasible by doing so.
267 //
268 // The same logic applies if x_B_r > u_B_r:
269 //
270 // t <= 0
271 //
272 // leads to:
273 //
274 // Z' - Z >= 0
275 //
276 // The dual objective increase in both cases; which is what we want for a
277 // minimization problem since the dual is a maximization.
278 //
279 // For a maximization problem the results are similar but the sign of t
280 // changes (which is expected since the dual is a minimization):
281 //
282 // Z' - Z = t * {l_B_r - x_B_r, if t <= 0,
283 // u_B_r - x_B_r, if t >= 0}
284 //
285 // If a problem is dual unbounded, this means that it is possible to grow t
286 // without limit. I.e. is possible to choose any value for t without making
287 // any λ'_N change sign.
288 //
289 // We can then express the changes of λ' from t:
290 //
291 // λ'_B = t e_r
292 // λ'_N = -t 𝚵^T e_r + λ_N
293 //
294 // Since λ_B = 0, we can rewrite those as:
295 //
296 // λ'_B - λ_B = t e_r
297 // λ'_N - λ_N = -t 𝚵^T e_r
298 //
299 // That is the dual ray.
300 const double primal_value = ComputeFormVarPrimalValue(
301 problem, /*num_cstrs=*/num_cstrs, /*k=*/basic_variable);
302
304 problem, /*num_cstrs=*/num_cstrs, /*k=*/basic_variable);
306 problem, /*num_cstrs=*/num_cstrs, /*k=*/basic_variable);
307 if (!(primal_value > upper_bound || primal_value < lower_bound)) {
308 return absl::InternalError(
309 "dual ray computation failed: GLPK identified a basic variable as the "
310 "source of unboundness but its primal value is within its bounds");
311 }
312
313 // As we have seen in the maths above, depending on which primal bound is
314 // violated and the optimization direction, we choose the sign of t.
315 //
316 // Here the problem is unbounded so we can pick any value for t we want.
317 const double t = (glp_get_obj_dir(problem) == GLP_MAX ? 1.0 : -1.0) *
318 (primal_value > upper_bound ? 1.0 : -1.0);
319
320 GlpkRay::SparseVector ray_non_zeros;
321
322 // As seen in the math above:
323 //
324 // λ'_B - λ_B = t e_r
325 //
326 ray_non_zeros.emplace_back(basic_variable, t);
327
328 // As we have seen above, to keep the dual feasible, we must update the
329 // reduced costs of the non-basic variables by the formula:
330 //
331 // λ'_N - λ_N = -t 𝚵^T e_r
332 //
333 // Here 𝚵^T e_r is the r-th row of the tableau. We thus use the GLPK function
334 // that returns this row.
335 const int num_structural_vars = glp_get_num_cols(problem);
336 std::vector<int> inds(num_structural_vars + 1);
337 std::vector<double> vals(num_structural_vars + 1);
338 const int non_zeros =
339 glp_eval_tab_row(problem, basic_variable, inds.data(), vals.data());
340 for (int i = 1; i <= non_zeros; ++i) {
341 ray_non_zeros.emplace_back(inds[i], -t * vals[i]);
342 }
343
344 return GlpkRay(GlpkRayType::kDual, std::move(ray_non_zeros));
345}
346
347} // namespace
348
349GlpkRay::GlpkRay(const GlpkRayType type, SparseVector non_zero_components)
350 : type(type), non_zero_components(std::move(non_zero_components)) {}
351
352absl::StatusOr<std::optional<GlpkRay>> GlpkComputeUnboundRay(
353 glp_prob* const problem) {
354 const int unbound_ray = glp_get_unbnd_ray(problem);
355 if (unbound_ray <= 0) {
356 // No ray, do nothing.
357 DCHECK_EQ(unbound_ray, 0);
358 return std::nullopt;
359 }
360
361 // The factorization may not exists when GLPK's trivial_lp() is used to solve
362 // a trivial LP. Here we force the computation of the factorization if
363 // necessary.
364 if (!glp_bf_exists(problem)) {
365 const int factorization_rc = glp_factorize(problem);
366 if (factorization_rc != 0) {
367 return util::InternalErrorBuilder() << "glp_factorize() failed: "
368 << ReturnCodeString(factorization_rc);
369 }
370 }
371
372 // The function glp_get_unbnd_ray() returns either:
373 // - a non-basic tableau variable if we have primal unboundness.
374 // - a basic tableau variable if we have dual unboundness.
375 const bool is_dual_ray =
376 ComputeFormVarStatus(problem,
377 /*num_cstrs=*/glp_get_num_rows(problem),
378 /*k=*/unbound_ray) == GLP_BS;
379 ASSIGN_OR_RETURN(const GlpkRay ray,
380 (is_dual_ray ? ComputeDualRay(problem, unbound_ray)
381 : ComputePrimalRay(problem, unbound_ray)));
382 return ray;
383}
384
385} // namespace operations_research::math_opt
#define ASSIGN_OR_RETURN(lhs, rexpr)
absl::Status status
Definition g_gurobi.cc:44
double upper_bound
double lower_bound
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
absl::StatusOr< std::optional< GlpkRay > > GlpkComputeUnboundRay(glp_prob *const problem)
Definition rays.cc:352
GlpkRayType
The type of the GlpkRay.
Definition rays.h:33
double ComputeFormVarPrimalValue(glp_prob *const problem, const int num_cstrs, const int k)
int ComputeFormVarStatus(glp_prob *const problem, const int num_cstrs, const int k)
std::string BasisStatusString(const int stat)
Formats a linear constraint or variable basis status (GLP_BS,...).
double ComputeFormVarReducedCost(glp_prob *const problem, const int num_cstrs, const int k)
double ComputeFormVarLowerBound(glp_prob *const problem, const int num_cstrs, const int k)
double ComputeFormVarUpperBound(glp_prob *const problem, const int num_cstrs, const int k)
std::string ReturnCodeString(const int rc)
STL namespace.
StatusBuilder InternalErrorBuilder()
GlpkRay(GlpkRayType type, SparseVector non_zero_components)
Definition rays.cc:349
std::vector< std::pair< int, double > > SparseVector
Definition rays.h:56