Skip to content

Commit b698691

Browse files
Merge pull request #37971 from mantidproject/37969_refactor_HRPD_prompt_pulse_subtraction_func_for_readability
Refactor HRPD prompt pulse subtraction for readability
2 parents 668d4d0 + 5a9900c commit b698691

File tree

1 file changed

+69
-46
lines changed
  • scripts/Diffraction/isis_powder

1 file changed

+69
-46
lines changed

scripts/Diffraction/isis_powder/hrpd.py

Lines changed: 69 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ def _mask_prompt_pulses(self, ws, ispecs=None):
202202
**mask_kwargs,
203203
)
204204

205-
def _subtract_prompt_pulses(self, ws, tof_res=0.002):
205+
def _subtract_prompt_pulses(self, ws):
206206
"""
207207
HRPD has a long flight path from the moderator resulting
208208
in sharp peaks from the proton pulse that maintain their
@@ -235,71 +235,39 @@ def _subtract_prompt_pulses(self, ws, tof_res=0.002):
235235
cen = npulse * PROMPT_PULSE_INTERVAL
236236
xlo = cen - PROMPT_PULSE_LEFT_WIDTH - 10 # add extra to ensure get background on both sides of peak
237237
xhi = cen + PROMPT_PULSE_RIGHT_WIDTH + 10
238-
comp_func = FunctionFactory.createInitialized(
239-
f"name=PearsonIV, Centre={8}, Intensity=1,Sigma=8.5, Exponent=1.5, Skew=-5,"
240-
f"constraints=(0.2<Sigma,1.5<Exponent);name=FlatBackground, A0=0,constraints=(0<A0)"
241-
)
242-
comp_func[0].setAttributeValue("CentreShift", cen)
243-
comp_func[0].setHeight(ws.readY(ispec)[ws.yIndexOfX(cen)])
244-
comp_func[0].addConstraints(f"{-15}<Centre<{15}")
245-
comp_func[0].addConstraints(f"{0}<Intensity")
246-
comp_func[1]["A0"] = min(ws.readY(ispec)[ws.yIndexOfX(xlo)], ws.readY(ispec)[ws.yIndexOfX(xhi)])
238+
comp_func = self._setup_single_prompt_pulse_function(ws, ispec, cen, xlo, xhi)
247239
func.add(comp_func)
248240
func.setDomainIndex(ipulse, ipulse)
249241
key_suffix = f"_{ipulse}" if ipulse > 0 else ""
250242
fit_kwargs["InputWorkspace" + key_suffix] = ws.name()
251243
fit_kwargs["StartX" + key_suffix] = xlo
252244
fit_kwargs["EndX" + key_suffix] = xhi
253245
fit_kwargs["WorkspaceIndex" + key_suffix] = ispec
254-
# work out how to exclude
255-
exclude = []
256-
diff_consts = si.diffractometerConstants(ispec)
257-
for dpk in dpks_bragg:
258-
tof_pk = UnitConversion.run("dSpacing", "TOF", dpk, 0, DeltaEModeType.Elastic, diff_consts)
259-
tof_pk_lo = tof_pk * (1 - tof_res)
260-
tof_pk_hi = tof_pk * (1 + tof_res)
261-
if xlo < tof_pk_lo < xhi or xlo < tof_pk_hi < xhi or (tof_pk_lo < xlo and tof_pk_hi > xhi):
262-
exclude.extend([tof_pk_lo, tof_pk_hi])
246+
# exclude x-ranges where Bragg peaks overlap prompt pulse within some fractional resolution
247+
exclude = self._get_tofs_to_exclude(si, ispec, dpks_bragg, xlo, xhi)
263248
if exclude:
264249
func.fixParameter(f"f{ipulse}.f1.A0") # fix constant background - Bragg peak can mess with bg
265250
fit_kwargs["Exclude" + key_suffix] = exclude
266251
# check that at least one fit region has no overlapping prompt pulse
267-
if len([key for key in fit_kwargs.keys() if "Exclude" in key]) < len(npulses):
268-
# tie peak parameters to be common for all prompt pulses
269-
for idomain in range(len(npulses) - 1):
270-
for param_name in ["Intensity", "Sigma", "Exponent", "Skew", "Centre"]:
271-
func.tie(f"f{idomain}.f0.{param_name}", f"f{len(npulses) - 1}.f0.{param_name}") # tie to first
272-
# fix some peak parameters
273-
for param_name in ["Sigma", "Exponent", "Skew"]:
274-
func.fixParameter(f"f{len(npulses) - 1}.f0.{param_name}")
252+
excluded_keys = [key for key in fit_kwargs.keys() if "Exclude" in key]
253+
if len(excluded_keys) < len(npulses):
254+
self._add_ties_to_multidomain_prompt_pulse_func(func)
275255
fit_output = mantid.Fit(Function=func, **fit_kwargs)
276256
# subtract prompt pulse only from spectrum
277-
success = fit_output.OutputStatus == "success" or "Changes in function value are too small" in fit_output.OutputStatus
278-
if success:
257+
is_success = fit_output.OutputStatus == "success"
258+
is_small_change = "Changes in function value are too small" in fit_output.OutputStatus
259+
if is_success or is_small_change:
279260
func = fit_output.Function.function
280-
for ipulse in range(fit_output.Function.nDomains):
281-
func.removeTie(f"f{ipulse}.f0.Centre")
282-
key_suffix = f"_{ipulse}" if ipulse > 0 else ""
283-
if "Exclude" + key_suffix in fit_kwargs:
284-
func.fixParameter(f"f{ipulse}.f0.Centre") # fix centre as can't fit independently with bragg
285-
else:
286-
fitted_cen = func[ipulse][0]["Centre"]
287-
func[ipulse][0].addConstraints(
288-
f"{fitted_cen - 5}<Centre<{fitted_cen + 5}"
289-
) # free centre but stricter constraint
261+
self._free_ties_of_multidomain_prompt_pulse_func(func, fit_kwargs)
290262
fit_output_final = mantid.Fit(Function=func, **fit_kwargs)
291263
success_final = (
292264
fit_output_final.OutputStatus == "success"
293265
or "Changes in function value are too small" in fit_output_final.OutputStatus
294266
)
295267
if success_final:
296-
fit_output = fit_output_final
297-
# accumulate peak functions
298-
pk_func = FunctionWrapper(fit_output.Function.function[0][0])
299-
for ifunc in range(1, fit_output.Function.nDomains):
300-
pk_func = pk_func + FunctionWrapper(fit_output.Function.function[ifunc][0])
301-
ws_eval = mantid.EvaluateFunction(InputWorkspace=ws, Function=pk_func, EnableLogging=False, StoreInADS=False)
302-
y_nopulse = ws.readY(ispec) - ws_eval.readY(1)
268+
func = fit_output.Function.function
269+
y_pulses = self._eval_fitted_prompt_pulse_peaks_only(ws, func)
270+
y_nopulse = ws.readY(ispec) - y_pulses
303271
y_nopulse[y_nopulse < 0] = 0 # can't have negative counts
304272
ws.setY(ispec, y_nopulse)
305273
else:
@@ -310,6 +278,61 @@ def _subtract_prompt_pulses(self, ws, tof_res=0.002):
310278
# Mask other banks and spectra for which fit failed
311279
self._mask_prompt_pulses(ws, ispec_failed)
312280

281+
@staticmethod
282+
def _eval_fitted_prompt_pulse_peaks_only(ws, func):
283+
# accumulate peak functions only (first function in individual composite functions)
284+
pk_func = FunctionWrapper(func[0][0])
285+
for ifunc in range(1, func.nDomains()):
286+
pk_func = pk_func + FunctionWrapper(func[ifunc][0])
287+
ws_eval = mantid.EvaluateFunction(InputWorkspace=ws, Function=pk_func, EnableLogging=False, StoreInADS=False)
288+
return ws_eval.readY(1)
289+
290+
@staticmethod
291+
def _free_ties_of_multidomain_prompt_pulse_func(func, fit_kwargs):
292+
for ipulse in range(func.nDomains()):
293+
func.removeTie(f"f{ipulse}.f0.Centre")
294+
key_suffix = f"_{ipulse}" if ipulse > 0 else ""
295+
if "Exclude" + key_suffix in fit_kwargs:
296+
func.fixParameter(f"f{ipulse}.f0.Centre") # fix centre as can't fit independently with bragg
297+
else:
298+
fitted_cen = func[ipulse][0]["Centre"]
299+
func[ipulse][0].addConstraints(f"{fitted_cen - 5}<Centre<{fitted_cen + 5}") # free centre but stricter constraint
300+
301+
@staticmethod
302+
def _add_ties_to_multidomain_prompt_pulse_func(func):
303+
# tie peak parameters to be common for all prompt pulses (tie to last peak func - seems to give better results than first)
304+
for idomain in range(func.nDomains() - 1):
305+
for param_name in ["Intensity", "Sigma", "Exponent", "Skew", "Centre"]:
306+
func.tie(f"f{idomain}.f0.{param_name}", f"f{func.nDomains() - 1}.f0.{param_name}") # tie to first
307+
# fix some peak parameters
308+
for param_name in ["Sigma", "Exponent", "Skew"]:
309+
func.fixParameter(f"f{func.nDomains() - 1}.f0.{param_name}")
310+
311+
@staticmethod
312+
def _get_tofs_to_exclude(specinfo, ispec, dpks_bragg, xlo, xhi, tof_res=0.002):
313+
exclude = []
314+
diff_consts = specinfo.diffractometerConstants(ispec)
315+
for dpk in dpks_bragg:
316+
tof_pk = UnitConversion.run("dSpacing", "TOF", dpk, 0, DeltaEModeType.Elastic, diff_consts)
317+
tof_pk_lo = tof_pk * (1 - tof_res)
318+
tof_pk_hi = tof_pk * (1 + tof_res)
319+
if xlo < tof_pk_lo < xhi or xlo < tof_pk_hi < xhi or (tof_pk_lo < xlo and tof_pk_hi > xhi):
320+
exclude.extend([tof_pk_lo, tof_pk_hi])
321+
return exclude
322+
323+
@staticmethod
324+
def _setup_single_prompt_pulse_function(ws, ispec, cen, xlo, xhi):
325+
comp_func = FunctionFactory.createInitialized(
326+
"name=PearsonIV, Centre=8, Intensity=1,Sigma=8.5, Exponent=1.5, Skew=-5,"
327+
"constraints=(0.2<Sigma,1.5<Exponent);name=FlatBackground, A0=0,constraints=(0<A0)"
328+
)
329+
comp_func[0].setAttributeValue("CentreShift", cen)
330+
comp_func[0].setHeight(ws.readY(ispec)[ws.yIndexOfX(cen)])
331+
comp_func[0].addConstraints(f"{-15}<Centre<{15}")
332+
comp_func[0].addConstraints(f"{0}<Intensity")
333+
comp_func[1]["A0"] = min(ws.readY(ispec)[ws.yIndexOfX(xlo)], ws.readY(ispec)[ws.yIndexOfX(xhi)])
334+
return comp_func
335+
313336
@staticmethod
314337
def _find_bragg_peak_dspacings_and_prompt_pulses(ws, ispec_start=0, ispec_end=60, dspac_res=0.001):
315338
"""

0 commit comments

Comments
 (0)