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