Skip to content
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

compute mu #278

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open

compute mu #278

wants to merge 8 commits into from

Conversation

yucongalicechen
Copy link
Contributor

@yucongalicechen yucongalicechen commented Dec 26, 2024

closes #45
@sbillinge ready for review. I can add docs in the same PR once we're happy with the function

Copy link

codecov bot commented Dec 26, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (b0b6676) to head (a108205).

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #278   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            8         8           
  Lines          405       409    +4     
=========================================
+ Hits           405       409    +4     
Files with missing lines Coverage Δ
tests/test_tools.py 100.00% <100.00%> (ø)

Copy link
Contributor

@sbillinge sbillinge left a comment

Choose a reason for hiding this comment

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

please see inline


Parameters
----------
sample str
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we would normally do tihs as:

    sample: str
       The chemical formula or the name of the material

i.e., colon and sentencecse

the chemical formula or the name of the material
energy float
the energy in eV
density float or None
Copy link
Contributor

Choose a reason for hiding this comment

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

density: float, optional, Default is None

energy float
the energy in eV
density float or None
material density in gr/cm^3
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be "sample mass density" or even better, "mass density of the packed powder/sample" to be clear. the material desnsity is something quite different (mass of atoms in the unit cell divided by unit cell volume)

@@ -131,3 +133,25 @@ def get_package_info(package_names, metadata=None):
pkg_info.update({package: importlib.metadata.version(package)})
metadata["package_info"] = pkg_info
return metadata


def compute_mu_using_xraydb(sample, energy, density=None):
Copy link
Contributor

Choose a reason for hiding this comment

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

let's change sample to composition or maybe sample_composition.

I think we probably need packing_fraction as an optional arg.

@@ -189,3 +189,9 @@ def test_get_package_info(monkeypatch, inputs, expected):
)
actual_metadata = get_package_info(inputs[0], metadata=inputs[1])
assert actual_metadata == expected


def test_compute_mu_using_xraydb():
Copy link
Contributor

Choose a reason for hiding this comment

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

we need a test for no density passed in. Also for packing fraction passed in and also for both passed in. Think about what behavior you want in each of these cases and write a "case" (look elsewhere in test_tools for how we are handling this now)

@yucongalicechen
Copy link
Contributor Author

@sbillinge ready for another review

@@ -131,3 +133,31 @@ def get_package_info(package_names, metadata=None):
pkg_info.update({package: importlib.metadata.version(package)})
metadata["package_info"] = pkg_info
return metadata


def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

added packing fraction. does this make sense or should we use None instead? I think if we set the default to 1 the code can be more concise

Copy link
Contributor

Choose a reason for hiding this comment

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

I think setting to None is clearer here. The logic from the perspective of the user is that we either have a packing fraction or we don't. If it is None, you can set it to 1 in the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, didn't we discuss to change the name to sample_mass_density?

({"sample_composition": "H2O", "energy": 10000, "density": 0.997, "packing_fraction": 1}, 0.5330),
({"sample_composition": "H2O", "energy": 10000, "density": 0.4985, "packing_fraction": 1}, 0.2665),
# C4: user specified a standard density and a packing fraction
({"sample_composition": "H2O", "energy": 10000, "density": 0.997, "packing_fraction": 0.5}, 0.2665),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

added test cases with description. I didn't include the bad cases where the user does not specify a density and xraydb doesn't know the density (this is taken care by xraydb)

sample, energy, density = "ZrO2", 17445.362740959618, 1.009
actual_mu = compute_mu_using_xraydb(sample, energy, density=density)
assert actual_mu == pytest.approx(1.252, rel=1e-4, abs=0.1)
params_mu = [
Copy link
Contributor

@bobleesj bobleesj Dec 26, 2024

Choose a reason for hiding this comment

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

Could you please follow the guides provided here? https://gitlab.thebillingegroup.com/resources/group-wiki/-/wikis/Group-standards-for-pytest-and-docstrings

add higher level function purpose, use descriptive yet concise variable names, no need to create extra variable of params_mu, no need to add "user specific, etc."

Copy link
Contributor

Choose a reason for hiding this comment

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

@yucongalicechen

@sbillinge if you have also further suggestions to the gitlab doc, please suggest or modify directly

Copy link
Contributor

Choose a reason for hiding this comment

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

gitlab doc looks pretty good to me. Lot's of great examples to work from.

@pytest.mark.parametrize(
"inputs, expected_mu",
[
# Test whether the function returns the correct mu
Copy link
Contributor Author

Choose a reason for hiding this comment

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

reorganized test comments

@yucongalicechen
Copy link
Contributor Author

thanks @bobleesj the link is very helpful and very clear to me. I edited the test comments. let me know if anything is unclear!
@sbillinge now ready for another review

Copy link
Contributor

@sbillinge sbillinge left a comment

Choose a reason for hiding this comment

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

please see inline comments. ATM I am just reviewing tests until we get them sorted. I don't really know what "standard density", and it doesn't make too much sense to me, so could you elaborate on that?

( # C1: No density or packing fraction provided, expect to compute mu based on standard density
{
"sample_composition": "H2O",
"energy": 10000,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should input energy in keV (so this would be 10, not 10000)

"sample_composition": "H2O",
"energy": 10000,
"density": None,
"packing_fraction": 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

According to your case, density and packing fraction should be missing. I think you are passing them in with the default values to mimick this behavior but this is a different test. Just delete them from this input dict to do the correct test.

],
)
def test_compute_mu_using_xraydb(inputs, expected_mu):
actual_mu = compute_mu_using_xraydb(
Copy link
Contributor

Choose a reason for hiding this comment

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

here you put

def test_compute_mu_using_xraydb(inputs, expected_mu):
    actual_mu = compute_mu_using_xraydb(**inputs)

which will unpack the input dict. You may have to handle the required args differently, but you can then have input_args and input_kwargs in your paramatrize and unpack them differently.

"inputs, expected_mu",
[
# Test whether the function returns the correct mu
( # C1: No density or packing fraction provided, expect to compute mu based on standard density
Copy link
Contributor

Choose a reason for hiding this comment

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

do we want to allow this? what is "standard density"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just density I think - density under the standard conditions. So user can specify either a measured density or packing fraction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason I'm not putting a strict "and/or" for density and packing fraction is that xraydb does not know the density to some materials like ZrO2. In this case user needs to put a density and a packing fraction, or they can put their measured density.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, so density is in the database? that is unexpected.

Copy link
Contributor

Choose a reason for hiding this comment

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

how does xraydb compute the density?

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, it has a database of some standard materials. So we will need tests for cases where the material can be found and cases where it can't. How many different materials are in that database? The examples show just a few satandard materials (Kapton, quartz, etc.). Is it an extensive database or just a few x-ray standards?

Copy link
Contributor

Choose a reason for hiding this comment

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

How will you compute it for the cases where it is not in the db?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They have densities for some materials (for example, this returns sample name and density: https://xraypy.github.io/XrayDB/python.html#xraydb.get_materials).
I just reread their docstring and their density refers to the material density, not mass density: https://xraypy.github.io/XrayDB/python.html#xraydb.material_mu.

Copy link
Contributor

@sbillinge sbillinge left a comment

Choose a reason for hiding this comment

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

Please see inline comments.

Also, this is conflicted

"inputs, expected_mu",
[
# Test whether the function returns the correct mu
( # C1: No density or packing fraction provided, expect to compute mu based on standard density
Copy link
Contributor

Choose a reason for hiding this comment

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

I see, it has a database of some standard materials. So we will need tests for cases where the material can be found and cases where it can't. How many different materials are in that database? The examples show just a few satandard materials (Kapton, quartz, etc.). Is it an extensive database or just a few x-ray standards?

"inputs, expected_mu",
[
# Test whether the function returns the correct mu
( # C1: No density or packing fraction provided, expect to compute mu based on standard density
Copy link
Contributor

Choose a reason for hiding this comment

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

How will you compute it for the cases where it is not in the db?

@yucongalicechen
Copy link
Contributor Author

yucongalicechen commented Dec 27, 2024

Please see inline comments.

Also, this is conflicted

So I think it only has density of pure elements and common compounds. I used get_materials to print out the list - it's pretty short.

I read through material_mu here: https://github.com/xraypy/XrayDB/blob/master/python/xraydb/materials.py. It uses a weighted sum approach for unknown materials. Here's the related code:

mass_tot, mu = 0.0, 0.0
    for elem, frac in chemparse(formula).items():
        mass  = frac * atomic_mass(elem)
        mu   += mass * mu_elam(elem, energy, kind=kind) # mu_elam is the mu for the given element
        mass_tot += mass
    return density*mu/mass_tot

It looks like it's using mass density so we're good. I'll solve the conflict and add more tests for unknown materials.

sample_composition : str
The chemical formula or the name of the material.
energy : float
The energy in keV.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

change unit to keV

Copy link
Contributor

Choose a reason for hiding this comment

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

"The energy of the incident x-rays in keV"


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.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

edit docstring

Copy link
Contributor

Choose a reason for hiding this comment

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

I think using xrayDB functionality here is not useful. The number of materials is so small, so there is no point to offer this to users as an option. It will just confuse them.

We could offer other approaches:

  1. given a composition, try and get density from COD or Materials Project. For different polymorphs the densities will be different for the same composition, so we would have to handle that.
  2. as a fallback, use a "best effort" atomic density (most tightly packed materials are around 5x10^22 cm^-3)? This would be "magic" which we don't normally like, but if we use this fallback we could give a warning.

},
0.5330,
),
( # 2. Unknown material
Copy link
Contributor Author

Choose a reason for hiding this comment

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

add unknown material for testing

@yucongalicechen
Copy link
Contributor Author

@sbillinge ready for another review

],
)
def test_compute_mu_using_xraydb(inputs, expected_mu):
actual_mu = compute_mu_using_xraydb(**inputs)
Copy link
Contributor

Choose a reason for hiding this comment

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

this is beautiful. thanks! @yucongalicechen

Copy link
Contributor

@sbillinge sbillinge left a comment

Choose a reason for hiding this comment

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

please see inline comments.

@@ -131,3 +133,31 @@ def get_package_info(package_names, metadata=None):
pkg_info.update({package: importlib.metadata.version(package)})
metadata["package_info"] = pkg_info
return metadata


def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1):
Copy link
Contributor

Choose a reason for hiding this comment

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

I think setting to None is clearer here. The logic from the perspective of the user is that we either have a packing fraction or we don't. If it is None, you can set it to 1 in the code.

@@ -131,3 +133,31 @@ def get_package_info(package_names, metadata=None):
pkg_info.update({package: importlib.metadata.version(package)})
metadata["package_info"] = pkg_info
return metadata


def compute_mu_using_xraydb(sample_composition, energy, density=None, packing_fraction=1):
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, didn't we discuss to change the name to sample_mass_density?


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.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think using xrayDB functionality here is not useful. The number of materials is so small, so there is no point to offer this to users as an option. It will just confuse them.

We could offer other approaches:

  1. given a composition, try and get density from COD or Materials Project. For different polymorphs the densities will be different for the same composition, so we would have to handle that.
  2. as a fallback, use a "best effort" atomic density (most tightly packed materials are around 5x10^22 cm^-3)? This would be "magic" which we don't normally like, but if we use this fallback we could give a warning.

sample_composition : str
The chemical formula or the name of the material.
energy : float
The 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.

"The energy of the incident x-rays in keV"

energy : float
The energy in keV.
density : float, optional, Default is None
The mass density of the packed powder/sample in gr/cm^3.
Copy link
Contributor

Choose a reason for hiding this comment

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

we need more info here about how these will be used, and what happens if someone specifies both.

},
0.5330,
),
( # C2: Packing fraction (=0.5) provided only (only for known material)
Copy link
Contributor

Choose a reason for hiding this comment

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

reuse my pattern from C1 here.

},
0.2665,
),
( # C3: Density provided only, expect to compute mu based on it
Copy link
Contributor

Choose a reason for hiding this comment

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

reuse my pattern above.

},
1.252,
),
( # C4: Both density and packing fraction are provided, expect to compute mu based on both
Copy link
Contributor

Choose a reason for hiding this comment

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

this doesn't make any sense. It will result in two numbers, not one number. We probably want to raise an exception if both are provided.

)
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)
Copy link
Contributor

Choose a reason for hiding this comment

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

this seems a bit too loose?

},
0.626,
),
],
Copy link
Contributor

Choose a reason for hiding this comment

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

we eem to be missing "no density, unknown material". Here expected could either be to raise an exception or to do a "best effort" calculation with a warning. I am not sure which is better. It depends how many unknown materials we have.

@sbillinge
Copy link
Contributor

@yucongalicechen further to my review comments, I recommend not using the densities from the xrayDB at all and just computing using given densities. We can add another function that is get_density_from_cloud() or something that tries to get it from materials project or COD. This would require there to be a packing fraction specified, because the density we got from MP would be the fully dense density, so "material-density" not "sample-density"

@yucongalicechen
Copy link
Contributor Author

@yucongalicechen further to my review comments, I recommend not using the densities from the xrayDB at all and just computing using given densities. We can add another function that is get_density_from_cloud() or something that tries to get it from materials project or COD. This would require there to be a packing fraction specified, because the density we got from MP would be the fully dense density, so "material-density" not "sample-density"

Just to clarify - this means user must enter density or packing fraction right? Something like what this is doing https://11bm.xray.aps.anl.gov/absorb/absorb.php? I can make another issue on getting density.

@bobleesj
Copy link
Contributor

diffpy/diffpy.labpdfproc#146 (comment) - actually I meant to write this comment here in diffpy.utils @yucongalicechen

@sbillinge
Copy link
Contributor

@yucongalicechen further to my review comments, I recommend not using the densities from the xrayDB at all and just computing using given densities. We can add another function that is get_density_from_cloud() or something that tries to get it from materials project or COD. This would require there to be a packing fraction specified, because the density we got from MP would be the fully dense density, so "material-density" not "sample-density"

Just to clarify - this means user must enter density or packing fraction right? Something like what this is doing https://11bm.xray.aps.anl.gov/absorb/absorb.php? I can make another issue on getting density.

Yes, either or and it triggers a different workflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

mu T calculator
3 participants