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