diff --git a/README.md b/README.md index 6a862bdd..99172e00 100644 --- a/README.md +++ b/README.md @@ -37,17 +37,17 @@ The following hypergraph illustrates how some of the state-of-the-art solvers ca Combination hypergraph

-## Uno 1.0.0 +## Uno --> -Uno 1.0.0 implements the following strategies: +Uno implements the following strategies:

- Uno 1.0.0 hypergraph + Uno hypergraph

**Any strategy combination** can be automatically generated without any programming effort from the user. Note that all combinations do not necessarily result in sensible algorithms, or even convergent approaches. For more details, check out our [preprint](https://www.researchgate.net/publication/381522383_Unifying_nonlinearly_constrained_nonconvex_optimization) or my [presentation at the ICCOPT 2022 conference](https://www.researchgate.net/publication/362254109). -Uno 1.0.0 implements three **presets**, that is strategy combinations that correspond to existing solvers (as well as hyperparameter values found in their documentations): +Uno implements three **presets**, that is strategy combinations that correspond to existing solvers (as well as hyperparameter values found in their documentations): * `filtersqp` mimics filterSQP (trust-region feasibility restoration filter SQP method); * `ipopt` mimics IPOPT (line-search feasibility restoration filter barrier method); * `byrd` mimics Byrd's S $\ell_1$ QP (line-search $\ell_1$ merit S $\ell_1$ QP method). @@ -58,7 +58,7 @@ Some of Uno combinations that correspond to existing solvers (called presets, se The figure below is a performance profile of Uno and state-of-the-art solvers filterSQP, IPOPT, SNOPT, MINOS, LANCELOT, LOQO and CONOPT; it shows how many problems are solved for a given budget of function evaluations (1 time, 2 times, 4 times, ..., $2^x$ times the number of objective evaluations of the best solver for each instance).

- Performance profile of Uno 1.1.0 + Performance profile of Uno

All log files can be found [here](https://github.com/cvanaret/nonconvex_solver_comparison). @@ -91,21 +91,30 @@ See the [INSTALL](INSTALL.md) file. ## Solving a problem with Uno -At the moment, Uno only reads models from [.nl files](https://en.wikipedia.org/wiki/Nl_(format)). A couple of CUTEst instances are available in the `/examples` directory. +### Controlling Uno via options -To solve an AMPL model, type in the `build` directory: ```./uno_ampl model.nl -AMPL [key=value ...]``` -where ```[key=value ...]``` is a list of options. +Options can be set in three different ways (with decreasing precedence): +- passing an option file (`option_file=file`) that contains `option value` on each line; +- setting a preset that mimics an existing solver (`preset=[filtersqp|ipopt|byrd]`); +- setting individual options (see the [default options](https://github.com/cvanaret/Uno/blob/main/uno/options/DefaultOptions.cpp)). -To use Uno with Julia/JuMP, a solution in the short term is to use the package [AmplNLWriter.jl](https://juliahub.com/ui/Packages/General/AmplNLWriter.jl) to dump JuMP models into .nl files. +### Interfaces -### Combination of ingredients +#### AMPL/nl files +To solve an AMPL model in the [.nl format](https://en.wikipedia.org/wiki/Nl_(format)), type in the `build` directory: ```./uno_ampl model.nl -AMPL [option=value ...]``` +where ```[option=value ...]``` is a list of options separated by spaces. -To pick a globalization mechanism, use the argument (choose one of the possible options in brackets): ```globalization_mechanism=[LS|TR]``` -To pick a constraint relaxation strategy, use the argument: ```constraint_relaxation_strategy=[feasibility_restoration|l1_relaxation]``` -To pick a globalization strategy, use the argument: ```globalization_strategy=[l1_merit|fletcher_filter_method|waechter_filter_method|funnel_method]``` -To pick a subproblem method, use the argument: ```subproblem=[QP|LP|primal_dual_interior_point]``` -The options can be combined in the same command line. +A couple of CUTEst instances are available in the `/examples` directory. + +#### Julia +Uno can be installed in Julia via [Uno_jll.jl](https://github.com/JuliaBinaryWrappers/Uno_jll.jl) and used via [AmplNLWriter.jl](https://juliahub.com/ui/Packages/General/AmplNLWriter.jl). An example can be found [here](https://discourse.julialang.org/t/the-uno-unifying-nonconvex-optimization-solver/115883/15?u=cvanaret). + +### Combining strategies on the fly + +For an overview of the available strategies, type: ```./uno_ampl --strategies```: +- to pick a globalization mechanism, use the argument : ```globalization_mechanism=[LS|TR]``` +- to pick a constraint relaxation strategy, use the argument: ```constraint_relaxation_strategy=[feasibility_restoration|l1_relaxation]``` +- to pick a globalization strategy, use the argument: ```globalization_strategy=[l1_merit|fletcher_filter_method|waechter_filter_method|funnel_method]``` +- to pick a subproblem method, use the argument: ```subproblem=[QP|LP|primal_dual_interior_point]``` -For an overview of the available strategies, type: ```./uno_ampl --strategies``` -To pick a preset, use the argument: ```preset=[filtersqp|ipopt|byrd]``` diff --git a/bindings/AMPL/uno_ampl.cpp b/bindings/AMPL/uno_ampl.cpp index 7aa89c0b..05237e5d 100644 --- a/bindings/AMPL/uno_ampl.cpp +++ b/bindings/AMPL/uno_ampl.cpp @@ -10,8 +10,9 @@ #include "AMPLModel.hpp" #include "Uno.hpp" #include "model/ModelFactory.hpp" -#include "options/Options.hpp" #include "options/DefaultOptions.hpp" +#include "options/Options.hpp" +#include "options/Presets.hpp" #include "tools/Logger.hpp" /* @@ -91,8 +92,6 @@ int main(int argc, char* argv[]) { } } else if (argc >= 3) { - Options options = DefaultOptions::load(); - // AMPL expects: ./uno_ampl model.nl -AMPL [option_name=option_value, ...] // model name std::string model_name = std::string(argv[1]); @@ -102,12 +101,28 @@ int main(int argc, char* argv[]) { throw std::runtime_error("The second command line argument should be -AMPL"); } - // determine the default solvers based on the available libraries and possibly set a preset - Options solvers_options = DefaultOptions::determine_solvers_and_preset(); + Options options = DefaultOptions::load(); + + // determine the default solvers based on the available libraries + Options solvers_options = DefaultOptions::determine_solvers(); options.overwrite_with(solvers_options); - // overwrite the default options with the command line arguments - Options command_line_options = Options::get_command_line_options(argc, argv); + // get the command line arguments (options start at index 3) + Options command_line_options = Options::get_command_line_options(argc, argv, 3); + + // possibly set options from an option file + const auto optional_option_file = command_line_options.get_string_optional("option_file"); + if (optional_option_file.has_value()) { + Options file_options = Options::load_option_file(optional_option_file.value()); + options.overwrite_with(file_options); + } + + // possibly set a preset + const auto optional_preset = command_line_options.get_string_optional("preset"); + Options preset_options = Presets::get_preset_options(optional_preset); + options.overwrite_with(preset_options); + + // overwrite the options with the command line arguments options.overwrite_with(command_line_options); // solve the model diff --git a/uno/options/DefaultOptions.cpp b/uno/options/DefaultOptions.cpp index f7acf82e..3088bb5c 100644 --- a/uno/options/DefaultOptions.cpp +++ b/uno/options/DefaultOptions.cpp @@ -189,7 +189,7 @@ namespace uno { return options; } - Options DefaultOptions::determine_solvers_and_preset() { + Options DefaultOptions::determine_solvers() { Options options(false); /** solvers: check the available solvers **/ @@ -208,16 +208,6 @@ namespace uno { if (not linear_solvers.empty()) { options["linear_solver"] = linear_solvers[0]; } - - /** default preset **/ - if (not QP_solvers.empty()) { - Options::set_preset(options, "filtersqp"); - } - else if (not linear_solvers.empty()) { - Options::set_preset(options, "ipopt"); - } - // note: byrd is not very robust and is not considered as a default preset - return options; } } // namespace diff --git a/uno/options/DefaultOptions.hpp b/uno/options/DefaultOptions.hpp index cf3ed80e..3c763a45 100644 --- a/uno/options/DefaultOptions.hpp +++ b/uno/options/DefaultOptions.hpp @@ -4,14 +4,16 @@ #ifndef UNO_DEFAULTOPTIONS_H #define UNO_DEFAULTOPTIONS_H +#include +#include #include "Options.hpp" namespace uno { class DefaultOptions { public: [[nodiscard]] static Options load(); - [[nodiscard]] static Options determine_solvers_and_preset(); + [[nodiscard]] static Options determine_solvers(); }; } // namespace -#endif // UNO_DEFAULTOPTIONS_H \ No newline at end of file +#endif // UNO_DEFAULTOPTIONS_H diff --git a/uno/options/Options.cpp b/uno/options/Options.cpp index 6388e8ff..874db138 100644 --- a/uno/options/Options.cpp +++ b/uno/options/Options.cpp @@ -19,7 +19,7 @@ namespace uno { return this->options[option_name]; } - // getter + // getters const std::string& Options::at(const std::string& option_name) const { try { const std::string& option_value = this->options.at(option_name); @@ -31,10 +31,25 @@ namespace uno { } } + std::optional Options::at_optional(const std::string& option_name) const { + try { + const std::string& option_value = this->options.at(option_name); + this->used[option_name] = true; + return option_value; + } + catch(const std::out_of_range&) { + return std::nullopt; + } + } + const std::string& Options::get_string(const std::string& option_name) const { return this->at(option_name); } + std::optional Options::get_string_optional(const std::string& option_name) const { + return this->at_optional(option_name); + } + double Options::get_double(const std::string& option_name) const { const std::string& entry = this->at(option_name); return std::stod(entry); @@ -56,12 +71,12 @@ namespace uno { } // argv[i] for i = 3..argc-1 are overwriting options - Options Options::get_command_line_options(int argc, char* argv[]) { + Options Options::get_command_line_options(int argc, char* argv[], size_t offset) { static const std::string delimiter = "="; Options overwriting_options(false); // build the (name, value) map - for (size_t i = 3; i < static_cast(argc); i++) { + for (size_t i = offset; i < static_cast(argc); i++) { const std::string argument = std::string(argv[i]); size_t position = argument.find_first_of(delimiter); if (position == std::string::npos) { @@ -69,20 +84,13 @@ namespace uno { } const std::string option_name = argument.substr(0, position); const std::string option_value = argument.substr(position + 1); - if (option_name == "preset") { - Options::set_preset(overwriting_options, option_value); - } - else if (option_name == "option_file") { - Options::overwrite_with_option_file(overwriting_options, option_value); - } - else { - overwriting_options[option_name] = option_value; - } + overwriting_options[option_name] = option_value; } return overwriting_options; } - void Options::overwrite_with_option_file(Options& options, const std::string& file_name) { + Options Options::load_option_file(const std::string& file_name) { + Options options(false); std::ifstream file; file.open(file_name); if (!file) { @@ -101,105 +109,7 @@ namespace uno { } file.close(); } - } - - void Options::set_preset(Options& options, const std::string& preset_name) { - // shortcuts for state-of-the-art combinations - if (preset_name == "ipopt") { - options["constraint_relaxation_strategy"] = "feasibility_restoration"; - options["subproblem"] = "primal_dual_interior_point"; - options["globalization_mechanism"] = "LS"; - options["globalization_strategy"] = "waechter_filter_method"; - options["filter_type"] = "standard"; - options["filter_beta"] = "0.99999"; - options["filter_gamma"] = "1e-8"; - options["switching_delta"] = "1"; - options["filter_ubd"] = "1e4"; - options["filter_fact"] = "1e4"; - options["filter_switching_infeasibility_exponent"] = "1.1"; - options["armijo_decrease_fraction"] = "1e-8"; - options["LS_backtracking_ratio"] = "0.5"; - options["LS_min_step_length"] = "5e-7"; - options["barrier_tau_min"] = "0.99"; - options["barrier_damping_factor"] = "1e-5"; - options["l1_constraint_violation_coefficient"] = "1000."; - options["progress_norm"] = "L1"; - options["residual_norm"] = "INF"; - options["scale_functions"] = "yes"; - options["sparse_format"] = "COO"; - options["tolerance"] = "1e-8"; - options["loose_tolerance"] = "1e-6"; - options["loose_tolerance_consecutive_iteration_threshold"] = "15"; - options["switch_to_optimality_requires_linearized_feasibility"] = "no"; - options["LS_scale_duals_with_step_length"] = "yes"; - options["protect_actual_reduction_against_roundoff"] = "yes"; - } - else if (preset_name == "filtersqp") { - options["constraint_relaxation_strategy"] = "feasibility_restoration"; - options["subproblem"] = "QP"; - options["globalization_mechanism"] = "TR"; - options["globalization_strategy"] = "fletcher_filter_method"; - options["filter_type"] = "standard"; - options["progress_norm"] = "L1"; - options["residual_norm"] = "L2"; - options["sparse_format"] = "CSC"; - options["TR_radius"] = "10"; - options["l1_constraint_violation_coefficient"] = "1."; - options["enforce_linear_constraints"] = "yes"; - options["tolerance"] = "1e-6"; - options["loose_tolerance"] = "1e-6"; - options["TR_min_radius"] = "1e-8"; - options["switch_to_optimality_requires_linearized_feasibility"] = "yes"; - options["protect_actual_reduction_against_roundoff"] = "no"; - } - else if (preset_name == "byrd") { - options["constraint_relaxation_strategy"] = "l1_relaxation"; - options["subproblem"] = "QP"; - options["globalization_mechanism"] = "LS"; - options["globalization_strategy"] = "l1_merit"; - options["l1_relaxation_initial_parameter"] = "1"; - options["LS_backtracking_ratio"] = "0.5"; - options["armijo_decrease_fraction"] = "1e-8"; - options["l1_relaxation_epsilon1"] = "0.1"; - options["l1_relaxation_epsilon2"] = "0.1"; - options["l1_constraint_violation_coefficient"] = "1."; - options["tolerance"] = "1e-6"; - options["loose_tolerance"] = "1e-6"; - options["progress_norm"] = "L1"; - options["residual_norm"] = "L1"; - options["sparse_format"] = "CSC"; - options["LS_scale_duals_with_step_length"] = "no"; - options["protect_actual_reduction_against_roundoff"] = "no"; - } - else if (preset_name == "funnelsqp") { - options["constraint_relaxation_strategy"] = "feasibility_restoration"; - options["subproblem"] = "QP"; - options["globalization_mechanism"] = "TR"; - options["globalization_strategy"] = "funnel_method"; - options["progress_norm"] = "L1"; - options["residual_norm"] = "L2"; - options["sparse_format"] = "CSC"; - options["TR_radius"] = "10"; - options["l1_constraint_violation_coefficient"] = "1."; - options["enforce_linear_constraints"] = "yes"; - options["tolerance"] = "1e-6"; - options["loose_tolerance"] = "1e-6"; - options["TR_min_radius"] = "1e-8"; - options["switch_to_optimality_requires_acceptance"] = "no"; - options["switch_to_optimality_requires_linearized_feasibility"] = "yes"; - - options["funnel_beta"] = "0.9999"; - options["funnel_gamma"] = "0.001"; - options["switching_delta"] = "0.999"; - options["funnel_kappa"] = "0.5"; - options["funnel_ubd"] = "1.0"; - options["funnel_fact"] = "1.5"; - options["funnel_switching_infeasibility_exponent"] = "2"; - options["funnel_update_strategy"] = "2"; - } - else { - throw std::runtime_error("The preset " + preset_name + " is not known"); - } + return options; } void Options::overwrite_with(const Options& overwriting_options) { diff --git a/uno/options/Options.hpp b/uno/options/Options.hpp index 13d99db8..948160d4 100644 --- a/uno/options/Options.hpp +++ b/uno/options/Options.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace uno { class Options { @@ -16,14 +17,14 @@ namespace uno { std::string& operator[](const std::string& option_name); [[nodiscard]] const std::string& get_string(const std::string& option_name) const; + [[nodiscard]] std::optional get_string_optional(const std::string& option_name) const; [[nodiscard]] double get_double(const std::string& option_name) const; [[nodiscard]] int get_int(const std::string& option_name) const; [[nodiscard]] size_t get_unsigned_int(const std::string& option_name) const; [[nodiscard]] bool get_bool(const std::string& option_name) const; - [[nodiscard]] static Options get_command_line_options(int argc, char* argv[]); - static void overwrite_with_option_file(Options& options, const std::string& file_name); - static void set_preset(Options& options, const std::string& preset_name); + [[nodiscard]] static Options get_command_line_options(int argc, char* argv[], size_t offset); + [[nodiscard]] static Options load_option_file(const std::string& file_name); void overwrite_with(const Options& overwriting_options); void print_used() const; @@ -37,6 +38,7 @@ namespace uno { const bool are_default_options; [[nodiscard]] const std::string& at(const std::string& option_name) const; + [[nodiscard]] std::optional at_optional(const std::string& option_name) const; }; } // namespace diff --git a/uno/options/Presets.cpp b/uno/options/Presets.cpp new file mode 100644 index 00000000..a2d3e5c2 --- /dev/null +++ b/uno/options/Presets.cpp @@ -0,0 +1,132 @@ +// Copyright (c) 2024 Charlie Vanaret +// Licensed under the MIT license. See LICENSE file in the project directory for details. + +#include +#include "Presets.hpp" +#include "Options.hpp" +#include "solvers/QPSolverFactory.hpp" +#include "solvers/SymmetricIndefiniteLinearSolverFactory.hpp" + +namespace uno { + Options Presets::get_preset_options(const std::optional& optional_preset) { + Options options(false); + + /** optional user preset **/ + if (optional_preset.has_value()) { + Presets::set(options, optional_preset.value()); + } + else { + /** default preset **/ + const auto QP_solvers = QPSolverFactory::available_solvers(); + const auto linear_solvers = SymmetricIndefiniteLinearSolverFactory::available_solvers(); + + if (not QP_solvers.empty()) { + Presets::set(options, "filtersqp"); + } + else if (not linear_solvers.empty()) { + Presets::set(options, "ipopt"); + } + // note: byrd is not very robust and is not considered as a default preset + } + return options; + } + + void Presets::set(Options& options, const std::string& preset_name) { + // shortcuts for state-of-the-art combinations + if (preset_name == "ipopt") { + options["constraint_relaxation_strategy"] = "feasibility_restoration"; + options["subproblem"] = "primal_dual_interior_point"; + options["globalization_mechanism"] = "LS"; + options["globalization_strategy"] = "waechter_filter_method"; + options["filter_type"] = "standard"; + options["filter_beta"] = "0.99999"; + options["filter_gamma"] = "1e-8"; + options["switching_delta"] = "1"; + options["filter_ubd"] = "1e4"; + options["filter_fact"] = "1e4"; + options["filter_switching_infeasibility_exponent"] = "1.1"; + options["armijo_decrease_fraction"] = "1e-8"; + options["LS_backtracking_ratio"] = "0.5"; + options["LS_min_step_length"] = "5e-7"; + options["barrier_tau_min"] = "0.99"; + options["barrier_damping_factor"] = "1e-5"; + options["l1_constraint_violation_coefficient"] = "1000."; + options["progress_norm"] = "L1"; + options["residual_norm"] = "INF"; + options["scale_functions"] = "yes"; + options["sparse_format"] = "COO"; + options["tolerance"] = "1e-8"; + options["loose_tolerance"] = "1e-6"; + options["loose_tolerance_consecutive_iteration_threshold"] = "15"; + options["switch_to_optimality_requires_linearized_feasibility"] = "no"; + options["LS_scale_duals_with_step_length"] = "yes"; + options["protect_actual_reduction_against_roundoff"] = "yes"; + } + else if (preset_name == "filtersqp") { + options["constraint_relaxation_strategy"] = "feasibility_restoration"; + options["subproblem"] = "QP"; + options["globalization_mechanism"] = "TR"; + options["globalization_strategy"] = "fletcher_filter_method"; + options["filter_type"] = "standard"; + options["progress_norm"] = "L1"; + options["residual_norm"] = "L2"; + options["sparse_format"] = "CSC"; + options["TR_radius"] = "10"; + options["l1_constraint_violation_coefficient"] = "1."; + options["enforce_linear_constraints"] = "yes"; + options["tolerance"] = "1e-6"; + options["loose_tolerance"] = "1e-6"; + options["TR_min_radius"] = "1e-8"; + options["switch_to_optimality_requires_linearized_feasibility"] = "yes"; + options["protect_actual_reduction_against_roundoff"] = "no"; + } + else if (preset_name == "byrd") { + options["constraint_relaxation_strategy"] = "l1_relaxation"; + options["subproblem"] = "QP"; + options["globalization_mechanism"] = "LS"; + options["globalization_strategy"] = "l1_merit"; + options["l1_relaxation_initial_parameter"] = "1"; + options["LS_backtracking_ratio"] = "0.5"; + options["armijo_decrease_fraction"] = "1e-8"; + options["l1_relaxation_epsilon1"] = "0.1"; + options["l1_relaxation_epsilon2"] = "0.1"; + options["l1_constraint_violation_coefficient"] = "1."; + options["tolerance"] = "1e-6"; + options["loose_tolerance"] = "1e-6"; + options["progress_norm"] = "L1"; + options["residual_norm"] = "L1"; + options["sparse_format"] = "CSC"; + options["LS_scale_duals_with_step_length"] = "no"; + options["protect_actual_reduction_against_roundoff"] = "no"; + } + else if (preset_name == "funnelsqp") { + options["constraint_relaxation_strategy"] = "feasibility_restoration"; + options["subproblem"] = "QP"; + options["globalization_mechanism"] = "TR"; + options["globalization_strategy"] = "funnel_method"; + options["progress_norm"] = "L1"; + options["residual_norm"] = "L2"; + options["sparse_format"] = "CSC"; + options["TR_radius"] = "10"; + options["l1_constraint_violation_coefficient"] = "1."; + options["enforce_linear_constraints"] = "yes"; + options["tolerance"] = "1e-6"; + options["loose_tolerance"] = "1e-6"; + options["TR_min_radius"] = "1e-8"; + options["switch_to_optimality_requires_acceptance"] = "no"; + options["switch_to_optimality_requires_linearized_feasibility"] = "yes"; + + options["funnel_beta"] = "0.9999"; + options["funnel_gamma"] = "0.001"; + options["switching_delta"] = "0.999"; + options["funnel_kappa"] = "0.5"; + options["funnel_ubd"] = "1.0"; + options["funnel_fact"] = "1.5"; + options["funnel_switching_infeasibility_exponent"] = "2"; + options["funnel_update_strategy"] = "2"; + } + else { + throw std::runtime_error("The preset " + preset_name + " is not known"); + } + } +} // namespace \ No newline at end of file diff --git a/uno/options/Presets.hpp b/uno/options/Presets.hpp new file mode 100644 index 00000000..35130abb --- /dev/null +++ b/uno/options/Presets.hpp @@ -0,0 +1,21 @@ +// Copyright (c) 2024 Charlie Vanaret +// Licensed under the MIT license. See LICENSE file in the project directory for details. + +#ifndef UNO_PRESETS_H +#define UNO_PRESETS_H + +#include +#include + +namespace uno { + // forward declaration + class Options; + + class Presets { + public: + [[nodiscard]] static Options get_preset_options(const std::optional& optional_preset); + static void set(Options& options, const std::string& preset_name); + }; +} // namespace + +#endif // UNO_PRESETS_H