All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Macros Pages
budgeted_maximum_coverage.hpp
Go to the documentation of this file.
1 
8 #ifndef PAAL_BUDGETED_MAXIMUM_COVERAGE_HPP
9 #define PAAL_BUDGETED_MAXIMUM_COVERAGE_HPP
10 
14 #include "paal/utils/functors.hpp"
16 
17 #include <boost/heap/d_ary_heap.hpp>
18 #include <boost/range/adaptor/indexed.hpp>
19 #include <boost/range/algorithm/copy.hpp>
20 #include <boost/range/algorithm_ext/iota.hpp>
21 #include <boost/range/algorithm/fill.hpp>
22 #include <boost/range/algorithm/for_each.hpp>
23 #include <boost/range/algorithm/sort.hpp>
24 #include <boost/range/combine.hpp>
25 
26 #include <algorithm>
27 #include <vector>
28 
29 namespace paal {
30 namespace greedy {
31 namespace detail {
32 
33 template <typename ElementWeight, typename SetCost> struct set_data_type {
35  : m_weight_of_uncovered_elements{}, m_cost{}, m_is_processed{ true } {}
36  ElementWeight m_weight_of_uncovered_elements; // sum of weight of uncovered
37  // elements in backtrack and
38  // greedy
39  SetCost m_cost;
40  bool m_is_processed;
41  // set is processed if is selected or is not selected and at least one of
42  // the following situations occurs
43  // -we have not enough budget to select set
44  // -sum of weight of uncovered elements belong to set equal 0
45 };
46 
47 template <typename SetReference, typename GetElementsOfSet,
48  typename GetWeightOfElement>
49 using element_weight_t = pure_result_of_t<GetWeightOfElement(
50  range_to_elem_t<pure_result_of_t<GetElementsOfSet(SetReference)>>)>;
51 
52 const int UNCOVERED = -1;
53 template <class Budget, class SetCost, class SetIdToData, class ElementWeight,
54  class SetIdToElements, class ElementIndex, class GetWeightOfElement,
55  typename DecreseWeight>
56 class selector {
57  const Budget m_budget;
58  SetCost &m_cost_of_solution;
59  std::vector<int> m_selected_sets;
60  SetIdToData &m_sets_data;
61  const ElementWeight &m_weight_of_bests_solution;
62  ElementWeight &m_weight_of_covered_elements;
63  SetIdToElements m_set_id_to_elements;
64  std::vector<int> &m_covered_by;
65  std::vector<std::vector<int>> &m_sets_covering_element;
66  ElementIndex &m_get_el_index;
67  GetWeightOfElement &m_element_to_weight;
68  DecreseWeight m_decrese_weight;
69 
70  using SetData = range_to_elem_t<SetIdToData>;
71 
72  public:
73  selector(const Budget budget, SetCost &cost_of_solution,
74  SetIdToData &sets_data,
75  const ElementWeight &weight_of_bests_solution,
76  ElementWeight &weight_of_covered_elements,
77  SetIdToElements set_id_to_elements, std::vector<int> &covered_by,
78  std::vector<std::vector<int>> &sets_covering_element,
79  ElementIndex &get_el_index, GetWeightOfElement &element_to_weight,
80  DecreseWeight decrese_weight)
81  : m_budget(budget), m_cost_of_solution(cost_of_solution),
82  m_sets_data(sets_data),
83  m_weight_of_bests_solution(weight_of_bests_solution),
84  m_weight_of_covered_elements(weight_of_covered_elements),
85  m_set_id_to_elements(set_id_to_elements), m_covered_by(covered_by),
86  m_sets_covering_element(sets_covering_element),
87  m_get_el_index(get_el_index), m_element_to_weight(element_to_weight),
88  m_decrese_weight(decrese_weight) {}
89 
90  // we return true if (we select set) or (set is already in solution)
91  bool select_set_backtrack(int selected_set_id, bool in_reset = false) {
92  if(!can_select(m_sets_data[selected_set_id])) return false;
93 
94  select_set(selected_set_id, true);
95  if(!in_reset) m_selected_sets.push_back(selected_set_id);
96  return true;
97  }
98 
99  // we return true if we violated budget
100  bool select_set_greedy(int selected_set_id) {
101  auto &select_set_data = m_sets_data[selected_set_id];
102 
103  if(!can_select(select_set_data)) return false;
104  m_selected_sets.push_back(selected_set_id);
105 
106  if(greedy_prune(select_set_data)) return true;
107  select_set(selected_set_id, false);
108  return false;
109  }
110 
111 
112  void deselect_set(int selected_set_id, bool backtrack = true) {
113  // we deselect set
114 
115  m_cost_of_solution -= m_sets_data[selected_set_id].m_cost;
116  for (auto element : m_set_id_to_elements(selected_set_id)) {
117  if (covered_by(element) == selected_set_id) {
118  m_covered_by[m_get_el_index(element)] = UNCOVERED;
119  auto weight_of_element = m_element_to_weight(element);
120  cover_element(element, -weight_of_element, backtrack, false);
121  }
122  }
123  m_selected_sets.pop_back();
124  }
125 
126  void set_unprocessed(
127  boost::iterator_range<std::vector<int>::const_iterator> const & set_ids) {
128  for (auto set_id : set_ids) {
129  m_sets_data[set_id].m_is_processed = false;
130  }
131  };
132 
133  void reset() {
134  set_unprocessed(m_selected_sets);
135  if (m_selected_sets.size() > 0) {
136  boost::for_each(m_selected_sets, [&](int selected_set_id) {
137  select_set_backtrack(selected_set_id, true);
138  });
139  }
140  }
141 
142  std::size_t size() {
143  return m_selected_sets.size();
144  }
145 
146  void resize(std::size_t size) {
147  return m_selected_sets.resize(size);
148  }
149 
150  template <typename OutputIterator>
151  void copy_to(OutputIterator out){
152  boost::copy(m_selected_sets, out);
153  }
154 private:
155 
156  void select_set(int selected_set_id, bool backtrack) {
157  auto &select_set_data = m_sets_data[selected_set_id];
158 
159  m_cost_of_solution += select_set_data.m_cost;
160  for (auto element : m_set_id_to_elements(selected_set_id)) {
161  if (covered_by(element) == UNCOVERED) { /* we do not cover the
162  elements being covered*/
163 
164  m_covered_by[m_get_el_index(element)] = selected_set_id;
165  auto weight_of_element = m_element_to_weight(element);
166  cover_element(element, weight_of_element, backtrack);
167  }
168  }
169  }
170 
175  bool greedy_prune(const SetData & set_data) {
176  return (m_budget - m_cost_of_solution) * set_data.m_weight_of_uncovered_elements <=
177  static_cast<Budget>(set_data.m_cost * (m_weight_of_bests_solution - m_weight_of_covered_elements));
178  }
179 
181  bool can_select(SetData & set_data) {
182  if (set_data.m_is_processed) return false;
183  set_data.m_is_processed = true;
184  return static_cast<Budget>(m_cost_of_solution + set_data.m_cost) <= m_budget &&
185  set_data.m_weight_of_uncovered_elements!= ElementWeight{};
186  }
187 
188  template <typename Element>
189  void cover_element(Element && el, ElementWeight weight_diff, bool backtrack, bool select = true) {
190  m_weight_of_covered_elements += weight_diff;
191  for (auto set_id :
192  m_sets_covering_element[m_get_el_index(el)]) {
193  if (m_sets_data[set_id].m_weight_of_uncovered_elements >
194  weight_diff || backtrack) {
195  m_sets_data[set_id].m_weight_of_uncovered_elements -= weight_diff;
196  if (!backtrack && !m_sets_data[set_id].m_is_processed) {
197  m_decrese_weight(set_id);
198  }
199  } else {
200  if (select) {
201  m_sets_data[set_id].m_is_processed = true;
202  }
203  }
204  }
205  }
206 
207  template <typename Element>
208  auto covered_by(Element && el) const
209  -> decltype(m_covered_by[m_get_el_index(el)]) {
210 
211  return m_covered_by[m_get_el_index(el)];
212  }
213 };
214 
215 template <class Budget, class SetCost, class SetIdToData, class ElementWeight,
216  class SetIdToElements, class ElementIndex, class GetWeightOfElement,
217  typename DecreseWeight>
218 auto make_selector(const Budget budget, SetCost &cost_of_solution,
219  SetIdToData &sets_data,
220  const ElementWeight &weight_of_bests_solution,
221  ElementWeight &weight_of_covered_elements,
222  SetIdToElements set_id_to_elements,
223  std::vector<int> &covered_by,
224  std::vector<std::vector<int>> &sets_covering_element,
225  ElementIndex &get_el_index,
226  GetWeightOfElement &element_to_weight,
227  DecreseWeight decrese_weight) {
228  return selector<Budget, SetCost, SetIdToData, ElementWeight,
229  SetIdToElements, ElementIndex, GetWeightOfElement,
230  DecreseWeight>(
231  budget, cost_of_solution, sets_data,
232  weight_of_bests_solution, weight_of_covered_elements,
233  set_id_to_elements, covered_by, sets_covering_element, get_el_index,
234  element_to_weight, decrese_weight);
235 }
236 }
237 
261 template <typename SetRange, class GetCostOfSet, class GetElementsOfSet,
262  class OutputIterator, class ElementIndex, class Budget,
263  class GetWeightOfElement = utils::return_one_functor>
265  SetRange && sets, GetCostOfSet set_to_cost,
266  GetElementsOfSet set_to_elements, OutputIterator result,
267  ElementIndex get_el_index, Budget budget,
268  GetWeightOfElement element_to_weight = GetWeightOfElement(),
269  const unsigned int initial_set_size = 3) {
270 
271  using set_reference = typename boost::range_reference<SetRange>::type;
272  using element_weight = typename detail::element_weight_t<
273  set_reference, GetElementsOfSet, GetWeightOfElement>;
275  using set_id_to_data = std::vector<detail::set_data_type<element_weight, set_cost>>;
276 
277  auto nu_sets = boost::distance(sets);
278  set_id_to_data initial_sets_data(nu_sets), sets_data(nu_sets);
279  set_cost cost_of_solution{}, cost_of_best_solution{};
280  int number_of_elements = 0;
281 
282  // we find max index of elements in all sets
283  for (auto set_and_data : boost::combine(sets, initial_sets_data)) {
284  auto const & set = boost::get<0>(set_and_data);
285  auto &cost = boost::get<1>(set_and_data).m_cost;
286  cost = set_to_cost(set);
287  assert(cost != set_cost{});
288  auto const & elements = set_to_elements(set);
289  if (!boost::empty(elements)) {
290  number_of_elements = std::max(number_of_elements,
291  *max_element_functor(elements, get_el_index) + 1);
292  }
293  }
294  element_weight weight_of_covered_elements{}, weight_of_bests_solution{};
295  std::vector<int> best_solution(1);
296  std::vector<int> covered_by(
297  number_of_elements, detail::UNCOVERED); // index of the first set that covers
298  // element or -1 if element is uncovered
299  std::vector<int> sets_id(nu_sets);
300  boost::iota(sets_id, 0);
301  std::vector<std::vector<int>> sets_covering_element(number_of_elements);
302  auto decreasing_density_order =
305  sets_data[x].m_weight_of_uncovered_elements, sets_data[x].m_cost);
306  });
307  using queue = boost::heap::d_ary_heap<
308  int, boost::heap::arity<3>, boost::heap::mutable_<true>,
309  boost::heap::compare<decltype(decreasing_density_order)>>;
310 
311  queue uncovered_set_queue{ decreasing_density_order };
312  std::vector<typename queue::handle_type> set_id_to_handle(nu_sets);
313 
314  // we fill sets_covering_element and setToWeightOfElements
315  for (auto set : sets | boost::adaptors::indexed()) {
316  auto set_id = set.index();
317  auto &set_data = initial_sets_data[set_id];
318 
319  for (auto &&element : set_to_elements(set.value())) {
320  sets_covering_element[get_el_index(element)].push_back(set_id);
321  set_data.m_weight_of_uncovered_elements += element_to_weight(element);
322  }
323  if (initial_set_size ==
324  0) { /* we check all one element set. if initial_set_size!= 0 then
325  we will do it anyway */
326  set_cost cost_of_set = set_data.m_cost;
327  if (set_data.m_weight_of_uncovered_elements >= weight_of_bests_solution &&
328  static_cast<Budget>(cost_of_set) <= budget) {
329  weight_of_bests_solution = set_data.m_weight_of_uncovered_elements;
330  best_solution[0] = set_id;
331  cost_of_best_solution = cost_of_set;
332  }
333  }
334  };
335 
336  auto sort_sets = [&](std::vector<int>& sets_range) {
337  boost::sort(sets_range, utils::make_functor_to_comparator([&](int x) {
338  return sets_data[x].m_weight_of_uncovered_elements;
339  }));
340  return sets_range.end();
341  };
342  auto selector = detail::make_selector
343  (budget, cost_of_solution,sets_data,
344  weight_of_bests_solution,weight_of_covered_elements,
345  [&](int selected_set_id){return set_to_elements(sets[selected_set_id]);},
346  covered_by,sets_covering_element,get_el_index,
347  element_to_weight,
348  [&](int set_id){uncovered_set_queue.decrease(set_id_to_handle[set_id]);});
349 
350  boost::copy(initial_sets_data, sets_data.begin());
351  sort_sets(sets_id);
352  auto solver = make_subset_backtrack(sets_id);
353 
354  auto reset = [&]() {
355  boost::copy(initial_sets_data, sets_data.begin());
356  cost_of_solution = set_cost{};
357  selector.set_unprocessed(solver.get_moves());
358  boost::fill(covered_by, detail::UNCOVERED);
359  weight_of_covered_elements = element_weight{};
360  selector.reset();
361  };
362 
363  auto on_pop = [&](int deselected_set_id) {
364  selector.deselect_set(deselected_set_id);
365  selector.set_unprocessed(solver.get_moves());
366  };
367 
368  auto save_best_solution = [&]() {
369  // we check that new solution is better than any previous
370  // if is we remember them
371  // tricky: either better weight, or equal weight and lower cost
372  if (std::make_pair(weight_of_covered_elements, cost_of_best_solution) >
373  std::make_pair(weight_of_bests_solution, cost_of_solution)) {
374  weight_of_bests_solution = weight_of_covered_elements;
375  cost_of_best_solution = cost_of_solution;
376  best_solution.resize(selector.size());
377  selector.copy_to(best_solution.begin());
378  }
379  };
380  auto greedy_phase = [&]() {
381  uncovered_set_queue.clear();
382  auto moves = solver.get_moves();
383  if(boost::empty(moves)) return;
384 
385  for (auto set_id : moves) {
386  set_id_to_handle[set_id] = uncovered_set_queue.push(set_id);
387  }
388  /* we select set with best elements to cost ratio, and add it to the
389  * result until all elements are covered*/
390  int uncovered_set_id;
391  do {
392  uncovered_set_id = uncovered_set_queue.top();
393  uncovered_set_queue.pop();
394  } while (!selector.select_set_greedy(uncovered_set_id) &&
395  !uncovered_set_queue.empty());
396  };
397 
398  auto can_push = [&](int candidate) {
399  if (!selector.select_set_backtrack(candidate)) {
400  return false;
401  }
402  if (selector.size() == initial_set_size) {
403  greedy_phase();
404  save_best_solution();
405  selector.resize(initial_set_size-1);
406  reset();
407  return false;
408  } else {
409  save_best_solution();
410  return true;
411  }
412  };
413 
414  reset();
415  if (initial_set_size != 0) { /* if initial_set_size == 0 then we do greedy
416  algorithm once starts from empty initial Set.
417  Otherwise we starts greedy algorithm from all initial set of size equal
418  initial_set_size those for which we have enough budget*/
419  solver.solve(can_push, on_pop, sort_sets);
420  } else {
421  greedy_phase();
422  save_best_solution();
423  }
424 
425  for (auto set_id : best_solution) {
426  *result = *(sets.begin() + set_id);
427  ++result;
428  }
429  return weight_of_bests_solution;
430 };
431 }
432 }
433 
434 #endif /* PAAL_BUDGETED_MAXIMUM_COVERAGE_HPP */
typename boost::range_value< Range >::type range_to_elem_t
for given range returns type of its element
auto budgeted_maximum_coverage(SetRange &&sets, GetCostOfSet set_to_cost, GetElementsOfSet set_to_elements, OutputIterator result, ElementIndex get_el_index, Budget budget, GetWeightOfElement element_to_weight=GetWeightOfElement(), const unsigned int initial_set_size=3)
detail
auto max_element_functor(Range &&range, Functor f)
combination of boost::max_element and boost::adaptors::transformed
auto make_functor_to_comparator(Functor functor, Compare compare=Compare())
make for functor to comparator
Definition: functors.hpp:654
This file contains set of simple useful functors or functor adapters.
fraction< A, B > make_fraction(A a, B b)
make function for fraction
Definition: fraction.hpp:149
Implementation of fractions, which are used only for comparison purposes, and thus they can be used w...
auto make_subset_backtrack(const Elements &elements)
make subset_backtrack
typename std::decay< typename std::result_of< F >::type >::type pure_result_of_t
return pure type of function (decays const and reference)