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 +
from __future__ import (absolute_import, division, print_function)
from copy import deepcopy
from mantid.api import AnalysisDataService, WorkspaceGroup
from sans.common.general_functions import (create_managed_non_child_algorithm, create_unmanaged_algorithm,
get_output_name, get_base_name_from_multi_period_name, get_transmission_output_name)
from sans.common.enums import (SANSDataType, SaveType, OutputMode, ISISReductionMode, DataType)
from sans.common.constants import (TRANS_SUFFIX, SANS_SUFFIX, ALL_PERIODS,
LAB_CAN_SUFFIX, LAB_CAN_COUNT_SUFFIX, LAB_CAN_NORM_SUFFIX,
HAB_CAN_SUFFIX, HAB_CAN_COUNT_SUFFIX, HAB_CAN_NORM_SUFFIX,
LAB_SAMPLE_SUFFIX, HAB_SAMPLE_SUFFIX,
REDUCED_HAB_AND_LAB_WORKSPACE_FOR_MERGED_REDUCTION,
CAN_AND_SAMPLE_WORKSPACE)
from sans.common.file_information import (get_extension_for_file_type, SANSFileInformationFactory)
from sans.state.data import StateData
try:
import mantidplot
except (Exception, Warning):
mantidplot = None
# this should happen when this is called from outside Mantidplot and only then,
# the result is that attempting to plot will raise an exception
# ----------------------------------------------------------------------------------------------------------------------
# Functions for the execution of a single batch iteration
# ----------------------------------------------------------------------------------------------------------------------
def single_reduction_for_batch(state, use_optimizations, output_mode, plot_results, output_graph, save_can=False):
"""
Runs a single reduction.
This function creates reduction packages which essentially contain information for a single valid reduction, run it
and store the results according to the user specified setting (output_mode). Although this is considered a single
reduction it can contain still several reductions since the SANSState object can at this point contain slice
settings which require on reduction per time slice.
:param state: a SANSState object
:param use_optimizations: if true then the optimizations of child algorithms are enabled.
:param output_mode: the output mode
:param save_can: bool. whether or not to save out can workspaces
"""
# ------------------------------------------------------------------------------------------------------------------
# Load the data
# ------------------------------------------------------------------------------------------------------------------
workspace_to_name = {SANSDataType.SampleScatter: "SampleScatterWorkspace",
SANSDataType.SampleTransmission: "SampleTransmissionWorkspace",
SANSDataType.SampleDirect: "SampleDirectWorkspace",
SANSDataType.CanScatter: "CanScatterWorkspace",
SANSDataType.CanTransmission: "CanTransmissionWorkspace",
SANSDataType.CanDirect: "CanDirectWorkspace"}
workspace_to_monitor = {SANSDataType.SampleScatter: "SampleScatterMonitorWorkspace",
SANSDataType.CanScatter: "CanScatterMonitorWorkspace"}
workspaces, monitors = provide_loaded_data(state, use_optimizations, workspace_to_name, workspace_to_monitor)
# ------------------------------------------------------------------------------------------------------------------
# Get reduction settings
# Split into individual bundles which can be reduced individually. We split here if we have multiple periods or
# sliced times for example.
# ------------------------------------------------------------------------------------------------------------------
reduction_packages = get_reduction_packages(state, workspaces, monitors)
# ------------------------------------------------------------------------------------------------------------------
# Run reductions (one at a time)
# ------------------------------------------------------------------------------------------------------------------
single_reduction_name = "SANSSingleReduction"
single_reduction_options = {"UseOptimizations": use_optimizations,
"SaveCan": save_can}
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
reduction_alg = create_managed_non_child_algorithm(single_reduction_name, **single_reduction_options)
reduction_alg.setChild(False)
# Perform the data reduction
for reduction_package in reduction_packages:
# -----------------------------------
# Set the properties on the algorithm
# -----------------------------------
set_properties_for_reduction_algorithm(reduction_alg, reduction_package,
workspace_to_name, workspace_to_monitor)
# -----------------------------------
# Run the reduction
# -----------------------------------
reduction_alg.execute()
# -----------------------------------
# Get the output of the algorithm
# -----------------------------------
reduction_package.reduced_lab = get_workspace_from_algorithm(reduction_alg, "OutputWorkspaceLAB")
reduction_package.reduced_hab = get_workspace_from_algorithm(reduction_alg, "OutputWorkspaceHAB")
reduction_package.reduced_merged = get_workspace_from_algorithm(reduction_alg, "OutputWorkspaceMerged")
reduction_package.reduced_lab_can = get_workspace_from_algorithm(reduction_alg, "OutputWorkspaceLABCan")
reduction_package.reduced_lab_can_count = get_workspace_from_algorithm(reduction_alg,
"OutputWorkspaceLABCanCount")
reduction_package.reduced_lab_can_norm = get_workspace_from_algorithm(reduction_alg,
"OutputWorkspaceLABCanNorm")
reduction_package.reduced_hab_can = get_workspace_from_algorithm(reduction_alg, "OutputWorkspaceHABCan")
reduction_package.reduced_hab_can_count = get_workspace_from_algorithm(reduction_alg,
"OutputWorkspaceHABCanCount")
reduction_package.reduced_hab_can_norm = get_workspace_from_algorithm(reduction_alg,
"OutputWorkspaceHABCanNorm")
reduction_package.calculated_transmission = get_workspace_from_algorithm(reduction_alg,
"OutputWorkspaceCalculatedTransmission")
reduction_package.unfitted_transmission = get_workspace_from_algorithm(reduction_alg,
"OutputWorkspaceUnfittedTransmission")
reduction_package.calculated_transmission_can = get_workspace_from_algorithm(reduction_alg,
"OutputWorkspaceCalculatedTransmissionCan")
reduction_package.unfitted_transmission_can = get_workspace_from_algorithm(reduction_alg,
"OutputWorkspaceUnfittedTransmissionCan")
reduction_package.reduced_lab_sample = get_workspace_from_algorithm(reduction_alg, "OutputWorkspaceLABSample")
reduction_package.reduced_hab_sample = get_workspace_from_algorithm(reduction_alg, "OutputWorkspaceHABSample")
reduction_package.out_scale_factor = reduction_alg.getProperty("OutScaleFactor").value
reduction_package.out_shift_factor = reduction_alg.getProperty("OutShiftFactor").value
plot_workspace(reduction_package, output_graph)
# -----------------------------------
# The workspaces are already on the ADS, but should potentially be grouped
# -----------------------------------
group_workspaces_if_required(reduction_package, output_mode, save_can)
# --------------------------------
# Perform output of all workspaces
# --------------------------------
# We have three options here
# 1. PublishToADS:
# * This means we can leave it as it is
# 2. SaveToFile:
# * This means we need to save out the reduced data
# * Then we need to delete the reduced data from the ADS
# 3. Both:
# * This means that we need to save out the reduced data
# * The data is already on the ADS, so do nothing
if output_mode is OutputMode.SaveToFile:
save_to_file(reduction_packages, save_can)
delete_reduced_workspaces(reduction_packages)
elif output_mode is OutputMode.Both:
save_to_file(reduction_packages, save_can)
# -----------------------------------------------------------------------
# Clean up other workspaces if the optimizations have not been turned on.
# -----------------------------------------------------------------------
if not use_optimizations:
delete_optimization_workspaces(reduction_packages, workspaces, monitors, save_can)
out_scale_factors = [reduction_package.out_scale_factor for reduction_package in reduction_packages]
out_shift_factors = [reduction_package.out_shift_factor for reduction_package in reduction_packages]
return out_scale_factors, out_shift_factors
def load_workspaces_from_states(state):
workspace_to_name = {SANSDataType.SampleScatter: "SampleScatterWorkspace",
SANSDataType.SampleTransmission: "SampleTransmissionWorkspace",
SANSDataType.SampleDirect: "SampleDirectWorkspace",
SANSDataType.CanScatter: "CanScatterWorkspace",
SANSDataType.CanTransmission: "CanTransmissionWorkspace",
SANSDataType.CanDirect: "CanDirectWorkspace"}
workspace_to_monitor = {SANSDataType.SampleScatter: "SampleScatterMonitorWorkspace",
SANSDataType.CanScatter: "CanScatterMonitorWorkspace"}
workspaces, monitors = provide_loaded_data(state, True, workspace_to_name, workspace_to_monitor)
# ----------------------------------------------------------------------------------------------------------------------
# Function for plotting
# ----------------------------------------------------------------------------------------------------------------------
def plot_workspace(reduction_package, output_graph):
if reduction_package.reduction_mode == ISISReductionMode.All:
graph_handle = mantidplot.plotSpectrum([reduction_package.reduced_hab, reduction_package.reduced_lab], 0,
window=mantidplot.graph(output_graph), clearWindow=True)
graph_handle.activeLayer().logLogAxes()
elif reduction_package.reduction_mode == ISISReductionMode.HAB:
graph_handle = mantidplot.plotSpectrum(reduction_package.reduced_hab, 0, window=mantidplot.graph(output_graph), clearWindow=True)
graph_handle.activeLayer().logLogAxes()
elif reduction_package.reduction_mode == ISISReductionMode.LAB:
graph_handle = mantidplot.plotSpectrum(reduction_package.reduced_lab, 0, window=mantidplot.graph(output_graph), clearWindow=True)
graph_handle.activeLayer().logLogAxes()
elif reduction_package.reduction_mode == ISISReductionMode.Merged:
graph_handle = mantidplot.plotSpectrum([reduction_package.reduced_merged,
reduction_package.reduced_hab, reduction_package.reduced_lab], 0,
window=mantidplot.graph(output_graph), clearWindow=True)
graph_handle.activeLayer().logLogAxes()
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# ----------------------------------------------------------------------------------------------------------------------
# Functions for Data Loading
# ----------------------------------------------------------------------------------------------------------------------
def get_expected_workspace_names(file_information, is_transmission, period, get_base_name_only=False):
"""
Creates the expected names for SANS workspaces.
SANS scientists expect the load workspaces to have certain, typical names. For example, the file SANS2D00022024.nxs
which is used as a transmission workspace translates into 22024_trans_nxs.
:param file_information: a file information object
:param is_transmission: if the file information is for a transmission or not
:param period: the period of interest
:param get_base_name_only: if we only want the base name and not the name with the period information
:return: a list of workspace names
"""
suffix_file_type = get_extension_for_file_type(file_information)
if is_transmission:
suffix_data = TRANS_SUFFIX
else:
suffix_data = SANS_SUFFIX
run_number = file_information.get_run_number()
# Three possibilities:
# 1. No period data => 22024_sans_nxs
# 2. Period data, but wants all => 22025p1_sans_nxs, 22025p2_sans_nxs, ...
# 3. Period data, select particular period => 22025p3_sans_nxs
if file_information.get_number_of_periods() == 1:
workspace_name = "{0}_{1}_{2}".format(run_number, suffix_data, suffix_file_type)
names = [workspace_name]
elif file_information.get_number_of_periods() > 1 and period is StateData.ALL_PERIODS:
workspace_names = []
if get_base_name_only:
workspace_names.append("{0}_{1}_{2}".format(run_number, suffix_data, suffix_file_type))
else:
for period in range(1, file_information.get_number_of_periods() + 1):
workspace_names.append("{0}p{1}_{2}_{3}".format(run_number, period, suffix_data, suffix_file_type))
names = workspace_names
elif file_information.get_number_of_periods() > 1 and period is not StateData.ALL_PERIODS:
workspace_name = "{0}p{1}_{2}_{3}".format(run_number, period, suffix_data, suffix_file_type)
names = [workspace_name]
else:
raise RuntimeError("SANSLoad: Cannot create workspace names.")
return names
def set_output_workspace_on_load_algorithm_for_one_workspace_type(load_options, load_workspace_name, file_name, period,
is_transmission, file_info_factory,
load_monitor_name=None):
file_info = file_info_factory.create_sans_file_information(file_name)
workspace_names = get_expected_workspace_names(file_info, is_transmission=is_transmission, period=period,
get_base_name_only=True)
count = 0
# Now we set the load options if we are dealing with multi-period data, then we need to
for workspace_name in workspace_names:
if count == 0:
load_options.update({load_workspace_name: workspace_name})
if load_monitor_name is not None:
monitor_name = workspace_name + "_monitors"
load_options.update({load_monitor_name: monitor_name})
else:
load_workspace_name_for_period = load_workspace_name + "_" + str(count)
load_options.update({load_workspace_name_for_period: workspace_name})
if load_monitor_name is not None:
load_monitor_name_for_period = load_monitor_name + "_" + str(count)
monitor_name = workspace_name + "_monitors"
load_options.update({load_monitor_name_for_period: monitor_name})
count += 1
def set_output_workspaces_on_load_algorithm(load_options, state):
data = state.data
file_information_factory = SANSFileInformationFactory()
# SampleScatter and SampleScatterMonitor
set_output_workspace_on_load_algorithm_for_one_workspace_type(load_options=load_options,
load_workspace_name="SampleScatterWorkspace",
file_name=data.sample_scatter,
period=data.sample_scatter_period,
is_transmission=False,
file_info_factory=file_information_factory,
load_monitor_name="SampleScatterMonitorWorkspace")
# SampleTransmission
sample_transmission = data.sample_transmission
if sample_transmission:
set_output_workspace_on_load_algorithm_for_one_workspace_type(load_options=load_options,
load_workspace_name="SampleTransmissionWorkspace",
file_name=sample_transmission,
period=data.sample_transmission_period,
is_transmission=True,
file_info_factory=file_information_factory)
# SampleDirect
sample_direct = data.sample_direct
if sample_direct:
set_output_workspace_on_load_algorithm_for_one_workspace_type(load_options=load_options,
load_workspace_name="SampleDirectWorkspace",
file_name=sample_direct,
period=data.sample_direct_period,
is_transmission=True,
file_info_factory=file_information_factory)
# CanScatter + CanMonitor
can_scatter = data.can_scatter
if can_scatter:
set_output_workspace_on_load_algorithm_for_one_workspace_type(load_options=load_options,
load_workspace_name="CanScatterWorkspace",
file_name=can_scatter,
period=data.can_scatter_period,
is_transmission=False,
file_info_factory=file_information_factory,
load_monitor_name="CanScatterMonitorWorkspace")
# CanTransmission
can_transmission = data.can_transmission
if can_transmission:
set_output_workspace_on_load_algorithm_for_one_workspace_type(load_options=load_options,
load_workspace_name="CanTransmissionWorkspace",
file_name=can_transmission,
period=data.can_transmission_period,
is_transmission=True,
file_info_factory=file_information_factory)
# CanDirect
can_direct = data.can_direct
if can_direct:
set_output_workspace_on_load_algorithm_for_one_workspace_type(load_options=load_options,
load_workspace_name="CanDirectWorkspace",
file_name=can_direct,
period=data.can_direct_period,
is_transmission=True,
file_info_factory=file_information_factory)
def provide_loaded_data(state, use_optimizations, workspace_to_name, workspace_to_monitor):
"""
Provide the data for reduction.
:param state: a SANSState object.
:param use_optimizations: if optimizations are enabled, then the load mechanism will search for workspaces on the
ADS.
:param workspace_to_name: a map of SANSDataType vs output-property name of SANSLoad for workspaces
:param workspace_to_monitor: a map of SANSDataType vs output-property name of SANSLoad for monitor workspaces
:return: a list fo workspaces and a list of monitor workspaces
"""
# Load the data
state_serialized = state.property_manager
load_name = "SANSLoad"
load_options = {"SANSState": state_serialized,
"PublishToCache": use_optimizations,
# Set the output workspaces
set_output_workspaces_on_load_algorithm(load_options, state)
load_alg = create_managed_non_child_algorithm(load_name, **load_options)
load_alg.execute()
# Retrieve the data
workspace_to_count = {SANSDataType.SampleScatter: "NumberOfSampleScatterWorkspaces",
SANSDataType.SampleTransmission: "NumberOfSampleTransmissionWorkspaces",
SANSDataType.SampleDirect: "NumberOfSampleDirectWorkspaces",
SANSDataType.CanScatter: "NumberOfCanScatterWorkspaces",
SANSDataType.CanTransmission: "NumberOfCanTransmissionWorkspaces",
SANSDataType.CanDirect: "NumberOfCanDirectWorkspaces"}
workspaces = get_workspaces_from_load_algorithm(load_alg, workspace_to_count, workspace_to_name)
monitors = get_workspaces_from_load_algorithm(load_alg, workspace_to_count, workspace_to_monitor)
for key, workspace_type in workspaces.items():
for workspace in workspace_type:
add_to_group(workspace, 'sans_interface_raw_data')
for key, monitor_workspace_type in monitors.items():
for monitor_workspace in monitor_workspace_type:
add_to_group(monitor_workspace, 'sans_interface_raw_data')
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
return workspaces, monitors
def add_loaded_workspace_to_ads(load_alg, workspace_property_name, workspace):
"""
Adds a workspace with the name that was set on the output of the load algorithm to the ADS
:param load_alg: a handle to the load algorithm
:param workspace_property_name: the workspace property name
:param workspace: the workspace
"""
workspace_name = load_alg.getProperty(workspace_property_name).valueAsStr
AnalysisDataService.addOrReplace(workspace_name, workspace)
def get_workspaces_from_load_algorithm(load_alg, workspace_to_count, workspace_name_dict):
"""
Reads the workspaces from SANSLoad
:param load_alg: a handle to the load algorithm
:param workspace_to_count: a map from SANSDataType to the output-number property name of SANSLoad for workspaces
:param workspace_name_dict: a map of SANSDataType vs output-property name of SANSLoad for (monitor) workspaces
:return: a map of SANSDataType vs list of workspaces (to handle multi-period data)
"""
workspace_output = {}
for workspace_type, workspace_name in list(workspace_name_dict.items()):
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
count_id = workspace_to_count[workspace_type]
number_of_workspaces = load_alg.getProperty(count_id).value
workspaces = []
if number_of_workspaces > 1:
workspaces = get_multi_period_workspaces(load_alg, workspace_name_dict[workspace_type],
number_of_workspaces)
else:
workspace_id = workspace_name_dict[workspace_type]
workspace = get_workspace_from_algorithm(load_alg, workspace_id)
if workspace is not None:
workspaces.append(workspace)
# Add the workspaces to the to the output
workspace_output.update({workspace_type: workspaces})
return workspace_output
def get_multi_period_workspaces(load_alg, workspace_name, number_of_workspaces):
# Create an output name for each workspace and retrieve it from the load algorithm
workspaces = []
workspace_names = []
for index in range(1, number_of_workspaces + 1):
output_property_name = workspace_name + "_" + str(index)
output_workspace_name = load_alg.getProperty(output_property_name).valueAsStr
workspace_names.append(output_workspace_name)
workspace = get_workspace_from_algorithm(load_alg, output_property_name)
workspaces.append(workspace)
# Group the workspaces
base_name = get_base_name_from_multi_period_name(workspace_names[0])
group_name = "GroupWorkspaces"
group_options = {"InputWorkspaces": workspace_names,
"OutputWorkspace": base_name}
group_alg = create_unmanaged_algorithm(group_name, **group_options)
group_alg.setChild(False)
group_alg.execute()
return workspaces
# ----------------------------------------------------------------------------------------------------------------------
# Functions for reduction packages
# ----------------------------------------------------------------------------------------------------------------------
def get_reduction_packages(state, workspaces, monitors):
"""
This function creates a set of reduction packages which contain the necessary state for a single reduction
as well as the required workspaces.
There are several reasons why a state can (and should) split up:
1. Multi-period files were loaded. This means that we need to perform one reduction per (loaded) period
2. Event slices were specified. This means that we need to perform one reduction per event slice.
:param state: A single state which potentially needs to be split up into several states
:param workspaces: The workspaces contributing to the reduction
:param monitors: The monitors contributing to the reduction
:return: A set of "Reduction packages" where each reduction package defines a single reduction.
"""
# First: Split the state on a per-period basis
reduction_packages = create_initial_reduction_packages(state, workspaces, monitors)
# Second: Split resulting reduction packages on a per-event-slice basis
# Note that at this point all reduction packages will have the same state information. They only differ in the
# workspaces that they use.
if reduction_packages_require_splitting_for_event_slices(reduction_packages):
reduction_packages = split_reduction_packages_for_event_slice_packages(reduction_packages)
if reduction_packages_require_splitting_for_wavelength_range(reduction_packages):
reduction_packages = split_reduction_packages_for_wavelength_range(reduction_packages)
return reduction_packages
def reduction_packages_require_splitting_for_event_slices(reduction_packages):
"""
Creates reduction packages from a list of reduction packages by splitting up event slices.
The SANSSingleReduction algorithm can handle only a single time slice. For each time slice, we require an individual
reduction. Hence we split the states up at this point.
:param reduction_packages: a list of reduction packages.
:return: a list of reduction packages which has at least the same length as the input
"""
# Determine if the event slice sub-state object contains multiple event slice requests. This is given
# by the number of elements in start_tof
reduction_package = reduction_packages[0]
state = reduction_package.state
slice_event_info = state.slice
start_time = slice_event_info.start_time
if start_time is not None and len(start_time) > 1:
requires_split = True
else:
requires_split = False
return requires_split
def reduction_packages_require_splitting_for_wavelength_range(reduction_packages):
"""
Creates reduction packages from a list of reduction packages by splitting up wavelength ranges.
The SANSSingleReduction algorithm can handle only a single wavelength range. For each wavelength range, we require an individual
reduction. Hence we split the states up at this point.
:param reduction_packages: a list of reduction packages.
:return: a list of reduction packages which has at least the same length as the input
"""
# Determine if the event slice sub-state object contains multiple event slice requests. This is given
# by the number of elements in start_tof
reduction_package = reduction_packages[0]
state = reduction_package.state
wavelength_info = state.wavelength
start_wavelength = wavelength_info.wavelength_low
if start_wavelength is not None and len(start_wavelength) > 1:
requires_split = True
else:
requires_split = False
return requires_split
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
def split_reduction_packages_for_wavelength_range(reduction_packages):
reduction_packages_split = []
for reduction_package in reduction_packages:
state = reduction_package.state
wavelength_info = state.wavelength
start_wavelength = wavelength_info.wavelength_low
end_wavelength = wavelength_info.wavelength_high
states = []
for start, end in zip(start_wavelength, end_wavelength):
state_copy = deepcopy(state)
state_copy.wavelength.wavelength_low = [start]
state_copy.wavelength.wavelength_high = [end]
state_copy.adjustment.normalize_to_monitor.wavelength_low = [start]
state_copy.adjustment.normalize_to_monitor.wavelength_high = [end]
state_copy.adjustment.calculate_transmission.wavelength_low = [start]
state_copy.adjustment.calculate_transmission.wavelength_high = [end]
state_copy.adjustment.wavelength_and_pixel_adjustment.wavelength_low = [start]
state_copy.adjustment.wavelength_and_pixel_adjustment.wavelength_high = [end]
states.append(state_copy)
workspaces = reduction_package.workspaces
monitors = reduction_package.monitors
is_part_of_multi_period_reduction = reduction_package.is_part_of_multi_period_reduction
is_part_of_event_slice_reduction = reduction_package.is_part_of_event_slice_reduction
for state in states:
new_state = deepcopy(state)
new_reduction_package = ReductionPackage(state=new_state,
workspaces=workspaces,
monitors=monitors,
is_part_of_multi_period_reduction=is_part_of_multi_period_reduction,
is_part_of_event_slice_reduction=is_part_of_event_slice_reduction,
is_part_of_wavelength_range_reduction=True)
reduction_packages_split.append(new_reduction_package)
def split_reduction_packages_for_event_slice_packages(reduction_packages):
"""
Splits a reduction package object into several reduction package objects if it contains several event slice settings
We want to split this up here since each event slice is a full reduction cycle in itself.
:param reduction_packages: a list of reduction packages
:return: a list of reduction packages where each reduction setting contains only one event slice.
"""
# Since the state is the same for all reduction packages at this point we only need to create the split state once
# for the first package and the apply to all the other packages. If we have 5 reduction packages and the user
# requests 6 event slices, then we end up with 60 reductions!
reduction_packages_split = []
for reduction_package in reduction_packages:
state = reduction_package.state
slice_event_info = state.slice
start_time = slice_event_info.start_time
end_time = slice_event_info.end_time
states = []
for start, end in zip(start_time, end_time):
state_copy = deepcopy(state)
slice_event_info = state_copy.slice
slice_event_info.start_time = [start]
slice_event_info.end_time = [end]
states.append(state_copy)
workspaces = reduction_package.workspaces
monitors = reduction_package.monitors
is_part_of_multi_period_reduction = reduction_package.is_part_of_multi_period_reduction
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
for state in states:
new_state = deepcopy(state)
new_reduction_package = ReductionPackage(state=new_state,
workspaces=workspaces,
monitors=monitors,
is_part_of_multi_period_reduction=is_part_of_multi_period_reduction,
is_part_of_event_slice_reduction=True)
reduction_packages_split.append(new_reduction_package)
return reduction_packages_split
def create_initial_reduction_packages(state, workspaces, monitors):
"""
This provides the initial split of the workspaces.
If the data stems from multi-period data, then we need to split up the workspaces. The state object is valid
for each one of these workspaces. Hence we need to create a deep copy of them for each reduction package.
The way multi-period files are handled over the different workspaces input types is:
1. The sample scatter period determines all other periods, i.e. if the sample scatter workspace is has only
one period, but the sample transmission has two, then only the first period is used.
2. If the sample scatter period is not available on an other workspace type, then the last period on that
workspace type is used.
For the cases where the periods between the different workspaces types does not match, an information is logged.
:param state: A single state which potentially needs to be split up into several states
:param workspaces: The workspaces contributing to the reduction
:param monitors: The monitors contributing to the reduction
:return: A set of "Reduction packages" where each reduction package defines a single reduction.
"""
# For loaded peri0d we create a package
packages = []
data_info = state.data
sample_scatter_period = data_info.sample_scatter_period
requires_new_period_selection = len(workspaces[SANSDataType.SampleScatter]) > 1 \
and sample_scatter_period == ALL_PERIODS # noqa
is_multi_period = len(workspaces[SANSDataType.SampleScatter]) > 1
for index in range(0, len(workspaces[SANSDataType.SampleScatter])):
workspaces_for_package = {}
# For each workspace type, i.e sample scatter, can transmission, etc. find the correct workspace
for workspace_type, workspace_list in list(workspaces.items()):
workspace = get_workspace_for_index(index, workspace_list)
workspaces_for_package.update({workspace_type: workspace})
# For each monitor type, find the correct workspace
monitors_for_package = {}
for workspace_type, workspace_list in list(monitors.items()):
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
workspace = get_workspace_for_index(index, workspace_list)
monitors_for_package.update({workspace_type: workspace})
state_copy = deepcopy(state)
# Set the period on the state
if requires_new_period_selection:
state_copy.data.sample_scatter_period = index + 1
packages.append(ReductionPackage(state=state_copy,
workspaces=workspaces_for_package,
monitors=monitors_for_package,
is_part_of_multi_period_reduction=is_multi_period,
is_part_of_event_slice_reduction=False))
return packages
def get_workspace_for_index(index, workspace_list):
"""
Extracts the workspace from the list of workspaces. The index is set by the nth ScatterSample workspace.
There might be situation where there is no corresponding CanXXX workspace or SampleTransmission workspace etc,
since they are optional.
:param index: The index of the workspace from which to extract.
:param workspace_list: A list of workspaces.
:return: The workspace corresponding to the index or None
"""
if workspace_list:
if index < len(workspace_list):
workspace = workspace_list[index]
else:
workspace = None
else:
workspace = None
return workspace
def set_properties_for_reduction_algorithm(reduction_alg, reduction_package, workspace_to_name, workspace_to_monitor):
"""
Sets up everything necessary on the reduction algorithm.
:param reduction_alg: a handle to the reduction algorithm
:param reduction_package: a reduction package object
:param workspace_to_name: the workspace to name map
:param workspace_to_monitor: a workspace to monitor map
"""
def _set_output_name(_reduction_alg, _reduction_package, _is_group, _reduction_mode, _property_name,
_attr_out_name, _atrr_out_name_base, multi_reduction_type, _suffix=None, transmission=False):
if not transmission:
_out_name, _out_name_base = get_output_name(_reduction_package.state, _reduction_mode, _is_group,
multi_reduction_type=multi_reduction_type)
else:
_out_name, _out_name_base = get_transmission_output_name(_reduction_package.state, _reduction_mode
, multi_reduction_type=multi_reduction_type)
if _suffix is not None:
_out_name += _suffix
_out_name_base += _suffix
_reduction_alg.setProperty(_property_name, _out_name)
setattr(_reduction_package, _attr_out_name, _out_name)
setattr(_reduction_package, _atrr_out_name_base, _out_name_base)
def _set_output_name_from_string(reduction_alg, reduction_package, algorithm_property_name, workspace_name,
workspace_name_base ,package_attribute_name, package_attribute_name_base):
reduction_alg.setProperty(algorithm_property_name, workspace_name)
setattr(reduction_package, package_attribute_name, workspace_name)
setattr(reduction_package, package_attribute_name_base, workspace_name_base)
def _set_lab(_reduction_alg, _reduction_package, _is_group):
_set_output_name(_reduction_alg, _reduction_package, _is_group, ISISReductionMode.LAB,
"OutputWorkspaceLABCan", "reduced_lab_can_name", "reduced_lab_can_base_name",
multi_reduction_type, LAB_CAN_SUFFIX)
# Lab Can Count workspace - this is a partial workspace
_set_output_name(_reduction_alg, _reduction_package, _is_group, ISISReductionMode.LAB,
"OutputWorkspaceLABCanCount", "reduced_lab_can_count_name", "reduced_lab_can_count_base_name",
multi_reduction_type, LAB_CAN_COUNT_SUFFIX)
# Lab Can Norm workspace - this is a partial workspace
_set_output_name(_reduction_alg, _reduction_package, _is_group, ISISReductionMode.LAB,
"OutputWorkspaceLABCanNorm", "reduced_lab_can_norm_name", "reduced_lab_can_norm_base_name",
multi_reduction_type, LAB_CAN_NORM_SUFFIX)
_set_output_name(_reduction_alg, _reduction_package, _is_group, ISISReductionMode.LAB,
"OutputWorkspaceLABSample", "reduced_lab_sample_name", "reduced_lab_sample_base_name",
multi_reduction_type, LAB_SAMPLE_SUFFIX)
def _set_hab(_reduction_alg, _reduction_package, _is_group):
# Hab Can Workspace
_set_output_name(_reduction_alg, _reduction_package, _is_group, ISISReductionMode.HAB,
"OutputWorkspaceHABCan", "reduced_hab_can_name", "reduced_hab_can_base_name",
multi_reduction_type, HAB_CAN_SUFFIX)
# Hab Can Count workspace - this is a partial workspace
_set_output_name(_reduction_alg, _reduction_package, _is_group, ISISReductionMode.HAB,
"OutputWorkspaceHABCanCount", "reduced_hab_can_count_name", "reduced_hab_can_count_base_name",
multi_reduction_type, HAB_CAN_COUNT_SUFFIX)
# Hab Can Norm workspace - this is a partial workspace
_set_output_name(_reduction_alg, _reduction_package, _is_group, ISISReductionMode.HAB,
"OutputWorkspaceHABCanNorm", "reduced_hab_can_norm_name", "reduced_hab_can_norm_base_name",
multi_reduction_type, HAB_CAN_NORM_SUFFIX)
_set_output_name(_reduction_alg, _reduction_package, _is_group, ISISReductionMode.HAB,
"OutputWorkspaceHABSample", "reduced_hab_sample_name", "reduced_hab_sample_base_name",
multi_reduction_type, HAB_SAMPLE_SUFFIX)
# Go through the elements of the reduction package and set them on the reduction algorithm
# Set the SANSState
state = reduction_package.state
state_dict = state.property_manager
reduction_alg.setProperty("SANSState", state_dict)
# Set the input workspaces
workspaces = reduction_package.workspaces
for workspace_type, workspace in list(workspaces.items()):
if workspace is not None:
reduction_alg.setProperty(workspace_to_name[workspace_type], workspace)
# Set the monitors
monitors = reduction_package.monitors
for workspace_type, monitor in list(monitors.items()):
if monitor is not None:
reduction_alg.setProperty(workspace_to_monitor[workspace_type], monitor)
# ------------------------------------------------------------------------------------------------------------------
# Set the output workspaces for LAB, HAB and Merged
# ------------------------------------------------------------------------------------------------------------------
is_part_of_multi_period_reduction = reduction_package.is_part_of_multi_period_reduction
is_part_of_event_slice_reduction = reduction_package.is_part_of_event_slice_reduction
is_part_of_wavelength_range_reduction = reduction_package.is_part_of_wavelength_range_reduction
is_group = is_part_of_multi_period_reduction or is_part_of_event_slice_reduction or is_part_of_wavelength_range_reduction
multi_reduction_type = {"period": is_part_of_multi_period_reduction, "event_slice": is_part_of_event_slice_reduction,
"wavelength_range": is_part_of_wavelength_range_reduction}
reduction_mode = reduction_package.reduction_mode
if reduction_mode is ISISReductionMode.Merged:
_set_output_name(reduction_alg, reduction_package, is_group, ISISReductionMode.Merged,
"OutputWorkspaceMerged", "reduced_merged_name", "reduced_merged_base_name", multi_reduction_type)
_set_output_name(reduction_alg, reduction_package, is_group, ISISReductionMode.LAB,
"OutputWorkspaceLAB", "reduced_lab_name", "reduced_lab_base_name", multi_reduction_type)
_set_output_name(reduction_alg, reduction_package, is_group, ISISReductionMode.HAB,
"OutputWorkspaceHAB", "reduced_hab_name", "reduced_hab_base_name", multi_reduction_type)
elif reduction_mode is ISISReductionMode.LAB:
_set_output_name(reduction_alg, reduction_package, is_group, ISISReductionMode.LAB,
"OutputWorkspaceLAB", "reduced_lab_name", "reduced_lab_base_name", multi_reduction_type)
elif reduction_mode is ISISReductionMode.HAB:
_set_output_name(reduction_alg, reduction_package, is_group, ISISReductionMode.HAB,
"OutputWorkspaceHAB", "reduced_hab_name", "reduced_hab_base_name", multi_reduction_type)
elif reduction_mode is ISISReductionMode.All:
_set_output_name(reduction_alg, reduction_package, is_group, ISISReductionMode.LAB,
"OutputWorkspaceLAB", "reduced_lab_name", "reduced_lab_base_name", multi_reduction_type)
_set_output_name(reduction_alg, reduction_package, is_group, ISISReductionMode.HAB,
"OutputWorkspaceHAB", "reduced_hab_name", "reduced_hab_base_name", multi_reduction_type)
else:
raise RuntimeError("The reduction mode {0} is not known".format(reduction_mode))
# ------------------------------------------------------------------------------------------------------------------
# Set the output workspaces for the can reduction and the partial can reductions
# ------------------------------------------------------------------------------------------------------------------
# Set the output workspaces for the can reductions -- note that these will only be set if optimizations
# are enabled
# Lab Can Workspace
if reduction_mode is ISISReductionMode.Merged:
_set_lab(reduction_alg, reduction_package, is_group)
_set_hab(reduction_alg, reduction_package, is_group)
elif reduction_mode is ISISReductionMode.LAB:
_set_lab(reduction_alg, reduction_package, is_group)
elif reduction_mode is ISISReductionMode.HAB:
_set_hab(reduction_alg, reduction_package, is_group)
elif reduction_mode is ISISReductionMode.All:
_set_lab(reduction_alg, reduction_package, is_group)
_set_hab(reduction_alg, reduction_package, is_group)
else:
raise RuntimeError("The reduction mode {0} is not known".format(reduction_mode))
#-------------------------------------------------------------------------------------------------------------------
# Set the output workspaces for the calculated and unfitted transmission
#-------------------------------------------------------------------------------------------------------------------
if state.adjustment.show_transmission:
sample_calculated_transmission_base = get_transmission_output_name(reduction_package.state, DataType.Sample,
multi_reduction_type, True)
can_calculated_transmission_base = get_transmission_output_name(reduction_package.state, DataType.Can,
multi_reduction_type, True)
sample_unfitted_transmission_base = get_transmission_output_name(reduction_package.state, DataType.Sample,
multi_reduction_type, False)
can_unfitted_transmission_base = get_transmission_output_name(reduction_package.state, DataType.Can,
multi_reduction_type, False)
_set_output_name_from_string(reduction_alg, reduction_package, "OutputWorkspaceCalculatedTransmission",
sample_calculated_transmission, sample_calculated_transmission_base
,"calculated_transmission_name", "calculated_transmission_base_name")
_set_output_name_from_string(reduction_alg, reduction_package, "OutputWorkspaceUnfittedTransmission",
sample_unfitted_transmission, sample_unfitted_transmission_base
, "unfitted_transmission_name", "unfitted_transmission_base_name")
_set_output_name_from_string(reduction_alg, reduction_package, "OutputWorkspaceCalculatedTransmissionCan",
can_calculated_transmission, can_calculated_transmission_base
, "calculated_transmission_can_name", "calculated_transmission_can_base_name")
_set_output_name_from_string(reduction_alg, reduction_package, "OutputWorkspaceUnfittedTransmissionCan",
can_unfitted_transmission, can_unfitted_transmission_base
, "unfitted_transmission_can_name", "unfitted_transmission_can_base_name")
def get_workspace_from_algorithm(alg, output_property_name):
"""
Gets the output workspace from an algorithm. Since we don't run this as a child we need to get it from the
ADS.
:param alg: a handle to the algorithm from which we want to take the output workspace property.
:param output_property_name: the name of the output property.
:return the workspace or None
"""
output_workspace_name = alg.getProperty(output_property_name).valueAsStr
if not output_workspace_name:
return None
if AnalysisDataService.doesExist(output_workspace_name):
return AnalysisDataService.retrieve(output_workspace_name)
else:
return None
# ----------------------------------------------------------------------------------------------------------------------
# Functions for outputs to the ADS and saving the file
# ----------------------------------------------------------------------------------------------------------------------
def group_workspaces_if_required(reduction_package, output_mode, save_can):
"""
The output workspaces have already been published to the ADS by the algorithm. Now we might have to
bundle them into a group if:
* They are part of a multi-period workspace or a sliced reduction
* They are reduced LAB and HAB workspaces of a Merged reduction
* They are can workspaces - they are all grouped into a single group
:param reduction_package: a list of reduction packages
:param output_mode: one of OutputMode. SaveToFile, PublishToADS, Both.
:param save_can: a bool. If true save out can and sample workspaces.
"""
is_part_of_multi_period_reduction = reduction_package.is_part_of_multi_period_reduction
is_part_of_event_slice_reduction = reduction_package.is_part_of_event_slice_reduction
is_part_of_wavelength_range_reduction = reduction_package.is_part_of_wavelength_range_reduction
requires_grouping = is_part_of_multi_period_reduction or is_part_of_event_slice_reduction\
reduced_lab = reduction_package.reduced_lab
reduced_hab = reduction_package.reduced_hab
reduced_merged = reduction_package.reduced_merged
is_merged_reduction = reduced_merged is not None
# Add the reduced workspaces to groups if they require this
if is_merged_reduction:
if requires_grouping:
add_to_group(reduced_merged, reduction_package.reduced_merged_base_name)
add_to_group(reduced_lab, REDUCED_HAB_AND_LAB_WORKSPACE_FOR_MERGED_REDUCTION)
add_to_group(reduced_hab, REDUCED_HAB_AND_LAB_WORKSPACE_FOR_MERGED_REDUCTION)
else:
add_to_group(reduced_lab, REDUCED_HAB_AND_LAB_WORKSPACE_FOR_MERGED_REDUCTION)
add_to_group(reduced_hab, REDUCED_HAB_AND_LAB_WORKSPACE_FOR_MERGED_REDUCTION)
else:
if requires_grouping:
add_to_group(reduced_lab, reduction_package.reduced_lab_base_name)
add_to_group(reduced_hab, reduction_package.reduced_hab_base_name)
# Can group workspace depends on if save_can is checked and output_mode
# Logic table for which group to save CAN into
# CAN | FILE | In OPTIMIZATION group
# ----------------------------------
# Y | Y | YES
# N | Y | YES
# Y | N | NO
# N | N | YES
if save_can and output_mode is not OutputMode.SaveToFile:
CAN_WORKSPACE_GROUP = CAN_AND_SAMPLE_WORKSPACE
else:
CAN_WORKSPACE_GROUP = CAN_COUNT_AND_NORM_FOR_OPTIMIZATION
# Add the can workspaces (used for optimizations) to a Workspace Group (if they exist)
add_to_group(reduction_package.reduced_lab_can, CAN_WORKSPACE_GROUP)
add_to_group(reduction_package.reduced_lab_can_count, CAN_COUNT_AND_NORM_FOR_OPTIMIZATION)
add_to_group(reduction_package.reduced_lab_can_norm, CAN_COUNT_AND_NORM_FOR_OPTIMIZATION)
add_to_group(reduction_package.reduced_hab_can, CAN_WORKSPACE_GROUP)
add_to_group(reduction_package.reduced_hab_can_count, CAN_COUNT_AND_NORM_FOR_OPTIMIZATION)
add_to_group(reduction_package.reduced_hab_can_norm, CAN_COUNT_AND_NORM_FOR_OPTIMIZATION)
add_to_group(reduction_package.reduced_lab_sample, CAN_AND_SAMPLE_WORKSPACE)
add_to_group(reduction_package.reduced_hab_sample, CAN_AND_SAMPLE_WORKSPACE)
if reduction_package.state.adjustment.show_transmission:
add_to_group(reduction_package.calculated_transmission, reduction_package.calculated_transmission_base_name)
add_to_group(reduction_package.calculated_transmission_can,
reduction_package.calculated_transmission_can_base_name)
add_to_group(reduction_package.unfitted_transmission, reduction_package.unfitted_transmission_base_name)
add_to_group(reduction_package.unfitted_transmission_can, reduction_package.unfitted_transmission_can_base_name)
def add_to_group(workspace, name_of_group_workspace):
"""
Creates a group workspace with the base name for the workspace
:param workspace: the workspace to add to the WorkspaceGroup
:param name_of_group_workspace: the name of the WorkspaceGroup
"""
if workspace is None:
return
name_of_workspace = workspace.name()
if AnalysisDataService.doesExist(name_of_group_workspace):
group_workspace = AnalysisDataService.retrieve(name_of_group_workspace)
if type(group_workspace) is WorkspaceGroup:
if not group_workspace.contains(name_of_workspace):
group_workspace.add(name_of_workspace)
group_name = "GroupWorkspaces"
group_options = {"InputWorkspaces": [name_of_workspace],
"OutputWorkspace": name_of_group_workspace}
group_alg = create_unmanaged_algorithm(group_name, **group_options)
group_alg.setAlwaysStoreInADS(True)
else:
group_name = "GroupWorkspaces"
group_options = {"InputWorkspaces": [name_of_workspace],
"OutputWorkspace": name_of_group_workspace}
group_alg = create_unmanaged_algorithm(group_name, **group_options)
group_alg.setAlwaysStoreInADS(True)
group_alg.execute()
def save_to_file(reduction_packages, save_can):
"""
Extracts all workspace names which need to be saved and saves them into a file.
@param reduction_packages: a list of reduction packages which contain all the relevant information for saving
@param save_can: a bool. When true save the unsubtracted can and sample workspaces
workspaces_names_to_save = get_all_names_to_save(reduction_packages, save_can=save_can)
state = reduction_packages[0].state
save_info = state.save
file_formats = save_info.file_format
for name_to_save in workspaces_names_to_save:
save_workspace_to_file(name_to_save, file_formats, name_to_save)
def delete_reduced_workspaces(reduction_packages):
"""
Deletes all workspaces which would have been generated from a list of reduction packages.
@param reduction_packages: a list of reduction package
"""
def _delete_workspaces(_delete_alg, _workspaces):
for _workspace in _workspaces:
if _workspace is not None:
_delete_alg.setProperty("Workspace", _workspace.name())
_delete_alg.execute()
# Get all names which were saved out to workspaces
# Delete each workspace
delete_name = "DeleteWorkspace"
delete_options = {}
delete_alg = create_unmanaged_algorithm(delete_name, **delete_options)
for reduction_package in reduction_packages: