Skip to content

feat: options for estimating muD theoretically #173

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

Merged
merged 4 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions news/muD-theoretical.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* Functionalities to estimate mu*D theoretically.

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
41 changes: 37 additions & 4 deletions src/diffpy/labpdfproc/labpdfprocapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,11 @@ def _define_arguments():
def _add_mud_selection_group(p, is_gui=False):
"""Current Options:
1. Manually enter muD (`--mud`).
2. Estimate muD from a z-scan file (`-z` or `--z-scan-file`).
2. Estimate from a z-scan file (`-z` or `--z-scan-file`).
3. Estimate theoretically based on sample mass density
(`-td` or `--theoretical-from-density`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check but this is breaking the usual pattern that it should be -l (one letter) or --letters (multiple letters need multiple dashes.

4. Estimate theoretically based on packing fraction
(`-tp` or `--theoretical-from-packing`).
"""
g = p.add_argument_group("Options for setting mu*D value (Required)")
g = g.add_mutually_exclusive_group(required=True)
Expand All @@ -165,10 +169,39 @@ def _add_mud_selection_group(p, is_gui=False):
g.add_argument(
"-z",
"--z-scan-file",
help="Provide the path to the z-scan file to be loaded "
"to determine the mu*D value.",
help=(
"Estimate mu*D experimentally from a z-scan file. "
"Specify the path to the file "
"used to compute the mu*D value."
),
**({"widget": "FileChooser"} if is_gui else {}),
)
g.add_argument(
"-td",
"--theoretical-from-density",
help=(
"Estimate mu*D theoretically using sample mass density. "
"Specify the sample composition (chemical formula), "
"incident x-ray energy in keV, "
"and sample mass density in g/cm^3 "
"in that exact order "
"and separated by commas with no whitespaces "
"(e.g., 'ZrO2,2,1.2')."
),
)
g.add_argument(
"-tp",
"--theoretical-from-packing",
help=(
"Estimate mu*D theoretically using packing fraction. "
"Specify the sample composition (chemical formula), "
"incident x-ray energy in keV, "
"and packing fraction (0 to 1) "
"in that exact order "
"and separated by commas with no whitespaces "
"(e.g., 'ZrO2,2,0.5')."
),
)
return p


Expand All @@ -186,7 +219,7 @@ def get_args(override_cli_inputs=None):
return args


@Gooey(required_cols=1, optional_cols=1, program_name="Labpdfproc GUI")
@Gooey(required_cols=1, optional_cols=2, program_name="labpdfproc GUI")
def gooey_parser():
p = GooeyParser()
p = _add_mud_selection_group(p, is_gui=True)
Expand Down
91 changes: 72 additions & 19 deletions src/diffpy/labpdfproc/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from diffpy.utils.tools import (
_load_config,
check_and_build_global_config,
compute_mu_using_xraydb,
compute_mud,
get_package_info,
get_user_info,
Expand All @@ -31,14 +32,19 @@
}
known_sources = [key for key in WAVELENGTHS.keys()]

# Exclude wavelength from metadata to prevent duplication,
# as the dump function in diffpy.utils writes it explicitly.
# Exclude wavelength to avoid duplication,
# as it's written explicitly by diffpy.utils dump function.
# Exclude "theoretical_from_density" and "theoretical_from_packing"
# as they are only used for theoretical mu*D estimation
# and will be written into separate arguments for clarity.
METADATA_KEYS_TO_EXCLUDE = [
"output_correction",
"force_overwrite",
"input",
"input_paths",
"wavelength",
"theoretical_from_density",
"theoretical_from_packing",
]


Expand Down Expand Up @@ -298,19 +304,8 @@ def set_xtype(args):
return args


def _estimate_mud_from_zscan(args):
"""Compute mu*D based on the given z-scan file.

Parameters
----------
args : argparse.Namespace
The arguments from the parser.

Returns
-------
args : argparse.Namespace
The updated arguments with mu*D.
"""
def _set_mud_from_zscan(args):
"""Experimental estimation of mu*D from a z-scan file."""
filepath = Path(args.z_scan_file).resolve()
if not filepath.is_file():
raise FileNotFoundError(
Expand All @@ -322,10 +317,64 @@ def _estimate_mud_from_zscan(args):
return args


def _parse_theoretical_input(input_str):
"""Helper function to parse and validate the input string."""
parts = input_str.split(",")
if len(parts) != 3:
raise ValueError(
f"Invalid mu*D input '{input_str}'. "
"Expected format is 'sample composition, energy, "
"sample mass density or packing fraction' "
"with no whitespaces (e.g., 'ZrO2,2,0.8').",
)
sample_composition = parts[0]
energy = float(parts[1])
mass_density_or_packing_fraction = float(parts[2])
return sample_composition, energy, mass_density_or_packing_fraction


def _set_theoretical_mud_from_density(args):
"""Theoretical estimation of mu*D from
sample composition, energy, and sample mass density."""
sample_composition, energy, sample_mass_density = _parse_theoretical_input(
args.theoretical_from_density
)
args.sample_composition = sample_composition
args.energy = energy
args.sample_mass_density = sample_mass_density
args.mud = compute_mu_using_xraydb(
args.sample_composition,
args.energy,
sample_mass_density=args.sample_mass_density,
)
return args


def _set_theoretical_mud_from_packing(args):
"""Theoretical estimation of mu*D from
sample composition, energy, and packing fraction."""
sample_composition, energy, packing_fraction = _parse_theoretical_input(
args.theoretical_from_packing
)
args.sample_composition = sample_composition
args.energy = energy
args.packing_fraction = packing_fraction
args.mud = compute_mu_using_xraydb(
args.sample_composition,
args.energy,
packing_fraction=args.packing_fraction,
)
return args


def set_mud(args):
"""Compute and set mu*D based on different options.
Current options include manually entering a value,
or estimating from a z-scan file.
"""Compute and set mu*D based on the selected method.

Options include:
1. Manually entering a value.
2. Estimating from a z-scan file.
3. Estimating theoretically based on sample mass density.
4. Estimating theoretically based on packing fraction.

Parameters
----------
Expand All @@ -338,7 +387,11 @@ def set_mud(args):
The updated arguments with mu*D.
"""
if args.z_scan_file:
return _estimate_mud_from_zscan(args)
return _set_mud_from_zscan(args)
elif args.theoretical_from_density:
return _set_theoretical_mud_from_density(args)
elif args.theoretical_from_packing:
return _set_theoretical_mud_from_packing(args)
return args


Expand Down
56 changes: 55 additions & 1 deletion tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,12 @@ def test_set_xtype_bad():
(["--mud", "2.5"], 2.5),
# C2: user provides a z-scan file, expect to estimate through the file
(["--z-scan-file", "test_dir/testfile.xy"], 3),
# C3: user specifies sample composition, energy,
# and sample mass density, expect to estimate theoretically
(["--theoretical-from-density", "ZrO2,17.45,1.2"], 1.49),
# C4: user specifies sample composition, energy, and packing fraction
# expect to estimate theoretically
# (["--theoretical-from-packing", "ZrO2,17.45,0.3"], 1.49),
],
)
def test_set_mud(user_filesystem, inputs, expected_mud):
Expand All @@ -483,6 +489,54 @@ def test_set_mud(user_filesystem, inputs, expected_mud):
"Cannot find invalid file. Please specify a valid file path.",
],
),
# C2.1: user provides fewer than three input values
# expect ValueError with a message indicating the correct format
(
["--theoretical-from-density", "ZrO2,0.5"],
[
ValueError,
"Invalid mu*D input 'ZrO2,0.5'. "
"Expected format is 'sample composition, energy, "
"sample mass density or packing fraction' "
"with no whitespaces (e.g., 'ZrO2,2,0.8').",
],
),
# C2.1: user provides fewer than three input values
# expect ValueError with a message indicating the correct format
(
["--theoretical-from-packing", "ZrO2,0.5"],
[
ValueError,
"Invalid mu*D input 'ZrO2,0.5'. "
"Expected format is 'sample composition, energy, "
"sample mass density or packing fraction' "
"with no whitespaces (e.g., 'ZrO2,2,0.8').",
],
),
# C3.1: user provides more than 3 input values
# expect ValueError with a message indicating the correct format
(
["--theoretical-from-density", "ZrO2,1.5,1.5,0.5"],
[
ValueError,
"Invalid mu*D input 'ZrO2,1.5,1.5,0.5'. "
"Expected format is 'sample composition, energy, "
"sample mass density or packing fraction' "
"with no whitespaces (e.g., 'ZrO2,2,0.8').",
],
),
# C3.2: user provides more than 3 input values
# expect ValueError with a message indicating the correct format
(
["--theoretical-from-packing", "ZrO2,1.5,1.5,0.5"],
[
ValueError,
"Invalid mu*D input 'ZrO2,1.5,1.5,0.5'. "
"Expected format is 'sample composition, energy, "
"sample mass density or packing fraction' "
"with no whitespaces (e.g., 'ZrO2,2,0.8').",
],
),
],
)
def test_set_mud_bad(user_filesystem, inputs, expected):
Expand All @@ -491,7 +545,7 @@ def test_set_mud_bad(user_filesystem, inputs, expected):
os.chdir(cwd)
cli_inputs = ["data.xy"] + inputs
actual_args = get_args(cli_inputs)
with pytest.raises(expected_error, match=expected_error_msg):
with pytest.raises(expected_error, match=re.escape(expected_error_msg)):
actual_args = set_mud(actual_args)


Expand Down
Loading