Skip to content

Commit

Permalink
Merge pull request #110 from cvanaret/statuses
Browse files Browse the repository at this point in the history
Separated iterate status and optimization status
  • Loading branch information
cvanaret authored Nov 25, 2024
2 parents 95214a6 + 9db19bd commit 6757d33
Show file tree
Hide file tree
Showing 25 changed files with 171 additions and 96 deletions.
14 changes: 7 additions & 7 deletions bindings/AMPL/AMPLModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,23 +357,23 @@ namespace uno {
std::copy(this->asl->i.pi0_, this->asl->i.pi0_ + this->number_constraints, multipliers.begin());
}

void AMPLModel::postprocess_solution(Iterate& iterate, TerminationStatus termination_status) const {
void AMPLModel::postprocess_solution(Iterate& iterate, IterateStatus iterate_status) const {
if (this->write_solution_to_file) {
// write the primal-dual solution and status into a *.sol file
this->asl->p.solve_code_ = 400; // limit
if (termination_status == TerminationStatus::FEASIBLE_KKT_POINT) {
if (iterate_status == IterateStatus::FEASIBLE_KKT_POINT) {
this->asl->p.solve_code_ = 0;
}
if (termination_status == TerminationStatus::FEASIBLE_SMALL_STEP) {
if (iterate_status == IterateStatus::FEASIBLE_SMALL_STEP) {
this->asl->p.solve_code_ = 100;
}
else if (termination_status == TerminationStatus::INFEASIBLE_STATIONARY_POINT) {
else if (iterate_status == IterateStatus::INFEASIBLE_STATIONARY_POINT) {
this->asl->p.solve_code_ = 200;
}
else if (termination_status == TerminationStatus::UNBOUNDED) {
else if (iterate_status == IterateStatus::UNBOUNDED) {
this->asl->p.solve_code_ = 300;
}
else if (termination_status == TerminationStatus::INFEASIBLE_SMALL_STEP) {
else if (iterate_status == IterateStatus::INFEASIBLE_SMALL_STEP) {
this->asl->p.solve_code_ = 500;
}

Expand All @@ -394,7 +394,7 @@ namespace uno {
Option_Info option_info{};
option_info.wantsol = 9; // write the solution without printing the message to stdout
std::string message = "Uno ";
message.append(Uno::current_version()).append(": ").append(status_to_message(termination_status));
message.append(Uno::current_version()).append(": ").append(iterate_status_to_message(iterate_status));
write_sol_ASL(this->asl, message.data(), iterate.primals.data(), iterate.multipliers.constraints.data(), &option_info);
}
}
Expand Down
2 changes: 1 addition & 1 deletion bindings/AMPL/AMPLModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ namespace uno {

void initial_primal_point(Vector<double>& x) const override;
void initial_dual_point(Vector<double>& multipliers) const override;
void postprocess_solution(Iterate& iterate, TerminationStatus termination_status) const override;
void postprocess_solution(Iterate& iterate, IterateStatus iterate_status) const override;

[[nodiscard]] size_t number_objective_gradient_nonzeros() const override;
[[nodiscard]] size_t number_jacobian_nonzeros() const override;
Expand Down
34 changes: 25 additions & 9 deletions uno/Uno.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "solvers/LPSolverFactory.hpp"
#include "solvers/SymmetricIndefiniteLinearSolverFactory.hpp"
#include "tools/Logger.hpp"
#include "optimization/OptimizationStatus.hpp"
#include "options/Options.hpp"
#include "tools/Statistics.hpp"
#include "tools/Timer.hpp"
Expand Down Expand Up @@ -44,6 +45,7 @@ namespace uno {
Statistics statistics = Uno::create_statistics(model, options);
WarmstartInformation warmstart_information{};
warmstart_information.whole_problem_changed();
OptimizationStatus optimization_status = OptimizationStatus::SUCCESS;

try {
// use the initial primal-dual point to initialize the strategies and generate the initial iterate
Expand All @@ -64,7 +66,7 @@ namespace uno {
// compute an acceptable iterate by solving a subproblem at the current point
warmstart_information.iterate_changed();
this->globalization_mechanism.compute_next_iterate(statistics, model, current_iterate, trial_iterate, warmstart_information, user_callbacks);
termination = this->termination_criteria(trial_iterate.status, major_iterations, timer.get_duration());
termination = this->termination_criteria(trial_iterate.status, major_iterations, timer.get_duration(), optimization_status);
user_callbacks.notify_new_primals(trial_iterate.primals);
user_callbacks.notify_new_multipliers(trial_iterate.multipliers);

Expand All @@ -77,15 +79,17 @@ namespace uno {
statistics.set("status", exception.what());
if (Logger::level == INFO) statistics.print_current_line();
DEBUG << exception.what() << '\n';
optimization_status = OptimizationStatus::ALGORITHMIC_ERROR;
}
if (Logger::level == INFO) statistics.print_footer();

Uno::postprocess_iterate(model, current_iterate, current_iterate.status);
Result result = this->create_result(model, current_iterate, major_iterations, timer);
Result result = this->create_result(model, optimization_status, current_iterate, major_iterations, timer);
this->print_optimization_summary(result);
}
catch (const std::exception& e) {
DISCRETE << "An error occurred at the initial iterate: " << e.what() << '\n';
optimization_status = OptimizationStatus::EVALUATION_ERROR;
}
}

Expand All @@ -96,7 +100,7 @@ namespace uno {
this->globalization_mechanism.initialize(statistics, current_iterate, options);
options.print_used();
if (Logger::level == INFO) statistics.print_current_line();
current_iterate.status = TerminationStatus::NOT_OPTIMAL;
current_iterate.status = IterateStatus::NOT_OPTIMAL;
}

Statistics Uno::create_statistics(const Model& model, const Options& options) {
Expand All @@ -113,22 +117,34 @@ namespace uno {
return statistics;
}

bool Uno::termination_criteria(TerminationStatus current_status, size_t iteration, double current_time) const {
return current_status != TerminationStatus::NOT_OPTIMAL || this->max_iterations <= iteration || this->time_limit <= current_time;
bool Uno::termination_criteria(IterateStatus current_status, size_t iteration, double current_time, OptimizationStatus& optimization_status) const {
if (current_status != IterateStatus::NOT_OPTIMAL) {
return true;
}
else if (this->max_iterations <= iteration) {
optimization_status = OptimizationStatus::ITERATION_LIMIT;
return true;
}
else if (this->time_limit <= current_time) {
optimization_status = OptimizationStatus::TIME_LIMIT;
return true;
}
return false;
}

void Uno::postprocess_iterate(const Model& model, Iterate& iterate, TerminationStatus termination_status) {
void Uno::postprocess_iterate(const Model& model, Iterate& iterate, IterateStatus termination_status) {
// in case the objective was not yet evaluated, evaluate it
iterate.evaluate_objective(model);
model.postprocess_solution(iterate, termination_status);
DEBUG2 << "Final iterate:\n" << iterate;
}

Result Uno::create_result(const Model& model, Iterate& current_iterate, size_t major_iterations, const Timer& timer) {
Result Uno::create_result(const Model& model, OptimizationStatus optimization_status, Iterate& current_iterate, size_t major_iterations,
const Timer& timer) {
const size_t number_subproblems_solved = this->globalization_mechanism.get_number_subproblems_solved();
const size_t number_hessian_evaluations = this->globalization_mechanism.get_hessian_evaluation_count();
return {std::move(current_iterate), model.number_variables, model.number_constraints, major_iterations, timer.get_duration(),
Iterate::number_eval_objective, Iterate::number_eval_constraints, Iterate::number_eval_objective_gradient,
return {optimization_status, std::move(current_iterate), model.number_variables, model.number_constraints, major_iterations,
timer.get_duration(), Iterate::number_eval_objective, Iterate::number_eval_constraints, Iterate::number_eval_objective_gradient,
Iterate::number_eval_jacobian, number_hessian_evaluations, number_subproblems_solved};
}

Expand Down
10 changes: 6 additions & 4 deletions uno/Uno.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#define UNO_H

#include "optimization/Result.hpp"
#include "optimization/TerminationStatus.hpp"
#include "optimization/IterateStatus.hpp"

namespace uno {
// forward declarations
Expand Down Expand Up @@ -38,9 +38,11 @@ namespace uno {

void initialize(Statistics& statistics, Iterate& current_iterate, const Options& options);
[[nodiscard]] static Statistics create_statistics(const Model& model, const Options& options);
[[nodiscard]] bool termination_criteria(TerminationStatus current_status, size_t iteration, double current_time) const;
static void postprocess_iterate(const Model& model, Iterate& iterate, TerminationStatus termination_status);
[[nodiscard]] Result create_result(const Model& model, Iterate& current_iterate, size_t major_iterations, const Timer& timer);
[[nodiscard]] bool termination_criteria(IterateStatus current_status, size_t iteration, double current_time,
OptimizationStatus& optimization_status) const;
static void postprocess_iterate(const Model& model, Iterate& iterate, IterateStatus termination_status);
[[nodiscard]] Result create_result(const Model& model, OptimizationStatus optimization_status, Iterate& current_iterate,
size_t major_iterations, const Timer& timer);
};
} // namespace

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,40 +165,40 @@ namespace uno {
}
}

TerminationStatus ConstraintRelaxationStrategy::check_termination(Iterate& iterate) {
IterateStatus ConstraintRelaxationStrategy::check_termination(Iterate& iterate) {
if (iterate.is_objective_computed && iterate.evaluations.objective < this->unbounded_objective_threshold) {
return TerminationStatus::UNBOUNDED;
return IterateStatus::UNBOUNDED;
}

// compute the residuals
this->compute_primal_dual_residuals(iterate);

// test convergence wrt the tight tolerance
const TerminationStatus status_tight_tolerance = this->check_first_order_convergence(iterate, this->tight_tolerance);
if (status_tight_tolerance != TerminationStatus::NOT_OPTIMAL || this->loose_tolerance <= this->tight_tolerance) {
const IterateStatus status_tight_tolerance = this->check_first_order_convergence(iterate, this->tight_tolerance);
if (status_tight_tolerance != IterateStatus::NOT_OPTIMAL || this->loose_tolerance <= this->tight_tolerance) {
return status_tight_tolerance;
}

// if not converged, check convergence wrt loose tolerance (provided it is strictly looser than the tight tolerance)
const TerminationStatus status_loose_tolerance = this->check_first_order_convergence(iterate, this->loose_tolerance);
const IterateStatus status_loose_tolerance = this->check_first_order_convergence(iterate, this->loose_tolerance);
// if converged, keep track of the number of consecutive iterations
if (status_loose_tolerance != TerminationStatus::NOT_OPTIMAL) {
if (status_loose_tolerance != IterateStatus::NOT_OPTIMAL) {
this->loose_tolerance_consecutive_iterations++;
}
else {
this->loose_tolerance_consecutive_iterations = 0;
return TerminationStatus::NOT_OPTIMAL;
return IterateStatus::NOT_OPTIMAL;
}
// check if loose tolerance achieved for enough consecutive iterations
if (this->loose_tolerance_consecutive_iteration_threshold <= this->loose_tolerance_consecutive_iterations) {
return status_loose_tolerance;
}
else {
return TerminationStatus::NOT_OPTIMAL;
return IterateStatus::NOT_OPTIMAL;
}
}

TerminationStatus ConstraintRelaxationStrategy::check_first_order_convergence(Iterate& current_iterate, double tolerance) const {
IterateStatus ConstraintRelaxationStrategy::check_first_order_convergence(Iterate& current_iterate, double tolerance) const {
// evaluate termination conditions based on optimality conditions
const bool stationarity = (current_iterate.residuals.stationarity / current_iterate.residuals.stationarity_scaling <= tolerance);
const bool primal_feasibility = (current_iterate.primal_feasibility <= tolerance);
Expand All @@ -219,13 +219,13 @@ namespace uno {

if (stationarity && primal_feasibility && 0. < current_iterate.objective_multiplier && complementarity) {
// feasible regular stationary point
return TerminationStatus::FEASIBLE_KKT_POINT;
return IterateStatus::FEASIBLE_KKT_POINT;
}
else if (this->model.is_constrained() && feasibility_stationarity && not primal_feasibility && feasibility_complementarity && no_trivial_duals) {
// no primal feasibility, stationary point of constraint violation
return TerminationStatus::INFEASIBLE_STATIONARY_POINT;
return IterateStatus::INFEASIBLE_STATIONARY_POINT;
}
return TerminationStatus::NOT_OPTIMAL;
return IterateStatus::NOT_OPTIMAL;
}

void ConstraintRelaxationStrategy::set_statistics(Statistics& statistics, const Iterate& iterate) const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include <cstddef>
#include <memory>
#include "linear_algebra/Norm.hpp"
#include "optimization/TerminationStatus.hpp"
#include "optimization/IterateStatus.hpp"

namespace uno {
// forward declarations
Expand Down Expand Up @@ -52,7 +52,7 @@ namespace uno {
// trial iterate acceptance
[[nodiscard]] virtual bool is_iterate_acceptable(Statistics& statistics, Iterate& current_iterate, Iterate& trial_iterate, const Direction& direction,
double step_length, WarmstartInformation& warmstart_information, UserCallbacks& user_callbacks) = 0;
[[nodiscard]] TerminationStatus check_termination(Iterate& iterate);
[[nodiscard]] IterateStatus check_termination(Iterate& iterate);

// primal-dual residuals
virtual void compute_primal_dual_residuals(Iterate& iterate) = 0;
Expand Down Expand Up @@ -92,7 +92,7 @@ namespace uno {
[[nodiscard]] double compute_stationarity_scaling(const Multipliers& multipliers) const;
[[nodiscard]] double compute_complementarity_scaling(const Multipliers& multipliers) const;

[[nodiscard]] TerminationStatus check_first_order_convergence(Iterate& current_iterate, double tolerance) const;
[[nodiscard]] IterateStatus check_first_order_convergence(Iterate& current_iterate, double tolerance) const;

void set_statistics(Statistics& statistics, const Iterate& iterate) const;
void set_progress_statistics(Statistics& statistics, const Iterate& iterate) const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ namespace uno {
bool BacktrackingLineSearch::terminate_with_small_step_length(Statistics& statistics, Iterate& trial_iterate) {
bool termination = false;
trial_iterate.status = this->constraint_relaxation_strategy.check_termination(trial_iterate);
if (trial_iterate.status != TerminationStatus::NOT_OPTIMAL) {
if (trial_iterate.status != IterateStatus::NOT_OPTIMAL) {
statistics.set("status", "accepted (small step length)");
this->constraint_relaxation_strategy.set_dual_residuals_statistics(statistics, trial_iterate);
termination = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ namespace uno {
trial_iterate.is_objective_gradient_computed = false;
trial_iterate.are_constraints_computed = false;
trial_iterate.is_constraint_jacobian_computed = false;
trial_iterate.status = TerminationStatus::NOT_OPTIMAL;
trial_iterate.status = IterateStatus::NOT_OPTIMAL;
}

size_t GlobalizationMechanism::get_hessian_evaluation_count() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,12 @@ namespace uno {
bool TrustRegionStrategy::check_termination_with_small_step(Iterate& trial_iterate) const {
// terminate with a feasible point
if (trial_iterate.progress.infeasibility <= this->tolerance) {
trial_iterate.status = TerminationStatus::FEASIBLE_SMALL_STEP;
trial_iterate.status = IterateStatus::FEASIBLE_SMALL_STEP;
this->constraint_relaxation_strategy.compute_primal_dual_residuals(trial_iterate);
return true;
}
else if (this->constraint_relaxation_strategy.solving_feasibility_problem()) { // terminate with an infeasible point
trial_iterate.status = TerminationStatus::INFEASIBLE_SMALL_STEP;
trial_iterate.status = IterateStatus::INFEASIBLE_SMALL_STEP;
this->constraint_relaxation_strategy.compute_primal_dual_residuals(trial_iterate);
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion uno/model/BoundRelaxedModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ namespace uno {

void initial_primal_point(Vector<double>& x) const override { this->model->initial_primal_point(x); }
void initial_dual_point(Vector<double>& multipliers) const override { this->model->initial_dual_point(multipliers); }
void postprocess_solution(Iterate& iterate, TerminationStatus termination_status) const override {
void postprocess_solution(Iterate& iterate, IterateStatus termination_status) const override {
this->model->postprocess_solution(iterate, termination_status);
}

Expand Down
2 changes: 1 addition & 1 deletion uno/model/FixedBoundsConstraintsModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ namespace uno {
this->model->initial_dual_point(multipliers);
}

void FixedBoundsConstraintsModel::postprocess_solution(Iterate& iterate, TerminationStatus termination_status) const {
void FixedBoundsConstraintsModel::postprocess_solution(Iterate& iterate, IterateStatus termination_status) const {
// move the multipliers back from the general constraints to the bound constraints
size_t current_constraint = this->model->number_constraints;
for (size_t variable_index: this->model->get_fixed_variables()) {
Expand Down
2 changes: 1 addition & 1 deletion uno/model/FixedBoundsConstraintsModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ namespace uno {
void initial_primal_point(Vector<double>& x) const override;
void initial_dual_point(Vector<double>& multipliers) const override;

void postprocess_solution(Iterate& iterate, TerminationStatus termination_status) const override;
void postprocess_solution(Iterate& iterate, IterateStatus termination_status) const override;

[[nodiscard]] size_t number_objective_gradient_nonzeros() const override;
[[nodiscard]] size_t number_jacobian_nonzeros() const override;
Expand Down
2 changes: 1 addition & 1 deletion uno/model/HomogeneousEqualityConstrainedModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ namespace uno {
this->model->initial_dual_point(multipliers);
}

void HomogeneousEqualityConstrainedModel::postprocess_solution(Iterate& iterate, TerminationStatus termination_status) const {
void HomogeneousEqualityConstrainedModel::postprocess_solution(Iterate& iterate, IterateStatus termination_status) const {
// discard the slacks
iterate.number_variables = this->model->number_variables;
this->model->postprocess_solution(iterate, termination_status);
Expand Down
2 changes: 1 addition & 1 deletion uno/model/HomogeneousEqualityConstrainedModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ namespace uno {

void initial_primal_point(Vector<double>& x) const override;
void initial_dual_point(Vector<double>& multipliers) const override;
void postprocess_solution(Iterate& iterate, TerminationStatus termination_status) const override;
void postprocess_solution(Iterate& iterate, IterateStatus termination_status) const override;

[[nodiscard]] size_t number_objective_gradient_nonzeros() const override;
[[nodiscard]] size_t number_jacobian_nonzeros() const override;
Expand Down
4 changes: 2 additions & 2 deletions uno/model/Model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include <string>
#include <vector>
#include "linear_algebra/Norm.hpp"
#include "optimization/TerminationStatus.hpp"
#include "optimization/IterateStatus.hpp"
#include "symbolic/VectorExpression.hpp"

namespace uno {
Expand Down Expand Up @@ -76,7 +76,7 @@ namespace uno {

virtual void initial_primal_point(Vector<double>& x) const = 0;
virtual void initial_dual_point(Vector<double>& multipliers) const = 0;
virtual void postprocess_solution(Iterate& iterate, TerminationStatus termination_status) const = 0;
virtual void postprocess_solution(Iterate& iterate, IterateStatus termination_status) const = 0;

[[nodiscard]] virtual size_t number_objective_gradient_nonzeros() const = 0;
[[nodiscard]] virtual size_t number_jacobian_nonzeros() const = 0;
Expand Down
Loading

0 comments on commit 6757d33

Please sign in to comment.