Pull from upstream before testing

This commit is contained in:
Isaac Meyer 2018-07-05 16:23:14 -05:00
commit 0bcb6f8874
311 changed files with 26880 additions and 22017 deletions

15
.gitignore vendored
View file

@ -42,11 +42,8 @@ results_error.dat
inputs_error.dat
results_test.dat
# Test build files
tests/build/
tests/coverage/
tests/memcheck/
tests/ctestscript.run
# Test
.pytest_cache/
# HDF5 files
*.h5
@ -102,4 +99,10 @@ examples/jupyter/plots
.tox/
.python-version
.coverage
htmlcov
htmlcov
#macOS
*.DS_Store
#Dynamic Library
*.dylib

View file

@ -1,26 +1,14 @@
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
project(openmc Fortran C CXX)
# Setup output directories
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/include)
# Set module path
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules)
# Make sure Fortran module directory is included when building
include_directories(${CMAKE_BINARY_DIR}/include)
#===============================================================================
# Architecture specific definitions
#===============================================================================
if (${UNIX})
add_definitions(-DUNIX)
endif()
#===============================================================================
# Command line options
#===============================================================================
@ -31,10 +19,7 @@ option(debug "Compile with debug flags" OFF)
option(optimize "Turn on all compiler optimization flags" OFF)
option(coverage "Compile with coverage analysis flags" OFF)
option(mpif08 "Use Fortran 2008 MPI interface" OFF)
# Maximum number of nested coordinates levels
set(maxcoord 10 CACHE STRING "Maximum number of nested coordinate levels")
add_definitions(-DMAX_COORD=${maxcoord})
#===============================================================================
# MPI for distributed-memory parallelism
@ -43,17 +28,12 @@ add_definitions(-DMAX_COORD=${maxcoord})
set(MPI_ENABLED FALSE)
if($ENV{FC} MATCHES "(mpi[^/]*|ftn)$")
message("-- Detected MPI wrapper: $ENV{FC}")
add_definitions(-DOPENMC_MPI)
set(MPI_ENABLED TRUE)
# Get directory containing MPI wrapper
get_filename_component(MPI_DIR $ENV{FC} DIRECTORY)
endif()
# Check for Fortran 2008 MPI interface
if(MPI_ENABLED AND mpif08)
message("-- Using Fortran 2008 MPI bindings")
add_definitions(-DOPENMC_MPIF08)
endif()
#===============================================================================
@ -73,7 +53,7 @@ if(NOT DEFINED HDF5_PREFER_PARALLEL)
endif()
endif()
find_package(HDF5 COMPONENTS Fortran_HL)
find_package(HDF5 COMPONENTS HL)
if(NOT HDF5_FOUND)
message(FATAL_ERROR "Could not find HDF5")
endif()
@ -81,7 +61,6 @@ if(HDF5_IS_PARALLEL)
if(NOT MPI_ENABLED)
message(FATAL_ERROR "Parallel HDF5 must be used with MPI.")
endif()
add_definitions(-DPHDF5)
message("-- Using parallel HDF5")
endif()
@ -89,19 +68,15 @@ endif()
# Set compile/link flags based on which compiler is being used
#===============================================================================
# Support for Fortran in FindOpenMP was added in CMake 3.1. To support lower
# versions, we manually add the flags. However, at some point in time, the
# manual logic can be removed in favor of the block below
#if(NOT (CMAKE_VERSION VERSION_LESS 3.1))
# if(openmp)
# find_package(OpenMP)
# if(OPENMP_FOUND)
# list(APPEND f90flags ${OpenMP_Fortran_FLAGS})
# list(APPEND ldflags ${OpenMP_Fortran_FLAGS})
# endif()
# endif()
#endif()
if(openmp)
# Requires CMake 3.1+
find_package(OpenMP)
if(OPENMP_FOUND)
list(APPEND f90flags ${OpenMP_Fortran_FLAGS})
list(APPEND cxxflags ${OpenMP_CXX_FLAGS})
list(APPEND ldflags ${OpenMP_Fortran_FLAGS})
endif()
endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
@ -129,10 +104,6 @@ if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU)
list(REMOVE_ITEM f90flags -O2)
list(APPEND f90flags -O3)
endif()
if(openmp)
list(APPEND f90flags -fopenmp)
list(APPEND ldflags -fopenmp)
endif()
if(coverage)
list(APPEND f90flags -coverage)
list(APPEND ldflags -coverage)
@ -153,10 +124,6 @@ elseif(CMAKE_Fortran_COMPILER_ID STREQUAL Intel)
if(optimize)
list(APPEND f90flags -O3)
endif()
if(openmp)
list(APPEND f90flags -qopenmp)
list(APPEND ldflags -qopenmp)
endif()
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL PGI)
# PGI Fortran compiler options
@ -191,10 +158,6 @@ elseif(CMAKE_Fortran_COMPILER_ID STREQUAL XL)
list(REMOVE_ITEM f90flags -O2)
list(APPEND f90flags -O3)
endif()
if(openmp)
list(APPEND f90flags -qsmp=omp)
list(APPEND ldflags -qsmp=omp)
endif()
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL Cray)
# Cray Fortran compiler options
@ -208,7 +171,7 @@ endif()
if(CMAKE_C_COMPILER_ID STREQUAL GNU)
# GCC compiler options
list(APPEND cflags -std=c99 -O2)
list(APPEND cflags -O2)
if(debug)
list(REMOVE_ITEM cflags -O2)
list(APPEND cflags -g -Wall -pedantic -fbounds-check)
@ -226,7 +189,6 @@ if(CMAKE_C_COMPILER_ID STREQUAL GNU)
elseif(CMAKE_C_COMPILER_ID STREQUAL Intel)
# Intel compiler options
list(APPEND cflags -std=c99)
if(debug)
list(APPEND cflags -g -w3 -ftrapuv -fp-stack-check -O0)
endif()
@ -239,7 +201,6 @@ elseif(CMAKE_C_COMPILER_ID STREQUAL Intel)
elseif(CMAKE_C_COMPILER_ID MATCHES Clang)
# Clang options
list(APPEND cflags -std=c99)
if(debug)
list(APPEND cflags -g -O0 -ftrapv)
endif()
@ -261,9 +222,6 @@ if(optimize)
list(REMOVE_ITEM cxxflags -O2)
list(APPEND cxxflags -O3)
endif()
if(openmp)
list(APPEND cxxflags -fopenmp)
endif()
# Show flags being used
message(STATUS "Fortran flags: ${f90flags}")
@ -271,26 +229,12 @@ message(STATUS "C flags: ${cflags}")
message(STATUS "C++ flags: ${cxxflags}")
message(STATUS "Linker flags: ${ldflags}")
#===============================================================================
# git SHA1 hash
#===============================================================================
execute_process(COMMAND git rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SHA1_SUCCESS
OUTPUT_VARIABLE GIT_SHA1
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if(GIT_SHA1_SUCCESS EQUAL 0)
add_definitions(-DGIT_SHA1="${GIT_SHA1}")
endif()
#===============================================================================
# pugixml library
#===============================================================================
add_library(pugixml src/pugixml/pugixml_c.cpp src/pugixml/pugixml.cpp)
add_library(pugixml_fortran src/pugixml/pugixml_f.F90)
target_link_libraries(pugixml_fortran pugixml)
add_library(pugixml vendor/pugixml/pugixml.cpp)
target_include_directories(pugixml PUBLIC vendor/pugixml/)
#===============================================================================
# RPATH information
@ -299,7 +243,7 @@ target_link_libraries(pugixml_fortran pugixml)
# This block of code ensures that dynamic libraries can be found via the RPATH
# whether the executable is the original one from the build directory or the
# installed one in CMAKE_INSTALL_PREFIX. Ref:
# https://cmake.org/Wiki/CMake_RPATH_handling#Always_full_RPATH
# https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/RPATH-handling
# use, i.e. don't skip the full RPATH for the build tree
set(CMAKE_SKIP_BUILD_RPATH FALSE)
@ -321,17 +265,20 @@ if("${isSystemDir}" STREQUAL "-1")
endif()
#===============================================================================
# Build faddeeva library
# faddeeva library
#===============================================================================
add_library(faddeeva STATIC src/faddeeva/Faddeeva.c)
add_library(faddeeva STATIC vendor/faddeeva/Faddeeva.c)
target_compile_options(faddeeva PRIVATE ${cflags})
set_target_properties(faddeeva PROPERTIES
C_STANDARD 99
C_STANDARD_REQUIRED ON)
#===============================================================================
# List source files. Define the libopenmc and the OpenMC executable
# libopenmc
#===============================================================================
set(program "openmc")
set(LIBOPENMC_FORTRAN_SRC
add_library(libopenmc SHARED
src/algorithm.F90
src/angle_distribution.F90
src/angleenergy_header.F90
@ -348,7 +295,6 @@ set(LIBOPENMC_FORTRAN_SRC
src/dict_header.F90
src/distribution_multivariate.F90
src/distribution_univariate.F90
src/doppler.F90
src/eigenvalue.F90
src/endf.F90
src/endf_header.F90
@ -367,8 +313,7 @@ set(LIBOPENMC_FORTRAN_SRC
src/mesh_header.F90
src/message_passing.F90
src/mgxs_data.F90
src/mgxs_header.F90
src/multipole.F90
src/mgxs_interface.F90
src/multipole_header.F90
src/nuclide_header.F90
src/output.F90
@ -381,11 +326,11 @@ set(LIBOPENMC_FORTRAN_SRC
src/plot_header.F90
src/product_header.F90
src/progress_header.F90
src/pugixml/pugixml_f.F90
src/random_lcg.F90
src/reaction_header.F90
src/relaxng
src/sab_header.F90
src/scattdata_header.F90
src/secondary_correlated.F90
src/secondary_kalbach.F90
src/secondary_nbody.F90
@ -421,64 +366,93 @@ set(LIBOPENMC_FORTRAN_SRC
src/tallies/tally_filter_distribcell.F90
src/tallies/tally_filter_energy.F90
src/tallies/tally_filter_energyfunc.F90
src/tallies/tally_filter_legendre.F90
src/tallies/tally_filter_material.F90
src/tallies/tally_filter_mesh.F90
src/tallies/tally_filter_meshsurface.F90
src/tallies/tally_filter_mu.F90
src/tallies/tally_filter_polar.F90
src/tallies/tally_filter_sph_harm.F90
src/tallies/tally_filter_sptl_legendre.F90
src/tallies/tally_filter_surface.F90
src/tallies/tally_filter_universe.F90
src/tallies/tally_filter_zernike.F90
src/tallies/tally_header.F90
src/tallies/trigger.F90
src/tallies/trigger_header.F90
)
set(LIBOPENMC_CXX_SRC
src/error.h
src/hdf5_interface.h
src/cell.cpp
src/initialize.cpp
src/finalize.cpp
src/geometry_aux.cpp
src/hdf5_interface.cpp
src/lattice.cpp
src/math_functions.cpp
src/message_passing.cpp
src/mgxs.cpp
src/mgxs_interface.cpp
src/plot.cpp
src/pugixml/pugixml_c.cpp
src/random_lcg.cpp
src/random_lcg.h
src/scattdata.cpp
src/simulation.cpp
src/state_point.cpp
src/string_functions.cpp
src/surface.cpp
src/surface.h
src/xml_interface.h
src/pugixml/pugixml.cpp
src/pugixml/pugixml.hpp)
add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC} ${LIBOPENMC_CXX_SRC})
set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc)
add_executable(${program} src/main.F90)
src/xml_interface.cpp
src/xsdata.cpp)
set_target_properties(libopenmc PROPERTIES
OUTPUT_NAME openmc
PUBLIC_HEADER include/openmc.h
LINKER_LANGUAGE Fortran)
#===============================================================================
# Add compiler/linker flags
#===============================================================================
set_property(TARGET ${program} libopenmc pugixml_fortran
PROPERTY LINKER_LANGUAGE Fortran)
target_include_directories(libopenmc PUBLIC ${HDF5_INCLUDE_DIRS})
# The executable and the faddeeva package use only one language. They can be
# set via target_compile_options which accepts a list.
target_compile_options(${program} PUBLIC ${f90flags})
target_compile_options(faddeeva PRIVATE ${cflags})
target_include_directories(libopenmc
PUBLIC include
PRIVATE ${HDF5_INCLUDE_DIRS})
# The libopenmc library has both F90 and C++ so the compile flags must be set
# file-by-file via set_source_file_properties. The compile flags must first be
# converted from lists to strings.
string(REPLACE ";" " " f90flags "${f90flags}")
string(REPLACE ";" " " cxxflags "${cxxflags}")
set_source_files_properties(${LIBOPENMC_FORTRAN_SRC} PROPERTIES COMPILE_FLAGS
${f90flags})
set_source_files_properties(${LIBOPENMC_CXX_SRC} PROPERTIES COMPILE_FLAGS
${cxxflags})
# differently depending on the language. The $<COMPILE_LANGUAGE> generator
# expression was added in CMake 3.3
target_compile_options(libopenmc PRIVATE
$<$<COMPILE_LANGUAGE:Fortran>:${f90flags}>
$<$<COMPILE_LANGUAGE:CXX>:${cxxflags}>)
# Add HDF5 library directories to link line with -L
foreach(LIBDIR ${HDF5_LIBRARY_DIRS})
list(APPEND ldflags "-L${LIBDIR}")
endforeach()
target_compile_definitions(libopenmc PRIVATE -DMAX_COORD=${maxcoord})
if (UNIX)
# Used in progress_header.F90 for calling check_isatty
target_compile_definitions(libopenmc PRIVATE -DUNIX)
endif()
if (HDF5_IS_PARALLEL)
target_compile_definitions(libopenmc PRIVATE -DPHDF5)
endif()
if (MPI_ENABLED)
target_compile_definitions(libopenmc PUBLIC -DOPENMC_MPI)
if (mpif08)
target_compile_definitions(libopenmc PRIVATE -DOPENMC_MPIF08)
endif()
endif()
# Set git SHA1 hash as a compile definition
execute_process(COMMAND git rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SHA1_SUCCESS
OUTPUT_VARIABLE GIT_SHA1
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
if(GIT_SHA1_SUCCESS EQUAL 0)
target_compile_definitions(libopenmc PRIVATE -DGIT_SHA1="${GIT_SHA1}")
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} pugixml_fortran
target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} pugixml
faddeeva)
target_link_libraries(${program} ${ldflags} libopenmc)
#===============================================================================
# openmc executable
#===============================================================================
add_executable(openmc src/main.cpp)
target_compile_options(openmc PRIVATE ${cxxflags})
target_link_libraries(openmc libopenmc)
#===============================================================================
# Python package
@ -494,9 +468,12 @@ add_custom_command(TARGET libopenmc POST_BUILD
# Install executable, scripts, manpage, license
#===============================================================================
install(TARGETS ${program} libopenmc
install(TARGETS openmc libopenmc
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib)
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
PUBLIC_HEADER DESTINATION include
)
install(DIRECTORY src/relaxng DESTINATION share/openmc)
install(FILES man/man1/openmc.1 DESTINATION share/man/man1)
install(FILES LICENSE DESTINATION "share/doc/${program}" RENAME copyright)
install(FILES LICENSE DESTINATION "share/doc/openmc" RENAME copyright)

View file

@ -46,10 +46,13 @@ Type Definitions
Functions
---------
.. c:function:: void openmc_calculate_volumes()
.. c:function:: int openmc_calculate_volumes()
Run a stochastic volume calculation
:return: Return status (negative if an error occurred)
:rtype: int
.. c:function:: int openmc_cell_get_fill(int32_t index, int* type, int32_t** indices, int32_t* n)
Get the fill for a cell
@ -192,11 +195,14 @@ Functions
:return: Return status (negative if an error occurred)
:rtype: int
.. c:function:: void openmc_finalize()
.. c:function:: int openmc_finalize()
Finalize a simulation
.. c:function:: void openmc_find(double* xyz, int rtype, int32_t* id, int32_t* instance)
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: int openmc_find(double* xyz, int rtype, int32_t* id, int32_t* instance)
Determine the ID of the cell/material containing a given point
@ -207,6 +213,8 @@ Functions
occurs, the ID is -1.
:param int32_t* instance: If a cell is repeated in the geometry, the instance
of the cell that was found and zero otherwise.
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: int openmc_get_cell_index(int32_t id, int32_t* index)
@ -247,11 +255,12 @@ Functions
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: int openmc_get_nuclide_index(char name[], int* index)
.. c:function:: int openmc_get_nuclide_index(const char name[], int* index)
Get the index in the nuclides array for a nuclide with a given name
:param char[] name: Name of the nuclide
:param name: Name of the nuclide
:type name: const char[]
:param int* index: Index in the nuclides array
:return: Return status (negative if an error occurs)
:rtype: int
@ -265,17 +274,24 @@ Functions
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: void openmc_hard_reset()
.. c:function:: int openmc_hard_reset()
Reset tallies, timers, and pseudo-random number generator state
.. c:function:: void openmc_init(const int* intracomm)
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: int openmc_init(int argc, char** argv, const void* intracomm)
Initialize OpenMC
:param int argc: Number of command-line arguments (including command)
:param char** argv: Command-line arguments
:param intracomm: MPI intracommunicator. If MPI is not being used, a null
pointer should be passed.
:type intracomm: const int*
:type intracomm: const void*
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: int openmc_load_nuclide(char name[])
@ -393,26 +409,41 @@ Functions
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: void openmc_plot_geometry()
.. c:function:: int openmc_plot_geometry()
Run plotting mode.
.. c:function:: void openmc_reset()
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: int openmc_reset()
Resets all tally scores
.. c:function:: void openmc_run()
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: int openmc_run()
Run a simulation
.. c:function:: void openmc_simulation_finalize()
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: int openmc_simulation_finalize()
Finalize a simulation.
.. c:function:: void openmc_simulation_init()
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: int openmc_simulation_init()
Initialize a simulation. Must be called after openmc_init().
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: int openmc_source_bank(struct Bank** ptr, int64_t* n)
Return a pointer to the source bank array.
@ -432,13 +463,15 @@ Functions
:return: Return status (negative if an error occurred)
:rtype: int
.. c:function:: void openmc_statepoint_write(const char filename[])
.. c:function:: int openmc_statepoint_write(const char filename[])
Write a statepoint file
:param filename: Name of file to create. If a null pointer is passed, a
filename is assigned automatically.
:type filename: const char[]
:return: Return status (negative if an error occurs)
:rtype: int
.. c:function:: int openmc_tally_get_id(int32_t index, int32_t* id)

View file

@ -21,15 +21,18 @@ on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
from unittest.mock import MagicMock
MOCK_MODULES = ['numpy', 'numpy.polynomial', 'numpy.polynomial.polynomial',
'numpy.ctypeslib', 'scipy', 'scipy.sparse', 'scipy.interpolate',
'scipy.integrate', 'scipy.optimize', 'scipy.special',
'scipy.stats', 'scipy.spatial', 'h5py', 'pandas', 'uncertainties',
'matplotlib', 'matplotlib.pyplot','openmoc',
'openmc.data.reconstruct']
MOCK_MODULES = [
'numpy', 'numpy.polynomial', 'numpy.polynomial.polynomial',
'numpy.ctypeslib', 'scipy', 'scipy.sparse', 'scipy.sparse.linalg',
'scipy.interpolate', 'scipy.integrate', 'scipy.optimize', 'scipy.special',
'scipy.stats', 'scipy.spatial', 'h5py', 'pandas', 'uncertainties',
'matplotlib', 'matplotlib.pyplot', 'openmoc',
'openmc.data.reconstruct'
]
sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES)
import numpy as np
np.ndarray = MagicMock
np.polynomial.Polynomial = MagicMock

View file

@ -219,8 +219,8 @@ Curly braces
For a function definition, the opening and closing braces should each be on
their own lines. This helps distinguish function code from the argument list.
If the entire function fits on one line, then the braces can be on the same
line. e.g.:
If the entire function fits on one or two lines, then the braces can be on the
same line. e.g.:
.. code-block:: C++
@ -238,6 +238,9 @@ line. e.g.:
int return_one() {return 1;}
int return_one()
{return 1;}
For a conditional, the opening brace should be on the same line as the end of
the conditional statement. If there is a following ``else if`` or ``else``
statement, the closing brace should be on the same line as that following

View file

@ -0,0 +1,13 @@
.. _notebook_expansion:
=====================
Functional Expansions
=====================
.. only:: html
.. notebook:: ../../../examples/jupyter/expansion-filters.ipynb
.. only:: latex
IPython notebooks must be viewed in the online HTML documentation.

View file

@ -1,13 +1,12 @@
.. _examples:
=================
Example Notebooks
=================
========
Examples
========
The following series of Jupyter_ Notebooks provide examples for usage of OpenMC
features via the :ref:`pythonapi`.
.. _Jupyter: https://jupyter.org/
The following series of `Jupyter <https://jupyter.org/>`_ Notebooks provide
examples for how to use various features of OpenMC by leveraging the
:ref:`pythonapi`.
-----------
Basic Usage
@ -20,6 +19,7 @@ Basic Usage
post-processing
pandas-dataframes
tally-arithmetic
expansion-filters
search
triso
candu

View file

@ -28,12 +28,6 @@ Windowed Multipole Library Format
":math:`r`" and ":math:`i`" identifiers, similar to how `h5py`_ does it.
- **end_E** (*double*)
Highest energy the windowed multipole part of the library is valid for.
- **energy_points** (*double[]*)
Energy grid for the pointwise library in the reaction group.
- **fissionable** (*int*)
1 if this nuclide has fission data. 0 if it does not.
- **fit_order** (*int*)
The order of the curve fit.
- **formalism** (*int*)
The formalism of the underlying data. Uses the `ENDF-6`_ format
formalism numbers.
@ -51,18 +45,6 @@ Windowed Multipole Library Format
- **l_value** (*int[]*)
The index for a corresponding pole. Equivalent to the :math:`l` quantum
number of the resonance the pole comes from :math:`+1`.
- **length** (*int*)
Total count of poles in `data`.
- **max_w** (*int*)
Maximum number of poles in a window.
- **MT_count** (*int*)
Number of pointwise tables in the library.
- **MT_list** (*int[]*)
A list of available MT identifiers. See `ENDF-6`_ for meaning.
- **n_grid** (*int*)
Total length of the pointwise data.
- **num_l** (*int*)
Number of possible :math:`l` quantum states for this nuclide.
- **pseudo_K0RS** (*double[]*)
:math:`l` dependent value of
@ -90,13 +72,6 @@ Windowed Multipole Library Format
The pole to start from for each window.
- **w_end** (*int[]*)
The pole to end at for each window.
- **windows** (*int*)
Number of windows.
**/nuclide/reactions/MT<i>**
- **MT_sigma** (*double[]*) -- Cross section value for this reaction.
- **Q_value** (*double*) -- Energy released in this reaction, in eV.
- **threshold** (*int*) -- The first non-zero entry in ``MT_sigma``.
.. _h5py: http://docs.h5py.org/en/latest/
.. _ENDF-6: https://www.oecd-nea.org/dbdata/data/manual-endf/endf102.pdf

View file

@ -0,0 +1,102 @@
.. _io_depletion_chain:
============================
Depletion Chain -- chain.xml
============================
A depletion chain file has a ``<depletion_chain>`` root element with one or more
``<nuclide>`` child elements. The decay, reaction, and fission product data for
each nuclide appears as child elements of ``<nuclide>``.
---------------------
``<nuclide>`` Element
---------------------
The ``<nuclide>`` element contains information on the decay modes, reactions,
and fission product yields for a given nuclide in the depletion chain. This
element may have the following attributes:
:name:
Name of the nuclide
:half_life:
Half-life of the nuclide in [s]
:decay_modes:
Number of decay modes present
:decay_energy:
Decay energy released in [eV]
:reactions:
Number of reactions present
For each decay mode, a :ref:`io_chain_decay` appears as a child of
``<nuclide>``. For each reaction present, a :ref:`io_chain_reaction` appears as
a child of ``<nuclide>``. If the nuclide is fissionable, a :ref:`io_chain_nfy`
appears as well.
.. _io_chain_decay:
-------------------
``<decay>`` Element
-------------------
The ``<decay>`` element represents a single decay mode and has the following
attributes:
:type:
The type of the decay, e.g. 'ec/beta+'
:target:
The daughter nuclide produced from the decay
:branching_ratio:
The branching ratio for this decay mode
.. _io_chain_reaction:
----------------------
``<reaction>`` Element
----------------------
The ``<reaction>`` element represents a single transmutation reaction. This
element has the following attributes:
:type:
The type of the reaction, e.g., '(n,gamma)'
:Q:
The Q value of the reaction in [eV]
:target:
The nuclide produced in the reaction (absent if the type is 'fission')
:branching_ratio:
The branching ratio for the reaction
.. _io_chain_nfy:
------------------------------------
``<neutron_fission_yields>`` Element
------------------------------------
The ``<neutron_fission_yields>`` element provides yields of fission products for
fissionable nuclides. It has the follow sub-elements:
:energies:
Energies in [eV] at which yields for products are tabulated
:fission_yields:
Fission product yields for a single energy point. This element itself has a
number of attributes/sub-elements:
:energy:
Energy in [eV] at which yields are tabulated
:products:
Names of fission products
:data:
Independent yields for each fission product

View file

@ -0,0 +1,42 @@
.. _io_depletion_results:
=============================
Depletion Results File Format
=============================
The current version of the depletion results file format is 1.0.
**/**
:Attributes: - **filetype** (*char[]*) -- String indicating the type of file.
- **version** (*int[2]*) -- Major and minor version of the
statepoint file format.
:Datasets: - **eigenvalues** (*double[][]*) -- k-eigenvalues at each
time/stage. This array has shape (number of timesteps, number of
stages).
- **number** (*double[][][][]*) -- Total number of atoms. This array
has shape (number of timesteps, number of stages, number of
materials, number of nuclides).
- **reaction rates** (*double[][][][][]*) -- Reaction rates used to
build depletion matrices. This array has shape (number of
timesteps, number of stages, number of materials, number of
nuclides, number of reactions).
- **time** (*double[][2]*) -- Time in [s] at beginning/end of each
step.
**/materials/<id>/**
:Attributes: - **index** (*int*) -- Index used in results for this material
- **volume** (*double*) -- Volume of this material in [cm^3]
**/nuclides/<name>/**
:Attributes: - **atom number index** (*int*) -- Index in array of total atoms
for this nuclide
- **reaction rate index** (*int*) -- Index in array of reaction
rates for this nuclide
**/reactions/<name>/**
:Attributes: - **index** (*int*) -- Index user in results for this reaction

View file

@ -12,7 +12,7 @@ Input Files
.. toctree::
:numbered:
:maxdepth: 2
:maxdepth: 1
geometry
materials
@ -27,9 +27,10 @@ Data Files
.. toctree::
:numbered:
:maxdepth: 2
:maxdepth: 1
cross_sections
depletion_chain
nuclear_data
mgxs_library
data_wmp
@ -41,11 +42,12 @@ Output Files
.. toctree::
:numbered:
:maxdepth: 2
:maxdepth: 1
statepoint
source
summary
depletion_results
particle_restart
track
voxel

View file

@ -93,29 +93,13 @@ or ``multi-group``.
*Default*: continuous-energy
---------------------
``<entropy>`` Element
---------------------
--------------------------
``<entropy_mesh>`` Element
--------------------------
The ``<entropy>`` element describes a mesh that is used for calculating Shannon
entropy. This mesh should cover all possible fissionable materials in the
problem. It has the following attributes/sub-elements:
:dimension:
The number of mesh cells in the x, y, and z directions, respectively.
*Default*: If this tag is not present, the number of mesh cells is
automatically determined by the code.
:lower_left:
The Cartesian coordinates of the lower-left corner of the mesh.
*Default*: None
:upper_right:
The Cartesian coordinates of the upper-right corner of the mesh.
*Default*: None
The ``<entropy_mesh>`` element indicates the ID of a mesh that is to be used for
calculating Shannon entropy. The mesh should cover all possible fissionable
materials in the problem and is specified using a :ref:`mesh_element`.
-----------------------------------
``<generations_per_batch>`` Element
@ -199,6 +183,36 @@ then, OpenMC will only use up to the :math:`P_1` data.
.. note:: This element is not used in the continuous-energy
:ref:`energy_mode`.
.. _mesh_element:
------------------
``<mesh>`` Element
------------------
The ``<mesh>`` element describes a mesh that is used either for calculating
Shannon entropy, applying the uniform fission site method, or in tallies. For
Shannon entropy meshes, the mesh should cover all possible fissionable materials
in the problem. It has the following attributes/sub-elements:
:id:
A unique integer that is used to identify the mesh.
:dimension:
The number of mesh cells in the x, y, and z directions, respectively.
*Default*: If this tag is not present, the number of mesh cells is
automatically determined by the code.
:lower_left:
The Cartesian coordinates of the lower-left corner of the mesh.
*Default*: None
:upper_right:
The Cartesian coordinates of the upper-right corner of the mesh.
*Default*: None
-----------------------
``<no_reduce>`` Element
-----------------------
@ -765,30 +779,15 @@ has the following attributes/sub-elements:
------------------------
``<uniform_fs>`` Element
``<ufs_mesh>`` Element
------------------------
The ``<uniform_fs>`` element describes a mesh that is used for re-weighting
source sites at every generation based on the uniform fission site methodology
described in Kelly et al., "MC21 Analysis of the Nuclear Energy Agency Monte
Carlo Performance Benchmark Problem," Proceedings of *Physor 2012*, Knoxville,
TN (2012). This mesh should cover all possible fissionable materials in the
problem. It has the following attributes/sub-elements:
:dimension:
The number of mesh cells in the x, y, and z directions, respectively.
*Default*: None
:lower_left:
The Cartesian coordinates of the lower-left corner of the mesh.
*Default*: None
:upper_right:
The Cartesian coordinates of the upper-right corner of the mesh.
*Default*: None
The ``<ufs_mesh>`` element indicates the ID of a mesh that is used for
re-weighting source sites at every generation based on the uniform fission site
methodology described in Kelly et al., "MC21 Analysis of the Nuclear Energy
Agency Monte Carlo Performance Benchmark Problem," Proceedings of *Physor 2012*,
Knoxville, TN (2012). The mesh should cover all possible fissionable materials
in the problem and is specified using a :ref:`mesh_element`.
.. _verbosity:

View file

@ -8,13 +8,13 @@ Normally, source data is stored in a state point file. However, it is possible
to request that the source be written separately, in which case the format used
is that documented here.
**/filetype** (*char[]*)
**/**
String indicating the type of file.
:Attributes: - **filetype** (*char[]*) -- String indicating the type of file.
**/source_bank** (Compound type)
Source bank information for each particle. The compound type has fields
``wgt``, ``xyz``, ``uvw``, ``E``, and ``delayed_group``, which
represent the weight, position, direction, energy, energy group, and
delayed_group of the source particle, respectively.
:Datasets:
- **source_bank** (Compound type) -- Source bank information for each
particle. The compound type has fields ``wgt``, ``xyz``, ``uvw``,
``E``, and ``delayed_group``, which represent the weight, position,
direction, energy, energy group, and delayed_group of the source
particle, respectively.

View file

@ -133,15 +133,8 @@ The current version of the statepoint file format is 17.0.
- **derivative** (*int*) -- ID of the derivative applied to the
tally.
- **n_score_bins** (*int*) -- Number of scoring bins for a single
nuclide. In general, this can be greater than the number of
user-specified scores since each score might have multiple scoring
bins, e.g., scatter-PN.
nuclide.
- **score_bins** (*char[][]*) -- Values of specified scores.
- **n_user_scores** (*int*) -- Number of scores without accounting
for those added by expansions, e.g. scatter-PN.
- **moment_orders** (*char[][]*) -- Tallying moment orders for
Legendre and spherical harmonic tally expansions (e.g., 'P2',
'Y1,2', etc.).
- **results** (*double[][][2]*) -- Accumulated sum and sum-of-squares
for each bin of the i-th tally. The first dimension represents
combinations of filter bins, the second dimensions represents

View file

@ -4,7 +4,7 @@
Summary File Format
===================
The current version of the summary file format is 5.0.
The current version of the summary file format is 6.0.
**/**
@ -104,8 +104,13 @@ The current version of the summary file format is 5.0.
- **atom_density** (*double[]*) -- Total atom density of the material
in atom/b-cm.
- **nuclides** (*char[][]*) -- Array of nuclides present in the
material, e.g., 'U235'.
material, e.g., 'U235'. This data set is only present if nuclides
are used.
- **nuclide_densities** (*double[]*) -- Atom density of each nuclide.
This data set is only present if 'nuclides' data set is present.
- **macroscopics** (*char[][]*) -- Array of macroscopic data sets
present in the material. This dataset is only present if
macroscopic data sets are used in multi-group mode.
- **sab_names** (*char[][]*) -- Names of
S(:math:`\alpha,\beta`) tables assigned to the material.
@ -116,6 +121,13 @@ The current version of the summary file format is 5.0.
:Datasets: - **names** (*char[][]*) -- Names of nuclides.
- **awrs** (*float[]*) -- Atomic weight ratio of each nuclide.
**/macroscopics/**
:Attributes: - **n_macroscopics** (*int*) -- Number of macroscopic data sets
in the problem.
:Datasets: - **names** (*char[][]*) -- Names of the macroscopic data sets.
**/tallies/tally <uid>/**
:Datasets: - **name** (*char[]*) -- Name of the tally.

View file

@ -57,6 +57,11 @@ Benchmarking
Coupling and Multi-physics
--------------------------
- Jun Chen, Liangzhi Cao, Chuanqi Zhao, and Zhouyu Liu, "`Development of
Subchannel Code SUBSC for high-fidelity multi-physics coupling application
<https://doi.org/10.1016/j.egypro.2017.08.121>`_", Energy Procedia, **127**,
264-274 (2017).
- Tianliang Hu, Liangzhu Cao, Hongchun Wu, Xianan Du, and Mingtao He, "`Coupled
neutrons and thermal-hydraulics simulation of molten salt reactors based on
OpenMC/TANSY <https://doi.org/10.1016/j.anucene.2017.05.002>`_,"
@ -98,6 +103,11 @@ Coupling and Multi-physics
Geometry and Visualization
--------------------------
- Jin-Yang Li, Long Gu, Hu-Shan Xu, Nadezha Korepanova, Rui Yu, Yan-Lei Zhu, and
Chang-Ping Qin, "`CAD modeling study on FLUKA and OpenMC for accelerator
driven system simulation <https://doi.org/10.1016/j.anucene.2017.12.050>`_",
*Ann. Nucl. Energy*, **114**, 329-341 (2018).
- Logan Abel, William Boyd, Benoit Forget, and Kord Smith, "Interactive
Visualization of Multi-Group Cross Sections on High-Fidelity Spatial Meshes,"
*Trans. Am. Nucl. Soc.*, **114**, 391-394 (2016).
@ -114,6 +124,11 @@ Geometry and Visualization
Miscellaneous
-------------
- Bruno Merk, Dzianis Litskevich, R. Gregg, and A. R. Mount, "`Demand driven
salt clean-up in a molten salt fast reactor -- Defining a priority list
<https://doi.org/10.1371/journal.pone.0192020>`_", *PLOS One*, **13**,
e0192020 (2018).
- Adam G. Nelson, Samuel Shaner, William Boyd, and Paul K. Romano,
"Incorporation of a Multigroup Transport Capability in the OpenMC Monte Carlo
Particle Transport Code," *Trans. Am. Nucl. Soc.*, **117**, 679-681 (2017).

View file

@ -109,6 +109,7 @@ Constructing Tallies
openmc.CellbornFilter
openmc.SurfaceFilter
openmc.MeshFilter
openmc.MeshSurfaceFilter
openmc.EnergyFilter
openmc.EnergyoutFilter
openmc.MuFilter
@ -117,6 +118,10 @@ Constructing Tallies
openmc.DistribcellFilter
openmc.DelayedGroupFilter
openmc.EnergyFunctionFilter
openmc.LegendreFilter
openmc.SpatialLegendreFilter
openmc.SphericalHarmonicsFilter
openmc.ZernikeFilter
openmc.Mesh
openmc.Trigger
openmc.TallyDerivative

View file

@ -44,5 +44,8 @@ Classes
EnergyFilter
MaterialFilter
Material
Mesh
MeshFilter
MeshSurfaceFilter
Nuclide
Tally

View file

@ -32,10 +32,12 @@ Core Functions
:template: myfunction.rst
openmc.data.atomic_mass
openmc.data.gnd_name
openmc.data.linearize
openmc.data.thin
openmc.data.water_density
openmc.data.write_compact_458_library
openmc.data.zam
Angle-Energy Distributions
--------------------------

View file

@ -0,0 +1,85 @@
.. _pythonapi_deplete:
----------------------------------
:mod:`openmc.deplete` -- Depletion
----------------------------------
.. module:: openmc.deplete
Two functions are provided that implement different time-integration algorithms
for depletion calculations.
.. autosummary::
:toctree: generated
:nosignatures:
:template: myfunction.rst
integrator.predictor
integrator.cecm
Each of these functions expects a "transport operator" to be passed. An operator
specific to OpenMC is available using the following class:
.. autosummary::
:toctree: generated
:nosignatures:
:template: myclass.rst
Operator
When running in parallel using `mpi4py <http://mpi4py.scipy.org>`_, the MPI
intercommunicator used can be changed by modifying the following module
variable. If it is not explicitly modified, it defaults to
``mpi4py.MPI.COMM_WORLD``.
.. data:: comm
MPI intercommunicator used to call OpenMC library
:type: mpi4py.MPI.Comm
Internal Classes and Functions
------------------------------
During a depletion calculation, the depletion chain, reaction rates, and number
densities are managed through a series of internal classes that are not normally
visible to a user. However, should you find yourself wondering about these
classes (e.g., if you want to know what decay modes or reactions are present in
a depletion chain), they are documented here. The following classes store data
for a depletion chain:
.. autosummary::
:toctree: generated
:nosignatures:
:template: myclass.rst
Chain
DecayTuple
Nuclide
ReactionTuple
The following classes are used during a depletion simulation and store auxiliary
data, such as number densities and reaction rates for each material.
.. autosummary::
:toctree: generated
:nosignatures:
:template: myclass.rst
AtomNumber
OperatorResult
ReactionRates
Results
ResultsList
TransportOperator
Each of the integrator functions also relies on a number of "helper" functions
as follows:
.. autosummary::
:toctree: generated
:nosignatures:
:template: myfunction.rst
integrator.CRAM16
integrator.CRAM48

View file

@ -15,14 +15,15 @@ there are many substantial benefits to using the Python API, including:
- The ability to define dimensions using variables.
- Availability of standard-library modules for working with files.
- An entire ecosystem of third-party packages for scientific computing.
- Ability to create materials based on natural elements or uranium enrichment
- Automated multi-group cross section generation (:mod:`openmc.mgxs`)
- A fully-featured nuclear data interface (:mod:`openmc.data`)
- Depletion capability (:mod:`openmc.deplete`)
- Convenience functions (e.g., a function returning a hexagonal region)
- Ability to plot individual universes as geometry is being created
- A :math:`k_\text{eff}` search function (:func:`openmc.search_for_keff`)
- Random sphere packing for generating TRISO particle locations
(:func:`openmc.model.pack_trisos`)
- A fully-featured nuclear data interface (:mod:`openmc.data`)
- Ability to create materials based on natural elements or uranium enrichment
For those new to Python, there are many good tutorials available online. We
recommend going through the modules from `Codecademy
@ -45,6 +46,7 @@ Modules
base
model
examples
deplete
mgxs
stats
data

View file

@ -141,16 +141,10 @@ Prerequisites
recommend that your HDF5 installation be built with parallel I/O
features. An example of configuring HDF5_ is listed below::
FC=mpifort ./configure --enable-fortran --enable-parallel
FC=mpifort ./configure --enable-parallel
You may omit ``--enable-parallel`` if you want to compile HDF5_ in serial.
.. important::
If you are building HDF5 version 1.8.x or earlier, you must include
``--enable-fortran2003`` when configuring HDF5 or else OpenMC will not
be able to compile.
.. admonition:: Optional
:class: note
@ -416,7 +410,9 @@ Prerequisites
The Python API works with Python 3.4+. 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.
distributions. To run simulations in parallel using MPI, it is recommended to
build mpi4py, HDF5, h5py from source, in that order, using the same compilers
as for OpenMC.
.. admonition:: Required
:class: error
@ -452,6 +448,11 @@ distributions.
.. admonition:: Optional
:class: note
`mpi4py <http://mpi4py.scipy.org/>`_
mpi4py provides Python bindings to MPI for running distributed-memory
parallel runs. This package is needed if you plan on running depletion
simulations in parallel using MPI.
`Cython <http://cython.org/>`_
Cython is used for resonance reconstruction for ENDF data converted to
:class:`openmc.data.IncidentNeutron`.

View file

@ -43,14 +43,22 @@ of an element, you specify the element itself. For example,
Internally, OpenMC stores data on the atomic masses and natural abundances of
all known isotopes and then uses this data to determine what isotopes should be
added to the material. When the material is later exported to XML for use by the
:ref:`scripts_openmc` executable, you'll see that any natural elements are
:ref:`scripts_openmc` executable, you'll see that any natural elements were
expanded to the naturally-occurring isotopes.
The :meth:`Material.add_element` method can also be used to add uranium at a
specified enrichment through the `enrichment` argument. For example, the
following would add 3.2% enriched uranium to a material::
mat.add_element('U', 1.0, enrichment=3.2)
In addition to U235 and U238, concentrations of U234 and U236 will be present
and are determined through a correlation based on measured data.
Often, cross section libraries don't actually have all naturally-occurring
isotopes for a given element. For example, in ENDF/B-VII.1, cross section
evaluations are given for O16 and O17 but not for O18. If OpenMC is aware of
what cross sections you will be using (either through the
:attr:`Materials.cross_sections` attribute or the
what cross sections you will be using (through the
:envvar:`OPENMC_CROSS_SECTIONS` environment variable), it will attempt to only
put isotopes in your model for which you have cross section data. In the case of
oxygen in ENDF/B-VII.1, the abundance of O18 would end up being lumped with O16.

View file

@ -21,7 +21,12 @@ region of phase space, as in:
Thus, to specify a tally, we need to specify what regions of phase space should
be included when deciding whether to score an event as well as what the scoring
function (:math:`f` in the above equation) should be used. The regions of phase
space are called *filters* and the scoring functions are simply called *scores*.
space are generally called *filters* and the scoring functions are simply
called *scores*.
The only cases when filters do not correspond directly with the regions of
phase space are when expansion functions are applied in the integrand, such as
for Legendre expansions of the scattering kernel.
-------
Filters
@ -69,10 +74,9 @@ Scores
------
To specify the scoring functions, a list of strings needs to be given to the
:attr:`Tally.scores` attribute. You can score the flux ('flux'), a reaction rate
('total', 'fission', etc.), or even scattering moments (e.g., 'scatter-P3'). For
example, to tally the elastic scattering rate and the fission neutron
production, you'd assign::
:attr:`Tally.scores` attribute. You can score the flux ('flux'), or a reaction
rate ('total', 'fission', etc.). For example, to tally the elastic scattering
rate and the fission neutron production, you'd assign::
tally.scores = ['elastic', 'nu-fission']
@ -98,12 +102,6 @@ The following tables show all valid scores:
+======================+===================================================+
|flux |Total flux. |
+----------------------+---------------------------------------------------+
|flux-YN |Spherical harmonic expansion of the direction of |
| |motion :math:`\left(\Omega\right)` of the total |
| |flux. This score will tally all of the harmonic |
| |moments of order 0 to N. N must be between 0 and |
| |10. |
+----------------------+---------------------------------------------------+
.. table:: **Reaction scores: units are reactions per source particle.**
@ -118,43 +116,10 @@ The following tables show all valid scores:
+----------------------+---------------------------------------------------+
|fission |Total fission reaction rate. |
+----------------------+---------------------------------------------------+
|scatter |Total scattering rate. Can also be identified with |
| |the "scatter-0" response type. |
+----------------------+---------------------------------------------------+
|scatter-N |Tally the N\ :sup:`th` \ scattering moment, where N|
| |is the Legendre expansion order of the change in |
| |particle angle :math:`\left(\mu\right)`. N must be |
| |between 0 and 10. As an example, tallying the 2\ |
| |:sup:`nd` \ scattering moment would be specified as|
| |``<scores>scatter-2</scores>``. |
+----------------------+---------------------------------------------------+
|scatter-PN |Tally all of the scattering moments from order 0 to|
| |N, where N is the Legendre expansion order of the |
| |change in particle angle |
| |:math:`\left(\mu\right)`. That is, "scatter-P1" is |
| |equivalent to requesting tallies of "scatter-0" and|
| |"scatter-1". Like for "scatter-N", N must be |
| |between 0 and 10. As an example, tallying up to the|
| |2\ :sup:`nd` \ scattering moment would be specified|
| |as ``<scores> scatter-P2 </scores>``. |
+----------------------+---------------------------------------------------+
|scatter-YN |"scatter-YN" is similar to "scatter-PN" except an |
| |additional expansion is performed for the incoming |
| |particle direction :math:`\left(\Omega\right)` |
| |using the real spherical harmonics. This is useful|
| |for performing angular flux moment weighting of the|
| |scattering moments. Like "scatter-PN", "scatter-YN"|
| |will tally all of the moments from order 0 to N; N |
| |again must be between 0 and 10. |
|scatter |Total scattering rate. |
+----------------------+---------------------------------------------------+
|total |Total reaction rate. |
+----------------------+---------------------------------------------------+
|total-YN |The total reaction rate expanded via spherical |
| |harmonics about the direction of motion of the |
| |neutron, :math:`\Omega`. This score will tally all |
| |of the harmonic moments of order 0 to N. N must be|
| |between 0 and 10. |
+----------------------+---------------------------------------------------+
|(n,2nd) |(n,2nd) reaction rate. |
+----------------------+---------------------------------------------------+
|(n,2n) |(n,2n) reaction rate. |
@ -248,10 +213,10 @@ The following tables show all valid scores:
+----------------------+---------------------------------------------------+
|nu-fission |Total production of neutrons due to fission. |
+----------------------+---------------------------------------------------+
|nu-scatter, |These scores are similar in functionality to their |
|nu-scatter-N, |``scatter*`` equivalents except the total |
|nu-scatter-PN, |production of neutrons due to scattering is scored |
|nu-scatter-YN |vice simply the scattering rate. This accounts for |
|nu-scatter |This score is similar in functionality to the |
| |``scatter`` score except the total production of |
| |neutrons due to scattering is scored vice simply |
| |the scattering rate. This accounts for |
| |multiplicity from (n,2n), (n,3n), and (n,4n) |
| |reactions. |
+----------------------+---------------------------------------------------+
@ -261,7 +226,7 @@ The following tables show all valid scores:
+----------------------+---------------------------------------------------+
|Score | Description |
+======================+===================================================+
|current |Used in combination with a mesh filter: |
|current |Used in combination with a meshsurface filter: |
| |Partial currents on the boundaries of each cell in |
| |a mesh. It may not be used in conjunction with any |
| |other score. Only energy and mesh filters may be |
@ -269,7 +234,7 @@ The following tables show all valid scores:
| |Used in combination with a surface filter: |
| |Net currents on any surface previously defined in |
| |the geometry. It may be used along with any other |
| |filter, except mesh filters. |
| |filter, except meshsurface filters. |
| |Surfaces can alternatively be defined with cell |
| |from and cell filters thereby resulting in tallying|
| |partial currents. |

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -287,18 +287,7 @@
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"# Run openmc in plotting mode\n",
"openmc.plot_geometry(output=False)"
@ -313,7 +302,7 @@
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///9yEhLpgJFNv8Tq\nQYT7AAAAAWJLR0QAiAUdSAAAAAd0SU1FB+EMBQIrDwapSyIAAALKSURBVGje7dpLcqQwDAbgHHE2\nYeEj+D4cwQucBUfo+3CEXoSp8OhuhF70T4qpKXmdr21LogK2Pj7A8QmNP+HDhw8fPnz48Kf6VH9G\n+66vy+je8k19jnf8C5dXIPv86ms56lPdjvaYbyodx3ze+XLE76cXFiD4zPji99z0/AJ4n1lfvJ6f\nnl0A6x+578efMSg1wPr172/jPO5yFXM+Ef78gdblM+WPHyguP//t1/g6pA0wfln+ho/fwgYYn19C\n/xwDvwHGc9OvC+hs37DTrwuwfWanXxdQTC9Mvyygs3wjTL8uwPJpn/tNDbSGz7T0SBEWw4vLXzbQ\n6b6RoveIoO6TvPxlA63qs7z8ZQPF9F+SH22vbX8OQKf5Rtv+EgDNJ3X58wZaxWd1+fMGiuFvir8b\nvjp8J/tGy/6jAmRvhW8fwL3vVT+o3grfPoB7r/IpALI3tz8FoJN84/NV873hB8UnM3xzANtf8nb4\ndwmg3grfFEDJO8JPE0i9Ff4pAYL3pI8mkHor/HMCeO9JH00g9SafEsh7T/ppARBvp48UwJnelT5S\nACd7O31TAlnvKx9SQCd7B58KgPO+8iMFuPWe9E8F8BveWX7bAjzX9y4//Jve+fhsH6Ctv7n8PTzj\nvY/v9gEOHz58+PBX+6v/f/wPvnd54f3j6venE/yl769Xv7+j3x/o98/V32/o9+fl389Xnx+g5x/o\n+Qt6/oOeP6HnX+j5G3z+h54/ouefV5/foufP6Pk3ev4On/+j9w/o/Qd6/4Le/6D3T/D9V67Y/ZsV\nQBq+s+8f0ftP+P41axXguP9NWgDuu/Cdfv+N3r/D9/9TAID+A7T/Ae2/gPs/0P4TtP8F7r9J3AIO\n9P+g/Udw/9Oygbf7r9D+L7j/DO1/Q/vv4P4/tP8Q7n9E+y/h/k+0/xTuf4X7b+H+X7T/+BPuf3aM\n8OHDhw8fPnz4w/4vzcvgeY10sY0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMTItMDRUMjA6NDM6\nMTQtMDY6MDCrFYTfAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTEyLTA0VDIwOjQzOjE0LTA2OjAw\n2kg8YwAAAABJRU5ErkJggg==\n",
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdC\nAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAxQTFRF\n////chIS6YCRTb/E6kGE+wAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAALKSURB\nVGje7dpLcqQwDAbgHHE2YeEj+D4cwQucBUfo+3CEXoSp8OhuhF70T4qpKXmdr21LogK2Pj7A8QmN\nP+HDhw8fPnz48Kf6VH9G+66vy+je8k19jnf8C5dXIPv86ms56lPdjvaYbyodx3ze+XLE76cXFiD4\nzPji99z0/AJ4n1lfvJ6fnl0A6x+578efMSg1wPr172/jPO5yFXM+Ef78gdblM+WPHyguP//t1/g6\npA0wfln+ho/fwgYYn19C/xwDvwHGc9OvC+hs37DTrwuwfWanXxdQTC9Mvyygs3wjTL8uwPJpn/tN\nDbSGz7T0SBEWw4vLXzbQ6b6RoveIoO6TvPxlA63qs7z8ZQPF9F+SH22vbX8OQKf5Rtv+EgDNJ3X5\n8wZaxWd1+fMGiuFvir8bvjp8J/tGy/6jAmRvhW8fwL3vVT+o3grfPoB7r/IpALI3tz8FoJN84/NV\n873hB8UnM3xzANtf8nb4dwmg3grfFEDJO8JPE0i9Ff4pAYL3pI8mkHor/HMCeO9JH00g9SafEsh7\nT/ppARBvp48UwJnelT5SACd7O31TAlnvKx9SQCd7B58KgPO+8iMFuPWe9E8F8BveWX7bAjzX9y4/\n/Jve+fhsH6Ctv7n8PTzjvY/v9gEOHz58+PBX+6v/f/wPvnd54f3j6venE/yl769Xv7+j3x/o98/V\n32/o9+fl389Xnx+g5x/o+Qt6/oOeP6HnX+j5G3z+h54/ouefV5/foufP6Pk3ev4On/+j9w/o/Qd6\n/4Le/6D3T/D9V67Y/ZsVQBq+s+8f0ftP+P41axXguP9NWgDuu/Cdfv+N3r/D9/9TAID+A7T/Ae2/\ngPs/0P4TtP8F7r9J3AIO9P+g/Udw/9Oygbf7r9D+L7j/DO1/Q/vv4P4/tP8Q7n9E+y/h/k+0/xTu\nf4X7b+H+X7T/+BPuf3aM8OHDhw8fPnz4w/4vzcvgeY10sY0AAAAldEVYdGRhdGU6Y3JlYXRlADIw\nMTgtMDQtMDNUMjE6MTE6MzgtMDQ6MDD1dVTHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE4LTA0LTAz\nVDIxOjExOjM4LTA0OjAwhCjsewAAAABJRU5ErkJggg==\n",
"text/plain": [
"<IPython.core.display.Image object>"
]
@ -392,21 +381,21 @@
"mesh.dimension = [1, 1, 1]\n",
"mesh.lower_left = [-0.63, -0.63, -100.]\n",
"mesh.width = [1.26, 1.26, 200.]\n",
"mesh_filter = openmc.MeshFilter(mesh)\n",
"meshsurface_filter = openmc.MeshSurfaceFilter(mesh)\n",
"\n",
"# Instantiate thermal, fast, and total leakage tallies\n",
"leak = openmc.Tally(name='leakage')\n",
"leak.filters = [mesh_filter]\n",
"leak.filters = [meshsurface_filter]\n",
"leak.scores = ['current']\n",
"tallies_file.append(leak)\n",
"\n",
"thermal_leak = openmc.Tally(name='thermal leakage')\n",
"thermal_leak.filters = [mesh_filter, openmc.EnergyFilter([0., 0.625])]\n",
"thermal_leak.filters = [meshsurface_filter, openmc.EnergyFilter([0., 0.625])]\n",
"thermal_leak.scores = ['current']\n",
"tallies_file.append(thermal_leak)\n",
"\n",
"fast_leak = openmc.Tally(name='fast leakage')\n",
"fast_leak.filters = [mesh_filter, openmc.EnergyFilter([0.625, 20.0e6])]\n",
"fast_leak.filters = [meshsurface_filter, openmc.EnergyFilter([0.625, 20.0e6])]\n",
"fast_leak.scores = ['current']\n",
"tallies_file.append(fast_leak)"
]
@ -504,11 +493,11 @@
"name": "stderr",
"output_type": "stream",
"text": [
"/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=6.\n",
"/home/liangjg/.local/lib/python3.5/site-packages/openmc-0.10.0-py3.5.egg/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=6.\n",
" warn(msg, IDWarning)\n",
"/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=3.\n",
"/home/liangjg/.local/lib/python3.5/site-packages/openmc-0.10.0-py3.5.egg/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=3.\n",
" warn(msg, IDWarning)\n",
"/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=2.\n",
"/home/liangjg/.local/lib/python3.5/site-packages/openmc-0.10.0-py3.5.egg/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.\n",
" warn(msg, IDWarning)\n"
]
}
@ -563,24 +552,25 @@
" %%%%%%%%%%%\n",
"\n",
" | The OpenMC Monte Carlo Code\n",
" Copyright | 2011-2017 Massachusetts Institute of Technology\n",
" Copyright | 2011-2018 Massachusetts Institute of Technology\n",
" License | http://openmc.readthedocs.io/en/latest/license.html\n",
" Version | 0.9.0\n",
" Git SHA1 | 9b7cebf7bc34d60e0f1750c3d6cb103df11e8dc4\n",
" Date/Time | 2017-12-04 20:43:15\n",
" OpenMP Threads | 4\n",
" Version | 0.10.0\n",
" Git SHA1 | 47fbf8282ea94c138f75219bd10fdb31501d3fb7\n",
" Date/Time | 2018-04-03 21:12:27\n",
" MPI Processes | 1\n",
" OpenMP Threads | 20\n",
"\n",
" Reading settings XML file...\n",
" Reading cross sections XML file...\n",
" Reading materials XML file...\n",
" Reading geometry XML file...\n",
" Building neighboring cells lists for each surface...\n",
" Reading U235 from /home/romano/openmc/scripts/nndc_hdf5/U235.h5\n",
" Reading U238 from /home/romano/openmc/scripts/nndc_hdf5/U238.h5\n",
" Reading O16 from /home/romano/openmc/scripts/nndc_hdf5/O16.h5\n",
" Reading H1 from /home/romano/openmc/scripts/nndc_hdf5/H1.h5\n",
" Reading B10 from /home/romano/openmc/scripts/nndc_hdf5/B10.h5\n",
" Reading Zr90 from /home/romano/openmc/scripts/nndc_hdf5/Zr90.h5\n",
" Reading U235 from /home/liangjg/nucdata/nndc_hdf5/U235.h5\n",
" Reading U238 from /home/liangjg/nucdata/nndc_hdf5/U238.h5\n",
" Reading O16 from /home/liangjg/nucdata/nndc_hdf5/O16.h5\n",
" Reading H1 from /home/liangjg/nucdata/nndc_hdf5/H1.h5\n",
" Reading B10 from /home/liangjg/nucdata/nndc_hdf5/B10.h5\n",
" Reading Zr90 from /home/liangjg/nucdata/nndc_hdf5/Zr90.h5\n",
" Maximum neutron transport energy: 2.00000E+07 eV for U235\n",
" Reading tallies XML file...\n",
" Writing summary.h5 file...\n",
@ -614,20 +604,20 @@
"\n",
" =======================> TIMING STATISTICS <=======================\n",
"\n",
" Total time for initialization = 5.6782E-01 seconds\n",
" Reading cross sections = 5.3276E-01 seconds\n",
" Total time in simulation = 6.4149E+00 seconds\n",
" Time in transport only = 6.2767E+00 seconds\n",
" Time in inactive batches = 6.8747E-01 seconds\n",
" Time in active batches = 5.7274E+00 seconds\n",
" Time synchronizing fission bank = 2.7492E-03 seconds\n",
" Sampling source sites = 1.9584E-03 seconds\n",
" SEND/RECV source sites = 7.4113E-04 seconds\n",
" Time accumulating tallies = 1.0576E-04 seconds\n",
" Total time for finalization = 2.2075E-03 seconds\n",
" Total time elapsed = 7.0056E+00 seconds\n",
" Calculation Rate (inactive) = 18182.5 neutrons/second\n",
" Calculation Rate (active) = 6547.45 neutrons/second\n",
" Total time for initialization = 4.9090E-01 seconds\n",
" Reading cross sections = 4.2387E-01 seconds\n",
" Total time in simulation = 1.4928E+00 seconds\n",
" Time in transport only = 1.3545E+00 seconds\n",
" Time in inactive batches = 1.3625E-01 seconds\n",
" Time in active batches = 1.3565E+00 seconds\n",
" Time synchronizing fission bank = 2.4053E-03 seconds\n",
" Sampling source sites = 1.6466E-03 seconds\n",
" SEND/RECV source sites = 5.6159E-04 seconds\n",
" Time accumulating tallies = 3.3647E-04 seconds\n",
" Total time for finalization = 1.6066E-02 seconds\n",
" Total time elapsed = 2.0336E+00 seconds\n",
" Calculation Rate (inactive) = 91743.2 neutrons/second\n",
" Calculation Rate (active) = 27644.5 neutrons/second\n",
"\n",
" ============================> RESULTS <============================\n",
"\n",
@ -638,16 +628,6 @@
" Leakage Fraction = 0.01717 +/- 0.00107\n",
"\n"
]
},
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
@ -741,8 +721,7 @@
"\n",
"# Get the leakage tally\n",
"leak = sp.get_tally(name='leakage')\n",
"leak = leak.summation(filter_type=openmc.SurfaceFilter, remove_filter=True)\n",
"leak = leak.summation(filter_type=openmc.MeshFilter, remove_filter=True)\n",
"leak = leak.summation(filter_type=openmc.MeshSurfaceFilter, remove_filter=True)\n",
"\n",
"# Compute k-infinity using tally arithmetic\n",
"keff = fiss_rate / (abs_rate + leak)\n",
@ -812,8 +791,7 @@
"# Compute resonance escape probability using tally arithmetic\n",
"therm_abs_rate = sp.get_tally(name='therm. abs. rate')\n",
"thermal_leak = sp.get_tally(name='thermal leakage')\n",
"thermal_leak = thermal_leak.summation(filter_type=openmc.SurfaceFilter, remove_filter=True)\n",
"thermal_leak = thermal_leak.summation(filter_type=openmc.MeshFilter, remove_filter=True)\n",
"thermal_leak = thermal_leak.summation(filter_type=openmc.MeshSurfaceFilter, remove_filter=True)\n",
"res_esc = (therm_abs_rate + thermal_leak) / (abs_rate + thermal_leak)\n",
"res_esc.get_pandas_dataframe()"
]

View file

@ -0,0 +1,49 @@
<?xml version="1.0"?>
<depletion_chain>
<nuclide name="I135" decay_modes="1" reactions="1" half_life="2.36520E+04">
<decay type="beta" target="Xe135" branching_ratio="1.0" />
<reaction type="(n,gamma)" Q="0.0" target="Xe136" /> <!-- Not precisely true, but whatever -->
</nuclide>
<nuclide name="Xe135" decay_modes="1" reactions="1" half_life="3.29040E+04">
<decay type=" beta" target="Cs135" branching_ratio="1.0" />
<reaction type="(n,gamma)" Q="0.0" target="Xe136" />
</nuclide>
<nuclide name="Xe136" decay_modes="0" reactions="0" />
<nuclide name="Cs135" decay_modes="0" reactions="0" />
<nuclide name="Gd157" decay_modes="0" reactions="1" >
<reaction type="(n,gamma)" Q="0.0" target="Nothing" />
</nuclide>
<nuclide name="Gd156" decay_modes="0" reactions="1">
<reaction type="(n,gamma)" Q="0.0" target="Gd157" />
</nuclide>
<nuclide name="U234" decay_modes="0" reactions="1">
<reaction type="fission" Q="191840000."/>
<neutron_fission_yields>
<energies>2.53000e-02</energies>
<fission_yields energy="2.53000e-02">
<products>Gd157 Gd156 I135 Xe135 Xe136 Cs135</products>
<data>1.093250e-04 2.087260e-04 2.780820e-02 6.759540e-03 2.392300e-02 4.356330e-05</data>
</fission_yields>
</neutron_fission_yields>
</nuclide>
<nuclide name="U235" decay_modes="0" reactions="1">
<reaction type="fission" Q="193410000."/>
<neutron_fission_yields>
<energies>2.53000e-02</energies>
<fission_yields energy="2.53000e-02">
<products>Gd157 Gd156 I135 Xe135 Xe136 Cs135</products>
<data>6.142710e-5 1.483250e-04 0.0292737 0.002566345 0.0219242 4.9097e-6</data>
</fission_yields>
</neutron_fission_yields>
</nuclide>
<nuclide name="U238" decay_modes="0" reactions="1">
<reaction type="fission" Q="197790000."/>
<neutron_fission_yields>
<energies>2.53000e-02</energies>
<fission_yields energy="2.53000e-02">
<products>Gd157 Gd156 I135 Xe135 Xe136 Cs135</products>
<data>4.141120e-04 7.605360e-04 0.0135457 0.00026864 0.0024432 3.7100E-07</data>
</fission_yields>
</neutron_fission_yields>
</nuclide>
</depletion_chain>

View file

@ -0,0 +1,103 @@
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
###############################################################################
# Load geometry from statepoint
statepoint = 'statepoint.100.h5'
with openmc.StatePoint(statepoint) as sp:
geometry = sp.summary.geometry
# Load previous depletion results
previous_results = openmc.deplete.ResultsList("depletion_results.h5")
###############################################################################
# Transport calculation settings
###############################################################################
# Instantiate a Settings object, set all runtime parameters
settings_file = openmc.Settings()
settings_file.batches = batches
settings_file.inactive = inactive
settings_file.particles = particles
# 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)
entropy_mesh = openmc.Mesh()
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
###############################################################################
# Initialize and run depletion calculation
###############################################################################
op = openmc.deplete.Operator(geometry, settings_file, chain_file,
previous_results)
# Perform simulation using the predictor algorithm
openmc.deplete.integrator.predictor(op, time_steps, power)
###############################################################################
# Read depletion calculation results
###############################################################################
# Open results file
results = openmc.deplete.ResultsList("depletion_results.h5")
# Obtain K_eff as a function of time
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)')
###############################################################################
# Generate plots
###############################################################################
plt.figure()
plt.plot(time/(24*60*60), 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.xlabel("Time (days)")
plt.ylabel("n U5 (-)")
plt.show()
plt.figure()
plt.plot(time/(24*60*60), Xe_gam, label="Xe135 absorption")
plt.xlabel("Time (days)")
plt.ylabel("RR (-)")
plt.show()
plt.close('all')

View file

@ -0,0 +1,175 @@
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.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.set_density('g/cm3', 0.001598)
helium.add_element('He', 2.4044e-4)
zircaloy = openmc.Material(material_id=3, name='Zircaloy 4')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_element('Sn', 0.014 , 'wo')
zircaloy.add_element('Fe', 0.00165, '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.set_density('g/cm3', 0.740582)
borated_water.add_element('B', 4.0e-5)
borated_water.add_element('H', 5.0e-2)
borated_water.add_element('O', 2.4e-2)
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')
left.boundary_type = 'reflective'
right.boundary_type = 'reflective'
top.boundary_type = 'reflective'
bottom.boundary_type = 'reflective'
# 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)
###############################################################################
# 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]
###############################################################################
# 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
# 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)
entropy_mesh = openmc.Mesh()
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
###############################################################################
# Initialize and run depletion calculation
###############################################################################
op = openmc.deplete.Operator(geometry, settings_file, chain_file)
# Perform simulation using the predictor algorithm
openmc.deplete.integrator.predictor(op, time_steps, power)
###############################################################################
# Read depletion calculation results
###############################################################################
# Open results file
results = openmc.deplete.ResultsList("depletion_results.h5")
# Obtain K_eff as a function of time
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)')
###############################################################################
# Generate plots
###############################################################################
plt.figure()
plt.plot(time/(24*60*60), 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.xlabel("Time (days)")
plt.ylabel("n U5 (-)")
plt.show()
plt.figure()
plt.plot(time/(24*60*60), Xe_gam, label="Xe135 absorption")
plt.xlabel("Time (days)")
plt.ylabel("RR (-)")
plt.show()
plt.close('all')

View file

@ -8,7 +8,7 @@
</mesh>
<filter id="1" type="mesh">
<bins>1</bins>
<bins>2</bins>
</filter>
<filter id="2" type="energy">

View file

@ -16,7 +16,8 @@ extern "C" {
int delayed_group;
};
void openmc_calculate_voumes();
int openmc_calculate_volumes();
int openmc_cell_filter_get_bins(int32_t index, int32_t** cells, int32_t* n);
int openmc_cell_get_fill(int32_t index, int* type, int32_t** indices, int32_t* n);
int openmc_cell_get_id(int32_t index, int32_t* id);
int openmc_cell_set_fill(int32_t index, int type, int32_t n, const int32_t* indices);
@ -27,21 +28,29 @@ extern "C" {
int openmc_extend_cells(int32_t n, int32_t* index_start, int32_t* index_end);
int openmc_extend_filters(int32_t n, int32_t* index_start, int32_t* index_end);
int openmc_extend_materials(int32_t n, int32_t* index_start, int32_t* index_end);
int openmc_extend_meshes(int32_t n, int32_t* index_start, int32_t* index_end);
int openmc_extend_sources(int32_t n, int32_t* index_start, int32_t* index_end);
int openmc_extend_tallies(int32_t n, int32_t* index_start, int32_t* index_end);
int openmc_filter_get_id(int32_t index, int32_t* id);
int openmc_filter_get_type(int32_t index, char* type);
int openmc_filter_set_id(int32_t index, int32_t id);
void openmc_finalize();
int openmc_filter_set_type(int32_t index, const char* type);
int openmc_finalize();
int openmc_find(double* xyz, int rtype, int32_t* id, int32_t* instance);
int openmc_get_cell_index(int32_t id, int32_t* index);
int openmc_get_filter_index(int32_t id, int32_t* index);
void openmc_get_filter_next_id(int32_t* id);
int openmc_get_keff(double k_combined[]);
int openmc_get_material_index(int32_t id, int32_t* index);
int openmc_get_nuclide_index(char name[], int* index);
int openmc_get_mesh_index(int32_t id, int32_t* index);
int openmc_get_nuclide_index(const char name[], int* index);
int64_t openmc_get_seed();
int openmc_get_tally_index(int32_t id, int32_t* index);
void openmc_hard_reset();
void openmc_init(const int* intracomm);
int openmc_hard_reset();
int openmc_init(int argc, char* argv[], const void* intracomm);
int openmc_init_f(const int* intracomm);
int openmc_legendre_filter_get_order(int32_t index, int* order);
int openmc_legendre_filter_set_order(int32_t index, int order);
int openmc_load_nuclide(char name[]);
int openmc_material_add_nuclide(int32_t index, const char name[], double density);
int openmc_material_get_densities(int32_t index, int** nuclides, double** densities, int* n);
@ -51,45 +60,73 @@ extern "C" {
int openmc_material_set_id(int32_t index, int32_t id);
int openmc_material_filter_get_bins(int32_t index, int32_t** bins, int32_t* n);
int openmc_material_filter_set_bins(int32_t index, int32_t n, const int32_t* bins);
int openmc_mesh_filter_get_mesh(int32_t index, int32_t* index_mesh);
int openmc_mesh_filter_set_mesh(int32_t index, int32_t index_mesh);
int openmc_next_batch();
int openmc_mesh_get_id(int32_t index, int32_t* id);
int openmc_mesh_get_dimension(int32_t index, int** id, int* n);
int openmc_mesh_get_params(int32_t index, double** ll, double** ur, double** width, int* n);
int openmc_mesh_set_id(int32_t index, int32_t id);
int openmc_mesh_set_dimension(int32_t index, int n, const int* dims);
int openmc_mesh_set_params(int32_t index, const double* ll, const double* ur, const double* width, int n);
int openmc_meshsurface_filter_get_mesh(int32_t index, int32_t* index_mesh);
int openmc_meshsurface_filter_set_mesh(int32_t index, int32_t index_mesh);
int openmc_next_batch(int* status);
int openmc_nuclide_name(int index, char** name);
void openmc_plot_geometry();
void openmc_reset();
void openmc_run();
void openmc_simulation_finalize();
void openmc_simulation_init();
int openmc_particle_restart();
int openmc_plot_geometry();
int openmc_reset();
int openmc_run();
void openmc_set_seed(int64_t new_seed);
int openmc_simulation_finalize();
int openmc_simulation_init();
int openmc_source_bank(struct Bank** ptr, int64_t* n);
int openmc_source_set_strength(int32_t index, double strength);
void openmc_statepoint_write(const char filename[]);
int openmc_spatial_legendre_filter_get_order(int32_t index, int* order);
int openmc_spatial_legendre_filter_get_params(int32_t index, int* axis, double* min, double* max);
int openmc_spatial_legendre_filter_set_order(int32_t index, int order);
int openmc_spatial_legendre_filter_set_params(int32_t index, const int* axis,
const double* min, const double* max);
int openmc_sphharm_filter_get_order(int32_t index, int* order);
int openmc_sphharm_filter_get_cosine(int32_t index, char cosine[]);
int openmc_sphharm_filter_set_order(int32_t index, int order);
int openmc_sphharm_filter_set_cosine(int32_t index, const char cosine[]);
int openmc_statepoint_write(const char filename[]);
int openmc_tally_get_active(int32_t index, bool* active);
int openmc_tally_get_id(int32_t index, int32_t* id);
int openmc_tally_get_filters(int32_t index, int32_t** indices, int* n);
int openmc_tally_get_n_realizations(int32_t index, int32_t* n);
int openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n);
int openmc_tally_get_scores(int32_t index, int** scores, int* n);
int openmc_tally_results(int32_t index, double** ptr, int shape_[3]);
int openmc_tally_set_active(int32_t index, bool active);
int openmc_tally_set_filters(int32_t index, int n, const int32_t* indices);
int openmc_tally_set_id(int32_t index, int32_t id);
int openmc_tally_set_nuclides(int32_t index, int n, const char** nuclides);
int openmc_tally_set_scores(int32_t index, int n, const int* scores);
int openmc_tally_set_scores(int32_t index, int n, const char** scores);
int openmc_tally_set_type(int32_t index, const char* type);
int openmc_zernike_filter_get_order(int32_t index, int* order);
int openmc_zernike_filter_get_params(int32_t index, double* x, double* y, double* r);
int openmc_zernike_filter_set_order(int32_t index, int order);
int openmc_zernike_filter_set_params(int32_t index, const double* x,
const double* y, const double* r);
// Error codes
extern int E_UNASSIGNED;
extern int E_ALLOCATE;
extern int E_OUT_OF_BOUNDS;
extern int E_INVALID_SIZE;
extern int E_INVALID_ARGUMENT;
extern int E_INVALID_TYPE;
extern int E_INVALID_ID;
extern int E_GEOMETRY;
extern int E_DATA;
extern int E_PHYSICS;
extern int E_WARNING;
extern int OPENMC_E_UNASSIGNED;
extern int OPENMC_E_ALLOCATE;
extern int OPENMC_E_OUT_OF_BOUNDS;
extern int OPENMC_E_INVALID_SIZE;
extern int OPENMC_E_INVALID_ARGUMENT;
extern int OPENMC_E_INVALID_TYPE;
extern int OPENMC_E_INVALID_ID;
extern int OPENMC_E_GEOMETRY;
extern int OPENMC_E_DATA;
extern int OPENMC_E_PHYSICS;
extern int OPENMC_E_WARNING;
// Global variables
extern char openmc_err_msg[256];
extern double keff;
extern double keff_std;
extern double openmc_keff;
extern double openmc_keff_std;
extern int32_t n_batches;
extern int32_t n_cells;
extern int32_t n_filters;
@ -97,6 +134,7 @@ extern "C" {
extern int32_t n_lattices;
extern int32_t n_materials;
extern int32_t n_meshes;
extern int n_nuclides;
extern int64_t n_particles;
extern int32_t n_plots;
extern int32_t n_realizations;
@ -105,9 +143,24 @@ extern "C" {
extern int32_t n_surfaces;
extern int32_t n_tallies;
extern int32_t n_universes;
extern int run_mode;
extern bool simulation_initialized;
extern int verbosity;
extern int openmc_run_mode;
extern bool openmc_simulation_initialized;
extern int openmc_verbosity;
// Variables that are shared by necessity (can be removed from public header
// later)
extern bool openmc_master;
extern int openmc_n_procs;
extern int openmc_n_threads;
extern int openmc_rank;
extern int64_t openmc_work;
// Run modes
constexpr int RUN_MODE_FIXEDSOURCE {1};
constexpr int RUN_MODE_EIGENVALUE {2};
constexpr int RUN_MODE_PLOTTING {3};
constexpr int RUN_MODE_PARTICLE {4};
constexpr int RUN_MODE_VOLUME {5};
#ifdef __cplusplus
}

View file

@ -15,6 +15,7 @@ from openmc.surface import *
from openmc.universe import *
from openmc.lattice import *
from openmc.filter import *
from openmc.filter_expansion import *
from openmc.trigger import *
from openmc.tally_derivative import *
from openmc.tallies import *

49
openmc/_utils.py Normal file
View file

@ -0,0 +1,49 @@
import os.path
from pathlib import Path
from urllib.parse import urlparse
from urllib.request import urlopen
_BLOCK_SIZE = 16384
def download(url):
"""Download file from a URL
Parameters
----------
url : str
URL from which to download
Returns
-------
basename : str
Name of file written locally
"""
req = urlopen(url)
# Get file size from header
file_size = req.length
# Check if file already downloaded
basename = Path(urlparse(url).path).name
if os.path.exists(basename):
if os.path.getsize(basename) == file_size:
print('Skipping {}, already downloaded'.format(basename))
return basename
# Copy file to disk in chunks
print('Downloading {}... '.format(basename), end='')
downloaded = 0
with open(basename, 'wb') as fh:
while True:
chunk = req.read(_BLOCK_SIZE)
if not chunk:
break
fh.write(chunk)
downloaded += len(chunk)
status = '{:10} [{:3.2f}%]'.format(
downloaded, downloaded * 100. / file_size)
print(status + '\b'*len(status), end='')
print('')
return basename

View file

@ -15,7 +15,6 @@ objects in the :mod:`openmc.capi` subpackage, for example:
from ctypes import CDLL
import os
import sys
from warnings import warn
import pkg_resources
@ -36,10 +35,7 @@ else:
# available. Instead, we create a mock object so that when the modules
# within the openmc.capi package try to configure arguments and return
# values for symbols, no errors occur
try:
from unittest.mock import Mock
except ImportError:
from mock import Mock
from unittest.mock import Mock
_dll = Mock()
from .error import *
@ -47,9 +43,8 @@ from .core import *
from .nuclide import *
from .material import *
from .cell import *
from .mesh import *
from .filter import *
from .tally import *
from .settings import settings
warn("The Python bindings to OpenMC's C API are still unstable "
"and may change substantially in future releases.", FutureWarning)
from .math import *

View file

@ -5,9 +5,10 @@ from weakref import WeakValueDictionary
import numpy as np
from numpy.ctypeslib import as_array
from openmc.exceptions import AllocationError, InvalidIDError
from . import _dll
from .core import _FortranObjectWithID
from .error import _error_handler, AllocationError, InvalidIDError
from .error import _error_handler
from .material import Material
__all__ = ['Cell', 'cells']
@ -44,7 +45,7 @@ class Cell(_FortranObjectWithID):
This class exposes a cell that is stored internally in the OpenMC
library. To obtain a view of a cell with a given ID, use the
:data:`openmc.capi.nuclides` mapping.
:data:`openmc.capi.cells` mapping.
Parameters
----------
@ -115,14 +116,15 @@ class Cell(_FortranObjectWithID):
def fill(self, fill):
if isinstance(fill, Iterable):
n = len(fill)
indices = (c_int*n)(*(m._index for m in fill))
_dll.openmc_cell_set_fill(self._index, 1, 1, indices)
indices = (c_int32*n)(*(m._index if m is not None else -1
for m in fill))
_dll.openmc_cell_set_fill(self._index, 1, n, indices)
elif isinstance(fill, Material):
materials = [fill]
indices = (c_int*1)(fill._index)
indices = (c_int32*1)(fill._index)
_dll.openmc_cell_set_fill(self._index, 1, 1, indices)
elif fill is None:
indices = (c_int32*1)(-1)
_dll.openmc_cell_set_fill(self._index, 1, 1, indices)
else:
raise NotImplementedError
def set_temperature(self, T, instance=None):
"""Set the temperature of a cell

View file

@ -1,13 +1,14 @@
from contextlib import contextmanager
from ctypes import (CDLL, c_int, c_int32, c_int64, c_double, c_char_p,
POINTER, Structure)
from ctypes import (CDLL, c_int, c_int32, c_int64, c_double, c_char_p, c_char,
POINTER, Structure, c_void_p, create_string_buffer)
from warnings import warn
import numpy as np
from numpy.ctypeslib import as_array
from openmc.exceptions import AllocationError
from . import _dll
from .error import _error_handler, AllocationError
from .error import _error_handler
import openmc.capi
@ -19,29 +20,41 @@ class _Bank(Structure):
('delayed_group', c_int)]
_dll.openmc_calculate_volumes.restype = None
_dll.openmc_finalize.restype = None
_dll.openmc_calculate_volumes.restype = c_int
_dll.openmc_calculate_volumes.errcheck = _error_handler
_dll.openmc_finalize.restype = c_int
_dll.openmc_finalize.errcheck = _error_handler
_dll.openmc_find.argtypes = [POINTER(c_double*3), c_int, POINTER(c_int32),
POINTER(c_int32)]
_dll.openmc_find.restype = c_int
_dll.openmc_find.errcheck = _error_handler
_dll.openmc_hard_reset.restype = None
_dll.openmc_init.argtypes = [POINTER(c_int)]
_dll.openmc_init.restype = None
_dll.openmc_hard_reset.restype = c_int
_dll.openmc_hard_reset.errcheck = _error_handler
_dll.openmc_init.argtypes = [c_int, POINTER(POINTER(c_char)), c_void_p]
_dll.openmc_init.restype = c_int
_dll.openmc_init.errcheck = _error_handler
_dll.openmc_get_keff.argtypes = [POINTER(c_double*2)]
_dll.openmc_get_keff.restype = c_int
_dll.openmc_get_keff.errcheck = _error_handler
_dll.openmc_next_batch.argtypes = [POINTER(c_int)]
_dll.openmc_next_batch.restype = c_int
_dll.openmc_plot_geometry.restype = None
_dll.openmc_run.restype = None
_dll.openmc_reset.restype = None
_dll.openmc_next_batch.errcheck = _error_handler
_dll.openmc_plot_geometry.restype = c_int
_dll.openmc_plot_geometry.restype = _error_handler
_dll.openmc_run.restype = c_int
_dll.openmc_run.errcheck = _error_handler
_dll.openmc_reset.restype = c_int
_dll.openmc_reset.errcheck = _error_handler
_dll.openmc_source_bank.argtypes = [POINTER(POINTER(_Bank)), POINTER(c_int64)]
_dll.openmc_source_bank.restype = c_int
_dll.openmc_source_bank.errcheck = _error_handler
_dll.openmc_simulation_init.restype = None
_dll.openmc_simulation_finalize.restype = None
_dll.openmc_simulation_init.restype = c_int
_dll.openmc_simulation_init.errcheck = _error_handler
_dll.openmc_simulation_finalize.restype = c_int
_dll.openmc_simulation_finalize.errcheck = _error_handler
_dll.openmc_statepoint_write.argtypes = [POINTER(c_char_p)]
_dll.openmc_statepoint_write.restype = None
_dll.openmc_statepoint_write.restype = c_int
_dll.openmc_statepoint_write.errcheck = _error_handler
def calculate_volumes():
@ -102,25 +115,42 @@ def hard_reset():
_dll.openmc_hard_reset()
def init(intracomm=None):
def init(args=None, intracomm=None):
"""Initialize OpenMC
Parameters
----------
args : list of str
Command-line arguments
intracomm : mpi4py.MPI.Intracomm or None
MPI intracommunicator
"""
if intracomm is not None:
# If an mpi4py communicator was passed, convert it to an integer to
# be passed to openmc_init
try:
intracomm = intracomm.py2f()
except AttributeError:
pass
_dll.openmc_init(c_int(intracomm))
if args is not None:
args = ['openmc'] + list(args)
argc = len(args)
# Create the argv array. Note that it is actually expected to be of
# length argc + 1 with the final item being a null pointer.
argv = (POINTER(c_char) * (argc + 1))()
for i, arg in enumerate(args):
argv[i] = create_string_buffer(arg.encode())
else:
_dll.openmc_init(None)
argc = 0
argv = None
if intracomm is not None:
# If an mpi4py communicator was passed, convert it to void* to be passed
# to openmc_init
try:
from mpi4py import MPI
except ImportError:
intracomm = None
else:
address = MPI._addressof(intracomm)
intracomm = c_void_p(address)
_dll.openmc_init(argc, argv, intracomm)
def iter_batches():
@ -147,13 +177,13 @@ def iter_batches():
"""
while True:
# Run next batch
retval = next_batch()
status = next_batch()
# Provide opportunity for user to perform action between batches
yield
# End the iteration
if retval < 0:
if status != 0:
break
@ -174,18 +204,25 @@ def keff():
return tuple(k)
else:
# Otherwise, return the tracklength estimator
mean = c_double.in_dll(_dll, 'keff').value
std_dev = c_double.in_dll(_dll, 'keff_std').value if n > 1 else np.inf
mean = c_double.in_dll(_dll, 'openmc_keff').value
std_dev = c_double.in_dll(_dll, 'openmc_keff_std').value \
if n > 1 else np.inf
return (mean, std_dev)
def next_batch():
"""Run next batch."""
retval = _dll.openmc_next_batch()
if retval == -3:
raise AllocationError('Simulation has not been initialized. You must call '
'openmc.capi.simulation_init() first.')
return retval
"""Run next batch.
Returns
-------
int
Status after running a batch (0=normal, 1=reached maximum number of
batches, 2=tally triggers reached)
"""
status = c_int()
_dll.openmc_next_batch(status)
return status.value
def plot_geometry():

View file

@ -1,45 +1,10 @@
from ctypes import c_int, c_char
from warnings import warn
import openmc.exceptions as exc
from . import _dll
class OpenMCError(Exception):
"""Root exception class for OpenMC."""
class GeometryError(OpenMCError):
"""Geometry-related error"""
class InvalidIDError(OpenMCError):
"""Use of an ID that is invalid."""
class AllocationError(OpenMCError):
"""Error related to memory allocation."""
class OutOfBoundsError(OpenMCError):
"""Index in array out of bounds."""
class DataError(OpenMCError):
"""Error relating to nuclear data."""
class PhysicsError(OpenMCError):
"""Error relating to performing physics."""
class InvalidArgumentError(OpenMCError):
"""Argument passed was invalid."""
class InvalidTypeError(OpenMCError):
"""Tried to perform an operation on the wrong type."""
def _error_handler(err, func, args):
"""Raise exception according to error code."""
@ -52,23 +17,23 @@ def _error_handler(err, func, args):
msg = errmsg.value.decode()
# Raise exception type corresponding to error code
if err == errcode('e_allocate'):
raise AllocationError(msg)
elif err == errcode('e_out_of_bounds'):
raise OutOfBoundsError(msg)
elif err == errcode('e_invalid_argument'):
raise InvalidArgumentError(msg)
elif err == errcode('e_invalid_type'):
raise InvalidTypeError(msg)
if err == errcode('e_invalid_id'):
raise InvalidIDError(msg)
elif err == errcode('e_geometry'):
raise GeometryError(msg)
elif err == errcode('e_data'):
raise DataError(msg)
elif err == errcode('e_physics'):
raise PhysicsError(msg)
elif err == errcode('e_warning'):
if err == errcode('OPENMC_E_ALLOCATE'):
raise exc.AllocationError(msg)
elif err == errcode('OPENMC_E_OUT_OF_BOUNDS'):
raise exc.OutOfBoundsError(msg)
elif err == errcode('OPENMC_E_INVALID_ARGUMENT'):
raise exc.InvalidArgumentError(msg)
elif err == errcode('OPENMC_E_INVALID_TYPE'):
raise exc.InvalidTypeError(msg)
if err == errcode('OPENMC_E_INVALID_ID'):
raise exc.InvalidIDError(msg)
elif err == errcode('OPENMC_E_GEOMETRY'):
raise exc.GeometryError(msg)
elif err == errcode('OPENMC_E_DATA'):
raise exc.DataError(msg)
elif err == errcode('OPENMC_E_PHYSICS'):
raise exc.PhysicsError(msg)
elif err == errcode('OPENMC_E_WARNING'):
warn(msg)
elif err < 0:
raise OpenMCError("Unknown error encountered (code {}).".format(err))
raise exc.OpenMCError("Unknown error encountered (code {}).".format(err))

View file

@ -6,20 +6,27 @@ from weakref import WeakValueDictionary
import numpy as np
from numpy.ctypeslib import as_array
from openmc.exceptions import AllocationError, InvalidIDError
from . import _dll
from .core import _FortranObjectWithID
from .error import _error_handler, AllocationError, InvalidIDError
from .error import _error_handler
from .material import Material
from .mesh import Mesh
__all__ = ['Filter', 'AzimuthalFilter', 'CellFilter',
'CellbornFilter', 'CellfromFilter', 'DistribcellFilter',
'DelayedGroupFilter', 'EnergyFilter', 'EnergyoutFilter',
'EnergyFunctionFilter', 'MaterialFilter', 'MeshFilter',
'MuFilter', 'PolarFilter', 'SurfaceFilter',
'UniverseFilter', 'filters']
'EnergyFunctionFilter', 'LegendreFilter', 'MaterialFilter', 'MeshFilter',
'MeshSurfaceFilter', 'MuFilter', 'PolarFilter', 'SphericalHarmonicsFilter',
'SpatialLegendreFilter', 'SurfaceFilter',
'UniverseFilter', 'ZernikeFilter', 'filters']
# Tally functions
_dll.openmc_cell_filter_get_bins.argtypes = [
c_int32, POINTER(POINTER(c_int32)), POINTER(c_int32)]
_dll.openmc_cell_filter_get_bins.restype = c_int
_dll.openmc_cell_filter_get_bins.errcheck = _error_handler
_dll.openmc_energy_filter_get_bins.argtypes = [
c_int32, POINTER(POINTER(c_double)), POINTER(c_int32)]
_dll.openmc_energy_filter_get_bins.restype = c_int
@ -45,6 +52,12 @@ _dll.openmc_filter_set_type.errcheck = _error_handler
_dll.openmc_get_filter_index.argtypes = [c_int32, POINTER(c_int32)]
_dll.openmc_get_filter_index.restype = c_int
_dll.openmc_get_filter_index.errcheck = _error_handler
_dll.openmc_legendre_filter_get_order.argtypes = [c_int32, POINTER(c_int)]
_dll.openmc_legendre_filter_get_order.restype = c_int
_dll.openmc_legendre_filter_get_order.errcheck = _error_handler
_dll.openmc_legendre_filter_set_order.argtypes = [c_int32, c_int]
_dll.openmc_legendre_filter_set_order.restype = c_int
_dll.openmc_legendre_filter_set_order.errcheck = _error_handler
_dll.openmc_material_filter_get_bins.argtypes = [
c_int32, POINTER(POINTER(c_int32)), POINTER(c_int32)]
_dll.openmc_material_filter_get_bins.restype = c_int
@ -52,10 +65,36 @@ _dll.openmc_material_filter_get_bins.errcheck = _error_handler
_dll.openmc_material_filter_set_bins.argtypes = [c_int32, c_int32, POINTER(c_int32)]
_dll.openmc_material_filter_set_bins.restype = c_int
_dll.openmc_material_filter_set_bins.errcheck = _error_handler
_dll.openmc_mesh_filter_get_mesh.argtypes = [c_int32, POINTER(c_int32)]
_dll.openmc_mesh_filter_get_mesh.restype = c_int
_dll.openmc_mesh_filter_get_mesh.errcheck = _error_handler
_dll.openmc_mesh_filter_set_mesh.argtypes = [c_int32, c_int32]
_dll.openmc_mesh_filter_set_mesh.restype = c_int
_dll.openmc_mesh_filter_set_mesh.errcheck = _error_handler
_dll.openmc_meshsurface_filter_get_mesh.argtypes = [c_int32, POINTER(c_int32)]
_dll.openmc_meshsurface_filter_get_mesh.restype = c_int
_dll.openmc_meshsurface_filter_get_mesh.errcheck = _error_handler
_dll.openmc_meshsurface_filter_set_mesh.argtypes = [c_int32, c_int32]
_dll.openmc_meshsurface_filter_set_mesh.restype = c_int
_dll.openmc_meshsurface_filter_set_mesh.errcheck = _error_handler
_dll.openmc_spatial_legendre_filter_get_order.argtypes = [c_int32, POINTER(c_int)]
_dll.openmc_spatial_legendre_filter_get_order.restype = c_int
_dll.openmc_spatial_legendre_filter_get_order.errcheck = _error_handler
_dll.openmc_spatial_legendre_filter_set_order.argtypes = [c_int32, c_int]
_dll.openmc_spatial_legendre_filter_set_order.restype = c_int
_dll.openmc_spatial_legendre_filter_set_order.errcheck = _error_handler
_dll.openmc_sphharm_filter_get_order.argtypes = [c_int32, POINTER(c_int)]
_dll.openmc_sphharm_filter_get_order.restype = c_int
_dll.openmc_sphharm_filter_get_order.errcheck = _error_handler
_dll.openmc_sphharm_filter_set_order.argtypes = [c_int32, c_int]
_dll.openmc_sphharm_filter_set_order.restype = c_int
_dll.openmc_sphharm_filter_set_order.errcheck = _error_handler
_dll.openmc_zernike_filter_get_order.argtypes = [c_int32, POINTER(c_int)]
_dll.openmc_zernike_filter_get_order.restype = c_int
_dll.openmc_zernike_filter_get_order.errcheck = _error_handler
_dll.openmc_zernike_filter_set_order.argtypes = [c_int32, c_int]
_dll.openmc_zernike_filter_set_order.restype = c_int
_dll.openmc_zernike_filter_set_order.errcheck = _error_handler
class Filter(_FortranObjectWithID):
__instances = WeakValueDictionary()
@ -128,7 +167,7 @@ class EnergyFilter(Filter):
self._index, len(energies), energies_p)
class EnergyoutFilter(Filter):
class EnergyoutFilter(EnergyFilter):
filter_type = 'energyout'
@ -139,6 +178,13 @@ class AzimuthalFilter(Filter):
class CellFilter(Filter):
filter_type = 'cell'
@property
def bins(self):
cells = POINTER(c_int32)()
n = c_int32()
_dll.openmc_cell_filter_get_bins(self._index, cells, n)
return as_array(cells, (n.value,))
class CellbornFilter(Filter):
filter_type = 'cellborn'
@ -160,6 +206,25 @@ class EnergyFunctionFilter(Filter):
filter_type = 'energyfunction'
class LegendreFilter(Filter):
filter_type = 'legendre'
def __init__(self, order=None, uid=None, new=True, index=None):
super().__init__(uid, new, index)
if order is not None:
self.order = order
@property
def order(self):
temp_order = c_int()
_dll.openmc_legendre_filter_get_order(self._index, temp_order)
return temp_order.value
@order.setter
def order(self, order):
_dll.openmc_legendre_filter_set_order(self._index, order)
class MaterialFilter(Filter):
filter_type = 'material'
@ -187,6 +252,40 @@ class MaterialFilter(Filter):
class MeshFilter(Filter):
filter_type = 'mesh'
def __init__(self, mesh=None, uid=None, new=True, index=None):
super().__init__(uid, new, index)
if mesh is not None:
self.mesh = mesh
@property
def mesh(self):
index_mesh = c_int32()
_dll.openmc_mesh_filter_get_mesh(self._index, index_mesh)
return Mesh(index=index_mesh.value)
@mesh.setter
def mesh(self, mesh):
_dll.openmc_mesh_filter_set_mesh(self._index, mesh._index)
class MeshSurfaceFilter(Filter):
filter_type = 'meshsurface'
def __init__(self, mesh=None, uid=None, new=True, index=None):
super().__init__(uid, new, index)
if mesh is not None:
self.mesh = mesh
@property
def mesh(self):
index_mesh = c_int32()
_dll.openmc_meshsurface_filter_get_mesh(self._index, index_mesh)
return Mesh(index=index_mesh.value)
@mesh.setter
def mesh(self, mesh):
_dll.openmc_meshsurface_filter_set_mesh(self._index, mesh._index)
class MuFilter(Filter):
filter_type = 'mu'
@ -196,6 +295,44 @@ class PolarFilter(Filter):
filter_type = 'polar'
class SphericalHarmonicsFilter(Filter):
filter_type = 'sphericalharmonics'
def __init__(self, order=None, uid=None, new=True, index=None):
super().__init__(uid, new, index)
if order is not None:
self.order = order
@property
def order(self):
temp_order = c_int()
_dll.openmc_sphharm_filter_get_order(self._index, temp_order)
return temp_order.value
@order.setter
def order(self, order):
_dll.openmc_sphharm_filter_set_order(self._index, order)
class SpatialLegendreFilter(Filter):
filter_type = 'spatiallegendre'
def __init__(self, order=None, uid=None, new=True, index=None):
super().__init__(uid, new, index)
if order is not None:
self.order = order
@property
def order(self):
temp_order = c_int()
_dll.openmc_spatial_legendre_filter_get_order(self._index, temp_order)
return temp_order.value
@order.setter
def order(self, order):
_dll.openmc_spatial_legendre_filter_set_order(self._index, order)
class SurfaceFilter(Filter):
filter_type = 'surface'
@ -204,6 +341,25 @@ class UniverseFilter(Filter):
filter_type = 'universe'
class ZernikeFilter(Filter):
filter_type = 'zernike'
def __init__(self, order=None, uid=None, new=True, index=None):
super().__init__(uid, new, index)
if order is not None:
self.order = order
@property
def order(self):
temp_order = c_int()
_dll.openmc_zernike_filter_get_order(self._index, temp_order)
return temp_order.value
@order.setter
def order(self, order):
_dll.openmc_zernike_filter_set_order(self._index, order)
_FILTER_TYPE_MAP = {
'azimuthal': AzimuthalFilter,
'cell': CellFilter,
@ -214,12 +370,17 @@ _FILTER_TYPE_MAP = {
'energy': EnergyFilter,
'energyout': EnergyoutFilter,
'energyfunction': EnergyFunctionFilter,
'legendre': LegendreFilter,
'material': MaterialFilter,
'mesh': MeshFilter,
'meshsurface': MeshSurfaceFilter,
'mu': MuFilter,
'polar': PolarFilter,
'sphericalharmonics': SphericalHarmonicsFilter,
'spatiallegendre': SpatialLegendreFilter,
'surface': SurfaceFilter,
'universe': UniverseFilter,
'zernike': ZernikeFilter
}

View file

@ -5,9 +5,10 @@ from weakref import WeakValueDictionary
import numpy as np
from numpy.ctypeslib import as_array
from openmc.exceptions import AllocationError, InvalidIDError
from . import _dll, Nuclide
from .core import _FortranObjectWithID
from .error import _error_handler, AllocationError, InvalidIDError
from .error import _error_handler
__all__ = ['Material', 'materials']
@ -89,6 +90,9 @@ class Material(_FortranObjectWithID):
index = index.value
else:
index = mapping[uid]._index
elif index == -1:
# Special value indicates void material
return None
if index not in cls.__instances:
instance = super(Material, cls).__new__(cls)

250
openmc/capi/math.py Normal file
View file

@ -0,0 +1,250 @@
from ctypes import (c_int, c_double, POINTER)
import numpy as np
from numpy.ctypeslib import ndpointer
from . import _dll
_dll.t_percentile_c.restype = c_double
_dll.t_percentile_c.argtypes = [c_double, c_int]
_dll.calc_pn_c.restype = None
_dll.calc_pn_c.argtypes = [c_int, c_double, ndpointer(c_double)]
_dll.evaluate_legendre_c.restype = c_double
_dll.evaluate_legendre_c.argtypes = [c_int, POINTER(c_double), c_double]
_dll.calc_rn_c.restype = None
_dll.calc_rn_c.argtypes = [c_int, ndpointer(c_double), ndpointer(c_double)]
_dll.calc_zn_c.restype = None
_dll.calc_zn_c.argtypes = [c_int, c_double, c_double, ndpointer(c_double)]
_dll.rotate_angle_c.restype = None
_dll.rotate_angle_c.argtypes = [ndpointer(c_double), c_double,
POINTER(c_double)]
_dll.maxwell_spectrum_c.restype = c_double
_dll.maxwell_spectrum_c.argtypes = [c_double]
_dll.watt_spectrum_c.restype = c_double
_dll.watt_spectrum_c.argtypes = [c_double, c_double]
_dll.broaden_wmp_polynomials_c.restype = None
_dll.broaden_wmp_polynomials_c.argtypes = [c_double, c_double, c_int,
ndpointer(c_double)]
def t_percentile(p, df):
""" Calculate the percentile of the Student's t distribution with a
specified probability level and number of degrees of freedom
Parameters
----------
p : float
Probability level
df : int
Degrees of freedom
Returns
-------
float
Corresponding t-value
"""
return _dll.t_percentile_c(p, df)
def calc_pn(n, x):
""" Calculate the n-th order Legendre polynomial at the value of x.
Parameters
----------
n : int
Legendre order
x : float
Independent variable to evaluate the Legendre at
Returns
-------
float
Corresponding Legendre polynomial result
"""
pnx = np.empty(n + 1, dtype=np.float64)
_dll.calc_pn_c(n, x, pnx)
return pnx
def evaluate_legendre(data, x):
""" Finds the value of f(x) given a set of Legendre coefficients
and the value of x.
Parameters
----------
data : iterable of float
Legendre coefficients
x : float
Independent variable to evaluate the Legendre at
Returns
-------
float
Corresponding Legendre expansion result
"""
data_arr = np.array(data, dtype=np.float64)
return _dll.evaluate_legendre_c(len(data),
data_arr.ctypes.data_as(POINTER(c_double)),
x)
def calc_rn(n, uvw):
""" Calculate the n-th order real Spherical Harmonics for a given angle;
all Rn,m values are provided for all n (where -n <= m <= n).
Parameters
----------
n : int
Harmonics order
uvw : iterable of float
Independent variable to evaluate the Legendre at
Returns
-------
numpy.ndarray
Corresponding real harmonics value
"""
num_nm = (n + 1) * (n + 1)
rn = np.empty(num_nm, dtype=np.float64)
uvw_arr = np.array(uvw, dtype=np.float64)
_dll.calc_rn_c(n, uvw_arr, rn)
return rn
def calc_zn(n, rho, phi):
""" Calculate the n-th order modified Zernike polynomial moment for a
given angle (rho, theta) location in the unit disk. The normalization of
the polynomials is such that the integral of Z_pq*Z_pq over the unit disk
is exactly pi
Parameters
----------
n : int
Maximum order
rho : float
Radial location in the unit disk
phi : float
Theta (radians) location in the unit disk
Returns
-------
numpy.ndarray
Corresponding resulting list of coefficients
"""
num_bins = ((n + 1) * (n + 2)) // 2
zn = np.zeros(num_bins, dtype=np.float64)
_dll.calc_zn_c(n, rho, phi, zn)
return zn
def rotate_angle(uvw0, mu, phi=None):
""" Rotates direction cosines through a polar angle whose cosine is
mu and through an azimuthal angle sampled uniformly.
Parameters
----------
uvw0 : iterable of float
Original direction cosine
mu : float
Polar angle cosine to rotate
phi : float, optional
Azimuthal angle; if None, one will be sampled uniformly
Returns
-------
numpy.ndarray
Rotated direction cosine
"""
uvw0_arr = np.array(uvw0, dtype=np.float64)
if phi is None:
_dll.rotate_angle_c(uvw0_arr, mu, None)
else:
_dll.rotate_angle_c(uvw0_arr, mu, c_double(phi))
uvw = uvw0_arr
return uvw
def maxwell_spectrum(T):
""" Samples an energy from the Maxwell fission distribution based
on a direct sampling scheme.
Parameters
----------
T : float
Spectrum parameter
Returns
-------
float
Sampled outgoing energy
"""
return _dll.maxwell_spectrum_c(T)
def watt_spectrum(a, b):
""" Samples an energy from the Watt energy-dependent fission spectrum.
Parameters
----------
a : float
Spectrum parameter a
b : float
Spectrum parameter b
Returns
-------
float
Sampled outgoing energy
"""
return _dll.watt_spectrum_c(a, b)
def broaden_wmp_polynomials(E, dopp, n):
""" Doppler broadens the windowed multipole curvefit. The curvefit is a
polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ...
Parameters
----------
E : float
Energy to evaluate at
dopp : float
sqrt(atomic weight ratio / kT), with kT given in eV
n : int
Number of components to the polynomial
Returns
-------
numpy.ndarray
Resultant leading coefficients
"""
factors = np.zeros(n, dtype=np.float64)
_dll.broaden_wmp_polynomials_c(E, dopp, n, factors)
return factors

183
openmc/capi/mesh.py Normal file
View file

@ -0,0 +1,183 @@
from collections.abc import Mapping, Iterable
from ctypes import c_int, c_int32, c_double, POINTER
from weakref import WeakValueDictionary
import numpy as np
from numpy.ctypeslib import as_array
from openmc.exceptions import AllocationError, InvalidIDError
from . import _dll
from .core import _FortranObjectWithID
from .error import _error_handler
from .material import Material
__all__ = ['Mesh', 'meshes']
# Mesh functions
_dll.openmc_extend_meshes.argtypes = [c_int32, POINTER(c_int32), POINTER(c_int32)]
_dll.openmc_extend_meshes.restype = c_int
_dll.openmc_extend_meshes.errcheck = _error_handler
_dll.openmc_mesh_get_id.argtypes = [c_int32, POINTER(c_int32)]
_dll.openmc_mesh_get_id.restype = c_int
_dll.openmc_mesh_get_id.errcheck = _error_handler
_dll.openmc_mesh_get_dimension.argtypes = [c_int32, POINTER(POINTER(c_int)), POINTER(c_int)]
_dll.openmc_mesh_get_dimension.restype = c_int
_dll.openmc_mesh_get_dimension.errcheck = _error_handler
_dll.openmc_mesh_get_params.argtypes = [
c_int32, POINTER(POINTER(c_double)), POINTER(POINTER(c_double)),
POINTER(POINTER(c_double)), POINTER(c_int)]
_dll.openmc_mesh_get_params.restype = c_int
_dll.openmc_mesh_get_params.errcheck = _error_handler
_dll.openmc_mesh_set_id.argtypes = [c_int32, c_int32]
_dll.openmc_mesh_set_id.restype = c_int
_dll.openmc_mesh_set_id.errcheck = _error_handler
_dll.openmc_mesh_set_dimension.argtypes = [c_int32, c_int, POINTER(c_int)]
_dll.openmc_mesh_set_dimension.restype = c_int
_dll.openmc_mesh_set_dimension.errcheck = _error_handler
_dll.openmc_mesh_set_params.argtypes = [
c_int32, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double)]
_dll.openmc_mesh_set_params.restype = c_int
_dll.openmc_mesh_set_params.errcheck = _error_handler
_dll.openmc_get_mesh_index.argtypes = [c_int32, POINTER(c_int32)]
_dll.openmc_get_mesh_index.restype = c_int
_dll.openmc_get_mesh_index.errcheck = _error_handler
class Mesh(_FortranObjectWithID):
"""Mesh stored internally.
This class exposes a mesh that is stored internally in the OpenMC
library. To obtain a view of a mesh with a given ID, use the
:data:`openmc.capi.meshes` mapping.
Parameters
----------
index : int
Index in the `meshes` array.
Attributes
----------
id : int
ID of the mesh
dimension : iterable of int
The number of mesh cells in each direction.
lower_left : numpy.ndarray
The lower-left corner of the structured mesh. If only two coordinate are
given, it is assumed that the mesh is an x-y mesh.
upper_right : numpy.ndarray
The upper-right corner of the structrued mesh. If only two coordinate
are given, it is assumed that the mesh is an x-y mesh.
width : numpy.ndarray
The width of mesh cells in each direction.
"""
__instances = WeakValueDictionary()
def __new__(cls, uid=None, new=True, index=None):
mapping = meshes
if index is None:
if new:
# Determine ID to assign
if uid is None:
uid = max(mapping, default=0) + 1
else:
if uid in mapping:
raise AllocationError('A mesh with ID={} has already '
'been allocated.'.format(uid))
index = c_int32()
_dll.openmc_extend_meshes(1, index, None)
index = index.value
else:
index = mapping[uid]._index
if index not in cls.__instances:
instance = super().__new__(cls)
instance._index = index
if uid is not None:
instance.id = uid
cls.__instances[index] = instance
return cls.__instances[index]
@property
def id(self):
mesh_id = c_int32()
_dll.openmc_mesh_get_id(self._index, mesh_id)
return mesh_id.value
@id.setter
def id(self, mesh_id):
_dll.openmc_mesh_set_id(self._index, mesh_id)
@property
def dimension(self):
dims = POINTER(c_int)()
n = c_int()
_dll.openmc_mesh_get_dimension(self._index, dims, n)
return tuple(as_array(dims, (n.value,)))
@dimension.setter
def dimension(self, dimension):
n = len(dimension)
dimension = (c_int*n)(*dimension)
_dll.openmc_mesh_set_dimension(self._index, n, dimension)
@property
def lower_left(self):
return self._get_parameters()[0]
@property
def upper_right(self):
return self._get_parameters()[1]
@property
def width(self):
return self._get_parameters()[2]
def _get_parameters(self):
ll = POINTER(c_double)()
ur = POINTER(c_double)()
w = POINTER(c_double)()
n = c_int()
_dll.openmc_mesh_get_params(self._index, ll, ur, w, n)
return (
as_array(ll, (n.value,)),
as_array(ur, (n.value,)),
as_array(w, (n.value,))
)
def set_parameters(self, lower_left=None, upper_right=None, width=None):
if lower_left is not None:
n = len(lower_left)
lower_left = (c_double*n)(*lower_left)
if upper_right is not None:
n = len(upper_right)
upper_right = (c_double*n)(*upper_right)
if width is not None:
n = len(width)
width = (c_double*n)(*width)
_dll.openmc_mesh_set_params(self._index, n, lower_left, upper_right, width)
class _MeshMapping(Mapping):
def __getitem__(self, key):
index = c_int32()
try:
_dll.openmc_get_mesh_index(key, index)
except (AllocationError, InvalidIDError) as e:
# __contains__ expects a KeyError to work correctly
raise KeyError(str(e))
return Mesh(index=index.value)
def __iter__(self):
for i in range(len(self)):
yield Mesh(index=i + 1).id
def __len__(self):
return c_int32.in_dll(_dll, 'n_meshes').value
def __repr__(self):
return repr(dict(self))
meshes = _MeshMapping()

View file

@ -5,9 +5,10 @@ from weakref import WeakValueDictionary
import numpy as np
from numpy.ctypeslib import as_array
from openmc.exceptions import DataError, AllocationError
from . import _dll
from .core import _FortranObject
from .error import _error_handler, DataError, AllocationError
from .error import _error_handler
__all__ = ['Nuclide', 'nuclides', 'load_nuclide']

View file

@ -20,11 +20,11 @@ class _Settings(object):
generations_per_batch = _DLLGlobal(c_int32, 'gen_per_batch')
inactive = _DLLGlobal(c_int32, 'n_inactive')
particles = _DLLGlobal(c_int64, 'n_particles')
verbosity = _DLLGlobal(c_int, 'verbosity')
verbosity = _DLLGlobal(c_int, 'openmc_verbosity')
@property
def run_mode(self):
i = c_int.in_dll(_dll, 'run_mode').value
i = c_int.in_dll(_dll, 'openmc_run_mode').value
try:
return _RUN_MODES[i]
except KeyError:
@ -32,7 +32,7 @@ class _Settings(object):
@run_mode.setter
def run_mode(self, mode):
current_idx = c_int.in_dll(_dll, 'run_mode')
current_idx = c_int.in_dll(_dll, 'openmc_run_mode')
for idx, mode_value in _RUN_MODES.items():
if mode_value == mode:
current_idx.value = idx

View file

@ -1,15 +1,16 @@
from collections.abc import Mapping
from ctypes import c_int, c_int32, c_double, c_char_p, POINTER
from ctypes import c_int, c_int32, c_double, c_char_p, c_bool, POINTER
from weakref import WeakValueDictionary
import numpy as np
from numpy.ctypeslib import as_array
import scipy.stats
from openmc.exceptions import AllocationError, InvalidIDError
from openmc.data.reaction import REACTION_NAME
from . import _dll, Nuclide
from .core import _FortranObjectWithID
from .error import _error_handler, AllocationError, InvalidIDError
from .error import _error_handler
from .filter import _get_filter
@ -25,6 +26,9 @@ _dll.openmc_get_tally_index.errcheck = _error_handler
_dll.openmc_global_tallies.argtypes = [POINTER(POINTER(c_double))]
_dll.openmc_global_tallies.restype = c_int
_dll.openmc_global_tallies.errcheck = _error_handler
_dll.openmc_tally_get_active.argtypes = [c_int32, POINTER(c_bool)]
_dll.openmc_tally_get_active.restype = c_int
_dll.openmc_tally_get_active.errcheck = _error_handler
_dll.openmc_tally_get_id.argtypes = [c_int32, POINTER(c_int32)]
_dll.openmc_tally_get_id.restype = c_int
_dll.openmc_tally_get_id.errcheck = _error_handler
@ -47,6 +51,9 @@ _dll.openmc_tally_results.argtypes = [
c_int32, POINTER(POINTER(c_double)), POINTER(c_int*3)]
_dll.openmc_tally_results.restype = c_int
_dll.openmc_tally_results.errcheck = _error_handler
_dll.openmc_tally_set_active.argtypes = [c_int32, c_bool]
_dll.openmc_tally_set_active.restype = c_int
_dll.openmc_tally_set_active.errcheck = _error_handler
_dll.openmc_tally_set_filters.argtypes = [c_int32, c_int, POINTER(c_int32)]
_dll.openmc_tally_set_filters.restype = c_int
_dll.openmc_tally_set_filters.errcheck = _error_handler
@ -66,10 +73,10 @@ _dll.openmc_tally_set_type.errcheck = _error_handler
_SCORES = {
-1: 'flux', -2: 'total', -3: 'scatter', -4: 'nu-scatter',
-9: 'absorption', -10: 'fission', -11: 'nu-fission', -12: 'kappa-fission',
-13: 'current', -18: 'events', -19: 'delayed-nu-fission',
-20: 'prompt-nu-fission', -21: 'inverse-velocity', -22: 'fission-q-prompt',
-23: 'fission-q-recoverable', -24: 'decay-rate'
-5: 'absorption', -6: 'fission', -7: 'nu-fission', -8: 'kappa-fission',
-9: 'current', -10: 'events', -11: 'delayed-nu-fission',
-12: 'prompt-nu-fission', -13: 'inverse-velocity', -14: 'fission-q-prompt',
-15: 'fission-q-recoverable', -16: 'decay-rate'
}
@ -177,6 +184,16 @@ class Tally(_FortranObjectWithID):
return cls.__instances[index]
@property
def active(self):
active = c_bool()
_dll.openmc_tally_get_active(self._index, active)
return active.value
@active.setter
def active(self, active):
_dll.openmc_tally_set_active(self._index, active)
@property
def id(self):
tally_id = c_int32()

View file

@ -295,7 +295,7 @@ class Cell(IDManagerMixin):
"""
if volume_calc.domain_type == 'cell':
if self.id in volume_calc.volumes:
self._volume = volume_calc.volumes[self.id][0]
self._volume = volume_calc.volumes[self.id].n
self._atoms = volume_calc.atoms[self.id]
else:
raise ValueError('No volume information found for this cell.')
@ -335,7 +335,7 @@ class Cell(IDManagerMixin):
volume = self.volume
for name, atoms in self._atoms.items():
nuclide = openmc.Nuclide(name)
density = 1.0e-24 * atoms[0]/volume # density in atoms/b-cm
density = 1.0e-24 * atoms.n/volume # density in atoms/b-cm
nuclides[name] = (nuclide, density)
else:
raise RuntimeError(

View file

@ -196,7 +196,7 @@ def check_less_than(name, value, maximum, equality=False):
maximum : object
Maximum value to check against
equality : bool, optional
Whether equality is allowed. Defaluts to False.
Whether equality is allowed. Defaults to False.
"""
@ -223,7 +223,7 @@ def check_greater_than(name, value, minimum, equality=False):
minimum : object
Minimum value to check against
equality : bool, optional
Whether equality is allowed. Defaluts to False.
Whether equality is allowed. Defaults to False.
"""
@ -246,9 +246,9 @@ def check_filetype_version(obj, expected_type, expected_version):
----------
obj : h5py.File
HDF5 file to check
expected_type
expected_type : str
Expected file type, e.g. 'statepoint'
expected_version
expected_version : int
Expected major version number.
"""
@ -265,8 +265,9 @@ def check_filetype_version(obj, expected_type, expected_version):
if this_version[0] != expected_version:
raise IOError('{} file has a version of {} which is not '
'consistent with the version expected by OpenMC, {}'
.format(this_filetype, '.'.join(this_version),
expected_version))
.format(this_filetype,
'.'.join(str(v) for v in this_version),
expected_version))
except AttributeError:
raise IOError('Could not read {} file. This most likely means the {} '
'file was produced by a different version of OpenMC than '

View file

@ -22,7 +22,7 @@ import sys
import numpy as np
from openmc.mixin import EqualityMixin
from openmc.data.endf import ENDF_FLOAT_RE
from openmc.data.endf import _ENDF_FLOAT_RE
def ascii_to_binary(ascii_file, binary_file):
"""Convert an ACE file in ASCII format (type 1) to binary format (type 2).
@ -349,7 +349,7 @@ class Library(EqualityMixin):
# after it). If it's too short, then we apply the ENDF float regular
# expression. We don't do this by default because it's expensive!
if xss.size != nxs[1] + 1:
datastr = ENDF_FLOAT_RE.sub(r'\1e\2', datastr)
datastr = _ENDF_FLOAT_RE.sub(r'\1e\2', datastr)
xss = np.fromstring(datastr, sep=' ')
assert xss.size == nxs[1] + 1

View file

@ -1,10 +1,9 @@
import itertools
from math import sqrt
import os
import re
from warnings import warn
from numpy import sqrt
# Isotopic abundances from Meija J, Coplen T B, et al, "Isotopic compositions
# of the elements 2013 (IUPAC Technical Report)", Pure. Appl. Chem. 88 (3),
@ -136,23 +135,24 @@ ATOMIC_NUMBER = {value: key for key, value in ATOMIC_SYMBOL.items()}
_ATOMIC_MASS = {}
_GND_NAME_RE = re.compile(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)')
def atomic_mass(isotope):
"""Return atomic mass of isotope in atomic mass units.
Atomic mass data comes from the Atomic Mass Evaluation 2012, published in
Chinese Physics C 36 (2012), 1287--1602.
Atomic mass data comes from the `Atomic Mass Evaluation 2012
<https://www-nds.iaea.org/amdc/ame2012/AME2012-1.pdf>`_.
Parameters
----------
isotope : str
Name of isotope, e.g. 'Pu239'
Name of isotope, e.g., 'Pu239'
Returns
-------
float or None
Atomic mass of isotope in atomic mass units. If the isotope listed does
not have a known atomic mass, None is returned.
float
Atomic mass of isotope in [amu]
"""
if not _ATOMIC_MASS:
@ -183,7 +183,7 @@ def atomic_mass(isotope):
if '_' in isotope:
isotope = isotope[:isotope.find('_')]
return _ATOMIC_MASS.get(isotope.lower())
return _ATOMIC_MASS[isotope.lower()]
def atomic_weight(element):
@ -199,16 +199,19 @@ def atomic_weight(element):
Returns
-------
float or None
Atomic weight of element in atomic mass units. If the element listed does
not exist, None is returned.
float
Atomic weight of element in [amu]
"""
weight = 0.
for nuclide, abundance in NATURAL_ABUNDANCE.items():
if re.match(r'{}\d+'.format(element), nuclide):
weight += atomic_mass(nuclide) * abundance
return None if weight == 0. else weight
if weight > 0.:
return weight
else:
raise ValueError("No naturally-occurring isotopes for element '{}'."
.format(element))
def water_density(temperature, pressure=0.1013):
@ -234,7 +237,7 @@ def water_density(temperature, pressure=0.1013):
Returns
-------
float
Water density in units of [g / cm^3]
Water density in units of [g/cm^3]
"""
@ -313,14 +316,62 @@ def water_density(temperature, pressure=0.1013):
return coeff / pi / gamma1_pi
def gnd_name(Z, A, m=0):
"""Return nuclide name using GND convention
Parameters
----------
Z : int
Atomic number
A : int
Mass number
m : int, optional
Metastable state
Returns
-------
str
Nuclide name in GND convention, e.g., 'Am242_m1'
"""
if m > 0:
return '{}{}_m{}'.format(ATOMIC_SYMBOL[Z], A, m)
else:
return '{}{}'.format(ATOMIC_SYMBOL[Z], A)
def zam(name):
"""Return tuple of (atomic number, mass number, metastable state)
Parameters
----------
name : str
Name of nuclide using GND convention, e.g., 'Am242_m1'
Returns
-------
3-tuple of int
Atomic number, mass number, and metastable state
"""
try:
symbol, A, state = _GND_NAME_RE.match(name).groups()
except AttributeError:
raise ValueError("'{}' does not appear to be a nuclide name in GND "
"format.".format(name))
metastable = int(state[2:]) if state else 0
return (ATOMIC_NUMBER[symbol], int(A), metastable)
# Values here are from the Committee on Data for Science and Technology
# (CODATA) 2014 recommendation (doi:10.1103/RevModPhys.88.035009).
# The value of the Boltzman constant in units of eV / K
K_BOLTZMANN = 8.6173303e-5
# Used for converting units in ACE data
# Unit conversions
EV_PER_MEV = 1.0e6
JOULE_PER_EV = 1.6021766208e-19
# Avogadro's constant
AVOGADRO = 6.022140857e23

View file

@ -7,10 +7,7 @@ import re
from warnings import warn
import numpy as np
try:
from uncertainties import ufloat, unumpy, UFloat
except ImportError:
ufloat = UFloat = namedtuple('UFloat', ['nominal_value', 'std_dev'])
from uncertainties import ufloat, unumpy, UFloat
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
@ -457,6 +454,7 @@ class Decay(EqualityMixin):
items, values = get_list_record(file_obj)
self.nuclide['spin'] = items[0]
self.nuclide['parity'] = items[1]
self.half_life = ufloat(float('inf'), float('inf'))
@property
def decay_constant(self):

View file

@ -10,13 +10,14 @@ import io
import re
import os
from math import pi
from pathlib import PurePath
from collections import OrderedDict
from collections.abc import Iterable
import numpy as np
from numpy.polynomial.polynomial import Polynomial
from .data import ATOMIC_SYMBOL
from .data import ATOMIC_SYMBOL, gnd_name
from .function import Tabulated1D, INTERPOLATION_SCHEME
from openmc.stats.univariate import Uniform, Tabular, Legendre
@ -44,7 +45,7 @@ SUM_RULES = {1: [2, 3],
106: list(range(750, 800)),
107: list(range(800, 850))}
ENDF_FLOAT_RE = re.compile(r'([\s\-\+]?\d*\.\d+)([\+\-]\d+)')
_ENDF_FLOAT_RE = re.compile(r'([\s\-\+]?\d*\.\d+)([\+\-]\d+)')
def float_endf(s):
@ -367,8 +368,8 @@ class Evaluation(object):
"""
def __init__(self, filename_or_obj):
if isinstance(filename_or_obj, str):
fh = open(filename_or_obj, 'r')
if isinstance(filename_or_obj, (str, PurePath)):
fh = open(str(filename_or_obj), 'r')
else:
fh = filename_or_obj
self.section = {}
@ -491,13 +492,9 @@ class Evaluation(object):
@property
def gnd_name(self):
symbol = ATOMIC_SYMBOL[self.target['atomic_number']]
A = self.target['mass_number']
m = self.target['isomeric_state']
if m > 0:
return '{}{}_m{}'.format(symbol, A, m)
else:
return '{}{}'.format(symbol, A)
return gnd_name(self.target['atomic_number'],
self.target['mass_number'],
self.target['isomeric_state'])
class Tabulated2D(object):

View file

@ -23,9 +23,14 @@ class DataLibrary(EqualityMixin):
def __init__(self):
self.libraries = []
def get_by_material(self, value):
def get_by_material(self, name):
"""Return the library dictionary containing a given material.
Parameters
----------
name : str
Name of material, e.g. 'Am241'
Returns
-------
library : dict or None
@ -34,7 +39,7 @@ class DataLibrary(EqualityMixin):
"""
for library in self.libraries:
if value in library['materials']:
if name in library['materials']:
return library
return None

View file

@ -24,7 +24,7 @@ _RM_RF = 3 # Residue fission
# Multi-level Breit Wigner indices
_MLBW_RT = 1 # Residue total
_MLBW_RX = 2 # Residue compettitive
_MLBW_RX = 2 # Residue competitive
_MLBW_RA = 3 # Residue absorption
_MLBW_RF = 4 # Residue fission
@ -141,6 +141,12 @@ def _broaden_wmp_polynomials(E, dopp, n):
class WindowedMultipole(EqualityMixin):
"""Resonant cross sections represented in the windowed multipole format.
Parameters
----------
formalism : {'MLBW', 'RM'}
The R-matrix formalism used to reconstruct resonances. Either 'MLBW'
for multi-level Breit Wigner or 'RM' for Reich-Moore.
Attributes
----------
num_l : Integral
@ -195,11 +201,9 @@ class WindowedMultipole(EqualityMixin):
a/E + b/sqrt(E) + c + d sqrt(E) + ...
"""
def __init__(self):
self.num_l = None
self.fit_order = None
self.fissionable = None
self.formalism = None
def __init__(self, formalism):
self._num_l = None
self.formalism = formalism
self.spacing = None
self.sqrtAWR = None
self.start_E = None
@ -218,11 +222,15 @@ class WindowedMultipole(EqualityMixin):
@property
def fit_order(self):
return self._fit_order
return self.curvefit.shape[1] - 1
@property
def fissionable(self):
return self._fissionable
if self.formalism == 'RM':
return self.data.shape[1] == 4
else:
# Assume self.formalism == 'MLBW'
return self.data.shape[1] == 5
@property
def formalism(self):
@ -272,35 +280,10 @@ class WindowedMultipole(EqualityMixin):
def curvefit(self):
return self._curvefit
@num_l.setter
def num_l(self, num_l):
if num_l is not None:
cv.check_type('num_l', num_l, Integral)
cv.check_greater_than('num_l', num_l, 1, equality=True)
cv.check_less_than('num_l', num_l, 4, equality=True)
# There is an if block in _evaluate that assumes num_l <= 4.
self._num_l = num_l
@fit_order.setter
def fit_order(self, fit_order):
if fit_order is not None:
cv.check_type('fit_order', fit_order, Integral)
cv.check_greater_than('fit_order', fit_order, 2, equality=True)
# _broaden_wmp_polynomials assumes the curve fit has at least 3
# terms.
self._fit_order = fit_order
@fissionable.setter
def fissionable(self, fissionable):
if fissionable is not None:
cv.check_type('fissionable', fissionable, bool)
self._fissionable = fissionable
@formalism.setter
def formalism(self, formalism):
if formalism is not None:
cv.check_type('formalism', formalism, str)
cv.check_value('formalism', formalism, ('MLBW', 'RM'))
cv.check_type('formalism', formalism, str)
cv.check_value('formalism', formalism, ('MLBW', 'RM'))
self._formalism = formalism
@spacing.setter
@ -337,9 +320,20 @@ class WindowedMultipole(EqualityMixin):
cv.check_type('data', data, np.ndarray)
if len(data.shape) != 2:
raise ValueError('Multipole data arrays must be 2D')
if data.shape[1] not in (3, 4, 5): # 3 or 4 for RM, 4 or 5 for MLBW
raise ValueError('The second dimension of multipole data arrays'
' must have a length of 3, 4 or 5')
if self.formalism == 'RM':
if data.shape[1] not in (3, 4):
raise ValueError('For the Reich-Moore formalism, '
'data.shape[1] must be 3 or 4. One value for the pole.'
' One each for the total and absorption residues. '
'Possibly one more for a fission residue.')
else:
# Assume self.formalism == 'MLBW'
if data.shape[1] not in (4, 5):
raise ValueError('For the Multi-level Breit-Wigner '
'formalism, data.shape[1] must be 4 or 5. One value '
'for the pole. One each for the total, competitive, '
'and absorption residues. Possibly one more for a '
'fission residue.')
if not np.issubdtype(data.dtype, complex):
raise TypeError('Multipole data arrays must be complex dtype')
self._data = data
@ -363,6 +357,12 @@ class WindowedMultipole(EqualityMixin):
if not np.issubdtype(l_value.dtype, int):
raise TypeError('Multipole l_value arrays must be integer'
' dtype')
self._num_l = len(np.unique(l_value))
else:
self._num_l = None
self._l_value = l_value
@w_start.setter
@ -428,6 +428,7 @@ class WindowedMultipole(EqualityMixin):
format.
"""
if isinstance(group_or_filename, h5py.Group):
group = group_or_filename
else:
@ -442,20 +443,12 @@ class WindowedMultipole(EqualityMixin):
'Python API expects version ' + WMP_VERSION)
group = h5file['nuclide']
out = cls()
# Read scalar values. Note that group['max_w'] is ignored.
length = group['length'].value
windows = group['windows'].value
out.num_l = group['num_l'].value
out.fit_order = group['fit_order'].value
out.fissionable = bool(group['fissionable'].value)
# Read scalars.
if group['formalism'].value == _FORM_MLBW:
out.formalism = 'MLBW'
out = cls('MLBW')
elif group['formalism'].value == _FORM_RM:
out.formalism = 'RM'
out = cls('RM')
else:
raise ValueError('Unrecognized/Unsupported R-matrix formalism')
@ -466,39 +459,36 @@ class WindowedMultipole(EqualityMixin):
# Read arrays.
err = "WMP '{}' array shape is not consistent with the '{}' value"
err = "WMP '{}' array shape is not consistent with the '{}' array shape"
out.data = group['data'].value
if out.data.shape[0] != length:
raise ValueError(err.format('data', 'length'))
out.l_value = group['l_value'].value
if out.l_value.shape[0] != out.data.shape[0]:
raise ValueError(err.format('l_value', 'data'))
out.pseudo_k0RS = group['pseudo_K0RS'].value
if out.pseudo_k0RS.shape[0] != out.num_l:
raise ValueError(err.format('pseudo_k0RS', 'num_l'))
out.l_value = group['l_value'].value
if out.l_value.shape[0] != length:
raise ValueError(err.format('l_value', 'length'))
raise ValueError(err.format('pseudo_k0RS', 'l_value'))
out.w_start = group['w_start'].value
if out.w_start.shape[0] != windows:
raise ValueError(err.format('w_start', 'windows'))
out.w_end = group['w_end'].value
if out.w_end.shape[0] != windows:
raise ValueError(err.format('w_end', 'windows'))
if out.w_end.shape[0] != out.w_start.shape[0]:
raise ValueError(err.format('w_end', 'w_start'))
out.broaden_poly = group['broaden_poly'].value.astype(np.bool)
if out.broaden_poly.shape[0] != windows:
raise ValueError(err.format('broaden_poly', 'windows'))
if out.broaden_poly.shape[0] != out.w_start.shape[0]:
raise ValueError(err.format('broaden_poly', 'w_start'))
out.curvefit = group['curvefit'].value
if out.curvefit.shape[0] != windows:
raise ValueError(err.format('curvefit', 'windows'))
if out.curvefit.shape[1] != out.fit_order + 1:
raise ValueError(err.format('curvefit', 'fit_order'))
if out.curvefit.shape[0] != out.w_start.shape[0]:
raise ValueError(err.format('curvefit', 'w_start'))
# Note that all the file 3 data (group['reactions/MT...']) are ignored.
# _broaden_wmp_polynomials assumes the curve fit has at least 3 terms.
if out.fit_order < 2:
raise ValueError("Windowed multipole is only supported for "
"curvefits with 3 or more terms.")
return out
@ -661,3 +651,47 @@ class WindowedMultipole(EqualityMixin):
fun = np.vectorize(lambda x: self._evaluate(x, T))
return fun(E)
def export_to_hdf5(self, path, libver='earliest'):
"""Export windowed multipole data to an HDF5 file.
Parameters
----------
path : str
Path to write HDF5 file to
libver : {'earliest', 'latest'}
Compatibility mode for the HDF5 file. 'latest' will produce files
that are less backwards compatible but have performance benefits.
"""
# Open file and write version.
with h5py.File(path, 'w', libver=libver) as f:
f.create_dataset('version', (1, ), dtype='S10')
f['version'][:] = WMP_VERSION.encode('ASCII')
# Make a nuclide group.
g = f.create_group('nuclide')
# Write scalars.
if self.formalism == 'MLBW':
g.create_dataset('formalism',
data=np.array(_FORM_MLBW, dtype=np.int32))
else:
# Assume RM.
g.create_dataset('formalism',
data=np.array(_FORM_RM, dtype=np.int32))
g.create_dataset('spacing', data=np.array(self.spacing))
g.create_dataset('sqrtAWR', data=np.array(self.sqrtAWR))
g.create_dataset('start_E', data=np.array(self.start_E))
g.create_dataset('end_E', data=np.array(self.end_E))
# Write arrays.
g.create_dataset('data', data=self.data)
g.create_dataset('l_value', data=self.l_value)
g.create_dataset('pseudo_K0RS', data=self.pseudo_k0RS)
g.create_dataset('w_start', data=self.w_start)
g.create_dataset('w_end', data=self.w_end)
g.create_dataset('broaden_poly',
data=self.broaden_poly.astype(np.int8))
g.create_dataset('curvefit', data=self.curvefit)

View file

@ -16,7 +16,7 @@ import h5py
from . import HDF5_VERSION, HDF5_VERSION_MAJOR
from .ace import Library, Table, get_table
from .data import ATOMIC_SYMBOL, K_BOLTZMANN, EV_PER_MEV
from .data import ATOMIC_SYMBOL, K_BOLTZMANN, EV_PER_MEV, gnd_name
from .endf import Evaluation, SUM_RULES, get_head_record, get_tab1_record
from .fission_energy import FissionEnergyRelease
from .function import Tabulated1D, Sum, ResonancesWithBackground
@ -95,9 +95,7 @@ def _get_metadata(zaid, metastable_scheme='nndc'):
# Determine name
element = ATOMIC_SYMBOL[Z]
name = '{}{}'.format(element, mass_number)
if metastable > 0:
name += '_m{}'.format(metastable)
name = gnd_name(Z, mass_number, metastable)
return (name, element, Z, mass_number, metastable)

View file

@ -24,40 +24,44 @@ from openmc.stats import Discrete, Tabular
_THERMAL_NAMES = {
'c_Al27': ('al', 'al27'),
'c_Be': ('be', 'be-metal'),
'c_BeO': ('beo'),
'c_Be_in_BeO': ('bebeo', 'be-o', 'be/o'),
'c_Al27': ('al', 'al27', 'al-27'),
'c_Be': ('be', 'be-metal', 'be-met'),
'c_BeO': ('beo',),
'c_Be_in_BeO': ('bebeo', 'be-beo', 'be-o', 'be/o'),
'c_C6H6': ('benz', 'c6h6'),
'c_C_in_SiC': ('csic',),
'c_Ca_in_CaH2': ('cah'),
'c_D_in_D2O': ('dd2o', 'hwtr', 'hw'),
'c_Fe56': ('fe', 'fe56'),
'c_C_in_SiC': ('csic', 'c-sic'),
'c_Ca_in_CaH2': ('cah',),
'c_D_in_D2O': ('dd2o', 'd-d2o', 'hwtr', 'hw'),
'c_Fe56': ('fe', 'fe56', 'fe-56'),
'c_Graphite': ('graph', 'grph', 'gr'),
'c_H_in_CaH2': ('hcah2'),
'c_H_in_CH2': ('hch2', 'poly', 'pol'),
'c_Graphite_10p': ('grph10',),
'c_Graphite_30p': ('grph30',),
'c_H_in_CaH2': ('hcah2',),
'c_H_in_CH2': ('hch2', 'poly', 'pol', 'h-poly'),
'c_H_in_CH4_liquid': ('lch4', 'lmeth'),
'c_H_in_CH4_solid': ('sch4', 'smeth'),
'c_H_in_H2O': ('hh2o', 'lwtr', 'lw'),
'c_H_in_H2O_solid': ('hice',),
'c_H_in_C5O2H8': ('lucite', 'c5o2h8'),
'c_H_in_YH2': ('hyh2'),
'c_H_in_ZrH': ('hzrh', 'h-zr', 'h/zr', 'hzr'),
'c_H_in_H2O': ('hh2o', 'h-h2o', 'lwtr', 'lw'),
'c_H_in_H2O_solid': ('hice', 'h-ice'),
'c_H_in_C5O2H8': ('lucite', 'c5o2h8', 'h-luci'),
'c_H_in_YH2': ('hyh2', 'h-yh2'),
'c_H_in_ZrH': ('hzrh', 'h-zrh', 'h-zr', 'h/zr', 'hzr'),
'c_Mg24': ('mg', 'mg24'),
'c_O_in_BeO': ('obeo', 'o-be', 'o/be'),
'c_O_in_D2O': ('od2o'),
'c_O_in_H2O_ice': ('oice'),
'c_O_in_UO2': ('ouo2', 'o2-u', 'o2/u'),
'c_ortho_D': ('orthod', 'dortho'),
'c_ortho_H': ('orthoh', 'hortho'),
'c_Si_in_SiC': ('sisic'),
'c_O_in_BeO': ('obeo', 'o-beo', 'o-be', 'o/be'),
'c_O_in_D2O': ('od2o', 'o-d2o'),
'c_O_in_H2O_ice': ('oice', 'o-ice'),
'c_O_in_UO2': ('ouo2', 'o-uo2', 'o2-u', 'o2/u'),
'c_N_in_UN': ('n-un',),
'c_ortho_D': ('orthod', 'orthoD', 'dortho'),
'c_ortho_H': ('orthoh', 'orthoH', 'hortho'),
'c_Si_in_SiC': ('sisic', 'si-sic'),
'c_SiO2_alpha': ('sio2', 'sio2a'),
'c_SiO2_beta': ('sio2b'),
'c_para_D': ('parad', 'dpara'),
'c_para_H': ('parah', 'hpara'),
'c_U_in_UO2': ('uuo2', 'u-o2', 'u/o2'),
'c_Y_in_YH2': ('yyh2'),
'c_Zr_in_ZrH': ('zrzrh', 'zr-h', 'zr/h')
'c_SiO2_beta': ('sio2b',),
'c_para_D': ('parad', 'paraD', 'dpara'),
'c_para_H': ('parah', 'paraH', 'hpara'),
'c_U_in_UN': ('u-un',),
'c_U_in_UO2': ('uuo2', 'u-uo2', 'u-o2', 'u/o2'),
'c_Y_in_YH2': ('yyh2', 'y-yh2'),
'c_Zr_in_ZrH': ('zrzrh', 'zr-zrh', 'zr-h', 'zr/h')
}

View file

@ -0,0 +1,24 @@
"""
openmc.deplete
==============
A depletion front-end tool.
"""
from .dummy_comm import DummyCommunicator
try:
from mpi4py import MPI
comm = MPI.COMM_WORLD
have_mpi = True
except ImportError:
comm = DummyCommunicator()
have_mpi = False
from .nuclide import *
from .chain import *
from .operator import *
from .reaction_rates import *
from .abc import *
from .results import *
from .results_list import *
from .integrator import *

142
openmc/deplete/abc.py Normal file
View file

@ -0,0 +1,142 @@
"""function module.
This module contains the Operator class, which is then passed to an integrator
to run a full depletion simulation.
"""
from collections import namedtuple
import os
from pathlib import Path
from abc import ABCMeta, abstractmethod
from .chain import Chain
OperatorResult = namedtuple('OperatorResult', ['k', 'rates'])
OperatorResult.__doc__ = """\
Result of applying transport operator
Parameters
----------
k : float
Resulting eigenvalue
rates : openmc.deplete.ReactionRates
Resulting reaction rates
"""
try:
OperatorResult.k.__doc__ = None
OperatorResult.rates.__doc__ = None
except AttributeError:
# Can't set __doc__ on properties on Python 3.4
pass
class TransportOperator(metaclass=ABCMeta):
"""Abstract class defining a transport operator
Each depletion integrator is written to work with a generic transport
operator that takes a vector of material compositions and returns an
eigenvalue and reaction rates. This abstract class sets the requirements for
such a transport operator. Users should instantiate
:class:`openmc.deplete.Operator` rather than this class.
Parameters
----------
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the
:envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists.
Attributes
----------
dilute_initial : float
Initial atom density to add for nuclides that are zero in initial
condition to ensure they exist in the decay chain. Only done for
nuclides with reaction rates. Defaults to 1.0e3.
"""
def __init__(self, chain_file=None):
self.dilute_initial = 1.0e3
self.output_dir = '.'
# Read depletion chain
if chain_file is None:
chain_file = os.environ.get("OPENMC_DEPLETE_CHAIN", None)
if chain_file is None:
raise IOError("No chain specified, either manually or in "
"environment variable OPENMC_DEPLETE_CHAIN.")
self.chain = Chain.from_xml(chain_file)
@abstractmethod
def __call__(self, vec, print_out=True):
"""Runs a simulation.
Parameters
----------
vec : list of numpy.ndarray
Total atoms to be used in function.
print_out : bool, optional
Whether or not to print out time.
Returns
-------
openmc.deplete.OperatorResult
Eigenvalue and reaction rates resulting from transport operator
"""
pass
def __enter__(self):
# Save current directory and move to specific output directory
self._orig_dir = os.getcwd()
if not self.output_dir.exists():
self.output_dir.mkdir() # exist_ok parameter is 3.5+
# In Python 3.6+, chdir accepts a Path directly
os.chdir(str(self.output_dir))
return self.initial_condition()
def __exit__(self, exc_type, exc_value, traceback):
self.finalize()
os.chdir(self._orig_dir)
@property
def output_dir(self):
return self._output_dir
@output_dir.setter
def output_dir(self, output_dir):
self._output_dir = Path(output_dir)
@abstractmethod
def initial_condition(self):
"""Performs final setup and returns initial condition.
Returns
-------
list of numpy.ndarray
Total density for initial conditions.
"""
pass
@abstractmethod
def get_results_info(self):
"""Returns volume list, cell lists, and nuc lists.
Returns
-------
volume : list of float
Volumes corresponding to materials in burn_list
nuc_list : list of str
A list of all nuclide names. Used for sorting the simulation.
burn_list : list of int
A list of all cell IDs to be burned. Used for sorting the simulation.
full_burn_list : list of int
All burnable materials in the geometry.
"""
pass
def finalize(self):
pass

View file

@ -0,0 +1,214 @@
"""AtomNumber module.
An ndarray to store atom densities with string, integer, or slice indexing.
"""
from collections import OrderedDict
import numpy as np
class AtomNumber(object):
"""Stores local material compositions (atoms of each nuclide).
Parameters
----------
local_mats : list of str
Material IDs
nuclides : list of str
Nuclides to be tracked
volume : dict
Volume of each material in [cm^3]
n_nuc_burn : int
Number of nuclides to be burned.
Attributes
----------
index_mat : dict
A dictionary mapping material ID as string to index.
index_nuc : dict
A dictionary mapping nuclide name to index.
volume : numpy.ndarray
Volume of each material in [cm^3]. If a volume is not found, it defaults
to 1 so that reading density still works correctly.
number : numpy.ndarray
Array storing total atoms for each material/nuclide
materials : list of str
Material IDs as strings
nuclides : list of str
All nuclide names
burnable_nuclides : list of str
Burnable nuclides names. Used for sorting the simulation.
n_nuc_burn : int
Number of burnable nuclides.
n_nuc : int
Number of nuclides.
"""
def __init__(self, local_mats, nuclides, volume, n_nuc_burn):
self.index_mat = OrderedDict((mat, i) for i, mat in enumerate(local_mats))
self.index_nuc = OrderedDict((nuc, i) for i, nuc in enumerate(nuclides))
self.volume = np.ones(len(local_mats))
for mat, val in volume.items():
if mat in self.index_mat:
ind = self.index_mat[mat]
self.volume[ind] = val
self.n_nuc_burn = n_nuc_burn
self.number = np.zeros((len(local_mats), len(nuclides)))
def __getitem__(self, pos):
"""Retrieves total atom number from AtomNumber.
Parameters
----------
pos : tuple
A two-length tuple containing a material index and a nuc index.
These indexes can be strings (which get converted to integers via
the dictionaries), integers used directly, or slices.
Returns
-------
numpy.ndarray
The value indexed from self.number.
"""
mat, nuc = pos
if isinstance(mat, str):
mat = self.index_mat[mat]
if isinstance(nuc, str):
nuc = self.index_nuc[nuc]
return self.number[mat, nuc]
def __setitem__(self, pos, val):
"""Sets total atom number into AtomNumber.
Parameters
----------
pos : tuple
A two-length tuple containing a material index and a nuc index.
These indexes can be strings (which get converted to integers via
the dictionaries), integers used directly, or slices.
val : float
The value [atom] to set the array to.
"""
mat, nuc = pos
if isinstance(mat, str):
mat = self.index_mat[mat]
if isinstance(nuc, str):
nuc = self.index_nuc[nuc]
self.number[mat, nuc] = val
@property
def materials(self):
return self.index_mat.keys()
@property
def nuclides(self):
return self.index_nuc.keys()
@property
def n_nuc(self):
return len(self.index_nuc)
@property
def burnable_nuclides(self):
return [nuc for nuc, ind in self.index_nuc.items()
if ind < self.n_nuc_burn]
def get_atom_density(self, mat, nuc):
"""Accesses atom density instead of total number.
Parameters
----------
mat : str, int or slice
Material index.
nuc : str, int or slice
Nuclide index.
Returns
-------
numpy.ndarray
Density in [atom/cm^3]
"""
if isinstance(mat, str):
mat = self.index_mat[mat]
if isinstance(nuc, str):
nuc = self.index_nuc[nuc]
return self[mat, nuc] / self.volume[mat]
def set_atom_density(self, mat, nuc, val):
"""Sets atom density instead of total number.
Parameters
----------
mat : str, int or slice
Material index.
nuc : str, int or slice
Nuclide index.
val : numpy.ndarray
Array of densities to set in [atom/cm^3]
"""
if isinstance(mat, str):
mat = self.index_mat[mat]
if isinstance(nuc, str):
nuc = self.index_nuc[nuc]
self[mat, nuc] = val * self.volume[mat]
def get_mat_slice(self, mat):
"""Gets atom quantity indexed by mats for all burned nuclides
Parameters
----------
mat : str, int or slice
Material index.
Returns
-------
numpy.ndarray
The slice requested in [atom].
"""
if isinstance(mat, str):
mat = self.index_mat[mat]
return self[mat, :self.n_nuc_burn]
def set_mat_slice(self, mat, val):
"""Sets atom quantity indexed by mats for all burned nuclides
Parameters
----------
mat : str, int or slice
Material index.
val : numpy.ndarray
The slice to set in [atom]
"""
if isinstance(mat, str):
mat = self.index_mat[mat]
self[mat, :self.n_nuc_burn] = val
def set_density(self, total_density):
"""Sets density.
Sets the density in the exact same order as total_density_list outputs,
allowing for internal consistency
Parameters
----------
total_density : list of numpy.ndarray
Total atoms.
"""
for i, density_slice in enumerate(total_density):
self.set_mat_slice(i, density_slice)

440
openmc/deplete/chain.py Normal file
View file

@ -0,0 +1,440 @@
"""chain module.
This module contains information about a depletion chain. A depletion chain is
loaded from an .xml file and all the nuclides are linked together.
"""
from collections import OrderedDict, defaultdict
from io import StringIO
from itertools import chain
import math
import re
import os
# Try to use lxml if it is available. It preserves the order of attributes and
# provides a pretty-printer by default. If not available, use OpenMC function to
# pretty print.
try:
import lxml.etree as ET
_have_lxml = True
except ImportError:
import xml.etree.ElementTree as ET
_have_lxml = False
import scipy.sparse as sp
import openmc.data
from openmc.clean_xml import clean_xml_indentation
from .nuclide import Nuclide, DecayTuple, ReactionTuple
# tuple of (reaction name, possible MT values, (dA, dZ)) where dA is the change
# in the mass number and dZ is the change in the atomic number
_REACTIONS = [
('(n,2n)', set(chain([16], range(875, 892))), (-1, 0)),
('(n,3n)', {17}, (-2, 0)),
('(n,4n)', {37}, (-3, 0)),
('(n,gamma)', {102}, (1, 0)),
('(n,p)', set(chain([103], range(600, 650))), (0, -1)),
('(n,a)', set(chain([107], range(800, 850))), (-3, -2))
]
def replace_missing(product, decay_data):
"""Replace missing product with suitable decay daughter.
Parameters
----------
product : str
Name of product in GND format, e.g. 'Y86_m1'.
decay_data : dict
Dictionary of decay data
Returns
-------
product : str
Replacement for missing product in GND format.
"""
# Determine atomic number, mass number, and metastable state
Z, A, state = openmc.data.zam(product)
symbol = openmc.data.ATOMIC_SYMBOL[Z]
# Replace neutron with proton
if Z == 0 and A == 1:
return 'H1'
# First check if ground state is available
if state:
product = '{}{}'.format(symbol, A)
# Find isotope with longest half-life
half_life = 0.0
for nuclide, data in decay_data.items():
m = re.match(r'{}(\d+)(?:_m\d+)?'.format(symbol), nuclide)
if m:
# If we find a stable nuclide, stop search
if data.nuclide['stable']:
mass_longest_lived = int(m.group(1))
break
if data.half_life.nominal_value > half_life:
mass_longest_lived = int(m.group(1))
half_life = data.half_life.nominal_value
# If mass number of longest-lived isotope is less than that of missing
# product, assume it undergoes beta-. Otherwise assume beta+.
beta_minus = (mass_longest_lived < A)
# Iterate until we find an existing nuclide
while product not in decay_data:
if Z > 98:
Z -= 2
A -= 4
else:
if beta_minus:
Z += 1
else:
Z -= 1
product = '{}{}'.format(openmc.data.ATOMIC_SYMBOL[Z], A)
return product
class Chain(object):
"""Full representation of a depletion chain.
A depletion chain can be created by using the :meth:`from_endf` method which
requires a list of ENDF incident neutron, decay, and neutron fission product
yield sublibrary files. The depletion chain used during a depletion
simulation is indicated by either an argument to
:class:`openmc.deplete.Operator` or through the
:envvar:`OPENMC_DEPLETE_CHAIN` environment variable.
Attributes
----------
nuclides : list of openmc.deplete.Nuclide
Nuclides present in the chain.
reactions : list of str
Reactions that are tracked in the depletion chain
nuclide_dict : OrderedDict of str to int
Maps a nuclide name to an index in nuclides.
"""
def __init__(self):
self.nuclides = []
self.reactions = []
self.nuclide_dict = OrderedDict()
def __contains__(self, nuclide):
return nuclide in self.nuclide_dict
def __getitem__(self, name):
"""Get a Nuclide by name."""
return self.nuclides[self.nuclide_dict[name]]
def __len__(self):
"""Number of nuclides in chain."""
return len(self.nuclides)
@classmethod
def from_endf(cls, decay_files, fpy_files, neutron_files):
"""Create a depletion chain from ENDF files.
Parameters
----------
decay_files : list of str
List of ENDF decay sub-library files
fpy_files : list of str
List of ENDF neutron-induced fission product yield sub-library files
neutron_files : list of str
List of ENDF neutron reaction sub-library files
"""
chain = cls()
# Create dictionary mapping target to filename
print('Processing neutron sub-library files...')
reactions = {}
for f in neutron_files:
evaluation = openmc.data.endf.Evaluation(f)
name = evaluation.gnd_name
reactions[name] = {}
for mf, mt, nc, mod in evaluation.reaction_list:
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[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:
data = openmc.data.Decay(f)
# Skip decay data for neutron itself
if data.nuclide['atomic_number'] == 0:
continue
decay_data[data.nuclide['name']] = data
print('Processing fission product yield sub-library files...')
fpy_data = {}
for f in fpy_files:
data = openmc.data.FissionProductYields(f)
fpy_data[data.nuclide['name']] = data
print('Creating depletion_chain...')
missing_daughter = []
missing_rx_product = []
missing_fpy = []
missing_fp = []
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 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):
type_ = ','.join(mode.modes)
if mode.daughter in decay_data:
target = mode.daughter
else:
print('missing {} {} {}'.format(parent, ','.join(mode.modes), mode.daughter))
target = replace_missing(mode.daughter, decay_data)
# Write branching ratio, taking care to ensure sum is unity
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(type_, target, br))
if parent in reactions:
reactions_available = set(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))
# Store Q value
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))
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:
nuclide.yield_energies = fpy.energies
else:
nuclide.yield_energies = [0.0]
for E, table in zip(nuclide.yield_energies, fpy.independent):
yield_replace = 0.0
yields = defaultdict(float)
for product, y in table.items():
# Handle fission products that have no decay data available
if product not in decay_data:
daughter = replace_missing(product, decay_data)
product = daughter
yield_replace += y.nominal_value
yields[product] += y.nominal_value
if yield_replace > 0.0:
missing_fp.append((parent, E, yield_replace))
nuclide.yield_data[E] = []
for k in sorted(yields, key=openmc.data.zam):
nuclide.yield_data[E].append((k, yields[k]))
# Display warnings
if missing_daughter:
print('The following decay modes have daughters with no decay data:')
for mode in missing_daughter:
print(' {}'.format(mode))
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('')
if missing_fp:
print('The following nuclides have fission products with no decay data:')
for vals in missing_fp:
print(' {}, E={} eV (total yield={})'.format(*vals))
return chain
@classmethod
def from_xml(cls, filename):
"""Reads a depletion chain XML file.
Parameters
----------
filename : str
The path to the depletion chain XML file.
"""
chain = cls()
# Load XML tree
root = ET.parse(str(filename))
for i, nuclide_elem in enumerate(root.findall('nuclide')):
nuc = Nuclide.from_xml(nuclide_elem)
chain.nuclide_dict[nuc.name] = i
# Check for reaction paths
for rx in nuc.reactions:
if rx.type not in chain.reactions:
chain.reactions.append(rx.type)
chain.nuclides.append(nuc)
return chain
def export_to_xml(self, filename):
"""Writes a depletion chain XML file.
Parameters
----------
filename : str
The path to the depletion chain XML file.
"""
root_elem = ET.Element('depletion_chain')
for nuclide in self.nuclides:
root_elem.append(nuclide.to_xml_element())
tree = ET.ElementTree(root_elem)
if _have_lxml:
tree.write(str(filename), encoding='utf-8', pretty_print=True)
else:
clean_xml_indentation(root_elem)
tree.write(str(filename), encoding='utf-8')
def form_matrix(self, rates):
"""Forms depletion matrix.
Parameters
----------
rates : numpy.ndarray
2D array indexed by (nuclide, reaction)
Returns
-------
scipy.sparse.csr_matrix
Sparse matrix representing depletion.
"""
matrix = defaultdict(float)
reactions = set()
for i, nuc in enumerate(self.nuclides):
if nuc.n_decay_modes != 0:
# Decay paths
# Loss
decay_constant = math.log(2) / nuc.half_life
if decay_constant != 0.0:
matrix[i, i] -= decay_constant
# Gain
for _, target, branching_ratio in nuc.decay_modes:
# Allow for total annihilation for debug purposes
if target != 'Nothing':
branch_val = branching_ratio * decay_constant
if branch_val != 0.0:
k = self.nuclide_dict[target]
matrix[k, i] += branch_val
if nuc.name in rates.index_nuc:
# Extract all reactions for this nuclide in this cell
nuc_ind = rates.index_nuc[nuc.name]
nuc_rates = rates[nuc_ind, :]
for r_type, target, _, br in nuc.reactions:
# Extract reaction index, and then final reaction rate
r_id = rates.index_rx[r_type]
path_rate = nuc_rates[r_id]
# Loss term -- make sure we only count loss once for
# reactions with branching ratios
if r_type not in reactions:
reactions.add(r_type)
if path_rate != 0.0:
matrix[i, i] -= path_rate
# Gain term; allow for total annihilation for debug purposes
if target != 'Nothing':
if r_type != 'fission':
if path_rate != 0.0:
k = self.nuclide_dict[target]
matrix[k, i] += path_rate * br
else:
# Assume that we should always use thermal fission
# yields. At some point it would be nice to account
# for the energy-dependence..
energy, data = sorted(nuc.yield_data.items())[0]
for product, y in data:
yield_val = y * path_rate
if yield_val != 0.0:
k = self.nuclide_dict[product]
matrix[k, i] += yield_val
# Clear set of reactions
reactions.clear()
# Use DOK matrix as intermediate representation, then convert to CSR and return
n = len(self)
matrix_dok = sp.dok_matrix((n, n))
dict.update(matrix_dok, matrix)
return matrix_dok.tocsr()

View file

@ -0,0 +1,27 @@
class DummyCommunicator(object):
rank = 0
size = 1
def allgather(self, sendobj):
return [sendobj]
def allreduce(self, sendobj, op=None):
return sendobj
def barrier(self):
pass
def bcast(self, obj, root=0):
return obj
def gather(self, sendobj, root=0):
return [sendobj]
def py2f(self):
return 0
def reduce(self, sendobj, op=None, root=0):
return sendobj
def scatter(self, sendobj, root=0):
return sendobj[0]

View file

@ -0,0 +1,10 @@
"""
Integrator
===========
The integrator subcomponents.
"""
from .cecm import *
from .cram import *
from .predictor import *

View file

@ -0,0 +1,110 @@
"""The CE/CM integrator."""
import copy
from collections.abc import Iterable
from .cram import deplete
from ..results import Results
def cecm(operator, timesteps, power, print_out=True):
r"""Deplete using the CE/CM algorithm.
Implements the second order `CE/CM predictor-corrector algorithm
<https://doi.org/10.13182/NSE14-92>`_. This algorithm is mathematically
defined as:
.. math::
y' &= A(y, t) y(t)
A_p &= A(y_n, t_n)
y_m &= \text{expm}(A_p h/2) y_n
A_c &= A(y_m, t_n + h/2)
y_{n+1} &= \text{expm}(A_c h) y_n
Parameters
----------
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.
power : float or iterable of float
Power of the reactor in [W]. A single value indicates that the power is
constant over all timesteps. An iterable indicates potentially different
power levels for each timestep. For a 2D problem, the power can be given
in [W/cm] as long as the "volume" assigned to a depletion material is
actually an area in [cm^2].
print_out : bool, optional
Whether or not to print out time.
"""
if not isinstance(power, Iterable):
power = [power]*len(timesteps)
# Generate initial conditions
with operator as vec:
chain = operator.chain
# Initialize time
if operator.prev_res is None:
t = 0.0
else:
t = operator.prev_res[-1].time[-1]
# Initialize starting index for saving results
if operator.prev_res is None:
i_res = 0
else:
i_res = len(operator.prev_res)
for i, (dt, p) in enumerate(zip(timesteps, power)):
# Get beginning-of-timestep concentrations and reaction rates
# Avoid doing first transport run if already done in previous
# calculation
if i > 0 or operator.prev_res is None:
x = [copy.deepcopy(vec)]
op_results = [operator(x[0], p)]
else:
# Get initial concentration
x = [operator.prev_res[-1].data[0]]
# Get rates
op_results = [operator.prev_res[-1]]
op_results[0].rates = op_results[0].rates[0]
# Set first stage value of keff
op_results[0].k = op_results[0].k[0]
# Scale reaction rates by ratio of powers
power_res = operator.prev_res[-1].power
ratio_power = p / power_res
op_results[0].rates[0] *= ratio_power[0]
# Deplete for first half of timestep
x_middle = deplete(chain, x[0], op_results[0], dt/2, print_out)
# Get middle-of-timestep reaction rates
x.append(x_middle)
op_results.append(operator(x_middle, p))
# Deplete for full timestep using beginning-of-step materials
# and middle-of-timestep reaction rates
x_end = deplete(chain, x[0], op_results[1], dt, print_out)
# Create results, write to disk
Results.save(operator, x, op_results, [t, t + dt], p, i_res + i)
# Advance time, update vector
t += dt
vec = copy.deepcopy(x_end)
# Perform one last simulation
x = [copy.deepcopy(vec)]
op_results = [operator(x[0], power[-1])]
# Create results, write to disk
Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps))

View file

@ -0,0 +1,227 @@
"""Chebyshev Rational Approximation Method module
Implements two different forms of CRAM for use in openmc.deplete.
"""
from itertools import repeat
from multiprocessing import Pool
import time
import numpy as np
import scipy.sparse as sp
import scipy.sparse.linalg as sla
from .. import comm
def deplete(chain, x, op_result, dt, print_out):
"""Deplete materials using given reaction rates for a specified time
Parameters
----------
chain : openmc.deplete.Chain
Depletion chain
x : list of numpy.ndarray
Atom number vectors for each material
op_result : openmc.deplete.OperatorResult
Result of applying transport operator (contains reaction rates)
dt : float
Time in [s] to deplete for
print_out : bool
Whether to show elapsed time
Returns
-------
x_result : list of numpy.ndarray
Updated atom number vectors for each material
"""
t_start = time.time()
# Set up iterators
n_mats = len(x)
chains = repeat(chain, n_mats)
vecs = (x[i] for i in range(n_mats))
rates = (op_result.rates[i, :, :] for i in range(n_mats))
dts = repeat(dt, n_mats)
# Use multiprocessing pool to distribute work
with Pool() as pool:
iters = zip(chains, vecs, rates, dts)
x_result = list(pool.starmap(_cram_wrapper, iters))
t_end = time.time()
if comm.rank == 0:
if print_out:
print("Time to matexp: ", t_end - t_start)
return x_result
def _cram_wrapper(chain, n0, rates, dt):
"""Wraps depletion matrix creation / CRAM solve for multiprocess execution
Parameters
----------
chain : DepletionChain
Depletion chain used to construct the burnup matrix
n0 : numpy.array
Vector to operate a matrix exponent on.
rates : numpy.ndarray
2D array indexed by nuclide then by cell.
dt : float
Time to integrate to.
Returns
-------
numpy.array
Results of the matrix exponent.
"""
A = chain.form_matrix(rates)
return CRAM48(A, n0, dt)
def CRAM16(A, n0, dt):
"""Chebyshev Rational Approximation Method, order 16
Algorithm is the 16th order Chebyshev Rational Approximation Method,
implemented in the more stable `incomplete partial fraction (IPF)
<https://doi.org/10.13182/NSE15-26>`_ form.
Parameters
----------
A : scipy.linalg.csr_matrix
Matrix to take exponent of.
n0 : numpy.array
Vector to operate a matrix exponent on.
dt : float
Time to integrate to.
Returns
-------
numpy.array
Results of the matrix exponent.
"""
alpha = np.array([+2.124853710495224e-16,
+5.464930576870210e+3 - 3.797983575308356e+4j,
+9.045112476907548e+1 - 1.115537522430261e+3j,
+2.344818070467641e+2 - 4.228020157070496e+2j,
+9.453304067358312e+1 - 2.951294291446048e+2j,
+7.283792954673409e+2 - 1.205646080220011e+5j,
+3.648229059594851e+1 - 1.155509621409682e+2j,
+2.547321630156819e+1 - 2.639500283021502e+1j,
+2.394538338734709e+1 - 5.650522971778156e+0j],
dtype=np.complex128)
theta = np.array([+0.0,
+3.509103608414918 + 8.436198985884374j,
+5.948152268951177 + 3.587457362018322j,
-5.264971343442647 + 16.22022147316793j,
+1.419375897185666 + 10.92536348449672j,
+6.416177699099435 + 1.194122393370139j,
+4.993174737717997 + 5.996881713603942j,
-1.413928462488886 + 13.49772569889275j,
-10.84391707869699 + 19.27744616718165j],
dtype=np.complex128)
n = A.shape[0]
alpha0 = 2.124853710495224e-16
k = 8
y = np.array(n0, dtype=np.float64)
for l in range(1, k+1):
y = 2.0*np.real(alpha[l]*sla.spsolve(A*dt - theta[l]*sp.eye(n), y)) + y
y *= alpha0
return y
def CRAM48(A, n0, dt):
"""Chebyshev Rational Approximation Method, order 48
Algorithm is the 48th order Chebyshev Rational Approximation Method,
implemented in the more stable `incomplete partial fraction (IPF)
<https://doi.org/10.13182/NSE15-26>`_ form.
Parameters
----------
A : scipy.linalg.csr_matrix
Matrix to take exponent of.
n0 : numpy.array
Vector to operate a matrix exponent on.
dt : float
Time to integrate to.
Returns
-------
numpy.array
Results of the matrix exponent.
"""
theta_r = np.array([-4.465731934165702e+1, -5.284616241568964e+0,
-8.867715667624458e+0, +3.493013124279215e+0,
+1.564102508858634e+1, +1.742097597385893e+1,
-2.834466755180654e+1, +1.661569367939544e+1,
+8.011836167974721e+0, -2.056267541998229e+0,
+1.449208170441839e+1, +1.853807176907916e+1,
+9.932562704505182e+0, -2.244223871767187e+1,
+8.590014121680897e-1, -1.286192925744479e+1,
+1.164596909542055e+1, +1.806076684783089e+1,
+5.870672154659249e+0, -3.542938819659747e+1,
+1.901323489060250e+1, +1.885508331552577e+1,
-1.734689708174982e+1, +1.316284237125190e+1])
theta_i = np.array([+6.233225190695437e+1, +4.057499381311059e+1,
+4.325515754166724e+1, +3.281615453173585e+1,
+1.558061616372237e+1, +1.076629305714420e+1,
+5.492841024648724e+1, +1.316994930024688e+1,
+2.780232111309410e+1, +3.794824788914354e+1,
+1.799988210051809e+1, +5.974332563100539e+0,
+2.532823409972962e+1, +5.179633600312162e+1,
+3.536456194294350e+1, +4.600304902833652e+1,
+2.287153304140217e+1, +8.368200580099821e+0,
+3.029700159040121e+1, +5.834381701800013e+1,
+1.194282058271408e+0, +3.583428564427879e+0,
+4.883941101108207e+1, +2.042951874827759e+1])
theta = np.array(theta_r + theta_i * 1j, dtype=np.complex128)
alpha_r = np.array([+6.387380733878774e+2, +1.909896179065730e+2,
+4.236195226571914e+2, +4.645770595258726e+2,
+7.765163276752433e+2, +1.907115136768522e+3,
+2.909892685603256e+3, +1.944772206620450e+2,
+1.382799786972332e+5, +5.628442079602433e+3,
+2.151681283794220e+2, +1.324720240514420e+3,
+1.617548476343347e+4, +1.112729040439685e+2,
+1.074624783191125e+2, +8.835727765158191e+1,
+9.354078136054179e+1, +9.418142823531573e+1,
+1.040012390717851e+2, +6.861882624343235e+1,
+8.766654491283722e+1, +1.056007619389650e+2,
+7.738987569039419e+1, +1.041366366475571e+2])
alpha_i = np.array([-6.743912502859256e+2, -3.973203432721332e+2,
-2.041233768918671e+3, -1.652917287299683e+3,
-1.783617639907328e+4, -5.887068595142284e+4,
-9.953255345514560e+3, -1.427131226068449e+3,
-3.256885197214938e+6, -2.924284515884309e+4,
-1.121774011188224e+3, -6.370088443140973e+4,
-1.008798413156542e+6, -8.837109731680418e+1,
-1.457246116408180e+2, -6.388286188419360e+1,
-2.195424319460237e+2, -6.719055740098035e+2,
-1.693747595553868e+2, -1.177598523430493e+1,
-4.596464999363902e+3, -1.738294585524067e+3,
-4.311715386228984e+1, -2.777743732451969e+2])
alpha = np.array(alpha_r + alpha_i * 1j, dtype=np.complex128)
n = A.shape[0]
alpha0 = 2.258038182743983e-47
k = 24
y = np.array(n0, dtype=np.float64)
for l in range(k):
y = 2.0*np.real(alpha[l]*sla.spsolve(A*dt - theta[l]*sp.eye(n), y)) + y
y *= alpha0
return y

View file

@ -0,0 +1,93 @@
"""First-order predictor algorithm."""
import copy
from collections.abc import Iterable
from .cram import deplete
from ..results import Results
def predictor(operator, timesteps, power, print_out=True):
r"""Deplete using a first-order predictor algorithm.
Implements the first-order predictor algorithm. This algorithm is
mathematically defined as:
.. math::
y' &= A(y, t) y(t)
A_p &= A(y_n, t_n)
y_{n+1} &= \text{expm}(A_p h) y_n
Parameters
----------
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.
power : float or iterable of float
Power of the reactor in [W]. A single value indicates that the power is
constant over all timesteps. An iterable indicates potentially different
power levels for each timestep. For a 2D problem, the power can be given
in [W/cm] as long as the "volume" assigned to a depletion material is
actually an area in [cm^2].
print_out : bool, optional
Whether or not to print out time.
"""
if not isinstance(power, Iterable):
power = [power]*len(timesteps)
# Generate initial conditions
with operator as vec:
chain = operator.chain
# Initialize time
if operator.prev_res is None:
t = 0.0
else:
t = operator.prev_res[-1].time[-1]
# Initialize starting index for saving results
if operator.prev_res is None:
i_res = 0
else:
i_res = len(operator.prev_res) - 1
for i, (dt, p) in enumerate(zip(timesteps, power)):
# Get beginning-of-timestep concentrations and reaction rates
# Avoid doing first transport run if already done in previous
# calculation
if i > 0 or operator.prev_res is None:
x = [copy.deepcopy(vec)]
op_results = [operator(x[0], p)]
# Create results, write to disk
Results.save(operator, x, op_results, [t, t + dt], p, i_res + i)
else:
# Get initial concentration
x = [operator.prev_res[-1].data[0]]
# Get rates
op_results = [operator.prev_res[-1]]
op_results[0].rates = op_results[0].rates[0]
# Scale reaction rates by ratio of powers
power_res = operator.prev_res[-1].power
ratio_power = p / power_res
op_results[0].rates[0] *= ratio_power[0]
# Deplete for full timestep
x_end = deplete(chain, x[0], op_results[0], dt, print_out)
# Advance time, update vector
t += dt
vec = copy.deepcopy(x_end)
# Perform one last simulation
x = [copy.deepcopy(vec)]
op_results = [operator(x[0], power[-1])]
# Create results, write to disk
Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps))

219
openmc/deplete/nuclide.py Normal file
View file

@ -0,0 +1,219 @@
"""Nuclide module.
Contains the per-nuclide components of a depletion chain.
"""
from collections import namedtuple
try:
import lxml.etree as ET
except ImportError:
import xml.etree.ElementTree as ET
DecayTuple = namedtuple('DecayTuple', 'type target branching_ratio')
DecayTuple.__doc__ = """\
Decay mode information
Parameters
----------
type : str
Type of the decay mode, e.g., 'beta-'
target : str
Nuclide resulting from decay
branching_ratio : float
Branching ratio of the decay mode
"""
try:
DecayTuple.type.__doc__ = None
DecayTuple.target.__doc__ = None
DecayTuple.branching_ratio.__doc__ = None
except AttributeError:
# Can't set __doc__ on properties on Python 3.4
pass
ReactionTuple = namedtuple('ReactionTuple', 'type target Q branching_ratio')
ReactionTuple.__doc__ = """\
Transmutation reaction information
Parameters
----------
type : str
Type of the reaction, e.g., 'fission'
target : str
nuclide resulting from reaction
Q : float
Q value of the reaction in [eV]
branching_ratio : float
Branching ratio of the reaction
"""
try:
ReactionTuple.type.__doc__ = None
ReactionTuple.target.__doc__ = None
ReactionTuple.Q.__doc__ = None
ReactionTuple.branching_ratio.__doc__ = None
except AttributeError:
pass
class Nuclide(object):
"""Decay modes, reactions, and fission yields for a single nuclide.
Attributes
----------
name : str
Name of nuclide.
half_life : float
Half life of nuclide in [s].
decay_energy : float
Energy deposited from decay in [eV].
n_decay_modes : int
Number of decay pathways.
decay_modes : list of openmc.deplete.DecayTuple
Decay mode information. Each element of the list is a named tuple with
attributes 'type', 'target', and 'branching_ratio'.
n_reaction_paths : int
Number of possible reaction pathways.
reactions : list of openmc.deplete.ReactionTuple
Reaction information. Each element of the list is a named tuple with
attribute 'type', 'target', 'Q', and 'branching_ratio'.
yield_data : dict of float to list
Maps tabulated energy to list of (product, yield) for all
neutron-induced fission products.
yield_energies : list of float
Energies at which fission product yiels exist
"""
def __init__(self):
# Information about the nuclide
self.name = None
self.half_life = None
self.decay_energy = 0.0
# Decay paths
self.decay_modes = []
# Reaction paths
self.reactions = []
# Neutron fission yields, if present
self.yield_data = {}
self.yield_energies = []
@property
def n_decay_modes(self):
return len(self.decay_modes)
@property
def n_reaction_paths(self):
return len(self.reactions)
@classmethod
def from_xml(cls, element):
"""Read nuclide from an XML element.
Parameters
----------
element : xml.etree.ElementTree.Element
XML element to write nuclide data to
Returns
-------
nuc : openmc.deplete.Nuclide
Instance of a nuclide
"""
nuc = cls()
nuc.name = element.get('name')
# Check for half-life
if 'half_life' in element.attrib:
nuc.half_life = float(element.get('half_life'))
nuc.decay_energy = float(element.get('decay_energy', '0'))
# Check for decay paths
for decay_elem in element.iter('decay'):
d_type = decay_elem.get('type')
target = decay_elem.get('target')
branching_ratio = float(decay_elem.get('branching_ratio'))
nuc.decay_modes.append(DecayTuple(d_type, target, branching_ratio))
# Check for reaction paths
for reaction_elem in element.iter('reaction'):
r_type = reaction_elem.get('type')
Q = float(reaction_elem.get('Q', '0'))
branching_ratio = float(reaction_elem.get('branching_ratio', '1'))
# If the type is not fission, get target and Q value, otherwise
# just set null values
if r_type != 'fission':
target = reaction_elem.get('target')
else:
target = None
# Append reaction
nuc.reactions.append(ReactionTuple(
r_type, target, Q, branching_ratio))
fpy_elem = element.find('neutron_fission_yields')
if fpy_elem is not None:
for yields_elem in fpy_elem.iter('fission_yields'):
E = float(yields_elem.get('energy'))
products = yields_elem.find('products').text.split()
yields = [float(y) for y in
yields_elem.find('data').text.split()]
nuc.yield_data[E] = list(zip(products, yields))
nuc.yield_energies = list(sorted(nuc.yield_data.keys()))
return nuc
def to_xml_element(self):
"""Write nuclide to XML element.
Returns
-------
elem : xml.etree.ElementTree.Element
XML element to write nuclide data to
"""
elem = ET.Element('nuclide')
elem.set('name', self.name)
if self.half_life is not None:
elem.set('half_life', str(self.half_life))
elem.set('decay_modes', str(len(self.decay_modes)))
elem.set('decay_energy', str(self.decay_energy))
for mode, daughter, br in self.decay_modes:
mode_elem = ET.SubElement(elem, 'decay')
mode_elem.set('type', mode)
mode_elem.set('target', daughter)
mode_elem.set('branching_ratio', str(br))
elem.set('reactions', str(len(self.reactions)))
for rx, daughter, Q, br in self.reactions:
rx_elem = ET.SubElement(elem, 'reaction')
rx_elem.set('type', rx)
rx_elem.set('Q', str(Q))
if rx != 'fission':
rx_elem.set('target', daughter)
if br != 1.0:
rx_elem.set('branching_ratio', str(br))
if self.yield_data:
fpy_elem = ET.SubElement(elem, 'neutron_fission_yields')
energy_elem = ET.SubElement(fpy_elem, 'energies')
energy_elem.text = ' '.join(str(E) for E in self.yield_energies)
for E in self.yield_energies:
yields_elem = ET.SubElement(fpy_elem, 'fission_yields')
yields_elem.set('energy', str(E))
products_elem = ET.SubElement(yields_elem, 'products')
products_elem.text = ' '.join(x[0] for x in self.yield_data[E])
data_elem = ET.SubElement(yields_elem, 'data')
data_elem.text = ' '.join(str(x[1]) for x in self.yield_data[E])
return elem

629
openmc/deplete/operator.py Normal file
View file

@ -0,0 +1,629 @@
"""OpenMC transport operator
This module implements a transport operator for OpenMC so that it can be used by
depletion integrators. The implementation makes use of the Python bindings to
OpenMC's C API so that reading tally results and updating material number
densities is all done in-memory instead of through the filesystem.
"""
import copy
from collections import OrderedDict
from itertools import chain
import os
import time
import xml.etree.ElementTree as ET
import h5py
import numpy as np
import openmc
import openmc.capi
from openmc.data import JOULE_PER_EV
from . import comm
from .abc import TransportOperator, OperatorResult
from .atom_number import AtomNumber
from .reaction_rates import ReactionRates
def _distribute(items):
"""Distribute items across MPI communicator
Parameters
----------
items : list
List of items of distribute
Returns
-------
list
Items assigned to process that called
"""
min_size, extra = divmod(len(items), comm.size)
j = 0
for i in range(comm.size):
chunk_size = min_size + int(i < extra)
if comm.rank == i:
return items[j:j + chunk_size]
j += chunk_size
class Operator(TransportOperator):
"""OpenMC transport operator for depletion.
Instances of this class can be used to perform depletion using OpenMC as the
transport operator. Normally, a user needn't call methods of this class
directly. Instead, an instance of this class is passed to an integrator
function, such as :func:`openmc.deplete.integrator.cecm`.
Parameters
----------
geometry : openmc.Geometry
OpenMC geometry object
settings : openmc.Settings
OpenMC Settings object
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the
:envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists.
prev_results : ResultsList, optional
Results from a previous depletion calculation. If this argument is
specified, the depletion calculation will start from the latest state
in the previous results.
Attributes
----------
geometry : openmc.Geometry
OpenMC geometry object
settings : openmc.Settings
OpenMC settings object
dilute_initial : float
Initial atom density to add for nuclides that are zero in initial
condition to ensure they exist in the decay chain. Only done for
nuclides with reaction rates. Defaults to 1.0e3.
output_dir : pathlib.Path
Path to output directory to save results.
round_number : bool
Whether or not to round output to OpenMC to 8 digits.
Useful in testing, as OpenMC is incredibly sensitive to exact values.
number : openmc.deplete.AtomNumber
Total number of atoms in simulation.
nuclides_with_data : set of str
A set listing all unique nuclides available from cross_sections.xml.
chain : openmc.deplete.Chain
The depletion chain information necessary to form matrices and tallies.
reaction_rates : openmc.deplete.ReactionRates
Reaction rates from the last operator step.
burnable_mats : list of str
All burnable material IDs
local_mats : list of str
All burnable material IDs being managed by a single process
prev_res : ResultsList
Results from a previous depletion calculation
"""
def __init__(self, geometry, settings, chain_file=None, prev_results=None):
super().__init__(chain_file)
self.round_number = False
self.settings = settings
self.geometry = geometry
if prev_results != None:
# Reload volumes into geometry
prev_results[-1].transfer_volumes(geometry)
# Store previous results in operator
self.prev_res = prev_results
else:
self.prev_res = None
# Clear out OpenMC, create task lists, distribute
openmc.reset_auto_ids()
self.burnable_mats, volume, nuclides = self._get_burnable_mats()
self.local_mats = _distribute(self.burnable_mats)
# Determine which nuclides have incident neutron data
self.nuclides_with_data = self._get_nuclides_with_data()
# Select nuclides with data that are also in the chain
self._burnable_nucs = [nuc.name for nuc in self.chain.nuclides
if nuc.name in self.nuclides_with_data]
# Extract number densities from the geometry / previous depletion run
self._extract_number(self.local_mats, volume, nuclides, self.prev_res)
# Create reaction rates array
self.reaction_rates = ReactionRates(
self.local_mats, self._burnable_nucs, self.chain.reactions)
def __call__(self, vec, power, print_out=True):
"""Runs a simulation.
Parameters
----------
vec : list of numpy.ndarray
Total atoms to be used in function.
power : float
Power of the reactor in [W]
print_out : bool, optional
Whether or not to print out time.
Returns
-------
openmc.deplete.OperatorResult
Eigenvalue and reaction rates resulting from transport operator
"""
# Prevent OpenMC from complaining about re-creating tallies
openmc.reset_auto_ids()
# Update status
self.number.set_density(vec)
time_start = time.time()
# Update material compositions and tally nuclides
self._update_materials()
self._tally.nuclides = self._get_tally_nuclides()
# Run OpenMC
openmc.capi.reset()
openmc.capi.run()
time_openmc = time.time()
# Extract results
op_result = self._unpack_tallies_and_normalize(power)
if comm.rank == 0:
time_unpack = time.time()
if print_out:
print("Time to openmc: ", time_openmc - time_start)
print("Time to unpack: ", time_unpack - time_openmc)
return copy.deepcopy(op_result)
def _get_burnable_mats(self):
"""Determine depletable materials, volumes, and nuclids
Returns
-------
burnable_mats : list of str
List of burnable material IDs
volume : OrderedDict of str to float
Volume of each material in [cm^3]
nuclides : list of str
Nuclides in order of how they'll appear in the simulation.
"""
burnable_mats = set()
model_nuclides = set()
volume = OrderedDict()
# Iterate once through the geometry to get dictionaries
for mat in self.geometry.get_all_materials().values():
for nuclide in mat.get_nuclides():
model_nuclides.add(nuclide)
if mat.depletable:
burnable_mats.add(str(mat.id))
if mat.volume is None:
raise RuntimeError("Volume not specified for depletable "
"material with ID={}.".format(mat.id))
volume[str(mat.id)] = mat.volume
# Make sure there are burnable materials
if not burnable_mats:
raise RuntimeError(
"No depletable materials were found in the model.")
# Sort the sets
burnable_mats = sorted(burnable_mats, key=int)
model_nuclides = sorted(model_nuclides)
# Construct a global nuclide dictionary, burned first
nuclides = list(self.chain.nuclide_dict)
for nuc in model_nuclides:
if nuc not in nuclides:
nuclides.append(nuc)
return burnable_mats, volume, nuclides
def _extract_number(self, local_mats, volume, nuclides, prev_res=None):
"""Construct AtomNumber using geometry
Parameters
----------
local_mats : list of str
Material IDs to be managed by this process
volume : OrderedDict of str to float
Volumes for the above materials in [cm^3]
nuclides : list of str
Nuclides to be used in the simulation.
prev_res : ResultsList, optional
Results from a previous depletion calculation
"""
self.number = AtomNumber(local_mats, nuclides, volume, len(self.chain))
if self.dilute_initial != 0.0:
for nuc in self._burnable_nucs:
self.number.set_atom_density(np.s_[:], nuc, self.dilute_initial)
# Now extract and store the number densities
# From the geometry if no previous depletion results
if prev_res is None:
for mat in self.geometry.get_all_materials().values():
if str(mat.id) in local_mats:
self._set_number_from_mat(mat)
# Else from previous depletion results
else:
for mat in self.geometry.get_all_materials().values():
if str(mat.id) in local_mats:
self._set_number_from_results(mat, prev_res)
def _set_number_from_mat(self, mat):
"""Extracts material and number densities from openmc.Material
Parameters
----------
mat : openmc.Material
The material to read from
"""
mat_id = str(mat.id)
for nuclide, density in mat.get_nuclide_atom_densities().values():
number = density * 1.0e24
self.number.set_atom_density(mat_id, nuclide, number)
def _set_number_from_results(self, mat, prev_res):
"""Extracts material nuclides and number densities.
If the nuclide concentration's evolution is tracked, the densities come
from depletion results. Else, densities are extracted from the geometry
in the summary.
Parameters
----------
mat : openmc.Material
The material to read from
prev_res : ResultsList
Results from a previous depletion calculation
"""
mat_id = str(mat.id)
# Get nuclide lists from geometry and depletion results
depl_nuc = prev_res[-1].nuc_to_ind
geom_nuc_densities = mat.get_nuclide_atom_densities()
# Merge lists of nuclides, with the same order for every calculation
geom_nuc_densities.update(depl_nuc)
for nuclide in geom_nuc_densities.keys():
if nuclide in depl_nuc:
concentration = prev_res.get_atoms(mat_id, nuclide)[1][-1]
volume = prev_res[-1].volume[mat_id]
number = concentration / volume
else:
density = geom_nuc_densities[nuclide][1]
number = density * 1.0e24
self.number.set_atom_density(mat_id, nuclide, number)
def initial_condition(self):
"""Performs final setup and returns initial condition.
Returns
-------
list of numpy.ndarray
Total density for initial conditions.
"""
# Create XML files
if comm.rank == 0:
self.geometry.export_to_xml()
self.settings.export_to_xml()
self._generate_materials_xml()
# Initialize OpenMC library
comm.barrier()
openmc.capi.init(intracomm=comm)
# Generate tallies in memory
self._generate_tallies()
# Return number density vector
return list(self.number.get_mat_slice(np.s_[:]))
def finalize(self):
"""Finalize a depletion simulation and release resources."""
openmc.capi.finalize()
def _update_materials(self):
"""Updates material compositions in OpenMC on all processes."""
for rank in range(comm.size):
number_i = comm.bcast(self.number, root=rank)
for mat in number_i.materials:
nuclides = []
densities = []
for nuc in number_i.nuclides:
if nuc in self.nuclides_with_data:
val = 1.0e-24 * number_i.get_atom_density(mat, nuc)
# If nuclide is zero, do not add to the problem.
if val > 0.0:
if self.round_number:
val_magnitude = np.floor(np.log10(val))
val_scaled = val / 10**val_magnitude
val_round = round(val_scaled, 8)
val = val_round * 10**val_magnitude
nuclides.append(nuc)
densities.append(val)
else:
# Only output warnings if values are significantly
# negative. CRAM does not guarantee positive values.
if val < -1.0e-21:
print("WARNING: nuclide ", nuc, " in material ", mat,
" is negative (density = ", val, " at/barn-cm)")
number_i[mat, nuc] = 0.0
# Update densities on C API side
mat_internal = openmc.capi.materials[int(mat)]
mat_internal.set_densities(nuclides, densities)
#TODO Update densities on the Python side, otherwise the
# summary.h5 file contains densities at the first time step
def _generate_materials_xml(self):
"""Creates materials.xml from self.number.
Due to uncertainty with how MPI interacts with OpenMC API, this
constructs the XML manually. The long term goal is to do this
through direct memory writing.
"""
materials = openmc.Materials(self.geometry.get_all_materials()
.values())
# Sort nuclides according to order in AtomNumber object
nuclides = list(self.number.nuclides)
for mat in materials:
mat._nuclides.sort(key=lambda x: nuclides.index(x[0]))
materials.export_to_xml()
def _get_tally_nuclides(self):
"""Determine nuclides that should be tallied for reaction rates.
This method returns a list of all nuclides that have neutron data and
are listed in the depletion chain. Technically, we should tally nuclides
that may not appear in the depletion chain because we still need to get
the fission reaction rate for these nuclides in order to normalize
power, but that is left as a future exercise.
Returns
-------
list of str
Tally nuclides
"""
nuc_set = set()
# Create the set of all nuclides in the decay chain in materials marked
# for burning in which the number density is greater than zero.
for nuc in self.number.nuclides:
if nuc in self.nuclides_with_data:
if np.sum(self.number[:, nuc]) > 0.0:
nuc_set.add(nuc)
# Communicate which nuclides have nonzeros to rank 0
if comm.rank == 0:
for i in range(1, comm.size):
nuc_newset = comm.recv(source=i, tag=i)
nuc_set |= nuc_newset
else:
comm.send(nuc_set, dest=0, tag=comm.rank)
if comm.rank == 0:
# Sort nuclides in the same order as self.number
nuc_list = [nuc for nuc in self.number.nuclides
if nuc in nuc_set]
else:
nuc_list = None
# Store list of tally nuclides on each process
nuc_list = comm.bcast(nuc_list)
return [nuc for nuc in nuc_list if nuc in self.chain]
def _generate_tallies(self):
"""Generates depletion tallies.
Using information from the depletion chain as well as the nuclides
currently in the problem, this function automatically generates a
tally.xml for the simulation.
"""
# Create tallies for depleting regions
materials = [openmc.capi.materials[int(i)]
for i in self.burnable_mats]
mat_filter = openmc.capi.MaterialFilter(materials)
# Set up a tally that has a material filter covering each depletable
# material and scores corresponding to all reactions that cause
# transmutation. The nuclides for the tally are set later when eval() is
# called.
self._tally = openmc.capi.Tally()
self._tally.scores = self.chain.reactions
self._tally.filters = [mat_filter]
def _unpack_tallies_and_normalize(self, power):
"""Unpack tallies from OpenMC and return an operator result
This method uses OpenMC's C API bindings to determine the k-effective
value and reaction rates from the simulation. The reaction rates are
normalized by the user-specified power, summing the product of the
fission reaction rate times the fission Q value for each material.
Parameters
----------
power : float
Power of the reactor in [W]
Returns
-------
openmc.deplete.OperatorResult
Eigenvalue and reaction rates resulting from transport operator
"""
rates = self.reaction_rates
rates[:, :, :] = 0.0
k_combined = openmc.capi.keff()[0]
# Extract tally bins
materials = self.burnable_mats
nuclides = self._tally.nuclides
# Form fast map
nuc_ind = [rates.index_nuc[nuc] for nuc in nuclides]
react_ind = [rates.index_rx[react] for react in self.chain.reactions]
# Compute fission power
# TODO : improve this calculation
# Keep track of energy produced from all reactions in eV per source
# particle
energy = 0.0
# Create arrays to store fission Q values, reaction rates, and nuclide
# numbers
fission_Q = np.zeros(rates.n_nuc)
rates_expanded = np.zeros((rates.n_nuc, rates.n_react))
number = np.zeros(rates.n_nuc)
fission_ind = rates.index_rx["fission"]
for nuclide in self.chain.nuclides:
if nuclide.name in rates.index_nuc:
for rx in nuclide.reactions:
if rx.type == 'fission':
ind = rates.index_nuc[nuclide.name]
fission_Q[ind] = rx.Q
break
# Extract results
for i, mat in enumerate(self.local_mats):
# Get tally index
slab = materials.index(mat)
# Get material results hyperslab
results = self._tally.results[slab, :, 1]
# Zero out reaction rates and nuclide numbers
rates_expanded[:] = 0.0
number[:] = 0.0
# Expand into our memory layout
j = 0
for nuc, i_nuc_results in zip(nuclides, nuc_ind):
number[i_nuc_results] = self.number[mat, nuc]
for react in react_ind:
rates_expanded[i_nuc_results, react] = results[j]
j += 1
# Accumulate energy from fission
energy += np.dot(rates_expanded[:, fission_ind], fission_Q)
# Divide by total number and store
for i_nuc_results in nuc_ind:
if number[i_nuc_results] != 0.0:
for react in react_ind:
rates_expanded[i_nuc_results, react] /= number[i_nuc_results]
rates[i, :, :] = rates_expanded
# Reduce energy produced from all processes
energy = comm.allreduce(energy)
# Determine power in eV/s
power /= JOULE_PER_EV
# Scale reaction rates to obtain units of reactions/sec
rates *= power / energy
return OperatorResult(k_combined, rates)
def _get_nuclides_with_data(self):
"""Loads a cross_sections.xml file to find participating nuclides.
This allows for nuclides that are important in the decay chain but not
important neutronically, or have no cross section data.
"""
# Reads cross_sections.xml to create a dictionary containing
# participating (burning and not just decaying) nuclides.
try:
filename = os.environ["OPENMC_CROSS_SECTIONS"]
except KeyError:
filename = None
nuclides = set()
try:
tree = ET.parse(filename)
except Exception:
if filename is None:
msg = "No cross_sections.xml specified in materials."
else:
msg = 'Cross section file "{}" is invalid.'.format(filename)
raise IOError(msg)
root = tree.getroot()
for nuclide_node in root.findall('library'):
mats = nuclide_node.get('materials')
if not mats:
continue
for name in mats.split():
# Make a burn list of the union of nuclides in cross_sections.xml
# and nuclides in depletion chain.
if name not in nuclides:
nuclides.add(name)
return nuclides
def get_results_info(self):
"""Returns volume list, material lists, and nuc lists.
Returns
-------
volume : dict of str float
Volumes corresponding to materials in full_burn_dict
nuc_list : list of str
A list of all nuclide names. Used for sorting the simulation.
burn_list : list of int
A list of all material IDs to be burned. Used for sorting the simulation.
full_burn_list : list
List of all burnable material IDs
"""
nuc_list = self.number.burnable_nuclides
burn_list = self.local_mats
volume = {}
for i, mat in enumerate(burn_list):
volume[mat] = self.number.volume[i]
# Combine volume dictionaries across processes
volume_list = comm.allgather(volume)
volume = {k: v for d in volume_list for k, v in d.items()}
return volume, nuc_list, burn_list, self.burnable_mats

View file

@ -0,0 +1,150 @@
"""ReactionRates module.
An ndarray to store reaction rates with string, integer, or slice indexing.
"""
from collections import OrderedDict
import numpy as np
class ReactionRates(np.ndarray):
"""Reaction rates resulting from a transport operator call
This class is a subclass of :class:`numpy.ndarray` with a few custom
attributes that make it easy to determine what index corresponds to a given
material, nuclide, and reaction rate.
Parameters
----------
local_mats : list of str
Material IDs
nuclides : list of str
Depletable nuclides
reactions : list of str
Transmutation reactions being tracked
from_results : boolean
If the reaction rates are loaded from results, indexing dictionnaries
need to be kept the same.
Attributes
----------
index_mat : OrderedDict of str to int
A dictionary mapping material ID as string to index.
index_nuc : OrderedDict of str to int
A dictionary mapping nuclide name as string to index.
index_rx : OrderedDict of str to int
A dictionary mapping reaction name as string to index.
n_mat : int
Number of materials.
n_nuc : int
Number of nucs.
n_react : int
Number of reactions.
"""
# NumPy arrays can be created 1) explicitly 2) using view casting, and 3) by
# slicing an existing array. Because of these possibilities, it's necessary
# to put initialization logic in __new__ rather than __init__. Additionally,
# subclasses need to handle the multiple ways of creating arrays by using
# the __array_finalize__ method (discussed here:
# https://docs.scipy.org/doc/numpy/user/basics.subclassing.html)
def __new__(cls, local_mats, nuclides, reactions, from_results=False):
# Create appropriately-sized zeroed-out ndarray
shape = (len(local_mats), len(nuclides), len(reactions))
obj = super().__new__(cls, shape)
obj[:] = 0.0
# Add mapping attributes, keep same indexing if from depletion_results
if from_results:
obj.index_mat = local_mats
obj.index_nuc = nuclides
obj.index_rx = reactions
# Else, assumes that reaction rates are ordered the same way as
# the lists of local_mats, nuclides and reactions (or keys if these
# are dictionnaries)
else:
obj.index_mat = {mat: i for i, mat in enumerate(local_mats)}
obj.index_nuc = {nuc: i for i, nuc in enumerate(nuclides)}
obj.index_rx = {rx: i for i, rx in enumerate(reactions)}
return obj
def __array_finalize__(self, obj):
if obj is None:
return
self.index_mat = getattr(obj, 'index_mat', None)
self.index_nuc = getattr(obj, 'index_nuc', None)
self.index_rx = getattr(obj, 'index_rx', None)
# Reaction rates are distributed to other processes via multiprocessing,
# which entails pickling the objects. In order to preserve the custom
# attributes, we have to modify how the ndarray is pickled as described
# here: https://stackoverflow.com/a/26599346/1572453
def __reduce__(self):
state = super().__reduce__()
new_state = state[2] + (self.index_mat, self.index_nuc, self.index_rx)
return (state[0], state[1], new_state)
def __setstate__(self, state):
self.index_mat = state[-3]
self.index_nuc = state[-2]
self.index_rx = state[-1]
super().__setstate__(state[0:-3])
@property
def n_mat(self):
return len(self.index_mat)
@property
def n_nuc(self):
return len(self.index_nuc)
@property
def n_react(self):
return len(self.index_rx)
def get(self, mat, nuc, rx):
"""Get reaction rate by material/nuclide/reaction
Parameters
----------
mat : str
Material ID as a string
nuc : str
Nuclide name
rx : str
Name of the reaction
Returns
-------
float
Reaction rate corresponding to given material, nuclide, and reaction
"""
mat = self.index_mat[mat]
nuc = self.index_nuc[nuc]
rx = self.index_rx[rx]
return self[mat, nuc, rx]
def set(self, mat, nuc, rx, value):
"""Set reaction rate by material/nuclide/reaction
Parameters
----------
mat : str
Material ID as a string
nuc : str
Nuclide name
rx : str
Name of the reaction
value : float
Corresponding reaction rate to set
"""
mat = self.index_mat[mat]
nuc = self.index_nuc[nuc]
rx = self.index_rx[rx]
self[mat, nuc, rx] = value

440
openmc/deplete/results.py Normal file
View file

@ -0,0 +1,440 @@
"""The results module.
Contains results generation and saving capabilities.
"""
from collections import OrderedDict
import copy
from warnings import warn
import numpy as np
import h5py
from . import comm, have_mpi
from .reaction_rates import ReactionRates
_VERSION_RESULTS = (1, 0)
class Results(object):
"""Output of a depletion run
Attributes
----------
k : list of float
Eigenvalue for each substep.
time : list of float
Time at beginning, end of step, in seconds.
power : float
Power during time step, in Watts
n_mat : int
Number of mats.
n_nuc : int
Number of nuclides.
rates : list of ReactionRates
The reaction rates for each substep.
volume : OrderedDict of int to float
Dictionary mapping mat id to volume.
mat_to_ind : OrderedDict of str to int
A dictionary mapping mat ID as string to index.
nuc_to_ind : OrderedDict of str to int
A dictionary mapping nuclide name as string to index.
mat_to_hdf5_ind : OrderedDict of str to int
A dictionary mapping mat ID as string to global index.
n_hdf5_mats : int
Number of materials in entire geometry.
n_stages : int
Number of stages in simulation.
data : numpy.ndarray
Atom quantity, stored by stage, mat, then by nuclide.
"""
def __init__(self):
self.k = None
self.time = None
self.power = None
self.rates = None
self.volume = None
self.mat_to_ind = None
self.nuc_to_ind = None
self.mat_to_hdf5_ind = None
self.data = None
def __getitem__(self, pos):
"""Retrieves an item from results.
Parameters
----------
pos : tuple
A three-length tuple containing a stage index, mat index and a nuc
index. All can be integers or slices. The second two can be
strings corresponding to their respective dictionary.
Returns
-------
float
The atoms for stage, mat, nuc
"""
stage, mat, nuc = pos
if isinstance(mat, str):
mat = self.mat_to_ind[mat]
if isinstance(nuc, str):
nuc = self.nuc_to_ind[nuc]
return self.data[stage, mat, nuc]
def __setitem__(self, pos, val):
"""Sets an item from results.
Parameters
----------
pos : tuple
A three-length tuple containing a stage index, mat index and a nuc
index. All can be integers or slices. The second two can be
strings corresponding to their respective dictionary.
val : float
The value to set data to.
"""
stage, mat, nuc = pos
if isinstance(mat, str):
mat = self.mat_to_ind[mat]
if isinstance(nuc, str):
nuc = self.nuc_to_ind[nuc]
self.data[stage, mat, nuc] = val
@property
def n_mat(self):
return len(self.mat_to_ind)
@property
def n_nuc(self):
return len(self.nuc_to_ind)
@property
def n_hdf5_mats(self):
return len(self.mat_to_hdf5_ind)
@property
def n_stages(self):
return self.data.shape[0]
def allocate(self, volume, nuc_list, burn_list, full_burn_list, stages):
"""Allocates memory of Results.
Parameters
----------
volume : dict of str float
Volumes corresponding to materials in full_burn_dict
nuc_list : list of str
A list of all nuclide names. Used for sorting the simulation.
burn_list : list of int
A list of all mat IDs to be burned. Used for sorting the simulation.
full_burn_list : list of str
List of all burnable material IDs
stages : int
Number of stages in simulation.
"""
self.volume = copy.deepcopy(volume)
self.nuc_to_ind = {nuc: i for i, nuc in enumerate(nuc_list)}
self.mat_to_ind = {mat: i for i, mat in enumerate(burn_list)}
self.mat_to_hdf5_ind = {mat: i for i, mat in enumerate(full_burn_list)}
# Create storage array
self.data = np.zeros((stages, self.n_mat, self.n_nuc))
def export_to_hdf5(self, filename, step):
"""Export results to an HDF5 file
Parameters
----------
filename : str
The filename to write to
step : int
What step is this?
"""
if have_mpi and h5py.get_config().mpi:
kwargs = {'driver': 'mpio', 'comm': comm}
else:
kwargs = {}
# Write new file if first time step, else add to existing file
kwargs['mode'] = "w" if step == 0 else "a"
with h5py.File(filename, **kwargs) as handle:
self._to_hdf5(handle, step)
def _write_hdf5_metadata(self, handle):
"""Writes result metadata in HDF5 file
Parameters
----------
handle : h5py.File or h5py.Group
An hdf5 file or group type to store this in.
"""
# Create and save the 5 dictionaries:
# quantities
# self.mat_to_ind -> self.volume (TODO: support for changing volumes)
# self.nuc_to_ind
# reactions
# self.rates[0].nuc_to_ind (can be different from above, above is superset)
# self.rates[0].react_to_ind
# these are shared by every step of the simulation, and should be deduplicated.
# Store concentration mat and nuclide dictionaries (along with volumes)
handle.attrs['version'] = np.array(_VERSION_RESULTS)
handle.attrs['filetype'] = np.string_('depletion results')
mat_list = sorted(self.mat_to_hdf5_ind, key=int)
nuc_list = sorted(self.nuc_to_ind)
rxn_list = sorted(self.rates[0].index_rx)
n_mats = self.n_hdf5_mats
n_nuc_number = len(nuc_list)
n_nuc_rxn = len(self.rates[0].index_nuc)
n_rxn = len(rxn_list)
n_stages = self.n_stages
mat_group = handle.create_group("materials")
for mat in mat_list:
mat_single_group = mat_group.create_group(mat)
mat_single_group.attrs["index"] = self.mat_to_hdf5_ind[mat]
mat_single_group.attrs["volume"] = self.volume[mat]
nuc_group = handle.create_group("nuclides")
for nuc in nuc_list:
nuc_single_group = nuc_group.create_group(nuc)
nuc_single_group.attrs["atom number index"] = self.nuc_to_ind[nuc]
if nuc in self.rates[0].index_nuc:
nuc_single_group.attrs["reaction rate index"] = self.rates[0].index_nuc[nuc]
rxn_group = handle.create_group("reactions")
for rxn in rxn_list:
rxn_single_group = rxn_group.create_group(rxn)
rxn_single_group.attrs["index"] = self.rates[0].index_rx[rxn]
# Construct array storage
handle.create_dataset("number", (1, n_stages, n_mats, n_nuc_number),
maxshape=(None, n_stages, n_mats, n_nuc_number),
chunks=(1, 1, n_mats, n_nuc_number),
dtype='float64')
handle.create_dataset("reaction rates", (1, n_stages, n_mats, n_nuc_rxn, n_rxn),
maxshape=(None, n_stages, n_mats, n_nuc_rxn, n_rxn),
chunks=(1, 1, n_mats, n_nuc_rxn, n_rxn),
dtype='float64')
handle.create_dataset("eigenvalues", (1, n_stages),
maxshape=(None, n_stages), dtype='float64')
handle.create_dataset("time", (1, 2), maxshape=(None, 2), dtype='float64')
handle.create_dataset("power", (1, n_stages), maxshape=(None, n_stages),
dtype='float64')
def _to_hdf5(self, handle, index):
"""Converts results object into an hdf5 object.
Parameters
----------
handle : h5py.File or h5py.Group
An HDF5 file or group type to store this in.
index : int
What step is this?
"""
if "/number" not in handle:
comm.barrier()
self._write_hdf5_metadata(handle)
comm.barrier()
# Grab handles
number_dset = handle["/number"]
rxn_dset = handle["/reaction rates"]
eigenvalues_dset = handle["/eigenvalues"]
time_dset = handle["/time"]
power_dset = handle["/power"]
# Get number of results stored
number_shape = list(number_dset.shape)
number_results = number_shape[0]
new_shape = index + 1
if number_results < new_shape:
# Extend first dimension by 1
number_shape[0] = new_shape
number_dset.resize(number_shape)
rxn_shape = list(rxn_dset.shape)
rxn_shape[0] = new_shape
rxn_dset.resize(rxn_shape)
eigenvalues_shape = list(eigenvalues_dset.shape)
eigenvalues_shape[0] = new_shape
eigenvalues_dset.resize(eigenvalues_shape)
time_shape = list(time_dset.shape)
time_shape[0] = new_shape
time_dset.resize(time_shape)
power_shape = list(power_dset.shape)
power_shape[0] = new_shape
power_dset.resize(power_shape)
# If nothing to write, just return
if len(self.mat_to_ind) == 0:
return
# Add data
# Note, for the last step, self.n_stages = 1, even if n_stages != 1.
n_stages = self.n_stages
inds = [self.mat_to_hdf5_ind[mat] for mat in self.mat_to_ind]
low = min(inds)
high = max(inds)
for i in range(n_stages):
number_dset[index, i, low:high+1, :] = self.data[i, :, :]
rxn_dset[index, i, low:high+1, :, :] = self.rates[i][:, :, :]
if comm.rank == 0:
eigenvalues_dset[index, i] = self.k[i]
if comm.rank == 0:
time_dset[index, :] = self.time
power_dset[index, :] = self.power
@classmethod
def from_hdf5(cls, handle, step):
"""Loads results object from HDF5.
Parameters
----------
handle : h5py.File or h5py.Group
An HDF5 file or group type to load from.
step : int
What step is this?
"""
results = cls()
# Grab handles
number_dset = handle["/number"]
eigenvalues_dset = handle["/eigenvalues"]
time_dset = handle["/time"]
power_dset = handle["/power"]
results.data = number_dset[step, :, :, :]
results.k = eigenvalues_dset[step, :]
results.time = time_dset[step, :]
results.power = power_dset[step, :]
# Reconstruct dictionaries
results.volume = OrderedDict()
results.mat_to_ind = OrderedDict()
results.nuc_to_ind = OrderedDict()
rxn_nuc_to_ind = OrderedDict()
rxn_to_ind = OrderedDict()
for mat, mat_handle in handle["/materials"].items():
vol = mat_handle.attrs["volume"]
ind = mat_handle.attrs["index"]
results.volume[mat] = vol
results.mat_to_ind[mat] = ind
for nuc, nuc_handle in handle["/nuclides"].items():
ind_atom = nuc_handle.attrs["atom number index"]
results.nuc_to_ind[nuc] = ind_atom
if "reaction rate index" in nuc_handle.attrs:
rxn_nuc_to_ind[nuc] = nuc_handle.attrs["reaction rate index"]
for rxn, rxn_handle in handle["/reactions"].items():
rxn_to_ind[rxn] = rxn_handle.attrs["index"]
results.rates = []
# Reconstruct reactions
for i in range(results.n_stages):
rate = ReactionRates(results.mat_to_ind, rxn_nuc_to_ind, rxn_to_ind, True)
rate[:] = handle["/reaction rates"][step, i, :, :, :]
results.rates.append(rate)
return results
@staticmethod
def save(op, x, op_results, t, power, step_ind):
"""Creates and writes depletion results to disk
Parameters
----------
op : openmc.deplete.TransportOperator
The operator used to generate these results.
x : list of list of numpy.array
The prior x vectors. Indexed [i][cell] using the above equation.
op_results : list of openmc.deplete.OperatorResult
Results of applying transport operator
t : list of float
Time indices.
power : float
Power during time step
step_ind : int
Step index.
"""
# Get indexing terms
vol_dict, nuc_list, burn_list, full_burn_list = op.get_results_info()
# For a restart calculation, limit number of stages saved to meet the
# format of the hdf5 file
stages = len(x)
offset = 0
if op.prev_res is not None and op.prev_res[0].n_stages < stages:
offset = stages - op.prev_res[0].n_stages
stages = min(stages, op.prev_res[0].n_stages)
warn("Number of restart integrator stages saved limited by initial"
" depletion integrator choice to {}"
.format(op.prev_res[0].n_stages))
# Create results
results = Results()
results.allocate(vol_dict, nuc_list, burn_list, full_burn_list, stages)
n_mat = len(burn_list)
for i in range(stages):
for mat_i in range(n_mat):
results[i, mat_i, :] = x[offset + i][mat_i][:]
results.k = [r.k for r in op_results]
results.rates = [r.rates for r in op_results]
results.time = t
results.power = power
results.export_to_hdf5("depletion_results.h5", step_ind)
def transfer_volumes(self, geometry):
"""Transfers volumes from depletion results to geometry
Parameters
----------
geometry : OpenMC geometry to be used in a depletion restart
calculation
"""
for cell in geometry.get_all_material_cells().values():
for material in cell.get_all_materials().values():
if material.depletable:
material.volume = self.volume[str(material.id)]

View file

@ -0,0 +1,105 @@
import h5py
import numpy as np
from .results import Results, _VERSION_RESULTS
from openmc.checkvalue import check_filetype_version
class ResultsList(list):
"""A list of openmc.deplete.Results objects
Parameters
----------
filename : str
The filename to read from.
"""
def __init__(self, filename):
super().__init__()
with h5py.File(str(filename), "r") as fh:
check_filetype_version(fh, 'depletion results', _VERSION_RESULTS[0])
# Get number of results stored
n = fh["number"].value.shape[0]
for i in range(n):
self.append(Results.from_hdf5(fh, i))
def get_atoms(self, mat, nuc):
"""Get nuclide concentration over time from a single material
Parameters
----------
mat : str
Material name to evaluate
nuc : str
Nuclide name to evaluate
Returns
-------
time : numpy.ndarray
Array of times in [s]
concentration : numpy.ndarray
Total number of atoms for specified nuclide
"""
time = np.empty_like(self, dtype=float)
concentration = np.empty_like(self, dtype=float)
# Evaluate value in each region
for i, result in enumerate(self):
time[i] = result.time[0]
concentration[i] = result[0, mat, nuc]
return time, concentration
def get_reaction_rate(self, mat, nuc, rx):
"""Get reaction rate in a single material/nuclide over time
Parameters
----------
mat : str
Material name to evaluate
nuc : str
Nuclide name to evaluate
rx : str
Reaction rate to evaluate
Returns
-------
time : numpy.ndarray
Array of times in [s]
rate : numpy.ndarray
Array of reaction rates
"""
time = np.empty_like(self, dtype=float)
rate = np.empty_like(self, dtype=float)
# Evaluate value in each region
for i, result in enumerate(self):
time[i] = result.time[0]
rate[i] = result.rates[0].get(mat, nuc, rx) * result[0, mat, nuc]
return time, rate
def get_eigenvalue(self):
"""Evaluates the eigenvalue from a results list.
Returns
-------
time : numpy.ndarray
Array of times in [s]
eigenvalue : numpy.ndarray
k-eigenvalue at each time
"""
time = np.empty_like(self, dtype=float)
eigenvalue = np.empty_like(self, dtype=float)
# Get time/eigenvalue at each point
for i, result in enumerate(self):
time[i] = result.time[0]
eigenvalue[i] = result.k[0]
return time, eigenvalue

38
openmc/exceptions.py Normal file
View file

@ -0,0 +1,38 @@
class OpenMCError(Exception):
"""Root exception class for OpenMC."""
class GeometryError(OpenMCError):
"""Geometry-related error"""
class InvalidIDError(OpenMCError):
"""Use of an ID that is invalid."""
class AllocationError(OpenMCError):
"""Error related to memory allocation."""
class OutOfBoundsError(OpenMCError):
"""Index in array out of bounds."""
class DataError(OpenMCError):
"""Error relating to nuclear data."""
class PhysicsError(OpenMCError):
"""Error relating to performing physics."""
class InvalidArgumentError(OpenMCError):
"""Argument passed was invalid."""
class InvalidTypeError(OpenMCError):
"""Tried to perform an operation on the wrong type."""
class SetupError(OpenMCError):
"""Error while setting up a problem."""

File diff suppressed because it is too large Load diff

465
openmc/filter_expansion.py Normal file
View file

@ -0,0 +1,465 @@
from numbers import Integral, Real
from xml.etree import ElementTree as ET
import numpy as np
import pandas as pd
import openmc.checkvalue as cv
from . import Filter
class ExpansionFilter(Filter):
"""Abstract filter class for functional expansions."""
def __init__(self, order, filter_id=None):
self.order = order
self.id = filter_id
def __eq__(self, other):
if type(self) is not type(other):
return False
else:
return self.bins == other.bins
@property
def order(self):
return self._order
@order.setter
def order(self, order):
cv.check_type('expansion order', order, Integral)
cv.check_greater_than('expansion order', order, 0, equality=True)
self._order = order
def to_xml_element(self):
"""Return XML Element representing the filter.
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing Legendre filter data
"""
element = ET.Element('filter')
element.set('id', str(self.id))
element.set('type', self.short_name.lower())
subelement = ET.SubElement(element, 'order')
subelement.text = str(self.order)
return element
class LegendreFilter(ExpansionFilter):
r"""Score Legendre expansion moments up to specified order.
This filter allows scores to be multiplied by Legendre polynomials of the
change in particle angle (:math:`\mu`) up to a user-specified order.
Parameters
----------
order : int
Maximum Legendre polynomial order
filter_id : int or None
Unique identifier for the filter
Attributes
----------
order : int
Maximum Legendre polynomial order
id : int
Unique identifier for the filter
num_bins : int
The number of filter bins
"""
def __hash__(self):
string = type(self).__name__ + '\n'
string += '{: <16}=\t{}\n'.format('\tOrder', self.order)
return hash(string)
def __repr__(self):
string = type(self).__name__ + '\n'
string += '{: <16}=\t{}\n'.format('\tOrder', self.order)
string += '{: <16}=\t{}\n'.format('\tID', self.id)
return string
@ExpansionFilter.order.setter
def order(self, order):
ExpansionFilter.order.__set__(self, order)
self.bins = ['P{}'.format(i) for i in range(order + 1)]
@classmethod
def from_hdf5(cls, group, **kwargs):
if group['type'].value.decode() != cls.short_name.lower():
raise ValueError("Expected HDF5 data for filter type '"
+ cls.short_name.lower() + "' but got '"
+ group['type'].value.decode() + " instead")
filter_id = int(group.name.split('/')[-1].lstrip('filter '))
out = cls(group['order'].value, filter_id)
return out
class SpatialLegendreFilter(ExpansionFilter):
r"""Score Legendre expansion moments in space up to specified order.
This filter allows scores to be multiplied by Legendre polynomials of the
the particle's position along a particular axis, normalized to a given
range, up to a user-specified order.
Parameters
----------
order : int
Maximum Legendre polynomial order
axis : {'x', 'y', 'z'}
Axis along which to take the expansion
minimum : float
Minimum value along selected axis
maximum : float
Maximum value along selected axis
filter_id : int or None
Unique identifier for the filter
Attributes
----------
order : int
Maximum Legendre polynomial order
axis : {'x', 'y', 'z'}
Axis along which to take the expansion
minimum : float
Minimum value along selected axis
maximum : float
Maximum value along selected axis
id : int
Unique identifier for the filter
num_bins : int
The number of filter bins
"""
def __init__(self, order, axis, minimum, maximum, filter_id=None):
super().__init__(order, filter_id)
self.axis = axis
self.minimum = minimum
self.maximum = maximum
def __hash__(self):
string = type(self).__name__ + '\n'
string += '{: <16}=\t{}\n'.format('\tOrder', self.order)
string += '{: <16}=\t{}\n'.format('\tAxis', self.axis)
string += '{: <16}=\t{}\n'.format('\tMin', self.minimum)
string += '{: <16}=\t{}\n'.format('\tMax', self.maximum)
return hash(string)
def __repr__(self):
string = type(self).__name__ + '\n'
string += '{: <16}=\t{}\n'.format('\tOrder', self.order)
string += '{: <16}=\t{}\n'.format('\tAxis', self.axis)
string += '{: <16}=\t{}\n'.format('\tMin', self.minimum)
string += '{: <16}=\t{}\n'.format('\tMax', self.maximum)
string += '{: <16}=\t{}\n'.format('\tID', self.id)
return string
@ExpansionFilter.order.setter
def order(self, order):
ExpansionFilter.order.__set__(self, order)
self.bins = ['P{}'.format(i) for i in range(order + 1)]
@property
def axis(self):
return self._axis
@axis.setter
def axis(self, axis):
cv.check_value('axis', axis, ('x', 'y', 'z'))
self._axis = axis
@property
def minimum(self):
return self._minimum
@minimum.setter
def minimum(self, minimum):
cv.check_type('minimum', minimum, Real)
self._minimum = minimum
@property
def maximum(self):
return self._maximum
@maximum.setter
def maximum(self, maximum):
cv.check_type('maximum', maximum, Real)
self._maximum = maximum
@classmethod
def from_hdf5(cls, group, **kwargs):
if group['type'].value.decode() != cls.short_name.lower():
raise ValueError("Expected HDF5 data for filter type '"
+ cls.short_name.lower() + "' but got '"
+ group['type'].value.decode() + " instead")
filter_id = int(group.name.split('/')[-1].lstrip('filter '))
order = group['order'].value
axis = group['axis'].value.decode()
min_, max_ = group['min'].value, group['max'].value
return cls(order, axis, min_, max_, filter_id)
def to_xml_element(self):
"""Return XML Element representing the filter.
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing Legendre filter data
"""
element = super().to_xml_element()
subelement = ET.SubElement(element, 'axis')
subelement.text = self.axis
subelement = ET.SubElement(element, 'min')
subelement.text = str(self.minimum)
subelement = ET.SubElement(element, 'max')
subelement.text = str(self.maximum)
return element
class SphericalHarmonicsFilter(ExpansionFilter):
r"""Score spherical harmonic expansion moments up to specified order.
This filter allows you to obtain real spherical harmonic moments of either
the particle's direction or the cosine of the scattering angle. Specifying a
filter with order :math:`\ell` tallies moments for all orders from 0 to
:math:`\ell`.
Parameters
----------
order : int
Maximum spherical harmonics order, :math:`\ell`
filter_id : int or None
Unique identifier for the filter
Attributes
----------
order : int
Maximum spherical harmonics order, :math:`\ell`
id : int
Unique identifier for the filter
cosine : {'scatter', 'particle'}
How to handle the cosine term.
num_bins : int
The number of filter bins
"""
def __init__(self, order, filter_id=None):
super().__init__(order, filter_id)
self._cosine = 'particle'
def __hash__(self):
string = type(self).__name__ + '\n'
string += '{: <16}=\t{}\n'.format('\tOrder', self.order)
string += '{: <16}=\t{}\n'.format('\tCosine', self.cosine)
return hash(string)
def __repr__(self):
string = type(self).__name__ + '\n'
string += '{: <16}=\t{}\n'.format('\tOrder', self.order)
string += '{: <16}=\t{}\n'.format('\tCosine', self.cosine)
string += '{: <16}=\t{}\n'.format('\tID', self.id)
return string
@ExpansionFilter.order.setter
def order(self, order):
ExpansionFilter.order.__set__(self, order)
self.bins = ['Y{},{}'.format(n, m)
for n in range(order + 1)
for m in range(-n, n + 1)]
@property
def cosine(self):
return self._cosine
@cosine.setter
def cosine(self, cosine):
cv.check_value('Spherical harmonics cosine treatment', cosine,
('scatter', 'particle'))
self._cosine = cosine
@classmethod
def from_hdf5(cls, group, **kwargs):
if group['type'].value.decode() != cls.short_name.lower():
raise ValueError("Expected HDF5 data for filter type '"
+ cls.short_name.lower() + "' but got '"
+ group['type'].value.decode() + " instead")
filter_id = int(group.name.split('/')[-1].lstrip('filter '))
out = cls(group['order'].value, filter_id)
out.cosine = group['cosine'].value.decode()
return out
def to_xml_element(self):
"""Return XML Element representing the filter.
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing spherical harmonics filter data
"""
element = super().to_xml_element()
element.set('cosine', self.cosine)
return element
class ZernikeFilter(ExpansionFilter):
r"""Score Zernike expansion moments in space up to specified order.
This filter allows scores to be multiplied by Zernike polynomials of the
particle's position normalized to a given unit circle, up to a
user-specified order. The standard Zernike polynomials follow the definition by
Born and Wolf, *Principles of Optics* and are defined as
.. math::
Z_n^m(\rho, \theta) = R_n^m(\rho) \cos (m\theta), \quad m > 0
Z_n^{m}(\rho, \theta) = R_n^{m}(\rho) \sin (m\theta), \quad m < 0
Z_n^{m}(\rho, \theta) = R_n^{m}(\rho), \quad m = 0
where the radial polynomials are
.. math::
R_n^m(\rho) = \sum\limits_{k=0}^{(n-m)/2} \frac{(-1)^k (n-k)!}{k! (
\frac{n+m}{2} - k)! (\frac{n-m}{2} - k)!} \rho^{n-2k}.
With this definition, the integral of :math:`(Z_n^m)^2` over the unit disk
is :math:`\frac{\epsilon_m\pi}{2n+2}` for each polynomial where :math:`\epsilon_m` is
2 if :math:`m` equals 0 and 1 otherwise.
Specifying a filter with order N tallies moments for all :math:`n` from 0 to
N and each value of :math:`m`. The ordering of the Zernike polynomial
moments follows the ANSI Z80.28 standard, where the one-dimensional index
:math:`j` corresponds to the :math:`n` and :math:`m` by
.. math::
j = \frac{n(n + 2) + m}{2}.
Parameters
----------
order : int
Maximum Zernike polynomial order
x : float
x-coordinate of center of circle for normalization
y : float
y-coordinate of center of circle for normalization
r : int or None
Radius of circle for normalization
Attributes
----------
order : int
Maximum Zernike polynomial order
x : float
x-coordinate of center of circle for normalization
y : float
y-coordinate of center of circle for normalization
r : int or None
Radius of circle for normalization
id : int
Unique identifier for the filter
num_bins : int
The number of filter bins
"""
def __init__(self, order, x=0.0, y=0.0, r=1.0, filter_id=None):
super().__init__(order, filter_id)
self.x = x
self.y = y
self.r = r
def __hash__(self):
string = type(self).__name__ + '\n'
string += '{: <16}=\t{}\n'.format('\tOrder', self.order)
return hash(string)
def __repr__(self):
string = type(self).__name__ + '\n'
string += '{: <16}=\t{}\n'.format('\tOrder', self.order)
string += '{: <16}=\t{}\n'.format('\tID', self.id)
return string
@ExpansionFilter.order.setter
def order(self, order):
ExpansionFilter.order.__set__(self, order)
self.bins = ['Z{},{}'.format(n, m)
for n in range(order + 1)
for m in range(-n, n + 1, 2)]
@property
def x(self):
return self._x
@x.setter
def x(self, x):
cv.check_type('x', x, Real)
self._x = x
@property
def y(self):
return self._y
@y.setter
def y(self, y):
cv.check_type('y', y, Real)
self._y = y
@property
def r(self):
return self._r
@r.setter
def r(self, r):
cv.check_type('r', r, Real)
self._r = r
@classmethod
def from_hdf5(cls, group, **kwargs):
if group['type'].value.decode() != cls.short_name.lower():
raise ValueError("Expected HDF5 data for filter type '"
+ cls.short_name.lower() + "' but got '"
+ group['type'].value.decode() + " instead")
filter_id = int(group.name.split('/')[-1].lstrip('filter '))
order = group['order'].value
x, y, r = group['x'].value, group['y'].value, group['r'].value
return cls(order, x, y, r, filter_id)
def to_xml_element(self):
"""Return XML Element representing the filter.
Returns
-------
element : xml.etree.ElementTree.Element
XML element containing Zernike filter data
"""
element = super().to_xml_element()
subelement = ET.SubElement(element, 'x')
subelement.text = str(self.x)
subelement = ET.SubElement(element, 'y')
subelement.text = str(self.y)
subelement = ET.SubElement(element, 'r')
subelement.text = str(self.r)
return element

View file

@ -546,10 +546,15 @@ class RectLattice(Lattice):
"""
if self.ndim == 2:
nx, ny = self.shape
return np.broadcast(*np.ogrid[:nx, :ny])
for iy in range(ny):
for ix in range(nx):
yield (ix, iy)
else:
nx, ny, nz = self.shape
return np.broadcast(*np.ogrid[:nx, :ny, :nz])
for iz in range(nz):
for iy in range(ny):
for ix in range(nx):
yield (ix, iy, iz)
@property
def lower_left(self):

View file

@ -24,7 +24,7 @@ class Material(IDManagerMixin):
To create a material, one should create an instance of this class, add
nuclides or elements with :meth:`Material.add_nuclide` or
`Material.add_element`, respectively, and set the total material density
with `Material.export_to_xml()`. The material can then be assigned to a cell
with `Material.set_density()`. The material can then be assigned to a cell
using the :attr:`Cell.fill` attribute.
Parameters
@ -52,8 +52,7 @@ class Material(IDManagerMixin):
'atom/b-cm', 'atom/cm3', 'sum', or 'macro'. The 'macro' unit only
applies in the case of a multi-group calculation.
depletable : bool
Indicate whether the material is depletable. This attribute can be used
by downstream depletion applications.
Indicate whether the material is depletable.
nuclides : list of tuple
List in which each item is a 3-tuple consisting of a nuclide string, the
percent density, and the percent type ('ao' or 'wo').
@ -74,6 +73,9 @@ class Material(IDManagerMixin):
:meth:`Geometry.determine_paths` method.
num_instances : int
The number of instances of this material throughout the geometry.
fissionable_mass : float
Mass of fissionable nuclides in the material in [g]. Requires that the
:attr:`volume` attribute is set.
"""
@ -86,7 +88,7 @@ class Material(IDManagerMixin):
self.name = name
self.temperature = temperature
self._density = None
self._density_units = ''
self._density_units = 'sum'
self._depletable = False
self._paths = None
self._num_instances = None
@ -245,6 +247,18 @@ class Material(IDManagerMixin):
str)
self._isotropic = list(isotropic)
@property
def fissionable_mass(self):
if self.volume is None:
raise ValueError("Volume must be set in order to determine mass.")
density = 0.0
for nuc, atoms_per_cc in self.get_nuclide_atom_densities().values():
Z = openmc.data.zam(nuc)[0]
if Z >= 90:
density += 1e24 * atoms_per_cc * openmc.data.atomic_mass(nuc) \
/ openmc.data.AVOGADRO
return density*self.volume
@classmethod
def from_hdf5(cls, group):
"""Create material from HDF5 group
@ -264,8 +278,8 @@ class Material(IDManagerMixin):
name = group['name'].value.decode() if 'name' in group else ''
density = group['atom_density'].value
nuc_densities = group['nuclide_densities'][...]
nuclides = group['nuclides'].value
if 'nuclide_densities' in group:
nuc_densities = group['nuclide_densities'][...]
# Create the Material
material = cls(mat_id, name)
@ -281,10 +295,18 @@ class Material(IDManagerMixin):
# Set the Material's density to atom/b-cm as used by OpenMC
material.set_density(density=density, units='atom/b-cm')
# Add all nuclides to the Material
for fullname, density in zip(nuclides, nuc_densities):
name = fullname.decode().strip()
material.add_nuclide(name, percent=density, percent_type='ao')
if 'nuclides' in group:
nuclides = group['nuclides'].value
# Add all nuclides to the Material
for fullname, density in zip(nuclides, nuc_densities):
name = fullname.decode().strip()
material.add_nuclide(name, percent=density, percent_type='ao')
if 'macroscopics' in group:
macroscopics = group['macroscopics'].value
# Add all macroscopics to the Material
for fullname in macroscopics:
name = fullname.decode().strip()
material.add_macroscopic(name)
return material
@ -299,7 +321,7 @@ class Material(IDManagerMixin):
"""
if volume_calc.domain_type == 'material':
if self.id in volume_calc.volumes:
self._volume = volume_calc.volumes[self.id][0]
self._volume = volume_calc.volumes[self.id].n
self._atoms = volume_calc.atoms[self.id]
else:
raise ValueError('No volume information found for this material.')
@ -365,33 +387,31 @@ class Material(IDManagerMixin):
Parameters
----------
nuclide : str
Nuclide to add
Nuclide to add, e.g., 'Mo95'
percent : float
Atom or weight percent
percent_type : {'ao', 'wo'}
'ao' for atom percent and 'wo' for weight percent
"""
cv.check_type('nuclide', nuclide, str)
cv.check_type('percent', percent, Real)
cv.check_value('percent type', percent_type, {'ao', 'wo'})
if self._macroscopic is not None:
msg = 'Unable to add a Nuclide to Material ID="{}" as a ' \
'macroscopic data-set has already been added'.format(self._id)
raise ValueError(msg)
if not isinstance(nuclide, str):
msg = 'Unable to add a Nuclide to Material ID="{}" with a ' \
'non-string value "{}"'.format(self._id, nuclide)
raise ValueError(msg)
elif not isinstance(percent, Real):
msg = 'Unable to add a Nuclide to Material ID="{}" with a ' \
'non-floating point value "{}"'.format(self._id, percent)
raise ValueError(msg)
elif percent_type not in ('ao', 'wo'):
msg = 'Unable to add a Nuclide to Material ID="{}" with a ' \
'percent type "{}"'.format(self._id, percent_type)
raise ValueError(msg)
# If nuclide name doesn't look valid, give a warning
try:
Z, _, _ = openmc.data.zam(nuclide)
except ValueError as e:
warnings.warn(str(e))
else:
# For actinides, have the material be depletable by default
if Z >= 89:
self.depletable = True
self._nuclides.append((nuclide, percent, percent_type))
@ -447,7 +467,7 @@ class Material(IDManagerMixin):
raise ValueError(msg)
# Generally speaking, the density for a macroscopic object will
# be 1.0. Therefore, lets set density to 1.0 so that the user
# be 1.0. Therefore, lets set density to 1.0 so that the user
# doesnt need to set it unless its needed.
# Of course, if the user has already set a value of density,
# then we will not override it.
@ -479,7 +499,7 @@ class Material(IDManagerMixin):
Parameters
----------
element : str
Element to add
Element to add, e.g., 'Zr'
percent : float
Atom or weight percent
percent_type : {'ao', 'wo'}, optional
@ -491,27 +511,15 @@ class Material(IDManagerMixin):
(natural composition).
"""
cv.check_type('nuclide', element, str)
cv.check_type('percent', percent, Real)
cv.check_value('percent type', percent_type, {'ao', 'wo'})
if self._macroscopic is not None:
msg = 'Unable to add an Element to Material ID="{}" as a ' \
'macroscopic data-set has already been added'.format(self._id)
raise ValueError(msg)
if not isinstance(element, str):
msg = 'Unable to add an Element to Material ID="{}" with a ' \
'non-string value "{}"'.format(self._id, element)
raise ValueError(msg)
if not isinstance(percent, Real):
msg = 'Unable to add an Element to Material ID="{}" with a ' \
'non-floating point value "{}"'.format(self._id, percent)
raise ValueError(msg)
if percent_type not in ['ao', 'wo']:
msg = 'Unable to add an Element to Material ID="{}" with a ' \
'percent type "{}"'.format(self._id, percent_type)
raise ValueError(msg)
if enrichment is not None:
if not isinstance(enrichment, Real):
msg = 'Unable to add an Element to Material ID="{}" with a ' \
@ -537,10 +545,15 @@ class Material(IDManagerMixin):
format(enrichment, self._id)
warnings.warn(msg)
# Make sure element name is just that
if not element.isalpha():
raise ValueError("Element name should be given by the "
"element's symbol, e.g., 'Zr'")
# Add naturally-occuring isotopes
element = openmc.Element(element)
for nuclide in element.expand(percent, percent_type, enrichment):
self._nuclides.append(nuclide)
self.add_nuclide(*nuclide)
def add_s_alpha_beta(self, name, fraction=1.0):
r"""Add an :math:`S(\alpha,\beta)` table to the material
@ -687,6 +700,51 @@ class Material(IDManagerMixin):
return nuclides
def get_mass_density(self, nuclide=None):
"""Return mass density of one or all nuclides
Parameters
----------
nuclides : str, optional
Nuclide for which density is desired. If not specified, the density
for the entire material is given.
Returns
-------
float
Density of the nuclide/material in [g/cm^3]
"""
mass_density = 0.0
for nuc, atoms_per_cc in self.get_nuclide_atom_densities().values():
density_i = 1e24 * atoms_per_cc * openmc.data.atomic_mass(nuc) \
/ openmc.data.AVOGADRO
if nuclide is None or nuclide == nuc:
mass_density += density_i
return mass_density
def get_mass(self, nuclide=None):
"""Return mass of one or all nuclides.
Note that this method requires that the :attr:`Material.volume` has
already been set.
Parameters
----------
nuclides : str, optional
Nuclide for which mass is desired. If not specified, the density
for the entire material is given.
Returns
-------
float
Mass of the nuclide/material in [g]
"""
if self.volume is None:
raise ValueError("Volume must be set in order to determine mass.")
return self.volume*self.get_mass_density(nuclide)
def clone(self, memo=None):
"""Create a copy of this material with a new unique ID.

View file

@ -38,6 +38,9 @@ class Mesh(IDManagerMixin):
are given, it is assumed that the mesh is an x-y mesh.
width : Iterable of float
The width of mesh cells in each direction.
indices : list of tuple
A list of mesh indices for each mesh element, e.g. [(1, 1, 1), (2, 1,
1), ...]
"""
@ -82,6 +85,24 @@ class Mesh(IDManagerMixin):
def num_mesh_cells(self):
return np.prod(self._dimension)
@property
def indices(self):
ndim = len(self._dimension)
if ndim == 3:
nx, ny, nz = self.dimension
return ((x, y, z)
for z in range(1, nz + 1)
for y in range(1, ny + 1)
for x in range(1, nx + 1))
elif ndim == 2:
nx, ny = self.dimension
return ((x, y)
for y in range(1, ny + 1)
for x in range(1, nx + 1))
else:
nx, = self.dimension
return ((x,) for x in range(1, nx + 1))
@name.setter
def name(self, name):
if name is not None:
@ -161,40 +182,39 @@ class Mesh(IDManagerMixin):
return mesh
def cell_generator(self):
"""Generator function to traverse through every [i,j,k] index of the
mesh
@classmethod
def from_rect_lattice(cls, lattice, division=1, mesh_id=None, name=''):
"""Create mesh from an existing rectangular lattice
For example the following code:
.. code-block:: python
for mesh_index in mymesh.cell_generator():
print(mesh_index)
will produce the following output for a 3-D 2x2x2 mesh in mymesh::
[1, 1, 1]
[2, 1, 1]
[1, 2, 1]
[2, 2, 1]
...
Parameters
----------
lattice : openmc.RectLattice
Rectangular lattice used as a template for this mesh
division : int
Number of mesh cells per lattice cell.
If not specified, there will be 1 mesh cell per lattice cell.
mesh_id : int
Unique identifier for the mesh
name : str
Name of the mesh
Returns
-------
openmc.Mesh
Mesh instance
"""
cv.check_type('rectangular lattice', lattice, openmc.RectLattice)
if len(self.dimension) == 1:
for x in range(self.dimension[0]):
yield [x + 1, 1, 1]
elif len(self.dimension) == 2:
for y in range(self.dimension[1]):
for x in range(self.dimension[0]):
yield [x + 1, y + 1, 1]
else:
for z in range(self.dimension[2]):
for y in range(self.dimension[1]):
for x in range(self.dimension[0]):
yield [x + 1, y + 1, z + 1]
shape = np.array(lattice.shape)
width = lattice.pitch*shape
mesh = cls(mesh_id, name)
mesh.lower_left = lattice.lower_left
mesh.upper_right = lattice.lower_left + width
mesh.dimension = shape*division
return mesh
def to_xml_element(self):
"""Return XML representation of the mesh
@ -259,12 +279,14 @@ class Mesh(IDManagerMixin):
cv.check_value('bc', entry, ['transmission', 'vacuum',
'reflective', 'periodic'])
n_dim = len(self.dimension)
# Build the cell which will contain the lattice
xplanes = [openmc.XPlane(x0=self.lower_left[0],
boundary_type=bc[0]),
openmc.XPlane(x0=self.upper_right[0],
boundary_type=bc[1])]
if len(self.dimension) == 1:
if n_dim == 1:
yplanes = [openmc.YPlane(y0=-1e10, boundary_type='reflective'),
openmc.YPlane(y0=1e10, boundary_type='reflective')]
else:
@ -273,7 +295,7 @@ class Mesh(IDManagerMixin):
openmc.YPlane(y0=self.upper_right[1],
boundary_type=bc[3])]
if len(self.dimension) <= 2:
if n_dim <= 2:
# Would prefer to have the z ranges be the max supported float, but
# these values are apparently different between python and Fortran.
# Choosing a safe and sane default.
@ -293,12 +315,12 @@ class Mesh(IDManagerMixin):
(+yplanes[0] & -yplanes[1]) &
(+zplanes[0] & -zplanes[1]))
# Build the universes which will be used for each of the [i,j,k]
# Build the universes which will be used for each of the (i,j,k)
# locations within the mesh.
# We will concurrently build cells to assign to these universes
cells = []
universes = []
for [i, j, k] in self.cell_generator():
for index in self.indices:
cells.append(openmc.Cell())
universes.append(openmc.Universe())
universes[-1].add_cell(cells[-1])
@ -308,7 +330,26 @@ class Mesh(IDManagerMixin):
# Assign the universe and rotate to match the indexing expected for
# the lattice
lattice.universes = np.rot90(np.reshape(universes, self.dimension))
if n_dim == 1:
universe_array = np.array([universes])
elif n_dim == 2:
universe_array = np.empty(self.dimension[::-1],
dtype=openmc.Universe)
i = 0
for y in range(self.dimension[1] - 1, -1, -1):
for x in range(self.dimension[0]):
universe_array[y][x] = universes[i]
i += 1
else:
universe_array = np.empty(self.dimension[::-1],
dtype=openmc.Universe)
i = 0
for z in range(self.dimension[2]):
for y in range(self.dimension[1] - 1, -1, -1):
for x in range(self.dimension[0]):
universe_array[z][y][x] = universes[i]
i += 1
lattice.universes = universe_array
if self.width is not None:
lattice.pitch = self.width
@ -316,9 +357,9 @@ class Mesh(IDManagerMixin):
dx = ((self.upper_right[0] - self.lower_left[0]) /
self.dimension[0])
if len(self.dimension) == 1:
if n_dim == 1:
lattice.pitch = [dx]
elif len(self.dimension) == 2:
elif n_dim == 2:
dy = ((self.upper_right[1] - self.lower_left[1]) /
self.dimension[1])
lattice.pitch = [dx, dy]

View file

@ -587,7 +587,7 @@ class Library(object):
self._nuclides = statepoint.summary.nuclides
if statepoint.run_mode == 'eigenvalue':
self._keff = statepoint.k_combined[0]
self._keff = statepoint.k_combined.n
# Load tallies for each MGXS for each domain and mgxs type
for domain in self.domains:
@ -1220,9 +1220,8 @@ class Library(object):
xs_type = 'macro'
# Initialize file
mgxs_file = openmc.MGXSLibrary(self.energy_groups,
num_delayed_groups=\
self.num_delayed_groups)
mgxs_file = openmc.MGXSLibrary(
self.energy_groups, num_delayed_groups=self.num_delayed_groups)
if self.domain_type == 'mesh':
# Create the xsdata objects and add to the mgxs_file
@ -1231,7 +1230,7 @@ class Library(object):
if self.by_nuclide:
raise NotImplementedError("Mesh domains do not currently "
"support nuclidic tallies")
for subdomain in domain.cell_generator():
for subdomain in domain.indices:
# Build & add metadata to XSdata object
if xsdata_names is None:
xsdata_name = 'set' + str(i + 1)
@ -1346,7 +1345,7 @@ class Library(object):
geometry.root_universe = root
materials = openmc.Materials()
for i, subdomain in enumerate(self.domains[0].cell_generator()):
for i, subdomain in enumerate(self.domains[0].indices):
xsdata = mgxs_file.xsdatas[i]
# Build the macroscopic and assign it to the cell of
@ -1401,24 +1400,16 @@ class Library(object):
The rules to check include:
- Either total or transport should be present.
- Either total or transport must be present.
- Both can be available if one wants, but we should
use whatever corresponds to Library.correction (if P0: transport)
- Absorption and total (or transport) are required.
- Absorption is required.
- A nu-fission cross section and chi values are not required as a
fixed source problem could be the target.
- Fission and kappa-fission are not required as they are only
needed to support tallies the user may wish to request.
- A nu-scatter matrix is required.
- Having a multiplicity matrix is preferred.
- Having both nu-scatter (of any order) and scatter
(at least isotropic) matrices is the second choice.
- If only nu-scatter, need total (not transport), to
be used in adjusting absorption
(i.e., reduced_abs = tot - nuscatt)
See also
--------
@ -1428,36 +1419,51 @@ class Library(object):
"""
error_flag = False
# if correction is 'P0', then transport must be provided
# otherwise total must be provided
if self.correction == 'P0':
if ('transport' not in self.mgxs_types and
'nu-transport' not in self.mgxs_types):
error_flag = True
warn('If the "correction" parameter is "P0", then a '
'"transport" or "nu-transport" MGXS type is required.')
else:
if 'total' not in self.mgxs_types:
error_flag = True
warn('If the "correction" parameter is None, then a '
'"total" MGXS type is required.')
# Check consistency of "nu-transport" and "nu-scatter"
if 'nu-transport' in self.mgxs_types:
if not ('nu-scatter matrix' in self.mgxs_types or
'consistent nu-scatter matrix' in self.mgxs_types):
error_flag = True
warn('If a "nu-transport" MGXS type is used then a '
'"nu-scatter matrix" or "consistent nu-scatter matrix" '
'must also be used.')
elif 'transport' in self.mgxs_types:
if not ('scatter matrix' in self.mgxs_types or
'consistent scatter matrix' in self.mgxs_types):
error_flag = True
warn('If a "transport" MGXS type is used then a '
'"scatter matrix" or "consistent scatter matrix" '
'must also be used.')
# Make sure there is some kind of a scattering matrix data
if 'nu-scatter matrix' not in self.mgxs_types and \
'consistent nu-scatter matrix' not in self.mgxs_types and \
'scatter matrix' not in self.mgxs_types and \
'consistent scatter matrix' not in self.mgxs_types:
error_flag = True
warn('A "nu-scatter matrix", "consistent nu-scatter matrix", '
'"scatter matrix", or "consistent scatter matrix" MGXS '
'type is required.')
# Ensure absorption is present
if 'absorption' not in self.mgxs_types:
error_flag = True
warn('An "absorption" MGXS type is required but not provided.')
# Ensure nu-scattering matrix is required
if 'nu-scatter matrix' not in self.mgxs_types and \
'consistent nu-scatter matrix' not in self.mgxs_types:
error_flag = True
warn('A "nu-scatter matrix" MGXS type is required but not provided.')
else:
# Ok, now see the status of scatter and/or multiplicity
if 'scatter matrix' not in self.mgxs_types or \
'consistent scatter matrix' not in self.mgxs_types and \
'multiplicity matrix' not in self.mgxs_types:
# We dont have data needed for multiplicity matrix, therefore
# we need total, and not transport.
if 'total' not in self.mgxs_types:
error_flag = True
warn('A "total" MGXS type is required if a '
'scattering matrix is not provided.')
# Total or transport can be present, but if using
# self.correction=="P0", then we should use transport.
if self.correction == "P0" and 'nu-transport' not in self.mgxs_types:
error_flag = True
warn('A "nu-transport" MGXS type is required since a "P0" '
'correction is applied, but a "nu-transport" MGXS is '
'not provided.')
elif self.correction is None and 'total' not in self.mgxs_types:
error_flag = True
warn('A "total" MGXS type is required, but not provided.')
if error_flag:
raise ValueError('Invalid MGXS configuration encountered.')

View file

@ -4,7 +4,6 @@ import warnings
import os
import copy
from abc import ABCMeta
import itertools
import numpy as np
import h5py
@ -937,8 +936,7 @@ class MGXS(metaclass=ABCMeta):
# NOTE: This is important if tally merging was used
if self.domain_type == 'mesh':
filters = [_DOMAIN_TO_FILTER[self.domain_type]]
xyz = [range(1, x + 1) for x in self.domain.dimension]
filter_bins = [tuple(itertools.product(*xyz))]
filter_bins = [tuple(self.domain.indices)]
elif self.domain_type != 'distribcell':
filters = [_DOMAIN_TO_FILTER[self.domain_type]]
filter_bins = [(self.domain.id,)]
@ -1166,12 +1164,14 @@ class MGXS(metaclass=ABCMeta):
if not isinstance(tally_filter, (openmc.EnergyFilter,
openmc.EnergyoutFilter)):
continue
elif len(tally_filter.bins) != len(fine_edges):
elif len(tally_filter.bins) != len(fine_edges) - 1:
continue
elif not np.allclose(tally_filter.bins, fine_edges):
elif not np.allclose(tally_filter.bins[:, 0], fine_edges[:-1]):
continue
else:
tally_filter.bins = coarse_groups.group_edges
cedge = coarse_groups.group_edges
tally_filter.values = cedge
tally_filter.bins = np.vstack((cedge[:-1], cedge[1:])).T
mean = np.add.reduceat(mean, energy_indices, axis=i)
std_dev = np.add.reduceat(std_dev**2, energy_indices,
axis=i)
@ -1531,8 +1531,7 @@ class MGXS(metaclass=ABCMeta):
elif self.domain_type == 'distribcell':
subdomains = np.arange(self.num_subdomains, dtype=np.int)
elif self.domain_type == 'mesh':
xyz = [range(1, x + 1) for x in self.domain.dimension]
subdomains = list(itertools.product(*xyz))
subdomains = list(self.domain.indices)
else:
subdomains = [self.domain.id]
@ -1702,8 +1701,7 @@ class MGXS(metaclass=ABCMeta):
domain_filter = self.xs_tally.find_filter('sum(distribcell)')
subdomains = domain_filter.bins
elif self.domain_type == 'mesh':
xyz = [range(1, x+1) for x in self.domain.dimension]
subdomains = list(itertools.product(*xyz))
subdomains = list(self.domain.indices)
else:
subdomains = [self.domain.id]
@ -2342,8 +2340,7 @@ class MatrixMGXS(MGXS):
elif self.domain_type == 'distribcell':
subdomains = np.arange(self.num_subdomains, dtype=np.int)
elif self.domain_type == 'mesh':
xyz = [range(1, x + 1) for x in self.domain.dimension]
subdomains = list(itertools.product(*xyz))
subdomains = list(self.domain.indices)
else:
subdomains = [self.domain.id]
@ -2720,9 +2717,9 @@ class TransportXS(MGXS):
@property
def scores(self):
if not self.nu:
return ['flux', 'total', 'flux', 'scatter-1']
return ['flux', 'total', 'flux', 'scatter']
else:
return ['flux', 'total', 'flux', 'nu-scatter-1']
return ['flux', 'total', 'flux', 'nu-scatter']
@property
def tally_keys(self):
@ -2733,8 +2730,9 @@ class TransportXS(MGXS):
group_edges = self.energy_groups.group_edges
energy_filter = openmc.EnergyFilter(group_edges)
energyout_filter = openmc.EnergyoutFilter(group_edges)
p1_filter = openmc.LegendreFilter(1)
filters = [[energy_filter], [energy_filter],
[energy_filter], [energyout_filter]]
[energy_filter], [energyout_filter, p1_filter]]
return self._add_angle_filters(filters)
@ -2742,12 +2740,18 @@ class TransportXS(MGXS):
def rxn_rate_tally(self):
if self._rxn_rate_tally is None:
# Switch EnergyoutFilter to EnergyFilter.
old_filt = self.tallies['scatter-1'].filters[-1]
new_filt = openmc.EnergyFilter(old_filt.bins)
self.tallies['scatter-1'].filters[-1] = new_filt
p1_tally = self.tallies['scatter-1']
old_filt = p1_tally.filters[-2]
new_filt = openmc.EnergyFilter(old_filt.values)
p1_tally.filters[-2] = new_filt
self._rxn_rate_tally = \
self.tallies['total'] - self.tallies['scatter-1']
# Slice Legendre expansion filter and change name of score
p1_tally = p1_tally.get_slice(filters=[openmc.LegendreFilter],
filter_bins=[('P1',)],
squeeze=True)
p1_tally.scores = ['scatter-1']
self._rxn_rate_tally = self.tallies['total'] - p1_tally
self._rxn_rate_tally.sparse = self.sparse
return self._rxn_rate_tally
@ -2761,15 +2765,22 @@ class TransportXS(MGXS):
raise ValueError(msg)
# Switch EnergyoutFilter to EnergyFilter.
old_filt = self.tallies['scatter-1'].filters[-1]
new_filt = openmc.EnergyFilter(old_filt.bins)
self.tallies['scatter-1'].filters[-1] = new_filt
p1_tally = self.tallies['scatter-1']
old_filt = p1_tally.filters[-2]
new_filt = openmc.EnergyFilter(old_filt.values)
p1_tally.filters[-2] = new_filt
# Slice Legendre expansion filter and change name of score
p1_tally = p1_tally.get_slice(filters=[openmc.LegendreFilter],
filter_bins=[('P1',)],
squeeze=True)
p1_tally.scores = ['scatter-1']
# Compute total cross section
total_xs = self.tallies['total'] / self.tallies['flux (tracklength)']
# Compute transport correction term
trans_corr = self.tallies['scatter-1'] / self.tallies['flux (analog)']
trans_corr = p1_tally / self.tallies['flux (analog)']
# Compute the transport-corrected total cross section
self._xs_tally = total_xs - trans_corr
@ -3513,6 +3524,7 @@ class ScatterXS(MGXS):
self._estimator = 'analog'
self._valid_estimators = ['analog']
class ScatterMatrixXS(MatrixMGXS):
r"""A scattering matrix multi-group cross section with the cosine of the
change-in-angle represented as one or more Legendre moments or a histogram.
@ -3601,10 +3613,10 @@ class ScatterMatrixXS(MatrixMGXS):
name : str, optional
Name of the multi-group cross section. Used as a label to identify
tallies in OpenMC 'tallies.xml' file.
num_polar : Integral, optional
num_polar : int, optional
Number of equi-width polar angle bins for angle discretization;
defaults to one bin
num_azimuthal : Integral, optional
num_azimuthal : int, optional
Number of equi-width azimuthal angle bins for angle discretization;
defaults to one bin
nu : bool
@ -3650,9 +3662,9 @@ class ScatterMatrixXS(MatrixMGXS):
Domain type for spatial homogenization
energy_groups : openmc.mgxs.EnergyGroups
Energy group structure for energy condensation
num_polar : Integral
num_polar : int
Number of equi-width polar angle bins for angle discretization
num_azimuthal : Integral
num_azimuthal : int
Number of equi-width azimuthal angle bins for angle discretization
tally_trigger : openmc.Trigger
An (optional) tally precision trigger given to each tally used to
@ -3770,38 +3782,25 @@ class ScatterMatrixXS(MatrixMGXS):
def scores(self):
if self.formulation == 'simple':
scores = ['flux']
if self.scatter_format == 'legendre':
if self.legendre_order == 0:
scores.append('{}-0'.format(self.rxn_type))
if self.correction:
scores.append('{}-1'.format(self.rxn_type))
else:
scores.append('{}-P{}'.format(self.rxn_type, self.legendre_order))
elif self.scatter_format == 'histogram':
scores += [self.rxn_type]
scores = ['flux', self.rxn_type]
else:
# Add scores for groupwise scattering cross section
scores = ['flux', 'scatter']
# Add scores for group-to-group scattering probability matrix
if self.scatter_format == 'legendre':
if self.legendre_order == 0:
scores.append('scatter-0')
else:
scores.append('scatter-P{}'.format(self.legendre_order))
elif self.scatter_format == 'histogram':
scores.append('scatter-0')
# these scores also contain the angular information, whether it be
# Legendre expansion or histogram bins
scores.append('scatter')
# Add scores for multiplicity matrix
# Add scores for multiplicity matrix; scatter info for the
# denominator will come from the previous score
if self.nu:
scores.extend(['nu-scatter-0', 'scatter-0'])
scores.append('nu-scatter')
# Add scores for transport correction
if self.correction == 'P0' and self.legendre_order == 0:
scores.extend(['{}-1'.format(self.rxn_type), 'flux'])
scores.extend([self.rxn_type, 'flux'])
return scores
@ -3814,15 +3813,15 @@ class ScatterMatrixXS(MatrixMGXS):
tally_keys = ['flux (tracklength)', 'scatter']
# Add keys for group-to-group scattering probability matrix
tally_keys.append('scatter-P{}'.format(self.legendre_order))
tally_keys.append('scatter matrix')
# Add keys for multiplicity matrix
if self.nu:
tally_keys.extend(['nu-scatter-0', 'scatter-0'])
tally_keys.extend(['nu-scatter'])
# Add keys for transport correction
if self.correction == 'P0' and self.legendre_order == 0:
tally_keys.extend(['{}-1'.format(self.rxn_type), 'flux (analog)'])
tally_keys.extend(['correction', 'flux (analog)'])
return tally_keys
@ -3839,7 +3838,7 @@ class ScatterMatrixXS(MatrixMGXS):
# Add estimators for multiplicity matrix
if self.nu:
estimators.extend(['analog', 'analog'])
estimators.extend(['analog'])
# Add estimators for transport correction
if self.correction == 'P0' and self.legendre_order == 0:
@ -3856,13 +3855,15 @@ class ScatterMatrixXS(MatrixMGXS):
if self.scatter_format == 'legendre':
if self.correction == 'P0' and self.legendre_order == 0:
filters = [[energy], [energy, energyout], [energyout]]
angle_filter = openmc.LegendreFilter(order=1)
else:
filters = [[energy], [energy, energyout]]
angle_filter = \
openmc.LegendreFilter(order=self.legendre_order)
elif self.scatter_format == 'histogram':
bins = np.linspace(-1., 1., num=self.histogram_bins + 1,
endpoint=True)
filters = [[energy], [energy, energyout, openmc.MuFilter(bins)]]
angle_filter = openmc.MuFilter(bins)
filters = [[energy], [energy, energyout, angle_filter]]
else:
group_edges = self.energy_groups.group_edges
@ -3874,19 +3875,21 @@ class ScatterMatrixXS(MatrixMGXS):
# Group-to-group scattering probability matrix
if self.scatter_format == 'legendre':
filters.append([energy, energyout])
angle_filter = openmc.LegendreFilter(order=self.legendre_order)
elif self.scatter_format == 'histogram':
bins = np.linspace(-1., 1., num=self.histogram_bins + 1,
endpoint=True)
filters.append([energy, energyout, openmc.MuFilter(bins)])
angle_filter = openmc.MuFilter(bins)
filters.append([energy, energyout, angle_filter])
# Multiplicity matrix
if self.nu:
filters.extend([[energy, energyout], [energy, energyout]])
filters.extend([[energy, energyout]])
# Add filters for transport correction
if self.correction == 'P0' and self.legendre_order == 0:
filters.extend([[energyout], [energy]])
filters.extend([[energyout, openmc.LegendreFilter(1)],
[energy]])
return self._add_angle_filters(filters)
@ -3897,27 +3900,39 @@ class ScatterMatrixXS(MatrixMGXS):
if self.formulation == 'simple':
if self.scatter_format == 'legendre':
# If using P0 correction subtract scatter-1 from the diagonal
# If using P0 correction subtract P2 scatter from the diag.
if self.correction == 'P0' and self.legendre_order == 0:
scatter_p0 = self.tallies['{}-0'.format(self.rxn_type)]
scatter_p1 = self.tallies['{}-1'.format(self.rxn_type)]
energy_filter = scatter_p0.find_filter(openmc.EnergyFilter)
scatter_p0 = self.tallies[self.rxn_type].get_slice(
filters=[openmc.LegendreFilter],
filter_bins=[('P0',)])
scatter_p1 = self.tallies[self.rxn_type].get_slice(
filters=[openmc.LegendreFilter],
filter_bins=[('P1',)])
# Transform scatter-p1 tally into an energyin/out matrix
# Set the Legendre order of these tallies to be 0
# so they can be subtracted
legendre = openmc.LegendreFilter(order=0)
scatter_p0.filters[-1] = legendre
scatter_p1.filters[-1] = legendre
scatter_p1 = scatter_p1.summation(
filter_type=openmc.EnergyFilter,
remove_filter=True)
energy_filter = \
scatter_p0.find_filter(openmc.EnergyFilter)
# Transform scatter-p1 into an energyin/out matrix
# to match scattering matrix shape for tally arithmetic
energy_filter = copy.deepcopy(energy_filter)
scatter_p1 = scatter_p1.diagonalize_filter(energy_filter)
scatter_p1 = \
scatter_p1.diagonalize_filter(energy_filter)
self._rxn_rate_tally = scatter_p0 - scatter_p1
# Extract scattering moment reaction rate Tally
elif self.legendre_order == 0:
tally_key = '{}-{}'.format(self.rxn_type,
self.legendre_order)
self._rxn_rate_tally = self.tallies[tally_key]
# Otherwise, extract scattering moment reaction rate Tally
else:
tally_key = '{}-P{}'.format(self.rxn_type,
self.legendre_order)
self._rxn_rate_tally = self.tallies[tally_key]
self._rxn_rate_tally = self.tallies[self.rxn_type]
elif self.scatter_format == 'histogram':
# Extract scattering rate distribution tally
self._rxn_rate_tally = self.tallies[self.rxn_type]
@ -3944,35 +3959,24 @@ class ScatterMatrixXS(MatrixMGXS):
self._xs_tally = MGXS.xs_tally.fget(self)
else:
# Compute scattering probability matrix
energyout_bins = [self.energy_groups.get_group_bounds(i)
for i in range(self.num_groups, 0, -1)]
tally_key = 'scatter-P{}'.format(self.legendre_order)
# Compute scattering probability matrixS
tally_key = 'scatter matrix'
# Compute normalization factor summed across outgoing energies
norm = self.tallies[tally_key].get_slice(scores=['scatter-0'])
norm = norm.summation(
filter_type=openmc.EnergyoutFilter, filter_bins=energyout_bins)
# Remove the AggregateFilter summed across energyout bins
norm._filters = norm._filters[:2]
if self.scatter_format == 'legendre':
norm = self.tallies[tally_key].get_slice(
scores=['scatter'],
filters=[openmc.LegendreFilter],
filter_bins=[('P0',)], squeeze=True)
# Compute normalization factor summed across outgoing mu bins
if self.scatter_format == 'histogram':
# (Re-)append the MuFilter which was removed above
mu_bins = np.linspace(
-1., 1., num=self.histogram_bins + 1, endpoint=True)
norm._filters.append(openmc.MuFilter(mu_bins))
# Sum across all mu bins
mu_bins = [(mu_bins[i], mu_bins[i+1]) for
i in range(self.histogram_bins)]
elif self.scatter_format == 'histogram':
norm = self.tallies[tally_key].get_slice(
scores=['scatter'])
norm = norm.summation(
filter_type=openmc.MuFilter, filter_bins=mu_bins)
# Remove the AggregateFilter summed across mu bins
norm._filters = norm._filters[:2]
filter_type=openmc.MuFilter, remove_filter=True)
norm = norm.summation(filter_type=openmc.EnergyoutFilter,
remove_filter=True)
# Compute groupwise scattering cross section
self._xs_tally = self.tallies['scatter'] * \
@ -3984,15 +3988,36 @@ class ScatterMatrixXS(MatrixMGXS):
# Multiply by the multiplicity matrix
if self.nu:
numer = self.tallies['nu-scatter-0']
denom = self.tallies['scatter-0']
numer = self.tallies['nu-scatter']
# Get the denominator
if self.scatter_format == 'legendre':
denom = self.tallies[tally_key].get_slice(
scores=['scatter'],
filters=[openmc.LegendreFilter],
filter_bins=[('P0',)], squeeze=True)
# Compute normalization factor summed across mu bins
elif self.scatter_format == 'histogram':
denom = self.tallies[tally_key].get_slice(
scores=['scatter'])
# Sum across all mu bins
denom = denom.summation(
filter_type=openmc.MuFilter, remove_filter=True)
self._xs_tally *= (numer / denom)
# If using P0 correction subtract scatter-1 from the diagonal
if self.correction == 'P0' and self.legendre_order == 0:
scatter_p1 = self.tallies['{}-1'.format(self.rxn_type)]
scatter_p1 = self.tallies['correction'].get_slice(
filters=[openmc.LegendreFilter], filter_bins=[('P1',)])
flux = self.tallies['flux (analog)']
# Set the Legendre order of the P1 tally to be P0
# so it can be subtracted
legendre = openmc.LegendreFilter(order=0)
scatter_p1.filters[-1] = legendre
# Transform scatter-p1 tally into an energyin/out matrix
# to match scattering matrix shape for tally arithmetic
energy_filter = flux.find_filter(openmc.EnergyFilter)
@ -4004,10 +4029,34 @@ class ScatterMatrixXS(MatrixMGXS):
# Override the nuclides for tally arithmetic
correction.nuclides = scatter_p1.nuclides
# Set xs_tally to be itself with only P0 data
self._xs_tally = self._xs_tally.get_slice(
filters=[openmc.LegendreFilter], filter_bins=[('P0',)])
# Tell xs_tally that it is P0
legendre_xs_tally = \
self._xs_tally.find_filter(openmc.LegendreFilter)
legendre_xs_tally.order = 0
# And subtract the P1 correction from the P0 matrix
self._xs_tally -= correction
self._compute_xs()
# Force the angle filter to be the last filter
if self.scatter_format == 'histogram':
angle_filter = self._xs_tally.find_filter(openmc.MuFilter)
else:
angle_filter = \
self._xs_tally.find_filter(openmc.LegendreFilter)
angle_filter_index = self._xs_tally.filters.index(angle_filter)
# If the angle filter index is not last, then make it last
if angle_filter_index != len(self._xs_tally.filters) - 1:
energyout_filter = \
self._xs_tally.find_filter(openmc.EnergyoutFilter)
self._xs_tally._swap_filters(energyout_filter,
angle_filter)
return self._xs_tally
@nu.setter
@ -4128,16 +4177,6 @@ class ScatterMatrixXS(MatrixMGXS):
self._rxn_rate_tally = None
self._loaded_sp = False
if self.scatter_format == 'legendre':
# Expand scores to match the format in the statepoint
# e.g., "scatter-P2" -> "scatter-0", "scatter-1", "scatter-2"
for tally_key, tally in self.tallies.items():
if 'scatter-P' in tally.scores[0]:
score_prefix = tally.scores[0].split('P')[0]
self.tallies[tally_key].scores = \
[score_prefix + '{}'.format(i)
for i in range(self.legendre_order + 1)]
super().load_from_statepoint(statepoint)
def get_slice(self, nuclides=[], in_groups=[], out_groups=[],
@ -4190,12 +4229,11 @@ class ScatterMatrixXS(MatrixMGXS):
slice_xs.legendre_order = legendre_order
# Slice the scattering tally
tally_key = '{}-P{}'.format(self.rxn_type, self.legendre_order)
expand_scores = \
[self.rxn_type + '-{}'.format(i)
for i in range(self.legendre_order + 1)]
slice_xs.tallies[tally_key] = \
slice_xs.tallies[tally_key].get_slice(scores=expand_scores)
filter_bins = [tuple(['P{}'.format(i)
for i in range(self.legendre_order + 1)])]
slice_xs.tallies[self.rxn_type] = \
slice_xs.tallies[self.rxn_type].get_slice(
filters=[openmc.LegendreFilter], filter_bins=filter_bins)
# Slice outgoing energy groups if needed
if len(out_groups) != 0:
@ -4209,7 +4247,8 @@ class ScatterMatrixXS(MatrixMGXS):
for tally_type, tally in slice_xs.tallies.items():
if tally.contains_filter(openmc.EnergyoutFilter):
tally_slice = tally.get_slice(
filters=[openmc.EnergyoutFilter], filter_bins=filter_bins)
filters=[openmc.EnergyoutFilter],
filter_bins=filter_bins)
slice_xs.tallies[tally_type] = tally_slice
slice_xs.sparse = self.sparse
@ -4320,14 +4359,19 @@ class ScatterMatrixXS(MatrixMGXS):
filter_bins.append((self.energy_groups.get_group_bounds(group),))
# Construct CrossScore for requested scattering moment
if moment != 'all' and self.scatter_format == 'legendre':
cv.check_type('moment', moment, Integral)
cv.check_greater_than('moment', moment, 0, equality=True)
cv.check_less_than(
'moment', moment, self.legendre_order, equality=True)
scores = [self.xs_tally.scores[moment]]
if self.scatter_format == 'legendre':
if moment != 'all':
cv.check_type('moment', moment, Integral)
cv.check_greater_than('moment', moment, 0, equality=True)
cv.check_less_than(
'moment', moment, self.legendre_order, equality=True)
filters.append(openmc.LegendreFilter)
filter_bins.append(('P{}'.format(moment),))
num_angle_bins = 1
else:
num_angle_bins = self.legendre_order + 1
else:
scores = []
num_angle_bins = self.histogram_bins
# Construct a collection of the nuclides to retrieve from the xs tally
if self.by_nuclide:
@ -4339,6 +4383,7 @@ class ScatterMatrixXS(MatrixMGXS):
query_nuclides = ['total']
# Use tally summation if user requested the sum for all nuclides
scores = self.xs_tally.scores
if nuclides == 'sum' or nuclides == ['sum']:
xs_tally = self.xs_tally.summation(nuclides=query_nuclides)
xs = xs_tally.get_values(scores=scores, filters=filters,
@ -4370,24 +4415,15 @@ class ScatterMatrixXS(MatrixMGXS):
else:
num_out_groups = len(out_groups)
if self.scatter_format == 'histogram':
num_mu_bins = self.histogram_bins
else:
num_mu_bins = 1
# Reshape tally data array with separate axes for domain and energy
# Accomodate the polar and azimuthal bins if needed
num_subdomains = int(xs.shape[0] / (num_mu_bins * num_in_groups *
num_subdomains = int(xs.shape[0] / (num_angle_bins * num_in_groups *
num_out_groups * self.num_polar *
self.num_azimuthal))
if self.num_polar > 1 or self.num_azimuthal > 1:
if self.scatter_format == 'histogram':
new_shape = (self.num_polar, self.num_azimuthal,
num_subdomains, num_in_groups, num_out_groups,
num_mu_bins)
else:
new_shape = (self.num_polar, self.num_azimuthal,
num_subdomains, num_in_groups, num_out_groups)
new_shape = (self.num_polar, self.num_azimuthal,
num_subdomains, num_in_groups, num_out_groups,
num_angle_bins)
new_shape += xs.shape[1:]
xs = np.reshape(xs, new_shape)
@ -4400,11 +4436,9 @@ class ScatterMatrixXS(MatrixMGXS):
if order_groups == 'increasing':
xs = xs[:, :, :, ::-1, ::-1, ...]
else:
if self.scatter_format == 'histogram':
new_shape = (num_subdomains, num_in_groups, num_out_groups,
num_mu_bins)
else:
new_shape = (num_subdomains, num_in_groups, num_out_groups)
new_shape = (num_subdomains, num_in_groups, num_out_groups,
num_angle_bins)
new_shape += xs.shape[1:]
xs = np.reshape(xs, new_shape)
@ -4419,14 +4453,14 @@ class ScatterMatrixXS(MatrixMGXS):
if squeeze:
# We want to squeeze out everything but the angles, in_groups,
# out_groups, and, if needed, num_mu_bins dimension. These must
# out_groups, and, if needed, num_angle_bins dimension. These must
# not be squeezed so 1-group, 1-angle problems have the correct
# shape.
xs = self._squeeze_xs(xs)
return xs
def get_pandas_dataframe(self, groups='all', nuclides='all', moment='all',
xs_type='macro', paths=True):
def get_pandas_dataframe(self, groups='all', nuclides='all',
xs_type='macro', paths=False):
"""Build a Pandas DataFrame for the MGXS data.
This method leverages :meth:`openmc.Tally.get_pandas_dataframe`, but
@ -4441,19 +4475,15 @@ class ScatterMatrixXS(MatrixMGXS):
may be a list of nuclide name strings (e.g., ['U235', 'U238']).
The special string 'all' will include the cross sections for all
nuclides in the spatial domain. The special string 'sum' will
include the cross sections summed over all nuclides. Defaults
to 'all'.
moment : int or 'all'
The scattering matrix moment to return. All moments will be
returned if the moment is 'all' (default); otherwise, a specific
moment will be returned.
include the cross sections summed over all nuclides. Defaults to
'all'.
xs_type: {'macro', 'micro'}
Return macro or micro cross section in units of cm^-1 or barns.
Defaults to 'macro'.
paths : bool, optional
Construct columns for distribcell tally filters (default is True).
The geometric information in the Summary object is embedded into a
Multi-index column with a geometric "path" to each distribcell
The geometric information in the Summary object is embedded into
a Multi-index column with a geometric "path" to each distribcell
instance.
Returns
@ -4469,35 +4499,13 @@ class ScatterMatrixXS(MatrixMGXS):
"""
df = super().get_pandas_dataframe(groups, nuclides, xs_type, paths)
# Build the dataframe using the parent class method
df = super().get_pandas_dataframe(groups, nuclides, xs_type,
paths=paths)
if self.scatter_format == 'legendre':
# Add a moment column to dataframe
if self.legendre_order > 0:
# Insert a column corresponding to the Legendre moments
moments = ['P{}'.format(i)
for i in range(self.legendre_order + 1)]
moments = np.tile(moments, int(df.shape[0] / len(moments)))
df['moment'] = moments
# Place the moment column before the mean column
columns = df.columns.tolist()
mean_index \
= [i for i, s in enumerate(columns) if 'mean' in s][0]
if self.domain_type == 'mesh':
df = df[columns[:mean_index] + [('moment', '')] +
columns[mean_index:-1]]
else:
df = df[columns[:mean_index] + ['moment'] +
columns[mean_index:-1]]
# Select rows corresponding to requested scattering moment
if moment != 'all':
cv.check_type('moment', moment, Integral)
cv.check_greater_than('moment', moment, 0, equality=True)
cv.check_less_than(
'moment', moment, self.legendre_order, equality=True)
df = df[df['moment'] == 'P{}'.format(moment)]
# If the matrix is P0, remove the legendre column
if self.scatter_format == 'legendre' and self.legendre_order == 0:
df = df.drop(axis=1, labels=['legendre'])
return df
@ -4514,8 +4522,9 @@ class ScatterMatrixXS(MatrixMGXS):
The nuclides of the cross-sections to include in the report. This
may be a list of nuclide name strings (e.g., ['U235', 'U238']).
The special string 'all' will report the cross sections for all
nuclides in the spatial domain. The special string 'sum' will report
the cross sections summed over all nuclides. Defaults to 'all'.
nuclides in the spatial domain. The special string 'sum' will
report the cross sections summed over all nuclides. Defaults to
'all'.
xs_type: {'macro', 'micro'}
Return the macro or micro cross section in units of cm^-1 or barns.
Defaults to 'macro'.
@ -4530,8 +4539,7 @@ class ScatterMatrixXS(MatrixMGXS):
elif self.domain_type == 'distribcell':
subdomains = np.arange(self.num_subdomains, dtype=np.int)
elif self.domain_type == 'mesh':
xyz = [range(1, x + 1) for x in self.domain.dimension]
subdomains = list(itertools.product(*xyz))
subdomains = list(self.domain.indices)
else:
subdomains = [self.domain.id]
@ -4990,14 +4998,9 @@ class ScatterProbabilityMatrix(MatrixMGXS):
def xs_tally(self):
if self._xs_tally is None:
energyout_bins = [self.energy_groups.get_group_bounds(i)
for i in range(self.num_groups, 0, -1)]
norm = self.rxn_rate_tally.get_slice(scores=[self.rxn_type])
norm = norm.summation(
filter_type=openmc.EnergyoutFilter, filter_bins=energyout_bins)
# Remove the AggregateFilter summed across energyout bins
norm._filters = norm._filters[:2]
filter_type=openmc.EnergyoutFilter, remove_filter=True)
# Compute the group-to-group probabilities
self._xs_tally = self.tallies[self.rxn_type] / norm

View file

@ -1,7 +1,7 @@
from collections.abc import Iterable
import openmc
from openmc.checkvalue import check_type
from openmc.checkvalue import check_type, check_value
class Model(object):
@ -136,9 +136,49 @@ class Model(object):
for plot in plots:
self._plots.append(plot)
def export_to_xml(self):
"""Export model to XML files.
def deplete(self, timesteps, power, chain_file=None, method='cecm',
**kwargs):
"""Deplete model using specified timesteps/power
Parameters
----------
timesteps : iterable of float
Array of timesteps in units of [s]. Note that values are not
cumulative.
power : float or iterable of float
Power of the reactor in [W]. A single value indicates that the power
is constant over all timesteps. An iterable indicates potentially
different power levels for each timestep. For a 2D problem, the
power can be given in [W/cm] as long as the "volume" assigned to a
depletion material is actually an area in [cm^2].
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the
:envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists.
method : {'cecm', 'predictor'}
Integration method used for depletion
**kwargs
Keyword arguments passed to integration function (e.g.,
:func:`openmc.deplete.integrator.cecm`)
"""
# Import the depletion module. This is done here rather than the module
# header to delay importing openmc.capi (through openmc.deplete) which
# can be tough to install properly.
import openmc.deplete as dep
# Create OpenMC transport operator
op = dep.Operator(self.geometry, self.settings, chain_file)
# Perform depletion
if method == 'predictor':
dep.integrator.predictor(op, timesteps, power, **kwargs)
elif method == 'cecm':
dep.integrator.cecm(op, timesteps, power, **kwargs)
else:
check_value('method', method, ('cecm', 'predictor'))
def export_to_xml(self):
"""Export model to XML files."""
self.settings.export_to_xml()
self.geometry.export_to_xml()
@ -166,19 +206,17 @@ class Model(object):
Parameters
----------
**kwargs
All keyword arguments are passed to openmc.run
All keyword arguments are passed to :func:`openmc.run`
Returns
-------
2-tuple of float
uncertainties.UFloat
Combined estimator of k-effective from the statepoint
"""
self.export_to_xml()
return_code = openmc.run(**kwargs)
assert (return_code == 0), "OpenMC did not execute successfully"
openmc.run(**kwargs)
n = self.settings.batches
if self.settings.statepoint is not None:

View file

@ -113,8 +113,7 @@ class _Domain(metaclass=ABCMeta):
Length in x-, y-, and z- directions of each cell in mesh overlaid on
domain.
limits : list of float
Minimum and maximum position in x-, y-, and z-directions where particle
center can be placed.
Constraint on where particle center can be placed.
volume : float
Volume of the container.
@ -158,8 +157,6 @@ class _Domain(metaclass=ABCMeta):
raise ValueError('Unable to set domain center to {} since it must '
'be of length 3'.format(center))
self._center = [float(x) for x in center]
self._limits = None
self._cell_length = None
def mesh_cell(self, p):
"""Calculate the index of the cell in a mesh overlaid on the domain in
@ -211,6 +208,26 @@ class _Domain(metaclass=ABCMeta):
"""
pass
@abstractmethod
def repel_particles(self, p, q, d, d_new):
"""Move particles p and q apart according to the following
transformation (accounting for boundary conditions on domain):
r_i^(n+1) = r_i^(n) + 1/2(d_out^(n+1) - d^(n))
r_j^(n+1) = r_j^(n) - 1/2(d_out^(n+1) - d^(n))
Parameters
----------
p, q : numpy.ndarray
Cartesian coordinates of particle center.
d : float
distance between centers of particles i and j.
d_new : float
final distance between centers of particles i and j.
"""
pass
class _CubicDomain(_Domain):
"""Cubic container in which to pack particles.
@ -238,7 +255,7 @@ class _CubicDomain(_Domain):
Length in x-, y-, and z- directions of each cell in mesh overlaid on
domain.
limits : list of float
Minimum and maximum position in x-, y-, and z-directions where particle
Maximum distance from center in x-, y-, or z-direction where particle
center can be placed.
volume : float
Volume of the container.
@ -256,9 +273,7 @@ class _CubicDomain(_Domain):
@property
def limits(self):
if self._limits is None:
xlim = self.length/2 - self.particle_radius
self._limits = [[x - xlim for x in self.center],
[x + xlim for x in self.center]]
self._limits = [self.length/2 - self.particle_radius]
return self._limits
@property
@ -284,9 +299,27 @@ class _CubicDomain(_Domain):
self._limits = limits
def random_point(self):
return [uniform(self.limits[0][0], self.limits[1][0]),
uniform(self.limits[0][1], self.limits[1][1]),
uniform(self.limits[0][2], self.limits[1][2])]
x_max = self.limits[0]
return [uniform(-x_max, x_max),
uniform(-x_max, x_max),
uniform(-x_max, x_max)]
def repel_particles(self, p, q, d, d_new):
# Moving each particle distance 's' away from the other along the line
# joining the particle centers will ensure their final distance is
# equal to the outer diameter
s = (d_new - d)/2
v = (p - q)/d
p += s*v
q -= s*v
# Enforce the rigid boundary by moving each particle back along the
# surface normal until it is completely within the container if it
# overlaps the surface
x_max = self.limits[0]
p[:] = np.clip(p, -x_max, x_max)
q[:] = np.clip(q, -x_max, x_max)
class _CylindricalDomain(_Domain):
@ -317,8 +350,8 @@ class _CylindricalDomain(_Domain):
Length in x-, y-, and z- directions of each cell in mesh overlaid on
domain.
limits : list of float
Minimum and maximum position in x-, y-, and z-directions where particle
center can be placed.
Maximum radial distance and maximum distance from center in z-direction
where particle center can be placed.
volume : float
Volume of the container.
@ -340,12 +373,8 @@ class _CylindricalDomain(_Domain):
@property
def limits(self):
if self._limits is None:
xlim = self.length/2 - self.particle_radius
rlim = self.radius - self.particle_radius
self._limits = [[self.center[0] - rlim, self.center[1] - rlim,
self.center[2] - xlim],
[self.center[0] + rlim, self.center[1] + rlim,
self.center[2] + xlim]]
self._limits = [self.radius - self.particle_radius,
self.length/2 - self.particle_radius]
return self._limits
@property
@ -377,10 +406,37 @@ class _CylindricalDomain(_Domain):
self._limits = limits
def random_point(self):
r = sqrt(uniform(0, (self.radius - self.particle_radius)**2))
r_max = self.limits[0]
z_max = self.limits[1]
r = sqrt(uniform(0, r_max**2))
t = uniform(0, 2*pi)
return [r*cos(t) + self.center[0], r*sin(t) + self.center[1],
uniform(self.limits[0][2], self.limits[1][2])]
return [r*cos(t), r*sin(t), uniform(-z_max, z_max)]
def repel_particles(self, p, q, d, d_new):
# Moving each particle distance 's' away from the other along the line
# joining the particle centers will ensure their final distance is
# equal to the outer diameter
s = (d_new - d)/2
v = (p - q)/d
p += s*v
q -= s*v
# Enforce the rigid boundary by moving each particle back along the
# surface normal until it is completely within the container if it
# overlaps the surface
r_max = self.limits[0]
z_max = self.limits[1]
r = sqrt(p[0]**2 + p[1]**2)
if r > r_max:
p[0:2] *= r_max/r
p[2] = np.clip(p[2], -z_max, z_max)
r = sqrt(q[0]**2 + q[1]**2)
if r > r_max:
q[0:2] *= r_max/r
q[2] = np.clip(q[2], -z_max, z_max)
class _SphericalDomain(_Domain):
@ -407,8 +463,7 @@ class _SphericalDomain(_Domain):
Length in x-, y-, and z- directions of each cell in mesh overlaid on
domain.
limits : list of float
Minimum and maximum position in x-, y-, and z-directions where particle
center can be placed.
Maximum radial distance where particle center can be placed.
volume : float
Volume of the container.
@ -425,9 +480,7 @@ class _SphericalDomain(_Domain):
@property
def limits(self):
if self._limits is None:
rlim = self.radius - self.particle_radius
self._limits = [[x - rlim for x in self.center],
[x + rlim for x in self.center]]
self._limits = [self.radius - self.particle_radius]
return self._limits
@property
@ -453,10 +506,33 @@ class _SphericalDomain(_Domain):
self._limits = limits
def random_point(self):
r_max = self.limits[0]
x = (gauss(0, 1), gauss(0, 1), gauss(0, 1))
r = (uniform(0, (self.radius - self.particle_radius)**3)**(1/3) /
sqrt(x[0]**2 + x[1]**2 + x[2]**2))
return [r*x[i] + self.center[i] for i in range(3)]
r = (uniform(0, r_max**3)**(1/3) / sqrt(x[0]**2 + x[1]**2 + x[2]**2))
return [r*s for s in x]
def repel_particles(self, p, q, d, d_new):
# Moving each particle distance 's' away from the other along the line
# joining the particle centers will ensure their final distance is
# equal to the outer diameter
s = (d_new - d)/2
v = (p - q)/d
p += s*v
q -= s*v
# Enforce the rigid boundary by moving each particle back along the
# surface normal until it is completely within the container if it
# overlaps the surface
r_max = self.limits[0]
r = sqrt(p[0]**2 + p[1]**2 + p[2]**2)
if r > r_max:
p *= r_max/r
r = sqrt(q[0]**2 + q[1]**2 + q[2]**2)
if r > r_max:
q *= r_max/r
def create_triso_lattice(trisos, lower_left, pitch, shape, background):
@ -636,6 +712,7 @@ def _close_random_pack(domain, particles, contraction_rate):
del rods_map[i]
del rods_map[j]
return d, i, j
return None, None, None
def create_rod_list():
"""Generate sorted list of rods (distances between particle centers).
@ -660,8 +737,8 @@ def _close_random_pack(domain, particles, contraction_rate):
# Find distance to nearest neighbor and index of nearest neighbor for
# all particles
d, n = tree.query(particles, k=2)
d = d[:,1]
n = n[:,1]
d = d[:, 1]
n = n[:, 1]
# Array of particle indices, indices of nearest neighbors, and
# distances to nearest neighbors
@ -670,8 +747,8 @@ def _close_random_pack(domain, particles, contraction_rate):
# Sort along second column and swap first and second columns to create
# array of nearest neighbor indices, indices of particles they are
# nearest neighbors of, and distances between them
b = a[a[:,1].argsort()]
b[:,[0, 1]] = b[:,[1, 0]]
b = a[a[:, 1].argsort()]
b[:, [0, 1]] = b[:, [1, 0]]
# Find the intersection between 'a' and 'b': a list of particles who
# are each other's nearest neighbors and the distance between them
@ -685,12 +762,8 @@ def _close_random_pack(domain, particles, contraction_rate):
del rods[:]
rods_map.clear()
for d, i, j in r:
add_rod(d, i, j)
# Inner diameter is set initially to the shortest center-to-center
# distance between any two particles
if rods:
inner_diameter[0] = rods[0][0]
if d < outer_diameter and not np.isclose(d, outer_diameter, atol=1.0e-14):
add_rod(d, i, j)
def update_mesh(i):
"""Update which mesh cells the particle is in based on new particle
@ -729,50 +802,19 @@ def _close_random_pack(domain, particles, contraction_rate):
j = floor(-log10(pf_out - pf_in)).
Returns
-------
float
New outer diameter
"""
inner_pf = (4/3 * pi * (inner_diameter[0]/2)**3 * n_particles /
domain.volume)
outer_pf = (4/3 * pi * (outer_diameter[0]/2)**3 * n_particles /
domain.volume)
inner_pf = 4/3*pi*(inner_diameter/2)**3*n_particles/domain.volume
outer_pf = 4/3*pi*(outer_diameter/2)**3*n_particles/domain.volume
j = floor(-log10(outer_pf - inner_pf))
outer_diameter[0] = (outer_diameter[0] - 0.5**j * contraction_rate *
initial_outer_diameter / n_particles)
def repel_particles(i, j, d):
"""Move particles p and q apart according to the following
transformation (accounting for reflective boundary conditions on
domain):
r_i^(n+1) = r_i^(n) + 1/2(d_out^(n+1) - d^(n))
r_j^(n+1) = r_j^(n) - 1/2(d_out^(n+1) - d^(n))
Parameters
----------
i, j : int
Index of particles in particles array.
d : float
distance between centers of particles i and j.
"""
# Moving each particle distance 'r' away from the other along the line
# joining the particle centers will ensure their final distance is equal
# to the outer diameter
r = (outer_diameter[0] - d)/2
v = (particles[i] - particles[j])/d
particles[i] += r*v
particles[j] -= r*v
# Apply reflective boundary conditions
particles[i] = particles[i].clip(domain.limits[0], domain.limits[1])
particles[j] = particles[j].clip(domain.limits[0], domain.limits[1])
update_mesh(i)
update_mesh(j)
return (outer_diameter - 0.5**j * contraction_rate *
initial_outer_diameter / n_particles)
def nearest(i):
"""Find index of nearest neighbor of particle i.
@ -803,14 +845,14 @@ def _close_random_pack(domain, particles, contraction_rate):
else:
return None, None
def update_rod_list(i, j):
"""Update the rod list with the new nearest neighbors of particles i
and j since their overlap was eliminated.
def update_rod_list(i):
"""Update the rod list with the new nearest neighbors of particle since
its overlap was eliminated.
Parameters
----------
i, j : int
Index of particles in particles array.
i : int
Index of particle in particles array.
"""
@ -818,18 +860,10 @@ def _close_random_pack(domain, particles, contraction_rate):
# remove the rod currently containing k from the rod list and add rod
# k-i, keeping the rod list sorted
k, d_ik = nearest(i)
if k and nearest(k)[0] == i:
if (k and nearest(k)[0] == i and d_ik < outer_diameter
and not np.isclose(d, outer_diameter, atol=1.0e-14)):
remove_rod(k)
add_rod(d_ik, i, k)
l, d_jl = nearest(j)
if l and nearest(l)[0] == j:
remove_rod(l)
add_rod(d_jl, j, l)
# Set inner diameter to the shortest distance between two particle
# centers
if rods:
inner_diameter[0] = rods[0][0]
n_particles = len(particles)
diameter = 2*domain.particle_radius
@ -841,36 +875,71 @@ def _close_random_pack(domain, particles, contraction_rate):
initial_outer_diameter = 2*(domain.volume/(n_particles*4/3*pi))**(1/3)
# Inner and outer diameter of particles will change during packing
outer_diameter = [initial_outer_diameter]
inner_diameter = [0]
outer_diameter = initial_outer_diameter
inner_diameter = 0.
# List of rods arranged in a heap and mapping of particle ids to rods
rods = []
rods_map = {}
# Initialize two-way dictionary that identifies which particles are near a
# given mesh cell and which mesh cells a particle is near
mesh = defaultdict(set)
mesh_map = defaultdict(set)
for i in range(n_particles):
for idx in domain.nearby_mesh_cells(particles[i]):
mesh[idx].add(i)
mesh_map[i].add(idx)
while True:
# Rebuild the sorted list of rods according to the current particle
# configuration
create_rod_list()
if inner_diameter[0] >= diameter:
# Set the inner diameter to the shortest center-to-center distance
# between any two particles
if rods:
inner_diameter = rods[0][0]
# Reached the desired particle radius
if inner_diameter >= diameter:
break
# The algorithm converged before reaching the desired particle radius.
# This can happen when the desired packing fraction is close to the
# packing fraction limit. The packing fraction is a random variable
# that is determined by the particle locations and the contraction
# rate. A higher packing fraction can be achieved with a smaller
# contraction rate, though at the cost of a longer simulation time --
# the number of iterations needed to remove all overlaps is inversely
# proportional to the contraction rate.
if inner_diameter >= outer_diameter or not rods:
warnings.warn('Close random pack converged before reaching true '
'particle radius; some particles may overlap. Try '
'reducing contraction rate or packing fraction.')
break
while True:
d, i, j = pop_rod()
reduce_outer_diameter()
repel_particles(i, j, d)
update_rod_list(i, j)
if inner_diameter[0] >= diameter or not rods:
if not d:
break
outer_diameter = reduce_outer_diameter()
domain.repel_particles(particles[i], particles[j], d, outer_diameter)
update_mesh(i)
update_mesh(j)
update_rod_list(i)
update_rod_list(j)
if not rods:
break
inner_diameter = rods[0][0]
if inner_diameter >= diameter or inner_diameter >= outer_diameter:
break
def pack_trisos(radius, fill, domain_shape='cylinder', domain_length=None,
domain_radius=None, domain_center=[0., 0., 0.],
n_particles=None, packing_fraction=None,
initial_packing_fraction=0.3, contraction_rate=1/400, seed=1):
initial_packing_fraction=0.3, contraction_rate=1.e-3, seed=1):
"""Generate a random, non-overlapping configuration of TRISO particles
within a container.
@ -933,7 +1002,7 @@ def pack_trisos(radius, fill, domain_shape='cylinder', domain_length=None,
to speed up the nearest neighbor search by only searching for a particle's
neighbors within that mesh cell.
In CRP, each particle is assigned two diameters, and inner and an outer,
In CRP, each particle is assigned two diameters, an inner and an outer,
which approach each other during the simulation. The inner diameter,
defined as the minimum center-to-center distance, is the true diameter of
the particles and defines the pf. At each iteration the worst overlap
@ -1008,8 +1077,7 @@ def pack_trisos(radius, fill, domain_shape='cylinder', domain_length=None,
# Recalculate the limits for the initial random sequential packing using
# the desired final particle radius to ensure particles are fully contained
# within the domain during the close random pack
domain.limits = [[x - initial_radius + radius for x in domain.limits[0]],
[x + initial_radius - radius for x in domain.limits[1]]]
domain.limits = [x + initial_radius - radius for x in domain.limits]
# Generate non-overlapping particles for an initial inner radius using
# random sequential packing algorithm
@ -1024,5 +1092,5 @@ def pack_trisos(radius, fill, domain_shape='cylinder', domain_length=None,
trisos = []
for p in particles:
trisos.append(TRISO(radius, fill, p))
trisos.append(TRISO(radius, fill, [x + c for x, c in zip(p, domain.center)]))
return trisos

View file

@ -1,6 +1,7 @@
from collections.abc import Iterable, Mapping
from numbers import Real, Integral
from xml.etree import ElementTree as ET
import subprocess
import sys
import warnings
@ -650,6 +651,49 @@ class Plot(IDManagerMixin):
return element
def to_ipython_image(self, openmc_exec='openmc', cwd='.',
convert_exec='convert'):
"""Render plot as an image
This method runs OpenMC in plotting mode to produce a bitmap image which
is then converted to a .png file and loaded in as an
:class:`IPython.display.Image` object. As such, it requires that your
model geometry, materials, and settings have already been exported to
XML.
Parameters
----------
openmc_exec : str
Path to OpenMC executable
cwd : str, optional
Path to working directory to run in
convert_exec : str, optional
Command that can convert PPM files into PNG files
Returns
-------
IPython.display.Image
Image generated
"""
from IPython.display import Image
# Create plots.xml
Plots([self]).export_to_xml()
# Run OpenMC in geometry plotting mode
openmc.plot_geometry(False, openmc_exec, cwd)
# Convert to .png
if self.filename is not None:
ppm_file = '{}.ppm'.format(self.filename)
else:
ppm_file = 'plot_{}.ppm'.format(self.id)
png_file = ppm_file.replace('.ppm', '.png')
subprocess.check_call([convert_exec, ppm_file, png_file])
return Image(png_file)
class Plots(cv.CheckedList):
"""Collection of Plots used for an OpenMC simulation.

View file

@ -2,7 +2,6 @@ from numbers import Integral, Real
from itertools import chain
import string
import matplotlib.pyplot as plt
import numpy as np
import openmc.checkvalue as cv
@ -125,6 +124,8 @@ def plot_xs(this, types, divisor_types=None, temperature=294., data_type=None,
generated.
"""
import matplotlib.pyplot as plt
cv.check_type("plot_CE", plot_CE, bool)
if data_type is None:
@ -169,13 +170,13 @@ def plot_xs(this, types, divisor_types=None, temperature=294., data_type=None,
data = data_new
else:
# Calculate for MG cross sections
E, data = calculate_mgxs(this, types, orders, temperature,
E, data = calculate_mgxs(this, data_type, types, orders, temperature,
mg_cross_sections, ce_cross_sections,
enrichment)
if divisor_types:
cv.check_length('divisor types', divisor_types, len(types))
Ediv, data_div = calculate_mgxs(this, divisor_types,
Ediv, data_div = calculate_mgxs(this, data_type, divisor_types,
divisor_orders, temperature,
mg_cross_sections,
ce_cross_sections, enrichment)
@ -242,7 +243,7 @@ def calculate_cexs(this, data_type, types, temperature=294., sab_name=None,
Parameters
----------
this : str or openmc.Material
this : {str, openmc.Nuclide, openmc.Element, openmc.Material}
Object to source data from
data_type : {'nuclide', 'element', material'}
Type of object to plot
@ -279,7 +280,11 @@ def calculate_cexs(this, data_type, types, temperature=294., sab_name=None,
cv.check_type('enrichment', enrichment, Real)
if data_type == 'nuclide':
energy_grid, xs = _calculate_cexs_nuclide(this, types, temperature,
if isinstance(this, str):
nuc = openmc.Nuclide(this)
else:
nuc = this
energy_grid, xs = _calculate_cexs_nuclide(nuc, types, temperature,
sab_name, cross_sections)
# Convert xs (Iterable of Callable) to a grid of cross section values
# calculated on @ the points in energy_grid for consistency with the
@ -288,10 +293,15 @@ def calculate_cexs(this, data_type, types, temperature=294., sab_name=None,
for line in range(len(types)):
data[line, :] = xs[line](energy_grid)
elif data_type == 'element':
energy_grid, data = _calculate_cexs_elem_mat(this, types, temperature,
if isinstance(this, str):
elem = openmc.Element(this)
else:
elem = this
energy_grid, data = _calculate_cexs_elem_mat(elem, types, temperature,
cross_sections, sab_name,
enrichment)
elif data_type == 'material':
cv.check_type('this', this, openmc.Material)
energy_grid, data = _calculate_cexs_elem_mat(this, types, temperature,
cross_sections)
else:
@ -517,10 +527,8 @@ def _calculate_cexs_elem_mat(this, types, temperature=294.,
T = this.temperature
else:
T = temperature
data_type = 'material'
else:
T = temperature
data_type = 'element'
# Load the library
library = openmc.data.DataLibrary.from_xml(cross_sections)
@ -570,7 +578,7 @@ def _calculate_cexs_elem_mat(this, types, temperature=294.,
name = nuclide[0]
nuc = nuclide[1]
sab_tab = sabs[name]
temp_E, temp_xs = calculate_cexs(nuc, data_type, types, T, sab_tab,
temp_E, temp_xs = calculate_cexs(nuc, 'nuclide', types, T, sab_tab,
cross_sections)
E.append(temp_E)
# Since the energy grids are different, store the cross sections as

View file

@ -60,9 +60,9 @@ def _search_keff(guess, target, model_builder, model_args, print_iterations,
if print_iterations:
text = 'Iteration: {}; Guess of {:.2e} produced a keff of ' + \
'{:1.5f} +/- {:1.5f}'
print(text.format(len(guesses), guess, keff[0], keff[1]))
print(text.format(len(guesses), guess, keff.n, keff.s))
return (keff[0] - target)
return keff.n - target
def search_for_keff(model_builder, initial_guess=None, target=1.0,

View file

@ -1,3 +1,4 @@
from datetime import datetime
import re
import os
import warnings
@ -5,6 +6,7 @@ import glob
import numpy as np
import h5py
from uncertainties import ufloat
import openmc
import openmc.checkvalue as cv
@ -47,8 +49,8 @@ class StatePoint(object):
CMFD fission source distribution over all mesh cells and energy groups.
current_batch : int
Number of batches simulated
date_and_time : str
Date and time when simulation began
date_and_time : datetime.datetime
Date and time at which statepoint was written
entropy : numpy.ndarray
Shannon entropy of fission source at each batch
filters : dict
@ -59,8 +61,8 @@ class StatePoint(object):
global_tallies : numpy.ndarray of compound datatype
Global tallies for k-effective estimates and leakage. The compound
datatype has fields 'name', 'sum', 'sum_sq', 'mean', and 'std_dev'.
k_combined : list
Combined estimator for k-effective and its uncertainty
k_combined : uncertainties.UFloat
Combined estimator for k-effective
k_col_abs : float
Cross-product of collision and absorption estimates of k-effective
k_col_tra : float
@ -148,6 +150,8 @@ class StatePoint(object):
def __exit__(self, *exc):
self._f.close()
if self._summary is not None:
self._summary._f.close()
@property
def cmfd_on(self):
@ -187,7 +191,8 @@ class StatePoint(object):
@property
def date_and_time(self):
return self._f.attrs['date_and_time'].decode()
s = self._f.attrs['date_and_time'].decode()
return datetime.strptime(s, '%Y-%m-%d %H:%M:%S')
@property
def entropy(self):
@ -255,7 +260,7 @@ class StatePoint(object):
@property
def k_combined(self):
if self.run_mode == 'eigenvalue':
return self._f['k_combined'].value
return ufloat(*self._f['k_combined'].value)
else:
return None
@ -402,17 +407,10 @@ class StatePoint(object):
scores = group['score_bins'].value
n_score_bins = group['n_score_bins'].value
# Read scattering moment order strings (e.g., P3, Y1,2, etc.)
moments = group['moment_orders'].value
# Add the scores to the Tally
for j, score in enumerate(scores):
score = score.decode()
# If this is a moment, use generic moment order
pattern = r'-n$|-pn$|-yn$'
score = re.sub(pattern, '-' + moments[j].decode(), score)
tally.scores.append(score)
# Add Tally to the global dictionary of all Tallies
@ -457,7 +455,7 @@ class StatePoint(object):
@property
def version(self):
return tuple(self._f.attrs['version'])
return tuple(self._f.attrs['openmc_version'])
@property
def summary(self):

View file

@ -9,7 +9,7 @@ import openmc
import openmc.checkvalue as cv
from openmc.region import Region
_VERSION_SUMMARY = 5
_VERSION_SUMMARY = 6
class Summary(object):
@ -26,6 +26,8 @@ class Summary(object):
nuclides : dict
Dictionary whose keys are nuclide names and values are atomic weight
ratios.
macroscopics : list
Names of macroscopic data sets
version: tuple of int
Version of OpenMC
@ -44,13 +46,15 @@ class Summary(object):
self._fast_materials = {}
self._fast_surfaces = {}
self._fast_cells = {}
self._fast_universes = {}
self._fast_universes = {}
self._fast_lattices = {}
self._materials = openmc.Materials()
self._nuclides = {}
self._macroscopics = []
self._read_nuclides()
self._read_macroscopics()
with warnings.catch_warnings():
warnings.simplefilter("ignore", openmc.IDWarning)
self._read_geometry()
@ -71,15 +75,26 @@ class Summary(object):
def nuclides(self):
return self._nuclides
@property
def macroscopics(self):
return self._macroscopics
@property
def version(self):
return tuple(self._f.attrs['openmc_version'])
def _read_nuclides(self):
names = self._f['nuclides/names'].value
awrs = self._f['nuclides/awrs'].value
for name, awr in zip(names, awrs):
self._nuclides[name.decode()] = awr
if 'nuclides/names' in self._f:
names = self._f['nuclides/names'].value
awrs = self._f['nuclides/awrs'].value
for name, awr in zip(names, awrs):
self._nuclides[name.decode()] = awr
def _read_macroscopics(self):
if 'macroscopics/names' in self._f:
names = self._f['macroscopics/names'].value
for name in names:
self._macroscopics = name.decode()
def _read_geometry(self):
# Read in and initialize the Materials and Geometry

View file

@ -65,9 +65,7 @@ class Tally(IDManagerMixin):
triggers : list of openmc.Trigger
List of tally triggers
num_scores : int
Total number of scores, accounting for the fact that a single
user-specified score, e.g. scatter-P3 or flux-Y2,2, might have multiple
bins
Total number of scores
num_filter_bins : int
Total number of filter bins accounting for all filters
num_bins : int
@ -218,10 +216,9 @@ class Tally(IDManagerMixin):
f = h5py.File(self._sp_filename, 'r')
# Extract Tally data from the file
data = f['tallies/tally {0}/results'.format(
self.id)].value
sum = data[:,:,0]
sum_sq = data[:,:,1]
data = f['tallies/tally {0}/results'.format(self.id)].value
sum = data[:, :, 0]
sum_sq = data[:, :, 1]
# Reshape the results arrays
sum = np.reshape(sum, self.shape)
@ -251,7 +248,7 @@ class Tally(IDManagerMixin):
@property
def sum_sq(self):
if not self._sp_filename:
if not self._sp_filename or self.derived:
return None
if not self._results_read:
@ -273,8 +270,8 @@ class Tally(IDManagerMixin):
# Convert NumPy array to SciPy sparse LIL matrix
if self.sparse:
self._mean = \
sps.lil_matrix(self._mean.flatten(), self._mean.shape)
self._mean = sps.lil_matrix(self._mean.flatten(),
self._mean.shape)
if self.sparse:
return np.reshape(self._mean.toarray(), self.shape)
@ -295,8 +292,8 @@ class Tally(IDManagerMixin):
# Convert NumPy array to SciPy sparse LIL matrix
if self.sparse:
self._std_dev = \
sps.lil_matrix(self._std_dev.flatten(), self._std_dev.shape)
self._std_dev = sps.lil_matrix(self._std_dev.flatten(),
self._std_dev.shape)
self.with_batch_statistics = True
@ -389,6 +386,13 @@ class Tally(IDManagerMixin):
# If score is a string, strip whitespace
if isinstance(score, str):
# Check to see if scores are deprecated before storing
for deprecated in ['scatter-', 'nu-scatter-', 'scatter-p',
'nu-scatter-p', 'scatter-y', 'nu-scatter-y',
'flux-y', 'total-y']:
if score.startswith(deprecated):
msg = score.strip() + ' is no longer supported.'
raise ValueError(msg)
scores[i] = score.strip()
self._scores = cv.CheckedList(_SCORE_CLASSES, 'tally scores', scores)
@ -436,17 +440,16 @@ class Tally(IDManagerMixin):
# Convert NumPy arrays to SciPy sparse LIL matrices
if sparse and not self.sparse:
if self._sum is not None:
self._sum = \
sps.lil_matrix(self._sum.flatten(), self._sum.shape)
self._sum = sps.lil_matrix(self._sum.flatten(), self._sum.shape)
if self._sum_sq is not None:
self._sum_sq = \
sps.lil_matrix(self._sum_sq.flatten(), self._sum_sq.shape)
self._sum_sq = sps.lil_matrix(self._sum_sq.flatten(),
self._sum_sq.shape)
if self._mean is not None:
self._mean = \
sps.lil_matrix(self._mean.flatten(), self._mean.shape)
self._mean = sps.lil_matrix(self._mean.flatten(),
self._mean.shape)
if self._std_dev is not None:
self._std_dev = \
sps.lil_matrix(self._std_dev.flatten(), self._std_dev.shape)
self._std_dev = sps.lil_matrix(self._std_dev.flatten(),
self._std_dev.shape)
self._sparse = True
@ -776,11 +779,11 @@ class Tally(IDManagerMixin):
other_sum = other_copy.get_reshaped_data(value='sum')
if join_right:
merged_sum = \
np.concatenate((self_sum, other_sum), axis=merge_axis)
merged_sum = np.concatenate((self_sum, other_sum),
axis=merge_axis)
else:
merged_sum = \
np.concatenate((other_sum, self_sum), axis=merge_axis)
merged_sum = np.concatenate((other_sum, self_sum),
axis=merge_axis)
merged_tally._sum = np.reshape(merged_sum, merged_tally.shape)
@ -790,11 +793,11 @@ class Tally(IDManagerMixin):
other_sum_sq = other_copy.get_reshaped_data(value='sum_sq')
if join_right:
merged_sum_sq = \
np.concatenate((self_sum_sq, other_sum_sq), axis=merge_axis)
merged_sum_sq = np.concatenate((self_sum_sq, other_sum_sq),
axis=merge_axis)
else:
merged_sum_sq = \
np.concatenate((other_sum_sq, self_sum_sq), axis=merge_axis)
merged_sum_sq = np.concatenate((other_sum_sq, self_sum_sq),
axis=merge_axis)
merged_tally._sum_sq = np.reshape(merged_sum_sq, merged_tally.shape)
@ -804,11 +807,11 @@ class Tally(IDManagerMixin):
other_mean = other_copy.get_reshaped_data(value='mean')
if join_right:
merged_mean = \
np.concatenate((self_mean, other_mean), axis=merge_axis)
merged_mean = np.concatenate((self_mean, other_mean),
axis=merge_axis)
else:
merged_mean = \
np.concatenate((other_mean, self_mean), axis=merge_axis)
merged_mean = np.concatenate((other_mean, self_mean),
axis=merge_axis)
merged_tally._mean = np.reshape(merged_mean, merged_tally.shape)
@ -818,62 +821,19 @@ class Tally(IDManagerMixin):
other_std_dev = other_copy.get_reshaped_data(value='std_dev')
if join_right:
merged_std_dev = \
np.concatenate((self_std_dev, other_std_dev), axis=merge_axis)
merged_std_dev = np.concatenate((self_std_dev, other_std_dev),
axis=merge_axis)
else:
merged_std_dev = \
np.concatenate((other_std_dev, self_std_dev), axis=merge_axis)
merged_std_dev = np.concatenate((other_std_dev, self_std_dev),
axis=merge_axis)
merged_tally._std_dev = np.reshape(merged_std_dev, merged_tally.shape)
# Sparsify merged tally if both tallies are sparse
merged_tally.sparse = self.sparse and other.sparse
# Consolidate scatter and flux Legendre moment scores
merged_tally._consolidate_moment_scores()
return merged_tally
def _consolidate_moment_scores(self):
"""Remove redundant scattering and flux moment scores from a Tally."""
# Define regex for scatter, nu-scatter and flux moment scores
regex = [(r'^((?!nu-)scatter-\d)', r'^((?!nu-)scatter-(P|p)\d)'),
(r'nu-scatter-\d', r'nu-scatter-(P|p)\d'),
(r'flux-\d', r'flux-(P|p)\d')]
# Find all non-scattering and non-flux moment scores
scores = [x for x in self.scores if
re.search(r'^((?!scatter-).)*$', x)]
scores = [x for x in scores if
re.search(r'^((?!flux-).)*$', x)]
for regex_n, regex_pn in regex:
# Use regex to find score-(P)n scores
score_n = [x for x in self.scores if re.search(regex_n, x)]
score_pn = [x for x in self.scores if re.search(regex_pn, x)]
# Consolidate moment scores
if len(score_pn) > 0:
# Only keep the highest score-PN score
high_pn = sorted([x.lower() for x in score_pn])[-1]
pn = int(high_pn.split('-')[-1].replace('p', ''))
# Only keep the score-N scores with N > PN
score_n = sorted([x.lower() for x in score_n])
score_n = [x for x in score_n if (int(x.split('-')[1]) > pn)]
# Append highest score-PN and any higher score-N scores
scores.extend([high_pn] + score_n)
else:
scores.extend(score_n)
# Override Tally's scores with consolidated list of scores
self.scores = scores
def to_xml_element(self):
"""Return XML representation of the tally
@ -1003,35 +963,6 @@ class Tally(IDManagerMixin):
return filter_found
def get_filter_index(self, filter_type, filter_bin):
"""Returns the index in the Tally's results array for a Filter bin
Parameters
----------
filter_type : openmc.FilterMeta
Type of the filter, e.g. MeshFilter
filter_bin : int or tuple
The bin is an integer ID for 'material', 'surface', 'cell',
'cellborn', and 'universe' Filters. The bin is an integer for the
cell instance ID for 'distribcell' Filters. The bin is a 2-tuple of
floats for 'energy' and 'energyout' filters corresponding to the
energy boundaries of the bin of interest. The bin is a (x,y,z)
3-tuple for 'mesh' filters corresponding to the mesh cell of
interest.
Returns
-------
The index in the Tally data array for this filter bin
"""
# Find the equivalent Filter in this Tally's list of Filters
filter_found = self.find_filter(filter_type)
# Get the index for the requested bin from the Filter and return it
filter_index = filter_found.get_bin_index(filter_bin)
return filter_index
def get_nuclide_index(self, nuclide):
"""Returns the index in the Tally's results array for a Nuclide bin
@ -1151,50 +1082,28 @@ class Tally(IDManagerMixin):
# Loop over all of the Tally's Filters
for i, self_filter in enumerate(self.filters):
user_filter = False
# If a user-requested Filter, get the user-requested bins
for j, test_filter in enumerate(filters):
if type(self_filter) is test_filter:
bins = filter_bins[j]
user_filter = True
break
else:
# If not a user-requested Filter, get all bins
if isinstance(self_filter, openmc.DistribcellFilter):
# Create list of cell instance IDs for distribcell Filters
bins = list(range(self_filter.num_bins))
# If not a user-requested Filter, get all bins
if not user_filter:
# Create list of 2- or 3-tuples tuples for mesh cell bins
if isinstance(self_filter, openmc.MeshFilter):
dimension = self_filter.mesh.dimension
xyz = [range(1, x+1) for x in dimension]
bins = list(product(*xyz))
# Create list of 2-tuples for energy boundary bins
elif isinstance(self_filter, (openmc.EnergyFilter,
openmc.EnergyoutFilter, openmc.MuFilter,
openmc.PolarFilter, openmc.AzimuthalFilter)):
bins = []
for k in range(self_filter.num_bins):
bins.append((self_filter.bins[k], self_filter.bins[k+1]))
# Create list of cell instance IDs for distribcell Filters
elif isinstance(self_filter, openmc.DistribcellFilter):
bins = [b for b in range(self_filter.num_bins)]
# EnergyFunctionFilters don't have bins so just add a None
elif isinstance(self_filter, openmc.EnergyFunctionFilter):
# EnergyFunctionFilters don't have bins so just add a None
bins = [None]
# Create list of IDs for bins for all other filter types
else:
# Create list of IDs for bins for all other filter types
bins = self_filter.bins
# Initialize a NumPy array for the Filter bin indices
filter_indices.append(np.zeros(len(bins), dtype=np.int))
# Add indices for each bin in this Filter to the list
for j, bin in enumerate(bins):
filter_index = self.get_filter_index(type(self_filter), bin)
filter_indices[i][j] = filter_index
indices = np.array([self_filter.get_bin_index(b) for b in bins])
filter_indices.append(indices)
# Account for stride in each of the previous filters
for indices in filter_indices[:i]:
@ -1233,7 +1142,7 @@ class Tally(IDManagerMixin):
# Determine the score indices from any of the requested scores
if nuclides:
nuclide_indices = np.zeros(len(nuclides), dtype=np.int)
nuclide_indices = np.zeros(len(nuclides), dtype=int)
for i, nuclide in enumerate(nuclides):
nuclide_indices[i] = self.get_nuclide_index(nuclide)
@ -1272,7 +1181,7 @@ class Tally(IDManagerMixin):
# Determine the score indices from any of the requested scores
if scores:
score_indices = np.zeros(len(scores), dtype=np.int)
score_indices = np.zeros(len(scores), dtype=int)
for i, score in enumerate(scores):
score_indices[i] = self.get_score_index(score)
@ -1544,11 +1453,8 @@ class Tally(IDManagerMixin):
data = self.get_values(value=value)
# Build a new array shape with one dimension per filter
new_shape = ()
for self_filter in self.filters:
new_shape += (self_filter.num_bins, )
new_shape += (self.num_nuclides,)
new_shape += (self.num_scores,)
new_shape = tuple(f.num_bins for f in self.filters)
new_shape += (self.num_nuclides, self.num_scores)
# Reshape the data with one dimension for each filter
data = np.reshape(data, new_shape)
@ -1676,19 +1582,22 @@ class Tally(IDManagerMixin):
new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 +
data['other']['std. dev.']**2)
elif binary_op == '*':
self_rel_err = data['self']['std. dev.'] / data['self']['mean']
other_rel_err = data['other']['std. dev.'] / data['other']['mean']
with np.errstate(divide='ignore', invalid='ignore'):
self_rel_err = data['self']['std. dev.'] / data['self']['mean']
other_rel_err = data['other']['std. dev.'] / data['other']['mean']
new_tally._mean = data['self']['mean'] * data['other']['mean']
new_tally._std_dev = np.abs(new_tally.mean) * \
np.sqrt(self_rel_err**2 + other_rel_err**2)
elif binary_op == '/':
self_rel_err = data['self']['std. dev.'] / data['self']['mean']
other_rel_err = data['other']['std. dev.'] / data['other']['mean']
new_tally._mean = data['self']['mean'] / data['other']['mean']
with np.errstate(divide='ignore', invalid='ignore'):
self_rel_err = data['self']['std. dev.'] / data['self']['mean']
other_rel_err = data['other']['std. dev.'] / data['other']['mean']
new_tally._mean = data['self']['mean'] / data['other']['mean']
new_tally._std_dev = np.abs(new_tally.mean) * \
np.sqrt(self_rel_err**2 + other_rel_err**2)
elif binary_op == '^':
mean_ratio = data['other']['mean'] / data['self']['mean']
with np.errstate(divide='ignore', invalid='ignore'):
mean_ratio = data['other']['mean'] / data['self']['mean']
first_term = mean_ratio * data['self']['std. dev.']
second_term = \
np.log(data['self']['mean']) * data['other']['std. dev.']
@ -1955,14 +1864,14 @@ class Tally(IDManagerMixin):
elif isinstance(filter1, openmc.EnergyFunctionFilter):
filter1_bins = [None]
else:
filter1_bins = [filter1.get_bin(i) for i in range(filter1.num_bins)]
filter1_bins = filter1.bins
if isinstance(filter2, openmc.DistribcellFilter):
filter2_bins = [b for b in range(filter2.num_bins)]
elif isinstance(filter2, openmc.EnergyFunctionFilter):
filter2_bins = [None]
else:
filter2_bins = [filter2.get_bin(i) for i in range(filter2.num_bins)]
filter2_bins = filter2.bins
# Create variables to store views of data in the misaligned structure
mean = {}
@ -2603,7 +2512,8 @@ class Tally(IDManagerMixin):
new_tally = self * -1
return new_tally
def get_slice(self, scores=[], filters=[], filter_bins=[], nuclides=[]):
def get_slice(self, scores=[], filters=[], filter_bins=[], nuclides=[],
squeeze=False):
"""Build a sliced tally for the specified filters, scores and nuclides.
This method constructs a new tally to encapsulate a subset of the data
@ -2614,26 +2524,26 @@ class Tally(IDManagerMixin):
Parameters
----------
scores : list of str
A list of one or more score strings
(e.g., ['absorption', 'nu-fission']; default is [])
A list of one or more score strings (e.g., ['absorption',
'nu-fission']
filters : Iterable of openmc.FilterMeta
An iterable of filter types
(e.g., [MeshFilter, EnergyFilter]; default is [])
An iterable of filter types (e.g., [MeshFilter, EnergyFilter])
filter_bins : list of Iterables
A list of tuples of filter bins corresponding to the filter_types
parameter (e.g., [(1,), ((0., 0.625e-6),)]; default is []). Each
tuple contains bins to slice for the corresponding filter type in
the filters parameter. Each bins is the integer ID for 'material',
A list of iterables of filter bins corresponding to the specified
filter types (e.g., [(1,), ((0., 0.625e-6),)]). Each iterable
contains bins to slice for the corresponding filter type in the
filters parameter. Each bin is the integer ID for 'material',
'surface', 'cell', 'cellborn', and 'universe' Filters. Each bin is
an integer for the cell instance ID for 'distribcell' Filters. Each
bin is a 2-tuple of floats for 'energy' and 'energyout' filters
corresponding to the energy boundaries of the bin of interest. The
bin is an (x,y,z) 3-tuple for 'mesh' filters corresponding to the
mesh cell of interest. The order of the bins in the list must
correspond to the filter_types parameter.
correspond to the `filters` argument.
nuclides : list of str
A list of nuclide name strings
(e.g., ['U235', 'U238']; default is [])
A list of nuclide name strings (e.g., ['U235', 'U238'])
squeeze : bool
Whether to remove filters with only a single bin in the sliced tally
Returns
-------
@ -2713,32 +2623,29 @@ class Tally(IDManagerMixin):
# Determine the filter indices from any of the requested filters
for i, filter_type in enumerate(filters):
find_filter = new_tally.find_filter(filter_type)
f = new_tally.find_filter(filter_type)
# Remove filters with only a single bin if requested
if squeeze:
if len(filter_bins[i]) == 1:
new_tally.filters.remove(f)
continue
else:
raise RuntimeError('Cannot remove sliced filter with '
'more than one bin.')
# Remove and/or reorder filter bins to user specifications
bin_indices = []
bin_indices = [f.get_bin_index(b)
for b in filter_bins[i]]
bin_indices = np.unique(bin_indices)
for filter_bin in filter_bins[i]:
bin_index = find_filter.get_bin_index(filter_bin)
if issubclass(filter_type, openmc.RealFilter):
bin_indices.extend([bin_index, bin_index+1])
else:
bin_indices.append(bin_index)
# Set bins for mesh/distribcell filters apart from others
if filter_type is openmc.MeshFilter:
bins = find_filter.mesh
elif filter_type is openmc.DistribcellFilter:
bins = find_filter.bins
else:
bins = np.unique(find_filter.bins[bin_indices])
# Create new filter
new_filter = filter_type(bins)
# Set bins for sliced filter
new_filter = copy.copy(f)
new_filter.bins = [f.bins[i] for i in bin_indices]
# Set number of bins manually for mesh/distribcell filters
if filter_type in (openmc.DistribcellFilter, openmc.MeshFilter):
new_filter._num_bins = find_filter._num_bins
if filter_type is openmc.DistribcellFilter:
new_filter._num_bins = f._num_bins
# Replace existing filter with new one
for j, test_filter in enumerate(new_tally.filters):
@ -2815,9 +2722,7 @@ class Tally(IDManagerMixin):
elif isinstance(find_filter, openmc.EnergyFunctionFilter):
filter_bins = [None]
else:
num_bins = find_filter.num_bins
filter_bins = \
[(find_filter.get_bin(i)) for i in range(num_bins)]
filter_bins = find_filter.bins
# Only sum across bins specified by the user
else:
@ -2826,7 +2731,7 @@ class Tally(IDManagerMixin):
# Sum across the bins in the user-specified filter
for i, self_filter in enumerate(self.filters):
if isinstance(self_filter, filter_type):
if type(self_filter) == filter_type:
shape = mean.shape
mean = np.take(mean, indices=bin_indices, axis=i)
std_dev = np.take(std_dev, indices=bin_indices, axis=i)
@ -2969,9 +2874,7 @@ class Tally(IDManagerMixin):
elif isinstance(find_filter, openmc.EnergyFunctionFilter):
filter_bins = [None]
else:
num_bins = find_filter.num_bins
filter_bins = \
[(find_filter.get_bin(i)) for i in range(num_bins)]
filter_bins = find_filter.bins
# Only average across bins specified by the user
else:
@ -3068,7 +2971,7 @@ class Tally(IDManagerMixin):
The data in the derived tally arrays is "diagonalized" along the bins in
the new filter. This functionality is used by the openmc.mgxs module; to
transport-correct scattering matrices by subtracting a 'scatter-P1'
reaction rate tally with an energy filter from an 'scatter' reaction
reaction rate tally with an energy filter from a 'scatter' reaction
rate tally with both energy and energyout filters.
Parameters
@ -3087,7 +2990,7 @@ class Tally(IDManagerMixin):
if new_filter in self.filters:
msg = 'Unable to diagonalize Tally ID="{0}" which already ' \
'contains a "{1}" filter'.format(self.id, new_filter.type)
'contains a "{1}" filter'.format(self.id, type(new_filter))
raise ValueError(msg)
# Add the new filter to a copy of this Tally
@ -3098,8 +3001,8 @@ class Tally(IDManagerMixin):
# by which the "base" indices should be repeated to account for all
# other filter bins in the diagonalized tally
indices = np.arange(0, new_filter.num_bins**2, new_filter.num_bins+1)
diag_factor = int(self.num_filter_bins / new_filter.num_bins)
diag_indices = np.zeros(self.num_filter_bins, dtype=np.int)
diag_factor = self.num_filter_bins // new_filter.num_bins
diag_indices = np.zeros(self.num_filter_bins, dtype=int)
# Determine the filter indices along the new "diagonal"
for i in range(diag_factor):

View file

@ -4,7 +4,6 @@ from numbers import Integral, Real
import random
import sys
import matplotlib.pyplot as plt
import numpy as np
import openmc
@ -146,7 +145,7 @@ class Universe(IDManagerMixin):
"""
if volume_calc.domain_type == 'universe':
if self.id in volume_calc.volumes:
self._volume = volume_calc.volumes[self.id][0]
self._volume = volume_calc.volumes[self.id].n
self._atoms = volume_calc.atoms[self.id]
else:
raise ValueError('No volume information found for this universe.')
@ -184,10 +183,14 @@ class Universe(IDManagerMixin):
return []
def plot(self, origin=(0., 0., 0.), width=(1., 1.), pixels=(200, 200),
basis='xy', color_by='cell', colors=None, filename=None, seed=None,
basis='xy', color_by='cell', colors=None, seed=None,
**kwargs):
"""Display a slice plot of the universe.
To display or save the plot, call :func:`matplotlib.pyplot.show` or
:func:`matplotlib.pyplot.savefig`. In a Jupyter notebook, enabling the
matplotlib inline backend will show the plot inline.
Parameters
----------
origin : Iterable of float
@ -212,9 +215,6 @@ class Universe(IDManagerMixin):
water = openmc.Cell(fill=h2o)
universe.plot(..., colors={water: (0., 0., 1.))
filename : str or None
Filename to save plot to. If no filename is given, the plot will be
displayed using the currently enabled matplotlib backend.
seed : hashable object or None
Hashable object which is used to seed the random number generator
used to select colors. If None, the generator is seeded from the
@ -223,7 +223,14 @@ class Universe(IDManagerMixin):
All keyword arguments are passed to
:func:`matplotlib.pyplot.imshow`.
Returns
-------
matplotlib.image.AxesImage
Resulting image
"""
import matplotlib.pyplot as plt
# Seed the random number generator
if seed is not None:
random.seed(seed)
@ -298,14 +305,8 @@ class Universe(IDManagerMixin):
img[j, i, :] = colors[obj]
# Display image
plt.imshow(img, extent=(x_min, x_max, y_min, y_max),
interpolation='nearest', **kwargs)
# Show or save the plot
if filename is None:
plt.show()
else:
plt.savefig(filename)
return plt.imshow(img, extent=(x_min, x_max, y_min, y_max),
interpolation='nearest', **kwargs)
def add_cell(self, cell):
"""Add a cell to the universe.
@ -405,7 +406,7 @@ class Universe(IDManagerMixin):
volume = self.volume
for name, atoms in self._atoms.items():
nuclide = openmc.Nuclide(name)
density = 1.0e-24 * atoms[0]/volume # density in atoms/b-cm
density = 1.0e-24 * atoms.n/volume # density in atoms/b-cm
nuclides[name] = (nuclide, density)
else:
raise RuntimeError(

View file

@ -7,6 +7,7 @@ import warnings
import numpy as np
import pandas as pd
import h5py
from uncertainties import ufloat
import openmc
import openmc.checkvalue as cv
@ -137,11 +138,10 @@ class VolumeCalculation(object):
@property
def atoms_dataframe(self):
items = []
columns = [self.domain_type.capitalize(), 'Nuclide', 'Atoms',
'Uncertainty']
columns = [self.domain_type.capitalize(), 'Nuclide', 'Atoms']
for uid, atoms_dict in self.atoms.items():
for name, atoms in atoms_dict.items():
items.append((uid, name, atoms[0], atoms[1]))
items.append((uid, name, atoms))
return pd.DataFrame.from_records(items, columns=columns)
@ -211,13 +211,13 @@ class VolumeCalculation(object):
domain_id = int(obj_name[7:])
ids.append(domain_id)
group = f[obj_name]
volume = tuple(group['volume'].value)
volume = ufloat(*group['volume'].value)
nucnames = group['nuclides'].value
atoms_ = group['atoms'].value
atom_dict = OrderedDict()
for name_i, atoms_i in zip(nucnames, atoms_):
atom_dict[name_i.decode()] = tuple(atoms_i)
atom_dict[name_i.decode()] = ufloat(*atoms_i)
volumes[domain_id] = volume
atoms[domain_id] = atom_dict

View file

@ -41,7 +41,7 @@ parser.add_argument('-b', '--batch', action='store_true',
parser.add_argument('-d', '--destination', default='jeff-3.2-hdf5',
help='Directory to create new library in')
parser.add_argument('--libver', choices=['earliest', 'latest'],
default='earliest', help="Output HDF5 versioning. Use "
default='latest', help="Output HDF5 versioning. Use "
"'earliest' for backwards compatibility or 'latest' for "
"performance")
args = parser.parse_args()

View file

@ -0,0 +1,33 @@
#!/usr/bin/env python3
import glob
import os
from zipfile import ZipFile
from openmc._utils import download
import openmc.deplete
URLS = [
'http://www.nndc.bnl.gov/endf/b7.1/zips/ENDF-B-VII.1-neutrons.zip',
'http://www.nndc.bnl.gov/endf/b7.1/zips/ENDF-B-VII.1-decay.zip',
'http://www.nndc.bnl.gov/endf/b7.1/zips/ENDF-B-VII.1-nfy.zip'
]
def main():
for url in URLS:
basename = download(url)
with ZipFile(basename, 'r') as zf:
print('Extracting {}...'.format(basename))
zf.extractall()
decay_files = glob.glob(os.path.join('decay', '*.endf'))
nfy_files = glob.glob(os.path.join('nfy', '*.endf'))
neutron_files = glob.glob(os.path.join('neutrons', '*.endf'))
chain = openmc.deplete.Chain.from_endf(decay_files, nfy_files, neutron_files)
chain.export_to_xml('chain_endfb71.xml')
if __name__ == '__main__':
main()

View file

@ -1,12 +1,10 @@
module angle_distribution
use hdf5, only: HID_T, HSIZE_T
use algorithm, only: binary_search
use constants, only: ZERO, ONE, HISTOGRAM, LINEAR_LINEAR
use distribution_univariate, only: DistributionContainer, Tabular
use hdf5_interface, only: read_attribute, get_shape, read_dataset, &
open_dataset, close_dataset
open_dataset, close_dataset, HID_T, HSIZE_T
use random_lcg, only: prn
implicit none

View file

@ -1,6 +1,6 @@
module angleenergy_header
use hdf5, only: HID_T
use hdf5_interface, only: HID_T
!===============================================================================
! ANGLEENERGY (abstract) defines a correlated or uncorrelated angle-energy

View file

@ -2,8 +2,6 @@ module openmc_api
use, intrinsic :: ISO_C_BINDING
use hdf5, only: HID_T, h5tclose_f, h5close_f
use bank_header, only: openmc_source_bank
use constants, only: K_BOLTZMANN
use eigenvalue, only: k_sum, openmc_get_keff
@ -12,10 +10,11 @@ module openmc_api
use geometry_header
use hdf5_interface
use material_header
use math
use mesh_header
use message_passing
use nuclide_header
use initialize, only: openmc_init
use initialize, only: openmc_init_f
use particle_header, only: Particle
use plot, only: openmc_plot_geometry
use random_lcg, only: openmc_get_seed, openmc_set_seed
@ -36,6 +35,7 @@ module openmc_api
private
public :: openmc_calculate_volumes
public :: openmc_cell_filter_get_bins
public :: openmc_cell_get_id
public :: openmc_cell_get_fill
public :: openmc_cell_set_fill
@ -64,7 +64,7 @@ module openmc_api
public :: openmc_get_tally_index
public :: openmc_global_tallies
public :: openmc_hard_reset
public :: openmc_init
public :: openmc_init_f
public :: openmc_load_nuclide
public :: openmc_material_add_nuclide
public :: openmc_material_get_id
@ -75,11 +75,11 @@ module openmc_api
public :: openmc_material_filter_get_bins
public :: openmc_material_filter_set_bins
public :: openmc_mesh_filter_set_mesh
public :: openmc_meshsurface_filter_set_mesh
public :: openmc_next_batch
public :: openmc_nuclide_name
public :: openmc_plot_geometry
public :: openmc_reset
public :: openmc_run
public :: openmc_set_seed
public :: openmc_simulation_finalize
public :: openmc_simulation_init
@ -104,12 +104,16 @@ contains
! variables
!===============================================================================
subroutine openmc_finalize() bind(C)
function openmc_finalize() result(err) bind(C)
integer(C_INT) :: err
integer :: err
interface
subroutine openmc_free_bank() bind(C)
end subroutine openmc_free_bank
end interface
! Clear results
call openmc_reset()
err = openmc_reset()
! Reset global variables
assume_separate = .false.
@ -121,9 +125,11 @@ contains
energy_min_neutron = ZERO
entropy_on = .false.
gen_per_batch = 1
index_entropy_mesh = -1
index_ufs_mesh = -1
keff = ONE
legendre_to_tabular = .true.
legendre_to_tabular_points = 33
legendre_to_tabular_points = C_NONE
n_batch_interval = 1
n_lost_particles = 0
n_particles = 0
@ -167,18 +173,14 @@ contains
! Deallocate arrays
call free_memory()
! Release compound datatypes
call h5tclose_f(hdf5_bank_t, err)
! Close FORTRAN interface.
call h5close_f(err)
err = 0
#ifdef OPENMC_MPI
! Free all MPI types
call MPI_TYPE_FREE(MPI_BANK, err)
call openmc_free_bank()
#endif
end subroutine openmc_finalize
end function openmc_finalize
!===============================================================================
! OPENMC_FIND determines the ID or a cell or material at a given point in space
@ -205,7 +207,7 @@ contains
if (found) then
if (rtype == 1) then
id = cells(p % coord(p % n_coord) % cell) % id
id = cells(p % coord(p % n_coord) % cell) % id()
elseif (rtype == 2) then
if (p % material == MATERIAL_VOID) then
id = 0
@ -229,9 +231,11 @@ contains
! generator state
!===============================================================================
subroutine openmc_hard_reset() bind(C)
function openmc_hard_reset() result(err) bind(C)
integer(C_INT) :: err
! Reset all tallies and timers
call openmc_reset()
err = openmc_reset()
! Reset total generations and keff guess
keff = ONE
@ -239,13 +243,15 @@ contains
! Reset the random number generator state
call openmc_set_seed(DEFAULT_SEED)
end subroutine openmc_hard_reset
end function openmc_hard_reset
!===============================================================================
! OPENMC_RESET resets tallies and timers
!===============================================================================
subroutine openmc_reset() bind(C)
function openmc_reset() result(err) bind(C)
integer(C_INT) :: err
integer :: i
if (allocated(tallies)) then
@ -273,8 +279,9 @@ contains
! Clear active tally lists
call active_analog_tallies % clear()
call active_tracklength_tallies % clear()
call active_current_tallies % clear()
call active_meshsurf_tallies % clear()
call active_collision_tallies % clear()
call active_surface_tallies % clear()
call active_tallies % clear()
! Reset timers
@ -292,7 +299,8 @@ contains
call time_transport % reset()
call time_finalize % reset()
end subroutine openmc_reset
err = 0
end function openmc_reset
!===============================================================================
! FREE_MEMORY deallocates and clears all global allocatable arrays in the
@ -302,7 +310,6 @@ contains
subroutine free_memory()
use cmfd_header
use mgxs_header
use plot_header
use sab_header
use settings
@ -320,7 +327,6 @@ contains
call free_memory_simulation()
call free_memory_nuclide()
call free_memory_settings()
call free_memory_mgxs()
call free_memory_sab()
call free_memory_source()
call free_memory_mesh()

523
src/cell.cpp Normal file
View file

@ -0,0 +1,523 @@
#include "cell.h"
#include <cmath>
#include <limits>
#include <sstream>
#include <string>
#include "constants.h"
#include "error.h"
#include "hdf5_interface.h"
#include "lattice.h"
#include "surface.h"
#include "xml_interface.h"
namespace openmc {
//==============================================================================
// Constants
//==============================================================================
constexpr int32_t OP_LEFT_PAREN {std::numeric_limits<int32_t>::max()};
constexpr int32_t OP_RIGHT_PAREN {std::numeric_limits<int32_t>::max() - 1};
constexpr int32_t OP_COMPLEMENT {std::numeric_limits<int32_t>::max() - 2};
constexpr int32_t OP_INTERSECTION {std::numeric_limits<int32_t>::max() - 3};
constexpr int32_t OP_UNION {std::numeric_limits<int32_t>::max() - 4};
extern "C" double FP_PRECISION;
//==============================================================================
// Global variables
//==============================================================================
int32_t n_cells {0};
std::vector<Cell*> global_cells;
std::unordered_map<int32_t, int32_t> cell_map;
std::vector<Universe*> global_universes;
std::unordered_map<int32_t, int32_t> universe_map;
//==============================================================================
//! Convert region specification string to integer tokens.
//!
//! The characters (, ), |, and ~ count as separate tokens since they represent
//! operators.
//==============================================================================
std::vector<int32_t>
tokenize(const std::string region_spec) {
// Check for an empty region_spec first.
std::vector<int32_t> tokens;
if (region_spec.empty()) {
return tokens;
}
// Parse all halfspaces and operators except for intersection (whitespace).
for (int i = 0; i < region_spec.size(); ) {
if (region_spec[i] == '(') {
tokens.push_back(OP_LEFT_PAREN);
i++;
} else if (region_spec[i] == ')') {
tokens.push_back(OP_RIGHT_PAREN);
i++;
} else if (region_spec[i] == '|') {
tokens.push_back(OP_UNION);
i++;
} else if (region_spec[i] == '~') {
tokens.push_back(OP_COMPLEMENT);
i++;
} else if (region_spec[i] == '-' || region_spec[i] == '+'
|| std::isdigit(region_spec[i])) {
// This is the start of a halfspace specification. Iterate j until we
// find the end, then push-back everything between i and j.
int j = i + 1;
while (j < region_spec.size() && std::isdigit(region_spec[j])) {j++;}
tokens.push_back(std::stoi(region_spec.substr(i, j-i)));
i = j;
} else if (std::isspace(region_spec[i])) {
i++;
} else {
std::stringstream err_msg;
err_msg << "Region specification contains invalid character, \""
<< region_spec[i] << "\"";
fatal_error(err_msg);
}
}
// Add in intersection operators where a missing operator is needed.
int i = 0;
while (i < tokens.size()-1) {
bool left_compat {(tokens[i] < OP_UNION) || (tokens[i] == OP_RIGHT_PAREN)};
bool right_compat {(tokens[i+1] < OP_UNION)
|| (tokens[i+1] == OP_LEFT_PAREN)
|| (tokens[i+1] == OP_COMPLEMENT)};
if (left_compat && right_compat) {
tokens.insert(tokens.begin()+i+1, OP_INTERSECTION);
}
i++;
}
return tokens;
}
//==============================================================================
//! Convert infix region specification to Reverse Polish Notation (RPN)
//!
//! This function uses the shunting-yard algorithm.
//==============================================================================
std::vector<int32_t>
generate_rpn(int32_t cell_id, std::vector<int32_t> infix)
{
std::vector<int32_t> rpn;
std::vector<int32_t> stack;
for (int32_t token : infix) {
if (token < OP_UNION) {
// If token is not an operator, add it to output
rpn.push_back(token);
} else if (token < OP_RIGHT_PAREN) {
// Regular operators union, intersection, complement
while (stack.size() > 0) {
int32_t op = stack.back();
if (op < OP_RIGHT_PAREN &&
((token == OP_COMPLEMENT && token < op) ||
(token != OP_COMPLEMENT && token <= op))) {
// While there is an operator, op, on top of the stack, if the token
// is left-associative and its precedence is less than or equal to
// that of op or if the token is right-associative and its precedence
// is less than that of op, move op to the output queue and push the
// token on to the stack. Note that only complement is
// right-associative.
rpn.push_back(op);
stack.pop_back();
} else {
break;
}
}
stack.push_back(token);
} else if (token == OP_LEFT_PAREN) {
// If the token is a left parenthesis, push it onto the stack
stack.push_back(token);
} else {
// If the token is a right parenthesis, move operators from the stack to
// the output queue until reaching the left parenthesis.
for (auto it = stack.rbegin(); *it != OP_LEFT_PAREN; it++) {
// 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);
}
rpn.push_back(stack.back());
stack.pop_back();
}
// Pop the left parenthesis.
stack.pop_back();
}
}
while (stack.size() > 0) {
int32_t op = stack.back();
// 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);
}
rpn.push_back(stack.back());
stack.pop_back();
}
return rpn;
}
//==============================================================================
// Cell implementation
//==============================================================================
Cell::Cell(pugi::xml_node cell_node)
{
if (check_for_node(cell_node, "id")) {
id = stoi(get_node_value(cell_node, "id"));
} else {
fatal_error("Must specify id of cell in geometry XML file.");
}
//TODO: don't automatically lowercase cell and surface names
if (check_for_node(cell_node, "name")) {
name = get_node_value(cell_node, "name");
}
if (check_for_node(cell_node, "universe")) {
universe = stoi(get_node_value(cell_node, "universe"));
} else {
universe = 0;
}
if (check_for_node(cell_node, "fill")) {
fill = stoi(get_node_value(cell_node, "fill"));
} else {
fill = C_NONE;
}
if (check_for_node(cell_node, "material")) {
//TODO: read material ids.
material.push_back(C_NONE+1);
material.shrink_to_fit();
} else {
material.push_back(C_NONE);
material.shrink_to_fit();
}
// Make sure that either material or fill was specified.
if ((material[0] == C_NONE) && (fill == C_NONE)) {
std::stringstream err_msg;
err_msg << "Neither material nor fill was specified for cell " << id;
fatal_error(err_msg);
}
// Make sure that material and fill haven't been specified simultaneously.
if ((material[0] != C_NONE) && (fill != C_NONE)) {
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);
}
// Read the region specification.
std::string region_spec;
if (check_for_node(cell_node, "region")) {
region_spec = get_node_value(cell_node, "region");
}
// Get a tokenized representation of the region specification.
region = tokenize(region_spec);
region.shrink_to_fit();
// Convert user IDs to surface indices.
for (auto &r : region) {
if (r < OP_UNION) {
r = copysign(surface_map[abs(r)] + 1, r);
}
}
// Convert the infix region spec to RPN.
rpn = generate_rpn(id, region);
rpn.shrink_to_fit();
// Check if this is a simple cell.
simple = true;
for (int32_t token : rpn) {
if ((token == OP_COMPLEMENT) || (token == OP_UNION)) {
simple = false;
break;
}
}
}
//==============================================================================
bool
Cell::contains(const double xyz[3], const double uvw[3],
int32_t on_surface) const
{
if (simple) {
return contains_simple(xyz, uvw, on_surface);
} else {
return contains_complex(xyz, uvw, on_surface);
}
}
//==============================================================================
std::pair<double, int32_t>
Cell::distance(const double xyz[3], const double uvw[3],
int32_t on_surface) const
{
double min_dist {INFTY};
int32_t i_surf {std::numeric_limits<int32_t>::max()};
for (int32_t token : rpn) {
// Ignore this token if it corresponds to an operator rather than a region.
if (token >= OP_UNION) {continue;}
// Calculate the distance to this surface.
// Note the off-by-one indexing
bool coincident {token == on_surface};
double d {surfaces_c[abs(token)-1]->distance(xyz, uvw, coincident)};
// Check if this distance is the new minimum.
if (d < min_dist) {
if (std::abs(d - min_dist) / min_dist >= FP_PRECISION) {
min_dist = d;
i_surf = -token;
}
}
}
return {min_dist, i_surf};
}
//==============================================================================
void
Cell::to_hdf5(hid_t cell_group) const
{
if (!name.empty()) {
write_string(cell_group, "name", name, false);
}
//TODO: Fix the off-by-one indexing.
write_int(cell_group, 0, nullptr, "universe",
&global_universes[universe-1]->id, false);
// Write the region specification.
if (!region.empty()) {
std::stringstream region_spec {};
for (int32_t token : region) {
if (token == OP_LEFT_PAREN) {
region_spec << " (";
} else if (token == OP_RIGHT_PAREN) {
region_spec << " )";
} else if (token == OP_COMPLEMENT) {
region_spec << " ~";
} else if (token == OP_INTERSECTION) {
} else if (token == OP_UNION) {
region_spec << " |";
} else {
// Note the off-by-one indexing
region_spec << " " << copysign(surfaces_c[abs(token)-1]->id, token);
}
}
write_string(cell_group, "region", region_spec.str(), false);
}
}
//==============================================================================
bool
Cell::contains_simple(const double xyz[3], const double uvw[3],
int32_t on_surface) const
{
for (int32_t token : rpn) {
if (token < OP_UNION) {
// If the token is not an operator, evaluate the sense of particle with
// respect to the surface and see if the token matches the sense. If the
// particle's surface attribute is set and matches the token, that
// overrides the determination based on sense().
if (token == on_surface) {
} else if (-token == on_surface) {
return false;
} else {
// Note the off-by-one indexing
bool sense = surfaces_c[abs(token)-1]->sense(xyz, uvw);
if (sense != (token > 0)) {return false;}
}
}
}
return true;
}
//==============================================================================
bool
Cell::contains_complex(const double xyz[3], const double uvw[3],
int32_t on_surface) const
{
// Make a stack of booleans. We don't know how big it needs to be, but we do
// know that rpn.size() is an upper-bound.
bool stack[rpn.size()];
int i_stack = -1;
for (int32_t token : rpn) {
// If the token is a binary operator (intersection/union), apply it to
// the last two items on the stack. If the token is a unary operator
// (complement), apply it to the last item on the stack.
if (token == OP_UNION) {
stack[i_stack-1] = stack[i_stack-1] || stack[i_stack];
i_stack --;
} else if (token == OP_INTERSECTION) {
stack[i_stack-1] = stack[i_stack-1] && stack[i_stack];
i_stack --;
} else if (token == OP_COMPLEMENT) {
stack[i_stack] = !stack[i_stack];
} else {
// If the token is not an operator, evaluate the sense of particle with
// respect to the surface and see if the token matches the sense. If the
// particle's surface attribute is set and matches the token, that
// overrides the determination based on sense().
i_stack ++;
if (token == on_surface) {
stack[i_stack] = true;
} else if (-token == on_surface) {
stack[i_stack] = false;
} else {
// Note the off-by-one indexing
bool sense = surfaces_c[abs(token)-1]->sense(xyz, uvw);;
stack[i_stack] = (sense == (token > 0));
}
}
}
if (i_stack == 0) {
// The one remaining bool on the stack indicates whether the particle is
// in the cell.
return stack[i_stack];
} else {
// This case occurs if there is no region specification since i_stack will
// still be -1.
return true;
}
}
//==============================================================================
// Non-method functions
//==============================================================================
extern "C" void
read_cells(pugi::xml_node *node)
{
// Count the number of cells.
for (pugi::xml_node cell_node: node->children("cell")) {n_cells++;}
if (n_cells == 0) {
fatal_error("No cells found in geometry.xml!");
}
// Allocate the vector of Cells.
global_cells.reserve(n_cells);
// Loop over XML cell elements and populate the array.
for (pugi::xml_node cell_node: node->children("cell")) {
global_cells.push_back(new Cell(cell_node));
}
// Populate the Universe vector and map.
for (int i = 0; i < global_cells.size(); i++) {
int32_t uid = global_cells[i]->universe;
auto it = universe_map.find(uid);
if (it == universe_map.end()) {
global_universes.push_back(new Universe());
global_universes.back()->id = uid;
global_universes.back()->cells.push_back(i);
universe_map[uid] = global_universes.size() - 1;
} else {
global_universes[it->second]->cells.push_back(i);
}
}
}
//==============================================================================
// Fortran compatibility functions
//==============================================================================
extern "C" {
Cell* cell_pointer(int32_t cell_ind) {return global_cells[cell_ind];}
int32_t cell_id(Cell *c) {return c->id;}
void cell_set_id(Cell *c, int32_t id) {c->id = id;}
int cell_type(Cell *c) {return c->type;}
void cell_set_type(Cell *c, int type) {c->type = type;}
int32_t cell_universe(Cell *c) {return c->universe;}
void cell_set_universe(Cell *c, int32_t universe) {c->universe = universe;}
int32_t cell_fill(Cell *c) {return c->fill;}
int32_t* cell_fill_ptr(Cell *c) {return &c->fill;}
int32_t cell_n_instances(Cell *c) {return c->n_instances;}
bool cell_simple(Cell *c) {return c->simple;}
bool cell_contains(Cell *c, double xyz[3], double uvw[3], int32_t on_surface)
{return c->contains(xyz, uvw, on_surface);}
void cell_distance(Cell *c, double xyz[3], double uvw[3], int32_t on_surface,
double *min_dist, int32_t *i_surf)
{
std::pair<double, int32_t> out = c->distance(xyz, uvw, on_surface);
*min_dist = out.first;
*i_surf = out.second;
}
int32_t cell_offset(Cell *c, int map) {return c->offset[map];}
void cell_to_hdf5(Cell *c, hid_t group) {c->to_hdf5(group);}
void extend_cells_c(int32_t n)
{
global_cells.reserve(global_cells.size() + n);
for (int32_t i = 0; i < n; i++) {
global_cells.push_back(new Cell());
}
n_cells = global_cells.size();
}
}
} // namespace openmc

118
src/cell.h Normal file
View file

@ -0,0 +1,118 @@
#ifndef CELL_H
#define CELL_H
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
#include "hdf5.h"
#include "pugixml.hpp"
namespace openmc {
//==============================================================================
// Constants
//==============================================================================
extern "C" int FILL_MATERIAL;
extern "C" int FILL_UNIVERSE;
extern "C" int FILL_LATTICE;
//==============================================================================
// Global variables
//==============================================================================
extern "C" int32_t n_cells;
class Cell;
extern std::vector<Cell*> global_cells;
extern std::unordered_map<int32_t, int32_t> cell_map;
class Universe;
extern std::vector<Universe*> global_universes;
extern std::unordered_map<int32_t, int32_t> universe_map;
//==============================================================================
//! A geometry primitive that fills all space and contains cells.
//==============================================================================
class Universe
{
public:
int32_t id; //!< Unique ID
std::vector<int32_t> cells; //!< Cells within this universe
//double x0, y0, z0; //!< Translation coordinates.
};
//==============================================================================
//! A geometry primitive that links surfaces, universes, and materials
//==============================================================================
class Cell
{
public:
int32_t id; //!< Unique ID
std::string name; //!< User-defined name
int type; //!< Material, universe, or lattice
int32_t universe; //!< Universe # this cell is in
int32_t fill; //!< Universe # filling this cell
int32_t n_instances{0}; //!< Number of instances of this cell
//! \brief Material(s) within this cell.
//!
//! May be multiple materials for distribcell. C_NONE signifies a universe.
std::vector<int32_t> material;
//! Definition of spatial region as Boolean expression of half-spaces
std::vector<std::int32_t> region;
//! Reverse Polish notation for region expression
std::vector<std::int32_t> rpn;
bool simple; //!< Does the region contain only intersections?
std::vector<int32_t> offset; //!< Distribcell offset table
Cell() {};
explicit Cell(pugi::xml_node cell_node);
//! \brief Determine if a cell contains the particle at a given location.
//!
//! The bounds of the cell are detemined by a logical expression involving
//! surface half-spaces. At initialization, the expression was converted
//! to RPN notation.
//!
//! The function is split into two cases, one for simple cells (those
//! involving only the intersection of half-spaces) and one for complex cells.
//! Simple cells can be evaluated with short circuit evaluation, i.e., as soon
//! as we know that one half-space is not satisfied, we can exit. This
//! provides a performance benefit for the common case. In
//! contains_complex, we evaluate the RPN expression using a stack, similar to
//! how a RPN calculator would work.
//! @param xyz[3] The 3D Cartesian coordinate to check.
//! @param uvw[3] A direction used to "break ties" the coordinates are very
//! close to a surface.
//! @param on_surface The signed index of a surface that the coordinate is
//! known to be on. This index takes precedence over surface sense
//! calculations.
bool
contains(const double xyz[3], const double uvw[3], int32_t on_surface) const;
//! Find the oncoming boundary of this cell.
std::pair<double, int32_t>
distance(const double xyz[3], const double uvw[3], int32_t on_surface) const;
//! \brief Write cell information to an HDF5 group.
//! @param group_id An HDF5 group id.
void to_hdf5(hid_t group_id) const;
protected:
bool contains_simple(const double xyz[3], const double uvw[3],
int32_t on_surface) const;
bool contains_complex(const double xyz[3], const double uvw[3],
int32_t on_surface) const;
};
} // namespace openmc
#endif // CELL_H

View file

@ -73,12 +73,11 @@ contains
integer :: ital ! tally object index
integer :: ijk(3) ! indices for mesh cell
integer :: score_index ! index to pull from tally object
integer :: i_filt ! index in filters array
integer :: i_filter_mesh ! index for mesh filter
integer :: i_filter_ein ! index for incoming energy filter
integer :: i_filter_eout ! index for outgoing energy filter
integer :: i_filter_surf ! index for surface filter
integer :: stride_surf ! stride for surface filter
integer :: i_filter_legendre ! index for Legendre filter
integer :: i_mesh ! flattend index for mesh
logical :: energy_filters! energy filters present
real(8) :: flux ! temp variable for flux
type(RegularMesh), pointer :: m ! pointer for mesh object
@ -95,10 +94,10 @@ contains
! Associate tallies and mesh
associate (t => cmfd_tallies(1) % obj)
i_filt = t % filter(t % find_filter(FILTER_MESH))
i_filter_mesh = t % filter(t % find_filter(FILTER_MESH))
end associate
select type(filt => filters(i_filt) % obj)
select type(filt => filters(i_filter_mesh) % obj)
type is (MeshFilter)
m => meshes(filt % mesh)
end select
@ -115,16 +114,19 @@ contains
! Associate tallies and mesh
associate (t => cmfd_tallies(ital) % obj)
i_filt = t % filter(t % find_filter(FILTER_MESH))
select type(filt => filters(i_filt) % obj)
type is (MeshFilter)
m => meshes(filt % mesh)
end select
if (ital < 3) then
i_filter_mesh = t % filter(t % find_filter(FILTER_MESH))
else if (ital == 3) then
i_filter_mesh = t % filter(t % find_filter(FILTER_MESHSURFACE))
else if (ital == 4) then
i_filter_mesh = t % filter(t % find_filter(FILTER_MESH))
i_filter_legendre = t % filter(t % find_filter(FILTER_LEGENDRE))
end if
! Check for energy filters
energy_filters = (t % find_filter(FILTER_ENERGYIN) > 0)
i_filter_mesh = t % filter(t % find_filter(FILTER_MESH))
if (energy_filters) then
i_filter_ein = t % filter(t % find_filter(FILTER_ENERGYIN))
i_filter_eout = t % filter(t % find_filter(FILTER_ENERGYOUT))
@ -189,13 +191,6 @@ contains
! Get total rr and convert to total xs
cmfd % totalxs(h,i,j,k) = t % results(RESULT_SUM,2,score_index) / flux
! Get p1 scatter rr and convert to p1 scatter xs
cmfd % p1scattxs(h,i,j,k) = t % results(RESULT_SUM,3,score_index) / flux
! Calculate diffusion coefficient
cmfd % diffcof(h,i,j,k) = ONE/(3.0_8*(cmfd % totalxs(h,i,j,k) - &
cmfd % p1scattxs(h,i,j,k)))
else if (ital == 2) then
! Begin loop to get energy out tallies
@ -247,65 +242,102 @@ contains
else if (ital == 3) then
i_filter_surf = t % filter(t % find_filter(FILTER_SURFACE))
stride_surf = t % stride(t % find_filter(FILTER_SURFACE))
! Initialize and filter for energy
do l = 1, size(t % filter)
call filter_matches(t % filter(l)) % bins % clear()
call filter_matches(t % filter(l)) % bins % push_back(1)
end do
! Set the bin for this mesh cell
i_mesh = m % get_bin_from_indices([ i, j, k ])
filter_matches(i_filter_mesh) % bins % data(1) = 12*(i_mesh - 1) + 1
! Set the energy bin if needed
if (energy_filters) then
filter_matches(i_filter_ein) % bins % data(1) = ng - h + 1
end if
! Get the bin for this mesh cell
filter_matches(i_filter_mesh) % bins % data(1) = &
m % get_bin_from_indices([ i, j, k ])
score_index = 1
score_index = 0
do l = 1, size(t % filter)
if (t % filter(l) == i_filter_surf) cycle
score_index = score_index + (filter_matches(t % filter(l)) &
% bins % data(1) - 1) * t % stride(l)
end do
! Left surface
cmfd % current(1,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (OUT_LEFT - 1) * stride_surf)
score_index + OUT_LEFT)
cmfd % current(2,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (IN_LEFT - 1) * stride_surf)
score_index + IN_LEFT)
! Right surface
cmfd % current(3,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (IN_RIGHT - 1) * stride_surf)
score_index + IN_RIGHT)
cmfd % current(4,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (OUT_RIGHT - 1) * stride_surf)
score_index + OUT_RIGHT)
! Back surface
cmfd % current(5,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (OUT_BACK - 1) * stride_surf)
score_index + OUT_BACK)
cmfd % current(6,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (IN_BACK - 1) * stride_surf)
score_index + IN_BACK)
! Front surface
cmfd % current(7,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (IN_FRONT - 1) * stride_surf)
score_index + IN_FRONT)
cmfd % current(8,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (OUT_FRONT - 1) * stride_surf)
score_index + OUT_FRONT)
! Bottom surface
! Left surface
cmfd % current(9,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (OUT_BOTTOM - 1) * stride_surf)
score_index + OUT_BOTTOM)
cmfd % current(10,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (IN_BOTTOM - 1) * stride_surf)
score_index + IN_BOTTOM)
! Top surface
! Right surface
cmfd % current(11,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (IN_TOP - 1) * stride_surf)
score_index + IN_TOP)
cmfd % current(12,h,i,j,k) = t % results(RESULT_SUM, 1, &
score_index + (OUT_TOP - 1) * stride_surf)
score_index + OUT_TOP)
else if (ital == 4) then
! Reset all bins to 1
do l = 1, size(t % filter)
call filter_matches(t % filter(l)) % bins % clear()
call filter_matches(t % filter(l)) % bins % push_back(1)
end do
! Set ijk as mesh indices
ijk = (/ i, j, k /)
! Get bin number for mesh indices
filter_matches(i_filter_mesh) % bins % data(1) = &
m % get_bin_from_indices(ijk)
! Apply energy in filter
if (energy_filters) then
filter_matches(i_filter_ein) % bins % data(1) = ng - h + 1
end if
! Apply Legendre filter
filter_matches(i_filter_legendre) % bins % data(1) = 2
! Calculate score index from bins
score_index = 1
do l = 1, size(t % filter)
score_index = score_index + (filter_matches(t % filter(l)) &
% bins % data(1) - 1) * t % stride(l)
end do
! Get p1 scatter rr and convert to p1 scatter xs
cmfd % p1scattxs(h,i,j,k) = &
t % results(RESULT_SUM,1,score_index) / &
cmfd % flux(h,i,j,k)
! Calculate diffusion coefficient
cmfd % diffcof(h,i,j,k) = &
ONE/(3.0_8*(cmfd % totalxs(h,i,j,k) - &
cmfd % p1scattxs(h,i,j,k)))
end if TALLY
end do OUTGROUP

View file

@ -3,8 +3,8 @@ module cmfd_input
use, intrinsic :: ISO_C_BINDING
use cmfd_header
use mesh_header, only: mesh_dict
use mgxs_header, only: energy_bins
use mesh_header, only: mesh_dict
use mgxs_interface, only: energy_bins, num_energy_groups
use tally
use tally_header
use timer_header
@ -278,7 +278,7 @@ contains
m % id = i_start
! Set mesh type to rectangular
m % type = LATTICE_RECT
m % type = MESH_REGULAR
! Get pointer to mesh XML node
node_mesh = root % child("mesh")
@ -409,48 +409,25 @@ contains
! Duplicate the mesh filter for the mesh current tally since other
! tallies use this filter and we need to change the dimension
i_filt = i_filt + 1
err = openmc_filter_set_type(i_filt, C_CHAR_'mesh' // C_NULL_CHAR)
err = openmc_filter_set_type(i_filt, C_CHAR_'meshsurface' // C_NULL_CHAR)
call openmc_get_filter_next_id(filt_id)
err = openmc_filter_set_id(i_filt, filt_id)
err = openmc_mesh_filter_set_mesh(i_filt, i_start)
err = openmc_meshsurface_filter_set_mesh(i_filt, i_start)
! We need to increase the dimension by one since we also need
! currents coming into and out of the boundary mesh cells.
filters(i_filt) % obj % n_bins = product(m % dimension + 1)
! Set up surface filter
! Add in legendre filter for the P1 tally
i_filt = i_filt + 1
allocate(SurfaceFilter :: filters(i_filt) % obj)
select type(filt => filters(i_filt) % obj)
type is(SurfaceFilter)
filt % id = i_filt
filt % n_bins = 4 * m % n_dimension
allocate(filt % surfaces(4 * m % n_dimension))
if (m % n_dimension == 2) then
filt % surfaces = (/ OUT_LEFT, IN_LEFT, IN_RIGHT, OUT_RIGHT, &
OUT_BACK, IN_BACK, IN_FRONT, OUT_FRONT /)
elseif (m % n_dimension == 3) then
filt % surfaces = (/ OUT_LEFT, IN_LEFT, IN_RIGHT, OUT_RIGHT, &
OUT_BACK, IN_BACK, IN_FRONT, OUT_FRONT, &
OUT_BOTTOM, IN_BOTTOM, IN_TOP, OUT_TOP /)
end if
filt % current = .true.
! Add filter to dictionary
call filter_dict % set(filt % id, i_filt)
end select
err = openmc_filter_set_type(i_filt, C_CHAR_'legendre' // C_NULL_CHAR)
call openmc_get_filter_next_id(filt_id)
err = openmc_filter_set_id(i_filt, filt_id)
err = openmc_legendre_filter_set_order(i_filt, 1)
! Initialize filters
do i = i_filt_start, i_filt_end
select type (filt => filters(i) % obj)
type is (SurfaceFilter)
! Don't do anything
class default
call filt % initialize()
end select
call filters(i) % obj % initialize()
end do
! Allocate tallies
err = openmc_extend_tallies(3, i_start, i_end)
err = openmc_extend_tallies(4, i_start, i_end)
cmfd_tallies => tallies(i_start:i_end)
! Begin loop around tallies
@ -484,7 +461,7 @@ contains
if (i == 1) then
! Set name
t % name = "CMFD flux, total, scatter-1"
t % name = "CMFD flux, total"
! Set tally estimator to analog
t % estimator = ESTIMATOR_ANALOG
@ -502,19 +479,12 @@ contains
deallocate(filter_indices)
! Allocate scoring bins
allocate(t % score_bins(3))
t % n_score_bins = 3
t % n_user_score_bins = 3
! Allocate scattering order data
allocate(t % moment_order(3))
t % moment_order = 0
allocate(t % score_bins(2))
t % n_score_bins = 2
! Set macro_bins
t % score_bins(1) = SCORE_FLUX
t % score_bins(2) = SCORE_TOTAL
t % score_bins(3) = SCORE_SCATTER_N
t % moment_order(3) = 1
else if (i == 2) then
@ -546,11 +516,6 @@ contains
! Allocate macro reactions
allocate(t % score_bins(2))
t % n_score_bins = 2
t % n_user_score_bins = 2
! Allocate scattering order data
allocate(t % moment_order(2))
t % moment_order = 0
! Set macro_bins
t % score_bins(1) = SCORE_NU_SCATTER
@ -564,13 +529,9 @@ contains
! Set tally estimator to analog
t % estimator = ESTIMATOR_ANALOG
! Set the surface filter index in the tally find_filter array
n_filter = n_filter + 1
! Allocate and set filters
allocate(filter_indices(n_filter))
filter_indices(1) = i_filt_end - 1
filter_indices(n_filter) = i_filt_end
if (energy_filters) then
filter_indices(2) = i_filt_start + 1
end if
@ -580,15 +541,41 @@ contains
! Allocate macro reactions
allocate(t % score_bins(1))
t % n_score_bins = 1
t % n_user_score_bins = 1
! Allocate scattering order data
allocate(t % moment_order(1))
t % moment_order = 0
! Set macro bins
t % score_bins(1) = SCORE_CURRENT
t % type = TALLY_MESH_CURRENT
t % type = TALLY_MESH_SURFACE
else if (i == 4) then
! Set name
t % name = "CMFD P1 scatter"
! Set tally estimator to analog
t % estimator = ESTIMATOR_ANALOG
! Set tally type to volume
t % type = TALLY_VOLUME
! Allocate and set filters
n_filter = 2
if (energy_filters) then
n_filter = n_filter + 1
end if
allocate(filter_indices(n_filter))
filter_indices(1) = i_filt_start
filter_indices(2) = i_filt_end
if (energy_filters) then
filter_indices(3) = i_filt_start + 1
end if
err = openmc_tally_set_filters(i_start + i - 1, n_filter, filter_indices)
deallocate(filter_indices)
! Allocate scoring bins
allocate(t % score_bins(1))
t % n_score_bins = 1
! Set macro_bins
t % score_bins(1) = SCORE_SCATTER
end if
! Make CMFD tallies active from the start

View file

@ -21,7 +21,7 @@ module constants
integer, parameter :: VERSION_STATEPOINT(2) = [17, 0]
integer, parameter :: VERSION_PARTICLE_RESTART(2) = [2, 0]
integer, parameter :: VERSION_TRACK(2) = [2, 0]
integer, parameter :: VERSION_SUMMARY(2) = [5, 0]
integer, parameter :: VERSION_SUMMARY(2) = [6, 0]
integer, parameter :: VERSION_VOLUME(2) = [1, 0]
integer, parameter :: VERSION_VOXEL(2) = [1, 0]
integer, parameter :: VERSION_MGXS_LIBRARY(2) = [1, 0]
@ -113,6 +113,9 @@ module constants
FILL_MATERIAL = 1, & ! Cell with a specified material
FILL_UNIVERSE = 2, & ! Cell filled by a separate universe
FILL_LATTICE = 3 ! Cell filled with a lattice
integer(C_INT), bind(C, name='FILL_MATERIAL') :: FILL_MATERIAL_C = FILL_MATERIAL
integer(C_INT), bind(C, name='FILL_UNIVERSE') :: FILL_UNIVERSE_C = FILL_UNIVERSE
integer(C_INT), bind(C, name='FILL_LATTICE') :: FILL_LATTICE_C = FILL_LATTICE
! Void material
integer, parameter :: MATERIAL_VOID = -1
@ -219,7 +222,7 @@ module constants
N_3HEC = 799, N_A0 = 800, N_AC = 849, N_2N0 = 875, N_2NC = 891
! Depletion reactions
integer, parameter :: DEPLETION_RX(6) = [N_2N, N_3N, N_4N, N_GAMMA, N_P, N_A]
integer, parameter :: DEPLETION_RX(6) = [N_GAMMA, N_P, N_A, N_2N, N_3N, N_4N]
! ACE table types
integer, parameter :: &
@ -232,6 +235,10 @@ module constants
MGXS_ISOTROPIC = 1, & ! Isotropically Weighted Data
MGXS_ANGLE = 2 ! Data by Angular Bins
! Flag to denote this was a macroscopic data object
real(8), parameter :: &
MACROSCOPIC_AWR = -TWO
! Fission neutron emission (nu) type
integer, parameter :: &
NU_NONE = 0, & ! No nu values (non-fissionable)
@ -292,7 +299,7 @@ module constants
! Tally type
integer, parameter :: &
TALLY_VOLUME = 1, &
TALLY_MESH_CURRENT = 2, &
TALLY_MESH_SURFACE = 2, &
TALLY_SURFACE = 3
! Tally estimator types
@ -310,55 +317,33 @@ module constants
! Tally score type -- if you change these, make sure you also update the
! _SCORES dictionary in openmc/capi/tally.py
integer, parameter :: N_SCORE_TYPES = 24
integer, parameter :: N_SCORE_TYPES = 16
integer, parameter :: &
SCORE_FLUX = -1, & ! flux
SCORE_TOTAL = -2, & ! total reaction rate
SCORE_SCATTER = -3, & ! scattering rate
SCORE_NU_SCATTER = -4, & ! scattering production rate
SCORE_SCATTER_N = -5, & ! arbitrary scattering moment
SCORE_SCATTER_PN = -6, & ! system for scoring 0th through nth moment
SCORE_NU_SCATTER_N = -7, & ! arbitrary nu-scattering moment
SCORE_NU_SCATTER_PN = -8, & ! system for scoring 0th through nth nu-scatter moment
SCORE_ABSORPTION = -9, & ! absorption rate
SCORE_FISSION = -10, & ! fission rate
SCORE_NU_FISSION = -11, & ! neutron production rate
SCORE_KAPPA_FISSION = -12, & ! fission energy production rate
SCORE_CURRENT = -13, & ! current
SCORE_FLUX_YN = -14, & ! angular moment of flux
SCORE_TOTAL_YN = -15, & ! angular moment of total reaction rate
SCORE_SCATTER_YN = -16, & ! angular flux-weighted scattering moment (0:N)
SCORE_NU_SCATTER_YN = -17, & ! angular flux-weighted nu-scattering moment (0:N)
SCORE_EVENTS = -18, & ! number of events
SCORE_DELAYED_NU_FISSION = -19, & ! delayed neutron production rate
SCORE_PROMPT_NU_FISSION = -20, & ! prompt neutron production rate
SCORE_INVERSE_VELOCITY = -21, & ! flux-weighted inverse velocity
SCORE_FISS_Q_PROMPT = -22, & ! prompt fission Q-value
SCORE_FISS_Q_RECOV = -23, & ! recoverable fission Q-value
SCORE_DECAY_RATE = -24 ! delayed neutron precursor decay rate
SCORE_ABSORPTION = -5, & ! absorption rate
SCORE_FISSION = -6, & ! fission rate
SCORE_NU_FISSION = -7, & ! neutron production rate
SCORE_KAPPA_FISSION = -8, & ! fission energy production rate
SCORE_CURRENT = -9, & ! current
SCORE_EVENTS = -10, & ! number of events
SCORE_DELAYED_NU_FISSION = -11, & ! delayed neutron production rate
SCORE_PROMPT_NU_FISSION = -12, & ! prompt neutron production rate
SCORE_INVERSE_VELOCITY = -13, & ! flux-weighted inverse velocity
SCORE_FISS_Q_PROMPT = -14, & ! prompt fission Q-value
SCORE_FISS_Q_RECOV = -15, & ! recoverable fission Q-value
SCORE_DECAY_RATE = -16 ! delayed neutron precursor decay rate
! Maximum scattering order supported
integer, parameter :: MAX_ANG_ORDER = 10
! Names of *-PN & *-YN scores (MOMENT_STRS) and *-N moment scores
character(*), parameter :: &
MOMENT_STRS(6) = (/ "scatter-p ", &
"nu-scatter-p", &
"flux-y ", &
"total-y ", &
"scatter-y ", &
"nu-scatter-y"/), &
MOMENT_N_STRS(2) = (/ "scatter- ", &
"nu-scatter- "/)
! Location in MOMENT_STRS where the YN data begins
integer, parameter :: YN_LOC = 3
! Tally map bin finding
integer, parameter :: NO_BIN_FOUND = -1
! Tally filter and map types
integer, parameter :: N_FILTER_TYPES = 15
integer, parameter :: N_FILTER_TYPES = 20
integer, parameter :: &
FILTER_UNIVERSE = 1, &
FILTER_MATERIAL = 2, &
@ -374,7 +359,12 @@ module constants
FILTER_AZIMUTHAL = 12, &
FILTER_DELAYEDGROUP = 13, &
FILTER_ENERGYFUNCTION = 14, &
FILTER_CELLFROM = 15
FILTER_CELLFROM = 15, &
FILTER_MESHSURFACE = 16, &
FILTER_LEGENDRE = 17, &
FILTER_SPH_HARMONICS = 18, &
FILTER_SPTL_LEGENDRE = 19, &
FILTER_ZERNIKE = 20
! Mesh types
integer, parameter :: &
@ -415,6 +405,25 @@ module constants
DIFF_NUCLIDE_DENSITY = 2, &
DIFF_TEMPERATURE = 3
! Mgxs::get_xs enumerated types
integer(C_INT), parameter :: &
MG_GET_XS_TOTAL = 0, &
MG_GET_XS_ABSORPTION = 1, &
MG_GET_XS_INVERSE_VELOCITY = 2, &
MG_GET_XS_DECAY_RATE = 3, &
MG_GET_XS_SCATTER = 4, &
MG_GET_XS_SCATTER_MULT = 5, &
MG_GET_XS_SCATTER_FMU_MULT = 6, &
MG_GET_XS_SCATTER_FMU = 7, &
MG_GET_XS_FISSION = 8, &
MG_GET_XS_KAPPA_FISSION = 9, &
MG_GET_XS_PROMPT_NU_FISSION = 10, &
MG_GET_XS_DELAYED_NU_FISSION = 11, &
MG_GET_XS_NU_FISSION = 12, &
MG_GET_XS_CHI_PROMPT = 13, &
MG_GET_XS_CHI_DELAYED = 14
! ============================================================================
! RANDOM NUMBER STREAM CONSTANTS
@ -431,6 +440,7 @@ module constants
! indicates that an array index hasn't been set
integer, parameter :: NONE = 0
integer, parameter :: C_NONE = -1
! Codes for read errors -- better hope these numbers are never used in an
! input file!

91
src/constants.h Normal file
View file

@ -0,0 +1,91 @@
//! \file constants.h
//! A collection of constants
#ifndef CONSTANTS_H
#define CONSTANTS_H
#include <cmath>
#include <array>
#include <limits>
#include <vector>
namespace openmc {
typedef std::vector<double> double_1dvec;
typedef std::vector<std::vector<double> > double_2dvec;
typedef std::vector<std::vector<std::vector<double> > > double_3dvec;
typedef std::vector<std::vector<std::vector<std::vector<double> > > > double_4dvec;
typedef std::vector<std::vector<std::vector<std::vector<std::vector<double> > > > > double_5dvec;
typedef std::vector<std::vector<std::vector<std::vector<std::vector<std::vector<double> > > > > > double_6dvec;
typedef std::vector<int> int_1dvec;
typedef std::vector<std::vector<int> > int_2dvec;
typedef std::vector<std::vector<std::vector<int> > > int_3dvec;
constexpr int MAX_SAMPLE {10000};
constexpr std::array<int, 3> VERSION {0, 10, 0};
constexpr std::array<int, 2> VERSION_PARTICLE_RESTART {2, 0};
// Maximum number of words in a single line, length of line, and length of
// single word
constexpr int MAX_WORDS {500};
constexpr int MAX_LINE_LEN {250};
constexpr int MAX_WORD_LEN {150};
constexpr int MAX_FILE_LEN {255};
// Physical Constants
constexpr double K_BOLTZMANN {8.6173303e-5}; // Boltzmann constant in eV/K
// Angular distribution type
constexpr int ANGLE_ISOTROPIC {1};
constexpr int ANGLE_32_EQUI {2};
constexpr int ANGLE_TABULAR {3};
constexpr int ANGLE_LEGENDRE {4};
constexpr int ANGLE_HISTOGRAM {5};
// MGXS Table Types
constexpr int MGXS_ISOTROPIC {1}; // Isotroically weighted data
constexpr int MGXS_ANGLE {2}; // Data by angular bins
// Flag to denote this was a macroscopic data object
constexpr double MACROSCOPIC_AWR {-2.};
// Number of mu bins to use when converting Legendres to tabular type
constexpr int DEFAULT_NMU {33};
// Temperature treatment method
constexpr int TEMPERATURE_NEAREST {1};
constexpr int TEMPERATURE_INTERPOLATION {2};
// TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we
// use so for now we will reuse the Fortran constant until we are OK with
// modifying test results
constexpr double PI {3.1415926535898};
const double SQRT_PI {std::sqrt(PI)};
// Mgxs::get_xs enumerated types
constexpr int MG_GET_XS_TOTAL {0};
constexpr int MG_GET_XS_ABSORPTION {1};
constexpr int MG_GET_XS_INVERSE_VELOCITY {2};
constexpr int MG_GET_XS_DECAY_RATE {3};
constexpr int MG_GET_XS_SCATTER {4};
constexpr int MG_GET_XS_SCATTER_MULT {5};
constexpr int MG_GET_XS_SCATTER_FMU_MULT {6};
constexpr int MG_GET_XS_SCATTER_FMU {7};
constexpr int MG_GET_XS_FISSION {8};
constexpr int MG_GET_XS_KAPPA_FISSION {9};
constexpr int MG_GET_XS_PROMPT_NU_FISSION {10};
constexpr int MG_GET_XS_DELAYED_NU_FISSION {11};
constexpr int MG_GET_XS_NU_FISSION {12};
constexpr int MG_GET_XS_CHI_PROMPT {13};
constexpr int MG_GET_XS_CHI_DELAYED {14};
extern "C" double FP_COINCIDENT;
extern "C" double FP_PRECISION;
constexpr double INFTY {std::numeric_limits<double>::max()};
constexpr int C_NONE {-1};
} // namespace openmc
#endif // CONSTANTS_H

Some files were not shown because too many files have changed in this diff Show more