26 #include "Eigen/SparseCore"
27 #include "absl/log/check.h"
28 #include "absl/random/distributions.h"
37 constexpr
double kInfinity = std::numeric_limits<double>::infinity();
38 using ::Eigen::ColMajor;
39 using ::Eigen::SparseMatrix;
40 using ::Eigen::VectorXd;
41 using ::Eigen::VectorXi;
56 CHECK_EQ(datapoint.size(), average_.size());
59 const double weight_ratio =
weight / (sum_weights_ +
weight);
61 shard(average_) += weight_ratio * (shard(datapoint) - shard(average_));
83 double CombineBounds(
const double v1,
const double v2,
84 const double infinite_bound_threshold) {
86 if (std::abs(v1) < infinite_bound_threshold) {
89 if (std::abs(v2) < infinite_bound_threshold) {
118 class VectorInfoAccumulator {
120 VectorInfoAccumulator() {}
123 VectorInfoAccumulator(
const VectorInfoAccumulator&) =
delete;
124 VectorInfoAccumulator& operator=(
const VectorInfoAccumulator&) =
delete;
125 VectorInfoAccumulator(VectorInfoAccumulator&&) =
default;
126 VectorInfoAccumulator& operator=(VectorInfoAccumulator&&) =
default;
127 void Add(
double value);
128 void Add(
const VectorInfoAccumulator& other);
129 explicit operator VectorInfo()
const;
132 int64_t num_infinite_ = 0;
133 int64_t num_zero_ = 0;
134 int64_t num_finite_nonzero_ = 0;
138 double sum_squared_ = 0.0;
141 void VectorInfoAccumulator::Add(
const double value) {
142 if (std::isinf(
value)) {
144 }
else if (
value == 0) {
147 ++num_finite_nonzero_;
148 const double abs_value = std::abs(
value);
152 sum_squared_ += abs_value * abs_value;
156 void VectorInfoAccumulator::Add(
const VectorInfoAccumulator& other) {
157 num_infinite_ += other.num_infinite_;
158 num_zero_ += other.num_zero_;
159 num_finite_nonzero_ += other.num_finite_nonzero_;
163 sum_squared_ += other.sum_squared_;
166 VectorInfoAccumulator::operator VectorInfo()
const {
168 .num_finite_nonzero = num_finite_nonzero_,
169 .num_infinite = num_infinite_,
170 .num_zero = num_zero_,
171 .largest = num_finite_nonzero_ > 0 ? max_ : 0.0,
172 .smallest = num_finite_nonzero_ > 0 ? min_ : 0.0,
173 .average = num_finite_nonzero_ + num_zero_ > 0
174 ? sum_ / (num_finite_nonzero_ + num_zero_)
175 : std::numeric_limits<double>::quiet_NaN(),
176 .l2_norm = std::sqrt(sum_squared_),
180 VectorInfo CombineAccumulators(
181 const std::vector<VectorInfoAccumulator>& accumulators) {
182 VectorInfoAccumulator result;
183 for (
const VectorInfoAccumulator& accumulator : accumulators) {
184 result.Add(accumulator);
186 return VectorInfo(result);
192 VectorInfo ComputeVectorInfo(
const VectorXd& vec,
const Sharder& sharder) {
193 std::vector<VectorInfoAccumulator> local_accumulator(sharder.NumShards());
194 sharder.ParallelForEachShard([&](
const Sharder::Shard& shard) {
195 VectorInfoAccumulator shard_accumulator;
196 for (
double element : shard(vec)) {
197 shard_accumulator.Add(element);
199 local_accumulator[shard.Index()] = std::move(shard_accumulator);
201 return CombineAccumulators(local_accumulator);
204 VectorInfo VariableBoundGapInfo(
const VectorXd&
lower_bounds,
206 const Sharder& sharder) {
207 std::vector<VectorInfoAccumulator> local_accumulator(sharder.NumShards());
208 sharder.ParallelForEachShard([&](
const Sharder::Shard& shard) {
209 VectorInfoAccumulator shard_accumulator;
211 shard_accumulator.Add(element);
213 local_accumulator[shard.Index()] = std::move(shard_accumulator);
215 return CombineAccumulators(local_accumulator);
218 VectorInfo MatrixAbsElementInfo(
219 const SparseMatrix<double, ColMajor, int64_t>& matrix,
220 const Sharder& sharder) {
221 std::vector<VectorInfoAccumulator> local_accumulator(sharder.NumShards());
222 sharder.ParallelForEachShard([&](
const Sharder::Shard& shard) {
223 VectorInfoAccumulator shard_accumulator;
224 const auto matrix_shard = shard(matrix);
225 for (int64_t col_idx = 0; col_idx < matrix_shard.outerSize(); ++col_idx) {
226 for (decltype(matrix_shard)::InnerIterator it(matrix_shard, col_idx); it;
228 shard_accumulator.Add(it.value());
231 local_accumulator[shard.Index()] = std::move(shard_accumulator);
233 return CombineAccumulators(local_accumulator);
236 VectorInfo CombinedBoundsInfo(
const VectorXd& rhs_upper_bounds,
237 const VectorXd& rhs_lower_bounds,
238 const Sharder& sharder,
239 const double infinite_bound_threshold =
240 std::numeric_limits<double>::infinity()) {
241 std::vector<VectorInfoAccumulator> local_accumulator(sharder.NumShards());
242 sharder.ParallelForEachShard([&](
const Sharder::Shard& shard) {
243 VectorInfoAccumulator shard_accumulator;
244 const auto lb_shard = shard(rhs_lower_bounds);
245 const auto ub_shard = shard(rhs_upper_bounds);
246 for (int64_t i = 0; i < lb_shard.size(); ++i) {
247 shard_accumulator.Add(
248 CombineBounds(ub_shard[i], lb_shard[i], infinite_bound_threshold));
250 local_accumulator[shard.Index()] = std::move(shard_accumulator);
252 return CombineAccumulators(local_accumulator);
255 InfNormInfo ConstraintMatrixRowColInfo(
256 const SparseMatrix<double, ColMajor, int64_t>& constraint_matrix,
257 const SparseMatrix<double, ColMajor, int64_t>& constraint_matrix_transpose,
258 const Sharder& matrix_sharder,
const Sharder& matrix_transpose_sharder,
259 const Sharder& primal_sharder,
const Sharder& dual_sharder) {
261 constraint_matrix_transpose,
263 OnesVector(dual_sharder), matrix_transpose_sharder);
268 return InfNormInfo{.row_norms = ComputeVectorInfo(
row_norms, dual_sharder),
269 .col_norms = ComputeVectorInfo(
col_norms, primal_sharder)};
276 const double infinite_constraint_bound_threshold) {
279 InfNormInfo cons_matrix_norm_info = ConstraintMatrixRowColInfo(
283 VectorInfo cons_matrix_info = MatrixAbsElementInfo(
285 VectorInfo combined_bounds_info = CombinedBoundsInfo(
287 qp.
DualSharder(), infinite_constraint_bound_threshold);
288 VectorInfo obj_vec_info =
290 VectorInfo gaps_info =
293 QuadraticProgramStats program_stats;
294 program_stats.set_num_variables(qp.
PrimalSize());
295 program_stats.set_num_constraints(qp.
DualSize());
296 program_stats.set_constraint_matrix_col_min_l_inf_norm(
297 cons_matrix_norm_info.col_norms.smallest);
298 program_stats.set_constraint_matrix_row_min_l_inf_norm(
299 cons_matrix_norm_info.row_norms.smallest);
300 program_stats.set_constraint_matrix_num_nonzeros(
301 cons_matrix_info.num_finite_nonzero);
302 program_stats.set_constraint_matrix_abs_max(cons_matrix_info.largest);
303 program_stats.set_constraint_matrix_abs_min(cons_matrix_info.smallest);
304 program_stats.set_constraint_matrix_abs_avg(cons_matrix_info.average);
305 program_stats.set_constraint_matrix_l2_norm(cons_matrix_info.l2_norm);
306 program_stats.set_combined_bounds_max(combined_bounds_info.largest);
307 program_stats.set_combined_bounds_min(combined_bounds_info.smallest);
308 program_stats.set_combined_bounds_avg(combined_bounds_info.average);
309 program_stats.set_combined_bounds_l2_norm(combined_bounds_info.l2_norm);
310 program_stats.set_variable_bound_gaps_num_finite(
311 gaps_info.num_finite_nonzero + gaps_info.num_zero);
312 program_stats.set_variable_bound_gaps_max(gaps_info.largest);
313 program_stats.set_variable_bound_gaps_min(gaps_info.smallest);
314 program_stats.set_variable_bound_gaps_avg(gaps_info.average);
315 program_stats.set_variable_bound_gaps_l2_norm(gaps_info.l2_norm);
316 program_stats.set_objective_vector_abs_max(obj_vec_info.largest);
317 program_stats.set_objective_vector_abs_min(obj_vec_info.smallest);
318 program_stats.set_objective_vector_abs_avg(obj_vec_info.average);
319 program_stats.set_objective_vector_l2_norm(obj_vec_info.l2_norm);
321 program_stats.set_objective_matrix_num_nonzeros(0);
322 program_stats.set_objective_matrix_abs_max(0);
323 program_stats.set_objective_matrix_abs_min(0);
324 program_stats.set_objective_matrix_abs_avg(
325 std::numeric_limits<double>::quiet_NaN());
326 program_stats.set_objective_matrix_l2_norm(0);
328 VectorInfo obj_matrix_info = ComputeVectorInfo(
330 program_stats.set_objective_matrix_num_nonzeros(
331 obj_matrix_info.num_finite_nonzero);
332 program_stats.set_objective_matrix_abs_max(obj_matrix_info.largest);
333 program_stats.set_objective_matrix_abs_min(obj_matrix_info.smallest);
334 program_stats.set_objective_matrix_abs_avg(obj_matrix_info.average);
335 program_stats.set_objective_matrix_l2_norm(obj_matrix_info.l2_norm);
337 return program_stats;
342 enum class ScalingNorm { kL2, kLInf };
349 void DivideBySquareRootOfDivisor(
const VectorXd& divisor,
350 const Sharder& sharder, VectorXd& vector) {
351 sharder.ParallelForEachShard([&](
const Sharder::Shard& shard) {
352 auto vec_shard = shard(vector);
353 const auto divisor_shard = shard(divisor);
355 if (divisor_shard[
index] != 0) {
356 vec_shard[
index] /= std::sqrt(divisor_shard[
index]);
362 void ApplyScalingIterationsForNorm(
const ShardedQuadraticProgram& sharded_qp,
363 const int num_iterations,
364 const ScalingNorm norm,
365 VectorXd& row_scaling_vec,
366 VectorXd& col_scaling_vec) {
367 const QuadraticProgram& qp = sharded_qp.Qp();
368 const int64_t num_col = qp.constraint_matrix.cols();
369 const int64_t num_row = qp.constraint_matrix.rows();
370 CHECK_EQ(num_col, col_scaling_vec.size());
371 CHECK_EQ(num_row, row_scaling_vec.size());
372 for (
int i = 0; i < num_iterations; ++i) {
376 case ScalingNorm::kL2: {
379 sharded_qp.ConstraintMatrixSharder());
381 sharded_qp.TransposedConstraintMatrix(), col_scaling_vec,
382 row_scaling_vec, sharded_qp.TransposedConstraintMatrixSharder());
385 case ScalingNorm::kLInf: {
388 sharded_qp.ConstraintMatrixSharder());
390 sharded_qp.TransposedConstraintMatrix(), col_scaling_vec,
391 row_scaling_vec, sharded_qp.TransposedConstraintMatrixSharder());
395 DivideBySquareRootOfDivisor(col_norm, sharded_qp.PrimalSharder(),
397 DivideBySquareRootOfDivisor(row_norm, sharded_qp.DualSharder(),
405 const int num_iterations, VectorXd& row_scaling_vec,
406 VectorXd& col_scaling_vec) {
407 ApplyScalingIterationsForNorm(sharded_qp, num_iterations, ScalingNorm::kLInf,
408 row_scaling_vec, col_scaling_vec);
412 VectorXd& row_scaling_vec, VectorXd& col_scaling_vec) {
413 ApplyScalingIterationsForNorm(sharded_qp, 1,
414 ScalingNorm::kL2, row_scaling_vec,
423 bool do_rescale =
false;
427 scaling.row_scaling_vec, scaling.col_scaling_vec);
432 scaling.col_scaling_vec);
436 scaling.row_scaling_vec);
442 const VectorXd& primal_solution,
443 const VectorXd& dual_product) {
450 shard(result.gradient) =
452 value_parts[shard.
Index()] =
453 shard(primal_solution).dot(shard(result.gradient));
458 const VectorXd objective_product =
461 objective_product - shard(dual_product);
462 value_parts[shard.Index()] =
463 shard(primal_solution)
464 .dot(shard(result.gradient) - 0.5 * objective_product);
467 result.value = value_parts.sum();
472 const double constraint_upper_bound,
474 const double primal_product) {
476 return constraint_upper_bound;
477 }
else if (dual > 0.0) {
478 return constraint_lower_bound;
479 }
else if (std::isfinite(constraint_lower_bound) &&
480 std::isfinite(constraint_upper_bound)) {
481 if (primal_product < constraint_lower_bound) {
482 return constraint_lower_bound;
483 }
else if (primal_product > constraint_upper_bound) {
484 return constraint_upper_bound;
486 return primal_product;
488 }
else if (std::isfinite(constraint_lower_bound)) {
489 return constraint_lower_bound;
490 }
else if (std::isfinite(constraint_upper_bound)) {
491 return constraint_upper_bound;
498 const VectorXd& dual_solution,
499 const VectorXd& primal_product) {
507 const auto dual_solution_shard = shard(dual_solution);
508 auto dual_gradient_shard = shard(result.gradient);
509 const auto primal_product_shard = shard(primal_product);
510 double value_sum = 0.0;
511 for (int64_t i = 0; i < dual_gradient_shard.size(); ++i) {
513 constraint_lower_bounds[i], constraint_upper_bounds[i],
514 dual_solution_shard[i], primal_product_shard[i]);
515 value_sum += dual_gradient_shard[i] * dual_solution_shard[i];
517 value_parts[shard.
Index()] = value_sum;
518 dual_gradient_shard -= primal_product_shard;
520 result.value = value_parts.sum();
526 using ::Eigen::ColMajor;
527 using ::Eigen::SparseMatrix;
531 double NormalizeVector(
const Sharder& sharder, VectorXd& vector) {
532 const double norm =
Norm(vector, sharder);
534 sharder.ParallelForEachShard(
535 [&](
const Sharder::Shard& shard) { shard(vector) /= norm; });
545 double PowerMethodFailureProbability(int64_t dimension,
double epsilon,
int k) {
546 if (k < 2 || epsilon <= 0.0) {
550 return std::min(0.824, 0.354 / std::sqrt(epsilon * (k - 1))) *
551 std::sqrt(dimension) * std::pow(1.0 - epsilon, k - 0.5);
554 SingularValueAndIterations EstimateMaximumSingularValue(
555 const SparseMatrix<double, ColMajor, int64_t>& matrix,
556 const SparseMatrix<double, ColMajor, int64_t>& matrix_transpose,
557 const std::optional<VectorXd>& active_set_indicator,
558 const std::optional<VectorXd>& transpose_active_set_indicator,
559 const Sharder& matrix_sharder,
const Sharder& matrix_transpose_sharder,
560 const Sharder& primal_vector_sharder,
const Sharder& dual_vector_sharder,
561 const double desired_relative_error,
const double failure_probability,
562 std::mt19937& mt_generator) {
563 const int64_t dimension = matrix.cols();
564 VectorXd eigenvector(dimension);
567 for (
double& entry : eigenvector) {
568 entry = absl::Gaussian<double>(mt_generator);
570 if (active_set_indicator.has_value()) {
574 NormalizeVector(primal_vector_sharder, eigenvector);
575 double eigenvalue_estimate = 0.0;
577 int num_iterations = 0;
582 const double epsilon = 1.0 -
MathUtil::Square(1.0 - desired_relative_error);
583 while (PowerMethodFailureProbability(dimension, epsilon, num_iterations) >
584 failure_probability) {
586 matrix_transpose, eigenvector, matrix_transpose_sharder);
587 if (transpose_active_set_indicator.has_value()) {
589 dual_vector_sharder, dual_eigenvector);
591 VectorXd next_eigenvector =
593 if (active_set_indicator.has_value()) {
595 primal_vector_sharder, next_eigenvector);
597 eigenvalue_estimate =
598 Dot(eigenvector, next_eigenvector, primal_vector_sharder);
599 eigenvector = std::move(next_eigenvector);
601 const double primal_norm =
602 NormalizeVector(primal_vector_sharder, eigenvector);
604 VLOG(1) <<
"Iteration " << num_iterations <<
" singular value estimate "
605 << std::sqrt(eigenvalue_estimate) <<
" primal norm " << primal_norm;
607 return SingularValueAndIterations{
608 .singular_value = std::sqrt(eigenvalue_estimate),
609 .num_iterations = num_iterations,
610 .estimated_relative_error = desired_relative_error};
615 VectorXd ComputePrimalActiveSetIndicator(
616 const ShardedQuadraticProgram& sharded_qp,
617 const VectorXd& primal_solution) {
618 VectorXd indicator(sharded_qp.PrimalSize());
619 sharded_qp.PrimalSharder().ParallelForEachShard(
620 [&](
const Sharder::Shard& shard) {
621 const auto lower_bound_shard =
622 shard(sharded_qp.Qp().variable_lower_bounds);
623 const auto upper_bound_shard =
624 shard(sharded_qp.Qp().variable_upper_bounds);
625 const auto primal_solution_shard = shard(primal_solution);
626 auto indicator_shard = shard(indicator);
627 const int64_t shard_size =
628 sharded_qp.PrimalSharder().ShardSize(shard.Index());
629 for (int64_t i = 0; i < shard_size; ++i) {
630 if ((primal_solution_shard[i] == lower_bound_shard[i]) ||
631 (primal_solution_shard[i] == upper_bound_shard[i])) {
632 indicator_shard[i] = 0.0;
634 indicator_shard[i] = 1.0;
643 VectorXd ComputeDualActiveSetIndicator(
644 const ShardedQuadraticProgram& sharded_qp,
const VectorXd& dual_solution) {
645 VectorXd indicator(sharded_qp.DualSize());
646 sharded_qp.DualSharder().ParallelForEachShard(
647 [&](
const Sharder::Shard& shard) {
648 const auto lower_bound_shard =
649 shard(sharded_qp.Qp().constraint_lower_bounds);
650 const auto upper_bound_shard =
651 shard(sharded_qp.Qp().constraint_upper_bounds);
652 const auto dual_solution_shard = shard(dual_solution);
653 auto indicator_shard = shard(indicator);
654 const int64_t shard_size =
655 sharded_qp.DualSharder().ShardSize(shard.Index());
656 for (int64_t i = 0; i < shard_size; ++i) {
657 if (dual_solution_shard[i] == 0.0 &&
658 (std::isinf(lower_bound_shard[i]) ||
659 std::isinf(upper_bound_shard[i]))) {
660 indicator_shard[i] = 0.0;
662 indicator_shard[i] = 1.0;
673 const std::optional<VectorXd>& primal_solution,
674 const std::optional<VectorXd>& dual_solution,
675 const double desired_relative_error,
const double failure_probability,
676 std::mt19937& mt_generator) {
677 std::optional<VectorXd> primal_active_set_indicator;
678 std::optional<VectorXd> dual_active_set_indicator;
679 if (primal_solution.has_value()) {
680 primal_active_set_indicator =
681 ComputePrimalActiveSetIndicator(sharded_qp, *primal_solution);
683 if (dual_solution.has_value()) {
684 dual_active_set_indicator =
685 ComputeDualActiveSetIndicator(sharded_qp, *dual_solution);
687 return EstimateMaximumSingularValue(
693 desired_relative_error, failure_probability, mt_generator);
698 const bool constraint_bounds_valid =
705 const bool variable_bounds_valid =
713 return constraint_bounds_valid && variable_bounds_valid;
721 shard(primal) = shard(primal)
734 auto dual_shard = shard(dual);
736 for (int64_t i = 0; i < dual_shard.size(); ++i) {
737 if (!std::isfinite(upper_bound_shard[i])) {
738 dual_shard[i] =
std::max(dual_shard[i], 0.0);
740 if (!std::isfinite(lower_bound_shard[i])) {
741 dual_shard[i] =
std::min(dual_shard[i], 0.0);
static T Square(const T x)
const Sharder & DualSharder() const
void RescaleQuadraticProgram(const Eigen::VectorXd &col_scaling_vec, const Eigen::VectorXd &row_scaling_vec)
const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > & TransposedConstraintMatrix() const
const Sharder & ConstraintMatrixSharder() const
const Sharder & PrimalSharder() const
int64_t PrimalSize() const
const Sharder & TransposedConstraintMatrixSharder() const
const QuadraticProgram & Qp() const
ShardedWeightedAverage(const Sharder *sharder)
void Add(const Eigen::VectorXd &datapoint, double weight)
Eigen::VectorXd ComputeAverage() const
void ParallelForEachShard(const std::function< void(const Shard &)> &func) const
bool ParallelTrueForAllShards(const std::function< bool(const Shard &)> &func) const
void SetZero(const Sharder &sharder, VectorXd &dest)
LagrangianPart ComputeDualGradient(const ShardedQuadraticProgram &sharded_qp, const VectorXd &dual_solution, const VectorXd &primal_product)
double Dot(const VectorXd &v1, const VectorXd &v2, const Sharder &sharder)
LagrangianPart ComputePrimalGradient(const ShardedQuadraticProgram &sharded_qp, const VectorXd &primal_solution, const VectorXd &dual_product)
VectorXd TransposedMatrixVectorProduct(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix, const VectorXd &vector, const Sharder &sharder)
void LInfRuizRescaling(const ShardedQuadraticProgram &sharded_qp, const int num_iterations, VectorXd &row_scaling_vec, VectorXd &col_scaling_vec)
constexpr double kInfinity
SingularValueAndIterations EstimateMaximumSingularValueOfConstraintMatrix(const ShardedQuadraticProgram &sharded_qp, const std::optional< VectorXd > &primal_solution, const std::optional< VectorXd > &dual_solution, const double desired_relative_error, const double failure_probability, std::mt19937 &mt_generator)
VectorXd ScaledColLInfNorm(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix, const VectorXd &row_scaling_vec, const VectorXd &col_scaling_vec, const Sharder &sharder)
double DualSubgradientCoefficient(const double constraint_lower_bound, const double constraint_upper_bound, const double dual, const double primal_product)
bool HasValidBounds(const QuadraticProgram &qp)
bool IsLinearProgram(const QuadraticProgram &qp)
void ProjectToDualVariableBounds(const ShardedQuadraticProgram &sharded_qp, VectorXd &dual)
void CoefficientWiseProductInPlace(const VectorXd &scale, const Sharder &sharder, VectorXd &dest)
void L2NormRescaling(const ShardedQuadraticProgram &sharded_qp, VectorXd &row_scaling_vec, VectorXd &col_scaling_vec)
VectorXd ScaledColL2Norm(const Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix, const VectorXd &row_scaling_vec, const VectorXd &col_scaling_vec, const Sharder &sharder)
ScalingVectors ApplyRescaling(const RescalingOptions &rescaling_options, ShardedQuadraticProgram &sharded_qp)
QuadraticProgramStats ComputeStats(const ShardedQuadraticProgram &qp, const double infinite_constraint_bound_threshold)
void ProjectToPrimalVariableBounds(const ShardedQuadraticProgram &sharded_qp, VectorXd &primal)
double Norm(const VectorXd &vector, const Sharder &sharder)
VectorXd ZeroVector(const Sharder &sharder)
void AssignVector(const VectorXd &vec, const Sharder &sharder, VectorXd &dest)
VectorXd OnesVector(const Sharder &sharder)
std::vector< double > lower_bounds
std::vector< double > upper_bounds
int64_t num_finite_nonzero
Eigen::VectorXd variable_upper_bounds
Eigen::VectorXd variable_lower_bounds
Eigen::VectorXd constraint_lower_bounds
Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > constraint_matrix
std::optional< Eigen::DiagonalMatrix< double, Eigen::Dynamic > > objective_matrix
Eigen::VectorXd constraint_upper_bounds
Eigen::VectorXd objective_vector
int l_inf_ruiz_iterations
Eigen::VectorXd row_scaling_vec
#define VLOG(verboselevel)