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 all commits
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>
45 changes: 40 additions & 5 deletions src/diffpy/labpdfproc/labpdfprocapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
from diffpy.utils.diffraction_objects import XQUANTITIES, DiffractionObject
from diffpy.utils.parsers.loaddata import loadData

theoretical_mud_hmsg_suffix = (
"in that exact order, "
"separated by commas (e.g., ZrO2,17.45,0.5). "
"If you add whitespaces, "
"enclose it in quotes (e.g., 'ZrO2, 17.45, 0.5'). "
)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

common part for help messages



def _define_arguments():
args = [
Expand All @@ -21,12 +28,14 @@ def _define_arguments():
"The filename(s) or folder(s) of the datafile(s) to load. "
"Required.\n"
"Supply a space-separated list of files or directories. "
"Avoid spaces in filenames when possible; "
"if present, enclose the name in quotes. "
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

edit input help message

"Long lists can be supplied, one per line, "
"in a file with name file_list.txt. "
"If one or more directory is provided, all valid "
"data-files in that directory will be processed. "
"Examples of valid inputs are 'file.xy', 'data/file.xy', "
"'file.xy, data/file.xy', "
"'file.xy data/file.xy', "
"'.' (load everything in the current directory), "
"'data' (load everything in the folder ./data), "
"'data/file_list.txt' (load the list of files "
Expand Down Expand Up @@ -152,7 +161,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
(`-d` or `--theoretical-from-density`).
4. Estimate theoretically based on packing fraction
(`-p` 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 +178,32 @@ 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(
"-d",
"--theoretical-from-density",
help=(
"Estimate mu*D theoretically using sample mass density. "
"Specify the chemical formula, incident x-ray energy (in keV), "
"and sample mass density (in g/cm^3), "
+ theoretical_mud_hmsg_suffix
),
)
g.add_argument(
"-p",
"--theoretical-from-packing",
help=(
"Estimate mu*D theoretically using packing fraction. "
"Specify the chemical formula, incident x-ray energy (in keV), "
Copy link
Contributor

Choose a reason for hiding this comment

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

since this seems to be used 2x, define it in a variable and call the variable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@sbillinge ready for another review. I edited the help messages according to the comments above. When there's whitespace but no quotes the CLI will raise an error saying that it doesn't recognize the command

"and packing fraction (0 to 1), " + theoretical_mud_hmsg_suffix
),
)
return p


Expand All @@ -186,7 +221,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 = [part.strip() for part in 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' "
"(e.g., 'ZrO2,17.45,0.5').",
)
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
63 changes: 62 additions & 1 deletion tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,15 @@ 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,
# both with and without whitespaces, expect to estimate theoretically
(["--theoretical-from-density", "ZrO2,17.45,1.2"], 1.49),
(["--theoretical-from-density", "ZrO2, 17.45, 1.2"], 1.49),
# C4: user specifies sample composition, energy, and packing fraction
# both with and without whitespaces, expect to estimate theoretically
# (["--theoretical-from-packing", "ZrO2,17.45,0.3"], 1.49),
# (["--theoretical-from-packing", "ZrO2, 17.45, 0.3"], 1.49),
],
)
def test_set_mud(user_filesystem, inputs, expected_mud):
Expand All @@ -483,6 +492,58 @@ def test_set_mud(user_filesystem, inputs, expected_mud):
"Cannot find invalid file. Please specify a valid file path.",
],
),
# C2.1: (sample mass density option)
# 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' "
"(e.g., 'ZrO2,17.45,0.5').",
],
),
# C2.2: (packing fraction option)
# 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' "
"(e.g., 'ZrO2,17.45,0.5').",
],
),
# C3.1: (sample mass density option)
# user provides more than 3 input values
# expect ValueError with a message indicating the correct format
(
["--theoretical-from-density", "ZrO2,17.45,1.5,0.5"],
[
ValueError,
"Invalid mu*D input 'ZrO2,17.45,1.5,0.5'. "
"Expected format is 'sample composition, energy, "
"sample mass density or packing fraction' "
"(e.g., 'ZrO2,17.45,0.5').",
],
),
# C3.2: (packing fraction option)
# user provides more than 3 input values
# expect ValueError with a message indicating the correct format
(
["--theoretical-from-packing", "ZrO2,17.45,1.5,0.5"],
[
ValueError,
"Invalid mu*D input 'ZrO2,17.45,1.5,0.5'. "
"Expected format is 'sample composition, energy, "
"sample mass density or packing fraction' "
"(e.g., 'ZrO2,17.45,0.5').",
],
),
],
)
def test_set_mud_bad(user_filesystem, inputs, expected):
Expand All @@ -491,7 +552,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