16#include "MantidNexus/NexusFile.h"
18#include <boost/lexical_cast.hpp>
27using namespace Kernel;
28using Mantid::Types::Core::DateAndTime;
32Kernel::Logger
g_log(
"Run");
34auto addPeriodSeries = [](Property *
left,
const Property *
right) {
35 auto *leftAP =
dynamic_cast<PropertyWithValue<std::vector<double>
> *>(
left);
36 const auto *rightAP =
dynamic_cast<const PropertyWithValue<std::vector<double>
> *>(
right);
37 if (!leftAP || !rightAP) {
38 throw std::runtime_error(
"Expected ArrayProperty<double> for both inputs.");
40 const auto &leftVec = leftAP->operator()();
41 const auto &rightVec = rightAP->operator()();
42 if (leftVec.size() != rightVec.size()) {
44 "Summing runs with differing number of periods. Proton charge logs will retain values from LHS operand.");
47 std::vector<double> resVec;
48 resVec.reserve(leftVec.size());
49 for (
size_t i = 0; i < leftVec.size(); i++) {
50 resVec.push_back(leftVec[i] + rightVec[i]);
56struct addableProperty {
58 using func = std::function<void(Property *,
const Property *)>;
59 addableProperty(
const std::string &name, func opFunc_in =
nullptr) :
name(
name),
opFunc(
std::move(opFunc_in)) {};
65const std::vector<addableProperty> ADDABLE{addableProperty(
"tot_prtn_chrg"),
66 addableProperty(
"rawfrm"),
67 addableProperty(
"goodfrm"),
68 addableProperty(
"dur"),
69 addableProperty(
"gd_prtn_chrg"),
70 addableProperty(
"uA.hour"),
71 addableProperty(
"monitor0_counts"),
72 addableProperty(
"monitor1_counts"),
73 addableProperty(
"monitor2_counts"),
74 addableProperty(
"monitor3_counts"),
75 addableProperty(
"monitor4_counts"),
76 addableProperty(
"monitor5_counts"),
77 addableProperty(
"proton_charge_by_period", addPeriodSeries)};
80const char *GONIOMETER_LOG_NAME =
"goniometer";
81const char *GONIOMETERS_LOG_NAME =
"goniometers";
83const char *HISTO_BINS_LOG_NAME =
"processed_histogram_bins";
84const char *PEAK_RADIUS_GROUP =
"peak_radius";
85const char *INNER_BKG_RADIUS_GROUP =
"inner_bkg_radius";
86const char *OUTER_BKG_RADIUS_GROUP =
"outer_bkg_radius";
91 m_goniometers.push_back(std::make_unique<Geometry::Goniometer>());
121 auto clone = std::make_shared<Run>();
122 for (
auto property : this->
m_manager->getProperties()) {
123 clone->addProperty(property->clone());
125 clone->copyGoniometers(
const_cast<Run &
>(*
this));
141void Run::filterByTime(
const Types::Core::DateAndTime start,
const Types::Core::DateAndTime stop) {
148void findAndConcatenateTimeStrProp(
const Run *runObjLHS,
const Run *runObjRHS,
const std::string &firstSuggestion,
149 const std::string &secondSuggestion, std::string &propName, std::string &propValue) {
152 std::string rhsValue;
154 propName = firstSuggestion;
156 }
else if (runObjRHS->
hasProperty(secondSuggestion)) {
157 propName = secondSuggestion;
162 std::string lhsValue;
164 propName = firstSuggestion;
166 }
else if (runObjLHS->
hasProperty(secondSuggestion)) {
167 propName = secondSuggestion;
171 if (lhsValue.empty()) {
172 propValue = rhsValue;
173 }
else if (rhsValue.empty()) {
174 propValue = lhsValue;
176 if (firstSuggestion ==
"start_time") {
179 auto value = std::min(DateAndTime(lhsValue), DateAndTime(rhsValue));
180 propValue =
value.toISO8601String();
181 }
catch (std::invalid_argument &) {
182 propValue = lhsValue + rhsValue;
187 auto value = std::max(DateAndTime(lhsValue), DateAndTime(rhsValue));
188 propValue =
value.toISO8601String();
189 }
catch (std::invalid_argument &) {
190 propValue = lhsValue + rhsValue;
206 if ((!
m_timeroi->useAll()) || (!
rhs.m_timeroi->useAll())) {
215 if (rightROI.
useAll() &&
rhs.hasStartTime() &&
rhs.hasEndTime()) {
225 std::string startTimePropName;
226 std::string startTimePropValue;
227 std::string endTimePropName;
228 std::string endTimePropValue;
229 findAndConcatenateTimeStrProp(
this, &
rhs,
"start_time",
"start_run", startTimePropName, startTimePropValue);
230 findAndConcatenateTimeStrProp(
this, &
rhs,
"end_time",
"run_end", endTimePropName, endTimePropValue);
236 for (
const auto &addableProp : ADDABLE) {
237 const std::string
name = addableProp.name;
238 if (
rhs.m_manager->existsProperty(
name)) {
245 if (!addableProp.opFunc) {
253 m_manager->declareProperty(std::unique_ptr<Property>(
right->clone()),
"");
259 if (!startTimePropName.empty()) {
263 if (!endTimePropName.empty()) {
284 const std::string PROTON_CHARGE_UNITS(
"uA.hour");
289 charge_prop->
setValue(boost::lexical_cast<std::string>(charge));
290 charge_prop->
setUnits(PROTON_CHARGE_UNITS);
304 g_log.
notice() <<
"There is no proton charge associated with this workspace" << std::endl;
312 const std::vector<double> &protonChargeByPeriod =
m_manager->getProperty(
"proton_charge_by_period");
313 const int currentPeriod =
m_manager->getProperty(
"current_period");
340 g_log.
warning(logname +
" log was not found. The value of the total proton "
341 "charge has not been set");
353 if (
const auto *filteredLog =
360 total = std::accumulate(logValues.begin(), logValues.end(), 0.0);
364 total = std::accumulate(times.cbegin(), times.cend(), 0., [&](
double valueTotal,
const DateAndTime &time) {
365 return timeroi.valueAtTime(time) ? valueTotal + log->getSingleValue(time) : valueTotal;
369 const std::string &unit = log->
units();
371 if (unit.find(
"picoCoulomb") != std::string::npos) {
373 const double currentConversion = 1.e-6 / 3600.;
374 total *= currentConversion;
375 }
else if (!unit.empty() && unit !=
"uAh") {
376 g_log.
warning(logname +
" log has units other than uAh or "
377 "picoCoulombs. The value of the total proton charge has "
378 "been left at the sum of the log values.");
387 g_log.
warning(logname +
" log was not a time series property. The value of the total proton "
388 "charge has not been set");
401 throw std::runtime_error(
"Failed to find \"" + logname +
"\" in sample logs");
405 throw std::logic_error(
"Failed to find \"" + logname +
"\" in sample logs");
411 throw std::runtime_error(
"Maximum value of charge is not greater than zero (" + logname +
")");
415 const double min_pcharge = stats.
mean * cutoff * 0.01;
416 const double max_pcharge = stats.
maximum * 1.1;
417 if (min_pcharge >= max_pcharge) {
418 throw std::runtime_error(
"proton_charge window filters out all of the data");
420 return {min_pcharge, max_pcharge, stats.
mean};
432 double duration{
m_timeroi->durationInSeconds()};
433 const std::string NAME(
"duration");
434 const std::string UNITS(
"second");
439 prop->
setValue(boost::lexical_cast<std::string>(duration));
453 if (histoBins.size() < 2) {
454 std::ostringstream os;
455 os <<
"Run::storeEnergyBinBoundaries - Fewer than 2 values given, size=" << histoBins.size()
456 <<
". Cannot interpret values as bin boundaries.";
457 throw std::invalid_argument(os.str());
459 if (histoBins.front() >= histoBins.back()) {
460 std::ostringstream os;
461 os <<
"Run::storeEnergyBinBoundaries - Inconsistent start & end values "
463 << histoBins.size() <<
". Cannot interpret values as bin boundaries.";
464 throw std::out_of_range(os.str());
478 throw std::runtime_error(
"Run::histogramBoundaries - No energy bins have "
479 "been stored for this run");
483 std::ostringstream os;
484 os <<
"Run::histogramBinBoundaries- Value lower than first bin boundary. "
487 throw std::out_of_range(os.str());
490 std::ostringstream os;
491 os <<
"Run::histogramBinBoundaries- Value greater than last bin boundary. "
494 throw std::out_of_range(os.str());
507 throw std::runtime_error(
"Run::histogramBoundaries - No energy bins have "
508 "been stored for this run");
540 m_goniometers.emplace_back(std::make_unique<Geometry::Goniometer>(goniometer));
543 }
catch (std::runtime_error &) {
558 }
catch (std::runtime_error &) {
583 m_goniometers.emplace_back(std::make_unique<Geometry::Goniometer>(goniometer));
599 throw std::out_of_range(
"Run::getGoniometer() const: index is out of range.");
611 throw std::out_of_range(
"Run::getGoniometer() const: index is out of range.");
624 throw std::out_of_range(
"Run::getGoniometer() const: index is out of range.");
633 std::vector<Kernel::Matrix<double>> goniometers;
636 goniometers.emplace_back((*it)->getR());
654 file->makeGroup(GONIOMETERS_LOG_NAME,
"NXcollection",
true);
655 file->writeData(
"num_goniometer",
int(
m_goniometers.size()));
664 file->makeGroup(HISTO_BINS_LOG_NAME,
"NXdata",
true);
669 const std::vector<double> &values = this->getPropertyValueAsType<std::vector<double>>(
"PeakRadius");
671 file->makeGroup(PEAK_RADIUS_GROUP,
"NXdata",
true);
672 file->writeData(
"value", values);
676 file->makeGroup(INNER_BKG_RADIUS_GROUP,
"NXdata",
true);
677 const std::vector<double> &values = this->getPropertyValueAsType<std::vector<double>>(
"BackgroundInnerRadius");
678 file->writeData(
"value", values);
682 file->makeGroup(OUTER_BKG_RADIUS_GROUP,
"NXdata",
true);
683 const std::vector<double> &values = this->getPropertyValueAsType<std::vector<double>>(
"BackgroundOuterRadius");
684 file->writeData(
"value", values);
701 const std::string &prefix,
bool keepOpen) {
703 if (!
group.empty()) {
704 file->openGroup(
group,
"NXgroup");
708 const std::string absoluteGroupName = prefix +
"/" +
group;
712 const auto levels = std::count(absoluteGroupName.begin(), absoluteGroupName.end(),
'/');
716 for (
const auto &nxClassPair : allEntries) {
717 const std::set<std::string> &nxClassEntries = nxClassPair.second;
722 auto itLower = nxClassEntries.lower_bound(absoluteGroupName);
724 if (itLower == nxClassEntries.end()) {
727 if (itLower->compare(0, absoluteGroupName.size(), absoluteGroupName) != 0) {
732 for (
auto it = itLower;
733 it != nxClassEntries.end() && it->compare(0, absoluteGroupName.size(), absoluteGroupName) == 0; ++it) {
736 const std::string &absoluteEntryName = *it;
737 if (std::count(absoluteEntryName.begin(), absoluteEntryName.end(),
'/') != levels + 1) {
740 const std::string nameClass = absoluteEntryName.substr(absoluteEntryName.find_last_of(
'/') + 1);
745 if (!(
group.empty() || keepOpen))
752 this->
setProtonCharge(boost::lexical_cast<double>(charge_log->value()));
767 if (!
group.empty()) {
768 file->openGroup(
group,
"NXgroup");
770 std::map<std::string, std::string> entries;
771 file->getEntries(entries);
773 for (
const auto &name_class : entries) {
776 if (!(
group.empty() || keepOpen))
783 this->
setProtonCharge(boost::lexical_cast<double>(charge_log->value()));
796 for (
size_t i = 0; i <
m_goniometers[0]->getNumberAxes(); ++i) {
797 const std::string axisName =
m_goniometers[0]->getAxis(i).name;
799 const double minAngle = stats.minimum;
800 const double maxAngle = stats.maximum;
801 const double angle = stats.time_mean;
803 if (minAngle != maxAngle && !(std::isnan(minAngle) && std::isnan(maxAngle))) {
805 g_log.
warning(
"Goniometer angle changed in " + axisName +
" log from " +
806 boost::lexical_cast<std::string>(minAngle) +
" to " + boost::lexical_cast<std::string>(maxAngle) +
807 ". Used time averaged value = " + boost::lexical_cast<std::string>(angle) +
".");
808 if (axisName ==
"omega") {
809 g_log.
warning(
"To set to last angle, replace omega with " + boost::lexical_cast<std::string>(lastAngle) +
811 "SetGoniometer(Workspace=\'workspace\',Axis0=omega,0,1,0,"
812 "1\',Axis1='chi,0,0,1,1',Axis2='phi,0,1,0,1')");
813 }
else if (axisName ==
"chi") {
814 g_log.
warning(
"To set to last angle, replace chi with " + boost::lexical_cast<std::string>(lastAngle) +
816 "SetGoniometer(Workspace=\'workspace\',Axis0=omega,0,1,0,"
817 "1\',Axis1='chi,0,0,1,1',Axis2='phi,0,1,0,1')");
818 }
else if (axisName ==
"phi") {
819 g_log.
warning(
"To set to last angle, replace phi with " + boost::lexical_cast<std::string>(lastAngle) +
821 "SetGoniometer(Workspace=\'workspace\',Axis0=omega,0,1,0,"
822 "1\',Axis1='chi,0,0,1,1',Axis2='phi,0,1,0,1')");
835 throw std::runtime_error(
"Run::calculateGoniometerMatrices must include axes for goniometer");
837 const size_t num_log_values = getTimeSeriesProperty<double>(goniometer.
getAxis(0).
name)->size();
842 for (
size_t i = 0; i < num_log_values; ++i)
843 m_goniometers.emplace_back(std::make_unique<Geometry::Goniometer>(goniometer));
846 const auto angles = getTimeSeriesProperty<double>(goniometer.
getAxis(i).
name)->valuesAsVector();
847 if (angles.size() != num_log_values)
848 throw std::runtime_error(
"Run::calculateGoniometerMatrices different "
849 "number of log entries between axes");
851 for (
size_t j = 0; j < num_log_values; ++j) {
866 for (
auto ptr : inc) {
867 const std::string &rhs_name = ptr->name();
871 lhs_prop->
merge(ptr);
874 auto copy = std::unique_ptr<Property>(ptr->clone());
887 for (
const auto &goniometer : other.m_goniometers) {
888 auto new_goniometer = std::make_unique<Geometry::Goniometer>(*goniometer);
894 if (nameClass == GONIOMETER_LOG_NAME) {
897 }
else if (nameClass == GONIOMETERS_LOG_NAME) {
898 file->openGroup(nameClass,
"NXcollection");
900 file->readData(
"num_goniometer", num_goniometer);
903 for (
int i = 0; i < num_goniometer; i++) {
904 m_goniometers.emplace_back(std::make_unique<Geometry::Goniometer>());
908 }
else if (nameClass == HISTO_BINS_LOG_NAME) {
909 file->openGroup(nameClass,
"NXdata");
912 }
else if (nameClass == PEAK_RADIUS_GROUP) {
913 file->openGroup(nameClass,
"NXdata");
914 std::vector<double> values;
915 file->readData(
"value", values);
918 }
else if (nameClass == INNER_BKG_RADIUS_GROUP) {
919 file->openGroup(nameClass,
"NXdata");
920 std::vector<double> values;
921 file->readData(
"value", values);
923 this->
addProperty(
"BackgroundInnerRadius", values,
true);
924 }
else if (nameClass == OUTER_BKG_RADIUS_GROUP) {
925 file->openGroup(nameClass,
"NXdata");
926 std::vector<double> values;
927 file->readData(
"value", values);
929 this->
addProperty(
"BackgroundOuterRadius", values,
true);
930 }
else if (nameClass ==
"proton_charge" && !this->
hasProperty(
"proton_charge")) {
933 file->readData(
"proton_charge", charge);
935 }
else if (nameClass ==
"proton_charge_by_period") {
936 file->openGroup(nameClass,
"NXlog");
937 std::vector<double> values;
938 file->readData(
"value", values);
940 this->
addProperty(
"proton_charge_by_period", values,
true);
const std::vector< double > & rhs
double value
The value of the point.
std::map< DeltaEMode::Type, std::string > index
This class contains the information about the log entries.
virtual size_t getMemorySize() const
Return an approximate memory size for the object in bytes.
const Types::Core::DateAndTime endTime() const
Return the run end time.
virtual void setTimeROI(const Kernel::TimeROI &timeroi)
bool hasProperty(const std::string &name) const
Does the property exist on the object.
const Kernel::TimeROI & getTimeROI() const
const std::vector< Kernel::Property * > & getLogData() const
Access all log entries.
std::unique_ptr< Kernel::PropertyManager > m_manager
A pointer to a property manager.
Kernel::TimeSeriesPropertyStatistics getStatistics(const std::string &name) const
Returns various statistics computations for a given property.
const Types::Core::DateAndTime startTime() const
Return the run start time.
Kernel::Property * getProperty(const std::string &name) const
Returns the named property as a pointer.
virtual void filterByTime(const Types::Core::DateAndTime start, const Types::Core::DateAndTime stop)
Filter the logs by time.
static const std::string PROTON_CHARGE_LOG_NAME
Name of the log entry containing the proton charge when retrieved using getProtonCharge.
void addProperty(Kernel::Property *prop, bool overwrite=false)
Add data to the object in the form of a property.
virtual void saveNexus(Nexus::File *file, const std::string &group, bool keepOpen=false) const
Save the run to a NeXus file with a given group name.
LogManager & operator=(const LogManager &other)
bool operator==(const LogManager &other) const
std::unique_ptr< Kernel::TimeROI > m_timeroi
double getLogAsSingleValue(const std::string &name, Kernel::Math::StatisticType statistic=Kernel::Math::Mean) const
static const std::string PROTON_CHARGE_UNFILTERED_LOG_NAME
Flag to signify if a filter has been applied to the proton charge log.
virtual void loadNexus(Nexus::File *file, const std::string &group, const Mantid::Nexus::NexusDescriptor &fileInfo, const std::string &prefix, bool keepOpen=false)
Load the run from a NeXus file with a given group name. Overload that uses NexusDescriptor for faster...
This class stores information regarding an experimental run as a series of log entries.
std::tuple< double, double, double > getBadPulseRange(const std::string &logname="proton_charge", const double &cutoff=95.) const
determine the range of bad pulses to filter
void storeHistogramBinBoundaries(const std::vector< double > &histoBins)
Store the given values as a set of histogram bin boundaries.
bool operator!=(const Run &other)
void setProtonCharge(const double charge)
Set the proton charge.
bool operator==(const Run &other)
Run & operator+=(const Run &rhs)
Addition.
void integrateProtonCharge(const std::string &logname="proton_charge") const
Integrate the proton charge over the whole run time - default log proton_charge.
void setDuration()
update property "duration" with the duration of the Run's TimeROI attribute
void copyGoniometers(const Run &other)
Copy the goniometers from another.
std::vector< double > getBinBoundaries() const
Returns the vector of bin boundaries.
std::vector< std::unique_ptr< Geometry::Goniometer > > m_goniometers
Goniometer for this run.
size_t getNumGoniometers() const
Get the number of goniometers in the Run.
void clearGoniometers()
Clear all goniometers on the Run.
void calculateGoniometerMatrices(const Geometry::Goniometer &goniometer)
Calculate the gonoimeter matrices from logs.
void setGoniometer(const Geometry::Goniometer &goniometer, const bool useLogValues)
Set a single gonoimeter & read the average values from the logs if told to do so.
void calculateAverageGoniometerMatrix()
Calculate the average gonoimeter matrix.
Geometry::Goniometer & mutableGoniometer()
Return reference to the first non-const Goniometer object for this run.
const Geometry::Goniometer & getGoniometer() const
Return reference to the first const Goniometer object for this run.
size_t getMemorySize() const override
Return an approximate memory size for the object in bytes.
const Kernel::Matrix< double > & getGoniometerMatrix() const
Retrieve the first goniometer rotation matrix.
void filterByTime(const Types::Core::DateAndTime start, const Types::Core::DateAndTime stop) override
Filter the logs by time.
size_t addGoniometer(const Geometry::Goniometer &goniometer)
Append a goniometer to the run.
void setTimeROI(const Kernel::TimeROI &timeroi) override
std::shared_ptr< Run > clone()
Clone.
void loadNexusCommon(Nexus::File *file, const std::string &nameClass)
double getProtonCharge() const
Get the proton charge.
void saveNexus(Nexus::File *file, const std::string &group, bool keepOpen=false) const override
Save the run to a NeXus file with a given group name.
void mergeMergables(Mantid::Kernel::PropertyManager &sum, const Mantid::Kernel::PropertyManager &toAdd)
Adds all the time series in from one property manager into another.
std::pair< double, double > histogramBinBoundaries(const double value) const
Returns the bin boundaries for a given value.
void setGoniometers(const Geometry::Goniometer &goniometer)
Set the gonoimeters using the individual values.
Run & operator=(const Run &other)
const std::vector< Kernel::Matrix< double > > getGoniometerMatrices() const
Get vector of all goniometer matrices in the Run.
std::vector< double > m_histoBins
A set of histograms that can be stored here for future reference.
void loadNexus(Nexus::File *file, const std::string &group, const Mantid::Nexus::NexusDescriptor &fileInfo, const std::string &prefix, bool keepOpen=false) override
Load the run from a NeXus file with a given group name.
Class to represent a particular goniometer setting, which is described by the rotation matrix.
size_t getNumberAxes() const
const GoniometerAxis & getAxis(size_t axisnumber) const
Get GoniometerAxis obfject using motor number.
Exception for when an item is not found in a collection.
Templated class that defines a filtered time series but still gives access to the original data.
void notice(const std::string &msg)
Logs at notice level.
void warning(const std::string &msg)
Logs at warning level.
Property manager helper class.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
void declareProperty(std::unique_ptr< Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
const std::vector< Property * > & getProperties() const override
Get the list of managed properties.
The concrete, templated class for properties.
Base class for properties.
virtual const std::string & units() const
Returns the units of the property, if any, as a string.
virtual std::string setValue(const std::string &)=0
Set the value of the property via a string.
virtual void setUnits(const std::string &unit)
Sets the units of the property, as a string.
virtual Property & merge(Property *)
Just returns the property (*this) unless overridden.
virtual std::string value() const =0
Returns the value of the property as a string.
TimeROI : Object that holds information about when the time measurement was active.
void update_union(const TimeROI &other)
Updates the TimeROI values with the union with another TimeROI.
void addROI(const std::string &startTime, const std::string &stopTime)
bool useAll() const
TimeROI selects all time to be used.
void update_or_replace_intersection(const TimeROI &other)
If this is empty, replace it with the supplied TimeROI, otherwise calculate the intersection.
A specialised Property class for holding a series of time-value pairs.
std::vector< TYPE > valuesAsVector() const
Return the time series's values (unfiltered) as a vector<TYPE>
std::vector< Types::Core::DateAndTime > timesAsVector() const override
Return the time series's times as a vector<DateAndTime>
const std::map< std::string, std::set< std::string > > & getAllEntries() const noexcept
Returns a const reference of the internal map holding all entries in the Nexus HDF5 file.
Kernel::Logger g_log("ExperimentInfo")
static logger object
MANTID_KERNEL_DLL int getBinIndex(const std::vector< double > &bins, const double value)
Return the index into a vector of bin boundaries for a particular X value.
std::string to_string(const wide_integer< Bits, Signed > &n)
Struct holding some useful statistics for a TimeSeriesProperty.
double maximum
Maximum value.