diff --git a/news/mu.rst b/news/mu.rst new file mode 100644 index 00000000..bbf9cb1a --- /dev/null +++ b/news/mu.rst @@ -0,0 +1,23 @@ +**Added:** + +* function to compute x-ray attenuation coefficient (mu) using XrayDB + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/requirements/conda.txt b/requirements/conda.txt index 6bad1038..9de10da8 100644 --- a/requirements/conda.txt +++ b/requirements/conda.txt @@ -1,2 +1,3 @@ numpy +xraydb scipy diff --git a/requirements/pip.txt b/requirements/pip.txt index 6bad1038..9de10da8 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1,2 +1,3 @@ numpy +xraydb scipy diff --git a/src/diffpy/utils/tools.py b/src/diffpy/utils/tools.py index 040e62ea..036cc696 100644 --- a/src/diffpy/utils/tools.py +++ b/src/diffpy/utils/tools.py @@ -6,6 +6,7 @@ import numpy as np from scipy.optimize import dual_annealing from scipy.signal import convolve +from xraydb import material_mu from diffpy.utils.parsers.loaddata import loadData @@ -194,6 +195,57 @@ def get_package_info(package_names, metadata=None): return metadata +def get_density_from_cloud(sample_composition, mp_token=""): + """Function to get material density from the MP or COD database. + + It is not implemented yet. + """ + raise NotImplementedError( + "So sorry, density computation from composition is not implemented right now. " + "We hope to have this implemented in the next release. " + "Please rerun specifying a sample mass density." + ) + + +def compute_mu_using_xraydb(sample_composition, energy, sample_mass_density=None, packing_fraction=None): + """Compute the attenuation coefficient (mu) using the XrayDB database. + + Computes mu based on the sample composition and energy. + User should provide a sample mass density or a packing fraction. + If neither density nor packing fraction is specified, or if both are specified, a ValueError will be raised. + Reference: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu. + + Parameters + ---------- + sample_composition : str + The chemical formula of the material. + energy : float + The energy of the incident x-rays in keV. + sample_mass_density : float, optional, Default is None + The mass density of the packed powder/sample in g/cm*3. + packing_fraction : float, optional, Default is None + The fraction of sample in the capillary (between 0 and 1). + Specify either sample_mass_density or packing_fraction but not both. + + Returns + ------- + mu : float + The attenuation coefficient mu in mm^{-1}. + """ + if (sample_mass_density is None and packing_fraction is None) or ( + sample_mass_density is not None and packing_fraction is not None + ): + raise ValueError( + "You must specify either sample_mass_density or packing_fraction, but not both. " + "Please rerun specifying only one." + ) + if packing_fraction is not None: + sample_mass_density = get_density_from_cloud(sample_composition) * packing_fraction + energy_eV = energy * 1000 + mu = material_mu(sample_composition, energy_eV, density=sample_mass_density, kind="total") / 10 + return mu + + def _top_hat(z, half_slit_width): """Create a top-hat function, return 1.0 for values within the specified slit width and 0 otherwise.""" diff --git a/tests/test_tools.py b/tests/test_tools.py index fa8b3c32..4843fede 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -9,6 +9,7 @@ from diffpy.utils.tools import ( _extend_z_and_convolve, check_and_build_global_config, + compute_mu_using_xraydb, compute_mud, get_package_info, get_user_info, @@ -169,6 +170,35 @@ def test_get_package_info(monkeypatch, inputs, expected): assert actual_metadata == expected +@pytest.mark.parametrize( + "inputs", + [ + # Test when the function has invalid inputs + ( # C1: Both mass density and packing fraction are provided, expect ValueError exception + { + "sample_composition": "SiO2", + "energy": 10, + "sample_mass_density": 2.65, + "packing_fraction": 1, + } + ), + ( # C2: None of mass density or packing fraction are provided, expect ValueError exception + { + "sample_composition": "SiO2", + "energy": 10, + } + ), + ], +) +def test_compute_mu_using_xraydb_bad(inputs): + with pytest.raises( + ValueError, + match="You must specify either sample_mass_density or packing_fraction, but not both. " + "Please rerun specifying only one.", + ): + compute_mu_using_xraydb(**inputs) + + def test_compute_mud(tmp_path): diameter, slit_width, z0, I0, mud, slope = 1, 0.1, 0, 1e5, 3, 0 z_data = np.linspace(-1, 1, 50)