Skip to content

Commit

Permalink
enforce requirement to specify either density or packing fraction
Browse files Browse the repository at this point in the history
  • Loading branch information
yucongalicechen committed Dec 28, 2024
1 parent a108205 commit 0bdbbb4
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 47 deletions.
31 changes: 24 additions & 7 deletions src/diffpy/utils/tools.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import importlib.metadata
import json
import warnings
from copy import copy
from pathlib import Path

Expand Down Expand Up @@ -210,29 +211,45 @@ def get_package_info(package_names, metadata=None):
return metadata


def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1):
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 can provide a measured density or an estimated packing fraction.
Specifying the density is recommended, though not required for some pure or standard materials.
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 or the name of the material.
energy : float
The energy in keV.
density : float, optional, Default is None
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 gr/cm^3.
packing_fraction : float, optional, Default is 1
packing_fraction : float, optional, Default is None
The fraction of sample in the capillary (between 0 and 1).
Returns
-------
mu : float
The attenuation coefficient mu in mm^{-1}.
"""
mu = material_mu(sample_composition, energy * 1000, density=density, kind="total") * packing_fraction / 10
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 sample_mass_density is not None:
mu = material_mu(sample_composition, energy * 1000, density=sample_mass_density, kind="total") / 10
else:
warnings.warn(
"Warning: Density is set to None if a packing fraction is specified, "
"which may cause errors for some materials. "
"We recommend specifying sample mass density for now. "
"Auto-density calculation is coming soon."
)
mu = material_mu(sample_composition, energy * 1000, density=None, kind="total") * packing_fraction / 10
return mu
82 changes: 42 additions & 40 deletions tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,59 +171,61 @@ def test_get_package_info(monkeypatch, inputs, expected):
"inputs, expected_mu",
[
# Test whether the function returns the correct mu
( # C1: No density or packing fraction (only for known material), expect to get mu from database
( # C1: Composition, energy, and mass density provided, expect to get mu based on mass density
# 1. Fully dense mass density
{"sample_composition": "quartz", "energy": 10, "sample_mass_density": 2.65},
5.0368,
),
( # 2. Measured mass density
{
"sample_composition": "H2O",
"energy": 10,
"sample_composition": "ZrO2",
"energy": 17.445,
"sample_mass_density": 1.009,
},
0.5330,
1.2522,
),
( # C2: Packing fraction (=0.5) provided only (only for known material)
( # C2: Composition, energy, and packing fraction provided, expect to get mu based on packing fraction
# Reuse pattern from C1.1 here
{
"sample_composition": "H2O",
"sample_composition": "quartz",
"energy": 10,
"packing_fraction": 0.5,
},
0.2665,
2.5184,
),
( # C3: Density provided only, expect to compute mu based on it
# 1. Known material
],
)
def test_compute_mu_using_xraydb(inputs, expected_mu):
actual_mu = compute_mu_using_xraydb(**inputs)
assert actual_mu == pytest.approx(expected_mu, rel=1e-6, abs=1e-4)


@pytest.mark.parametrize(
"inputs",
[
# Test when the function raises ValueError
# C1: Both mass density and packing fraction are provided
(
{
"sample_composition": "H2O",
"sample_composition": "quartz",
"energy": 10,
"density": 0.987,
},
0.5330,
"sample_mass_density": 2.65,
"packing_fraction": 1,
}
),
( # 2. Unknown material
{
"sample_composition": "ZrO2",
"energy": 17,
"density": 1.009,
},
1.252,
),
( # C4: Both density and packing fraction are provided, expect to compute mu based on both
# 1. Known material
# C2: None of mass density or packing fraction are provided
(
{
"sample_composition": "H2O",
"sample_composition": "quartz",
"energy": 10,
"density": 0.997,
"packing_fraction": 0.5,
},
0.2665,
),
( # 2. Unknown material
{
"sample_composition": "ZrO2",
"energy": 17,
"density": 1.009,
"packing_fraction": 0.5,
},
0.626,
}
),
],
)
def test_compute_mu_using_xraydb(inputs, expected_mu):
actual_mu = compute_mu_using_xraydb(**inputs)
assert actual_mu == pytest.approx(expected_mu, rel=0.01, abs=0.1)
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)

0 comments on commit 0bdbbb4

Please sign in to comment.