Skip to content
Snippets Groups Projects
ExperimentInfo.cpp 40.6 KiB
Newer Older
#include "MantidAPI/ExperimentInfo.h"

#include "MantidAPI/ChopperModel.h"
#include "MantidAPI/InstrumentDataService.h"
#include "MantidAPI/ModeratorModel.h"

#include "MantidGeometry/Instrument/InstrumentDefinitionParser.h"
#include "MantidGeometry/Crystal/OrientedLattice.h"
#include "MantidGeometry/Instrument/ParameterMap.h"
#include "MantidGeometry/Instrument/ParComponentFactory.h"
#include "MantidGeometry/Instrument/XMLlogfile.h"
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/DateAndTime.h"
#include "MantidKernel/InstrumentInfo.h"
#include "MantidKernel/Property.h"
#include "MantidKernel/Strings.h"
#include "MantidKernel/TimeSeriesProperty.h"
#include <boost/algorithm/string.hpp>
#include <boost/make_shared.hpp>
#include <boost/regex.hpp>
#include <Poco/DirectoryIterator.h>
#include <Poco/Path.h>
#include <Poco/SAX/ContentHandler.h>
#include <Poco/SAX/SAXParser.h>
#include <Poco/ScopedLock.h>
#include <nexus/NeXusException.hpp>
using namespace Mantid::Geometry;
  Kernel::Logger& ExperimentInfo::g_log = Kernel::Logger::get("ExperimentInfo");

  //----------------------------------------------------------------------------------------------
  /** Constructor
   */
  ExperimentInfo::ExperimentInfo()
    m_sample(new Sample()),
    m_run(new Run()),
    m_parmap(new ParameterMap()),
    sptr_instrument(new Instrument())
  {
  }
    
  //----------------------------------------------------------------------------------------------
  /** Destructor
   */
  ExperimentInfo::~ExperimentInfo()
  {
  //---------------------------------------------------------------------------------------
  /**
   * 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);
  }

  //---------------------------------------------------------------------------------------
  /** 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;
    m_run = other->m_run;
    this->setInstrument(other->getInstrument());
    if(other->m_moderatorModel) m_moderatorModel = other->m_moderatorModel->clone();
    m_choppers.clear();
    for(auto iter = other->m_choppers.begin(); iter != other->m_choppers.end(); ++iter)
    {
      m_choppers.push_back((*iter)->clone());
    }
  //---------------------------------------------------------------------------------------
  /** Clone this ExperimentInfo class into a new one
  ExperimentInfo * ExperimentInfo::cloneExperimentInfo()const
  {
    ExperimentInfo * out = new ExperimentInfo();
    out->copyExperimentInfoFrom(this);
    return out;
  }

  //---------------------------------------------------------------------------------------

  /// @returns A human-readable description of the object
  const std::string ExperimentInfo::toString() const
  {
    std::ostringstream out;

    Geometry::Instrument_const_sptr inst = this->getInstrument();
    out << "Instrument: " << inst->getName() << " ("
        << inst->getValidFromDate().toFormattedString("%Y-%b-%d")
        << " to " << inst->getValidToDate().toFormattedString("%Y-%b-%d") << ")";
    out << "\n";
    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";
    }
    return out.str();
  }

  //---------------------------------------------------------------------------------------
  /** Set the instrument
  * @param instr :: Shared pointer to an instrument.
  */
  void ExperimentInfo::setInstrument(const Instrument_const_sptr& instr)
      sptr_instrument = instr->baseInstrument();
      m_parmap = instr->getParameterMap();
    }
  }


  //---------------------------------------------------------------------------------------
  /** Get a shared pointer to the parametrized instrument associated with this workspace
  *
  *  @return The instrument class
  */
  Instrument_const_sptr ExperimentInfo::getInstrument()const
  {
    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()
  {
    //TODO: Here duplicates cow_ptr. Figure out if there's a better way

    // Use a double-check for sharing so that we only
    // enter the critical region if absolutely necessary
    if (!m_parmap.unique())
    {
      Poco::Mutex::ScopedLock lock(m_mutex);
      // Check again because another thread may have taken copy
      // and dropped reference count since previous check
      if (!m_parmap.unique())
        ParameterMap_sptr oldData = m_parmap;
        m_parmap = boost::make_shared<ParameterMap>(*oldData);
      }
    }
    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
  {
    return *m_parmap.get();
  }

  //---------------------------------------------------------------------------------------
  /**  Returns a const reference to the instrument parameters.
  *    @return a const reference to the instrument ParameterMap.
  */
  const Geometry::ParameterMap& ExperimentInfo::constInstrumentParameters() const
  {
    return *m_parmap;
  }

    ///@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::XMLlogfile & paramInfo, 
                     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::XMLlogfile & info;
      const Run & runData;
    };
    ///@endcond

  //---------------------------------------------------------------------------------------
  /** Add parameters to the instrument parameter map that are defined in instrument
  *   definition file and for which logfile data are available. Logs must be loaded
  *   before running this method.
  */
  void ExperimentInfo::populateInstrumentParameters()
  {
    // Get instrument and sample
    boost::shared_ptr<const Instrument> instrument = getInstrument()->baseInstrument();
    // 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& paramMap = instrumentParameters();
    const auto & paramInfoFromIDF = instrument->getLogfileCache();
    const double deg2rad(M_PI/180.0);
    std::map<const IComponent*, RTP> rtpParams;
    auto cacheEnd = paramInfoFromIDF.end();
    for(auto cacheItr = paramInfoFromIDF.begin(); cacheItr != cacheEnd; ++cacheItr)
      const auto & nameComp = cacheItr->first;
      const auto & paramInfo = cacheItr->second;
      const std::string & paramN = nameComp.first;
        // Special case for r,t,p. We need to know all three first to calculate 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 = deg2rad*value;
          else if(paramN.compare(0,1,"p") == 0) rtpValues.phi = deg2rad*value;
          else {}
          if(rtpValues.haveRadius) // Just overwrite x,y,z
          {
            // convert spherical coordinates to cartesian coordinate values
            double x = rtpValues.radius*std::sin(rtpValues.theta)*std::cos(rtpValues.phi);
            paramMap.addPositionCoordinate(paramInfo->m_component, "x", x);
            double y = rtpValues.radius*std::sin(rtpValues.theta)*std::sin(rtpValues.phi);
            paramMap.addPositionCoordinate(paramInfo->m_component, "y", y);
            double z = rtpValues.radius*std::cos(rtpValues.theta);
            paramMap.addPositionCoordinate(paramInfo->m_component, "z", z);
          }
          populateWithParameter(paramMap, paramN, *paramInfo, runData);
      catch(std::exception& exc)
        g_log.information() << "Unable to add component parameter '" << nameComp.first << "'. Error: " << exc.what();
        continue;
  
  //---------------------------------------------------------------------------------------
  /**
   * Replaces current parameter map with a copy of the given map
   * @ pmap const reference to parameter map whose copy replaces the current parameter map
   */
  void ExperimentInfo::replaceInstrumentParameters(const Geometry::ParameterMap & pmap)
  {
    this->m_parmap.reset(new ParameterMap(pmap));
  }

  //---------------------------------------------------------------------------------------
  /**
   * Caches a lookup for the detector IDs of the members that are part of the same group
   * @param mapping :: A map between a detector ID and the other IDs that are part of the same
   * group.
   */
  void ExperimentInfo::cacheDetectorGroupings(const det2group_map & mapping)
  {
    m_detgroups = mapping;
  }

  //---------------------------------------------------------------------------------------
  /// Returns the detector IDs that make up the group that this ID is part of
  const std::vector<detid_t> & ExperimentInfo::getGroupMembers(const detid_t detID) const
  {
    auto iter = m_detgroups.find(detID);
    if(iter != m_detgroups.end())
    {
      return iter->second;
    }
    else
    {
      throw std::runtime_error("ExperimentInfo::getGroupMembers - Unable to find ID " + boost::lexical_cast<std::string>(detID) + " in lookup");
    }
  }

  //---------------------------------------------------------------------------------------
  /**
   * Get a detector or detector group from an ID
   * @param detID :: 
   * @returns A single detector or detector group depending on the mapping set.
   * @see set
   */
  Geometry::IDetector_const_sptr ExperimentInfo::getDetectorByID(const detid_t detID) const
  {
    if(m_detgroups.empty())
    {
      g_log.debug("No detector mapping cached, getting detector from instrument");
      return getInstrument()->getDetector(detID);
    }
    else
    {
      const std::vector<detid_t> & ids = this->getGroupMembers(detID);
      return getInstrument()->getDetectorG(ids);
    }
  }

  //---------------------------------------------------------------------------------------

  /**
   * Set an object describing the moderator properties and take ownership
   * @param source :: A pointer to an object describing the source. Ownership is transferred to this object
   */
  void ExperimentInfo::setModeratorModel(ModeratorModel *source)
  {
    if(!source)
    {
      throw std::invalid_argument("ExperimentInfo::setModeratorModel - NULL source object found.");
    }
    m_moderatorModel = boost::shared_ptr<ModeratorModel>(source);
  }

  /// Returns a reference to the source properties object
  ModeratorModel & ExperimentInfo::moderatorModel() const
  {
    if(!m_moderatorModel)
    {
      throw std::runtime_error("ExperimentInfo::moderatorModel - No source desciption has been defined");
    }
    return *m_moderatorModel;
  }

  //---------------------------------------------------------------------------------------
  /**
   * Sets a new chopper description at a given point. The point is given by index where 0 is
   * closest to the source
   * @param chopper :: A pointer to a new chopper object, this class takes ownership of the pointer
   * @param index :: An optional index that specifies which chopper point the chopper belongs to (default=0)
   */
  void ExperimentInfo::setChopperModel(ChopperModel *chopper, const size_t index)
  {
    if(!chopper)
    {
      throw std::invalid_argument("ExperimentInfo::setChopper - NULL chopper object found.");
    }
    auto iter = m_choppers.begin();
    std::advance(iter, index);
    if(index < m_choppers.size()) // Replacement
    {
      (*iter) = boost::shared_ptr<ChopperModel>(chopper);
    }
    else // Insert it
    {
      m_choppers.insert(iter, boost::shared_ptr<ChopperModel>(chopper));
    }
  }

  /**
   * Returns a const reference to a chopper description
   * @param index :: An optional index giving the point within the instrument this chopper describes (default=0)
   * @return A reference to a const chopper object
   */
  ChopperModel & ExperimentInfo::chopperModel(const size_t index) const
  {
    if(index < m_choppers.size())
    {
      auto iter = m_choppers.begin();
      std::advance(iter, index);
      return **iter;
    }
    else
    {
      std::ostringstream os;
      os << "ExperimentInfo::chopper - Invalid index=" << index << ". " << m_choppers.size()
         << " chopper descriptions have been set.";
      throw std::invalid_argument(os.str());
    }
  }

  //---------------------------------------------------------------------------------------
  /** Get a constant reference to the Sample associated with this workspace.
  * @return const reference to Sample object
  */
  const  Sample& ExperimentInfo::sample() const
  {
    Poco::Mutex::ScopedLock lock(m_mutex);
    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.
  *  Can ONLY be taken by reference!
  * @return reference to sample object
  */
  Sample& ExperimentInfo::mutableSample()
  {
    // Use a double-check for sharing so that we only
    // enter the critical region if absolutely necessary
    if (!m_sample.unique())
    {
      Poco::Mutex::ScopedLock lock(m_mutex);
      // Check again because another thread may have taken copy
      // and dropped reference count since previous check
      if (!m_sample.unique())
      {
        boost::shared_ptr<Sample> oldData = m_sample;
        m_sample = boost::make_shared<Sample>(*oldData);
      }
    }
    return *m_sample;
  }


  //---------------------------------------------------------------------------------------
  /** Get a constant reference to the Run object associated with this workspace.
  * @return const reference to run object
  */
  const Run& ExperimentInfo::run() const
  {
    Poco::Mutex::ScopedLock lock(m_mutex);
    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.
  *  Can ONLY be taken by reference!
  * @return reference to Run object
  */
  Run& ExperimentInfo::mutableRun()
  {
    // Use a double-check for sharing so that we only
    // enter the critical region if absolutely necessary
    if (!m_run.unique())
    {
      Poco::Mutex::ScopedLock lock(m_mutex);
      // Check again because another thread may have taken copy
      // and dropped reference count since previous check
      if (!m_run.unique())
      {
        boost::shared_ptr<Run> oldData = m_run;
        m_run = boost::make_shared<Run>(*oldData);
      }
    }
    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
  {
    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 = instrumentParameters().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
  {
    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 = instrumentParameters().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
  {
    const Run& thisRun = run();
    if (!thisRun.hasProperty("run_number"))
    {
      // No run_number property, default to 0
      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 only checks the instrument
   */
  Kernel::DeltaEMode::Type ExperimentInfo::getEMode() const
  {
    static const char * emodeTag = "deltaE-mode";
    std::string emodeStr;
    if(run().hasProperty(emodeTag))
    {
      emodeStr = run().getPropertyValueAsType<std::string>(emodeTag);
    }
    else if(sptr_instrument && instrumentParameters().contains(sptr_instrument.get(), emodeTag))
    {
      Geometry::Parameter_sptr param = instrumentParameters().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
  {
    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 Geometry::IDetector_const_sptr detector) const
  {
    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)
  {
      IDetector_const_sptr det = getInstrument()->getDetector(detID);
      Geometry::ParameterMap& pmap = instrumentParameters();
      pmap.addDouble(det.get(), "Efixed", value);
  }

  // used to terminate SAX process
  class DummyException {
  public:
    std::string m_validFrom;
    std::string m_validTo;
    DummyException(std::string validFrom, std::string validTo)
      : m_validFrom(validFrom), m_validTo(validTo) {}
  };

  // SAX content handler for grapping stuff quickly from IDF
  class myContentHandler : public Poco::XML::ContentHandler
  {
    virtual void startElement(const XMLString &, const XMLString & localName, const XMLString &, const Attributes & attrList )
    {
      if (localName == "instrument" )
      {
        throw DummyException(static_cast<std::string>(attrList.getValue("","valid-from")),
            static_cast<std::string>(attrList.getValue("","valid-to")));
      }
    }
    virtual void endElement(const XMLString &, const XMLString &, const XMLString & ) {}
    virtual void startDocument() {}
    virtual void endDocument() {}
    virtual void characters(const XMLChar [], int , int ) {}
    virtual void endPrefixMapping(const XMLString & ) {}
    virtual void ignorableWhitespace(const XMLChar [], int , int ) {}
    virtual void processingInstruction(const XMLString & , const XMLString & ) {}
    virtual void setDocumentLocator(const Locator * ) {}
    virtual void skippedEntity(const XMLString & ) {}
    virtual void startPrefixMapping(const XMLString & , const XMLString & ) {}
  };

  //---------------------------------------------------------------------------------------
  /** 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
        }
  }

  //---------------------------------------------------------------------------------------
  /** Return workspace start date as an ISO 8601 string. If this info not stored in workspace the
  *   method returns current date.
  *
  *  @return workspace start date as a string
  */
  std::string ExperimentInfo::getWorkspaceStartDate()
  {
    std::string date;
      date = run().startTime().toISO8601String();
    }
    catch (std::runtime_error &)
      g_log.information("run_start/start_time not stored in workspace. Default to current date.");
      date = Kernel::DateAndTime::getCurrentTime().toISO8601String();
    }
    return date;
  }

  //---------------------------------------------------------------------------------------
  /** A given instrument may have multiple IDFs associated with it. This method return an
  *  identifier which identify a given IDF for a given instrument. An IDF filename is
  *  required to be of the form IDFname + _Definition + Identifier + .xml, the identifier
  *  then is the part of a filename that identifies the IDF valid at a given date.
  *
  *  If several IDF 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 IDF
  */
  std::string ExperimentInfo::getInstrumentFilename(const std::string& instrumentName, const std::string& date)
  {
      if (date.empty())
      {
          // Just use the current date
          g_log.debug() << "No date specified, using current date and time." << std::endl;
          const std::string now = Kernel::DateAndTime::getCurrentTime().toISO8601String();
          // Recursively call this method, but with both parameters.
          return ExperimentInfo::getInstrumentFilename(instrumentName, now);
      }

    g_log.debug() << "Looking for instrument XML file for " << instrumentName << " that is valid on '" << date << "'\n";
    // Lookup the instrument (long) name
    std::string instrument(Kernel::ConfigService::Instance().getInstrument(instrumentName).name());

    // Get the search directory for XML instrument definition files (IDFs)
    std::string directoryName = Kernel::ConfigService::Instance().getInstrumentDirectory();

    boost::regex regex(instrument+"_Definition.*\\.xml", boost::regex_constants::icase);
    Poco::DirectoryIterator end_iter;
    DateAndTime d(date);
    bool foundGoodFile = false; // True if we have found a matching file (valid at the given date)
    std::string mostRecentIDF; // store most recently starting matching IDF if found, else most recently starting IDF.
    DateAndTime refDate("1900-01-31 23:59:00"); // used to help determine the most recently starting IDF, if none match 
    DateAndTime refDateGoodFile("1900-01-31 23:59:00"); // used to help determine the most recently starting matching IDF 
    for ( Poco::DirectoryIterator dir_itr(directoryName); dir_itr != end_iter; ++dir_itr )
    {
      if ( !Poco::File(dir_itr->path() ).isFile() ) continue;

      std::string l_filenamePart = Poco::Path(dir_itr->path()).getFileName();
      if ( regex_match(l_filenamePart, regex) )
      {
        g_log.debug() << "Found file: '" << dir_itr->path() << "'\n";
        std::string validFrom, validTo;
        getValidFromTo(dir_itr->path(), validFrom, validTo);
        g_log.debug() << "File '" << dir_itr->path() << " valid dates: from '" << validFrom << "' to '" << validTo << "'\n";
        DateAndTime from(validFrom);
        // Use a default valid-to date if none was found.
        DateAndTime to;
        if (validTo.length() > 0)
          to.setFromISO8601(validTo);
          to.setFromISO8601("2100-01-01T00:00:00");
          if( from > refDateGoodFile ) 
          { // We'd found a matching file more recently starting than any other matching file found
            foundGoodFile = true;
            refDateGoodFile = from;
            mostRecentIDF = dir_itr->path();
          }
        if ( !foundGoodFile && ( from > refDate ) )
        {  // Use most recently starting file, in case we don't find a matching file.
          refDate = from;
    g_log.debug() << "IDF selected is " << mostRecentIDF << std::endl;
  //--------------------------------------------------------------------------------------------
  /** Save the object to an open NeXus file.
   * @param file :: open NeXus file
   */
  void ExperimentInfo::saveExperimentInfoNexus(::NeXus::File * file) const
    Instrument_const_sptr instrument = getInstrument();
    instrument->saveNexus( file, "instrument");
    sample().saveNexus(file, "sample");
    run().saveNexus(file, "logs");
  }

  //--------------------------------------------------------------------------------------------
  /** Load the object from an open NeXus file.
   * @param file :: open NeXus file
   * @param[out] parameterStr :: special string for all the parameters.
   *             Feed that to ExperimentInfo::readParameterMap() after the instrument is done.
   * @throws Exception::NotFoundError If instrument definition is not in the nexus file and cannot
   *                                  be loaded from the IDF.
  void ExperimentInfo::loadExperimentInfoNexus(::NeXus::File * file, std::string & parameterStr)
    std::string instrumentName;
    std::string instrumentXml;
    std::string instrumentFilename;

    int sampleVersion = mutableSample().loadNexus(file, "sample");
    if (sampleVersion == 0)
    {
      // Old-style (before Sep-9-2011) NXS processed
      // sample field contains both the logs and the sample details
      file->openGroup("sample", "NXsample");
      this->mutableRun().loadNexus(file, "");
      file->closeGroup();
    }
    else
    {
      // Newer style: separate "logs" field for the Run object
      this->mutableRun().loadNexus(file, "logs");
    }

    // Try to get the instrument file
    file->openGroup("instrument", "NXinstrument");
    file->readData("name", instrumentName);

    try {
      file->openGroup("instrument_xml", "NXnote");
      file->readData("data", instrumentXml );
      file->closeGroup();
    }
    catch (NeXus::Exception& ex) {
      // Just carry on - it might not be there (e.g. old-style processed files)
      g_log.debug(ex.what());
    }

    // We first assume this is a new version file, but if the next step fails we assume its and old version file.
    int version = 1;
    try {
      file->readData("instrument_source", instrumentFilename);
      // In the old version 'processed' file, this was held at the top level (as was the parameter map)
      try {
        file->readData("instrument_source", instrumentFilename);
      }
      catch (NeXus::Exception& ex) {
        // Just carry on - it might not be there (e.g. for SNS files)
        g_log.debug(ex.what());
      }
    try {
      file->openGroup("instrument_parameter_map", "NXnote");
      file->readData("data", parameterStr);
      file->closeGroup();
    }
    catch (NeXus::Exception& ex) {
      // Just carry on - it might not be there (e.g. for SNS files)
      g_log.debug(ex.what());
    }
    {
      file->closeGroup();
    }

    instrumentFilename = Strings::strip(instrumentFilename);
    instrumentXml = Strings::strip(instrumentXml);
    instrumentName = Strings::strip(instrumentName);
    if (instrumentXml.empty() && !instrumentName.empty())
    {
      // XML was not included or was empty.
      // Use the instrument name to find the file
      try
      {
        std::string filename = getInstrumentFilename(instrumentName, getWorkspaceStartDate() );
        instrumentFilename = filename;
        instrumentXml = Strings::loadFile(filename);
        g_log.error() << "Error loading instrument IDF file for '" << instrumentName << "'.\n";
        g_log.debug() << e.what() << std::endl;
        throw;
      g_log.debug() << "Using instrument IDF XML text contained in nexus file.\n";
    }


    // ---------- Now parse that XML to make the instrument -------------------
    if (!instrumentXml.empty() && !instrumentName.empty())
    {
      InstrumentDefinitionParser parser;
      parser.initialize(instrumentFilename, instrumentName, instrumentXml);
      std::string instrumentNameMangled = parser.getMangledName();
      Instrument_sptr instr;
      // Check whether the instrument is already in the InstrumentDataService
      if ( InstrumentDataService::Instance().doesExist(instrumentNameMangled) )
      {
        // If it does, just use the one from the one stored there
        instr = InstrumentDataService::Instance().retrieve(instrumentNameMangled);
      }
      else
      {
        // Really create the instrument
        instr = parser.parseXML(NULL);
        // Add to data service for later retrieval
        InstrumentDataService::Instance().add(instrumentNameMangled, instr);
      }
      // Now set the instrument
      this->setInstrument(instr);
    }
  //-------------------------------------------------------------------------------------------------
  /** Parse the result of ParameterMap.asString() into the ParameterMap
   * of the current instrument. The instrument needs to have been loaded
   * already, of course.
   *