diff --git a/Code/Mantid/Framework/PythonInterface/plugins/algorithms/PoldiMerge.py b/Code/Mantid/Framework/PythonInterface/plugins/algorithms/PoldiMerge.py new file mode 100644 index 000000000000..8ffb4ffcc946 --- /dev/null +++ b/Code/Mantid/Framework/PythonInterface/plugins/algorithms/PoldiMerge.py @@ -0,0 +1,108 @@ +"""*WIKI* +PoldiMerge takes a list of workspace names and adds the counts, resulting in a new workspace. The difference to Plus is that it performs some POLDI-specific tests +that determine whether merging those files is sensible or not. The following requirements have to be fulfilled: + +* The time-binning (x-data) of all workspaces must match (offset as well as width of time bins) +* These quantities from the sample log: +** Position of the sample table (x, y and z) +** Rotation speed of the chopper + +The algorithm does not perform partial summation - if any of the workspaces does not fulfill the criteria, the intermediate result is discarded. +*WIKI*""" + +from mantid.kernel import StringArrayProperty, Direction +from mantid.simpleapi import * +from mantid.api import * + +import numpy as np + +class PoldiMerge(PythonAlgorithm): + comparedPropertyNames = ["TablePositionX", "TablePositionY", "TablePositionZ", "ChopperSpeed"] + outputWorkspaceName = None + + def category(self): + return "SINQ\\Poldi" + + def name(self): + return "PoldiMerge" + + def PyInit(self): + self.declareProperty(StringArrayProperty(name="WorkspaceNames", + direction=Direction.Input), + doc="List of Workspace names to merge.") + + self.declareProperty(WorkspaceProperty(name="OutputWorkspace", + defaultValue="MergedPoldiWorkspaces", + direction=Direction.Output), + doc="Workspace where all counts from the list workspaces have been added") + + def PyExec(self): + workspaceNames = self.getProperty("WorkspaceNames").value + self.outputWorkspaceName = self.getProperty("OutputWorkspace").valueAsStr + + self.log().information("Workspaces to merge: %i" % (len(workspaceNames))) + + if False in [AnalysisDataService.doesExist(x) for x in workspaceNames]: + raise KeyError("Not all strings in the input list are valid workspace names.") + + workspaces = [AnalysisDataService.retrieve(x) for x in workspaceNames] + + # Create a target workspace for the summation. It inherits the log of + # the first workspace used in the summation. + output = WorkspaceFactory.create(workspaces[0]) + + xdata = workspaces[0].dataX(0) + ydata = np.zeros(len(xdata)) + + for h in range(output.getNumberHistograms()): + output.setX(h, xdata) + output.setY(h, ydata) + + AnalysisDataService.addOrReplace(self.outputWorkspaceName, output) + + while workspaces: + current = workspaces.pop(0) + + try: + if self.canMerge(output, current): + output += current + except RuntimeError as error: + self.handleError(error) + + self.setProperty("OutputWorkspace", output) + + def canMerge(self, leftWorkspace, rightWorkspace): + if not self.timingsMatch(leftWorkspace.dataX(0), rightWorkspace.dataX(0)): + raise RuntimeError("Timings don't match") + + leftRun = leftWorkspace.getRun() + rightRun = rightWorkspace.getRun() + + return self.propertiesMatch(leftRun, rightRun) + + def timingsMatch(self, leftXData, rightXData): + leftDeltaX = leftXData[1] - leftXData[0] + rightDeltaX = rightXData[1] - rightXData[0] + + return abs(leftDeltaX - rightDeltaX) < 1e-4 and abs(rightXData[0] - leftXData[0]) < 1e-4 + + def propertiesMatch(self, leftRun, rightRun): + for propertyName in self.comparedPropertyNames: + if abs(self.getPropertyValue(leftRun.getProperty(propertyName)) - self.getPropertyValue(rightRun.getProperty(propertyName))) > 1e-4: + raise RuntimeError("Property '%s' does not match" % (propertyName)) + + return True + + def getPropertyValue(self, runProperty): + try: + return runProperty.value[0] + except: + return runProperty.value + + def handleError(self, error): + if AnalysisDataService.doesExist(self.outputWorkspaceName): + AnalysisDataService.remove(self.outputWorkspaceName) + + raise RuntimeError("Workspaces can not be merged. %s. Aborting." % (str(error))) + +AlgorithmFactory.subscribe(PoldiMerge) \ No newline at end of file diff --git a/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt b/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt index 7e6c1d1cb608..ad5915559acd 100644 --- a/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt +++ b/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/CMakeLists.txt @@ -40,6 +40,7 @@ set ( TEST_PY_FILES SANSSubtractTest.py ExportSampleLogsToCSVFileTest.py ExportExperimentLogTest.py + PoldiMergeTest.py ) check_tests_valid ( ${CMAKE_CURRENT_SOURCE_DIR} ${TEST_PY_FILES} ) diff --git a/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/PoldiMergeTest.py b/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/PoldiMergeTest.py new file mode 100644 index 000000000000..97fdde8f1946 --- /dev/null +++ b/Code/Mantid/Framework/PythonInterface/test/python/plugins/algorithms/PoldiMergeTest.py @@ -0,0 +1,76 @@ +import unittest + +from mantid.kernel import * +from mantid.api import * +from mantid.simpleapi import * + +import numpy as np + +class PoldiMergeTest(unittest.TestCase): + def __init__(self, *args): + unittest.TestCase.__init__(self, *args) + properties = ["TablePositionX", "TablePositionY", "TablePositionZ", "ChopperSpeed"] + + leftData = [0.0, 1.0, 2.0, 3.0] + rightDataGood = [0.0, 1.0, 2.0, 3.0] + + rightDataBadOffset = [1.0, 2.0, 3.0, 4.0] + rightDataBadDelta = [0.0, 2.0, 4.0, 6.0] + + ydata = np.ones(len(leftData)) + + self.base = CreateWorkspace(leftData, ydata, OutputWorkspace="Base") + self.goodTiming = CreateWorkspace(rightDataGood, ydata, OutputWorkspace="GoodTiming") + self.goodTimingBadProperties = CreateWorkspace(rightDataGood, ydata, OutputWorkspace="GoodTimingBadProperties") + self.badTimingOffset = CreateWorkspace(rightDataBadOffset, ydata, OutputWorkspace="BadTimingOffset") + self.badTimingDelta = CreateWorkspace(rightDataBadDelta, ydata, OutputWorkspace="BadTimingDelta") + + goodProperty = 10.0 + badProperty = 20.0 + + for p in properties: + self.base.getRun().addProperty(p, goodProperty, True) + self.goodTiming.getRun().addProperty(p, goodProperty, True) + self.badTimingOffset.getRun().addProperty(p, goodProperty, True) + self.badTimingDelta.getRun().addProperty(p, goodProperty, True) + + self.goodTimingBadProperties.getRun().addProperty(p, badProperty, True) + + def __runMerge__(self, workspaceNames): + return PoldiMerge(WorkspaceNames=workspaceNames, OutputWorkspace="PoldiMergeOutput") + + def test_happyCase(self): + output = self.__runMerge__("Base,GoodTiming") + + self.assertTrue(isinstance(output, MatrixWorkspace)) + + dataX = output.dataX(0) + self.assertEqual(dataX[0], 0.0) + self.assertEqual(dataX[-1], 3.0) + self.assertEqual(len(dataX), 4) + + dataY = output.dataY(0) + self.assertEqual(dataY[0], 2.0) + self.assertEqual(dataY[1], 2.0) + self.assertEqual(len(dataY), 4) + + DeleteWorkspace("PoldiMergeOutput") + + def test_timingDelta(self): + self.assertRaises(RuntimeError, lambda: self.__runMerge__("Base,BadTimingDelta")) + self.assertFalse(AnalysisDataService.doesExist("PoldiMergeOutput")) + + def test_timingOffset(self): + self.assertRaises(RuntimeError, lambda: self.__runMerge__("Base,BadTimingOffset")) + self.assertFalse(AnalysisDataService.doesExist("PoldiMergeOutput")) + + def test_badProperties(self): + self.assertRaises(RuntimeError, lambda: self.__runMerge__("Base,GoodTimingBadProperties")) + self.assertFalse(AnalysisDataService.doesExist("PoldiMergeOutput")) + + def test_badName(self): + self.assertRaises(RuntimeError, lambda: self.__runMerge__("Base,NotExisting")) + self.assertFalse(AnalysisDataService.doesExist("PoldiMergeOutput")) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/Code/Mantid/instrument/POLDI_Definition_ipp13.xml b/Code/Mantid/instrument/POLDI_Definition_ipp13.xml index 6af23afd4565..d41be04f4bd6 100644 --- a/Code/Mantid/instrument/POLDI_Definition_ipp13.xml +++ b/Code/Mantid/instrument/POLDI_Definition_ipp13.xml @@ -3,7 +3,7 @@ see http://www.mantidproject.org/IDF --> diff --git a/Code/Mantid/instrument/nexusdictionaries/poldi.dic b/Code/Mantid/instrument/nexusdictionaries/poldi.dic index 0741e2008795..83d526452114 100644 --- a/Code/Mantid/instrument/nexusdictionaries/poldi.dic +++ b/Code/Mantid/instrument/nexusdictionaries/poldi.dic @@ -51,3 +51,9 @@ ChopperPhase=/entry1/POLDI/chopper/chopper_phase ChopperSpeed=/entry1/POLDI/chopper/rotation_speed # SampleName=/entry1/POLDI/name +# +start_time=/entry1/start_time +# +TablePositionX=/entry1/sample/sample_x +TablePositionY=/entry1/sample/sample_y +TablePositionZ=/entry1/sample/sample_lift \ No newline at end of file