Skip to content
Snippets Groups Projects
SetSampleMaterial.cpp 15.2 KiB
Newer Older
//--------------------------------
// Includes
//--------------------------------
#include "MantidDataHandling/SetSampleMaterial.h"
#include "MantidAPI/ExperimentInfo.h"
#include "MantidAPI/Workspace.h"
#include "MantidAPI/Sample.h"
#include "MantidGeometry/Crystal/OrientedLattice.h"
#include "MantidKernel/MandatoryValidator.h"
#include "MantidKernel/Atom.h"
#include "MantidKernel/EnabledWhenProperty.h"
#include "MantidKernel/Material.h"
#include "MantidKernel/BoundedValidator.h"
#include "MantidKernel/PhysicalConstants.h"
using namespace Mantid::PhysicalConstants;

namespace Mantid {
namespace DataHandling {
// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(SetSampleMaterial)

SetSampleMaterial::SetSampleMaterial() : Mantid::API::Algorithm() {}
SetSampleMaterial::~SetSampleMaterial() {}
const std::string SetSampleMaterial::name() const {
  return "SetSampleMaterial";
}
int SetSampleMaterial::version() const { return (1); }
const std::string SetSampleMaterial::category() const {
  return "Sample;DataHandling";
}
using namespace Mantid::DataHandling;
using namespace Mantid::API;
using namespace Kernel;

/**
* Initialize the algorithm
*/
void SetSampleMaterial::init() {
  using namespace Mantid::Kernel;
  declareProperty(
      new WorkspaceProperty<Workspace>("InputWorkspace", "", Direction::InOut),
      "The workspace with which to associate the sample ");
  declareProperty("ChemicalFormula", "",
                  "ChemicalFormula or AtomicNumber must be given.");
  declareProperty("AtomicNumber", 0,
                  "ChemicalFormula or AtomicNumber must be given");
  declareProperty("MassNumber", 0, "Mass number if ion (default is 0)");
  auto mustBePositive = boost::make_shared<BoundedValidator<double>>();
  mustBePositive->setLower(0.0);
  declareProperty("SampleNumberDensity", EMPTY_DBL(), mustBePositive,
                  "Optional:  This number density of the sample in number of "
                  "formulas per cubic angstrom will be used instead of "
                  "calculated");
  declareProperty("ZParameter", EMPTY_DBL(), mustBePositive,
                  "Number of formula units in unit cell");
  declareProperty("UnitCellVolume", EMPTY_DBL(), mustBePositive,
                  "Unit cell volume in Angstoms^3. Will be calculated from the "
                  "OrientedLattice if not supplied.");
  declareProperty("CoherentXSection", EMPTY_DBL(), mustBePositive,
                  "Optional:  This coherent cross-section for the sample "
                  "material in barns will be used instead of tabulated");
  declareProperty("IncoherentXSection", EMPTY_DBL(), mustBePositive,
                  "Optional:  This incoherent cross-section for the sample "
                  "material in barns will be used instead of tabulated");
  declareProperty("AttenuationXSection", EMPTY_DBL(), mustBePositive,
                  "Optional:  This absorption cross-section for the sample "
                  "material in barns will be used instead of tabulated");
  declareProperty("ScatteringXSection", EMPTY_DBL(), mustBePositive,
                  "Optional:  This total scattering cross-section (coherent + "
                  "incoherent) for the sample material in barns will be used "
                  "instead of tabulated");

  // Perform Group Associations.
  std::string formulaGrp("By Formula or Atomic Number");
  setPropertyGroup("ChemicalFormula", formulaGrp);
  setPropertyGroup("AtomicNumber", formulaGrp);
  setPropertyGroup("MassNumber", formulaGrp);

  std::string densityGrp("Sample Density");
  setPropertyGroup("SampleNumberDensity", densityGrp);
  setPropertyGroup("ZParameter", densityGrp);
  setPropertyGroup("UnitCellVolume", densityGrp);

  std::string specificValuesGrp("Override Cross Section Values");
  setPropertyGroup("CoherentXSection", specificValuesGrp);
  setPropertyGroup("IncoherentXSection", specificValuesGrp);
  setPropertyGroup("AttenuationXSection", specificValuesGrp);
  setPropertyGroup("ScatteringXSection", specificValuesGrp);

  // Extra property settings
  setPropertySettings(
      "AtomicNumber",
      new Kernel::EnabledWhenProperty("ChemicalFormula", Kernel::IS_DEFAULT));
  setPropertySettings("MassNumber", new Kernel::EnabledWhenProperty(
                                        "ChemicalFormula", Kernel::IS_DEFAULT));

  setPropertySettings("UnitCellVolume",
                      new Kernel::EnabledWhenProperty("SampleNumberDensity",
                                                      Kernel::IS_DEFAULT));
  setPropertySettings("ZParameter",
                      new Kernel::EnabledWhenProperty("SampleNumberDensity",
                                                      Kernel::IS_DEFAULT));

  // output properties
  declareProperty(
      "SampleNumberDensityResult", EMPTY_DBL(),
      "The provided or calculated sample number density in atoms/Angstrom^3",
      Direction::Output);
  declareProperty("ReferenceWavelength", EMPTY_DBL(),
                  "The reference wavelength in Angstroms", Direction::Output);
  declareProperty("TotalXSectionResult", EMPTY_DBL(),
                  "The provided or calculated total cross-section for the "
                  "sample material in barns.",
                  Direction::Output);
  declareProperty("IncoherentXSectionResult", EMPTY_DBL(),
                  "The provided or calculated incoherent cross-section for the "
                  "sample material in barns.",
                  Direction::Output);
  declareProperty("CoherentXSectionResult", EMPTY_DBL(),
                  "The provided or calculated coherent cross-section for the "
                  "sample material in barns.",
                  Direction::Output);
  declareProperty("AbsorptionXSectionResult", EMPTY_DBL(),
                  "The provided or calculated Absorption cross-section for the "
                  "sample material in barns.",
                  Direction::Output);
  declareProperty("bAverage", EMPTY_DBL(),
                  "The calculated average scattering length, <b>, for the "
                  "sample material in barns.",
                  Direction::Output);
  declareProperty("bSquaredAverage", EMPTY_DBL(),
                  "The calculated average scattering length squared, <b^2>, "
                  "for the sample material in barns.",
                  Direction::Output);
  declareProperty("NormalizedLaue", EMPTY_DBL(),
                  "The (unitless) normalized Laue diffuse scattering, L.",
                  Direction::Output);
std::map<std::string, std::string> SetSampleMaterial::validateInputs() {
  std::map<std::string, std::string> result;
  const std::string chemicalSymbol = getProperty("ChemicalFormula");
  const int z_number = getProperty("AtomicNumber");
  const int a_number = getProperty("MassNumber");
  if (chemicalSymbol.empty()) {
    if (z_number <= 0) {
      result["ChemicalFormula"] = "Need to specify the material";
  } else {
    if (z_number > 0)
      result["AtomicNumber"] =
          "Cannot specify both ChemicalFormula and AtomicNumber";
  }
  if (a_number > 0 && z_number <= 0)
    result["AtomicNumber"] = "Specified MassNumber without AtomicNumber";
  return result;
}
/**
* Add the cross sections to the neutron atom if they are not-empty
* numbers. All values are in barns.
*
* @param neutron The neutron to update
* @param coh_xs Coherent cross section
* @param inc_xs Incoherent cross section
* @param abs_xs Absorption cross section
* @param tot_xs Total scattering cross section
*/
void SetSampleMaterial::fixNeutron(NeutronAtom &neutron, double coh_xs,
                                   double inc_xs, double abs_xs,
                                   double tot_xs) {
  if (!isEmpty(coh_xs))
    neutron.coh_scatt_xs = coh_xs;
  if (!isEmpty(inc_xs))
    neutron.inc_scatt_xs = inc_xs;
  if (!isEmpty(abs_xs))
    neutron.abs_scatt_xs = abs_xs;
  if (!isEmpty(tot_xs))
    neutron.tot_scatt_xs = tot_xs;
}
/**
* Execute the algorithm
*/
void SetSampleMaterial::exec() {
  // Get the input workspace
  Workspace_sptr workspace = getProperty("InputWorkspace");
  // an ExperimentInfo object has a sample
  ExperimentInfo_sptr expInfo =
      boost::dynamic_pointer_cast<ExperimentInfo>(workspace);
  if (!bool(expInfo)) {
    throw std::runtime_error("InputWorkspace does not have a sample object");
  }
  // determine the sample number density
  double rho = getProperty("SampleNumberDensity"); // in Angstroms-3
  double zParameter = getProperty("ZParameter");   // number of atoms
  // get the scattering information - this will override table values
  double coh_xs = getProperty("CoherentXSection");         // in barns
  double inc_xs = getProperty("IncoherentXSection");       // in barns
  double sigma_atten = getProperty("AttenuationXSection"); // in barns
  double sigma_s = getProperty("ScatteringXSection");      // in barns

  // determine the material
  const std::string chemicalSymbol = getProperty("ChemicalFormula");
  const int z_number = getProperty("AtomicNumber");
  const int a_number = getProperty("MassNumber");

  double b_avg = 0.; // to accumulate <b>
  double b_sq_avg = 0.; // to accumulate <b^2>

  boost::scoped_ptr<Material> mat;
  if (!chemicalSymbol.empty()) {
    // Use chemical formula if given by user
    Material::ChemicalFormula CF;
    try {
      CF = Material::parseChemicalFormula(chemicalSymbol);
    } catch(std::runtime_error &ex) {
      UNUSED_ARG(ex);
      std::stringstream msg;
      msg << "Could not parse chemical formula: " << chemicalSymbol
          << std::endl;
      throw std::runtime_error(msg.str());
    }
    g_log.notice() << "Found " << CF.atoms.size() << " types of atoms in \""
                   << chemicalSymbol << "\"\n";

    NeutronAtom neutron(0, 0., 0., 0., 0., 0.,
                        0.); // starting thing for neutronic information
    if (CF.atoms.size() == 1 && isEmpty(zParameter) && isEmpty(rho)) {
      mat.reset(new Material(chemicalSymbol, CF.atoms[0]->neutron,
                             CF.atoms[0]->number_density));
      // can be directly calculated from the one atom
      b_sq_avg = (CF.atoms[0]->neutron.coh_scatt_length_real*CF.atoms[0]->neutron.coh_scatt_length_real)
              + (CF.atoms[0]->neutron.coh_scatt_length_img*CF.atoms[0]->neutron.coh_scatt_length_img);
      b_avg = sqrt(b_sq_avg);
    } else {
      double numAtoms = 0.; // number of atoms in formula
      for (size_t i = 0; i < CF.atoms.size(); i++) {
        neutron = neutron + CF.numberAtoms[i] * CF.atoms[i]->neutron;

        double b_magnitude_sqrd = (CF.atoms[i]->neutron.coh_scatt_length_real*CF.atoms[i]->neutron.coh_scatt_length_real)
                + (CF.atoms[i]->neutron.coh_scatt_length_img*CF.atoms[i]->neutron.coh_scatt_length_img);
        b_sq_avg += b_magnitude_sqrd;
        b_avg += sqrt(b_magnitude_sqrd);

        g_log.information() << CF.atoms[i] << ": " << CF.atoms[i]->neutron
                            << "\n";
        numAtoms += static_cast<double>(CF.numberAtoms[i]);
      // normalize the accumulated number by the number of atoms
      neutron = (1. / numAtoms) *
                neutron; // funny syntax b/c of operators in neutron atom
      if (isEmpty(rho)) {
        double unitCellVolume = getProperty("UnitCellVolume"); // in Angstroms^3

        // get the unit cell volume from the workspace if it isn't set
        if (isEmpty(unitCellVolume) && expInfo->sample().hasOrientedLattice()) {
          unitCellVolume = expInfo->sample().getOrientedLattice().volume();
          g_log.notice() << "found unit cell volume " << unitCellVolume
                         << " Angstrom^-3\n";
        }
        // density is just number of atoms in the unit cell
        // ...but only calculate it if you have both numbers
        if ((!isEmpty(zParameter)) && (!isEmpty(unitCellVolume)))
          rho = numAtoms * zParameter / unitCellVolume;
      }

      b_avg = b_avg / numAtoms;
      b_sq_avg = b_sq_avg / numAtoms;
      fixNeutron(neutron, coh_xs, inc_xs, sigma_atten, sigma_s);
      // create the material
      mat.reset(new Material(chemicalSymbol, neutron, rho));
  } else {
    // try using the atomic number
    Atom atom = getAtom(static_cast<uint16_t>(z_number),
                        static_cast<uint16_t>(a_number));
    NeutronAtom neutron = atom.neutron;
    fixNeutron(neutron, coh_xs, inc_xs, sigma_atten, sigma_s);

    // create the material
    if (isEmpty(rho)) {
      double unitCellVolume = getProperty("UnitCellVolume"); // in Angstroms^3

      // get the unit cell volume from the workspace if it isn't set
      if (isEmpty(unitCellVolume) && expInfo->sample().hasOrientedLattice()) {
        unitCellVolume = expInfo->sample().getOrientedLattice().volume();
        g_log.notice() << "found unit cell volume " << unitCellVolume
                       << " Angstrom^-3\n";
      }
      // density is just number of atoms in the unit cell
      // ...but only calculate it if you have both numbers
      if ((!isEmpty(zParameter)) && (!isEmpty(unitCellVolume)))
        rho = zParameter / unitCellVolume;
    }
    mat.reset(new Material(chemicalSymbol, neutron, rho));
  }
  double normalizedLaue = (b_sq_avg-b_avg*b_avg)/(b_avg*b_avg);
  if (b_sq_avg == b_avg*b_avg) normalizedLaue = 0.;

  // set the material but leave the geometry unchanged
  auto shapeObject = expInfo->sample().getShape(); // copy
  shapeObject.setMaterial(*mat);
  expInfo->mutableSample().setShape(shapeObject);
  g_log.notice() << "Sample number density ";
  if (isEmpty(mat->numberDensity())) {
    g_log.notice() << "was not specified\n";
  } else {
    g_log.notice() << "= " << mat->numberDensity() << " atoms/Angstrom^3\n";
    setProperty("SampleNumberDensityResult",
                mat->numberDensity()); // in atoms/Angstrom^3
  }
  g_log.notice() << "Cross sections for wavelength = "
                 << NeutronAtom::ReferenceLambda << " Angstroms\n"
                 << "    Coherent " << mat->cohScatterXSection() << " barns\n"
                 << "    Incoherent " << mat->incohScatterXSection()
                 << " barns\n"
                 << "    Total " << mat->totalScatterXSection() << " barns\n"
                 << "    Absorption " << mat->absorbXSection() << " barns\n"
                 << "PDF terms\n"
                 << "    <b>^2 = " << (b_avg*b_avg) << "\n"
                 << "    <b^2> = " << b_sq_avg << "\n"
                 << "    L     = " << normalizedLaue << "\n";
  setProperty("CoherentXSectionResult", mat->cohScatterXSection()); // in barns
  setProperty("IncoherentXSectionResult",
              mat->incohScatterXSection());                        // in barns
  setProperty("TotalXSectionResult", mat->totalScatterXSection()); // in barns
  setProperty("AbsorptionXSectionResult", mat->absorbXSection());  // in barns
  setProperty("ReferenceWavelength",
              NeutronAtom::ReferenceLambda); // in Angstroms
  setProperty("bAverage", b_avg);
  setProperty("bSquaredAverage", b_sq_avg);
  setProperty("NormalizedLaue", normalizedLaue);

  if (isEmpty(rho)) {
    g_log.notice("Unknown value for number density");
  } else {
    double smu = mat->totalScatterXSection(NeutronAtom::ReferenceLambda) * rho;
    double amu = mat->absorbXSection(NeutronAtom::ReferenceLambda) * rho;
    g_log.notice() << "Anvred LinearScatteringCoef = " << smu << " 1/cm\n"
                   << "Anvred LinearAbsorptionCoef = " << amu << " 1/cm\n";
  // Done!
  progress(1);
}
}