TECA
The Toolkit for Extreme Climate Analysis
teca_priority_queue.h
1 #ifndef teca_priority_queue_h
2 #define teca_priority_queue_h
3 
4 #include "teca_config.h"
5 
6 #include <ostream>
7 #include <vector>
8 #include <map>
9 #include <type_traits>
10 #include <cmath>
11 #include <memory>
12 
13 
14 /// @cond
15 
16 // use one of the following aliases for key_map_t. key_map_t
17 // is the type of container used to hold the locations of user provided
18 // keys in the heap.
19 // for keys that are not ordinals 0 to N, use the mapped_key_t alias
20 // for contiguous keys 0 to N (faster), use the contiguous_key_t alias
21 template<typename key_t>
22 using mapped_key_t = std::map<key_t, unsigned long>;
23 
24 using contiguous_key_t = std::vector<unsigned long>;
25 
26 // use one of the following objects to provide the priority for the
27 // given key. these objects internally point to the container index by
28 // key value holding the associated priority.
29 // for keys that are not ordinals 0 to N, use the mapped_key_priority_t alias
30 // for contiguous keys 0 to N (faster), use the contiguous_key_priority_t alias
31 template<typename key_t, typename priority_t>
32 struct TECA_EXPORT mapped_key_priority
33 {
34  using key_map_t = mapped_key_t<key_t>;
35 
36  mapped_key_priority(std::map<key_t, priority_t> &mp) : m_map(&mp)
37  {}
38 
39  priority_t operator()(key_t i)
40  { return (*m_map)[i]; }
41 
42  std::map<key_t, priority_t> *m_map;
43 };
44 
45 template<typename key_t, typename priority_t>
46 struct TECA_EXPORT contiguous_key_priority
47 {
48  using key_map_t = contiguous_key_t;
49 
50  contiguous_key_priority(const std::vector<priority_t> &vec) :
51  m_vec(vec.data())
52  {}
53 
54  priority_t operator()(key_t i)
55  { return m_vec[i]; }
56 
57  const priority_t *m_vec;
58 };
59 
60 // forward declare the queue
61 template <typename key_t, typename lookup_t, typename comp_t=std::less<>,
62  typename key_map_t=contiguous_key_t>
64 
65 // pointer type
66 template<typename key_t, typename lookup_t, typename ...Types >
67 using p_teca_priority_queue = std::shared_ptr<
68  teca_priority_queue<key_t, lookup_t, Types...>>;
69 
70 /// @endcond
71 
72 /** @brief
73  * An indirect priority queue that supports random access modification of
74  * priority.
75  *
76  * @details
77  * an indirect priority queue that supports random access modification of
78  * priority the queue works with user provided keys and lookup functor that
79  * converts keys to priorities.
80  *
81  * ### template parameters:
82  *
83  * | name | description |
84  * | ---- | ----------- |
85  * | key_t | type of the user provided keys |
86  * | lookup_t | callable that implements: priority_t operator()(key_t key) |
87  * | comp_t | callable that implements the predicate: bool(key_t, key_t), |
88  * | | used to enforce heap order. (std::less<key_t>) |
89  * | key_map_t | type of container used to track the position in the heap |
90  * | | of the keys. The default, a vector, is only valid for |
91  * | | interger ordinals from 0 to N. Use mapped_key_t<key_t> |
92  * | | for all other cases. (contiguous_key_t) |
93  *
94  * ### typical usage:
95  *
96  * construct a container of objects to prioritize, and initialize a lookup
97  * object that given a key returns the priority of the coresponding object.
98  * create an instance of the priority_queue and push the key values. as keys
99  * are pushed heap ording is imposed, this is why objects need to be in place
100  * before pushing keys. when an object's priority has been changed one must
101  * call modified passing the key of the object. the location of each object is
102  * tracked and the queue will reprioritize itself after modification.
103  *
104  * ### recomendation:
105  *
106  * to obtain high performance, it's best to avoid using std::function for
107  * lookup operations. Instead, write a small functor so that the compiler
108  * can inline lookup calls.
109  *
110  * don't forget to change key_map_t to mapped_key_t<key_t> if
111  * keys are not integer ordinals 0 to N.
112  */
113 template <typename key_t, typename lookup_t,
114  typename comp_t, typename key_map_t>
116 {
117 public:
118 
119  ~teca_priority_queue() = default;
120 
121  // return a new instance, must pass the lookup operator that
122  // translates keys into priority values
123  static p_teca_priority_queue<key_t, lookup_t, comp_t, key_map_t>
124  New(lookup_t &lookup, unsigned long init_size=256,
125  unsigned long block_size=256)
126  {
127  p_teca_priority_queue<key_t, lookup_t,
128  comp_t, key_map_t> ptr(
130  key_t, lookup_t, comp_t, key_map_t>(
131  lookup, init_size, block_size));
132  return ptr;
133  }
134 
135  // return true if the queue has no keys
136  bool empty() { return m_end == 0; }
137 
138 
139  /// add a value into the queue
140  void push(const key_t &key);
141 
142  /// free all resources and reset the queue to an empty state
143  void clear();
144 
145  // restore heap condition after an id is modified
146  void modified(const key_t &key);
147 
148  // return the id at the top of the queue, and remove it.
149  // internal memory is not deallocated.
150  key_t pop();
151 
152  // return the id in the top of queue
153  key_t peak();
154 
155  // print the state of the queue
156  void to_stream(std::ostream &os, bool priorities = true);
157 
158 protected:
159  teca_priority_queue() = default;
160 
162  void operator=(const teca_priority_queue<key_t, lookup_t> &) = delete;
163 
164  // initialize the queue with an comperator, the initial size, and declare
165  // the amount to grow the queue by during dynamic resizing.
166  template<typename u = key_map_t>
167  teca_priority_queue(lookup_t lookup, unsigned long init_size,
168  unsigned long block_size,
169  typename std::enable_if<std::is_same<
170  std::vector<unsigned long>, u>::value>::type * = 0);
171 
172  template<typename u = key_map_t>
173  teca_priority_queue(lookup_t lookup, unsigned long init_size,
174  unsigned long block_size,
175  typename std::enable_if<std::is_same<
176  std::map<key_t,unsigned long>, u>::value>::type * = 0);
177 
178  // grow the queue to the new size
179  template<typename u = key_map_t>
180  void grow(unsigned long n,
181  typename std::enable_if<std::is_same<
182  std::vector<unsigned long>, u>::value>::type * = 0);
183 
184  // grow the queue to the new size
185  template<typename u = key_map_t>
186  void grow(unsigned long n,
187  typename std::enable_if<std::is_same<
188  std::map<key_t, unsigned long>, u>::value>::type * = 0);
189 
190  // restore the heap condition starting from here
191  // and working up
192  void up_heapify(unsigned long id);
193 
194  // restore the heap condition starting from here
195  // and working down
196  void down_heapify(unsigned long id);
197 
198  // exchange two items
199  void swap(unsigned long i, unsigned long j);
200 
201  // helpers for walking tree
202  unsigned long left_child(unsigned long a_id)
203  { return a_id*2; }
204 
205  unsigned long right_child(unsigned long a_id)
206  { return a_id*2 + 1; }
207 
208  unsigned long parent(unsigned long a_id)
209  { return a_id/2; }
210 
211 
212 private:
213  lookup_t m_lookup; // callable to turn keys into priority values
214  std::vector<key_t> m_ids; // array of keys
215  key_map_t m_locs; // map indexed by key to find the current position in the queue
216  unsigned long m_size; // size of the key buffer
217  unsigned long m_end; // index of the last key in the queue
218  unsigned long m_block_size; // amount to grow the dynamically alloacted buffers by
219 };
220 
221 
222 // --------------------------------------------------------------------------
223 template <typename key_t, typename lookup_t,
224  typename comp_t, typename key_map_t>
225 void teca_priority_queue<key_t, lookup_t,
226  comp_t, key_map_t>::push(const key_t &key)
227 {
228  // extend the queue
229  ++m_end;
230 
231  // verify that there is space, if not allocate it
232  if (m_end >= m_size)
233  this->grow(m_size + m_block_size);
234 
235  // add key and it's location
236  m_ids[m_end] = key;
237  m_locs[key] = m_end;
238 
239  // restore heap condition
240  this->up_heapify(m_end);
241 }
242 
243 // --------------------------------------------------------------------------
244 template <typename key_t, typename lookup_t,
245  typename comp_t, typename key_map_t>
246 void teca_priority_queue<key_t, lookup_t,
247  comp_t, key_map_t>::clear()
248 {
249  m_ids.clear();
250  m_locs.clear();
251  m_size = 0;
252  m_end = 0;
253 }
254 
255 // --------------------------------------------------------------------------
256 template <typename key_t, typename lookup_t,
257  typename comp_t, typename key_map_t>
258 void teca_priority_queue<key_t, lookup_t,
259  comp_t, key_map_t>::modified(const key_t &key)
260 {
261  // find the loc of the modified key
262  unsigned long id = m_locs[key];
263  // fix up then down
264  this->up_heapify(id);
265  this->down_heapify(id);
266 }
267 
268 // --------------------------------------------------------------------------
269 template <typename key_t, typename lookup_t,
270  typename comp_t, typename key_map_t>
271 key_t teca_priority_queue<key_t, lookup_t,
272  comp_t, key_map_t>::pop()
273 {
274  key_t id_1 = m_ids[1];
275  if (m_end > 0)
276  {
277  this->swap(1, m_end);
278  --m_end;
279  this->down_heapify(1);
280  }
281  return id_1;
282 }
283 
284 // --------------------------------------------------------------------------
285 template <typename key_t, typename lookup_t,
286  typename comp_t, typename key_map_t>
287 key_t teca_priority_queue<key_t, lookup_t,
288  comp_t, key_map_t>::peak()
289 {
290  return m_ids[1];
291 }
292 
293 // --------------------------------------------------------------------------
294 template <typename key_t, typename lookup_t,
295  typename comp_t, typename key_map_t>
296 void teca_priority_queue<key_t, lookup_t,
297  comp_t, key_map_t>::to_stream(std::ostream &os, bool priorities)
298 {
299  long log_end = std::log2(m_end);
300  long n_rows = log_end + 1;
301  unsigned long q = 0;
302  for (long i = 0; i < n_rows; ++i)
303  {
304  if (q > m_end)
305  break;
306 
307  long n_elem = 1 << i;
308  long isp = (1 << (n_rows - 1 - i)) - 1;
309  long bsp = 2*isp + 1;
310 
311  for (long j = 0; j < isp; ++j)
312  os << " ";
313 
314  for (long j = 0; (j < n_elem) && (q < m_end); ++j)
315  {
316  if (priorities)
317  os << m_lookup(m_ids[++q]);
318  else
319  os << m_ids[++q];
320  for (long k = 0; k < bsp; ++k)
321  os << " ";
322  }
323 
324  os << std::endl;
325  }
326 }
327 
328 // --------------------------------------------------------------------------
329 template <typename key_t, typename lookup_t,
330  typename comp_t, typename key_map_t>
331 template<typename u>
332 teca_priority_queue<key_t, lookup_t,
333  comp_t, key_map_t>::teca_priority_queue(lookup_t lookup,
334  unsigned long init_size, unsigned long block_size,
335  typename std::enable_if<std::is_same<
336  std::vector<unsigned long>, u>::value>::type *) :
337  m_lookup(lookup), m_size(init_size), m_end(0),
338  m_block_size(block_size)
339 {
340  m_ids.resize(init_size);
341  m_locs.resize(init_size);
342 }
343 
344 // --------------------------------------------------------------------------
345 template <typename key_t, typename lookup_t,
346  typename comp_t, typename key_map_t>
347 template<typename u>
348 teca_priority_queue<key_t, lookup_t,
349  comp_t, key_map_t>::teca_priority_queue(lookup_t lookup,
350  unsigned long init_size, unsigned long block_size,
351  typename std::enable_if<std::is_same<
352  std::map<key_t,unsigned long>, u>::value>::type *) :
353  m_lookup(lookup), m_size(init_size), m_end(0),
354  m_block_size(block_size)
355 {
356  m_ids.resize(init_size);
357 }
358 
359 // --------------------------------------------------------------------------
360 template <typename key_t, typename lookup_t,
361  typename comp_t, typename key_map_t>
362 template<typename u>
363 void teca_priority_queue<key_t, lookup_t,
364  comp_t, key_map_t>::grow(unsigned long n,
365  typename std::enable_if<std::is_same<
366  std::vector<unsigned long>, u>::value>::type *)
367 {
368  m_ids.resize(n);
369  m_locs.resize(n);
370  m_size = n;
371 }
372 
373 // --------------------------------------------------------------------------
374 template <typename key_t, typename lookup_t,
375  typename comp_t, typename key_map_t>
376 template<typename u>
377 void teca_priority_queue<key_t, lookup_t,
378  comp_t, key_map_t>::grow(unsigned long n,
379  typename std::enable_if<std::is_same<
380  std::map<key_t, unsigned long>, u>::value>::type *)
381 {
382  m_ids.resize(n);
383  m_size = n;
384 }
385 
386 
387  // --------------------------------------------------------------------------
388 template <typename key_t, typename lookup_t,
389  typename comp_t, typename key_map_t>
390 void teca_priority_queue<key_t, lookup_t,
391  comp_t, key_map_t>::up_heapify(unsigned long id)
392 {
393  // if at tree root then stop
394  if (id < 2)
395  return;
396 
397  // else find parent and enforce heap order
398  comp_t comp;
399  unsigned long id_p = parent(id);
400  if (comp(m_lookup(m_ids[id]), m_lookup(m_ids[id_p])))
401  this->swap(id, id_p);
402 
403  // continue up toward the root
404  this->up_heapify(id_p);
405 }
406 
407 // --------------------------------------------------------------------------
408 template <typename key_t, typename lookup_t,
409  typename comp_t, typename key_map_t>
410 void teca_priority_queue<key_t, lookup_t,
411  comp_t, key_map_t>::down_heapify(unsigned long id)
412 {
413  // if no current node then stop
414  if (id > m_end)
415  return;
416 
417  // if no left child then stop
418  unsigned long lc = left_child(id);
419  if (lc > m_end)
420  return;
421 
422  // find the smaller child
423  comp_t comp;
424  unsigned long smallc = lc;
425  unsigned long rc = right_child(id);
426  if (rc <= m_end)
427  smallc = comp(m_lookup(m_ids[lc]),
428  m_lookup(m_ids[rc])) ? lc : rc;
429 
430  // if in heap order then stop
431  if (comp(m_lookup(m_ids[id]), m_lookup(m_ids[smallc])))
432  return;
433 
434  // else swap and continue
435  this->swap(id, smallc);
436  this->down_heapify(smallc);
437 }
438 
439 // --------------------------------------------------------------------------
440 template <typename key_t, typename lookup_t,
441  typename comp_t, typename key_map_t>
442 void teca_priority_queue<key_t, lookup_t,
443  comp_t, key_map_t>::swap(unsigned long i, unsigned long j)
444 {
445  key_t key_i = m_ids[i];
446  key_t key_j = m_ids[j];
447  // exchange keys
448  m_ids[i] = key_j;
449  m_ids[j] = key_i;
450  // update locs
451  m_locs[key_j] = i;
452  m_locs[key_i] = j;
453 }
454 
455 template <typename key_t, typename lookup_t, typename ... Args>
456 std::ostream & operator<<(std::ostream &os, p_teca_priority_queue<key_t, lookup_t, Args ...> &q)
457 {
458  q->to_stream(os);
459  return os;
460 }
461 
462 #endif
operator<<
TECA_EXPORT std::ostream & operator<<(std::ostream &os, const teca_calendar_util::time_point &tpt)
send the time_point to a stream in humnan readable form
teca_priority_queue::clear
void clear()
free all resources and reset the queue to an empty state
Definition: teca_priority_queue.h:247
teca_priority_queue
An indirect priority queue that supports random access modification of priority.
Definition: teca_priority_queue.h:115
teca_error::TECA_EXPORT
p_teca_error_handler error_handler TECA_EXPORT
The global error handler instance.