-
Notifications
You must be signed in to change notification settings - Fork 5
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
"Added enrich function to material-db-material_db_tools" #36
base: main
Are you sure you want to change the base?
"Added enrich function to material-db-material_db_tools" #36
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @FusionSandwich - a few design ideas to discuss
""" | ||
# Convert all keys to PyNE nuclide IDs | ||
material_composition = { | ||
nucname.id(k): Decimal(str(v)) for k, v in material_composition.items() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decimal
is new to me. I think I see some of the benefits, but it probably makes sense to introduce it with a complete overhaul - and PyNE's just going to convert things back to floats.
You also change this back to a float
below anyay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converting material_composition and isotope_enrichments allows the enrichment function to take either letters or numbers for the isotopes and elements as input. With how Pyne likes to switch between letter and number names for materials, it makes the function a lot more flexible. In regards to returning it as a float, I had left it like that for the pytests. But I'm thinking of rewriting them to all work with decimal and/or using the pytest.approx. That should remove the issues of pytests failing from testing two floats together.
Enrich a specific element in a material composition by replacing it with its isotopes. | ||
|
||
Args: | ||
material_composition (Dict[Union[int, str], float]): The original material composition. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this method only work for mass fractions? Or maybe it works for both as long as the fraction in material_composition
are the same basis as the fractions in isotope_enrichments
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right that they both need to be on the same basis for the current version of the enrich function. With the logic currently in the make_mat_from_atom you'd have to make your function that converts the mass fraction to an atomic fraction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be good to add this to the docstring - that if the material composition is defined as atom fractions, the enrichment must be as well, and vice versa.
if element_to_enrich_id in material_composition: | ||
fract = material_composition.pop(element_to_enrich_id) | ||
for nuclide, enrich_frac in isotope_enrichments.items(): | ||
material_composition[nuclide] = enrich_frac * fract |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the lingering concerns I have is about assumptions regarding elemental mass fractions when we change enrichment. We should discuss. I think it may depend on how materials are manufactured and/or specified, and may be outside the scope of our work, but we should probably be clear about our assumptions.
Here is an example of what I mean: I could imagine a material that is specified as being 10% Li by mass, where that really represents an atom ratio that is determiend by the manufacturing process. If we change the Li enrichment substantially, the molar mass of Li changes, and the mass ratio for a given atom ratio changes.
In most cases, this will be a small effect, but we need to be sure we understand exactly what the impact is and how to document it for users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the example you provided, a user could specify their original material which is 10% Li by mass, then convert the material to atomic fraction. Then if they enriched either material using make_mat_from_atom that would allow them to easily maintain their atomic ratio while enriching the element. I ran a test based on your example using Li2O3Ti with 10% mass lithium and enriching it to 0 and 100%. The elemental mass fraction of lithium stayed at 10%. The atomic mass fraction of lithium changed by 3.5% from the 0% to 100% lithium 6 enrichment case, or roughly +-5% relative to the overall atomic mass. We could tell them that the enrichment function does not currently take into consideration changes in density that result from enrichment and that the density provided will be the final material density. The user-specified fractions for either case are what the material's final composition is.
isotope_enrichments = { | ||
nucname.id(k): Decimal(str(v)) for k, v in isotope_enrichments.items() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need this step? It seems that it's just converting the key from potentially being a valid string representation to being a canonical nucname ID. The string representation will work below, won't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this provides protection for the cases where the isotope_enrichment is provided with letters such as "Li6"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the way you use this data below, I don't think it matters what form is uses. You don't compare it to anything, but just use it to define a composition. PyNE doesn't care if you mix integers and strings for this.
} | ||
element_to_enrich_id = nucname.id(element_to_enrich) | ||
|
||
if element_to_enrich_id in material_composition: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you have to loop through the materials above to convert the keys to the nucname ID, perhaps this whole method could be inside that loop.
For each material:
convert to ID
if ID is the element_to_enrich_id
replace with enrichment stuff
else
fill in composition
material_composition = { | ||
nucname.id(k): Decimal(str(v)) for k, v in material_composition.items() | ||
} | ||
isotope_enrichments = { | ||
nucname.id(k): Decimal(str(v)) for k, v in isotope_enrichments.items() | ||
} | ||
element_to_enrich_id = nucname.id(element_to_enrich) | ||
|
||
if element_to_enrich_id in material_composition: | ||
fract = material_composition.pop(element_to_enrich_id) | ||
for nuclide, enrich_frac in isotope_enrichments.items(): | ||
material_composition[nuclide] = enrich_frac * fract | ||
|
||
# Convert back to float for consistency with the original function signature | ||
return {k: float(v) for k, v in material_composition.items()} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will do the same thing with less converting things back and forth, fewer loops, and valid with PyNE's interface.
material_composition = { | |
nucname.id(k): Decimal(str(v)) for k, v in material_composition.items() | |
} | |
isotope_enrichments = { | |
nucname.id(k): Decimal(str(v)) for k, v in isotope_enrichments.items() | |
} | |
element_to_enrich_id = nucname.id(element_to_enrich) | |
if element_to_enrich_id in material_composition: | |
fract = material_composition.pop(element_to_enrich_id) | |
for nuclide, enrich_frac in isotope_enrichments.items(): | |
material_composition[nuclide] = enrich_frac * fract | |
# Convert back to float for consistency with the original function signature | |
return {k: float(v) for k, v in material_composition.items()} | |
enriched_composition = {} | |
element_to_enrich_id = nucname.id(element_to_enrich) | |
for element, fract in material_composition.items(): | |
if nucname.id(element) == element_to_enrich_id: | |
for nuclide, enrich_frac in isotope_enrichments.items(): | |
enriched_composition[nuclide] = enrich_frac * fract | |
else: | |
enriched_composition[element] = fract | |
return enriched_composition |
It is different from the original enrich function from pr 19. This is to account for a rounding issue found during a few pytests I ran.