Skip to content
Snippets Groups Projects
Commit 0fac1e10 authored by David Fairbrother's avatar David Fairbrother
Browse files

Re #19509 Implemented SampleDetails for inst and absorption methods

parent 1bd161dc
No related branches found
No related tags found
No related merge requests found
......@@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function)
from isis_powder.abstract_inst import AbstractInst
from isis_powder.gem_routines import gem_advanced_config, gem_algs, gem_param_mapping
from isis_powder.routines import common, instrument_settings
from isis_powder.routines import common, instrument_settings, sample_details
class Gem(AbstractInst):
......@@ -16,6 +16,9 @@ class Gem(AbstractInst):
output_dir=self._inst_settings.output_dir, inst_prefix="GEM")
self._cached_run_details = {}
self._sample_details = None
# Public API
def focus(self, **kwargs):
self._inst_settings.update_attributes(kwargs=kwargs)
......@@ -28,6 +31,18 @@ class Gem(AbstractInst):
return self._create_vanadium(run_number_string=self._inst_settings.run_in_range,
do_absorb_corrections=self._inst_settings.do_absorb_corrections)
def set_sample_details(self, **kwargs):
sample_details_obj = common.dictionary_key_helper(
dictionary=kwargs, key="new_sample_details",
exception_msg="The argument containing sample details was not found. Please"
" set the following argument: new_sample_details")
if not isinstance(sample_details_obj, sample_details.SampleDetails):
raise ValueError("The object passed was not a SampleDetails object. Please create a"
" SampleDetails object and set the relevant properties to use on this method")
self._sample_details = sample_details_obj
# Private methods
def _get_run_details(self, run_number_string):
run_number_string_key = run_number_string + str(self._inst_settings.file_extension)
if run_number_string_key in self._cached_run_details:
......
......@@ -2,7 +2,7 @@ from __future__ import (absolute_import, division, print_function)
import os
from isis_powder.routines import common, instrument_settings
from isis_powder.routines import common, instrument_settings, sample_details
from isis_powder.abstract_inst import AbstractInst
from isis_powder.polaris_routines import polaris_advanced_config, polaris_algs, polaris_param_mapping
......@@ -19,6 +19,9 @@ class Polaris(AbstractInst):
# Hold the last dictionary later to avoid us having to keep parsing the YAML
self._run_details_cached_obj = {}
self._sample_details = None
# Public API
def focus(self, **kwargs):
self._inst_settings.update_attributes(kwargs=kwargs)
......@@ -30,6 +33,16 @@ class Polaris(AbstractInst):
return self._create_vanadium(run_number_string=self._inst_settings.run_in_range,
do_absorb_corrections=self._inst_settings.do_absorb_corrections)
def set_sample_details(self, **kwargs):
sample_details_obj = common.dictionary_key_helper(
dictionary=kwargs, key="new_sample_details",
exception_msg="The argument containing sample details was not found. Please"
" set the following argument: new_sample_details")
if not isinstance(sample_details_obj, sample_details.SampleDetails):
raise ValueError("The object passed was not a SampleDetails object. Please create a"
" SampleDetails object and set the relevant properties to use on this method")
self._sample_details = sample_details_obj
# Overrides
def _apply_absorb_corrections(self, run_details, van_ws):
return polaris_algs.calculate_absorb_corrections(ws_to_correct=van_ws,
......
......@@ -2,22 +2,15 @@ from __future__ import (absolute_import, division, print_function)
import mantid.simpleapi as mantid
from isis_powder.routines import common
from isis_powder.routines import common, sample_details
def run_cylinder_absorb_corrections(ws_to_correct, multiple_scattering, config_dict):
def create_vanadium_sample_details_obj(config_dict):
"""
Sets a cylindrical sample from the user specified config dictionary and performs Mayers
sample correction on the workspace. The config dictionary must be for a cylinder and contain
the keys "cylinder_sample_height", "cylinder_sample_radius", "cylinder_position" and
"chemical_formula". If any of these keys are not found an exception is raise informing
the user that the key was not in the advanced configuration file.
Additionally it checks the value of multiple_scattering to determine whether to take
into account the effects of multiple scattering.
:param ws_to_correct: The workspace to perform Mayers sample correction on
:param multiple_scattering: Boolean of whether to account for the effects of multiple scattering
:param config_dict: A dictionary containing the required keys to set a cylinder sample
:return: The corrected workspace
Creates a SampleDetails object based on a vanadium sample which is found
in the advanced config of an instrument.
:param config_dict: The advanced config dictionary of the instrument for the vanadium sample
:return: A sample details object which holds properties used in sample corrections
"""
height_key = "cylinder_sample_height"
radius_key = "cylinder_sample_radius"
......@@ -30,18 +23,37 @@ def run_cylinder_absorb_corrections(ws_to_correct, multiple_scattering, config_d
height = common.dictionary_key_helper(dictionary=config_dict, key=height_key, exception_msg=e_msg + height_key)
radius = common.dictionary_key_helper(dictionary=config_dict, key=radius_key, exception_msg=e_msg + radius_key)
pos = common.dictionary_key_helper(dictionary=config_dict, key=pos_key, exception_msg=e_msg + pos_key)
formula = common.dictionary_key_helper(dictionary=config_dict, key=formula_key, exception_msg=e_msg + formula_key)
if len(formula) > 1:
# Not a trivial element so we need them to manually specify number density as Mantid
# struggles to calculate it
number_density = common.dictionary_key_helper(
dictionary=config_dict, key=number_density_key,
exception_msg="The number density is required as the chemical formula (" + str(formula) + ")"
" is not a single element. The number density was not found. Please add the following key: " +
number_density_key)
else:
number_density = None
number_density = common.dictionary_key_helper(dictionary=config_dict, key=number_density_key, throws=False)
vanadium_sample_details = sample_details.SampleDetails(height=height, radius=radius, pos=pos)
vanadium_sample_details.set_material(chemical_formula=formula, number_density=number_density)
return vanadium_sample_details
def run_cylinder_absorb_corrections(ws_to_correct, multiple_scattering, sample_details_obj):
"""
Sets a cylindrical sample from the user specified config dictionary and performs Mayers
sample correction on the workspace. The SampleDetails object defines the sample, material
and associated properties. Additionally it checks the value of multiple_scattering to
determine whether to take into account the effects of multiple scattering.
:param ws_to_correct: The workspace to perform Mayers sample correction on
:param multiple_scattering: Boolean of whether to account for the effects of multiple scattering
:param sample_details_obj: The object containing the sample details
:return: The corrected workspace
"""
height = sample_details_obj.height
radius = sample_details_obj.radius
pos = sample_details_obj.center
# Get the underlying material object
if not sample_details_obj.is_material_set():
raise RuntimeError("The material for this sample has not been set yet. Please call"
" set_material on the SampleDetails object to set the material")
material_obj = sample_details_obj.material_object
formula = material_obj.chemical_formula
number_density = material_obj.number_density
ws_to_correct = _calculate__cylinder_absorb_corrections(
ws_to_correct=ws_to_correct, multiple_scattering=multiple_scattering,
......
......@@ -20,13 +20,16 @@ class SampleDetails(object):
self.radius = float(radius)
self.center = [float(i) for i in center] # List of X, Y, Z position
self._material_object = None
self.material_object = None
def is_material_set(self):
return self.material_object is not None
def print_sample_details(self):
self._print()
def reset_sample_material(self):
self._material_object = None
self.material_object = None
def set_material(self, **kwargs):
chemical_formula = common.dictionary_key_helper(dictionary=kwargs, key="chemical_formula",
......@@ -34,13 +37,13 @@ class SampleDetails(object):
" passed: chemical_formula")
number_density = common.dictionary_key_helper(dictionary=kwargs, key="number_density", throws=False)
if self._material_object is not None:
if self.material_object is not None:
self.print_sample_details()
raise RuntimeError("The material has already been set to the above details. If the properties"
" have not been set they can be modified with 'set_material_properties()'. Otherwise"
" to change the material call 'reset_sample_material()'")
self._material_object = _Material(chemical_formula=chemical_formula, numeric_density=number_density)
self.material_object = _Material(chemical_formula=chemical_formula, numeric_density=number_density)
def set_material_properties(self, **kwargs):
err_msg = "The following argument is required but was not set or passed: "
......@@ -48,12 +51,12 @@ class SampleDetails(object):
exception_msg=err_msg + "absorption_cross_section")
scattering_cross_section = common.dictionary_key_helper(dictionary=kwargs, key="scattering_cross_section",
exception_msg=err_msg + "scattering_cross_section")
if self._material_object is None:
if self.material_object is None:
raise RuntimeError("The material has not been set (or reset). Please set it by calling"
" 'set_material()' to set the material details of the sample.")
self._material_object.set_material_properties(abs_cross_sect=absorption_cross_section,
scattering_cross_sect=scattering_cross_section)
self.material_object.set_material_properties(abs_cross_sect=absorption_cross_section,
scattering_cross_sect=scattering_cross_section)
def _print(self):
print("Sample Details:")
......@@ -63,10 +66,10 @@ class SampleDetails(object):
print("Radius: {}".format(self.radius))
print("Center X:{}, Y:{}, Z{}".format(self.center[0], self.center[1], self.center[2]))
print("------------------------")
if self._material_object is None:
if self.material_object is None:
print("Material has not been set (or has been reset).")
else:
self._material_object.print_material()
self.material_object.print_material()
print() # Newline for visual spacing
@staticmethod
......@@ -93,7 +96,7 @@ class SampleDetails(object):
class _Material(object):
def __init__(self, chemical_formula, numeric_density=None):
self._chemical_formula = chemical_formula
self.chemical_formula = chemical_formula
# If it is not an element Mantid requires us to provide the numeric density
# which is required for absorption corrections.
......@@ -105,11 +108,11 @@ class _Material(object):
# Always check value is sane if user has given one
_check_value_is_physical(property_name="numeric_density", value=numeric_density)
self._numeric_density = numeric_density
self.numeric_density = numeric_density
# Advanced material properties
self._absorption_cross_section = None
self._scattering_cross_section = None
self.absorption_cross_section = None
self.scattering_cross_section = None
# Internal flags so we are only allowed to set the material properties once
self._is_material_props_set = False
......@@ -117,18 +120,18 @@ class _Material(object):
def print_material(self):
print("Material properties:")
print("------------------------")
print("Chemical formula: {}".format(self._chemical_formula))
print("Chemical formula: {}".format(self.chemical_formula))
if self._numeric_density:
print("Numeric Density: {}".format(self._numeric_density))
if self.numeric_density:
print("Numeric Density: {}".format(self.numeric_density))
else:
print("Numeric Density: Set from elemental properties by Mantid")
self._print_material_properties()
def _print_material_properties(self):
if self._is_material_props_set:
print("Absorption cross section: {}".format(self._absorption_cross_section))
print("Scattering cross section: {}".format(self._scattering_cross_section))
print("Absorption cross section: {}".format(self.absorption_cross_section))
print("Scattering cross section: {}".format(self.scattering_cross_section))
else:
print("Absorption cross section: Calculated by Mantid based on chemical/elemental formula")
print("Scattering cross section: Calculated by Mantid based on chemical/elemental formula")
......@@ -143,8 +146,8 @@ class _Material(object):
_check_value_is_physical("absorption_cross_section", abs_cross_sect)
_check_value_is_physical("scattering_cross_section", scattering_cross_sect)
self._absorption_cross_section = float(abs_cross_sect)
self._scattering_cross_section = float(scattering_cross_sect)
self.absorption_cross_section = float(abs_cross_sect)
self.scattering_cross_section = float(scattering_cross_sect)
self._is_material_props_set = True
......
......@@ -112,7 +112,7 @@ class ISISPowderSampleDetailsTest(unittest.TestCase):
# Check that we can only set a material once. We will test the underlying class elsewhere
sample_details_obj.set_material(chemical_formula='V')
self.assertIsNotNone(sample_details_obj._material_object)
self.assertIsNotNone(sample_details_obj.material_object)
# Check that the material is now immutable
with assertRaisesRegex(self, RuntimeError, "The material has already been set to the above details"):
......@@ -120,16 +120,16 @@ class ISISPowderSampleDetailsTest(unittest.TestCase):
# Check resetting it works
sample_details_obj.reset_sample_material()
self.assertIsNone(sample_details_obj._material_object)
self.assertIsNone(sample_details_obj.material_object)
# And ensure setting it for a second time works
sample_details_obj.set_material(chemical_formula='V')
self.assertIsNotNone(sample_details_obj._material_object)
self.assertIsNotNone(sample_details_obj.material_object)
def test_set_material_properties(self):
sample_details_obj = sample_details.SampleDetails(height=1.0, radius=1.0, center=[2, 3, 5])
self.assertIsNone(sample_details_obj._material_object)
self.assertIsNone(sample_details_obj.material_object)
# Check we cannot set a material property without setting the underlying material
with assertRaisesRegex(self, RuntimeError, "The material has not been set"):
......@@ -148,24 +148,24 @@ class ISISPowderSampleDetailsTest(unittest.TestCase):
material_obj_one_char = sample_details._Material(chemical_formula=chemical_formula_one_char_element)
self.assertIsNotNone(material_obj_one_char)
self.assertEqual(material_obj_one_char._chemical_formula, chemical_formula_one_char_element)
self.assertIsNone(material_obj_one_char._numeric_density)
self.assertEqual(material_obj_one_char.chemical_formula, chemical_formula_one_char_element)
self.assertIsNone(material_obj_one_char.numeric_density)
# Also check that the absorption and scattering X sections have not been set
self.assertIsNone(material_obj_one_char._absorption_cross_section)
self.assertIsNone(material_obj_one_char._scattering_cross_section)
self.assertIsNone(material_obj_one_char.absorption_cross_section)
self.assertIsNone(material_obj_one_char.scattering_cross_section)
self.assertFalse(material_obj_one_char._is_material_props_set)
# Check if it accepts two character elements without numeric density
material_obj_two_char = sample_details._Material(chemical_formula=chemical_formula_two_char_element)
self.assertIsNotNone(material_obj_two_char)
self.assertEqual(material_obj_two_char._chemical_formula, chemical_formula_two_char_element)
self.assertIsNone(material_obj_two_char._numeric_density)
self.assertEqual(material_obj_two_char.chemical_formula, chemical_formula_two_char_element)
self.assertIsNone(material_obj_two_char.numeric_density)
# Check it stores numeric density if passed
material_obj_numeric_density = sample_details._Material(chemical_formula=chemical_formula_two_char_element,
numeric_density=numeric_density_sample)
self.assertEqual(material_obj_numeric_density._numeric_density, numeric_density_sample)
self.assertEqual(material_obj_numeric_density.numeric_density, numeric_density_sample)
# Check that it raises an error if we have a non-elemental formula without numeric density
with assertRaisesRegex(self, ValueError, "A numeric density formula must be set on a chemical formula"):
......@@ -174,8 +174,8 @@ class ISISPowderSampleDetailsTest(unittest.TestCase):
# Check it constructs if it is given the numeric density too
material_obj_num_complex_formula = sample_details._Material(chemical_formula=chemical_formula_complex,
numeric_density=numeric_density_sample)
self.assertEqual(material_obj_num_complex_formula._chemical_formula, chemical_formula_complex)
self.assertEqual(material_obj_num_complex_formula._numeric_density, numeric_density_sample)
self.assertEqual(material_obj_num_complex_formula.chemical_formula, chemical_formula_complex)
self.assertEqual(material_obj_num_complex_formula.numeric_density, numeric_density_sample)
def test_material_set_properties(self):
bad_absorb = '-1'
......@@ -196,20 +196,20 @@ class ISISPowderSampleDetailsTest(unittest.TestCase):
material_obj.set_material_properties(abs_cross_sect=good_absorb, scattering_cross_sect=bad_scattering)
# Check nothing has been set yet
self.assertIsNone(material_obj._absorption_cross_section)
self.assertIsNone(material_obj._scattering_cross_section)
self.assertIsNone(material_obj.absorption_cross_section)
self.assertIsNone(material_obj.scattering_cross_section)
# Set the object this time
material_obj.set_material_properties(abs_cross_sect=good_absorb, scattering_cross_sect=good_scattering)
self.assertTrue(material_obj._is_material_props_set)
self.assertEqual(material_obj._absorption_cross_section, float(good_absorb))
self.assertEqual(material_obj._scattering_cross_section, float(good_scattering))
self.assertEqual(material_obj.absorption_cross_section, float(good_absorb))
self.assertEqual(material_obj.scattering_cross_section, float(good_scattering))
# Check we cannot set it twice and fields do not change
with assertRaisesRegex(self, RuntimeError, "The material properties have already been set"):
material_obj.set_material_properties(abs_cross_sect=999, scattering_cross_sect=999)
self.assertEqual(material_obj._absorption_cross_section, float(good_absorb))
self.assertEqual(material_obj._scattering_cross_section, float(good_scattering))
self.assertEqual(material_obj.absorption_cross_section, float(good_absorb))
self.assertEqual(material_obj.scattering_cross_section, float(good_scattering))
def test_print_sample_details(self):
expected_height = 1
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment