From 7973ca11d7217b7e3da0e1f2aa1c072557d17e5f Mon Sep 17 00:00:00 2001 From: Henri Lefebvre Date: Thu, 30 Apr 2026 18:13:31 +0200 Subject: [PATCH 1/9] compute correct big-Ms --- docs/website/Doxyfile.in | 12 +++++----- docs/website/DoxygenLayout.xml | 4 ++-- examples/robust/ddro-kp.example.cpp | 2 +- .../Formulation.h | 2 +- .../Formulation.cpp | 22 +++++++++++++++++-- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/docs/website/Doxyfile.in b/docs/website/Doxyfile.in index e47f1d27e..85b5634d9 100644 --- a/docs/website/Doxyfile.in +++ b/docs/website/Doxyfile.in @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/docs/website/DoxygenLayout.xml b/docs/website/DoxygenLayout.xml index 92ef2ec08..09835590f 100644 --- a/docs/website/DoxygenLayout.xml +++ b/docs/website/DoxygenLayout.xml @@ -50,7 +50,7 @@ - + @@ -150,7 +150,7 @@ - + diff --git a/examples/robust/ddro-kp.example.cpp b/examples/robust/ddro-kp.example.cpp index bd3d7329b..1c9dd99cf 100644 --- a/examples/robust/ddro-kp.example.cpp +++ b/examples/robust/ddro-kp.example.cpp @@ -136,7 +136,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); diff --git a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h index 9a74b9f98..4e85ef3a0 100644 --- a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h +++ b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h @@ -49,7 +49,7 @@ class idol::CVCCG::Formulation { std::vector m_uncertainties; bool m_all_linking_variables_are_binary = true; bool m_all_data_in_linking_constraints_is_integer = true; - bool m_use_indicator = false; + const bool m_use_indicator = false; Model m_master; Model m_sub_problem; diff --git a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp index 811f87733..1be3b7228 100644 --- a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp +++ b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp @@ -256,8 +256,26 @@ void idol::CVCCG::Formulation::add_scenario_to_master(const std::list 0) { + const double ub = model.get_var_ub(var); + if (is_pos_inf(ub)) { + throw Exception("Found variable with infinite bound during big-M computation."); + } + penalty += coeff * ub; + } else { + const double lb = model.get_var_lb(var); + if (is_neg_inf(lb)) { + throw Exception("Found variable with infinite bound during big-M computation."); + } + penalty += coeff * lb; + } + } + + if (type == LessOrEqual) { + penalty *= -1.; + } if (m_linking_constraints.empty() || m_use_indicator) { // add indicator From a4f0d20973a7719b3f628c7bf7cf21cf0cff7a70 Mon Sep 17 00:00:00 2001 From: Henri Lefebvre Date: Mon, 4 May 2026 09:59:26 +0200 Subject: [PATCH 2/9] avoid conflicting names in guroni with C++ library --- .../wrappers/Gurobi/Optimizers_Gurobi.h | 4 +- .../wrappers/Gurobi/headers/header_gurobi.h | 172 +++++++++--------- .../wrappers/Gurobi/GurobiCallbackI.cpp | 22 +-- .../wrappers/Gurobi/Optimizers_Gurobi.cpp | 26 +-- 4 files changed, 112 insertions(+), 112 deletions(-) diff --git a/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/Optimizers_Gurobi.h b/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/Optimizers_Gurobi.h index 549637005..7caac886a 100644 --- a/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/Optimizers_Gurobi.h +++ b/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/Optimizers_Gurobi.h @@ -119,8 +119,8 @@ class idol::Optimizers::Gurobi : public OptimizerWithLazyUpdates solution(n_vars); - if (m_where == GRB_CB_MIPSOL) { + if (m_where == idol_GRB_CB_MIPSOL) { // Get primal values int error = lib.GRBcbget(m_cbdata, m_where, - GRB_CB_MIPSOL_SOL, + idol_GRB_CB_MIPSOL_SOL, solution.data()); if (error) { throw Exception("Error when getting solution."); @@ -119,7 +119,7 @@ idol::PrimalPoint idol::GurobiCallbackI::primal_solution() const { double objective_value; error = lib.GRBcbget(m_cbdata, m_where, - GRB_CB_MIPSOL_OBJ, + idol_GRB_CB_MIPSOL_OBJ, &objective_value); if (error) { @@ -128,13 +128,13 @@ idol::PrimalPoint idol::GurobiCallbackI::primal_solution() const { result.set_objective_value(objective_value); - } else if (m_where == GRB_CB_MIPNODE) { + } else if (m_where == idol_GRB_CB_MIPNODE) { // Get node status int node_status; int error = lib.GRBcbget(m_cbdata, m_where, - GRB_CB_MIPNODE_STATUS, + idol_GRB_CB_MIPNODE_STATUS, &node_status); if (error) { @@ -152,7 +152,7 @@ idol::PrimalPoint idol::GurobiCallbackI::primal_solution() const { // Get primal values error = lib.GRBcbget(m_cbdata, m_where, - GRB_CB_MIPNODE_REL, + idol_GRB_CB_MIPNODE_REL, solution.data()); if (error) { diff --git a/lib/src/mixed-integer/optimizers/wrappers/Gurobi/Optimizers_Gurobi.cpp b/lib/src/mixed-integer/optimizers/wrappers/Gurobi/Optimizers_Gurobi.cpp index 87c68e8be..6d44e1b23 100644 --- a/lib/src/mixed-integer/optimizers/wrappers/Gurobi/Optimizers_Gurobi.cpp +++ b/lib/src/mixed-integer/optimizers/wrappers/Gurobi/Optimizers_Gurobi.cpp @@ -20,7 +20,7 @@ } #define GUROBI_SYM_LOAD(name) { \ -name = (name##_t) dlsym(m_handle, #name); \ +name = (idol_##name##_t) dlsym(m_handle, #name); \ const char* err = dlerror(); \ if (err) \ throw std::runtime_error(std::string("Missing Gurobi symbol ") + #name + ": " + err); \ @@ -223,20 +223,20 @@ char idol::Optimizers::Gurobi::gurobi_obj_sense(int t_sense) { std::pair idol::Optimizers::Gurobi::gurobi_status(int t_status) const { auto& lib = get_dynamic_lib(); switch (t_status) { - case GRB_SUBOPTIMAL: return { SubOptimal, Proved }; - case GRB_OPTIMAL: return { Optimal, Proved }; - case GRB_INFEASIBLE: return { Infeasible, Proved }; - case GRB_INF_OR_UNBD: return {InfOrUnbnd, Proved }; - case GRB_UNBOUNDED: return { Unbounded, Proved }; - case GRB_CUTOFF: [[fallthrough]]; - case GRB_USER_OBJ_LIMIT: return {Feasible, ObjLimit }; - case GRB_TIME_LIMIT: { + case idol_GRB_SUBOPTIMAL: return { SubOptimal, Proved }; + case idol_GRB_OPTIMAL: return { Optimal, Proved }; + case idol_GRB_INFEASIBLE: return { Infeasible, Proved }; + case idol_GRB_INF_OR_UNBD: return {InfOrUnbnd, Proved }; + case idol_GRB_UNBOUNDED: return { Unbounded, Proved }; + case idol_GRB_CUTOFF: [[fallthrough]]; + case idol_GRB_USER_OBJ_LIMIT: return {Feasible, ObjLimit }; + case idol_GRB_TIME_LIMIT: { int n_solutions; GUROBI_CATCH(m_model, GRBgetintattr(m_model, "SolCount", &n_solutions)); return { n_solutions > 0 ? Feasible : Infeasible, TimeLimit }; } - case GRB_NUMERIC: return {Fail, NotSpecified }; - case GRB_SOLUTION_LIMIT: return {Feasible, SolutionLimit }; + case idol_GRB_NUMERIC: return {Fail, NotSpecified }; + case idol_GRB_SOLUTION_LIMIT: return {Feasible, SolutionLimit }; default:; } throw Exception("Unsupported Gurobi status: " + std::to_string(t_status)); @@ -244,10 +244,10 @@ std::pair idol::Optimizers::Gurobi:: double idol::Optimizers::Gurobi::gurobi_numeric(double t_value) { if (is_pos_inf(t_value)) { - return GRB_INFINITY; + return idol_GRB_INFINITY; } if (is_neg_inf(t_value)) { - return -GRB_INFINITY; + return -idol_GRB_INFINITY; } return t_value; } From be8a5baccb8d870d9891f81e71a6561afa55059a Mon Sep 17 00:00:00 2001 From: Henri Lefebvre Date: Mon, 4 May 2026 20:20:06 +0200 Subject: [PATCH 3/9] Lozano scheme OK --- .../Formulation.h | 47 +++++-- .../Formulation.cpp | 131 +++++++++++++----- ...icalValueColumnAndConstraintGeneration.cpp | 4 +- 3 files changed, 134 insertions(+), 48 deletions(-) diff --git a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h index 4e85ef3a0..b0ff39aa7 100644 --- a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h +++ b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h @@ -7,6 +7,7 @@ #include #include +#include "idol/general/utils/Optional.h" #include "idol/mixed-integer/modeling/models/Model.h" namespace idol::Optimizers::Robust { @@ -19,14 +20,12 @@ namespace idol::CVCCG { class idol::CVCCG::Formulation { - class Uncertainty { - std::optional m_constraint; - public: - [[nodiscard]] bool is_constraint() const { return m_constraint.has_value(); } - [[nodiscard]] const Ctr& ctr() const { return m_constraint.value(); } + struct LinkingConstraint { + Ctr ctr_in_uncertainty_set; + Map> critical_values; + Optional cover_constraint; - Uncertainty() = default; - Uncertainty(const Ctr& t_ctr) : m_constraint(t_ctr) {} + LinkingConstraint(Ctr t_ctr) : ctr_in_uncertainty_set(std::move(t_ctr)) {} }; struct GeneratedScenario { @@ -34,44 +33,62 @@ class idol::CVCCG::Formulation { PrimalPoint master_solution; }; - struct LinkingConstraint { - Ctr ctr_in_uncertainty_set; - Map critical_values; + struct CurrentlyPresentCut { + Ctr cut; + std::list::iterator scenario; + std::list::iterator, long int>> linking_constraints; + }; - LinkingConstraint(Ctr t_ctr) : ctr_in_uncertainty_set(std::move(t_ctr)) {} + class Uncertainty { + std::optional m_constraint_in_original_model; + std::list m_currently_present_cuts; + public: + [[nodiscard]] bool is_constraint() const { return m_constraint_in_original_model.has_value(); } + [[nodiscard]] const Ctr& ctr() const { return m_constraint_in_original_model.value(); } + [[nodiscard]] auto currently_present_cuts() const { return ConstIteratorForward(m_currently_present_cuts); } + auto currently_present_cuts() { return IteratorForward(m_currently_present_cuts); } + + Uncertainty() = default; + Uncertainty(const Ctr& t_ctr) : m_constraint_in_original_model(t_ctr) {} }; const Optimizers::Robust::CriticalValueColumnAndConstraintGeneration& m_parent; // Analysis of the model std::list m_linking_variables; - std::list m_linking_constraints; - std::vector m_uncertainties; bool m_all_linking_variables_are_binary = true; bool m_all_data_in_linking_constraints_is_integer = true; const bool m_use_indicator = false; + const bool m_use_cover_constraints = true; Model m_master; Model m_sub_problem; // Scenarios std::list m_scenario_pool; - std::list::iterator> m_currently_present_scenarios; + std::list m_linking_constraints; + std::list m_uncertainties; void check_assumptions(); void initialize_master(); void initialize_sub_problem(); void add_scenario_to_master(const std::list::iterator& t_iterator_in_pool, Uncertainty& t_uncertainty); + void create_critical_value_variable_if_needed(const PrimalPoint& t_scenario); + void create_critical_value_variable(const PrimalPoint& t_scenario, LinkingConstraint& t_linking); + double compute_critical_value(const Ctr& t_ctr, const PrimalPoint& t_scenario) const; public: Formulation(const idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration& t_parent); Model& master() { return m_master; } Model& sub_problem() { return m_sub_problem; } + auto uncertainties() { return IteratorForward(m_uncertainties); } + auto uncertainties() const { return ConstIteratorForward(m_uncertainties); } + void update_sub_problem_constraints(const PrimalPoint& t_master_solution); unsigned int n_uncertainties() const { return m_uncertainties.size(); } - void update_sub_problem_objective(const PrimalPoint& t_master_solution, unsigned int t_index); + void update_sub_problem_objective(const PrimalPoint& t_master_solution, const Uncertainty& t_uncertainty); std::list::iterator add_scenario_to_pool(PrimalPoint&& t_scenario, PrimalPoint&& t_master_scenario); void add_scenario_to_master(const std::list::iterator& t_iterator_in_pool); }; diff --git a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp index 1be3b7228..3c3d557d8 100644 --- a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp +++ b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp @@ -2,6 +2,9 @@ // Created by Henri on 17/04/2026. // #include "idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h" + +#include + #include "idol/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.h" #include "idol/mixed-integer/modeling/variables/TempVar.h" #include "idol/mixed-integer/modeling/expressions/operations/operators.h" @@ -106,6 +109,13 @@ void idol::CVCCG::Formulation::initialize_master() { m_master.set_obj_expr(model.get_obj_expr()); } + // Create cover constraints + if (m_use_cover_constraints) { + for (auto& linking : m_linking_constraints) { + linking.cover_constraint = m_master.add_ctr(LinExpr(), LessOrEqual, 1, "__cover_" + linking.ctr_in_uncertainty_set.name()); + } + } + // Set optimizer for master m_master.use(m_parent.get_master_optimizer_factory()); @@ -177,17 +187,15 @@ void idol::CVCCG::Formulation::update_sub_problem_constraints(const PrimalPoint& } void idol::CVCCG::Formulation:: -update_sub_problem_objective(const PrimalPoint& t_master_solution, unsigned int t_index) { - - const auto& uncertainty = m_uncertainties[t_index]; +update_sub_problem_objective(const PrimalPoint& t_master_solution, const Uncertainty& t_uncertainty) { - if (!uncertainty.is_constraint()) { + if (!t_uncertainty.is_constraint()) { throw Exception("Not implemented."); } const auto& model = m_parent.parent(); const auto& description = m_parent.description(); - const auto& ctr = uncertainty.ctr(); + const auto& ctr = t_uncertainty.ctr(); const auto type = model.get_ctr_type(ctr); const auto& row = model.get_ctr_row(ctr); @@ -228,11 +236,91 @@ void idol::CVCCG::Formulation::add_scenario_to_master(const std::list::iterator idol::CVCCG::Formulation::add_scenario_to_pool(PrimalPoint&& t_scenario, PrimalPoint&& t_master_scenario) { + create_critical_value_variable_if_needed(t_scenario); + m_scenario_pool.emplace_back(std::move(t_scenario), std::move(t_master_scenario)); return --m_scenario_pool.end(); } +double idol::CVCCG::Formulation::compute_critical_value(const Ctr& t_ctr, const PrimalPoint& t_scenario) const { + + const auto& model = m_parent.parent(); + const auto& uncertainty_set = m_parent.description().uncertainty_set(); + + const auto& row = uncertainty_set.get_ctr_row(t_ctr); + const auto type = uncertainty_set.get_ctr_type(t_ctr); + assert(type == LessOrEqual); + + double result = uncertainty_set.get_ctr_rhs(t_ctr) + 1; + for (const auto& [var, coeff] : row) { + if (!model.has(var)) { + result -= coeff * t_scenario.get(var); + } + } + assert(is_integer(result, m_parent.get_tol_integer())); + + return result; +} + +void idol::CVCCG::Formulation::create_critical_value_variable_if_needed(const PrimalPoint& t_scenario) { + + for (auto& linking : m_linking_constraints) { + + const double critical_value = compute_critical_value(linking.ctr_in_uncertainty_set, t_scenario); + + const auto it = linking.critical_values.find((long int) critical_value); + if (it != linking.critical_values.end()) { + continue; + } + + create_critical_value_variable(t_scenario, linking); + + } + +} + +void idol::CVCCG::Formulation::create_critical_value_variable(const PrimalPoint& t_scenario, LinkingConstraint& t_linking) { + + const double critical_value = compute_critical_value(t_linking.ctr_in_uncertainty_set, t_scenario); + const auto& model = m_parent.parent(); + const auto& uncertainty_set = m_parent.description().uncertainty_set(); + const auto& row = uncertainty_set.get_ctr_row(t_linking.ctr_in_uncertainty_set); + const auto type = uncertainty_set.get_ctr_type(t_linking.ctr_in_uncertainty_set); + + assert(type == LessOrEqual); + + LinExpr column; + for (const auto& uncertainty : m_uncertainties) { + for (const auto& cut : uncertainty.currently_present_cuts()) { + const double local_critical_value = compute_critical_value(t_linking.ctr_in_uncertainty_set, cut.scenario->scenario); + if ((long int) critical_value <= (long int) local_critical_value) { + const double penalty = 1e4; // TODO store it in the cut? + column.set(cut.cut, -penalty); + } + } + } + + if (t_linking.cover_constraint) { + column.set(*t_linking.cover_constraint, 1); + } + + const auto& activation_var = m_master.add_var(0, 1, Binary, 0, std::move(column), "__" + t_linking.ctr_in_uncertainty_set.name() + "_cv_" + std::to_string((long int)critical_value)); + + LinExpr parameterized_part; + for (const auto& [var, coeff] : row) { + if (model.has(var)) { + parameterized_part += coeff * var; + } + } + + const auto& activation_ctr = m_master.add_ctr(critical_value * activation_var <= parameterized_part); + + const auto [it, success] = t_linking.critical_values.emplace((long int)critical_value, std::make_pair(activation_var, activation_ctr)); + assert(success); + +} + void idol::CVCCG::Formulation::add_scenario_to_master(const std::list::iterator& t_iterator_in_pool, Uncertainty& t_uncertainty) { if (!t_uncertainty.is_constraint()) { @@ -272,6 +360,7 @@ void idol::CVCCG::Formulation::add_scenario_to_master(const std::listupdate_sub_problem_constraints(master_solution); std::list scenarios; - for (unsigned int i = 0; i < n_uncertainties; ++i) { + for (const auto& uncertainty : m_formulation->uncertainties()) { - m_formulation->update_sub_problem_objective(master_solution, i); + m_formulation->update_sub_problem_objective(master_solution, uncertainty); auto& sub_problem = m_formulation->sub_problem(); sub_problem.optimize(); From c69c4f05edf1f42cea9e45cfc13f02a55ab36f63 Mon Sep 17 00:00:00 2001 From: Henri Lefebvre Date: Mon, 4 May 2026 20:28:26 +0200 Subject: [PATCH 4/9] fix declare currently present cuts --- .../Formulation.h | 3 ++- .../Formulation.cpp | 8 +++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h index b0ff39aa7..e45ef4927 100644 --- a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h +++ b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h @@ -36,7 +36,7 @@ class idol::CVCCG::Formulation { struct CurrentlyPresentCut { Ctr cut; std::list::iterator scenario; - std::list::iterator, long int>> linking_constraints; + double penalty; }; class Uncertainty { @@ -47,6 +47,7 @@ class idol::CVCCG::Formulation { [[nodiscard]] const Ctr& ctr() const { return m_constraint_in_original_model.value(); } [[nodiscard]] auto currently_present_cuts() const { return ConstIteratorForward(m_currently_present_cuts); } auto currently_present_cuts() { return IteratorForward(m_currently_present_cuts); } + void add_currently_present_cut(const Ctr& t_ctr, std::list::iterator t_scenario, double t_penalty) { m_currently_present_cuts.emplace_back(t_ctr, t_scenario, t_penalty); } Uncertainty() = default; Uncertainty(const Ctr& t_ctr) : m_constraint_in_original_model(t_ctr) {} diff --git a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp index 3c3d557d8..ab57e2503 100644 --- a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp +++ b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp @@ -295,8 +295,7 @@ void idol::CVCCG::Formulation::create_critical_value_variable(const PrimalPoint& for (const auto& cut : uncertainty.currently_present_cuts()) { const double local_critical_value = compute_critical_value(t_linking.ctr_in_uncertainty_set, cut.scenario->scenario); if ((long int) critical_value <= (long int) local_critical_value) { - const double penalty = 1e4; // TODO store it in the cut? - column.set(cut.cut, -penalty); + column.set(cut.cut, cut.penalty); } } } @@ -330,7 +329,6 @@ void idol::CVCCG::Formulation::add_scenario_to_master(const std::list Date: Wed, 6 May 2026 11:48:05 +0200 Subject: [PATCH 5/9] build scenario via expected generated --- examples/robust/ddro-kp.example.cpp | 4 + .../Formulation.h | 5 + ...iticalValueColumnAndConstraintGeneration.h | 29 +++--- .../Formulation.cpp | 16 ++++ ...icalValueColumnAndConstraintGeneration.cpp | 91 +++++++++++++++++-- 5 files changed, 125 insertions(+), 20 deletions(-) diff --git a/examples/robust/ddro-kp.example.cpp b/examples/robust/ddro-kp.example.cpp index 1c9dd99cf..673719489 100644 --- a/examples/robust/ddro-kp.example.cpp +++ b/examples/robust/ddro-kp.example.cpp @@ -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) { diff --git a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h index e45ef4927..8c46c3daf 100644 --- a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h +++ b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h @@ -69,6 +69,7 @@ class idol::CVCCG::Formulation { std::list m_scenario_pool; std::list m_linking_constraints; std::list m_uncertainties; + unsigned int m_n_critical_values = 0; void check_assumptions(); void initialize_master(); @@ -92,6 +93,10 @@ class idol::CVCCG::Formulation { void update_sub_problem_objective(const PrimalPoint& t_master_solution, const Uncertainty& t_uncertainty); std::list::iterator add_scenario_to_pool(PrimalPoint&& t_scenario, PrimalPoint&& t_master_scenario); void add_scenario_to_master(const std::list::iterator& t_iterator_in_pool); + + unsigned int n_critical_values() const { return m_n_critical_values; } + unsigned int n_scenarios_in_pool() const { return m_scenario_pool.size(); } + double get_scenario_var_primal(const Var& t_var) const; }; #endif //IDOL_CVCCG_FORMULATION_H \ No newline at end of file diff --git a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.h b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.h index 26fa2880c..8598eb621 100644 --- a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.h +++ b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.h @@ -19,6 +19,7 @@ class idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration : pub std::unique_ptr m_master_optimizer_factory; std::unique_ptr m_deterministic_optimizer_factory; std::unique_ptr m_formulation; + unsigned int m_n_iterations = 0; public: CriticalValueColumnAndConstraintGeneration(const Model& t_model, const idol::Robust::Description& t_description, @@ -26,6 +27,18 @@ class idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration : pub const OptimizerFactory& t_deterministic_optimizer ); + [[nodiscard]] std::string name() const override { return "CVCCG"; } + [[nodiscard]] double get_var_primal(const Var& t_var) const override; + [[nodiscard]] double get_var_reduced_cost(const Var& t_var) const override; + [[nodiscard]] double get_var_ray(const Var& t_var) const override; + [[nodiscard]] double get_ctr_dual(const Ctr& t_ctr) const override; + [[nodiscard]] double get_ctr_farkas(const Ctr& t_ctr) const override; + [[nodiscard]] unsigned get_n_solutions() const override; + [[nodiscard]] unsigned get_solution_index() const override; + + [[nodiscard]] const idol::Robust::Description& description() const { return m_description; } + [[nodiscard]] const OptimizerFactory& get_master_optimizer_factory() const { return *m_master_optimizer_factory; } + [[nodiscard]] const OptimizerFactory& get_deterministic_optimizer_factory() const { return *m_deterministic_optimizer_factory; } protected: void add(const Var& t_var) override { m_formulation.reset(); } void add(const Ctr& t_ctr) override { m_formulation.reset(); } @@ -37,6 +50,7 @@ class idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration : pub void remove(const SOSCtr& t_ctr) override { m_formulation.reset(); } void update() override { m_formulation.reset(); } void write(const std::string& t_name) override; + void hook_before_optimize() override; void hook_optimize() override; void set_solution_index(unsigned t_index) override; void update_obj_sense() override { m_formulation.reset(); } @@ -55,19 +69,10 @@ class idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration : pub void analyze_master_problem(); bool check_stopping_criterion(); void solve_sub_problems(); -public: - [[nodiscard]] std::string name() const override { return "CVCCG"; } - [[nodiscard]] double get_var_primal(const Var& t_var) const override; - [[nodiscard]] double get_var_reduced_cost(const Var& t_var) const override; - [[nodiscard]] double get_var_ray(const Var& t_var) const override; - [[nodiscard]] double get_ctr_dual(const Ctr& t_ctr) const override; - [[nodiscard]] double get_ctr_farkas(const Ctr& t_ctr) const override; - [[nodiscard]] unsigned get_n_solutions() const override; - [[nodiscard]] unsigned get_solution_index() const override; + void log_banner(); + void log_iteration_separator(); + void log_iteration(); - [[nodiscard]] const idol::Robust::Description& description() const { return m_description; } - [[nodiscard]] const OptimizerFactory& get_master_optimizer_factory() const { return *m_master_optimizer_factory; } - [[nodiscard]] const OptimizerFactory& get_deterministic_optimizer_factory() const { return *m_deterministic_optimizer_factory; } }; #endif //IDOL_OPTIMIZERS_CRITICALVALUECOLUMNANDCONSTRAINTGENERATION_H \ No newline at end of file diff --git a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp index ab57e2503..51965abe4 100644 --- a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp +++ b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp @@ -234,6 +234,20 @@ void idol::CVCCG::Formulation::add_scenario_to_master(const std::listscenario.get(t_var); + n_scenarios++; + } + } + + return value / n_scenarios; +} + std::list::iterator idol::CVCCG::Formulation::add_scenario_to_pool(PrimalPoint&& t_scenario, PrimalPoint&& t_master_scenario) { create_critical_value_variable_if_needed(t_scenario); @@ -318,6 +332,8 @@ void idol::CVCCG::Formulation::create_critical_value_variable(const PrimalPoint& const auto [it, success] = t_linking.critical_values.emplace((long int)critical_value, std::make_pair(activation_var, activation_ctr)); assert(success); + m_n_critical_values += 1; + } void idol::CVCCG::Formulation::add_scenario_to_master(const std::list::iterator& t_iterator_in_pool, Uncertainty& t_uncertainty) { diff --git a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.cpp b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.cpp index 7998b1130..e107c88ef 100644 --- a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.cpp +++ b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.cpp @@ -3,6 +3,8 @@ // #include "idol/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.h" +#include "idol/general/optimizers/logs.h" + idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::CriticalValueColumnAndConstraintGeneration(const Model& t_model, const idol::Robust::Description& t_description, const OptimizerFactory& t_master_optimizer, const OptimizerFactory& t_deterministic_optimizer) : Algorithm(t_model), m_description(t_description), @@ -15,6 +17,8 @@ void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::hook_ m_formulation = std::make_unique(*this); + log_banner(); + do { solve_master_problem(); @@ -23,6 +27,10 @@ void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::hook_ solve_sub_problems(); + m_n_iterations++; + + log_iteration(); + if (check_stopping_criterion()) { break; } } while (true); @@ -38,8 +46,6 @@ void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::analy const auto status = master.get_status(); const auto reason = master.get_reason(); - std::cout << status << "\t" << reason << '\t'; - if (status != Optimal) { set_status(status); set_reason(reason); @@ -49,8 +55,6 @@ void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::analy const double objective_value = master.get_best_obj(); - std::cout << objective_value << '\n'; - set_best_bound(objective_value); } @@ -107,16 +111,84 @@ void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::solve } +void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::log_banner() { + + if (!get_param_logs()) { + return; + } + + center(std::cout, "", 100, '*') << std::endl; + center(std::cout, " Critical-Value Column-and-constraint Generation ", 100) << std::endl; + center(std::cout, "", 100, '*') << std::endl; + + std::cout << std::setw(12) << "Time (s)" << "\t" + << std::setw(5) << "# Iter." << "\t" + << std::setw(5) << "# Scen." << "\t" + << std::setw(5) << "# CV" << "\t" + << std::setw(12) << "LB" << "\t" + << std::setw(12) << "UB" << "\t" + << std::setw(12) << "Gap (%)" << "\t" + << std::setw(12) << "Abs. Gap" + << std::endl; + log_iteration_separator(); +} + +void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::log_iteration_separator() { + + if (!get_param_logs()) { + return; + } + + center(std::cout, "-", 100, '-') << std::endl; + +} + +void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::log_iteration() { + + if (!get_param_logs()) { + return; + } + + std::cout << std::setw(12) << pretty_double(time().count(), 5) << "s\t" + << std::setw(5) << m_n_iterations << "\t" + << std::setw(5) << m_formulation->n_scenarios_in_pool() << "\t" + << std::setw(5) << m_formulation->n_critical_values() << "\t" + << std::setw(12) << pretty_double(get_best_bound(), 5) << "\t" + << std::setw(12) << pretty_double(get_best_obj(), 5) << "\t" + << std::setw(12) << pretty_double(get_relative_gap() * 100, 5) << "\t" + << std::setw(12) << pretty_double(get_absolute_gap(), 5) + << std::endl; + +} + void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::write(const std::string& t_name) { - throw Exception("Not implemented write"); + if (!m_formulation) { + m_formulation = std::make_unique(*this); + } + m_formulation->master().write(t_name); +} + +void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::hook_before_optimize() { + Algorithm::hook_before_optimize(); + + m_n_iterations = 0; + set_best_obj(Inf); + set_best_bound(-Inf); + } void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::set_solution_index(unsigned t_index) { - throw Exception("Not implemented set_solution_index"); + if (t_index == 0) { + return; + } + throw Exception("Solution index out of bounds."); } double idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::get_var_primal(const Var& t_var) const { - throw Exception("Not implemented get_var_primal"); + if (parent().has(t_var)) { + return m_formulation->master().get_var_primal(t_var); + } + return m_formulation->get_scenario_var_primal(t_var); } double idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::get_var_reduced_cost(const Var& t_var) const { @@ -136,7 +208,10 @@ double idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::get } unsigned idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::get_n_solutions() const { - throw Exception("Not implemented get_n_solutions"); + if (const auto status = get_solution_index() ; status == Feasible || status == Optimal) { + return 1; + } + return 0; } unsigned idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::get_solution_index() const { From be4886a9d8e88ee4a31ae32e1f365408cddcb9ed Mon Sep 17 00:00:00 2001 From: Henri Lefebvre Date: Wed, 6 May 2026 13:44:42 +0200 Subject: [PATCH 6/9] add CVCCG and GEN-IND to idol_cl --- bin/CMakeLists.txt | 2 +- bin/idol_cl.cpp | 6 +- ...lve_robust.h => solve_adjustable_robust.h} | 85 ++++++++++++++++--- examples/robust/ddro-kp.example.cpp | 4 +- .../idol/robust/modeling/Description.h | 17 ++-- ...iticalValueColumnAndConstraintGeneration.h | 2 + .../Formulation.h | 1 - ...iticalValueColumnAndConstraintGeneration.h | 8 +- lib/src/robust/modeling/Description.cpp | 31 +++++++ lib/src/robust/modeling/read_from_file.cpp | 40 ++++++--- ...icalValueColumnAndConstraintGeneration.cpp | 18 +++- .../Formulation.cpp | 2 +- ...icalValueColumnAndConstraintGeneration.cpp | 50 +++++++++-- 13 files changed, 217 insertions(+), 49 deletions(-) rename bin/{solve_robust.h => solve_adjustable_robust.h} (71%) diff --git a/bin/CMakeLists.txt b/bin/CMakeLists.txt index c5484a114..c8160aa33 100644 --- a/bin/CMakeLists.txt +++ b/bin/CMakeLists.txt @@ -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 diff --git a/bin/idol_cl.cpp b/bin/idol_cl.cpp index 8a96dcef6..8a552deb4 100644 --- a/bin/idol_cl.cpp +++ b/bin/idol_cl.cpp @@ -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) { @@ -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."); } diff --git a/bin/solve_robust.h b/bin/solve_adjustable_robust.h similarity index 71% rename from bin/solve_robust.h rename to bin/solve_adjustable_robust.h index 74f7b9534..299d2b271 100644 --- a/bin/solve_robust.h +++ b/bin/solve_adjustable_robust.h @@ -19,26 +19,36 @@ #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; @@ -46,6 +56,22 @@ inline UncertaintySetAnalysisResult get_uncertainty_set_analysis(const idol::Mod 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; } @@ -60,14 +86,14 @@ struct StageAnalysisResult { }; inline StageAnalysisResult get_stage_analysis(const idol::Model& t_model, - const idol::Bilevel::Description& t_bilevel_description, + const std::optional& 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); @@ -79,20 +105,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; + 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) { @@ -180,19 +210,35 @@ 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; - std::cout << "-- Solving problem using " << method << "." << std::endl; if (method.starts_with("CCG-")) { - auto ccg = Robust::ColumnAndConstraintGeneration(robust_description, bilevel_description); + auto ccg = Robust::ColumnAndConstraintGeneration(robust_description, *bilevel_description); ccg.with_initial_scenario_by_maximization(Gurobi()); ccg.with_master_optimizer(Gurobi()); ccg.with_logs(!t_args.mute); @@ -247,6 +293,19 @@ 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") { + 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); + ccg.with_indicator(true); + model.use(ccg); } // Set Parameters diff --git a/examples/robust/ddro-kp.example.cpp b/examples/robust/ddro-kp.example.cpp index 673719489..4906396fa 100644 --- a/examples/robust/ddro-kp.example.cpp +++ b/examples/robust/ddro-kp.example.cpp @@ -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); @@ -66,6 +66,8 @@ void solve_with_critical_value_ccg(Env& t_env, const Instance& t_instance) { description.set_uncertain_mat_coeff(c, x[j], std::ceil(.5 * t_instance.weights[j]) * u[j]); } + std::cout << description << std::endl; + // Print robust model // std::cout << Robust::Description::View(model, description) << std::endl; diff --git a/lib/include/idol/robust/modeling/Description.h b/lib/include/idol/robust/modeling/Description.h index dc42ee831..bce6d180c 100644 --- a/lib/include/idol/robust/modeling/Description.h +++ b/lib/include/idol/robust/modeling/Description.h @@ -25,21 +25,21 @@ class idol::Robust::Description { Description(Description&&) = default; - const Model& uncertainty_set() const { return *m_uncertainty_set; } + [[nodiscard]] const Model& uncertainty_set() const { return *m_uncertainty_set; } - auto uncertain_mat_coeffs() const { return ConstIteratorForward(m_uncertain_mat_coeff); } + [[nodiscard]] auto uncertain_mat_coeffs() const { return ConstIteratorForward(m_uncertain_mat_coeff); } - auto uncertain_rhs() const { return m_uncertain_rhs; } + [[nodiscard]] auto uncertain_rhs() const { return m_uncertain_rhs; } - auto uncertain_obj() const { return m_uncertain_obj; } + [[nodiscard]] auto uncertain_obj() const { return m_uncertain_obj; } - const LinExpr& uncertain_mat_coeff(const Ctr& t_ctr, const Var& t_var) const; + [[nodiscard]] const LinExpr& uncertain_mat_coeff(const Ctr& t_ctr, const Var& t_var) const; - const LinExpr>& uncertain_mat_coeffs(const Ctr& t_ctr) const; + [[nodiscard]] const LinExpr>& uncertain_mat_coeffs(const Ctr& t_ctr) const; - const LinExpr& uncertain_rhs(const Ctr& t_ctr) const; + [[nodiscard]] const LinExpr& uncertain_rhs(const Ctr& t_ctr) const; - const LinExpr& uncertain_obj(const Var& t_var) const; + [[nodiscard]] const LinExpr& uncertain_obj(const Var& t_var) const; void set_uncertain_mat_coeff(const Ctr& t_ctr, const Var& t_var, const LinExpr& t_coeff) { m_uncertain_mat_coeff[t_ctr].set(t_var, t_coeff); @@ -76,6 +76,7 @@ class idol::Robust::Description { namespace idol { std::ostream &operator<<(std::ostream &t_os, const idol::Robust::Description::View &t_view); + std::ostream &operator<<(std::ostream &t_os, const idol::Robust::Description &t_description); } diff --git a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/CriticalValueColumnAndConstraintGeneration.h b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/CriticalValueColumnAndConstraintGeneration.h index a41f896b2..1edf2e31d 100644 --- a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/CriticalValueColumnAndConstraintGeneration.h +++ b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/CriticalValueColumnAndConstraintGeneration.h @@ -16,6 +16,7 @@ class idol::Robust::CriticalValueColumnAndConstraintGeneration : public Optimize const Description& m_description; std::unique_ptr m_master_optimizer_factory; std::unique_ptr m_deterministic_optimizer_factory; + std::optional m_use_indicator; protected: [[nodiscard]] Optimizer* create(const Model& t_model) const override; CriticalValueColumnAndConstraintGeneration(const CriticalValueColumnAndConstraintGeneration& t_src); @@ -26,6 +27,7 @@ class idol::Robust::CriticalValueColumnAndConstraintGeneration : public Optimize CriticalValueColumnAndConstraintGeneration& with_master_optimizer(const OptimizerFactory& t_optimizer); CriticalValueColumnAndConstraintGeneration& with_deterministic_optimizer(const OptimizerFactory& t_optimizer); + CriticalValueColumnAndConstraintGeneration& with_indicator(bool t_value); }; #endif //IDOL_CRITICALVALUECOLUMNANDCONSTRAINTGENERATION_H \ No newline at end of file diff --git a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h index 8c46c3daf..5f9fb061f 100644 --- a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h +++ b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.h @@ -59,7 +59,6 @@ class idol::CVCCG::Formulation { std::list m_linking_variables; bool m_all_linking_variables_are_binary = true; bool m_all_data_in_linking_constraints_is_integer = true; - const bool m_use_indicator = false; const bool m_use_cover_constraints = true; Model m_master; diff --git a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.h b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.h index 8598eb621..0b2e00995 100644 --- a/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.h +++ b/lib/include/idol/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.h @@ -18,13 +18,16 @@ class idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration : pub const idol::Robust::Description& m_description; std::unique_ptr m_master_optimizer_factory; std::unique_ptr m_deterministic_optimizer_factory; + const bool m_use_indicator; + std::unique_ptr m_formulation; unsigned int m_n_iterations = 0; public: CriticalValueColumnAndConstraintGeneration(const Model& t_model, const idol::Robust::Description& t_description, const OptimizerFactory& t_master_optimizer, - const OptimizerFactory& t_deterministic_optimizer + const OptimizerFactory& t_deterministic_optimizer, + bool t_use_indicator ); [[nodiscard]] std::string name() const override { return "CVCCG"; } @@ -39,6 +42,7 @@ class idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration : pub [[nodiscard]] const idol::Robust::Description& description() const { return m_description; } [[nodiscard]] const OptimizerFactory& get_master_optimizer_factory() const { return *m_master_optimizer_factory; } [[nodiscard]] const OptimizerFactory& get_deterministic_optimizer_factory() const { return *m_deterministic_optimizer_factory; } + [[nodiscard]] bool use_indicator() const { return m_use_indicator; } protected: void add(const Var& t_var) override { m_formulation.reset(); } void add(const Ctr& t_ctr) override { m_formulation.reset(); } @@ -67,7 +71,7 @@ class idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration : pub void solve_master_problem(); void analyze_master_problem(); - bool check_stopping_criterion(); + void check_termination_criterion(); void solve_sub_problems(); void log_banner(); void log_iteration_separator(); diff --git a/lib/src/robust/modeling/Description.cpp b/lib/src/robust/modeling/Description.cpp index 26b1cc584..029d3d5d0 100644 --- a/lib/src/robust/modeling/Description.cpp +++ b/lib/src/robust/modeling/Description.cpp @@ -196,3 +196,34 @@ idol::Robust::Description::View::View(const Model& t_deterministic_model, const : m_description(t_description), m_deterministic_model(t_deterministic_model) { } + +std::ostream &idol::operator<<(std::ostream &t_os, const idol::Robust::Description &t_description) { + + t_os << "@RHS\n"; + + for (const auto& [ctr, unc_expr] : t_description.uncertain_rhs()) { + for (const auto& [unc_var, coeff] : unc_expr) { + t_os << ctr.name() << " " << unc_var.name() << " " << coeff << '\n'; + } + } + + t_os << "@OBJ\n"; + + for (const auto& [var, unc_expr] : t_description.uncertain_obj()) { + for (const auto& [unc_var, coeff] : unc_expr) { + t_os << var.name() << " " << unc_var.name() << " " << coeff << '\n'; + } + } + + t_os << "@MAT\n"; + + for (const auto& [ctr, unc_ctr_expr] : t_description.uncertain_mat_coeffs()) { + for (const auto& [var, unc_expr] : unc_ctr_expr) { + for (const auto& [unc_var, coeff] : unc_expr) { + t_os << ctr.name() << " " << var.name() << " " << unc_var.name() << " " << coeff << '\n'; + } + } + } + + return t_os; +} \ No newline at end of file diff --git a/lib/src/robust/modeling/read_from_file.cpp b/lib/src/robust/modeling/read_from_file.cpp index ac22a0449..f393d790a 100644 --- a/lib/src/robust/modeling/read_from_file.cpp +++ b/lib/src/robust/modeling/read_from_file.cpp @@ -16,7 +16,6 @@ idol::Robust::Description idol::Robust::read_from_file(Model& t_model, const std::string& t_path_to_par, const std::string& t_path_to_unc) { auto& env = t_model.env(); - bool is_decision_dependent = false; // Store variable names Map variables; @@ -31,13 +30,34 @@ idol::Robust::read_from_file(Model& t_model, const std::string& t_path_to_par, c } // Read uncertainty set - auto uncertainty_set = GLPK::read_from_file(env, t_path_to_unc); + auto uncertainty_set = Model::read_from_file(env, t_path_to_unc); + + // Replace variables that belong to the model but that parameterize the uncertainty set + std::list> columns_to_be_replaced; for (const auto& var : uncertainty_set.vars()) { const auto it = variables.find(var.name()); if (it == variables.end()) { - variables.emplace_hint(it, var.name(), var); - } else { - is_decision_dependent = true; + continue; + } + columns_to_be_replaced.emplace_back(var, it->second); + } + + for (const auto& [var_to_delete, var_to_add] : columns_to_be_replaced) { + if (!uncertainty_set.has(var_to_delete)) { + continue; + } + const auto& lb = uncertainty_set.get_var_lb(var_to_delete); + const auto& ub = uncertainty_set.get_var_ub(var_to_delete); + const auto& type = uncertainty_set.get_var_type(var_to_delete); + const auto& column = uncertainty_set.get_var_column(var_to_delete); + const auto& obj = uncertainty_set.get_var_obj(var_to_delete); + uncertainty_set.add(var_to_add, TempVar(lb, ub, type, obj, LinExpr(column))); + uncertainty_set.remove(var_to_delete); + } + + for (const auto& var : uncertainty_set.vars()) { + if (!t_model.has(var)) { + variables.emplace(var.name(), var); } } @@ -99,9 +119,9 @@ idol::Robust::read_from_file(Model& t_model, const std::string& t_path_to_par, c } if (section == MAT) { - const auto ctr = constraints.at(cols[0]); - const auto var = variables.at(cols[1]); - const auto unc_par = variables.at(cols[2]); + const auto& ctr = constraints.at(cols[0]); + const auto& var = variables.at(cols[1]); + const auto& unc_par = variables.at(cols[2]); const auto val = std::stod(cols[3]); auto current_expr = result.uncertain_mat_coeff(ctr, var) + val * unc_par; result.set_uncertain_mat_coeff(ctr, var, current_expr); @@ -109,10 +129,6 @@ idol::Robust::read_from_file(Model& t_model, const std::string& t_path_to_par, c } - if (is_decision_dependent) { - throw Exception("Decision-dependent problems are not implemented."); - } - return std::move(result); } diff --git a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/CriticalValueColumnAndConstraintGeneration.cpp b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/CriticalValueColumnAndConstraintGeneration.cpp index 752f0a2a9..fc24779b5 100644 --- a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/CriticalValueColumnAndConstraintGeneration.cpp +++ b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/CriticalValueColumnAndConstraintGeneration.cpp @@ -14,7 +14,9 @@ idol::Robust::CriticalValueColumnAndConstraintGeneration::CriticalValueColumnAnd : OptimizerFactoryWithDefaultParameters(*this), m_description(t_src.m_description), m_master_optimizer_factory(t_src.m_master_optimizer_factory ? t_src.m_master_optimizer_factory->clone() : nullptr), - m_deterministic_optimizer_factory(t_src.m_deterministic_optimizer_factory ? t_src.m_deterministic_optimizer_factory->clone() : nullptr) { + m_deterministic_optimizer_factory(t_src.m_deterministic_optimizer_factory ? t_src.m_deterministic_optimizer_factory->clone() : nullptr), + m_use_indicator(t_src.m_use_indicator) +{ } @@ -36,7 +38,8 @@ idol::Optimizer* idol::Robust::CriticalValueColumnAndConstraintGeneration::creat t_model, m_description, *m_master_optimizer_factory, - *m_deterministic_optimizer_factory + *m_deterministic_optimizer_factory, + m_use_indicator.value_or(false) ); return result; @@ -63,3 +66,14 @@ idol::Robust::CriticalValueColumnAndConstraintGeneration& idol::Robust::Critical return *this; } + +idol::Robust::CriticalValueColumnAndConstraintGeneration& idol::Robust::CriticalValueColumnAndConstraintGeneration::with_indicator(bool t_value) { + + if (m_use_indicator.has_value()) { + throw Exception("The use of indicator functions has already been configured."); + } + + m_use_indicator = t_value; + + return *this; +} diff --git a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp index 51965abe4..4a65f20ea 100644 --- a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp +++ b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp @@ -379,7 +379,7 @@ void idol::CVCCG::Formulation::add_scenario_to_master(const std::list get_best_obj() + get_tol_mip_absolute_gap()) { + std::cerr << "The current best bound is larger than current best obj. This should should not happen. Terminating..." << std::endl; + set_status(Fail); + set_reason(Numerical); + terminate(); + return; + } + + set_status(is_pos_inf(get_best_obj()) ? Infeasible : Feasible); + + if (get_remaining_time() == 0) { + std::cout << "The time limit has been reached. Terminating..." << std::endl; + set_reason(TimeLimit); + terminate(); + return; + } + + if (m_n_iterations > get_param_iteration_limit()) { + std::cout << "The iteration limit has been reached. Terminating..." << std::endl; + set_reason(IterLimit); + terminate(); + return; + } + + if (Algorithm::get_relative_gap() <= get_tol_mip_relative_gap() || Algorithm::get_absolute_gap() <= get_tol_mip_absolute_gap()) { + std::cout << "The optimality gap has been closed. Terminating..." << std::endl; + set_status(Optimal); + set_reason(Proved); + terminate(); + return; + } + } void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::solve_sub_problems() { From 3aadc0e4a25cabec7ebdde558e9fbaad7378317e Mon Sep 17 00:00:00 2001 From: Henri Lefebvre Date: Wed, 6 May 2026 15:04:13 +0200 Subject: [PATCH 7/9] use Model::read_from_file instead of GLPK::read_from_file --- bin/method-managers/MILPMethodManager.h | 10 ++++++--- bin/solve_adjustable_robust.h | 23 ++++++++++++--------- bin/solve_milp.h | 2 +- lib/src/bilevel/modeling/read_from_file.cpp | 4 ++-- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/bin/method-managers/MILPMethodManager.h b/bin/method-managers/MILPMethodManager.h index 9c4f13778..717795200 100644 --- a/bin/method-managers/MILPMethodManager.h +++ b/bin/method-managers/MILPMethodManager.h @@ -84,10 +84,10 @@ class MILPMethodManager : public MethodManager { return std::move(result); } - std::unique_ptr 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); @@ -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 get_sub_milp_optimizer(const Arguments& t_args, bool t_continuous_relaxation = false) { + return operator()(get_sub_milp_method(t_args), t_continuous_relaxation); } }; diff --git a/bin/solve_adjustable_robust.h b/bin/solve_adjustable_robust.h index 299d2b271..084feae45 100644 --- a/bin/solve_adjustable_robust.h +++ b/bin/solve_adjustable_robust.h @@ -13,7 +13,6 @@ #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" @@ -233,20 +232,22 @@ inline void solve_robust(const Arguments& t_args) { robust_method_manager.print_available_methods(t_args); const auto method = robust_method_manager.get_method(t_args); + 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()); + 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); @@ -255,7 +256,7 @@ inline void solve_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(); @@ -271,8 +272,6 @@ inline void solve_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); @@ -294,17 +293,21 @@ inline void solve_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") { - 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); + ccg.with_master_optimizer(*sub_milp_optimizer); + ccg.with_deterministic_optimizer(*sub_milp_optimizer); ccg.with_indicator(true); + model.use(ccg); } diff --git a/bin/solve_milp.h b/bin/solve_milp.h index 10ea138a2..aaf5d0bba 100644 --- a/bin/solve_milp.h +++ b/bin/solve_milp.h @@ -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()); diff --git a/lib/src/bilevel/modeling/read_from_file.cpp b/lib/src/bilevel/modeling/read_from_file.cpp index 041417741..368e1335d 100644 --- a/lib/src/bilevel/modeling/read_from_file.cpp +++ b/lib/src/bilevel/modeling/read_from_file.cpp @@ -276,7 +276,7 @@ std::pair Bilevel::read_from_file(Env& t_env, const AuxParser parser(t_env, lower_level_description, t_path_to_aux, - [](Env& t_env, const std::string& t_file) { return GLPK::read_from_file(t_env, t_file); }); + [](Env& t_env, const std::string& t_file) { return Model::read_from_file(t_env, t_file); }); return { std::move(parser.model()), @@ -291,7 +291,7 @@ Bilevel::Description idol::Bilevel::read_bilevel_description(const Model& t_mode AuxParser parser(t_model, result, t_path_to_aux, - [](Env& t_env, const std::string& t_file) { return GLPK::read_from_file(t_env, t_file); }); + [](Env& t_env, const std::string& t_file) { return Model::read_from_file(t_env, t_file); }); return result; } \ No newline at end of file From 2e8278e12e603330fccef1f52bdfde77c06cc840 Mon Sep 17 00:00:00 2001 From: Henri Lefebvre Date: Mon, 11 May 2026 10:29:33 +0200 Subject: [PATCH 8/9] add node count to callback --- examples/robust/ddro-kp.example.cpp | 2 - .../callbacks/BranchAndBoundCallback.h | 15 ++++++ .../CallbackAsBranchAndBoundCallback.h | 7 ++- .../optimizers/callbacks/Callback.h | 4 ++ .../wrappers/Cplex/CplexCallbackI.h | 9 ++-- .../wrappers/Gurobi/GurobiCallbackI.h | 1 + .../wrappers/Gurobi/headers/header_gurobi.h | 53 +++++++++++++++++++ .../optimizers/callbacks/Callback.cpp | 5 ++ .../wrappers/Cplex/CplexCallbackI.cpp | 4 ++ .../wrappers/Gurobi/GurobiCallbackI.cpp | 15 ++++++ lib/src/robust/modeling/read_from_file.cpp | 4 +- .../Formulation.cpp | 6 ++- ...icalValueColumnAndConstraintGeneration.cpp | 1 - 13 files changed, 115 insertions(+), 11 deletions(-) diff --git a/examples/robust/ddro-kp.example.cpp b/examples/robust/ddro-kp.example.cpp index 4906396fa..09d6a2f26 100644 --- a/examples/robust/ddro-kp.example.cpp +++ b/examples/robust/ddro-kp.example.cpp @@ -66,8 +66,6 @@ void solve_with_critical_value_ccg(Env& t_env, const Instance& t_instance) { description.set_uncertain_mat_coeff(c, x[j], std::ceil(.5 * t_instance.weights[j]) * u[j]); } - std::cout << description << std::endl; - // Print robust model // std::cout << Robust::Description::View(model, description) << std::endl; diff --git a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/BranchAndBoundCallback.h b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/BranchAndBoundCallback.h index f67483921..6845d80a6 100644 --- a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/BranchAndBoundCallback.h +++ b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/BranchAndBoundCallback.h @@ -61,6 +61,8 @@ class idol::BranchAndBoundCallbackI : public AbstractBranchAndBoundCallbackI::best_obj() const { return m_parent->get_best_obj(); } +template +unsigned int idol::BranchAndBoundCallbackI::node_count() const { + return m_parent->n_solved_nodes(); +} + template double idol::BranchAndBoundCallbackI::best_bound() const { return m_parent->get_best_bound(); @@ -207,6 +214,8 @@ class idol::BranchAndBoundCallback { [[nodiscard]] double best_obj() const; + [[nodiscard]] unsigned int node_count() const; + void terminate(); private: BranchAndBoundCallbackI* m_interface = nullptr; @@ -228,6 +237,12 @@ double idol::BranchAndBoundCallback::best_obj() const { return m_interface->best_obj(); } +template +unsigned int idol::BranchAndBoundCallback::node_count() const { + throw_if_no_interface(); + return m_interface->node_count(); +} + template double idol::BranchAndBoundCallback::best_bound() const { throw_if_no_interface(); diff --git a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/CallbackAsBranchAndBoundCallback.h b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/CallbackAsBranchAndBoundCallback.h index f48fe7caf..8e02fec37 100644 --- a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/CallbackAsBranchAndBoundCallback.h +++ b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/CallbackAsBranchAndBoundCallback.h @@ -60,11 +60,11 @@ class idol::CallbackAsBranchAndBoundCallback : public BranchAndBoundCallbackFact return m_parent.time(); } - double best_obj() const override { + [[nodiscard]] double best_obj() const override { return m_parent.best_obj(); } - double best_bound() const override { + [[nodiscard]] double best_bound() const override { return m_parent.best_bound(); } @@ -72,6 +72,9 @@ class idol::CallbackAsBranchAndBoundCallback : public BranchAndBoundCallbackFact m_parent.terminate(); } + [[nodiscard]] unsigned node_count() const override { + return m_parent.node_count(); + } }; Interface m_interface; diff --git a/lib/include/idol/mixed-integer/optimizers/callbacks/Callback.h b/lib/include/idol/mixed-integer/optimizers/callbacks/Callback.h index 2f203e91d..de83c9a70 100644 --- a/lib/include/idol/mixed-integer/optimizers/callbacks/Callback.h +++ b/lib/include/idol/mixed-integer/optimizers/callbacks/Callback.h @@ -59,6 +59,8 @@ class idol::CallbackI { [[nodiscard]] virtual double best_bound() const = 0; + [[nodiscard]] virtual unsigned int node_count() const = 0; + virtual void terminate() = 0; void execute(Callback& t_cb, CallbackEvent t_event); @@ -99,6 +101,8 @@ class idol::Callback { */ [[nodiscard]] virtual PrimalPoint primal_solution() const; + [[nodiscard]] virtual unsigned int node_count() const; + /** * Returns the current time * @return the optimizer's time diff --git a/lib/include/idol/mixed-integer/optimizers/wrappers/Cplex/CplexCallbackI.h b/lib/include/idol/mixed-integer/optimizers/wrappers/Cplex/CplexCallbackI.h index d1fe6974a..d5f8487e4 100644 --- a/lib/include/idol/mixed-integer/optimizers/wrappers/Cplex/CplexCallbackI.h +++ b/lib/include/idol/mixed-integer/optimizers/wrappers/Cplex/CplexCallbackI.h @@ -109,12 +109,15 @@ class idol::CplexCallbackI : public CallbackI { [[nodiscard]] double best_bound() const override; - double get_var_primal(const Var& t_var) const; - double get_var_local_lb(const Var& t_var) const; - double get_var_local_ub(const Var& t_var) const; + [[nodiscard]] unsigned node_count() const override; + + [[nodiscard]] double get_var_primal(const Var& t_var) const; + [[nodiscard]] double get_var_local_lb(const Var& t_var) const; + [[nodiscard]] double get_var_local_ub(const Var& t_var) const; void set_var_local_lb(const Var& t_var, double t_bound); void set_var_local_ub(const Var& t_var, double t_bound); +public: void terminate() override; friend class idol::Optimizers::Cplex; diff --git a/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/GurobiCallbackI.h b/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/GurobiCallbackI.h index 1d85b33e0..c2f99a07b 100644 --- a/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/GurobiCallbackI.h +++ b/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/GurobiCallbackI.h @@ -56,6 +56,7 @@ class idol::GurobiCallbackI : public CallbackI { [[nodiscard]] const Timer &time() const override; [[nodiscard]] double best_obj() const override; [[nodiscard]] double best_bound() const override; + [[nodiscard]] unsigned node_count() const override; void terminate() override; }; diff --git a/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/headers/header_gurobi.h b/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/headers/header_gurobi.h index a75d3057b..998ec7d47 100644 --- a/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/headers/header_gurobi.h +++ b/lib/include/idol/mixed-integer/optimizers/wrappers/Gurobi/headers/header_gurobi.h @@ -54,6 +54,59 @@ #define idol_GRB_CB_MIPNODE_STATUS 5001 #define idol_GRB_CB_MIPNODE_REL 5002 +// Callback infos +#define idol_GRB_CB_PRE_COLDEL 1000 +#define idol_GRB_CB_PRE_ROWDEL 1001 +#define idol_GRB_CB_PRE_SENCHG 1002 +#define idol_GRB_CB_PRE_BNDCHG 1003 +#define idol_GRB_CB_PRE_COECHG 1004 + +#define idol_GRB_CB_SPX_ITRCNT 2000 +#define idol_GRB_CB_SPX_OBJVAL 2001 +#define idol_GRB_CB_SPX_PRIMINF 2002 +#define idol_GRB_CB_SPX_DUALINF 2003 +#define idol_GRB_CB_SPX_ISPERT 2004 + +#define idol_GRB_CB_MIP_OBJBST 3000 +#define idol_GRB_CB_MIP_OBJBND 3001 +#define idol_GRB_CB_MIP_NODCNT 3002 +#define idol_GRB_CB_MIP_SOLCNT 3003 +#define idol_GRB_CB_MIP_CUTCNT 3004 +#define idol_GRB_CB_MIP_NODLFT 3005 +#define idol_GRB_CB_MIP_ITRCNT 3006 +#define idol_GRB_CB_MIP_OBJBNDC 3007 + +#define idol_GRB_CB_MIPSOL_SOL 4001 +#define idol_GRB_CB_MIPSOL_OBJ 4002 +#define idol_GRB_CB_MIPSOL_OBJBST 4003 +#define idol_GRB_CB_MIPSOL_OBJBND 4004 +#define idol_GRB_CB_MIPSOL_NODCNT 4005 +#define idol_GRB_CB_MIPSOL_SOLCNT 4006 +#define idol_GRB_CB_MIPSOL_OBJBNDC 4007 + +#define idol_GRB_CB_MIPNODE_STATUS 5001 +#define idol_GRB_CB_MIPNODE_REL 5002 +#define idol_GRB_CB_MIPNODE_OBJBST 5003 +#define idol_GRB_CB_MIPNODE_OBJBND 5004 +#define idol_GRB_CB_MIPNODE_NODCNT 5005 +#define idol_GRB_CB_MIPNODE_SOLCNT 5006 +#define idol_GRB_CB_MIPNODE_BRVAR 5007 +#define idol_GRB_CB_MIPNODE_OBJBNDC 5008 + +#define idol_GRB_CB_MSG_STRING 6001 +#define idol_GRB_CB_RUNTIME 6002 + +#define idol_GRB_CB_BARRIER_ITRCNT 7001 +#define idol_GRB_CB_BARRIER_PRIMOBJ 7002 +#define idol_GRB_CB_BARRIER_DUALOBJ 7003 +#define idol_GRB_CB_BARRIER_PRIMINF 7004 +#define idol_GRB_CB_BARRIER_DUALINF 7005 +#define idol_GRB_CB_BARRIER_COMPL 7006 + +#define idol_GRB_FEASRELAX_LINEAR 0 +#define idol_GRB_FEASRELAX_QUADRATIC 1 +#define idol_GRB_FEASRELAX_CARDINALITY 2 + extern "C" { typedef struct _GRBmodel GRBmodel; diff --git a/lib/src/mixed-integer/optimizers/callbacks/Callback.cpp b/lib/src/mixed-integer/optimizers/callbacks/Callback.cpp index 47a6e46fb..d7fbff2b0 100644 --- a/lib/src/mixed-integer/optimizers/callbacks/Callback.cpp +++ b/lib/src/mixed-integer/optimizers/callbacks/Callback.cpp @@ -25,6 +25,11 @@ idol::PrimalPoint idol::Callback::primal_solution() const { return m_interface->primal_solution(); } +unsigned int idol::Callback::node_count() const { + throw_if_no_interface(); + return m_interface->node_count(); +} + const idol::Timer &idol::Callback::time() const { throw_if_no_interface(); return m_interface->time(); diff --git a/lib/src/mixed-integer/optimizers/wrappers/Cplex/CplexCallbackI.cpp b/lib/src/mixed-integer/optimizers/wrappers/Cplex/CplexCallbackI.cpp index 81b1bf965..060a4fb31 100644 --- a/lib/src/mixed-integer/optimizers/wrappers/Cplex/CplexCallbackI.cpp +++ b/lib/src/mixed-integer/optimizers/wrappers/Cplex/CplexCallbackI.cpp @@ -80,6 +80,10 @@ double idol::CplexCallbackI::best_bound() const { throw Exception("Not implemented"); } +unsigned idol::CplexCallbackI::node_count() const { + throw Exception("Not implemented (node count)"); +} + void idol::CplexCallbackI::terminate() { std::cerr << "terminate()" << std::endl; m_caller->abort(); diff --git a/lib/src/mixed-integer/optimizers/wrappers/Gurobi/GurobiCallbackI.cpp b/lib/src/mixed-integer/optimizers/wrappers/Gurobi/GurobiCallbackI.cpp index 3c489326a..cf4efd31c 100644 --- a/lib/src/mixed-integer/optimizers/wrappers/Gurobi/GurobiCallbackI.cpp +++ b/lib/src/mixed-integer/optimizers/wrappers/Gurobi/GurobiCallbackI.cpp @@ -219,6 +219,21 @@ double idol::GurobiCallbackI::best_bound() const { return m_best_bound; } +unsigned idol::GurobiCallbackI::node_count() const { + auto& lib = idol::Optimizers::Gurobi::get_dynamic_lib(); + int error = 0; + double result = 0.; + if (m_where == idol_GRB_CB_MIPSOL) { + error = lib.GRBcbget(m_cbdata, idol_GRB_CB_MIPSOL, idol_GRB_CB_MIPSOL_NODCNT, &result); + } else if (m_where == idol_GRB_CB_MIP) { + error = lib.GRBcbget(m_cbdata, idol_GRB_CB_MIP, idol_GRB_CB_MIP_NODCNT, &result); + } + if (error) { + throw Exception("Failed to retrieve node count."); + } + return (unsigned int) result; +} + void idol::GurobiCallbackI::terminate() { auto& lib = idol::Optimizers::Gurobi::get_dynamic_lib(); lib.GRBterminate(m_parent.m_model); diff --git a/lib/src/robust/modeling/read_from_file.cpp b/lib/src/robust/modeling/read_from_file.cpp index f393d790a..35f0826c4 100644 --- a/lib/src/robust/modeling/read_from_file.cpp +++ b/lib/src/robust/modeling/read_from_file.cpp @@ -107,8 +107,8 @@ idol::Robust::read_from_file(Model& t_model, const std::string& t_path_to_par, c } if (section == RHS) { - const auto ctr = constraints.at(cols[0]); - const auto unc_par = variables.at(cols[1]); + const auto& ctr = constraints.at(cols[0]); + const auto& unc_par = variables.at(cols[1]); const auto val = std::stod(cols[2]); auto current_expr = result.uncertain_rhs(ctr) + val * unc_par; result.set_uncertain_rhs(ctr, current_expr); diff --git a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp index 4a65f20ea..abe991321 100644 --- a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp +++ b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Formulation.cpp @@ -212,10 +212,13 @@ update_sub_problem_objective(const PrimalPoint& t_master_solution, const Uncerta for (const auto& [var, unc_coeff] : description.uncertain_mat_coeffs(ctr)) { fixed_row += t_master_solution.get(var) * unc_coeff; } + std::cout << description.uncertain_rhs(ctr) << std::endl; for (const auto& [unc_param, coeff] : description.uncertain_rhs(ctr)) { - fixed_row += coeff * unc_param; + fixed_row += -coeff * unc_param; } + std::cout << fixed_row << std::endl; + if (type == LessOrEqual) { m_sub_problem.set_obj_expr(fixed_rhs - fixed_row); } else if (type == GreaterOrEqual) { @@ -264,6 +267,7 @@ double idol::CVCCG::Formulation::compute_critical_value(const Ctr& t_ctr, const const auto& row = uncertainty_set.get_ctr_row(t_ctr); const auto type = uncertainty_set.get_ctr_type(t_ctr); + std::cout << type << std::endl; assert(type == LessOrEqual); double result = uncertainty_set.get_ctr_rhs(t_ctr) + 1; diff --git a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.cpp b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.cpp index 07d8e262b..69bfd5ef1 100644 --- a/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.cpp +++ b/lib/src/robust/optimizers/critical-value-column-and-constraint-generation/Optimizers_CriticalValueColumnAndConstraintGeneration.cpp @@ -107,7 +107,6 @@ void idol::Optimizers::Robust::CriticalValueColumnAndConstraintGeneration::solve const auto& master = m_formulation->master(); const auto& master_solution = save_primal(master); - const unsigned int n_uncertainties = m_formulation->n_uncertainties(); m_formulation->update_sub_problem_constraints(master_solution); From 9a57404ad855db0a70be7308cbc7cffe41b58a62 Mon Sep 17 00:00:00 2001 From: Henri Lefebvre Date: Mon, 11 May 2026 13:47:19 +0200 Subject: [PATCH 9/9] upgrade version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e83d809f2..b2678276c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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")