Newer
Older
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
// NScD Oak Ridge National Laboratory, European Spallation Source
// & Institut Laue - Langevin
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidAPI/ExperimentInfo.h"
#include "MantidAPI/InstrumentDataService.h"
#include "MantidAPI/ResizeRectangularDetectorHelper.h"
#include "MantidAPI/Run.h"
#include "MantidAPI/Sample.h"
#include "MantidAPI/SpectrumInfo.h"
#include "MantidGeometry/Crystal/OrientedLattice.h"
#include "MantidGeometry/IComponent.h"
#include "MantidGeometry/Instrument/ComponentInfo.h"
#include "MantidGeometry/Instrument/Detector.h"
#include "MantidGeometry/Instrument/DetectorInfo.h"
#include "MantidGeometry/Instrument/InstrumentDefinitionParser.h"
#include "MantidGeometry/Instrument/ParComponentFactory.h"
#include "MantidGeometry/Instrument/ParameterFactory.h"
#include "MantidGeometry/Instrument/ParameterMap.h"
#include "MantidGeometry/Instrument/RectangularDetector.h"
#include "MantidGeometry/Instrument/XMLInstrumentParameter.h"
#include "MantidBeamline/DetectorInfo.h"
#include "MantidBeamline/SpectrumInfo.h"
Janik Zikovsky
committed
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/DateAndTime.h"
#include "MantidKernel/EigenConversionHelpers.h"
#include "MantidKernel/IPropertyManager.h"
Janik Zikovsky
committed
#include "MantidKernel/Property.h"
#include "MantidKernel/StringTokenizer.h"
#include "MantidTypes/SpectrumDefinition.h"
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/make_shared.hpp>
#include <boost/regex.hpp>
Janik Zikovsky
committed
#include <Poco/DirectoryIterator.h>
#include <Poco/Path.h>
Janik Zikovsky
committed
#include <Poco/SAX/ContentHandler.h>
#include <Poco/SAX/SAXParser.h>
#include <nexus/NeXusException.hpp>
using namespace Mantid::Geometry;
Janik Zikovsky
committed
using namespace Mantid::Kernel;
using namespace Mantid::Types::Core;
Janik Zikovsky
committed
using namespace Poco::XML;
namespace API {
namespace {
/// static logger object
Kernel::Logger g_log("ExperimentInfo");
// used to terminate SAX process
class DummyException {
public:
std::string m_validFrom;
std::string m_validTo;
DummyException(const std::string &validFrom, const std::string &validTo)
: m_validFrom(validFrom), m_validTo(validTo) {}
};
// SAX content handler for grapping stuff quickly from IDF
class myContentHandler : public Poco::XML::ContentHandler {
void startElement(const XMLString & /*uri*/, const XMLString &localName,
const XMLString & /*qname*/,
const Attributes &attrList) override {
if (localName == "instrument" || localName == "parameter-file") {
throw DummyException(
static_cast<std::string>(attrList.getValue("", "valid-from")),
static_cast<std::string>(attrList.getValue("", "valid-to")));
}
}
void endElement(const XMLString & /*uri*/, const XMLString & /*localName*/,
const XMLString & /*qname*/) override {}
void startDocument() override {}
void endDocument() override {}
void characters(const XMLChar /*ch*/[], int /*start*/,
int /*length*/) override {}
void endPrefixMapping(const XMLString & /*prefix*/) override {}
void ignorableWhitespace(const XMLChar /*ch*/[], int /*start*/,
int /*length*/) override {}
void processingInstruction(const XMLString & /*target*/,
const XMLString & /*data*/) override {}
void setDocumentLocator(const Locator * /*loc*/) override {}
void skippedEntity(const XMLString & /*name*/) override {}
void startPrefixMapping(const XMLString & /*prefix*/,
const XMLString & /*uri*/) override {}
/** Constructor
*/
ExperimentInfo::ExperimentInfo()
: m_parmap(new ParameterMap()), sptr_instrument(new Instrument()) {
m_parmap->setInstrument(sptr_instrument.get());
/**
* Constructs the object from a copy if the input. This leaves the new mutex
* unlocked.
* @param source The source object from which to initialize
*/
ExperimentInfo::ExperimentInfo(const ExperimentInfo &source) {
this->copyExperimentInfoFrom(&source);
setSpectrumDefinitions(source.spectrumInfo().sharedSpectrumDefinitions());
// Defined as default in source for forward declaration with std::unique_ptr.
ExperimentInfo::~ExperimentInfo() = default;
/** Copy the experiment info data from another ExperimentInfo instance,
* e.g. a MatrixWorkspace.
* @param other :: the source from which to copy ExperimentInfo
*/
void ExperimentInfo::copyExperimentInfoFrom(const ExperimentInfo *other) {
m_sample = other->m_sample;
this->setInstrument(other->getInstrument());
// We do not copy Beamline::SpectrumInfo (which contains detector grouping
// information) for now:
// - For MatrixWorkspace, grouping information is still stored in ISpectrum
// and should not be overridden (copy is done in ExperimentInfo ctor, but
// not here since we just copy the experiment data).
// - For cached groupings (for MDWorkspaces), grouping was not copied in the
// old implementation either.
}
/** Clone this ExperimentInfo class into a new one
*/
ExperimentInfo *ExperimentInfo::cloneExperimentInfo() const {
return new ExperimentInfo(*this);
}
/// @returns A human-readable description of the object
const std::string ExperimentInfo::toString() const {
try {
populateIfNotLoaded();
} catch (std::exception &) {
// Catch any errors so that the string returned has as much information
// as possible
}
std::ostringstream out;
Geometry::Instrument_const_sptr inst = this->getInstrument();
const auto instName = inst->getName();
out << "Instrument: ";
if (!instName.empty()) {
out << instName << " ("
<< inst->getValidFromDate().toFormattedString("%Y-%b-%d") << " to "
<< inst->getValidToDate().toFormattedString("%Y-%b-%d") << ")";
const auto instFilename = inst->getFilename();
if (!instFilename.empty()) {
out << "Instrument from: " << instFilename;
out << "\n";
}
} else {
out << "None";
out << "\n";
auto paramFileVector =
this->constInstrumentParameters().getParameterFilenames();
for (auto &itFilename : paramFileVector) {
out << "Parameters from: " << itFilename;
out << "\n";
}
std::string runStart = getAvailableWorkspaceStartDate();
std::string runEnd = getAvailableWorkspaceEndDate();
std::string msgNA = "not available";
if (runStart.empty())
runStart = msgNA;
if (runEnd.empty())
runEnd = msgNA;
out << "Run start: " << runStart << "\n";
out << "Run end: " << runEnd
<< "\n"; // note extra space for pseudo/approx-alignment
if (this->sample().hasOrientedLattice()) {
const Geometry::OrientedLattice &latt = this->sample().getOrientedLattice();
out << "Sample: a " << std::fixed << std::setprecision(1) << latt.a()
<< ", b " << latt.b() << ", c " << latt.c();
out << "; alpha " << std::fixed << std::setprecision(0) << latt.alpha()
<< ", beta " << latt.beta() << ", gamma " << latt.gamma();
out << "\n";
}
// Helpers for setInstrument and getInstrument
namespace {
void checkDetectorInfoSize(const Instrument &instr,
const Geometry::DetectorInfo &detInfo) {
const auto numDets = instr.getNumberDetectors();
if (numDets != detInfo.size())
throw std::runtime_error("ExperimentInfo: size mismatch between "
"DetectorInfo and number of detectors in "
"instrument");
}
* @param instr :: Shared pointer to an instrument.
*/
void ExperimentInfo::setInstrument(const Instrument_const_sptr &instr) {
m_spectrumInfoWrapper = nullptr;
// Detector IDs that were previously dropped because they were not part of the
// instrument may now suddenly be valid, so we have to reinitialize the
// detector grouping. Also the index corresponding to specific IDs may have
// changed.
if (sptr_instrument !=
(instr->isParametrized() ? instr->baseInstrument() : instr))
invalidateAllSpectrumDefinitions();
if (instr->isParametrized()) {
sptr_instrument = instr->baseInstrument();
// We take a *copy* of the ParameterMap since we are modifying it by setting
// a pointer to our DetectorInfo, and in case it contains legacy parameters
// such as positions or rotations.
m_parmap = boost::make_shared<ParameterMap>(*instr->getParameterMap());
} else {
sptr_instrument = instr;
m_parmap = boost::make_shared<ParameterMap>();
}
m_parmap->setInstrument(sptr_instrument.get());
}
/** Get a shared pointer to the parametrized instrument associated with this
*workspace
*
* @return The instrument class
*/
Instrument_const_sptr ExperimentInfo::getInstrument() const {
populateIfNotLoaded();
checkDetectorInfoSize(*sptr_instrument, detectorInfo());
return Geometry::ParComponentFactory::createInstrument(sptr_instrument,
m_parmap);
}
/** Returns a new copy of the instrument parameters
* @return a (new) copy of the instruments parameter map
*/
Geometry::ParameterMap &ExperimentInfo::instrumentParameters() {
populateIfNotLoaded();
return *m_parmap;
}
/** Returns a const reference to the instrument parameters.
* @return a const reference to the instrument ParameterMap.
*/
const Geometry::ParameterMap &ExperimentInfo::instrumentParameters() const {
populateIfNotLoaded();
}
/** Returns a const reference to the instrument parameters.
* @return a const reference to the instrument ParameterMap.
*/
const Geometry::ParameterMap &
ExperimentInfo::constInstrumentParameters() const {
populateIfNotLoaded();
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
return *m_parmap;
}
namespace {
///@cond
/// Used for storing info about "r-position", "t-position" and "p-position"
/// parameters
/// These are translated to X,Y,Z and so must all be processed together
struct RTP {
RTP() : radius(0.0), haveRadius(false), theta(0.0), phi(0.0) {}
double radius;
bool haveRadius;
double theta;
double phi;
};
struct ParameterValue {
ParameterValue(const Geometry::XMLInstrumentParameter ¶mInfo,
const API::Run &run)
: info(paramInfo), runData(run) {}
operator double() {
if (info.m_logfileID.empty())
return boost::lexical_cast<double>(info.m_value);
else
return info.createParamValue(
runData.getTimeSeriesProperty<double>(info.m_logfileID));
}
operator int() { return boost::lexical_cast<int>(info.m_value); }
operator bool() {
if (boost::iequals(info.m_value, "true"))
return true;
else if (boost::iequals(info.m_value, "yes"))
return true;
else
return false;
const Geometry::XMLInstrumentParameter &info;
const Run &runData;
};
///@endcond
namespace {
bool isPositionParameter(const std::string &name) {
return ParameterMap::pos() == name;
}
bool isRotationParameter(const std::string &name) {
return ParameterMap::rot() == name;
}
bool isScaleParameter(const std::string &name) {
return (name == "scalex" || name == "scaley");
}
bool isRedundantPosOrRot(const std::string &name) {
// Check size first as a small optimization.
return (name.size() == 4) &&
(name == "posx" || name == "posy" || name == "posz" ||
name == "rotx" || name == "roty" || name == "rotz");
}
template <class T>
T getParam(const std::string ¶mType, const std::string ¶mValue) {
const std::string name = "dummy";
auto param = ParameterFactory::create(paramType, name);
param->fromString(paramValue);
void updatePosition(ComponentInfo &componentInfo, const IComponent *component,
const V3D &newRelPos) {
const auto compIndex = componentInfo.indexOf(component->getComponentID());
V3D position = newRelPos;
if (componentInfo.hasParent(compIndex)) {
const auto parentIndex = componentInfo.parent(compIndex);
componentInfo.rotation(parentIndex).rotate(position);
position += componentInfo.position(parentIndex);
}
componentInfo.setPosition(compIndex, position);
}
void updateRotation(ComponentInfo &componentInfo, const IComponent *component,
const Quat &newRelRot) {
const auto compIndex = componentInfo.indexOf(component->getComponentID());
auto rotation = newRelRot;
if (componentInfo.hasParent(compIndex)) {
const auto parentIndex = componentInfo.parent(compIndex);
rotation = componentInfo.rotation(parentIndex) * newRelRot;
}
componentInfo.setRotation(compIndex, rotation);
void adjustPositionsFromScaleFactor(ComponentInfo &componentInfo,
const IComponent *component,
const std::string ¶mName,
double factor) {
double ScaleX = 1.0;
double ScaleY = 1.0;
if (paramName == "scalex")
ScaleX = factor;
else
ScaleY = factor;
applyRectangularDetectorScaleToComponentInfo(
componentInfo, component->getComponentID(), ScaleX, ScaleY);
/** Add parameters to the instrument parameter map that are defined in
* instrument
* definition file or parameter file, which may contain parameters that
* require logfile data to be available. Logs must be loaded before running this
* method.
*/
void ExperimentInfo::populateInstrumentParameters() {
populateIfNotLoaded();
// Reference to the run
const auto &runData = run();
// Get pointer to parameter map that we may add parameters to and information
// about
// the parameters that my be specified in the instrument definition file (IDF)
Geometry::ParameterMap ¶mMap = instrumentParameters();
Geometry::ParameterMap paramMapForPosAndRot;
// Get instrument and sample
auto &componentInfo = mutableComponentInfo();
const auto parInstrument = getInstrument();
const auto instrument = parInstrument->baseInstrument();
const auto ¶mInfoFromIDF = instrument->getLogfileCache();
std::map<const IComponent *, RTP> rtpParams;
// In this loop position and rotation parameters are inserted into the
// temporary map paramMapForPosAndRot. In the subsequent loop, after all
// parameters have been parsed, we update positions and rotations in
// DetectorInfo and the temporary map goes out of scope. The main reason for
// this is that ParameterMap will then take care of assembling parameters for
// individual position or rotation components into a vector or quaternion. In
// particular, we cannot directly change DetectorInfo since the order of
// rotation components is not guaranteed.
for (const auto &item : paramInfoFromIDF) {
const auto &nameComp = item.first;
const auto ¶mInfo = item.second;
const std::string ¶mN = nameComp.first;
Janik Zikovsky
committed
try {
// Special case where user has specified r-position,t-position, and/or
// p-position.
// We need to know all three first to calculate a set of X,Y,Z
if (paramN.compare(1, 9, "-position") == 0) {
auto &rtpValues = rtpParams[paramInfo->m_component]; // If not found,
// constructs
// default
double value = ParameterValue(*paramInfo, runData);
if (paramN.compare(0, 1, "r") == 0) {
rtpValues.radius = value;
rtpValues.haveRadius = true;
} else if (paramN.compare(0, 1, "t") == 0)
rtpValues.theta = value;
else if (paramN.compare(0, 1, "p") == 0)
rtpValues.phi = value;
if (rtpValues.haveRadius) {
V3D pos;
pos.spherical(rtpValues.radius, rtpValues.theta, rtpValues.phi);
paramMapForPosAndRot.addV3D(paramInfo->m_component,
Janik Zikovsky
committed
}
populateWithParameter(paramMap, paramMapForPosAndRot, paramN,
*paramInfo, runData);
Janik Zikovsky
committed
}
} catch (std::exception &exc) {
g_log.information() << "Unable to add component parameter '"
<< nameComp.first << "'. Error: " << exc.what();
continue;
Janik Zikovsky
committed
}
}
for (const auto &item : paramMapForPosAndRot) {
if (isPositionParameter(item.second->name())) {
const auto newRelPos = item.second->value<V3D>();
updatePosition(componentInfo, item.first, newRelPos);
} else if (isRotationParameter(item.second->name())) {
const auto newRelRot = item.second->value<Quat>();
updateRotation(componentInfo, item.first, newRelRot);
}
// Parameters for individual components (x,y,z) are ignored. ParameterMap
// did compute the correct compound positions and rotations internally.
}
// Special case RectangularDetector: Parameters scalex and scaley affect pixel
// positions.
for (const auto &item : paramMap) {
if (isScaleParameter(item.second->name()))
adjustPositionsFromScaleFactor(componentInfo, item.first,
item.second->name(),
item.second->value<double>());
}
// paramMapForPosAndRot goes out of scope, dropping all position and rotation
// parameters of detectors (parameters for non-detector components have been
// inserted into paramMap via DetectorInfo::setPosition(IComponent *)).
/** Sets the number of detector groups.
*
* This method should not need to be called explicitly. The number of detector
* groups will be set either when initializing a MatrixWorkspace, or by calling
* `cacheDetectorGroupings` for an ExperimentInfo stored in an MDWorkspace. */
void ExperimentInfo::setNumberOfDetectorGroups(const size_t count) const {
populateIfNotLoaded();
if (m_spectrumInfo)
m_spectrumDefinitionNeedsUpdate.clear();
m_spectrumDefinitionNeedsUpdate.resize(count, 1);
m_spectrumInfo = std::make_unique<Beamline::SpectrumInfo>(count);
m_spectrumInfoWrapper = nullptr;
/** Returns the number of detector groups.
*
* For MatrixWorkspace this is equal to getNumberHistograms() (after
*initialization). */
size_t ExperimentInfo::numberOfDetectorGroups() const {
return m_spectrumDefinitionNeedsUpdate.size();
}
/** Sets the detector grouping for the spectrum with the given `index`.
*
* This method should not need to be called explicitly. Groupings are updated
* automatically when modifying detector IDs in a workspace (via ISpectrum). */
void ExperimentInfo::setDetectorGrouping(
const size_t index, const std::set<detid_t> &detIDs) const {
SpectrumDefinition specDef;
for (const auto detID : detIDs) {
try {
const size_t detIndex = detectorInfo().indexOf(detID);
specDef.add(detIndex);
} catch (std::out_of_range &) {
// Silently strip bad detector IDs
m_spectrumInfo->setSpectrumDefinition(index, std::move(specDef));
m_spectrumDefinitionNeedsUpdate.at(index) = 0;
}
/** Update detector grouping for spectrum with given index.
* This method is called when the detector grouping stored in SpectrumDefinition
* at `index` in Beamline::SpectrumInfo is not initialized or outdated. The
* implementation throws, since no grouping information for update is available
* when grouping comes from a call to `cacheDetectorGroupings`. This method is
* overridden in MatrixWorkspace. */
void ExperimentInfo::updateCachedDetectorGrouping(
const size_t /*unused*/) const {
throw std::runtime_error("ExperimentInfo::updateCachedDetectorGrouping: "
"Cannot update -- grouping information not "
"available");
}
/** Get a constant reference to the Sample associated with this workspace.
* @return const reference to Sample object
*/
const Sample &ExperimentInfo::sample() const {
populateIfNotLoaded();
return *m_sample;
}
/** Get a reference to the Sample associated with this workspace.
* This non-const method will copy the sample if it is shared between
* more than one workspace, and the reference returned will be to the copy.
* @return reference to sample object
*/
Sample &ExperimentInfo::mutableSample() {
populateIfNotLoaded();
return m_sample.access();
}
/** Get a constant reference to the Run object associated with this workspace.
* @return const reference to run object
*/
const Run &ExperimentInfo::run() const {
populateIfNotLoaded();
return *m_run;
}
/** Get a reference to the Run object associated with this workspace.
* This non-const method will copy the Run object if it is shared between
* more than one workspace, and the reference returned will be to the copy.
* @return reference to Run object
*/
Run &ExperimentInfo::mutableRun() {
populateIfNotLoaded();
return m_run.access();
}
/// Set the run object. Use in particular to clear run without copying old run.
void ExperimentInfo::setSharedRun(Kernel::cow_ptr<Run> run) {
m_run = std::move(run);
/// Return the cow ptr of the run
Kernel::cow_ptr<Run> ExperimentInfo::sharedRun() { return m_run; }
/**
* Get an experimental log either by log name or by type, e.g.
* - temperature_log
* - chopper_speed_log
* The logs are first checked for one matching the given string and if that
* fails then the instrument is checked for a parameter of the same name
* and if this exists then its value is assume to be the actual log required
* @param log :: A string giving either a specific log name or instrument
* parameter whose
* value is to be retrieved
* @return A pointer to the property
*/
Kernel::Property *ExperimentInfo::getLog(const std::string &log) const {
populateIfNotLoaded();
try {
return run().getProperty(log);
} catch (Kernel::Exception::NotFoundError &) {
// No log with that name
}
// If the instrument has a parameter with that name then take the value as a
// log name
const std::string logName =
constInstrumentParameters().getString(sptr_instrument.get(), log);
if (logName.empty()) {
throw std::invalid_argument(
"ExperimentInfo::getLog - No instrument parameter named \"" + log +
"\". Cannot access full log name");
}
return run().getProperty(logName);
}
/**
* Get an experimental log as a single value either by log name or by type. @see
* getLog
* @param log :: A string giving either a specific log name or instrument
* parameter whose
* value is to be retrieved
* @return A pointer to the property
*/
double ExperimentInfo::getLogAsSingleValue(const std::string &log) const {
populateIfNotLoaded();
try {
return run().getPropertyAsSingleValue(log);
} catch (Kernel::Exception::NotFoundError &) {
// No log with that name
// If the instrument has a parameter with that name then take the value as a
// log name
const std::string logName =
constInstrumentParameters().getString(sptr_instrument.get(), log);
if (logName.empty()) {
throw std::invalid_argument(
"ExperimentInfo::getLog - No instrument parameter named \"" + log +
"\". Cannot access full log name");
return run().getPropertyAsSingleValue(logName);
}
/** Utility method to get the run number
*
* @return the run number (int) or 0 if not found.
*/
int ExperimentInfo::getRunNumber() const {
populateIfNotLoaded();
const Run &thisRun = run();
if (!thisRun.hasProperty("run_number")) {
// No run_number property, default to 0
Janik Zikovsky
committed
return 0;
} else {
Property *prop = m_run->getProperty("run_number");
if (prop) {
// Use the string representation. That way both a string and a number
// property will work.
int val;
if (Strings::convert(prop->value(), val))
return val;
else
return 0;
}
}
return 0;
}
/**
* Returns the emode for this run. It first searchs the run logs for a
* "deltaE-mode" log and falls back to
* the instrument if one is not found. If neither exist then the run is
* considered Elastic.
* @return The emode enum for the energy transfer mode of this run. Currently
* checks the sample log & instrument in this order
*/
Kernel::DeltaEMode::Type ExperimentInfo::getEMode() const {
populateIfNotLoaded();
static const char *emodeTag = "deltaE-mode";
std::string emodeStr;
if (run().hasProperty(emodeTag)) {
emodeStr = run().getPropertyValueAsType<std::string>(emodeTag);
} else if (sptr_instrument && constInstrumentParameters().contains(
sptr_instrument.get(), emodeTag)) {
constInstrumentParameters().get(sptr_instrument.get(), emodeTag);
emodeStr = param->asString();
} else {
return Kernel::DeltaEMode::Elastic;
}
return Kernel::DeltaEMode::fromString(emodeStr);
}
/**
* Easy access to the efixed value for this run & detector ID
* @param detID :: The detector ID to ask for the efixed mode (ignored in Direct
* & Elastic mode). The
* detector with ID matching that given is pulled from the instrument with this
* method and it will
* throw a Exception::NotFoundError if the ID is unknown.
* @return The current EFixed value
*/
double ExperimentInfo::getEFixed(const detid_t detID) const {
populateIfNotLoaded();
IDetector_const_sptr det = getInstrument()->getDetector(detID);
return getEFixed(det);
}
/**
* Easy access to the efixed value for this run & detector
* @param detector :: The detector object to ask for the efixed mode. Only
* required for Indirect mode
* @return The current efixed value
*/
double ExperimentInfo::getEFixed(
const boost::shared_ptr<const Geometry::IDetector> detector) const {
populateIfNotLoaded();
Kernel::DeltaEMode::Type emode = getEMode();
if (emode == Kernel::DeltaEMode::Direct) {
try {
return this->run().getPropertyValueAsType<double>("Ei");
} catch (Kernel::Exception::NotFoundError &) {
throw std::runtime_error(
"Experiment logs do not contain an Ei value. Have you run GetEi?");
}
} else if (emode == Kernel::DeltaEMode::Indirect) {
if (!detector)
throw std::runtime_error("ExperimentInfo::getEFixed - Indirect mode "
"efixed requested without a valid detector.");
Parameter_sptr par =
constInstrumentParameters().getRecursive(detector.get(), "Efixed");
if (par) {
return par->value<double>();
} else {
std::vector<double> efixedVec = detector->getNumberParameter("Efixed");
if (efixedVec.empty()) {
int detid = detector->getID();
IDetector_const_sptr detectorSingle =
getInstrument()->getDetector(detid);
efixedVec = detectorSingle->getNumberParameter("Efixed");
}
if (!efixedVec.empty()) {
return efixedVec.at(0);
} else {
std::ostringstream os;
os << "ExperimentInfo::getEFixed - Indirect mode efixed requested but "
"detector has no Efixed parameter attached. ID="
<< detector->getID();
throw std::runtime_error(os.str());
}
}
} else {
throw std::runtime_error("ExperimentInfo::getEFixed - EFixed requested for "
"elastic mode, don't know what to do!");
}
void ExperimentInfo::setEFixed(const detid_t detID, const double value) {
populateIfNotLoaded();
IDetector_const_sptr det = getInstrument()->getDetector(detID);
Geometry::ParameterMap &pmap = instrumentParameters();
pmap.addDouble(det.get(), "Efixed", value);
}
/** Return workspace start date as an ISO 8601 string. If this info not stored
*in workspace the
* method returns current date. This date is used for example to retrieve the
*instrument file.
*
* @return workspace start date as a string (current time if start date not
*available)
*/
std::string ExperimentInfo::getWorkspaceStartDate() const {
populateIfNotLoaded();
std::string date;
try {
date = run().startTime().toISO8601String();
} catch (std::runtime_error &) {
g_log.information("run_start/start_time not stored in workspace. Default "
"to current date.");
date = Types::Core::DateAndTime::getCurrentTime().toISO8601String();
return date;
}
/** Return workspace start date as a formatted string (strftime, as
* returned by Types::Core::DateAndTime) string, if available. If
* unavailable, an empty string is returned
*
* @return workspace start date as a string (empty if no date available)
*/
std::string ExperimentInfo::getAvailableWorkspaceStartDate() const {
populateIfNotLoaded();
std::string date;
try {
date = run().startTime().toFormattedString();
} catch (std::runtime_error &) {
g_log.information("Note: run_start/start_time not stored in workspace.");
}
return date;
}
/** Return workspace end date as a formatted string (strftime style,
* as returned by Kernel::DateAdnTime) string, if available. If
* unavailable, an empty string is returned
*
* @return workspace end date as a string (empty if no date available)
*/
std::string ExperimentInfo::getAvailableWorkspaceEndDate() const {
populateIfNotLoaded();
std::string date;
try {
date = run().endTime().toFormattedString();
} catch (std::runtime_error &) {
g_log.information("Note: run_start/start_time not stored in workspace.");
}
return date;
}
/** Return from an IDF the values of the valid-from and valid-to attributes
*
* @param IDFfilename :: Full path of an IDF
* @param[out] outValidFrom :: Used to return valid-from date
* @param[out] outValidTo :: Used to return valid-to date
*/
void ExperimentInfo::getValidFromTo(const std::string &IDFfilename,
std::string &outValidFrom,
std::string &outValidTo) {
SAXParser pParser;
// Create on stack to ensure deletion. Relies on pParser also being local
// variable.
myContentHandler conHand;
pParser.setContentHandler(&conHand);
try {
pParser.parse(IDFfilename);
} catch (DummyException &e) {
outValidFrom = e.m_validFrom;
outValidTo = e.m_validTo;
} catch (...) {
// should throw some sensible here
}
}
/** Compile a list of files in compliance with name pattern-matching, file
* format, and date-stamp constraints
*
* Ideally, the valid-from and valid-to of any valid file should encapsulate
* argument date. If this is not possible, then the file with the most recent
* valid-from stamp is selected.
*
* @param prefix :: the name of a valid file must begin with this pattern
* @param fileFormats :: the extension of a valid file must be one of these
* formats
* @param directoryNames :: search only in these directories
* @param date :: the valid-from and valid-to of a valid file should encapsulate
* this date
* @return list of absolute paths for each valid file
*/
std::vector<std::string> ExperimentInfo::getResourceFilenames(
const std::string &prefix, const std::vector<std::string> &fileFormats,
const std::vector<std::string> &directoryNames, const std::string &date) {
if (date.empty()) {
// Just use the current date
g_log.debug() << "No date specified, using current date and time.\n";
Types::Core::DateAndTime::getCurrentTime().toISO8601String();
// Recursively call this method, but with all parameters.
return ExperimentInfo::getResourceFilenames(prefix, fileFormats,
}
// Join all the file formats into a single string
std::stringstream ss;
ss << "(";
for (size_t i = 0; i < fileFormats.size(); ++i) {
if (i != 0)
ss << "|";
ss << fileFormats[i];
}
ss << ")";
const std::string allFileFormats = ss.str();
const boost::regex regex(prefix + ".*\\." + allFileFormats,
boost::regex_constants::icase);
Poco::DirectoryIterator end_iter;
DateAndTime d(date);
DateAndTime refDate("1900-01-31 23:59:00"); // used to help determine the most
// recently starting file, if none match
DateAndTime refDateGoodFile(
"1900-01-31 23:59:00"); // used to help determine the most recently
// Two files could have the same `from` date so multimap is required.
// Sort with newer dates placed at the beginning
std::multimap<DateAndTime, std::string, std::greater<DateAndTime>>
matchingFiles;
bool foundFile = false;
std::string mostRecentFile; // path to the file with most recent "valid-from"
for (const auto &directoryName : directoryNames) {
// Iterate over the directories from user ->etc ->install, and find the
// first beat file
for (Poco::DirectoryIterator dir_itr(directoryName); dir_itr != end_iter;
++dir_itr) {
const auto &filePath = dir_itr.path();
if (!filePath.isFile())
continue;
const std::string &l_filenamePart = filePath.getFileName();
if (regex_match(l_filenamePart, regex)) {
const auto &pathName = filePath.toString();
g_log.debug() << "Found file: '" << pathName << "'\n";
std::string validFrom, validTo;
getValidFromTo(pathName, validFrom, validTo);
g_log.debug() << "File '" << pathName << " valid dates: from '"
<< validFrom << "' to '" << validTo << "'\n";
// Use default valid "from" and "to" dates if none were found.
DateAndTime to, from;
if (validFrom.length() > 0)
from.setFromISO8601(validFrom);
else
from = refDate;
if (validTo.length() > 0)
to.setFromISO8601(validTo);
else
to.setFromISO8601("2100-01-01T00:00:00");
if (from <= d && d <= to) {
foundFile = true;
matchingFiles.insert(
std::pair<DateAndTime, std::string>(from, pathName));
}
// Consider the most recent file in the absence of matching files
if (!foundFile && (from >= refDate)) {
refDate = from;
mostRecentFile = pathName;
}
}
}
// Retrieve the file names only
std::vector<std::string> pathNames;
if (!matchingFiles.empty()) {
pathNames.reserve(matchingFiles.size());
for (auto &elem : matchingFiles)
pathNames.emplace_back(std::move(elem.second));
pathNames.emplace_back(std::move(mostRecentFile));
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
return pathNames;
}
/** A given instrument may have multiple definition files associated with it.
*This method returns a file name which identifies a given instrument definition
*for a given instrument.
*The instrument geometry can be loaded from either a ".xml" file (old-style
*IDF) or a ".hdf5/.nxs" file (new-style nexus).
*The filename is required to be of the form InstrumentName + _Definition +
*Identifier + extension. The identifier then is the part of a filename that
*identifies the instrument definition valid at a given date.
*
* If several instrument files files are valid at the given date the file with
*the most recent from date is selected. If no such files are found the file
*with the latest from date is selected.
*
* If no file is found for the given instrument, an empty string is returned.
*
* @param instrumentName :: Instrument name e.g. GEM, TOPAS or BIOSANS
* @param date :: ISO 8601 date
* @return full path of instrument geometry file
*
* @throws Exception::NotFoundError If no valid instrument definition filename
*is found
*/
std::string
ExperimentInfo::getInstrumentFilename(const std::string &instrumentName,
const std::string &date) {
const std::vector<std::string> validFormats = {"xml", "nxs", "hdf5"};
g_log.debug() << "Looking for instrument file for " << instrumentName
<< " that is valid on '" << date << "'\n";
// Lookup the instrument (long) name
Kernel::ConfigService::Instance().getInstrument(instrumentName).name());
// Get the instrument directories for instrument file search
const std::vector<std::string> &directoryNames =
Kernel::ConfigService::Instance().getInstrumentDirectories();
// matching files sorted with newest files coming first
const std::vector<std::string> matchingFiles = getResourceFilenames(
instrument + "_Definition", validFormats, directoryNames, date);
std::string instFile;
instFile = matchingFiles[0];
g_log.debug() << "Instrument file selected is " << instFile << '\n';
g_log.debug() << "No instrument file found\n";