Skip to content

Commit

Permalink
add Utils::ExportBranchAndBoundTreeToCSV
Browse files Browse the repository at this point in the history
  • Loading branch information
hlefebvr committed Oct 27, 2023
1 parent 447fd56 commit 56bf9d5
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 16 deletions.
18 changes: 12 additions & 6 deletions examples/knapsack-problem/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@
#include "idol/optimizers/branch-and-bound/branching-rules/factories/MostInfeasible.h"
#include "idol/optimizers/wrappers/HiGHS/HiGHS.h"
#include "idol/optimizers/branch-and-bound/branching-rules/factories/Diver.h"
#include "idol/optimizers/branch-and-bound/watchers/ExportBranchAndBoundTreeToCSV.h"
#include "idol/optimizers/branch-and-bound/node-selection-rules/factories/DepthFirst.h"
#include "idol/optimizers/branch-and-bound/branching-rules/factories/PseudoCost.h"
#include "idol/optimizers/branch-and-bound/node-selection-rules/factories/WorstBound.h"
#include "idol/optimizers/branch-and-bound/node-selection-rules/factories/BreadthFirst.h"
#include "idol/optimizers/branch-and-bound/node-selection-rules/factories/BestEstimate.h"

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

using namespace idol;

const auto instance = Problems::KP::read_instance("instance.txt");
//const auto instance = Problems::KP::read_instance("/home/henri/CLionProjects/optimize/examples/knapsack-problem/instance50.txt");
//const auto instance = Problems::KP::read_instance("instance.txt");
const auto instance = Problems::KP::read_instance("/home/henri/Research/idol/examples/knapsack-problem/instance50.txt");

const auto n_items = instance.n_items();

Expand All @@ -41,10 +47,10 @@ int main(int t_argc, const char** t_argv) {
model.use(
BranchAndBound()
.with_node_optimizer(HiGHS::ContinuousRelaxation())
.with_branching_rule(
Diver<BranchingRules::MostInfeasible<NodeVarInfo>>()
)
.with_node_selection_rule(BestBound())
.with_branching_rule(PseudoCost())
.with_callback(Utils::ExportBranchAndBoundTreeToCSV("tree.csv"))
.with_callback(Heuristics::SimpleRounding())
.with_node_selection_rule(BestEstimate())
.with_log_level(Info, Blue)
.with_log_frequency(1)
);
Expand Down
2 changes: 2 additions & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ add_library(idol STATIC
include/idol/optimizers/branch-and-bound/node-selection-rules/impls/BestEstimate.h
include/idol/optimizers/branch-and-bound/branching-rules/impls/Diver.h
include/idol/optimizers/branch-and-bound/branching-rules/factories/Diver.h
include/idol/optimizers/branch-and-bound/watchers/ExportBranchAndBoundTreeToCSV.h
src/optimizers/branch-and-bound/watchers/BranchAndBoundTree.cpp
)

find_package(OpenMP REQUIRED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,6 @@ void idol::Optimizers::BranchAndBound<NodeVarInfoT>::submit_heuristic_solution(N
idol_Log(Trace, "Ignoring submitted heuristic solution, objective value is " << t_node.info().objective_value() << " while best obj is " << get_best_obj() << '.');
}


}

template<class NodeVarInfoT>
Expand Down Expand Up @@ -506,6 +505,7 @@ void idol::Optimizers::BranchAndBound<NodeVarInfoT>::analyze(const BranchAndBoun
return;
}


if (t_node.id() % m_log_frequency == 0) {
log_node(Info, t_node);
}
Expand Down Expand Up @@ -533,11 +533,13 @@ void idol::Optimizers::BranchAndBound<NodeVarInfoT>::analyze(const BranchAndBoun
set_status(status);
}

call_callbacks(PrunedSolution, t_node);
idol_Log(Trace, "Pruning node " << t_node.id() << " (by feasibility).");
return;
}

if (status == Feasible && reason == ObjLimit) {
call_callbacks(PrunedSolution, t_node);
idol_Log(Trace, "Node " << t_node.id() << " was pruned by bound " << "(BestObj: " << get_best_obj() << ", Obj: " << t_node.info().primal_solution().objective_value() << ").");
return;
}
Expand All @@ -557,6 +559,7 @@ void idol::Optimizers::BranchAndBound<NodeVarInfoT>::analyze(const BranchAndBoun
set_status(Fail);
set_reason(NotSpecified);
terminate();
call_callbacks(PrunedSolution, t_node);
idol_Log(Trace, "Terminate. Node " << t_node.id() << " could not be solved to optimality (status = " << status << ").");
return;
}
Expand Down Expand Up @@ -586,6 +589,7 @@ void idol::Optimizers::BranchAndBound<NodeVarInfoT>::analyze(const BranchAndBoun

} else {

call_callbacks(PrunedSolution, t_node);
idol_Log(Trace, "Node " << t_node.id() << " was pruned by bound " << "(BestObj: " << get_best_obj() << ", Obj: " << t_node.info().objective_value() << ").");
return;

Expand All @@ -599,6 +603,7 @@ void idol::Optimizers::BranchAndBound<NodeVarInfoT>::analyze(const BranchAndBoun

if (side_effects.n_added_lazy_cuts > 0 || side_effects.n_added_user_cuts > 0) {
*t_reoptimize_flag = true;
return;
}

*t_explore_children_flag = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

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

namespace idol::BranchingRules {
template<class NodeVarInfoT> class PseudoCost;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class idol::BranchAndBoundCallbackI : public AbstractBranchAndBoundCallbackI<Nod

[[nodiscard]] const Timer& time() const;

[[nodiscard]] const SideEffectRegistry& side_effect_registry() const;

void submit_heuristic_solution(NodeVarInfoT* t_info);

void submit_bound(double t_bound);
Expand All @@ -69,6 +71,14 @@ class idol::BranchAndBoundCallbackI : public AbstractBranchAndBoundCallbackI<Nod
void initialize(const Model& t_model) override;
};

template<class NodeVarInfoT>
const idol::SideEffectRegistry &idol::BranchAndBoundCallbackI<NodeVarInfoT>::side_effect_registry() const {
if (!m_registry) {
throw Exception("No side effect registry was found");
}
return *m_registry;
}

template<class NodeVarInfoT>
const idol::Timer &idol::BranchAndBoundCallbackI<NodeVarInfoT>::time() const {
return m_parent->time();
Expand Down Expand Up @@ -152,6 +162,12 @@ class idol::BranchAndBoundCallback {
*/
void submit_bound(double t_bound);

/**
* Returns the side effect registry
* @return the side effect registry
*/
const SideEffectRegistry& side_effect_registry() const;

[[nodiscard]] const Timer& time() const;
private:
BranchAndBoundCallbackI<NodeVarInfoT>* m_interface = nullptr;
Expand All @@ -161,6 +177,12 @@ class idol::BranchAndBoundCallback {
friend class BranchAndBoundCallbackI<NodeVarInfoT>;
};

template<class NodeVarInfoT>
const idol::SideEffectRegistry &idol::BranchAndBoundCallback<NodeVarInfoT>::side_effect_registry() const {
throw_if_no_interface();
return m_interface->side_effect_registry();
}

template<class NodeVarInfoT>
const idol::Timer &idol::BranchAndBoundCallback<NodeVarInfoT>::time() const {
throw_if_no_interface();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Created by henri on 27.10.23.
//

#ifndef IDOL_EXPORTBRANCHANDBOUNDTREETOCSV_H
#define IDOL_EXPORTBRANCHANDBOUNDTREETOCSV_H


#include "idol/optimizers/branch-and-bound/nodes/NodeVarInfo.h"
#include "idol/optimizers/branch-and-bound/callbacks/BranchAndBoundCallbackFactory.h"
#include "idol/optimizers/branch-and-bound/callbacks/BranchAndBoundCallback.h"

namespace idol::Utils {
class ExportBranchAndBoundTreeToCSV;
}

class idol::Utils::ExportBranchAndBoundTreeToCSV : public BranchAndBoundCallbackFactory<NodeVarInfo> {
const std::string m_filename;
public:
explicit ExportBranchAndBoundTreeToCSV(std::string t_filename);

BranchAndBoundCallback<NodeVarInfo> *operator()() override;

BranchAndBoundCallbackFactory<NodeVarInfo> *clone() const override;

class Strategy : public BranchAndBoundCallback<NodeVarInfo> {
const std::string m_filename;
protected:
void operator()(CallbackEvent t_event) override;
public:
explicit Strategy(std::string t_filename);
};
};

#endif //IDOL_EXPORTBRANCHANDBOUNDTREETOCSV_H
4 changes: 2 additions & 2 deletions lib/include/idol/optimizers/callbacks/Callback.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ namespace idol {
* It is typically used inside of a Callback to influence or modify the behaviour of the algorithm at execution time.
*/
enum CallbackEvent {
AlgorithmStarts, /*! Occurs when the algorithm starts */
AlgorithmStops, /*! Occurs when the algorithm stops */
NodeLoaded, /*!< Occurs when a node is about to be solved */
IncumbentSolution, /*!< Occurs when an incumbent solution has been found */
InvalidSolution, /*!< Occurs when a solution of the relaxation is not valid (e.g., not integer) */
PrunedSolution
};
}

Expand All @@ -32,6 +31,7 @@ static std::ostream &operator<<(std::ostream& t_os, idol::CallbackEvent t_event)
case IncumbentSolution: return t_os << "IncumbentSolution";
case InvalidSolution: return t_os << "InvalidSolution";
case NodeLoaded: return t_os << "NodeLoaded";
case PrunedSolution: return t_os << "PrunedSolution";
default:;
}
throw Exception("Enum out of bounds.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// Created by henri on 27.10.23.
//
#include "idol/optimizers/branch-and-bound/watchers/ExportBranchAndBoundTreeToCSV.h"
#include <fstream>

idol::Utils::ExportBranchAndBoundTreeToCSV::ExportBranchAndBoundTreeToCSV(std::string t_filename) : m_filename(std::move(t_filename)) {

}

idol::BranchAndBoundCallbackFactory<idol::NodeVarInfo> *idol::Utils::ExportBranchAndBoundTreeToCSV::clone() const {
return new ExportBranchAndBoundTreeToCSV(*this);
}

idol::BranchAndBoundCallback<idol::NodeVarInfo> *idol::Utils::ExportBranchAndBoundTreeToCSV::operator()() {
return new Strategy(m_filename);
}

idol::Utils::ExportBranchAndBoundTreeToCSV::Strategy::Strategy(std::string t_filename) : m_filename(std::move(t_filename)) {

std::ofstream file(m_filename);

if (!file.is_open()) {
throw Exception("Could not open destination file.");
}

file.close();

}

void idol::Utils::ExportBranchAndBoundTreeToCSV::Strategy::operator()(idol::CallbackEvent t_event) {

if (t_event != InvalidSolution && t_event != IncumbentSolution && t_event != PrunedSolution) {
return;
}

const auto& side_effect_registry = this->side_effect_registry();

if (side_effect_registry.n_added_lazy_cuts > 0 || side_effect_registry.n_added_user_cuts > 0) {
return;
}

std::ofstream file(m_filename, std::ios::app);

if (!file.is_open()) {
throw Exception("Could not open destination file.");
}

const auto& node = this->node();

const unsigned int id = node.id();
const auto status = node.info().status();
unsigned int parent_id;
std::stringstream branch_label;
double sum_of_infeasibilities = Inf;

if (id == 0) {
parent_id = 0;
} else {
parent_id = node.parent().id();

const auto& branching_decision = node.info().branching_decision();
branch_label << branching_decision.variable.name() << " ";
if (branching_decision.type == LessOrEqual) {
branch_label << "&le;";
} else {
branch_label << "&ge;";
}
branch_label << " " << branching_decision.bound;
}

if (status == Optimal || status == Feasible) {
sum_of_infeasibilities = node.info().sum_of_infeasibilities();
}

file << id << ","
<< parent_id << ","
<< status << ","
<< node.info().objective_value() << ","
<< branch_label.str() << ","
<< t_event << ","
<< sum_of_infeasibilities
<< std::endl;

file.close();

}
7 changes: 0 additions & 7 deletions lib/src/optimizers/wrappers/gurobi/Optimizers_Gurobi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,20 +277,13 @@ void idol::Optimizers::Gurobi::hook_remove(const Ctr& t_ctr) {
void idol::Optimizers::Gurobi::hook_optimize() {
set_solution_index(0);

if (m_gurobi_callback) {
m_gurobi_callback->call(AlgorithmStarts);
}

try {
m_model.optimize();
} catch (const GRBException& err) {
std::cout << "[Gurobi exception]" << err.getErrorCode() << ": " << err.getMessage() << std::endl;
__throw_exception_again;
}

if (m_gurobi_callback) {
m_gurobi_callback->call(AlgorithmStops);
}
}

void idol::Optimizers::Gurobi::hook_write(const std::string &t_name) {
Expand Down

0 comments on commit 56bf9d5

Please sign in to comment.