Commit 66d24a59 authored by Alex Buts's avatar Alex Buts
Browse files

refs #5871 Tests and final separation of Run

into Run and LogManager
parent 0a971db7
......@@ -60,9 +60,7 @@ namespace Mantid
LogManager(const LogManager& copy);
/// Assignment operator
const LogManager& operator=(const LogManager& rhs);
/// Addition
LogManager& operator+=(const LogManager& rhs);
//-------------------------------------------------------------
/// Set the run start and end
......@@ -74,14 +72,14 @@ namespace Mantid
//-------------------------------------------------------------
/// Filter the logs by time
void filterByTime(const Kernel::DateAndTime start, const Kernel::DateAndTime stop);
virtual void filterByTime(const Kernel::DateAndTime start, const Kernel::DateAndTime stop);
/// Split the logs based on the given intervals
void splitByTime(Kernel::TimeSplitterType& splitter, std::vector< LogManager* > outputs) const;
virtual void splitByTime(Kernel::TimeSplitterType& splitter, std::vector< LogManager* > outputs) const;
/// Filter the run by the given boolean log
void filterByLog(const Kernel::TimeSeriesProperty<bool> & filter);
/// Return an approximate memory size for the object in bytes
size_t getMemorySize() const;
virtual size_t getMemorySize() const;
/// Add data to the object in the form of a property
void addProperty(Kernel::Property *prop, bool overwrite = false);
......@@ -113,14 +111,7 @@ namespace Mantid
/// Returns the named property as a pointer
Kernel::Property * getProperty(const std::string & name) const;
/// Set the proton charge
void setProtonCharge( const double charge);
/// Get the proton charge
double getProtonCharge() const;
/// Integrate the proton charge over the whole run time
double integrateProtonCharge();
/**
* Add a log entry
* @param p :: A pointer to the property containing the log entry
......@@ -154,9 +145,9 @@ namespace Mantid
void clearTimeSeriesLogs();
/// Save the run to a NeXus file with a given group name
void saveNexus(::NeXus::File * file, const std::string & group) const;
virtual void saveNexus(::NeXus::File * file, const std::string & group,bool keepOpen=false) const;
/// Load the run from a NeXus file with a given group name
void loadNexus(::NeXus::File * file, const std::string & group);
virtual void loadNexus(::NeXus::File * file, const std::string & group,bool keepOpen=false);
protected:
/// Static reference to the logger class
......@@ -165,10 +156,8 @@ namespace Mantid
Kernel::PropertyManager m_manager;
/// Name of the log entry containing the proton charge when retrieved using getProtonCharge
static const char * PROTON_CHARGE_LOG_NAME;
private:
/// Adds all the time series in from one property manager into another
void mergeMergables(Mantid::Kernel::PropertyManager & sum, const Mantid::Kernel::PropertyManager & toAdd);
/// Cache type for single value logs
private:
/// Cache type for single value logs
typedef Kernel::Cache<std::pair<std::string,Kernel::Math::StatisticType>, double> SingleValueCache;
/// Cache for the retrieved single values
mutable SingleValueCache m_singleValueCache;
......
......@@ -54,6 +54,9 @@ namespace Mantid
Run(const Run& copy);
/// Assignment operator
const Run& operator=(const Run& rhs);
/// Addition
Run& operator+=(const Run& rhs);
/// Filter the logs by time
void filterByTime(const Kernel::DateAndTime start, const Kernel::DateAndTime stop);
......@@ -101,6 +104,9 @@ namespace Mantid
/// A set of histograms that can be stored here for future reference
std::vector<double> m_histoBins;
/// Adds all the time series in from one property manager into another
void mergeMergables(Mantid::Kernel::PropertyManager & sum, const Mantid::Kernel::PropertyManager & toAdd);
};
}
......
......@@ -20,13 +20,6 @@ namespace API
{
using namespace Kernel;
namespace
{
/// The number of log entries summed when adding a run
const int ADDABLES = 6;
/// The names of the log entries summed when adding two runs together
const std::string ADDABLE[ADDABLES] = {"tot_prtn_chrg", "rawfrm", "goodfrm", "dur", "gd_prtn_chrg", "uA.hour"};
}
// Get a reference to the logger
Kernel::Logger& LogManager::g_log = Kernel::Logger::get("LogManager");
......@@ -75,40 +68,6 @@ Kernel::Logger& LogManager::g_log = Kernel::Logger::get("LogManager");
}
//-----------------------------------------------------------------------------------------------
/**
* Adds just the properties that are safe to add. All time series are
* merged together and the list of addable properties are added
* @param rhs The object that is being added to this.
* @returns A reference to the summed object
*/
LogManager& LogManager::operator+=(const LogManager& rhs)
{
//merge and copy properties where there is no risk of corrupting data
mergeMergables(m_manager, rhs.m_manager);
// Other properties are added to gether if they are on the approved list
for(int i = 0; i < ADDABLES; ++i )
{
if (rhs.m_manager.existsProperty(ADDABLE[i]))
{
// get a pointer to the property on the right-handside workspace
Property * right = rhs.m_manager.getProperty(ADDABLE[i]);
// now deal with the left-handside
if (m_manager.existsProperty(ADDABLE[i]))
{
Property * left = m_manager.getProperty(ADDABLE[i]);
left->operator+=(right);
}
else
//no property on the left-handside, create one and copy the right-handside across verbatum
m_manager.declareProperty(right->clone(), "");
}
}
return *this;
}
/**
* Set the run start and end
* @param start :: The run start
......@@ -395,23 +354,13 @@ Kernel::Logger& LogManager::g_log = Kernel::Logger::get("LogManager");
/** Save the object to an open NeXus file.
* @param file :: open NeXus file
* @param group :: name of the group to create
* @param keepOpen :: do not close group on exit to allow overloading and child classes writing to the same group
*/
void LogManager::saveNexus(::NeXus::File * file, const std::string & group) const
void LogManager::saveNexus(::NeXus::File * file, const std::string & group,bool keepOpen) const
{
file->makeGroup(group, "NXgroup", 1);
file->putAttr("version", 1);
//// Now the goniometer
//m_goniometer.saveNexus(file, GONIOMETER_LOG_NAME);
//// Now the histogram bins, if there are any
//if(!m_histoBins.empty())
//{
// file->makeGroup(HISTO_BINS_LOG_NAME, "NXdata", 1);
// file->writeData("value", m_histoBins);
// file->closeGroup();
//}
// Save all the properties as NXlog
std::vector<Property *> props = m_manager.getProperties();
for (size_t i=0; i<props.size(); i++)
......@@ -425,16 +374,17 @@ Kernel::Logger& LogManager::g_log = Kernel::Logger::get("LogManager");
g_log.warning(exc.what());
}
}
file->closeGroup();
if(!keepOpen)file->closeGroup();
}
//--------------------------------------------------------------------------------------------
/** Load the object from an open NeXus file.
* @param file :: open NeXus file
* @param group :: name of the group to open. Empty string to NOT open a group, but
* @param keepOpen :: do not close group on exit to allow overloading and child classes reading from the same group
* load any NXlog in the current open group.
*/
void LogManager::loadNexus(::NeXus::File * file, const std::string & group)
void LogManager::loadNexus(::NeXus::File * file, const std::string & group,bool keepOpen)
{
if (!group.empty()) file->openGroup(group, "NXgroup");
......@@ -458,52 +408,14 @@ Kernel::Logger& LogManager::g_log = Kernel::Logger::get("LogManager");
}
}
}
if (!group.empty()) file->closeGroup();
//if( this->hasProperty("proton_charge") )
//{
// // Old files may have a proton_charge field, single value.
// // Modern files (e.g. SNS) have a proton_charge TimeSeriesProperty.
// PropertyWithValue<double> *charge_log = dynamic_cast<PropertyWithValue<double>*>(this->getProperty("proton_charge"));
// if (charge_log)
// { this->setProtonCharge(boost::lexical_cast<double>(charge_log->value()));
// }
//}
if (!(group.empty()||keepOpen))file->closeGroup();
}
//-----------------------------------------------------------------------------------------------------------------------
// Private methods
//-----------------------------------------------------------------------------------------------------------------------
/** Adds all the time series in the second property manager to those in the first
* @param sum the properties to add to
* @param toAdd the properties to add
*/
void LogManager::mergeMergables(Mantid::Kernel::PropertyManager & sum, const Mantid::Kernel::PropertyManager & toAdd)
{
// get pointers to all the properties on the right-handside and prepare to loop through them
const std::vector<Property*> inc = toAdd.getProperties();
std::vector<Property*>::const_iterator end = inc.end();
for (std::vector<Property*>::const_iterator it=inc.begin(); it != end;++it)
{
const std::string rhs_name = (*it)->name();
try
{
//now get pointers to the same properties on the left-handside
Property * lhs_prop(sum.getProperty(rhs_name));
lhs_prop->merge(*it);
}
catch (Exception::NotFoundError &)
{
//copy any properties that aren't already on the left hand side
Property * copy = (*it)->clone();
//And we add a copy of that property to *this
sum.declareProperty(copy, "");
}
}
}
/// @cond
......
......@@ -23,6 +23,10 @@ using namespace Kernel;
namespace
{
/// The number of log entries summed when adding a run
const int ADDABLES = 6;
/// The names of the log entries summed when adding two runs together
const std::string ADDABLE[ADDABLES] = {"tot_prtn_chrg", "rawfrm", "goodfrm", "dur", "gd_prtn_chrg", "uA.hour"};
/// Name of the goniometer log when saved to a NeXus file
const char * GONIOMETER_LOG_NAME = "goniometer";
/// Name of the stored histogram bins log when saved to NeXus
......@@ -89,6 +93,39 @@ Kernel::Logger& Run::g_log = Kernel::Logger::get("Run");
this->integrateProtonCharge();
}
/**
* Adds just the properties that are safe to add. All time series are
* merged together and the list of addable properties are added
* @param rhs The object that is being added to this.
* @returns A reference to the summed object
*/
Run& Run::operator+=(const Run& rhs)
{
//merge and copy properties where there is no risk of corrupting data
mergeMergables(m_manager, rhs.m_manager);
// Other properties are added together if they are on the approved list
for(int i = 0; i < ADDABLES; ++i )
{
if (rhs.m_manager.existsProperty(ADDABLE[i]))
{
// get a pointer to the property on the right-handside workspace
Property * right = rhs.m_manager.getProperty(ADDABLE[i]);
// now deal with the left-handside
if (m_manager.existsProperty(ADDABLE[i]))
{
Property * left = m_manager.getProperty(ADDABLE[i]);
left->operator+=(right);
}
else
//no property on the left-handside, create one and copy the right-handside across verbatum
m_manager.declareProperty(right->clone(), "");
}
}
return *this;
}
//-----------------------------------------------------------------------------------------------
/**
......@@ -302,13 +339,12 @@ Kernel::Logger& Run::g_log = Kernel::Logger::get("Run");
*/
void Run::saveNexus(::NeXus::File * file, const std::string & group) const
{
file->makeGroup(group, "NXgroup", 1);
file->putAttr("version", 1);
LogManager::saveNexus(file,group,true);
// Now the goniometer
// write the goniometer
m_goniometer.saveNexus(file, GONIOMETER_LOG_NAME);
// Now the histogram bins, if there are any
// write the histogram bins, if there are any
if(!m_histoBins.empty())
{
file->makeGroup(HISTO_BINS_LOG_NAME, "NXdata", 1);
......@@ -316,19 +352,6 @@ Kernel::Logger& Run::g_log = Kernel::Logger::get("Run");
file->closeGroup();
}
// Save all the properties as NXlog
std::vector<Property *> props = m_manager.getProperties();
for (size_t i=0; i<props.size(); i++)
{
try
{
PropertyNexus::saveProperty(file, props[i]);
}
catch(std::invalid_argument &exc)
{
g_log.warning(exc.what());
}
}
file->closeGroup();
}
......@@ -340,7 +363,7 @@ Kernel::Logger& Run::g_log = Kernel::Logger::get("Run");
*/
void Run::loadNexus(::NeXus::File * file, const std::string & group)
{
if (!group.empty()) file->openGroup(group, "NXgroup");
LogManager::loadNexus(file,group,true);
std::map<std::string, std::string> entries;
file->getEntries(entries);
......@@ -350,18 +373,7 @@ Kernel::Logger& Run::g_log = Kernel::Logger::get("Run");
{
// Get the name/class pair
const std::pair<std::string, std::string> & name_class = *it;
// NXLog types are the main one.
if (name_class.second == "NXlog")
{
Property * prop = PropertyNexus::loadProperty(file, name_class.first);
if (prop)
{
if (m_manager.existsProperty(prop->name() ))
m_manager.removeProperty(prop->name() );
m_manager.declareProperty(prop);
}
}
else if (name_class.second == "NXpositioner")
if (name_class.second == "NXpositioner")
{
// Goniometer class
m_goniometer.loadNexus(file, name_class.first);
......@@ -372,7 +384,7 @@ Kernel::Logger& Run::g_log = Kernel::Logger::get("Run");
file->readData("value",m_histoBins);
file->closeGroup();
}
else if (name_class.first == "proton_charge")
else if (name_class.first == "proton_charge" && !this->hasProperty("proton_charge"))
{
// Old files may have a proton_charge field, single value (not even NXlog)
double charge;
......@@ -410,6 +422,33 @@ Kernel::Logger& Run::g_log = Kernel::Logger::get("Run");
}
}
/** Adds all the time series in the second property manager to those in the first
* @param sum the properties to add to
* @param toAdd the properties to add
*/
void Run::mergeMergables(Mantid::Kernel::PropertyManager & sum, const Mantid::Kernel::PropertyManager & toAdd)
{
// get pointers to all the properties on the right-handside and prepare to loop through them
const std::vector<Property*> inc = toAdd.getProperties();
std::vector<Property*>::const_iterator end = inc.end();
for (std::vector<Property*>::const_iterator it=inc.begin(); it != end;++it)
{
const std::string rhs_name = (*it)->name();
try
{
//now get pointers to the same properties on the left-handside
Property * lhs_prop(sum.getProperty(rhs_name));
lhs_prop->merge(*it);
}
catch (Exception::NotFoundError &)
{
//copy any properties that aren't already on the left hand side
Property * copy = (*it)->clone();
//And we add a copy of that property to *this
sum.declareProperty(copy, "");
}
}
}
} //API namespace
......
#ifndef LOG_MANAGER_TEST_H_
#define LOG_MANAGER_TEST_H_
#include "MantidAPI/LogManager.h"
#include "MantidKernel/Exception.h"
#include "MantidKernel/Matrix.h"
#include "MantidKernel/Property.h"
#include "MantidKernel/TimeSeriesProperty.h"
#include "MantidKernel/V3D.h"
#include <cxxtest/TestSuite.h>
#include "MantidKernel/NexusTestHelper.h"
#include "MantidGeometry/Instrument/Goniometer.h"
using namespace Mantid::Kernel;
using namespace Mantid::API;
using namespace Mantid::Geometry;
using Mantid::Kernel::NexusTestHelper;
// Helper class
namespace
{
class ConcreteProperty : public Property
{
public:
ConcreteProperty() : Property( "Test", typeid( int ) ) {}
ConcreteProperty* clone() const { return new ConcreteProperty(*this); }
bool isDefault() const { return true; }
std::string getDefault() const { return "getDefault() is not implemented in this class"; }
std::string value() const { return "Nothing"; }
std::string setValue( const std::string& ) { return ""; }
std::string setValueFromProperty( const Property& ) { return ""; }
std::string setDataItem(const boost::shared_ptr<DataItem>) { return ""; }
Property& operator+=( Property const * ) { return *this; }
};
void addTestTimeSeries(LogManager & run, const std::string & name)
{
auto timeSeries = new TimeSeriesProperty<double>(name);
timeSeries->addValue("2012-07-19T16:17:00",2);
timeSeries->addValue("2012-07-19T16:17:10",3);
timeSeries->addValue("2012-07-19T16:17:20",4);
timeSeries->addValue("2012-07-19T16:17:30",5);
timeSeries->addValue("2012-07-19T16:17:40",6);
timeSeries->addValue("2012-07-19T16:17:50",20);
timeSeries->addValue("2012-07-19T16:18:00",21);
timeSeries->addValue("2012-07-19T16:18:10",22);
timeSeries->addValue("2012-07-19T16:19:20",23);
timeSeries->addValue("2012-07-19T16:19:20",24);
run.addProperty(timeSeries);
}
}
class LogManagerTest : public CxxTest::TestSuite
{
public:
LogManagerTest()
{}
void testAddGetData()
{
LogManager runInfo;
Property *p = new ConcreteProperty();
TS_ASSERT_THROWS_NOTHING( runInfo.addProperty(p) );
Property *pp = NULL;
TS_ASSERT_THROWS_NOTHING( pp = runInfo.getProperty("Test") );
TS_ASSERT_EQUALS( p, pp );
TS_ASSERT( ! pp->name().compare("Test") );
TS_ASSERT( dynamic_cast<ConcreteProperty*>(pp) );
TS_ASSERT_THROWS( pp = runInfo.getProperty("NotThere"), Exception::NotFoundError );
std::vector< Property* > props = runInfo.getProperties();
TS_ASSERT( ! props.empty() );
TS_ASSERT_EQUALS( props.size(), 1 );
TS_ASSERT( ! props[0]->name().compare("Test") );
TS_ASSERT( dynamic_cast<ConcreteProperty*>(props[0]) );
}
void testRemoveLogData()
{
LogManager runInfo;
Property *p = new ConcreteProperty();
TS_ASSERT_THROWS_NOTHING( runInfo.addProperty(p) );
TS_ASSERT_THROWS_NOTHING( runInfo.removeProperty("Test") );
TS_ASSERT_EQUALS( runInfo.getProperties().size(), 0 );
}
void testMemory()
{
LogManager runInfo;
TS_ASSERT_EQUALS( runInfo.getMemorySize(), 0);
Property *p = new ConcreteProperty();
runInfo.addProperty(p);
TS_ASSERT_EQUALS( runInfo.getMemorySize(), sizeof(ConcreteProperty) + sizeof( void *));
}
void test_GetTimeSeriesProperty_Returns_TSP_When_Log_Exists()
{
LogManager runInfo;
const std::string & name = "double_time_series";
const double value = 10.9;
addTimeSeriesEntry(runInfo, name, value);
TimeSeriesProperty<double> * tsp(NULL);
TS_ASSERT_THROWS_NOTHING(tsp = runInfo.getTimeSeriesProperty<double>(name));
TS_ASSERT_DELTA(tsp->firstValue(), value, 1e-12);
}
void test_GetTimeSeriesProperty_Throws_When_Log_Does_Not_Exist()
{
LogManager runInfo;
TS_ASSERT_THROWS(runInfo.getTimeSeriesProperty<double>("not_a_log"), Exception::NotFoundError);
}
void test_GetTimeSeriesProperty_Throws_When_Log_Exists_But_Is_Not_Correct_Type()
{
LogManager runInfo;
const std::string & name = "double_prop";
runInfo.addProperty(name, 5.6); // Standard double property
TS_ASSERT_THROWS(runInfo.getTimeSeriesProperty<double>(name), std::invalid_argument);
}
void test_GetPropertyAsType_Throws_When_Property_Does_Not_Exist()
{
LogManager runInfo;
TS_ASSERT_THROWS(runInfo.getPropertyValueAsType<double>("not_a_log"), Exception::NotFoundError);
}
void test_GetPropertyAsType_Returns_Expected_Value_When_Type_Is_Correct()
{
LogManager runInfo;
const std::string & name = "double_prop";
const double value = 5.6;
runInfo.addProperty(name, value); // Standard double property
double retrieved(0.0);
TS_ASSERT_THROWS_NOTHING(retrieved = runInfo.getPropertyValueAsType<double>(name));
TS_ASSERT_DELTA(retrieved, value, 1e-12);
}
void test_GetPropertyAsType_Throws_When_Requested_Type_Does_Not_Match()
{
LogManager runInfo;
runInfo.addProperty("double_prop", 6.7); // Standard double property
TS_ASSERT_THROWS(runInfo.getPropertyValueAsType<int>("double_prop"), std::invalid_argument);
}
void test_GetPropertyAsSingleValue_Throws_If_Type_Is_Not_Double_Or_TimeSeries_Double()
{
LogManager runInfo;
const std::string name = "int_prop";
runInfo.addProperty(name, 1); // Adds an int property
TS_ASSERT_THROWS(runInfo.getPropertyAsSingleValue(name), std::invalid_argument);
}
void test_GetPropertyAsSingleValue_Throws_If_StatisticType_Is_Unknown_And_Type_Is_TimeSeries()
{
LogManager runInfo;
const std::string name = "series";
addTestTimeSeries(runInfo, name);
const unsigned int statistic(100);
TS_ASSERT_THROWS(runInfo.getPropertyAsSingleValue(name, (Math::StatisticType)statistic), std::invalid_argument);
}
void test_GetPropertyAsSingleValue_Returns_Simple_Mean_By_Default_For_Time_Series()
{
LogManager runInfo;
const std::string name = "series";
addTestTimeSeries(runInfo, name);
const double expectedValue(13.0);
TS_ASSERT_DELTA(runInfo.getPropertyAsSingleValue(name), expectedValue, 1e-12);
}
void test_GetPropertyAsSingleValue_Returns_Correct_SingleValue_For_Each_StatisticType()
{
LogManager runInfo;
const std::string name = "series";
addTestTimeSeries(runInfo, name);
TS_ASSERT_DELTA(runInfo.getPropertyAsSingleValue(name, Math::Mean), 13.0, 1e-12);
TS_ASSERT_DELTA(runInfo.getPropertyAsSingleValue(name, Math::Minimum), 2.0, 1e-12);
TS_ASSERT_DELTA(runInfo.getPropertyAsSingleValue(name, Math::Maximum), 24.0, 1e-12);
TS_ASSERT_DELTA(runInfo.getPropertyAsSingleValue(name, Math::FirstValue), 2.0, 1e-12);
TS_ASSERT_DELTA(runInfo.getPropertyAsSingleValue(name, Math::LastValue), 24.0, 1e-12);
TS_ASSERT_DELTA(runInfo.getPropertyAsSingleValue(name, Math::Median), 13.0, 1e-12);
}