From 58f227eae00565fd6114ba48c4cd632042abdb2a Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Thu, 20 Apr 2023 12:49:36 +0100 Subject: [PATCH 01/15] fix create output and clean workspaces --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 26 ++++++++++++------- .../tests/system/test_system.py | 17 ++++++++++++ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index dba94119..f70057e6 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -420,7 +420,7 @@ def _fit_bragg_peaks(self): 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, unconstrained_fit_selected, find_peaks_output_name, + self._get_output_and_clean_workspaces(fit_results_unconstrained is not None, fit_results_unconstrained is not False, unconstrained_fit_selected, find_peaks_output_name, fit_peaks_output_name)) if self._create_output: @@ -466,6 +466,8 @@ def _fit_peaks_to_spectra(self, workspace_index, spec_number, peak_estimates_lis 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): @@ -519,7 +521,7 @@ def _output_params_to_table(self, spec_num, num_estimated_peaks, params, output_ mtd[output_table_name].addRow([spec_num] + row) - def _get_output_and_clean_workspaces(self, unconstrained_fit_performed, unconstrained_fit_selected, find_peaks_output_name, + 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) @@ -530,14 +532,17 @@ def _get_output_and_clean_workspaces(self, unconstrained_fit_performed, unconstr output_workspace = fit_peaks_output_name + '_Workspace' if unconstrained_fit_performed: - DeleteWorkspace(fit_peaks_output_name_u + '_NormalisedCovarianceMatrix') - DeleteWorkspace(fit_peaks_output_name_u + '_Parameters') DeleteWorkspace(find_peaks_output_name_u) - 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') + + 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 @@ -1137,8 +1142,9 @@ def _generate_fit_workspaces(self): """ Output the fit workspace for each spectrum fitted. """ - fit_workspaces = map(list, zip(*self._peak_fit_workspaces_by_spec)) + 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) diff --git a/unpackaged/vesuvio_calibration/tests/system/test_system.py b/unpackaged/vesuvio_calibration/tests/system/test_system.py index 0f76ab49..6636f2e8 100644 --- a/unpackaged/vesuvio_calibration/tests/system/test_system.py +++ b/unpackaged/vesuvio_calibration/tests/system/test_system.py @@ -319,6 +319,23 @@ 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.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) + def tearDown(self): mtd.clear() From f61a6e58d987b9f50c41142f548b4469ec7b595f Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Thu, 20 Apr 2023 13:37:42 +0100 Subject: [PATCH 02/15] ensure theta fit table has correct name --- .../vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index f70057e6..a2d4555f 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -431,7 +431,7 @@ 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() + table = CreateEmptyTableWorkspace(OutputWorkspace=output_table_name) col_headers = self._generate_column_headers(num_estimated_peaks) From 3445323b73dfc9a88eb0dab684d09e592e28025a Mon Sep 17 00:00:00 2001 From: Jess Farmer Date: Thu, 23 Mar 2023 16:27:22 +0000 Subject: [PATCH 03/15] add function for shared parameter fitting --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 155 +++++++++++++++++- 1 file changed, 148 insertions(+), 7 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index a2d4555f..dad354fc 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -25,6 +25,7 @@ import scipy.constants import scipy.stats import numpy as np +import time # Configuration for Uranium runs / Indium runs #---------------------------------------------------------------------------------------- @@ -105,6 +106,37 @@ def calculate_r_theta(sample_mass, 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. @@ -261,6 +293,8 @@ def PyInit(self): self.declareProperty('OutputWorkspace', '', StringMandatoryValidator(), doc="Name to call the output workspace.") + + self.declareProperty('CalculateSharedParameter', False, doc='Calculate shared parameter across all detectors') #---------------------------------------------------------------------------------------- @@ -299,6 +333,7 @@ def _setup_class_variables_from_properties(self): self._energy_estimates = self.getProperty("Energy").value self._sample_mass = self.getProperty("Mass").value self._create_output = self.getProperty("CreateOutput").value + self._calculate_shared_parameter = self.getProperty("CalculateSharedParameter").value def _setup_spectra_list(self): self._spec_list = self.getProperty("SpectrumRange").value.tolist() @@ -754,6 +789,13 @@ def _fit_peaks(self): GroupWorkspaces(self._output_parameter_tables, OutputWorkspace=self._output_workspace_name + '_Peak_Parameters') + if self._calculate_shared_parameter == True: + 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(estimated_peak_positions_all_peaks, invalid_spectra) + 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)) @@ -814,6 +856,106 @@ def _del_fit_workspaces(self, ncm, fit_params, fws): if not self._create_output: DeleteWorkspace(fws) +#---------------------------------------------------------------------------------------- + + def _fit_shared_parameter(self, peak_positions, invalid_spectra): + """ + Fit peaks to all detectors, with one set of fit parameters for all detectors. + """ + + peak_centre = np.float(np.nanmean(peak_positions, axis=1)) + find_peak_params = self._get_find_peak_parameters(self._spec_list[0], [peak_centre]) + + peaks_table = '__' + self._sample + '_peaks_table' + param_names = self._create_parameter_table(peaks_table) + peak_table = '__' + self._sample + '_peak_table' + FindPeaks(InputWorkspace=self._sample, 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() + + fit_func = self._build_function_string(peak_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' + + #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.') + + # 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') + start = time.time() + + 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) + + end = time.time() + print(end-start) + + [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[peaks_table].addRow([0] + row) + + self._parameter_tables = peaks_table + self._peak_fit_workspaces = fws.name() + + DeleteWorkspace(ncm) + DeleteWorkspace(params) + if not self._create_output: + DeleteWorkspace(fws) + + #self._fit_workspaces = self._peak_fit_workspaces + + GroupWorkspaces(self._parameter_tables, OutputWorkspace=self._output_workspace_name + '_Shared_Peak_Parameters') + +>>>>>>> 1b75c5c (add function for shared parameter fitting) #---------------------------------------------------------------------------------------- def _get_find_peak_parameters(self, spec_number, peak_centre, unconstrained=False): @@ -1300,12 +1442,11 @@ def PyExec(self): 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 + #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') - + PeakType='Recoil', CalculateSharedParameter=True) E1_peak_fits_back = mtd[self._current_workspace + '_E1_back_Peak_Parameters'].getNames()[0] self._calculate_final_energy(E1_peak_fits_back, BACKSCATTERING_RANGE) @@ -1317,7 +1458,7 @@ def PyExec(self): 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') + PeakType='Recoil', CalculateSharedParameter=True) E1_peak_fits_front = mtd[self._current_workspace + '_E1_front_Peak_Parameters'].getNames()[0] self._calculate_final_flight_path(E1_peak_fits_front, FRONTSCATTERING_RANGE) @@ -1434,7 +1575,7 @@ def _calculate_final_flight_path(self, peak_table, spec_list): 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 = self._identify_invalid_spectra(peak_table, peak_centres, peak_centres_errors, 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:' @@ -1529,7 +1670,7 @@ def _calculate_scattering_angle(self, table_name, spec_list): #---------------------------------------------------------------------------------------- - + def _calculate_final_energy(self, peak_table, spec_list): """ @@ -1554,7 +1695,7 @@ def _calculate_final_energy(self, peak_table, spec_list): 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 = self._identify_invalid_spectra(peak_table, peak_centres, peak_centres_errors, spec_list) + invalid_spectra = identify_invalid_spectra(peak_table, peak_centres, peak_centres_errors, spec_list) peak_centres[invalid_spectra] = np.nan delta_t = (peak_centres - t0) / 1e+6 From 4c9e8bcfcce925e409adb16f28e6c6bcd6bae818 Mon Sep 17 00:00:00 2001 From: Jess Farmer Date: Fri, 24 Mar 2023 15:47:14 +0000 Subject: [PATCH 04/15] set initial parameters for multifit --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index dad354fc..7d966ea2 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -790,11 +790,21 @@ def _fit_peaks(self): GroupWorkspaces(self._output_parameter_tables, OutputWorkspace=self._output_workspace_name + '_Peak_Parameters') if self._calculate_shared_parameter == True: + 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(estimated_peak_positions_all_peaks, invalid_spectra) + 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 @@ -843,7 +853,6 @@ def _find_fit_x_window(self, peak_params): 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] @@ -858,37 +867,18 @@ def _del_fit_workspaces(self, ncm, fit_params, fws): #---------------------------------------------------------------------------------------- - def _fit_shared_parameter(self, peak_positions, invalid_spectra): + 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. """ - peak_centre = np.float(np.nanmean(peak_positions, axis=1)) - find_peak_params = self._get_find_peak_parameters(self._spec_list[0], [peak_centre]) - peaks_table = '__' + self._sample + '_peaks_table' - param_names = self._create_parameter_table(peaks_table) - peak_table = '__' + self._sample + '_peak_table' - FindPeaks(InputWorkspace=self._sample, 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() - - fit_func = self._build_function_string(peak_params) - + + 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 From a995df2de7d04db3dfe0eeba0b9c2e4cb073ba7f Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Mon, 17 Apr 2023 17:11:06 +0100 Subject: [PATCH 05/15] minimal changes to make script work --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index 7d966ea2..bf739594 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -16,7 +16,8 @@ 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 + ConjoinWorkspaces, Transpose, PlotPeakByLogValue, CloneWorkspace, Fit, RenameWorkspace, MaskDetectors,\ + ExtractUnmaskedSpectra, CreateWorkspace from functools import partial @@ -780,8 +781,8 @@ def _fit_peaks(self): 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) + fit_workspace_name, x_range = 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) @@ -804,7 +805,7 @@ def _fit_peaks(self): 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) + self._fit_shared_parameter(invalid_spectra, initial_params, output_parameter_table_headers, output_parameter_table_name, x_range) 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 @@ -825,7 +826,7 @@ def _fit_peak(self, peak_index, spec_index, peak_position, output_parameter_tabl fit_workspace_name = fws.name() self._del_fit_workspaces(ncm, fit_params, fws) - return fit_workspace_name + return fit_workspace_name, (xmin, xmax) 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) @@ -867,12 +868,14 @@ def _del_fit_workspaces(self, ncm, fit_params, fws): #---------------------------------------------------------------------------------------- - def _fit_shared_parameter(self, invalid_spectra, initial_params, param_names): + def _fit_shared_parameter(self, invalid_spectra, initial_params, param_names, old_peak_table, x_range): """ Fit peaks to all detectors, with one set of fit parameters for all detectors. """ - + peaks_table = '__' + self._sample + '_peaks_table' + CloneWorkspace(InputWorkspace=old_peak_table, OutputWorkspace=peaks_table) + mtd[peaks_table].setRowCount(0) fit_func = self._build_function_string(initial_params) start_str = 'composite=MultiDomainFunction, NumDeriv=1;' @@ -889,14 +892,7 @@ def _fit_shared_parameter(self, invalid_spectra, initial_params, param_names): fit_output_name = '__' + self._output_workspace_name + '_Peak_0' - #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.') + xmin, xmax = x_range # create new workspace for each spectra x = validSpecs.readX(0) @@ -944,8 +940,7 @@ def _fit_shared_parameter(self, invalid_spectra, initial_params, param_names): #self._fit_workspaces = self._peak_fit_workspaces GroupWorkspaces(self._parameter_tables, OutputWorkspace=self._output_workspace_name + '_Shared_Peak_Parameters') - ->>>>>>> 1b75c5c (add function for shared parameter fitting) + #---------------------------------------------------------------------------------------- def _get_find_peak_parameters(self, spec_number, peak_centre, unconstrained=False): From 755811a44b29174ca4e0845f860d6e7699b5e006 Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Thu, 20 Apr 2023 14:10:00 +0100 Subject: [PATCH 06/15] manual merge jess updates --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 71 ++++++++++++------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index bf739594..f26377f7 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -805,7 +805,7 @@ def _fit_peaks(self): 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, output_parameter_table_name, x_range) + 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 @@ -868,14 +868,20 @@ def _del_fit_workspaces(self, ncm, fit_params, fws): #---------------------------------------------------------------------------------------- - def _fit_shared_parameter(self, invalid_spectra, initial_params, param_names, old_peak_table, x_range): + 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. """ - peaks_table = '__' + self._sample + '_peaks_table' - CloneWorkspace(InputWorkspace=old_peak_table, OutputWorkspace=peaks_table) - mtd[peaks_table].setRowCount(0) + 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;' @@ -892,7 +898,7 @@ def _fit_shared_parameter(self, invalid_spectra, initial_params, param_names, ol fit_output_name = '__' + self._output_workspace_name + '_Peak_0' - xmin, xmax = x_range + xmin, xmax = None, None # create new workspace for each spectra x = validSpecs.readX(0) @@ -908,16 +914,11 @@ def _fit_shared_parameter(self, invalid_spectra, initial_params, param_names, ol added_args = {f'InputWorkspace_{i + 1}': v for i,v in enumerate(other_inputs)} print('starting global fit') - start = time.time() 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) - - end = time.time() - print(end-start) - [DeleteWorkspace(f"{self._sample}_Spec_{i}") for i in range(0,n_valid_specs)] fit_values = dict(zip(params.column(0), params.column(1))) @@ -926,20 +927,17 @@ def _fit_shared_parameter(self, invalid_spectra, initial_params, param_names, ol 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[peaks_table].addRow([0] + row) - self._parameter_tables = peaks_table - self._peak_fit_workspaces = fws.name() + mtd[shared_peak_table].addRow([0] + row) DeleteWorkspace(ncm) DeleteWorkspace(params) if not self._create_output: DeleteWorkspace(fws) - #self._fit_workspaces = self._peak_fit_workspaces - - GroupWorkspaces(self._parameter_tables, OutputWorkspace=self._output_workspace_name + '_Shared_Peak_Parameters') + DeleteWorkspace(validSpecs) + + mtd[self._output_workspace_name + '_Peak_Parameters'].add(shared_peak_table) #---------------------------------------------------------------------------------------- @@ -1433,8 +1431,8 @@ def PyExec(self): InstrumentParameterWorkspace=self._param_table, Mass=self._sample_mass, OutputWorkspace=E1_fit_back, CreateOutput=self._create_output, PeakType='Recoil', CalculateSharedParameter=True) - E1_peak_fits_back = mtd[self._current_workspace + '_E1_back_Peak_Parameters'].getNames()[0] - self._calculate_final_energy(E1_peak_fits_back, BACKSCATTERING_RANGE) + E1_peak_fits_back = mtd[self._current_workspace + '_E1_back_Peak_Parameters'].getNames() + self._calculate_final_energy(E1_peak_fits_back[0], BACKSCATTERING_RANGE) # calibrate L1 for backscattering detectors based on the averaged E1 value and calibrated theta values self._calculate_final_flight_path(E1_peak_fits_back, BACKSCATTERING_RANGE) @@ -1445,8 +1443,8 @@ def PyExec(self): InstrumentParameterWorkspace=self._param_table, Mass=self._sample_mass, OutputWorkspace=E1_fit_front, CreateOutput=self._create_output, PeakType='Recoil', CalculateSharedParameter=True) - E1_peak_fits_front = mtd[self._current_workspace + '_E1_front_Peak_Parameters'].getNames()[0] - self._calculate_final_flight_path(E1_peak_fits_front, FRONTSCATTERING_RANGE) + 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. @@ -1668,6 +1666,8 @@ def _calculate_final_energy(self, peak_table, spec_list): 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) @@ -1677,10 +1677,11 @@ def _calculate_final_energy(self, peak_table, 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) + 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, peak_centres, peak_centres_errors, 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 @@ -1691,15 +1692,33 @@ def _calculate_final_energy(self, peak_table, spec_list): 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] - + + # calculate global energy + 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) + mean_E1.fill(mean_E1_val) E1_error.fill(E1_error_val) + global_E1.fill(global_E1_val) + global_E1_error.fill(global_E1_error_val) + self._set_table_column(self._current_workspace, 'E1', mean_E1) self._set_table_column(self._current_workspace, 'E1_Err', E1_error) + self._set_table_column(self._current_workspace, 'global_E1', global_E1) + self._set_table_column(self._current_workspace, 'global_E1_Err', global_E1_error) #---------------------------------------------------------------------------------------- From 00f6531a23380da0963e1f9617dd6dc7ad5abb3a Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Thu, 20 Apr 2023 14:55:05 +0100 Subject: [PATCH 07/15] correct merge error --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index f26377f7..c019230e 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -1432,10 +1432,10 @@ def PyExec(self): PeakType='Recoil', CalculateSharedParameter=True) E1_peak_fits_back = mtd[self._current_workspace + '_E1_back_Peak_Parameters'].getNames() - self._calculate_final_energy(E1_peak_fits_back[0], BACKSCATTERING_RANGE) + self._calculate_final_energy(E1_peak_fits_back, BACKSCATTERING_RANGE) # calibrate L1 for backscattering detectors based on the averaged E1 value and calibrated theta values - self._calculate_final_flight_path(E1_peak_fits_back, BACKSCATTERING_RANGE) + 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' @@ -1680,7 +1680,6 @@ def _calculate_final_energy(self, peak_table, spec_list): 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 From 617b7eddde67fc4d920631582a95c92698ca5a88 Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Tue, 18 Apr 2023 11:00:57 +0100 Subject: [PATCH 08/15] rename shared param --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index c019230e..2984688c 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -295,7 +295,7 @@ def PyInit(self): self.declareProperty('OutputWorkspace', '', StringMandatoryValidator(), doc="Name to call the output workspace.") - self.declareProperty('CalculateSharedParameter', False, doc='Calculate shared parameter across all detectors') + self.declareProperty('GlobalFitSharedParameter', False, doc='Perform a global fit to calculate shared parameter across all detectors') #---------------------------------------------------------------------------------------- @@ -334,7 +334,7 @@ def _setup_class_variables_from_properties(self): self._energy_estimates = self.getProperty("Energy").value self._sample_mass = self.getProperty("Mass").value self._create_output = self.getProperty("CreateOutput").value - self._calculate_shared_parameter = self.getProperty("CalculateSharedParameter").value + self._global_fit_shared_parameter = self.getProperty("GlobalFitSharedParameter").value def _setup_spectra_list(self): self._spec_list = self.getProperty("SpectrumRange").value.tolist() @@ -790,7 +790,7 @@ def _fit_peaks(self): GroupWorkspaces(self._output_parameter_tables, OutputWorkspace=self._output_workspace_name + '_Peak_Parameters') - if self._calculate_shared_parameter == True: + if self._global_fit_shared_parameter == True: 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) @@ -1429,7 +1429,7 @@ def PyExec(self): 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', CalculateSharedParameter=True) + PeakType='Recoil', GlobalFitSharedParameter=False) E1_peak_fits_back = mtd[self._current_workspace + '_E1_back_Peak_Parameters'].getNames() self._calculate_final_energy(E1_peak_fits_back, BACKSCATTERING_RANGE) @@ -1441,7 +1441,7 @@ def PyExec(self): 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', CalculateSharedParameter=True) + PeakType='Recoil', GlobalFitSharedParameter=False) 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) From e1ba4420eabe6f42a3bf809cbf4dca0025138eca Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Tue, 18 Apr 2023 16:58:11 +0100 Subject: [PATCH 09/15] add drop down for shared parameter fit type --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index 2984688c..2f7eae83 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -286,6 +286,10 @@ def PyInit(self): 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.') @@ -294,8 +298,6 @@ def PyInit(self): self.declareProperty('OutputWorkspace', '', StringMandatoryValidator(), doc="Name to call the output workspace.") - - self.declareProperty('GlobalFitSharedParameter', False, doc='Perform a global fit to calculate shared parameter across all detectors') #---------------------------------------------------------------------------------------- @@ -334,7 +336,7 @@ def _setup_class_variables_from_properties(self): self._energy_estimates = self.getProperty("Energy").value self._sample_mass = self.getProperty("Mass").value self._create_output = self.getProperty("CreateOutput").value - self._global_fit_shared_parameter = self.getProperty("GlobalFitSharedParameter").value + self._global_fit_shared_parameter = self.getProperty("SharedParameterFitType").value def _setup_spectra_list(self): self._spec_list = self.getProperty("SpectrumRange").value.tolist() @@ -1357,6 +1359,10 @@ def PyInit(self): 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.") @@ -1429,7 +1435,7 @@ def PyExec(self): 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', GlobalFitSharedParameter=False) + 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) @@ -1441,7 +1447,7 @@ def PyExec(self): 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', GlobalFitSharedParameter=False) + 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) @@ -1731,6 +1737,7 @@ def _setup(self): 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") From fc2a8f1b53fb00d513063f9049c24942cbe9421b Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Thu, 20 Apr 2023 16:33:02 +0100 Subject: [PATCH 10/15] abstract global code --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index 2f7eae83..1f41b547 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -336,7 +336,7 @@ def _setup_class_variables_from_properties(self): self._energy_estimates = self.getProperty("Energy").value self._sample_mass = self.getProperty("Mass").value self._create_output = self.getProperty("CreateOutput").value - self._global_fit_shared_parameter = self.getProperty("SharedParameterFitType").value + self._shared_parameter_fit_type = self.getProperty("SharedParameterFitType").value def _setup_spectra_list(self): self._spec_list = self.getProperty("SpectrumRange").value.tolist() @@ -792,22 +792,25 @@ def _fit_peaks(self): GroupWorkspaces(self._output_parameter_tables, OutputWorkspace=self._output_workspace_name + '_Peak_Parameters') - if self._global_fit_shared_parameter == True: - 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) + 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 From 30d5608d1cc30aaa444219b2199843cb8ce6cb12 Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:12:31 +0100 Subject: [PATCH 11/15] fix unit tests --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 11 ++- .../tests/unit/test_calibrate_vesuvio_fit.py | 91 ++++++++++++++----- 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index 1f41b547..2e22072b 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -458,8 +458,9 @@ def _fit_bragg_peaks(self): 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 False, unconstrained_fit_selected, find_peaks_output_name, - fit_peaks_output_name)) + 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") @@ -783,8 +784,8 @@ def _fit_peaks(self): 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, x_range = self._fit_peak(peak_index, spec_index, peak_position, output_parameter_table_name, - output_parameter_table_headers) + 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) @@ -831,7 +832,7 @@ def _fit_peak(self, peak_index, spec_index, peak_position, output_parameter_tabl fit_workspace_name = fws.name() self._del_fit_workspaces(ncm, fit_params, fws) - return fit_workspace_name, (xmin, xmax) + 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) 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 bc4181e2..cc3e0921 100644 --- a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_fit.py +++ b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_fit.py @@ -641,12 +641,13 @@ def test_load_file_raw(self, mock_load_vesuvio, mock_load_raw, mock_convert_to_d mock_convert_to_dist.assert_called_once_with(output_name, EnableLogging=False) @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.GroupWorkspaces') - def test_fit_peaks(self, group_workspaces_mock): + 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]])) alg._create_parameter_table_and_output_headers = MagicMock(return_value=['a', 'b', 'c']) alg._fit_peak = MagicMock(side_effect=lambda a, b, c, d, e: f'fit_ws_{a}_{b}') alg._output_workspace_name = 'output_ws_name' + alg._shared_parameter_fit_type = 'Individual' alg._fit_peaks() alg._create_parameter_table_and_output_headers.assert_has_calls([call('output_ws_name_Peak_0_Parameters'), call('output_ws_name_Peak_1_Parameters')]) @@ -661,6 +662,54 @@ def test_fit_peaks(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') + 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]])) + alg._create_parameter_table_and_output_headers = MagicMock(return_value=['a', 'b', 'c']) + alg._fit_peak = MagicMock(side_effect=lambda a, b, c, d, e: f'fit_ws_{a}_{b}') + alg._output_workspace_name = 'output_ws_name' + alg._shared_parameter_fit_type = 'Shared' + alg._fit_peaks() + alg._create_parameter_table_and_output_headers.assert_has_calls([call('output_ws_name_Peak_0_Parameters'), + call('output_ws_name_Peak_1_Parameters')]) + alg._fit_peak.assert_has_calls([call(0, 0, 5, 'output_ws_name_Peak_0_Parameters', ['a', 'b', 'c']), + call(0, 1, 10, 'output_ws_name_Peak_0_Parameters', ['a', 'b', 'c']), + call(0, 2, 15, 'output_ws_name_Peak_0_Parameters', ['a', 'b', 'c']), + call(1, 0, 2.5, 'output_ws_name_Peak_1_Parameters', ['a', 'b', 'c']), + call(1, 1, 7.5, 'output_ws_name_Peak_1_Parameters', ['a', 'b', 'c']), + call(1, 2, 10.5, 'output_ws_name_Peak_1_Parameters', ['a', 'b', 'c'])]) + self.assertEqual([['fit_ws_0_0', 'fit_ws_0_1', 'fit_ws_0_2'], ['fit_ws_1_0', 'fit_ws_1_1', 'fit_ws_1_2']], + alg._peak_fit_workspaces) + 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') + 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') + 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]])) + alg._create_parameter_table_and_output_headers = MagicMock(return_value=['a', 'b', 'c']) + alg._fit_peak = MagicMock(side_effect=lambda a, b, c, d, e: f'fit_ws_{a}_{b}') + alg._output_workspace_name = 'output_ws_name' + alg._shared_parameter_fit_type = 'Both' + alg._fit_peaks() + alg._create_parameter_table_and_output_headers.assert_has_calls([call('output_ws_name_Peak_0_Parameters'), + call('output_ws_name_Peak_1_Parameters')]) + alg._fit_peak.assert_has_calls([call(0, 0, 5, 'output_ws_name_Peak_0_Parameters', ['a', 'b', 'c']), + call(0, 1, 10, 'output_ws_name_Peak_0_Parameters', ['a', 'b', 'c']), + call(0, 2, 15, 'output_ws_name_Peak_0_Parameters', ['a', 'b', 'c']), + call(1, 0, 2.5, 'output_ws_name_Peak_1_Parameters', ['a', 'b', 'c']), + call(1, 1, 7.5, 'output_ws_name_Peak_1_Parameters', ['a', 'b', 'c']), + call(1, 2, 10.5, 'output_ws_name_Peak_1_Parameters', ['a', 'b', 'c'])]) + self.assertEqual([['fit_ws_0_0', 'fit_ws_0_1', 'fit_ws_0_2'], ['fit_ws_1_0', 'fit_ws_1_1', 'fit_ws_1_2']], + alg._peak_fit_workspaces) + 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') + shared_parameter_fit_mock.assert_called_once_with('output_ws_name_Peak_1_Parameters', ['a', 'b', 'c']) + @staticmethod def _setup_alg_mocks_fit_peak(): alg = EVSCalibrationFit() @@ -907,7 +956,7 @@ def test_get_output_and_clean_workspaces_unconstrained_not_performed(self, mock_ alg = EVSCalibrationFit() find_peaks_output_name = 'test_find_output_name' fit_peaks_output_name = 'test_fit_output_name' - output_ws = alg._get_output_and_clean_workspaces(False, False, find_peaks_output_name, fit_peaks_output_name) + output_ws = alg._get_output_and_clean_workspaces(False, False, False, find_peaks_output_name, fit_peaks_output_name) mock_delete_ws.assert_has_calls([call(fit_peaks_output_name + '_NormalisedCovarianceMatrix'), call(fit_peaks_output_name + '_Parameters'), call(find_peaks_output_name)]) @@ -918,14 +967,14 @@ def test_get_output_and_clean_workspaces_unconstrained_performed(self, mock_dele alg = EVSCalibrationFit() find_peaks_output_name = 'test_find_output_name' fit_peaks_output_name = 'test_fit_output_name' - output_ws = alg._get_output_and_clean_workspaces(True, False, find_peaks_output_name, fit_peaks_output_name) + output_ws = alg._get_output_and_clean_workspaces(True, True, False, find_peaks_output_name, fit_peaks_output_name) mock_delete_ws.assert_has_calls([call(fit_peaks_output_name + '_NormalisedCovarianceMatrix'), - call(fit_peaks_output_name + '_Parameters'), - call(find_peaks_output_name), - call(fit_peaks_output_name + '_unconstrained' + '_NormalisedCovarianceMatrix'), - call(fit_peaks_output_name + '_unconstrained' + '_Parameters'), - call(find_peaks_output_name + '_unconstrained'), - call(fit_peaks_output_name + '_unconstrained' + '_Workspace')]) + call(fit_peaks_output_name + '_Parameters'), + call(find_peaks_output_name), + call(find_peaks_output_name + '_unconstrained'), + call(fit_peaks_output_name + '_unconstrained_NormalisedCovarianceMatrix'), + call(fit_peaks_output_name + '_unconstrained_Parameters'), + 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') @@ -933,14 +982,14 @@ def test_get_output_and_clean_workspaces_unconstrained_performed_and_selected(se alg = EVSCalibrationFit() find_peaks_output_name = 'test_find_output_name' fit_peaks_output_name = 'test_fit_output_name' - output_ws = alg._get_output_and_clean_workspaces(True, True, find_peaks_output_name, fit_peaks_output_name) + output_ws = alg._get_output_and_clean_workspaces(True, True, True, find_peaks_output_name, fit_peaks_output_name) mock_delete_ws.assert_has_calls([call(fit_peaks_output_name + '_NormalisedCovarianceMatrix'), - call(fit_peaks_output_name + '_Parameters'), - call(find_peaks_output_name), - call(fit_peaks_output_name + '_unconstrained' + '_NormalisedCovarianceMatrix'), - call(fit_peaks_output_name + '_unconstrained' + '_Parameters'), - call(find_peaks_output_name + '_unconstrained'), - call(fit_peaks_output_name + '_Workspace')]) + call(fit_peaks_output_name + '_Parameters'), + call(find_peaks_output_name), + call(find_peaks_output_name + '_unconstrained'), + call(fit_peaks_output_name + '_unconstrained_NormalisedCovarianceMatrix'), + call(fit_peaks_output_name + '_unconstrained_Parameters'), + call(fit_peaks_output_name + '_Workspace')]) self.assertEqual(output_ws, fit_peaks_output_name + '_unconstrained' + '_Workspace') def test_generate_column_headers(self): @@ -1145,7 +1194,7 @@ def test_fit_peaks_to_spectra_not_unconstrained_peaks_not_found(self): alg._get_find_peak_parameters.assert_called_once_with(spec_number, peak_estimates_list, False) alg._run_find_peaks.assert_called_once_with(workspace_index, find_peaks_output_name, 'find_peaks_params', False) alg._filter_and_fit_found_peaks.assert_not_called() - self.assertEqual(result, None) + self.assertEqual(result, False) @staticmethod def _setup_fit_bragg_peaks_mocks(fit_results, selected_params, output_workspaces): @@ -1191,8 +1240,8 @@ def test_fit_bragg_peaks_success(self, group_workspaces_mock): call(2, fit_result_ret_val, None)]) alg._output_params_to_table.assert_has_calls([call(1, 2, selected_params[0], 'output_Peak_Parameters'), call(2, 2, selected_params[1], 'output_Peak_Parameters')]) - alg._get_output_and_clean_workspaces.assert_has_calls([call(False, False, 'sample_peaks_table_1', 'output_Spec_1'), - call(False, False, 'sample_peaks_table_2', 'output_Spec_2')]) + alg._get_output_and_clean_workspaces.assert_has_calls([call(False, False, False, 'sample_peaks_table_1', 'output_Spec_1'), + 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') @@ -1217,8 +1266,8 @@ def test_fit_bragg_peaks_not_success(self, group_workspaces_mock): call(2, fit_res, rit_u_res)]) alg._output_params_to_table.assert_has_calls([call(1, 2, selected_params[0], 'output_Peak_Parameters'), call(2, 2, selected_params[1], 'output_Peak_Parameters')]) - alg._get_output_and_clean_workspaces.assert_has_calls([call(True, False, 'sample_peaks_table_1', 'output_Spec_1'), - call(True, False, 'sample_peaks_table_2', 'output_Spec_2')]) + alg._get_output_and_clean_workspaces.assert_has_calls([call(True, True, False, 'sample_peaks_table_1', 'output_Spec_1'), + call(True, True, False, 'sample_peaks_table_2', 'output_Spec_2')]) group_workspaces_mock.assert_called_once_with(','.join(output_workspaces), OutputWorkspace='output_Peak_Fits') From 1df69836b1f579e1899c35ed22cdea30a55976f6 Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Mon, 24 Apr 2023 13:44:54 +0100 Subject: [PATCH 12/15] add unit test for global fit --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 43 ++++++++++--------- .../tests/system/test_system.py | 27 ++++++++++++ 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index 2e22072b..dc94df87 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -1440,9 +1440,9 @@ def PyExec(self): 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._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) @@ -1452,7 +1452,7 @@ def PyExec(self): 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) @@ -1665,7 +1665,7 @@ def _calculate_scattering_angle(self, table_name, spec_list): - def _calculate_final_energy(self, peak_table, 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. @@ -1706,28 +1706,29 @@ def _calculate_final_energy(self, peak_table, spec_list): mean_E1_val = self._E1_value_and_error[0] E1_error_val = self._E1_value_and_error[1] - # calculate global energy - 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) - mean_E1.fill(mean_E1_val) E1_error.fill(E1_error_val) - global_E1.fill(global_E1_val) - global_E1_error.fill(global_E1_error_val) - self._set_table_column(self._current_workspace, 'E1', mean_E1) self._set_table_column(self._current_workspace, 'E1_Err', E1_error) - self._set_table_column(self._current_workspace, 'global_E1', global_E1) - self._set_table_column(self._current_workspace, 'global_E1_Err', global_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) #---------------------------------------------------------------------------------------- diff --git a/unpackaged/vesuvio_calibration/tests/system/test_system.py b/unpackaged/vesuvio_calibration/tests/system/test_system.py index 6636f2e8..aa52bf06 100644 --- a/unpackaged/vesuvio_calibration/tests/system/test_system.py +++ b/unpackaged/vesuvio_calibration/tests/system/test_system.py @@ -336,6 +336,24 @@ 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.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() @@ -366,6 +384,15 @@ def assert_L1_parameters_match_expected(self, params_table, rel_tolerance): 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) From 3a92c591b88d9aa0e2bcc5864898cc2132b83b7c Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:18:51 +0100 Subject: [PATCH 13/15] remove duplicated fn --- .../calibrate_vesuvio_uranium_martyn_MK5.py | 37 +------------------ 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index dc94df87..1caae9cd 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -138,7 +138,6 @@ def identify_invalid_spectra(peak_table, peak_centres, peak_centres_errors, spec 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 @@ -288,7 +287,7 @@ def PyInit(self): 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) + 'global fit.', validator=shared_fit_type_validator) self.declareProperty(ITableWorkspaceProperty("InstrumentParameterWorkspace", "", Direction.Input, PropertyMode.Optional), doc='Workspace contain instrument parameters.') @@ -1365,7 +1364,7 @@ def PyInit(self): 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) + 'global fit.', validator=shared_fit_type_validator) self.declareProperty('CreateOutput', False, doc="Whether to create output from fitting.") @@ -1585,38 +1584,6 @@ def _calculate_final_flight_path(self, peak_table, spec_list): 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 _identify_invalid_spectra(self, 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 peak_centres - a list of the found peak centres - @param peak_centres_errors - a list of errors associated with the peak centres - @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 - # ---------------------------------------------------------------------------------------- def _calculate_scattering_angle(self, table_name, spec_list): From ec8ddc3c1b95ac48fb07dbc55488f9ead093d731 Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:32:49 +0100 Subject: [PATCH 14/15] move unit tests --- .../unit/test_calibrate_vesuvio_analysis.py | 54 +------------------ .../tests/unit/test_calibrate_vesuvio_misc.py | 50 ++++++++++++++++- 2 files changed, 51 insertions(+), 53 deletions(-) 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 6e2cb6af..eca5ce17 100644 --- a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_analysis.py +++ b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_analysis.py @@ -1,8 +1,6 @@ from unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5 import EVSCalibrationAnalysis -from mock import MagicMock, patch import unittest -import numpy as np class TestVesuvioCalibrationAnalysis(unittest.TestCase): @@ -13,56 +11,8 @@ def setUpClass(cls): def setUp(self): pass - @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.mtd') - def test_identify_invalid_spectra_no_invalid(self, mock_mtd): - alg = EVSCalibrationAnalysis() - ws_mock = MagicMock() - mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [0.1, 0.2, 0.3], - 'f1.LorentzFWHM': [1.1, 2.1, 3.1], 'f1.LorentzFWHM_Err': [0.11, 0.21, 0.31], - 'f1.LorentzAmp': [1.2, 2.2, 3.2], 'f1.LorentzAmp_Err': [0.12, 0.22, 0.32]} - 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([]), alg._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') - def test_identify_invalid_spectra_nan_in_errors(self, mock_mtd): - alg = EVSCalibrationAnalysis() - ws_mock = MagicMock() - mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [np.nan, 0.2, 0.3], - 'f1.LorentzFWHM': [1.1, 2.1, 3.1], 'f1.LorentzFWHM_Err': [0.11, 0.21, 0.31], - 'f1.LorentzAmp': [1.2, 2.2, 3.2], 'f1.LorentzAmp_Err': [0.12, 0.22, np.nan]} - 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(np.array([True, False, True])), - alg._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') - def test_identify_invalid_spectra_inf_in_errors(self, mock_mtd): - alg = EVSCalibrationAnalysis() - ws_mock = MagicMock() - mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [0.1, 0.2, 0.3], - 'f1.LorentzFWHM': [1.1, 2.1, 3.1], 'f1.LorentzFWHM_Err': [np.inf, 0.21, 0.31], - 'f1.LorentzAmp': [1.2, 2.2, 3.2], 'f1.LorentzAmp_Err': [0.12, np.inf, 0.32]} - 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(np.array([True, True, False])), - alg._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') - def test_identify_invalid_spectra_error_greater_than_peak(self, mock_mtd): - alg = EVSCalibrationAnalysis() - ws_mock = MagicMock() - mock_column_output_dict = {'f1.GaussianFWHM': [1, 2, 3], 'f1.GaussianFWHM_Err': [0.1, 0.2, 0.3], - 'f1.LorentzFWHM': [1.1, 2.1, 3.1], 'f1.LorentzFWHM_Err': [0.11, 10, 0.31], - 'f1.LorentzAmp': [1.2, 2.2, 3.2], 'f1.LorentzAmp_Err': [0.12, 0.22, 10]} - 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(np.array([False, True, True])), - alg._identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) + def test_placeholder(self): + pass if __name__ == '__main__': 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 6019ab8d..08d1269c 100644 --- a/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_misc.py +++ b/unpackaged/vesuvio_calibration/tests/unit/test_calibrate_vesuvio_misc.py @@ -1,7 +1,8 @@ -from unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5 import generate_fit_function_header +from unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5 import generate_fit_function_header, identify_invalid_spectra from mock import MagicMock, patch import unittest +import numpy as np class TestVesuvioCalibrationMisc(unittest.TestCase): @@ -33,6 +34,53 @@ def test_generate_header_function_invalid(self): with self.assertRaises(ValueError): generate_fit_function_header("Invalid") + @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.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], + 'f1.LorentzFWHM': [1.1, 2.1, 3.1], 'f1.LorentzFWHM_Err': [0.11, 0.21, 0.31], + 'f1.LorentzAmp': [1.2, 2.2, 3.2], 'f1.LorentzAmp_Err': [0.12, 0.22, 0.32]} + 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])) + + @patch('unpackaged.vesuvio_calibration.calibrate_vesuvio_uranium_martyn_MK5.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], + 'f1.LorentzFWHM': [1.1, 2.1, 3.1], 'f1.LorentzFWHM_Err': [0.11, 0.21, 0.31], + 'f1.LorentzAmp': [1.2, 2.2, 3.2], 'f1.LorentzAmp_Err': [0.12, 0.22, np.nan]} + 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(np.array([True, False, True])), + 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') + 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], + 'f1.LorentzFWHM': [1.1, 2.1, 3.1], 'f1.LorentzFWHM_Err': [np.inf, 0.21, 0.31], + 'f1.LorentzAmp': [1.2, 2.2, 3.2], 'f1.LorentzAmp_Err': [0.12, np.inf, 0.32]} + 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(np.array([True, True, False])), + 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') + 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], + 'f1.LorentzFWHM': [1.1, 2.1, 3.1], 'f1.LorentzFWHM_Err': [0.11, 10, 0.31], + 'f1.LorentzAmp': [1.2, 2.2, 3.2], 'f1.LorentzAmp_Err': [0.12, 0.22, 10]} + 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(np.array([False, True, True])), + identify_invalid_spectra('peak_table', [5, 10, 20], [0.1, 0.15, 0.2], [0, 2])) + if __name__ == '__main__': unittest.main() From d2d18e0d79a7c9982e368ea4c10a5e8962555fd5 Mon Sep 17 00:00:00 2001 From: MialLewis <95620982+MialLewis@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:36:08 +0100 Subject: [PATCH 15/15] remove unused import --- .../vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py | 1 - 1 file changed, 1 deletion(-) diff --git a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py index 1caae9cd..b7317059 100644 --- a/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py +++ b/unpackaged/vesuvio_calibration/calibrate_vesuvio_uranium_martyn_MK5.py @@ -26,7 +26,6 @@ import scipy.constants import scipy.stats import numpy as np -import time # Configuration for Uranium runs / Indium runs #----------------------------------------------------------------------------------------