Files
ortools-clone/ortools/pdlp/primal_dual_hybrid_gradient.h
Corentin Le Molgat a66a6daac7 Bump Copyright to 2025
2025-01-10 11:35:44 +01:00

185 lines
8.3 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PDLP_PRIMAL_DUAL_HYBRID_GRADIENT_H_
#define PDLP_PRIMAL_DUAL_HYBRID_GRADIENT_H_
#include <atomic>
#include <functional>
#include <optional>
#include <string>
#include "Eigen/Core"
#include "ortools/lp_data/lp_data.h"
#include "ortools/pdlp/quadratic_program.h"
#include "ortools/pdlp/solve_log.pb.h"
#include "ortools/pdlp/solvers.pb.h"
#include "ortools/pdlp/termination.h"
namespace operations_research::pdlp {
struct PrimalAndDualSolution {
Eigen::VectorXd primal_solution;
Eigen::VectorXd dual_solution;
};
// The following table defines the interpretation of the result vectors
// depending on the value of `solve_log.termination_reason`: (the
// TERMINATION_REASON_ prefix is omitted for brevity):
//
// * OPTIMAL: the vectors satisfy the termination criteria for optimality.
// * PRIMAL_INFEASIBLE: dual_solution and reduced_costs provide an approximate
// certificate of primal infeasibility; see
// https://developers.google.com/optimization/lp/pdlp_math#infeasibility_identification
// for more information.
// * DUAL_INFEASIBLE: `primal_solution` provides an approximate certificate of
// dual infeasibility; see
// https://developers.google.com/optimization/lp/pdlp_math#infeasibility_identification
// for more information.
// * PRIMAL_OR_DUAL_INFEASIBLE: the problem was shown to be primal and/or dual
// infeasible but no certificate of infeasibility is available. The
// `primal_solution` and `dual_solution` have no meaning. This status is only
// used when presolve is enabled.
// * TIME_LIMIT, ITERATION_LIMIT, KKT_MATRIX_PASS_LIMIT, NUMERICAL_ERROR,
// INTERRUPTED_BY_USER: the vectors contain an iterate at the time that the
// respective event occurred. Their values may or may not be meaningful. In
// some cases solution quality information is available; see documentation for
// `solve_log.solution_type`.
// * INVALID_PROBLEM, INVALID_PARAMETER, OTHER: the solution vectors are
// meaningless and may not have lengths consistent with the input problem.
struct SolverResult {
Eigen::VectorXd primal_solution;
// See https://developers.google.com/optimization/lp/pdlp_math for the
// interpretation of `dual_solution` and `reduced_costs`.
Eigen::VectorXd dual_solution;
// NOTE: The definition of reduced_costs changed in OR-tools version 9.8.
// See
// https://developers.google.com/optimization/lp/pdlp_math#reduced_costs_dual_residuals_and_the_corrected_dual_objective
// for details.
Eigen::VectorXd reduced_costs;
SolveLog solve_log;
};
// Identifies the iteration type in a callback. The callback is called both for
// intermediate iterations and upon termination.
enum class IterationType {
// An intermediate iteration in the "main" phase.
kNormal,
// An intermediate iteration during a primal feasibility polishing phase.
kPrimalFeasibility,
// An intermediate iteration during a dual feasibility polishing phase.
kDualFeasibility,
// Terminating with a solution found by presolve.
kPresolveTermination,
// Terminating with a solution found by the "main" phase.
kNormalTermination,
// Terminating with a solution found by feasibility polishing.
kFeasibilityPolishingTermination,
};
struct IterationCallbackInfo {
IterationType iteration_type;
TerminationCriteria termination_criteria;
// Note that if feasibility polishing is enabled, `iteration_stats` will only
// describe the iteration within the current phase (main, primal feasibility
// polishing, or dual feasibility polishing), and not the total work.
IterationStats iteration_stats;
QuadraticProgramBoundNorms bound_norms;
};
// Solves the given QP using PDLP (Primal-Dual hybrid gradient enhanced for LP).
//
// All operations that are repeated each iteration are executed in parallel
// using `params.num_threads()` threads.
//
// The algorithm generally follows the description in
// https://arxiv.org/pdf/2106.04756.pdf, with further enhancements for QP.
// Notation here and in the implementation follows Chambolle and Pock, "On the
// ergodic convergence rates of a first-order primal-dual algorithm"
// (http://www.optimization-online.org/DB_FILE/2014/09/4532.pdf).
// That paper doesn't explicitly use the terminology "primal-dual hybrid
// gradient" but their Theorem 1 is analyzing PDHG. See
// https://developers.google.com/optimization/lp/pdlp_math#saddle-point_formulation
// for the saddle-point formulation of the QP we use that is compatible with
// Chambolle and Pock.
//
// We use 0.5 ||.||^2 for both the primal and dual distance functions.
//
// We parameterize the primal and dual step sizes (tau and sigma in Chambolle
// and Pock) as:
// primal_stepsize = step_size / primal_weight
// dual_stepsize = step_size * primal_weight
// where step_size and primal_weight are parameters.
// `params.linesearch_rule` determines the update rule for step_size.
// `params.initial_primal_weight` specifies how primal_weight is initialized
// and `params.primal_weight_update_smoothing` controls how primal_weight is
// updated.
//
// If `interrupt_solve` is not nullptr, then the solver will periodically check
// if `interrupt_solve->load()` is true, in which case the solve will terminate
// with `TERMINATION_REASON_INTERRUPTED_BY_USER`.
//
// If `message_callback` is not nullptr, solver logging will be passed to
// `message_callback`. Otherwise solver logging will be written to stdout.
// In either case, the amount of logging is controlled by `verbosity_level`.
// In particular, if `verbosity_level == 0`, there will be no logging in either
// case.
//
// If `iteration_stats_callback` is not nullptr, then at each termination step
// (when iteration stats are logged), `iteration_stats_callback` will also
// be called with those iteration stats.
//
// Callers MUST check `solve_log.termination_reason` before using the vectors in
// the `SolverResult`. See the comment on `SolverResult` for interpreting the
// termination reason.
//
// All objective values reported by the algorithm are transformed by using
// `QuadraticProgram::ApplyObjectiveScalingAndOffset`.
//
// NOTE: `qp` is intentionally passed by value, because
// `PrimalDualHybridGradient` modifies its copy.
SolverResult PrimalDualHybridGradient(
QuadraticProgram qp, const PrimalDualHybridGradientParams& params,
const std::atomic<bool>* interrupt_solve = nullptr,
std::function<void(const std::string&)> message_callback = nullptr,
std::function<void(const IterationCallbackInfo&)> iteration_stats_callback =
nullptr);
// Like above but optionally starts with the given `initial_solution`. If no
// `initial_solution` is given the zero vector is used. In either case
// `initial_solution` is projected onto the primal and dual variable bounds
// before use. Convergence should be faster if `initial_solution` is close to an
// optimal solution. NOTE: `initial_solution` is intentionally passed by value.
SolverResult PrimalDualHybridGradient(
QuadraticProgram qp, const PrimalDualHybridGradientParams& params,
std::optional<PrimalAndDualSolution> initial_solution,
const std::atomic<bool>* interrupt_solve = nullptr,
std::function<void(const std::string&)> message_callback = nullptr,
std::function<void(const IterationCallbackInfo&)> iteration_stats_callback =
nullptr);
namespace internal {
// Computes variable and constraint statuses. This determines if primal
// variables are at their bounds based on exact comparisons and therefore may
// not work with unscaled solutions. The primal and dual solution in the
// returned `ProblemSolution` are NOT set.
glop::ProblemSolution ComputeStatuses(const QuadraticProgram& qp,
const PrimalAndDualSolution& solution);
} // namespace internal
} // namespace operations_research::pdlp
#endif // PDLP_PRIMAL_DUAL_HYBRID_GRADIENT_H_