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
-## Uno 1.0.0
+## Uno
-->
-Uno 1.0.0 implements the following strategies:
+Uno implements the following strategies:
-
+
**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).
-
+
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