Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
CreateChunkingFromInstrument.cpp 16.30 KiB
/*WIKI*
TODO: Enter a full wiki-markup description of your algorithm here. You can then use the Build/wiki_maker.py script to generate your full wiki page.
*WIKI*/

#include "MantidAPI/FileProperty.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/TableRow.h"
#include "MantidDataHandling/CreateChunkingFromInstrument.h"
#include "MantidDataObjects/Workspace2D.h"
#include "MantidGeometry/IDetector.h"
#include "MantidKernel/ListValidator.h"
#include <boost/tokenizer.hpp>
#include <nexus/NeXusFile.hpp>
#include <nexus/NeXusException.hpp>

namespace Mantid
{
namespace DataHandling
{
  using namespace Mantid::API;
  using namespace Mantid::DataObjects;
  using namespace Mantid::Geometry;
  using namespace Mantid::Kernel;
  using namespace std;

  typedef boost::tokenizer<boost::char_separator<char> >  tokenizer;

  // Register the algorithm into the AlgorithmFactory
  DECLARE_ALGORITHM(CreateChunkingFromInstrument)
  

  namespace { // anonymous namespace to hide things
    /// Input file name
    const string PARAM_IN_FILE("Filename");
    /// Input workspace parameter name
    const string PARAM_IN_WKSP("InputWorkspace");
    /// Instrument name parameter name
    const string PARAM_INST_NAME("InstrumentName");
    /// Instrument file parameter name
    const string PARAM_INST_FILE("InstrumentFilename");
    /// Explicitly name instrument components
    const string PARAM_CHUNK_NAMES("ChunkNames");
    /// Canned instrument components names
    const string PARAM_CHUNK_BY("ChunkBy");
    /// Recursion depth parameter name
    const string PARAM_MAX_RECURSE("MaxRecursionDepth");
    /// Output workspace parameter name
    const string PARAM_OUT_WKSP("OutputWorkspace");
    /// Maximum number of banks to look for
    const string PARAM_MAX_BANK_NUM("MaxBankNumber");
  }

  //----------------------------------------------------------------------------------------------
  /** Constructor
   */
  CreateChunkingFromInstrument::CreateChunkingFromInstrument()
  {
  }
    
  //----------------------------------------------------------------------------------------------
  /** Destructor
   */
  CreateChunkingFromInstrument::~CreateChunkingFromInstrument()
  {
  }
  

  //----------------------------------------------------------------------------------------------
  /// Algorithm's name for identification. @see Algorithm::name
  const string CreateChunkingFromInstrument::name() const { return "CreateChunkingFromInstrument";};
  
  /// Algorithm's version for identification. @see Algorithm::version
  int CreateChunkingFromInstrument::version() const { return 1;};
  
  /// Algorithm's category for identification. @see Algorithm::category
  const string CreateChunkingFromInstrument::category() const { return "Workflow\\DataHandling";}

  //----------------------------------------------------------------------------------------------
  /// Sets documentation strings for this algorithm
  void CreateChunkingFromInstrument::initDocs()
  {
    this->setWikiSummary("TODO: Enter a quick description of your algorithm.");
    this->setOptionalMessage("TODO: Enter a quick description of your algorithm.");
  }

  //----------------------------------------------------------------------------------------------
  /** Initialize the algorithm's properties.
   */
  void CreateChunkingFromInstrument::init()
  {
    // instrument selection
    string grp1Name("Specify the Instrument");

    std::vector<std::string> exts;
    exts.push_back("_event.nxs");
    exts.push_back(".nxs.h5");
    exts.push_back(".nxs");
    this->declareProperty(new FileProperty(PARAM_IN_FILE, "", FileProperty::OptionalLoad, exts),
        "The name of the event nexus file to read, including its full or relative path." );

    this->declareProperty(new WorkspaceProperty<>(PARAM_IN_WKSP,"",Direction::Input, PropertyMode::Optional),
                          "Optional: An input workspace with the instrument we want to use.");

    this->declareProperty(new PropertyWithValue<string>(PARAM_INST_NAME,"",Direction::Input),
                          "Optional: Name of the instrument to base the ChunkingWorkpace on which to base the GroupingWorkspace.");

    this->declareProperty(new FileProperty(PARAM_INST_FILE, "", FileProperty::OptionalLoad, ".xml"),
                          "Optional: Path to the instrument definition file on which to base the ChunkingWorkpace.");

    this->setPropertyGroup(PARAM_IN_FILE, grp1Name);
    this->setPropertyGroup(PARAM_IN_WKSP, grp1Name);
    this->setPropertyGroup(PARAM_INST_NAME, grp1Name);
    this->setPropertyGroup(PARAM_INST_FILE, grp1Name);

    // chunking
    string grp2Name("Specify Instrument Components");

    declareProperty(PARAM_CHUNK_NAMES,"",
      "Optional: A string of the instrument component names to use as separate groups. "
      "Use / or , to separate multiple groups. "
      "If empty, then an empty GroupingWorkspace will be created.");
    vector<string> grouping;
    grouping.push_back("");
    grouping.push_back("All");
    grouping.push_back("Group");
    grouping.push_back("Column");
    grouping.push_back("bank");
    declareProperty(PARAM_CHUNK_BY, "", boost::make_shared<StringListValidator>(grouping),
        "Only used if GroupNames is empty: All detectors as one group, Groups (East,West for SNAP), Columns for SNAP, detector banks");

    this->setPropertyGroup(PARAM_CHUNK_NAMES, grp2Name);
    this->setPropertyGroup(PARAM_CHUNK_BY, grp2Name);

    // everything else
    declareProperty(PARAM_MAX_RECURSE, 5,
                    "Number of levels to search into the instrument (default=5)");
    declareProperty(PARAM_MAX_BANK_NUM, 300,
                    "Maximum bank number to search for in the instrument");

    declareProperty(new WorkspaceProperty<API::ITableWorkspace>(PARAM_OUT_WKSP,"",Direction::Output),
                    "An output workspace describing the cunking.");
  }

  /// @copydoc Mantid::API::Algorithm::validateInputs
  map<string, string> CreateChunkingFromInstrument::validateInputs()
  {
    map<string, string> result;

    // get the input paramters
    string filename = getPropertyValue(PARAM_IN_FILE);
    MatrixWorkspace_sptr inWS = getProperty(PARAM_IN_WKSP);
    string instName = getPropertyValue(PARAM_INST_NAME);
    string instFilename = getPropertyValue(PARAM_INST_FILE);

    // count how many ways the input instrument was specified
    int numInst = 0;
    if (!filename.empty()) numInst++;
    if (inWS) numInst++;
    if (!instName.empty()) numInst++;
    if (!instFilename.empty()) numInst++;

    // set the error bits
    string msg;
    if (numInst == 0)
    {
      msg = "Must specify instrument one way";
    }
    else if (numInst > 1)
    {
      msg = "Can only specify instrument one way";
    }
    if (!msg.empty())
    {
      result[PARAM_IN_FILE]   = msg;
      result[PARAM_IN_WKSP]   = msg;
      result[PARAM_INST_NAME] = msg;
      result[PARAM_INST_FILE] = msg;
    }

    // get the chunking technology to use
    string chunkNames  = getPropertyValue(PARAM_CHUNK_NAMES);
    string chunkGroups = getPropertyValue(PARAM_CHUNK_BY);
    msg = "";
    if (chunkNames.empty() && chunkGroups.empty())
    {
      msg = "Must specify either " + PARAM_CHUNK_NAMES + " or "
                 + PARAM_CHUNK_BY;
    }
    else if ((!chunkNames.empty()) && (!chunkGroups.empty()))
    {
      msg = "Must specify either " + PARAM_CHUNK_NAMES + " or "
                 + PARAM_CHUNK_BY + " not both";
    }
    if (!msg.empty())
    {
      result[PARAM_CHUNK_NAMES] = msg;
      result[PARAM_CHUNK_BY]    = msg;
    }

    return result;
  }

  /**
   * Returns true if str starts with prefix.
   *
   * @param str The string to check.
   * @param prefix The prefix to look for.
   * @return true if str starts with prefix.
   */
  bool startsWith(const string & str, const string & prefix)
  {
    // can't start with if it is shorter than the prefix
    if (str.length() < prefix.length())
      return false;

    return (str.substr(0, prefix.length()).compare(prefix) == 0);
  }

  /**
   * Find the name of the parent of the component that starts with the
   * supplied prefix.
   *
   * @param comp The component to find the parent of.
   * @param prefix Prefix of parent names to look for.
   * @return The correct parent name. This is an empty string if the name
   * isn't found.
   */
  string parentName(IComponent_const_sptr comp, const string & prefix)
  {
    // handle the special case of the component has the name
    if (startsWith(comp->getName(), prefix))
      return comp->getName();

    // find the parent with the correct name
    IComponent_const_sptr parent = comp->getParent();
    if (parent)
    {
      if (startsWith(parent->getName(), prefix))
        return parent->getName();
      else
        return parentName(parent, prefix);
    }
    else
    {
      return "";
    }
  }

  /**
   * Find the name of the parent of the component that is in the list of
   * parents that are being searched for.
   *
   * @param comp The component to find the parent of.
   * @param names List of parent names to look for.
   * @return The correct parent name. This is an empty string if the name
   * isn't found.
   */
  string parentName(IComponent_const_sptr comp, const vector<string> & names)
  {
    // handle the special case of the component has the name
    for (auto name = names.begin(); name != names.end(); ++name)
      if (name->compare(comp->getName()) == 0)
        return (*name);

    // find the parent with the correct name
    IComponent_const_sptr parent = comp->getParent();
    if (parent)
    {
      // see if this is the parent
      for (auto name = names.begin(); name != names.end(); ++name)
        if (name->compare(parent->getName()) == 0)
          return (*name);

      // or recurse
      return parentName(parent, names);
    }
    else
    {
      return "";
    }
  }

  /**
   * Split a list of instrument components into a vector of strings.
   *
   * @param names Comma separated list of instrument components
   * @return The vector of instrument component names.
   */
  vector<string> getGroupNames(const string & names)
  {
    vector<string> groups;

    // check that there is something
    if (names.empty())
      return groups;

    // do the actual splitting
    const boost::char_separator<char> SEPERATOR(",");
    tokenizer tokens(names, SEPERATOR);
    for (auto item = tokens.begin(); item != tokens.end(); ++item)
    {
      groups.push_back(*item);
    }

    return groups;
  }

  /**
   * Determine the instrument from the various input parameters.
   *
   * @return The correct instrument.
   */
  Instrument_const_sptr CreateChunkingFromInstrument::getInstrument()
  {
    // try the input workspace
    MatrixWorkspace_sptr inWS = getProperty(PARAM_IN_WKSP);
    if (inWS)
    {
      return inWS->getInstrument();
    }

    // temporary workspace to hang everything else off of
    MatrixWorkspace_sptr tempWS(new Workspace2D());
    // name of the instrument
    string instName = getPropertyValue(PARAM_INST_NAME);

    // see if there is an input file
    string filename = getPropertyValue(PARAM_IN_FILE);
    if (!filename.empty())
    {
      string top_entry_name("entry"); // TODO make more flexible

      // get the instrument name from the filename
      size_t n = filename.rfind('/');
      if (n != std::string::npos)
      {
        std::string temp = filename.substr(n+1, filename.size()-n-1);
        n = temp.find('_');
        if (n != std::string::npos && n > 0)
        {
          instName = temp.substr(0, n);
        }
      }

      // read information from the nexus file itself
      try {
        NeXus::File nxsfile(filename);

        // get the run start time
        string start_time;
        nxsfile.openGroup(top_entry_name, "NXentry");
        nxsfile.readData("start_time", start_time);
        tempWS->mutableRun().addProperty("run_start", DateAndTime(start_time).toISO8601String(), true );

        // get the instrument name
        nxsfile.openGroup("instrument", "NXinstrument");
        nxsfile.readData("name", instName);
        nxsfile.closeGroup();

        // Test if IDF exists in file, move on quickly if not
        nxsfile.openPath("instrument/instrument_xml");
        nxsfile.close();
        IAlgorithm_sptr loadInst= createChildAlgorithm("LoadIDFFromNexus",0.0,0.2);
        // Now execute the Child Algorithm. Catch and log any error, but don't stop.
        try
        {
          loadInst->setPropertyValue("Filename", filename);
          loadInst->setProperty<MatrixWorkspace_sptr> ("Workspace", tempWS);
          loadInst->setPropertyValue("InstrumentParentPath",top_entry_name);
          loadInst->execute();
        }
        catch( std::invalid_argument&)
        {
          g_log.error("Invalid argument to LoadIDFFromNexus Child Algorithm ");
        }
        catch (std::runtime_error&)
        {
          g_log.debug("No instrument definition found in "+filename+" at "+top_entry_name+"/instrument");
        }

        if ( loadInst->isExecuted() )
          return tempWS->getInstrument();
        else
          g_log.information("No IDF loaded from Nexus file.");

      } catch (::NeXus::Exception&) {
        g_log.information("No instrument definition found in "+filename+" at "+top_entry_name+"/instrument");
      }
    }

    // run LoadInstrument if other methods have not run
    string instFilename = getPropertyValue(PARAM_INST_FILE);

    Algorithm_sptr childAlg = createChildAlgorithm("LoadInstrument",0.0,0.2);
    childAlg->setProperty<MatrixWorkspace_sptr>("Workspace", tempWS);
    childAlg->setPropertyValue("Filename", instFilename);
    childAlg->setPropertyValue("InstrumentName", instName);
    childAlg->executeAsChildAlg();
    return tempWS->getInstrument();
  }

  //----------------------------------------------------------------------------------------------
  /** Execute the algorithm.
   */
  void CreateChunkingFromInstrument::exec()
  {
    // get the instrument
    Instrument_const_sptr inst = this->getInstrument();

    // setup the output workspace
    ITableWorkspace_sptr strategy = WorkspaceFactory::Instance().createTable("TableWorkspace");
    strategy->addColumn("str", "BankName");
    this->setProperty("OutputWorkspace", strategy);

    // get the correct level of grouping
    string groupLevel = this->getPropertyValue(PARAM_CHUNK_BY);
    vector<string> groupNames = getGroupNames(this->getPropertyValue(PARAM_CHUNK_NAMES));
    if (groupLevel.compare("All") == 0)
    {
      groupNames.clear();
      groupNames.push_back(inst->getName());
    }
    else if (inst->getName().compare("SNAP") == 0 && groupLevel.compare("Group") == 0)
    {
      groupNames.clear();
      groupNames.push_back("East");
      groupNames.push_back("West");
    }

    // set up a progress bar with the "correct" number of steps
    int maxBankNum = this->getProperty(PARAM_MAX_BANK_NUM);
    Progress progress(this, .2, 1., maxBankNum);

    // search the instrument for the bank names
    int maxRecurseDepth = this->getProperty(PARAM_MAX_RECURSE);
    map<string, vector<string> > grouping;
    // cppcheck-suppress syntaxError
    PRAGMA_OMP(parallel for schedule(dynamic, 1) )
    for (int num = 0; num < maxBankNum; ++num)
    {
      PARALLEL_START_INTERUPT_REGION
      ostringstream mess;
      mess<< "bank"<<num;
      IComponent_const_sptr comp = inst->getComponentByName(mess.str(), maxRecurseDepth);
      PARALLEL_CRITICAL(grouping)
      if(comp)
      {
        // get the name of the correct parent
        string parent;
        if (groupNames.empty())
        {
          parent = parentName(comp, groupLevel);
        }
        else
        {
          parent = parentName(comp, groupNames);
        }

        // add it to the correct chunk
        if (!parent.empty())
        {
          if (grouping.count(parent) == 0)
            grouping[parent] = vector<string>();

          grouping[parent].push_back(comp->getName());
        }
      }
      progress.report();
      PARALLEL_END_INTERUPT_REGION
    }
    PARALLEL_CHECK_INTERUPT_REGION

    // fill in the table workspace
    for (auto group = grouping.begin(); group != grouping.end(); ++group)
    {
      stringstream banks;
      for (auto bank = group->second.begin(); bank != group->second.end(); ++bank)
        banks << (*bank) << ",";

      // remove the trailing comma
      string banksStr = banks.str();
      banksStr = banksStr.substr(0, banksStr.size()-1);

      // add it to the table
      TableRow row = strategy->appendRow();
      row << banksStr;
    }

  }


} // namespace DataHandling
} // namespace Mantid