Skip to content
Snippets Groups Projects
BinFinder.cpp 4.99 KiB
Newer Older
#include "MantidKernel/BinFinder.h"
#include "MantidKernel/Exception.h"

using std::size_t;

namespace Mantid {
namespace Kernel {

/** Constructor. Sets up the calculation for later.
 *
 * @param binParams: the binning parameters, as a vector of doubles. E.g.
 *    0, 1.0, 100, -0.5, 1e7:
 *    bins in step to 1.0 from 0 to 100; then log steps up to 1e7
 */
BinFinder::BinFinder(const std::vector<double> &binParams) {
  boundaries.clear();
  stepSizes.clear();

  size_t n = binParams.size();
  if (n < 3)
    throw std::invalid_argument("BinFinder: not enough bin parameters.");
  if (n % 2 == 0)
    throw std::invalid_argument(
        "BinFinder: the number of bin parameters should be odd.");

  for (size_t i = 0; i < n / 2; i++) {
    // The boundaries
    double min = binParams[i * 2];
    double max = binParams[i * 2 + 2];
    // Only the first bin needs the min boundary
    if (i == 0)
      boundaries.push_back(min);
    boundaries.push_back(max);
    // The step
    double step = binParams[i * 2 + 1];
    stepSizes.push_back(step);
    if (step == 0)
      throw std::invalid_argument("BinFinder: step size of 0.");
    if ((step < 0) && (min <= 0))
      throw std::invalid_argument(
          "BinFinder: logarithmic binning with 0.0 starting bin.");
    if (max <= min)
      throw std::invalid_argument(
          "BinFinder: final bin must be > starting bin boundary.");

    int numBins = 0;

    // Pre-do some calculations for log binning.
    if (step < 0) {
      double log_step = log1p(fabs(step));
      logSteps.push_back(log_step);
      if (i == 0)
        logBoundaries.push_back(log(min));
      logBoundaries.push_back(log(max));
      // How many bins is that?
      numBins = static_cast<int>(ceil((log(max) - log(min)) / log_step));

      // Check that the last bin is at least .25 x the previous step
      // This is because the VectorHelper removes that final bin. Annoying!
      double nextToLastValue = min * pow(1.0 + fabs(step), numBins - 1);
      double nextToNextToLastValue = min * pow(1.0 + fabs(step), numBins - 2);
      double lastBinSize = max - nextToLastValue;
      double nextToLastBinSize = nextToLastValue - nextToNextToLastValue;
      if (lastBinSize < nextToLastBinSize * 0.25)
        numBins--;
      if (numBins < 1)
        numBins = 1;

    } else {
      // Empty log values; these won't be used
      logSteps.push_back(0);
      if (i == 0)
        logBoundaries.push_back(0);
      logBoundaries.push_back(0);
      //# of linear bins
      numBins = static_cast<int>(ceil((max - min) / step));

      // Check that the last bin is at least .25 x the previous step
      // This is because the VectorHelper removes that final bin. Annoying!
      double lastBinSize = max - ((numBins - 1) * step + min);
      if (lastBinSize < step * 0.25)
        numBins--;
      if (numBins < 1)
        numBins = 1;
    // Find the end bin index
    int startBinIndex = 0;
    if (i > 0)
      startBinIndex = this->endBinIndex[i - 1];
    endBinIndex.push_back(numBins + startBinIndex);
  // How many binning regions?
  numRegions = static_cast<int>(stepSizes.size());
}

/// Destructor
BinFinder::~BinFinder() {}

/** Returns the last bin boundary index,
 * which should be == to the size of the X axis.
 */
int BinFinder::lastBinIndex() {
  if (endBinIndex.size() > 0)
    return endBinIndex[endBinIndex.size() - 1];
  else
    return -1;
}

/** Find the bin index for a value.
 * @param x: x-value to histogram
 * @return an int corresponding to the bin index to use, or -1 if out of bounds.
 */
int BinFinder::bin(double x) {
  int index;
  double min(boundaries[0]);

  // Too small?
  if (x < boundaries[0])
    return -1;

  // Find which binning region to use
  int i = -1;
  for (i = 0; i < numRegions; i++) {
    min = boundaries[i];
    double max = boundaries[i + 1];
    if ((x >= min) && (x < max))
      break;
  // Didn't find it?
  if (i >= numRegions)
    return -1;

  // Step size in this region
  double step = stepSizes[i];
  if (step > 0) {
    // Linear binning. Truncate when you divide by the step size
    index = static_cast<int>((x - min) / step);
    // Add bin index offset if not in the first region
    if (i > 0)
      index += endBinIndex[i - 1];
    // In the event that a final bin was skipped, cap to the max
    if (index >= endBinIndex[i])
      index = endBinIndex[i] - 1;
    return index;
  } else {
    /** Log binning formula for bin index n:
     *  x_n = min * ( 1 + |step| ) ^ n
     *  log(x_n) = log(min) + n * log(1+|step|)
     *  therefore
     *  n = (log(x_n) - log(min))/log(1+|step|)
     */

    double log_x = log(x); // Just one log to call per event!
    double log_step = logSteps[i];
    double log_min = logBoundaries[i];
    index = static_cast<int>((log_x - log_min) / log_step);
    // Add bin index offset if not in the first region
    if (i > 0)
      index += endBinIndex[i - 1];
    // In the event that a final bin was skipped, cap to the max
    if (index >= endBinIndex[i])
      index = endBinIndex[i] - 1;
    return index;