diff --git a/Code/Mantid/Framework/CurveFitting/CMakeLists.txt b/Code/Mantid/Framework/CurveFitting/CMakeLists.txt index 7d6e399fe93b..c60bcf76181e 100644 --- a/Code/Mantid/Framework/CurveFitting/CMakeLists.txt +++ b/Code/Mantid/Framework/CurveFitting/CMakeLists.txt @@ -54,6 +54,7 @@ set ( SRC_FILES src/GaussianComptonProfile.cpp src/GramCharlierComptonProfile.cpp src/IkedaCarpenterPV.cpp + src/LatticeDomainCreator.cpp src/LatticeFunction.cpp src/LeBailFit.cpp src/LeBailFunction.cpp @@ -171,6 +172,7 @@ set ( INC_FILES inc/MantidCurveFitting/GramCharlierComptonProfile.h inc/MantidCurveFitting/IkedaCarpenterPV.h inc/MantidCurveFitting/Jacobian.h + inc/MantidCurveFitting/LatticeDomainCreator.h inc/MantidCurveFitting/LatticeFunction.h inc/MantidCurveFitting/LeBailFit.h inc/MantidCurveFitting/LeBailFunction.h @@ -279,6 +281,7 @@ set ( TEST_FILES IPeakFunctionCentreParameterNameTest.h IPeakFunctionIntensityTest.h IkedaCarpenterPVTest.h + LatticeDomainCreatorTest.h LatticeFunctionTest.h LeBailFitTest.h LeBailFunctionTest.h diff --git a/Code/Mantid/Framework/CurveFitting/inc/MantidCurveFitting/LatticeDomainCreator.h b/Code/Mantid/Framework/CurveFitting/inc/MantidCurveFitting/LatticeDomainCreator.h new file mode 100644 index 000000000000..290a8347fd0e --- /dev/null +++ b/Code/Mantid/Framework/CurveFitting/inc/MantidCurveFitting/LatticeDomainCreator.h @@ -0,0 +1,81 @@ +#ifndef MANTID_CURVEFITTING_LATTICEDOMAINCREATOR_H_ +#define MANTID_CURVEFITTING_LATTICEDOMAINCREATOR_H_ + +#include "MantidKernel/System.h" +#include "MantidAPI/IDomainCreator.h" +#include "MantidAPI/Workspace.h" +#include "MantidAPI/IPeaksWorkspace.h" + +namespace Mantid { +namespace CurveFitting { + +/** LatticeDomainCreator + + Domain creator for LatticeDomain, which processes IPeaksWorkspace or + an ITableWorkspace with certain columns (HKL, d). + + @author Michael Wedel, Paul Scherrer Institut - SINQ + @date 15/04/2015 + + Copyright © 2015 PSI-NXMM + + This file is part of Mantid. + + Mantid is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + Mantid is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + File change history is stored at: + Code Documentation is available at: +*/ +class DLLExport LatticeDomainCreator : public API::IDomainCreator { +public: + LatticeDomainCreator(Kernel::IPropertyManager *manager, + const std::string &workspacePropertyName, + DomainType domainType = Simple); + + virtual ~LatticeDomainCreator() {} + + virtual void createDomain(boost::shared_ptr &domain, + boost::shared_ptr &values, + size_t i0); + + virtual API::Workspace_sptr createOutputWorkspace( + const std::string &baseName, + API::IFunction_sptr function, + boost::shared_ptr domain, + boost::shared_ptr values, + const std::string &outputWorkspacePropertyName); + + virtual size_t getDomainSize() const; + +protected: + void setWorkspaceFromPropertyManager(); + void + createDomainFromPeaksWorkspace(const API::IPeaksWorkspace_sptr &workspace, + boost::shared_ptr &domain, + boost::shared_ptr &values, + size_t i0); + + void createDomainFromPeakTable(const API::ITableWorkspace_sptr &workspace, + boost::shared_ptr &domain, + boost::shared_ptr &values, + size_t i0); + + std::string m_workspacePropertyName; + API::Workspace_sptr m_workspace; +}; + +} // namespace CurveFitting +} // namespace Mantid + +#endif /* MANTID_CURVEFITTING_LATTICEDOMAINCREATOR_H_ */ diff --git a/Code/Mantid/Framework/CurveFitting/src/LatticeDomainCreator.cpp b/Code/Mantid/Framework/CurveFitting/src/LatticeDomainCreator.cpp new file mode 100644 index 000000000000..35ed74b6ac65 --- /dev/null +++ b/Code/Mantid/Framework/CurveFitting/src/LatticeDomainCreator.cpp @@ -0,0 +1,221 @@ +#include "MantidCurveFitting/LatticeDomainCreator.h" + +#include "MantidAPI/IPeak.h" +#include "MantidAPI/LatticeDomain.h" +#include "MantidAPI/TableRow.h" +#include "MantidAPI/WorkspaceFactory.h" + +#include "MantidDataObjects/Peak.h" +#include "MantidCurveFitting/PawleyFit.h" + +namespace Mantid { +namespace CurveFitting { + +using namespace API; +using namespace Kernel; +using namespace DataObjects; + +LatticeDomainCreator::LatticeDomainCreator( + Kernel::IPropertyManager *manager, const std::string &workspacePropertyName, + API::IDomainCreator::DomainType domainType) + : IDomainCreator(manager, + std::vector(1, workspacePropertyName), + domainType), + m_workspace() { + m_workspacePropertyName = m_workspacePropertyNames.front(); +} + +void LatticeDomainCreator::createDomain( + boost::shared_ptr &domain, + boost::shared_ptr &values, size_t i0) { + setWorkspaceFromPropertyManager(); + + API::IPeaksWorkspace_sptr peaksWorkspace = + boost::dynamic_pointer_cast(m_workspace); + if (peaksWorkspace) { + createDomainFromPeaksWorkspace(peaksWorkspace, domain, values, i0); + } else { + API::ITableWorkspace_sptr tableWorkspace = + boost::dynamic_pointer_cast(m_workspace); + if (tableWorkspace) { + createDomainFromPeakTable(tableWorkspace, domain, values, i0); + } + } +} + +Workspace_sptr LatticeDomainCreator::createOutputWorkspace( + const std::string &baseName, IFunction_sptr function, + boost::shared_ptr domain, + boost::shared_ptr values, + const std::string &outputWorkspacePropertyName) { + boost::shared_ptr latticeDomain = + boost::dynamic_pointer_cast(domain); + + if (!latticeDomain) { + throw std::invalid_argument("LatticeDomain is required."); + } + + ITableWorkspace_sptr tableWorkspace = + WorkspaceFactory::Instance().createTable(); + + if (tableWorkspace) { + tableWorkspace->addColumn("V3D", "HKL"); + tableWorkspace->addColumn("double", "d(obs)"); + tableWorkspace->addColumn("double", "d(calc)"); + tableWorkspace->addColumn("double", "d(obs) - d(calc)"); + + for (size_t i = 0; i < values->size(); ++i) { + double dObs = values->getFitData(i); + double dCalc = values->getCalculated(i); + + TableRow newRow = tableWorkspace->appendRow(); + newRow << (*latticeDomain)[i] << dObs << dCalc << dObs - dCalc; + } + } + + if (m_manager && !outputWorkspacePropertyName.empty()) { + declareProperty( + new WorkspaceProperty(outputWorkspacePropertyName, "", + Kernel::Direction::Output), + "Result workspace"); + + m_manager->setPropertyValue(outputWorkspacePropertyName, + baseName + "Workspace"); + m_manager->setProperty(outputWorkspacePropertyName, tableWorkspace); + } + + return tableWorkspace; +} + +size_t LatticeDomainCreator::getDomainSize() const { + API::IPeaksWorkspace_sptr peaksWorkspace = + boost::dynamic_pointer_cast(m_workspace); + if (peaksWorkspace) { + return peaksWorkspace->getNumberPeaks(); + } + + API::ITableWorkspace_sptr tableWorkspace = + boost::dynamic_pointer_cast(m_workspace); + if (tableWorkspace) { + return tableWorkspace->rowCount(); + } + + return 0; +} + +void LatticeDomainCreator::setWorkspaceFromPropertyManager() { + if (!m_manager) { + throw std::invalid_argument( + "PropertyManager not set in LatticeDomainCreator."); + } + + try { + m_workspace = m_manager->getProperty(m_workspacePropertyName); + } + catch (Kernel::Exception::NotFoundError) { + throw std::invalid_argument( + "Could not extract workspace from PropertyManager."); + } +} + +void LatticeDomainCreator::createDomainFromPeaksWorkspace( + const API::IPeaksWorkspace_sptr &workspace, + boost::shared_ptr &domain, + boost::shared_ptr &values, size_t i0) { + if (!workspace) { + throw std::invalid_argument( + "This function only works on an IPeaksWorkspace-object."); + } + + size_t peakCount = workspace->getNumberPeaks(); + + if (peakCount < 1) { + throw std::range_error("Cannot create a domain for 0 peaks."); + } + + std::vector hkls; + hkls.reserve(peakCount); + + std::vector dSpacings; + dSpacings.reserve(peakCount); + + for (size_t i = 0; i < peakCount; ++i) { + IPeak *currentPeak = workspace->getPeakPtr(static_cast(i)); + hkls.push_back(currentPeak->getHKL()); + dSpacings.push_back(currentPeak->getDSpacing()); + } + + LatticeDomain *latticeDomain = new LatticeDomain(hkls); + domain.reset(latticeDomain); + + if (!values) { + FunctionValues *functionValues = new FunctionValues(*domain); + values.reset(functionValues); + } else { + values->expand(i0 + latticeDomain->size()); + } + + values->setFitData(dSpacings); + + // Set unit weights. + values->setFitWeights(1.0); +} + +void LatticeDomainCreator::createDomainFromPeakTable( + const ITableWorkspace_sptr &workspace, + boost::shared_ptr &domain, + boost::shared_ptr &values, size_t i0) { + + size_t peakCount = workspace->rowCount(); + + if (peakCount < 1) { + throw std::range_error("Cannot create a domain for 0 peaks."); + } + + try { + Column_const_sptr hklColumn = workspace->getColumn("HKL"); + Column_const_sptr dColumn = workspace->getColumn("d"); + + std::vector hkls; + hkls.reserve(peakCount); + + std::vector dSpacings; + dSpacings.reserve(peakCount); + + V3DFromHKLColumnExtractor extractor; + + for (size_t i = 0; i < peakCount; ++i) { + try { + hkls.push_back(extractor(hklColumn, i)); + + double d = (*dColumn)[i]; + dSpacings.push_back(d); + } + catch (std::bad_alloc) { + // do nothing. + } + } + + LatticeDomain *latticeDomain = new LatticeDomain(hkls); + domain.reset(latticeDomain); + + if (!values) { + FunctionValues *functionValues = new FunctionValues(*domain); + values.reset(functionValues); + } else { + values->expand(i0 + latticeDomain->size()); + } + + values->setFitData(dSpacings); + + values->setFitWeights(1.0); + } + catch (std::runtime_error) { + // Column does not exist + throw std::runtime_error("Can not process table, the following columns are " + "required: HKL, d."); + } +} + +} // namespace CurveFitting +} // namespace Mantid diff --git a/Code/Mantid/Framework/CurveFitting/test/LatticeDomainCreatorTest.h b/Code/Mantid/Framework/CurveFitting/test/LatticeDomainCreatorTest.h new file mode 100644 index 000000000000..c1ae02b191ad --- /dev/null +++ b/Code/Mantid/Framework/CurveFitting/test/LatticeDomainCreatorTest.h @@ -0,0 +1,202 @@ +#ifndef MANTID_CURVEFITTING_LATTICEDOMAINCREATORTEST_H_ +#define MANTID_CURVEFITTING_LATTICEDOMAINCREATORTEST_H_ + +#include + +#include "MantidAPI/LatticeDomain.h" +#include "MantidCurveFitting/LatticeDomainCreator.h" +#include "MantidDataObjects/PeaksWorkspace.h" +#include "MantidTestHelpers/WorkspaceCreationHelper.h" + +using Mantid::CurveFitting::LatticeDomainCreator; +using Mantid::Kernel::V3D; + +using namespace Mantid::API; +using namespace Mantid::DataObjects; + +class LatticeDomainCreatorTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static LatticeDomainCreatorTest *createSuite() { + return new LatticeDomainCreatorTest(); + } + static void destroySuite(LatticeDomainCreatorTest *suite) { delete suite; } + + void testGetDomainSizeIPeaksWorkspace() { + Workspace_sptr peaksWs = boost::dynamic_pointer_cast( + WorkspaceCreationHelper::createPeaksWorkspace(5)); + + TestableLatticeDomainCreator dc; + TS_ASSERT_THROWS_NOTHING(dc.setWorkspace(peaksWs)); + + TS_ASSERT_EQUALS(dc.getDomainSize(), 5); + } + + void testGetDomainSizeTableWorkspace() { + ITableWorkspace_sptr table = getValidTableWs(); + + TestableLatticeDomainCreator dc; + TS_ASSERT_THROWS_NOTHING(dc.setWorkspace(table)); + + TS_ASSERT_EQUALS(dc.getDomainSize(), 3); + } + + void testCreateDomainTableWs() { + ITableWorkspace_sptr table = getValidTableWs(); + + TestableLatticeDomainCreator dc; + + FunctionDomain_sptr domain; + FunctionValues_sptr values; + + TS_ASSERT_THROWS_NOTHING( + dc.createDomainFromPeakTable(table, domain, values, 0)); + + TS_ASSERT_EQUALS(domain->size(), 3); + TS_ASSERT_EQUALS(values->size(), 3); + + boost::shared_ptr latticeDomain = + boost::dynamic_pointer_cast(domain); + + TS_ASSERT(latticeDomain); + + TS_ASSERT_EQUALS((*latticeDomain)[0], V3D(1, 1, 1)); + TS_ASSERT_EQUALS((*latticeDomain)[1], V3D(2, 2, 0)); + TS_ASSERT_EQUALS((*latticeDomain)[2], V3D(3, 1, 1)); + + TS_ASSERT_EQUALS(values->getFitData(0), 3.135702); + TS_ASSERT_EQUALS(values->getFitData(1), 1.920217); + TS_ASSERT_EQUALS(values->getFitData(2), 1.637567); + + TS_ASSERT_EQUALS(values->getFitWeight(0), 1.0); + TS_ASSERT_EQUALS(values->getFitWeight(1), 1.0); + TS_ASSERT_EQUALS(values->getFitWeight(2), 1.0); + } + + void testCreateDomainTableWsInvalid() { + ITableWorkspace_sptr invalid = getInvalidTableWs(); + ITableWorkspace_sptr empty = getEmptyTableWs(); + + TestableLatticeDomainCreator dc; + + FunctionDomain_sptr domain; + FunctionValues_sptr values; + + TS_ASSERT_THROWS(dc.createDomainFromPeakTable(invalid, domain, values, 0), + std::runtime_error); + + TS_ASSERT_THROWS(dc.createDomainFromPeakTable(empty, domain, values, 0), + std::range_error); + } + + void testCreateDomainPeaksWorkspace() { + IPeaksWorkspace_sptr peaksWs = boost::dynamic_pointer_cast( + WorkspaceCreationHelper::createPeaksWorkspace(2)); + peaksWs->getPeak(0).setHKL(V3D(1, 1, 1)); + peaksWs->getPeak(1).setHKL(V3D(2, 2, 0)); + + TestableLatticeDomainCreator dc; + + FunctionDomain_sptr domain; + FunctionValues_sptr values; + + TS_ASSERT_THROWS_NOTHING( + dc.createDomainFromPeaksWorkspace(peaksWs, domain, values, 0)); + + TS_ASSERT_EQUALS(domain->size(), 2); + TS_ASSERT_EQUALS(values->size(), 2); + + boost::shared_ptr latticeDomain = + boost::dynamic_pointer_cast(domain); + + TS_ASSERT(latticeDomain); + + TS_ASSERT_EQUALS((*latticeDomain)[0], V3D(1, 1, 1)); + TS_ASSERT_EQUALS((*latticeDomain)[1], V3D(2, 2, 0)); + + TS_ASSERT_EQUALS(values->getFitData(0), peaksWs->getPeak(0).getDSpacing()); + TS_ASSERT_EQUALS(values->getFitData(1), peaksWs->getPeak(1).getDSpacing()); + + TS_ASSERT_EQUALS(values->getFitWeight(0), 1.0); + TS_ASSERT_EQUALS(values->getFitWeight(1), 1.0); + } + + void testCreateOutputWorkspace() { + ITableWorkspace_sptr table = getValidTableWs(); + + IFunction_sptr fn; + FunctionDomain_sptr domain; + FunctionValues_sptr values; + + // Make domain and values + TestableLatticeDomainCreator dc; + dc.createDomainFromPeakTable(table, domain, values, 0); + + values->setCalculated(0, 3.125702); + values->setCalculated(1, 1.930217); + values->setCalculated(2, 1.627567); + + Workspace_sptr outputWs; + TS_ASSERT_THROWS_NOTHING( + outputWs = dc.createOutputWorkspace("base", fn, domain, values, "pp")); + + ITableWorkspace_sptr tableWs = + boost::dynamic_pointer_cast(outputWs); + TS_ASSERT(tableWs); + + TS_ASSERT_EQUALS(tableWs->rowCount(), 3); + TS_ASSERT_DELTA(tableWs->cell(0, 3), 0.01, 1e-6); + TS_ASSERT_DELTA(tableWs->cell(1, 3), -0.01, 1e-6); + TS_ASSERT_DELTA(tableWs->cell(2, 3), 0.01, 1e-6); + } + +private: + ITableWorkspace_sptr getValidTableWs() { + ITableWorkspace_sptr table = WorkspaceFactory::Instance().createTable(); + table->addColumn("V3D", "HKL"); + table->addColumn("double", "d"); + + TableRow newRow = table->appendRow(); + newRow << V3D(1, 1, 1) << 3.135702; + newRow = table->appendRow(); + newRow << V3D(2, 2, 0) << 1.920217; + newRow = table->appendRow(); + newRow << V3D(3, 1, 1) << 1.637567; + + return table; + } + + ITableWorkspace_sptr getEmptyTableWs() { + ITableWorkspace_sptr table = WorkspaceFactory::Instance().createTable(); + table->addColumn("V3D", "HKL"); + table->addColumn("double", "d"); + + return table; + } + + ITableWorkspace_sptr getInvalidTableWs() { + ITableWorkspace_sptr table = WorkspaceFactory::Instance().createTable(); + table->addColumn("V3D", "HKL"); + + TableRow newRow = table->appendRow(); + newRow << V3D(1, 1, 1); + newRow = table->appendRow(); + newRow << V3D(2, 2, 0); + newRow = table->appendRow(); + newRow << V3D(3, 1, 1); + + return table; + } + + class TestableLatticeDomainCreator : public LatticeDomainCreator { + friend class LatticeDomainCreatorTest; + + public: + TestableLatticeDomainCreator() : LatticeDomainCreator(NULL, "") {} + + void setWorkspace(const Workspace_sptr &ws) { m_workspace = ws; } + }; +}; + +#endif /* MANTID_CURVEFITTING_LATTICEDOMAINCREATORTEST_H_ */