Skip to content

Commit 17aa715

Browse files
Use table of profiles passed as an argument
Previously the profiles were accessed through a list of bespoke dataclass objects called NeutronComptonProfile. Made the change to use a table workspace instead, to conform with the new input argument. Still need to change the way the Joint routine links profiles together.
1 parent 9a5722c commit 17aa715

File tree

2 files changed

+113
-104
lines changed

2 files changed

+113
-104
lines changed

src/mvesuvio/analysis_reduction.py

Lines changed: 72 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ def PyInit(self):
5252
direction=Direction.Input),
5353
doc="Workspace to fit Neutron Compton Profiles.")
5454

55+
self.declareProperty(TableWorkspaceProperty(name="InputProfiles",
56+
defaultValue="",
57+
direction=Direction.Input),
58+
doc="Table workspace containing starting parameters for profiles")
59+
5560
self.declareProperty(FileProperty(name='InstrumentParametersFile',
5661
defaultValue='',
5762
action=FileAction.Load,
@@ -90,17 +95,14 @@ def PyInit(self):
9095
self.declareProperty("ResultsPath", "", doc="Directory to store results, to be deleted later")
9196
self.declareProperty("FiguresPath", "", doc="Directory to store figures, to be deleted later")
9297

93-
self.declareProperty(TableWorkspaceProperty(name="InputProfiles",
94-
defaultValue="",
95-
direction=Direction.Input),
96-
doc="Table workspace containing starting parameters for profiles")
9798

9899
def PyExec(self):
99100
self._setup()
101+
self.run()
100102

101103
def _setup(self):
102104
self._name = self.getProperty("InputWorkspace").value.name()
103-
self._ip_file = self.getPropertY("InstrumentParametersFile").value
105+
self._ip_file = self.getProperty("InstrumentParametersFile").value
104106
self._number_of_iterations = self.getProperty("NumberOfIterations").value
105107
self._mask_spectra = self.getProperty("InvalidDetectors").value
106108
self._transmission_guess = self.getProperty("TransmissionGuess").value
@@ -133,76 +135,32 @@ def _setup(self):
133135
self._run_norm_voigt = False
134136

135137

136-
def add_profiles(self, *args: NeutronComptonProfile):
137-
for profile in args:
138-
self._profiles[profile.label] = profile
139-
140-
141-
@property
142-
def profiles(self):
143-
return self._profiles
144-
145-
146-
def set_initial_profiles_from(self, source: 'AnalysisRoutine'):
147-
148-
# Set intensities
149-
for p in self._profiles.values():
150-
if np.isclose(p.mass, 1, atol=0.1): # Hydrogen present
151-
p.intensity = source._h_ratio * source._get_lightest_profile().mean_intensity
152-
continue
153-
p.intensity = source.profiles[p.label].mean_intensity
154-
155-
# Normalise intensities
156-
sum_intensities = sum([p.intensity for p in self._profiles.values()])
157-
for p in self._profiles.values():
158-
p.intensity /= sum_intensities
159-
160-
# Set widths
161-
for p in self._profiles.values():
162-
try:
163-
p.width = source.profiles[p.label].mean_width
164-
except KeyError:
165-
continue
166-
167-
# Fix all widths except lightest mass
168-
for p in self._profiles.values():
169-
if p == self._get_lightest_profile():
170-
continue
171-
p.width_bounds = [p.width, p.width]
172-
173-
return
174-
175-
176-
def _get_lightest_profile(self):
177-
profiles = [p for p in self._profiles.values()]
178-
masses = [p.mass for p in self._profiles.values()]
179-
return profiles[np.argmin(masses)]
180-
181-
182138
def calculate_h_ratio(self):
183139

184-
if not np.isclose(self._get_lightest_profile().mass, 1, atol=0.1): # Hydrogen present
140+
if not np.isclose(min(self._profiles_table.column("mass")), 1, atol=0.1): # Hydrogen not present
185141
return None
186142

187-
# Hydrogen is present
188-
intensities = np.array([p.mean_intensity for p in self._profiles.values()])
189-
masses = np.array([p.mass for p in self._profiles.values()])
143+
# Hydrogen present
144+
# intensities = np.array([p.mean_intensity for p in self._profiles.values()])
145+
intensities = self.mean_intensity_ratios
146+
# masses = np.array([p.mass for p in self._profiles.values()])
147+
masses = self._profiles_table.column("mass")
190148

191149
sorted_intensities = intensities[np.argsort(masses)]
192150
return sorted_intensities[0] / sorted_intensities[1]
193151

194152

195-
@property
196-
def profiles(self):
197-
return self._profiles
153+
# @property
154+
# def profiles(self):
155+
# return self._profiles
198156

199157

200-
@profiles.setter
201-
def profiles(self, incoming_profiles):
202-
assert(isinstance(incoming_profiles, dict))
203-
common_keys = self._profiles.keys() & incoming_profiles.keys()
204-
common_keys_profiles = {k: incoming_profiles[k] for k in common_keys}
205-
self._profiles = {**self._profiles, **common_keys_profiles}
158+
# @profiles.setter
159+
# def profiles(self, incoming_profiles):
160+
# assert(isinstance(incoming_profiles, dict))
161+
# common_keys = self._profiles.keys() & incoming_profiles.keys()
162+
# common_keys_profiles = {k: incoming_profiles[k] for k in common_keys}
163+
# self._profiles = {**self._profiles, **common_keys_profiles}
206164

207165

208166
def _update_workspace_data(self):
@@ -216,13 +174,13 @@ def _update_workspace_data(self):
216174

217175
self._set_up_kinematic_arrays()
218176

219-
self._fit_parameters = np.zeros((len(self._dataY), 3 * len(self._profiles) + 3))
177+
self._fit_parameters = np.zeros((len(self._dataY), 3 * self._profiles_table.rowCount() + 3))
220178
self._row_being_fit = 0
221179
self._table_fit_results = self._initialize_table_fit_parameters()
222180

223181
# Initialise workspaces for fitted ncp
224182
self._fit_profiles_workspaces = {}
225-
for element, p in self._profiles.items():
183+
for element in self._profiles_table.column("label"):
226184
self._fit_profiles_workspaces[element] = self._create_emtpy_ncp_workspace(f'_{element}_ncp')
227185
self._fit_profiles_workspaces['total'] = self._create_emtpy_ncp_workspace(f'_total_ncp')
228186

@@ -239,10 +197,10 @@ def _initialize_table_fit_parameters(self):
239197
)
240198
table.setTitle("SciPy Fit Parameters")
241199
table.addColumn(type="float", name="Spectrum")
242-
for key in self._profiles.keys():
243-
table.addColumn(type="float", name=f"{key} Intensity")
244-
table.addColumn(type="float", name=f"{key} Width")
245-
table.addColumn(type="float", name=f"{key} Center ")
200+
for label in self._profiles_table.column("label"):
201+
table.addColumn(type="float", name=f"{label} Intensity")
202+
table.addColumn(type="float", name=f"{label} Width")
203+
table.addColumn(type="float", name=f"{label} Center ")
246204
table.addColumn(type="float", name="Normalised Chi2")
247205
table.addColumn(type="float", name="Number of Iterations")
248206
return table
@@ -256,8 +214,8 @@ def mean_widths(self):
256214
@mean_widths.setter
257215
def mean_widths(self, value):
258216
self._mean_widths = value
259-
for i, p in enumerate(self._profiles.values()):
260-
p.mean_width = self._mean_widths[i]
217+
# for i, p in enumerate(self._profiles.values()):
218+
# p.mean_width = self._mean_widths[i]
261219
return
262220

263221

@@ -268,8 +226,8 @@ def mean_intensity_ratios(self):
268226
@mean_intensity_ratios.setter
269227
def mean_intensity_ratios(self, value):
270228
self._mean_intensity_ratios = value
271-
for i, p in enumerate(self.profiles.values()):
272-
p.mean_intensity = self._mean_intensity_ratios[i]
229+
# for i, p in enumerate(self.profiles.values()):
230+
# p.mean_intensity = self._mean_intensity_ratios[i]
273231
return
274232

275233

@@ -294,7 +252,7 @@ def _set_up_kinematic_arrays(self):
294252

295253
def run(self):
296254

297-
assert len(self.profiles) > 0, "Add profiles before attempting to run the routine!"
255+
assert self._profiles_table.rowCount() > 0, "Need at least one profile to run the routine!"
298256

299257
# Legacy code from Bootstrap
300258
# if self.runningSampleWS:
@@ -442,7 +400,7 @@ def convertDataXToYSpacesForEachMass(self, dataX, delta_Q, delta_E):
442400
delta_E = delta_E[np.newaxis, :, :]
443401

444402
mN, Ef, en_to_vel, vf, hbar = loadConstants()
445-
masses = np.array([p.mass for p in self._profiles.values()]).reshape(-1, 1, 1)
403+
masses = np.array(self._profiles_table.column("mass")).reshape(-1, 1, 1)
446404

447405
energyRecoil = np.square(hbar * delta_Q) / 2.0 / masses
448406
ySpacesForEachMass = (
@@ -475,7 +433,7 @@ def _save_plots(self):
475433
ax.legend()
476434

477435
fileName = self._workspace_being_fit.name() + "_profiles_sum.pdf"
478-
savePath = self._save_figures_path / fileName
436+
savePath = self._save_figures_path + fileName
479437
plt.savefig(savePath, bbox_inches="tight")
480438
plt.close(fig)
481439
return
@@ -497,11 +455,11 @@ def _set_means_and_std(self):
497455
"""Calculate mean widths and intensities from tableWorkspace"""
498456

499457
fitParsTable = self._table_fit_results
500-
widths = np.zeros((len(self._profiles), fitParsTable.rowCount()))
458+
widths = np.zeros((self._profiles_table.rowCount(), fitParsTable.rowCount()))
501459
intensities = np.zeros(widths.shape)
502-
for i, p in enumerate(self._profiles.values()):
503-
widths[i] = fitParsTable.column(f"{p.label} Width")
504-
intensities[i] = fitParsTable.column(f"{p.label} Intensity")
460+
for i, label in enumerate(self._profiles_table.column("label")):
461+
widths[i] = fitParsTable.column(f"{label} Width")
462+
intensities[i] = fitParsTable.column(f"{label} Intensity")
505463
(
506464
meanWidths,
507465
stdWidths,
@@ -510,7 +468,7 @@ def _set_means_and_std(self):
510468
) = self.calculateMeansAndStds(widths, intensities)
511469

512470
assert (
513-
len(meanWidths) == len(self._profiles)
471+
len(meanWidths) == self._profiles_table.rowCount()
514472
), "Number of mean widths must match number of profiles!"
515473

516474
self.mean_widths = meanWidths # Use setter
@@ -534,15 +492,15 @@ def createMeansAndStdTableWS(self):
534492

535493
print("\nCreated Table with means and std:")
536494
print("\nMass Mean \u00B1 Std Widths Mean \u00B1 Std Intensities\n")
537-
for p, mean_width, std_width, mean_intensity, std_intensity in zip(
538-
self._profiles.values(),
495+
for label, mean_width, std_width, mean_intensity, std_intensity in zip(
496+
self._profiles_table.column("label"),
539497
self._mean_widths,
540498
self._std_widths,
541499
self._mean_intensity_ratios,
542500
self._std_intensity_ratios,
543501
):
544-
meansTableWS.addRow([p.label, mean_width, std_width, mean_intensity, std_intensity])
545-
print(f"{p.label:5s} {mean_width:10.5f} \u00B1 {std_width:7.5f} \
502+
meansTableWS.addRow([label, mean_width, std_width, mean_intensity, std_intensity])
503+
print(f"{label:5s} {mean_width:10.5f} \u00B1 {std_width:7.5f} \
546504
{mean_intensity:10.5f} \u00B1 {std_intensity:7.5f}\n")
547505
return
548506

@@ -613,17 +571,31 @@ def filterWidthsAndIntensities(self, widthsIn, intensitiesIn):
613571
def _fit_neutron_compton_profiles_to_row(self):
614572

615573
if np.all(self._dataY[self._row_being_fit] == 0):
616-
self._table_fit_results.addRow(np.zeros(3*len(self._profiles)+3))
574+
self._table_fit_results.addRow(np.zeros(3*self._profiles_table.rowCount()+3))
617575
return
618576

619577
# Need to transform profiles into parameter array for minimize
620578
initial_parameters = []
579+
for intensity, width, center in zip(
580+
self._profiles_table.column("intensity"),
581+
self._profiles_table.column("width"),
582+
self._profiles_table.column("center")
583+
):
584+
initial_parameters.extend([intensity, width, center])
585+
621586
bounds = []
622-
for p in self._profiles.values():
623-
for attr in ['intensity', 'width', 'center']:
624-
initial_parameters.append(getattr(p, attr))
625-
for attr in ['intensity_bounds', 'width_bounds', 'center_bounds']:
626-
bounds.append(getattr(p, attr))
587+
for intensity_bounds, width_bounds, center_bounds in zip(
588+
self._profiles_table.column("intensity_bounds"),
589+
self._profiles_table.column("width_bounds"),
590+
self._profiles_table.column("center_bounds")
591+
):
592+
bounds.extend([eval(intensity_bounds), eval(width_bounds), eval(center_bounds)])
593+
594+
# for p in self._profiles.values():
595+
# for attr in ['intensity', 'width', 'center']:
596+
# initial_parameters.append(getattr(p, attr))
597+
# for attr in ['intensity_bounds', 'width_bounds', 'center_bounds']:
598+
# bounds.append(getattr(p, attr))
627599

628600
result = optimize.minimize(
629601
self.errorFunction,
@@ -649,7 +621,7 @@ def _fit_neutron_compton_profiles_to_row(self):
649621

650622
# Pass fit profiles into workspaces
651623
individual_ncps = self._neutron_compton_profiles(fitPars)
652-
for ncp, element in zip(individual_ncps, self._profiles.keys()):
624+
for ncp, element in zip(individual_ncps, self._profiles_table.column("label")):
653625
self._fit_profiles_workspaces[element].dataY(self._row_being_fit)[:] = ncp
654626

655627
self._fit_profiles_workspaces['total'].dataY(self._row_being_fit)[:] = np.sum(individual_ncps, axis=0)
@@ -683,7 +655,7 @@ def _neutron_compton_profiles(self, pars):
683655
intensities = pars[::3].reshape(-1, 1)
684656
widths = pars[1::3].reshape(-1, 1)
685657
centers = pars[2::3].reshape(-1, 1)
686-
masses = np.array([p.mass for p in self._profiles.values()]).reshape(-1, 1)
658+
masses = np.array(self._profiles_table.column("mass")).reshape(-1, 1)
687659

688660
v0, E0, deltaE, deltaQ = self._kinematic_arrays[self._row_being_fit]
689661

@@ -734,7 +706,7 @@ def kinematicsAtYCenters(self, centers):
734706

735707

736708
def calcGaussianResolution(self, centers):
737-
masses = np.array([p.mass for p in self._profiles.values()]).reshape(-1, 1)
709+
masses = np.array(self._profiles_table.column("mass")).reshape(-1, 1)
738710
v0, E0, delta_E, delta_Q = self.kinematicsAtYCenters(centers)
739711
det, plick, angle, T0, L0, L1 = self._instrument_params[self._row_being_fit]
740712
dE1, dTOF, dTheta, dL0, dL1, dE1_lorz = self._resolution_params[self._row_being_fit]
@@ -780,7 +752,7 @@ def calcGaussianResolution(self, centers):
780752

781753

782754
def calcLorentzianResolution(self, centers):
783-
masses = np.array([p.mass for p in self._profiles.values()]).reshape(-1, 1)
755+
masses = np.array(self._profiles_table.column("mass")).reshape(-1, 1)
784756
v0, E0, delta_E, delta_Q = self.kinematicsAtYCenters(centers)
785757
det, plick, angle, T0, L0, L1 = self._instrument_params[self._row_being_fit]
786758
dE1, dTOF, dTheta, dL0, dL1, dE1_lorz = self._resolution_params[self._row_being_fit]
@@ -949,7 +921,7 @@ def createSlabGeometry(self, wsNCPM):
949921

950922

951923
def calcMSCorrectionSampleProperties(self, meanWidths, meanIntensityRatios):
952-
masses = [p.mass for p in self._profiles.values()]
924+
masses = np.array(self._profiles_table.column("mass"))
953925

954926
# If Backscattering mode and H is present in the sample, add H to MS properties
955927
if self._mode_running == "BACKWARD":
@@ -1040,7 +1012,7 @@ def create_gamma_workspaces(self):
10401012

10411013

10421014
def calcGammaCorrectionProfiles(self, meanWidths, meanIntensityRatios):
1043-
masses = [p.mass for p in self._profiles.values()]
1015+
masses = np.array(self._profiles_table.column("mass"))
10441016
profiles = ""
10451017
for mass, width, intensity in zip(masses, meanWidths, meanIntensityRatios):
10461018
profiles += (
@@ -1091,9 +1063,9 @@ def _set_results(self):
10911063

10921064
# Extract individual ncp
10931065
allNCP = []
1094-
for p in self._profiles.values():
1066+
for label in self._profiles_table.column("label"):
10951067
ncpWsToAppend = mtd[
1096-
wsIterName + f"_{p.label}_ncp"
1068+
wsIterName + f"_{label}_ncp"
10971069
]
10981070
allNCP.append(ncpWsToAppend.extractY())
10991071
allNCP = np.swapaxes(np.array(allNCP), 0, 1)

0 commit comments

Comments
 (0)