Skip to content

Commit 61da79d

Browse files
Create function to update input profiles table
Transformed existing function to create a function that takes in an outputs table containing the means at the end of the routine and passes these to the inputs profiles table as the initial parameters. The lightest mass is always unfixed (bounds are relaxed). The h ratio is used depending on whether the sample contains hydrogen.
1 parent 1ffa208 commit 61da79d

File tree

5 files changed

+219
-82
lines changed

5 files changed

+219
-82
lines changed

src/mvesuvio/analysis_reduction.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,21 +516,23 @@ def _create_means_table(self):
516516
OutputWorkspace=self._workspace_being_fit.name() + "_means"
517517
)
518518
table.addColumn(type="str", name="label")
519+
table.addColumn(type="float", name="mass")
519520
table.addColumn(type="float", name="mean_width")
520521
table.addColumn(type="float", name="std_width")
521522
table.addColumn(type="float", name="mean_intensity")
522523
table.addColumn(type="float", name="std_intensity")
523524

524525
print("\nCreated Table with means and std:")
525526
print("\nMass Mean \u00B1 Std Widths Mean \u00B1 Std Intensities\n")
526-
for label, mean_width, std_width, mean_intensity, std_intensity in zip(
527+
for label, mass, mean_width, std_width, mean_intensity, std_intensity in zip(
527528
self._profiles_table.column("label"),
529+
self._masses,
528530
self._mean_widths,
529531
self._std_widths,
530532
self._mean_intensity_ratios,
531533
self._std_intensity_ratios,
532534
):
533-
table.addRow([label, mean_width, std_width, mean_intensity, std_intensity])
535+
table.addRow([label, mass, mean_width, std_width, mean_intensity, std_intensity])
534536
print(f"{label:5s} {mean_width:10.5f} \u00B1 {std_width:7.5f} \
535537
{mean_intensity:10.5f} \u00B1 {std_intensity:7.5f}\n")
536538

src/mvesuvio/analysis_routines.py

Lines changed: 27 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
from mantid.api import AlgorithmFactory, AlgorithmManager
55
import numpy as np
66

7-
from mvesuvio.util.analysis_helpers import loadRawAndEmptyWsFromUserPath, cropAndMaskWorkspace
7+
from mvesuvio.util.analysis_helpers import loadRawAndEmptyWsFromUserPath, cropAndMaskWorkspace, NeutronComptonProfile
88
from mvesuvio.analysis_reduction import AnalysisRoutine
9-
from mvesuvio.analysis_reduction import NeutronComptonProfile
109
from tests.testhelpers.calibration.algorithms import create_algorithm
1110

12-
def _create_analysis_object_from_current_interface(IC, running_tests=False):
11+
def _create_analysis_object_from_current_interface(IC, running_tests=False, overwrite_profiles_table=None):
1312
ws = loadRawAndEmptyWsFromUserPath(
1413
userWsRawPath=IC.userWsRawPath,
1514
userWsEmptyPath=IC.userWsEmptyPath,
@@ -27,17 +26,20 @@ def _create_analysis_object_from_current_interface(IC, running_tests=False):
2726
maskTOFRange=IC.maskTOFRange
2827
)
2928

30-
profiles = []
31-
for mass, intensity, width, center, intensity_bound, width_bound, center_bound in zip(
32-
IC.masses, IC.initPars[::3], IC.initPars[1::3], IC.initPars[2::3],
33-
IC.bounds[::3], IC.bounds[1::3], IC.bounds[2::3]
34-
):
35-
profiles.append(NeutronComptonProfile(
36-
label=str(mass), mass=mass, intensity=intensity, width=width, center=center,
37-
intensity_bounds=list(intensity_bound), width_bounds=list(width_bound), center_bounds=list(center_bound)
38-
))
39-
40-
profiles_table = create_profiles_table(cropedWs.name()+"_initial_parameters", profiles)
29+
if overwrite_profiles_table:
30+
profiles_table = overwrite_profiles_table
31+
else:
32+
profiles = []
33+
for mass, intensity, width, center, intensity_bound, width_bound, center_bound in zip(
34+
IC.masses, IC.initPars[::3], IC.initPars[1::3], IC.initPars[2::3],
35+
IC.bounds[::3], IC.bounds[1::3], IC.bounds[2::3]
36+
):
37+
profiles.append(NeutronComptonProfile(
38+
label=str(mass), mass=mass, intensity=intensity, width=width, center=center,
39+
intensity_bounds=list(intensity_bound), width_bounds=list(width_bound), center_bounds=list(center_bound)
40+
))
41+
42+
profiles_table = create_profiles_table(cropedWs.name()+"_initial_parameters", profiles)
4143

4244
kwargs = {
4345
"InputWorkspace": cropedWs,
@@ -90,40 +92,6 @@ def create_profiles_table(name, profiles: list[NeutronComptonProfile]):
9092
return table
9193

9294

93-
def set_initial_profiles_from(self, source: 'AnalysisRoutine'):
94-
95-
# Set intensities
96-
for p in self._profiles.values():
97-
if np.isclose(p.mass, 1, atol=0.1): # Hydrogen present
98-
p.intensity = source._h_ratio * source._get_lightest_profile().mean_intensity
99-
continue
100-
p.intensity = source.profiles[p.label].mean_intensity
101-
102-
# Normalise intensities
103-
sum_intensities = sum([p.intensity for p in self._profiles.values()])
104-
for p in self._profiles.values():
105-
p.intensity /= sum_intensities
106-
107-
# Set widths
108-
for p in self._profiles.values():
109-
try:
110-
p.width = source.profiles[p.label].mean_width
111-
except KeyError:
112-
continue
113-
114-
# Fix all widths except lightest mass
115-
for p in self._profiles.values():
116-
if p == self._get_lightest_profile():
117-
continue
118-
p.width_bounds = [p.width, p.width]
119-
120-
return
121-
122-
def _get_lightest_profile(self):
123-
profiles = [p for p in self._profiles.values()]
124-
masses = [p.mass for p in self._profiles.values()]
125-
return profiles[np.argmin(masses)]
126-
12795
def runIndependentIterativeProcedure(IC, clearWS=True, running_tests=False):
12896
"""
12997
Runs the iterative fitting of NCP, cleaning any previously stored workspaces.
@@ -137,7 +105,6 @@ def runIndependentIterativeProcedure(IC, clearWS=True, running_tests=False):
137105

138106
alg = _create_analysis_object_from_current_interface(IC, running_tests=running_tests)
139107
alg.execute()
140-
means_table = alg.getPropertyValue("OutputMeansTable")
141108
return alg
142109

143110

@@ -212,12 +179,18 @@ def createTableWSHRatios():
212179

213180
def runJoint(bckwdIC, fwdIC):
214181

215-
backRoutine = _create_analysis_object_from_current_interface(bckwdIC)
216-
frontRoutine = _create_analysis_object_from_current_interface(fwdIC)
182+
back_alg= _create_analysis_object_from_current_interface(bckwdIC)
183+
front_alg= _create_analysis_object_from_current_interface(fwdIC)
217184

218-
backRoutine.execute()
219-
frontRoutine.set_initial_profiles_from(backRoutine)
220-
frontRoutine.execute()
185+
back_alg.execute()
186+
incoming_means_table = back_alg.getProperty("OutputMeansTable").value
187+
receiving_profiles_table = front_alg.getProperty("InputProfiles").value
188+
189+
fixed_profiles_table = fix_profiles(incoming_means_table, receiving_profiles_table)
190+
191+
front_alg.setProperty("InputProfiles", fixed_profiles_table)
192+
193+
front_alg.execute()
221194
return
222195

223196

src/mvesuvio/util/analysis_helpers.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,26 @@
66

77
from mvesuvio.analysis_fitting import passDataIntoWS
88

9+
from dataclasses import dataclass
10+
11+
@dataclass(frozen=False)
12+
class NeutronComptonProfile:
13+
label: str
14+
mass: float
15+
16+
intensity: float
17+
width: float
18+
center: float
19+
20+
intensity_bounds: list[float, float]
21+
width_bounds: list[float, float]
22+
center_bounds: list[float, float]
23+
24+
mean_intensity: float = None
25+
mean_width: float = None
26+
mean_center: float = None
27+
28+
929

1030
def loadRawAndEmptyWsFromUserPath(userWsRawPath, userWsEmptyPath,
1131
tofBinning, name, scaleRaw, scaleEmpty, subEmptyFromRaw):
@@ -152,3 +172,62 @@ def createWS(dataX, dataY, dataE, wsName, parentWorkspace=None):
152172
ParentWorkspace=parentWorkspace
153173
)
154174
return ws
175+
176+
177+
def fix_profile_parameters(incoming_means_table, receiving_profiles_table, h_ratio):
178+
means_dict = _convert_table_to_dict(incoming_means_table)
179+
profiles_dict = _convert_table_to_dict(receiving_profiles_table)
180+
181+
# Set intensities
182+
for p in profiles_dict.values():
183+
if np.isclose(p['mass'], 1, atol=0.1): # Hydrogen present
184+
p['intensity'] = h_ratio * _get_lightest_profile(means_dict)['mean_intensity']
185+
continue
186+
p['intensity'] = means_dict[p['label']]['mean_intensity']
187+
188+
# Normalise intensities
189+
sum_intensities = sum([p['intensity'] for p in profiles_dict.values()])
190+
for p in profiles_dict.values():
191+
p['intensity'] /= sum_intensities
192+
193+
# Set widths
194+
for p in profiles_dict.values():
195+
try:
196+
p['width'] = means_dict[p['label']]['mean_width']
197+
except KeyError:
198+
continue
199+
200+
# Fix all widths except lightest mass
201+
for p in profiles_dict.values():
202+
if p == _get_lightest_profile(profiles_dict):
203+
continue
204+
p['width_bounds'] = str([p['width'] , p['width']])
205+
206+
result_profiles_table = _convert_dict_to_table(profiles_dict)
207+
return result_profiles_table
208+
209+
210+
def _convert_table_to_dict(table):
211+
result_dict = {}
212+
for i in range(table.rowCount()):
213+
row_dict = table.row(i)
214+
result_dict[row_dict['label']] = row_dict
215+
return result_dict
216+
217+
218+
def _convert_dict_to_table(m_dict):
219+
table = CreateEmptyTableWorkspace()
220+
for p in m_dict.values():
221+
if table.columnCount() == 0:
222+
for key, value in p.items():
223+
value_type = 'str' if isinstance(value, str) else 'float'
224+
table.addColumn(value_type, key)
225+
226+
table.addRow(p)
227+
return table
228+
229+
230+
def _get_lightest_profile(p_dict):
231+
profiles = [p for p in p_dict.values()]
232+
masses = [p['mass'] for p in p_dict.values()]
233+
return profiles[np.argmin(masses)]

tests/unit/analysis/test_analysis_functions.py

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import unittest
2+
import numpy as np
3+
import numpy.testing as nptest
4+
from mock import MagicMock
5+
from mvesuvio.util.analysis_helpers import extractWS, _convert_dict_to_table, fix_profile_parameters
6+
from mantid.simpleapi import CreateWorkspace, DeleteWorkspace
7+
8+
9+
class TestAnalysisHelpers(unittest.TestCase):
10+
def setUp(self):
11+
pass
12+
13+
def test_extract_ws(self):
14+
data = [1, 2, 3]
15+
ws = CreateWorkspace(DataX=data, DataY=data, DataE=data, NSpec=1, UnitX="some_unit")
16+
17+
dataX, dataY, dataE = extractWS(ws)
18+
nptest.assert_array_equal([data], dataX)
19+
nptest.assert_array_equal([data], dataY)
20+
nptest.assert_array_equal([data], dataE)
21+
22+
DeleteWorkspace(ws)
23+
24+
25+
def test_convert_dict_to_table(self):
26+
d = {'H': {'label': 'H', 'mass': 1, 'intensity': 1}}
27+
table = _convert_dict_to_table(d)
28+
self.assertEqual(['label', 'mass', 'intensity'], table.getColumnNames())
29+
self.assertEqual({'label': 'H', 'mass': 1, 'intensity': 1}, table.row(0))
30+
31+
32+
def test_fix_profile_parameters_with_H(self):
33+
means_table_mock = MagicMock()
34+
means_table_mock.rowCount.return_value = 3
35+
means_table_mock.row.side_effect = [
36+
{'label': '16.0', 'mass': 16.0, 'mean_width': 8.974, 'std_width': 1.401, 'mean_intensity': 0.176, 'std_intensity': 0.08722},
37+
{'label': '27.0', 'mass': 27.0, 'mean_width': 15.397, 'std_width': 1.131, 'mean_intensity': 0.305, 'std_intensity': 0.04895},
38+
{'label': '12.0', 'mass': 12.0, 'mean_width': 13.932, 'std_width': 0.314, 'mean_intensity': 0.517, 'std_intensity': 0.09531}
39+
]
40+
profiles_table_mock = MagicMock()
41+
profiles_table_mock.rowCount.return_value = 4
42+
profiles_table_mock.row.side_effect = [
43+
{'label': '1.0079', 'mass': 1.0078, 'intensity': 1.0, 'intensity_bounds': '[0, None]', 'width': 4.699, 'width_bounds': '[3, 6]', 'center': 0.0, 'center_bounds': '[-3, 1]'},
44+
{'label': '16.0', 'mass': 16.0, 'intensity': 1.0, 'intensity_bounds': '[0, None]', 'width': 12, 'width_bounds': '[0, None]', 'center': 0.0, 'center_bounds': '[-3, 1]'},
45+
{'label': '12.0', 'mass': 12.0, 'intensity': 1.0, 'intensity_bounds': '[0, None]', 'width': 8, 'width_bounds': '[0, None]', 'center': 0.0, 'center_bounds': '[-3, 1]'},
46+
{'label': '27.0', 'mass': 27.0, 'intensity': 1.0, 'intensity_bounds': '[0, None]', 'width': 13, 'width_bounds': '[0, None]', 'center': 0.0, 'center_bounds': '[-3, 1]'}
47+
]
48+
49+
result_table = fix_profile_parameters(means_table_mock, profiles_table_mock, h_ratio=14.7)
50+
51+
# For some reason, reading floats from table introduces small variations in stored values
52+
# TODO: Fix floating positions eg. 8.973999977111816 -> 8.974
53+
self.assertEqual(
54+
result_table.row(0),
55+
{'label': '1.0079', 'mass': 1.0077999830245972, 'intensity': 0.8839251399040222, 'intensity_bounds': '[0, None]', 'width': 4.698999881744385, 'width_bounds': '[3, 6]', 'center': 0.0, 'center_bounds': '[-3, 1]'}
56+
)
57+
self.assertEqual(
58+
result_table.row(1),
59+
{'label': '16.0', 'mass': 16.0, 'intensity': 0.020470114424824715, 'intensity_bounds': '[0, None]', 'width': 8.973999977111816, 'width_bounds': '[8.974, 8.974]', 'center': 0.0, 'center_bounds': '[-3, 1]'}
60+
)
61+
self.assertEqual(
62+
result_table.row(2),
63+
{'label': '12.0', 'mass': 12.0, 'intensity': 0.06013096123933792, 'intensity_bounds': '[0, None]', 'width': 13.932000160217285, 'width_bounds': '[13.932, 13.932]', 'center': 0.0, 'center_bounds': '[-3, 1]'}
64+
)
65+
self.assertEqual(
66+
result_table.row(3),
67+
{'label': '27.0', 'mass': 27.0, 'intensity': 0.0354737788438797, 'intensity_bounds': '[0, None]', 'width': 15.397000312805176, 'width_bounds': '[15.397, 15.397]', 'center': 0.0, 'center_bounds': '[-3, 1]'}
68+
)
69+
70+
71+
def test_fix_profile_parameters_without_H(self):
72+
# TODO: Use a more physical example containing Deuterium
73+
# Same profiles as before, except when H is not present,
74+
# the first mass will be made free to vary and the intensities don't change
75+
76+
means_table_mock = MagicMock()
77+
means_table_mock.rowCount.return_value = 3
78+
means_table_mock.row.side_effect = [
79+
{'label': '16.0', 'mass': 16.0, 'mean_width': 8.974, 'std_width': 1.401, 'mean_intensity': 0.176, 'std_intensity': 0.08722},
80+
{'label': '27.0', 'mass': 27.0, 'mean_width': 15.397, 'std_width': 1.131, 'mean_intensity': 0.305, 'std_intensity': 0.04895},
81+
{'label': '12.0', 'mass': 12.0, 'mean_width': 13.932, 'std_width': 0.314, 'mean_intensity': 0.517, 'std_intensity': 0.09531}
82+
]
83+
profiles_table_mock = MagicMock()
84+
profiles_table_mock.rowCount.return_value = 3
85+
profiles_table_mock.row.side_effect = [
86+
{'label': '16.0', 'mass': 16.0, 'intensity': 1.0, 'intensity_bounds': '[0, None]', 'width': 12, 'width_bounds': '[0, None]', 'center': 0.0, 'center_bounds': '[-3, 1]'},
87+
{'label': '12.0', 'mass': 12.0, 'intensity': 1.0, 'intensity_bounds': '[0, None]', 'width': 8, 'width_bounds': '[0, None]', 'center': 0.0, 'center_bounds': '[-3, 1]'},
88+
{'label': '27.0', 'mass': 27.0, 'intensity': 1.0, 'intensity_bounds': '[0, None]', 'width': 13, 'width_bounds': '[0, None]', 'center': 0.0, 'center_bounds': '[-3, 1]'}
89+
]
90+
91+
result_table = fix_profile_parameters(means_table_mock, profiles_table_mock, h_ratio=14.7)
92+
93+
# For some reason, reading floats from table introduces small variations in stored values
94+
# TODO: Fix floating positions eg. 8.973999977111816 -> 8.974
95+
self.assertEqual(
96+
result_table.row(0),
97+
{'label': '16.0', 'mass': 16.0, 'intensity': 0.17635270953178406, 'intensity_bounds': '[0, None]', 'width': 8.973999977111816, 'width_bounds': '[8.974, 8.974]', 'center': 0.0, 'center_bounds': '[-3, 1]'}
98+
)
99+
self.assertEqual(
100+
result_table.row(1),
101+
{'label': '12.0', 'mass': 12.0, 'intensity': 0.5180360674858093, 'intensity_bounds': '[0, None]', 'width': 13.932000160217285, 'width_bounds': '[0, None]', 'center': 0.0, 'center_bounds': '[-3, 1]'}
102+
)
103+
self.assertEqual(
104+
result_table.row(2),
105+
{'label': '27.0', 'mass': 27.0, 'intensity': 0.3056112229824066, 'intensity_bounds': '[0, None]', 'width': 15.397000312805176, 'width_bounds': '[15.397, 15.397]', 'center': 0.0, 'center_bounds': '[-3, 1]'}
106+
)
107+
108+
if __name__ == "__main__":
109+
unittest.main()

0 commit comments

Comments
 (0)