Skip to content
Snippets Groups Projects
TimeSeriesProperty.cpp 67 KiB
Newer Older
#include "MantidKernel/TimeSeriesProperty.h"
#include "MantidKernel/EmptyValues.h"
#include "MantidKernel/Exception.h"
#include "MantidKernel/Logger.h"
#include "MantidKernel/TimeSplitter.h"
#if !(defined __APPLE__ && defined __INTEL_COMPILER)
#else
#include <boost/range/algorithm_ext/is_sorted.hpp>
#endif

using namespace std;
namespace Mantid {
namespace Kernel {
namespace {
/// static Logger definition
Logger g_log("TimeSeriesProperty");
}
/**
 * Constructor
 *  @param name :: The name to assign to the property
 */
template <typename TYPE>
TimeSeriesProperty<TYPE>::TimeSeriesProperty(const std::string &name)
    : Property(name, typeid(std::vector<TimeValueUnit<TYPE>>)), m_values(),
      m_size(), m_propSortedFlag(), m_filterApplied() {}

/// Virtual destructor
template <typename TYPE> TimeSeriesProperty<TYPE>::~TimeSeriesProperty() {}

/**
 * "Virtual" copy constructor
 */
template <typename TYPE>
TimeSeriesProperty<TYPE> *TimeSeriesProperty<TYPE>::clone() const {
  return new TimeSeriesProperty<TYPE>(*this);
}
/**
 * "Virutal copy constructor with a time shift
 * @param timeShift :: a time shift in seconds
 */
template <typename TYPE>
Property *
TimeSeriesProperty<TYPE>::cloneWithTimeShift(const double timeShift) const {
  auto timeSeriesProperty = this->clone();
  auto values = timeSeriesProperty->valuesAsVector();
  auto times = timeSeriesProperty->timesAsVector();
  // Shift the time
  for (auto it = times.begin(); it != times.end(); ++it) {
    (*it) += timeShift;
  }
  timeSeriesProperty->clear();
  timeSeriesProperty->addValues(times, values);
  return timeSeriesProperty;
}

/** Return time series property, containing time derivative of current property.
* The property itself and the returned time derivative become sorted by time and
* the derivative is calculated in seconds^-1.
* (e.g. dValue/dT where dT=t2-t1 is time difference in seconds
* for subsequent time readings and dValue=Val1-Val2 is difference in
* subsequent values)
std::unique_ptr<TimeSeriesProperty<double>>
TimeSeriesProperty<TYPE>::getDerivative() const {
  if (this->m_values.size() < 2) {
    throw std::runtime_error("Derivative is not defined for a time-series "
                             "property with less then two values");
  }

  this->sort();
  auto it = this->m_values.begin();
  int64_t t0 = it->time().totalNanoseconds();
  TYPE v0 = it->value();

  it++;
  auto timeSeriesDeriv = std::unique_ptr<TimeSeriesProperty<double>>(
      new TimeSeriesProperty<double>(this->name() + "_derivative"));
  timeSeriesDeriv->reserve(this->m_values.size() - 1);
  for (; it != m_values.end(); it++) {
    TYPE v1 = it->value();
    int64_t t1 = it->time().totalNanoseconds();
    if (t1 != t0) {
      double deriv = 1.e+9 * (double(v1 - v0) / double(t1 - t0));
      int64_t tm = static_cast<int64_t>((t1 + t0) / 2);
      timeSeriesDeriv->addValue(Kernel::DateAndTime(tm), deriv);
    t0 = t1;
    v0 = v1;
  }
  return timeSeriesDeriv;
}
/** time series derivative specialization for string type */
template <>
std::unique_ptr<TimeSeriesProperty<double>>
TimeSeriesProperty<std::string>::getDerivative() const {
  throw std::runtime_error(
      "Time series property derivative is not defined for strings");
  // return nullptr;
/**
 * Return the memory used by the property, in bytes
 * */
template <typename TYPE>
size_t TimeSeriesProperty<TYPE>::getMemorySize() const {
  // Rough estimate
  return m_values.size() * (sizeof(TYPE) + sizeof(DateAndTime));
}
/**
 * Just returns the property (*this) unless overridden
 *  @param rhs a property that is merged in some descendent classes
 *  @return a property with the value
 */
template <typename TYPE>
TimeSeriesProperty<TYPE> &TimeSeriesProperty<TYPE>::merge(Property *rhs) {
  return operator+=(rhs);
}
/**
 * Add the value of another property
 * @param right the property to add
 * @return the sum
 */
template <typename TYPE>
TimeSeriesProperty<TYPE> &TimeSeriesProperty<TYPE>::
operator+=(Property const *right) {
  TimeSeriesProperty<TYPE> const *rhs =
      dynamic_cast<TimeSeriesProperty<TYPE> const *>(right);

  if (rhs) {
    if (this->operator!=(*rhs)) {
      m_values.insert(m_values.end(), rhs->m_values.begin(),
                      rhs->m_values.end());
      m_propSortedFlag = TimeSeriesSortStatus::TSUNKNOWN;
    } else {
      // Do nothing if appending yourself to yourself. The net result would be
      // the same anyway
      ;
    }

    // Count the REAL size.
    m_size = static_cast<int>(m_values.size());

  } else
    g_log.warning() << "TimeSeriesProperty " << this->name()
                    << " could not be added to another property of the same "
                       "name but incompatible type.\n";

  return *this;
}
/**
 * Deep comparison.
 * @param right The other property to compare to.
 * @return true if the are equal.
 */
template <typename TYPE>
bool TimeSeriesProperty<TYPE>::
operator==(const TimeSeriesProperty<TYPE> &right) const {
  sort();

  if (this->name() != right.name()) // should this be done?
  {
    return false;
  }
  if (this->m_size != right.m_size) {
    return false;
  }
  { // so vectors can go out of scope
    std::vector<DateAndTime> lhsTimes = this->timesAsVector();
    std::vector<DateAndTime> rhsTimes = right.timesAsVector();
    if (!std::equal(lhsTimes.begin(), lhsTimes.end(), rhsTimes.begin())) {
      return false;
  {
    // so vectors can go out of scope
    std::vector<TYPE> lhsValues = this->valuesAsVector();
    std::vector<TYPE> rhsValues = right.valuesAsVector();
    if (!std::equal(lhsValues.begin(), lhsValues.end(), rhsValues.begin())) {
      return false;
    }
  }
/**
 * Deep comparison.
 * @param right The other property to compare to.
 * @return true if the are equal.
 */
template <typename TYPE>
bool TimeSeriesProperty<TYPE>::operator==(const Property &right) const {
  auto rhs_tsp = dynamic_cast<const TimeSeriesProperty<TYPE> *>(&right);
  if (!rhs_tsp)
    return false;
  return this->operator==(*rhs_tsp);
}
/**
 * Deep comparison (not equal).
 * @param right The other property to compare to.
 * @return true if the are not equal.
 */
template <typename TYPE>
bool TimeSeriesProperty<TYPE>::
operator!=(const TimeSeriesProperty<TYPE> &right) const {
  return !(*this == right);
}
/**
 * Deep comparison (not equal).
 * @param right The other property to compare to.
 * @return true if the are not equal.
 */
template <typename TYPE>
bool TimeSeriesProperty<TYPE>::operator!=(const Property &right) const {
  return !(*this == right);
}
/**
 * Set name of the property
 */
template <typename TYPE>
void TimeSeriesProperty<TYPE>::setName(const std::string name) {
  m_name = name;
}
/** Filter out a run by time. Takes out any TimeSeriesProperty log entries
 *outside of the given
 *  absolute time range.
 *  Be noticed that this operation is not reversible.
 *
 *  Use case 1: if start time of the filter fstart is in between t1 and t2 of
 *the TimeSeriesProperty,
 *              then, the new start time is fstart and the value of the log is
 *the log value @ t1
 *
 *  Use case 2: if the start time of the filter in on t1 or before log start
 *time t0, then
 *              the new start time is t1/t0/filter start time.
 *
 * EXCEPTION: If there is only one entry in the list, it is considered to mean
 * "constant" so the value is kept even if the time is outside the range.
 *
 * @param start :: Absolute start time. Any log entries at times >= to this time
 *are kept.
 * @param stop  :: Absolute stop time. Any log entries at times < than this time
 *are kept.
 */
template <typename TYPE>
void TimeSeriesProperty<TYPE>::filterByTime(const Kernel::DateAndTime &start,
                                            const Kernel::DateAndTime &stop) {
  // 0. Sort
  sort();

  // 1. Do nothing for single (constant) value
  if (m_values.size() <= 1)
    return;

  typename std::vector<TimeValueUnit<TYPE>>::iterator iterhead, iterend;

  // 2. Determine index for start and remove  Note erase is [...)
  int istart = this->findIndex(start);
  if (istart >= 0 && static_cast<size_t>(istart) < m_values.size()) {
    // "start time" is behind time-series's starting time
    iterhead = m_values.begin() + istart;

    bool useprefiltertime;
    if (m_values[istart].time() == start) {
      // The filter time is on the mark.  Erase [begin(),  istart)
      useprefiltertime = false;
    } else {
      // The filter time is larger than T[istart]. Erase[begin(), istart) ...
      // filter start(time)
      // and move istart to filter startime
      useprefiltertime = true;
    }

    // Remove the series
    m_values.erase(m_values.begin(), iterhead);

    if (useprefiltertime) {
      m_values[0].setTime(start);
    }
  } else {
    // "start time" is before/after time-series's starting time: do nothing
  // 3. Determine index for end and remove  Note erase is [...)
  int iend = this->findIndex(stop);
  if (static_cast<size_t>(iend) < m_values.size()) {
    if (m_values[iend].time() == stop) {
      // Filter stop is on a log.  Delete that log
      iterend = m_values.begin() + iend;
    } else {
      // Filter stop is behind iend. Keep iend
      iterend = m_values.begin() + iend + 1;
    }
    // Delete from [iend to mp.end)
    m_values.erase(iterend, m_values.end());
  }
  // 4. Make size consistent
  m_size = static_cast<int>(m_values.size());
/**
 * Filter by a range of times. If current property has a single value it remains
 * unaffected
 * @param splittervec :: A list of intervals to split filter on
 */
template <typename TYPE>
void TimeSeriesProperty<TYPE>::filterByTimes(
    const std::vector<SplittingInterval> &splittervec) {
  // 1. Sort
  sort();

  // 2. Return for single value
  if (m_values.size() <= 1) {
    return;
  }
  // 3. Prepare a copy
  std::vector<TimeValueUnit<TYPE>> mp_copy;
  g_log.debug() << "DB541  mp_copy Size = " << mp_copy.size()
                << "  Original MP Size = " << m_values.size() << "\n";
  // 4. Create new
  for (size_t isp = 0; isp < splittervec.size(); ++isp) {
    Kernel::SplittingInterval splitter = splittervec[isp];
    Kernel::DateAndTime t_start = splitter.start();
    Kernel::DateAndTime t_stop = splitter.stop();
    int tstartindex = findIndex(t_start);
    if (tstartindex < 0) {
      // The splitter is not well defined, and use the first
      tstartindex = 0;
    } else if (tstartindex >= int(m_values.size())) {
      // The splitter is not well defined, adn use the last
      tstartindex = int(m_values.size()) - 1;
    int tstopindex = findIndex(t_stop);
    if (tstopindex < 0) {
      tstopindex = 0;
    } else if (tstopindex >= int(m_values.size())) {
      tstopindex = int(m_values.size()) - 1;
    } else {
      if (t_stop == m_values[size_t(tstopindex)].time() &&
          size_t(tstopindex) > 0) {
        tstopindex--;
    /* Check */
    if (tstartindex < 0 || tstopindex >= int(m_values.size())) {
      g_log.warning() << "Memory Leak In SplitbyTime!\n";
    }
    if (tstartindex == tstopindex) {
      TimeValueUnit<TYPE> temp(t_start, m_values[tstartindex].value());
      mp_copy.push_back(temp);
    } else {
      mp_copy.push_back(
          TimeValueUnit<TYPE>(t_start, m_values[tstartindex].value()));
      for (size_t im = size_t(tstartindex + 1); im <= size_t(tstopindex);
           ++im) {
        mp_copy.push_back(
            TimeValueUnit<TYPE>(m_values[im].time(), m_values[im].value()));
      }
    }
  } // ENDFOR
  g_log.debug() << "DB530  Filtered Log Size = " << mp_copy.size()
                << "  Original Log Size = " << m_values.size() << "\n";
  // 5. Clear
  m_values.clear();
  m_values = mp_copy;
  mp_copy.clear();
  m_size = static_cast<int>(m_values.size());
 * Split this time series property by time intervals to multiple time series
 * property according to number of distinct splitters' indexes, such as 0 and 1
 *
 * NOTE: If the input TSP has a single value, it is assumed to be a constant
 *  and so is not split, but simply copied to all outputs.
 *
 * @param splitter :: a TimeSplitterType object containing the list of intervals
 *                     and destinations.
 * @param outputs  :: A vector of output TimeSeriesProperty pointers of the same
 * @param isPeriodic :: whether the log (this TSP) is periodic. For example
 *                    proton-charge is periodic log.
 */
template <typename TYPE>
void TimeSeriesProperty<TYPE>::splitByTime(
    std::vector<SplittingInterval> &splitter, std::vector<Property *> outputs,
    bool isPeriodic) const {
  // 0. Sort if necessary
  sort();

  if (outputs.empty())
    return;

  std::vector<TimeSeriesProperty<TYPE> *> outputs_tsp;

  size_t numOutputs = outputs.size();
  // 1. Clear the outputs before you start
  for (size_t i = 0; i < numOutputs; i++) {
    TimeSeriesProperty<TYPE> *myOutput =
        dynamic_cast<TimeSeriesProperty<TYPE> *>(outputs[i]);
    if (myOutput) {
      outputs_tsp.push_back(myOutput);
      if (this->m_values.size() == 1) {
        // Special case for TSP with a single entry = just copy.
        myOutput->m_values = this->m_values;
        myOutput->m_size = 1;
      } else {
        myOutput->m_values.clear();
        myOutput->m_size = 0;
      }
    } else {
      outputs_tsp.push_back(NULL);
  // 2. Special case for TSP with a single entry = just copy.
  if (this->m_values.size() == 1)
    return;

  // 3. We will be iterating through all the entries in the the map/vector
  size_t i_property = 0;

  //    And at the same time, iterate through the splitter
  Kernel::TimeSplitterType::iterator itspl = splitter.begin();

  size_t counter = 0;
  g_log.debug() << "[DB] Number of time series entries = " << m_values.size()
                << ", Number of splitters = " << splitter.size() << "\n";
  while (itspl != splitter.end() && i_property < m_values.size()) {
    // Get the splitting interval times and destination
    DateAndTime start = itspl->start();
    DateAndTime stop = itspl->stop();

    int output_index = itspl->index();
    // output workspace index is out of range. go to the next splitter
    if (output_index < 0 || output_index >= static_cast<int>(numOutputs))
      continue;

    TimeSeriesProperty<TYPE> *myOutput = outputs_tsp[output_index];
    // skip if the input property is of wrong type
    if (!myOutput) {
      ++itspl;
      ++counter;

    // Skip the events before the start of the time
    while (i_property < m_values.size() && m_values[i_property].time() < start)
      ++i_property;
    if (i_property == m_values.size()) {
      // i_property is out of the range. Then use the last entry
      myOutput->addValue(m_values[i_property - 1].time(),
                         m_values[i_property - 1].value());

      ++itspl;
      ++counter;
      break;
    }

    // The current entry is within an interval. Record them until out
    if (m_values[i_property].time() > start && i_property > 0 && !isPeriodic) {
      // Record the previous oneif this property is not exactly on start time
      //   and this entry is not recorded
      size_t i_prev = i_property - 1;
      if (myOutput->size() == 0 ||
          m_values[i_prev].time() != myOutput->lastTime())
        myOutput->addValue(m_values[i_prev].time(), m_values[i_prev].value());
    }

    // Loop through all the entries until out.
    while (i_property < m_values.size() && m_values[i_property].time() < stop) {

      // Copy the log out to the output
      myOutput->addValue(m_values[i_property].time(),
                         m_values[i_property].value());
      ++i_property;
    // Go to the next interval
    ++itspl;
    ++counter;
    // But if we reached the end, then we are done.
    if (itspl == splitter.end())
      break;
    // No need to keep looping through the filter if we are out of events
    if (i_property == this->m_values.size())
  } // Looping through entries in the splitter vector
  // Make sure all entries have the correct size recorded in m_size.
  for (std::size_t i = 0; i < numOutputs; i++) {
    TimeSeriesProperty<TYPE> *myOutput =
        dynamic_cast<TimeSeriesProperty<TYPE> *>(outputs[i]);
    if (myOutput) {
      myOutput->m_size = myOutput->realSize();
      // g_log.notice() << "[DB] Final output size = " << myOutput->size() <<
      // "\n";
    }
// The makeFilterByValue & expandFilterToRange methods generate a bunch of
// warnings when the template type is the wider integer types
// (when it's being assigned back to a double such as in a call to minValue or
// firstValue)
// However, in reality these methods are only used for TYPE=int or double (they
// are only called from FilterByLogValue) so suppress the warnings
#ifdef _WIN32
#pragma warning(push)
#pragma warning(disable : 4244)
#pragma warning(disable : 4804) // This one comes about for TYPE=bool - again
                                // the method is never called for this type
#endif
#if defined(__GNUC__) && !(defined(__INTEL_COMPILER))
#pragma GCC diagnostic ignored "-Wconversion"
/**
 * Fill a TimeSplitterType that will filter the events by matching
 * log values >= min and <= max. Creates SplittingInterval's where
 * times match the log values, and going to index==0.
 * This method is used by the FilterByLogValue algorithm.
 *
 * @param split :: Splitter that will be filled.
 * @param min :: min value
 * @param max :: max value
 * @param TimeTolerance :: offset added to times in seconds (default: 0)
 * @param centre :: Whether the log value time is considered centred or at the
 *beginning (the default).
 */
template <typename TYPE>
void TimeSeriesProperty<TYPE>::makeFilterByValue(
    std::vector<SplittingInterval> &split, double min, double max,
    double TimeTolerance, bool centre) const {
  const bool emptyMin = (min == EMPTY_DBL());
  const bool emptyMax = (max == EMPTY_DBL());

  if (!emptyMin && !emptyMax && max < min) {
    std::stringstream ss;
    ss << "TimeSeriesProperty::makeFilterByValue: 'max' argument must be "
          "greater than 'min' "
       << "(got min=" << min << " max=" << max << ")";
    throw std::invalid_argument(ss.str());
  }
  // If min or max were unset ("empty") in the algorithm, set to the min or max
  // value of the log
  if (emptyMin)
    min = minValue();
  if (emptyMax)
    max = maxValue();

  // Make sure the splitter starts out empty
  split.clear();

  // Do nothing if the log is empty.
  if (m_values.empty())
    return;

  // 1. Sort
  sort();

  // 2. Do the rest
  bool lastGood(false);
  time_duration tol = DateAndTime::durationFromSeconds(TimeTolerance);
  int numgood = 0;
  DateAndTime t;
  DateAndTime start, stop;

  for (size_t i = 0; i < m_values.size(); ++i) {
    const DateAndTime lastTime = t;
    // The new entry
    t = m_values[i].time();
    TYPE val = m_values[i].value();

    // A good value?
    const bool isGood = ((val >= min) && (val <= max));
    if (isGood)
      numgood++;

    if (isGood != lastGood) {
      // We switched from bad to good or good to bad

      if (isGood) {
        // Start of a good section. Subtract tolerance from the time if
        // boundaries are centred.
        start = centre ? t - tol : t;
      } else {
        // End of the good section. Add tolerance to the LAST GOOD time if
        // boundaries are centred.
        // Otherwise, use the first 'bad' time.
        stop = centre ? lastTime + tol : t;
        split.push_back(SplittingInterval(start, stop, 0));
        // Reset the number of good ones, for next time
        numgood = 0;
      }
      lastGood = isGood;
  if (numgood > 0) {
    // The log ended on "good" so we need to close it using the last time we
    // found
    stop = t + tol;
    split.push_back(SplittingInterval(start, stop, 0));
  }
/** Function specialization for TimeSeriesProperty<std::string>
 *  @throws Kernel::Exception::NotImplementedError always
 */
template <>
void TimeSeriesProperty<std::string>::makeFilterByValue(
    std::vector<SplittingInterval> &, double, double, double, bool) const {
  throw Exception::NotImplementedError("TimeSeriesProperty::makeFilterByValue "
                                       "is not implemented for string "
                                       "properties");
}
/** If the first and/or last values in a log are between min & max, expand and
 * existing TimeSplitter
 *  (created by makeFilterByValue) if necessary to cover the full TimeInterval
 * given.
 *  This method is used by the FilterByLogValue algorithm.
 *  @param split The splitter to modify if necessary
 *  @param min   The minimum 'good' value
 *  @param max   The maximum 'good' value
 *  @param range The full time range that we want this splitter to cover
 */
template <typename TYPE>
void TimeSeriesProperty<TYPE>::expandFilterToRange(
    std::vector<SplittingInterval> &split, double min, double max,
    const TimeInterval &range) const {
  const bool emptyMin = (min == EMPTY_DBL());
  const bool emptyMax = (max == EMPTY_DBL());

  if (!emptyMin && !emptyMax && max < min) {
    std::stringstream ss;
    ss << "TimeSeriesProperty::expandFilterToRange: 'max' argument must be "
          "greater than 'min' "
       << "(got min=" << min << " max=" << max << ")";
    throw std::invalid_argument(ss.str());
  }
  // If min or max were unset ("empty") in the algorithm, set to the min or max
  // value of the log
  if (emptyMin)
    min = minValue();
  if (emptyMax)
    max = maxValue();

  // Assume everything before the 1st value is constant
  double val = firstValue();
  if ((val >= min) && (val <= max)) {
    TimeSplitterType extraFilter;
    extraFilter.push_back(SplittingInterval(range.begin(), firstTime(), 0));
    // Include everything from the start of the run to the first time measured
    // (which may be a null time interval; this'll be ignored)
    split = split | extraFilter;
  }
  // Assume everything after the LAST value is constant
  val = lastValue();
  if ((val >= min) && (val <= max)) {
    TimeSplitterType extraFilter;
    extraFilter.push_back(SplittingInterval(lastTime(), range.end(), 0));
    // Include everything from the start of the run to the first time measured
    // (which may be a null time interval; this'll be ignored)
    split = split | extraFilter;
  }
/** Function specialization for TimeSeriesProperty<std::string>
 *  @throws Kernel::Exception::NotImplementedError always
 */
template <>
void TimeSeriesProperty<std::string>::expandFilterToRange(
    std::vector<SplittingInterval> &, double, double,
    const TimeInterval &) const {
  throw Exception::NotImplementedError("TimeSeriesProperty::makeFilterByValue "
                                       "is not implemented for string "
                                       "properties");
}
/** Calculates the time-weighted average of a property in a filtered range.
 *  This is written for that case of logs whose values start at the times given.
 *  @param filter The splitter/filter restricting the range of values included
 *  @return The time-weighted average value of the log in the range within the
 * filter.
 */
template <typename TYPE>
double TimeSeriesProperty<TYPE>::averageValueInFilter(
    const std::vector<SplittingInterval> &filter) const {
  // TODO: Consider logs that aren't giving starting values.

  // First of all, if the log or the filter is empty, return NaN
  if (realSize() == 0 || filter.empty()) {
    return std::numeric_limits<double>::quiet_NaN();
  }
  // If there's just a single value in the log, return that.
  if (realSize() == 1) {
    return static_cast<double>(m_values.front().value());
  }
  // Sort, if necessary.
  sort();

  double numerator(0.0), totalTime(0.0);
  // Loop through the filter ranges
  for (TimeSplitterType::const_iterator it = filter.begin(); it != filter.end();
       ++it) {
    // Calculate the total time duration (in seconds) within by the filter
    totalTime += it->duration();

    // Get the log value and index at the start time of the filter
    int index;
    double value = getSingleValue(it->start(), index);
    DateAndTime startTime = it->start();

    while (index < realSize() - 1 && m_values[index + 1].time() < it->stop()) {
      ++index;
      numerator +=
          DateAndTime::secondsFromDuration(m_values[index].time() - startTime) *
          value;
      startTime = m_values[index].time();
      value = static_cast<double>(m_values[index].value());
    }

    // Now close off with the end of the current filter range
    numerator +=
        DateAndTime::secondsFromDuration(it->stop() - startTime) * value;
  }
  // 'Normalise' by the total time
  return numerator / totalTime;
}
/** Calculates the time-weighted average of a property.
 *  @return The time-weighted average value of the log.
 */
template <typename TYPE>
double TimeSeriesProperty<TYPE>::timeAverageValue() const {
  double retVal = 0.0;
  try {
    TimeSplitterType filter;
    filter.push_back(SplittingInterval(this->firstTime(), this->lastTime()));
    retVal = this->averageValueInFilter(filter);
  } catch (exception &) {
    // just return nan
    retVal = std::numeric_limits<double>::quiet_NaN();
  }
  return retVal;
}
/** Function specialization for TimeSeriesProperty<std::string>
 *  @throws Kernel::Exception::NotImplementedError always
 */
template <>
double TimeSeriesProperty<std::string>::averageValueInFilter(
    const TimeSplitterType &) const {
  throw Exception::NotImplementedError("TimeSeriesProperty::"
                                       "averageValueInFilter is not "
                                       "implemented for string properties");
}
// Re-enable the warnings disabled before makeFilterByValue
#ifdef _WIN32
#pragma warning(pop)
#endif
#if defined(__GNUC__) && !(defined(__INTEL_COMPILER))
#pragma GCC diagnostic warning "-Wconversion"
#endif
/**
 *  Return the time series as a correct C++ map<DateAndTime, TYPE>. All values
 * are included.
 *
 * @return time series property values as map
 */
template <typename TYPE>
std::map<DateAndTime, TYPE>
TimeSeriesProperty<TYPE>::valueAsCorrectMap() const {
  // 1. Sort if necessary
  sort();

  // 2. Data Strcture
  std::map<DateAndTime, TYPE> asMap;

  if (m_values.size() > 0) {
    for (size_t i = 0; i < m_values.size(); i++)
      asMap[m_values[i].time()] = m_values[i].value();
  }
/**
 *  Return the time series's values as a vector<TYPE>
 *  @return the time series's values as a vector<TYPE>
 */
template <typename TYPE>
std::vector<TYPE> TimeSeriesProperty<TYPE>::valuesAsVector() const {
  sort();
  std::vector<TYPE> out;
  out.reserve(m_values.size());
  for (size_t i = 0; i < m_values.size(); i++)
    out.push_back(m_values[i].value());
/**
  * Return the time series as a C++ multimap<DateAndTime, TYPE>. All values.
  * This method is used in parsing the ISIS ICPevent log file: different
 * commands
  * can be recorded against the same time stamp but all must be present.
  */
template <typename TYPE>
std::multimap<DateAndTime, TYPE>
TimeSeriesProperty<TYPE>::valueAsMultiMap() const {
  std::multimap<DateAndTime, TYPE> asMultiMap;

  if (m_values.size() > 0) {
    for (size_t i = 0; i < m_values.size(); i++)
      asMultiMap.insert(
          std::make_pair(m_values[i].time(), m_values[i].value()));
  }
  return asMultiMap;
}
/**
 * Return the time series's times as a vector<DateAndTime>
 * @return A vector of DateAndTime objects
 */
template <typename TYPE>
std::vector<DateAndTime> TimeSeriesProperty<TYPE>::timesAsVector() const {
  sort();
  std::vector<DateAndTime> out;
  out.reserve(m_values.size());
  for (size_t i = 0; i < m_values.size(); i++) {
    out.push_back(m_values[i].time());
  }
/**
 * @return Return the series as list of times, where the time is the number of
 * seconds since the start.
 */
template <typename TYPE>
std::vector<double> TimeSeriesProperty<TYPE>::timesAsVectorSeconds() const {
  // 1. Sort if necessary
  sort();

  // 2. Output data structure
  std::vector<double> out;
  out.reserve(m_values.size());

  Kernel::DateAndTime start = m_values[0].time();
  for (size_t i = 0; i < m_values.size(); i++) {
    out.push_back(DateAndTime::secondsFromDuration(m_values[i].time() - start));
  }
/** Add a value to the series.
 *  Added values need not be sequential in time.
 *  @param time   The time
 *  @param value  The associated value
 */
template <typename TYPE>
void TimeSeriesProperty<TYPE>::addValue(const Kernel::DateAndTime &time,
                                        const TYPE value) {
  TimeValueUnit<TYPE> newvalue(time, value);
  // Add the value to the back of the vector
  m_values.push_back(newvalue);
  // Increment the separate record of the property's size
  m_size++;

  // Toggle the sorted flag if necessary
  // (i.e. if the flag says we're sorted and the added time is before the prior
  // last time)
  if (m_size == 1) {
    // First item, must be sorted.
    m_propSortedFlag = TimeSeriesSortStatus::TSSORTED;
  } else if (m_propSortedFlag == TimeSeriesSortStatus::TSUNKNOWN &&
             m_values.back() < *(m_values.rbegin() + 1)) {
    // Previously unknown and still unknown
    m_propSortedFlag = TimeSeriesSortStatus::TSUNSORTED;
  } else if (m_propSortedFlag == TimeSeriesSortStatus::TSSORTED &&
             m_values.back() < *(m_values.rbegin() + 1)) {
    // Previously sorted but last added is not in order
    m_propSortedFlag = TimeSeriesSortStatus::TSUNSORTED;
  }
  m_filterApplied = false;
/** Add a value to the map
 *  @param time :: The time as a string in the format: (ISO 8601)
 * yyyy-mm-ddThh:mm:ss
 *  @param value :: The associated value
 *  @return True if insertion successful (i.e. identical time not already in map
 */
template <typename TYPE>
void TimeSeriesProperty<TYPE>::addValue(const std::string &time,
                                        const TYPE value) {
  return addValue(Kernel::DateAndTime(time), value);
}
/**
 * Add a value to the map using a time_t
 *  @param time :: The time as a time_t value
 *  @param value :: The associated value
 *  @return True if insertion successful (i.e. identical time not already in map
 */
template <typename TYPE>
void TimeSeriesProperty<TYPE>::addValue(const std::time_t &time,
                                        const TYPE value) {
  Kernel::DateAndTime dt;
  dt.set_from_time_t(time);
  return addValue(dt, value);
}
/** Adds vectors of values to the map. Should be much faster than repeated calls
 * to addValue.
 *  @param times :: The time as a boost::posix_time::ptime value
 *  @param values :: The associated value
 */
template <typename TYPE>
void TimeSeriesProperty<TYPE>::addValues(
    const std::vector<Kernel::DateAndTime> &times,
    const std::vector<TYPE> &values) {
  for (size_t i = 0; i < times.size(); i++) {
    if (i >= values.size())
      break;
    else {
      m_values.push_back(TimeValueUnit<TYPE>(times[i], values[i]));
      m_size++;
  if (values.size() > 0)
    m_propSortedFlag = TimeSeriesSortStatus::TSUNKNOWN;