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
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"
32
33// TODO(user): abstract this in some way to the port directory.
34#ifndef __PORTABLE_PLATFORM__
36#endif
37
38ABSL_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.");
42ABSL_FLAG(bool, lp_dump_to_proto_file, false,
43 "Tells whether do dump the problem to a protobuf file.");
44ABSL_FLAG(bool, lp_dump_compressed_file, true,
45 "Whether the proto dump file is compressed.");
46ABSL_FLAG(bool, lp_dump_binary_file, false,
47 "Whether the proto dump file is binary.");
48ABSL_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().");
52ABSL_FLAG(std::string, lp_dump_dir, "/tmp",
53 "Directory where dump files are written.");
54ABSL_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.");
58ABSL_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
62namespace operations_research {
63namespace glop {
64namespace {
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).
73void 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
114LPSolver::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
128const 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.
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;
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
279namespace {
280// Computes the "real" problem objective from the one without offset nor
281// scaling.
282Fractional 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.
288Fractional 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 !!";
409 }
410 if (cost_perturbation_is_too_large) {
411 if (log_info) LOG(INFO) << "The needed cost perturbation is too large !!";
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 }
426 }
427 }
428 if ((status == ProblemStatus::OPTIMAL ||
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 }
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 }
444 }
445
446 may_have_multiple_solutions_ =
447 (status == ProblemStatus::OPTIMAL) ? IsOptimalSolutionOnFacet(lp) : false;
448 return status;
449}
450
451bool 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
512void 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
530void 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
555void 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
565void 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
607namespace {
608
609void 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
617void 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
626bool 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.
757Fractional 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.
786Fractional 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
815void 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
826void 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
837double 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.
863double 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
926double 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
941double 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
971double 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
1022double 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
1046double 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
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define DLOG(severity)
Definition: base/logging.h:876
#define CHECK(condition)
Definition: base/logging.h:491
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:888
#define LOG(severity)
Definition: base/logging.h:416
#define DCHECK(condition)
Definition: base/logging.h:885
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:886
#define VLOG(verboselevel)
Definition: base/logging.h:979
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
void MergeFrom(const GlopParameters &from)
const GlopParameters & GetParameters() const
Definition: lp_solver.cc:128
void SetInitialBasis(const VariableStatusRow &variable_statuses, const ConstraintStatusColumn &constraint_statuses)
Definition: lp_solver.cc:241
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
const ConstraintStatusColumn & constraint_statuses() const
Definition: lp_solver.h:116
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
std::string GetBoundsStatsString() const
Definition: lp_data.cc:465
const DenseColumn & constraint_lower_bounds() const
Definition: lp_data.h:215
const DenseRow & variable_upper_bounds() const
Definition: lp_data.h:232
const DenseColumn & constraint_upper_bounds() const
Definition: lp_data.h:218
const DenseRow & variable_lower_bounds() const
Definition: lp_data.h:229
std::string GetDimensionString() const
Definition: lp_data.cc:425
Fractional objective_scaling_factor() const
Definition: lp_data.h:261
void DestructiveRecoverSolution(ProblemSolution *solution)
void SetTimeLimit(TimeLimit *time_limit)
Definition: preprocessor.h:75
void assign(IntType size, const T &v)
Definition: lp_types.h:278
SatParameters parameters
CpModelProto proto
ModelSharedTimeLimit * 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:106
bool AreWithinAbsoluteTolerance(FloatType x, FloatType y, FloatType absolute_tolerance)
Definition: fp_utils.h:145
ConstraintStatusColumn constraint_statuses
Definition: lp_data.h:686
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41