// 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 #include #include #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::epsilon()) { return InvalidArgumentError(absl::StrCat( "diagonal_qp_trust_region_solver_tolerance must be at least ", 10 * std::numeric_limits::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