diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 53979940..72d564a7 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -8,4 +8,4 @@ function(ADD_EXAMPLE NAME) endfunction() add_subdirectory(mixed-integer) -add_subdirectory(bilevel) +add_subdirectory(bilevel) \ No newline at end of file diff --git a/examples/mixed-integer/facility.example.cpp b/examples/mixed-integer/facility.example.cpp index c4289b49..57cc70a0 100644 --- a/examples/mixed-integer/facility.example.cpp +++ b/examples/mixed-integer/facility.example.cpp @@ -11,6 +11,8 @@ #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" using namespace idol; @@ -57,10 +59,10 @@ int main(int t_argc, const char** t_argv) { // Set backend options model.use( BranchAndBound() - .with_node_optimizer(GLPK::ContinuousRelaxation()) - //.add_callback(Cuts::KnapsackCover()) + .with_node_optimizer(Gurobi::ContinuousRelaxation()) .with_branching_rule(PseudoCost()) .with_node_selection_rule(BestEstimate()) + .add_callback(ReducedCostFixing()) .with_logs(true) ); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c498ea8c..b7af16a8 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -227,6 +227,7 @@ add_library(idol STATIC include/idol/general/utils/GenerationPattern.h include/idol/mixed-integer/modeling/models/KKT.h src/mixed-integer/modeling/models/KKT.cpp + include/idol/mixed-integer/optimizers/callbacks/ReducedCostFixing.h ) find_package(OpenMP REQUIRED) diff --git a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/BranchAndBound.h b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/BranchAndBound.h index 3e1fb690..7787e2ce 100644 --- a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/BranchAndBound.h +++ b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/BranchAndBound.h @@ -36,7 +36,6 @@ class idol::BranchAndBound : public OptimizerFactoryWithDefaultParameters m_subtree_depth; std::optional m_log_frequency; - std::optional m_scaling; public: /** * This type is used to exploit [SFINAE](https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error) @@ -175,8 +174,6 @@ class idol::BranchAndBound : public OptimizerFactoryWithDefaultParameters& with_logger(const Logs::BranchAndBound::Factory& t_log_factory); - BranchAndBound& with_scaling(bool t_value); - /** * Adds a callback which will be called by the optimizer. * @@ -206,6 +203,7 @@ class idol::BranchAndBound : public OptimizerFactoryWithDefaultParameters& add_callback(const CallbackFactory& t_callback); + }; template @@ -226,18 +224,6 @@ idol::BranchAndBound::with_logger(const typename idol::Logs::BranchAndBou return *this; } -template -idol::BranchAndBound &idol::BranchAndBound::with_scaling(bool t_value) { - - if (m_scaling.has_value()) { - throw Exception("Scaling has already been configured."); - } - - m_scaling = t_value; - - return *this; -} - template idol::BranchAndBound &idol::BranchAndBound::operator+=(const idol::OptimizerFactory &t_node_optimizer) { return with_node_optimizer(t_node_optimizer); @@ -328,7 +314,6 @@ idol::BranchAndBound::BranchAndBound(const BranchAndBound &t_rhs) m_branching_rule_factory(t_rhs.m_branching_rule_factory ? t_rhs.m_branching_rule_factory->clone() : nullptr), m_node_selection_rule_factory(t_rhs.m_node_selection_rule_factory ? t_rhs.m_node_selection_rule_factory->clone() : nullptr), m_subtree_depth(t_rhs.m_subtree_depth), - m_scaling(t_rhs.m_scaling), m_logger_factory(t_rhs.m_logger_factory ? t_rhs.m_logger_factory->clone() : nullptr) { for (auto& cb : t_rhs.m_callbacks) { @@ -352,20 +337,17 @@ idol::Optimizer *idol::BranchAndBound::operator()(const Model &t_model) c throw Exception("No node selection rule has been given, please call BranchAndBound::with_node_selection_rule to configure."); } - auto* callback_interface = new BranchAndBoundCallbackI(); - std::unique_ptr> default_logger_factory; if (!m_logger_factory) { default_logger_factory = std::make_unique>(); } auto* result = new Optimizers::BranchAndBound(t_model, - *m_relaxation_optimizer_factory, - *m_branching_rule_factory, - *m_node_selection_rule_factory, - callback_interface, - m_scaling.has_value() && m_scaling.value(), - m_logger_factory ? *m_logger_factory : *default_logger_factory); + *m_relaxation_optimizer_factory, + *m_branching_rule_factory, + *m_node_selection_rule_factory, + new BranchAndBoundCallbackI(), + m_logger_factory ? *m_logger_factory : *default_logger_factory); this->handle_default_parameters(result); diff --git a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/Optimizers_BranchAndBound.h b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/Optimizers_BranchAndBound.h index 7ac93f94..ccbbd496 100644 --- a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/Optimizers_BranchAndBound.h +++ b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/Optimizers_BranchAndBound.h @@ -106,7 +106,6 @@ class idol::Optimizers::BranchAndBound : public Algorithm { const BranchingRuleFactory& t_branching_rule_factory, const NodeSelectionRuleFactory& t_node_selection_rule_factory, AbstractBranchAndBoundCallbackI* t_callback, - bool t_scaling, const Logs::BranchAndBound::Factory& t_logger_factory); [[nodiscard]] std::string name() const override { return "Branch-and-Bound"; } @@ -424,7 +423,6 @@ idol::Optimizers::BranchAndBound::BranchAndBound(const Model &t_model const BranchingRuleFactory& t_branching_rule_factory, const NodeSelectionRuleFactory& t_node_selection_rule_factory, AbstractBranchAndBoundCallbackI* t_callback, - bool t_scaling, const Logs::BranchAndBound::Factory& t_logger_factory) : Algorithm(t_model), m_relaxation_optimizer_factory(t_node_optimizer.clone()), @@ -452,7 +450,7 @@ void idol::Optimizers::BranchAndBound::create_relaxations() { auto* relaxation = original_model.clone(); relaxation->use(*m_relaxation_optimizer_factory); m_relaxations.emplace_back(relaxation); - m_node_updators.emplace_back(dynamic_cast*>(NodeInfoT::create_updator(*relaxation))); + m_node_updators.emplace_back(dynamic_cast*>(NodeInfoT::create_updator(parent(), *relaxation))); } } @@ -463,6 +461,7 @@ idol::Node idol::Optimizers::BranchAndBound::create_root_n auto root_node = Node::create_root_node(); assert(root_node.id() == 0); ++m_n_created_nodes; + return root_node; } @@ -637,14 +636,27 @@ template void idol::Optimizers::BranchAndBound::solve(TreeNode& t_node, unsigned int t_relaxation_id) const { - m_node_updators[t_relaxation_id]->prepare(t_node); + auto& node_updator = *m_node_updators[t_relaxation_id]; + auto& relaxation = *m_relaxations[t_relaxation_id]; + + relaxation.optimizer().set_param_best_bound_stop(std::min(get_best_obj(), get_param_best_bound_stop())); + relaxation.optimizer().set_param_time_limit(get_remaining_time()); - m_relaxations[t_relaxation_id]->optimizer().set_param_best_bound_stop(std::min(get_best_obj(), get_param_best_bound_stop())); - m_relaxations[t_relaxation_id]->optimizer().set_param_time_limit(get_remaining_time()); + std::cout << "Preparing node " << t_node.id() << std::endl; + node_updator.prepare(t_node); + std::cout << "END" << std::endl; - m_relaxations[t_relaxation_id]->optimize(); + for (const auto& var : parent().vars()) { + const double lb = relaxation.get_var_lb(var); + const double ub = relaxation.get_var_ub(var); + if (lb > ub + Tolerance::Integer) { + std::cout << "Inconsistent bounds for variable " << var << ": " << lb << " > " << ub << std::endl; + } + } - t_node.info().save(parent(), *m_relaxations[t_relaxation_id]); + relaxation.optimize(); + + t_node.info().save(parent(), relaxation); m_branching_rule->on_node_solved(t_node); @@ -696,15 +708,7 @@ void idol::Optimizers::BranchAndBound::analyze(const BranchAndBound:: if (status == Fail || status == Loaded) { - /* - if (m_n_postponed_nodes < m_max_postponed_nodes) { - *t_explore_children_flag = true; - std::cout << "Postponing Node " << t_node.id() << " since returned status is " << status << "." << std::endl; - idol_Log(Trace, "Postponing Node " << t_node.id() << " since returned status is " << status << "."); - ++m_n_postponed_nodes; - return; - } - */ + std::cout << "HERE?" << std::endl; set_status(Fail); set_reason(NotSpecified); @@ -746,7 +750,7 @@ void idol::Optimizers::BranchAndBound::analyze(const BranchAndBound:: m_root_node_best_obj = get_best_obj(); } - if (side_effects.n_added_lazy_cuts > 0 || side_effects.n_added_user_cuts > 0) { + if (side_effects.n_added_lazy_cuts > 0 || side_effects.n_added_user_cuts > 0 || side_effects.n_added_local_variable_branching > 0) { *t_reoptimize_flag = true; return; } diff --git a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/AbstractBranchAndBoundCallbackI.h b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/AbstractBranchAndBoundCallbackI.h index 55a97c86..c52bc200 100644 --- a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/AbstractBranchAndBoundCallbackI.h +++ b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/callbacks/AbstractBranchAndBoundCallbackI.h @@ -28,6 +28,7 @@ namespace idol { struct idol::SideEffectRegistry { unsigned int n_added_lazy_cuts = 0; unsigned int n_added_user_cuts = 0; + unsigned int n_added_local_variable_branching = 0; }; template 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 8c5c5e92..61a76b61 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 @@ -41,6 +41,8 @@ class idol::BranchAndBoundCallbackI : public AbstractBranchAndBoundCallbackI& node() const; [[nodiscard]] const Model& relaxation() const; @@ -74,6 +76,12 @@ class idol::BranchAndBoundCallbackI : public AbstractBranchAndBoundCallbackI +void idol::BranchAndBoundCallbackI::add_local_variable_branching(const idol::Var &t_var, CtrType t_type, double t_rhs) { + m_node->info().add_branching_variable(t_var, t_type, t_rhs); + m_registry->n_added_local_variable_branching++; +} + template void idol::BranchAndBoundCallbackI::terminate() { m_parent->terminate(); @@ -150,6 +158,8 @@ class idol::BranchAndBoundCallback { */ void add_lazy_cut(const TempCtr& t_cut); + void add_local_variable_branching(const Var &t_var, CtrType t_type, double t_rhs); + /** * Returns the node which is currently explored * @return the node which is currently explored @@ -206,6 +216,12 @@ class idol::BranchAndBoundCallback { friend class BranchAndBoundCallbackI; }; +template +void idol::BranchAndBoundCallback::add_local_variable_branching(const Var &t_var, CtrType t_type, double t_rhs) { + throw_if_no_interface(); + m_interface->add_local_variable_branching(t_var, t_type, t_rhs); +} + template double idol::BranchAndBoundCallback::best_obj() const { throw_if_no_interface(); diff --git a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeInfo.h b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeInfo.h index dea1d344..d36cbbe4 100644 --- a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeInfo.h +++ b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeInfo.h @@ -15,7 +15,6 @@ namespace idol { namespace Optimizers { template class BranchAndBound; } - } class idol::DefaultNodeInfo { @@ -50,7 +49,8 @@ class idol::DefaultNodeInfo { [[nodiscard]] auto constraint_branching_decisions() const { return ConstIteratorForward(m_constraint_branching_decisions); } - static DefaultNodeUpdator* create_updator(Model& t_relaxation); + static DefaultNodeUpdator* create_updator(const Model& t_src_model, Model& t_relaxation); + private: PrimalPoint m_primal_solution; std::optional m_sum_of_infeasibilities; diff --git a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeUpdator.h b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeUpdator.h index 2df81a2e..c2adfd88 100644 --- a/lib/include/idol/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeUpdator.h +++ b/lib/include/idol/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeUpdator.h @@ -20,6 +20,7 @@ namespace idol { template class idol::DefaultNodeUpdator : public NodeUpdator { + const Model& m_parent; Model& m_relaxation; Map m_changed_lower_bounds; // variable -> old_bound @@ -37,7 +38,7 @@ class idol::DefaultNodeUpdator : public NodeUpdator { void apply_constraint_branching_decisions(const Node &t_node); public: - explicit DefaultNodeUpdator(Model& t_relaxation); + explicit DefaultNodeUpdator(const Model& t_parent, Model& t_relaxation); DefaultNodeUpdator(const DefaultNodeUpdator&) = delete; @@ -71,7 +72,8 @@ idol::DefaultNodeUpdator::apply_constraint_branching_decisions(const } template -idol::DefaultNodeUpdator::DefaultNodeUpdator(idol::Model &t_relaxation) : m_relaxation(t_relaxation) { +idol::DefaultNodeUpdator::DefaultNodeUpdator(const Model& t_parent, idol::Model &t_relaxation) + : m_parent(t_parent), m_relaxation(t_relaxation) { } @@ -117,12 +119,10 @@ void idol::DefaultNodeUpdator::apply_variable_branching_decisions(con Map &t_changed_upper_bounds ) { - if (t_node.level() == 0) { - return; - } - for (const auto& branching_decision : t_node.info().variable_branching_decisions()) { + std::cout << branching_decision.variable << " " << branching_decision.type << " " << branching_decision.bound << std::endl; + switch (branching_decision.type) { case LessOrEqual: apply_variable_branching_decision(branching_decision, m_changed_upper_bounds, t_changed_upper_bounds); @@ -136,6 +136,10 @@ void idol::DefaultNodeUpdator::apply_variable_branching_decisions(con } + if (t_node.level() == 0) { + return; + } + apply_variable_branching_decisions(t_node.parent(), t_changed_lower_bounds, t_changed_upper_bounds); } diff --git a/lib/include/idol/mixed-integer/optimizers/callbacks/ReducedCostFixing.h b/lib/include/idol/mixed-integer/optimizers/callbacks/ReducedCostFixing.h new file mode 100644 index 00000000..0b237398 --- /dev/null +++ b/lib/include/idol/mixed-integer/optimizers/callbacks/ReducedCostFixing.h @@ -0,0 +1,59 @@ +// +// Created by henri on 24.11.24. +// + +#ifndef IDOL_REDUCEDCOSTFIXING_H +#define IDOL_REDUCEDCOSTFIXING_H + +#include "idol/mixed-integer/optimizers/branch-and-bound/callbacks/BranchAndBoundCallback.h" + +namespace idol { + template class ReducedCostFixing; +} + +template +class idol::ReducedCostFixing : public BranchAndBoundCallbackFactory { +public: + class Strategy : public BranchAndBoundCallback { + public: + protected: + void operator()(CallbackEvent t_event) override { + + if (t_event != InvalidSolution) { + return; + } + + const auto& original_model = this->original_model(); + const auto& relaxation = this->relaxation(); + const double best_obj = this->best_obj(); + const double current_obj = this->relaxation().get_best_obj(); + + for (const auto &var : original_model.vars()) { + + double reduced_cost = relaxation.get_var_reduced_cost(var); + + if (current_obj + reduced_cost > best_obj + Tolerance::MIPAbsoluteGap) { + const double relaxation_lb = original_model.get_var_lb(var); + this->add_local_variable_branching(var, LessOrEqual, relaxation_lb); + } + + if (current_obj - reduced_cost > best_obj + Tolerance::MIPAbsoluteGap) { + const double relaxation_ub = original_model.get_var_ub(var); + this->add_local_variable_branching(var, GreaterOrEqual, relaxation_ub); + } + + } + + } + }; + + BranchAndBoundCallback *operator()() override { + return new Strategy(); + } + + BranchAndBoundCallbackFactory *clone() const override { + return new ReducedCostFixing(); + } +}; + +#endif //IDOL_REDUCEDCOSTFIXING_H diff --git a/lib/src/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeInfo.cpp b/lib/src/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeInfo.cpp index 3267a22f..ea4ccceb 100644 --- a/lib/src/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeInfo.cpp +++ b/lib/src/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeInfo.cpp @@ -6,8 +6,8 @@ #include "idol/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeUpdator.h" idol::DefaultNodeUpdator * -idol::DefaultNodeInfo::create_updator(idol::Model &t_relaxation) { - return new DefaultNodeUpdator(t_relaxation); +idol::DefaultNodeInfo::create_updator(const Model& t_src_model, idol::Model &t_relaxation) { + return new DefaultNodeUpdator(t_src_model, t_relaxation); } void idol::DefaultNodeInfo::add_branching_constraint(const Ctr &t_ctr, TempCtr t_temporary_constraint) { diff --git a/lib/src/mixed-integer/optimizers/wrappers/GLPK/Optimizers_GLPK.cpp b/lib/src/mixed-integer/optimizers/wrappers/GLPK/Optimizers_GLPK.cpp index 0cc260de..519c6447 100644 --- a/lib/src/mixed-integer/optimizers/wrappers/GLPK/Optimizers_GLPK.cpp +++ b/lib/src/mixed-integer/optimizers/wrappers/GLPK/Optimizers_GLPK.cpp @@ -785,7 +785,7 @@ idol::Model idol::Optimizers::GLPK::read_from_mps_file(idol::Env &t_env, const s } double idol::Optimizers::GLPK::get_var_reduced_cost(const idol::Var &t_var) const { - throw Exception("Not implemented get_var_reduced_cost"); + return glp_get_col_dual(m_model, lazy(t_var).impl()); } void idol::Optimizers::GLPK::set_param_logs(bool t_value) {