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

Ignoring nuclides with no xs data when writing material xml files #2800

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
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
19 changes: 1 addition & 18 deletions openmc/deplete/coupled_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ def _get_nuclides_with_data(cross_sections):

return nuclides


class CoupledOperator(OpenMCOperator):
"""Transport-coupled transport operator.

Expand Down Expand Up @@ -300,22 +299,6 @@ def _load_previous_results(self):
new_res = res_obj.distribute(self.local_mats, mat_indexes)
self.prev_res.append(new_res)

def _get_nuclides_with_data(self, cross_sections):
"""Loads cross_sections.xml file to find nuclides with neutron data

Parameters
----------
cross_sections : str
Path to cross_sections.xml file

Returns
-------
nuclides : set of str
Set of nuclide names that have cross secton data

"""
return _get_nuclides_with_data(cross_sections)

def _get_helper_classes(self, helper_kwargs):
"""Create the ``_rate_helper``, ``_normalization_helper``, and
``_yield_helper`` objects.
Expand Down Expand Up @@ -404,7 +387,7 @@ def _generate_materials_xml(self):
for mat in self.materials:
mat._nuclides.sort(key=lambda x: nuclides.index(x[0]))

self.materials.export_to_xml(nuclides_to_ignore=self._decay_nucs)
self.materials.export_to_xml(ignore_phantom_nuclides=True)

def __call__(self, vec, source_rate):
"""Runs a simulation.
Expand Down
4 changes: 0 additions & 4 deletions openmc/deplete/independent_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,6 @@ def _load_previous_results(self):
self.prev_res.append(new_res)


def _get_nuclides_with_data(self, cross_sections: List[MicroXS]) -> Set[str]:
"""Finds nuclides with cross section data"""
return set(cross_sections[0].nuclides)

class _IndependentRateHelper(ReactionRateHelper):
"""Class for generating one-group reaction rates with flux and
one-group cross sections.
Expand Down
4 changes: 2 additions & 2 deletions openmc/deplete/microxs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@

from openmc.checkvalue import check_type, check_value, check_iterable_type, PathLike
from openmc.exceptions import DataError
from openmc import StatePoint
from openmc.mgxs import GROUP_STRUCTURES
from openmc.data import REACTION_MT
import openmc
from .abc import change_directory
from .chain import Chain, REACTIONS
from .coupled_operator import _find_cross_sections
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
from .coupled_operator import _find_cross_sections

from .coupled_operator import _find_cross_sections, _get_nuclides_with_data
import openmc.lib

Expand Down Expand Up @@ -91,7 +91,7 @@ def get_microxs_and_flux(
reactions = chain.reactions
if not nuclides:
cross_sections = _find_cross_sections(model)
nuclides_with_data = _get_nuclides_with_data(cross_sections)
nuclides_with_data = get_nuclides_with_data(cross_sections)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
nuclides_with_data = get_nuclides_with_data(cross_sections)
nuclides_with_data = _get_nuclides_with_data(cross_sections)

nuclides = [nuc.name for nuc in chain.nuclides
if nuc.name in nuclides_with_data]

Expand Down
28 changes: 4 additions & 24 deletions openmc/deplete/openmc_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from openmc.checkvalue import check_value
from openmc.exceptions import DataError
from openmc.mpi import comm
from openmc import get_nuclides_with_data
Copy link
Member

@shimwell shimwell Jan 2, 2024

Choose a reason for hiding this comment

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

I think get_nuclides_with_data is now called _get_nuclides_with_data. Also perhaps this does not need importing as I see an abstract method in the openmc_operator file

from .abc import TransportOperator, OperatorResult
from .atom_number import AtomNumber
from .reaction_rates import ReactionRates
Expand Down Expand Up @@ -148,16 +149,12 @@ def __init__(
# for which there is an entry in the micro_xs parameter
openmc.reset_auto_ids()

self.nuclides_with_data = self._get_nuclides_with_data(
self.cross_sections)
self.nuclides_with_data = get_nuclides_with_data(self.cross_sections)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
self.nuclides_with_data = get_nuclides_with_data(self.cross_sections)
self.nuclides_with_data = _get_nuclides_with_data(self.cross_sections)


# Select nuclides with data that are also in the chain
self._burnable_nucs = [nuc.name for nuc in self.chain.nuclides
if nuc.name in self.nuclides_with_data]

# Select nuclides without data that are also in the chain
self._decay_nucs = [nuc.name for nuc in self.chain.nuclides
if nuc.name not in self.nuclides_with_data]

self.burnable_mats, volumes, all_nuclides = self._get_burnable_mats()
self.local_mats = _distribute(self.burnable_mats)
Expand Down Expand Up @@ -207,10 +204,10 @@ def _get_burnable_mats(self) -> Tuple[List[str], Dict[str, float], List[str]]:
# Iterate once through the geometry to get dictionaries
for mat in self.materials:
for nuclide in mat.get_nuclides():
if nuclide in self.nuclides_with_data or self._decay_nucs:
if nuclide in self.nuclides_with_data or self.chain.nuclides:
model_nuclides.add(nuclide)
else:
msg = (f"Nuclilde {nuclide} in material {mat.id} is not "
msg = (f"Nuclide {nuclide} in material {mat.id} is not "
"present in the depletion chain and has no cross "
"section data.")
raise warn(msg)
Expand Down Expand Up @@ -247,23 +244,6 @@ def _load_previous_results(self):
"""Load results from a previous depletion simulation"""
pass

@abstractmethod
def _get_nuclides_with_data(self, cross_sections):
"""Find nuclides with cross section data

Parameters
----------
cross_sections : str or pandas.DataFrame
Path to continuous energy cross section library, or object
containing one-group cross-sections.

Returns
-------
nuclides : set of str
Set of nuclide names that have cross secton data

"""

def _extract_number(self, local_mats, volume, all_nuclides, prev_res=None):
"""Construct AtomNumber using geometry

Expand Down
71 changes: 66 additions & 5 deletions openmc/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import openmc
import openmc.data
import openmc.checkvalue as cv
from openmc.data import DataLibrary
from ._xml import clean_indentation, reorder_attributes
from .mixin import IDManagerMixin
from openmc.checkvalue import PathLike
Expand Down Expand Up @@ -1314,7 +1315,7 @@ def _get_macroscopic_xml(self, macroscopic: str) -> ET.Element:

def _get_nuclides_xml(
self, nuclides: typing.Iterable[NuclideTuple],
nuclides_to_ignore: Optional[typing.Iterable[str]] = None)-> List[ET.Element]:
nuclides_to_ignore: Optional[typing.Iterable[str]] = None) -> List[ET.Element]:
xml_elements = []

# Remove any nuclides to ignore from the XML export
Expand All @@ -1340,6 +1341,16 @@ def to_xml_element(
XML element containing material data

"""
if nuclides_to_ignore is not None:
nuclides = [nuc.name for nuc in self.nuclides]
phantoms = [nuc for nuc in nuclides_to_ignore if nuc in nuclides]
phantom_masses = sum([self.get_mass_density(nuc) for nuc in phantoms])
mass_loss_ratio = phantom_masses / self.get_mass_density()
msg = f"By ignoring {phantoms} in material {self.id} for transport calculation, " \
f"{mass_loss_ratio:.2%} of the material total mass density in lost."
openmc.warnings.warn(msg)
else:
phantoms = None

# Create Material XML element
element = ET.Element("material")
Expand Down Expand Up @@ -1379,7 +1390,7 @@ def to_xml_element(
if self._macroscopic is None:
# Create nuclide XML subelements
subelements = self._get_nuclides_xml(self._nuclides,
nuclides_to_ignore=nuclides_to_ignore)
nuclides_to_ignore=phantoms)
for subelement in subelements:
element.append(subelement)
else:
Expand Down Expand Up @@ -1636,7 +1647,7 @@ def make_isotropic_in_lab(self):
material.make_isotropic_in_lab()

def _write_xml(self, file, header=True, level=0, spaces_per_level=2,
trailing_indent=True, nuclides_to_ignore=None):
trailing_indent=True, nuclides_to_ignore=None, ignore_phantom_nuclides=False):
"""Writes XML content of the materials to an open file handle.

Parameters
Expand All @@ -1653,8 +1664,23 @@ def _write_xml(self, file, header=True, level=0, spaces_per_level=2,
Whether or not to write a trailing indentation for the materials element
nuclides_to_ignore : list of str
Nuclides to ignore when exporting to XML.
ignore_phantom_nuclides: bool
If True, nuclides present in the materials but with no available cross-sections
will be ignored when writing to xml.

"""
if ignore_phantom_nuclides:
xml_path = openmc.config.get("cross_sections", None)
if xml_path is None:
raise ValueError("A cross_sections.xml file must be set to identify phantom nuclides.")
available = get_nuclides_with_data(xml_path)
candidates = list(set([nuc.name for m in self for nuc in m.nuclides]))
phantoms = [nuc for nuc in candidates if nuc not in available]
if nuclides_to_ignore is None:
nuclides_to_ignore = phantoms
else:
nuclides_to_ignore += phantoms

indentation = level*spaces_per_level*' '
# Write the header and the opening tag for the root element.
if header:
Expand Down Expand Up @@ -1689,7 +1715,8 @@ def _write_xml(self, file, header=True, level=0, spaces_per_level=2,
file.write(indentation)

def export_to_xml(self, path: PathLike = 'materials.xml',
nuclides_to_ignore: Optional[typing.Iterable[str]] = None):
nuclides_to_ignore: Optional[typing.Iterable[str]] = None,
ignore_phantom_nuclides: bool = False):
"""Export material collection to an XML file.

Parameters
Expand All @@ -1698,6 +1725,9 @@ def export_to_xml(self, path: PathLike = 'materials.xml',
Path to file to write. Defaults to 'materials.xml'.
nuclides_to_ignore : list of str
Nuclides to ignore when exporting to XML.
ignore_phantom_nuclides: bool
If True, nuclides present in the materials but with no available cross-sections
will be ignored when writing to xml.

"""
# Check if path is a directory
Expand All @@ -1710,7 +1740,9 @@ def export_to_xml(self, path: PathLike = 'materials.xml',
# one go.
with open(str(p), 'w', encoding='utf-8',
errors='xmlcharrefreplace') as fh:
self._write_xml(fh, nuclides_to_ignore=nuclides_to_ignore)
self._write_xml(fh,
ignore_phantom_nuclides=ignore_phantom_nuclides,
nuclides_to_ignore=nuclides_to_ignore)

@classmethod
def from_xml_element(cls, elem) -> Materials:
Expand Down Expand Up @@ -1758,3 +1790,32 @@ def from_xml(cls, path: PathLike = 'materials.xml') -> Materials:
root = tree.getroot()

return cls.from_xml_element(root)


def get_nuclides_with_data(cross_sections):
"""Loads cross_sections.xml file to find nuclides with neutron data or
finds nuclides with cross section data from iterable of MicroXs objects.

Parameters
----------
cross_sections : patlib.Path or iterable of MicroXs
Path to cross_sections.xml file or iterable of MicroXs objects.

Returns
-------
nuclides : set of str
Set of nuclide names that have cross secton data

"""
if isinstance(cross_sections, Path):
nuclides = set()
data_lib = DataLibrary.from_xml(cross_sections)
for library in data_lib.libraries:
if library['type'] != 'neutron':
continue
for name in library['materials']:
if name not in nuclides:
nuclides.add(name)
return nuclides
else:
return set(cross_sections[0].nuclides)
16 changes: 11 additions & 5 deletions openmc/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ def deplete(self, timesteps, method='cecm', final_step=True,
depletion_operator.cleanup_when_done = True
depletion_operator.finalize()

def export_to_xml(self, directory='.', remove_surfs=False):
def export_to_xml(self, directory='.', remove_surfs=False, ignore_phantom_nuclides=False):
"""Export model to separate XML files.

Parameters
Expand All @@ -442,6 +442,9 @@ def export_to_xml(self, directory='.', remove_surfs=False):
remove_surfs : bool
Whether or not to remove redundant surfaces from the geometry when
exporting.
ignore_phantom_nuclides: bool
If True, nuclides present in the materials but with no available cross-sections
will be ignored when writing to xml.

.. versionadded:: 0.13.1
"""
Expand All @@ -457,18 +460,18 @@ def export_to_xml(self, directory='.', remove_surfs=False):
# for all materials in the geometry and use that to automatically build
# a collection.
if self.materials:
self.materials.export_to_xml(d)
self.materials.export_to_xml(d, ignore_phantom_nuclides=ignore_phantom_nuclides)
else:
materials = openmc.Materials(self.geometry.get_all_materials()
.values())
materials.export_to_xml(d)
materials.export_to_xml(d, ignore_phantom_nuclides=ignore_phantom_nuclides)

if self.tallies:
self.tallies.export_to_xml(d)
if self.plots:
self.plots.export_to_xml(d)

def export_to_model_xml(self, path='model.xml', remove_surfs=False):
def export_to_model_xml(self, path='model.xml', remove_surfs=False, ignore_phantom_nuclides=False):
"""Export model to a single XML file.

.. versionadded:: 0.13.3
Expand All @@ -481,6 +484,9 @@ def export_to_model_xml(self, path='model.xml', remove_surfs=False):
remove_surfs : bool
Whether or not to remove redundant surfaces from the geometry when
exporting.
ignore_phantom_nuclides: bool
If True, nuclides present in the materials but with no available cross-sections
will be ignored when writing to xml.

"""
xml_path = Path(path)
Expand Down Expand Up @@ -523,7 +529,7 @@ def export_to_model_xml(self, path='model.xml', remove_surfs=False):
fh.write("<model>\n")
# Write the materials collection to the open XML file first.
# This will write the XML header also
materials._write_xml(fh, False, level=1)
materials._write_xml(fh, False, level=1, ignore_phantom_nuclides=ignore_phantom_nuclides)
# Write remaining elements as a tree
fh.write(ET.tostring(geometry_element, encoding="unicode"))
fh.write(ET.tostring(settings_element, encoding="unicode"))
Expand Down
Loading