Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
LogParser.cpp 10.98 KiB
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidKernel/LogParser.h"
#include "MantidKernel/Strings.h"
#include "MantidKernel/Logger.h"
#include "MantidKernel/PropertyWithValue.h"
#include "MantidKernel/TimeSeriesProperty.h"

#include <fstream>

// constants for the new style icp event commands
const char *START_COLLECTION = "START_COLLECTION";
const char *STOP_COLLECTION = "STOP_COLLECTION";

using std::size_t;

namespace Mantid {
namespace Kernel {
namespace {
/// static logger
Logger g_log("LogParser");
}

/// @returns the name of the log created that defines the status during a run
const std::string LogParser::statusLogName() { return std::string("running"); }

/// @returns the name of the log that contains all of the periods
const std::string LogParser::periodsLogName() { return std::string("periods"); }

/**  Reads in log data from a log file and stores them in a TimeSeriesProperty.
@param logFName :: The name of the log file
@param name :: The name of the property
@return A pointer to the created property.
*/
Kernel::Property *LogParser::createLogProperty(const std::string &logFName,
                                               const std::string &name) {
  std::ifstream file(logFName.c_str());
  if (!file) {
    g_log.warning() << "Cannot open log file " << logFName << "\n";
    return 0;
  }

  // Change times and new values read from file
  std::multimap<std::string, std::string> change_times;

  // Read in the data and determin if it is numeric
  std::string str, old_data;
  bool isNumeric(false);
  std::string stime, sdata;
  // MG 22/09/09: If the log file was written on a Windows machine and then read
  // on a Linux machine, std::getline will
  // leave CR at the end of the string and this causes problems when reading out
  // the log values. Mantid::extractTOEOL
  // extracts all EOL characters
  while (Mantid::Kernel::Strings::extractToEOL(file, str)) {
    if (str.empty() || str[0] == '#') {
      continue;
    }

    if (!Kernel::TimeSeriesProperty<double>::isTimeString(str)) {
      // if the line doesn't start with a time treat it as a continuation of the
      // previous data
      if (change_times.empty() || isNumeric) { // if there are no previous data
        std::string mess =
            "Cannot parse log file " + logFName + ". Line:" + str;
        g_log.error(mess);
        throw std::logic_error(mess);
      }
      auto range = change_times.equal_range(stime);
      if (range.first != range.second) {
        auto last = range.first;
        for (auto it = last; it != range.second; ++it) {
          last = it;
        }
        last->second += std::string(" ") + str;
        old_data = last->second;
        continue;
      }
    }
    stime = str.substr(0, 19);
    sdata = str.substr(19);

    if (sdata == old_data)
      continue; // looking for a change in the data

    // check if the data is numeric
    std::istringstream istr(sdata);
    double tmp;
    istr >> tmp;
    isNumeric = !istr.fail();
    old_data = sdata;

    change_times.insert(std::make_pair(stime, sdata));
  }

  if (change_times.empty())
    return 0;

  if (isNumeric) {
    Kernel::TimeSeriesProperty<double> *logv =
        new Kernel::TimeSeriesProperty<double>(name);
    auto it = change_times.begin();
    for (; it != change_times.end(); ++it) {
      std::istringstream istr(it->second);
      double d;
      istr >> d;
      logv->addValue(it->first, d);
    }
    return logv;
  } else {
    Kernel::TimeSeriesProperty<std::string> *logv =
        new Kernel::TimeSeriesProperty<std::string>(name);
    auto it = change_times.begin();
    for (; it != change_times.end(); ++it) {
      logv->addValue(it->first, it->second);
    }
    return logv;
  }
  return 0;
}

/**
 * Destructor
 */
LogParser::~LogParser() {}

/**
Common creational method for generating a command map.
Better ensures that the same command mapping is available for any constructor.
@param newStyle Command style selector.
@return fully constructed command map.
*/
LogParser::CommandMap LogParser::createCommandMap(bool newStyle) const {
  CommandMap command_map;

  if (newStyle) {
    command_map["START_COLLECTION"] = BEGIN;
    command_map["STOP_COLLECTION"] = END;
    command_map["CHANGE"] = CHANGE_PERIOD;
    command_map["CHANGE_PERIOD"] = CHANGE_PERIOD;
  } else {
    command_map["BEGIN"] = BEGIN;
    command_map["RESUME"] = BEGIN;
    command_map["END_SE_WAIT"] = BEGIN;
    command_map["PAUSE"] = END;
    command_map["END"] = END;
    command_map["ABORT"] = END;
    command_map["UPDATE"] = END;
    command_map["START_SE_WAIT"] = END;
    command_map["CHANGE"] = CHANGE_PERIOD;
    command_map["CHANGE_PERIOD"] = CHANGE_PERIOD;
  }
  return command_map;
}

/**
Try to pass the periods
@param scom : The command corresponding to a change in period.
@param time : The time
@param idata : stream of input data
@param periods : periods data to update
*/
void LogParser::tryParsePeriod(const std::string &scom, const DateAndTime &time,
                               std::istringstream &idata,
                               Kernel::TimeSeriesProperty<int> *const periods) {
  int ip = -1;
  bool shouldAddPeriod = false;
  // Handle the version where log flag is CHANGE PERIOD
  if (scom == "CHANGE") {
    std::string s;
    idata >> s >> ip;
    if (ip > 0 && s == "PERIOD") {
      shouldAddPeriod = true;
    }
  }
  // Handle the version where log flat is CHANGE_PERIOD
  else if (scom == "CHANGE_PERIOD") {
    idata >> ip;
    if (ip > 0) {
      shouldAddPeriod = true;
    }
  }
  // Common for either variant of the log flag.
  if (shouldAddPeriod) {
    if (ip > m_nOfPeriods) {
      m_nOfPeriods = ip;
    }
    periods->addValue(time, ip);
  }
}

/** Create given the icpevent log property.
*  @param log :: A pointer to the property
*/
LogParser::LogParser(const Kernel::Property *log) : m_nOfPeriods(1) {
  Kernel::TimeSeriesProperty<int> *periods =
      new Kernel::TimeSeriesProperty<int>(periodsLogName());
  Kernel::TimeSeriesProperty<bool> *status =
      new Kernel::TimeSeriesProperty<bool>(statusLogName());
  m_periods.reset(periods);
  m_status.reset(status);

  const Kernel::TimeSeriesProperty<std::string> *icpLog =
      dynamic_cast<const Kernel::TimeSeriesProperty<std::string> *>(log);
  if (!icpLog || icpLog->size() == 0) {
    periods->addValue(Kernel::DateAndTime(), 1);
    status->addValue(Kernel::DateAndTime(), true);
    g_log.warning()
        << "Cannot process ICPevent log. Period 1 assumed for all data.\n";
    return;
  }

  std::multimap<Kernel::DateAndTime, std::string> logm =
      icpLog->valueAsMultiMap();
  CommandMap command_map =
      createCommandMap(LogParser::isICPEventLogNewStyle(logm));

  m_nOfPeriods = 1;

  auto it = logm.begin();

  for (; it != logm.end(); ++it) {
    std::string scom;
    std::istringstream idata(it->second);
    idata >> scom;
    commands com = command_map[scom];
    if (com == CHANGE_PERIOD) {
      tryParsePeriod(scom, it->first, idata, periods);
    } else if (com == BEGIN) {
      status->addValue(it->first, true);
    } else if (com == END) {
      status->addValue(it->first, false);
    }
  };

  if (periods->size() == 0)
    periods->addValue(icpLog->firstTime(), 1);
  if (status->size() == 0)
    status->addValue(icpLog->firstTime(), true);
}

/** Creates a TimeSeriesProperty<bool> showing times when a particular period
 * was active.
 *  @param period :: The data period
 *  @return times requested period was active
 */
Kernel::TimeSeriesProperty<bool> *LogParser::createPeriodLog(int period) const {
  Kernel::TimeSeriesProperty<int> *periods =
      dynamic_cast<Kernel::TimeSeriesProperty<int> *>(m_periods.get());
  if (!periods) {
    throw std::logic_error("Failed to cast periods to TimeSeriesProperty");
  }
  std::ostringstream ostr;
  ostr << period;
  Kernel::TimeSeriesProperty<bool> *p =
      new Kernel::TimeSeriesProperty<bool>("period " + ostr.str());
  std::map<Kernel::DateAndTime, int> pMap = periods->valueAsMap();
  auto it = pMap.begin();
  if (it->second != period)
    p->addValue(it->first, false);
  for (; it != pMap.end(); ++it)
    p->addValue(it->first, (it->second == period));

  return p;
}

/**
 * Create a log vale for the current period.
 * @param period: The period number to create the log entry for.
*/
Kernel::Property *LogParser::createCurrentPeriodLog(const int &period) const {
  Kernel::PropertyWithValue<int> *currentPeriodProperty =
      new Kernel::PropertyWithValue<int>("current_period", period);
  return currentPeriodProperty;
}

/// Ctreates a TimeSeriesProperty<int> with all data periods
Kernel::Property *LogParser::createAllPeriodsLog() const {
  return m_periods->clone();
}

/// Creates a TimeSeriesProperty<bool> with running status
Kernel::TimeSeriesProperty<bool> *LogParser::createRunningLog() const {
  return m_status->clone();
}

namespace {
/// Define operator for checking for new-style icp events
struct hasNewStyleCommands {
  bool
  operator()(const std::pair<Mantid::Kernel::DateAndTime, std::string> &p) {
    return p.second.find(START_COLLECTION) != std::string::npos ||
           p.second.find(STOP_COLLECTION) != std::string::npos;
  }
};
}

/**
 * Check if the icp log commands are in the new style. The new style is the one
 * that uses START_COLLECTION and STOP_COLLECTION commands for changing periods
 * and running status.
 * @param logm :: A log map created from a icp-event log.
 */
bool LogParser::isICPEventLogNewStyle(
    const std::multimap<Kernel::DateAndTime, std::string> &logm) {
  hasNewStyleCommands checker;

  return std::find_if(logm.begin(), logm.end(), checker) != logm.end();
}

/**
 * Returns the time-weighted mean value if the property is
 * TimeSeriesProperty<double>.
 *
 * TODO: Make this more efficient.
 *
 * @param p :: Property with the data. Will throw if not
 *             TimeSeriesProperty<double>.
 * @return The mean value over time.
 * @throw runtime_error if the property is not TimeSeriesProperty<double>
*/
double timeMean(const Kernel::Property *p) {
  const Kernel::TimeSeriesProperty<double> *dp =
      dynamic_cast<const Kernel::TimeSeriesProperty<double> *>(p);
  if (!dp) {
    throw std::runtime_error("Property of a wrong type. Cannot be cast to a "
                             "TimeSeriesProperty<double>.");
  }

  // Special case for only one value - the algorithm
  if (dp->size() == 1) {
    return dp->nthValue(1);
  }
  double res = 0.;
  Kernel::time_duration total(0, 0, 0, 0);
  size_t dp_size = dp->size();
  for (size_t i = 0; i < dp_size; i++) {
    Kernel::TimeInterval t = dp->nthInterval(static_cast<int>(i));
    Kernel::time_duration dt = t.length();
    total += dt;
    res += dp->nthValue(static_cast<int>(i)) *
           Kernel::DateAndTime::secondsFromDuration(dt);
  }

  double total_seconds = Kernel::DateAndTime::secondsFromDuration(total);

  // If all the time stamps were the same, just return the first value.
  if (total_seconds == 0.0)
    res = dp->nthValue(1);

  if (total_seconds > 0)
    res /= total_seconds;

  return res;
}

} // namespace Geometry
} // namespace Mantid