diff --git a/CMakeLists.txt b/CMakeLists.txt index 866050a98d..ca0b885b27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/README.md b/README.md index bd7a8479aa..561bf4e1a6 100644 --- a/README.md +++ b/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 diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index 882e3c71b3..89fa167ffe 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -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 diff --git a/openmc/data/library.py b/openmc/data/library.py index ad6c65fb66..5cafd19617 100644 --- a/openmc/data/library.py +++ b/openmc/data/library.py @@ -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: diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index c9dc198166..555a7ce373 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -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: diff --git a/openmc/filter.py b/openmc/filter.py index cc7bdd9311..5233908c8d 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -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 diff --git a/openmc/mgxs/groups.py b/openmc/mgxs/groups.py index d49f2d651e..eaef519da1 100644 --- a/openmc/mgxs/groups.py +++ b/openmc/mgxs/groups.py @@ -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"" + else: + return f"" + @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. diff --git a/openmc/model/triso.py b/openmc/model/triso.py index f486482aab..b41ae7fa61 100644 --- a/openmc/model/triso.py +++ b/openmc/model/triso.py @@ -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 diff --git a/openmc/surface.py b/openmc/surface.py index b8ea5479d5..984bc7a5cc 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -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·( - p1) = 0. Determine # coefficients a, b, c, and d based on that a, b, c = n diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 94bfc6cd49..8056b2574f 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -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 diff --git a/tests/regression_tests/triso/test.py b/tests/regression_tests/triso/test.py index 248724cc29..06ae04492f 100644 --- a/tests/regression_tests/triso/test.py +++ b/tests/regression_tests/triso/test.py @@ -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] diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py index f88be2602d..226d848a50 100644 --- a/tests/unit_tests/test_data_misc.py +++ b/tests/unit_tests/test_data_misc.py @@ -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): diff --git a/tests/unit_tests/test_deplete_resultslist.py b/tests/unit_tests/test_deplete_resultslist.py index 3c6d77a759..6e4a4210bd 100644 --- a/tests/unit_tests/test_deplete_resultslist.py +++ b/tests/unit_tests/test_deplete_resultslist.py @@ -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":