Adding methods to automatically apply results to existing Tally objects.
Description
One thing I've always found a bit clunky when working with our simulation results is the process of extracting user-specified tally results from the statepoint file. It seems to me that finding a match is left to the user, but it should be possible to automate that in the majority of cases.
The idea here is that we can now apply results from our statepoint file to the original tally objects created as inputs to the simulation. This simplifies situations like:
tally = openmc.Tally()
model.tallies = [tally]
# further tally setup here
sp_file = model.run()
with openmc.StatePoint(sp_file) as sp:
sp_tally = sp.get_tally(id=tally.id)
# do result postprocessing here with the new sp_tally object, two objects to keep track of
to
tally = openmc.Tally()
model.tallies = [tally]
# further tally setup here
sp_file = model.run()
tally.add_results(sp_file)
# only one tally object to worry about
# do result postprocessing here with original tally object
or even
tally = openmc.Tally()
model.tallies = [tally]
# further tally setup here
sp_file = model.run(apply_tally_results=True)
# do result postprocessing here with original tally
Keeping this as a draft for now as I'm kind of workshopping it. But I'll take care of the following dev tasks soon if there aren't any objections to heading this direction.
- [ ] I have performed a self-review of my own code
- [ ] I have followed the style guidelines for Python source files (if applicable)
- [ ] I have made corresponding changes to the documentation (if applicable)
- [ ] I have added tests that prove my fix is effective or that my feature works (if applicable)
After a good amount of massaging related to the Tally.estimator and Tally object equivalence I came to the following conclusion:
When the Tally.estimator attribute is None this is effectively an indication that OpenMC should select an appropriate estimator for the filters and tallies applied. (This is often the case from user tally generation code I've seen). At runtime, OpenMC will select and apply an appropriate estimator with a preference for the tracklength estimator if allowed. This estimator is written to the state point file explicitly, as it should be, but it makes matching the original, "input" Tally object to the Tally object in the StatePoint object less straightforward.
I've updated the documentation about the Tally.estimator attribute to reflect this understanding and the Tally.__eq__ method allows a match between tallies if either of the Tally.estimator attributes are set to None.
With that, I think I'm pretty happy with the state of this pending further review or something I've missed (impossible); I'll be looking forward to updating tutorials/notebooks to use this feature as I think it will make setup and post-processing in a single script/notebook more intuitive.
@MicahGale @paulromano @jtramm I'd be curious to hear your thoughts in particular!
I still need to pull this branch and play around with it a bit. My first inclination for the equality of None is: why use implicit defaults? If an explicit default were you used where it just defaulted to track length, then all the __eq__ logic would be unnecessary, and would be overall more intuitive for the users.
I still need to pull this branch and play around with it a bit. My first inclination for the equality of
Noneis: why use implicit defaults? If an explicit default were you used where it just defaulted to track length, then all the__eq__logic would be unnecessary, and would be overall more intuitive for the users.
Yeah I agree that an explicit default might be more clear in terms of intention, but I think the equivalence logic would mainly remain the same. We can't really rely on 'tracklength' as the default value because there are restrictions for estimators on certain scores/filters, but the C++ will try to respect an explicitly set estimator on the Tally object currently.
https://github.com/openmc-dev/openmc/blob/dcb25575ca0691ddde36af5141376c29aab884bc/src/tallies/tally.cpp#L283
Explicitly setting tracklength is going to cause a lot of problems in the test suite and a lot of this implicit default is already baked into that in one way or another. I actually tried this out earlier in this PR but it was clear that it's sort of its own mess to untangle if we want to change that behavior.
Another option is on the C++ side, the calls to fatal_error could be reduced to warnings with an update to the applied estimator as needed, but I prefer the status quo tbh.
Sorry for the delay, yes that makes sense for now. Long term it would be nice it the default were automatically changed as needed, but that's beyond this scope.
@MicahGale all of your comments should be addressed at this point. Let us know if you're satisfied and if so, we'll go ahead and merge. Thanks!
Rather than separating out the
_populate_tallymethod as you had done in theStatePointclass and then using that within theTallyclass, I simply updated theTally._read_resultsmethod to get all the information it needs (number of realizations, list of nuclides) and thenadd_resultsjust adds the_sp_filenameattribute that is used when_read_resultsis called. This makes the overall diff a bit smaller.
Nice! I like it. More succinct; fewer methods to maintain. Thanks!
- Taking that logic you had as
_populate_tallyand perhaps having it be aTally.from_hdf5classmethod.
Yeah I think that might be nice. I'll snag the last commit with that here and make an issue.
- Setting
Tally.nuclidesto be["total"]by default (explicit is better than implicit). I could definitely sense some awkwardness around this in our logic.
I like it. As @MicahGale suggested, I think we might want to do something with the estimator that's more descriptive than None ( "auto" perhaps?). I can create an issue for that as well if you're on board.
Thanks for cleaning this up!