Skip to content

Commit a778d56

Browse files
Kvieta1990pre-commit-ci[bot]y8z
authored andcommitted
Enable user defined sample and container geometry for abs correction (mantidproject#38887)
* enable user defined sample and container geometry for abs correction * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add in release doc * fix logic * fix beam height issue * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * remove redundant checks * fix gauge volume issue * container gauge vol definition * fix quite a few issues with the implementation * fix vanadium gauge volume def issue * add in gauge volume exception * implemented coord system checking --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: y8z <zhangy3@ornl.gov>
1 parent 6c69fd6 commit a778d56

File tree

3 files changed

+165
-5
lines changed

3 files changed

+165
-5
lines changed

Framework/PythonInterface/mantid/utils/absorptioncorrutils.py

Lines changed: 137 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from mantid.kernel import Logger, Property, PropertyManager
99
from mantid.simpleapi import (
1010
AbsorptionCorrection,
11+
DefineGaugeVolume,
1112
DeleteWorkspace,
1213
Divide,
1314
Load,
@@ -24,9 +25,15 @@
2425
import numpy as np
2526
import os
2627
from functools import wraps
28+
import xml.etree.ElementTree as ET
2729

2830
VAN_SAMPLE_DENSITY = 0.0721
2931
_EXTENSIONS_NXS = ["_event.nxs", ".nxs.h5"]
32+
GV_VALS = {
33+
"X": {"x": "0.2", "y": "0.0", "z": "0.0", "t": "90.0", "p": "180.0"},
34+
"Y": {"x": "0.0", "y": "0.2", "z": "0.0", "t": "90.0", "p": "270.0"},
35+
"Z": {"x": "0.0", "y": "0.0", "z": "0.2", "t": "180.0", "p": "0.0"},
36+
}
3037

3138

3239
# ---------------------------- #
@@ -284,6 +291,11 @@ def calculate_absorption_correction(
284291
sample_formula,
285292
mass_density,
286293
sample_geometry={},
294+
can_geometry={},
295+
can_material={},
296+
gauge_vol="",
297+
container_gauge_vol="",
298+
beam_height=Property.EMPTY_DBL,
287299
number_density=Property.EMPTY_DBL,
288300
container_shape="PAC06",
289301
num_wl_bins=1000,
@@ -323,6 +335,11 @@ def calculate_absorption_correction(
323335
:param sample_formula: Sample formula to specify the Material for absorption correction
324336
:param mass_density: Mass density of the sample to specify the Material for absorption correction
325337
:param sample_geometry: Dictionary to specify the sample geometry for absorption correction
338+
:param can_geometry: Dictionary to specify the container geometry for absorption correction
339+
:param can_material: Dictionary to specify the container material for absorption correction
340+
:param gauge_vol: String in XML form to define the volume of the sample visible to the beam
341+
:param container_gauge_vol: String in XML form to define the volume of the container visible to the beam
342+
:param beam_height: Optional beam height to use for absorption correction
326343
:param number_density: Optional number density of sample to be added to the Material for absorption correction
327344
:param container_shape: Shape definition of container, such as PAC06.
328345
:param num_wl_bins: Number of bins for calculating wavelength
@@ -344,12 +361,26 @@ def calculate_absorption_correction(
344361
material["SampleNumberDensity"] = number_density
345362

346363
environment = {}
347-
if container_shape:
364+
find_env = True
365+
if container_shape or (can_geometry and can_material):
348366
environment["Name"] = "InAir"
349-
environment["Container"] = container_shape
367+
find_env = False
368+
if not (can_geometry and can_material):
369+
environment["Container"] = container_shape
350370

351371
donorWS = create_absorption_input(
352-
filename, props, num_wl_bins, material=material, geometry=sample_geometry, environment=environment, metaws=metaws
372+
filename,
373+
props,
374+
num_wl_bins,
375+
material=material,
376+
geometry=sample_geometry,
377+
can_geometry=can_geometry,
378+
can_material=can_material,
379+
gauge_vol=gauge_vol,
380+
beam_height=beam_height,
381+
environment=environment,
382+
find_environment=find_env,
383+
metaws=metaws,
353384
)
354385

355386
# NOTE: Ideally we want to separate cache related task from calculation,
@@ -381,6 +412,8 @@ def calculate_absorption_correction(
381412
donorWS,
382413
abs_method,
383414
element_size,
415+
container_gauge_vol=container_gauge_vol,
416+
beam_height=beam_height,
384417
prefix_name=absName,
385418
cache_dirs=cache_dirs,
386419
ms_method=ms_method,
@@ -392,6 +425,8 @@ def calc_absorption_corr_using_wksp(
392425
donor_wksp,
393426
abs_method,
394427
element_size=1,
428+
container_gauge_vol="",
429+
beam_height=Property.EMPTY_DBL,
395430
prefix_name="",
396431
cache_dirs=[],
397432
ms_method="",
@@ -401,7 +436,7 @@ def calc_absorption_corr_using_wksp(
401436
if cache_dirs:
402437
log.warning("Empty cache dir found.")
403438
# 1. calculate first order absorption correction
404-
abs_s, abs_c = calc_1st_absorption_corr_using_wksp(donor_wksp, abs_method, element_size, prefix_name)
439+
abs_s, abs_c = calc_1st_absorption_corr_using_wksp(donor_wksp, abs_method, element_size, container_gauge_vol, beam_height, prefix_name)
405440
# 2. calculate 2nd order absorption correction
406441
if ms_method in ["", None, "None"]:
407442
log.information("Skip multiple scattering correction as instructed.")
@@ -452,6 +487,8 @@ def calc_1st_absorption_corr_using_wksp(
452487
donor_wksp,
453488
abs_method,
454489
element_size=1,
490+
container_gauge_vol="",
491+
beam_height=Property.EMPTY_DBL,
455492
prefix_name="",
456493
):
457494
"""
@@ -461,6 +498,8 @@ def calc_1st_absorption_corr_using_wksp(
461498
:param donor_wksp: Input workspace to compute absorption correction on
462499
:param abs_method: Type of absorption correction: None, SampleOnly, SampleAndContainer, FullPaalmanPings
463500
:param element_size: Size of one side of the integration element cube in mm
501+
:param container_gauge_vol: String in XML form to define the volume of container visible to the beam
502+
:param beam_height: Beam height for defining the gauge volume for container visible to the beam
464503
:param prefix_name: Optional prefix of the output workspaces, default is the donor_wksp name.
465504
466505
:return: Two workspaces (A_s, A_c), the first for the sample and the second for the container
@@ -473,6 +512,13 @@ def calc_1st_absorption_corr_using_wksp(
473512
raise RuntimeError("Specified donor workspace not found in the ADS")
474513
donor_wksp = mtd[donor_wksp]
475514

515+
def is_valid_xml(xml_string):
516+
try:
517+
ET.fromstring(xml_string)
518+
return True
519+
except ET.ParseError:
520+
return False
521+
476522
absName = donor_wksp.name()
477523
if prefix_name != "":
478524
absName = prefix_name
@@ -482,6 +528,42 @@ def calc_1st_absorption_corr_using_wksp(
482528
return absName + "_ass", ""
483529
elif abs_method == "SampleAndContainer":
484530
AbsorptionCorrection(donor_wksp, OutputWorkspace=absName + "_ass", ScatterFrom="Sample", ElementSize=element_size)
531+
if container_gauge_vol and is_valid_xml(container_gauge_vol):
532+
try:
533+
DefineGaugeVolume(donor_wksp, container_gauge_vol)
534+
except ValueError:
535+
pass
536+
elif container_gauge_vol and beam_height != Property.EMPTY_DBL:
537+
ref_frame = donor_wksp.getInstrument().getReferenceFrame()
538+
up_direction = ref_frame.pointingUpAxis()
539+
info_to_use = GV_VALS[up_direction]
540+
541+
# Factor '100' here is for unit conversion from cm to m. An extra factor
542+
# of '2' in 'beam_height / 200.0' is to make sure the center of the
543+
# bottom base of the gauge volume is down from the origin by half of the
544+
# gauge volume height so the center of the gauge volume is at the origin.
545+
# This is our assumed location of the beam if only beam height is given.
546+
gauge_vol = """<hollow-cylinder id="container_gauge">
547+
<centre-of-bottom-base r="{0:4.2F}" t="{1:s}" p="{2:s}" />
548+
<axis x="{3:s}" y="{4:s}" z="{5:s}" />
549+
<inner-radius val="{6:7.5F}" />
550+
<outer-radius val="{7:7.5F}" />
551+
<height val="{8:4.2F}" />
552+
</hollow-cylinder>
553+
"""
554+
gauge_vol = gauge_vol.format(
555+
beam_height / 200.0,
556+
info_to_use["t"],
557+
info_to_use["p"],
558+
info_to_use["x"],
559+
info_to_use["y"],
560+
info_to_use["z"],
561+
float(container_gauge_vol.split()[0]) / 100.0,
562+
float(container_gauge_vol.split()[1]) / 100.0,
563+
beam_height / 100.0,
564+
)
565+
DefineGaugeVolume(donor_wksp, gauge_vol)
566+
485567
AbsorptionCorrection(donor_wksp, OutputWorkspace=absName + "_acc", ScatterFrom="Container", ElementSize=element_size)
486568
return absName + "_ass", absName + "_acc"
487569
elif abs_method == "FullPaalmanPings":
@@ -499,6 +581,10 @@ def create_absorption_input(
499581
num_wl_bins=1000,
500582
material={},
501583
geometry={},
584+
can_geometry={},
585+
can_material={},
586+
gauge_vol="",
587+
beam_height=Property.EMPTY_DBL,
502588
environment={},
503589
find_environment=True,
504590
opt_wl_min=0,
@@ -513,6 +599,10 @@ def create_absorption_input(
513599
:param num_wl_bins: The number of wavelength bins used for absorption correction
514600
:param material: Optional material to use in SetSample
515601
:param geometry: Optional geometry to use in SetSample
602+
:param can_geometry: Optional container geometry to use in SetSample
603+
:param can_material: Optional container material to use in SetSample
604+
:param gauge_vol: Optional gauge volume definition, i.e., sample portion visible to the beam.
605+
:param beam_height: Optional beam height to define gauge volume
516606
:param environment: Optional environment to use in SetSample
517607
:param find_environment: Optional find_environment to control whether to figure out environment automatically.
518608
:param opt_wl_min: Optional minimum wavelength. If specified, this is used instead of from the props
@@ -611,7 +701,49 @@ def confirmProps(props):
611701
# Make sure one is set before calling SetSample
612702
if material or geometry or environment:
613703
mantid.simpleapi.SetSampleFromLogs(
614-
InputWorkspace=absName, Material=material, Geometry=geometry, Environment=environment, FindEnvironment=find_environment
704+
InputWorkspace=absName,
705+
Material=material,
706+
Geometry=geometry,
707+
ContainerGeometry=can_geometry,
708+
ContainerMaterial=can_material,
709+
Environment=environment,
710+
FindEnvironment=find_environment,
615711
)
616712

713+
if beam_height != Property.EMPTY_DBL and not gauge_vol:
714+
# If the gauge volume is not defined, use the beam height to define it,
715+
# and we will be assuming a cylinder shape of the sample.
716+
ref_frame = mtd[absName].getInstrument().getReferenceFrame()
717+
up_direction = ref_frame.pointingUpAxis()
718+
info_to_use = GV_VALS[up_direction]
719+
gauge_vol = """<cylinder id="shape">
720+
<centre-of-bottom-base r="{0:4.2F}" t="{1:s}" p="{2:s}" />
721+
<axis x="{3:s}" y="{4:s}" z="{5:s}" />
722+
<radius val="{6:7.5F}" />
723+
<height val="{7:4.2F}" />
724+
</cylinder>"""
725+
if isinstance(geometry["Radius"], float):
726+
sam_rad = geometry["Radius"]
727+
else:
728+
sam_rad = geometry["Radius"].value
729+
730+
# Factor '100' here is for unit conversion from cm to m. An extra factor
731+
# of '2' in 'beam_height / 200.0' is to make sure the center of the
732+
# bottom base of the gauge volume is down from the origin by half of the
733+
# gauge volume height so the center of the gauge volume is at the origin.
734+
# This is our assumed location of the beam if only beam height is given.
735+
gauge_vol = gauge_vol.format(
736+
beam_height / 200.0,
737+
info_to_use["t"],
738+
info_to_use["p"],
739+
info_to_use["x"],
740+
info_to_use["y"],
741+
info_to_use["z"],
742+
sam_rad / 100.0,
743+
beam_height / 100.0,
744+
)
745+
746+
if gauge_vol:
747+
DefineGaugeVolume(absName, gauge_vol)
748+
617749
return absName

Framework/PythonInterface/plugins/algorithms/SNSPowderReduction.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,19 @@ def PyInit(self):
291291
)
292292
self.declareProperty("SampleFormula", "", doc="Chemical formula of the sample")
293293
self.declareProperty("SampleGeometry", {}, doc="A dictionary of geometry parameters for the sample.")
294+
self.declareProperty("ContainerGeometry", {}, doc="A dictionary of geometry parameters for the container.")
295+
self.declareProperty("ContainerMaterial", {}, doc="A dictionary of material parameters for the container.")
296+
self.declareProperty(
297+
"GaugeVolume", "", "A string in XML form for gauge volume definition indicating sample portion visible to the beam."
298+
)
299+
self.declareProperty(
300+
"ContainerGaugeVolume", "", "A string in XML form for gauge volume definition indicating container portion visible to the beam."
301+
)
302+
self.declareProperty(
303+
"BeamHeight",
304+
defaultValue=Property.EMPTY_DBL,
305+
doc="Height of the neutron beam cross section in cm",
306+
)
294307
self.declareProperty(
295308
"MeasuredMassDensity",
296309
defaultValue=0.1,
@@ -423,6 +436,11 @@ def PyExec(self): # noqa
423436
self._absMethod = self.getProperty("TypeOfCorrection").value
424437
self._sampleFormula = self.getProperty("SampleFormula").value
425438
self._sampleGeometry = self.getProperty("SampleGeometry").value
439+
self._containerGeometry = self.getProperty("ContainerGeometry").value
440+
self._containerMaterial = self.getProperty("ContainerMaterial").value
441+
self._gaugeVolume = self.getProperty("GaugeVolume").value
442+
self._containerGaugeVolume = self.getProperty("ContainerGaugeVolume").value
443+
self._beamHeight = self.getProperty("BeamHeight").value
426444
self._massDensity = self.getProperty("MeasuredMassDensity").value
427445
self._numberDensity = self.getProperty("SampleNumberDensity").value
428446
self._containerShape = self.getProperty("ContainerShape").value
@@ -524,6 +542,11 @@ def PyExec(self): # noqa
524542
self._sampleFormula, # Material for absorption correction
525543
self._massDensity, # Mass density of the sample
526544
self._sampleGeometry, # Geometry parameters for the sample
545+
self._containerGeometry, # Geometry parameters for the container
546+
self._containerMaterial, # Material parameters for the container
547+
self._gaugeVolume, # Gauge volume definition for sample
548+
self._containerGaugeVolume, # Gauge volume definition for container
549+
self._beamHeight, # Height of the neutron beam cross section in cm
527550
self._numberDensity, # Optional number density of sample to be added
528551
self._containerShape, # Shape definition of container
529552
self._num_wl_bins, # Number of bins: len(ws.readX(0))-1
@@ -1515,6 +1538,7 @@ def _process_vanadium_runs(self, van_run_number_list, samRunIndex, **dummy_focus
15151538
self._num_wl_bins,
15161539
material={"ChemicalFormula": "V", "SampleNumberDensity": absorptioncorrutils.VAN_SAMPLE_DENSITY},
15171540
geometry={"Shape": "Cylinder", "Height": 7.0, "Radius": self._vanRadius, "Center": [0.0, 0.0, 0.0]},
1541+
beam_height=self._beamHeight,
15181542
find_environment=False,
15191543
opt_wl_min=self._wavelengthMin,
15201544
opt_wl_max=self._wavelengthMax,
@@ -1543,6 +1567,9 @@ def _process_vanadium_runs(self, van_run_number_list, samRunIndex, **dummy_focus
15431567
)
15441568
api.RenameWorkspace(abs_v_wsn, "__V_corr_abs")
15451569

1570+
# Here, we are using a combo of absorption correction with the numerical integration approach and multiple
1571+
# scattering correction with the Carpenter approach - `Absorption` param set to `False` below, making sure
1572+
# only `__V_corr_ms` will be created without overwriting the already calculated `__V_corr_abs`.
15461573
api.CalculateCarpenterSampleCorrection(
15471574
InputWorkspace=absWksp, OutputWorkspaceBaseName="__V_corr", CylinderSampleRadius=self._vanRadius, Absorption=False
15481575
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Enable user defined sample and container geometry together with the definition of gauge volume to account for the beam size. Implementation made in :ref:`SNSPowderReduction <algm-SNSPowderReduction>` and ``mantid.utils.absorptioncorrutils``.

0 commit comments

Comments
 (0)