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

Adding methods to automatically apply results to existing Tally objects. #2671

Open
wants to merge 23 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a364244
Adding method to automatically apply tally results to existing objects.
pshriwise Aug 28, 2023
481dd48
Slight refactor and allowing application through Model.run
pshriwise Aug 28, 2023
d58c811
properly handle internal tallies
pshriwise Aug 29, 2023
6322a30
Some slight cleanup
pshriwise Apr 10, 2024
0f9d35c
Adding test
pshriwise Apr 10, 2024
a761b42
Replacing filters, nuclides, scores instead of appending.
pshriwise Apr 10, 2024
41841f1
Updates to tally equivalence and version numbers
pshriwise Apr 11, 2024
495eec2
Updates to the tally class to account for estimator type
pshriwise Apr 30, 2024
df3469a
Update version in add_results docstring
pshriwise Sep 1, 2024
5734b75
Moving additional checks into the Statepoint.get_tally method
pshriwise Sep 13, 2024
165b340
Adding tmate debug
pshriwise Sep 13, 2024
0f69c6f
Adopt the tally estimator from the statepoint file and warn if it's d…
pshriwise Sep 13, 2024
bf0d60c
Revert to default tally estimator as None. Allow None as equivalent i…
pshriwise Sep 13, 2024
e8fcebf
Adding a comment to explain flexibility of tally estimators in equiva…
pshriwise Sep 13, 2024
d39c4a5
Allow estimator to be set to None
pshriwise Sep 13, 2024
38b1467
Propoagate passive None value to StatePoint.get_tally
pshriwise Sep 14, 2024
5af1eea
Update expected test result basd on conclusion about tally estimator …
pshriwise Sep 16, 2024
5e46acf
Explicitly allow setting tally estimator to None
pshriwise Sep 17, 2024
fa4d976
Correction to object name in get_tally condition for estimator match
pshriwise Sep 17, 2024
03d80eb
Eesh. Correction to control flow of Tally.estimator property setter
pshriwise Sep 17, 2024
7699d48
Set estimator regardless of warning
pshriwise Sep 18, 2024
3ac0ca3
A little bit of self-review
pshriwise Sep 20, 2024
2fb7e3d
Removing tmate from CI
pshriwise Sep 20, 2024
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
22 changes: 21 additions & 1 deletion openmc/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,8 @@ def import_properties(self, filename):
def run(self, particles=None, threads=None, geometry_debug=False,
restart_file=None, tracks=False, output=True, cwd='.',
openmc_exec='openmc', mpi_args=None, event_based=None,
export_model_xml=True, **export_kwargs):
export_model_xml=True, apply_tally_results=False,
**export_kwargs):
"""Run OpenMC

If the C API has been initialized, then the C API is used, otherwise,
Expand Down Expand Up @@ -644,6 +645,11 @@ def run(self, particles=None, threads=None, geometry_debug=False,
to True.

.. versionadded:: 0.13.3
apply_tally_results : bool
Whether or not to apply results of the final statepoint file to the
Copy link
Contributor

Choose a reason for hiding this comment

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

@tjlaboss would say:

Suggested change
Whether or not to apply results of the final statepoint file to the
Whether to apply results of the final statepoint file to the
model's tally objects.

model's tally objects.

.. versionadded:: 0.15.1
**export_kwargs
Keyword arguments passed to either :meth:`Model.export_to_model_xml`
or :meth:`Model.export_to_xml`.
Expand Down Expand Up @@ -715,6 +721,10 @@ def run(self, particles=None, threads=None, geometry_debug=False,
if mtime >= tstart: # >= allows for poor clock resolution
tstart = mtime
last_statepoint = sp

if apply_tally_results:
self.apply_tally_results(last_statepoint)

return last_statepoint

def calculate_volumes(self, threads=None, output=True, cwd='.',
Expand Down Expand Up @@ -909,6 +919,16 @@ def sample_external_source(
n_samples=n_samples, prn_seed=prn_seed
)

def apply_tally_results(self, statepoint):
"""Apply results from a statepoint to tally objects on the Model

Parameters
----------
statepoint : PathLike or openmc.StatePoint instance
StatePoint file used to update tally results
"""
self.tallies.add_results(statepoint)

def plot_geometry(self, output=True, cwd='.', openmc_exec='openmc'):
"""Creates plot images as specified by the Model.plots attribute

Expand Down
175 changes: 118 additions & 57 deletions openmc/statepoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ def __enter__(self):
def __exit__(self, *exc):
self.close()

def __del__(self):
self.close()

@property
def cmfd_on(self):
return self._f.attrs['cmfd_on'] > 0
Expand Down Expand Up @@ -392,6 +395,7 @@ def tallies(self):
if self.tallies_present and not self._tallies_read:
# Read the number of tallies
tallies_group = self._f['tallies']

n_tallies = tallies_group.attrs['n_tallies']

# Read a list of the IDs for each Tally
Expand All @@ -407,64 +411,77 @@ def tallies(self):

# Iterate over all tallies
for tally_id in tally_ids:
group = tallies_group[f'tally {tally_id}']

# Check if tally is internal and therefore has no data
Comment on lines -410 to -412
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 to have moved to _populate_tally, but it doesn't seem like that's always ran. Is that ok that this won't always be ran?

if group.attrs.get("internal"):
continue

# Create Tally object and assign basic properties
tally = openmc.Tally(tally_id)
tally._sp_filename = self._f.filename
tally.name = group['name'][()].decode() if 'name' in group else ''

# Check if tally has multiply_density attribute
if "multiply_density" in group.attrs:
tally.multiply_density = group.attrs["multiply_density"].item() > 0

# Read the number of realizations
n_realizations = group['n_realizations'][()]

tally.estimator = group['estimator'][()].decode()
tally.num_realizations = n_realizations

# Read derivative information.
if 'derivative' in group:
deriv_id = group['derivative'][()]
tally.derivative = self.tally_derivatives[deriv_id]

# Read all filters
n_filters = group['n_filters'][()]
if n_filters > 0:
filter_ids = group['filters'][()]
filters_group = self._f['tallies/filters']
for filter_id in filter_ids:
filter_group = filters_group[f'filter {filter_id}']
new_filter = openmc.Filter.from_hdf5(
filter_group, meshes=self.meshes)
tally.filters.append(new_filter)

# Read nuclide bins
nuclide_names = group['nuclides'][()]

# Add all nuclides to the Tally
for name in nuclide_names:
nuclide = openmc.Nuclide(name.decode().strip())
tally.nuclides.append(nuclide)

# Add the scores to the Tally
scores = group['score_bins'][()]
for score in scores:
tally.scores.append(score.decode())

# Add Tally to the global dictionary of all Tallies
tally.sparse = self.sparse
self._tallies[tally_id] = tally
tally = self._read_tally(tally_id)
if tally is not None:
self._tallies[tally_id] = tally

self._tallies_read = True

return self._tallies

def _read_tally(self, tally_id):
if self._f['tallies'][f'tally {tally_id}'].attrs.get('internal'):
return
tally = openmc.Tally(tally_id)
self._populate_tally(tally)
return tally

def _populate_tally(self, tally):
group = self._f['tallies'][f'tally {tally.id}']

# Check if tally is internal and therefore has no data
if group.attrs.get('internal'):
return

tally._sp_filename = self._f.filename
tally.name = group['name'][()].decode() if 'name' in group else ''

# Check if tally has multiply_density attribute
if "multiply_density" in group.attrs:
tally.multiply_density = group.attrs["multiply_density"].item() > 0

# Read the number of realizations
n_realizations = group['n_realizations'][()]

# accept the estimator set during execution of OpenMC
estimator = group['estimator'][()].decode()
if tally.estimator is not None and tally.estimator != estimator:
warnings.warn(f"Estimator for Tally {tally.id} changed from "
f"{tally.estimator} to {estimator} in OpenMC execution")
tally.estimator = estimator

tally.num_realizations = n_realizations

# Read derivative information.
if 'derivative' in group:
deriv_id = group['derivative'][()]
tally.derivative = self.tally_derivatives[deriv_id]

# Read all filters
n_filters = group['n_filters'][()]
if n_filters > 0:
filter_ids = group['filters'][()]
filters_group = self._f['tallies/filters']
tally.filters = []
for filter_id in filter_ids:
filter_group = filters_group[f'filter {filter_id}']
new_filter = openmc.Filter.from_hdf5(
filter_group, meshes=self.meshes)
tally.filters.append(new_filter)

# Read nuclide bins
nuclide_names = group['nuclides'][()]

# Add all nuclides to the Tally
tally.nuclides = [openmc.Nuclide(name.decode().strip()) for name in nuclide_names]

# Add the scores to the Tally
scores = group['score_bins'][()]
tally.scores = [score.decode() for score in scores]

# Add Tally to the global dictionary of all Tallies
tally.sparse = self.sparse

@property
def tallies_present(self):
return self._f.attrs['tallies_present'] > 0
Expand Down Expand Up @@ -524,9 +541,39 @@ def add_volume_information(self, volume_calc):
if self.summary is not None:
self.summary.add_volume_information(volume_calc)

def match_tally(self, tally):
"""
Find a tally with an exact specification match.

.. versionadded:: 0.15.1

Parameters
----------
tally : openmc.Tally instance
The Tally object to match.

Returns
-------
None or openmc.Tally
"""
sp_tally = self.get_tally(tally.scores,
tally.filters,
tally.nuclides,
tally.name,
tally.id,
tally.estimator,
exact_filters=True,
exact_nuclides=True,
exact_scores=True,
multiply_density=True,
derivative=tally.derivative)
Comment on lines +559 to +569
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 feel PEP8. With such a long arguments list keyword arguments should be used to avoid off-by-1 errors.


return sp_tally

def get_tally(self, scores=[], filters=[], nuclides=[],
name=None, id=None, estimator=None, exact_filters=False,
exact_nuclides=False, exact_scores=False):
exact_nuclides=False, exact_scores=False,
multiply_density=None, derivative=None):
"""Finds and returns a Tally object with certain properties.

This routine searches the list of Tallies and returns the first Tally
Expand Down Expand Up @@ -564,6 +611,12 @@ def get_tally(self, scores=[], filters=[], nuclides=[],
If True, the number of scores in the parameters must be identical
to those in the matching Tally. If False (default), the scores
in the parameters may be a subset of those in the matching Tally.
Default is None (no check).
multiply_density : bool, optional
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels more like an enforce_multipl_dens than a multiply_density

If True, the Tally must have the multiply by density flag set to
the same value as the input parameter. Default is True.
derivative : openmc.TallyDerivative, optional
TallyDerivative object to match. Default is None.

Returns
-------
Expand Down Expand Up @@ -591,17 +644,25 @@ def get_tally(self, scores=[], filters=[], nuclides=[],
if id and id != test_tally.id:
continue

# Determine if Tally has queried estimator
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 a bit out of scope, but why iterate over self.tallies.values()? This seems like its a dictionary, and doing it this makes it an O(N^2) operation to find all tallies.

if estimator and estimator != test_tally.estimator:
# Determine if Tally has queried estimator, only move on to next tally
# if the estimator is both specified and the tally estimtor does not
# match
if estimator is not None and estimator != test_tally.estimator:
continue

# The number of filters, nuclides and scores must exactly match
if exact_scores and len(scores) != test_tally.num_scores:
continue
if exact_nuclides and len(nuclides) != test_tally.num_nuclides:
if exact_nuclides and nuclides and len(nuclides) != test_tally.num_nuclides:
continue
if exact_nuclides and not nuclides and test_tally.nuclides != ['total']:
continue
if exact_filters and len(filters) != test_tally.num_filters:
continue
if derivative is not None and derivative != sp_tally.derivative:
continue
if multiply_density is not None and multiply_density != sp_tally.multiply_density:
continue

# Determine if Tally has the queried score(s)
if scores:
Expand Down
Loading