Advertisement
andybuckley

Improved 1D and rectangular 2D binning of arbitrary types

Jul 17th, 2014
305
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 10.58 KB | None | 0 0
  1. // IMPLEMENTATION: PUT IN A HEADER E.G. BINNEDFN.H
  2.  
  3. #pragma once
  4.  
  5. #include <cstddef>
  6. #include <vector>
  7. #include <algorithm>
  8. #include <stdexcept>
  9. #include <cassert>
  10. #include <cmath>
  11.  
  12.  
  13. // Simple class just to do 1D bin index lookups. Not a container.
  14. template <typename TX=float>
  15. class Binning1D {
  16. public:
  17.  
  18.   /// Constructor taking a list of bin edges
  19.   Binning1D(const std::vector<TX>& binedges)
  20.     : edges(binedges)
  21.   {
  22.     reset();
  23.   }
  24.  
  25.  
  26.   /// Get the number of bins
  27.   size_t num_bins() const {
  28.     check();
  29.     return edges.size() - 1;
  30.   }
  31.   /// Get the number of bins
  32.   size_t size() const {
  33.     return num_bins();
  34.   }
  35.  
  36.  
  37.   /// Get the bin index enclosing position @a x
  38.   /// @note This is where _all_ the bin searching intelligence lives
  39.   size_t get_index(const TX& x) const {
  40.     check();
  41.     if (x < edges.front()) throw std::out_of_range("Bin position underflow");
  42.     if (x > edges.back()) throw std::out_of_range("Bin position overflow");
  43.     // Find the closest knot below the requested value, starting with intelligent guesses
  44.     // First assume linear/uniform binning
  45.     const TX xfrac = (x - edges.front()) / (edges.back() - edges.front());
  46.     const size_t iguess = floor( num_bins() * xfrac );
  47.     if (edges[iguess] <= x && edges[iguess+1] > x) return iguess;
  48.     // Then we'll try for uniform binning in log space if the vector is big enough to justify the log calls
  49.     if (edges.size() > 64) { //< @todo Optimise
  50.       const TX lnxlow = log(edges.front());
  51.       const TX lnxtot = log(edges.back()) - lnxlow;
  52.       const TX dlnx = log(x) - lnxlow;
  53.       const TX xfrac2 = dlnx / lnxtot;
  54.       const size_t iguess2 = floor( num_bins() * xfrac2 );
  55.       if (edges[iguess2] <= x && edges[iguess2+1] > x) return iguess2;
  56.     }
  57.     // Fall back to something more general
  58.     /// @todo Specialise for a linear search if size < 32, otherwise binary split as below
  59.     size_t i = upper_bound(edges.begin(), edges.end(), x) - edges.begin();
  60.     if (i == edges.size()) i -= 1; // can't return the last knot index
  61.     i -= 1; // have to step back to get the knot <= x behaviour
  62.     return i;
  63.   }
  64.  
  65.  
  66.   /// Clear the bin contents (but leave the binning intact)
  67.   void reset() {
  68.     assert(edges.size() > 1);
  69.     check();
  70.   }
  71.  
  72.   /// Check consistency
  73.   void check() const {
  74.     if (edges.size() <= 1) throw std::length_error("There must be 2 or more bin edges");
  75.   }
  76.  
  77.   /// The list of bin edges
  78.   std::vector<TX> edges;
  79.  
  80. };
  81.  
  82.  
  83. // Simple class just to do 1D bin index lookups. Not a container.
  84. template <typename TX=float, typename TY=float>
  85. class Binning2D {
  86. public:
  87.  
  88.   /// Constructor taking lists of bin edges
  89.   Binning2D(const std::vector<TX>& xbinedges, const std::vector<TX>& ybinedges)
  90.     : binningX(xbinedges), binningY(ybinedges)
  91.   {
  92.     reset();
  93.   }
  94.  
  95.   /// Constructor taking 1D binnings
  96.   Binning2D(const Binning1D<TX>& xbinning, const Binning1D<TY>& ybinning)
  97.     : binningX(xbinning), binningY(ybinning)
  98.   {
  99.     reset();
  100.   }
  101.  
  102.  
  103.   /// Get the number of bins in x
  104.   size_t num_bins_x() const {
  105.     return binningX.num_bins();
  106.   }
  107.  
  108.   /// Get the number of bins in y
  109.   size_t num_bins_y() const {
  110.     return binningY.num_bins();
  111.   }
  112.  
  113.   /// Get the total number of bins
  114.   size_t num_bins() const {
  115.     return num_bins_x() * num_bins_y();
  116.   }
  117.   /// Get the total number of bins
  118.   size_t size() const {
  119.     return num_bins();
  120.   }
  121.  
  122.  
  123.   /// Get the x bin index enclosing position @a x
  124.   size_t get_index_x(const TX& x) const {
  125.     return binningX.get_index(x);
  126.   }
  127.  
  128.   /// Get the y bin index enclosing position @a y
  129.   size_t get_index_y(const TY& y) const {
  130.     return binningY.get_index(y);
  131.   }
  132.  
  133.   /// Get the (x,y) bin index pair enclosing position @a (x,y)
  134.   std::pair<size_t,size_t> get_index_pair(const TX& x, const TY& y) const {
  135.     return std::make_pair(get_index_x(x), get_index_y(y));
  136.   }
  137.  
  138.  
  139.   /// Clear the bin contents (but leave the binning intact)
  140.   void reset() {
  141.     binningX.reset();
  142.     binningY.reset();
  143.     check();
  144.   }
  145.  
  146.   /// Check consistency
  147.   void check() const {
  148.     binningX.check();
  149.     binningY.check();
  150.   }
  151.  
  152.   /// The lists of bin edges
  153.   Binning1D<TX> binningX, binningY;
  154.  
  155. };
  156.  
  157.  
  158. /// Binned container of Ts in 1D
  159. template <typename T, typename TX=float>
  160. class BinnedFn1D {
  161. public:
  162.  
  163.   /// Constructor taking a list of bin edges
  164.   BinnedFn1D(const std::vector<TX>& binedges)
  165.     : binning(binedges)
  166.   {
  167.     reset();
  168.   }
  169.  
  170.   /// Constructor taking lists of bin edges and values
  171.   BinnedFn1D(const std::vector<TX>& binedges, const std::vector<T>& binvalues)
  172.     : binning(binedges), values(binvalues)
  173.   {
  174.     check();
  175.   }
  176.  
  177.  
  178.   /// Get the number of bins
  179.   size_t num_bins() const {
  180.     return binning.num_bins();
  181.   }
  182.  
  183.   /// Get the bin index enclosing position @a x
  184.   size_t get_index(const TX& x) const {
  185.     return binning.get_index(x);
  186.   }
  187.  
  188.   /// Get the value in bin number @a ix
  189.   const T& get_at_index(size_t ix) const {
  190.     check();
  191.     return values[ix];
  192.   }
  193.  
  194.   /// Get the value in the bin at position @a x
  195.   const T& get_at(const TX& x) const {
  196.     return get_at_index(get_index(x));
  197.   }
  198.  
  199.   /// Set the value in bin number @a ix
  200.   void set_at_index(size_t ix, const T& val) {
  201.     check();
  202.     values[ix] = val;
  203.   }
  204.  
  205.   /// Set the value in the bin at position @a x
  206.   void set_at(const TX& x, const T& val) {
  207.     set_at_index(get_index(x), val);
  208.   }
  209.  
  210.  
  211.   /// Clear the bin contents (but leave the binning intact)
  212.   void reset() {
  213.     // binning.reset();
  214.     values.clear();
  215.     values.resize(num_bins());
  216.     check();
  217.   }
  218.  
  219.   /// Check consistency of the edges and values vectors
  220.   void check() const {
  221.     binning.check();
  222.     if (values.size() < 1) throw std::length_error("There must be 1 or more bin values");
  223.     if (binning.size() != values.size()) throw std::length_error("There must be one more bin edge than there are bin values");
  224.   }
  225.  
  226.   /// The list of bin edges
  227.   Binning1D<TX> binning;
  228.  
  229.   /// The list of values
  230.   std::vector<T> values;
  231.  
  232. };
  233.  
  234.  
  235.  
  236. /// Binned container of Ts in 2D
  237. template <typename T, typename TX=float, typename TY=float>
  238. class BinnedFn2D {
  239. public:
  240.  
  241.   /// Constructor taking a list of bin edges
  242.   BinnedFn2D(const std::vector<TX>& xbinedges, const std::vector<TY>& ybinedges)
  243.     : binning(xbinedges, ybinedges)
  244.   {
  245.     reset();
  246.   }
  247.  
  248.   /// Constructor taking lists of bin edges and values
  249.   BinnedFn2D(const std::vector<TX>& xbinedges, const std::vector<TY>& ybinedges, const std::vector<T>& binvalues)
  250.     : binning(xbinedges, ybinedges), values(binvalues)
  251.   {
  252.     check();
  253.   }
  254.  
  255.  
  256.   /// Get the number of bins
  257.   size_t num_bins() const {
  258.     return binning.num_bins();
  259.   }
  260.   /// Get the number of bins in x
  261.   size_t num_bins_x() const {
  262.     return binning.num_bins_x();
  263.   }
  264.   /// Get the number of bins in y
  265.   size_t num_bins_y() const {
  266.     return binning.num_bins_y();
  267.   }
  268.  
  269.  
  270.   /// Get the x bin index enclosing position @a x
  271.   size_t get_index_x(const TX& x) const {
  272.     return binning.get_index_x(x);
  273.   }
  274.  
  275.   /// Get the y bin index enclosing position @a y
  276.   size_t get_index_y(const TY& y) const {
  277.     return binning.get_index_y(y);
  278.   }
  279.  
  280.   /// Get the x,y bin index pair enclosing position @a (x,y)
  281.   std::pair<size_t,size_t> get_index_pair(const TX& x, const TY& y) const {
  282.     return binning.get_index_pair(x, y);
  283.   }
  284.  
  285.   /// Get the global bin index enclosing position @a (x,y)
  286.   size_t get_index(const TX& x, const TY& y) const {
  287.     return global_index(get_index_pair(x, y));
  288.   }
  289.  
  290.  
  291.   /// Get the global bin index from individual indices @a ix and @a iy
  292.   size_t global_index(size_t ix, size_t iy) const {
  293.     return ix*num_bins_y() + iy;
  294.   }
  295.   /// Get the global bin index from individual indices @a ix and @a iy
  296.   size_t global_index(const std::pair<size_t,size_t>& ixiy) const {
  297.     return global_index(ixiy.first, ixiy.second);
  298.   }
  299.  
  300.  
  301.  
  302.   /// Get the value in global bin number @a i
  303.   const T& get_at_index(size_t i) const {
  304.     check();
  305.     return values[i];
  306.   }
  307.  
  308.   /// Get the value in bin number pair @a (ix, iy)
  309.   const T& get_at_index(size_t ix, size_t iy) const {
  310.     return get_at_index(global_index(ix, iy));
  311.   }
  312.  
  313.   /// Get the value in the bin at position @a (x,y)
  314.   const T& get_at(const TX& x, const TY& y) const {
  315.     return get_at_index(get_index(x, y));
  316.   }
  317.  
  318.  
  319.   /// Set the value in global bin number @a ix
  320.   void set_at_index(size_t i, const T& val) {
  321.     check();
  322.     values[i] = val;
  323.   }
  324.  
  325.   /// Set the value in bin number pair @a (ix, iy)
  326.   void set_at_index(size_t ix, size_t iy, const T& val) {
  327.     set_at_index(global_index(ix, iy), val);
  328.   }
  329.  
  330.   /// Set the value in the bin at position @a (x,y)
  331.   void set_at(const TX& x, const TY& y, const T& val) {
  332.     set_at_index(get_index(x,y), val);
  333.   }
  334.  
  335.  
  336.   /// Clear the bin contents (but leave the binning intact)
  337.   void reset() {
  338.     // binning.reset();
  339.     values.clear();
  340.     values.resize(num_bins());
  341.     check();
  342.   }
  343.  
  344.   /// Check consistency of the edges and values vectors
  345.   void check() const {
  346.     binning.check();
  347.     if (values.size() < 1) throw std::length_error("There must be 1 or more bin values");
  348.     if (binning.size() != values.size()) throw std::length_error("There must be one more bin edge than there are bin values");
  349.   }
  350.  
  351.  
  352.   /// The list of bin edges
  353.   Binning2D<TX, TY> binning;
  354.  
  355.   /// The list of values
  356.   std::vector<T> values;
  357.  
  358. };
  359.  
  360.  
  361.  
  362.  
  363. /////////////////
  364. // TEST:
  365.  
  366.  
  367.  
  368. #include <iostream>
  369. using namespace std;
  370.  
  371. struct F {
  372.   F() : i(-1) {}
  373.   F(int a) : i(a) {}
  374.   int i;
  375. };
  376. ostream& operator << (ostream& os, const F& f) { os << "F" << f.i; return os; }
  377.  
  378.  
  379. int main() {
  380.  
  381.   cout << "1D floats" << endl;
  382.   BinnedFn1D<float> b1a({{0,1,2,3,4,5}}, {{10,20,30,40,50}});
  383.   cout << b1a.get_index(3.3) << endl;
  384.   cout << b1a.get_at(3.3) << endl;
  385.   cout << b1a.get_at_index(3) << endl;
  386.   cout << endl;
  387.  
  388.   cout << "1D Fs" << endl;
  389.   BinnedFn1D<F> b1b({{0,1,2,3,4,5}}, {{10,20,30,40,50}});
  390.   cout << b1b.get_index(2.3) << endl;
  391.   cout << b1b.get_at(2.3) << endl;
  392.   cout << b1b.get_at_index(2) << endl;
  393.   cout << endl;
  394.  
  395.   ///////
  396.  
  397.   cout << "2D floats" << endl;
  398.   BinnedFn2D<float> b2a({{0,1,2}}, {{10,20,30}}, {{1,2,3,4}});
  399.   cout << b2a.get_index(1.3, 25) << endl;
  400.   cout << b2a.get_at(1.3, 19) << endl;
  401.   for (ssize_t i = b2a.num_bins()-1; i >= 0; --i) cout << b2a.get_at_index(i) << endl;
  402.   cout << endl;
  403.  
  404.   cout << "2D Fs" << endl;
  405.   BinnedFn2D<F> b2b({{0,1,2}}, {{10,20,30}}, {{1,2,3,4}});
  406.   cout << b2b.get_index(1.3, 21) << endl;
  407.   cout << b2b.get_at(1.3, 12) << endl;
  408.   for (ssize_t i = b2b.num_bins()-1; i >= 0; --i) cout << b2b.get_at_index(i) << endl;
  409.  
  410.   return 0;
  411. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement