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

301 lines
12 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.
#include "ortools/pdlp/solvers_proto_validation.h"
#include <cmath>
#include <limits>
#include <string>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "ortools/base/status_macros.h"
#include "ortools/pdlp/solvers.pb.h"
namespace operations_research::pdlp {
using ::absl::InvalidArgumentError;
using ::absl::OkStatus;
const double kTinyDouble = 1.0e-50;
const double kHugeDouble = 1.0e50;
absl::Status CheckNonNegative(const double value,
const absl::string_view name) {
if (std::isnan(value)) {
return InvalidArgumentError(absl::StrCat(name, " is NAN"));
}
if (value < 0) {
return InvalidArgumentError(absl::StrCat(name, " must be non-negative"));
}
return absl::OkStatus();
}
absl::Status ValidateTerminationCriteria(const TerminationCriteria& criteria) {
if (criteria.optimality_norm() != OPTIMALITY_NORM_L_INF &&
criteria.optimality_norm() != OPTIMALITY_NORM_L2 &&
criteria.optimality_norm() != OPTIMALITY_NORM_L_INF_COMPONENTWISE) {
return InvalidArgumentError("invalid value for optimality_norm");
}
if (criteria.has_detailed_optimality_criteria() ||
criteria.has_simple_optimality_criteria()) {
if (criteria.has_eps_optimal_absolute()) {
return InvalidArgumentError(
"eps_optimal_absolute should not be set if "
"detailed_optimality_criteria or simple_optimality_criteria is used");
}
if (criteria.has_eps_optimal_relative()) {
return InvalidArgumentError(
"eps_optimal_relative should not be set if "
"detailed_optimality_criteria or simple_optimality_criteria is used");
}
}
if (criteria.has_detailed_optimality_criteria()) {
RETURN_IF_ERROR(CheckNonNegative(
criteria.detailed_optimality_criteria()
.eps_optimal_primal_residual_absolute(),
"detailed_optimality_criteria.eps_optimal_primal_residual_absolute"));
RETURN_IF_ERROR(CheckNonNegative(
criteria.detailed_optimality_criteria()
.eps_optimal_primal_residual_relative(),
"detailed_optimality_criteria.eps_optimal_primal_residual_relative"));
RETURN_IF_ERROR(CheckNonNegative(
criteria.detailed_optimality_criteria()
.eps_optimal_dual_residual_absolute(),
"detailed_optimality_criteria.eps_optimal_dual_residual_absolute"));
RETURN_IF_ERROR(CheckNonNegative(
criteria.detailed_optimality_criteria()
.eps_optimal_dual_residual_relative(),
"detailed_optimality_criteria.eps_optimal_dual_residual_relative"));
RETURN_IF_ERROR(CheckNonNegative(
criteria.detailed_optimality_criteria()
.eps_optimal_objective_gap_absolute(),
"detailed_optimality_criteria.eps_optimal_objective_gap_absolute"));
RETURN_IF_ERROR(CheckNonNegative(
criteria.detailed_optimality_criteria()
.eps_optimal_objective_gap_relative(),
"detailed_optimality_criteria.eps_optimal_objective_gap_relative"));
} else if (criteria.has_simple_optimality_criteria()) {
RETURN_IF_ERROR(CheckNonNegative(
criteria.simple_optimality_criteria().eps_optimal_absolute(),
"simple_optimality_criteria.eps_optimal_absolute"));
RETURN_IF_ERROR(CheckNonNegative(
criteria.simple_optimality_criteria().eps_optimal_relative(),
"simple_optimality_criteria.eps_optimal_relative"));
} else {
RETURN_IF_ERROR(CheckNonNegative(criteria.eps_optimal_absolute(),
"eps_optimal_absolute"));
RETURN_IF_ERROR(CheckNonNegative(criteria.eps_optimal_relative(),
"eps_optimal_relative"));
}
RETURN_IF_ERROR(CheckNonNegative(criteria.eps_primal_infeasible(),
"eps_primal_infeasible"));
RETURN_IF_ERROR(
CheckNonNegative(criteria.eps_dual_infeasible(), "eps_dual_infeasible"));
RETURN_IF_ERROR(
CheckNonNegative(criteria.eps_dual_infeasible(), "eps_dual_infeasible"));
RETURN_IF_ERROR(
CheckNonNegative(criteria.time_sec_limit(), "time_sec_limit"));
if (criteria.iteration_limit() < 0) {
return InvalidArgumentError("iteration_limit must be non-negative");
}
RETURN_IF_ERROR(CheckNonNegative(criteria.kkt_matrix_pass_limit(),
"kkt_matrix_pass_limit"));
return OkStatus();
}
absl::Status ValidateAdaptiveLinesearchParams(
const AdaptiveLinesearchParams& params) {
if (std::isnan(params.step_size_reduction_exponent())) {
return InvalidArgumentError("step_size_reduction_exponent is NAN");
}
if (params.step_size_reduction_exponent() < 0.1 ||
params.step_size_reduction_exponent() > 1.0) {
return InvalidArgumentError(
"step_size_reduction_exponent must be between 0.1 and 1.0 inclusive");
}
if (std::isnan(params.step_size_growth_exponent())) {
return InvalidArgumentError("step_size_growth_exponent is NAN");
}
if (params.step_size_growth_exponent() < 0.1 ||
params.step_size_growth_exponent() > 1.0) {
return InvalidArgumentError(
"step_size_growth_exponent must be between 0.1 and 1.0 inclusive");
}
return OkStatus();
}
absl::Status ValidateMalitskyPockParams(const MalitskyPockParams& params) {
if (std::isnan(params.step_size_downscaling_factor())) {
return InvalidArgumentError("step_size_downscaling_factor is NAN");
}
if (params.step_size_downscaling_factor() <= kTinyDouble ||
params.step_size_downscaling_factor() >= 1) {
return InvalidArgumentError(
absl::StrCat("step_size_downscaling_factor must be between ",
kTinyDouble, " and 1 exclusive"));
}
if (std::isnan(params.linesearch_contraction_factor())) {
return InvalidArgumentError("linesearch_contraction_factor is NAN");
}
if (params.linesearch_contraction_factor() <= 0 ||
params.linesearch_contraction_factor() >= 1) {
return InvalidArgumentError(
"linesearch_contraction_factor must be between 0 and 1 exclusive");
}
if (std::isnan(params.step_size_interpolation())) {
return InvalidArgumentError("step_size_interpolation is NAN");
}
if (params.step_size_interpolation() < 0 ||
params.step_size_interpolation() >= kHugeDouble) {
return InvalidArgumentError(absl::StrCat(
"step_size_interpolation must be non-negative and less than ",
kHugeDouble));
}
return OkStatus();
}
absl::Status ValidatePrimalDualHybridGradientParams(
const PrimalDualHybridGradientParams& params) {
RETURN_IF_ERROR(ValidateTerminationCriteria(params.termination_criteria()))
<< "termination_criteria invalid";
if (params.num_threads() <= 0) {
return InvalidArgumentError("num_threads must be positive");
}
if (params.verbosity_level() < 0) {
return InvalidArgumentError("verbosity_level must be non-negative");
}
if (params.log_interval_seconds() < 0.0) {
return InvalidArgumentError("log_interval_seconds must be non-negative");
}
if (std::isnan(params.log_interval_seconds())) {
return InvalidArgumentError("log_interval_seconds is NAN");
}
if (params.major_iteration_frequency() <= 0) {
return InvalidArgumentError("major_iteration_frequency must be positive");
}
if (params.termination_check_frequency() <= 0) {
return InvalidArgumentError("termination_check_frequency must be positive");
}
if (params.restart_strategy() !=
PrimalDualHybridGradientParams::NO_RESTARTS &&
params.restart_strategy() !=
PrimalDualHybridGradientParams::EVERY_MAJOR_ITERATION &&
params.restart_strategy() !=
PrimalDualHybridGradientParams::ADAPTIVE_HEURISTIC &&
params.restart_strategy() !=
PrimalDualHybridGradientParams::ADAPTIVE_DISTANCE_BASED) {
return InvalidArgumentError("invalid restart_strategy");
}
if (std::isnan(params.primal_weight_update_smoothing())) {
return InvalidArgumentError("primal_weight_update_smoothing is NAN");
}
if (params.primal_weight_update_smoothing() < 0 ||
params.primal_weight_update_smoothing() > 1) {
return InvalidArgumentError(
"primal_weight_update_smoothing must be between 0 and 1 inclusive");
}
if (std::isnan(params.initial_primal_weight())) {
return InvalidArgumentError("initial_primal_weight is NAN");
}
if (params.has_initial_primal_weight() &&
(params.initial_primal_weight() <= kTinyDouble ||
params.initial_primal_weight() >= kHugeDouble)) {
return InvalidArgumentError(
absl::StrCat("initial_primal_weight must be between ", kTinyDouble,
" and ", kHugeDouble, " if specified"));
}
if (params.l_inf_ruiz_iterations() < 0) {
return InvalidArgumentError("l_inf_ruiz_iterations must be non-negative");
}
if (params.l_inf_ruiz_iterations() > 100) {
return InvalidArgumentError("l_inf_ruiz_iterations must be at most 100");
}
if (std::isnan(params.sufficient_reduction_for_restart())) {
return InvalidArgumentError("sufficient_reduction_for_restart is NAN");
}
if (params.sufficient_reduction_for_restart() <= 0 ||
params.sufficient_reduction_for_restart() >= 1) {
return InvalidArgumentError(
"sufficient_reduction_for_restart must be between 0 and 1 exclusive");
}
if (std::isnan(params.necessary_reduction_for_restart())) {
return InvalidArgumentError("necessary_reduction_for_restart is NAN");
}
if (params.necessary_reduction_for_restart() <
params.sufficient_reduction_for_restart() ||
params.necessary_reduction_for_restart() >= 1) {
return InvalidArgumentError(
"necessary_reduction_for_restart must be in the interval "
"[sufficient_reduction_for_restart, 1)");
}
if (params.linesearch_rule() !=
PrimalDualHybridGradientParams::ADAPTIVE_LINESEARCH_RULE &&
params.linesearch_rule() !=
PrimalDualHybridGradientParams::MALITSKY_POCK_LINESEARCH_RULE &&
params.linesearch_rule() !=
PrimalDualHybridGradientParams::CONSTANT_STEP_SIZE_RULE) {
return InvalidArgumentError("invalid linesearch_rule");
}
RETURN_IF_ERROR(
ValidateAdaptiveLinesearchParams(params.adaptive_linesearch_parameters()))
<< "adaptive_linesearch_parameters invalid";
RETURN_IF_ERROR(ValidateMalitskyPockParams(params.malitsky_pock_parameters()))
<< "malitsky_pock_parameters invalid";
if (std::isnan(params.initial_step_size_scaling())) {
return InvalidArgumentError("initial_step_size_scaling is NAN");
}
if (params.initial_step_size_scaling() <= kTinyDouble ||
params.initial_step_size_scaling() >= kHugeDouble) {
return InvalidArgumentError(
absl::StrCat("initial_step_size_scaling must be between ", kTinyDouble,
" and ", kHugeDouble));
}
if (std::isnan(params.infinite_constraint_bound_threshold())) {
return InvalidArgumentError("infinite_constraint_bound_threshold is NAN");
}
if (params.infinite_constraint_bound_threshold() <= 0.0) {
return InvalidArgumentError(
"infinite_constraint_bound_threshold must be positive");
}
if (std::isnan(params.diagonal_qp_trust_region_solver_tolerance())) {
return InvalidArgumentError(
"diagonal_qp_trust_region_solver_tolerance is NAN");
}
if (params.diagonal_qp_trust_region_solver_tolerance() <
10 * std::numeric_limits<double>::epsilon()) {
return InvalidArgumentError(absl::StrCat(
"diagonal_qp_trust_region_solver_tolerance must be at least ",
10 * std::numeric_limits<double>::epsilon()));
}
if (params.use_feasibility_polishing() &&
params.handle_some_primal_gradients_on_finite_bounds_as_residuals()) {
return InvalidArgumentError(
"use_feasibility_polishing requires "
"!handle_some_primal_gradients_on_finite_bounds_as_residuals");
}
if (params.use_feasibility_polishing() &&
params.presolve_options().use_glop()) {
return InvalidArgumentError(
"use_feasibility_polishing and glop presolve can not be used "
"together.");
}
return OkStatus();
}
} // namespace operations_research::pdlp