Skip to content

Commit

Permalink
Merge pull request #85 from cvanaret/option_precedence
Browse files Browse the repository at this point in the history
Defined precedence rules for setting options
  • Loading branch information
cvanaret authored Nov 14, 2024
2 parents 9e66eec + 6bc215d commit 4d5c16c
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 152 deletions.
43 changes: 26 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ The following hypergraph illustrates how some of the state-of-the-art solvers ca
<img src="docs/figures/combination_hypergraph.png" alt="Combination hypergraph" width="75%" />
</p>
## Uno 1.0.0
## Uno
-->

Uno 1.0.0 implements the following strategies:
Uno implements the following strategies:
<p align="center">
<img src="docs/figures/hypergraph_uno.png" alt="Uno 1.0.0 hypergraph" width="65%" />
<img src="docs/figures/hypergraph_uno.png" alt="Uno hypergraph" width="65%" />
</p>

**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).
Expand All @@ -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).

<p align="center">
<img src="docs/figures/uno_performance_profile.png" alt="Performance profile of Uno 1.1.0" width="75%" />
<img src="docs/figures/uno_performance_profile.png" alt="Performance profile of Uno" width="75%" />
</p>

All log files can be found [here](https://github.com/cvanaret/nonconvex_solver_comparison).
Expand Down Expand Up @@ -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]```
29 changes: 22 additions & 7 deletions bindings/AMPL/uno_ampl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

/*
Expand Down Expand Up @@ -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]);
Expand All @@ -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
Expand Down
12 changes: 1 addition & 11 deletions uno/options/DefaultOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 **/
Expand All @@ -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
6 changes: 4 additions & 2 deletions uno/options/DefaultOptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
#ifndef UNO_DEFAULTOPTIONS_H
#define UNO_DEFAULTOPTIONS_H

#include <optional>
#include <string>
#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
#endif // UNO_DEFAULTOPTIONS_H
134 changes: 22 additions & 112 deletions uno/options/Options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -31,10 +31,25 @@ namespace uno {
}
}

std::optional<std::string> 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<std::string> 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);
Expand All @@ -56,33 +71,26 @@ 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<size_t>(argc); i++) {
for (size_t i = offset; i < static_cast<size_t>(argc); i++) {
const std::string argument = std::string(argv[i]);
size_t position = argument.find_first_of(delimiter);
if (position == std::string::npos) {
throw std::runtime_error("The option " + argument + " does not contain the delimiter " + delimiter);
}
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) {
Expand All @@ -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) {
Expand Down
8 changes: 5 additions & 3 deletions uno/options/Options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <map>
#include <string>
#include <optional>

namespace uno {
class Options {
Expand All @@ -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<std::string> 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;

Expand All @@ -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<std::string> at_optional(const std::string& option_name) const;
};
} // namespace

Expand Down
Loading

0 comments on commit 4d5c16c

Please sign in to comment.