OR-Tools  9.1
lp_solver.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 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 #include "ortools/glop/lp_solver.h"
15 
16 #include <cmath>
17 #include <stack>
18 #include <vector>
19 
20 #include "absl/memory/memory.h"
21 #include "absl/strings/match.h"
22 #include "absl/strings/str_format.h"
25 #include "ortools/base/timer.h"
27 #include "ortools/glop/status.h"
31 #include "ortools/util/fp_utils.h"
32 
33 // TODO(user): abstract this in some way to the port directory.
34 #ifndef __PORTABLE_PLATFORM__
35 #include "ortools/util/file_util.h"
36 #endif
37 
38 ABSL_FLAG(bool, lp_solver_enable_fp_exceptions, false,
39  "When true, NaNs and division / zero produce errors. "
40  "This is very useful for debugging, but incompatible with LLVM. "
41  "It is recommended to set this to false for production usage.");
42 ABSL_FLAG(bool, lp_dump_to_proto_file, false,
43  "Tells whether do dump the problem to a protobuf file.");
44 ABSL_FLAG(bool, lp_dump_compressed_file, true,
45  "Whether the proto dump file is compressed.");
46 ABSL_FLAG(bool, lp_dump_binary_file, false,
47  "Whether the proto dump file is binary.");
48 ABSL_FLAG(int, lp_dump_file_number, -1,
49  "Number for the dump file, in the form name-000048.pb. "
50  "If < 0, the file is automatically numbered from the number of "
51  "calls to LPSolver::Solve().");
52 ABSL_FLAG(std::string, lp_dump_dir, "/tmp",
53  "Directory where dump files are written.");
54 ABSL_FLAG(std::string, lp_dump_file_basename, "",
55  "Base name for dump files. LinearProgram::name_ is used if "
56  "lp_dump_file_basename is empty. If LinearProgram::name_ is "
57  "empty, \"linear_program_dump_file\" is used.");
58 ABSL_FLAG(std::string, glop_params, "",
59  "Override any user parameters with the value of this flag. This is "
60  "interpreted as a GlopParameters proto in text format.");
61 
62 namespace operations_research {
63 namespace glop {
64 namespace {
65 
66 // Writes a LinearProgram to a file if FLAGS_lp_dump_to_proto_file is true. The
67 // integer num is appended to the base name of the file. When this function is
68 // called from LPSolver::Solve(), num is usually the number of times Solve() was
69 // called. For a LinearProgram whose name is "LinPro", and num = 48, the default
70 // output file will be /tmp/LinPro-000048.pb.gz.
71 //
72 // Warning: is a no-op on portable platforms (android, ios, etc).
73 void DumpLinearProgramIfRequiredByFlags(const LinearProgram& linear_program,
74  int num) {
75  if (!absl::GetFlag(FLAGS_lp_dump_to_proto_file)) return;
76 #ifdef __PORTABLE_PLATFORM__
77  LOG(WARNING) << "DumpLinearProgramIfRequiredByFlags(linear_program, num) "
78  "requested for linear_program.name()='"
79  << linear_program.name() << "', num=" << num
80  << " but is not implemented for this platform.";
81 #else
82  std::string filename = absl::GetFlag(FLAGS_lp_dump_file_basename);
83  if (filename.empty()) {
84  if (linear_program.name().empty()) {
85  filename = "linear_program_dump";
86  } else {
87  filename = linear_program.name();
88  }
89  }
90  const int file_num = absl::GetFlag(FLAGS_lp_dump_file_number) >= 0
91  ? absl::GetFlag(FLAGS_lp_dump_file_number)
92  : num;
93  absl::StrAppendFormat(&filename, "-%06d.pb", file_num);
94  const std::string filespec =
95  absl::StrCat(absl::GetFlag(FLAGS_lp_dump_dir), "/", filename);
96  MPModelProto proto;
97  LinearProgramToMPModelProto(linear_program, &proto);
98  const ProtoWriteFormat write_format = absl::GetFlag(FLAGS_lp_dump_binary_file)
101  if (!WriteProtoToFile(filespec, proto, write_format,
102  absl::GetFlag(FLAGS_lp_dump_compressed_file))) {
103  LOG(DFATAL) << "Could not write " << filespec;
104  }
105 #endif
106 }
107 
108 } // anonymous namespace
109 
110 // --------------------------------------------------------
111 // LPSolver
112 // --------------------------------------------------------
113 
114 LPSolver::LPSolver() : num_solves_(0) {}
115 
117  parameters_ = parameters;
118 #ifndef __PORTABLE_PLATFORM__
119  if (!absl::GetFlag(FLAGS_glop_params).empty()) {
120  GlopParameters flag_params;
121  CHECK(google::protobuf::TextFormat::ParseFromString(
122  absl::GetFlag(FLAGS_glop_params), &flag_params));
123  parameters_.MergeFrom(flag_params);
124  }
125 #endif
126 }
127 
128 const GlopParameters& LPSolver::GetParameters() const { return parameters_; }
129 
131 
133  std::unique_ptr<TimeLimit> time_limit =
134  TimeLimit::FromParameters(parameters_);
135  return SolveWithTimeLimit(lp, time_limit.get());
136 }
137 
140  if (time_limit == nullptr) {
141  LOG(DFATAL) << "SolveWithTimeLimit() called with a nullptr time_limit.";
143  }
144  ++num_solves_;
145  num_revised_simplex_iterations_ = 0;
146  DumpLinearProgramIfRequiredByFlags(lp, num_solves_);
147  // Check some preconditions.
148  if (!lp.IsCleanedUp()) {
149  LOG(DFATAL) << "The columns of the given linear program should be ordered "
150  << "by row and contain no zero coefficients. Call CleanUp() "
151  << "on it before calling Solve().";
152  ResizeSolution(lp.num_constraints(), lp.num_variables());
154  }
155  if (!lp.IsValid()) {
156  LOG(DFATAL) << "The given linear program is invalid. It contains NaNs, "
157  << "infinite coefficients or invalid bounds specification. "
158  << "You can construct it in debug mode to get the exact cause.";
159  ResizeSolution(lp.num_constraints(), lp.num_variables());
161  }
162  // Display a warning if running in non-opt, unless we're inside a unit test.
163  DLOG(WARNING)
164  << "\n******************************************************************"
165  "\n* WARNING: Glop will be very slow because it will use DCHECKs *"
166  "\n* to verify the results and the precision of the solver. *"
167  "\n* You can gain at least an order of magnitude speedup by *"
168  "\n* compiling with optimizations enabled and by defining NDEBUG. *"
169  "\n******************************************************************";
170 
171  // Note that we only activate the floating-point exceptions after we are sure
172  // that the program is valid. This way, if we have input NaNs, we will not
173  // crash.
174  ScopedFloatingPointEnv scoped_fenv;
175  if (absl::GetFlag(FLAGS_lp_solver_enable_fp_exceptions)) {
176 #ifdef _MSC_VER
177  scoped_fenv.EnableExceptions(_EM_INVALID | EM_ZERODIVIDE);
178 #else
179  scoped_fenv.EnableExceptions(FE_DIVBYZERO | FE_INVALID);
180 #endif
181  }
182 
183  // Make an internal copy of the problem for the preprocessing.
184  const bool log_info = parameters_.log_search_progress() || VLOG_IS_ON(1);
185  if (log_info) {
186  LOG(INFO) << "Initial problem: " << lp.GetDimensionString();
187  LOG(INFO) << "Objective stats: " << lp.GetObjectiveStatsString();
188  LOG(INFO) << "Bounds stats: " << lp.GetBoundsStatsString();
189  }
190  current_linear_program_.PopulateFromLinearProgram(lp);
191 
192  // Preprocess.
193  MainLpPreprocessor preprocessor(&parameters_);
194  preprocessor.SetTimeLimit(time_limit);
195 
196  const bool postsolve_is_needed = preprocessor.Run(&current_linear_program_);
197 
198  if (log_info) {
199  LOG(INFO) << "Presolved problem: "
200  << current_linear_program_.GetDimensionString();
201  LOG(INFO) << "Objective stats: "
202  << current_linear_program_.GetObjectiveStatsString();
203  LOG(INFO) << "Bounds stats: "
204  << current_linear_program_.GetBoundsStatsString();
205  }
206 
207  // At this point, we need to initialize a ProblemSolution with the correct
208  // size and status.
209  ProblemSolution solution(current_linear_program_.num_constraints(),
210  current_linear_program_.num_variables());
211  solution.status = preprocessor.status();
212 
213  // Do not launch the solver if the time limit was already reached. This might
214  // mean that the pre-processors were not all run, and current_linear_program_
215  // might not be in a completely safe state.
216  if (!time_limit->LimitReached()) {
217  RunRevisedSimplexIfNeeded(&solution, time_limit);
218  }
219 
220  if (postsolve_is_needed) preprocessor.DestructiveRecoverSolution(&solution);
221  const ProblemStatus status = LoadAndVerifySolution(lp, solution);
222 
223  // LOG some statistics that can be parsed by our benchmark script.
224  if (log_info) {
225  LOG(INFO) << "status: " << status;
226  LOG(INFO) << "objective: " << GetObjectiveValue();
227  LOG(INFO) << "iterations: " << GetNumberOfSimplexIterations();
228  LOG(INFO) << "time: " << time_limit->GetElapsedTime();
229  LOG(INFO) << "deterministic_time: "
230  << time_limit->GetElapsedDeterministicTime();
231  }
232 
233  return status;
234 }
235 
237  ResizeSolution(RowIndex(0), ColIndex(0));
238  revised_simplex_.reset(nullptr);
239 }
240 
242  const VariableStatusRow& variable_statuses,
243  const ConstraintStatusColumn& constraint_statuses) {
244  // Create the associated basis state.
245  BasisState state;
246  state.statuses = variable_statuses;
247  for (const ConstraintStatus status : constraint_statuses) {
248  // Note the change of upper/lower bound between the status of a constraint
249  // and the status of its associated slack variable.
250  switch (status) {
253  break;
256  break;
259  break;
262  break;
265  break;
266  }
267  }
268  if (revised_simplex_ == nullptr) {
269  revised_simplex_ = absl::make_unique<RevisedSimplex>();
270  }
271  revised_simplex_->LoadStateForNextSolve(state);
272  if (parameters_.use_preprocessing()) {
273  LOG(WARNING) << "In GLOP, SetInitialBasis() was called but the parameter "
274  "use_preprocessing is true, this will likely not result in "
275  "what you want.";
276  }
277 }
278 
279 namespace {
280 // Computes the "real" problem objective from the one without offset nor
281 // scaling.
282 Fractional ProblemObjectiveValue(const LinearProgram& lp, Fractional value) {
283  return lp.objective_scaling_factor() * (value + lp.objective_offset());
284 }
285 
286 // Returns the allowed error magnitude for something that should evaluate to
287 // value under the given tolerance.
288 Fractional AllowedError(Fractional tolerance, Fractional value) {
289  return tolerance * std::max(1.0, std::abs(value));
290 }
291 } // namespace
292 
293 // TODO(user): Try to also check the precision of an INFEASIBLE or UNBOUNDED
294 // return status.
296  const ProblemSolution& solution) {
297  const bool log_info = parameters_.log_search_progress() || VLOG_IS_ON(1);
298 
299  if (!IsProblemSolutionConsistent(lp, solution)) {
300  if (log_info) LOG(INFO) << "Inconsistency detected in the solution.";
301  ResizeSolution(lp.num_constraints(), lp.num_variables());
303  }
304 
305  // Load the solution.
306  primal_values_ = solution.primal_values;
307  dual_values_ = solution.dual_values;
308  variable_statuses_ = solution.variable_statuses;
309  constraint_statuses_ = solution.constraint_statuses;
310  ProblemStatus status = solution.status;
311 
312  // Objective before eventually moving the primal/dual values inside their
313  // bounds.
314  ComputeReducedCosts(lp);
315  const Fractional primal_objective_value = ComputeObjective(lp);
316  const Fractional dual_objective_value = ComputeDualObjective(lp);
317  if (log_info) {
318  LOG(INFO) << "Primal objective (before moving primal/dual values) = "
319  << absl::StrFormat("%.15E", ProblemObjectiveValue(
320  lp, primal_objective_value));
321  LOG(INFO) << "Dual objective (before moving primal/dual values) = "
322  << absl::StrFormat(
323  "%.15E", ProblemObjectiveValue(lp, dual_objective_value));
324  }
325 
326  // Eventually move the primal/dual values inside their bounds.
327  if (status == ProblemStatus::OPTIMAL &&
328  parameters_.provide_strong_optimal_guarantee()) {
329  MovePrimalValuesWithinBounds(lp);
330  MoveDualValuesWithinBounds(lp);
331  }
332 
333  // The reported objective to the user.
334  problem_objective_value_ = ProblemObjectiveValue(lp, ComputeObjective(lp));
335  if (log_info) {
336  LOG(INFO) << "Primal objective (after moving primal/dual values) = "
337  << absl::StrFormat("%.15E", problem_objective_value_);
338  }
339 
340  ComputeReducedCosts(lp);
341  ComputeConstraintActivities(lp);
342 
343  // These will be set to true if the associated "infeasibility" is too large.
344  //
345  // The tolerance used is the parameter solution_feasibility_tolerance. To be
346  // somewhat independent of the original problem scaling, the thresholds used
347  // depend of the quantity involved and of its coordinates:
348  // - tolerance * max(1.0, abs(cost[col])) when a reduced cost is infeasible.
349  // - tolerance * max(1.0, abs(bound)) when a bound is crossed.
350  // - tolerance for an infeasible dual value (because the limit is always 0.0).
351  bool rhs_perturbation_is_too_large = false;
352  bool cost_perturbation_is_too_large = false;
353  bool primal_infeasibility_is_too_large = false;
354  bool dual_infeasibility_is_too_large = false;
355  bool primal_residual_is_too_large = false;
356  bool dual_residual_is_too_large = false;
357 
358  // Computes all the infeasiblities and update the Booleans above.
359  ComputeMaxRhsPerturbationToEnforceOptimality(lp,
360  &rhs_perturbation_is_too_large);
361  ComputeMaxCostPerturbationToEnforceOptimality(
362  lp, &cost_perturbation_is_too_large);
363  const double primal_infeasibility =
364  ComputePrimalValueInfeasibility(lp, &primal_infeasibility_is_too_large);
365  const double dual_infeasibility =
366  ComputeDualValueInfeasibility(lp, &dual_infeasibility_is_too_large);
367  const double primal_residual =
368  ComputeActivityInfeasibility(lp, &primal_residual_is_too_large);
369  const double dual_residual =
370  ComputeReducedCostInfeasibility(lp, &dual_residual_is_too_large);
371 
372  // TODO(user): the name is not really consistent since in practice those are
373  // the "residual" since the primal/dual infeasibility are zero when
374  // parameters_.provide_strong_optimal_guarantee() is true.
375  max_absolute_primal_infeasibility_ =
376  std::max(primal_infeasibility, primal_residual);
377  max_absolute_dual_infeasibility_ =
378  std::max(dual_infeasibility, dual_residual);
379  if (log_info) {
380  LOG(INFO) << "Max. primal infeasibility = "
381  << max_absolute_primal_infeasibility_;
382  LOG(INFO) << "Max. dual infeasibility = "
383  << max_absolute_dual_infeasibility_;
384  }
385 
386  // Now that all the relevant quantities are computed, we check the precision
387  // and optimality of the result. See Chvatal pp. 61-62. If any of the tests
388  // fail, we return the IMPRECISE status.
389  const double objective_error_ub = ComputeMaxExpectedObjectiveError(lp);
390  if (log_info) {
391  LOG(INFO) << "Objective error <= " << objective_error_ub;
392  }
393 
394  if (status == ProblemStatus::OPTIMAL &&
395  parameters_.provide_strong_optimal_guarantee()) {
396  // If the primal/dual values were moved to the bounds, then the primal/dual
397  // infeasibilities should be exactly zero (but not the residuals).
398  if (primal_infeasibility != 0.0 || dual_infeasibility != 0.0) {
399  LOG(ERROR) << "Primal/dual values have been moved to their bounds. "
400  << "Therefore the primal/dual infeasibilities should be "
401  << "exactly zero (but not the residuals). If this message "
402  << "appears, there is probably a bug in "
403  << "MovePrimalValuesWithinBounds() or in "
404  << "MoveDualValuesWithinBounds().";
405  }
406  if (rhs_perturbation_is_too_large) {
407  if (log_info) LOG(INFO) << "The needed rhs perturbation is too large !!";
408  status = ProblemStatus::IMPRECISE;
409  }
410  if (cost_perturbation_is_too_large) {
411  if (log_info) LOG(INFO) << "The needed cost perturbation is too large !!";
412  status = ProblemStatus::IMPRECISE;
413  }
414  }
415 
416  // Note that we compare the values without offset nor scaling. We also need to
417  // compare them before we move the primal/dual values, otherwise we lose some
418  // precision since the values are modified independently of each other.
419  if (status == ProblemStatus::OPTIMAL) {
420  if (std::abs(primal_objective_value - dual_objective_value) >
421  objective_error_ub) {
422  if (log_info) {
423  LOG(INFO) << "The objective gap of the final solution is too large.";
424  }
425  status = ProblemStatus::IMPRECISE;
426  }
427  }
428  if ((status == ProblemStatus::OPTIMAL ||
429  status == ProblemStatus::PRIMAL_FEASIBLE) &&
430  (primal_residual_is_too_large || primal_infeasibility_is_too_large)) {
431  if (log_info) {
432  LOG(INFO)
433  << "The primal infeasibility of the final solution is too large.";
434  }
435  status = ProblemStatus::IMPRECISE;
436  }
437  if ((status == ProblemStatus::OPTIMAL ||
438  status == ProblemStatus::DUAL_FEASIBLE) &&
439  (dual_residual_is_too_large || dual_infeasibility_is_too_large)) {
440  if (log_info) {
441  LOG(INFO) << "The dual infeasibility of the final solution is too large.";
442  }
443  status = ProblemStatus::IMPRECISE;
444  }
445 
446  may_have_multiple_solutions_ =
447  (status == ProblemStatus::OPTIMAL) ? IsOptimalSolutionOnFacet(lp) : false;
448  return status;
449 }
450 
451 bool LPSolver::IsOptimalSolutionOnFacet(const LinearProgram& lp) {
452  // Note(user): We use the following same two tolerances for the dual and
453  // primal values.
454  // TODO(user): investigate whether to use the tolerances defined in
455  // parameters.proto.
456  const double kReducedCostTolerance = 1e-9;
457  const double kBoundTolerance = 1e-7;
458  const ColIndex num_cols = lp.num_variables();
459  for (ColIndex col(0); col < num_cols; ++col) {
460  if (variable_statuses_[col] == VariableStatus::FIXED_VALUE) continue;
463  const Fractional value = primal_values_[col];
464  if (AreWithinAbsoluteTolerance(reduced_costs_[col], 0.0,
465  kReducedCostTolerance) &&
466  (AreWithinAbsoluteTolerance(value, lower_bound, kBoundTolerance) ||
467  AreWithinAbsoluteTolerance(value, upper_bound, kBoundTolerance))) {
468  return true;
469  }
470  }
471  const RowIndex num_rows = lp.num_constraints();
472  for (RowIndex row(0); row < num_rows; ++row) {
473  if (constraint_statuses_[row] == ConstraintStatus::FIXED_VALUE) continue;
476  const Fractional activity = constraint_activities_[row];
477  if (AreWithinAbsoluteTolerance(dual_values_[row], 0.0,
478  kReducedCostTolerance) &&
479  (AreWithinAbsoluteTolerance(activity, lower_bound, kBoundTolerance) ||
480  AreWithinAbsoluteTolerance(activity, upper_bound, kBoundTolerance))) {
481  return true;
482  }
483  }
484  return false;
485 }
486 
488  return problem_objective_value_;
489 }
490 
492  return max_absolute_primal_infeasibility_;
493 }
494 
496  return max_absolute_dual_infeasibility_;
497 }
498 
500  return may_have_multiple_solutions_;
501 }
502 
504  return num_revised_simplex_iterations_;
505 }
506 
508  return revised_simplex_ == nullptr ? 0.0
509  : revised_simplex_->DeterministicTime();
510 }
511 
512 void LPSolver::MovePrimalValuesWithinBounds(const LinearProgram& lp) {
513  const ColIndex num_cols = lp.num_variables();
514  DCHECK_EQ(num_cols, primal_values_.size());
515  Fractional error = 0.0;
516  for (ColIndex col(0); col < num_cols; ++col) {
520 
521  error = std::max(error, primal_values_[col] - upper_bound);
522  error = std::max(error, lower_bound - primal_values_[col]);
523  primal_values_[col] = std::min(primal_values_[col], upper_bound);
524  primal_values_[col] = std::max(primal_values_[col], lower_bound);
525  }
526  const bool log_info = parameters_.log_search_progress() || VLOG_IS_ON(1);
527  if (log_info) LOG(INFO) << "Max. primal values move = " << error;
528 }
529 
530 void LPSolver::MoveDualValuesWithinBounds(const LinearProgram& lp) {
531  const RowIndex num_rows = lp.num_constraints();
532  DCHECK_EQ(num_rows, dual_values_.size());
533  const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
534  Fractional error = 0.0;
535  for (RowIndex row(0); row < num_rows; ++row) {
536  const Fractional lower_bound = lp.constraint_lower_bounds()[row];
537  const Fractional upper_bound = lp.constraint_upper_bounds()[row];
538 
539  // For a minimization problem, we want a lower bound.
540  Fractional minimization_dual_value = optimization_sign * dual_values_[row];
541  if (lower_bound == -kInfinity && minimization_dual_value > 0.0) {
542  error = std::max(error, minimization_dual_value);
543  minimization_dual_value = 0.0;
544  }
545  if (upper_bound == kInfinity && minimization_dual_value < 0.0) {
546  error = std::max(error, -minimization_dual_value);
547  minimization_dual_value = 0.0;
548  }
549  dual_values_[row] = optimization_sign * minimization_dual_value;
550  }
551  const bool log_info = parameters_.log_search_progress() || VLOG_IS_ON(1);
552  if (log_info) LOG(INFO) << "Max. dual values move = " << error;
553 }
554 
555 void LPSolver::ResizeSolution(RowIndex num_rows, ColIndex num_cols) {
556  primal_values_.resize(num_cols, 0.0);
557  reduced_costs_.resize(num_cols, 0.0);
558  variable_statuses_.resize(num_cols, VariableStatus::FREE);
559 
560  dual_values_.resize(num_rows, 0.0);
561  constraint_activities_.resize(num_rows, 0.0);
562  constraint_statuses_.resize(num_rows, ConstraintStatus::FREE);
563 }
564 
565 void LPSolver::RunRevisedSimplexIfNeeded(ProblemSolution* solution,
566  TimeLimit* time_limit) {
567  // Note that the transpose matrix is no longer needed at this point.
568  // This helps reduce the peak memory usage of the solver.
569  //
570  // TODO(user): actually, once the linear_program is loaded into the internal
571  // glop memory, there is no point keeping it around. Add a more complex
572  // Load/Solve API to RevisedSimplex so we can completely reclaim its memory
573  // right away.
574  current_linear_program_.ClearTransposeMatrix();
575  if (solution->status != ProblemStatus::INIT) return;
576  if (revised_simplex_ == nullptr) {
577  revised_simplex_ = absl::make_unique<RevisedSimplex>();
578  }
579  revised_simplex_->SetParameters(parameters_);
580  if (revised_simplex_->Solve(current_linear_program_, time_limit).ok()) {
581  num_revised_simplex_iterations_ = revised_simplex_->GetNumberOfIterations();
582  solution->status = revised_simplex_->GetProblemStatus();
583 
584  // Make sure we do not copy the slacks added by revised_simplex_.
585  const ColIndex num_cols = solution->primal_values.size();
586  DCHECK_LE(num_cols, revised_simplex_->GetProblemNumCols());
587  for (ColIndex col(0); col < num_cols; ++col) {
588  solution->primal_values[col] = revised_simplex_->GetVariableValue(col);
589  solution->variable_statuses[col] =
590  revised_simplex_->GetVariableStatus(col);
591  }
592 
593  const RowIndex num_rows = revised_simplex_->GetProblemNumRows();
594  DCHECK_EQ(solution->dual_values.size(), num_rows);
595  for (RowIndex row(0); row < num_rows; ++row) {
596  solution->dual_values[row] = revised_simplex_->GetDualValue(row);
597  solution->constraint_statuses[row] =
598  revised_simplex_->GetConstraintStatus(row);
599  }
600  } else {
601  const bool log_info = parameters_.log_search_progress() || VLOG_IS_ON(1);
602  if (log_info) LOG(INFO) << "Error during the revised simplex algorithm.";
603  solution->status = ProblemStatus::ABNORMAL;
604  }
605 }
606 
607 namespace {
608 
609 void LogVariableStatusError(ColIndex col, Fractional value,
610  VariableStatus status, Fractional lb,
611  Fractional ub) {
612  VLOG(1) << "Variable " << col << " status is "
613  << GetVariableStatusString(status) << " but its value is " << value
614  << " and its bounds are [" << lb << ", " << ub << "].";
615 }
616 
617 void LogConstraintStatusError(RowIndex row, ConstraintStatus status,
618  Fractional lb, Fractional ub) {
619  VLOG(1) << "Constraint " << row << " status is "
620  << GetConstraintStatusString(status) << " but its bounds are [" << lb
621  << ", " << ub << "].";
622 }
623 
624 } // namespace
625 
626 bool LPSolver::IsProblemSolutionConsistent(
627  const LinearProgram& lp, const ProblemSolution& solution) const {
628  const RowIndex num_rows = lp.num_constraints();
629  const ColIndex num_cols = lp.num_variables();
630  if (solution.variable_statuses.size() != num_cols) return false;
631  if (solution.constraint_statuses.size() != num_rows) return false;
632  if (solution.primal_values.size() != num_cols) return false;
633  if (solution.dual_values.size() != num_rows) return false;
634  if (solution.status != ProblemStatus::OPTIMAL &&
635  solution.status != ProblemStatus::PRIMAL_FEASIBLE &&
636  solution.status != ProblemStatus::DUAL_FEASIBLE) {
637  return true;
638  }
639 
640  // This checks that the variable statuses verify the properties described
641  // in the VariableStatus declaration.
642  RowIndex num_basic_variables(0);
643  for (ColIndex col(0); col < num_cols; ++col) {
644  const Fractional value = solution.primal_values[col];
645  const Fractional lb = lp.variable_lower_bounds()[col];
646  const Fractional ub = lp.variable_upper_bounds()[col];
647  const VariableStatus status = solution.variable_statuses[col];
648  switch (solution.variable_statuses[col]) {
650  // TODO(user): Check that the reduced cost of this column is epsilon
651  // close to zero.
652  ++num_basic_variables;
653  break;
655  // TODO(user): Because of scaling, it is possible that a FIXED_VALUE
656  // status (only reserved for the exact lb == ub case) is now set for a
657  // variable where (ub == lb + epsilon). So we do not check here that the
658  // two bounds are exactly equal. The best is probably to remove the
659  // FIXED status from the API completely and report one of AT_LOWER_BOUND
660  // or AT_UPPER_BOUND instead. This also allows to indicate if at
661  // optimality, the objective is limited because of this variable lower
662  // bound or its upper bound. Note that there are other TODOs in the
663  // codebase about removing this FIXED_VALUE status.
664  if (value != ub && value != lb) {
665  LogVariableStatusError(col, value, status, lb, ub);
666  return false;
667  }
668  break;
670  if (value != lb || lb == ub) {
671  LogVariableStatusError(col, value, status, lb, ub);
672  return false;
673  }
674  break;
676  // TODO(user): revert to an exact comparison once the bug causing this
677  // to fail has been fixed.
678  if (!AreWithinAbsoluteTolerance(value, ub, 1e-7) || lb == ub) {
679  LogVariableStatusError(col, value, status, lb, ub);
680  return false;
681  }
682  break;
684  if (lb != -kInfinity || ub != kInfinity || value != 0.0) {
685  LogVariableStatusError(col, value, status, lb, ub);
686  return false;
687  }
688  break;
689  }
690  }
691  for (RowIndex row(0); row < num_rows; ++row) {
692  const Fractional dual_value = solution.dual_values[row];
693  const Fractional lb = lp.constraint_lower_bounds()[row];
694  const Fractional ub = lp.constraint_upper_bounds()[row];
695  const ConstraintStatus status = solution.constraint_statuses[row];
696 
697  // The activity value is not checked since it is imprecise.
698  // TODO(user): Check that the activity is epsilon close to the expected
699  // value.
700  switch (status) {
702  if (dual_value != 0.0) {
703  VLOG(1) << "Constraint " << row << " is BASIC, but its dual value is "
704  << dual_value << " instead of 0.";
705  return false;
706  }
707  ++num_basic_variables;
708  break;
710  // Exactly the same remark as for the VariableStatus::FIXED_VALUE case
711  // above. Because of precision error, this can happen when the
712  // difference between the two bounds is small and not just exactly zero.
713  if (ub - lb > 1e-12) {
714  LogConstraintStatusError(row, status, lb, ub);
715  return false;
716  }
717  break;
719  if (lb == -kInfinity) {
720  LogConstraintStatusError(row, status, lb, ub);
721  return false;
722  }
723  break;
725  if (ub == kInfinity) {
726  LogConstraintStatusError(row, status, lb, ub);
727  return false;
728  }
729  break;
731  if (dual_value != 0.0) {
732  VLOG(1) << "Constraint " << row << " is FREE, but its dual value is "
733  << dual_value << " instead of 0.";
734  return false;
735  }
736  if (lb != -kInfinity || ub != kInfinity) {
737  LogConstraintStatusError(row, status, lb, ub);
738  return false;
739  }
740  break;
741  }
742  }
743 
744  // TODO(user): We could check in debug mode (because it will be costly) that
745  // the basis is actually factorizable.
746  if (num_basic_variables != num_rows) {
747  VLOG(1) << "Wrong number of basic variables: " << num_basic_variables;
748  return false;
749  }
750  return true;
751 }
752 
753 // This computes by how much the objective must be perturbed to enforce the
754 // following complementary slackness conditions:
755 // - Reduced cost is exactly zero for FREE and BASIC variables.
756 // - Reduced cost is of the correct sign for variables at their bounds.
757 Fractional LPSolver::ComputeMaxCostPerturbationToEnforceOptimality(
758  const LinearProgram& lp, bool* is_too_large) {
759  Fractional max_cost_correction = 0.0;
760  const ColIndex num_cols = lp.num_variables();
761  const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
762  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
763  for (ColIndex col(0); col < num_cols; ++col) {
764  // We correct the reduced cost, so we have a minimization problem and
765  // thus the dual objective value will be a lower bound of the primal
766  // objective.
767  const Fractional reduced_cost = optimization_sign * reduced_costs_[col];
768  const VariableStatus status = variable_statuses_[col];
769  if (status == VariableStatus::BASIC || status == VariableStatus::FREE ||
770  (status == VariableStatus::AT_UPPER_BOUND && reduced_cost > 0.0) ||
771  (status == VariableStatus::AT_LOWER_BOUND && reduced_cost < 0.0)) {
772  max_cost_correction =
773  std::max(max_cost_correction, std::abs(reduced_cost));
774  *is_too_large |=
775  std::abs(reduced_cost) >
776  AllowedError(tolerance, lp.objective_coefficients()[col]);
777  }
778  }
779  const bool log_info = parameters_.log_search_progress() || VLOG_IS_ON(1);
780  if (log_info) LOG(INFO) << "Max. cost perturbation = " << max_cost_correction;
781  return max_cost_correction;
782 }
783 
784 // This computes by how much the rhs must be perturbed to enforce the fact that
785 // the constraint activities exactly reflect their status.
786 Fractional LPSolver::ComputeMaxRhsPerturbationToEnforceOptimality(
787  const LinearProgram& lp, bool* is_too_large) {
788  Fractional max_rhs_correction = 0.0;
789  const RowIndex num_rows = lp.num_constraints();
790  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
791  for (RowIndex row(0); row < num_rows; ++row) {
792  const Fractional lower_bound = lp.constraint_lower_bounds()[row];
793  const Fractional upper_bound = lp.constraint_upper_bounds()[row];
794  const Fractional activity = constraint_activities_[row];
795  const ConstraintStatus status = constraint_statuses_[row];
796 
797  Fractional rhs_error = 0.0;
798  Fractional allowed_error = 0.0;
799  if (status == ConstraintStatus::AT_LOWER_BOUND || activity < lower_bound) {
800  rhs_error = std::abs(activity - lower_bound);
801  allowed_error = AllowedError(tolerance, lower_bound);
802  } else if (status == ConstraintStatus::AT_UPPER_BOUND ||
803  activity > upper_bound) {
804  rhs_error = std::abs(activity - upper_bound);
805  allowed_error = AllowedError(tolerance, upper_bound);
806  }
807  max_rhs_correction = std::max(max_rhs_correction, rhs_error);
808  *is_too_large |= rhs_error > allowed_error;
809  }
810  const bool log_info = parameters_.log_search_progress() || VLOG_IS_ON(1);
811  if (log_info) LOG(INFO) << "Max. rhs perturbation = " << max_rhs_correction;
812  return max_rhs_correction;
813 }
814 
815 void LPSolver::ComputeConstraintActivities(const LinearProgram& lp) {
816  const RowIndex num_rows = lp.num_constraints();
817  const ColIndex num_cols = lp.num_variables();
818  DCHECK_EQ(num_cols, primal_values_.size());
819  constraint_activities_.assign(num_rows, 0.0);
820  for (ColIndex col(0); col < num_cols; ++col) {
821  lp.GetSparseColumn(col).AddMultipleToDenseVector(primal_values_[col],
822  &constraint_activities_);
823  }
824 }
825 
826 void LPSolver::ComputeReducedCosts(const LinearProgram& lp) {
827  const RowIndex num_rows = lp.num_constraints();
828  const ColIndex num_cols = lp.num_variables();
829  DCHECK_EQ(num_rows, dual_values_.size());
830  reduced_costs_.resize(num_cols, 0.0);
831  for (ColIndex col(0); col < num_cols; ++col) {
832  reduced_costs_[col] = lp.objective_coefficients()[col] -
833  ScalarProduct(dual_values_, lp.GetSparseColumn(col));
834  }
835 }
836 
837 double LPSolver::ComputeObjective(const LinearProgram& lp) {
838  const ColIndex num_cols = lp.num_variables();
839  DCHECK_EQ(num_cols, primal_values_.size());
840  KahanSum sum;
841  for (ColIndex col(0); col < num_cols; ++col) {
842  sum.Add(lp.objective_coefficients()[col] * primal_values_[col]);
843  }
844  return sum.Value();
845 }
846 
847 // By the duality theorem, the dual "objective" is a bound on the primal
848 // objective obtained by taking the linear combinaison of the constraints
849 // given by dual_values_.
850 //
851 // As it is written now, this has no real precise meaning since we ignore
852 // infeasible reduced costs. This is almost the same as computing the objective
853 // to the perturbed problem, but then we don't use the pertubed rhs. It is just
854 // here as an extra "consistency" check.
855 //
856 // Note(user): We could actually compute an EXACT lower bound for the cost of
857 // the non-cost perturbed problem. The idea comes from "Safe bounds in linear
858 // and mixed-integer linear programming", Arnold Neumaier , Oleg Shcherbina,
859 // Math Prog, 2003. Note that this requires having some variable bounds that may
860 // not be in the original problem so that the current dual solution is always
861 // feasible. It also involves changing the rounding mode to obtain exact
862 // confidence intervals on the reduced costs.
863 double LPSolver::ComputeDualObjective(const LinearProgram& lp) {
864  KahanSum dual_objective;
865 
866  // Compute the part coming from the row constraints.
867  const RowIndex num_rows = lp.num_constraints();
868  const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
869  for (RowIndex row(0); row < num_rows; ++row) {
870  const Fractional lower_bound = lp.constraint_lower_bounds()[row];
871  const Fractional upper_bound = lp.constraint_upper_bounds()[row];
872 
873  // We correct the optimization_sign so we have to compute a lower bound.
874  const Fractional corrected_value = optimization_sign * dual_values_[row];
875  if (corrected_value > 0.0 && lower_bound != -kInfinity) {
876  dual_objective.Add(dual_values_[row] * lower_bound);
877  }
878  if (corrected_value < 0.0 && upper_bound != kInfinity) {
879  dual_objective.Add(dual_values_[row] * upper_bound);
880  }
881  }
882 
883  // For a given column associated to a variable x, we want to find a lower
884  // bound for c.x (where c is the objective coefficient for this column). If we
885  // write a.x the linear combination of the constraints at this column we have:
886  // (c + a - c) * x = a * x, and so
887  // c * x = a * x + (c - a) * x
888  // Now, if we suppose for example that the reduced cost 'c - a' is positive
889  // and that x is lower-bounded by 'lb' then the best bound we can get is
890  // c * x >= a * x + (c - a) * lb.
891  //
892  // Note: when summing over all variables, the left side is the primal
893  // objective and the right side is a lower bound to the objective. In
894  // particular, a necessary and sufficient condition for both objectives to be
895  // the same is that all the single variable inequalities above be equalities.
896  // This is possible only if c == a or if x is at its bound (modulo the
897  // optimization_sign of the reduced cost), or both (this is one side of the
898  // complementary slackness conditions, see Chvatal p. 62).
899  const ColIndex num_cols = lp.num_variables();
900  for (ColIndex col(0); col < num_cols; ++col) {
901  const Fractional lower_bound = lp.variable_lower_bounds()[col];
902  const Fractional upper_bound = lp.variable_upper_bounds()[col];
903 
904  // Correct the reduced cost, so as to have a minimization problem and
905  // thus a dual objective that is a lower bound of the primal objective.
906  const Fractional reduced_cost = optimization_sign * reduced_costs_[col];
907 
908  // We do not do any correction if the reduced cost is 'infeasible', which is
909  // the same as computing the objective of the perturbed problem.
910  Fractional correction = 0.0;
911  if (variable_statuses_[col] == VariableStatus::AT_LOWER_BOUND &&
912  reduced_cost > 0.0) {
913  correction = reduced_cost * lower_bound;
914  } else if (variable_statuses_[col] == VariableStatus::AT_UPPER_BOUND &&
915  reduced_cost < 0.0) {
916  correction = reduced_cost * upper_bound;
917  } else if (variable_statuses_[col] == VariableStatus::FIXED_VALUE) {
918  correction = reduced_cost * upper_bound;
919  }
920  // Now apply the correction in the right direction!
921  dual_objective.Add(optimization_sign * correction);
922  }
923  return dual_objective.Value();
924 }
925 
926 double LPSolver::ComputeMaxExpectedObjectiveError(const LinearProgram& lp) {
927  const ColIndex num_cols = lp.num_variables();
928  DCHECK_EQ(num_cols, primal_values_.size());
929  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
930  Fractional primal_objective_error = 0.0;
931  for (ColIndex col(0); col < num_cols; ++col) {
932  // TODO(user): Be more precise since the non-BASIC variables are exactly at
933  // their bounds, so for them the error bound is just the term magnitude
934  // times std::numeric_limits<double>::epsilon() with KahanSum.
935  primal_objective_error += std::abs(lp.objective_coefficients()[col]) *
936  AllowedError(tolerance, primal_values_[col]);
937  }
938  return primal_objective_error;
939 }
940 
941 double LPSolver::ComputePrimalValueInfeasibility(const LinearProgram& lp,
942  bool* is_too_large) {
943  double infeasibility = 0.0;
944  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
945  const ColIndex num_cols = lp.num_variables();
946  for (ColIndex col(0); col < num_cols; ++col) {
947  const Fractional lower_bound = lp.variable_lower_bounds()[col];
948  const Fractional upper_bound = lp.variable_upper_bounds()[col];
949  DCHECK(IsFinite(primal_values_[col]));
950 
951  if (lower_bound == upper_bound) {
952  const Fractional error = std::abs(primal_values_[col] - upper_bound);
953  infeasibility = std::max(infeasibility, error);
954  *is_too_large |= error > AllowedError(tolerance, upper_bound);
955  continue;
956  }
957  if (primal_values_[col] > upper_bound) {
958  const Fractional error = primal_values_[col] - upper_bound;
959  infeasibility = std::max(infeasibility, error);
960  *is_too_large |= error > AllowedError(tolerance, upper_bound);
961  }
962  if (primal_values_[col] < lower_bound) {
963  const Fractional error = lower_bound - primal_values_[col];
964  infeasibility = std::max(infeasibility, error);
965  *is_too_large |= error > AllowedError(tolerance, lower_bound);
966  }
967  }
968  return infeasibility;
969 }
970 
971 double LPSolver::ComputeActivityInfeasibility(const LinearProgram& lp,
972  bool* is_too_large) {
973  double infeasibility = 0.0;
974  int num_problematic_rows(0);
975  const RowIndex num_rows = lp.num_constraints();
976  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
977  for (RowIndex row(0); row < num_rows; ++row) {
978  const Fractional activity = constraint_activities_[row];
979  const Fractional lower_bound = lp.constraint_lower_bounds()[row];
980  const Fractional upper_bound = lp.constraint_upper_bounds()[row];
981  DCHECK(IsFinite(activity));
982 
983  if (lower_bound == upper_bound) {
984  if (std::abs(activity - upper_bound) >
985  AllowedError(tolerance, upper_bound)) {
986  VLOG(2) << "Row " << row.value() << " has activity " << activity
987  << " which is different from " << upper_bound << " by "
988  << activity - upper_bound;
989  ++num_problematic_rows;
990  }
991  infeasibility = std::max(infeasibility, std::abs(activity - upper_bound));
992  continue;
993  }
994  if (activity > upper_bound) {
995  const Fractional row_excess = activity - upper_bound;
996  if (row_excess > AllowedError(tolerance, upper_bound)) {
997  VLOG(2) << "Row " << row.value() << " has activity " << activity
998  << ", exceeding its upper bound " << upper_bound << " by "
999  << row_excess;
1000  ++num_problematic_rows;
1001  }
1002  infeasibility = std::max(infeasibility, row_excess);
1003  }
1004  if (activity < lower_bound) {
1005  const Fractional row_deficit = lower_bound - activity;
1006  if (row_deficit > AllowedError(tolerance, lower_bound)) {
1007  VLOG(2) << "Row " << row.value() << " has activity " << activity
1008  << ", below its lower bound " << lower_bound << " by "
1009  << row_deficit;
1010  ++num_problematic_rows;
1011  }
1012  infeasibility = std::max(infeasibility, row_deficit);
1013  }
1014  }
1015  if (num_problematic_rows > 0) {
1016  *is_too_large = true;
1017  VLOG(1) << "Number of infeasible rows = " << num_problematic_rows;
1018  }
1019  return infeasibility;
1020 }
1021 
1022 double LPSolver::ComputeDualValueInfeasibility(const LinearProgram& lp,
1023  bool* is_too_large) {
1024  const Fractional allowed_error = parameters_.solution_feasibility_tolerance();
1025  const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
1026  double infeasibility = 0.0;
1027  const RowIndex num_rows = lp.num_constraints();
1028  for (RowIndex row(0); row < num_rows; ++row) {
1029  const Fractional dual_value = dual_values_[row];
1030  const Fractional lower_bound = lp.constraint_lower_bounds()[row];
1031  const Fractional upper_bound = lp.constraint_upper_bounds()[row];
1032  DCHECK(IsFinite(dual_value));
1033  const Fractional minimization_dual_value = optimization_sign * dual_value;
1034  if (lower_bound == -kInfinity) {
1035  *is_too_large |= minimization_dual_value > allowed_error;
1036  infeasibility = std::max(infeasibility, minimization_dual_value);
1037  }
1038  if (upper_bound == kInfinity) {
1039  *is_too_large |= -minimization_dual_value > allowed_error;
1040  infeasibility = std::max(infeasibility, -minimization_dual_value);
1041  }
1042  }
1043  return infeasibility;
1044 }
1045 
1046 double LPSolver::ComputeReducedCostInfeasibility(const LinearProgram& lp,
1047  bool* is_too_large) {
1048  const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
1049  double infeasibility = 0.0;
1050  const ColIndex num_cols = lp.num_variables();
1051  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
1052  for (ColIndex col(0); col < num_cols; ++col) {
1053  const Fractional reduced_cost = reduced_costs_[col];
1054  const Fractional lower_bound = lp.variable_lower_bounds()[col];
1055  const Fractional upper_bound = lp.variable_upper_bounds()[col];
1056  DCHECK(IsFinite(reduced_cost));
1057  const Fractional minimization_reduced_cost =
1058  optimization_sign * reduced_cost;
1059  const Fractional allowed_error =
1060  AllowedError(tolerance, lp.objective_coefficients()[col]);
1061  if (lower_bound == -kInfinity) {
1062  *is_too_large |= minimization_reduced_cost > allowed_error;
1063  infeasibility = std::max(infeasibility, minimization_reduced_cost);
1064  }
1065  if (upper_bound == kInfinity) {
1066  *is_too_large |= -minimization_reduced_cost > allowed_error;
1067  infeasibility = std::max(infeasibility, -minimization_reduced_cost);
1068  }
1069  }
1070  return infeasibility;
1071 }
1072 
1073 } // namespace glop
1074 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:491
void MergeFrom(const GlopParameters &from)
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
std::string GetObjectiveStatsString() const
Definition: lp_data.cc:452
void LinearProgramToMPModelProto(const LinearProgram &input, MPModelProto *output)
Definition: proto_utils.cc:20
void Add(const FpNumber &value)
Definition: accurate_sum.h:29
Fractional GetMaximumPrimalInfeasibility() const
Definition: lp_solver.cc:491
int64_t min
Definition: alldiff_cst.cc:139
const GlopParameters & GetParameters() const
Definition: lp_solver.cc:128
void SetInitialBasis(const VariableStatusRow &variable_statuses, const ConstraintStatusColumn &constraint_statuses)
Definition: lp_solver.cc:241
ModelSharedTimeLimit * time_limit
#define VLOG(verboselevel)
Definition: base/logging.h:979
const int ERROR
Definition: log_severity.h:32
Fractional GetObjectiveValue() const
Definition: lp_solver.cc:487
#define DLOG(severity)
Definition: base/logging.h:876
#define LOG(severity)
Definition: base/logging.h:416
ColIndex col
Definition: markowitz.cc:183
ABSL_MUST_USE_RESULT ProblemStatus SolveWithTimeLimit(const LinearProgram &lp, TimeLimit *time_limit)
Definition: lp_solver.cc:138
RowIndex row
Definition: markowitz.cc:182
void assign(IntType size, const T &v)
Definition: lp_types.h:278
bool IsFinite(Fractional value)
Definition: lp_types.h:91
void SetParameters(const GlopParameters &parameters)
Definition: lp_solver.cc:116
const DenseColumn & constraint_upper_bounds() const
Definition: lp_data.h:218
ProblemStatus LoadAndVerifySolution(const LinearProgram &lp, const ProblemSolution &solution)
Definition: lp_solver.cc:295
int64_t max
Definition: alldiff_cst.cc:140
static std::unique_ptr< TimeLimit > FromParameters(const Parameters &parameters)
Creates a time limit object initialized from an object that provides methods max_time_in_seconds() an...
Definition: time_limit.h:159
Fractional objective_scaling_factor() const
Definition: lp_data.h:261
double upper_bound
std::string GetDimensionString() const
Definition: lp_data.cc:425
const DenseColumn & constraint_lower_bounds() const
Definition: lp_data.h:215
CpModelProto proto
const int WARNING
Definition: log_severity.h:31
void SetTimeLimit(TimeLimit *time_limit)
Definition: preprocessor.h:75
std::string GetConstraintStatusString(ConstraintStatus status)
Definition: lp_types.cc:90
GlopParameters * GetMutableParameters()
Definition: lp_solver.cc:130
double lower_bound
AccurateSum< Fractional > KahanSum
void DestructiveRecoverSolution(ProblemSolution *solution)
bool AreWithinAbsoluteTolerance(FloatType x, FloatType y, FloatType absolute_tolerance)
Definition: fp_utils.h:145
const double kInfinity
Definition: lp_types.h:84
void push_back(const value_type &x)
bool WriteProtoToFile(absl::string_view filename, const google::protobuf::Message &proto, ProtoWriteFormat proto_write_format, bool gzipped, bool append_extension_to_file_name)
Definition: file_util.cc:106
bool MayHaveMultipleOptimalSolutions() const
Definition: lp_solver.cc:499
ConstraintStatusColumn constraint_statuses
Definition: lp_data.h:686
#define DCHECK(condition)
Definition: base/logging.h:885
const DenseRow & variable_upper_bounds() const
Definition: lp_data.h:232
const DenseRow & variable_lower_bounds() const
Definition: lp_data.h:229
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:886
const VariableStatusRow & variable_statuses() const
Definition: lp_solver.h:102
std::string GetVariableStatusString(VariableStatus status)
Definition: lp_types.cc:71
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:888
const ConstraintStatusColumn & constraint_statuses() const
Definition: lp_solver.h:116
Collection of objects used to extend the Constraint Solver library.
ABSL_FLAG(bool, lp_solver_enable_fp_exceptions, false, "When true, NaNs and division / zero produce errors. " "This is very useful for debugging, but incompatible with LLVM. " "It is recommended to set this to false for production usage.")
SatParameters parameters
Fractional ScalarProduct(const DenseRowOrColumn1 &u, const DenseRowOrColumn2 &v)
std::string GetBoundsStatsString() const
Definition: lp_data.cc:465
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41
ABSL_MUST_USE_RESULT ProblemStatus Solve(const LinearProgram &lp)
Definition: lp_solver.cc:132
int64_t value
void PopulateFromLinearProgram(const LinearProgram &linear_program)
Definition: lp_data.cc:862
Fractional GetMaximumDualInfeasibility() const
Definition: lp_solver.cc:495
const int INFO
Definition: log_severity.h:31