diff --git a/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h b/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h index 5d06df47752bebcc0526fb2e80191e2781596526..b4f7577491ad34528c464e34ad28579450976a58 100644 --- a/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h +++ b/Framework/Crystal/inc/MantidCrystal/PeakHKLErrors.h @@ -153,6 +153,11 @@ private: // OptRuns setup void setUpOptRuns(); + + mutable boost::shared_ptr<Geometry::Instrument> instChange; + mutable bool hasParameterMap = false; + mutable Kernel::V3D sampPos; + mutable boost::shared_ptr<const Geometry::ParameterMap> pmapSv; }; } // namespace Crystal } // namespace Mantid diff --git a/Framework/Crystal/src/FilterPeaks.cpp b/Framework/Crystal/src/FilterPeaks.cpp index 3fa0520ff9b3a8216bfb444bc72a2788dd22b1d5..b1b1b7aef61de38d16b3bbdb6103e971ab154b5f 100644 --- a/Framework/Crystal/src/FilterPeaks.cpp +++ b/Framework/Crystal/src/FilterPeaks.cpp @@ -34,6 +34,8 @@ double QMOD(const Mantid::Geometry::IPeak &p) { double SN(const Mantid::Geometry::IPeak &p) { return p.getIntensity() / p.getSigmaIntensity(); } + +double RUN(const Mantid::Geometry::IPeak &p) { return p.getRunNumber(); } } // namespace namespace Mantid { @@ -66,7 +68,7 @@ void FilterPeaks::init() { std::vector<std::string> filters{"h+k+l", "h^2+k^2+l^2", "Intensity", "Signal/Noise", "QMod", "Wavelength", - "DSpacing", "TOF"}; + "DSpacing", "TOF", "RunNumber"}; declareProperty("FilterVariable", "", boost::make_shared<StringListValidator>(filters), "The variable on which to filter the peaks"); @@ -150,6 +152,8 @@ FilterPeaks::FilterFunction FilterPeaks::getFilterVariableFunction( filterFunction = &SN; else if (filterVariable == "QMod") filterFunction = &QMOD; + else if (filterVariable == "RunNumber") + filterFunction = &RUN; else throw std::invalid_argument("Unknown FilterVariable: " + filterVariable); return filterFunction; diff --git a/Framework/Crystal/src/PeakHKLErrors.cpp b/Framework/Crystal/src/PeakHKLErrors.cpp index 3edfe24e37f5f5fc0a06130cca41282b3975c7ae..f7a32da50a7c93b372562836a1d15ee8ebc22294 100644 --- a/Framework/Crystal/src/PeakHKLErrors.cpp +++ b/Framework/Crystal/src/PeakHKLErrors.cpp @@ -190,26 +190,31 @@ boost::shared_ptr<Geometry::Instrument> PeakHKLErrors::getNewInstrument(PeaksWorkspace_sptr Peaks) const { Geometry::Instrument_const_sptr instSave = Peaks->getPeak(0).getInstrument(); auto pmap = boost::make_shared<Geometry::ParameterMap>(); - boost::shared_ptr<const Geometry::ParameterMap> pmapSv = - instSave->getParameterMap(); if (!instSave) { g_log.error(" Peaks workspace does not have an instrument"); throw std::invalid_argument(" Not all peaks have an instrument"); } - auto instChange = boost::shared_ptr<Geometry::Instrument>(); - if (!instSave->isParametrized()) { - - boost::shared_ptr<Geometry::Instrument> instClone(instSave->clone()); - auto Pinsta = boost::make_shared<Geometry::Instrument>(instSave, pmap); - - instChange = Pinsta; - } else // catch(... ) - { - auto P1 = boost::make_shared<Geometry::Instrument>( - instSave->baseInstrument(), instSave->makeLegacyParameterMap()); - instChange = P1; + if (!hasParameterMap) { + pmapSv = instSave->getParameterMap(); + hasParameterMap = true; + if (!instSave->isParametrized()) { + + boost::shared_ptr<Geometry::Instrument> instClone(instSave->clone()); + auto Pinsta = boost::make_shared<Geometry::Instrument>(instSave, pmap); + + instChange = Pinsta; + IComponent_const_sptr sample = instChange->getSample(); + sampPos = sample->getRelativePos(); + } else // catch(... ) + { + auto P1 = boost::make_shared<Geometry::Instrument>( + instSave->baseInstrument(), instSave->makeLegacyParameterMap()); + instChange = P1; + IComponent_const_sptr sample = instChange->getSample(); + sampPos = sample->getRelativePos(); + } } if (!instChange) { @@ -219,11 +224,10 @@ PeakHKLErrors::getNewInstrument(PeaksWorkspace_sptr Peaks) const { //------------------"clone" orig instruments pmap ------------------- cLone(pmap, instSave, pmapSv); - IComponent_const_sptr sample = instChange->getSample(); - V3D sampPos = sample->getRelativePos(); V3D sampOffsets(getParameter("SampleXOffset"), getParameter("SampleYOffset"), getParameter("SampleZOffset")); + IComponent_const_sptr sample = instChange->getSample(); pmap->addPositionCoordinate(sample.get(), std::string("x"), sampPos.X() + sampOffsets.X()); pmap->addPositionCoordinate(sample.get(), std::string("y"), diff --git a/Framework/PythonInterface/plugins/algorithms/OptimizeCrystalPlacementByRun.py b/Framework/PythonInterface/plugins/algorithms/OptimizeCrystalPlacementByRun.py new file mode 100644 index 0000000000000000000000000000000000000000..0ab0361b4cae86e3c2e6443e5433ed6fb64d5f4a --- /dev/null +++ b/Framework/PythonInterface/plugins/algorithms/OptimizeCrystalPlacementByRun.py @@ -0,0 +1,76 @@ +# 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 + +#pylint: disable=no-init + +from mantid.api import PythonAlgorithm, AlgorithmFactory, ITableWorkspaceProperty +from mantid.kernel import Direction +from mantid.simpleapi import * +from mantid import mtd + +# Create an empty table workspace to be populated by a python script. + + +class OptimizeCrystalPlacementByRun(PythonAlgorithm): + + def summary(self): + return "Optimizes the sample position for each run in a peaks workspace." + + def category(self): + return 'Crystal\\Corrections' + + def seeAlso(self): + return [ "OptimizeCrystalPlacement" ] + + def PyInit(self): + # Declare properties + self.declareProperty(ITableWorkspaceProperty("InputWorkspace", "", Direction.Input), + "The name of the peaks workspace that will be optimized.") + self.declareProperty("Tolerance", 0.15, "Tolerance of indexing of peaks.") + self.declareProperty("OutputWorkspace", "", + "The name of the peaks workspace that will be created.") + + def PyExec(self): + ws = self.getProperty("InputWorkspace").value + ws_append = self.getProperty("OutputWorkspace").value + ws_group = 'ws_group' + tolerance = self.getProperty("Tolerance").value + if not ws.sample().hasOrientedLattice(): + FindUBUsingIndexedPeaks(PeaksWorkspace=ws,Tolerance=tolerance) + num,err=IndexPeaks(PeaksWorkspace=ws,Tolerance=tolerance) + logger.notice('Initial Number indexed: %s error: %s\n'%(num, err)) + stats = StatisticsOfTableWorkspace(InputWorkspace=ws) + stat_col = stats.column('Statistic') + minR = int(stats.column('RunNumber')[stat_col.index('Minimum')]) + maxR = int(stats.column('RunNumber')[stat_col.index('Maximum')]) + 1 + AnalysisDataService.remove(stats.getName()) + group = [] + for run in range(minR, maxR): + FilterPeaks(InputWorkspace=ws, OutputWorkspace=str(run), FilterVariable='RunNumber', + FilterValue=run, Operator='=') + run = mtd[str(run)] + peaks = run.getNumberPeaks() + if peaks == 0: + AnalysisDataService.remove(str(run)) + else: + group.append(str(run)) + GroupWorkspaces(InputWorkspaces=group, OutputWorkspace=ws_group) + OptimizeCrystalPlacement(PeaksWorkspace=ws_group, ModifiedPeaksWorkspace=ws_group, AdjustSampleOffsets=True, + MaxSamplePositionChangeMeters=0.005,MaxIndexingError=tolerance) + RenameWorkspace(InputWorkspace=str(minR),OutputWorkspace=ws_append) + for run in range(minR+1, maxR): + if AnalysisDataService.doesExist(str(run)): + CombinePeaksWorkspaces(LHSWorkspace=ws_append, RHSWorkspace=str(run),OutputWorkspace=ws_append) + logger.notice('Optimized %s sample position: %s\n'%(str(run),mtd[str(run)].getPeak(0).getSamplePos())) + AnalysisDataService.remove( str(run)) + num,err=IndexPeaks(PeaksWorkspace=ws_append,Tolerance=tolerance) + logger.notice('After Optimization Number indexed: %s error: %s\n'%(num, err)) + AnalysisDataService.remove(ws_group) + self.setProperty("OutputWorkspace", ws_append) + + +# Register algorithm with Mantid +AlgorithmFactory.subscribe(OptimizeCrystalPlacementByRun) diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt index 0baa419f1c91d63c35bd654abca34a52f652acc7..4603c39cde4eec159288be26df141e29c8fc97de 100644 --- a/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt +++ b/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt @@ -79,6 +79,7 @@ set ( TEST_PY_FILES MuonMaxEntTest.py NMoldyn4InterpolationTest.py NormaliseSpectraTest.py + OptimizeCrystalPlacementByRunTest.py ReflectometryReductionOneLiveDataTest.py ReflectometrySliceEventWorkspaceTest.py RetrieveRunInfoTest.py diff --git a/Framework/PythonInterface/test/python/plugins/algorithms/OptimizeCrystalPlacementByRunTest.py b/Framework/PythonInterface/test/python/plugins/algorithms/OptimizeCrystalPlacementByRunTest.py new file mode 100644 index 0000000000000000000000000000000000000000..9429be6ff88a77351cf3ce73fff45f42079f8e5a --- /dev/null +++ b/Framework/PythonInterface/test/python/plugins/algorithms/OptimizeCrystalPlacementByRunTest.py @@ -0,0 +1,32 @@ +# 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 + +from __future__ import (absolute_import, division, print_function) + +import unittest +from mantid.simpleapi import LoadIsawPeaks, FindUBUsingFFT, IndexPeaks, OptimizeCrystalPlacementByRun +from mantid.api import mtd + + +class OptimizeCrystalPlacementByRunTest(unittest.TestCase): + def test_simple(self): + ws=LoadIsawPeaks("calibrated.peaks") + FindUBUsingFFT(PeaksWorkspace=ws,MinD=2,MaxD=20,Tolerance=0.12) + IndexPeaks(PeaksWorkspace='ws',Tolerance=0.12) + wsd = OptimizeCrystalPlacementByRun(InputWorkspace=ws,OutputWorkspace='wsd',Tolerance=0.12) + result = mtd['wsd'].getPeak(0).getSamplePos() + self.assertAlmostEqual(result.getX(), -0.000678629) + self.assertAlmostEqual(result.getY(), -2.16033e-05) + self.assertAlmostEqual(result.getZ(), 0.00493278) + result = mtd['wsd'].getPeak(8).getSamplePos() + self.assertAlmostEqual(result.getX(), -0.0027929) + self.assertAlmostEqual(result.getY(), -0.00105681) + self.assertAlmostEqual(result.getZ(), 0.00497094) + + + +if __name__=="__main__": + unittest.main() diff --git a/docs/source/algorithms/OptimizeCrystalPlacementByRun-v1.rst b/docs/source/algorithms/OptimizeCrystalPlacementByRun-v1.rst new file mode 100644 index 0000000000000000000000000000000000000000..758e4c699cbc0ffd59b44ee0c1d41b3f669e54a1 --- /dev/null +++ b/docs/source/algorithms/OptimizeCrystalPlacementByRun-v1.rst @@ -0,0 +1,45 @@ +.. algorithm:: + +.. summary:: + +.. relatedalgorithms:: + +.. properties:: + +Description +----------- + +This algorithm basically optimizes h,k, and l offsets from an integer by +varying the parameters sample positions for each run in the peaks workspace. + +The crystal orientation matrix, :ref:`UB matrix <Lattice>`, from the +PeaksWorkspace should index all the runs "very well". Otherwise iterations +that slowly build a :ref:`UB matrix <Lattice>` with corrected sample +orientations may be needed. + + +Usage +----- + +**Example:** + +.. testcode:: ExOptimizeCrystalPlacementByRun + + ws=LoadIsawPeaks("calibrated.peaks") + FindUBUsingFFT(PeaksWorkspace=ws,MinD=2,MaxD=20,Tolerance=0.12) + IndexPeaks(PeaksWorkspace='ws',Tolerance=0.12) + wsd = OptimizeCrystalPlacementByRun(InputWorkspace=ws,OutputWorkspace='wsd',Tolerance=0.12) + print('Optimized %s sample position: %s'%(mtd['wsd'].getPeak(0).getRunNumber(),mtd['wsd'].getPeak(0).getSamplePos())) + print('Optimized %s sample position: %s'%(mtd['wsd'].getPeak(8).getRunNumber(),mtd['wsd'].getPeak(8).getSamplePos())) + +Output: + +.. testoutput:: ExOptimizeCrystalPlacementByRun + + Optimized 71907 sample position: [-0.000678629,-2.16033e-05,0.00493278] + Optimized 72007 sample position: [-0.0027929,-0.00105681,0.00497094] + + +.. categories:: + +.. sourcelink:: diff --git a/docs/source/release/v3.14.0/diffraction.rst b/docs/source/release/v3.14.0/diffraction.rst index 30df5d2faaeccedec9d20d8361829f72f8978756..131bbab7513af585788e1eb4ffb4d5e87798788c 100644 --- a/docs/source/release/v3.14.0/diffraction.rst +++ b/docs/source/release/v3.14.0/diffraction.rst @@ -49,6 +49,7 @@ Improvements - :ref:`IntegratePeaksUsingClusters <algm-IntegratePeaksUsingClusters>` will now treat NaN's as background. - SCD Event Data Reduction Diffraction Interface now adds goniometer for CORELLI and used proton charge as monitor count if no monitors are in input file. - :ref:`SetCrystalLocation <algm-SetCrystalLocation>` is a new algorithm to set the sample location in events workspaces. +- :ref:`OptimizeCrystalPlacementByRun <algm-OptimizeCrystalPlacementByRun>` is new algorithm to update the sample position for each run in a peaks workspace. Bugfixes ########