ReflectometryISISLoadAndProcess.py 26.1 KB
Newer Older
1
# -*- coding: utf-8 -*-# Mantid Repository : https://github.com/mantidproject/mantid
2
3
#
# Copyright © 2019 ISIS Rutherford Appleton Laboratory UKRI,
4
5
#   NScD Oak Ridge National Laboratory, European Spallation Source,
#   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6
7
8
# SPDX - License - Identifier: GPL - 3.0 +

from mantid.api import (AlgorithmFactory, AnalysisDataService, DataProcessorAlgorithm,
9
                        WorkspaceGroup)
10

11
from mantid.simpleapi import MergeRuns
12

13
14
15
16
from mantid.kernel import (CompositeValidator, Direction, EnabledWhenProperty,
                           IntBoundedValidator, Property, PropertyCriterion,
                           StringArrayLengthValidator, StringArrayMandatoryValidator,
                           StringArrayProperty)
17
18
19
20
21
22
23


class Prop:
    RUNS = 'InputRunList'
    FIRST_TRANS_RUNS = 'FirstTransmissionRunList'
    SECOND_TRANS_RUNS = 'SecondTransmissionRunList'
    SLICE = 'SliceWorkspace'
24
    NUMBER_OF_SLICES = 'NumberOfSlices'
25
26
27
28
29
    QMIN = 'MomentumTransferMin'
    QSTEP = 'MomentumTransferStep'
    QMAX = 'MomentumTransferMax'
    GROUP_TOF = 'GroupTOFWorkspaces'
    RELOAD = 'ReloadInvalidWorkspaces'
Gemma Guest's avatar
Gemma Guest committed
30
    DEBUG = 'Debug'
31
32
33
    OUTPUT_WS = 'OutputWorkspace'
    OUTPUT_WS_BINNED = 'OutputWorkspaceBinned'
    OUTPUT_WS_LAM = 'OutputWorkspaceWavelength'
34
35
    OUTPUT_WS_FIRST_TRANS = 'OutputWorkspaceFirstTransmission'
    OUTPUT_WS_SECOND_TRANS = 'OutputWorkspaceSecondTransmission'
Gemma Guest's avatar
Gemma Guest committed
36
    OUTPUT_WS_TRANS = 'OutputWorkspaceTransmission'
37
38
39
40
41
42
43
44
45
46
47


class ReflectometryISISLoadAndProcess(DataProcessorAlgorithm):

    def __init__(self):
        """Initialize an instance of the algorithm."""
        DataProcessorAlgorithm.__init__(self)
        self._tofPrefix = "TOF_"
        self._transPrefix = "TRANS_"

    def category(self):
Alice Russell's avatar
Alice Russell committed
48
        """Return the categories of the algorithm."""
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
        return 'ISIS\\Reflectometry;Workflow\\Reflectometry'

    def name(self):
        """Return the name of the algorithm."""
        return 'ReflectometryISISLoadAndProcess'

    def summary(self):
        """Return a summary of the algorithm."""
        return "Reduce ISIS reflectometry data, including optional loading and summing/slicing of the input runs."

    def seeAlso(self):
        """Return a list of related algorithm names."""
        return ['ReflectometrySliceEventWorkspace', 'ReflectometryReductionOneAuto']

    def version(self):
        """Return the version of the algorithm."""
        return 1

    def PyInit(self):
        """Initialize the input and output properties of the algorithm."""
69
70
71
72
73
74
        self._reduction_properties = [] # cached list of properties copied from child alg
        self._declareRunProperties()
        self._declareSlicingProperties()
        self._declareReductionProperties()
        self._declareTransmissionProperties()
        self._declareOutputProperties()
75
76
77

    def PyExec(self):
        """Execute the algorithm."""
78
        self._reload = self.getProperty(Prop.RELOAD).value
79
80
81
82
        # Convert run numbers to real workspaces
        inputRuns = self.getProperty(Prop.RUNS).value
        inputWorkspaces = self._getInputWorkspaces(inputRuns, False)
        firstTransRuns = self.getProperty(Prop.FIRST_TRANS_RUNS).value
Gemma Guest's avatar
Gemma Guest committed
83
        firstTransWorkspaces = self._getInputWorkspaces(firstTransRuns, True)
84
        secondTransRuns = self.getProperty(Prop.SECOND_TRANS_RUNS).value
Gemma Guest's avatar
Gemma Guest committed
85
        secondTransWorkspaces = self._getInputWorkspaces(secondTransRuns, True)
86
        # Combine multiple input runs, if required
87
88
89
        inputWorkspace = self._sumWorkspaces(inputWorkspaces, False)
        firstTransWorkspace = self._sumWorkspaces(firstTransWorkspaces, True)
        secondTransWorkspace = self._sumWorkspaces(secondTransWorkspaces, True)
90
        # Slice the input workspace, if required
Gemma Guest's avatar
Gemma Guest committed
91
        inputWorkspace = self._sliceWorkspace(inputWorkspace)
92
        # Perform the reduction
Gemma Guest's avatar
Gemma Guest committed
93
        alg = self._reduce(inputWorkspace, firstTransWorkspace, secondTransWorkspace)
94
        # Set outputs and tidy TOF workspaces into a group
95
        self._finalize(alg)
96
        self._groupTOFWorkspaces(inputWorkspaces)
97

98
    def _groupTOFWorkspaces(self, inputWorkspaces):
99
100
101
102
        """Put all of the TOF workspaces into a group called 'TOF' to hide some noise
        for the user."""
        if not self.getProperty(Prop.GROUP_TOF).value:
            return
103
        tofWorkspaces = set(inputWorkspaces)
Gemma Guest's avatar
Gemma Guest committed
104
105
106
        # If slicing, also group the monitor workspace (note that there is only one
        # input run when slicing)
        if self._slicingEnabled():
107
            tofWorkspaces.add(_monitorWorkspace(inputWorkspaces[0]))
108
        # Create the group
109
        self._group_workspaces(tofWorkspaces, "TOF")
110

111
112
113
114
115
116
117
    def validateInputs(self):
        """Return a dictionary containing issues found in properties."""
        issues = dict()
        if len(self.getProperty(Prop.RUNS).value) > 1 and self.getProperty(Prop.SLICE).value:
            issues[Prop.SLICE] = "Cannot perform slicing when summing multiple input runs"
        return issues

118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
    def _declareRunProperties(self):
        mandatoryInputRuns = CompositeValidator()
        mandatoryInputRuns.add(StringArrayMandatoryValidator())
        lenValidator = StringArrayLengthValidator()
        lenValidator.setLengthMin(1)
        mandatoryInputRuns.add(lenValidator)
        # Add property for the input runs
        self.declareProperty(StringArrayProperty(Prop.RUNS,
                                                 values=[],
                                                 validator=mandatoryInputRuns),
                             doc='A list of run numbers or workspace names for the input runs. '
                                 'Multiple runs will be summed before reduction.')
        # Add properties from child algorithm
        properties = [
            'ThetaIn', 'ThetaLogName',
        ]
        self.copyProperties('ReflectometryReductionOneAuto', properties)
        self._reduction_properties += properties
        # Add properties for settings to apply to input runs
        self.declareProperty(Prop.RELOAD, True,
                             doc='If true, reload input workspaces if they are of the incorrect type')
        self.declareProperty(Prop.GROUP_TOF, True, doc='If true, group the TOF workspaces')

    def _declareSlicingProperties(self):
142
        """Copy properties from the child slicing algorithm and add our own custom ones"""
143
        self.declareProperty(Prop.SLICE, False, doc='If true, slice the input workspace')
144
        self.setPropertyGroup(Prop.SLICE, 'Slicing')
145
        # Convenience variables for conditional properties
146
        whenSliceEnabled = EnabledWhenProperty(Prop.SLICE, PropertyCriterion.IsEqualTo, "1")
147

148
        self._slice_properties = ['TimeInterval', 'LogName', 'LogValueInterval', 'UseNewFilterAlgorithm']
149
        self.copyProperties('ReflectometrySliceEventWorkspace', self._slice_properties)
150
151
        for property in self._slice_properties:
            self.setPropertySettings(property, whenSliceEnabled)
152
            self.setPropertyGroup(property, 'Slicing')
153

154
155
156
157
158
        self.declareProperty(name=Prop.NUMBER_OF_SLICES,
                             defaultValue=Property.EMPTY_INT,
                             validator=IntBoundedValidator(lower=1),
                             direction=Direction.Input,
                             doc='The number of uniform-length slices to slice the input workspace into')
159
        self.setPropertySettings(Prop.NUMBER_OF_SLICES, whenSliceEnabled)
160
161
        self.setPropertyGroup(Prop.NUMBER_OF_SLICES, 'Slicing')

162
163
    def _declareReductionProperties(self):
        properties = [
164
            'SummationType', 'ReductionType', 'IncludePartialBins',
165
            'AnalysisMode', 'ProcessingInstructions', 'CorrectDetectors',
166
167
168
            'DetectorCorrectionType', 'WavelengthMin', 'WavelengthMax', 'I0MonitorIndex',
            'MonitorBackgroundWavelengthMin', 'MonitorBackgroundWavelengthMax',
            'MonitorIntegrationWavelengthMin', 'MonitorIntegrationWavelengthMax',
169
170
            'SubtractBackground', 'BackgroundProcessingInstructions', 'BackgroundCalculationMethod',
            'DegreeOfPolynomial', 'CostFunction',
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
            'NormalizeByIntegratedMonitors', 'PolarizationAnalysis',
            'FloodCorrection', 'FloodWorkspace',
            'CorrectionAlgorithm', 'Polynomial', 'C0', 'C1'
        ]
        self.copyProperties('ReflectometryReductionOneAuto', properties)
        self._reduction_properties += properties

    def _declareTransmissionProperties(self):
        # Add input transmission run properties
        self.declareProperty(StringArrayProperty(Prop.FIRST_TRANS_RUNS,
                                                 values=[]),
                             doc='A list of run numbers or workspace names for the first transmission run. '
                                 'Multiple runs will be summed before reduction.')
        self.setPropertyGroup(Prop.FIRST_TRANS_RUNS, 'Transmission')
        self.declareProperty(StringArrayProperty(Prop.SECOND_TRANS_RUNS,
                                                 values=[]),
                             doc='A list of run numbers or workspace names for the second transmission run. '
                                 'Multiple runs will be summed before reduction.')
        self.setPropertyGroup(Prop.SECOND_TRANS_RUNS, 'Transmission')
        # Add properties copied from child algorithm
        properties = [
            'Params', 'StartOverlap', 'EndOverlap',
193
            'ScaleRHSWorkspace', 'TransmissionProcessingInstructions'
194
195
196
197
198
199
200
201
        ]
        self.copyProperties('ReflectometryReductionOneAuto', properties)
        self._reduction_properties += properties

    def _declareOutputProperties(self):
        properties = [Prop.DEBUG,
                      'MomentumTransferMin', 'MomentumTransferStep', 'MomentumTransferMax',
                      'ScaleFactor',
202
203
                      Prop.OUTPUT_WS_BINNED, Prop.OUTPUT_WS, Prop.OUTPUT_WS_LAM,
                      Prop.OUTPUT_WS_TRANS, Prop.OUTPUT_WS_FIRST_TRANS, Prop.OUTPUT_WS_SECOND_TRANS]
204
205
        self.copyProperties('ReflectometryReductionOneAuto', properties)
        self._reduction_properties += properties
206

Gemma Guest's avatar
Gemma Guest committed
207
    def _getInputWorkspaces(self, runs, isTrans):
208
209
210
211
212
213
        """Convert the given run numbers into real workspace names. Uses workspaces from
        the ADS if they exist, or loads them otherwise."""
        workspaces = list()
        for run in runs:
            ws = self._getRunFromADSOrNone(run, isTrans)
            if not ws:
Gemma Guest's avatar
Gemma Guest committed
214
                ws = self._loadRun(run, isTrans)
215
216
217
218
219
            if not ws:
                raise RuntimeError('Error loading run ' + run)
            workspaces.append(ws)
        return workspaces

220
221
    def _prefixedName(self, name, isTrans):
        """Add a prefix for TOF workspaces onto the given name"""
222
        if isTrans:
223
            return self._transPrefix + name
224
        else:
225
            return self._tofPrefix + name
226

227
228
    def _isValidWorkspace(self, workspace_name, workspace_id):
        """Returns true, if the workspace of name workspace_name is a valid
229
230
231
232
        reflectometry workspace of type workspace_id. Otherwise, deletes the
        workspace if the user requested to reload invalid workspaces, or raises
        an error otherwise
        """
233
        if not _hasWorkspaceID(workspace_name, workspace_id):
234
235
236
237
238
239
240
            message = 'Workspace ' + workspace_name + ' exists but is not a ' + workspace_id
            if self._reload:
                self.log().information(message)
                _removeWorkspace(workspace_name)
                return False
            else:
                raise RuntimeError(message)
241

242
243
244
        # For event workspaces, the monitors workspace must also exist, otherwise it's not valid
        if workspace_id == "EventWorkspace":
            if not AnalysisDataService.doesExist(_monitorWorkspace(workspace_name)):
Giovanni Di Siena's avatar
Giovanni Di Siena committed
245
                message = 'Monitors workspace ' + workspace_name + '_monitors does not exist'
246
247
248
249
250
251
                if self._reload:
                    self.log().information(message)
                    _removeWorkspace(workspace_name)
                    return False
                else:
                    raise RuntimeError(message)
252
        return True
253
254

    def _workspaceExistsAndIsValid(self, workspace_name, isTrans):
255
256
257
        """Return true, if the given workspace exists in the ADS and is valid"""
        if not AnalysisDataService.doesExist(workspace_name):
            self.log().information('Workspace ' + workspace_name + ' does not exist')
258
            return False
259
        self.log().information('Workspace ' + workspace_name + ' exists')
260
        if not isTrans and self._slicingEnabled():
261
            return self._isValidWorkspace(workspace_name, "EventWorkspace")
262
        else:
263
            return self._isValidWorkspace(workspace_name, "Workspace2D")
264
265
266
267
268
269
270
271
272
273

    def _getRunFromADSOrNone(self, run, isTrans):
        """Given a run name, return the name of the equivalent workspace in the ADS (
        which may or may not have a prefix applied). Returns None if the workspace does
        not exist or is not valid for the requested reflectometry reduction."""
        # Try given run number
        workspace_name = run
        if self._workspaceExistsAndIsValid(workspace_name, isTrans):
            return workspace_name
        # Try with prefix
274
        workspace_name = self._prefixedName(run, isTrans)
275
276
277
278
279
        if self._workspaceExistsAndIsValid(workspace_name, isTrans):
            return workspace_name
        # Not found
        return None

Giovanni Di Siena's avatar
Giovanni Di Siena committed
280
    def _collapse_workspace_groups(self, workspaces):
281
282
        """Given a list of workspaces, which themselves could be groups of workspaces,
        return a new list of workspaces which are TOF"""
283
        ungrouped_workspaces = set([])
284
        delete_ws_group_flag = True
285
286
        for ws_name in workspaces:
            ws = AnalysisDataService.retrieve(ws_name)
287
            if isinstance(ws, WorkspaceGroup):
288
289
                ungrouped_workspaces = ungrouped_workspaces.union(
                    self._collapse_workspace_groups(ws.getNames()))
290
291
292
293
                if delete_ws_group_flag is True:
                    AnalysisDataService.remove(ws_name)
            else:
                if (ws.getAxis(0).getUnit().unitID()) == 'TOF':
294
                    ungrouped_workspaces.add(ws_name)
295
                else:
296
                    # Do not remove the workspace group from the ADS if a non-TOF workspace exists
297
                    delete_ws_group_flag = False
298
        return ungrouped_workspaces
299

300
    def _group_workspaces(self, workspaces, output_ws_name):
Alice Russell's avatar
Alice Russell committed
301
302
303
304
        """
        Groups all the given workspaces into a group with the given name. If the group
        already exists it will add them to that group.
        """
305
        if len(workspaces) < 1:
306
307
            return

Giovanni Di Siena's avatar
Giovanni Di Siena committed
308
        workspaces = self._collapse_workspace_groups(workspaces)
309
310
311

        if not workspaces:
            return
312

Alice Russell's avatar
Alice Russell committed
313
314
315
316
317
318
319
320
321
322
        if AnalysisDataService.doesExist(output_ws_name):
            ws_group = AnalysisDataService.retrieve(output_ws_name)
            if not isinstance(ws_group, WorkspaceGroup):
                raise RuntimeError('Cannot group TOF workspaces, a workspace called TOF already exists')
            else:
                for ws in workspaces:
                    if ws not in ws_group:
                        ws_group.add(ws)
        else:
            alg = self.createChildAlgorithm("GroupWorkspaces")
323
            alg.setProperty("InputWorkspaces", list(workspaces))
Alice Russell's avatar
Alice Russell committed
324
325
326
            alg.setProperty("OutputWorkspace", output_ws_name)
            alg.execute()
            ws_group = alg.getProperty("OutputWorkspace").value
327
328
329
330
            # We can't add the group as an output property or it will duplicate
            # the history for the contained workspaces, so add it directly to
            # the ADS
            AnalysisDataService.addOrReplace(output_ws_name, ws_group)
331

Gemma Guest's avatar
Gemma Guest committed
332
    def _loadRun(self, run, isTrans):
333
334
        """Load a run as an event workspace if slicing is requested, or a histogram
        workspace otherwise. Transmission runs are always loaded as histogram workspaces."""
335
336
337
338
339
340
341
        event_mode = not isTrans and self._slicingEnabled()
        args = {'InputRunList': [run], 'EventMode': event_mode}
        alg = self.createChildAlgorithm('ReflectometryISISPreprocess', **args)
        alg.setRethrows(True)
        alg.execute()

        ws = alg.getProperty('OutputWorkspace').value
342
        monitor_ws = alg.getProperty('MonitorWorkspace').value
343
344
        workspace_name = self._prefixedName(_getRunNumberAsString(ws), isTrans)
        AnalysisDataService.addOrReplace(workspace_name, ws)
345
346
        if monitor_ws:
            AnalysisDataService.addOrReplace(_monitorWorkspace(workspace_name), monitor_ws)
347
348

        if event_mode:
349
350
351
352
353
354
            _throwIfNotValidReflectometryEventWorkspace(workspace_name)
            self.log().information('Loaded event workspace ' + workspace_name)
        else:
            self.log().information('Loaded workspace ' + workspace_name)
        return workspace_name

355
    def _sumWorkspaces(self, workspaces, isTrans):
356
357
358
359
360
361
        """If there are multiple input workspaces, sum them and return the result. Otherwise
        just return the single input workspace, or None if the list is empty."""
        if len(workspaces) < 1:
            return None
        if len(workspaces) < 2:
            return workspaces[0]
362
363
        workspaces_without_prefixes = [self._removePrefix(ws, isTrans) for ws in workspaces]
        concatenated_names = "+".join(workspaces_without_prefixes)
364
365
366
        summed_name = self._prefixedName(concatenated_names, isTrans)
        self.log().information('Summing workspaces' + " ".join(workspaces) + ' into ' + summed_name)
        summed_ws = MergeRuns(InputWorkspaces=", ".join(workspaces), OutputWorkspace=summed_name)
367
368
369
        # The reduction algorithm sets the output workspace names from the run number,
        # which by default is just the first run. Set it to the concatenated name,
        # e.g. 13461+13462
370
371
        if isinstance(summed_ws, WorkspaceGroup):
            for workspaceName in summed_ws.getNames():
372
373
374
                grouped_ws = AnalysisDataService.retrieve(workspaceName)
                grouped_ws.run().addProperty('run_number', concatenated_names, True)
        else:
375
376
            summed_ws.run().addProperty('run_number', concatenated_names, True)
        return summed_name
377
378
379
380

    def _slicingEnabled(self):
        return self.getProperty(Prop.SLICE).value

381
382
383
384
385
386
387
    def _setUniformNumberOfSlices(self, alg, workspace_name):
        """If slicing by a specified number of slices is requested, find the time
        interval to use to give this number of even time slices and set the relevant
        property on the given slicing algorithm"""
        if self.getProperty(Prop.NUMBER_OF_SLICES).isDefault:
            return
        number_of_slices = self.getProperty(Prop.NUMBER_OF_SLICES).value
388
        run = AnalysisDataService.retrieve(workspace_name).run()
389
390
391
        total_duration = (run.endTime() - run.startTime()).total_seconds()
        slice_duration = total_duration / number_of_slices
        alg.setProperty("TimeInterval", slice_duration)
Gemma Guest's avatar
Gemma Guest committed
392
393
        self.log().information('Slicing ' + workspace_name + ' into ' + str(number_of_slices)
                               + ' even slices of duration ' + str(slice_duration))
394
395
396
397
398
399
400

    def _setSliceStartStopTimes(self, alg, workspace_name):
        """Set the start/stop time for the slicing algorithm based on the
        run start/end times if the time interval is specified, otherwise
        we can end up with more slices than we expect"""
        if alg.getProperty("TimeInterval").isDefault:
            return
401
        run = AnalysisDataService.retrieve(workspace_name).run()
402
403
404
        alg.setProperty("StartTime", str(run.startTime()))
        alg.setProperty("StopTime", str(run.endTime()))

405
406
407
408
409
410
411
412
413
    def _runSliceAlgorithm(self, input_workspace, output_workspace):
        """Run the child algorithm to perform the slicing"""
        self.log().information('Running ReflectometrySliceEventWorkspace')
        alg = self.createChildAlgorithm("ReflectometrySliceEventWorkspace")
        for property in self._slice_properties:
            alg.setProperty(property, self.getPropertyValue(property))
        alg.setProperty("OutputWorkspace", output_workspace)
        alg.setProperty("InputWorkspace", input_workspace)
        alg.setProperty("MonitorWorkspace", _monitorWorkspace(input_workspace))
414
415
        self._setUniformNumberOfSlices(alg, input_workspace)
        self._setSliceStartStopTimes(alg, input_workspace)
416
        alg.execute()
417
        return alg.getProperty("OutputWorkspace").value
418
419
420
421
422
423

    def _sliceWorkspace(self, workspace):
        """If slicing has been requested, slice the input workspace, otherwise
        return it unchanged"""
        if not self._slicingEnabled():
            return workspace
424
        # Perform the slicing
425
426
        sliced_workspace_name = self._getSlicedWorkspaceGroupName(workspace)
        self.log().information('Slicing workspace ' + workspace + ' into ' + sliced_workspace_name)
427
        workspace = self._runSliceAlgorithm(workspace, sliced_workspace_name)
428
429
430
431
432
433
434
435
436
        return sliced_workspace_name

    def _getSlicedWorkspaceGroupName(self, workspace):
        return workspace + '_sliced'

    def _reduce(self, input_workspace, first_trans_workspace, second_trans_workspace):
        """Run the child algorithm to do the reduction. Return the child algorithm."""
        self.log().information('Running ReflectometryReductionOneAuto on ' + input_workspace)
        alg = self.createChildAlgorithm("ReflectometryReductionOneAuto")
437
        # Set properties that we copied directly from the child
438
439
        for property in self._reduction_properties:
            alg.setProperty(property, self.getPropertyValue(property))
440
        # Set properties that we could not take directly from the child
441
442
443
444
445
446
        alg.setProperty("InputWorkspace", input_workspace)
        alg.setProperty("FirstTransmissionRun", first_trans_workspace)
        alg.setProperty("SecondTransmissionRun", second_trans_workspace)
        alg.execute()
        return alg

447
    def _removePrefix(self, workspace, isTrans):
448
        """Remove the TOF prefix from the given workspace name"""
449
450
        prefix = self._transPrefix if isTrans else self._tofPrefix
        prefix_len = len(prefix)
451
        name_start = workspace[:prefix_len]
452
453
        if len(workspace) > prefix_len and name_start == prefix:
            return workspace[prefix_len:]
454
455
456
        else:
            return workspace

457
458
459
    def _hasTransmissionRuns(self):
        return not self.getProperty(Prop.FIRST_TRANS_RUNS).isDefault

Gemma Guest's avatar
Gemma Guest committed
460
461
462
    def _isDebug(self):
        return not self.getProperty(Prop.DEBUG).isDefault

463
464
    def _finalize(self, child_alg):
        """Set our output properties from the results in the given child algorithm"""
465
        # Set the main workspace outputs
466
467
468
        self._setOutputProperty(Prop.OUTPUT_WS, child_alg)
        self._setOutputProperty(Prop.OUTPUT_WS_BINNED, child_alg)
        self._setOutputProperty(Prop.OUTPUT_WS_LAM, child_alg)
469
        # Set the Q params as outputs if they were not specified as inputs
470
471
472
        self._setOutputPropertyIfInputNotSet(Prop.QMIN, child_alg)
        self._setOutputPropertyIfInputNotSet(Prop.QSTEP, child_alg)
        self._setOutputPropertyIfInputNotSet(Prop.QMAX, child_alg)
473
        # Set the transmission workspace outputs
474
475
476
        self._setOutputProperty(Prop.OUTPUT_WS_TRANS, child_alg)
        self._setOutputProperty(Prop.OUTPUT_WS_FIRST_TRANS, child_alg)
        self._setOutputProperty(Prop.OUTPUT_WS_SECOND_TRANS, child_alg)
477

478
    def _setOutputProperty(self, property_name, child_alg):
479
        """Set the given output property from the result in the given child algorithm,
480
        if it exists in the child algorithm's outputs"""
481
482
483
484
485
486
        value_name = child_alg.getPropertyValue(property_name)
        if value_name:
            self.setPropertyValue(property_name, value_name)
            value = child_alg.getProperty(property_name).value
            if value:
                self.setProperty(property_name, value)
487

488
489
490
491
492
    def _setOutputPropertyIfInputNotSet(self, property_name, child_alg):
        """Set the given output property from the result in the given child algorithm,
        if it was not set as an input to this algorithm and if it exists in the
        child algorithm's outputs"""
        if self.getProperty(property_name).isDefault:
493
            self._setOutputProperty(property_name, child_alg)
494

495
496

def _throwIfNotValidReflectometryEventWorkspace(workspace_name):
497
    workspace = AnalysisDataService.retrieve(workspace_name)
498
499
500
501
502
503
504
505
506
507
508
    if isinstance(workspace, WorkspaceGroup):
        raise RuntimeError('Slicing workspace groups is not supported')
    if not workspace.run().hasProperty('proton_charge'):
        raise RuntimeError('Cannot slice workspace: run must contain proton_charge')


def _monitorWorkspace(workspace):
    """Return the associated monitor workspace name for the given workspace"""
    return workspace + '_monitors'


509
def _getRunNumberAsString(workspace):
Gemma Guest's avatar
Gemma Guest committed
510
511
512
513
514
515
516
517
    """Get the run number for a workspace. If it's a workspace group, get
    the run number from the first child workspace."""
    try:
        if not isinstance(workspace, WorkspaceGroup):
            return str(workspace.getRunNumber())
        # Get first child in the group
        return str(workspace[0].getRunNumber())
    except:
518
        raise RuntimeError('Could not find run number for workspace ' + workspace.getName())
Gemma Guest's avatar
Gemma Guest committed
519
520


521
def _hasWorkspaceID(workspace_name, workspace_id):
522
    """Check that a workspace has the given type"""
523
524
    workspace = AnalysisDataService.retrieve(workspace_name)
    if isinstance(workspace, WorkspaceGroup):
525
        return workspace[0].id() == workspace_id
526
    else:
527
        return workspace.id() == workspace_id
528
529
530


def _removeWorkspace(workspace_name):
531
532
    """Remove the workspace with the given name, including any child workspaces if it
    is a group. If a corresponding monitors workspace exists, remove that too."""
533
    if AnalysisDataService.doesExist(workspace_name):
534
535
536
537
538
539
540
        workspace = AnalysisDataService.retrieve(workspace_name)
        if isinstance(workspace, WorkspaceGroup):
            # Remove child workspaces first
            while workspace.getNumberOfEntries():
                _removeWorkspace(workspace[0].name())
        AnalysisDataService.remove(workspace_name)
    # If a corresponding monitors workspace also exists, remove that too
541
542
543
544
    if AnalysisDataService.doesExist(_monitorWorkspace(workspace_name)):
        _removeWorkspace(_monitorWorkspace(workspace_name))


545
AlgorithmFactory.subscribe(ReflectometryISISLoadAndProcess)