Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.22)
project(idol VERSION 0.9.5)
project(idol VERSION 0.9.6)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${idol_SOURCE_DIR}/cmake")
Expand Down
2 changes: 1 addition & 1 deletion bin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ add_executable(idol_cl
idol_cl.cpp
solve_milp.h
solve_bilevel.h
solve_robust.h
solve_adjustable_robust.h
output.h
method-managers/MethodManager.cpp
method-managers/MethodManager.h
Expand Down
6 changes: 3 additions & 3 deletions bin/idol_cl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#include "Arguments.h"
#include "solve_milp.h"
#include "solve_robust.h"
#include "solve_adjustable_robust.h"
#include "solve_bilevel.h"

int main(int t_argc, const char** t_argv) {
Expand All @@ -15,8 +15,8 @@ int main(int t_argc, const char** t_argv) {
switch (args.problem_type) {
case MILP: solve_milp(args); break;
case BilevelProblem: solve_bilevel(args); break;
case RobustProblem: throw idol::Exception("Not available yet.");
case AdjustableRobustProblem: solve_adjustable_robust(args); break;
case RobustProblem: [[fallthrough]];
case AdjustableRobustProblem: solve_robust(args); break;
default: throw std::runtime_error("Sorry, an error occurred... Undefined problem type.");
}

Expand Down
10 changes: 7 additions & 3 deletions bin/method-managers/MILPMethodManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ class MILPMethodManager : public MethodManager {
return std::move(result);
}

std::unique_ptr<idol::OptimizerFactory> get_sub_milp_optimizer(const Arguments& t_args, bool t_continuous_relaxation = false) {
std::string get_sub_milp_method(const Arguments& t_args) {

if (t_args.default_milp_method.empty()) {
return operator()(get_default_method(), t_continuous_relaxation);
return get_default_method();
}

const auto it = m_all_methods.find(t_args.default_milp_method);
Expand All @@ -100,7 +100,11 @@ class MILPMethodManager : public MethodManager {
throw idol::Exception("The requested sub-MILP method exists, but is not available in this context.");
}

return operator()(t_args.default_milp_method, t_continuous_relaxation);
return t_args.default_milp_method;
}

std::unique_ptr<idol::OptimizerFactory> get_sub_milp_optimizer(const Arguments& t_args, bool t_continuous_relaxation = false) {
return operator()(get_sub_milp_method(t_args), t_continuous_relaxation);
}
};

Expand Down
102 changes: 82 additions & 20 deletions bin/solve_robust.h → bin/solve_adjustable_robust.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,64 @@
#include "idol/bilevel/modeling/read_from_file.h"
#include "idol/bilevel/optimizers/KKT/KKT.h"
#include "idol/bilevel/optimizers/wrappers/MibS/MibS.h"
#include "idol/mixed-integer/optimizers/wrappers/Gurobi/Gurobi.h"
#include "idol/robust/modeling/read_from_file.h"
#include "idol/robust/optimizers/column-and-constraint-generation/ColumnAndConstraintGeneration.h"
#include "idol/robust/optimizers/column-and-constraint-generation/separation/BigMFreeSeparation.h"
#include "idol/robust/optimizers/column-and-constraint-generation/separation/FeasibilitySeparation.h"
#include "idol/robust/optimizers/column-and-constraint-generation/separation/OptimalitySeparation.h"
#include "idol/robust/optimizers/critical-value-column-and-constraint-generation/CriticalValueColumnAndConstraintGeneration.h"

class RobustMethodManager : public MethodManager {
class AdjustableRobustMethodManager : public MethodManager {
public:
RobustMethodManager() : MethodManager({
AdjustableRobustMethodManager() : MethodManager({
/*** Adjustable robust problems ***/
{ "CCG-FARKAS", { 50, "Column-and-constraint generation with Farkas-based separation; see Ayoub and Poss (2016) [https://doi.org/10.1007/s10287-016-0249-2]." } },
{ "CCG-MIBS", { 25, "Column-and-constraint generation with separation by MibS." } },
{ "CCG-KKT-SOS1", { 20, "Column-and-constraint generation with KKT-based separation using SOS1." } },
//{ "YASOL", { 15, "Quantified programming formulation solved with Yasol; see Goerigk and Hartisch (2021) [https://doi.org/10.1016/j.cor.2021.105434]." } },
//{ "BBBB-MIBS", { 5, "Bilevel-based branch-and-bound with MibS; see Lefebvre et al. (2023) [https://doi.org/10.1287/ijoc.2022.0086]." } },
//{ "BBBB-KKT-SOS1", { 0, "Bilevel-based branch-and-bound with KKT using SOS1; see Lefebvre et al. (2023) [https://doi.org/10.1287/ijoc.2022.0086]." } }

/*** Static robust problems ***/
{ "CVCCG", { 140, "Critical-value column-and-constraint generation; see Lozano and Borrero (2025) [https://link.springer.com/article/10.1007/s10107-025-02249-6]." } },
{ "GEN-IND", { 130, "Scenario generation (indicator functions in case of decision-dependent uncertainty sets)." } }
}) {}
};

struct UncertaintySetAnalysisResult : VariableAnalysisResult{
bool is_zero_one_polytope = false;
bool has_integer_linking_variables = true;
bool has_binary_linking_variables = true;
bool has_integer_coefficients = true;
bool has_decision_dependence = false;
//bool has_rhs_uncerrtainty = false;
//bool has_ctr_uncertainty = false;
};

inline UncertaintySetAnalysisResult get_uncertainty_set_analysis(const idol::Model& t_uncertainty_set) {
inline UncertaintySetAnalysisResult get_uncertainty_set_analysis(const idol::Model& t_model, const idol::Model& t_uncertainty_set) {

UncertaintySetAnalysisResult result;

for (const auto& var : t_uncertainty_set.vars()) {
do_variable_analysis(result, t_uncertainty_set, var);
}

for (const auto& var : t_uncertainty_set.vars()) {

if (!t_model.has(var)) {
continue;
}

result.has_decision_dependence = true;
const auto type = t_uncertainty_set.get_var_type(var);
if (type != idol::Binary) {
result.has_binary_linking_variables = false;
}
if (type == idol::Continuous) {
result.has_integer_linking_variables = false;
}
}

return result;
}

Expand All @@ -60,14 +85,14 @@ struct StageAnalysisResult {
};

inline StageAnalysisResult get_stage_analysis(const idol::Model& t_model,
const idol::Bilevel::Description& t_bilevel_description,
const std::optional<idol::Bilevel::Description>& t_bilevel_description,
const idol::Robust::Description& t_robust_description) {

StageAnalysisResult result;

for (const auto& var : t_model.vars()) {

if (t_bilevel_description.is_upper(var)) {
if (!t_bilevel_description || t_bilevel_description->is_upper(var)) {
do_variable_analysis(result.first_stage, t_model, var);
} else {
do_variable_analysis(result.second_stage, t_model, var);
Expand All @@ -79,20 +104,24 @@ inline StageAnalysisResult get_stage_analysis(const idol::Model& t_model,
return result;
}

inline void solve_adjustable_robust(const Arguments& t_args) {
inline void solve_robust(const Arguments& t_args) {

using namespace idol;

Env env;
auto model = GLPK::read_from_file(env, t_args.file);
auto bilevel_description = Bilevel::read_bilevel_description(model, t_args.aux_file);
auto model = Model::read_from_file(env, t_args.file);
auto robust_description = Robust::read_from_file(model, t_args.uncertainty_param_file, t_args.uncertainty_set_file);
const auto& uncertainty_set = robust_description.uncertainty_set();

const auto uncertainty_analysis = get_uncertainty_set_analysis(uncertainty_set);
std::optional<Bilevel::Description> bilevel_description;
if (!t_args.aux_file.empty()) {
bilevel_description = Bilevel::read_bilevel_description(model, t_args.aux_file);
}

const auto uncertainty_analysis = get_uncertainty_set_analysis(model, uncertainty_set);
const auto stage_analysis = get_stage_analysis(model, bilevel_description, robust_description);

RobustMethodManager robust_method_manager;
AdjustableRobustMethodManager robust_method_manager;
MILPMethodManager sub_milp_method_manager;

if (stage_analysis.has_second_stage) {
Expand Down Expand Up @@ -180,27 +209,45 @@ inline void solve_adjustable_robust(const Arguments& t_args) {

}

} else { // Static robust problem

if (uncertainty_analysis.has_decision_dependence) {
std::cout << "-- Detected: uncertainty set is decision-dependent." << std::endl;
if (uncertainty_analysis.has_integer_coefficients) {
std::cout << "-- Detected: uncertainty set has integer coefficients." << std::endl;
if (uncertainty_analysis.has_integer_linking_variables) {
std::cout << "-- Detected: uncertainty set has integer linking variables." << std::endl;
robust_method_manager.add("CVCCG");
}
}
}

if (!uncertainty_analysis.has_decision_dependence || uncertainty_analysis.has_binary_linking_variables) {
std::cout << "-- Detected: uncertainty set has binary linking variables." << std::endl;
robust_method_manager.add("GEN-IND");
}

}

robust_method_manager.print_available_methods(t_args);

const auto method = robust_method_manager.get_method(t_args);

std::cout << "-- Solving using " << method << std::endl;
const auto sub_milp_optimizer = sub_milp_method_manager.get_sub_milp_optimizer(t_args);

std::cout << "-- Solving problem using " << method << "." << std::endl;
std::cout << "-- Sub-MILP method is " << sub_milp_method_manager.get_sub_milp_method(t_args) << "." << std::endl;

if (method.starts_with("CCG-")) {

auto ccg = Robust::ColumnAndConstraintGeneration(robust_description, bilevel_description);
ccg.with_initial_scenario_by_maximization(Gurobi());
ccg.with_master_optimizer(Gurobi());
auto ccg = Robust::ColumnAndConstraintGeneration(robust_description, *bilevel_description);
ccg.with_initial_scenario_by_maximization(*sub_milp_optimizer);
ccg.with_master_optimizer(*sub_milp_optimizer);
ccg.with_logs(!t_args.mute);

if (method == "CCG-FARKAS") {

auto farkas = Robust::CCG::BigMFreeSeparation();
farkas.with_single_level_optimizer(Gurobi());
farkas.with_single_level_optimizer(*sub_milp_optimizer);
farkas.with_binary_uncertainty_set(!uncertainty_analysis.has_continuous || uncertainty_analysis.is_zero_one_polytope);

ccg.add_separation(farkas);
Expand All @@ -209,7 +256,7 @@ inline void solve_adjustable_robust(const Arguments& t_args) {

auto kkt = Bilevel::KKT();
kkt.with_sos1_constraints(true);
kkt.with_single_level_optimizer(Gurobi());
kkt.with_single_level_optimizer(*sub_milp_optimizer);

if (!t_args.complete_recourse) {
auto feasibility_separation = Robust::CCG::FeasibilitySeparation();
Expand All @@ -225,8 +272,6 @@ inline void solve_adjustable_robust(const Arguments& t_args) {

} else if (method == "CCG-MIBS") {

auto sub_milp_optimizer = sub_milp_method_manager.get_sub_milp_optimizer(t_args);

auto mibs = Bilevel::MibS();
mibs.with_feasibility_checker(*sub_milp_optimizer);

Expand All @@ -247,6 +292,23 @@ inline void solve_adjustable_robust(const Arguments& t_args) {

model.use(ccg);

} else if (method == "CVCCG") {

const auto milp_solver = sub_milp_method_manager.get_sub_milp_optimizer(t_args);

auto ccg = Robust::CriticalValueColumnAndConstraintGeneration(robust_description);
ccg.with_master_optimizer(*milp_solver);
ccg.with_deterministic_optimizer(*milp_solver);

model.use(ccg);

} else if (method == "GEN-IND") {
auto ccg = Robust::CriticalValueColumnAndConstraintGeneration(robust_description);
ccg.with_master_optimizer(*sub_milp_optimizer);
ccg.with_deterministic_optimizer(*sub_milp_optimizer);
ccg.with_indicator(true);

model.use(ccg);
}

// Set Parameters
Expand Down
2 changes: 1 addition & 1 deletion bin/solve_milp.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ inline void solve_milp(const Arguments& t_args) {
std::cout << "-- Solving using " << method << std::endl;

Env env;
auto model = GLPK::read_from_file(env, t_args.file);
auto model = Model::read_from_file(env, t_args.file);

if (method == "GUROBI") {
model.use(Gurobi());
Expand Down
12 changes: 6 additions & 6 deletions docs/website/Doxyfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ FULL_PATH_NAMES = YES
# will be relative from the directory where doxygen is started.
# This tag requires that the tag FULL_PATH_NAMES is set to YES.

STRIP_FROM_PATH =
STRIP_FROM_PATH = ../../lib/include

# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
# path mentioned in the documentation of a class, which tells the reader which
Expand All @@ -179,7 +179,7 @@ STRIP_FROM_PATH =
# specify the list of include paths that are normally passed to the compiler
# using the -I flag.

STRIP_FROM_INC_PATH =
STRIP_FROM_INC_PATH = ../../lib/include

# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
# less readable) file names. This can be useful is your file systems doesn't
Expand Down Expand Up @@ -577,7 +577,7 @@ HIDE_COMPOUND_REFERENCE= NO
# the files that are included by a file in the documentation of that file.
# The default value is: YES.

SHOW_INCLUDE_FILES = NO
SHOW_INCLUDE_FILES = YES

# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
# grouped member an include statement to the documentation, telling the reader
Expand Down Expand Up @@ -706,7 +706,7 @@ SHOW_USED_FILES = NO
# (if specified).
# The default value is: YES.

SHOW_FILES = NO
SHOW_FILES = YES

# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
# page. This will remove the Namespaces entry from the Quick Index and from the
Expand Down Expand Up @@ -1055,13 +1055,13 @@ STRIP_CODE_COMMENTS = YES
# entity all documented functions referencing it will be listed.
# The default value is: NO.

REFERENCED_BY_RELATION = NO
REFERENCED_BY_RELATION = YES

# If the REFERENCES_RELATION tag is set to YES then for each documented function
# all documented entities called/used by that function will be listed.
# The default value is: NO.

REFERENCES_RELATION = NO
REFERENCES_RELATION = YES

# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
# to YES then the hyperlinks from functions in REFERENCES_RELATION and
Expand Down
4 changes: 2 additions & 2 deletions docs/website/DoxygenLayout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
<!-- Layout definition for a class page -->
<class>
<briefdescription visible="yes"/>
<includes visible="$SHOW_HEADERFILE"/>
<includes visible="yes"/>
<inheritancegraph visible="$CLASS_GRAPH"/>
<collaborationgraph visible="yes"/>
<detaileddescription title="Description"/>
Expand Down Expand Up @@ -150,7 +150,7 @@
<!-- Layout definition for a file page -->
<file>
<briefdescription visible="yes"/>
<includes visible="$SHOW_INCLUDE_FILES"/>
<includes visible="yes"/>
<includegraph visible="yes"/>
<includedbygraph visible="yes"/>
<sourcelink visible="yes"/>
Expand Down
8 changes: 6 additions & 2 deletions examples/robust/ddro-kp.example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct Instance {
const unsigned int n_items = weights.size();
const double knapsack_capacity = 50;
const double defender_capacity = 2;
const double uncertainty_budget = 2; // std::round(std::sqrt(n_items));
const double uncertainty_budget = std::round(std::sqrt(n_items));
};

void solve_with_critical_value_ccg(Env& t_env, const Instance& t_instance);
Expand Down Expand Up @@ -84,6 +84,10 @@ void solve_with_critical_value_ccg(Env& t_env, const Instance& t_instance) {
std::cout << "Objective: " << model.get_best_obj() << std::endl;
std::cout << "Time: " << model.optimizer().time().count() << std::endl;

for (const auto& var : description.uncertainty_set().vars()) {
std::cout << var << " = " << model.get_var_primal(var) << std::endl;
}

}

void solve_as_bilevel(Env& t_env, const Instance& t_instance) {
Expand Down Expand Up @@ -136,7 +140,7 @@ void solve_as_bilevel(Env& t_env, const Instance& t_instance) {

// Set optimizer
auto mibs = Bilevel::MibS(description);
mibs.with_cplex_for_feasibility(true);
mibs.with_feasibility_checker(Gurobi());
mibs.with_logs(true);
model.use(mibs);

Expand Down
Loading
Loading