diff --git a/dev/main.cpp b/dev/main.cpp index 39d652a0..197d1619 100644 --- a/dev/main.cpp +++ b/dev/main.cpp @@ -22,9 +22,85 @@ #include "idol/mixed-integer/modeling/models/KKT.h" #include "idol/mixed-integer/problems/facility-location-problem/FLP_Instance.h" #include "idol/robust/modeling/Description.h" +#include "idol/bilevel/modeling/Description.h" using namespace idol; +Model make_deterministic(const Model& t_model, const Robust::Description& t_description) { + + auto& env = t_model.env(); + Model result(env); + + for (const auto& var : t_model.vars()) { + const double lb = t_model.get_var_lb(var); + const double ub = t_model.get_var_ub(var); + const auto type = t_model.get_var_type(var); + result.add(var, TempVar(lb, ub, type, 0, LinExpr())); + } + + for (const auto& ctr : t_model.ctrs()) { + const auto& row = t_model.get_ctr_row(ctr); + const auto rhs = t_model.get_ctr_rhs(ctr); + const auto type = t_model.get_ctr_type(ctr); + + const auto& uncertain_mat_coeffs = t_description.uncertain_mat_coeffs(ctr); + const auto& uncertain_rhs = t_description.uncertain_rhs(ctr); + + const bool has_lhs_uncertainty = !uncertain_mat_coeffs.is_zero(Tolerance::Sparsity); + const bool has_rhs_uncertainty = !uncertain_rhs.is_zero(Tolerance::Sparsity); + + if (!has_lhs_uncertainty && !has_rhs_uncertainty) { + result.add(ctr, TempCtr(LinExpr(row), type, rhs)); + continue; + } + + if (type == Equal) { + throw Exception("Cannot make equality constraints deterministic."); + } + + const auto& uncertainty_set = t_description.uncertainty_set(); + + QuadExpr objective = row; + for (const auto& [var, uncertain_coeff] : row) { + const auto& uncertain_mat_coeff = uncertain_mat_coeffs.get(var); + objective += uncertain_mat_coeff * var; + } + for (const auto& [var, uncertain_coeff] : uncertain_rhs) { + objective -= uncertain_coeff * var; + } + + if (type == LessOrEqual) { + objective *= -1; + } + + Reformulators::KKT kkt(uncertainty_set, + objective, + [&](const Var& t_var) { return uncertainty_set.has(t_var); }, + [&](const Ctr& t_ctr) { return uncertainty_set.has(t_ctr); }, + [&](const QCtr& t_qvar) { return uncertainty_set.has(t_qvar); } + ); + kkt.set_prefix("robust_" + ctr.name() + "_"); + kkt.add_dual_variables(result); + kkt.add_dual_constraints(result); + + auto dual_objective = kkt.get_dual_obj_expr(); + if (type == LessOrEqual) { + dual_objective *= -1; + } + + assert(!dual_objective.has_quadratic()); + + result.add(ctr, TempCtr( + std::move(dual_objective.affine().linear()), + type, + -dual_objective.affine().constant()) + ); + + } + + return result; +} + int main(int t_argc, const char** t_argv) { Env env; @@ -44,7 +120,7 @@ int main(int t_argc, const char** t_argv) { auto y = Var::make_vector(env, Dim<2>(n_facilities, n_customers), 0., 1., Continuous, 0., "y"); Model uncertainty_set(env); - const auto xi = uncertainty_set.add_vars(Dim<1>(n_facilities), 0., 1., Binary, 0., "xi"); + const auto xi = uncertainty_set.add_vars(Dim<1>(n_facilities), 0., 1., Continuous, 0., "xi"); uncertainty_set.add_ctr(idol_Sum(i, Range(n_facilities), xi[i]) <= Gamma); Model model(env); @@ -77,5 +153,15 @@ int main(int t_argc, const char** t_argv) { std::cout << Robust::Description::View(model, description) << std::endl; + auto deterministic = make_deterministic(model, description); + + deterministic.use(Gurobi()); + + deterministic.optimize(); + + std::cout << deterministic.get_status() << std::endl; + std::cout << deterministic.optimizer().time().count() << std::endl; + std::cout << save_primal(deterministic) << std::endl; + return 0; } diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 72d564a7..1d80a138 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -3,9 +3,11 @@ function(ADD_EXAMPLE NAME) target_link_libraries(example_${NAME} PRIVATE idol) file(GLOB DATA_FILES "${CMAKE_CURRENT_SOURCE_DIR}/${NAME}.data.*") foreach(FILE ${DATA_FILES}) + message(STATUS "Copying ${FILE}") add_custom_command(TARGET example_${NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${FILE} $) endforeach() endfunction() add_subdirectory(mixed-integer) -add_subdirectory(bilevel) \ No newline at end of file +add_subdirectory(bilevel) +add_subdirectory(robust) \ No newline at end of file diff --git a/examples/robust/CMakeLists.txt b/examples/robust/CMakeLists.txt new file mode 100644 index 00000000..7e7d0e02 --- /dev/null +++ b/examples/robust/CMakeLists.txt @@ -0,0 +1 @@ +add_example(robust-facility-location) \ No newline at end of file diff --git a/examples/robust/robust-facility-location.data.txt b/examples/robust/robust-facility-location.data.txt new file mode 100644 index 00000000..f62be9d9 --- /dev/null +++ b/examples/robust/robust-facility-location.data.txt @@ -0,0 +1,41 @@ +10 20 +109.6 131.484 +169.498 103.513 +24.7946 41.8319 +150.331 156.535 +140.651 128.462 +68.8231 66.0825 +5.40097 33.6793 +123.19 113.974 +118.503 152.262 +27.5682 125.587 +36.1992 +5.60365 +10.0339 +20.1965 +17.4686 +35.8237 +14.8866 +2.75196 +36.7471 +2.76135 +12.0892 +16.2641 +11.3815 +8.96618 +32.1548 +13.7501 +0.631599 +26.9407 +36.4762 +10.0098 +0.674345 0.377274 0.60077 0.725902 0.347025 0.648906 0.635155 0.607522 0.333549 0.300607 0.416598 0.628605 0.753687 0.644812 0.405533 0.152511 0.424802 0.261078 0.50283 0.403443 +0.79483 0.0798539 0.347963 0.756363 0.654002 0.484362 0.663717 0.820894 0.220568 0.548021 0.218845 0.779403 0.804299 0.507375 0.592223 0.172742 0.63654 0.0894973 0.819295 0.712606 +0.988408 0.473435 0.741449 1.02582 0.420089 0.859221 0.932609 0.913111 0.206479 0.606641 0.592807 0.943148 1.05883 0.871605 0.719628 0.296455 0.735305 0.405338 0.70923 0.472351 +0.801213 0.930165 1.12413 0.970236 0.281743 1.11036 0.911725 0.579372 0.840547 0.467774 0.937797 0.721997 0.961574 1.08599 0.57822 0.70739 0.533045 0.812734 0.148052 0.238144 +0.033511 0.742563 0.729705 0.21527 0.807152 0.579068 0.209073 0.232984 0.91811 0.384516 0.620859 0.0675881 0.187979 0.528276 0.248045 0.699975 0.263439 0.677736 0.632054 0.831136 +0.379508 0.394834 0.402656 0.338167 0.703938 0.315728 0.243857 0.475284 0.607753 0.336413 0.270847 0.38184 0.381389 0.285442 0.271919 0.405371 0.331478 0.346229 0.681973 0.748633 +1.01954 0.586318 0.852622 1.08059 0.340762 0.955962 0.989606 0.914744 0.331927 0.616543 0.693984 0.966318 1.10785 0.963435 0.742256 0.385068 0.74801 0.505968 0.654321 0.385256 +0.124851 0.647767 0.637432 0.198938 0.766693 0.495492 0.15157 0.278676 0.831333 0.339767 0.525301 0.137635 0.200255 0.446836 0.203461 0.614955 0.238989 0.585824 0.629149 0.796432 +0.822042 0.611747 0.850139 0.920143 0.113758 0.898665 0.836445 0.683577 0.462689 0.406345 0.66767 0.759121 0.93636 0.891583 0.542633 0.376275 0.534339 0.501362 0.400407 0.172095 +0.728693 0.27883 0.523355 0.749647 0.439242 0.602651 0.655937 0.692852 0.235195 0.391798 0.348472 0.692708 0.785082 0.607712 0.47688 0.0436219 0.506139 0.16976 0.61561 0.497567 diff --git a/examples/robust/robust-facility-location.example.cpp b/examples/robust/robust-facility-location.example.cpp new file mode 100644 index 00000000..6bd9089e --- /dev/null +++ b/examples/robust/robust-facility-location.example.cpp @@ -0,0 +1,99 @@ +// +// Created by henri on 28.11.24. +// +// +// Created by henri on 06/04/23. +// +#include +#include "idol/modeling.h" +#include "idol/mixed-integer/problems/facility-location-problem/FLP_Instance.h" +#include "idol/mixed-integer/optimizers/branch-and-bound/BranchAndBound.h" +#include "idol/mixed-integer/optimizers/branch-and-bound/branching-rules/factories/PseudoCost.h" +#include "idol/mixed-integer/optimizers/branch-and-bound/node-selection-rules/factories/BestEstimate.h" +#include "idol/mixed-integer/optimizers/wrappers/HiGHS/HiGHS.h" +#include "idol/mixed-integer/optimizers/wrappers/GLPK/GLPK.h" +#include "idol/mixed-integer/optimizers/wrappers/Gurobi/Gurobi.h" +#include "idol/mixed-integer/optimizers/branch-and-bound/node-selection-rules/factories/BestBound.h" +#include "idol/mixed-integer/optimizers/branch-and-bound/branching-rules/factories/MostInfeasible.h" +#include "idol/mixed-integer/optimizers/callbacks/ReducedCostFixing.h" +#include "idol/robust/modeling/Description.h" +#include "idol/robust/optimizers/deterministic/Deterministic.h" + +using namespace idol; + +int main(int t_argc, const char** t_argv) { + + Env env; + + // Read instance + const auto instance = Problems::FLP::read_instance_1991_Cornuejols_et_al("robust-facility-location.data.txt"); + const unsigned int n_customers = instance.n_customers(); + const unsigned int n_facilities = instance.n_facilities(); + + // Uncertainty set + Model uncertainty_set(env); + const double Gamma = 1; + const auto xi = uncertainty_set.add_vars(Dim<1>(n_customers), 0., 1., Continuous, 0., "xi"); + uncertainty_set.add_ctr(idol_Sum(i, Range(n_customers), xi[i]) <= Gamma); + + // Make model + Model model(env); + Robust::Description description(uncertainty_set); + + auto x = model.add_vars(Dim<1>(n_facilities), 0., 1., Binary, 0., "x"); + auto y = model.add_vars(Dim<2>(n_facilities, n_customers), 0., 1., Continuous, 0., "y"); + + for (unsigned int i = 0 ; i < n_facilities ; ++i) { + + const auto c = model.add_ctr(idol_Sum(j, Range(n_customers), instance.demand(j) * y[i][j]) <= instance.capacity(i)); + + for (unsigned int j = 0 ; j < n_customers ; ++j) { + description.set_uncertain_mat_coeff(c, y[i][j], 0.2 * instance.demand(j) * xi[j]); + } + + } + + for (unsigned int j = 0 ; j < n_customers ; ++j) { + model.add_ctr(idol_Sum(i, Range(n_facilities), y[i][j]) == 1); + } + + for (unsigned int i = 0 ; i < n_facilities ; ++i) { + for (unsigned int j = 0 ; j < n_customers ; ++j) { + model.add_ctr(y[i][j] <= x[i]); + } + } + + model.set_obj_expr(idol_Sum(i, Range(n_facilities), + instance.fixed_cost(i) * x[i] + + idol_Sum(j, Range(n_customers), + instance.per_unit_transportation_cost(i, j) * + instance.demand(j) * + y[i][j] + ) + ) + ); + + for (unsigned int i = 0 ; i < n_facilities ; ++i) { + for (unsigned int j = 0; j < n_customers; ++j) { + description.set_uncertain_obj(y[i][j], 0.2 * instance.per_unit_transportation_cost(i, j) * instance.demand(j) * xi[j]); + } + } + + /* + * const auto deterministic_model = Robust::Deterministic::make_model(model, description); + * std::cout << Robust::Description::View(model, description) << std::endl; + */ + + model.use(Gurobi()); + model.optimize(); + std::cout << "Deterministic Problem has value: " << model.get_best_obj() << std::endl; + + model.use( + Robust::Deterministic(description) + .with_deterministic_optimizer(Gurobi().with_logs(false)) + ); + model.optimize(); + std::cout << "Robust Problem has value: " << model.get_best_obj() << std::endl; + + return 0; +} diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 1d62e2e4..cf57457d 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -229,6 +229,10 @@ add_library(idol STATIC include/idol/mixed-integer/optimizers/callbacks/ReducedCostFixing.h include/idol/robust/modeling/Description.h src/robust/modeling/Description.cpp + include/idol/robust/optimizers/deterministic/Deterministic.h + src/robust/optimizers/deterministic/Deterministic.cpp + include/idol/robust/optimizers/deterministic/Optimizers_Deterministic.h + src/robust/optimizers/deterministic/Optimizers_Deterministic.cpp ) find_package(OpenMP REQUIRED) diff --git a/lib/include/idol/bilevel/modeling/Description.h b/lib/include/idol/bilevel/modeling/Description.h index bedc76c7..0dc306a5 100644 --- a/lib/include/idol/bilevel/modeling/Description.h +++ b/lib/include/idol/bilevel/modeling/Description.h @@ -2,8 +2,8 @@ // Created by henri on 20.06.24. // -#ifndef IDOL_DESCRIPTION_H -#define IDOL_DESCRIPTION_H +#ifndef IDOL_BILEVEL_DESCRIPTION_H +#define IDOL_BILEVEL_DESCRIPTION_H #include @@ -62,4 +62,4 @@ class idol::Bilevel::Description { [[nodiscard]] bool is_follower(const QCtr& t_ctr) const { return t_ctr.get(m_lower_level) != MasterId; } }; -#endif //IDOL_DESCRIPTION_H +#endif //IDOL_BILEVEL_DESCRIPTION_H diff --git a/lib/include/idol/mixed-integer/modeling/models/KKT.h b/lib/include/idol/mixed-integer/modeling/models/KKT.h index 33f5f96f..6e2ad985 100644 --- a/lib/include/idol/mixed-integer/modeling/models/KKT.h +++ b/lib/include/idol/mixed-integer/modeling/models/KKT.h @@ -26,6 +26,7 @@ namespace idol { class idol::Reformulators::KKT { const Model& m_primal; const QuadExpr& m_primal_objective; + std::string m_prefix; std::function m_primal_variable_indicator; std::function m_primal_constraint_indicator; @@ -57,8 +58,9 @@ class idol::Reformulators::KKT { KKT(const Model& t_high_point_relaxation, const Bilevel::Description& t_bilevel_description); + void set_prefix(std::string t_prefix) { m_prefix = std::move(t_prefix); } - const QuadExpr& get_dual_obj_expr() const { return m_dual_objective; } + [[nodiscard]] const QuadExpr& get_dual_obj_expr() const { return m_dual_objective; } /** * Adds the coupling variables and constraints to the destination model. diff --git a/lib/include/idol/robust/modeling/Description.h b/lib/include/idol/robust/modeling/Description.h index 9e177c26..27ab355a 100644 --- a/lib/include/idol/robust/modeling/Description.h +++ b/lib/include/idol/robust/modeling/Description.h @@ -17,8 +17,8 @@ namespace idol::Robust { class idol::Robust::Description { mutable std::optional> m_stages; Map>> m_uncertain_mat_coeff; - Map> m_uncertain_rhs; - Map> m_uncertain_obj; + LinExpr> m_uncertain_rhs; + LinExpr> m_uncertain_obj; const Model& m_uncertainty_set; public: explicit Description(const Model& t_uncertainty_set) : m_uncertainty_set(t_uncertainty_set) {} @@ -35,7 +35,11 @@ class idol::Robust::Description { const Model& uncertainty_set() const { return m_uncertainty_set; } - auto uncertain_coefficients() const { return ConstIteratorForward(m_uncertain_mat_coeff); } + auto uncertain_mat_coeffs() const { return ConstIteratorForward(m_uncertain_mat_coeff); } + + auto uncertain_rhs() const { return m_uncertain_rhs; } + + auto uncertain_obj() const { return m_uncertain_obj; } const LinExpr& uncertain_mat_coeff(const Ctr& t_ctr, const Var& t_var) const; @@ -50,11 +54,11 @@ class idol::Robust::Description { } void set_uncertain_rhs(const Ctr& t_ctr, const LinExpr& t_rhs) { - m_uncertain_rhs[t_ctr] = t_rhs; + m_uncertain_rhs.set(t_ctr, t_rhs); } void set_uncertain_obj(const Var& t_var, const LinExpr& t_obj) { - m_uncertain_obj[t_var] = t_obj; + m_uncertain_obj.set(t_var, t_obj); } class View { diff --git a/lib/include/idol/robust/optimizers/deterministic/Deterministic.h b/lib/include/idol/robust/optimizers/deterministic/Deterministic.h new file mode 100644 index 00000000..27b668f8 --- /dev/null +++ b/lib/include/idol/robust/optimizers/deterministic/Deterministic.h @@ -0,0 +1,33 @@ +// +// Created by henri on 28.11.24. +// + +#ifndef IDOL_DETERMINISTIC_H +#define IDOL_DETERMINISTIC_H + +#include "idol/general/optimizers/OptimizerFactory.h" +#include "idol/robust/modeling/Description.h" +#include "idol/mixed-integer/modeling/models/Model.h" + +namespace idol::Robust { + class Deterministic; +} + +class idol::Robust::Deterministic : public OptimizerFactoryWithDefaultParameters { + const Robust::Description& m_description; + std::unique_ptr m_deterministic_optimizer; +public: + explicit Deterministic(const Robust::Description& t_description); + + Deterministic(const Deterministic& t_src); + + Optimizer *operator()(const Model &t_model) const override; + + [[nodiscard]] OptimizerFactory *clone() const override; + + Deterministic& with_deterministic_optimizer(const OptimizerFactory& t_deterministic_optimizer); + + static Model make_model(const Model& t_model, const Robust::Description& t_description); +}; + +#endif //IDOL_DETERMINISTIC_H diff --git a/lib/include/idol/robust/optimizers/deterministic/Optimizers_Deterministic.h b/lib/include/idol/robust/optimizers/deterministic/Optimizers_Deterministic.h new file mode 100644 index 00000000..4bf410b2 --- /dev/null +++ b/lib/include/idol/robust/optimizers/deterministic/Optimizers_Deterministic.h @@ -0,0 +1,94 @@ +// +// Created by henri on 28.11.24. +// + +#ifndef IDOL_OPTIMIZERS_DETERMINISTIC_H +#define IDOL_OPTIMIZERS_DETERMINISTIC_H + +#include "idol/robust/modeling/Description.h" +#include "idol/general/optimizers/Algorithm.h" +#include "idol/general/optimizers/OptimizerFactory.h" +#include "idol/mixed-integer/modeling/models/Model.h" + +namespace idol::Optimizers::Robust { + class Deterministic; +} + +class idol::Optimizers::Robust::Deterministic : public Algorithm { + const idol::Robust::Description& m_description; + std::unique_ptr m_deterministic_optimizer; + std::unique_ptr m_deterministic_model; + + void throw_if_no_deterministic_model() const; +public: + Deterministic(const Model& t_parent, const idol::Robust::Description& t_description, const OptimizerFactory& t_deterministic_optimizer); + + [[nodiscard]] std::string name() const override; + + [[nodiscard]] SolutionStatus get_status() const override; + + [[nodiscard]] SolutionReason get_reason() const override; + + [[nodiscard]] double get_best_obj() const override; + + [[nodiscard]] double get_best_bound() const override; + + [[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 int get_n_solutions() const override; + + [[nodiscard]] unsigned int get_solution_index() const override; + +protected: + void add(const Var &t_var) override; + + void add(const Ctr &t_ctr) override; + + void add(const QCtr &t_ctr) override; + + void remove(const Var &t_var) override; + + void remove(const Ctr &t_ctr) override; + + void remove(const QCtr &t_ctr) override; + + void update() override; + + void write(const std::string &t_name) override; + + void hook_optimize() override; + + void set_solution_index(unsigned int t_index) override; + + void update_obj_sense() override; + + void update_obj() override; + + void update_rhs() override; + + void update_obj_constant() override; + + void update_mat_coeff(const Ctr &t_ctr, const Var &t_var) override; + + void update_ctr_type(const Ctr &t_ctr) override; + + void update_ctr_rhs(const Ctr &t_ctr) override; + + void update_var_type(const Var &t_var) override; + + void update_var_lb(const Var &t_var) override; + + void update_var_ub(const Var &t_var) override; + + void update_var_obj(const Var &t_var) override; +}; + +#endif //IDOL_OPTIMIZERS_DETERMINISTIC_H diff --git a/lib/src/mixed-integer/modeling/models/KKT.cpp b/lib/src/mixed-integer/modeling/models/KKT.cpp index 5e424b4d..8828af06 100644 --- a/lib/src/mixed-integer/modeling/models/KKT.cpp +++ b/lib/src/mixed-integer/modeling/models/KKT.cpp @@ -79,7 +79,7 @@ void idol::Reformulators::KKT::create_dual_variables() { const auto [lb, ub] = bounds_for_dual_variable(type); - m_dual_variables_for_constraints[index] = Var(env, lb, ub, Continuous, 0, "dual_" + ctr.name()); + m_dual_variables_for_constraints[index] = Var(env, lb, ub, Continuous, 0, m_prefix + "dual_" + ctr.name()); } @@ -97,7 +97,7 @@ void idol::Reformulators::KKT::create_dual_variables() { const auto [lb, ub] = bounds_for_dual_variable(type); - m_dual_variables_for_qconstraints[index] = Var(env, lb, ub, Continuous, 0, "dual_" + qctr.name()); + m_dual_variables_for_qconstraints[index] = Var(env, lb, ub, Continuous, 0, m_prefix + "dual_" + qctr.name()); } @@ -117,7 +117,7 @@ void idol::Reformulators::KKT::create_dual_variables() { continue; } - m_dual_variables_for_lower_bounds[index] = Var(env, 0, Inf, Continuous, 0, "dual_lb_" + var.name()); + m_dual_variables_for_lower_bounds[index] = Var(env, 0, Inf, Continuous, 0, m_prefix + "dual_lb_" + var.name()); } diff --git a/lib/src/robust/modeling/Description.cpp b/lib/src/robust/modeling/Description.cpp index 214ba2a8..3a8e3e27 100644 --- a/lib/src/robust/modeling/Description.cpp +++ b/lib/src/robust/modeling/Description.cpp @@ -45,11 +45,7 @@ idol::Robust::Description::uncertain_mat_coeff(const Ctr &t_ctr, const Var &t_va } const idol::LinExpr &idol::Robust::Description::uncertain_rhs(const idol::Ctr &t_ctr) const { - auto it_ctr = m_uncertain_rhs.find(t_ctr); - if (it_ctr == m_uncertain_rhs.end()) { - return LinExpr::Zero; - } - return it_ctr->second; + return m_uncertain_rhs.get(t_ctr); } const idol::LinExpr> & @@ -62,11 +58,7 @@ idol::Robust::Description::uncertain_mat_coeffs(const idol::Ctr &t_ctr) const { } const idol::LinExpr &idol::Robust::Description::uncertain_obj(const idol::Var &t_var) const { - auto it_var = m_uncertain_obj.find(t_var); - if (it_var == m_uncertain_obj.end()) { - return LinExpr::Zero; - } - return it_var->second; + return m_uncertain_obj.get(t_var); } std::ostream &idol::operator<<(std::ostream &t_os, const idol::Robust::Description::View &t_view) { diff --git a/lib/src/robust/optimizers/deterministic/Deterministic.cpp b/lib/src/robust/optimizers/deterministic/Deterministic.cpp new file mode 100644 index 00000000..96c10528 --- /dev/null +++ b/lib/src/robust/optimizers/deterministic/Deterministic.cpp @@ -0,0 +1,159 @@ +// +// Created by henri on 28.11.24. +// +#include "idol/robust/optimizers/deterministic/Deterministic.h" +#include "idol/mixed-integer/modeling/models/KKT.h" +#include "idol/mixed-integer/modeling/expressions/operations/operators.h" +#include "idol/mixed-integer/modeling/variables/TempVar.h" +#include "idol/robust/optimizers/deterministic/Optimizers_Deterministic.h" + +idol::Robust::Deterministic::Deterministic(const Robust::Description &t_description) : m_description(t_description) { + +} + +idol::Optimizer *idol::Robust::Deterministic::operator()(const idol::Model &t_model) const { + + if (!m_deterministic_optimizer) { + throw Exception("No deterministic optimizer has been set."); + } + + auto* result = new Optimizers::Robust::Deterministic(t_model, m_description, *m_deterministic_optimizer); + + handle_default_parameters(result); + + return result; +} + +idol::OptimizerFactory *idol::Robust::Deterministic::clone() const { + return new Deterministic(*this); +} + +idol::Robust::Deterministic & +idol::Robust::Deterministic::with_deterministic_optimizer(const idol::OptimizerFactory &t_deterministic_optimizer) { + if (m_deterministic_optimizer) { + throw Exception("Deterministic optimizer has already been set."); + } + m_deterministic_optimizer.reset(t_deterministic_optimizer.clone()); + return *this; +} + +idol::Robust::Deterministic::Deterministic(const idol::Robust::Deterministic &t_src) + : m_description(t_src.m_description), + m_deterministic_optimizer(t_src.m_deterministic_optimizer->clone()) { + +} + +idol::Model +idol::Robust::Deterministic::make_model(const idol::Model &t_model, const idol::Robust::Description &t_description) { + + if (t_model.get_obj_sense() != Minimize) { + throw Exception("Only minimization problems are supported."); + } + + if (t_model.qctrs().size() > 0) { + throw Exception("Quadratic constraints are not supported."); + } + + auto& env = t_model.env(); + const auto& uncertainty_set = t_description.uncertainty_set(); + Model result(env); + + // Add variables + for (const auto& var : t_model.vars()) { + const double lb = t_model.get_var_lb(var); + const double ub = t_model.get_var_ub(var); + const auto type = t_model.get_var_type(var); + result.add(var, TempVar(lb, ub, type, 0, LinExpr())); + } + + // Add constraints + for (const auto& ctr : t_model.ctrs()) { + const auto& row = t_model.get_ctr_row(ctr); + const auto rhs = t_model.get_ctr_rhs(ctr); + const auto type = t_model.get_ctr_type(ctr); + + const auto& uncertain_mat_coeffs = t_description.uncertain_mat_coeffs(ctr); + const auto& uncertain_rhs = t_description.uncertain_rhs(ctr); + + const bool has_lhs_uncertainty = !uncertain_mat_coeffs.is_zero(Tolerance::Sparsity); + const bool has_rhs_uncertainty = !uncertain_rhs.is_zero(Tolerance::Sparsity); + + if (!has_lhs_uncertainty && !has_rhs_uncertainty) { + result.add(ctr, TempCtr(LinExpr(row), type, rhs)); + continue; + } + + if (type == Equal) { + throw Exception("Cannot make equality constraints deterministic."); + } + + QuadExpr objective = row; + for (const auto& [var, uncertain_coeff] : row) { + const auto& uncertain_mat_coeff = uncertain_mat_coeffs.get(var); + objective += uncertain_mat_coeff * var; + } + objective -= rhs + uncertain_rhs; + + if (type == LessOrEqual) { + objective *= -1; + } + + Reformulators::KKT kkt(uncertainty_set, + objective, + [&](const Var& t_var) { return uncertainty_set.has(t_var); }, + [&](const Ctr& t_ctr) { return uncertainty_set.has(t_ctr); }, + [&](const QCtr& t_qvar) { return uncertainty_set.has(t_qvar); } + ); + kkt.set_prefix("robust_" + ctr.name() + "_"); + kkt.add_dual_variables(result); + kkt.add_dual_constraints(result); + + auto dual_objective = kkt.get_dual_obj_expr(); + if (type == LessOrEqual) { + dual_objective *= -1; + } + + assert(!dual_objective.has_quadratic()); + + result.add(ctr, TempCtr( + std::move(dual_objective.affine().linear()), + type, + -dual_objective.affine().constant()) + ); + + } + + // Add objective + const auto& obj = t_model.get_obj_expr(); + const auto& uncertain_obj = t_description.uncertain_obj(); + if (uncertain_obj.is_zero(Tolerance::Sparsity)) { + result.set_obj_expr(obj); + } else { + + QuadExpr objective = obj; + for (const auto& [var, coeff] : uncertain_obj) { + objective += coeff * var; + } + + objective *= -1; // Minimization problem + + Reformulators::KKT kkt(uncertainty_set, + objective, + [&](const Var& t_var) { return uncertainty_set.has(t_var); }, + [&](const Ctr& t_ctr) { return uncertainty_set.has(t_ctr); }, + [&](const QCtr& t_qvar) { return uncertainty_set.has(t_qvar); } + ); + kkt.set_prefix("robust_obj_"); + kkt.add_dual_variables(result); + kkt.add_dual_constraints(result); + + auto dual_objective = kkt.get_dual_obj_expr(); + dual_objective *= -1; + + result.set_obj_expr(dual_objective); + + } + + return result; +} + diff --git a/lib/src/robust/optimizers/deterministic/Optimizers_Deterministic.cpp b/lib/src/robust/optimizers/deterministic/Optimizers_Deterministic.cpp new file mode 100644 index 00000000..23830150 --- /dev/null +++ b/lib/src/robust/optimizers/deterministic/Optimizers_Deterministic.cpp @@ -0,0 +1,186 @@ +// +// Created by henri on 28.11.24. +// +#include "idol/robust/optimizers/deterministic/Optimizers_Deterministic.h" +#include "idol/robust/optimizers/deterministic/Deterministic.h" + +idol::Optimizers::Robust::Deterministic::Deterministic(const Model& t_parent, + const idol::Robust::Description &t_description, + const OptimizerFactory &t_deterministic_optimizer) + : Algorithm(t_parent), + m_description(t_description), + m_deterministic_optimizer(t_deterministic_optimizer.clone()) { + +} + +std::string idol::Optimizers::Robust::Deterministic::name() const { + return "deterministic"; +} + +double idol::Optimizers::Robust::Deterministic::get_var_primal(const idol::Var &t_var) const { + throw_if_no_deterministic_model(); + return m_deterministic_model->get_var_primal(t_var); +} + +double idol::Optimizers::Robust::Deterministic::get_var_reduced_cost(const idol::Var &t_var) const { + throw_if_no_deterministic_model(); + return m_deterministic_model->get_var_reduced_cost(t_var); +} + +double idol::Optimizers::Robust::Deterministic::get_var_ray(const idol::Var &t_var) const { + throw_if_no_deterministic_model(); + return m_deterministic_model->get_var_ray(t_var); +} + +double idol::Optimizers::Robust::Deterministic::get_ctr_dual(const idol::Ctr &t_ctr) const { + throw_if_no_deterministic_model(); + return m_deterministic_model->get_ctr_dual(t_ctr); +} + +double idol::Optimizers::Robust::Deterministic::get_ctr_farkas(const idol::Ctr &t_ctr) const { + throw_if_no_deterministic_model(); + return m_deterministic_model->get_ctr_farkas(t_ctr); +} + +unsigned int idol::Optimizers::Robust::Deterministic::get_n_solutions() const { + throw_if_no_deterministic_model(); + return m_deterministic_model->get_n_solutions(); +} + +unsigned int idol::Optimizers::Robust::Deterministic::get_solution_index() const { + throw_if_no_deterministic_model(); + return m_deterministic_model->get_solution_index(); +} + +void idol::Optimizers::Robust::Deterministic::add(const idol::Var &t_var) { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::add(const idol::Ctr &t_ctr) { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::add(const idol::QCtr &t_ctr) { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::remove(const idol::Var &t_var) { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::remove(const idol::Ctr &t_ctr) { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::remove(const idol::QCtr &t_ctr) { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::update() { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::write(const std::string &t_name) { + throw_if_no_deterministic_model(); + m_deterministic_model->write(t_name); +} + +void idol::Optimizers::Robust::Deterministic::hook_optimize() { + + if (!m_deterministic_model) { + m_deterministic_model = std::make_unique(idol::Robust::Deterministic::make_model(parent(), m_description)); + m_deterministic_model->use(*m_deterministic_optimizer); + } + + m_deterministic_model->optimize(); + +} + +void idol::Optimizers::Robust::Deterministic::set_solution_index(unsigned int t_index) { + throw_if_no_deterministic_model(); + m_deterministic_model->set_solution_index(t_index); +} + +void idol::Optimizers::Robust::Deterministic::update_obj_sense() { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::update_obj() { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::update_rhs() { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::update_obj_constant() { + throw_if_no_deterministic_model(); + m_deterministic_model->set_obj_const(parent().get_obj_expr().affine().constant()); +} + +void idol::Optimizers::Robust::Deterministic::update_mat_coeff(const idol::Ctr &t_ctr, const idol::Var &t_var) { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::update_ctr_type(const idol::Ctr &t_ctr) { + m_deterministic_model.reset(); +} + +void idol::Optimizers::Robust::Deterministic::update_ctr_rhs(const idol::Ctr &t_ctr) { + throw_if_no_deterministic_model(); + m_deterministic_model->set_ctr_rhs(t_ctr, parent().get_ctr_rhs(t_ctr)); +} + +void idol::Optimizers::Robust::Deterministic::update_var_type(const idol::Var &t_var) { + throw_if_no_deterministic_model(); + m_deterministic_model->set_var_type(t_var, parent().get_var_type(t_var)); +} + +void idol::Optimizers::Robust::Deterministic::update_var_lb(const idol::Var &t_var) { + throw_if_no_deterministic_model(); + m_deterministic_model->set_var_lb(t_var, parent().get_var_lb(t_var)); +} + +void idol::Optimizers::Robust::Deterministic::update_var_ub(const idol::Var &t_var) { + throw_if_no_deterministic_model(); + m_deterministic_model->set_var_ub(t_var, parent().get_var_ub(t_var)); +} + +void idol::Optimizers::Robust::Deterministic::update_var_obj(const idol::Var &t_var) { + throw_if_no_deterministic_model(); + m_deterministic_model->set_var_obj(t_var, parent().get_var_obj(t_var)); +} + +void idol::Optimizers::Robust::Deterministic::throw_if_no_deterministic_model() const { + if (!m_deterministic_optimizer) { + throw Exception("Not available."); + } +} + +idol::SolutionStatus idol::Optimizers::Robust::Deterministic::get_status() const { + if (!m_deterministic_model) { + return Algorithm::get_status(); + } + return m_deterministic_model->get_status(); +} + +idol::SolutionReason idol::Optimizers::Robust::Deterministic::get_reason() const { + if (!m_deterministic_model) { + return Algorithm::get_reason(); + } + return m_deterministic_model->get_reason(); +} + +double idol::Optimizers::Robust::Deterministic::get_best_obj() const { + if (!m_deterministic_model) { + return Algorithm::get_best_obj(); + } + return m_deterministic_model->get_best_obj(); +} + +double idol::Optimizers::Robust::Deterministic::get_best_bound() const { + if (!m_deterministic_model) { + return Algorithm::get_best_bound(); + } + return m_deterministic_model->get_best_bound(); +}