Skip to content

Commit

Permalink
first version of strong branching
Browse files Browse the repository at this point in the history
  • Loading branch information
hlefebvr committed Oct 17, 2023
1 parent b329a12 commit 60ad97e
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
- uses: actions/setup-python@v2
- name: Install dependencies
run: |
pip install sphinx breathe sphinx-sitemap sphinx-rtd-theme sphinx-copybutton
pip install sphinx breathe sphinx-sitemap sphinx-rtd-theme sphinx-copybutton sphinx-mermaid
sudo apt-get install doxygen
- name: Sphinx build
run: |
Expand Down
3 changes: 2 additions & 1 deletion examples/facility-location-problem/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "optimizers/branch-and-bound/branching-rules/factories/VariableBranching.h"
#include "optimizers/branch-and-bound/branching-rules/factories/MostInfeasible.h"
#include "optimizers/branch-and-bound/branching-rules/factories/UniformlyRandom.h"
#include "optimizers/branch-and-bound/branching-rules/factories/StrongBranching.h"

int main(int t_argc, const char** t_argv) {

Expand Down Expand Up @@ -52,7 +53,7 @@ int main(int t_argc, const char** t_argv) {
BranchAndBound()
.with_node_optimizer(Gurobi::ContinuousRelaxation())
.with_branching_rule(
UniformlyRandom()
StrongBranching()
)
.with_node_selection_rule(BestBound())
.with_log_level(Trace, Blue)
Expand Down
3 changes: 3 additions & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ add_library(idol STATIC include/modeling/numericals.h include/containers/Optiona
include/optimizers/branch-and-bound/branching-rules/impls/UniformlyRandom.h
include/optimizers/branch-and-bound/branching-rules/factories/FirstInfeasibleFound.h
include/optimizers/branch-and-bound/branching-rules/impls/FirstInfeasibleFound.h
include/optimizers/branch-and-bound/branching-rules/factories/StrongBranching.h
include/optimizers/branch-and-bound/branching-rules/impls/StrongBranching.h
include/optimizers/branch-and-bound/branching-rules/impls/strong-branching/NodeScoreFunction.h
)

find_package(OpenMP REQUIRED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ class idol::Optimizers::BranchAndBound : public Algorithm {
void create_relaxations();
Node<NodeInfoT> create_root_node();
void explore(TreeNode& t_node, SetOfActiveNodes& t_active_nodes, unsigned int t_step);
void solve(TreeNode& t_node);
void analyze(const TreeNode& t_node, bool* t_explore_children_flag, bool* t_reoptimize_flag);
Node<NodeInfoT> select_node_for_branching(SetOfActiveNodes& t_active_nodes);
std::vector<TreeNode> create_child_nodes(const TreeNode& t_node);
Expand Down Expand Up @@ -105,6 +104,8 @@ class idol::Optimizers::BranchAndBound : public Algorithm {

[[nodiscard]] std::string name() const override { return "branch-and-bound"; }

void solve(TreeNode& t_node) const;

virtual void set_log_frequency(unsigned int t_log_frequency) { m_log_frequency = t_log_frequency; }

[[nodiscard]] unsigned int log_frequency() const { return m_log_frequency; }
Expand Down Expand Up @@ -456,7 +457,7 @@ void idol::Optimizers::BranchAndBound<NodeInfoT>::explore(TreeNode &t_node,
}

template<class NodeInfoT>
void idol::Optimizers::BranchAndBound<NodeInfoT>::solve(TreeNode& t_node) {
void idol::Optimizers::BranchAndBound<NodeInfoT>::solve(TreeNode& t_node) const {

idol_Log(Trace, "Preparing to solve node " << t_node.id() << ".");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Created by henri on 17.10.23.
//

#ifndef IDOL_STRONGBRANCHING_H
#define IDOL_STRONGBRANCHING_H

#include "VariableBranching.h"
#include "optimizers/branch-and-bound/branching-rules/impls/StrongBranching.h"

namespace idol {
class StrongBranching;
}

class idol::StrongBranching : public idol::VariableBranching {
public:
StrongBranching() = default;

template<class NodeInfoT>
class Strategy : public VariableBranching::Strategy<NodeInfoT> {
public:
Strategy() = default;

explicit Strategy(const StrongBranching& t_parent) : VariableBranching::Strategy<NodeInfoT>(t_parent) {}

BranchingRules::VariableBranching<NodeInfoT> *
operator()(const Optimizers::BranchAndBound<NodeInfoT> &t_parent) const override {
return new BranchingRules::StrongBranching<NodeInfoT>(t_parent, idol::VariableBranching::Strategy<NodeInfoT>::create_branching_candidates(t_parent.parent()));
}

VariableBranching::Strategy<NodeInfoT> *clone() const override {
return new Strategy(*this);
}
};

};

#endif //IDOL_STRONGBRANCHING_H
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class idol::BranchingRule {
explicit BranchingRule(const Optimizers::BranchAndBound<NodeInfoT>& t_parent) : m_parent(t_parent) {}
virtual ~BranchingRule() = default;

[[nodiscard]] const Optimizers::BranchAndBound<NodeInfoT>& parent() const { return m_parent; }

[[nodiscard]] const Model& model() const { return m_parent.parent(); }

[[nodiscard]] virtual bool is_valid(const Node<NodeInfoT>& t_node) const = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace idol::BranchingRules {
template<class NodeInfoT>
class idol::BranchingRules::FirstInfeasibleFound : public VariableBranching<NodeInfoT> {
protected:
std::list<std::pair<Var, double>> scoring_function(const std::list<Var> &t_var, const Solution::Primal &t_primal_values) override;
std::list<std::pair<Var, double>> scoring_function(const std::list<Var> &t_var, const Node<NodeInfoT> &t_node) override;
public:
explicit FirstInfeasibleFound(const Optimizers::BranchAndBound<NodeInfoT>& t_parent, std::list<Var> t_branching_candidates);
};
Expand All @@ -27,7 +27,7 @@ idol::BranchingRules::FirstInfeasibleFound<NodeInfoT>::FirstInfeasibleFound(
template<class NodeInfoT>
std::list<std::pair<idol::Var, double>>
idol::BranchingRules::FirstInfeasibleFound<NodeInfoT>::scoring_function(const std::list<idol::Var> &t_variables,
const idol::Solution::Primal &t_primal_values) {
const Node<NodeInfoT> &t_node) {
return { { t_variables.front(), -1. } };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#ifndef IDOL_IMPL_LEASTINFEASIBLE_H
#define IDOL_IMPL_LEASTINFEASIBLE_H

#include <cmath>
#include "VariableBranching.h"

namespace idol::BranchingRules {
Expand All @@ -14,7 +15,7 @@ namespace idol::BranchingRules {
template<class NodeInfoT>
class idol::BranchingRules::LeastInfeasible : public VariableBranching<NodeInfoT> {
protected:
std::list<std::pair<Var, double>> scoring_function(const std::list<Var> &t_var, const Solution::Primal &t_primal_values) override;
std::list<std::pair<Var, double>> scoring_function(const std::list<Var> &t_var, const Node<NodeInfoT> &t_node) override;
public:
explicit LeastInfeasible(const Optimizers::BranchAndBound<NodeInfoT>& t_parent, std::list<Var> t_branching_candidates);
};
Expand All @@ -27,12 +28,14 @@ idol::BranchingRules::LeastInfeasible<NodeInfoT>::LeastInfeasible(
template<class NodeInfoT>
std::list<std::pair<idol::Var, double>>
idol::BranchingRules::LeastInfeasible<NodeInfoT>::scoring_function(const std::list<idol::Var> &t_variables,
const idol::Solution::Primal &t_primal_values) {
const Node<NodeInfoT> &t_node) {

const auto& primal_solution = t_node.info().primal_solution();

std::list<std::pair<Var, double>> result;

for (const auto& var : t_variables) {
const double value = t_primal_values.get(var);
const double value = primal_solution.get(var);
const double score = -std::abs(value - std::round(value));
result.emplace_back(var, score);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#ifndef IDOL_IMPL_MOSTINFEASIBLE_H
#define IDOL_IMPL_MOSTINFEASIBLE_H

#include <cmath>
#include "VariableBranching.h"

namespace idol::BranchingRules {
Expand All @@ -14,7 +15,7 @@ namespace idol::BranchingRules {
template<class NodeInfoT>
class idol::BranchingRules::MostInfeasible : public VariableBranching<NodeInfoT> {
protected:
std::list<std::pair<Var, double>> scoring_function(const std::list<Var> &t_var, const Solution::Primal &t_primal_values) override;
std::list<std::pair<Var, double>> scoring_function(const std::list<Var> &t_var, const Node<NodeInfoT> &t_node) override;
public:
explicit MostInfeasible(const Optimizers::BranchAndBound<NodeInfoT>& t_parent, std::list<Var> t_branching_candidates);
};
Expand All @@ -27,12 +28,14 @@ idol::BranchingRules::MostInfeasible<NodeInfoT>::MostInfeasible(
template<class NodeInfoT>
std::list<std::pair<idol::Var, double>>
idol::BranchingRules::MostInfeasible<NodeInfoT>::scoring_function(const std::list<idol::Var> &t_variables,
const idol::Solution::Primal &t_primal_values) {
const Node<NodeInfoT> &t_node) {

const auto& primal_solution = t_node.info().primal_solution();

std::list<std::pair<Var, double>> result;

for (const auto& var : t_variables) {
const double value = t_primal_values.get(var);
const double value = primal_solution.get(var);
const double score = std::abs(value - std::round(value));
result.emplace_back(var, score);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//
// Created by henri on 17.10.23.
//

#ifndef IDOL_IMPL_STRONGBRANCHING_H
#define IDOL_IMPL_STRONGBRANCHING_H

#include "VariableBranching.h"
#include "optimizers/branch-and-bound/branching-rules/impls/strong-branching/NodeScoreFunction.h"

namespace idol::BranchingRules {
template<class NodeInfoT> class StrongBranching;
}

template<class NodeInfoT>
class idol::BranchingRules::StrongBranching : public VariableBranching<NodeInfoT> {
std::unique_ptr<VariableBranching<NodeInfoT>> m_inner_variable_branching_rule;
unsigned int m_max_n_var = 2;
std::unique_ptr<StrongBranchingScoreFunction> m_score_function;

std::vector<std::pair<Var, double>> sort_variables_by_score(const std::list<std::pair<Var, double>>& t_scores);

std::list<Node<NodeInfoT>> make_nodes(const std::list<NodeInfoT*>& t_node_infos, const Node<NodeInfoT>& t_parent_node);

void solve_nodes(std::list<Node<NodeInfoT>>& t_nodes);

double compute_score(double t_parent_objective, std::list<Node<NodeInfoT>>& t_nodes);
public:
explicit StrongBranching(const Optimizers::BranchAndBound<NodeInfoT>& t_parent, std::list<Var> t_branching_candidates);

std::list<std::pair<Var, double>> scoring_function(const std::list<Var> &t_var, const Node<NodeInfoT> &t_node) override;
};

template<class NodeInfoT>
idol::BranchingRules::StrongBranching<NodeInfoT>::StrongBranching(
const idol::Optimizers::BranchAndBound<NodeInfoT> &t_parent, std::list<Var> t_branching_candidates)
: VariableBranching<NodeInfoT>(t_parent, std::move(t_branching_candidates)),
m_inner_variable_branching_rule(new BranchingRules::MostInfeasible<NodeInfoT>(t_parent, {})),
m_score_function(new StrongBranchingScores::Product())
{}

template<class NodeInfoT>
std::list<std::pair<idol::Var, double>>
idol::BranchingRules::StrongBranching<NodeInfoT>::scoring_function(const std::list<idol::Var> &t_variables,
const Node<NodeInfoT> &t_node) {

std::list<std::pair<Var, double>> result;

const auto scores = m_inner_variable_branching_rule->scoring_function(t_variables, t_node);
const auto sorted_scores = sort_variables_by_score(scores);
const unsigned int n_nodes_to_solve = std::min<unsigned int>(m_max_n_var, sorted_scores.size());

const double objective_value_parent_node = t_node.info().objective_value();

for (unsigned int k = 0 ; k < n_nodes_to_solve ; ++k) {

const auto branching_candidate = sorted_scores[k].first;

std::cout << "Should solve node_infos for " << branching_candidate << ", score = " << sorted_scores[k].second << std::endl;

auto node_infos = m_inner_variable_branching_rule->create_child_nodes_for_selected_variable(t_node, branching_candidate);
auto nodes = make_nodes(node_infos, t_node);

solve_nodes(nodes);

result.emplace_back(branching_candidate, compute_score(objective_value_parent_node, nodes));

}

return result;
}

template<class NodeInfoT>
std::vector<std::pair<idol::Var, double>> idol::BranchingRules::StrongBranching<NodeInfoT>::sort_variables_by_score(
const std::list<std::pair<Var, double>> &t_scores) {

std::vector<std::pair<Var, double>> result;
result.reserve(t_scores.size());

std::copy(t_scores.begin(), t_scores.end(), std::back_inserter(result));

std::sort(result.begin(), result.end(), [](const auto& t_a, const auto& t_b) {
return t_a.second > t_b.second;
});

return result;
}
template<class NodeInfoT>
std::list<idol::Node<NodeInfoT>>
idol::BranchingRules::StrongBranching<NodeInfoT>::make_nodes(const std::list<NodeInfoT*>& t_node_infos,
const Node<NodeInfoT>& t_parent_node) {

std::list<idol::Node<NodeInfoT>> result;

const unsigned int id = t_parent_node.id();
const unsigned int level = t_parent_node.level();

for (auto* info : t_node_infos) {
result.emplace_back(info, id, level);
}

return result;
}

template<class NodeInfoT>
void idol::BranchingRules::StrongBranching<NodeInfoT>::solve_nodes(std::list<Node<NodeInfoT>>& t_nodes) {

for (auto& node : t_nodes) {
this->parent().solve(node);
}

}

template<class NodeInfoT>
double idol::BranchingRules::StrongBranching<NodeInfoT>::compute_score(double t_parent_objective,
std::list<Node<NodeInfoT>>& t_nodes) {

if (t_nodes.size() != 2) {
throw Exception("Strong branching expected two nodes, got " + std::to_string(t_nodes.size()) + ".");
}

const auto& left_node_info = t_nodes.front().info();
const auto& right_node_info = t_nodes.back().info();

const double left_objective_value = left_node_info.has_objective_value() ? left_node_info.objective_value() : Inf;
const double right_objective_value = right_node_info.has_objective_value() ? right_node_info.objective_value() : Inf;

return m_score_function->operator()(
t_parent_objective,
left_objective_value,
right_objective_value);

}

#endif //IDOL_IMPL_STRONGBRANCHING_H
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ template<class NodeInfoT>
class idol::BranchingRules::UniformlyRandom : public VariableBranching<NodeInfoT> {
unsigned int m_seed;
protected:
std::list<std::pair<Var, double>> scoring_function(const std::list<Var> &t_var, const Solution::Primal &t_primal_values) override;
std::list<std::pair<Var, double>> scoring_function(const std::list<Var> &t_var, const Node<NodeInfoT> &t_node) override;
public:
explicit UniformlyRandom(const Optimizers::BranchAndBound<NodeInfoT>& t_parent,
std::list<Var> t_branching_candidates,
Expand All @@ -35,7 +35,7 @@ idol::BranchingRules::UniformlyRandom<NodeInfoT>::UniformlyRandom(
template<class NodeInfoT>
std::list<std::pair<idol::Var, double>>
idol::BranchingRules::UniformlyRandom<NodeInfoT>::scoring_function(const std::list<idol::Var> &t_variables,
const idol::Solution::Primal &t_primal_values) {
const Node<NodeInfoT> &t_node) {

std::mt19937 engine(m_seed);
std::uniform_real_distribution<double> distribution(0, 1);
Expand Down
Loading

0 comments on commit 60ad97e

Please sign in to comment.