8#include "idol/mixed-integer/optimizers/branch-and-bound/callbacks/BranchAndBoundCallbackFactory.h"
9#include "idol/mixed-integer/optimizers/branch-and-bound/callbacks/BranchAndBoundCallback.h"
10#include "idol/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeInfo.h"
11#include "idol/mixed-integer/optimizers/callbacks/cutting-planes/CutFamily.h"
14#include <CglKnapsackCover.hpp>
15#include <CglFlowCover.hpp>
16#include <CglZeroHalf.hpp>
17#include <CglMixedIntegerRounding.hpp>
18#include <CglOddHole.hpp>
19#include <CglLandP.hpp>
20#include <CglResidualCapacity.hpp>
29template<
class NodeInfoT =
idol::DefaultNodeInfo>
34 class NodeCutContext {
35 const unsigned int m_node_id;
36 unsigned int m_pass_count = 0;
37 unsigned int m_n_accepted_cuts = 0;
39 NodeCutContext(
unsigned int node_id) : m_node_id(node_id) {}
41 [[nodiscard]]
unsigned int node_id()
const {
return m_node_id; }
42 [[nodiscard]]
bool is_root_node()
const {
return (m_node_id == 0); }
43 [[nodiscard]]
unsigned int pass()
const {
return m_pass_count; }
44 [[nodiscard]]
unsigned int n_added_cuts()
const {
return m_n_accepted_cuts; }
46 void increment_pass() { m_pass_count++; }
47 void add_accepted_cut() { m_n_accepted_cuts++; }
50 class OsiIdolCglSolverInterface;
52 std::unique_ptr<OsiIdolCglSolverInterface> m_osi_solver;
54 std::vector<std::unique_ptr<CutFamily>> m_cut_families;
55 std::unique_ptr<NodeCutContext> m_cut_context;
57 unsigned int m_total_n_added_cuts = 0;
58 const unsigned int m_max_pass_at_aggressive_node = 20;
59 const unsigned int m_max_pass_per_regular_node = 1;
60 const unsigned int m_max_depth_for_cuts = 50;
61 const double m_effectiveness_threshold = 1e-3;
62 const unsigned int m_max_cut_at_aggressive_node = std::numeric_limits<unsigned int>::max();
63 const unsigned int m_max_cut_per_regular_node = std::numeric_limits<unsigned int>::max();
64 const unsigned int m_max_cuts = std::numeric_limits<unsigned int>::max();
66 std::list<TempCtr> to_idol_cuts(
OsiCuts& t_cuts);
67 TempCtr to_idol_cut(OsiRowCut& t_cut);
68 std::vector<std::pair<TempCtr, double>> sort_cuts_by_effectiveness(
const std::list<TempCtr>& t_cuts);
70 NodeCutContext& get_cut_context();
71 const NodeCutContext& get_cut_context()
const {
return const_cast<Strategy*
>(
this)->get_cut_context(); }
73 void log_after_termination()
override;
74 void operator()(CallbackEvent t_event)
override;
81template <
class NodeInfoT>
83 return new Strategy();
86template <
class NodeInfoT>
88 return new CglCutCallback<NodeInfoT>(*
this);
91#include "idol/mixed-integer/optimizers/callbacks/cutting-planes/OsiIdolCglSolverInterface.h"
93template <
class NodeInfoT>
99 m_osi_solver = std::make_unique<OsiIdolCglSolverInterface>(*
this);
101 m_cut_families.emplace_back(
new CutFamily(
new CglKnapsackCover(),
"Cover"));
102 m_cut_families.emplace_back(
new CutFamily(
new CglFlowCover(),
"Flow Cover"));
103 m_cut_families.emplace_back(
new CutFamily(
new CglZeroHalf(),
"Zero Half"));
104 m_cut_families.emplace_back(
new CutFamily(
new CglMixedIntegerRounding(),
"MIR"));
105 m_cut_families.emplace_back(
new CutFamily(
new CglResidualCapacity(),
"Residual Capacity"));
110 throw Exception(
"idol was not linked with Cgl.");
114template <
class NodeInfoT>
115void idol::CglCutCallback<NodeInfoT>::Strategy::log_after_termination() {
117 BranchAndBoundCallback<NodeInfoT>::log_after_termination();
119 std::cout <<
"Cgl Cutting Planes:" << std::endl;
121 for (
const auto& cut_family : m_cut_families) {
122 std::cout <<
'\t' << cut_family->name() <<
": " << cut_family->n_accepted() <<
"\n";
127template <
class NodeInfoT>
130 if (t_event != InvalidSolution) {
134 if (m_total_n_added_cuts > m_max_cuts) {
138 if (this->
node().level() > m_max_depth_for_cuts) {
142 const double relative_gap = idol::relative_gap(this->best_bound(), this->best_obj()) * 100;
144 if (relative_gap < 1) {
148 auto& cut_context = get_cut_context();
149 const bool is_root_node = cut_context.is_root_node();
151 bool be_aggressive =
false;
152 if (is_root_node || relative_gap > 50) {
153 be_aggressive =
true;
156 unsigned int max_n_added_cut_at_this_node = m_max_cut_per_regular_node;
157 unsigned int max_pass_at_this_node = m_max_pass_per_regular_node;
159 max_n_added_cut_at_this_node = m_max_cut_at_aggressive_node;
160 max_pass_at_this_node = m_max_pass_at_aggressive_node;
163 if (cut_context.pass() > max_pass_at_this_node) {
167 cut_context.increment_pass();
170 if (!be_aggressive) {
171 std::sort(m_cut_families.begin(), m_cut_families.end(), [](
const auto& t_a,
const auto& t_b) {
172 return t_a->score() > t_b->score();
176 m_osi_solver->update_current_solution();
179 for (
auto& cut_family : m_cut_families) {
181 if (!be_aggressive && cut_family->score() <= 1e-2) {
185 auto osi_cuts = cut_family->generate(*m_osi_solver, be_aggressive ? 100 : 0);
186 auto idol_cuts = to_idol_cuts(osi_cuts);
187 auto sorted_cuts = sort_cuts_by_effectiveness(idol_cuts);
189 for (
auto& [cut, effectiveness] : sorted_cuts) {
191 if (m_total_n_added_cuts > m_max_cuts) {
195 if (cut_context.n_added_cuts() > max_n_added_cut_at_this_node) {
199 cut_family->add_effectiveness_statistics(effectiveness);
201 if (effectiveness <= m_effectiveness_threshold) {
205 const unsigned int old_n_added_user_cuts = registry.n_added_user_cuts;
207 const bool cut_was_accepted_by_idol = registry.n_added_user_cuts > old_n_added_user_cuts;
209 if (cut_was_accepted_by_idol) {
210 m_total_n_added_cuts++;
211 cut_context.add_accepted_cut();
212 cut_family->add_accepted_cut();
219template <
class NodeInfoT>
220std::list<idol::TempCtr> idol::CglCutCallback<NodeInfoT>::Strategy::to_idol_cuts(
OsiCuts& t_cuts) {
222 std::list<TempCtr> result;
224 for (
unsigned int k = 0, n = t_cuts.sizeRowCuts(); k < n; k++) {
225 auto& cut = *t_cuts.rowCutPtr(k);
227 if (!cut.consistent()) {
231 if (cut.infeasible(*m_osi_solver)) {
235 result.emplace_back(to_idol_cut(cut));
240 throw Exception(
"idol was not linked with Cgl.");
244template <
class NodeInfoT>
245idol::TempCtr idol::CglCutCallback<NodeInfoT>::Strategy::to_idol_cut(OsiRowCut& t_cut) {
247 const auto& model = this->original_model();
251 const auto osi_sense = t_cut.sense();
252 if (osi_sense ==
'G') {
253 cut.set_type(GreaterOrEqual);
254 cut.set_rhs(t_cut.lb());
256 else if (osi_sense ==
'L') {
257 cut.set_type(LessOrEqual);
258 cut.set_rhs(t_cut.ub());
260 else if (osi_sense ==
'R') {
261 if (
const double lb = t_cut.lb(); !is_neg_inf(lb)) {
262 cut.set_type(GreaterOrEqual);
263 cut.set_rhs(t_cut.lb());
265 else if (
const double ub = t_cut.ub(); !is_pos_inf(ub)) {
266 cut.set_type(LessOrEqual);
267 cut.set_rhs(t_cut.ub());
270 throw Exception(
"Cgl returned a cut of type R, which is not handled in idol.");
274 throw Exception(
"Could not handle cut type from Cgl.");
277 const auto& osi_row = t_cut.row();
278 const auto* indices = osi_row.getIndices();
279 const auto* values = osi_row.getElements();
281 for (
unsigned int k = 0, n = osi_row.getNumElements(); k < n; k++) {
282 const auto& var = model.get_var_by_index(indices[k]);
283 cut.lhs() += values[k] * var;
288 throw Exception(
"idol was not linked with Cgl.");
292template <
class NodeInfoT>
293std::vector<std::pair<idol::TempCtr, double>> idol::CglCutCallback<NodeInfoT>::Strategy::sort_cuts_by_effectiveness(
294 const std::list<TempCtr>& t_cuts) {
295 std::vector<std::pair<TempCtr, double>> result;
296 const auto primal_solution = this->node().info().primal_solution();
298 for (
auto& cut : t_cuts) {
299 double activity = 0.;
302 for (
const auto& [var, coefficient] : cut.lhs()) {
303 norm += coefficient * coefficient;
304 activity += coefficient * primal_solution.get(var);
306 norm = std::sqrt(norm);
308 double effectiveness = (activity - cut.rhs()) / norm;
309 if (cut.type() == GreaterOrEqual) {
310 effectiveness *= -1.;
313 result.emplace_back(std::move(cut), effectiveness);
316 std::sort(result.begin(), result.end(), [](
const auto& t_a,
const auto& t_b) {
317 return t_a.second > t_b.second;
323template <
class NodeInfoT>
324idol::CglCutCallback<NodeInfoT>::Strategy::NodeCutContext& idol::CglCutCallback<
325 NodeInfoT>::Strategy::get_cut_context() {
326 const auto current_node_id = this->node().id();
328 if (!m_cut_context || m_cut_context->node_id() != current_node_id) {
329 m_cut_context.reset(
new NodeCutContext(current_node_id));
332 return *m_cut_context;
void add_user_cut(const TempCtr &t_cut)
const SideEffectRegistry & side_effect_registry() const
const Node< NodeInfoT > & node() const
virtual void initialize()
void operator()(CallbackEvent t_event) override
void initialize() override