Merge pull request #2466 from paulromano/nea-workshop-updates

Miscellaneous updates and improvements from NEA course
This commit is contained in:
Jonathan Shimwell 2023-04-24 21:15:57 +01:00 committed by GitHub
commit 895d969eed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 114 additions and 65 deletions

View file

@ -292,7 +292,7 @@ endif()
if(OPENMC_BUILD_TESTS)
find_package_write_status(Catch2)
if (NOT Catch2)
if (NOT Catch2_FOUND)
add_subdirectory(vendor/Catch2)
endif()
endif()

View file

@ -15,7 +15,8 @@ project started under the Computational Reactor Physics Group at MIT.
Complete documentation on the usage of OpenMC is hosted on Read the Docs (both
for the [latest release](https://docs.openmc.org/en/stable/) and
[developmental](https://docs.openmc.org/en/latest/) version). If you are
interested in the project, or would like to help and contribute, please get in touch on the OpenMC [discussion forum](https://openmc.discourse.group/).
interested in the project, or would like to help and contribute, please get in
touch on the OpenMC [discussion forum](https://openmc.discourse.group/).
## Installation
@ -36,20 +37,21 @@ citing the following publication:
## Troubleshooting
If you run into problems compiling, installing, or running OpenMC, first check
the [Troubleshooting section](https://docs.openmc.org/en/stable/usersguide/troubleshoot.html) in
the User's Guide. If you are not able to find a solution to your problem there,
the [Troubleshooting
section](https://docs.openmc.org/en/stable/usersguide/troubleshoot.html) in the
User's Guide. If you are not able to find a solution to your problem there,
please post to the [discussion forum](https://openmc.discourse.group/).
## Reporting Bugs
OpenMC is hosted on GitHub and all bugs are reported and tracked through the
[Issues](https://github.com/openmc-dev/openmc/issues) feature on GitHub. However,
GitHub Issues should not be used for common troubleshooting purposes. If you are
having trouble installing the code or getting your model to run properly, you
should first send a message to the User's Group mailing list. If it turns out
your issue really is a bug in the code, an issue will then be created on
GitHub. If you want to request that a feature be added to the code, you may
create an Issue on github.
[Issues](https://github.com/openmc-dev/openmc/issues) feature on GitHub.
However, GitHub Issues should not be used for common troubleshooting purposes.
If you are having trouble installing the code or getting your model to run
properly, you should first send a message to the [discussion
forum](https://openmc.discourse.group/). If it turns out your issue really is a
bug in the code, an issue will then be created on GitHub. If you want to request
that a feature be added to the code, you may create an Issue on github.
## License

View file

@ -13,20 +13,28 @@ Functions
:template: myfunction.rst
calculate_volumes
current_batch
export_properties
finalize
find_cell
find_material
global_bounding_box
global_tallies
hard_reset
id_map
import_properties
init
is_statepoint_batch
iter_batches
keff
load_nuclide
master
next_batch
num_realizations
plot_geometry
property_map
reset
reset_timers
run
run_in_memory
sample_external_source

View file

@ -5,24 +5,29 @@ import pathlib
import h5py
import openmc
from openmc.mixin import EqualityMixin
from openmc._xml import clean_indentation, reorder_attributes
class DataLibrary(EqualityMixin):
class DataLibrary(list):
"""Collection of cross section data libraries.
Attributes
----------
libraries : list of dict
List in which each item is a dictionary summarizing cross section data
from a single file. The dictionary has keys 'path', 'type', and
'materials'.
This class behaves like a list where each item is a dictionary summarizing
cross section data from a single file. The dictionary has keys 'path',
'type', and 'materials'.
.. versionchanged:: 0.13.4
This class now behaves like a list rather than requiring you to access
the list of libraries through a special attribute.
"""
def __init__(self):
self.libraries = []
super().__init__()
@property
def libraries(self):
# For backwards compatibility
return self
def get_by_material(self, name, data_type='neutron'):
"""Return the library dictionary containing a given material.
@ -43,11 +48,26 @@ class DataLibrary(EqualityMixin):
the dictionary has keys 'path', 'type', and 'materials'.
"""
for library in self.libraries:
for library in self:
if name in library['materials'] and data_type in library['type']:
return library
return None
def remove_by_material(self, name: str, data_type='neutron'):
"""Remove the library dictionary containing a specific material
Parameters
----------
name : str
Name of material, e.g. 'Am241'
data_type : str
Name of data type, e.g. 'neutron', 'photon', 'wmp', or 'thermal'
"""
library = self.get_by_material(name, data_type)
if library is not None:
self.remove(library)
def register_file(self, filename):
"""Register a file with the data library.
@ -77,7 +97,7 @@ class DataLibrary(EqualityMixin):
.format(path.name, self.__class__.__name__))
library = {'path': str(path), 'type': filetype, 'materials': materials}
self.libraries.append(library)
self.append(library)
def export_to_xml(self, path='cross_sections.xml'):
"""Export cross section data library to an XML file.
@ -92,7 +112,7 @@ class DataLibrary(EqualityMixin):
# Determine common directory for library paths
common_dir = os.path.dirname(os.path.commonprefix(
[lib['path'] for lib in self.libraries]))
[lib['path'] for lib in self]))
if common_dir == '':
common_dir = '.'
@ -100,7 +120,7 @@ class DataLibrary(EqualityMixin):
dir_element = ET.SubElement(root, "directory")
dir_element.text = os.path.realpath(common_dir)
for library in self.libraries:
for library in self:
if library['type'] == "depletion_chain":
lib_element = ET.SubElement(root, "depletion_chain")
else:

View file

@ -54,11 +54,11 @@ class Results(list):
Parameters
----------
filename : str
filename : str, optional
Path to depletion result file
"""
def __init__(self, filename=None):
def __init__(self, filename='depletion_results.h5'):
data = []
if filename is not None:
with h5py.File(str(filename), "r") as fh:

View file

@ -4,6 +4,7 @@ from collections.abc import Iterable
import hashlib
from itertools import product
from numbers import Real, Integral
import warnings
from xml.etree import ElementTree as ET
import numpy as np
@ -588,7 +589,7 @@ class CellInstanceFilter(Filter):
bins : iterable of 2-tuples or numpy.ndarray
The cell instances to tally, given as 2-tuples. For the first value in
the tuple, either openmc.Cell objects or their integral ID numbers can
be used.
be used. The second value indicates the cell instance.
filter_id : int
Unique identifier for the filter

View file

@ -5,30 +5,36 @@ from numbers import Real
import numpy as np
import openmc.checkvalue as cv
import openmc.mgxs
class EnergyGroups:
"""An energy groups structure used for multi-group cross-sections.
"""An energy group structure used for multigroup cross-sections.
Parameters
----------
group_edges : Iterable of Real
The energy group boundaries [eV]
group_edges : Iterable of float or str
The energy group boundaries in [eV] or the name of the group structure
(Must be a valid key in the openmc.mgxs.GROUP_STRUCTURES dictionary).
.. versionchanged:: 0.13.4
Changed to allow a string specifying the group structure name.
Attributes
----------
group_edges : Iterable of Real
The energy group boundaries [eV]
group_edges : np.ndarray
The energy group boundaries in [eV]
num_groups : int
The number of energy groups
"""
def __init__(self, group_edges=None):
self._group_edges = None
def __init__(self, group_edges):
if isinstance(group_edges, str):
self._name = group_edges.upper()
group_edges = openmc.mgxs.GROUP_STRUCTURES[self._name]
if group_edges is not None:
self.group_edges = group_edges
self.group_edges = group_edges
def __deepcopy__(self, memo):
existing = memo.get(id(self))
@ -59,6 +65,12 @@ class EnergyGroups:
def __hash__(self):
return hash(tuple(self.group_edges))
def __repr__(self):
if hasattr(self, '_name'):
return f"<EnergyGroups: {self.num_groups} groups ({self._name})>"
else:
return f"<EnergyGroups: {self.num_groups} groups>"
@property
def group_edges(self):
return self._group_edges
@ -226,10 +238,7 @@ class EnergyGroups:
group_edges = np.sort(group_edges)
# Create a new condensed EnergyGroups object
condensed_groups = EnergyGroups()
condensed_groups.group_edges = group_edges
return condensed_groups
return EnergyGroups(group_edges)
def can_merge(self, other):
"""Determine if energy groups can be merged with another.

View file

@ -1206,7 +1206,7 @@ def _close_random_pack(domain, spheres, contraction_rate):
def pack_spheres(radius, region, pf=None, num_spheres=None, initial_pf=0.3,
contraction_rate=1.e-3, seed=1):
contraction_rate=1.e-3, seed=None):
"""Generate a random, non-overlapping configuration of spheres within a
container.
@ -1235,7 +1235,7 @@ def pack_spheres(radius, region, pf=None, num_spheres=None, initial_pf=0.3,
reached using a smaller contraction rate, but the algorithm will take
longer to converge.
seed : int, optional
RNG seed.
Pseudorandom number generator seed passed to :func:`random.seed`
Returns
------
@ -1278,7 +1278,8 @@ def pack_spheres(radius, region, pf=None, num_spheres=None, initial_pf=0.3,
"""
# Seed RNG
random.seed(seed)
if seed is not None:
random.seed(seed)
# Create container with the correct shape based on the supplied region
domain = None
@ -1310,14 +1311,13 @@ def pack_spheres(radius, region, pf=None, num_spheres=None, initial_pf=0.3,
# Check packing fraction for close random packing
if pf > MAX_PF_CRP:
raise ValueError('Packing fraction {0} is greater than the limit for '
'close random packing, {1}'.format(pf, MAX_PF_CRP))
raise ValueError(f'Packing fraction {pf} is greater than the limit for '
f'close random packing, {MAX_PF_CRP}')
# Check packing fraction for random sequential packing
if initial_pf > MAX_PF_RSP:
raise ValueError('Initial packing fraction {0} is greater than the '
'limit for random sequential packing, '
'{1}'.format(initial_pf, MAX_PF_RSP))
raise ValueError(f'Initial packing fraction {initial_pf} is greater than'
f'the limit for random sequential packing, {MAX_PF_RSP}')
# Calculate the sphere radius used in the initial random sequential
# packing from the initial packing fraction

View file

@ -737,16 +737,25 @@ class Plane(PlaneMixin, Surface):
Plane
Plane that passes through the three points
Raises
------
ValueError
If all three points lie along a line
"""
# Convert to numpy arrays
p1 = np.asarray(p1)
p2 = np.asarray(p2)
p3 = np.asarray(p3)
p1 = np.asarray(p1, dtype=float)
p2 = np.asarray(p2, dtype=float)
p3 = np.asarray(p3, dtype=float)
# Find normal vector to plane by taking cross product of two vectors
# connecting p1->p2 and p1->p3
n = np.cross(p2 - p1, p3 - p1)
# Check for points along a line
if np.allclose(n, 0.):
raise ValueError("All three points appear to lie along a line.")
# The equation of the plane will by n·(<x,y,z> - p1) = 0. Determine
# coefficients a, b, c, and d based on that
a, b, c = n

View file

@ -191,9 +191,9 @@ Tally::Tally(pugi::xml_node node)
case SCORE_DELAYED_NU_FISSION:
case SCORE_PROMPT_NU_FISSION:
case SCORE_DECAY_RATE:
warning("Particle filter is not used with photon transport"
" on and " +
reaction_name(score) + " score.");
warning("You are tallying the '" + reaction_name(score) +
"' score and haven't used a particle filter. This score will "
"include contributions from all particles.");
break;
}
}
@ -1283,7 +1283,8 @@ extern "C" int openmc_remove_tally(int32_t index)
if (index < 0 || index > model::tallies.size()) {
return OPENMC_E_OUT_OF_BOUNDS;
}
// grab tally so it's ID can be obtained to remove the (ID,index) pair from tally_map
// grab tally so it's ID can be obtained to remove the (ID,index) pair from
// tally_map
auto& tally = model::tallies[index];
// delete the tally via iterator pointing to correct position
// this calls the Tally destructor, removing the tally from the map as well

View file

@ -1,7 +1,3 @@
import random
from math import sqrt
import numpy as np
import openmc
import openmc.model
@ -65,8 +61,8 @@ class TRISOTestHarness(PyAPITestHarness):
box = openmc.Cell(region=box_region)
outer_radius = 422.5*1e-4
centers = openmc.model.pack_spheres(radius=outer_radius,
region=box_region, num_spheres=100)
centers = openmc.model.pack_spheres(
radius=outer_radius, region=box_region, num_spheres=100, seed=1)
trisos = [openmc.model.TRISO(outer_radius, inner_univ, c)
for c in centers]

View file

@ -11,7 +11,7 @@ import openmc.data
def test_data_library(tmpdir):
lib = openmc.data.DataLibrary.from_xml()
for f in lib.libraries:
for f in lib:
assert sorted(f.keys()) == ['materials', 'path', 'type']
f = lib.get_by_material('U235')
@ -22,6 +22,9 @@ def test_data_library(tmpdir):
assert f['type'] == 'thermal'
assert 'c_H_in_H2O' in f['materials']
lib.remove_by_material('Pu239')
assert lib.get_by_material('Pu239') is None
filename = str(tmpdir.join('test.xml'))
lib.export_to_xml(filename)
assert os.path.exists(filename)
@ -29,9 +32,9 @@ def test_data_library(tmpdir):
new_lib = openmc.data.DataLibrary()
directory = os.path.dirname(os.environ['OPENMC_CROSS_SECTIONS'])
new_lib.register_file(os.path.join(directory, 'H1.h5'))
assert new_lib.libraries[-1]['type'] == 'neutron'
assert new_lib[-1]['type'] == 'neutron'
new_lib.register_file(os.path.join(directory, 'c_Zr_in_ZrH.h5'))
assert new_lib.libraries[-1]['type'] == 'thermal'
assert new_lib[-1]['type'] == 'thermal'
def test_depletion_chain_data_library(run_in_tmpdir):

View file

@ -74,7 +74,7 @@ def test_get_keff(res):
def test_get_steps(unit):
# Make a Results full of near-empty Result instances
# Just fill out a time schedule
results = openmc.deplete.Results()
results = openmc.deplete.Results(filename=None)
# Time in units of unit
times = np.linspace(0, 100, num=5)
if unit == "a":