Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable user defined sample and container geometry for abs correction #38887

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
102 changes: 97 additions & 5 deletions Framework/PythonInterface/mantid/utils/absorptioncorrutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from mantid.kernel import Logger, Property, PropertyManager
from mantid.simpleapi import (
AbsorptionCorrection,
DefineGaugeVolume,
DeleteWorkspace,
Divide,
Load,
Expand All @@ -24,6 +25,7 @@
import numpy as np
import os
from functools import wraps
import xml.etree.ElementTree as ET

VAN_SAMPLE_DENSITY = 0.0721
_EXTENSIONS_NXS = ["_event.nxs", ".nxs.h5"]
Expand Down Expand Up @@ -284,6 +286,11 @@ def calculate_absorption_correction(
sample_formula,
mass_density,
sample_geometry={},
can_geometry={},
can_material={},
gauge_vol="",
container_gauge_vol="",
beam_height=Property.EMPTY_DBL,
number_density=Property.EMPTY_DBL,
container_shape="PAC06",
num_wl_bins=1000,
Expand Down Expand Up @@ -323,6 +330,11 @@ def calculate_absorption_correction(
:param sample_formula: Sample formula to specify the Material for absorption correction
:param mass_density: Mass density of the sample to specify the Material for absorption correction
:param sample_geometry: Dictionary to specify the sample geometry for absorption correction
:param can_geometry: Dictionary to specify the container geometry for absorption correction
:param can_material: Dictionary to specify the container material for absorption correction
:param gauge_vol: String in XML form to define the volume of the sample visible to the beam
:param container_gauge_vol: String in XML form to define the volume of the container visible to the beam
:param beam_height: Optional beam height to use for absorption correction
:param number_density: Optional number density of sample to be added to the Material for absorption correction
:param container_shape: Shape definition of container, such as PAC06.
:param num_wl_bins: Number of bins for calculating wavelength
Expand All @@ -344,12 +356,26 @@ def calculate_absorption_correction(
material["SampleNumberDensity"] = number_density

environment = {}
if container_shape:
find_env = True
if container_shape or (can_geometry and can_material):
environment["Name"] = "InAir"
environment["Container"] = container_shape
find_env = False
if not (can_geometry and can_material):
environment["Container"] = container_shape

donorWS = create_absorption_input(
filename, props, num_wl_bins, material=material, geometry=sample_geometry, environment=environment, metaws=metaws
filename,
props,
num_wl_bins,
material=material,
geometry=sample_geometry,
can_geometry=can_geometry,
can_material=can_material,
gauge_vol=gauge_vol,
beam_height=beam_height,
environment=environment,
find_environment=find_env,
metaws=metaws,
)

# NOTE: Ideally we want to separate cache related task from calculation,
Expand Down Expand Up @@ -381,6 +407,8 @@ def calculate_absorption_correction(
donorWS,
abs_method,
element_size,
container_gauge_vol=container_gauge_vol,
beam_height=beam_height,
prefix_name=absName,
cache_dirs=cache_dirs,
ms_method=ms_method,
Expand All @@ -392,6 +420,8 @@ def calc_absorption_corr_using_wksp(
donor_wksp,
abs_method,
element_size=1,
container_gauge_vol="",
beam_height=Property.EMPTY_DBL,
prefix_name="",
cache_dirs=[],
ms_method="",
Expand All @@ -401,7 +431,7 @@ def calc_absorption_corr_using_wksp(
if cache_dirs:
log.warning("Empty cache dir found.")
# 1. calculate first order absorption correction
abs_s, abs_c = calc_1st_absorption_corr_using_wksp(donor_wksp, abs_method, element_size, prefix_name)
abs_s, abs_c = calc_1st_absorption_corr_using_wksp(donor_wksp, abs_method, element_size, container_gauge_vol, beam_height, prefix_name)
# 2. calculate 2nd order absorption correction
if ms_method in ["", None, "None"]:
log.information("Skip multiple scattering correction as instructed.")
Expand Down Expand Up @@ -452,6 +482,8 @@ def calc_1st_absorption_corr_using_wksp(
donor_wksp,
abs_method,
element_size=1,
container_gauge_vol="",
beam_height=Property.EMPTY_DBL,
prefix_name="",
):
"""
Expand All @@ -461,6 +493,8 @@ def calc_1st_absorption_corr_using_wksp(
:param donor_wksp: Input workspace to compute absorption correction on
:param abs_method: Type of absorption correction: None, SampleOnly, SampleAndContainer, FullPaalmanPings
:param element_size: Size of one side of the integration element cube in mm
:param container_gauge_vol: String in XML form to define the volume of container visible to the beam
:param beam_height: Beam height for defining the gauge volume for container visible to the beam
:param prefix_name: Optional prefix of the output workspaces, default is the donor_wksp name.

:return: Two workspaces (A_s, A_c), the first for the sample and the second for the container
Expand All @@ -473,6 +507,13 @@ def calc_1st_absorption_corr_using_wksp(
raise RuntimeError("Specified donor workspace not found in the ADS")
donor_wksp = mtd[donor_wksp]

def is_valid_xml(xml_string):
try:
ET.fromstring(xml_string)
return True
except ET.ParseError:
return False

absName = donor_wksp.name()
if prefix_name != "":
absName = prefix_name
Expand All @@ -482,6 +523,25 @@ def calc_1st_absorption_corr_using_wksp(
return absName + "_ass", ""
elif abs_method == "SampleAndContainer":
AbsorptionCorrection(donor_wksp, OutputWorkspace=absName + "_ass", ScatterFrom="Sample", ElementSize=element_size)
if container_gauge_vol and is_valid_xml(container_gauge_vol):
DefineGaugeVolume(donor_wksp, container_gauge_vol)
elif container_gauge_vol and beam_height != Property.EMPTY_DBL:
gauge_vol = """<hollow-cylinder id="container_gauge">
<centre-of-bottom-base r="{0:4.2F}" t="90.0" p="270.0" />
<axis x="0.0" y="0.2" z="0" />
<inner-radius val="{1:7.5F}" />
<outer-radius val="{2:7.5F}" />
<height val="{3:4.2F}" />
</hollow-cylinder>
"""
gauge_vol = gauge_vol.format(
beam_height / 200.0,
float(container_gauge_vol.split()[0]) / 100.0,
float(container_gauge_vol.split()[1]) / 100.0,
beam_height / 100.0,
)
DefineGaugeVolume(donor_wksp, gauge_vol)

AbsorptionCorrection(donor_wksp, OutputWorkspace=absName + "_acc", ScatterFrom="Container", ElementSize=element_size)
return absName + "_ass", absName + "_acc"
elif abs_method == "FullPaalmanPings":
Expand All @@ -499,6 +559,10 @@ def create_absorption_input(
num_wl_bins=1000,
material={},
geometry={},
can_geometry={},
can_material={},
gauge_vol="",
beam_height=Property.EMPTY_DBL,
environment={},
find_environment=True,
opt_wl_min=0,
Expand All @@ -513,6 +577,10 @@ def create_absorption_input(
:param num_wl_bins: The number of wavelength bins used for absorption correction
:param material: Optional material to use in SetSample
:param geometry: Optional geometry to use in SetSample
:param can_geometry: Optional container geometry to use in SetSample
:param can_material: Optional container material to use in SetSample
:param gauge_vol: Optional gauge volume definition, i.e., sample portion visible to the beam.
:param beam_height: Optional beam height to define gauge volume
:param environment: Optional environment to use in SetSample
:param find_environment: Optional find_environment to control whether to figure out environment automatically.
:param opt_wl_min: Optional minimum wavelength. If specified, this is used instead of from the props
Expand Down Expand Up @@ -611,7 +679,31 @@ def confirmProps(props):
# Make sure one is set before calling SetSample
if material or geometry or environment:
mantid.simpleapi.SetSampleFromLogs(
InputWorkspace=absName, Material=material, Geometry=geometry, Environment=environment, FindEnvironment=find_environment
InputWorkspace=absName,
Material=material,
Geometry=geometry,
ContainerGeometry=can_geometry,
ContainerMaterial=can_material,
Environment=environment,
FindEnvironment=find_environment,
)

if beam_height != Property.EMPTY_DBL and not gauge_vol:
# If the gauge volume is not defined, use the beam height to define it,
# and we will be assuming a cylinder shape of the sample.
gauge_vol = """<cylinder id="shape">
<centre-of-bottom-base r="{0:4.2F}" t="90.0" p="270.0" />
<axis x="0.0" y="0.2" z="0.0" />
<radius val="{1:7.5F}" />
<height val="{2:4.2F}" />
</cylinder>"""
if isinstance(geometry["Radius"], float):
sam_rad = geometry["Radius"]
else:
sam_rad = geometry["Radius"].value
gauge_vol = gauge_vol.format(beam_height / 200.0, sam_rad / 100.0, beam_height / 100.0)

if gauge_vol:
DefineGaugeVolume(absName, gauge_vol)

return absName
27 changes: 27 additions & 0 deletions Framework/PythonInterface/plugins/algorithms/SNSPowderReduction.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,19 @@ def PyInit(self):
)
self.declareProperty("SampleFormula", "", doc="Chemical formula of the sample")
self.declareProperty("SampleGeometry", {}, doc="A dictionary of geometry parameters for the sample.")
self.declareProperty("ContainerGeometry", {}, doc="A dictionary of geometry parameters for the container.")
self.declareProperty("ContainerMaterial", {}, doc="A dictionary of material parameters for the container.")
self.declareProperty(
"GaugeVolume", "", "A string in XML form for gauge volume definition indicating sample portion visible to the beam."
)
self.declareProperty(
"ContainerGaugeVolume", "", "A string in XML form for gauge volume definition indicating container portion visible to the beam."
)
self.declareProperty(
"BeamHeight",
defaultValue=Property.EMPTY_DBL,
doc="Height of the neutron beam cross section in cm",
)
self.declareProperty(
"MeasuredMassDensity",
defaultValue=0.1,
Expand Down Expand Up @@ -423,6 +436,11 @@ def PyExec(self): # noqa
self._absMethod = self.getProperty("TypeOfCorrection").value
self._sampleFormula = self.getProperty("SampleFormula").value
self._sampleGeometry = self.getProperty("SampleGeometry").value
self._containerGeometry = self.getProperty("ContainerGeometry").value
self._containerMaterial = self.getProperty("ContainerMaterial").value
self._gaugeVolume = self.getProperty("GaugeVolume").value
self._containerGaugeVolume = self.getProperty("ContainerGaugeVolume").value
self._beamHeight = self.getProperty("BeamHeight").value
self._massDensity = self.getProperty("MeasuredMassDensity").value
self._numberDensity = self.getProperty("SampleNumberDensity").value
self._containerShape = self.getProperty("ContainerShape").value
Expand Down Expand Up @@ -524,6 +542,11 @@ def PyExec(self): # noqa
self._sampleFormula, # Material for absorption correction
self._massDensity, # Mass density of the sample
self._sampleGeometry, # Geometry parameters for the sample
self._containerGeometry, # Geometry parameters for the container
self._containerMaterial, # Material parameters for the container
self._gaugeVolume, # Gauge volume definition for sample
self._containerGaugeVolume, # Gauge volume definition for container
self._beamHeight, # Height of the neutron beam cross section in cm
self._numberDensity, # Optional number density of sample to be added
self._containerShape, # Shape definition of container
self._num_wl_bins, # Number of bins: len(ws.readX(0))-1
Expand Down Expand Up @@ -1515,6 +1538,7 @@ def _process_vanadium_runs(self, van_run_number_list, samRunIndex, **dummy_focus
self._num_wl_bins,
material={"ChemicalFormula": "V", "SampleNumberDensity": absorptioncorrutils.VAN_SAMPLE_DENSITY},
geometry={"Shape": "Cylinder", "Height": 7.0, "Radius": self._vanRadius, "Center": [0.0, 0.0, 0.0]},
beam_height=self._beamHeight,
find_environment=False,
opt_wl_min=self._wavelengthMin,
opt_wl_max=self._wavelengthMax,
Expand Down Expand Up @@ -1543,6 +1567,9 @@ def _process_vanadium_runs(self, van_run_number_list, samRunIndex, **dummy_focus
)
api.RenameWorkspace(abs_v_wsn, "__V_corr_abs")

# Here, we are using a combo of absorption correction with the numerical integration approach and multiple
# scattering correction with the Carpenter approach - `Absorption` param set to `False` below, making sure
# only `__V_corr_ms` will be created without overwriting the already calculated `__V_corr_abs`.
api.CalculateCarpenterSampleCorrection(
InputWorkspace=absWksp, OutputWorkspaceBaseName="__V_corr", CylinderSampleRadius=self._vanRadius, Absorption=False
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +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``.