Skip to content
Snippets Groups Projects
Material.cpp 20.5 KiB
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 "MantidKernel/Material.h"
#include "MantidKernel/Atom.h"
#include "MantidKernel/StringTokenizer.h"
#include <NeXusFile.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/make_shared.hpp>
Peterson, Peter's avatar
Peterson, Peter committed
#include <numeric>
namespace Mantid {

namespace Kernel {
using tokenizer = Mantid::Kernel::StringTokenizer;
using str_pair = std::pair<std::string, std::string>;

using PhysicalConstants::Atom;
using PhysicalConstants::NeutronAtom;
using PhysicalConstants::getAtom;
namespace {
Peterson, Peter's avatar
Peterson, Peter committed
constexpr double INV_FOUR_PI = 1. / (4. * M_PI);

inline double scatteringLength(const double real, const double imag) {
  double length;
  if (imag == 0.) {
    length = std::abs(real);
  } else if (real == 0.) {
    length = std::abs(imag);
  } else {
Peterson, Peter's avatar
Peterson, Peter committed
    length = std::hypot(real, imag);
  }

  if (!std::isnormal(length)) {
    return 0.;
  } else {
    return length;
  }
}

inline double scatteringXS(const double realLength, const double imagLength) {
  double lengthSqrd = (realLength * realLength) + (imagLength * imagLength);

  if (!std::isnormal(lengthSqrd)) {
    return 0.;
  } else {
    return .04 * M_PI * lengthSqrd;
  }
} // namespace
Peterson, Peter's avatar
Peterson, Peter committed
Mantid::Kernel::Material::FormulaUnit::FormulaUnit(
Peterson, Peter's avatar
Peterson, Peter committed
    const boost::shared_ptr<PhysicalConstants::Atom> &atom,
    const double multiplicity)
Peterson, Peter's avatar
Peterson, Peter committed
    : atom(atom), multiplicity(multiplicity) {}
Peterson, Peter's avatar
Peterson, Peter committed
Mantid::Kernel::Material::FormulaUnit::FormulaUnit(
    const PhysicalConstants::Atom &atom, const double multiplicity)
Peterson, Peter's avatar
Peterson, Peter committed
    : atom(boost::make_shared<PhysicalConstants::Atom>(atom)),
      multiplicity(multiplicity) {}
/**
 * Construct an "empty" material. Everything returns zero
 */
Material::Material()
Peterson, Peter's avatar
Peterson, Peter committed
    : m_name(), m_chemicalFormula(), m_atomTotal(0.0), m_numberDensity(0.0),
      m_temperature(0.0), m_pressure(0.0) {}
 * Construct a material object
 * @param name :: The name of the material
 * @param formula :: The chemical formula
 * @param numberDensity :: Density in atoms / Angstrom^3
 * @param temperature :: The temperature in Kelvin (Default = 300K)
 * @param pressure :: Pressure in kPa (Default: 101.325 kPa)
 */
Peterson, Peter's avatar
Peterson, Peter committed
Material::Material(const std::string &name, const ChemicalFormula &formula,
                   const double numberDensity, const double temperature,
                   const double pressure)
    : m_name(name), m_atomTotal(0.0), m_numberDensity(numberDensity),
      m_temperature(temperature), m_pressure(pressure) {
  m_chemicalFormula.assign(formula.begin(), formula.end());
  this->countAtoms();
}

/**
 * Construct a material object
 * @param name :: The name of the material
 * @param atom :: The neutron atom to take scattering infrmation from
 * @param numberDensity :: Density in atoms / Angstrom^3
 * @param temperature :: The temperature in Kelvin (Default = 300K)
 * @param pressure :: Pressure in kPa (Default: 101.325 kPa)
 */
Material::Material(const std::string &name,
                   const PhysicalConstants::NeutronAtom &atom,
                   const double numberDensity, const double temperature,
                   const double pressure)
Peterson, Peter's avatar
Peterson, Peter committed
    : m_name(name), m_chemicalFormula(), m_atomTotal(1.0),
      m_numberDensity(numberDensity), m_temperature(temperature),
      m_pressure(pressure) {
  if (atom.z_number == 0) { // user specified atom
    m_chemicalFormula.emplace_back(atom, 1.);
  } else if (atom.a_number > 0) { // single isotope
    m_chemicalFormula.emplace_back(getAtom(atom.z_number, atom.a_number), 1.);
Peterson, Peter's avatar
Peterson, Peter committed
  } else { // isotopic average
    m_chemicalFormula.emplace_back(atom, 1.);
// update the total atom count
void Material::countAtoms() {
  m_atomTotal = std::accumulate(std::begin(m_chemicalFormula),
Peterson, Peter's avatar
Peterson, Peter committed
                                std::end(m_chemicalFormula), 0.,
                                [](double subtotal, const FormulaUnit &right) {
Peterson, Peter's avatar
Peterson, Peter committed
                                  return subtotal + right.multiplicity;
                                });

/**
 * Returns the name
 * @returns A string containing the name of the material
 */
const std::string &Material::name() const { return m_name; }

Peterson, Peter's avatar
Peterson, Peter committed
const Material::ChemicalFormula &Material::chemicalFormula() const {
  return m_chemicalFormula;
}
/**
 * Get the number density
 * @returns The number density of the material in atoms / Angstrom^3
 */
double Material::numberDensity() const { return m_numberDensity; }

/**
 * Get the temperature
 * @returns The temperature of the material in Kelvin
 */
double Material::temperature() const { return m_temperature; }

/**
 * Get the pressure
 * @returns The pressure of the material
 */
double Material::pressure() const { return m_pressure; }

/**
 * Get the coherent scattering cross section according to Sears eqn 7.
 *
 * @param lambda :: The wavelength to evaluate the cross section
 * @returns The value of the coherent scattering cross section at
 * the given wavelength
 */
double Material::cohScatterXSection(const double lambda) const {
  if (m_chemicalFormula.size() == 1)
Peterson, Peter's avatar
Peterson, Peter committed
    return m_chemicalFormula.front().atom->neutron.coh_scatt_xs;
  return scatteringXS(cohScatterLengthReal(), cohScatterLengthImg());
 * Get the incoherent scattering cross section according to Sears eqn 16
 *
 * @param lambda :: The wavelength to evaluate the cross section
 * @returns The value of the coherent scattering cross section at
 * the given wavelength
 */
double Material::incohScatterXSection(const double lambda) const {
  if (m_chemicalFormula.size() == 1)
Peterson, Peter's avatar
Peterson, Peter committed
    return m_chemicalFormula.front().atom->neutron.inc_scatt_xs;
  return totalScatterXSection() - cohScatterXSection();
 * Get the total scattering cross section following Sears eqn 13.
 *
 * @param lambda :: The wavelength to evaluate the cross section
 * @returns The value of the total scattering cross section at
 * the given wavelength
 */
double Material::totalScatterXSection(const double lambda) const {
  UNUSED_ARG(lambda);
  if (m_chemicalFormula.size() == 1)
Peterson, Peter's avatar
Peterson, Peter committed
    return m_chemicalFormula.front().atom->neutron.tot_scatt_xs;
Peterson, Peter's avatar
Peterson, Peter committed
  const double weightedTotal =
      std::accumulate(std::begin(m_chemicalFormula),
                      std::end(m_chemicalFormula), 0.,
                      [](double subtotal, const FormulaUnit &right) {
                        return subtotal + right.atom->neutron.tot_scatt_xs *
                                              right.multiplicity;
                      }) /
Peterson, Peter's avatar
Peterson, Peter committed
      m_atomTotal;
Peterson, Peter's avatar
Peterson, Peter committed
  if (!std::isnormal(weightedTotal)) {
    return 0.;
  } else {
    return weightedTotal;
  }
 * Get the absorption cross section for a given wavelength
 * according to Sears eqn 14
 *
 * CURRENTLY This assumes a linear dependence on the wavelength with the
 * reference wavelength = NeutronAtom::ReferenceLambda angstroms.
 *
 * @param lambda :: The wavelength to evaluate the cross section
 * @returns The value of the absoprtion cross section at
 * the given wavelength
 */
double Material::absorbXSection(const double lambda) const {
  double weightedTotal;

  if (m_chemicalFormula.size() == 1) {
Peterson, Peter's avatar
Peterson, Peter committed
    weightedTotal = m_chemicalFormula.front().atom->neutron.abs_scatt_xs;
  } else {
    weightedTotal =
        std::accumulate(std::begin(m_chemicalFormula),
                        std::end(m_chemicalFormula), 0.,
                        [](double subtotal, const FormulaUnit &right) {
                          return subtotal + right.atom->neutron.abs_scatt_xs *
                                                right.multiplicity;
                        }) /
Peterson, Peter's avatar
Peterson, Peter committed

  if (!std::isnormal(weightedTotal)) {
    return 0.;
  } else {
    return weightedTotal * (lambda / NeutronAtom::ReferenceLambda);
  }
}

// NOTE: the angstrom^-2 to barns and the angstrom^-1 to cm^-1
// will cancel for mu to give units: cm^-1
double Material::linearAbsorpCoef(const double lambda) const {
  return absorbXSection(NeutronAtom::ReferenceLambda) * 100. * numberDensity() *
         lambda / NeutronAtom::ReferenceLambda;
}

// This must match the values that come from the scalar version
std::vector<double> Material::linearAbsorpCoef(
    std::vector<double>::const_iterator lambdaBegin,
    std::vector<double>::const_iterator lambdaEnd) const {
  const double linearCoefByWL =
      absorbXSection(PhysicalConstants::NeutronAtom::ReferenceLambda) * 100. *
      numberDensity() / PhysicalConstants::NeutronAtom::ReferenceLambda;

  std::vector<double> linearCoef(std::distance(lambdaBegin, lambdaEnd));

  std::transform(lambdaBegin, lambdaEnd, linearCoef.begin(),
                 [linearCoefByWL](const double lambda) {
                   return linearCoefByWL * lambda;
                 });

  return linearCoef;
}
/// According to Sears eqn 12
double Material::cohScatterLength(const double lambda) const {
  UNUSED_ARG(lambda);

  if (m_chemicalFormula.size() == 1)
Peterson, Peter's avatar
Peterson, Peter committed
    return m_chemicalFormula.front().atom->neutron.coh_scatt_length;
Peterson, Peter's avatar
Peterson, Peter committed
  // these have already accounted for single atom case
  return scatteringLength(cohScatterLengthReal(), cohScatterLengthImg());
/// According to Sears eqn 7
double Material::incohScatterLength(const double lambda) const {
  UNUSED_ARG(lambda);

  if (m_chemicalFormula.size() == 1)
Peterson, Peter's avatar
Peterson, Peter committed
    return m_chemicalFormula.front().atom->neutron.inc_scatt_length;
  return scatteringLength(incohScatterLengthReal(), incohScatterLengthImg());
Peterson, Peter's avatar
Peterson, Peter committed
double Material::cohScatterLengthReal(const double lambda) const {
  UNUSED_ARG(lambda);
  if (m_chemicalFormula.size() == 1)
Peterson, Peter's avatar
Peterson, Peter committed
    return m_chemicalFormula.front().atom->neutron.coh_scatt_length_real;
Peterson, Peter's avatar
Peterson, Peter committed
  const double weightedTotal =
      std::accumulate(
          std::begin(m_chemicalFormula), std::end(m_chemicalFormula), 0.,
          [](double subtotal, const FormulaUnit &right) {
            return subtotal + right.atom->neutron.coh_scatt_length_real *
                                  right.multiplicity;
          }) /
Peterson, Peter's avatar
Peterson, Peter committed
      m_atomTotal;

  if (!std::isnormal(weightedTotal)) {
    return 0.;
  } else {
    return weightedTotal;
  }
}

double Material::cohScatterLengthImg(const double lambda) const {
  if (m_chemicalFormula.size() == 1)
Peterson, Peter's avatar
Peterson, Peter committed
    return m_chemicalFormula.front().atom->neutron.coh_scatt_length_img;
Peterson, Peter's avatar
Peterson, Peter committed
  const double weightedTotal =
      std::accumulate(
          std::begin(m_chemicalFormula), std::end(m_chemicalFormula), 0.,
          [](double subtotal, const FormulaUnit &right) {
            return subtotal + right.atom->neutron.coh_scatt_length_img *
                                  right.multiplicity;
          }) /
Peterson, Peter's avatar
Peterson, Peter committed
      m_atomTotal;

  if (!std::isnormal(weightedTotal)) {
    return 0.;
  } else {
    return weightedTotal;
  }
}

/// Not explicitly in Sears, but following eqn 12
double Material::incohScatterLengthReal(const double lambda) const {
  if (m_chemicalFormula.size() == 1)
Peterson, Peter's avatar
Peterson, Peter committed
    return m_chemicalFormula.front().atom->neutron.inc_scatt_length_real;
Peterson, Peter's avatar
Peterson, Peter committed
  const double weightedTotal =
      std::accumulate(
          std::begin(m_chemicalFormula), std::end(m_chemicalFormula), 0.,
          [](double subtotal, const FormulaUnit &right) {
            return subtotal + right.atom->neutron.inc_scatt_length_real *
                                  right.multiplicity;
          }) /
Peterson, Peter's avatar
Peterson, Peter committed
      m_atomTotal;
Peterson, Peter's avatar
Peterson, Peter committed
  if (!std::isnormal(weightedTotal)) {
    return 0.;
  } else {
    return weightedTotal;
  }
}

/// Not explicitly in Sears, but following eqn 12
double Material::incohScatterLengthImg(const double lambda) const {
  if (m_chemicalFormula.size() == 1)
Peterson, Peter's avatar
Peterson, Peter committed
    return m_chemicalFormula.front().atom->neutron.inc_scatt_length_img;
Peterson, Peter's avatar
Peterson, Peter committed
  const double weightedTotal =
      std::accumulate(
          std::begin(m_chemicalFormula), std::end(m_chemicalFormula), 0.,
          [](double subtotal, const FormulaUnit &right) {
            return subtotal + right.atom->neutron.inc_scatt_length_img *
                                  right.multiplicity;
          }) /
Peterson, Peter's avatar
Peterson, Peter committed
      m_atomTotal;
Peterson, Peter's avatar
Peterson, Peter committed
  if (!std::isnormal(weightedTotal)) {
    return 0.;
  } else {
    return weightedTotal;
  }
double Material::totalScatterLength(const double lambda) const {
  if (m_chemicalFormula.size() == 1)
Peterson, Peter's avatar
Peterson, Peter committed
    return m_chemicalFormula.front().atom->neutron.tot_scatt_length;
  const double crossSection = totalScatterXSection();
  return 10. * std::sqrt(crossSection) * INV_FOUR_PI;
Peterson, Peter's avatar
Peterson, Peter committed
double Material::cohScatterLengthSqrd(const double lambda) const {
  // these have already acconted for single atom case
Peterson, Peter's avatar
Peterson, Peter committed
  const double real = this->cohScatterLengthReal();
  const double imag = this->cohScatterLengthImg();
Peterson, Peter's avatar
Peterson, Peter committed
  double lengthSqrd;
  if (imag == 0.) {
    lengthSqrd = real * real;
  } else if (real == 0.) {
    lengthSqrd = imag * imag;
  } else {
    lengthSqrd = real * real + imag * imag;
  }

  if (!std::isnormal(lengthSqrd)) {
    return 0.;
  } else {
    return lengthSqrd;
  }
Peterson, Peter's avatar
Peterson, Peter committed
double Material::incohScatterLengthSqrd(const double lambda) const {
  // cross section has this properly averaged already
  const double crossSection = incohScatterXSection();

  // 1 barn = 100 fm^2
  return 100. * crossSection * INV_FOUR_PI;
Peterson, Peter's avatar
Peterson, Peter committed
double Material::totalScatterLengthSqrd(const double lambda) const {
  // cross section has this properly averaged already
  const double crossSection = totalScatterXSection();
  // 1 barn = 100 fm^2
  return 100. * crossSection * INV_FOUR_PI;
}

/** Save the object to an open NeXus file.
 * @param file :: open NeXus file
 * @param group :: name of the group to create
 */
void Material::saveNexus(::NeXus::File *file, const std::string &group) const {
  file->makeGroup(group, "NXdata", true);
  file->putAttr("version", 2);
  file->putAttr("name", m_name);

  // determine how the information will be stored
Peterson, Peter's avatar
Peterson, Peter committed
  std::string style = "formula"; // default is a chemical formula
  if (m_chemicalFormula.empty()) {
    style = "empty";
  } else if (m_chemicalFormula.size() == 1) {
    if (m_chemicalFormula[0].atom->symbol == "user") {
      style = "userdefined";
    }
  }
  file->putAttr("formulaStyle", style);

  // write the actual information out
  if (style == "formula") {
    std::stringstream formula;
Peterson, Peter's avatar
Peterson, Peter committed
    for (const auto &formulaUnit : m_chemicalFormula) {
      if (formulaUnit.atom->a_number != 0) {
        formula << "(";
      }
      formula << formulaUnit.atom->symbol;
      if (formulaUnit.atom->a_number != 0) {
        formula << formulaUnit.atom->a_number << ")";
      }
      formula << formulaUnit.multiplicity << " ";
    }
    file->writeData("chemical_formula", formula.str());
  } else if (style == "userdefined") {
Peterson, Peter's avatar
Peterson, Peter committed
    file->writeData("coh_scatt_length_real",
                    m_chemicalFormula[0].atom->neutron.coh_scatt_length_real);
    file->writeData("coh_scatt_length_img",
                    m_chemicalFormula[0].atom->neutron.coh_scatt_length_img);
    file->writeData("inc_scatt_length_real",
                    m_chemicalFormula[0].atom->neutron.inc_scatt_length_real);
    file->writeData("inc_scatt_length_img",
                    m_chemicalFormula[0].atom->neutron.inc_scatt_length_img);
    file->writeData("coh_scatt_xs",
                    m_chemicalFormula[0].atom->neutron.coh_scatt_xs);
    file->writeData("inc_scatt_xs",
                    m_chemicalFormula[0].atom->neutron.inc_scatt_xs);
    file->writeData("tot_scatt_xs",
                    m_chemicalFormula[0].atom->neutron.tot_scatt_xs);
    file->writeData("abs_scatt_xs",
                    m_chemicalFormula[0].atom->neutron.abs_scatt_xs);
    file->writeData("tot_scatt_length",
                    m_chemicalFormula[0].atom->neutron.tot_scatt_length);
    file->writeData("coh_scatt_length",
                    m_chemicalFormula[0].atom->neutron.coh_scatt_length);
    file->writeData("inc_scatt_length",
                    m_chemicalFormula[0].atom->neutron.inc_scatt_length);
  file->writeData("number_density", m_numberDensity);
  file->writeData("temperature", m_temperature);
  file->writeData("pressure", m_pressure);
  file->closeGroup();
}

/** Load the object from an open NeXus file.
 * @param file :: open NeXus file
 * @param group :: name of the group to open
 */
void Material::loadNexus(::NeXus::File *file, const std::string &group) {
  file->openGroup(group, "NXdata");
  file->getAttr("name", m_name);
  int version;
  file->getAttr("version", version);

  if (version == 1) {
    // Find the element
    uint16_t element_Z, element_A;
    file->readData("element_Z", element_Z);
    file->readData("element_A", element_A);
    try {
      m_chemicalFormula.clear();
      if (element_Z > 0) {
        m_chemicalFormula.emplace_back(getAtom(element_Z, element_A), 1);
Peterson, Peter's avatar
Peterson, Peter committed
        m_chemicalFormula.emplace_back(
            Mantid::PhysicalConstants::getNeutronAtom(element_Z, element_A), 1);
      }
    } catch (std::runtime_error &) { /* ignore and use the default */
    }
  } else if (version == 2) {
    std::string style;
    file->getAttr("formulaStyle", style);
    if (style == "formula") {
      std::string formula;
      file->readData("chemical_formula", formula);
      this->m_chemicalFormula = Material::parseChemicalFormula(formula);
      this->countAtoms();
    } else if (style == "userdefined") {
      NeutronAtom neutron;
      file->readData("coh_scatt_length_real", neutron.coh_scatt_length_real);
Peterson, Peter's avatar
Peterson, Peter committed
      file->readData("coh_scatt_length_img", neutron.coh_scatt_length_img);
      file->readData("inc_scatt_length_real", neutron.inc_scatt_length_real);
Peterson, Peter's avatar
Peterson, Peter committed
      file->readData("inc_scatt_length_img", neutron.inc_scatt_length_img);
      file->readData("coh_scatt_xs", neutron.coh_scatt_xs);
      file->readData("inc_scatt_xs", neutron.inc_scatt_xs);
      file->readData("tot_scatt_xs", neutron.tot_scatt_xs);
      file->readData("abs_scatt_xs", neutron.abs_scatt_xs);
      file->readData("tot_scatt_length", neutron.tot_scatt_length);
      file->readData("coh_scatt_length", neutron.coh_scatt_length);
      file->readData("inc_scatt_length", neutron.inc_scatt_length);

      m_chemicalFormula.emplace_back(boost::make_shared<Atom>(neutron), 1);
    }
    // the other option is empty which does not need to be addressed
Peterson, Peter's avatar
Peterson, Peter committed
  } else {
    throw std::runtime_error(
        "Only know how to read version 1 or 2 for Material");
  this->countAtoms();
  file->readData("number_density", m_numberDensity);
  file->readData("temperature", m_temperature);
  file->readData("pressure", m_pressure);
  file->closeGroup();
}
namespace { // anonymous namespace to hide the function
str_pair
getAtomName(const std::string &text) // TODO change to get number after letters
{
  // one character doesn't need
  if (text.size() <= 1)
    return std::make_pair(text, "");

  // check the second character
  const char *s;
  s = text.c_str();
  if ((s[1] >= '0' && s[1] <= '9') || s[1] == '.')
    return std::make_pair(text.substr(0, 1), text.substr(1));
  else
    return std::make_pair(text.substr(0, 2), text.substr(2));
}
} // namespace

Material::ChemicalFormula
Material::parseChemicalFormula(const std::string chemicalSymbol) {
  Material::ChemicalFormula CF;

  tokenizer tokens(chemicalSymbol, " -",
                   Mantid::Kernel::StringTokenizer::TOK_IGNORE_EMPTY);
  for (const auto &atom : tokens) {
      float numberAtoms = 1;
      uint16_t aNumber = 0;

      // split out the isotope bit
      if (atom.find('(') != std::string::npos) {
        // error check
        size_t end = atom.find(')');
        if (end == std::string::npos) {
          std::stringstream msg;
          msg << "Failed to parse isotope \"" << atom << "\"";
          throw std::runtime_error(msg.str());
        }

        // get the number of atoms
        std::string numberAtomsStr = atom.substr(end + 1);
        if (!numberAtomsStr.empty())
          numberAtoms = boost::lexical_cast<float>(numberAtomsStr);

        // split up the atom and isotope number
        name = atom.substr(1, end - 1);
        str_pair temp = getAtomName(name);

        name = temp.first;
        aNumber = boost::lexical_cast<uint16_t>(temp.second);
      } else // for non-isotopes
        str_pair temp = getAtomName(atom);
        name = temp.first;
        if (!temp.second.empty())
          numberAtoms = boost::lexical_cast<float>(temp.second);
      CF.emplace_back(getAtom(name, aNumber), static_cast<double>(numberAtoms));
    } catch (boost::bad_lexical_cast &e) {
      std::stringstream msg;
      msg << "While trying to parse atom \"" << atom
          << "\" encountered bad_lexical_cast: " << e.what();
      throw std::runtime_error(msg.str());
Stuart Ansell's avatar
Stuart Ansell committed

} // namespace Kernel
} // namespace Mantid