LArSoft  v07_13_02
Liquid Argon Software toolkit - http://larsoft.org/
FindManyInChainP.tcc
Go to the documentation of this file.
1 /**
2  * @file lardata/Utilities/FindManyInChainP.tcc
3  * @brief Template implementation for `FindManyInChainP.h`.
4  * @author Gianluca Petrillo (petrillo@fnal.gov)
5  * @date June 26, 2017
6  *
7  */
8 
9 #ifndef LARDATA_UTILITIES_FINDMANYINCHAINP_TCC
10 #define LARDATA_UTILITIES_FINDMANYINCHAINP_TCC
11 
12 #ifndef LARDATA_UTILITIES_FINDMANYINCHAINP_H
13 #error "FindManyInChainP.tcc must not be included directly. Include FindManyInChainP.h instead."
14 #endif // LARDATA_UTILITIES_FINDMANYINCHAINP_H
15 
16 // framework
17 #include "art/Framework/Principal/Handle.h"
18 #include "art/Persistency/Provenance/ProductMetaData.h"
19 #include "canvas/Persistency/Common/FindManyP.h"
20 #include "canvas/Persistency/Common/Assns.h"
21 #include "canvas/Utilities/Exception.h"
22 
23 // C/C++ standard library
24 #include <map>
25 #include <iterator> // std::begin(), std::cbegin(), std::distance()...
26 #include <tuple> // std::tuple_cat(), ...
27 #include <algorithm> // std::lower_bound(), std::sort(), std::transform()...
28 #include <utility> // std::pair<>, std::move(), std::declval()...
29 #include <type_traits> // std::decay_t<>, std::enable_if_t<>, ...
30 
31 
32 //------------------------------------------------------------------------------
33 //--- template implementation
34 //---
35 namespace lar {
36 
37  namespace details {
38 
39  //--------------------------------------------------------------------------
40  // this is a copy of things in cetlib; should be replaced by the original
41  // if they get exposed
42  template <class T, class R /* = void */>
43  struct enable_if_type_exists { using type = R; };
44 
45  //--------------------------------------------------------------------------
46  /// Trait evaluating to true if H is a art Handle type.
47  template <typename H, typename = void>
48  struct is_handle: public std::false_type {};
49 
50  template <typename H>
51  struct is_handle<H, enable_if_is_handle_t<H>>: public std::true_type {};
52 
53  template <typename H>
54  constexpr bool is_handle_v = is_handle<H>::value;
55 
56  //--------------------------------------------------------------------------
57  template <typename T>
58  struct is_art_ptr_impl: public std::false_type {};
59 
60  template <typename T>
61  struct is_art_ptr_impl<art::Ptr<T>>: public std::true_type {};
62 
63  /// Whether the specified type is an art::Ptr.
64  template <typename T>
65  using is_art_ptr = is_art_ptr_impl<std::decay_t<T>>;
66 
67 
68  //--------------------------------------------------------------------------
69 
70  /// Hosts as `type` the type in position `N` (first is `0`).
71  // this might be implemented with std::tuple_element;
72  // here we get control on error messages
73  template <unsigned int N, typename... Args>
74  struct get_type;
75 
76  template <unsigned int N, typename... Args>
77  using get_type_t = typename get_type<N, Args...>::type;
78 
79 
80  template <unsigned int N, typename First, typename... Others>
81  struct get_type<N, First, Others...> {
82  static_assert
83  (N <= sizeof...(Others), "No type in the specified position.");
84  using type = get_type_t<N-1, Others...>;
85  }; // get_type<>
86 
87  template <typename First, typename... Others>
88  struct get_type<0U, First, Others...> { using type = First; };
89 
90 
91  //--------------------------------------------------------------------------
92 
93  template <unsigned int NCopies>
94  struct TupleAppender {
95  template <typename Tuple, typename T>
96  static auto append(Tuple&& t, T const& value)
97  {
98  return TupleAppender<(NCopies - 1)>::append(
99  std::tuple_cat(std::forward<Tuple>(t), std::make_tuple(value)),
100  value
101  );
102  }
103  }; // TupleAppender<>
104 
105  template <>
106  struct TupleAppender<0> {
107  template <typename Tuple, typename T>
108  static auto append(Tuple&& t, T const&) { return t; }
109  }; // TupleAppender<0>
110 
111 
112  template <unsigned int NCopies, typename T, typename Tuple>
113  auto appendToTuple(Tuple&& t, T value)
114  { return TupleAppender<NCopies>::append(std::forward<Tuple>(t), value); }
115 
116 
117  //--------------------------------------------------------------------------
118  /// Returns the input tag of the product identified by the handle.
119  template <typename Handle>
120  art::InputTag tagFromHandle(Handle&& handle)
121  { return handle.provenance()->inputTag(); }
122 
123  /// Returns the input tag of the product identified by `id`.
124  template <typename Data, typename Event>
125  art::InputTag tagFromProductID
126  (art::ProductID const& id, Event const& /* event */)
127  {
128  //
129  // This implementation will need to be revisited with art 3.0 and in
130  // general with multithreading (note the singleton product list).
131  //
132  // Also note that this is not efficient for repeated queries, in which
133  // case the product list should be resorted into some map with product ID
134  // as key.
135  //
136  auto const& products = art::ProductMetaData::instance().productList();
137  for (auto const& productInfoPair: products) {
138  auto const& bd = productInfoPair.second; // branch description here
139  if (bd.productID() != id) continue;
140  return { bd.moduleLabel(), bd.productInstanceName(), bd.processName() };
141  } // for
142  throw art::Exception(art::errors::ProductNotFound)
143  << "Couldn't find data product with product ID " << id << "\n";
144  } // tagFromProductID()
145 
146 
147  //--------------------------------------------------------------------------
148  template <typename Value>
149  class SimpleDataIndex {
150  public:
151  using Value_t = Value;
152 
153  /// Constructor: indexes pointers to data in the specified collection.
154  template <typename BeginIter, typename EndIter>
155  SimpleDataIndex(BeginIter begin, EndIter end) { init(begin, end); }
156 
157  /// Constructor: indexes pointers to data in the specified collection.
158  template <typename Coll>
159  SimpleDataIndex(Coll& data)
160  { using std::begin; using std::end; init(begin(data), end(data)); }
161 
162  /// Returns a pointer to the matched data, nullptr if none.
163  std::size_t const* operator() (Value_t const& key) const
164  {
165  auto const e = index.cend();
166  auto const it = std::lower_bound(index.cbegin(), e, key, comparer());
167  return ((it == e) || (key < it->first))? nullptr: &(it->second);
168  }
169 
170 
171  private:
172  using IndexElement_t = std::pair<Value_t const*, std::size_t>;
173 
174  std::vector<IndexElement_t> index; ///< The index.
175 
176  template <typename BeginIter, typename EndIter>
177  void init(BeginIter begin, EndIter end)
178  {
179  index.reserve(std::distance(begin, end)); // hope it's fast
180  std::size_t i = 0;
181  for (auto it = begin; it != end; ++it, ++i)
182  index.emplace_back(&*it, i);
183  std::sort(index.begin(), index.end(), comparer());
184  }
185 
186  struct KeyComparer {
187  bool operator() (Value_t const& a, Value_t const& b) const
188  { return a < b; }
189  bool operator() (IndexElement_t const& a, Value_t const& b) const
190  { return *(a.first) < b; }
191  bool operator() (Value_t const& a, IndexElement_t const& b) const
192  { return a < *(b.first); }
193  bool operator() (IndexElement_t const& a, IndexElement_t const& b) const
194  { return *(a.first) < *(b.first); }
195  }; // struct KeyComparer
196 
197  static auto comparer() { return KeyComparer(); }
198 
199  }; // class SimpleDataIndex
200 
201 
202  template <typename Key, typename Value>
203  class DataIndex {
204  public:
205  using Key_t = Key;
206  using Value_t = Value;
207  using ValuePtr_t = std::add_pointer_t<Value_t>;
208 
209  /// Constructor: indexes pointers to data in the specified collection.
210  template <typename BeginIter, typename EndIter, typename KeyExtractor>
211  DataIndex(BeginIter begin, EndIter end, KeyExtractor&& getKey)
212  { init(begin, end, std::forward<KeyExtractor>(getKey)); }
213 
214  /// Constructor: indexes pointers to data in the specified collection.
215  template <typename Coll, typename KeyExtractor>
216  DataIndex(Coll& data, KeyExtractor&& getKey)
217  {
218  using std::begin;
219  using std::end;
220  init(begin(data), end(data), std::forward<KeyExtractor>(getKey));
221  }
222 
223  /// Returns a pointer to the matched data, nullptr if none.
224  ValuePtr_t operator() (Key_t const& key) const
225  {
226  auto const e = index.cend();
227  auto const it = std::lower_bound(index.cbegin(), e, key, comparer());
228  return ((it == e) || (key < it->first))? nullptr: it->second;
229  }
230 
231 
232  private:
233  using IndexElement_t = std::pair<Key_t, Value_t*>;
234 
235  std::vector<IndexElement_t> index; ///< The index.
236 
237  template <typename BeginIter, typename EndIter, typename KeyExtractor>
238  void init(BeginIter begin, EndIter end, KeyExtractor&& getKey)
239  {
240  index.reserve(std::distance(begin, end)); // hope it's fast
241  for (auto it = begin; it != end; ++it)
242  index.emplace_back(getKey(*it), &*it);
243  std::sort(index.begin(), index.end(), comparer());
244  }
245 
246  struct KeyComparer {
247  bool operator() (Key_t const& a, Key_t const& b) const
248  { return a < b; }
249  bool operator() (IndexElement_t const& a, Key_t const& b) const
250  { return a.first < b; }
251  bool operator() (Key_t const& a, IndexElement_t const& b) const
252  { return a < b.first; }
253  bool operator() (IndexElement_t const& a, IndexElement_t const& b) const
254  { return a.first < b.first; }
255  }; // struct KeyComparer
256 
257  static auto comparer() { return KeyComparer(); }
258 
259  }; // class DataIndex
260 
261 
262  /// Returns a new index for data using the keys extracted by getKey.
263  template <typename Coll, typename KeyExtractor>
264  auto makeIndex(Coll& data, KeyExtractor&& getKey)
265  {
266  using Value_t = typename Coll::value_type;
267  using Key_t = std::decay_t<decltype(getKey(std::declval<Value_t>()))>;
268  return
269  DataIndex<Key_t, Value_t>(data, std::forward<KeyExtractor>(getKey));
270  }
271 
272  /// Returns a new index for data in range using the data itself as key.
273  template <typename BeginIter, typename EndIter>
274  auto makeSimpleIndex(BeginIter begin, EndIter end)
275  {
276  using Value_t = std::decay_t<decltype(*begin)>;
277  return SimpleDataIndex<Value_t>(begin, end);
278  }
279 
280  /// Returns a new index for data using the data itself as key.
281  template <typename Coll>
282  auto makeSimpleIndex(Coll& data)
283  {
284  using Value_t = typename Coll::value_type;
285  return SimpleDataIndex<Value_t>(data);
286  }
287 
288 
289  //--------------------------------------------------------------------------
290  //--- finders implementation
291  //---
292 
293  namespace AssociationFinderBase {
294 
295  /// Information on a single source: its index in the source list, and
296  /// "key" (it can be any sortable object).
297  template <typename Key = std::size_t>
298  struct SourceIDinfo_t: public std::tuple<Key, std::size_t> {
299  using Base_t = std::tuple<Key, std::size_t>;
300  using Key_t = Key;
301 
302  /// Returns the key of the art pointer to this source element.
303  Key_t const& key() const { return std::get<0>(*this); }
304  /// Returns the index of this source element within the source list.
305  std::size_t position() const { return std::get<1>(*this); }
306 
307  using Base_t::Base_t; // inherit constructor
308 
309  /// Comparison functor: compares art pointer keys.
310  struct Comparer_t {
311  bool operator()
312  (SourceIDinfo_t const& a, SourceIDinfo_t const& b) const
313  { return a.key() < b.key(); }
314  bool operator() (SourceIDinfo_t const& a, Key_t const& b) const
315  { return a.key() < b; }
316  bool operator() (Key_t const& a, SourceIDinfo_t const& b) const
317  { return a < b.key(); }
318  }; // struct SourceIDinfo_t::Comparer_t
319 
320  static Comparer_t comparer() { return {}; }
321  }; // struct SourceIDinfo_t
322 
323  template <typename Key = std::size_t>
324  struct SourceVector_t: private std::vector<SourceIDinfo_t<Key>> {
325  using Base_t = std::vector<SourceIDinfo_t<Key>>;
326  using Info_t = typename Base_t::value_type;
327  using Key_t = typename Info_t::Key_t;
328 
329  using const_iterator = typename Base_t::const_iterator;
330 
331  using Base_t::begin;
332  using Base_t::end;
333  using Base_t::size;
334  using Base_t::empty;
335  using Base_t::reserve;
336 
337  struct MatchConstIterator_t: public std::pair<const_iterator, bool> {
338  using Base_t = std::pair<const_iterator, bool>;
339  using Base_t::Base_t; // inherit all constructors
340  auto const& operator->() const { return std::get<0>(*this); }
341  operator bool() const { return std::get<1>(*this); }
342  bool operator!() const { return !this->operator bool(); }
343  }; // struct MatchConstIterator_t
344 
345  /// Default constructor: starts empty.
346  SourceVector_t() = default;
347 
348  /// Constructor: from the elements between the specified iterators.
349  template <typename BeginIter, typename EndIter>
350  SourceVector_t(BeginIter b, EndIter e) { assign(b, e); }
351 
352  /// Sorts the vector according to keys.
353  void sort()
354  { std::sort(Base_t::begin(), Base_t::end(), Info_t::comparer()); }
355 
356  /// Constructs a new element at the end of the vector.
357  void emplace_back(Key_t const& key, std::size_t pos)
358  { Base_t::emplace_back(key, pos); }
359 
360  /// Returns iterator to element with key, if it exists.
361  /// Requires the vector to be sort()ed.
362  /// If key is not present it may return end() or the wrong element.
363  auto find(Key_t const& key) const
364  {
365  auto const e = end();
366  auto it = std::lower_bound(begin(), e, key, Info_t::comparer());
367  return MatchConstIterator_t(it, (it != e) && (it->key() == key));
368  }
369 
370  /// Resets the content to map a container delimited by the iterators.
371  template <typename BeginIter, typename EndIter>
372  void assign(BeginIter b, EndIter e)
373  {
374  Base_t::clear();
375  std::size_t i = 0;
376  for (auto it = b; it != e; ++it, ++i) emplace_back(it->key(), i);
377  sort();
378  }
379 
380  }; // struct SourceVector_t
381 
382 
383  /// Returns a tuple of NTags elements, including all specified tags and
384  /// then copies of the default one to make it N.
385  template <unsigned int NTags, typename DefaultTag, typename... InputTags>
386  auto makeTagsTuple(DefaultTag defTag, InputTags... tags) {
387  return appendToTuple<(NTags - sizeof...(InputTags))>
388  (std::make_tuple(tags...), defTag);
389  } // makeTagsTuple()
390 
391 
392  /// A named pair of a key and all its connections.
393  /// Comparisons are based solely on key.
394  template <typename A, typename B>
395  struct Connections: private std::pair<A, std::vector<B>> {
396  using Base_t = std::pair<A, std::vector<B>>;
397  using Key_t = A;
398  using ConnectedItem_t = B;
399  using Connections_t = Connections<A, B>;
400  using Connected_t = std::tuple_element_t<1, Base_t>;
401  using Base_t::Base_t; // import pair constructors
402 
403  /// Additional constructor: key with no connected items.
404  Connections(Key_t const& key): Base_t(key, {}) {}
405 
406  Key_t const& key() const { return std::get<0>(*this); }
407  Connected_t const& connectedTo() const { return std::get<1>(*this); }
408  Connected_t& connectedTo() { return std::get<1>(*this); }
409 
410  struct Comparer_t {
411  bool operator() (Key_t const& a, Key_t const& b) const
412  { return a < b; }
413  bool operator() (Key_t const& a, Connections_t const& b) const
414  { return a < b.key(); }
415  bool operator() (Connections_t const& a, Key_t const& b) const
416  { return a.key() < b; }
417  bool operator() (Connections_t const& a, Connections_t const& b) const
418  { return a.key() < b.key(); }
419  }; // Comparer_t
420 
421  static Comparer_t comparer() { return {}; }
422 
423  /// Compares the connection lists by key only.
424  bool operator< (Connections_t const& b) const
425  { return comparer()(*this, b); }
426 
427  }; // struct Connections<>
428 
429 
430  struct KeysFromTag {};
431  struct KeysFromIndexTag {};
432  struct KeysFromOtherListTag {};
433  constexpr KeysFromTag KeysFrom;
434  constexpr KeysFromIndexTag KeysFromIndex;
435  constexpr KeysFromOtherListTag KeysFromOtherList;
436 
437  /// A connections list.
438  template <typename Key, typename Connected>
439  class ConnectionList: private std::vector<Connections<Key, Connected>> {
440  using Connections_t = Connections<Key, Connected>;
441  using Data_t = std::vector<Connections_t>;
442 
443  Data_t& data() { return *this; }
444  Data_t const& data() const { return *this; }
445 
446  public:
447  /// Type of an item connected to the key.
448  using ConnectedItem_t = typename Connections_t::ConnectedItem_t;
449  /// Type of list of connected items.
450  using Connected_t = typename Connections_t::Connected_t;
451 
452  // import types from the base class
453  using value_type = typename Data_t::value_type;
454 
455 
456  /// Initializes an empty connection list.
457  ConnectionList() = default;
458 
459  /// Initializes with no connection for each of the keys from b to e.
460  template <typename BeginIter, typename EndIter>
461  ConnectionList(KeysFromTag, BeginIter b, EndIter e)
462  {
463  data().reserve(std::distance(b, e)); // hope this is fast
464  std::transform(b, e, std::back_inserter(data()),
465  [](auto const& key) { return Connections_t(key, {}); }
466  );
467  }
468 
469  /// Initializes with no connection for each of the keys from a list.
470  template <typename Other>
471  ConnectionList
472  (KeysFromOtherListTag, ConnectionList<Key, Other> const& source)
473  {
474  data().reserve(source.size()); // hope this is fast
475  using std::cbegin;
476  using std::cend;
477  std::transform(
478  cbegin(source), cend(source), std::back_inserter(data()),
479  [](auto const& conn) { return Connections_t(conn.key(), {}); }
480  );
481  }
482 
483  /// Initializes with no connection for each of the generated keys.
484  template <typename KeyMaker>
485  ConnectionList(KeysFromIndexTag, std::size_t n, KeyMaker keyMaker)
486  {
487  data().reserve(n);
488  for (std::size_t i = 0; i < n; ++i)
489  data().emplace_back(keyMaker(i));
490  }
491 
492  // import methods from the base class (not the constructors)
493  using Data_t::size;
494  using Data_t::operator[];
495  using Data_t::clear;
496  using Data_t::cbegin;
497  using Data_t::begin;
498  using Data_t::cend;
499  using Data_t::end;
500 
501  /// @{
502  /// @name Special methods
503 
504  void addConnectionAt(std::size_t index, ConnectedItem_t const& item)
505  { data()[index].connectedTo().push_back(item); }
506  void addConnectionAt(std::size_t index, ConnectedItem_t&& item)
507  { data()[index].connectedTo().push_back(std::move(item)); }
508 
509  /// Returns all connected objects on a vector, one element per key.
510  /// As a R-value reference method, the current content is lost
511  std::vector<Connected_t> values() &&
512  {
513  std::vector<Connected_t> result;
514  result.reserve(data().size());
515  std::transform(
516  data().begin(), data().end(), std::back_inserter(result),
517  [](auto& conn){ return std::move(conn.connectedTo()); }
518  );
519  return result;
520  } // values()
521 
522  /// @}
523 
524  }; // struct ConnectionList<>
525 
526 
527  /// Class handling a connection list.
528  template <typename Source, typename Target>
529  class PtrConnectionManager {
530 
531  public:
532  using Manager_t = PtrConnectionManager<Source, Target>;
533  using Source_t = Source;
534  using Target_t = Target;
535  using SourcePtr_t = art::Ptr<Source_t>;
536  using TargetPtr_t = art::Ptr<Target_t>;
537  using ConnectionList_t = ConnectionList<SourcePtr_t, TargetPtr_t>;
538 
539  /// Constructor: steals the connections from the argument.
540  PtrConnectionManager(ConnectionList_t&& data): data(std::move(data)) {}
541 
542 
543  /// Returns all connected objects as a single connection of pointers.
544  std::vector<TargetPtr_t> allConnected() const
545  {
546 
547  // this might as well be implemented with ranges
548 
549  std::vector<TargetPtr_t> result;
550 
551  using std::cbegin;
552  using std::cend;
553 
554  for (std::size_t i = 0; i < data.size(); ++i) {
555  auto const& connected = data[i].connectedTo();
556  result.insert(result.end(), cbegin(connected), cend(connected));
557  } // for
558  return result;
559 
560  } // allConnected()
561 
562  /// Returns a new connection list connecting each of our sources to
563  /// FurtherPtr_t objects, using shared TargetPtr_t to map connections.
564  template <typename FurtherPtr_t>
565  ConnectionList<SourcePtr_t, FurtherPtr_t> join
566  (ConnectionList<TargetPtr_t, FurtherPtr_t>&& other) const
567  {
568  // internally, we'll call the current data A -> B, other data B -> C,
569  // and we are after A -> C
570  using APtr_t = SourcePtr_t;
571  // using BPtr_t = TargetPtr_t;
572  using CPtr_t = FurtherPtr_t;
573 
574  auto const& AB = data;
575  auto&& BC = other;
576  ConnectionList<APtr_t, CPtr_t> AC(KeysFromOtherList, data);
577 
578  // the match object returns the iterator to the list connected to the
579  // specified key; it does not own the data structure;
580  auto const match
581  = makeIndex(BC, [](auto const& elem){ return elem.key(); });
582 
583  for (std::size_t iA = 0; iA < AB.size(); iA++) {
584  auto const& connAB = AB[iA]; // connections for this A
585  auto const& BsForA = connAB.connectedTo();
586 
587  auto& CsForA = AC[iA].connectedTo(); // connected list to fill (empty)
588 
589  // for each B connected to this A:
590  for (auto const& key: BsForA) {
591 
592  auto iConnBC = match(key);
593  if (!iConnBC) continue; // no C's associated to the matching B
594 
595  // matched! move or copy the while list of Cs
596  auto& CsForB = iConnBC->connectedTo(); // (this list)
597  if (CsForA.empty()) CsForA = std::move(CsForB); // move
598  else CsForA.insert(CsForA.end(), cbegin(CsForB), cend(CsForB));
599 
600  } // for connected Bs
601  } // for iA
602 
603  return AC;
604  } // join()
605 
606  private:
607  ConnectionList_t data; ///< All connection information.
608 
609  }; // PtrConnectionManager<>
610 
611  /// Helper function to created the right type of manager from a temporary.
612  template <typename A, typename B>
613  auto makeConnectionManager(ConnectionList<A, B>&& data)
614  {
615  return PtrConnectionManager
616  <typename A::value_type, typename B::value_type>(std::move(data));
617  }
618 
619  } // namespace AssociationFinderBase
620 
621 
622  template <typename Target>
623  struct AssociationFinder {
624 
625  using Target_t = Target;
626  using TargetPtr_t = art::Ptr<Target_t>;
627 
628  /// Type of result of finders: one list of associated targets per source.
629  template <typename Source>
630  using Result_t
631  = AssociationFinderBase::ConnectionList<art::Ptr<Source>, TargetPtr_t>;
632 
633  template <typename Source>
634  using Assns_t = art::Assns<Source, Target>;
635 
636  template <typename Assns>
637  using AssnKey_t = typename Assns::left_t;
638 
639  template <typename Assns>
640  using AssnKeyPtr_t = art::Ptr<AssnKey_t<Assns>>;
641 
642  template <typename Assns>
643  using ResultFromAssns_t = Result_t<AssnKey_t<Assns>>;
644 
645  template <typename Handle>
646  using ValueFromHandle_t = typename Handle::element_type::value_type;
647 
648  template <typename Handle>
649  using AssnsFromHandle_t = Assns_t<ValueFromHandle_t<Handle>>;
650 
651  template <typename Handle>
652  using ResultFromHandle_t = Result_t<art::Ptr<ValueFromHandle_t<Handle>>>;
653 
654  /// Helper finding a associations from a single complete data product.
655  template <typename Assns>
656  static ResultFromAssns_t<Assns> findFromDataProduct(
657  art::ProductID const& sourceID, std::size_t nSources,
658  Assns const& assns
659  )
660  {
661  using SourcePtr_t = AssnKeyPtr_t<Assns>;
662 
663  using namespace AssociationFinderBase;
664 
665  // as many lists in result as sources, keys created from source ID
666  ResultFromAssns_t<Assns> result(
667  KeysFromIndex, nSources,
668  [id=sourceID](std::size_t i){ return SourcePtr_t(id, i, nullptr); }
669  );
670 
671  // get the associations, following the content of the assns data product
672  for (decltype(auto) assn: assns) {
673  auto const& sourcePtr = assn.first;
674 
675  // does this association contain a pointer with an ID different than
676  // the one we are looking for? (it may mean our assumption that the
677  // same module produced source and its association is wrong)
678  if (sourcePtr.id() != sourceID) continue;
679 
680  // we follow the assumption that the data product is complete with
681  // nSources elements, therefore no pointer can exist with a larger
682  // key:
683  assert(sourcePtr.key() < nSources);
684 
685  // push target pointer into the result of the matched source
686  result.addConnectionAt(sourcePtr.key(), assn.second);
687 
688  } // for
689 
690  return result;
691  } // findFromDataProduct()
692 
693 
694  /// Helper finding a associations from a single list.
695  template <
696  typename Source, typename IndexBegin, typename IndexEnd, typename Event
697  >
698  static Result_t<Source> findSingle(
699  art::ProductID const& sourceID,
700  IndexBegin sbegin, IndexEnd send,
701  Assns_t<Source> const& assns
702  )
703  {
704  // type of the source object (hidden in PtrColl)
705  using Source_t = Source;
706  using SourcePtr_t = art::Ptr<Source_t>;
707  using namespace AssociationFinderBase;
708 
709  std::size_t const nSources = std::distance(sbegin, send);
710  Result_t<Source_t> result(nSources);
711 
712  // These are the source "pointers" we still have to find; they are all
713  // on the same product ID. We keep track of the original position too
714  // (unordered map may or may not be better...);
715  // given that these are all from the same product ID, the product ID is
716  // not saved. SourceVector_t provides log(N) lookup.
717  SourceVector_t<std::size_t> sourceInfos(sbegin, send);
718 
719  // get the association, following the content of the assns data product
720  for (decltype(auto) assn: assns) {
721  SourcePtr_t const& sourcePtr = assn.first;
722 
723  // does this association contain a pointer with an ID different than
724  // the one we are looking for? (it may mean our assumption that the
725  // same module produced source and its association is wrong)
726  if (sourcePtr.id() != sourceID) continue;
727 
728  // is this pointer interesting?
729  auto iSrcInfo = sourceInfos.find(sourcePtr.key());
730  if (!iSrcInfo) continue; // it's not it
731 
732  // match! push target pointer into the result of the matched source
733  result[iSrcInfo->position()].connectedTo().push_back(assn.second);
734 
735  } // for
736 
737  return result;
738  } // findSingle()
739 
740 
741  /// Helper finding a single degree of associations from the tag of each
742  /// source art pointer.
743  template <typename PtrCollBegin, typename PtrCollEnd, typename Event>
744  static auto findWithRange(
745  PtrCollBegin sbegin, PtrCollEnd send, Event const& event,
746  art::InputTag const& tag
747  ) {
748  /*
749  * The strategy of this implementation is:
750  *
751  * 1. collect all source art pointers, sorted by key for faster lookup
752  * 2. parse all the associated pairs
753  * 1. if the source pointer of a pair is in the list of
754  * interesting source pointers, push the target pointer of the
755  * pair into the results for this source
756  *
757  */
758 
759  using SourcePtr_t = std::decay_t<decltype(*sbegin)>;
760  static_assert(is_art_ptr<SourcePtr_t>(),
761  "Collection for AssociationFinder (FindManyInChainP) is not art pointers!"
762  );
763 
764  // type of the source object (hidden in PtrColl)
765  using Source_t = typename SourcePtr_t::value_type;
766  using namespace AssociationFinderBase;
767 
768  Result_t<Source_t> result(KeysFrom, sbegin, send);
769  // std::size_t const nSources = result.size();
770 
771  // use this index for fast lookup of the sources
772  auto match = makeSimpleIndex(sbegin, send);
773 
774  // fetch the association data product
775  auto const& assns
776  = *(event.template getValidHandle<Assns_t<Source_t>>(tag));
777 
778  // get the association, following the content of the assns data product
779  for (decltype(auto) assn: assns) {
780  SourcePtr_t const& sourcePtr = assn.first;
781 
782  // is this pointer interesting?
783  auto iPos = match(sourcePtr);
784  if (!iPos) continue; // it's not it
785 
786  // match! push target pointer into the result of the matched source
787  result[*iPos].connectedTo().push_back(assn.second);
788 
789  } // for
790 
791  return result;
792  } // findWithRange()
793 
794 
795  /// Helper finding a single degree of associations from the tag of each
796  /// source art pointer.
797  template <typename PtrCollBegin, typename PtrCollEnd, typename Event>
798  static auto findWithRange
799  (PtrCollBegin sbegin, PtrCollEnd send, Event const& event)
800  {
801  /*
802  * The strategy of this implementation is:
803  *
804  * 1. collect all the source art pointers, grouped by product ID
805  * (and sorted by key for faster lookup)
806  * 2. for each interesting product ID:
807  * 1. fetch the association collection; this is assumed to have been
808  * created with the same input tag as the source product
809  * 2. parse all the associated pairs
810  * 1. if the source pointer of a pair is in the list of
811  * interesting source pointers, push the target pointer of the
812  * pair into the results for this source
813  *
814  * The maximum complexity of this algorithm is N log(M), where M is no
815  * larger than the maximum number of source pointers with a single
816  * product ID and N is the number of associations in each association
817  * data product.
818  *
819  */
820 
821  using SourcePtr_t = std::decay_t<decltype(*sbegin)>;
822  static_assert(is_art_ptr<SourcePtr_t>(),
823  "Collection for AssociationFinder (FindManyInChainP) is not art pointers!"
824  );
825 
826  // type of the source object (hidden in PtrColl)
827  using Source_t = typename SourcePtr_t::value_type;
828  using namespace AssociationFinderBase;
829 
830  Result_t<Source_t> result(KeysFrom, sbegin, send);
831 
832  // These are the source pointers we still have to find,
833  // organised by product ID; we keep track of the original position too
834  // (unordered map may or may not be better...);
835  // given that these are all from the same product ID, the product ID is
836  // not saved.
837  // Also, for fast lookup the lists are sorted by pointer key.
838  std::map<art::ProductID, SourceVector_t<std::size_t>>
839  sourcesLeft;
840  std::size_t iSource = 0;
841  for (auto it = sbegin; it != send; ++it, ++iSource)
842  sourcesLeft[it->id()].emplace_back(it->key(), iSource);
843  for (auto& sourcesWithinID: sourcesLeft)
844  sourcesWithinID.second.sort();
845 
846  // look for all sources in each product ID
847  for (auto const& sourcesWithinID: sourcesLeft) {
848 
849  art::ProductID sourceID = sourcesWithinID.first;
850  auto const& sourceInfos = sourcesWithinID.second;
851 
852  // need to get the association between source and target,
853  // as produced by the same producer that produced the source itself
854  art::InputTag tag
855  = tagFromProductID<std::vector<Source_t>>(sourceID, event);
856 
857  // fetch the association data product
858  auto const& assns
859  = *(event.template getValidHandle<Assns_t<Source_t>>(tag));
860 
861  // get the association, following the content of the assns data product
862  for (decltype(auto) assn: assns) {
863  SourcePtr_t const& sourcePtr = assn.first;
864 
865  // does this association contain a pointer with an ID different than
866  // the one we are looking for? (it may mean our assumption that the
867  // same module produced source and its association is wrong)
868  if (sourcePtr.id() != sourceID) continue;
869 
870  // is this pointer interesting?
871  auto iSrcInfo = sourceInfos.find(sourcePtr.key());
872  if (!iSrcInfo) continue; // it's not it
873 
874  // match! push target pointer into the result of the matched source
875  result[iSrcInfo->position()].connectedTo().push_back(assn.second);
876 
877  } // for
878 
879  } // for sourcesWithinID
880 
881  return result;
882  } // findWithRange()
883 
884 
885  }; // class AssociationFinder
886 
887 
888 
889  /// Helper finding a single degree of associations from a specified tag.
890  template <
891  typename Target, typename Handle, typename Event,
892  typename = std::enable_if_t<is_handle_v<Handle>>
893  >
894  auto findAssociations
895  (Handle&& handle, Event const& event, art::InputTag const& assnsTag)
896  {
897  using Finder = AssociationFinder<Target>;
898  using Assns_t
899  = typename Finder::template AssnsFromHandle_t<std::decay_t<Handle>>;
900 
901  auto const& assns = *(event.template getValidHandle<Assns_t>(assnsTag));
902 
903  // FIXME: this implementation is NOT canvas-compatible (Handle::id())
904  return Finder::findFromDataProduct(handle.id(), handle->size(), assns);
905  } // findAssociations(Handle, tag)
906 
907 
908  /// Helper finding a single degree of associations from any tag.
909  template<
910  typename Target, typename Handle, typename Event,
911  typename = std::enable_if_t<is_handle_v<Handle>>
912  >
913  auto findAssociations(Handle handle, Event const& event, lar::SameAsDataTag)
914  {
915  /*
916  * By definition, here all the interesting sources share the same product
917  * ID, which simplifies the implementation.
918  */
919 
920  // need to get the association between source and target,
921  // as produced by the same producer that produced the source itself
922  // FIXME: the implementation of tagFromHandle is NOT canvas-compatible
923  art::InputTag tag = tagFromHandle(handle);
924 
925  return findAssociations<Target>
926  (std::forward<Handle>(handle), event, tag);
927  } // findAssociations(Handle, same tag)
928 
929 
930  template<
931  typename Target, typename PtrColl, typename Event,
932  typename = std::enable_if_t<!is_handle_v<PtrColl>>
933  >
934  auto findAssociations
935  (PtrColl const& coll, Event const& event, lar::SameAsDataTag)
936  {
937  using std::cbegin;
938  using std::cend;
939  return AssociationFinder<Target>::findWithRange
940  (cbegin(coll), cend(coll), event);
941  }
942 
943 
944  /// Helper finding a single degree of associations from a specified tag.
945  template <
946  typename Target, typename PtrColl, typename Event,
947  typename = std::enable_if_t<!is_handle_v<PtrColl>>
948  >
949  auto findAssociations
950  (PtrColl const& coll, Event const& event, art::InputTag const& tag)
951  {
952  using std::cbegin;
953  using std::cend;
954  return AssociationFinder<Target>::findWithRange
955  (cbegin(coll), cend(coll), event, tag);
956  } // findAssociations(Handle, tag)
957 
958 
959  //--------------------------------------------------------------------------
960 
961  /**
962  * @tparam Target type to be associated to the source data
963  * @tparam Tier tier: 0 -> get target, 1 -> get leftmost intermediate, ...
964  * @tparam Intermediate intermediate types, leftmost is closest to `Target`
965  */
966  template <typename Target, unsigned int Tier, typename... Intermediate>
967  struct FindManyInChainPimpl {
968 
969  /// Total number of tiers (original source + all intermediates).
970  static constexpr unsigned int Tiers = sizeof...(Intermediate) + 1;
971 
972  static_assert
973  (Tier < Tiers, "Invalid tier: there is no type in position #N");
974 
975  /// Type used for the association to source.
976  using Intermediate_t = get_type_t<(Tier - 1), Intermediate...>;
977 
978  template <typename Source, typename Event, typename InputTags>
979  static auto find
980  (Source&& source, Event const& event, InputTags const& tags)
981  {
982  static_assert(std::tuple_size<InputTags>() == Tiers,
983  "Wrong number of input tags specified");
984 
985  // number of the intermediate being addressed
986  constexpr auto nTag = Tiers - 1 - Tier;
987 
988  // find the associations between source and the next tier:
989  // Source <==> Intermediate;
990  // result is a ConnectionList object, that we store in a "manager"
991  auto iq = makeConnectionManager(
992  findAssociations<Intermediate_t>
993  (source, event, std::get<nTag>(tags))
994  );
995 
996  // collapse the result for input into the next tier;
997  // this will result in a long sequence of art pointers
998  auto const intermediateData = iq.allConnected();
999 
1000  // process the next tier: Intermediate <==> Target;
1001  // this is also a ConnectionList
1002  auto oq
1003  = FindManyInChainPimpl<Target, (Tier - 1U), Intermediate...>::find
1004  (intermediateData, event, tags);
1005 
1006  // combine the result into jet another connection list:
1007  // Source <==> Intermediate (+) Intermediate <==> Target
1008  return iq.join(std::move(oq)); // AssociationFinderBase::combine(iq, oq);
1009 
1010  } // find()
1011 
1012 
1013  }; // FindManyInChainPimpl<>
1014 
1015 
1016  // Specialization for tier 0
1017  // (target association to first intermediate in list)
1018  template <typename Target, typename... Intermediate>
1019  struct FindManyInChainPimpl<Target, 0U, Intermediate...> {
1020 
1021  /// Total number of tiers (original source + all intermediates).
1022  static constexpr unsigned int Tiers = sizeof...(Intermediate) + 1;
1023 
1024  template <typename Source, typename Event, typename InputTags>
1025  static auto find
1026  (Source&& source, Event const& event, InputTags const& tags)
1027  {
1028  static_assert(std::tuple_size<InputTags>() == Tiers,
1029  "Wrong number of input tags specified");
1030  // find the associations between the last intermediate and the target
1031  // (using the last tag)
1032  return findAssociations<Target>
1033  (source, event, std::get<Tiers - 1>(tags));
1034  } // find()
1035 
1036  }; // FindManyInChainPimpl
1037 
1038 
1039 
1040  //--------------------------------------------------------------------------
1041 
1042  } // namespace details
1043 
1044 } // namespace lar
1045 
1046 //------------------------------------------------------------------------------
1047 //
1048 // Implementation note:
1049 // - FindManyInChainP is the front-end utility with minimal template arguments;
1050 // it sets up and runs FindManyInChainPimpl...
1051 // - FindManyInChainPimpl is the implementation of recursion where associations
1052 // are extracted for the results of the previous level ("tier") of
1053 // associated objects; it juggles with tags and parameters, and relies on
1054 // findAssociations()...
1055 // - findAssociations() implements the extraction of a single association tier,
1056 // from a (flattened) collection of input objects
1057 //
1058 //------------------------------------------------------------------------------
1059 template <typename Target, typename... Intermediate>
1060 template <typename Source, typename Event, typename... InputTags>
1061 auto lar::FindManyInChainP<Target, Intermediate...>::find
1062  (Source&& source, Event const& event, InputTags... tags)
1063  -> std::vector<TargetPtrCollection_t>
1064 {
1065  constexpr auto Tiers = sizeof...(Intermediate) + 1U;
1066 
1067  // create a parameter pack with one tag per association
1068  auto const allTags
1069  = details::AssociationFinderBase::makeTagsTuple<Tiers>
1070  (SameAsData, std::forward<InputTags>(tags)...);
1071 
1072  return details::FindManyInChainPimpl<Target, (Tiers - 1), Intermediate...>
1073  ::find(source, event, allTags).values();
1074 
1075 } // lar::FindManyInChainP<Target, Intermediate...>::FindManyInChainP()
1076 
1077 
1078 //------------------------------------------------------------------------------
1079 template <typename Target, typename... Intermediate>
1080 std::size_t
1081 lar::FindManyInChainP<Target, Intermediate...>::size() const noexcept
1082  { return results.size(); }
1083 
1084 
1085 //------------------------------------------------------------------------------
1086 template <typename Target, typename... Intermediate>
1087 typename lar::FindManyInChainP<Target, Intermediate...>::TargetPtrCollection_t const&
1088 lar::FindManyInChainP<Target, Intermediate...>::at(std::size_t i) const
1089  { return results.at(i); }
1090 
1091 
1092 //------------------------------------------------------------------------------
1093 
1094 
1095 #endif // LARDATA_UTILITIES_FINDMANYINCHAINP_TCC