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_and_filter_cuts_by_effectiveness(
const std::list<TempCtr>& t_cuts);
69 void standard_scaling(std::list<TempCtr>& t_cuts);
71 NodeCutContext& get_cut_context();
72 const NodeCutContext& get_cut_context()
const {
return const_cast<Strategy*
>(
this)->get_cut_context(); }
74 void log_after_termination()
override;
75 void operator()(CallbackEvent t_event)
override;
82template <
class NodeInfoT>
84 return new Strategy();
87template <
class NodeInfoT>
89 return new CglCutCallback<NodeInfoT>(*
this);
92#include "idol/mixed-integer/optimizers/callbacks/cutting-planes/OsiIdolCglSolverInterface.h"
94template <
class NodeInfoT>
100 m_osi_solver = std::make_unique<OsiIdolCglSolverInterface>(*
this);
102 m_cut_families.emplace_back(
new CutFamily(
new CglKnapsackCover(),
"Cover"));
103 m_cut_families.emplace_back(
new CutFamily(
new CglFlowCover(),
"Flow Cover"));
104 m_cut_families.emplace_back(
new CutFamily(
new CglZeroHalf(),
"Zero Half"));
105 m_cut_families.emplace_back(
new CutFamily(
new CglMixedIntegerRounding(),
"MIR"));
106 m_cut_families.emplace_back(
new CutFamily(
new CglResidualCapacity(),
"Residual Capacity"));
111 throw Exception(
"idol was not linked with Cgl.");
115template <
class NodeInfoT>
116void idol::CglCutCallback<NodeInfoT>::Strategy::log_after_termination() {
118 BranchAndBoundCallback<NodeInfoT>::log_after_termination();
120 std::cout <<
"Cgl Cutting Planes:" << std::endl;
122 for (
const auto& cut_family : m_cut_families) {
123 std::cout <<
'\t' << cut_family->name() <<
": " << cut_family->n_accepted() <<
"\n";
128template <
class NodeInfoT>
131 if (t_event != InvalidSolution) {
135 if (m_total_n_added_cuts > m_max_cuts) {
139 if (this->
node().level() > m_max_depth_for_cuts) {
143 const double relative_gap = idol::relative_gap(this->best_bound(), this->best_obj()) * 100;
145 if (relative_gap < 1) {
149 auto& cut_context = get_cut_context();
150 const bool is_root_node = cut_context.is_root_node();
152 bool be_aggressive =
false;
153 if (is_root_node || relative_gap > 50) {
154 be_aggressive =
true;
157 unsigned int max_n_added_cut_at_this_node = m_max_cut_per_regular_node;
158 unsigned int max_pass_at_this_node = m_max_pass_per_regular_node;
160 max_n_added_cut_at_this_node = m_max_cut_at_aggressive_node;
161 max_pass_at_this_node = m_max_pass_at_aggressive_node;
164 if (cut_context.pass() > max_pass_at_this_node) {
168 cut_context.increment_pass();
171 if (!be_aggressive) {
172 std::sort(m_cut_families.begin(), m_cut_families.end(), [](
const auto& t_a,
const auto& t_b) {
173 return t_a->score() > t_b->score();
177 m_osi_solver->update_current_solution();
180 for (
auto& cut_family : m_cut_families) {
182 if (!be_aggressive && cut_family->score() <= 1e-2) {
186 auto osi_cuts = cut_family->generate(*m_osi_solver, be_aggressive ? 100 : 0);
187 auto idol_cuts = to_idol_cuts(osi_cuts);
188 standard_scaling(idol_cuts);
189 auto sorted_cuts = sort_and_filter_cuts_by_effectiveness(idol_cuts);
191 for (
auto& [cut, effectiveness] : sorted_cuts) {
193 if (m_total_n_added_cuts > m_max_cuts) {
197 if (cut_context.n_added_cuts() > max_n_added_cut_at_this_node) {
201 cut_family->add_effectiveness_statistics(effectiveness);
203 if (effectiveness <= m_effectiveness_threshold) {
207 const unsigned int old_n_added_user_cuts = registry.n_added_user_cuts;
209 const bool cut_was_accepted_by_idol = registry.n_added_user_cuts > old_n_added_user_cuts;
211 if (cut_was_accepted_by_idol) {
212 m_total_n_added_cuts++;
213 cut_context.add_accepted_cut();
214 cut_family->add_accepted_cut();
221template <
class NodeInfoT>
222std::list<idol::TempCtr> idol::CglCutCallback<NodeInfoT>::Strategy::to_idol_cuts(
OsiCuts& t_cuts) {
224 std::list<TempCtr> result;
226 for (
unsigned int k = 0, n = t_cuts.sizeRowCuts(); k < n; k++) {
227 auto& cut = *t_cuts.rowCutPtr(k);
229 if (!cut.consistent()) {
233 if (cut.infeasible(*m_osi_solver)) {
237 result.emplace_back(to_idol_cut(cut));
242 throw Exception(
"idol was not linked with Cgl.");
246template <
class NodeInfoT>
247idol::TempCtr idol::CglCutCallback<NodeInfoT>::Strategy::to_idol_cut(OsiRowCut& t_cut) {
249 const auto& model = this->original_model();
253 const auto osi_sense = t_cut.sense();
254 if (osi_sense ==
'G') {
255 cut.set_type(GreaterOrEqual);
256 cut.set_rhs(t_cut.lb());
258 else if (osi_sense ==
'L') {
259 cut.set_type(LessOrEqual);
260 cut.set_rhs(t_cut.ub());
262 else if (osi_sense ==
'R') {
263 if (
const double lb = t_cut.lb(); !is_neg_inf(lb)) {
264 cut.set_type(GreaterOrEqual);
265 cut.set_rhs(t_cut.lb());
267 else if (
const double ub = t_cut.ub(); !is_pos_inf(ub)) {
268 cut.set_type(LessOrEqual);
269 cut.set_rhs(t_cut.ub());
272 throw Exception(
"Cgl returned a cut of type R, which is not handled in idol.");
276 throw Exception(
"Could not handle cut type from Cgl.");
279 const auto& osi_row = t_cut.row();
280 const auto* indices = osi_row.getIndices();
281 const auto* values = osi_row.getElements();
283 for (
unsigned int k = 0, n = osi_row.getNumElements(); k < n; k++) {
284 const auto& var = model.get_var_by_index(indices[k]);
285 cut.lhs() += values[k] * var;
290 throw Exception(
"idol was not linked with Cgl.");
294template <
class NodeInfoT>
295std::vector<std::pair<idol::TempCtr, double>> idol::CglCutCallback<NodeInfoT>::Strategy::sort_and_filter_cuts_by_effectiveness(
296 const std::list<TempCtr>& t_cuts) {
297 std::vector<std::pair<TempCtr, double>> result;
298 const auto primal_solution = this->node().info().primal_solution();
300 for (
auto& cut : t_cuts) {
301 double activity = 0.;
304 for (
const auto& [var, coefficient] : cut.lhs()) {
305 norm += coefficient * coefficient;
306 activity += coefficient * primal_solution.get(var);
308 norm = std::sqrt(norm);
310 double effectiveness = (activity - cut.rhs()) / norm;
311 if (cut.type() == GreaterOrEqual) {
312 effectiveness *= -1.;
315 result.emplace_back(std::move(cut), effectiveness);
318 std::sort(result.begin(), result.end(), [](
const auto& t_a,
const auto& t_b) {
319 return t_a.second > t_b.second;
325template <
class NodeInfoT>
326void idol::CglCutCallback<NodeInfoT>::Strategy::standard_scaling(std::list<TempCtr>& t_cuts) {
328 for (
auto& cut : t_cuts) {
330 auto& row = cut.lhs();
331 double& rhs = cut.rhs();
332 double infinity_norm = 0;
333 for (
const auto& [var, coeff] : row) {
334 infinity_norm = std::max(std::abs(coeff), infinity_norm);
336 infinity_norm = std::max(infinity_norm, std::abs(rhs));
343 std::frexp(infinity_norm, &e);
344 double closest_power_of_2 = std::ldexp(1.0, e - 1);
345 if (closest_power_of_2 != 1.) {
346 row /= closest_power_of_2;
347 rhs /= closest_power_of_2;
353template <
class NodeInfoT>
354idol::CglCutCallback<NodeInfoT>::Strategy::NodeCutContext& idol::CglCutCallback<
355 NodeInfoT>::Strategy::get_cut_context() {
356 const auto current_node_id = this->node().id();
358 if (!m_cut_context || m_cut_context->node_id() != current_node_id) {
359 m_cut_context.reset(
new NodeCutContext(current_node_id));
362 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