Skip to content

Commit b5ca2d2

Browse files
lperronMizux
authored andcommitted
Use Fractional everywhere
1 parent d724c85 commit b5ca2d2

10 files changed

Lines changed: 82 additions & 78 deletions

ortools/glop/lp_solver.cc

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ Fractional ProblemObjectiveValue(const LinearProgram& lp, Fractional value) {
325325
// Returns the allowed error magnitude for something that should evaluate to
326326
// value under the given tolerance.
327327
Fractional AllowedError(Fractional tolerance, Fractional value) {
328-
return tolerance * std::max(1.0, std::abs(value));
328+
return tolerance * std::max(Fractional(1.0), std::abs(value));
329329
}
330330
} // namespace
331331

@@ -492,15 +492,15 @@ bool LPSolver::IsOptimalSolutionOnFacet(const LinearProgram& lp) {
492492
// primal values.
493493
// TODO(user): investigate whether to use the tolerances defined in
494494
// parameters.proto.
495-
const double kReducedCostTolerance = 1e-9;
496-
const double kBoundTolerance = 1e-7;
495+
const Fractional kReducedCostTolerance = 1e-9;
496+
const Fractional kBoundTolerance = 1e-7;
497497
const ColIndex num_cols = lp.num_variables();
498498
for (ColIndex col(0); col < num_cols; ++col) {
499499
if (variable_statuses_[col] == VariableStatus::FIXED_VALUE) continue;
500500
const Fractional lower_bound = lp.variable_lower_bounds()[col];
501501
const Fractional upper_bound = lp.variable_upper_bounds()[col];
502502
const Fractional value = primal_values_[col];
503-
if (AreWithinAbsoluteTolerance(reduced_costs_[col], 0.0,
503+
if (AreWithinAbsoluteTolerance(reduced_costs_[col], Fractional(0.0),
504504
kReducedCostTolerance) &&
505505
(AreWithinAbsoluteTolerance(value, lower_bound, kBoundTolerance) ||
506506
AreWithinAbsoluteTolerance(value, upper_bound, kBoundTolerance))) {
@@ -513,7 +513,7 @@ bool LPSolver::IsOptimalSolutionOnFacet(const LinearProgram& lp) {
513513
const Fractional lower_bound = lp.constraint_lower_bounds()[row];
514514
const Fractional upper_bound = lp.constraint_upper_bounds()[row];
515515
const Fractional activity = constraint_activities_[row];
516-
if (AreWithinAbsoluteTolerance(dual_values_[row], 0.0,
516+
if (AreWithinAbsoluteTolerance(dual_values_[row], Fractional(0.0),
517517
kReducedCostTolerance) &&
518518
(AreWithinAbsoluteTolerance(activity, lower_bound, kBoundTolerance) ||
519519
AreWithinAbsoluteTolerance(activity, upper_bound, kBoundTolerance))) {
@@ -739,7 +739,8 @@ bool LPSolver::IsProblemSolutionConsistent(
739739
case VariableStatus::AT_UPPER_BOUND:
740740
// TODO(user): revert to an exact comparison once the bug causing this
741741
// to fail has been fixed.
742-
if (!AreWithinAbsoluteTolerance(value, ub, 1e-7) || lb == ub) {
742+
if (!AreWithinAbsoluteTolerance(value, ub, Fractional(1e-7)) ||
743+
lb == ub) {
743744
LogVariableStatusError(col, value, status, lb, ub);
744745
return false;
745746
}
@@ -1002,7 +1003,7 @@ double LPSolver::ComputeMaxExpectedObjectiveError(const LinearProgram& lp) {
10021003

10031004
double LPSolver::ComputePrimalValueInfeasibility(const LinearProgram& lp,
10041005
bool* is_too_large) {
1005-
double infeasibility = 0.0;
1006+
Fractional infeasibility = 0.0;
10061007
const Fractional tolerance = parameters_.solution_feasibility_tolerance();
10071008
const ColIndex num_cols = lp.num_variables();
10081009
for (ColIndex col(0); col < num_cols; ++col) {
@@ -1032,7 +1033,7 @@ double LPSolver::ComputePrimalValueInfeasibility(const LinearProgram& lp,
10321033

10331034
double LPSolver::ComputeActivityInfeasibility(const LinearProgram& lp,
10341035
bool* is_too_large) {
1035-
double infeasibility = 0.0;
1036+
Fractional infeasibility = 0.0;
10361037
int num_problematic_rows(0);
10371038
const RowIndex num_rows = lp.num_constraints();
10381039
const Fractional tolerance = parameters_.solution_feasibility_tolerance();
@@ -1085,7 +1086,7 @@ double LPSolver::ComputeDualValueInfeasibility(const LinearProgram& lp,
10851086
bool* is_too_large) {
10861087
const Fractional allowed_error = parameters_.solution_feasibility_tolerance();
10871088
const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
1088-
double infeasibility = 0.0;
1089+
Fractional infeasibility = 0.0;
10891090
const RowIndex num_rows = lp.num_constraints();
10901091
for (RowIndex row(0); row < num_rows; ++row) {
10911092
const Fractional dual_value = dual_values_[row];
@@ -1108,7 +1109,7 @@ double LPSolver::ComputeDualValueInfeasibility(const LinearProgram& lp,
11081109
double LPSolver::ComputeReducedCostInfeasibility(const LinearProgram& lp,
11091110
bool* is_too_large) {
11101111
const Fractional optimization_sign = lp.IsMaximizationProblem() ? -1.0 : 1.0;
1111-
double infeasibility = 0.0;
1112+
Fractional infeasibility = 0.0;
11121113
const ColIndex num_cols = lp.num_variables();
11131114
const Fractional tolerance = parameters_.solution_feasibility_tolerance();
11141115
for (ColIndex col(0); col < num_cols; ++col) {

ortools/glop/preprocessor.cc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2430,7 +2430,8 @@ bool SingletonPreprocessor::IntegerSingletonColumnIsRemovable(
24302430
const Fractional coefficient_ratio = coefficient / matrix_entry.coeff;
24312431
// Check if coefficient_ratio is integer.
24322432
if (!IsIntegerWithinTolerance(
2433-
coefficient_ratio, parameters_.solution_feasibility_tolerance())) {
2433+
coefficient_ratio,
2434+
Fractional(parameters_.solution_feasibility_tolerance()))) {
24342435
return false;
24352436
}
24362437
}
@@ -2439,7 +2440,8 @@ bool SingletonPreprocessor::IntegerSingletonColumnIsRemovable(
24392440
if (IsFinite(constraint_lb)) {
24402441
const Fractional lower_bound_ratio = constraint_lb / matrix_entry.coeff;
24412442
if (!IsIntegerWithinTolerance(
2442-
lower_bound_ratio, parameters_.solution_feasibility_tolerance())) {
2443+
lower_bound_ratio,
2444+
Fractional(parameters_.solution_feasibility_tolerance()))) {
24432445
return false;
24442446
}
24452447
}
@@ -2448,7 +2450,8 @@ bool SingletonPreprocessor::IntegerSingletonColumnIsRemovable(
24482450
if (IsFinite(constraint_ub)) {
24492451
const Fractional upper_bound_ratio = constraint_ub / matrix_entry.coeff;
24502452
if (!IsIntegerWithinTolerance(
2451-
upper_bound_ratio, parameters_.solution_feasibility_tolerance())) {
2453+
upper_bound_ratio,
2454+
Fractional(parameters_.solution_feasibility_tolerance()))) {
24522455
return false;
24532456
}
24542457
}

ortools/glop/preprocessor.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ class Preprocessor {
8383
// tolerance).
8484
bool IsSmallerWithinFeasibilityTolerance(Fractional a, Fractional b) const {
8585
return ::operations_research::IsSmallerWithinTolerance(
86-
a, b, parameters_.solution_feasibility_tolerance());
86+
a, b, Fractional(parameters_.solution_feasibility_tolerance()));
8787
}
8888
bool IsSmallerWithinPreprocessorZeroTolerance(Fractional a,
8989
Fractional b) const {
9090
// TODO(user): use an absolute tolerance here to be even more defensive?
9191
return ::operations_research::IsSmallerWithinTolerance(
92-
a, b, parameters_.preprocessor_zero_tolerance());
92+
a, b, Fractional(parameters_.preprocessor_zero_tolerance()));
9393
}
9494

9595
ProblemStatus status_;

ortools/glop/primal_edge_norms.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,11 +238,14 @@ void PrimalEdgeNorms::UpdateEdgeSquaredNorms(ColIndex entering_col,
238238
const Fractional pivot = -direction[leaving_row];
239239
DCHECK_NE(pivot, 0.0);
240240

241+
const ColIndex first_slack =
242+
compact_matrix_.num_cols() - RowToColIndex(compact_matrix_.num_rows());
243+
241244
// Note that this should be precise because of the call to
242245
// TestEnteringEdgeNormPrecision().
243246
const Fractional entering_squared_norm = edge_squared_norms_[entering_col];
244247
const Fractional leaving_squared_norm =
245-
std::max(1.0, entering_squared_norm / Square(pivot));
248+
std::max(Fractional(1.0), entering_squared_norm / Square(pivot));
246249

247250
int stat_lower_bounded_norms = 0;
248251
const Fractional factor = 2.0 / pivot;
@@ -253,7 +256,9 @@ void PrimalEdgeNorms::UpdateEdgeSquaredNorms(ColIndex entering_col,
253256
for (const ColIndex col : update_row.GetNonZeroPositions()) {
254257
const Fractional coeff = update_row.GetCoefficient(col);
255258
const Fractional scalar_product =
256-
view.ColumnScalarProduct(col, direction_left_inverse);
259+
col >= first_slack
260+
? direction_left_inverse_[col - first_slack]
261+
: view.ColumnScalarProduct(col, direction_left_inverse);
257262
num_operations_ += view.ColumnNumEntries(col).value();
258263

259264
// Update the edge squared norm of this column. Note that the update
@@ -288,7 +293,7 @@ void PrimalEdgeNorms::UpdateDevexWeights(
288293
const Fractional entering_norm = sqrt(PreciseSquaredNorm(direction));
289294
const Fractional pivot_magnitude = std::abs(direction[leaving_row]);
290295
const Fractional leaving_norm =
291-
std::max(1.0, entering_norm / pivot_magnitude);
296+
std::max(Fractional(1.0), entering_norm / pivot_magnitude);
292297
for (const ColIndex col : update_row.GetNonZeroPositions()) {
293298
const Fractional coeff = update_row.GetCoefficient(col);
294299
const Fractional update_vector_norm = std::abs(coeff) * leaving_norm;

ortools/glop/reduced_costs.cc

Lines changed: 21 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,12 @@
2626
#include "ortools/glop/update_row.h"
2727
#include "ortools/glop/variables_info.h"
2828
#include "ortools/lp_data/lp_types.h"
29+
#include "ortools/lp_data/lp_utils.h"
2930
#include "ortools/lp_data/scattered_vector.h"
3031
#include "ortools/lp_data/sparse.h"
3132
#include "ortools/util/bitset.h"
3233
#include "ortools/util/stats.h"
3334

34-
#ifdef OMP
35-
#include <omp.h>
36-
#endif
37-
38-
#include "ortools/lp_data/lp_utils.h"
39-
4035
namespace operations_research {
4136
namespace glop {
4237

@@ -275,8 +270,8 @@ void ReducedCosts::PerturbCosts() {
275270
case VariableType::UPPER_AND_LOWER_BOUNDED:
276271
// Here we don't necessarily maintain the dual-feasibility of a dual
277272
// feasible solution, however we can always shift the variable to its
278-
// other bound (because it is boxed) to restore dual-feasiblity. This is
279-
// done by MakeBoxedVariableDualFeasible() at the end of the dual
273+
// other bound (because it is boxed) to restore dual-feasibility. This
274+
// is done by MakeBoxedVariableDualFeasible() at the end of the dual
280275
// phase-I algorithm.
281276
if (objective > 0.0) {
282277
cost_perturbations_[col] = magnitude;
@@ -370,52 +365,30 @@ void ReducedCosts::ComputeReducedCosts() {
370365
}
371366
Fractional dual_residual_error(0.0);
372367
const ColIndex num_cols = matrix_.num_cols();
368+
const ColIndex first_slack = num_cols - RowToColIndex(matrix_.num_rows());
373369

374370
reduced_costs_.resize(num_cols, 0.0);
375371
const DenseBitRow& is_basic = variables_info_.GetIsBasicBitRow();
376-
#ifdef OMP
377-
const int num_omp_threads = parameters_.num_omp_threads();
378-
#else
379-
const int num_omp_threads = 1;
380-
#endif
381-
if (num_omp_threads == 1) {
382-
for (ColIndex col(0); col < num_cols; ++col) {
383-
reduced_costs_[col] = objective_[col] + cost_perturbations_[col] -
384-
matrix_.ColumnScalarProduct(
385-
col, basic_objective_left_inverse_.values);
386-
387-
// We also compute the dual residual error y.B - c_B.
388-
if (is_basic.IsSet(col)) {
389-
dual_residual_error =
390-
std::max(dual_residual_error, std::abs(reduced_costs_[col]));
391-
}
392-
}
393-
} else {
394-
#ifdef OMP
395-
// In the multi-threaded case, perform the same computation as in the
396-
// single-threaded case above.
397-
std::vector<Fractional> thread_local_dual_residual_error(num_omp_threads,
398-
0.0);
399-
const int parallel_loop_size = num_cols.value();
400-
#pragma omp parallel for num_threads(num_omp_threads)
401-
for (int i = 0; i < parallel_loop_size; i++) {
402-
const ColIndex col(i);
403-
reduced_costs_[col] = objective_[col] + objective_perturbation_[col] -
404-
matrix_.ColumnScalarProduct(
405-
col, basic_objective_left_inverse_.values);
406-
407-
if (is_basic.IsSet(col)) {
408-
thread_local_dual_residual_error[omp_get_thread_num()] =
409-
std::max(thread_local_dual_residual_error[omp_get_thread_num()],
410-
std::abs(reduced_costs_[col]));
411-
}
372+
for (ColIndex col(0); col < first_slack; ++col) {
373+
reduced_costs_[col] =
374+
objective_[col] + cost_perturbations_[col] -
375+
matrix_.ColumnScalarProduct(col, basic_objective_left_inverse_.values);
376+
377+
// We also compute the dual residual error y.B - c_B.
378+
if (is_basic.IsSet(col)) {
379+
dual_residual_error =
380+
std::max(dual_residual_error, std::abs(reduced_costs_[col]));
412381
}
413-
// end of omp parallel for
414-
for (int i = 0; i < num_omp_threads; i++) {
382+
}
383+
for (ColIndex col(first_slack); col < num_cols; ++col) {
384+
reduced_costs_[col] = objective_[col] + cost_perturbations_[col] -
385+
basic_objective_left_inverse_[col - first_slack];
386+
387+
// We also compute the dual residual error y.B - c_B.
388+
if (is_basic.IsSet(col)) {
415389
dual_residual_error =
416-
std::max(dual_residual_error, thread_local_dual_residual_error[i]);
390+
std::max(dual_residual_error, std::abs(reduced_costs_[col]));
417391
}
418-
#endif // OMP
419392
}
420393

421394
deterministic_time_ +=

ortools/glop/revised_simplex.cc

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ ABSL_MUST_USE_RESULT Status RevisedSimplex::SolveInternal(
478478
double min_distance = kInfinity;
479479
const DenseRow& lower_bounds = variables_info_.GetVariableLowerBounds();
480480
const DenseRow& upper_bounds = variables_info_.GetVariableUpperBounds();
481-
double cost_delta = 0.0;
481+
Fractional cost_delta = 0.0;
482482
for (ColIndex col(0); col < num_cols_; ++col) {
483483
cost_delta += solution_primal_ray_[col] * objective_[col];
484484
if (solution_primal_ray_[col] > 0 && upper_bounds[col] != kInfinity) {
@@ -586,10 +586,12 @@ ABSL_MUST_USE_RESULT Status RevisedSimplex::SolveInternal(
586586
// infeasibility lower than its corresponding residual error. Note that
587587
// we already adapt the tolerance like this during the simplex
588588
// execution.
589-
const Fractional primal_tolerance = std::max(
590-
primal_residual, parameters_.primal_feasibility_tolerance());
589+
const Fractional primal_tolerance =
590+
std::max(primal_residual,
591+
Fractional(parameters_.primal_feasibility_tolerance()));
591592
const Fractional dual_tolerance =
592-
std::max(dual_residual, parameters_.dual_feasibility_tolerance());
593+
std::max(dual_residual,
594+
Fractional(parameters_.dual_feasibility_tolerance()));
593595
const Fractional primal_infeasibility =
594596
variable_values_.ComputeMaximumPrimalInfeasibility();
595597
const Fractional dual_infeasibility =
@@ -2747,7 +2749,7 @@ Status RevisedSimplex::Polish(TimeLimit* time_limit) {
27472749
const auto get_diff = [this](ColIndex col, Fractional old_value,
27482750
Fractional new_value) {
27492751
if (col >= integrality_scale_.size() || integrality_scale_[col] == 0.0) {
2750-
return 0.0;
2752+
return Fractional(0.0);
27512753
}
27522754
const Fractional s = integrality_scale_[col];
27532755
return (std::abs(new_value * s - std::round(new_value * s)) -

ortools/glop/update_row.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,14 +304,19 @@ void UpdateRow::ComputeUpdatesColumnWise() {
304304
non_zero_position_list_.resize(matrix_.num_cols().value());
305305
auto* non_zeros = non_zero_position_list_.data();
306306

307+
const ColIndex first_slack =
308+
matrix_.num_cols() - RowToColIndex(matrix_.num_rows());
309+
307310
const Fractional drop_tolerance = parameters_.drop_tolerance();
308311
const auto output_coeffs = coefficient_.view();
309312
const auto view = matrix_.view();
310313
const auto unit_row_left_inverse = unit_row_left_inverse_.values.const_view();
311314
for (const ColIndex col : variables_info_.GetIsRelevantBitRow()) {
312315
// Coefficient of the column right inverse on the 'leaving_row'.
313316
const Fractional coeff =
314-
view.ColumnScalarProduct(col, unit_row_left_inverse);
317+
col >= first_slack
318+
? unit_row_left_inverse_[col - first_slack]
319+
: view.ColumnScalarProduct(col, unit_row_left_inverse);
315320

316321
// Nothing to do if 'coeff' is (almost) zero which does happen due to
317322
// sparsity. Note that it shouldn't be too bad to use a non-zero drop

ortools/glop/variable_values.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ Fractional VariableValues::ComputeSumOfPrimalInfeasibilities() const {
170170
for (ColIndex col(0); col < num_cols; ++col) {
171171
const Fractional infeasibility =
172172
GetColInfeasibility(col, values, lower_bounds, upper_bounds);
173-
sum += std::max(0.0, infeasibility);
173+
sum += std::max(Fractional{0.0}, infeasibility);
174174
}
175175
return sum;
176176
}

ortools/lp_data/lp_parser.cc

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,21 @@ bool LPParser::ParseConstraint(StringPiece constraint) {
235235
return true;
236236
}
237237

238+
namespace {
239+
240+
template <typename T>
241+
bool SimpleAtoFractional(absl::string_view str, T* value) {
242+
if constexpr (std::is_same_v<T, double>) {
243+
return absl::SimpleAtod(str, value);
244+
} else if constexpr (std::is_same_v<T, float>) {
245+
return absl::SimpleAtof(str, value);
246+
} else {
247+
static_assert(false, "Unsupported fractional type");
248+
return false;
249+
}
250+
}
251+
} // namespace
252+
238253
bool LPParser::SetVariableBounds(ColIndex col, Fractional lb, Fractional ub) {
239254
if (bounded_variables_.find(col) == bounded_variables_.end()) {
240255
// The variable was not bounded yet, thus reset its bounds.
@@ -250,7 +265,7 @@ bool LPParser::SetVariableBounds(ColIndex col, Fractional lb, Fractional ub) {
250265
}
251266

252267
TokenType ConsumeToken(StringPiece* sp, std::string* consumed_name,
253-
double* consumed_coeff) {
268+
Fractional* consumed_coeff) {
254269
DCHECK(consumed_name != nullptr);
255270
DCHECK(consumed_coeff != nullptr);
256271
// We use LazyRE2 everywhere so that all the patterns are just compiled once
@@ -305,7 +320,7 @@ TokenType ConsumeToken(StringPiece* sp, std::string* consumed_name,
305320
static const LazyRE2 kValuePattern = {
306321
R"(\s*([0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?))"};
307322
if (RE2::Consume(sp, *kValuePattern, &coeff)) {
308-
if (!absl::SimpleAtod(coeff, consumed_coeff)) {
323+
if (!SimpleAtoFractional(coeff, consumed_coeff)) {
309324
// Note: If absl::SimpleAtod(), Consume(), and kValuePattern are correct,
310325
// this should never happen.
311326
LOG(ERROR) << "Text: " << coeff << " was matched by RE2 to be "

ortools/lp_data/lp_types.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@ static inline double ToDouble(double f) { return f; }
8181
typedef double Fractional;
8282

8383
// Range max for type Fractional. DBL_MAX for double for example.
84-
constexpr double kRangeMax = std::numeric_limits<double>::max();
84+
constexpr Fractional kRangeMax = std::numeric_limits<Fractional>::max();
8585

8686
// Infinity for type Fractional.
87-
constexpr double kInfinity = std::numeric_limits<double>::infinity();
87+
constexpr Fractional kInfinity = std::numeric_limits<Fractional>::infinity();
8888

8989
// Epsilon for type Fractional, i.e. the smallest e such that 1.0 + e != 1.0 .
90-
constexpr double kEpsilon = std::numeric_limits<double>::epsilon();
90+
constexpr Fractional kEpsilon = std::numeric_limits<Fractional>::epsilon();
9191

9292
// Returns true if the given value is finite, that means for a double:
9393
// not a NaN and not +/- infinity.

0 commit comments

Comments
 (0)