From 158f0a61c94fc611bdeb3179504af469b5867027 Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Tue, 25 Apr 2023 12:46:14 +0100 Subject: [PATCH 1/4] seperate out calibration script into modules --- .../CalibrateVesuvioTest.py | 318 --- .../calibrate_vesuvio_uranium_martyn_MK4.py | 1331 ------------ .../calibrate_vesuvio_uranium_martyn_MK5.py | 1845 ----------------- .../calibration_scripts/__init__.py | 0 .../calibrate_vesuvio_analysis.py | 510 +++++ .../calibrate_vesuvio_fit.py | 1080 ++++++++++ .../calibrate_vesuvio_helper_functions.py | 218 ++ .../load_calibration_algorithms.py | 22 + .../tests/system/test_system.py | 660 ------ .../tests/system/test_system_analysis.py | 272 +++ .../tests/system/test_system_fit.py | 226 ++ .../tests/testhelpers/system_test_base.py | 172 ++ .../testhelpers/system_test_misc_functions.py | 19 + .../unit/test_calibrate_vesuvio_analysis.py | 2 +- .../tests/unit/test_calibrate_vesuvio_fit.py | 213 +- .../tests/unit/test_calibrate_vesuvio_misc.py | 29 +- 16 files changed, 2641 insertions(+), 4276 deletions(-) delete mode 100644 unpackaged/vesuvio_calibration/CalibrateVesuvioTest.py delete mode 100644 unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK4.py delete mode 100644 unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py create mode 100644 unpackaged/vesuvio_calibration/calibration_scripts/__init__.py create mode 100644 unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py create mode 100644 unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py create mode 100644 unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_helper_functions.py create mode 100644 unpackaged/vesuvio_calibration/load_calibration_algorithms.py delete mode 100644 unpackaged/vesuvio_calibration/tests/system/test_system.py create mode 100644 unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py create mode 100644 unpackaged/vesuvio_calibration/tests/system/test_system_fit.py create mode 100644 unpackaged/vesuvio_calibration/tests/testhelpers/system_test_base.py create mode 100644 unpackaged/vesuvio_calibration/tests/testhelpers/system_test_misc_functions.py diff --git a/unpackaged/vesuvio_calibration/CalibrateVesuvioTest.py b/unpackaged/vesuvio_calibration/CalibrateVesuvioTest.py deleted file mode 100644 index 9bc4e3f8..00000000 --- a/unpackaged/vesuvio_calibration/CalibrateVesuvioTest.py +++ /dev/null @@ -1,318 +0,0 @@ -import unittest -import numpy as np -import scipy.constants -import scipy.stats - -from mantid.api import FileFinder, WorkspaceGroup -from mantid.simpleapi import * -from CalibrateVesuvio import (calculate_r_theta, FRONTSCATTERING_RANGE, DETECTOR_RANGE, \ - BACKSCATTERING_RANGE, ENERGY_ESTIMATE, MEV_CONVERSION, \ - U_FRONTSCATTERING_SAMPLE, U_FRONTSCATTERING_BACKGROUND, \ - U_BACKSCATTERING_SAMPLE, U_BACKSCATTERING_BACKGROUND,\ - U_MASS, U_PEAK_ENERGIES) - -class EVSCalibrationTest(unittest.TestCase): - - def load_ip_file(self): - param_names = ['spectrum', 'theta', 't0', 'L0', 'L1'] - file_data = np.loadtxt(self._parameter_file, skiprows=3, usecols=[0,2,3,4,5], unpack=True) - - params = {} - for name, column in zip(param_names, file_data): - params[name] = column - - return params - - def _setup_copper_test(self): - self._run_range = "17087-17088" - self._background = "17086" - #Mass of copper in amu - self._mass = 63.546 - #d-spacings of a copper sample - self._d_spacings = np.array([2.0865, 1.807, 1.278, 1.0897]) - self._d_spacings.sort() - self._energy_estimates = np.array(ENERGY_ESTIMATE) - self._mode = 'FoilOut' - - def _setup_lead_test(self): - self._run_range = "17083-17084" - self._background = "17086" - #Mass of a lead in amu - self._mass = 207.19 - #d-spacings of a lead sample - self._d_spacings = np.array([1.489, 1.750, 2.475, 2.858]) - self._d_spacings.sort() - self._energy_estimates = np.array(ENERGY_ESTIMATE) - self._mode = 'FoilOut' - - def _setup_niobium_test(self): - self._run_range = "17089-17091" - self._background = "17086" - #Mass of a lead in amu - self._mass = 92.906 - #d-spacings of a lead sample - self._d_spacings = np.array([2.3356, 1.6515, 1.3484, 1.1678]) - self._d_spacings.sort() - self._energy_estimates = np.array(ENERGY_ESTIMATE) - self._mode = 'FoilOut' - - def _setup_E1_fit_test(self): - self._spec_list = DETECTOR_RANGE - self._d_spacings = [] - self._background = '' - self._mode = 'SingleDifference' - - def _setup_uranium_test(self): - self._function= 'Gaussian' - self._mass = U_MASS - self._d_spacings = [] - self._energy_estimates = np.array(U_PEAK_ENERGIES) - self._energy_estimates.sort() - self._mode = 'FoilOut' - - def tearDown(self): - mtd.clear() - - -class EVSCalibrationAnalysisTests(EVSCalibrationTest): - - def setUp(self): - self._calc_L0 = False - self._parameter_file = FileFinder.getFullPath("IP0005.par") - self._calibrated_params = self.load_ip_file() - self._iterations = 2 - - def test_copper(self): - self._setup_copper_test() - self._output_workspace = "copper_analysis_test" - - params_table = self.run_evs_calibration_analysis() - self.assert_theta_parameters_match_expected(params_table) - - def test_lead(self): - self._setup_lead_test() - self._output_workspace = "lead_analysis_test" - - params_table = self.run_evs_calibration_analysis() - self.assert_theta_parameters_match_expected(params_table) - - # def test_niobium(self): - # self._setup_niobium_test() - # self._output_workspace = "niobium_analysis_test" - # self._iterations = 1 - - # params_table = self.run_evs_calibration_analysis() - # self.assert_theta_parameters_match_expected(params_table) - - def test_copper_with_uranium(self): - self._setup_copper_test() - self._calc_L0 = True - self._output_workspace = "copper_analysis_test" - - params_table = self.run_evs_calibration_analysis() - self.assert_theta_parameters_match_expected(params_table) - - def test_lead_with_uranium(self): - self._setup_lead_test() - self._calc_L0 = True - self._output_workspace = "lead_analysis_test" - - params_table = self.run_evs_calibration_analysis() - self.assert_theta_parameters_match_expected(params_table) - - def tearDown(self): - mtd.clear() - - #------------------------------------------------------------------ - # Misc helper functions - #------------------------------------------------------------------ - - def assert_theta_parameters_match_expected(self, params_table, rel_tolerance=0.4): - thetas = params_table.column('theta') - actual_thetas = self._calibrated_params['theta'] - - self.assertFalse(np.isnan(thetas).any()) - self.assertFalse(np.isinf(thetas).any()) - np.testing.assert_allclose(actual_thetas, thetas, rtol=rel_tolerance) - - def run_evs_calibration_analysis(self): - EVSCalibrationAnalysis(self._output_workspace, Samples=self._run_range, Background=self._background, - InstrumentParameterFile=self._parameter_file, Mass=self._mass, DSpacings=self._d_spacings, - Iterations=self._iterations, CalculateL0=self._calc_L0) - - last_fit_iteration = self._output_workspace + '_Iteration_%d' % (self._iterations-1) - return mtd[last_fit_iteration] - - -class EVSCalibrationFitTests(EVSCalibrationTest): - - def setUp(self): - self._function = 'Voigt' - self._parameter_file = FileFinder.getFullPath("IP0005.par") - self._calibrated_params = self.load_ip_file() - self._mode = 'FoilOut' - self._energy_estimates = np.array([ENERGY_ESTIMATE]) - - def test_fit_bragg_peaks_copper(self): - self._setup_copper_test() - self._spec_list = DETECTOR_RANGE - self._output_workspace = "copper_bragg_peak_fit" - - params_table = self.run_evs_calibration_fit() - expected_values = self.calculate_theta_peak_positions() - self.assert_fitted_positions_match_expected(expected_values, params_table, rel_tolerance=0.7) - - def test_fit_bragg_peaks_lead(self): - self._setup_lead_test() - self._spec_list = DETECTOR_RANGE - self._output_workspace = "lead_bragg_peak_fit" - - expected_values = self.calculate_theta_peak_positions() - params_table = self.run_evs_calibration_fit() - self.assert_fitted_positions_match_expected(expected_values, params_table, rel_tolerance=0.7) - - def test_fit_peaks_copper(self): - self._setup_copper_test() - self._setup_E1_fit_test() - self._output_workspace = "copper_peak_fit" - - expected_values = self.calculate_energy_peak_positions() - params_table = self.run_evs_calibration_fit() - self.assert_fitted_positions_match_expected(expected_values, params_table, rel_tolerance=0.7) - - def test_fit_peaks_lead(self): - self._setup_copper_test() - self._setup_E1_fit_test() - self._output_workspace = "lead_peak_fit" - - expected_values = self.calculate_energy_peak_positions() - params_table = self.run_evs_calibration_fit() - self.assert_fitted_positions_match_expected(expected_values, params_table, rel_tolerance=0.7) - - def test_fit_frontscattering_uranium(self): - self._setup_uranium_test() - self._run_range = U_FRONTSCATTERING_SAMPLE - self._background = U_FRONTSCATTERING_BACKGROUND - self._spec_list = FRONTSCATTERING_RANGE - self._output_workspace = 'uranium_peak_fit_front' - - expected_values = self.calculate_energy_peak_positions() - params_table = self.run_evs_calibration_fit() - - self.assert_fitted_positions_match_expected(expected_values, params_table, rel_tolerance=0.1) - - def test_fit_backscattering_uranium(self): - self._setup_uranium_test() - self._run_range = U_BACKSCATTERING_SAMPLE - self._background = U_BACKSCATTERING_BACKGROUND - self._spec_list = BACKSCATTERING_RANGE - self._output_workspace = 'uranium_peak_fit_back' - - expected_values = self.calculate_energy_peak_positions() - params_table = self.run_evs_calibration_fit() - self.assert_fitted_positions_match_expected(expected_values, params_table, rel_tolerance=0.01, ignore_zero=True) - - #------------------------------------------------------------------ - # Misc Helpers functions - #------------------------------------------------------------------ - - def assert_fitted_positions_match_expected(self, expected_positions, params_table, rel_tolerance=1e-7, abs_tolerance=0, ignore_zero=False): - """ - Check that each of the fitted peak positions match the expected - positions in time of flight calculated from the parameter file - within a small tolerance. - """ - if isinstance(params_table, WorkspaceGroup): - params_table = params_table.getNames()[0] - params_table = mtd[params_table] - - column_names = self.find_all_peak_positions(params_table) - - for name, expected_position in zip(column_names, expected_positions): - position = np.array(params_table.column(name)) - - if ignore_zero: - expected_position, position = self.mask_bad_detector_readings(expected_position, position) - - self.assertFalse(np.isnan(position).any()) - self.assertFalse(np.isinf(position).any()) - np.set_printoptions(threshold=np.nan) - np.testing.assert_allclose(expected_position, position, rtol=rel_tolerance, atol=abs_tolerance) - - def mask_bad_detector_readings(self, expected_positions, actual_positions): - """ - Mask values that are very close to zero. - - This handles the case where some of the uranium runs have a missing entry - for one of the detectors. - """ - zero_mask = np.where(actual_positions > 1e-10) - expected_positions = expected_positions[zero_mask] - actual_positions = actual_positions[zero_mask] - return expected_positions, actual_positions - - def run_evs_calibration_fit(self): - EVSCalibrationFit(Samples=self._run_range, Background=self._background, - Function=self._function, Mode=self._mode, SpectrumRange=self._spec_list, - InstrumentParameterFile=self._parameter_file, DSpacings=self._d_spacings, - Energy=self._energy_estimates, - OutputWorkspace=self._output_workspace, CreateOutput=False) - - return mtd[self._output_workspace + '_Peak_Parameters'] - - def find_all_peak_positions(self, params_table): - filter_errors_func = lambda name: ('LorentzPos' in name or 'PeakCentre' in name) and 'Err' not in name - column_names = params_table.getColumnNames() - column_names = filter(filter_errors_func, column_names) - return column_names - - def calculate_energy_peak_positions(self): - """ - Using the calibrated values to calculate the expected - position of the L0/L1/E1 peak in time of flight. - """ - lower, upper = self._spec_list[0] - DETECTOR_RANGE[0], self._spec_list[1] - DETECTOR_RANGE[0] +1 - - L0 = self._calibrated_params['L0'][lower:upper] - L1 = self._calibrated_params['L1'][lower:upper] - t0 = self._calibrated_params['t0'][lower:upper] - thetas = self._calibrated_params['theta'][lower:upper] - r_theta = calculate_r_theta(self._mass, thetas) - - t0 /= 1e+6 - - energy_estimates = np.copy(self._energy_estimates) - energy_estimates = energy_estimates.reshape(1, energy_estimates.size).T - energy_estimates = energy_estimates * MEV_CONVERSION - - v1 = np.sqrt(2 * energy_estimates / scipy.constants.m_n) - tof = ((L0 * r_theta + L1) / v1) + t0 - tof *= 1e+6 - - return tof - - def calculate_theta_peak_positions(self): - """ - Using the calibrated values of theta calculate the expected - peak position in time of flight. - """ - L0 = self._calibrated_params['L0'] - L1 = self._calibrated_params['L1'] - t0 = self._calibrated_params['t0'] - thetas = self._calibrated_params['theta'] - - t0 /= 1e+6 - - d_spacings = np.copy(self._d_spacings) - d_spacings *= 1e-10 - d_spacings = d_spacings.reshape(1, d_spacings.size).T - - lambdas = 2 * d_spacings * np.sin(np.radians(thetas) / 2) - tof = (lambdas * scipy.constants.m_n * (L0 + L1)) / scipy.constants.h + t0 - tof *= 1e+6 - - return tof - -if __name__ == '__main__': - unittest.main() - diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK4.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK4.py deleted file mode 100644 index 03d04b5e..00000000 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK4.py +++ /dev/null @@ -1,1331 +0,0 @@ -""" - Calibration algorithms for the VESUVIO spectrometer. This file provides two Mantid algorithms: EVSCalibrationFit and EVSCalibrationAnalysis. EVSCalibrationFit - is used to fit m peaks to n spectra. The positions of the peaks are esitmated using the supplied instrument parameter file and the d-spacings of the sample (if provided) - and provides support for both Voigt and Gaussian functions. EVSCalibrationAnalysis uses EVSCalibrationFit to calculate instrument parameters using the output of - the fitting and the and an existing instrument parameter file. - - The procedures used here are based upon those descibed in: Calibration of an electron volt neutron spectrometer, Nuclear Instruments and Methods in Physics Research A - (15 October 2010), doi:10.1016/j.nima.2010.09.079 by J. Mayers, M. A. Adams -""" - -from mantid.kernel import * -from mantid.api import * -from mantid.simpleapi import * - -import os -import sys -import math -import scipy.constants -import scipy.stats -import numpy as np - -# Configuration for Uranium runs / Indium runs -#---------------------------------------------------------------------------------------- -#Uranium sample & background run numbers -U_FRONTSCATTERING_SAMPLE = [14025] #[14025] for U foil in the beam, [19129, 19130] for In foil in the beam - -# ['12570'] or [19132, 19134, 19136, 19138, 19140, 19142,19144, 19146, 19148, 19150, 19152] or [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, 42221, 42222, 42223, 42224, 42225, 42226, 42227,42228] for Pb 2mm with U foil out -U_FRONTSCATTERING_BACKGROUND = [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, 42221, 42222, 42223, 42224, 42225, 42226, 42227,42228] - -# ['12570'] or [19132, 19134, 19136, 19138, 19140, 19142,19144, 19146, 19148, 19150, 19152] or [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, 42221, 42222, 42223, 42224, 42225, 42226, 42227,42228] for Pb 2mm with U foil out -U_BACKSCATTERING_SAMPLE = [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, 42221, 42222, 42223, 42224, 42225, 42226, 42227,42228] - -# ['12571'] or [42229,42230,42231,42232,42233,42234,42235,42236,42237,42238,42239,42240,42241,42242,42243,42244,42245,42246,42247,42248,42249,42250,42251,42252,42253] or [19131, 19133, 19135, 19137, 19139, 19141, 19143, 19145, 19147, 19149, 19151] for Pb 2mm with U foil in -U_BACKSCATTERING_BACKGROUND = [42229,42230,42231,42232,42233,42234,42235,42236,42237,42238,42239,42240,42241,42242,42243,42244,42245,42246,42247,42248,42249,42250,42251,42252,42253] - -#peak enegy for U/In in mev -U_PEAK_ENERGIES = [36684, 20874, 6672] # [36684, 20874, 6672] for uranium, [39681,22723,14599,12056,9088,3855] for indium -#U mass/ In mass in amu -U_MASS=238.0289 # 113 for indium - -#misc global variables -#---------------------------------------------------------------------------------------- -#full range of both the front and backscattering banks -FRONTSCATTERING_RANGE = [135,198] -BACKSCATTERING_RANGE = [3,134] -DETECTOR_RANGE = [BACKSCATTERING_RANGE[0], FRONTSCATTERING_RANGE[1]] - -#file loading modes -MODES=["SingleDifference", "DoubleDifference", "ThickDifference", "FoilOut", "FoilIn", "FoilInOut"] - -# Different peak types that will be fit -PEAK_TYPES = ["Resonance", "Recoil", "Bragg"] - -# self._fit_window_range applies bith to fitting the resonances and the lead recoil peaks -# it is defined as the range left and right from the peak centre (i. e. the whole fitting window is twice the fitting range) - -BRAGG_PEAK_CROP_RANGE = (2000, 17000) -BRAGG_FIT_WINDOW_RANGE = 500 - -RECOIL_PEAK_CROP_RANGE = (300, 500) -RECOIL_FIT_WINDOW_RANGE = 300 - -RESONANCE_PEAK_CROP_RANGE =(100, 350) -RESONANCE_FIT_WINDOW_RANGE = 50 - - -#energy used to estimate peak position -ENERGY_ESTIMATE = 4897.3 - -#physical constants -#---------------------------------------------------------------------------------------- -#convert to 1 / v and scale for fitting -U_NEUTRON_VELOCITY = np.array([83769.7, 63190.5, 35725.4]) #np.array([83769.7, 63190.5, 35725.4]) # for U # np.array([87124.5,65929.8,52845.8,48023.1,41694.9,27155.7]) # for indium -U_NEUTRON_VELOCITY = 1.0 / U_NEUTRON_VELOCITY -U_NEUTRON_VELOCITY *= 1e+6 - - -#mass of a neutron in amu -NEUTRON_MASS_AMU = scipy.constants.value("neutron mass in u") -#1 meV in Joules -MEV_CONVERSION = 1.602176487e-22 - -#misc helper functions used by both algorithms -#---------------------------------------------------------------------------------------- - -def calculate_r_theta(sample_mass, thetas): - """ - Returns the ratio of the final neutron velocity to the initial neutron velocity - as a function of the scattering angle theta and atomic mass of lead and a neutron. - - @param sample_mass - mass of the sample in amu - @param thetas - vector containing the values of theta - @return the ratio of final and incident velocities - """ - rad_theta = np.radians(thetas) - r_theta = (np.cos(rad_theta) + np.sqrt( (sample_mass / NEUTRON_MASS_AMU)**2 - np.sin(rad_theta)**2 )) / ((sample_mass / NEUTRON_MASS_AMU) +1) - - return r_theta - -#---------------------------------------------------------------------------------------- -# The IP text file load function skips the first 3 rows of the text file. -# This assumes that there is one line for the file header and 2 lines for -# parameters of monitor 1 and monitor 2, respectively. -# One has to be careful here as some IP files have no header! -# In such a case one has to add header -# Otherwise, the algorithm will read the number of spectra to be fitted -# for the calibration of of L0, t0, L1, theta from the vectors: -# FRONTSCATTERING_RANGE, BACKSCATTERING_RANGE and DETECTOR_RANGE -# This number will be different from the number of fits performed -# (which will be given by the length of the variable table_name) -# The program will start running but will crash after fitting L0 and t0 - -def load_instrument_parameters(file_path, table_name): - """ - Load instrument parameters from file into a table workspace - - @param file_path - the location of the file on disk - @return the name of the table workspace. - """ - - file_data = np.loadtxt(file_path, skiprows=3, usecols=[0,2,3,4,5]) - - CreateEmptyTableWorkspace(OutputWorkspace=table_name) - table_ws = mtd[table_name] - - table_ws.addColumn('double', 'Spectrum') - table_ws.addColumn('double', 'theta') - table_ws.addColumn('double', 't0') - table_ws.addColumn('double', 'L0') - table_ws.addColumn('double', 'L1') - - for row in file_data: - table_ws.addRow(row.tolist()) - - return table_name - -#---------------------------------------------------------------------------------------- - -def read_table_column(table_name, column_name, spec_list=DETECTOR_RANGE): - """ - Read a column from a table workspace and return the data as an array. - - @param table_name - name of the table workspace - @param column_name - name of the column to select - @param spec_list - range of spectra to use - @return numpy array of values in the spec_list range - """ - - offset = DETECTOR_RANGE[0] - if len(spec_list) > 1: - lower, upper = spec_list - else: - lower = spec_list[0] - upper = spec_list[0] - - column_values = mtd[table_name].column(column_name) - return np.array(column_values[lower-offset:upper+1-offset]) - -#---------------------------------------------------------------------------------------- - -class EVSCalibrationFit(PythonAlgorithm): - - def summary(self): - return "Fits peaks to a list of spectra using the mass or the d-spacings (for bragg peaks) of the sample." - - def category(self): - return "VesuvioCalibration" - - def PyInit(self): - - self.declareProperty(StringArrayProperty("Samples", Direction.Input), - doc="Sample run numbers to fit peaks to.") - - self.declareProperty(StringArrayProperty("Background", Direction.Input), - doc="Run numbers to use as a background.") - - self.declareProperty('Mode', 'FoilOut', StringListValidator(MODES), - doc="Mode to load files with. This is passed to the LoadVesuvio algorithm. Default is FoilOut.") - - self.declareProperty('Function', 'Gaussian', StringListValidator(['Gaussian', 'Voigt']), - doc="Function to fit each of the spectra with. Default is Gaussian") - - spectrum_validator = IntArrayBoundedValidator() - spectrum_validator.setLower(DETECTOR_RANGE[0]) - spectrum_validator.setUpper(DETECTOR_RANGE[1]) - self.declareProperty(IntArrayProperty('SpectrumRange', DETECTOR_RANGE, spectrum_validator, Direction.Input), - doc='Spectrum range to use. Default is the total range (%d,%d)' % tuple(DETECTOR_RANGE)) - - self.declareProperty('Mass', 207.19, - doc="Mass of the sample in amu to be used when calculating energy. Default is Pb: 207.19") - - greaterThanZero = FloatArrayBoundedValidator() - greaterThanZero.setLower(0) - self.declareProperty(FloatArrayProperty('DSpacings', [], greaterThanZero, Direction.Input), - doc="List of d-spacings used to estimate the positions of bragg peaks in TOF.") - - - self.declareProperty(FloatArrayProperty('Energy', [ENERGY_ESTIMATE], FloatArrayMandatoryValidator(), Direction.Input), - doc='List of estimated expected energies for peaks. Optional: the default is %f' % ENERGY_ESTIMATE) - - self.declareProperty(FileProperty('InstrumentParameterFile', '', action=FileAction.OptionalLoad, extensions=["par"]), - doc='Filename of the instrument parameter file.') - - self.declareProperty('PeakType', '', StringListValidator(PEAK_TYPES), - doc='Choose the peak type that is being fitted.' - 'Note that supplying a set of dspacings overrides the setting here') - - self.declareProperty(ITableWorkspaceProperty("InstrumentParameterWorkspace", "", Direction.Input, PropertyMode.Optional), - doc='Workspace contain instrument parameters.') - - self.declareProperty('CreateOutput', False, - doc='Create fitting workspaces for each of the parameters.') - - self.declareProperty('OutputWorkspace', '', StringMandatoryValidator(), - doc="Name to call the output workspace.") - -#---------------------------------------------------------------------------------------- - - def PyExec(self): - self._setup() - self._preprocess() - - if self._fitting_bragg_peaks: - self._fit_bragg_peaks() - else: - self._fit_peaks() - - #create output of fit if required. - if self._create_output and not self._fitting_bragg_peaks: - self._generate_fit_workspaces() - - #clean up workspaces - if self._param_file != "": - DeleteWorkspace(self._param_table) - -#---------------------------------------------------------------------------------------- - - def _setup(self): - """ - Setup parameters for fitting. - """ - self._sample_run_numbers = self.getProperty("Samples").value - self._bkg_run_numbers = self.getProperty("Background").value - self._peak_function = self.getProperty("Function").value - self._mode = self.getProperty("Mode").value - self._energy_estimates = self.getProperty("Energy").value - self._sample_mass = self.getProperty("Mass").value - self._d_spacings = self.getProperty("DSpacings").value - self._param_file = self.getPropertyValue('InstrumentParameterFile') - self._peak_type = self.getPropertyValue('PeakType') - self._param_workspace = self.getPropertyValue('InstrumentParameterWorkspace') - self._spec_list = self.getProperty("SpectrumRange").value.tolist() - self._output_workspace_name = self.getPropertyValue("OutputWorkspace") - self._create_output = self.getProperty("CreateOutput").value - - self._d_spacings.sort() - - #validate spectra list - if len(self._spec_list) > 2: - self._spec_list = [self._spec_list[0], self._spec_list[-1]] - elif len(self._spec_list) == 1: - self._spec_list = [self._spec_list[0]] - elif len(self._spec_list) < 1: - raise ValueError("You must specify a spectrum range.") - - #validate sample run numbers - if len(self._sample_run_numbers) == 0: - raise ValueError("You must supply at least one sample run number.") - - self._sample = self._output_workspace_name + '_Sample_' + '_'.join(self._sample_run_numbers) - if len(self._bkg_run_numbers) > 0: - self._background = '' + self._bkg_run_numbers[0] - - - #fit function type - if self._peak_function == 'Voigt': - self._func_param_names = {'Height': 'LorentzAmp', 'Position': 'LorentzPos', 'Width': 'LorentzFWHM', 'Width_2': 'GaussianFWHM'} - elif self._peak_function == 'Gaussian': - self._func_param_names = {'Height': 'Height', 'Width': 'Sigma', 'Position': 'PeakCentre'} - else: - raise ValueError("Unsupported fit function type: %s" % self._peak_function) - - #validate instrument parameter workspace/file - if self._param_workspace != "": - self._param_table = self._param_workspace - elif self._param_file != "": - base = os.path.basename(self._param_file) - self._param_table = os.path.splitext(base)[0] - load_instrument_parameters(self._param_file, self._param_table) - - # check peak types - self._fitting_recoil_peaks = False - self._fitting_resonance_peaks = False - self._fitting_bragg_peaks = len(self._d_spacings) > 0 - if self._fitting_bragg_peaks: - self._ws_crop_range = BRAGG_PEAK_CROP_RANGE - self._fit_window_range = BRAGG_FIT_WINDOW_RANGE - else: - if self._peak_type == "Recoil": - self._fitting_recoil_peaks = True - self._ws_crop_range = RECOIL_PEAK_CROP_RANGE - self._fit_window_range = RECOIL_FIT_WINDOW_RANGE - elif self._peak_type == "Resonance": - self._fitting_resonance_peaks = True - self._ws_crop_range = RESONANCE_PEAK_CROP_RANGE - self._fit_window_range = RESONANCE_FIT_WINDOW_RANGE - -#---------------------------------------------------------------------------------------- - - def _preprocess(self): - """ - Preprocess a workspace. This include optionally dividing by a background - """ - self._load_files(self._sample_run_numbers, self._sample) - - xmin, xmax = self._ws_crop_range - CropWorkspace(self._sample, XMin=xmin, XMax=xmax, OutputWorkspace=self._sample) - - if len(self._bkg_run_numbers) > 0: - self._load_files(self._bkg_run_numbers, self._background) - - CropWorkspace(self._background, XMin=xmin, XMax=xmax, OutputWorkspace=self._background) - RebinToWorkspace(WorkspaceToRebin=self._background, WorkspaceToMatch=self._sample, OutputWorkspace=self._background) - Divide(self._sample, self._background, OutputWorkspace=self._sample) - - DeleteWorkspace(self._background) - - ReplaceSpecialValues(self._sample, NaNValue=0, NaNError=0, InfinityValue=0, InfinityError=0, OutputWorkspace=self._sample) - -#---------------------------------------------------------------------------------------- - - def _fit_bragg_peaks(self): - peak_positions = self._estimate_bragg_peak_positions() - num_peaks, num_spectra = peak_positions.shape - self._prog_reporter = Progress(self, 0.0, 1.0, num_spectra) - - peaks_table = self._output_workspace_name + '_Peak_Parameters' - CreateEmptyTableWorkspace(OutputWorkspace=peaks_table) - - param_names = ['f0.A0', 'f0.A1'] - for i in range(num_peaks): - param_names += ['f' + str(i) + '.' + name for name in self._func_param_names.values()] - - err_names = [name + '_Err' for name in param_names] - col_names = [element for tupl in zip(param_names, err_names) for element in tupl] - - mtd[peaks_table].addColumn('int', 'Spectrum') - for name in col_names: - mtd[peaks_table].addColumn('double', name) - - output_workspaces = [] - for i, peak_estimates_list in enumerate(peak_positions.transpose()): - self._prog_reporter.report("Fitting to spectrum %d" % i) - - spec_number = self._spec_list[0]+i - peak_table = self._sample + '_peaks_table_%d' % spec_number - find_peak_params = self._get_find_peak_parameters(spec_number, peak_estimates_list) - logger.notice(str(i) + ' ' + str(find_peak_params)) - FindPeaks(InputWorkspace=self._sample, WorkspaceIndex=i, PeaksList=peak_table, **find_peak_params) - - - #get parameters from the peaks table - peak_params = [] - prefix = ''#'f0.' - position = prefix + self._func_param_names['Position'] - - print(peak_estimates_list) - self._set_table_column(peak_table, position, peak_estimates_list, spec_list=None) - - for peak_index in range(mtd[peak_table].rowCount()): - peak_params.append(mtd[peak_table].row(peak_index)) - DeleteWorkspace(peak_table) - - - #build function string - func_string = self._build_multiple_peak_function(peak_params) - - #select min and max x range for fitting - positions = [params[position] for params in peak_params] - - if len(positions) < 1: - raise RuntimeError("FindPeaks could not find a peaks with the given parameters: %s" % find_peak_params) - - xmin = min(positions) - self._fit_window_range - xmax = max(positions) + self._fit_window_range - - #xmin, xmax = None, None - - #fit function to workspace - fit_output_name = self._output_workspace_name + '_Spec_%d' % spec_number - status, chi2, ncm, params, fws, func, cost_func = Fit(Function=func_string, InputWorkspace=self._sample, IgnoreInvalidData=True, - StartX=xmin, EndX=xmax, WorkspaceIndex=i, - CalcErrors=True, Output=fit_output_name, - Minimizer='Levenberg-Marquardt,RelError=1e-8') - - fit_values = dict(zip(params.column(0), params.column(1))) - fit_errors = dict(zip(params.column(0), params.column(2))) - - row_values = [fit_values['f0.A0'], fit_values['f0.A1']] - row_errors = [fit_errors['f0.A0'], fit_errors['f0.A1']] - row_values += [fit_values['f1.' + name] for name in param_names[2:]] - row_errors += [fit_errors['f1.' + name] for name in param_names[2:]] - row = [element for tupl in zip(row_values, row_errors) for element in tupl] - - mtd[peaks_table].addRow([spec_number] + row) - - DeleteWorkspace(fit_output_name + '_NormalisedCovarianceMatrix') - DeleteWorkspace(fit_output_name + '_Parameters') - - if self._create_output: - output_workspaces.append(fit_output_name + '_Workspace') - else: - DeleteWorkspace(fit_output_name + '_Workspace') - - if self._create_output: - GroupWorkspaces(','.join(output_workspaces), OutputWorkspace=self._output_workspace_name + "_Peak_Fits") - - -#---------------------------------------------------------------------------------------- - - def _fit_peaks(self): - """ - Fit peaks to time of flight data. - - This uses the Mantid algorithms FindPeaks and Fit. Estimates for the centre of the peaks - are found using either the energy or d-spacings provided. It creates a group workspace - containing one table workspace per peak with parameters for each detector. - """ - - peak_positions = self._estimate_peak_positions() - num_peaks, num_spectra = peak_positions.shape - self._prog_reporter = Progress(self,0.0,1.0,num_peaks*num_spectra) - - self._parameter_tables = [] - self._fit_workspaces = [] - for i, peak_estimates_list in enumerate(peak_positions): - #create parameter table - peaks_table = self._output_workspace_name + '_Peak_%d_Parameters' % i - param_names = self._create_parameter_table(peaks_table) - - #fit every spectrum - self._peak_fit_workspaces = [] - for j, peak_centre in enumerate(peak_estimates_list): - spec_number = self._spec_list[0]+j - self._prog_reporter.report("Fitting peak %d to spectrum %d" % (i,spec_number)) - - #find inital parameters given the estimate position - peak_table = '__' + self._sample + '_peaks_table_%d_%d' % (i,j) - find_peak_params = self._get_find_peak_parameters(spec_number, [peak_centre]) - FindPeaks(InputWorkspace=self._sample, WorkspaceIndex=j, PeaksList=peak_table, **find_peak_params) - - #extract data from table - if mtd[peak_table].rowCount() > 0: - peak_params = mtd[peak_table].row(0) - DeleteWorkspace(peak_table) - else: - logger.error('FindPeaks could not find any peaks matching the parameters:\n' + str(find_peak_params)) - sys.exit() - - #build fit function string - func_string = self._build_function_string(peak_params) - fit_output_name = '__' + self._output_workspace_name + '_Peak_%d_Spec_%d' % (i,j) - - #find x window - xmin, xmax = None, None - position = '' + self._func_param_names['Position'] - if peak_params[position] > 0: - xmin = peak_params[position]-self._fit_window_range - xmax = peak_params[position]+self._fit_window_range - else: - logger.warning('Could not specify fit window. Using full spectrum x range.') - - status, chi2, ncm, params, fws, func, cost_func = Fit(Function=func_string, InputWorkspace=self._sample, IgnoreInvalidData=True, - StartX=xmin, EndX=xmax, WorkspaceIndex=j, - CalcErrors=True, Output=fit_output_name, - Minimizer='Levenberg-Marquardt,RelError=1e-8') - - #output fit parameters to table workspace - fit_values = dict(zip(params.column(0), params.column(1))) - fit_errors = dict(zip(params.column(0), params.column(2))) - - row_values = [fit_values[name] for name in param_names] - row_errors = [fit_errors[name] for name in param_names] - row = [element for tupl in zip(row_values, row_errors) for element in tupl] - - mtd[peaks_table].addRow([spec_number] + row) - - self._parameter_tables.append(peaks_table) - self._peak_fit_workspaces.append(fws.name()) - - DeleteWorkspace(ncm) - DeleteWorkspace(params) - if not self._create_output: - DeleteWorkspace(fws) - - self._fit_workspaces.append(self._peak_fit_workspaces) - - GroupWorkspaces(self._parameter_tables, OutputWorkspace=self._output_workspace_name + '_Peak_Parameters') - - -#---------------------------------------------------------------------------------------- - - def _get_find_peak_parameters(self, spec_number, peak_centre): - """ - Get find peak parameters - - @param spec_num - the current spectrum number being fitted - @return dictionary of parameters for find peaks - """ - find_peak_params = {} - find_peak_params['PeakPositions'] = peak_centre - find_peak_params['PeakFunction'] = self._peak_function - find_peak_params['RawPeakParameters'] = True - find_peak_params['BackgroundType'] = 'Linear' - - if self._fitting_bragg_peaks: - find_peak_params['PeakPositionTolerance'] = 1000 - - if spec_number >= FRONTSCATTERING_RANGE[0]: - find_peak_params['FWHM'] = 70 - else: - find_peak_params['FWHM'] = 5 - - else: - if self._fitting_resonance_peaks: - # 25 seems to be able to fit the final peak in the first backscattering spectrum - if spec_number >= FRONTSCATTERING_RANGE[0]: - half_peak_window = 20 - else: - half_peak_window = 25 - else: - # #ust be recoil - half_peak_window = 60 - - - fit_windows = [[peak-half_peak_window, peak+half_peak_window] for peak in peak_centre] - # flatten the list: http://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python - find_peak_params['FitWindows'] = [peak for pair in fit_windows for peak in pair] - - return find_peak_params - -#---------------------------------------------------------------------------------------- - - def _create_parameter_table(self, peaks_table): - """ - Create a table workspace with headers corresponding to the parameters - output from fitting. - - @param peak_table - name to call the parameter table. - @return param_names - name of the columns - """ - CreateEmptyTableWorkspace(OutputWorkspace=peaks_table) - - param_names = ['f0.A0', 'f0.A1'] - param_names += ['f1.' + name for name in self._func_param_names.values()] - - err_names = [name + '_Err' for name in param_names] - col_names = [element for tupl in zip(param_names, err_names) for element in tupl] - - mtd[peaks_table].addColumn('int', 'Spectrum') - for name in col_names: - mtd[peaks_table].addColumn('double', name) - - return param_names - -#---------------------------------------------------------------------------------------- - - def _estimate_peak_positions(self): - """ - Estimate the position of a peak based on a given energy and previous calibrated parameters. - - @return list of estimates in tof for the peak centres - """ - L0 = self._read_param_column('L0', self._spec_list) - L1 = self._read_param_column('L1', self._spec_list) - t0 = self._read_param_column('t0', self._spec_list) - t0 /= 1e+6 - thetas = self._read_param_column('theta', self._spec_list) - r_theta = calculate_r_theta(self._sample_mass, thetas) - - self._energy_estimates = self._energy_estimates.reshape(1, self._energy_estimates.size).T - self._energy_estimates *= MEV_CONVERSION - - v1 = np.sqrt(2 * self._energy_estimates / scipy.constants.m_n) - if self._fitting_recoil_peaks: - # for recoil peaks: - tof = ((L0 * r_theta + L1) / v1) + t0 - elif self._fitting_resonance_peaks: - # for resonances: - tof = (L0 / v1) + t0 - else: - raise RuntimeError("Cannot estimate peak positions for unknown peak type") - - tof *= 1e+6 - return tof - -#---------------------------------------------------------------------------------------- - - def _estimate_bragg_peak_positions(self): - """ - Estimate the position of bragg peaks in TOF using estimates of the parameters - and d-spacings of the sample. - - The number of peaks will depend on the number of d-spacings provided. - - @return estimates of peak positions in TOF for all spectra - """ - - L0 = self._read_param_column('L0', self._spec_list) - L1 = self._read_param_column('L1', self._spec_list) - t0 = self._read_param_column('t0', self._spec_list) - thetas = self._read_param_column('theta', self._spec_list) - - t0 /= 1e+6 - - self._d_spacings *= 1e-10 - self._d_spacings = self._d_spacings.reshape(1, self._d_spacings.size).T - - lambdas = 2 * self._d_spacings * np.sin(np.radians(thetas) / 2) - tof = (lambdas * scipy.constants.m_n * (L0 + L1)) / scipy.constants.h + t0 - tof *= 1e+6 - - return tof - -#---------------------------------------------------------------------------------------- - - def _load_files(self, ws_numbers, output_name): - """ - Load a list of run numbers and sum each of the runs. - - @param ws_numbers - list of run numbers to load. - @param output_name - name to call the final workspace. - """ - - run_numbers = [run for run in self._parse_run_numbers(ws_numbers)] - self._load_file(run_numbers[0], output_name) - - temp_ws_name = '__EVS_calib_temp_ws' - for run_number in run_numbers[1:]: - self._load_file(run_number, temp_ws_name) - Plus(output_name, temp_ws_name, OutputWorkspace=output_name) - DeleteWorkspace(temp_ws_name) - -#---------------------------------------------------------------------------------------- - - def _parse_run_numbers(self, run_numbers): - """ - Converts a list of mixed single runs and run ranges - into a single flat list of run numbers. - - @param run_numbers - list of mixed single runs and run ranges - @return iterator to each run number in the flat list of runs - """ - for run in run_numbers: - if '-' in run: - sample_range = run.split('-') - sample_range = map(int, sample_range) - - for i in range(*sample_range): - yield str(i) - - else: - yield run - -#---------------------------------------------------------------------------------------- - - def _load_file(self, ws_name, output_name): - """ - Load a file into a workspace. - - This will attempt to use LoadVesuvio, but will fall back on LoadRaw if LoadVesuvio fails. - @param ws_name - name of the run to load. - @param output_name - name to call the loaded workspace - """ - try: - LoadVesuvio(Filename=ws_name, Mode=self._mode, OutputWorkspace=output_name, - SpectrumList="%d-%d" % (self._spec_list[0], self._spec_list[-1]), - EnableLogging=False) - except RuntimeError: - LoadRaw('EVS' + ws_name + '.raw', OutputWorkspace=output_name, - SpectrumMin=self._spec_list[0], SpectrumMax=self._spec_list[-1], - EnableLogging=False) - ConvertToDistribution(output_name, EnableLogging=False) - -#---------------------------------------------------------------------------------------- - - def _build_linear_background_function(self, peak, tie_gradient=False): - """ - Builds a string describing a peak function that can be passed to Fit. - This will be either a Gaussian or Lorentzian shape. - - @param peaks - dictionary containing the parameters for the function - @return the function string - """ - - bkg_intercept, bkg_graident = peak['A0'], peak['A1'] - fit_func = 'name=LinearBackground, A0=%f, A1=%f' % (bkg_intercept, bkg_graident) - fit_func += ';' - - return fit_func - -#---------------------------------------------------------------------------------------- - - def _build_peak_function(self, peak): - """ - Builds a string describing a linear background function that can be passed to Fit. - - @param peaks - dictionary containing the parameters for the function - @return the function string - """ - - fit_func = 'name=%s, ' % self._peak_function - - prefix = ''#f0.' - func_list = [name + '=' + str(peak[prefix + name]) for name in self._func_param_names.values()] - fit_func += ', '.join(func_list) + ';' - - return fit_func - -#---------------------------------------------------------------------------------------- - - def _build_multiple_peak_function(self, peaks): - """ - Builds a string describing a composite function that can be passed to Fit. - This will be a linear background with multiple peaks of either a Gaussian or Voigt shape. - - @param peaks - list of dictionaries containing the parameters for the function - @return the function string - """ - - if(len(peaks) == 0): - return '' - - fit_func = self._build_linear_background_function(peaks[0], False) - fit_func += '(' - for i, peak in enumerate(peaks): - fit_func += self._build_peak_function(peak) - - fit_func = fit_func[:-1] - fit_func += ');' - - return fit_func - -#---------------------------------------------------------------------------------------- - - def _build_function_string(self, peak): - """ - Builds a string describing a composite function that can be passed to Fit. - This will be a linear background with either a Gaussian or Voigt function. - - @param peak - dictionary containing the parameters for the function - @return the function string - """ - - if(len(peak) == 0): - return '' - - fit_func = self._build_linear_background_function(peak) - fit_func += self._build_peak_function(peak) - - return fit_func - -#---------------------------------------------------------------------------------------- - - def _read_param_column(self, column_name, spec_list=DETECTOR_RANGE): - """ - Read a column from a table workspace and return the data as an array. - - @param column_name - name of the column to select - @param spec_list - range of spectra to use - @return numpy array of values in the spec_list range - """ - - return read_table_column(self._param_table, column_name, spec_list) - -#---------------------------------------------------------------------------------------- - - def _set_table_column(self, table_name, column_name, data, spec_list=None): - """ - Add a set of data to a table workspace - - @param table_name - name of the table workspace to modify - @param column_name - name of the column to add data to - @param data - data array to add to the table - @param spec_list - range of rows to add the data too - """ - table_ws = mtd[table_name] - - if column_name not in table_ws.getColumnNames(): - table_ws.addColumn('double', column_name) - - if spec_list == None: - offset = 0 - else: - if len(data) < (spec_list[1]+1 - spec_list[0]): - raise ValueError("Not enough data from spectrum range.") - - offset = spec_list[0] - DETECTOR_RANGE[0] - - if isinstance(data, np.ndarray): - data = data.tolist() - - for i, value in enumerate(data): - table_ws.setCell(column_name, offset+i, value) - -#---------------------------------------------------------------------------------------- - - def _generate_fit_workspaces(self): - """ - Output the fit workspace for each spectrum fitted. - """ - fit_workspaces = map(list, zip(*self._fit_workspaces)) - group_ws_names = [] - for i, peak_fits in enumerate(fit_workspaces): - ws_name = self._output_workspace_name + '_%d_Workspace' % i - ExtractSingleSpectrum(InputWorkspace=self._sample, WorkspaceIndex=i, OutputWorkspace=ws_name) - - #transfer fits to individual spectrum - peak_collection = WorkspaceFactory.create(mtd[ws_name], NVectors=len(peak_fits)) - data_x = mtd[ws_name].readX(0) - - ax = TextAxis.create(len(peak_fits)+2) - ax.setLabel(0, "Data") - - for j, peak in enumerate(peak_fits): - peak_x = mtd[peak].readX(0) - peak_y = mtd[peak].readY(1) - peak_e = mtd[peak].readE(1) - - index = int(np.where(data_x == peak_x[0])[0]) - data_y = peak_collection.dataY(j) - data_y[index:index + peak_y.size] = peak_y - - data_e = peak_collection.dataE(j) - data_e[index:index + peak_e.size] = peak_e - - peak_collection.setX(j, data_x) - peak_collection.setY(j, data_y) - peak_collection.setE(j, data_e) - - ax.setLabel(j+1, "Peak_%d" % (j+1)) - - DeleteWorkspace(peak) - - peak_ws = "__tmp_peak_workspace" - mtd.addOrReplace(peak_ws, peak_collection) - AppendSpectra(ws_name, peak_ws, ValidateInputs=False, OutputWorkspace=ws_name) - - #create sum of peak fits - temp_sum_workspace = '__temp_sum_ws' - SumSpectra(InputWorkspace=peak_ws, OutputWorkspace=peak_ws) - AppendSpectra(ws_name, peak_ws, ValidateInputs=False, OutputWorkspace=ws_name) - ax.setLabel(mtd[ws_name].getNumberHistograms()-1, "Total") - DeleteWorkspace(peak_ws) - - mtd[ws_name].replaceAxis(1, ax) - group_ws_names.append(ws_name) - - GroupWorkspaces(group_ws_names, OutputWorkspace=self._output_workspace_name + "_Peak_Fits") - - -#---------------------------------------------------------------------------------------- - -AlgorithmFactory.subscribe(EVSCalibrationFit) - -######################################################################################### - -class EVSCalibrationAnalysis(PythonAlgorithm): - - def summary(self): - return "Calculates the calibration parameters for the EVS intrument." - - def category(self): - return "VesuvioCalibration" - - def PyInit(self): - - self.declareProperty(StringArrayProperty("Samples", Direction.Input), - doc="Sample run numbers to fit peaks to.") - - self.declareProperty(StringArrayProperty("Background", Direction.Input), - doc="Run numbers to use as a background.") - - self.declareProperty(FileProperty('InstrumentParameterFile', '', action=FileAction.Load, extensions=["par"]), - doc="Filename of the instrument parameter file.") - - self.declareProperty('Mass', sys.float_info.max, - doc="Mass of the sample in amu to be used when calculating energy. Default is Pb: 207.19") - - greaterThanZero = FloatArrayBoundedValidator() - greaterThanZero.setLower(0) - self.declareProperty(FloatArrayProperty('DSpacings', [], greaterThanZero, Direction.Input), - doc="List of d-spacings used to estimate the positions of peaks in TOF.") - - self.declareProperty('Iterations', 2, validator=IntBoundedValidator(lower=1), - doc="Number of iterations to perform. Default is 2.") - - self.declareProperty('CreateOutput', False, - doc="Whether to create output from fitting.") - - self.declareProperty('CalculateL0', False, - doc="Whether to calculate L0 or just use the values from the parameter file.") - - self.declareProperty('CreateIPFile', False, - doc="Whether to save the output as an IP file. \ - This file will use the same name as the OutputWorkspace and will be saved to the default save directory.") - - self.declareProperty('OutputWorkspace', '', StringMandatoryValidator(), - doc="Name to call the output workspace.") - -#---------------------------------------------------------------------------------------- - - def PyExec(self): - self._setup() - self._current_workspace = self._output_workspace_name + '_Iteration_0' - self._create_calib_parameter_table(self._current_workspace) - - if self._calc_L0: - #calibrate L0 from the fronstscattering detectors, use the value of the L0 calibrated from frontscattering detectors for all detectors - L0_fit = self._current_workspace + '_L0' - self._run_calibration_fit(Samples=U_FRONTSCATTERING_SAMPLE, Background=U_FRONTSCATTERING_BACKGROUND, SpectrumRange=FRONTSCATTERING_RANGE, - InstrumentParameterWorkspace=self._param_table, Mass=U_MASS, Energy=U_PEAK_ENERGIES, OutputWorkspace=L0_fit, - CreateOutput=self._create_output, PeakType='Resonance') - self._L0_peak_fits = L0_fit + '_Peak_Parameters' - self._calculate_incident_flight_path(self._L0_peak_fits, FRONTSCATTERING_RANGE) - - #calibrate t0 from the front scattering detectors - t0_fit_front = self._current_workspace + '_t0_front' - self._run_calibration_fit(Samples=U_FRONTSCATTERING_SAMPLE, Background=U_FRONTSCATTERING_BACKGROUND, SpectrumRange=FRONTSCATTERING_RANGE, - InstrumentParameterWorkspace=self._param_table, Mass=U_MASS, Energy=U_PEAK_ENERGIES, OutputWorkspace=t0_fit_front, - CreateOutput=self._create_output, PeakType='Resonance') - t0_peak_fits_front = t0_fit_front + '_Peak_Parameters' - self._calculate_time_delay(t0_peak_fits_front, FRONTSCATTERING_RANGE) - - #calibrate t0 from the backscattering detectors - t0_fit_back = self._current_workspace + '_t0_back' - self._run_calibration_fit(Samples=U_BACKSCATTERING_SAMPLE, Background=U_BACKSCATTERING_BACKGROUND, SpectrumRange=BACKSCATTERING_RANGE, - InstrumentParameterWorkspace=self._param_table, Mass=U_MASS, Energy=U_PEAK_ENERGIES, OutputWorkspace=t0_fit_back, - CreateOutput=self._create_output, PeakType='Resonance') - t0_peak_fits_back = t0_fit_back + '_Peak_Parameters' - self._calculate_time_delay(t0_peak_fits_back, BACKSCATTERING_RANGE) - else: - #Just copy values over from parameter file - t0 = read_table_column(self._param_table, 't0', DETECTOR_RANGE) - L0 = read_table_column(self._param_table, 'L0', DETECTOR_RANGE) - self._set_table_column(self._current_workspace, 't0', t0) - self._set_table_column(self._current_workspace, 'L0', L0) - - #repeatedly fit L1, E1 and Theta parameters - table_group = [] - for i in range(self._iterations): - #calibrate E1 from backscattering and use the backscattering averaged value for all detectors - E1_fit_back = self._current_workspace + '_E1_back' - self._run_calibration_fit(Samples=self._samples, Function='Voigt', Mode='SingleDifference', SpectrumRange=BACKSCATTERING_RANGE, - InstrumentParameterWorkspace=self._param_table, Mass=self._sample_mass, OutputWorkspace=E1_fit_back, CreateOutput=self._create_output, - PeakType='Recoil') - - - E1_peak_fits_back = mtd[self._current_workspace + '_E1_back_Peak_Parameters'].getNames()[0] - self._calculate_final_energy(E1_peak_fits_back, BACKSCATTERING_RANGE) - - # calibrate L1 for all detectors based on the averaged E1 value - self._calculate_final_flight_path(DETECTOR_RANGE) - - - - #calibrate theta for all detectors - theta_fit = self._current_workspace + '_theta' - self._run_calibration_fit(Samples=self._samples, Background=self._background, Function='Voigt', Mode='FoilOut', SpectrumRange=DETECTOR_RANGE, - InstrumentParameterWorkspace=self._param_table, DSpacings=self._d_spacings, OutputWorkspace=theta_fit, CreateOutput=self._create_output, - PeakType='Bragg') - self._theta_peak_fits = theta_fit + '_Peak_Parameters' - self._calculate_scattering_angle(self._theta_peak_fits, DETECTOR_RANGE) - - - #make the fitted parameters for this iteration the input to the next iteration. - table_group.append(self._current_workspace) - self._param_table = self._current_workspace - - if i < self._iterations-1: - self._current_workspace = self._output_workspace_name + '_Iteration_%d' % (i+1) - self._create_calib_parameter_table(self._current_workspace) - - #copy over L0 and t0 parameters to new table - t0 = read_table_column(self._param_table, 't0', DETECTOR_RANGE) - t0_error = read_table_column(self._param_table, 't0_Err', DETECTOR_RANGE) - L0 = read_table_column(self._param_table, 'L0', DETECTOR_RANGE) - L0_error = read_table_column(self._param_table, 'L0_Err', DETECTOR_RANGE) - - self._set_table_column(self._current_workspace, 't0', t0) - self._set_table_column(self._current_workspace, 'L0', L0) - self._set_table_column(self._current_workspace, 't0_Err', t0_error) - self._set_table_column(self._current_workspace, 'L0_Err', L0_error) - - GroupWorkspaces(','.join(table_group), OutputWorkspace=self._output_workspace_name) - - if self._make_IP_file: - ws_name = mtd[self._output_workspace_name].getNames()[-1] - self._save_instrument_parameter_file(ws_name, DETECTOR_RANGE) - -#---------------------------------------------------------------------------------------- - - def _run_calibration_fit(self, *args, **kwargs): - """ - Runs EVSCalibrationFit using the AlgorithmManager. - - This allows the calibration script to be run directly from the - script window after Mantid has started. - - @param args - positional arguments to the algorithm - @param kwargs - key word arguments to the algorithm - """ - from mantid.simpleapi import set_properties - alg = AlgorithmManager.create('EVSCalibrationFit') - alg.initialize() - alg.setRethrows(True) - set_properties(alg, *args, **kwargs) - alg.execute() - -#---------------------------------------------------------------------------------------- - - def _calculate_time_delay(self, table_name, spec_list): - """ - Calculate time delay from frontscattering detectors. - - @param table_name - name of table containing fitted parameters for the peak centres - @param spec_list - spectrum range to calculate t0 for. - """ - t0_param_table = self._current_workspace + '_t0_Parameters' - self._fit_linear(table_name, t0_param_table) - - t0 = np.asarray(mtd[t0_param_table].column('A0')) - t0_error = np.asarray(mtd[t0_param_table].column('A0_Err')) - - self._set_table_column(self._current_workspace, 't0', t0, spec_list) - self._set_table_column(self._current_workspace, 't0_Err', t0_error, spec_list) - - DeleteWorkspace(t0_param_table) - -#---------------------------------------------------------------------------------------- - - def _calculate_incident_flight_path(self, table_name, spec_list): - """ - Calculate incident flight path from frontscattering detectors. - This takes the average value of a fit over all detectors for the value of L0 - and t0. - - @param table_name - name of table containing fitted parameters for the peak centres - @param spec_list - spectrum range to calculate t0 for. - """ - L0_param_table = self._current_workspace + '_L0_Parameters' - self._fit_linear(table_name, L0_param_table) - - L0 = np.asarray(mtd[L0_param_table].column('A1')) - - spec_range = DETECTOR_RANGE[1]+1 - DETECTOR_RANGE[0] - mean_L0 = np.empty(spec_range) - L0_error = np.empty(spec_range) - - mean_L0.fill(np.mean(L0)) - L0_error.fill(scipy.stats.sem(L0)) - - self._set_table_column(self._current_workspace, 'L0', mean_L0) - self._set_table_column(self._current_workspace, 'L0_Err', L0_error) - - DeleteWorkspace(L0_param_table) - -#---------------------------------------------------------------------------------------- - - def _calculate_final_flight_path(self, spec_list): - """ - Calculate the final flight path using the values for energy. - This also uses the old value for L1 loaded from the parameter file. - - @param spec_list - spectrum range to calculate t0 for. - """ - nominal_E1 = np.empty(spec_list[1]+1-spec_list[0]) - nominal_E1.fill(ENERGY_ESTIMATE) # expected energy value - - E1 = read_table_column(self._current_workspace, 'E1', spec_list) - E1_error = read_table_column(self._current_workspace, 'E1_Err', spec_list) - t0 = read_table_column(self._current_workspace, 't0', spec_list) - L0 = read_table_column(self._current_workspace, 'L0', spec_list) - - old_L1 = read_table_column(self._param_table, 'L1', spec_list) - - #Deviation of fitted E1 from nominal E1. - deviation_E1 = nominal_E1 - E1 - dtv = deviation_E1*0.039 - - v1 = np.sqrt(nominal_E1 / 5.2276e-6) - delta_L1 = dtv*v1*1E-6 - L1 = old_L1 + delta_L1 - - #Calculate error in L1. - error_deviation_E1= nominal_E1-E1+E1_error - ddtv=error_deviation_E1*0.039 - - #Change in L1 required to give E1=4897+DE1. - error_delta_L1=ddtv*v1*1E-6 - L1_error=np.absolute(error_delta_L1-delta_L1) - - self._set_table_column(self._current_workspace, 'L1', L1, spec_list) - self._set_table_column(self._current_workspace, 'L1_Err', L1_error, spec_list) - -#---------------------------------------------------------------------------------------- - - def _calculate_scattering_angle(self, table_name, spec_list): - """ - Calculate the total scattering angle using the previous calculated parameters. - - @param table_name - name of table containing fitted parameters for the peak centres - @param spec_list - spectrum range to calculate t0 for. - """ - t0 = read_table_column(self._current_workspace, 't0', spec_list) - L0 = read_table_column(self._current_workspace, 'L0', spec_list) - L1 = read_table_column(self._current_workspace, 'L1', spec_list) - spec = read_table_column(self._current_workspace, 'Spectrum', spec_list) - - t0 /= 1e+6 - - d_spacings = np.asarray(self._d_spacings) - d_spacings *= 1e-10 - d_spacings = d_spacings.reshape(1, d_spacings.size).T - - peak_centres = [] - col_names = [name for name in mtd[table_name].getColumnNames() if 'LorentzPos' in name and 'LorentzPos_Err' not in name] - for name in col_names: - t = np.asarray(mtd[table_name].column(name)) - peak_centres.append(t) - - peak_centres = np.asarray(peak_centres) - masked_peak_centres = np.ma.masked_array(peak_centres, np.logical_or(peak_centres<=2000,peak_centres>=20000)) - masked_peak_centres /= 1e+6 - - sin_theta = ((masked_peak_centres - t0) * scipy.constants.h) / (scipy.constants.m_n * d_spacings * 2 * (L0+L1)) - theta = np.arcsin(sin_theta) * 2 - theta = np.degrees(theta) - - masked_theta = np.nanmean(theta, axis=0) - theta_error = np.nanstd(theta, axis=0) - - self._set_table_column(self._current_workspace, 'theta', masked_theta, spec_list) - self._set_table_column(self._current_workspace, 'theta_Err', theta_error, spec_list) - -#---------------------------------------------------------------------------------------- - - - - def _calculate_final_energy(self, peak_table, spec_list): - """ - Calculate the final energy using the fitted peak centres of a run. - - @param table_name - name of table containing fitted parameters for the peak centres - @param spec_list - spectrum range to calculate t0 for. - """ - t0 = read_table_column(self._current_workspace, 't0', spec_list) - L0 = read_table_column(self._current_workspace, 'L0', spec_list) - - L1 = read_table_column(self._param_table, 'L1', spec_list) - theta = read_table_column(self._param_table, 'theta', spec_list) - r_theta = calculate_r_theta(self._sample_mass, theta) - - peak_centres = read_table_column(peak_table, 'f1.LorentzPos', spec_list) - - delta_t = (peak_centres - t0) / 1e+6 - v1 = (L0 * r_theta + L1) / delta_t - - E1 = 0.5*scipy.constants.m_n*v1**2 - E1 /= MEV_CONVERSION - - - spec_range = DETECTOR_RANGE[1]+1 - DETECTOR_RANGE[0] - mean_E1 = np.empty(spec_range) - E1_error = np.empty(spec_range) - - mean_E1.fill(np.mean(E1)) - E1_error.fill(scipy.stats.sem(E1)) - - self._set_table_column(self._current_workspace, 'E1', mean_E1) - self._set_table_column(self._current_workspace, 'E1_Err', E1_error) - - -#---------------------------------------------------------------------------------------- - - def _setup(self): - """ - Setup algorithm. - """ - self._samples = self.getProperty("Samples").value - self._background = self.getProperty("Background").value - self._param_file = self.getProperty("InstrumentParameterFile").value - self._sample_mass = self.getProperty("Mass").value - self._d_spacings = self.getProperty("DSpacings").value.tolist() - self._calc_L0 = self.getProperty("CalculateL0").value - self._make_IP_file = self.getProperty("CreateIPFile").value - self._output_workspace_name = self.getPropertyValue("OutputWorkspace") - self._iterations = self.getProperty("Iterations").value - self._create_output = self.getProperty("CreateOutput").value - - if len(self._samples) == 0: - raise ValueError("You must supply at least one sample run number.") - - #if len(self._background) == 0: - # raise ValueError("You must supply at least one background run number.") - - self._d_spacings.sort() - - self._param_table = '__EVS_calib_analysis_parameters' - load_instrument_parameters(self._param_file, self._param_table) - -#---------------------------------------------------------------------------------------- - - def _create_calib_parameter_table(self, ws_name): - #create table for calculated parameters - CreateEmptyTableWorkspace(OutputWorkspace=ws_name) - table_ws = mtd[ws_name] - table_ws.addColumn('int', 'Spectrum') - - for value in range(DETECTOR_RANGE[0], DETECTOR_RANGE[1]+1): - table_ws.addRow([value]) - - column_names = ['t0','t0_Err','L0','L0_Err','L1','L1_Err','E1','E1_Err','theta','theta_Err'] - for name in column_names: - table_ws.addColumn('double', name) - -#---------------------------------------------------------------------------------------- - - def _fit_linear(self, table_workspace_group, output_table): - """ - Create a workspace wth the fitted peak_centres on the y and corresponding neutron velocity - on the x. The intercept is the value of t0 and the gradient is the value of L0/L-Total. - - @param table_workspace_group - workspace group containing the fitted parameters of the peaks. - @param output_table - name to call the fit workspace. - """ - #extract fit data to workspace - peak_workspaces = [] - for i, param_ws in enumerate(mtd[table_workspace_group].getNames()): - temp_peak_data = '__temp_peak_ws_%d' % i - ConvertTableToMatrixWorkspace(InputWorkspace=param_ws, OutputWorkspace=temp_peak_data, - ColumnX='Spectrum', ColumnY='f1.PeakCentre') - peak_workspaces.append(temp_peak_data) - - #create workspace of peaks - peak_workspace = table_workspace_group + '_Workspace' - RenameWorkspace(peak_workspaces[0], OutputWorkspace=peak_workspace) - for temp_ws in peak_workspaces[1:]: - ConjoinWorkspaces(peak_workspace, temp_ws, CheckOverlapping=False) - Transpose(peak_workspace, OutputWorkspace=peak_workspace) - - num_spectra = mtd[peak_workspace].getNumberHistograms() - plot_peak_indicies = ';'.join([peak_workspace + ',i' + str(i) for i in range(num_spectra)]) - - for i in range(num_spectra): - mtd[peak_workspace].setX(i, np.asarray(U_NEUTRON_VELOCITY)) - - ReplaceSpecialValues(peak_workspace, NaNValue=0, NaNError=0, InfinityValue=0, InfinityError=0, - OutputWorkspace=peak_workspace) - - #perform linear fit on peak centres - func_string = 'name=LinearBackground, A0=0, A1=0;' - PlotPeakByLogValue(Input=plot_peak_indicies, Function=func_string, - FitType='Individual', CreateOutput=False, OutputWorkspace=output_table) - DeleteWorkspace(peak_workspace) - -#---------------------------------------------------------------------------------------- - - - def _set_table_column(self, table_name, column_name, data, spec_list=None): - """ - Add a set of data to a table workspace - - @param table_name - name of the table workspace to modify - @param column_name - name of the column to add data to - @param data - data array to add to the table - @param spec_list - range of rows to add the data too - """ - table_ws = mtd[table_name] - - if column_name not in table_ws.getColumnNames(): - table_ws.addColumn('double', column_name) - - if spec_list == None: - offset = 0 - else: - if len(data) < (spec_list[1]+1 - spec_list[0]): - raise ValueError("Not enough data from spectrum range.") - - offset = spec_list[0] - DETECTOR_RANGE[0] - - if isinstance(data, np.ndarray): - data = data.tolist() - - for i, value in enumerate(data): - table_ws.setCell(column_name, offset+i, value) - -#---------------------------------------------------------------------------------------- - - def _save_instrument_parameter_file(self, ws_name, spec_list): - """ - Save the calibrated parameters to a tab delimited instrument parameter file. - - @param ws_name - name of the workspace to save the IP file from. - @param spec_list - spectrum range to save to file. - """ - file_header = '\t'.join(['plik', 'det', 'theta', 't0', 'L0', 'L1']) + '\n' - fmt = "%d %d %.4f %.4f %.3f %.4f" - - det = read_table_column(ws_name, 'Spectrum', spec_list) - t0 = read_table_column(ws_name, 't0', spec_list) - L0 = read_table_column(ws_name, 'L0', spec_list) - L1 = read_table_column(ws_name, 'L1', spec_list) - theta = read_table_column(ws_name, 'theta', spec_list) - - #pad the start of the file with dummy data for the monitors - file_data = np.asarray([[1,1,0,0,0,0], [2,2,0,0,0,0]]) - file_data = np.append(file_data, np.column_stack((det, det, theta, t0, L0, L1)), axis=0) - - #workdir = config['defaultsave.directory'] - workdir='C:\\Repos\\GitHub\Working Files\\VesuvioCalibrationScript\\VesuvioCalibration\\VesuvioCalibration\\uranium calibration and IP files' - #workdir='K:\\Neutron_computations\\MANTID\Mantid Vesuvio Calibration 2015\\uranium calibration and IP files' - file_path = os.path.join(workdir, self._output_workspace_name+'.par') - - with open(file_path, 'wb') as f_handle: - f_handle.write(file_header) - np.savetxt(f_handle, file_data, fmt=fmt) - -#---------------------------------------------------------------------------------------- - -AlgorithmFactory.subscribe(EVSCalibrationAnalysis) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py deleted file mode 100644 index b7317059..00000000 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ /dev/null @@ -1,1845 +0,0 @@ -""" - Calibration algorithms for the VESUVIO spectrometer. This file provides two Mantid algorithms: EVSCalibrationFit and EVSCalibrationAnalysis. EVSCalibrationFit - is used to fit m peaks to n spectra. The positions of the peaks are esitmated using the supplied instrument parameter file and the d-spacings of the sample (if provided) - and provides support for both Voigt and Gaussian functions. EVSCalibrationAnalysis uses EVSCalibrationFit to calculate instrument parameters using the output of - the fitting and the and an existing instrument parameter file. - - The procedures used here are based upon those descibed in: Calibration of an electron volt neutron spectrometer, Nuclear Instruments and Methods in Physics Research A - (15 October 2010), doi:10.1016/j.nima.2010.09.079 by J. Mayers, M. A. Adams -""" - -from mantid.kernel import StringArrayProperty, Direction, StringListValidator, IntArrayBoundedValidator, IntArrayProperty,\ - FloatArrayBoundedValidator, FloatArrayMandatoryValidator, StringMandatoryValidator, IntBoundedValidator,\ - FloatArrayProperty, logger -from mantid.api import FileProperty, FileAction, ITableWorkspaceProperty, PropertyMode, Progress, TextAxis, PythonAlgorithm, \ - AlgorithmFactory, WorkspaceFactory, AlgorithmManager, AnalysisDataService -from mantid.simpleapi import CreateEmptyTableWorkspace, DeleteWorkspace, CropWorkspace, RebinToWorkspace, Divide,\ - ReplaceSpecialValues, FindPeaks, GroupWorkspaces, mtd, Plus, LoadVesuvio, LoadRaw, ConvertToDistribution,\ - FindPeakBackground, ExtractSingleSpectrum, SumSpectra, AppendSpectra, ConvertTableToMatrixWorkspace,\ - ConjoinWorkspaces, Transpose, PlotPeakByLogValue, CloneWorkspace, Fit, RenameWorkspace, MaskDetectors,\ - ExtractUnmaskedSpectra, CreateWorkspace - -from functools import partial - -import os -import sys -import scipy.constants -import scipy.stats -import numpy as np - -# Configuration for Uranium runs / Indium runs -#---------------------------------------------------------------------------------------- -#Uranium sample & background run numbers -U_FRONTSCATTERING_SAMPLE = [14025] #[14025] for U foil in the beam, [19129, 19130] for In foil in the beam - -# ['12570'] or [19132, 19134, 19136, 19138, 19140, 19142,19144, 19146, 19148, 19150, 19152] or [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, 42221, 42222, 42223, 42224, 42225, 42226, 42227,42228] for Pb 2mm with U foil out -U_FRONTSCATTERING_BACKGROUND = [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, 42221, 42222, 42223, 42224, 42225, 42226, 42227,42228] - -# ['12570'] or [19132, 19134, 19136, 19138, 19140, 19142,19144, 19146, 19148, 19150, 19152] or [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, 42221, 42222, 42223, 42224, 42225, 42226, 42227,42228] for Pb 2mm with U foil out -U_BACKSCATTERING_SAMPLE = [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, 42221, 42222, 42223, 42224, 42225, 42226, 42227,42228] - -# ['12571'] or [42229,42230,42231,42232,42233,42234,42235,42236,42237,42238,42239,42240,42241,42242,42243,42244,42245,42246,42247,42248,42249,42250,42251,42252,42253] or [19131, 19133, 19135, 19137, 19139, 19141, 19143, 19145, 19147, 19149, 19151] for Pb 2mm with U foil in -U_BACKSCATTERING_BACKGROUND = [42229,42230,42231,42232,42233,42234,42235,42236,42237,42238,42239,42240,42241,42242,42243,42244,42245,42246,42247,42248,42249,42250,42251,42252,42253] - -#peak enegy for U/In in mev -U_PEAK_ENERGIES = [36684, 20874, 6672] # [36684, 20874, 6672] for uranium, [39681,22723,14599,12056,9088,3855] for indium -#U mass/ In mass in amu -U_MASS=238.0289 # 113 for indium - -#misc global variables -#---------------------------------------------------------------------------------------- -#full range of both the front and backscattering banks -FRONTSCATTERING_RANGE = [135,198] -BACKSCATTERING_RANGE = [3,134] -DETECTOR_RANGE = [BACKSCATTERING_RANGE[0], FRONTSCATTERING_RANGE[1]] - -#file loading modes -MODES=["SingleDifference", "DoubleDifference", "ThickDifference", "FoilOut", "FoilIn", "FoilInOut"] - -# Different peak types that will be fit -PEAK_TYPES = ["Resonance", "Recoil", "Bragg"] - -# self._fit_window_range applies bith to fitting the resonances and the lead recoil peaks -# it is defined as the range left and right from the peak centre (i. e. the whole fitting window is twice the fitting range) - -BRAGG_PEAK_CROP_RANGE = (2000, 20000) -BRAGG_FIT_WINDOW_RANGE = 500 -BRAGG_PEAK_POSITION_TOLERANCE = 1000 - -RECOIL_PEAK_CROP_RANGE = (300, 500) -RECOIL_FIT_WINDOW_RANGE = 300 - -RESONANCE_PEAK_CROP_RANGE =(100, 350) -RESONANCE_FIT_WINDOW_RANGE = 50 - -PEAK_HEIGHT_RELATIVE_THRESHOLD = 0.25 - -#energy used to estimate peak position -ENERGY_ESTIMATE = 4897.3 - -#physical constants -#---------------------------------------------------------------------------------------- -#convert to 1 / v and scale for fitting -U_NEUTRON_VELOCITY = np.array([83769.7, 63190.5, 35725.4]) #np.array([83769.7, 63190.5, 35725.4]) # for U # np.array([87124.5,65929.8,52845.8,48023.1,41694.9,27155.7]) # for indium -U_NEUTRON_VELOCITY = 1.0 / U_NEUTRON_VELOCITY -U_NEUTRON_VELOCITY *= 1e+6 - - -#mass of a neutron in amu -NEUTRON_MASS_AMU = scipy.constants.value("neutron mass in u") -#1 meV in Joules -MEV_CONVERSION = 1.602176487e-22 - -#misc helper functions used by both algorithms -#---------------------------------------------------------------------------------------- - -def calculate_r_theta(sample_mass, thetas): - """ - Returns the ratio of the final neutron velocity to the initial neutron velocity - as a function of the scattering angle theta and atomic mass of lead and a neutron. - - @param sample_mass - mass of the sample in amu - @param thetas - vector containing the values of theta - @return the ratio of final and incident velocities - """ - rad_theta = np.radians(thetas) - r_theta = (np.cos(rad_theta) + np.sqrt( (sample_mass / NEUTRON_MASS_AMU)**2 - np.sin(rad_theta)**2 )) / ((sample_mass / NEUTRON_MASS_AMU) +1) - - return r_theta - -#---------------------------------------------------------------------------------------- - -def identify_invalid_spectra(peak_table, peak_centres, peak_centres_errors, spec_list): - """ - Inspect fitting results, and identify the fits associated with invalid spectra. These are spectra associated with detectors - which have lost foil coverage following a recent reduction in distance from source to detectors. - - @param peak_table - name of table containing fitted parameters each spectra. - @param spec_list - spectrum range to inspect. - @return a list of invalid spectra. - """ - peak_Gaussian_FWHM = read_fitting_result_table_column(peak_table, 'f1.GaussianFWHM', spec_list) - peak_Gaussian_FWHM_errors = read_fitting_result_table_column(peak_table, 'f1.GaussianFWHM_Err', spec_list) - peak_Lorentz_FWHM = read_fitting_result_table_column(peak_table, 'f1.LorentzFWHM', spec_list) - peak_Lorentz_FWHM_errors = read_fitting_result_table_column(peak_table, 'f1.LorentzFWHM_Err', spec_list) - peak_Lorentz_Amp = read_fitting_result_table_column(peak_table, 'f1.LorentzAmp', spec_list) - peak_Lorentz_Amp_errors = read_fitting_result_table_column(peak_table, 'f1.LorentzAmp_Err', spec_list) - - invalid_spectra = np.argwhere((np.isinf(peak_Lorentz_Amp_errors)) | (np.isnan(peak_Lorentz_Amp_errors)) | \ - (np.isinf(peak_centres_errors)) | (np.isnan(peak_centres_errors)) | \ - (np.isnan(peak_Gaussian_FWHM_errors)) | (np.isinf(peak_Gaussian_FWHM_errors)) | \ - (np.isnan(peak_Lorentz_FWHM_errors)) | (np.isinf(peak_Lorentz_FWHM_errors)) | \ - (np.isnan(peak_Lorentz_Amp_errors)) | (np.isinf(peak_Lorentz_Amp_errors)) | \ - (np.absolute(peak_Gaussian_FWHM_errors) > np.absolute(peak_Gaussian_FWHM)) | \ - (np.absolute(peak_Lorentz_FWHM_errors) > np.absolute(peak_Lorentz_FWHM)) | \ - (np.absolute(peak_Lorentz_Amp_errors) > np.absolute(peak_Lorentz_Amp)) | \ - (np.absolute(peak_centres_errors) > np.absolute(peak_centres))) - - return invalid_spectra - -#---------------------------------------------------------------------------------------- -# The IP text file load function skips the first 3 rows of the text file. -# This assumes that there is one line for the file header and 2 lines for -# parameters of monitor 1 and monitor 2, respectively. -# One has to be careful here as some IP files have no header! -# In such a case one has to add header -# Otherwise, the algorithm will read the number of spectra to be fitted -# for the calibration of of L0, t0, L1, theta from the vectors: -# FRONTSCATTERING_RANGE, BACKSCATTERING_RANGE and DETECTOR_RANGE -# This number will be different from the number of fits performed -# (which will be given by the length of the variable table_name) -# The program will start running but will crash after fitting L0 and t0 - -def load_instrument_parameters(file_path, table_name): - """ - Load instrument parameters from file into a table workspace - - @param file_path - the location of the file on disk - @return the name of the table workspace. - """ - - file_data = np.loadtxt(file_path, skiprows=3, usecols=[0,2,3,4,5]) - - CreateEmptyTableWorkspace(OutputWorkspace=table_name) - table_ws = mtd[table_name] - - table_ws.addColumn('double', 'Spectrum') - table_ws.addColumn('double', 'theta') - table_ws.addColumn('double', 't0') - table_ws.addColumn('double', 'L0') - table_ws.addColumn('double', 'L1') - - for row in file_data: - table_ws.addRow(row.tolist()) - - return table_name - -#---------------------------------------------------------------------------------------- - -def read_table_column(table_name, column_name, spec_list=DETECTOR_RANGE): - """ - Read a column from a table workspace representing the instrument parameter file and return the data as an array. - - @param table_name - name of the table workspace - @param column_name - name of the column to select - @param spec_list - range of spectra to use - @return numpy array of values in the spec_list range - """ - - offset = DETECTOR_RANGE[0] - if len(spec_list) > 1: - lower, upper = spec_list - else: - lower = spec_list[0] - upper = spec_list[0] - - column_values = mtd[table_name].column(column_name) - - return np.array(column_values[lower-offset:upper+1-offset]) - -#---------------------------------------------------------------------------------------- - -def read_fitting_result_table_column(table_name, column_name, spec_list): - """ - Read a column from a table workspace resulting from fitting and return the data as an array. - - @param table_name - name of the table workspace - @param column_name - name of the column to select - @param spec_list - range of spectra to use - @return numpy array of values in the spec_list range - """ - - offset = spec_list[0] - if len(spec_list) > 1: - lower, upper = spec_list - else: - lower = spec_list[0] - upper = spec_list[0] - - column_values = mtd[table_name].column(column_name) - - return np.array(column_values[lower-offset:upper+1-offset]) - -#---------------------------------------------------------------------------------------- - -def generate_fit_function_header(function_type, error=False): - if function_type == 'Voigt': - error_str = "Err" if error else "" - func_header = {'Height': 'LorentzAmp', 'Position': 'LorentzPos', 'Width': 'LorentzFWHM', 'Width_2': 'GaussianFWHM'} - elif function_type == 'Gaussian': - error_str = "_Err" if error else "" - func_header = {'Height': 'Height', 'Width': 'Sigma', 'Position': 'PeakCentre'} - else: - raise ValueError("Unsupported fit function type: %s" % function_type) - - return {k: v+error_str for k,v in func_header.items()} - -#---------------------------------------------------------------------------------------- - - -class EVSCalibrationFit(PythonAlgorithm): - - def summary(self): - return "Fits peaks to a list of spectra using the mass or the d-spacings (for bragg peaks) of the sample." - - def category(self): - return "VesuvioCalibration" - - def PyInit(self): - - self.declareProperty(StringArrayProperty("Samples", Direction.Input), - doc="Sample run numbers to fit peaks to.") - - self.declareProperty(StringArrayProperty("Background", Direction.Input), - doc="Run numbers to use as a background.") - - self.declareProperty('Mode', 'FoilOut', StringListValidator(MODES), - doc="Mode to load files with. This is passed to the LoadVesuvio algorithm. Default is FoilOut.") - - self.declareProperty('Function', 'Gaussian', StringListValidator(['Gaussian', 'Voigt']), - doc="Function to fit each of the spectra with. Default is Gaussian") - - spectrum_validator = IntArrayBoundedValidator() - spectrum_validator.setLower(DETECTOR_RANGE[0]) - spectrum_validator.setUpper(DETECTOR_RANGE[1]) - self.declareProperty(IntArrayProperty('SpectrumRange', DETECTOR_RANGE, spectrum_validator, Direction.Input), - doc='Spectrum range to use. Default is the total range (%d,%d)' % tuple(DETECTOR_RANGE)) - - self.declareProperty('Mass', 207.19, - doc="Mass of the sample in amu to be used when calculating energy. Default is Pb: 207.19") - - greaterThanZero = FloatArrayBoundedValidator() - greaterThanZero.setLower(0) - self.declareProperty(FloatArrayProperty('DSpacings', [], greaterThanZero, Direction.Input), - doc="List of d-spacings used to estimate the positions of bragg peaks in TOF.") - - - self.declareProperty(FloatArrayProperty('Energy', [ENERGY_ESTIMATE], FloatArrayMandatoryValidator(), Direction.Input), - doc='List of estimated expected energies for peaks. Optional: the default is %f' % ENERGY_ESTIMATE) - - self.declareProperty(FileProperty('InstrumentParameterFile', '', action=FileAction.OptionalLoad, extensions=["par"]), - doc='Filename of the instrument parameter file.') - - self.declareProperty('PeakType', '', StringListValidator(PEAK_TYPES), - doc='Choose the peak type that is being fitted.' - 'Note that supplying a set of dspacings overrides the setting here') - - shared_fit_type_validator = StringListValidator(["Individual", "Shared", "Both"]) - self.declareProperty('SharedParameterFitType', "Individual", doc='Calculate shared parameters using an individual and/or' - 'global fit.', validator=shared_fit_type_validator) - - self.declareProperty(ITableWorkspaceProperty("InstrumentParameterWorkspace", "", Direction.Input, PropertyMode.Optional), - doc='Workspace contain instrument parameters.') - - self.declareProperty('CreateOutput', False, - doc='Create fitting workspaces for each of the parameters.') - - self.declareProperty('OutputWorkspace', '', StringMandatoryValidator(), - doc="Name to call the output workspace.") - -#---------------------------------------------------------------------------------------- - - def PyExec(self): - self._setup() - self._preprocess() - - if self._fitting_bragg_peaks: - self._fit_bragg_peaks() - else: - self._fit_peaks() - - #create output of fit if required. - if self._create_output and not self._fitting_bragg_peaks: - self._generate_fit_workspaces() - - #clean up workspaces - if self._param_file != "": - DeleteWorkspace(self._param_table) - -#---------------------------------------------------------------------------------------- - - def _setup(self): - """ - Setup parameters for fitting. - """ - self._setup_spectra_list() - self._setup_run_numbers_and_output_workspace() - self._setup_function_type() - self._setup_parameter_workspace() - self._setup_peaks_and_set_crop_and_fit_ranges() - self._setup_class_variables_from_properties() - - def _setup_class_variables_from_properties(self): - self._mode = self.getProperty("Mode").value - self._energy_estimates = self.getProperty("Energy").value - self._sample_mass = self.getProperty("Mass").value - self._create_output = self.getProperty("CreateOutput").value - self._shared_parameter_fit_type = self.getProperty("SharedParameterFitType").value - - def _setup_spectra_list(self): - self._spec_list = self.getProperty("SpectrumRange").value.tolist() - if len(self._spec_list) > 2: - self._spec_list = [self._spec_list[0], self._spec_list[-1]] - elif len(self._spec_list) == 1: - self._spec_list = [self._spec_list[0]] - elif len(self._spec_list) < 1: - raise ValueError("You must specify a spectrum range.") - - def _setup_run_numbers_and_output_workspace(self): - self._sample_run_numbers = self.getProperty("Samples").value - self._bkg_run_numbers = self.getProperty("Background").value - self._output_workspace_name = self.getPropertyValue("OutputWorkspace") - if len(self._sample_run_numbers) == 0: - raise ValueError("You must supply at least one sample run number.") - if len(self._bkg_run_numbers) > 0: - self._background = '' + self._bkg_run_numbers[0] - - self._sample = self._output_workspace_name + '_Sample_' + '_'.join(self._sample_run_numbers) - - def _setup_function_type(self): - self._peak_function = self.getProperty("Function").value - self._func_param_names = generate_fit_function_header(self._peak_function) - self._func_param_names_error = generate_fit_function_header(self._peak_function, error=True) - - def _setup_parameter_workspace(self): - self._param_workspace = self.getPropertyValue('InstrumentParameterWorkspace') - self._param_file = self.getPropertyValue('InstrumentParameterFile') - if self._param_workspace != "": - self._param_table = self._param_workspace - elif self._param_file != "": - base = os.path.basename(self._param_file) - self._param_table = os.path.splitext(base)[0] - load_instrument_parameters(self._param_file, self._param_table) - - def _setup_peaks_and_set_crop_and_fit_ranges(self): - self._d_spacings = self.getProperty("DSpacings").value - self._d_spacings.sort() - self._peak_type = self.getPropertyValue('PeakType') - - if self._fitting_bragg_peaks: - self._ws_crop_range, self._fit_window_range = BRAGG_PEAK_CROP_RANGE, BRAGG_FIT_WINDOW_RANGE - elif self._fitting_recoil_peaks: - self._ws_crop_range, self._fit_window_range = RECOIL_PEAK_CROP_RANGE, RECOIL_FIT_WINDOW_RANGE - elif self._fitting_resonance_peaks: - self._ws_crop_range, self._fit_window_range = RESONANCE_PEAK_CROP_RANGE, RESONANCE_FIT_WINDOW_RANGE - - @property - def _fitting_bragg_peaks(self): - return len(self._d_spacings) > 0 - - @property - def _fitting_recoil_peaks(self): - return self._peak_type == "Recoil" and not self._fitting_bragg_peaks - - @property - def _fitting_resonance_peaks(self): - return self._peak_type == "Resonance" and not self._fitting_bragg_peaks - -#---------------------------------------------------------------------------------------- - - def _preprocess(self): - """ - Preprocess a workspace. This include optionally dividing by a background - """ - xmin, xmax = self._ws_crop_range - self._load_to_ads_and_crop(self._sample_run_numbers, self._sample, xmin, xmax) - - if self._background_provided: - self._load_to_ads_and_crop(self._bkg_run_numbers, self._background, xmin, xmax) - self._normalise_sample_by_background() - - ReplaceSpecialValues(self._sample, NaNValue=0, NaNError=0, InfinityValue=0, InfinityError=0, OutputWorkspace=self._sample) - - @property - def _background_provided(self): - return len(self._bkg_run_numbers) > 0 - - def _load_to_ads_and_crop(self, run_numbers, output, xmin, xmax): - self._load_files(run_numbers, output) - CropWorkspace(output, XMin=xmin, XMax=xmax, OutputWorkspace=output) - - def _normalise_sample_by_background(self): - RebinToWorkspace(WorkspaceToRebin=self._background, WorkspaceToMatch=self._sample, OutputWorkspace=self._background) - Divide(self._sample, self._background, OutputWorkspace=self._sample) - DeleteWorkspace(self._background) - -#---------------------------------------------------------------------------------------- - - def _fit_bragg_peaks(self): - estimated_peak_positions_all_spec = self._estimate_bragg_peak_positions() - num_estimated_peaks, num_spectra = estimated_peak_positions_all_spec.shape - - self._prog_reporter = Progress(self, 0.0, 1.0, num_spectra) - - output_parameters_tbl_name = self._output_workspace_name + '_Peak_Parameters' - self._create_output_parameters_table_ws(output_parameters_tbl_name, num_estimated_peaks) - - output_workspaces = [] - for index, estimated_peak_positions in enumerate(estimated_peak_positions_all_spec.transpose()): - spec_number = self._spec_list[0]+index - self._prog_reporter.report("Fitting to spectrum %d" % spec_number) - - find_peaks_output_name = self._sample + '_peaks_table_%d' % spec_number - fit_peaks_output_name = self._output_workspace_name + '_Spec_%d' % spec_number - fit_results = self._fit_peaks_to_spectra(index, spec_number, estimated_peak_positions, find_peaks_output_name, - fit_peaks_output_name) - - fit_results_unconstrained = None - if fit_results['status'] != "success": - self._prog_reporter.report("Fitting to spectrum %d without constraining parameters" % spec_number) - fit_results_unconstrained = self._fit_peaks_to_spectra(index, spec_number, estimated_peak_positions, find_peaks_output_name, - fit_peaks_output_name, unconstrained=True, - x_range=(fit_results['xmin'], fit_results['xmax'])) - - selected_params, unconstrained_fit_selected = self._select_best_fit_params(spec_number, fit_results, fit_results_unconstrained) - - self._output_params_to_table(spec_number, num_estimated_peaks, selected_params, output_parameters_tbl_name) - - output_workspaces.append( - self._get_output_and_clean_workspaces(fit_results_unconstrained is not None, - (fit_results_unconstrained is not None and fit_results_unconstrained is not False), - unconstrained_fit_selected, find_peaks_output_name, fit_peaks_output_name)) - - if self._create_output: - GroupWorkspaces(','.join(output_workspaces), OutputWorkspace=self._output_workspace_name + "_Peak_Fits") - - @staticmethod - def _get_unconstrained_ws_name(ws_name): - return ws_name + '_unconstrained' - - def _create_output_parameters_table_ws(self, output_table_name, num_estimated_peaks): - table = CreateEmptyTableWorkspace(OutputWorkspace=output_table_name) - - col_headers = self._generate_column_headers(num_estimated_peaks) - - table.addColumn('int', 'Spectrum') - for name in col_headers: - table.addColumn('double', name) - AnalysisDataService.addOrReplace(output_table_name, table) - - def _generate_column_headers(self, num_estimated_peaks): - param_names = self._get_param_names(num_estimated_peaks) - err_names = [name + '_Err' for name in param_names] - col_headers = [element for tupl in zip(param_names, err_names) for element in tupl] - return col_headers - - def _get_param_names(self, num_estimated_peaks): - param_names = ['f0.A0', 'f0.A1'] - for i in range(num_estimated_peaks): - param_names += ['f' + str(i) + '.' + name for name in self._func_param_names.values()] - return param_names - - def _fit_peaks_to_spectra(self, workspace_index, spec_number, peak_estimates_list, find_peaks_output_name, - fit_peaks_output_name, unconstrained=False, x_range=None): - if unconstrained: - find_peaks_output_name = self._get_unconstrained_ws_name(find_peaks_output_name) - fit_peaks_output_name = self._get_unconstrained_ws_name(fit_peaks_output_name) - logger.notice("Fitting to spectrum %d without constraining parameters" % spec_number) - - find_peaks_input_params = self._get_find_peak_parameters(spec_number, peak_estimates_list, unconstrained) - logger.notice(str(spec_number) + ' ' + str(find_peaks_input_params)) - peaks_found = self._run_find_peaks(workspace_index, find_peaks_output_name, find_peaks_input_params, unconstrained) - - if peaks_found: - return self._filter_and_fit_found_peaks(workspace_index, peak_estimates_list, find_peaks_output_name, - fit_peaks_output_name, x_range, unconstrained) - else: - return False - - def _filter_and_fit_found_peaks(self, workspace_index, peak_estimates_list, find_peaks_output_name, fit_peaks_output_name, - x_range, unconstrained): - if unconstrained: - linear_bg_coeffs = self._calc_linear_bg_coefficients() - self._filter_found_peaks(find_peaks_output_name, peak_estimates_list, linear_bg_coeffs, - peak_height_rel_threshold=PEAK_HEIGHT_RELATIVE_THRESHOLD) - - fit_results = self._fit_found_peaks(find_peaks_output_name, peak_estimates_list if not unconstrained else None, - workspace_index, fit_peaks_output_name, x_range) - fit_results['status'] = "peaks invalid" if not \ - self._check_fitted_peak_validity(fit_peaks_output_name + '_Parameters', peak_estimates_list, - peak_height_rel_threshold=PEAK_HEIGHT_RELATIVE_THRESHOLD) else fit_results[ - 'status'] - return fit_results - - def _run_find_peaks(self, workspace_index, find_peaks_output_name, find_peaks_input_params, unconstrained): - try: - FindPeaks(InputWorkspace=self._sample, WorkspaceIndex=workspace_index, PeaksList=find_peaks_output_name, - **find_peaks_input_params) - if mtd[find_peaks_output_name].rowCount() > 0: - peaks_found = True - else: - raise ValueError - except ValueError: - peaks_found = False - if not unconstrained: #Ignore error if unconstrained, as we will use peaks found during constrained workflow. - raise ValueError("Error finding peaks.") - return peaks_found - - def _select_best_fit_params(self, spec_num, fit_results, fit_results_u=None): - selected_params = fit_results['params'] - unconstrained_fit_selected = False - if fit_results_u: - if fit_results_u['chi2'] < fit_results['chi2'] and fit_results_u['status'] != "peaks invalid": - selected_params = fit_results_u['params'] - unconstrained_fit_selected = True - self._prog_reporter.report("Fit to spectrum %d without constraining parameters successful" % spec_num) - return selected_params, unconstrained_fit_selected - - def _output_params_to_table(self, spec_num, num_estimated_peaks, params, output_table_name): - fit_values = dict(zip(params.column(0), params.column(1))) - fit_errors = dict(zip(params.column(0), params.column(2))) - - row_values = [fit_values['f0.A0'], fit_values['f0.A1']] - row_errors = [fit_errors['f0.A0'], fit_errors['f0.A1']] - param_names = self._get_param_names(num_estimated_peaks) - row_values += [fit_values['f1.' + name] for name in param_names[2:]] - row_errors += [fit_errors['f1.' + name] for name in param_names[2:]] - row = [element for tupl in zip(row_values, row_errors) for element in tupl] - - mtd[output_table_name].addRow([spec_num] + row) - - def _get_output_and_clean_workspaces(self, unconstrained_fit_performed, unconstrained_peaks_found, unconstrained_fit_selected, find_peaks_output_name, - fit_peaks_output_name): - find_peaks_output_name_u = self._get_unconstrained_ws_name(find_peaks_output_name) - fit_peaks_output_name_u = self._get_unconstrained_ws_name(fit_peaks_output_name) - - DeleteWorkspace(fit_peaks_output_name + '_NormalisedCovarianceMatrix') - DeleteWorkspace(fit_peaks_output_name + '_Parameters') - DeleteWorkspace(find_peaks_output_name) - - output_workspace = fit_peaks_output_name + '_Workspace' - if unconstrained_fit_performed: - DeleteWorkspace(find_peaks_output_name_u) - - if unconstrained_peaks_found: - DeleteWorkspace(fit_peaks_output_name_u + '_NormalisedCovarianceMatrix') - DeleteWorkspace(fit_peaks_output_name_u + '_Parameters') - - if unconstrained_fit_selected: - output_workspace = fit_peaks_output_name_u + '_Workspace' - DeleteWorkspace(fit_peaks_output_name + '_Workspace') - else: - DeleteWorkspace(fit_peaks_output_name_u + '_Workspace') - return output_workspace - - -#---------------------------------------------------------------------------------------- - - def _calc_linear_bg_coefficients(self): - temp_bg_fit = Fit(Function="(name=LinearBackground,A0=0,A1=0)", InputWorkspace=self._sample, Output="temp_bg_fit", CreateOutput=True) - linear_bg_coeffs = temp_bg_fit.OutputParameters.cell("Value", 0), temp_bg_fit.OutputParameters.cell("Value", 1) - DeleteWorkspace('temp_bg_fit_Workspace') - DeleteWorkspace('temp_bg_fit_NormalisedCovarianceMatrix') - DeleteWorkspace('temp_bg_fit_Parameters') - return linear_bg_coeffs - -#---------------------------------------------------------------------------------------- - - def _check_fitted_peak_validity(self, table_name, estimated_peaks, peak_height_abs_threshold=0.0, peak_height_rel_threshold=0.0): - check_nans = self._check_nans(table_name) - check_peak_positions = self._check_peak_positions(table_name, estimated_peaks) - check_peak_heights = self._check_peak_heights(table_name, peak_height_abs_threshold, peak_height_rel_threshold) - - if check_nans and check_peak_positions and check_peak_heights: - return True - else: - return False - -#---------------------------------------------------------------------------------------- - - def _check_nans(self, table_name): - table_ws = mtd[table_name] - for i in table_ws.column("Value"): - if np.isnan(i): - print(f"nan found in value common, indicates invalid peak") - return False - return True - -#---------------------------------------------------------------------------------------- - - def _check_peak_positions(self, table_name, estimated_positions): - pos_str = self._func_param_names["Position"] - - i = 0 - invalid_positions = [] - for name, value in zip(mtd[table_name].column("Name"), mtd[table_name].column("Value")): - if pos_str in name: - if not (value >= estimated_positions[i] - BRAGG_PEAK_POSITION_TOLERANCE and value <= estimated_positions[i] + BRAGG_PEAK_POSITION_TOLERANCE): - invalid_positions.append(value) - i += 1 - - if len(invalid_positions) > 0: - print(f"Invalid peak positions found: {invalid_positions}") - return False - else: - return True - -#---------------------------------------------------------------------------------------- - - def _evaluate_peak_height_against_bg(self, height, position, linear_bg_A0, linear_bg_A1, rel_threshold, abs_threshold): - bg = position * linear_bg_A1 + linear_bg_A0 - required_height = bg*rel_threshold + abs_threshold - if height < required_height: - return False - else: - return True - -#---------------------------------------------------------------------------------------- - - def _check_peak_heights(self, table_name, abs_threshold_over_bg, rel_threshold_over_bg): - height_str = self._func_param_names["Height"] - pos_str = self._func_param_names["Position"] - - peak_heights = [] - peak_positions = [] - for name, value in zip(mtd[table_name].column("Name"), mtd[table_name].column("Value")): - if height_str in name: - peak_heights.append(value) - elif pos_str in name: - peak_positions.append(value) - elif name == "f0.A0": - linear_bg_A0 = value - elif name == "f0.A1": - linear_bg_A1 = value - - for height, pos in zip(peak_heights, peak_positions): - if not self._evaluate_peak_height_against_bg(height, pos, linear_bg_A0, linear_bg_A1, abs_threshold_over_bg, rel_threshold_over_bg): - print(f"Peak height threshold not met. Found height: {height}") - return False - return True - -#---------------------------------------------------------------------------------------- - - def _filter_found_peaks(self, find_peaks_output_name, peak_estimates_list, linear_bg_coeffs, peak_height_abs_threshold=0.0, peak_height_rel_threshold=0.0): - unfiltered_fits_ws_name = find_peaks_output_name + '_unfiltered' - CloneWorkspace(InputWorkspace=mtd[find_peaks_output_name], OutputWorkspace=unfiltered_fits_ws_name) - - peak_estimate_deltas = [] - linear_bg_A0, linear_bg_A1 = linear_bg_coeffs - #with estimates for spectrum, loop through all peaks, get and store delta from peak estimates - for peak_estimate_index, peak_estimate in enumerate(peak_estimates_list): - for position_index, (position, height) in enumerate(zip(mtd[unfiltered_fits_ws_name].column(2), - mtd[unfiltered_fits_ws_name].column(1))): - if not position == 0 and self._evaluate_peak_height_against_bg(height, position, linear_bg_A0, linear_bg_A1, peak_height_abs_threshold, peak_height_rel_threshold): - peak_estimate_deltas.append((peak_estimate_index, position_index, abs(position - peak_estimate))) - - #loop through accendings delta, assign peaks until there are none left to be assigned. - peak_estimate_deltas.sort(key=partial(self._get_x_elem, elem=2)) - index_matches = [] - for peak_estimate_index, position_index, delta in peak_estimate_deltas: - #if all estimated peaks matched, break - if len(index_matches)==len(peak_estimates_list): - break - #assign match for smallest delta if that position or estimate has not been already matched - if position_index not in [i[1] for i in index_matches] and \ - peak_estimate_index not in [i[0] for i in index_matches] and \ - all(x>position_index for x in [i[1] for i in index_matches if i[0]>peak_estimate_index]) and \ - all(x 0: - index_matches.sort(key=partial(self._get_x_elem, elem=1)) - mtd[find_peaks_output_name].setRowCount(len(peak_estimates_list)) - for col_index in range(mtd[find_peaks_output_name].columnCount()): - match_n = 0 - for row_index in range(mtd[find_peaks_output_name].rowCount()): - if row_index in [x[0] for x in index_matches]: - position_row_index = index_matches[match_n][1] - mtd[find_peaks_output_name].setCell(row_index,col_index, - mtd[unfiltered_fits_ws_name].cell(position_row_index, col_index)) - match_n+=1 - else: #otherwise just use estimate position - pos_str = self._func_param_names["Position"] - mtd[find_peaks_output_name].setCell(pos_str, row_index, peak_estimates_list[row_index]) - DeleteWorkspace(unfiltered_fits_ws_name) - -#---------------------------------------------------------------------------------------- - def _get_x_elem(self, input_list, elem): - return input_list[elem] - -#---------------------------------------------------------------------------------------- - - def _fit_found_peaks(self, peak_table, peak_estimates_list, workspace_index, fit_output_name, xLimits=None): - #get parameters from the peaks table - peak_params = [] - prefix = ''#'f0.' - position = prefix + self._func_param_names['Position'] - - if peak_estimates_list is not None: #If no peak estimates list, we are doing an unconstrained fit - #Don't yet understand what this is doing here - self._set_table_column(peak_table, position, peak_estimates_list, spec_list=None) - unconstrained=False - else: - unconstrained=True - - for peak_index in range(mtd[peak_table].rowCount()): - peak_params.append(mtd[peak_table].row(peak_index)) - - #build function string - func_string = self._build_multiple_peak_function(peak_params, workspace_index, unconstrained) - - #select min and max x range for fitting - positions = [params[position] for params in peak_params] - - if len(positions) < 1: - raise RuntimeError("No position parameter provided.") - - if xLimits: - xmin, xmax = xLimits - else: - xmin = min(peak_estimates_list) - BRAGG_PEAK_POSITION_TOLERANCE - self._fit_window_range - xmax = max(peak_estimates_list) + BRAGG_PEAK_POSITION_TOLERANCE + self._fit_window_range - - #fit function to workspace - fit_result = Fit(Function=func_string, InputWorkspace=self._sample, IgnoreInvalidData=True, StartX=xmin, EndX=xmax, - WorkspaceIndex=workspace_index, CalcErrors=True, Output=fit_output_name, - Minimizer='Levenberg-Marquardt,AbsError=0,RelError=1e-8') + (xmin,) + (xmax,) - fit_result_key = 'status', 'chi2', 'ncm', 'params', 'fws', 'func', 'cost_func', 'xmin', 'xmax' - fit_results_dict = dict(zip(fit_result_key, fit_result)) - return fit_results_dict - -#---------------------------------------------------------------------------------------- - - def _fit_peaks(self): - """ - Fit peaks to time of flight data. - - This uses the Mantid algorithms FindPeaks and Fit. Estimates for the centre of the peaks - are found using either the energy or d-spacings provided. It creates a group workspace - containing one table workspace per peak with parameters for each detector. - """ - - estimated_peak_positions_all_peaks = self._estimate_peak_positions() - num_estimated_peaks, num_spectra = estimated_peak_positions_all_peaks.shape - - self._prog_reporter = Progress(self, 0.0, 1.0, num_estimated_peaks*num_spectra) - - self._output_parameter_tables = [] - self._peak_fit_workspaces = [] - for peak_index, estimated_peak_positions in enumerate(estimated_peak_positions_all_peaks): - - self._peak_fit_workspaces_by_spec = [] - output_parameter_table_name = self._output_workspace_name + '_Peak_%d_Parameters' % peak_index - output_parameter_table_headers = self._create_parameter_table_and_output_headers(output_parameter_table_name) - for spec_index, peak_position in enumerate(estimated_peak_positions): - fit_workspace_name = self._fit_peak(peak_index, spec_index, peak_position, output_parameter_table_name, - output_parameter_table_headers) - self._peak_fit_workspaces_by_spec.append(fit_workspace_name) - - self._output_parameter_tables.append(output_parameter_table_name) - self._peak_fit_workspaces.append(self._peak_fit_workspaces_by_spec) - - GroupWorkspaces(self._output_parameter_tables, OutputWorkspace=self._output_workspace_name + '_Peak_Parameters') - - if self._shared_parameter_fit_type != "Individual": - self._shared_parameter_fit(output_parameter_table_name, output_parameter_table_headers) - - def _shared_parameter_fit(self, output_parameter_table_name, output_parameter_table_headers): - init_Gaussian_FWHM = read_fitting_result_table_column(output_parameter_table_name, 'f1.GaussianFWHM', self._spec_list) - init_Gaussian_FWHM = np.nanmean(init_Gaussian_FWHM[init_Gaussian_FWHM != 0]) - init_Lorentz_FWHM = read_fitting_result_table_column(output_parameter_table_name, 'f1.LorentzFWHM', self._spec_list) - init_Lorentz_FWHM = np.nanmean(init_Lorentz_FWHM[init_Lorentz_FWHM != 0]) - init_Lorentz_Amp = read_fitting_result_table_column(output_parameter_table_name, 'f1.LorentzAmp', self._spec_list) - init_Lorentz_Amp = np.nanmean(init_Lorentz_Amp[init_Lorentz_Amp != 0]) - init_Lorentz_Pos = read_fitting_result_table_column(output_parameter_table_name, 'f1.LorentzPos', self._spec_list) - init_Lorentz_Pos = np.nanmean(init_Lorentz_Pos[init_Lorentz_Pos != 0]) - - initial_params = {'A0': 0.0, 'A1': 0.0, 'LorentzAmp': init_Lorentz_Amp, 'LorentzPos': init_Lorentz_Pos, 'LorentzFWHM': init_Lorentz_FWHM, 'GaussianFWHM': init_Gaussian_FWHM} - - peak_centres = read_fitting_result_table_column(output_parameter_table_name, 'f1.LorentzPos', self._spec_list) - peak_centres_errors = read_fitting_result_table_column(output_parameter_table_name, 'f1.LorentzPos_Err', self._spec_list) - invalid_spectra = identify_invalid_spectra(output_parameter_table_name, peak_centres, peak_centres_errors, self._spec_list) - self._fit_shared_parameter(invalid_spectra, initial_params, output_parameter_table_headers) - - def _fit_peak(self, peak_index, spec_index, peak_position, output_parameter_table_name, output_parameter_table_headers): - spec_number = self._spec_list[0] + spec_index - self._prog_reporter.report("Fitting peak %d to spectrum %d" % (peak_index, spec_number)) - - peak_params = self._find_peaks_and_output_params(peak_index, spec_number, spec_index, peak_position) - fit_func_string = self._build_function_string(peak_params) - xmin, xmax = self._find_fit_x_window(peak_params) - fit_output_name = '__' + self._output_workspace_name + '_Peak_%d_Spec_%d' % (peak_index, spec_index) - status, chi2, ncm, fit_params, fws, func, cost_func = Fit(Function=fit_func_string, InputWorkspace=self._sample, - IgnoreInvalidData=True, - StartX=xmin, EndX=xmax, WorkspaceIndex=spec_index, - CalcErrors=True, Output=fit_output_name, - Minimizer='Levenberg-Marquardt,RelError=1e-8') - - self._output_fit_params_to_table_ws(spec_number, fit_params, output_parameter_table_name, - output_parameter_table_headers) - fit_workspace_name = fws.name() - self._del_fit_workspaces(ncm, fit_params, fws) - - return fit_workspace_name - - def _find_peaks_and_output_params(self, peak_index, spec_number, spec_index, peak_position): - peak_table_name = '__' + self._sample + '_peaks_table_%d_%d' % (peak_index, spec_index) - find_peak_params = self._get_find_peak_parameters(spec_number, [peak_position]) - FindPeaks(InputWorkspace=self._sample, WorkspaceIndex=spec_index, PeaksList=peak_table_name, **find_peak_params) - if mtd[peak_table_name].rowCount() == 0: - logger.error('FindPeaks could not find any peaks matching the parameters:\n' + str(find_peak_params)) - sys.exit() - - peak_params = mtd[peak_table_name].row(0) - DeleteWorkspace(peak_table_name) - return peak_params - - def _find_fit_x_window(self, peak_params): - xmin, xmax = None, None - - position = '' + self._func_param_names['Position'] - if peak_params[position] > 0: - xmin = peak_params[position] - self._fit_window_range - xmax = peak_params[position] + self._fit_window_range - else: - logger.warning('Could not specify fit window. Using full spectrum x range.') - return xmin, xmax - - def _output_fit_params_to_table_ws(self, spec_num, params, output_table_name, output_table_headers): - fit_values = dict(zip(params.column(0), params.column(1))) - fit_errors = dict(zip(params.column(0), params.column(2))) - row_values = [fit_values[name] for name in output_table_headers] - row_errors = [fit_errors[name] for name in output_table_headers] - row = [element for tupl in zip(row_values, row_errors) for element in tupl] - - mtd[output_table_name].addRow([spec_num] + row) - - def _del_fit_workspaces(self, ncm, fit_params, fws): - DeleteWorkspace(ncm) - DeleteWorkspace(fit_params) - if not self._create_output: - DeleteWorkspace(fws) - -#---------------------------------------------------------------------------------------- - - def _fit_shared_parameter(self, invalid_spectra, initial_params, param_names): - """ - Fit peaks to all detectors, with one set of fit parameters for all detectors. - """ - - shared_peak_table = self._output_workspace_name + '_shared_peak_parameters' - CreateEmptyTableWorkspace(OutputWorkspace=shared_peak_table) - - err_names = [name + '_Err' for name in param_names] - col_names = [element for tupl in zip(param_names, err_names) for element in tupl] - - mtd[shared_peak_table].addColumn('int', 'Spectrum') - for name in col_names: - mtd[shared_peak_table].addColumn('double', name) - - fit_func = self._build_function_string(initial_params) - start_str = 'composite=MultiDomainFunction, NumDeriv=1;' - sample_ws = mtd[self._sample] - MaskDetectors(Workspace=sample_ws, WorkspaceIndexList=invalid_spectra) - validSpecs = ExtractUnmaskedSpectra(InputWorkspace=sample_ws, OutputWorkspace='valid_spectra') - n_valid_specs = validSpecs.getNumberHistograms() - - fit_func = ('(composite=CompositeFunction, NumDeriv=false, $domains=i;' + fit_func[:-1] + ');') * n_valid_specs - composite_func = start_str + fit_func[:-1] - - ties = ','.join(f'f{i}.f1.{p}=f0.f1.{p}' for p in self._func_param_names.values() for i in range(1,n_valid_specs)) - func_string = composite_func + f';ties=({ties})' - - fit_output_name = '__' + self._output_workspace_name + '_Peak_0' - - xmin, xmax = None, None - - # create new workspace for each spectra - x = validSpecs.readX(0) - y = validSpecs.readY(0) - e = validSpecs.readE(0) - out_ws = self._sample + '_Spec_0' - CreateWorkspace(DataX=x, DataY=y, DataE=e, NSpec=1, OutputWorkspace=out_ws) - - other_inputs = [CreateWorkspace(DataX=validSpecs.readX(j), DataY=validSpecs.readY(j), DataE=validSpecs.readE(j), - OutputWorkspace=f'{self._sample}_Spec_{j}') - for j in range(1,n_valid_specs)] - - added_args = {f'InputWorkspace_{i + 1}': v for i,v in enumerate(other_inputs)} - - print('starting global fit') - - status, chi2, ncm, params, fws, func, cost_func = Fit(Function=func_string, InputWorkspace=out_ws, IgnoreInvalidData=True, - StartX=xmin, EndX=xmax, - CalcErrors=True, Output=fit_output_name, - Minimizer='SteepestDescent,RelError=1e-8', **added_args) - [DeleteWorkspace(f"{self._sample}_Spec_{i}") for i in range(0,n_valid_specs)] - - fit_values = dict(zip(params.column(0), params.column(1))) - fit_errors = dict(zip(params.column(0), params.column(2))) - - row_values = [fit_values['f0.' + name] for name in param_names] - row_errors = [fit_errors['f0.' + name] for name in param_names] - row = [element for tupl in zip(row_values, row_errors) for element in tupl] - - mtd[shared_peak_table].addRow([0] + row) - - DeleteWorkspace(ncm) - DeleteWorkspace(params) - if not self._create_output: - DeleteWorkspace(fws) - - DeleteWorkspace(validSpecs) - - mtd[self._output_workspace_name + '_Peak_Parameters'].add(shared_peak_table) - -#---------------------------------------------------------------------------------------- - - def _get_find_peak_parameters(self, spec_number, peak_centre, unconstrained=False): - """ - Get find peak parameters - - @param spec_num - the current spectrum number being fitted - @return dictionary of parameters for find peaks - """ - - find_peak_params = {} - find_peak_params['PeakFunction'] = self._peak_function - find_peak_params['RawPeakParameters'] = True - find_peak_params['BackgroundType'] = 'Linear' - - if not unconstrained: - find_peak_params['PeakPositions'] = peak_centre - - if self._fitting_bragg_peaks and not unconstrained: - find_peak_params['PeakPositionTolerance'] = BRAGG_PEAK_POSITION_TOLERANCE - - if spec_number >= FRONTSCATTERING_RANGE[0]: - find_peak_params['FWHM'] = 70 - else: - find_peak_params['FWHM'] = 5 - - elif not self._fitting_bragg_peaks: - if self._fitting_resonance_peaks: - # 25 seems to be able to fit the final peak in the first backscattering spectrum - if spec_number >= FRONTSCATTERING_RANGE[0]: - half_peak_window = 20 - else: - half_peak_window = 25 - else: - # #ust be recoil - half_peak_window = 60 - - fit_windows = [[peak-half_peak_window, peak+half_peak_window] for peak in peak_centre] - # flatten the list: http://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python - find_peak_params['FitWindows'] = [peak for pair in fit_windows for peak in pair] - - return find_peak_params - -#---------------------------------------------------------------------------------------- - - def _create_parameter_table_and_output_headers(self, peaks_table): - """ - Create a table workspace with headers corresponding to the parameters - output from fitting. - - @param peak_table - name to call the parameter table. - @return param_names - name of the columns - """ - CreateEmptyTableWorkspace(OutputWorkspace=peaks_table) - - param_names = ['f0.A0', 'f0.A1'] - param_names += ['f1.' + name for name in self._func_param_names.values()] - - err_names = [name + '_Err' for name in param_names] - col_names = [element for tupl in zip(param_names, err_names) for element in tupl] - - mtd[peaks_table].addColumn('int', 'Spectrum') - for name in col_names: - mtd[peaks_table].addColumn('double', name) - - return param_names - -#---------------------------------------------------------------------------------------- - - def _estimate_peak_positions(self): - """ - Estimate the position of a peak based on a given energy and previous calibrated parameters. - - @return list of estimates in tof for the peak centres - """ - L0 = self._read_param_column('L0', self._spec_list) - L1 = self._read_param_column('L1', self._spec_list) - t0 = self._read_param_column('t0', self._spec_list) - t0 /= 1e+6 - thetas = self._read_param_column('theta', self._spec_list) - r_theta = calculate_r_theta(self._sample_mass, thetas) - - self._energy_estimates = self._energy_estimates.reshape(1, self._energy_estimates.size).T - self._energy_estimates *= MEV_CONVERSION - - v1 = np.sqrt(2 * self._energy_estimates / scipy.constants.m_n) - if self._fitting_recoil_peaks: - # for recoil peaks: - tof = ((L0 * r_theta + L1) / v1) + t0 - elif self._fitting_resonance_peaks: - # for resonances: - tof = (L0 / v1) + t0 - else: - raise RuntimeError("Cannot estimate peak positions for unknown peak type") - - tof *= 1e+6 - return tof - -#---------------------------------------------------------------------------------------- - - def _estimate_bragg_peak_positions(self): - """ - Estimate the position of bragg peaks in TOF using estimates of the parameters - and d-spacings of the sample. - - The number of peaks will depend on the number of d-spacings provided. - - @return estimates of peak positions in TOF for all spectra - """ - - L0 = self._read_param_column('L0', self._spec_list) - L1 = self._read_param_column('L1', self._spec_list) - t0 = self._read_param_column('t0', self._spec_list) - thetas = self._read_param_column('theta', self._spec_list) - - t0 /= 1e+6 - - self._d_spacings *= 1e-10 - self._d_spacings = self._d_spacings.reshape(1, self._d_spacings.size).T - - lambdas = 2 * self._d_spacings * np.sin(np.radians(thetas) / 2) - - L1_nan_to_num = np.nan_to_num(L1) - tof = (lambdas * scipy.constants.m_n * (L0 + L1_nan_to_num)) / scipy.constants.h + t0 - tof *= 1e+6 - - return tof - -#---------------------------------------------------------------------------------------- - - def _load_files(self, ws_numbers, output_name): - """ - Load a list of run numbers and sum each of the runs. - - @param ws_numbers - list of run numbers to load. - @param output_name - name to call the final workspace. - """ - - run_numbers = [run for run in self._parse_run_numbers(ws_numbers)] - - self._load_file(run_numbers[0], output_name) - temp_ws_name = '__EVS_calib_temp_ws' - - for run_number in run_numbers[1:]: - self._load_file(run_number, temp_ws_name) - Plus(output_name, temp_ws_name, OutputWorkspace=output_name) - DeleteWorkspace(temp_ws_name) - -#---------------------------------------------------------------------------------------- - - def _parse_run_numbers(self, run_numbers): - """ - Converts a list of mixed single runs and run ranges - into a single flat list of run numbers. - - @param run_numbers - list of mixed single runs and run ranges - @return iterator to each run number in the flat list of runs - """ - for run in run_numbers: - if '-' in run: - sample_range = run.split('-') - sample_range = map(int, sample_range) - - for i in range(*sample_range): - yield str(i) - - else: - yield run - -#---------------------------------------------------------------------------------------- - - def _load_file(self, ws_name, output_name): - """ - Load a file into a workspace. - - This will attempt to use LoadVesuvio, but will fall back on LoadRaw if LoadVesuvio fails. - @param ws_name - name of the run to load. - @param output_name - name to call the loaded workspace - """ - - try: - LoadVesuvio(Filename=ws_name, Mode=self._mode, OutputWorkspace=output_name, - SpectrumList="%d-%d" % (self._spec_list[0], self._spec_list[-1]), - EnableLogging=False) - except RuntimeError: - LoadRaw('EVS' + ws_name + '.raw', OutputWorkspace=output_name, - SpectrumMin=self._spec_list[0], SpectrumMax=self._spec_list[-1], - EnableLogging=False) - ConvertToDistribution(output_name, EnableLogging=False) - -#---------------------------------------------------------------------------------------- - - def _build_linear_background_function(self, peak, tie_gradient=False): - """ - Builds a string describing a peak function that can be passed to Fit. - This will be either a Gaussian or Lorentzian shape. - - @param peaks - dictionary containing the parameters for the function - @return the function string - """ - - bkg_intercept, bkg_graident = peak['A0'], peak['A1'] - fit_func = 'name=LinearBackground, A0=%f, A1=%f' % (bkg_intercept, bkg_graident) - fit_func += ';' - - return fit_func - -#---------------------------------------------------------------------------------------- - - def _build_peak_function(self, peak): - """ - Builds a string describing a linear background function that can be passed to Fit. - - @param peaks - dictionary containing the parameters for the function - @return the function string - """ - - fit_func = 'name=%s, ' % self._peak_function - - prefix = ''#f0.' - func_list = [name + '=' + str(peak[prefix + name]) for name in self._func_param_names.values()] - fit_func += ', '.join(func_list) + ';' - - return fit_func - -#---------------------------------------------------------------------------------------- - - def _build_multiple_peak_function(self, peaks, workspace_index, unconstrained): - """ - Builds a string describing a composite function that can be passed to Fit. - This will be a linear background with multiple peaks of either a Gaussian or Voigt shape. - - @param peaks - list of dictionaries containing the parameters for the function - @return the function string - """ - - if(len(peaks) == 0): - return '' - - if not unconstrained: - fit_func = self._build_linear_background_function(peaks[0], False) - else: - pos_str = self._func_param_names["Position"] - if len(peaks) > 1: - fit_window = [peaks[0][pos_str] - 1000, peaks[len(peaks)-1][pos_str] + 1000] - else: - fit_window = [peaks[0][pos_str] - 1000, peaks[0][pos_str] + 1000] - FindPeakBackground(InputWorkspace=self._sample, WorkspaceIndex=workspace_index, FitWindow=fit_window, BackgroundType="Linear", OutputWorkspace="temp_fit_bg") - fit_func = f'name=LinearBackground, A0={mtd["temp_fit_bg"].cell("bkg0",0)}, A1={mtd["temp_fit_bg"].cell("bkg1",0)};' - DeleteWorkspace("temp_fit_bg") - - fit_func += '(' - for i, peak in enumerate(peaks): - fit_func += self._build_peak_function(peak) - - fit_func = fit_func[:-1] - fit_func += ');' - - return fit_func - -#---------------------------------------------------------------------------------------- - - def _build_function_string(self, peak): - """ - Builds a string describing a composite function that can be passed to Fit. - This will be a linear background with either a Gaussian or Voigt function. - - @param peak - dictionary containing the parameters for the function - @return the function string - """ - - if(len(peak) == 0): - return '' - - fit_func = self._build_linear_background_function(peak) - fit_func += self._build_peak_function(peak) - - return fit_func - -#---------------------------------------------------------------------------------------- - - def _read_param_column(self, column_name, spec_list=DETECTOR_RANGE): - """ - Read a column from a table workspace and return the data as an array. - - @param column_name - name of the column to select - @param spec_list - range of spectra to use - @return numpy array of values in the spec_list range - """ - - return read_table_column(self._param_table, column_name, spec_list) - -#---------------------------------------------------------------------------------------- - - def _set_table_column(self, table_name, column_name, data, spec_list=None): - """ - Add a set of data to a table workspace - - @param table_name - name of the table workspace to modify - @param column_name - name of the column to add data to - @param data - data array to add to the table - @param spec_list - range of rows to add the data too - """ - table_ws = mtd[table_name] - - if column_name not in table_ws.getColumnNames(): - table_ws.addColumn('double', column_name) - - if spec_list == None: - offset = 0 - else: - if len(data) < (spec_list[1]+1 - spec_list[0]): - raise ValueError("Not enough data from spectrum range.") - - offset = spec_list[0] - DETECTOR_RANGE[0] - - if isinstance(data, np.ndarray): - data = data.tolist() - - for i, value in enumerate(data): - table_ws.setCell(column_name, offset+i, value) - -#---------------------------------------------------------------------------------------- - - def _generate_fit_workspaces(self): - """ - Output the fit workspace for each spectrum fitted. - """ - fit_workspaces = map(list, zip(*self._peak_fit_workspaces)) - group_ws_names = [] - - for i, peak_fits in enumerate(fit_workspaces): - ws_name = self._output_workspace_name + '_%d_Workspace' % i - ExtractSingleSpectrum(InputWorkspace=self._sample, WorkspaceIndex=i, OutputWorkspace=ws_name) - - #transfer fits to individual spectrum - peak_collection = WorkspaceFactory.create(mtd[ws_name], NVectors=len(peak_fits)) - data_x = mtd[ws_name].readX(0) - - ax = TextAxis.create(len(peak_fits)+2) - ax.setLabel(0, "Data") - - for j, peak in enumerate(peak_fits): - peak_x = mtd[peak].readX(0) - peak_y = mtd[peak].readY(1) - peak_e = mtd[peak].readE(1) - - index = int(np.where(data_x == peak_x[0])[0]) - data_y = peak_collection.dataY(j) - data_y[index:index + peak_y.size] = peak_y - - data_e = peak_collection.dataE(j) - data_e[index:index + peak_e.size] = peak_e - - peak_collection.setX(j, data_x) - peak_collection.setY(j, data_y) - peak_collection.setE(j, data_e) - - ax.setLabel(j+1, "Peak_%d" % (j+1)) - - DeleteWorkspace(peak) - - peak_ws = "__tmp_peak_workspace" - mtd.addOrReplace(peak_ws, peak_collection) - AppendSpectra(ws_name, peak_ws, ValidateInputs=False, OutputWorkspace=ws_name) - - #create sum of peak fits - temp_sum_workspace = '__temp_sum_ws' - SumSpectra(InputWorkspace=peak_ws, OutputWorkspace=peak_ws) - AppendSpectra(ws_name, peak_ws, ValidateInputs=False, OutputWorkspace=ws_name) - ax.setLabel(mtd[ws_name].getNumberHistograms()-1, "Total") - DeleteWorkspace(peak_ws) - - mtd[ws_name].replaceAxis(1, ax) - group_ws_names.append(ws_name) - - GroupWorkspaces(group_ws_names, OutputWorkspace=self._output_workspace_name + "_Peak_Fits") - - -#---------------------------------------------------------------------------------------- - -AlgorithmFactory.subscribe(EVSCalibrationFit) - -######################################################################################### - -class EVSCalibrationAnalysis(PythonAlgorithm): - - def summary(self): - return "Calculates the calibration parameters for the EVS intrument." - - def category(self): - return "VesuvioCalibration" - - def PyInit(self): - - self.declareProperty(StringArrayProperty("Samples", Direction.Input), - doc="Sample run numbers to fit peaks to.") - - self.declareProperty(StringArrayProperty("Background", Direction.Input), - doc="Run numbers to use as a background.") - - self.declareProperty(FileProperty('InstrumentParameterFile', '', action=FileAction.Load, extensions=["par"]), - doc="Filename of the instrument parameter file.") - - self.declareProperty('Mass', sys.float_info.max, - doc="Mass of the sample in amu to be used when calculating energy. Default is Pb: 207.19") - - greaterThanZero = FloatArrayBoundedValidator() - greaterThanZero.setLower(0) - self.declareProperty(FloatArrayProperty('DSpacings', [], greaterThanZero, Direction.Input), - doc="List of d-spacings used to estimate the positions of peaks in TOF.") - - self.declareProperty(FloatArrayProperty('E1FixedValueAndError', [], greaterThanZero, Direction.Input), - doc="Value at which to fix E1 and E1 error (form: E1 value, E1 Error). If no input is provided, values will be calculated.") - - self.declareProperty('Iterations', 2, validator=IntBoundedValidator(lower=1), - doc="Number of iterations to perform. Default is 2.") - - shared_fit_type_validator = StringListValidator(["Individual", "Shared", "Both"]) - self.declareProperty('SharedParameterFitType', "Individual", doc='Calculate shared parameters using an individual and/or' - 'global fit.', validator=shared_fit_type_validator) - - self.declareProperty('CreateOutput', False, - doc="Whether to create output from fitting.") - - self.declareProperty('CalculateL0', False, - doc="Whether to calculate L0 or just use the values from the parameter file.") - - self.declareProperty('CreateIPFile', False, - doc="Whether to save the output as an IP file. \ - This file will use the same name as the OutputWorkspace and will be saved to the default save directory.") - - self.declareProperty('OutputWorkspace', '', StringMandatoryValidator(), - doc="Name to call the output workspace.") - -#---------------------------------------------------------------------------------------- - - def PyExec(self): - self._setup() - self._current_workspace = self._output_workspace_name + '_Iteration_0' - self._create_calib_parameter_table(self._current_workspace) - - #PEAK FUNCTIONS - self._theta_peak_function = 'Gaussian' - self._theta_func_param_names = generate_fit_function_header(self._theta_peak_function) - self._theta_func_param_names_error = generate_fit_function_header(self._theta_peak_function, error=True) - - if self._calc_L0: - #calibrate L0 from the fronstscattering detectors, use the value of the L0 calibrated from frontscattering detectors for all detectors - L0_fit = self._current_workspace + '_L0' - self._run_calibration_fit(Samples=U_FRONTSCATTERING_SAMPLE, Background=U_FRONTSCATTERING_BACKGROUND, SpectrumRange=FRONTSCATTERING_RANGE, - InstrumentParameterWorkspace=self._param_table, Mass=U_MASS, Energy=U_PEAK_ENERGIES, OutputWorkspace=L0_fit, - CreateOutput=self._create_output, PeakType='Resonance') - self._L0_peak_fits = L0_fit + '_Peak_Parameters' - self._calculate_incident_flight_path(self._L0_peak_fits, FRONTSCATTERING_RANGE) - - #calibrate t0 from the front scattering detectors - t0_fit_front = self._current_workspace + '_t0_front' - self._run_calibration_fit(Samples=U_FRONTSCATTERING_SAMPLE, Background=U_FRONTSCATTERING_BACKGROUND, SpectrumRange=FRONTSCATTERING_RANGE, - InstrumentParameterWorkspace=self._param_table, Mass=U_MASS, Energy=U_PEAK_ENERGIES, OutputWorkspace=t0_fit_front, - CreateOutput=self._create_output, PeakType='Resonance') - t0_peak_fits_front = t0_fit_front + '_Peak_Parameters' - self._calculate_time_delay(t0_peak_fits_front, FRONTSCATTERING_RANGE) - - #calibrate t0 from the backscattering detectors - t0_fit_back = self._current_workspace + '_t0_back' - self._run_calibration_fit(Samples=U_BACKSCATTERING_SAMPLE, Background=U_BACKSCATTERING_BACKGROUND, SpectrumRange=BACKSCATTERING_RANGE, - InstrumentParameterWorkspace=self._param_table, Mass=U_MASS, Energy=U_PEAK_ENERGIES, OutputWorkspace=t0_fit_back, - CreateOutput=self._create_output, PeakType='Resonance') - t0_peak_fits_back = t0_fit_back + '_Peak_Parameters' - self._calculate_time_delay(t0_peak_fits_back, BACKSCATTERING_RANGE) - else: - #Just copy values over from parameter file - t0 = read_table_column(self._param_table, 't0', DETECTOR_RANGE) - L0 = read_table_column(self._param_table, 'L0', DETECTOR_RANGE) - self._set_table_column(self._current_workspace, 't0', t0) - self._set_table_column(self._current_workspace, 'L0', L0) - - #repeatedly fit L1, E1 and Theta parameters - table_group = [] - for i in range(self._iterations): - - #calibrate theta for all detectors - theta_fit = self._current_workspace + '_theta' - self._run_calibration_fit(Samples=self._samples, Background=self._background, Function=self._theta_peak_function, Mode='FoilOut', SpectrumRange=DETECTOR_RANGE, - InstrumentParameterWorkspace=self._param_table, DSpacings=self._d_spacings, OutputWorkspace=theta_fit, CreateOutput=self._create_output, - PeakType='Bragg') - self._theta_peak_fits = theta_fit + '_Peak_Parameters' - self._calculate_scattering_angle(self._theta_peak_fits, DETECTOR_RANGE) - - #calibrate E1 for backscattering detectors and use the backscattering averaged value for all detectors - E1_fit_back = self._current_workspace + '_E1_back' - self._run_calibration_fit(Samples=self._samples, Function='Voigt', Mode='SingleDifference', SpectrumRange=BACKSCATTERING_RANGE, - InstrumentParameterWorkspace=self._param_table, Mass=self._sample_mass, OutputWorkspace=E1_fit_back, CreateOutput=self._create_output, - PeakType='Recoil', SharedParameterFitType=self._shared_parameter_fit_type) - - E1_peak_fits_back = mtd[self._current_workspace + '_E1_back_Peak_Parameters'].getNames() - self._calculate_final_energy(E1_peak_fits_back, BACKSCATTERING_RANGE, self._shared_parameter_fit_type != "Individual") - - # calibrate L1 for backscattering detectors based on the averaged E1 value and calibrated theta values - self._calculate_final_flight_path(E1_peak_fits_back[0], BACKSCATTERING_RANGE) - - # calibrate L1 for frontscattering detectors based on the averaged E1 value and calibrated theta values - E1_fit_front = self._current_workspace + '_E1_front' - self._run_calibration_fit(Samples=self._samples, Function='Voigt', Mode='SingleDifference', SpectrumRange=FRONTSCATTERING_RANGE, - InstrumentParameterWorkspace=self._param_table, Mass=self._sample_mass, OutputWorkspace=E1_fit_front, CreateOutput=self._create_output, - PeakType='Recoil', SharedParameterFitType=self._shared_parameter_fit_type) - - E1_peak_fits_front = mtd[self._current_workspace + '_E1_front_Peak_Parameters'].getNames() - self._calculate_final_flight_path(E1_peak_fits_front[0], FRONTSCATTERING_RANGE) - - - #make the fitted parameters for this iteration the input to the next iteration. - table_group.append(self._current_workspace) - self._param_table = self._current_workspace - - if i < self._iterations-1: - self._current_workspace = self._output_workspace_name + '_Iteration_%d' % (i+1) - self._create_calib_parameter_table(self._current_workspace) - - #copy over L0 and t0 parameters to new table - t0 = read_table_column(self._param_table, 't0', DETECTOR_RANGE) - t0_error = read_table_column(self._param_table, 't0_Err', DETECTOR_RANGE) - L0 = read_table_column(self._param_table, 'L0', DETECTOR_RANGE) - L0_error = read_table_column(self._param_table, 'L0_Err', DETECTOR_RANGE) - - self._set_table_column(self._current_workspace, 't0', t0) - self._set_table_column(self._current_workspace, 'L0', L0) - self._set_table_column(self._current_workspace, 't0_Err', t0_error) - self._set_table_column(self._current_workspace, 'L0_Err', L0_error) - - GroupWorkspaces(','.join(table_group), OutputWorkspace=self._output_workspace_name) - - if self._make_IP_file: - ws_name = mtd[self._output_workspace_name].getNames()[-1] - self._save_instrument_parameter_file(ws_name, DETECTOR_RANGE) - -#---------------------------------------------------------------------------------------- - - def _run_calibration_fit(self, *args, **kwargs): - """ - Runs EVSCalibrationFit using the AlgorithmManager. - - This allows the calibration script to be run directly from the - script window after Mantid has started. - - @param args - positional arguments to the algorithm - @param kwargs - key word arguments to the algorithm - """ - from mantid.simpleapi import set_properties - alg = AlgorithmManager.create('EVSCalibrationFit') - alg.initialize() - alg.setRethrows(True) - set_properties(alg, *args, **kwargs) - alg.execute() - -#---------------------------------------------------------------------------------------- - - def _calculate_time_delay(self, table_name, spec_list): - """ - Calculate time delay from frontscattering detectors. - - @param table_name - name of table containing fitted parameters for the peak centres - @param spec_list - spectrum range to calculate t0 for. - """ - t0_param_table = self._current_workspace + '_t0_Parameters' - self._fit_linear(table_name, t0_param_table) - - t0 = np.asarray(mtd[t0_param_table].column('A0')) - t0_error = np.asarray(mtd[t0_param_table].column('A0_Err')) - - self._set_table_column(self._current_workspace, 't0', t0, spec_list) - self._set_table_column(self._current_workspace, 't0_Err', t0_error, spec_list) - - DeleteWorkspace(t0_param_table) - -#---------------------------------------------------------------------------------------- - - def _calculate_incident_flight_path(self, table_name, spec_list): - """ - Calculate incident flight path from frontscattering detectors. - This takes the average value of a fit over all detectors for the value of L0 - and t0. - - @param table_name - name of table containing fitted parameters for the peak centres - @param spec_list - spectrum range to calculate t0 for. - """ - L0_param_table = self._current_workspace + '_L0_Parameters' - self._fit_linear(table_name, L0_param_table) - - L0 = np.asarray(mtd[L0_param_table].column('A1')) - - spec_range = DETECTOR_RANGE[1]+1 - DETECTOR_RANGE[0] - mean_L0 = np.empty(spec_range) - L0_error = np.empty(spec_range) - - mean_L0.fill(np.mean(L0)) - L0_error.fill(scipy.stats.sem(L0)) - - self._set_table_column(self._current_workspace, 'L0', mean_L0) - self._set_table_column(self._current_workspace, 'L0_Err', L0_error) - - DeleteWorkspace(L0_param_table) - -#---------------------------------------------------------------------------------------- - - def _calculate_final_flight_path(self, peak_table, spec_list): - """ - Calculate the final flight path using the values for energy. - This also uses the old value for L1 loaded from the parameter file. - - @param spec_list - spectrum range to calculate t0 for. - """ - - E1 = read_table_column(self._current_workspace, 'E1', spec_list) - t0 = read_table_column(self._current_workspace, 't0', spec_list) - t0_error = read_table_column(self._current_workspace, 't0_Err', spec_list) - L0 = read_table_column(self._current_workspace, 'L0', spec_list) - theta = read_table_column(self._current_workspace, 'theta', spec_list) - r_theta = calculate_r_theta(self._sample_mass, theta) - - peak_centres = read_fitting_result_table_column(peak_table, 'f1.LorentzPos', spec_list) - peak_centres_errors = read_fitting_result_table_column(peak_table, 'f1.LorentzPos_Err', spec_list) - invalid_spectra = identify_invalid_spectra(peak_table, peak_centres, peak_centres_errors, spec_list) - peak_centres[invalid_spectra] = np.nan - - print(f'Invalid Spectra Index Found and Marked NAN: {invalid_spectra.flatten()} from Spectra Index List:' - f'{[x-3 for x in spec_list]}') - - delta_t = (peak_centres - t0) / 1e+6 - delta_t_error = t0_error / 1e+6 - - E1 *= MEV_CONVERSION - v1 = np.sqrt(2*E1/scipy.constants.m_n) - L1 = v1 * delta_t - L0 * r_theta - L1_error = v1 * delta_t_error - - self._set_table_column(self._current_workspace, 'L1', L1, spec_list) - self._set_table_column(self._current_workspace, 'L1_Err', L1_error, spec_list) - - # ---------------------------------------------------------------------------------------- - - def _calculate_scattering_angle(self, table_name, spec_list): - """ - Calculate the total scattering angle using the previous calculated parameters. - - @param table_name - name of table containing fitted parameters for the peak centres - @param spec_list - spectrum range to calculate t0 for. - """ - - t0 = read_table_column(self._current_workspace, 't0', spec_list) - L0 = read_table_column(self._current_workspace, 'L0', spec_list) - L1 = read_table_column(self._param_table, 'L1', spec_list) - L1_nan_to_num = np.nan_to_num(L1) - spec = read_table_column(self._current_workspace, 'Spectrum', spec_list) - - t0 /= 1e+6 - - d_spacings = np.asarray(self._d_spacings) - d_spacings *= 1e-10 - d_spacings = d_spacings.reshape(1, d_spacings.size).T - - peak_centres = [] - pos_str = self._theta_func_param_names["Position"] - err_str = self._theta_func_param_names_error["Position"] - col_names = [name for name in mtd[table_name].getColumnNames() if pos_str in name and err_str not in name] - for name in col_names: - t = np.asarray(mtd[table_name].column(name)) - peak_centres.append(t) - - peak_centres = np.asarray(peak_centres) - masked_peak_centres = np.ma.masked_array(peak_centres, np.logical_or(peak_centres<=2000,peak_centres>=20000)) - masked_peak_centres /= 1e+6 - - sin_theta = ((masked_peak_centres - t0) * scipy.constants.h) / (scipy.constants.m_n * d_spacings * 2 * (L0+L1_nan_to_num)) - theta = np.arcsin(sin_theta) * 2 - theta = np.degrees(theta) - - masked_theta = np.nanmean(theta, axis=0) - theta_error = np.nanstd(theta, axis=0) - - self._set_table_column(self._current_workspace, 'theta', masked_theta, spec_list) - self._set_table_column(self._current_workspace, 'theta_Err', theta_error, spec_list) - -#---------------------------------------------------------------------------------------- - - - - def _calculate_final_energy(self, peak_table, spec_list, calculate_global): - """ - Calculate the final energy using the fitted peak centres of a run. - - @param table_name - name of table containing fitted parameters for the peak centres - @param spec_list - spectrum range to calculate t0 for. - """ - - spec_range = DETECTOR_RANGE[1] + 1 - DETECTOR_RANGE[0] - mean_E1 = np.empty(spec_range) - E1_error = np.empty(spec_range) - global_E1 = np.empty(spec_range) - global_E1_error = np.empty(spec_range) - - if not self._E1_value_and_error: - t0 = read_table_column(self._current_workspace, 't0', spec_list) - L0 = read_table_column(self._current_workspace, 'L0', spec_list) - - L1 = read_table_column(self._param_table, 'L1', spec_list) - theta = read_table_column(self._current_workspace, 'theta', spec_list) - r_theta = calculate_r_theta(self._sample_mass, theta) - - peak_centres = read_fitting_result_table_column(peak_table[0], 'f1.LorentzPos', spec_list) - peak_centres_errors = read_fitting_result_table_column(peak_table[0], 'f1.LorentzPos_Err', spec_list) - - invalid_spectra = identify_invalid_spectra(peak_table[0], peak_centres, peak_centres_errors, spec_list) - peak_centres[invalid_spectra] = np.nan - - delta_t = (peak_centres - t0) / 1e+6 - v1 = (L0 * r_theta + L1) / delta_t - - E1 = 0.5*scipy.constants.m_n*v1**2 - E1 /= MEV_CONVERSION - - mean_E1_val = np.nanmean(E1) - E1_error_val = np.nanstd(E1) - - else: - mean_E1_val = self._E1_value_and_error[0] - E1_error_val = self._E1_value_and_error[1] - - mean_E1.fill(mean_E1_val) - E1_error.fill(E1_error_val) - - self._set_table_column(self._current_workspace, 'E1', mean_E1) - self._set_table_column(self._current_workspace, 'E1_Err', E1_error) - - if calculate_global: # This fn will need updating for the only global option - peak_centre = read_fitting_result_table_column(peak_table[1], 'f1.LorentzPos', [0]) - peak_centre = [peak_centre] * len(peak_centres) - - delta_t = (peak_centre - t0) / 1e+6 - v1 = (L0 * r_theta + L1) / delta_t - E1 = 0.5*scipy.constants.m_n*v1**2 - E1 /= MEV_CONVERSION - - global_E1_val = np.nanmean(E1) - global_E1_error_val = np.nanstd(E1) - - global_E1.fill(global_E1_val) - global_E1_error.fill(global_E1_error_val) - - self._set_table_column(self._current_workspace, 'global_E1', global_E1) - self._set_table_column(self._current_workspace, 'global_E1_Err', global_E1_error) - -#---------------------------------------------------------------------------------------- - - def _setup(self): - """ - Setup algorithm. - """ - self._samples = self.getProperty("Samples").value - self._background = self.getProperty("Background").value - self._param_file = self.getProperty("InstrumentParameterFile").value - self._sample_mass = self.getProperty("Mass").value - self._d_spacings = self.getProperty("DSpacings").value.tolist() - self._E1_value_and_error = self.getProperty("E1FixedValueAndError").value.tolist() - self._shared_parameter_fit_type = self.getProperty("SharedParameterFitType").value - self._calc_L0 = self.getProperty("CalculateL0").value - self._make_IP_file = self.getProperty("CreateIPFile").value - self._output_workspace_name = self.getPropertyValue("OutputWorkspace") - self._iterations = self.getProperty("Iterations").value - self._create_output = self.getProperty("CreateOutput").value - - if len(self._samples) == 0: - raise ValueError("You must supply at least one sample run number.") - - #if len(self._background) == 0: - # raise ValueError("You must supply at least one background run number.") - - self._d_spacings.sort() - - self._param_table = '__EVS_calib_analysis_parameters' - load_instrument_parameters(self._param_file, self._param_table) - -#---------------------------------------------------------------------------------------- - - def _create_calib_parameter_table(self, ws_name): - #create table for calculated parameters - CreateEmptyTableWorkspace(OutputWorkspace=ws_name) - table_ws = mtd[ws_name] - table_ws.addColumn('int', 'Spectrum') - - for value in range(DETECTOR_RANGE[0], DETECTOR_RANGE[1]+1): - table_ws.addRow([value]) - - column_names = ['t0','t0_Err','L0','L0_Err','L1','L1_Err','E1','E1_Err','theta','theta_Err'] - for name in column_names: - table_ws.addColumn('double', name) - -#---------------------------------------------------------------------------------------- - - def _fit_linear(self, table_workspace_group, output_table): - """ - Create a workspace wth the fitted peak_centres on the y and corresponding neutron velocity - on the x. The intercept is the value of t0 and the gradient is the value of L0/L-Total. - - @param table_workspace_group - workspace group containing the fitted parameters of the peaks. - @param output_table - name to call the fit workspace. - """ - #extract fit data to workspace - peak_workspaces = [] - for i, param_ws in enumerate(mtd[table_workspace_group].getNames()): - temp_peak_data = '__temp_peak_ws_%d' % i - ConvertTableToMatrixWorkspace(InputWorkspace=param_ws, OutputWorkspace=temp_peak_data, - ColumnX='Spectrum', ColumnY='f1.PeakCentre') - peak_workspaces.append(temp_peak_data) - - #create workspace of peaks - peak_workspace = table_workspace_group + '_Workspace' - RenameWorkspace(peak_workspaces[0], OutputWorkspace=peak_workspace) - for temp_ws in peak_workspaces[1:]: - ConjoinWorkspaces(peak_workspace, temp_ws, CheckOverlapping=False) - Transpose(peak_workspace, OutputWorkspace=peak_workspace) - - num_spectra = mtd[peak_workspace].getNumberHistograms() - plot_peak_indicies = ';'.join([peak_workspace + ',i' + str(i) for i in range(num_spectra)]) - - for i in range(num_spectra): - mtd[peak_workspace].setX(i, np.asarray(U_NEUTRON_VELOCITY)) - - ReplaceSpecialValues(peak_workspace, NaNValue=0, NaNError=0, InfinityValue=0, InfinityError=0, - OutputWorkspace=peak_workspace) - - #perform linear fit on peak centres - func_string = 'name=LinearBackground, A0=0, A1=0;' - PlotPeakByLogValue(Input=plot_peak_indicies, Function=func_string, - FitType='Individual', CreateOutput=False, OutputWorkspace=output_table) - DeleteWorkspace(peak_workspace) - -#---------------------------------------------------------------------------------------- - - - def _set_table_column(self, table_name, column_name, data, spec_list=None): - """ - Add a set of data to a table workspace - - @param table_name - name of the table workspace to modify - @param column_name - name of the column to add data to - @param data - data array to add to the table - @param spec_list - range of rows to add the data too - """ - table_ws = mtd[table_name] - - if column_name not in table_ws.getColumnNames(): - table_ws.addColumn('double', column_name) - - if spec_list == None: - offset = 0 - else: - if len(data) < (spec_list[1]+1 - spec_list[0]): - raise ValueError("Not enough data from spectrum range.") - - offset = spec_list[0] - DETECTOR_RANGE[0] - - if isinstance(data, np.ndarray): - data = data.tolist() - - for i, value in enumerate(data): - table_ws.setCell(column_name, offset+i, value) - -#---------------------------------------------------------------------------------------- - - def _save_instrument_parameter_file(self, ws_name, spec_list): - """ - Save the calibrated parameters to a tab delimited instrument parameter file. - - @param ws_name - name of the workspace to save the IP file from. - @param spec_list - spectrum range to save to file. - """ - file_header = '\t'.join(['plik', 'det', 'theta', 't0', 'L0', 'L1']) - fmt = "%d %d %.4f %.4f %.3f %.4f" - - det = read_table_column(ws_name, 'Spectrum', spec_list) - t0 = read_table_column(ws_name, 't0', spec_list) - L0 = read_table_column(ws_name, 'L0', spec_list) - L1 = read_table_column(ws_name, 'L1', spec_list) - theta = read_table_column(ws_name, 'theta', spec_list) - - #pad the start of the file with dummy data for the monitors - file_data = np.asarray([[1,1,0,0,0,0], [2,2,0,0,0,0]]) - file_data = np.append(file_data, np.column_stack((det, det, theta, t0, L0, L1)), axis=0) - - workdir = config['defaultsave.directory'] - file_path = os.path.join(workdir, self._output_workspace_name+'.par') - - with open(file_path, 'wb') as f_handle: - np.savetxt(f_handle, file_data, header = file_header, fmt=fmt) - -#---------------------------------------------------------------------------------------- - -AlgorithmFactory.subscribe(EVSCalibrationAnalysis) diff --git a/unpackaged/vesuvio_calibration/calibration_scripts/__init__.py b/unpackaged/vesuvio_calibration/calibration_scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py new file mode 100644 index 00000000..acf9ab24 --- /dev/null +++ b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py @@ -0,0 +1,510 @@ +from mantid.kernel import StringArrayProperty, Direction, StringListValidator, IntArrayBoundedValidator, IntArrayProperty,\ + FloatArrayBoundedValidator, FloatArrayMandatoryValidator, StringMandatoryValidator, IntBoundedValidator,\ + FloatArrayProperty +from mantid.api import FileProperty, FileAction, ITableWorkspaceProperty, PropertyMode, Progress, TextAxis, PythonAlgorithm,\ + AlgorithmManager +from mantid.simpleapi import CreateEmptyTableWorkspace, DeleteWorkspace, CropWorkspace, RebinToWorkspace, Divide,\ + ReplaceSpecialValues, FindPeaks, GroupWorkspaces, mtd, Plus, LoadVesuvio, LoadRaw, ConvertToDistribution,\ + FindPeakBackground, ExtractSingleSpectrum, SumSpectra, AppendSpectra, ConvertTableToMatrixWorkspace,\ + ConjoinWorkspaces, Transpose, PlotPeakByLogValue, CloneWorkspace, MaskDetectors,\ + ExtractUnmaskedSpectra, CreateWorkspace, RenameWorkspace +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals, EVSMiscFunctions + +import os +import sys +import scipy.constants +import scipy.stats +import numpy as np + + +class EVSCalibrationAnalysis(PythonAlgorithm): + + def summary(self): + return "Calculates the calibration parameters for the EVS intrument." + + def category(self): + return "VesuvioCalibration" + + def PyInit(self): + + self.declareProperty(StringArrayProperty("Samples", Direction.Input), + doc="Sample run numbers to fit peaks to.") + + self.declareProperty(StringArrayProperty("Background", Direction.Input), + doc="Run numbers to use as a background.") + + self.declareProperty(FileProperty('InstrumentParameterFile', '', action=FileAction.Load, extensions=["par"]), + doc="Filename of the instrument parameter file.") + + self.declareProperty('Mass', sys.float_info.max, + doc="Mass of the sample in amu to be used when calculating energy. Default is Pb: 207.19") + + greaterThanZero = FloatArrayBoundedValidator() + greaterThanZero.setLower(0) + self.declareProperty(FloatArrayProperty('DSpacings', [], greaterThanZero, Direction.Input), + doc="List of d-spacings used to estimate the positions of peaks in TOF.") + + self.declareProperty(FloatArrayProperty('E1FixedValueAndError', [], greaterThanZero, Direction.Input), + doc="Value at which to fix E1 and E1 error (form: E1 value, E1 Error). If no input is provided," + "values will be calculated.") + + self.declareProperty('Iterations', 2, validator=IntBoundedValidator(lower=1), + doc="Number of iterations to perform. Default is 2.") + + shared_fit_type_validator = StringListValidator(["Individual", "Shared", "Both"]) + self.declareProperty('SharedParameterFitType', "Individual", doc='Calculate shared parameters using an individual and/or' + 'global fit.', validator=shared_fit_type_validator) + + self.declareProperty('CreateOutput', False, + doc="Whether to create output from fitting.") + + self.declareProperty('CalculateL0', False, + doc="Whether to calculate L0 or just use the values from the parameter file.") + + self.declareProperty('CreateIPFile', False, + doc="Whether to save the output as an IP file. \ + This file will use the same name as the OutputWorkspace and will be saved to the default save directory.") + + self.declareProperty('OutputWorkspace', '', StringMandatoryValidator(), + doc="Name to call the output workspace.") + + def PyExec(self): + self._setup() + self._current_workspace = self._output_workspace_name + '_Iteration_0' + self._create_calib_parameter_table(self._current_workspace) + + # PEAK FUNCTIONS + self._theta_peak_function = 'Gaussian' + self._theta_func_param_names = EVSMiscFunctions.generate_fit_function_header(self._theta_peak_function) + self._theta_func_param_names_error = EVSMiscFunctions.generate_fit_function_header(self._theta_peak_function, error=True) + + if self._calc_L0: + # calibrate L0 from the fronstscattering detectors, use the value of the L0 calibrated from frontscattering detectors for all detectors + L0_fit = self._current_workspace + '_L0' + self._run_calibration_fit(Samples=EVSGlobals.U_FRONTSCATTERING_SAMPLE, Background=EVSGlobals.U_FRONTSCATTERING_BACKGROUND, + SpectrumRange=EVSGlobals.FRONTSCATTERING_RANGE, InstrumentParameterWorkspace=self._param_table, + Mass=EVSGlobals.U_MASS, Energy=EVSGlobals.U_PEAK_ENERGIES, OutputWorkspace=L0_fit, + CreateOutput=self._create_output, PeakType='Resonance') + self._L0_peak_fits = L0_fit + '_Peak_Parameters' + self._calculate_incident_flight_path(self._L0_peak_fits, EVSGlobals.FRONTSCATTERING_RANGE) + + # calibrate t0 from the front scattering detectors + t0_fit_front = self._current_workspace + '_t0_front' + self._run_calibration_fit(Samples=EVSGlobals.U_FRONTSCATTERING_SAMPLE, Background=EVSGlobals.U_FRONTSCATTERING_BACKGROUND, + SpectrumRange=EVSGlobals.FRONTSCATTERING_RANGE, InstrumentParameterWorkspace=self._param_table, + Mass=EVSGlobals.U_MASS, Energy=EVSGlobals.U_PEAK_ENERGIES, OutputWorkspace=t0_fit_front, + CreateOutput=self._create_output, PeakType='Resonance') + t0_peak_fits_front = t0_fit_front + '_Peak_Parameters' + self._calculate_time_delay(t0_peak_fits_front, EVSGlobals.FRONTSCATTERING_RANGE) + + # calibrate t0 from the backscattering detectors + t0_fit_back = self._current_workspace + '_t0_back' + self._run_calibration_fit(Samples=EVSGlobals.U_BACKSCATTERING_SAMPLE, Background=EVSGlobals.U_BACKSCATTERING_BACKGROUND, + SpectrumRange=EVSGlobals.BACKSCATTERING_RANGE, InstrumentParameterWorkspace=self._param_table, + Mass=EVSGlobals.U_MASS, Energy=EVSGlobals.U_PEAK_ENERGIES, OutputWorkspace=t0_fit_back, + CreateOutput=self._create_output, PeakType='Resonance') + t0_peak_fits_back = t0_fit_back + '_Peak_Parameters' + self._calculate_time_delay(t0_peak_fits_back, EVSGlobals.BACKSCATTERING_RANGE) + else: + # Just copy values over from parameter file + t0 = EVSMiscFunctions.read_table_column(self._param_table, 't0', EVSGlobals.DETECTOR_RANGE) + L0 = EVSMiscFunctions.read_table_column(self._param_table, 'L0', EVSGlobals.DETECTOR_RANGE) + self._set_table_column(self._current_workspace, 't0', t0) + self._set_table_column(self._current_workspace, 'L0', L0) + + # repeatedly fit L1, E1 and Theta parameters + table_group = [] + for i in range(self._iterations): + + # calibrate theta for all detectors + theta_fit = self._current_workspace + '_theta' + self._run_calibration_fit(Samples=self._samples, Background=self._background, Function=self._theta_peak_function, + Mode='FoilOut', SpectrumRange=EVSGlobals.DETECTOR_RANGE, + InstrumentParameterWorkspace=self._param_table, DSpacings=self._d_spacings, OutputWorkspace=theta_fit, + CreateOutput=self._create_output, PeakType='Bragg') + self._theta_peak_fits = theta_fit + '_Peak_Parameters' + self._calculate_scattering_angle(self._theta_peak_fits, EVSGlobals.DETECTOR_RANGE) + + # calibrate E1 for backscattering detectors and use the backscattering averaged value for all detectors + E1_fit_back = self._current_workspace + '_E1_back' + self._run_calibration_fit(Samples=self._samples, Function='Voigt', Mode='SingleDifference', + SpectrumRange=EVSGlobals.BACKSCATTERING_RANGE, InstrumentParameterWorkspace=self._param_table, + Mass=self._sample_mass, OutputWorkspace=E1_fit_back, CreateOutput=self._create_output, + PeakType='Recoil', SharedParameterFitType=self._shared_parameter_fit_type) + + E1_peak_fits_back = mtd[self._current_workspace + '_E1_back_Peak_Parameters'].getNames() + self._calculate_final_energy(E1_peak_fits_back, EVSGlobals.BACKSCATTERING_RANGE, self._shared_parameter_fit_type != "Individual") + + # calibrate L1 for backscattering detectors based on the averaged E1 value and calibrated theta values + self._calculate_final_flight_path(E1_peak_fits_back[0], EVSGlobals.BACKSCATTERING_RANGE) + + # calibrate L1 for frontscattering detectors based on the averaged E1 value and calibrated theta values + E1_fit_front = self._current_workspace + '_E1_front' + self._run_calibration_fit(Samples=self._samples, Function='Voigt', Mode='SingleDifference', + SpectrumRange=EVSGlobals.FRONTSCATTERING_RANGE, InstrumentParameterWorkspace=self._param_table, + Mass=self._sample_mass, OutputWorkspace=E1_fit_front, CreateOutput=self._create_output, + PeakType='Recoil', SharedParameterFitType=self._shared_parameter_fit_type) + + E1_peak_fits_front = mtd[self._current_workspace + '_E1_front_Peak_Parameters'].getNames() + self._calculate_final_flight_path(E1_peak_fits_front[0], EVSGlobals.FRONTSCATTERING_RANGE) + + # make the fitted parameters for this iteration the input to the next iteration. + table_group.append(self._current_workspace) + self._param_table = self._current_workspace + + if i < self._iterations -1: + self._current_workspace = self._output_workspace_name + '_Iteration_%d' % ( i +1) + self._create_calib_parameter_table(self._current_workspace) + + # copy over L0 and t0 parameters to new table + t0 = EVSMiscFunctions.read_table_column(self._param_table, 't0', EVSGlobals.DETECTOR_RANGE) + t0_error = EVSMiscFunctions.read_table_column(self._param_table, 't0_Err', EVSGlobals.DETECTOR_RANGE) + L0 = EVSMiscFunctions.read_table_column(self._param_table, 'L0', EVSGlobals.DETECTOR_RANGE) + L0_error = EVSMiscFunctions.read_table_column(self._param_table, 'L0_Err', EVSGlobals.DETECTOR_RANGE) + + self._set_table_column(self._current_workspace, 't0', t0) + self._set_table_column(self._current_workspace, 'L0', L0) + self._set_table_column(self._current_workspace, 't0_Err', t0_error) + self._set_table_column(self._current_workspace, 'L0_Err', L0_error) + + GroupWorkspaces(','.join(table_group), OutputWorkspace=self._output_workspace_name) + + if self._make_IP_file: + ws_name = mtd[self._output_workspace_name].getNames()[-1] + self._save_instrument_parameter_file(ws_name, EVSGlobals.DETECTOR_RANGE) + + def _run_calibration_fit(self, *args, **kwargs): + """ + Runs EVSCalibrationFit using the AlgorithmManager. + + This allows the calibration script to be run directly from the + script window after Mantid has started. + + @param args - positional arguments to the algorithm + @param kwargs - key word arguments to the algorithm + """ + from mantid.simpleapi import set_properties + alg = AlgorithmManager.create('EVSCalibrationFit') + alg.initialize() + alg.setRethrows(True) + set_properties(alg, *args, **kwargs) + alg.execute() + + def _calculate_time_delay(self, table_name, spec_list): + """ + Calculate time delay from frontscattering detectors. + + @param table_name - name of table containing fitted parameters for the peak centres + @param spec_list - spectrum range to calculate t0 for. + """ + t0_param_table = self._current_workspace + '_t0_Parameters' + self._fit_linear(table_name, t0_param_table) + + t0 = np.asarray(mtd[t0_param_table].column('A0')) + t0_error = np.asarray(mtd[t0_param_table].column('A0_Err')) + + self._set_table_column(self._current_workspace, 't0', t0, spec_list) + self._set_table_column(self._current_workspace, 't0_Err', t0_error, spec_list) + + DeleteWorkspace(t0_param_table) + + def _calculate_incident_flight_path(self, table_name, spec_list): + """ + Calculate incident flight path from frontscattering detectors. + This takes the average value of a fit over all detectors for the value of L0 + and t0. + + @param table_name - name of table containing fitted parameters for the peak centres + @param spec_list - spectrum range to calculate t0 for. + """ + L0_param_table = self._current_workspace + '_L0_Parameters' + self._fit_linear(table_name, L0_param_table) + + L0 = np.asarray(mtd[L0_param_table].column('A1')) + + spec_range = EVSGlobals.DETECTOR_RANGE[1 ] +1 - EVSGlobals.DETECTOR_RANGE[0] + mean_L0 = np.empty(spec_range) + L0_error = np.empty(spec_range) + + mean_L0.fill(np.mean(L0)) + L0_error.fill(scipy.stats.sem(L0)) + + self._set_table_column(self._current_workspace, 'L0', mean_L0) + self._set_table_column(self._current_workspace, 'L0_Err', L0_error) + + DeleteWorkspace(L0_param_table) + + def _calculate_final_flight_path(self, peak_table, spec_list): + """ + Calculate the final flight path using the values for energy. + This also uses the old value for L1 loaded from the parameter file. + + @param spec_list - spectrum range to calculate t0 for. + """ + + E1 = EVSMiscFunctions.read_table_column(self._current_workspace, 'E1', spec_list) + t0 = EVSMiscFunctions.read_table_column(self._current_workspace, 't0', spec_list) + t0_error = EVSMiscFunctions.read_table_column(self._current_workspace, 't0_Err', spec_list) + L0 = EVSMiscFunctions.read_table_column(self._current_workspace, 'L0', spec_list) + theta = EVSMiscFunctions.read_table_column(self._current_workspace, 'theta', spec_list) + r_theta = EVSMiscFunctions.calculate_r_theta(self._sample_mass, theta) + + peak_centres = EVSMiscFunctions.read_fitting_result_table_column(peak_table, 'f1.LorentzPos', spec_list) + peak_centres_errors = EVSMiscFunctions.read_fitting_result_table_column(peak_table, 'f1.LorentzPos_Err', spec_list) + invalid_spectra = EVSMiscFunctions.identify_invalid_spectra(peak_table, peak_centres, peak_centres_errors, spec_list) + peak_centres[invalid_spectra] = np.nan + + print(f'Invalid Spectra Index Found and Marked NAN: {invalid_spectra.flatten()} from Spectra Index List:' + f'{[ x -3 for x in spec_list]}') + + delta_t = (peak_centres - t0) / 1e+6 + delta_t_error = t0_error / 1e+6 + + E1 *= EVSGlobals.MEV_CONVERSION + v1 = np.sqrt( 2 * E1 /scipy.constants.m_n) + L1 = v1 * delta_t - L0 * r_theta + L1_error = v1 * delta_t_error + + self._set_table_column(self._current_workspace, 'L1', L1, spec_list) + self._set_table_column(self._current_workspace, 'L1_Err', L1_error, spec_list) + + def _calculate_scattering_angle(self, table_name, spec_list): + """ + Calculate the total scattering angle using the previous calculated parameters. + + @param table_name - name of table containing fitted parameters for the peak centres + @param spec_list - spectrum range to calculate t0 for. + """ + + t0 = EVSMiscFunctions.read_table_column(self._current_workspace, 't0', spec_list) + L0 = EVSMiscFunctions.read_table_column(self._current_workspace, 'L0', spec_list) + L1 = EVSMiscFunctions.read_table_column(self._param_table, 'L1', spec_list) + L1_nan_to_num = np.nan_to_num(L1) + spec = EVSMiscFunctions.read_table_column(self._current_workspace, 'Spectrum', spec_list) + + t0 /= 1e+6 + + d_spacings = np.asarray(self._d_spacings) + d_spacings *= 1e-10 + d_spacings = d_spacings.reshape(1, d_spacings.size).T + + peak_centres = [] + pos_str = self._theta_func_param_names["Position"] + err_str = self._theta_func_param_names_error["Position"] + col_names = [name for name in mtd[table_name].getColumnNames() if pos_str in name and err_str not in name] + for name in col_names: + t = np.asarray(mtd[table_name].column(name)) + peak_centres.append(t) + + peak_centres = np.asarray(peak_centres) + masked_peak_centres = np.ma.masked_array(peak_centres, np.logical_or(peak_centres <= 2000, peak_centres >= 20000)) + masked_peak_centres /= 1e+6 + + sin_theta = ((masked_peak_centres - t0) * scipy.constants.h) / \ + (scipy.constants.m_n * d_spacings * 2 * (L0 + L1_nan_to_num)) + theta = np.arcsin(sin_theta) * 2 + theta = np.degrees(theta) + + masked_theta = np.nanmean(theta, axis=0) + theta_error = np.nanstd(theta, axis=0) + + self._set_table_column(self._current_workspace, 'theta', masked_theta, spec_list) + self._set_table_column(self._current_workspace, 'theta_Err', theta_error, spec_list) + + def _calculate_final_energy(self, peak_table, spec_list, calculate_global): + """ + Calculate the final energy using the fitted peak centres of a run. + + @param table_name - name of table containing fitted parameters for the peak centres + @param spec_list - spectrum range to calculate t0 for. + """ + + spec_range = EVSGlobals.DETECTOR_RANGE[1] + 1 - EVSGlobals.DETECTOR_RANGE[0] + mean_E1 = np.empty(spec_range) + E1_error = np.empty(spec_range) + global_E1 = np.empty(spec_range) + global_E1_error = np.empty(spec_range) + + if not self._E1_value_and_error: + t0 = EVSMiscFunctions.read_table_column(self._current_workspace, 't0', spec_list) + L0 = EVSMiscFunctions.read_table_column(self._current_workspace, 'L0', spec_list) + + L1 = EVSMiscFunctions.read_table_column(self._param_table, 'L1', spec_list) + theta = EVSMiscFunctions.read_table_column(self._current_workspace, 'theta', spec_list) + r_theta = EVSMiscFunctions.calculate_r_theta(self._sample_mass, theta) + + peak_centres = EVSMiscFunctions.read_fitting_result_table_column(peak_table[0], 'f1.LorentzPos', spec_list) + peak_centres_errors = EVSMiscFunctions.read_fitting_result_table_column(peak_table[0], 'f1.LorentzPos_Err', spec_list) + + invalid_spectra = EVSMiscFunctions.identify_invalid_spectra(peak_table[0], peak_centres, peak_centres_errors, spec_list) + peak_centres[invalid_spectra] = np.nan + + delta_t = (peak_centres - t0) / 1e+6 + v1 = (L0 * r_theta + L1) / delta_t + + E1 = 0.5 * scipy.constants.m_n * v1 ** 2 + E1 /= EVSGlobals.MEV_CONVERSION + + mean_E1_val = np.nanmean(E1) + E1_error_val = np.nanstd(E1) + + else: + mean_E1_val = self._E1_value_and_error[0] + E1_error_val = self._E1_value_and_error[1] + + mean_E1.fill(mean_E1_val) + E1_error.fill(E1_error_val) + + self._set_table_column(self._current_workspace, 'E1', mean_E1) + self._set_table_column(self._current_workspace, 'E1_Err', E1_error) + + if calculate_global: # This fn will need updating for the only global option + peak_centre = EVSMiscFunctions.read_fitting_result_table_column(peak_table[1], 'f1.LorentzPos', [0]) + peak_centre = [peak_centre] * len(peak_centres) + + delta_t = (peak_centre - t0) / 1e+6 + v1 = (L0 * r_theta + L1) / delta_t + E1 = 0.5 * scipy.constants.m_n * v1 ** 2 + E1 /= EVSGlobals.MEV_CONVERSION + + global_E1_val = np.nanmean(E1) + global_E1_error_val = np.nanstd(E1) + + global_E1.fill(global_E1_val) + global_E1_error.fill(global_E1_error_val) + + self._set_table_column(self._current_workspace, 'global_E1', global_E1) + self._set_table_column(self._current_workspace, 'global_E1_Err', global_E1_error) + + def _setup(self): + """ + Setup algorithm. + """ + self._samples = self.getProperty("Samples").value + self._background = self.getProperty("Background").value + self._param_file = self.getProperty("InstrumentParameterFile").value + self._sample_mass = self.getProperty("Mass").value + self._d_spacings = self.getProperty("DSpacings").value.tolist() + self._E1_value_and_error = self.getProperty("E1FixedValueAndError").value.tolist() + self._shared_parameter_fit_type = self.getProperty("SharedParameterFitType").value + self._calc_L0 = self.getProperty("CalculateL0").value + self._make_IP_file = self.getProperty("CreateIPFile").value + self._output_workspace_name = self.getPropertyValue("OutputWorkspace") + self._iterations = self.getProperty("Iterations").value + self._create_output = self.getProperty("CreateOutput").value + + if len(self._samples) == 0: + raise ValueError("You must supply at least one sample run number.") + + # if len(self._background) == 0: + # raise ValueError("You must supply at least one background run number.") + + self._d_spacings.sort() + + self._param_table = '__EVS_calib_analysis_parameters' + EVSMiscFunctions.load_instrument_parameters(self._param_file, self._param_table) + + def _create_calib_parameter_table(self, ws_name): + # create table for calculated parameters + CreateEmptyTableWorkspace(OutputWorkspace=ws_name) + table_ws = mtd[ws_name] + table_ws.addColumn('int', 'Spectrum') + + for value in range(EVSGlobals.DETECTOR_RANGE[0], EVSGlobals.DETECTOR_RANGE[1] + 1): + table_ws.addRow([value]) + + column_names = ['t0', 't0_Err', 'L0', 'L0_Err', 'L1', 'L1_Err', 'E1', 'E1_Err', 'theta', 'theta_Err'] + for name in column_names: + table_ws.addColumn('double', name) + + def _fit_linear(self, table_workspace_group, output_table): + """ + Create a workspace wth the fitted peak_centres on the y and corresponding neutron velocity + on the x. The intercept is the value of t0 and the gradient is the value of L0/L-Total. + + @param table_workspace_group - workspace group containing the fitted parameters of the peaks. + @param output_table - name to call the fit workspace. + """ + # extract fit data to workspace + peak_workspaces = [] + for i, param_ws in enumerate(mtd[table_workspace_group].getNames()): + temp_peak_data = '__temp_peak_ws_%d' % i + ConvertTableToMatrixWorkspace(InputWorkspace=param_ws, OutputWorkspace=temp_peak_data, + ColumnX='Spectrum', ColumnY='f1.PeakCentre') + peak_workspaces.append(temp_peak_data) + + # create workspace of peaks + peak_workspace = table_workspace_group + '_Workspace' + RenameWorkspace(peak_workspaces[0], OutputWorkspace=peak_workspace) + for temp_ws in peak_workspaces[1:]: + ConjoinWorkspaces(peak_workspace, temp_ws, CheckOverlapping=False) + Transpose(peak_workspace, OutputWorkspace=peak_workspace) + + num_spectra = mtd[peak_workspace].getNumberHistograms() + plot_peak_indicies = ';'.join([peak_workspace + ',i' + str(i) for i in range(num_spectra)]) + + for i in range(num_spectra): + mtd[peak_workspace].setX(i, np.asarray(EVSGlobals.U_NEUTRON_VELOCITY)) + + ReplaceSpecialValues(peak_workspace, NaNValue=0, NaNError=0, InfinityValue=0, InfinityError=0, + OutputWorkspace=peak_workspace) + + # perform linear fit on peak centres + func_string = 'name=LinearBackground, A0=0, A1=0;' + PlotPeakByLogValue(Input=plot_peak_indicies, Function=func_string, + FitType='Individual', CreateOutput=False, OutputWorkspace=output_table) + DeleteWorkspace(peak_workspace) + + def _set_table_column(self, table_name, column_name, data, spec_list=None): + """ + Add a set of data to a table workspace + + @param table_name - name of the table workspace to modify + @param column_name - name of the column to add data to + @param data - data array to add to the table + @param spec_list - range of rows to add the data too + """ + table_ws = mtd[table_name] + + if column_name not in table_ws.getColumnNames(): + table_ws.addColumn('double', column_name) + + if spec_list == None: + offset = 0 + else: + if len(data) < (spec_list[1] + 1 - spec_list[0]): + raise ValueError("Not enough data from spectrum range.") + + offset = spec_list[0] - EVSGlobals.DETECTOR_RANGE[0] + + if isinstance(data, np.ndarray): + data = data.tolist() + + for i, value in enumerate(data): + table_ws.setCell(column_name, offset + i, value) + + def _save_instrument_parameter_file(self, ws_name, spec_list): + """ + Save the calibrated parameters to a tab delimited instrument parameter file. + + @param ws_name - name of the workspace to save the IP file from. + @param spec_list - spectrum range to save to file. + """ + file_header = '\t'.join(['plik', 'det', 'theta', 't0', 'L0', 'L1']) + fmt = "%d %d %.4f %.4f %.3f %.4f" + + det = EVSMiscFunctions.read_table_column(ws_name, 'Spectrum', spec_list) + t0 = EVSMiscFunctions.read_table_column(ws_name, 't0', spec_list) + L0 = EVSMiscFunctions.read_table_column(ws_name, 'L0', spec_list) + L1 = EVSMiscFunctions.read_table_column(ws_name, 'L1', spec_list) + theta = EVSMiscFunctions.read_table_column(ws_name, 'theta', spec_list) + + # pad the start of the file with dummy data for the monitors + file_data = np.asarray([[1, 1, 0, 0, 0, 0], [2, 2, 0, 0, 0, 0]]) + file_data = np.append(file_data, np.column_stack((det, det, theta, t0, L0, L1)), axis=0) + + workdir = config['defaultsave.directory'] + file_path = os.path.join(workdir, self._output_workspace_name + '.par') + + with open(file_path, 'wb') as f_handle: + np.savetxt(f_handle, file_data, header=file_header, fmt=fmt) diff --git a/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py new file mode 100644 index 00000000..439fb671 --- /dev/null +++ b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py @@ -0,0 +1,1080 @@ +from mantid.kernel import StringArrayProperty, Direction, StringListValidator, IntArrayBoundedValidator, IntArrayProperty,\ + FloatArrayBoundedValidator, FloatArrayMandatoryValidator, StringMandatoryValidator, IntBoundedValidator,\ + FloatArrayProperty, logger +from mantid.api import FileProperty, FileAction, ITableWorkspaceProperty, PropertyMode, Progress, TextAxis, PythonAlgorithm,\ + WorkspaceFactory, AnalysisDataService +from mantid.simpleapi import CreateEmptyTableWorkspace, DeleteWorkspace, CropWorkspace, RebinToWorkspace, Divide,\ + ReplaceSpecialValues, FindPeaks, GroupWorkspaces, mtd, Plus, LoadVesuvio, LoadRaw, ConvertToDistribution,\ + FindPeakBackground, ExtractSingleSpectrum, SumSpectra, AppendSpectra, ConvertTableToMatrixWorkspace,\ + ConjoinWorkspaces, Transpose, PlotPeakByLogValue, CloneWorkspace, Fit, MaskDetectors,\ + ExtractUnmaskedSpectra, CreateWorkspace +from functools import partial +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals, EVSMiscFunctions + +import os +import sys +import scipy.constants +import scipy.stats +import numpy as np + + +class EVSCalibrationFit(PythonAlgorithm): + + def summary(self): + return "Fits peaks to a list of spectra using the mass or the d-spacings (for bragg peaks) of the sample." + + def category(self): + return "VesuvioCalibration" + + def PyInit(self): + + self.declareProperty(StringArrayProperty("Samples", Direction.Input), + doc="Sample run numbers to fit peaks to.") + + self.declareProperty(StringArrayProperty("Background", Direction.Input), + doc="Run numbers to use as a background.") + + self.declareProperty('Mode', 'FoilOut', StringListValidator(EVSGlobals.MODES), + doc="Mode to load files with. This is passed to the LoadVesuvio algorithm. Default is FoilOut.") + + self.declareProperty('Function', 'Gaussian', StringListValidator(['Gaussian', 'Voigt']), + doc="Function to fit each of the spectra with. Default is Gaussian") + + spectrum_validator = IntArrayBoundedValidator() + spectrum_validator.setLower(EVSGlobals.DETECTOR_RANGE[0]) + spectrum_validator.setUpper(EVSGlobals.DETECTOR_RANGE[1]) + self.declareProperty(IntArrayProperty('SpectrumRange', EVSGlobals.DETECTOR_RANGE, spectrum_validator, Direction.Input), + doc='Spectrum range to use. Default is the total range (%d,%d)' % tuple(EVSGlobals.DETECTOR_RANGE)) + + self.declareProperty('Mass', 207.19, + doc="Mass of the sample in amu to be used when calculating energy. Default is Pb: 207.19") + + greaterThanZero = FloatArrayBoundedValidator() + greaterThanZero.setLower(0) + self.declareProperty(FloatArrayProperty('DSpacings', [], greaterThanZero, Direction.Input), + doc="List of d-spacings used to estimate the positions of bragg peaks in TOF.") + + self.declareProperty( + FloatArrayProperty('Energy', [EVSGlobals.ENERGY_ESTIMATE], FloatArrayMandatoryValidator(), Direction.Input), + doc='List of estimated expected energies for peaks. Optional: the default is %f' % EVSGlobals.ENERGY_ESTIMATE) + + self.declareProperty( + FileProperty('InstrumentParameterFile', '', action=FileAction.OptionalLoad, extensions=["par"]), + doc='Filename of the instrument parameter file.') + + self.declareProperty('PeakType', '', StringListValidator(EVSGlobals.PEAK_TYPES), + doc='Choose the peak type that is being fitted.' + 'Note that supplying a set of dspacings overrides the setting here') + + shared_fit_type_validator = StringListValidator(["Individual", "Shared", "Both"]) + self.declareProperty('SharedParameterFitType', "Individual", + doc='Calculate shared parameters using an individual and/or' + 'global fit.', validator=shared_fit_type_validator) + + self.declareProperty( + ITableWorkspaceProperty("InstrumentParameterWorkspace", "", Direction.Input, PropertyMode.Optional), + doc='Workspace contain instrument parameters.') + + self.declareProperty('CreateOutput', False, + doc='Create fitting workspaces for each of the parameters.') + + self.declareProperty('OutputWorkspace', '', StringMandatoryValidator(), + doc="Name to call the output workspace.") + + def PyExec(self): + self._setup() + self._preprocess() + + if self._fitting_bragg_peaks: + self._fit_bragg_peaks() + else: + self._fit_peaks() + + # create output of fit if required. + if self._create_output and not self._fitting_bragg_peaks: + self._generate_fit_workspaces() + + # clean up workspaces + if self._param_file != "": + DeleteWorkspace(self._param_table) + + def _setup(self): + """ + Setup parameters for fitting. + """ + self._setup_spectra_list() + self._setup_run_numbers_and_output_workspace() + self._setup_function_type() + self._setup_parameter_workspace() + self._setup_peaks_and_set_crop_and_fit_ranges() + self._setup_class_variables_from_properties() + + def _setup_class_variables_from_properties(self): + self._mode = self.getProperty("Mode").value + self._energy_estimates = self.getProperty("Energy").value + self._sample_mass = self.getProperty("Mass").value + self._create_output = self.getProperty("CreateOutput").value + self._shared_parameter_fit_type = self.getProperty("SharedParameterFitType").value + + def _setup_spectra_list(self): + self._spec_list = self.getProperty("SpectrumRange").value.tolist() + if len(self._spec_list) > 2: + self._spec_list = [self._spec_list[0], self._spec_list[-1]] + elif len(self._spec_list) == 1: + self._spec_list = [self._spec_list[0]] + elif len(self._spec_list) < 1: + raise ValueError("You must specify a spectrum range.") + + def _setup_run_numbers_and_output_workspace(self): + self._sample_run_numbers = self.getProperty("Samples").value + self._bkg_run_numbers = self.getProperty("Background").value + self._output_workspace_name = self.getPropertyValue("OutputWorkspace") + if len(self._sample_run_numbers) == 0: + raise ValueError("You must supply at least one sample run number.") + if len(self._bkg_run_numbers) > 0: + self._background = '' + self._bkg_run_numbers[0] + + self._sample = self._output_workspace_name + '_Sample_' + '_'.join(self._sample_run_numbers) + + def _setup_function_type(self): + self._peak_function = self.getProperty("Function").value + self._func_param_names = EVSMiscFunctions.generate_fit_function_header(self._peak_function) + self._func_param_names_error = EVSMiscFunctions.generate_fit_function_header(self._peak_function, error=True) + + def _setup_parameter_workspace(self): + self._param_workspace = self.getPropertyValue('InstrumentParameterWorkspace') + self._param_file = self.getPropertyValue('InstrumentParameterFile') + if self._param_workspace != "": + self._param_table = self._param_workspace + elif self._param_file != "": + base = os.path.basename(self._param_file) + self._param_table = os.path.splitext(base)[0] + EVSMiscFunctions.load_instrument_parameters(self._param_file, self._param_table) + + def _setup_peaks_and_set_crop_and_fit_ranges(self): + self._d_spacings = self.getProperty("DSpacings").value + self._d_spacings.sort() + self._peak_type = self.getPropertyValue('PeakType') + + if self._fitting_bragg_peaks: + self._ws_crop_range, self._fit_window_range = EVSGlobals.BRAGG_PEAK_CROP_RANGE, EVSGlobals.BRAGG_FIT_WINDOW_RANGE + elif self._fitting_recoil_peaks: + self._ws_crop_range, self._fit_window_range = EVSGlobals.RECOIL_PEAK_CROP_RANGE, EVSGlobals.RECOIL_FIT_WINDOW_RANGE + elif self._fitting_resonance_peaks: + self._ws_crop_range, self._fit_window_range = EVSGlobals.RESONANCE_PEAK_CROP_RANGE, EVSGlobals.RESONANCE_FIT_WINDOW_RANGE + + @property + def _fitting_bragg_peaks(self): + return len(self._d_spacings) > 0 + + @property + def _fitting_recoil_peaks(self): + return self._peak_type == "Recoil" and not self._fitting_bragg_peaks + + @property + def _fitting_resonance_peaks(self): + return self._peak_type == "Resonance" and not self._fitting_bragg_peaks + + def _preprocess(self): + """ + Preprocess a workspace. This include optionally dividing by a background + """ + xmin, xmax = self._ws_crop_range + self._load_to_ads_and_crop(self._sample_run_numbers, self._sample, xmin, xmax) + + if self._background_provided: + self._load_to_ads_and_crop(self._bkg_run_numbers, self._background, xmin, xmax) + self._normalise_sample_by_background() + + ReplaceSpecialValues(self._sample, NaNValue=0, NaNError=0, InfinityValue=0, InfinityError=0, + OutputWorkspace=self._sample) + + @property + def _background_provided(self): + return len(self._bkg_run_numbers) > 0 + + def _load_to_ads_and_crop(self, run_numbers, output, xmin, xmax): + self._load_files(run_numbers, output) + CropWorkspace(output, XMin=xmin, XMax=xmax, OutputWorkspace=output) + + def _normalise_sample_by_background(self): + RebinToWorkspace(WorkspaceToRebin=self._background, WorkspaceToMatch=self._sample, + OutputWorkspace=self._background) + Divide(self._sample, self._background, OutputWorkspace=self._sample) + DeleteWorkspace(self._background) + + def _fit_bragg_peaks(self): + estimated_peak_positions_all_spec = self._estimate_bragg_peak_positions() + num_estimated_peaks, num_spectra = estimated_peak_positions_all_spec.shape + + self._prog_reporter = Progress(self, 0.0, 1.0, num_spectra) + + output_parameters_tbl_name = self._output_workspace_name + '_Peak_Parameters' + self._create_output_parameters_table_ws(output_parameters_tbl_name, num_estimated_peaks) + + output_workspaces = [] + for index, estimated_peak_positions in enumerate(estimated_peak_positions_all_spec.transpose()): + spec_number = self._spec_list[0] + index + self._prog_reporter.report("Fitting to spectrum %d" % spec_number) + + find_peaks_output_name = self._sample + '_peaks_table_%d' % spec_number + fit_peaks_output_name = self._output_workspace_name + '_Spec_%d' % spec_number + fit_results = self._fit_peaks_to_spectra(index, spec_number, estimated_peak_positions, + find_peaks_output_name, + fit_peaks_output_name) + + fit_results_unconstrained = None + if fit_results['status'] != "success": + self._prog_reporter.report("Fitting to spectrum %d without constraining parameters" % spec_number) + fit_results_unconstrained = self._fit_peaks_to_spectra(index, spec_number, estimated_peak_positions, + find_peaks_output_name, + fit_peaks_output_name, unconstrained=True, + x_range=( + fit_results['xmin'], fit_results['xmax'])) + + selected_params, unconstrained_fit_selected = self._select_best_fit_params(spec_number, fit_results, + fit_results_unconstrained) + + self._output_params_to_table(spec_number, num_estimated_peaks, selected_params, output_parameters_tbl_name) + + output_workspaces.append( + self._get_output_and_clean_workspaces(fit_results_unconstrained is not None, + (fit_results_unconstrained is not None and fit_results_unconstrained is not False), + unconstrained_fit_selected, find_peaks_output_name, + fit_peaks_output_name)) + + if self._create_output: + GroupWorkspaces(','.join(output_workspaces), OutputWorkspace=self._output_workspace_name + "_Peak_Fits") + + @staticmethod + def _get_unconstrained_ws_name(ws_name): + return ws_name + '_unconstrained' + + def _create_output_parameters_table_ws(self, output_table_name, num_estimated_peaks): + table = CreateEmptyTableWorkspace(OutputWorkspace=output_table_name) + + col_headers = self._generate_column_headers(num_estimated_peaks) + + table.addColumn('int', 'Spectrum') + for name in col_headers: + table.addColumn('double', name) + AnalysisDataService.addOrReplace(output_table_name, table) + + def _generate_column_headers(self, num_estimated_peaks): + param_names = self._get_param_names(num_estimated_peaks) + err_names = [name + '_Err' for name in param_names] + col_headers = [element for tupl in zip(param_names, err_names) for element in tupl] + return col_headers + + def _get_param_names(self, num_estimated_peaks): + param_names = ['f0.A0', 'f0.A1'] + for i in range(num_estimated_peaks): + param_names += ['f' + str(i) + '.' + name for name in self._func_param_names.values()] + return param_names + + def _fit_peaks_to_spectra(self, workspace_index, spec_number, peak_estimates_list, find_peaks_output_name, + fit_peaks_output_name, unconstrained=False, x_range=None): + if unconstrained: + find_peaks_output_name = self._get_unconstrained_ws_name(find_peaks_output_name) + fit_peaks_output_name = self._get_unconstrained_ws_name(fit_peaks_output_name) + logger.notice("Fitting to spectrum %d without constraining parameters" % spec_number) + + find_peaks_input_params = self._get_find_peak_parameters(spec_number, peak_estimates_list, unconstrained) + logger.notice(str(spec_number) + ' ' + str(find_peaks_input_params)) + peaks_found = self._run_find_peaks(workspace_index, find_peaks_output_name, find_peaks_input_params, + unconstrained) + + if peaks_found: + return self._filter_and_fit_found_peaks(workspace_index, peak_estimates_list, find_peaks_output_name, + fit_peaks_output_name, x_range, unconstrained) + else: + return False + + def _filter_and_fit_found_peaks(self, workspace_index, peak_estimates_list, find_peaks_output_name, + fit_peaks_output_name, + x_range, unconstrained): + if unconstrained: + linear_bg_coeffs = self._calc_linear_bg_coefficients() + self._filter_found_peaks(find_peaks_output_name, peak_estimates_list, linear_bg_coeffs, + peak_height_rel_threshold=EVSGlobals.PEAK_HEIGHT_RELATIVE_THRESHOLD) + + fit_results = self._fit_found_peaks(find_peaks_output_name, peak_estimates_list if not unconstrained else None, + workspace_index, fit_peaks_output_name, x_range) + fit_results['status'] = "peaks invalid" if not \ + self._check_fitted_peak_validity(fit_peaks_output_name + '_Parameters', peak_estimates_list, + peak_height_rel_threshold=EVSGlobals.PEAK_HEIGHT_RELATIVE_THRESHOLD) else fit_results[ + 'status'] + return fit_results + + def _run_find_peaks(self, workspace_index, find_peaks_output_name, find_peaks_input_params, unconstrained): + try: + FindPeaks(InputWorkspace=self._sample, WorkspaceIndex=workspace_index, PeaksList=find_peaks_output_name, + **find_peaks_input_params) + if mtd[find_peaks_output_name].rowCount() > 0: + peaks_found = True + else: + raise ValueError + except ValueError: + peaks_found = False + if not unconstrained: # Ignore error if unconstrained, as we will use peaks found during constrained workflow. + raise ValueError("Error finding peaks.") + return peaks_found + + def _select_best_fit_params(self, spec_num, fit_results, fit_results_u=None): + selected_params = fit_results['params'] + unconstrained_fit_selected = False + if fit_results_u: + if fit_results_u['chi2'] < fit_results['chi2'] and fit_results_u['status'] != "peaks invalid": + selected_params = fit_results_u['params'] + unconstrained_fit_selected = True + self._prog_reporter.report("Fit to spectrum %d without constraining parameters successful" % spec_num) + return selected_params, unconstrained_fit_selected + + def _output_params_to_table(self, spec_num, num_estimated_peaks, params, output_table_name): + fit_values = dict(zip(params.column(0), params.column(1))) + fit_errors = dict(zip(params.column(0), params.column(2))) + + row_values = [fit_values['f0.A0'], fit_values['f0.A1']] + row_errors = [fit_errors['f0.A0'], fit_errors['f0.A1']] + param_names = self._get_param_names(num_estimated_peaks) + row_values += [fit_values['f1.' + name] for name in param_names[2:]] + row_errors += [fit_errors['f1.' + name] for name in param_names[2:]] + row = [element for tupl in zip(row_values, row_errors) for element in tupl] + + mtd[output_table_name].addRow([spec_num] + row) + + def _get_output_and_clean_workspaces(self, unconstrained_fit_performed, unconstrained_peaks_found, + unconstrained_fit_selected, find_peaks_output_name, + fit_peaks_output_name): + find_peaks_output_name_u = self._get_unconstrained_ws_name(find_peaks_output_name) + fit_peaks_output_name_u = self._get_unconstrained_ws_name(fit_peaks_output_name) + + DeleteWorkspace(fit_peaks_output_name + '_NormalisedCovarianceMatrix') + DeleteWorkspace(fit_peaks_output_name + '_Parameters') + DeleteWorkspace(find_peaks_output_name) + + output_workspace = fit_peaks_output_name + '_Workspace' + if unconstrained_fit_performed: + DeleteWorkspace(find_peaks_output_name_u) + + if unconstrained_peaks_found: + DeleteWorkspace(fit_peaks_output_name_u + '_NormalisedCovarianceMatrix') + DeleteWorkspace(fit_peaks_output_name_u + '_Parameters') + + if unconstrained_fit_selected: + output_workspace = fit_peaks_output_name_u + '_Workspace' + DeleteWorkspace(fit_peaks_output_name + '_Workspace') + else: + DeleteWorkspace(fit_peaks_output_name_u + '_Workspace') + return output_workspace + + def _calc_linear_bg_coefficients(self): + temp_bg_fit = Fit(Function="(name=LinearBackground,A0=0,A1=0)", InputWorkspace=self._sample, + Output="temp_bg_fit", CreateOutput=True) + linear_bg_coeffs = temp_bg_fit.OutputParameters.cell("Value", 0), temp_bg_fit.OutputParameters.cell("Value", 1) + DeleteWorkspace('temp_bg_fit_Workspace') + DeleteWorkspace('temp_bg_fit_NormalisedCovarianceMatrix') + DeleteWorkspace('temp_bg_fit_Parameters') + return linear_bg_coeffs + + def _check_fitted_peak_validity(self, table_name, estimated_peaks, peak_height_abs_threshold=0.0, + peak_height_rel_threshold=0.0): + check_nans = self._check_nans(table_name) + check_peak_positions = self._check_peak_positions(table_name, estimated_peaks) + check_peak_heights = self._check_peak_heights(table_name, peak_height_abs_threshold, peak_height_rel_threshold) + + if check_nans and check_peak_positions and check_peak_heights: + return True + else: + return False + + def _check_nans(self, table_name): + table_ws = mtd[table_name] + for i in table_ws.column("Value"): + if np.isnan(i): + print(f"nan found in value common, indicates invalid peak") + return False + return True + + def _check_peak_positions(self, table_name, estimated_positions): + pos_str = self._func_param_names["Position"] + + i = 0 + invalid_positions = [] + for name, value in zip(mtd[table_name].column("Name"), mtd[table_name].column("Value")): + if pos_str in name: + if not (value >= estimated_positions[i] - EVSGlobals.BRAGG_PEAK_POSITION_TOLERANCE and value <= + estimated_positions[i] + EVSGlobals.BRAGG_PEAK_POSITION_TOLERANCE): + invalid_positions.append(value) + i += 1 + + if len(invalid_positions) > 0: + print(f"Invalid peak positions found: {invalid_positions}") + return False + else: + return True + + def _evaluate_peak_height_against_bg(self, height, position, linear_bg_A0, linear_bg_A1, rel_threshold, + abs_threshold): + bg = position * linear_bg_A1 + linear_bg_A0 + required_height = bg * rel_threshold + abs_threshold + if height < required_height: + return False + else: + return True + + def _check_peak_heights(self, table_name, abs_threshold_over_bg, rel_threshold_over_bg): + height_str = self._func_param_names["Height"] + pos_str = self._func_param_names["Position"] + + peak_heights = [] + peak_positions = [] + for name, value in zip(mtd[table_name].column("Name"), mtd[table_name].column("Value")): + if height_str in name: + peak_heights.append(value) + elif pos_str in name: + peak_positions.append(value) + elif name == "f0.A0": + linear_bg_A0 = value + elif name == "f0.A1": + linear_bg_A1 = value + + for height, pos in zip(peak_heights, peak_positions): + if not self._evaluate_peak_height_against_bg(height, pos, linear_bg_A0, linear_bg_A1, abs_threshold_over_bg, + rel_threshold_over_bg): + print(f"Peak height threshold not met. Found height: {height}") + return False + return True + + def _filter_found_peaks(self, find_peaks_output_name, peak_estimates_list, linear_bg_coeffs, + peak_height_abs_threshold=0.0, peak_height_rel_threshold=0.0): + unfiltered_fits_ws_name = find_peaks_output_name + '_unfiltered' + CloneWorkspace(InputWorkspace=mtd[find_peaks_output_name], OutputWorkspace=unfiltered_fits_ws_name) + + peak_estimate_deltas = [] + linear_bg_A0, linear_bg_A1 = linear_bg_coeffs + # with estimates for spectrum, loop through all peaks, get and store delta from peak estimates + for peak_estimate_index, peak_estimate in enumerate(peak_estimates_list): + for position_index, (position, height) in enumerate(zip(mtd[unfiltered_fits_ws_name].column(2), + mtd[unfiltered_fits_ws_name].column(1))): + if not position == 0 and self._evaluate_peak_height_against_bg(height, position, linear_bg_A0, + linear_bg_A1, peak_height_abs_threshold, + peak_height_rel_threshold): + peak_estimate_deltas.append((peak_estimate_index, position_index, abs(position - peak_estimate))) + + # loop through accendings delta, assign peaks until there are none left to be assigned. + peak_estimate_deltas.sort(key=partial(self._get_x_elem, elem=2)) + index_matches = [] + for peak_estimate_index, position_index, delta in peak_estimate_deltas: + # if all estimated peaks matched, break + if len(index_matches) == len(peak_estimates_list): + break + # assign match for smallest delta if that position or estimate has not been already matched + if position_index not in [i[1] for i in index_matches] and \ + peak_estimate_index not in [i[0] for i in index_matches] and \ + all(x > position_index for x in [i[1] for i in index_matches if i[0] > peak_estimate_index]) and \ + all(x < position_index for x in [i[1] for i in index_matches if i[0] < peak_estimate_index]): + index_matches.append((peak_estimate_index, position_index)) + + if len(index_matches) > 0: + index_matches.sort(key=partial(self._get_x_elem, elem=1)) + mtd[find_peaks_output_name].setRowCount(len(peak_estimates_list)) + for col_index in range(mtd[find_peaks_output_name].columnCount()): + match_n = 0 + for row_index in range(mtd[find_peaks_output_name].rowCount()): + if row_index in [x[0] for x in index_matches]: + position_row_index = index_matches[match_n][1] + mtd[find_peaks_output_name].setCell(row_index, col_index, + mtd[unfiltered_fits_ws_name].cell(position_row_index, + col_index)) + match_n += 1 + else: # otherwise just use estimate position + pos_str = self._func_param_names["Position"] + mtd[find_peaks_output_name].setCell(pos_str, row_index, peak_estimates_list[row_index]) + DeleteWorkspace(unfiltered_fits_ws_name) + + def _get_x_elem(self, input_list, elem): + return input_list[elem] + + def _fit_found_peaks(self, peak_table, peak_estimates_list, workspace_index, fit_output_name, xLimits=None): + # get parameters from the peaks table + peak_params = [] + prefix = '' # 'f0.' + position = prefix + self._func_param_names['Position'] + + if peak_estimates_list is not None: # If no peak estimates list, we are doing an unconstrained fit + # Don't yet understand what this is doing here + self._set_table_column(peak_table, position, peak_estimates_list, spec_list=None) + unconstrained = False + else: + unconstrained = True + + for peak_index in range(mtd[peak_table].rowCount()): + peak_params.append(mtd[peak_table].row(peak_index)) + + # build function string + func_string = self._build_multiple_peak_function(peak_params, workspace_index, unconstrained) + + # select min and max x range for fitting + positions = [params[position] for params in peak_params] + + if len(positions) < 1: + raise RuntimeError("No position parameter provided.") + + if xLimits: + xmin, xmax = xLimits + else: + xmin = min(peak_estimates_list) - EVSGlobals.BRAGG_PEAK_POSITION_TOLERANCE - self._fit_window_range + xmax = max(peak_estimates_list) + EVSGlobals.BRAGG_PEAK_POSITION_TOLERANCE + self._fit_window_range + + # fit function to workspace + fit_result = Fit(Function=func_string, InputWorkspace=self._sample, IgnoreInvalidData=True, StartX=xmin, + EndX=xmax, + WorkspaceIndex=workspace_index, CalcErrors=True, Output=fit_output_name, + Minimizer='Levenberg-Marquardt,AbsError=0,RelError=1e-8') + (xmin,) + (xmax,) + fit_result_key = 'status', 'chi2', 'ncm', 'params', 'fws', 'func', 'cost_func', 'xmin', 'xmax' + fit_results_dict = dict(zip(fit_result_key, fit_result)) + return fit_results_dict + + def _fit_peaks(self): + """ + Fit peaks to time of flight data. + + This uses the Mantid algorithms FindPeaks and Fit. Estimates for the centre of the peaks + are found using either the energy or d-spacings provided. It creates a group workspace + containing one table workspace per peak with parameters for each detector. + """ + + estimated_peak_positions_all_peaks = self._estimate_peak_positions() + num_estimated_peaks, num_spectra = estimated_peak_positions_all_peaks.shape + + self._prog_reporter = Progress(self, 0.0, 1.0, num_estimated_peaks * num_spectra) + + self._output_parameter_tables = [] + self._peak_fit_workspaces = [] + for peak_index, estimated_peak_positions in enumerate(estimated_peak_positions_all_peaks): + + self._peak_fit_workspaces_by_spec = [] + output_parameter_table_name = self._output_workspace_name + '_Peak_%d_Parameters' % peak_index + output_parameter_table_headers = self._create_parameter_table_and_output_headers( + output_parameter_table_name) + for spec_index, peak_position in enumerate(estimated_peak_positions): + fit_workspace_name = self._fit_peak(peak_index, spec_index, peak_position, output_parameter_table_name, + output_parameter_table_headers) + self._peak_fit_workspaces_by_spec.append(fit_workspace_name) + + self._output_parameter_tables.append(output_parameter_table_name) + self._peak_fit_workspaces.append(self._peak_fit_workspaces_by_spec) + + GroupWorkspaces(self._output_parameter_tables, OutputWorkspace=self._output_workspace_name + '_Peak_Parameters') + + if self._shared_parameter_fit_type != "Individual": + self._shared_parameter_fit(output_parameter_table_name, output_parameter_table_headers) + + def _shared_parameter_fit(self, output_parameter_table_name, output_parameter_table_headers): + init_Gaussian_FWHM = EVSMiscFunctions.read_fitting_result_table_column(output_parameter_table_name, 'f1.GaussianFWHM', + self._spec_list) + init_Gaussian_FWHM = np.nanmean(init_Gaussian_FWHM[init_Gaussian_FWHM != 0]) + init_Lorentz_FWHM = EVSMiscFunctions.read_fitting_result_table_column(output_parameter_table_name, 'f1.LorentzFWHM', + self._spec_list) + init_Lorentz_FWHM = np.nanmean(init_Lorentz_FWHM[init_Lorentz_FWHM != 0]) + init_Lorentz_Amp = EVSMiscFunctions.read_fitting_result_table_column(output_parameter_table_name, 'f1.LorentzAmp', + self._spec_list) + init_Lorentz_Amp = np.nanmean(init_Lorentz_Amp[init_Lorentz_Amp != 0]) + init_Lorentz_Pos = EVSMiscFunctions.read_fitting_result_table_column(output_parameter_table_name, 'f1.LorentzPos', + self._spec_list) + init_Lorentz_Pos = np.nanmean(init_Lorentz_Pos[init_Lorentz_Pos != 0]) + + initial_params = {'A0': 0.0, 'A1': 0.0, 'LorentzAmp': init_Lorentz_Amp, 'LorentzPos': init_Lorentz_Pos, + 'LorentzFWHM': init_Lorentz_FWHM, 'GaussianFWHM': init_Gaussian_FWHM} + + peak_centres = EVSMiscFunctions.read_fitting_result_table_column(output_parameter_table_name, 'f1.LorentzPos', self._spec_list) + peak_centres_errors = EVSMiscFunctions.read_fitting_result_table_column(output_parameter_table_name, 'f1.LorentzPos_Err', + self._spec_list) + invalid_spectra = EVSMiscFunctions.identify_invalid_spectra(output_parameter_table_name, peak_centres, peak_centres_errors, + self._spec_list) + self._fit_shared_parameter(invalid_spectra, initial_params, output_parameter_table_headers) + + def _fit_peak(self, peak_index, spec_index, peak_position, output_parameter_table_name, + output_parameter_table_headers): + spec_number = self._spec_list[0] + spec_index + self._prog_reporter.report("Fitting peak %d to spectrum %d" % (peak_index, spec_number)) + + peak_params = self._find_peaks_and_output_params(peak_index, spec_number, spec_index, peak_position) + fit_func_string = self._build_function_string(peak_params) + xmin, xmax = self._find_fit_x_window(peak_params) + fit_output_name = '__' + self._output_workspace_name + '_Peak_%d_Spec_%d' % (peak_index, spec_index) + status, chi2, ncm, fit_params, fws, func, cost_func = Fit(Function=fit_func_string, InputWorkspace=self._sample, + IgnoreInvalidData=True, + StartX=xmin, EndX=xmax, WorkspaceIndex=spec_index, + CalcErrors=True, Output=fit_output_name, + Minimizer='Levenberg-Marquardt,RelError=1e-8') + + self._output_fit_params_to_table_ws(spec_number, fit_params, output_parameter_table_name, + output_parameter_table_headers) + fit_workspace_name = fws.name() + self._del_fit_workspaces(ncm, fit_params, fws) + + return fit_workspace_name + + def _find_peaks_and_output_params(self, peak_index, spec_number, spec_index, peak_position): + peak_table_name = '__' + self._sample + '_peaks_table_%d_%d' % (peak_index, spec_index) + find_peak_params = self._get_find_peak_parameters(spec_number, [peak_position]) + FindPeaks(InputWorkspace=self._sample, WorkspaceIndex=spec_index, PeaksList=peak_table_name, **find_peak_params) + if mtd[peak_table_name].rowCount() == 0: + logger.error('FindPeaks could not find any peaks matching the parameters:\n' + str(find_peak_params)) + sys.exit() + + peak_params = mtd[peak_table_name].row(0) + DeleteWorkspace(peak_table_name) + return peak_params + + def _find_fit_x_window(self, peak_params): + xmin, xmax = None, None + + position = '' + self._func_param_names['Position'] + if peak_params[position] > 0: + xmin = peak_params[position] - self._fit_window_range + xmax = peak_params[position] + self._fit_window_range + else: + logger.warning('Could not specify fit window. Using full spectrum x range.') + return xmin, xmax + + def _output_fit_params_to_table_ws(self, spec_num, params, output_table_name, output_table_headers): + fit_values = dict(zip(params.column(0), params.column(1))) + fit_errors = dict(zip(params.column(0), params.column(2))) + row_values = [fit_values[name] for name in output_table_headers] + row_errors = [fit_errors[name] for name in output_table_headers] + row = [element for tupl in zip(row_values, row_errors) for element in tupl] + + mtd[output_table_name].addRow([spec_num] + row) + + def _del_fit_workspaces(self, ncm, fit_params, fws): + DeleteWorkspace(ncm) + DeleteWorkspace(fit_params) + if not self._create_output: + DeleteWorkspace(fws) + + def _fit_shared_parameter(self, invalid_spectra, initial_params, param_names): + """ + Fit peaks to all detectors, with one set of fit parameters for all detectors. + """ + + shared_peak_table = self._output_workspace_name + '_shared_peak_parameters' + CreateEmptyTableWorkspace(OutputWorkspace=shared_peak_table) + + err_names = [name + '_Err' for name in param_names] + col_names = [element for tupl in zip(param_names, err_names) for element in tupl] + + mtd[shared_peak_table].addColumn('int', 'Spectrum') + for name in col_names: + mtd[shared_peak_table].addColumn('double', name) + + fit_func = self._build_function_string(initial_params) + start_str = 'composite=MultiDomainFunction, NumDeriv=1;' + sample_ws = mtd[self._sample] + MaskDetectors(Workspace=sample_ws, WorkspaceIndexList=invalid_spectra) + validSpecs = ExtractUnmaskedSpectra(InputWorkspace=sample_ws, OutputWorkspace='valid_spectra') + n_valid_specs = validSpecs.getNumberHistograms() + + fit_func = ('(composite=CompositeFunction, NumDeriv=false, $domains=i;' + fit_func[:-1] + ');') * n_valid_specs + composite_func = start_str + fit_func[:-1] + + ties = ','.join( + f'f{i}.f1.{p}=f0.f1.{p}' for p in self._func_param_names.values() for i in range(1, n_valid_specs)) + func_string = composite_func + f';ties=({ties})' + + fit_output_name = '__' + self._output_workspace_name + '_Peak_0' + + xmin, xmax = None, None + + # create new workspace for each spectra + x = validSpecs.readX(0) + y = validSpecs.readY(0) + e = validSpecs.readE(0) + out_ws = self._sample + '_Spec_0' + CreateWorkspace(DataX=x, DataY=y, DataE=e, NSpec=1, OutputWorkspace=out_ws) + + other_inputs = [CreateWorkspace(DataX=validSpecs.readX(j), DataY=validSpecs.readY(j), DataE=validSpecs.readE(j), + OutputWorkspace=f'{self._sample}_Spec_{j}') + for j in range(1, n_valid_specs)] + + added_args = {f'InputWorkspace_{i + 1}': v for i, v in enumerate(other_inputs)} + + print('starting global fit') + + status, chi2, ncm, params, fws, func, cost_func = Fit(Function=func_string, InputWorkspace=out_ws, + IgnoreInvalidData=True, + StartX=xmin, EndX=xmax, + CalcErrors=True, Output=fit_output_name, + Minimizer='SteepestDescent,RelError=1e-8', **added_args) + [DeleteWorkspace(f"{self._sample}_Spec_{i}") for i in range(0, n_valid_specs)] + + fit_values = dict(zip(params.column(0), params.column(1))) + fit_errors = dict(zip(params.column(0), params.column(2))) + + row_values = [fit_values['f0.' + name] for name in param_names] + row_errors = [fit_errors['f0.' + name] for name in param_names] + row = [element for tupl in zip(row_values, row_errors) for element in tupl] + + mtd[shared_peak_table].addRow([0] + row) + + DeleteWorkspace(ncm) + DeleteWorkspace(params) + if not self._create_output: + DeleteWorkspace(fws) + + DeleteWorkspace(validSpecs) + + mtd[self._output_workspace_name + '_Peak_Parameters'].add(shared_peak_table) + + def _get_find_peak_parameters(self, spec_number, peak_centre, unconstrained=False): + """ + Get find peak parameters + + @param spec_num - the current spectrum number being fitted + @return dictionary of parameters for find peaks + """ + + find_peak_params = {} + find_peak_params['PeakFunction'] = self._peak_function + find_peak_params['RawPeakParameters'] = True + find_peak_params['BackgroundType'] = 'Linear' + + if not unconstrained: + find_peak_params['PeakPositions'] = peak_centre + + if self._fitting_bragg_peaks and not unconstrained: + find_peak_params['PeakPositionTolerance'] = EVSGlobals.BRAGG_PEAK_POSITION_TOLERANCE + + if spec_number >= EVSGlobals.FRONTSCATTERING_RANGE[0]: + find_peak_params['FWHM'] = 70 + else: + find_peak_params['FWHM'] = 5 + + elif not self._fitting_bragg_peaks: + if self._fitting_resonance_peaks: + # 25 seems to be able to fit the final peak in the first backscattering spectrum + if spec_number >= EVSGlobals.FRONTSCATTERING_RANGE[0]: + half_peak_window = 20 + else: + half_peak_window = 25 + else: + # #ust be recoil + half_peak_window = 60 + + fit_windows = [[peak - half_peak_window, peak + half_peak_window] for peak in peak_centre] + # flatten the list: http://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python + find_peak_params['FitWindows'] = [peak for pair in fit_windows for peak in pair] + + return find_peak_params + + def _create_parameter_table_and_output_headers(self, peaks_table): + """ + Create a table workspace with headers corresponding to the parameters + output from fitting. + + @param peak_table - name to call the parameter table. + @return param_names - name of the columns + """ + CreateEmptyTableWorkspace(OutputWorkspace=peaks_table) + + param_names = ['f0.A0', 'f0.A1'] + param_names += ['f1.' + name for name in self._func_param_names.values()] + + err_names = [name + '_Err' for name in param_names] + col_names = [element for tupl in zip(param_names, err_names) for element in tupl] + + mtd[peaks_table].addColumn('int', 'Spectrum') + for name in col_names: + mtd[peaks_table].addColumn('double', name) + + return param_names + + def _estimate_peak_positions(self): + """ + Estimate the position of a peak based on a given energy and previous calibrated parameters. + + @return list of estimates in tof for the peak centres + """ + L0 = self._read_param_column('L0', self._spec_list) + L1 = self._read_param_column('L1', self._spec_list) + t0 = self._read_param_column('t0', self._spec_list) + t0 /= 1e+6 + thetas = self._read_param_column('theta', self._spec_list) + r_theta = EVSMiscFunctions.calculate_r_theta(self._sample_mass, thetas) + + self._energy_estimates = self._energy_estimates.reshape(1, self._energy_estimates.size).T + self._energy_estimates *= EVSGlobals.MEV_CONVERSION + + v1 = np.sqrt(2 * self._energy_estimates / scipy.constants.m_n) + if self._fitting_recoil_peaks: + # for recoil peaks: + tof = ((L0 * r_theta + L1) / v1) + t0 + elif self._fitting_resonance_peaks: + # for resonances: + tof = (L0 / v1) + t0 + else: + raise RuntimeError("Cannot estimate peak positions for unknown peak type") + + tof *= 1e+6 + return tof + + def _estimate_bragg_peak_positions(self): + """ + Estimate the position of bragg peaks in TOF using estimates of the parameters + and d-spacings of the sample. + + The number of peaks will depend on the number of d-spacings provided. + + @return estimates of peak positions in TOF for all spectra + """ + + L0 = self._read_param_column('L0', self._spec_list) + L1 = self._read_param_column('L1', self._spec_list) + t0 = self._read_param_column('t0', self._spec_list) + thetas = self._read_param_column('theta', self._spec_list) + + t0 /= 1e+6 + + self._d_spacings *= 1e-10 + self._d_spacings = self._d_spacings.reshape(1, self._d_spacings.size).T + + lambdas = 2 * self._d_spacings * np.sin(np.radians(thetas) / 2) + + L1_nan_to_num = np.nan_to_num(L1) + tof = (lambdas * scipy.constants.m_n * (L0 + L1_nan_to_num)) / scipy.constants.h + t0 + tof *= 1e+6 + + return tof + + def _load_files(self, ws_numbers, output_name): + """ + Load a list of run numbers and sum each of the runs. + + @param ws_numbers - list of run numbers to load. + @param output_name - name to call the final workspace. + """ + + run_numbers = [run for run in self._parse_run_numbers(ws_numbers)] + + self._load_file(run_numbers[0], output_name) + temp_ws_name = '__EVS_calib_temp_ws' + + for run_number in run_numbers[1:]: + self._load_file(run_number, temp_ws_name) + Plus(output_name, temp_ws_name, OutputWorkspace=output_name) + DeleteWorkspace(temp_ws_name) + + def _parse_run_numbers(self, run_numbers): + """ + Converts a list of mixed single runs and run ranges + into a single flat list of run numbers. + + @param run_numbers - list of mixed single runs and run ranges + @return iterator to each run number in the flat list of runs + """ + for run in run_numbers: + if '-' in run: + sample_range = run.split('-') + sample_range = map(int, sample_range) + + for i in range(*sample_range): + yield str(i) + + else: + yield run + + def _load_file(self, ws_name, output_name): + """ + Load a file into a workspace. + + This will attempt to use LoadVesuvio, but will fall back on LoadRaw if LoadVesuvio fails. + @param ws_name - name of the run to load. + @param output_name - name to call the loaded workspace + """ + + try: + LoadVesuvio(Filename=ws_name, Mode=self._mode, OutputWorkspace=output_name, + SpectrumList="%d-%d" % (self._spec_list[0], self._spec_list[-1]), + EnableLogging=False) + except RuntimeError: + LoadRaw('EVS' + ws_name + '.raw', OutputWorkspace=output_name, + SpectrumMin=self._spec_list[0], SpectrumMax=self._spec_list[-1], + EnableLogging=False) + ConvertToDistribution(output_name, EnableLogging=False) + + def _build_linear_background_function(self, peak, tie_gradient=False): + """ + Builds a string describing a peak function that can be passed to Fit. + This will be either a Gaussian or Lorentzian shape. + + @param peaks - dictionary containing the parameters for the function + @return the function string + """ + + bkg_intercept, bkg_graident = peak['A0'], peak['A1'] + fit_func = 'name=LinearBackground, A0=%f, A1=%f' % (bkg_intercept, bkg_graident) + fit_func += ';' + + return fit_func + + def _build_peak_function(self, peak): + """ + Builds a string describing a linear background function that can be passed to Fit. + + @param peaks - dictionary containing the parameters for the function + @return the function string + """ + + fit_func = 'name=%s, ' % self._peak_function + + prefix = '' # f0.' + func_list = [name + '=' + str(peak[prefix + name]) for name in self._func_param_names.values()] + fit_func += ', '.join(func_list) + ';' + + return fit_func + + def _build_multiple_peak_function(self, peaks, workspace_index, unconstrained): + """ + Builds a string describing a composite function that can be passed to Fit. + This will be a linear background with multiple peaks of either a Gaussian or Voigt shape. + + @param peaks - list of dictionaries containing the parameters for the function + @return the function string + """ + + if (len(peaks) == 0): + return '' + + if not unconstrained: + fit_func = self._build_linear_background_function(peaks[0], False) + else: + pos_str = self._func_param_names["Position"] + if len(peaks) > 1: + fit_window = [peaks[0][pos_str] - 1000, peaks[len(peaks) - 1][pos_str] + 1000] + else: + fit_window = [peaks[0][pos_str] - 1000, peaks[0][pos_str] + 1000] + FindPeakBackground(InputWorkspace=self._sample, WorkspaceIndex=workspace_index, FitWindow=fit_window, + BackgroundType="Linear", OutputWorkspace="temp_fit_bg") + fit_func = f'name=LinearBackground, A0={mtd["temp_fit_bg"].cell("bkg0", 0)}, A1={mtd["temp_fit_bg"].cell("bkg1", 0)};' + DeleteWorkspace("temp_fit_bg") + + fit_func += '(' + for i, peak in enumerate(peaks): + fit_func += self._build_peak_function(peak) + + fit_func = fit_func[:-1] + fit_func += ');' + + return fit_func + + def _build_function_string(self, peak): + """ + Builds a string describing a composite function that can be passed to Fit. + This will be a linear background with either a Gaussian or Voigt function. + + @param peak - dictionary containing the parameters for the function + @return the function string + """ + + if (len(peak) == 0): + return '' + + fit_func = self._build_linear_background_function(peak) + fit_func += self._build_peak_function(peak) + + return fit_func + + def _read_param_column(self, column_name, spec_list=EVSGlobals.DETECTOR_RANGE): + """ + Read a column from a table workspace and return the data as an array. + + @param column_name - name of the column to select + @param spec_list - range of spectra to use + @return numpy array of values in the spec_list range + """ + + return EVSMiscFunctions.read_table_column(self._param_table, column_name, spec_list) + + def _set_table_column(self, table_name, column_name, data, spec_list=None): + """ + Add a set of data to a table workspace + + @param table_name - name of the table workspace to modify + @param column_name - name of the column to add data to + @param data - data array to add to the table + @param spec_list - range of rows to add the data too + """ + table_ws = mtd[table_name] + + if column_name not in table_ws.getColumnNames(): + table_ws.addColumn('double', column_name) + + if spec_list == None: + offset = 0 + else: + if len(data) < (spec_list[1] + 1 - spec_list[0]): + raise ValueError("Not enough data from spectrum range.") + + offset = spec_list[0] - EVSGlobals.DETECTOR_RANGE[0] + + if isinstance(data, np.ndarray): + data = data.tolist() + + for i, value in enumerate(data): + table_ws.setCell(column_name, offset + i, value) + + def _generate_fit_workspaces(self): + """ + Output the fit workspace for each spectrum fitted. + """ + fit_workspaces = map(list, zip(*self._peak_fit_workspaces)) + group_ws_names = [] + + for i, peak_fits in enumerate(fit_workspaces): + ws_name = self._output_workspace_name + '_%d_Workspace' % i + ExtractSingleSpectrum(InputWorkspace=self._sample, WorkspaceIndex=i, OutputWorkspace=ws_name) + + # transfer fits to individual spectrum + peak_collection = WorkspaceFactory.create(mtd[ws_name], NVectors=len(peak_fits)) + data_x = mtd[ws_name].readX(0) + + ax = TextAxis.create(len(peak_fits) + 2) + ax.setLabel(0, "Data") + + for j, peak in enumerate(peak_fits): + peak_x = mtd[peak].readX(0) + peak_y = mtd[peak].readY(1) + peak_e = mtd[peak].readE(1) + + index = int(np.where(data_x == peak_x[0])[0]) + data_y = peak_collection.dataY(j) + data_y[index:index + peak_y.size] = peak_y + + data_e = peak_collection.dataE(j) + data_e[index:index + peak_e.size] = peak_e + + peak_collection.setX(j, data_x) + peak_collection.setY(j, data_y) + peak_collection.setE(j, data_e) + + ax.setLabel(j + 1, "Peak_%d" % (j + 1)) + + DeleteWorkspace(peak) + + peak_ws = "__tmp_peak_workspace" + mtd.addOrReplace(peak_ws, peak_collection) + AppendSpectra(ws_name, peak_ws, ValidateInputs=False, OutputWorkspace=ws_name) + + # create sum of peak fits + temp_sum_workspace = '__temp_sum_ws' + SumSpectra(InputWorkspace=peak_ws, OutputWorkspace=peak_ws) + AppendSpectra(ws_name, peak_ws, ValidateInputs=False, OutputWorkspace=ws_name) + ax.setLabel(mtd[ws_name].getNumberHistograms() - 1, "Total") + DeleteWorkspace(peak_ws) + + mtd[ws_name].replaceAxis(1, ax) + group_ws_names.append(ws_name) + + GroupWorkspaces(group_ws_names, OutputWorkspace=self._output_workspace_name + "_Peak_Fits") diff --git a/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_helper_functions.py b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_helper_functions.py new file mode 100644 index 00000000..964fc42c --- /dev/null +++ b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_helper_functions.py @@ -0,0 +1,218 @@ +import numpy as np +import scipy.constants + +from mantid.simpleapi import CreateEmptyTableWorkspace, mtd + + +class EVSGlobals: + # Configuration for Uranium runs / Indium runs + # ---------------------------------------------------------------------------------------- + # Uranium sample & background run numbers + U_FRONTSCATTERING_SAMPLE = [14025] # [14025] for U foil in the beam, [19129, 19130] for In foil in the beam + + # ['12570'] or [19132, 19134, 19136, 19138, 19140, 19142,19144, 19146, 19148, 19150, 19152] or [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, 42221, 42222, 42223, 42224, 42225, 42226, 42227,42228] for Pb 2mm with U foil out + U_FRONTSCATTERING_BACKGROUND = [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, + 42221, 42222, 42223, 42224, 42225, 42226, 42227, 42228] + + # ['12570'] or [19132, 19134, 19136, 19138, 19140, 19142,19144, 19146, 19148, 19150, 19152] or [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, 42221, 42222, 42223, 42224, 42225, 42226, 42227,42228] for Pb 2mm with U foil out + U_BACKSCATTERING_SAMPLE = [42209, 42210, 42211, 42212, 42213, 42214, 42215, 42216, 42217, 42218, 42219, 42220, + 42221, 42222, 42223, 42224, 42225, 42226, 42227, 42228] + + # ['12571'] or [42229,42230,42231,42232,42233,42234,42235,42236,42237,42238,42239,42240,42241,42242,42243,42244,42245,42246,42247,42248,42249,42250,42251,42252,42253] or [19131, 19133, 19135, 19137, 19139, 19141, 19143, 19145, 19147, 19149, 19151] for Pb 2mm with U foil in + U_BACKSCATTERING_BACKGROUND = [42229, 42230, 42231, 42232, 42233, 42234, 42235, 42236, 42237, 42238, 42239, 42240, + 42241, 42242, 42243, 42244, 42245, 42246, 42247, 42248, 42249, 42250, 42251, 42252, + 42253] + + # peak enegy for U/In in mev + U_PEAK_ENERGIES = [36684, 20874, + 6672] # [36684, 20874, 6672] for uranium, [39681,22723,14599,12056,9088,3855] for indium + # U mass/ In mass in amu + U_MASS = 238.0289 # 113 for indium + + # misc global variables + # ---------------------------------------------------------------------------------------- + # full range of both the front and backscattering banks + FRONTSCATTERING_RANGE = [135, 198] + BACKSCATTERING_RANGE = [3, 134] + DETECTOR_RANGE = [BACKSCATTERING_RANGE[0], FRONTSCATTERING_RANGE[1]] + + # file loading modes + MODES = ["SingleDifference", "DoubleDifference", "ThickDifference", "FoilOut", "FoilIn", "FoilInOut"] + + # Different peak types that will be fit + PEAK_TYPES = ["Resonance", "Recoil", "Bragg"] + + # self._fit_window_range applies bith to fitting the resonances and the lead recoil peaks + # it is defined as the range left and right from the peak centre (i. e. the whole fitting window is twice the fitting range) + + BRAGG_PEAK_CROP_RANGE = (2000, 20000) + BRAGG_FIT_WINDOW_RANGE = 500 + BRAGG_PEAK_POSITION_TOLERANCE = 1000 + + RECOIL_PEAK_CROP_RANGE = (300, 500) + RECOIL_FIT_WINDOW_RANGE = 300 + + RESONANCE_PEAK_CROP_RANGE = (100, 350) + RESONANCE_FIT_WINDOW_RANGE = 50 + + PEAK_HEIGHT_RELATIVE_THRESHOLD = 0.25 + + # energy used to estimate peak position + ENERGY_ESTIMATE = 4897.3 + + # physical constants + # ---------------------------------------------------------------------------------------- + # convert to 1 / v and scale for fitting + U_NEUTRON_VELOCITY = np.array([83769.7, 63190.5, + 35725.4]) # np.array([83769.7, 63190.5, 35725.4]) # for U # np.array([87124.5,65929.8,52845.8,48023.1,41694.9,27155.7]) # for indium + U_NEUTRON_VELOCITY = 1.0 / U_NEUTRON_VELOCITY + U_NEUTRON_VELOCITY *= 1e+6 + + # mass of a neutron in amu + NEUTRON_MASS_AMU = scipy.constants.value("neutron mass in u") + # 1 meV in Joules + MEV_CONVERSION = 1.602176487e-22 + + +class EVSMiscFunctions: + + @staticmethod + def calculate_r_theta(sample_mass, thetas): + """ + Returns the ratio of the final neutron velocity to the initial neutron velocity + as a function of the scattering angle theta and atomic mass of lead and a neutron. + + @param sample_mass - mass of the sample in amu + @param thetas - vector containing the values of theta + @return the ratio of final and incident velocities + """ + rad_theta = np.radians(thetas) + r_theta = (np.cos(rad_theta) + np.sqrt((sample_mass / EVSGlobals.NEUTRON_MASS_AMU) ** 2 - np.sin(rad_theta) ** 2)) / ( + (sample_mass / EVSGlobals.NEUTRON_MASS_AMU) + 1) + + return r_theta + + @staticmethod + def identify_invalid_spectra(peak_table, peak_centres, peak_centres_errors, spec_list): + """ + Inspect fitting results, and identify the fits associated with invalid spectra. These are spectra associated with detectors + which have lost foil coverage following a recent reduction in distance from source to detectors. + + @param peak_table - name of table containing fitted parameters each spectra. + @param spec_list - spectrum range to inspect. + @return a list of invalid spectra. + """ + peak_Gaussian_FWHM = EVSMiscFunctions.read_fitting_result_table_column(peak_table, 'f1.GaussianFWHM', spec_list) + peak_Gaussian_FWHM_errors = EVSMiscFunctions.read_fitting_result_table_column(peak_table, 'f1.GaussianFWHM_Err', spec_list) + peak_Lorentz_FWHM = EVSMiscFunctions.read_fitting_result_table_column(peak_table, 'f1.LorentzFWHM', spec_list) + peak_Lorentz_FWHM_errors = EVSMiscFunctions.read_fitting_result_table_column(peak_table, 'f1.LorentzFWHM_Err', spec_list) + peak_Lorentz_Amp = EVSMiscFunctions.read_fitting_result_table_column(peak_table, 'f1.LorentzAmp', spec_list) + peak_Lorentz_Amp_errors = EVSMiscFunctions.read_fitting_result_table_column(peak_table, 'f1.LorentzAmp_Err', spec_list) + + invalid_spectra = np.argwhere((np.isinf(peak_Lorentz_Amp_errors)) | (np.isnan(peak_Lorentz_Amp_errors)) | \ + (np.isinf(peak_centres_errors)) | (np.isnan(peak_centres_errors)) | \ + (np.isnan(peak_Gaussian_FWHM_errors)) | (np.isinf(peak_Gaussian_FWHM_errors)) | \ + (np.isnan(peak_Lorentz_FWHM_errors)) | (np.isinf(peak_Lorentz_FWHM_errors)) | \ + (np.isnan(peak_Lorentz_Amp_errors)) | (np.isinf(peak_Lorentz_Amp_errors)) | \ + (np.absolute(peak_Gaussian_FWHM_errors) > np.absolute(peak_Gaussian_FWHM)) | \ + (np.absolute(peak_Lorentz_FWHM_errors) > np.absolute(peak_Lorentz_FWHM)) | \ + (np.absolute(peak_Lorentz_Amp_errors) > np.absolute(peak_Lorentz_Amp)) | \ + (np.absolute(peak_centres_errors) > np.absolute(peak_centres))) + + return invalid_spectra + + # The IP text file load function skips the first 3 rows of the text file. + # This assumes that there is one line for the file header and 2 lines for + # parameters of monitor 1 and monitor 2, respectively. + # One has to be careful here as some IP files have no header! + # In such a case one has to add header + # Otherwise, the algorithm will read the number of spectra to be fitted + # for the calibration of of L0, t0, L1, theta from the vectors: + # FRONTSCATTERING_RANGE, BACKSCATTERING_RANGE and DETECTOR_RANGE + # This number will be different from the number of fits performed + # (which will be given by the length of the variable table_name) + # The program will start running but will crash after fitting L0 and t0 + @staticmethod + def load_instrument_parameters(file_path, table_name): + """ + Load instrument parameters from file into a table workspace + + @param file_path - the location of the file on disk + @return the name of the table workspace. + """ + + file_data = np.loadtxt(file_path, skiprows=3, usecols=[0, 2, 3, 4, 5]) + + CreateEmptyTableWorkspace(OutputWorkspace=table_name) + table_ws = mtd[table_name] + + table_ws.addColumn('double', 'Spectrum') + table_ws.addColumn('double', 'theta') + table_ws.addColumn('double', 't0') + table_ws.addColumn('double', 'L0') + table_ws.addColumn('double', 'L1') + + for row in file_data: + table_ws.addRow(row.tolist()) + + return table_name + + @staticmethod + def read_table_column(table_name, column_name, spec_list=EVSGlobals.DETECTOR_RANGE): + """ + Read a column from a table workspace representing the instrument parameter file and return the data as an array. + + @param table_name - name of the table workspace + @param column_name - name of the column to select + @param spec_list - range of spectra to use + @return numpy array of values in the spec_list range + """ + + offset = EVSGlobals.DETECTOR_RANGE[0] + if len(spec_list) > 1: + lower, upper = spec_list + else: + lower = spec_list[0] + upper = spec_list[0] + + column_values = mtd[table_name].column(column_name) + + return np.array(column_values[lower - offset:upper + 1 - offset]) + + + @staticmethod + def read_fitting_result_table_column(table_name, column_name, spec_list): + """ + Read a column from a table workspace resulting from fitting and return the data as an array. + + @param table_name - name of the table workspace + @param column_name - name of the column to select + @param spec_list - range of spectra to use + @return numpy array of values in the spec_list range + """ + + offset = spec_list[0] + if len(spec_list) > 1: + lower, upper = spec_list + else: + lower = spec_list[0] + upper = spec_list[0] + + column_values = mtd[table_name].column(column_name) + + return np.array(column_values[lower - offset:upper + 1 - offset]) + + + @staticmethod + def generate_fit_function_header(function_type, error=False): + if function_type == 'Voigt': + error_str = "Err" if error else "" + func_header = {'Height': 'LorentzAmp', 'Position': 'LorentzPos', 'Width': 'LorentzFWHM', + 'Width_2': 'GaussianFWHM'} + elif function_type == 'Gaussian': + error_str = "_Err" if error else "" + func_header = {'Height': 'Height', 'Width': 'Sigma', 'Position': 'PeakCentre'} + else: + raise ValueError("Unsupported fit function type: %s" % function_type) + + return {k: v + error_str for k, v in func_header.items()} diff --git a/unpackaged/vesuvio_calibration/load_calibration_algorithms.py b/unpackaged/vesuvio_calibration/load_calibration_algorithms.py new file mode 100644 index 00000000..2abc5c21 --- /dev/null +++ b/unpackaged/vesuvio_calibration/load_calibration_algorithms.py @@ -0,0 +1,22 @@ +""" + This script imports and subscribes calibration algorithms for the VESUVIO spectrometer. + + Two Mantid algorithms are provided: EVSCalibrationFit and EVSCalibrationAnalysis. + + EVSCalibrationFit is used to fit m peaks to n spectra. The positions of the peaks are esitmated using the supplied instrument parameter + file and the d-spacings of the sample (if provided). Support is provided for both Voigt and Gaussian functions. + + EVSCalibrationAnalysis uses EVSCalibrationFit to calculate instrument parameters using the output of the fitting and the and an existing + instrument parameter file. + + The procedures used here are based upon those descibed in: Calibration of an electron volt neutron spectrometer, Nuclear Instruments and + Methods in Physics Research A (15 October 2010), doi:10.1016/j.nima.2010.09.079 by J. Mayers, M. A. Adams +""" + +from mantid.api import AlgorithmFactory +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_analysis import EVSCalibrationAnalysis + +if __name__ == "__main__": + AlgorithmFactory.subscribe(EVSCalibrationFit) + AlgorithmFactory.subscribe(EVSCalibrationAnalysis) diff --git a/unpackaged/vesuvio_calibration/tests/system/test_system.py b/unpackaged/vesuvio_calibration/tests/system/test_system.py deleted file mode 100644 index aa52bf06..00000000 --- a/unpackaged/vesuvio_calibration/tests/system/test_system.py +++ /dev/null @@ -1,660 +0,0 @@ -import unittest -import numpy as np -import scipy.constants -import scipy.stats - -from mantid.api import WorkspaceGroup -from mantid.simpleapi import * -from mock import patch -from unpackaged.vesuvio_calibration.tests.testhelpers.algorithms import create_algorithm -from unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5 import (calculate_r_theta, FRONTSCATTERING_RANGE, DETECTOR_RANGE, - BACKSCATTERING_RANGE, ENERGY_ESTIMATE, MEV_CONVERSION, - U_FRONTSCATTERING_SAMPLE, U_FRONTSCATTERING_BACKGROUND, - U_BACKSCATTERING_SAMPLE, U_BACKSCATTERING_BACKGROUND, - U_MASS, U_PEAK_ENERGIES) -from copy import copy - - -D_SPACINGS_COPPER = [2.0865, 1.807, 1.278, 1.0897] -D_SPACINGS_LEAD = [1.489, 1.750, 2.475, 2.858] -D_SPACINGS_NIOBIUM = [2.3356, 1.6515, 1.3484, 1.1678] - -MASS_COPPER = 63.546 -MASS_LEAD = 207.19 -MASS_NIOBIUM = 92.906 - -DEFAULT_RELATIVE_TOLERANCE = 0.1 -IGNORE_DETECTOR = 100 # Detectors to be ignored by the system test, as specified by the user -INVALID_DETECTOR = 101 # Detectors identified as invalid by the script - - -class EVSCalibrationTest(unittest.TestCase): - def load_ip_file(self): - param_names = ['spectrum', 'theta', 't0', 'L0', 'L1'] - file_data = np.loadtxt(self._parameter_file, skiprows=3, usecols=[0,2,3,4,5], unpack=True) - - params = {} - for name, column in zip(param_names, file_data): - params[name] = column - - return params - - def _setup_copper_test(self): - self._run_range = [17087, 17088] - self._background = [17086] - # Mass of copper in amu - self._mass = MASS_COPPER - # d-spacings of a copper sample - self._d_spacings = np.array(D_SPACINGS_COPPER) - self._d_spacings.sort() - self._energy_estimates = np.array(ENERGY_ESTIMATE) - - def _setup_lead_test(self): - self._run_range = [17083, 17084] - self._background = [17086] - # Mass of a lead in amu - self._mass = MASS_LEAD - # d-spacings of a lead sample - self._d_spacings = np.array(D_SPACINGS_LEAD) - self._d_spacings.sort() - self._energy_estimates = np.array(ENERGY_ESTIMATE) - - def _setup_niobium_test(self): - self._run_range = [17089, 17090, 17091] - self._background = [17086] - # Mass of a lead in amu - self._mass = MASS_NIOBIUM - # d-spacings of a lead sample - self._d_spacings = np.array(D_SPACINGS_NIOBIUM) - self._d_spacings.sort() - self._energy_estimates = np.array(ENERGY_ESTIMATE) - - def _setup_uranium_test(self): - self._function = 'Gaussian' - self._mass = U_MASS - self._d_spacings = [] - self._energy_estimates = np.array(U_PEAK_ENERGIES) - self._energy_estimates.sort() - - def _select_mode(self): - self._selected_mode = self._mode.pop(0) if len(self._mode) > 1 else self._mode[0] - - def _select_spec_range(self): - self._selected_spec_range = self._spec_range.pop(0) if len(self._spec_range) > 1 else self._spec_range[0] - - def _select_active_fits(self): - self._E1_fit_active = self._E1_fit.pop(0) if len(self._E1_fit) > 1 else self._E1_fit[0] - self._L0_fit_active = self._L0_fit.pop(0) if len(self._L0_fit) > 1 else self._L0_fit[0] - - def _load_file_side_effect(self, *args): - """ - Replaces the _load_file function in the calibration script, allowing locally stored test files to be loaded - rather than requiring access to the archive. - - The arguments to the load algorithms are specific for each run of the EVSCalibrationFit algorithm. As the - EVSCalibration algorithm consists of multiple calls to EVSCalibrationFit, and each call to EVSCalibrationFi - calls _load_file a differing number of times, the input to this side effect must vary for each call. - - This is handled through lists, whose first element is popped following each run of EVSCalibration fit. - """ - - sample_no = args[0] - output_name = args[1] - - if self._current_run is None or self._current_run >= self._total_runs(): - # set variables that vary in consecutive runs of EVSCalibrationFit - self._select_mode() - self._select_spec_range() - self._select_active_fits() - self._current_run = 1 - else: - self._current_run += 1 - - try: - self._load_file_vesuvio(sample_no, output_name) - except RuntimeError: - self._load_file_raw(sample_no, output_name) - print('Load Successful') - - def _total_runs(self): - """ - Calculates the total runs in the current call to EVSCalibrationFit. This varies depending upon the samples - specified, and whether the call is a fit to calculate either L0 or E1. - """ - - if self._L0_fit_active: - # First 2 runs use frontscattering, rest use backscattering. - if len(self._L0_fit) > 3: - run_range = U_FRONTSCATTERING_SAMPLE - background = U_FRONTSCATTERING_BACKGROUND - else: - run_range = U_BACKSCATTERING_SAMPLE - background = U_BACKSCATTERING_BACKGROUND - else: - run_range = self._run_range - background = self._background - - run_no = len(run_range) if self._E1_fit_active else len(run_range) + len(background) - return run_no - - def _background_run_range(self): - background = '' if self._E1_fit_active else self._background - return background - - def _get_d_spacings(self): - d_spacings = [] if (self._E1_fit_active or self._L0_fit_active) else self._d_spacings - return d_spacings - - def _load_file_vesuvio(self, sample_no, output_name): - print("Attempting LoadVesuvio") - test_directory = os.path.dirname(os.path.dirname(__file__)) - try: - prefix = 'EVS' - filename = str(os.path.join(test_directory, 'data', f'{prefix}{sample_no}.raw')) - LoadVesuvio(Filename=filename, Mode=self._selected_mode, OutputWorkspace=output_name, - SpectrumList="%d-%d" % (self._selected_spec_range[0], self._selected_spec_range[1]), - EnableLogging=False) - except RuntimeError: - prefix = 'VESUVIO000' - filename = str(os.path.join(test_directory, 'data', f'{prefix}{sample_no}.raw')) - LoadVesuvio(Filename=filename, Mode=self._selected_mode, OutputWorkspace=output_name, - SpectrumList="%d-%d" % (self._selected_spec_range[0], self._selected_spec_range[1]), - EnableLogging=False) - - def _load_file_raw(self, sample_no, output_name): - print("Attempting LoadRaw") - test_directory = os.path.dirname(os.path.dirname(__file__)) - try: - prefix = 'EVS' - filename = str(os.path.join(test_directory, 'data', f'{prefix}{sample_no}.raw')) - LoadRaw(filename, OutputWorkspace=output_name, SpectrumMin=self._selected_spec_range[0], - SpectrumMax=self._selected_spec_range[-1], EnableLogging=False) - except RuntimeError as err: - print(err) - prefix = 'VESUVIO000' - filename = str(os.path.join(test_directory, 'data', f'{prefix}{sample_no}.raw')) - LoadRaw(filename, OutputWorkspace=output_name, SpectrumMin=self._selected_spec_range[0], - SpectrumMax=self._selected_spec_range[-1], EnableLogging=False) - ConvertToDistribution(output_name, EnableLogging=False) - - def tearDown(self): - mtd.clear() - - -class TestEVSCalibrationAnalysis(EVSCalibrationTest): - - def setUp(self): - test_directory = os.path.dirname(os.path.dirname(__file__)) - self._parameter_file = os.path.join(test_directory, 'data', 'IP0005.par') - self._calibrated_params = self.load_ip_file() - self._iterations = 1 - self._alg = None - - # Switches set to none, activated if necessary in the load file side_effect - self._L0_fit_active = None - self._E1_fit_active = None - self._current_run = None - - # Lists in order of call of EVSCalibrationFit in the EVSCalibrationAnalysis function - self._mode = ['FoilOut', 'SingleDifference', 'SingleDifference'] - self._spec_range = [DETECTOR_RANGE, BACKSCATTERING_RANGE, FRONTSCATTERING_RANGE] - self._E1_fit = [False, True, True] - self._L0_fit = [False] - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_copper(self, load_file_mock): - self._setup_copper_test() - self._output_workspace = "copper_analysis_test" - - load_file_mock.side_effect = self._load_file_side_effect - - params_table = self.run_evs_calibration_analysis() - - # Specify detectors tolerances set by user, then update with those to mask as invalid. - detector_specific_r_tols = {"L1": {116: 0.65, 170: 0.75}} - detector_specific_r_tols["L1"].update({k: INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, - 165, 167, 168, 169, 170, 182, 191, 192]}) - self.assert_parameters_match_expected(params_table, detector_specific_r_tols) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_lead(self, load_file_mock): - self._setup_lead_test() - self._output_workspace = "lead_analysis_test" - - load_file_mock.side_effect = self._load_file_side_effect - - params_table = self.run_evs_calibration_analysis() - - # Specify detectors tolerances set by user, then update with those to mask as invalid. - detector_specific_r_tols = {"L1": {116: 0.25, 170: 0.70}, "Theta": {156: 0.19}} - detector_specific_r_tols["L1"].update({k: INVALID_DETECTOR for k in [137, 141, 143, 144, 145, 146, 161, 170, 171, - 178, 180, 182, 183]}) - self.assert_parameters_match_expected(params_table, detector_specific_r_tols) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_niobium(self, load_file_mock): - self._setup_niobium_test() - self._output_workspace = "niobium_analysis_test" - - load_file_mock.side_effect = self._load_file_side_effect - - params_table = self.run_evs_calibration_analysis() - - # Specify detectors tolerances set by user, then update with those to mask as invalid. - detector_specific_r_tols = {"L1": {116: 0.25, 170: IGNORE_DETECTOR, 171: IGNORE_DETECTOR}} - detector_specific_r_tols["L1"].update({k: INVALID_DETECTOR for k in [3, 41, 44, 48, 49, 65, 73, 89, 99, 100, 102, - 110, 114, 118, 123, 126, 131, 138, 141, 143, - 146, 147, 151, 154, 156, 157, 159, 160, 162, - 163, 166, 170, 171, 172, 178, 179, 180, 181, - 182, 186, 187, 189, 191]}) - self.assert_parameters_match_expected(params_table, detector_specific_r_tols) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_copper_with_uranium(self, load_file_mock): - self._setup_copper_test() - self._output_workspace = "copper_analysis_test" - - load_file_mock.side_effect = self._load_file_side_effect - - params_table = self.run_evs_calibration_analysis() - - # Specify detectors tolerances set by user, then update with those to mask as invalid. - detector_specific_r_tols = {"L1": {116: 0.25, 170: 0.75}} - detector_specific_r_tols["L1"].update({k: INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, - 165, 167, 168, 169, 170, 182, 191, 192]}) - self.assert_parameters_match_expected(params_table, detector_specific_r_tols) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_lead_with_uranium(self, load_file_mock): - self._setup_lead_test() - self._output_workspace = "lead_analysis_test" - - load_file_mock.side_effect = self._load_file_side_effect - - params_table = self.run_evs_calibration_analysis() - - # Specify detectors tolerances set by user, then update with those to mask as invalid. - detector_specific_r_tols = {"L1": {116: 0.25, 170: 0.70}, "Theta": {156: 0.19}} - detector_specific_r_tols["L1"].update({k: INVALID_DETECTOR for k in [137, 141, 143, 144, 145, 146, 161, 170, 171, - 178, 180, 182, 183]}) - self.assert_parameters_match_expected(params_table, detector_specific_r_tols) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_copper_with_l0_calc(self, load_file_mock): - self._setup_copper_test() - self._L0_fit = [True, True, True, False, False, False] - self._output_workspace = "copper_analysis_test" - self._mode = ['FoilOut', 'FoilOut', 'FoilOut', 'FoilOut', 'SingleDifference', 'SingleDifference'] - self._spec_range = [FRONTSCATTERING_RANGE, FRONTSCATTERING_RANGE, BACKSCATTERING_RANGE, DETECTOR_RANGE, - BACKSCATTERING_RANGE, FRONTSCATTERING_RANGE] - self._E1_fit = [False, False, False, False, True, True] - - load_file_mock.side_effect = self._load_file_side_effect - - params_table = self.run_evs_calibration_analysis() - - # Specify detectors tolerances set by user, then update with those to mask as invalid. - detector_specific_r_tols = {"L1": {116: 0.45, 170: 0.75, 171: 0.15, 178: 0.15}} - detector_specific_r_tols["L1"].update({k: INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, - 165, 167, 168, 169, 170, 182, 191, 192]}) - self.assert_parameters_match_expected(params_table, detector_specific_r_tols) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_copper_with_multiple_iterations(self, load_file_mock): - self._setup_copper_test() - self._iterations = 2 - self._output_workspace = "copper_analysis_test" - self._mode = self._mode + self._mode - self._spec_range = self._spec_range + self._spec_range - self._E1_fit = self._E1_fit + self._E1_fit - - load_file_mock.side_effect = self._load_file_side_effect - - params_table = self.run_evs_calibration_analysis() - - # Specify detectors tolerances set by user, then update with those to mask as invalid. - detector_specific_r_tols = {"L1": {116: 0.25, 170: IGNORE_DETECTOR}, "Theta": {156: 0.14, 158: 0.14, 167: 0.2, - 170: 0.5, 182: 0.3}} - detector_specific_r_tols["L1"].update({k: INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, - 165, 167, 168, 169, 170, 182, 191, 192]}) - self.assert_parameters_match_expected(params_table, detector_specific_r_tols) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_copper_create_output(self, load_file_mock): - self._setup_copper_test() - self._output_workspace = "copper_analysis_test" - - load_file_mock.side_effect = self._load_file_side_effect - - self.create_evs_calibration_alg() - self._alg.setProperty("CreateOutput", True) - params_table = self.execute_evs_calibration_analysis() - - # Specify detectors tolerances set by user, then update with those to mask as invalid. - detector_specific_r_tols = {"L1": {116: 0.65, 170: 0.75}} - detector_specific_r_tols["L1"].update({k: INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, - 165, 167, 168, 169, 170, 182, 191, 192]}) - self.assert_parameters_match_expected(params_table, detector_specific_r_tols) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_copper_with_individual_and_global_fit(self, load_file_mock): - self._setup_copper_test() - self._output_workspace = "copper_analysis_test" - - load_file_mock.side_effect = self._load_file_side_effect - - self.create_evs_calibration_alg() - self._alg.setProperty("SharedParameterFitType", "Both") - params_table = self.execute_evs_calibration_analysis() - - # Specify detectors tolerances set by user, then update with those to mask as invalid. - detector_specific_r_tols = {"L1": {116: 0.65, 170: 0.75}} - detector_specific_r_tols["L1"].update({k: INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, - 165, 167, 168, 169, 170, 182, 191, 192]}) - self.assert_parameters_match_expected(params_table, detector_specific_r_tols) - self.assert_E1_parameters_match_expected(params_table, 3e-3, "Both") - - def tearDown(self): - mtd.clear() - - #------------------------------------------------------------------ - # Misc helper functions - #------------------------------------------------------------------ - - def assert_theta_parameters_match_expected(self, params_table, rel_tolerance): - thetas = params_table.column('theta') - actual_thetas = self._calibrated_params['theta'] - - self.assertFalse(np.isnan(thetas).any()) - self.assertFalse(np.isinf(thetas).any()) - return _assert_allclose_excluding_bad_detectors(actual_thetas, thetas, rel_tolerance) - - def assert_L1_parameters_match_expected(self, params_table, rel_tolerance): - L1 = params_table.column('L1') - actual_L1 = self._calibrated_params['L1'] - - # Filter invalid detectors then mask - invalid_detectors = [(k, rel_tolerance.pop(k))[0] for k, v in copy(rel_tolerance).items() if v == INVALID_DETECTOR] - invalid_detector_mask = np.zeros(len(L1)) - for index in invalid_detectors: - invalid_detector_mask[index] = 1 - - self.assertFalse(np.isnan(np.ma.masked_array(L1, mask=invalid_detector_mask)).any()) - self.assertFalse(np.isinf(L1).any()) - return _assert_allclose_excluding_bad_detectors(np.ma.masked_array(actual_L1, mask=invalid_detector_mask), - np.ma.masked_array(L1, mask=invalid_detector_mask), rel_tolerance) - - def assert_E1_parameters_match_expected(self, params_table, rel_tolerance, fit_type): - if fit_type != "Shared": - E1 = params_table.column('E1')[0] - self.assertAlmostEqual(E1, ENERGY_ESTIMATE, delta=ENERGY_ESTIMATE*rel_tolerance) - - if fit_type != "Individual": - global_E1 = params_table.column('global_E1')[0] - self.assertAlmostEqual(global_E1, ENERGY_ESTIMATE, delta=ENERGY_ESTIMATE*rel_tolerance) - - def assert_parameters_match_expected(self, params_table, tolerances=None): - rel_tol_theta, rel_tol_L1 = self._extract_tolerances(tolerances) - theta_errors = self.assert_theta_parameters_match_expected(params_table, rel_tol_theta) - L1_errors = self.assert_L1_parameters_match_expected(params_table, rel_tol_L1) - - if theta_errors or L1_errors: - raise AssertionError(f"Theta: {theta_errors})\n L1: {L1_errors}") - - @staticmethod - def _extract_tolerances(tolerances: dict) -> (dict, dict): - theta_tol = {} - L1_tol = {} - if tolerances: - if "Theta" in tolerances: - theta_tol = tolerances["Theta"] - if INVALID_DETECTOR in theta_tol.values(): - raise ValueError('INVALID DETECTORS ONLY RELATE TO L1 TOLERANCES') - if "L1" in tolerances: - L1_tol = tolerances["L1"] - return theta_tol, L1_tol - - def create_evs_calibration_alg(self): - args = { - "OutputWorkspace": self._output_workspace, "Samples": self._run_range, "Background": self._background, - "InstrumentParameterFile": self._parameter_file, "Mass": self._mass, "DSpacings": self._d_spacings, - "Iterations": self._iterations, "CalculateL0": True in self._L0_fit - } - - self._alg = create_algorithm("EVSCalibrationAnalysis", **args) - - def execute_evs_calibration_analysis(self): - self._alg.execute() - last_fit_iteration = self._output_workspace + '_Iteration_%d' % (self._iterations-1) - return mtd[last_fit_iteration] - - def run_evs_calibration_analysis(self): - self.create_evs_calibration_alg() - return self.execute_evs_calibration_analysis() - - -class TestEVSCalibrationFit(EVSCalibrationTest): - - def setUp(self): - self._function = 'Voigt' - test_directory = os.path.dirname(os.path.dirname(__file__)) - self._parameter_file = os.path.join(test_directory, 'data', 'IP0005.par') - self._calibrated_params = self.load_ip_file() - self._energy_estimates = np.array([ENERGY_ESTIMATE]) - self._alg = None - - # Switches set to none, activated if necessary in the load file side_effect - self._L0_fit_active = None - self._E1_fit_active = None - self._current_run = None - - # Lists in order of call of EVSCalibrationFit in the EVSCalibrationAnalysis function - self._mode = ['FoilOut'] - self._spec_range = [FRONTSCATTERING_RANGE] - self._E1_fit = [False] - self._L0_fit = [False] - - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_fit_bragg_peaks_copper(self, load_file_mock): - self._setup_copper_test() - self._spec_range = [DETECTOR_RANGE] - self._output_workspace = "copper_bragg_peak_fit" - - load_file_mock.side_effect = self._load_file_side_effect - - expected_values = self.calculate_theta_peak_positions() - params_table = self.run_evs_calibration_fit("Bragg") - self.assert_fitted_positions_match_expected(expected_values, params_table, {15: IGNORE_DETECTOR}) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_fit_bragg_peaks_lead(self, load_file_mock): - self._setup_lead_test() - self._spec_range = [DETECTOR_RANGE] - self._output_workspace = "lead_bragg_peak_fit" - - load_file_mock.side_effect = self._load_file_side_effect - - expected_values = self.calculate_theta_peak_positions() - params_table = self.run_evs_calibration_fit("Bragg") - self.assert_fitted_positions_match_expected(expected_values, params_table, {145: 0.27, 158: 0.15, 190: IGNORE_DETECTOR}) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_fit_peaks_copper_E1(self, load_file_mock): - self._setup_copper_test() - self._E1_fit_active = True - self._E1_fit = [True] - self._output_workspace = "copper_peak_fit" - self._mode = ['SingleDifference'] - - load_file_mock.side_effect = self._load_file_side_effect - - expected_values = self.calculate_energy_peak_positions() - params_table = self.run_evs_calibration_fit("Recoil") - self.assert_fitted_positions_match_expected(expected_values, params_table, {38: 0.12}) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_fit_peaks_lead_E1(self, load_file_mock): - self._setup_lead_test() - self._E1_fit_active = True - self._E1_fit = [True] - self._output_workspace = "lead_peak_fit" - self._mode = ['SingleDifference'] - - load_file_mock.side_effect = self._load_file_side_effect - - expected_values = self.calculate_energy_peak_positions() - params_table = self.run_evs_calibration_fit("Recoil") - self.assert_fitted_positions_match_expected(expected_values, params_table, {38: 0.12}) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_fit_frontscattering_uranium(self, load_file_mock): - self._setup_uranium_test() - self._run_range = U_FRONTSCATTERING_SAMPLE - self._background = U_FRONTSCATTERING_BACKGROUND - self._spec_range = [FRONTSCATTERING_RANGE] - self._output_workspace = 'uranium_peak_fit_front' - - load_file_mock.side_effect = self._load_file_side_effect - - expected_values = self.calculate_energy_peak_positions() - params_table = self.run_evs_calibration_fit("Recoil") - self.assert_fitted_positions_match_expected(expected_values, params_table) - - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') - def test_fit_backscattering_uranium(self, load_file_mock): - self._setup_uranium_test() - self._run_range = U_BACKSCATTERING_SAMPLE - self._background = U_BACKSCATTERING_BACKGROUND - self._spec_range = [BACKSCATTERING_RANGE] - self._output_workspace = 'uranium_peak_fit_back' - - load_file_mock.side_effect = self._load_file_side_effect - - expected_values = self.calculate_energy_peak_positions() - params_table = self.run_evs_calibration_fit("Recoil") - self.assert_fitted_positions_match_expected(expected_values, params_table, default_rel_tol=0.01) - - #------------------------------------------------------------------ - # Misc Helper functions - #------------------------------------------------------------------ - - def assert_fitted_positions_match_expected(self, expected_positions, params_table, rel_tolerance=None, - default_rel_tol=DEFAULT_RELATIVE_TOLERANCE): - """ - Check that each of the fitted peak positions match the expected - positions in time of flight calculated from the parameter file - within a small tolerance. - """ - rel_tolerance = {} if not rel_tolerance else rel_tolerance - - if isinstance(params_table, WorkspaceGroup): - params_table = params_table.getNames()[0] - params_table = mtd[params_table] - - column_names = self.find_all_peak_positions(params_table) - - error_dict = {} - for name, expected_position in zip(column_names, expected_positions): - position = np.array(params_table.column(name)) - - self.assertFalse(np.isnan(position).any()) - self.assertFalse(np.isinf(position).any()) - error_list = _assert_allclose_excluding_bad_detectors(expected_position, position, rel_tolerance, default_rel_tol) - if error_list: - error_dict[name] = error_list - if error_dict: - raise AssertionError(error_dict) - - def create_evs_calibration_fit(self, peak_type): - args = { - "OutputWorkspace": self._output_workspace, "Samples": self._run_range, - "Background": self._background_run_range(), "InstrumentParameterFile": self._parameter_file, - "DSpacings": self._get_d_spacings(), "Energy": self._energy_estimates, "CreateOutput": False, - "Function": self._function, "Mode": self._mode[0], "SpectrumRange": self._spec_range[0], - "PeakType": peak_type - } - - self._alg = create_algorithm("EVSCalibrationFit", **args) - - def execute_evs_calibration_fit(self): - self._alg.execute() - return mtd[self._output_workspace + '_Peak_Parameters'] - - def run_evs_calibration_fit(self, peak_type): - self.create_evs_calibration_fit(peak_type) - return self.execute_evs_calibration_fit() - - @staticmethod - def find_all_peak_positions(params_table): - filter_errors_func = lambda name: ('LorentzPos' in name or 'PeakCentre' in name) and 'Err' not in name - column_names = params_table.getColumnNames() - column_names = filter(filter_errors_func, column_names) - return column_names - - def calculate_energy_peak_positions(self): - """ - Using the calibrated values to calculate the expected - position of the L0/L1/E1 peak in time of flight. - """ - lower, upper = self._spec_range[0][0] - DETECTOR_RANGE[0], self._spec_range[0][1] - DETECTOR_RANGE[0] +1 - - L0 = self._calibrated_params['L0'][lower:upper] - L1 = self._calibrated_params['L1'][lower:upper] - t0 = self._calibrated_params['t0'][lower:upper] - thetas = self._calibrated_params['theta'][lower:upper] - r_theta = calculate_r_theta(self._mass, thetas) - - t0 /= 1e+6 - - energy_estimates = np.copy(self._energy_estimates) - energy_estimates = energy_estimates.reshape(1, energy_estimates.size).T - energy_estimates = energy_estimates * MEV_CONVERSION - - v1 = np.sqrt(2 * energy_estimates / scipy.constants.m_n) - tof = ((L0 * r_theta + L1) / v1) + t0 - tof *= 1e+6 - - return tof - - def calculate_theta_peak_positions(self): - """ - Using the calibrated values of theta calculate the expected - peak position in time of flight. - """ - L0 = self._calibrated_params['L0'] - L1 = self._calibrated_params['L1'] - t0 = self._calibrated_params['t0'] - thetas = self._calibrated_params['theta'] - - t0 /= 1e+6 - - d_spacings = np.copy(self._d_spacings) - d_spacings *= 1e-10 - d_spacings = d_spacings.reshape(1, d_spacings.size).T - - lambdas = 2 * d_spacings * np.sin(np.radians(thetas) / 2) - tof = (lambdas * scipy.constants.m_n * (L0 + L1)) / scipy.constants.h + t0 - tof *= 1e+6 - - return tof - - -def _assert_allclose_excluding_bad_detectors(expected_position, position, rtol, default_rtol=DEFAULT_RELATIVE_TOLERANCE): - np.set_printoptions(threshold=sys.maxsize) - test_failures = [] - for i, (elem_m, elem_n) in enumerate(zip(expected_position, position)): - if np.ma.is_masked(elem_m) and np.ma.is_masked(elem_n): # detector masked - break - detector_specific_rtol = rtol[i] if i in rtol else default_rtol - try: - np.testing.assert_allclose(elem_m, elem_n, detector_specific_rtol, atol=0) - except AssertionError: - test_failures.append(f"Element {i}: Expected {elem_m}, found {elem_n}. atol " - f"{abs(elem_n-elem_m)}, rtol {abs(elem_n-elem_m)/elem_n}," - f"max tol: {detector_specific_rtol}") - return test_failures - - -if __name__ == '__main__': - unittest.main() diff --git a/unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py b/unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py new file mode 100644 index 00000000..59c0b88c --- /dev/null +++ b/unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py @@ -0,0 +1,272 @@ +import unittest +import numpy as np + +from mantid.api import AlgorithmFactory +from mantid.simpleapi import LoadVesuvio, LoadRaw, mtd, ConvertToDistribution +from mock import patch +from unpackaged.vesuvio_calibration.tests.testhelpers.algorithms import create_algorithm +from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_base import EVSCalibrationTest, TestConstants +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals +from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_misc_functions import assert_allclose_excluding_bad_detectors +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_analysis import EVSCalibrationAnalysis +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit +from copy import copy +from os import path + + +class TestEVSCalibrationAnalysis(EVSCalibrationTest): + + @classmethod + def setUpClass(cls): + # Register Algorithms + AlgorithmFactory.subscribe(EVSCalibrationFit) + AlgorithmFactory.subscribe(EVSCalibrationAnalysis) + + def setUp(self): + test_directory = path.dirname(path.dirname(__file__)) + self._parameter_file = path.join(test_directory, 'data', 'IP0005.par') + self._calibrated_params = self.load_ip_file() + self._iterations = 1 + self._alg = None + + # Switches set to none, activated if necessary in the load file side_effect + self._L0_fit_active = None + self._E1_fit_active = None + self._current_run = None + + # Lists in order of call of EVSCalibrationFit in the EVSCalibrationAnalysis function + self._mode = ['FoilOut', 'SingleDifference', 'SingleDifference'] + self._spec_range = [EVSGlobals.DETECTOR_RANGE, EVSGlobals.BACKSCATTERING_RANGE, EVSGlobals.FRONTSCATTERING_RANGE] + self._E1_fit = [False, True, True] + self._L0_fit = [False] + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_copper(self, load_file_mock): + self._setup_copper_test() + self._output_workspace = "copper_analysis_test" + + load_file_mock.side_effect = self._load_file_side_effect + + params_table = self._run_evs_calibration_analysis() + + # Specify detectors tolerances set by user, then update with those to mask as invalid. + detector_specific_r_tols = {"L1": {116: 0.65, 170: 0.75}} + detector_specific_r_tols["L1"].update({k: TestConstants.INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, + 165, 167, 168, 169, 170, 182, 191, 192]}) + self._assert_parameters_match_expected(params_table, detector_specific_r_tols) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_lead(self, load_file_mock): + self._setup_lead_test() + self._output_workspace = "lead_analysis_test" + + load_file_mock.side_effect = self._load_file_side_effect + + params_table = self._run_evs_calibration_analysis() + + # Specify detectors tolerances set by user, then update with those to mask as invalid. + detector_specific_r_tols = {"L1": {116: 0.25, 170: 0.70}, "Theta": {156: 0.19}} + detector_specific_r_tols["L1"].update({k: TestConstants.INVALID_DETECTOR for k in [137, 141, 143, 144, 145, 146, 161, 170, 171, + 178, 180, 182, 183]}) + self._assert_parameters_match_expected(params_table, detector_specific_r_tols) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_niobium(self, load_file_mock): + self._setup_niobium_test() + self._output_workspace = "niobium_analysis_test" + + load_file_mock.side_effect = self._load_file_side_effect + + params_table = self._run_evs_calibration_analysis() + + # Specify detectors tolerances set by user, then update with those to mask as invalid. + detector_specific_r_tols = {"L1": {116: 0.25, 170: TestConstants.IGNORE_DETECTOR, 171: TestConstants.IGNORE_DETECTOR}} + detector_specific_r_tols["L1"].update({k: TestConstants.INVALID_DETECTOR for k in [3, 41, 44, 48, 49, 65, 73, 89, 99, 100, 102, + 110, 114, 118, 123, 126, 131, 138, 141, 143, + 146, 147, 151, 154, 156, 157, 159, 160, 162, + 163, 166, 170, 171, 172, 178, 179, 180, 181, + 182, 186, 187, 189, 191]}) + self._assert_parameters_match_expected(params_table, detector_specific_r_tols) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_copper_with_uranium(self, load_file_mock): + self._setup_copper_test() + self._output_workspace = "copper_analysis_test" + + load_file_mock.side_effect = self._load_file_side_effect + + params_table = self._run_evs_calibration_analysis() + + # Specify detectors tolerances set by user, then update with those to mask as invalid. + detector_specific_r_tols = {"L1": {116: 0.25, 170: 0.75}} + detector_specific_r_tols["L1"].update({k: TestConstants.INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, + 165, 167, 168, 169, 170, 182, 191, 192]}) + self._assert_parameters_match_expected(params_table, detector_specific_r_tols) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_lead_with_uranium(self, load_file_mock): + self._setup_lead_test() + self._output_workspace = "lead_analysis_test" + + load_file_mock.side_effect = self._load_file_side_effect + + params_table = self._run_evs_calibration_analysis() + + # Specify detectors tolerances set by user, then update with those to mask as invalid. + detector_specific_r_tols = {"L1": {116: 0.25, 170: 0.70}, "Theta": {156: 0.19}} + detector_specific_r_tols["L1"].update({k: TestConstants.INVALID_DETECTOR for k in [137, 141, 143, 144, 145, 146, 161, 170, 171, + 178, 180, 182, 183]}) + self._assert_parameters_match_expected(params_table, detector_specific_r_tols) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_copper_with_l0_calc(self, load_file_mock): + self._setup_copper_test() + self._L0_fit = [True, True, True, False, False, False] + self._output_workspace = "copper_analysis_test" + self._mode = ['FoilOut', 'FoilOut', 'FoilOut', 'FoilOut', 'SingleDifference', 'SingleDifference'] + self._spec_range = [EVSGlobals.FRONTSCATTERING_RANGE, EVSGlobals.FRONTSCATTERING_RANGE, EVSGlobals.BACKSCATTERING_RANGE, + EVSGlobals.DETECTOR_RANGE, EVSGlobals.BACKSCATTERING_RANGE, EVSGlobals.FRONTSCATTERING_RANGE] + self._E1_fit = [False, False, False, False, True, True] + + load_file_mock.side_effect = self._load_file_side_effect + + params_table = self._run_evs_calibration_analysis() + + # Specify detectors tolerances set by user, then update with those to mask as invalid. + detector_specific_r_tols = {"L1": {116: 0.45, 170: 0.75, 171: 0.15, 178: 0.15}} + detector_specific_r_tols["L1"].update({k: TestConstants.INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, + 165, 167, 168, 169, 170, 182, 191, 192]}) + self._assert_parameters_match_expected(params_table, detector_specific_r_tols) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_copper_with_multiple_iterations(self, load_file_mock): + self._setup_copper_test() + self._iterations = 2 + self._output_workspace = "copper_analysis_test" + self._mode = self._mode + self._mode + self._spec_range = self._spec_range + self._spec_range + self._E1_fit = self._E1_fit + self._E1_fit + + load_file_mock.side_effect = self._load_file_side_effect + + params_table = self._run_evs_calibration_analysis() + + # Specify detectors tolerances set by user, then update with those to mask as invalid. + detector_specific_r_tols = {"L1": {116: 0.25, 170: TestConstants.IGNORE_DETECTOR}, "Theta": {156: 0.14, 158: 0.14, 167: 0.2, + 170: 0.5, 182: 0.3}} + detector_specific_r_tols["L1"].update({k: TestConstants.INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, + 165, 167, 168, 169, 170, 182, 191, 192]}) + self._assert_parameters_match_expected(params_table, detector_specific_r_tols) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_copper_create_output(self, load_file_mock): + self._setup_copper_test() + self._output_workspace = "copper_analysis_test" + + load_file_mock.side_effect = self._load_file_side_effect + + self._create_evs_calibration_alg() + self._alg.setProperty("CreateOutput", True) + params_table = self._execute_evs_calibration_analysis() + + # Specify detectors tolerances set by user, then update with those to mask as invalid. + detector_specific_r_tols = {"L1": {116: 0.65, 170: 0.75}} + detector_specific_r_tols["L1"].update({k: TestConstants.INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, + 165, 167, 168, 169, 170, 182, 191, 192]}) + self._assert_parameters_match_expected(params_table, detector_specific_r_tols) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_copper_with_individual_and_global_fit(self, load_file_mock): + self._setup_copper_test() + self._output_workspace = "copper_analysis_test" + + load_file_mock.side_effect = self._load_file_side_effect + + self._create_evs_calibration_alg() + self._alg.setProperty("SharedParameterFitType", "Both") + params_table = self._execute_evs_calibration_analysis() + + # Specify detectors tolerances set by user, then update with those to mask as invalid. + detector_specific_r_tols = {"L1": {116: 0.65, 170: 0.75}} + detector_specific_r_tols["L1"].update({k: TestConstants.INVALID_DETECTOR for k in [138, 141, 146, 147, 156, 158, 160, 163, 164, + 165, 167, 168, 169, 170, 182, 191, 192]}) + self._assert_parameters_match_expected(params_table, detector_specific_r_tols) + self._assert_E1_parameters_match_expected(params_table, 3e-3, "Both") + + def _assert_theta_parameters_match_expected(self, params_table, rel_tolerance): + thetas = params_table.column('theta') + actual_thetas = self._calibrated_params['theta'] + + self.assertFalse(np.isnan(thetas).any()) + self.assertFalse(np.isinf(thetas).any()) + return assert_allclose_excluding_bad_detectors(actual_thetas, thetas, rel_tolerance) + + def _assert_L1_parameters_match_expected(self, params_table, rel_tolerance): + L1 = params_table.column('L1') + actual_L1 = self._calibrated_params['L1'] + + # Filter invalid detectors then mask + invalid_detectors = [(k, rel_tolerance.pop(k))[0] for k, v in copy(rel_tolerance).items() if v == TestConstants.INVALID_DETECTOR] + invalid_detector_mask = np.zeros(len(L1)) + for index in invalid_detectors: + invalid_detector_mask[index] = 1 + + self.assertFalse(np.isnan(np.ma.masked_array(L1, mask=invalid_detector_mask)).any()) + self.assertFalse(np.isinf(L1).any()) + return assert_allclose_excluding_bad_detectors(np.ma.masked_array(actual_L1, mask=invalid_detector_mask), + np.ma.masked_array(L1, mask=invalid_detector_mask), rel_tolerance) + + def _assert_E1_parameters_match_expected(self, params_table, rel_tolerance, fit_type): + if fit_type != "Shared": + E1 = params_table.column('E1')[0] + self.assertAlmostEqual(E1, EVSGlobals.ENERGY_ESTIMATE, delta=EVSGlobals.ENERGY_ESTIMATE*rel_tolerance) + + if fit_type != "Individual": + global_E1 = params_table.column('global_E1')[0] + self.assertAlmostEqual(global_E1, EVSGlobals.ENERGY_ESTIMATE, delta=EVSGlobals.ENERGY_ESTIMATE*rel_tolerance) + + def _assert_parameters_match_expected(self, params_table, tolerances=None): + rel_tol_theta, rel_tol_L1 = self._extract_tolerances(tolerances) + theta_errors = self._assert_theta_parameters_match_expected(params_table, rel_tol_theta) + L1_errors = self._assert_L1_parameters_match_expected(params_table, rel_tol_L1) + + if theta_errors or L1_errors: + raise AssertionError(f"Theta: {theta_errors})\n L1: {L1_errors}") + + @staticmethod + def _extract_tolerances(tolerances: dict) -> (dict, dict): + theta_tol = {} + L1_tol = {} + if tolerances: + if "Theta" in tolerances: + theta_tol = tolerances["Theta"] + if TestConstants.INVALID_DETECTOR in theta_tol.values(): + raise ValueError('INVALID DETECTORS ONLY RELATE TO L1 TOLERANCES') + if "L1" in tolerances: + L1_tol = tolerances["L1"] + return theta_tol, L1_tol + + def _create_evs_calibration_alg(self): + args = { + "OutputWorkspace": self._output_workspace, "Samples": self._run_range, "Background": self._background, + "InstrumentParameterFile": self._parameter_file, "Mass": self._mass, "DSpacings": self._d_spacings, + "Iterations": self._iterations, "CalculateL0": True in self._L0_fit + } + + self._alg = create_algorithm("EVSCalibrationAnalysis", **args) + + def _execute_evs_calibration_analysis(self): + self._alg.execute() + last_fit_iteration = self._output_workspace + '_Iteration_%d' % (self._iterations-1) + return mtd[last_fit_iteration] + + def _run_evs_calibration_analysis(self): + self._create_evs_calibration_alg() + return self._execute_evs_calibration_analysis() + + def tearDown(self): + mtd.clear() + + +if __name__ == '__main__': + unittest.main() diff --git a/unpackaged/vesuvio_calibration/tests/system/test_system_fit.py b/unpackaged/vesuvio_calibration/tests/system/test_system_fit.py new file mode 100644 index 00000000..7e0648c9 --- /dev/null +++ b/unpackaged/vesuvio_calibration/tests/system/test_system_fit.py @@ -0,0 +1,226 @@ +import unittest +import numpy as np +import scipy.constants +import scipy.stats + +from mantid.api import WorkspaceGroup, AlgorithmFactory +from mantid.simpleapi import LoadVesuvio, LoadRaw, mtd, ConvertToDistribution +from mock import patch +from unpackaged.vesuvio_calibration.tests.testhelpers.algorithms import create_algorithm +from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_base import EVSCalibrationTest, TestConstants +from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_misc_functions import assert_allclose_excluding_bad_detectors +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSMiscFunctions, EVSGlobals +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit +from os import path + + +class TestEVSCalibrationFit(EVSCalibrationTest): + + @classmethod + def setUpClass(cls): + # Register Algorithm + AlgorithmFactory.subscribe(EVSCalibrationFit) + + def setUp(self): + self._function = 'Voigt' + test_directory = path.dirname(path.dirname(__file__)) + self._parameter_file = path.join(test_directory, 'data', 'IP0005.par') + self._calibrated_params = self.load_ip_file() + self._energy_estimates = np.array([EVSGlobals.ENERGY_ESTIMATE]) + self._alg = None + + # Switches set to none, activated if necessary in the load file side_effect + self._L0_fit_active = None + self._E1_fit_active = None + self._current_run = None + + # Lists in order of call of EVSCalibrationFit in the EVSCalibrationAnalysis function + self._mode = ['FoilOut'] + self._spec_range = [EVSGlobals.FRONTSCATTERING_RANGE] + self._E1_fit = [False] + self._L0_fit = [False] + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_fit_bragg_peaks_copper(self, load_file_mock): + self._setup_copper_test() + self._spec_range = [EVSGlobals.DETECTOR_RANGE] + self._output_workspace = "copper_bragg_peak_fit" + + load_file_mock.side_effect = self._load_file_side_effect + + expected_values = self._calculate_theta_peak_positions() + params_table = self._run_evs_calibration_fit("Bragg") + self._assert_fitted_positions_match_expected(expected_values, params_table, {15: TestConstants.IGNORE_DETECTOR}) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_fit_bragg_peaks_lead(self, load_file_mock): + self._setup_lead_test() + self._spec_range = [EVSGlobals.DETECTOR_RANGE] + self._output_workspace = "lead_bragg_peak_fit" + + load_file_mock.side_effect = self._load_file_side_effect + + expected_values = self._calculate_theta_peak_positions() + params_table = self._run_evs_calibration_fit("Bragg") + self._assert_fitted_positions_match_expected(expected_values, params_table, {145: 0.27, 158: 0.15, 190: + TestConstants.IGNORE_DETECTOR}) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_fit_peaks_copper_E1(self, load_file_mock): + self._setup_copper_test() + self._E1_fit_active = True + self._E1_fit = [True] + self._output_workspace = "copper_peak_fit" + self._mode = ['SingleDifference'] + + load_file_mock.side_effect = self._load_file_side_effect + + expected_values = self._calculate_energy_peak_positions() + params_table = self._run_evs_calibration_fit("Recoil") + self._assert_fitted_positions_match_expected(expected_values, params_table, {38: 0.12}) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_fit_peaks_lead_E1(self, load_file_mock): + self._setup_lead_test() + self._E1_fit_active = True + self._E1_fit = [True] + self._output_workspace = "lead_peak_fit" + self._mode = ['SingleDifference'] + + load_file_mock.side_effect = self._load_file_side_effect + + expected_values = self._calculate_energy_peak_positions() + params_table = self._run_evs_calibration_fit("Recoil") + self._assert_fitted_positions_match_expected(expected_values, params_table, {38: 0.12}) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_fit_frontscattering_uranium(self, load_file_mock): + self._setup_uranium_test() + self._run_range = EVSGlobals.U_FRONTSCATTERING_SAMPLE + self._background = EVSGlobals.U_FRONTSCATTERING_BACKGROUND + self._spec_range = [EVSGlobals.FRONTSCATTERING_RANGE] + self._output_workspace = 'uranium_peak_fit_front' + + load_file_mock.side_effect = self._load_file_side_effect + + expected_values = self._calculate_energy_peak_positions() + params_table = self._run_evs_calibration_fit("Recoil") + self._assert_fitted_positions_match_expected(expected_values, params_table) + + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + def test_fit_backscattering_uranium(self, load_file_mock): + self._setup_uranium_test() + self._run_range = EVSGlobals.U_BACKSCATTERING_SAMPLE + self._background = EVSGlobals.U_BACKSCATTERING_BACKGROUND + self._spec_range = [EVSGlobals.BACKSCATTERING_RANGE] + self._output_workspace = 'uranium_peak_fit_back' + + load_file_mock.side_effect = self._load_file_side_effect + + expected_values = self._calculate_energy_peak_positions() + params_table = self._run_evs_calibration_fit("Recoil") + self._assert_fitted_positions_match_expected(expected_values, params_table, default_rel_tol=0.01) + + def _assert_fitted_positions_match_expected(self, expected_positions, params_table, rel_tolerance=None, + default_rel_tol=TestConstants.DEFAULT_RELATIVE_TOLERANCE): + """ + Check that each of the fitted peak positions match the expected + positions in time of flight calculated from the parameter file + within a small tolerance. + """ + rel_tolerance = {} if not rel_tolerance else rel_tolerance + + if isinstance(params_table, WorkspaceGroup): + params_table = params_table.getNames()[0] + params_table = mtd[params_table] + + column_names = self._find_all_peak_positions(params_table) + + error_dict = {} + for name, expected_position in zip(column_names, expected_positions): + position = np.array(params_table.column(name)) + + self.assertFalse(np.isnan(position).any()) + self.assertFalse(np.isinf(position).any()) + error_list = assert_allclose_excluding_bad_detectors(expected_position, position, rel_tolerance, default_rel_tol) + if error_list: + error_dict[name] = error_list + if error_dict: + raise AssertionError(error_dict) + + def _create_evs_calibration_fit(self, peak_type): + args = { + "OutputWorkspace": self._output_workspace, "Samples": self._run_range, + "Background": self._background_run_range(), "InstrumentParameterFile": self._parameter_file, + "DSpacings": self._get_d_spacings(), "Energy": self._energy_estimates, "CreateOutput": False, + "Function": self._function, "Mode": self._mode[0], "SpectrumRange": self._spec_range[0], + "PeakType": peak_type + } + + self._alg = create_algorithm("EVSCalibrationFit", **args) + + def _execute_evs_calibration_fit(self): + self._alg.execute() + return mtd[self._output_workspace + '_Peak_Parameters'] + + def _run_evs_calibration_fit(self, peak_type): + self._create_evs_calibration_fit(peak_type) + return self._execute_evs_calibration_fit() + + @staticmethod + def _find_all_peak_positions(params_table): + filter_errors_func = lambda name: ('LorentzPos' in name or 'PeakCentre' in name) and 'Err' not in name + column_names = params_table.getColumnNames() + column_names = filter(filter_errors_func, column_names) + return column_names + + def _calculate_energy_peak_positions(self): + """ + Using the calibrated values to calculate the expected + position of the L0/L1/E1 peak in time of flight. + """ + lower, upper = self._spec_range[0][0] - EVSGlobals.DETECTOR_RANGE[0], self._spec_range[0][1] - EVSGlobals.DETECTOR_RANGE[0] +1 + + L0 = self._calibrated_params['L0'][lower:upper] + L1 = self._calibrated_params['L1'][lower:upper] + t0 = self._calibrated_params['t0'][lower:upper] + thetas = self._calibrated_params['theta'][lower:upper] + r_theta = EVSMiscFunctions.calculate_r_theta(self._mass, thetas) + + t0 /= 1e+6 + + energy_estimates = np.copy(self._energy_estimates) + energy_estimates = energy_estimates.reshape(1, energy_estimates.size).T + energy_estimates = energy_estimates * EVSGlobals.MEV_CONVERSION + + v1 = np.sqrt(2 * energy_estimates / scipy.constants.m_n) + tof = ((L0 * r_theta + L1) / v1) + t0 + tof *= 1e+6 + + return tof + + def _calculate_theta_peak_positions(self): + """ + Using the calibrated values of theta calculate the expected + peak position in time of flight. + """ + L0 = self._calibrated_params['L0'] + L1 = self._calibrated_params['L1'] + t0 = self._calibrated_params['t0'] + thetas = self._calibrated_params['theta'] + + t0 /= 1e+6 + + d_spacings = np.copy(self._d_spacings) + d_spacings *= 1e-10 + d_spacings = d_spacings.reshape(1, d_spacings.size).T + + lambdas = 2 * d_spacings * np.sin(np.radians(thetas) / 2) + tof = (lambdas * scipy.constants.m_n * (L0 + L1)) / scipy.constants.h + t0 + tof *= 1e+6 + + return tof + + +if __name__ == '__main__': + unittest.main() diff --git a/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_base.py b/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_base.py new file mode 100644 index 00000000..3bab0fe9 --- /dev/null +++ b/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_base.py @@ -0,0 +1,172 @@ +import unittest +import numpy as np +from mantid.simpleapi import LoadVesuvio, LoadRaw, mtd, ConvertToDistribution +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals +from os import path + + +class TestConstants: + D_SPACINGS_COPPER = [2.0865, 1.807, 1.278, 1.0897] + D_SPACINGS_LEAD = [1.489, 1.750, 2.475, 2.858] + D_SPACINGS_NIOBIUM = [2.3356, 1.6515, 1.3484, 1.1678] + + MASS_COPPER = 63.546 + MASS_LEAD = 207.19 + MASS_NIOBIUM = 92.906 + + DEFAULT_RELATIVE_TOLERANCE = 0.1 + IGNORE_DETECTOR = 100 # Detectors to be ignored by the system test, as specified by the user + INVALID_DETECTOR = 101 # Detectors identified as invalid by the script + + +class EVSCalibrationTest(unittest.TestCase): + def load_ip_file(self): + param_names = ['spectrum', 'theta', 't0', 'L0', 'L1'] + file_data = np.loadtxt(self._parameter_file, skiprows=3, usecols=[0,2,3,4,5], unpack=True) + + params = {} + for name, column in zip(param_names, file_data): + params[name] = column + + return params + + def _setup_copper_test(self): + self._run_range = [17087, 17088] + self._background = [17086] + # Mass of copper in amu + self._mass = TestConstants.MASS_COPPER + # d-spacings of a copper sample + self._d_spacings = np.array(TestConstants.D_SPACINGS_COPPER) + self._d_spacings.sort() + self._energy_estimates = np.array(EVSGlobals.ENERGY_ESTIMATE) + + def _setup_lead_test(self): + self._run_range = [17083, 17084] + self._background = [17086] + # Mass of a lead in amu + self._mass = TestConstants.MASS_LEAD + # d-spacings of a lead sample + self._d_spacings = np.array(TestConstants.D_SPACINGS_LEAD) + self._d_spacings.sort() + self._energy_estimates = np.array(EVSGlobals.ENERGY_ESTIMATE) + + def _setup_niobium_test(self): + self._run_range = [17089, 17090, 17091] + self._background = [17086] + # Mass of a lead in amu + self._mass = TestConstants.MASS_NIOBIUM + # d-spacings of a lead sample + self._d_spacings = np.array(TestConstants.D_SPACINGS_NIOBIUM) + self._d_spacings.sort() + self._energy_estimates = np.array(EVSGlobals.ENERGY_ESTIMATE) + + def _setup_uranium_test(self): + self._function = 'Gaussian' + self._mass = EVSGlobals.U_MASS + self._d_spacings = [] + self._energy_estimates = np.array(EVSGlobals.U_PEAK_ENERGIES) + self._energy_estimates.sort() + + def _select_mode(self): + self._selected_mode = self._mode.pop(0) if len(self._mode) > 1 else self._mode[0] + + def _select_spec_range(self): + self._selected_spec_range = self._spec_range.pop(0) if len(self._spec_range) > 1 else self._spec_range[0] + + def _select_active_fits(self): + self._E1_fit_active = self._E1_fit.pop(0) if len(self._E1_fit) > 1 else self._E1_fit[0] + self._L0_fit_active = self._L0_fit.pop(0) if len(self._L0_fit) > 1 else self._L0_fit[0] + + def _load_file_side_effect(self, *args): + """ + Replaces the _load_file function in the calibration script, allowing locally stored test files to be loaded + rather than requiring access to the archive. + + The arguments to the load algorithms are specific for each run of the EVSCalibrationFit algorithm. As the + EVSCalibration algorithm consists of multiple calls to EVSCalibrationFit, and each call to EVSCalibrationFi + calls _load_file a differing number of times, the input to this side effect must vary for each call. + + This is handled through lists, whose first element is popped following each run of EVSCalibration fit. + """ + + sample_no = args[0] + output_name = args[1] + + if self._current_run is None or self._current_run >= self._total_runs(): + # set variables that vary in consecutive runs of EVSCalibrationFit + self._select_mode() + self._select_spec_range() + self._select_active_fits() + self._current_run = 1 + else: + self._current_run += 1 + + try: + self._load_file_vesuvio(sample_no, output_name) + except RuntimeError: + self._load_file_raw(sample_no, output_name) + print('Load Successful') + + def _total_runs(self): + """ + Calculates the total runs in the current call to EVSCalibrationFit. This varies depending upon the samples + specified, and whether the call is a fit to calculate either L0 or E1. + """ + + if self._L0_fit_active: + # First 2 runs use frontscattering, rest use backscattering. + if len(self._L0_fit) > 3: + run_range = EVSGlobals.U_FRONTSCATTERING_SAMPLE + background = EVSGlobals.U_FRONTSCATTERING_BACKGROUND + else: + run_range = EVSGlobals.U_BACKSCATTERING_SAMPLE + background = EVSGlobals.U_BACKSCATTERING_BACKGROUND + else: + run_range = self._run_range + background = self._background + + run_no = len(run_range) if self._E1_fit_active else len(run_range) + len(background) + return run_no + + def _background_run_range(self): + background = '' if self._E1_fit_active else self._background + return background + + def _get_d_spacings(self): + d_spacings = [] if (self._E1_fit_active or self._L0_fit_active) else self._d_spacings + return d_spacings + + def _load_file_vesuvio(self, sample_no, output_name): + print("Attempting LoadVesuvio") + test_directory = path.dirname(path.dirname(__file__)) + try: + prefix = 'EVS' + filename = str(path.join(test_directory, 'data', f'{prefix}{sample_no}.raw')) + LoadVesuvio(Filename=filename, Mode=self._selected_mode, OutputWorkspace=output_name, + SpectrumList="%d-%d" % (self._selected_spec_range[0], self._selected_spec_range[1]), + EnableLogging=False) + except RuntimeError: + prefix = 'VESUVIO000' + filename = str(path.join(test_directory, 'data', f'{prefix}{sample_no}.raw')) + LoadVesuvio(Filename=filename, Mode=self._selected_mode, OutputWorkspace=output_name, + SpectrumList="%d-%d" % (self._selected_spec_range[0], self._selected_spec_range[1]), + EnableLogging=False) + + def _load_file_raw(self, sample_no, output_name): + print("Attempting LoadRaw") + test_directory = path.dirname(path.dirname(__file__)) + try: + prefix = 'EVS' + filename = str(path.join(test_directory, 'data', f'{prefix}{sample_no}.raw')) + LoadRaw(filename, OutputWorkspace=output_name, SpectrumMin=self._selected_spec_range[0], + SpectrumMax=self._selected_spec_range[-1], EnableLogging=False) + except RuntimeError as err: + print(err) + prefix = 'VESUVIO000' + filename = str(path.join(test_directory, 'data', f'{prefix}{sample_no}.raw')) + LoadRaw(filename, OutputWorkspace=output_name, SpectrumMin=self._selected_spec_range[0], + SpectrumMax=self._selected_spec_range[-1], EnableLogging=False) + ConvertToDistribution(output_name, EnableLogging=False) + + def tearDown(self): + mtd.clear() diff --git a/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_misc_functions.py b/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_misc_functions.py new file mode 100644 index 00000000..d507f4c2 --- /dev/null +++ b/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_misc_functions.py @@ -0,0 +1,19 @@ +import numpy as np +from sys import maxsize +from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_base import TestConstants + + +def assert_allclose_excluding_bad_detectors(expected_position, position, rtol, default_rtol=TestConstants.DEFAULT_RELATIVE_TOLERANCE): + np.set_printoptions(threshold=maxsize) + test_failures = [] + for i, (elem_m, elem_n) in enumerate(zip(expected_position, position)): + if np.ma.is_masked(elem_m) and np.ma.is_masked(elem_n): # detector masked + break + detector_specific_rtol = rtol[i] if i in rtol else default_rtol + try: + np.testing.assert_allclose(elem_m, elem_n, detector_specific_rtol, atol=0) + except AssertionError: + test_failures.append(f"Element {i}: Expected {elem_m}, found {elem_n}. atol " + f"{abs(elem_n-elem_m)}, rtol {abs(elem_n-elem_m)/elem_n}," + f"max tol: {detector_specific_rtol}") + return test_failures diff --git a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_analysis.py b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_analysis.py index eca5ce17..14aadddd 100644 --- a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_analysis.py +++ b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_analysis.py @@ -1,4 +1,4 @@ -from unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5 import EVSCalibrationAnalysis +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_analysis import EVSCalibrationAnalysis import unittest diff --git a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_fit.py b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_fit.py index cc3e0921..97c299a5 100644 --- a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_fit.py +++ b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_fit.py @@ -1,9 +1,6 @@ -import sys - -from unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5 import EVSCalibrationFit, DETECTOR_RANGE, \ - ENERGY_ESTIMATE, BRAGG_PEAK_CROP_RANGE, BRAGG_FIT_WINDOW_RANGE, RECOIL_PEAK_CROP_RANGE, RECOIL_FIT_WINDOW_RANGE, \ - RESONANCE_PEAK_CROP_RANGE, RESONANCE_FIT_WINDOW_RANGE, PEAK_HEIGHT_RELATIVE_THRESHOLD -from mock import MagicMock, patch, call, ANY +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals +from mock import MagicMock, patch, call from functools import partial from mantid.kernel import IntArrayProperty, StringArrayProperty, FloatArrayProperty @@ -33,9 +30,9 @@ def side_effect_set_cell(self, arg1, arg2, value): def side_effect_cell(row_index, col_index, peaks): return peaks[row_index] - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_perfect_match(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -67,9 +64,9 @@ def test_filter_peaks_perfect_match(self, mock_mtd, mock_clone_workspace, mock_d mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_no_match(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -99,9 +96,9 @@ def test_filter_peaks_no_match(self, mock_mtd, mock_clone_workspace, mock_del_wo OutputWorkspace=find_peaks_output_name + '_unfiltered') mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_one_match(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -132,9 +129,9 @@ def test_filter_peaks_one_match(self, mock_mtd, mock_clone_workspace, mock_del_w OutputWorkspace=find_peaks_output_name + '_unfiltered') mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_two_match(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -166,9 +163,9 @@ def test_filter_peaks_two_match(self, mock_mtd, mock_clone_workspace, mock_del_w mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_does_not_include_higher_found_peak(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -199,9 +196,9 @@ def test_filter_peaks_does_not_include_higher_found_peak(self, mock_mtd, mock_cl OutputWorkspace=find_peaks_output_name + '_unfiltered') mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_does_not_include_lower_found_peak(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -233,9 +230,9 @@ def test_filter_peaks_does_not_include_lower_found_peak(self, mock_mtd, mock_clo mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_handles_multiple_peaks(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -266,9 +263,9 @@ def test_filter_peaks_handles_multiple_peaks(self, mock_mtd, mock_clone_workspac mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') #Found peaks sometimes returns 'zero' peaks, usually at the end of the table workspace. - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_handles_zero_position_in_found_peaks(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -319,7 +316,7 @@ def side_effect(arg1, arg2): np.testing.assert_almost_equal([9629.84, 13619.43, 15727.03], estimated_positions.flatten().tolist(), 0.01) print(estimated_positions) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_check_nans_false(self, mock_mtd): alg = EVSCalibrationFit() table_ws = 'table_ws' @@ -334,7 +331,7 @@ def test_check_nans_false(self, mock_mtd): self.assertFalse(alg._check_nans(table_ws)) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_check_nans_true(self, mock_mtd): alg = EVSCalibrationFit() table_ws = 'table_ws' @@ -352,9 +349,10 @@ def test_check_nans_true(self, mock_mtd): def test_PyInit_property_defaults(self): alg = EVSCalibrationFit() alg.PyInit() - properties = {'Samples': [], 'Background': [], 'Mode': 'FoilOut', 'Function': 'Gaussian', 'SpectrumRange': DETECTOR_RANGE, - 'Mass': 207.19, 'DSpacings': [], 'Energy': [ENERGY_ESTIMATE], 'InstrumentParameterFile': '', - 'PeakType': '', 'InstrumentParameterWorkspace': None, 'CreateOutput': False, 'OutputWorkspace': ''} + properties = {'Samples': [], 'Background': [], 'Mode': 'FoilOut', 'Function': 'Gaussian', + 'SpectrumRange': EVSGlobals.DETECTOR_RANGE, 'Mass': 207.19, 'DSpacings': [], 'Energy': [EVSGlobals.ENERGY_ESTIMATE], + 'InstrumentParameterFile': '', 'PeakType': '', 'InstrumentParameterWorkspace': None, 'CreateOutput': False, + 'OutputWorkspace': ''} for prop in properties: expected_value = alg.getProperty(prop).value if type(expected_value) == np.ndarray: @@ -362,12 +360,12 @@ def test_PyInit_property_defaults(self): self.assertEqual(expected_value, properties[prop], f'Property {prop}. Expected: {expected_value},' f'Actual: {properties[prop]}') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._setup_spectra_list') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._setup_run_numbers_and_output_workspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._setup_function_type') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._setup_parameter_workspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._setup_peaks_and_set_crop_and_fit_ranges') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._setup_class_variables_from_properties') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_spectra_list') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_run_numbers_and_output_workspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_function_type') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_parameter_workspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_peaks_and_set_crop_and_fit_ranges') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_class_variables_from_properties') def test_setup_calls_all_functions(self, mock_setup_vars, mock_setup_peaks, mock_setup_param_ws, mock_setup_fn_type, mock_setup_run_nos, mock_setup_spec): alg = EVSCalibrationFit() @@ -482,7 +480,7 @@ def test_setup_parameter_workspace(self): self.assertEqual('test_ws', alg._param_workspace) self.assertEqual('test_ws', alg._param_table) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.load_instrument_parameters') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSMiscFunctions.load_instrument_parameters') def test_setup_parameter_workspace_no_ws(self, mock_load_instrument_parameters): alg = EVSCalibrationFit() alg.declareProperty('InstrumentParameterWorkspace', '') @@ -498,8 +496,8 @@ def test_setup_peaks_and_set_crop_and_fit_ranges_bragg(self): alg.declareProperty('PeakType', 'Bragg') alg._setup_peaks_and_set_crop_and_fit_ranges() self.assertEqual(sorted(test_d_spacings), list(alg._d_spacings)) - self.assertEqual(BRAGG_PEAK_CROP_RANGE, alg._ws_crop_range) - self.assertEqual(BRAGG_FIT_WINDOW_RANGE, alg._fit_window_range) + self.assertEqual(EVSGlobals.BRAGG_PEAK_CROP_RANGE, alg._ws_crop_range) + self.assertEqual(EVSGlobals.BRAGG_FIT_WINDOW_RANGE, alg._fit_window_range) def test_setup_peaks_and_set_crop_and_fit_ranges_recoil(self): test_d_spacings = [] @@ -507,8 +505,8 @@ def test_setup_peaks_and_set_crop_and_fit_ranges_recoil(self): alg.declareProperty(FloatArrayProperty('DSpacings', test_d_spacings)) alg.declareProperty('PeakType', 'Recoil') alg._setup_peaks_and_set_crop_and_fit_ranges() - self.assertEqual(RECOIL_PEAK_CROP_RANGE, alg._ws_crop_range) - self.assertEqual(RECOIL_FIT_WINDOW_RANGE, alg._fit_window_range) + self.assertEqual(EVSGlobals.RECOIL_PEAK_CROP_RANGE, alg._ws_crop_range) + self.assertEqual(EVSGlobals.RECOIL_FIT_WINDOW_RANGE, alg._fit_window_range) def test_setup_peaks_and_set_crop_and_fit_ranges_resonance(self): test_d_spacings = [] @@ -516,11 +514,11 @@ def test_setup_peaks_and_set_crop_and_fit_ranges_resonance(self): alg.declareProperty(FloatArrayProperty('DSpacings', test_d_spacings)) alg.declareProperty('PeakType', 'Resonance') alg._setup_peaks_and_set_crop_and_fit_ranges() - self.assertEqual(RESONANCE_PEAK_CROP_RANGE, alg._ws_crop_range) - self.assertEqual(RESONANCE_FIT_WINDOW_RANGE, alg._fit_window_range) + self.assertEqual(EVSGlobals.RESONANCE_PEAK_CROP_RANGE, alg._ws_crop_range) + self.assertEqual(EVSGlobals.RESONANCE_FIT_WINDOW_RANGE, alg._fit_window_range) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.ReplaceSpecialValues') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_to_ads_and_crop') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.ReplaceSpecialValues') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_to_ads_and_crop') def test_preprocess_no_bg(self, mock_load_to_ads_and_crop, mock_replace_special_values): test_run_numbers = [1, 2, 3, 4] test_crop_range = [3, 10] @@ -537,9 +535,9 @@ def test_preprocess_no_bg(self, mock_load_to_ads_and_crop, mock_replace_special_ mock_replace_special_values.assert_called_once_with(test_sample_ws_name, NaNValue=0, NaNError=0, InfinityValue=0, InfinityError=0, OutputWorkspace=test_sample_ws_name) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._normalise_sample_by_background') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.ReplaceSpecialValues') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_to_ads_and_crop') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._normalise_sample_by_background') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.ReplaceSpecialValues') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_to_ads_and_crop') def test_preprocess_with_bg(self, mock_load_to_ads_and_crop, mock_replace_special_values, mock_normalise_sample): test_run_numbers = [1, 2, 3, 4] test_bg_run_numbers = [5, 6] @@ -561,8 +559,8 @@ def test_preprocess_with_bg(self, mock_load_to_ads_and_crop, mock_replace_specia mock_replace_special_values.assert_called_once_with(test_sample_ws_name, NaNValue=0, NaNError=0, InfinityValue=0, InfinityError=0, OutputWorkspace=test_sample_ws_name) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.CropWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_files') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CropWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_files') def test_load_to_ads_and_crop(self, mock_load_files, mock_crop_workspace): alg = EVSCalibrationFit() run_numbers = [1, 2, 3, 4] @@ -574,9 +572,9 @@ def test_load_to_ads_and_crop(self, mock_load_files, mock_crop_workspace): mock_load_files.assert_called_once_with(run_numbers, output) mock_crop_workspace.assert_called_once_with(output, XMin=xmin, XMax=xmax, OutputWorkspace=output) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.Divide') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.RebinToWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.Divide') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.RebinToWorkspace') def test_normalise_sample_by_background(self, mock_rebin, mock_divide, mock_delete): alg = EVSCalibrationFit() sample_ws = 'test_ws' @@ -590,9 +588,9 @@ def test_normalise_sample_by_background(self, mock_rebin, mock_divide, mock_dele mock_divide.assert_called_once_with(sample_ws, bg_ws, OutputWorkspace=sample_ws) mock_delete.assert_called_once_with(bg_ws) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.Plus') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._load_file') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.Plus') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_load_files(self, mock_load_file, mock_plus, mock_delete): alg = EVSCalibrationFit() ws_numbers = ['1-4'] # Note this is parsed as '1-3', is this intentional? @@ -604,8 +602,8 @@ def test_load_files(self, mock_load_file, mock_plus, mock_delete): call(output_name, '__EVS_calib_temp_ws', OutputWorkspace=output_name)]) mock_delete.assert_has_calls([call('__EVS_calib_temp_ws'), call('__EVS_calib_temp_ws')]) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.LoadRaw') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.LoadVesuvio') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.LoadRaw') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.LoadVesuvio') def test_load_file_vesuvio(self, mock_load_vesuvio, mock_load_raw): alg = EVSCalibrationFit() ws_name = 'test_file' @@ -621,9 +619,9 @@ def test_load_file_vesuvio(self, mock_load_vesuvio, mock_load_raw): EnableLogging=False) mock_load_raw.assert_not_called() - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.ConvertToDistribution') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.LoadRaw') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.LoadVesuvio') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.ConvertToDistribution') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.LoadRaw') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.LoadVesuvio') def test_load_file_raw(self, mock_load_vesuvio, mock_load_raw, mock_convert_to_dist): alg = EVSCalibrationFit() ws_name = 'test_file' @@ -640,7 +638,7 @@ def test_load_file_raw(self, mock_load_vesuvio, mock_load_raw, mock_convert_to_d EnableLogging=False) mock_convert_to_dist.assert_called_once_with(output_name, EnableLogging=False) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.GroupWorkspaces') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') def test_fit_peaks_individual(self, group_workspaces_mock): alg = EVSCalibrationFit() alg._estimate_peak_positions = MagicMock(return_value=np.asarray([[5, 10, 15], [2.5, 7.5, 10.5]])) @@ -662,8 +660,8 @@ def test_fit_peaks_individual(self, group_workspaces_mock): group_workspaces_mock.assert_called_once_with(['output_ws_name_Peak_0_Parameters', 'output_ws_name_Peak_1_Parameters'], OutputWorkspace='output_ws_name_Peak_Parameters') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._shared_parameter_fit') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.GroupWorkspaces') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._shared_parameter_fit') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') def test_fit_peaks_shared(self, group_workspaces_mock, shared_parameter_fit_mock): alg = EVSCalibrationFit() alg._estimate_peak_positions = MagicMock(return_value=np.asarray([[5, 10, 15], [2.5, 7.5, 10.5]])) @@ -686,8 +684,8 @@ def test_fit_peaks_shared(self, group_workspaces_mock, shared_parameter_fit_mock OutputWorkspace='output_ws_name_Peak_Parameters') shared_parameter_fit_mock.assert_called_once_with('output_ws_name_Peak_1_Parameters', ['a', 'b', 'c']) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._shared_parameter_fit') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.GroupWorkspaces') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._shared_parameter_fit') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') def test_fit_peaks_both(self, group_workspaces_mock, shared_parameter_fit_mock): alg = EVSCalibrationFit() alg._estimate_peak_positions = MagicMock(return_value=np.asarray([[5, 10, 15], [2.5, 7.5, 10.5]])) @@ -725,7 +723,7 @@ def _setup_alg_mocks_fit_peak(): return alg - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.Fit') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.Fit') def test_fit_peak(self, mock_fit): alg = self._setup_alg_mocks_fit_peak() @@ -749,9 +747,9 @@ def test_fit_peak(self, mock_fit): self.assertEqual(fit_workspace_name, 'fws') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.FindPeaks') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') def test_find_peaks_and_output_params(self, find_peaks_mock, delete_workspace_mock, mtd_mock): alg = EVSCalibrationFit() alg._sample = 'sample' @@ -769,11 +767,11 @@ def test_find_peaks_and_output_params(self, find_peaks_mock, delete_workspace_mo peak_table_name_ws.rowCount.assert_called_once() delete_workspace_mock.assert_called_once_with('__sample_peaks_table_0_3') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.sys') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.logger.error') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.FindPeaks') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.EVSCalibrationFit._get_find_peak_parameters') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.sys') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.logger.error') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._get_find_peak_parameters') def test_find_peaks_and_output_params_no_peaks_found(self, find_peak_params_mock, find_peaks_mock, mtd_mock, logger_mock, sys_mock): alg = EVSCalibrationFit() @@ -804,7 +802,7 @@ def test_find_fit_x_window(self): self.assertEqual(3, xmin) self.assertEqual(7, xmax) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.logger.warning') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.logger.warning') def test_find_fit_x_window_position_less_than_1(self, logger_mock): alg = EVSCalibrationFit() alg._func_param_names = {'Position': 'Position_key'} @@ -825,7 +823,7 @@ def _params_column_side_effect(self, col_index): else: raise ValueError("incorrect column index supplied") - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_output_fit_params_to_table_ws(self, mtd_mock): alg = EVSCalibrationFit() spec_num = 45 @@ -840,22 +838,22 @@ def test_output_fit_params_to_table_ws(self, mtd_mock): mtd_mock.__getitem__.assert_called_once_with(output_table_name) output_table_ws_mock.addRow.assert_called_once_with([spec_num, 1, 0.1, 2, 0.2, 3, 0.3]) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') def test_del_fit_workspace(self, del_ws_mock): alg = EVSCalibrationFit() alg._create_output = True alg._del_fit_workspaces('ncm', 'params', 'fws') del_ws_mock.assert_has_calls([call('ncm'), call('params')]) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') def test_del_fit_workspace_create_output_true(self, del_ws_mock): alg = EVSCalibrationFit() alg._create_output = False alg._del_fit_workspaces('ncm', 'params', 'fws') del_ws_mock.assert_has_calls([call('ncm'), call('params'), call('fws')]) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.CreateEmptyTableWorkspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.AnalysisDataService') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CreateEmptyTableWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.AnalysisDataService') def test_create_output_parameters_table_ws(self, mock_ADS, mock_create_empty_table_ws): output_table_name = 'test_output_table' num_estimated_peaks = 3 @@ -922,7 +920,7 @@ def test_select_best_fit_params_unconstrained_has_invalid_peaks(self): self.assertEqual(selected_params, fit_results['params']) self.assertFalse(unconstrained_fit_selected) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_output_params_to_table(self, mock_mtd_module): alg = EVSCalibrationFit() spec_num = 1 @@ -951,7 +949,7 @@ def test_output_params_to_table(self, mock_mtd_module): expected_row = [1, 0.1, 2, 0.2, 3, 0.3, 4, 0.4, 5, 0.5] mock_output_table.addRow.assert_called_once_with([spec_num] + expected_row) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') def test_get_output_and_clean_workspaces_unconstrained_not_performed(self, mock_delete_ws): alg = EVSCalibrationFit() find_peaks_output_name = 'test_find_output_name' @@ -962,7 +960,7 @@ def test_get_output_and_clean_workspaces_unconstrained_not_performed(self, mock_ call(find_peaks_output_name)]) self.assertEqual(output_ws, fit_peaks_output_name + '_Workspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') def test_get_output_and_clean_workspaces_unconstrained_performed(self, mock_delete_ws): alg = EVSCalibrationFit() find_peaks_output_name = 'test_find_output_name' @@ -977,7 +975,7 @@ def test_get_output_and_clean_workspaces_unconstrained_performed(self, mock_dele call(fit_peaks_output_name + '_unconstrained_Workspace')]) self.assertEqual(output_ws, fit_peaks_output_name + '_Workspace') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.DeleteWorkspace') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') def test_get_output_and_clean_workspaces_unconstrained_performed_and_selected(self, mock_delete_ws): alg = EVSCalibrationFit() find_peaks_output_name = 'test_find_output_name' @@ -1028,8 +1026,8 @@ def _setup_mtd_mock(mtd_mock_obj, find_peaks_name, peaks_found): mtd_mock_obj.__getitem__.side_effect = lambda name: mock_find_peaks_output if\ name == find_peaks_name else None - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.FindPeaks') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_run_find_peaks_peaks_found(self, mock_mtd_module, mock_find_peaks): alg, fn_args = self._setup_run_find_peaks_test(unconstrained=False) @@ -1039,8 +1037,8 @@ def test_run_find_peaks_peaks_found(self, mock_mtd_module, mock_find_peaks): mock_find_peaks.assert_called_once() self.assertTrue(result) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.FindPeaks') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_run_find_peaks_no_peaks_found_raises_value_error(self, mock_mtd_module, mock_find_peaks): alg, fn_args = self._setup_run_find_peaks_test(unconstrained=False) @@ -1050,8 +1048,8 @@ def test_run_find_peaks_no_peaks_found_raises_value_error(self, mock_mtd_module, alg._run_find_peaks(**fn_args) mock_find_peaks.assert_called_once() - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.FindPeaks') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') def test_run_find_peaks_unconstrained_no_peaks_found_no_error(self, mock_mtd_module, mock_find_peaks): alg, fn_args = self._setup_run_find_peaks_test(unconstrained=True) @@ -1061,7 +1059,7 @@ def test_run_find_peaks_unconstrained_no_peaks_found_no_error(self, mock_mtd_mod mock_find_peaks.assert_called_once() self.assertFalse(result) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.FindPeaks') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') def test_run_find_peaks_unconstrained_peaks_found_raises_error(self, mock_find_peaks): alg, fn_args = self._setup_run_find_peaks_test(unconstrained=True) @@ -1097,12 +1095,12 @@ def test_filter_and_fit_found_peaks_unconstrained(self): alg._calc_linear_bg_coefficients.assert_called_once() alg._filter_found_peaks.assert_called_once_with(find_peaks_output_name, peak_estimates_list, 'linear_bg_coeffs', - peak_height_rel_threshold=PEAK_HEIGHT_RELATIVE_THRESHOLD) + peak_height_rel_threshold=EVSGlobals.PEAK_HEIGHT_RELATIVE_THRESHOLD) alg._fit_found_peaks.assert_called_once_with(find_peaks_output_name, None, workspace_index, fit_peaks_output_name, x_range) alg._check_fitted_peak_validity.assert_called_once_with(fit_peaks_output_name + '_Parameters', peak_estimates_list, - peak_height_rel_threshold=PEAK_HEIGHT_RELATIVE_THRESHOLD) + peak_height_rel_threshold=EVSGlobals.PEAK_HEIGHT_RELATIVE_THRESHOLD) self.assertEqual(fit_results['status'], 'test_status') @@ -1111,15 +1109,15 @@ def test_filter_and_fit_found_peaks_not_unconstrained(self): x_range = self._setup_filter_and_fit_found_peaks_mocks() fit_results = alg._filter_and_fit_found_peaks(workspace_index, peak_estimates_list, find_peaks_output_name, - fit_peaks_output_name, x_range, unconstrained=False) + fit_peaks_output_name, x_range, unconstrained=False) alg._calc_linear_bg_coefficients.assert_not_called() alg._filter_found_peaks.assert_not_called() alg._fit_found_peaks.assert_called_once_with(find_peaks_output_name, peak_estimates_list, workspace_index, - fit_peaks_output_name, x_range) + fit_peaks_output_name, x_range) alg._check_fitted_peak_validity.assert_called_once_with(fit_peaks_output_name + '_Parameters', - peak_estimates_list, - peak_height_rel_threshold=PEAK_HEIGHT_RELATIVE_THRESHOLD) + peak_estimates_list, + peak_height_rel_threshold=EVSGlobals.PEAK_HEIGHT_RELATIVE_THRESHOLD) self.assertEqual(fit_results['status'], 'test_status') def test_filter_and_fit_found_peaks_invalid_peaks(self): @@ -1128,14 +1126,15 @@ def test_filter_and_fit_found_peaks_invalid_peaks(self): alg._check_fitted_peak_validity.return_value = False fit_results = alg._filter_and_fit_found_peaks(workspace_index, peak_estimates_list, find_peaks_output_name, - fit_peaks_output_name, x_range, unconstrained=False) + fit_peaks_output_name, x_range, unconstrained=False) alg._calc_linear_bg_coefficients.assert_not_called() alg._filter_found_peaks.assert_not_called() alg._fit_found_peaks.assert_called_once_with(find_peaks_output_name, peak_estimates_list, workspace_index, fit_peaks_output_name, x_range) alg._check_fitted_peak_validity.assert_called_once_with(fit_peaks_output_name + '_Parameters', - peak_estimates_list, peak_height_rel_threshold=PEAK_HEIGHT_RELATIVE_THRESHOLD) + peak_estimates_list, + peak_height_rel_threshold=EVSGlobals.PEAK_HEIGHT_RELATIVE_THRESHOLD) self.assertEqual(fit_results['status'], 'peaks invalid') @staticmethod @@ -1222,7 +1221,7 @@ def _test_calls_individually(self, call_list, calls): except ValueError: np.testing.assert_array_equal(arg, expected_arg) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.GroupWorkspaces') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') def test_fit_bragg_peaks_success(self, group_workspaces_mock): fit_result_ret_val = {'status': 'success'} fit_results = lambda *args: fit_result_ret_val @@ -1244,7 +1243,7 @@ def test_fit_bragg_peaks_success(self, group_workspaces_mock): call(False, False, False, 'sample_peaks_table_2', 'output_Spec_2')]) group_workspaces_mock.assert_called_once_with(','.join(output_workspaces), OutputWorkspace='output_Peak_Fits') - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.GroupWorkspaces') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') def test_fit_bragg_peaks_not_success(self, group_workspaces_mock): x_range = (1, 2) fit_res = {'status': 'failure', 'xmin': x_range[0], 'xmax': x_range[1]} diff --git a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_misc.py b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_misc.py index 08d1269c..c3010c17 100644 --- a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_misc.py +++ b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_misc.py @@ -1,4 +1,4 @@ -from unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5 import generate_fit_function_header, identify_invalid_spectra +from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSMiscFunctions from mock import MagicMock, patch import unittest @@ -14,27 +14,27 @@ def setUp(self): pass def test_generate_header_function_gaussian(self): - header = generate_fit_function_header("Gaussian") + header = EVSMiscFunctions.generate_fit_function_header("Gaussian") self.assertEqual({'Height': 'Height', 'Width': 'Sigma', 'Position': 'PeakCentre'}, header) def test_generate_header_function_gaussian_with_error(self): - header = generate_fit_function_header("Gaussian", error=True) + header = EVSMiscFunctions.generate_fit_function_header("Gaussian", error=True) self.assertEqual({'Height': 'Height_Err', 'Width': 'Sigma_Err', 'Position': 'PeakCentre_Err'}, header) def test_generate_header_function_voigt(self): - header = generate_fit_function_header("Voigt") + header = EVSMiscFunctions.generate_fit_function_header("Voigt") self.assertEqual({'Height': 'LorentzAmp', 'Position': 'LorentzPos', 'Width': 'LorentzFWHM', 'Width_2': 'GaussianFWHM'}, header) def test_generate_header_function_voigt_with_error(self): - header = generate_fit_function_header("Voigt", error=True) + header = EVSMiscFunctions.generate_fit_function_header("Voigt", error=True) self.assertEqual({'Height': 'LorentzAmpErr', 'Position': 'LorentzPosErr', 'Width': 'LorentzFWHMErr', 'Width_2': 'GaussianFWHMErr'}, header) def test_generate_header_function_invalid(self): with self.assertRaises(ValueError): - generate_fit_function_header("Invalid") + EVSMiscFunctions.generate_fit_function_header("Invalid") - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions.mtd') def test_identify_invalid_spectra_no_invalid(self, mock_mtd): ws_mock = MagicMock() mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [0.1, 0.2, 0.3], @@ -43,9 +43,10 @@ def test_identify_invalid_spectra_no_invalid(self, mock_mtd): ws_mock.column.side_effect = lambda key: mock_column_output_dict[key] mock_mtd.__getitem__.return_value = ws_mock - np.testing.assert_equal(np.argwhere([]), identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) + np.testing.assert_equal(np.argwhere([]), + EVSMiscFunctions.identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions.mtd') def test_identify_invalid_spectra_nan_in_errors(self, mock_mtd): ws_mock = MagicMock() mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [np.nan, 0.2, 0.3], @@ -55,9 +56,9 @@ def test_identify_invalid_spectra_nan_in_errors(self, mock_mtd): mock_mtd.__getitem__.return_value = ws_mock np.testing.assert_equal(np.argwhere(np.array([True, False, True])), - identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) + EVSMiscFunctions.identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions.mtd') def test_identify_invalid_spectra_inf_in_errors(self, mock_mtd): ws_mock = MagicMock() mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [0.1, 0.2, 0.3], @@ -67,9 +68,9 @@ def test_identify_invalid_spectra_inf_in_errors(self, mock_mtd): mock_mtd.__getitem__.return_value = ws_mock np.testing.assert_equal(np.argwhere(np.array([True, True, False])), - identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) + EVSMiscFunctions.identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') + @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions.mtd') def test_identify_invalid_spectra_error_greater_than_peak(self, mock_mtd): ws_mock = MagicMock() mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [0.1, 0.2, 0.3], @@ -79,7 +80,7 @@ def test_identify_invalid_spectra_error_greater_than_peak(self, mock_mtd): mock_mtd.__getitem__.return_value = ws_mock np.testing.assert_equal(np.argwhere(np.array([False, True, True])), - identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) + EVSMiscFunctions.identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) if __name__ == '__main__': From dcdd02530bc1cf3e47cab538bd7ce48b403e3ab9 Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Tue, 25 Apr 2023 17:16:05 +0100 Subject: [PATCH 2/4] add readme and remove if main from loader --- unpackaged/vesuvio_calibration/README.md | 56 +++++++++++++++++++ .../load_calibration_algorithms.py | 5 +- 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 unpackaged/vesuvio_calibration/README.md diff --git a/unpackaged/vesuvio_calibration/README.md b/unpackaged/vesuvio_calibration/README.md new file mode 100644 index 00000000..f8c7d5ff --- /dev/null +++ b/unpackaged/vesuvio_calibration/README.md @@ -0,0 +1,56 @@ +# Vesuvio Calibration Scripts + +In this subdirectory of the EVSVesuvio repository the following is provided: +- Vesuvio calibration scripts, composed of 3 python modules: + 1. `calibration_scripts.calibrate_vesuvio_fit` + 2. `calibration_scripts.calibrate_vesuvio_analysis` + 3. `calibration_scripts.calibrate_vesuvio_helper_functions` + +- A script that is designed to run in `mantid workbench` to register the calibration algorithms: + `unpackaged\vesuvio_calibration\load_calibration_algorithms.py` + +- Unit tests to ensure the consistent functionality of the functions that make up the provided algorithms. + 1. `tests\unit\test_calibrate_vesuvio_fit.py` + 2. `tests\unit\test_calibrate_vesuvio_analysis.py` + 3. `tests\unit\test_calibrate_vesuvio_misc.py` + +- System tests to ensure the correct output of the overall calibration script. + 1. `tests\system\test_system_fit.py` + 2. `tests\system\test_system_analysis.py` + +## Running the Calibration Scripts in mantid `workbench`. + +To run the calibration scripts in mantid, a script is provided: `unpackaged\vesuvio_calibration\load_calibration_algorithms.py`. + +This script can be loaded into `workbench` via `File` > `Open Script`. + +Before running this script, the script directory `unpackaged\vesuvio_calibration` must be added via: +1. `File` > `Manage User Directories`. +2. Navigate to `Python Script Directories` tab. +3. Click `Browse To Directory` and select the `unpackaged\vesuvio_calibration` directory. +4. Press `Ok`. + +Upon running the script, the two calibration algorithms will be registered under the `VesuvioCalibration` heading in the `Algorithms` pane. + + +# Running Calibration Script Tests + +## Create Conda Environment from the command line. + +1. Ensure `conda`and or `mamba` is installed on your machine using `conda --verison`/`mamba --version`. + +2. If no such module is found install `mamba` (recommended): https://mamba.readthedocs.io/en/latest/installation.html or `conda`: https://conda.io/projects/conda/en/latest/user-guide/install/index.html + +3. From the root of the repository run `conda env create -f environment.yml`. + +## Running the unit tests from the command line. + +1. Actviate the conda environment using `conda activate vesuvio-env`. + +2. From the root of the repository run `python -m unittest discover -s ./unpackaged/vesuvio_calibration/tests/unit` + +## Running the system tests from the command line. + +1. Actviate the conda environment using `conda activate vesuvio-env`. + +2. From the root of the repository run `python -m unittest discover -s ./unpackaged/vesuvio_calibration/tests/system` diff --git a/unpackaged/vesuvio_calibration/load_calibration_algorithms.py b/unpackaged/vesuvio_calibration/load_calibration_algorithms.py index 2abc5c21..cd7fa434 100644 --- a/unpackaged/vesuvio_calibration/load_calibration_algorithms.py +++ b/unpackaged/vesuvio_calibration/load_calibration_algorithms.py @@ -17,6 +17,5 @@ from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_analysis import EVSCalibrationAnalysis -if __name__ == "__main__": - AlgorithmFactory.subscribe(EVSCalibrationFit) - AlgorithmFactory.subscribe(EVSCalibrationAnalysis) +AlgorithmFactory.subscribe(EVSCalibrationFit) +AlgorithmFactory.subscribe(EVSCalibrationAnalysis) From 4c54a5cec948fe88e17f6398f6c2a3e6308b654a Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Tue, 2 May 2023 11:08:22 +0100 Subject: [PATCH 3/4] respond to pr review comments --- .../calibrate_vesuvio_analysis.py | 16 +++++----------- .../calibration_scripts/calibrate_vesuvio_fit.py | 9 +++------ .../load_calibration_algorithms.py | 5 ++--- .../tests/system/test_system_analysis.py | 2 +- .../tests/system/test_system_fit.py | 2 +- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py index acf9ab24..ed1ee5e8 100644 --- a/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py +++ b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py @@ -1,13 +1,8 @@ -from mantid.kernel import StringArrayProperty, Direction, StringListValidator, IntArrayBoundedValidator, IntArrayProperty,\ - FloatArrayBoundedValidator, FloatArrayMandatoryValidator, StringMandatoryValidator, IntBoundedValidator,\ - FloatArrayProperty -from mantid.api import FileProperty, FileAction, ITableWorkspaceProperty, PropertyMode, Progress, TextAxis, PythonAlgorithm,\ - AlgorithmManager -from mantid.simpleapi import CreateEmptyTableWorkspace, DeleteWorkspace, CropWorkspace, RebinToWorkspace, Divide,\ - ReplaceSpecialValues, FindPeaks, GroupWorkspaces, mtd, Plus, LoadVesuvio, LoadRaw, ConvertToDistribution,\ - FindPeakBackground, ExtractSingleSpectrum, SumSpectra, AppendSpectra, ConvertTableToMatrixWorkspace,\ - ConjoinWorkspaces, Transpose, PlotPeakByLogValue, CloneWorkspace, MaskDetectors,\ - ExtractUnmaskedSpectra, CreateWorkspace, RenameWorkspace +from mantid.kernel import StringArrayProperty, Direction, StringListValidator, FloatArrayBoundedValidator, StringMandatoryValidator,\ + IntBoundedValidator, FloatArrayProperty +from mantid.api import FileProperty, FileAction, PythonAlgorithm,AlgorithmManager +from mantid.simpleapi import CreateEmptyTableWorkspace, DeleteWorkspace, ReplaceSpecialValues, GroupWorkspaces, mtd,\ + ConvertTableToMatrixWorkspace, ConjoinWorkspaces, Transpose, PlotPeakByLogValue,RenameWorkspace from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals, EVSMiscFunctions import os @@ -280,7 +275,6 @@ def _calculate_scattering_angle(self, table_name, spec_list): L0 = EVSMiscFunctions.read_table_column(self._current_workspace, 'L0', spec_list) L1 = EVSMiscFunctions.read_table_column(self._param_table, 'L1', spec_list) L1_nan_to_num = np.nan_to_num(L1) - spec = EVSMiscFunctions.read_table_column(self._current_workspace, 'Spectrum', spec_list) t0 /= 1e+6 diff --git a/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py index 439fb671..d6c37b24 100644 --- a/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py +++ b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py @@ -1,13 +1,10 @@ from mantid.kernel import StringArrayProperty, Direction, StringListValidator, IntArrayBoundedValidator, IntArrayProperty,\ - FloatArrayBoundedValidator, FloatArrayMandatoryValidator, StringMandatoryValidator, IntBoundedValidator,\ - FloatArrayProperty, logger + FloatArrayBoundedValidator, FloatArrayMandatoryValidator, StringMandatoryValidator, FloatArrayProperty, logger from mantid.api import FileProperty, FileAction, ITableWorkspaceProperty, PropertyMode, Progress, TextAxis, PythonAlgorithm,\ WorkspaceFactory, AnalysisDataService from mantid.simpleapi import CreateEmptyTableWorkspace, DeleteWorkspace, CropWorkspace, RebinToWorkspace, Divide,\ - ReplaceSpecialValues, FindPeaks, GroupWorkspaces, mtd, Plus, LoadVesuvio, LoadRaw, ConvertToDistribution,\ - FindPeakBackground, ExtractSingleSpectrum, SumSpectra, AppendSpectra, ConvertTableToMatrixWorkspace,\ - ConjoinWorkspaces, Transpose, PlotPeakByLogValue, CloneWorkspace, Fit, MaskDetectors,\ - ExtractUnmaskedSpectra, CreateWorkspace + ReplaceSpecialValues, FindPeaks, GroupWorkspaces, mtd, Plus, LoadVesuvio, LoadRaw, ConvertToDistribution, FindPeakBackground,\ + ExtractSingleSpectrum, SumSpectra, AppendSpectra, CloneWorkspace, Fit, MaskDetectors, ExtractUnmaskedSpectra, CreateWorkspace from functools import partial from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals, EVSMiscFunctions diff --git a/unpackaged/vesuvio_calibration/load_calibration_algorithms.py b/unpackaged/vesuvio_calibration/load_calibration_algorithms.py index cd7fa434..1c982c5e 100644 --- a/unpackaged/vesuvio_calibration/load_calibration_algorithms.py +++ b/unpackaged/vesuvio_calibration/load_calibration_algorithms.py @@ -6,10 +6,9 @@ EVSCalibrationFit is used to fit m peaks to n spectra. The positions of the peaks are esitmated using the supplied instrument parameter file and the d-spacings of the sample (if provided). Support is provided for both Voigt and Gaussian functions. - EVSCalibrationAnalysis uses EVSCalibrationFit to calculate instrument parameters using the output of the fitting and the and an existing - instrument parameter file. + EVSCalibrationAnalysis uses the output from EVSCalibrationFit to calculate instrument parameters. - The procedures used here are based upon those descibed in: Calibration of an electron volt neutron spectrometer, Nuclear Instruments and + The procedures used here are based upon those described in: Calibration of an electron volt neutron spectrometer, Nuclear Instruments and Methods in Physics Research A (15 October 2010), doi:10.1016/j.nima.2010.09.079 by J. Mayers, M. A. Adams """ diff --git a/unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py b/unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py index 59c0b88c..5165d1e2 100644 --- a/unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py +++ b/unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py @@ -2,7 +2,7 @@ import numpy as np from mantid.api import AlgorithmFactory -from mantid.simpleapi import LoadVesuvio, LoadRaw, mtd, ConvertToDistribution +from mantid.simpleapi import mtd from mock import patch from unpackaged.vesuvio_calibration.tests.testhelpers.algorithms import create_algorithm from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_base import EVSCalibrationTest, TestConstants diff --git a/unpackaged/vesuvio_calibration/tests/system/test_system_fit.py b/unpackaged/vesuvio_calibration/tests/system/test_system_fit.py index 7e0648c9..cc9343bc 100644 --- a/unpackaged/vesuvio_calibration/tests/system/test_system_fit.py +++ b/unpackaged/vesuvio_calibration/tests/system/test_system_fit.py @@ -4,7 +4,7 @@ import scipy.stats from mantid.api import WorkspaceGroup, AlgorithmFactory -from mantid.simpleapi import LoadVesuvio, LoadRaw, mtd, ConvertToDistribution +from mantid.simpleapi import mtd from mock import patch from unpackaged.vesuvio_calibration.tests.testhelpers.algorithms import create_algorithm from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_base import EVSCalibrationTest, TestConstants From 86de1c25aa549977a3c4c243136fefa64f9957c4 Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Tue, 2 May 2023 11:51:59 +0100 Subject: [PATCH 4/4] update paths to run from calibration root --- .github/workflows/pr_workflow.yml | 3 +- unpackaged/vesuvio_calibration/README.md | 4 +- .../calibrate_vesuvio_analysis.py | 3 +- .../calibrate_vesuvio_fit.py | 2 +- .../load_calibration_algorithms.py | 4 +- .../tests/system/test_system_analysis.py | 30 +-- .../tests/system/test_system_fit.py | 22 +-- .../tests/testhelpers/system_test_base.py | 2 +- .../testhelpers/system_test_misc_functions.py | 2 +- .../unit/test_calibrate_vesuvio_analysis.py | 2 +- .../tests/unit/test_calibrate_vesuvio_fit.py | 172 +++++++++--------- .../tests/unit/test_calibrate_vesuvio_misc.py | 10 +- 12 files changed, 129 insertions(+), 127 deletions(-) diff --git a/.github/workflows/pr_workflow.yml b/.github/workflows/pr_workflow.yml index 63865310..4ce286d3 100644 --- a/.github/workflows/pr_workflow.yml +++ b/.github/workflows/pr_workflow.yml @@ -39,7 +39,8 @@ jobs: - name: Run EVSVesuvio Calibration Unit Tests run: | export MANTIDPROPERTIES=$(pwd)/Mantid.user.properties - python -m unittest discover -s ./unpackaged/vesuvio_calibration/tests/unit + cd unpackaged/vesuvio_calibration + python -m unittest discover -s ./tests/unit #DISABLED AS THEY APPEAR TOO LARGE FOR GITHUB HOSTED RUNNERS #- name: Run Vesuvio Calibration System Tests diff --git a/unpackaged/vesuvio_calibration/README.md b/unpackaged/vesuvio_calibration/README.md index f8c7d5ff..ca7c711c 100644 --- a/unpackaged/vesuvio_calibration/README.md +++ b/unpackaged/vesuvio_calibration/README.md @@ -47,10 +47,10 @@ Upon running the script, the two calibration algorithms will be registered under 1. Actviate the conda environment using `conda activate vesuvio-env`. -2. From the root of the repository run `python -m unittest discover -s ./unpackaged/vesuvio_calibration/tests/unit` +2. From `/unpackaged/vesuvio_calibration` run `python -m unittest discover -s ./tests/unit` ## Running the system tests from the command line. 1. Actviate the conda environment using `conda activate vesuvio-env`. -2. From the root of the repository run `python -m unittest discover -s ./unpackaged/vesuvio_calibration/tests/system` +2. From `/unpackaged/vesuvio_calibration` run `python -m unittest discover -s ./tests/system` diff --git a/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py index ed1ee5e8..de66f8a4 100644 --- a/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py +++ b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_analysis.py @@ -3,7 +3,8 @@ from mantid.api import FileProperty, FileAction, PythonAlgorithm,AlgorithmManager from mantid.simpleapi import CreateEmptyTableWorkspace, DeleteWorkspace, ReplaceSpecialValues, GroupWorkspaces, mtd,\ ConvertTableToMatrixWorkspace, ConjoinWorkspaces, Transpose, PlotPeakByLogValue,RenameWorkspace -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals, EVSMiscFunctions +from calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals, EVSMiscFunctions + import os import sys diff --git a/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py index d6c37b24..1fbe846b 100644 --- a/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py +++ b/unpackaged/vesuvio_calibration/calibration_scripts/calibrate_vesuvio_fit.py @@ -6,7 +6,7 @@ ReplaceSpecialValues, FindPeaks, GroupWorkspaces, mtd, Plus, LoadVesuvio, LoadRaw, ConvertToDistribution, FindPeakBackground,\ ExtractSingleSpectrum, SumSpectra, AppendSpectra, CloneWorkspace, Fit, MaskDetectors, ExtractUnmaskedSpectra, CreateWorkspace from functools import partial -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals, EVSMiscFunctions +from calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals, EVSMiscFunctions import os import sys diff --git a/unpackaged/vesuvio_calibration/load_calibration_algorithms.py b/unpackaged/vesuvio_calibration/load_calibration_algorithms.py index 1c982c5e..96fcc672 100644 --- a/unpackaged/vesuvio_calibration/load_calibration_algorithms.py +++ b/unpackaged/vesuvio_calibration/load_calibration_algorithms.py @@ -13,8 +13,8 @@ """ from mantid.api import AlgorithmFactory -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_analysis import EVSCalibrationAnalysis +from calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit +from calibration_scripts.calibrate_vesuvio_analysis import EVSCalibrationAnalysis AlgorithmFactory.subscribe(EVSCalibrationFit) AlgorithmFactory.subscribe(EVSCalibrationAnalysis) diff --git a/unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py b/unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py index 5165d1e2..779568cd 100644 --- a/unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py +++ b/unpackaged/vesuvio_calibration/tests/system/test_system_analysis.py @@ -4,12 +4,12 @@ from mantid.api import AlgorithmFactory from mantid.simpleapi import mtd from mock import patch -from unpackaged.vesuvio_calibration.tests.testhelpers.algorithms import create_algorithm -from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_base import EVSCalibrationTest, TestConstants -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals -from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_misc_functions import assert_allclose_excluding_bad_detectors -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_analysis import EVSCalibrationAnalysis -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit +from tests.testhelpers.algorithms import create_algorithm +from tests.testhelpers.system_test_base import EVSCalibrationTest, TestConstants +from calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals +from tests.testhelpers.system_test_misc_functions import assert_allclose_excluding_bad_detectors +from calibration_scripts.calibrate_vesuvio_analysis import EVSCalibrationAnalysis +from calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit from copy import copy from os import path @@ -40,7 +40,7 @@ def setUp(self): self._E1_fit = [False, True, True] self._L0_fit = [False] - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_copper(self, load_file_mock): self._setup_copper_test() self._output_workspace = "copper_analysis_test" @@ -55,7 +55,7 @@ def test_copper(self, load_file_mock): 165, 167, 168, 169, 170, 182, 191, 192]}) self._assert_parameters_match_expected(params_table, detector_specific_r_tols) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_lead(self, load_file_mock): self._setup_lead_test() self._output_workspace = "lead_analysis_test" @@ -70,7 +70,7 @@ def test_lead(self, load_file_mock): 178, 180, 182, 183]}) self._assert_parameters_match_expected(params_table, detector_specific_r_tols) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_niobium(self, load_file_mock): self._setup_niobium_test() self._output_workspace = "niobium_analysis_test" @@ -88,7 +88,7 @@ def test_niobium(self, load_file_mock): 182, 186, 187, 189, 191]}) self._assert_parameters_match_expected(params_table, detector_specific_r_tols) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_copper_with_uranium(self, load_file_mock): self._setup_copper_test() self._output_workspace = "copper_analysis_test" @@ -103,7 +103,7 @@ def test_copper_with_uranium(self, load_file_mock): 165, 167, 168, 169, 170, 182, 191, 192]}) self._assert_parameters_match_expected(params_table, detector_specific_r_tols) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_lead_with_uranium(self, load_file_mock): self._setup_lead_test() self._output_workspace = "lead_analysis_test" @@ -118,7 +118,7 @@ def test_lead_with_uranium(self, load_file_mock): 178, 180, 182, 183]}) self._assert_parameters_match_expected(params_table, detector_specific_r_tols) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_copper_with_l0_calc(self, load_file_mock): self._setup_copper_test() self._L0_fit = [True, True, True, False, False, False] @@ -138,7 +138,7 @@ def test_copper_with_l0_calc(self, load_file_mock): 165, 167, 168, 169, 170, 182, 191, 192]}) self._assert_parameters_match_expected(params_table, detector_specific_r_tols) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_copper_with_multiple_iterations(self, load_file_mock): self._setup_copper_test() self._iterations = 2 @@ -158,7 +158,7 @@ def test_copper_with_multiple_iterations(self, load_file_mock): 165, 167, 168, 169, 170, 182, 191, 192]}) self._assert_parameters_match_expected(params_table, detector_specific_r_tols) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_copper_create_output(self, load_file_mock): self._setup_copper_test() self._output_workspace = "copper_analysis_test" @@ -175,7 +175,7 @@ def test_copper_create_output(self, load_file_mock): 165, 167, 168, 169, 170, 182, 191, 192]}) self._assert_parameters_match_expected(params_table, detector_specific_r_tols) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_copper_with_individual_and_global_fit(self, load_file_mock): self._setup_copper_test() self._output_workspace = "copper_analysis_test" diff --git a/unpackaged/vesuvio_calibration/tests/system/test_system_fit.py b/unpackaged/vesuvio_calibration/tests/system/test_system_fit.py index cc9343bc..0e4698ce 100644 --- a/unpackaged/vesuvio_calibration/tests/system/test_system_fit.py +++ b/unpackaged/vesuvio_calibration/tests/system/test_system_fit.py @@ -6,11 +6,11 @@ from mantid.api import WorkspaceGroup, AlgorithmFactory from mantid.simpleapi import mtd from mock import patch -from unpackaged.vesuvio_calibration.tests.testhelpers.algorithms import create_algorithm -from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_base import EVSCalibrationTest, TestConstants -from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_misc_functions import assert_allclose_excluding_bad_detectors -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSMiscFunctions, EVSGlobals -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit +from tests.testhelpers.algorithms import create_algorithm +from tests.testhelpers.system_test_base import EVSCalibrationTest, TestConstants +from tests.testhelpers.system_test_misc_functions import assert_allclose_excluding_bad_detectors +from calibration_scripts.calibrate_vesuvio_helper_functions import EVSMiscFunctions, EVSGlobals +from calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit from os import path @@ -40,7 +40,7 @@ def setUp(self): self._E1_fit = [False] self._L0_fit = [False] - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_fit_bragg_peaks_copper(self, load_file_mock): self._setup_copper_test() self._spec_range = [EVSGlobals.DETECTOR_RANGE] @@ -52,7 +52,7 @@ def test_fit_bragg_peaks_copper(self, load_file_mock): params_table = self._run_evs_calibration_fit("Bragg") self._assert_fitted_positions_match_expected(expected_values, params_table, {15: TestConstants.IGNORE_DETECTOR}) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_fit_bragg_peaks_lead(self, load_file_mock): self._setup_lead_test() self._spec_range = [EVSGlobals.DETECTOR_RANGE] @@ -65,7 +65,7 @@ def test_fit_bragg_peaks_lead(self, load_file_mock): self._assert_fitted_positions_match_expected(expected_values, params_table, {145: 0.27, 158: 0.15, 190: TestConstants.IGNORE_DETECTOR}) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_fit_peaks_copper_E1(self, load_file_mock): self._setup_copper_test() self._E1_fit_active = True @@ -79,7 +79,7 @@ def test_fit_peaks_copper_E1(self, load_file_mock): params_table = self._run_evs_calibration_fit("Recoil") self._assert_fitted_positions_match_expected(expected_values, params_table, {38: 0.12}) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_fit_peaks_lead_E1(self, load_file_mock): self._setup_lead_test() self._E1_fit_active = True @@ -93,7 +93,7 @@ def test_fit_peaks_lead_E1(self, load_file_mock): params_table = self._run_evs_calibration_fit("Recoil") self._assert_fitted_positions_match_expected(expected_values, params_table, {38: 0.12}) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_fit_frontscattering_uranium(self, load_file_mock): self._setup_uranium_test() self._run_range = EVSGlobals.U_FRONTSCATTERING_SAMPLE @@ -107,7 +107,7 @@ def test_fit_frontscattering_uranium(self, load_file_mock): params_table = self._run_evs_calibration_fit("Recoil") self._assert_fitted_positions_match_expected(expected_values, params_table) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_fit_backscattering_uranium(self, load_file_mock): self._setup_uranium_test() self._run_range = EVSGlobals.U_BACKSCATTERING_SAMPLE diff --git a/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_base.py b/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_base.py index 3bab0fe9..1b9532d5 100644 --- a/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_base.py +++ b/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_base.py @@ -1,7 +1,7 @@ import unittest import numpy as np from mantid.simpleapi import LoadVesuvio, LoadRaw, mtd, ConvertToDistribution -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals +from calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals from os import path diff --git a/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_misc_functions.py b/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_misc_functions.py index d507f4c2..f0524e07 100644 --- a/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_misc_functions.py +++ b/unpackaged/vesuvio_calibration/tests/testhelpers/system_test_misc_functions.py @@ -1,6 +1,6 @@ import numpy as np from sys import maxsize -from unpackaged.vesuvio_calibration.tests.testhelpers.system_test_base import TestConstants +from tests.testhelpers.system_test_base import TestConstants def assert_allclose_excluding_bad_detectors(expected_position, position, rtol, default_rtol=TestConstants.DEFAULT_RELATIVE_TOLERANCE): diff --git a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_analysis.py b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_analysis.py index 14aadddd..3453a535 100644 --- a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_analysis.py +++ b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_analysis.py @@ -1,4 +1,4 @@ -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_analysis import EVSCalibrationAnalysis +from calibration_scripts.calibrate_vesuvio_analysis import EVSCalibrationAnalysis import unittest diff --git a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_fit.py b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_fit.py index 97c299a5..3c8bce65 100644 --- a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_fit.py +++ b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_fit.py @@ -1,5 +1,5 @@ -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals +from calibration_scripts.calibrate_vesuvio_fit import EVSCalibrationFit +from calibration_scripts.calibrate_vesuvio_helper_functions import EVSGlobals from mock import MagicMock, patch, call from functools import partial from mantid.kernel import IntArrayProperty, StringArrayProperty, FloatArrayProperty @@ -30,9 +30,9 @@ def side_effect_set_cell(self, arg1, arg2, value): def side_effect_cell(row_index, col_index, peaks): return peaks[row_index] - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_perfect_match(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -64,9 +64,9 @@ def test_filter_peaks_perfect_match(self, mock_mtd, mock_clone_workspace, mock_d mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_no_match(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -96,9 +96,9 @@ def test_filter_peaks_no_match(self, mock_mtd, mock_clone_workspace, mock_del_wo OutputWorkspace=find_peaks_output_name + '_unfiltered') mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_one_match(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -129,9 +129,9 @@ def test_filter_peaks_one_match(self, mock_mtd, mock_clone_workspace, mock_del_w OutputWorkspace=find_peaks_output_name + '_unfiltered') mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_two_match(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -163,9 +163,9 @@ def test_filter_peaks_two_match(self, mock_mtd, mock_clone_workspace, mock_del_w mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_does_not_include_higher_found_peak(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -196,9 +196,9 @@ def test_filter_peaks_does_not_include_higher_found_peak(self, mock_mtd, mock_cl OutputWorkspace=find_peaks_output_name + '_unfiltered') mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_does_not_include_lower_found_peak(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -230,9 +230,9 @@ def test_filter_peaks_does_not_include_lower_found_peak(self, mock_mtd, mock_clo mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_handles_multiple_peaks(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -263,9 +263,9 @@ def test_filter_peaks_handles_multiple_peaks(self, mock_mtd, mock_clone_workspac mock_del_workspace.assert_called_with(find_peaks_output_name + '_unfiltered') #Found peaks sometimes returns 'zero' peaks, usually at the end of the table workspace. - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.CloneWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_filter_peaks_handles_zero_position_in_found_peaks(self, mock_mtd, mock_clone_workspace, mock_del_workspace): alg = EVSCalibrationFit() @@ -316,7 +316,7 @@ def side_effect(arg1, arg2): np.testing.assert_almost_equal([9629.84, 13619.43, 15727.03], estimated_positions.flatten().tolist(), 0.01) print(estimated_positions) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_check_nans_false(self, mock_mtd): alg = EVSCalibrationFit() table_ws = 'table_ws' @@ -331,7 +331,7 @@ def test_check_nans_false(self, mock_mtd): self.assertFalse(alg._check_nans(table_ws)) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_check_nans_true(self, mock_mtd): alg = EVSCalibrationFit() table_ws = 'table_ws' @@ -360,12 +360,12 @@ def test_PyInit_property_defaults(self): self.assertEqual(expected_value, properties[prop], f'Property {prop}. Expected: {expected_value},' f'Actual: {properties[prop]}') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_spectra_list') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_run_numbers_and_output_workspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_function_type') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_parameter_workspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_peaks_and_set_crop_and_fit_ranges') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_class_variables_from_properties') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_spectra_list') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_run_numbers_and_output_workspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_function_type') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_parameter_workspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_peaks_and_set_crop_and_fit_ranges') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._setup_class_variables_from_properties') def test_setup_calls_all_functions(self, mock_setup_vars, mock_setup_peaks, mock_setup_param_ws, mock_setup_fn_type, mock_setup_run_nos, mock_setup_spec): alg = EVSCalibrationFit() @@ -480,7 +480,7 @@ def test_setup_parameter_workspace(self): self.assertEqual('test_ws', alg._param_workspace) self.assertEqual('test_ws', alg._param_table) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSMiscFunctions.load_instrument_parameters') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSMiscFunctions.load_instrument_parameters') def test_setup_parameter_workspace_no_ws(self, mock_load_instrument_parameters): alg = EVSCalibrationFit() alg.declareProperty('InstrumentParameterWorkspace', '') @@ -517,8 +517,8 @@ def test_setup_peaks_and_set_crop_and_fit_ranges_resonance(self): self.assertEqual(EVSGlobals.RESONANCE_PEAK_CROP_RANGE, alg._ws_crop_range) self.assertEqual(EVSGlobals.RESONANCE_FIT_WINDOW_RANGE, alg._fit_window_range) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.ReplaceSpecialValues') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_to_ads_and_crop') + @patch('calibration_scripts.calibrate_vesuvio_fit.ReplaceSpecialValues') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_to_ads_and_crop') def test_preprocess_no_bg(self, mock_load_to_ads_and_crop, mock_replace_special_values): test_run_numbers = [1, 2, 3, 4] test_crop_range = [3, 10] @@ -535,9 +535,9 @@ def test_preprocess_no_bg(self, mock_load_to_ads_and_crop, mock_replace_special_ mock_replace_special_values.assert_called_once_with(test_sample_ws_name, NaNValue=0, NaNError=0, InfinityValue=0, InfinityError=0, OutputWorkspace=test_sample_ws_name) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._normalise_sample_by_background') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.ReplaceSpecialValues') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_to_ads_and_crop') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._normalise_sample_by_background') + @patch('calibration_scripts.calibrate_vesuvio_fit.ReplaceSpecialValues') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_to_ads_and_crop') def test_preprocess_with_bg(self, mock_load_to_ads_and_crop, mock_replace_special_values, mock_normalise_sample): test_run_numbers = [1, 2, 3, 4] test_bg_run_numbers = [5, 6] @@ -559,8 +559,8 @@ def test_preprocess_with_bg(self, mock_load_to_ads_and_crop, mock_replace_specia mock_replace_special_values.assert_called_once_with(test_sample_ws_name, NaNValue=0, NaNError=0, InfinityValue=0, InfinityError=0, OutputWorkspace=test_sample_ws_name) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CropWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_files') + @patch('calibration_scripts.calibrate_vesuvio_fit.CropWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_files') def test_load_to_ads_and_crop(self, mock_load_files, mock_crop_workspace): alg = EVSCalibrationFit() run_numbers = [1, 2, 3, 4] @@ -572,9 +572,9 @@ def test_load_to_ads_and_crop(self, mock_load_files, mock_crop_workspace): mock_load_files.assert_called_once_with(run_numbers, output) mock_crop_workspace.assert_called_once_with(output, XMin=xmin, XMax=xmax, OutputWorkspace=output) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.Divide') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.RebinToWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.Divide') + @patch('calibration_scripts.calibrate_vesuvio_fit.RebinToWorkspace') def test_normalise_sample_by_background(self, mock_rebin, mock_divide, mock_delete): alg = EVSCalibrationFit() sample_ws = 'test_ws' @@ -588,9 +588,9 @@ def test_normalise_sample_by_background(self, mock_rebin, mock_divide, mock_dele mock_divide.assert_called_once_with(sample_ws, bg_ws, OutputWorkspace=sample_ws) mock_delete.assert_called_once_with(bg_ws) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.Plus') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.Plus') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._load_file') def test_load_files(self, mock_load_file, mock_plus, mock_delete): alg = EVSCalibrationFit() ws_numbers = ['1-4'] # Note this is parsed as '1-3', is this intentional? @@ -602,8 +602,8 @@ def test_load_files(self, mock_load_file, mock_plus, mock_delete): call(output_name, '__EVS_calib_temp_ws', OutputWorkspace=output_name)]) mock_delete.assert_has_calls([call('__EVS_calib_temp_ws'), call('__EVS_calib_temp_ws')]) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.LoadRaw') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.LoadVesuvio') + @patch('calibration_scripts.calibrate_vesuvio_fit.LoadRaw') + @patch('calibration_scripts.calibrate_vesuvio_fit.LoadVesuvio') def test_load_file_vesuvio(self, mock_load_vesuvio, mock_load_raw): alg = EVSCalibrationFit() ws_name = 'test_file' @@ -619,9 +619,9 @@ def test_load_file_vesuvio(self, mock_load_vesuvio, mock_load_raw): EnableLogging=False) mock_load_raw.assert_not_called() - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.ConvertToDistribution') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.LoadRaw') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.LoadVesuvio') + @patch('calibration_scripts.calibrate_vesuvio_fit.ConvertToDistribution') + @patch('calibration_scripts.calibrate_vesuvio_fit.LoadRaw') + @patch('calibration_scripts.calibrate_vesuvio_fit.LoadVesuvio') def test_load_file_raw(self, mock_load_vesuvio, mock_load_raw, mock_convert_to_dist): alg = EVSCalibrationFit() ws_name = 'test_file' @@ -638,7 +638,7 @@ def test_load_file_raw(self, mock_load_vesuvio, mock_load_raw, mock_convert_to_d EnableLogging=False) mock_convert_to_dist.assert_called_once_with(output_name, EnableLogging=False) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') + @patch('calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') def test_fit_peaks_individual(self, group_workspaces_mock): alg = EVSCalibrationFit() alg._estimate_peak_positions = MagicMock(return_value=np.asarray([[5, 10, 15], [2.5, 7.5, 10.5]])) @@ -660,8 +660,8 @@ def test_fit_peaks_individual(self, group_workspaces_mock): group_workspaces_mock.assert_called_once_with(['output_ws_name_Peak_0_Parameters', 'output_ws_name_Peak_1_Parameters'], OutputWorkspace='output_ws_name_Peak_Parameters') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._shared_parameter_fit') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._shared_parameter_fit') + @patch('calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') def test_fit_peaks_shared(self, group_workspaces_mock, shared_parameter_fit_mock): alg = EVSCalibrationFit() alg._estimate_peak_positions = MagicMock(return_value=np.asarray([[5, 10, 15], [2.5, 7.5, 10.5]])) @@ -684,8 +684,8 @@ def test_fit_peaks_shared(self, group_workspaces_mock, shared_parameter_fit_mock OutputWorkspace='output_ws_name_Peak_Parameters') shared_parameter_fit_mock.assert_called_once_with('output_ws_name_Peak_1_Parameters', ['a', 'b', 'c']) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._shared_parameter_fit') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._shared_parameter_fit') + @patch('calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') def test_fit_peaks_both(self, group_workspaces_mock, shared_parameter_fit_mock): alg = EVSCalibrationFit() alg._estimate_peak_positions = MagicMock(return_value=np.asarray([[5, 10, 15], [2.5, 7.5, 10.5]])) @@ -723,7 +723,7 @@ def _setup_alg_mocks_fit_peak(): return alg - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.Fit') + @patch('calibration_scripts.calibrate_vesuvio_fit.Fit') def test_fit_peak(self, mock_fit): alg = self._setup_alg_mocks_fit_peak() @@ -747,9 +747,9 @@ def test_fit_peak(self, mock_fit): self.assertEqual(fit_workspace_name, 'fws') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.FindPeaks') def test_find_peaks_and_output_params(self, find_peaks_mock, delete_workspace_mock, mtd_mock): alg = EVSCalibrationFit() alg._sample = 'sample' @@ -767,11 +767,11 @@ def test_find_peaks_and_output_params(self, find_peaks_mock, delete_workspace_mo peak_table_name_ws.rowCount.assert_called_once() delete_workspace_mock.assert_called_once_with('__sample_peaks_table_0_3') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.sys') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.logger.error') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._get_find_peak_parameters') + @patch('calibration_scripts.calibrate_vesuvio_fit.sys') + @patch('calibration_scripts.calibrate_vesuvio_fit.logger.error') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.FindPeaks') + @patch('calibration_scripts.calibrate_vesuvio_fit.EVSCalibrationFit._get_find_peak_parameters') def test_find_peaks_and_output_params_no_peaks_found(self, find_peak_params_mock, find_peaks_mock, mtd_mock, logger_mock, sys_mock): alg = EVSCalibrationFit() @@ -802,7 +802,7 @@ def test_find_fit_x_window(self): self.assertEqual(3, xmin) self.assertEqual(7, xmax) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.logger.warning') + @patch('calibration_scripts.calibrate_vesuvio_fit.logger.warning') def test_find_fit_x_window_position_less_than_1(self, logger_mock): alg = EVSCalibrationFit() alg._func_param_names = {'Position': 'Position_key'} @@ -823,7 +823,7 @@ def _params_column_side_effect(self, col_index): else: raise ValueError("incorrect column index supplied") - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_output_fit_params_to_table_ws(self, mtd_mock): alg = EVSCalibrationFit() spec_num = 45 @@ -838,22 +838,22 @@ def test_output_fit_params_to_table_ws(self, mtd_mock): mtd_mock.__getitem__.assert_called_once_with(output_table_name) output_table_ws_mock.addRow.assert_called_once_with([spec_num, 1, 0.1, 2, 0.2, 3, 0.3]) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') def test_del_fit_workspace(self, del_ws_mock): alg = EVSCalibrationFit() alg._create_output = True alg._del_fit_workspaces('ncm', 'params', 'fws') del_ws_mock.assert_has_calls([call('ncm'), call('params')]) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') def test_del_fit_workspace_create_output_true(self, del_ws_mock): alg = EVSCalibrationFit() alg._create_output = False alg._del_fit_workspaces('ncm', 'params', 'fws') del_ws_mock.assert_has_calls([call('ncm'), call('params'), call('fws')]) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.CreateEmptyTableWorkspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.AnalysisDataService') + @patch('calibration_scripts.calibrate_vesuvio_fit.CreateEmptyTableWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.AnalysisDataService') def test_create_output_parameters_table_ws(self, mock_ADS, mock_create_empty_table_ws): output_table_name = 'test_output_table' num_estimated_peaks = 3 @@ -920,7 +920,7 @@ def test_select_best_fit_params_unconstrained_has_invalid_peaks(self): self.assertEqual(selected_params, fit_results['params']) self.assertFalse(unconstrained_fit_selected) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_output_params_to_table(self, mock_mtd_module): alg = EVSCalibrationFit() spec_num = 1 @@ -949,7 +949,7 @@ def test_output_params_to_table(self, mock_mtd_module): expected_row = [1, 0.1, 2, 0.2, 3, 0.3, 4, 0.4, 5, 0.5] mock_output_table.addRow.assert_called_once_with([spec_num] + expected_row) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') def test_get_output_and_clean_workspaces_unconstrained_not_performed(self, mock_delete_ws): alg = EVSCalibrationFit() find_peaks_output_name = 'test_find_output_name' @@ -960,7 +960,7 @@ def test_get_output_and_clean_workspaces_unconstrained_not_performed(self, mock_ call(find_peaks_output_name)]) self.assertEqual(output_ws, fit_peaks_output_name + '_Workspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') def test_get_output_and_clean_workspaces_unconstrained_performed(self, mock_delete_ws): alg = EVSCalibrationFit() find_peaks_output_name = 'test_find_output_name' @@ -975,7 +975,7 @@ def test_get_output_and_clean_workspaces_unconstrained_performed(self, mock_dele call(fit_peaks_output_name + '_unconstrained_Workspace')]) self.assertEqual(output_ws, fit_peaks_output_name + '_Workspace') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') + @patch('calibration_scripts.calibrate_vesuvio_fit.DeleteWorkspace') def test_get_output_and_clean_workspaces_unconstrained_performed_and_selected(self, mock_delete_ws): alg = EVSCalibrationFit() find_peaks_output_name = 'test_find_output_name' @@ -1026,8 +1026,8 @@ def _setup_mtd_mock(mtd_mock_obj, find_peaks_name, peaks_found): mtd_mock_obj.__getitem__.side_effect = lambda name: mock_find_peaks_output if\ name == find_peaks_name else None - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.FindPeaks') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_run_find_peaks_peaks_found(self, mock_mtd_module, mock_find_peaks): alg, fn_args = self._setup_run_find_peaks_test(unconstrained=False) @@ -1037,8 +1037,8 @@ def test_run_find_peaks_peaks_found(self, mock_mtd_module, mock_find_peaks): mock_find_peaks.assert_called_once() self.assertTrue(result) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.FindPeaks') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_run_find_peaks_no_peaks_found_raises_value_error(self, mock_mtd_module, mock_find_peaks): alg, fn_args = self._setup_run_find_peaks_test(unconstrained=False) @@ -1048,8 +1048,8 @@ def test_run_find_peaks_no_peaks_found_raises_value_error(self, mock_mtd_module, alg._run_find_peaks(**fn_args) mock_find_peaks.assert_called_once() - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.mtd') + @patch('calibration_scripts.calibrate_vesuvio_fit.FindPeaks') + @patch('calibration_scripts.calibrate_vesuvio_fit.mtd') def test_run_find_peaks_unconstrained_no_peaks_found_no_error(self, mock_mtd_module, mock_find_peaks): alg, fn_args = self._setup_run_find_peaks_test(unconstrained=True) @@ -1059,7 +1059,7 @@ def test_run_find_peaks_unconstrained_no_peaks_found_no_error(self, mock_mtd_mod mock_find_peaks.assert_called_once() self.assertFalse(result) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.FindPeaks') + @patch('calibration_scripts.calibrate_vesuvio_fit.FindPeaks') def test_run_find_peaks_unconstrained_peaks_found_raises_error(self, mock_find_peaks): alg, fn_args = self._setup_run_find_peaks_test(unconstrained=True) @@ -1221,7 +1221,7 @@ def _test_calls_individually(self, call_list, calls): except ValueError: np.testing.assert_array_equal(arg, expected_arg) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') + @patch('calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') def test_fit_bragg_peaks_success(self, group_workspaces_mock): fit_result_ret_val = {'status': 'success'} fit_results = lambda *args: fit_result_ret_val @@ -1243,7 +1243,7 @@ def test_fit_bragg_peaks_success(self, group_workspaces_mock): call(False, False, False, 'sample_peaks_table_2', 'output_Spec_2')]) group_workspaces_mock.assert_called_once_with(','.join(output_workspaces), OutputWorkspace='output_Peak_Fits') - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') + @patch('calibration_scripts.calibrate_vesuvio_fit.GroupWorkspaces') def test_fit_bragg_peaks_not_success(self, group_workspaces_mock): x_range = (1, 2) fit_res = {'status': 'failure', 'xmin': x_range[0], 'xmax': x_range[1]} diff --git a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_misc.py b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_misc.py index c3010c17..d995dff3 100644 --- a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_misc.py +++ b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_misc.py @@ -1,4 +1,4 @@ -from unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions import EVSMiscFunctions +from calibration_scripts.calibrate_vesuvio_helper_functions import EVSMiscFunctions from mock import MagicMock, patch import unittest @@ -34,7 +34,7 @@ def test_generate_header_function_invalid(self): with self.assertRaises(ValueError): EVSMiscFunctions.generate_fit_function_header("Invalid") - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions.mtd') + @patch('calibration_scripts.calibrate_vesuvio_helper_functions.mtd') def test_identify_invalid_spectra_no_invalid(self, mock_mtd): ws_mock = MagicMock() mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [0.1, 0.2, 0.3], @@ -46,7 +46,7 @@ def test_identify_invalid_spectra_no_invalid(self, mock_mtd): np.testing.assert_equal(np.argwhere([]), EVSMiscFunctions.identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions.mtd') + @patch('calibration_scripts.calibrate_vesuvio_helper_functions.mtd') def test_identify_invalid_spectra_nan_in_errors(self, mock_mtd): ws_mock = MagicMock() mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [np.nan, 0.2, 0.3], @@ -58,7 +58,7 @@ def test_identify_invalid_spectra_nan_in_errors(self, mock_mtd): np.testing.assert_equal(np.argwhere(np.array([True, False, True])), EVSMiscFunctions.identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions.mtd') + @patch('calibration_scripts.calibrate_vesuvio_helper_functions.mtd') def test_identify_invalid_spectra_inf_in_errors(self, mock_mtd): ws_mock = MagicMock() mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [0.1, 0.2, 0.3], @@ -70,7 +70,7 @@ def test_identify_invalid_spectra_inf_in_errors(self, mock_mtd): np.testing.assert_equal(np.argwhere(np.array([True, True, False])), EVSMiscFunctions.identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) - @patch('unpackaged.vesuvio_calibration.calibration_scripts.calibrate_vesuvio_helper_functions.mtd') + @patch('calibration_scripts.calibrate_vesuvio_helper_functions.mtd') def test_identify_invalid_spectra_error_greater_than_peak(self, mock_mtd): ws_mock = MagicMock() mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [0.1, 0.2, 0.3],