OR-Tools  9.0
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 
116 void LPSolver::SetParameters(const GlopParameters& parameters) {
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 
130 GlopParameters* LPSolver::GetMutableParameters() { return &parameters_; }
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.RecoverSolution(&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  const ColIndex num_cols = revised_simplex_->GetProblemNumCols();
585  DCHECK_EQ(solution->primal_values.size(), num_cols);
586  for (ColIndex col(0); col < num_cols; ++col) {
587  solution->primal_values[col] = revised_simplex_->GetVariableValue(col);
588  solution->variable_statuses[col] =
589  revised_simplex_->GetVariableStatus(col);
590  }
591 
592  const RowIndex num_rows = revised_simplex_->GetProblemNumRows();
593  DCHECK_EQ(solution->dual_values.size(), num_rows);
594  for (RowIndex row(0); row < num_rows; ++row) {
595  solution->dual_values[row] = revised_simplex_->GetDualValue(row);
596  solution->constraint_statuses[row] =
597  revised_simplex_->GetConstraintStatus(row);
598  }
599  } else {
600  const bool log_info = parameters_.log_search_progress() || VLOG_IS_ON(1);
601  if (log_info) LOG(INFO) << "Error during the revised simplex algorithm.";
602  solution->status = ProblemStatus::ABNORMAL;
603  }
604 }
605 
606 namespace {
607 
608 void LogVariableStatusError(ColIndex col, Fractional value,
609  VariableStatus status, Fractional lb,
610  Fractional ub) {
611  VLOG(1) << "Variable " << col << " status is "
612  << GetVariableStatusString(status) << " but its value is " << value
613  << " and its bounds are [" << lb << ", " << ub << "].";
614 }
615 
616 void LogConstraintStatusError(RowIndex row, ConstraintStatus status,
617  Fractional lb, Fractional ub) {
618  VLOG(1) << "Constraint " << row << " status is "
619  << GetConstraintStatusString(status) << " but its bounds are [" << lb
620  << ", " << ub << "].";
621 }
622 
623 } // namespace
624 
625 bool LPSolver::IsProblemSolutionConsistent(
626  const LinearProgram& lp, const ProblemSolution& solution) const {
627  const RowIndex num_rows = lp.num_constraints();
628  const ColIndex num_cols = lp.num_variables();
629  if (solution.variable_statuses.size() != num_cols) return false;
630  if (solution.constraint_statuses.size() != num_rows) return false;
631  if (solution.primal_values.size() != num_cols) return false;
632  if (solution.dual_values.size() != num_rows) return false;
633  if (solution.status != ProblemStatus::OPTIMAL &&
634  solution.status != ProblemStatus::PRIMAL_FEASIBLE &&
635  solution.status != ProblemStatus::DUAL_FEASIBLE) {
636  return true;
637  }
638 
639  // This checks that the variable statuses verify the properties described
640  // in the VariableStatus declaration.
641  RowIndex num_basic_variables(0);
642  for (ColIndex col(0); col < num_cols; ++col) {
643  const Fractional value = solution.primal_values[col];
644  const Fractional lb = lp.variable_lower_bounds()[col];
645  const Fractional ub = lp.variable_upper_bounds()[col];
646  const VariableStatus status = solution.variable_statuses[col];
647  switch (solution.variable_statuses[col]) {
649  // TODO(user): Check that the reduced cost of this column is epsilon
650  // close to zero.
651  ++num_basic_variables;
652  break;
654  // TODO(user): Because of scaling, it is possible that a FIXED_VALUE
655  // status (only reserved for the exact lb == ub case) is now set for a
656  // variable where (ub == lb + epsilon). So we do not check here that the
657  // two bounds are exactly equal. The best is probably to remove the
658  // FIXED status from the API completely and report one of AT_LOWER_BOUND
659  // or AT_UPPER_BOUND instead. This also allows to indicate if at
660  // optimality, the objective is limited because of this variable lower
661  // bound or its upper bound. Note that there are other TODOs in the
662  // codebase about removing this FIXED_VALUE status.
663  if (value != ub && value != lb) {
664  LogVariableStatusError(col, value, status, lb, ub);
665  return false;
666  }
667  break;
669  if (value != lb || lb == ub) {
670  LogVariableStatusError(col, value, status, lb, ub);
671  return false;
672  }
673  break;
675  // TODO(user): revert to an exact comparison once the bug causing this
676  // to fail has been fixed.
677  if (!AreWithinAbsoluteTolerance(value, ub, 1e-7) || lb == ub) {
678  LogVariableStatusError(col, value, status, lb, ub);
679  return false;
680  }
681  break;
683  if (lb != -kInfinity || ub != kInfinity || value != 0.0) {
684  LogVariableStatusError(col, value, status, lb, ub);
685  return false;
686  }
687  break;
688  }
689  }
690  for (RowIndex row(0); row < num_rows; ++row) {
691  const Fractional dual_value = solution.dual_values[row];
692  const Fractional lb = lp.constraint_lower_bounds()[row];
693  const Fractional ub = lp.constraint_upper_bounds()[row];
694  const ConstraintStatus status = solution.constraint_statuses[row];
695 
696  // The activity value is not checked since it is imprecise.
697  // TODO(user): Check that the activity is epsilon close to the expected
698  // value.
699  switch (status) {
701  if (dual_value != 0.0) {
702  VLOG(1) << "Constraint " << row << " is BASIC, but its dual value is "
703  << dual_value << " instead of 0.";
704  return false;
705  }
706  ++num_basic_variables;
707  break;
709  // Exactly the same remark as for the VariableStatus::FIXED_VALUE case
710  // above. Because of precision error, this can happen when the
711  // difference between the two bounds is small and not just exactly zero.
712  if (ub - lb > 1e-12) {
713  LogConstraintStatusError(row, status, lb, ub);
714  return false;
715  }
716  break;
718  if (lb == -kInfinity) {
719  LogConstraintStatusError(row, status, lb, ub);
720  return false;
721  }
722  break;
724  if (ub == kInfinity) {
725  LogConstraintStatusError(row, status, lb, ub);
726  return false;
727  }
728  break;
730  if (dual_value != 0.0) {
731  VLOG(1) << "Constraint " << row << " is FREE, but its dual value is "
732  << dual_value << " instead of 0.";
733  return false;
734  }
735  if (lb != -kInfinity || ub != kInfinity) {
736  LogConstraintStatusError(row, status, lb, ub);
737  return false;
738  }
739  break;
740  }
741  }
742 
743  // TODO(user): We could check in debug mode (because it will be costly) that
744  // the basis is actually factorizable.
745  if (num_basic_variables != num_rows) {
746  VLOG(1) << "Wrong number of basic variables: " << num_basic_variables;
747  return false;
748  }
749  return true;
750 }
751 
752 // This computes by how much the objective must be perturbed to enforce the
753 // following complementary slackness conditions:
754 // - Reduced cost is exactly zero for FREE and BASIC variables.
755 // - Reduced cost is of the correct sign for variables at their bounds.
756 Fractional LPSolver::ComputeMaxCostPerturbationToEnforceOptimality(
757  const LinearProgram& lp, bool* is_too_large) {
758  Fractional max_cost_correction = 0.0;
759  const ColIndex num_cols = lp.num_variables();
760  const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
761  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
762  for (ColIndex col(0); col < num_cols; ++col) {
763  // We correct the reduced cost, so we have a minimization problem and
764  // thus the dual objective value will be a lower bound of the primal
765  // objective.
766  const Fractional reduced_cost = optimization_sign * reduced_costs_[col];
767  const VariableStatus status = variable_statuses_[col];
768  if (status == VariableStatus::BASIC || status == VariableStatus::FREE ||
769  (status == VariableStatus::AT_UPPER_BOUND && reduced_cost > 0.0) ||
770  (status == VariableStatus::AT_LOWER_BOUND && reduced_cost < 0.0)) {
771  max_cost_correction =
772  std::max(max_cost_correction, std::abs(reduced_cost));
773  *is_too_large |=
774  std::abs(reduced_cost) >
775  AllowedError(tolerance, lp.objective_coefficients()[col]);
776  }
777  }
778  const bool log_info = parameters_.log_search_progress() || VLOG_IS_ON(1);
779  if (log_info) LOG(INFO) << "Max. cost perturbation = " << max_cost_correction;
780  return max_cost_correction;
781 }
782 
783 // This computes by how much the rhs must be perturbed to enforce the fact that
784 // the constraint activities exactly reflect their status.
785 Fractional LPSolver::ComputeMaxRhsPerturbationToEnforceOptimality(
786  const LinearProgram& lp, bool* is_too_large) {
787  Fractional max_rhs_correction = 0.0;
788  const RowIndex num_rows = lp.num_constraints();
789  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
790  for (RowIndex row(0); row < num_rows; ++row) {
791  const Fractional lower_bound = lp.constraint_lower_bounds()[row];
792  const Fractional upper_bound = lp.constraint_upper_bounds()[row];
793  const Fractional activity = constraint_activities_[row];
794  const ConstraintStatus status = constraint_statuses_[row];
795 
796  Fractional rhs_error = 0.0;
797  Fractional allowed_error = 0.0;
798  if (status == ConstraintStatus::AT_LOWER_BOUND || activity < lower_bound) {
799  rhs_error = std::abs(activity - lower_bound);
800  allowed_error = AllowedError(tolerance, lower_bound);
801  } else if (status == ConstraintStatus::AT_UPPER_BOUND ||
802  activity > upper_bound) {
803  rhs_error = std::abs(activity - upper_bound);
804  allowed_error = AllowedError(tolerance, upper_bound);
805  }
806  max_rhs_correction = std::max(max_rhs_correction, rhs_error);
807  *is_too_large |= rhs_error > allowed_error;
808  }
809  const bool log_info = parameters_.log_search_progress() || VLOG_IS_ON(1);
810  if (log_info) LOG(INFO) << "Max. rhs perturbation = " << max_rhs_correction;
811  return max_rhs_correction;
812 }
813 
814 void LPSolver::ComputeConstraintActivities(const LinearProgram& lp) {
815  const RowIndex num_rows = lp.num_constraints();
816  const ColIndex num_cols = lp.num_variables();
817  DCHECK_EQ(num_cols, primal_values_.size());
818  constraint_activities_.assign(num_rows, 0.0);
819  for (ColIndex col(0); col < num_cols; ++col) {
820  lp.GetSparseColumn(col).AddMultipleToDenseVector(primal_values_[col],
821  &constraint_activities_);
822  }
823 }
824 
825 void LPSolver::ComputeReducedCosts(const LinearProgram& lp) {
826  const RowIndex num_rows = lp.num_constraints();
827  const ColIndex num_cols = lp.num_variables();
828  DCHECK_EQ(num_rows, dual_values_.size());
829  reduced_costs_.resize(num_cols, 0.0);
830  for (ColIndex col(0); col < num_cols; ++col) {
831  reduced_costs_[col] = lp.objective_coefficients()[col] -
832  ScalarProduct(dual_values_, lp.GetSparseColumn(col));
833  }
834 }
835 
836 double LPSolver::ComputeObjective(const LinearProgram& lp) {
837  const ColIndex num_cols = lp.num_variables();
838  DCHECK_EQ(num_cols, primal_values_.size());
839  KahanSum sum;
840  for (ColIndex col(0); col < num_cols; ++col) {
841  sum.Add(lp.objective_coefficients()[col] * primal_values_[col]);
842  }
843  return sum.Value();
844 }
845 
846 // By the duality theorem, the dual "objective" is a bound on the primal
847 // objective obtained by taking the linear combinaison of the constraints
848 // given by dual_values_.
849 //
850 // As it is written now, this has no real precise meaning since we ignore
851 // infeasible reduced costs. This is almost the same as computing the objective
852 // to the perturbed problem, but then we don't use the pertubed rhs. It is just
853 // here as an extra "consistency" check.
854 //
855 // Note(user): We could actually compute an EXACT lower bound for the cost of
856 // the non-cost perturbed problem. The idea comes from "Safe bounds in linear
857 // and mixed-integer linear programming", Arnold Neumaier , Oleg Shcherbina,
858 // Math Prog, 2003. Note that this requires having some variable bounds that may
859 // not be in the original problem so that the current dual solution is always
860 // feasible. It also involves changing the rounding mode to obtain exact
861 // confidence intervals on the reduced costs.
862 double LPSolver::ComputeDualObjective(const LinearProgram& lp) {
863  KahanSum dual_objective;
864 
865  // Compute the part coming from the row constraints.
866  const RowIndex num_rows = lp.num_constraints();
867  const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
868  for (RowIndex row(0); row < num_rows; ++row) {
869  const Fractional lower_bound = lp.constraint_lower_bounds()[row];
870  const Fractional upper_bound = lp.constraint_upper_bounds()[row];
871 
872  // We correct the optimization_sign so we have to compute a lower bound.
873  const Fractional corrected_value = optimization_sign * dual_values_[row];
874  if (corrected_value > 0.0 && lower_bound != -kInfinity) {
875  dual_objective.Add(dual_values_[row] * lower_bound);
876  }
877  if (corrected_value < 0.0 && upper_bound != kInfinity) {
878  dual_objective.Add(dual_values_[row] * upper_bound);
879  }
880  }
881 
882  // For a given column associated to a variable x, we want to find a lower
883  // bound for c.x (where c is the objective coefficient for this column). If we
884  // write a.x the linear combination of the constraints at this column we have:
885  // (c + a - c) * x = a * x, and so
886  // c * x = a * x + (c - a) * x
887  // Now, if we suppose for example that the reduced cost 'c - a' is positive
888  // and that x is lower-bounded by 'lb' then the best bound we can get is
889  // c * x >= a * x + (c - a) * lb.
890  //
891  // Note: when summing over all variables, the left side is the primal
892  // objective and the right side is a lower bound to the objective. In
893  // particular, a necessary and sufficient condition for both objectives to be
894  // the same is that all the single variable inequalities above be equalities.
895  // This is possible only if c == a or if x is at its bound (modulo the
896  // optimization_sign of the reduced cost), or both (this is one side of the
897  // complementary slackness conditions, see Chvatal p. 62).
898  const ColIndex num_cols = lp.num_variables();
899  for (ColIndex col(0); col < num_cols; ++col) {
900  const Fractional lower_bound = lp.variable_lower_bounds()[col];
901  const Fractional upper_bound = lp.variable_upper_bounds()[col];
902 
903  // Correct the reduced cost, so as to have a minimization problem and
904  // thus a dual objective that is a lower bound of the primal objective.
905  const Fractional reduced_cost = optimization_sign * reduced_costs_[col];
906 
907  // We do not do any correction if the reduced cost is 'infeasible', which is
908  // the same as computing the objective of the perturbed problem.
909  Fractional correction = 0.0;
910  if (variable_statuses_[col] == VariableStatus::AT_LOWER_BOUND &&
911  reduced_cost > 0.0) {
912  correction = reduced_cost * lower_bound;
913  } else if (variable_statuses_[col] == VariableStatus::AT_UPPER_BOUND &&
914  reduced_cost < 0.0) {
915  correction = reduced_cost * upper_bound;
916  } else if (variable_statuses_[col] == VariableStatus::FIXED_VALUE) {
917  correction = reduced_cost * upper_bound;
918  }
919  // Now apply the correction in the right direction!
920  dual_objective.Add(optimization_sign * correction);
921  }
922  return dual_objective.Value();
923 }
924 
925 double LPSolver::ComputeMaxExpectedObjectiveError(const LinearProgram& lp) {
926  const ColIndex num_cols = lp.num_variables();
927  DCHECK_EQ(num_cols, primal_values_.size());
928  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
929  Fractional primal_objective_error = 0.0;
930  for (ColIndex col(0); col < num_cols; ++col) {
931  // TODO(user): Be more precise since the non-BASIC variables are exactly at
932  // their bounds, so for them the error bound is just the term magnitude
933  // times std::numeric_limits<double>::epsilon() with KahanSum.
934  primal_objective_error += std::abs(lp.objective_coefficients()[col]) *
935  AllowedError(tolerance, primal_values_[col]);
936  }
937  return primal_objective_error;
938 }
939 
940 double LPSolver::ComputePrimalValueInfeasibility(const LinearProgram& lp,
941  bool* is_too_large) {
942  double infeasibility = 0.0;
943  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
944  const ColIndex num_cols = lp.num_variables();
945  for (ColIndex col(0); col < num_cols; ++col) {
946  const Fractional lower_bound = lp.variable_lower_bounds()[col];
947  const Fractional upper_bound = lp.variable_upper_bounds()[col];
948  DCHECK(IsFinite(primal_values_[col]));
949 
950  if (lower_bound == upper_bound) {
951  const Fractional error = std::abs(primal_values_[col] - upper_bound);
952  infeasibility = std::max(infeasibility, error);
953  *is_too_large |= error > AllowedError(tolerance, upper_bound);
954  continue;
955  }
956  if (primal_values_[col] > upper_bound) {
957  const Fractional error = primal_values_[col] - upper_bound;
958  infeasibility = std::max(infeasibility, error);
959  *is_too_large |= error > AllowedError(tolerance, upper_bound);
960  }
961  if (primal_values_[col] < lower_bound) {
962  const Fractional error = lower_bound - primal_values_[col];
963  infeasibility = std::max(infeasibility, error);
964  *is_too_large |= error > AllowedError(tolerance, lower_bound);
965  }
966  }
967  return infeasibility;
968 }
969 
970 double LPSolver::ComputeActivityInfeasibility(const LinearProgram& lp,
971  bool* is_too_large) {
972  double infeasibility = 0.0;
973  int num_problematic_rows(0);
974  const RowIndex num_rows = lp.num_constraints();
975  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
976  for (RowIndex row(0); row < num_rows; ++row) {
977  const Fractional activity = constraint_activities_[row];
978  const Fractional lower_bound = lp.constraint_lower_bounds()[row];
979  const Fractional upper_bound = lp.constraint_upper_bounds()[row];
980  DCHECK(IsFinite(activity));
981 
982  if (lower_bound == upper_bound) {
983  if (std::abs(activity - upper_bound) >
984  AllowedError(tolerance, upper_bound)) {
985  VLOG(2) << "Row " << row.value() << " has activity " << activity
986  << " which is different from " << upper_bound << " by "
987  << activity - upper_bound;
988  ++num_problematic_rows;
989  }
990  infeasibility = std::max(infeasibility, std::abs(activity - upper_bound));
991  continue;
992  }
993  if (activity > upper_bound) {
994  const Fractional row_excess = activity - upper_bound;
995  if (row_excess > AllowedError(tolerance, upper_bound)) {
996  VLOG(2) << "Row " << row.value() << " has activity " << activity
997  << ", exceeding its upper bound " << upper_bound << " by "
998  << row_excess;
999  ++num_problematic_rows;
1000  }
1001  infeasibility = std::max(infeasibility, row_excess);
1002  }
1003  if (activity < lower_bound) {
1004  const Fractional row_deficit = lower_bound - activity;
1005  if (row_deficit > AllowedError(tolerance, lower_bound)) {
1006  VLOG(2) << "Row " << row.value() << " has activity " << activity
1007  << ", below its lower bound " << lower_bound << " by "
1008  << row_deficit;
1009  ++num_problematic_rows;
1010  }
1011  infeasibility = std::max(infeasibility, row_deficit);
1012  }
1013  }
1014  if (num_problematic_rows > 0) {
1015  *is_too_large = true;
1016  VLOG(1) << "Number of infeasible rows = " << num_problematic_rows;
1017  }
1018  return infeasibility;
1019 }
1020 
1021 double LPSolver::ComputeDualValueInfeasibility(const LinearProgram& lp,
1022  bool* is_too_large) {
1023  const Fractional allowed_error = parameters_.solution_feasibility_tolerance();
1024  const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
1025  double infeasibility = 0.0;
1026  const RowIndex num_rows = lp.num_constraints();
1027  for (RowIndex row(0); row < num_rows; ++row) {
1028  const Fractional dual_value = dual_values_[row];
1029  const Fractional lower_bound = lp.constraint_lower_bounds()[row];
1030  const Fractional upper_bound = lp.constraint_upper_bounds()[row];
1031  DCHECK(IsFinite(dual_value));
1032  const Fractional minimization_dual_value = optimization_sign * dual_value;
1033  if (lower_bound == -kInfinity) {
1034  *is_too_large |= minimization_dual_value > allowed_error;
1035  infeasibility = std::max(infeasibility, minimization_dual_value);
1036  }
1037  if (upper_bound == kInfinity) {
1038  *is_too_large |= -minimization_dual_value > allowed_error;
1039  infeasibility = std::max(infeasibility, -minimization_dual_value);
1040  }
1041  }
1042  return infeasibility;
1043 }
1044 
1045 double LPSolver::ComputeReducedCostInfeasibility(const LinearProgram& lp,
1046  bool* is_too_large) {
1047  const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
1048  double infeasibility = 0.0;
1049  const ColIndex num_cols = lp.num_variables();
1050  const Fractional tolerance = parameters_.solution_feasibility_tolerance();
1051  for (ColIndex col(0); col < num_cols; ++col) {
1052  const Fractional reduced_cost = reduced_costs_[col];
1053  const Fractional lower_bound = lp.variable_lower_bounds()[col];
1054  const Fractional upper_bound = lp.variable_upper_bounds()[col];
1055  DCHECK(IsFinite(reduced_cost));
1056  const Fractional minimization_reduced_cost =
1057  optimization_sign * reduced_cost;
1058  const Fractional allowed_error =
1059  AllowedError(tolerance, lp.objective_coefficients()[col]);
1060  if (lower_bound == -kInfinity) {
1061  *is_too_large |= minimization_reduced_cost > allowed_error;
1062  infeasibility = std::max(infeasibility, minimization_reduced_cost);
1063  }
1064  if (upper_bound == kInfinity) {
1065  *is_too_large |= -minimization_reduced_cost > allowed_error;
1066  infeasibility = std::max(infeasibility, -minimization_reduced_cost);
1067  }
1068  }
1069  return infeasibility;
1070 }
1071 
1072 } // namespace glop
1073 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define DLOG(severity)
Definition: base/logging.h:883
#define CHECK(condition)
Definition: base/logging.h:498
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:895
#define LOG(severity)
Definition: base/logging.h:423
#define DCHECK(condition)
Definition: base/logging.h:892
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:893
#define VLOG(verboselevel)
Definition: base/logging.h:986
void push_back(const value_type &x)
void Add(const FpNumber &value)
Definition: accurate_sum.h:29
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
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
const GlopParameters & GetParameters() const
Definition: lp_solver.cc:128
void SetInitialBasis(const VariableStatusRow &variable_statuses, const ConstraintStatusColumn &constraint_statuses)
Definition: lp_solver.cc:241
const ConstraintStatusColumn & constraint_statuses() const
Definition: lp_solver.h:116
bool MayHaveMultipleOptimalSolutions() const
Definition: lp_solver.cc:499
const VariableStatusRow & variable_statuses() const
Definition: lp_solver.h:102
GlopParameters * GetMutableParameters()
Definition: lp_solver.cc:130
Fractional GetMaximumDualInfeasibility() const
Definition: lp_solver.cc:495
Fractional GetMaximumPrimalInfeasibility() const
Definition: lp_solver.cc:491
Fractional GetObjectiveValue() const
Definition: lp_solver.cc:487
ProblemStatus LoadAndVerifySolution(const LinearProgram &lp, const ProblemSolution &solution)
Definition: lp_solver.cc:295
ABSL_MUST_USE_RESULT ProblemStatus Solve(const LinearProgram &lp)
Definition: lp_solver.cc:132
ABSL_MUST_USE_RESULT ProblemStatus SolveWithTimeLimit(const LinearProgram &lp, TimeLimit *time_limit)
Definition: lp_solver.cc:138
void SetParameters(const GlopParameters &parameters)
Definition: lp_solver.cc:116
std::string GetObjectiveStatsString() const
Definition: lp_data.cc:452
void PopulateFromLinearProgram(const LinearProgram &linear_program)
Definition: lp_data.cc:862
const DenseRow & variable_lower_bounds() const
Definition: lp_data.h:229
const DenseColumn & constraint_lower_bounds() const
Definition: lp_data.h:215
std::string GetBoundsStatsString() const
Definition: lp_data.cc:465
const DenseColumn & constraint_upper_bounds() const
Definition: lp_data.h:218
const DenseRow & variable_upper_bounds() const
Definition: lp_data.h:232
std::string GetDimensionString() const
Definition: lp_data.cc:425
Fractional objective_scaling_factor() const
Definition: lp_data.h:261
void RecoverSolution(ProblemSolution *solution) const override
void SetTimeLimit(TimeLimit *time_limit)
Definition: preprocessor.h:75
void assign(IntType size, const T &v)
Definition: lp_types.h:275
SatParameters parameters
CpModelProto proto
SharedTimeLimit * time_limit
int64_t value
double upper_bound
double lower_bound
const int WARNING
Definition: log_severity.h:31
const int INFO
Definition: log_severity.h:31
const int ERROR
Definition: log_severity.h:32
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.")
ColIndex col
Definition: markowitz.cc:183
RowIndex row
Definition: markowitz.cc:182
AccurateSum< Fractional > KahanSum
Fractional ScalarProduct(const DenseRowOrColumn1 &u, const DenseRowOrColumn2 &v)
std::string GetConstraintStatusString(ConstraintStatus status)
Definition: lp_types.cc:90
void LinearProgramToMPModelProto(const LinearProgram &input, MPModelProto *output)
Definition: proto_utils.cc:20
bool IsFinite(Fractional value)
Definition: lp_types.h:91
std::string GetVariableStatusString(VariableStatus status)
Definition: lp_types.cc:71
const double kInfinity
Definition: lp_types.h:84
Collection of objects used to extend the Constraint Solver library.
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:102
bool AreWithinAbsoluteTolerance(FloatType x, FloatType y, FloatType absolute_tolerance)
Definition: fp_utils.h:141
ConstraintStatusColumn constraint_statuses
Definition: lp_data.h:686
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41