mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-28 14:15:42 -04:00
Merge pull request #2466 from paulromano/nea-workshop-updates
Miscellaneous updates and improvements from NEA course
This commit is contained in:
commit
895d969eed
13 changed files with 114 additions and 65 deletions
|
|
@ -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()
|
||||
|
|
|
|||
22
README.md
22
README.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue