FastFiller

FastFiller

IDComparator

IndexRangeAssociation

LazyFiller

MultiAssociation

Macros

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
#ifndef DataFormats_Common_MultiAssociation_h
#define DataFormats_Common_MultiAssociation_h
/* \class MultiAssociation
 *
 * \author Giovanni Petrucciani, SNS Pisa and CERN PH-CMG
 * 
 * One-to-Many variant of edm::Association<C> / edm::ValueMap<T>, 
 *
 * Given a key, it will return a range of iterators within a collection (fast access), or collection by value (slow access mode)
 *
 * The range of iterators is handled through boost::sub_range, so it should feel like a collection:
 *   1) it has a '::const_iterator', a 'begin()' and an 'end()'
 *   2) it has a 'size()' and an 'empty()'
 *   3) it has a 'front()', 'back()'
 *   4) it works as an array  (i.e. you can use range[index] to pick an element)
 *   5) if your MultiAssociation is not const, you can modify the values associated to each key
 *      (but you can't push_back new values for a given key)
 * ( details at http://www.boost.org/doc/libs/1_37_0/libs/range/doc/utility_class.html#sub_range )
 *
 * The collection can be a RefVector<C> (to work a la edm::Association<C>), a std::vector<T> (to work a la ValueMap<T>), a PtrVector<T>...
 * The collection must be compatible with sub_range and support a few other features. Usually you need:
 *  - that it has a default constructor
 *  - that it has a const_iterator, and "begin() const" returns such const_iterator. 
 *  - that it has an iterator, and "begin()" returns such iterator (note: it doesn't have to be writable, it can be const as well) 
 *  - that it has a swap method
 *  - that 'begin() + offset' is legal (and fast, otherwise this thing is will be awfully slow)
 *  - that you can push_back on a C the * of a C::const_iterator. Namely this should be legal
 *    <code>
 *       C::const_iterator it = ..something..
 *       C someOtherC = ...
 *       someOtherC.push_back(*it);
 *    </code>
 *
 * It can be filled through a FastFiller or a LazyFiller. 
 * FastFiller is probably faster, but has many constraints:
 * - you must fill only one product id at time
 * - you must fill items in strict key order
 * - there is no way to abort a filling operation
 * Try LazyFiller first, unless you're doing some sort of batch task that satisfies the FastFiller requirements
 *
 * It stores:
 *  - for each collection of keys: a product id and an offset  
 *  - for each key (even those not mapped to any value): one offset
 *  - for each value: one value
 * With respect to ValueMap / Association, there is one extra int32 for each key (but we don't store null values)
 *
 * Its backbone is given by edm::helper::IndexRangeAssociation, that maps keys to ranges, and is not templated.
 *
 */

#include <vector>
#include <map>
#include <memory>
#include <boost/range.hpp>
#include "DataFormats/Common/interface/CMS_CLASS_VERSION.h"
#include "DataFormats/Provenance/interface/ProductID.h"

namespace edm {
  namespace helper {
    /// Base class to map to items one a range within a target collection.
    /// All items in a target collection must be mapped to one and only one key.
    /// The target collection will normally be a RefVector (also PtrVector and std::vector could work)
    /// To be used by MultiAssociation. Any other usage beyond MultiAssociation is not supported.
    /** This class holds:
           - ref_offsets_: A non decreasing list of offsets in the target collection, with one item for each key *plus one end item*.
                           A range is given by a consecutive pair of offsets.
                           While filling the map, some items can be '-1', but not the last one.
           - id_offsets_ : A list of pairs, mapping a product id to its associated range in ref_offsets_
          This class can be filled through a FastFiller, that requires to fill keys and offsets in strictly increasing order, and without gaps. 
          Only one FastFiller can exist at time, and each FastFiller fills only for a given key collection. */
    class IndexRangeAssociation {
    public:
      typedef std::pair<unsigned int, unsigned int> range;

      IndexRangeAssociation() : isFilling_(false) {}

      /// Get a range of indices for this key. RefKey can be edm::Ref, edm::Ptr, edm::RefToBase
      /// And end value of -1 means 'until the end of the other collection, AFAICT'
      template <typename RefKey>
      range operator[](const RefKey &r) const {
        return get(r.id(), r.key());
      }

      /// Get a range of indices for this product id and key (non-template version)
      /// And end value of -1 means 'until the end of the other collection, AFAICT'
      range get(const edm::ProductID &id, unsigned int t) const;

      /// True if this IndexRangeAssociation has info for this product id
      bool contains(ProductID id) const;

      /// Size of this collection (number of keys)
      unsigned int size() const { return ref_offsets_.empty() ? 0 : ref_offsets_.size() - 1; }

      /// True if it's empty (no keys)
      bool empty() const { return ref_offsets_.empty(); }

      /// FastFiller for the IndexRangeAssociation:
      /// It requires to fill items in strict key order.
      /// You can have a single FastFiller for a given map at time
      /// You can't access the map for this collection while filling it
      class FastFiller {
      public:
        FastFiller(const FastFiller &) = delete;
        FastFiller &operator=(const FastFiller &) = delete;

        /// Make a filler for a collection with a given product id and size
        FastFiller(IndexRangeAssociation &assoc, ProductID id, unsigned int size);

        /// When the FastFiller goes out of scope, it unlocks the map so you can make a new one
        ~FastFiller();

        /// Sets the starting offset for this key.
        template <typename RefKey>
        void insert(const RefKey &r, unsigned int startingOffset, unsigned int size) {
          insert(r.id(), r.key(), startingOffset, size);
        }

        /// Sets the starting offset for this key (non-templated variant)
        void insert(edm::ProductID id, unsigned int key, unsigned int startingOffset, unsigned int size);

      private:
        IndexRangeAssociation &assoc_;
        const ProductID id_;
        unsigned int start_, end_;  // indices into assoc_.ref_offsets_ (end_ points to the end marker, so it's valid)
        /// last key used to fill (to check that the new key must be strictly greater than lastKey_)
        int lastKey_;
      };  // FastFiller
      friend class FastFiller;

      void swap(IndexRangeAssociation &other);

      static void throwUnexpectedProductID(ProductID found, ProductID expected, const char *where);

    private:
      typedef std::pair<edm::ProductID, unsigned int> id_off_pair;
      typedef std::vector<id_off_pair> id_offset_vector;  // sorted by product id
      typedef std::vector<int> offset_vector;
      id_offset_vector id_offsets_;
      offset_vector ref_offsets_;

      bool isFilling_;  // transient, to check no two fillers exist at the same time
      struct IDComparator {
        bool operator()(const id_off_pair &p, const edm::ProductID &id) const { return p.first < id; }
      };  // IDComparator
    };

    // Free swap function
    inline void swap(IndexRangeAssociation &lhs, IndexRangeAssociation &rhs) { lhs.swap(rhs); }

  }  // namespace helper

  template <typename C>
  class MultiAssociation {
  public:
    typedef C Collection;
    typedef boost::sub_range<const Collection> const_range;
    typedef boost::sub_range<Collection> range;

    MultiAssociation() {}

    /// Get a range of values for this key (fast)
    template <typename RefKey>
    const_range operator[](const RefKey &r) const {
      return get(r.id(), r.key());
    }
    // ---- and the non-const sister
    /// Get a range of values for this key (fast)
    template <typename RefKey>
    range operator[](const RefKey &r) {
      return get(r.id(), r.key());
    }

    /// Get a copy of the values for this key (slow!)
    template <typename RefKey>
    Collection getValues(const RefKey &r) const {
      return getValues(r.id(), r.key());
    }

    /// True if there are keys from this product id
    bool contains(const edm::ProductID &id) const { return indices_.contains(id); }

    /// Get a range of values for this product id and index (fast)
    const_range get(const edm::ProductID &id, unsigned int t) const;
    // ---- and the non-const sister
    /// Get a range of values for this product id and index (fast)
    range get(const edm::ProductID &id, unsigned int t);

    /// Get a copy of the values for this product id and index (slow!)
    Collection getValues(const edm::ProductID &id, unsigned int t) const;

    void swap(MultiAssociation &other) {
      indices_.swap(other.indices_);
      data_.swap(other.data_);
    }

    /// Returns the number of values
    unsigned int dataSize() const { return data_.size(); }

    /// Returns the number of keys
    unsigned int size() const { return indices_.size(); }

    /// Returns true if there are no keys
    bool empty() const { return indices_.empty(); }

    /// FastFiller for the MultiAssociation.
    /// It is fast, but it requires to fill items in strict key order.
    /// You can have a single FastFiller for a given map at time
    /// You can't access the map for this collection while filling it
    class FastFiller {
    public:
      template <typename HandleType>
      FastFiller(MultiAssociation &assoc, const HandleType &handle)
          : assoc_(assoc), indexFiller_(new IndexFiller(assoc_.indices_, handle.id(), handle->size())) {}

      FastFiller(MultiAssociation &assoc, edm::ProductID id, unsigned int size)
          : assoc_(assoc), indexFiller_(new IndexFiller(assoc_.indices_, id, size)) {}

      ~FastFiller() {}

      /// Sets the Collection values associated to this key, making copies of those in refs
      template <typename KeyRef>
      void setValues(const KeyRef &k, const Collection &refs) {
        setValues(k.id(), k.key(), refs);
      }

      /// Sets the Collection values associated to this key, making copies of those in refs
      void setValues(const edm::ProductID &id, unsigned int key, const Collection &refs);

    private:
      MultiAssociation &assoc_;
      typedef edm::helper::IndexRangeAssociation::FastFiller IndexFiller;
      std::shared_ptr<IndexFiller> indexFiller_;

    };  // FastFiller
    friend class FastFiller;

    template <typename HandleType>
    FastFiller fastFiller(const HandleType &handle) {
      return FastFiller(*this, handle);
    }

    /// LazyFiller for the MultiAssociation.
    /// It is slower than FastFiller, as it keeps a copy of the input before filling it (unless you use swapValues)
    /// Anyway it has no constraints on the order of the keys, or the number of fillers
    /// It can also be copied around by value without cloning its keys (e.g to put it into a std::vector)
    /// If you set fillOnExit to 'true', it will fill the MultiAssociation automatically when going out of scope
    class LazyFiller {
    public:
      template <typename HandleType>
      LazyFiller(MultiAssociation &assoc, const HandleType &handle, bool fillOnExit = false)
          : assoc_(assoc),
            id_(handle.id()),
            size_(handle->size()),
            tempValues_(new TempValues()),
            fillOnExit_(fillOnExit) {}
      ~LazyFiller() noexcept(false) {
        if (fillOnExit_)
          fill();
      }

      /// Does the real filling. Until this is called, the map is not modified at all.
      /// Calling this twice won't have any effect (but you can't modify a LazyFiller
      /// after calling 'fill()')
      /// Implementation note: inside, it just makes a FastFiller and uses it.
      void fill() noexcept(false);

      /// If set to true, the LazyFiller wil call 'fill()' when it goes out of scope
      void setFillOnExit(bool fillOnExit) { fillOnExit_ = fillOnExit; }

      /// Sets the Collection values associated to this key, making copies of those in refs
      template <typename KeyRef>
      void setValues(const KeyRef &k, const Collection &refs);

      /// Swaps the Collection values associated to this key with the ones in 'ref'
      /// This is expected to be faster than 'setValues'.
      template <typename KeyRef>
      void swapValues(const KeyRef &k, Collection &refs);

    private:
      typedef std::map<unsigned int, Collection> TempValues;
      MultiAssociation &assoc_;
      ProductID id_;
      unsigned int size_;
      std::shared_ptr<TempValues> tempValues_;
      bool fillOnExit_;
    };  // LazyFiller
    friend class LazyFiller;

    template <typename HandleType>
    LazyFiller lazyFiller(const HandleType &h, bool fillOnExit = false) {
      return LazyFiller(*this, h, fillOnExit);
    }

    //Used by ROOT storage
    CMS_CLASS_VERSION(10)

  private:
    typedef helper::IndexRangeAssociation Indices;
    Indices indices_;
    Collection data_;

  };  // class

  // Free swap function
  template <typename C>
  inline void swap(MultiAssociation<C> &lhs, MultiAssociation<C> &rhs) {
    lhs.swap(rhs);
  }

  //============= IMPLEMENTATION OF THE METHODS =============
  template <typename C>
  typename MultiAssociation<C>::const_range MultiAssociation<C>::get(const edm::ProductID &id, unsigned int key) const {
    Indices::range idxrange = indices_.get(id, key);
    return const_range(data_.begin() + idxrange.first, data_.begin() + idxrange.second);
  }

  template <typename C>
  typename MultiAssociation<C>::range MultiAssociation<C>::get(const edm::ProductID &id, unsigned int key) {
    Indices::range idxrange = indices_.get(id, key);
    return range(data_.begin() + idxrange.first, data_.begin() + idxrange.second);
  }

  template <typename C>
  typename MultiAssociation<C>::Collection MultiAssociation<C>::getValues(const edm::ProductID &id,
                                                                          unsigned int key) const {
    Collection ret;
    const_range values = get(id, key);
    for (typename const_range::const_iterator it = values.begin(), ed = values.end(); it != ed; ++it) {
      ret.push_back(*it);
    }
    return ret;
  }

  template <typename C>
  void MultiAssociation<C>::FastFiller::setValues(const edm::ProductID &id, unsigned int key, const Collection &vals) {
    indexFiller_->insert(id, key, assoc_.data_.size(), vals.size());
    for (typename Collection::const_iterator it = vals.begin(), ed = vals.end(); it != ed; ++it) {
      assoc_.data_.push_back(*it);
    }
  }

  template <typename C>
  template <typename KeyRef>
  void MultiAssociation<C>::LazyFiller::setValues(const KeyRef &k, const Collection &vals) {
    if (k.id() != id_)
      Indices::throwUnexpectedProductID(k.id(), id_, "LazyFiller::insert");
    (*tempValues_)[k.key()] = vals;
  }

  template <typename C>
  template <typename KeyRef>
  void MultiAssociation<C>::LazyFiller::swapValues(const KeyRef &k, Collection &vals) {
    if (k.id() != id_)
      Indices::throwUnexpectedProductID(k.id(), id_, "LazyFiller::insert");
    vals.swap((*tempValues_)[k.key()]);
  }

  template <typename C>
  void MultiAssociation<C>::LazyFiller::fill() {
    if (id_ != ProductID()) {  // protection against double filling
      typename MultiAssociation<C>::FastFiller filler(assoc_, id_, size_);
      for (typename TempValues::const_iterator it = tempValues_->begin(), ed = tempValues_->end(); it != ed; ++it) {
        filler.setValues(id_, it->first, it->second);
      }
      id_ = ProductID();  // protection against double filling
    }
  }

}  // namespace edm

#endif