diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..153f0e4a3 --- /dev/null +++ b/.clang-format @@ -0,0 +1,108 @@ +--- +Language: Cpp +# BasedOnStyle: Mozilla +AccessModifierOffset: -2 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... diff --git a/.gitmodules b/.gitmodules index 4e2f34167..ff9120010 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "vendor/xtl"] path = vendor/xtl url = https://github.com/xtensor-stack/xtl.git +[submodule "vendor/fmt"] + path = vendor/fmt + url = https://github.com/fmtlib/fmt.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 80909b83c..29eed4fb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,11 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # Set module path set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules) +# Allow user to specify _ROOT variables +if (NOT (CMAKE_VERSION VERSION_LESS 3.12)) + cmake_policy(SET CMP0074 NEW) +endif() + #=============================================================================== # Command line options #=============================================================================== @@ -35,18 +40,18 @@ endif() #=============================================================================== if(dagmc) find_package(DAGMC REQUIRED) + add_library(dagmc-imported INTERFACE IMPORTED) link_directories(${DAGMC_LIBRARY_DIRS}) + target_link_libraries(dagmc-imported INTERFACE ${DAGMC_LIBRARIES}) + target_include_directories(dagmc-imported INTERFACE ${DAGMC_INCLUDE_DIRS}) +else() + set(DAGMC_FOUND false) endif() #=============================================================================== # HDF5 for binary output #=============================================================================== -# Allow user to specify HDF5_ROOT -if (NOT (CMAKE_VERSION VERSION_LESS 3.12)) - cmake_policy(SET CMP0074 NEW) -endif() - # Unfortunately FindHDF5.cmake will always prefer a serial HDF5 installation # over a parallel installation if both appear on the user's PATH. To get around # this, we check for the environment variable HDF5_ROOT and if it exists, use it @@ -140,6 +145,13 @@ endif() add_subdirectory(vendor/pugixml) +#=============================================================================== +# {fmt} library +#=============================================================================== + +set(FMT_INSTALL ON CACHE BOOL "Generate the install target.") +add_subdirectory(vendor/fmt) + #=============================================================================== # xtensor header-only library #=============================================================================== @@ -161,7 +173,8 @@ target_link_libraries(xtensor INTERFACE xtl) add_subdirectory(vendor/gsl-lite) # Make sure contract violations throw exceptions -target_compile_definitions(gsl-lite INTERFACE GSL_THROW_ON_CONTRACT_VIOLATION) +target_compile_definitions(gsl-lite-v1 INTERFACE GSL_THROW_ON_CONTRACT_VIOLATION) +target_compile_definitions(gsl-lite-v1 INTERFACE gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON=1) #=============================================================================== # RPATH information @@ -345,12 +358,11 @@ endif() # target_link_libraries treats any arguments starting with - but not -l as # linker flags. Thus, we can pass both linker flags and libraries together. target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} ${HDF5_HL_LIBRARIES} - pugixml faddeeva xtensor gsl-lite) + pugixml faddeeva xtensor gsl-lite-v1 fmt::fmt) if(dagmc) target_compile_definitions(libopenmc PRIVATE DAGMC) - target_link_libraries(libopenmc ${DAGMC_LIBRARIES}) - target_include_directories(libopenmc PRIVATE ${DAGMC_INCLUDE_DIRS}) + target_link_libraries(libopenmc dagmc-imported) endif() #=============================================================================== @@ -380,6 +392,8 @@ add_custom_command(TARGET libopenmc POST_BUILD # Install executable, scripts, manpage, license #=============================================================================== +configure_file(cmake/OpenMCConfig.cmake.in "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMCConfig.cmake" @ONLY) + set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/OpenMC) install(TARGETS openmc libopenmc faddeeva EXPORT openmc-targets @@ -393,7 +407,7 @@ install(EXPORT openmc-targets DESTINATION ${INSTALL_CONFIGDIR}) install(DIRECTORY src/relaxng DESTINATION ${CMAKE_INSTALL_DATADIR}/openmc) -install(FILES cmake/OpenMCConfig.cmake DESTINATION ${INSTALL_CONFIGDIR}) +install(FILES "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMCConfig.cmake" DESTINATION ${INSTALL_CONFIGDIR}) install(FILES man/man1/openmc.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) install(FILES LICENSE DESTINATION "${CMAKE_INSTALL_DOCDIR}" RENAME copyright) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/cmake/Modules/FindDAGMC.cmake b/cmake/Modules/FindDAGMC.cmake index c644c8886..bb9bc56dd 100644 --- a/cmake/Modules/FindDAGMC.cmake +++ b/cmake/Modules/FindDAGMC.cmake @@ -12,7 +12,9 @@ find_path(DAGMC_CMAKE_CONFIG NAMES DAGMCConfig.cmake PATHS ENV LD_LIBRARY_PATH PATH_SUFFIXES lib Lib cmake lib/cmake NO_DEFAULT_PATH) - -message(STATUS "Found DAGMC in ${DAGMC_CMAKE_CONFIG}") - -include(${DAGMC_CMAKE_CONFIG}/DAGMCConfig.cmake) +if(DAGMC_CMAKE_CONFIG) + message(STATUS "Found DAGMC in ${DAGMC_CMAKE_CONFIG}") + include(${DAGMC_CMAKE_CONFIG}/DAGMCConfig.cmake) +else() + message(WARNING "Cound not find DAGMC") +endif() diff --git a/cmake/OpenMCConfig.cmake b/cmake/OpenMCConfig.cmake deleted file mode 100644 index 0bc86fa71..000000000 --- a/cmake/OpenMCConfig.cmake +++ /dev/null @@ -1,8 +0,0 @@ -get_filename_component(OpenMC_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) - -find_package(xtl REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtl) -find_package(xtensor REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtensor) - -if(NOT TARGET OpenMC::libopenmc) - include("${OpenMC_CMAKE_DIR}/OpenMCTargets.cmake") -endif() diff --git a/cmake/OpenMCConfig.cmake.in b/cmake/OpenMCConfig.cmake.in new file mode 100644 index 000000000..1c6a488f5 --- /dev/null +++ b/cmake/OpenMCConfig.cmake.in @@ -0,0 +1,20 @@ +get_filename_component(OpenMC_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) + +find_package(fmt REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../fmt) +find_package(gsl-lite REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../gsl-lite) +find_package(pugixml REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../pugixml) +find_package(xtl REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtl) +find_package(xtensor REQUIRED HINTS ${OpenMC_CMAKE_DIR}/../xtensor) +if(@DAGMC_FOUND@) + find_package(DAGMC REQUIRED HINTS @DAGMC_LIBRARY_DIRS@) + add_library(dagmc-imported INTERFACE IMPORTED) + target_link_libraries(dagmc-imported INTERFACE ${DAGMC_LIBRARIES}) + foreach(dir ${DAGMC_LIBRARY_DIRS}) + target_link_libraries(dagmc-imported INTERFACE "-L${dir}") + endforeach() + target_include_directories(dagmc-imported INTERFACE ${DAGMC_INCLUDE_DIRS}) +endif() + +if(NOT TARGET OpenMC::libopenmc) + include("${OpenMC_CMAKE_DIR}/OpenMCTargets.cmake") +endif() diff --git a/docs/source/devguide/styleguide.rst b/docs/source/devguide/styleguide.rst index 562b75e89..a46744a3f 100644 --- a/docs/source/devguide/styleguide.rst +++ b/docs/source/devguide/styleguide.rst @@ -12,6 +12,14 @@ adding new code in OpenMC. C++ --- +.. important:: To ensure consistent styling with little effort, this project + uses `clang-format `_. The + repository contains a ``.clang-format`` file that can be used to + automatically apply the style rules that are described below. The easiest + way to use clang-format is through a plugin/extension for your editor/IDE + that automatically runs clang-format using the ``.clang-format`` file + whenever a file is saved. + Indentation ----------- diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 2e2648237..83aa0691f 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -142,7 +142,7 @@ materials in the problem and is specified using a :ref:`mesh_element`. ---------------------------- Determines whether to use event-based parallelism instead of the default -history-based parallelism. +history-based parallelism. *Default*: false @@ -459,6 +459,15 @@ attributes/sub-elements: *Default*: None + :library: + If this attribute is given, it indicates that the source is to be + instantiated from an externally compiled source function. This source can be + as complex as is required to define the source for your problem. The only + requirement is that there is a function called ``sample_source()``. More + documentation on how to build sources can be found in :ref:`custom_source`. + + *Default*: None + :space: An element specifying the spatial distribution of source sites. This element has the following attributes: @@ -591,6 +600,67 @@ attributes/sub-elements: *Default*: false +.. _custom_source: + +Custom Sources +++++++++++++++ + +It is often the case that one may wish to simulate a complex source +distribution, which may include physics not present within OpenMC or to be phase +space complex. It is possible to define a complex source with an externally +defined source function that is loaded at runtime. A simple example source is +shown below. + +.. code-block:: c++ + + #include "openmc/random_lcg.h" + #include "openmc/source.h" + #include "openmc/particle.h" + + // you must have external C linkage here + extern "C" openmc::Particle::Bank sample_source(uint64_t* seed) { + openmc::Particle::Bank particle; + // weight + particle.particle = openmc::Particle::Type::neutron; + particle.wgt = 1.0; + // position + double angle = 2.0 * M_PI * openmc::prn(seed); + double radius = 3.0; + particle.r.x = radius * std::cos(angle); + particle.r.y = radius * std::sin(angle); + particle.r.z = 0.0; + // angle + particle.u = {1.0, 0.0, 0.0}; + particle.E = 14.08e6; + particle.delayed_group = 0; + return particle; + } + +The above source, creates 14.08 MeV neutrons, with an istropic direction +vector but distributed in a ring with a 3 cm radius. This routine is +not particularly complex, but should serve as an example upon which to build +more complicated sources. + + .. note:: The function signature must be declared to be extern "C". + + .. note:: You should only use the openmc::prn() random number generator + +In order to build your external source, you will need to link it against the +OpenMC shared library. This can be done by writing a CMakeLists.txt file: + +.. code-block:: cmake + + cmake_minimum_required(VERSION 3.3 FATAL_ERROR) + project(openmc_sources CXX) + add_library(source SHARED source_ring.cpp) + find_package(OpenMC REQUIRED HINTS ) + target_link_libraries(source OpenMC::libopenmc) + +After running ``cmake`` and ``make``, you will have a libsource.so (or .dylib) +file in your build directory. Setting the :attr:`openmc.Source.library` +attribute to the path of this shared library will indicate that it should be +used for sampling source particles at runtime. + .. _univariate: Univariate Probability Distributions diff --git a/docs/source/usersguide/depletion.rst b/docs/source/usersguide/depletion.rst index 4a4c7b1f8..f6feb6d24 100644 --- a/docs/source/usersguide/depletion.rst +++ b/docs/source/usersguide/depletion.rst @@ -33,12 +33,11 @@ material compositions over time. Each method appears as a different class. For example, :class:`openmc.deplete.CECMIntegrator` runs a depletion calculation using the CE/CM algorithm (deplete over a timestep using the middle-of-step reaction rates). An instance of :class:`openmc.deplete.Operator` is passed to -one of these functions along with the power level and timesteps:: +one of these functions along with the timesteps and power level:: - power = 1200.0e6 - days = 24*60*60 - timesteps = [10.0*days, 10.0*days, 10.0*days] - openmc.deplete.CECMIntegrator(op, power, timesteps).integrate() + power = 1200.0e6 # watts + timesteps = [10.0, 10.0, 10.0] # days + openmc.deplete.CECMIntegrator(op, timesteps, power, timestep_units='d').integrate() The coupled transport-depletion problem is executed, and once it is done a ``depletion_results.h5`` file is written. The results can be analyzed using the @@ -67,7 +66,7 @@ the energy deposited during a transport calculation will be lower than expected. This causes the reaction rates to be over-adjusted to hit the user-specific power, or power density, leading to an over-depletion of burnable materials. -There are some remedies. First, the fission Q values can be directly set in a +There are some remedies. First, the fission Q values can be directly set in a variety of ways. This requires knowing what the total fission energy release should be, including indirect components. Some examples are provided below:: @@ -99,11 +98,11 @@ Local Spectra and Repeated Materials ------------------------------------ It is not uncommon to explicitly create a single burnable material across many locations. -From a pure transport perspective, there is nothing wrong with creating a single +From a pure transport perspective, there is nothing wrong with creating a single 3.5 wt.% enriched fuel ``fuel_3``, and placing that fuel in every fuel pin in an assembly or even full core problem. This certainly expedites the model making process, but can pose -issues with depletion. -Under this setup, :mod:`openmc.deplete` will deplete a single ``fuel_3`` material using +issues with depletion. +Under this setup, :mod:`openmc.deplete` will deplete a single ``fuel_3`` material using a single set of reaction rates, and produce a single new composition for the next time step. This can be problematic if the same ``fuel_3`` is used in very different regions of the problem. diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 66440a3c3..ec10fea4f 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -404,7 +404,7 @@ to install the Python package in :ref:`"editable" mode `. Prerequisites ------------- -The Python API works with Python 3.4+. In addition to Python itself, the API +The Python API works with Python 3.5+. In addition to Python itself, the API relies on a number of third-party packages. All prerequisites can be installed using Conda_ (recommended), pip_, or through the package manager in most Linux distributions. To run simulations in parallel using MPI, it is recommended to diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index 013a2469e..f0387e066 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -1,25 +1,7 @@ import openmc import openmc.deplete -import numpy as np import matplotlib.pyplot as plt -############################################################################### -# Simulation Input File Parameters -############################################################################### - -# OpenMC simulation parameters -batches = 100 -inactive = 10 -particles = 1000 - -# Depletion simulation parameters -time_step = 1*24*60*60 # s -final_time = 5*24*60*60 # s -time_steps = np.full(final_time // time_step, time_step) - -chain_file = './chain_simple.xml' -power = 174 # W/cm, for 2D simulations only (use W for 3D) - ############################################################################### # Load previous simulation results ############################################################################### @@ -37,31 +19,34 @@ previous_results = openmc.deplete.ResultsList("depletion_results.h5") ############################################################################### # Instantiate a Settings object, set all runtime parameters -settings_file = openmc.Settings() -settings_file.batches = batches -settings_file.inactive = inactive -settings_file.particles = particles +settings = openmc.Settings() +settings.batches = 100 +settings.inactive = 10 +settings.particles = 10000 # Create an initial uniform spatial source distribution over fissionable zones bounds = [-0.62992, -0.62992, -1, 0.62992, 0.62992, 1] uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) -settings_file.source = openmc.source.Source(space=uniform_dist) +settings.source = openmc.source.Source(space=uniform_dist) entropy_mesh = openmc.RegularMesh() entropy_mesh.lower_left = [-0.39218, -0.39218, -1.e50] entropy_mesh.upper_right = [0.39218, 0.39218, 1.e50] entropy_mesh.dimension = [10, 10, 1] -settings_file.entropy_mesh = entropy_mesh +settings.entropy_mesh = entropy_mesh ############################################################################### # Initialize and run depletion calculation ############################################################################### -op = openmc.deplete.Operator(geometry, settings_file, chain_file, - previous_results) +# Create depletion "operator" +chain_file = './chain_simple.xml' +op = openmc.deplete.Operator(geometry, settings, chain_file, previous_results) # Perform simulation using the predictor algorithm -integrator = openmc.deplete.PredictorIntegrator(op, time_steps, power) +time_steps = [1.0, 1.0, 1.0, 1.0, 1.0] # days +power = 174 # W/cm, for 2D simulations only (use W for 3D) +integrator = openmc.deplete.PredictorIntegrator(op, time_steps, power, timestep_units='d') integrator.integrate() ############################################################################### @@ -77,27 +62,28 @@ time, keff = results.get_eigenvalue() # Obtain U235 concentration as a function of time time, n_U235 = results.get_atoms('1', 'U235') -# Obtain Xe135 absorption as a function of time -time, Xe_gam = results.get_reaction_rate('1', 'Xe135', '(n,gamma)') +# Obtain Xe135 capture reaction rate as a function of time +time, Xe_capture = results.get_reaction_rate('1', 'Xe135', '(n,gamma)') ############################################################################### # Generate plots ############################################################################### +days = 24*60*60 plt.figure() -plt.plot(time/(24*60*60), keff, label="K-effective") +plt.plot(time/days, keff, label="K-effective") plt.xlabel("Time (days)") plt.ylabel("Keff") plt.show() plt.figure() -plt.plot(time/(24*60*60), n_U235, label="U 235") +plt.plot(time/days, n_U235, label="U 235") plt.xlabel("Time (days)") plt.ylabel("n U5 (-)") plt.show() plt.figure() -plt.plot(time/(24*60*60), Xe_gam, label="Xe135 absorption") +plt.plot(time/days, Xe_capture, label="Xe135 capture") plt.xlabel("Time (days)") plt.ylabel("RR (-)") plt.show() diff --git a/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py index 9933edd48..ce4b0cf5e 100644 --- a/examples/python/pincell_depletion/run_depletion.py +++ b/examples/python/pincell_depletion/run_depletion.py @@ -1,47 +1,31 @@ +from math import pi + import openmc import openmc.deplete -import numpy as np import matplotlib.pyplot as plt -############################################################################### -# Simulation Input File Parameters -############################################################################### - -# OpenMC simulation parameters -batches = 100 -inactive = 10 -particles = 1000 - -# Depletion simulation parameters -time_step = 1*24*60*60 # s -final_time = 5*24*60*60 # s -time_steps = np.full(final_time // time_step, time_step) -chain_file = './chain_simple.xml' -power = 174 # W/cm, for 2D simulations only (use W for 3D) - ############################################################################### # Define materials ############################################################################### # Instantiate some Materials and register the appropriate Nuclides -uo2 = openmc.Material(material_id=1, name='UO2 fuel at 2.4% wt enrichment') +uo2 = openmc.Material(name='UO2 fuel at 2.4% wt enrichment') uo2.set_density('g/cm3', 10.29769) uo2.add_element('U', 1., enrichment=2.4) uo2.add_element('O', 2.) -uo2.depletable = True -helium = openmc.Material(material_id=2, name='Helium for gap') +helium = openmc.Material(name='Helium for gap') helium.set_density('g/cm3', 0.001598) helium.add_element('He', 2.4044e-4) -zircaloy = openmc.Material(material_id=3, name='Zircaloy 4') +zircaloy = openmc.Material(name='Zircaloy 4') zircaloy.set_density('g/cm3', 6.55) -zircaloy.add_element('Sn', 0.014 , 'wo') +zircaloy.add_element('Sn', 0.014, 'wo') zircaloy.add_element('Fe', 0.00165, 'wo') -zircaloy.add_element('Cr', 0.001 , 'wo') +zircaloy.add_element('Cr', 0.001, 'wo') zircaloy.add_element('Zr', 0.98335, 'wo') -borated_water = openmc.Material(material_id=4, name='Borated water') +borated_water = openmc.Material(name='Borated water') borated_water.set_density('g/cm3', 0.740582) borated_water.add_element('B', 4.0e-5) borated_water.add_element('H', 5.0e-2) @@ -52,87 +36,62 @@ borated_water.add_s_alpha_beta('c_H_in_H2O') # Create geometry ############################################################################### -# Instantiate ZCylinder surfaces -fuel_or = openmc.ZCylinder(surface_id=1, x0=0, y0=0, r=0.39218, name='Fuel OR') -clad_ir = openmc.ZCylinder(surface_id=2, x0=0, y0=0, r=0.40005, name='Clad IR') -clad_or = openmc.ZCylinder(surface_id=3, x0=0, y0=0, r=0.45720, name='Clad OR') -left = openmc.XPlane(surface_id=4, x0=-0.62992, name='left') -right = openmc.XPlane(surface_id=5, x0=0.62992, name='right') -bottom = openmc.YPlane(surface_id=6, y0=-0.62992, name='bottom') -top = openmc.YPlane(surface_id=7, y0=0.62992, name='top') +# Define surfaces +pitch = 1.25984 +fuel_or = openmc.ZCylinder(r=0.39218, name='Fuel OR') +clad_ir = openmc.ZCylinder(r=0.40005, name='Clad IR') +clad_or = openmc.ZCylinder(r=0.45720, name='Clad OR') +box = openmc.model.rectangular_prism(pitch, pitch, boundary_type='reflective') -left.boundary_type = 'reflective' -right.boundary_type = 'reflective' -top.boundary_type = 'reflective' -bottom.boundary_type = 'reflective' +# Define cells +fuel = openmc.Cell(fill=uo2, region=-fuel_or) +gap = openmc.Cell(fill=helium, region=+fuel_or & -clad_ir) +clad = openmc.Cell(fill=zircaloy, region=+clad_ir & -clad_or) +water = openmc.Cell(fill=borated_water, region=+clad_or & box) -# Instantiate Cells -fuel = openmc.Cell(cell_id=1, name='cell 1') -gap = openmc.Cell(cell_id=2, name='cell 2') -clad = openmc.Cell(cell_id=3, name='cell 3') -water = openmc.Cell(cell_id=4, name='cell 4') - -# Use surface half-spaces to define regions -fuel.region = -fuel_or -gap.region = +fuel_or & -clad_ir -clad.region = +clad_ir & -clad_or -water.region = +clad_or & +left & -right & +bottom & -top - -# Register Materials with Cells -fuel.fill = uo2 -gap.fill = helium -clad.fill = zircaloy -water.fill = borated_water - -# Instantiate Universe -root = openmc.Universe(universe_id=0, name='root universe') - -# Register Cells with Universe -root.add_cells([fuel, gap, clad, water]) - -# Instantiate a Geometry, register the root Universe -geometry = openmc.Geometry(root) +# Define overall geometry +geometry = openmc.Geometry([fuel, gap, clad, water]) ############################################################################### # Set volumes of depletable materials ############################################################################### -# Compute cell areas -area = {} -area[fuel] = np.pi * fuel_or.coefficients['r'] ** 2 - -# Set materials volume for depletion. Set to an area for 2D simulations -uo2.volume = area[fuel] +# Set material volume for depletion. For 2D simulations, this should be an area. +uo2.volume = pi * fuel_or.r**2 ############################################################################### # Transport calculation settings ############################################################################### # Instantiate a Settings object, set all runtime parameters, and export to XML -settings_file = openmc.Settings() -settings_file.batches = batches -settings_file.inactive = inactive -settings_file.particles = particles +settings = openmc.Settings() +settings.batches = 100 +settings.inactive = 10 +settings.particles = 1000 # Create an initial uniform spatial source distribution over fissionable zones bounds = [-0.62992, -0.62992, -1, 0.62992, 0.62992, 1] uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) -settings_file.source = openmc.source.Source(space=uniform_dist) +settings.source = openmc.source.Source(space=uniform_dist) entropy_mesh = openmc.RegularMesh() entropy_mesh.lower_left = [-0.39218, -0.39218, -1.e50] entropy_mesh.upper_right = [0.39218, 0.39218, 1.e50] entropy_mesh.dimension = [10, 10, 1] -settings_file.entropy_mesh = entropy_mesh +settings.entropy_mesh = entropy_mesh ############################################################################### # Initialize and run depletion calculation ############################################################################### -op = openmc.deplete.Operator(geometry, settings_file, chain_file) +# Create depletion "operator" +chain_file = './chain_simple.xml' +op = openmc.deplete.Operator(geometry, settings, chain_file) # Perform simulation using the predictor algorithm -integrator = openmc.deplete.PredictorIntegrator(op, time_steps, power) +time_steps = [1.0, 1.0, 1.0, 1.0, 1.0] # days +power = 174 # W/cm, for 2D simulations only (use W for 3D) +integrator = openmc.deplete.PredictorIntegrator(op, time_steps, power, timestep_units='d') integrator.integrate() ############################################################################### @@ -148,27 +107,28 @@ time, keff = results.get_eigenvalue() # Obtain U235 concentration as a function of time time, n_U235 = results.get_atoms('1', 'U235') -# Obtain Xe135 absorption as a function of time -time, Xe_gam = results.get_reaction_rate('1', 'Xe135', '(n,gamma)') +# Obtain Xe135 capture reaction rate as a function of time +time, Xe_capture = results.get_reaction_rate('1', 'Xe135', '(n,gamma)') ############################################################################### # Generate plots ############################################################################### +days = 24*60*60 plt.figure() -plt.plot(time/(24*60*60), keff, label="K-effective") +plt.plot(time/days, keff, label="K-effective") plt.xlabel("Time (days)") plt.ylabel("Keff") plt.show() plt.figure() -plt.plot(time/(24*60*60), n_U235, label="U 235") +plt.plot(time/days, n_U235, label="U235") plt.xlabel("Time (days)") plt.ylabel("n U5 (-)") plt.show() plt.figure() -plt.plot(time/(24*60*60), Xe_gam, label="Xe135 absorption") +plt.plot(time/days, Xe_capture, label="Xe135 capture") plt.xlabel("Time (days)") plt.ylabel("RR (-)") plt.show() diff --git a/examples/xml/custom_source/CMakeLists.txt b/examples/xml/custom_source/CMakeLists.txt new file mode 100644 index 000000000..949817694 --- /dev/null +++ b/examples/xml/custom_source/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.3 FATAL_ERROR) +project(openmc_sources CXX) +add_library(source SHARED source_ring.cpp) +find_package(OpenMC REQUIRED) +if (OpenMC_FOUND) + message(STATUS "Found OpenMC: ${OpenMC_DIR}") +endif() +target_link_libraries(source OpenMC::libopenmc) diff --git a/examples/xml/custom_source/geometry.xml b/examples/xml/custom_source/geometry.xml new file mode 100644 index 000000000..b30884f8c --- /dev/null +++ b/examples/xml/custom_source/geometry.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/xml/custom_source/materials.xml b/examples/xml/custom_source/materials.xml new file mode 100644 index 000000000..606c676df --- /dev/null +++ b/examples/xml/custom_source/materials.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/examples/xml/custom_source/settings.xml b/examples/xml/custom_source/settings.xml new file mode 100644 index 000000000..f935ed685 --- /dev/null +++ b/examples/xml/custom_source/settings.xml @@ -0,0 +1,14 @@ + + + + fixed source + 10 + 0 + 100000 + + + + build/libsource.so + + + diff --git a/examples/xml/custom_source/source_ring.cpp b/examples/xml/custom_source/source_ring.cpp new file mode 100644 index 000000000..d68122dd7 --- /dev/null +++ b/examples/xml/custom_source/source_ring.cpp @@ -0,0 +1,26 @@ +#include // for M_PI + +#include "openmc/random_lcg.h" +#include "openmc/source.h" +#include "openmc/particle.h" + +// you must have external C linkage here otherwise +// dlopen will not find the file +extern "C" openmc::Particle::Bank sample_source(uint64_t* seed) +{ + openmc::Particle::Bank particle; + // wgt + particle.particle = openmc::Particle::Type::neutron; + particle.wgt = 1.0; + // position + double angle = 2. * M_PI * openmc::prn(seed); + double radius = 3.0; + particle.r.x = radius * std::cos(angle); + particle.r.y = radius * std::sin(angle); + particle.r.z = 0.0; + // angle + particle.u = {1.0, 0.0, 0.0}; + particle.E = 14.08e6; + particle.delayed_group = 0; + return particle; +} diff --git a/examples/xml/custom_source/tallies.xml b/examples/xml/custom_source/tallies.xml new file mode 100644 index 000000000..7f6f29926 --- /dev/null +++ b/examples/xml/custom_source/tallies.xml @@ -0,0 +1,17 @@ + + + + + 100 + + + + 0 20.0e6 + + + + 1 2 + flux + + + diff --git a/include/openmc/distribution.h b/include/openmc/distribution.h index f9d718cdb..92c10aed3 100644 --- a/include/openmc/distribution.h +++ b/include/openmc/distribution.h @@ -62,6 +62,9 @@ public: //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample(uint64_t* seed) const; + + double a() const { return a_; } + double b() const { return b_; } private: double a_; //!< Lower bound of distribution double b_; //!< Upper bound of distribution @@ -80,6 +83,8 @@ public: //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample(uint64_t* seed) const; + + double theta() const { return theta_; } private: double theta_; //!< Factor in exponential [eV] }; @@ -97,6 +102,9 @@ public: //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample(uint64_t* seed) const; + + double a() const { return a_; } + double b() const { return b_; } private: double a_; //!< Factor in exponential [eV] double b_; //!< Factor in square root [1/eV] @@ -115,6 +123,9 @@ public: //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample(uint64_t* seed) const; + + double mean_value() const { return mean_value_; } + double std_dev() const { return std_dev_; } private: double mean_value_; //!< middle of distribution [eV] double std_dev_; //!< standard deviation [eV] @@ -134,6 +145,10 @@ public: //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample(uint64_t* seed) const; + + double e0() const { return e0_; } + double m_rat() const { return m_rat_; } + double kt() const { return kt_; } private: // example DT fusion m_rat = 5 (D = 2 + T = 3) // ion temp = 20000 eV @@ -161,6 +176,8 @@ public: // x property std::vector& x() { return x_; } const std::vector& x() const { return x_; } + const std::vector& p() const { return p_; } + Interpolation interp() const { return interp_; } private: std::vector x_; //!< tabulated independent variable std::vector p_; //!< tabulated probability density @@ -188,6 +205,8 @@ public: //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample(uint64_t* seed) const; + + const std::vector& x() const { return x_; } private: std::vector x_; //! Possible outcomes }; diff --git a/include/openmc/hdf5_interface.h b/include/openmc/hdf5_interface.h index ee3419fa2..ddb2319ae 100644 --- a/include/openmc/hdf5_interface.h +++ b/include/openmc/hdf5_interface.h @@ -26,13 +26,18 @@ namespace openmc { //============================================================================== void read_attr(hid_t obj_id, const char* name, hid_t mem_type_id, - void* buffer); + void* buffer); + void write_attr(hid_t obj_id, int ndim, const hsize_t* dims, const char* name, - hid_t mem_type_id, const void* buffer); -void read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id, - void* buffer, bool indep); -void write_dataset(hid_t group_id, int ndim, const hsize_t* dims, const char* name, - hid_t mem_type_id, const void* buffer, bool indep); + hid_t mem_type_id, const void* buffer); + +void read_dataset_lowlevel(hid_t obj_id, const char* name, hid_t mem_type_id, + hid_t mem_space_id, bool indep, void* buffer); + +void write_dataset_lowlevel(hid_t group_id, int ndim, const hsize_t* dims, + const char* name, hid_t mem_type_id, hid_t mem_space_id, bool indep, + const void* buffer); + bool using_mpio_device(hid_t obj_id); //============================================================================== @@ -86,34 +91,32 @@ extern "C" { void read_attr_string(hid_t obj_id, const char* name, size_t slen, char* buffer); void read_complex(hid_t obj_id, const char* name, - std::complex* buffer, bool indep); - void read_double(hid_t obj_id, const char* name, double* buffer, - bool indep); - void read_int(hid_t obj_id, const char* name, int* buffer, - bool indep); + std::complex* buffer, bool indep); + void read_double(hid_t obj_id, const char* name, double* buffer, bool indep); + void read_int(hid_t obj_id, const char* name, int* buffer, bool indep); void read_llong(hid_t obj_id, const char* name, long long* buffer, - bool indep); - void read_string(hid_t obj_id, const char* name, size_t slen, - char* buffer, bool indep); + bool indep); + void read_string(hid_t obj_id, const char* name, size_t slen, char* buffer, + bool indep); - void read_tally_results(hid_t group_id, hsize_t n_filter, - hsize_t n_score, double* results); + void read_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, + double* results); void write_attr_double(hid_t obj_id, int ndim, const hsize_t* dims, - const char* name, const double* buffer); + const char* name, const double* buffer); void write_attr_int(hid_t obj_id, int ndim, const hsize_t* dims, - const char* name, const int* buffer); + const char* name, const int* buffer); void write_attr_string(hid_t obj_id, const char* name, const char* buffer); void write_double(hid_t group_id, int ndim, const hsize_t* dims, - const char* name, const double* buffer, bool indep); + const char* name, const double* buffer, bool indep); void write_int(hid_t group_id, int ndim, const hsize_t* dims, - const char* name, const int* buffer, bool indep); + const char* name, const int* buffer, bool indep); void write_llong(hid_t group_id, int ndim, const hsize_t* dims, - const char* name, const long long* buffer, bool indep); + const char* name, const long long* buffer, bool indep); void write_string(hid_t group_id, int ndim, const hsize_t* dims, size_t slen, - const char* name, char const* buffer, bool indep); + const char* name, char const* buffer, bool indep); void write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, - const double* results); + const double* results); } // extern "C" //============================================================================== @@ -233,7 +236,8 @@ template inline std::enable_if_t>::value> read_dataset(hid_t obj_id, const char* name, T& buffer, bool indep=false) { - read_dataset(obj_id, name, H5TypeMap::type_id, &buffer, indep); + read_dataset_lowlevel(obj_id, name, H5TypeMap::type_id, H5S_ALL, indep, + &buffer); } // overload for std::string @@ -251,9 +255,11 @@ read_dataset(hid_t obj_id, const char* name, std::string& str, bool indep=false) // array version template inline void -read_dataset(hid_t dset, const char* name, std::array& buffer, bool indep=false) +read_dataset(hid_t dset, const char* name, std::array& buffer, + bool indep=false) { - read_dataset(dset, name, H5TypeMap::type_id, buffer.data(), indep); + read_dataset_lowlevel(dset, name, H5TypeMap::type_id, H5S_ALL, indep, + buffer.data()); } // vector version @@ -267,11 +273,13 @@ void read_dataset(hid_t dset, std::vector& vec, bool indep=false) vec.resize(shape[0]); // Read data into vector - read_dataset(dset, nullptr, H5TypeMap::type_id, vec.data(), indep); + read_dataset_lowlevel(dset, nullptr, H5TypeMap::type_id, H5S_ALL, indep, + vec.data()); } template -void read_dataset(hid_t obj_id, const char* name, std::vector& vec, bool indep=false) +void read_dataset(hid_t obj_id, const char* name, std::vector& vec, + bool indep=false) { hid_t dset = open_dataset(obj_id, name); read_dataset(dset, vec, indep); @@ -291,14 +299,17 @@ void read_dataset(hid_t dset, xt::xarray& arr, bool indep=false) arr.resize(shape); // Read data from attribute - read_dataset(dset, nullptr, H5TypeMap::type_id, arr.data(), indep); + read_dataset_lowlevel(dset, nullptr, H5TypeMap::type_id, H5S_ALL, indep, + arr.data()); } template<> -void read_dataset(hid_t dset, xt::xarray>& arr, bool indep); +void read_dataset(hid_t dset, xt::xarray>& arr, + bool indep); template -void read_dataset(hid_t obj_id, const char* name, xt::xarray& arr, bool indep=false) +void read_dataset(hid_t obj_id, const char* name, xt::xarray& arr, + bool indep=false) { // Open dataset and read array hid_t dset = open_dataset(obj_id, name); @@ -308,7 +319,8 @@ void read_dataset(hid_t obj_id, const char* name, xt::xarray& arr, bool indep template -void read_dataset(hid_t obj_id, const char* name, xt::xtensor& arr, bool indep=false) +void read_dataset(hid_t obj_id, const char* name, xt::xtensor& arr, + bool indep=false) { // Open dataset and read array hid_t dset = open_dataset(obj_id, name); @@ -346,7 +358,7 @@ read_dataset(hid_t obj_id, const char* name, Position& r, bool indep=false) template inline void read_dataset_as_shape(hid_t obj_id, const char* name, - xt::xtensor& arr, bool indep=false) + xt::xtensor& arr, bool indep=false) { hid_t dset = open_dataset(obj_id, name); @@ -357,7 +369,8 @@ inline void read_dataset_as_shape(hid_t obj_id, const char* name, std::vector buffer(size); // Read data from attribute - read_dataset(dset, nullptr, H5TypeMap::type_id, buffer.data(), indep); + read_dataset_lowlevel(dset, nullptr, H5TypeMap::type_id, H5S_ALL, indep, + buffer.data()); // Adapt into xarray arr = xt::adapt(buffer, arr.shape()); @@ -367,8 +380,8 @@ inline void read_dataset_as_shape(hid_t obj_id, const char* name, template -inline void read_nd_vector(hid_t obj_id, const char* name, xt::xtensor& result, - bool must_have=false) +inline void read_nd_vector(hid_t obj_id, const char* name, + xt::xtensor& result, bool must_have=false) { if (object_exists(obj_id, name)) { read_dataset_as_shape(obj_id, name, result, true); @@ -431,7 +444,8 @@ template inline std::enable_if_t>::value> write_dataset(hid_t obj_id, const char* name, T buffer) { - write_dataset(obj_id, 0, nullptr, name, H5TypeMap::type_id, &buffer, false); + write_dataset_lowlevel(obj_id, 0, nullptr, name, H5TypeMap::type_id, + H5S_ALL, false, &buffer); } inline void @@ -444,11 +458,13 @@ template inline void write_dataset(hid_t obj_id, const char* name, const std::array& buffer) { hsize_t dims[] {N}; - write_dataset(obj_id, 1, dims, name, H5TypeMap::type_id, buffer.data(), false); + write_dataset_lowlevel(obj_id, 1, dims, name, H5TypeMap::type_id, + H5S_ALL, false, buffer.data()); } inline void -write_dataset(hid_t obj_id, const char* name, const std::vector& buffer) +write_dataset(hid_t obj_id, const char* name, + const std::vector& buffer) { auto n {buffer.size()}; hsize_t dims[] {n}; @@ -477,7 +493,8 @@ template inline void write_dataset(hid_t obj_id, const char* name, const std::vector& buffer) { hsize_t dims[] {buffer.size()}; - write_dataset(obj_id, 1, dims, name, H5TypeMap::type_id, buffer.data(), false); + write_dataset_lowlevel(obj_id, 1, dims, name, H5TypeMap::type_id, + H5S_ALL, false, buffer.data()); } // Template for xarray, xtensor, etc. @@ -487,8 +504,8 @@ write_dataset(hid_t obj_id, const char* name, const xt::xcontainer& arr) using T = typename D::value_type; auto s = arr.shape(); std::vector dims {s.cbegin(), s.cend()}; - write_dataset(obj_id, dims.size(), dims.data(), name, H5TypeMap::type_id, - arr.data(), false); + write_dataset_lowlevel(obj_id, dims.size(), dims.data(), name, + H5TypeMap::type_id, H5S_ALL, false, arr.data()); } inline void diff --git a/include/openmc/particle.h b/include/openmc/particle.h index 8143ee5a3..2917b45e4 100644 --- a/include/openmc/particle.h +++ b/include/openmc/particle.h @@ -34,12 +34,6 @@ namespace openmc { // use to store the bins for delayed group tallies. constexpr int MAX_DELAYED_GROUPS {8}; -// Maximum number of lost particles -constexpr int MAX_LOST_PARTICLES {10}; - -// Maximum number of lost particles, relative to the total number of particles -constexpr double REL_MAX_LOST_PARTICLES {1.0e-6}; - constexpr double CACHE_INVALID {-1.0}; //============================================================================== diff --git a/include/openmc/plot.h b/include/openmc/plot.h index 04db9710a..e05d6f2e2 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -11,6 +11,7 @@ #include "openmc/position.h" #include "openmc/constants.h" #include "openmc/cell.h" +#include "openmc/error.h" #include "openmc/geometry.h" #include "openmc/particle.h" #include "openmc/xml_interface.h" @@ -154,10 +155,8 @@ T PlotBase::get_map() const { in_i = 1; out_i = 2; break; -#ifdef __GNUC__ default: - __builtin_unreachable(); -#endif + UNREACHABLE(); } // set initial position diff --git a/include/openmc/settings.h b/include/openmc/settings.h index cbc0c488b..494cfd1a2 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -60,13 +60,16 @@ extern std::string path_input; //!< directory where main .xml files r extern std::string path_output; //!< directory where output files are written extern std::string path_particle_restart; //!< path to a particle restart file extern std::string path_source; +extern std::string path_source_library; //!< path to the source shared object extern std::string path_sourcepoint; //!< path to a source file extern "C" std::string path_statepoint; //!< path to a statepoint file -extern "C" int32_t n_batches; //!< number of (inactive+active) batches -extern "C" int32_t n_inactive; //!< number of inactive batches -extern "C" int32_t gen_per_batch; //!< number of generations per batch -extern "C" int64_t n_particles; //!< number of particles per generation +extern "C" int32_t n_batches; //!< number of (inactive+active) batches +extern "C" int32_t n_inactive; //!< number of inactive batches +extern "C" int32_t max_lost_particles; //!< maximum number of lost particles +extern double rel_max_lost_particles; //!< maximum number of lost particles, relative to the total number of particles +extern "C" int32_t gen_per_batch; //!< number of generations per batch +extern "C" int64_t n_particles; //!< number of particles per generation extern int64_t max_particles_in_flight; //!< Max num. event-based particles in flight diff --git a/include/openmc/source.h b/include/openmc/source.h index a177995ea..4db17f241 100644 --- a/include/openmc/source.h +++ b/include/openmc/source.h @@ -68,6 +68,9 @@ Particle::Bank sample_external_source(uint64_t* seed); //! Fill source bank at end of generation for fixed source simulations void fill_source_bank_fixedsource(); +//! Fill source bank at the end of a generation for dlopen based source simulation +void fill_source_bank_custom_source(); + void free_memory_source(); } // namespace openmc diff --git a/include/openmc/volume_calc.h b/include/openmc/volume_calc.h index e025ab3b0..3258be9d6 100644 --- a/include/openmc/volume_calc.h +++ b/include/openmc/volume_calc.h @@ -7,6 +7,7 @@ #include "pugixml.hpp" #include "xtensor/xtensor.hpp" +#include #include #include #include diff --git a/openmc/data/photon.py b/openmc/data/photon.py index 32ed3010a..9cc697b78 100644 --- a/openmc/data/photon.py +++ b/openmc/data/photon.py @@ -784,8 +784,9 @@ class IncidentPhoton(EqualityMixin): sub_group = shell_group.create_group(key) # Write atomic relaxation - if key in self.atomic_relaxation.subshells: - self.atomic_relaxation.to_hdf5(sub_group, key) + if self.atomic_relaxation is not None: + if key in self.atomic_relaxation.subshells: + self.atomic_relaxation.to_hdf5(sub_group, key) else: continue diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 10976dd0e..31479f995 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -31,6 +31,10 @@ __all__ = [ "Integrator", "SIIntegrator", "DepSystemSolver"] +_SECONDS_PER_MINUTE = 60 +_SECONDS_PER_HOUR = 60*60 +_SECONDS_PER_DAY = 24*60*60 + OperatorResult = namedtuple('OperatorResult', ['k', 'rates']) OperatorResult.__doc__ = """\ Result of applying transport operator @@ -597,9 +601,11 @@ class Integrator(ABC): ---------- operator : openmc.deplete.TransportOperator Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. power : float or iterable of float, optional Power of the reactor in [W]. A single value indicates that the power is constant over all timesteps. An iterable @@ -612,6 +618,11 @@ class Integrator(ABC): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. + timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, and 'MWd/kg' indicates + that the values are given in burnup (MW-d of energy deposited per + kilogram of initial heavy metal). Attributes ---------- @@ -625,7 +636,8 @@ class Integrator(ABC): Power of the reactor in [W] for each interval in :attr:`timesteps` """ - def __init__(self, operator, timesteps, power=None, power_density=None): + def __init__(self, operator, timesteps, power=None, power_density=None, + timestep_units='s'): # Check number of stages previously used if operator.prev_res is not None: res = operator.prev_res[-1] @@ -638,27 +650,59 @@ class Integrator(ABC): self._num_stages)) self.operator = operator self.chain = operator.chain - if not isinstance(timesteps, Iterable): - self.timesteps = [timesteps] - else: - self.timesteps = timesteps + + # Determine power and normalize units to W if power is None: if power_density is None: raise ValueError("Either power or power density must be set") if not isinstance(power_density, Iterable): power = power_density * operator.heavy_metal else: - power = [p * operator.heavy_metal for p in power_density] - + power = [p*operator.heavy_metal for p in power_density] if not isinstance(power, Iterable): # Ensure that power is single value if that is the case - power = [power] * len(self.timesteps) - elif len(power) != len(self.timesteps): - raise ValueError( - "Number of time steps != number of powers. {} vs {}".format( - len(self.timesteps), len(power))) + power = [power] * len(timesteps) - self.power = power + if len(power) != len(timesteps): + raise ValueError( + "Number of time steps ({}) != number of powers ({})".format( + len(timesteps), len(power))) + + # Get list of times / units + if isinstance(timesteps[0], Iterable): + times, units = zip(*timesteps) + else: + times = timesteps + units = [timestep_units] * len(timesteps) + + # Determine number of seconds for each timestep + seconds = [] + for time, unit, watts in zip(times, units, power): + # Make sure values passed make sense + check_type('timestep', time, Real) + check_greater_than('timestep', time, 0.0, False) + check_type('timestep units', unit, str) + check_type('power', watts, Real) + check_greater_than('power', watts, 0.0, True) + + if unit in ('s', 'sec'): + seconds.append(time) + elif unit in ('min', 'minute'): + seconds.append(time*_SECONDS_PER_MINUTE) + elif unit in ('h', 'hr', 'hour'): + seconds.append(time*_SECONDS_PER_HOUR) + elif unit in ('d', 'day'): + seconds.append(time*_SECONDS_PER_DAY) + elif unit.lower() == 'mwd/kg': + watt_days_per_kg = 1e6*time + kilograms = 1e-3*operator.heavy_metal + days = watt_days_per_kg * kilograms / watts + seconds.append(days*_SECONDS_PER_DAY) + else: + raise ValueError("Invalid timestep unit '{}'".format(unit)) + + self.timesteps = asarray(seconds) + self.power = asarray(power) @abstractmethod def __call__(self, conc, rates, dt, power, i): @@ -772,9 +816,11 @@ class SIIntegrator(Integrator): ---------- operator : openmc.deplete.TransportOperator The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. power : float or iterable of float, optional Power of the reactor in [W]. A single value indicates that the power is constant over all timesteps. An iterable @@ -787,6 +833,11 @@ class SIIntegrator(Integrator): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. + timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, and 'MWd/kg' indicates + that the values are given in burnup (MW-d of energy deposited per + kilogram of initial heavy metal). n_steps : int, optional Number of stochastic iterations per depletion interval. Must be greater than zero. Default : 10 @@ -805,10 +856,10 @@ class SIIntegrator(Integrator): Number of stochastic iterations per depletion interval """ def __init__(self, operator, timesteps, power=None, power_density=None, - n_steps=10): + timestep_units='s', n_steps=10): check_type("n_steps", n_steps, Integral) check_greater_than("n_steps", n_steps, 0) - super().__init__(operator, timesteps, power, power_density) + super().__init__(operator, timesteps, power, power_density, timestep_units) self.n_steps = n_steps def _get_bos_data_from_operator(self, step_index, step_power, bos_conc): diff --git a/openmc/deplete/integrators.py b/openmc/deplete/integrators.py index 67106aa3e..38a7c33b1 100644 --- a/openmc/deplete/integrators.py +++ b/openmc/deplete/integrators.py @@ -31,9 +31,11 @@ class PredictorIntegrator(Integrator): ---------- operator : openmc.deplete.TransportOperator Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. power : float or iterable of float, optional Power of the reactor in [W]. A single value indicates that the power is constant over all timesteps. An iterable @@ -46,6 +48,11 @@ class PredictorIntegrator(Integrator): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. + timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, and 'MWd/kg' indicates + that the values are given in burnup (MW-d of energy deposited per + kilogram of initial heavy metal). Attributes ---------- @@ -113,9 +120,11 @@ class CECMIntegrator(Integrator): ---------- operator : openmc.deplete.TransportOperator Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. power : float or iterable of float, optional Power of the reactor in [W]. A single value indicates that the power is constant over all timesteps. An iterable @@ -128,6 +137,11 @@ class CECMIntegrator(Integrator): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. + timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, and 'MWd/kg' indicates + that the values are given in burnup (MW-d of energy deposited per + kilogram of initial heavy metal). Attributes ---------- @@ -203,9 +217,11 @@ class CF4Integrator(Integrator): ---------- operator : openmc.deplete.TransportOperator Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. power : float or iterable of float, optional Power of the reactor in [W]. A single value indicates that the power is constant over all timesteps. An iterable @@ -218,6 +234,11 @@ class CF4Integrator(Integrator): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. + timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, and 'MWd/kg' indicates + that the values are given in burnup (MW-d of energy deposited per + kilogram of initial heavy metal). Attributes ---------- @@ -310,9 +331,11 @@ class CELIIntegrator(Integrator): ---------- operator : openmc.deplete.TransportOperator Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. power : float or iterable of float, optional Power of the reactor in [W]. A single value indicates that the power is constant over all timesteps. An iterable @@ -325,6 +348,11 @@ class CELIIntegrator(Integrator): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. + timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, and 'MWd/kg' indicates + that the values are given in burnup (MW-d of energy deposited per + kilogram of initial heavy metal). Attributes ---------- @@ -404,9 +432,11 @@ class EPCRK4Integrator(Integrator): ---------- operator : openmc.deplete.TransportOperator Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. power : float or iterable of float, optional Power of the reactor in [W]. A single value indicates that the power is constant over all timesteps. An iterable @@ -419,6 +449,11 @@ class EPCRK4Integrator(Integrator): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. + timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, and 'MWd/kg' indicates + that the values are given in burnup (MW-d of energy deposited per + kilogram of initial heavy metal). Attributes ---------- @@ -518,9 +553,11 @@ class LEQIIntegrator(Integrator): ---------- operator : openmc.deplete.TransportOperator Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. power : float or iterable of float, optional Power of the reactor in [W]. A single value indicates that the power is constant over all timesteps. An iterable @@ -533,6 +570,11 @@ class LEQIIntegrator(Integrator): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. + timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, and 'MWd/kg' indicates + that the values are given in burnup (MW-d of energy deposited per + kilogram of initial heavy metal). Attributes ---------- @@ -629,9 +671,11 @@ class SICELIIntegrator(SIIntegrator): ---------- operator : openmc.deplete.TransportOperator The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. power : float or iterable of float, optional Power of the reactor in [W]. A single value indicates that the power is constant over all timesteps. An iterable @@ -644,6 +688,11 @@ class SICELIIntegrator(SIIntegrator): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. + timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, and 'MWd/kg' indicates + that the values are given in burnup (MW-d of energy deposited per + kilogram of initial heavy metal). n_steps : int, optional Number of stochastic iterations per depletion interval. Must be greater than zero. Default : 10 @@ -730,9 +779,11 @@ class SILEQIIntegrator(SIIntegrator): ---------- operator : openmc.deplete.TransportOperator The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. + timesteps : iterable of float or iterable of tuple + Array of timesteps. Note that values are not cumulative. The units are + specified by the `timestep_units` argument when `timesteps` is an + iterable of float. Alternatively, units can be specified for each step + by passing an iterable of (value, unit) tuples. power : float or iterable of float, optional Power of the reactor in [W]. A single value indicates that the power is constant over all timesteps. An iterable @@ -745,6 +796,11 @@ class SILEQIIntegrator(SIIntegrator): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. + timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'} + Units for values specified in the `timesteps` argument. 's' means + seconds, 'min' means minutes, 'h' means hours, and 'MWd/kg' indicates + that the values are given in burnup (MW-d of energy deposited per + kilogram of initial heavy metal). n_steps : int, optional Number of stochastic iterations per depletion interval. Must be greater than zero. Default : 10 diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 7e352fbf0..0aae5adb7 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -533,6 +533,7 @@ class FissionYield(Mapping): return zip(self.products, self.yields) def __add__(self, other): + """Add one set of fission yields to this set, return new yields""" if not isinstance(other, FissionYield): return NotImplemented new = FissionYield(self.products, self.yields.copy()) @@ -550,12 +551,14 @@ class FissionYield(Mapping): return self + other def __imul__(self, scalar): + """Scale these fission yields by a real scalar""" if not isinstance(scalar, Real): return NotImplemented self.yields *= scalar return self def __mul__(self, scalar): + """Return a new set of yields scaled by a real scalar""" if not isinstance(scalar, Real): return NotImplemented new = FissionYield(self.products, self.yields.copy()) @@ -568,3 +571,8 @@ class FissionYield(Mapping): def __repr__(self): return "<{} containing {} products and yields>".format( self.__class__.__name__, len(self)) + + # Avoid greedy numpy operations like np.float64 * fission_yield + # converting this to an array on the fly. Force __rmul__ and + # __radd__. See issue #1492 + __array_ufunc__ = None diff --git a/openmc/lib/core.py b/openmc/lib/core.py index dcd896fa6..6a2a7cdea 100644 --- a/openmc/lib/core.py +++ b/openmc/lib/core.py @@ -336,9 +336,23 @@ def source_bank(): n = c_int64() _dll.openmc_source_bank(ptr, n) - # Convert to numpy array with appropriate datatype - bank_dtype = np.dtype(_Bank) - return as_array(ptr, (n.value,)).view(bank_dtype) + try: + # Convert to numpy array with appropriate datatype + bank_dtype = np.dtype(_Bank) + return as_array(ptr, (n.value,)).view(bank_dtype) + + except ValueError as err: + # If a known numpy error was raised (github.com/numpy/numpy/issues + # /14214), re-raise with a more helpful error message. + if len(err.args) == 0: + raise err + if err.args[0].startswith('invalid shape in fixed-type tuple'): + raise ValueError('The source bank is too large to access via ' + 'openmc.lib with this version of numpy. Use a different ' + 'version of numpy or reduce the bank size (fewer particles ' + 'per MPI process) so that it is smaller than 2 GB.') from err + else: + raise err def statepoint_write(filename=None, write_source=True): diff --git a/openmc/lib/settings.py b/openmc/lib/settings.py index 275c73a93..68d68c29e 100644 --- a/openmc/lib/settings.py +++ b/openmc/lib/settings.py @@ -22,6 +22,8 @@ class _Settings(object): entropy_on = _DLLGlobal(c_bool, 'entropy_on') generations_per_batch = _DLLGlobal(c_int32, 'gen_per_batch') inactive = _DLLGlobal(c_int32, 'n_inactive') + max_lost_particles = _DLLGlobal(c_int32, 'max_lost_particles') + rel_max_lost_particles = _DLLGlobal(c_double, 'rel_max_lost_particles') particles = _DLLGlobal(c_int64, 'n_particles') restart_run = _DLLGlobal(c_bool, 'restart_run') run_CE = _DLLGlobal(c_bool, 'run_CE') diff --git a/openmc/model/funcs.py b/openmc/model/funcs.py index 3ee505240..9999c383f 100644 --- a/openmc/model/funcs.py +++ b/openmc/model/funcs.py @@ -373,52 +373,7 @@ def get_hexagonal_prism(*args, **kwargs): return hexagonal_prism(*args, **kwargs) -def cylinder_from_points(p1, p2, r, **kwargs): - """Return cylinder defined by two points passing through its center. - - Parameters - ---------- - p1, p2 : 3-tuples - Coordinates of two points that pass through the center of the cylinder - r : float - Radius of the cylinder - kwargs : dict - Keyword arguments passed to the :class:`openmc.Quadric` constructor - - Returns - ------- - openmc.Quadric - Quadric surface representing the cylinder. - - """ - # Get x, y, z coordinates of two points - x1, y1, z1 = p1 - x2, y2, z2 = p2 - - # Define intermediate terms - dx = x2 - x1 - dy = y2 - y1 - dz = z2 - z1 - cx = y1*z2 - y2*z1 - cy = x2*z1 - x1*z2 - cz = x1*y2 - x2*y1 - - # Given p=(x,y,z), p1=(x1, y1, z1), p2=(x2, y2, z2), the equation for the - # cylinder can be derived as r = |(p - p1) ⨯ (p - p2)| / |p2 - p1|. - # Expanding out all terms and grouping according to what Quadric expects - # gives the following coefficients. - kwargs['a'] = dy*dy + dz*dz - kwargs['b'] = dx*dx + dz*dz - kwargs['c'] = dx*dx + dy*dy - kwargs['d'] = -2*dx*dy - kwargs['e'] = -2*dy*dz - kwargs['f'] = -2*dx*dz - kwargs['g'] = 2*(cy*dz - cz*dy) - kwargs['h'] = 2*(cz*dx - cx*dz) - kwargs['j'] = 2*(cx*dy - cy*dx) - kwargs['k'] = cx*cx + cy*cy + cz*cz - (dx*dx + dy*dy + dz*dz)*r*r - - return Quadric(**kwargs) +cylinder_from_points = Cylinder.from_points def subdivide(surfaces): diff --git a/openmc/settings.py b/openmc/settings.py index 3730d7bd7..794c97b61 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -58,6 +58,10 @@ class Settings(object): history-based parallelism. generations_per_batch : int Number of generations per batch + max_lost_particles : int + Maximum number of lost particles + rel_max_lost_particles : int + Maximum number of lost particles, relative to the total number of particles inactive : int Number of inactive batches keff_trigger : dict @@ -176,6 +180,8 @@ class Settings(object): self._batches = None self._generations_per_batch = None self._inactive = None + self._max_lost_particles = None + self._rel_max_lost_particles = None self._particles = None self._keff_trigger = None @@ -254,6 +260,14 @@ class Settings(object): def inactive(self): return self._inactive + @property + def max_lost_particles(self): + return self._max_lost_particles + + @property + def rel_max_lost_particles(self): + return self._rel_max_lost_particles + @property def particles(self): return self._particles @@ -385,11 +399,11 @@ class Settings(object): @property def dagmc(self): return self._dagmc - + @property def event_based(self): return self._event_based - + @property def max_particles_in_flight(self): return self._max_particles_in_flight @@ -417,6 +431,19 @@ class Settings(object): cv.check_greater_than('inactive batches', inactive, 0, True) self._inactive = inactive + @max_lost_particles.setter + def max_lost_particles(self, max_lost_particles): + cv.check_type('max_lost_particles', max_lost_particles, Integral) + cv.check_greater_than('max_lost_particles', max_lost_particles, 0) + self._max_lost_particles = max_lost_particles + + @rel_max_lost_particles.setter + def rel_max_lost_particles(self, rel_max_lost_particles): + cv.check_type('rel_max_lost_particles', rel_max_lost_particles, Real) + cv.check_greater_than('rel_max_lost_particles', rel_max_lost_particles, 0) + cv.check_less_than('rel_max_lost_particles', rel_max_lost_particles, 1) + self._rel_max_lost_particles = rel_max_lost_particles + @particles.setter def particles(self, particles): cv.check_type('particles', particles, Integral) @@ -721,12 +748,12 @@ class Settings(object): def delayed_photon_scaling(self, value): cv.check_type('delayed photon scaling', value, bool) self._delayed_photon_scaling = value - + @event_based.setter def event_based(self, value): cv.check_type('event based', value, bool) self._event_based = value - + @max_particles_in_flight.setter def max_particles_in_flight(self, value): cv.check_type('max particles in flight', value, Integral) @@ -763,6 +790,16 @@ class Settings(object): element = ET.SubElement(root, "inactive") element.text = str(self._inactive) + def _create_max_lost_particles_subelement(self, root): + if self._max_lost_particles is not None: + element = ET.SubElement(root, "max_lost_particles") + element.text = str(self._max_lost_particles) + + def _create_rel_max_lost_particles_subelement(self, root): + if self._rel_max_lost_particles is not None: + element = ET.SubElement(root, "rel_max_lost_particles") + element.text = str(self._rel_max_lost_particles) + def _create_particles_subelement(self, root): if self._particles is not None: element = ET.SubElement(root, "particles") @@ -976,12 +1013,12 @@ class Settings(object): if self._delayed_photon_scaling is not None: elem = ET.SubElement(root, "delayed_photon_scaling") elem.text = str(self._delayed_photon_scaling).lower() - + def _create_event_based_subelement(self, root): if self._event_based is not None: elem = ET.SubElement(root, "event_based") elem.text = str(self._event_based).lower() - + def _create_max_particles_in_flight_subelement(self, root): if self._max_particles_in_flight is not None: elem = ET.SubElement(root, "max_particles_in_flight") @@ -1009,6 +1046,8 @@ class Settings(object): self._particles_from_xml_element(elem) self._batches_from_xml_element(elem) self._inactive_from_xml_element(elem) + self._max_lost_particles_from_xml_element(elem) + self._rel_max_lost_particles_from_xml_element(elem) self._generations_per_batch_from_xml_element(elem) def _run_mode_from_xml_element(self, root): @@ -1031,6 +1070,16 @@ class Settings(object): if text is not None: self.inactive = int(text) + def _max_lost_particles_from_xml_element(self, root): + text = get_text(root, 'max_lost_particles') + if text is not None: + self.max_lost_particles = int(text) + + def _rel_max_lost_particles_from_xml_element(self, root): + text = get_text(root, 'rel_max_lost_particles') + if text is not None: + self.rel_max_lost_particles = float(text) + def _generations_per_batch_from_xml_element(self, root): text = get_text(root, 'generations_per_batch') if text is not None: @@ -1056,7 +1105,7 @@ class Settings(object): if value is not None: if key in ('summary', 'tallies'): value = value in ('true', '1') - self.output[key] = value + self.output[key] = value def _statepoint_from_xml_element(self, root): elem = root.find('state_point') @@ -1227,12 +1276,12 @@ class Settings(object): text = get_text(root, 'delayed_photon_scaling') if text is not None: self.delayed_photon_scaling = text in ('true', '1') - + def _event_based_from_xml_element(self, root): text = get_text(root, 'event_based') if text is not None: self.event_based = text in ('true', '1') - + def _max_particles_in_flight_from_xml_element(self, root): text = get_text(root, 'max_particles_in_flight') if text is not None: @@ -1270,6 +1319,8 @@ class Settings(object): self._create_particles_subelement(root_element) self._create_batches_subelement(root_element) self._create_inactive_subelement(root_element) + self._create_max_lost_particles_subelement(root_element) + self._create_rel_max_lost_particles_subelement(root_element) self._create_generations_per_batch_subelement(root_element) self._create_keff_trigger_subelement(root_element) self._create_source_subelement(root_element) @@ -1340,6 +1391,8 @@ class Settings(object): settings._particles_from_xml_element(root) settings._batches_from_xml_element(root) settings._inactive_from_xml_element(root) + settings._max_lost_particles_from_xml_element(root) + settings._rel_max_lost_particles_from_xml_element(root) settings._generations_per_batch_from_xml_element(root) settings._keff_trigger_from_xml_element(root) settings._source_from_xml_element(root) diff --git a/openmc/source.py b/openmc/source.py index 88c2f8611..7b709321f 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -8,20 +8,22 @@ from openmc.stats.multivariate import UnitSphere, Spatial import openmc.checkvalue as cv -class Source(object): +class Source: """Distribution of phase space coordinates for source sites. Parameters ---------- - space : openmc.stats.Spatial, optional + space : openmc.stats.Spatial Spatial distribution of source sites - angle : openmc.stats.UnitSphere, optional + angle : openmc.stats.UnitSphere Angular distribution of source sites - energy : openmc.stats.Univariate, optional + energy : openmc.stats.Univariate Energy distribution of source sites - filename : str, optional + filename : str Source file from which sites should be sampled - strength : Real + library : str + Path to a custom source library + strength : float Strength of the source particle : {'neutron', 'photon'} Source particle type @@ -36,7 +38,9 @@ class Source(object): Energy distribution of source sites file : str or None Source file from which sites should be sampled - strength : Real + library : str or None + Path to a custom source library + strength : float Strength of the source particle : {'neutron', 'photon'} Source particle type @@ -44,11 +48,12 @@ class Source(object): """ def __init__(self, space=None, angle=None, energy=None, filename=None, - strength=1.0, particle='neutron'): + library=None, strength=1.0, particle='neutron'): self._space = None self._angle = None self._energy = None self._file = None + self._library = None if space is not None: self.space = space @@ -58,6 +63,8 @@ class Source(object): self.energy = energy if filename is not None: self.file = filename + if library is not None: + self.library = library self.strength = strength self.particle = particle @@ -65,6 +72,10 @@ class Source(object): def file(self): return self._file + @property + def library(self): + return self._library + @property def space(self): return self._space @@ -90,6 +101,11 @@ class Source(object): cv.check_type('source file', filename, str) self._file = filename + @library.setter + def library(self, library_name): + cv.check_type('library', library_name, str) + self._library = library_name + @space.setter def space(self, space): cv.check_type('spatial distribution', space, Spatial) @@ -131,6 +147,8 @@ class Source(object): element.set("particle", self.particle) if self.file is not None: element.set("file", self.file) + if self.library is not None: + element.set("library", self.library) if self.space is not None: element.append(self.space.to_xml_element()) if self.angle is not None: @@ -168,6 +186,10 @@ class Source(object): if filename is not None: source.file = filename + library = get_text(elem, 'library') + if library is not None: + source.library = library + space = elem.find('space') if space is not None: source.space = Spatial.from_xml_element(space) diff --git a/openmc/statepoint.py b/openmc/statepoint.py index 5802a74ca..84512d70e 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -310,7 +310,7 @@ class StatePoint(object): if self.run_mode == 'eigenvalue': return self._f['n_inactive'][()] else: - return None + return None @property def n_particles(self): diff --git a/openmc/surface.py b/openmc/surface.py index 55e3f459d..567968b2c 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -1,15 +1,16 @@ from abc import ABCMeta, abstractmethod from collections import OrderedDict from copy import deepcopy -from numbers import Real, Integral +from numbers import Real from xml.etree import ElementTree as ET -from warnings import warn +from warnings import warn, catch_warnings, simplefilter +import math import numpy as np from openmc.checkvalue import check_type, check_value from openmc.region import Region, Intersection, Union -from openmc.mixin import IDManagerMixin +from openmc.mixin import IDManagerMixin, IDWarning _BOUNDARY_TYPES = ['transmission', 'vacuum', 'reflective', 'periodic', 'white'] @@ -19,6 +20,11 @@ _WARNING_UPPER = """\ will not accept the capitalized version.\ """ +_WARNING_KWARGS = """\ +"{}(...) accepts keyword arguments only for '{}'. Future versions of OpenMC \ +will not accept positional parameters for superclass arguments.\ +""" + class Surface(IDManagerMixin, metaclass=ABCMeta): """An implicit surface with an associated boundary condition. @@ -60,6 +66,7 @@ class Surface(IDManagerMixin, metaclass=ABCMeta): next_id = 1 used_ids = set() + _atol = 1.e-12 def __init__(self, surface_id=None, boundary_type='transmission', name=''): self.id = surface_id @@ -179,13 +186,85 @@ class Surface(IDManagerMixin, metaclass=ABCMeta): return memo[self] - @abstractmethod - def evaluate(self, point): - pass + def normalize(self, coeffs=None): + """Normalize coefficients by first nonzero value + + Parameters + ---------- + coeffs : tuple, optional + Tuple of surface coefficients to normalize. Defaults to None. If no + coefficients are supplied then the coefficients will be taken from + the current Surface. + + Returns + ------- + tuple of normalized coefficients + + """ + if coeffs is None: + coeffs = self._get_base_coeffs() + coeffs = np.asarray(coeffs) + nonzeros = ~np.isclose(coeffs, 0., rtol=0., atol=self._atol) + norm_factor = np.abs(coeffs[nonzeros][0]) + return tuple([c/norm_factor for c in coeffs]) + + def is_equal(self, other): + """Determine if this Surface is equivalent to another + + Parameters + ---------- + other : instance of openmc.Surface + Instance of openmc.Surface that should be compared to the current + surface + + """ + coeffs1 = self.normalize(self._get_base_coeffs()) + coeffs2 = self.normalize(other._get_base_coeffs()) + + return np.allclose(coeffs1, coeffs2, rtol=0., atol=self._atol) @abstractmethod - def translate(self, vector): - pass + def _get_base_coeffs(self): + """Return polynomial coefficients representing the implicit surface + equation. + + """ + + @abstractmethod + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + Evaluation of the surface polynomial at point :math:`(x',y',z')` + + """ + + @abstractmethod + def translate(self, vector, inplace=False): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + inplace : boolean + Whether or not to return a new instance of this Surface or to + modify the coefficients of this Surface. Defaults to False + + Returns + ------- + instance of openmc.Surface + Translated surface + + """ def to_xml_element(self): """Return XML representation of the surface @@ -228,21 +307,7 @@ class Surface(IDManagerMixin, metaclass=ABCMeta): # Determine appropriate class surf_type = elem.get('type') - surface_classes = { - 'plane': Plane, - 'x-plane': XPlane, - 'y-plane': YPlane, - 'z-plane': ZPlane, - 'x-cylinder': XCylinder, - 'y-cylinder': YCylinder, - 'z-cylinder': ZCylinder, - 'sphere': Sphere, - 'x-cone': XCone, - 'y-cone': YCone, - 'z-cone': ZCone, - 'quadric': Quadric, - } - cls = surface_classes[surf_type] + cls = _SURFACE_CLASSES[surf_type] # Determine ID, boundary type, coefficients kwargs = {} @@ -268,62 +333,155 @@ class Surface(IDManagerMixin, metaclass=ABCMeta): Instance of surface subclass """ + surface_id = int(group.name.split('/')[-1].lstrip('surface ')) name = group['name'][()].decode() if 'name' in group else '' surf_type = group['type'][()].decode() bc = group['boundary_type'][()].decode() coeffs = group['coefficients'][...] + kwargs = {'boundary_type': bc, 'name': name, 'surface_id': surface_id} - # Create the Surface based on its type - if surf_type == 'x-plane': - x0 = coeffs[0] - surface = XPlane(x0, bc, name, surface_id) + cls = _SURFACE_CLASSES[surf_type] - elif surf_type == 'y-plane': - y0 = coeffs[0] - surface = YPlane(y0, bc, name, surface_id) - - elif surf_type == 'z-plane': - z0 = coeffs[0] - surface = ZPlane(z0, bc, name, surface_id) - - elif surf_type == 'plane': - A, B, C, D = coeffs - surface = Plane(A, B, C, D, bc, name, surface_id) - - elif surf_type == 'x-cylinder': - y0, z0, r = coeffs - surface = XCylinder(y0, z0, r, bc, name, surface_id) - - elif surf_type == 'y-cylinder': - x0, z0, r = coeffs - surface = YCylinder(x0, z0, r, bc, name, surface_id) - - elif surf_type == 'z-cylinder': - x0, y0, r = coeffs - surface = ZCylinder(x0, y0, r, bc, name, surface_id) - - elif surf_type == 'sphere': - x0, y0, z0, r = coeffs - surface = Sphere(x0, y0, z0, r, bc, name, surface_id) - - elif surf_type in ['x-cone', 'y-cone', 'z-cone']: - x0, y0, z0, r2 = coeffs - if surf_type == 'x-cone': - surface = XCone(x0, y0, z0, r2, bc, name, surface_id) - elif surf_type == 'y-cone': - surface = YCone(x0, y0, z0, r2, bc, name, surface_id) - elif surf_type == 'z-cone': - surface = ZCone(x0, y0, z0, r2, bc, name, surface_id) - - elif surf_type == 'quadric': - a, b, c, d, e, f, g, h, j, k = coeffs - surface = Quadric(a, b, c, d, e, f, g, h, j, k, bc, name, surface_id) - - return surface + return cls(*coeffs, **kwargs) -class Plane(Surface): +class PlaneMixin(metaclass=ABCMeta): + """A Plane mixin class for all operations on order 1 surfaces""" + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._periodic_surface = None + + @property + def periodic_surface(self): + return self._periodic_surface + + @periodic_surface.setter + def periodic_surface(self, periodic_surface): + check_type('periodic surface', periodic_surface, Plane) + self._periodic_surface = periodic_surface + periodic_surface._periodic_surface = self + + def _get_base_coeffs(self): + return (self.a, self.b, self.c, self.d) + + def _get_normal(self): + a, b, c = self._get_base_coeffs()[:3] + return np.array((a, b, c)) / math.sqrt(a*a + b*b + c*c) + + def bounding_box(self, side): + """Determine an axis-aligned bounding box. + + An axis-aligned bounding box for Plane half-spaces is represented by + its lower-left and upper-right coordinates. If the half-space is + unbounded in a particular direction, numpy.inf is used to represent + infinity. + + Parameters + ---------- + side : {'+', '-'} + Indicates the negative or positive half-space + + Returns + ------- + numpy.ndarray + Lower-left coordinates of the axis-aligned bounding box for the + desired half-space + numpy.ndarray + Upper-right coordinates of the axis-aligned bounding box for the + desired half-space + + """ + # Compute the bounding box based on the normal vector to the plane + nhat = self._get_normal() + ll = np.array([-np.inf, -np.inf, -np.inf]) + ur = np.array([np.inf, np.inf, np.inf]) + # If the plane is axis aligned, find the proper bounding box + if np.any(np.isclose(np.abs(nhat), 1., rtol=0., atol=self._atol)): + sign = nhat.sum() + a, b, c, d = self._get_base_coeffs() + vals = [d/val if not np.isclose(val, 0., rtol=0., atol=self._atol) + else np.nan for val in (a, b, c)] + if side == '-': + if sign > 0: + ur = np.array([v if not np.isnan(v) else np.inf for v in vals]) + else: + ll = np.array([v if not np.isnan(v) else -np.inf for v in vals]) + elif side == '+': + if sign > 0: + ll = np.array([v if not np.isnan(v) else -np.inf for v in vals]) + else: + ur = np.array([v if not np.isnan(v) else np.inf for v in vals]) + + return (ll, ur) + + def evaluate(self, point): + """Evaluate the surface equation at a given point. + + Parameters + ---------- + point : 3-tuple of float + The Cartesian coordinates, :math:`(x',y',z')`, at which the surface + equation should be evaluated. + + Returns + ------- + float + :math:`Ax' + By' + Cz' - D` + + """ + + x, y, z = point + a, b, c, d = self._get_base_coeffs() + return a*x + b*y + c*z - d + + def translate(self, vector, inplace=False): + """Translate surface in given direction + + Parameters + ---------- + vector : iterable of float + Direction in which surface should be translated + inplace : boolean + Whether or not to return a new instance of a Plane or to modify the + coefficients of this plane. Defaults to False + + Returns + ------- + openmc.Plane + Translated surface + + """ + vx, vy, vz = vector + a, b, c, d = self._get_base_coeffs() + d = d + a*vx + b*vy + c*vz + + surf = self if inplace else self.clone() + + setattr(surf, surf._coeff_keys[-1], d) + + return surf + + def to_xml_element(self): + """Return XML representation of the surface + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing source data + + """ + element = super().to_xml_element() + + # Add periodic surface pair information + if self.boundary_type == 'periodic': + if self.periodic_surface is not None: + element.set("periodic_surface_id", + str(self.periodic_surface.id)) + return element + + +class Plane(PlaneMixin, Surface): """An arbitrary plane of the form :math:`Ax + By + Cz = D`. Parameters @@ -376,19 +534,40 @@ class Plane(Surface): _type = 'plane' _coeff_keys = ('a', 'b', 'c', 'd') - def __init__(self, a=1., b=0., c=0., d=0., boundary_type='transmission', - name='', surface_id=None, **kwargs): - super().__init__(surface_id, boundary_type, name=name) - self._periodic_surface = None - self.a = a - self.b = b - self.c = c - self.d = d - for k, v in kwargs.items(): - if k in 'ABCD': - warn(_WARNING_UPPER.format(type(self).__name__, k.lower(), k), - FutureWarning) - setattr(self, k.lower(), v) + def __init__(self, a=1., b=0., c=0., d=0., *args, **kwargs): + # *args should ultimately be limited to a, b, c, d as specified in + # __init__, but to preserve the API it is allowed to accept Surface + # parameters for now, but will raise warnings if this is done. + argtup = ('a', 'b', 'c', 'd', 'boundary_type', 'name', 'surface_id') + kwargs.update(dict(zip(argtup, args))) + + # Warn if Surface parameters are passed by position, not by keyword + superkwargs = {} + for k in ('boundary_type', 'name', 'surface_id'): + val = kwargs.get(k, None) + if val is not None: + superkwargs[k] = val + warn(_WARNING_KWARGS.format(type(self), k), + FutureWarning) + + super().__init__(**superkwargs) + + for key, val in zip(self._coeff_keys, (a, b, c, d)): + setattr(self, key, val) + + # Warn if capital letter arguments are passed + for k in 'ABCD': + val = kwargs.pop(k, None) + if val is not None: + warn(_WARNING_UPPER.format(type(self), k.lower(), k), + FutureWarning) + setattr(self, k.lower(), val) + + @classmethod + def __subclasshook__(cls, c): + if cls is Plane and c in (XPlane, YPlane, ZPlane): + return True + return NotImplemented @property def a(self): @@ -406,10 +585,6 @@ class Plane(Surface): def d(self): return self.coefficients['d'] - @property - def periodic_surface(self): - return self._periodic_surface - @a.setter def a(self, a): check_type('A coefficient', a, Real) @@ -430,69 +605,6 @@ class Plane(Surface): check_type('D coefficient', d, Real) self._coefficients['d'] = d - @periodic_surface.setter - def periodic_surface(self, periodic_surface): - check_type('periodic surface', periodic_surface, Plane) - self._periodic_surface = periodic_surface - periodic_surface._periodic_surface = self - - def evaluate(self, point): - """Evaluate the surface equation at a given point. - - Parameters - ---------- - point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. - - Returns - ------- - float - :math:`Ax' + By' + Cz' - D` - - """ - - x, y, z = point - return self.a*x + self.b*y + self.c*z - self.d - - def translate(self, vector): - """Translate surface in given direction - - Parameters - ---------- - vector : iterable of float - Direction in which surface should be translated - - Returns - ------- - openmc.Plane - Translated surface - - """ - vx, vy, vz = vector - d = self.d + self.a*vx + self.b*vy + self.c*vz - if d == self.d: - return self - else: - return type(self)(a=self.a, b=self.b, c=self.c, d=d) - - def to_xml_element(self): - """Return XML representation of the surface - - Returns - ------- - element : xml.etree.ElementTree.Element - XML element containing source data - - """ - element = super().to_xml_element() - - # Add periodic surface pair information - if self.boundary_type == 'periodic': - if self.periodic_surface is not None: - element.set("periodic_surface_id", str(self.periodic_surface.id)) - return element - @classmethod def from_points(cls, p1, p2, p3, **kwargs): """Return a plane given three points that pass through it. @@ -526,7 +638,7 @@ class Plane(Surface): return cls(a=a, b=b, c=c, d=d, **kwargs) -class XPlane(Plane): +class XPlane(PlaneMixin, Surface): """A plane perpendicular to the x axis of the form :math:`x - x_0 = 0` Parameters @@ -568,90 +680,47 @@ class XPlane(Plane): _type = 'x-plane' _coeff_keys = ('x0',) - def __init__(self, x0=0., boundary_type='transmission', - name='', surface_id=None): - super().__init__(surface_id=surface_id, boundary_type=boundary_type, name=name) + def __init__(self, x0=0., *args, **kwargs): + # work around for accepting Surface kwargs as positional parameters + # until they are deprecated + argsdict = dict(zip(('boundary_type', 'name', 'surface_id'), args)) + for k in argsdict: + warn(_WARNING_KWARGS.format(type(self).__name__, k), FutureWarning) + kwargs.update(argsdict) + + super().__init__(**kwargs) self.x0 = x0 @property def x0(self): return self.coefficients['x0'] + @property + def a(self): + return 1. + + @property + def b(self): + return 0. + + @property + def c(self): + return 0. + + @property + def d(self): + return self.x0 + @x0.setter def x0(self, x0): check_type('x0 coefficient', x0, Real) self._coefficients['x0'] = x0 - def bounding_box(self, side): - """Determine an axis-aligned bounding box. - - An axis-aligned bounding box for surface half-spaces is represented by - its lower-left and upper-right coordinates. For the x-plane surface, the - half-spaces are unbounded in their y- and z- directions. To represent - infinity, numpy.inf is used. - - Parameters - ---------- - side : {'+', '-'} - Indicates the negative or positive half-space - - Returns - ------- - numpy.ndarray - Lower-left coordinates of the axis-aligned bounding box for the - desired half-space - numpy.ndarray - Upper-right coordinates of the axis-aligned bounding box for the - desired half-space - - """ - - if side == '-': - return (np.array([-np.inf, -np.inf, -np.inf]), - np.array([self.x0, np.inf, np.inf])) - elif side == '+': - return (np.array([self.x0, -np.inf, -np.inf]), - np.array([np.inf, np.inf, np.inf])) - def evaluate(self, point): - """Evaluate the surface equation at a given point. - - Parameters - ---------- - point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. - - Returns - ------- - float - :math:`x' - x_0` - - """ return point[0] - self.x0 - def translate(self, vector): - """Translate surface in given direction - Parameters - ---------- - vector : iterable of float - Direction in which surface should be translated - - Returns - ------- - openmc.XPlane - Translated surface - - """ - vx = vector[0] - if vx == 0: - return self - else: - return type(self)(x0=self.x0 + vx) - - -class YPlane(Plane): +class YPlane(PlaneMixin, Surface): """A plane perpendicular to the y axis of the form :math:`y - y_0 = 0` Parameters @@ -693,106 +762,63 @@ class YPlane(Plane): _type = 'y-plane' _coeff_keys = ('y0',) - def __init__(self, y0=0., boundary_type='transmission', - name='', surface_id=None): - super().__init__(surface_id=surface_id, boundary_type=boundary_type, name=name) + def __init__(self, y0=0., *args, **kwargs): + # work around for accepting Surface kwargs as positional parameters + # until they are deprecated + argsdict = dict(zip(('boundary_type', 'name', 'surface_id'), args)) + for k in argsdict: + warn(_WARNING_KWARGS.format(type(self).__name__, k), FutureWarning) + kwargs.update(argsdict) + + super().__init__(**kwargs) self.y0 = y0 @property def y0(self): return self.coefficients['y0'] + @property + def a(self): + return 0. + + @property + def b(self): + return 1. + + @property + def c(self): + return 0. + + @property + def d(self): + return self.y0 + @y0.setter def y0(self, y0): check_type('y0 coefficient', y0, Real) self._coefficients['y0'] = y0 - def bounding_box(self, side): - """Determine an axis-aligned bounding box. - - An axis-aligned bounding box for surface half-spaces is represented by - its lower-left and upper-right coordinates. For the y-plane surface, the - half-spaces are unbounded in their x- and z- directions. To represent - infinity, numpy.inf is used. - - Parameters - ---------- - side : {'+', '-'} - Indicates the negative or positive half-space - - Returns - ------- - numpy.ndarray - Lower-left coordinates of the axis-aligned bounding box for the - desired half-space - numpy.ndarray - Upper-right coordinates of the axis-aligned bounding box for the - desired half-space - - """ - - if side == '-': - return (np.array([-np.inf, -np.inf, -np.inf]), - np.array([np.inf, self.y0, np.inf])) - elif side == '+': - return (np.array([-np.inf, self.y0, -np.inf]), - np.array([np.inf, np.inf, np.inf])) - def evaluate(self, point): - """Evaluate the surface equation at a given point. - - Parameters - ---------- - point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. - - Returns - ------- - float - :math:`y' - y_0` - - """ return point[1] - self.y0 - def translate(self, vector): - """Translate surface in given direction - Parameters - ---------- - vector : iterable of float - Direction in which surface should be translated - - Returns - ------- - openmc.YPlane - Translated surface - - """ - vy = vector[1] - if vy == 0.0: - return self - else: - return type(self)(y0=self.y0 + vy) - - -class ZPlane(Plane): +class ZPlane(PlaneMixin, Surface): """A plane perpendicular to the z axis of the form :math:`z - z_0 = 0` Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. + z0 : float, optional + Location of the plane. Defaults to 0. boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. Only axis-aligned periodicity is supported, i.e., x-planes can only be paired with x-planes. - z0 : float, optional - Location of the plane. Defaults to 0. name : str, optional Name of the plane. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- @@ -818,50 +844,99 @@ class ZPlane(Plane): _type = 'z-plane' _coeff_keys = ('z0',) - def __init__(self, z0=0., boundary_type='transmission', - name='', surface_id=None): - super().__init__(surface_id=surface_id, boundary_type=boundary_type, name=name) + def __init__(self, z0=0., *args, **kwargs): + # work around for accepting Surface kwargs as positional parameters + # until they are deprecated + argsdict = dict(zip(('boundary_type', 'name', 'surface_id'), args)) + for k in argsdict: + warn(_WARNING_KWARGS.format(type(self).__name__, k), FutureWarning) + kwargs.update(argsdict) + + super().__init__(**kwargs) self.z0 = z0 @property def z0(self): return self.coefficients['z0'] + @property + def a(self): + return 0. + + @property + def b(self): + return 0. + + @property + def c(self): + return 1. + + @property + def d(self): + return self.z0 + @z0.setter def z0(self, z0): check_type('z0 coefficient', z0, Real) self._coefficients['z0'] = z0 - def bounding_box(self, side): - """Determine an axis-aligned bounding box. + def evaluate(self, point): + return point[2] - self.z0 - An axis-aligned bounding box for surface half-spaces is represented by - its lower-left and upper-right coordinates. For the z-plane surface, the - half-spaces are unbounded in their x- and y- directions. To represent - infinity, numpy.inf is used. + +class QuadricMixin(metaclass=ABCMeta): + """A Mixin class implementing common functionality for quadric surfaces""" + + @property + def _origin(self): + return np.array((self.x0, self.y0, self.z0)) + + @property + def _axis(self): + axis = np.array((self.dx, self.dy, self.dz)) + return axis / np.linalg.norm(axis) + + def get_Abc(self, coeffs=None): + """Compute matrix, vector, and scalar coefficients for this surface or + for a specified set of coefficients. Parameters ---------- - side : {'+', '-'} - Indicates the negative or positive half-space + coeffs : tuple, optional + Tuple of coefficients from which to compute the quadric elements. + If none are supplied the coefficients of this surface will be used. + """ + if coeffs is None: + a, b, c, d, e, f, g, h, j, k = self._get_base_coeffs() + else: + a, b, c, d, e, f, g, h, j, k = coeffs + + A = np.array([[a, d/2, f/2], [d/2, b, e/2], [f/2, e/2, c]]) + bvec = np.array([g, h, j]) + + return A, bvec, k + + def eigh(self, coeffs=None): + """Wrapper method for returning eigenvalues and eigenvectors of this + quadric surface which is used for transformations. + + Parameters + ---------- + coeffs : tuple, optional + Tuple of coefficients from which to compute the quadric elements. + If none are supplied the coefficients of this surface will be used. Returns ------- - numpy.ndarray - Lower-left coordinates of the axis-aligned bounding box for the - desired half-space - numpy.ndarray - Upper-right coordinates of the axis-aligned bounding box for the - desired half-space + w, v : tuple of numpy arrays with shapes (3,) and (3,3) respectively + Returns the eigenvalues and eigenvectors of the quadric matrix A + that represents the supplied coefficients. The vector w contains + the eigenvalues in ascending order and the matrix v contains the + eigenvectors such that v[:,i] is the eigenvector corresponding to + the eigenvalue w[i]. """ - - if side == '-': - return (np.array([-np.inf, -np.inf, -np.inf]), - np.array([np.inf, np.inf, self.z0])) - elif side == '+': - return (np.array([-np.inf, -np.inf, self.z0]), - np.array([np.inf, np.inf, np.inf])) + return np.linalg.eigh(self.get_Abc(coeffs=coeffs)[0]) def evaluate(self, point): """Evaluate the surface equation at a given point. @@ -875,37 +950,287 @@ class ZPlane(Plane): Returns ------- float - :math:`z' - z_0` + :math:`Ax'^2 + By'^2 + Cz'^2 + Dx'y' + Ey'z' + Fx'z' + Gx' + Hy' + + Jz' + K = 0` """ - return point[2] - self.z0 + x = np.asarray(point) + A, b, c = self.get_Abc() + return x.T @ A @ x + b.T @ x + c - def translate(self, vector): + def translate(self, vector, inplace=False): """Translate surface in given direction Parameters ---------- vector : iterable of float Direction in which surface should be translated + inplace : boolean + Whether to return a clone of the Surface or the Surface itself. + Defaults to False Returns ------- - openmc.ZPlane + openmc.Surface Translated surface """ - vz = vector[2] - if vz == 0.0: - return self + vector = np.asarray(vector) + + surf = self if inplace else self.clone() + + if hasattr(self, 'x0'): + for vi, xi in zip(vector, ('x0', 'y0', 'z0')): + val = getattr(surf, xi) + try: + setattr(surf, xi, val + vi) + except AttributeError: + # That attribute is read only i.e x0 for XCylinder + pass + else: - return type(self)(z0=self.z0 + vz) + A, bvec, cnst = self.get_Abc() + + g, h, j = bvec - 2*vector.T @ A + k = cnst + vector.T @ A @ vector - bvec.T @ vector + + for key, val in zip(('g', 'h', 'j', 'k'), (g, h, j, k)): + setattr(surf, key, val) + + return surf -class Cylinder(Surface): - """A cylinder whose length is parallel to the x-, y-, or z-axis. +class Cylinder(QuadricMixin, Surface): + """A cylinder with radius r, centered on the point (x0, y0, z0) with an + axis specified by the line through points (x0, y0, z0) and (x0+dx, y0+dy, + z0+dz) Parameters ---------- + x0 : float, optional + x-coordinate for the origin of the Cylinder. Defaults to 0 + y0 : float, optional + y-coordinate for the origin of the Cylinder. Defaults to 0 + z0 : float, optional + z-coordinate for the origin of the Cylinder. Defaults to 0 + r : float, optional + Radius of the cylinder. Defaults to 1. + dx : float, optional + x-component of the vector representing the axis of the cylinder. + Defaults to 0. + dy : float, optional + y-component of the vector representing the axis of the cylinder. + Defaults to 0. + dz : float, optional + z-component of the vector representing the axis of the cylinder. + Defaults to 1. + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional + Boundary condition that defines the behavior for particles hitting the + surface. Defaults to transmissive boundary condition where particles + freely pass through the surface. + name : str, optional + Name of the cylinder. If not specified, the name will be the empty + string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. + + Attributes + ---------- + x0 : float + x-coordinate for the origin of the Cylinder + y0 : float + y-coordinate for the origin of the Cylinder + z0 : float + z-coordinate for the origin of the Cylinder + r : float + Radius of the cylinder + dx : float + x-component of the vector representing the axis of the cylinder + dy : float + y-component of the vector representing the axis of the cylinder + dz : float + z-component of the vector representing the axis of the cylinder + boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} + Boundary condition that defines the behavior for particles hitting the + surface. + coefficients : dict + Dictionary of surface coefficients + id : int + Unique identifier for the surface + name : str + Name of the surface + type : str + Type of the surface + + """ + _type = 'cylinder' + _coeff_keys = ('x0', 'y0', 'z0', 'r', 'dx', 'dy', 'dz') + + def __init__(self, x0=0., y0=0., z0=0., r=1., dx=0., dy=0., dz=1., **kwargs): + super().__init__(**kwargs) + + for key, val in zip(self._coeff_keys, (x0, y0, z0, r, dx, dy, dz)): + setattr(self, key, val) + + @classmethod + def __subclasshook__(cls, c): + if cls is Cylinder and c in (XCylinder, YCylinder, ZCylinder): + return True + return NotImplemented + + @property + def x0(self): + return self.coefficients['x0'] + + @property + def y0(self): + return self.coefficients['y0'] + + @property + def z0(self): + return self.coefficients['z0'] + + @property + def r(self): + return self.coefficients['r'] + + @property + def dx(self): + return self.coefficients['dx'] + + @property + def dy(self): + return self.coefficients['dy'] + + @property + def dz(self): + return self.coefficients['dz'] + + @x0.setter + def x0(self, x0): + check_type('x0 coefficient', x0, Real) + self._coefficients['x0'] = x0 + + @y0.setter + def y0(self, y0): + check_type('y0 coefficient', y0, Real) + self._coefficients['y0'] = y0 + + @z0.setter + def z0(self, z0): + check_type('z0 coefficient', z0, Real) + self._coefficients['z0'] = z0 + + @r.setter + def r(self, r): + check_type('r coefficient', r, Real) + self._coefficients['r'] = r + + @dx.setter + def dx(self, dx): + check_type('dx coefficient', dx, Real) + self._coefficients['dx'] = dx + + @dy.setter + def dy(self, dy): + check_type('dy coefficient', dy, Real) + self._coefficients['dy'] = dy + + @dz.setter + def dz(self, dz): + check_type('dz coefficient', dz, Real) + self._coefficients['dz'] = dz + + def _get_base_coeffs(self): + # Get x, y, z coordinates of two points + x1, y1, z1 = self._origin + x2, y2, z2 = self._origin + self._axis + r = self.r + + # Define intermediate terms + dx = x2 - x1 + dy = y2 - y1 + dz = z2 - z1 + cx = y1*z2 - y2*z1 + cy = x2*z1 - x1*z2 + cz = x1*y2 - x2*y1 + + # Given p=(x,y,z), p1=(x1, y1, z1), p2=(x2, y2, z2), the equation + # for the cylinder can be derived as + # r = |(p - p1) ⨯ (p - p2)| / |p2 - p1|. + # Expanding out all terms and grouping according to what Quadric + # expects gives the following coefficients. + a = dy*dy + dz*dz + b = dx*dx + dz*dz + c = dx*dx + dy*dy + d = -2*dx*dy + e = -2*dy*dz + f = -2*dx*dz + g = 2*(cy*dz - cz*dy) + h = 2*(cz*dx - cx*dz) + j = 2*(cx*dy - cy*dx) + k = cx*cx + cy*cy + cz*cz - (dx*dx + dy*dy + dz*dz)*r*r + + return (a, b, c, d, e, f, g, h, j, k) + + @classmethod + def from_points(cls, p1, p2, r=1., **kwargs): + """Return a cylinder given points that define the axis and a radius. + + Parameters + ---------- + p1, p2 : 3-tuples + Points that pass through the plane, p1 will be used as (x0, y0, z0) + r : float, optional + Radius of the cylinder. Defaults to 1. + kwargs : dict + Keyword arguments passed to the :class:`Cylinder` constructor + + Returns + ------- + Cylinder + Cylinder that has an axis through the points p1 and p2, and a + radius r. + + """ + # Convert to numpy arrays + p1 = np.asarray(p1) + p2 = np.asarray(p2) + x0, y0, z0 = p1 + dx, dy, dz = p2 - p1 + + return cls(x0=x0, y0=y0, z0=z0, r=r, dx=dx, dy=dy, dz=dz, **kwargs) + + def to_xml_element(self): + """Return XML representation of the surface + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing source data + + """ + # This method overrides Surface.to_xml_element to generate a Quadric + # since the C++ layer doesn't support Cylinders right now + with catch_warnings(): + simplefilter('ignore', IDWarning) + kwargs = {'boundary_type': self.boundary_type, 'name': self.name, + 'surface_id': self.id} + quad_rep = Quadric(*self._get_base_coeffs(), **kwargs) + return quad_rep.to_xml_element() + + +class XCylinder(QuadricMixin, Surface): + """An infinite cylinder whose length is parallel to the x-axis of the form + :math:`(y - y_0)^2 + (z - z_0)^2 = r^2`. + + Parameters + ---------- + y0 : float, optional + y-coordinate for the origin of the Cylinder. Defaults to 0 + z0 : float, optional + z-coordinate for the origin of the Cylinder. Defaults to 0 r : float, optional Radius of the cylinder. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional @@ -921,6 +1246,10 @@ class Cylinder(Surface): Attributes ---------- + y0 : float + y-coordinate for the origin of the Cylinder + z0 : float + z-coordinate for the origin of the Cylinder r : float Radius of the cylinder boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} @@ -935,76 +1264,21 @@ class Cylinder(Surface): type : str Type of the surface - """ - def __init__(self, r=1., boundary_type='transmission', - name='', surface_id=None): - super().__init__(surface_id, boundary_type, name=name) - self.r = r - - @property - def r(self): - return self.coefficients['r'] - - @r.setter - def r(self, r): - check_type('r coefficient', r, Real) - self._coefficients['r'] = r - - -class XCylinder(Cylinder): - """An infinite cylinder whose length is parallel to the x-axis of the form - :math:`(y - y_0)^2 + (z - z_0)^2 = r^2`. - - Parameters - ---------- - y0 : float, optional - y-coordinate of the center of the cylinder. Defaults to 0. - z0 : float, optional - z-coordinate of the center of the cylinder. Defaults to 0. - r : float, optional - Radius of the cylinder. Defaults to 0. - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional - Boundary condition that defines the behavior for particles hitting the - surface. Defaults to transmissive boundary condition where particles - freely pass through the surface. - name : str, optional - Name of the cylinder. If not specified, the name will be the empty - string. - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. - - Attributes - ---------- - y0 : float - y-coordinate of the center of the cylinder - z0 : float - z-coordinate of the center of the cylinder - boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} - Boundary condition that defines the behavior for particles hitting the - surface. - coefficients : dict - Dictionary of surface coefficients - id : int - Unique identifier for the surface - name : str - Name of the surface - type : str - Type of the surface - """ _type = 'x-cylinder' _coeff_keys = ('y0', 'z0', 'r') - def __init__(self, y0=0., z0=0., r=1., boundary_type='transmission', - name='', surface_id=None, *, R=None): + def __init__(self, y0=0., z0=0., r=1., **kwargs): + R = kwargs.pop('R', None) if R is not None: - warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) + warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), + FutureWarning) r = R - super().__init__(r, boundary_type, name, surface_id) - self.y0 = y0 - self.z0 = z0 + super().__init__(**kwargs) + + for key, val in zip(self._coeff_keys, (y0, z0, r)): + setattr(self, key, val) @property def y0(self): @@ -1014,6 +1288,26 @@ class XCylinder(Cylinder): def z0(self): return self.coefficients['z0'] + @property + def r(self): + return self.coefficients['r'] + + @property + def x0(self): + return 0. + + @property + def dx(self): + return 1. + + @property + def dy(self): + return 0. + + @property + def dz(self): + return 0. + @y0.setter def y0(self, y0): check_type('y0 coefficient', y0, Real) @@ -1024,31 +1318,21 @@ class XCylinder(Cylinder): check_type('z0 coefficient', z0, Real) self._coefficients['z0'] = z0 + @r.setter + def r(self, r): + check_type('r coefficient', r, Real) + self._coefficients['r'] = r + + def _get_base_coeffs(self): + y0, z0, r = self.y0, self.z0, self.r + + a = d = e = f = g = 0. + b = c = 1. + h, j, k = -2*y0, -2*z0, y0*y0 + z0*z0 - r*r + + return (a, b, c, d, e, f, g, h, j, k) + def bounding_box(self, side): - """Determine an axis-aligned bounding box. - - An axis-aligned bounding box for surface half-spaces is represented by - its lower-left and upper-right coordinates. For the x-cylinder surface, - the negative half-space is unbounded in the x- direction and the - positive half-space is unbounded in all directions. To represent - infinity, numpy.inf is used. - - Parameters - ---------- - side : {'+', '-'} - Indicates the negative or positive half-space - - Returns - ------- - numpy.ndarray - Lower-left coordinates of the axis-aligned bounding box for the - desired half-space - numpy.ndarray - Upper-right coordinates of the axis-aligned bounding box for the - desired half-space - - """ - if side == '-': return (np.array([-np.inf, self.y0 - self.r, self.z0 - self.r]), np.array([np.inf, self.y0 + self.r, self.z0 + self.r])) @@ -1057,57 +1341,21 @@ class XCylinder(Cylinder): np.array([np.inf, np.inf, np.inf])) def evaluate(self, point): - """Evaluate the surface equation at a given point. - - Parameters - ---------- - point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. - - Returns - ------- - float - :math:`(y' - y_0)^2 + (z' - z_0)^2 - r^2` - - """ y = point[1] - self.y0 z = point[2] - self.z0 - return y**2 + z**2 - self.r**2 - - def translate(self, vector): - """Translate surface in given direction - - Parameters - ---------- - vector : iterable of float - Direction in which surface should be translated - - Returns - ------- - openmc.XCylinder - Translated surface - - """ - vx, vy, vz = vector - if vy == 0.0 and vz == 0.0: - return self - else: - y0 = self.y0 + vy - z0 = self.z0 + vz - return type(self)(y0=y0, z0=z0, r=self.r) + return y*y + z*z - self.r**2 -class YCylinder(Cylinder): +class YCylinder(QuadricMixin, Surface): """An infinite cylinder whose length is parallel to the y-axis of the form :math:`(x - x_0)^2 + (z - z_0)^2 = r^2`. Parameters ---------- x0 : float, optional - x-coordinate of the center of the cylinder. Defaults to 0. + x-coordinate for the origin of the Cylinder. Defaults to 0 z0 : float, optional - z-coordinate of the center of the cylinder. Defaults to 0. + z-coordinate for the origin of the Cylinder. Defaults to 0 r : float, optional Radius of the cylinder. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional @@ -1124,9 +1372,11 @@ class YCylinder(Cylinder): Attributes ---------- x0 : float - x-coordinate of the center of the cylinder + x-coordinate for the origin of the Cylinder z0 : float - z-coordinate of the center of the cylinder + z-coordinate for the origin of the Cylinder + r : float + Radius of the cylinder boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. @@ -1144,14 +1394,16 @@ class YCylinder(Cylinder): _type = 'y-cylinder' _coeff_keys = ('x0', 'z0', 'r') - def __init__(self, x0=0., z0=0., r=1., boundary_type='transmission', - name='', surface_id=None, *, R=None): + def __init__(self, x0=0., z0=0., r=1., **kwargs): + R = kwargs.pop('R', None) if R is not None: - warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) + warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), + FutureWarning) r = R - super().__init__(r, boundary_type, name, surface_id) - self.x0 = x0 - self.z0 = z0 + super().__init__(**kwargs) + + for key, val in zip(self._coeff_keys, (x0, z0, r)): + setattr(self, key, val) @property def x0(self): @@ -1161,6 +1413,26 @@ class YCylinder(Cylinder): def z0(self): return self.coefficients['z0'] + @property + def r(self): + return self.coefficients['r'] + + @property + def y0(self): + return 0. + + @property + def dx(self): + return 0. + + @property + def dy(self): + return 1. + + @property + def dz(self): + return 0. + @x0.setter def x0(self, x0): check_type('x0 coefficient', x0, Real) @@ -1171,31 +1443,21 @@ class YCylinder(Cylinder): check_type('z0 coefficient', z0, Real) self._coefficients['z0'] = z0 + @r.setter + def r(self, r): + check_type('r coefficient', r, Real) + self._coefficients['r'] = r + + def _get_base_coeffs(self): + x0, z0, r = self.x0, self.z0, self.r + + b = d = e = f = h = 0. + a = c = 1. + g, j, k = -2*x0, -2*z0, x0*x0 + z0*z0 - r*r + + return (a, b, c, d, e, f, g, h, j, k) + def bounding_box(self, side): - """Determine an axis-aligned bounding box. - - An axis-aligned bounding box for surface half-spaces is represented by - its lower-left and upper-right coordinates. For the y-cylinder surface, - the negative half-space is unbounded in the y- direction and the - positive half-space is unbounded in all directions. To represent - infinity, numpy.inf is used. - - Parameters - ---------- - side : {'+', '-'} - Indicates the negative or positive half-space - - Returns - ------- - numpy.ndarray - Lower-left coordinates of the axis-aligned bounding box for the - desired half-space - numpy.ndarray - Upper-right coordinates of the axis-aligned bounding box for the - desired half-space - - """ - if side == '-': return (np.array([self.x0 - self.r, -np.inf, self.z0 - self.r]), np.array([self.x0 + self.r, np.inf, self.z0 + self.r])) @@ -1204,76 +1466,42 @@ class YCylinder(Cylinder): np.array([np.inf, np.inf, np.inf])) def evaluate(self, point): - """Evaluate the surface equation at a given point. - - Parameters - ---------- - point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. - - Returns - ------- - float - :math:`(x' - x_0)^2 + (z' - z_0)^2 - r^2` - - """ x = point[0] - self.x0 z = point[2] - self.z0 - return x**2 + z**2 - self.r**2 - - def translate(self, vector): - """Translate surface in given direction - - Parameters - ---------- - vector : iterable of float - Direction in which surface should be translated - - Returns - ------- - openmc.YCylinder - Translated surface - - """ - vx, vy, vz = vector - if vx == 0.0 and vz == 0.0: - return self - else: - x0 = self.x0 + vx - z0 = self.z0 + vz - return type(self)(x0=x0, z0=z0, r=self.r) + return x*x + z*z - self.r**2 -class ZCylinder(Cylinder): +class ZCylinder(QuadricMixin, Surface): """An infinite cylinder whose length is parallel to the z-axis of the form :math:`(x - x_0)^2 + (y - y_0)^2 = r^2`. Parameters ---------- - surface_id : int, optional - Unique identifier for the surface. If not specified, an identifier will - automatically be assigned. + x0 : float, optional + x-coordinate for the origin of the Cylinder. Defaults to 0 + y0 : float, optional + y-coordinate for the origin of the Cylinder. Defaults to 0 + r : float, optional + Radius of the cylinder. Defaults to 1. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional Boundary condition that defines the behavior for particles hitting the surface. Defaults to transmissive boundary condition where particles freely pass through the surface. - x0 : float, optional - x-coordinate of the center of the cylinder. Defaults to 0. - y0 : float, optional - y-coordinate of the center of the cylinder. Defaults to 0. - r : float, optional - Radius of the cylinder. Defaults to 1. name : str, optional Name of the cylinder. If not specified, the name will be the empty string. + surface_id : int, optional + Unique identifier for the surface. If not specified, an identifier will + automatically be assigned. Attributes ---------- x0 : float - x-coordinate of the center of the cylinder + x-coordinate for the origin of the Cylinder y0 : float - y-coordinate of the center of the cylinder + y-coordinate for the origin of the Cylinder + r : float + Radius of the cylinder boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. @@ -1291,14 +1519,16 @@ class ZCylinder(Cylinder): _type = 'z-cylinder' _coeff_keys = ('x0', 'y0', 'r') - def __init__(self, x0=0., y0=0., r=1., boundary_type='transmission', - name='', surface_id=None, *, R=None): + def __init__(self, x0=0., y0=0., r=1., **kwargs): + R = kwargs.pop('R', None) if R is not None: - warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) + warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), + FutureWarning) r = R - super().__init__(r, boundary_type, name, surface_id) - self.x0 = x0 - self.y0 = y0 + super().__init__(**kwargs) + + for key, val in zip(self._coeff_keys, (x0, y0, r)): + setattr(self, key, val) @property def x0(self): @@ -1308,6 +1538,26 @@ class ZCylinder(Cylinder): def y0(self): return self.coefficients['y0'] + @property + def r(self): + return self.coefficients['r'] + + @property + def z0(self): + return 0. + + @property + def dx(self): + return 0. + + @property + def dy(self): + return 0. + + @property + def dz(self): + return 1. + @x0.setter def x0(self, x0): check_type('x0 coefficient', x0, Real) @@ -1318,31 +1568,21 @@ class ZCylinder(Cylinder): check_type('y0 coefficient', y0, Real) self._coefficients['y0'] = y0 + @r.setter + def r(self, r): + check_type('r coefficient', r, Real) + self._coefficients['r'] = r + + def _get_base_coeffs(self): + x0, y0, r = self.x0, self.y0, self.r + + c = d = e = f = j = 0. + a = b = 1. + g, h, k = -2*x0, -2*y0, x0*x0 + y0*y0 - r*r + + return (a, b, c, d, e, f, g, h, j, k) + def bounding_box(self, side): - """Determine an axis-aligned bounding box. - - An axis-aligned bounding box for surface half-spaces is represented by - its lower-left and upper-right coordinates. For the z-cylinder surface, - the negative half-space is unbounded in the z- direction and the - positive half-space is unbounded in all directions. To represent - infinity, numpy.inf is used. - - Parameters - ---------- - side : {'+', '-'} - Indicates the negative or positive half-space - - Returns - ------- - numpy.ndarray - Lower-left coordinates of the axis-aligned bounding box for the - desired half-space - numpy.ndarray - Upper-right coordinates of the axis-aligned bounding box for the - desired half-space - - """ - if side == '-': return (np.array([self.x0 - self.r, self.y0 - self.r, -np.inf]), np.array([self.x0 + self.r, self.y0 + self.r, np.inf])) @@ -1351,48 +1591,12 @@ class ZCylinder(Cylinder): np.array([np.inf, np.inf, np.inf])) def evaluate(self, point): - """Evaluate the surface equation at a given point. - - Parameters - ---------- - point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. - - Returns - ------- - float - :math:`(x' - x_0)^2 + (y' - y_0)^2 - r^2` - - """ x = point[0] - self.x0 y = point[1] - self.y0 - return x**2 + y**2 - self.r**2 - - def translate(self, vector): - """Translate surface in given direction - - Parameters - ---------- - vector : iterable of float - Direction in which surface should be translated - - Returns - ------- - openmc.ZCylinder - Translated surface - - """ - vx, vy, vz = vector - if vx == 0.0 and vy == 0.0: - return self - else: - x0 = self.x0 + vx - y0 = self.y0 + vy - return type(self)(x0=x0, y0=y0, r=self.r) + return x*x + y*y - self.r**2 -class Sphere(Surface): +class Sphere(QuadricMixin, Surface): """A sphere of the form :math:`(x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = r^2`. Parameters @@ -1442,16 +1646,16 @@ class Sphere(Surface): _type = 'sphere' _coeff_keys = ('x0', 'y0', 'z0', 'r') - def __init__(self, x0=0., y0=0., z0=0., r=1., boundary_type='transmission', - name='', surface_id=None, *, R=None): + def __init__(self, x0=0., y0=0., z0=0., r=1., **kwargs): + R = kwargs.pop('R', None) if R is not None: - warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), FutureWarning) + warn(_WARNING_UPPER.format(type(self).__name__, 'r', 'R'), + FutureWarning) r = R - super().__init__(surface_id, boundary_type, name=name) - self.x0 = x0 - self.y0 = y0 - self.z0 = z0 - self.r = r + super().__init__(**kwargs) + + for key, val in zip(self._coeff_keys, (x0, y0, z0, r)): + setattr(self, key, val) @property def x0(self): @@ -1489,30 +1693,16 @@ class Sphere(Surface): check_type('r coefficient', r, Real) self._coefficients['r'] = r + def _get_base_coeffs(self): + x0, y0, z0, r = self.x0, self.y0, self.z0, self.r + a = b = c = 1. + d = e = f = 0. + g, h, j = -2*x0, -2*y0, -2*z0 + k = x0*x0 + y0*y0 + z0*z0 - r*r + + return (a, b, c, d, e, f, g, h, j, k) + def bounding_box(self, side): - """Determine an axis-aligned bounding box. - - An axis-aligned bounding box for surface half-spaces is represented by - its lower-left and upper-right coordinates. The positive half-space of a - sphere is unbounded in all directions. To represent infinity, numpy.inf - is used. - - Parameters - ---------- - side : {'+', '-'} - Indicates the negative or positive half-space - - Returns - ------- - numpy.ndarray - Lower-left coordinates of the axis-aligned bounding box for the - desired half-space - numpy.ndarray - Upper-right coordinates of the axis-aligned bounding box for the - desired half-space - - """ - if side == '-': return (np.array([self.x0 - self.r, self.y0 - self.r, self.z0 - self.r]), @@ -1523,50 +1713,13 @@ class Sphere(Surface): np.array([np.inf, np.inf, np.inf])) def evaluate(self, point): - """Evaluate the surface equation at a given point. - - Parameters - ---------- - point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. - - Returns - ------- - float - :math:`(x' - x_0)^2 + (y' - y_0)^2 + (z' - z_0)^2 - r^2` - - """ x = point[0] - self.x0 y = point[1] - self.y0 z = point[2] - self.z0 - return x**2 + y**2 + z**2 - self.r**2 - - def translate(self, vector): - """Translate surface in given direction - - Parameters - ---------- - vector : iterable of float - Direction in which surface should be translated - - Returns - ------- - openmc.Sphere - Translated surface - - """ - vx, vy, vz = vector - if vx == 0.0 and vy == 0.0 and vz == 0.0: - return self - else: - x0 = self.x0 + vx - y0 = self.y0 + vy - z0 = self.z0 + vz - return type(self)(x0=x0, y0=y0, z0=z0, r=self.r) + return x*x + y*y + z*z - self.r**2 -class Cone(Surface): +class Cone(QuadricMixin, Surface): """A conical surface parallel to the x-, y-, or z-axis. Parameters @@ -1579,6 +1732,15 @@ class Cone(Surface): z-coordinate of the apex. Defaults to 0. r2 : float, optional Parameter related to the aperature. Defaults to 1. + dx : float, optional + x-component of the vector representing the axis of the cone. + Defaults to 0. + dy : float, optional + y-component of the vector representing the axis of the cone. + Defaults to 0. + dz : float, optional + z-component of the vector representing the axis of the cone. + Defaults to 1. surface_id : int, optional Unique identifier for the surface. If not specified, an identifier will automatically be assigned. @@ -1599,6 +1761,12 @@ class Cone(Surface): z-coordinate of the apex r2 : float Parameter related to the aperature + dx : float + x-component of the vector representing the axis of the cone. + dy : float + y-component of the vector representing the axis of the cone. + dz : float + z-component of the vector representing the axis of the cone. boundary_type : {'transmission, 'vacuum', 'reflective', 'white'} Boundary condition that defines the behavior for particles hitting the surface. @@ -1613,18 +1781,25 @@ class Cone(Surface): """ - _coeff_keys = ('x0', 'y0', 'z0', 'r2') + _type = 'cone' + _coeff_keys = ('x0', 'y0', 'z0', 'r2', 'dx', 'dy', 'dz') - def __init__(self, x0=0., y0=0., z0=0., r2=1., boundary_type='transmission', - name='', surface_id=None, *, R2=None): + def __init__(self, x0=0., y0=0., z0=0., r2=1., dx=0., dy=0., dz=1., **kwargs): + R2 = kwargs.pop('R2', None) if R2 is not None: - warn(_WARNING_UPPER.format(type(self).__name__, 'r2', 'R2'), FutureWarning) + warn(_WARNING_UPPER.format(type(self).__name__, 'r2', 'R2'), + FutureWarning) r2 = R2 - super().__init__(surface_id, boundary_type, name=name) - self.x0 = x0 - self.y0 = y0 - self.z0 = z0 - self.r2 = r2 + super().__init__(**kwargs) + + for key, val in zip(self._coeff_keys, (x0, y0, z0, r2, dx, dy, dz)): + setattr(self, key, val) + + @classmethod + def __subclasshook__(cls, c): + if cls is Cone and c in (XCone, YCone, ZCone): + return True + return NotImplemented @property def x0(self): @@ -1642,6 +1817,18 @@ class Cone(Surface): def r2(self): return self.coefficients['r2'] + @property + def dx(self): + return self.coefficients['dx'] + + @property + def dy(self): + return self.coefficients['dy'] + + @property + def dz(self): + return self.coefficients['dz'] + @x0.setter def x0(self, x0): check_type('x0 coefficient', x0, Real) @@ -1662,31 +1849,72 @@ class Cone(Surface): check_type('r^2 coefficient', r2, Real) self._coefficients['r2'] = r2 - def translate(self, vector): - """Translate surface in given direction + @dx.setter + def dx(self, dx): + check_type('dx coefficient', dx, Real) + self._coefficients['dx'] = dx - Parameters - ---------- - vector : iterable of float - Direction in which surface should be translated + @dy.setter + def dy(self, dy): + check_type('dy coefficient', dy, Real) + self._coefficients['dy'] = dy + + @dz.setter + def dz(self, dz): + check_type('dz coefficient', dz, Real) + self._coefficients['dz'] = dz + + def _get_base_coeffs(self): + # The equation for a general cone with vertex at point p = (x0, y0, z0) + # and axis specified by the unit vector d = (dx, dy, dz) and opening + # half angle theta can be described by the equation + # + # (d*(r - p))^2 - (r - p)*(r - p)cos^2(theta) = 0 + # + # where * is the dot product and the vector r is the evaulation point + # r = (x, y, z) + # + # The argument r2 for cones is actually tan^2(theta) so that + # cos^2(theta) = 1 / (1 + r2) + + x0, y0, z0 = self._origin + dx, dy, dz = self._axis + cos2 = 1 / (1 + self.r2) + + a = cos2 - dx*dx + b = cos2 - dy*dy + c = cos2 - dz*dz + d = -2*dx*dy + e = -2*dy*dz + f = -2*dx*dz + g = 2*(dx*(dy*y0 + dz*z0) - a*x0) + h = 2*(dy*(dx*x0 + dz*z0) - b*y0) + j = 2*(dz*(dx*x0 + dy*y0) - c*z0) + k = a*x0*x0 + b*y0*y0 + c*z0*z0 - 2*(dx*dy*x0*y0 + dy*dz*y0*z0 + + dx*dz*x0*z0) + + return (a, b, c, d, e, f, g, h, j, k) + + def to_xml_element(self): + """Return XML representation of the surface Returns ------- - openmc.Cone - Translated surface + element : xml.etree.ElementTree.Element + XML element containing source data """ - vx, vy, vz = vector - if vx == 0.0 and vy == 0.0 and vz == 0.0: - return self - else: - x0 = self.x0 + vx - y0 = self.y0 + vy - z0 = self.z0 + vz - return type(self)(x0=x0, y0=y0, z0=z0, r2=self.r2) + # This method overrides Surface.to_xml_element to generate a Quadric + # since the C++ layer doesn't support Cones right now + with catch_warnings(): + simplefilter('ignore', IDWarning) + kwargs = {'boundary_type': self.boundary_type, 'name': self.name, + 'surface_id': self.id} + quad_rep = Quadric(*self._get_base_coeffs(), **kwargs) + return quad_rep.to_xml_element() -class XCone(Cone): +class XCone(QuadricMixin, Surface): """A cone parallel to the x-axis of the form :math:`(y - y_0)^2 + (z - z_0)^2 = r^2 (x - x_0)^2`. @@ -1735,29 +1963,86 @@ class XCone(Cone): """ _type = 'x-cone' + _coeff_keys = ('x0', 'y0', 'z0', 'r2') + + def __init__(self, x0=0., y0=0., z0=0., r2=1., **kwargs): + R2 = kwargs.pop('R2', None) + if R2 is not None: + warn(_WARNING_UPPER.format(type(self).__name__, 'r2', 'R2'), + FutureWarning) + r2 = R2 + super().__init__(**kwargs) + + for key, val in zip(self._coeff_keys, (x0, y0, z0, r2)): + setattr(self, key, val) + + @property + def x0(self): + return self.coefficients['x0'] + + @property + def y0(self): + return self.coefficients['y0'] + + @property + def z0(self): + return self.coefficients['z0'] + + @property + def r2(self): + return self.coefficients['r2'] + + @property + def dx(self): + return 1. + + @property + def dy(self): + return 0. + + @property + def dz(self): + return 0. + + @x0.setter + def x0(self, x0): + check_type('x0 coefficient', x0, Real) + self._coefficients['x0'] = x0 + + @y0.setter + def y0(self, y0): + check_type('y0 coefficient', y0, Real) + self._coefficients['y0'] = y0 + + @z0.setter + def z0(self, z0): + check_type('z0 coefficient', z0, Real) + self._coefficients['z0'] = z0 + + @r2.setter + def r2(self, r2): + check_type('r^2 coefficient', r2, Real) + self._coefficients['r2'] = r2 + + def _get_base_coeffs(self): + x0, y0, z0, r2 = self.x0, self.y0, self.z0, self.r2 + + a = -r2 + b = c = 1. + d = e = f = 0. + g, h, j = 2*x0*r2, -2*y0, -2*z0 + k = y0*y0 + z0*z0 - r2*x0*x0 + + return (a, b, c, d, e, f, g, h, j, k) def evaluate(self, point): - """Evaluate the surface equation at a given point. - - Parameters - ---------- - point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. - - Returns - ------- - float - :math:`(y' - y_0)^2 + (z' - z_0)^2 - r^2(x' - x_0)^2` - - """ x = point[0] - self.x0 y = point[1] - self.y0 z = point[2] - self.z0 - return y**2 + z**2 - self.r2*x**2 + return y*y + z*z - self.r2*x*x -class YCone(Cone): +class YCone(QuadricMixin, Surface): """A cone parallel to the y-axis of the form :math:`(x - x_0)^2 + (z - z_0)^2 = r^2 (y - y_0)^2`. @@ -1806,29 +2091,86 @@ class YCone(Cone): """ _type = 'y-cone' + _coeff_keys = ('x0', 'y0', 'z0', 'r2') + + def __init__(self, x0=0., y0=0., z0=0., r2=1., **kwargs): + R2 = kwargs.pop('R2', None) + if R2 is not None: + warn(_WARNING_UPPER.format(type(self).__name__, 'r2', 'R2'), + FutureWarning) + r2 = R2 + super().__init__(**kwargs) + + for key, val in zip(self._coeff_keys, (x0, y0, z0, r2)): + setattr(self, key, val) + + @property + def x0(self): + return self.coefficients['x0'] + + @property + def y0(self): + return self.coefficients['y0'] + + @property + def z0(self): + return self.coefficients['z0'] + + @property + def r2(self): + return self.coefficients['r2'] + + @property + def dx(self): + return 0. + + @property + def dy(self): + return 1. + + @property + def dz(self): + return 0. + + @x0.setter + def x0(self, x0): + check_type('x0 coefficient', x0, Real) + self._coefficients['x0'] = x0 + + @y0.setter + def y0(self, y0): + check_type('y0 coefficient', y0, Real) + self._coefficients['y0'] = y0 + + @z0.setter + def z0(self, z0): + check_type('z0 coefficient', z0, Real) + self._coefficients['z0'] = z0 + + @r2.setter + def r2(self, r2): + check_type('r^2 coefficient', r2, Real) + self._coefficients['r2'] = r2 + + def _get_base_coeffs(self): + x0, y0, z0, r2 = self.x0, self.y0, self.z0, self.r2 + + b = -r2 + a = c = 1. + d = e = f = 0. + g, h, j = -2*x0, 2*y0*r2, -2*z0 + k = x0*x0 + z0*z0 - r2*y0*y0 + + return (a, b, c, d, e, f, g, h, j, k) def evaluate(self, point): - """Evaluate the surface equation at a given point. - - Parameters - ---------- - point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. - - Returns - ------- - float - :math:`(x' - x_0)^2 + (z' - z_0)^2 - r^2(y' - y_0)^2` - - """ x = point[0] - self.x0 y = point[1] - self.y0 z = point[2] - self.z0 - return x**2 + z**2 - self.r2*y**2 + return x*x + z*z - self.r2*y*y -class ZCone(Cone): +class ZCone(QuadricMixin, Surface): """A cone parallel to the x-axis of the form :math:`(x - x_0)^2 + (y - y_0)^2 = r^2 (z - z_0)^2`. @@ -1877,29 +2219,86 @@ class ZCone(Cone): """ _type = 'z-cone' + _coeff_keys = ('x0', 'y0', 'z0', 'r2') + + def __init__(self, x0=0., y0=0., z0=0., r2=1., **kwargs): + R2 = kwargs.pop('R2', None) + if R2 is not None: + warn(_WARNING_UPPER.format(type(self).__name__, 'r2', 'R2'), + FutureWarning) + r2 = R2 + super().__init__(**kwargs) + + for key, val in zip(self._coeff_keys, (x0, y0, z0, r2)): + setattr(self, key, val) + + @property + def x0(self): + return self.coefficients['x0'] + + @property + def y0(self): + return self.coefficients['y0'] + + @property + def z0(self): + return self.coefficients['z0'] + + @property + def r2(self): + return self.coefficients['r2'] + + @property + def dx(self): + return 0. + + @property + def dy(self): + return 0. + + @property + def dz(self): + return 1. + + @x0.setter + def x0(self, x0): + check_type('x0 coefficient', x0, Real) + self._coefficients['x0'] = x0 + + @y0.setter + def y0(self, y0): + check_type('y0 coefficient', y0, Real) + self._coefficients['y0'] = y0 + + @z0.setter + def z0(self, z0): + check_type('z0 coefficient', z0, Real) + self._coefficients['z0'] = z0 + + @r2.setter + def r2(self, r2): + check_type('r^2 coefficient', r2, Real) + self._coefficients['r2'] = r2 + + def _get_base_coeffs(self): + x0, y0, z0, r2 = self.x0, self.y0, self.z0, self.r2 + + c = -r2 + a = b = 1. + d = e = f = 0. + g, h, j = -2*x0, -2*y0, 2*z0*r2 + k = x0*x0 + y0*y0 - r2*z0*z0 + + return (a, b, c, d, e, f, g, h, j, k) def evaluate(self, point): - """Evaluate the surface equation at a given point. - - Parameters - ---------- - point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. - - Returns - ------- - float - :math:`(x' - x_0)^2 + (y' - y_0)^2 - r^2(z' - z_0)^2` - - """ x = point[0] - self.x0 y = point[1] - self.y0 z = point[2] - self.z0 - return x**2 + y**2 - self.r2*z**2 + return x*x + y*y - self.r2*z*z -class Quadric(Surface): +class Quadric(QuadricMixin, Surface): """A surface of the form :math:`Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0`. @@ -1939,18 +2338,12 @@ class Quadric(Surface): _coeff_keys = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k') def __init__(self, a=0., b=0., c=0., d=0., e=0., f=0., g=0., h=0., j=0., - k=0., boundary_type='transmission', name='', surface_id=None): - super().__init__(surface_id, boundary_type, name=name) - self.a = a - self.b = b - self.c = c - self.d = d - self.e = e - self.f = f - self.g = g - self.h = h - self.j = j - self.k = k + k=0., **kwargs): + + super().__init__(**kwargs) + + for key, val in zip(self._coeff_keys, (a, b, c, d, e, f, g, h, j, k)): + setattr(self, key, val) @property def a(self): @@ -2042,50 +2435,8 @@ class Quadric(Surface): check_type('k coefficient', k, Real) self._coefficients['k'] = k - def evaluate(self, point): - """Evaluate the surface equation at a given point. - - Parameters - ---------- - point : 3-tuple of float - The Cartesian coordinates, :math:`(x',y',z')`, at which the surface - equation should be evaluated. - - Returns - ------- - float - :math:`Ax'^2 + By'^2 + Cz'^2 + Dx'y' + Ey'z' + Fx'z' + Gx' + Hy' + - Jz' + K = 0` - - """ - x, y, z = point - return x*(self.a*x + self.d*y + self.g) + \ - y*(self.b*y + self.e*z + self.h) + \ - z*(self.c*z + self.f*x + self.j) + self.k - - def translate(self, vector): - """Translate surface in given direction - - Parameters - ---------- - vector : iterable of float - Direction in which surface should be translated - - Returns - ------- - openmc.Quadric - Translated surface - - """ - vx, vy, vz = vector - a, b, c, d, e, f, g, h, j, k = (getattr(self, key) for key in - self._coeff_keys) - k = (k + vx*vx + vy*vy + vz*vz + d*vx*vy + e*vy*vz + f*vx*vz - - g*vx - h*vy - j*vz) - g = g - 2*a*vx - d*vy - f*vz - h = h - 2*b*vy - d*vx - e*vz - j = j - 2*c*vz - e*vy - f*vx - return type(self)(a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h, j=j, k=k) + def _get_base_coeffs(self): + return tuple(getattr(self, c) for c in self._coeff_keys) class Halfspace(Region): @@ -2274,3 +2625,5 @@ class Halfspace(Region): # Return translated surface return type(self)(memo[key], self.side) + +_SURFACE_CLASSES = {cls._type: cls for cls in Surface.__subclasses__()} diff --git a/scripts/casl_chain.py b/scripts/casl_chain.py deleted file mode 100755 index 2edc1b6d9..000000000 --- a/scripts/casl_chain.py +++ /dev/null @@ -1,280 +0,0 @@ -# This dictionary contains the 255-nuclides, simplified burnup chain used in -# CASL-ORIGEN, which can be found in Appendix A of Kang Seog Kim, "Specification -# for the VERA Depletion Benchmark Suite", CASL-U-2015-1014-000, Rev. 0, -# ORNL/TM-2016/53, 2016. -# -# Note 32 of the 255 nuclides appear twice as they are both activation -# nuclides (category 1) and fission product nuclides (category 3). - -# Te129 has been added due to its link to I129 production. - -CASL_CHAIN = { - # Nuclide: (Stable, CAT, IFPY, Special yield treatment) - # Stable: True if nuclide has no decay reactions - # CAT: Category of nuclides - # 1-Activation nuclides - # 2-Heavy metal nuclides - # 3-Fission product nuclides - # IFPY: Indicator of fission product yield - # 0-Non FPY - # 1-Direct FPY (-1 indicates (stable+metastable) direct FPY) - # 2-Cumulative FPY - # 3-Special treatment with weight fractions - # Special yield: (nuclide_i/weight_i/IFPY_i) - 'B10': (True, 1, 0, None), - 'B11': (True, 1, 0, None), - 'O16': (True, 1, 0, None), - 'Ag107': (True, 1, 0, None), - 'Ag109': (True, 1, 0, None), # redundant as FP - 'Ag110': (False, 1, 0, None), # redundant as FP - 'Cd110': (True, 1, 0, None), # redundant as FP - 'Cd111': (True, 1, 0, None), # redundant as FP - 'Cd112': (True, 1, 0, None), - 'Cd113': (True, 1, 0, None), # redundant as FP - 'Cd114': (True, 1, 0, None), - 'Cd115': (False, 1, 0, None), - 'In113': (True, 1, 0, None), - 'In115': (True, 1, 0, None), # redundant as FP - 'Sm152': (True, 1, 0, None), # redundant as FP - 'Sm153': (False, 1, 0, None), # redundant as FP - 'Eu151': (True, 1, 0, None), # redundant as FP - 'Eu152': (False, 1, 0, None), - 'Eu152_m1': (False, 1, 0, None), - 'Eu153': (True, 1, 0, None), # redundant as FP - 'Eu154': (False, 1, 0, None), # redundant as FP - 'Eu155': (False, 1, 0, None), # redundant as FP - 'Eu156': (False, 1, 0, None), # redundant as FP - 'Eu157': (False, 1, 0, None), # redundant as FP - 'Gd152': (True, 1, 0, None), - 'Gd154': (True, 1, 0, None), # redundant as FP - 'Gd155': (True, 1, 0, None), # redundant as FP - 'Gd156': (True, 1, 0, None), # redundant as FP - 'Gd157': (True, 1, 0, None), # redundant as FP - 'Gd158': (True, 1, 0, None), # redundant as FP - 'Gd159': (False, 1, 0, None), # redundant as FP - 'Gd160': (True, 1, 0, None), # redundant as FP - 'Gd161': (False, 1, 0, None), # redundant as FP - 'Tb159': (True, 1, 0, None), # redundant as FP - 'Tb160': (False, 1, 0, None), # redundant as FP - 'Tb161': (False, 1, 0, None), # redundant as FP - 'Dy160': (True, 1, 0, None), # redundant as FP - 'Dy161': (True, 1, 0, None), # redundant as FP - 'Dy162': (True, 1, 0, None), # redundant as FP - 'Dy163': (True, 1, 0, None), # redundant as FP - 'Dy164': (True, 1, 0, None), # redundant as FP - 'Dy165': (False, 1, 0, None), # redundant as FP - 'Ho165': (True, 1, 0, None), # redundant as FP - 'Er162': (True, 1, 0, None), - 'Er164': (True, 1, 0, None), - 'Er166': (True, 1, 0, None), - 'Er167': (True, 1, 0, None), - 'Er168': (True, 1, 0, None), - 'Er169': (False, 1, 0, None), - 'Er170': (True, 1, 0, None), - 'Er171': (False, 1, 0, None), - 'Tm169': (True, 1, 0, None), - 'Tm170': (False, 1, 0, None), - 'Tm171': (False, 1, 0, None), - 'Hf174': (True, 1, 0, None), - 'Hf176': (True, 1, 0, None), - 'Hf177': (True, 1, 0, None), - 'Hf178': (True, 1, 0, None), - 'Hf179': (True, 1, 0, None), - 'Hf180': (True, 1, 0, None), - 'Hf181': (False, 1, 0, None), - 'Ta181': (True, 1, 0, None), - 'Ta182': (False, 1, 0, None), - 'Th230': (False, 2, 0, None), - 'Th231': (False, 2, 0, None), - 'Th232': (False, 2, 0, None), - 'Th233': (False, 2, 0, None), - 'Th234': (False, 2, 0, None), - 'Pa231': (False, 2, 0, None), - 'Pa232': (False, 2, 0, None), - 'Pa233': (False, 2, 0, None), - 'Pa234': (False, 2, 0, None), - 'U232': (False, 2, 0, None), - 'U233': (False, 2, 0, None), - 'U234': (False, 2, 0, None), - 'U235': (False, 2, 0, None), - 'U236': (False, 2, 0, None), - 'U237': (False, 2, 0, None), - 'U238': (False, 2, 0, None), - 'U239': (False, 2, 0, None), - 'Np236': (False, 2, 0, None), - 'Np237': (False, 2, 0, None), - 'Np238': (False, 2, 0, None), - 'Np239': (False, 2, 0, None), - 'Np240': (False, 2, 0, None), - 'Np240_m1': (False, 2, 0, None), - 'Pu236': (False, 2, 0, None), - 'Pu237': (False, 2, 0, None), - 'Pu238': (False, 2, 0, None), - 'Pu239': (False, 2, 0, None), - 'Pu240': (False, 2, 0, None), - 'Pu241': (False, 2, 0, None), - 'Pu242': (False, 2, 0, None), - 'Pu243': (False, 2, 0, None), - 'Am241': (False, 2, 0, None), - 'Am242': (False, 2, 0, None), - 'Am242_m1': (False, 2, 0, None), - 'Am243': (False, 2, 0, None), - 'Am244': (False, 2, 0, None), - 'Am244_m1': (False, 2, 0, None), - 'Cm242': (False, 2, 0, None), - 'Cm243': (False, 2, 0, None), - 'Cm244': (False, 2, 0, None), - 'Cm245': (False, 2, 0, None), - 'Cm246': (False, 2, 0, None), - 'Br81': (True, 3, 2, None), - 'Br82': (False, 3, 2, None), - 'Kr82': (True, 3, 3, [('Br82_m1', 0.024, 1), ('Kr82', 1.000, 1)]), - 'Kr83': (True, 3, 2, None), - 'Kr84': (True, 3, 2, None), - 'Kr85': (False, 3, 2, None), - 'Kr86': (True, 3, 2, None), - 'Sr89': (False, 3, 2, None), - 'Sr90': (False, 3, 2, None), - 'Y89': (True, 3, 1, None), - 'Y90': (False, 3, 1, None), - 'Y91': (False, 3, 2, None), - 'Zr91': (True, 3, 1, None), - 'Zr93': (False, 3, 2, None), - 'Zr95': (False, 3, 2, None), - 'Zr96': (True, 3, 2, None), - 'Nb95': (False, 3, 3, [('Nb95',1.000, 1), ('Nb95_m1', 0.944, 1)]), - 'Mo95': (True, 3, 3, [('Nb95_m1',0.056, 1), ('Mo95', 1.000, 1)]), - 'Mo96': (True, 3, 3, [('Nb96',1.000, 1), ('Mo96', 1.000, 1)]), - 'Mo97': (True, 3, 2, None), - 'Mo98': (True, 3, 2, None), - 'Mo99': (False, 3, 2, None), - 'Mo100': (True, 3, 2, None), - 'Tc99': (False, 3, 1, None), - 'Tc99_m1': (False, 3, 1, None), - 'Tc100': (False, 3, 1, None), - 'Ru100': (True, 3, 1, None), - 'Ru101': (True, 3, 2, None), - 'Ru102': (True, 3, 2, None), - 'Ru103': (False, 3, 2, None), - 'Ru104': (True, 3, 2, None), - 'Ru105': (False, 3, 2, None), - 'Ru106': (False, 3, 2, None), - 'Rh102': (False, 3, 1, None), - 'Rh102_m1': (False, 3, 1, None), - 'Rh103': (True, 3, 1, None), - 'Rh103_m1': (False, 3, 1, None), - 'Rh104': (False, 3, 1, None), - 'Rh105': (False, 3, 1, None), - 'Rh105_m1': (False, 3, 1, None), - 'Rh106': (False, 3, 1, None), - 'Rh106_m1': (False, 3, 1, None), - 'Pd104': (True, 3, 1, None), - 'Pd105': (True, 3, 1, None), - 'Pd106': (True, 3, 1, None), - 'Pd107': (False, 3, 2, None), - 'Pd108': (True, 3, 2, None), - 'Pd109': (False, 3, 2, None), - 'Ag109': (True, 3, 1, None), - 'Ag109_m1': (False, 3, 1, None), - 'Ag110': (False, 3, 2, None), - 'Ag110_m1': (False, 3, 2, None), - 'Ag111': (False, 3, 2, None), - 'Cd110': (True, 3, 1, None), - 'Cd111': (True, 3, 3, [('Ag110', -1.000, 2), ('Cd110', 1.000, 2), ('Cd111', 1.000, 1)]), - 'Cd113': (True, 3, 2, None), - 'In115': (True, 3, 2, None), - 'Sb121': (True, 3, 2, None), - 'Sb123': (False, 3, 2, None), - 'Sb125': (False, 3, 2, None), - 'Sb127': (False, 3, 2, None), - 'Te127': (False, 3, -1, None), - 'Te127_m1': (False, 3, -1, None), - 'Te129': (False, 3, 1, None), - 'Te129_m1': (False, 3, 2, None), - 'Te132': (False, 3, 2, None), - 'I127': (True, 3, 1, None), - 'I128': (False, 3, 3, [('I128', 0.931, 2)]), - 'I129': (False, 3, 3, [('I129', 1.000, 2), ('I129', -1.000, 2)]), - 'I130': (False, 3, 2, None), - 'I131': (False, 3, 2, None), - 'I132': (False, 3, 1, None), - 'I135': (False, 3, 2, None), - 'Xe128': (True, 3, 1, None), - 'Xe130': (True, 3, 1, None), - 'Xe131': (True, 3, 1, None), - 'Xe132': (True, 3, 1, None), - 'Xe133': (False, 3, 2, None), - 'Xe134': (True, 3, 2, None), - 'Xe135': (False, 3, 1, None), - 'Xe135_m1': (False, 3, 1, None), - 'Xe136': (True, 3, 2, None), - 'Xe137': (False, 3, 2, None), - 'Cs133': (True, 3, 1, None), - 'Cs134': (False, 3, 1, None), - 'Cs135': (False, 3, 1, None), - 'Cs136': (False, 3, 1, None), - 'Cs137': (False, 3, 1, None), - 'Ba134': (True, 3, 1, None), - 'Ba137': (True, 3, 1, None), - 'Ba140': (False, 3, 2, None), - 'La139': (True, 3, 2, None), - 'La140': (False, 3, 1, None), - 'Ce140': (True, 3, 1, None), - 'Ce141': (False, 3, 2, None), - 'Ce142': (True, 3, 2, None), - 'Ce143': (False, 3, 2, None), - 'Ce144': (False, 3, 2, None), - 'Pr141': (True, 3, 1, None), - 'Pr142': (False, 3, 1, None), - 'Pr143': (False, 3, 1, None), - 'Pr144': (False, 3, 1, None), - 'Nd142': (True, 3, 1, None), - 'Nd143': (True, 3, 1, None), - 'Nd144': (False, 3, 1, None), - 'Nd145': (True, 3, 2, None), - 'Nd146': (True, 3, 2, None), - 'Nd147': (False, 3, 2, None), - 'Nd148': (True, 3, 2, None), - 'Nd149': (False, 3, 2, None), - 'Nd150': (True, 3, 2, None), - 'Nd151': (False, 3, 2, None), - 'Pm147': (False, 3, 1, None), - 'Pm148': (False, 3, -1, None), - 'Pm148_m1': (False, 3, -1, None), - 'Pm149': (False, 3, 1, None), - 'Pm150': (False, 3, 1, None), - 'Pm151': (False, 3, 1, None), - 'Sm147': (False, 3, 1, None), - 'Sm148': (False, 3, 1, None), - 'Sm149': (False, 3, 1, None), - 'Sm150': (True, 3, 1, None), - 'Sm151': (False, 3, 1, None), - 'Sm152': (True, 3, 2, None), - 'Sm153': (False, 3, 2, None), - 'Sm154': (True, 3, 2, None), - 'Sm155': (False, 3, 2, None), - 'Eu151': (True, 3, 1, None), - 'Eu153': (True, 3, 1, None), - 'Eu154': (False, 3, 1, None), - 'Eu155': (False, 3, 1, None), - 'Eu156': (False, 3, 2, None), - 'Eu157': (False, 3, 2, None), - 'Gd154': (True, 3, 1, None), - 'Gd155': (True, 3, 1, None), - 'Gd156': (True, 3, 1, None), - 'Gd157': (True, 3, 1, None), - 'Gd158': (True, 3, 2, None), - 'Gd159': (False, 3, 2, None), - 'Gd160': (True, 3, 2, None), - 'Gd161': (False, 3, 2, None), - 'Tb159': (True, 3, 1, None), - 'Tb160': (False, 3, 1, None), - 'Tb161': (False, 3, 1, None), - 'Dy160': (True, 3, 1, None), - 'Dy161': (True, 3, 1, None), - 'Dy162': (True, 3, 2, None), - 'Dy163': (True, 3, 2, None), - 'Dy164': (True, 3, 2, None), - 'Dy165': (False, 3, 2, None), - 'Ho165': (True, 3, 3, [('Dy165_m1', 0.022, 2), ('Ho165', 1.000, 1)]) -} diff --git a/scripts/openmc-make-depletion-chain b/scripts/openmc-make-depletion-chain deleted file mode 100755 index 01f009a2a..000000000 --- a/scripts/openmc-make-depletion-chain +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 - -import os -from pathlib import Path -from zipfile import ZipFile - -from openmc._utils import download -import openmc.deplete - - -URLS = [ - 'https://www.nndc.bnl.gov/endf/b7.1/zips/ENDF-B-VII.1-neutrons.zip', - 'https://www.nndc.bnl.gov/endf/b7.1/zips/ENDF-B-VII.1-decay.zip', - 'https://www.nndc.bnl.gov/endf/b7.1/zips/ENDF-B-VII.1-nfy.zip' -] - -def main(): - endf_dir = os.environ.get("OPENMC_ENDF_DATA") - if endf_dir is not None: - endf_dir = Path(endf_dir) - elif all(os.path.isdir(lib) for lib in ("neutrons", "decay", "nfy")): - endf_dir = Path(".") - else: - for url in URLS: - basename = download(url) - with ZipFile(basename, 'r') as zf: - print('Extracting {}...'.format(basename)) - zf.extractall() - endf_dir = Path(".") - - decay_files = tuple((endf_dir / "decay").glob("*endf")) - neutron_files = tuple((endf_dir / "neutrons").glob("*endf")) - nfy_files = tuple((endf_dir / "nfy").glob("*endf")) - - # check files exist - for flist, ftype in [(decay_files, "decay"), (neutron_files, "neutron"), - (nfy_files, "neutron fission product yield")]: - if not flist: - raise IOError("No {} endf files found in {}".format(ftype, endf_dir)) - - chain = openmc.deplete.Chain.from_endf(decay_files, nfy_files, neutron_files) - chain.export_to_xml('chain_endfb71.xml') - - -if __name__ == '__main__': - main() diff --git a/scripts/openmc-make-depletion-chain-casl b/scripts/openmc-make-depletion-chain-casl deleted file mode 100755 index 9da3b01c3..000000000 --- a/scripts/openmc-make-depletion-chain-casl +++ /dev/null @@ -1,247 +0,0 @@ -#!/usr/bin/env python3 - -import glob -import os -from zipfile import ZipFile -from collections import OrderedDict, defaultdict -from io import StringIO -from itertools import chain - -try: - import lxml.etree as ET - _have_lxml = True -except ImportError: - import xml.etree.ElementTree as ET - _have_lxml = False - -import openmc.data -import openmc.deplete -from openmc._xml import clean_indentation -from openmc.deplete.chain import _REACTIONS -from openmc.deplete.nuclide import Nuclide, DecayTuple, ReactionTuple, \ - FissionYieldDistribution -from openmc._utils import download - -from casl_chain import CASL_CHAIN - -URLS = [ - 'https://www.nndc.bnl.gov/endf/b7.1/zips/ENDF-B-VII.1-neutrons.zip', - 'https://www.nndc.bnl.gov/endf/b7.1/zips/ENDF-B-VII.1-decay.zip', - 'https://www.nndc.bnl.gov/endf/b7.1/zips/ENDF-B-VII.1-nfy.zip' -] - -def main(): - if os.path.isdir('./decay') and os.path.isdir('./nfy') and os.path.isdir('./neutrons'): - endf_dir = '.' - elif 'OPENMC_ENDF_DATA' in os.environ: - endf_dir = os.environ['OPENMC_ENDF_DATA'] - else: - for url in URLS: - basename = download(url) - with ZipFile(basename, 'r') as zf: - print('Extracting {}...'.format(basename)) - zf.extractall() - endf_dir = '.' - - decay_files = glob.glob(os.path.join(endf_dir, 'decay', '*.endf')) - fpy_files = glob.glob(os.path.join(endf_dir, 'nfy', '*.endf')) - neutron_files = glob.glob(os.path.join(endf_dir, 'neutrons', '*.endf')) - - # Create a Chain - chain = openmc.deplete.Chain() - - print('Reading ENDF nuclear data from "{}"...'.format(os.path.abspath(endf_dir))) - - # Create dictionary mapping target to filename - print('Processing neutron sub-library files...') - reactions = {} - for f in neutron_files: - evaluation = openmc.data.endf.Evaluation(f) - nuc_name = evaluation.gnd_name - if nuc_name in CASL_CHAIN: - reactions[nuc_name] = {} - for mf, mt, nc, mod in evaluation.reaction_list: - # Q value for each reaction is given in MF=3 - if mf == 3: - file_obj = StringIO(evaluation.section[3, mt]) - openmc.data.endf.get_head_record(file_obj) - q_value = openmc.data.endf.get_cont_record(file_obj)[1] - reactions[nuc_name][mt] = q_value - - # Determine what decay and FPY nuclides are available - print('Processing decay sub-library files...') - decay_data = {} - for f in decay_files: - decay_obj = openmc.data.Decay(f) - nuc_name = decay_obj.nuclide['name'] - if nuc_name in CASL_CHAIN: - decay_data[nuc_name] = decay_obj - - for nuc_name in CASL_CHAIN: - if nuc_name not in decay_data: - print('WARNING: {} has no decay data!'.format(nuc_name)) - - print('Processing fission product yield sub-library files...') - fpy_data = {} - for f in fpy_files: - fpy_obj = openmc.data.FissionProductYields(f) - name = fpy_obj.nuclide['name'] - if name in CASL_CHAIN: - fpy_data[name] = fpy_obj - - print('Creating depletion_chain...') - missing_daughter = [] - missing_rx_product = [] - missing_fpy = [] - - for idx, parent in enumerate(sorted(decay_data, key=openmc.data.zam)): - data = decay_data[parent] - - nuclide = Nuclide() - nuclide.name = parent - - chain.nuclides.append(nuclide) - chain.nuclide_dict[parent] = idx - - if not CASL_CHAIN[parent][0] and \ - not data.nuclide['stable'] and data.half_life.nominal_value != 0.0: - nuclide.half_life = data.half_life.nominal_value - nuclide.decay_energy = sum(E.nominal_value for E in - data.average_energies.values()) - sum_br = 0.0 - for i, mode in enumerate(data.modes): - decay_type = ','.join(mode.modes) - if mode.daughter in decay_data: - target = mode.daughter - else: - missing_daughter.append((parent, mode)) - continue - - # Write branching ratio, taking care to ensure sum is unity by - # slightly modifying last value if necessary - br = mode.branching_ratio.nominal_value - sum_br += br - if i == len(data.modes) - 1 and sum_br != 1.0: - br = 1.0 - sum(m.branching_ratio.nominal_value - for m in data.modes[:-1]) - - # Append decay mode - nuclide.decay_modes.append(DecayTuple(decay_type, target, br)) - - # If nuclide has incident neutron data, we need to list what - # transmutation reactions are possible - if parent in reactions: - reactions_available = reactions[parent].keys() - for name, mts, changes in _REACTIONS: - if mts & reactions_available: - delta_A, delta_Z = changes - A = data.nuclide['mass_number'] + delta_A - Z = data.nuclide['atomic_number'] + delta_Z - daughter = '{}{}'.format(openmc.data.ATOMIC_SYMBOL[Z], A) - - if name not in chain.reactions: - chain.reactions.append(name) - - if daughter not in decay_data: - missing_rx_product.append((parent, name, daughter)) - daughter = 'Nothing' - - # Store Q value -- use sorted order so we get summation - # reactions (e.g., MT=103) first - for mt in sorted(mts): - if mt in reactions[parent]: - q_value = reactions[parent][mt] - break - else: - q_value = 0.0 - - nuclide.reactions.append(ReactionTuple( - name, daughter, q_value, 1.0)) - - # Check for fission reactions - if any(mt in reactions_available for mt in [18, 19, 20, 21, 38]): - if parent in fpy_data: - q_value = reactions[parent][18] - nuclide.reactions.append( - ReactionTuple('fission', 0, q_value, 1.0)) - - if 'fission' not in chain.reactions: - chain.reactions.append('fission') - else: - missing_fpy.append(parent) - - if parent in fpy_data: - fpy = fpy_data[parent] - - if fpy.energies is not None: - yield_energies = fpy.energies - else: - yield_energies = [0.0] - - yield_data = {} - for E, table_yd, table_yc in zip(yield_energies, fpy.independent, fpy.cumulative): - yields = defaultdict(float) - for product in table_yd: - if product in decay_data: - # identifier - ifpy = CASL_CHAIN[product][2] - # 1 for independent - if ifpy == 1: - if product not in table_yd: - print('No independent fission yields found for {} in {}'.format(product, parent)) - else: - yields[product] += table_yd[product].nominal_value - # 2 for cumulative - elif ifpy == 2: - if product not in table_yc: - print('No cumulative fission yields found for {} in {}'.format(product, parent)) - else: - yields[product] += table_yc[product].nominal_value - # -1 for independent (stable + metastable) - elif ifpy == -1: - if product not in table_yd: - print('No independent fission yields found for {} in {}'.format(product, parent)) - else: - yields[product] += table_yc[product].nominal_value - product_meta = '{}_m1'.format(product) - if product_meta in table_yd: - yields[product] += table_yc[product_meta].nominal_value - # 3 for special treatment with weight fractions - elif ifpy == 3: - for name_i, weight_i, ifpy_i in CASL_CHAIN[product][3]: - if name_i not in table_yd: - print('No fission yields found for {} in {}'.format(name_i, parent)) - else: - if ifpy_i == 1: - yields[product] += weight_i * table_yd[name_i].nominal_value - elif ifpy_i == 2: - yields[product] += weight_i * table_yc[name_i].nominal_value - - yield_data[E] = yields - - nuclide.yield_data = FissionYieldDistribution(yield_data) - - # Display warnings - if missing_daughter: - print('The following decay modes have daughters with no decay data:') - for parent, mode in missing_daughter: - print(' {} -> {} ({})'.format(parent, mode.daughter, ','.join(mode.modes))) - print('') - - if missing_rx_product: - print('The following reaction products have no decay data:') - for vals in missing_rx_product: - print('{} {} -> {}'.format(*vals)) - print('') - - if missing_fpy: - print('The following fissionable nuclides have no fission product yields:') - for parent in missing_fpy: - print(' ' + parent) - print('') - - chain.export_to_xml('chain_casl.xml') - - -if __name__ == '__main__': - main() diff --git a/setup.py b/setup.py index 99db83148..c193645c3 100755 --- a/setup.py +++ b/setup.py @@ -56,14 +56,13 @@ kwargs = { 'Topic :: Scientific/Engineering' 'Programming Language :: C++', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', ], # Dependencies - 'python_requires': '>=3.4', + 'python_requires': '>=3.5', 'install_requires': [ 'numpy>=1.9', 'h5py', 'scipy', 'ipython', 'matplotlib', 'pandas', 'lxml', 'uncertainties' diff --git a/src/cell.cpp b/src/cell.cpp index d56e877d3..1d175f05d 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -8,6 +8,8 @@ #include #include #include + +#include #include #include "openmc/capi.h" @@ -83,9 +85,8 @@ tokenize(const std::string region_spec) { i++; } else { - std::stringstream err_msg; - err_msg << "Region specification contains invalid character, \"" - << region_spec[i] << "\""; + auto err_msg = fmt::format( + "Region specification contains invalid character, \"{}\"", region_spec[i]); fatal_error(err_msg); } } @@ -156,10 +157,8 @@ generate_rpn(int32_t cell_id, std::vector infix) // If we run out of operators without finding a left parenthesis, it // means there are mismatched parentheses. if (it == stack.rend()) { - std::stringstream err_msg; - err_msg << "Mismatched parentheses in region specification for cell " - << cell_id; - fatal_error(err_msg); + fatal_error(fmt::format( + "Mismatched parentheses in region specification for cell {}", cell_id)); } rpn.push_back(stack.back()); stack.pop_back(); @@ -175,10 +174,8 @@ generate_rpn(int32_t cell_id, std::vector infix) // If the operator is a parenthesis it is mismatched. if (op >= OP_RIGHT_PAREN) { - std::stringstream err_msg; - err_msg << "Mismatched parentheses in region specification for cell " - << cell_id; - fatal_error(err_msg); + fatal_error(fmt::format( + "Mismatched parentheses in region specification for cell {}", cell_id)); } rpn.push_back(stack.back()); @@ -196,9 +193,7 @@ void Universe::to_hdf5(hid_t universes_group) const { // Create a group for this universe. - std::stringstream group_name; - group_name << "universe " << id_; - auto group = create_group(universes_group, group_name); + auto group = create_group(universes_group, fmt::format("universe {}", id_)); // Write the contained cells. if (cells_.size() > 0) { @@ -299,22 +294,19 @@ CSGCell::CSGCell(pugi::xml_node cell_node) bool fill_present = check_for_node(cell_node, "fill"); bool material_present = check_for_node(cell_node, "material"); if (!(fill_present || material_present)) { - std::stringstream err_msg; - err_msg << "Neither material nor fill was specified for cell " << id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "Neither material nor fill was specified for cell {}", id_)); } if (fill_present && material_present) { - std::stringstream err_msg; - err_msg << "Cell " << id_ << " has both a material and a fill specified; " - << "only one can be specified per cell"; - fatal_error(err_msg); + fatal_error(fmt::format("Cell {} has both a material and a fill specified; " + "only one can be specified per cell", id_)); } if (fill_present) { fill_ = std::stoi(get_node_value(cell_node, "fill")); if (fill_ == universe_) { - fatal_error("Cell " + std::to_string(id_) + - " is filled with the same universe that it is contained in."); + fatal_error(fmt::format("Cell {} is filled with the same universe that" + "it is contained in.", id_)); } } else { fill_ = C_NONE; @@ -336,9 +328,8 @@ CSGCell::CSGCell(pugi::xml_node cell_node) } } } else { - std::stringstream err_msg; - err_msg << "An empty material element was specified for cell " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("An empty material element was specified for cell {}", + id_)); } } @@ -349,20 +340,16 @@ CSGCell::CSGCell(pugi::xml_node cell_node) // Make sure this is a material-filled cell. if (material_.size() == 0) { - std::stringstream err_msg; - err_msg << "Cell " << id_ << " was specified with a temperature but " - "no material. Temperature specification is only valid for cells " - "filled with a material."; - fatal_error(err_msg); + fatal_error(fmt::format( + "Cell {} was specified with a temperature but no material. Temperature" + "specification is only valid for cells filled with a material.", id_)); } // Make sure all temperatures are non-negative. for (auto T : sqrtkT_) { if (T < 0) { - std::stringstream err_msg; - err_msg << "Cell " << id_ - << " was specified with a negative temperature"; - fatal_error(err_msg); + fatal_error(fmt::format( + "Cell {} was specified with a negative temperature", id_)); } } @@ -390,7 +377,7 @@ CSGCell::CSGCell(pugi::xml_node cell_node) throw std::runtime_error{"Invalid surface ID " + std::to_string(abs(r)) + " specified in region for cell " + std::to_string(id_) + "."}; } - r = copysign(it->second + 1, r); + r = (r > 0) ? it->second + 1 : -(it->second + 1); } } @@ -424,17 +411,14 @@ CSGCell::CSGCell(pugi::xml_node cell_node) // Read the translation vector. if (check_for_node(cell_node, "translation")) { if (fill_ == C_NONE) { - std::stringstream err_msg; - err_msg << "Cannot apply a translation to cell " << id_ - << " because it is not filled with another universe"; - fatal_error(err_msg); + fatal_error(fmt::format("Cannot apply a translation to cell {}" + " because it is not filled with another universe", id_)); } auto xyz {get_node_array(cell_node, "translation")}; if (xyz.size() != 3) { - std::stringstream err_msg; - err_msg << "Non-3D translation vector applied to cell " << id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "Non-3D translation vector applied to cell {}", id_)); } translation_ = xyz; } @@ -442,17 +426,14 @@ CSGCell::CSGCell(pugi::xml_node cell_node) // Read the rotation transform. if (check_for_node(cell_node, "rotation")) { if (fill_ == C_NONE) { - std::stringstream err_msg; - err_msg << "Cannot apply a rotation to cell " << id_ - << " because it is not filled with another universe"; - fatal_error(err_msg); + fatal_error(fmt::format("Cannot apply a rotation to cell {}" + " because it is not filled with another universe", id_)); } auto rot {get_node_array(cell_node, "rotation")}; if (rot.size() != 3 && rot.size() != 9) { - std::stringstream err_msg; - err_msg << "Non-3D rotation vector applied to cell " << id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "Non-3D rotation vector applied to cell {}", id_)); } // Compute and store the rotation matrix. @@ -532,9 +513,7 @@ void CSGCell::to_hdf5(hid_t cell_group) const { // Create a group for this cell. - std::stringstream group_name; - group_name << "cell " << id_; - auto group = create_group(cell_group, group_name); + auto group = create_group(cell_group, fmt::format("cell {}", id_)); if (!name_.empty()) { write_string(group, "name", name_, false); @@ -557,8 +536,8 @@ CSGCell::to_hdf5(hid_t cell_group) const region_spec << " |"; } else { // Note the off-by-one indexing - region_spec << " " - << copysign(model::surfaces[abs(token)-1]->id_, token); + auto surf_id = model::surfaces[abs(token)-1]->id_; + region_spec << " " << ((token > 0) ? surf_id : -surf_id); } } write_string(group, "region", region_spec.str(), false); @@ -1011,9 +990,7 @@ void read_cells(pugi::xml_node node) if (search == model::cell_map.end()) { model::cell_map[id] = i; } else { - std::stringstream err_msg; - err_msg << "Two or more cells use the same unique ID: " << id; - fatal_error(err_msg); + fatal_error(fmt::format("Two or more cells use the same unique ID: {}", id)); } } diff --git a/src/dagmc.cpp b/src/dagmc.cpp index e14a6062a..59435f245 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -12,11 +12,10 @@ #include "openmc/surface.h" #ifdef DAGMC - #include "uwuw.hpp" #include "dagmcmetadata.hpp" - #endif +#include #include #include @@ -110,11 +109,10 @@ void legacy_assign_material(const std::string& mat_string, DAGCell* c) c->material_.push_back(m->id_); // report error if more than one material is found } else { - std::stringstream err_msg; - err_msg << "More than one material found with name " << mat_string - << ". Please ensure materials have unique names if using this" - << " property to assign materials."; - fatal_error(err_msg); + fatal_error(fmt::format( + "More than one material found with name {}. Please ensure materials " + "have unique names if using this property to assign materials.", + mat_string)); } } } @@ -125,10 +123,8 @@ void legacy_assign_material(const std::string& mat_string, DAGCell* c) auto id = std::stoi(mat_string); c->material_.emplace_back(id); } catch (const std::invalid_argument&) { - std::stringstream err_msg; - err_msg << "No material " << mat_string - << " found for volume (cell) " << c->id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "No material {} found for volume (cell) {}", mat_string, c->id_)); } } @@ -153,7 +149,6 @@ void load_dagmc_geometry() model::DAG = new moab::DagMC(); } - std::string filename = settings::path_input + DAGMC_FILENAME; // --- Materials --- @@ -253,9 +248,7 @@ void load_dagmc_geometry() rval = model::DAG->prop_value(vol_handle, "mat", mat_value); MB_CHK_ERR_CONT(rval); } else { - std::stringstream err_msg; - err_msg << "Volume " << c->id_ << " has no material assignment."; - fatal_error(err_msg.str()); + fatal_error(fmt::format("Volume {} has no material assignment.", c->id_)); } std::string cmp_str = mat_value; @@ -277,10 +270,8 @@ void load_dagmc_geometry() int mat_number = uwuw.material_library[uwuw_mat].metadata["mat_number"].asInt(); c->material_.push_back(mat_number); } else { - std::stringstream err_msg; - err_msg << "Material with value " << mat_value << " not found "; - err_msg << "in the UWUW material library"; - fatal_error(err_msg); + fatal_error(fmt::format("Material with value {} not found in the " + "UWUW material library", mat_value)); } } else { legacy_assign_material(mat_value, c); @@ -348,10 +339,8 @@ void load_dagmc_geometry() } else if (bc_value == "periodic") { fatal_error("Periodic boundary condition not supported in DAGMC."); } else { - std::stringstream err_msg; - err_msg << "Unknown boundary condition \"" << bc_value - << "\" specified on surface " << s->id_; - fatal_error(err_msg); + fatal_error(fmt::format("Unknown boundary condition \"{}\" specified " + "on surface {}", bc_value, s->id_)); } } else { // if no condition is found, set to transmit diff --git a/src/distribution_spatial.cpp b/src/distribution_spatial.cpp index 12432baf7..125d8afa6 100644 --- a/src/distribution_spatial.cpp +++ b/src/distribution_spatial.cpp @@ -96,9 +96,7 @@ CylindricalIndependent::CylindricalIndependent(pugi::xml_node node) if (origin.size() == 3) { origin_ = origin; } else { - std::stringstream err_msg; - err_msg << "Origin for cylindrical source distribution must be length 3"; - fatal_error(err_msg); + fatal_error("Origin for cylindrical source distribution must be length 3"); } } else { // If no coordinates were specified, default to (0, 0, 0) @@ -162,9 +160,7 @@ SphericalIndependent::SphericalIndependent(pugi::xml_node node) if (origin.size() == 3) { origin_ = origin; } else { - std::stringstream err_msg; - err_msg << "Origin for spherical source distribution must be length 3"; - fatal_error(err_msg); + fatal_error("Origin for spherical source distribution must be length 3"); } } else { // If no coordinates were specified, default to (0, 0, 0) diff --git a/src/geometry.cpp b/src/geometry.cpp index 8bbc4d90c..055f0ec7f 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -1,7 +1,9 @@ #include "openmc/geometry.h" #include -#include + +#include +#include #include "openmc/cell.h" #include "openmc/constants.h" @@ -47,11 +49,9 @@ bool check_cell_overlap(Particle* p, bool error) if (c.contains(p->coord_[j].r, p->coord_[j].u, p->surface_)) { if (index_cell != p->coord_[j].cell) { if (error) { - std::stringstream err_msg; - err_msg << "Overlapping cells detected: " << c.id_ << ", " - << model::cells[p->coord_[j].cell]->id_ << " on universe " - << univ.id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "Overlapping cells detected: {}, {} on universe {}", + c.id_, model::cells[p->coord_[j].cell]->id_, univ.id_)); } return true; } @@ -120,8 +120,7 @@ find_cell_inner(Particle* p, const NeighborList* neighbor_list) // Announce the cell that the particle is entering. if (found && (settings::verbosity >= 10 || p->trace_)) { - std::stringstream msg; - msg << " Entering cell " << model::cells[i_cell]->id_; + auto msg = fmt::format(" Entering cell {}", model::cells[i_cell]->id_); write_message(msg, 1); } @@ -229,11 +228,8 @@ find_cell_inner(Particle* p, const NeighborList* neighbor_list) if (lat.outer_ != NO_OUTER_UNIVERSE) { coord.universe = lat.outer_; } else { - std::stringstream err_msg; - err_msg << "Particle " << p->id_ << " is outside lattice " - << lat.id_ << " but the lattice has no defined outer " - "universe."; - warning(err_msg); + warning(fmt::format("Particle {} is outside lattice {} but the " + "lattice has no defined outer universe.", p->id_, lat.id_)); return false; } } @@ -298,11 +294,9 @@ cross_lattice(Particle* p, const BoundaryInfo& boundary) auto& lat {*model::lattices[coord.lattice]}; if (settings::verbosity >= 10 || p->trace_) { - std::stringstream msg; - msg << " Crossing lattice " << lat.id_ << ". Current position (" - << coord.lattice_x << "," << coord.lattice_y << "," - << coord.lattice_z << "). r=" << p->r(); - write_message(msg, 1); + write_message(fmt::format( + " Crossing lattice {}. Current position ({},{},{}). r={}", + lat.id_, coord.lattice_x, coord.lattice_y, coord.lattice_z, p->r()), 1); } // Set the lattice indices. @@ -326,10 +320,8 @@ cross_lattice(Particle* p, const BoundaryInfo& boundary) p->n_coord_ = 1; bool found = find_cell(p, 0); if (!found && p->alive_) { - std::stringstream err_msg; - err_msg << "Could not locate particle " << p->id_ - << " after crossing a lattice boundary"; - p->mark_as_lost(err_msg); + p->mark_as_lost(fmt::format("Could not locate particle {} after " + "crossing a lattice boundary", p->id_)); } } else { @@ -343,10 +335,8 @@ cross_lattice(Particle* p, const BoundaryInfo& boundary) p->n_coord_ = 1; bool found = find_cell(p, 0); if (!found && p->alive_) { - std::stringstream err_msg; - err_msg << "Could not locate particle " << p->id_ - << " after crossing a lattice boundary"; - p->mark_as_lost(err_msg); + p->mark_as_lost(fmt::format("Could not locate particle {} after " + "crossing a lattice boundary", p->id_)); } } } @@ -400,10 +390,8 @@ BoundaryInfo distance_to_boundary(Particle* p) level_lat_trans = lattice_distance.second; if (d_lat < 0) { - std::stringstream err_msg; - err_msg << "Particle " << p->id_ - << " had a negative distance to a lattice boundary"; - p->mark_as_lost(err_msg); + p->mark_as_lost(fmt::format( + "Particle {} had a negative distance to a lattice boundary", p->id_)); } } @@ -462,10 +450,7 @@ openmc_find_cell(const double* xyz, int32_t* index, int32_t* instance) p.u() = {0.0, 0.0, 1.0}; if (!find_cell(&p, false)) { - std::stringstream msg; - msg << "Could not find cell at position (" << p.r().x << ", " << p.r().y - << ", " << p.r().z << ")."; - set_errmsg(msg); + set_errmsg(fmt::format("Could not find cell at position {}.", p.r())); return OPENMC_E_GEOMETRY; } diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 6ee289949..ae6d894aa 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -4,7 +4,8 @@ #include #include -#include "pugixml.hpp" +#include +#include #include "openmc/cell.h" #include "openmc/constants.h" @@ -94,10 +95,8 @@ adjust_indices() c->type_ = Fill::LATTICE; c->fill_ = search_lat->second; } else { - std::stringstream err_msg; - err_msg << "Specified fill " << id << " on cell " << c->id_ - << " is neither a universe nor a lattice."; - fatal_error(err_msg); + fatal_error(fmt::format("Specified fill {} on cell {} is neither a " + "universe nor a lattice.", id, c->id_)); } } else { c->type_ = Fill::MATERIAL; @@ -105,10 +104,9 @@ adjust_indices() if (mat_id != MATERIAL_VOID) { auto search = model::material_map.find(mat_id); if (search == model::material_map.end()) { - std::stringstream err_msg; - err_msg << "Could not find material " << mat_id - << " specified on cell " << c->id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "Could not find material {} specified on cell {}", + mat_id, c->id_)); } // Change from ID to index mat_id = search->second; @@ -123,10 +121,8 @@ adjust_indices() if (search != model::universe_map.end()) { c->universe_ = search->second; } else { - std::stringstream err_msg; - err_msg << "Could not find universe " << c->universe_ - << " specified on cell " << c->id_; - fatal_error(err_msg); + fatal_error(fmt::format("Could not find universe {} specified on cell {}", + c->universe_, c->id_)); } } @@ -345,23 +341,21 @@ prepare_distribcell() if (c.material_.size() > 1) { if (c.material_.size() != c.n_instances_) { - std::stringstream err_msg; - err_msg << "Cell " << c.id_ << " was specified with " - << c.material_.size() << " materials but has " << c.n_instances_ - << " distributed instances. The number of materials must equal " - "one or the number of instances."; - fatal_error(err_msg); + fatal_error(fmt::format( + "Cell {} was specified with {} materials but has {} distributed " + "instances. The number of materials must equal one or the number " + "of instances.", c.id_, c.material_.size(), c.n_instances_ + )); } } if (c.sqrtkT_.size() > 1) { if (c.sqrtkT_.size() != c.n_instances_) { - std::stringstream err_msg; - err_msg << "Cell " << c.id_ << " was specified with " - << c.sqrtkT_.size() << " temperatures but has " << c.n_instances_ - << " distributed instances. The number of temperatures must equal " - "one or the number of instances."; - fatal_error(err_msg); + fatal_error(fmt::format( + "Cell {} was specified with {} temperatures but has {} distributed " + "instances. The number of temperatures must equal one or the number " + "of instances.", c.id_, c.sqrtkT_.size(), c.n_instances_ + )); } } } diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index b1a90cb8f..1fcc97f3c 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -2,12 +2,12 @@ #include #include -#include #include #include #include "xtensor/xtensor.hpp" #include "xtensor/xarray.hpp" +#include #include "hdf5.h" #include "hdf5_hl.h" @@ -49,7 +49,8 @@ get_shape(hid_t obj_id, hsize_t* dims) } else if (type == H5I_ATTR) { dspace = H5Aget_space(obj_id); } else { - throw std::runtime_error{"Expected dataset or attribute in call to get_shape."}; + throw std::runtime_error{ + "Expected dataset or attribute in call to get_shape."}; } H5Sget_simple_extent_dims(dspace, dims, nullptr); H5Sclose(dspace); @@ -74,7 +75,8 @@ std::vector object_shape(hid_t obj_id) } else if (type == H5I_ATTR) { dspace = H5Aget_space(obj_id); } else { - throw std::runtime_error{"Expected dataset or attribute in call to object_shape."}; + throw std::runtime_error{ + "Expected dataset or attribute in call to object_shape."}; } int n = H5Sget_simple_extent_ndims(dspace); @@ -103,9 +105,7 @@ create_group(hid_t parent_id, char const *name) { hid_t out = H5Gcreate(parent_id, name, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); if (out < 0) { - std::stringstream err_msg; - err_msg << "Failed to create HDF5 group \"" << name << "\""; - fatal_error(err_msg); + fatal_error(fmt::format("Failed to create HDF5 group \"{}\"", name)); } return out; } @@ -159,17 +159,13 @@ ensure_exists(hid_t obj_id, const char* name, bool attribute) { if (attribute) { if (!attribute_exists(obj_id, name)) { - std::stringstream err_msg; - err_msg << "Attribute \"" << name << "\" does not exist in object " - << object_name(obj_id); - fatal_error(err_msg); + fatal_error(fmt::format("Attribute \"{}\" does not exist in object {}", + name, object_name(obj_id))); } } else { if (!object_exists(obj_id, name)) { - std::stringstream err_msg; - err_msg << "Object \"" << name << "\" does not exist in object " - << object_name(obj_id); - fatal_error(err_msg); + fatal_error(fmt::format("Object \"{}\" does not exist in object {}", + name, object_name(obj_id))); } } } @@ -192,9 +188,7 @@ file_open(const char* filename, char mode, bool parallel) flags = (mode == 'x' ? H5F_ACC_EXCL : H5F_ACC_TRUNC); break; default: - std::stringstream err_msg; - err_msg << "Invalid file mode: " << mode; - fatal_error(err_msg); + fatal_error(fmt::format("Invalid file mode: ", mode)); } hid_t plist = H5P_DEFAULT; @@ -214,9 +208,8 @@ file_open(const char* filename, char mode, bool parallel) file_id = H5Fopen(filename, flags, plist); } if (file_id < 0) { - std::stringstream msg; - msg << "Failed to open HDF5 file with mode '" << mode << "': " << filename; - fatal_error(msg); + fatal_error(fmt::format( + "Failed to open HDF5 file with mode '{}': {}", mode, filename)); } #ifdef PHDF5 @@ -392,9 +385,7 @@ object_exists(hid_t object_id, const char* name) { htri_t out = H5LTpath_valid(object_id, name, true); if (out < 0) { - std::stringstream err_msg; - err_msg << "Failed to check if object \"" << name << "\" exists."; - fatal_error(err_msg); + fatal_error(fmt::format("Failed to check if object \"{}\" exists.", name)); } return (out > 0); } @@ -471,8 +462,8 @@ read_attr_string(hid_t obj_id, const char* name, size_t slen, char* buffer) void -read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id, - void* buffer, bool indep) +read_dataset_lowlevel(hid_t obj_id, const char* name, hid_t mem_type_id, + hid_t mem_space_id, bool indep, void* buffer) { hid_t dset = obj_id; if (name) dset = open_dataset(obj_id, name); @@ -487,11 +478,11 @@ read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id, H5Pset_dxpl_mpio(plist, data_xfer_mode); // Read data - H5Dread(dset, mem_type_id, H5S_ALL, H5S_ALL, plist, buffer); + H5Dread(dset, mem_type_id, mem_space_id, H5S_ALL, plist, buffer); H5Pclose(plist); #endif } else { - H5Dread(dset, mem_type_id, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); + H5Dread(dset, mem_type_id, mem_space_id, H5S_ALL, H5P_DEFAULT, buffer); } if (name) H5Dclose(dset); @@ -520,26 +511,28 @@ void read_dataset(hid_t dset, xt::xarray>& arr, bool indep) void read_double(hid_t obj_id, const char* name, double* buffer, bool indep) { - read_dataset(obj_id, name, H5T_NATIVE_DOUBLE, buffer, indep); + read_dataset_lowlevel(obj_id, name, H5T_NATIVE_DOUBLE, H5S_ALL, indep, + buffer); } void read_int(hid_t obj_id, const char* name, int* buffer, bool indep) { - read_dataset(obj_id, name, H5T_NATIVE_INT, buffer, indep); + read_dataset_lowlevel(obj_id, name, H5T_NATIVE_INT, H5S_ALL, indep, buffer); } void read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep) { - read_dataset(obj_id, name, H5T_NATIVE_LLONG, buffer, indep); + read_dataset_lowlevel(obj_id, name, H5T_NATIVE_LLONG, H5S_ALL, indep, buffer); } void -read_string(hid_t obj_id, const char* name, size_t slen, char* buffer, bool indep) +read_string(hid_t obj_id, const char* name, size_t slen, char* buffer, + bool indep) { // Create datatype for a string hid_t datatype = H5Tcopy(H5T_C_S1); @@ -548,7 +541,7 @@ read_string(hid_t obj_id, const char* name, size_t slen, char* buffer, bool inde H5Tset_strpad(datatype, H5T_STR_NULLPAD); // Read data into buffer - read_dataset(obj_id, name, datatype, buffer, indep); + read_dataset_lowlevel(obj_id, name, datatype, H5S_ALL, indep, buffer); // Free resources H5Tclose(datatype); @@ -556,7 +549,8 @@ read_string(hid_t obj_id, const char* name, size_t slen, char* buffer, bool inde void -read_complex(hid_t obj_id, const char* name, std::complex* buffer, bool indep) +read_complex(hid_t obj_id, const char* name, std::complex* buffer, + bool indep) { // Create compound datatype for complex numbers struct complex_t { @@ -569,7 +563,7 @@ read_complex(hid_t obj_id, const char* name, std::complex* buffer, bool H5Tinsert(complex_id, "i", HOFFSET(complex_t, im), H5T_NATIVE_DOUBLE); // Read data - read_dataset(obj_id, name, complex_id, buffer, indep); + read_dataset_lowlevel(obj_id, name, complex_id, H5S_ALL, indep, buffer); // Free resources H5Tclose(complex_id); @@ -577,21 +571,22 @@ read_complex(hid_t obj_id, const char* name, std::complex* buffer, bool void -read_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, double* results) +read_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, + double* results) { // Create dataspace for hyperslab in memory - hsize_t dims[] {n_filter, n_score, 3}; - hsize_t start[] {0, 0, 1}; - hsize_t count[] {n_filter, n_score, 2}; - hid_t memspace = H5Screate_simple(3, dims, nullptr); + constexpr int ndim = 3; + hsize_t dims[ndim] {n_filter, n_score, 3}; + hsize_t start[ndim] {0, 0, 1}; + hsize_t count[ndim] {n_filter, n_score, 2}; + hid_t memspace = H5Screate_simple(ndim, dims, nullptr); H5Sselect_hyperslab(memspace, H5S_SELECT_SET, start, nullptr, count, nullptr); - // Create and write dataset - hid_t dset = H5Dopen(group_id, "results", H5P_DEFAULT); - H5Dread(dset, H5T_NATIVE_DOUBLE, memspace, H5S_ALL, H5P_DEFAULT, results); + // Read the dataset + read_dataset_lowlevel(group_id, "results", H5T_NATIVE_DOUBLE, memspace, + false, results); // Free resources - H5Dclose(dset); H5Sclose(memspace); } @@ -654,8 +649,9 @@ write_attr_string(hid_t obj_id, const char* name, const char* buffer) void -write_dataset(hid_t group_id, int ndim, const hsize_t* dims, const char* name, - hid_t mem_type_id, const void* buffer, bool indep) +write_dataset_lowlevel(hid_t group_id, int ndim, const hsize_t* dims, + const char* name, hid_t mem_type_id, hid_t mem_space_id, bool indep, + const void* buffer) { // If array is given, create a simple dataspace. Otherwise, create a scalar // datascape. @@ -679,11 +675,11 @@ write_dataset(hid_t group_id, int ndim, const hsize_t* dims, const char* name, H5Pset_dxpl_mpio(plist, data_xfer_mode); // Write data - H5Dwrite(dset, mem_type_id, H5S_ALL, H5S_ALL, plist, buffer); + H5Dwrite(dset, mem_type_id, mem_space_id, H5S_ALL, plist, buffer); H5Pclose(plist); #endif } else { - H5Dwrite(dset, mem_type_id, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); + H5Dwrite(dset, mem_type_id, mem_space_id, H5S_ALL, H5P_DEFAULT, buffer); } // Free resources @@ -696,7 +692,8 @@ void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const double* buffer, bool indep) { - write_dataset(group_id, ndim, dims, name, H5T_NATIVE_DOUBLE, buffer, indep); + write_dataset_lowlevel(group_id, ndim, dims, name, H5T_NATIVE_DOUBLE, H5S_ALL, + indep, buffer); } @@ -704,7 +701,8 @@ void write_int(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const int* buffer, bool indep) { - write_dataset(group_id, ndim, dims, name, H5T_NATIVE_INT, buffer, indep); + write_dataset_lowlevel(group_id, ndim, dims, name, H5T_NATIVE_INT, H5S_ALL, + indep, buffer); } @@ -712,7 +710,8 @@ void write_llong(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const long long* buffer, bool indep) { - write_dataset(group_id, ndim, dims, name, H5T_NATIVE_LLONG, buffer, indep); + write_dataset_lowlevel(group_id, ndim, dims, name, H5T_NATIVE_LLONG, H5S_ALL, + indep, buffer); } @@ -725,7 +724,8 @@ write_string(hid_t group_id, int ndim, const hsize_t* dims, size_t slen, hid_t datatype = H5Tcopy(H5T_C_S1); H5Tset_size(datatype, slen); - write_dataset(group_id, ndim, dims, name, datatype, buffer, indep); + write_dataset_lowlevel(group_id, ndim, dims, name, datatype, H5S_ALL, indep, + buffer); // Free resources H5Tclose(datatype); @@ -734,34 +734,34 @@ write_string(hid_t group_id, int ndim, const hsize_t* dims, size_t slen, void -write_string(hid_t group_id, const char* name, const std::string& buffer, bool indep) +write_string(hid_t group_id, const char* name, const std::string& buffer, + bool indep) { - write_string(group_id, 0, nullptr, buffer.length(), name, buffer.c_str(), indep); + write_string(group_id, 0, nullptr, buffer.length(), name, buffer.c_str(), + indep); } void -write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, const double* results) +write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, + const double* results) { // Set dimensions of sum/sum_sq hyperslab to store - hsize_t count[] {n_filter, n_score, 2}; - hid_t dspace = H5Screate_simple(3, count, nullptr); + constexpr int ndim = 3; + hsize_t count[ndim] {n_filter, n_score, 2}; // Set dimensions of results array - hsize_t dims[] {n_filter, n_score, 3}; - hsize_t start[] {0, 0, 1}; - hid_t memspace = H5Screate_simple(3, dims, nullptr); + hsize_t dims[ndim] {n_filter, n_score, 3}; + hsize_t start[ndim] {0, 0, 1}; + hid_t memspace = H5Screate_simple(ndim, dims, nullptr); H5Sselect_hyperslab(memspace, H5S_SELECT_SET, start, nullptr, count, nullptr); // Create and write dataset - hid_t dset = H5Dcreate(group_id, "results", H5T_NATIVE_DOUBLE, dspace, - H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); - H5Dwrite(dset, H5T_NATIVE_DOUBLE, memspace, H5S_ALL, H5P_DEFAULT, results); + write_dataset_lowlevel(group_id, ndim, count, "results", H5T_NATIVE_DOUBLE, + memspace, false, results); // Free resources - H5Dclose(dset); H5Sclose(memspace); - H5Sclose(dspace); } diff --git a/src/initialize.cpp b/src/initialize.cpp index 22479ae29..10d86ee80 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -3,13 +3,13 @@ #include #include // for getenv #include -#include #include #include #ifdef _OPENMP #include #endif +#include #include "openmc/capi.h" #include "openmc/constants.h" @@ -134,7 +134,7 @@ parse_command_line(int argc, char* argv[]) } else if (arg == "-n" || arg == "--particles") { i += 1; settings::n_particles = std::stoll(argv[i]); - + } else if (arg == "-e" || arg == "--event") { settings::event_based = true; @@ -155,9 +155,8 @@ parse_command_line(int argc, char* argv[]) settings::path_particle_restart = argv[i]; settings::particle_restart_run = true; } else { - std::stringstream msg; - msg << "Unrecognized file after restart flag: " << filetype << "."; - strcpy(openmc_err_msg, msg.str().c_str()); + auto msg = fmt::format("Unrecognized file after restart flag: {}.", filetype); + strcpy(openmc_err_msg, msg.c_str()); return OPENMC_E_INVALID_ARGUMENT; } @@ -224,7 +223,7 @@ parse_command_line(int argc, char* argv[]) settings::write_all_tracks = true; } else { - std::cerr << "Unknown option: " << argv[i] << '\n'; + fmt::print(stderr, "Unknown option: {}\n", argv[i]); print_usage(); return OPENMC_E_UNASSIGNED; } diff --git a/src/lattice.cpp b/src/lattice.cpp index 092ff3d11..8f88d8c59 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -1,9 +1,11 @@ #include "openmc/lattice.h" #include -#include +#include #include +#include + #include "openmc/cell.h" #include "openmc/error.h" #include "openmc/geometry.h" @@ -71,10 +73,8 @@ Lattice::adjust_indices() if (search != model::universe_map.end()) { *it = search->second; } else { - std::stringstream err_msg; - err_msg << "Invalid universe number " << uid << " specified on " - "lattice " << id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "Invalid universe number {} specified on lattice {}", uid, id_)); } } @@ -84,10 +84,8 @@ Lattice::adjust_indices() if (search != model::universe_map.end()) { outer_ = search->second; } else { - std::stringstream err_msg; - err_msg << "Invalid universe number " << outer_ << " specified on " - "lattice " << id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "Invalid universe number {} specified on lattice {}", outer_, id_)); } } } @@ -184,12 +182,9 @@ RectLattice::RectLattice(pugi::xml_node lat_node) std::string univ_str {get_node_value(lat_node, "universes")}; std::vector univ_words {split(univ_str)}; if (univ_words.size() != nx*ny*nz) { - std::stringstream err_msg; - err_msg << "Expected " << nx*ny*nz - << " universes for a rectangular lattice of size " - << nx << "x" << ny << "x" << nz << " but " << univ_words.size() - << " were specified."; - fatal_error(err_msg); + fatal_error(fmt::format( + "Expected {} universes for a rectangular lattice of size {}x{]x{} but {} " + "were specified.", nx*ny*nz, nx, ny, nz, univ_words.size())); } // Parse the universes. @@ -487,12 +482,10 @@ HexLattice::HexLattice(pugi::xml_node lat_node) std::string univ_str {get_node_value(lat_node, "universes")}; std::vector univ_words {split(univ_str)}; if (univ_words.size() != n_univ) { - std::stringstream err_msg; - err_msg << "Expected " << n_univ - << " universes for a hexagonal lattice with " << n_rings_ - << " rings and " << n_axial_ << " axial levels" << " but " - << univ_words.size() << " were specified."; - fatal_error(err_msg); + fatal_error(fmt::format( + "Expected {} universes for a hexagonal lattice with {} rings and {} " + "axial levels but {} were specified.", n_univ, n_rings_, n_axial_, + univ_words.size())); } // Parse the universes. @@ -1069,9 +1062,8 @@ void read_lattices(pugi::xml_node node) if (in_map == model::lattice_map.end()) { model::lattice_map[id] = i_lat; } else { - std::stringstream err_msg; - err_msg << "Two or more lattices use the same unique ID: " << id; - fatal_error(err_msg); + fatal_error(fmt::format( + "Two or more lattices use the same unique ID: {}", id)); } } } diff --git a/src/material.cpp b/src/material.cpp index 570c28bcb..6ee874935 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -47,8 +47,9 @@ std::vector> materials; //============================================================================== Material::Material(pugi::xml_node node) - : index_{model::materials.size()} { + index_ = model::materials.size(); // Avoids warning about narrowing + if (check_for_node(node, "id")) { this->set_id(std::stoi(get_node_value(node, "id"))); } else { diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 55be267bc..163954caa 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -9,6 +9,7 @@ #include #endif +#include #include "xtensor/xmath.hpp" #include "xtensor/xsort.hpp" #include "xtensor/xadapt.hpp" @@ -121,10 +122,9 @@ Mgxs::metadata_from_hdf5(hid_t xs_id, const std::vector& temperature, temps_to_read.push_back(std::round(temp_actual)); } } else { - std::stringstream msg; - msg << "MGXS library does not contain cross sections for " - << in_name << " at or near " << std::round(T) << " K."; - fatal_error(msg); + fatal_error(fmt::format( + "MGXS library does not contain cross sections for {} at or near {} K.", + in_name, std::round(T))); } } break; @@ -350,10 +350,9 @@ Mgxs::Mgxs(const std::string& in_name, const std::vector& mat_kTs, auto temp_actual = micros[m]->kTs[micro_t[m]]; if (std::abs(temp_actual - temp_desired) >= K_BOLTZMANN * settings::temperature_tolerance) { - std::stringstream msg; - msg << "MGXS Library does not contain cross section for " << name - << " at or near " << std::round(temp_desired / K_BOLTZMANN) << "K."; - fatal_error(msg); + fatal_error(fmt::format( + "MGXS Library does not contain cross section for {} at or near {} K.", + name, std::round(temp_desired / K_BOLTZMANN))); } } break; diff --git a/src/output.cpp b/src/output.cpp index 4744385c1..18b94be9b 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -11,6 +11,8 @@ #include #include // for pair +#include +#include #ifdef _OPENMP #include #endif @@ -44,53 +46,53 @@ namespace openmc { void title() { - std::cout << - " %%%%%%%%%%%%%%%\n" << - " %%%%%%%%%%%%%%%%%%%%%%%%\n" << - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" << - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" << - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" << - " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" << - " %%%%%%%%%%%%%%%%%%%%%%%%\n" << - " %%%%%%%%%%%%%%%%%%%%%%%%\n" << - " ############### %%%%%%%%%%%%%%%%%%%%%%%%\n" << - " ################## %%%%%%%%%%%%%%%%%%%%%%%\n" << - " ################### %%%%%%%%%%%%%%%%%%%%%%%\n" << - " #################### %%%%%%%%%%%%%%%%%%%%%%\n" << - " ##################### %%%%%%%%%%%%%%%%%%%%%\n" << - " ###################### %%%%%%%%%%%%%%%%%%%%\n" << - " ####################### %%%%%%%%%%%%%%%%%%\n" << - " ####################### %%%%%%%%%%%%%%%%%\n" << - " ###################### %%%%%%%%%%%%%%%%%\n" << - " #################### %%%%%%%%%%%%%%%%%\n" << - " ################# %%%%%%%%%%%%%%%%%\n" << - " ############### %%%%%%%%%%%%%%%%\n" << - " ############ %%%%%%%%%%%%%%%\n" << - " ######## %%%%%%%%%%%%%%\n" << - " %%%%%%%%%%%\n\n"; + fmt::print( + " %%%%%%%%%%%%%%%\n" + " %%%%%%%%%%%%%%%%%%%%%%%%\n" + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" + " %%%%%%%%%%%%%%%%%%%%%%%%\n" + " %%%%%%%%%%%%%%%%%%%%%%%%\n" + " ############### %%%%%%%%%%%%%%%%%%%%%%%%\n" + " ################## %%%%%%%%%%%%%%%%%%%%%%%\n" + " ################### %%%%%%%%%%%%%%%%%%%%%%%\n" + " #################### %%%%%%%%%%%%%%%%%%%%%%\n" + " ##################### %%%%%%%%%%%%%%%%%%%%%\n" + " ###################### %%%%%%%%%%%%%%%%%%%%\n" + " ####################### %%%%%%%%%%%%%%%%%%\n" + " ####################### %%%%%%%%%%%%%%%%%\n" + " ###################### %%%%%%%%%%%%%%%%%\n" + " #################### %%%%%%%%%%%%%%%%%\n" + " ################# %%%%%%%%%%%%%%%%%\n" + " ############### %%%%%%%%%%%%%%%%\n" + " ############ %%%%%%%%%%%%%%%\n" + " ######## %%%%%%%%%%%%%%\n" + " %%%%%%%%%%%\n\n"); // Write version information - std::cout << - " | The OpenMC Monte Carlo Code\n" << - " Copyright | 2011-2020 MIT and OpenMC contributors\n" << - " License | http://openmc.readthedocs.io/en/latest/license.html\n" << - " Version | " << VERSION_MAJOR << '.' << VERSION_MINOR << '.' - << VERSION_RELEASE << (VERSION_DEV ? "-dev" : "") << '\n'; + fmt::print( + " | The OpenMC Monte Carlo Code\n" + " Copyright | 2011-2020 MIT and OpenMC contributors\n" + " License | http://openmc.readthedocs.io/en/latest/license.html\n" + " Version | {}.{}.{}{}\n", VERSION_MAJOR, VERSION_MINOR, + VERSION_RELEASE, VERSION_DEV ? "-dev" : ""); #ifdef GIT_SHA1 - std::cout << " Git SHA1 | " << GIT_SHA1 << '\n'; + fmt::print(" Git SHA1 | {}\n", GIT_SHA1); #endif // Write the date and time - std::cout << " Date/Time | " << time_stamp() << '\n'; + fmt::print(" Date/Time | {}\n", time_stamp()); #ifdef OPENMC_MPI // Write number of processors - std::cout << " MPI Processes | " << mpi::n_procs << '\n'; + fmt::print(" MPI Processes | {}\n", mpi::n_procs); #endif #ifdef _OPENMP // Write number of OpenMP threads - std::cout << " OpenMP Threads | " << omp_get_max_threads() << '\n'; + fmt::print(" OpenMP Threads | {}\n", omp_get_max_threads()); #endif std::cout << std::endl; } @@ -146,64 +148,59 @@ extern "C" void print_particle(Particle* p) // Display particle type and ID. switch (p->type_) { case Particle::Type::neutron: - std::cout << "Neutron "; + fmt::print("Neutron "); break; case Particle::Type::photon: - std::cout << "Photon "; + fmt::print("Photon "); break; case Particle::Type::electron: - std::cout << "Electron "; + fmt::print("Electron "); break; case Particle::Type::positron: - std::cout << "Positron "; + fmt::print("Positron "); break; default: - std::cout << "Unknown Particle "; + fmt::print("Unknown Particle "); } - std::cout << p->id_ << "\n"; + fmt::print("{}\n", p->id_); // Display particle geometry hierarchy. for (auto i = 0; i < p->n_coord_; i++) { - std::cout << " Level " << i << "\n"; + fmt::print(" Level {}\n", i); if (p->coord_[i].cell != C_NONE) { const Cell& c {*model::cells[p->coord_[i].cell]}; - std::cout << " Cell = " << c.id_ << "\n"; + fmt::print(" Cell = {}\n", c.id_); } if (p->coord_[i].universe != C_NONE) { const Universe& u {*model::universes[p->coord_[i].universe]}; - std::cout << " Universe = " << u.id_ << "\n"; + fmt::print(" Universe = {}\n", u.id_); } if (p->coord_[i].lattice != C_NONE) { const Lattice& lat {*model::lattices[p->coord_[i].lattice]}; - std::cout << " Lattice = " << lat.id_ << "\n"; - std::cout << " Lattice position = (" << p->coord_[i].lattice_x - << "," << p->coord_[i].lattice_y << "," - << p->coord_[i].lattice_z << ")\n"; + fmt::print(" Lattice = {}\n", lat.id_); + fmt::print(" Lattice position = ({},{},{})\n", p->coord_[i].lattice_x, + p->coord_[i].lattice_y, p->coord_[i].lattice_z); } - std::cout << " r = (" << p->coord_[i].r.x << ", " - << p->coord_[i].r.y << ", " << p->coord_[i].r.z << ")\n"; - std::cout << " u = (" << p->coord_[i].u.x << ", " - << p->coord_[i].u.y << ", " << p->coord_[i].u.z << ")\n"; + fmt::print(" r = {}\n", p->coord_[i].r); + fmt::print(" u = {}\n", p->coord_[i].u); } // Display miscellaneous info. if (p->surface_ != 0) { const Surface& surf {*model::surfaces[std::abs(p->surface_)-1]}; - std::cout << " Surface = " << std::copysign(surf.id_, p->surface_) << "\n"; + fmt::print(" Surface = {}\n", (p->surface_ > 0) ? surf.id_ : -surf.id_); } - std::cout << " Weight = " << p->wgt_ << "\n"; + fmt::print(" Weight = {}\n", p->wgt_); if (settings::run_CE) { - std::cout << " Energy = " << p->E_ << "\n"; + fmt::print(" Energy = {}\n", p->E_); } else { - std::cout << " Energy Group = " << p->g_ << "\n"; + fmt::print(" Energy Group = {}\n", p->g_); } - std::cout << " Delayed Group = " << p->delayed_group_ << "\n"; - - std::cout << "\n"; + fmt::print(" Delayed Group = {}\n\n", p->delayed_group_); } //============================================================================== @@ -215,65 +212,53 @@ void print_plot() for (auto pl : model::plots) { // Plot id - std::cout << "Plot ID: " << pl.id_ << "\n"; + fmt::print("Plot ID: {}\n", pl.id_); // Plot filename - std::cout << "Plot file: " << pl.path_plot_ << "\n"; + fmt::print("Plot file: {}\n", pl.path_plot_); // Plot level - std::cout << "Universe depth: " << pl.level_ << "\n"; + fmt::print("Universe depth: {}\n", pl.level_); // Plot type if (PlotType::slice == pl.type_) { - std::cout << "Plot Type: Slice" << "\n"; + fmt::print("Plot Type: Slice\n"); } else if (PlotType::voxel == pl.type_) { - std::cout << "Plot Type: Voxel" << "\n"; + fmt::print("Plot Type: Voxel\n"); } // Plot parameters - std::cout << "Origin: " << pl.origin_[0] << " " - << pl.origin_[1] << " " - << pl.origin_[2] << "\n"; + fmt::print("Origin: {} {} {}\n", pl.origin_[0], pl.origin_[1], pl.origin_[2]); if (PlotType::slice == pl.type_) { - std::cout << std::setprecision(4) - << "Width: " - << pl.width_[0] << " " - << pl.width_[1] << "\n"; + fmt::print("Width: {:4} {:4}\n", pl.width_[0], pl.width_[1]); } else if (PlotType::voxel == pl.type_) { - std::cout << std::setprecision(4) - << "Width: " - << pl.width_[0] << " " - << pl.width_[1] << " " - << pl.width_[2] << "\n"; + fmt::print("Width: {:4} {:4} {:4}\n", pl.width_[0], pl.width_[1], + pl.width_[2]); } if (PlotColorBy::cells == pl.color_by_) { - std::cout << "Coloring: Cells" << "\n"; + fmt::print("Coloring: Cells\n"); } else if (PlotColorBy::mats == pl.color_by_) { - std::cout << "Coloring: Materials" << "\n"; + fmt::print("Coloring: Materials\n"); } if (PlotType::slice == pl.type_) { switch(pl.basis_) { case PlotBasis::xy: - std::cout << "Basis: XY" << "\n"; + fmt::print("Basis: XY\n"); break; case PlotBasis::xz: - std::cout << "Basis: XZ" << "\n"; + fmt::print("Basis: XZ\n"); break; case PlotBasis::yz: - std::cout << "Basis: YZ" << "\n"; + fmt::print("Basis: YZ\n"); break; } - std::cout << "Pixels: " << pl.pixels_[0] << " " - << pl.pixels_[1] << " " << "\n"; + fmt::print("Pixels: {} {}\n", pl.pixels_[0], pl.pixels_[1]); } else if (PlotType::voxel == pl.type_) { - std::cout << "Voxels: " << pl.pixels_[0] << " " - << pl.pixels_[1] << " " - << pl.pixels_[2] << "\n"; + fmt::print("Voxels: {} {} {}\n", pl.pixels_[0], pl.pixels_[1], pl.pixels_[2]); } - std::cout << "\n"; - + fmt::print("\n"); } } @@ -291,23 +276,22 @@ print_overlap_check() if (mpi::master) { header("cell overlap check summary", 1); - std::cout << " Cell ID No. Overlap Checks\n"; + fmt::print(" Cell ID No. Overlap Checks\n"); std::vector sparse_cell_ids; for (int i = 0; i < model::cells.size(); i++) { - std::cout << " " << std::setw(8) << model::cells[i]->id_ << std::setw(17) - << model::overlap_check_count[i] << "\n"; + fmt::print(" {:8}{:17}\n", model::cells[i]->id_, model::overlap_check_count[i]); if (model::overlap_check_count[i] < 10) { sparse_cell_ids.push_back(model::cells[i]->id_); } } - std::cout << "\n There were " << sparse_cell_ids.size() - << " cells with less than 10 overlap checks\n"; + fmt::print("\n There were {} cells with less than 10 overlap checks\n", + sparse_cell_ids.size()); for (auto id : sparse_cell_ids) { - std::cout << " " << id; + fmt::print(" {}", id); } - std::cout << "\n"; + fmt::print("\n"); } } @@ -316,7 +300,7 @@ print_overlap_check() void print_usage() { if (mpi::master) { - std::cout << + fmt::print( "Usage: openmc [options] [directory]\n\n" "Options:\n" " -c, --volume Run in stochastic volume calculation mode\n" @@ -329,7 +313,7 @@ void print_usage() " -t, --track Write tracks for all particles\n" " -e, --event Run using event-based parallelism\n" " -v, --version Show version information\n" - " -h, --help Show this message\n"; + " -h, --help Show this message\n"); } } @@ -338,14 +322,14 @@ void print_usage() void print_version() { if (mpi::master) { - std::cout << "OpenMC version " << VERSION_MAJOR << '.' << VERSION_MINOR - << '.' << VERSION_RELEASE << '\n'; + fmt::print("OpenMC version {}.{}.{}\n", VERSION_MAJOR, VERSION_MINOR, + VERSION_RELEASE); #ifdef GIT_SHA1 - std::cout << "Git SHA1: " << GIT_SHA1 << '\n'; + fmt::print("Git SHA1: {}\n", GIT_SHA1); #endif - std::cout << "Copyright (c) 2011-2019 Massachusetts Institute of " + fmt::print("Copyright (c) 2011-2019 Massachusetts Institute of " "Technology and OpenMC contributors\nMIT/X license at " - "\n"; + "\n"); } } @@ -354,13 +338,13 @@ void print_version() void print_columns() { if (settings::entropy_on) { - std::cout << + fmt::print( " Bat./Gen. k Entropy Average k \n" - " ========= ======== ======== ====================\n"; + " ========= ======== ======== ====================\n"); } else { - std::cout << + fmt::print( " Bat./Gen. k Average k\n" - " ========= ======== ====================\n"; + " ========= ======== ====================\n"); } } @@ -368,85 +352,63 @@ void print_columns() void print_generation() { - // Save state of cout - auto f {std::cout.flags()}; - // Determine overall generation and number of active generations int i = overall_generation() - 1; int n = simulation::current_batch > settings::n_inactive ? settings::gen_per_batch*simulation::n_realizations + simulation::current_gen : 0; - // Set format for values - std::cout << std::fixed << std::setprecision(5); - // write out information batch and option independent output - std::cout << " " << std::setw(9) << std::to_string(simulation::current_batch) - + "/" + std::to_string(simulation::current_gen) << " " << std::setw(8) - << simulation::k_generation[i]; + auto batch_and_gen = std::to_string(simulation::current_batch) + "/" + + std::to_string(simulation::current_gen); + fmt::print(" {:>9} {:8.5f}", batch_and_gen, simulation::k_generation[i]); // write out entropy info if (settings::entropy_on) { - std::cout << " " << std::setw(8) << simulation::entropy[i]; + fmt::print(" {:8.5f}", simulation::entropy[i]); } if (n > 1) { - std::cout << " " << std::setw(8) << simulation::keff << " +/-" - << std::setw(8) << simulation::keff_std; + fmt::print(" {:8.5f} +/-{:8.5f}", simulation::keff, simulation::keff_std); } - std::cout << '\n'; - - // Restore state of cout - std::cout.flags(f); + std::cout << std::endl; } //============================================================================== void print_batch_keff() { - // Save state of cout - auto f {std::cout.flags()}; - // Determine overall generation and number of active generations int i = simulation::current_batch*settings::gen_per_batch - 1; int n = simulation::n_realizations*settings::gen_per_batch; - // Set format for values - std::cout << std::fixed << std::setprecision(5); - // write out information batch and option independent output - std::cout << " " << std::setw(9) << std::to_string(simulation::current_batch) - + "/" + std::to_string(settings::gen_per_batch) << " " << std::setw(8) - << simulation::k_generation[i]; + auto batch_and_gen = std::to_string(simulation::current_batch) + "/" + + std::to_string(settings::gen_per_batch); + fmt::print(" {:>9} {:8.5f}", batch_and_gen, simulation::k_generation[i]); // write out entropy info if (settings::entropy_on) { - std::cout << " " << std::setw(8) << simulation::entropy[i]; + fmt::print(" {:8.5f}", simulation::entropy[i]); } if (n > 1) { - std::cout << " " << std::setw(8) << simulation::keff << " +/-" - << std::setw(8) << simulation::keff_std; + fmt::print(" {:8.5f} +/-{:8.5f}", simulation::keff, simulation::keff_std); } std::cout << std::endl; - - // Restore state of cout - std::cout.flags(f); } //============================================================================== void show_time(const char* label, double secs, int indent_level=0) { - std::cout << std::string(2*indent_level, ' '); int width = 33 - indent_level*2; - std::cout << " " << std::setw(width) << std::left << label << " = " - << std::setw(10) << std::right << secs << " seconds\n"; + fmt::print("{0:{1}} {2:<{3}} = {4:>10.4e} seconds\n", + "", 2*indent_level, label, width, secs); } void show_rate(const char* label, double particles_per_sec) { - std::cout << " " << std::setw(33) << std::left << label << " = " << - particles_per_sec << " particles/second\n"; + fmt::print(" {:<33} = {:.6} particles/second\n", label, particles_per_sec); } void print_runtime() @@ -457,11 +419,7 @@ void print_runtime() header("Timing Statistics", 6); if (settings::verbosity < 6) return; - // Save state of cout - auto f {std::cout.flags()}; - // display time elapsed for various sections - std::cout << std::scientific << std::setprecision(4); show_time("Total time for initialization", time_initialize.elapsed()); show_time("Reading cross sections", time_read_xs.elapsed(), 1); show_time("Total time in simulation", time_inactive.elapsed() + @@ -490,9 +448,6 @@ void print_runtime() show_time("Total time for finalization", time_finalize.elapsed()); show_time("Total time elapsed", time_total.elapsed()); - // Restore state of cout - std::cout.flags(f); - // Calculate particle rate in active/inactive batches int n_active = simulation::current_batch - settings::n_inactive; double speed_inactive = 0.0; @@ -519,15 +474,11 @@ void print_runtime() } // display calculation rate - std::cout << std::setprecision(6) << std::showpoint; if (!(settings::restart_run && (simulation::restart_batch >= settings::n_inactive)) && settings::n_inactive > 0) { show_rate("Calculation Rate (inactive)", speed_inactive); } show_rate("Calculation Rate (active)", speed_active); - - // Restore state of cout - std::cout.flags(f); } //============================================================================== @@ -545,9 +496,6 @@ mean_stdev(const double* x, int n) void print_results() { - // Save state of cout - auto f {std::cout.flags()}; - // display header block for results header("Results", 4); if (settings::verbosity < 4) return; @@ -564,52 +512,46 @@ void print_results() t_n3 = 1.0; } - // Set formatting for floats - std::cout << std::fixed << std::setprecision(5); - // write global tallies const auto& gt = simulation::global_tallies; double mean, stdev; if (n > 1) { if (settings::run_mode == RunMode::EIGENVALUE) { std::tie(mean, stdev) = mean_stdev(>(GlobalTally::K_COLLISION, 0), n); - std::cout << " k-effective (Collision) = " - << mean << " +/- " << t_n1 * stdev << '\n'; + fmt::print(" k-effective (Collision) = {:.5f} +/- {:.5f}\n", + mean, t_n1 * stdev); std::tie(mean, stdev) = mean_stdev(>(GlobalTally::K_TRACKLENGTH, 0), n); - std::cout << " k-effective (Track-length) = " - << mean << " +/- " << t_n1 * stdev << '\n'; + fmt::print(" k-effective (Track-length) = {:.5f} +/- {:.5f}\n", + mean, t_n1 * stdev); std::tie(mean, stdev) = mean_stdev(>(GlobalTally::K_ABSORPTION, 0), n); - std::cout << " k-effective (Absorption) = " - << mean << " +/- " << t_n1 * stdev << '\n'; + fmt::print(" k-effective (Absorption) = {:.5f} +/- {:.5f}\n", + mean, t_n1 * stdev); if (n > 3) { double k_combined[2]; openmc_get_keff(k_combined); - std::cout << " Combined k-effective = " - << k_combined[0] << " +/- " << t_n3 * k_combined[1] << '\n'; + fmt::print(" Combined k-effective = {:.5f} +/- {:.5f}\n", + k_combined[0], k_combined[1]); } } std::tie(mean, stdev) = mean_stdev(>(GlobalTally::LEAKAGE, 0), n); - std::cout << " Leakage Fraction = " - << mean << " +/- " << t_n1 * stdev << '\n'; + fmt::print(" Leakage Fraction = {:.5f} +/- {:.5f}\n", + mean, t_n1 * stdev); } else { if (mpi::master) warning("Could not compute uncertainties -- only one " "active batch simulated!"); if (settings::run_mode == RunMode::EIGENVALUE) { - std::cout << " k-effective (Collision) = " - << gt(GlobalTally::K_COLLISION, TallyResult::SUM) / n << '\n'; - std::cout << " k-effective (Track-length) = " - << gt(GlobalTally::K_TRACKLENGTH, TallyResult::SUM) / n << '\n'; - std::cout << " k-effective (Absorption) = " - << gt(GlobalTally::K_ABSORPTION, TallyResult::SUM) / n << '\n'; + fmt::print(" k-effective (Collision) = {:.5f}\n", + gt(GlobalTally::K_COLLISION, TallyResult::SUM) / n); + fmt::print(" k-effective (Track-length) = {:.5f}\n", + gt(GlobalTally::K_TRACKLENGTH, TallyResult::SUM) / n); + fmt::print(" k-effective (Absorption) = {:.5f}\n", + gt(GlobalTally::K_ABSORPTION, TallyResult::SUM) / n); } - std::cout << " Leakage Fraction = " - << gt(GlobalTally::LEAKAGE, TallyResult::SUM) / n << '\n'; + fmt::print(" Leakage Fraction = {:.5f}\n", + gt(GlobalTally::LEAKAGE, TallyResult::SUM) / n); } - std::cout << '\n'; - - // Restore state of cout - std::cout.flags(f); + fmt::print("\n"); } //============================================================================== @@ -643,7 +585,6 @@ write_tallies() // Open the tallies.out file. std::ofstream tallies_out; tallies_out.open("tallies.out", std::ios::out | std::ios::trunc); - tallies_out << std::setprecision(6); // Loop over each tally. for (auto i_tally = 0; i_tally < model::tallies.size(); ++i_tally) { @@ -652,10 +593,10 @@ write_tallies() // Write header block. std::string tally_header("TALLY " + std::to_string(tally.id_)); if (!tally.name_.empty()) tally_header += ": " + tally.name_; - tallies_out << header(tally_header) << "\n\n"; + fmt::print(tallies_out, "{}\n\n", header(tally_header)); if (!tally.writable_) { - tallies_out << " Internal\n\n"; + fmt::print(tallies_out, " Internal\n\n"); continue; } @@ -671,21 +612,20 @@ write_tallies() const auto& deriv {model::tally_derivs[tally.deriv_]}; switch (deriv.variable) { case DerivativeVariable::DENSITY: - tallies_out << " Density derivative Material " - << std::to_string(deriv.diff_material) << "\n"; + fmt::print(tallies_out, " Density derivative Material {}\n", + deriv.diff_material); break; case DerivativeVariable::NUCLIDE_DENSITY: - tallies_out << " Nuclide density derivative Material " - << std::to_string(deriv.diff_material) << " Nuclide " - << data::nuclides[deriv.diff_nuclide]->name_ << "\n"; + fmt::print(tallies_out, " Nuclide density derivative Material {} Nuclide {}\n", + deriv.diff_material, data::nuclides[deriv.diff_nuclide]->name_); break; case DerivativeVariable::TEMPERATURE: - tallies_out << " Temperature derivative Material " - << std::to_string(deriv.diff_material) << "\n"; + fmt::print(tallies_out, " Temperature derivative Material {}\n", + deriv.diff_material); break; default: - fatal_error("Differential tally dependent variable for tally " - + std::to_string(tally.id_) + " not defined in output.cpp"); + fatal_error(fmt::format("Differential tally dependent variable for " + "tally {} not defined in output.cpp", tally.id_)); } } @@ -708,8 +648,8 @@ write_tallies() auto i_filt = tally.filters(i); const auto& filt {*model::tally_filters[i_filt]}; auto& match {filter_matches[i_filt]}; - tallies_out << std::string(indent+1, ' ') - << filt.text_label(match.i_bin_) << "\n"; + fmt::print(tallies_out, "{0:{1}}{2}\n", "", indent + 1, + filt.text_label(match.i_bin_)); } indent += 2; } @@ -719,14 +659,14 @@ write_tallies() for (auto i_nuclide : tally.nuclides_) { // Write label for this nuclide bin. if (i_nuclide == -1) { - tallies_out << std::string(indent+1, ' ') << "Total Material\n"; + fmt::print(tallies_out, "{0:{1}}Total Material\n", "", indent + 1); } else { if (settings::run_CE) { - tallies_out << std::string(indent+1, ' ') - << data::nuclides[i_nuclide]->name_ << "\n"; + fmt::print(tallies_out, "{0:{1}}{2}\n", "", indent + 1, + data::nuclides[i_nuclide]->name_); } else { - tallies_out << std::string(indent+1, ' ') - << data::mg.nuclides_[i_nuclide].name << "\n"; + fmt::print(tallies_out, "{0:{1}}{2}\n", "", indent + 1, + data::mg.nuclides_[i_nuclide].name); } } @@ -738,9 +678,8 @@ write_tallies() double mean, stdev; std::tie(mean, stdev) = mean_stdev( &tally.results_(filter_index, score_index, 0), tally.n_realizations_); - tallies_out << std::string(indent+1, ' ') << std::left - << std::setw(36) << score_name << " " << mean << " +/- " - << t_value * stdev << "\n"; + fmt::print(tallies_out, "{0:{1}}{2:<36} {3:.6} +/- {4:.6}\n", + "", indent + 1, score_name, mean, t_value * stdev); score_index += 1; } indent -= 2; diff --git a/src/particle.cpp b/src/particle.cpp index bd9a61139..ea0d03cd0 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -1,8 +1,9 @@ #include "openmc/particle.h" #include // copy, min -#include // log, abs, copysign -#include +#include // log, abs + +#include #include "openmc/bank.h" #include "openmc/capi.h" @@ -235,7 +236,7 @@ Particle::event_advance() score_track_derivative(this, distance); } } - + void Particle::event_cross_surface() { @@ -538,7 +539,7 @@ Particle::cross_surface() // TODO: off-by-one surface_ = rotational ? surf_p->i_periodic_ + 1 : - std::copysign(surf_p->i_periodic_ + 1, surface_); + ((surface_ > 0) ? surf_p->i_periodic_ + 1 : -(surf_p->i_periodic_ + 1)); // Figure out what cell particle is in now n_coord_ = 1; @@ -630,8 +631,8 @@ Particle::mark_as_lost(const char* message) // Abort the simulation if the maximum number of lost particles has been // reached - if (simulation::n_lost_particles >= MAX_LOST_PARTICLES && - simulation::n_lost_particles >= REL_MAX_LOST_PARTICLES*n) { + if (simulation::n_lost_particles >= settings::max_lost_particles && + simulation::n_lost_particles >= settings::rel_max_lost_particles*n) { fatal_error("Maximum number of lost particles has been reached."); } } @@ -643,14 +644,13 @@ Particle::write_restart() const if (settings::run_mode == RunMode::PARTICLE) return; // Set up file name - std::stringstream filename; - filename << settings::path_output << "particle_" << simulation::current_batch - << '_' << id_ << ".h5"; + auto filename = fmt::format("{}particle_{}_{}.h5", settings::path_output, + simulation::current_batch, id_); #pragma omp critical (WriteParticleRestart) { // Create file - hid_t file_id = file_open(filename.str(), 'w'); + hid_t file_id = file_open(filename, 'w'); // Write filetype and version info write_attribute(file_id, "filetype", "particle restart"); diff --git a/src/physics.cpp b/src/physics.cpp index 6c46bf429..1065c200b 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -21,9 +21,10 @@ #include "openmc/thermal.h" #include "openmc/tallies/tally.h" +#include + #include // for max, min, max_element #include // for sqrt, exp, log, abs, copysign -#include namespace openmc { @@ -61,17 +62,19 @@ void collision(Particle* p) // Display information about collision if (settings::verbosity >= 10 || p->trace_) { - std::stringstream msg; + std::string msg; if (p->event_ == TallyEvent::KILL) { - msg << " Killed. Energy = " << p->E_ << " eV."; + msg = fmt::format(" Killed. Energy = {} eV.", p->E_); } else if (p->type_ == Particle::Type::neutron) { - msg << " " << reaction_name(p->event_mt_) << " with " << - data::nuclides[p->event_nuclide_]->name_ << ". Energy = " << p->E_ << " eV."; + msg = fmt::format(" {} with {}. Energy = {} eV.", + reaction_name(p->event_mt_), data::nuclides[p->event_nuclide_]->name_, + p->E_); } else if (p->type_ == Particle::Type::photon) { - msg << " " << reaction_name(p->event_mt_) << " with " << - to_element(data::nuclides[p->event_nuclide_]->name_) << ". Energy = " << p->E_ << " eV."; + msg = fmt::format(" {} with {}. Energy = {} eV.", + reaction_name(p->event_mt_), + to_element(data::nuclides[p->event_nuclide_]->name_), p->E_); } else { - msg << " Disappeared. Energy = " << p->E_ << " eV."; + msg = fmt::format(" Disappeared. Energy = {} eV.", p->E_); } write_message(msg, 1); } @@ -189,7 +192,7 @@ create_fission_sites(Particle* p, int i_nuclide, const Reaction* rx) // Sample delayed group and angle/energy for fission reaction sample_fission_neutron(i_nuclide, rx, p->E_, &site, p->current_seed()); - + // Store fission site in bank if (use_fission_bank) { int64_t idx = simulation::fission_bank.thread_safe_append(site); @@ -210,7 +213,7 @@ create_fission_sites(Particle* p, int i_nuclide, const Reaction* rx) if (p->delayed_group_ > 0) { nu_d[p->delayed_group_-1]++; } - + // Write fission particles to nuBank if (use_fission_bank) { p->nu_bank_.emplace_back(); @@ -220,7 +223,7 @@ create_fission_sites(Particle* p, int i_nuclide, const Reaction* rx) nu_bank_entry->delayed_group = site.delayed_group; } } - + // If shared fission bank was full, and no fissions could be added, // set the particle fission flag to false. if (nu == skipped) { diff --git a/src/physics_mg.cpp b/src/physics_mg.cpp index 1ac7c1979..f63a48d2a 100644 --- a/src/physics_mg.cpp +++ b/src/physics_mg.cpp @@ -1,8 +1,8 @@ #include "openmc/physics_mg.h" #include -#include +#include #include "xtensor/xarray.hpp" #include "openmc/bank.h" @@ -31,10 +31,8 @@ collision_mg(Particle* p) sample_reaction(p); // Display information about collision - if ((settings::verbosity >= 10) || (p->trace_)) { - std::stringstream msg; - msg << " Energy Group = " << p->g_; - write_message(msg, 1); + if ((settings::verbosity >= 10) || p->trace_) { + write_message(fmt::format(" Energy Group = {}", p->g_), 1); } } @@ -113,13 +111,13 @@ create_fission_sites(Particle* p) // Initialize the counter of delayed neutrons encountered for each delayed // group. double nu_d[MAX_DELAYED_GROUPS] = {0.}; - + // Clear out particle's nu fission bank p->nu_bank_.clear(); p->fission_ = true; int skipped = 0; - + // Determine whether to place fission sites into the shared fission bank // or the secondary particle bank. bool use_fission_bank = (settings::run_mode == RunMode::EIGENVALUE); @@ -155,7 +153,7 @@ create_fission_sites(Particle* p) // We add 1 to the delayed_group bc in MG, -1 is prompt, but in the rest // of the code, 0 is prompt. site.delayed_group = dg + 1; - + // Store fission site in bank if (use_fission_bank) { int64_t idx = simulation::fission_bank.thread_safe_append(site); @@ -176,7 +174,7 @@ create_fission_sites(Particle* p) if (p->delayed_group_ > 0) { nu_d[dg]++; } - + // Write fission particles to nuBank if (use_fission_bank) { p->nu_bank_.emplace_back(); @@ -186,7 +184,7 @@ create_fission_sites(Particle* p) nu_bank_entry->delayed_group = site.delayed_group; } } - + // If shared fission bank was full, and no fissions could be added, // set the particle fission flag to false. if (nu == skipped) { diff --git a/src/plot.cpp b/src/plot.cpp index 5aa6a61b8..bc94b3ad6 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include #include "xtensor/xview.hpp" #include "openmc/constants.h" @@ -92,10 +94,8 @@ extern "C" int openmc_plot_geometry() { for (auto pl : model::plots) { - std::stringstream ss; - ss << "Processing plot " << pl.id_ << ": " - << pl.path_plot_ << "..."; - write_message(ss.str(), 5); + write_message(fmt::format("Processing plot {}: {}...", + pl.id_, pl.path_plot_), 5); if (PlotType::slice == pl.type_) { // create 2D image @@ -188,9 +188,7 @@ Plot::set_id(pugi::xml_node plot_node) // Check to make sure 'id' hasn't been used if (model::plot_map.find(id_) != model::plot_map.end()) { - std::stringstream err_msg; - err_msg << "Two or more plots use the same unique ID: " << id_; - fatal_error(err_msg.str()); + fatal_error(fmt::format("Two or more plots use the same unique ID: {}", id_)); } } @@ -210,11 +208,9 @@ Plot::set_type(pugi::xml_node plot_node) else if (type_str == "voxel") { type_ = PlotType::voxel; } else { - // if we're here, something is wrong - std::stringstream err_msg; - err_msg << "Unsupported plot type '" << type_str - << "' in plot " << id_; - fatal_error(err_msg.str()); + // if we're here, something is wrong + fatal_error(fmt::format("Unsupported plot type '{}' in plot {}", + type_str, id_)); } } } @@ -223,24 +219,24 @@ void Plot::set_output_path(pugi::xml_node plot_node) { // Set output file path - std::stringstream filename; + std::string filename; if (check_for_node(plot_node, "filename")) { - filename << get_node_value(plot_node, "filename"); + filename = get_node_value(plot_node, "filename"); } else { - filename << "plot_" << id_; + filename = fmt::format("plot_{}", id_); } // add appropriate file extension to name switch(type_) { case PlotType::slice: - filename << ".ppm"; + filename.append(".ppm"); break; case PlotType::voxel: - filename << ".h5"; + filename.append(".h5"); break; } - path_plot_ = filename.str(); + path_plot_ = filename; // Copy plot pixel size std::vector pxls = get_node_array(plot_node, "pixels"); @@ -249,10 +245,7 @@ Plot::set_output_path(pugi::xml_node plot_node) pixels_[0] = pxls[0]; pixels_[1] = pxls[1]; } else { - std::stringstream err_msg; - err_msg << " must be length 2 in slice plot " - << id_; - fatal_error(err_msg.str()); + fatal_error(fmt::format(" must be length 2 in slice plot {}", id_)); } } else if (PlotType::voxel == type_) { if (pxls.size() == 3) { @@ -260,10 +253,7 @@ Plot::set_output_path(pugi::xml_node plot_node) pixels_[1] = pxls[1]; pixels_[2] = pxls[2]; } else { - std::stringstream err_msg; - err_msg << " must be length 3 in voxel plot " - << id_; - fatal_error(err_msg.str()); + fatal_error(fmt::format(" must be length 3 in voxel plot {}", id_)); } } } @@ -276,19 +266,13 @@ Plot::set_bg_color(pugi::xml_node plot_node) std::vector bg_rgb = get_node_array(plot_node, "background"); if (PlotType::voxel == type_) { if (mpi::master) { - std::stringstream err_msg; - err_msg << "Background color ignored in voxel plot " - << id_; - warning(err_msg.str()); + warning(fmt::format("Background color ignored in voxel plot {}", id_)); } } if (bg_rgb.size() == 3) { not_found_ = bg_rgb; } else { - std::stringstream err_msg; - err_msg << "Bad background RGB in plot " - << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Bad background RGB in plot {}", id_)); } } } @@ -309,10 +293,8 @@ Plot::set_basis(pugi::xml_node plot_node) } else if ("yz" == pl_basis) { basis_ = PlotBasis::yz; } else { - std::stringstream err_msg; - err_msg << "Unsupported plot basis '" << pl_basis - << "' in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Unsupported plot basis '{}' in plot {}", + pl_basis, id_)); } } } @@ -325,10 +307,7 @@ Plot::set_origin(pugi::xml_node plot_node) if (pl_origin.size() == 3) { origin_ = pl_origin; } else { - std::stringstream err_msg; - err_msg << "Origin must be length 3 in plot " - << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Origin must be length 3 in plot {}", id_)); } } @@ -342,20 +321,14 @@ Plot::set_width(pugi::xml_node plot_node) width_.x = pl_width[0]; width_.y = pl_width[1]; } else { - std::stringstream err_msg; - err_msg << " must be length 2 in slice plot " - << id_; - fatal_error(err_msg); + fatal_error(fmt::format(" must be length 2 in slice plot {}", id_)); } } else if (PlotType::voxel == type_) { if (pl_width.size() == 3) { pl_width = get_node_array(plot_node, "width"); width_ = pl_width; } else { - std::stringstream err_msg; - err_msg << " must be length 3 in voxel plot " - << id_; - fatal_error(err_msg); + fatal_error(fmt::format(" must be length 3 in voxel plot {}", id_)); } } } @@ -367,9 +340,7 @@ Plot::set_universe(pugi::xml_node plot_node) if (check_for_node(plot_node, "level")) { level_ = std::stoi(get_node_value(plot_node, "level")); if (level_ < 0) { - std::stringstream err_msg; - err_msg << "Bad universe level in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Bad universe level in plot {}", id_)); } } else { level_ = PLOT_LEVEL_LOWEST; @@ -391,10 +362,8 @@ Plot::set_default_colors(pugi::xml_node plot_node) color_by_ = PlotColorBy::mats; colors_.resize(model::materials.size()); } else { - std::stringstream err_msg; - err_msg << "Unsupported plot color type '" << pl_color_by - << "' in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Unsupported plot color type '{}' in plot {}", + pl_color_by, id_)); } for (auto& c : colors_) { @@ -411,10 +380,7 @@ Plot::set_user_colors(pugi::xml_node plot_node) { if (!plot_node.select_nodes("color").empty() && PlotType::voxel == type_) { if (mpi::master) { - std::stringstream err_msg; - err_msg << "Color specifications ignored in voxel plot " - << id_; - warning(err_msg); + warning(fmt::format("Color specifications ignored in voxel plot {}", id_)); } } @@ -422,19 +388,15 @@ Plot::set_user_colors(pugi::xml_node plot_node) // Make sure 3 values are specified for RGB std::vector user_rgb = get_node_array(cn, "rgb"); if (user_rgb.size() != 3) { - std::stringstream err_msg; - err_msg << "Bad RGB in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Bad RGB in plot {}", id_)); } // Ensure that there is an id for this color specification int col_id; if (check_for_node(cn, "id")) { col_id = std::stoi(get_node_value(cn, "id")); } else { - std::stringstream err_msg; - err_msg << "Must specify id for color specification in plot " - << id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "Must specify id for color specification in plot {}", id_)); } // Add RGB if (PlotColorBy::cells == color_by_) { @@ -442,20 +404,16 @@ Plot::set_user_colors(pugi::xml_node plot_node) col_id = model::cell_map[col_id]; colors_[col_id] = user_rgb; } else { - std::stringstream err_msg; - err_msg << "Could not find cell " << col_id - << " specified in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Could not find cell {} specified in plot {}", + col_id, id_)); } } else if (PlotColorBy::mats == color_by_) { if (model::material_map.find(col_id) != model::material_map.end()) { col_id = model::material_map[col_id]; colors_[col_id] = user_rgb; } else { - std::stringstream err_msg; - err_msg << "Could not find material " << col_id - << " specified in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "Could not find material {} specified in plot {}", col_id, id_)); } } } // color node loop @@ -469,9 +427,7 @@ Plot::set_meshlines(pugi::xml_node plot_node) if (!mesh_line_nodes.empty()) { if (PlotType::voxel == type_) { - std::stringstream msg; - msg << "Meshlines ignored in voxel plot " << id_; - warning(msg); + warning(fmt::format("Meshlines ignored in voxel plot {}", id_)); } if (mesh_line_nodes.size() == 1) { @@ -483,9 +439,8 @@ Plot::set_meshlines(pugi::xml_node plot_node) if (check_for_node(meshlines_node, "meshtype")) { meshtype = get_node_value(meshlines_node, "meshtype"); } else { - std::stringstream err_msg; - err_msg << "Must specify a meshtype for meshlines specification in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "Must specify a meshtype for meshlines specification in plot {}", id_)); } // Ensure that there is a linewidth for this meshlines specification @@ -494,9 +449,8 @@ Plot::set_meshlines(pugi::xml_node plot_node) meshline_width = get_node_value(meshlines_node, "linewidth"); meshlines_width_ = std::stoi(meshline_width); } else { - std::stringstream err_msg; - err_msg << "Must specify a linewidth for meshlines specification in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format( + "Must specify a linewidth for meshlines specification in plot {}", id_)); } // Check for color @@ -504,9 +458,7 @@ Plot::set_meshlines(pugi::xml_node plot_node) // Check and make sure 3 values are specified for RGB std::vector ml_rgb = get_node_array(meshlines_node, "color"); if (ml_rgb.size() != 3) { - std::stringstream err_msg; - err_msg << "Bad RGB for meshlines color in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Bad RGB for meshlines color in plot {}", id_)); } meshlines_color_ = ml_rgb; } @@ -514,9 +466,7 @@ Plot::set_meshlines(pugi::xml_node plot_node) // Set mesh based on type if ("ufs" == meshtype) { if (!simulation::ufs_mesh) { - std::stringstream err_msg; - err_msg << "No UFS mesh for meshlines on plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("No UFS mesh for meshlines on plot {}", id_)); } else { for (int i = 0; i < model::meshes.size(); ++i) { if (const auto* m @@ -531,9 +481,7 @@ Plot::set_meshlines(pugi::xml_node plot_node) } } else if ("entropy" == meshtype) { if (!simulation::entropy_mesh) { - std::stringstream err_msg; - err_msg << "No entropy mesh for meshlines on plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("No entropy mesh for meshlines on plot {}", id_)); } else { for (int i = 0; i < model::meshes.size(); ++i) { if (const auto* m @@ -553,29 +501,22 @@ Plot::set_meshlines(pugi::xml_node plot_node) tally_mesh_id = std::stoi(get_node_value(meshlines_node, "id")); } else { std::stringstream err_msg; - err_msg << "Must specify a mesh id for meshlines tally " - << "mesh specification in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Must specify a mesh id for meshlines tally " + "mesh specification in plot {}", id_)); } // find the tally index int idx; int err = openmc_get_mesh_index(tally_mesh_id, &idx); if (err != 0) { - std::stringstream err_msg; - err_msg << "Could not find mesh " << tally_mesh_id - << " specified in meshlines for plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Could not find mesh {} specified in " + "meshlines for plot {}", tally_mesh_id, id_)); } index_meshlines_mesh_ = idx; } else { - std::stringstream err_msg; - err_msg << "Invalid type for meshlines on plot " << id_ ; - fatal_error(err_msg); + fatal_error(fmt::format("Invalid type for meshlines on plot {}", id_ )); } } else { - std::stringstream err_msg; - err_msg << "Mutliple meshlines specified in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Mutliple meshlines specified in plot {}", id_)); } } } @@ -589,9 +530,7 @@ Plot::set_mask(pugi::xml_node plot_node) if (!mask_nodes.empty()) { if (PlotType::voxel == type_) { if (mpi::master) { - std::stringstream wrn_msg; - wrn_msg << "Mask ignored in voxel plot " << id_; - warning(wrn_msg); + warning(fmt::format("Mask ignored in voxel plot {}", id_)); } } @@ -602,9 +541,7 @@ Plot::set_mask(pugi::xml_node plot_node) // Determine how many components there are and allocate std::vector iarray = get_node_array(mask_node, "components"); if (iarray.size() == 0) { - std::stringstream err_msg; - err_msg << "Missing in mask of plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Missing in mask of plot {}", id_)); } // First we need to change the user-specified identifiers to indices @@ -615,20 +552,16 @@ Plot::set_mask(pugi::xml_node plot_node) col_id = model::cell_map[col_id]; } else { - std::stringstream err_msg; - err_msg << "Could not find cell " << col_id - << " specified in the mask in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Could not find cell {} specified in the " + "mask in plot {}", col_id, id_)); } } else if (PlotColorBy::mats == color_by_) { if (model::material_map.find(col_id) != model::material_map.end()) { col_id = model::material_map[col_id]; } else { - std::stringstream err_msg; - err_msg << "Could not find material " << col_id - << " specified in the mask in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Could not find material {} specified in " + "the mask in plot {}", col_id, id_)); } } } @@ -646,9 +579,7 @@ Plot::set_mask(pugi::xml_node plot_node) } } else { - std::stringstream err_msg; - err_msg << "Mutliple masks specified in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Mutliple masks specified in plot {}", id_)); } } } @@ -660,18 +591,14 @@ void Plot::set_overlap_color(pugi::xml_node plot_node) { // check for custom overlap color if (check_for_node(plot_node, "overlap_color")) { if (!color_overlaps_) { - std::stringstream wrn_msg; - wrn_msg << "Overlap color specified in plot " << id_ - << " but overlaps won't be shown."; - warning(wrn_msg); + warning(fmt::format( + "Overlap color specified in plot {} but overlaps won't be shown.", id_)); } std::vector olap_clr = get_node_array(plot_node, "overlap_color"); if (olap_clr.size() == 3) { overlap_color_ = olap_clr; } else { - std::stringstream err_msg; - err_msg << "Bad overlap RGB in plot " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Bad overlap RGB in plot {}", id_)); } } } @@ -716,9 +643,9 @@ void output_ppm(Plot pl, const ImageData& data) of.open(fname); // Write header - of << "P6" << "\n"; + of << "P6\n"; of << pl.pixels_[0] << " " << pl.pixels_[1] << "\n"; - of << "255" << "\n"; + of << "255\n"; of.close(); of.open(fname, std::ios::binary | std::ios::app); @@ -729,11 +656,7 @@ void output_ppm(Plot pl, const ImageData& data) of << rgb.red << rgb.green << rgb.blue; } } - - // Close file - // THIS IS HERE TO MATCH FORTRAN VERSION, NOT TECHNICALLY NECESSARY of << "\n"; - of.close(); } //============================================================================== diff --git a/src/reaction.cpp b/src/reaction.cpp index f7f1e87ac..ce0090752 100644 --- a/src/reaction.cpp +++ b/src/reaction.cpp @@ -3,6 +3,8 @@ #include #include // for move +#include + #include "openmc/constants.h" #include "openmc/hdf5_interface.h" #include "openmc/endf.h" @@ -35,8 +37,7 @@ Reaction::Reaction(hid_t group, const std::vector& temperatures) // Read cross section and threshold_idx data for (auto t : temperatures) { // Get group corresponding to temperature - std::string temp_str {std::to_string(t) + "K"}; - hid_t temp_group = open_group(group, temp_str.c_str()); + hid_t temp_group = open_group(group, fmt::format("{}K", t).c_str()); hid_t dset = open_dataset(temp_group, "xs"); // Get threshold index @@ -178,7 +179,7 @@ std::string reaction_name(int mt) } else if (mt == N_NPA) { return "(n,npa)"; } else if (N_N1 <= mt && mt <= N_N40) { - return "(n,n" + std::to_string(mt-50) + ")"; + return fmt::format("(n,n{})", mt - 50); } else if (mt == N_NC) { return "(n,nc)"; } else if (mt == N_DISAPPEAR) { @@ -244,33 +245,31 @@ std::string reaction_name(int mt) } else if (mt == PHOTOELECTRIC) { return "photoelectric"; } else if (534 <= mt && mt <= 572) { - std::stringstream name; - name << "photoelectric, " << SUBSHELLS[mt - 534] << " subshell"; - return name.str(); + return fmt::format("photoelectric, {} subshell", SUBSHELLS[mt - 534]); } else if (600 <= mt && mt <= 648) { - return "(n,p" + std::to_string(mt-600) + ")"; + return fmt::format("(n,p{})", mt - 600); } else if (mt == 649) { return "(n,pc)"; } else if (650 <= mt && mt <= 698) { - return "(n,d" + std::to_string(mt-650) + ")"; + return fmt::format("(n,d{})", mt - 650); } else if (mt == 699) { return "(n,dc)"; } else if (700 <= mt && mt <= 748) { - return "(n,t" + std::to_string(mt-700) + ")"; + return fmt::format("(n,t{})", mt - 700); } else if (mt == 749) { return "(n,tc)"; } else if (750 <= mt && mt <= 798) { - return "(n,3He" + std::to_string(mt-750) + ")"; + return fmt::format("(n,3He{})", mt - 750); } else if (mt == 799) { return "(n,3Hec)"; } else if (800 <= mt && mt <= 848) { - return "(n,a" + std::to_string(mt-800) + ")"; + return fmt::format("(n,a{})", mt - 800); } else if (mt == 849) { return "(n,ac)"; } else if (mt == HEATING_LOCAL) { return "heating-local"; } else { - return "MT=" + std::to_string(mt); + return fmt::format("MT={}", mt); } } diff --git a/src/secondary_uncorrelated.cpp b/src/secondary_uncorrelated.cpp index 1421fcf83..a1aa8ca0b 100644 --- a/src/secondary_uncorrelated.cpp +++ b/src/secondary_uncorrelated.cpp @@ -1,8 +1,9 @@ #include "openmc/secondary_uncorrelated.h" -#include // for stringstream #include // for string +#include + #include "openmc/error.h" #include "openmc/hdf5_interface.h" #include "openmc/random_lcg.h" @@ -42,9 +43,7 @@ UncorrelatedAngleEnergy::UncorrelatedAngleEnergy(hid_t group) } else if (type == "watt") { energy_ = UPtrEDist{new WattEnergy{energy_group}}; } else { - std::stringstream msg; - msg << "Energy distribution type '" << type << "' not implemented."; - warning(msg); + warning(fmt::format("Energy distribution type '{}' not implemented.", type)); } close_group(energy_group); } diff --git a/src/settings.cpp b/src/settings.cpp index 32a7958d8..614dd8f5b 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2,9 +2,9 @@ #include // for ceil, pow #include // for numeric_limits -#include #include +#include #ifdef _OPENMP #include #endif @@ -74,11 +74,14 @@ std::string path_input; std::string path_output; std::string path_particle_restart; std::string path_source; +std::string path_source_library; std::string path_sourcepoint; std::string path_statepoint; int32_t n_batches; int32_t n_inactive {0}; +int32_t max_lost_particles {10}; +double rel_max_lost_particles {1.0e-6}; int32_t gen_per_batch {1}; int64_t n_particles {-1}; @@ -130,7 +133,7 @@ void get_run_parameters(pugi::xml_node node_base) if (n_particles == -1) { n_particles = std::stoll(get_node_value(node_base, "particles")); } - + // Get maximum number of in flight particles for event-based mode if (check_for_node(node_base, "max_particles_in_flight")) { max_particles_in_flight = std::stoll(get_node_value(node_base, @@ -143,6 +146,16 @@ void get_run_parameters(pugi::xml_node node_base) } if (!trigger_on) n_max_batches = n_batches; + // Get max number of lost particles + if (check_for_node(node_base, "max_lost_particles")) { + max_lost_particles = std::stoi(get_node_value(node_base, "max_lost_particles")); + } + + // Get relative number of lost particles + if (check_for_node(node_base, "rel_max_lost_particles")) { + rel_max_lost_particles = std::stod(get_node_value(node_base, "rel_max_lost_particles")); + } + // Get number of inactive batches if (run_mode == RunMode::EIGENVALUE) { if (check_for_node(node_base, "inactive")) { @@ -195,13 +208,13 @@ void read_settings_xml() std::string filename = path_input + "settings.xml"; if (!file_exists(filename)) { if (run_mode != RunMode::PLOTTING) { - std::stringstream msg; - msg << "Settings XML file '" << filename << "' does not exist! In order " + fatal_error(fmt::format( + "Settings XML file '{}' does not exist! In order " "to run OpenMC, you first need a set of input files; at a minimum, this " "includes settings.xml, geometry.xml, and materials.xml. Please consult " - "the user's guide at http://openmc.readthedocs.io for further " - "information."; - fatal_error(msg); + "the user's guide at https://docs.openmc.org for further " + "information.", filename + )); } else { // The settings.xml file is optional if we just want to make a plot. return; @@ -343,14 +356,18 @@ void read_settings_xml() // Read run parameters get_run_parameters(node_mode); - // Check number of active batches, inactive batches, and particles + // Check number of active batches, inactive batches, max lost particles and particles if (n_batches <= n_inactive) { fatal_error("Number of active batches must be greater than zero."); } else if (n_inactive < 0) { fatal_error("Number of inactive batches must be non-negative."); } else if (n_particles <= 0) { fatal_error("Number of particles must be greater than zero."); - } + } else if (max_lost_particles <= 0) { + fatal_error("Number of max lost particles must be greater than zero."); + } else if (rel_max_lost_particles <= 0.0 || rel_max_lost_particles >= 1.0) { + fatal_error("Relative max lost particles must be between zero and one."); + } } // Copy random number seed if specified @@ -494,9 +511,8 @@ void read_settings_xml() if (check_for_node(root, "entropy_mesh")) { int temp = std::stoi(get_node_value(root, "entropy_mesh")); if (model::mesh_map.find(temp) == model::mesh_map.end()) { - std::stringstream msg; - msg << "Mesh " << temp << " specified for Shannon entropy does not exist."; - fatal_error(msg); + fatal_error(fmt::format( + "Mesh {} specified for Shannon entropy does not exist.", temp)); } index_entropy_mesh = model::mesh_map.at(temp); @@ -544,10 +560,8 @@ void read_settings_xml() if (check_for_node(root, "ufs_mesh")) { auto temp = std::stoi(get_node_value(root, "ufs_mesh")); if (model::mesh_map.find(temp) == model::mesh_map.end()) { - std::stringstream msg; - msg << "Mesh " << temp << " specified for uniform fission site method " - "does not exist."; - fatal_error(msg); + fatal_error(fmt::format("Mesh {} specified for uniform fission site " + "method does not exist.", temp)); } i_ufs_mesh = model::mesh_map.at(temp); @@ -792,7 +806,7 @@ void read_settings_xml() if (check_for_node(root, "delayed_photon_scaling")) { delayed_photon_scaling = get_node_value_bool(root, "delayed_photon_scaling"); } - + // Check whether to use event-based parallelism if (check_for_node(root, "event_based")) { event_based = get_node_value_bool(root, "event_based"); diff --git a/src/source.cpp b/src/source.cpp index 6314eb23f..96175804c 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1,8 +1,16 @@ #include "openmc/source.h" -#include // for move -#include // for stringstream +#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) +#define HAS_DYNAMIC_LINKING +#endif +#include // for move + +#ifdef HAS_DYNAMIC_LINKING +#include // for dlopen, dlsym, dlclose, dlerror +#endif + +#include #include "xtensor/xadapt.hpp" #include "openmc/bank.h" @@ -68,11 +76,15 @@ SourceDistribution::SourceDistribution(pugi::xml_node node) // Check if source file exists if (!file_exists(settings::path_source)) { - std::stringstream msg; - msg << "Source file '" << settings::path_source << "' does not exist."; - fatal_error(msg); + fatal_error(fmt::format("Source file '{}' does not exist.", + settings::path_source)); + } + } else if (check_for_node(node, "library")) { + settings::path_source_library = get_node_value(node, "library", false, true); + if (!file_exists(settings::path_source_library)) { + fatal_error(fmt::format("Source library '{}' does not exist.", + settings::path_source_library)); } - } else { // Spatial distribution for external source @@ -97,9 +109,8 @@ SourceDistribution::SourceDistribution(pugi::xml_node node) } else if (type == "point") { space_ = UPtrSpace{new SpatialPoint(node_space)}; } else { - std::stringstream msg; - msg << "Invalid spatial distribution for external source: " << type; - fatal_error(msg); + fatal_error(fmt::format( + "Invalid spatial distribution for external source: {}", type)); } } else { @@ -123,9 +134,8 @@ SourceDistribution::SourceDistribution(pugi::xml_node node) } else if (type == "mu-phi") { angle_ = UPtrAngle{new PolarAzimuthal(node_angle)}; } else { - std::stringstream msg; - msg << "Invalid angular distribution for external source: " << type; - fatal_error(msg); + fatal_error(fmt::format( + "Invalid angular distribution for external source: {}", type)); } } else { @@ -240,13 +250,12 @@ void initialize_source() { write_message("Initializing source particles...", 5); - if (settings::path_source != "") { + if (!settings::path_source.empty()) { // Read the source from a binary file instead of sampling from some // assumed source distribution - std::stringstream msg; - msg << "Reading source file from " << settings::path_source << "..."; - write_message(msg, 6); + write_message(fmt::format("Reading source file from {}...", + settings::path_source), 6); // Open the binary file hid_t file_id = file_open(settings::path_source, 'r', true); @@ -265,6 +274,27 @@ void initialize_source() // Close file file_close(file_id); + } else if (!settings::path_source_library.empty()) { + + #ifdef HAS_DYNAMIC_LINKING + // Get the source from a library object + write_message(fmt::format("Sampling from library source {}...", + settings::path_source), 6); + + // Open the library + auto source_library = dlopen(settings::path_source_library.c_str(), RTLD_LAZY); + if (!source_library) { + fatal_error("Couldn't open source library " + settings::path_source_library); + } + + // reset errors + dlerror(); + + fill_source_bank_custom_source(); + #else + fatal_error("Custom source libraries have not yet been implemented for " + "non-POSIX systems"); + #endif } else { // Generation source sites from specified distribution in user input @@ -325,10 +355,50 @@ void free_memory_source() model::external_sources.clear(); } +// fill the source bank from the external source +void fill_source_bank_custom_source() +{ +#ifdef HAS_DYNAMIC_LINKING + // Open the library + auto source_library = dlopen(settings::path_source_library.c_str(), RTLD_LAZY); + if (!source_library) { + fatal_error("Couldn't open source library " + settings::path_source_library); + } + + // reset errors + dlerror(); + + // get the function from the library + using sample_t = Particle::Bank (*)(uint64_t* seed); + auto sample_source = reinterpret_cast(dlsym(source_library, "sample_source")); + + // check for any dlsym errors + auto dlsym_error = dlerror(); + if (dlsym_error) { + dlclose(source_library); + fatal_error(fmt::format("Couldn't open the sample_source symbol: {}", dlsym_error)); + } + + // Generation source sites from specified distribution in the + // library source + for (int64_t i = 0; i < simulation::work_per_rank; ++i) { + // initialize random number seed + int64_t id = (simulation::total_gen + overall_generation()) * + settings::n_particles + simulation::work_index[mpi::rank] + i + 1; + uint64_t seed = init_seed(id, STREAM_SOURCE); + + // sample external source distribution + simulation::source_bank[i] = sample_source(&seed); + } + + // release the library + dlclose(source_library); +#endif +} + void fill_source_bank_fixedsource() { - if (settings::path_source.empty()) { - #pragma omp parallel for + if (settings::path_source.empty() && settings::path_source_library.empty()) { for (int64_t i = 0; i < simulation::work_per_rank; ++i) { // initialize random number seed int64_t id = (simulation::total_gen + overall_generation()) * @@ -338,6 +408,8 @@ void fill_source_bank_fixedsource() // sample external source distribution simulation::source_bank[i] = sample_external_source(&seed); } + } else if (settings::path_source.empty() && !settings::path_source_library.empty()) { + fill_source_bank_custom_source(); } } diff --git a/src/state_point.cpp b/src/state_point.cpp index 39f7a198c..503b1354e 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -2,10 +2,10 @@ #include #include // for int64_t -#include // for setfill, setw #include #include +#include #include "xtensor/xbuilder.hpp" // for empty_like #include "xtensor/xview.hpp" @@ -41,10 +41,8 @@ openmc_statepoint_write(const char* filename, bool* write_source) int w = std::to_string(settings::n_max_batches).size(); // Set filename for state point - std::stringstream ss; - ss << settings::path_output << "statepoint." << std::setfill('0') - << std::setw(w) << simulation::current_batch << ".h5"; - filename_ = ss.str(); + filename_ = fmt::format("{0}statepoint.{1:0{2}}.h5", + settings::path_output, simulation::current_batch, w); } // Determine whether or not to write the source bank @@ -420,8 +418,8 @@ void load_state_point() if (mpi::master) { #endif // Read global tally data - read_dataset(file_id, "global_tallies", H5T_NATIVE_DOUBLE, - simulation::global_tallies.data(), false); + read_dataset_lowlevel(file_id, "global_tallies", H5T_NATIVE_DOUBLE, + H5S_ALL, false, simulation::global_tallies.data()); // Check if tally results are present bool present; @@ -523,10 +521,8 @@ write_source_point(const char* filename) // Determine width for zero padding int w = std::to_string(settings::n_max_batches).size(); - std::stringstream s; - s << settings::path_output << "source." << std::setfill('0') - << std::setw(w) << simulation::current_batch << ".h5"; - filename_ = s.str(); + filename_ = fmt::format("{0}source.{1:0{2}}.h5", + settings::path_output, simulation::current_batch, w); } hid_t file_id; diff --git a/src/surface.cpp b/src/surface.cpp index f5399fa6a..a2c54260a 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -2,9 +2,10 @@ #include #include -#include #include +#include + #include "openmc/error.h" #include "openmc/dagmc.h" #include "openmc/hdf5_interface.h" @@ -35,10 +36,8 @@ void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1) std::string coeffs = get_node_value(surf_node, "coeffs"); int n_words = word_count(coeffs); if (n_words != 1) { - std::stringstream err_msg; - err_msg << "Surface " << surf_id << " expects 1 coeff but was given " - << n_words; - fatal_error(err_msg); + fatal_error(fmt::format("Surface {} expects 1 coeff but was given {}", + surf_id, n_words)); } // Parse the coefficients. @@ -55,10 +54,8 @@ void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1, double &c2, std::string coeffs = get_node_value(surf_node, "coeffs"); int n_words = word_count(coeffs); if (n_words != 3) { - std::stringstream err_msg; - err_msg << "Surface " << surf_id << " expects 3 coeffs but was given " - << n_words; - fatal_error(err_msg); + fatal_error(fmt::format("Surface {} expects 3 coeffs but was given {}", + surf_id, n_words)); } // Parse the coefficients. @@ -75,10 +72,8 @@ void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1, double &c2, std::string coeffs = get_node_value(surf_node, "coeffs"); int n_words = word_count(coeffs); if (n_words != 4) { - std::stringstream err_msg; - err_msg << "Surface " << surf_id << " expects 4 coeffs but was given " - << n_words; - fatal_error(err_msg); + fatal_error(fmt::format("Surface {} expects 4 coeffs but was given ", + surf_id, n_words)); } // Parse the coefficients. @@ -96,10 +91,8 @@ void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1, double &c2, std::string coeffs = get_node_value(surf_node, "coeffs"); int n_words = word_count(coeffs); if (n_words != 10) { - std::stringstream err_msg; - err_msg << "Surface " << surf_id << " expects 10 coeffs but was given " - << n_words; - fatal_error(err_msg); + fatal_error(fmt::format("Surface {} expects 10 coeffs but was given {}", + surf_id, n_words)); } // Parse the coefficients. @@ -145,10 +138,8 @@ Surface::Surface(pugi::xml_node surf_node) } else if (surf_bc == "periodic") { bc_ = BoundaryType::PERIODIC; } else { - std::stringstream err_msg; - err_msg << "Unknown boundary condition \"" << surf_bc - << "\" specified on surface " << id_; - fatal_error(err_msg); + fatal_error(fmt::format("Unknown boundary condition \"{}\" specified " + "on surface {}", surf_bc, id_)); } } else { @@ -1170,9 +1161,7 @@ void read_surfaces(pugi::xml_node node) model::surfaces.push_back(std::make_unique(surf_node)); } else { - std::stringstream err_msg; - err_msg << "Invalid surface type, \"" << surf_type << "\""; - fatal_error(err_msg); + fatal_error(fmt::format("Invalid surface type, \"{}\"", surf_type)); } } } @@ -1184,9 +1173,8 @@ void read_surfaces(pugi::xml_node node) if (in_map == model::surface_map.end()) { model::surface_map[id] = i_surf; } else { - std::stringstream err_msg; - err_msg << "Two or more surfaces use the same unique ID: " << id; - fatal_error(err_msg); + fatal_error(fmt::format( + "Two or more surfaces use the same unique ID: {}", id)); } } @@ -1202,11 +1190,9 @@ void read_surfaces(pugi::xml_node node) // Make sure this surface inherits from PeriodicSurface. if (!surf) { - std::stringstream err_msg; - err_msg << "Periodic boundary condition not supported for surface " - << surf_base->id_ - << ". Periodic BCs are only supported for planar surfaces."; - fatal_error(err_msg); + fatal_error(fmt::format( + "Periodic boundary condition not supported for surface {}. Periodic " + "BCs are only supported for planar surfaces.", surf_base->id_)); } // See if this surface makes part of the global bounding box. @@ -1278,10 +1264,8 @@ void read_surfaces(pugi::xml_node node) // This is a SurfacePlane. We won't try to find it's partner if the // user didn't specify one. if (surf->i_periodic_ == C_NONE) { - std::stringstream err_msg; - err_msg << "No matching periodic surface specified for periodic " - "boundary condition on surface " << surf->id_; - fatal_error(err_msg); + fatal_error(fmt::format("No matching periodic surface specified for " + "periodic boundary condition on surface {}", surf->id_)); } else { // Convert the surface id to an index. surf->i_periodic_ = model::surface_map[surf->i_periodic_]; @@ -1290,10 +1274,8 @@ void read_surfaces(pugi::xml_node node) // Make sure the opposite surface is also periodic. if (model::surfaces[surf->i_periodic_]->bc_ != Surface::BoundaryType::PERIODIC) { - std::stringstream err_msg; - err_msg << "Could not find matching surface for periodic boundary " - "condition on surface " << surf->id_; - fatal_error(err_msg); + fatal_error(fmt::format("Could not find matching surface for periodic " + "boundary condition on surface {}", surf->id_)); } } } diff --git a/src/tallies/derivative.cpp b/src/tallies/derivative.cpp index 1d2d2fc99..b749d6906 100644 --- a/src/tallies/derivative.cpp +++ b/src/tallies/derivative.cpp @@ -7,7 +7,7 @@ #include "openmc/tallies/tally.h" #include "openmc/xml_interface.h" -#include +#include template class std::vector; @@ -55,20 +55,16 @@ TallyDerivative::TallyDerivative(pugi::xml_node node) } } if (!found) { - std::stringstream out; - out << "Could not find the nuclide \"" << nuclide_name - << "\" specified in derivative " << id << " in any material."; - fatal_error(out); + fatal_error(fmt::format("Could not find the nuclide \"{}\" specified in " + "derivative {} in any material.", nuclide_name, id)); } } else if (variable_str == "temperature") { variable = DerivativeVariable::TEMPERATURE; } else { - std::stringstream out; - out << "Unrecognized variable \"" << variable_str - << "\" on derivative " << id; - fatal_error(out); + fatal_error(fmt::format("Unrecognized variable \"{}\" on derivative {}", + variable_str, id)); } diff_material = std::stoi(get_node_value(node, "material")); @@ -568,7 +564,7 @@ score_track_derivative(Particle* p, double distance) // A void material cannot be perturbed so it will not affect flux derivatives. if (p->material_ == MATERIAL_VOID) return; const Material& material {*model::materials[p->material_]}; - + for (auto idx = 0; idx < model::tally_derivs.size(); idx++) { const auto& deriv = model::tally_derivs[idx]; auto& flux_deriv = p->flux_derivs_[idx]; @@ -641,11 +637,9 @@ void score_collision_derivative(Particle* p) if (material.nuclide_[i] == deriv.diff_nuclide) break; // Make sure we found the nuclide. if (material.nuclide_[i] != deriv.diff_nuclide) { - std::stringstream err_msg; - err_msg << "Could not find nuclide " - << data::nuclides[deriv.diff_nuclide]->name_ << " in material " - << material.id_ << " for tally derivative " << deriv.id; - fatal_error(err_msg); + fatal_error(fmt::format( + "Could not find nuclide {} in material {} for tally derivative {}", + data::nuclides[deriv.diff_nuclide]->name_, material.id_, deriv.id)); } // phi is proportional to Sigma_s // (1 / phi) * (d_phi / d_N) = (d_Sigma_s / d_N) / Sigma_s diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index 689abde60..afe47fbd0 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -57,8 +57,10 @@ extern "C" size_t tally_filters_size() // Filter implementation //============================================================================== -Filter::Filter() : index_{model::tally_filters.size()} -{ } +Filter::Filter() +{ + index_ = model::tally_filters.size(); // Avoids warning about narrowing +} Filter::~Filter() { diff --git a/src/tallies/filter_azimuthal.cpp b/src/tallies/filter_azimuthal.cpp index 2cf03a127..a7c29b2db 100644 --- a/src/tallies/filter_azimuthal.cpp +++ b/src/tallies/filter_azimuthal.cpp @@ -1,7 +1,8 @@ #include "openmc/tallies/filter_azimuthal.h" #include -#include + +#include #include "openmc/constants.h" #include "openmc/error.h" @@ -77,9 +78,7 @@ AzimuthalFilter::to_statepoint(hid_t filter_group) const std::string AzimuthalFilter::text_label(int bin) const { - std::stringstream out; - out << "Azimuthal Angle [" << bins_[bin] << ", " << bins_[bin+1] << ")"; - return out.str(); + return fmt::format("Azimuthal Angle [{}, {})", bins_[bin], bins_[bin+1]); } } // namespace openmc diff --git a/src/tallies/filter_cell.cpp b/src/tallies/filter_cell.cpp index 3077c42ea..76a80f0d5 100644 --- a/src/tallies/filter_cell.cpp +++ b/src/tallies/filter_cell.cpp @@ -1,6 +1,6 @@ #include "openmc/tallies/filter_cell.h" -#include +#include #include "openmc/capi.h" #include "openmc/cell.h" @@ -17,10 +17,8 @@ CellFilter::from_xml(pugi::xml_node node) for (auto& c : cells) { auto search = model::cell_map.find(c); if (search == model::cell_map.end()) { - std::stringstream err_msg; - err_msg << "Could not find cell " << c - << " specified on tally filter."; - throw std::runtime_error{err_msg.str()}; + throw std::runtime_error{fmt::format( + "Could not find cell {} specified on tally filter.", c)}; } c = search->second; } @@ -72,7 +70,7 @@ CellFilter::to_statepoint(hid_t filter_group) const std::string CellFilter::text_label(int bin) const { - return "Cell " + std::to_string(model::cells[cells_[bin]]->id_); + return fmt::format("Cell {}", model::cells[cells_[bin]]->id_); } //============================================================================== diff --git a/src/tallies/filter_cell_instance.cpp b/src/tallies/filter_cell_instance.cpp index 3417ca077..e3d7e02b2 100644 --- a/src/tallies/filter_cell_instance.cpp +++ b/src/tallies/filter_cell_instance.cpp @@ -1,8 +1,9 @@ #include "openmc/tallies/filter_cell_instance.h" -#include #include +#include + #include "openmc/capi.h" #include "openmc/cell.h" #include "openmc/error.h" @@ -29,10 +30,8 @@ CellInstanceFilter::from_xml(pugi::xml_node node) gsl::index instance = cells[2*i + 1]; auto search = model::cell_map.find(cell_id); if (search == model::cell_map.end()) { - std::stringstream err_msg; - err_msg << "Could not find cell " << cell_id - << " specified on tally filter."; - throw std::runtime_error{err_msg.str()}; + throw std::runtime_error{fmt::format( + "Could not find cell {} specified on tally filter.", cell_id)}; } gsl::index index = search->second; instances.push_back({index, instance}); @@ -55,9 +54,9 @@ CellInstanceFilter::set_cell_instances(gsl::span instances) Expects(x.index_cell < model::cells.size()); const auto& c {model::cells[x.index_cell]}; if (c->type_ != Fill::MATERIAL) { - throw std::invalid_argument{"Cell " + std::to_string(c->id_) + " is not " - "filled with a material. Only material cells can be used in a cell " - "instance filter."}; + throw std::invalid_argument{fmt::format( + "Cell {} is not filled with a material. Only material cells can be " + "used in a cell instance filter.", c->id_)}; } cell_instances_.push_back(x); map_[x] = cell_instances_.size() - 1; diff --git a/src/tallies/filter_distribcell.cpp b/src/tallies/filter_distribcell.cpp index 9efe31759..43e68a8f9 100644 --- a/src/tallies/filter_distribcell.cpp +++ b/src/tallies/filter_distribcell.cpp @@ -1,5 +1,7 @@ #include "openmc/tallies/filter_distribcell.h" +#include + #include "openmc/cell.h" #include "openmc/error.h" #include "openmc/geometry_aux.h" // For distribcell_path @@ -19,10 +21,8 @@ DistribcellFilter::from_xml(pugi::xml_node node) // Find index in global cells vector corresponding to cell ID auto search = model::cell_map.find(cells[0]); if (search == model::cell_map.end()) { - std::stringstream err_msg; - err_msg << "Could not find cell " << cell_ - << " specified on tally filter."; - throw std::runtime_error{err_msg.str()}; + throw std::runtime_error{fmt::format( + "Could not find cell {} specified on tally filter.", cell_)}; } this->set_cell(search->second); diff --git a/src/tallies/filter_energy.cpp b/src/tallies/filter_energy.cpp index a2c69c947..1138dabe2 100644 --- a/src/tallies/filter_energy.cpp +++ b/src/tallies/filter_energy.cpp @@ -1,5 +1,7 @@ #include "openmc/tallies/filter_energy.h" +#include + #include "openmc/capi.h" #include "openmc/constants.h" // For F90_NONE #include "openmc/mgxs_interface.h" @@ -90,9 +92,7 @@ EnergyFilter::to_statepoint(hid_t filter_group) const std::string EnergyFilter::text_label(int bin) const { - std::stringstream out; - out << "Incoming Energy [" << bins_[bin] << ", " << bins_[bin+1] << ")"; - return out.str(); + return fmt::format("Incoming Energy [{}, {})", bins_[bin], bins_[bin+1]); } //============================================================================== @@ -119,9 +119,7 @@ EnergyoutFilter::get_all_bins(const Particle* p, TallyEstimator estimator, std::string EnergyoutFilter::text_label(int bin) const { - std::stringstream out; - out << "Outgoing Energy [" << bins_[bin] << ", " << bins_[bin+1] << ")"; - return out.str(); + return fmt::format("Outgoing Energy [{}, {})", bins_[bin], bins_[bin+1]); } //============================================================================== diff --git a/src/tallies/filter_energyfunc.cpp b/src/tallies/filter_energyfunc.cpp index 59cc4fcaa..8c4e5609d 100644 --- a/src/tallies/filter_energyfunc.cpp +++ b/src/tallies/filter_energyfunc.cpp @@ -1,8 +1,6 @@ #include "openmc/tallies/filter_energyfunc.h" -#include // for setprecision -#include // for scientific -#include +#include #include "openmc/error.h" #include "openmc/search.h" @@ -82,12 +80,9 @@ EnergyFunctionFilter::to_statepoint(hid_t filter_group) const std::string EnergyFunctionFilter::text_label(int bin) const { - std::stringstream out; - out << std::scientific << std::setprecision(1) - << "Energy Function f" - << "([ " << energy_.front() << ", ..., " << energy_.back() << "]) = " - << "[" << y_.front() << ", ..., " << y_.back() << "]"; - return out.str(); + return fmt::format( + "Energy Function f([{:.1e}, ..., {:.1e}]) = [{:.1e}, ..., {:.1e}]", + energy_.front(), energy_.back(), y_.front(), y_.back()); } //============================================================================== diff --git a/src/tallies/filter_material.cpp b/src/tallies/filter_material.cpp index f0c2bbf8e..8311fc73b 100644 --- a/src/tallies/filter_material.cpp +++ b/src/tallies/filter_material.cpp @@ -1,6 +1,6 @@ #include "openmc/tallies/filter_material.h" -#include +#include #include "openmc/capi.h" #include "openmc/material.h" @@ -16,10 +16,8 @@ MaterialFilter::from_xml(pugi::xml_node node) for (auto& m : mats) { auto search = model::material_map.find(m); if (search == model::material_map.end()) { - std::stringstream err_msg; - err_msg << "Could not find material " << m - << " specified on tally filter."; - throw std::runtime_error{err_msg.str()}; + throw std::runtime_error{fmt::format( + "Could not find material {} specified on tally filter.", m)}; } m = search->second; } @@ -69,7 +67,7 @@ MaterialFilter::to_statepoint(hid_t filter_group) const std::string MaterialFilter::text_label(int bin) const { - return "Material " + std::to_string(model::materials[materials_[bin]]->id_); + return fmt::format("Material {}", model::materials[materials_[bin]]->id_); } //============================================================================== diff --git a/src/tallies/filter_mesh.cpp b/src/tallies/filter_mesh.cpp index f80e31dc1..6c2cc8bd1 100644 --- a/src/tallies/filter_mesh.cpp +++ b/src/tallies/filter_mesh.cpp @@ -1,6 +1,6 @@ #include "openmc/tallies/filter_mesh.h" -#include +#include #include "openmc/capi.h" #include "openmc/constants.h" @@ -24,9 +24,8 @@ MeshFilter::from_xml(pugi::xml_node node) if (search != model::mesh_map.end()) { set_mesh(search->second); } else{ - std::stringstream err_msg; - err_msg << "Could not find mesh " << id << " specified on tally filter."; - fatal_error(err_msg); + fatal_error(fmt::format( + "Could not find mesh {} specified on tally filter.", id)); } } @@ -61,13 +60,13 @@ MeshFilter::text_label(int bin) const std::vector ijk(n_dim); mesh.get_indices_from_bin(bin, ijk.data()); - std::stringstream out; - out << "Mesh Index (" << ijk[0]; - if (n_dim > 1) out << ", " << ijk[1]; - if (n_dim > 2) out << ", " << ijk[2]; - out << ")"; - - return out.str(); + if (n_dim > 2) { + return fmt::format("Mesh Index ({}, {}, {})", ijk[0], ijk[1], ijk[2]); + } else if (n_dim > 1) { + return fmt::format("Mesh Index ({}, {})", ijk[0], ijk[1]); + } else { + return fmt::format("Mesh Index ({})", ijk[0]) ; + } } void diff --git a/src/tallies/filter_mu.cpp b/src/tallies/filter_mu.cpp index a345fe2bd..f874488b1 100644 --- a/src/tallies/filter_mu.cpp +++ b/src/tallies/filter_mu.cpp @@ -1,6 +1,6 @@ #include "openmc/tallies/filter_mu.h" -#include +#include #include "openmc/error.h" #include "openmc/search.h" @@ -69,9 +69,7 @@ MuFilter::to_statepoint(hid_t filter_group) const std::string MuFilter::text_label(int bin) const { - std::stringstream out; - out << "Change-in-Angle [" << bins_[bin] << ", " << bins_[bin+1] << ")"; - return out.str(); + return fmt::format("Change-in-Angle [{}, {})", bins_[bin], bins_[bin+1]); } } // namespace openmc diff --git a/src/tallies/filter_polar.cpp b/src/tallies/filter_polar.cpp index f35c1d3cd..c81410750 100644 --- a/src/tallies/filter_polar.cpp +++ b/src/tallies/filter_polar.cpp @@ -1,6 +1,6 @@ #include "openmc/tallies/filter_polar.h" -#include +#include #include "openmc/constants.h" #include "openmc/error.h" @@ -77,9 +77,7 @@ PolarFilter::to_statepoint(hid_t filter_group) const std::string PolarFilter::text_label(int bin) const { - std::stringstream out; - out << "Polar Angle [" << bins_[bin] << ", " << bins_[bin+1] << ")"; - return out.str(); + return fmt::format("Polar Angle [{}, {})", bins_[bin], bins_[bin+1]); } } // namespace openmc diff --git a/src/tallies/filter_sph_harm.cpp b/src/tallies/filter_sph_harm.cpp index bbeebf5b1..fe014d0f4 100644 --- a/src/tallies/filter_sph_harm.cpp +++ b/src/tallies/filter_sph_harm.cpp @@ -2,6 +2,9 @@ #include // For pair +#include +#include + #include "openmc/capi.h" #include "openmc/error.h" #include "openmc/math_functions.h" @@ -36,10 +39,8 @@ SphericalHarmonicsFilter::set_cosine(gsl::cstring_span cosine) } else if (cosine == "particle") { cosine_ = SphericalHarmonicsCosine::particle; } else { - std::stringstream err_msg; - err_msg << "Unrecognized cosine type, \"" << cosine - << "\" in spherical harmonics filter"; - throw std::invalid_argument{err_msg.str()}; + throw std::invalid_argument{fmt::format("Unrecognized cosine type, \"{}\" " + "in spherical harmonics filter", gsl::to_string(cosine))}; } } @@ -88,15 +89,14 @@ SphericalHarmonicsFilter::to_statepoint(hid_t filter_group) const std::string SphericalHarmonicsFilter::text_label(int bin) const { - std::stringstream out; + Expects(bin >= 0 && bin < n_bins_); for (int n = 0; n < order_ + 1; n++) { if (bin < (n + 1) * (n + 1)) { int m = (bin - n*n) - n; - out << "Spherical harmonic expansion, Y" << n << "," << m; - break; + return fmt::format("Spherical harmonic expansion, Y{},{}", n, m); } } - return out.str(); + UNREACHABLE(); } //============================================================================== diff --git a/src/tallies/filter_sptl_legendre.cpp b/src/tallies/filter_sptl_legendre.cpp index 71cc3f399..6f060ed44 100644 --- a/src/tallies/filter_sptl_legendre.cpp +++ b/src/tallies/filter_sptl_legendre.cpp @@ -2,6 +2,8 @@ #include // For pair +#include + #include "openmc/capi.h" #include "openmc/error.h" #include "openmc/math_functions.h" @@ -107,17 +109,13 @@ SpatialLegendreFilter::to_statepoint(hid_t filter_group) const std::string SpatialLegendreFilter::text_label(int bin) const { - std::stringstream out; - out << "Legendre expansion, "; if (axis_ == LegendreAxis::x) { - out << "x"; + return fmt::format("Legendre expansion, x axis, P{}", bin); } else if (axis_ == LegendreAxis::y) { - out << "y"; + return fmt::format("Legendre expansion, y axis, P{}", bin); } else { - out << "z"; + return fmt::format("Legendre expansion, z axis, P{}", bin); } - out << " axis, P" << std::to_string(bin); - return out.str(); } //============================================================================== diff --git a/src/tallies/filter_surface.cpp b/src/tallies/filter_surface.cpp index 5613f039a..72061becb 100644 --- a/src/tallies/filter_surface.cpp +++ b/src/tallies/filter_surface.cpp @@ -1,6 +1,6 @@ #include "openmc/tallies/filter_surface.h" -#include +#include #include "openmc/error.h" #include "openmc/surface.h" @@ -17,10 +17,8 @@ SurfaceFilter::from_xml(pugi::xml_node node) for (auto& s : surfaces) { auto search = model::surface_map.find(s); if (search == model::surface_map.end()) { - std::stringstream err_msg; - err_msg << "Could not find surface " << s - << " specified on tally filter."; - throw std::runtime_error{err_msg.str()}; + throw std::runtime_error{fmt::format( + "Could not find surface {} specified on tally filter.", s)}; } s = search->second; @@ -75,7 +73,7 @@ SurfaceFilter::to_statepoint(hid_t filter_group) const std::string SurfaceFilter::text_label(int bin) const { - return "Surface " + std::to_string(model::surfaces[surfaces_[bin]]->id_); + return fmt::format("Surface {}", model::surfaces[surfaces_[bin]]->id_); } } // namespace openmc diff --git a/src/tallies/filter_universe.cpp b/src/tallies/filter_universe.cpp index 289e96f56..ac3903041 100644 --- a/src/tallies/filter_universe.cpp +++ b/src/tallies/filter_universe.cpp @@ -1,6 +1,6 @@ #include "openmc/tallies/filter_universe.h" -#include +#include #include "openmc/cell.h" #include "openmc/error.h" @@ -16,10 +16,8 @@ UniverseFilter::from_xml(pugi::xml_node node) for (auto& u : universes) { auto search = model::universe_map.find(u); if (search == model::universe_map.end()) { - std::stringstream err_msg; - err_msg << "Could not find universe " << u - << " specified on tally filter."; - throw std::runtime_error{err_msg.str()}; + throw std::runtime_error{fmt::format( + "Could not find universe {} specified on tally filter.", u)}; } u = search->second; } @@ -71,7 +69,7 @@ UniverseFilter::to_statepoint(hid_t filter_group) const std::string UniverseFilter::text_label(int bin) const { - return "Universe " + std::to_string(model::universes[universes_[bin]]->id_); + return fmt::format("Universe {}", model::universes[universes_[bin]]->id_); } } // namespace openmc diff --git a/src/tallies/filter_zernike.cpp b/src/tallies/filter_zernike.cpp index 826d9f508..eab76cbc8 100644 --- a/src/tallies/filter_zernike.cpp +++ b/src/tallies/filter_zernike.cpp @@ -4,6 +4,9 @@ #include #include // For pair +#include +#include + #include "openmc/capi.h" #include "openmc/error.h" #include "openmc/math_functions.h" @@ -58,17 +61,16 @@ ZernikeFilter::to_statepoint(hid_t filter_group) const std::string ZernikeFilter::text_label(int bin) const { - std::stringstream out; + Expects(bin >= 0 && bin < n_bins_); for (int n = 0; n < order_+1; n++) { int last = (n + 1) * (n + 2) / 2; if (bin < last) { int first = last - (n + 1); int m = -n + (bin - first) * 2; - out << "Zernike expansion, Z" << n << "," << m; - break; + return fmt::format("Zernike expansion, Z{},{}", n, m); } } - return out.str(); + UNREACHABLE(); } void diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 49bfd901a..b63128dc0 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -28,6 +28,7 @@ #include "openmc/tallies/filter_surface.h" #include "openmc/xml_interface.h" +#include #include "xtensor/xadapt.hpp" #include "xtensor/xbuilder.hpp" // for empty_like #include "xtensor/xview.hpp" @@ -35,7 +36,6 @@ #include // for max #include #include // for size_t -#include #include namespace openmc { @@ -244,15 +244,16 @@ score_str_to_int(std::string score_str) //============================================================================== Tally::Tally(int32_t id) - : index_{model::tallies.size()} { + index_ = model::tallies.size(); // Avoids warning about narrowing this->set_id(id); this->set_filters({}); } Tally::Tally(pugi::xml_node node) - : index_{model::tallies.size()} { + index_ = model::tallies.size(); // Avoids warning about narrowing + // Copy and set tally id if (!check_for_node(node, "id")) { throw std::runtime_error{"Must specify id for tally in tally XML file."}; @@ -286,8 +287,8 @@ Tally::Tally(pugi::xml_node node) // Determine if filter ID is valid auto it = model::filter_map.find(filter_id); if (it == model::filter_map.end()) { - throw std::runtime_error{"Could not find filter " + std::to_string(filter_id) - + " specified on tally " + std::to_string(id_)}; + throw std::runtime_error{fmt::format( + "Could not find filter {} specified on tally {}", filter_id, id_)}; } // Store the index of the filter @@ -333,8 +334,7 @@ Tally::Tally(pugi::xml_node node) this->set_scores(node); if (!check_for_node(node, "scores")) { - fatal_error("No scores specified on tally " + std::to_string(id_) - + "."); + fatal_error(fmt::format("No scores specified on tally {}.", id_)); } // Check if tally is compatible with particle type @@ -370,9 +370,9 @@ Tally::Tally(pugi::xml_node node) auto pf = dynamic_cast(f); for (auto p : pf->particles()) { if (p != Particle::Type::neutron) { - warning("Particle filter other than NEUTRON used with photon " - "transport turned off. All tallies for particle type " + - std::to_string(static_cast(p)) + " will have no scores"); + warning(fmt::format("Particle filter other than NEUTRON used with " + "photon transport turned off. All tallies for particle type {}" + " will have no scores", static_cast(p))); } } } @@ -385,8 +385,8 @@ Tally::Tally(pugi::xml_node node) // Find the derivative with the given id, and store it's index. auto it = model::tally_deriv_map.find(deriv_id); if (it == model::tally_deriv_map.end()) { - fatal_error("Could not find derivative " + std::to_string(deriv_id) - + " specified on tally " + std::to_string(id_)); + fatal_error(fmt::format( + "Could not find derivative {} specified on tally {}", deriv_id, id_)); } deriv_ = it->second; @@ -402,11 +402,10 @@ Tally::Tally(pugi::xml_node node) || deriv.variable == DerivativeVariable::TEMPERATURE) { for (int i_nuc : nuclides_) { if (has_energyout && i_nuc == -1) { - fatal_error("Error on tally " + std::to_string(id_) - + ": Cannot use a 'nuclide_density' or 'temperature' " - "derivative on a tally with an outgoing energy filter and " - "'total' nuclide rate. Instead, tally each nuclide in the " - "material individually."); + fatal_error(fmt::format("Error on tally {}: Cannot use a " + "'nuclide_density' or 'temperature' derivative on a tally with an " + "outgoing energy filter and 'total' nuclide rate. Instead, tally " + "each nuclide in the material individually.", id_)); // Note that diff tallies with these characteristics would work // correctly if no tally events occur in the perturbed material // (e.g. pertrubing moderator but only tallying fuel), but this @@ -431,11 +430,11 @@ Tally::Tally(pugi::xml_node node) estimator_ = TallyEstimator::ANALOG; } else if (est == "tracklength" || est == "track-length" || est == "pathlength" || est == "path-length") { - // If the estimator was set to an analog/collision estimator, this means - // the tally needs post-collision information + // If the estimator was set to an analog estimator, this means the + // tally needs post-collision information if (estimator_ == TallyEstimator::ANALOG || estimator_ == TallyEstimator::COLLISION) { - throw std::runtime_error{"Cannot use track-length estimator for tally " - + std::to_string(id_)}; + throw std::runtime_error{fmt::format("Cannot use track-length " + "estimator for tally {}", id_)}; } // Set estimator to track-length estimator @@ -445,16 +444,16 @@ Tally::Tally(pugi::xml_node node) // If the estimator was set to an analog estimator, this means the // tally needs post-collision information if (estimator_ == TallyEstimator::ANALOG) { - throw std::runtime_error{"Cannot use collision estimator for tally " + - std::to_string(id_)}; + throw std::runtime_error{fmt::format("Cannot use collision estimator " + "for tally ", id_)}; } // Set estimator to collision estimator estimator_ = TallyEstimator::COLLISION; } else { - throw std::runtime_error{"Invalid estimator '" + est + "' on tally " + - std::to_string(id_)}; + throw std::runtime_error{fmt::format( + "Invalid estimator '{}' on tally {}", est, id_)}; } } } @@ -484,7 +483,7 @@ Tally::set_id(int32_t id) // Make sure no other tally has the same ID if (model::tally_map.find(id) != model::tally_map.end()) { - throw std::runtime_error{"Two tallies have the same ID: " + std::to_string(id)}; + throw std::runtime_error{fmt::format("Two tallies have the same ID: {}", id)}; } // If no ID specified, auto-assign next ID in sequence @@ -541,7 +540,7 @@ void Tally::set_scores(pugi::xml_node node) { if (!check_for_node(node, "scores")) - fatal_error("No scores specified on tally " + std::to_string(id_)); + fatal_error(fmt::format("No scores specified on tally {}", id_)); auto scores = get_node_array(node, "scores"); set_scores(scores); @@ -658,8 +657,9 @@ Tally::set_scores(const std::vector& scores) for (auto it1 = scores_.begin(); it1 != scores_.end(); ++it1) { for (auto it2 = it1 + 1; it2 != scores_.end(); ++it2) { if (*it1 == *it2) - fatal_error("Duplicate score of type \"" + reaction_name(*it1) - + "\" found in tally " + std::to_string(id_)); + fatal_error(fmt::format( + "Duplicate score of type \"{}\" found in tally {}", + reaction_name(*it1), id_)); } } @@ -719,9 +719,8 @@ Tally::set_nuclides(const std::vector& nuclides) } else { auto search = data::nuclide_map.find(nuc); if (search == data::nuclide_map.end()) - fatal_error("Could not find the nuclide " + nuc - + " specified in tally " + std::to_string(id_) - + " in any material"); + fatal_error(fmt::format("Could not find the nuclide {} specified in " + "tally {} in any material", nuc, id_)); nuclides_.push_back(search->second); } } @@ -742,15 +741,12 @@ Tally::init_triggers(pugi::xml_node node) } else if (type_str == "rel_err") { metric = TriggerMetric::relative_error; } else { - std::stringstream msg; - msg << "Unknown trigger type \"" << type_str << "\" in tally " << id_; - fatal_error(msg); + fatal_error(fmt::format("Unknown trigger type \"{}\" in tally {}", + type_str, id_)); } } else { - std::stringstream msg; - msg << "Must specify trigger type for tally " << id_ - << " in tally XML file"; - fatal_error(msg); + fatal_error(fmt::format( + "Must specify trigger type for tally {} in tally XML file", id_)); } // Read the trigger threshold. @@ -758,10 +754,8 @@ Tally::init_triggers(pugi::xml_node node) if (check_for_node(trigger_node, "threshold")) { threshold = std::stod(get_node_value(trigger_node, "threshold")); } else { - std::stringstream msg; - msg << "Must specify trigger threshold for tally " << id_ - << " in tally XML file"; - fatal_error(msg); + fatal_error(fmt::format( + "Must specify trigger threshold for tally {} in tally XML file", id_)); } // Read the trigger scores. @@ -785,10 +779,8 @@ Tally::init_triggers(pugi::xml_node node) if (reaction_name(this->scores_[i_score]) == score_str) break; } if (i_score == this->scores_.size()) { - std::stringstream msg; - msg << "Could not find the score \"" << score_str << "\" in tally " - << id_ << " but it was listed in a trigger on that tally"; - fatal_error(msg); + fatal_error(fmt::format("Could not find the score \"{}\" in tally " + "{} but it was listed in a trigger on that tally", score_str, id_)); } triggers_.push_back({metric, threshold, i_score}); } @@ -1077,7 +1069,7 @@ openmc_get_tally_index(int32_t id, int32_t* index) { auto it = model::tally_map.find(id); if (it == model::tally_map.end()) { - set_errmsg("No tally exists with ID=" + std::to_string(id) + "."); + set_errmsg(fmt::format("No tally exists with ID={}.", id)); return OPENMC_E_INVALID_ID; } @@ -1181,9 +1173,7 @@ openmc_tally_set_type(int32_t index, const char* type) } else if (strcmp(type, "surface") == 0) { model::tallies[index]->type_ = TallyType::SURFACE; } else { - std::stringstream errmsg; - errmsg << "Unknown tally type: " << type; - set_errmsg(errmsg); + set_errmsg(fmt::format("Unknown tally type: {}", type)); return OPENMC_E_INVALID_ARGUMENT; } diff --git a/src/tallies/trigger.cpp b/src/tallies/trigger.cpp index 7fb3475c9..0665828fd 100644 --- a/src/tallies/trigger.cpp +++ b/src/tallies/trigger.cpp @@ -1,9 +1,10 @@ #include "openmc/tallies/trigger.h" #include -#include #include // for std::pair +#include + #include "openmc/capi.h" #include "openmc/constants.h" #include "openmc/error.h" @@ -170,13 +171,14 @@ check_triggers() // At least one trigger is unsatisfied. Let the user know which one. simulation::satisfy_triggers = false; - std::stringstream msg; - msg << "Triggers unsatisfied, max unc./thresh. is "; + std::string msg; if (keff_ratio >= tally_ratio) { - msg << keff_ratio << " for eigenvalue"; + msg = fmt::format("Triggers unsatisfied, max unc./thresh. is {} for " + "eigenvalue", keff_ratio); } else { - msg << tally_ratio << " for " << reaction_name(score) << " in tally " - << tally_id; + msg = fmt::format( + "Triggers unsatisfied, max unc./thresh. is {} for {} in tally {}", + tally_ratio, reaction_name(score), tally_id); } write_message(msg, 7); @@ -189,10 +191,10 @@ check_triggers() auto n_pred_batches = static_cast(n_active * max_ratio * max_ratio) + settings::n_inactive + 1; - std::stringstream msg; - msg << "The estimated number of batches is " << n_pred_batches; + std::string msg = fmt::format("The estimated number of batches is {}", + n_pred_batches); if (n_pred_batches > settings::n_max_batches) { - msg << " --- greater than max batches"; + msg.append(" --- greater than max batches"); warning(msg); } else { write_message(msg, 7); diff --git a/src/thermal.cpp b/src/thermal.cpp index ee06ed8aa..67ca8905f 100644 --- a/src/thermal.cpp +++ b/src/thermal.cpp @@ -2,8 +2,8 @@ #include // for sort, move, min, max, find #include // for round, sqrt, abs -#include // for stringstream +#include #include "xtensor/xarray.hpp" #include "xtensor/xbuilder.hpp" #include "xtensor/xmath.hpp" @@ -88,10 +88,8 @@ ThermalScattering::ThermalScattering(hid_t group, const std::vector& tem temps_to_read.push_back(std::round(temp_actual)); } } else { - std::stringstream msg; - msg << "Nuclear data library does not contain cross sections for " - << name_ << " at or near " << std::round(T) << " K."; - fatal_error(msg); + fatal_error(fmt::format("Nuclear data library does not contain cross " + "sections for {} at or near {} K.", name_, std::round(T))); } } break; @@ -115,10 +113,8 @@ ThermalScattering::ThermalScattering(hid_t group, const std::vector& tem } } if (!found) { - std::stringstream msg; - msg << "Nuclear data library does not contain cross sections for " - << name_ << " at temperatures that bound " << std::round(T) << " K."; - fatal_error(msg); + fatal_error(fmt::format("Nuclear data library does not contain cross " + "sections for {} at temperatures that bound {} K.", name_, std::round(T))); } } } @@ -132,7 +128,7 @@ ThermalScattering::ThermalScattering(hid_t group, const std::vector& tem for (auto T : temps_to_read) { // Get temperature as a string - std::string temp_str = std::to_string(T) + "K"; + std::string temp_str = fmt::format("{}K", T); // Read exact temperature value double kT; diff --git a/src/track_output.cpp b/src/track_output.cpp index 5e071be01..314e43351 100644 --- a/src/track_output.cpp +++ b/src/track_output.cpp @@ -6,10 +6,10 @@ #include "openmc/settings.h" #include "openmc/simulation.h" +#include #include "xtensor/xtensor.hpp" #include // for size_t -#include #include #include @@ -35,9 +35,9 @@ void write_particle_track(Particle& p) void finalize_particle_track(Particle& p) { - std::stringstream filename; - filename << settings::path_output << "track_" << simulation::current_batch - << '_' << simulation::current_gen << '_' << p.id_ << ".h5"; + std::string filename = fmt::format("{}track_{}_{}_{}.h5", + settings::path_output, simulation::current_batch, simulation::current_gen, + p.id_); // Determine number of coordinates for each particle std::vector n_coords; @@ -47,7 +47,7 @@ void finalize_particle_track(Particle& p) #pragma omp critical (FinalizeParticleTrack) { - hid_t file_id = file_open(filename.str().c_str(), 'w'); + hid_t file_id = file_open(filename, 'w'); write_attribute(file_id, "filetype", "track"); write_attribute(file_id, "version", VERSION_TRACK); write_attribute(file_id, "n_particles", p.tracks_.size()); @@ -61,7 +61,7 @@ void finalize_particle_track(Particle& p) data(j, 1) = t[j].y; data(j, 2) = t[j].z; } - std::string name = "coordinates_" + std::to_string(i); + std::string name = fmt::format("coordinates_{}", i); write_dataset(file_id, name.c_str(), data); } file_close(file_id); diff --git a/src/volume_calc.cpp b/src/volume_calc.cpp index 10ea6bca4..8cae7743d 100644 --- a/src/volume_calc.cpp +++ b/src/volume_calc.cpp @@ -15,6 +15,7 @@ #include "openmc/timer.h" #include "openmc/xml_interface.h" +#include #ifdef _OPENMP #include #endif @@ -23,7 +24,6 @@ #include // for copy #include // for pow, sqrt -#include #include namespace openmc { @@ -66,9 +66,8 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) threshold_ = std::stod(get_node_value(threshold_node, "threshold")); if (threshold_ <= 0.0) { - std::stringstream msg; - msg << "Invalid error threshold " << threshold_ << " provided for a volume calculation."; - fatal_error(msg); + fatal_error(fmt::format("Invalid error threshold {} provided for a " + "volume calculation.", threshold_)); } std::string tmp = get_node_value(threshold_node, "type"); @@ -79,9 +78,8 @@ VolumeCalculation::VolumeCalculation(pugi::xml_node node) } else if ( tmp == "rel_err") { trigger_type_ = TriggerMetric::relative_error; } else { - std::stringstream msg; - msg << "Invalid volume calculation trigger type '" << tmp << "' provided."; - fatal_error(msg); + fatal_error(fmt::format( + "Invalid volume calculation trigger type '{}' provided.", tmp)); } } @@ -394,8 +392,7 @@ void VolumeCalculation::to_hdf5(const std::string& filename, for (int i = 0; i < domain_ids_.size(); ++i) { - hid_t group_id = create_group(file_id, "domain_" - + std::to_string(domain_ids_[i])); + hid_t group_id = create_group(file_id, fmt::format("domain_{}", domain_ids_[i])); // Write volume for domain const auto& result {results[i]}; @@ -468,7 +465,7 @@ int openmc_calculate_volumes() { for (int i = 0; i < model::volume_calcs.size(); ++i) { if (mpi::master) { - write_message("Running volume calculation " + std::to_string(i+1) + "...", 4); + write_message(fmt::format("Running volume calculation {}...", i + 1), 4); } // Run volume calculation @@ -487,15 +484,13 @@ int openmc_calculate_volumes() { // Display domain volumes for (int j = 0; j < vol_calc.domain_ids_.size(); j++) { - std::stringstream msg; - msg << domain_type << vol_calc.domain_ids_[j] << ": " << - results[j].volume[0] << " +/- " << results[j].volume[1] << " cm^3"; - write_message(msg, 4); + write_message(fmt::format("{}{}: {} +/- {} cm^3", domain_type, + vol_calc.domain_ids_[j], results[j].volume[0], results[j].volume[1]), 4); } // Write volumes to HDF5 file - std::string filename = settings::path_output + "volume_" - + std::to_string(i+1) + ".h5"; + std::string filename = fmt::format("{}volume_{}.h5", + settings::path_output, i + 1); vol_calc.to_hdf5(filename, results); } @@ -504,8 +499,7 @@ int openmc_calculate_volumes() { // Show elapsed time time_volume.stop(); if (mpi::master) { - write_message("Elapsed time: " + std::to_string(time_volume.elapsed()) - + " s", 6); + write_message(fmt::format("Elapsed time: {} s", time_volume.elapsed()), 6); } return 0; diff --git a/src/wmp.cpp b/src/wmp.cpp index f514b9e83..96e1d5641 100644 --- a/src/wmp.cpp +++ b/src/wmp.cpp @@ -6,8 +6,9 @@ #include "openmc/math_functions.h" #include "openmc/nuclide.h" +#include + #include -#include namespace openmc { @@ -201,15 +202,14 @@ void check_wmp_version(hid_t file) std::array version; read_attribute(file, "version", version); if (version[0] != WMP_VERSION[0]) { - std::stringstream msg; - msg << "WMP data format uses version " << version[0] << "." << - version[1] << " whereas your installation of OpenMC expects version " - << WMP_VERSION[0] << ".x data."; - fatal_error(msg); + fatal_error(fmt::format( + "WMP data format uses version {}.{} whereas your installation of " + "OpenMC expects version {}.x data.", + version[0], version[1], WMP_VERSION[0])); } } else { - fatal_error("WMP data does not indicate a version. Your installation of " - "OpenMC expects version " + std::to_string(WMP_VERSION[0]) + ".x data."); + fatal_error(fmt::format("WMP data does not indicate a version. Your " + "installation of OpenMC expects version {}x data.", WMP_VERSION[0])); } } diff --git a/src/xml_interface.cpp b/src/xml_interface.cpp index 60ae7ffca..a715dfae2 100644 --- a/src/xml_interface.cpp +++ b/src/xml_interface.cpp @@ -1,10 +1,9 @@ #include "openmc/xml_interface.h" -#include // for transform -#include +#include #include "openmc/error.h" - +#include "openmc/string_utils.h" namespace openmc { @@ -19,17 +18,13 @@ get_node_value(pugi::xml_node node, const char* name, bool lowercase, } else if (node.child(name)) { value_char = node.child_value(name); } else { - std::stringstream err_msg; - err_msg << "Node \"" << name << "\" is not a member of the \"" - << node.name() << "\" XML node"; - fatal_error(err_msg); + fatal_error(fmt::format( + "Node \"{}\" is not a member of the \"{}\" XML node", name, node.name())); } std::string value {value_char}; // Convert to lower-case if needed - if (lowercase) { - std::transform(value.begin(), value.end(), value.begin(), ::tolower); - } + if (lowercase) to_lower(value); // Strip leading/trailing whitespace if needed if (strip) { @@ -48,10 +43,8 @@ get_node_value_bool(pugi::xml_node node, const char* name) } else if (node.child(name)) { return node.child(name).text().as_bool(); } else { - std::stringstream err_msg; - err_msg << "Node \"" << name << "\" is not a member of the \"" - << node.name() << "\" XML node"; - fatal_error(err_msg); + fatal_error(fmt::format( + "Node \"{}\" is not a member of the \"{}\" XML node", name, node.name())); } return false; } diff --git a/tests/regression_tests/mgxs_library_distribcell/results_true.dat b/tests/regression_tests/mgxs_library_distribcell/results_true.dat index 9f58621c1..a24cfb7ce 100644 --- a/tests/regression_tests/mgxs_library_distribcell/results_true.dat +++ b/tests/regression_tests/mgxs_library_distribcell/results_true.dat @@ -1,91 +1,91 @@ - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.457353 0.010474 - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.410174 0.011573 - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.410166 0.011577 - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.066556 0.00251 - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.028979 0.002712 - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.037577 0.001487 - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.092377 0.003628 - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 7.276706e+06 287579.247699 - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.390797 0.008717 - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.387332 0.014241 - sum(distribcell) group in group out legendre nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P0 total 0.387009 0.014230 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P1 total 0.047179 0.004923 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P2 total 0.015713 0.003654 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P3 total 0.005378 0.003137 - sum(distribcell) group in group out legendre nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P0 total 0.387332 0.014241 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P1 total 0.047187 0.004933 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P2 total 0.015727 0.003654 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P3 total 0.005387 0.003141 - sum(distribcell) group in group out nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total 1.000834 0.037242 - sum(distribcell) group in group out nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total 0.094516 0.0059 - sum(distribcell) group in group out nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total 1.0 0.037213 - sum(distribcell) group in group out legendre nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P0 total 0.390797 0.016955 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P1 total 0.047641 0.005091 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P2 total 0.015866 0.003708 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P3 total 0.005430 0.003170 - sum(distribcell) group in group out legendre nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P0 total 0.391123 0.022356 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P1 total 0.047680 0.005395 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P2 total 0.015880 0.003758 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P3 total 0.005435 0.003179 - sum(distribcell) group out nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 1.0 0.080455 - sum(distribcell) group out nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 1.0 0.080541 - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 5.139437e-07 2.133314e-08 - sum(distribcell) group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.091725 0.003604 - sum(distribcell) group in group out nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total 0.093985 0.005872 - sum(distribcell) delayedgroup group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total 0.000021 8.253906e-07 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 2 1 total 0.000112 4.284000e-06 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 3 1 total 0.000109 4.105197e-06 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 4 1 total 0.000252 9.271419e-06 -4 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 5 1 total 0.000112 3.888624e-06 -5 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 6 1 total 0.000047 1.625563e-06 - sum(distribcell) delayedgroup group out nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total 0.0 0.000000 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 2 1 total 1.0 1.414214 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 3 1 total 1.0 1.414214 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 4 1 total 0.0 0.000000 -4 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 5 1 total 0.0 0.000000 -5 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 6 1 total 1.0 1.414214 - sum(distribcell) delayedgroup group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total 0.000227 0.000012 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 2 1 total 0.001209 0.000061 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 3 1 total 0.001177 0.000059 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 4 1 total 0.002727 0.000135 -4 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 5 1 total 0.001210 0.000058 -5 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 6 1 total 0.000504 0.000024 - sum(distribcell) delayedgroup group in nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total 0.013353 0.000686 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 2 1 total 0.032613 0.001627 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 3 1 total 0.121054 0.005911 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 4 1 total 0.305627 0.014428 -4 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 5 1 total 0.860892 0.037879 -5 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 6 1 total 2.891521 0.127879 - sum(distribcell) delayedgroup group in group out nuclide mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 1 total 0.000000 0.000000 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 2 1 1 total 0.000175 0.000175 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 3 1 1 total 0.000178 0.000178 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 4 1 1 total 0.000000 0.000000 -4 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 5 1 1 total 0.000000 0.000000 -5 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 6 1 1 total 0.000178 0.000178 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.457353 0.010474 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.410174 0.011573 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.410166 0.011577 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.066556 0.00251 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.028979 0.002712 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.037577 0.001487 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.092377 0.003628 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 7.276706e+06 287579.247699 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.390797 0.008717 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.387332 0.014241 + sum(distribcell) group in group out legendre nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P0 total 0.387009 0.014230 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P1 total 0.047179 0.004923 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P2 total 0.015713 0.003654 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P3 total 0.005378 0.003137 + sum(distribcell) group in group out legendre nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P0 total 0.387332 0.014241 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P1 total 0.047187 0.004933 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P2 total 0.015727 0.003654 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P3 total 0.005387 0.003141 + sum(distribcell) group in group out nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 total 1.000834 0.037242 + sum(distribcell) group in group out nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 total 0.094516 0.0059 + sum(distribcell) group in group out nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 total 1.0 0.037213 + sum(distribcell) group in group out legendre nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P0 total 0.390797 0.016955 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P1 total 0.047641 0.005091 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P2 total 0.015866 0.003708 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P3 total 0.005430 0.003170 + sum(distribcell) group in group out legendre nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P0 total 0.391123 0.022356 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P1 total 0.047680 0.005395 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P2 total 0.015880 0.003758 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 P3 total 0.005435 0.003179 + sum(distribcell) group out nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 1.0 0.080455 + sum(distribcell) group out nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 1.0 0.080541 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 5.139437e-07 2.133314e-08 + sum(distribcell) group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.091725 0.003604 + sum(distribcell) group in group out nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 total 0.093985 0.005872 + sum(distribcell) delayedgroup group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 total 0.000021 8.253906e-07 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 2 1 total 0.000112 4.284000e-06 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 3 1 total 0.000109 4.105197e-06 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 4 1 total 0.000252 9.271419e-06 +4 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 5 1 total 0.000112 3.888624e-06 +5 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 6 1 total 0.000047 1.625563e-06 + sum(distribcell) delayedgroup group out nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 total 0.0 0.000000 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 2 1 total 1.0 1.414214 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 3 1 total 1.0 1.414214 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 4 1 total 0.0 0.000000 +4 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 5 1 total 0.0 0.000000 +5 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 6 1 total 1.0 1.414214 + sum(distribcell) delayedgroup group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 total 0.000227 0.000012 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 2 1 total 0.001209 0.000061 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 3 1 total 0.001177 0.000059 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 4 1 total 0.002727 0.000135 +4 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 5 1 total 0.001210 0.000058 +5 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 6 1 total 0.000504 0.000024 + sum(distribcell) delayedgroup group in nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 total 0.013353 0.000686 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 2 1 total 0.032613 0.001627 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 3 1 total 0.121054 0.005911 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 4 1 total 0.305627 0.014428 +4 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 5 1 total 0.860892 0.037879 +5 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 6 1 total 2.891521 0.127879 + sum(distribcell) delayedgroup group in group out nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 1 1 total 0.000000 0.000000 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 2 1 1 total 0.000175 0.000175 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 3 1 1 total 0.000178 0.000178 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 4 1 1 total 0.000000 0.000000 +4 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 5 1 1 total 0.000000 0.000000 +5 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 6 1 1 total 0.000178 0.000178 diff --git a/tests/regression_tests/mgxs_library_distribcell/test.py b/tests/regression_tests/mgxs_library_distribcell/test.py index 5290d313b..9fe567388 100644 --- a/tests/regression_tests/mgxs_library_distribcell/test.py +++ b/tests/regression_tests/mgxs_library_distribcell/test.py @@ -1,8 +1,10 @@ import hashlib +import sys import openmc import openmc.mgxs from openmc.examples import pwr_assembly +import pytest from tests.testing_harness import PyAPITestHarness @@ -64,6 +66,8 @@ class MGXSTestHarness(PyAPITestHarness): return outstr +@pytest.mark.xfail(sys.version_info < (3, 6), + reason="Pandas 1.0 API changed and requires Python 3.6+") def test_mgxs_library_distribcell(): model = pwr_assembly() harness = MGXSTestHarness('statepoint.10.h5', model) diff --git a/tests/regression_tests/source_dlopen/__init__.py b/tests/regression_tests/source_dlopen/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/source_dlopen/inputs_true.dat b/tests/regression_tests/source_dlopen/inputs_true.dat new file mode 100644 index 000000000..23878ac20 --- /dev/null +++ b/tests/regression_tests/source_dlopen/inputs_true.dat @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + 0 + + diff --git a/tests/regression_tests/source_dlopen/results_true.dat b/tests/regression_tests/source_dlopen/results_true.dat new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/source_dlopen/source_sampling.cpp b/tests/regression_tests/source_dlopen/source_sampling.cpp new file mode 100644 index 000000000..eaf8b74d9 --- /dev/null +++ b/tests/regression_tests/source_dlopen/source_sampling.cpp @@ -0,0 +1,23 @@ +#include +#include "openmc/random_lcg.h" +#include "openmc/source.h" +#include "openmc/particle.h" + +// you must have external C linkage here otherwise +// dlopen will not find the file +extern "C" openmc::Particle::Bank sample_source(uint64_t *seed) { + openmc::Particle::Bank particle; + // wgt + particle.particle = openmc::Particle::Type::neutron; + particle.wgt = 1.0; + // position + + particle.r.x = 0.; + particle.r.y = 0.; + particle.r.z = 0.; + // angle + particle.u = {1.0, 0.0, 0.0}; + particle.E = 14.08e6; + particle.delayed_group = 0; + return particle; +} diff --git a/tests/regression_tests/source_dlopen/test.py b/tests/regression_tests/source_dlopen/test.py new file mode 100644 index 000000000..12969cf29 --- /dev/null +++ b/tests/regression_tests/source_dlopen/test.py @@ -0,0 +1,73 @@ +from pathlib import Path +import os +import shutil +import subprocess +import textwrap + +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def compile_source(request): + """Compile the external source""" + + # Get build directory and write CMakeLists.txt file + openmc_dir = Path(str(request.config.rootdir)) / 'build' + with open('CMakeLists.txt', 'w') as f: + f.write(textwrap.dedent(""" + cmake_minimum_required(VERSION 3.3 FATAL_ERROR) + project(openmc_sources CXX) + add_library(source SHARED source_sampling.cpp) + find_package(OpenMC REQUIRED HINTS {}) + target_link_libraries(source OpenMC::libopenmc) + """.format(openmc_dir))) + + # Create temporary build directory and change to there + local_builddir = Path('build') + local_builddir.mkdir(exist_ok=True) + os.chdir(str(local_builddir)) + + # Run cmake/make to build the shared libary + subprocess.run(['cmake', os.path.pardir], check=True) + subprocess.run(['make'], check=True) + os.chdir(os.path.pardir) + + yield + + # Remove local build directory when test is complete + shutil.rmtree('build') + + +@pytest.fixture +def model(): + model = openmc.model.Model() + natural_lead = openmc.Material(name="natural_lead") + natural_lead.add_element('Pb', 1.0) + natural_lead.set_density('g/cm3', 11.34) + model.materials.append(natural_lead) + + # geometry + surface_sph1 = openmc.Sphere(r=100, boundary_type='vacuum') + cell_1 = openmc.Cell(fill=natural_lead, region=-surface_sph1) + model.geometry = openmc.Geometry([cell_1]) + + # settings + model.settings.batches = 10 + model.settings.inactive = 0 + model.settings.particles = 1000 + model.settings.run_mode = 'fixed source' + + # custom source from shared library + source = openmc.Source() + source.library = 'build/libsource.so' + model.settings.source = source + + return model + + +def test_dlopen_source(compile_source, model): + harness = PyAPITestHarness('statepoint.10.h5', model) + harness.main() diff --git a/tests/unit_tests/test_data_photon.py b/tests/unit_tests/test_data_photon.py index c767d19e6..f7274e9c1 100644 --- a/tests/unit_tests/test_data_photon.py +++ b/tests/unit_tests/test_data_photon.py @@ -2,6 +2,7 @@ from collections.abc import Mapping, Callable import os +from pathlib import Path import numpy as np import pandas as pd @@ -144,3 +145,9 @@ def test_export_to_hdf5(tmpdir, element): element2.bremsstrahlung['electron_energy']).all() # Export to hdf5 again element2.export_to_hdf5(filename, 'w') + +def test_photodat_only(run_in_tmpdir): + endf_dir = Path(os.environ['OPENMC_ENDF_DATA']) + photoatomic_file = endf_dir / 'photoat' / 'photoat-001_H_000.endf' + data = openmc.data.IncidentPhoton.from_endf(photoatomic_file) + data.export_to_hdf5('tmp.h5', 'w') \ No newline at end of file diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 47c9690ae..6c09b3fea 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -7,6 +7,7 @@ will be left unimplemented and testing will be done via regression. """ import copy +from random import uniform from unittest.mock import MagicMock import numpy as np @@ -15,11 +16,24 @@ import pytest from openmc.deplete import ( ReactionRates, Results, ResultsList, comm, OperatorResult, - PredictorIntegrator, SICELIIntegrator) + PredictorIntegrator, CECMIntegrator, CF4Integrator, CELIIntegrator, + EPCRK4Integrator, LEQIIntegrator, SICELIIntegrator, SILEQIIntegrator) from tests import dummy_operator +INTEGRATORS = [ + PredictorIntegrator, + CECMIntegrator, + CF4Integrator, + CELIIntegrator, + EPCRK4Integrator, + LEQIIntegrator, + SICELIIntegrator, + SILEQIIntegrator +] + + def test_results_save(run_in_tmpdir): """Test data save module""" @@ -105,14 +119,14 @@ def test_results_save(run_in_tmpdir): np.testing.assert_array_equal(res[1].time, t2) -@pytest.mark.parametrize("timesteps", (1, [1])) -def test_bad_integrator_inputs(timesteps): +def test_bad_integrator_inputs(): """Test failure modes for Integrator inputs""" op = MagicMock() op.prev_res = None op.chain = None op.heavy_metal = 1.0 + timesteps = [1] # No power nor power density given with pytest.raises(ValueError, match="Either power or power density"): @@ -159,3 +173,62 @@ def test_integrator(run_in_tmpdir, scheme): dep_time = res.get_depletion_time() assert dep_time.shape == (2, ) assert all(dep_time > 0) + + +@pytest.mark.parametrize("integrator", INTEGRATORS) +def test_timesteps(integrator): + # Crate fake operator + op = MagicMock() + op.prev_res = None + op.chain = None + + # Set heavy metal mass and power randomly + op.heavy_metal = uniform(0, 10000) + power = uniform(0, 1e6) + + # Reference timesteps in seconds + day = 86400.0 + ref_timesteps = [1*day, 2*day, 5*day, 10*day] + + # Case 1, timesteps in seconds + timesteps = ref_timesteps + x = integrator(op, timesteps, power, timestep_units='s') + assert np.allclose(x.timesteps, ref_timesteps) + + # Case 2, timesteps in minutes + minute = 60 + timesteps = [t / minute for t in ref_timesteps] + x = integrator(op, timesteps, power, timestep_units='min') + assert np.allclose(x.timesteps, ref_timesteps) + + # Case 3, timesteps in hours + hour = 60*60 + timesteps = [t / hour for t in ref_timesteps] + x = integrator(op, timesteps, power, timestep_units='h') + assert np.allclose(x.timesteps, ref_timesteps) + + # Case 4, timesteps in days + timesteps = [t / day for t in ref_timesteps] + x = integrator(op, timesteps, power, timestep_units='d') + assert np.allclose(x.timesteps, ref_timesteps) + + # Case 5, timesteps in MWd/kg + kilograms = op.heavy_metal / 1000.0 + days = [t/day for t in ref_timesteps] + megawatts = power / 1000000.0 + burnup = [t * megawatts / kilograms for t in days] + x = integrator(op, burnup, power, timestep_units='MWd/kg') + assert np.allclose(x.timesteps, ref_timesteps) + + # Case 6, mixed units + burnup_per_day = (1e-6*power) / kilograms + timesteps = [(burnup_per_day, 'MWd/kg'), (2*day, 's'), (5, 'd'), + (10*burnup_per_day, 'MWd/kg')] + x = integrator(op, timesteps, power) + assert np.allclose(x.timesteps, ref_timesteps) + + # Bad units should raise an exception + with pytest.raises(ValueError, match="unit"): + integrator(op, ref_timesteps, power, timestep_units='🐨') + with pytest.raises(ValueError, match="unit"): + integrator(op, [(800.0, 'gorillas')], power) diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index 7d68b3a3d..b8a258df0 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -149,12 +149,18 @@ def test_fission_yield_distribution(): # __getitem__ return yields as a view into yield matrix assert orig_yields.yields.base is yield_dist.yield_matrix - # Fission yield feature uses scaled and incremented + # Scale and increment fission yields mod_yields = orig_yields * 2 assert numpy.array_equal(orig_yields.yields * 2, mod_yields.yields) mod_yields += orig_yields assert numpy.array_equal(orig_yields.yields * 3, mod_yields.yields) + mod_yields = 2.0 * orig_yields + assert numpy.array_equal(orig_yields.yields * 2, mod_yields.yields) + + mod_yields = numpy.float64(2.0) * orig_yields + assert numpy.array_equal(orig_yields.yields * 2, mod_yields.yields) + # Failure modes for adding, multiplying yields similar = numpy.empty_like(orig_yields.yields) with pytest.raises(TypeError): diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index 9c33f6a2d..c3ff72879 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -9,6 +9,8 @@ def test_export_to_xml(run_in_tmpdir): s.generations_per_batch = 10 s.inactive = 100 s.particles = 1000000 + s.max_lost_particles = 5 + s.rel_max_lost_particles = 1e-4 s.keff_trigger = {'type': 'std_dev', 'threshold': 0.001} s.energy_mode = 'continuous-energy' s.max_order = 5 @@ -62,6 +64,8 @@ def test_export_to_xml(run_in_tmpdir): assert s.generations_per_batch == 10 assert s.inactive == 100 assert s.particles == 1000000 + assert s.max_lost_particles == 5 + assert s.rel_max_lost_particles == 1e-4 assert s.keff_trigger == {'type': 'std_dev', 'threshold': 0.001} assert s.energy_mode == 'continuous-energy' assert s.max_order == 5 diff --git a/tests/unit_tests/test_source.py b/tests/unit_tests/test_source.py index 1c70e159d..d4d17a3da 100644 --- a/tests/unit_tests/test_source.py +++ b/tests/unit_tests/test_source.py @@ -34,3 +34,11 @@ def test_source_file(): elem = src.to_xml_element() assert 'strength' in elem.attrib assert 'file' in elem.attrib + +def test_source_dlopen(): + library = './libsource.so' + src = openmc.Source(library=library) + assert src.library == library + + elem = src.to_xml_element() + assert 'library' in elem.attrib diff --git a/tests/unit_tests/test_surface.py b/tests/unit_tests/test_surface.py index 703010a05..749ae9621 100644 --- a/tests/unit_tests/test_surface.py +++ b/tests/unit_tests/test_surface.py @@ -2,6 +2,7 @@ from functools import partial from random import uniform, seed import numpy as np +import math import openmc import pytest @@ -139,6 +140,51 @@ def test_zplane(): repr(s) +def test_cylinder(): + x0, y0, z0, r = 2, 3, 4, 2 + dx, dy, dz = 1, -1, 1 + s = openmc.Cylinder(x0=x0, y0=y0, z0=z0, dx=dx, dy=dy, dz=dz, r=r) + assert s.x0 == 2 + assert s.y0 == 3 + assert s.z0 == 4 + assert s.dx == 1 + assert s.dy == -1 + assert s.dz == 1 + assert s.r == 2 + + # Check bounding box + assert_infinite_bb(s) + + # evaluate method + # |(p - p1) ⨯ (p - p2)|^2 / |p2 - p1|^2 - r^2 + p1 = s._origin + p2 = p1 + s._axis + perp = np.array((1, -2, 1))*(1 / s._axis) + divisor = np.linalg.norm(p2 - p1) + pin = p1 + 5*s._axis # point inside cylinder + pout = np.array((4., 0., 2.5)) # point outside the cylinder + pon = p1 + s.r*perp / np.linalg.norm(perp) # point on cylinder + for p, fn in zip((pin, pout, pon), (np.less, np.greater, np.isclose)): + c1 = np.linalg.norm(np.cross(p - p1, p - p2)) / divisor + val = c1*c1 - s.r*s.r + p_eval = s.evaluate(p) + assert fn(p_eval, 0.) + assert p_eval == pytest.approx(val) + + # translate method + st = s.translate((1.0, 1.0, 1.0)) + assert st.x0 == s.x0 + 1 + assert st.y0 == s.y0 + 1 + assert st.z0 == s.z0 + 1 + assert st.dx == s.dx + assert st.dy == s.dy + assert st.dz == s.dz + assert st.r == s.r + + # Make sure repr works + repr(s) + + def test_xcylinder(): y, z, r = 3, 5, 2 s = openmc.XCylinder(y0=y, z0=z, r=r) @@ -284,6 +330,58 @@ def cone_common(apex, r2, cls): repr(s) +def test_cone(): + x0, y0, z0, r2 = 2, 3, 4, 4 + dx, dy, dz = 1, -1, 1 + s = openmc.Cone(x0=x0, y0=y0, z0=z0, dx=dx, dy=dy, dz=dz, r2=r2) + assert s.x0 == 2 + assert s.y0 == 3 + assert s.z0 == 4 + assert s.dx == 1 + assert s.dy == -1 + assert s.dz == 1 + assert s.r2 == 4 + + # Check bounding box + assert_infinite_bb(s) + + # evaluate method + # cos^2(theta) * ((p - p1))**2 - (d @ (p - p1))^2 + # The argument r2 for cones is actually tan^2(theta) so that + # cos^2(theta) = 1 / (1 + r2) + # + # This makes the evaluation equation shown below where p is the evaluation + # point (x, y, z) p1 is the apex (origin) of the cone and r2 is related to + # the aperature of the cone as described above + # (p - p1) @ (p - p1) / (1 + r2) - (d @ (p - p1))^2 + # point inside + p1 = s._origin + d = s._axis + perp = np.array((1, -2, 1))*(1 / d) + perp /= np.linalg.norm(perp) + pin = p1 + 5*d # point inside cone + pout = p1 + 3.2*perp # point outside cone + pon = p1 + 3.2*d + 3.2*math.sqrt(s.r2)*perp # point on cone + for p, fn in zip((pin, pout, pon), (np.less, np.greater, np.isclose)): + val = np.sum((p - p1)**2) / (1 + s.r2) - np.sum((d @ (p - p1))**2) + p_eval = s.evaluate(p) + assert fn(p_eval, 0.) + assert p_eval == pytest.approx(val) + + # translate method + st = s.translate((1.0, 1.0, 1.0)) + assert st.x0 == s.x0 + 1 + assert st.y0 == s.y0 + 1 + assert st.z0 == s.z0 + 1 + assert st.dx == s.dx + assert st.dy == s.dy + assert st.dz == s.dz + assert st.r2 == s.r2 + + # Make sure repr works + repr(s) + + def test_xcone(): apex = (10, 0, 0) r2 = 4 @@ -339,7 +437,7 @@ def test_cylinder_from_points(): p1 = np.array([xi(), xi(), xi()]) p2 = np.array([xi(), xi(), xi()]) r = uniform(1.0, 100.0) - s = openmc.model.cylinder_from_points(p1, p2, r) + s = openmc.Cylinder.from_points(p1, p2, r) # Points p1 and p2 need to be inside cylinder assert p1 in -s @@ -369,24 +467,27 @@ def test_cylinder_from_points_axis(): # (x - 3)^2 + (y - 4)^2 = 2^2 # x^2 + y^2 - 6x - 8y + 21 = 0 - s = openmc.model.cylinder_from_points((3., 4., 0.), (3., 4., 1.), 2.) - assert (s.a, s.b, s.c) == pytest.approx((1., 1., 0.)) - assert (s.d, s.e, s.f) == pytest.approx((0., 0., 0.)) - assert (s.g, s.h, s.j) == pytest.approx((-6., -8., 0.)) - assert s.k == pytest.approx(21.) + s = openmc.Cylinder.from_points((3., 4., 0.), (3., 4., 1.), 2.) + a, b, c, d, e, f, g, h, j, k = s._get_base_coeffs() + assert (a, b, c) == pytest.approx((1., 1., 0.)) + assert (d, e, f) == pytest.approx((0., 0., 0.)) + assert (g, h, j) == pytest.approx((-6., -8., 0.)) + assert k == pytest.approx(21.) # (y + 7)^2 + (z - 1)^2 = 3^2 # y^2 + z^2 + 14y - 2z + 41 = 0 - s = openmc.model.cylinder_from_points((0., -7, 1.), (1., -7., 1.), 3.) - assert (s.a, s.b, s.c) == pytest.approx((0., 1., 1.)) - assert (s.d, s.e, s.f) == pytest.approx((0., 0., 0.)) - assert (s.g, s.h, s.j) == pytest.approx((0., 14., -2.)) - assert s.k == 41. + s = openmc.Cylinder.from_points((0., -7, 1.), (1., -7., 1.), 3.) + a, b, c, d, e, f, g, h, j, k = s._get_base_coeffs() + assert (a, b, c) == pytest.approx((0., 1., 1.)) + assert (d, e, f) == pytest.approx((0., 0., 0.)) + assert (g, h, j) == pytest.approx((0., 14., -2.)) + assert k == 41. # (x - 2)^2 + (z - 5)^2 = 4^2 # x^2 + z^2 - 4x - 10z + 13 = 0 - s = openmc.model.cylinder_from_points((2., 0., 5.), (2., 1., 5.), 4.) - assert (s.a, s.b, s.c) == pytest.approx((1., 0., 1.)) - assert (s.d, s.e, s.f) == pytest.approx((0., 0., 0.)) - assert (s.g, s.h, s.j) == pytest.approx((-4., 0., -10.)) - assert s.k == pytest.approx(13.) + s = openmc.Cylinder.from_points((2., 0., 5.), (2., 1., 5.), 4.) + a, b, c, d, e, f, g, h, j, k = s._get_base_coeffs() + assert (a, b, c) == pytest.approx((1., 0., 1.)) + assert (d, e, f) == pytest.approx((0., 0., 0.)) + assert (g, h, j) == pytest.approx((-4., 0., -10.)) + assert k == pytest.approx(13.) diff --git a/vendor/fmt b/vendor/fmt new file mode 160000 index 000000000..65ac626c5 --- /dev/null +++ b/vendor/fmt @@ -0,0 +1 @@ +Subproject commit 65ac626c5856f5aad1f1542e79407a6714357043 diff --git a/vendor/gsl-lite b/vendor/gsl-lite index 93607223a..a5706326e 160000 --- a/vendor/gsl-lite +++ b/vendor/gsl-lite @@ -1 +1 @@ -Subproject commit 93607223a48621dae3cedd6b3335431b38067fae +Subproject commit a5706326ed116c315c0e12b72ed39439aff2222f