From 5c32d86770e80d3f2d56f302622d2b2bc168bd1d Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 9 Aug 2017 12:16:19 -0400 Subject: [PATCH 001/282] Update style guide and add C++ style --- docs/source/devguide/index.rst | 1 - docs/source/devguide/structures.rst | 155 ----------------------- docs/source/devguide/styleguide.rst | 184 +++++++++++++++++++++------- 3 files changed, 142 insertions(+), 198 deletions(-) delete mode 100644 docs/source/devguide/structures.rst diff --git a/docs/source/devguide/index.rst b/docs/source/devguide/index.rst index 4dc1b2c74..1a8252383 100644 --- a/docs/source/devguide/index.rst +++ b/docs/source/devguide/index.rst @@ -12,7 +12,6 @@ as debugging. :numbered: :maxdepth: 3 - structures styleguide workflow user-input diff --git a/docs/source/devguide/structures.rst b/docs/source/devguide/structures.rst deleted file mode 100644 index 15777f606..000000000 --- a/docs/source/devguide/structures.rst +++ /dev/null @@ -1,155 +0,0 @@ -.. _devguide_structures: - -=============== -Data Structures -=============== - -The purpose of this section is to give you an overview of the major data -structures in OpenMC and how they are logically related. A majority of variables -in OpenMC are `derived types`_ (similar to a struct in C). These derived types -are defined in the various header modules, e.g. src/geometry_header.F90. Most -important variables are found in the `global module`_. Have a look through that -module to get a feel for what variables you'll often come across when looking at -OpenMC code. - --------- -Particle --------- - -Perhaps the variable that you will see most often is simply called ``p`` and is -of type(Particle). This variable stores information about a particle's physical -characteristics (coordinates, direction, energy), what cell and material it's -currently in, how many collisions it has undergone, etc. In practice, only one -particle is followed at a time so there is no array of type(Particle). The -Particle type is defined in the `particle_header module`_. - -You will notice that the direction and angle of the particle is stored in a -linked list of type(LocalCoord). In geometries with multiple :ref:`universes`, -the coordinates in each universe are stored in this linked list. If universes or -lattices are not used in a geometry, only one LocalCoord is present in the -linked list. - -The LocalCoord type has a component called cell which gives the index in the -``cells`` array in the `global module`_. The ``cells`` array is of type(Cell) -and stored information about each region defined by the user. - ----- -Cell ----- - -The Cell type is defined in the `geometry_header module`_ along with other -geometry-related derived types. Each cell in the problem is described in terms -of its bounding surfaces, which are listed on the ``surfaces`` component. The -absolute value of each item in the ``surfaces`` component contains the index of -the corresponding surface in the ``surfaces`` array defined in the `global -module`_. The sign on each item in the ``surfaces`` component indicates whether -the cell exists on the positive or negative side of the surface (see -:ref:`methods_geometry`). - -Each cell can either be filled with another universe/lattice or with a -material. If it is filled with a material, the ``material`` component gives the -index of the material in the ``materials`` array defined in the `global -module`_. - -------- -Surface -------- - -The Surface type is defined in the `geometry_header module`_. A surface is -defined by a type (sphere, cylinder, etc.) and a list of coefficients for that -surface type. The simplest example would be a plane perpendicular to the xy, yz, -or xz plane which needs only one parameter. The ``type`` component indicates the -type through integer parameters such as SURF_SPHERE or SURF_CYL_Y (these are -defined in the `constants module`_). The ``coeffs`` component gives the -necessary coefficients to parameterize the surface type (see -:ref:`surface_element`). - --------- -Material --------- - -The Material type is defined in the `material_header module`_. Each material -contains a number of nuclides at a given atom density. Each item in the -``nuclide`` component corresponds to the index in the global ``nuclides`` array -(as usual, found in the `global module`_). The ``atom_density`` component is the -same length as the ``nuclides`` component and lists the corresponding atom -density in atom/barn-cm for each nuclide in the ``nuclides`` component. - -If the material contains nuclides for which binding effects are important in -low-energy scattering, a :math:`S(\alpha,\beta)` can be associated with that -material through the ``sab_table`` component. Again, this component contains the -index in the ``sab_tables`` array from the `global module`_. - -------- -Nuclide -------- - -The Nuclide derived type stores cross section and interaction data for a nucleus -and is defined in the `ace_header module`_. The ``energy`` component is an array -that gives the discrete energies at which microscopic cross sections are -tabulated. The actual microscopic cross sections are stored in a separate -derived type, Reaction. An arrays of Reactions is present in the ``reactions`` -component. There are a few summary microscopic cross sections stored in other -components, such as ``total``, ``elastic``, ``fission``, and ``nu_fission``. - -If a Nuclide is fissionable, the prompt and delayed neutron yield and energy -distributions are also stored on the Nuclide type. Many nuclides also have -unresolved resonance probability table data. If present, this data is stored in -the component ``urr_data`` of derived type UrrData. A complete description of -the probability table method is given in :ref:`probability_tables`. - -The list of nuclides present in a problem is stored in the ``nuclides`` array -defined in the `global module`_. - ----------- -SAlphaBeta ----------- - -The SAlphaBeta derived type stores :math:`S(\alpha,\beta)` data to account for -molecular binding effects when treating thermal scattering. Each SAlphaBeta -table is associated with a specific nuclide as identified in the ``zaid`` -component. A complete description of the :math:`S(\alpha,\beta)` treatment can -be found in :ref:`sab_tables`. - ---------- -XsListing ---------- - -The XsListing derived type stores information on the location of an ACE cross -section table based on the data in cross_sections.xml and is defined in the -`ace_header module`_. For each ```` you see in cross_sections.xml, -there is a XsListing with its information. When the user input is read, the -array ``xs_listings`` in the `global module`_ that is of derived type XsListing -is used to locate the ACE data to parse. - --------------- -NuclideMicroXS --------------- - -The NuclideMicroXS derived type, defined in the `ace_header module`_, acts as a -'cache' for microscopic cross sections. As a particle is traveling through -different materials, cross sections can be reused if the energy of the particle -hasn't changed. The components ``total``, ``elastic``, ``absorption``, -``fission``, and ``nu_fission`` represent those microscopic cross sections at -the current energy of the particle for a given nuclide. An array ``micro_xs`` in -the `global module`_ that is the same length as the ``nuclides`` array stores -these cached cross sections for each nuclide in the problem. - ---------------- -MaterialMacroXS ---------------- - -In addition to the NuclideMicroXS type, there is also a MaterialMacroXS derived -type, defined in the `ace_header module`_ that stored cached *macroscopic* cross -sections for the current material. These macroscopic cross sections are used for -both physics and tallying purposes. The variable ``material_xs`` in the `global -module`_ is of type MaterialMacroXS. - - -.. _derived types: http://nf.nci.org.au/training/FortranAdvanced/slides/slides.025.html -.. _global module: https://github.com/mit-crpg/openmc/blob/master/src/global.F90 -.. _particle_header module: https://github.com/mit-crpg/openmc/blob/master/src/particle_header.F90 -.. _geometry_header module: https://github.com/mit-crpg/openmc/blob/master/src/geometry_header.F90 -.. _constants module: https://github.com/mit-crpg/openmc/blob/master/src/constants.F90 -.. _material_header module: https://github.com/mit-crpg/openmc/blob/master/src/material_header.F90 -.. _ace_header module: https://github.com/mit-crpg/openmc/blob/master/src/ace_header.F90 diff --git a/docs/source/devguide/styleguide.rst b/docs/source/devguide/styleguide.rst index 936f0c838..e2b3de5c3 100644 --- a/docs/source/devguide/styleguide.rst +++ b/docs/source/devguide/styleguide.rst @@ -8,35 +8,50 @@ In order to keep the OpenMC code base consistent in style, this guide specifies a number of rules which should be adhered to when modified existing code or adding new code in OpenMC. -------- -Fortran -------- +--------------- +Fortran and C++ +--------------- -General Rules +Miscellaneous ------------- -Conform to the Fortran 2008 standard. - -Make sure code can be compiled with most common compilers, especially gfortran -and the Intel Fortran compiler. This supercedes the previous rule --- if a -Fortran 2003/2008 feature is not implemented in a common compiler, do not use -it. +Make sure code can be compiled with most common compilers, especially the GCC +and Intel compilers. This supersedes the rules about standards---if a Fortran +2003/2008 feature is not implemented in a common compiler then do not use it. Do not use special extensions that can be only be used from certain compilers. -In general, write your code in lower-case. Having code in all caps does not -enhance code readability or otherwise. - Always include comments to describe what your code is doing. Do not be afraid of using copious amounts of comments. -Use <, >, <=, >=, ==, and /= rather than .lt., .gt., .le., .ge., .eq., and .ne. - Try to keep code within 80 columns when possible. -Don't use ``print *`` or ``write(*,*)``. If writing to a file, use a specific -unit. Writing to standard output or standard error should be handled by the -``write_message`` subroutine or functionality in the error module. +Don't use ``print *``, ``write(*,*)``, ``fprintf()``, or ``std::cout``. If +writing to a file, use a specific unit. Writing to standard output or standard +error should be handled by the ``write_message`` subroutine or functionality in +the error module. + +Naming +------ + +In general, write your code in lower-case. Having code in all caps does not +enhance code readability or otherwise. + +Module names should be lower-case with underscores if needed, e.g. +``xml_interface``. + +Class names should be CamelCase, e.g. ``HexLattice``. + +Functions and subroutines (including type-bound methods) should be lower-case +with underscores, e.g. ``get_indices``. + +Local variables, global variables, and type attributes should be lower-case +with underscores (e.g. ``n_cells``) except for physics symbols that are written +differently by convention (e.g. ``E`` for energy). + +Constant (parameter or const) variables should be in upper-case with +underscores, e.g. ``SQRT_PI``. These should usually be defined in the +constants.F90 module. Procedures ---------- @@ -53,28 +68,17 @@ Variables --------- Never, under any circumstances, should implicit variables be used! Always -include ``implicit none`` and define all your variables. +include ``implicit none`` in Fortran source code and define all your variables. -Variable names should be all lower-case and descriptive, i.e. not a random -assortment of letters that doesn't give any information to someone seeing it for -the first time. Variables consisting of multiple words should be separated by -underscores, not hyphens or in camel case. - -Constant (parameter) variables should be in ALL CAPITAL LETTERS and defined in -in the constants.F90 module. - -32-bit reals (real(4)) should never be used. Always use 64-bit reals (real(8)). +32-bit reals (``real(4)`` and ``float``) should never be used. Always use 64-bit +reals (``real(8)`` and ``double``). For arbitrary length character variables, use the pre-defined lengths -MAX_LINE_LEN, MAX_WORD_LEN, and MAX_FILE_LEN if possible. - -Do not use old-style character/array length (e.g. character*80, real*8). +``MAX_LINE_LEN``, ``MAX_WORD_LEN``, and ``MAX_FILE_LEN`` if possible. Integer values being used to indicate a certain state should be defined as named constants (see the constants.F90 module for many examples). -Always use a double colon :: when declaring a variable. - Yes: .. code-block:: fortran @@ -92,20 +96,12 @@ allocation instead. Use allocatable variables instead of pointer variables when possible. Shared/Module Variables -+++++++++++++++++++++++ +----------------------- Always put shared variables in modules. Access module variables through a ``use`` statement. Always use the ``only`` specifier on the ``use`` statement except for variables from the global, constants, and various header modules. -Never use ``equivalence`` statements, ``common`` blocks, or ``data`` statements. - -Derived Types and Classes -------------------------- - -Derived types and classes should have CamelCase names with words not separated -by underscores or hyphens. - Indentation ----------- @@ -158,6 +154,110 @@ each side. Do not leave trailing whitespace at the end of a line. +---------------- +Fortran-Specific +---------------- + +Conform to the Fortran 2008 standard. + +Use <, >, <=, >=, ==, and /= rather than .lt., .gt., .le., .ge., .eq., and .ne. + +Do not use old-style character/array length (e.g. character*80, real*8). + +Always use a double colon :: when declaring a variable. + +Never use ``equivalence`` statements, ``common`` blocks, or ``data`` statements. + +------------ +C++-Specific +------------ + +Miscellaneous +------------- + +Conform to the C++11 standard. + +Always use C++-style comments (``//``) as opposed to C-style (``/**/``). (It +is more difficult to comment out a large section of code that uses C-style +comments.) + +Header files should always use include guards with the following style: + +.. code-block:: C++ + + #ifndef MODULE_NAME_H + #define MODULE_NAME_H + ... + content + ... + #endif // MODULE_NAME_H + +Do not use using-directives e.g. ``using namespace foobar;`` + +Do not use C-style casting. Always use the C++-style casts ``static_cast``, +``const_cast``, or ``reinterpret_cast``. + +Curly braces +------------ + +For a function definition, the opening brace should be on the same line as the +end of the function definition. The closing brace should be on its own line. +If the entire function fits on one line, then the closing brace can be on the +same line. e.g.: + +.. code-block:: C++ + + return_type function(type1 arg1, type2 arg2) { + content(); + } + + return_type + function_with_many_args(type1 arg1, type2 arg2, type3 arg3, + type4 arg4) { + content(); + } + + 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 +statement. Otherwise, the closing brace should be on its own line. A one-line +conditional can have the closing brace on the same line or it can omit the +braces entirely e.g.: + +.. code-block:: C++ + + if (condition) { + content(); + } + + if (condition1) { + content(); + } else if (condition 2) { + more_content(); + } else { + further_content(); + } + + if (condition) {content()}; + + if (condition) content(); + +For loops similarly have an opening brace on the same line as the statement and +a closing brace on its own line. One-line loops may have the closing brace on +the same line or omit the braces entirely. + +.. code-block:: C++ + + for (int i = 0; i < 5; i++) { + content(); + } + + for (int i = 0; i < 5; i++) {content();} + + for (int i = 0; i < 5; i++) content(); + ------ Python ------ From e68185e89bd9c09eaedce99b053e6e76e9b41604 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 22 Nov 2017 07:33:11 -0600 Subject: [PATCH 002/282] Make sure libopenmc.so gets installed (and has correct RPATH) --- CMakeLists.txt | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b0bef14e..dfd9221ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,10 +281,30 @@ target_link_libraries(pugixml_fortran pugixml) # RPATH information #=============================================================================== +# 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 + +# use, i.e. don't skip the full RPATH for the build tree +set(CMAKE_SKIP_BUILD_RPATH FALSE) + +# when building, don't use the install RPATH already +# (but later on when installing) +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + # add the automatically determined parts of the RPATH # which point to directories outside the build tree to the install RPATH set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +# the RPATH to be used when installing, but only if it's not a system directory +list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) +if("${isSystemDir}" STREQUAL "-1") + set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") +endif() + #=============================================================================== # Build faddeeva library #=============================================================================== @@ -435,7 +455,9 @@ add_custom_command(TARGET libopenmc POST_BUILD # Install executable, scripts, manpage, license #=============================================================================== -install(TARGETS ${program} RUNTIME DESTINATION bin) +install(TARGETS ${program} libopenmc + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib) 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) From cc52bfc435f6d00ea6939ceb5ef6931840005780 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 1 Dec 2017 16:45:28 -0500 Subject: [PATCH 003/282] Translate the RNG to C++ --- CMakeLists.txt | 46 +++++++++- src/random_lcg.C | 153 +++++++++++++++++++++++++++++++ src/random_lcg.F90 | 223 ++++++++------------------------------------- src/random_lcg.h | 56 ++++++++++++ 4 files changed, 286 insertions(+), 192 deletions(-) create mode 100644 src/random_lcg.C create mode 100644 src/random_lcg.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b0bef14e..a8b801eb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -251,9 +251,26 @@ elseif(CMAKE_C_COMPILER_ID MATCHES Clang) endif() +list(APPEND cxxflags -std=c++11 -O2) +if(debug) + list(REMOVE_ITEM cxxflags -O2) + list(APPEND cxxflags -g -O0) +endif() +if(profile) + list(APPEND cxxflags -pg) +endif() +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}") message(STATUS "C flags: ${cflags}") +message(STATUS "CXX flags: ${cxxflags}") message(STATUS "Linker flags: ${ldflags}") #=============================================================================== @@ -292,7 +309,7 @@ set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) add_library(faddeeva STATIC src/faddeeva/Faddeeva.c) #=============================================================================== -# Build OpenMC executable +# List source files. Define the libopenmc and the OpenMC executable #=============================================================================== set(program "openmc") @@ -398,19 +415,37 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/trigger.F90 src/tallies/trigger_header.F90 ) -add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC}) +set(LIBOPENMC_CXX_SRC + src/random_lcg.h + src/random_lcg.C) +add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC} ${LIBOPENMC_CXX_SRC}) set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc) add_executable(${program} src/main.F90) + +#=============================================================================== +# Add compiler/linker flags +#=============================================================================== + set_property(TARGET ${program} libopenmc pugixml_fortran PROPERTY LINKER_LANGUAGE Fortran) target_include_directories(libopenmc PUBLIC ${HDF5_INCLUDE_DIRS}) -# set compile flags via target_compile_options +# 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(libopenmc PUBLIC ${f90flags}) target_compile_options(faddeeva PRIVATE ${cflags}) +# 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}) + # Add HDF5 library directories to link line with -L foreach(LIBDIR ${HDF5_LIBRARY_DIRS}) list(APPEND ldflags "-L${LIBDIR}") @@ -418,7 +453,8 @@ endforeach() # 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 faddeeva) +target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} pugixml_fortran + faddeeva) target_link_libraries(${program} ${ldflags} libopenmc) #=============================================================================== diff --git a/src/random_lcg.C b/src/random_lcg.C new file mode 100644 index 000000000..c78c40748 --- /dev/null +++ b/src/random_lcg.C @@ -0,0 +1,153 @@ +#include "random_lcg.h" +#include + + +// Starting seed +int64_t seed = 1; + +// LCG parameters +const int64_t prn_mult = 2806196910506780709LL; // multiplication factor, g +const int64_t prn_add = 1; // additive factor, c +const int64_t prn_mod = -0x8000000000000000; // -2^63 +const int64_t prn_mask = 0x7fffffffffffffff; // 2^63 - 1 +const int64_t prn_stride = 152917LL; // stride between particles +const double prn_norm = pow(2, -63); // 2^-63 + +// Module constants +const int N_STREAMS = 5; +const int STREAM_TRACKING = 0; +const int STREAM_TALLIES = 1; +const int STREAM_SOURCE = 2; +const int STREAM_URR_PTABLE = 3; +const int STREAM_VOLUME = 4; + +// Current PRNG state +int64_t prn_seed[N_STREAMS]; // current seed +int stream; // current RNG stream +#pragma omp threadprivate(prn_seed, stream) + + +//============================================================================== +// PRN +//============================================================================== + +extern "C" double +prn() +{ + // This algorithm uses bit-masking to find the next integer(8) value to be + // used to calculate the random number. + prn_seed[stream] = (prn_mult*prn_seed[stream] + prn_add) & prn_mask; + + // Once the integer is calculated, we just need to divide by 2**m, + // represented here as multiplying by a pre-calculated factor + double pseudo_rn = prn_seed[stream] * prn_norm; + return pseudo_rn; +} + +//============================================================================== +// FUTURE_PRN +//============================================================================== + +extern "C" double +future_prn(int64_t n) +{ + double pseudo_rn = future_seed(n, prn_seed[stream]) * prn_norm; + return pseudo_rn; +} + +//============================================================================== +// SET_PARTICLE_SEED +//============================================================================== + +extern "C" void +set_particle_seed(int64_t id) +{ + for (int i = 0; i < N_STREAMS; i++) { + prn_seed[i] = future_seed(id * prn_stride, seed + i); + } +} + +//============================================================================== +// ADVANCE_PRN_SEED +//============================================================================== + +extern "C" void +advance_prn_seed(int64_t n) +{ + prn_seed[stream] = future_seed(n, prn_seed[stream]); +} + +//============================================================================== +// FUTURE_SEED +//============================================================================== + +extern "C" int64_t +future_seed(int64_t n, int64_t seed) +{ + // In cases where we want to skip backwards, we add the period of the random + // number generator until the number of PRNs to skip is positive since + // skipping ahead that much is the same as skipping backwards by the original + // amount. + + int64_t nskip = n; + while (nskip < 0) nskip += prn_mod; + + // Make sure nskip is less than 2^M. + nskip &= prn_mask; + + // The algorithm here to determine the parameters used to skip ahead is + // described in F. Brown, "Random Number Generation with Arbitrary Stride," + // Trans. Am. Nucl. Soc. (Nov. 1994). This algorithm is able to skip ahead in + // O(log2(N)) operations instead of O(N). Basically, it computes parameters G + // and C which can then be used to find x_N = G*x_0 + C mod 2^M. + + // Initialize constants + int64_t g = prn_mult; + int64_t c = prn_add; + int64_t g_new = 1; + int64_t c_new = 0; + + while (nskip > 0) { + // Check if the least significant bit is 1. + if (nskip & 1) { + g_new = (g_new * g) & prn_mask; + c_new = (c_new * g + c) & prn_mask; + } + c = ((g + 1) * c) & prn_mask; + g = (g * g) & prn_mask; + + // Move bits right, dropping least significant bit. + nskip >>= 1; + } + + // With G and C, we can now find the new seed. + int64_t new_seed = (g_new * seed + c_new) & prn_mask; + return new_seed; +} + +//============================================================================== +// PRN_SET_STREAM +//============================================================================== + +extern "C" void +prn_set_stream(int i) +{ + stream = i - 1; +} + +//============================================================================== +// API FUNCTIONS +//============================================================================== + +extern "C" int +openmc_set_seed(int64_t new_seed) +{ + seed = new_seed; +#pragma omp parallel + for (int i = 0; i < N_STREAMS; i++) { + prn_seed[i] = seed + i; + } + stream = STREAM_TRACKING; +#pragma end omp parallel + return 0; +} diff --git a/src/random_lcg.F90 b/src/random_lcg.F90 index 685557f07..b76341cbd 100644 --- a/src/random_lcg.F90 +++ b/src/random_lcg.F90 @@ -2,199 +2,48 @@ module random_lcg use, intrinsic :: ISO_C_BINDING - use constants - implicit none - private - save + integer(C_INT64_T), bind(C) :: seed - ! Starting seed - integer(C_INT64_T), public, bind(C) :: seed = 1_8 + interface + function prn() result(pseudo_rn) bind(C, name='prn') + use ISO_C_BINDING + implicit none + real(C_DOUBLE) :: pseudo_rn + end function prn - ! LCG parameters - integer(C_INT64_T), parameter :: prn_mult = 2806196910506780709_8 ! multiplication factor, g - integer(C_INT64_T), parameter :: prn_add = 1_8 ! additive factor, c - integer, parameter :: prn_bits = 63 ! number of bits, M - integer(C_INT64_T), parameter :: prn_mod = ibset(0_8, prn_bits) ! 2^M - integer(C_INT64_T), parameter :: prn_mask = not(prn_mod) ! 2^M - 1 - integer(C_INT64_T), parameter :: prn_stride = 152917_8 ! stride between particles - real(C_DOUBLE), parameter :: prn_norm = 2._8**(-prn_bits) ! 2^(-M) + function future_prn(n) result(pseudo_rn) bind(C, name='future_prn') + use ISO_C_BINDING + implicit none + integer(C_INT64_T), value :: n + real(C_DOUBLE) :: pseudo_rn + end function future_prn - ! Current PRNG state - integer(C_INT64_T) :: prn_seed(N_STREAMS) ! current seed - integer :: stream ! current RNG stream -!$omp threadprivate(prn_seed, stream) + subroutine set_particle_seed(id) bind(C, name='set_particle_seed') + use ISO_C_BINDING + implicit none + integer(C_INT64_T), value :: id + end subroutine set_particle_seed - public :: prn - public :: future_prn - public :: set_particle_seed - public :: advance_prn_seed - public :: prn_set_stream - public :: openmc_set_seed + subroutine advance_prn_seed(n) bind(C, name='advance_prn_seed') + use ISO_C_BINDING + implicit none + integer(C_INT64_T), value :: n + end subroutine advance_prn_seed -contains - -!=============================================================================== -! PRN generates a pseudo-random number using a linear congruential generator -!=============================================================================== - - function prn() result(pseudo_rn) - - real(C_DOUBLE) :: pseudo_rn - - ! This algorithm uses bit-masking to find the next integer(C_INT64_T) value - ! to be used to calculate the random number - - prn_seed(stream) = iand(prn_mult*prn_seed(stream) + prn_add, prn_mask) - - ! Once the integer is calculated, we just need to divide by 2**m, - ! represented here as multiplying by a pre-calculated factor - - pseudo_rn = prn_seed(stream) * prn_norm - - end function prn - -!=============================================================================== -! FUTURE_PRN generates a pseudo-random number which is 'n' times ahead from the -! current seed. -!=============================================================================== - - function future_prn(n) result(pseudo_rn) - - integer(C_INT64_T), intent(in) :: n ! number of prns to skip - - real(C_DOUBLE) :: pseudo_rn - - pseudo_rn = future_seed(n, prn_seed(stream)) * prn_norm - - end function future_prn - -!=============================================================================== -! SET_PARTICLE_SEED sets the seed to a unique value based on the ID of the -! particle -!=============================================================================== - - subroutine set_particle_seed(id) - - integer(C_INT64_T), intent(in) :: id - - integer :: i - - do i = 1, N_STREAMS - prn_seed(i) = future_seed(id*prn_stride, seed + i - 1) - end do - - end subroutine set_particle_seed - -!=============================================================================== -! ADVANCE_PRN_SEED advances the random number seed 'n' times from the current -! seed. -!=============================================================================== - - subroutine advance_prn_seed(n) - - integer(C_INT64_T), intent(in) :: n ! number of seeds to skip - - prn_seed(stream) = future_seed(n, prn_seed(stream)) - - end subroutine advance_prn_seed - -!=============================================================================== -! FUTURE_SEED advances the random number seed 'skip' times. This is usually -! used to skip a fixed number of random numbers (the stride) so that a given -! particle always has the same starting seed regardless of how many processors -! are used -!=============================================================================== - - function future_seed(n, seed) result(new_seed) - - integer(C_INT64_T), intent(in) :: n ! number of seeds to skip - integer(C_INT64_T), intent(in) :: seed ! original seed - integer(C_INT64_T) :: new_seed ! new seed - - integer(C_INT64_T) :: nskip ! positive number of seeds to skip - integer(C_INT64_T) :: g ! original multiplicative constant - integer(C_INT64_T) :: c ! original additive constnat - integer(C_INT64_T) :: g_new ! new effective multiplicative constant - integer(C_INT64_T) :: c_new ! new effective additive constant - - ! In cases where we want to skip backwards, we add the period of the random - ! number generator until the number of PRNs to skip is positive since - ! skipping ahead that much is the same as skipping backwards by the original - ! amount - - nskip = n - do while (nskip < 0_8) - nskip = nskip + prn_mod - end do - - ! Make sure nskip is less than 2^M - nskip = iand(nskip, prn_mask) - - ! The algorithm here to determine the parameters used to skip ahead is - ! described in F. Brown, "Random Number Generation with Arbitrary Stride," - ! Trans. Am. Nucl. Soc. (Nov. 1994). This algorithm is able to skip ahead in - ! O(log2(N)) operations instead of O(N). Basically, it computes parameters G - ! and C which can then be used to find x_N = G*x_0 + C mod 2^M. - - ! Initialize constants - g = prn_mult - c = prn_add - g_new = 1 - c_new = 0 - BIT_LOOP: do while (nskip > 0_8) - ! Check if least significant bit is 1 - if (btest(nskip,0)) then - g_new = iand(g_new*g, prn_mask) - c_new = iand(c_new*g + c, prn_mask) - endif - c = iand((g+1)*c, prn_mask) - g = iand(g*g, prn_mask) - - ! Move bits right, dropping least significant bit - nskip = ishft(nskip, -1) - end do BIT_LOOP - - ! With G and C, we can now find the new seed - new_seed = iand(g_new*seed + c_new, prn_mask) - - end function future_seed - -!=============================================================================== -! PRN_SET_STREAM changes the random number stream. If random numbers are needed -! in routines not used directly for tracking (e.g. physics), this allows the -! numbers to be generated without affecting reproducibility of the physics. -!=============================================================================== - - subroutine prn_set_stream(i) - - integer, intent(in) :: i - - stream = i - - end subroutine prn_set_stream - -!=============================================================================== -! C API FUNCTIONS -!=============================================================================== - - function openmc_set_seed(new_seed) result(err) bind(C) - ! Saves the starting seed and sets up the PRNG thread state - integer(C_INT64_T), value, intent(in) :: new_seed - integer(C_INT) :: err - - integer :: i - - err = 0 - seed = new_seed -!$omp parallel - do i = 1, N_STREAMS - prn_seed(i) = seed + i - 1 - end do - stream = STREAM_TRACKING -!$omp end parallel - - end function openmc_set_seed + subroutine prn_set_stream(n) bind(C, name='prn_set_stream') + use ISO_C_BINDING + implicit none + integer(C_INT), value :: n + end subroutine prn_set_stream + function openmc_set_seed(new_seed) result(err) & + bind(C, name='openmc_set_seed') + use ISO_C_BINDING + implicit none + integer(C_INT64_T), value :: new_seed + integer(C_INT) :: err + end function openmc_set_seed + end interface end module random_lcg diff --git a/src/random_lcg.h b/src/random_lcg.h new file mode 100644 index 000000000..90d1c253e --- /dev/null +++ b/src/random_lcg.h @@ -0,0 +1,56 @@ +#ifndef RANDOM_LCG_H +#define RANDOM_LCG_H + +#include + +//============================================================================== +// PRN generates a pseudo-random number using a linear congruential generator. +//============================================================================== + +extern "C" double prn(); + +//============================================================================== +// FUTURE_PRN generates a pseudo-random number which is 'n' times ahead from the +// current seed. +//============================================================================== + +extern "C" double future_prn(int64_t n); + +//============================================================================== +// SET_PARTICLE_SEED sets the seed to a unique value based on the ID of the +// particle. +//============================================================================== + +extern "C" void set_particle_seed(int64_t id); + +//============================================================================== +// ADVANCE_PRN_SEED advances the random number seed 'n' times from the current +// seed. +//============================================================================== + +extern "C" void advance_prn_seed(int64_t n); + +//============================================================================== +// FUTURE_SEED advances the random number seed 'skip' times. This is usually +// used to skip a fixed number of random numbers (the stride) so that a given +// particle always has the same starting seed regardless of how many processors +// are used. +//============================================================================== + +extern "C" int64_t future_seed(int64_t n, int64_t seed); + +//============================================================================== +// PRN_SET_STREAM changes the random number stream. If random numbers are needed +// in routines not used directly for tracking (e.g. physics), this allows the +// numbers to be generated without affecting reproducibility of the physics. +//============================================================================== + +extern "C" void prn_set_stream(int n); + +//============================================================================== +// API FUNCTIONS +//============================================================================== + +extern "C" int openmc_set_seed(int64_t new_seed); + +#endif // RANDOM_LCG_H From 6811a197325a094db06a051860c85daf438b2435 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 1 Dec 2017 17:32:27 -0500 Subject: [PATCH 004/282] Use unsigned integers in the RNG --- src/random_lcg.C | 49 +++++++++++++++++++++++------------------------- src/random_lcg.h | 10 +++++----- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/src/random_lcg.C b/src/random_lcg.C index c78c40748..8383401f9 100644 --- a/src/random_lcg.C +++ b/src/random_lcg.C @@ -6,12 +6,12 @@ int64_t seed = 1; // LCG parameters -const int64_t prn_mult = 2806196910506780709LL; // multiplication factor, g -const int64_t prn_add = 1; // additive factor, c -const int64_t prn_mod = -0x8000000000000000; // -2^63 -const int64_t prn_mask = 0x7fffffffffffffff; // 2^63 - 1 -const int64_t prn_stride = 152917LL; // stride between particles -const double prn_norm = pow(2, -63); // 2^-63 +const uint64_t prn_mult = 2806196910506780709LL; // multiplication factor, g +const uint64_t prn_add = 1; // additive factor, c +const uint64_t prn_mod = 0x8000000000000000; // 2^63 +const uint64_t prn_mask = 0x7fffffffffffffff; // 2^63 - 1 +const uint64_t prn_stride = 152917LL; // stride between particles +const double prn_norm = pow(2, -63); // 2^-63 // Module constants const int N_STREAMS = 5; @@ -22,8 +22,8 @@ const int STREAM_URR_PTABLE = 3; const int STREAM_VOLUME = 4; // Current PRNG state -int64_t prn_seed[N_STREAMS]; // current seed -int stream; // current RNG stream +uint64_t prn_seed[N_STREAMS]; // current seed +int stream; // current RNG stream #pragma omp threadprivate(prn_seed, stream) @@ -40,8 +40,7 @@ prn() // Once the integer is calculated, we just need to divide by 2**m, // represented here as multiplying by a pre-calculated factor - double pseudo_rn = prn_seed[stream] * prn_norm; - return pseudo_rn; + return prn_seed[stream] * prn_norm; } //============================================================================== @@ -49,10 +48,9 @@ prn() //============================================================================== extern "C" double -future_prn(int64_t n) +future_prn(uint64_t n) { - double pseudo_rn = future_seed(n, prn_seed[stream]) * prn_norm; - return pseudo_rn; + return future_seed(n, prn_seed[stream]) * prn_norm; } //============================================================================== @@ -60,7 +58,7 @@ future_prn(int64_t n) //============================================================================== extern "C" void -set_particle_seed(int64_t id) +set_particle_seed(uint64_t id) { for (int i = 0; i < N_STREAMS; i++) { prn_seed[i] = future_seed(id * prn_stride, seed + i); @@ -72,7 +70,7 @@ set_particle_seed(int64_t id) //============================================================================== extern "C" void -advance_prn_seed(int64_t n) +advance_prn_seed(uint64_t n) { prn_seed[stream] = future_seed(n, prn_seed[stream]); } @@ -81,16 +79,16 @@ advance_prn_seed(int64_t n) // FUTURE_SEED //============================================================================== -extern "C" int64_t -future_seed(int64_t n, int64_t seed) +extern "C" uint64_t +future_seed(uint64_t n, uint64_t seed) { // In cases where we want to skip backwards, we add the period of the random // number generator until the number of PRNs to skip is positive since // skipping ahead that much is the same as skipping backwards by the original // amount. - int64_t nskip = n; - while (nskip < 0) nskip += prn_mod; + uint64_t nskip = n; + while (nskip > prn_mod) nskip += prn_mod; // Make sure nskip is less than 2^M. nskip &= prn_mask; @@ -102,10 +100,10 @@ future_seed(int64_t n, int64_t seed) // and C which can then be used to find x_N = G*x_0 + C mod 2^M. // Initialize constants - int64_t g = prn_mult; - int64_t c = prn_add; - int64_t g_new = 1; - int64_t c_new = 0; + uint64_t g = prn_mult; + uint64_t c = prn_add; + uint64_t g_new = 1; + uint64_t c_new = 0; while (nskip > 0) { // Check if the least significant bit is 1. @@ -121,8 +119,7 @@ future_seed(int64_t n, int64_t seed) } // With G and C, we can now find the new seed. - int64_t new_seed = (g_new * seed + c_new) & prn_mask; - return new_seed; + return (g_new * seed + c_new) & prn_mask; } //============================================================================== @@ -140,7 +137,7 @@ prn_set_stream(int i) //============================================================================== extern "C" int -openmc_set_seed(int64_t new_seed) +openmc_set_seed(uint64_t new_seed) { seed = new_seed; #pragma omp parallel diff --git a/src/random_lcg.h b/src/random_lcg.h index 90d1c253e..889195f10 100644 --- a/src/random_lcg.h +++ b/src/random_lcg.h @@ -14,21 +14,21 @@ extern "C" double prn(); // current seed. //============================================================================== -extern "C" double future_prn(int64_t n); +extern "C" double future_prn(uint64_t n); //============================================================================== // SET_PARTICLE_SEED sets the seed to a unique value based on the ID of the // particle. //============================================================================== -extern "C" void set_particle_seed(int64_t id); +extern "C" void set_particle_seed(uint64_t id); //============================================================================== // ADVANCE_PRN_SEED advances the random number seed 'n' times from the current // seed. //============================================================================== -extern "C" void advance_prn_seed(int64_t n); +extern "C" void advance_prn_seed(uint64_t n); //============================================================================== // FUTURE_SEED advances the random number seed 'skip' times. This is usually @@ -37,7 +37,7 @@ extern "C" void advance_prn_seed(int64_t n); // are used. //============================================================================== -extern "C" int64_t future_seed(int64_t n, int64_t seed); +extern "C" uint64_t future_seed(uint64_t n, uint64_t seed); //============================================================================== // PRN_SET_STREAM changes the random number stream. If random numbers are needed @@ -51,6 +51,6 @@ extern "C" void prn_set_stream(int n); // API FUNCTIONS //============================================================================== -extern "C" int openmc_set_seed(int64_t new_seed); +extern "C" int openmc_set_seed(uint64_t new_seed); #endif // RANDOM_LCG_H From 1cfa7c89568450f110e570dbf57b2266bd2d1a37 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 4 Dec 2017 15:19:49 -0500 Subject: [PATCH 005/282] Simplify RNG --- src/random_lcg.C | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/random_lcg.C b/src/random_lcg.C index 8383401f9..ef49cd13b 100644 --- a/src/random_lcg.C +++ b/src/random_lcg.C @@ -11,10 +11,10 @@ const uint64_t prn_add = 1; // additive factor, c const uint64_t prn_mod = 0x8000000000000000; // 2^63 const uint64_t prn_mask = 0x7fffffffffffffff; // 2^63 - 1 const uint64_t prn_stride = 152917LL; // stride between particles -const double prn_norm = pow(2, -63); // 2^-63 +const double prn_norm = pow(2, -63); // 2^-63 // Module constants -const int N_STREAMS = 5; +const int N_STREAMS = 5; const int STREAM_TRACKING = 0; const int STREAM_TALLIES = 1; const int STREAM_SOURCE = 2; @@ -82,16 +82,8 @@ advance_prn_seed(uint64_t n) extern "C" uint64_t future_seed(uint64_t n, uint64_t seed) { - // In cases where we want to skip backwards, we add the period of the random - // number generator until the number of PRNs to skip is positive since - // skipping ahead that much is the same as skipping backwards by the original - // amount. - - uint64_t nskip = n; - while (nskip > prn_mod) nskip += prn_mod; - // Make sure nskip is less than 2^M. - nskip &= prn_mask; + n &= prn_mask; // The algorithm here to determine the parameters used to skip ahead is // described in F. Brown, "Random Number Generation with Arbitrary Stride," @@ -105,17 +97,17 @@ future_seed(uint64_t n, uint64_t seed) uint64_t g_new = 1; uint64_t c_new = 0; - while (nskip > 0) { + while (n > 0) { // Check if the least significant bit is 1. - if (nskip & 1) { - g_new = (g_new * g) & prn_mask; - c_new = (c_new * g + c) & prn_mask; + if (n & 1) { + g_new = g_new * g; + c_new = c_new * g + c; } - c = ((g + 1) * c) & prn_mask; - g = (g * g) & prn_mask; + c = (g + 1) * c; + g = g * g; // Move bits right, dropping least significant bit. - nskip >>= 1; + n >>= 1; } // With G and C, we can now find the new seed. From 87c06cd334ccc61f96429151d479e6bc9ddefbab Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 28 Oct 2017 22:04:57 -0400 Subject: [PATCH 006/282] Update list of publications. Add note about citing OpenMC. --- docs/source/index.rst | 26 ++++++----- docs/source/publications.rst | 86 +++++++++++++++++++++++++----------- readme.rst | 12 +++++ 3 files changed, 88 insertions(+), 36 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 00b09db9e..4071b9794 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -10,21 +10,25 @@ interaction data is based on a native HDF5 format that can be generated from ACE files used by the MCNP and Serpent Monte Carlo codes. OpenMC was originally developed by members of the `Computational Reactor Physics -Group`_ at the `Massachusetts Institute of Technology`_ starting -in 2011. Various universities, laboratories, and other organizations now -contribute to the development of OpenMC. For more information on OpenMC, feel -free to send a message to the User's Group `mailing list`_. +Group `_ at the `Massachusetts Institute of Technology +`_ starting in 2011. Various universities, laboratories, and +other organizations now contribute to the development of OpenMC. For more +information on OpenMC, feel free to send a message to the User's Group `mailing +list `_. -.. _Computational Reactor Physics Group: http://crpg.mit.edu -.. _Massachusetts Institute of Technology: http://web.mit.edu -.. _mailing list: https://groups.google.com/forum/?fromgroups=#!forum/openmc-users -.. _Read the Docs: http://openmc.readthedocs.io/en/latest/ +.. admonition:: Recommended publication for citing + :class: tip + + Paul K. Romano, Nicholas E. Horelik, Bryan R. Herman, Adam G. Nelson, Benoit + Forget, and Kord Smith, "`OpenMC: A State-of-the-Art Monte Carlo Code for + Research and Development `_," + *Ann. Nucl. Energy*, **82**, 90--97 (2015). .. only:: html - -------- - Contents - -------- + -------- + Contents + -------- .. toctree:: :maxdepth: 1 diff --git a/docs/source/publications.rst b/docs/source/publications.rst index f50ec8a68..f47ac704c 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -10,7 +10,7 @@ Overviews - Paul K. Romano, Nicholas E. Horelik, Bryan R. Herman, Adam G. Nelson, Benoit Forget, and Kord Smith, "`OpenMC: A State-of-the-Art Monte Carlo Code for - Research and Development `_," + Research and Development `_," *Ann. Nucl. Energy*, **82**, 90--97 (2015). - Paul K. Romano, Bryan R. Herman, Nicholas E. Horelik, Benoit Forget, Kord @@ -19,16 +19,19 @@ Overviews Nuclear Science and Engineering*, Sun Valley, Idaho, May 5--9 (2013). - Paul K. Romano and Benoit Forget, "`The OpenMC Monte Carlo Particle Transport - Code `_," + Code `_," *Ann. Nucl. Energy*, **51**, 274--281 (2013). ------------ Benchmarking ------------ +- Travis J. Labossiere-Hickman and Benoit Forget, "Selected VERA Core Physics + Benchmarks in OpenMC," *Trans. Am. Nucl. Soc.*, **117**, 1520-1523 (2017). + - Khurrum S. Chaudri and Sikander M. Mirza, "`Burnup dependent Monte Carlo neutron physics calculations of IAEA MTR benchmark - `_," *Prog. Nucl. Energy*, + `_," *Prog. Nucl. Energy*, **81**, 43-52 (2015). - Daniel J. Kelly, Brian N. Aviles, Paul K. Romano, Bryan R. Herman, @@ -54,10 +57,15 @@ Benchmarking Coupling and Multi-physics -------------------------- +- 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 `_," + *Ann. Nucl. Energy*, **109**, 260-276 (2017). + - Matthew Ellis, Derek Gaston, Benoit Forget, and Kord Smith, "`Preliminary Coupling of the Monte Carlo Code OpenMC and the Multiphysics Object-Oriented Simulation Environment for Analyzing Doppler Feedback in Monte Carlo - Simulations `_," *Nucl. Sci. Eng.*, + Simulations `_," *Nucl. Sci. Eng.*, **185**, 184-193 (2017). - Matthew Ellis, Benoit Forget, Kord Smith, and Derek Gaston, "Continuous @@ -79,7 +87,7 @@ Coupling and Multi-physics - Bryan R. Herman, Benoit Forget, and Kord Smith, "`Progress toward Monte Carlo-thermal hydraulic coupling using low-order nonlinear diffusion - acceleration methods `_," + acceleration methods `_," *Ann. Nucl. Energy*, **84**, 63-72 (2015). - Bryan R. Herman, Benoit Forget, and Kord Smith, "Utilizing CMFD in OpenMC to @@ -106,6 +114,15 @@ Geometry and Visualization Miscellaneous ------------- +- 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). + +- Youqi Zheng, Yunlong Xiao, and Hongchun Wu, "`Application of the virtual + density theory in fast reactor analysis based on the neutron transport + calculation `_," + *Nucl. Eng. Des.*, **320**, 200-206 (2017). + - Amanda L. Lund, Paul K. Romano, and Andrew R. Siegel, "Accelerating Source Convergence in Monte Carlo Criticality Calculations Using a Particle Ramp-Up Technique," *Proc. Int. Conf. Mathematics & Computational Methods Applied to @@ -126,7 +143,7 @@ Miscellaneous - Yunzhao Li, Qingming He, Liangzhi Cao, Hongchun Wu, and Tiejun Zu, "`Resonance Elastic Scattering and Interference Effects Treatments in Subgroup Method - `_," *Nucl. Eng. Tech.*, **48**, + `_," *Nucl. Eng. Tech.*, **48**, 339-350 (2016). - William Boyd, Sterling Harper, and Paul K. Romano, "Equipping OpenMC for the @@ -135,7 +152,7 @@ Miscellaneous - Michal Kostal, Vojtech Rypar, Jan Milcak, Vlastimil Juricek, Evzen Losa, Benoit Forget, and Sterling Harper, "`Study of graphite reactivity worth on well-defined cores assembled on LR-0 reactor - `_," *Ann. Nucl. Energy*, + `_," *Ann. Nucl. Energy*, **87**, 601-611 (2016). - Qicang Shen, William Boyd, Benoit Forget, and Kord Smith, "Tally precision @@ -154,9 +171,19 @@ Miscellaneous Multi-group Cross Section Generation ------------------------------------ +- Gang Yang, Tongkyu Park, and Won Sik Yang, "Effects of Fuel Salt Velocity + Field on Neutronics Performances in Molten Salt Reactors with Open Flow + Channels," *Trans. Am. Nucl. Soc.*, **117**, 1339-1342 (2017). + +- William Boyd, Nathan Gibson, Benoit Forget, and Kord Smith, "`An analysis of + condensation errors in multi-group cross section generation for fine-mesh + neutron transport calculations + `_," *Ann. Nucl. Energy*, + **112**, 267-276 (2018). + - Hong Shuang, Yang Yongwei, Zhang Lu, and Gao Yucui, "`Fabrication and validation of multigroup cross section library based on the OpenMC code - `_," + `_," *Nucl. Techniques* **40** (4), 040504 (2017). (in Mandarin) - Nicholas E. Stauff, Changho Lee, Paul K. Romano, and Taek K. Kim, @@ -207,7 +234,7 @@ Doppler Broadening - Colin Josey, Pablo Ducru, Benoit Forget, and Kord Smith, "`Windowed multipole for cross section Doppler broadening - `_," *J. Comput. Phys.*, **307**, + `_," *J. Comput. Phys.*, **307**, 715-727 (2016). - Jonathan A. Walsh, Benoit Forget, Kord S. Smith, and Forrest B. Brown, @@ -217,12 +244,12 @@ Doppler Broadening - Colin Josey, Benoit Forget, and Kord Smith, "`Windowed multipole sensitivity to target accuracy of the optimization procedure - `_," + `_," *J. Nucl. Sci. Technol.*, **52**, 987-992 (2015). - Paul K. Romano and Timothy H. Trumbull, "`Comparison of algorithms for Doppler broadening pointwise tabulated cross sections - `_," *Ann. Nucl. Energy*, + `_," *Ann. Nucl. Energy*, **75**, 358--364 (2015). - Tuomas Viitanen, Jaakko Leppanen, and Benoit Forget, "Target motion sampling @@ -231,13 +258,17 @@ Doppler Broadening - Benoit Forget, Sheng Xu, and Kord Smith, "`Direct Doppler broadening in Monte Carlo simulations using the multipole representation - `_," *Ann. Nucl. Energy*, + `_," *Ann. Nucl. Energy*, **64**, 78--85 (2014). ------------ Nuclear Data ------------ +- Jonathan A. Walsh, "Comparison of Unresolved Resonance Region Cross Section + Formalisms in Transport Simulations," *Trans. Am. Nucl. Soc.*, **117**, + 749-752 (2017). + - Jonathan A. Walsh, Benoit Forget, Kord S. Smith, and Forrest B. Brown, "`Uncertainty in Fast Reactor-Relevant Critical Benchmark Simulations Due to Unresolved Resonance Structure @@ -255,13 +286,13 @@ Nuclear Data - Jonathan A. Walsh, Benoit Froget, Kord S. Smith, and Forrest B. Brown, "`Neutron Cross Section Processing Methods for Improved Integral Benchmarking of Unresolved Resonance Region Evaluations - `_," *Eur. Phys. J. Web Conf.* + `_," *Eur. Phys. J. Web Conf.* **111**, 06001 (2016). - Jonathan A. Walsh, Paul K. Romano, Benoit Forget, and Kord S. Smith, "`Optimizations of the energy grid search algorithm in continuous-energy Monte Carlo particle transport codes - `_", *Comput. Phys. Commun.*, + `_", *Comput. Phys. Commun.*, **196**, 134-142 (2015). - Jonathan A. Walsh, Benoit Forget, Kord S. Smith, Brian C. Kiedrowski, and @@ -280,7 +311,7 @@ Nuclear Data - Jonathan A. Walsh, Benoit Forget, and Kord S. Smith, "`Accelerated sampling of the free gas resonance elastic scattering kernel - `_," *Ann. Nucl. Energy*, + `_," *Ann. Nucl. Energy*, **69**, 116--124 (2014). ----------- @@ -325,45 +356,45 @@ Parallelism - Nicholas Horelik, Andrew Siegel, Benoit Forget, and Kord Smith, "`Monte Carlo domain decomposition for robust nuclear reactor analysis - `_," *Parallel Comput.*, + `_," *Parallel Comput.*, **40**, 646--660 (2014). - Andrew Siegel, Kord Smith, Kyle Felker, Paul Romano, Benoit Forget, and Peter Beckman, "`Improved cache performance in Monte Carlo transport calculations - using energy banding `_," + using energy banding `_," *Comput. Phys. Commun.*, **185** (4), 1195--1199 (2014). - Paul K. Romano, Benoit Forget, Kord Smith, and Andrew Siegel, "`On the use of tally servers in Monte Carlo simulations of light-water reactors - `_," *Proc. Joint International + `_," *Proc. Joint International Conference on Supercomputing in Nuclear Applications and Monte Carlo*, Paris, France, Oct. 27--31 (2013). - Kyle G. Felker, Andrew R. Siegel, Kord S. Smith, Paul K. Romano, and Benoit Forget, "`The energy band memory server algorithm for parallel Monte Carlo - calculations `_," *Proc. Joint + calculations `_," *Proc. Joint International Conference on Supercomputing in Nuclear Applications and Monte Carlo*, Paris, France, Oct. 27--31 (2013). - John R. Tramm and Andrew R. Siegel, "`Memory Bottlenecks and Memory Contention in Multi-Core Monte Carlo Transport Codes - `_," *Proc. Joint International + `_," *Proc. Joint International Conference on Supercomputing in Nuclear Applications and Monte Carlo*, Paris, France, Oct. 27--31 (2013). - Andrew R. Siegel, Kord Smith, Paul K. Romano, Benoit Forget, and Kyle Felker, "`Multi-core performance studies of a Monte Carlo neutron transport code - `_," *Int. J. High + `_," *Int. J. High Perform. Comput. Appl.*, **28** (1), 87--96 (2014). - Paul K. Romano, Andrew R. Siegel, Benoit Forget, and Kord Smith, "`Data decomposition of Monte Carlo particle transport simulations via tally servers - `_," *J. Comput. Phys.*, **252**, + `_," *J. Comput. Phys.*, **252**, 20--36 (2013). - Andrew R. Siegel, Kord Smith, Paul K. Romano, Benoit Forget, and Kyle Felker, "`The effect of load imbalances on the performance of Monte Carlo codes in LWR - analysis `_," *J. Comput. Phys.*, + analysis `_," *J. Comput. Phys.*, **235**, 901--911 (2013). @@ -372,13 +403,18 @@ Parallelism 519--522 (2012). - Paul K. Romano and Benoit Forget, "`Parallel Fission Bank Algorithms in Monte - Carlo Criticality Calculations `_," + Carlo Criticality Calculations `_," *Nucl. Sci. Eng.*, **170**, 125--135 (2012). --------- Depletion --------- +- Colin Josey, Benoit Forget, and Kord Smith, "`High order methods for the + integration of the Bateman equations and other problems of the form of y' = + F(y,t)y `_," *J. Comput. Phys.*, + **350**, 296-313 (2017). + - Matthew S. Ellis, Colin Josey, Benoit Forget, and Kord Smith, "`Spatially Continuous Depletion Algorithm for Monte Carlo Simulations `_," *Trans. Am. Nucl. Soc.*, **115**, @@ -386,7 +422,7 @@ Depletion - Anas Gul, K. S. Chaudri, R. Khan, and M. Azeen, "`Development and verification of LOOP: A Linkage of ORIGEN2.2 and OpenMC - `_," *Ann. Nucl. Energy*, + `_," *Ann. Nucl. Energy*, **99**, 321--327 (2017). - Kai Huang, Hongchun Wu, Yunzhao Li, and Liangzhi Cao, "Generalized depletion diff --git a/readme.rst b/readme.rst index 75e4a1438..b7366fe3e 100644 --- a/readme.rst +++ b/readme.rst @@ -20,6 +20,18 @@ Installation Detailed `installation instructions`_ can be found in the User's Guide. +------ +Citing +------ + +If you use OpenMC in your research, please consider giving proper attribution by +citing the following publication: + +- Paul K. Romano, Nicholas E. Horelik, Bryan R. Herman, Adam G. Nelson, Benoit + Forget, and Kord Smith, "`OpenMC: A State-of-the-Art Monte Carlo Code for + Research and Development `_," + *Ann. Nucl. Energy*, **82**, 90--97 (2015). + --------------- Troubleshooting --------------- From 3af20e8faf4fc65ff5abae78c2c66ad2ef4bffa8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 29 Oct 2017 05:47:48 -0400 Subject: [PATCH 007/282] Fix openmc.capi.Tally.scores getter --- openmc/capi/tally.py | 31 ++++++++++++++++++++++++++++++- src/api.F90 | 1 + src/constants.F90 | 3 ++- src/tallies/tally_header.F90 | 26 ++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index 799cb42ee..7037484ae 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -4,6 +4,7 @@ from weakref import WeakValueDictionary from numpy.ctypeslib import as_array +from openmc.data.reaction import REACTION_NAME from . import _dll, Nuclide from .core import _FortranObjectWithID from .error import _error_handler, AllocationError, InvalidIDError @@ -30,6 +31,10 @@ _dll.openmc_tally_get_nuclides.argtypes = [ c_int32, POINTER(POINTER(c_int)), POINTER(c_int)] _dll.openmc_tally_get_nuclides.restype = c_int _dll.openmc_tally_get_nuclides.errcheck = _error_handler +_dll.openmc_tally_get_scores.argtypes = [ + c_int32, POINTER(POINTER(c_int)), POINTER(c_int)] +_dll.openmc_tally_get_scores.restype = c_int +_dll.openmc_tally_get_scores.errcheck = _error_handler _dll.openmc_tally_results.argtypes = [ c_int32, POINTER(POINTER(c_double)), POINTER(c_int*3)] _dll.openmc_tally_results.restype = c_int @@ -51,6 +56,15 @@ _dll.openmc_tally_set_type.restype = c_int _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' +} + + class Tally(_FortranObjectWithID): """Tally stored internally. @@ -161,7 +175,22 @@ class Tally(_FortranObjectWithID): @property def scores(self): - pass + scores_as_int = POINTER(c_int)() + n = c_int() + try: + _dll.openmc_tally_get_scores(self._index, scores_as_int, n) + except AllocationError: + return [] + else: + scores = [] + for i in range(n.value): + if scores_as_int[i] in _SCORES: + scores.append(_SCORES[scores_as_int[i]]) + elif scores_as_int[i] in REACTION_NAME: + scores.append(REACTION_NAME[scores_as_int[i]]) + else: + scores.append(str(scores_as_int[i])) + return scores @scores.setter def scores(self, scores): diff --git a/src/api.F90 b/src/api.F90 index f5b27e718..933faaa09 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -74,6 +74,7 @@ module openmc_api public :: openmc_tally_get_id public :: openmc_tally_get_filters public :: openmc_tally_get_nuclides + public :: openmc_tally_get_scores public :: openmc_tally_results public :: openmc_tally_set_filters public :: openmc_tally_set_id diff --git a/src/constants.F90 b/src/constants.F90 index 73aca25d2..f6a7e8da9 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -304,7 +304,8 @@ module constants EVENT_SCATTER = 1, & EVENT_ABSORB = 2 - ! Tally score type + ! 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 :: & SCORE_FLUX = -1, & ! flux diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index df9d935e9..e137a1327 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -25,6 +25,7 @@ module tally_header public :: openmc_tally_get_id public :: openmc_tally_get_filters public :: openmc_tally_get_nuclides + public :: openmc_tally_get_scores public :: openmc_tally_results public :: openmc_tally_set_filters public :: openmc_tally_set_id @@ -535,6 +536,31 @@ contains end function openmc_tally_get_nuclides + function openmc_tally_get_scores(index, scores, n) result(err) bind(C) + ! Return the list of nuclides assigned to a tally + integer(C_INT32_T), value :: index + type(C_PTR), intent(out) :: scores + integer(C_INT), intent(out) :: n + integer(C_INT) :: err + + if (index >= 1 .and. index <= size(tallies)) then + associate (t => tallies(index) % obj) + if (allocated(t % score_bins)) then + scores = C_LOC(t % score_bins(1)) + n = size(t % score_bins) + err = 0 + else + err = E_ALLOCATE + call set_errmsg("Tally scores have not been allocated yet.") + end if + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg('Index in tallies array is out of bounds.') + end if + end function openmc_tally_get_scores + + function openmc_tally_results(index, ptr, shape_) result(err) bind(C) ! Returns a pointer to a tally results array along with its shape. This ! allows a user to obtain in-memory tally results from Python directly. From 9f66edb7fb01ea50cf11e2056c45a77e488c1257 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 3 Nov 2017 07:25:23 -0500 Subject: [PATCH 008/282] Move batch logic into openmc_next_batch --- src/simulation.F90 | 129 +++++++++++++++++++++++---------------------- 1 file changed, 65 insertions(+), 64 deletions(-) diff --git a/src/simulation.F90 b/src/simulation.F90 index a240f55b0..5fd6f60f7 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -43,6 +43,8 @@ module simulation implicit none private public :: openmc_run + public :: openmc_simulation_init + public :: openmc_simulation_finalize contains @@ -54,75 +56,71 @@ contains subroutine openmc_run() bind(C) - type(Particle) :: p - integer(8) :: i_work + call openmc_simulation_init() - call initialize_simulation() - - ! Turn on inactive timer - call time_inactive % start() - - ! ========================================================================== - ! LOOP OVER BATCHES BATCH_LOOP: do current_batch = 1, n_max_batches - - call initialize_batch() - - ! Handle restart runs - if (restart_run .and. current_batch <= restart_batch) then - call replay_batch_history() - cycle BATCH_LOOP - end if - - ! ======================================================================= - ! LOOP OVER GENERATIONS - GENERATION_LOOP: do current_gen = 1, gen_per_batch - - call initialize_generation() - - ! Start timer for transport - call time_transport % start() - - ! ==================================================================== - ! LOOP OVER PARTICLES -!$omp parallel do schedule(static) firstprivate(p) copyin(tally_derivs) - PARTICLE_LOOP: do i_work = 1, work - current_work = i_work - - ! grab source particle from bank - call initialize_history(p, current_work) - - ! transport particle - call transport(p) - - end do PARTICLE_LOOP -!$omp end parallel do - - ! Accumulate time for transport - call time_transport % stop() - - call finalize_generation() - - end do GENERATION_LOOP - - call finalize_batch() - + call openmc_next_batch() if (satisfy_triggers) exit BATCH_LOOP - end do BATCH_LOOP call time_active % stop() - ! ========================================================================== - ! END OF RUN WRAPUP - - call finalize_simulation() - - ! Clear particle - call p % clear() + call openmc_simulation_finalize() end subroutine openmc_run +!=============================================================================== +! OPENMC_NEXT_BATCH +!=============================================================================== + + subroutine openmc_next_batch() bind(C) + + type(Particle) :: p + integer(8) :: i_work + + call initialize_batch() + + ! Handle restart runs + if (restart_run .and. current_batch <= restart_batch) then + call replay_batch_history() + return + end if + + ! ======================================================================= + ! LOOP OVER GENERATIONS + GENERATION_LOOP: do current_gen = 1, gen_per_batch + + call initialize_generation() + + ! Start timer for transport + call time_transport % start() + + ! ==================================================================== + ! LOOP OVER PARTICLES +!$omp parallel do schedule(static) firstprivate(p) copyin(tally_derivs) + PARTICLE_LOOP: do i_work = 1, work + current_work = i_work + + ! grab source particle from bank + call initialize_history(p, current_work) + + ! transport particle + call transport(p) + + end do PARTICLE_LOOP +!$omp end parallel do + + ! Accumulate time for transport + call time_transport % stop() + + call finalize_generation() + + end do GENERATION_LOOP + + call finalize_batch() + + end subroutine openmc_next_batch + !=============================================================================== ! INITIALIZE_HISTORY !=============================================================================== @@ -184,7 +182,10 @@ contains ! Reset total starting particle weight used for normalizing tallies total_weight = ZERO - if (current_batch == n_inactive + 1) then + if (n_inactive > 0 .and. current_batch == 1) then + ! Turn on inactive timer + call time_inactive % start() + elseif (current_batch == n_inactive + 1) then ! Switch from inactive batch timer to active batch timer call time_inactive % stop() call time_active % start() @@ -385,7 +386,7 @@ contains ! INITIALIZE_SIMULATION !=============================================================================== - subroutine initialize_simulation() + subroutine openmc_simulation_init() bind(C) ! Set up tally procedure pointers call init_tally_routines() @@ -426,14 +427,14 @@ contains end if end if - end subroutine initialize_simulation + end subroutine openmc_simulation_init !=============================================================================== ! FINALIZE_SIMULATION calculates tally statistics, writes tallies, and displays ! execution time and results !=============================================================================== - subroutine finalize_simulation() + subroutine openmc_simulation_finalize() bind(C) #ifdef MPI integer :: i ! loop index for tallies @@ -496,7 +497,7 @@ contains if (check_overlaps) call print_overlap_check() end if - end subroutine finalize_simulation + end subroutine openmc_simulation_finalize !=============================================================================== ! CALCULATE_WORK determines how many particles each processor should simulate From ac565724dcb06c82ef847f20c68ff3010f8bd1a9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 3 Nov 2017 09:46:48 -0500 Subject: [PATCH 009/282] Have openmc_next_batch return a status --- src/simulation.F90 | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/simulation.F90 b/src/simulation.F90 index 5fd6f60f7..e45e43126 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -58,10 +58,9 @@ contains call openmc_simulation_init() - BATCH_LOOP: do current_batch = 1, n_max_batches - call openmc_next_batch() - if (satisfy_triggers) exit BATCH_LOOP - end do BATCH_LOOP + do + if (openmc_next_batch() < 0) exit + end do call time_active % stop() @@ -73,7 +72,8 @@ contains ! OPENMC_NEXT_BATCH !=============================================================================== - subroutine openmc_next_batch() bind(C) + function openmc_next_batch() result(retval) bind(C) + integer(C_INT) :: retval type(Particle) :: p integer(8) :: i_work @@ -119,7 +119,16 @@ contains call finalize_batch() - end subroutine openmc_next_batch + ! Check simulation ending criteria + if (current_batch == n_max_batches) then + retval = -1 + elseif (satisfy_triggers) then + retval = -2 + else + retval = 0 + end if + + end function openmc_next_batch !=============================================================================== ! INITIALIZE_HISTORY @@ -174,6 +183,9 @@ contains integer :: i + ! Increment current batch + current_batch = current_batch + 1 + if (run_mode == MODE_FIXEDSOURCE) then call write_message("Simulating batch " // trim(to_str(current_batch)) & // "...", 6) @@ -427,6 +439,9 @@ contains end if end if + ! Reset current batch + current_batch = 0 + end subroutine openmc_simulation_init !=============================================================================== From f14957940dad27da3258ce0b11cdfa3e2ba666c2 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 3 Nov 2017 21:45:49 -0500 Subject: [PATCH 010/282] Add openmc.capi.global_tallies() --- openmc/capi/tally.py | 33 +++++++++++++++++++++++++++++---- src/tallies/tally_header.F90 | 15 ++++++++++++++- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index 7037484ae..14f9d1c1c 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -2,6 +2,7 @@ from collections import Mapping from ctypes import c_int, c_int32, c_double, c_char_p, POINTER from weakref import WeakValueDictionary +import numpy as np from numpy.ctypeslib import as_array from openmc.data.reaction import REACTION_NAME @@ -11,15 +12,18 @@ from .error import _error_handler, AllocationError, InvalidIDError from .filter import _get_filter -__all__ = ['Tally', 'tallies'] +__all__ = ['Tally', 'tallies', 'global_tallies'] # Tally functions -_dll.openmc_get_tally_index.argtypes = [c_int32, POINTER(c_int32)] -_dll.openmc_get_tally_index.restype = c_int -_dll.openmc_get_tally_index.errcheck = _error_handler _dll.openmc_extend_tallies.argtypes = [c_int32, POINTER(c_int32), POINTER(c_int32)] _dll.openmc_extend_tallies.restype = c_int _dll.openmc_extend_tallies.errcheck = _error_handler +_dll.openmc_get_tally_index.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_get_tally_index.restype = c_int +_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_id.argtypes = [c_int32, POINTER(c_int32)] _dll.openmc_tally_get_id.restype = c_int _dll.openmc_tally_get_id.errcheck = _error_handler @@ -65,6 +69,27 @@ _SCORES = { } +def global_tallies(): + ptr = POINTER(c_double)() + _dll.openmc_global_tallies(ptr) + array = as_array(ptr, (4, 3)) + + # Get sum, sum-of-squares, and number of realizations + sum_ = array[:, 1] + sum_sq = array[:, 2] + n = c_int32.in_dll(_dll, 'n_realizations').value + + # Determine mean + mean = sum_ / n + + # Determine standard deviation + nonzero = np.abs(mean) > 0 + stdev = np.zeros_like(mean) + stdev[nonzero] = np.sqrt((sum_sq[nonzero]/n - mean[nonzero]**2)/(n - 1)) + + return list(zip(mean, stdev)) + + class Tally(_FortranObjectWithID): """Tally stored internally. diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index e137a1327..5aa16c2d0 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -146,7 +146,7 @@ module tally_header type(VectorInt), public :: active_surface_tallies ! Normalization for statistics - integer, public :: n_realizations = 0 ! # of independent realizations + integer(C_INT32_T), public, bind(C) :: n_realizations = 0 ! # of independent realizations real(8), public :: total_weight ! total starting particle weight in realization contains @@ -470,6 +470,19 @@ contains end function openmc_get_tally_index + function openmc_global_tallies(ptr) result(err) bind(C) + type(C_PTR), intent(out) :: ptr + integer(C_INT) :: err + + if (.not. allocated(global_tallies)) then + err = E_ALLOCATE + else + err = 0 + ptr = C_LOC(global_tallies) + end if + end function openmc_global_tallies + + function openmc_tally_get_id(index, id) result(err) bind(C) ! Return the ID of a tally integer(C_INT32_T), value :: index From 9ddc7316e494a8a81490a543bb5ba9222f03c1f4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 8 Nov 2017 16:26:28 -0600 Subject: [PATCH 011/282] Add simulation_init/finalize, iter_batches, source_bank in openmc.capi --- openmc/capi/core.py | 73 +++++++++++++++++++++++++++++++++++- src/bank_header.F90 | 22 +++++++++++ src/simulation.F90 | 4 +- src/state_point.F90 | 6 +-- src/tallies/tally_header.F90 | 2 + 5 files changed, 101 insertions(+), 6 deletions(-) diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 0d8ff5c43..530ed5063 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -1,11 +1,23 @@ from contextlib import contextmanager -from ctypes import CDLL, c_int, c_int32, c_double, POINTER +from ctypes import CDLL, c_int, c_int32, c_int64, c_double, POINTER, Structure from warnings import warn +import numpy as np +from numpy.ctypeslib import as_array + from . import _dll from .error import _error_handler +class _Bank(Structure): + _fields_ = [('wgt', c_double), + ('xyz', c_double*3), + ('uvw', c_double*3), + ('E', c_double), + ('delayed_group', c_int)] + + + _dll.openmc_calculate_volumes.restype = None _dll.openmc_finalize.restype = None _dll.openmc_find.argtypes = [POINTER(c_double*3), c_int, POINTER(c_int32), @@ -18,9 +30,16 @@ _dll.openmc_init.restype = None _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.restype = c_int _dll.openmc_plot_geometry.restype = None _dll.openmc_run.restype = None _dll.openmc_reset.restype = None +_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_statepoint_write.restype = None def calculate_volumes(): @@ -102,6 +121,20 @@ def init(intracomm=None): _dll.openmc_init(None) +def iter_batches(): + """Iterator over batches.""" + while True: + # Run next batch + retval = _dll.openmc_next_batch() + + # Provide opportunity for user to perform action between batches + yield + + # End the iteration + if retval < 0: + break + + def keff(): """Return the calculated k-eigenvalue and its standard deviation. @@ -115,6 +148,10 @@ def keff(): _dll.openmc_get_keff(k) return tuple(k) +def next_batch(): + """Run next batch.""" + return _dll.openmc_next_batch() + def plot_geometry(): """Plot geometry""" @@ -131,6 +168,40 @@ def run(): _dll.openmc_run() +def simulation_init(): + """Initialize simulation""" + _dll.openmc_simulation_init() + + +def simulation_finalize(): + """Finalize simulation""" + _dll.openmc_simulation_finalize() + + +def source_bank(): + """Return source bank as NumPy array + + Returns + ------- + numpy.ndarray + Source sites + + """ + # Get pointer to source bank + ptr = POINTER(_Bank)() + n = c_int64() + _dll.openmc_source_bank(ptr, n) + + # Convert to numpy array with appropriate datatype + bank_dtype = np.dtype(_Bank) + return as_array(ptr, (n.value,)).view(bank_dtype) + + +def statepoint_write(): + """Write a statepoint.""" + _dll.openmc_statepoint_write() + + @contextmanager def run_in_memory(intracomm=None): """Provides context manager for calling OpenMC shared library functions. diff --git a/src/bank_header.F90 b/src/bank_header.F90 index 961824a40..10dea5b8e 100644 --- a/src/bank_header.F90 +++ b/src/bank_header.F90 @@ -2,6 +2,8 @@ module bank_header use, intrinsic :: ISO_C_BINDING + use error, only: E_ALLOCATE, set_errmsg + implicit none !=============================================================================== @@ -48,4 +50,24 @@ contains end subroutine free_memory_bank +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_source_bank(ptr, n) result(err) bind(C) + ! Return a pointer to the source bank + type(C_PTR), intent(out) :: ptr + integer(C_INT64_T), intent(out) :: n + integer(C_INT) :: err + + if (.not. allocated(source_bank)) then + err = E_ALLOCATE + call set_errmsg("Source bank has not been allocated.") + else + err = 0 + ptr = C_LOC(source_bank) + n = size(source_bank) + end if + end function openmc_source_bank + end module bank_header diff --git a/src/simulation.F90 b/src/simulation.F90 index e45e43126..94a285688 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -29,7 +29,7 @@ module simulation use settings use simulation_header use source, only: initialize_source, sample_external_source - use state_point, only: write_state_point, write_source_point, load_state_point + use state_point, only: openmc_statepoint_write, write_source_point, load_state_point use string, only: to_str use tally, only: accumulate_tallies, setup_active_tallies, & init_tally_routines @@ -349,7 +349,7 @@ contains ! Write out state point if it's been specified for this batch if (statepoint_batch % contains(current_batch)) then - call write_state_point() + call openmc_statepoint_write() end if ! Write out source point if it's been specified for this batch diff --git a/src/state_point.F90 b/src/state_point.F90 index 5dde708aa..265cc49c0 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -40,10 +40,10 @@ module state_point contains !=============================================================================== -! WRITE_STATE_POINT +! OPENMC_STATEPOINT_WRITE writes an HDF5 statepoint file to disk !=============================================================================== - subroutine write_state_point() + subroutine openmc_statepoint_write() bind(C) integer :: i, j, k integer :: i_xs @@ -433,7 +433,7 @@ contains call file_close(file_id) end if - end subroutine write_state_point + end subroutine openmc_statepoint_write !=============================================================================== ! WRITE_SOURCE_POINT diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 5aa16c2d0..2812eef6e 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -22,6 +22,7 @@ module tally_header public :: free_memory_tally public :: openmc_extend_tallies public :: openmc_get_tally_index + public :: openmc_global_tallies public :: openmc_tally_get_id public :: openmc_tally_get_filters public :: openmc_tally_get_nuclides @@ -476,6 +477,7 @@ contains if (.not. allocated(global_tallies)) then err = E_ALLOCATE + call set_errmsg("Global tallies have not been allocated yet.") else err = 0 ptr = C_LOC(global_tallies) From 7d00c0b5eb41cdd8ef7d7b665539a2459cb7a4b9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 8 Nov 2017 23:30:02 -0600 Subject: [PATCH 012/282] Account for possibility of running beyond n_batches --- src/simulation.F90 | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/simulation.F90 b/src/simulation.F90 index 94a285688..ad2841f54 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -57,13 +57,8 @@ contains subroutine openmc_run() bind(C) call openmc_simulation_init() - - do - if (openmc_next_batch() < 0) exit + do while (openmc_next_batch() == 0) end do - - call time_active % stop() - call openmc_simulation_finalize() end subroutine openmc_run @@ -459,12 +454,15 @@ contains real(8) :: tempr(3) ! temporary array for communication #endif + ! Stop active batch timer + call time_active % stop() + !$omp parallel deallocate(micro_xs, filter_matches) !$omp end parallel ! Increment total number of generations - total_gen = total_gen + n_batches*gen_per_batch + total_gen = total_gen + current_batch*gen_per_batch ! Start finalization timer call time_finalize % start() From be28e1322031e04969f26f363d021ba4411de9d8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 9 Nov 2017 11:17:42 -0600 Subject: [PATCH 013/282] Allow k_generation and entropy to grow beyond n_max_batches --- src/eigenvalue.F90 | 28 ++++++++++++++-------------- src/input_xml.F90 | 7 +++---- src/output.F90 | 8 ++++---- src/simulation.F90 | 1 + src/simulation_header.F90 | 12 ++++++++---- src/state_point.F90 | 15 ++++++++++----- 6 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/eigenvalue.F90 b/src/eigenvalue.F90 index 5f99d1580..27ae4063e 100644 --- a/src/eigenvalue.F90 +++ b/src/eigenvalue.F90 @@ -301,8 +301,8 @@ contains subroutine shannon_entropy() - integer :: ent_idx ! entropy index integer :: i ! index for mesh elements + real(8) :: entropy_gen ! entropy at this generation logical :: sites_outside ! were there sites outside entropy box? associate (m => meshes(index_entropy_mesh)) @@ -320,14 +320,16 @@ contains ! Normalize to total weight of bank sites entropy_p = entropy_p / sum(entropy_p) - ent_idx = current_gen + gen_per_batch*(current_batch - 1) - entropy(ent_idx) = ZERO + entropy_gen = ZERO do i = 1, size(entropy_p, 2) if (entropy_p(1,i) > ZERO) then - entropy(ent_idx) = entropy(ent_idx) - & + entropy_gen = entropy_gen - & entropy_p(1,i) * log(entropy_p(1,i))/log(TWO) end if end do + + ! Add value to vector + call entropy % push_back(entropy_gen) end if end associate end subroutine shannon_entropy @@ -340,7 +342,7 @@ contains subroutine calculate_generation_keff() - integer :: i ! overall generation + real(8) :: keff_reduced #ifdef MPI integer :: mpi_err ! MPI error code #endif @@ -348,20 +350,18 @@ contains ! Get keff for this generation by subtracting off the starting value keff_generation = global_tallies(RESULT_VALUE, K_TRACKLENGTH) - keff_generation - ! Determine overall generation - i = overall_generation() - #ifdef MPI ! Combine values across all processors - call MPI_ALLREDUCE(keff_generation, k_generation(i), 1, MPI_REAL8, & + call MPI_ALLREDUCE(keff_generation, keff_reduced, 1, MPI_REAL8, & MPI_SUM, mpi_intracomm, mpi_err) #else - k_generation(i) = keff_generation + keff_reduced = keff_generation #endif ! Normalize single batch estimate of k ! TODO: This should be normalized by total_weight, not by n_particles - k_generation(i) = k_generation(i) / n_particles + keff_reduced = keff_reduced / n_particles + call k_generation % push_back(keff_reduced) end subroutine calculate_generation_keff @@ -385,12 +385,12 @@ contains if (n <= 0) then ! For inactive generations, use current generation k as estimate for next ! generation - keff = k_generation(i) + keff = k_generation % data(i) else ! Sample mean of keff - k_sum(1) = k_sum(1) + k_generation(i) - k_sum(2) = k_sum(2) + k_generation(i)**2 + k_sum(1) = k_sum(1) + k_generation % data(i) + k_sum(2) = k_sum(2) + k_generation % data(i)**2 ! Determine mean keff = k_sum(1) / n diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 71ebe3ae6..69b519ea5 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -861,10 +861,9 @@ contains call get_node_value(node_base, "generations_per_batch", gen_per_batch) end if - ! Allocate array for batch keff and entropy - allocate(k_generation(n_max_batches*gen_per_batch)) - allocate(entropy(n_max_batches*gen_per_batch)) - entropy = ZERO + ! Preallocate space for keff and entropy by generation + call k_generation % reserve(n_max_batches*gen_per_batch) + call entropy % initialize(n_max_batches*gen_per_batch) ! Get the trigger information for keff if (check_for_node(node_base, "keff_trigger")) then diff --git a/src/output.F90 b/src/output.F90 index f820ce4b9..3d02489f7 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -384,11 +384,11 @@ contains ! write out information about batch and generation write(UNIT=OUTPUT_UNIT, FMT='(2X,A9)', ADVANCE='NO') & trim(to_str(current_batch)) // "/" // trim(to_str(current_gen)) - write(UNIT=OUTPUT_UNIT, FMT='(3X,F8.5)', ADVANCE='NO') k_generation(i) + write(UNIT=OUTPUT_UNIT, FMT='(3X,F8.5)', ADVANCE='NO') k_generation % data(i) ! write out entropy info if (entropy_on) write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & - entropy(i) + entropy % data(i) if (n > 1) then write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5," +/-",F8.5)', ADVANCE='NO') & @@ -418,11 +418,11 @@ contains write(UNIT=OUTPUT_UNIT, FMT='(2X,A9)', ADVANCE='NO') & trim(to_str(current_batch)) // "/" // trim(to_str(gen_per_batch)) write(UNIT=OUTPUT_UNIT, FMT='(3X,F8.5)', ADVANCE='NO') & - k_generation(i) + k_generation % data(i) ! write out entropy info if (entropy_on) write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & - entropy(i) + entropy % data(i) ! write out accumulated k-effective if after first active batch if (n > 1) then diff --git a/src/simulation.F90 b/src/simulation.F90 index ad2841f54..e3eba0d51 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -78,6 +78,7 @@ contains ! Handle restart runs if (restart_run .and. current_batch <= restart_batch) then call replay_batch_history() + retval = 0 return end if diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index cfb75b449..9719cc72e 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -3,6 +3,7 @@ module simulation_header use bank_header use constants use settings, only: gen_per_batch + use stl_vector, only: VectorReal implicit none @@ -31,7 +32,7 @@ module simulation_header integer(8) :: current_work ! index in source bank of current history simulated ! Temporary k-effective values - real(8), allocatable :: k_generation(:) ! single-generation estimates of k + type(VectorReal) :: k_generation ! single-generation estimates of k real(8) :: keff = ONE ! average k over active batches real(8) :: keff_std ! standard deviation of average k real(8) :: k_col_abs = ZERO ! sum over batches of k_collision * k_absorption @@ -39,7 +40,7 @@ module simulation_header real(8) :: k_abs_tra = ZERO ! sum over batches of k_absorption * k_tracklength ! Shannon entropy - real(8), allocatable :: entropy(:) ! shannon entropy at each generation + type(VectorReal) :: entropy ! shannon entropy at each generation real(8), allocatable :: entropy_p(:,:) ! % of source sites in each cell ! Uniform fission source weighting @@ -85,11 +86,14 @@ contains subroutine free_memory_simulation() if (allocated(overlap_check_cnt)) deallocate(overlap_check_cnt) - if (allocated(k_generation)) deallocate(k_generation) - if (allocated(entropy)) deallocate(entropy) if (allocated(entropy_p)) deallocate(entropy_p) if (allocated(source_frac)) deallocate(source_frac) if (allocated(work_index)) deallocate(work_index) + + call k_generation % clear() + call k_generation % shrink_to_fit() + call entropy % clear() + call entropy % shrink_to_fit() end subroutine free_memory_simulation end module simulation_header diff --git a/src/state_point.F90 b/src/state_point.F90 index 265cc49c0..e93d6e472 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -121,8 +121,11 @@ contains if (run_mode == MODE_EIGENVALUE) then call write_dataset(file_id, "n_inactive", n_inactive) call write_dataset(file_id, "generations_per_batch", gen_per_batch) - call write_dataset(file_id, "k_generation", k_generation) - call write_dataset(file_id, "entropy", entropy) + k = k_generation % size() + call write_dataset(file_id, "k_generation", k_generation % data(1:k)) + if (entropy_on) then + call write_dataset(file_id, "entropy", entropy % data(1:k)) + end if call write_dataset(file_id, "k_col_abs", k_col_abs) call write_dataset(file_id, "k_col_tra", k_col_tra) call write_dataset(file_id, "k_abs_tra", k_abs_tra) @@ -707,10 +710,12 @@ contains if (run_mode == MODE_EIGENVALUE) then call read_dataset(int_array(1), file_id, "n_inactive") call read_dataset(gen_per_batch, file_id, "generations_per_batch") - call read_dataset(k_generation(1:restart_batch*gen_per_batch), & + call read_dataset(k_generation % data(1:restart_batch*gen_per_batch), & file_id, "k_generation") - call read_dataset(entropy(1:restart_batch*gen_per_batch), & - file_id, "entropy") + if (entropy_on) then + call read_dataset(entropy % data(1:restart_batch*gen_per_batch), & + file_id, "entropy") + end if call read_dataset(k_col_abs, file_id, "k_col_abs") call read_dataset(k_col_tra, file_id, "k_col_tra") call read_dataset(k_abs_tra, file_id, "k_abs_tra") From 671db024db057c17350666429890a805515effdc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 9 Nov 2017 14:01:26 -0600 Subject: [PATCH 014/282] Fix bugs with statepoint loading. Add properties to capi.Tally. --- docs/source/conf.py | 5 +- docs/source/pythonapi/capi.rst | 6 ++ openmc/capi/core.py | 22 ++++++- openmc/capi/tally.py | 117 +++++++++++++++++++++++++-------- src/input_xml.F90 | 2 +- src/state_point.F90 | 12 ++-- src/tallies/tally_header.F90 | 16 +++++ 7 files changed, 143 insertions(+), 37 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 95930fedb..5f993b1a3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,8 +26,9 @@ except ImportError: MOCK_MODULES = ['numpy', 'numpy.polynomial', 'numpy.polynomial.polynomial', 'numpy.ctypeslib', 'scipy', 'scipy.sparse', 'scipy.interpolate', - 'scipy.integrate', 'scipy.optimize', 'scipy.special', 'h5py', - 'pandas', 'uncertainties', 'openmoc', 'openmc.data.reconstruct'] + 'scipy.integrate', 'scipy.optimize', 'scipy.special', + 'scipy.stats','h5py', 'pandas', 'uncertainties', 'openmoc', + 'openmc.data.reconstruct'] sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES) import numpy as np diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index 457f05320..44a755bbd 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -18,12 +18,18 @@ Functions openmc.capi.find_material openmc.capi.hard_reset openmc.capi.init + openmc.capi.iter_batches openmc.capi.keff openmc.capi.load_nuclide + openmc.capi.next_batch openmc.capi.plot_geometry openmc.capi.reset openmc.capi.run openmc.capi.run_in_memory + openmc.capi.simulation_init + openmc.capi.simulation_finalize + openmc.capi.source_bank + openmc.capi.statepoint_write Classes ------- diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 530ed5063..77c9ca7a4 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -122,7 +122,27 @@ def init(intracomm=None): def iter_batches(): - """Iterator over batches.""" + """Iterator over batches. + + This function returns a generator-iterator that allows Python code to be run + between batches in an OpenMC simulation. It should be used in conjunction + with :func:`openmc.capi.simulation_init` and + :func:`openmc.capi.simulation_finalize`. For example: + + .. code-block:: Python + + with openmc.capi.run_in_memory(): + openmc.capi.simulation_init() + for _ in openmc.capi.iter_batches(): + # Look at convergence of tallies, for example + ... + openmc.capi.simulation_finalize() + + See Also + -------- + openmc.capi.next_batch + + """ while True: # Run next batch retval = _dll.openmc_next_batch() diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index 14f9d1c1c..6725ee5c9 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -4,6 +4,7 @@ from weakref import WeakValueDictionary import numpy as np from numpy.ctypeslib import as_array +import scipy.stats from openmc.data.reaction import REACTION_NAME from . import _dll, Nuclide @@ -31,6 +32,9 @@ _dll.openmc_tally_get_filters.argtypes = [ c_int32, POINTER(POINTER(c_int32)), POINTER(c_int)] _dll.openmc_tally_get_filters.restype = c_int _dll.openmc_tally_get_filters.errcheck = _error_handler +_dll.openmc_tally_get_n_realizations.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_tally_get_n_realizations.restype = c_int +_dll.openmc_tally_get_n_realizations.errcheck = _error_handler _dll.openmc_tally_get_nuclides.argtypes = [ c_int32, POINTER(POINTER(c_int)), POINTER(c_int)] _dll.openmc_tally_get_nuclides.restype = c_int @@ -70,6 +74,14 @@ _SCORES = { def global_tallies(): + """Mean and standard deviation of the mean for each global tally. + + Returns + ------- + list of tuple + For each global tally, a tuple of (mean, standard deviation) + + """ ptr = POINTER(c_double)() _dll.openmc_global_tallies(ptr) array = as_array(ptr, (4, 3)) @@ -113,10 +125,16 @@ class Tally(_FortranObjectWithID): ID of the tally filters : list List of tally filters + mean : numpy.ndarray + An array containing the sample mean for each bin nuclides : list of str List of nuclides to score results for + num_realizations : int + Number of realizations results : numpy.ndarray Array of tally results + std_dev : numpy.ndarray + An array containing the sample standard deviation for each bin """ __instances = WeakValueDictionary() @@ -169,21 +187,6 @@ class Tally(_FortranObjectWithID): _dll.openmc_tally_get_filters(self._index, filt_idx, n) return [_get_filter(filt_idx[i]) for i in range(n.value)] - @property - def nuclides(self): - nucs = POINTER(c_int)() - n = c_int() - _dll.openmc_tally_get_nuclides(self._index, nucs, n) - return [Nuclide(nucs[i]).name if nucs[i] > 0 else 'total' - for i in range(n.value)] - - @property - def results(self): - data = POINTER(c_double)() - shape = (c_int*3)() - _dll.openmc_tally_results(self._index, data, shape) - return as_array(data, tuple(shape[::-1])) - @filters.setter def filters(self, filters): # Get filter indices as int32_t[] @@ -192,12 +195,42 @@ class Tally(_FortranObjectWithID): _dll.openmc_tally_set_filters(self._index, n, indices) + @property + def mean(self): + n = self.num_realizations + sum_ = self.results[:, :, 1] + if n > 0: + return sum_ / n + else: + return sum_.copy() + + @property + def nuclides(self): + nucs = POINTER(c_int)() + n = c_int() + _dll.openmc_tally_get_nuclides(self._index, nucs, n) + return [Nuclide(nucs[i]).name if nucs[i] > 0 else 'total' + for i in range(n.value)] + @nuclides.setter def nuclides(self, nuclides): nucs = (c_char_p * len(nuclides))() nucs[:] = [x.encode() for x in nuclides] _dll.openmc_tally_set_nuclides(self._index, len(nuclides), nucs) + @property + def num_realizations(self): + n = c_int32() + _dll.openmc_tally_get_n_realizations(self._index, n) + return n.value + + @property + def results(self): + data = POINTER(c_double)() + shape = (c_int*3)() + _dll.openmc_tally_results(self._index, data, shape) + return as_array(data, tuple(shape[::-1])) + @property def scores(self): scores_as_int = POINTER(c_int)() @@ -223,21 +256,47 @@ class Tally(_FortranObjectWithID): scores_[:] = [x.encode() for x in scores] _dll.openmc_tally_set_scores(self._index, len(scores), scores_) - @classmethod - def new(cls, tally_id=None): - # Determine ID to assign - if tally_id is None: - try: - tally_id = max(tallies) + 1 - except ValueError: - tally_id = 1 + @property + def std_dev(self): + results = self.results + std_dev = np.empty(results.shape[:2]) + std_dev.fill(np.nan) - index = c_int32() - _dll.openmc_extend_tallies(1, index, None) - _dll.openmc_tally_set_type(index, b'generic') - tally = cls(index.value) - tally.id = tally_id - return tally + n = self.num_realizations + if n > 1: + # Get sum and sum-of-squares from results + sum_ = results[:, :, 1] + sum_sq = results[:, :, 2] + + # Determine non-zero entries + mean = sum_ / n + nonzero = np.abs(mean) > 0 + + # Calculate sample standard deviation of the mean + std_dev[nonzero] = np.sqrt( + (sum_sq[nonzero]/n - mean[nonzero]**2)/(n - 1)) + + return std_dev + + def ci_width(self, alpha=0.05): + """Confidence interval half-width based on a Student t distribution + + Parameters + ---------- + alpha : float + Significance level (one minus the confidence level!) + + Returns + ------- + float + Half-width of a two-sided (1 - :math:`alpha`) confidence interval + + """ + half_width = self.std_dev.copy() + n = self.num_realizations + if n > 1: + half_width *= scipy.stats.t.ppf(1 - alpha/2, n - 1) + return half_width class _TallyMapping(Mapping): diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 69b519ea5..766dbfa9f 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -863,7 +863,7 @@ contains ! Preallocate space for keff and entropy by generation call k_generation % reserve(n_max_batches*gen_per_batch) - call entropy % initialize(n_max_batches*gen_per_batch) + call entropy % reserve(n_max_batches*gen_per_batch) ! Get the trigger information for keff if (check_for_node(node_base, "keff_trigger")) then diff --git a/src/state_point.F90 b/src/state_point.F90 index e93d6e472..0383150ac 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -632,6 +632,7 @@ contains subroutine load_state_point() integer :: i + integer :: n integer :: int_array(3) integer, allocatable :: array(:) integer(HID_T) :: file_id @@ -710,11 +711,14 @@ contains if (run_mode == MODE_EIGENVALUE) then call read_dataset(int_array(1), file_id, "n_inactive") call read_dataset(gen_per_batch, file_id, "generations_per_batch") - call read_dataset(k_generation % data(1:restart_batch*gen_per_batch), & - file_id, "k_generation") + + n = restart_batch*gen_per_batch + call k_generation % resize(n) + call read_dataset(k_generation % data(1:n), file_id, "k_generation") + if (entropy_on) then - call read_dataset(entropy % data(1:restart_batch*gen_per_batch), & - file_id, "entropy") + call entropy % resize(n) + call read_dataset(entropy % data(1:n), file_id, "entropy") end if call read_dataset(k_col_abs, file_id, "k_col_abs") call read_dataset(k_col_tra, file_id, "k_col_tra") diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 2812eef6e..1778474e0 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -526,6 +526,22 @@ contains end function openmc_tally_get_filters + function openmc_tally_get_n_realizations(index, n) result(err) bind(C) + ! Return the number of realizations for a tally + integer(C_INT32_T), value :: index + integer(C_INT32_T), intent(out) :: n + integer(C_INT) :: err + + if (index >= 1 .and. index <= size(tallies)) then + n = tallies(index) % obj % n_realizations + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg('Index in tallies array is out of bounds.') + end if + end function openmc_tally_get_n_realizations + + function openmc_tally_get_nuclides(index, nuclides, n) result(err) bind(C) ! Return the list of nuclides assigned to a tally integer(C_INT32_T), value :: index From 198df07311cc5747e8eb5915c8dcb209b5fdeb63 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 9 Nov 2017 15:53:57 -0600 Subject: [PATCH 015/282] Add subdivide convenience function --- docs/source/pythonapi/index.rst | 8 ++++---- docs/source/pythonapi/model.rst | 10 ++++++++++ openmc/capi/core.py | 2 +- openmc/model/__init__.py | 1 + openmc/model/funcs.py | 25 +++++++++++++++++++++++++ 5 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 openmc/model/funcs.py diff --git a/docs/source/pythonapi/index.rst b/docs/source/pythonapi/index.rst index bb99dd470..862acb238 100644 --- a/docs/source/pythonapi/index.rst +++ b/docs/source/pythonapi/index.rst @@ -40,13 +40,13 @@ Modules ------- .. toctree:: - :maxdepth: 2 + :maxdepth: 1 base - stats - mgxs model + examples + mgxs + stats data capi - examples openmoc diff --git a/docs/source/pythonapi/model.rst b/docs/source/pythonapi/model.rst index 6e589b7eb..55a5d17b1 100644 --- a/docs/source/pythonapi/model.rst +++ b/docs/source/pythonapi/model.rst @@ -2,6 +2,16 @@ :mod:`openmc.model` -- Model Building ------------------------------------- +Convenience Functions +--------------------- + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myfunction.rst + + openmc.model.subdivide + TRISO Fuel Modeling ------------------- diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 77c9ca7a4..1f7225f64 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -17,7 +17,6 @@ class _Bank(Structure): ('delayed_group', c_int)] - _dll.openmc_calculate_volumes.restype = None _dll.openmc_finalize.restype = None _dll.openmc_find.argtypes = [POINTER(c_double*3), c_int, POINTER(c_int32), @@ -168,6 +167,7 @@ def keff(): _dll.openmc_get_keff(k) return tuple(k) + def next_batch(): """Run next batch.""" return _dll.openmc_next_batch() diff --git a/openmc/model/__init__.py b/openmc/model/__init__.py index 557effcef..9fa999dd4 100644 --- a/openmc/model/__init__.py +++ b/openmc/model/__init__.py @@ -1,2 +1,3 @@ from .triso import * from .model import * +from .funcs import * diff --git a/openmc/model/funcs.py b/openmc/model/funcs.py new file mode 100644 index 000000000..d9755b0e8 --- /dev/null +++ b/openmc/model/funcs.py @@ -0,0 +1,25 @@ +def subdivide(surfaces): + """Create regions separated by a series of surfaces. + + This function allows regions to be constructed from a set of a surfaces that + are "in order". For example, if you had four instances of + :class:`openmc.ZPlane` at z=-10, z=-5, z=5, and z=10, this function would + return a list of regions corresponding to z < -10, -10 < z < -5, -5 < z < 5, + 5 < z < 10, and 10 < z. That is, for n surfaces, n+1 regions are returned. + + Parameters + ---------- + surfaces : sequence of openmc.Surface + Surfaces separating regions + + Returns + ------- + list of openmc.Region + Regions formed by the given surfaces + + """ + regions = [-surfaces[0]] + for s0, s1 in zip(surfaces[:-1], surfaces[1:]): + regions.append(+s0 & -s1) + regions.append(+surfaces[-1]) + return regions From 6e9b8a5ce52ae15d147bc503c908e947963ae0bf Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 9 Nov 2017 16:06:58 -0600 Subject: [PATCH 016/282] Move get_*_prism functions to openmc.model --- docs/source/pythonapi/base.rst | 12 -- docs/source/pythonapi/model.rst | 7 + openmc/__init__.py | 3 + openmc/model/funcs.py | 254 ++++++++++++++++++++++++++++++++ openmc/surface.py | 253 +------------------------------ 5 files changed, 266 insertions(+), 263 deletions(-) diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index e6cf1e1cc..f1633f3f9 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -91,18 +91,6 @@ Many of the above classes are derived from several abstract classes: openmc.Region openmc.Lattice -Two helper function are also available to create rectangular and hexagonal -prisms defined by the intersection of four and six surface half-spaces, -respectively. - -.. autosummary:: - :toctree: generated - :nosignatures: - :template: myfunction.rst - - openmc.get_hexagonal_prism - openmc.get_rectangular_prism - .. _pythonapi_tallies: Constructing Tallies diff --git a/docs/source/pythonapi/model.rst b/docs/source/pythonapi/model.rst index 55a5d17b1..4ba247468 100644 --- a/docs/source/pythonapi/model.rst +++ b/docs/source/pythonapi/model.rst @@ -5,11 +5,18 @@ Convenience Functions --------------------- +Several helper functions are available here. Ther first two create rectangular +and hexagonal prisms defined by the intersection of four and six surface +half-spaces, respectively. The last function takes a sequence of surfaces and +returns the regions that separate them. + .. autosummary:: :toctree: generated :nosignatures: :template: myfunction.rst + openmc.model.get_hexagonal_prism + openmc.model.get_rectangular_prism openmc.model.subdivide TRISO Fuel Modeling diff --git a/openmc/__init__.py b/openmc/__init__.py index d692ebbae..8fb9bcf37 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -28,4 +28,7 @@ from openmc.mixin import * from openmc.plotter import * from openmc.search import * +# Import a few convencience functions that used to be here +from openmc.model import get_rectangular_prism, get_hexagonal_prism + __version__ = '0.9.0' diff --git a/openmc/model/funcs.py b/openmc/model/funcs.py index d9755b0e8..589765d2c 100644 --- a/openmc/model/funcs.py +++ b/openmc/model/funcs.py @@ -1,3 +1,257 @@ +from __future__ import division +from collections import Iterable, OrderedDict +from math import sqrt +from numbers import Real + +from openmc import XPlane, YPlane, Plane, ZCylinder +from openmc.checkvalue import check_type, check_value + + +def get_rectangular_prism(width, height, axis='z', origin=(0., 0.), + boundary_type='transmission', corner_radius=0.): + """Get an infinite rectangular prism from four planar surfaces. + + Parameters + ---------- + width: float + Prism width in units of cm. The width is aligned with the y, x, + or x axes for prisms parallel to the x, y, or z axis, respectively. + height: float + Prism height in units of cm. The height is aligned with the z, z, + or y axes for prisms parallel to the x, y, or z axis, respectively. + axis : {'x', 'y', 'z'} + Axis with which the infinite length of the prism should be aligned. + Defaults to 'z'. + origin: Iterable of two floats + Origin of the prism. The two floats correspond to (y,z), (x,z) or + (x,y) for prisms parallel to the x, y or z axis, respectively. + Defaults to (0., 0.). + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + Boundary condition that defines the behavior for particles hitting the + surfaces comprising the rectangular prism (default is 'transmission'). + corner_radius: float + Prism corner radius in units of cm. Defaults to 0. + + Returns + ------- + openmc.Region + The inside of a rectangular prism + + """ + + check_type('width', width, Real) + check_type('height', height, Real) + check_type('corner_radius', corner_radius, Real) + check_value('axis', axis, ['x', 'y', 'z']) + check_type('origin', origin, Iterable, Real) + + # Define function to create a plane on given axis + def plane(axis, name, value): + cls = globals()['{}Plane'.format(axis.upper())] + return cls(name='{} {}'.format(name, axis), + boundary_type=boundary_type, + **{axis + '0': value}) + + if axis == 'x': + x1, x2 = 'y', 'z' + elif axis == 'y': + x1, x2 = 'x', 'z' + else: + x1, x2 = 'x', 'y' + + # Get cylinder class corresponding to given axis + cyl = globals()['{}Cylinder'.format(axis.upper())] + + # Create rectangular region + min_x1 = plane(x1, 'minimum', -width/2 + origin[0]) + max_x1 = plane(x1, 'maximum', width/2 + origin[0]) + min_x2 = plane(x2, 'minimum', -height/2 + origin[1]) + max_x2 = plane(x2, 'maximum', height/2 + origin[1]) + prism = +min_x1 & -max_x1 & +min_x2 & -max_x2 + + # Handle rounded corners if given + if corner_radius > 0.: + args = {'R': corner_radius, 'boundary_type': boundary_type} + + args[x1 + '0'] = origin[0] - width/2 + corner_radius + args[x2 + '0'] = origin[1] - height/2 + corner_radius + x1_min_x2_min = cyl(name='{} min {} min'.format(x1, x2), **args) + + args[x1 + '0'] = origin[0] - width/2 + corner_radius + args[x2 + '0'] = origin[1] - height/2 + corner_radius + x1_min_x2_min = cyl(name='{} min {} min'.format(x1, x2), **args) + + args[x1 + '0'] = origin[0] - width/2 + corner_radius + args[x2 + '0'] = origin[1] + height/2 - corner_radius + x1_min_x2_max = cyl(name='{} min {} max'.format(x1, x2), **args) + + args[x1 + '0'] = origin[0] + width/2 - corner_radius + args[x2 + '0'] = origin[1] - height/2 + corner_radius + x1_max_x2_min = cyl(name='{} max {} min'.format(x1, x2), **args) + + args[x1 + '0'] = origin[0] + width/2 - corner_radius + args[x2 + '0'] = origin[1] + height/2 - corner_radius + x1_max_x2_max = cyl(name='{} max {} max'.format(x1, x2), **args) + + x1_min = plane(x1, 'min', -width/2 + origin[0] + corner_radius) + x1_max = plane(x1, 'max', width/2 + origin[0] - corner_radius) + x2_min = plane(x2, 'min', -height/2 + origin[1] + corner_radius) + x2_max = plane(x2, 'max', height/2 + origin[1] - corner_radius) + + corners = (+x1_min_x2_min & -x1_min & -x2_min) | \ + (+x1_min_x2_max & -x1_min & +x2_max) | \ + (+x1_max_x2_min & +x1_max & -x2_min) | \ + (+x1_max_x2_max & +x1_max & +x2_max) + + prism = prism & ~corners + + return prism + + +def get_hexagonal_prism(edge_length=1., orientation='y', origin=(0., 0.), + boundary_type='transmission', corner_radius=0.): + """Create a hexagon region from six surface planes. + + Parameters + ---------- + edge_length : float + Length of a side of the hexagon in cm + orientation : {'x', 'y'} + An 'x' orientation means that two sides of the hexagon are parallel to + the x-axis and a 'y' orientation means that two sides of the hexagon are + parallel to the y-axis. + origin: Iterable of two floats + Origin of the prism. Defaults to (0., 0.). + boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} + Boundary condition that defines the behavior for particles hitting the + surfaces comprising the hexagonal prism (default is 'transmission'). + corner_radius: float + Prism corner radius in units of cm. Defaults to 0. + + Returns + ------- + openmc.Region + The inside of a hexagonal prism + + """ + + l = edge_length + x, y = origin + + if orientation == 'y': + right = XPlane(x0=x + sqrt(3.)/2*l, boundary_type=boundary_type) + left = XPlane(x0=x - sqrt(3.)/2*l, boundary_type=boundary_type) + c = sqrt(3.)/3. + + # y = -x/sqrt(3) + a + upper_right = Plane(A=c, B=1., D=l+x*c+y, boundary_type=boundary_type) + + # y = x/sqrt(3) + a + upper_left = Plane(A=-c, B=1., D=l-x*c+y, boundary_type=boundary_type) + + # y = x/sqrt(3) - a + lower_right = Plane(A=-c, B=1., D=-l-x*c+y, boundary_type=boundary_type) + + # y = -x/sqrt(3) - a + lower_left = Plane(A=c, B=1., D=-l+x*c+y, boundary_type=boundary_type) + + prism = -right & +left & -upper_right & -upper_left & \ + +lower_right & +lower_left + + if boundary_type == 'periodic': + right.periodic_surface = left + upper_right.periodic_surface = lower_left + lower_right.periodic_surface = upper_left + + elif orientation == 'x': + top = YPlane(y0=y + sqrt(3.)/2*l, boundary_type=boundary_type) + bottom = YPlane(y0=y - sqrt(3.)/2*l, boundary_type=boundary_type) + c = sqrt(3.) + + # y = -sqrt(3)*(x - a) + upper_right = Plane(A=c, B=1., D=c*l+x*c+y, boundary_type=boundary_type) + + # y = sqrt(3)*(x + a) + lower_right = Plane(A=-c, B=1., D=-c*l-x*c+y, + boundary_type=boundary_type) + + # y = -sqrt(3)*(x + a) + lower_left = Plane(A=c, B=1., D=-c*l+x*c+y, boundary_type=boundary_type) + + # y = sqrt(3)*(x + a) + upper_left = Plane(A=-c, B=1., D=c*l-x*c+y, boundary_type=boundary_type) + + prism = -top & +bottom & -upper_right & +lower_right & \ + +lower_left & -upper_left + + if boundary_type == 'periodic': + top.periodic_surface = bottom + upper_right.periodic_surface = lower_left + lower_right.periodic_surface = upper_left + + # Handle rounded corners if given + if corner_radius > 0.: + if boundary_type == 'periodic': + raise ValueError('Periodic boundary conditions not permitted when ' + 'rounded corners are used.') + + c = sqrt(3.)/2 + t = l - corner_radius/c + + # Cylinder with corner radius and boundary type pre-applied + cyl1 = partial(ZCylinder, R=corner_radius, boundary_type=boundary_type) + cyl2 = partial(ZCylinder, R=corner_radius/(2*c), + boundary_type=boundary_type) + + if orientation == 'x': + x_min_y_min_in = cyl1(name='x min y min in', x0=x-t/2, y0=y-c*t) + x_min_y_max_in = cyl1(name='x min y max in', x0=x+t/2, y0=y-c*t) + x_max_y_min_in = cyl1(name='x max y min in', x0=x-t/2, y0=y+c*t) + x_max_y_max_in = cyl1(name='x max y max in', x0=x+t/2, y0=y+c*t) + x_min_in = cyl1(name='x min in', x0=x-t, y0=y) + x_max_in = cyl1(name='x max in', x0=x+t, y0=y) + + x_min_y_min_out = cyl2(name='x min y min out', x0=x-l/2, y0=y-c*l) + x_min_y_max_out = cyl2(name='x min y max out', x0=x+l/2, y0=y-c*l) + x_max_y_min_out = cyl2(name='x max y min out', x0=x-l/2, y0=y+c*l) + x_max_y_max_out = cyl2(name='x max y max out', x0=x+l/2, y0=y+c*l) + x_min_out = cyl2(name='x min out', x0=x-l, y0=y) + x_max_out = cyl2(name='x max out', x0=x+l, y0=y) + + corners = (+x_min_y_min_in & -x_min_y_min_out | + +x_min_y_max_in & -x_min_y_max_out | + +x_max_y_min_in & -x_max_y_min_out | + +x_max_y_max_in & -x_max_y_max_out | + +x_min_in & -x_min_out | + +x_max_in & -x_max_out) + + elif orientation == 'y': + x_min_y_min_in = cyl1(name='x min y min in', x0=x-c*t, y0=y-t/2) + x_min_y_max_in = cyl1(name='x min y max in', x0=x-c*t, y0=y+t/2) + x_max_y_min_in = cyl1(name='x max y min in', x0=x+c*t, y0=y-t/2) + x_max_y_max_in = cyl1(name='x max y max in', x0=x+c*t, y0=y+t/2) + y_min_in = cyl1(name='y min in', x0=x, y0=y-t) + y_max_in = cyl1(name='y max in', x0=x, y0=y+t) + + x_min_y_min_out = cyl2(name='x min y min out', x0=x-c*l, y0=y-l/2) + x_min_y_max_out = cyl2(name='x min y max out', x0=x-c*l, y0=y+l/2) + x_max_y_min_out = cyl2(name='x max y min out', x0=x+c*l, y0=y-l/2) + x_max_y_max_out = cyl2(name='x max y max out', x0=x+c*l, y0=y+l/2) + y_min_out = cyl2(name='y min out', x0=x, y0=y-l) + y_max_out = cyl2(name='y max out', x0=x, y0=y+l) + + corners = (+x_min_y_min_in & -x_min_y_min_out | + +x_min_y_max_in & -x_min_y_max_out | + +x_max_y_min_in & -x_max_y_min_out | + +x_max_y_max_in & -x_max_y_max_out | + +y_min_in & -y_min_out | + +y_max_in & -y_max_out) + + prism = prism & ~corners + + return prism + + def subdivide(surfaces): """Create regions separated by a series of surfaces. diff --git a/openmc/surface.py b/openmc/surface.py index b415ecb93..961ac4d45 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -1,23 +1,19 @@ from __future__ import division from abc import ABCMeta -from collections import Iterable, OrderedDict +from collections import OrderedDict from copy import deepcopy from functools import partial from numbers import Real, Integral from xml.etree import ElementTree as ET -from math import sqrt from six import add_metaclass, string_types import numpy as np -from openmc.checkvalue import check_type, check_value, check_greater_than +from openmc.checkvalue import check_type, check_value from openmc.region import Region, Intersection, Union from openmc.mixin import IDManagerMixin -# A static variable for auto-generated Surface IDs -AUTO_SURFACE_ID = 10000 - _BOUNDARY_TYPES = ['transmission', 'vacuum', 'reflective', 'periodic'] @@ -1944,248 +1940,3 @@ class Halfspace(Region): clone = deepcopy(self) clone.surface = self.surface.clone(memo) return clone - - -def get_rectangular_prism(width, height, axis='z', origin=(0., 0.), - boundary_type='transmission', corner_radius=0.): - """Get an infinite rectangular prism from four planar surfaces. - - Parameters - ---------- - width: float - Prism width in units of cm. The width is aligned with the y, x, - or x axes for prisms parallel to the x, y, or z axis, respectively. - height: float - Prism height in units of cm. The height is aligned with the z, z, - or y axes for prisms parallel to the x, y, or z axis, respectively. - axis : {'x', 'y', 'z'} - Axis with which the infinite length of the prism should be aligned. - Defaults to 'z'. - origin: Iterable of two floats - Origin of the prism. The two floats correspond to (y,z), (x,z) or - (x,y) for prisms parallel to the x, y or z axis, respectively. - Defaults to (0., 0.). - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} - Boundary condition that defines the behavior for particles hitting the - surfaces comprising the rectangular prism (default is 'transmission'). - corner_radius: float - Prism corner radius in units of cm. Defaults to 0. - - Returns - ------- - openmc.Region - The inside of a rectangular prism - - """ - - check_type('width', width, Real) - check_type('height', height, Real) - check_type('corner_radius', corner_radius, Real) - check_value('axis', axis, ['x', 'y', 'z']) - check_type('origin', origin, Iterable, Real) - - # Define function to create a plane on given axis - def plane(axis, name, value): - cls = globals()['{}Plane'.format(axis.upper())] - return cls(name='{} {}'.format(name, axis), - boundary_type=boundary_type, - **{axis + '0': value}) - - if axis == 'x': - x1, x2 = 'y', 'z' - elif axis == 'y': - x1, x2 = 'x', 'z' - else: - x1, x2 = 'x', 'y' - - # Get cylinder class corresponding to given axis - cyl = globals()['{}Cylinder'.format(axis.upper())] - - # Create rectangular region - min_x1 = plane(x1, 'minimum', -width/2 + origin[0]) - max_x1 = plane(x1, 'maximum', width/2 + origin[0]) - min_x2 = plane(x2, 'minimum', -height/2 + origin[1]) - max_x2 = plane(x2, 'maximum', height/2 + origin[1]) - prism = +min_x1 & -max_x1 & +min_x2 & -max_x2 - - # Handle rounded corners if given - if corner_radius > 0.: - args = {'R': corner_radius, 'boundary_type': boundary_type} - - args[x1 + '0'] = origin[0] - width/2 + corner_radius - args[x2 + '0'] = origin[1] - height/2 + corner_radius - x1_min_x2_min = cyl(name='{} min {} min'.format(x1, x2), **args) - - args[x1 + '0'] = origin[0] - width/2 + corner_radius - args[x2 + '0'] = origin[1] - height/2 + corner_radius - x1_min_x2_min = cyl(name='{} min {} min'.format(x1, x2), **args) - - args[x1 + '0'] = origin[0] - width/2 + corner_radius - args[x2 + '0'] = origin[1] + height/2 - corner_radius - x1_min_x2_max = cyl(name='{} min {} max'.format(x1, x2), **args) - - args[x1 + '0'] = origin[0] + width/2 - corner_radius - args[x2 + '0'] = origin[1] - height/2 + corner_radius - x1_max_x2_min = cyl(name='{} max {} min'.format(x1, x2), **args) - - args[x1 + '0'] = origin[0] + width/2 - corner_radius - args[x2 + '0'] = origin[1] + height/2 - corner_radius - x1_max_x2_max = cyl(name='{} max {} max'.format(x1, x2), **args) - - x1_min = plane(x1, 'min', -width/2 + origin[0] + corner_radius) - x1_max = plane(x1, 'max', width/2 + origin[0] - corner_radius) - x2_min = plane(x2, 'min', -height/2 + origin[1] + corner_radius) - x2_max = plane(x2, 'max', height/2 + origin[1] - corner_radius) - - corners = (+x1_min_x2_min & -x1_min & -x2_min) | \ - (+x1_min_x2_max & -x1_min & +x2_max) | \ - (+x1_max_x2_min & +x1_max & -x2_min) | \ - (+x1_max_x2_max & +x1_max & +x2_max) - - prism = prism & ~corners - - return prism - - -def get_hexagonal_prism(edge_length=1., orientation='y', origin=(0., 0.), - boundary_type='transmission', corner_radius=0.): - """Create a hexagon region from six surface planes. - - Parameters - ---------- - edge_length : float - Length of a side of the hexagon in cm - orientation : {'x', 'y'} - An 'x' orientation means that two sides of the hexagon are parallel to - the x-axis and a 'y' orientation means that two sides of the hexagon are - parallel to the y-axis. - origin: Iterable of two floats - Origin of the prism. Defaults to (0., 0.). - boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic'} - Boundary condition that defines the behavior for particles hitting the - surfaces comprising the hexagonal prism (default is 'transmission'). - corner_radius: float - Prism corner radius in units of cm. Defaults to 0. - - Returns - ------- - openmc.Region - The inside of a hexagonal prism - - """ - - l = edge_length - x, y = origin - - if orientation == 'y': - right = XPlane(x0=x + sqrt(3.)/2*l, boundary_type=boundary_type) - left = XPlane(x0=x - sqrt(3.)/2*l, boundary_type=boundary_type) - c = sqrt(3.)/3. - - # y = -x/sqrt(3) + a - upper_right = Plane(A=c, B=1., D=l+x*c+y, boundary_type=boundary_type) - - # y = x/sqrt(3) + a - upper_left = Plane(A=-c, B=1., D=l-x*c+y, boundary_type=boundary_type) - - # y = x/sqrt(3) - a - lower_right = Plane(A=-c, B=1., D=-l-x*c+y, boundary_type=boundary_type) - - # y = -x/sqrt(3) - a - lower_left = Plane(A=c, B=1., D=-l+x*c+y, boundary_type=boundary_type) - - prism = -right & +left & -upper_right & -upper_left & \ - +lower_right & +lower_left - - if boundary_type == 'periodic': - right.periodic_surface = left - upper_right.periodic_surface = lower_left - lower_right.periodic_surface = upper_left - - elif orientation == 'x': - top = YPlane(y0=y + sqrt(3.)/2*l, boundary_type=boundary_type) - bottom = YPlane(y0=y - sqrt(3.)/2*l, boundary_type=boundary_type) - c = sqrt(3.) - - # y = -sqrt(3)*(x - a) - upper_right = Plane(A=c, B=1., D=c*l+x*c+y, boundary_type=boundary_type) - - # y = sqrt(3)*(x + a) - lower_right = Plane(A=-c, B=1., D=-c*l-x*c+y, - boundary_type=boundary_type) - - # y = -sqrt(3)*(x + a) - lower_left = Plane(A=c, B=1., D=-c*l+x*c+y, boundary_type=boundary_type) - - # y = sqrt(3)*(x + a) - upper_left = Plane(A=-c, B=1., D=c*l-x*c+y, boundary_type=boundary_type) - - prism = -top & +bottom & -upper_right & +lower_right & \ - +lower_left & -upper_left - - if boundary_type == 'periodic': - top.periodic_surface = bottom - upper_right.periodic_surface = lower_left - lower_right.periodic_surface = upper_left - - # Handle rounded corners if given - if corner_radius > 0.: - if boundary_type == 'periodic': - raise ValueError('Periodic boundary conditions not permitted when ' - 'rounded corners are used.') - - c = sqrt(3.)/2 - t = l - corner_radius/c - - # Cylinder with corner radius and boundary type pre-applied - cyl1 = partial(ZCylinder, R=corner_radius, boundary_type=boundary_type) - cyl2 = partial(ZCylinder, R=corner_radius/(2*c), - boundary_type=boundary_type) - - if orientation == 'x': - x_min_y_min_in = cyl1(name='x min y min in', x0=x-t/2, y0=y-c*t) - x_min_y_max_in = cyl1(name='x min y max in', x0=x+t/2, y0=y-c*t) - x_max_y_min_in = cyl1(name='x max y min in', x0=x-t/2, y0=y+c*t) - x_max_y_max_in = cyl1(name='x max y max in', x0=x+t/2, y0=y+c*t) - x_min_in = cyl1(name='x min in', x0=x-t, y0=y) - x_max_in = cyl1(name='x max in', x0=x+t, y0=y) - - x_min_y_min_out = cyl2(name='x min y min out', x0=x-l/2, y0=y-c*l) - x_min_y_max_out = cyl2(name='x min y max out', x0=x+l/2, y0=y-c*l) - x_max_y_min_out = cyl2(name='x max y min out', x0=x-l/2, y0=y+c*l) - x_max_y_max_out = cyl2(name='x max y max out', x0=x+l/2, y0=y+c*l) - x_min_out = cyl2(name='x min out', x0=x-l, y0=y) - x_max_out = cyl2(name='x max out', x0=x+l, y0=y) - - corners = (+x_min_y_min_in & -x_min_y_min_out | - +x_min_y_max_in & -x_min_y_max_out | - +x_max_y_min_in & -x_max_y_min_out | - +x_max_y_max_in & -x_max_y_max_out | - +x_min_in & -x_min_out | - +x_max_in & -x_max_out) - - elif orientation == 'y': - x_min_y_min_in = cyl1(name='x min y min in', x0=x-c*t, y0=y-t/2) - x_min_y_max_in = cyl1(name='x min y max in', x0=x-c*t, y0=y+t/2) - x_max_y_min_in = cyl1(name='x max y min in', x0=x+c*t, y0=y-t/2) - x_max_y_max_in = cyl1(name='x max y max in', x0=x+c*t, y0=y+t/2) - y_min_in = cyl1(name='y min in', x0=x, y0=y-t) - y_max_in = cyl1(name='y max in', x0=x, y0=y+t) - - x_min_y_min_out = cyl2(name='x min y min out', x0=x-c*l, y0=y-l/2) - x_min_y_max_out = cyl2(name='x min y max out', x0=x-c*l, y0=y+l/2) - x_max_y_min_out = cyl2(name='x max y min out', x0=x+c*l, y0=y-l/2) - x_max_y_max_out = cyl2(name='x max y max out', x0=x+c*l, y0=y+l/2) - y_min_out = cyl2(name='y min out', x0=x, y0=y-l) - y_max_out = cyl2(name='y max out', x0=x, y0=y+l) - - corners = (+x_min_y_min_in & -x_min_y_min_out | - +x_min_y_max_in & -x_min_y_max_out | - +x_max_y_min_in & -x_max_y_min_out | - +x_max_y_max_in & -x_max_y_max_out | - +y_min_in & -y_min_out | - +y_max_in & -y_max_out) - - prism = prism & ~corners - - return prism From 7b5f2bb821b7346ada0ac84c9aed8936074f3009 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 10 Nov 2017 10:35:47 -0600 Subject: [PATCH 017/282] Simple optimizations to Universe._determine_paths --- openmc/universe.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/openmc/universe.py b/openmc/universe.py index 26977ac0a..cb6574f98 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -553,14 +553,16 @@ class Universe(IDManagerMixin): for cell in self.cells.values(): cell_path = '{}->c{}'.format(univ_path, cell.id) + fill = cell._fill + fill_type = cell.fill_type # If universe-filled, recursively count cells in filling universe - if cell.fill_type == 'universe': - cell.fill._determine_paths(cell_path + '->', instances_only) + if fill_type == 'universe': + fill._determine_paths(cell_path + '->', instances_only) # If lattice-filled, recursively call for all universes in lattice - elif cell.fill_type == 'lattice': - latt = cell.fill + elif fill_type == 'lattice': + latt = fill # Count instances in each universe in the lattice for index in latt._natural_indices: @@ -570,10 +572,10 @@ class Universe(IDManagerMixin): univ._determine_paths(latt_path, instances_only) else: - if cell.fill_type == 'material': - mat = cell.fill - elif cell.fill_type == 'distribmat': - mat = cell.fill[cell._num_instances] + if fill_type == 'material': + mat = fill + elif fill_type == 'distribmat': + mat = fill[cell._num_instances] else: mat = None From 402b09ee3d81054bc97d2fff0d45d9ebc27fe358 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 17 Nov 2017 11:22:48 -0600 Subject: [PATCH 018/282] Add Python bindings for global num_realizations. Fix active rate. --- docs/source/pythonapi/capi.rst | 1 + openmc/capi/tally.py | 9 +++++++-- src/output.F90 | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index 44a755bbd..3c6300bb6 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -22,6 +22,7 @@ Functions openmc.capi.keff openmc.capi.load_nuclide openmc.capi.next_batch + openmc.capi.num_realizations openmc.capi.plot_geometry openmc.capi.reset openmc.capi.run diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index 6725ee5c9..4a76de41d 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -13,7 +13,7 @@ from .error import _error_handler, AllocationError, InvalidIDError from .filter import _get_filter -__all__ = ['Tally', 'tallies', 'global_tallies'] +__all__ = ['Tally', 'tallies', 'global_tallies', 'num_realizations'] # Tally functions _dll.openmc_extend_tallies.argtypes = [c_int32, POINTER(c_int32), POINTER(c_int32)] @@ -89,7 +89,7 @@ def global_tallies(): # Get sum, sum-of-squares, and number of realizations sum_ = array[:, 1] sum_sq = array[:, 2] - n = c_int32.in_dll(_dll, 'n_realizations').value + n = num_realizations() # Determine mean mean = sum_ / n @@ -102,6 +102,11 @@ def global_tallies(): return list(zip(mean, stdev)) +def num_realizations(): + """Number of realizations of global tallies.""" + return c_int32.in_dll(_dll, 'n_realizations').value + + class Tally(_FortranObjectWithID): """Tally stored internally. diff --git a/src/output.F90 b/src/output.F90 index 3d02489f7..c20077584 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -570,7 +570,7 @@ contains write(ou,100) "Total time elapsed", time_total % elapsed ! Calculate particle rate in active/inactive batches - n_active = n_batches - n_inactive + n_active = current_batch - n_inactive if (restart_run) then if (restart_batch < n_inactive) then speed_inactive = real(n_particles * (n_inactive - restart_batch) * & From ee8b7edd9c0a1a70dbcecd4d0511e3baf9ad01aa Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 28 Nov 2017 14:17:45 -0600 Subject: [PATCH 019/282] Display message when writing summary file --- src/input_xml.F90 | 10 +++++----- src/summary.F90 | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 766dbfa9f..c76d66df7 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1920,10 +1920,7 @@ contains type(XMLDocument) :: doc type(XMLNode) :: root - ! Display output message - call write_message("Reading materials XML file...", 5) - - ! Check is materials.xml exists + ! Check if materials.xml exists filename = trim(path_input) // "materials.xml" inquire(FILE=filename, EXIST=file_exists) if (.not. file_exists) then @@ -2057,7 +2054,10 @@ contains type(XMLNode), allocatable :: node_macro_list(:) type(XMLNode), allocatable :: node_sab_list(:) - ! Check is materials.xml exists + ! Display output message + call write_message("Reading materials XML file...", 5) + + ! Check if materials.xml exists filename = trim(path_input) // "materials.xml" inquire(FILE=filename, EXIST=file_exists) if (.not. file_exists) then diff --git a/src/summary.F90 b/src/summary.F90 index e271fbaba..248b48e86 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -11,7 +11,7 @@ module summary use message_passing use mgxs_header, only: nuclides_MG use nuclide_header - use output, only: time_stamp + use output, only: time_stamp, write_message use settings, only: run_CE use surface_header use string, only: to_str @@ -33,6 +33,9 @@ contains integer(HID_T) :: file_id + ! Display output message + call write_message("Writing summary.h5 file...", 5) + ! Create a new file using default properties. file_id = file_create("summary.h5") From 726d1a268f9419db35f3aaf21b2cafa976a98c4a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 28 Nov 2017 23:04:12 -0600 Subject: [PATCH 020/282] Sort materials list before creating elements --- openmc/geometry.py | 2 +- openmc/material.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/openmc/geometry.py b/openmc/geometry.py index 0b02210b7..88c9f1834 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -87,7 +87,7 @@ class Geometry(object): # Write the XML Tree to the geometry.xml file tree = ET.ElementTree(root_element) - tree.write(path, xml_declaration=True, encoding='utf-8', method="xml") + tree.write(path, xml_declaration=True, encoding='utf-8') def find(self, point): """Find cells/universes/lattices which contain a given point diff --git a/openmc/material.py b/openmc/material.py index 03ef7fbc4..3800d89c6 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -10,7 +10,7 @@ import numpy as np import openmc import openmc.data import openmc.checkvalue as cv -from openmc.clean_xml import sort_xml_elements, clean_xml_indentation +from openmc.clean_xml import clean_xml_indentation from .mixin import IDManagerMixin @@ -1129,7 +1129,7 @@ class Materials(cv.CheckedList): material.make_isotropic_in_lab() def _create_material_subelements(self, root_element): - for material in self: + for material in sorted(self, key=lambda x: x.id): root_element.append(material.to_xml_element(self.cross_sections)) def _create_cross_sections_subelement(self, root_element): @@ -1153,14 +1153,13 @@ class Materials(cv.CheckedList): """ root_element = ET.Element("materials") - self._create_material_subelements(root_element) self._create_cross_sections_subelement(root_element) self._create_multipole_library_subelement(root_element) + self._create_material_subelements(root_element) # Clean the indentation in the file to be user-readable - sort_xml_elements(root_element) clean_xml_indentation(root_element) # Write the XML Tree to the materials.xml file tree = ET.ElementTree(root_element) - tree.write(path, xml_declaration=True, encoding='utf-8', method="xml") + tree.write(path, xml_declaration=True, encoding='utf-8') From 38550f3bf87f6400ea577fcfab51545911e21e08 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 06:34:15 -0600 Subject: [PATCH 021/282] Use sorted() to sort XML elements in geometry.xml --- openmc/clean_xml.py | 67 --------------------------------------------- openmc/geometry.py | 7 +++-- 2 files changed, 5 insertions(+), 69 deletions(-) diff --git a/openmc/clean_xml.py b/openmc/clean_xml.py index 2497d0d9f..d1002070b 100644 --- a/openmc/clean_xml.py +++ b/openmc/clean_xml.py @@ -1,70 +1,3 @@ -def sort_xml_elements(tree): - - # Retrieve all children of the root XML node in the tree - elements = list(tree) - - # Initialize empty lists for the sorted and comment elements - sorted_elements = [] - - # Initialize an empty set of tags (e.g., Surface, Cell, and Lattice) - tags = set() - - # Find the unique tags in the tree - for element in elements: - tags.add(element.tag) - - # Initialize an empty list for the comment elements - comment_elements = [] - - # Find the comment elements and record their ordering within the - # tree using a precedence with respect to the subsequent nodes - for index, element in enumerate(elements): - next_element = None - - if 'Comment' in str(element.tag): - - if index < len(elements)-1: - next_element = elements[index+1] - - comment_elements.append((element, next_element)) - - # Now iterate over all tags and order the elements within each tag - for tag in sorted(list(tags)): - - # Retrieve all of the elements for this tag - try: - tagged_elements = tree.findall(tag) - except: - continue - - # Initialize an empty list of tuples to sort (id, element) - tagged_data = [] - - # Retrieve the IDs for each of the elements - for element in tagged_elements: - key = element.get('id') - - # If this element has an "ID" tag, append it to the list to sort - if key is not None: - tagged_data.append((int(key), element)) - - # Sort the elements according to the IDs for this tag - tagged_data.sort() - sorted_elements.extend(list(item[-1] for item in tagged_data)) - - # Add the comment elements while preserving the original precedence - for element, next_element in comment_elements: - index = sorted_elements.index(next_element) - sorted_elements.insert(index, element) - - # Remove all of the sorted elements from the tree - for element in sorted_elements: - tree.remove(element) - - # Add the sorted elements back to the tree in the proper order - tree.extend(sorted_elements) - - def clean_xml_indentation(element, level=0, spaces_per_level=2): """ copy and paste from http://effbot.org/zone/elementent-lib.htm#prettyprint diff --git a/openmc/geometry.py b/openmc/geometry.py index 88c9f1834..0738ae615 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -5,7 +5,7 @@ from xml.etree import ElementTree as ET from six import string_types import openmc -from openmc.clean_xml import sort_xml_elements, clean_xml_indentation +from openmc.clean_xml import clean_xml_indentation from openmc.checkvalue import check_type @@ -81,8 +81,11 @@ class Geometry(object): root_element = ET.Element("geometry") self.root_universe.create_xml_subelement(root_element) + # Sort the elements in the file + root_element[:] = sorted(root_element, key=lambda x: ( + x.tag, int(x.get('id')))) + # Clean the indentation in the file to be user-readable - sort_xml_elements(root_element) clean_xml_indentation(root_element) # Write the XML Tree to the geometry.xml file From a9b831cc912d0858032b424a2b6e9a7cde8d976d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 07:19:18 -0600 Subject: [PATCH 022/282] Add one publication to list --- docs/source/publications.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source/publications.rst b/docs/source/publications.rst index f47ac704c..7578fee6b 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -171,6 +171,12 @@ Miscellaneous Multi-group Cross Section Generation ------------------------------------ +- Zhaoyuan Liu, Kord Smith, Benoit Forget, and Javier Ortensi, "`Cumulative + migration method for computing rigorous diffusion coefficients and transport + cross sections from Monte Carlo + `_," *Ann. Nucl. Energy*, + **112**, 507-516 (2018). + - Gang Yang, Tongkyu Park, and Won Sik Yang, "Effects of Fuel Salt Velocity Field on Neutronics Performances in Molten Salt Reactors with Open Flow Channels," *Trans. Am. Nucl. Soc.*, **117**, 1339-1342 (2017). From ad0283d0166e5f9779dcfc1c65d8e4f2a7236e52 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 1 Dec 2017 10:58:52 -0600 Subject: [PATCH 023/282] Account for 'ftn' as an MPI wrapper since it is common on Cray systems --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dfd9221ce..791e31c44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,7 +46,7 @@ add_definitions(-DMAX_COORD=${maxcoord}) #=============================================================================== set(MPI_ENABLED FALSE) -if($ENV{FC} MATCHES "mpi[^/]*$") +if($ENV{FC} MATCHES "(mpi[^/]*|ftn)$") message("-- Detected MPI wrapper: $ENV{FC}") add_definitions(-DMPI) set(MPI_ENABLED TRUE) From 4fbd9d75c22e4d6f22f0a6614f379a74a3fde169 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 1 Dec 2017 11:21:30 -0600 Subject: [PATCH 024/282] Allow openmc.capi.statepoint_write to pass filename --- openmc/capi/core.py | 20 ++++++++++++++++---- src/state_point.F90 | 25 ++++++++++++++++--------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 1f7225f64..1e3b8a510 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -1,5 +1,6 @@ from contextlib import contextmanager -from ctypes import CDLL, c_int, c_int32, c_int64, c_double, POINTER, Structure +from ctypes import (CDLL, c_int, c_int32, c_int64, c_double, c_char_p, + POINTER, Structure) from warnings import warn import numpy as np @@ -38,6 +39,7 @@ _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_statepoint_write.argtypes = [POINTER(c_char_p)] _dll.openmc_statepoint_write.restype = None @@ -217,9 +219,19 @@ def source_bank(): return as_array(ptr, (n.value,)).view(bank_dtype) -def statepoint_write(): - """Write a statepoint.""" - _dll.openmc_statepoint_write() +def statepoint_write(filename=None): + """Write a statepoint file. + + Parameters + ---------- + filename : str or None + Path to the statepoint to write. If None is passed, a default name that + contains the current batch will be written. + + """ + if filename is not None: + filename = c_char_p(filename.encode()) + _dll.openmc_statepoint_write(filename) @contextmanager diff --git a/src/state_point.F90 b/src/state_point.F90 index 0383150ac..6d647b72e 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -29,7 +29,7 @@ module state_point use random_lcg, only: seed use settings use simulation_header - use string, only: to_str, count_digits, zero_padded + use string, only: to_str, count_digits, zero_padded, to_f_string use tally_header use tally_filter_header use tally_derivative_header, only: tally_derivs @@ -43,7 +43,8 @@ contains ! OPENMC_STATEPOINT_WRITE writes an HDF5 statepoint file to disk !=============================================================================== - subroutine openmc_statepoint_write() bind(C) + subroutine openmc_statepoint_write(filename) bind(C) + type(C_PTR), intent(in), optional :: filename integer :: i, j, k integer :: i_xs @@ -57,19 +58,25 @@ contains integer(C_INT) :: err real(C_DOUBLE) :: k_combined(2) character(MAX_WORD_LEN), allocatable :: str_array(:) - character(MAX_FILE_LEN) :: filename + character(C_CHAR), pointer :: string(:) + character(len=:, kind=C_CHAR), allocatable :: filename_ - ! Set filename for state point - filename = trim(path_output) // 'statepoint.' // & - & zero_padded(current_batch, count_digits(n_max_batches)) - filename = trim(filename) // '.h5' + if (present(filename)) then + call c_f_pointer(filename, string, [MAX_FILE_LEN]) + filename_ = to_f_string(string) + else + ! Set filename for state point + filename_ = trim(path_output) // 'statepoint.' // & + & zero_padded(current_batch, count_digits(n_max_batches)) + filename_ = trim(filename_) // '.h5' + end if ! Write message - call write_message("Creating state point " // trim(filename) // "...", 5) + call write_message("Creating state point " // trim(filename_) // "...", 5) if (master) then ! Create statepoint file - file_id = file_create(filename) + file_id = file_create(filename_) ! Write file type call write_attribute(file_id, "filetype", "statepoint") From 5116916d7a21458e9e297912c0c0069a36eee1e6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 4 Dec 2017 17:57:27 -0600 Subject: [PATCH 025/282] Fix capi.Cell.__new__ --- openmc/capi/cell.py | 43 +++++++++++++++++++++++++++++++++-------- src/api.F90 | 1 + src/geometry_header.F90 | 32 ++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/openmc/capi/cell.py b/openmc/capi/cell.py index fe3382589..e14df3f4a 100644 --- a/openmc/capi/cell.py +++ b/openmc/capi/cell.py @@ -7,12 +7,15 @@ from numpy.ctypeslib import as_array from . import _dll from .core import _FortranObjectWithID -from .error import _error_handler +from .error import _error_handler, AllocationError, InvalidIDError from .material import Material __all__ = ['Cell', 'cells'] # Cell functions +_dll.openmc_extend_cells.argtypes = [c_int32, POINTER(c_int32), POINTER(c_int32)] +_dll.openmc_extend_cells.restype = c_int +_dll.openmc_extend_cells.errcheck = _error_handler _dll.openmc_cell_get_id.argtypes = [c_int32, POINTER(c_int32)] _dll.openmc_cell_get_id.restype = c_int _dll.openmc_cell_get_id.errcheck = _error_handler @@ -51,11 +54,35 @@ class Cell(_FortranObjectWithID): """ __instances = WeakValueDictionary() - def __new__(cls, *args): - if args not in cls.__instances: - instance = super(Cell, self).__new__(cls) - cls.__instances[args] = instance - return cls.__instances[args] + def __new__(cls, uid=None, new=True, index=None): + mapping = cells + if index is None: + if new: + # Determine ID to assign + if uid is None: + try: + uid = max(mapping) + 1 + except ValueError: + uid = 1 + else: + if uid in mapping: + raise AllocationError('A cell with ID={} has already ' + 'been allocated.'.format(uid)) + + index = c_int32() + _dll.openmc_extend_cells(1, index, None) + index = index.value + else: + index = mapping[uid]._index + + if index not in cls.__instances: + instance = super(Cell, cls).__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): @@ -113,11 +140,11 @@ class _CellMapping(Mapping): except (AllocationError, InvalidIDError) as e: # __contains__ expects a KeyError to work correctly raise KeyError(str(e)) - return Cell(index.value) + return Cell(index=index.value) def __iter__(self): for i in range(len(self)): - yield Cell(i + 1).id + yield Cell(index=i + 1).id def __len__(self): return c_int32.in_dll(_dll, 'n_cells').value diff --git a/src/api.F90 b/src/api.F90 index 933faaa09..d0231c9a8 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -40,6 +40,7 @@ module openmc_api public :: openmc_energy_filter_get_bins public :: openmc_energy_filter_set_bins public :: openmc_extend_filters + public :: openmc_extend_cells public :: openmc_extend_materials public :: openmc_extend_tallies public :: openmc_filter_get_id diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index dd2cad7f1..63b1bbe95 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -427,6 +427,38 @@ contains ! C API FUNCTIONS !=============================================================================== + function openmc_extend_cells(n, index_start, index_end) result(err) bind(C) + ! Extend the cells array by n elements + integer(C_INT32_T), value, intent(in) :: n + integer(C_INT32_T), optional, intent(out) :: index_start + integer(C_INT32_T), optional, intent(out) :: index_end + integer(C_INT) :: err + + type(Cell), allocatable :: temp(:) ! temporary cells array + + if (n_cells == 0) then + ! Allocate cells array + allocate(cells(n)) + else + ! Allocate cells array with increased size + allocate(temp(n_cells + n)) + + ! Copy original cells to temporary array + temp(1:n_cells) = cells + + ! Move allocation from temporary array + call move_alloc(FROM=temp, TO=cells) + end if + + ! Return indices in cells array + if (present(index_start)) index_start = n_cells + 1 + if (present(index_end)) index_end = n_cells + n + n_cells = n_cells + n + + err = 0 + end function openmc_extend_cells + + function openmc_get_cell_index(id, index) result(err) bind(C) ! Return the index in the cells array of a cell with a given ID integer(C_INT32_T), value :: id From 168f202e3ecf48cdd15b541dc396b24465832986 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 11 Dec 2017 15:31:31 -0500 Subject: [PATCH 026/282] Address #939 feedback --- CMakeLists.txt | 2 +- src/constants.F90 | 3 +++ src/initialize.F90 | 4 ++-- src/random_lcg.C | 28 ++++++++++++++-------------- src/random_lcg.h | 8 ++++---- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8b801eb7..41ca8d8ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -270,7 +270,7 @@ endif() # Show flags being used message(STATUS "Fortran flags: ${f90flags}") message(STATUS "C flags: ${cflags}") -message(STATUS "CXX flags: ${cxxflags}") +message(STATUS "C++ flags: ${cxxflags}") message(STATUS "Linker flags: ${ldflags}") #=============================================================================== diff --git a/src/constants.F90 b/src/constants.F90 index 73aca25d2..2466dbb14 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -1,5 +1,7 @@ module constants + use, intrinsic :: ISO_C_BINDING + implicit none ! ============================================================================ @@ -419,6 +421,7 @@ module constants integer, parameter :: STREAM_SOURCE = 3 integer, parameter :: STREAM_URR_PTABLE = 4 integer, parameter :: STREAM_VOLUME = 5 + integer(C_INT64_T), parameter :: DEFAULT_SEED = 1_8 ! ============================================================================ ! MISCELLANEOUS CONSTANTS diff --git a/src/initialize.F90 b/src/initialize.F90 index 117381328..21d568c72 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -20,7 +20,7 @@ module initialize use message_passing use mgxs_data, only: read_mgxs, create_macro_xs use output, only: print_version, write_message, print_usage - use random_lcg, only: openmc_set_seed, seed + use random_lcg, only: openmc_set_seed use settings #ifdef _OPENMP use simulation_header, only: n_threads @@ -84,7 +84,7 @@ contains ! Initialize random number generator -- if the user specifies a seed, it ! will be re-initialized later - err = openmc_set_seed(seed) + err = openmc_set_seed(DEFAULT_SEED) ! Read XML input files call read_input_xml() diff --git a/src/random_lcg.C b/src/random_lcg.C index ef49cd13b..5bdf13d70 100644 --- a/src/random_lcg.C +++ b/src/random_lcg.C @@ -15,11 +15,11 @@ const double prn_norm = pow(2, -63); // 2^-63 // Module constants const int N_STREAMS = 5; -const int STREAM_TRACKING = 0; -const int STREAM_TALLIES = 1; -const int STREAM_SOURCE = 2; -const int STREAM_URR_PTABLE = 3; -const int STREAM_VOLUME = 4; +const int STREAM_TRACKING = 1; +const int STREAM_TALLIES = 2; +const int STREAM_SOURCE = 3; +const int STREAM_URR_PTABLE = 4; +const int STREAM_VOLUME = 5; // Current PRNG state uint64_t prn_seed[N_STREAMS]; // current seed @@ -48,9 +48,9 @@ prn() //============================================================================== extern "C" double -future_prn(uint64_t n) +future_prn(int64_t n) { - return future_seed(n, prn_seed[stream]) * prn_norm; + return future_seed(static_cast(n), prn_seed[stream]) * prn_norm; } //============================================================================== @@ -58,10 +58,10 @@ future_prn(uint64_t n) //============================================================================== extern "C" void -set_particle_seed(uint64_t id) +set_particle_seed(int64_t id) { for (int i = 0; i < N_STREAMS; i++) { - prn_seed[i] = future_seed(id * prn_stride, seed + i); + prn_seed[i] = future_seed(static_cast(id) * prn_stride, seed + i); } } @@ -70,16 +70,16 @@ set_particle_seed(uint64_t id) //============================================================================== extern "C" void -advance_prn_seed(uint64_t n) +advance_prn_seed(int64_t n) { - prn_seed[stream] = future_seed(n, prn_seed[stream]); + prn_seed[stream] = future_seed(static_cast(n), prn_seed[stream]); } //============================================================================== // FUTURE_SEED //============================================================================== -extern "C" uint64_t +uint64_t future_seed(uint64_t n, uint64_t seed) { // Make sure nskip is less than 2^M. @@ -121,7 +121,7 @@ future_seed(uint64_t n, uint64_t seed) extern "C" void prn_set_stream(int i) { - stream = i - 1; + stream = i - 1; // Shift by one to move from Fortran to C indexing. } //============================================================================== @@ -136,7 +136,7 @@ openmc_set_seed(uint64_t new_seed) for (int i = 0; i < N_STREAMS; i++) { prn_seed[i] = seed + i; } - stream = STREAM_TRACKING; + prn_set_stream(STREAM_TRACKING); #pragma end omp parallel return 0; } diff --git a/src/random_lcg.h b/src/random_lcg.h index 889195f10..4c7ec0140 100644 --- a/src/random_lcg.h +++ b/src/random_lcg.h @@ -14,21 +14,21 @@ extern "C" double prn(); // current seed. //============================================================================== -extern "C" double future_prn(uint64_t n); +extern "C" double future_prn(int64_t n); //============================================================================== // SET_PARTICLE_SEED sets the seed to a unique value based on the ID of the // particle. //============================================================================== -extern "C" void set_particle_seed(uint64_t id); +extern "C" void set_particle_seed(int64_t id); //============================================================================== // ADVANCE_PRN_SEED advances the random number seed 'n' times from the current // seed. //============================================================================== -extern "C" void advance_prn_seed(uint64_t n); +extern "C" void advance_prn_seed(int64_t n); //============================================================================== // FUTURE_SEED advances the random number seed 'skip' times. This is usually @@ -37,7 +37,7 @@ extern "C" void advance_prn_seed(uint64_t n); // are used. //============================================================================== -extern "C" uint64_t future_seed(uint64_t n, uint64_t seed); +uint64_t future_seed(uint64_t n, uint64_t seed); //============================================================================== // PRN_SET_STREAM changes the random number stream. If random numbers are needed From ed5ff626f4ff2c35eb524c89198be7f6eb73e213 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 12 Dec 2017 15:16:54 -0500 Subject: [PATCH 027/282] Link Fortran RNG constants to C++ definitions --- src/constants.F90 | 12 ++++++------ src/random_lcg.C | 12 ++---------- src/random_lcg.F90 | 13 ++++++------- src/random_lcg.h | 13 ++++++++++++- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/constants.F90 b/src/constants.F90 index 2466dbb14..5bc9eb88e 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -415,12 +415,12 @@ module constants ! ============================================================================ ! RANDOM NUMBER STREAM CONSTANTS - integer, parameter :: N_STREAMS = 5 - integer, parameter :: STREAM_TRACKING = 1 - integer, parameter :: STREAM_TALLIES = 2 - integer, parameter :: STREAM_SOURCE = 3 - integer, parameter :: STREAM_URR_PTABLE = 4 - integer, parameter :: STREAM_VOLUME = 5 + integer(C_INT), bind(C, name='N_STREAMS') :: N_STREAMS + integer(C_INT), bind(C, name='STREAM_TRACKING') :: STREAM_TRACKING + integer(C_INT), bind(C, name='STREAM_TALLIES') :: STREAM_TALLIES + integer(C_INT), bind(C, name='STREAM_SOURCE') :: STREAM_SOURCE + integer(C_INT), bind(C, name='STREAM_URR_PTABLE') :: STREAM_URR_PTABLE + integer(C_INT), bind(C, name='STREAM_VOLUME') :: STREAM_VOLUME integer(C_INT64_T), parameter :: DEFAULT_SEED = 1_8 ! ============================================================================ diff --git a/src/random_lcg.C b/src/random_lcg.C index 5bdf13d70..42450fd37 100644 --- a/src/random_lcg.C +++ b/src/random_lcg.C @@ -13,14 +13,6 @@ const uint64_t prn_mask = 0x7fffffffffffffff; // 2^63 - 1 const uint64_t prn_stride = 152917LL; // stride between particles const double prn_norm = pow(2, -63); // 2^-63 -// Module constants -const int N_STREAMS = 5; -const int STREAM_TRACKING = 1; -const int STREAM_TALLIES = 2; -const int STREAM_SOURCE = 3; -const int STREAM_URR_PTABLE = 4; -const int STREAM_VOLUME = 5; - // Current PRNG state uint64_t prn_seed[N_STREAMS]; // current seed int stream; // current RNG stream @@ -121,7 +113,7 @@ future_seed(uint64_t n, uint64_t seed) extern "C" void prn_set_stream(int i) { - stream = i - 1; // Shift by one to move from Fortran to C indexing. + stream = i; // Shift by one to move from Fortran to C indexing. } //============================================================================== @@ -129,7 +121,7 @@ prn_set_stream(int i) //============================================================================== extern "C" int -openmc_set_seed(uint64_t new_seed) +openmc_set_seed(int64_t new_seed) { seed = new_seed; #pragma omp parallel diff --git a/src/random_lcg.F90 b/src/random_lcg.F90 index b76341cbd..839bd729a 100644 --- a/src/random_lcg.F90 +++ b/src/random_lcg.F90 @@ -7,39 +7,38 @@ module random_lcg integer(C_INT64_T), bind(C) :: seed interface - function prn() result(pseudo_rn) bind(C, name='prn') + function prn() result(pseudo_rn) bind(C) use ISO_C_BINDING implicit none real(C_DOUBLE) :: pseudo_rn end function prn - function future_prn(n) result(pseudo_rn) bind(C, name='future_prn') + function future_prn(n) result(pseudo_rn) bind(C) use ISO_C_BINDING implicit none integer(C_INT64_T), value :: n real(C_DOUBLE) :: pseudo_rn end function future_prn - subroutine set_particle_seed(id) bind(C, name='set_particle_seed') + subroutine set_particle_seed(id) bind(C) use ISO_C_BINDING implicit none integer(C_INT64_T), value :: id end subroutine set_particle_seed - subroutine advance_prn_seed(n) bind(C, name='advance_prn_seed') + subroutine advance_prn_seed(n) bind(C) use ISO_C_BINDING implicit none integer(C_INT64_T), value :: n end subroutine advance_prn_seed - subroutine prn_set_stream(n) bind(C, name='prn_set_stream') + subroutine prn_set_stream(n) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value :: n end subroutine prn_set_stream - function openmc_set_seed(new_seed) result(err) & - bind(C, name='openmc_set_seed') + function openmc_set_seed(new_seed) result(err) bind(C) use ISO_C_BINDING implicit none integer(C_INT64_T), value :: new_seed diff --git a/src/random_lcg.h b/src/random_lcg.h index 4c7ec0140..ef8ed8a44 100644 --- a/src/random_lcg.h +++ b/src/random_lcg.h @@ -3,6 +3,17 @@ #include +//============================================================================== +// Module constants. +//============================================================================== + +extern "C" const int N_STREAMS = 5; +extern "C" const int STREAM_TRACKING = 0; +extern "C" const int STREAM_TALLIES = 1; +extern "C" const int STREAM_SOURCE = 2; +extern "C" const int STREAM_URR_PTABLE = 3; +extern "C" const int STREAM_VOLUME = 4; + //============================================================================== // PRN generates a pseudo-random number using a linear congruential generator. //============================================================================== @@ -51,6 +62,6 @@ extern "C" void prn_set_stream(int n); // API FUNCTIONS //============================================================================== -extern "C" int openmc_set_seed(uint64_t new_seed); +extern "C" int openmc_set_seed(int64_t new_seed); #endif // RANDOM_LCG_H From ec302b52d64befdd53d4a0ac17daa16c935e660b Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 12 Dec 2017 17:57:53 -0500 Subject: [PATCH 028/282] Fix multiple definition error in random_lcg.h --- src/random_lcg.C | 38 ++++++++++++++++++++++++-------------- src/random_lcg.h | 12 ++++++------ 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/random_lcg.C b/src/random_lcg.C index 42450fd37..ea79be67f 100644 --- a/src/random_lcg.C +++ b/src/random_lcg.C @@ -2,16 +2,26 @@ #include +// Constants +extern "C" const int N_STREAMS {5}; +extern "C" const int STREAM_TRACKING {0}; +extern "C" const int STREAM_TALLIES {1}; +extern "C" const int STREAM_SOURCE {2}; +extern "C" const int STREAM_URR_PTABLE {3}; +extern "C" const int STREAM_VOLUME {4}; + // Starting seed -int64_t seed = 1; +int64_t seed {1}; // LCG parameters -const uint64_t prn_mult = 2806196910506780709LL; // multiplication factor, g -const uint64_t prn_add = 1; // additive factor, c -const uint64_t prn_mod = 0x8000000000000000; // 2^63 -const uint64_t prn_mask = 0x7fffffffffffffff; // 2^63 - 1 -const uint64_t prn_stride = 152917LL; // stride between particles -const double prn_norm = pow(2, -63); // 2^-63 +constexpr uint64_t prn_mult {2806196910506780709LL}; // multiplication + // factor, g +constexpr uint64_t prn_add {1}; // additive factor, c +constexpr uint64_t prn_mod {0x8000000000000000}; // 2^63 +constexpr uint64_t prn_mask {0x7fffffffffffffff}; // 2^63 - 1 +constexpr uint64_t prn_stride {152917LL}; // stride between + // particles +constexpr double prn_norm {pow(2, -63)}; // 2^-63 // Current PRNG state uint64_t prn_seed[N_STREAMS]; // current seed @@ -84,19 +94,19 @@ future_seed(uint64_t n, uint64_t seed) // and C which can then be used to find x_N = G*x_0 + C mod 2^M. // Initialize constants - uint64_t g = prn_mult; - uint64_t c = prn_add; - uint64_t g_new = 1; - uint64_t c_new = 0; + uint64_t g {prn_mult}; + uint64_t c {prn_add}; + uint64_t g_new {1}; + uint64_t c_new {0}; while (n > 0) { // Check if the least significant bit is 1. if (n & 1) { - g_new = g_new * g; + g_new *= g; c_new = c_new * g + c; } - c = (g + 1) * c; - g = g * g; + c *= (g + 1); + g *= g; // Move bits right, dropping least significant bit. n >>= 1; diff --git a/src/random_lcg.h b/src/random_lcg.h index ef8ed8a44..ca8dfd826 100644 --- a/src/random_lcg.h +++ b/src/random_lcg.h @@ -7,12 +7,12 @@ // Module constants. //============================================================================== -extern "C" const int N_STREAMS = 5; -extern "C" const int STREAM_TRACKING = 0; -extern "C" const int STREAM_TALLIES = 1; -extern "C" const int STREAM_SOURCE = 2; -extern "C" const int STREAM_URR_PTABLE = 3; -extern "C" const int STREAM_VOLUME = 4; +extern "C" const int N_STREAMS; +extern "C" const int STREAM_TRACKING; +extern "C" const int STREAM_TALLIES; +extern "C" const int STREAM_SOURCE; +extern "C" const int STREAM_URR_PTABLE; +extern "C" const int STREAM_VOLUME; //============================================================================== // PRN generates a pseudo-random number using a linear congruential generator. From 110684619d5c3825ddb1acfe764e8793f8e67565 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 12 Dec 2017 19:27:16 -0500 Subject: [PATCH 029/282] Separate Fortran and C++ info in the styleguide --- docs/source/devguide/styleguide.rst | 96 +++++++++++++++++------------ 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/docs/source/devguide/styleguide.rst b/docs/source/devguide/styleguide.rst index e2b3de5c3..4bba8f4c0 100644 --- a/docs/source/devguide/styleguide.rst +++ b/docs/source/devguide/styleguide.rst @@ -8,28 +8,32 @@ In order to keep the OpenMC code base consistent in style, this guide specifies a number of rules which should be adhered to when modified existing code or adding new code in OpenMC. ---------------- -Fortran and C++ ---------------- +------- +Fortran +------- Miscellaneous ------------- -Make sure code can be compiled with most common compilers, especially the GCC -and Intel compilers. This supersedes the rules about standards---if a Fortran -2003/2008 feature is not implemented in a common compiler then do not use it. +Conform to the Fortran 2008 standard. + +Make sure code can be compiled with most common compilers, especially gfortran +and the Intel Fortran compiler. This supercedes the previous rule --- if a +Fortran 2003/2008 feature is not implemented in a common compiler, do not use +it. Do not use special extensions that can be only be used from certain compilers. Always include comments to describe what your code is doing. Do not be afraid of using copious amounts of comments. +Use <, >, <=, >=, ==, and /= rather than .lt., .gt., .le., .ge., .eq., and .ne. + Try to keep code within 80 columns when possible. -Don't use ``print *``, ``write(*,*)``, ``fprintf()``, or ``std::cout``. If -writing to a file, use a specific unit. Writing to standard output or standard -error should be handled by the ``write_message`` subroutine or functionality in -the error module. +Don't use ``print *`` or ``write(*,*)``. If writing to a file, use a specific +unit. Writing to standard output or standard error should be handled by the +``write_message`` subroutine or functionality in the error module. Naming ------ @@ -49,8 +53,8 @@ Local variables, global variables, and type attributes should be lower-case with underscores (e.g. ``n_cells``) except for physics symbols that are written differently by convention (e.g. ``E`` for energy). -Constant (parameter or const) variables should be in upper-case with -underscores, e.g. ``SQRT_PI``. These should usually be defined in the +Constant (parameter) variables should be in upper-case with underscores, e.g. +``SQRT_PI``. If they are used by more than one module, define them in the constants.F90 module. Procedures @@ -68,17 +72,20 @@ Variables --------- Never, under any circumstances, should implicit variables be used! Always -include ``implicit none`` in Fortran source code and define all your variables. +include ``implicit none`` and define all your variables. -32-bit reals (``real(4)`` and ``float``) should never be used. Always use 64-bit -reals (``real(8)`` and ``double``). +32-bit reals (real(4)) should never be used. Always use 64-bit reals (real(8)). For arbitrary length character variables, use the pre-defined lengths ``MAX_LINE_LEN``, ``MAX_WORD_LEN``, and ``MAX_FILE_LEN`` if possible. +Do not use old-style character/array length (e.g. character*80, real*8). + Integer values being used to indicate a certain state should be defined as named constants (see the constants.F90 module for many examples). +Always use a double colon :: when declaring a variable. + Yes: .. code-block:: fortran @@ -102,6 +109,8 @@ Always put shared variables in modules. Access module variables through a ``use`` statement. Always use the ``only`` specifier on the ``use`` statement except for variables from the global, constants, and various header modules. +Never use ``equivalence`` statements, ``common`` blocks, or ``data`` statements. + Indentation ----------- @@ -154,30 +163,19 @@ each side. Do not leave trailing whitespace at the end of a line. ----------------- -Fortran-Specific ----------------- - -Conform to the Fortran 2008 standard. - -Use <, >, <=, >=, ==, and /= rather than .lt., .gt., .le., .ge., .eq., and .ne. - -Do not use old-style character/array length (e.g. character*80, real*8). - -Always use a double colon :: when declaring a variable. - -Never use ``equivalence`` statements, ``common`` blocks, or ``data`` statements. - ------------- -C++-Specific ------------- +--- +C++ +--- Miscellaneous ------------- +Follow the `C++ Core Guidelines`_ except when they conflict with another +guideline listed here. + Conform to the C++11 standard. -Always use C++-style comments (``//``) as opposed to C-style (``/**/``). (It +Always use C++-style comments (``//``) as opposed to C-style (``/**/``). (It is more difficult to comment out a large section of code that uses C-style comments.) @@ -197,23 +195,42 @@ Do not use using-directives e.g. ``using namespace foobar;`` Do not use C-style casting. Always use the C++-style casts ``static_cast``, ``const_cast``, or ``reinterpret_cast``. +Naming +------ + +In general, write your code in lower-case. Having code in all caps does not +enhance code readability or otherwise. + +Struct and class names should be CamelCase, e.g. ``HexLattice``. + +Functions (including member fuctions) should be lower-case with underscores, +e.g. ``get_indices``. + +Local variables, global variables, and struct/class attributes should be +lower-case with underscores (e.g. ``n_cells``) except for physics symbols that +are written differently by convention (e.g. ``E`` for energy). + +Const variables should be in upper-case with underscores, e.g. ``SQRT_PI``. + Curly braces ------------ -For a function definition, the opening brace should be on the same line as the -end of the function definition. The closing brace should be on its own line. -If the entire function fits on one line, then the closing brace can be on the -same line. e.g.: +For a function definition, the opening and closing braces should each be on +their own lines. This helps distinguish function code from the arugment list. +If the entire function fits on one line, then the braces can be on the same +line. e.g.: .. code-block:: C++ - return_type function(type1 arg1, type2 arg2) { + return_type function(type1 arg1, type2 arg2) + { content(); } return_type function_with_many_args(type1 arg1, type2 arg2, type3 arg3, - type4 arg4) { + type4 arg4) + { content(); } @@ -272,6 +289,7 @@ Use of third-party Python packages should be limited to numpy_, scipy_, and h5py_. Use of other third-party packages must be implemented as optional dependencies rather than required dependencies. +.. _C++ Core Guidelines: http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines .. _PEP8: https://www.python.org/dev/peps/pep-0008/ .. _numpydoc: https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt .. _numpy: http://www.numpy.org/ From 37671e1792327e7c8f6f58f0f26503f99abc960e Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 13 Dec 2017 18:10:17 -0500 Subject: [PATCH 030/282] Fix styleguide typos --- docs/source/devguide/styleguide.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/source/devguide/styleguide.rst b/docs/source/devguide/styleguide.rst index 4bba8f4c0..a0500965e 100644 --- a/docs/source/devguide/styleguide.rst +++ b/docs/source/devguide/styleguide.rst @@ -18,7 +18,7 @@ Miscellaneous Conform to the Fortran 2008 standard. Make sure code can be compiled with most common compilers, especially gfortran -and the Intel Fortran compiler. This supercedes the previous rule --- if a +and the Intel Fortran compiler. This supersedes the previous rule --- if a Fortran 2003/2008 feature is not implemented in a common compiler, do not use it. @@ -171,7 +171,8 @@ Miscellaneous ------------- Follow the `C++ Core Guidelines`_ except when they conflict with another -guideline listed here. +guideline listed here. For convenience, many important guidelines from that +list are repeated here. Conform to the C++11 standard. @@ -203,7 +204,7 @@ enhance code readability or otherwise. Struct and class names should be CamelCase, e.g. ``HexLattice``. -Functions (including member fuctions) should be lower-case with underscores, +Functions (including member functions) should be lower-case with underscores, e.g. ``get_indices``. Local variables, global variables, and struct/class attributes should be @@ -216,7 +217,7 @@ 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 arugment list. +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.: From f6d6b926b15d70b1ff7477f4bc8d7ea46504ff05 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 13 Dec 2017 18:49:12 -0500 Subject: [PATCH 031/282] Address #939 comments --- CMakeLists.txt | 2 +- src/{random_lcg.C => random_lcg.cpp} | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) rename src/{random_lcg.C => random_lcg.cpp} (95%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 41ca8d8ee..4edc89ebc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -417,7 +417,7 @@ set(LIBOPENMC_FORTRAN_SRC ) set(LIBOPENMC_CXX_SRC src/random_lcg.h - src/random_lcg.C) + src/random_lcg.cpp) add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC} ${LIBOPENMC_CXX_SRC}) set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc) add_executable(${program} src/main.F90) diff --git a/src/random_lcg.C b/src/random_lcg.cpp similarity index 95% rename from src/random_lcg.C rename to src/random_lcg.cpp index ea79be67f..047bda8e5 100644 --- a/src/random_lcg.C +++ b/src/random_lcg.cpp @@ -21,7 +21,7 @@ constexpr uint64_t prn_mod {0x8000000000000000}; // 2^63 constexpr uint64_t prn_mask {0x7fffffffffffffff}; // 2^63 - 1 constexpr uint64_t prn_stride {152917LL}; // stride between // particles -constexpr double prn_norm {pow(2, -63)}; // 2^-63 +constexpr double prn_norm {1.0 / prn_mod}; // 2^-63 // Current PRNG state uint64_t prn_seed[N_STREAMS]; // current seed @@ -135,10 +135,11 @@ openmc_set_seed(int64_t new_seed) { seed = new_seed; #pragma omp parallel - for (int i = 0; i < N_STREAMS; i++) { - prn_seed[i] = seed + i; + { + for (int i = 0; i < N_STREAMS; i++) { + prn_seed[i] = seed + i; + } + prn_set_stream(STREAM_TRACKING); } - prn_set_stream(STREAM_TRACKING); -#pragma end omp parallel return 0; } From 7b56bcead68b27608a8bca3d2e76b0ba04d78485 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Dec 2017 09:52:05 +0700 Subject: [PATCH 032/282] Add unit tests for openmc.capi --- openmc/__init__.py | 1 + openmc/capi/cell.py | 7 + openmc/capi/core.py | 13 +- openmc/capi/error.py | 1 + src/api.F90 | 1 + src/geometry_header.F90 | 17 +++ tests/unit_tests/test_capi.py | 241 ++++++++++++++++++++++++++++++++++ 7 files changed, 275 insertions(+), 6 deletions(-) create mode 100644 tests/unit_tests/test_capi.py diff --git a/openmc/__init__.py b/openmc/__init__.py index 8fb9bcf37..13dac05e7 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -27,6 +27,7 @@ from openmc.particle_restart import * from openmc.mixin import * from openmc.plotter import * from openmc.search import * +from . import examples # Import a few convencience functions that used to be here from openmc.model import get_rectangular_prism, get_hexagonal_prism diff --git a/openmc/capi/cell.py b/openmc/capi/cell.py index e14df3f4a..f13f64a04 100644 --- a/openmc/capi/cell.py +++ b/openmc/capi/cell.py @@ -25,6 +25,9 @@ _dll.openmc_cell_set_fill.argtypes = [ c_int32, c_int, c_int32, POINTER(c_int32)] _dll.openmc_cell_set_fill.restype = c_int _dll.openmc_cell_set_fill.errcheck = _error_handler +_dll.openmc_cell_set_id.argtypes = [c_int32, c_int32] +_dll.openmc_cell_set_id.restype = c_int +_dll.openmc_cell_set_id.errcheck = _error_handler _dll.openmc_cell_set_temperature.argtypes = [ c_int32, c_double, POINTER(c_int32)] _dll.openmc_cell_set_temperature.restype = c_int @@ -90,6 +93,10 @@ class Cell(_FortranObjectWithID): _dll.openmc_cell_get_id(self._index, cell_id) return cell_id.value + @id.setter + def id(self, cell_id): + _dll.openmc_cell_set_id(self._index, cell_id) + @property def fill(self): fill_type = c_int() diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 1e3b8a510..c25146fb5 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -8,6 +8,7 @@ from numpy.ctypeslib import as_array from . import _dll from .error import _error_handler +import openmc.capi class _Bank(Structure): @@ -63,8 +64,8 @@ def find_cell(xyz): Returns ------- - int - ID of the cell. + openmc.capi.Cell + Cell containing the point int If the cell at the given point is repeated in the geometry, this indicates which instance it is, i.e., 0 would be the first instance. @@ -73,7 +74,7 @@ def find_cell(xyz): uid = c_int32() instance = c_int32() _dll.openmc_find((c_double*3)(*xyz), 1, uid, instance) - return uid.value, instance.value + return openmc.capi.cells[uid.value], instance.value def find_material(xyz): @@ -86,14 +87,14 @@ def find_material(xyz): Returns ------- - int or None - ID of the material or None is no material is found + openmc.capi.Material or None + Material containing the point, or None is no material is found """ uid = c_int32() instance = c_int32() _dll.openmc_find((c_double*3)(*xyz), 2, uid, instance) - return uid.value if uid != 0 else None + return openmc.capi.materials[uid.value] if uid != 0 else None def hard_reset(): diff --git a/openmc/capi/error.py b/openmc/capi/error.py index 98d43ae46..c312987bc 100644 --- a/openmc/capi/error.py +++ b/openmc/capi/error.py @@ -1,4 +1,5 @@ from ctypes import c_int, c_char +from warnings import warn from . import _dll diff --git a/src/api.F90 b/src/api.F90 index d0231c9a8..4538bbf3e 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -36,6 +36,7 @@ module openmc_api public :: openmc_cell_get_id public :: openmc_cell_get_fill public :: openmc_cell_set_fill + public :: openmc_cell_set_id public :: openmc_cell_set_temperature public :: openmc_energy_filter_get_bins public :: openmc_energy_filter_set_bins diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 63b1bbe95..b827c055c 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -570,6 +570,23 @@ contains end function openmc_cell_set_fill + function openmc_cell_set_id(index, id) result(err) bind(C) + ! Set the ID of a cell + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), value, intent(in) :: id + integer(C_INT) :: err + + if (index >= 1 .and. index <= n_cells) then + cells(index) % id = id + call cell_dict % set(id, index) + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in cells array is out of bounds.") + end if + end function openmc_cell_set_id + + function openmc_cell_set_temperature(index, T, instance) result(err) bind(C) ! Set the temperature of a cell integer(C_INT32_T), value, intent(in) :: index ! index in cells diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py new file mode 100644 index 000000000..08387fe18 --- /dev/null +++ b/tests/unit_tests/test_capi.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python + +from collections.abc import Mapping +import os + +import numpy as np +import pytest +import openmc +import openmc.capi + + +@pytest.fixture(scope='module') +def pincell_model(): + """Set up a model to test with and delete files when done""" + pincell = openmc.examples.pwr_pin_cell() + + # Add a tally + filter1 = openmc.MaterialFilter(pincell.materials) + filter2 = openmc.EnergyFilter([0.0, 1.0, 1.0e3, 20.0e6]) + mat_tally = openmc.Tally() + mat_tally.filters = [filter1, filter2] + mat_tally.nuclides = ['U235', 'U238'] + mat_tally.scores = ['total', 'elastic', '(n,gamma)'] + pincell.tallies.append(mat_tally) + + # Write XML files + pincell.export_to_xml() + + yield + + # Delete generated files + files = ['geometry.xml', 'materials.xml', 'settings.xml', 'tallies.xml', + 'statepoint.10.h5', 'summary.h5', 'test_sp.h5'] + for f in files: + if os.path.exists(f): + os.remove(f) + + +@pytest.fixture(scope='module') +def capi_init(pincell_model): + openmc.capi.init() + yield + openmc.capi.finalize() + + +@pytest.fixture(scope='module') +def capi_run(capi_init): + openmc.capi.run() + + +def test_cell_mapping(capi_init): + cells = openmc.capi.cells + assert isinstance(cells, Mapping) + assert len(cells) == 3 + for cell_id, cell in cells.items(): + assert isinstance(cell, openmc.capi.Cell) + assert cell_id == cell.id + + +def test_cell(capi_init): + cell = openmc.capi.cells[1] + assert isinstance(cell.fill, openmc.capi.Material) + cell.fill = openmc.capi.materials[1] + assert str(cell) == 'Cell[1]' + + +def test_new_cell(capi_init): + with pytest.raises(openmc.capi.AllocationError): + openmc.capi.Cell(1) + new_cell = openmc.capi.Cell() + new_cell_with_id = openmc.capi.Cell(10) + assert len(openmc.capi.cells) == 5 + + +def test_material_mapping(capi_init): + mats = openmc.capi.materials + assert isinstance(mats, Mapping) + assert len(mats) == 3 + for mat_id, mat in mats.items(): + assert isinstance(mat, openmc.capi.Material) + assert mat_id == mat.id + + +def test_material(capi_init): + m = openmc.capi.materials[3] + assert m.nuclides == ['H1', 'O16', 'B10', 'B11'] + + old_dens = m.densities + test_dens = [1.0e-1, 2.0e-1, 2.5e-1, 1.0e-3] + m.set_densities(m.nuclides, test_dens) + assert m.densities == pytest.approx(test_dens) + + rho = 2.25e-2 + m.set_density(rho) + assert sum(m.densities) == pytest.approx(rho) + + +def test_new_material(capi_init): + with pytest.raises(openmc.capi.AllocationError): + openmc.capi.Material(1) + new_mat = openmc.capi.Material() + new_mat_with_id = openmc.capi.Material(10) + assert len(openmc.capi.materials) == 5 + + +def test_nuclide_mapping(capi_init): + nucs = openmc.capi.nuclides + assert isinstance(nucs, Mapping) + assert len(nucs) == 12 + for name, nuc in nucs.items(): + assert isinstance(nuc, openmc.capi.Nuclide) + assert name == nuc.name + + +def test_load_nuclide(capi_init): + openmc.capi.load_nuclide('Pu239') + with pytest.raises(openmc.capi.DataError): + openmc.capi.load_nuclide('Pu3') + + +def test_settings(capi_init): + settings = openmc.capi.settings + assert settings.batches == 10 + settings.batches = 10 + assert settings.inactive == 5 + assert settings.generations_per_batch == 1 + assert settings.particles == 100 + assert settings.seed == 1 + settings.seed = 11 + + assert settings.run_mode == 'eigenvalue' + settings.run_mode = 'volume' + settings.run_mode = 'eigenvalue' + + +def test_tally_mapping(capi_init): + tallies = openmc.capi.tallies + assert isinstance(tallies, Mapping) + assert len(tallies) == 1 + for tally_id, tally in tallies.items(): + assert isinstance(tally, openmc.capi.Tally) + assert tally_id == tally.id + + +def test_tally(capi_init): + t = openmc.capi.tallies[1] + t.id = 1 + assert len(t.filters) == 2 + assert isinstance(t.filters[0], openmc.capi.MaterialFilter) + assert isinstance(t.filters[1], openmc.capi.EnergyFilter) + + # Create new filter and replace existing + with pytest.raises(openmc.capi.AllocationError): + openmc.capi.MaterialFilter(uid=1) + mats = openmc.capi.materials + f = openmc.capi.MaterialFilter([mats[2], mats[1]]) + t.filters = [f] + assert t.filters == [f] + + assert t.nuclides == ['U235', 'U238'] + with pytest.raises(openmc.capi.DataError): + t.nuclides = ['Zr2'] + t.nuclides = ['U234', 'Zr90'] + assert t.nuclides == ['U234', 'Zr90'] + + assert t.scores == ['total', '(n,elastic)', '(n,gamma)'] + new_scores = ['scatter', 'fission', 'nu-fission', '(n,2n)'] + t.scores = new_scores + assert t.scores == new_scores + + +def test_new_tally(capi_init): + with pytest.raises(openmc.capi.AllocationError): + openmc.capi.Material(1) + new_tally = openmc.capi.Tally() + new_tally.scores = ['flux'] + new_tally_with_id = openmc.capi.Tally(10) + new_tally_with_id.scores = ['flux'] + assert len(openmc.capi.tallies) == 3 + + +def test_tally_results(capi_run): + t = openmc.capi.tallies[1] + assert t.num_realizations == 5 + assert np.all(t.mean >= 0) + nonzero = (t.mean > 0.0) + assert np.all(t.std_dev[nonzero] >= 0) + assert np.all(t.ci_width()[nonzero] >= 1.95*t.std_dev[nonzero]) + + +def test_global_tallies(capi_run): + assert openmc.capi.num_realizations() == 5 + gt = openmc.capi.global_tallies() + for mean, std_dev in gt: + assert mean >= 0 + + +def test_statepoint(capi_run): + openmc.capi.statepoint_write('test_sp.h5') + assert os.path.exists('test_sp.h5') + + +def test_source_bank(capi_run): + source = openmc.capi.source_bank() + assert np.all(source['E'] > 0.0) + assert np.all(source['wgt'] == 1.0) + + +def test_keff(capi_run): + mean, std_dev = openmc.capi.keff() + assert 0.0 < mean < 2.5 + assert std_dev > 0.0 + + +def test_by_batch(capi_run): + openmc.capi.hard_reset() + openmc.capi.simulation_init() + for _ in openmc.capi.iter_batches(): + pass + assert openmc.capi.num_realizations() == 5 + + for i in range(3): + openmc.capi.next_batch() + assert openmc.capi.num_realizations() == 8 + openmc.capi.simulation_finalize() + + +def test_find_cell(capi_init): + cell, instance = openmc.capi.find_cell((0., 0., 0.)) + assert cell is openmc.capi.cells[1] + cell, instance = openmc.capi.find_cell((0.4, 0., 0.)) + assert cell is openmc.capi.cells[2] + with pytest.raises(openmc.capi.GeometryError): + openmc.capi.find_cell((100., 100., 100.)) + + +def test_find_material(capi_init): + mat = openmc.capi.find_material((0., 0., 0.)) + assert mat is openmc.capi.materials[1] + mat = openmc.capi.find_material((0.4, 0., 0.)) + assert mat is openmc.capi.materials[2] From 74a28519d01e079128e552ce749def9b5ecec525 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Dec 2017 10:04:14 +0700 Subject: [PATCH 033/282] Fix import for Python 2.7 --- tests/unit_tests/test_capi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 08387fe18..cde3d8724 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from collections.abc import Mapping +from collections import Mapping import os import numpy as np From 147bbefbe7f99fe5a3aa02cfd598de65cb400a04 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Dec 2017 10:36:28 +0700 Subject: [PATCH 034/282] Run pytest verbosely --- .travis.yml | 2 +- tests/unit_tests/test_element_wo.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 19bb96040..9ae0b2f5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,6 +62,6 @@ script: ./check_source.py; else ./run_tests.py -C $OPENMC_CONFIG -j 2 && - pytest --cov=../openmc unit_tests/; + pytest --cov=../openmc -v unit_tests/; fi - cd .. diff --git a/tests/unit_tests/test_element_wo.py b/tests/unit_tests/test_element_wo.py index 7b9487f73..2431f9e0e 100644 --- a/tests/unit_tests/test_element_wo.py +++ b/tests/unit_tests/test_element_wo.py @@ -3,7 +3,7 @@ import os import sys -import numpy as np +import pytest from openmc import Material from openmc.data import NATURAL_ABUNDANCE, atomic_mass @@ -31,11 +31,11 @@ def test_element_wo(): if nuc in ('H1', 'H2'): val = 2 * NATURAL_ABUNDANCE[nuc] * atomic_mass(nuc) / water_am - assert np.isclose(densities[nuc][1], val, rtol=1.e-8) + assert densities[nuc][1] == pytest.approx(val) if nuc == 'O16': val = (NATURAL_ABUNDANCE[nuc] + NATURAL_ABUNDANCE['O18']) \ * atomic_mass(nuc) / water_am - assert np.isclose(densities[nuc][1], val, rtol=1.e-8) + assert densities[nuc][1] == pytest.approx(val) if nuc == 'O17': val = NATURAL_ABUNDANCE[nuc] * atomic_mass(nuc) / water_am - assert np.isclose(densities[nuc][1], val, rtol=1.e-8) + assert densities[nuc][1] == pytest.approx(val) From 9fa391aa666e6e0ec28def517a10392c0388e0d4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Dec 2017 11:52:30 +0700 Subject: [PATCH 035/282] Make sure latest version of pytest is used --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 9ae0b2f5b..49b6ed2d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,7 @@ before_install: install: - if [[ $OPENMC_CONFIG != "check_source" ]]; then pip install numpy cython; + pip install --upgrade pytest; pip install -e .[test]; fi From 4b6402104e0cbccc9cfdd23aeec78e85f5e8d595 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 14 Dec 2017 16:19:26 -0500 Subject: [PATCH 036/282] Switch C++ RNG to Doxygen-style comments --- src/random_lcg.h | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/random_lcg.h b/src/random_lcg.h index ca8dfd826..04191254f 100644 --- a/src/random_lcg.h +++ b/src/random_lcg.h @@ -15,45 +15,57 @@ extern "C" const int STREAM_URR_PTABLE; extern "C" const int STREAM_VOLUME; //============================================================================== -// PRN generates a pseudo-random number using a linear congruential generator. +//! Generate a pseudo-random number using a linear congruential generator. +//! @return A random number between 0 and 1 //============================================================================== extern "C" double prn(); //============================================================================== -// FUTURE_PRN generates a pseudo-random number which is 'n' times ahead from the -// current seed. +//! Generate a random number which is 'n' times ahead from the current seed. +//! +//! The result of this function will be the same as the result from calling +//! `prn()` 'n' times. +//! @param n The number of RNG seeds to skip ahead by +//! @return A random number between 0 and 1 //============================================================================== extern "C" double future_prn(int64_t n); //============================================================================== -// SET_PARTICLE_SEED sets the seed to a unique value based on the ID of the -// particle. +//! Set the RNG seed to a unique value based on the ID of the particle. +//! @param id The particle ID //============================================================================== extern "C" void set_particle_seed(int64_t id); //============================================================================== -// ADVANCE_PRN_SEED advances the random number seed 'n' times from the current -// seed. +//! Advance the random number seed 'n' times from the current seed. +//! @param n The number of RNG seeds to skip ahead by //============================================================================== extern "C" void advance_prn_seed(int64_t n); //============================================================================== -// FUTURE_SEED advances the random number seed 'skip' times. This is usually -// used to skip a fixed number of random numbers (the stride) so that a given -// particle always has the same starting seed regardless of how many processors -// are used. +//! Advance a random number seed 'n' times. +//! +//! This is usually used to skip a fixed number of random numbers (the stride) +//! so that a given particle always has the same starting seed regardless of +//! how many processors are used. +//! @param n The number of RNG seeds to skip ahead by +//! @param seed The starting to seed to advance from //============================================================================== uint64_t future_seed(uint64_t n, uint64_t seed); //============================================================================== -// PRN_SET_STREAM changes the random number stream. If random numbers are needed -// in routines not used directly for tracking (e.g. physics), this allows the -// numbers to be generated without affecting reproducibility of the physics. +//! Switch the RNG to a different stream of random numbers. +//! +//! If random numbers are needed in routines not used directly for tracking +//! (e.g. physics), this allows the numbers to be generated without affecting +//! reproducibility of the physics. +//! @param n The RNG stream to switch to. Use the constants such as +//! `STREAM_TRACKING` and `STREAM_TALLIES` for this argument. //============================================================================== extern "C" void prn_set_stream(int n); @@ -62,6 +74,12 @@ extern "C" void prn_set_stream(int n); // API FUNCTIONS //============================================================================== +//============================================================================== +//! Set OpenMC's master seed. +//! @param new_seed The master seed. All other seeds will be derived from this +//! one. +//============================================================================== + extern "C" int openmc_set_seed(int64_t new_seed); #endif // RANDOM_LCG_H From 8e1b8d6264266f72573c195e8370dc32cb8a3893 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 15 Dec 2017 11:05:46 +0700 Subject: [PATCH 037/282] Make capi.keff() usable during inactive and active batches --- openmc/capi/core.py | 14 +++++++++++--- openmc/capi/error.py | 20 ++++++++++---------- openmc/capi/tally.py | 13 +++++++++---- src/simulation_header.F90 | 6 ++++-- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/openmc/capi/core.py b/openmc/capi/core.py index c25146fb5..ced939043 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -166,9 +166,17 @@ def keff(): Mean k-eigenvalue and standard deviation of the mean """ - k = (c_double*2)() - _dll.openmc_get_keff(k) - return tuple(k) + n = openmc.capi.num_realizations() + if n > 3: + # Use the combined estimator if there are enough realizations + k = (c_double*2)() + _dll.openmc_get_keff(k) + 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 + return (mean, std_dev) def next_batch(): diff --git a/openmc/capi/error.py b/openmc/capi/error.py index c312987bc..a11d6ea87 100644 --- a/openmc/capi/error.py +++ b/openmc/capi/error.py @@ -4,39 +4,39 @@ from warnings import warn from . import _dll -class Error(Exception): +class OpenMCError(Exception): """Root exception class for OpenMC.""" -class GeometryError(Error): +class GeometryError(OpenMCError): """Geometry-related error""" -class InvalidIDError(Error): +class InvalidIDError(OpenMCError): """Use of an ID that is invalid.""" -class AllocationError(Error): +class AllocationError(OpenMCError): """Error related to memory allocation.""" -class OutOfBoundsError(Error): +class OutOfBoundsError(OpenMCError): """Index in array out of bounds.""" -class DataError(Error): +class DataError(OpenMCError): """Error relating to nuclear data.""" -class PhysicsError(Error): +class PhysicsError(OpenMCError): """Error relating to performing physics.""" -class InvalidArgumentError(Error): +class InvalidArgumentError(OpenMCError): """Argument passed was invalid.""" -class InvalidTypeError(Error): +class InvalidTypeError(OpenMCError): """Tried to perform an operation on the wrong type.""" @@ -71,4 +71,4 @@ def _error_handler(err, func, args): elif err == errcode('e_warning'): warn(msg) elif err < 0: - raise Exception("Unknown error encountered (code {}).".format(err)) + raise OpenMCError("Unknown error encountered (code {}).".format(err)) diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index 4a76de41d..751f0e603 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -92,12 +92,17 @@ def global_tallies(): n = num_realizations() # Determine mean - mean = sum_ / n + if n > 0: + mean = sum_ / n + else: + mean = sum_.copy() # Determine standard deviation nonzero = np.abs(mean) > 0 - stdev = np.zeros_like(mean) - stdev[nonzero] = np.sqrt((sum_sq[nonzero]/n - mean[nonzero]**2)/(n - 1)) + stdev = np.empty_like(mean) + stdev.fill(np.inf) + if n > 1: + stdev[nonzero] = np.sqrt((sum_sq[nonzero]/n - mean[nonzero]**2)/(n - 1)) return list(zip(mean, stdev)) @@ -265,7 +270,7 @@ class Tally(_FortranObjectWithID): def std_dev(self): results = self.results std_dev = np.empty(results.shape[:2]) - std_dev.fill(np.nan) + std_dev.fill(np.inf) n = self.num_realizations if n > 1: diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index 9719cc72e..62c66e5f9 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -1,5 +1,7 @@ module simulation_header + use, intrinsic :: ISO_C_BINDING + use bank_header use constants use settings, only: gen_per_batch @@ -33,8 +35,8 @@ module simulation_header ! Temporary k-effective values type(VectorReal) :: k_generation ! single-generation estimates of k - real(8) :: keff = ONE ! average k over active batches - real(8) :: keff_std ! standard deviation of average k + real(C_DOUBLE), bind(C) :: keff = ONE ! average k over active batches + real(C_DOUBLE), bind(C) :: keff_std ! standard deviation of average k real(8) :: k_col_abs = ZERO ! sum over batches of k_collision * k_absorption real(8) :: k_col_tra = ZERO ! sum over batches of k_collision * k_tracklength real(8) :: k_abs_tra = ZERO ! sum over batches of k_absorption * k_tracklength From 08bf32e1ae3ec0b8384dc809a77250bf4d9097b2 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 15 Dec 2017 11:38:28 +0700 Subject: [PATCH 038/282] Throw exception if next_batch() is called before simulation is initialized --- openmc/capi/core.py | 10 +++++++--- src/simulation.F90 | 18 ++++++++++++++++++ src/simulation_header.F90 | 6 +++++- tests/unit_tests/test_capi.py | 17 ++++++++++------- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/openmc/capi/core.py b/openmc/capi/core.py index ced939043..415fa2e93 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -7,7 +7,7 @@ import numpy as np from numpy.ctypeslib import as_array from . import _dll -from .error import _error_handler +from .error import _error_handler, AllocationError import openmc.capi @@ -147,7 +147,7 @@ def iter_batches(): """ while True: # Run next batch - retval = _dll.openmc_next_batch() + retval = next_batch() # Provide opportunity for user to perform action between batches yield @@ -181,7 +181,11 @@ def keff(): def next_batch(): """Run next batch.""" - return _dll.openmc_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 def plot_geometry(): diff --git a/src/simulation.F90 b/src/simulation.F90 index e3eba0d51..5eacbd40b 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -73,6 +73,12 @@ contains type(Particle) :: p integer(8) :: i_work + ! Make sure simulation has been initialized + if (.not. simulation_initialized) then + retval = -3 + return + end if + call initialize_batch() ! Handle restart runs @@ -396,6 +402,9 @@ contains subroutine openmc_simulation_init() bind(C) + ! Skip if simulation has already been initialized + if (simulation_initialized) return + ! Set up tally procedure pointers call init_tally_routines() @@ -438,6 +447,9 @@ contains ! Reset current batch current_batch = 0 + ! Set flag indicating initialization is done + simulation_initialized = .true. + end subroutine openmc_simulation_init !=============================================================================== @@ -455,6 +467,9 @@ contains real(8) :: tempr(3) ! temporary array for communication #endif + ! Skip if simulation was never run + if (.not. simulation_initialized) return + ! Stop active batch timer call time_active % stop() @@ -511,6 +526,9 @@ contains if (check_overlaps) call print_overlap_check() end if + ! Reset initialization flag + simulation_initialized = .false. + end subroutine openmc_simulation_finalize !=============================================================================== diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index 62c66e5f9..5041f0793 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -18,11 +18,12 @@ module simulation_header real(8) :: log_spacing ! spacing on logarithmic grid ! ============================================================================ - ! EIGENVALUE SIMULATION VARIABLES + ! SIMULATION VARIABLES integer :: current_batch ! current batch integer :: current_gen ! current generation within a batch integer :: total_gen = 0 ! total number of generations simulated + logical(C_BOOL), bind(C) :: simulation_initialized = .false. ! ============================================================================ ! TALLY PRECISION TRIGGER VARIABLES @@ -33,6 +34,9 @@ module simulation_header integer(8), allocatable :: work_index(:) ! starting index in source bank for each process integer(8) :: current_work ! index in source bank of current history simulated + ! ============================================================================ + ! K-EIGENVALUE SIMULATION VARIABLES + ! Temporary k-effective values type(VectorReal) :: k_generation ! single-generation estimates of k real(C_DOUBLE), bind(C) :: keff = ONE ! average k over active batches diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index cde3d8724..ba9ac5c9e 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -206,17 +206,20 @@ def test_source_bank(capi_run): assert np.all(source['wgt'] == 1.0) -def test_keff(capi_run): - mean, std_dev = openmc.capi.keff() - assert 0.0 < mean < 2.5 - assert std_dev > 0.0 - - def test_by_batch(capi_run): openmc.capi.hard_reset() + + # Running next batch before simulation is initialized should raise an + # exception + with pytest.raises(openmc.capi.AllocationError): + openmc.capi.next_batch() + openmc.capi.simulation_init() for _ in openmc.capi.iter_batches(): - pass + # Make sure we can get k-effective during inactive/active batches + mean, std_dev = openmc.capi.keff() + assert 0.0 < mean < 2.5 + assert std_dev > 0.0 assert openmc.capi.num_realizations() == 5 for i in range(3): From 53434692fab05ee5f4148c9051e520df3cd834a8 Mon Sep 17 00:00:00 2001 From: Brody Bassett Date: Sat, 9 Dec 2017 18:04:02 -0500 Subject: [PATCH 039/282] Removed reversal of energy bins in multigroup mode to agree with energy bin search in source. --- src/input_xml.F90 | 5 ----- src/tallies/tally_filter_energy.F90 | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 71ebe3ae6..b7bc88e25 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -4172,7 +4172,6 @@ contains integer :: n_libraries logical :: file_exists ! does mgxs.h5 exist? integer(HID_T) :: file_id - real(8), allocatable :: rev_energy_bins(:) character(len=MAX_WORD_LEN), allocatable :: names(:) ! Check if MGXS Library exists @@ -4202,7 +4201,6 @@ contains num_delayed_groups = 0 end if - allocate(rev_energy_bins(num_energy_groups + 1)) allocate(energy_bins(num_energy_groups + 1)) if (attribute_exists(file_id, "group structure")) then @@ -4212,9 +4210,6 @@ contains call fatal_error("'group structure' attribute must exist!") end if - ! First reverse the order of energy_groups - energy_bins = energy_bins(num_energy_groups + 1:1:-1) - allocate(energy_bin_avg(num_energy_groups)) do i = 1, num_energy_groups energy_bin_avg(i) = HALF * (energy_bins(i) + energy_bins(i + 1)) diff --git a/src/tallies/tally_filter_energy.F90 b/src/tallies/tally_filter_energy.F90 index 7f9655280..b2498d4b5 100644 --- a/src/tallies/tally_filter_energy.F90 +++ b/src/tallies/tally_filter_energy.F90 @@ -75,7 +75,7 @@ contains ! ordering of the library and tallying systems). if (.not. run_CE) then if (n == num_energy_groups + 1) then - if (all(this % bins == energy_bins(num_energy_groups + 1:1:-1))) & + if (all(this % bins == energy_bins)) & then this % matches_transport_groups = .true. end if From a9ec1af39eedd0994da54a7673c71c12c2f31387 Mon Sep 17 00:00:00 2001 From: Brody Bassett Date: Sat, 9 Dec 2017 20:14:03 -0500 Subject: [PATCH 040/282] Updated results for test_mgxs_library_ce_to_mg.py. --- tests/test_mgxs_library_ce_to_mg/results_true.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mgxs_library_ce_to_mg/results_true.dat b/tests/test_mgxs_library_ce_to_mg/results_true.dat index 0fd5b4827..332bf1237 100644 --- a/tests/test_mgxs_library_ce_to_mg/results_true.dat +++ b/tests/test_mgxs_library_ce_to_mg/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.133362E+00 1.746523E-02 +1.097605E+00 4.580419E-02 From fae2888b26b04258dbb8b3ba812c9c6bf7736147 Mon Sep 17 00:00:00 2001 From: Brody Bassett Date: Mon, 11 Dec 2017 04:10:32 -0500 Subject: [PATCH 041/282] Fixed issue in source.F90 with fixing energy bins. --- src/input_xml.F90 | 5 +++++ src/mgxs_header.F90 | 5 ++++- src/source.F90 | 11 ++++++----- src/tallies/tally_filter_energy.F90 | 4 ++-- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index b7bc88e25..1eb1a0aee 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -4201,6 +4201,7 @@ contains num_delayed_groups = 0 end if + allocate(rev_energy_bins(num_energy_groups + 1)) allocate(energy_bins(num_energy_groups + 1)) if (attribute_exists(file_id, "group structure")) then @@ -4210,6 +4211,10 @@ contains call fatal_error("'group structure' attribute must exist!") end if + ! First reverse the order of energy_groups + rev_energy_bins = energy_bins + energy_bins = energy_bins(num_energy_groups + 1:1:-1) + allocate(energy_bin_avg(num_energy_groups)) do i = 1, num_energy_groups energy_bin_avg(i) = HALF * (energy_bins(i) + energy_bins(i + 1)) diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index b28239724..46ad181d3 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -221,12 +221,15 @@ module mgxs_header ! Number of delayed groups integer :: num_delayed_groups - ! Energy group structure + ! Energy group structure with decreasing energy real(8), allocatable :: energy_bins(:) ! Midpoint of the energy group structure real(8), allocatable :: energy_bin_avg(:) + ! Energy group structure with increasing energy + real(8), allocatable :: rev_energy_bins(:) + contains !=============================================================================== diff --git a/src/source.F90 b/src/source.F90 index 0689cb403..5a58a6a85 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -15,7 +15,7 @@ module source use hdf5_interface, only: file_create, file_open, file_close, read_dataset use math use message_passing, only: rank - use mgxs_header, only: energy_bins, num_energy_groups + use mgxs_header, only: rev_energy_bins, num_energy_groups use output, only: write_message use particle_header, only: Particle use random_lcg, only: prn, set_particle_seed, prn_set_stream @@ -129,13 +129,14 @@ contains ! If running in MG, convert site % E to group if (.not. run_CE) then - if (site % E <= energy_bins(1)) then - site % E = real(1, 8) - else if (site % E > energy_bins(num_energy_groups + 1)) then + if (site % E <= rev_energy_bins(1)) then site % E = real(num_energy_groups, 8) + else if (site % E > rev_energy_bins(num_energy_groups + 1)) then + site % E = real(1, 8) else - site % E = real(binary_search(energy_bins, num_energy_groups + 1, & + site % E = real(binary_search(rev_energy_bins, num_energy_groups + 1, & site % E), 8) + site % E = num_energy_groups + 1 - site % E end if end if diff --git a/src/tallies/tally_filter_energy.F90 b/src/tallies/tally_filter_energy.F90 index b2498d4b5..2a247cf35 100644 --- a/src/tallies/tally_filter_energy.F90 +++ b/src/tallies/tally_filter_energy.F90 @@ -8,7 +8,7 @@ module tally_filter_energy use constants use error use hdf5_interface - use mgxs_header, only: num_energy_groups, energy_bins + use mgxs_header, only: num_energy_groups, rev_energy_bins use particle_header, only: Particle use settings, only: run_CE use string, only: to_str @@ -75,7 +75,7 @@ contains ! ordering of the library and tallying systems). if (.not. run_CE) then if (n == num_energy_groups + 1) then - if (all(this % bins == energy_bins)) & + if (all(this % bins == rev_energy_bins)) & then this % matches_transport_groups = .true. end if From b0fcb11136e439385a7a59a29db11c7265646118 Mon Sep 17 00:00:00 2001 From: Brody Bassett Date: Mon, 11 Dec 2017 04:16:29 -0500 Subject: [PATCH 042/282] Removed whitespace. --- src/mgxs_header.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index 46ad181d3..a99939f4c 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -229,7 +229,7 @@ module mgxs_header ! Energy group structure with increasing energy real(8), allocatable :: rev_energy_bins(:) - + contains !=============================================================================== From 2891fdae468c60c9630df50b104cad3ede99de2a Mon Sep 17 00:00:00 2001 From: Brody Bassett Date: Mon, 11 Dec 2017 04:22:51 -0500 Subject: [PATCH 043/282] Reverted changes to test_mgxs_library_ce_to_mg.py. --- tests/test_mgxs_library_ce_to_mg/results_true.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mgxs_library_ce_to_mg/results_true.dat b/tests/test_mgxs_library_ce_to_mg/results_true.dat index 332bf1237..0fd5b4827 100644 --- a/tests/test_mgxs_library_ce_to_mg/results_true.dat +++ b/tests/test_mgxs_library_ce_to_mg/results_true.dat @@ -1,2 +1,2 @@ k-combined: -1.097605E+00 4.580419E-02 +1.133362E+00 1.746523E-02 From 8e6b61711644effe17af82d58ddb196e748be798 Mon Sep 17 00:00:00 2001 From: Brody Bassett Date: Fri, 15 Dec 2017 02:19:50 -0500 Subject: [PATCH 044/282] Added a check for minimum energy in the source sampling. Set multigroup min and max energies and removed check for these after samping the source. --- src/input_xml.F90 | 5 +++++ src/source.F90 | 12 +++--------- src/source_header.F90 | 11 +++++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 1eb1a0aee..72aaa6b7b 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -4215,11 +4215,16 @@ contains rev_energy_bins = energy_bins energy_bins = energy_bins(num_energy_groups + 1:1:-1) + ! Get the midpoint of the energy groups allocate(energy_bin_avg(num_energy_groups)) do i = 1, num_energy_groups energy_bin_avg(i) = HALF * (energy_bins(i) + energy_bins(i + 1)) end do + ! Get the minimum and maximum energies + energy_min_neutron = energy_bins(num_energy_groups + 1) + energy_max_neutron = energy_bins(1) + ! Get the datasets present in the library call get_groups(file_id, names) n_libraries = size(names) diff --git a/src/source.F90 b/src/source.F90 index 5a58a6a85..557120e97 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -129,15 +129,9 @@ contains ! If running in MG, convert site % E to group if (.not. run_CE) then - if (site % E <= rev_energy_bins(1)) then - site % E = real(num_energy_groups, 8) - else if (site % E > rev_energy_bins(num_energy_groups + 1)) then - site % E = real(1, 8) - else - site % E = real(binary_search(rev_energy_bins, num_energy_groups + 1, & - site % E), 8) - site % E = num_energy_groups + 1 - site % E - end if + site % E = real(binary_search(rev_energy_bins, num_energy_groups + 1, & + site % E), 8) + site % E = num_energy_groups + 1 - site % E end if ! Set the random number generator back to the tracking stream. diff --git a/src/source_header.F90 b/src/source_header.F90 index a60696996..fca05d816 100644 --- a/src/source_header.F90 +++ b/src/source_header.F90 @@ -9,7 +9,7 @@ module source_header use error use geometry, only: find_cell use material_header, only: materials - use nuclide_header, only: energy_max_neutron + use nuclide_header, only: energy_min_neutron, energy_max_neutron use particle_header, only: Particle use string, only: to_lower use xml_interface @@ -275,9 +275,12 @@ contains ! Check for monoenergetic source above maximum neutron energy select type (energy => this % energy) type is (Discrete) - if (any(energy % x >= energy_max_neutron)) then + if (any(energy % x > energy_max_neutron)) then call fatal_error("Source energy above range of energies of at least & &one cross section table") + else if (any(energy % x < energy_min_neutron)) then + call fatal_error("Source energy below range of energies of at least & + &one cross section table") end if end select @@ -285,8 +288,8 @@ contains ! Sample energy spectrum site % E = this % energy % sample() - ! resample if energy is greater than maximum neutron energy - if (site % E < energy_max_neutron) exit + ! Resample if energy falls outside minimum or maximum neutron energy + if (site % E < energy_max_neutron .and. site % E > energy_min_neutron) exit end do ! Set delayed group From 9b867f98635f853c4e5515eea4fa32be8b91d990 Mon Sep 17 00:00:00 2001 From: Brody Bassett Date: Fri, 15 Dec 2017 04:17:40 -0500 Subject: [PATCH 045/282] Removed whitespace. --- src/input_xml.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 72aaa6b7b..b7c27e308 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -4224,7 +4224,7 @@ contains ! Get the minimum and maximum energies energy_min_neutron = energy_bins(num_energy_groups + 1) energy_max_neutron = energy_bins(1) - + ! Get the datasets present in the library call get_groups(file_id, names) n_libraries = size(names) From 61bdb5b322f6dab8c1b35b515a5f945bd19935df Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 10:00:55 -0600 Subject: [PATCH 046/282] Remove __eq__ and __hash__ on geometry objects --- openmc/cell.py | 26 -------------------------- openmc/lattice.py | 38 -------------------------------------- openmc/surface.py | 3 --- openmc/universe.py | 18 ------------------ 4 files changed, 85 deletions(-) diff --git a/openmc/cell.py b/openmc/cell.py index d962c2725..5975d7f70 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -108,32 +108,6 @@ class Cell(IDManagerMixin): else: return point in self.region - def __eq__(self, other): - if not isinstance(other, Cell): - return False - elif self.id != other.id: - return False - elif self.name != other.name: - return False - elif self.fill != other.fill: - return False - elif self.region != other.region: - return False - elif self.rotation != other.rotation: - return False - elif self.temperature != other.temperature: - return False - elif self.translation != other.translation: - return False - else: - return True - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash(repr(self)) - def __repr__(self): string = 'Cell\n' string += '{: <16}=\t{}\n'.format('\tID', self.id) diff --git a/openmc/lattice.py b/openmc/lattice.py index 3d078bd01..79f0d43f3 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -517,24 +517,6 @@ class RectLattice(Lattice): # Initialize Lattice class attributes self._lower_left = None - def __eq__(self, other): - if not isinstance(other, RectLattice): - return False - elif not super(RectLattice, self).__eq__(other): - return False - elif self.shape != other.shape: - return False - elif np.any(self.lower_left != other.lower_left): - return False - else: - return True - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash(repr(self)) - def __repr__(self): string = 'RectLattice\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) @@ -864,26 +846,6 @@ class HexLattice(Lattice): self._num_axial = None self._center = None - def __eq__(self, other): - if not isinstance(other, HexLattice): - return False - elif not super(HexLattice, self).__eq__(other): - return False - elif self.num_rings != other.num_rings: - return False - elif self.num_axial != other.num_axial: - return False - elif self.center != other.center: - return False - else: - return True - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash(repr(self)) - def __repr__(self): string = 'HexLattice\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) diff --git a/openmc/surface.py b/openmc/surface.py index 961ac4d45..6c29ed458 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -79,9 +79,6 @@ class Surface(IDManagerMixin): def __pos__(self): return Halfspace(self, '+') - def __hash__(self): - return hash(repr(self)) - def __repr__(self): string = 'Surface\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) diff --git a/openmc/universe.py b/openmc/universe.py index cb6574f98..7d9f5a29f 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -63,24 +63,6 @@ class Universe(IDManagerMixin): if cells is not None: self.add_cells(cells) - def __eq__(self, other): - if not isinstance(other, Universe): - return False - elif self.id != other.id: - return False - elif self.name != other.name: - return False - elif dict.__ne__(self.cells, other.cells): - return False - else: - return True - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash(repr(self)) - def __repr__(self): string = 'Universe\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) From e2dfb5b5fb94cd9624320cf44811ecb2198a87f4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 10:42:21 -0600 Subject: [PATCH 047/282] Remove more unnecessary eq/hash --- openmc/macroscopic.py | 17 --------------- openmc/tallies.py | 44 ++++++++++++++------------------------ openmc/tally_derivative.py | 3 --- 3 files changed, 16 insertions(+), 48 deletions(-) diff --git a/openmc/macroscopic.py b/openmc/macroscopic.py index f2521c4ac..3a46b5225 100644 --- a/openmc/macroscopic.py +++ b/openmc/macroscopic.py @@ -25,23 +25,6 @@ class Macroscopic(object): # Set the Macroscopic class attributes self.name = name - def __eq__(self, other): - if isinstance(other, Macroscopic): - if self.name != other.name: - return False - else: - return True - elif isinstance(other, string_types) and other == self.name: - return True - else: - return False - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash((self._name)) - def __repr__(self): string = 'Macroscopic - {0}\n'.format(self._name) return string diff --git a/openmc/tallies.py b/openmc/tallies.py index 3c91da8c9..de9a6657d 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1971,10 +1971,8 @@ class Tally(IDManagerMixin): """ # Get the set of filters that each tally is missing - other_missing_filters = \ - set(self.filters).difference(set(other.filters)) - self_missing_filters = \ - set(other.filters).difference(set(self.filters)) + other_missing_filters = set(self.filters) - set(other.filters) + self_missing_filters = set(other.filters) - set(self.filters) # Add filters present in self but not in other to other for other_filter in other_missing_filters: @@ -2001,14 +1999,10 @@ class Tally(IDManagerMixin): # Repeat and tile the data by nuclide in preparation for performing # the tensor product across nuclides. if nuclide_product == 'tensor': - self._mean = \ - np.repeat(self.mean, other.num_nuclides, axis=1) - self._std_dev = \ - np.repeat(self.std_dev, other.num_nuclides, axis=1) - other._mean = \ - np.tile(other.mean, (1, self.num_nuclides, 1)) - other._std_dev = \ - np.tile(other.std_dev, (1, self.num_nuclides, 1)) + self._mean = np.repeat(self.mean, other.num_nuclides, axis=1) + self._std_dev = np.repeat(self.std_dev, other.num_nuclides, axis=1) + other._mean = np.tile(other.mean, (1, self.num_nuclides, 1)) + other._std_dev = np.tile(other.std_dev, (1, self.num_nuclides, 1)) # Add nuclides to each tally such that each tally contains the complete # set of nuclides necessary to perform an entrywise product. New @@ -2016,25 +2010,21 @@ class Tally(IDManagerMixin): else: # Get the set of nuclides that each tally is missing - other_missing_nuclides = \ - set(self.nuclides).difference(set(other.nuclides)) - self_missing_nuclides = \ - set(other.nuclides).difference(set(self.nuclides)) + other_missing_nuclides = set(self.nuclides) - set(other.nuclides) + self_missing_nuclides = set(other.nuclides) - set(self.nuclides) # Add nuclides present in self but not in other to other for nuclide in other_missing_nuclides: - other._mean = \ - np.insert(other.mean, other.num_nuclides, 0, axis=1) - other._std_dev = \ - np.insert(other.std_dev, other.num_nuclides, 0, axis=1) + other._mean = np.insert(other.mean, other.num_nuclides, 0, axis=1) + other._std_dev = np.insert(other.std_dev, other.num_nuclides, 0, + axis=1) other.nuclides.append(nuclide) # Add nuclides present in other but not in self to self for nuclide in self_missing_nuclides: - self._mean = \ - np.insert(self.mean, self.num_nuclides, 0, axis=1) - self._std_dev = \ - np.insert(self.std_dev, self.num_nuclides, 0, axis=1) + self._mean = np.insert(self.mean, self.num_nuclides, 0, axis=1) + self._std_dev = np.insert(self.std_dev, self.num_nuclides, 0, + axis=1) self.nuclides.append(nuclide) # Align other nuclides with self nuclides @@ -2059,10 +2049,8 @@ class Tally(IDManagerMixin): else: # Get the set of scores that each tally is missing - other_missing_scores = \ - set(self.scores).difference(set(other.scores)) - self_missing_scores = \ - set(other.scores).difference(set(self.scores)) + other_missing_scores = set(self.scores) - set(other.scores) + self_missing_scores = set(other.scores) - set(self.scores) # Add scores present in self but not in other to other for score in other_missing_scores: diff --git a/openmc/tally_derivative.py b/openmc/tally_derivative.py index d6c2fab02..facc56f44 100644 --- a/openmc/tally_derivative.py +++ b/openmc/tally_derivative.py @@ -51,9 +51,6 @@ class TallyDerivative(EqualityMixin, IDManagerMixin): self.material = material self.nuclide = nuclide - def __hash__(self): - return hash(repr(self)) - def __repr__(self): string = 'Tally Derivative\n' string += '{: <16}=\t{}\n'.format('\tID', self.id) From b771fb7ae3bc54cc2c7e4083b71ae63595a1a88f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 11:05:52 -0600 Subject: [PATCH 048/282] Remove eq/hash on Mesh and Tally --- openmc/mesh.py | 5 +---- openmc/tallies.py | 43 ------------------------------------------- 2 files changed, 1 insertion(+), 47 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index 76a33d8e2..b315b2628 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -11,7 +11,7 @@ import openmc from openmc.mixin import EqualityMixin, IDManagerMixin -class Mesh(EqualityMixin, IDManagerMixin): +class Mesh(IDManagerMixin): """A structured Cartesian mesh in one, two, or three dimensions Parameters @@ -124,9 +124,6 @@ class Mesh(EqualityMixin, IDManagerMixin): cv.check_length('mesh width', width, 1, 3) self._width = width - def __hash__(self): - return hash(repr(self)) - def __repr__(self): string = 'Mesh\n' string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) diff --git a/openmc/tallies.py b/openmc/tallies.py index de9a6657d..b358a14bc 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -129,49 +129,6 @@ class Tally(IDManagerMixin): self._sp_filename = None self._results_read = False - def __eq__(self, other): - if not isinstance(other, Tally): - return False - - # Check all filters - if len(self.filters) != len(other.filters): - return False - - for self_filter in self.filters: - if self_filter not in other.filters: - return False - - # Check all nuclides - if len(self.nuclides) != len(other.nuclides): - return False - - for nuclide in self.nuclides: - if nuclide not in other.nuclides: - return False - - # Check derivatives - if self.derivative != other.derivative: - return False - - # Check all scores - if len(self.scores) != len(other.scores): - return False - - for score in self.scores: - if score not in other.scores: - return False - - if self.estimator != other.estimator: - return False - - return True - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash(repr(self)) - def __repr__(self): string = 'Tally\n' string += '{: <16}=\t{}\n'.format('\tID', self.id) From 3c9e259553e8e8cec08f3076cbdc55c0094c3b74 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 12:20:38 -0600 Subject: [PATCH 049/282] Remove __eq__ and __hash__ on Material --- openmc/material.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 3800d89c6..f79f7a4a1 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -115,33 +115,6 @@ class Material(IDManagerMixin): # If specified, this file will be used instead of composition values self._distrib_otf_file = None - def __eq__(self, other): - if not isinstance(other, Material): - return False - elif self.id != other.id: - return False - elif self.name != other.name: - return False - # FIXME: We cannot compare densities since OpenMC outputs densities - # in atom/b-cm in summary.h5 irregardless of input units, and we - # cannot compute the sum percent in Python since we lack AWR - #elif self.density != other.density: - # return False - #elif self._nuclides != other._nuclides: - # return False - #elif self._elements != other._elements: - # return False - elif self._sab != other._sab: - return False - else: - return True - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash(repr(self)) - def __repr__(self): string = 'Material\n' string += '{: <16}=\t{}\n'.format('\tID', self._id) From 4d162335f98ff3d9fc4010aecc7820fc78f97286 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 14:10:05 -0600 Subject: [PATCH 050/282] Remove __eq__ and __hash__ on Element --- openmc/element.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/openmc/element.py b/openmc/element.py index fc019a5d3..48c4e6643 100644 --- a/openmc/element.py +++ b/openmc/element.py @@ -38,29 +38,6 @@ class Element(object): # Set class attributes self.name = name - def __eq__(self, other): - if isinstance(other, Element): - if self.name != other.name: - return False - else: - return True - elif isinstance(other, string_types) and other == self.name: - return True - else: - return False - - def __ne__(self, other): - return not self == other - - def __gt__(self, other): - return repr(self) > repr(other) - - def __lt__(self, other): - return not self > other - - def __hash__(self): - return hash(repr(self)) - def __repr__(self): string = 'Element - {0}\n'.format(self._name) if self.scattering is not None: From f722d57ca352cec14ac83379522f2d265ed22587 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 17:36:59 -0600 Subject: [PATCH 051/282] Change input format for isotropic-in-lab scattering --- docs/source/io_formats/materials.rst | 23 ++- openmc/element.py | 25 +-- openmc/material.py | 32 ++-- openmc/nuclide.py | 22 +-- src/input_xml.F90 | 59 +++---- src/relaxng/materials.rnc | 4 +- src/relaxng/materials.rng | 21 +-- tests/test_iso_in_lab/inputs_true.dat | 214 ++++++++++++++------------ 8 files changed, 187 insertions(+), 213 deletions(-) diff --git a/docs/source/io_formats/materials.rst b/docs/source/io_formats/materials.rst index 5f0aa3a5e..7875eb330 100644 --- a/docs/source/io_formats/materials.rst +++ b/docs/source/io_formats/materials.rst @@ -92,20 +92,8 @@ Each ``material`` element can have the following attributes or sub-elements: .. note:: If one nuclide is specified in atom percent, all others must also be given in atom percent. The same applies for weight percentages. - An optional attribute/sub-element for each nuclide is ``scattering``. This - attribute may be set to "data" to use the scattering laws specified by the - cross section library (default). Alternatively, when set to "iso-in-lab", - the scattering laws are used to sample the outgoing energy but an - isotropic-in-lab distribution is used to sample the outgoing angle at each - scattering interaction. The ``scattering`` attribute may be most useful - when using OpenMC to compute multi-group cross-sections for deterministic - transport codes and to quantify the effects of anisotropic scattering. - *Default*: None - .. note:: The ``scattering`` attribute/sub-element is not used in the - multi-group :ref:`energy_mode`. - :sab: Associates an S(a,b) table with the material. This element has an attribute/sub-element called ``name``. The ``name`` attribute @@ -119,6 +107,17 @@ Each ``material`` element can have the following attributes or sub-elements: .. note:: This element is not used in the multi-group :ref:`energy_mode`. + :isotropic: + The ``isotropic`` element indicates a list of nuclides for which elastic + scattering should be treated as though it were isotropic in the laboratory + system. This element may be most useful when using OpenMC to compute + multi-group cross-sections for deterministic transport codes and to quantify + the effects of anisotropic scattering. + + *Default*: No nuclides are treated as have isotropic elastic scattering. + + .. note:: This element is not used in the multi-group :ref:`energy_mode`. + :macroscopic: The ``macroscopic`` element is similar to the ``nuclide`` element, but, recognizes that some multi-group libraries may be providing material diff --git a/openmc/element.py b/openmc/element.py index 48c4e6643..8543500e9 100644 --- a/openmc/element.py +++ b/openmc/element.py @@ -25,51 +25,29 @@ class Element(object): ---------- name : str Chemical symbol of the element, e.g. Pu - scattering : {'data', 'iso-in-lab', None} - The type of angular scattering distribution to use """ def __init__(self, name=''): # Initialize class attributes self._name = '' - self._scattering = None # Set class attributes self.name = name def __repr__(self): - string = 'Element - {0}\n'.format(self._name) - if self.scattering is not None: - string += '{0: <16}{1}{2}\n'.format('\tscattering', '=\t', - self.scattering) - - return string + return 'Element - {0}\n'.format(self._name) @property def name(self): return self._name - @property - def scattering(self): - return self._scattering - @name.setter def name(self, name): cv.check_type('element name', name, string_types) cv.check_length('element name', name, 1, 2) self._name = name - @scattering.setter - def scattering(self, scattering): - - if not scattering in ['data', 'iso-in-lab', None]: - msg = 'Unable to set scattering for Element to {0} which ' \ - 'is not "data", "iso-in-lab", or None'.format(scattering) - raise ValueError(msg) - - self._scattering = scattering - def expand(self, percent, percent_type, enrichment=None, cross_sections=None): """Expand natural element into its naturally-occurring isotopes. @@ -239,7 +217,6 @@ class Element(object): isotopes = [] for nuclide, abundance in abundances.items(): nuc = openmc.Nuclide(nuclide) - nuc.scattering = self.scattering isotopes.append((nuc, percent * abundance, percent_type)) return isotopes diff --git a/openmc/material.py b/openmc/material.py index f79f7a4a1..00f5981b8 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -63,6 +63,9 @@ class Material(IDManagerMixin): List in which each item is a 3-tuple consisting of an :class:`openmc.Nuclide` instance, the percent density, and the percent type ('ao' or 'wo'). + isotropic : list of str + Nuclides for which elastic scattering should be treated as though it + were isotropic in the laboratory system. average_molar_mass : float The average molar mass of nuclides in the material in units of grams per mol. For example, UO2 with 3 nuclides will have an average molar mass @@ -95,6 +98,7 @@ class Material(IDManagerMixin): self._num_instances = None self._volume = None self._atoms = {} + self._isotropic = [] # A list of tuples (nuclide, percent, percent type) self._nuclides = [] @@ -194,6 +198,10 @@ class Material(IDManagerMixin): def nuclides(self): return self._nuclides + @property + def isotropic(self): + return self._isotropic + @property def convert_to_distrib_comps(self): return self._convert_to_distrib_comps @@ -254,6 +262,12 @@ class Material(IDManagerMixin): cv.check_type('material volume', volume, Real) self._volume = volume + @isotropic.setter + def isotropic(self, isotropic): + cv.check_iterable_type('Isotropic scattering nuclides', isotropic, + string_types) + self._isotropic = list(isotropic) + @classmethod def from_hdf5(cls, group): """Create material from HDF5 group @@ -629,10 +643,10 @@ class Material(IDManagerMixin): self._sab.append((new_name, fraction)) def make_isotropic_in_lab(self): - for nuclide, percent, percent_type in self._nuclides: - nuclide.scattering = 'iso-in-lab' - for element, percent, percent_type, enrichment in self._elements: - element.scattering = 'iso-in-lab' + self.isotropic = [x[0].name for x in self._nuclides] + if self._elements: + raise NotImplementedError( + 'Isotropic-in-lab scattering on elements is not supported.') def get_nuclides(self): """Returns all nuclides in the material @@ -650,7 +664,6 @@ class Material(IDManagerMixin): nuclides.append(nuclide.name) for ele, ele_pct, ele_pct_type, enr in self._elements: - # Expand natural element into isotopes isotopes = ele.expand(ele_pct, ele_pct_type, enr) for iso, iso_pct, iso_pct_type in isotopes: @@ -808,9 +821,6 @@ class Material(IDManagerMixin): else: xml_element.set("wo", str(nuclide[1])) - if not nuclide[0].scattering is None: - xml_element.set("scattering", nuclide[0].scattering) - return xml_element def _get_macroscopic_xml(self, macroscopic): @@ -951,13 +961,17 @@ class Material(IDManagerMixin): subsubelement = self._get_macroscopic_xml(self._macroscopic) subelement.append(subsubelement) - if len(self._sab) > 0: + if self._sab: for sab in self._sab: subelement = ET.SubElement(element, "sab") subelement.set("name", sab[0]) if sab[1] != 1.0: subelement.set("fraction", str(sab[1])) + if self._isotropic: + subelement = ET.SubElement(element, "isotropic") + subelement.text = ' '.join(self._isotropic) + return element diff --git a/openmc/nuclide.py b/openmc/nuclide.py index fc0d7ef07..d1f0c1f29 100644 --- a/openmc/nuclide.py +++ b/openmc/nuclide.py @@ -17,15 +17,12 @@ class Nuclide(object): ---------- name : str Name of the nuclide, e.g. 'U235' - scattering : 'data' or 'iso-in-lab' or None - The type of angular scattering distribution to use """ def __init__(self, name=''): # Initialize class attributes self._name = '' - self._scattering = None # Set the Material class attributes self.name = name @@ -54,20 +51,12 @@ class Nuclide(object): return hash(repr(self)) def __repr__(self): - string = 'Nuclide - {0}\n'.format(self._name) - if self.scattering is not None: - string += '{0: <16}{1}{2}\n'.format('\tscattering', '=\t', - self.scattering) - return string + return 'Nuclide - {0}\n'.format(self._name) @property def name(self): return self._name - @property - def scattering(self): - return self._scattering - @name.setter def name(self, name): cv.check_type('name', name, string_types) @@ -82,12 +71,3 @@ class Nuclide(object): msg = 'OpenMC nuclides follow the GND naming convention. Nuclide ' \ '"{}" is being renamed as "{}".'.format(name, self._name) warnings.warn(msg) - - @scattering.setter - def scattering(self, scattering): - if not scattering in ['data', 'iso-in-lab', None]: - msg = 'Unable to set scattering for Nuclide to {0} which ' \ - 'is not "data", "iso-in-lab", or None'.format(scattering) - raise ValueError(msg) - - self._scattering = scattering diff --git a/src/input_xml.F90 b/src/input_xml.F90 index c76d66df7..cf5207572 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2025,6 +2025,7 @@ contains integer :: i ! loop index for materials integer :: j ! loop index for nuclides + integer :: k ! loop index integer :: n ! number of nuclides integer :: n_sab ! number of sab tables for a material integer :: i_library ! index in libraries array @@ -2035,11 +2036,12 @@ contains character(MAX_WORD_LEN) :: units ! units on density character(MAX_LINE_LEN) :: filename ! absolute path to materials.xml character(MAX_LINE_LEN) :: temp_str ! temporary string when reading + character(MAX_WORD_LEN), allocatable :: sarray(:) real(8) :: val ! value entered for density real(8) :: temp_dble ! temporary double prec. real logical :: sum_density ! density is sum of nuclide densities type(VectorChar) :: names ! temporary list of nuclide names - type(VectorInt) :: list_iso_lab ! temporary list of isotropic lab scatterers + type(VectorChar) :: list_iso_lab ! temporary list of isotropic lab scatterers type(VectorReal) :: densities ! temporary list of nuclide densities type(Material), pointer :: mat => null() type(XMLDocument) :: doc @@ -2246,23 +2248,6 @@ contains // trim(to_str(mat % id))) end if - ! Check enforced isotropic lab scattering - if (run_CE) then - if (check_for_node(node_nuc, "scattering")) then - call get_node_value(node_nuc, "scattering", temp_str) - if (adjustl(to_lower(temp_str)) == "iso-in-lab") then - call list_iso_lab % push_back(1) - else if (adjustl(to_lower(temp_str)) == "data") then - call list_iso_lab % push_back(0) - else - call fatal_error("Scattering must be isotropic in lab or follow& - & the ACE file data") - end if - else - call list_iso_lab % push_back(0) - end if - end if - ! store nuclide name call get_node_value(node_nuc, "name", name) name = trim(name) @@ -2297,6 +2282,19 @@ contains end do INDIVIDUAL_NUCLIDES end if + ! ======================================================================= + ! READ AND PARSE element + + if (check_for_node(node_mat, "isotropic")) then + n = node_word_count(node_mat, "isotropic") + allocate(sarray(n)) + call get_node_array(node_mat, "isotropic", sarray) + do j = 1, n + call list_iso_lab % push_back(sarray(j)) + end do + deallocate(sarray) + end if + ! ======================================================================== ! COPY NUCLIDES TO ARRAYS IN MATERIAL @@ -2306,7 +2304,6 @@ contains allocate(mat % names(n)) allocate(mat % nuclide(n)) allocate(mat % atom_density(n)) - allocate(mat % p0(n)) ALL_NUCLIDES: do j = 1, mat % n_nuclides ! Check that this nuclide is listed in the cross_sections.xml file @@ -2340,17 +2337,23 @@ contains mat % names(j) = name mat % atom_density(j) = densities % data(j) - ! Cast integer isotropic lab scattering flag to boolean - if (run_CE) then - if (list_iso_lab % data(j) == 1) then - mat % p0(j) = .true. - else - mat % p0(j) = .false. - end if - end if - end do ALL_NUCLIDES + if (run_CE) then + ! By default, isotropic-in-lab is not used + allocate(mat % p0(n)) + mat % p0(:) = .false. + + ! Apply isotropic-in-lab treatment to specified nuclides + do j = 1, list_iso_lab % size() + do k = 1, n + if (names % data(k) == list_iso_lab % data(j)) then + mat % p0(k) = .true. + end if + end do + end do + end if + ! Check to make sure either all atom percents or all weight percents are ! given if (.not. (all(mat % atom_density >= ZERO) .or. & diff --git a/src/relaxng/materials.rnc b/src/relaxng/materials.rnc index d55656536..c7e0fbf86 100644 --- a/src/relaxng/materials.rnc +++ b/src/relaxng/materials.rnc @@ -14,14 +14,14 @@ element materials { element nuclide { (element name { xsd:string } | attribute name { xsd:string }) & - (element scattering { ( "data" | "iso-in-lab" ) } | - attribute scattering { ( "data" | "iso-in-lab" ) })? & ( (element ao { xsd:double } | attribute ao { xsd:double }) | (element wo { xsd:double } | attribute wo { xsd:double }) ) }* & + element isotropic { xsd:string }? & + element macroscopic { (element name { xsd:string } | attribute name { xsd:string }) diff --git a/src/relaxng/materials.rng b/src/relaxng/materials.rng index 0fa81c11b..342260be8 100644 --- a/src/relaxng/materials.rng +++ b/src/relaxng/materials.rng @@ -68,22 +68,6 @@ - - - - - data - iso-in-lab - - - - - data - iso-in-lab - - - - @@ -105,6 +89,11 @@ + + + + + diff --git a/tests/test_iso_in_lab/inputs_true.dat b/tests/test_iso_in_lab/inputs_true.dat index 098056f5b..9e30f245f 100644 --- a/tests/test_iso_in_lab/inputs_true.dat +++ b/tests/test_iso_in_lab/inputs_true.dat @@ -150,149 +150,161 @@ - - - - - + + + + + + U234 U235 U238 Xe135 O16 - - - - - + + + + + + Zr90 Zr91 Zr92 Zr94 Zr96 - - - - + + + + + H1 O16 B10 B11 - - - - + + + + + H1 O16 B10 B11 - - - - - - - - - - + + + + + + + + + + + Fe54 Fe56 Fe57 Fe58 Ni58 Ni60 Mn55 Cr52 C0 Cu63 - - - - - - - - - - - + + + + + + + + + + + + H1 O16 B10 B11 Fe54 Fe56 Fe57 Fe58 Ni58 Mn55 Cr52 - - - - - - - - - - - + + + + + + + + + + + + H1 O16 B10 B11 Fe54 Fe56 Fe57 Fe58 Ni58 Mn55 Cr52 - - - - - - - - - - - + + + + + + + + + + + + H1 O16 B10 B11 Fe54 Fe56 Fe57 Fe58 Ni58 Mn55 Cr52 - - - - - - - - - - - + + + + + + + + + + + + H1 O16 B10 B11 Fe54 Fe56 Fe57 Fe58 Ni58 Mn55 Cr52 - - - - - - - - - - - + + + + + + + + + + + + H1 O16 B10 B11 Fe54 Fe56 Fe57 Fe58 Ni58 Mn55 Cr52 - - - - - - - - - + + + + + + + + + + H1 O16 B10 B11 Zr90 Zr91 Zr92 Zr94 Zr96 - - - - - - - - - + + + + + + + + + + H1 O16 B10 B11 Zr90 Zr91 Zr92 Zr94 Zr96 From 9744d173b774c710f020724d8faace5ab3f4a691 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 21:53:27 -0600 Subject: [PATCH 052/282] Don't store material % p0 if it isn't being used --- src/input_xml.F90 | 21 ++++++++++++--------- src/material_header.F90 | 9 ++------- src/physics.F90 | 27 +++++++++++++++------------ 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index cf5207572..91cd57ae2 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2341,17 +2341,20 @@ contains if (run_CE) then ! By default, isotropic-in-lab is not used - allocate(mat % p0(n)) - mat % p0(:) = .false. + if (list_iso_lab % size() > 0) then + mat % has_isotropic_nuclides = .true. + allocate(mat % p0(n)) + mat % p0(:) = .false. - ! Apply isotropic-in-lab treatment to specified nuclides - do j = 1, list_iso_lab % size() - do k = 1, n - if (names % data(k) == list_iso_lab % data(j)) then - mat % p0(k) = .true. - end if + ! Apply isotropic-in-lab treatment to specified nuclides + do j = 1, list_iso_lab % size() + do k = 1, n + if (names % data(k) == list_iso_lab % data(j)) then + mat % p0(k) = .true. + end if + end do end do - end do + end if end if ! Check to make sure either all atom percents or all weight percents are diff --git a/src/material_header.F90 b/src/material_header.F90 index eda0b4230..a3c14f8ca 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -57,7 +57,8 @@ module material_header logical :: fissionable = .false. logical :: depletable = .false. - ! enforce isotropic scattering in lab + ! enforce isotropic scattering in lab for specific nuclides + logical :: has_isotropic_nuclides = .false. logical, allocatable :: p0(:) contains @@ -302,7 +303,6 @@ contains real(8) :: awr integer, allocatable :: new_nuclide(:) real(8), allocatable :: new_density(:) - logical, allocatable :: new_p0(:) character(:), allocatable :: name_ name_ = to_f_string(name) @@ -339,11 +339,6 @@ contains if (n > 0) new_density(1:n) = m % atom_density call move_alloc(FROM=new_density, TO=m % atom_density) - allocate(new_p0(n + 1)) - if (n > 0) new_p0(1:n) = m % p0 - new_p0(n + 1) = .false. - call move_alloc(FROM=new_p0, TO=m % p0) - ! Append new nuclide/density k = nuclide_dict % get(to_lower(name_)) m % nuclide(n + 1) = k diff --git a/src/physics.F90 b/src/physics.F90 index cb309f93a..fd6a0087c 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -419,19 +419,22 @@ contains ! Set event component p % event = EVENT_SCATTER - ! Sample new outgoing angle for isotropic in lab scattering - if (materials(p % material) % p0(i_nuc_mat)) then + ! Sample new outgoing angle for isotropic-in-lab scattering + associate (mat => materials(p % material)) + if (mat % has_isotropic_nuclides) then + if (materials(p % material) % p0(i_nuc_mat)) then + ! Sample isotropic-in-lab outgoing direction + uvw_new(1) = TWO * prn() - ONE + phi = TWO * PI * prn() + uvw_new(2) = cos(phi) * sqrt(ONE - uvw_new(1)*uvw_new(1)) + uvw_new(3) = sin(phi) * sqrt(ONE - uvw_new(1)*uvw_new(1)) + p % mu = dot_product(uvw_old, uvw_new) - ! Sample isotropic-in-lab outgoing direction - uvw_new(1) = TWO * prn() - ONE - phi = TWO * PI * prn() - uvw_new(2) = cos(phi) * sqrt(ONE - uvw_new(1)*uvw_new(1)) - uvw_new(3) = sin(phi) * sqrt(ONE - uvw_new(1)*uvw_new(1)) - p % mu = dot_product(uvw_old, uvw_new) - - ! Change direction of particle - p % coord(1) % uvw = uvw_new - end if + ! Change direction of particle + p % coord(1) % uvw = uvw_new + end if + end if + end associate end subroutine scatter From ee2f7e925f5cd3c9aa792dc0bc17f5448af05440 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 22:23:47 -0600 Subject: [PATCH 053/282] Make Nuclide, Element, and Macroscopic subclasses of str --- openmc/element.py | 31 ++++++++--------------- openmc/macroscopic.py | 22 ++++------------ openmc/nuclide.py | 58 ++++++++++--------------------------------- 3 files changed, 28 insertions(+), 83 deletions(-) diff --git a/openmc/element.py b/openmc/element.py index 8543500e9..386f9ac58 100644 --- a/openmc/element.py +++ b/openmc/element.py @@ -10,7 +10,7 @@ import openmc.checkvalue as cv from openmc.data import NATURAL_ABUNDANCE, atomic_mass -class Element(object): +class Element(str): """A natural element that auto-expands to add the isotopes of an element to a material in their natural abundance. Internally, the OpenMC Python API expands the natural element into isotopes only when the materials.xml file @@ -28,25 +28,14 @@ class Element(object): """ - def __init__(self, name=''): - # Initialize class attributes - self._name = '' - - # Set class attributes - self.name = name - - def __repr__(self): - return 'Element - {0}\n'.format(self._name) + def __new__(cls, name): + cv.check_type('element name', name, string_types) + cv.check_length('element name', name, 1, 2) + return super(Element, cls).__new__(cls, name) @property def name(self): - return self._name - - @name.setter - def name(self, name): - cv.check_type('element name', name, string_types) - cv.check_length('element name', name, 1, 2) - self._name = name + return self def expand(self, percent, percent_type, enrichment=None, cross_sections=None): @@ -93,7 +82,7 @@ class Element(object): # Get the nuclides present in nature natural_nuclides = set() for nuclide in sorted(NATURAL_ABUNDANCE.keys()): - if re.match(r'{}\d+'.format(self.name), nuclide): + if re.match(r'{}\d+'.format(self), nuclide): natural_nuclides.add(nuclide) # Create dict to store the expanded nuclides and abundances @@ -113,7 +102,7 @@ class Element(object): root = tree.getroot() for child in root: nuclide = child.attrib['materials'] - if re.match(r'{}\d+'.format(self.name), nuclide) and \ + if re.match(r'{}\d+'.format(self), nuclide) and \ '_m' not in nuclide: library_nuclides.add(nuclide) @@ -134,14 +123,14 @@ class Element(object): # 0 nuclide is present. If so, set the abundance to 1 for this # nuclide. Else, raise an error. elif len(mutual_nuclides) == 0: - nuclide_0 = self.name + '0' + nuclide_0 = self + '0' if nuclide_0 in library_nuclides: abundances[nuclide_0] = 1.0 else: msg = 'Unable to expand element {0} because the cross '\ 'section library provided does not contain any of '\ 'the natural isotopes for that element.'\ - .format(self.name) + .format(self) raise ValueError(msg) # If some, but not all, natural nuclides are in the library, add diff --git a/openmc/macroscopic.py b/openmc/macroscopic.py index 3a46b5225..cdb5cbb39 100644 --- a/openmc/macroscopic.py +++ b/openmc/macroscopic.py @@ -3,7 +3,7 @@ from six import string_types from openmc.checkvalue import check_type -class Macroscopic(object): +class Macroscopic(str): """A Macroscopic object that can be used in a material. Parameters @@ -18,22 +18,10 @@ class Macroscopic(object): """ - def __init__(self, name=''): - # Initialize class attributes - self._name = '' - - # Set the Macroscopic class attributes - self.name = name - - def __repr__(self): - string = 'Macroscopic - {0}\n'.format(self._name) - return string + def __new__(cls, name): + check_type('name', name, string_types) + return super(Macroscopic, cls).__new__(cls, name) @property def name(self): - return self._name - - @name.setter - def name(self, name): - check_type('name', name, string_types) - self._name = name + return self diff --git a/openmc/nuclide.py b/openmc/nuclide.py index d1f0c1f29..2e5a8e119 100644 --- a/openmc/nuclide.py +++ b/openmc/nuclide.py @@ -5,7 +5,7 @@ from six import string_types import openmc.checkvalue as cv -class Nuclide(object): +class Nuclide(str): """A nuclide that can be used in a material. Parameters @@ -20,54 +20,22 @@ class Nuclide(object): """ - def __init__(self, name=''): + def __new__(cls, name): # Initialize class attributes - self._name = '' + orig_name = name - # Set the Material class attributes - self.name = name + if '-' in name: + name = name.replace('-', '') + name = name.replace('Nat', '0') + if name.endswith('m'): + name = name[:-1] + '_m1' - def __eq__(self, other): - if isinstance(other, Nuclide): - if self.name != other.name: - return False - else: - return True - elif isinstance(other, string_types) and other == self.name: - return True - else: - return False + msg = 'OpenMC nuclides follow the GND naming convention. Nuclide ' \ + '"{}" is being renamed as "{}".'.format(orig_name, name) + warnings.warn(msg) - def __ne__(self, other): - return not self == other - - def __gt__(self, other): - return repr(self) > repr(other) - - def __lt__(self, other): - return not self > other - - def __hash__(self): - return hash(repr(self)) - - def __repr__(self): - return 'Nuclide - {0}\n'.format(self._name) + return super(Nuclide, cls).__new__(cls, name) @property def name(self): - return self._name - - @name.setter - def name(self, name): - cv.check_type('name', name, string_types) - self._name = name - - if '-' in name: - self._name = name.replace('-', '') - self._name = self._name.replace('Nat', '0') - if self._name.endswith('m'): - self._name = self._name[:-1] + '_m1' - - msg = 'OpenMC nuclides follow the GND naming convention. Nuclide ' \ - '"{}" is being renamed as "{}".'.format(name, self._name) - warnings.warn(msg) + return self From 9c182ece342da6cb616a92e7dd5bae1c6c2d1ed3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 22:52:43 -0600 Subject: [PATCH 054/282] Remove various uses of Nuclide.name property --- openmc/material.py | 27 +++++++++++++-------------- openmc/tallies.py | 16 +++------------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 00f5981b8..f94df830d 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -136,7 +136,7 @@ class Material(IDManagerMixin): string += '{: <16}\n'.format('\tNuclides') for nuclide, percent, percent_type in self._nuclides: - string += '{0: <16}'.format('\t{0.name}'.format(nuclide)) + string += '{: <16}'.format('\t{}'.format(nuclide)) string += '=\t{: <12} [{}]\n'.format(percent, percent_type) if self._macroscopic is not None: @@ -146,7 +146,7 @@ class Material(IDManagerMixin): string += '{: <16}\n'.format('\tElements') for element, percent, percent_type, enr in self._elements: - string += '{0: <16}'.format('\t{0.name}'.format(element)) + string += '{: <16}'.format('\t{}'.format(element)) if enr is None: string += '=\t{: <12} [{}]\n'.format(percent, percent_type) else: @@ -510,7 +510,7 @@ class Material(IDManagerMixin): raise ValueError(msg) # If the Material contains the Macroscopic, delete it - if macroscopic.name == self._macroscopic.name: + if macroscopic == self._macroscopic: self._macroscopic = None def add_element(self, element, percent, percent_type='ao', enrichment=None): @@ -565,10 +565,9 @@ class Material(IDManagerMixin): .format(self._id, enrichment) raise ValueError(msg) - elif element.name != 'U': + elif element != 'U': msg = 'Unable to use enrichment for element {} which is not ' \ - 'uranium for Material ID="{}"'.format(element.name, - self._id) + 'uranium for Material ID="{}"'.format(element, self._id) raise ValueError(msg) # Check that the enrichment is in the valid range @@ -643,7 +642,7 @@ class Material(IDManagerMixin): self._sab.append((new_name, fraction)) def make_isotropic_in_lab(self): - self.isotropic = [x[0].name for x in self._nuclides] + self.isotropic = [x[0] for x in self._nuclides] if self._elements: raise NotImplementedError( 'Isotropic-in-lab scattering on elements is not supported.') @@ -661,13 +660,13 @@ class Material(IDManagerMixin): nuclides = [] for nuclide, percent, percent_type in self._nuclides: - nuclides.append(nuclide.name) + nuclides.append(nuclide) for ele, ele_pct, ele_pct_type, enr in self._elements: # Expand natural element into isotopes isotopes = ele.expand(ele_pct, ele_pct_type, enr) for iso, iso_pct, iso_pct_type in isotopes: - nuclides.append(iso.name) + nuclides.append(iso) return nuclides @@ -685,14 +684,14 @@ class Material(IDManagerMixin): nuclides = OrderedDict() for nuclide, density, density_type in self._nuclides: - nuclides[nuclide.name] = (nuclide, density, density_type) + nuclides[nuclide] = (nuclide, density, density_type) for ele, ele_pct, ele_pct_type, enr in self._elements: # Expand natural element into isotopes isotopes = ele.expand(ele_pct, ele_pct_type, enr) for iso, iso_pct, iso_pct_type in isotopes: - nuclides[iso.name] = (iso, iso_pct, iso_pct_type) + nuclides[iso] = (iso, iso_pct, iso_pct_type) return nuclides @@ -753,7 +752,7 @@ class Material(IDManagerMixin): if not percent_in_atom: for n, nuc in enumerate(nucs): nuc_densities[n] *= self.average_molar_mass / \ - openmc.data.atomic_mass(nuc.name) + openmc.data.atomic_mass(nuc) # Now that we have the atomic amounts, lets finish calculating densities sum_percent = np.sum(nuc_densities) @@ -813,7 +812,7 @@ class Material(IDManagerMixin): def _get_nuclide_xml(self, nuclide, distrib=False): xml_element = ET.Element("nuclide") - xml_element.set("name", nuclide[0].name) + xml_element.set("name", nuclide[0]) if not distrib: if nuclide[2] == 'ao': @@ -825,7 +824,7 @@ class Material(IDManagerMixin): def _get_macroscopic_xml(self, macroscopic): xml_element = ET.Element("macroscopic") - xml_element.set("name", macroscopic.name) + xml_element.set("name", macroscopic) return xml_element diff --git a/openmc/tallies.py b/openmc/tallies.py index b358a14bc..2cf358987 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -144,10 +144,7 @@ class Tally(IDManagerMixin): string += '{: <16}=\t'.format('\tNuclides') for nuclide in self.nuclides: - if isinstance(nuclide, openmc.Nuclide): - string += nuclide.name + ' ' - else: - string += str(nuclide) + ' ' + string += str(nuclide) + ' ' string += '\n' @@ -1012,16 +1009,9 @@ class Tally(IDManagerMixin): subelement.text = ' '.join(str(f.id) for f in self.filters) # Optional Nuclides - if len(self.nuclides) > 0: - nuclides = '' - for nuclide in self.nuclides: - if isinstance(nuclide, openmc.Nuclide): - nuclides += '{0} '.format(nuclide.name) - else: - nuclides += '{0} '.format(nuclide) - + if self.nuclides: subelement = ET.SubElement(element, "nuclides") - subelement.text = nuclides.rstrip(' ') + subelement.text = ' '.join(str(n) for n in self.nuclides) # Scores if len(self.scores) == 0: From 2b6cd880f1d4d76446ee764433a831059fc73f92 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 29 Nov 2017 23:23:38 -0600 Subject: [PATCH 055/282] Auto-expand elements at the time they are added to a material --- openmc/element.py | 7 +- openmc/material.py | 173 +++++---------------------------- tests/test_triso/test_triso.py | 2 +- 3 files changed, 29 insertions(+), 153 deletions(-) diff --git a/openmc/element.py b/openmc/element.py index 386f9ac58..5ba19c63c 100644 --- a/openmc/element.py +++ b/openmc/element.py @@ -65,8 +65,8 @@ class Element(str): ------- isotopes : list Naturally-occurring isotopes of the element. Each item of the list - is a tuple consisting of an openmc.Nuclide instance and the natural - abundance of the isotope. + is a tuple consisting of a nuclide string, the atom/weight percent, + and the string 'ao' or 'wo'. Notes ----- @@ -205,7 +205,6 @@ class Element(str): # Create a list of the isotopes in this element isotopes = [] for nuclide, abundance in abundances.items(): - nuc = openmc.Nuclide(nuclide) - isotopes.append((nuc, percent * abundance, percent_type)) + isotopes.append((nuclide, percent * abundance, percent_type)) return isotopes diff --git a/openmc/material.py b/openmc/material.py index f94df830d..e22fe0b8b 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -55,14 +55,9 @@ class Material(IDManagerMixin): depletable : bool Indicate whether the material is depletable. This attribute can be used by downstream depletion applications. - elements : list of tuple - List in which each item is a 4-tuple consisting of an - :class:`openmc.Element` instance, the percent density, the percent - type ('ao' or 'wo'), and enrichment. nuclides : list of tuple - List in which each item is a 3-tuple consisting of an - :class:`openmc.Nuclide` instance, the percent density, and the percent - type ('ao' or 'wo'). + List in which each item is a 3-tuple consisting of a nuclide string, the + percent density, and the percent type ('ao' or 'wo'). isotropic : list of str Nuclides for which elastic scattering should be treated as though it were isotropic in the laboratory system. @@ -107,9 +102,6 @@ class Material(IDManagerMixin): # (only one is allowed, hence this is different than _nuclides, etc) self._macroscopic = None - # A list of tuples (element, percent, percent type, enrichment) - self._elements = [] - # If specified, a list of table names self._sab = [] @@ -143,16 +135,6 @@ class Material(IDManagerMixin): string += '{: <16}\n'.format('\tMacroscopic Data') string += '{: <16}'.format('\t{}'.format(self._macroscopic)) - string += '{: <16}\n'.format('\tElements') - - for element, percent, percent_type, enr in self._elements: - string += '{: <16}'.format('\t{}'.format(element)) - if enr is None: - string += '=\t{: <12} [{}]\n'.format(percent, percent_type) - else: - string += '=\t{: <12} [{}] @ {} w/o enrichment\n'\ - .format(percent, percent_type, enr) - return string @property @@ -190,10 +172,6 @@ class Material(IDManagerMixin): 'the Geometry.determine_paths() method.') return self._num_instances - @property - def elements(self): - return self._elements - @property def nuclides(self): return self._nuclides @@ -387,7 +365,7 @@ class Material(IDManagerMixin): Parameters ---------- - nuclide : str or openmc.Nuclide + nuclide : str Nuclide to add percent : float Atom or weight percent @@ -401,9 +379,9 @@ class Material(IDManagerMixin): 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) - if not isinstance(nuclide, string_types + (openmc.Nuclide,)): + if not isinstance(nuclide, string_types): msg = 'Unable to add a Nuclide to Material ID="{}" with a ' \ - 'non-Nuclide value "{}"'.format(self._id, nuclide) + 'non-string value "{}"'.format(self._id, nuclide) raise ValueError(msg) elif not isinstance(percent, Real): @@ -411,18 +389,11 @@ class Material(IDManagerMixin): 'non-floating point value "{}"'.format(self._id, percent) raise ValueError(msg) - elif percent_type not in ['ao', 'wo', 'at/g-cm']: + 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 isinstance(nuclide, openmc.Nuclide): - # Copy this Nuclide to separate it from the Nuclide in - # other Materials - nuclide = deepcopy(nuclide) - else: - nuclide = openmc.Nuclide(nuclide) - self._nuclides.append((nuclide, percent, percent_type)) def remove_nuclide(self, nuclide): @@ -430,14 +401,11 @@ class Material(IDManagerMixin): Parameters ---------- - nuclide : openmc.Nuclide + nuclide : str Nuclide to remove """ - cv.check_type('nuclide', nuclide, string_types + (openmc.Nuclide,)) - - if isinstance(nuclide, string_types): - nuclide = openmc.Nuclide(nuclide) + cv.check_type('nuclide', nuclide, string_types) # If the Material contains the Nuclide, delete it for nuc in self._nuclides: @@ -452,32 +420,25 @@ class Material(IDManagerMixin): Parameters ---------- - macroscopic : str or openmc.Macroscopic + macroscopic : str Macroscopic to add """ # Ensure no nuclides, elements, or sab are added since these would be # incompatible with macroscopics - if self._nuclides or self._elements or self._sab: + if self._nuclides or self._sab: msg = 'Unable to add a Macroscopic data set to Material ID="{}" ' \ 'with a macroscopic value "{}" as an incompatible data ' \ - 'member (i.e., nuclide, element, or S(a,b) table) ' \ + 'member (i.e., nuclide or S(a,b) table) ' \ 'has already been added'.format(self._id, macroscopic) raise ValueError(msg) - if not isinstance(macroscopic, string_types + (openmc.Macroscopic,)): + if not isinstance(macroscopic, string_types): msg = 'Unable to add a Macroscopic to Material ID="{}" with a ' \ - 'non-Macroscopic value "{}"'.format(self._id, macroscopic) + 'non-string value "{}"'.format(self._id, macroscopic) raise ValueError(msg) - if isinstance(macroscopic, openmc.Macroscopic): - # Copy this Macroscopic to separate it from the Macroscopic in - # other Materials - macroscopic = deepcopy(macroscopic) - else: - macroscopic = openmc.Macroscopic(macroscopic) - if self._macroscopic is None: self._macroscopic = macroscopic else: @@ -499,14 +460,14 @@ class Material(IDManagerMixin): Parameters ---------- - macroscopic : openmc.Macroscopic + macroscopic : str Macroscopic to remove """ - if not isinstance(macroscopic, openmc.Macroscopic): + if not isinstance(macroscopic, string_types): msg = 'Unable to remove a Macroscopic "{}" in Material ID="{}" ' \ - 'since it is not a Macroscopic'.format(self._id, macroscopic) + 'since it is not a string'.format(self._id, macroscopic) raise ValueError(msg) # If the Material contains the Macroscopic, delete it @@ -518,7 +479,7 @@ class Material(IDManagerMixin): Parameters ---------- - element : openmc.Element or str + element : str Element to add percent : float Atom or weight percent @@ -537,9 +498,9 @@ class Material(IDManagerMixin): 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) - if not isinstance(element, string_types + (openmc.Element,)): + if not isinstance(element, string_types): msg = 'Unable to add an Element to Material ID="{}" with a ' \ - 'non-Element value "{}"'.format(self._id, element) + 'non-string value "{}"'.format(self._id, element) raise ValueError(msg) if not isinstance(percent, Real): @@ -552,12 +513,6 @@ class Material(IDManagerMixin): 'percent type "{}"'.format(self._id, percent_type) raise ValueError(msg) - # Copy this Element to separate it from same Element in other Materials - if isinstance(element, openmc.Element): - element = deepcopy(element) - else: - element = openmc.Element(element) - if enrichment is not None: if not isinstance(enrichment, Real): msg = 'Unable to add an Element to Material ID="{}" with a ' \ @@ -583,26 +538,10 @@ class Material(IDManagerMixin): format(enrichment, self._id) warnings.warn(msg) - self._elements.append((element, percent, percent_type, enrichment)) - - def remove_element(self, element): - """Remove a natural element from the material - - Parameters - ---------- - element : openmc.Element - Element to remove - - """ - cv.check_type('element', element, string_types + (openmc.Element,)) - - if isinstance(element, string_types): - element = openmc.Element(element) - - # If the Material contains the Element, delete it - for elm in self._elements: - if element == elm[0]: - self._elements.remove(elm) + # Add naturally-occuring isotopes + element = openmc.Element(element) + for nuclide in element.expand(percent, percent_type, enrichment): + self._nuclides.append(nuclide) def add_s_alpha_beta(self, name, fraction=1.0): r"""Add an :math:`S(\alpha,\beta)` table to the material @@ -643,9 +582,6 @@ class Material(IDManagerMixin): def make_isotropic_in_lab(self): self.isotropic = [x[0] for x in self._nuclides] - if self._elements: - raise NotImplementedError( - 'Isotropic-in-lab scattering on elements is not supported.') def get_nuclides(self): """Returns all nuclides in the material @@ -656,19 +592,7 @@ class Material(IDManagerMixin): List of nuclide names """ - - nuclides = [] - - for nuclide, percent, percent_type in self._nuclides: - nuclides.append(nuclide) - - for ele, ele_pct, ele_pct_type, enr in self._elements: - # Expand natural element into isotopes - isotopes = ele.expand(ele_pct, ele_pct_type, enr) - for iso, iso_pct, iso_pct_type in isotopes: - nuclides.append(iso) - - return nuclides + return [x[0] for x in self._nuclides] def get_nuclide_densities(self): """Returns all nuclides in the material and their densities @@ -686,13 +610,6 @@ class Material(IDManagerMixin): for nuclide, density, density_type in self._nuclides: nuclides[nuclide] = (nuclide, density, density_type) - for ele, ele_pct, ele_pct_type, enr in self._elements: - - # Expand natural element into isotopes - isotopes = ele.expand(ele_pct, ele_pct_type, enr) - for iso, iso_pct, iso_pct_type in isotopes: - nuclides[iso] = (iso, iso_pct, iso_pct_type) - return nuclides def get_nuclide_atom_densities(self): @@ -828,37 +745,10 @@ class Material(IDManagerMixin): return xml_element - def _get_element_xml(self, element, cross_sections, distrib=False): - - # Get the nuclides in this element - nuclides = element[0].expand(element[1], element[2], element[3], - cross_sections) - - xml_elements = [] - for nuclide in nuclides: - xml_elements.append(self._get_nuclide_xml(nuclide, distrib)) - - return xml_elements - def _get_nuclides_xml(self, nuclides, distrib=False): - xml_elements = [] - for nuclide in nuclides: xml_elements.append(self._get_nuclide_xml(nuclide, distrib)) - - return xml_elements - - def _get_elements_xml(self, elements, cross_sections, distrib=False): - - xml_elements = [] - - for element in elements: - nuclide_elements = self._get_element_xml(element, cross_sections, - distrib) - for nuclide_element in nuclide_elements: - xml_elements.append(nuclide_element) - return xml_elements def to_xml_element(self, cross_sections=None): @@ -907,12 +797,6 @@ class Material(IDManagerMixin): subelements = self._get_nuclides_xml(self._nuclides) for subelement in subelements: element.append(subelement) - - # Create element XML subelements - subelements = self._get_elements_xml(self._elements, - cross_sections) - for subelement in subelements: - element.append(subelement) else: # Create macroscopic XML subelements subelement = self._get_macroscopic_xml(self._macroscopic) @@ -922,7 +806,7 @@ class Material(IDManagerMixin): subelement = ET.SubElement(element, "compositions") comps = [] - allnucs = self._nuclides + self._elements + allnucs = self._nuclides dist_per_type = allnucs[0][2] for nuc in allnucs: if nuc[2] != dist_per_type: @@ -948,13 +832,6 @@ class Material(IDManagerMixin): distrib=True) for subelement_nuc in subelements: subelement.append(subelement_nuc) - - # Create element XML subelements - subelements = self._get_elements_xml(self._elements, - cross_sections, - distrib=True) - for subsubelement in subelements: - subelement.append(subsubelement) else: # Create macroscopic XML subelements subsubelement = self._get_macroscopic_xml(self._macroscopic) diff --git a/tests/test_triso/test_triso.py b/tests/test_triso/test_triso.py index 685034491..e5c823c01 100644 --- a/tests/test_triso/test_triso.py +++ b/tests/test_triso/test_triso.py @@ -36,8 +36,8 @@ class TRISOTestHarness(PyAPITestHarness): sic = openmc.Material() sic.set_density('g/cm3', 3.20) - sic.add_element('Si', 1.0) sic.add_nuclide('C0', 1.0) + sic.add_element('Si', 1.0) opyc = openmc.Material() opyc.set_density('g/cm3', 1.87) From 9aaa9272222881ac821f2aab10ccb69007647e89 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 4 Dec 2017 21:05:58 -0600 Subject: [PATCH 056/282] Update Jupyter notebooks to remove use of openmc.Nuclide --- docs/source/conf.py | 4 +- examples/jupyter/mg-mode-part-i.ipynb | 6 +- examples/jupyter/mg-mode-part-iii.ipynb | 117 +- examples/jupyter/mgxs-part-i.ipynb | 189 ++- examples/jupyter/mgxs-part-ii.ipynb | 1505 +++++++++++----------- examples/jupyter/mgxs-part-iii.ipynb | 45 +- examples/jupyter/pincell.ipynb | 439 +++---- examples/jupyter/post-processing.ipynb | 459 +++---- examples/jupyter/tally-arithmetic.ipynb | 405 ++---- openmc/examples.py | 8 +- openmc/plotter.py | 104 +- openmc/settings.py | 2 +- openmc/tallies.py | 4 +- openmc/universe.py | 3 +- tests/test_mg_tallies/test_mg_tallies.py | 6 +- 15 files changed, 1529 insertions(+), 1767 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 5f993b1a3..ae8bfe6b5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -27,8 +27,8 @@ except ImportError: MOCK_MODULES = ['numpy', 'numpy.polynomial', 'numpy.polynomial.polynomial', 'numpy.ctypeslib', 'scipy', 'scipy.sparse', 'scipy.interpolate', 'scipy.integrate', 'scipy.optimize', 'scipy.special', - 'scipy.stats','h5py', 'pandas', 'uncertainties', 'openmoc', - 'openmc.data.reconstruct'] + 'scipy.stats', 'h5py', 'pandas', 'uncertainties', + 'matplotlib.pyplot','openmoc', 'openmc.data.reconstruct'] sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES) import numpy as np diff --git a/examples/jupyter/mg-mode-part-i.ipynb b/examples/jupyter/mg-mode-part-i.ipynb index cdd56db03..7fcb3d079 100644 --- a/examples/jupyter/mg-mode-part-i.ipynb +++ b/examples/jupyter/mg-mode-part-i.ipynb @@ -148,11 +148,9 @@ "source": [ "To build the actual 2-D model, we will first begin by creating the `materials.xml` file.\n", "\n", - "First we need to define materials that will be used in the problem. In other notebooks, either `openmc.Nuclide`s or `openmc.Element`s were created at the equivalent stage. We can do that in multi-group mode as well. However, multi-group cross-sections are sometimes provided as macroscopic cross-sections; the C5G7 benchmark data are macroscopic. In this case, we can instead use `openmc.Macroscopic` objects to in-place of `openmc.Nuclide` or `openmc.Element` objects.\n", + "First we need to define materials that will be used in the problem. In other notebooks, either nuclides or elements were added to materials at the equivalent stage. We can do that in multi-group mode as well. However, multi-group cross-sections are sometimes provided as macroscopic cross-sections; the C5G7 benchmark data are macroscopic. In this case, we can instead use the `Material.add_macroscopic` method to specific a macroscopic object. Unlike for nuclides and elements, we do not need provide information on atom/weight percents as no number densities are needed.\n", "\n", - "`openmc.Macroscopic`, unlike `openmc.Nuclide` and `openmc.Element` objects, do not need to be provided enough information to calculate number densities, as no number densities are needed.\n", - "\n", - "When assigning `openmc.Macroscopic` objects to `openmc.Material` objects, the density can still be scaled by setting the density to a value that is not 1.0. This would be useful, for example, when slightly perturbing the density of water due to a small change in temperature (while of course ignoring any resultant spectral shift). The density of a macroscopic dataset is set to 1.0 in the `openmc.Material` object by default when an `openmc.Macroscopic` dataset is used; so we will show its use the first time and then afterwards it will not be required.\n", + "When assigning macroscopic objects to a material, the density can still be scaled by setting the density to a value that is not 1.0. This would be useful, for example, when slightly perturbing the density of water due to a small change in temperature (while of course ignoring any resultant spectral shift). The density of a macroscopic dataset is set to 1.0 in the `openmc.Material` object by default when a macroscopic dataset is used; so we will show its use the first time and then afterwards it will not be required.\n", "\n", "Aside from these differences, the following code is very similar to similar code in other OpenMC example Notebooks." ] diff --git a/examples/jupyter/mg-mode-part-iii.ipynb b/examples/jupyter/mg-mode-part-iii.ipynb index 055389131..c36ecd54a 100644 --- a/examples/jupyter/mg-mode-part-iii.ipynb +++ b/examples/jupyter/mg-mode-part-iii.ipynb @@ -402,9 +402,9 @@ "outputs": [ { "data": { - "image/png": "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\nQmCC\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -692,7 +692,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/nelsonag/git/openmc/openmc/mgxs/mgxs.py:3801: UserWarning: The legendre order will be ignored since the scatter format is set to histogram\n", + "/home/romano/openmc/openmc/mgxs/mgxs.py:4106: UserWarning: The legendre order will be ignored since the scatter format is set to histogram\n", " warnings.warn(msg)\n" ] } @@ -735,9 +735,30 @@ "cell_type": "code", "execution_count": 23, "metadata": { - "collapsed": true + "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another MeshFilter instance already exists with id=1.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=2.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyoutFilter instance already exists with id=11.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another PolarFilter instance already exists with id=21.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another AzimuthalFilter instance already exists with id=22.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another MuFilter instance already exists with id=12.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=18.\n", + " warn(msg, IDWarning)\n" + ] + } + ], "source": [ "# Instantiate tally Filter\n", "mesh_filter = openmc.MeshFilter(mesh)\n", @@ -802,10 +823,10 @@ " | The OpenMC Monte Carlo Code\n", " Copyright | 2011-2017 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | 966169de084fcfda3a5aaca3edc0065c8caf6bbc\n", - " Date/Time | 2017-03-09 09:18:01\n", - " OpenMP Threads | 8\n", + " Version | 0.9.0\n", + " Git SHA1 | da61fb4a55e1feaa127799ad9293a766161fbb3e\n", + " Date/Time | 2017-12-11 16:57:27\n", + " OpenMP Threads | 4\n", "\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -813,10 +834,10 @@ "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 0.83694 +/- 0.00098\n", - " k-effective (Track-length) = 0.83663 +/- 0.00116\n", - " k-effective (Absorption) = 0.83775 +/- 0.00102\n", - " Combined k-effective = 0.83724 +/- 0.00083\n", + " k-effective (Collision) = 0.83880 +/- 0.00101\n", + " k-effective (Track-length) = 0.83917 +/- 0.00118\n", + " k-effective (Absorption) = 0.83859 +/- 0.00104\n", + " Combined k-effective = 0.83879 +/- 0.00086\n", " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] @@ -957,12 +978,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/nelsonag/git/openmc/openmc/tallies.py:1835: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1798: RuntimeWarning: invalid value encountered in true_divide\n", " self_rel_err = data['self']['std. dev.'] / data['self']['mean']\n", - "/home/nelsonag/git/openmc/openmc/tallies.py:1836: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1799: RuntimeWarning: invalid value encountered in true_divide\n", " other_rel_err = data['other']['std. dev.'] / data['other']['mean']\n", - "/home/nelsonag/git/openmc/openmc/tallies.py:1837: RuntimeWarning: invalid value encountered in true_divide\n", - " new_tally._mean = data['self']['mean'] / data['other']['mean']\n" + "/home/romano/openmc/openmc/tallies.py:1800: RuntimeWarning: invalid value encountered in true_divide\n", + " new_tally._mean = data['self']['mean'] / data['other']['mean']\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another Universe instance already exists with id=0.\n", + " warn(msg, IDWarning)\n" ] } ], @@ -1043,9 +1066,9 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAP8AAAD8CAYAAAC4nHJkAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAEGVJREFUeJzt3XuwVeV9xvHvA0JQVC6iqKCijdhQx6iDisYLKdagdSS2\npsVWxRhjTZRgx4xDYkZtppncNU1zUaKoSRiNGiTWS8RrndpIghTlpiLGCwgcExUiGbn++sdeZLaH\nfWCz17u2h7zPZ+bMWWfv9/zWj7XPw1p7nXXWq4jAzPLT4/1uwMzeHw6/WaYcfrNMOfxmmXL4zTLl\n8JtlyuE3y5TDb5Yph98sUztta4CkqcDpQEdEHFr3+ETgEmAjcF9EXLGtWj0G9IidhvQs0W5je0Xv\n5DUBdn1l3+Q1Nx3wSvKaANFnSCV1X/vt65XUHTF4WCV1n1/6WvKaww5+N3lNgIWLhqcvun4FseFt\nNTN0m+EHbgG+B/x48wOSPgqMAz4cEWsl7dXUyob0ZK/pezQzdLtMXDs0eU2AY//l6uQ1111/cfKa\nAOs+9OVK6k4676pK6j552fWV1D1x8uXJa9587/zkNQE+fMzU5DU3Lbmg6bHbPOyPiCeANzs9/Bng\naxGxthjTsT0Nmtn7r9X3/MOBEyTNkvTfko5K2ZSZVa+Zw/6uvm8gMAo4CrhD0kHR4E8EJV0EXATQ\nc1+fXzTrLlpN41JgetT8GtgEDGo0MCKmRMTIiBjZY4DDb9ZdtJrGGcBHASQNB3oDv0vVlJlVr5lf\n9d0GjAYGSVoKXA1MBaZKmg+sAyY0OuQ3s+5rm+GPiLO7eOqcxL2YWRv5TbhZphx+s0w5/GaZcvjN\nMuXwm2XK4TfLlMNvlimH3yxTDr9Zphx+s0w5/GaZcvjNMuXwm2Wq1Tv5tGTP1w7gsxO/nbzuHh+Y\nmbwmwMGj0t8R99KOVclrAvzbvbMrqXvK/f0qqXvyfx1fSd3n3+qTvOaNk8ckrwnw98svSV7z4fWv\nNj3We36zTDn8Zply+M0y5fCbZWqb4Zc0VVJHcb++zs9dLikkNbxzr5l1X83s+W8BxnZ+UNJ+wClA\n86cXzazbaHW6LoDrgCsA37XXbAfU0nt+SeOAZRHxTOJ+zKxNtvsiH0m7AF+kdsjfzPg/TdfVr8+e\n27s6M6tIK3v+vwAOBJ6R9DIwFJgjae9Gg+un6+rbe/fWOzWzpLZ7zx8R84C9Nn9d/AcwMiI8XZfZ\nDqSZX/XdBvwKOETSUkmfqr4tM6tamem6Nj8/LFk3ZtY2vsLPLFMOv1mmHH6zTDn8Zply+M0y5fCb\nZcrhN8uUw2+WKUW07y9yd+59aBw0+K7kddded3vymgAb7vtq8po3nHRD8poAB8+aXkndXm8Or6Tu\nsVcdXUndnm8OTF7zO492JK8JsGHuHclrTn78cZa89baaGes9v1mmHH6zTDn8Zply+M0y5fCbZcrh\nN8uUw2+WKYffLFMOv1mmWpquS9I3JT0n6VlJd0vqX22bZpZaq9N1PQQcGhGHAS8AX0jcl5lVrKXp\nuiJiZkRsKL58itq9+81sB5LiPf8FwANdPSnpIkmzJc3euOmtBKszsxRKhV/SlcAGYFpXY+pn7OnZ\nY0CZ1ZlZQts9Y89mks4HTgfGRDv/LtjMkmgp/JLGUpue+6SI+GPalsysHVqdrut7wG7AQ5LmSrq+\n4j7NLLFWp+u6qYJezKyNfIWfWaYcfrNMtXy2vxXDDnuBm3/zN8nrTvrcJ5PXBPjazNeT11z/s0HJ\nawJcdvlnK6m7YPT/VlL3qy//XSV17ztrXPKaH7twdPKaAItvW5S8Zp+PvNv0WO/5zTLl8JtlyuE3\ny5TDb5Yph98sUw6/WaYcfrNMOfxmmXL4zTLl8JtlyuE3y5TDb5Yph98sUw6/WaZanbFnoKSHJC0u\nPvu2vGY7mFZn7JkMPBIRBwOPFF+b2Q6kpRl7gHHArcXyrcDHE/dlZhVr9T3/4IhYXiyvAAYn6sfM\n2qT0Cb9iwo4uJ+2on67r7Tc2lV2dmSXSavhXStoHoPjc0dXA+um6+u/pXy6YdRetpvEeYEKxPAH4\nRZp2zKxdtK1p9ooZe0YDg4CVwNXADOAOYH/gFeAfIqLzScEt7LzXsDjoH79UsuUtTdz72uQ1AVYe\n9oHkNYd/bHbymgBPDu1bSd3n9j+pkronLK7mNFGf2dclr/noOZ9IXhNgNZ9PXnPe/Em8s2axmhnb\n6ow9AGO2qysz61b8JtwsUw6/WaYcfrNMOfxmmXL4zTLl8JtlyuE3y5TDb5Yph98sUw6/WaYcfrNM\nOfxmmXL4zTLl8JtlyuE3y5TDb5Yph98sUw6/WaZKhV/Sv0paIGm+pNsk9UnVmJlVq+XwSxoCfA4Y\nGRGHAj2B8akaM7NqbfMGnk18/86S1gO7AK9vbfCIta/y5JJLSq5yS9P6dzltQCkHPDc6ec3XD5+X\n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\niEeI+AAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1106,10 +1129,10 @@ " | The OpenMC Monte Carlo Code\n", " Copyright | 2011-2017 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | 966169de084fcfda3a5aaca3edc0065c8caf6bbc\n", - " Date/Time | 2017-03-09 09:18:56\n", - " OpenMP Threads | 8\n", + " Version | 0.9.0\n", + " Git SHA1 | da61fb4a55e1feaa127799ad9293a766161fbb3e\n", + " Date/Time | 2017-12-11 17:00:35\n", + " OpenMP Threads | 4\n", "\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -1117,10 +1140,10 @@ "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 0.82605 +/- 0.00103\n", - " k-effective (Track-length) = 0.82596 +/- 0.00103\n", - " k-effective (Absorption) = 0.82503 +/- 0.00075\n", - " Combined k-effective = 0.82528 +/- 0.00067\n", + " k-effective (Collision) = 0.82313 +/- 0.00100\n", + " k-effective (Track-length) = 0.82363 +/- 0.00102\n", + " k-effective (Absorption) = 0.82532 +/- 0.00076\n", + " Combined k-effective = 0.82484 +/- 0.00067\n", " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] @@ -1186,12 +1209,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/nelsonag/git/openmc/openmc/tallies.py:1835: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1798: RuntimeWarning: invalid value encountered in true_divide\n", " self_rel_err = data['self']['std. dev.'] / data['self']['mean']\n", - "/home/nelsonag/git/openmc/openmc/tallies.py:1836: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1799: RuntimeWarning: invalid value encountered in true_divide\n", " other_rel_err = data['other']['std. dev.'] / data['other']['mean']\n", - "/home/nelsonag/git/openmc/openmc/tallies.py:1837: RuntimeWarning: invalid value encountered in true_divide\n", - " new_tally._mean = data['self']['mean'] / data['other']['mean']\n" + "/home/romano/openmc/openmc/tallies.py:1800: RuntimeWarning: invalid value encountered in true_divide\n", + " new_tally._mean = data['self']['mean'] / data['other']['mean']\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another Universe instance already exists with id=0.\n", + " warn(msg, IDWarning)\n" ] } ], @@ -1248,10 +1273,10 @@ " | The OpenMC Monte Carlo Code\n", " Copyright | 2011-2017 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | 966169de084fcfda3a5aaca3edc0065c8caf6bbc\n", - " Date/Time | 2017-03-09 09:19:06\n", - " OpenMP Threads | 8\n", + " Version | 0.9.0\n", + " Git SHA1 | da61fb4a55e1feaa127799ad9293a766161fbb3e\n", + " Date/Time | 2017-12-11 17:00:59\n", + " OpenMP Threads | 4\n", "\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -1259,10 +1284,10 @@ "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 0.83752 +/- 0.00102\n", - " k-effective (Track-length) = 0.83708 +/- 0.00104\n", - " k-effective (Absorption) = 0.83678 +/- 0.00077\n", - " Combined k-effective = 0.83684 +/- 0.00068\n", + " k-effective (Collision) = 0.83745 +/- 0.00108\n", + " k-effective (Track-length) = 0.83712 +/- 0.00108\n", + " k-effective (Absorption) = 0.83694 +/- 0.00078\n", + " Combined k-effective = 0.83695 +/- 0.00070\n", " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] @@ -1353,8 +1378,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Isotropic to CE Bias [pcm]: 1195.9\n", - "Angle to CE Bias [pcm]: 40.4\n" + "Isotropic to CE Bias [pcm]: 1394.6\n", + "Angle to CE Bias [pcm]: 183.4\n" ] } ], @@ -1389,9 +1414,9 @@ "outputs": [ { "data": { - "image/png": "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\nAajbsJNqTdi0AAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1448,7 +1473,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 41, @@ -1457,9 +1482,9 @@ }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAt0AAAFfCAYAAACSp3C8AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xm4JGV5///3h2ETBhhgUNlxwQU1oo5gYmIQg4Ir7qCC\nuKGJJJq4BeM3GBI05ufPheCSiSIiCCqK8lUUjYKoEWUwqCCiiBjGAQFhWAUF7u8fVWem+3DmnD7Q\nfeos79d11TXdtd5dM/P03U/d9VSqCkmSJEmjs17XAUiSJEnznUm3JEmSNGIm3ZIkSdKImXRLkiRJ\nI2bSLUmSJI2YSbckSZI0Yibd6pNkpyQ3JVnU0fHfnuSESZa/OMlXZzImSYNLckiSb3cdx0xIclaS\nV3Ydx3QkuSzJX0yy/MtJXjqTMUkLhUn3DJqqsRtwHyNt5Kvqf6tqcVXdMc24DklSSd4zbv7+7fzj\nphtLkl3abdfvie/EqnryFNstS/LFJNclWZ3kJ0mOSrLldGOQ5rO2PbkuyUZdxwKQZK8kd7Y//G9K\nsjLJp5M8tuvYRmmQHyrt31UleeS4+Z9v5+91N457l06Oqtqvqj4+yTZJcliSHyW5JcmVbWwHTPf4\n0kJj0j3P9CaoHfgF8MJxMRwM/GymAkjyJ8BZwHeAh1TVEmBf4HbgkevYpstzJnUiyS7AnwEFPLPT\nYPqtqqrFwGbA44CfAt9K8qRuw5oVfkbTpgKQZGuac3T1DMZwNPB64A3A1sD2wNto2tm7aJN0cw0J\nk+7OJHlgkm8muT7JNUk+1bPsT5Kc2y47t00kSXIUzZfkMW0v0DHt/Ery2iQ/B34+2T7aZWcleWeS\n77fLv5Bkq3ZZX+9ykq2SfCzJqrZH7POTfKwrgR8DTxnbFvgT4LSeY++VZOW4c7GuKwBnt3+ubj/v\nHw/QI/RvwMeq6p1V9RtY03t/RFWd1R7vkCTfSfLeJNcCb0+yXpK3JflVkquSHJ9ki0FibnuLTkny\nqSQ3JvnB+N4oaRY6GDgHOA7oKydIclySDyT5Uvtv+ntJHtCz/MlJLm7bjw+2bdmEV+CSPCTJ15Jc\n227zgkGCq8bKqvpH4CPAuwbZZxv7h9vlN7ax7TyNbSf73Psk+Wn7uY8BMu6zvjzJRW1beca441aS\n1yT5ebv8A21C+lDgw8Aft+3c6klOy4k0HRtj5X8HAqcCvx/3Gf6l5/1d2q92/r7AW9v93ZTkh+38\ndV5NTfIg4K+AA6rqa1X1u6q6o6q+XVWH9Kx3Vpqri98BbgHun2S7JKe15/2SJK8aNOa2vT08zVXL\n69rvpI0nOU/SrGTS3Z1/Br4KbAnsAPw7rElUv0TTm7A18B7gS0m2rqp/AL4FHNaWgBzWs7/9gT2B\n3SbbR8/6BwMvB7aj6QU+eh1xfgLYBHgYcG/gvVN8ruNZ2xNzAPAF4LYptlmXJ7R/Lmk/73cnWznJ\npsAfA58dYN97ApfSfKajgEPa6YnA/YHFwDHTiPVZwGeArYBPAp9PssE0tpdm2sE0SdyJwFOS3Gfc\n8gOBf6Jpoy6h+X9CkqXAKcDhNO3LxTQ/ru+i/T/5NZr/E/du9/nBJA+bZqyfAx6dZNMB9/limjZ2\nKXB++xkHjWeyz/1Zml7dpTRX9h7f81n3p0linwNsQ9NWnzTuczwdeCzNVbcXAE+pqouA1wDfbdu5\nJZOch1XAT4CxEruDadrcaauqrwDvAD7VHneQjoK9gcurasUA6x4EHEpzxeJXNOdiJc13zvOAd2R6\nVy9eTNOh8wDgQTR/D9KcYtLdnT8AOwPbVdWtVTXWe/s04OdV9Ymqur2qTqK5vPqMKfb3zqq6tqp+\nN+A+PlFVF1TVzcD/AV6QcTdPJtkW2A94TVVdV1V/qKpvThHHqcBebS/x3f5CuJu2pPk3feXYjCT/\nlqau++YkvY30qqr69/b8/I6mQX9PVV1aVTfRJBQHZPDSk/Oq6pSq+gPNj5yNaS77SrNOkj+laX8+\nXVXn0SSQLxq32ueq6vtVdTtN0rp7O/+pwIVV9bl22dH0/J8b5+nAZVX1sfb/2g9oEtfnTTPkVTS9\nyksG3OeXqursqroN+AeaXuQdB9x2ss/9k57/5+8b97lfTdMOX9Ru+w5g997ebuBfq2p1Vf0vcGbP\nvqfjeODgJA+m6ZCYtDNiyJYy7u86Td396iS3jvusx1XVhe25uC/wp8Bb2u+782muXhw0jWMfU1WX\nV9W1ND+EDrxnH0WaeSbd3XkzzZfI95NcmOTl7fztaHoFev2Kpm5uMpf3vB5kH5ePW7YBTYPaa0fg\n2qq6bopjr9EmsF+i7Q2qqu8Muu10JXlr1t5w9WHgOuBOYNueeN7c9hydCvQm0Jf37+0u5+xX7frj\ne//WZc3+qupO1vboSLPRS4GvVtU17ftPMq7EhP7k6haaqz/Q/Lvu/fdeNP/eJ7IzsGeblK1uSyde\nDNw3a0dKuinJTVPEuz1N7fnqyfbZs35vfDcB17ZxD7LtdD53bzuyM/D+nv1eS9PG97a769r3dHyO\npsf5r2muRI5M+9009nf0Z8Bv6WlfAapqB5rvjo3oL7cZ/510bVXd2DNvkO+1XuO/s2xfNed4A1lH\nqupK4FWwptfpv5KcTdOjs/O41XcCvjK26bp22fN6qn1Ak1D3LvsDcM24+ZcDWyVZUlWT1RmOdzzw\nDZpLtOPdTFOuAkDbu77NOvazrs/aLKx6B01v0hpJvkdzeffMKWIcv+/x52wnmrKb39A07lPFvGPP\n8vVoSoZWTRGDNOOS3IumtGFRkrEkcCNgSZJHVtUPp9jFFTT/vsf2l97341wOfLOq9lnH8kGTzmcD\nP6iqm5NMtU/o//+4mKbsa9UA8UzminH7DXdtL4+qqhPvxr4nbev6Vqy6JcmXgb+kKbUYr6+Npf8H\nxbSOW1V9ZUBJrqK5p2jZACUm47+TtkqyWU/ivRPw62nEPP47y/ZVc4493R1J8vwkY19U19E0UHcA\npwMPSvKiJOsneSGwG/DFdt3f0NQcT2aqfQC8JMluSTYBjgROGT9MYFVdAXyZpuZxyyQbJHkCU/sm\nsA9tnfo4PwM2TvK0tub5bTRf+BO5mqbneqrP2+vNwMuT/H2SewO05/l+U2x3EvC3Se7XfkmP1Tre\nPmDMj0nynLYc5fU0deznTCNuaabsT9PW7EZT3rA78FCaGuSDJ9luzJeAR6QZDnR94LWsO7H7Ik1b\ndFDbfmyQ5LFpbh6cVBrbJzkCeCVNvfSg+3xqkj9NsiFNbff3quryexJP+7kf1vP//G/Gfe4PA4eP\n1Ycn2SLJ8wfYLzTt+g5tvIN4K/DnVXXZBMvOp/n8WyW5L017NNlxd8mAo4tU1cXAfwAnp7mp9F5t\nJ8SENf09210O/DfwziQbJ/kj4BW0tfYDxvzaJDukuWfprcCnJlhHmtVMurvzWOB77WXV04DXVdUv\nq+q3NHWHb6C5lPdm4Ok9l4HfDzwvzR3cE978OMA+oLkseRzN5c6Nab5AJnIQTS/4T4GrmLwBHzt+\nVdXX29q78cuup7n7/SM0vRw3s45L01V1C03t3nfaS7ZT1ki3tfF709yE+bP2Mu9XaIYRnOhHwJhj\nac7J2cAvgVtpLt8OGvMXgBfS/IA6CHhOW/cpzTYvpRnh53+r6sqxiebG4RdPdR9D2448n2akoN/S\nJO8rmOCG6bZX88k0N1Wvomlv3sW6f2gDbNe2izcB5wKPAPaqqq9OY5+fBI6gKfF4DE0Jyd2NZ/zn\n/tf2c+9KMzTp2PJT232dnOQG4AKae2IG8Q3gQuDKJNdMtXJVreq5D2i8TwA/BC6juVl/suT0M+2f\nv03ygwFjfS1NHf97aM7vSpofNi8E/neS7Q4EdqE576cCR1TV16YR8yfbZZe2079MsI40q6UpS9NC\nkuQs4ISq+kjXscwHSd4OPLCqXtJ1LNJMa3tJVwIvrqqpyrpmIp7jgJVV5egW80SSy4BXVtV/dR2L\ndE/Y0y1JmpYkT0myJM2TLN9KcwOd5VSSNAmTbknSdP0xzTCD19AMRbp/O3KRJGkdLC+RJEmSRsye\nbkmSJGnETLo70vNgiEVTr73OfdyUZDrD6S1YSXZJUlONzDDJ9m9N4o2n0j1guzezbPek2cWke8SS\nXJbkd+l58lqS7drhuhaPHxt7OtrtLx1mvHCXmK9Mclw7dvUg296jRn6KfZ+V5lHDNyW5Jsnn0jyq\nftjH2StJ35CAVfWOqnrlsI8lzUe2e0ONy3ZPmidMumfGM9ovirFpLjxJ6xlVtZjmwRmPAg7vOJ4x\nh7VxPZDmaXbv7jgeSROz3Rse2z1pHjDp7sj4npEkhyS5NMmNSX6Z5MXt/Acm+WaS69tejk/17KOS\nPLB9vUWS45NcneRXSd429pSxdt/fTvLu9qE6v0wy0EMb2odmnEHzJTR23Kcl+Z8kNyS5vB2neszZ\n7Z+r256ZP263eXmSi9rjn5Fk53Z+krw3yVXtZ/xRkocPENdq4PPj4lovzZMof5Hkt0k+nebpZXeR\n5GVtPDe25/3V7fxNaZ7CuV1vD12Styc5oV3nK0kOG7e/HyZ5Tvv6IUm+luTaJBcnecFUn0daCGz3\nbPekhcykexZoG7yjgf2qajOaR+qe3y7+Z5qncG0J7MC6n6r478AWNI9M/3Oaxzm/rGf5nsDFwFKa\nJ8l9NEkGiG0HmqeqXdIz++Z2/0uApwF/mWT/dtnYY+KXtL1b322XvRV4DrANzeOmT2rXe3K7zYPa\n/b2Q5mlvU8W1dbu/3rj+huYR138ObEfzdMgPrGMXV9E8tXNzmvP03iSPrqqb28+7apIeuk/SPF1t\nLJbdgJ2BL7V/l19r17l3u94H0z4aWlLDds92T1pwqspphBPNY21vAla30+fb+bsABawPbNouey5w\nr3HbHw8sB3aYYN9Fc7lxEc0jmHfrWfZq4Kz29SHAJT3LNmm3ve8UMd/Yrvd1mi+TdX3G9wHvHf+5\nepZ/GXhFz/v1gFtoGuy9gZ8BjwPWm+JcntVud317jPOBnXqWXwQ8qef9tjSPsF9/orjG7fvzwOva\n13vRPNGud/nbaZ7iCbAZzRfwzu37o4Bj29cvBL41btv/oHnkcef/Hp2cZmKy3bPds91zcrrrZE/3\nzNi/qpa00/7jF1bTy/BC4DXAFUm+lOQh7eI30zzt7ftJLkzy8gn2vxTYEPhVz7xfAdv3vL+y53i3\ntC8nu0lo/2p6n/YCHtIeA4AkeyY5s72ke30b99KJdwM0XzLvT7I6yWrg2vYzbV9V3wCOoemZ+U2S\n5Uk2n2Rff1NVWwB/xNpesN7jnNpznIuAO4D7jN9Jkv2SnNNeCl0NPHWKz7BGVd0IfAk4oJ11AHBi\nTwx7jsXQ7vvFwH0H2bc0j9ju2e7Z7kk9TLpniao6o6r2oeml+Cnwn+38K6vqVVW1HU0vzgfH6hl7\nXEPTs7Fzz7ydgF8PIa5vAsfRf+POJ4HTgB3bL4IP03yZQNOrMt7lwKt7voCXVNW9quq/22McXVWP\nAR5Gc7n1TQPE9WPgX4AP9FwuvpzmUnXvcTauqr7zkObR1Z9tP9N9qmoJcPoUn2G8k4AD29rNewFn\n9sTwzXExLK6qvxxgn9KCYrtnuyctJCbds0CS+yR5ZlsXdxvNJc472mXPb+sLoanVq7FlY6oZfuvT\nwFFJNmtv1vk74IQhhfg+YJ8kYzfvbAZcW1W3JtkDeFHPulcDd9LUWI75MHD4WH1fmpufnt++fmzb\ng7QBzaXLW8d/vkl8nKZ+8Jk9xzmq52albZI8a4LtNgQ2amO9Pc3NVU/uWf4bYOskW0xy7NNpvuyP\nBD5VVXe2878IPCjJQUk2aKfHJnnogJ9JWhBs92z3pIXGpHt2WA94A7CK5hLknwN/1S57LPC9JDfR\n9LK8rqp+OcE+/pqm8b4U+DZNr8yxwwiuqq6mqbH8P+2svwKOTHIj8I80X3xj695CU+v3nfYy4+Oq\n6lTgXcDJSW4ALqC5aQeaG3r+k+aL9Vc0NxMNNBxWVf2e5kassbjeT3OOvtrGdg7NjVTjt7uR5uaj\nT7fHfVG73djyn9L06FzafobtJtjHbcDngL+gOde9+34yzaXXVTSXt99F82UnaS3bPds9aUFJ1SBX\nlCRJkqS5JcmxNKP2XFVVdxmas72X5GPAo4F/qKp39yzbl+aH7SLgI1X1r+38+wEnA1sBPwAOan8Q\nT8qebkmSJM1XxwH7TrL8WpqrQH1Xm5IsornZeT9gN5r7GXZrF7+LZvSiXWmuHL1ikEBMuiVJkjQv\nVdXZNIn1upZfVVXn0tyY3WsPmmFHL217sU8GntXexLw3cEq73sdpxsqf0vrTDV6SJEnz175JXdN1\nEAM6Dy6kuRl5zPKqWj6EXW9PMzLPmJU090tsDayuqtt75m/PAEy6JUmStMY1wIqugxhQ4NaqWjaa\nXd9FTTJ/Sibduot2xIA/qqpLu45FkmaC7Z40znpzpAL5zjunXufuWQns2PN+B5rRea4BliRZv+3t\nHps/pTlyRue2JJcl+Yt7sH2S/E2SC5LcnGRlks8kecQQYjsrySt757UPNZgzXzztZ7g1yU090//t\nOi5pIbPdGy3bPY3ceuvNjWl0zgV2TXK/JBvSDIl5WjXD/p0JPK9d76XAFwbZoT3dc8P7gacBrwK+\nQzN0zbPbeT/uMK7Z5LCq+sgoD9Dzq1bS6NnuTc12T6ORzJ2e7ikkOQnYC1iaZCVwBLABQFV9OMl9\naappNgfuTPJ6YLequiHJYcAZNO3PsVV1Ybvbt9CMwf8vwP8AHx0omKpyGuEEfILmSWW/o3ni2pvb\n+c+kKf5fDZwFPHQd2+9K86SyPSY5xhY0D3G4muZBC28D1muXHULz0Ih30wxr80uaRwZD8zCHO2hu\nQLgJOKadX8AD29fH0QyZ8yXgRuB7wAPaZbu0667fE8tZwCvb1+u1sfwKuKqNcYt22V7AynGf4zLg\nL9rXe7T/CW6geVLaeyb5/GuOOcGyvWguEb2hjeEK4GU9yzdqz83/tsf5MHCvcdu+heZhD59o57+5\n3c8q4JVj54vmgR6/GXc+nguc3/W/QyenmZxs92z3bPfm9vSYpGrDDefEBKzo+nwNOs2PnzGzWFUd\nRNOwPaOay5f/luRBNE/+ej2wDc2jdf9ve/livCfRNNLfn+Qw/07zBXR/mqe6HQy8rGf5nsDFwFLg\n34CPJklV/QPwLZreksVVddg69n8g8E/AlsAlNF9agziknZ7YxrYYOGbAbd8PvL+qNgceQM/T3+6G\n+9Kcn+1pxtL8QJIt22XvAh4E7E7zBbI9zdPmerfdiubRx4e2A+X/Hc0T2R5Ic74BqGbIod8C+/Rs\n/xKaBERaMGz3bPew3Zv7ui4b6b68ZOjmVrTzxwuBL1XV16rqDzQ9DvcC/mSCdbem6V2YUDt4+wuB\nw6vqxqq6DPj/gYN6VvtVVf1nVd1BM57ktsB9phHv56rq+9VcYjyRpqEexItpemouraqbgMOBA5IM\nUtb0B+CBSZZW1U1Vdc4U6x/dPrp4bPrncfs6sqr+UFWn0/RuPbgda/NVwN9W1bXVPMr4HTR1W2Pu\nBI6oqtuq6nfAC4CPVdWF1Tz6+Z/GxfFxmi8ckmwFPIWexyVLC5jt3tRs9zQ7jJWXzIVpDplb0c4f\n29FcegSgqu6kGQtyonEef0vzZbEuS4ENe/fXvu7d15U9x7qlfbl4GvFe2fP6lmls2/c529frM9gX\n3ytoemJ+muTcJE8HSPLhnpuG3tqz/t9U1ZKe6f/0LPtt9dckjn2GbYBNgPPGvrSAr7Tzx1xdVb3j\nf25H/7idva8BTgCekWQxzRfVt6pqncmDtIDY7k3Ndk+zR9fJtEm37qbx4zeuorlsBzR36dMMS/Pr\nCbb9OrBDknWNQXkNTY/Gzj3zdlrHvgaJbTpubv/cpGfefXte933ONq7baer/bu7dru25WtPoV9XP\nq+pA4N40l0JPSbJpVb2mvSS8uKrecQ9ih+bc/Q54WM+X1hZV1fvlOv78XEEzPNCY3uGEqKpfA9+l\nueHrILzEqoXLdm9tXLZ7mnu6TqZNunU3/Yamtm/Mp4GnJXlSkg1obna5Dfjv8RtW1c+BDwInJdkr\nyYZJNk5yQJK/by+dfho4KslmSXamqb074W7GNrCquprmS+4lSRYleTlNHeKYk4C/bYfbWUxzCfNT\nbe/Lz4CNkzytPQdvo7m5B4AkL0myTdsbtrqdfcfdiXOS+O8E/hN4b5J7t8fdPslTJtns08DLkjw0\nySb010GOOZ7mpqNHAKcOM2ZpDrHds93TXGV5yUjMrWjnrncCb2sv5b2xqi6mqX/7d5peh2fQ3HD0\n+3Vs/zc0N+J8gKYh/gVNj8LYmKx/TdODcinNHfufBI4dMLb3A89Lcl2So6f9yZrawDfRXA5+GP1f\noMfS9HicTTN6wK1trFTV9cBfAR+h+QK7meaO+TH7AhemeWDF+4EDxl3uHO+YcePVnjdg/G+huUnq\nnCQ3AP8FPHhdK1fVl4GjacbovISmdwea5GHMqTQ9XadW1c1IC5Ptnu2epB6puidX2aSFLclDgQuA\njXrrJ5P8Anh1Vf1XZ8FJ0gjY7s1/y9Zfv1ZssUXXYQwk1157Xo3mMfBD58NxpGlK8mya8Xs3pam7\n/L/jvnieS1MT+Y1uIpSk4bLdW2Dm0cNxZhOTbmn6Xk3z8Iw7gG/SXC4GmkczA7sBB7W1k5I0H9ju\nLTQm3UNn0i1NU1XtO8myvWYwFEmaEbZ7C5BJ99CZdEuSJGkty0tGwqRbkiRJ/Uy6h24kSXeytGCX\nUex6YJtsMvU6CyEGAAeoaSyezrPoRmTp5usaHW2G3XLL1OuM0GVXXcU1N9yQToMYsqUbbVS7bLpp\nt0HMhi/JjTaaep2ZsPHGXUcwO2LILPhvNlu+hO7sttz8siuu4JrVq2fBX4i6MqKe7l2AFaPZ9YAe\n/vBODw/A7rt3HUHj1slGeV1A/vRPu44AXvXkX0290kw4//xOD7/sDW/o9PijsMumm7Jin326DWI2\n/NJ/wAOmXmcm7Lpr1xHAwx7WdQSwaFHXEcAdQ32+z913442dHn7Zy1/e6fGnxfKSkbC8RJIkSf1M\nuofOpFuSJEn9TLqHzqRbkiRJa1leMhIm3ZIkSepn0j10nlFJkiRpxOzpliRJ0lqWl4yESbckSZL6\nmXQPnUm3JEmS+pl0D51JtyRJktayvGQkTLolSZLUz6R76Ey6JUmStJY93SMx5RlN8uAk5/dMNyR5\n/UwEJ0ldsN2TJA3blD3dVXUxsDtAkkXAr4FTRxyXJHXGdk/SgmdP99BNt7zkScAvqupXowhGkmYh\n2z1JC49J99BN94weAJw0ikAkaZay3ZO0sIzVdM+FacqPkmOTXJXkgnUsT5Kjk1yS5EdJHt3Of+K4\nMsNbk+zfLjsuyS97lu0+yGkduKc7yYbAM4HD17H8UODQ5t1Og+5Wkmat6bR7O22yyQxGJkkjNn96\nuo8DjgGOX8fy/YBd22lP4EPAnlV1JmvLDLcCLgG+2rPdm6rqlOkEMp3ykv2AH1TVbyZaWFXLgeVN\ncMtqOkFI0iw1cLu3bKutbPckzQ/zaPSSqjo7yS6TrPIs4PiqKuCcJEuSbFtVV/Ss8zzgy1V1yz2J\nZTpn9EC8xCppYbHdk6T5bXvg8p73K9t5vSYqMzyqLUd5b5KNBjnQQEl3kk2AfYDPDbK+JM11tnuS\nFrSua7UHr+lemmRFz3ToND9pJpi35splkm2BRwBn9Cw/HHgI8FhgK+AtgxxooPKStjt960HWlaT5\nwHZP0oI2d8pLrqmqZfdg+5XAjj3vdwBW9bx/AXBqVf1hbEZP6cltST4GvHGQA/lESkmSJK01j2q6\nB3AacFiSk2lupLx+XD33gYy7mX6s5jtJgP2BCUdGGc+kW5IkSf3mSdKd5CRgL5oylJXAEcAGAFX1\nYeB04Kk0o5PcArysZ9tdaHrBvzlutycm2YamNOV84DWDxGLSLUmSpLXmUU93VR04xfICXruOZZdx\n15sqqaq9704sJt2SJEnqN0+S7tnEMypJkiSNmD3dkiRJ6mdP99CZdEuSJGmteVTTPZuYdEuSJKmf\nSffQmXRLkiRpLXu6R8KkW5IkSf1MuoduJEn3llvCPvuMYs+D2/4uoyrOvG237TqCxi67dB0BXHNN\n1xHAkiVdRwCXr7dz1yEAsOPGP+02gPnYmCewaFG3MbzgBd0eH+AJT+g6gsYFAz0gbqSu2PrhXYfA\nJpt0HQH85CddR9B43OM6DmDTTTsOQF2zp1uSJElrWV4yEibdkiRJ6mfSPXQm3ZIkSepn0j10Jt2S\nJElay/KSkTDpliRJUj+T7qEz6ZYkSdJa9nSPhGdUkiRJGjF7uiVJktTPnu6hM+mWJElSP5PuoTPp\nliRJ0lrWdI+ESbckSZL6mXQPnUm3JEmS1rKneyQ8o5IkSdKIDdTTnWQJ8BHg4UABL6+q744yMEnq\nku2epAXNnu6hG7S85P3AV6rqeUk2BDYZYUySNBvY7klauEy6h27KpDvJ5sATgEMAqur3wO9HG5Yk\ndcd2T9KCZk33SAzS031/4GrgY0keCZwHvK6qbh5pZJLUHds9SQubSffQDXJG1wceDXyoqh4F3Az8\n/fiVkhyaZEWSFbfddvWQw5SkGTXtdu/qW2+d6RglaTTGerrnwjSHDBLtSmBlVX2vfX8KzZdRn6pa\nXlXLqmrZRhttM8wYJWmmTbvd22bjjWc0QEkaqa6T6YWYdFfVlcDlSR7cznoS8JORRiVJHbLdkyQN\n26Cjl/w1cGJ7B/+lwMtGF5IkzQq2e5IWrjnWizwXDJR0V9X5wLIRxyJJs4btnqQFax6NXpLkWODp\nwFVV9fAJlodmiNinArcAh1TVD9pldwA/blf936p6Zjv/fsDJwFbAD4CD2lGuJjU/zqgkSZKGp+ta\n7eHVdB8H7DvJ8v2AXdvpUOBDPct+V1W7t9Mze+a/C3hvVe0KXAe8YqBTOshKkiRJWiDm0eglVXU2\ncO0kqzwLOL4a5wBLkmy77lOTAHvT3GAP8HFg/0FO66A13ZIkSVoo5kl5yQC2By7veb+ynXcFsHGS\nFcDtwL+8WLmqAAAdlUlEQVRW1eeBrYHVVXX7uPWnZNItSZKkfnMn6V7aJsZjllfV8mlsnwnmVfvn\nTlW1Ksn9gW8k+TFwwyTrT8qkW5IkSXPVNVV1T256Xwns2PN+B2AVQFWN/XlpkrOARwGfpSlBWb/t\n7V6z/lTmzM8YSZIkzYB5VNM9gNOAg9N4HHB9VV2RZMskGzWnI0uBxwM/qaoCzgSe127/UuALgxzI\nnm5JkiT1mzvlJZNKchKwF00ZykrgCGADgKr6MHA6zXCBl9AMGTj2TIaHAv+R5E6aTup/raqxh6S9\nBTg5yb8A/wN8dJBYTLolSZK01jwap7uqDpxieQGvnWD+fwOPWMc2lwJ7TDcWk25JkiT1mydJ92xi\n0i1JkqR+Jt1D5xmVJEmSRmwkPd3rrw9bbTWKPQ/ukY/s9vgAK1ZMvc5MeNOLft11CJxw5kDjxo/U\n81e9v+sQYLvDuo6g8atNuz3+fOxBWbwY/uzPOg3h+j/Zr9PjA3zu011H0HjZVld2HQI/vaXrCOCJ\nvz+j6xDY7iFP6ToEAHLnHV2HMHfMo5ru2cTyEkmSJPUz6R46k25JkiStZU/3SJh0S5IkqZ9J99CZ\ndEuSJKmfSffQmXRLkiRpLctLRsIzKkmSJI2YPd2SJEnqZ0/30Jl0S5IkaS3LS0bCpFuSJEn9TLqH\nzqRbkiRJ/Uy6h86kW5IkSWtZXjISnlFJkiRpxAbq6U5yGXAjcAdwe1UtG2VQktQ12z1JC5o93UM3\nnfKSJ1bVNSOLRJJmH9s9SQuP5SUjYU23JEmS+pl0D92gSXcBX01SwH9U1fIRxiRJs4HtnqSFyZ7u\nkRg06X58Va1Kcm/ga0l+WlVn966Q5FDgUIDFi3cacpiSNOOm1e7ttNVWXcQoSaNh0j10A53RqlrV\n/nkVcCqwxwTrLK+qZVW17F732ma4UUrSDJtuu7fN4sUzHaIkjc56682NaQ6ZMtokmybZbOw18GTg\nglEHJkldsd2TJA3bIOUl9wFOTTK2/ier6isjjUqSumW7J2nhsqZ7JKZMuqvqUuCRMxCLJM0KtnuS\nFjyT7qFzyEBJkiStZU/3SJh0S5IkqZ9J99CZdEuSJKmfSffQeUYlSZKkETPpliRJ0lpjNd1zYZry\no+TYJFclmXDY1zSOTnJJkh8leXQ7f/ck301yYTv/hT3bHJfkl0nOb6fdBzmtlpdIkiSp3/wpLzkO\nOAY4fh3L9wN2bac9gQ+1f94CHFxVP0+yHXBekjOqanW73Zuq6pTpBGLSLUmSpLXm0eglVXV2kl0m\nWeVZwPFVVcA5SZYk2baqftazj1VJrgK2AVava0dTmR9nVJIkScPTddnIzD0Gfnvg8p73K9t5ayTZ\nA9gQ+EXP7KPaspP3JtlokAPZ0y1JkqR+c6ene2mSFT3vl1fV8mlsnwnm1ZqFybbAJ4CXVtWd7ezD\ngStpEvHlwFuAI6c6kEm3JEmS1ppb5SXXVNWye7D9SmDHnvc7AKsAkmwOfAl4W1WdM7ZCVV3Rvrwt\nyceANw5yoDlzRiVJkqQhOw04uB3F5HHA9VV1RZINgVNp6r0/07tB2/tNkgD7AxOOjDLeSHq6N9gA\ntttuFHse3IYbdnt8gH9//S+mXmkmbP+AriPgJS/pOgK4+urXdR0C27zq5V2H0PjAB7o9/iabdHv8\nUVi0CDbbrNMQbr+908MD8LLFn5l6pZnwrOd3HQFP7DoA4IQTntJ1CLzkD1/sOgQAfnzD0zs9/u9+\n1+nhp2/u9HRPKslJwF40ZSgrgSOADQCq6sPA6cBTgUtoRix5WbvpC4AnAFsnOaSdd0hVnQ+cmGQb\nmtKU84HXDBKL5SWSJElaa26Vl0yqqg6cYnkBr51g/gnACevYZu+7E4tJtyRJkvrNk6R7NjHpliRJ\nUj+T7qEz6ZYkSdJa86i8ZDbxjEqSJEkjZk+3JEmS+tnTPXQm3ZIkSVrL8pKRMOmWJElSP5PuoTPp\nliRJUj+T7qEz6ZYkSdJalpeMhEm3JEmS+pl0D51nVJIkSRqxgXu6kywCVgC/rqqnjy4kSZodbPck\nLUiWl4zEdMpLXgdcBGw+olgkabax3ZO0MJl0D91AZzTJDsDTgI+MNhxJmh1s9yQtaOutNzemOWTQ\nnu73AW8GNhthLJI0m9juSVqYLC8ZiSmT7iRPB66qqvOS7DXJeocChwJsscVOQwtQkmba3Wn3dtp6\n6xmKTpJmgEn30A1yRh8PPDPJZcDJwN5JThi/UlUtr6plVbVs0023GXKYkjSjpt3ubbO5Zd+SpHWb\nMumuqsOraoeq2gU4APhGVb1k5JFJUkds9yQtaGPlJXNhmkN8OI4kSZL6zbGEdi6YVtJdVWcBZ40k\nEkmahWz3JC1IJt1DZ0+3JEmS1nL0kpEw6ZYkSVI/k+6hM+mWJEnSWvZ0j4RnVJIkSRoxe7olSZLU\nz57uoTPpliRJ0lqWl4yESbckSZL6mXQPnUm3JEmS+pl0D51JtyRJktayvGQkPKOSJEmal5Icm+Sq\nJBesY3mSHJ3kkiQ/SvLonmUvTfLzdnppz/zHJPlxu83RSTJILCbdkiRJ6rfeenNjmtpxwL6TLN8P\n2LWdDgU+BJBkK+AIYE9gD+CIJFu223yoXXdsu8n2v8ZIyktuuw0uvXQUex7c/e7X7fEBvn7ZA7oO\nAYAnzY4wOrcNV3cdAr/9/47tOgQAtv7FhD/4Z85tt3V7/FHYdFPYc89OQ9hoo04PD8CZS5/fdQgA\nPLHrAGaJrbfuOgL4z5VP7zoEAPbbvdvjL1rU7fGnZR6Vl1TV2Ul2mWSVZwHHV1UB5yRZkmRbYC/g\na1V1LUCSrwH7JjkL2LyqvtvOPx7YH/jyVLFY0y1JkqR+8yTpHsD2wOU971e28yabv3KC+VMy6ZYk\nSVKfYqAy5dlgaZIVPe+XV9XyaWw/0QetuzF/SibdkiRJ6nPnnV1HMLBrqmrZPdh+JbBjz/sdgFXt\n/L3GzT+rnb/DBOtPacFcO5AkSdLUqpqkey5MQ3AacHA7isnjgOur6grgDODJSbZsb6B8MnBGu+zG\nJI9rRy05GPjCIAeyp1uSJEnzUpKTaHqslyZZSTMiyQYAVfVh4HTgqcAlwC3Ay9pl1yb5Z+DcdldH\njt1UCfwlzago96K5gXLKmyjBpFuSJEnjzKHykklV1YFTLC/gtetYdixwl2HHqmoF8PDpxmLSLUmS\npDXGyks0XCbdkiRJ6mPSPXwm3ZIkSepj0j18Jt2SJElaw/KS0XDIQEmSJGnE7OmWJElSH3u6h2/K\npDvJxsDZwEbt+qdU1RGjDkySumK7J2khs7xkNAbp6b4N2LuqbkqyAfDtJF+uqnNGHJskdcV2T9KC\nZtI9fFMm3e2g4Te1bzdopxplUJLUJds9SQuZPd2jMVBNd5JFwHnAA4EPVNX3RhqVJHXMdk/SQmbS\nPXwDJd1VdQewe5IlwKlJHl5VF/Suk+RQ4FCATTfdaeiBStJMmm67t9N223UQpSSNhkn38E1ryMCq\nWg2cBew7wbLlVbWsqpZtvPE2QwpPkro1aLu3zVZbzXhskqS5Y8qkO8k2bU8PSe4F/AXw01EHJkld\nsd2TtJCN1XTPhWkuGaS8ZFvg421943rAp6vqi6MNS5I6ZbsnaUGbawntXDDI6CU/Ah41A7FI0qxg\nuydpIXP0ktHwiZSSJEnqY9I9fCbdkiRJ6mPSPXzTGr1EkiRJ0vTZ0y1JkqQ1rOkeDZNuSZIk9THp\nHj6TbkmSJK1hT/domHRLkiSpj0n38Jl0S5IkqY9J9/CZdEuSJGkNy0tGwyEDJUmSpBGzp1uSJEl9\n7OkevpEk3VtsAU996ij2PLglS7o9PsCTLvmPrkMA4KY9X911CCy+7vKuQ4D73rfrCNj6Oc/oOoTG\n+97X7fHXm4cX2f7wB/jNbzoNYfEuu3R6fIAnXnta1yEAcOqpz+06BNafBd1aV17ZdQTwqj1+2HUI\nAFx04yM7Pf5cSmItLxmNWdAkSJIkaTYx6R4+k25JkiT1Mekevnl4jVeSJEl311h5yVyYBpFk3yQX\nJ7kkyd9PsHznJF9P8qMkZyXZoZ3/xCTn90y3Jtm/XXZckl/2LNt9qjjs6ZYkSdK8lGQR8AFgH2Al\ncG6S06rqJz2rvRs4vqo+nmRv4J3AQVV1JrB7u5+tgEuAr/Zs96aqOmXQWEy6JUmS1GcelZfsAVxS\nVZcCJDkZeBbQm3TvBvxt+/pM4PMT7Od5wJer6pa7G4jlJZIkSVpjjpWXLE2yomc6dNzH2R7oHUJt\nZTuv1w+BsSGPng1slmTrcescAJw0bt5RbUnKe5NsNNV5tadbkiRJfeZQT/c1VbVskuWZYF6Ne/9G\n4JgkhwBnA78Gbl+zg2Rb4BHAGT3bHA5cCWwILAfeAhw5WaAm3ZIkSeozh5LuqawEdux5vwOwqneF\nqloFPAcgyWLguVV1fc8qLwBOrao/9GxzRfvytiQfo0ncJ2XSLUmSpDXm2cNxzgV2TXI/mh7sA4AX\n9a6QZClwbVXdSdODfey4fRzYzu/dZtuquiJJgP2BC6YKxKRbkiRJfeZL0l1Vtyc5jKY0ZBFwbFVd\nmORIYEVVnQbsBbwzSdGUl7x2bPsku9D0lH9z3K5PTLINTfnK+cBrporFpFuSJEnzVlWdDpw+bt4/\n9rw+BZhw6L+quoy73nhJVe093ThMuiVJkrTGPCsvmTWmTLqT7AgcD9wXuBNYXlXvH3VgktQV2z1J\nC51J9/AN0tN9O/CGqvpBks2A85J8bdyTfCRpPrHdk7SgmXQP35RJdzskyhXt6xuTXERT2+KXj6R5\nyXZP0kJmecloTKumu72D81HA90YRjCTNNrZ7khYik+7hG/gx8O1g4Z8FXl9VN0yw/NCxR3DecMPV\nw4xRkjoxnXbv6tWrZz5ASdKcMVBPd5INaL54Tqyqz020TlUtp3kMJg94wLLxj9eUpDlluu3esoc8\nxHZP0rxgecloDDJ6SYCPAhdV1XtGH5Ikdct2T9JCZ9I9fIP0dD8eOAj4cZLz23lvbQcal6T5yHZP\n0oJm0j18g4xe8m2aR1xK0oJguydpIbO8ZDR8IqUkSZL6mHQPn0m3JEmS1rCnezQGHjJQkiRJ0t1j\nT7ckSZL62NM9fCbdkiRJWsPyktEw6ZYkSVIfk+7hM+mWJElSH5Pu4TPpliRJ0hqWl4yGo5dIkiRJ\nI2ZPtyRJkvrY0z18Jt2SJElaw/KS0TDpliRJUh+T7uEbSdK95Zbw/OfVKHY9sOtWp9PjA/xhr1d3\nHQIAi1/50q5DgA9+sOsI4DnP6ToCWL686wga55zT7fF/97tujz8KVXDHHd3GcPvt3R4fOG+X53Yd\nAgDPvurLXYfAMb/Yr+sQOOyR3+o6BM79/Z91HQIA227d7fHXm2N30Zl0D5893ZIkSVrD8pLRMOmW\nJElSH5Pu4ZtjFzskSZKkuceebkmSJK1heclomHRLkiSpj0n38FleIkmSpD533jk3pkEk2TfJxUku\nSfL3EyzfOcnXk/woyVlJduhZdkeS89vptJ7590vyvSQ/T/KpJBtOFYdJtyRJktYYKy+ZC9NUkiwC\nPgDsB+wGHJhkt3GrvRs4vqr+CDgSeGfPst9V1e7t9Mye+e8C3ltVuwLXAa+YKhaTbkmSJPXpOpke\nYk/3HsAlVXVpVf0eOBl41rh1dgO+3r4+c4LlfZIE2Bs4pZ31cWD/qQIx6ZYkSdJ8tT1wec/7le28\nXj8Exp7s9WxgsyRjj1PaOMmKJOckGUustwZWV9XYE8km2uddeCOlJEmS1phjo5csTbKi5/3yqup9\n/PNEjygf/9j0NwLHJDkEOBv4NTCWUO9UVauS3B/4RpIfAzcMsM+7MOmWJElSnzmUdF9TVcsmWb4S\n2LHn/Q7Aqt4VqmoV8ByAJIuB51bV9T3LqKpLk5wFPAr4LLAkyfptb/dd9jkRy0skSZLUp+ta7SHW\ndJ8L7NqONrIhcABwWu8KSZYmGcuJDweObedvmWSjsXWAxwM/qaqiqf1+XrvNS4EvTBXIlEl3kmOT\nXJXkgoE+miTNcbZ7khay+TR6SdsTfRhwBnAR8OmqujDJkUnGRiPZC7g4yc+A+wBHtfMfCqxI8kOa\nJPtfq+on7bK3AH+X5BKaGu+PThXLIOUlxwHHAMcPsK4kzQfHYbsnaQGbQ+UlU6qq04HTx837x57X\np7B2JJLedf4beMQ69nkpzcgoA5sy6a6qs5PsMp2dStJcZrsnaSGbYzdSzhlDq+lOcmg7pMqKq6++\neli7laRZq6/du/76rsORJM1iQxu9pB2eZTnAsmXLphw2RZLmur5278EPtt2TNG/Y0z18DhkoSZKk\nPibdw2fSLUmSpDWs6R6NQYYMPAn4LvDgJCuTvGL0YUlSd2z3JC10XQ8FOMRxumeNQUYvOXAmApGk\n2cJ2T9JCZk/3aPhESkmSJGnErOmWJElSH3u6h8+kW5IkSX1MuofPpFuSJElrWNM9GibdkiRJ6mPS\nPXwm3ZIkSVrDnu7RMOmWJElSH5Pu4XPIQEmSJGnE7OmWJElSH3u6h8+kW5IkSWtY0z0aJt2SJEnq\nY9I9fKNJum+5Bc4/fyS7HtSWt9/e6fEBuPe9u46g8fa3dx0BXHpp1xHA0Ud3HQGsWNF1BI1rr+32\n+LPh/+ewJbBoUbcxXHNNt8cHHvPw2dHu1Yb7dR0Ch1Fdh8B1q/+s6xBYtqTrCBpZfV2nx99w0R2d\nHn867OkeDXu6JUmS1Meke/gcvUSSJEkaMXu6JUmS1Mee7uEz6ZYkSdIa1nSPhkm3JEmS+ph0D59J\ntyRJktawp3s0TLolSZLUx6R7+Ey6JUmStIY93aPhkIGSJEnSiNnTLUmSpD72dA+fPd2SJEnqc+ed\nc2MaRJJ9k1yc5JIkfz/B8p2TfD3Jj5KclWSHdv7uSb6b5MJ22Qt7tjkuyS+TnN9Ou08Vhz3dkiRJ\nWmM+1XQnWQR8ANgHWAmcm+S0qvpJz2rvBo6vqo8n2Rt4J3AQcAtwcFX9PMl2wHlJzqiq1e12b6qq\nUwaNZaCe7ql+IUjSfGO7J2kh67oHe4g93XsAl1TVpVX1e+Bk4Fnj1tkN+Hr7+syx5VX1s6r6eft6\nFXAVsM3dPadTJt09vxD2a4M6MMlud/eAkjTb2e5JWsjGerrnwjSA7YHLe96vbOf1+iHw3Pb1s4HN\nkmzdu0KSPYANgV/0zD6qLTt5b5KNpgpkkJ7uQX4hSNJ8YrsnaUHrOpmeRtK9NMmKnunQcR8lE3y8\nGvf+jcCfJ/kf4M+BXwO3r9lBsi3wCeBlVTWW6h8OPAR4LLAV8JapzukgNd0T/ULYc/xK7Yc8FGCn\n+953gN1K0qw1/XbvPveZmcgkSb2uqaplkyxfCezY834HYFXvCm3pyHMAkiwGnltV17fvNwe+BLyt\nqs7p2eaK9uVtST5Gk7hPapCe7kF+IVBVy6tqWVUt22bLLQfYrSTNWtNv95YsmYGwJGlmdN2DPcTy\nknOBXZPcL8mGwAHAab0rJFmaZCwnPhw4tp2/IXAqzU2Wnxm3zbbtnwH2By6YKpBBerqn/IUgSfOM\n7Z6kBWs+jV5SVbcnOQw4A1gEHFtVFyY5ElhRVacBewHvTFLA2cBr281fADwB2DrJIe28Q6rqfODE\nJNvQdNKcD7xmqlgGSbrX/EKgqXE5AHjRQJ9UkuYm2z1JC9p8SboBqup04PRx8/6x5/UpwF2G/quq\nE4AT1rHPvacbx5RJ97p+IUz3QJI0V9juSVrI5lNP92wy0MNxJvqFIEnzme2epIXMpHv4fAy8JEmS\nNGI+Bl6SJEl97OkePpNuSZIkrWFN92iYdEuSJKmPSffwmXRLkiRpDXu6R8OkW5IkSX1MuofPpFuS\nJEl9TLqHzyEDJUmSpBGzp1uSJElrWNM9GibdkiRJ6mPSPXwm3ZIkSVrDnu7RSFUNf6fJ1cCv7sEu\nlgLXDCkcY7jnZkMcxjC/Yti5qrYZRjCzhe3eUM2GOIzBGIYdw5xp9zbaaFltt92KrsMYyGWX5byq\nWtZ1HIMYSU/3Pf1HlWRF1yfQGGZXHMZgDLOd7d78isMYjGG2xTDT7OkePkcvkSRJkkbMmm5JkiSt\nYU33aMzWpHt51wFgDL1mQxzG0DCG+Ws2nNfZEAPMjjiMoWEMjdkQw4wy6R6+kdxIKUmSpLlpgw2W\n1dKlc+NGyiuvXOA3UkqSJGnusqd7+GbdjZRJ9k1ycZJLkvx9B8c/NslVSS6Y6WP3xLBjkjOTXJTk\nwiSv6yCGjZN8P8kP2xj+aaZj6IllUZL/SfLFjo5/WZIfJzk/SWc//ZMsSXJKkp+2/zb+eIaP/+D2\nHIxNNyR5/UzGMF/Z7tnuTRBLp+1eG0PnbZ/tXnfuvHNuTHPJrCovSbII+BmwD7ASOBc4sKp+MoMx\nPAG4CTi+qh4+U8cdF8O2wLZV9YMkmwHnAfvP8HkIsGlV3ZRkA+DbwOuq6pyZiqEnlr8DlgGbV9XT\nOzj+ZcCyqup0nNgkHwe+VVUfSbIhsElVre4olkXAr4E9q+qejE294NnurYnBdq8/lk7bvTaGy+i4\n7bPd68b66y+rLbaYG+Ul1147d8pLZltP9x7AJVV1aVX9HjgZeNZMBlBVZwPXzuQxJ4jhiqr6Qfv6\nRuAiYPsZjqGq6qb27QbtNOO/0JLsADwN+MhMH3s2SbI58ATgowBV9fuuvnhaTwJ+Md+/eGaI7R62\ne71s9xq2e5pvZlvSvT1wec/7lcxwozvbJNkFeBTwvQ6OvSjJ+cBVwNeqasZjAN4HvBno8iJSAV9N\ncl6SQzuK4f7A1cDH2kvOH0myaUexABwAnNTh8ecT271xbPdmRbsH3bd9tnsd6rpsZD6Wl8y2pDsT\nzJs99S8zLMli4LPA66vqhpk+flXdUVW7AzsAeySZ0cvOSZ4OXFVV583kcSfw+Kp6NLAf8Nr2UvxM\nWx94NPChqnoUcDMw47W/AO0l3mcCn+ni+POQ7V4P271Z0+5B922f7V5HxsbpngvTXDLbku6VwI49\n73cAVnUUS6faesLPAidW1ee6jKW9nHcWsO8MH/rxwDPbusKTgb2TnDDDMVBVq9o/rwJOpSkHmGkr\ngZU9vW6n0HwZdWE/4AdV9ZuOjj/f2O61bPeAWdLuwaxo+2z3OtR1Mm3SPXrnArsmuV/7q/IA4LSO\nY5px7c08HwUuqqr3dBTDNkmWtK/vBfwF8NOZjKGqDq+qHapqF5p/C9+oqpfMZAxJNm1v6qK9rPlk\nYMZHeKiqK4HLkzy4nfUkYMZuMBvnQBbQJdYZYLuH7d6Y2dDuwexo+2z3utV1Mj0fk+5ZNU53Vd2e\n5DDgDGARcGxVXTiTMSQ5CdgLWJpkJXBEVX10JmOg6ek4CPhxW1sI8NaqOn0GY9gW+Hh7t/Z6wKer\nqrOhqzp0H+DUJh9gfeCTVfWVjmL5a+DENjG7FHjZTAeQZBOaUTZePdPHnq9s99aw3ZtdZkvbZ7vX\nAR8DPxqzashASZIkdWu99ZbVRhvNjSEDb73VIQMlSZI0R3VdNjLM8pJM8QCyJDsn+XqSHyU5qx22\nc2zZS5P8vJ1e2jP/MWkeHnVJkqPbErlJmXRLkiRpjfk0eklbLvYBmpthdwMOTLLbuNXeTfNwsD8C\njgTe2W67FXAEsCfNjcRHJNmy3eZDwKHAru005U3XJt2SJEnq03UyPcSe7kEeQLYb8PX29Zk9y59C\nM17/tVV1HfA1YN/2CbqbV9V3q6nTPh7Yf6pAZtWNlJIkSerePLqRcqIHkO05bp0fAs8F3g88G9gs\nydbr2Hb7dlo5wfxJmXRLkiSpx3lnQJZ2HcWANk7Se9fn8qpa3vN+kAeQvRE4JskhwNnAr4HbJ9n2\nbj3UzKRbkiRJa1TVTD8UapSmfABZ+yCo58Cap+I+t6qub4dQ3Wvctme1+9xh3PwpH2pmTbckSZLm\nqykfQJZkaZKxnPhw4Nj29RnAk5Ns2d5A+WTgjKq6ArgxyePaUUsOBr4wVSAm3ZIkSZqXqup2YOwB\nZBfRPPTqwiRHJnlmu9pewMVJfkbzYKij2m2vBf6ZJnE/FziynQfwl8BHgEuAXwBfnioWH44jSZIk\njZg93ZIkSdKImXRLkiRJI2bSLUmSJI2YSbckSZI0YibdkiRJ0oiZdEuSJEkjZtItSZIkjZhJtyRJ\nkjRi/w9hvYqGSZgiKAAAAABJRU5ErkJggg==\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, diff --git a/examples/jupyter/mgxs-part-i.ipynb b/examples/jupyter/mgxs-part-i.ipynb index 6fa0c02b1..d09aeaa46 100644 --- a/examples/jupyter/mgxs-part-i.ipynb +++ b/examples/jupyter/mgxs-part-i.ipynb @@ -151,7 +151,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "First we need to define materials that will be used in the problem. Before defining a material, we must create nuclides that are used in the material." + "We being by creating a material for the homogeneous medium." ] }, { @@ -161,38 +161,15 @@ "collapsed": true }, "outputs": [], - "source": [ - "# Instantiate some Nuclides\n", - "h1 = openmc.Nuclide('H1')\n", - "o16 = openmc.Nuclide('O16')\n", - "u235 = openmc.Nuclide('U235')\n", - "u238 = openmc.Nuclide('U238')\n", - "zr90 = openmc.Nuclide('Zr90')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With the nuclides we defined, we will now create a material for the homogeneous medium." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, - "outputs": [], "source": [ "# Instantiate a Material and register the Nuclides\n", "inf_medium = openmc.Material(name='moderator')\n", "inf_medium.set_density('g/cc', 5.)\n", - "inf_medium.add_nuclide(h1, 0.028999667)\n", - "inf_medium.add_nuclide(o16, 0.01450188)\n", - "inf_medium.add_nuclide(u235, 0.000114142)\n", - "inf_medium.add_nuclide(u238, 0.006886019)\n", - "inf_medium.add_nuclide(zr90, 0.002116053)" + "inf_medium.add_nuclide('H1', 0.028999667)\n", + "inf_medium.add_nuclide('O16', 0.01450188)\n", + "inf_medium.add_nuclide('U235', 0.000114142)\n", + "inf_medium.add_nuclide('U238', 0.006886019)\n", + "inf_medium.add_nuclide('Zr90', 0.002116053)" ] }, { @@ -204,7 +181,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -224,7 +201,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "collapsed": true }, @@ -246,7 +223,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -271,15 +248,14 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ - "# Instantiate Universe\n", - "root_universe = openmc.Universe(universe_id=0, name='root universe')\n", - "root_universe.add_cell(cell)" + "# Create root universe\n", + "root_universe = openmc.Universe(name='root universe', cells=[cell])" ] }, { @@ -291,15 +267,14 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Create Geometry and set root Universe\n", - "openmc_geometry = openmc.Geometry()\n", - "openmc_geometry.root_universe = root_universe\n", + "openmc_geometry = openmc.Geometry(root_universe)\n", "\n", "# Export to \"geometry.xml\"\n", "openmc_geometry.export_to_xml()" @@ -314,7 +289,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -350,7 +325,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -389,7 +364,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -414,7 +389,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -423,13 +398,13 @@ "data": { "text/plain": [ "OrderedDict([('flux', Tally\n", - " \tID =\t10000\n", + " \tID =\t1\n", " \tName =\t\n", " \tFilters =\tCellFilter, EnergyFilter\n", " \tNuclides =\ttotal \n", " \tScores =\t['flux']\n", " \tEstimator =\ttracklength), ('absorption', Tally\n", - " \tID =\t10001\n", + " \tID =\t2\n", " \tName =\t\n", " \tFilters =\tCellFilter, EnergyFilter\n", " \tNuclides =\ttotal \n", @@ -437,7 +412,7 @@ " \tEstimator =\ttracklength)])" ] }, - "execution_count": 13, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -455,11 +430,22 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=3.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=4.\n", + " warn(msg, IDWarning)\n" + ] + } + ], "source": [ "# Instantiate an empty Tallies object\n", "tallies_file = openmc.Tallies()\n", @@ -486,7 +472,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -523,37 +509,27 @@ " | The OpenMC Monte Carlo Code\n", " Copyright | 2011-2017 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | 43b141e9ba542da8b28c078cf2df8a6777cfb2ad\n", - " Date/Time | 2017-02-28 11:52:00\n", + " Version | 0.9.0\n", + " Git SHA1 | 9b7cebf7bc34d60e0f1750c3d6cb103df11e8dc4\n", + " Date/Time | 2017-12-04 20:56:46\n", " OpenMP Threads | 4\n", "\n", - " ===========================================================================\n", - " ========================> INITIALIZATION <=========================\n", - " ===========================================================================\n", - "\n", " Reading settings XML file...\n", - " Reading geometry XML file...\n", - " Reading materials XML file...\n", " Reading cross sections XML file...\n", - " Reading H1 from\n", - " /home/wbinventor/Documents/NSE-CRPG-Codes/openmc/data/nndc_hdf5/H1.h5\n", - " Reading O16 from\n", - " /home/wbinventor/Documents/NSE-CRPG-Codes/openmc/data/nndc_hdf5/O16.h5\n", - " Reading U235 from\n", - " /home/wbinventor/Documents/NSE-CRPG-Codes/openmc/data/nndc_hdf5/U235.h5\n", - " Reading U238 from\n", - " /home/wbinventor/Documents/NSE-CRPG-Codes/openmc/data/nndc_hdf5/U238.h5\n", - " Reading Zr90 from\n", - " /home/wbinventor/Documents/NSE-CRPG-Codes/openmc/data/nndc_hdf5/Zr90.h5\n", + " Reading materials XML file...\n", + " Reading geometry XML file...\n", + " Building neighboring cells lists for each surface...\n", + " Reading H1 from /home/romano/openmc/scripts/nndc_hdf5/H1.h5\n", + " Reading O16 from /home/romano/openmc/scripts/nndc_hdf5/O16.h5\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 Zr90 from /home/romano/openmc/scripts/nndc_hdf5/Zr90.h5\n", " Maximum neutron transport energy: 2.00000E+07 eV for H1\n", " Reading tallies XML file...\n", - " Building neighboring cells lists for each surface...\n", + " Writing summary.h5 file...\n", " Initializing source particles...\n", "\n", - " ===========================================================================\n", " ====================> K EIGENVALUE SIMULATION <====================\n", - " ===========================================================================\n", "\n", " Bat./Gen. k Average k \n", " ========= ======== ==================== \n", @@ -609,27 +585,22 @@ " 50/1 1.15798 1.16146 +/- 0.00457\n", " Creating state point statepoint.50.h5...\n", "\n", - " ===========================================================================\n", - " ======================> SIMULATION FINISHED <======================\n", - " ===========================================================================\n", - "\n", - "\n", " =======================> TIMING STATISTICS <=======================\n", "\n", - " Total time for initialization = 3.0114E-01 seconds\n", - " Reading cross sections = 1.8743E-01 seconds\n", - " Total time in simulation = 9.7641E+00 seconds\n", - " Time in transport only = 9.5168E+00 seconds\n", - " Time in inactive batches = 1.2602E+00 seconds\n", - " Time in active batches = 8.5039E+00 seconds\n", - " Time synchronizing fission bank = 5.4293E-03 seconds\n", - " Sampling source sites = 4.3508E-03 seconds\n", - " SEND/RECV source sites = 9.9399E-04 seconds\n", - " Time accumulating tallies = 1.2758E-04 seconds\n", - " Total time for finalization = 3.6982E-04 seconds\n", - " Total time elapsed = 1.0075E+01 seconds\n", - " Calculation Rate (inactive) = 19838.7 neutrons/second\n", - " Calculation Rate (active) = 11759.3 neutrons/second\n", + " Total time for initialization = 4.0504E-01 seconds\n", + " Reading cross sections = 3.6457E-01 seconds\n", + " Total time in simulation = 6.3478E+00 seconds\n", + " Time in transport only = 6.0079E+00 seconds\n", + " Time in inactive batches = 8.1713E-01 seconds\n", + " Time in active batches = 5.5307E+00 seconds\n", + " Time synchronizing fission bank = 5.4640E-03 seconds\n", + " Sampling source sites = 4.0981E-03 seconds\n", + " SEND/RECV source sites = 1.2606E-03 seconds\n", + " Time accumulating tallies = 1.2030E-04 seconds\n", + " Total time for finalization = 9.6554E-04 seconds\n", + " Total time elapsed = 6.7713E+00 seconds\n", + " Calculation Rate (inactive) = 30594.8 neutrons/second\n", + " Calculation Rate (active) = 18080.8 neutrons/second\n", "\n", " ============================> RESULTS <============================\n", "\n", @@ -647,7 +618,7 @@ "0" ] }, - "execution_count": 15, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -673,7 +644,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": { "collapsed": false }, @@ -699,7 +670,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": { "collapsed": false }, @@ -734,7 +705,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": { "collapsed": false }, @@ -769,7 +740,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -816,7 +787,7 @@ "0 1 2 total 1.292013 0.007642" ] }, - "execution_count": 19, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -835,7 +806,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": { "collapsed": false }, @@ -853,7 +824,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": { "collapsed": false }, @@ -880,7 +851,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -920,7 +891,7 @@ " 2.000000e+07\n", " total\n", " (((total / flux) - (absorption / flux)) - (sca...\n", - " 1.776357e-15\n", + " 7.771561e-16\n", " 0.002570\n", " \n", " \n", @@ -934,10 +905,10 @@ "\n", " score mean std. dev. \n", "0 (((total / flux) - (absorption / flux)) - (sca... -1.11e-15 1.13e-02 \n", - "1 (((total / flux) - (absorption / flux)) - (sca... 1.78e-15 2.57e-03 " + "1 (((total / flux) - (absorption / flux)) - (sca... 7.77e-16 2.57e-03 " ] }, - "execution_count": 22, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -959,7 +930,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -1016,7 +987,7 @@ "1 ((absorption / flux) / (total / flux)) 1.93e-02 9.46e-05 " ] }, - "execution_count": 23, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -1031,7 +1002,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "metadata": { "collapsed": false }, @@ -1088,7 +1059,7 @@ "1 ((scatter / flux) / (total / flux)) 9.81e-01 3.74e-03 " ] }, - "execution_count": 24, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -1110,7 +1081,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "metadata": { "collapsed": false }, @@ -1167,7 +1138,7 @@ "1 (((absorption / flux) / (total / flux)) + ((sc... 1.00e+00 3.74e-03 " ] }, - "execution_count": 25, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -1197,7 +1168,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.0" } }, "nbformat": 4, diff --git a/examples/jupyter/mgxs-part-ii.ipynb b/examples/jupyter/mgxs-part-ii.ipynb index b03838477..c10f55956 100644 --- a/examples/jupyter/mgxs-part-ii.ipynb +++ b/examples/jupyter/mgxs-part-ii.ipynb @@ -34,7 +34,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/wbinventor/miniconda3/lib/python3.5/site-packages/matplotlib/__init__.py:1350: UserWarning: This call to matplotlib.use() has no effect\n", + "/home/romano/miniconda3/envs/python3/lib/python3.6/site-packages/matplotlib/__init__.py:1401: UserWarning: This call to matplotlib.use() has no effect\n", "because the backend has already been chosen;\n", "matplotlib.use() must be called *before* pylab, matplotlib.pyplot,\n", "or matplotlib.backends is imported for the first time.\n", @@ -62,35 +62,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "First we need to define materials that will be used in the problem. Before defining a material, we must create nuclides that are used in the material." + "First we need to define materials that will be used in the problem. We'll create three distinct materials for water, clad and fuel." ] }, { "cell_type": "code", "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Instantiate some Nuclides\n", - "h1 = openmc.Nuclide('H1')\n", - "o16 = openmc.Nuclide('O16')\n", - "u235 = openmc.Nuclide('U235')\n", - "u238 = openmc.Nuclide('U238')\n", - "zr90 = openmc.Nuclide('Zr90')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With the nuclides we defined, we will now create three distinct materials for water, clad and fuel." - ] - }, - { - "cell_type": "code", - "execution_count": 3, "metadata": { "collapsed": false }, @@ -99,20 +76,20 @@ "# 1.6% enriched fuel\n", "fuel = openmc.Material(name='1.6% Fuel')\n", "fuel.set_density('g/cm3', 10.31341)\n", - "fuel.add_nuclide(u235, 3.7503e-4)\n", - "fuel.add_nuclide(u238, 2.2625e-2)\n", - "fuel.add_nuclide(o16, 4.6007e-2)\n", + "fuel.add_nuclide('U235', 3.7503e-4)\n", + "fuel.add_nuclide('U238', 2.2625e-2)\n", + "fuel.add_nuclide('O16', 4.6007e-2)\n", "\n", "# borated water\n", "water = openmc.Material(name='Borated Water')\n", "water.set_density('g/cm3', 0.740582)\n", - "water.add_nuclide(h1, 4.9457e-2)\n", - "water.add_nuclide(o16, 2.4732e-2)\n", + "water.add_nuclide('H1', 4.9457e-2)\n", + "water.add_nuclide('O16', 2.4732e-2)\n", "\n", "# zircaloy\n", "zircaloy = openmc.Material(name='Zircaloy')\n", "zircaloy.set_density('g/cm3', 6.55)\n", - "zircaloy.add_nuclide(zr90, 7.2758e-3)" + "zircaloy.add_nuclide('Zr90', 7.2758e-3)" ] }, { @@ -124,7 +101,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -146,7 +123,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -174,7 +151,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -211,7 +188,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -236,15 +213,14 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Create Geometry and set root Universe\n", - "openmc_geometry = openmc.Geometry()\n", - "openmc_geometry.root_universe = root_universe\n", + "openmc_geometry = openmc.Geometry(root_universe)\n", "\n", "# Export to \"geometry.xml\"\n", "openmc_geometry.export_to_xml()" @@ -259,7 +235,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "collapsed": true }, @@ -299,20 +275,18 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Instantiate a \"coarse\" 2-group EnergyGroups object\n", - "coarse_groups = mgxs.EnergyGroups()\n", - "coarse_groups.group_edges = np.array([0., 0.625, 20.0e6])\n", + "coarse_groups = mgxs.EnergyGroups([0., 0.625, 20.0e6])\n", "\n", "# Instantiate a \"fine\" 8-group EnergyGroups object\n", - "fine_groups = mgxs.EnergyGroups()\n", - "fine_groups.group_edges = np.array([0., 0.058, 0.14, 0.28,\n", - " 0.625, 4.0, 5.53e3, 821.0e3, 20.0e6])" + "fine_groups = mgxs.EnergyGroups([0., 0.058, 0.14, 0.28,\n", + " 0.625, 4.0, 5.53e3, 821.0e3, 20.0e6])" ] }, { @@ -324,7 +298,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -355,7 +329,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -379,11 +353,32 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=48.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=18.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=2.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyoutFilter 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=40.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=43.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=13.\n", + " warn(msg, IDWarning)\n" + ] + } + ], "source": [ "# Instantiate an empty Tallies object\n", "tallies_file = openmc.Tallies()\n", @@ -415,7 +410,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -452,37 +447,27 @@ " | The OpenMC Monte Carlo Code\n", " Copyright | 2011-2017 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | 647bf77a57a3cc5cce24b39cb192e1b99f52e499\n", - " Date/Time | 2017-02-27 13:35:52\n", + " Version | 0.9.0\n", + " Git SHA1 | da61fb4a55e1feaa127799ad9293a766161fbb3e\n", + " Date/Time | 2017-12-11 16:37:11\n", " OpenMP Threads | 4\n", "\n", - " ===========================================================================\n", - " ========================> INITIALIZATION <=========================\n", - " ===========================================================================\n", - "\n", " Reading settings XML file...\n", - " Reading geometry XML file...\n", - " Reading materials XML file...\n", " Reading cross sections XML file...\n", - " Reading U235 from\n", - " /home/wbinventor/Documents/NSE-CRPG-Codes/openmc/data/nndc_hdf5/U235.h5\n", - " Reading U238 from\n", - " /home/wbinventor/Documents/NSE-CRPG-Codes/openmc/data/nndc_hdf5/U238.h5\n", - " Reading O16 from\n", - " /home/wbinventor/Documents/NSE-CRPG-Codes/openmc/data/nndc_hdf5/O16.h5\n", - " Reading H1 from\n", - " /home/wbinventor/Documents/NSE-CRPG-Codes/openmc/data/nndc_hdf5/H1.h5\n", - " Reading Zr90 from\n", - " /home/wbinventor/Documents/NSE-CRPG-Codes/openmc/data/nndc_hdf5/Zr90.h5\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 Zr90 from /home/romano/openmc/scripts/nndc_hdf5/Zr90.h5\n", " Maximum neutron transport energy: 2.00000E+07 eV for U235\n", " Reading tallies XML file...\n", - " Building neighboring cells lists for each surface...\n", + " Writing summary.h5 file...\n", " Initializing source particles...\n", "\n", - " ===========================================================================\n", " ====================> K EIGENVALUE SIMULATION <====================\n", - " ===========================================================================\n", "\n", " Bat./Gen. k Average k \n", " ========= ======== ==================== \n", @@ -536,7 +521,7 @@ " 48/1 1.22204 1.22140 +/- 0.00239\n", " 49/1 1.22077 1.22139 +/- 0.00232\n", " 50/1 1.23166 1.22164 +/- 0.00228\n", - " Triggers unsatisfied, max unc./thresh. is 1.17623 for flux in tally 10052\n", + " Triggers unsatisfied, max unc./thresh. is 1.17623 for flux in tally 58\n", " The estimated number of batches is 66\n", " Creating state point statepoint.050.h5...\n", " 51/1 1.20071 1.22113 +/- 0.00228\n", @@ -555,7 +540,7 @@ " 64/1 1.23955 1.22122 +/- 0.00200\n", " 65/1 1.21143 1.22104 +/- 0.00197\n", " 66/1 1.21791 1.22099 +/- 0.00194\n", - " Triggers unsatisfied, max unc./thresh. is 1.13207 for flux in tally 10052\n", + " Triggers unsatisfied, max unc./thresh. is 1.13207 for flux in tally 58\n", " The estimated number of batches is 82\n", " 67/1 1.24897 1.22148 +/- 0.00196\n", " 68/1 1.22221 1.22149 +/- 0.00193\n", @@ -576,27 +561,22 @@ " Triggers satisfied for batch 82\n", " Creating state point statepoint.082.h5...\n", "\n", - " ===========================================================================\n", - " ======================> SIMULATION FINISHED <======================\n", - " ===========================================================================\n", - "\n", - "\n", " =======================> TIMING STATISTICS <=======================\n", "\n", - " Total time for initialization = 4.8359E-01 seconds\n", - " Reading cross sections = 3.0552E-01 seconds\n", - " Total time in simulation = 1.4019E+02 seconds\n", - " Time in transport only = 1.3995E+02 seconds\n", - " Time in inactive batches = 8.5036E+00 seconds\n", - " Time in active batches = 1.3169E+02 seconds\n", - " Time synchronizing fission bank = 2.6010E-02 seconds\n", - " Sampling source sites = 1.8806E-02 seconds\n", - " SEND/RECV source sites = 7.0698E-03 seconds\n", - " Time accumulating tallies = 2.8324E-03 seconds\n", - " Total time for finalization = 2.2540E-02 seconds\n", - " Total time elapsed = 1.4077E+02 seconds\n", - " Calculation Rate (inactive) = 11759.7 neutrons/second\n", - " Calculation Rate (active) = 3037.46 neutrons/second\n", + " Total time for initialization = 4.1610E-01 seconds\n", + " Reading cross sections = 3.7942E-01 seconds\n", + " Total time in simulation = 1.1100E+02 seconds\n", + " Time in transport only = 1.1076E+02 seconds\n", + " Time in inactive batches = 5.8101E+00 seconds\n", + " Time in active batches = 1.0519E+02 seconds\n", + " Time synchronizing fission bank = 3.8707E-02 seconds\n", + " Sampling source sites = 2.7232E-02 seconds\n", + " SEND/RECV source sites = 1.1284E-02 seconds\n", + " Time accumulating tallies = 1.0514E-03 seconds\n", + " Total time for finalization = 1.4526E-02 seconds\n", + " Total time elapsed = 1.1150E+02 seconds\n", + " Calculation Rate (inactive) = 17211.3 neutrons/second\n", + " Calculation Rate (active) = 6844.60 neutrons/second\n", "\n", " ============================> RESULTS <============================\n", "\n", @@ -614,7 +594,7 @@ "0" ] }, - "execution_count": 14, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -640,7 +620,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -659,7 +639,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": { "collapsed": false }, @@ -694,7 +674,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": { "collapsed": false }, @@ -706,7 +686,7 @@ "Multi-Group XS\n", "\tReaction Type =\tnu-fission\n", "\tDomain Type =\tcell\n", - "\tDomain ID =\t10000\n", + "\tDomain ID =\t1\n", "\tNuclide =\tU235\n", "\tCross Sections [barns]:\n", " Group 1 [821000.0 - 20000000.0eV]:\t3.30e+00 +/- 2.14e-01%\n", @@ -737,7 +717,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/wbinventor/Documents/NSE-CRPG-Codes/openmc/openmc/tallies.py:1835: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1798: RuntimeWarning: invalid value encountered in true_divide\n", " self_rel_err = data['self']['std. dev.'] / data['self']['mean']\n" ] } @@ -756,7 +736,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": { "collapsed": false }, @@ -768,7 +748,7 @@ "Multi-Group XS\n", "\tReaction Type =\tnu-fission\n", "\tDomain Type =\tcell\n", - "\tDomain ID =\t10000\n", + "\tDomain ID =\t1\n", "\tCross Sections [cm^-1]:\n", " Group 1 [821000.0 - 20000000.0eV]:\t2.52e-02 +/- 2.41e-01%\n", " Group 2 [5530.0 - 821000.0 eV]:\t1.51e-03 +/- 1.31e-01%\n", @@ -798,7 +778,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -807,7 +787,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/wbinventor/Documents/NSE-CRPG-Codes/openmc/openmc/tallies.py:1835: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1798: RuntimeWarning: invalid value encountered in true_divide\n", " self_rel_err = data['self']['std. dev.'] / data['self']['mean']\n" ] }, @@ -830,7 +810,7 @@ " \n", " \n", " 126\n", - " 10002\n", + " 3\n", " 1\n", " 1\n", " H1\n", @@ -839,7 +819,7 @@ " \n", " \n", " 127\n", - " 10002\n", + " 3\n", " 1\n", " 1\n", " O16\n", @@ -848,7 +828,7 @@ " \n", " \n", " 124\n", - " 10002\n", + " 3\n", " 1\n", " 2\n", " H1\n", @@ -857,7 +837,7 @@ " \n", " \n", " 125\n", - " 10002\n", + " 3\n", " 1\n", " 2\n", " O16\n", @@ -866,7 +846,7 @@ " \n", " \n", " 122\n", - " 10002\n", + " 3\n", " 1\n", " 3\n", " H1\n", @@ -875,7 +855,7 @@ " \n", " \n", " 123\n", - " 10002\n", + " 3\n", " 1\n", " 3\n", " O16\n", @@ -884,7 +864,7 @@ " \n", " \n", " 120\n", - " 10002\n", + " 3\n", " 1\n", " 4\n", " H1\n", @@ -893,7 +873,7 @@ " \n", " \n", " 121\n", - " 10002\n", + " 3\n", " 1\n", " 4\n", " O16\n", @@ -902,7 +882,7 @@ " \n", " \n", " 118\n", - " 10002\n", + " 3\n", " 1\n", " 5\n", " H1\n", @@ -911,7 +891,7 @@ " \n", " \n", " 119\n", - " 10002\n", + " 3\n", " 1\n", " 5\n", " O16\n", @@ -923,20 +903,20 @@ "" ], "text/plain": [ - " cell group in group out nuclide mean std. dev.\n", - "126 10002 1 1 H1 0.233991 0.003752\n", - "127 10002 1 1 O16 1.569288 0.006360\n", - "124 10002 1 2 H1 1.587279 0.003098\n", - "125 10002 1 2 O16 0.285599 0.001422\n", - "122 10002 1 3 H1 0.010482 0.000220\n", - "123 10002 1 3 O16 0.000000 0.000000\n", - "120 10002 1 4 H1 0.000009 0.000006\n", - "121 10002 1 4 O16 0.000000 0.000000\n", - "118 10002 1 5 H1 0.000005 0.000005\n", - "119 10002 1 5 O16 0.000000 0.000000" + " cell group in group out nuclide mean std. dev.\n", + "126 3 1 1 H1 0.233991 0.003752\n", + "127 3 1 1 O16 1.569288 0.006360\n", + "124 3 1 2 H1 1.587279 0.003098\n", + "125 3 1 2 O16 0.285599 0.001422\n", + "122 3 1 3 H1 0.010482 0.000220\n", + "123 3 1 3 O16 0.000000 0.000000\n", + "120 3 1 4 H1 0.000009 0.000006\n", + "121 3 1 4 O16 0.000000 0.000000\n", + "118 3 1 5 H1 0.000005 0.000005\n", + "119 3 1 5 O16 0.000000 0.000000" ] }, - "execution_count": 19, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -956,7 +936,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": { "collapsed": true }, @@ -978,7 +958,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": { "collapsed": false }, @@ -990,21 +970,21 @@ "Multi-Group XS\n", "\tReaction Type =\ttransport\n", "\tDomain Type =\tcell\n", - "\tDomain ID =\t10000\n", + "\tDomain ID =\t1\n", "\tNuclide =\tU235\n", "\tCross Sections [cm^-1]:\n", - " Group 1 [0.625 - 20000000.0eV]:\t7.84e-03 +/- 4.34e-01%\n", - " Group 2 [0.0 - 0.625 eV]:\t1.82e-01 +/- 1.91e-01%\n", + " Group 1 [0.625 - 20000000.0eV]:\t7.79e-03 +/- 2.12e-01%\n", + " Group 2 [0.0 - 0.625 eV]:\t1.82e-01 +/- 1.92e-01%\n", "\n", "\tNuclide =\tU238\n", "\tCross Sections [cm^-1]:\n", - " Group 1 [0.625 - 20000000.0eV]:\t2.17e-01 +/- 1.38e-01%\n", - " Group 2 [0.0 - 0.625 eV]:\t2.53e-01 +/- 2.27e-01%\n", + " Group 1 [0.625 - 20000000.0eV]:\t2.17e-01 +/- 1.12e-01%\n", + " Group 2 [0.0 - 0.625 eV]:\t2.53e-01 +/- 1.89e-01%\n", "\n", "\tNuclide =\tO16\n", "\tCross Sections [cm^-1]:\n", - " Group 1 [0.625 - 20000000.0eV]:\t1.45e-01 +/- 1.54e-01%\n", - " Group 2 [0.0 - 0.625 eV]:\t1.74e-01 +/- 2.60e-01%\n", + " Group 1 [0.625 - 20000000.0eV]:\t1.45e-01 +/- 1.12e-01%\n", + " Group 2 [0.0 - 0.625 eV]:\t1.74e-01 +/- 2.03e-01%\n", "\n", "\n", "\n" @@ -1017,7 +997,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -1040,67 +1020,67 @@ " \n", " \n", " 3\n", - " 10000\n", + " 1\n", " 1\n", " U235\n", - " 20.912730\n", - " 0.090857\n", + " 20.763062\n", + " 0.044093\n", " \n", " \n", " 4\n", - " 10000\n", + " 1\n", " 1\n", " U238\n", - " 9.577234\n", - " 0.013248\n", + " 9.579086\n", + " 0.010757\n", " \n", " \n", " 5\n", - " 10000\n", + " 1\n", " 1\n", " O16\n", - " 3.158619\n", - " 0.004864\n", + " 3.157274\n", + " 0.003531\n", " \n", " \n", " 0\n", - " 10000\n", + " 1\n", " 2\n", " U235\n", - " 485.364898\n", - " 0.925632\n", + " 485.349036\n", + " 0.930937\n", " \n", " \n", " 1\n", - " 10000\n", + " 1\n", " 2\n", " U238\n", - " 11.196946\n", - " 0.025466\n", + " 11.199167\n", + " 0.021167\n", " \n", " \n", " 2\n", - " 10000\n", + " 1\n", " 2\n", " O16\n", - " 3.788841\n", - " 0.009855\n", + " 3.788383\n", + " 0.007676\n", " \n", " \n", "\n", "" ], "text/plain": [ - " cell group in nuclide mean std. dev.\n", - "3 10000 1 U235 20.912730 0.090857\n", - "4 10000 1 U238 9.577234 0.013248\n", - "5 10000 1 O16 3.158619 0.004864\n", - "0 10000 2 U235 485.364898 0.925632\n", - "1 10000 2 U238 11.196946 0.025466\n", - "2 10000 2 O16 3.788841 0.009855" + " cell group in nuclide mean std. dev.\n", + "3 1 1 U235 20.763062 0.044093\n", + "4 1 1 U238 9.579086 0.010757\n", + "5 1 1 O16 3.157274 0.003531\n", + "0 1 2 U235 485.349036 0.930937\n", + "1 1 2 U238 11.199167 0.021167\n", + "2 1 2 O16 3.788383 0.007676" ] }, - "execution_count": 22, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -1126,7 +1106,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -1145,7 +1125,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "metadata": { "collapsed": false }, @@ -1154,11 +1134,11 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/wbinventor/Documents/NSE-CRPG-Codes/openmc/openmc/tallies.py:1835: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1798: RuntimeWarning: invalid value encountered in true_divide\n", " self_rel_err = data['self']['std. dev.'] / data['self']['mean']\n", - "/home/wbinventor/Documents/NSE-CRPG-Codes/openmc/openmc/tallies.py:1836: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1799: RuntimeWarning: invalid value encountered in true_divide\n", " other_rel_err = data['other']['std. dev.'] / data['other']['mean']\n", - "/home/wbinventor/Documents/NSE-CRPG-Codes/openmc/openmc/tallies.py:1837: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1800: RuntimeWarning: invalid value encountered in true_divide\n", " new_tally._mean = data['self']['mean'] / data['other']['mean']\n" ] } @@ -1203,7 +1183,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "metadata": { "collapsed": false }, @@ -1214,239 +1194,239 @@ "text": [ "[ NORMAL ] Importing ray tracing data from file...\n", "[ NORMAL ] Computing the eigenvalue...\n", - "[ NORMAL ] Iteration 0:\tk_eff = 0.423140\tres = 0.000E+00\n", - "[ NORMAL ] Iteration 1:\tk_eff = 0.475976\tres = 5.769E-01\n", - "[ NORMAL ] Iteration 2:\tk_eff = 0.491509\tres = 1.249E-01\n", - "[ NORMAL ] Iteration 3:\tk_eff = 0.487503\tres = 3.263E-02\n", - "[ NORMAL ] Iteration 4:\tk_eff = 0.484002\tres = 8.150E-03\n", - "[ NORMAL ] Iteration 5:\tk_eff = 0.477365\tres = 7.181E-03\n", - "[ NORMAL ] Iteration 6:\tk_eff = 0.469036\tres = 1.371E-02\n", - "[ NORMAL ] Iteration 7:\tk_eff = 0.460429\tres = 1.745E-02\n", - "[ NORMAL ] Iteration 8:\tk_eff = 0.450711\tres = 1.835E-02\n", - "[ NORMAL ] Iteration 9:\tk_eff = 0.441506\tres = 2.111E-02\n", - "[ NORMAL ] Iteration 10:\tk_eff = 0.432127\tres = 2.042E-02\n", - "[ NORMAL ] Iteration 11:\tk_eff = 0.423076\tres = 2.124E-02\n", - "[ NORMAL ] Iteration 12:\tk_eff = 0.414637\tres = 2.095E-02\n", - "[ NORMAL ] Iteration 13:\tk_eff = 0.406863\tres = 1.995E-02\n", - "[ NORMAL ] Iteration 14:\tk_eff = 0.399537\tres = 1.875E-02\n", - "[ NORMAL ] Iteration 15:\tk_eff = 0.393230\tres = 1.801E-02\n", - "[ NORMAL ] Iteration 16:\tk_eff = 0.387592\tres = 1.579E-02\n", - "[ NORMAL ] Iteration 17:\tk_eff = 0.382836\tres = 1.434E-02\n", - "[ NORMAL ] Iteration 18:\tk_eff = 0.378910\tres = 1.227E-02\n", - "[ NORMAL ] Iteration 19:\tk_eff = 0.375812\tres = 1.026E-02\n", - "[ NORMAL ] Iteration 20:\tk_eff = 0.373658\tres = 8.176E-03\n", - "[ NORMAL ] Iteration 21:\tk_eff = 0.372526\tres = 5.730E-03\n", - "[ NORMAL ] Iteration 22:\tk_eff = 0.372142\tres = 3.031E-03\n", - "[ NORMAL ] Iteration 23:\tk_eff = 0.372747\tres = 1.030E-03\n", - "[ NORMAL ] Iteration 24:\tk_eff = 0.374220\tres = 1.627E-03\n", - "[ NORMAL ] Iteration 25:\tk_eff = 0.376545\tres = 3.951E-03\n", - "[ NORMAL ] Iteration 26:\tk_eff = 0.379722\tres = 6.213E-03\n", - "[ NORMAL ] Iteration 27:\tk_eff = 0.383738\tres = 8.437E-03\n", - "[ NORMAL ] Iteration 28:\tk_eff = 0.388532\tres = 1.058E-02\n", - "[ NORMAL ] Iteration 29:\tk_eff = 0.394086\tres = 1.249E-02\n", - "[ NORMAL ] Iteration 30:\tk_eff = 0.400378\tres = 1.429E-02\n", - "[ NORMAL ] Iteration 31:\tk_eff = 0.407375\tres = 1.597E-02\n", - "[ NORMAL ] Iteration 32:\tk_eff = 0.415020\tres = 1.747E-02\n", - "[ NORMAL ] Iteration 33:\tk_eff = 0.423303\tres = 1.877E-02\n", - "[ NORMAL ] Iteration 34:\tk_eff = 0.432177\tres = 1.996E-02\n", - "[ NORMAL ] Iteration 35:\tk_eff = 0.441592\tres = 2.096E-02\n", - "[ NORMAL ] Iteration 36:\tk_eff = 0.451546\tres = 2.179E-02\n", - "[ NORMAL ] Iteration 37:\tk_eff = 0.461964\tres = 2.254E-02\n", - "[ NORMAL ] Iteration 38:\tk_eff = 0.472835\tres = 2.307E-02\n", - "[ NORMAL ] Iteration 39:\tk_eff = 0.484106\tres = 2.353E-02\n", - "[ NORMAL ] Iteration 40:\tk_eff = 0.495748\tres = 2.384E-02\n", - "[ NORMAL ] Iteration 41:\tk_eff = 0.507723\tres = 2.405E-02\n", - "[ NORMAL ] Iteration 42:\tk_eff = 0.519997\tres = 2.416E-02\n", - "[ NORMAL ] Iteration 43:\tk_eff = 0.532536\tres = 2.418E-02\n", - "[ NORMAL ] Iteration 44:\tk_eff = 0.545307\tres = 2.411E-02\n", - "[ NORMAL ] Iteration 45:\tk_eff = 0.558277\tres = 2.398E-02\n", - "[ NORMAL ] Iteration 46:\tk_eff = 0.571414\tres = 2.379E-02\n", - "[ NORMAL ] Iteration 47:\tk_eff = 0.584691\tres = 2.353E-02\n", - "[ NORMAL ] Iteration 48:\tk_eff = 0.598077\tres = 2.323E-02\n", - "[ NORMAL ] Iteration 49:\tk_eff = 0.611545\tres = 2.289E-02\n", - "[ NORMAL ] Iteration 50:\tk_eff = 0.625068\tres = 2.252E-02\n", - "[ NORMAL ] Iteration 51:\tk_eff = 0.638624\tres = 2.211E-02\n", - "[ NORMAL ] Iteration 52:\tk_eff = 0.652187\tres = 2.169E-02\n", - "[ NORMAL ] Iteration 53:\tk_eff = 0.665734\tres = 2.124E-02\n", - "[ NORMAL ] Iteration 54:\tk_eff = 0.679246\tres = 2.077E-02\n", - "[ NORMAL ] Iteration 55:\tk_eff = 0.692702\tres = 2.030E-02\n", - "[ NORMAL ] Iteration 56:\tk_eff = 0.706083\tres = 1.981E-02\n", - "[ NORMAL ] Iteration 57:\tk_eff = 0.719373\tres = 1.932E-02\n", - "[ NORMAL ] Iteration 58:\tk_eff = 0.732554\tres = 1.882E-02\n", - "[ NORMAL ] Iteration 59:\tk_eff = 0.745613\tres = 1.832E-02\n", - "[ NORMAL ] Iteration 60:\tk_eff = 0.758535\tres = 1.783E-02\n", - "[ NORMAL ] Iteration 61:\tk_eff = 0.771307\tres = 1.733E-02\n", - "[ NORMAL ] Iteration 62:\tk_eff = 0.783919\tres = 1.684E-02\n", - "[ NORMAL ] Iteration 63:\tk_eff = 0.796359\tres = 1.635E-02\n", - "[ NORMAL ] Iteration 64:\tk_eff = 0.808617\tres = 1.587E-02\n", - "[ NORMAL ] Iteration 65:\tk_eff = 0.820685\tres = 1.539E-02\n", - "[ NORMAL ] Iteration 66:\tk_eff = 0.832556\tres = 1.492E-02\n", - "[ NORMAL ] Iteration 67:\tk_eff = 0.844222\tres = 1.446E-02\n", - "[ NORMAL ] Iteration 68:\tk_eff = 0.855678\tres = 1.401E-02\n", - "[ NORMAL ] Iteration 69:\tk_eff = 0.866918\tres = 1.357E-02\n", - "[ NORMAL ] Iteration 70:\tk_eff = 0.877939\tres = 1.314E-02\n", - "[ NORMAL ] Iteration 71:\tk_eff = 0.888734\tres = 1.271E-02\n", - "[ NORMAL ] Iteration 72:\tk_eff = 0.899303\tres = 1.230E-02\n", - "[ NORMAL ] Iteration 73:\tk_eff = 0.909643\tres = 1.189E-02\n", - "[ NORMAL ] Iteration 74:\tk_eff = 0.919752\tres = 1.150E-02\n", - "[ NORMAL ] Iteration 75:\tk_eff = 0.929628\tres = 1.111E-02\n", - "[ NORMAL ] Iteration 76:\tk_eff = 0.939271\tres = 1.074E-02\n", - "[ NORMAL ] Iteration 77:\tk_eff = 0.948681\tres = 1.037E-02\n", - "[ NORMAL ] Iteration 78:\tk_eff = 0.957857\tres = 1.002E-02\n", - "[ NORMAL ] Iteration 79:\tk_eff = 0.966802\tres = 9.673E-03\n", - "[ NORMAL ] Iteration 80:\tk_eff = 0.975514\tres = 9.338E-03\n", - "[ NORMAL ] Iteration 81:\tk_eff = 0.983997\tres = 9.012E-03\n", - "[ NORMAL ] Iteration 82:\tk_eff = 0.992252\tres = 8.696E-03\n", - "[ NORMAL ] Iteration 83:\tk_eff = 1.000280\tres = 8.389E-03\n", - "[ NORMAL ] Iteration 84:\tk_eff = 1.008085\tres = 8.091E-03\n", - "[ NORMAL ] Iteration 85:\tk_eff = 1.015668\tres = 7.803E-03\n", - "[ NORMAL ] Iteration 86:\tk_eff = 1.023034\tres = 7.522E-03\n", - "[ NORMAL ] Iteration 87:\tk_eff = 1.030183\tres = 7.252E-03\n", - "[ NORMAL ] Iteration 88:\tk_eff = 1.037121\tres = 6.989E-03\n", - "[ NORMAL ] Iteration 89:\tk_eff = 1.043850\tres = 6.735E-03\n", - "[ NORMAL ] Iteration 90:\tk_eff = 1.050374\tres = 6.488E-03\n", - "[ NORMAL ] Iteration 91:\tk_eff = 1.056697\tres = 6.250E-03\n", - "[ NORMAL ] Iteration 92:\tk_eff = 1.062823\tres = 6.020E-03\n", - "[ NORMAL ] Iteration 93:\tk_eff = 1.068754\tres = 5.797E-03\n", - "[ NORMAL ] Iteration 94:\tk_eff = 1.074496\tres = 5.581E-03\n", - "[ NORMAL ] Iteration 95:\tk_eff = 1.080052\tres = 5.372E-03\n", - "[ NORMAL ] Iteration 96:\tk_eff = 1.085427\tres = 5.171E-03\n", - "[ NORMAL ] Iteration 97:\tk_eff = 1.090623\tres = 4.976E-03\n", - "[ NORMAL ] Iteration 98:\tk_eff = 1.095647\tres = 4.788E-03\n", - "[ NORMAL ] Iteration 99:\tk_eff = 1.100501\tres = 4.606E-03\n", - "[ NORMAL ] Iteration 100:\tk_eff = 1.105190\tres = 4.431E-03\n", - "[ NORMAL ] Iteration 101:\tk_eff = 1.109719\tres = 4.261E-03\n", - "[ NORMAL ] Iteration 102:\tk_eff = 1.114091\tres = 4.098E-03\n", - "[ NORMAL ] Iteration 103:\tk_eff = 1.118310\tres = 3.940E-03\n", - "[ NORMAL ] Iteration 104:\tk_eff = 1.122382\tres = 3.787E-03\n", - "[ NORMAL ] Iteration 105:\tk_eff = 1.126308\tres = 3.640E-03\n", - "[ NORMAL ] Iteration 106:\tk_eff = 1.130095\tres = 3.498E-03\n", - "[ NORMAL ] Iteration 107:\tk_eff = 1.133745\tres = 3.362E-03\n", - "[ NORMAL ] Iteration 108:\tk_eff = 1.137262\tres = 3.230E-03\n", - "[ NORMAL ] Iteration 109:\tk_eff = 1.140652\tres = 3.102E-03\n", - "[ NORMAL ] Iteration 110:\tk_eff = 1.143916\tres = 2.980E-03\n", - "[ NORMAL ] Iteration 111:\tk_eff = 1.147061\tres = 2.862E-03\n", - "[ NORMAL ] Iteration 112:\tk_eff = 1.150087\tres = 2.749E-03\n", - "[ NORMAL ] Iteration 113:\tk_eff = 1.153001\tres = 2.638E-03\n", - "[ NORMAL ] Iteration 114:\tk_eff = 1.155805\tres = 2.534E-03\n", - "[ NORMAL ] Iteration 115:\tk_eff = 1.158502\tres = 2.432E-03\n", - "[ NORMAL ] Iteration 116:\tk_eff = 1.161096\tres = 2.333E-03\n", - "[ NORMAL ] Iteration 117:\tk_eff = 1.163591\tres = 2.239E-03\n", - "[ NORMAL ] Iteration 118:\tk_eff = 1.165989\tres = 2.149E-03\n", - "[ NORMAL ] Iteration 119:\tk_eff = 1.168295\tres = 2.061E-03\n", - "[ NORMAL ] Iteration 120:\tk_eff = 1.170511\tres = 1.978E-03\n", - "[ NORMAL ] Iteration 121:\tk_eff = 1.172640\tres = 1.896E-03\n", - "[ NORMAL ] Iteration 122:\tk_eff = 1.174685\tres = 1.819E-03\n", - "[ NORMAL ] Iteration 123:\tk_eff = 1.176649\tres = 1.744E-03\n", - "[ NORMAL ] Iteration 124:\tk_eff = 1.178535\tres = 1.672E-03\n", - "[ NORMAL ] Iteration 125:\tk_eff = 1.180345\tres = 1.603E-03\n", - "[ NORMAL ] Iteration 126:\tk_eff = 1.182083\tres = 1.536E-03\n", - "[ NORMAL ] Iteration 127:\tk_eff = 1.183751\tres = 1.473E-03\n", - "[ NORMAL ] Iteration 128:\tk_eff = 1.185352\tres = 1.411E-03\n", - "[ NORMAL ] Iteration 129:\tk_eff = 1.186888\tres = 1.352E-03\n", - "[ NORMAL ] Iteration 130:\tk_eff = 1.188360\tres = 1.295E-03\n", - "[ NORMAL ] Iteration 131:\tk_eff = 1.189772\tres = 1.241E-03\n", - "[ NORMAL ] Iteration 132:\tk_eff = 1.191127\tres = 1.189E-03\n", - "[ NORMAL ] Iteration 133:\tk_eff = 1.192425\tres = 1.138E-03\n", - "[ NORMAL ] Iteration 134:\tk_eff = 1.193670\tres = 1.090E-03\n", - "[ NORMAL ] Iteration 135:\tk_eff = 1.194863\tres = 1.044E-03\n", - "[ NORMAL ] Iteration 136:\tk_eff = 1.196006\tres = 9.995E-04\n", - "[ NORMAL ] Iteration 137:\tk_eff = 1.197101\tres = 9.566E-04\n", - "[ NORMAL ] Iteration 138:\tk_eff = 1.198150\tres = 9.162E-04\n", - "[ NORMAL ] Iteration 139:\tk_eff = 1.199155\tres = 8.766E-04\n", - "[ NORMAL ] Iteration 140:\tk_eff = 1.200116\tres = 8.384E-04\n", - "[ NORMAL ] Iteration 141:\tk_eff = 1.201038\tres = 8.017E-04\n", - "[ NORMAL ] Iteration 142:\tk_eff = 1.201920\tres = 7.684E-04\n", - "[ NORMAL ] Iteration 143:\tk_eff = 1.202765\tres = 7.343E-04\n", - "[ NORMAL ] Iteration 144:\tk_eff = 1.203573\tres = 7.030E-04\n", - "[ NORMAL ] Iteration 145:\tk_eff = 1.204347\tres = 6.718E-04\n", - "[ NORMAL ] Iteration 146:\tk_eff = 1.205087\tres = 6.424E-04\n", - "[ NORMAL ] Iteration 147:\tk_eff = 1.205796\tres = 6.147E-04\n", - "[ NORMAL ] Iteration 148:\tk_eff = 1.206473\tres = 5.881E-04\n", - "[ NORMAL ] Iteration 149:\tk_eff = 1.207122\tres = 5.614E-04\n", - "[ NORMAL ] Iteration 150:\tk_eff = 1.207741\tres = 5.376E-04\n", - "[ NORMAL ] Iteration 151:\tk_eff = 1.208334\tres = 5.136E-04\n", - "[ NORMAL ] Iteration 152:\tk_eff = 1.208901\tres = 4.903E-04\n", - "[ NORMAL ] Iteration 153:\tk_eff = 1.209443\tres = 4.695E-04\n", - "[ NORMAL ] Iteration 154:\tk_eff = 1.209961\tres = 4.482E-04\n", - "[ NORMAL ] Iteration 155:\tk_eff = 1.210455\tres = 4.282E-04\n", - "[ NORMAL ] Iteration 156:\tk_eff = 1.210928\tres = 4.088E-04\n", - "[ NORMAL ] Iteration 157:\tk_eff = 1.211380\tres = 3.910E-04\n", - "[ NORMAL ] Iteration 158:\tk_eff = 1.211813\tres = 3.730E-04\n", - "[ NORMAL ] Iteration 159:\tk_eff = 1.212225\tres = 3.573E-04\n", - "[ NORMAL ] Iteration 160:\tk_eff = 1.212620\tres = 3.403E-04\n", - "[ NORMAL ] Iteration 161:\tk_eff = 1.212997\tres = 3.256E-04\n", - "[ NORMAL ] Iteration 162:\tk_eff = 1.213356\tres = 3.107E-04\n", - "[ NORMAL ] Iteration 163:\tk_eff = 1.213700\tres = 2.965E-04\n", - "[ NORMAL ] Iteration 164:\tk_eff = 1.214028\tres = 2.829E-04\n", - "[ NORMAL ] Iteration 165:\tk_eff = 1.214341\tres = 2.706E-04\n", - "[ NORMAL ] Iteration 166:\tk_eff = 1.214640\tres = 2.581E-04\n", - "[ NORMAL ] Iteration 167:\tk_eff = 1.214926\tres = 2.465E-04\n", - "[ NORMAL ] Iteration 168:\tk_eff = 1.215199\tres = 2.352E-04\n", - "[ NORMAL ] Iteration 169:\tk_eff = 1.215459\tres = 2.241E-04\n", - "[ NORMAL ] Iteration 170:\tk_eff = 1.215707\tres = 2.144E-04\n", - "[ NORMAL ] Iteration 171:\tk_eff = 1.215944\tres = 2.036E-04\n", - "[ NORMAL ] Iteration 172:\tk_eff = 1.216170\tres = 1.952E-04\n", - "[ NORMAL ] Iteration 173:\tk_eff = 1.216386\tres = 1.854E-04\n", - "[ NORMAL ] Iteration 174:\tk_eff = 1.216592\tres = 1.780E-04\n", - "[ NORMAL ] Iteration 175:\tk_eff = 1.216788\tres = 1.689E-04\n", - "[ NORMAL ] Iteration 176:\tk_eff = 1.216976\tres = 1.615E-04\n", - "[ NORMAL ] Iteration 177:\tk_eff = 1.217154\tres = 1.545E-04\n", - "[ NORMAL ] Iteration 178:\tk_eff = 1.217325\tres = 1.462E-04\n", - "[ NORMAL ] Iteration 179:\tk_eff = 1.217487\tres = 1.400E-04\n", - "[ NORMAL ] Iteration 180:\tk_eff = 1.217642\tres = 1.336E-04\n", - "[ NORMAL ] Iteration 181:\tk_eff = 1.217789\tres = 1.271E-04\n", - "[ NORMAL ] Iteration 182:\tk_eff = 1.217931\tres = 1.212E-04\n", - "[ NORMAL ] Iteration 183:\tk_eff = 1.218065\tres = 1.160E-04\n", - "[ NORMAL ] Iteration 184:\tk_eff = 1.218193\tres = 1.102E-04\n", - "[ NORMAL ] Iteration 185:\tk_eff = 1.218315\tres = 1.054E-04\n", - "[ NORMAL ] Iteration 186:\tk_eff = 1.218431\tres = 9.964E-05\n", - "[ NORMAL ] Iteration 187:\tk_eff = 1.218542\tres = 9.572E-05\n", - "[ NORMAL ] Iteration 188:\tk_eff = 1.218647\tres = 9.081E-05\n", - "[ NORMAL ] Iteration 189:\tk_eff = 1.218748\tres = 8.632E-05\n", - "[ NORMAL ] Iteration 190:\tk_eff = 1.218844\tres = 8.232E-05\n", - "[ NORMAL ] Iteration 191:\tk_eff = 1.218936\tres = 7.887E-05\n", - "[ NORMAL ] Iteration 192:\tk_eff = 1.219023\tres = 7.563E-05\n", - "[ NORMAL ] Iteration 193:\tk_eff = 1.219106\tres = 7.154E-05\n", - "[ NORMAL ] Iteration 194:\tk_eff = 1.219185\tres = 6.778E-05\n", - "[ NORMAL ] Iteration 195:\tk_eff = 1.219261\tres = 6.484E-05\n", - "[ NORMAL ] Iteration 196:\tk_eff = 1.219333\tres = 6.207E-05\n", - "[ NORMAL ] Iteration 197:\tk_eff = 1.219401\tres = 5.898E-05\n", - "[ NORMAL ] Iteration 198:\tk_eff = 1.219466\tres = 5.590E-05\n", - "[ NORMAL ] Iteration 199:\tk_eff = 1.219528\tres = 5.305E-05\n", - "[ NORMAL ] Iteration 200:\tk_eff = 1.219587\tres = 5.076E-05\n", - "[ NORMAL ] Iteration 201:\tk_eff = 1.219643\tres = 4.829E-05\n", - "[ NORMAL ] Iteration 202:\tk_eff = 1.219696\tres = 4.595E-05\n", - "[ NORMAL ] Iteration 203:\tk_eff = 1.219747\tres = 4.399E-05\n", - "[ NORMAL ] Iteration 204:\tk_eff = 1.219796\tres = 4.161E-05\n", - "[ NORMAL ] Iteration 205:\tk_eff = 1.219842\tres = 3.973E-05\n", - "[ NORMAL ] Iteration 206:\tk_eff = 1.219886\tres = 3.781E-05\n", - "[ NORMAL ] Iteration 207:\tk_eff = 1.219928\tres = 3.629E-05\n", - "[ NORMAL ] Iteration 208:\tk_eff = 1.219968\tres = 3.451E-05\n", - "[ NORMAL ] Iteration 209:\tk_eff = 1.220006\tres = 3.274E-05\n", - "[ NORMAL ] Iteration 210:\tk_eff = 1.220042\tres = 3.108E-05\n", - "[ NORMAL ] Iteration 211:\tk_eff = 1.220076\tres = 2.951E-05\n", - "[ NORMAL ] Iteration 212:\tk_eff = 1.220108\tres = 2.785E-05\n", - "[ NORMAL ] Iteration 213:\tk_eff = 1.220139\tres = 2.664E-05\n", - "[ NORMAL ] Iteration 214:\tk_eff = 1.220169\tres = 2.518E-05\n", - "[ NORMAL ] Iteration 215:\tk_eff = 1.220197\tres = 2.463E-05\n", - "[ NORMAL ] Iteration 216:\tk_eff = 1.220224\tres = 2.318E-05\n", - "[ NORMAL ] Iteration 217:\tk_eff = 1.220249\tres = 2.189E-05\n", - "[ NORMAL ] Iteration 218:\tk_eff = 1.220273\tres = 2.075E-05\n", - "[ NORMAL ] Iteration 219:\tk_eff = 1.220296\tres = 1.964E-05\n", - "[ NORMAL ] Iteration 220:\tk_eff = 1.220318\tres = 1.922E-05\n", - "[ NORMAL ] Iteration 221:\tk_eff = 1.220339\tres = 1.776E-05\n", - "[ NORMAL ] Iteration 222:\tk_eff = 1.220358\tres = 1.701E-05\n", - "[ NORMAL ] Iteration 223:\tk_eff = 1.220377\tres = 1.615E-05\n", - "[ NORMAL ] Iteration 224:\tk_eff = 1.220395\tres = 1.559E-05\n", - "[ NORMAL ] Iteration 225:\tk_eff = 1.220412\tres = 1.443E-05\n", - "[ NORMAL ] Iteration 226:\tk_eff = 1.220429\tres = 1.403E-05\n", - "[ NORMAL ] Iteration 227:\tk_eff = 1.220444\tres = 1.354E-05\n", - "[ NORMAL ] Iteration 228:\tk_eff = 1.220459\tres = 1.235E-05\n", - "[ NORMAL ] Iteration 229:\tk_eff = 1.220472\tres = 1.204E-05\n", - "[ NORMAL ] Iteration 230:\tk_eff = 1.220485\tres = 1.135E-05\n", - "[ NORMAL ] Iteration 231:\tk_eff = 1.220498\tres = 1.085E-05\n", - "[ NORMAL ] Iteration 232:\tk_eff = 1.220510\tres = 1.022E-05\n" + "[ NORMAL ] Iteration 0:\tk_eff = 0.423134\tres = 0.000E+00\n", + "[ NORMAL ] Iteration 1:\tk_eff = 0.475951\tres = 5.769E-01\n", + "[ NORMAL ] Iteration 2:\tk_eff = 0.491466\tres = 1.248E-01\n", + "[ NORMAL ] Iteration 3:\tk_eff = 0.487444\tres = 3.260E-02\n", + "[ NORMAL ] Iteration 4:\tk_eff = 0.483929\tres = 8.184E-03\n", + "[ NORMAL ] Iteration 5:\tk_eff = 0.477278\tres = 7.213E-03\n", + "[ NORMAL ] Iteration 6:\tk_eff = 0.468936\tres = 1.374E-02\n", + "[ NORMAL ] Iteration 7:\tk_eff = 0.460317\tres = 1.748E-02\n", + "[ NORMAL ] Iteration 8:\tk_eff = 0.450589\tres = 1.838E-02\n", + "[ NORMAL ] Iteration 9:\tk_eff = 0.441375\tres = 2.113E-02\n", + "[ NORMAL ] Iteration 10:\tk_eff = 0.431988\tres = 2.045E-02\n", + "[ NORMAL ] Iteration 11:\tk_eff = 0.422929\tres = 2.127E-02\n", + "[ NORMAL ] Iteration 12:\tk_eff = 0.414483\tres = 2.097E-02\n", + "[ NORMAL ] Iteration 13:\tk_eff = 0.406704\tres = 1.997E-02\n", + "[ NORMAL ] Iteration 14:\tk_eff = 0.399374\tres = 1.877E-02\n", + "[ NORMAL ] Iteration 15:\tk_eff = 0.393064\tres = 1.802E-02\n", + "[ NORMAL ] Iteration 16:\tk_eff = 0.387423\tres = 1.580E-02\n", + "[ NORMAL ] Iteration 17:\tk_eff = 0.382664\tres = 1.435E-02\n", + "[ NORMAL ] Iteration 18:\tk_eff = 0.378737\tres = 1.228E-02\n", + "[ NORMAL ] Iteration 19:\tk_eff = 0.375638\tres = 1.026E-02\n", + "[ NORMAL ] Iteration 20:\tk_eff = 0.373485\tres = 8.180E-03\n", + "[ NORMAL ] Iteration 21:\tk_eff = 0.372353\tres = 5.734E-03\n", + "[ NORMAL ] Iteration 22:\tk_eff = 0.371970\tres = 3.031E-03\n", + "[ NORMAL ] Iteration 23:\tk_eff = 0.372577\tres = 1.027E-03\n", + "[ NORMAL ] Iteration 24:\tk_eff = 0.374052\tres = 1.632E-03\n", + "[ NORMAL ] Iteration 25:\tk_eff = 0.376380\tres = 3.959E-03\n", + "[ NORMAL ] Iteration 26:\tk_eff = 0.379559\tres = 6.223E-03\n", + "[ NORMAL ] Iteration 27:\tk_eff = 0.383579\tres = 8.447E-03\n", + "[ NORMAL ] Iteration 28:\tk_eff = 0.388377\tres = 1.059E-02\n", + "[ NORMAL ] Iteration 29:\tk_eff = 0.393934\tres = 1.251E-02\n", + "[ NORMAL ] Iteration 30:\tk_eff = 0.400230\tres = 1.431E-02\n", + "[ NORMAL ] Iteration 31:\tk_eff = 0.407231\tres = 1.598E-02\n", + "[ NORMAL ] Iteration 32:\tk_eff = 0.414881\tres = 1.749E-02\n", + "[ NORMAL ] Iteration 33:\tk_eff = 0.423169\tres = 1.879E-02\n", + "[ NORMAL ] Iteration 34:\tk_eff = 0.432048\tres = 1.998E-02\n", + "[ NORMAL ] Iteration 35:\tk_eff = 0.441469\tres = 2.098E-02\n", + "[ NORMAL ] Iteration 36:\tk_eff = 0.451428\tres = 2.181E-02\n", + "[ NORMAL ] Iteration 37:\tk_eff = 0.461851\tres = 2.256E-02\n", + "[ NORMAL ] Iteration 38:\tk_eff = 0.472728\tres = 2.309E-02\n", + "[ NORMAL ] Iteration 39:\tk_eff = 0.484004\tres = 2.355E-02\n", + "[ NORMAL ] Iteration 40:\tk_eff = 0.495652\tres = 2.385E-02\n", + "[ NORMAL ] Iteration 41:\tk_eff = 0.507633\tres = 2.406E-02\n", + "[ NORMAL ] Iteration 42:\tk_eff = 0.519913\tres = 2.417E-02\n", + "[ NORMAL ] Iteration 43:\tk_eff = 0.532458\tres = 2.419E-02\n", + "[ NORMAL ] Iteration 44:\tk_eff = 0.545234\tres = 2.413E-02\n", + "[ NORMAL ] Iteration 45:\tk_eff = 0.558210\tres = 2.400E-02\n", + "[ NORMAL ] Iteration 46:\tk_eff = 0.571353\tres = 2.380E-02\n", + "[ NORMAL ] Iteration 47:\tk_eff = 0.584635\tres = 2.354E-02\n", + "[ NORMAL ] Iteration 48:\tk_eff = 0.598027\tres = 2.325E-02\n", + "[ NORMAL ] Iteration 49:\tk_eff = 0.611501\tres = 2.291E-02\n", + "[ NORMAL ] Iteration 50:\tk_eff = 0.625030\tres = 2.253E-02\n", + "[ NORMAL ] Iteration 51:\tk_eff = 0.638591\tres = 2.212E-02\n", + "[ NORMAL ] Iteration 52:\tk_eff = 0.652160\tres = 2.170E-02\n", + "[ NORMAL ] Iteration 53:\tk_eff = 0.665712\tres = 2.125E-02\n", + "[ NORMAL ] Iteration 54:\tk_eff = 0.679230\tres = 2.078E-02\n", + "[ NORMAL ] Iteration 55:\tk_eff = 0.692690\tres = 2.031E-02\n", + "[ NORMAL ] Iteration 56:\tk_eff = 0.706077\tres = 1.982E-02\n", + "[ NORMAL ] Iteration 57:\tk_eff = 0.719372\tres = 1.933E-02\n", + "[ NORMAL ] Iteration 58:\tk_eff = 0.732558\tres = 1.883E-02\n", + "[ NORMAL ] Iteration 59:\tk_eff = 0.745622\tres = 1.833E-02\n", + "[ NORMAL ] Iteration 60:\tk_eff = 0.758549\tres = 1.783E-02\n", + "[ NORMAL ] Iteration 61:\tk_eff = 0.771326\tres = 1.734E-02\n", + "[ NORMAL ] Iteration 62:\tk_eff = 0.783943\tres = 1.684E-02\n", + "[ NORMAL ] Iteration 63:\tk_eff = 0.796387\tres = 1.636E-02\n", + "[ NORMAL ] Iteration 64:\tk_eff = 0.808649\tres = 1.587E-02\n", + "[ NORMAL ] Iteration 65:\tk_eff = 0.820722\tres = 1.540E-02\n", + "[ NORMAL ] Iteration 66:\tk_eff = 0.832597\tres = 1.493E-02\n", + "[ NORMAL ] Iteration 67:\tk_eff = 0.844268\tres = 1.447E-02\n", + "[ NORMAL ] Iteration 68:\tk_eff = 0.855727\tres = 1.402E-02\n", + "[ NORMAL ] Iteration 69:\tk_eff = 0.866972\tres = 1.357E-02\n", + "[ NORMAL ] Iteration 70:\tk_eff = 0.877995\tres = 1.314E-02\n", + "[ NORMAL ] Iteration 71:\tk_eff = 0.888795\tres = 1.272E-02\n", + "[ NORMAL ] Iteration 72:\tk_eff = 0.899368\tres = 1.230E-02\n", + "[ NORMAL ] Iteration 73:\tk_eff = 0.909712\tres = 1.190E-02\n", + "[ NORMAL ] Iteration 74:\tk_eff = 0.919823\tres = 1.150E-02\n", + "[ NORMAL ] Iteration 75:\tk_eff = 0.929703\tres = 1.112E-02\n", + "[ NORMAL ] Iteration 76:\tk_eff = 0.939349\tres = 1.074E-02\n", + "[ NORMAL ] Iteration 77:\tk_eff = 0.948762\tres = 1.038E-02\n", + "[ NORMAL ] Iteration 78:\tk_eff = 0.957942\tres = 1.002E-02\n", + "[ NORMAL ] Iteration 79:\tk_eff = 0.966889\tres = 9.676E-03\n", + "[ NORMAL ] Iteration 80:\tk_eff = 0.975605\tres = 9.339E-03\n", + "[ NORMAL ] Iteration 81:\tk_eff = 0.984090\tres = 9.015E-03\n", + "[ NORMAL ] Iteration 82:\tk_eff = 0.992347\tres = 8.698E-03\n", + "[ NORMAL ] Iteration 83:\tk_eff = 1.000378\tres = 8.391E-03\n", + "[ NORMAL ] Iteration 84:\tk_eff = 1.008186\tres = 8.093E-03\n", + "[ NORMAL ] Iteration 85:\tk_eff = 1.015772\tres = 7.804E-03\n", + "[ NORMAL ] Iteration 86:\tk_eff = 1.023139\tres = 7.524E-03\n", + "[ NORMAL ] Iteration 87:\tk_eff = 1.030292\tres = 7.253E-03\n", + "[ NORMAL ] Iteration 88:\tk_eff = 1.037231\tres = 6.991E-03\n", + "[ NORMAL ] Iteration 89:\tk_eff = 1.043963\tres = 6.736E-03\n", + "[ NORMAL ] Iteration 90:\tk_eff = 1.050489\tres = 6.490E-03\n", + "[ NORMAL ] Iteration 91:\tk_eff = 1.056814\tres = 6.252E-03\n", + "[ NORMAL ] Iteration 92:\tk_eff = 1.062941\tres = 6.021E-03\n", + "[ NORMAL ] Iteration 93:\tk_eff = 1.068875\tres = 5.798E-03\n", + "[ NORMAL ] Iteration 94:\tk_eff = 1.074618\tres = 5.582E-03\n", + "[ NORMAL ] Iteration 95:\tk_eff = 1.080176\tres = 5.373E-03\n", + "[ NORMAL ] Iteration 96:\tk_eff = 1.085552\tres = 5.172E-03\n", + "[ NORMAL ] Iteration 97:\tk_eff = 1.090750\tres = 4.977E-03\n", + "[ NORMAL ] Iteration 98:\tk_eff = 1.095775\tres = 4.789E-03\n", + "[ NORMAL ] Iteration 99:\tk_eff = 1.100631\tres = 4.607E-03\n", + "[ NORMAL ] Iteration 100:\tk_eff = 1.105322\tres = 4.432E-03\n", + "[ NORMAL ] Iteration 101:\tk_eff = 1.109852\tres = 4.262E-03\n", + "[ NORMAL ] Iteration 102:\tk_eff = 1.114225\tres = 4.098E-03\n", + "[ NORMAL ] Iteration 103:\tk_eff = 1.118446\tres = 3.940E-03\n", + "[ NORMAL ] Iteration 104:\tk_eff = 1.122519\tres = 3.788E-03\n", + "[ NORMAL ] Iteration 105:\tk_eff = 1.126446\tres = 3.641E-03\n", + "[ NORMAL ] Iteration 106:\tk_eff = 1.130234\tres = 3.499E-03\n", + "[ NORMAL ] Iteration 107:\tk_eff = 1.133885\tres = 3.362E-03\n", + "[ NORMAL ] Iteration 108:\tk_eff = 1.137404\tres = 3.231E-03\n", + "[ NORMAL ] Iteration 109:\tk_eff = 1.140795\tres = 3.103E-03\n", + "[ NORMAL ] Iteration 110:\tk_eff = 1.144060\tres = 2.981E-03\n", + "[ NORMAL ] Iteration 111:\tk_eff = 1.147205\tres = 2.863E-03\n", + "[ NORMAL ] Iteration 112:\tk_eff = 1.150233\tres = 2.749E-03\n", + "[ NORMAL ] Iteration 113:\tk_eff = 1.153147\tres = 2.639E-03\n", + "[ NORMAL ] Iteration 114:\tk_eff = 1.155952\tres = 2.533E-03\n", + "[ NORMAL ] Iteration 115:\tk_eff = 1.158650\tres = 2.432E-03\n", + "[ NORMAL ] Iteration 116:\tk_eff = 1.161245\tres = 2.334E-03\n", + "[ NORMAL ] Iteration 117:\tk_eff = 1.163741\tres = 2.240E-03\n", + "[ NORMAL ] Iteration 118:\tk_eff = 1.166140\tres = 2.149E-03\n", + "[ NORMAL ] Iteration 119:\tk_eff = 1.168446\tres = 2.061E-03\n", + "[ NORMAL ] Iteration 120:\tk_eff = 1.170663\tres = 1.978E-03\n", + "[ NORMAL ] Iteration 121:\tk_eff = 1.172792\tres = 1.897E-03\n", + "[ NORMAL ] Iteration 122:\tk_eff = 1.174838\tres = 1.819E-03\n", + "[ NORMAL ] Iteration 123:\tk_eff = 1.176803\tres = 1.745E-03\n", + "[ NORMAL ] Iteration 124:\tk_eff = 1.178689\tres = 1.672E-03\n", + "[ NORMAL ] Iteration 125:\tk_eff = 1.180500\tres = 1.603E-03\n", + "[ NORMAL ] Iteration 126:\tk_eff = 1.182238\tres = 1.537E-03\n", + "[ NORMAL ] Iteration 127:\tk_eff = 1.183907\tres = 1.473E-03\n", + "[ NORMAL ] Iteration 128:\tk_eff = 1.185508\tres = 1.411E-03\n", + "[ NORMAL ] Iteration 129:\tk_eff = 1.187044\tres = 1.352E-03\n", + "[ NORMAL ] Iteration 130:\tk_eff = 1.188517\tres = 1.296E-03\n", + "[ NORMAL ] Iteration 131:\tk_eff = 1.189930\tres = 1.241E-03\n", + "[ NORMAL ] Iteration 132:\tk_eff = 1.191285\tres = 1.189E-03\n", + "[ NORMAL ] Iteration 133:\tk_eff = 1.192584\tres = 1.139E-03\n", + "[ NORMAL ] Iteration 134:\tk_eff = 1.193829\tres = 1.090E-03\n", + "[ NORMAL ] Iteration 135:\tk_eff = 1.195022\tres = 1.044E-03\n", + "[ NORMAL ] Iteration 136:\tk_eff = 1.196165\tres = 9.990E-04\n", + "[ NORMAL ] Iteration 137:\tk_eff = 1.197260\tres = 9.567E-04\n", + "[ NORMAL ] Iteration 138:\tk_eff = 1.198310\tres = 9.157E-04\n", + "[ NORMAL ] Iteration 139:\tk_eff = 1.199315\tres = 8.769E-04\n", + "[ NORMAL ] Iteration 140:\tk_eff = 1.200278\tres = 8.387E-04\n", + "[ NORMAL ] Iteration 141:\tk_eff = 1.201200\tres = 8.029E-04\n", + "[ NORMAL ] Iteration 142:\tk_eff = 1.202082\tres = 7.680E-04\n", + "[ NORMAL ] Iteration 143:\tk_eff = 1.202927\tres = 7.346E-04\n", + "[ NORMAL ] Iteration 144:\tk_eff = 1.203735\tres = 7.025E-04\n", + "[ NORMAL ] Iteration 145:\tk_eff = 1.204509\tres = 6.720E-04\n", + "[ NORMAL ] Iteration 146:\tk_eff = 1.205250\tres = 6.427E-04\n", + "[ NORMAL ] Iteration 147:\tk_eff = 1.205958\tres = 6.153E-04\n", + "[ NORMAL ] Iteration 148:\tk_eff = 1.206636\tres = 5.875E-04\n", + "[ NORMAL ] Iteration 149:\tk_eff = 1.207283\tres = 5.623E-04\n", + "[ NORMAL ] Iteration 150:\tk_eff = 1.207903\tres = 5.369E-04\n", + "[ NORMAL ] Iteration 151:\tk_eff = 1.208496\tres = 5.133E-04\n", + "[ NORMAL ] Iteration 152:\tk_eff = 1.209063\tres = 4.911E-04\n", + "[ NORMAL ] Iteration 153:\tk_eff = 1.209605\tres = 4.693E-04\n", + "[ NORMAL ] Iteration 154:\tk_eff = 1.210123\tres = 4.478E-04\n", + "[ NORMAL ] Iteration 155:\tk_eff = 1.210618\tres = 4.283E-04\n", + "[ NORMAL ] Iteration 156:\tk_eff = 1.211091\tres = 4.091E-04\n", + "[ NORMAL ] Iteration 157:\tk_eff = 1.211544\tres = 3.910E-04\n", + "[ NORMAL ] Iteration 158:\tk_eff = 1.211976\tres = 3.738E-04\n", + "[ NORMAL ] Iteration 159:\tk_eff = 1.212389\tres = 3.565E-04\n", + "[ NORMAL ] Iteration 160:\tk_eff = 1.212783\tres = 3.406E-04\n", + "[ NORMAL ] Iteration 161:\tk_eff = 1.213160\tres = 3.252E-04\n", + "[ NORMAL ] Iteration 162:\tk_eff = 1.213519\tres = 3.108E-04\n", + "[ NORMAL ] Iteration 163:\tk_eff = 1.213863\tres = 2.962E-04\n", + "[ NORMAL ] Iteration 164:\tk_eff = 1.214191\tres = 2.837E-04\n", + "[ NORMAL ] Iteration 165:\tk_eff = 1.214505\tres = 2.699E-04\n", + "[ NORMAL ] Iteration 166:\tk_eff = 1.214804\tres = 2.585E-04\n", + "[ NORMAL ] Iteration 167:\tk_eff = 1.215089\tres = 2.463E-04\n", + "[ NORMAL ] Iteration 168:\tk_eff = 1.215362\tres = 2.349E-04\n", + "[ NORMAL ] Iteration 169:\tk_eff = 1.215622\tres = 2.247E-04\n", + "[ NORMAL ] Iteration 170:\tk_eff = 1.215871\tres = 2.140E-04\n", + "[ NORMAL ] Iteration 171:\tk_eff = 1.216107\tres = 2.044E-04\n", + "[ NORMAL ] Iteration 172:\tk_eff = 1.216334\tres = 1.948E-04\n", + "[ NORMAL ] Iteration 173:\tk_eff = 1.216549\tres = 1.861E-04\n", + "[ NORMAL ] Iteration 174:\tk_eff = 1.216755\tres = 1.769E-04\n", + "[ NORMAL ] Iteration 175:\tk_eff = 1.216952\tres = 1.697E-04\n", + "[ NORMAL ] Iteration 176:\tk_eff = 1.217139\tres = 1.615E-04\n", + "[ NORMAL ] Iteration 177:\tk_eff = 1.217317\tres = 1.541E-04\n", + "[ NORMAL ] Iteration 178:\tk_eff = 1.217487\tres = 1.462E-04\n", + "[ NORMAL ] Iteration 179:\tk_eff = 1.217650\tres = 1.398E-04\n", + "[ NORMAL ] Iteration 180:\tk_eff = 1.217805\tres = 1.335E-04\n", + "[ NORMAL ] Iteration 181:\tk_eff = 1.217953\tres = 1.274E-04\n", + "[ NORMAL ] Iteration 182:\tk_eff = 1.218094\tres = 1.217E-04\n", + "[ NORMAL ] Iteration 183:\tk_eff = 1.218228\tres = 1.159E-04\n", + "[ NORMAL ] Iteration 184:\tk_eff = 1.218356\tres = 1.100E-04\n", + "[ NORMAL ] Iteration 185:\tk_eff = 1.218478\tres = 1.048E-04\n", + "[ NORMAL ] Iteration 186:\tk_eff = 1.218595\tres = 1.003E-04\n", + "[ NORMAL ] Iteration 187:\tk_eff = 1.218706\tres = 9.574E-05\n", + "[ NORMAL ] Iteration 188:\tk_eff = 1.218811\tres = 9.094E-05\n", + "[ NORMAL ] Iteration 189:\tk_eff = 1.218912\tres = 8.629E-05\n", + "[ NORMAL ] Iteration 190:\tk_eff = 1.219007\tres = 8.297E-05\n", + "[ NORMAL ] Iteration 191:\tk_eff = 1.219099\tres = 7.863E-05\n", + "[ NORMAL ] Iteration 192:\tk_eff = 1.219186\tres = 7.491E-05\n", + "[ NORMAL ] Iteration 193:\tk_eff = 1.219269\tres = 7.150E-05\n", + "[ NORMAL ] Iteration 194:\tk_eff = 1.219348\tres = 6.807E-05\n", + "[ NORMAL ] Iteration 195:\tk_eff = 1.219422\tres = 6.487E-05\n", + "[ NORMAL ] Iteration 196:\tk_eff = 1.219495\tres = 6.123E-05\n", + "[ NORMAL ] Iteration 197:\tk_eff = 1.219563\tres = 5.915E-05\n", + "[ NORMAL ] Iteration 198:\tk_eff = 1.219628\tres = 5.636E-05\n", + "[ NORMAL ] Iteration 199:\tk_eff = 1.219690\tres = 5.317E-05\n", + "[ NORMAL ] Iteration 200:\tk_eff = 1.219749\tres = 5.112E-05\n", + "[ NORMAL ] Iteration 201:\tk_eff = 1.219805\tres = 4.824E-05\n", + "[ NORMAL ] Iteration 202:\tk_eff = 1.219859\tres = 4.589E-05\n", + "[ NORMAL ] Iteration 203:\tk_eff = 1.219910\tres = 4.396E-05\n", + "[ NORMAL ] Iteration 204:\tk_eff = 1.219958\tres = 4.158E-05\n", + "[ NORMAL ] Iteration 205:\tk_eff = 1.220004\tres = 3.979E-05\n", + "[ NORMAL ] Iteration 206:\tk_eff = 1.220049\tres = 3.760E-05\n", + "[ NORMAL ] Iteration 207:\tk_eff = 1.220090\tres = 3.661E-05\n", + "[ NORMAL ] Iteration 208:\tk_eff = 1.220130\tres = 3.402E-05\n", + "[ NORMAL ] Iteration 209:\tk_eff = 1.220168\tres = 3.256E-05\n", + "[ NORMAL ] Iteration 210:\tk_eff = 1.220204\tres = 3.124E-05\n", + "[ NORMAL ] Iteration 211:\tk_eff = 1.220238\tres = 2.947E-05\n", + "[ NORMAL ] Iteration 212:\tk_eff = 1.220271\tres = 2.821E-05\n", + "[ NORMAL ] Iteration 213:\tk_eff = 1.220302\tres = 2.666E-05\n", + "[ NORMAL ] Iteration 214:\tk_eff = 1.220332\tres = 2.561E-05\n", + "[ NORMAL ] Iteration 215:\tk_eff = 1.220360\tres = 2.450E-05\n", + "[ NORMAL ] Iteration 216:\tk_eff = 1.220387\tres = 2.299E-05\n", + "[ NORMAL ] Iteration 217:\tk_eff = 1.220413\tres = 2.190E-05\n", + "[ NORMAL ] Iteration 218:\tk_eff = 1.220437\tres = 2.109E-05\n", + "[ NORMAL ] Iteration 219:\tk_eff = 1.220460\tres = 1.982E-05\n", + "[ NORMAL ] Iteration 220:\tk_eff = 1.220482\tres = 1.916E-05\n", + "[ NORMAL ] Iteration 221:\tk_eff = 1.220503\tres = 1.792E-05\n", + "[ NORMAL ] Iteration 222:\tk_eff = 1.220523\tres = 1.701E-05\n", + "[ NORMAL ] Iteration 223:\tk_eff = 1.220541\tres = 1.615E-05\n", + "[ NORMAL ] Iteration 224:\tk_eff = 1.220559\tres = 1.526E-05\n", + "[ NORMAL ] Iteration 225:\tk_eff = 1.220576\tres = 1.439E-05\n", + "[ NORMAL ] Iteration 226:\tk_eff = 1.220592\tres = 1.418E-05\n", + "[ NORMAL ] Iteration 227:\tk_eff = 1.220608\tres = 1.350E-05\n", + "[ NORMAL ] Iteration 228:\tk_eff = 1.220623\tres = 1.269E-05\n", + "[ NORMAL ] Iteration 229:\tk_eff = 1.220637\tres = 1.193E-05\n", + "[ NORMAL ] Iteration 230:\tk_eff = 1.220650\tres = 1.161E-05\n", + "[ NORMAL ] Iteration 231:\tk_eff = 1.220663\tres = 1.090E-05\n", + "[ NORMAL ] Iteration 232:\tk_eff = 1.220675\tres = 1.033E-05\n" ] } ], @@ -1469,7 +1449,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "metadata": { "collapsed": false }, @@ -1479,8 +1459,8 @@ "output_type": "stream", "text": [ "openmc keff = 1.224484\n", - "openmoc keff = 1.220510\n", - "bias [pcm]: -397.4\n" + "openmoc keff = 1.220675\n", + "bias [pcm]: -380.9\n" ] } ], @@ -1504,7 +1484,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 26, "metadata": { "collapsed": false }, @@ -1513,11 +1493,11 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/wbinventor/Documents/NSE-CRPG-Codes/openmc/openmc/tallies.py:1835: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1798: RuntimeWarning: invalid value encountered in true_divide\n", " self_rel_err = data['self']['std. dev.'] / data['self']['mean']\n", - "/home/wbinventor/Documents/NSE-CRPG-Codes/openmc/openmc/tallies.py:1836: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1799: RuntimeWarning: invalid value encountered in true_divide\n", " other_rel_err = data['other']['std. dev.'] / data['other']['mean']\n", - "/home/wbinventor/Documents/NSE-CRPG-Codes/openmc/openmc/tallies.py:1837: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1800: RuntimeWarning: invalid value encountered in true_divide\n", " new_tally._mean = data['self']['mean'] / data['other']['mean']\n" ] } @@ -1557,7 +1537,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 27, "metadata": { "collapsed": false }, @@ -1568,347 +1548,346 @@ "text": [ "[ NORMAL ] Importing ray tracing data from file...\n", "[ NORMAL ] Computing the eigenvalue...\n", - "[ NORMAL ] Iteration 0:\tk_eff = 0.366890\tres = 0.000E+00\n", - "[ NORMAL ] Iteration 1:\tk_eff = 0.391200\tres = 6.331E-01\n", - "[ NORMAL ] Iteration 2:\tk_eff = 0.393015\tres = 6.626E-02\n", - "[ NORMAL ] Iteration 3:\tk_eff = 0.381131\tres = 4.640E-03\n", - "[ NORMAL ] Iteration 4:\tk_eff = 0.375055\tres = 3.024E-02\n", - "[ NORMAL ] Iteration 5:\tk_eff = 0.369635\tres = 1.594E-02\n", - "[ NORMAL ] Iteration 6:\tk_eff = 0.365588\tres = 1.445E-02\n", - "[ NORMAL ] Iteration 7:\tk_eff = 0.363102\tres = 1.095E-02\n", - "[ NORMAL ] Iteration 8:\tk_eff = 0.361522\tres = 6.801E-03\n", - "[ NORMAL ] Iteration 9:\tk_eff = 0.361329\tres = 4.350E-03\n", - "[ NORMAL ] Iteration 10:\tk_eff = 0.362052\tres = 5.337E-04\n", - "[ NORMAL ] Iteration 11:\tk_eff = 0.363766\tres = 2.000E-03\n", - "[ NORMAL ] Iteration 12:\tk_eff = 0.366383\tres = 4.733E-03\n", - "[ NORMAL ] Iteration 13:\tk_eff = 0.369846\tres = 7.196E-03\n", - "[ NORMAL ] Iteration 14:\tk_eff = 0.374028\tres = 9.452E-03\n", - "[ NORMAL ] Iteration 15:\tk_eff = 0.378957\tres = 1.131E-02\n", - "[ NORMAL ] Iteration 16:\tk_eff = 0.384508\tres = 1.318E-02\n", - "[ NORMAL ] Iteration 17:\tk_eff = 0.390660\tres = 1.465E-02\n", - "[ NORMAL ] Iteration 18:\tk_eff = 0.397354\tres = 1.600E-02\n", - "[ NORMAL ] Iteration 19:\tk_eff = 0.404542\tres = 1.713E-02\n", - "[ NORMAL ] Iteration 20:\tk_eff = 0.412186\tres = 1.809E-02\n", - "[ NORMAL ] Iteration 21:\tk_eff = 0.420246\tres = 1.889E-02\n", - "[ NORMAL ] Iteration 22:\tk_eff = 0.428671\tres = 1.956E-02\n", - "[ NORMAL ] Iteration 23:\tk_eff = 0.437437\tres = 2.005E-02\n", - "[ NORMAL ] Iteration 24:\tk_eff = 0.446503\tres = 2.045E-02\n", - "[ NORMAL ] Iteration 25:\tk_eff = 0.455839\tres = 2.073E-02\n", - "[ NORMAL ] Iteration 26:\tk_eff = 0.465413\tres = 2.091E-02\n", - "[ NORMAL ] Iteration 27:\tk_eff = 0.475198\tres = 2.100E-02\n", - "[ NORMAL ] Iteration 28:\tk_eff = 0.485167\tres = 2.103E-02\n", - "[ NORMAL ] Iteration 29:\tk_eff = 0.495295\tres = 2.098E-02\n", - "[ NORMAL ] Iteration 30:\tk_eff = 0.505558\tres = 2.087E-02\n", - "[ NORMAL ] Iteration 31:\tk_eff = 0.515935\tres = 2.072E-02\n", - "[ NORMAL ] Iteration 32:\tk_eff = 0.526405\tres = 2.053E-02\n", - "[ NORMAL ] Iteration 33:\tk_eff = 0.536950\tres = 2.029E-02\n", - "[ NORMAL ] Iteration 34:\tk_eff = 0.547551\tres = 2.003E-02\n", - "[ NORMAL ] Iteration 35:\tk_eff = 0.558191\tres = 1.974E-02\n", - "[ NORMAL ] Iteration 36:\tk_eff = 0.568856\tres = 1.943E-02\n", - "[ NORMAL ] Iteration 37:\tk_eff = 0.579532\tres = 1.911E-02\n", - "[ NORMAL ] Iteration 38:\tk_eff = 0.590203\tres = 1.877E-02\n", - "[ NORMAL ] Iteration 39:\tk_eff = 0.600860\tres = 1.841E-02\n", - "[ NORMAL ] Iteration 40:\tk_eff = 0.611489\tres = 1.806E-02\n", - "[ NORMAL ] Iteration 41:\tk_eff = 0.622080\tres = 1.769E-02\n", - "[ NORMAL ] Iteration 42:\tk_eff = 0.632623\tres = 1.732E-02\n", - "[ NORMAL ] Iteration 43:\tk_eff = 0.643111\tres = 1.695E-02\n", - "[ NORMAL ] Iteration 44:\tk_eff = 0.653533\tres = 1.658E-02\n", - "[ NORMAL ] Iteration 45:\tk_eff = 0.663882\tres = 1.621E-02\n", - "[ NORMAL ] Iteration 46:\tk_eff = 0.674152\tres = 1.584E-02\n", - "[ NORMAL ] Iteration 47:\tk_eff = 0.684335\tres = 1.547E-02\n", - "[ NORMAL ] Iteration 48:\tk_eff = 0.694427\tres = 1.511E-02\n", - "[ NORMAL ] Iteration 49:\tk_eff = 0.704421\tres = 1.475E-02\n", - "[ NORMAL ] Iteration 50:\tk_eff = 0.714313\tres = 1.439E-02\n", - "[ NORMAL ] Iteration 51:\tk_eff = 0.724099\tres = 1.404E-02\n", - "[ NORMAL ] Iteration 52:\tk_eff = 0.733774\tres = 1.370E-02\n", - "[ NORMAL ] Iteration 53:\tk_eff = 0.743335\tres = 1.336E-02\n", - "[ NORMAL ] Iteration 54:\tk_eff = 0.752778\tres = 1.303E-02\n", - "[ NORMAL ] Iteration 55:\tk_eff = 0.762103\tres = 1.270E-02\n", - "[ NORMAL ] Iteration 56:\tk_eff = 0.771304\tres = 1.239E-02\n", - "[ NORMAL ] Iteration 57:\tk_eff = 0.780382\tres = 1.207E-02\n", - "[ NORMAL ] Iteration 58:\tk_eff = 0.789332\tres = 1.177E-02\n", - "[ NORMAL ] Iteration 59:\tk_eff = 0.798155\tres = 1.147E-02\n", - "[ NORMAL ] Iteration 60:\tk_eff = 0.806849\tres = 1.118E-02\n", - "[ NORMAL ] Iteration 61:\tk_eff = 0.815413\tres = 1.089E-02\n", - "[ NORMAL ] Iteration 62:\tk_eff = 0.823846\tres = 1.061E-02\n", - "[ NORMAL ] Iteration 63:\tk_eff = 0.832147\tres = 1.034E-02\n", - "[ NORMAL ] Iteration 64:\tk_eff = 0.840316\tres = 1.008E-02\n", - "[ NORMAL ] Iteration 65:\tk_eff = 0.848354\tres = 9.817E-03\n", - "[ NORMAL ] Iteration 66:\tk_eff = 0.856259\tres = 9.565E-03\n", - "[ NORMAL ] Iteration 67:\tk_eff = 0.864033\tres = 9.318E-03\n", - "[ NORMAL ] Iteration 68:\tk_eff = 0.871674\tres = 9.078E-03\n", - "[ NORMAL ] Iteration 69:\tk_eff = 0.879184\tres = 8.844E-03\n", - "[ NORMAL ] Iteration 70:\tk_eff = 0.886563\tres = 8.616E-03\n", - "[ NORMAL ] Iteration 71:\tk_eff = 0.893812\tres = 8.393E-03\n", - "[ NORMAL ] Iteration 72:\tk_eff = 0.900932\tres = 8.177E-03\n", - "[ NORMAL ] Iteration 73:\tk_eff = 0.907923\tres = 7.965E-03\n", - "[ NORMAL ] Iteration 74:\tk_eff = 0.914786\tres = 7.760E-03\n", - "[ NORMAL ] Iteration 75:\tk_eff = 0.921523\tres = 7.560E-03\n", - "[ NORMAL ] Iteration 76:\tk_eff = 0.928134\tres = 7.365E-03\n", - "[ NORMAL ] Iteration 77:\tk_eff = 0.934621\tres = 7.174E-03\n", - "[ NORMAL ] Iteration 78:\tk_eff = 0.940985\tres = 6.989E-03\n", - "[ NORMAL ] Iteration 79:\tk_eff = 0.947227\tres = 6.809E-03\n", - "[ NORMAL ] Iteration 80:\tk_eff = 0.953349\tres = 6.634E-03\n", - "[ NORMAL ] Iteration 81:\tk_eff = 0.959352\tres = 6.463E-03\n", - "[ NORMAL ] Iteration 82:\tk_eff = 0.965237\tres = 6.297E-03\n", - "[ NORMAL ] Iteration 83:\tk_eff = 0.971006\tres = 6.134E-03\n", - "[ NORMAL ] Iteration 84:\tk_eff = 0.976660\tres = 5.977E-03\n", - "[ NORMAL ] Iteration 85:\tk_eff = 0.982201\tres = 5.823E-03\n", - "[ NORMAL ] Iteration 86:\tk_eff = 0.987631\tres = 5.674E-03\n", - "[ NORMAL ] Iteration 87:\tk_eff = 0.992950\tres = 5.528E-03\n", - "[ NORMAL ] Iteration 88:\tk_eff = 0.998161\tres = 5.386E-03\n", - "[ NORMAL ] Iteration 89:\tk_eff = 1.003266\tres = 5.248E-03\n", - "[ NORMAL ] Iteration 90:\tk_eff = 1.008265\tres = 5.114E-03\n", - "[ NORMAL ] Iteration 91:\tk_eff = 1.013161\tres = 4.983E-03\n", - "[ NORMAL ] Iteration 92:\tk_eff = 1.017954\tres = 4.856E-03\n", - "[ NORMAL ] Iteration 93:\tk_eff = 1.022648\tres = 4.731E-03\n", - "[ NORMAL ] Iteration 94:\tk_eff = 1.027243\tres = 4.611E-03\n", - "[ NORMAL ] Iteration 95:\tk_eff = 1.031741\tres = 4.493E-03\n", - "[ NORMAL ] Iteration 96:\tk_eff = 1.036143\tres = 4.379E-03\n", - "[ NORMAL ] Iteration 97:\tk_eff = 1.040452\tres = 4.267E-03\n", - "[ NORMAL ] Iteration 98:\tk_eff = 1.044667\tres = 4.159E-03\n", - "[ NORMAL ] Iteration 99:\tk_eff = 1.048794\tres = 4.052E-03\n", - "[ NORMAL ] Iteration 100:\tk_eff = 1.052831\tres = 3.950E-03\n", - "[ NORMAL ] Iteration 101:\tk_eff = 1.056781\tres = 3.849E-03\n", - "[ NORMAL ] Iteration 102:\tk_eff = 1.060645\tres = 3.752E-03\n", - "[ NORMAL ] Iteration 103:\tk_eff = 1.064425\tres = 3.656E-03\n", - "[ NORMAL ] Iteration 104:\tk_eff = 1.068122\tres = 3.564E-03\n", - "[ NORMAL ] Iteration 105:\tk_eff = 1.071738\tres = 3.474E-03\n", - "[ NORMAL ] Iteration 106:\tk_eff = 1.075274\tres = 3.386E-03\n", - "[ NORMAL ] Iteration 107:\tk_eff = 1.078734\tres = 3.300E-03\n", - "[ NORMAL ] Iteration 108:\tk_eff = 1.082116\tres = 3.217E-03\n", - "[ NORMAL ] Iteration 109:\tk_eff = 1.085423\tres = 3.136E-03\n", - "[ NORMAL ] Iteration 110:\tk_eff = 1.088657\tres = 3.056E-03\n", - "[ NORMAL ] Iteration 111:\tk_eff = 1.091818\tres = 2.980E-03\n", - "[ NORMAL ] Iteration 112:\tk_eff = 1.094909\tres = 2.904E-03\n", - "[ NORMAL ] Iteration 113:\tk_eff = 1.097930\tres = 2.831E-03\n", - "[ NORMAL ] Iteration 114:\tk_eff = 1.100884\tres = 2.760E-03\n", - "[ NORMAL ] Iteration 115:\tk_eff = 1.103771\tres = 2.690E-03\n", - "[ NORMAL ] Iteration 116:\tk_eff = 1.106593\tres = 2.623E-03\n", - "[ NORMAL ] Iteration 117:\tk_eff = 1.109351\tres = 2.557E-03\n", - "[ NORMAL ] Iteration 118:\tk_eff = 1.112047\tres = 2.492E-03\n", - "[ NORMAL ] Iteration 119:\tk_eff = 1.114682\tres = 2.430E-03\n", - "[ NORMAL ] Iteration 120:\tk_eff = 1.117257\tres = 2.369E-03\n", - "[ NORMAL ] Iteration 121:\tk_eff = 1.119772\tres = 2.310E-03\n", - "[ NORMAL ] Iteration 122:\tk_eff = 1.122231\tres = 2.251E-03\n", - "[ NORMAL ] Iteration 123:\tk_eff = 1.124633\tres = 2.196E-03\n", - "[ NORMAL ] Iteration 124:\tk_eff = 1.126980\tres = 2.140E-03\n", - "[ NORMAL ] Iteration 125:\tk_eff = 1.129273\tres = 2.087E-03\n", - "[ NORMAL ] Iteration 126:\tk_eff = 1.131514\tres = 2.035E-03\n", - "[ NORMAL ] Iteration 127:\tk_eff = 1.133703\tres = 1.984E-03\n", - "[ NORMAL ] Iteration 128:\tk_eff = 1.135840\tres = 1.934E-03\n", - "[ NORMAL ] Iteration 129:\tk_eff = 1.137929\tres = 1.886E-03\n", - "[ NORMAL ] Iteration 130:\tk_eff = 1.139970\tres = 1.839E-03\n", - "[ NORMAL ] Iteration 131:\tk_eff = 1.141963\tres = 1.793E-03\n", - "[ NORMAL ] Iteration 132:\tk_eff = 1.143910\tres = 1.749E-03\n", - "[ NORMAL ] Iteration 133:\tk_eff = 1.145812\tres = 1.705E-03\n", - "[ NORMAL ] Iteration 134:\tk_eff = 1.147670\tres = 1.663E-03\n", - "[ NORMAL ] Iteration 135:\tk_eff = 1.149484\tres = 1.622E-03\n", - "[ NORMAL ] Iteration 136:\tk_eff = 1.151256\tres = 1.581E-03\n", - "[ NORMAL ] Iteration 137:\tk_eff = 1.152986\tres = 1.541E-03\n", - "[ NORMAL ] Iteration 138:\tk_eff = 1.154676\tres = 1.503E-03\n", - "[ NORMAL ] Iteration 139:\tk_eff = 1.156326\tres = 1.466E-03\n", - "[ NORMAL ] Iteration 140:\tk_eff = 1.157938\tres = 1.429E-03\n", - "[ NORMAL ] Iteration 141:\tk_eff = 1.159512\tres = 1.394E-03\n", - "[ NORMAL ] Iteration 142:\tk_eff = 1.161049\tres = 1.359E-03\n", - "[ NORMAL ] Iteration 143:\tk_eff = 1.162549\tres = 1.326E-03\n", - "[ NORMAL ] Iteration 144:\tk_eff = 1.164015\tres = 1.292E-03\n", - "[ NORMAL ] Iteration 145:\tk_eff = 1.165446\tres = 1.261E-03\n", - "[ NORMAL ] Iteration 146:\tk_eff = 1.166843\tres = 1.229E-03\n", - "[ NORMAL ] Iteration 147:\tk_eff = 1.168208\tres = 1.199E-03\n", - "[ NORMAL ] Iteration 148:\tk_eff = 1.169540\tres = 1.169E-03\n", - "[ NORMAL ] Iteration 149:\tk_eff = 1.170840\tres = 1.140E-03\n", - "[ NORMAL ] Iteration 150:\tk_eff = 1.172110\tres = 1.112E-03\n", - "[ NORMAL ] Iteration 151:\tk_eff = 1.173350\tres = 1.085E-03\n", - "[ NORMAL ] Iteration 152:\tk_eff = 1.174560\tres = 1.058E-03\n", - "[ NORMAL ] Iteration 153:\tk_eff = 1.175742\tres = 1.031E-03\n", - "[ NORMAL ] Iteration 154:\tk_eff = 1.176895\tres = 1.006E-03\n", - "[ NORMAL ] Iteration 155:\tk_eff = 1.178021\tres = 9.812E-04\n", - "[ NORMAL ] Iteration 156:\tk_eff = 1.179121\tres = 9.567E-04\n", - "[ NORMAL ] Iteration 157:\tk_eff = 1.180194\tres = 9.336E-04\n", - "[ NORMAL ] Iteration 158:\tk_eff = 1.181242\tres = 9.100E-04\n", - "[ NORMAL ] Iteration 159:\tk_eff = 1.182264\tres = 8.875E-04\n", - "[ NORMAL ] Iteration 160:\tk_eff = 1.183264\tres = 8.656E-04\n", - "[ NORMAL ] Iteration 161:\tk_eff = 1.184238\tres = 8.452E-04\n", - "[ NORMAL ] Iteration 162:\tk_eff = 1.185189\tres = 8.233E-04\n", - "[ NORMAL ] Iteration 163:\tk_eff = 1.186118\tres = 8.029E-04\n", - "[ NORMAL ] Iteration 164:\tk_eff = 1.187024\tres = 7.839E-04\n", - "[ NORMAL ] Iteration 165:\tk_eff = 1.187908\tres = 7.641E-04\n", - "[ NORMAL ] Iteration 166:\tk_eff = 1.188772\tres = 7.449E-04\n", - "[ NORMAL ] Iteration 167:\tk_eff = 1.189615\tres = 7.271E-04\n", - "[ NORMAL ] Iteration 168:\tk_eff = 1.190438\tres = 7.093E-04\n", - "[ NORMAL ] Iteration 169:\tk_eff = 1.191241\tres = 6.915E-04\n", - "[ NORMAL ] Iteration 170:\tk_eff = 1.192024\tres = 6.742E-04\n", - "[ NORMAL ] Iteration 171:\tk_eff = 1.192789\tres = 6.576E-04\n", - "[ NORMAL ] Iteration 172:\tk_eff = 1.193536\tres = 6.419E-04\n", - "[ NORMAL ] Iteration 173:\tk_eff = 1.194265\tres = 6.263E-04\n", - "[ NORMAL ] Iteration 174:\tk_eff = 1.194976\tres = 6.104E-04\n", - "[ NORMAL ] Iteration 175:\tk_eff = 1.195670\tres = 5.955E-04\n", - "[ NORMAL ] Iteration 176:\tk_eff = 1.196347\tres = 5.806E-04\n", - "[ NORMAL ] Iteration 177:\tk_eff = 1.197008\tres = 5.665E-04\n", - "[ NORMAL ] Iteration 178:\tk_eff = 1.197653\tres = 5.525E-04\n", - "[ NORMAL ] Iteration 179:\tk_eff = 1.198284\tres = 5.388E-04\n", - "[ NORMAL ] Iteration 180:\tk_eff = 1.198898\tres = 5.263E-04\n", - "[ NORMAL ] Iteration 181:\tk_eff = 1.199498\tres = 5.126E-04\n", - "[ NORMAL ] Iteration 182:\tk_eff = 1.200083\tres = 5.000E-04\n", - "[ NORMAL ] Iteration 183:\tk_eff = 1.200654\tres = 4.880E-04\n", - "[ NORMAL ] Iteration 184:\tk_eff = 1.201212\tres = 4.757E-04\n", - "[ NORMAL ] Iteration 185:\tk_eff = 1.201756\tres = 4.647E-04\n", - "[ NORMAL ] Iteration 186:\tk_eff = 1.202286\tres = 4.528E-04\n", - "[ NORMAL ] Iteration 187:\tk_eff = 1.202804\tres = 4.414E-04\n", - "[ NORMAL ] Iteration 188:\tk_eff = 1.203310\tres = 4.310E-04\n", - "[ NORMAL ] Iteration 189:\tk_eff = 1.203803\tres = 4.203E-04\n", - "[ NORMAL ] Iteration 190:\tk_eff = 1.204285\tres = 4.100E-04\n", - "[ NORMAL ] Iteration 191:\tk_eff = 1.204755\tres = 4.003E-04\n", - "[ NORMAL ] Iteration 192:\tk_eff = 1.205214\tres = 3.902E-04\n", - "[ NORMAL ] Iteration 193:\tk_eff = 1.205661\tres = 3.810E-04\n", - "[ NORMAL ] Iteration 194:\tk_eff = 1.206097\tres = 3.711E-04\n", - "[ NORMAL ] Iteration 195:\tk_eff = 1.206523\tres = 3.617E-04\n", - "[ NORMAL ] Iteration 196:\tk_eff = 1.206939\tres = 3.528E-04\n", - "[ NORMAL ] Iteration 197:\tk_eff = 1.207345\tres = 3.447E-04\n", - "[ NORMAL ] Iteration 198:\tk_eff = 1.207740\tres = 3.363E-04\n", - "[ NORMAL ] Iteration 199:\tk_eff = 1.208127\tres = 3.281E-04\n", - "[ NORMAL ] Iteration 200:\tk_eff = 1.208503\tres = 3.198E-04\n", - "[ NORMAL ] Iteration 201:\tk_eff = 1.208871\tres = 3.119E-04\n", - "[ NORMAL ] Iteration 202:\tk_eff = 1.209230\tres = 3.041E-04\n", - "[ NORMAL ] Iteration 203:\tk_eff = 1.209580\tres = 2.968E-04\n", - "[ NORMAL ] Iteration 204:\tk_eff = 1.209921\tres = 2.897E-04\n", - "[ NORMAL ] Iteration 205:\tk_eff = 1.210255\tres = 2.824E-04\n", - "[ NORMAL ] Iteration 206:\tk_eff = 1.210580\tres = 2.759E-04\n", - "[ NORMAL ] Iteration 207:\tk_eff = 1.210898\tres = 2.688E-04\n", - "[ NORMAL ] Iteration 208:\tk_eff = 1.211208\tres = 2.622E-04\n", - "[ NORMAL ] Iteration 209:\tk_eff = 1.211510\tres = 2.563E-04\n", - "[ NORMAL ] Iteration 210:\tk_eff = 1.211804\tres = 2.495E-04\n", - "[ NORMAL ] Iteration 211:\tk_eff = 1.212092\tres = 2.428E-04\n", - "[ NORMAL ] Iteration 212:\tk_eff = 1.212373\tres = 2.374E-04\n", - "[ NORMAL ] Iteration 213:\tk_eff = 1.212647\tres = 2.318E-04\n", - "[ NORMAL ] Iteration 214:\tk_eff = 1.212914\tres = 2.258E-04\n", - "[ NORMAL ] Iteration 215:\tk_eff = 1.213175\tres = 2.200E-04\n", - "[ NORMAL ] Iteration 216:\tk_eff = 1.213429\tres = 2.153E-04\n", - "[ NORMAL ] Iteration 217:\tk_eff = 1.213678\tres = 2.097E-04\n", - "[ NORMAL ] Iteration 218:\tk_eff = 1.213920\tres = 2.049E-04\n", - "[ NORMAL ] Iteration 219:\tk_eff = 1.214156\tres = 1.996E-04\n", - "[ NORMAL ] Iteration 220:\tk_eff = 1.214387\tres = 1.945E-04\n", - "[ NORMAL ] Iteration 221:\tk_eff = 1.214612\tres = 1.904E-04\n", - "[ NORMAL ] Iteration 222:\tk_eff = 1.214832\tres = 1.853E-04\n", - "[ NORMAL ] Iteration 223:\tk_eff = 1.215046\tres = 1.806E-04\n", - "[ NORMAL ] Iteration 224:\tk_eff = 1.215255\tres = 1.762E-04\n", - "[ NORMAL ] Iteration 225:\tk_eff = 1.215459\tres = 1.722E-04\n", - "[ NORMAL ] Iteration 226:\tk_eff = 1.215658\tres = 1.676E-04\n", - "[ NORMAL ] Iteration 227:\tk_eff = 1.215852\tres = 1.637E-04\n", - "[ NORMAL ] Iteration 228:\tk_eff = 1.216041\tres = 1.597E-04\n", - "[ NORMAL ] Iteration 229:\tk_eff = 1.216226\tres = 1.557E-04\n", - "[ NORMAL ] Iteration 230:\tk_eff = 1.216407\tres = 1.520E-04\n", - "[ NORMAL ] Iteration 231:\tk_eff = 1.216582\tres = 1.483E-04\n", - "[ NORMAL ] Iteration 232:\tk_eff = 1.216754\tres = 1.444E-04\n", - "[ NORMAL ] Iteration 233:\tk_eff = 1.216921\tres = 1.409E-04\n", - "[ NORMAL ] Iteration 234:\tk_eff = 1.217084\tres = 1.376E-04\n", - "[ NORMAL ] Iteration 235:\tk_eff = 1.217244\tres = 1.344E-04\n", - "[ NORMAL ] Iteration 236:\tk_eff = 1.217399\tres = 1.309E-04\n", - "[ NORMAL ] Iteration 237:\tk_eff = 1.217551\tres = 1.274E-04\n", - "[ NORMAL ] Iteration 238:\tk_eff = 1.217699\tres = 1.247E-04\n", - "[ NORMAL ] Iteration 239:\tk_eff = 1.217843\tres = 1.216E-04\n", - "[ NORMAL ] Iteration 240:\tk_eff = 1.217983\tres = 1.184E-04\n", - "[ NORMAL ] Iteration 241:\tk_eff = 1.218121\tres = 1.154E-04\n", - "[ NORMAL ] Iteration 242:\tk_eff = 1.218255\tres = 1.131E-04\n", - "[ NORMAL ] Iteration 243:\tk_eff = 1.218386\tres = 1.101E-04\n", - "[ NORMAL ] Iteration 244:\tk_eff = 1.218514\tres = 1.075E-04\n", - "[ NORMAL ] Iteration 245:\tk_eff = 1.218639\tres = 1.048E-04\n", - "[ NORMAL ] Iteration 246:\tk_eff = 1.218760\tres = 1.024E-04\n", - "[ NORMAL ] Iteration 247:\tk_eff = 1.218879\tres = 9.989E-05\n", - "[ NORMAL ] Iteration 248:\tk_eff = 1.218994\tres = 9.739E-05\n", - "[ NORMAL ] Iteration 249:\tk_eff = 1.219107\tres = 9.487E-05\n", - "[ NORMAL ] Iteration 250:\tk_eff = 1.219217\tres = 9.257E-05\n", - "[ NORMAL ] Iteration 251:\tk_eff = 1.219324\tres = 9.035E-05\n", - "[ NORMAL ] Iteration 252:\tk_eff = 1.219429\tres = 8.803E-05\n", - "[ NORMAL ] Iteration 253:\tk_eff = 1.219531\tres = 8.599E-05\n", - "[ NORMAL ] Iteration 254:\tk_eff = 1.219631\tres = 8.364E-05\n", - "[ NORMAL ] Iteration 255:\tk_eff = 1.219728\tres = 8.175E-05\n", - "[ NORMAL ] Iteration 256:\tk_eff = 1.219823\tres = 7.971E-05\n", - "[ NORMAL ] Iteration 257:\tk_eff = 1.219916\tres = 7.776E-05\n", - "[ NORMAL ] Iteration 258:\tk_eff = 1.220006\tres = 7.582E-05\n", - "[ NORMAL ] Iteration 259:\tk_eff = 1.220094\tres = 7.438E-05\n", - "[ NORMAL ] Iteration 260:\tk_eff = 1.220181\tres = 7.227E-05\n", - "[ NORMAL ] Iteration 261:\tk_eff = 1.220264\tres = 7.058E-05\n", - "[ NORMAL ] Iteration 262:\tk_eff = 1.220346\tres = 6.872E-05\n", - "[ NORMAL ] Iteration 263:\tk_eff = 1.220426\tres = 6.695E-05\n", - "[ NORMAL ] Iteration 264:\tk_eff = 1.220504\tres = 6.549E-05\n", - "[ NORMAL ] Iteration 265:\tk_eff = 1.220580\tres = 6.375E-05\n", - "[ NORMAL ] Iteration 266:\tk_eff = 1.220654\tres = 6.240E-05\n", - "[ NORMAL ] Iteration 267:\tk_eff = 1.220726\tres = 6.073E-05\n", - "[ NORMAL ] Iteration 268:\tk_eff = 1.220797\tres = 5.942E-05\n", - "[ NORMAL ] Iteration 269:\tk_eff = 1.220866\tres = 5.792E-05\n", - "[ NORMAL ] Iteration 270:\tk_eff = 1.220933\tres = 5.661E-05\n", - "[ NORMAL ] Iteration 271:\tk_eff = 1.220999\tres = 5.501E-05\n", - "[ NORMAL ] Iteration 272:\tk_eff = 1.221063\tres = 5.368E-05\n", - "[ NORMAL ] Iteration 273:\tk_eff = 1.221125\tres = 5.286E-05\n", - "[ NORMAL ] Iteration 274:\tk_eff = 1.221187\tres = 5.103E-05\n", - "[ NORMAL ] Iteration 275:\tk_eff = 1.221246\tres = 5.013E-05\n", - "[ NORMAL ] Iteration 276:\tk_eff = 1.221304\tres = 4.861E-05\n", - "[ NORMAL ] Iteration 277:\tk_eff = 1.221360\tres = 4.737E-05\n", - "[ NORMAL ] Iteration 278:\tk_eff = 1.221416\tres = 4.629E-05\n", - "[ NORMAL ] Iteration 279:\tk_eff = 1.221470\tres = 4.515E-05\n", - "[ NORMAL ] Iteration 280:\tk_eff = 1.221522\tres = 4.417E-05\n", - "[ NORMAL ] Iteration 281:\tk_eff = 1.221574\tres = 4.309E-05\n", - "[ NORMAL ] Iteration 282:\tk_eff = 1.221624\tres = 4.219E-05\n", - "[ NORMAL ] Iteration 283:\tk_eff = 1.221673\tres = 4.099E-05\n", - "[ NORMAL ] Iteration 284:\tk_eff = 1.221721\tres = 3.998E-05\n", - "[ NORMAL ] Iteration 285:\tk_eff = 1.221767\tres = 3.891E-05\n", - "[ NORMAL ] Iteration 286:\tk_eff = 1.221812\tres = 3.781E-05\n", - "[ NORMAL ] Iteration 287:\tk_eff = 1.221857\tres = 3.698E-05\n", - "[ NORMAL ] Iteration 288:\tk_eff = 1.221900\tres = 3.633E-05\n", - "[ NORMAL ] Iteration 289:\tk_eff = 1.221942\tres = 3.535E-05\n", - "[ NORMAL ] Iteration 290:\tk_eff = 1.221983\tres = 3.445E-05\n", - "[ NORMAL ] Iteration 291:\tk_eff = 1.222023\tres = 3.355E-05\n", - "[ NORMAL ] Iteration 292:\tk_eff = 1.222062\tres = 3.281E-05\n", - "[ NORMAL ] Iteration 293:\tk_eff = 1.222100\tres = 3.203E-05\n", - "[ NORMAL ] Iteration 294:\tk_eff = 1.222138\tres = 3.108E-05\n", - "[ NORMAL ] Iteration 295:\tk_eff = 1.222174\tres = 3.049E-05\n", - "[ NORMAL ] Iteration 296:\tk_eff = 1.222209\tres = 2.969E-05\n", - "[ NORMAL ] Iteration 297:\tk_eff = 1.222244\tres = 2.902E-05\n", - "[ NORMAL ] Iteration 298:\tk_eff = 1.222278\tres = 2.820E-05\n", - "[ NORMAL ] Iteration 299:\tk_eff = 1.222310\tres = 2.747E-05\n", - "[ NORMAL ] Iteration 300:\tk_eff = 1.222342\tres = 2.679E-05\n", - "[ NORMAL ] Iteration 301:\tk_eff = 1.222374\tres = 2.626E-05\n", - "[ NORMAL ] Iteration 302:\tk_eff = 1.222404\tres = 2.554E-05\n", - "[ NORMAL ] Iteration 303:\tk_eff = 1.222434\tres = 2.483E-05\n", - "[ NORMAL ] Iteration 304:\tk_eff = 1.222463\tres = 2.432E-05\n", - "[ NORMAL ] Iteration 305:\tk_eff = 1.222492\tres = 2.387E-05\n", - "[ NORMAL ] Iteration 306:\tk_eff = 1.222519\tres = 2.316E-05\n", - "[ NORMAL ] Iteration 307:\tk_eff = 1.222546\tres = 2.274E-05\n", - "[ NORMAL ] Iteration 308:\tk_eff = 1.222573\tres = 2.220E-05\n", - "[ NORMAL ] Iteration 309:\tk_eff = 1.222599\tres = 2.168E-05\n", - "[ NORMAL ] Iteration 310:\tk_eff = 1.222624\tres = 2.107E-05\n", - "[ NORMAL ] Iteration 311:\tk_eff = 1.222648\tres = 2.062E-05\n", - "[ NORMAL ] Iteration 312:\tk_eff = 1.222672\tres = 2.005E-05\n", - "[ NORMAL ] Iteration 313:\tk_eff = 1.222696\tres = 1.959E-05\n", - "[ NORMAL ] Iteration 314:\tk_eff = 1.222718\tres = 1.898E-05\n", - "[ NORMAL ] Iteration 315:\tk_eff = 1.222740\tres = 1.841E-05\n", - "[ NORMAL ] Iteration 316:\tk_eff = 1.222762\tres = 1.808E-05\n", - "[ NORMAL ] Iteration 317:\tk_eff = 1.222783\tres = 1.765E-05\n", - "[ NORMAL ] Iteration 318:\tk_eff = 1.222803\tres = 1.713E-05\n", - "[ NORMAL ] Iteration 319:\tk_eff = 1.222823\tres = 1.674E-05\n", - "[ NORMAL ] Iteration 320:\tk_eff = 1.222843\tres = 1.638E-05\n", - "[ NORMAL ] Iteration 321:\tk_eff = 1.222862\tres = 1.593E-05\n", - "[ NORMAL ] Iteration 322:\tk_eff = 1.222880\tres = 1.546E-05\n", - "[ NORMAL ] Iteration 323:\tk_eff = 1.222898\tres = 1.519E-05\n", - "[ NORMAL ] Iteration 324:\tk_eff = 1.222916\tres = 1.487E-05\n", - "[ NORMAL ] Iteration 325:\tk_eff = 1.222933\tres = 1.446E-05\n", - "[ NORMAL ] Iteration 326:\tk_eff = 1.222950\tres = 1.403E-05\n", - "[ NORMAL ] Iteration 327:\tk_eff = 1.222967\tres = 1.379E-05\n", - "[ NORMAL ] Iteration 328:\tk_eff = 1.222982\tres = 1.342E-05\n", - "[ NORMAL ] Iteration 329:\tk_eff = 1.222998\tres = 1.298E-05\n", - "[ NORMAL ] Iteration 330:\tk_eff = 1.223013\tres = 1.278E-05\n", - "[ NORMAL ] Iteration 331:\tk_eff = 1.223028\tres = 1.236E-05\n", - "[ NORMAL ] Iteration 332:\tk_eff = 1.223043\tres = 1.227E-05\n", - "[ NORMAL ] Iteration 333:\tk_eff = 1.223057\tres = 1.177E-05\n", - "[ NORMAL ] Iteration 334:\tk_eff = 1.223070\tres = 1.146E-05\n", - "[ NORMAL ] Iteration 335:\tk_eff = 1.223084\tres = 1.122E-05\n", - "[ NORMAL ] Iteration 336:\tk_eff = 1.223097\tres = 1.105E-05\n", - "[ NORMAL ] Iteration 337:\tk_eff = 1.223110\tres = 1.063E-05\n", - "[ NORMAL ] Iteration 338:\tk_eff = 1.223122\tres = 1.054E-05\n", - "[ NORMAL ] Iteration 339:\tk_eff = 1.223134\tres = 1.015E-05\n", - "[ NORMAL ] Iteration 340:\tk_eff = 1.223146\tres = 1.005E-05\n" + "[ NORMAL ] Iteration 0:\tk_eff = 0.366885\tres = 0.000E+00\n", + "[ NORMAL ] Iteration 1:\tk_eff = 0.391184\tres = 6.331E-01\n", + "[ NORMAL ] Iteration 2:\tk_eff = 0.392990\tres = 6.623E-02\n", + "[ NORMAL ] Iteration 3:\tk_eff = 0.381099\tres = 4.617E-03\n", + "[ NORMAL ] Iteration 4:\tk_eff = 0.375018\tres = 3.026E-02\n", + "[ NORMAL ] Iteration 5:\tk_eff = 0.369593\tres = 1.596E-02\n", + "[ NORMAL ] Iteration 6:\tk_eff = 0.365543\tres = 1.446E-02\n", + "[ NORMAL ] Iteration 7:\tk_eff = 0.363055\tres = 1.096E-02\n", + "[ NORMAL ] Iteration 8:\tk_eff = 0.361474\tres = 6.809E-03\n", + "[ NORMAL ] Iteration 9:\tk_eff = 0.361280\tres = 4.354E-03\n", + "[ NORMAL ] Iteration 10:\tk_eff = 0.362004\tres = 5.348E-04\n", + "[ NORMAL ] Iteration 11:\tk_eff = 0.363719\tres = 2.003E-03\n", + "[ NORMAL ] Iteration 12:\tk_eff = 0.366339\tres = 4.737E-03\n", + "[ NORMAL ] Iteration 13:\tk_eff = 0.369805\tres = 7.203E-03\n", + "[ NORMAL ] Iteration 14:\tk_eff = 0.373990\tres = 9.461E-03\n", + "[ NORMAL ] Iteration 15:\tk_eff = 0.378924\tres = 1.132E-02\n", + "[ NORMAL ] Iteration 16:\tk_eff = 0.384480\tres = 1.319E-02\n", + "[ NORMAL ] Iteration 17:\tk_eff = 0.390638\tres = 1.466E-02\n", + "[ NORMAL ] Iteration 18:\tk_eff = 0.397339\tres = 1.602E-02\n", + "[ NORMAL ] Iteration 19:\tk_eff = 0.404534\tres = 1.715E-02\n", + "[ NORMAL ] Iteration 20:\tk_eff = 0.412185\tres = 1.811E-02\n", + "[ NORMAL ] Iteration 21:\tk_eff = 0.420255\tres = 1.891E-02\n", + "[ NORMAL ] Iteration 22:\tk_eff = 0.428688\tres = 1.958E-02\n", + "[ NORMAL ] Iteration 23:\tk_eff = 0.437464\tres = 2.007E-02\n", + "[ NORMAL ] Iteration 24:\tk_eff = 0.446540\tres = 2.047E-02\n", + "[ NORMAL ] Iteration 25:\tk_eff = 0.455885\tres = 2.075E-02\n", + "[ NORMAL ] Iteration 26:\tk_eff = 0.465470\tres = 2.093E-02\n", + "[ NORMAL ] Iteration 27:\tk_eff = 0.475267\tres = 2.103E-02\n", + "[ NORMAL ] Iteration 28:\tk_eff = 0.485247\tres = 2.105E-02\n", + "[ NORMAL ] Iteration 29:\tk_eff = 0.495387\tres = 2.100E-02\n", + "[ NORMAL ] Iteration 30:\tk_eff = 0.505663\tres = 2.090E-02\n", + "[ NORMAL ] Iteration 31:\tk_eff = 0.516053\tres = 2.074E-02\n", + "[ NORMAL ] Iteration 32:\tk_eff = 0.526536\tres = 2.055E-02\n", + "[ NORMAL ] Iteration 33:\tk_eff = 0.537094\tres = 2.031E-02\n", + "[ NORMAL ] Iteration 34:\tk_eff = 0.547708\tres = 2.005E-02\n", + "[ NORMAL ] Iteration 35:\tk_eff = 0.558363\tres = 1.976E-02\n", + "[ NORMAL ] Iteration 36:\tk_eff = 0.569042\tres = 1.945E-02\n", + "[ NORMAL ] Iteration 37:\tk_eff = 0.579732\tres = 1.913E-02\n", + "[ NORMAL ] Iteration 38:\tk_eff = 0.590419\tres = 1.879E-02\n", + "[ NORMAL ] Iteration 39:\tk_eff = 0.601090\tres = 1.843E-02\n", + "[ NORMAL ] Iteration 40:\tk_eff = 0.611734\tres = 1.807E-02\n", + "[ NORMAL ] Iteration 41:\tk_eff = 0.622340\tres = 1.771E-02\n", + "[ NORMAL ] Iteration 42:\tk_eff = 0.632899\tres = 1.734E-02\n", + "[ NORMAL ] Iteration 43:\tk_eff = 0.643402\tres = 1.697E-02\n", + "[ NORMAL ] Iteration 44:\tk_eff = 0.653840\tres = 1.659E-02\n", + "[ NORMAL ] Iteration 45:\tk_eff = 0.664206\tres = 1.622E-02\n", + "[ NORMAL ] Iteration 46:\tk_eff = 0.674492\tres = 1.585E-02\n", + "[ NORMAL ] Iteration 47:\tk_eff = 0.684691\tres = 1.549E-02\n", + "[ NORMAL ] Iteration 48:\tk_eff = 0.694799\tres = 1.512E-02\n", + "[ NORMAL ] Iteration 49:\tk_eff = 0.704810\tres = 1.476E-02\n", + "[ NORMAL ] Iteration 50:\tk_eff = 0.714718\tres = 1.441E-02\n", + "[ NORMAL ] Iteration 51:\tk_eff = 0.724520\tres = 1.406E-02\n", + "[ NORMAL ] Iteration 52:\tk_eff = 0.734212\tres = 1.371E-02\n", + "[ NORMAL ] Iteration 53:\tk_eff = 0.743790\tres = 1.338E-02\n", + "[ NORMAL ] Iteration 54:\tk_eff = 0.753250\tres = 1.304E-02\n", + "[ NORMAL ] Iteration 55:\tk_eff = 0.762592\tres = 1.272E-02\n", + "[ NORMAL ] Iteration 56:\tk_eff = 0.771809\tres = 1.240E-02\n", + "[ NORMAL ] Iteration 57:\tk_eff = 0.780904\tres = 1.209E-02\n", + "[ NORMAL ] Iteration 58:\tk_eff = 0.789871\tres = 1.178E-02\n", + "[ NORMAL ] Iteration 59:\tk_eff = 0.798711\tres = 1.148E-02\n", + "[ NORMAL ] Iteration 60:\tk_eff = 0.807422\tres = 1.119E-02\n", + "[ NORMAL ] Iteration 61:\tk_eff = 0.816003\tres = 1.091E-02\n", + "[ NORMAL ] Iteration 62:\tk_eff = 0.824453\tres = 1.063E-02\n", + "[ NORMAL ] Iteration 63:\tk_eff = 0.832771\tres = 1.035E-02\n", + "[ NORMAL ] Iteration 64:\tk_eff = 0.840957\tres = 1.009E-02\n", + "[ NORMAL ] Iteration 65:\tk_eff = 0.849011\tres = 9.830E-03\n", + "[ NORMAL ] Iteration 66:\tk_eff = 0.856933\tres = 9.577E-03\n", + "[ NORMAL ] Iteration 67:\tk_eff = 0.864723\tres = 9.331E-03\n", + "[ NORMAL ] Iteration 68:\tk_eff = 0.872381\tres = 9.090E-03\n", + "[ NORMAL ] Iteration 69:\tk_eff = 0.879908\tres = 8.856E-03\n", + "[ NORMAL ] Iteration 70:\tk_eff = 0.887304\tres = 8.628E-03\n", + "[ NORMAL ] Iteration 71:\tk_eff = 0.894569\tres = 8.405E-03\n", + "[ NORMAL ] Iteration 72:\tk_eff = 0.901706\tres = 8.188E-03\n", + "[ NORMAL ] Iteration 73:\tk_eff = 0.908713\tres = 7.978E-03\n", + "[ NORMAL ] Iteration 74:\tk_eff = 0.915593\tres = 7.771E-03\n", + "[ NORMAL ] Iteration 75:\tk_eff = 0.922346\tres = 7.571E-03\n", + "[ NORMAL ] Iteration 76:\tk_eff = 0.928973\tres = 7.376E-03\n", + "[ NORMAL ] Iteration 77:\tk_eff = 0.935476\tres = 7.186E-03\n", + "[ NORMAL ] Iteration 78:\tk_eff = 0.941856\tres = 7.000E-03\n", + "[ NORMAL ] Iteration 79:\tk_eff = 0.948114\tres = 6.820E-03\n", + "[ NORMAL ] Iteration 80:\tk_eff = 0.954252\tres = 6.645E-03\n", + "[ NORMAL ] Iteration 81:\tk_eff = 0.960270\tres = 6.473E-03\n", + "[ NORMAL ] Iteration 82:\tk_eff = 0.966171\tres = 6.307E-03\n", + "[ NORMAL ] Iteration 83:\tk_eff = 0.971955\tres = 6.145E-03\n", + "[ NORMAL ] Iteration 84:\tk_eff = 0.977625\tres = 5.987E-03\n", + "[ NORMAL ] Iteration 85:\tk_eff = 0.983181\tres = 5.833E-03\n", + "[ NORMAL ] Iteration 86:\tk_eff = 0.988626\tres = 5.684E-03\n", + "[ NORMAL ] Iteration 87:\tk_eff = 0.993961\tres = 5.538E-03\n", + "[ NORMAL ] Iteration 88:\tk_eff = 0.999186\tres = 5.396E-03\n", + "[ NORMAL ] Iteration 89:\tk_eff = 1.004306\tres = 5.258E-03\n", + "[ NORMAL ] Iteration 90:\tk_eff = 1.009320\tres = 5.124E-03\n", + "[ NORMAL ] Iteration 91:\tk_eff = 1.014230\tres = 4.993E-03\n", + "[ NORMAL ] Iteration 92:\tk_eff = 1.019038\tres = 4.865E-03\n", + "[ NORMAL ] Iteration 93:\tk_eff = 1.023746\tres = 4.740E-03\n", + "[ NORMAL ] Iteration 94:\tk_eff = 1.028355\tres = 4.621E-03\n", + "[ NORMAL ] Iteration 95:\tk_eff = 1.032868\tres = 4.502E-03\n", + "[ NORMAL ] Iteration 96:\tk_eff = 1.037284\tres = 4.388E-03\n", + "[ NORMAL ] Iteration 97:\tk_eff = 1.041606\tres = 4.275E-03\n", + "[ NORMAL ] Iteration 98:\tk_eff = 1.045837\tres = 4.167E-03\n", + "[ NORMAL ] Iteration 99:\tk_eff = 1.049976\tres = 4.062E-03\n", + "[ NORMAL ] Iteration 100:\tk_eff = 1.054027\tres = 3.958E-03\n", + "[ NORMAL ] Iteration 101:\tk_eff = 1.057990\tres = 3.858E-03\n", + "[ NORMAL ] Iteration 102:\tk_eff = 1.061867\tres = 3.760E-03\n", + "[ NORMAL ] Iteration 103:\tk_eff = 1.065660\tres = 3.665E-03\n", + "[ NORMAL ] Iteration 104:\tk_eff = 1.069370\tres = 3.572E-03\n", + "[ NORMAL ] Iteration 105:\tk_eff = 1.072999\tres = 3.482E-03\n", + "[ NORMAL ] Iteration 106:\tk_eff = 1.076548\tres = 3.394E-03\n", + "[ NORMAL ] Iteration 107:\tk_eff = 1.080020\tres = 3.307E-03\n", + "[ NORMAL ] Iteration 108:\tk_eff = 1.083415\tres = 3.225E-03\n", + "[ NORMAL ] Iteration 109:\tk_eff = 1.086734\tres = 3.143E-03\n", + "[ NORMAL ] Iteration 110:\tk_eff = 1.089979\tres = 3.064E-03\n", + "[ NORMAL ] Iteration 111:\tk_eff = 1.093153\tres = 2.986E-03\n", + "[ NORMAL ] Iteration 112:\tk_eff = 1.096256\tres = 2.912E-03\n", + "[ NORMAL ] Iteration 113:\tk_eff = 1.099289\tres = 2.838E-03\n", + "[ NORMAL ] Iteration 114:\tk_eff = 1.102254\tres = 2.767E-03\n", + "[ NORMAL ] Iteration 115:\tk_eff = 1.105153\tres = 2.697E-03\n", + "[ NORMAL ] Iteration 116:\tk_eff = 1.107986\tres = 2.630E-03\n", + "[ NORMAL ] Iteration 117:\tk_eff = 1.110755\tres = 2.564E-03\n", + "[ NORMAL ] Iteration 118:\tk_eff = 1.113462\tres = 2.500E-03\n", + "[ NORMAL ] Iteration 119:\tk_eff = 1.116107\tres = 2.437E-03\n", + "[ NORMAL ] Iteration 120:\tk_eff = 1.118693\tres = 2.376E-03\n", + "[ NORMAL ] Iteration 121:\tk_eff = 1.121219\tres = 2.316E-03\n", + "[ NORMAL ] Iteration 122:\tk_eff = 1.123687\tres = 2.258E-03\n", + "[ NORMAL ] Iteration 123:\tk_eff = 1.126100\tres = 2.202E-03\n", + "[ NORMAL ] Iteration 124:\tk_eff = 1.128457\tres = 2.148E-03\n", + "[ NORMAL ] Iteration 125:\tk_eff = 1.130760\tres = 2.093E-03\n", + "[ NORMAL ] Iteration 126:\tk_eff = 1.133010\tres = 2.041E-03\n", + "[ NORMAL ] Iteration 127:\tk_eff = 1.135208\tres = 1.990E-03\n", + "[ NORMAL ] Iteration 128:\tk_eff = 1.137357\tres = 1.941E-03\n", + "[ NORMAL ] Iteration 129:\tk_eff = 1.139455\tres = 1.892E-03\n", + "[ NORMAL ] Iteration 130:\tk_eff = 1.141505\tres = 1.845E-03\n", + "[ NORMAL ] Iteration 131:\tk_eff = 1.143507\tres = 1.799E-03\n", + "[ NORMAL ] Iteration 132:\tk_eff = 1.145464\tres = 1.754E-03\n", + "[ NORMAL ] Iteration 133:\tk_eff = 1.147374\tres = 1.711E-03\n", + "[ NORMAL ] Iteration 134:\tk_eff = 1.149240\tres = 1.668E-03\n", + "[ NORMAL ] Iteration 135:\tk_eff = 1.151063\tres = 1.626E-03\n", + "[ NORMAL ] Iteration 136:\tk_eff = 1.152844\tres = 1.586E-03\n", + "[ NORMAL ] Iteration 137:\tk_eff = 1.154583\tres = 1.547E-03\n", + "[ NORMAL ] Iteration 138:\tk_eff = 1.156281\tres = 1.508E-03\n", + "[ NORMAL ] Iteration 139:\tk_eff = 1.157940\tres = 1.471E-03\n", + "[ NORMAL ] Iteration 140:\tk_eff = 1.159560\tres = 1.435E-03\n", + "[ NORMAL ] Iteration 141:\tk_eff = 1.161142\tres = 1.399E-03\n", + "[ NORMAL ] Iteration 142:\tk_eff = 1.162687\tres = 1.364E-03\n", + "[ NORMAL ] Iteration 143:\tk_eff = 1.164195\tres = 1.330E-03\n", + "[ NORMAL ] Iteration 144:\tk_eff = 1.165669\tres = 1.297E-03\n", + "[ NORMAL ] Iteration 145:\tk_eff = 1.167107\tres = 1.266E-03\n", + "[ NORMAL ] Iteration 146:\tk_eff = 1.168512\tres = 1.234E-03\n", + "[ NORMAL ] Iteration 147:\tk_eff = 1.169883\tres = 1.203E-03\n", + "[ NORMAL ] Iteration 148:\tk_eff = 1.171222\tres = 1.174E-03\n", + "[ NORMAL ] Iteration 149:\tk_eff = 1.172530\tres = 1.144E-03\n", + "[ NORMAL ] Iteration 150:\tk_eff = 1.173807\tres = 1.117E-03\n", + "[ NORMAL ] Iteration 151:\tk_eff = 1.175054\tres = 1.089E-03\n", + "[ NORMAL ] Iteration 152:\tk_eff = 1.176271\tres = 1.062E-03\n", + "[ NORMAL ] Iteration 153:\tk_eff = 1.177459\tres = 1.036E-03\n", + "[ NORMAL ] Iteration 154:\tk_eff = 1.178619\tres = 1.010E-03\n", + "[ NORMAL ] Iteration 155:\tk_eff = 1.179752\tres = 9.854E-04\n", + "[ NORMAL ] Iteration 156:\tk_eff = 1.180858\tres = 9.608E-04\n", + "[ NORMAL ] Iteration 157:\tk_eff = 1.181938\tres = 9.371E-04\n", + "[ NORMAL ] Iteration 158:\tk_eff = 1.182992\tres = 9.147E-04\n", + "[ NORMAL ] Iteration 159:\tk_eff = 1.184020\tres = 8.915E-04\n", + "[ NORMAL ] Iteration 160:\tk_eff = 1.185025\tres = 8.696E-04\n", + "[ NORMAL ] Iteration 161:\tk_eff = 1.186005\tres = 8.487E-04\n", + "[ NORMAL ] Iteration 162:\tk_eff = 1.186962\tres = 8.268E-04\n", + "[ NORMAL ] Iteration 163:\tk_eff = 1.187896\tres = 8.073E-04\n", + "[ NORMAL ] Iteration 164:\tk_eff = 1.188808\tres = 7.869E-04\n", + "[ NORMAL ] Iteration 165:\tk_eff = 1.189698\tres = 7.679E-04\n", + "[ NORMAL ] Iteration 166:\tk_eff = 1.190567\tres = 7.487E-04\n", + "[ NORMAL ] Iteration 167:\tk_eff = 1.191415\tres = 7.301E-04\n", + "[ NORMAL ] Iteration 168:\tk_eff = 1.192243\tres = 7.126E-04\n", + "[ NORMAL ] Iteration 169:\tk_eff = 1.193051\tres = 6.949E-04\n", + "[ NORMAL ] Iteration 170:\tk_eff = 1.193840\tres = 6.775E-04\n", + "[ NORMAL ] Iteration 171:\tk_eff = 1.194610\tres = 6.613E-04\n", + "[ NORMAL ] Iteration 172:\tk_eff = 1.195361\tres = 6.454E-04\n", + "[ NORMAL ] Iteration 173:\tk_eff = 1.196095\tres = 6.290E-04\n", + "[ NORMAL ] Iteration 174:\tk_eff = 1.196810\tres = 6.136E-04\n", + "[ NORMAL ] Iteration 175:\tk_eff = 1.197509\tres = 5.981E-04\n", + "[ NORMAL ] Iteration 176:\tk_eff = 1.198191\tres = 5.840E-04\n", + "[ NORMAL ] Iteration 177:\tk_eff = 1.198857\tres = 5.694E-04\n", + "[ NORMAL ] Iteration 178:\tk_eff = 1.199507\tres = 5.556E-04\n", + "[ NORMAL ] Iteration 179:\tk_eff = 1.200141\tres = 5.422E-04\n", + "[ NORMAL ] Iteration 180:\tk_eff = 1.200760\tres = 5.284E-04\n", + "[ NORMAL ] Iteration 181:\tk_eff = 1.201364\tres = 5.157E-04\n", + "[ NORMAL ] Iteration 182:\tk_eff = 1.201954\tres = 5.031E-04\n", + "[ NORMAL ] Iteration 183:\tk_eff = 1.202528\tres = 4.908E-04\n", + "[ NORMAL ] Iteration 184:\tk_eff = 1.203090\tres = 4.782E-04\n", + "[ NORMAL ] Iteration 185:\tk_eff = 1.203638\tres = 4.671E-04\n", + "[ NORMAL ] Iteration 186:\tk_eff = 1.204172\tres = 4.552E-04\n", + "[ NORMAL ] Iteration 187:\tk_eff = 1.204694\tres = 4.442E-04\n", + "[ NORMAL ] Iteration 188:\tk_eff = 1.205204\tres = 4.335E-04\n", + "[ NORMAL ] Iteration 189:\tk_eff = 1.205701\tres = 4.227E-04\n", + "[ NORMAL ] Iteration 190:\tk_eff = 1.206186\tres = 4.128E-04\n", + "[ NORMAL ] Iteration 191:\tk_eff = 1.206660\tres = 4.025E-04\n", + "[ NORMAL ] Iteration 192:\tk_eff = 1.207121\tres = 3.925E-04\n", + "[ NORMAL ] Iteration 193:\tk_eff = 1.207571\tres = 3.824E-04\n", + "[ NORMAL ] Iteration 194:\tk_eff = 1.208011\tres = 3.729E-04\n", + "[ NORMAL ] Iteration 195:\tk_eff = 1.208440\tres = 3.641E-04\n", + "[ NORMAL ] Iteration 196:\tk_eff = 1.208859\tres = 3.558E-04\n", + "[ NORMAL ] Iteration 197:\tk_eff = 1.209268\tres = 3.467E-04\n", + "[ NORMAL ] Iteration 198:\tk_eff = 1.209667\tres = 3.379E-04\n", + "[ NORMAL ] Iteration 199:\tk_eff = 1.210056\tres = 3.299E-04\n", + "[ NORMAL ] Iteration 200:\tk_eff = 1.210436\tres = 3.221E-04\n", + "[ NORMAL ] Iteration 201:\tk_eff = 1.210807\tres = 3.139E-04\n", + "[ NORMAL ] Iteration 202:\tk_eff = 1.211169\tres = 3.064E-04\n", + "[ NORMAL ] Iteration 203:\tk_eff = 1.211522\tres = 2.988E-04\n", + "[ NORMAL ] Iteration 204:\tk_eff = 1.211866\tres = 2.916E-04\n", + "[ NORMAL ] Iteration 205:\tk_eff = 1.212203\tres = 2.840E-04\n", + "[ NORMAL ] Iteration 206:\tk_eff = 1.212530\tres = 2.779E-04\n", + "[ NORMAL ] Iteration 207:\tk_eff = 1.212850\tres = 2.701E-04\n", + "[ NORMAL ] Iteration 208:\tk_eff = 1.213162\tres = 2.638E-04\n", + "[ NORMAL ] Iteration 209:\tk_eff = 1.213466\tres = 2.575E-04\n", + "[ NORMAL ] Iteration 210:\tk_eff = 1.213764\tres = 2.507E-04\n", + "[ NORMAL ] Iteration 211:\tk_eff = 1.214054\tres = 2.452E-04\n", + "[ NORMAL ] Iteration 212:\tk_eff = 1.214337\tres = 2.392E-04\n", + "[ NORMAL ] Iteration 213:\tk_eff = 1.214614\tres = 2.331E-04\n", + "[ NORMAL ] Iteration 214:\tk_eff = 1.214884\tres = 2.278E-04\n", + "[ NORMAL ] Iteration 215:\tk_eff = 1.215146\tres = 2.221E-04\n", + "[ NORMAL ] Iteration 216:\tk_eff = 1.215403\tres = 2.161E-04\n", + "[ NORMAL ] Iteration 217:\tk_eff = 1.215654\tres = 2.114E-04\n", + "[ NORMAL ] Iteration 218:\tk_eff = 1.215898\tres = 2.063E-04\n", + "[ NORMAL ] Iteration 219:\tk_eff = 1.216136\tres = 2.009E-04\n", + "[ NORMAL ] Iteration 220:\tk_eff = 1.216369\tres = 1.962E-04\n", + "[ NORMAL ] Iteration 221:\tk_eff = 1.216596\tres = 1.912E-04\n", + "[ NORMAL ] Iteration 222:\tk_eff = 1.216817\tres = 1.863E-04\n", + "[ NORMAL ] Iteration 223:\tk_eff = 1.217033\tres = 1.820E-04\n", + "[ NORMAL ] Iteration 224:\tk_eff = 1.217245\tres = 1.774E-04\n", + "[ NORMAL ] Iteration 225:\tk_eff = 1.217450\tres = 1.735E-04\n", + "[ NORMAL ] Iteration 226:\tk_eff = 1.217651\tres = 1.692E-04\n", + "[ NORMAL ] Iteration 227:\tk_eff = 1.217847\tres = 1.651E-04\n", + "[ NORMAL ] Iteration 228:\tk_eff = 1.218039\tres = 1.610E-04\n", + "[ NORMAL ] Iteration 229:\tk_eff = 1.218225\tres = 1.570E-04\n", + "[ NORMAL ] Iteration 230:\tk_eff = 1.218407\tres = 1.533E-04\n", + "[ NORMAL ] Iteration 231:\tk_eff = 1.218585\tres = 1.496E-04\n", + "[ NORMAL ] Iteration 232:\tk_eff = 1.218758\tres = 1.454E-04\n", + "[ NORMAL ] Iteration 233:\tk_eff = 1.218927\tres = 1.419E-04\n", + "[ NORMAL ] Iteration 234:\tk_eff = 1.219092\tres = 1.387E-04\n", + "[ NORMAL ] Iteration 235:\tk_eff = 1.219253\tres = 1.357E-04\n", + "[ NORMAL ] Iteration 236:\tk_eff = 1.219410\tres = 1.322E-04\n", + "[ NORMAL ] Iteration 237:\tk_eff = 1.219563\tres = 1.287E-04\n", + "[ NORMAL ] Iteration 238:\tk_eff = 1.219713\tres = 1.259E-04\n", + "[ NORMAL ] Iteration 239:\tk_eff = 1.219859\tres = 1.226E-04\n", + "[ NORMAL ] Iteration 240:\tk_eff = 1.220001\tres = 1.197E-04\n", + "[ NORMAL ] Iteration 241:\tk_eff = 1.220140\tres = 1.165E-04\n", + "[ NORMAL ] Iteration 242:\tk_eff = 1.220275\tres = 1.137E-04\n", + "[ NORMAL ] Iteration 243:\tk_eff = 1.220407\tres = 1.110E-04\n", + "[ NORMAL ] Iteration 244:\tk_eff = 1.220536\tres = 1.083E-04\n", + "[ NORMAL ] Iteration 245:\tk_eff = 1.220662\tres = 1.055E-04\n", + "[ NORMAL ] Iteration 246:\tk_eff = 1.220785\tres = 1.032E-04\n", + "[ NORMAL ] Iteration 247:\tk_eff = 1.220904\tres = 1.006E-04\n", + "[ NORMAL ] Iteration 248:\tk_eff = 1.221022\tres = 9.809E-05\n", + "[ NORMAL ] Iteration 249:\tk_eff = 1.221135\tres = 9.592E-05\n", + "[ NORMAL ] Iteration 250:\tk_eff = 1.221247\tres = 9.321E-05\n", + "[ NORMAL ] Iteration 251:\tk_eff = 1.221355\tres = 9.097E-05\n", + "[ NORMAL ] Iteration 252:\tk_eff = 1.221462\tres = 8.887E-05\n", + "[ NORMAL ] Iteration 253:\tk_eff = 1.221565\tres = 8.723E-05\n", + "[ NORMAL ] Iteration 254:\tk_eff = 1.221666\tres = 8.475E-05\n", + "[ NORMAL ] Iteration 255:\tk_eff = 1.221765\tres = 8.255E-05\n", + "[ NORMAL ] Iteration 256:\tk_eff = 1.221861\tres = 8.043E-05\n", + "[ NORMAL ] Iteration 257:\tk_eff = 1.221955\tres = 7.865E-05\n", + "[ NORMAL ] Iteration 258:\tk_eff = 1.222046\tres = 7.688E-05\n", + "[ NORMAL ] Iteration 259:\tk_eff = 1.222135\tres = 7.466E-05\n", + "[ NORMAL ] Iteration 260:\tk_eff = 1.222223\tres = 7.284E-05\n", + "[ NORMAL ] Iteration 261:\tk_eff = 1.222308\tres = 7.152E-05\n", + "[ NORMAL ] Iteration 262:\tk_eff = 1.222391\tres = 6.950E-05\n", + "[ NORMAL ] Iteration 263:\tk_eff = 1.222471\tres = 6.795E-05\n", + "[ NORMAL ] Iteration 264:\tk_eff = 1.222551\tres = 6.597E-05\n", + "[ NORMAL ] Iteration 265:\tk_eff = 1.222628\tres = 6.472E-05\n", + "[ NORMAL ] Iteration 266:\tk_eff = 1.222703\tres = 6.301E-05\n", + "[ NORMAL ] Iteration 267:\tk_eff = 1.222776\tres = 6.133E-05\n", + "[ NORMAL ] Iteration 268:\tk_eff = 1.222847\tres = 5.983E-05\n", + "[ NORMAL ] Iteration 269:\tk_eff = 1.222916\tres = 5.841E-05\n", + "[ NORMAL ] Iteration 270:\tk_eff = 1.222985\tres = 5.693E-05\n", + "[ NORMAL ] Iteration 271:\tk_eff = 1.223051\tres = 5.580E-05\n", + "[ NORMAL ] Iteration 272:\tk_eff = 1.223116\tres = 5.434E-05\n", + "[ NORMAL ] Iteration 273:\tk_eff = 1.223179\tres = 5.309E-05\n", + "[ NORMAL ] Iteration 274:\tk_eff = 1.223241\tres = 5.180E-05\n", + "[ NORMAL ] Iteration 275:\tk_eff = 1.223301\tres = 5.028E-05\n", + "[ NORMAL ] Iteration 276:\tk_eff = 1.223360\tres = 4.919E-05\n", + "[ NORMAL ] Iteration 277:\tk_eff = 1.223417\tres = 4.820E-05\n", + "[ NORMAL ] Iteration 278:\tk_eff = 1.223473\tres = 4.688E-05\n", + "[ NORMAL ] Iteration 279:\tk_eff = 1.223528\tres = 4.559E-05\n", + "[ NORMAL ] Iteration 280:\tk_eff = 1.223581\tres = 4.450E-05\n", + "[ NORMAL ] Iteration 281:\tk_eff = 1.223633\tres = 4.364E-05\n", + "[ NORMAL ] Iteration 282:\tk_eff = 1.223683\tres = 4.228E-05\n", + "[ NORMAL ] Iteration 283:\tk_eff = 1.223733\tres = 4.136E-05\n", + "[ NORMAL ] Iteration 284:\tk_eff = 1.223781\tres = 4.026E-05\n", + "[ NORMAL ] Iteration 285:\tk_eff = 1.223827\tres = 3.920E-05\n", + "[ NORMAL ] Iteration 286:\tk_eff = 1.223873\tres = 3.823E-05\n", + "[ NORMAL ] Iteration 287:\tk_eff = 1.223918\tres = 3.753E-05\n", + "[ NORMAL ] Iteration 288:\tk_eff = 1.223962\tres = 3.644E-05\n", + "[ NORMAL ] Iteration 289:\tk_eff = 1.224004\tres = 3.550E-05\n", + "[ NORMAL ] Iteration 290:\tk_eff = 1.224046\tres = 3.466E-05\n", + "[ NORMAL ] Iteration 291:\tk_eff = 1.224086\tres = 3.402E-05\n", + "[ NORMAL ] Iteration 292:\tk_eff = 1.224126\tres = 3.307E-05\n", + "[ NORMAL ] Iteration 293:\tk_eff = 1.224164\tres = 3.223E-05\n", + "[ NORMAL ] Iteration 294:\tk_eff = 1.224202\tres = 3.152E-05\n", + "[ NORMAL ] Iteration 295:\tk_eff = 1.224238\tres = 3.065E-05\n", + "[ NORMAL ] Iteration 296:\tk_eff = 1.224274\tres = 2.986E-05\n", + "[ NORMAL ] Iteration 297:\tk_eff = 1.224309\tres = 2.910E-05\n", + "[ NORMAL ] Iteration 298:\tk_eff = 1.224343\tres = 2.846E-05\n", + "[ NORMAL ] Iteration 299:\tk_eff = 1.224376\tres = 2.759E-05\n", + "[ NORMAL ] Iteration 300:\tk_eff = 1.224409\tres = 2.719E-05\n", + "[ NORMAL ] Iteration 301:\tk_eff = 1.224440\tres = 2.648E-05\n", + "[ NORMAL ] Iteration 302:\tk_eff = 1.224471\tres = 2.590E-05\n", + "[ NORMAL ] Iteration 303:\tk_eff = 1.224501\tres = 2.516E-05\n", + "[ NORMAL ] Iteration 304:\tk_eff = 1.224530\tres = 2.465E-05\n", + "[ NORMAL ] Iteration 305:\tk_eff = 1.224559\tres = 2.396E-05\n", + "[ NORMAL ] Iteration 306:\tk_eff = 1.224587\tres = 2.342E-05\n", + "[ NORMAL ] Iteration 307:\tk_eff = 1.224614\tres = 2.290E-05\n", + "[ NORMAL ] Iteration 308:\tk_eff = 1.224641\tres = 2.223E-05\n", + "[ NORMAL ] Iteration 309:\tk_eff = 1.224667\tres = 2.184E-05\n", + "[ NORMAL ] Iteration 310:\tk_eff = 1.224692\tres = 2.128E-05\n", + "[ NORMAL ] Iteration 311:\tk_eff = 1.224717\tres = 2.079E-05\n", + "[ NORMAL ] Iteration 312:\tk_eff = 1.224741\tres = 2.012E-05\n", + "[ NORMAL ] Iteration 313:\tk_eff = 1.224765\tres = 1.973E-05\n", + "[ NORMAL ] Iteration 314:\tk_eff = 1.224788\tres = 1.914E-05\n", + "[ NORMAL ] Iteration 315:\tk_eff = 1.224810\tres = 1.873E-05\n", + "[ NORMAL ] Iteration 316:\tk_eff = 1.224832\tres = 1.829E-05\n", + "[ NORMAL ] Iteration 317:\tk_eff = 1.224853\tres = 1.789E-05\n", + "[ NORMAL ] Iteration 318:\tk_eff = 1.224874\tres = 1.741E-05\n", + "[ NORMAL ] Iteration 319:\tk_eff = 1.224894\tres = 1.709E-05\n", + "[ NORMAL ] Iteration 320:\tk_eff = 1.224914\tres = 1.658E-05\n", + "[ NORMAL ] Iteration 321:\tk_eff = 1.224933\tres = 1.610E-05\n", + "[ NORMAL ] Iteration 322:\tk_eff = 1.224952\tres = 1.591E-05\n", + "[ NORMAL ] Iteration 323:\tk_eff = 1.224971\tres = 1.536E-05\n", + "[ NORMAL ] Iteration 324:\tk_eff = 1.224989\tres = 1.503E-05\n", + "[ NORMAL ] Iteration 325:\tk_eff = 1.225006\tres = 1.462E-05\n", + "[ NORMAL ] Iteration 326:\tk_eff = 1.225024\tres = 1.423E-05\n", + "[ NORMAL ] Iteration 327:\tk_eff = 1.225040\tres = 1.403E-05\n", + "[ NORMAL ] Iteration 328:\tk_eff = 1.225057\tres = 1.356E-05\n", + "[ NORMAL ] Iteration 329:\tk_eff = 1.225073\tres = 1.327E-05\n", + "[ NORMAL ] Iteration 330:\tk_eff = 1.225088\tres = 1.311E-05\n", + "[ NORMAL ] Iteration 331:\tk_eff = 1.225103\tres = 1.278E-05\n", + "[ NORMAL ] Iteration 332:\tk_eff = 1.225118\tres = 1.230E-05\n", + "[ NORMAL ] Iteration 333:\tk_eff = 1.225132\tres = 1.199E-05\n", + "[ NORMAL ] Iteration 334:\tk_eff = 1.225146\tres = 1.169E-05\n", + "[ NORMAL ] Iteration 335:\tk_eff = 1.225160\tres = 1.134E-05\n", + "[ NORMAL ] Iteration 336:\tk_eff = 1.225173\tres = 1.111E-05\n", + "[ NORMAL ] Iteration 337:\tk_eff = 1.225186\tres = 1.073E-05\n", + "[ NORMAL ] Iteration 338:\tk_eff = 1.225199\tres = 1.067E-05\n", + "[ NORMAL ] Iteration 339:\tk_eff = 1.225211\tres = 1.050E-05\n" ] } ], @@ -1924,7 +1903,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 28, "metadata": { "collapsed": false }, @@ -1934,8 +1913,8 @@ "output_type": "stream", "text": [ "openmc keff = 1.224484\n", - "openmoc keff = 1.223146\n", - "bias [pcm]: -133.8\n" + "openmoc keff = 1.225211\n", + "bias [pcm]: 72.7\n" ] } ], @@ -1983,7 +1962,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 29, "metadata": { "collapsed": false }, @@ -1992,7 +1971,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/wbinventor/Documents/NSE-CRPG-Codes/openmc/openmc/tallies.py:1835: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/romano/openmc/openmc/tallies.py:1798: RuntimeWarning: invalid value encountered in true_divide\n", " self_rel_err = data['self']['std. dev.'] / data['self']['mean']\n" ] }, @@ -2002,15 +1981,15 @@ "(1.0000000000000001e-05, 20000000.0)" ] }, - "execution_count": 30, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "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\nxhiT4P4fD4z+wrudGZ0AAAAASUVORK5CYII=\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -2019,7 +1998,7 @@ ], "source": [ "# Create a figure of the U-235 continuous-energy fission cross section \n", - "fig = openmc.plot_xs(u235, ['fission'])\n", + "fig = openmc.plot_xs('U235', ['fission'])\n", "\n", "# Get the axis to use for plotting the MGXS\n", "ax = fig.gca()\n", @@ -2054,7 +2033,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 30, "metadata": { "collapsed": false }, @@ -2086,16 +2065,16 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 31, "metadata": { "collapsed": false }, "outputs": [ { "data": { - "image/png": "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\niTQvPToippHm9Xci7Wy9n7Tf5sI2flWNPH+LNG10bkTcRfqMzqL3mtAzz/8iSc2vSbqHNJJ/iLQD\n+XLS52dKHvTcBDwZERv00kbj+QHAdEnX5L/B9/K/0nT5eurznrwl8TtJvR39YFYLeUQ9E1hc0sy5\n3Z9OqeNI3XrIm+3TGpu5pL38U+dmn8zKEBG3RURjX9TOwIPzUkEHj9TnGZGOBT+etN/gGdIx5n0d\nLmY2JEXEWNKhgwuRdpz+q6Q7526vOstF3cysRjz9YmZWI3P9OPWuOwd2tEp/Vvn4/UWF4tEpa895\nJRsSuscN+HpAgzaeqwrL6yldK8x5pZZdNOdVbMjo7p70vtz2SN3MrEZc1M3MasRF3cysRlzUzcxq\npPQdpRFxCuna0LOAQyTdUXabZmVzXltVlTpSzxfWWU3SWNL1snu7qYPZkOK8tiore/plU/KdRCT9\nCVg8X5fYbChzXltllV3Ul2H2u7e8kJeZDWXOa6usTu8o7fhJIGYd4Ly2yii7qE9n9hHMcqSLSZkN\nZc5rq6yyi/o1pDt0ExFjgGmNe3eaDWHOa6usUou6pKnAnRFxE3+/dZnZkOa8tior/Th1SYeX3YZZ\npzmvrap8RqmZWY24qJuZ1YiLuplZjbiom5nVyFy/8xGvFhdqUYq7afiG464rLNbUKZsUFsuGhild\ntxQW6yi2LizW0ZxcWKzklYLj2WB5pG5mViMu6mZmNeKibmZWIy7qZmY14qJuZlYjpRf1iFgnIh6O\niAPKbsusk5zbVkVl385uEdKtviaX2Y5Zpzm3rarKHqm/AWyNrzVt9ePctkoq+9K7syS9WWYbZnOD\nc9uqyjtKzcxqxEXdzKxGOlnUfXNeqyvntlVGqRf0yvdvPBlYEXg7InYEPifp5TLbNSubc9uqqtSi\nLukuYOMy2zCbG5zbVlWeUzczqxEXdTOzGnFRNzOrERd1M7Mamfu3syvQPVM2KCzWf437j8JivTtu\n/sJi3XbruMJiAfBORWPZe47mqMJiHcuhhcUCOKLQ2+P51nhF8EjdzKxGXNTNzGrERd3MrEZc1M3M\nasRF3cysRko/+iUiTgQ+DcwPHC/p0rLbNCub89qqquzb2Y0H1pI0lnSXmFPLbM+sE5zXVmVlT79M\nAXbKj18GFokIX6bUhjrntVVW2Vdp7AZez0/3BX6dl5kNWc5rq7KOnFEaEROAvYEtOtGeWSc4r62K\n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\n", + "image/png": "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\nK8T/B194ISi0+XToAAAAAElFTkSuQmCC\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -2139,7 +2118,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.0" } }, "nbformat": 4, diff --git a/examples/jupyter/mgxs-part-iii.ipynb b/examples/jupyter/mgxs-part-iii.ipynb index addca822f..450276c89 100644 --- a/examples/jupyter/mgxs-part-iii.ipynb +++ b/examples/jupyter/mgxs-part-iii.ipynb @@ -63,31 +63,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "First we need to define materials that will be used in the problem. Before defining a material, we must create nuclides that are used in the material." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "# Instantiate some Nuclides\n", - "h1 = openmc.Nuclide('H1')\n", - "b10 = openmc.Nuclide('B10')\n", - "o16 = openmc.Nuclide('O16')\n", - "u235 = openmc.Nuclide('U235')\n", - "u238 = openmc.Nuclide('U238')\n", - "zr90 = openmc.Nuclide('Zr90')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With the nuclides we defined, we will now create three materials for the fuel, water, and cladding of the fuel pins." + "First we need to define materials that will be used in the problem. We'll create three materials for the fuel, water, and cladding of the fuel pins." ] }, { @@ -101,21 +77,21 @@ "# 1.6 enriched fuel\n", "fuel = openmc.Material(name='1.6% Fuel')\n", "fuel.set_density('g/cm3', 10.31341)\n", - "fuel.add_nuclide(u235, 3.7503e-4)\n", - "fuel.add_nuclide(u238, 2.2625e-2)\n", - "fuel.add_nuclide(o16, 4.6007e-2)\n", + "fuel.add_nuclide('U235', 3.7503e-4)\n", + "fuel.add_nuclide('U238', 2.2625e-2)\n", + "fuel.add_nuclide('O16', 4.6007e-2)\n", "\n", "# borated water\n", "water = openmc.Material(name='Borated Water')\n", "water.set_density('g/cm3', 0.740582)\n", - "water.add_nuclide(h1, 4.9457e-2)\n", - "water.add_nuclide(o16, 2.4732e-2)\n", - "water.add_nuclide(b10, 8.0042e-6)\n", + "water.add_nuclide('H1', 4.9457e-2)\n", + "water.add_nuclide('O16', 2.4732e-2)\n", + "water.add_nuclide('B10', 8.0042e-6)\n", "\n", "# zircaloy\n", "zircaloy = openmc.Material(name='Zircaloy')\n", "zircaloy.set_density('g/cm3', 6.55)\n", - "zircaloy.add_nuclide(zr90, 7.2758e-3)" + "zircaloy.add_nuclide('Zr90', 7.2758e-3)" ] }, { @@ -338,8 +314,7 @@ "outputs": [], "source": [ "# Create Geometry and set root Universe\n", - "geometry = openmc.Geometry()\n", - "geometry.root_universe = root_universe" + "geometry = openmc.Geometry(root_universe)" ] }, { @@ -1679,7 +1654,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.0" } }, "nbformat": 4, diff --git a/examples/jupyter/pincell.ipynb b/examples/jupyter/pincell.ipynb index 163429688..72b65a265 100644 --- a/examples/jupyter/pincell.ipynb +++ b/examples/jupyter/pincell.ipynb @@ -25,7 +25,7 @@ "source": [ "## Defining Materials\n", "\n", - "Materials in OpenMC are defined as a set of nuclides or elements with specified atom/weight fractions. There are two ways we can go about adding nuclides or elements to materials. The first way involves creating `Nuclide` or `Element` objects explicitly." + "Materials in OpenMC are defined as a set of nuclides with specified atom/weight fractions. To begin, we will create a material by making an instance of the `Material` class. In OpenMC, many objects, including materials, are identified by a \"unique ID\" that is simply just a positive integer. These IDs are used when exporting XML files that the solver reads in. They also appear in the output and can be used for identification. Since an integer ID is not very useful by itself, you can also give a material a `name` as well." ] }, { @@ -34,28 +34,6 @@ "metadata": { "collapsed": false }, - "outputs": [], - "source": [ - "u235 = openmc.Nuclide('U235')\n", - "u238 = openmc.Nuclide('U238')\n", - "o16 = openmc.Nuclide('O16')\n", - "zr = openmc.Element('Zr')\n", - "h1 = openmc.Nuclide('H1')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that we have all the nuclides/elements that we need, we can start creating materials. In OpenMC, many objects are identified by a \"unique ID\" that is simply just a positive integer. These IDs are used when exporting XML files that the solver reads in. They also appear in the output and can be used for identification. Assigning an ID is required -- we can also give a `name` as well." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, "outputs": [ { "name": "stdout", @@ -68,7 +46,6 @@ "\tDensity =\tNone []\n", "\tS(a,b) Tables \n", "\tNuclides \n", - "\tElements \n", "\n" ] } @@ -87,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -97,13 +74,12 @@ "output_type": "stream", "text": [ "Material\n", - "\tID =\t10000\n", + "\tID =\t2\n", "\tName =\t\n", "\tTemperature =\tNone\n", "\tDensity =\tNone []\n", "\tS(a,b) Tables \n", "\tNuclides \n", - "\tElements \n", "\n" ] } @@ -117,12 +93,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We see that an ID of 10000 was automatically assigned. Let's now move on to adding nuclides to our `uo2` material. The `Material` object has a method `add_nuclide()` whose first argument is the nuclide and second argument is the atom or weight fraction." + "We see that an ID of 2 was automatically assigned. Let's now move on to adding nuclides to our `uo2` material. The `Material` object has a method `add_nuclide()` whose first argument is the name of the nuclide and second argument is the atom or weight fraction." ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "collapsed": false }, @@ -138,7 +114,7 @@ " \n", " Parameters\n", " ----------\n", - " nuclide : str or openmc.Nuclide\n", + " nuclide : str\n", " Nuclide to add\n", " percent : float\n", " Atom or weight percent\n", @@ -161,16 +137,16 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Add nuclides to uo2\n", - "uo2.add_nuclide(u235, 0.03)\n", - "uo2.add_nuclide(u238, 0.97)\n", - "uo2.add_nuclide(o16, 2.0)" + "uo2.add_nuclide('U235', 0.03)\n", + "uo2.add_nuclide('U238', 0.97)\n", + "uo2.add_nuclide('O16', 2.0)" ] }, { @@ -182,7 +158,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -202,19 +178,28 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another Material instance already exists with id=2.\n", + " warn(msg, IDWarning)\n" + ] + } + ], "source": [ "zirconium = openmc.Material(2, \"zirconium\")\n", - "zirconium.add_element(zr, 1.0)\n", + "zirconium.add_element('Zr', 1.0)\n", "zirconium.set_density('g/cm3', 6.6)\n", "\n", "water = openmc.Material(3, \"h2o\")\n", - "water.add_nuclide(h1, 2.0)\n", - "water.add_nuclide(o16, 1.0)\n", + "water.add_nuclide('H1', 2.0)\n", + "water.add_nuclide('O16', 1.0)\n", "water.set_density('g/cm3', 1.0)" ] }, @@ -227,7 +212,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -236,28 +221,6 @@ "water.add_s_alpha_beta('c_H_in_H2O')" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "So far you've seen the \"hard\" way to create a material. The \"easy\" way is to just pass strings to `add_nuclide()` and `add_element()` -- they are implicitly converted to `Nuclide` and `Element` objects. For example, we could have created our UO2 material as follows:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "uo2 = openmc.Material(1, \"uo2\")\n", - "uo2.add_nuclide('U235', 0.03)\n", - "uo2.add_nuclide('U238', 0.97)\n", - "uo2.add_nuclide('O16', 2.0)\n", - "uo2.set_density('g/cm3', 10.0)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -267,7 +230,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -285,7 +248,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -296,7 +259,7 @@ "True" ] }, - "execution_count": 12, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -317,7 +280,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -328,26 +291,26 @@ "text": [ "\r\n", "\r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", "\r\n" ] } @@ -374,7 +337,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -385,27 +348,27 @@ "text": [ "\r\n", "\r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", "\r\n" ] } @@ -438,7 +401,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 13, "metadata": { "collapsed": false }, @@ -464,7 +427,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", "\n" @@ -488,7 +451,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -521,7 +484,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -541,7 +504,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "metadata": { "collapsed": false }, @@ -560,7 +523,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 17, "metadata": { "collapsed": false }, @@ -588,7 +551,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 18, "metadata": { "collapsed": true }, @@ -607,7 +570,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 19, "metadata": { "collapsed": false }, @@ -618,7 +581,7 @@ "(array([-1., -1., 0.]), array([ 1., 1., 1.]))" ] }, - "execution_count": 21, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -636,7 +599,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 20, "metadata": { "collapsed": false }, @@ -658,7 +621,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 21, "metadata": { "collapsed": true }, @@ -683,7 +646,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 22, "metadata": { "collapsed": true }, @@ -705,16 +668,16 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -734,16 +697,16 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [ { "data": { - "image/png": "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\nkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw\n8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUp\nCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNf\nkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpIw8CUpCQNfkpJoNPAj4qkRcUNETEfEYxGxMSKW\nL3DMbRGxt+WxJyKuarKfkpTBwQ2//o3ACuA04EnANcDVwPp5jinAJ4H3AVFv29VcFyUph8YCPyJW\nAeuAsVLKnfW2PwJuiYh3lVJ2zHP4rlLKI031TZIyanJKZy3w2EzY1zZTVfDPW+DYsyPikYj4RkRc\nHBFPaayXkpREk1M6K4GHWzeUUvZExA/rfXO5AXgAeAg4Efgw8GzgrIb6KUkptB34EXEJcOE8TQqw\nutMOlVI2tjy9JyJ2AJsj4rhSyn1zHbdhwwZGRkb22TY+Ps74+HinXZGkxk1MTDAxMbHPtunp6Ube\nK0op7R0Q8TTgaQs0+w7wJuAjpZSft42IZcDjwFmllM8v8v0OA/4XWFdK+dJ+9o8Ck5OTk4yOji7y\nU0hS/5qammJsbAyqa6BTS/W6bVf4pZRHgUcXahcRW4AjI2JNyzz+aVQrb+5o4y3XUP3W8P12+ypJ\n+oXGLtqWUrYDm4BPRcSpEfFC4C+BiZkVOhHxjIjYFhGn1M+fFRHvjYjRiDgmIs4ErgW+Wkr596b6\nKkkZNL0O/43Ax6lW5+wF/h54Z8v+Q6guyB5WP/8p8LK6zXLgv4C/Az7UcD8laeg1GvillP9mnj+y\nKqU8ACxref4g8JIm+yRJWXkvHUlKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElK\nwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCX\npCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQM\nfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElKwsCXpCQMfElK\n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\n", + "image/png": "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\n7yvyVt+kjXr+3XF3tvyA7auq6OdS5Ni2G2yfyvbRPtt3VtHPcdneYfvEoM99LWmfRUTtb5J+WZ0P\n4/yLpJkB6yyT9IKkyyWdLWm/pDVV933Edt0laVs2vU3SXw5Y75ikC6ru74htGfn8S9oo6VFJlnSt\npO9U3e+E23aDpIer7usStu1Dkq6SND9g+dj7rBEjlSj+dYC6atPXFPI8/5sk3R8dT0k6N/t8Ut01\n8djKJSL2SvrhkFXG3meNCJWc+n0d4JKK+pJX3q8phKQ9tueyryvUUZ7nv4n7SMrf7+uyU4RHbb+/\nnK5N3Nj7rIzv/uRS1tcByjZsu7rvRAz9msL1EbFg+0JJT9g+kv0Pg/p4RtKqiHjd9kZJ35C0uuI+\nVaI2oRKT/TpAZYZtl+1cX1OIiIXs3xO2d6ozHK9bqOR5/mu5j3IY2e+IeK1rerftL9u+ICKa/mXD\nsfdZm05/hn0doK5Gfk3B9nLb5yxOS7pJnd+xqZs8z/8uSR/L3lG4VtKprtO/Ohu5bbYvsu1sep06\nr61XS+9peuPvs6qvPue8Qr1ZnXO5n0h6WdLj2fx3S9rdc6X6e+pcqb+j6n7n2K7z1flxqucl7ZF0\nXu92qfOOw/7sdqjO29Xv+Zd0m6Tbsmmr84NdL0g6qAHv5NXxlmPbbs/2z35JT0m6ruo+59yuByQd\nl/TT7DX2yaL7jI/pA0iqTac/AGqAUAGQFKECIClCBUBShAqApAgVAEkRKgCS+n/VNzgEUV8nEAAA\nAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -763,16 +726,16 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXwAAAFkCAYAAAAjYoA8AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAAPYQAAD2EBqD+naQAAFEZJREFUeJzt3X+MZWV9x/H31wVF1jBYaXdrpPwIVZZEYGeguhqtFXFD\njWhS1IysktK0GrTaIRYT46/SKNEKW2y7LbpRoMA0tiZIxWbjomhTdjGdAbR2QY2gRdx1RTq27qKy\n+/SPc0bvTnd+3Lv33F/f9yu5gXvuc+597rPnfuZ7n/PMmSilIEkafU/qdwckSb1h4EtSEga+JCVh\n4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEo0GfkS8KCJui4jvRcTBiLhwBfu8JCJmIuLx\niPhGRFzSZB8lKYumK/zVwL3AZcCyF+2JiJOBzwJ3AGcB1wJbI+L85rooSTlEry6eFhEHgVeXUm5b\nos2HgAtKKWe2bJsGxkopv9uDbkrSyBq0OfznA9sXbNsGbOhDXyRppBzV7w4ssBbYs2DbHuC4iHhK\nKeWnC3eIiGcAG4GHgMcb76EkNe8Y4GRgWynl0W496aAFfic2Ajf3uxOS1ICLgVu69WSDFvi7gTUL\ntq0Bfny46r72EMBNN93EunXrGuza6JmammLz5s397kZ3TPTmZaaYYjM9GrOZ3rxML4zUsdYDu3bt\nYtOmTVDnW7cMWuDvAC5YsO3l9fbFPA6wbt06xsfHm+rXSBobGxueMYt+d6Ayxhjj9GjMlvshNkR/\nrG6ojrXB0tVp6qbX4a+OiLMi4ux606n1/RPrx6+KiBtadvm7us2HIuI5EXEZcBFwTZP9lKQMmq7w\nzwG+SFWLFODqevsNwKVUJ2lPnG9cSnkoIl4BbAbeBjwM/EEpZeHKHY2qAankh8JiYzVElb96q9HA\nL6V8iSW+RZRSfv8w275Mz2Zk1TcGe3P8QaBFDNo6fPXQ5ORkv7swdCZxzDrhsTYYBu2krXqoZx/C\nEarmhzrwD/fv0KOq38AfDAa+mjFCIT/SWv+dnPIZeU7pSFISVvg6clbzo6GPUz7qDQNfnTHkc3DK\nZ6Q4pSNJSVjha2Ws6LXwGLDiHzpW+JKUhBW+FmdVr6U4vz90DHz9kgGvTjndMxSc0pGkJAx8Vazu\n1U0eTwPJKZ3M/FCqSc7xDxwrfElKwgo/G6t69YPV/kAw8LMw6DUo5o9Fg7/nnNKRpCSs8EeZVb0G\nmdM8PWfgjxpDXsPI8O8Jp3QkKQkr/FFhZa9R4UndxljhS1ISBv4osLrXKPK47jqndIaVHwZl4Mnc\nrrLCl6QkrPCHjZW9svJk7hGzwh8mhr3k5+AIGPiSlIRTOsPAikY6lNM7HTHwB5UhLy3PVTxtcUpH\nkpKwwh80VvZSZ5zmWZYV/iAx7KUj5+doUQa+JCXhlM4gsCKRusvpncOywpekJAz8frO6l5rj5+sQ\nTun0iwei1BtO7/yCFb4kJWHg94PVvdR7fu6c0ukpDzipv5JP71jhS1ISVvi9YGUvDZaklb4VftMM\ne2lwJft8GviSlIRTOk1JVjlIQyvR9I4VviQlYeBLUhIGfhOczpGGT4LPrYEvSUl40rabElQI0kgb\n8RO4VvjdYthLo2NEP88GviQl4ZTOkRrRSkBKbwSnd6zwJSkJA1+SkjDwj4TTOdLoG6HPuXP4nRih\nA0DSCozIfL4VviQlYYXfDit7Kbchr/St8CUpCQNfkpIw8FfK6RxJ84Y0Dwx8SUrCwJekJHoS+BHx\nloh4MCL2R8TOiDh3ibaXRMTBiDhQ//dgROzrRT8P3yGG9uubpAYNYTY0HvgR8TrgauB9wHrgPmBb\nRJywxG5zwNqW20lN91OSRl0vKvwp4LpSyo2llPuBNwP7gEuX2KeUUvaWUn5Q3/b2oJ//35D99JbU\nB0OUE40GfkQcDUwAd8xvK6UUYDuwYYldnxYRD0XEdyPi1og4o8l+SlIGTVf4JwCrgD0Ltu+hmqo5\nnAeoqv8LgYup+nhXRDyzqU5KUgYDd2mFUspOYOf8/YjYAewC3kR1HqB5Q/QVTdIAGJJLLjQd+D8E\nDgBrFmxfA+xeyROUUp6IiHuA05ZqNzU1xdjY2CHbJicnmZycXHlvJanHpqenmZ6ePmTb3NxcI68V\n1ZR6cyJiJ3B3KeXt9f0Avgt8tJTyFyvY/0nA14HbSynvOMzj48DMzMwM4+PjXep0d55GUjJditPZ\n2VkmJiYAJkops9151t5M6VwDXB8RM8BXqFbtHAtcDxARNwIPl1LeVd9/D9WUzreA44ErgN8Atvag\nr4a9pM4FAz2t03jgl1I+Va+5v5JqKudeYGPLUstnAU+07PJ04GNUJ3UfA2aADfWSzuYY9JK6YYDn\n83ty0raUsgXYsshjL11w/3Lg8l70S5Iy8Vo6kpSEgS9JSRj44Py9pO4bwFwx8CUpCQNfkpIYuEsr\n9NQAfuWSNEIGbImmFb4kJWHgS1ISBr4kJZFzDt+5e0m9NCBz+Vb4kpSEgS9JSeQLfKdzJPVLn/Mn\nX+BLUlIGviQlYeBLUhIGviQlkWcdvidrJQ2CPq7Jt8KXpCQMfElKIkfgO50jadD0IZdyBL4kycCX\npCwMfElKwsCXpCRGex2+J2slDbIer8m3wpekJAx8SUrCwJekJAx8SUrCwJekJEY38F2hI2lY9Civ\nRjfwJUmHMPAlKQkDX5KSMPAlKQkDX5KSGL1r6bg6R9Iw6sF1dazwJSkJA1+SkjDwJSkJA1+SkjDw\nJSkJA1+SkjDwJSmJ0Qp81+BLGnYN5thoBb4kaVEGviQlYeBLUhIGviQlYeBLUhIGviQlYeBLUhIG\nviQlYeBLUhIGviQlMTqBP9HvDkhSlzSUZ6MT+JKkJRn4kpSEgS9JSRj4kpSEgS9JSRj4kpSEgS9J\nSRj4kpSEgS9JSRj4kpRETwI/It4SEQ9GxP6I2BkR5y7T/jURsatuf19EXNCLfkrSKGs88CPidcDV\nwPuA9cB9wLaIOGGR9i8AbgE+DpwNfAa4NSLOaLqvkjTKelHhTwHXlVJuLKXcD7wZ2Adcukj7twH/\nUkq5ppTyQCnlvcAs8NYe9FWSRlajgR8RR1Nd9+2O+W2llAJsBzYsstuG+vFW25ZoL0lagaYr/BOA\nVcCeBdv3AGsX2Wdtm+0lSStwVL870C1TL55ibGzskG2Tk5NMTk72qUeStLzp6Wmmp6cP2TY3Nwdf\n7v5rNR34PwQOAGsWbF8D7F5kn91ttgdg8+bNjI+Pd9JHSeqbwxWms7OzTEx0/6+gNDqlU0r5OTAD\nnDe/LSKivn/XIrvtaG1fO7/eLknqUC+mdK4Bro+IGeArVKt2jgWuB4iIG4GHSynvqttfC9wZEZcD\ntwOTVCd+/7AHfZWkkdV44JdSPlWvub+SamrmXmBjKWVv3eRZwBMt7XdExOuBD9S3bwKvKqX8Z9N9\nlaRR1pOTtqWULcCWRR576WG2fRr4dNP9kqRMvJaOJCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+\nJCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh\n4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtS\nEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+\nJCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh\n4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCVh4EtSEga+JCXRaOBHxNMj4uaImIuI\nxyJia0SsXmafOyPiYMvtQERsabKfkpTBUQ0//y3AGuA84MnA9cB1wKYl9inAx4D3AFFv29dcFyUp\nh8YCPyJOBzYCE6WUe+ptfwzcHhHvKKXsXmL3faWUvU31TZIyanJKZwPw2HzY17ZTVfDPW2bfiyNi\nb0R8LSI+GBFPbayXkpREk1M6a4EftG4opRyIiB/Vjy3mZuA7wCPAmcCHgWcDFzXUT0lKoe3Aj4ir\ngHcu0aQA6zrtUClla8vdr0fEbmB7RJxSSnlwsf2mpqYYGxs7ZNvk5CSTk5OddkWSGjc9Pc309PQh\n2+bm5hp5rSiltLdDxDOAZyzT7NvAG4CPlFJ+0TYiVgGPAxeVUj6zwtc7FvhfYGMp5fOHeXwcmJmZ\nmWF8fHyF70KSBtfs7CwTExNQnQOd7dbztl3hl1IeBR5drl1E7ACOj4j1LfP451GtvLm7jZdcT/Wt\n4fvt9lWS9EuNnbQtpdwPbAM+HhHnRsQLgb8CpudX6ETEMyNiV0ScU98/NSLeHRHjEXFSRFwI3AB8\nqZTyH031VZIyaHod/uuBv6ZanXMQ+Cfg7S2PH011QvbY+v7PgJfVbVYD/wX8I/CBhvspSSOv0cAv\npfw3S/ySVSnlO8CqlvsPAy9psk+SlJXX0pGkJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8\nSUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrC\nwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJek\nJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8\nSUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrCwJekJAx8SUrC\n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\n", + "image/png": "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\n/h+9QcZO3frUoAAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -801,7 +764,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 26, "metadata": { "collapsed": true }, @@ -821,7 +784,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 27, "metadata": { "collapsed": true }, @@ -841,11 +804,22 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 28, "metadata": { - "collapsed": true + "collapsed": false }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another Cell instance already exists with id=1.\n", + " warn(msg, IDWarning)\n", + "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another Cell instance already exists with id=2.\n", + " warn(msg, IDWarning)\n" + ] + } + ], "source": [ "fuel = openmc.Cell(1, 'fuel')\n", "fuel.fill = uo2\n", @@ -868,7 +842,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 29, "metadata": { "collapsed": true }, @@ -890,7 +864,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 30, "metadata": { "collapsed": false }, @@ -912,7 +886,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 31, "metadata": { "collapsed": false }, @@ -923,7 +897,7 @@ "openmc.region.Intersection" ] }, - "execution_count": 33, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -943,7 +917,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 32, "metadata": { "collapsed": true }, @@ -961,7 +935,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 33, "metadata": { "collapsed": false }, @@ -972,17 +946,17 @@ "text": [ "\r\n", "\r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", - " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", + " \r\n", "\r\n" ] } @@ -1010,7 +984,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 34, "metadata": { "collapsed": true }, @@ -1029,7 +1003,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 35, "metadata": { "collapsed": true }, @@ -1044,7 +1018,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 36, "metadata": { "collapsed": false }, @@ -1055,15 +1029,15 @@ "text": [ "\r\n", "\r\n", - " eigenvalue\r\n", - " 1000\r\n", - " 100\r\n", - " 10\r\n", - " \r\n", - " \r\n", - " 0 0 0\r\n", - " \r\n", - " \r\n", + " eigenvalue\r\n", + " 1000\r\n", + " 100\r\n", + " 10\r\n", + " \r\n", + " \r\n", + " 0 0 0\r\n", + " \r\n", + " \r\n", "\r\n" ] } @@ -1088,7 +1062,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 37, "metadata": { "collapsed": true }, @@ -1109,7 +1083,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 38, "metadata": { "collapsed": false }, @@ -1128,7 +1102,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 39, "metadata": { "collapsed": false }, @@ -1139,11 +1113,14 @@ "text": [ "\r\n", "\r\n", - " \r\n", - " \r\n", - " U235\r\n", - " total fission absorption (n,gamma)\r\n", - " \r\n", + " \r\n", + " 1\r\n", + " \r\n", + " \r\n", + " 1\r\n", + " U235\r\n", + " total fission absorption (n,gamma)\r\n", + " \r\n", "\r\n" ] } @@ -1165,7 +1142,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 40, "metadata": { "collapsed": false, "scrolled": true @@ -1203,30 +1180,29 @@ " | The OpenMC Monte Carlo Code\n", " Copyright | 2011-2017 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | bc4683be1c853fe6d0e31bccad416ef219e3efaf\n", - " Date/Time | 2017-04-13 17:17:32\n", - " MPI Processes | 1\n", + " Version | 0.9.0\n", + " Git SHA1 | 168f202e3ecf48cdd15b541dc396b24465832986\n", + " Date/Time | 2017-12-12 15:10:36\n", " OpenMP Threads | 4\n", "\n", " Reading settings XML file...\n", - " Reading geometry XML file...\n", " Reading materials XML file...\n", " Reading cross sections XML file...\n", - " Reading U235 from /home/smharper/openmc/data/nndc_hdf5/U235.h5\n", - " Reading U238 from /home/smharper/openmc/data/nndc_hdf5/U238.h5\n", - " Reading O16 from /home/smharper/openmc/data/nndc_hdf5/O16.h5\n", - " Reading Zr90 from /home/smharper/openmc/data/nndc_hdf5/Zr90.h5\n", - " Reading Zr91 from /home/smharper/openmc/data/nndc_hdf5/Zr91.h5\n", - " Reading Zr92 from /home/smharper/openmc/data/nndc_hdf5/Zr92.h5\n", - " Reading Zr94 from /home/smharper/openmc/data/nndc_hdf5/Zr94.h5\n", - " Reading Zr96 from /home/smharper/openmc/data/nndc_hdf5/Zr96.h5\n", - " Reading H1 from /home/smharper/openmc/data/nndc_hdf5/H1.h5\n", - " Reading O17 from /home/smharper/openmc/data/nndc_hdf5/O17.h5\n", - " Reading c_H_in_H2O from /home/smharper/openmc/data/nndc_hdf5/c_H_in_H2O.h5\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 Zr90 from /home/romano/openmc/scripts/nndc_hdf5/Zr90.h5\n", + " Reading Zr91 from /home/romano/openmc/scripts/nndc_hdf5/Zr91.h5\n", + " Reading Zr92 from /home/romano/openmc/scripts/nndc_hdf5/Zr92.h5\n", + " Reading Zr94 from /home/romano/openmc/scripts/nndc_hdf5/Zr94.h5\n", + " Reading Zr96 from /home/romano/openmc/scripts/nndc_hdf5/Zr96.h5\n", + " Reading H1 from /home/romano/openmc/scripts/nndc_hdf5/H1.h5\n", + " Reading O17 from /home/romano/openmc/scripts/nndc_hdf5/O17.h5\n", + " Reading c_H_in_H2O from /home/romano/openmc/scripts/nndc_hdf5/c_H_in_H2O.h5\n", " Maximum neutron transport energy: 2.00000E+07 eV for U235\n", " Reading tallies XML file...\n", - " Building neighboring cells lists for each surface...\n", " Initializing source particles...\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -1337,20 +1313,20 @@ "\n", " =======================> TIMING STATISTICS <=======================\n", "\n", - " Total time for initialization = 5.3000E-01 seconds\n", - " Reading cross sections = 4.7425E-01 seconds\n", - " Total time in simulation = 5.9503E+00 seconds\n", - " Time in transport only = 5.6693E+00 seconds\n", - " Time in inactive batches = 5.9508E-01 seconds\n", - " Time in active batches = 5.3552E+00 seconds\n", - " Time synchronizing fission bank = 4.5385E-03 seconds\n", - " Sampling source sites = 2.4558E-03 seconds\n", - " SEND/RECV source sites = 1.0922E-03 seconds\n", - " Time accumulating tallies = 4.2963E-04 seconds\n", - " Total time for finalization = 4.4542E-04 seconds\n", - " Total time elapsed = 6.4846E+00 seconds\n", - " Calculation Rate (inactive) = 16804.5 neutrons/second\n", - " Calculation Rate (active) = 16806.1 neutrons/second\n", + " Total time for initialization = 1.5898E+00 seconds\n", + " Reading cross sections = 1.4448E+00 seconds\n", + " Total time in simulation = 8.3458E+00 seconds\n", + " Time in transport only = 7.7626E+00 seconds\n", + " Time in inactive batches = 6.6138E-01 seconds\n", + " Time in active batches = 7.6845E+00 seconds\n", + " Time synchronizing fission bank = 7.1921E-03 seconds\n", + " Sampling source sites = 4.8562E-03 seconds\n", + " SEND/RECV source sites = 2.0519E-03 seconds\n", + " Time accumulating tallies = 2.3785E-04 seconds\n", + " Total time for finalization = 3.0973E-03 seconds\n", + " Total time elapsed = 9.9670E+00 seconds\n", + " Calculation Rate (inactive) = 15120.0 neutrons/second\n", + " Calculation Rate (active) = 11711.9 neutrons/second\n", "\n", " ============================> RESULTS <============================\n", "\n", @@ -1368,7 +1344,7 @@ "0" ] }, - "execution_count": 42, + "execution_count": 40, "metadata": {}, "output_type": "execute_result" } @@ -1386,7 +1362,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": 41, "metadata": { "collapsed": false }, @@ -1422,7 +1398,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": 42, "metadata": { "collapsed": true }, @@ -1445,7 +1421,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 43, "metadata": { "collapsed": false }, @@ -1456,13 +1432,13 @@ "text": [ "\r\n", "\r\n", - " \r\n", - " 0.0 0.0 0.0\r\n", - " 1.26 1.26\r\n", - " 200 200\r\n", - " \r\n", - " \r\n", - " \r\n", + " \r\n", + " 0.0 0.0 0.0\r\n", + " 1.26 1.26\r\n", + " 200 200\r\n", + " \r\n", + " \r\n", + " \r\n", "\r\n" ] } @@ -1482,7 +1458,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 44, "metadata": { "collapsed": false }, @@ -1519,23 +1495,22 @@ " | The OpenMC Monte Carlo Code\n", " Copyright | 2011-2017 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | bc4683be1c853fe6d0e31bccad416ef219e3efaf\n", - " Date/Time | 2017-04-13 17:18:01\n", - " MPI Processes | 1\n", + " Version | 0.9.0\n", + " Git SHA1 | 168f202e3ecf48cdd15b541dc396b24465832986\n", + " Date/Time | 2017-12-12 15:10:46\n", " OpenMP Threads | 4\n", "\n", " Reading settings XML file...\n", - " Reading geometry XML file...\n", " Reading materials XML file...\n", " Reading cross sections XML file...\n", + " Reading geometry XML file...\n", + " Building neighboring cells lists for each surface...\n", " Reading tallies XML file...\n", " Reading plot XML file...\n", - " Building neighboring cells lists for each surface...\n", "\n", " =======================> PLOTTING SUMMARY <========================\n", "\n", - " Plot ID: 10000\n", + " Plot ID: 1\n", " Plot file: pinplot.ppm\n", " Universe depth: -1\n", " Plot Type: Slice\n", @@ -1545,7 +1520,7 @@ " Basis: xy\n", " Pixels: 200 200\n", "\n", - " Processing plot 10000: pinplot.ppm ...\n" + " Processing plot 1: pinplot.ppm ...\n" ] }, { @@ -1554,7 +1529,7 @@ "0" ] }, - "execution_count": 46, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } @@ -1572,7 +1547,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 45, "metadata": { "collapsed": false }, @@ -1590,7 +1565,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 46, "metadata": { "collapsed": false, "scrolled": false @@ -1598,12 +1573,12 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAgMAAADQNkYNAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///8AAP9yEhL//wDh\n3HbeAAAAAWJLR0QAiAUdSAAAAAd0SU1FB+EEDRUSBp1cqOcAAAKHSURBVGje7dlLjoJAEAZgMrNj\n4z28hKcYFhzBU5hwAFfsTQwJcIqJyzkNcU3SAw2j/agq4Zegk3Rv9Ut3VSt0V0XR/PE1ewQSSCCB\nBHIfSTGT1Hrk00lSj6OYTOrbyCeS/Z3U00hqiLqaQpLaGsUEsrdJ/Zg4kxDTRI8m8afxyPi9Q/ca\nZRLtkiFd5fDqjcmkRdQkZbRR3WhHk4tkCD7aqWFEWyIBDtHBHzaj6OYhEuAQvaxPdRtN7K/MJnpd\nB2WMb39lNunzVW5M0sZezmziTTJOk7Mk8SYZpylY0q/rtLOJ+nBXZpE+xZ+OUM3WSbNF+nW5kygV\nO8GYJPGD/0tAwZCUWtewsoohe3Jdw8p4ciKEuvCk+ySjSGPHb5A++g1F2q0Vv0FSJhQdTEUSLhQ3\nGJtkNGk4woUyBJMTJGFD0cEUNGFC0cFQJJVJRZOMIw1N9mz0Q/wE6RLGRa+UmTKTlDyJKSIlzE7Z\nM0RKmJ0ykxx50lJEyrGdZZPwCdNZJkgpkZgg3bYIQv3cN8YgJ4lcSHKWyNUnibgtemOKRchRIq1P\nUnEn9V5WixBpJ/vtd8mDzTe3/zkiiv4XswQ5yeTikXoCyRcgZ5lcX0YymTSBWOTR08J8XqxK3jZj\n70ze9l+5xkMJeFqu9Rhf45UEvCvXeYkDp4t1jj3AeQw79c0+WwInWOicPPs0Dpz5gZvFOlce4C6G\n3PiAeyVwewXuyMBNHLjvA1UFpHYBVEiAOgxQ7QFqSkjlCqiPAVU4oNYHVBSRuiVQHQVqsEClF6kn\nA1VroDaOVOCBOj/QTUB6FkBnBOm/AF0eoJeEdKyAvhjSfUN6fEgnEehX6pkK7pP/1+ENJJBAAnkF\n+QXfoOhE52QgVwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wNC0xM1QxNzoxODowMS0wNDowMJXP\nFDgAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDQtMTNUMTc6MTg6MDEtMDQ6MDDkkqyEAAAAAElF\nTkSuQmCC\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAgMAAADQNkYNAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///8AAP9yEhL//wDh\n3HbeAAAAAWJLR0QAiAUdSAAAAAd0SU1FB+EMDAgKL8HHtFUAAAKHSURBVGje7dlLjoJAEAZgMrNj\n4z28hKcYFhzBU5hwAFfsTQwJcIqJyzkNcU3SAw2j/agq4Zegk3Rv9Ut3VSt0V0XR/PE1ewQSSCCB\nBHIfSTGT1Hrk00lSj6OYTOrbyCeS/Z3U00hqiLqaQpLaGsUEsrdJ/Zg4kxDTRI8m8afxyPi9Q/ca\nZRLtkiFd5fDqjcmkRdQkZbRR3WhHk4tkCD7aqWFEWyIBDtHBHzaj6OYhEuAQvaxPdRtN7K/MJnpd\nB2WMb39lNunzVW5M0sZezmziTTJOk7Mk8SYZpylY0q/rtLOJ+nBXZpE+xZ+OUM3WSbNF+nW5kygV\nO8GYJPGD/0tAwZCUWtewsoohe3Jdw8p4ciKEuvCk+ySjSGPHb5A++g1F2q0Vv0FSJhQdTEUSLhQ3\nGJtkNGk4woUyBJMTJGFD0cEUNGFC0cFQJJVJRZOMIw1N9mz0Q/wE6RLGRa+UmTKTlDyJKSIlzE7Z\nM0RKmJ0ykxx50lJEyrGdZZPwCdNZJkgpkZgg3bYIQv3cN8YgJ4lcSHKWyNUnibgtemOKRchRIq1P\nUnEn9V5WixBpJ/vtd8mDzTe3/zkiiv4XswQ5yeTikXoCyRcgZ5lcX0YymTSBWOTR08J8XqxK3jZj\n70ze9l+5xkMJeFqu9Rhf45UEvCvXeYkDp4t1jj3AeQw79c0+WwInWOicPPs0Dpz5gZvFOlce4C6G\n3PiAeyVwewXuyMBNHLjvA1UFpHYBVEiAOgxQ7QFqSkjlCqiPAVU4oNYHVBSRuiVQHQVqsEClF6kn\nA1VroDaOVOCBOj/QTUB6FkBnBOm/AF0eoJeEdKyAvhjSfUN6fEgnEehX6pkK7pP/1+ENJJBAAnkF\n+QXfoOhE52QgVwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0xMi0xMlQxNToxMDo0NyswNzowMJPh\nN3AAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMTItMTJUMTU6MTA6NDcrMDc6MDDivI/MAAAAAElF\nTkSuQmCC\n", "text/plain": [ "" ] }, - "execution_count": 48, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -1622,14 +1597,14 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 47, "metadata": { "collapsed": false }, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAgMAAADQNkYNAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///8AAP9yEhL//wDh\n3HbeAAAAAWJLR0QAiAUdSAAAAAd0SU1FB+EEDRUSCQ3jtXYAAAKHSURBVGje7dlLjoJAEAZgMrNj\n4z28hKcYFhzBU5hwAFfsTQwJcIqJyzkNcU3SAw2j/agq4Zegk3Rv9Ut3VSt0V0XR/PE1ewQSSCCB\nBHIfSTGT1Hrk00lSj6OYTOrbyCeS/Z3U00hqiLqaQpLaGsUEsrdJ/Zg4kxDTRI8m8afxyPi9Q/ca\nZRLtkiFd5fDqjcmkRdQkZbRR3WhHk4tkCD7aqWFEWyIBDtHBHzaj6OYhEuAQvaxPdRtN7K/MJnpd\nB2WMb39lNunzVW5M0sZezmziTTJOk7Mk8SYZpylY0q/rtLOJ+nBXZpE+xZ+OUM3WSbNF+nW5kygV\nO8GYJPGD/0tAwZCUWtewsoohe3Jdw8p4ciKEuvCk+ySjSGPHb5A++g1F2q0Vv0FSJhQdTEUSLhQ3\nGJtkNGk4woUyBJMTJGFD0cEUNGFC0cFQJJVJRZOMIw1N9mz0Q/wE6RLGRa+UmTKTlDyJKSIlzE7Z\nM0RKmJ0ykxx50lJEyrGdZZPwCdNZJkgpkZgg3bYIQv3cN8YgJ4lcSHKWyNUnibgtemOKRchRIq1P\nUnEn9V5WixBpJ/vtd8mDzTe3/zkiiv4XswQ5yeTikXoCyRcgZ5lcX0YymTSBWOTR08J8XqxK3jZj\n70ze9l+5xkMJeFqu9Rhf45UEvCvXeYkDp4t1jj3AeQw79c0+WwInWOicPPs0Dpz5gZvFOlce4C6G\n3PiAeyVwewXuyMBNHLjvA1UFpHYBVEiAOgxQ7QFqSkjlCqiPAVU4oNYHVBSRuiVQHQVqsEClF6kn\nA1VroDaOVOCBOj/QTUB6FkBnBOm/AF0eoJeEdKyAvhjSfUN6fEgnEehX6pkK7pP/1+ENJJBAAnkF\n+QXfoOhE52QgVwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wNC0xM1QxNzoxODowOS0wNDowMKYg\nWl8AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDQtMTNUMTc6MTg6MDktMDQ6MDDXfeLjAAAAAElF\nTkSuQmCC\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAgMAAADQNkYNAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///8AAP9yEhL//wDh\n3HbeAAAAAWJLR0QAiAUdSAAAAAd0SU1FB+EMDAgKL8HHtFUAAAKHSURBVGje7dlLjoJAEAZgMrNj\n4z28hKcYFhzBU5hwAFfsTQwJcIqJyzkNcU3SAw2j/agq4Zegk3Rv9Ut3VSt0V0XR/PE1ewQSSCCB\nBHIfSTGT1Hrk00lSj6OYTOrbyCeS/Z3U00hqiLqaQpLaGsUEsrdJ/Zg4kxDTRI8m8afxyPi9Q/ca\nZRLtkiFd5fDqjcmkRdQkZbRR3WhHk4tkCD7aqWFEWyIBDtHBHzaj6OYhEuAQvaxPdRtN7K/MJnpd\nB2WMb39lNunzVW5M0sZezmziTTJOk7Mk8SYZpylY0q/rtLOJ+nBXZpE+xZ+OUM3WSbNF+nW5kygV\nO8GYJPGD/0tAwZCUWtewsoohe3Jdw8p4ciKEuvCk+ySjSGPHb5A++g1F2q0Vv0FSJhQdTEUSLhQ3\nGJtkNGk4woUyBJMTJGFD0cEUNGFC0cFQJJVJRZOMIw1N9mz0Q/wE6RLGRa+UmTKTlDyJKSIlzE7Z\nM0RKmJ0ykxx50lJEyrGdZZPwCdNZJkgpkZgg3bYIQv3cN8YgJ4lcSHKWyNUnibgtemOKRchRIq1P\nUnEn9V5WixBpJ/vtd8mDzTe3/zkiiv4XswQ5yeTikXoCyRcgZ5lcX0YymTSBWOTR08J8XqxK3jZj\n70ze9l+5xkMJeFqu9Rhf45UEvCvXeYkDp4t1jj3AeQw79c0+WwInWOicPPs0Dpz5gZvFOlce4C6G\n3PiAeyVwewXuyMBNHLjvA1UFpHYBVEiAOgxQ7QFqSkjlCqiPAVU4oNYHVBSRuiVQHQVqsEClF6kn\nA1VroDaOVOCBOj/QTUB6FkBnBOm/AF0eoJeEdKyAvhjSfUN6fEgnEehX6pkK7pP/1+ENJJBAAnkF\n+QXfoOhE52QgVwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0xMi0xMlQxNToxMDo0NyswNzowMJPh\nN3AAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMTItMTJUMTU6MTA6NDcrMDc6MDDivI/MAAAAAElF\nTkSuQmCC\n", "text/plain": [ "" ] @@ -1660,7 +1635,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.0" } }, "nbformat": 4, diff --git a/examples/jupyter/post-processing.ipynb b/examples/jupyter/post-processing.ipynb index b0e955298..4c0f4ef00 100644 --- a/examples/jupyter/post-processing.ipynb +++ b/examples/jupyter/post-processing.ipynb @@ -34,36 +34,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "First we need to define materials that will be used in the problem. Before defining a material, we must create nuclides that are used in the material." + "First we need to define materials that will be used in the problem. We'll create three materials for the fuel, water, and cladding of the fuel pin." ] }, { "cell_type": "code", "execution_count": 2, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Instantiate some Nuclides\n", - "h1 = openmc.Nuclide('H1')\n", - "b10 = openmc.Nuclide('B10')\n", - "o16 = openmc.Nuclide('O16')\n", - "u235 = openmc.Nuclide('U235')\n", - "u238 = openmc.Nuclide('U238')\n", - "zr90 = openmc.Nuclide('Zr90')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With the nuclides we defined, we will now create three materials for the fuel, water, and cladding of the fuel pin." - ] - }, - { - "cell_type": "code", - "execution_count": 3, "metadata": { "collapsed": false }, @@ -72,21 +48,21 @@ "# 1.6 enriched fuel\n", "fuel = openmc.Material(name='1.6% Fuel')\n", "fuel.set_density('g/cm3', 10.31341)\n", - "fuel.add_nuclide(u235, 3.7503e-4)\n", - "fuel.add_nuclide(u238, 2.2625e-2)\n", - "fuel.add_nuclide(o16, 4.6007e-2)\n", + "fuel.add_nuclide('U235', 3.7503e-4)\n", + "fuel.add_nuclide('U238', 2.2625e-2)\n", + "fuel.add_nuclide('O16', 4.6007e-2)\n", "\n", "# borated water\n", "water = openmc.Material(name='Borated Water')\n", "water.set_density('g/cm3', 0.740582)\n", - "water.add_nuclide(h1, 4.9457e-2)\n", - "water.add_nuclide(o16, 2.4732e-2)\n", - "water.add_nuclide(b10, 8.0042e-6)\n", + "water.add_nuclide('H1', 4.9457e-2)\n", + "water.add_nuclide('O16', 2.4732e-2)\n", + "water.add_nuclide('B10', 8.0042e-6)\n", "\n", "# zircaloy\n", "zircaloy = openmc.Material(name='Zircaloy')\n", "zircaloy.set_density('g/cm3', 6.55)\n", - "zircaloy.add_nuclide(zr90, 7.2758e-3)" + "zircaloy.add_nuclide('Zr90', 7.2758e-3)" ] }, { @@ -98,7 +74,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -120,7 +96,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "collapsed": false }, @@ -148,7 +124,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -185,7 +161,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -212,20 +188,19 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Create Geometry and set root Universe\n", - "geometry = openmc.Geometry()\n", - "geometry.root_universe = root_universe" + "geometry = openmc.Geometry(root_universe)" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -244,7 +219,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -279,7 +254,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -307,7 +282,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -318,7 +293,7 @@ "0" ] }, - "execution_count": 12, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -330,19 +305,19 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///9yEhLpgJFNv8Tq\nQYT7AAAAAWJLR0QAiAUdSAAAAAd0SU1FB+AKHxEvDnAaMmcAAALKSURBVGje7dpLcqQwDAbgHHE2\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/4vzcvgeY10sY0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTYtMTAtMzFUMTI6NDc6\nMTQtMDU6MDDmKf94AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE2LTEwLTMxVDEyOjQ3OjE0LTA1OjAw\nl3RHxAAAAABJRU5ErkJggg==\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///9yEhLpgJFNv8Tq\nQYT7AAAAAWJLR0QAiAUdSAAAAAd0SU1FB+EMBQIvI1Ad4sUAAALKSURBVGje7dpLcqQwDAbgHHE2\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/4vzcvgeY10sY0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMTItMDRUMjA6NDc6\nMzUtMDY6MDBGrChsAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTEyLTA0VDIwOjQ3OjM1LTA2OjAw\nN/GQ0AAAAABJRU5ErkJggg==\n", "text/plain": [ "" ] }, - "execution_count": 13, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -364,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": { "collapsed": true }, @@ -376,7 +351,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": { "collapsed": false }, @@ -400,7 +375,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -419,7 +394,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": { "collapsed": false, "scrolled": true @@ -455,21 +430,18 @@ " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2016 Massachusetts Institute of Technology\n", + " Copyright | 2011-2017 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | da5563eddb5f2c2d6b2c9839d518de40962b78f2\n", - " Date/Time | 2016-10-31 12:47:14\n", + " Version | 0.9.0\n", + " Git SHA1 | 9b7cebf7bc34d60e0f1750c3d6cb103df11e8dc4\n", + " Date/Time | 2017-12-04 20:47:36\n", " OpenMP Threads | 4\n", "\n", - " ===========================================================================\n", - " ========================> INITIALIZATION <=========================\n", - " ===========================================================================\n", - "\n", " Reading settings XML file...\n", - " Reading geometry XML file...\n", - " Reading materials 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", @@ -478,145 +450,138 @@ " Reading Zr90 from /home/romano/openmc/scripts/nndc_hdf5/Zr90.h5\n", " Maximum neutron transport energy: 2.00000E+07 eV for U235\n", " Reading tallies XML file...\n", - " Building neighboring cells lists for each surface...\n", + " Writing summary.h5 file...\n", " Initializing source particles...\n", "\n", - " ===========================================================================\n", " ====================> K EIGENVALUE SIMULATION <====================\n", - " ===========================================================================\n", "\n", " Bat./Gen. k Average k \n", " ========= ======== ==================== \n", " 1/1 1.04359 \n", - " 2/1 1.04244 \n", - " 3/1 1.03020 \n", - " 4/1 1.03630 \n", - " 5/1 1.06478 \n", - " 6/1 1.05450 \n", - " 7/1 1.02369 \n", - " 8/1 1.03454 \n", - " 9/1 1.05309 \n", - " 10/1 1.01741 \n", - " 11/1 1.04344 \n", - " 12/1 1.01457 1.02901 +/- 0.01444\n", - " 13/1 1.01643 1.02481 +/- 0.00933\n", - " 14/1 1.04657 1.03025 +/- 0.00855\n", - " 15/1 1.06420 1.03704 +/- 0.00949\n", - " 16/1 1.06048 1.04095 +/- 0.00867\n", - " 17/1 1.01627 1.03742 +/- 0.00813\n", - " 18/1 1.03667 1.03733 +/- 0.00705\n", - " 19/1 1.03977 1.03760 +/- 0.00622\n", - " 20/1 1.03996 1.03784 +/- 0.00557\n", - " 21/1 1.05663 1.03954 +/- 0.00532\n", - " 22/1 1.03944 1.03954 +/- 0.00485\n", - " 23/1 1.07702 1.04242 +/- 0.00532\n", - " 24/1 1.02378 1.04109 +/- 0.00510\n", - " 25/1 1.02817 1.04023 +/- 0.00482\n", - " 26/1 1.08000 1.04271 +/- 0.00515\n", - " 27/1 1.02733 1.04181 +/- 0.00492\n", - " 28/1 1.02993 1.04115 +/- 0.00469\n", - " 29/1 1.01755 1.03991 +/- 0.00461\n", - " 30/1 1.05836 1.04083 +/- 0.00447\n", - " 31/1 1.05936 1.04171 +/- 0.00434\n", - " 32/1 1.03683 1.04149 +/- 0.00414\n", - " 33/1 1.05112 1.04191 +/- 0.00398\n", - " 34/1 1.02927 1.04138 +/- 0.00385\n", - " 35/1 1.05326 1.04186 +/- 0.00372\n", - " 36/1 1.06014 1.04256 +/- 0.00364\n", - " 37/1 1.02320 1.04184 +/- 0.00358\n", - " 38/1 1.04297 1.04188 +/- 0.00345\n", - " 39/1 1.04544 1.04201 +/- 0.00333\n", - " 40/1 1.05178 1.04233 +/- 0.00323\n", - " 41/1 1.01744 1.04153 +/- 0.00323\n", - " 42/1 1.02376 1.04097 +/- 0.00317\n", - " 43/1 1.02344 1.04044 +/- 0.00312\n", - " 44/1 1.05813 1.04096 +/- 0.00307\n", - " 45/1 1.02370 1.04047 +/- 0.00303\n", - " 46/1 1.03536 1.04033 +/- 0.00294\n", - " 47/1 1.04344 1.04041 +/- 0.00286\n", - " 48/1 1.03879 1.04037 +/- 0.00279\n", - " 49/1 1.07122 1.04116 +/- 0.00283\n", - " 50/1 1.03861 1.04110 +/- 0.00276\n", - " 51/1 1.00812 1.04029 +/- 0.00281\n", - " 52/1 1.04620 1.04043 +/- 0.00274\n", - " 53/1 1.07050 1.04113 +/- 0.00277\n", - " 54/1 1.04038 1.04111 +/- 0.00270\n", - " 55/1 1.03770 1.04104 +/- 0.00264\n", - " 56/1 1.05627 1.04137 +/- 0.00261\n", - " 57/1 1.04191 1.04138 +/- 0.00255\n", - " 58/1 1.03633 1.04128 +/- 0.00250\n", - " 59/1 1.02002 1.04084 +/- 0.00249\n", - " 60/1 1.04293 1.04088 +/- 0.00244\n", - " 61/1 1.00919 1.04026 +/- 0.00247\n", - " 62/1 1.09274 1.04127 +/- 0.00262\n", - " 63/1 1.05344 1.04150 +/- 0.00258\n", - " 64/1 1.07892 1.04219 +/- 0.00263\n", - " 65/1 1.09293 1.04312 +/- 0.00274\n", - " 66/1 1.04485 1.04315 +/- 0.00269\n", - " 67/1 1.04794 1.04323 +/- 0.00264\n", - " 68/1 1.04183 1.04321 +/- 0.00260\n", - " 69/1 1.03052 1.04299 +/- 0.00256\n", - " 70/1 1.03630 1.04288 +/- 0.00252\n", - " 71/1 1.04611 1.04293 +/- 0.00248\n", - " 72/1 1.00214 1.04228 +/- 0.00253\n", - " 73/1 1.02488 1.04200 +/- 0.00250\n", - " 74/1 1.03607 1.04191 +/- 0.00246\n", - " 75/1 1.05488 1.04211 +/- 0.00243\n", - " 76/1 1.02252 1.04181 +/- 0.00242\n", - " 77/1 1.03196 1.04166 +/- 0.00238\n", - " 78/1 1.05769 1.04190 +/- 0.00236\n", - " 79/1 1.03101 1.04174 +/- 0.00233\n", - " 80/1 1.04693 1.04182 +/- 0.00230\n", - " 81/1 1.06110 1.04209 +/- 0.00228\n", - " 82/1 1.06273 1.04237 +/- 0.00227\n", - " 83/1 1.07699 1.04285 +/- 0.00229\n", - " 84/1 1.05740 1.04304 +/- 0.00226\n", - " 85/1 1.04040 1.04301 +/- 0.00223\n", - " 86/1 1.02147 1.04273 +/- 0.00222\n", - " 87/1 1.00842 1.04228 +/- 0.00224\n", - " 88/1 1.06173 1.04253 +/- 0.00222\n", - " 89/1 1.08649 1.04309 +/- 0.00227\n", - " 90/1 1.05826 1.04328 +/- 0.00224\n", - " 91/1 1.07673 1.04369 +/- 0.00225\n", - " 92/1 1.02425 1.04345 +/- 0.00224\n", - " 93/1 1.05359 1.04357 +/- 0.00222\n", - " 94/1 1.06085 1.04378 +/- 0.00220\n", - " 95/1 1.05319 1.04389 +/- 0.00218\n", - " 96/1 1.02321 1.04365 +/- 0.00216\n", - " 97/1 1.04124 1.04362 +/- 0.00214\n", - " 98/1 1.06307 1.04384 +/- 0.00213\n", - " 99/1 1.04616 1.04387 +/- 0.00210\n", - " 100/1 1.03278 1.04375 +/- 0.00208\n", + " 2/1 1.04323 \n", + " 3/1 1.04711 \n", + " 4/1 1.03892 \n", + " 5/1 1.02442 \n", + " 6/1 1.02046 \n", + " 7/1 1.05998 \n", + " 8/1 1.04184 \n", + " 9/1 1.04786 \n", + " 10/1 1.06636 \n", + " 11/1 1.07229 \n", + " 12/1 1.04413 1.05821 +/- 0.01408\n", + " 13/1 1.06376 1.06006 +/- 0.00834\n", + " 14/1 1.06898 1.06229 +/- 0.00630\n", + " 15/1 1.05095 1.06002 +/- 0.00538\n", + " 16/1 1.04453 1.05744 +/- 0.00510\n", + " 17/1 1.05626 1.05727 +/- 0.00431\n", + " 18/1 1.03423 1.05439 +/- 0.00472\n", + " 19/1 1.04240 1.05306 +/- 0.00437\n", + " 20/1 1.03719 1.05147 +/- 0.00422\n", + " 21/1 1.04308 1.05071 +/- 0.00389\n", + " 22/1 1.03956 1.04978 +/- 0.00367\n", + " 23/1 1.05824 1.05043 +/- 0.00344\n", + " 24/1 1.03151 1.04908 +/- 0.00346\n", + " 25/1 1.02695 1.04760 +/- 0.00354\n", + " 26/1 1.02581 1.04624 +/- 0.00358\n", + " 27/1 1.09932 1.04936 +/- 0.00459\n", + " 28/1 1.05983 1.04995 +/- 0.00437\n", + " 29/1 1.03381 1.04910 +/- 0.00422\n", + " 30/1 1.06727 1.05001 +/- 0.00410\n", + " 31/1 1.03180 1.04914 +/- 0.00400\n", + " 32/1 1.04520 1.04896 +/- 0.00382\n", + " 33/1 1.07158 1.04994 +/- 0.00378\n", + " 34/1 1.04283 1.04965 +/- 0.00363\n", + " 35/1 1.03272 1.04897 +/- 0.00354\n", + " 36/1 1.02730 1.04814 +/- 0.00351\n", + " 37/1 1.01975 1.04709 +/- 0.00353\n", + " 38/1 1.04815 1.04712 +/- 0.00341\n", + " 39/1 1.02642 1.04641 +/- 0.00336\n", + " 40/1 1.04063 1.04622 +/- 0.00325\n", + " 41/1 0.97384 1.04388 +/- 0.00392\n", + " 42/1 1.06049 1.04440 +/- 0.00383\n", + " 43/1 1.04467 1.04441 +/- 0.00371\n", + " 44/1 1.04454 1.04441 +/- 0.00360\n", + " 45/1 1.06529 1.04501 +/- 0.00355\n", + " 46/1 1.05496 1.04529 +/- 0.00346\n", + " 47/1 1.03717 1.04507 +/- 0.00337\n", + " 48/1 1.03874 1.04490 +/- 0.00328\n", + " 49/1 1.02083 1.04428 +/- 0.00326\n", + " 50/1 1.04847 1.04439 +/- 0.00318\n", + " 51/1 1.03789 1.04423 +/- 0.00310\n", + " 52/1 1.04447 1.04423 +/- 0.00303\n", + " 53/1 1.01942 1.04366 +/- 0.00301\n", + " 54/1 1.04639 1.04372 +/- 0.00294\n", + " 55/1 1.02539 1.04331 +/- 0.00291\n", + " 56/1 1.06312 1.04374 +/- 0.00288\n", + " 57/1 1.05854 1.04406 +/- 0.00283\n", + " 58/1 1.05150 1.04421 +/- 0.00278\n", + " 59/1 1.04321 1.04419 +/- 0.00272\n", + " 60/1 1.04762 1.04426 +/- 0.00266\n", + " 61/1 0.99442 1.04328 +/- 0.00279\n", + " 62/1 1.06907 1.04378 +/- 0.00278\n", + " 63/1 1.03170 1.04355 +/- 0.00274\n", + " 64/1 1.04308 1.04354 +/- 0.00268\n", + " 65/1 1.01439 1.04301 +/- 0.00269\n", + " 66/1 1.04581 1.04306 +/- 0.00264\n", + " 67/1 1.04404 1.04308 +/- 0.00259\n", + " 68/1 1.03158 1.04288 +/- 0.00256\n", + " 69/1 1.04953 1.04299 +/- 0.00251\n", + " 70/1 1.06338 1.04333 +/- 0.00250\n", + " 71/1 1.03768 1.04324 +/- 0.00246\n", + " 72/1 1.02531 1.04295 +/- 0.00243\n", + " 73/1 1.04552 1.04299 +/- 0.00240\n", + " 74/1 1.04293 1.04299 +/- 0.00236\n", + " 75/1 1.05928 1.04324 +/- 0.00233\n", + " 76/1 1.05057 1.04335 +/- 0.00230\n", + " 77/1 1.01846 1.04298 +/- 0.00230\n", + " 78/1 1.05755 1.04320 +/- 0.00227\n", + " 79/1 1.05222 1.04333 +/- 0.00224\n", + " 80/1 1.04860 1.04340 +/- 0.00221\n", + " 81/1 1.03026 1.04322 +/- 0.00219\n", + " 82/1 1.02360 1.04294 +/- 0.00218\n", + " 83/1 1.06679 1.04327 +/- 0.00217\n", + " 84/1 1.06297 1.04354 +/- 0.00216\n", + " 85/1 1.04426 1.04355 +/- 0.00213\n", + " 86/1 1.00337 1.04302 +/- 0.00217\n", + " 87/1 1.04787 1.04308 +/- 0.00214\n", + " 88/1 1.04332 1.04308 +/- 0.00211\n", + " 89/1 1.04369 1.04309 +/- 0.00208\n", + " 90/1 1.05006 1.04318 +/- 0.00206\n", + " 91/1 1.05394 1.04331 +/- 0.00204\n", + " 92/1 1.06017 1.04352 +/- 0.00202\n", + " 93/1 1.02032 1.04324 +/- 0.00202\n", + " 94/1 1.04816 1.04330 +/- 0.00200\n", + " 95/1 1.06601 1.04356 +/- 0.00199\n", + " 96/1 1.02876 1.04339 +/- 0.00197\n", + " 97/1 1.03929 1.04334 +/- 0.00195\n", + " 98/1 1.01958 1.04307 +/- 0.00195\n", + " 99/1 1.01899 1.04280 +/- 0.00195\n", + " 100/1 1.05150 1.04290 +/- 0.00193\n", " Creating state point statepoint.100.h5...\n", "\n", - " ===========================================================================\n", - " ======================> SIMULATION FINISHED <======================\n", - " ===========================================================================\n", - "\n", - "\n", " =======================> TIMING STATISTICS <=======================\n", "\n", - " Total time for initialization = 5.3314E-01 seconds\n", - " Reading cross sections = 3.6117E-01 seconds\n", - " Total time in simulation = 3.5193E+02 seconds\n", - " Time in transport only = 3.5172E+02 seconds\n", - " Time in inactive batches = 4.7990E+00 seconds\n", - " Time in active batches = 3.4714E+02 seconds\n", - " Time synchronizing fission bank = 2.3264E-02 seconds\n", - " Sampling source sites = 1.6661E-02 seconds\n", - " SEND/RECV source sites = 6.3975E-03 seconds\n", - " Time accumulating tallies = 2.3345E-02 seconds\n", - " Total time for finalization = 1.7400E-01 seconds\n", - " Total time elapsed = 3.5269E+02 seconds\n", - " Calculation Rate (inactive) = 10418.8 neutrons/second\n", - " Calculation Rate (active) = 1296.32 neutrons/second\n", + " Total time for initialization = 6.5927E-01 seconds\n", + " Reading cross sections = 6.1679E-01 seconds\n", + " Total time in simulation = 1.2768E+02 seconds\n", + " Time in transport only = 1.2740E+02 seconds\n", + " Time in inactive batches = 2.7221E+00 seconds\n", + " Time in active batches = 1.2496E+02 seconds\n", + " Time synchronizing fission bank = 2.4179E-02 seconds\n", + " Sampling source sites = 1.7261E-02 seconds\n", + " SEND/RECV source sites = 6.7268E-03 seconds\n", + " Time accumulating tallies = 1.5442E-02 seconds\n", + " Total time for finalization = 1.6664E-01 seconds\n", + " Total time elapsed = 1.2854E+02 seconds\n", + " Calculation Rate (inactive) = 18368.1 neutrons/second\n", + " Calculation Rate (active) = 3601.09 neutrons/second\n", "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 1.04220 +/- 0.00169\n", - " k-effective (Track-length) = 1.04375 +/- 0.00208\n", - " k-effective (Absorption) = 1.04213 +/- 0.00151\n", - " Combined k-effective = 1.04229 +/- 0.00127\n", + " k-effective (Collision) = 1.04331 +/- 0.00192\n", + " k-effective (Track-length) = 1.04290 +/- 0.00193\n", + " k-effective (Absorption) = 1.04136 +/- 0.00151\n", + " Combined k-effective = 1.04183 +/- 0.00122\n", " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] @@ -627,7 +592,7 @@ "0" ] }, - "execution_count": 17, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -653,7 +618,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": { "collapsed": false, "scrolled": true @@ -673,7 +638,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": { "collapsed": false }, @@ -683,9 +648,9 @@ "output_type": "stream", "text": [ "Tally\n", - "\tID =\t10000\n", + "\tID =\t1\n", "\tName =\tflux\n", - "\tFilters =\tMeshFilter\t[10000]\n", + "\tFilters =\tMeshFilter\n", "\tNuclides =\ttotal \n", "\tScores =\t['flux', 'fission']\n", "\tEstimator =\ttracklength\n", @@ -707,7 +672,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": { "collapsed": false }, @@ -715,21 +680,21 @@ { "data": { "text/plain": [ - "array([[[ 0.41167874, 0. ]],\n", + "array([[[ 0.40981574, 0. ]],\n", "\n", - " [[ 0.40853332, 0. ]],\n", + " [[ 0.40963388, 0. ]],\n", "\n", - " [[ 0.41140779, 0. ]],\n", + " [[ 0.41117481, 0. ]],\n", "\n", " ..., \n", - " [[ 0.40957563, 0. ]],\n", + " [[ 0.41179009, 0. ]],\n", "\n", - " [[ 0.40983338, 0. ]],\n", + " [[ 0.41329412, 0. ]],\n", "\n", - " [[ 0.40877195, 0. ]]])" + " [[ 0.41494587, 0. ]]])" ] }, - "execution_count": 20, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -747,7 +712,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": { "collapsed": false }, @@ -762,33 +727,33 @@ { "data": { "text/plain": [ - "(array([[[ 0.00457421, 0. ]],\n", + "(array([[[ 0.00455351, 0. ]],\n", " \n", - " [[ 0.00453926, 0. ]],\n", + " [[ 0.00455149, 0. ]],\n", " \n", - " [[ 0.0045712 , 0. ]],\n", + " [[ 0.00456861, 0. ]],\n", " \n", " ..., \n", - " [[ 0.00455084, 0. ]],\n", + " [[ 0.00457545, 0. ]],\n", " \n", - " [[ 0.0045537 , 0. ]],\n", + " [[ 0.00459216, 0. ]],\n", " \n", - " [[ 0.00454191, 0. ]]]),\n", - " array([[[ 1.84557765e-05, 0.00000000e+00]],\n", + " [[ 0.00461051, 0. ]]]),\n", + " array([[[ 2.00748004e-05, 0.00000000e+00]],\n", " \n", - " [[ 1.71073587e-05, 0.00000000e+00]],\n", + " [[ 1.75039529e-05, 0.00000000e+00]],\n", " \n", - " [[ 1.76546641e-05, 0.00000000e+00]],\n", + " [[ 1.96093103e-05, 0.00000000e+00]],\n", " \n", " ..., \n", - " [[ 1.82859458e-05, 0.00000000e+00]],\n", + " [[ 1.69721143e-05, 0.00000000e+00]],\n", " \n", - " [[ 1.80961726e-05, 0.00000000e+00]],\n", + " [[ 1.58964240e-05, 0.00000000e+00]],\n", " \n", - " [[ 2.01200499e-05, 0.00000000e+00]]]))" + " [[ 1.81009205e-05, 0.00000000e+00]]]))" ] }, - "execution_count": 21, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -807,7 +772,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": { "collapsed": false }, @@ -817,9 +782,9 @@ "output_type": "stream", "text": [ "Tally\n", - "\tID =\t10001\n", + "\tID =\t2\n", "\tName =\tflux\n", - "\tFilters =\tMeshFilter\t[10000]\n", + "\tFilters =\tMeshFilter\n", "\tNuclides =\ttotal \n", "\tScores =\t['flux']\n", "\tEstimator =\ttracklength\n", @@ -842,7 +807,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": { "collapsed": false }, @@ -856,7 +821,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "metadata": { "collapsed": false }, @@ -864,18 +829,18 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 24, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "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\nRNgj+L+7omDDDxCT/gAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -898,16 +863,16 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -940,7 +905,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "metadata": { "collapsed": false }, @@ -948,17 +913,17 @@ { "data": { "text/plain": [ - "array([ (1.0, [-0.04091602298055571, -0.28281765875028614, -0.23969748862718712], [-0.5162507278389928, 0.4979959592686946, 0.6967676876533259], 1167993.2181739977, 0),\n", - " (1.0, [-0.04091602298055571, -0.28281765875028614, -0.23969748862718712], [0.9314318185369252, -0.3421578395220699, 0.12394668317702494], 2076480.0407698506, 0),\n", - " (1.0, [0.07588719867579076, 0.3442630703689722, 0.24037509956303865], [-0.4263739205747279, 0.8745850440707152, -0.23088152923428204], 3435875.6567740417, 0),\n", + "array([ (1.0, [0.08865821621194064, 0.3784631548839458, 0.3904972254761878], [-0.1326194120629367, 0.6232164386612264, 0.7707226233389667], 1192346.6320091304, 0),\n", + " (1.0, [0.08865821621194064, 0.3784631548839458, 0.3904972254761878], [-0.6644604177986176, -0.6182443857504494, 0.41984072297352965], 356616.8122252148, 0),\n", + " (1.0, [-0.0524358478826145, -0.2989344511695743, -0.17188682459930016], [0.26140031911472983, 0.8973965547376798, 0.35545646246996265], 836460.5302888667, 0),\n", " ...,\n", - " (1.0, [-0.2976222011116176, 0.06744830909702038, 0.017418564319938733], [0.5146424164525509, 0.8138247541106745, -0.26987488357492534], 3072272.553085709, 0),\n", - " (1.0, [-0.21734045199173016, -0.10720270103504186, -0.532882255592573], [-0.8802303390142701, -0.22473292046402116, 0.4179589270951574], 5120128.793040418, 0),\n", - " (1.0, [-0.21734045199173016, -0.10720270103504186, -0.532882255592573], [-0.16842874876345693, 0.9852495482818028, 0.030250358683490713], 575131.1093690266, 0)], \n", + " (1.0, [-0.1061743912824093, 0.2687838889464804, 0.3209783107825243], [0.04074864852534854, 0.6722488179526406, -0.7392029994559244], 4419569.703536596, 0),\n", + " (1.0, [-0.1061743912824093, 0.2687838889464804, 0.3209783107825243], [0.2921501228867853, 0.9089796683155593, 0.2973285527596903], 2716068.3317314633, 0),\n", + " (1.0, [0.24742871049665383, 0.2887788071034633, 0.12388141872813332], [0.9088435703078486, -0.040848021273558396, -0.4151322727373983], 1005076.8690394862, 0)], \n", " dtype=[('wgt', '" + "" ] }, - "execution_count": 28, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYwAAAEOCAYAAACaQSCZAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAGGRJREFUeJzt3X+UpFV54PHvw0QE0QEjoxkbZmdcZ2Xb7DqyIwMh/kii\nkUGk90hWYCAsxCywC4km5mxIdnPQJTnBs9EVE2SYBVxABBRxGc0k+BNjFJDhhyI9sk4GYWZs4yCG\nQSHR0Wf/eN+ORU9V1+2efruqur6fc+p0vffet+vpe6r76fveW++NzESSpG7263UAkqTBYMKQJBUx\nYUiSipgwJElFTBiSpCImDElSEROGJKmICUOSVMSEIUkqYsKQJBX5mV4HMJcOPfTQXL58ea/DkKSB\ncffddz+amUtK2i6ohLF8+XI2b97c6zAkaWBExMOlbb0kJUkqYsKQJBUxYUiSipgwJElFTBiSpCIm\nDElSkQW1rFaSWn3ozke45b6dbevGVo2wbs2yeY5osDnCkLRg3XLfTsYndu9VPj6xu2MiUWeOMCQt\naKNLF3PjOcc8rezky2/vUTSDzYQhaSiNT+zumDi8XNWeCUPS0BlbNdKx7s6HHuPOhx5re8lq2BOJ\nCUPS0Fm3ZlnHP/ydJson50JMGJIkoHMycd7DVVKSpEImDElSEROGJKmICUOSVMSEIUkqYsKQJBUx\nYUiSipgwJElFTBiSpCImDElSEROGJKmICUOSVMSEIUkqYsKQJBVp9PbmEXEccAmwCLgiMy+eUh91\n/fHAk8CZmXlPXfc7wG8CCdwPnJWZ/9hkvJL6V6d9KiYN++ZG86GxEUZELAIuBdYCo8CpETE6pdla\nYGX9OBu4rD53BPhtYHVm/jxVwjmlqVgl9b9b7tv5z5sYTTU+sXvaZKK50eQI4yhga2ZuA4iIG4Ax\nYLylzRhwTWYmcEdEHBIRS1tiOzAifgQ8C/hWg7FKGgCjSxdz4znH7FXu5kbzo8mEMQJsbzneAawp\naDOSmZsj4s+AR4CngE9m5icbjFXSgBuf2L1X4hif2M3o0sU9imjh6ctJ74h4LtXoYwXwQuCgiDi9\nQ9uzI2JzRGzetWvXfIYpqU+MrRppmxhGly5mbNVIDyJamJocYewEDm85PqwuK2nzWuChzNwFEBE3\nA78AfHDqi2TmBmADwOrVq3Ougpc0ODrtw6251eQI4y5gZUSsiIj9qSatN05psxE4IypHA49n5gTV\npaijI+JZ9UqqXwG2NBirJKmLxkYYmbknIs4HbqVa5XRVZj4QEefW9euBTVRLardSLas9q667MyJu\nAu4B9gD3Uo8iJEm90ejnMDJzE1VSaC1b3/I8gfM6nHshcGGT8UmSyvXlpLckqf+YMCRJRUwYkqQi\nJgxJUhEThiSpiAlDklTEhCFJKmLCkCQVMWFIkoqYMCRJRUwYkqQiJgxJUhEThiSpiAlDklTEhCFJ\nKmLCkCQVaXQDJUmaqQ/d+Qi33Ldzr/Lxid2MLl3cg4g0yRGGpL5yy307GZ/YvVf56NLFjK0a6UFE\nmuQIQ1LfGV26mBvPOabXYWgKRxiSpCImDElSEROGJKmICUOSVMSEIUkqYsKQJBUxYUiSipgwJElF\nTBiSpCImDElSEROGJKmI95KSpELjE7s5+fLb9yofWzXCujXLehDR/DJhSFKBTnfKnbyzrglDkhoy\naPterFuzrG1SaDfiWKicw5DUE+57MXgcYUjqGfe9GCyNjjAi4riIeDAitkbEBW3qIyLeV9d/NSKO\nbKk7JCJuioivR8SWiPBdJUk91FjCiIhFwKXAWmAUODUiRqc0WwusrB9nA5e11F0C/HVmHgG8DNjS\nVKySpO6mTRgR8Yp9+N5HAVszc1tm/hC4ARib0mYMuCYrdwCHRMTSiDgYeBVwJUBm/jAz/2EfYpEk\n7aNuI4wNEfGNiLiozeigmxFge8vxjrqspM0KYBfwgYi4NyKuiIiD2r1IRJwdEZsjYvOuXbtmGKIk\nqdS0CSMzXw6cAOwBboqIr0TEBRGxvOG4fgY4ErisjuEHwF5zIHWMGzJzdWauXrJkScNhSdLw6jqH\nkZkPZuY7M3MUOAM4GPhMRHyxy6k7gcNbjg+ry0ra7AB2ZOaddflNVAlEktQjxZPeEbEf8HzgBcBB\nwHe6nHIXsDIiVkTE/sApwMYpbTYCZ9SrpY4GHs/Micz8NrA9Il5St/sVYLw0VknS3Ov6OYyIeCVw\nKvDvgfupJq9/JzMfn+68zNwTEecDtwKLgKsy84GIOLeuXw9sAo4HtgJPAme1fIvfAq6rk822KXWS\npHk2bcKIiO3Aw1RJ4h2Z2W1U8TSZuYkqKbSWrW95nsB5Hc69D1g9k9eTJDWn2wjjFzPz4cmDiHhW\nZj7ZcEySpD7UbZXUwwARcUxEjANfr49fFhHvn4f4JEl9onTS+73A64HvAmTmV6g+WCdJGhLFq6Qy\nc/uUoh/PcSySpD5Werfa7RHxC0BGxDOAt+K9nSRpqJSOMM6lWs00QvXBulV0WN0kSVqYui2rPRX4\nZGY+Cpw2PyFJWig67aoH/buznjrrNsJYBnwkIr4QEe+IiDUREfMRmKTB12lXPXBnvUE07QgjM98F\nvCsingO8FvgNYH1EbAH+Grg1M/+++TAlDSp31Vs4iia9M/MJ4GP1g/pW52uBa6iW20qSFriiSe+I\nuDkijq9vQEhmjmfmuzPTZCFJQ6J0ldT7qSa9vxERF7fcRVaSNCSKEkZmfjozT6Pak+KbwKcj4ksR\ncVb9uQxJ0gI3k/0wngecCfwmcC9wCVUC+VQjkUmS+krRpHdEfAx4CXAt8MbMnKirboyIzU0FJ0nq\nH6W3Bvnf9d4W/ywinpmZ/5SZ7lkhSUOg9JLUH7cpu30uA5Ek9bdutwb5Oar7Rx0YES8HJj/lvRh4\nVsOxSZL6SLdLUq+nmug+DHhPS/kTwB82FJMkqQ91uzXI1cDVEXFSZn50nmKSJPWhbpekTs/MDwLL\nI+J3p9Zn5nvanCZJWoC6XZI6qP767KYDkST1t26XpC6vv75zfsKRJPWrbpek3jddfWb+9tyGI0mD\nZ3xiNydf3v6TBmOrRli3Ztk8R9SMbpek7p6XKCRpQE23CdTk5lFDkTDqVVKSpA7WrVnWMSF0GnUM\nqm6XpN6bmW+LiI8DObU+M09sLDJJUl/pdknq2vrrnzUdiCSpv3W7JHV3/fXzEbE/cATVSOPBzPzh\nPMQnSeoTpbc3fwOwHvg7qvtJrYiIczLzr5oMTpLUP0pvb/5u4JcycytARPxL4C8BE4YkDYnS25s/\nMZksatuobkAoSRoS3VZJval+ujkiNgEfpprD+A/AXQ3HJknqI90uSb2x5fnfA6+un+8CDmwkIklS\nX+q2SuqsffnmEXEccAmwCLgiMy+eUh91/fHAk8CZmXlPS/0iYDOwMzNP2JdYJDXnQ3c+wi337dyr\nfHxiN6NLF/cgIjWhdJXUAcBbgJcCB0yWZ+ZvTHPOIuBS4HXADuCuiNiYmeMtzdYCK+vHGuCy+uuk\ntwJbqHb4k9SnbrlvZ9vkMLp08bS3ztBgKV0ldS3wdaod+P4HcBrVH/LpHAVszcxtABFxAzAGtCaM\nMeCazEzgjog4JCKWZuZERBwGvAH4E2CvvTgk9ZfRpYu58Zxjeh2GGlS6SurFmflHwA/q+0u9gaeP\nBNoZAba3HO+oy0rbvBf4r8BPCmOUJDWoNGH8qP76DxHx88DBwPObCQki4gTgO5OfNO/S9uyI2BwR\nm3ft2tVUSJI09EoTxoaIeC7wR8BGqstK7+pyzk7g8Jbjw+qykjbHAidGxDeBG4BfjogPtnuRzNyQ\nmaszc/WSJUsKfxxJ0kwVJYzMvCIzv5eZn8/MF2Xm8yd345vGXcDKiFhR34fqFKpk02ojcEZUjgYe\nz8yJzPyDzDwsM5fX5302M0+f2Y8mSZpLpauknge8g+o//wS+AFyUmd/tdE5m7omI84FbqZbVXpWZ\nD0TEuXX9emAT1ZLarVTLavdpGa+kZrl8driVrpK6Afgb4KT6+DTgRuC1052UmZuokkJr2fqW5wmc\n1+V73AbcVhinpAa5fHa4lSaMpZl5UcvxH0fEyU0EJKm/uXx2eJVOen8yIk6JiP3qx5upLjVJkoZE\nt5sPPkE1ZxHA24DJlUr7Ad8Hfq/R6CRJfaPbvaSeM1+BSNJCND6xm5Mvv32v8rFVI6xbs6wHEc1e\n6RwGEXEi8Kr68LbM/EQzIUnSwtBpIcD4xG6AhZkwIuJi4BXAdXXRWyPi2Mz8g8Yik6QBt27NsrZJ\nod2IYxCUjjCOB1Zl5k8AIuJq4F7AhCFJQ6J0lRTAIS3PD57rQCRJ/a10hPGnwL0R8TmqFVOvAi5o\nLCpJUt/pmjDqXfH+Fjiaah4D4Pcz89tNBiZJ6i9dE0ZmZkRsysx/w943D5QkDYnSOYx7IuIV3ZtJ\nkhaq0jmMNcDp9f4UP6Cax8jM/LdNBSZJ6i+lCeP1jUYhSep73e4ldQBwLvBi4H7gyszcMx+BSeqN\nTntegPteDLtucxhXA6upksVa4N2NRySppyb3vGjHfS+GW7dLUqP16igi4krgy82HJKnX3PNC7XQb\nYfxo8omXoiRpuHUbYbwsIibHpgEcWB9PrpLyYqYkDYlu+2Esmq9AJEn9bSY3H5QkDTEThiSpiAlD\nklTEhCFJKmLCkCQVMWFIkoqYMCRJRUwYkqQiJgxJUhEThiSpiAlDklTEhCFJKlK6RaukBabTznru\nqqdOHGFIQ6rTznruqqdOHGFIQ8yd9TQTjY4wIuK4iHgwIrZGxAVt6iMi3lfXfzUijqzLD4+Iz0XE\neEQ8EBFvbTJOSVJ3jSWMiFgEXAqsBUaBUyNidEqztcDK+nE2cFldvgd4e2aOAkcD57U5V5I0j5oc\nYRwFbM3MbZn5Q+AGYGxKmzHgmqzcARwSEUszcyIz7wHIzCeALYAXVSWph5pMGCPA9pbjHez9R79r\nm4hYDrwcuHPOI5QkFevrSe+IeDbwUeBtmbn3co6qzdlUl7NYtmzZPEYnSbM3PrGbky+/fa/ysVUj\nrFvTn3/Lmhxh7AQObzk+rC4rahMRz6BKFtdl5s2dXiQzN2Tm6sxcvWTJkjkJXJKaNLZqpO1nXcYn\ndrf9bEy/aHKEcRewMiJWUCWBU4B1U9psBM6PiBuANcDjmTkREQFcCWzJzPc0GKMkzbt1a5a1HUW0\nG3H0k8YSRmbuiYjzgVuBRcBVmflARJxb168HNgHHA1uBJ4Gz6tOPBX4duD8i7qvL/jAzNzUVryRp\neo3OYdR/4DdNKVvf8jyB89qc97dANBmbJGlmvDWIJKmICUOSVMSEIUkqYsKQJBUxYUiSipgwJElF\nTBiSpCJ9fS8pSWU6bbc6Hbdi1Uw5wpAWgE7brU7HrVg1U44wpAXC7VbVNEcYkqQiJgxJUhEThiSp\niHMYktRHOu3EB73fjc+EIUl9YrpVa5Or4EwYkqSOO/FBf+zG5xyGJKmIIwypD033ye1eX8fW8HKE\nIfWhTp/cHp/YPeNbgEhzxRGG1KfafXL75Mtvb7uKxvtCaT6YMKQB0mkVjfeF0nwwYUgDZLpVNFLT\nnMOQJBVxhCH1yHQroZyTUD9yhCH1yHR7WDgnoX7kCEPqIfew0CBxhCFJKmLCkCQV8ZKU1LBOk9tO\nbGvQmDCkOdIpMdz50GMArFnxs08rd2Jbg8aEIc3AdEthOyWGNSt+1hsGakEwYUgzMLkUtt2lJBOD\nmtZpN77RFy7mwje+tPHXN2FIM+RSWPVCP1y+NGFI0gDoh/uINZowIuI44BJgEXBFZl48pT7q+uOB\nJ4EzM/OeknOlqaabX5grrmzSMGvscxgRsQi4FFgLjAKnRsTolGZrgZX142zgshmcKz3NdLfamCuu\nbNIwa3KEcRSwNTO3AUTEDcAYMN7SZgy4JjMTuCMiDomIpcDygnPVZ2b7H367ieLZfK/J//6dX5Ca\n0WTCGAG2txzvANYUtBkpPHfOvPPjDzD+rWb/Mx0GnZaVdjvnzoce2ys5zOZ7+d+/1KyBn/SOiLOp\nLmexbJnLGXtpNstKO40kXKIq9Z8mE8ZO4PCW48PqspI2zyg4F4DM3ABsAFi9enXOJtD5WL+s9vph\n5YekMk3efPAuYGVErIiI/YFTgI1T2mwEzojK0cDjmTlReK4kaR41NsLIzD0RcT5wK9XS2Ksy84GI\nOLeuXw9solpSu5VqWe1Z053bVKySpO6iWqC0MKxevTo3b97c6zAkaWBExN2ZubqkrfthSJKKmDAk\nSUVMGJKkIiYMSVIRE4YkqciCWiUVEbuAh+vDg4HHW6q7HR8KPNpQaFNfa67Pm65dp7p25SVlrcdN\n9lmneObqvG5thq3fmnyvdarzd3T6uvl6r/2LzFzSpU0lMxfkA9gww+PN8xXLXJ83XbtOde3KS8pa\nj5vss6b7rVubYeu3Jt9rs+mjDsf+jvb4vbaQL0l9fIbHTZrta5WeN127TnXtykvKFkq/dWszbP3W\n5HutU52/o9PX9d17bUFdktoXEbE5Cz+8oop9Njv22+zYbzM31322kEcYM7Wh1wEMIPtsduy32bHf\nZm5O+8wRhiSpiCMMSVIRE4YkqYgJQ5JUxITRRkT864hYHxE3RcR/7nU8gyQiDoqIzRFxQq9jGRQR\n8ZqI+EL9nntNr+MZBBGxX0T8SUT8eUT8x17HMygi4pX1++yKiPjSTM8fmoQREVdFxHci4mtTyo+L\niAcjYmtEXACQmVsy81zgzcCxvYi3X8yk32q/D3x4fqPsPzPstwS+DxwA7JjvWPvFDPtsjGrr5h8x\nxH0GM/7b9oX6b9sngKtn/GJNfXKy3x7Aq4Ajga+1lC0C/g54EbA/8BVgtK47EfgrYF2vYx+UfgNe\nR7Wd7pnACb2OfYD6bb+6/gXAdb2OfUD67ALgnLrNTb2OfVD6raX+w8BzZvpaQzPCyMy/AR6bUnwU\nsDUzt2XmD4EbqP5zITM3ZuZa4LT5jbS/zLDfXgMcDawD/lNEDM37a6qZ9Ftm/qSu/x7wzHkMs6/M\n8L22g6q/AH7CEJvp37aIWAY8nplPzPS1GtvTe0CMANtbjncAa+rryG+i+uXd1IO4+l3bfsvM8wEi\n4kzg0ZY/hKp0er+9CXg9cAjwF70IrI+17TPgEuDPI+KVwOd7EVif69RvAG8BPjCbbzrsCaOtzLwN\nuK3HYQyszPw/vY5hkGTmzcDNvY5jkGTmk1R/+DRDmXnhbM8d2ksGtZ3A4S3Hh9Vlmp79Njv228zZ\nZ7PTSL8Ne8K4C1gZESsiYn+qCduNPY5pENhvs2O/zZx9NjuN9NvQJIyIuB64HXhJROyIiLdk5h7g\nfOBWYAvw4cx8oJdx9hv7bXbst5mzz2ZnPvvNmw9KkooMzQhDkrRvTBiSpCImDElSEROGJKmICUOS\nVMSEIUkqYsLQ0ImIH0fEfS2PC7qfNT/qPVheNE39hRHxp1PKVkXElvr5pyPiuU3HqeFkwtAweioz\nV7U8Lt7XbxgR+3xftoh4KbAoM7dN0+x64OQpZafU5QDXAv9lX2OR2jFhSLWI+GZEvDMi7omI+yPi\niLr8oHqTmi9HxL0RMXmb6DMjYmNEfBb4TL0L3Psj4usR8amI2BQRvxYRvxwR/7fldV4XER9rE8Jp\nwC0t7X41Im6v4/lIRDw7M/8f8L2IWNNy3pv5acLYCJw6tz0jVUwYGkYHTrkk1fof+6OZeSRwGfB7\nddl/Az6bmUcBvwT8z4g4qK47Evi1zHw11S3xl1Nt8PPrwDF1m88BR0TEkvr4LOCqNnEdC9wNEBGH\nAv8deG0dz2bgd+t211ONKoiIo4HHMvMbAJn5PeCZEfG8WfSLNC1vb65h9FRmrupQN3mb8bupEgDA\nrwInRsRkAjkAWFY//1RmTm5e84vAR+p9QL4dEZ8DyMyMiGuB0yPiA1SJ5Iw2r70U2FU/P5oq8Xwx\nIqDaNe32uu5G4EsR8Xaefjlq0neAFwLf7fAzSrNiwpCe7p/qrz/mp78fAZyUmQ+2NqwvC/2g8Pt+\nAPg48I9USWVPmzZPUSWjydf8VGbudXkpM7dHxEPAq4GT+OlIZtIB9feS5pSXpKTubgV+K+p/9SPi\n5R3afRE4qZ7LeAHVlrUAZOa3gG9RXWbqtNvZFuDF9fM7gGMj4sX1ax4UEf+qpe31wP8CtmXmjsnC\nOsafA745kx9QKmHC0DCaOofRbZXURcAzgK9GxAP1cTsfpdoKcxz4IHAP8HhL/XXA9szc0uH8v6RO\nMpm5CzgTuD4ivkp1OeqIlrYfAV7K3pej/h1wR4cRjLRPvL25NIfqlUzfryedvwwcm5nfruv+Arg3\nM6/scO6BVBPkx2bmj2f5+pcAGzPzM7P7CaTOnMOQ5tYnIuIQqknqi1qSxd1U8x1v73RiZj4VERcC\nI8Ajs3z9r5ks1BRHGJKkIs5hSJKKmDAkSUVMGJKkIiYMSVIRE4YkqYgJQ5JU5P8Dpg1LpAWtQ+kA\nAAAASUVORK5CYII=\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1064,7 +1029,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 28, "metadata": { "collapsed": false }, @@ -1075,15 +1040,15 @@ "(-0.5, 0.5)" ] }, - "execution_count": 29, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWMAAAD7CAYAAAC/gPV7AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xd41EbewPHvSNu9LuvesY2NTe+9d1IgIaTyJqT33nPp\nyV3apV9CekJ6pQQCBELoHUI1YIMLxr13b5fm/cNcenLcEXK+ZD/Po+fxSiNpJM3+JM/OjISUkoCA\ngICA/y7lv52BgICAgIBAMA4ICAjoEALBOCAgIKADCATjgICAgA4gEIwDAgICOoBAMA4ICAjoAAz/\n7Qz8mBAi0NYuICDgmEgpxfGsHyaEbDr25EeklCnHs79fIzpaO2MhhPw98jRjxgzmzZt3wvfze/sj\nHtcf8ZggcFzHSwhx3MFYCCH/doxp7+X4g/+v6XBPxgEBAQG/J+N/OwNHBeqMAwIC/tQMxzj9HCHE\nm0KIKiHE3h/Nv14IkSOEyBZCPH6s+fhT6tq16387CyfEH/G4/ojHBIHj6iisx7f6HOAF4N1/zhBC\njAGmAj2llH4hROSxbOhPG4y7dev2387CCfFHPK4/4jFB4Lg6iuOpppBSbhBCdPrR7KuBx6WU/qNp\nao9lW4FqioCAgD+146mm+AVdgFFCiC1CiNVCiAHHmo+AgICAP60T8AOeAXBIKYcIIQYCnwJpx7JS\nQEBAwJ/WCQiCJcB8ACnldiGELoSIkFLW/dpKgWqKgICAPzXjMU6/Qhyd/ulzYByAEKILYPxXgRgC\nT8YBAQF/csdTTSGE+BAYA0QIIYqBB4C3gDlCiGzAA8w6lm0FgnFAQMCf2vE0bZNSzvyFRRf8u9sK\nBOOAgIA/tY4SBDtKPgICAgL+KzpKd+hAMA4ICPhT6yhBsKPkI+BPwE8tBo6pZ2hAwO+mozwZB5q2\nBfxuqngKHe+3nyXaT9LoNCJxfTejYjNC6r9H9gL+pE5AD7z/SCAYB/xu2thCEwvRqaJVux2/e+1P\n0gis1HIOPv8e/HMugOyXkSJQTANOnN+gnfFv4jcp5UKIKUKIXCHEISHEnb+SbqAQwieEOOO32G/A\n/w6JjokUNGpoYDC6WoVx3RuQPRt0/7fpBGaMdKOWGbjf/xBPfsqx7cBTBt7KE5P5gD806zFOJ9px\nB2MhhAK8CEwGugPnCSGyfiHd48Dy491nwP8egUIEF+Lna6xcSxAPQ6/rYeV1kPPuD9IG+c8jqNCO\nPuMs2u56iqCK8l/ecNteyD0TDs0EY9QJPoqAP6I/0pPxICBPSnlESukDPgZO+5l01wNzgerfYJ8B\nx6t5y++6O4kXP3PQicHG7aikgBaNXjMeDn8Khc+DlCAlxrWzMTruxn8KGK+eibmx8Rc2qkPdPKjY\nAWmvglB/12MK+GP4I9UZJ9A+MMY/lR6d9y0hRDxwupTyZX7Yhzvgv0FzQsFNv7hY1s6G4oug7i3Q\nXT9c2LwbVg6GbVOgeukx7U7ippWrsHEJOpb2oFtxGHn9CJpiWyBoJ+TOAVc1cs8L6FoKdVFbsXW6\nH8/9TaSsWMpP3ouotULehUhDF/Saq5CrPvg3T0JAQDuj4dimE+33atr2HPD9uuRfDcgzZsz49u+u\nXbuekMGqN27c+JtvsyP4teNSdD9C+kkJ2cSgsG18+tFbaNLygzRGUyuRcYdI9hURG7SYHZX7KG3p\nz/cvWf+aMFJiN1Jc6sWsPkSrHk25rxdNTfFoBit+c/s2w0zFoPjpNGAFRTkTaaorJ3lYEfuWPEJ8\n6U4Sg8Fgz6GmIZaNbRcy/N2RNIl45JtNtAwwcKiiHmNNOOYx37D6phuoHDwUgCBDNQNC38H9DwPR\nWz4jf8bJ9DF/zpoiN+URfX9wPAavC7/pF2r8hAby93ua7ty8moLgMSDaz+WfsQwejwMHDpCTk/Ob\nb9dwrFHQ/6+THBcp5XFNwBBg2fc+3wXc+aM0hUenw0ALUAlM+4Xtyd/DBx988Lvs5/f2q8flcUr5\n9pVStu6TMucCKX0N0iv3tS/TfVJKKb1ylWyW46WuNUvd/Y2U1c9KefhsKV05323n8BYp7wyW8tCq\n9s91+6ScM03KF+Ol3HWelIWPSZk7Q+q7u8gm/Szpk3u+XbVE3ia1qkNSnoz0vy7kzl39pPR5pGwo\nlPKJ7lI+nyJbR3aXRXO7yjrXe1LmnyF37DhFNpzeV+oej5T1X0ttwSjpufR06bl4pvS9+aqUjWVS\nvj1TyuWvSjn3aSl1/bu8PnWNlE11Upd+6ZVV0in3yWZ9lax3Xyvz5WkyT54sK+UT0i9b/vXJ9ayX\n0pd7rJfihzY/I+V7438w609ZBn9DR2PF8cYv6Qw6tum32N+vTb/Fk/F2IP3oq0cqgHOB834U8L8d\nWFkIMQf4Qkq56DfYd8C/w2CGNa9CemdI6Y9mcNHEw0T6ZkP9UoiZhU4BGgVoIg+/8iyWqPch7Dyo\negCUUIi5B5IHwNUrYNNrkDEWVu+FOxbDorXQaxBUzEHWrkITgqCDUagZXeHoA6jZE4P+8HiUsTac\nzRYyLbeD1GDOubDTDenJmKbpGC0tSIMLjDGkuT9HvzCTtpsyCaosQxriMTz+JiK1M+LoUyYnPwRr\nXsQ3730qEteiDkhCqkY4eQ/kjoR+EzAYojEQjUFEYXTvIVQmYLRcTjATUTD9+rnTNGgNAe8wcMwD\n85hjP++eZtj9FnSZ+h9dtoAT65ifjE+w464zllJqwHXAV8B+4GMpZY4Q4kohxBU/t8rx7jPgP6Qo\nkDUWanKR5iRaqi5H1+rgyEPgKgDAxMUYGI1CDJr+EX7/cjDGQOIrEHoGFF8Aje/DwcXgSIalr8HC\nT2HNbhgyApr3IOs+obnvSETMM6gtyVD4xbdZiMzuiSHSAPH9cWnx2LblwBMJEOOFv1wKd32I8YzL\nMPtaqVcepiloPwa9DdvGzXiKStESragvvYSSlv5dIAaISofmUpoue5AdIpm4lgwS6xpIrLqYsC9i\nKSCTnRipJJlQTsOuTCSyZS+h+rBfD8T15fD+X+C2vqA7QEZA5XQomA7ekl9e7/saj0DaRBhxz39w\n0QJONKP52KYT7Te5J0gplwGZP5r36i+kveS32GfAf+iqD+H96fCKG8uwGixdJ0Dj5xDZ3vRbHH2E\nFRhRtfGIpbdBShWkjURbfAdK015ETDGycR/+6kvxvfcE/qwszE/fgDnFAj32c9h+PhW9BuKMqGH4\n6lex2TOpMYUTVF+D9Y3LkMMzcI96mspt9xD+zmzUiia8E/LxRj6DrH8ZdA3FKlHzoFEtIfqQE3Ex\nNESOQo38hDDlF7pU95iGo62EprTeiCUHYcyXEO/CnjSMgRttbB2t0kAZq5iNwe6gK4Ow+VYRZD4D\ngQBXLlh/1Cpz30rY/wrMHAVNN4NpAhhWQtxfwZT06+e6qRxC42HTEzDoRjAFHe/VCzgROsiTcQfJ\nRsAJ4/GA+Xu3dVsELNmHEHUYe1ei1E6GoB7Q6YFvkwhfC+y+CmNkJFqmQtt9l1C/LwFPUTGxkwHr\nAYzhfhTTq8hxYQjZhnLpbWD/FDrvJ0k1U816Cqz78QzuQ7eDBWxWPye6di8D0zU86yvw7plB0m4X\nrZmhGMLjseY0YHSMR7nhJfDXoC07g5aGUmJfqMH/mIpi8dN5+SGEfSZ0OZ+WzFSsc+5DiYxCdu2F\nknIpos8M1Oey0Af/DZm4FvFEZ8hdBG9+hO3Ftxg78nOk0t7m2S+8VAftxdd4NStNZRgb24gtXUF8\nz08IPrAKYkNg211g0+GqmyH2AjCntrcE0Suh+UYwfvLtj3E/a+4NMOJiCEuBffPBaIfo/63X2P8p\ndJAo2EGyEfBvcZWCNfHY0r7+LFx5KxiPNltXFDAZ0EcNQ4vcgRo+CFoOQ/F+eOMK8DShXhyJtA9D\nyXkHXyc39on9MSYW4HV3JnhITxRLJHywEM69BoZkwboHIPcmSJ4Fwfsxxg5iiBhPXzGcpuQ6gr/u\nRqIpm6DiOjwhoXgtoRjr6lny6Zl0aami2+pNaKERiL0r0W/PxHv/yeinZuE3ViBfEdTmZxFhbqRp\nso3w/GSU7JU0ylxMyTb0rruRIfNR9r+B4lZRgqrgyBr0rFxEqYq44QWwvgNjB9G85WOsqWkYty1H\nXf8x8TGdYcoEJhhC8V3xJLUXeTnQYwUDll+JGpqML/I0jD0nQ+xQUI9WZQgBahyYJ4HrXbBd+Mvn\n3miDuefDNbvhg9PBZIfoQFVFh9NBomAHyUbAMdH19mBa9CZY4iH18p9P17IPgnu0/73+aziSDY+8\nBSYzfPIKTEhHCz6CYuoCZiPIvpDYG275BBaejDn3G/DtQvgcGIwtwEGMBhdGWYes8CM9HsQYG3Su\nA98BGJIKW9Oh66VwZAnsfhZMIZiHPU60NQGpGhl5aA3sdCCq/dCzEveIEE4VKdQHbadiWDAGowlr\n90T0Ejext38BY3sRdZqH0obzKTgYxaSTJmBseRlvehPmqD3E6RUo6Sb8OFG/EajuNvT+IWiRCmq0\nC7fbjeXsWNSFN7PHcgZbBhSRsmcTk1+qwhddCA4TxnM+gaRuUDgSQ0U1UbtiiDUdRhzy4p96F3UL\nvyA2NQH2vwZIiB0AcVlgFqB0Ae/zYJ6CV7VhIvin16FTT6jdDkHREN4ZguN+sFgikeiIwBAx/10d\npK9QIBj/L/nqY+g7CmKmwNohYI6EmGmgqkgkAkGQUgNFMyHxWgi7DNwuOJgLVw2D65+DHevh0nT0\nNVsxeVPAuRvCRrVv35GCPHcn3u2PIzfsRKytRBqdKJ3TUKKHoPQ9jOL2QMwImLcKrrsBjtwCnT+C\n1kWQtwkG3wi1b4KmI00SzfcqspeO4Q0FVtfBBXFo/Xqj7ygkrPsIwrrfCWFt6AYbew+8Tk3zFxya\nEk/nvGJM860k759PorMVWlZgGjkash7DE11Eg5xPZHMyWuuT+AYq2LY0oOrvwvOXEjWtieb6FKyh\nidRclUNI0jpG25LICNtKuWMk+XoPek/Vka5Z+HMl/mIn4XYdUVBBddLzxOb6aGt+BvtJk8ARBRFn\nt3eUOfQXqDsC9ihwngLSi+x8HnsTTmOAciNIPyDaewJKCVWbYdgdsOtTSEqGqFgkkgI2U8BeKkY0\ncJBabJhIIrS93jrg99dBomDglvy/xGKD6yeDSIOuj0HNetjyBS21z7Yv19wMD3oB/ClQfSPU3Al3\nPQrjpkFrI7z4ENxwP7rBhL9bEiK/AJzZ6FVWfE89hnfWOfiuvQz2NSGn9cH05VZMC9fDgyMwhDSj\nVJsgdggseQWyt8Ohm6HzO2BwwMBZkLMUmTsOWX4nmn0lmvdWXPIdRMZs6GKH+xTEmIuoGDoLf68J\nsO4DkBKfIRtJKZ3WNNMlKI605mz0jCA481I44y7W97gBLl0PWY8hkZSyghIRhQydijVhK1bTa9Ba\nAO9MQJ48nNBmJ5tdoXzcfTRHCqYSs/N8zHTC2WSkukcF9c2RtPlOhfRozF0aCQkXKDM0FJ8kdLeD\ntuAg3PnlBI0dAXjBNRdanwCLhINBUDkU+j4EjW00Nx8mYt3f8e8cDb4iyLsS3CWQvxQ6jYJBs2D3\nPKAMdj5HMQf5is1sQLI9Pol32YV+9Eb6b/lxj8SA/5z5GKefIYR4UwhRJYTY+715DwghSoUQO49O\nU44lGx3knhBwTFKyoL4Kdq2EvLXQshN/1CpaYqsJtlwCZcWwVUJOBJz+JHhXQLIFvmqGSx6D1x4A\nkxevQYO+58KcO6GbE+T7qBNmIm6+A6GqaBzGy9tHA0Q3dFMReIPbg645GZxe6B4O64uRq2aAbkD6\nSyC4GlpDEGl/QQTdhKdkDEH1NhTTBvThvaByK6LiXcrG3EH0iKvh2Usp3fMq8X1mUO/pTbMU1E+6\nh36541D2vUfd4bW4zGGYkmLg6DCaq/iEPXIXU4u6YExt/4Yo1p4wMRsSH6a2aRxLYxzUJ0XS4yWV\nPe+WsPO0ePrnrCNn22m0FLUxdO8K9rwvaPaEEx2VSWbNN1gzDIhWjfIiFxZrMNFdgxG1n0KohNgr\nwDqivYrIfzsc+gS++Tucvooa+Tlh9bfSdJ8Zx213o5iqYWdvKBkCU19BO/ASBcPPJKz8r0QsKOTj\nSWvoG3QS04nhs9XLuG7iJJR/NxCv+Qj6ToTQwED9v4nji4JzgBeAd380/xkp5TO/XzYCfl9JGfDC\nclj5GbS6kOZUcO3BsRxQpoMjGrOnBblkHhTXg60ZJp4CNcmIebvhmfnwj1tw3+EiZN1hqG8BLCj9\nn/xBqwCFOHRfEUg3Ys5UdMdKKhZBXKgEjwtCI+DUOyClJ0IWosc3Irwgaj4F6xRkXRl6YSrWxlqE\nLRWMyaDtBauEljYG7NmEod/1oDUQ8uWDlAR3IfwDL7FGN7Zdu2nuewlhXRZhU3Joan6Zznkr0BZM\n5XCcDUfmOEY3lpDxxT648bvxNTRzKnU7yqgpeJ9uo3txKL6RLn0NGHzNWOJ3k2zLJ39wVypvHsie\najfZhWOoN47nzE5lxB16G6VtOU5HHG2VDvLGOZi2eC08shpSe8PDA9sDMcCwh8HbBHtehZ6Xo0RZ\nEMF/xXH/GmouryBqzlX4uJm2+nzMW/vg2WEnOC8OQ3QTarXOnbUjIKi9e39kjfguEHvb2pu+NedD\nSPovl4HNC+Gt2+Hd0t+2bP2ZHUcUlFJuONrh7cf+7TqnQDDuyMq2Q8LA7z6rKnTtj/bKg6hRVTDu\nMLotCEteBNKTit57PZb0WuRQFWpWIDwSuV8i9F0w9a+Q0hU5chzmBc+iVG2GRAO0jfxh8yzNg6hY\ngal0NTR1gbg2VJFEW7Af7n4Z9q0BmxWGTITQFGBIe11X0xZchWswDOqLJ64Fg/dmDPZbQahIvLgX\nf4niE1j8dgy5X0NGMRg0guqqWNm2gaF704lOacK45mucq9eiq32wJvejpU8DlroaVlx4ORPmvYq6\n9XP8UcnQUA+uOrBGULtpEzmPPUbGtdfRvfcbZKo5rHPVMLbzQUSxB0JdaInj+Tj2Vj7oGo7x8VVM\nvLA/Vkc8vPw0jDgCH9kwtJiI0CWxDxWjPPI6PPMoFB6GRa/CyJEQ2x+MVhj6EJSuBWcNDRSSaJqF\nsnMj9hvSqLztbuTUKUTELcDc4CGooYHm8UOxemLAuQ5Con/+Wm9/FgbeDAeeA0dP6HLFT5vNaRos\nfx26DvtNi9mf3on5Ae86IcQFwDfArVLKpn+1QqDOuCM7MB9WPfCD+kEpJWWHmqAkAlfMdIRfIKIL\nEaXzUPK91CzphvCNQaT8DXH6FpTzdiAcQ6HfPnA14B+WhNHqhKY0mJYIfS8BZxkUvAGbz4etF0P5\nFxgbHDBqA0QpKFXF6MZqcH4Mte+AJRuyn4BdD8OBF+HA2/gvP4eqtnq8oflgDscSfAd+3U0ea1nl\neYzC1HQMtuk0xBjZPSKeovwzkWoDTj0dQ1MUpqlXozx5AHndP5C3jCHvmk54Du6krMFHyJFmJr//\nHobkidSfezP+aW+DJtH2n0LRc0Oh5hqGv2whos8XbLar+OQ+Rh2sgfBJEDoT3vKSvT6c06wGgkQE\nJr8Va/xwyN8PuzbAkh1giqcuKgUZnoJ9UC+ICIGps8DYAE1PwJ67oX4d6F6wx8GE2bDyanz+IkxN\nt8KhZZiHjOZI/0HErNuIKWoKYoUZmRSHMrwYYQuGMCf+bZfhz33xp9f64AIoWApJ02DzVVC5+qdp\nPG1gssCtP/6PGDiSD1W/Mu5zwC/77cfQfAlIk1L2oX0cnmOqrggE444sfRKsfRiKvns9kdbURMWy\n9fgK8mht3YVhfhRkT4LuLvRwnV1jJpB92ljExBvBrEBMd7CPBv8p6Buy8K5+BdWeBUUt7a0C1t0I\nOU+ALREGvYF/2Nu4Q5OR0tdeRzwgB2E/E2GyQJQBOtdDgQp5CXh2tJBXXsJXFcvYfHUawX3HIlsd\nmLkBiaT+4FPsYgGm0u1k7N+P6HIhqtuNweWhJiuV3AsnI911BG1eTNh554EQmIynYmnsQqurCnfN\nGrqVlFMfk4gY/hDOMdfQGFuOwd4F7A4I2U7MsJ0E96+hqKiEnAd30+uWNZjzW1C2OdEP74WUQrS0\naJpM5Zy/7Blorm8PaABNle0/um3ygDEUWZSL7WATIdMmtC9zb4BZfaGlGhlugJYtgAL+PLDcjFSr\niN29EewPgjIUg306yjn9wVtA/bkLYMKF+IPctJhDUKs+hd46asSpyC0vIp+9GkXztefD2wbN5VCV\nDXFjYdjrUPwzQ7csegGmXv9d/v+prRWuOQ2sthNRCv/4fuNgLKWsOTqQEcDrwMBfS/9PgWDckSUN\ngxkfQOHKb2fpbjchY8fQlqRiX2KketpEdlw0nm+6DCfP7KCvYx7ds9+DpzPaB6ipyYbEJMizoVdJ\njD1BlLTBSf8HS+Kgpg/kBKMZMtmtLuMDrmCLYxO1CT2pMpbjM9oRvnqEIRVqD0KbA0IHgK+J2t3L\nONyUTfawGOqCk9iV3IX14Ra+5DM+8t/Jitj9KLIHtdLGoglj2C5vxpRXQ7eVh+j7mpeUlYKqiaGI\n6Y3ULD0X/cnzcP5jMpYXn6T3Ux9hq3LROmYmhRmDoK0Gf30uphXroboUhk1DcaVgTdyB+cAQtJg6\n3rzrfCrGxKNHJCEdzXhlKR7TFpbP7sWRW2JwD81B/uWU9mC27CmObJvNl/dfQcutN+OqOExpbBTe\nlevRXX6Yfy+MvhrcKvqsjeQfasRfeD/kToXys0DrjOh0Np3WFYBb0D7kikb/xS/hLDXhalbQel2G\noUBFM2UjazIgNg7R9CRi9FXIbe+RFrEc6akETUBIFuxaiY5EdrkEjMFQ9r2X4rQ1QeFu6Dn6p+Xk\n1bvhyCEIDj3hRfIP6fiDseB7dcRCiNjvLTsD2Hes2QjoqAxm6DUTFl0J1Qdoio7gUOx6tMfGkx9m\nYvAbFdR0gtTaXOzeJjbFOCjMG8rJeRthZRv0qAXtXmjrDuX51I4dTbRuhpY9EJoHxXvg/Nth2QTU\nBU76XHA/GZ4sWHUJIjmEIr2QQtcSejp34WvwUm6/lPjofnDek3D9IBLSx2KcMJ2Exg1kfVaLuvcd\nKruXkd01nkEHQondvhrGT8XzzQZau7kJdzYgkwywFgxTEjEUltLp8yrMe4Pwj85iz1l51JkNDD50\nN8GhVeifLqdF+QwREww738f2UT3Bu4oR/W4GZzlMO4xz+QQaBtv4R+tNXBj5Hv5z7ai7C5CJTZhb\njfijgmlxhpNR2IZ7YDXqSVHY3pwDuQkkT+7Fq8kjuL5rH252JDNh4VzihkRg2vQ8aJWw4z445UO8\nYYXEdDGh+j3s/WwQPe+6BtG6ERo/RMg2WDwSXOHwThzqqmY2PXg6SSVmYmZPQ0k0EbRdojXlIyos\niBYfavJ6vH9LxdTYjEvrA24vyohgjKsVRGstOSHvkdbnSiyrr4GIAWCJgIXPwek/80IA3Q2tc+Fv\nl/961+yAX3YcgwAJIT4ExgARQohi4AFgrBCiD6ADRcCVx7KtQDA+UaQEzQMGy79O+y/oI7qRn3Me\n30T2QRcW+sf0xfF4DtEznyf607thbAmE3sKoI91ZH/wqe2deT88pVsTsB6GmBNwbkVPSMNVaUMzD\nYG0jnJUNd14A1uUwRINVc2CZgaAl70JkNbjL6VZiBLUZzdOCQelNxKED0O0GuP9MiE/H37idIt3L\ngL0T0MZm8M1kHUU7zNiazzE010Mc+IKfQB/dguNRgTgNZJtA2w7SthQp0lCsEntLBWGvLyIiU2PT\n2cNZ2/oJKT36kGpWCKEPrYnrkNmg3zMD/8o2lP3luFbXoC+SmO738I8hjzDStxOzcSImYzoFKSUk\nbXkdo7OMkhQbqeQzILuZhj5x6PoumHkL7HgFsbqShzf9ldi/vM+qrBQG7uyOceBUWPY8pAH1u2HZ\nY3jPSsbW1Eh9bRaNu7fj99owhk8HfzbY9kJjE8gjUOUDF4T58vAO8dLYz4upzIcaZEFE2nBl6qgW\nI5LPQTGSGleI0tod9dEcxKwpaAM+RXeOJMaQwH7LPLL6P4Bt3UWI191QfACujIKsod+17ABwHW6v\ncrr1mJqyBvyc42tNMfNnZs/5nbMR8KuEgLX3QnQv6HH+t+1k/11S18FxJolZjxPtmo/V25mmOA1n\nuQ9SHoUPqiHBCuNvQhkcSvoljyKv2cKWLgZSLowl7q1i+MCH7FSDqVM0HNjf/mU+bIduh8BgA58O\n0T1hXx48mwPNe6F8EXQ6Fb55kKb6eJz59TSX12FfNBNroopPJFIb7abXgX5U6bnsG+6lt7iIaMMF\nEHsfWv5sWt5+lJYr8gi71Yplfyt6ZzO6W8cjQFldgm+mhWBjF6RswaOG45dlmJZIwh/dgsXxDb4s\nM6EvxrHvvFiarHmERd5Pw3mTqOErUi86F+/dvanP7UdP2Zezj+zC3f02DvAWXtdO0ovK8Gy1sGdU\nfyaVrWBd5kBqGxOxdAujp9KNuMZOmPLLMBQ0c80rT+EbGoM7MZqvWzeS+cxeOn1yDVgXQ8J8GovD\nCDal4ZhyLz27Odh88cUMf/YOROX7KM58NKPAlRmEu1sa2uQa0k2HqZfRhBjToIsHp1KOPsSOMa8e\ncbgSJSwMJcaI0VmHTDoTV9ELqPYjaEFdsK7PJbTYSJdyF4b6WehpTkQ0iFE3IC6++mgvv++VpVVz\nYdxUsJ3625TbP6NAd+g/gT6XwevdoekIjLjvX6eXGtR8AWEjwRQBQM3cuYRNGYLP0R9dX4PuasZW\ntwzHjCqoiAFjC6ywQe51ICXRVYcwLjAQHdnKmsmjOXhyPGOq16PtqcJ/4UzYugJn51SaDDYaNQ96\nM1ics7C6ytl910xcxuWIlv2cVLISa+4C8PuRnlA8lcWEFmVS+HgWWc8t49Ckrqi9r8X22ieU9U1g\nrP8pDMQhcdJc9yy2ktmYT9OxD+uKnwq8mhdvbRdkTTn+TtFYm8GoluCPSqR5ownFuY+a3RrWmyOx\nP/4Cppo8wra+AVFtWLaG0xrhw3zkbRzx09DNjRSmv0fsI1HUvavzf/v/Bj1f5whrKGQDp9T3oDV9\nH807PNj4HvKBAAAgAElEQVTMmRTU1WNLj0dxqkRaatlmD6Ji9On4+usQn4mjtpUBez+ib5lKy+gZ\nVC25hHXnPsOEz6FyXzXvDZzEbdrrxO+8irC4+8i88QqKcmdiPqcZY4kD1d4D624vjug3UNs8oDUS\nWrUWNbg3YuWXKLm5GPxORLoZ/0k9cSaNxc6dbP7iYQbWP4b3ah+k7iZ41UDUr/Yjkyuw9XPjSg7C\nExVJ0L4GfKNrMBz+PwhKxh8VhsY+DEzCuOQNxFPr/uObfQAdJgp2kGz8QUVkwYzPYc+bUHcQIjJ/\nPb1QQbHA2nhIugYyn6H288+RukZMj8uQllm0+W/DYnXSnGfHsWUPwhQM929p39f2r1EWfYy3Wcdo\nzqfb9l6U+I/g9lZgphXz1wtwVpWw7OqTSViXR2tkPL1W5mIK6YV2098ZL4IxYUbW3YJMEpCxFl7p\nRNiwDCLqhmB68Q3Sm3dR1287nuhgMo7EY29KZMC9C+FuO0yejV6+heDF94Fboo64HWX81ehvjcI0\neTDms7ohv5pLTV0Xat/6mug9rZQ4CgkZaiYsUcc8uDsF6R7SBs9i3RmnktTqRDTXMbBkJVqsgks+\nRWXiGoz0AT2O3NA4+uZvQjSfD6YI3DSQzlT0kB0YxEh8xs30U0YT3BrFzujDRNQV4DDaGFzuQCx5\nD2QE8rxUnkgMpbM00RjloCVoHSFxdYz+9DzeGHwpJ/kWUGGO4/GEB3AkCE6pmU1KUB62FgOmfDeO\nz0BJsELZYTjwElxxO3w9G+OWOcgUED0VVJvOoQETCG0tJrwihzZbG+4YQUi/XJrz/Zi3+ggiHjXt\nUvyPXYQWlI3IW4S5Ihdtcwt4VUwr34ZwI3rrGJQwP2pBLnQLgUgz2H+uz0HAMesgUbCDZOMPLGMq\nJI6AxRfChGfB0fnX00dOgfSHwXkImXMnzds2YggNJcZdheg+jaDQwWDsTtApj+Ns7ozNeibig/Ew\ndAye7N4Ul0Rh3LabsGnx2Bu+oTSqK+WPjyYut4zuhWbUzj7G7FhLeL8HkV9tRmhNMHwMiCgAZOUS\nNP/7yD4Xo+5eAQZQYy8npdsW5HPXonS3YGx0EZd/GJk3G3HlHPjkE+g8APJOpbXegjLUjC3fgBqW\nhFx4K+5BPTHt74Nc9xTV60005C7HnOJH8ZpJmXkR8sIKuH8pilaNITwV5YFUklNs6AVm1DG3sXpn\nNmOCViIdHtzGQqJ5laKcVaQHV2C0euGTT2jOOBM9UaOvfimNvEOQNpQkxUZR/RyMY65luLgVLgmh\n8ZnOiFWPQ4YC6iEonsOZ71bSeHECSQPubB8Nr1ssDGrlzs+mszAuApsmGFq/gbElJdQlXsbSmF5E\nKH9n7PwltJ2hYm/4GhGjQ+YYePgqZEQDcoqK6KehH06gxe0iM/MBxOcT8UYMQ1H34G+ag6XNgWt+\nPNooSdDmYii8Fu/YB7BFPgi9HkSPexqZfA/Vhhiim/qjeupQ002oebnQJGHR5zDhnBNdgv/4Okg1\nReB/mxNJSpg/B155FPZI+OhMOLL9p6+d/6e2epA6pNwB3d+EsAH0uruW+MyvoGwH1B9GuHMQOUsR\nlf3wutLxJX2Fds5DuBt0DPseofT2i4nbsYvge94lNKyIgeENiPJIvA4zxfZYrAY74VYbfPkOYujp\nkBECB7/3UpaoEWjdB6GoE8DdBKYoKKlFbapCNNUiLF0I6hJCZOQRyk9upHH1VZAZCQd1ODiFkL05\n2HKCUY2tsHYdutFKk5oKjkREiIGYfpKs+5ykPgKG2zMQ+z5ErdbhdIke3IIh3gHmNJKDU1C8Lug8\nhUpbTxqmx2EqdpP0ZV+kptJr8dvY1GD85ghaEhMpLHuFloO72LtsEq7i/RA7DKXTBFK2zaPG+xRO\niiC1B8LlRD/pBshLhZ7b0ZoGEe5rwtB9Gjj6g8cJq28ERcWQ0sjpBxdw0/yn8MgEYvvPpbs5nIs2\nXc/UVYVojRGowo8z0cyRuCQKau5j36xSXGdno7WAqL+fQyOnU3+SDW3zSbSMNuNN8xG+Jo3wDelE\nucsJqS/DaO1BXZidvQN7c8TzDMUHzsWjN6AcCsXY0odIfw0HO9fh2dUfGf4ODLocLjgJMtpg4C3w\n9Ts/6hgEK+uh9zYYvgNW1J/YYv4/77fv9PEfCQTjE0kImDCd2uKD8NUaWLgXOe9ctMaf9sCSDdV4\nFr8C9RdA8/1I7zfIuKk0Nl2PKb4Jgtqg92lQNQiqMzCeugKTko0h6i2UjVeiha1Bho4mqu0gxl1P\nUpVYRXnfUUQ791HZuyuj8w/SIyIapftT8GYOZCmQugbGfQjK99r2KG6ESMagTEKmj0T2uh7OuB4e\n+BhufBpRl4/SfBGG0mAyfBdSPaSGunPs6HF5yKgQRFAMakgseqWCrC1ATTmF+akDaC6fj7OrCecU\nAck9wWJB5OSiJOqwbDmyZRjOCDOOZW1QZsOQPAXpF+jOZpL7f4FqbqMsvBPKgpfw3RtD3pQq/HVl\n6FEeDszsg9PdTKW9ivK+dpqjp6CnnYKWnIa3NIn04kas1Ucg3Igh42I0fT9U1EFLFf6tixGXvIBq\nPzrWcFhnXHW7qZ87FG9bLoYYH/ZUhVPWrITCtdDcAGpX5O4CVHMj2y1jmFt9Mo759VR2DSMxpAy1\nMQhjrweQS+qIefJLkpzVuCenogTpqHYT3mnJaKMcOJUI5F1+LEO/wjqhgcTMYhJ6HMaaupHW3d2p\nCrqXspRu6J2/JsW9kzbxCf6tW8FxL2zqDpUpcEVPCImgTRcsroXrcjXO3tHGqrVLyWrO462gPUx0\nBEZ4+1WWY5xOsEA1xQnmDAnizafv5s6CR2HrV2iNL+HPvxXDwQo454Fv33S4r2ExYfsXkDT9U6gZ\nAv58GoK3YuzTH6fhQ0wNd8PaCdAQCWf+DXw6tRuSCBp6CyGiE5aKVqpuzqa4dhiuiHQyl83F7ktB\nROQz8puF5E7qTkbQA2i3no35wSXQfwp4NkDjFTDo6vbHKSHw64tQ1dPQa2oQfj+urouwyusR2+bC\nvGugUUEkD4AIE2ofKxnJGyno9BjO+jrC8x7BMjAc0ZaM2JiD3qMFNeZhvP4z2fV/LlIiLBhcOq32\nwSCGooTvwGDKQt30BYb129FcNiI3z4dWM1jKELGd0G4fhKNbTwyz/HR2DYaBOVjqmuj6zkYYKtBq\nYxBtGkP3RZGdmk5yeDXB1gdQMOCM9yAax6AcPAJB0+GqpRgsLfgbn8eYMRRevxpDbjXMCsaAG4Aq\nnLzXfxwT9y8lM3km/tqlhNha2TDxHqa9OoOijCzCc2vQw00UDoknvWA7I8pr0E620NPXhrO7it29\nCLLfQuu3mFDZjP5ZBPbXQ2DWhZDjhBYfdJ7Ozi0H6HSuhogOQ9Z/RvhBH6a+D2KwTKQtZQOGg6uJ\njZ6NASta5MuIkRfgfvVKjOpdyDmP0BIXTnFsL/5aFoG1bB1jnQe4151DrL8NCjcge5+C6HpZoP3x\nv9JBqikCwfgEe5cDVCguyBiCTO+CzzcXURcPC1+Ds9+D6VfS+H+XsyL5AOdhgBodYjYhvHuxte1B\nG7IAd34XmuwOQsV1EP08LLwE/9ehEOQkbKsRtbiG/Mx0bHs1uudtoVPOGuh/FgycAMs2klnZiFsv\npSZ5JGqvRsKL30ft+h4YT4eoOYjGR6BhPZj6oJlXYVLfxv3kPVD1Dfr15bQa+xKUm4HSeRI0lYC+\nDVIvgcbFiOYYOnW+kPyF4/AnxZMigpG2nmiTq3AObiR4UwuDVq3l4OlTGHnoAbSQCzB2ugoZlIm+\n+zL0+HTYng7FtegjdOqn6ujxVhRzOMaNzYiMCUSXL0E0gNuUixYLFsUJcRL2C3RTAwneVqp7m4jY\n/Clt5wTTyD8A0ON3EL6mAT20miDzDHD/DYP7QZz+bBh7B/TIQTxYgn5kJ2paXySSA9QxKe1auibc\niFzaF3+KD4du4kh0LPKsxcQvvw9joQvpbKHvwmpEtBHfOAOG4fn4Q24kPO8KDIoBXDtRZRW0JKCe\nNAV2qrArGxISYMtn4AinJWoAvWLPp4QV7MlcQ+JDu/Gn9SE8rB/hW5ZBt3uhuRQqV6FGDcPQbRe0\nTEG6iiiJCGXpmXPo3X8IH4QZMLTkgbE/PH4rdO4Jj7yEMJigMVBH8S91kCjYQbLxxyRp//dwEO29\nI4UwIUQq5thn4DYnzLsItm2kbccnhN03EVN0OiQc/YHP0BmTqRtlHw7DMXYJTRGNFE65iT7NS/AX\nPI365ecE3XgWGIchYv9BxkcSZl5GWf0nuJL7g1iPtvwLpNGFvVEDkwvbrnpK46x4svaSoOkIrCBM\nyPBnEA33IGvPRcSMQzGasf39Kcidj7fyFnxV9TizdWwpX0OoQKwORaaZEUs/R4wLxrjtVeKfLeXw\nrXaqvzxChG8dhjArpiqJzI1k9Kpsdp05GnHYiLFxK/TegtjyBuq2AtSY/XDLe8gwBxsMd9F1TyGd\nmiQtoZXYuukYo7bjTtVxN2XRkuTF7y0mtsmCSY/HPS6VoI/Xk/DONvRWhdYJfrxuQZx6LYoxhZKo\n6wktyUOkHYHyb2DQPJS9Z6EZ68B1NZp5Ip5uGfhdtagblyOGncxYcfSNz5oT975ILJ8VIgY1Ip0b\n4NPdGNwF6CNiUTU3IsiLLoyoMYmIgw8SZktHaaxEvj8eGlpxntmboNM2QdVCcLwHSj/YsgLOewGi\noxh68FnwnkFr/av0LWmlpm8CGU9cAF1HwsF1UP4l1G6CoDgI7YXREIIa6YONz5E8rDtXDUmH4KNf\n4fK1sHIuzF8DG8rBYIJ1X0JtJZxx8Q8Lpq8SjLEEHNVBomCgzvgEErqk0VfPOSsehC/GIw+9CSgI\nEQNhqTDpeUiB1htuZeTSeiL2HPrB+qohk0Z9Eq27onGk1OL2fojetB+1uRBxspHIQ19g2PwwepUK\nZxZB3zvxdLLD8ApqB3t577qx5KYnUBRtpM1to8UdSWJeOsqGkzgYehJ19plI06ntveodj6DFPozq\niQatAdnwEeWex1G3VGCNnIvhqgLEOA+i2QumRrQ1H+NzSWTdJ+iJO1AcIaiuOJaffBKuYCtceg9q\n91tB+KFbKpkunYN9/g4lCfDWE9C0F15+Ey4YAl36IEKjidlbgT1tMIbqWqL3dCVoZ2dM8QtoIpnw\nzcPoVHkdac/FEpQ4G4NWj/LMZmS2Ab85GJFVh6WsleA3S1EOH0Zu/4CQeWsRhbtheQPkV8OB9Yia\n07B6NWg5jP/VZRiKXkPT9mFoLIe/ngF1RdDWBrOfwpQfijD2QfZUkf0UvDdaUbIGYKg0I9Lvxh9y\nDlrUIMTkb2jpa6ctqRTNshDOcOOeZsfcIuHhFOS7VyKjLbD/I7BqMPISMCYRXXsA/Q07SRuPUBHd\nj7I+3dkyMhPqDbCvEeLS4Kx3oN+F0Lgc4uPxFUej9XwILlkNwd8FVFmwC6kuh9dmQ1h4+yhud8wE\nx48GoNedUH4Mbd7/TNRjnE6wDnJP+AOQElpzwBwFpiikx4N764eYRvVEHTEb1l2B1vYpam4t6E9D\n2gxI6gP2SDo9dxOGx/OQR25BFuSjdG4fXLzW9xqhE/diyW7BssxOn9Pm4/QVEhzeHd/GEpzTHibP\n+SmqYsRsFfh9V+If1YrP5SRI9zGtdTimHhOJuOUq9O6t7MzqwaCt64ms20t8/U4qwrewn2uJKs4i\n9uNKlLAyhKENLeEGtPh5BK0XqD0uAVcMalkbUgWlVYf+yShdRqHX7MUXUYjBcQPqyEa6nXI1G7Jf\nJr9mFH1TbsIIcM9fwOtlkFbP4rZVZPXLBKcFjqyBN4fCqQ+3n7/sl+i0s4qEPjYUW38UZ3foFIlc\nPIDQrhbEZX+Fa8ch/aVo69/Db7Jgm96K+DIM6QzGV22AVB1jsxvenoIWrCIHjIOifTB5AFz0Jay/\nEcQpmNVbQILf2oj13r+T13MtqjTQqcoF90+DAV0hogUmqWgxDqRJJaqhkZaFpVi+2QTTp6GPuwFt\nYTe8w2bg52o0Uz42/wSUuAz8tgW0ZNQR4e8EU69HmhLw172MKfpsMBbDlkmgBlHapR+Z7jTUg8tI\n3/UOC8dPJ7yrg+LSrSSbgqAhHVLOgc5miBoIC57ENMWOVrUW/FeDvwZqX0NGno2+6w3U1LGQ0af9\nfHo90LkbxP+oDbJzN9R9AAlPgiHs9/p2dGwdJAp2kGz8Eeiw5zJInAyxF6JvLaT8xUfpPWgRWCNh\n0jw0/60Y1RvbRx3Lfh5ai/F0TSEvaTKZswag11ThTKjEuD+BhokZSG8hEV+CUudHjLgB0zufoWRt\noS35AF67hcLW9/DFZhFhGkfihgUYu/UmZ3sFmd0j4dBicD0NGV2gV0+U0v2kRNdS/cARuOE6IqvK\niN1jJ2TRN2i+eWhBAnWPE/eUAZjq9mMMdqGGCVj/JvqOzzB4G/DHGPBH2VGVRjxddqMk1qBG/gXF\ndC6y4a8Iu52zQsbz1vUR9C7cgZLWv/3UmExEEEujNRb9/15EObgKdoRC+VzY8Ddo3Q5f55BsCAdn\nPqLHzfDRHdDjNvR6HxEFTehvjcA5fhe2D3w0de1HxfDx9FjwETLxCP6+MZSMcZEU/hn6369Fse9D\nGd6KRRZCVwekOkBrAnsyflsDhv0uUAzIulqUQdOwU4WFMOQlM5B9V6EsegPtmjG4TfegViVirv4/\n4sp2UWGqIVLRaBlqx2uYiOmUqdjM12Foa0XHg1L3ObRtQReSoMQPUJVh8MQ1iLZS9Jk7Ydg1sPc6\ncIVC9kHSG6tp6VGPz+imqkcUy+RkXjj4BkUDYvE7skgrOQz5z0CnG2Huh3DWi5DaG/+NUzB8PAHi\napFxYVD7EEqIDzwV8PFoGPYQ7AVuehQye/20qNp6g786EIz/qYNEwQ6Sjf9xLfMh6FRo2AymZpBe\nlOSzcGwqI3rWtciPViAUBakdQjF2gthOEDscpE7Orr9j+HoLuBuQM1KoOrMU34FKtIIj6G1Oav+f\nvfeOrqO6+v4/Z+b2ot57saxiWS6yZRsbGzcMGGyabTqmV4feIUCAUENLgIBDx4BxB2xcsI17k9yt\nYjWrd+leXd1+Z+b3h3gSnrx58xR4A8kvn7W0lmbmzD1bS2d/75k9Z+8zNRdnSgzG1nUYL5HAP56B\nLAVlqI7kkz1kLN+KYfRUcJuheQvupmgIlEG3DQLjoC8a1Ga4ewoRu/eyc+Aw4yUd2h03IeuN2BY9\njhb/EYFv65EyJ+G/dCht3e+Q+PtsRFIdylAT+qU9qLMk5DVGvO+lord9hGXpLTDqdig9Ds7r0Oqc\niKMriJ5yPeO3lfKdupNpFKOisJsdjGcSeSRQSQcFx9dAyTzwjQKTE06+CO5i8O2DHQpq3TqEbEZd\ndyXBs33I7RJqdBfWr/z4SpKwlL9O1tm/wTU1iJxvRefYRVA6C33LW4hFX8D9OQTnxGLc0gazbZB7\nHlS9CW0b2RVtZYqzDDU0FhFWCEB8MI9wOY1q5x1kj3gOzWxDvHoTpvtWQGwnQctTxA+Yac5JJWfK\nTKy2q7G98STSWdMgsAjc+5AS7oTY60BxoIt/A6MTKHsS9LsRzkpERxSazUxwZimdbMQ5bhfKqiPk\nnPcOknMdbnk9joqhxESMJ9pZyZGhMlWeI+SWLoVle2HShbD+WUTBdIRyDG1tH6IkHVHagRofjsjz\n4B3yMuamlZB+Hrx7Lyy8+/8cq7bTwJgFpqH/WB/5JfMjqrb9lPw7ZvxT4PgTOD+CtBvAED8Yl0vN\nZ9eiORhUC4RCqKHt6Dztf75FObQf78JzqDd30D7zBt5bvp8DCZOQq41ERfiR1pjYkP4syyJvZ3XX\n6dyZ8jRrwxfh6Ushc3czOd+eIulYL/pZfVB3F+xZBUfWM7RuI8SnwJyP4PRr4PB3oI2Cd43oypxM\nvfk2tLYamqp6qK8rx/PqJYjG3Rhv+hbtppdoVbZhC7MSahyDiBfIBxXUfD2BSVak7Ano4i8jJHYC\nvbD7Kzi2DhJGQH8fYskdiA8u4fQ1n3DC5sWhOZGQSSGVZXxCBib2a/WDdZHr/ggpswf3k/sqE00+\nhOZU4HMJdUgIdcyv8PXo8I7Uo+hl1ImRBAplZNGB/5pM9KVd2NaPQhm5EskRInfrakTtStjyBMFc\nM/5+P6J4LIT6Yev9gzs067IZ9kk15IZwb/CgdnWi7V5DYlWIAamBBns18tYJiAQj0k1fID+/GGXt\nF0h7isj2NlAS4cOsykhfTKEvo5Qq3wu02xNoSp3PkUQDZYYllMV2ciqwHg4uAb0FhEQoLA9pRQ8D\ney6lf/H5WP3JFFhfZsj6o5j7ZNTYoxwUi5gSkQKrXkCYfIz8toVgnJETxnSIAO3ol9Bdi+b7HKl3\ngF53Jk252WjeEWgzxxPcdgkVDX8EvRfenwEV2+Cte6Hu2F/GqbcLNs2Blm/h4JODVQX/zS8m6ePf\nM+OfAuNw6H0BspdDzW/BPJQWXYDecyehy48m9MzjiPt7kD0nIcILwgT1T8GsdszdQcaf+iNFL8eg\nz5xE+I4B1HPdxN+VQaFlKgJBT5SHtdojpFlbyIwxE9PQj6HMixZtoHFDBunBZrD7oSmWTn0BYa21\n8O3L4PNAzFj4+E18+WPQ0n2Qr+KLvpLQ6R9imh6G6YMwtP4G2PQMdWdUkeQIoWVOQlUqkFZpCNmL\n50Ud1o9GQqgNw+fVBHLXo2p2JE8nnPcEJI0AsXxQ/Ds6EQwwv0nHstQ13KAMJ8NYRPKXa+k6eAU9\nF51O0GhFP7AWyu6BU3UETtXjnhtBREci4rRU2o9kETFsHfqrL0D57jNcDRFEK01oVSrKXcsIs+cj\nDdOhlT5JR/kzWG0RxLt7EV390P0FnpkWDKUCbVYmYnkt2pnz4MTLDPj8nLpnHjE7P8A8oQItPBLx\n1rVIj66jmz1EiEJCoh1d/wlIuwLOvhDD/tUw/Vx0xw9gctVCtEJfvpWW8BRcIbDs3U+c4SyS/Q4k\nfwBKbiU4NBnXrE34tK1YMwUN0ZGkfyAhndtB2PHrkV58AvW8m1ENMt4VM3DdAnvbb+bi2E1gz4EI\nG5x1C4Wf3kJVtpGD8fWEpcSS3dOHaKoi6E2gNi6dsQc2oBWFIzakYBh+AW9tu5h3rk5FnO2F+Osg\nfzwESmHXm+DvBWP0YNF6ewHk3w7yL2RK+HPzC1HBX4gZ/+RE3w9qPxhs1PuMZAbaKaWDMS4ZOT8T\ntewISt9hZOMo0PpASkJubKDpynuZWn8PxrhYrHYfAfd3qLXdBPrNyENXQWIPRvMXnNK2Msa7ljTR\nR8+wG+l3pxIZN8Dq8SVE1jtJP14JWTIkXU1dp54ho+IhkA+SDWKy8eWU4G1sRWccjSWmBeP5V9Mt\nvYGubQK+30+jv+9LbC9sI/5ELfZzLyFQehjZvgWtV8PzlhFLjQnR5YHrLkTQheFID/6z0jElfgWL\nz4HqjZBcBFe9Actfgu5m4t19xJ1qY6t9JafF/RHjebcQ3reZs747SpO/mcy9GsKxHi2yEDlcpqzg\nNOoWplDiLyDzzR2UXrGfCd9WYbbmcaosEoPlGDZrNIYt79OVl4D91Dpkv4OUg0ZaJ8ahBG5EOvIV\nRNfgTzCghauYAtVwTgEdB/YiasOIilHIYBt0jkcdKEcftw0SI0DWoWs8SnHaO5w6w8HvPauZvfFa\nStraCTOVwcFaOHMpJEyAQ6uJ2vV7onbuAWsYtPTBk/fAhtehrxN2voUuLBzj2CKUUYDFR5aznuCE\nIoRyDN07zxA8LRdf6DWCMzXsp5zoWiz0eI2I6C9x3DmXsJZypHgrnJnNkA27OXDZSMLdh2mIjSC8\nYhrfzCth7ppvIO1MtMSDSCuaYdqrzC9KZ6A+ElucC7KOIjQbmE+H4qcHC9TD4IvmtidB7QGif06v\n+eXw76SPfyHkaIh6AHqf4x7lbl53LaaKXs4jBhxtyI88THD3EsgdD1IiVO6A3mbs619GP2Y47IpA\nGGownKzFMyMCx0UziVrzNeL6lTg+GU/YGeeRZomlPXw0EWIGcokbv20Euy19vBS7FCbPhZW7oLKW\n4LQwlK2nkA+dgme+AlmHSQiMHg9axzakmhdw78knWp+PqX0EroRNtIW1EvFgDpmrZ8Jb5ei8HhSn\njP6CELomFZHxARTeB9HVUPYlQgiMXQWw+waIyweLCfpPwNIXIAEYcgTiRjFKRPF4wiyiW5ZRFHkJ\nxqpNZA/LwTRwHP80K13x15G0fzWSS8f0I334bV5qw5pQRhgY+VQB6uaTqJleDE/1o67TUDMVPElO\nKhJcDKvQMDk1zA0OwmZciU4JQOtJSI4icrsbPApqwz4OJuSjH2cg1XgauiMniVhSR2hMGPq6IoS5\nD0aFo5QtJD4qA5EEaRWb+c2Gx/hq1BReO+tcShqLmLVkMYzqg1gftDdB1Ghg32CsUQY+fxjOvBZO\nVA6uwx52JoaEQgx1ZWgNB6F3FYYoP+paPaTpMOytwHgwkaPRkynCSVVrOpd1fcY4azjCvRWtvwJf\nsAq96EeaACWlxzk2ZSzuLANdn7k421FJ+/y72ZZ7BgvbH0QcXg3jCplWfT6B1iCKbhi6P30E+r9R\nlEp1gz4egh3/97ixpsFnT8DRzZBTAlc9++dM0X9JfiEqKP6vRWt+JoQQ2j/Cpk8//ZTLLvtbRfp/\nBO23cmvLQg4OmJk7KcBDlW1Qs5RARBVa1mx0xz9A4g7Ex3cRuPYJQi1/wDL/OGzeTHDZC7Rc7EQ3\n7lGSv96NaCnDv/AaML5EqMVJa0U4Jlc8CcPDUNvz+MSUQV7kZiZExSIlPw1lz0J0PY5nDhPmciB5\n9HD21X82TXO5UHZvRU7sg1gLmiELtaGcpofi6B1qYfR+CdFxEtoNhA56aL4gHsmkkFrajqjRQZwE\nsZEQlgShPDi5DxIzIeUI1Ifj3dCNuTAHFjwA+z8DpQvt7NNpC24l6O8ltcyJVNGOf4gdY+EE1OjR\n9NV+l9oAACAASURBVIVvox8JvWUMSZGPENidi1Hr4ySjOZGfQvHORiIfP4F5noJcrKKFT8H7oQ9P\nSKXzciu563ai6wghSvLA1AT2CZCcTShqC/UFC/DVrSTzYCu2tIcgLh9/UGX5kGWMO1DOkC12qDwK\nl2vsaBtNkbUKe08soVOVGDL9+G1mtAYjzz7yEvqaFi46dIx8YxQETLDsIxhXDJEKeIdARRnc/Bt4\n/9HBDU6fqwQOgn3qYEiq4UE089loX94HXxyDB4MIGyhmgWzQ0eSLJ8JUgK2tnYZcKxm7OiDvLLT2\nFQh3B+xVUYoEh5ImkH00jdpLP+PcTfDZGTBN3wkTcuDq8Wijx6JtfQa/modiuwjdqGL0Y8Ygp6YO\nCmzvHweflrACKnQWQHgEJCR9P0g0KN8BG94BZycMGQuXP/Wfdxb5b/D/xLf+BkIINE37UbneQghN\n+9N/s+31/Oj+/h6/kO+Ef240NAQCou7nIc+j3N17EbfuvIPgE/3Iqg71ygT0riaEQ4d26BPE7Z+i\nmzAPacl++HwJVJURmuYioWsAw6FtiCvfQju6Af2796BcpUdkxhLROxnz6r2o/hpatpzk8Oe/46q2\nQ1CWCsl6kMOhPALbQB9ajoDpp8FZr4Nu8F+sHi7D89ISjDOyMJQ0oJ77FMHXb0GVLeS834avYQBF\nL2Fs8uDLMSF3hBiw2hA1DO4uETJAXyHUHAelChZcC6H3QPLA6Jnohw2DmeOgfwXIk0FNRPz6GpJe\n30+j/FtEQze+zF70I9LRTEfAvYuIxGlEsJ23Y3OZ1XUvWfHDCGwtJTHcSbqIYF9eDOW/PZOJpq2E\n9XnA2oJ/jg77rz0YranobUkwNh3S8qD6T7BvG977rmd/RDkxO5YybOUppBkqrH4GsqZjzBpPUlgY\n4XW54O6kyprCE3G/J7d/OxPKDiFlVaLG+UGNwBgzEgoEQ3p2Me3337Lr/scID08iSRsNX64AUyTU\nfQ2X34j23ReIbx8ERyXaRedD6FuEezuE+qAvCnrDENJjCPkQuMC1OwuLKY9Qw2bqs8exMyKLy5MP\n4a+pIa3QDds0QvJidCENYRuOomujVMkk7atuem5S2LNvCzFqASO//ApP9W6MCRIDFVYsrndR420E\nm7uwuH+Lv2wO+nEjBgdp5xPQ/SzktoOvHDxHYV8PSr8D+aa7YOdS2LMCcifADa+DyQZ6w8/mU/9Q\n/h2m+BdA7QfNh1+tYiCwlBhvG6kNx3hoTTdcEkB+4B3U9x9GfBdEE8sQDOCInUFEQglSSEPap4J5\nGb5xEu3jFuIPDSN33yvw7c0oSgZqSxtdxnOx7KskuvAIwQe9aFsj+eaup7jaNAU5+feIzS/ApqWw\nfjV4otF8Ms452UT3VEHNjZD9CjjrEPt/g5Shp3uel/aRBZj3/QrLxUHC25twjDYQFiljrQ7iud2E\no7iIYydNGOwKuds6kIxeOPdC6CoDY85glTd1G5AMZj2qdBSRuxPaKiD1qcEZs6sPxp0LGz8g7bxc\nGDoGZ4qVaJsCoZNoshGpvRJhzed6Zz0nE87C5S1BiekjaOqCU4mMX7kBx/15bHDMIsvRwlCDDVvE\nEQxv34hh6TY0pxcRlwaVvWgLL6B3pwQr7ma0asBuPwWTngTTkzBxIXRtg34LJauP47Tm8Kviqwl5\nBvjdY5eSGNOJSNWgT4/PDMbkICJzNmTO4bTHz8fc7GK+ORq8PZCUQPCVZ5E+XYySEEJ/4DUaSsLR\nvC4yUq2IjtWoT3yN66IwpLyDWK1LEKsXwgg31E1Fy97H11fP5oJ19dT0z2S3rojZle8hnehG0kv4\n94xAyavCeFSh586hGAYyCA00UlR2AoM8itr2NGq1UxxIvBhjayIiIhrxpzcIz5oLK+cTmpHH089d\nyfMjbsZqXAtuEyH1GWT/UUTi66CLAl08hDpQ91YQqtmM3L8XJi2A+5eBTv/z+tTPwY+oyCaEeBc4\nF+jQNK3or67dA7wIxGia9l8WCfn30rb/Lb6t0F4EXRdicH5N2+7tbKo10tE2i2233cb2kWO4bdYZ\n/O7+ebQXBnAKwUCVnZr+Hu6r2k7nHfPQ5lyFFrMflz5IxpYONmWM5sDFq9AmPYpW+TsGMk2YKmuJ\nOKRDtFdBRhvVnkiaR2cx5ng9YkklVPnQ+ipQrpuP9tR76Fr8qHUh1PAUCO2GlkjYPBPRvBb3eSac\nYXriyjqJKe8lfX0vckgjWuknwjwTvfUK7B9Fk/hqIYWV3cQd6sSfpKANzwVJD+OfB4sV5CNgPQ6W\nZrS4ibQlJxNwJEBdM1R9DCEPiq0L5YFHIX8A7MVQswR/dhYiJhY18yKknJUIcxhE3YDBeSmFrcMI\n+/jXRNa1ErFvKLqectpfMxOmL2PKoa3oI818mRtF9egZOHkb79DD+HJUgvVf4Th9FN/FgjMpRNSZ\nT2Or96LEWSFuPWQPg7Qa+q/bT0/KUF7Lepi7J7/Pjc3reOPTB4kK9UC4AHMsSAFCLTNwx78Ivg9Q\nGxcRvaWBUJgM9Xeg+d5gwDMSZ8KtuCeWog6odH1wgOQzOvG4LTSGElGK30KU3Iu/uAhXhB/1tasg\nsm9wTbVH0FVSzISP9uO5MQEtXiPvwGHqI3KRDPn4r7kEnRZJUJ+O5IDo71wYwxYgZVkwHvES9Bwh\ndeMqLsh/H4MoQPS2Ic7OQVRcDltmQJyCrvEwmYl1VE/YDGkP4G+toqvnXDqj5tIUNRWF0GDMuHw3\ngcgeDo0pJHT/Uph8yf8/hRh+7NK294FZf31SCJECzAQa/rtm/FuM/zcoXRAoh2VF8IoF6akaTD4Z\nffEQll/m5WLdN5zWt5PXd1zOfZ+/SnKqCesDRUgvPkhaXR/PXn8dYftKCZVvoN+QRNRhA6K7gSbN\nw588ZbjLzibUr0M7/WqiN7eA/giUemhvn8iue0Zx6Qv3wbPXIsbOQjyxA01txB2/DSVwMSIPojY1\n47Gmgf562JoEB5yIUwrRLf3oVwsStzmJ6vagjQ0QNOjo1qJp2mOgb28/rq6xqO1fYEpsQecOYgz3\ngrESopaCcxFYd4LSDaHbYHQ9nqFX47JWo014E878Dop+TUjegUedieR5HXKWQfNK6O4iGGsBBaSO\nauh+Gc3wJMRdC/YkePtqqO6Fjz0Yiq7C8sBBUnuvwejSEdnqZtjik5z10E4GdioYum1ImpVgSQ4D\nv7uNgawaJhyrJGvsYsSoefBBDaJHRdu3B6REggk2Zlf2MyfqbsY6TvJ56HkKT62i94JYup69BJGa\nCz4dpORjnXc5rvWVkPsQwrKJsPdDhEZbwetE6TiG4WgzYfuCmE/6MDZKWMfGMPBiIiJjOHGtDbhH\nXwI7ltJrOJtyZxw7bwH3pHA0Tzya0sDukkmk003Y1hH4b1TJNvsZV3MQvF6M9VE4z2nFfYGd5qEZ\nBA60YPzDA4SvdSGHZVI//Twakkdw+m9309x2isC4cPC2Q8RMcNSAtxqtt4N6Zw6XPGwlOP5XnByj\nsi5iDGusx1BRkNGBHAZTvubkHc/j0dtp0Dl/Xn/6ufkRtSk0TdsJ9P2NS68A9/1PzPhJwhRCiLOA\nVxkU93c1TXv+r65fBjzw/aELuEXTtGP8sxKwwnOlcLACZlwA975ACq2o2nvE9+ZQHneEEQ47bTu6\nSbr7M9REFSkQwHb0GLa+LtTTMlB21sPjH2LJtMLpzahuG4mHPiROWPH7ddh8PsKOHYXUQrSWvfiP\nxLJeN5RjSSXMvUCCsEWDpRKbSxFVO5BGFsHIR9mfsYFRsa3o3twJZ3wBpTJEx+C+KpLSYBijHz+I\nGBpNINKHkqJg7AnQkRvF8RuNTMx8hjSRhufwHLQde3C4dSg1Erq5MngFkAq9IWj1Q+AjqDBjjD6d\neEchRufX0PUGWsAH2hZMfVa03CMQaUBYHaDLJK5xJZLPgLCfibb5Ayi4F81iRqx6BoxO6JSgOAtO\nmwaAsDkg4ju+yt+G7v6t5Fe0MK6zDWGaD998ArOehBVvEZkTC3FnQMsnEHShhU6hxXkRXaC8uZWd\n509mmLKKab59zHAegd0DcMZCWi/MIf+d7RCfD1c+Avs+xmR4D9/+KvAoBDYkoZ+YgnFyA3QLdJIC\nsgbWh1DXvU4gqMf627sI3PQ4Wd2lGPHxee9vuaq7jtyVndj6a2iencHx9IlI/Q2EpdsZH3EYYVDR\nfbQe6029xNw4i1BNLbp3DehGpRC16xJO9i3D0Oyn/dok7AfD0J1wYDH30aA6mSxV0pMTjalXofWi\nicg6B0lV56OLWYjIjKZ9w2OctqiNQN1OvtbaSfdFMmWggIQjFdimZf/F4/VGhocXUulQyCaWbkWj\nSdUY5dwPrWshohCiSsCW+TM52T+QnzhYK4SYAzRpmnZM/A9qSf9oM4QQEvAHYDrQChwQQqzRNK3y\nB83qgMmapjm/F+7FwPgf2/fPhsEIz7z3n4p2m0kCoTIk6mbSTt5GKL2T589/jFcSz8envIbcsQ1F\nPoK64zV4/TkGxmZi295A6DY9xq0wcFOA4r4DfJN8DfbSDnSqjHT9OhjohRdmETCNZ/OFFzHMXU1b\nVj3RBguG+q9hz8MQGURfU4uWWE6OaQv6niYCwxJgv4YW1KEFunF9009xpQelHfzzx6PPTqDf/CV2\nXQ+STmK03kddx24OxJeSZowi0S8Tng0+EYVFH0LeFALHfkgpBlcIzGOgfC/BrD6MkcXIOh8MGUZo\neDE9mwW233+DflgbhnvHQsTlYPsGIbWjJj+NXO+Aui6YAOz4HURooAD3DoEFB0E2oWle0NoRxtF4\nzXvQSwZiImsJVnmRlZPIpm44PhvyYsGjh1YTKNvBcxwRNYWQZES0+JHHn8nUdSeZmrca6h0wfAxs\n2QS5NRT+Zi3CHjaYtNK6F04dQHSfBNWD6j+fwJKVGMY30GYpJjpKhdFLCPb00vrGCzSXBYmKsDH0\naCYRN15BcNvHMFxw4dJ3+OaRWcx+8QOSbzaTNHAOQuki+MVJOmb30WV00zbPRuxwMzErmnA9cwDZ\n1o0+Kw3d228gv7eShGVvM3B7PEGPC/uk4wT7rHy5cBq+TRasNQJ9sZ+dWcWM+Ggv3st8NJlaUTPn\n0eL/GmdeBN7wPSQXO5ja8hmOmN+RYpqPLn8vfHUlnPk6BLvBfQrhaSCxvYKqHQuoCMIskw60APSW\ngfFXg0ki/8H3mw/8S/ITirEQwgw8zGCI4s+n/1FmlADVmqY1fG/M58Bc4M9irGna3h+03wsk/wT9\n/nzIf/uZJd5dSKf2PgnH+3DbIpgct43e4E4MShkaNUjnXIVkCSdwbTieQCIRZg3dR61okQLXF/Hs\nPX86wpqNIawQf7+MsbsZDq8npKWi9H/LMPU8Hi3fjS+8kerUuxkWmA8j70SckcuA7mmC1j4OLZxK\nRucpwmPqUVdmc2BWPELTMEQqWHoFuoEhmCNPYk430O83EXVSIbmln87ycqbcv5yvX78Vf5KCIyEa\n2efDdM5CJH4H9rFQPwpc5RAeBVoizHkaR8RrxLWXQOl5aDNX4+l+kY5XnMRmOpCuB5H9LjS2Q8pI\nvNY6LA23Q8UYCD8dOi8Ay3NoDoHITIMxt4D8/duUwHIwXIyKSnTsVnJPWDCbxqGL2IT22QAkxkL6\nAohfBYFCSHsVfH5AQVR+ia55O1LuPDjzGQh7DI6FIO4gnP0+bD0MRhVx+2p4bDos+BACHhgyAjbs\nJGqoHu+DC9Gb2xG1SViz61BampHl5XTv6Wbfi++TecXlZFx5Bf6192OZmY4+OcjANxJhcamM7vHR\nF2/D0ufAWPkE9FuQYjwkVwZJ6Bqga7wBT6wX9+nhZC/bAQEvctYJ1IwJiCevpf7ZUYxu34GnNhtt\nmo7eWQlo8R4WbFsBo3UYei/g8KzJZIeWYP/QxdF5uQQlFwnG45hyBJGqizO85RgsZ+IyDWUdnyOn\n6hhiu4ysqhfRG6PBmgGRo3CHZbOw4GXG6uI539IFgS4wDwPpB2M8FIBjn8Ooq/5fe9bPw0+7miIb\nyACOiMFpcQpQJoQo0TSt8+/d+FPEjJOBph8cN/P3xfZ64JufoN+fDU3TCLS2AqCoLSju/bD/csI3\nzsPhX4aS6kK1D3CuYTpR6vOYG/y43zqX3mv6kN3T0VeHk6QLIc7/CJFsR3JrhExWbjaegc4WQ8AS\ngzpggLsKUPf9EV/Zd1h0bTyy5XXkUBCrP4C1X9ChbQF/H/TXIwI+hGMfFq0bLb8VecDDyZviSIqL\nJyI1mZj9fci9OrxFQ2k1FFBpN9JkyaA+IpsaWwb7LitkybHLyOxvZMK7a5A6NOrTsvEc+hPqYR2u\nwwn4Xv6I4Nf70VprQJLRni5C9XchSxGQdRkd+16j8WYPua8sRIoB0TUZ3GFQvw9SRhL6vIvQp63Q\n/i1i7u2InlWwyw+TQmCtA0cPbHwf3r0fan4Di5ch/fY0xq7YS9uJGo40J0KlDanLS+fcx2DM6xA+\nGVLfA30O2AvBE4Dm3UjqEDBMgHeXQIMRDjdC1gLYfCOh/goWd3s4dGgJWigEycVQtg70x8Gnx25Q\n8B7ow/D2YbSxn5Bo7qOfcA79+nZavl3FGFOQ4TFbMNddjWRsRMl/FlFlRh+y4h5oI1qU4j4niNow\nAI0C7WiIltAQtGAUUl86cR/qiAk0I8eE8GwfRv9IK8GQQFu9l8azLXTFqwTsc3DKJjy6L5HsHoY4\nf4s8OR/M6eA4Qtr+ozj9Tjz0MnPZLub0XUBJZRFFmyBc70Jn3cPJCAljzztM/+I4032zCNltrM8f\nytfD0qnMKMBjCqNBM/OAYxdP22VQfdB2+/85A978OHSc+Bk87R/Ej98DT3z/g6ZpxzVNS9A0LUvT\ntEwG9XDUfyXE8A9e2iaEmApcA0z6R/b7U+M5sAtlzVMY4gNIagBXsQPF3Is163ZiHQbcCe8htyjI\nWVOh/kb8GeMwT78C5ztXECz9FGP7YYh5FPSPwtW3oh10csJYzdTOetIyUqnZ4yW9ug5t9mS61BH4\n975Bakk0IvkOOG0O1FxGeuZ7HNEeIrJvLIaOOoxHT2KJ62R0qoTNG6S/Kwp/ppkY72TyOjUoPB9e\nugNtYwTeFgOWz1dCbQZqqJ2WVgOtkU7Guq8nNqMPubmc8AEj+WU1aEUyDS0pmM87RuTGDLybK7EW\n2ZEmXY8/TsN4qgd14G7qFrsRYSrDbj0NqfdF2GmAvbsgOgP3mES0LWuwth1HN0SCLB0490DsfERO\nDZoygGYRiM2fQ1kj2hQ99KVAWwcUprMxeCsjLx2Ny3mMropSYlPq2THWziRaiYfB4kwA7Xvg0Muw\ntgU6m6D3UYj2QIEeLcWGqFbArCH7nMwOa2bvh5sZqQeUdqg8DDFGOP9xgh+/gRzeidPWQZj9MNoJ\niZZqjeQzU4grqCZ0dgyBxmGo8+6C9Vvpffkloi3hyNN60df1o8kycTs0DMeDqNeY8bntJHQ3ovUN\nQGQPitBh3x8kGN2Dc1EYxrRC1LZj6AucRHsOYVBGEazpp2Z8PpHqJ9CTz3D5V5Bjgonb4KubGdm1\nh/jGRuxWN1qgCD66DNHdgCnnLDT6UEUOWScO4xjWQtO0ifi1hdgHNKa9ZcE9Np+uaXn8XmymZJaC\n3HuEsOz5gyLs3gGOTyHyCjTlFHgUxPEvYPS1f9sZNAV6D0B4Eegs/xD/+8n5ETNjIcSnwBlAtBCi\nEXhc07T3f9BE478ZpvjRGXhCiPHAE5qmnfX98YOA9jde4hUBK4CzNE2r/Tufp1144YV/Ps7Pz6eg\noOBH2fi32LVrFxMnTvxf3Ru5fDn2mhMYFk7EHHSQWH8Y/0wNxSpj9vRjdTiRWwSSKgiEzOgNHoKK\nFUdcIta2ZsLDnTgOpqDcb0AeCLKzcgHHimIYJnYQaovFcrCdvK1N7Lr5foa8/xwxNQNkKm1UzZxF\n/8hkcpO/obM5D4u/mZ6pMpnLeyDFh3dEGO40DxYBnpfS0U/opEUaRUJ3N3sibmX4kWUk9R5G2tFH\n6dWLKBy1glhjJZ7OSKozUhnoT6Sg+iRKv5GDxZMZH/qCsD390C5wnBtGV0w+jn1pjGj6iqb9eXT+\nZgiJh4+RFFuBXKIirdXjjwvDsMNNT9MQXHFxWKjAH9uNftQwUrMOo/kFAbeJlY7FZHdspSmqhKHS\negr7V1Onm4IrIp6C9K9QvTqkhhC6bj89His6IjBH9KD0Qs2oLHYPuRRfeIg5zg2UHr2R6GAtQ70b\nqHdNorjlYzSXhsXTi2I0oAv48URE4fVFogZkIvqa8RaYaDPkk9N8gMOXLiD6VAWppUepu2IC9t/U\n4uvpZ+DFNFasz+TOuZuwJ/tRDhip5QxswU6Cu/qJuMhD8Lgef1I/CdtdSF0q8jkh1IMg60EdgOBC\nAy/mLuKcLzYzsu4IoQEjSsiAP8GCPN5Jb0oCptUDdJ0WhTUUIqXmFF2j4nC32WiLKcIQ38OIIwcx\nFPfjarHjdlgxdHmJEB6UVhk5Bjos+SS1HkXzCZyGRHqviEf+XMbbG4NlWgcxjXXU2k9Dpw1wIH8k\nySPKUPaaqe6bzvWlT/DeOdcRVT0BveRhWOwamvrH0OPNYVzhO1TVzWRY1dfsTb4BRfrP08PR9o/J\nDm6lz57ODsfd+IP2H+1bf4/y8nIqKir+fLxy5cqfJgNv73/dDkCM/+Vn4B0Ahggh0oE24BLg0h82\nEEKkMSjEV/49If4PVqxY8ROY9V/zv03ZbGtrwyVJ5NyxGNHXDvecgaa7k77RTXREL6eNCBKa9cSa\nnkEfN4cBcS02PsAK9L/4MNqOl4h8dzfuKCPW3WdjnDuGG6tWE71tLa6ucN49ZxFnjpxI+bk2kk9M\nJTxwGMloooBvoSkSbEEixxZC/yS07r14xw7BKh8ldnEjzTeOxFAwm6yIP7Ji1iIu+uhpRPFdZE68\nFGL9YFqAonuSqY1H0SWDJusIc+jI0Vn57mwL0W19SGqAkaefhf7gBiiIQ0TqiV5TTyi1isDZyVjK\nMhgStKJzbiGhshvZakIftCEuuQl91Q4YkUf8pXOxvf8xntxa/K4hpE1djLL9HOSUoZhSqrksfzGE\nzWVc+BzgSnixgCFz7of4XLS2zbDJjrhiNdw+i+CMeOKGx4A3ge6SK6kfc4oonR6n5mQgPEhJRA0p\nJ46hD3+Q9F2rQHPBiAUwfBq6o6+gqDpMYceRJ/8OU4WGFBaDIWYbdTF9yC/AmKFVOKe0g04lK30r\nXn0KuuEWTr1Yz8IbuvEZbOgkI6Zz1lNQ8wG+nCLcs95BcytY1AiilvmQukHWBERGoGZFIal10BqN\nobeXiSE3o8OyECNA3nMMzRKP5VQjakjD4unGMyYS91QbncKOPTxA0qYuOsdE4xx6itGbDyIHNdQm\nK7Jdj9QXIjp5EvL+b1g2fR7nG8JJ7dkFw26BFX8gbHwH9lI7utnzkMLKIPIxqFzMyKOVMDKc7JNH\nuPDIy7h6vLzf+zQWEQsFE7hs7Pe+4J9CQe97aHHF4NhOFlMQ6beQ/sN4sRaA+vugqRR0BuJKVnKR\nfthP4lv/E/4nKxX+Lr+Q1LcfHTPWNE0Bbgc2AieAzzVNqxBC3CSEuPH7Zo8BUcCbQohDQoj9P7bf\nnwVNA2cVtpROwnK6EN6tEJ0E2aNQz5uBIa6BZPk7DOI5mpNHURv/Ej3iE7SQAxUFx8B+5OaNOM82\n4w71s03ajnIoncPBTqIPfY5oA4tkojYznDLjKsJffJlYgx7lVANkF8MfuuGeHTBtImivQtXHpJ65\nmo65ufjCBUJVSHuvk9Q1m+mptJN56CRiwhMw8ZnBR9DUAohMQrp/OfJzH0JND6IjCKFObFNeJlLq\non5IGpz7PrEnnof9vdCTgDzmaZxvV7P6wTlYjxwkWNdC5fYD6IcVYZozmuCOHILPpUHdATieSCBh\nIs6X/oDp5vMZkI2k3bkGls5HDs+G6DmADrQhYB4N3U9C8zwY5gf5C1h6HSzpRpxmhNb74NZ89Ce8\nMHUDiAxixt/OXN0LzOcuFmjzyVKqMLsXc2T6WXw3rpGtdxbTP2QIzVedTb9rHZr9CN7xJ9GSgrja\n76ar/D56pdWIIY8wvK4eh+hFWKYgBjy4L4gn+IKODn8MTTUeJt0mk5GoI9oT5MuEz7jLKjgS00iv\n5T0UWyqRG53Yv2xHH/DgaQigpIBo8mCIKIDYIoIlr+I3ZDD1+Q8Rm3dDRySaxUTwcgeBB5MRTSAf\ndGEfVsy4Q04mV3qI7DHSPzqRnliJ3OPNyN0a9IPaomMgMkTP0fORZ62B/niMBbdTUXg5qH2w7G20\nc6wEC1X06a00pNTTk/YIhM0Blw+cjTDp17RJ8UweZWJWYjOLLHdRfjCN3BVf/2WMG3PAXw0iHrpG\nQmUp5M8dvKa2Q+/9sDsHfD0wqRLG18BfCfE/Hf9K9Yw1TVsP5P7Vubd/8PsNwA0/RV//MIIucNWC\nzgodW6FrNyhesA8FYyIi6TxwrYWuzahpcXilh7DwJySiCJNSQLoQFy00aI9jVcvQB7/D8sTzNNw2\nmfDmBupK76N29nBKi1XO6l2Kz2Gj5ZpcsodvxFT9MaOqD7Ju6PX4D3diyolEVB+E2lWQug7MueBQ\nwC8QVSfIzr6C/om70D6rRRehwrzlHMj4LeP60mDaPX/5m/LGASCCXtj5CuRmgdML/W6kvjnEmYup\nTk0huvwRTGGNWEs1pPPOhNPn4g5VYXFrRF38O3xv/ZqURfXo20N4Y7KxfryE4LqvCSx7CKmpAv/Q\nNCKWL0ctvYrqulFkiyD1nWlkJh8CbQHok8FfA+ZJYJkCnrbBF1PaB1AUhkg8A0J+8O6B3DupTuwm\nbumrED+4N6CEBJpGRFMbmsGPId7HSekbslrisGytQS5rgLg/oPhPoGo21ILxBBuriCpV0S1pQohG\nOHodhqQ8bANHGXCXYu0oxjOiA19nO7aJA2S7+xHfaChjkumo1HNu7QJqr7qWdw1nkH8ojWvex903\nIwAAIABJREFUqUGyyAi/H/Qy8tQ0hK8JApmQfRxpZyIm7xowmsE4FuLLUQYO41uQh6HGiTxtFZ7g\nFKzXuiDxbIh8B/2uq/A4TLQODSO1+RTWJh1q3mSkcS/S9Yc56GJKyF0UBVv+CBEjcEVaeVkt54Oq\nZpikEVTs6LcZEFIyMWove8fdR75yJymFF0JnNetTprMlaiLPf3slqquF5jntPDywirSKvUw7rxD5\nioVQPAP06dB/EJqdCNUG8klw/RHaT0FHCEZsBHvuX3vMPy+/kNoU/87A+2s0Deo/gy/zYPvFUPcB\nWDOh5A04fSmMfAotchQY7OC9lVBjJZ65KzErjyMR9Z8+yk4yhe0lRD7dRuO3t9FSnEW2mEJsm5Mh\nu0NENVroShlN7pc1mByzMKS56P74Ma5+803EgI3JF73Auvun0LzkFoLZQ8AQB7Y3wP4CyGfChYvg\nvsuw7G9EcdciHYJgpZWANRLX+JFEhf8g1q46oOp92PIMLJkPKWNhwRa0Hg/IKpzsxLKvkShDK32p\nPvSrFUREKiQOlmGsqv8j5/WcizkqD1tqJHp8aAdPoot8EiFkghXV+NdUoOlV7JdNQvRtoaspSPqs\n+fDNw2z5qBxfMAY23g0iAXSJ0PUiKCGoPQY7MqHrcahVwSRgxlaYVAFhY0gv2QsnV8DRMtSXFqC9\nczvUroGaleCOw7B3DJPvlUj/3E5sxl1YY1JIabIQWeFDuvAQkr6L7oJ85GEPIYw6iN8IQ72QUoDe\nko1cU4YSORuBjvAbQ8Sn+6DDCkGQux0k37gY7fRiri9bzsLuHSTRxp2P34QjKYOgx4ZnTCL92Rp+\nNR1u+hryx8CDu+C+1yB3GNrT3xLKz8CbngeHA2hdVtSuSqpnzABjFLS8B41boOogrbY2HEkqpvP3\nUn/dLYjMozi3vkT88C7iTnse2b8f7buHoW4vE9cuAY8TujW0jRIhkwvdp25oiMWuHCG7M4u+ppfQ\ndt5D0OYlb/9FPPvmWMTJY0hNJ0hZ7uEDy2w6chO5Mu7XOJ99GZ69HQxT4dXbobsF7NXgWg4VLtBm\nw8Qt/1pCDP9aM+N/KRQfhOcPCq+mgCkBvB2DM+Tv0RQFIctoj/6K714xMuXAAHLMBZC7Gs1ciPhh\nyUFjJq3Zkzk+fA7nHMyk5+G3iZoSxDrax8h3y4i1HUBJuhLn5A48UT78VV9ie3Uz8m8uItLhZ07E\nFexSFhA19gSROTMHZ4UAAQd0tg+GHfY9RUpFJ+rkXAJuGyc4TqGpBKb+QIyPXwGvb4SRxXDLdpB0\nsHcOWBSwWvB1qPjm+YjQN1BlG0W6LYTY0gQNtxLwrUGkW4hMzmLA9R2nZvaTuFLgaRtBVHw4waVv\noZTuwjohFalgGHy+EG1kL0c/Cmd8SQjMVfS39XBofQ4TpoRB+1HImjVY0vHLE+Bqg/u2g68FTnsQ\nZD0aEsKUDH0ufG1WKOqEU6Vox+yU3zKH6KXPY+sewFjUin7SA4iXbhkMxagqbH4eMo5CpQfX3htQ\nCwVxAR3SptvhV8/ApAD0lQN9CHsZxjKFfQVryHPE4s9Lxrw3Gm1oEDa6IC+EqD+fCKUfmxZDTPe3\nWIZfxaz1n3IoLoVjF53BDM9G4rvasZwlo5gj8LoP4nWcTseAhcDs4Zh4mjijk4j9TTTMG0fmhzsR\n/fdgNWeCJwS+kxD3OqGU2eycKjinL4WA6CIof4iIdqDzb0RJCyfY/QW6qlzUCTuRv8giY+daLtbS\nIDWGwHWLMJQfR1zaCVdtAV8XWY5f0R53EXXj+4jZ1Ubauv1IOWfDmIsI7HyU5uI4Mmyn8faOhyif\nMJrLfR/z2NFfM+6DPyC+qEC7OwKK7oHKnTD8+X89Ef4PfiGlmv89M/5rdGaIGglxkyB+ClS+Cvue\nAr/jL20UBaEo+As2UyUnU5F9H9TFQNMitBWPoPzmTjRVhRMHKN+4hOMTxnBxyq+wzZlL7NKvwZhO\nqMFBtnsLK86bDau+o/PdtZh3e8jwdpB5vApx20p4/w6sqpnT1Xwix7RwUtn1FxtcHfDxx/DIbbBl\nL1rUUOQFi9HFealUT5BL3qA4hUJw4E14di0MzYUZk0HzwKkF4GhDCxkYmD6c3gsuIrkqH71LJkXN\nozkzHJ5+EkbPhwdXMGHVGlg2CsuyRbgjnDSFxRIp9cGtGeg33EpY4mrkQBsiNhlGjMbVE8OhvSGU\nha+jzb+bKTdlEx1vh/5WaG+Azp3Q1AlRMTD3YbDHEwom0Pf0c7SfnkX3pefiXrEC3wuPELv0CKEd\nArUmhFzcS2F5LfEXJtD/4mMcunweW7Nd7BWf0MRhQsc3MjDqDDRTOMoICdv2XYR/fQi5phSSxsH8\nRSCOQUw+RE7CKYYRsBoo+XwLvrL9VMWG0TazCKngXkhIR1kYieYyoaW8wfLpr+PPe5DoVj2GQ6sZ\nZyvFInvorMrGavSBPBxJigFpNrqqWlpXwt1HX6Dh2CR07mwkzUr2u/8fe+8ZJVd1Lep+e1eOXV3V\nOWe1Uiu3UM4RCQlEBguDBCaIYEBkEEkGA8YmGIMBAQoEASIoIqGcW7m71VLnnENVV0577/dDPu/4\nvPvuOL7jXC6ca39jrB+1xq69V61Vc9asueaasw7RY0YYeztxnhrIMMHwK8Eg4Z/2KNdcDJNwYRsK\nERAL6DhZiCF7LKqKcYjf70Nu+A4pRkvgmZnIMdXMXf8jUqwWSTiFKu1F6PDD6hHw6a2QuJZETzne\nKSZ89z6G2KmGlotw5nEaFDU7tYUIZ14HlYdBo2r5/O0CPrnrEH/M2IAy0AZSGGQPjPsa+XwHkd89\nR/iRB5DPl/+fl8ufkn9Zxv8N8JRC01cQjIGjT8HkN8HlRGhpRNXejNaZwY0ffIEhEkGe/XvEqv0I\n6Vuguw1peROVY41UZEa52jj13y3athpUHSpUwXpCN12F1hWm5UQl7ltiUStGEjPsaI68B3d+C2MW\nw7Y3MMy5i2jXOhTxHrpYRoJyHxzbCfe9CqUPwd3PIV50wlWT6K/SM/6bjYjOLXC+BZrbIC0FnpkN\nJUdRTr8J6hPQEoPvstvRt92NYYMH88p3YLSV7NodRM8/yJ7xqcSePIbGXMrx16Yw5dFdkKpHXDya\n/I9PEk5Tg6cVLCqI00BxBFLjLlUyaRVoOmlh7jMjiI0PwLfPMLAgwOEdIgXzl4H/VeitgC2x0L8b\nGAx7S1G3NWOuqwa/AuXboOYwETGWTpJxtMcTzpdIbvDBVVFE32ZS6g6TYlqJkHQf/v5SPG3rCB9d\nS1+WDk17D0KiHnWaHw6IhIf2oe3VwruD4bJWlO59NEa2YYoLEhMIEcrWowwH4wU//XIlsS096C4f\njVAVQFi0BeGrp2gdMIeNBWNYdrGE4L1foq9YwiT9fjIqu2Aol44YB9rRmVagjn5C+lgz6/un4DjY\nxIHBj2F2xRFXW0N2dxffaGdypfdFwsF5aNvWQ/wirP5mGPUxlD5JJNJGSliDsehqRCkbzM2IraVw\nUUJpm4z6xMcIRyJIA0Q8C53oKjthaD7ctwc+ugGlYivRNVNwjjWSbWyhKmMH6odHYP2+BvUPffRN\nGohZHyIoxqDv6Uepq8bYv4J3bviAOnc8jJmIsKcRxX4d8mNXodTUEC11oX3nQ8TBQ35GofwJ+IVo\nwV/IMH4h1O+Fvc8CoMx9BUHXBUkZoJ6L4m5gfeMmMl7fwqQv19JaMITm/GEYFD3BEW4Gx2QSiBrR\nVeoIyQKhohJMXg1XVUWI2ovQynZw5MGx76GpDnl2LJ1PHcD4yGiyPnuH8wlfY9/Vi5ifA3Y/vH87\n3P4+vH8XtExFPXQ6doqI0E5nxVLi7RpE1Yew6ChoE1DO3kWw9nfEWFrxpeQhKNOh/DnItIOjFw7q\nwDgOZ3Ea6vItGIIh9O5TCCEjqhgbvHAjvLwFcucSNdXj02+lZJwT0+AM8iU1vLQEPilH+cNhVKNE\nwgkGfEP0mFtDYM2A5kwoOgzn/gLHYsiccA/m+lXw7dNgGYHhokzQdRFqqiHFAN5UKGuBvjLktHUE\nF1+PYcED+PVduMrfpi+1jjztTcSk3MGXX69j3vS1xJ2aCJ0GqEyDhTdAzUNQtR78r2DUOdCqFKKu\nQvqHJJFWtplO6wwsuafR27sw7IvAHV+DEksk+BSHxkwnRRxG1raNKKdqUU+fSMKGSpIONeB7xkk4\naKJ56CByfnAgTMiFK99l4onb0TviUBLc6PvO0a+3EX+ml2CshgsTVjJyxwdEq49Rl11O0/giCraU\nkHqiH2GGlzljRoLaD54OAjd/zazNLyB4JHorzrP22lIe8a9EKX8Sud6BKqrDpZSTpuxDrN4DhgUQ\nvgjG4ZCxAKFyPYJLQjFBaxfE1kVRt1cg+1ahKl4F8x9E+eZHApcNRl11BHNFMkMXaqgYYCE+dijC\nG2/j+uhx4pps6K7bifj+auTcaQjuz+D4o2TLIZTECDQ2ID+0CDHcDB80ogorCIlJP6+M/hT8QrTg\nL2QYPy9RIqgjATCpYdYLsO8lOHoPih4ESxmkvknE/XuGaksoWnQ5TUOLabd0YBPLST64j74Lek6o\n3qA7QU3T0Cu5OMNKV7qNCe0yNz/3FL7EnaSFtAhHV0GqDYoTkNtcxC8ciz5egyprBkmmUhIHforS\n8muadR0IMYuJ+eAqzDd/hPD2r+GWUSSE5yK0GPF/dyuBMR1EB63GqopHogb3XRWYV/9Az/QUBptn\nwPjZcMWv4Z07YGYxnP4TRDTElvUS6e+le+AgktvLiARzUM1bCs+/BBXHUFQ/oml5hctMWRzPSSMc\nC5d1FILzAIqjC3+sBmMZ2Ov8OOfqkOL0qHLmwOXvQLQRekaBWI+l5FEoiIIBaOgFxyxiYspwOb7C\nlpYImtEoj9UQVfQEak9h3HmM7szXcSfnYB18O2nJ89FgRCFE7KStqKvGoek8BX4tODtRrO/DoB0I\nEQv0uaA/TE+9Ct+YbgqdCxBavqJvTBcG0yBU/V3456gwrplHJM5L66Q40lpHkZ82GhLLEax2VDta\n4bgXUqygs2K5/D7M370O7Z1ENn6HotYxqhcOF01EsM+lOX8SPWc7Gc5hWmb9hlaNRHyKETn4O+yN\no3HII4mLZoG+DA53Q8uvIXMS+PswXHY5hiET6Fo5hiTBxyOfzwcpTNXlevLlc9A7jKhZg+pCAIJG\nGL8MQhdh/xfQUw9GFUqpAU9AQ1o0SuChMIZiNVw9CNZeDfNfhrSZ9KadJPP4GMTbnkLX+hA5p47T\nG2vEIbjomzWR3GAz4h23geBBXP4O1O5HSVxN+MFliMVTUSe2oJJtcONf4NSbYE4ETyHkzPiPuSv+\nm6P8Qj7Kv5Qx0MAZbJok4vpOw9nfQuZCGKaF0Cwor4MtdxJM7UITjUV4/jocg+bgmh4icf6tWAbH\nY3aXEh9XiexzUNbUyuAvd5HRYCY2GsY0ZwCa4yGiutWoL3cj+IugMwZ1hhl1r4uJZ0/i2TKV8DQj\nPZlXYzYe5oKhgNBQD7NYirBhJcy5B759B6GwCd45i/GRoSjedPpw0t6zFGvsDOTYEELxjZj270Gy\nqIgqPrQfXw/BNqiqA3ct2AYiNF5E6zUSP6iLxhGTcJz3oS3/A1yzBJ6fh7I0FsxGkvvzGHPuHCnV\n9RDeAaKOkEWF9rbxCIYwyoEjxDaGCI7QY/Afhy+KwJoF0QRY4oQeBVq10JIFOb+C794h062lqTSE\nLb8X2vMJOO7C3f8l7l+NJXG7n/gSJwmODDi1lf5ikaqEkxjk/ZzpWYhQ0c5irQSxGVDzKVS9AQWL\nQeMH3Rhc4nmSVn+E+9blqNu3QpeJ+EYHJkYhBKoR4noJjwJlp0DqiB6Unr1wdDd4OyDYBRXpUDAG\nDJVou1OIWPaiu20Qym41jc54zt6YyJStZSR6G6D5XmIaktGa44jEaHDGniXnUAizYzm2qg8RFj+J\n8uUAom4H4W1qDEEfwjg3XFwPU4dAsBGiQTz2ZBIGT4X5v6XJfIQ65c8UtAZQ3CfIPGlCMulRrFej\nTZsO1RVQ0wRqP9KhCJ4gKA9+jDbbgep3M6AnCFs+hZufQ97/EoHoKZLVmxAHTgVtB+T2EaNdSmd6\nHN6KD3GmjseerkIek4Tqh3pYMQaeeAmhrwztb69B6KuGiljoaofqNdB6ArKnQ/68/6sUMYD0C9GC\n/9rAA8IEOMR6lKAdZdQaIr2JKO5zKH3bUbR2cIykpiCJ5thWlHg1oncXXYFmjL17IHMp0cs+I+zJ\nJzBiG62LFjB8xTXk3Osg9vEVaAfGYv7dx7TnDCf6+ZUobfNR7HXQmQR7z9KgzaArzYij6iKmD6dh\n6O5l2qZaFjz3LvoPX4L6U7Dmmks11459CqE+GPhnBEGFoyYXx44LBNsex/SlH1X7MbJ3HSX8+3tR\ny/UQ6YeJy2HCmxASQayHnhBMfhqNJ4aU7QKdUhz98Sko5Z+hpPbDjhZEIYaoo5WT46+D6yvgijW4\nonE4yxxoFq+F9Fm477gH/5D5RFo1+LJ1KNZk0Hog0APH1ETjBeSWWJjwIvg74M0LJF++nKA7D0UW\nkJ2voCs5SKL6NlLMnXiWCCjeDPqnLKN8YD+1kbXo5EMkbW0heE6PLIhEY9Jhyr0gqcC9GdSzoTMZ\npBMYmIQy4DKs459BkPuRAhDrz0Jzfh9ReQGqQAQZCSFHi/rHBDTn3WA7CIYG8FhArgRrHwgC2l4L\n2pJjIJchxED28bNMj15Lx/XvUjLxSY5OeoSQNB1VdwCfpCe7N5ZBfz6L5anX6CnR0bLqGpytBQhy\nP8rqF2H5MzB6Fchm2FcDP/wBjCmk60+A1gjmWEL0kSxPQHKZiMSrIUOL5DUT2b2FaNkq2Pkn6PYg\nX/ARskjUx8/E1vYGSrCJnugVkJkB+YXIbyym37sNdb8K/Zpfw1YBttaB52EwrSbRPgfz8EU4Q33Y\nT25AUB+BORJky/D+Y/DkHIQPXoGYy2DFPrAUwZIN8JtTcMV74Mj/maX1fz+S+h9rPzW/kN+EnxcV\nakL4CXeeQCfGIIRykR6XCFtPYJyrRjnswd6mRTNFRMyZgknWIeRI6NL+BKoYenmCxMPVKFkurnbM\nh1gFzn4M2RJoUxH0uZhuf4O2o1+Qeu+nSCMEAvPCeOUCQmlJ9GeMxOmFvHWVGJNCaEIhmCVBvAbK\nQ5D+PmxZBQ1hWLr8UpIdZwXScPAWO/FvX4n2hjF0qv6K7bAOkxKHeH4HxKnAtQ92/wB9EngCkDYS\nqkshezJCeixpZTuI9nvpsadg1EYxid2wuwrFZGTSmT6EnF6CjqtoeqiF3OdToGIr9FYQ5SKqa+9B\nf6YKr1KBK2qjNT8HecgVqNrPIGfrSbu7lb6WtQwYchNaYwzqKyGppAb5goKoRAgN92BInESFciXd\nVW/CxENoNv2a3HAE/4gw6mo7MRl/ZMzpVmzBLtTGQojPA10CbCpDuCwZWrYjxT+Izv8oPLwepBoY\nfD3qzU9AngoCCQjz78MjfYR9i0RoQC7BdWXoxs2GgbfCR/cDEchPhNpeSM1HEIHeMJTlwaxv4cgM\nbMe/wVb0HC12LX2VIdyv7yF1fDfuiAWV4wQ91z6MMa+AuJavEXTHIXYmlJ7D5HoPlm0BjQUaCuDz\nB+DUXqRh5whbtGhrdgCP4aaWEa2LCNsuUjfDSEFlC3JXFG2Hi+Cn6zDnjgbTlyguFSxVMWzKBwib\n70fa/AiC+XKUwnEIkT48TyyDi++hOdMNATUYNXDeCbUPQPBJkFwIph5cS1/GdvY8BEww+QFI6Qel\nHNxJkDENJfQxQiAThhZD2QkoKv5ZZfSnJKT7Rwuvhn/ScfzLMgYKGE8c6WhCEbjwHeqr7kU9oAiD\nbEJqSUYu7CfW6iK9qhYlrhkKwqAoKLKREFWoez0IPidi46FLN5Sj4MoDQzGE90KkDQdFNGWHaJhm\nRtopoDMZSb3nKyZKZoZd/BazrKN+w90oA+LB3gUXe+BMKYhLYNd3MOsRKLbBoY8BNYq3HrfpLaxV\n89Ec/4Saq+6iu3gTTcmJCNfeAFWN8JttcO0GmH0XpOkv5QoeeiccWw85y1H69iMMnIu5vREx6qZ2\nfixSSA1BNZqOILGVrSj7/or46XzynzBjkLuRS16CwuuR7en42j8iEqNBFZqIiSSK/3CG0U9/xMgd\nNWQQj8XwKENaDxLIDRKWjoP9e7Rhha5PQOiajdbUQIV1FcHGJxh7eAspNQLxB6sIOZyIoSRixuyD\nYVehlSOMrt0GRz8DQJl926VcWEE/0pA78V94GgY8CjEt4PfD+TrIsELoLzDqQVTko23JQeiZhT4x\nDXHldJSbFqKEBkKTB0Y8BDGFMHc1GLvgbA9MjoWcF/GpO5EysmHDThTvJkZs6WfQHhvWwbloezOx\naYeBLYhj6RP8OFOheenrYMq7FIWjs4NvOBy4FTbcj9LjQZn2Jm2zw/TVLieQowFXB55wJSY5BeHL\nVTROU2OraqM7okJ30I1Ka6Pm+euJ5AVQPHFw26Noz1ng+AaUa9bhmn07vvjjyOEwHPoQQ/lmbH0g\nDvo1csxAotUGEFPAbYGZCizywY33oViKUVnGILhNMPdhKHoBhn0DE49C7Bz2G82c7PsjygQT0cad\neDiLiyM/k4T+tEgq1T/Ufmr+ZRkDQthPfFsrnd6jJAec8M0yyLMhDNWi3heCgUYskgulVyQk9SPg\nQW2Mx1dfj6fgbeJiVxCceQ7d8Fsu5cprOQpp4wALOB6BlrsgfiXae06QtqgFrLH4LQLa8t+gir8S\nb+QouYGReHq2oKTbIW8p9B+AM03g3Q06E2x6FmWiGyEMyodLCI0XMO8Loan8gKTYCBrVVDS/fYXj\nm/5E+PWP0CVYSNswA1tCL8IQG8hZMLYYmnfB0g9QkocSOWpCTD2LUjiG2PIz6BiMNNOMUF6MUN4N\n8T4ItqDul9GqFWSLSMTQjvPg25BUx6enX8T12xasgpPx4gGGnbaia3fTWusl7d3diPYYMAiog1vo\nUu8g9XQyhtJWKjVp9KYWoHbWUag+gXDMgmCbSc95D8Gr3WjGzyKhZglsvA8EgYltB5AVBZ+ngz7l\nD4jjL5B8IB3hiwfxLrsd2V+GEs5BOP0FlJ2DpnMwdSSIpyFlPCgKpq9tCPJZsL2M7sc7CQd+j1zd\nj7Z4Dix6BprPQowJ6hS48QGIbYCYWXT1P0q6XAVCLsK5d0govBeufhZKR8Krz6CddzNe6Ty+s7eS\ne9lDfCX+meWJl2HVpcL+aqAS+gJwYhPSxAu0JQzHUBbFfLKFZlUx8f4Son++miy3jFcXInWzGXO/\nh+bpWfRufIC4187hUGcQPPcZFsdIhDm/RWnbivjR8zgnpNAZ/AB70nxUciUoKrStBeB1gG8LSkw7\n/cGB2BMSof0U7PCiDDIibHuXednnEcwpYGqCrjpIyPmbMAhgncoo6zjuVL5hknyecYW7MR/cTsaE\n7yHqv+Ra+b8I6RdyHvpfyhjA20VKOI3G4qkkB8rgus9AiUDDQBi3Bd6ahmgQIBXEvmT8yjC0wTaa\nT35AYm4aYrAcb2EVYuc7aE3XQc12GLEMgmcuJeVOXgnNv0PTe5SO6Q9gWLuFuPGbCdUuRVJtJhg3\nEKtvBlbOIrjKOD1gBhnaB4izbYY9eyApGSVJgA4JebgJX6obfYkfTSooN2xEWrMYe/AQYqKD4XPq\nSZgUwK/x0LazgZo2FbGFFtJivQTt9djqwzBrFoJWj3SuCSFYeyniwanBfCIIjgiBM1WEqz3IMSJN\nky7HZqzEKLkQVCHKegpIDOWQ4S5lhfAs4YO3sCdwHnfAQG+yBjUJaNsjOPsEbCc/RdUroDNvIzZB\nQDmnwZqYTPYJP8diWpi4twvBpiBMiCKXe+lPr4ZxekRDLcKwqyChCOXrhVj8HUT9IoJawbh1PebJ\n2QjXr4W35iP3TEVpVMH+yTB6LNy5EcIBqFkBAz69tL4Xj6IyxoFfgoMvgpCA+owH/9WVqL6zolJr\nIbsYDtwM1jng/QySXoLuP5HsAmXSchS1D8VVi1j1MiRPgQsX4HwZOJKIqZpOf+xRBn3+EJrR0+jz\nncRyUI9w59eQUYj/8+WEp17AZe3Fqk1H1+vFXzScQF0cEauVpvmxGHxuEjJfw3awBFrfIvm9ahrv\n34C12Unil6eRz/cTfnAlKqEb6ZarkQxlRC98QcLZQeh9F6FXgmmL4cp1KO+PhQEy3a9B/4kK9M9l\nI2ZfjnLgezTlXrrTk6hOzadQkYjr6cEQ+z/WgjBL/VzLXk6IhYjCLWRt3Iqq621Iz4LiW/6PiudP\nTfRfyvgXhD0bh305Z3gPKiLguohgTkIhipI4AGlGKurv1JBrRxiRSiR5BOHdR2l4/QcK0x9GKPgO\nIVFAMeth7W1gF8CeB+RBSwlsmUJUTkalFUmMXuDMCgM6WrFlfIKyfzxKJEjvmw8Q/14qEkaSSn7E\n77pAcF8pOr8Pgp0QAQwphEaIqE0qNFoZuregtO8mOk9E/NCAZssJrEXtYBcwFqwgb5YMdBB+dy+l\nG11oZJng3DgSNdMRzj6GaXE7iqJFcYURdBGU8/VEsrV0DtRTNnwI+SdrcIizSH50IypPI3w8jUmW\n4yhTy2mOSSGt+xZ0QiGFTQdojVcRcGoxz56M/uWv8JrtdMxOB2+EuEg3xlASgtKNkNOJLhhl0ndH\niTFFCAiD0Kh0iMObMHVJCJ0KalcHypeLCaraEeN76LJnk9wXQJgwE735C/jGj5BwAPxhYt/9E50L\nhiEUnIOYZhSNhBQ+g1rjAH3ypVwjP7wLsS1Q1QLJqQgTn0OVtRBv+CBa3wJUUhC6S8CaD24vyHbQ\nF6HUraLeVEaqNQPN0Lm4f0ggccQfoeIevLHzMPfLUHUC1bHd2N09+Puc5FwoR+5Ppuzy8dhCZ+g8\n8zpZXcfx3KnF3ujE7H8XX9CIMW0BYu4u+u7W4rBX0npvJtnCWJiYD6b9CLuPkPZkJc1+e2BQAAAg\nAElEQVRPp5L9nhMEPbwwD2XB9XgXBohO6iWh0ki/bwg6rQsW/AraP4DSP4LmJBGPDXOsQGh8Ifqs\nVxHVeUjTT6Pa/xhCpJvyuaO4IvAx0dgbQaODc6egtgquuoGgUkG4YRrTdw1kwq9W8VfDBQafOAKG\nCriy6mcW1v/9SL8QNfjLGMUvAAGR2P46FPs+hNJ3UQyTkOmk05VPgqsL5YYiOHQKIVyPv7eNnDgN\n9WMHIzYchq5yzEo+8m0JsHAVfH4tNJRAypBLCYXS5+M/VoJ+yJVoTGMYX/8QHdnrwPICQsI8LD2H\nEd5cQKj7XXw782ldXMSoipN02oyEnhpKXKQbw9kiwuoeJFsL5g4jqO0o7hZwRtF5JOQxXuh3oekR\nkLNGIqYOh8Dn0H8P2rRehj49AFVdOd5RK4jsvx2t7EVMjaNncTbGw9UYylwICWqUxkR8DhmL3Ygn\nL4fs+fmXSv7ZMsGeji/Jinl3GeHrYvANKyRWvZCCi7VYEj4hbo6Fg612Dr90kgeemUNaexXcMhp/\nbR1ilUI0Ow51Rz+GAelEhOkESzZiHFaBfzdobtFiD8QQRED3Bz9BZymaWSLR1jm4qtpJyBLQpbrA\nMRns+8EyDFJHg7Mae1UNUlIcnqR+IsoEHI2jIfORSwu78UVoOgmz7ocLT0LSBMi+AoBk3WS69BnY\nzj2M1tkCkzdAge9SFq+et5Fd2zGcGsjZmyZT5DDif30T3LkQKnIRxE+JTC9Gc9ksSLARUR2jOqMM\n/VkVrWmj8GhCjCrdyIjT+2m6PQVHg4D5h1bE3mEwpBt1YANZiY24BxsQ94Yx3qhFivOhUZnhQCVY\nBDRDh2Lf30jnHWNJtq+mgS/QabZg6AzgOC0hzzmDd9sStMk6NCkOqD4Gwh6EpnS0kSw088zI4hxU\n2kIA1BRD0WgSD3zM6s9W0XX3cFKzn740Tz1dEI0gH32BYNEGdDnfYigWsDxwN7csvJyOGQkkTHsI\ntfg/2WZSFOiuhpZzZPUehOgSUP9Ckj78J/xS3BT/2sD7O3Qxs+lL/y0oSwjE3oosSSS+Xw99ItGw\nHyplfJUWfBnxmOeNYeD9qyEpEXwymmt3oI1OhfqtMPEB+OtE+KoAjKkw83v8GQ/RM2YuRx23oZyJ\nweY8CaFuEBYiZNyMIWUxfVnJ+Kf3kvT4LlytURKH+UkTZ+KJ/wul0xLwZmZirOiGyhpkqYXogCiM\nmwARAaUNouUCigRC51mUkmVwqgc+XwExY9CZk1FPvhXbjvfRqnTQ3oYSHYt1dT36QybwRQh2RTgy\nJ5lw/gAmV/cyxtuJ9tBTEI2AoiDjRBmRgv8qOyaVhhhh2t9mTiEpYkWdNITpgRYGxht5dc6NKN4A\n1BnQ9plQNbpQBQYQvCKVvj/GEWk/im5rNVJsEpphakItdoTRArpOM7rxKgzPDEE9bRr6x+/FdrMH\n3crbYeKXoB0KwXiweUBQg+BA6lDjTw8RVTmJaZ6L4HcT2Ft5aWjnD4AtDAOXweACGPXgvy94fzPG\n1AWwby0Y4kFjAmMCIY0Tufc9aFPjX/IAw8Rl7LGV0L1wLJL9t0hDRyIleohe34DUfxHFFEOb3UiT\nPpfmxZPQj/01Yw5K2M8dpGmJlQTPaPRZcxHTcwkV6dAdkQgf78dZH0vSLDfJ9/gxtLaj+fNTsGIU\nSncYIawgJfdji8xGxk+/tRIhoxNTzDDMyjxwtSDgRS2dpHauB1quBqUBtlgg+Rq45QUEWyqWUVl/\nW6Eo4db9NAw8Su9oEUHrwXSqCcFouzQXTeUo594gnP8dVu1nGCJJMPIyeG8Tqf292Lp7+FLvQkL+\n2/0UIvT9+1x2V8KbE+GbO3EbUv7bKGK4pIz/kfZT8y/L+O9IoZC2hHqMuxqoHHGIYaQhDG5B2qWF\nnd0IMxMoveEmnNpTDPn+JBmjRsOQp2DvBdAGEBQrHHgOpq+GwXbo90P8FBAE/P16LhQnc1EMMalL\nh97ZBW+PgGkDYYgWZ//LmCNxWL+oQB5qwBftwPVaCHPVn0maoyGp8EH8pnvxD1ejazaj/tCFaj5w\ncD/heBXe8dfjLTlEYncXgqyD8yFERzuCLMO6F6EgB9KKYVIIhj+GPCKH6B+mEdjWjHbsdMLX3071\noHImVhxG7G9ATo1BOFUHe31EgmmErxwEE93oa4KEYpZgT/8NomAGSYKta+FmI209YXymdhZ+N5kp\nqWl0TEkiYe8ewlPHg+4YnkHtxIbaMOwK4hFE/LWT0K68h9Aru9GfKyFyhR5DYR6c7obSkkuhgVs2\n0qzcRHpnF3jWQNk60DvBsQ7fzDvo+f4NDHO6sZYbMLV1IoTfQvjVQQKvvoz/ozXEDvcjXvMQRHwQ\nm3zp6Pa/YU7CXPsdcsiDdPRbxOHPIRhT0ESieMMthAI6mo0lZDCHGGZSf8Vmzp3fyS1jnqTPJmDs\nfh/j0XvQt4qYzDbsRXkM3XcMffdmOkbL1Kx0kLtrCMYTYXjkFXDmEsz0YDLLeOKDGLbqEOsdYNNg\nmBogkLgN81OLiDSdRN1ZiqqvCOG+D0n50zLqZzyNZfBKtIY2hIwFkO9F2LuOSKoV5xA/tfVq1OZC\npJt0SMVauPA8zEyF0EfAcYRoBHXvTvryNMiBOMQ0N0kbq8G+A6X3KErZdiJXqNA6vkQkHRpGgP1t\naDoF/r0YR93LiOPVrFG/zPgRFwlpO8jleWIaonDkLyDooLYf7ltDUuT7/yhYivI/Fjn9BRHiHw1t\n+2n5l2X8bygKducuChseQi2dYui35xAe88PuRHwvrSR641KkqsEMfvZ7RCmKriYArWvh0Cro9UP7\nHkCEuEEwYD4sKoHl7aC6VBfMX1dHS04KMb3rCKS54dBZqGiH+DBSvQVZCGPdXooghxBFLebMsWhj\n45Fa3VDyNIRVmJK+xaR/GmG3Ca6KQXbrcHZZULoFYrZsJMXRjUtJRWWxIRSYIdiIPLoFZYru0uEG\nNkK3HfbuQXnrLsS8G7E+dSuB4hCV8XUUpb2DOnEVJA5GzFUTWiHifUVLdEwf+hovplPj0NUYUA5U\nYIr+LVmMSoWSNQByvCS21WJob8LjcNOT2E/ttxpuuO97/mIdRXiKnpg9GaCKRZ5wL6rBzcjvORFX\nf4pq7zFCNw2ArhQEeyuM6SNgdCGnDAH3fMZc/AiO3w8nHwRLB0qHlq6XWpFbbsGcfhr1Bx5CJ1vp\nKlXj7ZGgrA/Tbb8mfGg3Yd8RZL0fufJVZJMT6fgy5Mg3KIpyKVVn/lQQdZQOGYr03jiIhhHL7sO8\nfwyRkBonbbSynhq/QGvDEKad7kLr6ce+vRPt1gCGHyOomgIoiUaaJuegefwW/Kut9CxPIKFEjX71\nToTEXITIJsImK+odfaiaPVgveqhNm4/w7I8I4WxMrRn4L8rI5Rl4/c0ICQriwDLYkYMwM4wxcRQt\n2teJRBsQlVg4fR4azhI3KZG4rhDx7/aR9LWfzNNDyWu4mfwfh5Bvfpf8HQ7y+R15X54h89AFhtX8\nhtTDPsLZhehu/DN8cB2RwnKkjD40cS8gRhPhrXlwIgJfT4HyzTDoChRHCsmXZWIaEeBAn52BD1YT\n86dHoO4AzHkRpjwOyaMh5XPM+u5L3w05DO0fQvtffwaB/seRUP9D7f8PQRA+FAShUxCE0r/re14Q\nhHN/q2q0QxCEfyihxz+3ZRztAd9e8B8DJYKAgObjOJR1TQRyWzDOj0BiBMvuF6FTRkqIIg0zk9mR\njjhGxCV1Y6s8AC4R5dOHEX4zHha8D3HD//0ZmkuVi4NdjSwLXY6trBu90QYlWrg7Hwo/oEn9Bglf\nGBG67TAhCTktCznvVcxt1wLxKFP+AK0HoOB6hOYy1Iv6wSqhNudif6QBRaNGVCyE28KYjrajHBUR\n7TFIQQvi914UdQQ5BiJb7WhsW1CZoqhkHRjdlE2eQc47mxhdfhJhTim+nEr60nWYxHhi21PQNXcg\nixBOOIf+aA/RRa8SaH0T45OL0b68GUQR5udA62nUwTKSJ31Kt+d7rE37aF9kYqLnc2acKqF/jBWj\nHKJbMwZr9gH07WZ8P7gQhluIXjWXcNF+7Gtl5NkOxMua0WyYQqSyCW1JCYJFAtkGfiu0uVFa3FjH\nX0ARrOhn/JauuBJSWkqIEYdcqsK49ldoj3cRt34+/c8dwrK8C03Tt8hZ2SCtQXZ+hmwyIIgjEIpt\nuAcasR2opTfHQcKaIQjxUcSMG0jcFSLnnIvYvne53izhHf4c0dXrIf4sSsZE6uZczsjHvoLsHILL\nvkZQfYJfEdFWd1D0iQtVy3SEe1Jh0knQDkXKnUH/8IsYd4aIZHeS8UMJdL8Oz6zGeOEv9Ot78H71\nJkZfCGZDX1oEKSeP2PAW4jyj6Kwz4lFvxay5G06fgYUpGPQDyPjwBDqDF41JhoUr4eOX4c5XQWeE\nsB9ajoOzGuGqA6hOPkXzGDtBsQXFXYry+CYi5svRN6UgWLJg/z2gN4DHDXmZKOfD9F6XQXjrOxi9\nRVx/KpVNootDl01n1odHYewIOLzukiKefy3oPsTlG3kpF/j5ReA7C6Mrfwbh/sf5L7ogPgLeAtb+\nXd8riqI8AyAIwr3AKuCu/+xG/9yWsSoGBA2EqyHaCsc6IOpEmGpAzHQRSTWjJAbxFGlxpTsI3rEd\nw6xTFKgfwJ5wGz5ziODoGNz5cfgK9LDreTj1Obia/8Njuo9twV39I+HTPjZM3oHgy4CFq2FQIpJB\nizPQg841EF/fYNy2HDblGxB998Kdg0DbgbB2KcLGJ6D1RSj/AFxaSP4aRq4AOYNI3HAwvAbOKYS6\nA8h5gKoHISKhBBQEl4zSH0M0aRCSLRVFlpB6/VQYvERaz6AP+ohmRfAOOwOhEOb9YbqzXETGfY9g\nG47o9aIrU6ChEfXbd6PubyY8pRB+98ilv6DJe6HUDdo8VMZcSI8j1tnH2IlTmfBJAwWaGoweN9WT\nmxDXH0T3Tj1ibB6qKfPpIhP17G4MrmykBQtgTx1ybxZq9SmIsRFcZoJcBcXjgmALYcNA3Nck4ArG\nUJs6imjCOqzSGVCbIekBOJkIU0cgPJGFpukIsfdOJPjsIbDfiLphKeLmG5CDw1HJT6Kuj0OoO0FM\nJJO42U+gcYYQMqrhohdP+2n+/PIKhpyrJqGtCWveB8TUnifad4z++XcQGj0c0WSBAQvA1Ynxoxsw\nSTYcylOYPCtQNUoo1+wjMMOJpM4H2YLG2U1KWRCh8Tyqvl7EQX5C87xw/i+oTrjpyXJwfOZgNt26\niAZ7Dkc14+gNGon2pqF+/zCJVVY0koxweAFMzgRfFjxxBPNuH+riLJRCoH0nJGaA4++MsR1vwpJd\nUPkG5N1JRHCR/6WAOP1ZlCQBw4aFqPa0IdSugvhymHgW5bIkImei9Cl1hAWJpBo1hr1fUpb0Kbpi\nFyXXDcW38yxK1WZoq4CTW8FaAQ2JyM0CtL0HxoGQ/z6orf8npfp/mf+Kz1hRlEOA8//T5/27lyb4\nm6P9P+Gf2zIWNGBdfKkpUUj1QcwN8PEuVEXDaNwfIXbVSIS+c+hmP4Gpohsa1oN7B7JZJHi5wom8\niYzwlRNV+uiedSXx0QLY+dylwPipK/HGVODseJDgOS/6wt2EQwLyhXaEa6/GeShEzYHZhM8bONNe\nyOD+KnSPH2Xy40mopAIYvhIG5YIQhI79sPF5cABmHRiGgHUenPwB6bJc8MxA8+NLqEIQyVuJaso1\nKEeuR2o9i+awGpXXiG6SgkrlQjBMRhj6Kvk//h7N9iMoaTLOgxkkqIcjmgPI3+8m0i/ivP01kqxF\nCN12BNf3kF4APQr2Y0Giw9sgcQTK+0/CfDPCiiY4vxyPpQdRikfntyEqMWQaFdQWIwZnENcUE5Yf\ndZyelszo9VUII8Zj3vcpgfyp2MV1SB/dgFCejKy1Ii9dhfaTX+NPtxB0WtAkA/GFNE7KQXeyntBM\nPUp+J0F6MV2AaGQcmvdehuIlcLYMHvkeqp9GdWEdpmw/nrfDWDQK0Xk+VGs7UWvOw9gliKPeRTl4\nN6ZxHcjFCkqjgJLfjf7YXu75yo3KOhK2bEbI+BEzOryzH8ZfsofeiZXoxLxLFVN+e4bGU1egrz0E\nvg8Qt9VCohpBnoPOcx5JOoiquhlN72mQkpDjBMJJGr633EHfUD1p+44iz08nzewlKyObqUePobH2\nkXpoJ+peHaJDhOkaksQ9eLvsBDsk9KpmOLgG7h+BP8dISAmhCzaihJ7HMP/A/2tl+fRmfhhazJWt\n6xCyrgVTEelHUtAXXwGqclT+I3C9Co5oQfkRRbOUnsKJSIffxNLRzTe3X4PKV8HskZU09F6OL2cy\nDkVLizyWJYqTj/3tJOEH6Sg0uMA+GaOtFyQX5PzhF+0r/jd+ijhjQRBeBJYCLmDaf3I58M9uGcvh\nSw0u7cxfWAfbjqJEZRR/M6keP7Z3QlAZRP3tfXByGTjsMP8bvDl3oO5NoSAygtJxhQgeGbdrLzS8\nBYteRR6/jMAPC1Gv+RVJ21tJXrgQUTEx89E7OFHpYd8N2bR/+CLKZBvjn9/GmNUvoMsdgXd0PNoW\nEfS74PBamHArhEIw8WFISYA0LVgXQcVhOPQncPsQOpywdxVC7nSq7LOJZBQRsnxFdPZC/LfcQuDd\nB4m8tQLBYUGo9yNRRjh/LdJSDdI0FUI4gYScZsS0AWAvQLIOImZvMo5ILAgnwLgXYrJAHwKNGnHq\nA6jG/ga4CJFP4SMXbN8E1TVY15VhCN3A0SX30O4uIbbrBCgeVE1m9K4AWkMx2Q9fJFDeRfepUqKB\nVDR7U3C+NJ1oVwPC759ApYtF/OQZhIt2DOv1BK4UkdJDXAhI5L36PYE0P26hF2u4D13PndAdguZy\nWHwTOEsheSj87iHoG4+U/BjhcRGi888hnT+Deo8KdfGfYeB4mHoNWGwIE19D3HoeubGXjUNWI/fE\nosoOIZ49DT4/pBugoh4mPY82KZXg04dxbOxCogtXsRa6nmKk7gyixYW8pw4lMwnG3QY1fsQLUTT9\nnaAvBy8oBjfyIC2tGXfQbzGR3yIzb/8Frn1jHSkGG2lFv0GTMRK0LlQJPUjxA2D+CuiMhUNganGh\nLe1DOSvBJBs0V2HYvRfTicP4tVa0io9w87N0BXp5ky7m3XIfSkY6gqCC9Nkg/hX9tEMQ8wp414N6\nKPTOB6eEnP4nqgfIqDrbSfz2CIpHxJ41ndn1G0jVdjM+OBhBWcgmdwZDzn/A2n13k+SrhsGTIXkm\nTLwBsvPwKQmQ8cR/C0UM/zWf8f8MRVGeUhQlA9gA3PuPvOef3DJWQ+1SyH4X+pyw5R1w9+PJSKZl\n+lQKbn6A4Ic3YhJ8KAlRIApTHgBdCqx5jVhPFdbfbcfbehUtA9KIN5ugIpPwd/PomavHppNpf9PF\n+dMSloTd2B+6Dm28mpHLRxDsrqR+eiqp3XGIuybCd3523zWW6Wv6UBcUw7lWWPIlHKmF8yXQ8h2Y\ntBBvgegR0EyDzPHIcSIq7ylYtAaA5sBfKMovJaocRn2oG33+XKINu1EfV8ArQLkW0VKM7v7tECvC\novcgdznUg9J+BEGyoMx9AE3RUFS20aCph8DDUHkaRjeBko4wxIzaNACW3o/Q/QjKfYug9gUY5oRD\na7DW1DPh3gcJJJ9CiEg0DBlHv95DUkcTkYHpGOs0qL3xOBwiwR8vwP311A9OJC/Rjvz+Q+j7QPBJ\nUJiDsiSCtbMVzwwtaX+sRci/hgHb19N9g4nz/iGkPfkGcncI5Yp2OPQQZN6NcmYNUtdFQplrUKbO\nQOsbi2m8kfC2MtRn3ehWT4dX74A5N4FGA/Vn6LWH6E8azdWpK1GZtoO7GZoVUPbCFS/A3kpoqETQ\navGdKiX7Sx9i5gBsl32EL9BASeoIPG37KI8byOEJM0kVa5l78QQanQmy1iOkT4Pq+ZB0kMgAgWTH\nX7miroi8tyx0jE2Epx9GpX8Jp/wamuYLqEw2VIUupLVHiXTloombC+vWITSr8NzyAgcSXMwd8jKh\nxs2YXJ1of9hA3P46GK9CU/ktwS9PM9aRyYKQi6xAJySMgkM3Q6APSlWQEAXthyhD/opiV4E1G/Hi\ny+SLtyDUnwWNCnNtgKsuliD3NtIvmmms+Yy83duZas5FzlyEbvBjUL0UOtUwqhPcZoibTa0vyNif\nU67/F/mJw9Y+BbYBz/5nF/6TK2MR5CBcmAkDtoGqjmCqhU13TuSqbXtRyzF4Imq8eTYsLgcIMdDw\nAgz4C1FXOYaGAKht5AbTOGNy4ZOcuItMmH91muS/QmisgLF4ISNndaA3BbBfaMbtSKXmgeOErs8m\nODaJ7GO9RLLa6LpmIcqwMagH1kH6QEgsQDHYUIaXIHZZYdSLcPBGMI+CAe9Bz3NgextVZCaqvx6E\ntMdhcDGRoAWD6S5oLkM5sBThTCnekSLCPbsQVl8Py5fByGw4uxQyvoLNj0B9LMqFLgTHIVj5KZrh\nixH0+ktzZMqG3hbwz4Bq76WThbuOgOYPUNAIprEIn22HJ5+Azh14ZsdiueMdpO730TWnIt7/Ptly\nH+7gXrqHekncdwx9fx/qlCGolo5FM3oaNVWVOJ9dhKH+Q6Rzk5HMVaj+H/beO8qKKl3cfqpOjp1z\nzkA3OTQ5Z0QJZsWIYnYMqOM4JoyYFRlHURTFhIKSkYxAk5rQTTd0oHM+p7tP98mhqn5/tN9378y9\n67dmrnPn8353nrX2Oqeq9tq7VlW979rr3W+obEO40ITYIiP2Kqjq1KgnqxA+OI7/sQVYt29GmGag\nfkQuydXdaJoDyHiRmlfhezoBrWsRxpc64PIPCVIAYjrG59cSvGoG3juzMYy6Dr54GWwHYNhCorLc\nRJ3UgPg5dJyC8IkQsQu64qB8H9z4PLzzOOG3PYp9VCbufA9Sdw3SkzfTnNXFgCMXOPH8YAIzY1jS\n+wHtFbFsmzAbT6+KfDUMFEBlSEW4oGAIG4XYVYyxzE7b081oxalEHnZgLckgOD4PsXc7zoXjMV2o\ngQwLYtVHKAciEFzAzESsQoB8MY5PqGKc/Qcy2/ahD3YhqIzQYULOHEvbYA+7kx/g90ffQsx9CgIB\n8Dng8I8QMKBYoxB8MkK7habiYUQe34I/dzSREyaALg+qfNCxDWrOYQ8Lx9duJF+XhGrMKjBG/OWq\nt20fOKshfQ2ET6FP//zPIfDrXduEX1rfgSBkK4pS/cvhAuDC3zLI/25lDBB1LTS9DkeXwqAgSm4O\nkl6PryFI28x5pC4po8taj1JwDUZbHGjNIAWR6EQz6Y6+MUQtg8sraBgVhdH+BdLkIAGHBrFcjRQ4\nRfRVHYT2ReHLNRAwLkCX/Cq9d6QgJKRgzFmP7PyB/Zo9zKk5j7JgGa6v38V7m4Ojygjmn2uEgR74\n9kbolKG0FMLuAEEB9XBIuQI6miF3CAwai9K6F1ovwuvTEdx2eGcfimk9gV2PoJt7JyRJUPs55F8H\n4SJMLoH79iLfcQNiWxW8+wDCO5P6QokBJegAqQImN0IgHLIeQgikwLpX+6IMw83QewMk2WFjD0Ft\nOMpnD6Ju+gBuPw0JBVC8HVP8UFziYnT3H8Qu/464jfs4VfAwwy+8j39gClO861Hifahjb0NctRr2\nN4AHlFEgnFfRrY4kLthGqMVOsKMOYyX0zzlNVEU3PdkRiH4r4UUq1M/Ox9J8DGzfwRP7kZ+5HPGy\nVNSaFxHSJ6EZOx132XnUchHyT/Vog26EihK4exn0z4GSl6FLD45KsOhQItwoN3+CuPExyM9C2vU5\nqieHQiAT66n38chnsUxJRz9KITcuGV2nC5MnjDSPTOYnW1D6+TicfTMfsItryg6hHT4e/Sk/Ojme\niP3t6D9z419SiWvYRlRmGe25YyiSxMGYKyho2kua70f8edFoBkWgHj0Wxsmw/hMyrlyBMehC3XiY\nBszk2Byg0UKHi1fvfpWBjW9zZdMfUc38EcJy/+17P3oQ2RpFz5VaImplGLudZJsMX22hvCmH2tc2\nM/ipp1B3HICnj4HtILH2PRC+AGK2wOblsPBt0FvA7wFVCAYVQdpnvyjif0fXJajZBV1VMHAJJAz7\nJwj038+vsRkLgvAlMBmIEgShgT7PiXmCIOQBElAP3PW3jPUvZRw2Fw7eDaazkKmjIWYxC8rXUNpv\nNLnf3sUHS+czxiEjbjkNM5dD3SI4dxx8ToRJN/a5dqUsR2zcROKJo2AO4qoXkXoSUT90BVGbv0Nd\nY0A3cgqM9GIY/Swxs47RGxFNgfAiKnScM2cwsCOAtbKF87NE6jMKyT5QxLwZdlT5Q/vy4Ka8BIX0\nFf/s/zQQRG47DN1rEG48hhCeD4CgyFBdBBOWwL43AAVDbSbuyE/QKRK062HUxxCogEs3QtgA6PgD\nQn4ajuRJRGi/htI1MPWPEHDCgXHQEwGn2sHvJrTgEOoWF4LYAOfbwd4Ed2oh5w4Y1Iiv9RChjnLU\nnXnw5isIeMAai2qQiEw06tYDWKdeh7z3GJYv30bYU8qguS2oPN8BVqj7BiXmXF/yojEiFMkEfQbi\nNrWh98rIqSKG9REIdg+xL3bCLLAYQpyZNYr0ZfcSU1+HYLNCgRUCryEk1KPePxAK+rwLBEs0Ed8c\ngi1vIWs+IaTSImnD0eU8gLDvPjjhBHU6XL4IAucJlZXTYXkV7U1DiTjYQ+fxnVjtLeg3BxGb3MjZ\nYCq9BnHSMizaSEJ1TyFUH0Od7QMxiFAFE1fdwMQHKvG4/dh8IcqSoxhU0kP59ZczLaUAfczX+E0T\n0B70Exh+BNkaTVjHLrprO0h2q1EyZuOYd4iwVw+haolHyB2PrXsdMy8IGJtbcLdkIbiioauGQKyO\n4YoPfcqDeLp3QMODkPcpaOP6wp1DPbQ/ArqAHVTR0PYJgiMK5s9l5EfraD+whwPXXM1YOjHOLoCK\nLRAvwckfYf4YyPldnyIGsFVCwhGI6gcRC/9Srhz1ULQSyr6GOat/s4oYfl1uClGWKPwAACAASURB\nVEVRrv9PTq/9r4z1v3sDD6DqM8h6FGIjkfXRlBmg6WIiEyN/Ji4iiSVrd9CoDOK4V6FHsYC+gJDU\nCForGMP6xjD2h4yFaOPTULsGETE1g+h7JxIeMxTNgsl4phoJTjyDInSD5EfJ+oABF3pQKwY8uKkW\nKhmkXozN7+Ed/dOM8taQta0edcdtQC/oNkHEOIgRocUF515GOv4xnrcuQOYRBOcT4C8HQBFEmHAr\nRKZA+ggIeFGvXY3eZ0Nq3YSt/yQ6xGN0e/cSopOu9CcINHYQyEqkbblC9VXZuCtX0lF1Od2bFiJt\nr8Z1yEbomB/5qAvp5Wdx/PwOttR2Ou9W0/nls3RlRmKLk6gcW4/3iny6phmoff01qm84wv6nZJqe\nWgIzX6JDNw8hsgCjYMY/PJrkM5fwJUejavfCdzfBN9fC9lcJBU0os+MRCkWEFPANsOJ8cBDO155H\nLY1Cdf8UhAFWhHwB4Ywe5biVkd1uOgLfczhsDb6MSRB+J2jn4Lp8NoHyk+zes4/Syktgr4ZV94Pt\nPOJbJahjxhNEQXovHbnnAPJVT8OsmZBQAgmL0GiiSOJ9LMJl2CaF8NxkpSU/FvugeIRUENUGhKPv\nwQePIj59Pdp9DdCUCTvCIA4YEQn+HlgxC2NyPvHX7SQzbCiR2lbGN/6AlFmOO3Y6Btdq1NUKxsMa\njN47GOm2kTOoBnmYzN68NFqaxiIERVzxLRyda8OvbiW2/Qw6rUy4vgbljiU4Mix4E6OZubuUIms2\nJ6ImgvsctH7Q951eKkaK1BKKBJ0vFaadhJZvoOs0PHYj1L9IXNg8pj14Co1QRtPHryP5a8GUCTds\ngYsq+PlekH/x1EoeAppoSHn13+TJdoFRHWvg6Csw+hFYehoG3vBPE+f/Cv8Kh/6tkHs7qLTQ2IJN\ns5VLUgTx9jQasuKwDO7GrMxh0e7T1KoXst9/ClP0ZQxVfsQfH4lCqG+M7g19icS1E2HuCpBCKFoz\nFD2EWLcTc3YPssdGV2ojRmUNet0StBEzYe8znA+XmDLiAQT7MVTZV/EhSYgP7UZunI5wZC8s3wae\noShWP5jfBt8xlOINoMrCdPcIhO73IH452P4AUU8iKqE+399LP0PhLfDd2zD/KkTrKOxjClHUehR/\nJ9aGdbgS5yH7ShCOdeO+LRV1VCYdkQqJuzqJuWE7QruEMiIPKU+ge6GNR0f8wBWmM0x0nEQjhEBb\nitKwEckwAVeoCx/tiN01ePVmMg6/gL67BWX9CJJfGwcGMBlSIHoyiuJH3rqCmon9yJ2+Bskcja1s\nBeFVX6G1JqLQi5idDxeOIYfFYhXakEUXAfU+XLNKCNZUEOF2IMwsgDwVwq44Qq9cIGfBbs5O6sep\npjcZv/k0pI1FF5GCY6aZiW88Sm/mC1SmZkBnHWZPiKgnR6Jud2O4W4sqLYiyvJuakX/CnpzH6KQY\n8MP/I4N68knYa6JDE0bPmEQ60qvRv5KHRROHqKlHufY6vBkygZ5DmFbvQtXTg/hBFCSqYHY/eO8M\njH0WnaClX74GYrPwf34OfjiD5YqfEb68DPJVkJCG6NmGwWdH8akIBWDesfP4ImqQdB5c6mQUd4iE\nhsGQdBApUo9O24Lv9AZ6UqM5evVo5n1+kmYW02QeAMPLoOl1XFILR8PWorm1G3qzKU0ZyEDv7xmi\n8yKbjyLqY9FWXUSnT0Qsjked0kvU3Fl0dn2DvtmKqeMtlIVXwL4HEL9KRF64EpXheoSc3/eZJ1pO\nwYl3wRRDaeRCsuf+TQ4Evwl+K4mC/qWMVX3G+4ASokNvxtQtM1TQsCp/Bte2rqAzIoXkrkiynCVk\n6N7Dvm8pF+ZYiZMVuoRzxPa2Q88uDqfeQ37EU+j8V4A1DwEVGtUpxBQZwmORgyZMm1rpuPN7wkUH\n5tS7aXIuJePHJmKGPAuVm4mbthIu/hEcFxDniVDSA+4mkHtRdGYIrMT7dRwGtRaVKQNaAxCdBhW/\nhzAdpD1DjHowHFgCjSeh/5Ug1MPIm9HQt1CjYi+cvR08FvQHt0JWAEb8nhh3EdEt6WQXHQKpAS7v\nS+lMcpBwdzu9PWEsOLeOQd2nEHpbcKkMdJsy6Wc8gifRh8ESSVKjgkodR5fBgampErKuwLSojfY9\nbxM3/XdEK904A/PRntmGqtfIgD11cPKP3PvKInSDxvNaxnDE5sWo7GMhK5KQOhUp4EA+bEK7w0Ow\nUEXYeTOBxC7q7kkkRqrBZM7AtugCjQWxFOquZdShV3GV6+g4E4azrpzYnHIsBFDNHoP1s8MYR02B\n4q0YnR68g2KRrTokSeYr2+vkDtlLQyCGCQcOELzgQTVcQNT+kvCm/iQcWkVk/lzC9n9PICjT3D8d\n57g2LMc9+HgM5Bwk4yXEKX70HQa0DbNgYBBq7ZBXDaN3gfw4hK7D6z5GxfyxDD7XilAcDdNU0KKG\nnjjk5iZE+4MI2hCquvcRLjuMXmNC0itEqZsoD2ZR7y4j0xWkN+tKjD+fwivVk9zeSSBiAsWzfYyX\n9RhEhQpNK7kZL6Bq3UK5OZP59n2IXTfi08k8mnA10zLTear6XS4Z7PhzLUSXRJL14Umkx6ajmh5F\nTFEHwRMBXFU/YE9PpGf4QoZUHUC18VWE6cNAlQDfXwORuTDjDTDF4P3yf9YG3r/yGf9GUGQFZ1Mx\new1OUlSZLHBsQ6NycpfopD77Apmtg/l85JNk7dnOyO+uI3bcg4SENaj9Ck3etWAP0pW8igbFxnjD\nFoLrH0Vz51u/JEcZidyrQWk0op6xDuHSPGLvq4WYD7E/vJHObCMFA3NRVs1G0F6Cxq+h9v2+pCvq\nRMjzw975KFFG5PIzSBY9hjsiUco9tIa1o99ZQuBuHbGt5xH2u1Dm5zHRsg/FkY0Q2wBbXoS59yB/\n9yBibys0noLkVohSQ7cekECRoXAR9DYhZA4Dy/solzbDofehW4HIWhRdJBZLGrPHFuIVhxOlGgaB\nNsJaVlBfcCOdlgsM7/kj6u5HIPt6PNoNcKkeBr5OfISZ2qMjca87RLDjCHg9ZPuT2fDYHOb8XID+\n+Sco/P2NzAobhdos4ogdQ7imGNyPIie+TjCinUDQinwiCsOhHoQfbegmR5Pe66X1rsuwG5qJO91A\ndG8bUk8D6vQVWHXvYR0WRbizBt9wBfcBI64dbSSrQdn+Pe05g5DvEvCWmzD3NNHYkMHIa+4mS+ok\nI96Euaceac05nCe3Ig3PxqR4MEalAxGoNq8j8IIO7V4FQ0UCgq8O/yzoTuklor6L+K+1CBkmBF0B\nRCSCtqnPhn/VzWDbQbBrGEEpnlBWNlXfDWNIQhfsfwo0QJWAYu4ktNSD9o3D8PBshIF+pJo5NI50\nEKdtRy2qmVxWTmP/XKp8PoyqYh6482Pe+uYeVK1HmL37IKcLc8m4sIId+Vb6cx0CApuFMuY5z5L0\nkQ1hehTJ9e/ja66hNWsCqzJu4v4dW7EYI+DyIlBHo573Z9ClgfpFtIlXodJ1sNFtJ1N1N2LwAqR3\n9dV5tMT0pQDQ/baj7P5vBPhtZJj7X6+Me95/nOJBpdBPheSSCY/vjxj7BWbbdAq+yUOyuLnjshfw\njcjAu/Y8L4wNMCsUyYCjB8g2qLA/soOtoa+564Qexk8leKQdzR0KVN+DX1WNd0gQEzcieqoJxkbj\nN6Vj7UrG6LCQYC1j5zg/c3ceRZUahOp3QAyBxgoxV/cp9MPf4/lej3aOBlVXAWJPI7SJxNefoWVM\nCuGv7qDt2jiim31IFe3Y1elEGzrQyyKYvFC+kzJzLwNq3ahu+xAaZ4DmcvA0QFMINAI0LQL1myD/\nGdL/hJA6DTbtQ8mahFzjQ75wHNUMN574YZzTfsJU7kBwHULf4cNnlolXFqEuWgFj/gy7HiIyow6y\nluMr34oc+TM9W1o58l4NMQWDmDe+gqar5uKNjOeDK4ZyV8pn3PrZh3D3OFx1j2JVKuBYBBR+idhe\njFF8EnPwGS5d/RjJn2wBgwztIYTaIInOfQT6D6Tt6muRu/egOeEl0WwDTxpYnGji3LBHjyXaR9SH\nwzmx3MyQE2ewDqmmzPMsI899hCbSTpK7FU9DP5ymSOSiHsSx0ajfOk5E8bf4T39L6clH0fu8xN/8\nEtElJYhtb6PeIJI60UpvhQtzQSfhNS6CtXZCAzQI5SE6djSgjpJRR7ehSb0B9Y4TqHUqnPfqkM3d\nhLcsJ7Lqfdi9F6QI0Nuh0Y8yJQrRJqOsmoTQsQ3pT3rqUhuRjAnovCmEhHAU4xnSv2vB32PAm2Lj\n6dm3ESsXg1kgrqSF4Q4PFwe7ufNPCvG3PkSnzoVdlMg46UVdqaZ36EbKRqaxcNdP8MPXVMwcxVMz\nlrFIl8qk5rfg9mlQdwN0pNIrZ2OJSkNwb+HU6K+4ruEo9JT2RYca0iDqF3twzSFoLgY5hDHw28iC\n9rfyLzPFP5uAD758DnZ+CJERjJ8WiVS7gwbJxqW8LJICLYxkGwG1FmH6bIh8DM/DXfhV6wkr6sUY\neRDj4Pk8sec1uvVdhJp7afUN5YT6DNd9eRhz5VHIvRJBp0Np/hBFsuCJjcZSUkfPu+9hXZRKp2Yg\n3ggv9iWnkffdwX36u8iQTzAvYxNSWwbilFMInpPgPAvpD6MA8rHDGJ+zIJgHQ+waKN4EPy9GSFBI\nsgUJPb+fuFcex//MJtQrxlH5agzOuiQSm9SY5G7cbU78wxahCnTAF0/C4DhAgsu2wE9/grEzoXY+\nfP8KLLVAqB28Wrj1HYQnF6PaZkelBGH75agrPsc3sIsearG0fkp1fjJZwuPoyn+C1DkQngmJKWjs\nR2g69DZnTrkQxHjyDH4mr84h1p6Hye/E59tInHwtN4hpaLOT4OIXKO8NRrXYieBYBJFeFG0Mqsog\nQvt5LlpHknz+E3oLryJmfxekD4TSo3DShlZVSsoKA03LPNjnhCN+u5X4UD9CFVV4JkYRlhSAsmwU\n5Qey751Bb9lZIupdjFz/JKJPwaeJQFgUh6nRidlbg6zNQXVOhroZYBqN3nGRkftqkC0Krua9eM0+\n1L0KQpyM4DmNBS0tpxJJSm9FG1Sg3QcamcShTUitLoLaaEJaM77AZILWWuRT55HfVNFbPYyxul5a\ng2mE33oP+q63EKLjILIX1TEJEiuhNBN/Wjf+AjP9PvoOIUWLKtRCR3oU+vwAkcUONDoj5qPliKcA\njUhQbyDWbqPRnIgvoIa7p2EfbOXWQV2oVbUgSwgxE7iQV03aWSOJ01aSp9Tz+pFHWTf4d+w3xfLg\njAgihAi29LuNvB8ex9z0JtK0BYw+dgz9hdfB9DQkPQiXFoMrFioOQeMJaD8PV36Cp7nz/2Nh//v4\nl5niv5vuVqj4ue935r2g1cMtL8OQ6VB+GE5vQzm1ifhwA5k2AX2MCiQtev9QEGoI9XyNLG5H321G\nNOVDTwhO1aHW2onpaMY3YRT6GDdT1r5EUisgg/zTU4jpBUgdaXjNb2HU34Uq8BiRV6lQouLQRKhR\nug1Y7DdTlxmDRA9zj55GTumP0mRFaKpCSO0H7VvgyHMIjZsRY8tQvAKK9h6QDiAnbkCZraA+rEKw\neQl8tZLKjBCJd04i7Honox85Aekz0PuOEUi20BtvJzqygpCvC/XJEsgZBpOeBHMCLH6hz3m/agjU\n1YFqGnSuhrjnwOuFsGhoqISMAWCMJ6yxhtiBY7GQRLW1jGTVUxh8VmjYBaPewX9mExc//RN1Z2VC\nA9MZPm8O6cYuGPcWUnQC3nen0mNU4x0SzTDvBtSHO8AQC7NWEKq5H01zN0LnGqi2ILSOJNgVS0mB\nSGJnCwZXAMPhMhg7F+64Cd6dgpy3EIoPIo4sJfmIhSTXaJxl3yEpNrpvEAnbNwgWJSNF2nAfrCes\ndwf+y9TIHQIadQriZVloNtTA0bi+CMyBBajmfw1b74UJT0Djj9BsBIsHMc6EVd+ILycOeYsXRseA\nYkFc+AI6QxxFJWsY070TQekHA4wIcimq6HSo06KtPA16J0pGC8LKBJTIFOTETrySlVBWCE3nBhjj\nRba2EzJ40egEBFstvt4Qmu4Gcpsvw397Ddqdl9jduxC738JC8x7IdCAMkFCHexGOgtRPJpSUgNYy\nk6HHt/PTkssQC6cSe+I5jK2t0Ckiu5IRmw4x83gzEWYnOLdA9CQ0uhxuv/AGNTMbed6wm6kBPz8F\nq5gSqkWxhrPTegOFrudRbALCly9A5deQGQ7mx2D8epi4vG8RITUg/m15cX4z/Kvs0n8nAR+c2QqH\nP+9TxrXFIEu/XAxCoIQEUx0htQqXJ4q0QxfRpEQiDMuF1jeRwoYSCG/HFPoMuj+A6ouQ6YDRMmx1\nw+wxXHTIHJoxmUm6IkwlaYSflBDPbkQVsQunV0UoKRa/dhuMioKAhE0PVvE8loMhOtPbWM6TvBX4\nmAFXv4kaI8ysg7V/gHv+DGe3QeNo6BQhMQaympE2fU7ZvYuoj4rAOmsMGSM6iSzqwjehgTTteNrm\nFRD+x/fRNIQ4k9OB45rnkIV2xh3cgHndGkJDh8KlICSOh4Sxf/m8HDOR89uo91ykJ+Bhg388T46f\ni2nv15DZ57/MlDUIR5djJpUKnifxWBeW+dOh6ywORzQnJuQhGSBvZA4FnxjpSpBJ170GreeQ+k2l\n6ZPJCHlJGBIbQduLpk2gfPTNHDfaGNj9PP1jyzH9PB5sNhgpQudRTg4azel8M7dtcqCyx8CDq2Hr\nt1DxEyzdhHfjAzgvdxPV0QuSHt/0Tryv+Qkunk1o2AG68jTERXyGKlvAMk1GOPM94tarCalBlOzQ\nnAJyK9z9FMGwyajPliN8+hLBTA/qp59ASDD2uTCahlF9zXu0te5hcPXjKJ8o6JbYIbEdyl8iRvZi\nxI/cZUP8vAWy1AhTohHu345a90sl5d5V4FgBlz2EoppN1c8PoKQUk6sJoXZ0gxiFcNpEYFg7wQoP\nlSOMtKTomVNcjOL5ApUtQKBby6iTewl3uhEVH4oJBK8DokFOFZBnJGEsqoJhT6BqPE/h0SoOzXcz\nyxaE0yIkR+G7zYtw+ATa8hSqZ85m4NA1oNNDXTGEDybTdYnX2k6wXuPH6MzDkT8R075ikk89iTdk\nRojOgqVhMPUNiMmC2nvgwlzAAYoXhuxEpvWfJOj/GP5lpvjvRKuHqXf0NVcXmCP/8roicXjPI2QP\ncdLisuPQ60gz96J4BLCUgVKCoSsNIbQMRfAjC714jSMwjckBZzpSQMW6GwxMk2OQ/YfxKufQDhBw\npw8hYk8r6FKJjjuKgBoC62H3H7hH8xTPTfwKja2IF4P387AmgUGmj8DnAa3Qp4Q62+GZQhgahK1f\nwdzbEa40oby/GrXdTs6JW+hJ6odHn0FHdRQtk/UEXJEE4pPxmGuJnW/EssrLqLpIarIWsEE8RMax\nDHIvXOTiaBN5hTq0jPpPnpcRf7/xuJZsw2So44oh9/FN1DzGNpSRWHYKc84gRK0exryCwkrASvju\nTij+HVy5EHZsJdsYQ/rNbQg1STRutRE1ohc67oW4AaxLFUgen070KTNxQzuors5iRGQ0A/YvoWbC\nEr6PKCDbkcCQa19g5BODYfjDuJKgN97C0l1foOkOQHQd1NwNDSFQx0D9GUz958Pe76jfKRP3WSqy\nvhXjZ2k4PJWYpTQCobPUBXeRqZmNcG4bFL+AOHccmuVHUNIcCAcvQf5lEHsT1dIfyPPnIghdiL5k\nuLCf0B0rUZ97G+VcGduNdSyNmESoWY3GqEcYuhQKr4XwwSCoMLlbcNcPRX+NCypDCDs78VQWYBo+\nC+GqR6D6VYjsgagl+AUHdfP64WwJUW93MrttN8KxOAgkobrehN8fRL7kJcrlpnpOOiln2vlOfxVT\nwosIP1ZPsM2HOqQgzgMaofHcUKJmutEd6QZrNIhroE7CUnCehDIdzTFWkqeYqMrpj8sQS65hBxZF\nTXmCDt59Dpa/DFovZPwR9Hp61wW4RvsltqQYvl3yIw/+MBKr34R54XdgiYef7gZfGVS+C75WUNoh\nbmHfPkfE/7xw6H8p438Wf62IAQQV9t7+ZES8CvoJJJmj6S88j6g9A/YSVG3DoN/b0PAnBO8xnME6\nKnUqRjhWQ9oaVMuXcn/BD2QUXIHbL9NjWEHo50aMB1oI3ZmCbvVghBG/PNq6PaBkU6CtptQQQ57O\nTKpuGHM8cfDWErA1QlgM5I2C29+A1+eAlAVvrIELx6FmDWLu4yil69CfszPRXQERfoRDftDJ0DwA\nJXM1P+o/x7RkBhs1c7i2ZBdnKrZy/+k6jE1+pLlZxKovUD5yPMZvXyZxdCRmzUQQ1CiyjFRcRCAU\nTWJCPpJeS8JVqxjgOoX2QhnqrYWE9AZaHTeivz0VY3IiDkJQaIU/bYCy7YTP60JjhZZ3A0TfqUFV\n0UlsRxB/bg06l46b1ENRao6z8eYFRFdWMCC+CjxmXKKHed+sI3dyNKfDL+ebrk8onnkHy77+HcbR\nFmZvj4OIcEKdXRCpA6UMFi2CFDX0bgLpHKaSTtIfvJqGJQeIWDqVsNtvJagUYRarqIr6hGrpCBk1\nexDeuRymAfsyQaVGjg5DVd0AzVnIa+6DceWI4mC48nlUvV0o5q3Ih57GOy0WrbmFGd0upK0PoHcP\nQTOwDU42waWvwf5m33tWd6G3DkTcvx+eMtN2IBV1b4hg4o+4Ow+hPavDOXYmeuEi+/iJZ8SbSDQ3\n8fGae+nInkac/iQYVChaC1YxnqHvnkdobaA+LRffcAOZ5hrcN8bhtxlJKy9DZfKjhEQEtYXkwnw8\nYVWoirwo7RaE3BDSnDqESokR1cfZPWUykkZHQ6zIyFOTaDQ20KiNZkzdQRyx01C98zCWvJ3guhln\nezW9ebswfSGT9FSAhw/cTXNKAlqdE53Jj2LbTiCiFo7ciyojHbW1H6RdD70bIXrdf5S1/wH8VpTx\n/9oIvISsIvaUPorJGEG+8AfUmED2QOBJEAdB0TSImQ9jDlNy+Y14/QY4mIfkP0jg1vtILj2NFxuh\n8KkE6gQ6P3CjCQ+iOCyIvl1IxSfBVQPBEBgimclBvNp4ahcu5mlfImx4DdQaGLcIntoAVy2HpJy+\nm6tsgcLpcNVAiLsRSosREmNQ2UIIB6oRKkUY0wUuLzgvUH/mPhJIQxWUyGg5jDPSxhVP/g5j0kBI\n8+Oz+rDoOxmsfYAcl4KxZRr+XQ/jeOABuq6+GqX+EqZHHiNq41ZiXxmBKdmFXngfjSwjtoK620Pr\npo+JPPAcaZs/IuHoOvD0wlQjxGtgjw9joZkwEc4fCtEyfjHaPxzllJzBdSMWsfWbTwlMz2Ci1M0R\n8zB8WHGHX8CbPRJ52HD0iVauDr+bZRs+RuOt5MH7XiNkccHQDoh5iB5/EnSGg7cX2j4B7WWQ/DGc\nmQH1BtRdP5Kx1IzvyCFaVm7GErwZUUhgAGEkqMfTfOgtuPlFMM9EPttCMFxHZ1M+ZApQX4lP3UOS\nLR4ad8DLkzm982m6stPQKAo6XwOeEVqy3ngIIboIjasOwdANUSlwzUPwzHp49kv4/RZUpmaUWwWw\naQi/Kkj04EtEbHWS/HAjlpxnCD9kY0XXMfYoBSz1ruXZthWob0nCNr0H50wVTChBu60a8dWfEbtt\nKHcWkLpMwWzvYfjmWtyilvMpcdgnvgnuCMROGTlSQOj4Hk1XHUKmBnlUFbK/kp46E/6AhlCrQvbu\nGmq8AcY+VoR57XOERDt+WUeNPhclbhvmjPfocop8bNjPyux2wnc6ObJkOqdHvEEwqpoYWzdRrlbq\niufyk/stGrQ+lJwkRGcs2KLgbDd0DYSu3X8pZO010Fr1zxfuvxM/ur+p/XfzD1kZC4IwG3ibPuX+\nsaIor/4nfd4F5gBu4BZFUc7+I+b+r9JYNQFN4QX6qR5DSyQoMsrF9dB5BGFLEJwWiGpGjpbQWuIJ\nK7qEsugoNKwnWPchDVEeziqdtAoKE49LhHWGUBcuZ29sOGnJa8kPPQ4l4VBSD0jEzRjBnmYj8wpu\nQCQMbnv5P96U0QJzZ8H5kxBygfMryP0IVvjgulgIhkHhBKg8CnF68ESiCM1EO9aT+n4b0uRSuqKm\nYGvpJT0/tS9hkNCEMzxEhEbG9sBqTOiQ7u+POOEClruSUWXcD8++DhkZAJyNKSDt5+WEBbIRwrTQ\n205jt4B1qoI691WE1jUYOm0ojT2wqxfBp4JwUDab0Lfbqbk5njHv7oTpDzF2dB7bGutY9vLbfO9Z\nwtg1pYzW2ylbtJTMiIPsi7wXt8/JksOvYU97jZxYK8nLv0D37dV0hMfizXmdnBYZ8752SBkCbWoQ\nG2HjQhDioESBu1fAmVUInRIJj47B0TOF8oICkt9ZQOSSZkZRyDc33c9oJZ+0Q2/CikQczzfR0n6R\n2slz0M+aTkROLbG7a0FlhcE3sjcxRN30IQwIlXBL159RhFykqTWo0zIQ+n8IW96Ae1/re2d1B/t8\nbBOGQtgwlKRqlBg3uorhdIeCRBhbEaaE0B25h48W3M6b65/GOG88stRN4MhhLt1wNXW6aeylles+\n2EH0yToUj4J8/01Inkg0ez5AHDoC8eBxhnzfwKBUFbbkduzpGgyGeEw6AcUVQN3lh3YbXe4wTM6h\nfHztIJZ9s5auYVm4xw1k2v5itAUiHFUTd9zLgCUP8Jn+MIoznMI2NZGVm7jy+J/pSTDhucNEpvYS\nUUX3IQUC6PK8qAWB7NpaklxttKlT2BubgsXoItfRS9y01QjtJ+Dnj0GzgYxOLTz5cp+//LNH/nlC\n/V/k/zcrY0EQRGAVMAvIB64TBKHfX/WZA2QpipIDLAM++LXz/hpkAphGHKSmchkGEqlS2tlRexfH\npYuczp6G55a1UOGCzS8hSiEk/GT2u4/Ax6vwDbmZc7e+wMWhcxm8/SJTvz6OVXQjfJRIIEsira6C\nT/9wG9VR48FeBM4a2saY8WYO4mJRAZY6P3y7uq+q8l/jau2zd08eDs5jznr84wAAIABJREFUoIoB\n0Qy+czAqDVLGgMMPk6LALkN9G0qnBjkQhzgrB0FKJ6vpENuWjUe+UgvffAihZnTqqWDTIviDCJdd\nhXVoDObfvYfKfAC6FoHiBMCHl4v6Nkx5bTD1M0ifhjz3GdpKEshLugLhq7Uo66owVLch20EZGUno\n+hg635yJElRQ4o3IOT7MU+NoHpGJc+dXLIur4PPWW1hlX8qu7KEk5dxDMCyB08GxLKv+nIdrtxOI\nLURzcQ+HerKhReLKjT9gVXSsig9yqHEHfkkHOXPBOBxci1FGziLgTyNkjoRwG0qYBIVJ0FmOqXIt\nCdMzsT+3Cf97T8HBNSyqUHGodweSOhvRcDsxL4bIXtxN+zkPK7RJeFr2oe+fBbo/Q8QnxFjsTDx1\nkHcKb+F7aQHmCjua9mRUTSbQ6+Fs8b+9s6ALuteD8y1I3YXKLaPyJCOOuBZVWgKh6HAUj0TL7BiW\nxMVgvGo37HMgrt9HtXYWaesSGFS0mWmluzk4Ig9/okAwIhJB+wPq0HroVRDPncO5wEzPmBQ6QyZi\nz5eh73IinXMhV0jIziBihZfWuDT06hy8dU3Mj6ih54+f4Rg0nEEfnUDbXAv9fgcfVCJn90P1xk1M\n3/QTEScuIZdtQfIpGDvdxH3TiaBJJbI3hFcdRbsUjrNXj69Vi642SEjUkkYEc49dZNjpM9iEw+y1\n38TZzk8o6h9GqFtPTu1P0HEessMg5PnnCPWv4LeSm+IfYaYYBVQpilKvKEoQ+Bq44q/6XMEvBfsU\nRTkOhAmCEPcPmPvvRkGiilf44eerSDUmoShBcoQ4xkU8zMXoxdSkx/LVYDWrd6/mQnQunU+OQZJU\ndI3L5MDkUxyWNpEpDmEhjzDgHAxpNRB9pp4InQul+lt0Ue0ManXikNoIpV+PY+71INbRv7qEB8+t\nRrsoB+Js4NsKkv0vb84UB85mOLIe9t4F5lv7znv9YMyE1FgYcB46O2C4BuLCEHWxWJVhUHUMYecx\ndtw3lzt0etRn9ZDrgv6TEAaaEaIuI+aNJzD2OwTuLuh4BjSDQPMTWH3guJOj8mZGyM1odDch1p2F\nS5do/nQzSaoOVF99h5IWiWdhNqEWGaEQuh6aQPET82hM8SGPd6BK9DJ7RzHel5ZjmJFGz7oawg3r\nKdQH+LT4Xvp1lCK2P8bAmi9pV3voTn8Jdf8vSEu8jSifkwkN3yKM0SPnaCmNSKNNFYtBULNoxdfs\nbbHj03fgDx2lc0AYav1ZlMKFsB0kvx75pB1OBdBc/jBxb3xLzh9uR5gyGBLy0HTUsfCnEvw/VqI8\n8zRtlXMw5YjMuuwQL7x9Fw53CmTMhqErYeBT5BxuYIKlgk2fXs2RftOo8GaCVIs64VHYuxISW6H2\nXF/0ouFjsLwB7ZegyIhishASk3H61+NLC1K8OIsjhaPotFkxfLwHghpIyEcJWAj/tgTH92vw9qhI\nPNFMR2EsDm8MqluvREwbgZjYC+kSvWhQYsCU4sAwTA3xBsyHZMLkZQjnzARPRROqTiJ6rRP/AC3u\nJ3vRGefRa2imIFpBuP0eIBn2rIWPZqBJ7Ids9JJWvA/D/ma6a7UIdWZUexU0LoX4Yxew/NROzMYa\nEg/bUJf66PZGIsXpMNu9XBrkpCfFhJibTq6tlykne9AMmEddqp8dY+pwhlvg8b0w43lwNv3TZfzv\nJYTqb2r/3fwjzBRJwL+vwNkE/2HL/q/7NP9yrv0fMP/fjJOLtCvbiQpJPDtqGQnRXeBfiaIahqVk\nG7ekz8ez4yPU5x9AmziRgKji1KJkWupPUGm1c2X1fvRlV6CN+R7K9sJl96N8fRPWITqcioIp7iVy\nal8na8N6Pl16OXK8huzWDcR2pCA09tLfdYrtE25l3rCboetykOog4iAo8VD0Jzj5KQyYA5mjIbod\ndjwHqVPAmAzd+6F/LAzeD2yD5o9AdkCsCdZtg8R4pAcnkr4tCX3vBvBdhBsWQN59aO2XIepfhtod\nIJ+ETBHqZ8DY26BkO0x8gF6NkXD7W2R5NAhtZvju97Q0qmnoMTFy7gKUli+RtxzH8LyALTGa8AY7\nkU0uorO+JRQfpE0/Em+uSEbuFMJWvox8gwvd4mTab7URGV2NdaCX9OOHkW4ZRWrJKe50tWLvthFT\n6gR3LUy8CUH3BsIhE7oEPQPqG3jB8Wci6osZZUjni0F5jFy7Douvh8iJ+2FECur5x+Ga9yEigUDP\n8+hUXyDEjYcHFyPOnYd29Uq6+vmIHDcWc4YOlzYehxyO31OH3yKg0dxI5Mr99H5eQyA9E23+bHA3\nM6B7DUbqif8pwMsVD/Pkg+t4+LPPydV+A0H66hC27kJJdOA36lCK43BHf4M8N5poqRW5txKF8bQ7\nOqgYV4ApMcDot/egnvUivPMo+A4hDBxNeOgUpsdL4INlCJXJWGygDg+gnP+SkH4yosNEKFFCPULC\nuL+L4AKBoD8W1ZleOCZBzUaE78qxaR+mO+gnvPkSEZUdeNvHYHa9QLqSgWBaBsGVcM1YWF8JR/YT\ne+EMrU+8TELMLEyr7+L8KBXptaUkVQuIVjOClA2L78Rb1Ixq/GR0htew9CxF274YnSOWpJ8UGgaN\nZEDee6j9Qfh8FPmbvyLfHo5y7XN8mVNBRs7Uf6Zo/yr+5Wf8f2Hx4sX/7//+/fszYMCAXz+oIBM3\n73MktxX7/gX8uPURlp16ESljLdZ7DjMgej+B9q/QhHmQh3vZbJhBe0oeYepOjLtTWPj5BwiT9bTs\nWklqViXO2EQaf/oEubo/XfNziOlpZMdFFYO16cQNPE/BkTJ0Yzpoccdh/LgU3ZRjyBNiKRdjGfn5\nTFSaICFdAobEOSiXBAzVvag8AeyOgyjJCudV19LhzSe+pJSBPauw5hiw74jloKOacSU70WeHcSTh\nUeYd+iN+nZWm3FxS1x1nWP1xArFuBJNCq7eCC7t3MG6ID2X7PYQ6FNyFMew3PELCmcMIzsMU+H7A\nHpFLR72enubBVPnLiJL3ER5opOG8FY8LTgwewCiNBmGxHyVbRNhjRgpoqIvoRvPVFI6xjJlt7Thv\nDcO2/Ssc4kD0n+ViTGgnYInHXlSPxQLq/tB6qRvPzoF0LBDJjfgRpawTtKA0FuM/Y0Ac6sIv++ly\nppFYdBBDqJc7T75PWnonDfED6WrLRWd2UakdR17RHuTts3DnhZN4Sxc/HThI0q736I5ORx15hCFv\n1lD0o0Thc0+jc3jojM2g4rZ0+vlLkSUFn2ojQoOamFEC3qNDKP12Go3pM5nSv4udqukkTbISGlrH\nLOPHPDfvSh7Z+WeMMUZyW0WaTnxNe89RrJk1JDZ7UZ80YLfGET66ijOGDCrC4ph0voiRa0vx95ew\nm43IP/werzWc6E49VXVRJDpDBJ4ZR2nCIga5Sxi4/iQXr01n5Pdn0R3ZiG0XdMzLI1dXi6IH8RiU\nTh7AsLpzmAYEkfo1cmLXXRiml6A0hRMskimOH40vFabvaabGn8KJtiD5qhT6tX+LnCLQG5eEulTC\nVvU5zj3fYZUaGbvpEnKHQE9OPJeuGYSs19F+yYY/Kp5hzz+I6ZogQetLlLpvIdvzPdaaCxwpzKa8\n8hHSvgmS6LISG3aalqyB1B3cxNGfHWj9XoJa06+X239HeXk5Fy78TUUz/i5+KzZjQVGUXzeAIIwG\nnlUUZfYvx08Ayr/fxBME4QNgv6Io3/xyfBGYpCjKf1gZC4Kg/Np7+s+wsYduTpDBPWhcEkVv3kfO\nxYNU/+zDmqwnc3IEgUlz2TsrioZgEi0aPbcI03HwIWN4HI4+D+u/QhHakKyD4O7VsOdhOraa0Xxf\nSJjvCrT6PLDthlNX0hM2AatvGx2nkomwqtBaXSjdGj4d8AqzdTtIkC+CaRBU+SHMBDlNEHYD+PvD\n9qVw+RfQUQ6BIjDVAdNg7R9h8AiYshLa94L9GWifA5e2glNPaMoSPMe/wlrhhtRIiPCjKGpQRGRP\nOzjBn56GeqEWSW9CXz8TYfsW2p7bTWnwOFNXHENe9QbkJaO6MoHz/4e8946S4sjSvn+ZWd50VXvv\nPQ2Nh8ZbYYQTRkLee2llQXaQRtKMRqORFwLk3QACBEJYCY/wHrqbdtDe22pTviozvz+Yd3d2vz3v\nzjvS7M7MPufkORURWRFx8uS9EXnj3udurCF3egHapExo/hAiPMgFA1C6u6HFBsMexS+/irHRiX+n\nC/F3p5DvuQHHU0MIW7kDMSUTnSGA/2wP8qVGJF83jRUCqfF6mJpGQ2Y3ariF+MRmxKZIqB2EeukH\nCImFUSMR5Gq4dB6nyUzJoNmMiHkYPnwWxt0N865wd6jnbkFWpxNU70dYHYWmvhklaKFm0SRsC35E\nMJUTSSyUF8Pvn6DV2MeGu4dw20tfYYiUKHtpGJG7A5QnBBnTehrVH40m2ENXj4bwli6UpGTK7ltI\nux++7BzNC+eWknK4AUxBMGbCCRUWDySQPIqS5AO0BT3kaEaREP8SwkchEKUj2DeWzhFWondegqLT\n8EoJfLcCZf8HiOPuhOSx8Lt7qR6ZhfbmIHH7OvHrshFrz9CdEUZksAkhWUAuisRt11M1bjAemxe7\nsQolxopEAdmbjoDWgjxrO5JyCXn7CYTWV1A7QhEqG1En3oD/Bgnt8e0oF8z0OhzYL7pRTAaUNgeS\nHZRZQ1BSQgiOCoPaBjR7StAGuhFjbYhTjoM1C+XEdqiZT2d4f07nJDD9leOIubOg5xR0lUHMIqrr\nGkjtrYYxC2H+YxCb9ovLM4AgCKiq+rOyngqCoD6nLvuL7n1VeOVnj/d/wy+xMz4FZAiCkAw0A9cD\nN/yHe7YADwHr/qS8u/8zRfy3hJ1hRDIVVBV/wx4MBoGQeQvQvbOQw/XH+SRgx6gzMcSXx4CeT5jk\nrCMluYDS3hY49SLUHoYHfof8ygqC3+zG0d6fkADgCMFZfA5dpQe133XocCAkLcPWsJ7gXhEhV0V3\nspbgwAFIR0u4zXk7Z0fPINZ0D5zfBROfh9QRV/gqe1dB94vQUQKGMMhbDEUvgKMderKhLRsyF8CW\nm0BfA/1CoacEOmS848A59Sv8KXasxQkIA+vA2h8MXnBfwtuTiL62nu7PuxEeiyNK2IHwznBUOZ4j\n7GWmdiHSywsQX/o9BLyg0dLvlsNIkWNQLq9E2KXAcRnh7DmER7QI4QbEzqeR9BNROnaiz/IhFPeA\nPhnj0q0E0gvoSr6MvbgSjSijfctA07sQdbV6JVvYrFYSjCpt2niWh07nrgPrMM2pQ0jyQ6YBSs/A\nhVoIaGiOSuB07gxGNLRBXT3MWQRAUPDQl38/LsdRoo+Ho02qQY5OQeuuQX+pEOnCjYSN+tPRRHZ/\nGDmI6LAGrH4flkoXijaW3DX1aKKcmNt7WZ13Ozd1bSK4og9DhwHMsYjPvU2ucx85Sh3Zuo08N/MN\nXir5FYlSFcS14K/2UhIXTceAEnK768h/P0DwdzkojbuQNgbhZQOanF1EVYyF9l6Ij4QHpkG/XLbM\neJtr4hJgxR8gaxCV020Mr6hBQYcmfT6qs5Awdy9CyBgY8waieC2WzbX0v9RG9dMxBEN1qLIDub0P\nb/wIDOnPI+3aAwc3ILr2gKsbdXQX3Q9bsQmPYtTlwth3ULoGYunrJPjRLlpPPkvSumOobiva9W5o\nuwSzb4IFd6IWLiBwj4Ic7UAXKEMgC3HHh/gKkgnXFzL87TrcwyZjGXcHdDsg+TWInc2pLz4hVa26\n4qrZ2wkxqX/XmaJ9Pz8H3i+Cn62MVVWVBUF4GNjFv7m2lQqCcN+VZvUjVVV3CIJwtSAIl7ni2nbH\nzx33/xVa7ARQ+Fao4accC37jYtKSU+jnLGKa/yj3jPyGnuMXqL1uGaaQGjSPurkUupjEH6shoj+M\nzQJ5E9LzUQix4URLfQTz/UT19OFpHYyvyYJr0034q8owyBqsBhntWIken5mmgkFktNVhFkSUYxAb\nehFShsKtG+H4D7Dv13D7MrD9C1huhOyJ4HsVhLkQPhOyX4BVMyGpGyJyYc5o6C0HZ9IV9zZLG8F4\nAUOFB40nSGCiB408HNE3GbSrIeoivRvGYp5gB5dK1K5+SOkVEJlD5eQ5xMidaLsfwmuPRhU6EGsr\nUA16lPh4BM8qDFVbCGYHkI+Z0OUGUTQSmgoRoUNFmLAe4YQJJc+MvPcGJIsVaf6v0N7xMFHf3I1S\n10nH4lQ6zb2ohyqJM3mhYDTYCsHrRTTo0WpE9i4eRYGmjUiNgqCUQmgBPHsQqi5xsngHBWePoO5Y\nhzBgCAgCKjKNZY9iLvyWKEGLtqgdmkxId/WhNIQQu66Ohtzx1O54kbwnn0Rvt0NnC9z1FTQ9hjBC\nQorvgugwULxcTBiDXQtt23VE1wmsfepebhr/JMbQRIQdGxFmrCG692WWudby7INLefliLd32vXRN\n0dDvx3IGaRT8Yjd9IVakE2tRUvZhiQehdzYELyIMNYM1CNtHgeskhOYiqxowmqDhHGgMtI68Acub\nh3CmJ2E5+xZdE+xE7XHCYz+A3oTgyIKZESg9oaReGE3VxEtkda/DY55MRb98Qt66huRPz9G+bDYR\n07ehbHwEIaYHSdOB33IMI7kgahEdwwlmBjGXziUprhchKQrBWkDV7RtIC9WB1wWvT0GYPgxNQyVB\nez1y+xI0lXug6CDOwZHYigQsC5fg/+hd1BPfI1x9PYycBUBQZ4IbX/3vFvG/Gv9UNmNVVX8Asv9D\n3Yf/ofzwLzHWXwsFlZ000IibTEKIOFbNrckDofII2M+CYzn20U9jW/8Zl++cScet1VheDKV9ajLa\nih7s6a9A8BKCYxXio3NRukahc/4OGqqxytlY0y9ASvYV/oegBrIzUbwtJPsT+HBuOmEXtYiuNzCW\n+Qgv6YFRZgg6YMQ0+M2t0NkMS1eBFA7TD4CmFTqXgXIH+HvBng7zb4ML90FSD8SvvrJLHqzDuzcb\njeRC79UQaOuiSdFAVi2d7RaGnJyFHPkyMdtrceaNAFMpztH70bT+gJqTz5l8I/Prv0DjqUDSP4Ng\nfA4hugO+zYYxK0H3PTg8iMGFKPrLiAcKkQwiaoIPJk2Boo2oPVq6Ly9EX/sl9UVWTA0/Ie7+iSh5\nK542GWH/KKxrjqGOHo6QXAf9hsFnhyAmjIjJadzvvYlLjZvxhwVRT6oE9U+hHXcNGO0QHkKKo4j8\nE7tQAgp9gxtxdj6AsfIgMU4TavNAdBsOAjpYtR11/Wz6glYMN3hwH7uV+Onb0JlM0HQZWirhy/sZ\nFfUTxAWhW4HTerjjJUaULMG7SuToyDH4NFbign00F27E1l6K4fBZjMPaEKN+T+rFH3jxhwf44x03\ncUufk8TWVgLZCrVpTaheld5f+bBWnsdq12AcE43mqs+h9mOo2nrl3bhbhZTzUNPEsBfvg8teSNGi\nuA0YztYTsKVz8dohFDy5gRDpFsi3gFYPfjeUHYScKWirfwBJi/+NYtyPDcHSXkX+NatRpsyjausi\nzgw6R3jwdUbHjYU73sJ6cAT1mT/i8pQTWneccF81Zn0bilcBXxzSfRvg2xU8Vazj9YEyacsnw/AU\nmL4a8bfT0HQ1II/UIoeqSIILe48dAiKGC9+jG5hCsMOJaByGR/iMPl5nyAwjfvqhY9D/pMj/xfg5\nNmNBED4FZgOtqqrm/6nudWAOV3LFVAJ3qKra+1/19fexJPw3QERgLkn/Wl6jXLjyo3w/TLgVHDsh\n/GkEQyzJr29D/9ZbeGr9uB/aR/drkTikj4ipOochaxVIdgKpX+FxjUeMaMVorkDoHAPRt0HF55Bn\nhtJVMECLGHeBW5rDcIgDEaNS8GobMNRFwdplqBN3IUzbD0tWwo9fX+GpMJrh0k7ovxiUt2DTMujX\nBDk3Q0Q0qF6I3QtH3oCQUvyh3yMPb8UifABtodQee4N4UyvdShdRua9SExtKaKAeWxgYz5Xi7vVi\nKnaj6ZX5dHw6iR1H0HVXQvyrCGIkOO4GTRakzYfWTyDgRBWi8G88ieGDP6Ks/A1C5x6Y4AR7P3wx\ng9G6nsVqP4d76IdELugmOP8cUesjEQ/lor+/PzQPo1L7Iyn33g1SL5S8AbohMPMd2PEChD9FprYQ\nwdGFOgyEuo0E37mM0F0O9jQOTR5Nv7yTmE53YXMWIVzWIiv5SGe3EgiEoeRrUMU4xPeeRu7y4cgM\nJfrYYHKV8whH5hP8XAFTHJqYLoTQcC5mDSLLuxC2vg6j6iBxLOL+QWy/M56ijDsJGProt+1tjDXb\nsLa0ockB1/kJBGyJWC+4yOio4/EN79G0eBQ+l4+IKg26jwMoQT/Bghj0Q2JQS4+hhP7Jc1QZA5+/\nDy8fgIhe6LwDkpbim2uByjOgV/DeHeT41OEMip1MmOTHnzIDQ40GhuTBqSXQeBAijCA6YOQ8yLma\nhOXH0awcCGIN6mNmhOnXkmo/SHJHNfgiYOQR1BUWVG858RfPUheWyfnsKEbbHUgdEYiHGgmOfAm5\nLwqpu5sBhbvp2/o8LoOX3u9CMG9chLmrEWnqkwh1F5CNnyDPFZB8efgiZARDMepgO8Gk/khr34Dx\nj2E0L+KnPenkXPePoYjhZx/gfQ68z59cd/+EXcAzqqoqgiC8Bjz7p+v/iv81yvg/RdFaMMdA1Kuw\nIR5uugShmWgiI9E5i0gYKNH4uB+zPJTAgZNcmNRKnPwhSa6Z6F1aFOc2/HEqOE6jb+pEynsKxg6H\n7xfC2I8RJ91FDWsJVyJJ9hxDTSpF8bnwHW5B3xyJkngAoXoKQqwFYVod6ndxqCEmCHkAcfUsyL4N\nYjdCZx6MHA6tz0Le76D7NMz7EvngVXgTKrGeDoOBPjBuwpEbT1J6PuHHGwlqfsJZ0Ebz4Sy6bnFi\n7ZyBENGCL/IQ3gs2uifYmXn8AEz8GiyjQbKAbhbU/BEGtMORPeCMwndyEvr7TAgmO75FU5BWH0TT\n7UDNmY3+/Qmol1XqlzcRFqohnMdQnVWIP/SHJ78Dfw+B7S9gXjgAqWUVZA8DfweMDEPt/RimRsLx\n76DNiDo0E/VQK2JhNb4GL2rsUAxPrMTp/xJTlQdflB6xMRJbUTHYyiEiBqmqj2D6OLTyfgLba/EM\n0pFY34I8dS6iOBQlTkactBu1qZrgsRS0aib5WzfB2Q1gtYPHBCXPw5BwZvTuQ6mCSdVFKD1uBBkk\nVUAsV7EebkXJ6MA/2IQvLISeziAtv/My0tCCkh6ACSKajvlo4mZAjQvh84OI1wKnD8Dq9+DtgxAS\nCkE/9D4IVQ+TaKqH1Gy4qhT9phjCh/SRsvskgYSx1A01YOn2EFexBnqPglYHsTZIGwS2HFRDJPpC\nL4G6ExiyJsBdT6Kaz+N2fUGTOZuMC/EIYdegzpmFeqkV0TWblJJSUlrLaHYlURYVTr+gA+2+t+jz\n1WOvusCtuo/ZHLKIyMYK1K52KNuKcO1NBAoeQXp9DlKJl+CSeHzZSXh0KZi+/hp9WxN6ixbx6k/R\nbf4BbnoDOfiPRRT0c3yIVVU9/Kfzsj+v2/NnxePAQv4C/O9WxkffxJmYCNRhiZ8EJz6C0AzEy3uw\nRpci5o8lQhNO0L8PU3ctyZXj6I4/iqOnmdCT5zF59BhrXQheFcYMgZ03QvVBqLVA3HFoyUfTtpoQ\nw20I6U+jijfj7f4DdZPPkPTuRVTsGIwHQZ+EK2QEvpZmTMbrsGTOhcNvgi4dNAPBb4HOZyDsBbAO\ngR9mo7bpcSY3Ym38PcLq1+HkF9C/nWEJlYiVD+IePQ2/LZbQ7ipCBu8muNmM5twaqtpshDrScI9P\n48btx4gTVOiWwPE5sqDFd+5NTN0xBBb8AU1zMapwAYJ1iKId9bIearYi/ctQOHEaiu6F1j6ETkh0\nPIErVKCRB4j5pB5x6M1Q9iy0nUdJjMPebw44LkHOq/Det6gDg5DWhnLOj3DJiyB5CKzLR4gbjCZs\nPfrYUITX1kLDSnQaJ7rSMQjefWgamgksGo72vBfOFkGJhrJ+qaRfPovJ10PgwdkE68qRL17gZIIF\nm9ZA/+RVBI7+ETX7G9RB22hLjSBFGo1YUA9VjeAGuk5g6I4g9VgFTZkRpJZ0I7j7YOoToHNDgQfR\n+wWGbzVwrA/z3QKOC+c53dDL6KECviQDhvx1CPZ9cLATJlnAo8J3n8KLH0D5DijbBXIAMiZA3k4q\ntz1E/9E/gO5LpNhq5vVlIbXvQPpqJ8F78nB2BwiOuhsNNih4Bva/DWnXQ9cZqNmAdoQZqbQTf4YL\nnSNAT1c5RWl3YqsdiXDXPQiFb4LRhpifCMeiIbEVpXsQ8eYEag1JVC6IwtYDKd9+h9DVQOrv7gbt\nNGqcpxi29il0o+9DWfImLco9xIa4Cbh0tNu0mBp/IjR6Lf6MY6hnW5CSNJAeCked0PjLu579rfE3\nthnfyZVAuP8S/9xEQf+Vi5wxDHnyrygU3kaN7A+H34O9r8A1K2mrmgCDv8E44ATBnJfRxU4luARy\nNBuxn+uAMe+CeSbCoEdAnwT5j0LjDkifCks+gZoLeBq/I7LwPMLh5+DgY6iHnqfr/pX4Us9QfkyL\nY3MMraaB+C3NBEMP0L14Ap6GWryqjHr3EXCug+pwyHFA9Mtw8QNQfKgX63EaH8eseRFx9QYob4MB\n18HVReyofpW+odUExa+xB5ehK56AtiQVQ4wTg+omvKOZ8LNVpGxfTURTIT1hEyFuFMGOXYiFDyB5\nLKg1R9jl/i2ytRBFdKObG0SxBAjWbUWnb0GwLUFwDYHAedSYeBg8BF1LA6HcTCyv0TMzht6Jzagd\nQdQyM/Vb49AbUmHgw6jnP0f9Fz0+TRN9dX7E4/tQLeMRtKCbb0H7+HKEtDTE9iKE9+PovrgCi8uL\ncOkCmoSHqB0WQk/TOdTOUtRG8EdClnQW0w0TIDYLU0MCDnsCP9yYSbw2jQEXMxG+eQ9d2kC0l0cS\n3FPJyPozMCYZufw4slKLb/UeXOv6oXrjyL6zA2lsJsKcifDMRzCLH5vVAAAgAElEQVR2xpUvkT1r\n4Mw0iJoE4weAVceAW+8gJCmGy61mtM0q/s1a1GXNMMIP0zvACiT3wM4XQWeG6z6A29fA2Pug4gz9\nQ7fgcjxBebwKC24m8/WVBFxlBMen05MYRkqxD82WHVCwGLbeCYOuh5iJkPsEwsjXUIqNBOtVvKEJ\nuGs/wHJhO2NPtHNePcWX311Hfes2CoNboP0D8FciC2MJ2pvxJz6F3utg4OVq4iPdCCEZMNIMhx/n\n4aLpNJ3diuvAZZQn36RNeQHruVpaZ2rpfnYScb0LiTjfh7R3Hrr+n+O6yop6LBm6n4f598LGX//X\ncvd3hr9VOLQgCM8DAVVV/6JPhX/unXHFVjj3EcSPgqH3gSni37fPWoFqCOJQL9Kr1WKLiIMHikFv\n/rcXSvYTemIbWPOIvK2X4IoMtAmxsONaCE9CNUdCZz3CF/Ng/DIY8yIIIiReoiStFiV/DkO1yxGR\nEIHEq/6IdcM3yDvfp/N0M8IjA7EvjcKW/iQaMY6+kZvo8HyMWWpDFxtFTNk2KBoGzg+haT24dXhS\nO9EGo9AkLIbok2AohTmPgCJj62zCeMaDpicSHIOguwdh1nI60t7EJCjIVZV457jQlGmRekIRuhrx\nFN5Mh76DmHAL/nwvilnCFmihJiOecGcjVp8Ov1mPNrwTwacQOPso8uBhaD1mgjepCFsj6ItsBioQ\nnE2Y65rwmKI4meogbYsNIccJFz5DvW041O+HtM3s6HudguNHCblqOVKHDJVeZH8F4qGrEGxmcArg\n89DQpue6o99Arw+xs4eomOdQkp/HXSugyQlHvdYFwYv4NWaY9jwnStbj6V/A7J/OoeuJh3ueAyWI\nvHMxYtpxNBqRr613s7hwL0rDOIK7D6Lrr0VvqoLQGwkx9MNS9SVq4r8gDBsKTgWuWQ6t1TB0Bpht\nV96L4izU7IXku9o51zeA/bU7meA/Q+dYO67dOsxZXvr6GfHYjRhCczD0XMB4bDdNOZHYI2YR532C\nwo5FhM+ahUkAOfEy8m8z0ZYVoZ6bzeC1m5CaGwk6Q1Ce/w5NdDf43kDdtgb11Fb8FX6M+RB0a6mx\nHsHiSyStrj/UKoyv3M+PTw/D88cX6D/UDzojeAuQ0p8m0P0kysGbydGZkQ0t6H7qgUg/+PwQkY12\n5ir6fzkB1x8CBE5l05VrJzR7IFHVeWhTHrpy6Fy1BfThSMVvoDdLdM+eh233PsRZyyB1BINOrIXu\nMDBkgiHjv1Pi/yr4/waubYIg3A5cDfzFoYj/3Dvj7LmQcTWcWQlnVkHA8+/bwzMwk0CacB3S0PvB\nYAJny5U2UURtPgbfjYSij8HRgCEjlUCXEX+xB3XMJ+CdAbtfg14XTFgGY1+6oogBbnsEobaGLPFh\nRPXPHrMKdscpwq+7i4yNVYS9vYyLJ3W4T92C91fXYX9lBzlRL5LUpBBTPBtCs2DgM9DkhFIf/sY1\nKFkGDNWJcHknJGXA0neguRC+msC41nfRKMPA0gMDXgdNAMkyCKU8jKqpClJAJXhCxljnR9V2YPTs\noNLaS6xhCFr3VVj3dWP4XmHobSVovvZgPRiNpzQG/afNaI6qCMfjqBNH0JQ6mR45Cr2pCW3XXvxy\nNYGS5WiPvUDj8Gtp9jXizInB2NFE6IQq5JALdJ/+Ct/7NTScu5nBhacJd3cgV72OQ3mT2rge6kLS\nEBzF0FsE4aC6DEQ0BymJmwhpI0BUMRUswOguQMqX0DV2Y/gxDf2BG+n0p3FOv4L00ccZ17oaBlmQ\nLcdRvxyJumMxGE/jHyyCL4iUGY1nv4zYdA7L46A3B9CkgKblPeTvv0doCxKI6oO2UWAoRo3OhPGL\n/00Rqyod1jAOa0tw0EDUjQOxHamltELG6FFZffNiNFYrkfXRRJ09T1jJblyxcTSMGsXF6Co6vQ/R\nHJuLr8yIg0uYguuRlYPoeu9F3JmGFJ+E/qtygk+MRrxtBtpXliIMXQSR/cF3EtHtRmmXUVUZzzWJ\n2Fy9pNXkoj72FS5XKuHJVzHuUi3+q0wE3AJII0F2QdN+HGHzOD59GIaIRqoHpqKeHghXvQkR8ZAw\nj6rgAdqnRVBvT8QxWCAoxBBTcQSt3Q19z4EtgZ78Yci5j4NkRFd2iarIj/BlD4azA8HwHf2qtsPZ\nR0Gf/reW8F8EvwA3hfCn60rhCovlUmCuqqq+v3Qe/9w7Y4BhD0LuImgrgo2Lof8NkHf9vzZrsRDN\nKNo057B4nVC5F8LT0UREEJTS0c4/BqdvhfQlsOMBdHozPevPExqyCKElAkQDjImFqr0w5p5/G9di\nJaV2EDZXCxxeAdc/CnEp8P79MHIOjJmPCISH5nHq9lhcPaFEpJbS+Yc7aL1mPMbB4wnrrcLw4iYw\nyPg7HHiuGo85/15UXkJpqkPceBOc0sLEEXDgGCSMpaJjClljngNfI5x6EPq/QNDyKiZdBXpDDv6y\nWjRfa2GsiDN5IG1T7yOvdxrCB7dC7TEwSARTzRQPGEzOhjMEoqE1JYGkgTakrj6wmsioS4KSBtCO\ngdvOIvxhALGFTQSzm/BNzSAs8B7qaD8JRRcxp4Kp3I+sRmAoUtEmubG/207nIjuq20ZdXQIxg4+i\nztficbjYEz+WkZ2nMSe54WwvH69/iKSdDhj7OiDD7hVIIUaEcj/+oB4xu4kjsybh09cx5Mxpwo7E\nIl6UUK+NhKbvUVJCIKIFX7KbYIQAUQX006Vg/uBLRPEn1IsrEWJ6Eab/HlKHotkzHyVyABrLKgKX\nIsHrR/jhGTTv/JmnptxJpDScUO6AinfQPfcM/qXRVH7Ww/Gwwdy7eQ294+JpnnY/AwKTMX3WH/uY\nj0CfiNX7Pcm76hF+exrHDTPo8azHdn4sml2FEPIFPPcDWEIQjn6AbupavI23oj/RhJoSj9i6DaGx\nGcZZEDtdBCwaTKqE4JhMy7dbkd/ahLGpgfZEPZkNfoKyxObX3mCCchUx1Tuh5SzNGbcSZd6EJ95G\n3Ovl9M3tJiT4AGQ8h2vXH2jJTiUsuZum/WH4Ho4m890KWtM8hGuuRmu9IjealKeo51NCIrOxayaR\nUhmkZ2QmxtW7oc8PiwBHxt91oMef4+fYjAVBWANMBMIFQagDXgSeA3TAbuHKMziuquqD/1Vf//zK\nGMAcBalTIHkinP8M1s8n3Dv4X5vt5FLDd9DXChe/hRH3oo2JIdDcjFY+DlEz4cMp4BbR+IaimzuR\noH8t2rYK1AILjByIum4XKh4kjP/ab9jUF2DpbTB1GlzfDwpGwu2/hYET/9308oU7qWq8TGRdGVFP\nZyO26PDoHqThkRsQKz8gTdJROn4xmeVnUbThOE1xGMcPQ03+I2KNitB7EhxAvI7KvAlk6UNAZ0XR\nuHFnvQumWehKZ2NJtKKZXoTW5KV4QC5hjn7kGe4BA6g3vI3y2FwkbyO+XBPHrxtK1xwT6c52Ep+p\nR1vTA4qKcm06aOrh6C7k+27CqdxMiLGNYJQVKeDHLLyHoLOgqm68XcsRDj2NAIiTotFsLsVdoEef\n58Z6LI6973xNr7eOqV+baCqupz06jDhbA8pQDcHzerQdXhySnetObwHxUUidCfOehrdSEHSw/ZnJ\nBIxaBojrSWIh5qFnEMT3oWU5wrnNECtA9t3IqZPQCqVoAi0E0o4wyBGP0LmXYJIT6vpgZCL07oHv\n34GwUoTIYugAsTwU4ch90N6AutyLkJgH1nBUUwP4T6E5+Su4VIYmcwyJ9TY27HyF/k9v49jeM8zw\nXqRtbC/tPRtJ0lkRukrB0EpUeynywVzEgW4yy/ZypmgETftXkKTpfyXBwE9LwO+A8l0I3z2OPlqL\nYAmAV4DeZIQhEaihjchRAkpQAy29BM1pWK4tRVjTjFs0En/LAuQOD5zbx/RHtrD9vnNMXHEee+FF\n0iK3YHrBg7hLJdilw3S+B0qyqB7ZiXtcDnm/Lmb5Ha/ykO455DPlBDpjCbN/SqdlB0Z8hHALZgZh\nUJfiCa6jM8WPxvYYeqIgowDWf0VZ3DRy8u3/LaL9S+DnuLapqnrjf1L9+V/T1/8OZfx/IEow5B7I\nW0zCRzfD1rtg/K8RbYlEH5BAa4a+elAUtNHRBGvPwbmn4TLg64XRSyB7IdaMXOQvGmDoXsjqQ2jY\nR8uMVLTqMSKEyaiqAvgQzBYYVABGGyRGwoUT0FTx/1PGcUUixncqaM0YSKzxVdSh2zBW7yIjphf6\nPwRhebQpr5PvbcTb/QVBUy2y8TV0xQdQbj6DUKIi3n4QGtYxou8z1NoQvFEbCOTWYvLeheabGpq6\nT2NxTsDxlIbuw6nIYx8n/JwWtbwYz/vv49+8GfxOSMmgWo3kzkOf0NmZTfUkifRhAkpUCKpbi/LT\nbkSbCdIFvPld+NznEI0j0HvGQtFvIKUTIiwIggnjiCBkpsLoBgjUIcTYKL12MlFlJzB2tDLq/bs5\ns2QwJ+6AnNYeUovq0acsRPxhD8GkNjwfGFna+S6R3mZwToOTm6n7aTkhlwWKl43FIxrJarhM1qEG\nNBkTIbcONCUwRQueKmiRIOkJJI0NiYkggajuxRe2D0NlKYTlIPR0oOZHIKgnwXgZvAaEMj+95w3o\nls5AH/ge9khQdhQsoRAaBfLn4K9H3VKGMDwKv87Ex3feys3iSILjO9i/7hMud5oY9ukuePBbCN8H\nLVWorbeie8OHv6ALQ3MSclkFvUkmLHOyoGMP9FwD3q2oWhF5sBH1soLgDKLYTchjRmB41wEJGXTG\nSsjhFxFNMmGLnsYSqADvWZyL4gjvCSD2t4E0EGK8GBFZcLCOzW8MZc7eJkJG6BAZBzUHuLjqUQae\n9VHt20xPYiVxXbEEozt5euODSC9U8UP4LQzrdOI9/iTR6X/AmdNBi+E+wngK5A/QqXWY9MtwSF30\nqV+jTaxH+8RKOjYWwuyXr1CMCn//ltB/GqKgXxp/K6Kg/4g1a9Zw48wC+OklsKXg/qwU03QHtFfA\ndd/Te7Ed/ZfL0MedRrVLCPcVQ3gmfLUcOqtBKgWrFTXhMCBxblwuJiGdHP0KAsGvQYhE6x0NbfUo\nT03G89bn6Jb/FrG9G2nVmSsk8v8HLXWUP3wHGeIByDcjSl5IfAjheB28+ik9pg7KO+5lRFEhSuqb\ndCmrCP86BcHRiZrbgjqyFjV3FqLhMZrL7iA01oNouB/d6t0IU16H3RO5NDGKxMPJtI4uIfotK4aQ\nYSBpwB6G6nUQ9JuRVZmNSwcwsTqWqoNvMbK7iO3PjmPO8RT8FevwjQJtlYy+W0asMeIZpMM05iXE\nO56DbY2wdyQE5sC0J0Hugq77YH8VZM6Dz96lb3wBHrmejxc+SIinjgFbS8hqEIkOOYuqVxBv2or6\n1a0Ix9pwppsxSr3I/X0EkiRE2YQhMI+6E0HKbfGMGbYSv0bEvgak576C2q1QFwWNP4GpHlyD4ORO\neLcDDFYI1Fx51nIrXuUZNDU1SOdUlJBmpElH4cwaiD4NHYdAUelYlkzIrZPQdh9EdWYhDr0Sho2k\nhZRDqFYL1CXCZx9QGZ2I+TfFxBICHYUoXw+k0jeNjIGRuHYWYc7xgXMQSv02On6fiuy4gbjz0ezV\n7KF7iouRuzpJaNBA8hAUkwuca0EN4Ikw49MHsO4XES8LtC/Jozx5DiFCAmmvF2KpKkb64HtYtxCn\nxUXd1dXkrkpEUBpRPY2odR7E3OuQb7yOTt1H7NHYmN/px/itHeKjOLXIRIQ8ip76X5FfcprgUR0a\nRUJQvQgJSTgbrShWHZ6bXITvNqDpLEIOsdBx7Xi0YVMR+9ZhphWN9SRq40s0R7ro0p+i9tBcZo/7\nzd9chn8poqCZ6sa/6N6dwsK/e6Kgf1yEphGc91t8da+g9hZCzBxoKYaSTYSMfwry3oDdj+NtciLs\negXDuN+ARgNffAIL86FkL4z+CureJFyYjrHlGGqSij/4FJLmMVj9LcLJH6h/Mo3WxOcJeW0YGX/o\nQXpsIrx3CEQBRA2qGbYOG87snC66xmdQUO1GWF8DuzaDpxFnvouMMYvAXoD43itoCjqR+9vRGPUI\naXchhOehGpKQ3ROIDGtDqg1HPPIdOBrg92NxzJBoi7ZTNsdIVs8YDJMiYe6vwRIFgPD6TLShWg7c\ncg+jSvuIX/YEe341gYHF5ehMXiqya3GPjMHcKZMeeyuK8hpNj4Wj78lEu/FddG4HwrEVqGYfQu+H\n8OWL0GOCSDfk2GDDN/h641C7ztKhTSCir5y5O1109URhrduN2NOJmpCKZ9USTKV9XJ6ZTmjGTVit\n+WjXXgMtAtqC2fgjjxCbVodrcC6SMRbzKQ+iqxW1ugshDsi4ATQvQ/02+OxXYPfDa+kw7SqI3gT6\noWC+BgpvpKfsTaTtVRhusyBZhsKEodAyF/pUVNWKv7GTnr0+whJSEUfYYfafcS20vw+2+TS9MY6I\nHohYaMJ+9BEYvhJ0NsSsBWSmhEJXMR0Zt9C5/xMS8qvBHkZI5y14KnaALwJvtkCOuoDoq69DObgK\nfnwaZBXVmoNgiEFntyDaDuMMtWJOayH8xGnGrTyDcCmEQOo8VKfnyoIaPxJ3zzcYWwWEac/D6jtx\nXT0KU7uEarXj0y0lVMxlevcl9jZlMrPHj/DA4zQqT2I5tou8Hy/gk82IuZn43R1oG2XEOjdm2Uvf\nATfinZk4k33YAjFIriaiV5fQdnsoTnMNJk9/fK5JaI1TiNS/golTtEduwkcNelL++2X6r8A/FTfF\nPxpUgkTFnaHLvQ7RlIMx9l4UazeB/UEEdwiarldgxP0QPgBuOECw/WVMs99G/fAMwtufwqjJkGqD\nBc8h7PkIhi0mvHAPPTEKTR1TsFk66K1aTdjRJvxD0ki0PISdFOzCGISlwNJ58OwsuP0O6KtGqfyc\ne23t3DpiH6+EdlBjd5Ea8h5qbSzCtBhCjxzCeHTTlfRIE1PQDvbiN9ehCfsMTjwGhglQWonUo6XH\nFkVIUi5cSEI5WE3N/ZE0DI7DiYVhJ04SWemHrgB4q69kYkgcDqYwKq69nWDtCtJePYKaGIM/T0J+\n30vo1ADn49OZcaAdo7UWTctBWkdGE1d+M5pBiwnkPYeqL4G1/8LWaTOYda4UoTkR0Z8MWgF6z+OM\ntFDWPwyvfQSj9vo4efdIdNdkkbfjSYLGPoJVAmrcINyN5VxYmo82bhqZf9wLymoIqIiXg6hDT6EL\n1iH4AjhNEWgM49AGluMzh9G36VMiRsei5D6IL+oNTH2nwOhEcZtwFytY8hJg0E6QQkE/EG1GGeqK\n1+g9FMAn6enddBPa9HR0qT50iGjip2NZsI8LvmZG/fgTTJkNPtcVl0evA/pMuGteoPunHhg+kPhp\ne6BlAWy/FjImQlgXxD0Mrg9JnpXD+RW1WCcOxu63Y/j+CA035mELeweX62ayKqx41iRhkXtQ9UEE\nKYzGLDvGlj7CSw6gGsKx6bMRX6hCvvglLs9DGPr3oVasRdF74f0bwR2Db66P5A9aoOthFK0Tr+E0\n+tnFeBo3YfrpO4SMVEJDHmH88RvpHJxK+PcZzGiSCXj01JqHkTrzOXbcOIeJd85AN3owyo8fIyWL\nGBoCqF35SFNvo3HEBuL1v0Vw9RG5+TXsBRkoQgdaQwHBmABicC126QlazrrQ56T8D0v5X46/hWvb\nX4P/NcpYpgUve5GVWgK92xihFGHZZEWw6gmsuxnONCEVFCCm5MH5Fji6BAwhIOkxGw04P+iHudSO\ntGwR2HRQ5YTUAAww0NHyPsFIH4Y6PZbWVnzjRULLGiiePZrQ2HSSowcRSv8rExGA1zbCw1NgwzoI\nPYgUnkWVfSqP967goO0+RrV9TuL3FWimZqJe3IGEEWHqAKg9AuOb0EqdeFQ31HwEnZUw6muEbh98\nNozGxMlYR5dwMdZH88fzSa66QFRoJtHsQ58koYQPobfgbbRiBEbiAOgxazhua+WmYzqUhB4c9/pJ\nqouhtV8mUd9ZOLUUQjMKoUWkNb+S0MsxaI+8DCeXo1OCoOrw+8Ipjcxj4qhGPAXziQhZgiQbYP8T\nGA5+zJDGUgQpAq8tjMlvnsbu84GsR6MGcQ3NRb6wj67Zdvq0QSZ99lu8/WMQihqRrzYjxvaiBDrx\ndkShSYlhZ/F9DI36Cvp3IheGUfn1YdrHzSbzwmUMlrngcUK4HjUQw97fXc2cbzoQ5xVcMQ3JHiR5\nKxF3NBG4VY+Y5EPQeJDrzuE/epC+EoVA2zYCIZlYhFpcIeHom7zI2+fhHZSKN7CboNWH9/twNNck\nYZ9finrqfjAp9A4vxOc9imBQCa9eiyiHIuxZRP8bdZxdfpxBew8gfvQSnfZWunkb0aNB3nQHKkbE\nm37A13eaKv0JNB1d2P1mxBLQVbbDcBHlzDZ+StfSpL2a60p3QLaA2hvEOWIr/hMGNJcEhGovqq6V\nriemY963harc91BidqBEZRBf2oi+5nZMYU6sP51BNehxi6GUxF3NmPR45OULSMiwYB0+CjJn4y+q\nQvCcp69RxvzuFpTHI9Fnj6PVvYwwNR/drEfQFi2Bk+cQlp5EEjQE5dX4grcQGZWIqioI/wD2Yvh5\n4dC/JP5XKGMfp+jidlQ8RIgbCLE/x8baD5hbdwGlvBNh/ENIEyXEex+4EuyhvAkT7wFRBNmHKPsw\naW+gr+BL7LfvRX7tadRvV1KdJ9Od0knKbjfWbDsmq4mu+BAsjeUodh8DOn5ArJDh4irIfxKS54Ix\nFo4thTe3w0OTwTgYws3kZ35MS99kcr6aR9SlFlqTM4luP0NQNqLmjARbKMx/CFr2odUJ9OUnQn0W\nmKaAKQNMwKL3qSnpwpaag+3iRfp9/zk+KY2atPPEtl9Fl3EHVZkm3OI9hKlj0KlhGMVkysbJTO+J\nQ1i3Af/vHyCk84/kufQUzo1g+poA/covExRG4+inxbrlOIZaL4SPgmtuhr5MCHxL2aNP0u/kCkIy\n3yEk5E9+7qIC4fX4H16E/oOdCMYOusMlwo72Uf72a+Ru24KghKCdOovT958m5qsaxn9xFMdSPTqx\nHmuoB6lBS1VYP2zp6YQ8swONu5kBkzbj3leKdbEZvSmOnPEGgpYX0U4YAoEeOLAX9fPrEMaayDmp\npavkJBHvXQvD5sG426FERv12AOr1t+E1PIW0+Rh6ewHB/qHoRzvxp4eiykEau7JJWX+CvqLzGK6K\nQL/9OLb0cFyNC3EpHdie2kzv1xYMQ3YgaYZiDZpQo6bgoYJOdRdqjB0xPhZTQwoZ8klKZt9M/otP\nYju+mtoRNoSQHnpvWElDkoYhuinoD50md38R3L4TStYiCwcQYhLovuc3bA5uYdDuQibu20vg5aWw\n5ncorWOokRagH7Gc6G/bUDJlhGv6CCRrCCaNJHNzNz3zQ/CTi5qTitRYi1TfgiAJqHVBnHFmCuQj\nKJUyFcOjSPY56EsoxRL9FNqx3Xg+ysY+ohhPkxvN/u+wBfpTq2zDtv8YFD4MGQIYhoNwRY1opJuA\nUEaPm0NADkenWfI/JfL/T/h7MVP8YyxdPwMqCgIiEWwmhnNofHkEVy1n0CefI467Ft3qbxHsYQgJ\niVf+sPE9UK1gsNFbWE7zuvU0ntlMzeebqPhiDyeOTOTMhCO4E8xElJnJc95JuD+Ey+M+Z5NpDpLu\nMpKioFGBqafpcs6i8aSfhvUbUfcvgvdTof4IHHsM7lwApXVgL0IujMTRYkJXGo4/Sk+UoxqcLoj1\nox80HjwOyBoKoXsRaltRuxvhxLtgHwxy8MrcR92FUx9FwvlwErpbEUYtp/n6Aux9NkKqwoip7Ma+\npxlDy2iySxpJ40FC+zIYcbGQ8KcW0n19BNWWo1yOH0Fvup6K+FyE24sZ/vvjNKR2UmgbhrleA4kD\n4KE9VzgYtnwNdy6h8Ox75J/4DjquBM2o3kLU5pWoUVPxMgbZEKQtOYcIdwcGi5Z+69+FrsO4rnfg\nyP8Y7fk+fBOjUR1Wwi9HQrMNcbOKZqNE85aJmPRP4Xn1MwQlkQmFe/A1qohl6WgAS34+5StXXjm5\n14Uiu7xgFKCvgXTxIqEXyugOGY53/UaURyKRv34JV4ce8fALGM54kGd66Rttp2NIP84tD1L8iIeW\nL/pxQZmAbdhCIh8aj7XhMpYeCLYupGf1ISLmqoiewUSbh6CWe5A7T+IyjMMYOIrRcAcRbTcSucuL\ntUyBtGmYRpjR3BBO7R9fJvGbKtK3nkZ7MQpvRn+sugTw9sGB10CvwJEloAvizErgwFN3Uxf2axaa\n5jGk8TBCUgS6tWVQMpzAliNkv7cMKT4G/8jhCPEGLhpuw+k6xdnsONq92+mpEwl/9QT2Z79Eye5F\n9UmoLhUGwbHioZSnRdCnDXJc9/+x995RVlRpv/9nV9XJ5/TpdDrnSDc0GREkCogkUTFjVlBnMI+j\no2PWcWZ0DIM5jAExkxEByZKb1E1DNzRN55xPn3xOVd0/mHtn3nDven/3ve+Mvj8/a9VaZ1WtOmtX\neL5r17Ofvb9ZWC1gf+crxO+LED1pSGm5CFM05mQPPbdHIza/TM5fejEdKkfPkaEwCS596F/Emyxd\nzJ5dj6Drp9G0E/+4QP9P8N/JHfpHjUDCyCikzWeIPPQE4RuvRmRkcuAX9yNfOAMhBFpTI1JaOpRu\nhpYaIoUTOX3P3Rx6aDo98S8T7HsV9bIIWdc/Rv6k8ZzXcR5RhRNwlq3G9Pb1tI0Pk390PtNHzsSq\nhMEdQbc8TPu6CsqeO0D9JgvxeTriaDNEJJj1G7QJf8AzKJneB6ZCczJSXSLJS0tRRy/g9ZG/4vCI\n2egOM1KfCls/AC0MPStoNd/H9yMOQ+ZkdIcGkgW+XghfLYTjjzM15XegNcOUZfgLSgh7GkmoM6Pb\nKvAMn0Kex0XIe5hGrR9FisXe4ca58zhKioXYsXMo6smgyPgeg7oEFkMu2opkuh9NZqDJS603HvKu\nh7oK+O3VkJQB1y2CNfdQn5xFxvy30crXoAUG0DbMwas8iv3wUiYAACAASURBVN/6W/piB1CibcT3\nV+ML2pAdbvTGM0SwoH4TwDW/n4wyPzmT1mO9YjBKykiM9hKqrl9AMD2DqK5mLHoEu8mP9s7DOIwy\nPR4jgboBKhY+iDL0QpAEvs8XEXktCa3hD3BeLqGYRHyDOhBXmPG88jrhsnKEXUaKj8KeW4Ziy0Mc\n1jn1jJ+uh7dhuKeU/P4II0wWRianIvu8iAmPQbWAaCeRWY/S+oc3SSksR+v9BvPdNqRVlchbFaTl\nIWz7PkI/PoBwv0/9BdVU3GQgZFShYwMgU5wWpq/ag6fHQ+qe0xRvqcLxyRKSvvsEdXU+4ZlhwnPN\n6NNfpe/75Sy/bCpJCZUUaMdxRA8jZItClXsgMxtDugkRZaLrmQKIMRI3/iKkgRCFGTdgipHJTNiK\nZ4KApc00eg2ELpuObhHo+cXoY6agRjmxJvio/CwNc7mfKd1eTtqHoUUnglQH5VtRgmfoXl2BlptF\n7Nt9aCYZ2RJGJDkg4T6Y1AyRL2HNc/D96+DuQAiJrs7BmAzvIsT/A+/KfwA/FjH+cfTP/4tR164i\ndOt1SBdMwrjiW4SiwGd/W7tDa2pE2vMBtDTBk1+ivPoc+bdcS9KwRcifv455/1ak825EbNtO8NQq\ntNp+AjnD0Msj1L44iIyebqyGLPTam3FbJHoOTKZ363qShsKYF5/A6l6L3LztnKvD5HRU05u0mkJ0\nJqxFibXRoxgh1cPJWRdSXBQkQ4WKQDy2C7NJW9mK5O2BsRp261A0+62U1e1ieMhCJN6GYfzdMP5u\naH8NBu7D0WFHywigxcTh7n6O/HediBFl+Mc+gaXme0jXGX9iExuHzyfu03yiUKA6Bm65CN44hbjE\nAa4oJMN0Mg6X0tLcT5sSReXHxUzpWorW5kMKdMKgAdj8W9hswJ0/lf7MGDpFIseiDFgCzzLoqIRh\n2DicThvZvduIeP2gRRHV60X4ddRuEGY39lNOfBfNI+X+T5ACx8F5BNXwC96PmUFsooF46+1EZR+g\nlYO46CKkv4fZFuHU5FFsuDGTOdYRYEvClb0TNryNPHgMouQR+OhhjA1NdJpjUSaYiB00jH67hmPf\nfpj1S5j0GPorlyE6YFhqEPX+52lJPUPm588jjnrgxAYu6oiB+gOQ3Y0+7E8037eU2N/dQe3UA2CN\nxTA5H2NPIkrzIQx1bYT2OTBM19GPVZOyuRyvNQ97/ig0pQU6vAhfEOOrs6h+8AfOj/TRmxlP96BW\nhh3fi8RoxImz+MJNtDOJvnwH87sbiEqV6Gccr4g3OP+qp5j2zv3w/YdImQGkO/PxJXrJ6hyK6HgL\nwlY8DQ/jLFqC6eVNiKtPYBqcjO710lfyS4ym9xFndOixoBcPZcrkvYSW6dSG4smafQe+5u+QPY1Q\nB8Q2IcaaiaTGIld6kGdl4U93YzozAbF+Byx/E32KA9bXQO8puP41iEr4F3EnfjIz8H7OGf9D0Hs7\nEboH84rPEIlpoCiE8BNJ7KGJk6RRDO01iGMbYXQ61O+CL/6CiHIQFcqFs03g9sDGd2D0JUiTPkZd\ndzMm/1GI+Mnt70FxxtJ/qoZAvRttuIHYGc+TfX4Qnrof7J9DdDeaZkVasg3iDcgDz5AmHiAleA3B\nlddg8fTBkN/z+Kk5LC9SyfjuJlZNWkC0YwWBgmxs1TWYdq1HG/Dg0r9k8AOJdLnr6Z8YhYj8mky/\ngjywFF21Ekk0YTrvt3TyGAmbYxFJx2HEm4T8n2KrqoVgPZJ5MlP3H6Apy4b9rV70x2JRSz5GFBtQ\nykch9l+KOJNBXnwDpy8Yzud9M3g873nS13ejJStwNUjR7TBmPSQWUkcXY/o3Ez5+CRe21uP3m3HE\nuWCTD0a4UAN1EPGhpCnoCTEEnV2ENhuwnjYiFfmwTx4F6jpoW0LEnccOW5hM8RKruAzb8JEczhqE\nGolGMeSTEshlbuT3pNmbaGhNperg07Sfl8ag0h1YjAmI+gDUPQE9pUhaiLQfGujKtiGLEwSVKDwB\nsNmS0JUIvdMbccx+AcOmZwitfJyk2z9CyM/DxBiYV8mG8ucYuewl2GQnHPcHYga345j4Z0zdKzBW\nX4rW+x1haxOh1HmEM1ehXpJFQ7UPMdhH9Gg3aRuLEVWtiNYWhIigZkxnYMr5mN5/gM03X0WOezfN\n8+ch2o0Iy0gGqt7npdsfxWyK48Hj36I1HidY7KPhTAHXdb1L5mFQu63Ik0ron3knNbYviArWU6+V\nktk9Ejk1DLH9WD9sIvL1NpS0RVDzKiKkYHnlYprmppDb04+Y8SRKUwV6XylcbKBwWzWSfzs1cy4j\na+UA9mEPoB99F/fpASy5dqSiM4iydmwns0GUQep8SPJC7FWIRfPAGn1ufOUnShDTP7sJwP8PxFiY\nLIi+Wnjrj1A4ikheMXWDJJTcTg7oXTQcDTGk7QBnl1xDivsI8qabqF0yHFtmM87GcvRJg4npq8Zg\nbIfy9zEci0CbE81zBu0KcK/vovVwkPy7+hBJycQU9GPech983AzIcMEVsOk5AgUC5cPLMBhGIWZJ\ncOx+JIsFi3k3uCahj5tN2qpOAh8+g3LmB+bWHCcwdSJyYRWGVp2IrhA27UU5ruNqKsDlqUVPBEP7\n2wRDEu0xlxCnTsCz/x3CvqXEegYh73sb/nAMhIZWfR+SJQ4Kr4ParwgrdtK/UDi6aD4jC3+BZGxH\nzzmDllGLHvwCIRWRvraWvVkp3FKznsghF30uKzR1E3Cmk5p3DN1yGEEh5XQz0TGb1KJ8gvbPkL7/\nmprJU8n8rgvl/I/5wfsC46vraR1uw9LpxnTAgNlSjDTQT+j2q9GaX8bclwChJSg9nzPd+BLulq/p\nSOmiLy+NXxx5C3/hDFRNQj5Uij81irTORha/sBqTZiLS0EH7zByEZfK5e+73gLcMAj0Ig068sZ81\nxVeRdLKO0AiVYE4NLs80Ymx3YxxyD2HvdsItrURV94E9B5wjoOwZFJtGZNA8lItnYfzsboyOAOH+\nF1Da4pBO/QVp5CgU02IsfgW9346qbac9exSuJzcRNy2AeWwvFC2EbfeiSyWoPTUcYgnGDCuFLV58\nHR6c247RI7JJHpJObUEKNx04Q3bqWSRLEt7MU6hcRXEwC/OEwajFK+i2byBuRxltze8QlddP0J9I\npZJGZsMhtKhhiMQC2PUh+MNI9WugzgX2dEwZ3ZxvO8QT01Zyj38l6pSLiFTEY+9tpSM6Adcz60l7\nsoe2QUbydi1DjTEh4gW26CGI2FkgbSIcbMc46G147RN4ax3iJyzAf8/PPeN/FFY73PQEzF0MagRF\nlimoKiW09H0Koo+hdHsJt/QxqDcJrVMlEuwiz3aIrrGj6M/IIhIJY7HaMWRcDb0d0FELgbVoQzTa\nVks44icz6JWjqI4stICK7A3BikMwIQrmPQpHvoNME9aAGf9oCXdeA7HfguRaDTuBKaDv3Utwg4PH\nndF4Rvvoud6ByddPdFcz5vYezpTM4wxe8hpqyWkIk/9oPcZGH/L4IIZiBVE5kvRfP4zPFUHufIWY\n5a8jCoaAJRc23QohH2ZrBKm9GiJD0fMeJ3LwOWymdPKqd9IUbicjZhGo2TBqEZyqQNu/l4aJ45j5\n7DZMxhBvPX8fF3dYyX6/lJjkhWiVNxNy3o8h6nzqGWChVABxYzDFjUHPvxW/+lu6d5ZR3z8boz0O\nNSWDpJoQ+vZG1JzBRP44Cum+b6DtTUyxEhiHgD8Z8heDkIh65w0W/2oVK1PLObq7kh5RRFFkDWAi\nqMqkd3rg6l/in3g3/UsXE3MgF9K7oe0HMITAFg1qL3RKiKIE5ic/zZeRD0g58DldVaUkx4YwDL8J\nHZ32XBOxw16Gj/8MCz6G0y9Bx+dEz1hJX/9R4rMFdAfQm41Ib3+PdNEcyIhA+BJwZUPLQQLOAGfC\nnZRUDDBgzGDXnINMOjQHU8fLdOfHU3H+r3Ee+JCLGEOhTSE8J5/jO44RscaSdNmXYDAxbOsecI0C\nz+/AciG6LUj4sS04JhdjGL2YxkQTO7V2Esd1MHJ1KcE4M3mhHIpP+SAlF7WjHLP7KrTc11GaIghj\nEzywHmINiK6vWaw0ECiJhZoqDGXNNB/1opyVkYcoBGoExeExlGXq5C7ci3x2F1FRCpK3DgbPI+Lt\np0/ouHZ/ixgmw8BroGSC9ZL/VU2BzwPvPcuEvdvBGoD5t/wkFgv6WYz/0cQl/e33BZdQXu9hyHXX\nobvd6H03IicVIR/8AUN1LVq6l1TvuyiWORCTiEd46VG3EJvYAcYVaAkyumwn5YMatLuyUXuikVP/\njLH1RsQHMtotf0RyvgFd34O2D3JCcNqIxWvA0CDovryXuM+MSOM0eCVC8BIDUnwe8px59BvPIlXH\n4Nb34mmMED6ST+qBDeR4wshFLqTFr3GsaA+jd65BaaiDI8Mh0oDhkWtxevuJ6mtGKBL64WMQtsNG\nDdKHYW45A848iE1ioOkMxCcjjbgQDq7BE1Lo23gDzuYQ4r1YSDcihrgoePks5YMLcSV7eKR9C+rQ\ndMSSCPLWtxAtYMrsYnP0czRb5tNLLLHkEKCFFtsaDORjHTjGoE0plF/UQrszQMa7lYh0iFzWiLH+\nA+Q6B/KL8WgzPIjSDqS++9AkC42XRjgwLRdP659It6XSk2Jj7OEA3mQr3YYkpl72GsF359F8UQOG\nyAs0XtrJ+Y+9BjlApgHS0+FYCEIa1Jng2nEIVxZXv1VFp8tGTLtGJONpJOGkjWW4Y3qI6XkX//wM\nzJseRgwuR+2fREukm83FuVx37DYwmtAzwujn5SD2hiF5GJz+DOb/lrB4n464DIr3BJDzhhGjZDFj\ndDcD35ThHRHHogufZt6RPdzi9iN98wbsWonB2kzzr1/g/EGZCMNfP5MHXQquwdCwGU0bi7L0XZyT\nnBhnvQZAEzsw+9zEdfiImWxEnJVhbStieAuMTEVtbcZ0+YMwQUbPjoAzDOYVkHw7aGN4vvZqvnBO\nhvSHYfg8xNd57N2ucuX4Nr5fOJMpLd9RO/te8loSidlTilwwEarL0JKupiamlpymBsSqjXDPcPB8\ncs6PMXgAnI/B0SOw+gOoPEJH1igyfiJCDD/XGf9oEFFRGJ9fei6A5y2G7jak5t143E9hD2lIhR6U\nt9rozR8gtmoAvSkGrb8f+bwL0db8FqXNh9w7DdG6B9NXKpFRBdTNO4n9gB+Xe+e58p+uDoTLA9ZC\nlLMVxH3hRp9jwJ+eijmzgYgjGltyKkrRoyQtfQ7+8CcG8q14rhiBY6QFR56Me1Q2zm9qCey6l/To\nAEdHJWAZ8wTFp5f+tSQmGeLPQAboQRt0DqAuGQCTDflUGGGMg7ElkB5PeZaZEVWnwb0dMdBO7MQs\nDhVeiaO8lLHVbWBzQEEAfc4zpJgnI6shpNUlSCVfoRfEIFaUQMiD6DQyKPoYpXIics+1qJWXYk6/\nkByfGU/PPiy9XuStHzLh7FD02ipwQDApBssONyJFxTc/FqnUQ+MNFsy59bg2K+gFMofm2sg8qlG4\nfi3RkQFOhdPo/1WQNtKY1e5DqrsNW3IDmfvDdESbcToHIX73e3j1NyCqIJAPi9+Cj66FnfvA2wNo\niGxwuS7ljKeS0vwYLqIXNxuRRT+ipwNT1uN41a+xH3MhX3QrtOyiV1gg5Ur00ufQCyRkxyMwMQLf\nvwdHD6Ce2Yw8JUR6MBNp7IdgTIJfxCNtX4lBXs2Lxc9w2YFvmVEZQgrUwu7foDqSYOkOBr49iivO\nibdtBTZrAcTmou97BV0pwrvqUUwpMYRzh4PRSS0ddGj7uKi5HntVC1K6HVK+gueHw8YbYPc6RIyK\ndE8nekOEyAkZEWsi0rycnoqTRA/sw+RUuXboEnDdBg8W4TEoXHpZHsIicaFhK8+0Ps+inU/gd9uI\nUwHNDtd9SqtnKSn7GjHUucDSBGIepNwGQQ2++wx2XA9DhkEmEBWP3d957p7b4/6Zof0f5sdSZ/zj\naMU/GSnFBX3lEFUM/vfB9zw2yYbe+yn6znhEkY3o+g7waGiVPgIL8rEf24TwzUMrGsVAcxl2cw9y\nXDTKGB+pX0YYiNbpSEsgZDbgb5pPlqRhjC0EdyP6NREkbRHGirdRdZAP9VB7azVtza8RGnmapA+G\n0ebKZeT5lxG18Q10YUSN82OMy8eYU01HjYPh7zWi5G9AtAdh3KXo/R9DjAVdCkGsEbHZhNRqQpz2\nIOxlIIWg6xhqYgnBnhPYWr+B4TuwVc3HrQ+QJM6yY/5Y8povJK6tC1q+hLhLiYt9C2FeDNm3wP6Z\niCG/gzu/hQ2PQ3M13hwLd/bUIdXHEYzfj+WHOgIl+fiN/dgVHRKnokc1Eb52GIrbjnKyg9CdFZhW\nTcDkNFA9YRptAzHk+OoxKluQVBtztx+HPV8Rtkp0J+Vz0HUJqfJ2ZpaXg1KPHvATSYtGHluBIp3B\nwFcEfliLZboLxn8E8QWw6xNYvA4qR0PFPgheA9P2I17tZvBtv2WVfpY+dSdze86SsToBWTTi+3wV\nlfdNYMwHzfDB9RTMvoPEwqngPo06LgGpIYiofBGMLijUISYJqaoT9oEoPABlt8JAAPxdMNuGWUnk\nuQ/+iJ6Xi8i7Fg4dgaueRLfI9PMS58un4EgfnXosxu5+0PoRbc2EanSsg42gqgT6+tnb8z2ens+Z\nZWzCmPAsND0KWgZs/iV0NUNaMiRaMH7vA4cd3QBanw2tvwf3DxAx7sF4oQ6yFRp/CydfQ++uJXmM\nC+OQeOiehjLYgbLGzLHUBYx27kfUVUFumB5lL0nfb0fWQqDmwOT5sGcnfPEOhPogOxOKU8Dpg7AD\nkqZR3pvCoJ+IEMPPaYofBxEvnHkHql4CoxOcg8AoQ9RiVGciPeaXcK1z0zZ5DsTmYX/xjwTHZGKv\n8EGGGVFehnhoNfa6Rwke2I8S6MNgCSGOdyPNCaGpNsIRCUvvfkInugnVHMLkEEi1PvTGN5B6TfQV\nGFi1ZB6BOAuXr/oYPeIgjqNYCmaiqh3nLJ0Gj8Wi5uC/ugfLrgvIWvlnRECBqBb0m2So/Qh9tBXR\nbCZwVsY6+lYYegQx7XFo+yU09UO8AtolNNgySJejQdoBYQ9yazHRnRrp/jApvvmcSbUTl3Elfd6L\nMHZehVX9q8Fk/k2w+V1ofxOyboD52dDdTLbpEMbo/ajFr2Ow3oE2rZc2/x1k/kqH/T1wWxGYB2GI\nTUY0HELqrUAxpqBNeIe9a4/w5fcpDPn6FJPHVSEG3wYpRkzFd+IrGY35L0sYCHmxjP2BpPYBwtYh\nWNJeJ2zcj1a2DEXagovZuHgKfZwPFOvfnm1rNTw1FYZNgNBhqP8G2hLRzuxCku/nkcMKe+xXYnS+\niJLWCQdupOveJbhdawhfuoSqk6vZk5fNC6cehIFu5NxbELFNEBkMxX91XY/4EZsmU9/rwRCfR4o8\nAhKz4MBjIJuR0meCYSPCsB1atkPEAOveQrlkCvZ93YhQNXqJkyS9lEMz30U/tIzM1Sm4/vQB4sxd\ndHhb6PE1M6jqNmJ6GhGngJJFkNcN5cAln8G778G3+yDDgJ6ioQe8iAGVwFkVh0VGP38aHuGm0zaC\n+MQuwsJPKKEOf7aL3mtdxHIlVCfCa49w/fBZPLJ2NhlX+EhO9RD0KDhXP4ekhqFDhqbDEDkK/jCM\nmw2L/whJBX9LR+g6CEHk78pGfwr8LMY/BmQrZC2E+HEQdkPyzP91yIBG1NYP0eQWiNRD6QGCuQLv\nvDgM7hgM+ia47DLoewI5eTrWFImQ9wTh06dR+3zEfhIiEh9GnRhF/JYB1KBG5P5FaKvfgwGZ3tkp\naIMW0HJ8I+O37OfgdWN444oriHdHuGddGWm7j9IX6QEaILkemXwwGlHPc9FozSNzazWRC9woLbHg\nTUTqL4Kh8wnXPQzPvgmzC6BiBXjjYdK9sP1X0L2BuiGNTNBnQ96zsP5RxLZ27COHUD34Fe48nYDZ\nPhi7pBOn5LI4ZjPD/EvQ2Ybo3QtxEnQfhywDRC1Gtz+Bp76AsNBI6alE2/IIbSP34Up7GWnIF5A7\nEzrbEae2QIsbXc+mPmYuO594n6iO0yR5j/PYqXISbsqgd6eOSQGzJQo+vBarkopWtIDaQRoFrSeI\nLyygMTWBGg6TwXgG7ziGPHoWVH0MCaMQsYP/5bOdfS8cWQ/RydDRB9F2NG83gVsUrNILOEbO4pJ9\ni9G/XAsdYfRZsaR79hHt6UQ1HmTpmOkkD0hE56+AXZcT+b4ag68UsirBfgns/QEOrEctHoVlyg5O\n5ccQzySMofOgbgXMXAZVr0O3B+S/5u5ThkFMM1iNGDx2GsZlMZD1GGf63mDIsQdQNkHUG/sxRPmp\nb6xBsmukiz4cxe9Ba+CcV11mO4g9cP5TsOcFePgjtD8sQEuqRK6HQIeOyWPAYg0TdE5g5eQhVMa5\nKKjpIirlCgxGCVPTGqRr/CSIBji8Aqa+j35iFq6za5gwZgx7B/LIiNlB60idgsE7EXs+BbkKpBp4\nZB/Y7GB1/Nt4+onkiP81wdDPCwX98xECzInntr9H1xDt27B40/FdfiFa/XYUq51jicVIqwRpMw8T\nZU5Ei1axtfcSKPsca6UR49RhaN8b6DC48FT/gAgI4n/Q4ZpHYd0bmD95iY5bxyHFmYjd6EU7+T6W\n0xrmaxWSB+az1r4LxZLJ8cLZVFhMDDXKRDsvQK52Q3QM5kAXAfkojjNW9DFXE961mdCNX2D7/pcQ\nNxfUbE73XcSYzuUw7CaI74TuanhnMSQKAmYTUncHYu1DcNc+GOSDkofobJLRjI9REv0MY9QKFpiP\nYu6tgO4g+sA24Gv0jCcQznth3WswegZgw9+9mnDOzSS9VAp57+NJjQUpDfu+V+H0btQrLsC//yDW\ngR5auzIQ2R78hjIWpq5BzvdAswI5sYhQgJhbryNYeoCB5/+CLcqPNqMZ2TyeWMMpop/sIf56E07X\naRKTN+MxL6Mtqo/q+tsZVr6dmIAMw++BkrtA+usrHZOMdtcrSK/eAte5QZ2PlJeOtfWPsOMPUPsS\n+HYipgCdMwh16GhJs2gePYICfkWcZzu/eO8ZuOdW1MRs9lx6MVM2F8PpP6Ptu5TQWCPhe6zoTh+G\nSAshr8BgKUDvvwviYxHrxpxbt2PU21D7A0wIwRdrICMFtWAuB03LaI9OQDvyLNkPVRMWBcQvuxdZ\nu4+ujnRErk5yezsBzzSIng//0zjDvQosi6BqNUQaGXg/g75ujeSICjGJWG/IRd1+FtHWi9J4nHkn\nrFwX1oi0mYgu/Q4eSIRNLryZO/Edl2HsW9D6Ju7pA2ixd3D7pg3cHT+V+KH5XLKvHUXdBOfdAF1/\nAsLgSv5HRec/DDXy45DBH0crfmwICczJiIRmrGt+oP6WKVgUjfPFYPxeO3qjwp72Js6zrcXnVOmL\nU+hLU3F6s7C2p5N6TQttK6YTt3sT4e5+lPAyDNdOgyn3kLzjEdyx0bSPtmI5YCW6rxFJldgTPsA0\n6SlS6hfB5nTy5eVIMWFqRw+mNxRPsnwZ0dHvY9gUQooPEz5eBk1DCf16LsaHfo1h2zsw8reETVa4\n+gEYMhkCm2HWAxB5CF2SGYhOJM95Obq+F+03I5FK8tEtMi5TJRFCvDxwN6rcjR5ygLcWEhYjKp3o\n44ZDWhoYb4aPXoKDy1GLhuGpeojk5TmI5CKC4+fSZfsTWb634Pzh9K+6limvf0YkEODB1LuYknGK\nFHsniddei1TxR4gSMOk6xKfbQYkgtv8Bs6sAefgsJNcu/P1O2iYpuCqm0KqeISHxQUw5ZkxHn8Y5\n7Q3Uq45jdQ4huPcuuH4FyP/yU1Pvq8Kb9SWO3/0atn0GRdeBby2k3AHaSdiwH0YNh/OegmMHEK6z\nhD69AxFzL1uyTzPVnknc0Mmw+lm8WgsdkbW4kzWMLglsyYjCB7G1pyMd2Ia+ZjsFi3YSTBiL6UQs\n4lQKzF8DWYOh7HOoPASGEkhPRW91U7P3N5jnXY5zZw2urxrx/9BPwl0B5KQraOoZjbV2AvYsA40F\n75C+a//fLqpp0zlzXP+HoO2G473YmuOwjc9CyxmCHt6JfnYQUvwRQuY8+gZ5cJ1qRLngatC/g6Mn\noSIHKjcRGT0L2VgGjQeh2oNz+3JIziJwsc6M2CB9gWjaq+rI4AuY+DAE+8Dd8Q8Nw38UauTnNMWP\nm6pdMFCAmP8bzMFvsLbXoyuVWEYtgvZmci4pQjtRQdTRLuw/ePCFExEZdXTd0Y1pVz9JxyogFqRM\nFQIeWLsO1mxG4MI+woBDsyNK2wki8VrJYjqkTPK/fBLau+DsUSzTFqJ3vU2evR0tcpLIxoO0WbKJ\nd2pUjxvDcNtC+p5fgmKIcHbBZvJtfUi15YRMVrj1KZB7QW2HiX+CsuXoAy2YDTXEfP4wqiyou8pF\n3t4T6CJMY7wFXHb0jmo0RSYQm41xSNm5z+s3r4OGXrjnGHpkCiIxD636bUTpSXpSS2ixdDHE1kSb\nZRXRP8gEei8m7B9CdYeZW4Y8S4LrJAvO+walPgY6FEJ7t4NlIiJvAcYP151LEbXsgvHT4cxZDL1l\n6PGdREbFIdk0ovKH0ussR928BinvS0i5GHnlB8ieAMao/ZB73b8VYnQ85sfQ2g/Clm/xXbkG3/aH\niXfYoOQbSKwAnoG6DdC0GVoCKGOyMLdG0PY2siOjlEc2voy28gTBmQoMySbP4EQb+gCmnc2I1mYo\nnI/u0tBHnIIcB3F+icqzJYxIeAgunv5379EOSCmGfeUwdQEiXSf/kyfRa05zdHA3SsplVK/uIEdJ\nBYOJJE+Q/q96aLp3KunRK5A9Rgj2QOlvwJYGkz+FVxPAfTkYjiOVNKFbDiPtP4KeqCOGf064w4qW\n3ImrMYgSaIKeWmg6AiqwcgPk2lHCbpS6OpBehsIbIfFGqD9Es8tAsrUN58dODpszSenbidK4AIZO\nhE1/gZAPjFb+O/GfEWMhxAfAXKBd1/Whf913BfAUKT4RSwAAIABJREFUUASM0XX9yH/kv34W43+P\nkPfcugt5U6Guk+RANVEjnkLNuR+5cTMiZCB7Vx0+pRstLYB8NAab1A8VRqzGNrR+E0wvoTcxAK5W\notzDUFpKodAO77WhLXwAyZyP5n2H926Zzpm4RH5zGNJnPw3hlyD5VfDuQXdKiLN9SOYARredjFQf\nvrxxyBlpqBkNxG3cgP7608gvlNJ/l5XoU1+jxU0Dkxl0F2id564n/XwCWUM5UBJi8rrfY6xqJW9d\nLcSBHgRdD+MuepjU7GJExXXYexznBr0cGniPIyIamvFx/JunIuJDDKRGIcWNY+u8hXjD/QS1b3GF\nsonaV0bzbU6UwydIuimb6ZO/pejoAUTvREJHezFMuwN1gsKA8ntMVccQXc1QMhFl/vOIvW/CvmZI\ntBMoyCEQ10lUSxMh0+/RxqWjbz0Bw4ph2TfQ0wVeG2SmQMxIWLPm3HUKcW4QSVbRrinF2GajpUCi\nxXw7QyZ+CJ/eAobbz40POHNhyO2QOg98HyONeQzZe4KKMsGYwC5sznL0X4zE2GHCJN1KD2FGMg1G\nfQp7ZhBUN8HA/RgjbmiajsVTRP9cO8h/J8T9LbB7OdzwGugZMHUB7PsLpAQ4eYuT6rJZjHryCoo+\nv4Tu26wkEELprCIm3saBLgcJh/LQ2t9C/24CsqMA0V8H/Z3nrJ9uuBwil8Lam8/5xKfKaK5MRNtZ\nusvjsc+JQ7WdJOKQkRPqkU0CKUOCNDsM5GI73UBvxI65TUPO8sCoRWiz36KNa8jdGsXR7Fw2pCQz\n6cszxH3thSVLYOdaOLMbii9CJ0KICgQmBGYU0hE/UTmJhP9TPeMPgaXAJ3+37zhwGfDOv3vG/4af\n5t37r6ZmOyx4G04+AOu+JXVIBvKsOegd36Nr3yLyHkFKmYt1WwGRKCNSUgARCIApCOk6Ulw0tOUR\nnpOP9cByIsknkcYtRjrTDv5l7Bg4jLPiJFtvHsfQzItYoH9KYsQDi+5En2KDy1WE6Q3YMhm924MY\nMMMCI9T1Y672k2Y1o2TlEpr8MbbdKZgvboKBWtSSVkZ/3QAiBGkJkHsGHCEomAUF5zMJM4bu99Hz\nslCnNaHvaUPpCJN2pAuGB5ETh9M+6hKSuybA0tnoVQF0s4SWlYFsGoY3PZfG89uwnpSRZy9kEj34\nlN2ECFBtdHLy/vNJUAdQR4Yxaj7kiJN+52DUmjCmnADGqmVYCydg7h0Eq/eiXhRLMNeD17EOcjtx\nGFX0xx6nw/hnUqqshOKrsRh3w4wb0Td0oTcXIfKLwFUAB1fDwmdg/JXnnlnYA63bIH0e+sl1SBlh\njJ/X4n1qLEkeG+bqzyA/CWrXw4Lmc64drZvA33rufIMddfzz7E3aybNVS5FbLYizHQi7AkffQJ46\nmMhXO5GUMO6F0dj6r0AOqiC9hJi6GHr3YBtYwUB0B46IgJYlcGIreroPkVIMqVPhznF03J2MNc2M\npbkRS4cfVt7P3kt/yeVyGj08i2PsQoxlUzCXOLCXbkRq9eO7KhktsR/7dhDBHWDQYPtdYIyFrBHg\n9sPsx5Aankdv1EnY3Yp8YQ6ctKNHD0LvKYPcDyHmMQh3g7sMvUvQObMQ6bCOs9wDg9IJil7YH0uq\nL4n4Sx5D+fZu3L9YQZw5BWyxkFyCXraGcGE8oaZv6EtbiZAtxPA8yk/EYunfQ1P/72VQ1/XdQojM\nf7XvFID4/7hS0s9i/O9RNBeNCKqvCsOU91Aam6F1HyLpYTTzZsK56RgOr0YKufC0u4hKO4uyTYFJ\n8YAOBS/DnmUYy9Zg7JUwhME9J5bopi2ow4eQubKS3qF2CiQ/40/uwWaV8Q8rwX79i4ja14n0BQk1\nP4nVp6G7DYARIl2QIKEPzCZh3zfo+77DOmYiauoJpEAKYva9RA5/w85nRnDx92EMb74E4V4QKfD2\nd1hlFwC6W4K5ZuTYdexPeZdhp77BkpEEf36EyOWfYyvpgbY2+q50oWxXsR1sQ6k4Ci016IZUDDGt\nZP+uBcPu5chzH6EjegOKx0/MMj9+l5GGfBua6IYoB66jp7G1SrivbMJ43EdXmgNrrx3ryWxY+AxS\nSgYGw7mZkfqfigmNDhGK+ZoovQOGz8EkTyGsvk5p7SVYC94myW1HGZMNG18D1yTY9t7fxFgyoLsb\nCLw7moGq08iJV9Axtp3kZXuxFo2GgrvBvB6CZ+D9G+Gur8CSAr1HQKhoRDhiixBX4OX0bpm4wcNI\na0zDYK2EjGQcoy6iyfEqlmQPzr4MAocm4j7kI9n/BGJUFcx9iYyaV2l0HqRYmQtpn8EXw3GHYomK\nvII4Xkr/I4MJpEaIP6GQW78Lg9cLxbcjhSOYTnbjDXxK0HYao7MU+gfjuHUV3tcX070xgvOyM4Sc\nSZjccWAJgbkAT8CLfuFvcJzcBj8sxZtkxVxvRxquQosP9GsRfIVIvBNcseBrOTdhJWRBdKWjFy6k\n17cGZ+GH8PkS6vJcFLX7ENc8i1nVmNnWQItfI9K8gXDfblTrPsx7m6DtMLZ9J7FJAv3GZ5BmzgT5\np1lJAcCPJGf832Olj/+HBOjmML9nA5exf0w3VfPuQSNEb/dH9Oz9BN3jQ7xyO1rjxxCtE5Oq4R2R\nQqRHg+Z7IGMiON6Ei5MJWb0YG7qJyAWoejMdLRY83nrST7Yw6otjXLz7CHvjR3LQNBK9Yi2qcz0Y\nuog0GLFWbCPcamQgdwh6YjyEx4JfR5hXoM4CoWqIHQPIvlxUTw+enLPIlijsWoSK+8bDxmYYmwRq\nAP78NFSWQdsZuCAC7S56tQSqbSOQJBVxXSni+hkYWERVaQJN6VVEspyYJ0YhEkzQ2IF+8yC8UgWD\n9gxGyetHOnMQ7cyXOJvjiQrOIjg9DndJIzlqFYWnNdRxH7Bx/sN8d9svaFbmIc6mEt0fxBj5HVrm\nu2j+adB+HbiXQtNHiAQFk8uF47NjxETXoLgeR0T2I7n3c8WGZziVV8TRE8fwH10Ds98HpQ9ObgNP\nB+g6Wk85we5NBNz9hI5p+Jctw/4XH74fOvCnD4JIPxx/HArvgSIHfPYbgi2nOO7YRdniXio657Ei\ncpwLQrvRsRJvdWC45zP05mr0vhPYQ3/EH6WSsDwXT8/v0L5eSeU7n1PTnE7f1q84+4uxuFfXsatl\nNStvuJr2S5NxezPo3pWBv+NBfLOWMTDcQnpZKVK6GeJnkZh5Al0qZ/abLyC/diMxXZdhO7gKPdgP\nMRkQlYftni+JirkSbtAQXIU6dTPET4POBqS4XmxSEHX6H/BbomgdMRO57gLEpGywlsKQT0C1QseH\n0H0IlDhoLIFSA2hZFLhvoaMgCrejjcikWzBWbiFGlVH7jhN5cQJay35i9t1GqH0FsjUbW9qvUGwp\nGCc8gFjyHuKdGqTZ9/6bnP1PjoDyH9v+i/m5Z/yvMBPHSB6ikxlIqsD56VrEDg2Hey+RoaeJZNnx\nDonB2dMPmc8iSndiKr4F/21X4nCVgzSZiNiCZt6CPjKAfmwqZwvi2ekezLze74hPFqjdMqI4Bps3\nnTnffkZTfAwbZpzHjNJGYobr6PVXESj/HFO/H5tWiyoHkJuTEfGzILgKsU4QmJyGIaxhaPag1Paj\nbNqNOuIqRmxZj2dQGprJiHTvchi6AUrGQfQAev2TYM+EHV/zXep05vcVIhQ7ndIhotPvo6/tStri\nR+Jf6WTqsN3oe00w4ABd0P1wNtFNAxheX02YFFSDRDi7HlvmJ/hi+2jzbyJlhQODVofI7KXk0xso\nCQZpcQWpzMuiONSAsk6DwaB5DGhqGCHaEQmfQoIGl/dBRguErHBgCqK3CFkHqbyL8CDBiMRSLKX9\nNBeeR+74axHefehKG/rWa9F6W5BX12ESNoyxBgxXJaJ1t+NIn4Rn+BYMnXshUgsXrICUudB9FYxa\nhOmd2ylJ6IL92dS/MJ3MvgNM6N+GNnQ9+sGb8afuRR8cg6T4qbEupMhbjOh+n/jKT8HZz4UPFaNe\n+y2G6ASiT66lv+dpHLZeqm9O4PKPOykrcCH9eTm96+5DHZFD2sEZiNBwUDQ48D5axAhj1uCcZoHl\nw1EOHEQtuodIaDVKIAVqjoCnheim/eiWHkLPPE1bQTmpI3S0oI9e16WkRty4j4yiY3QWue/tgIJc\nMJ2EMGAcAHQ4mAVvPgVuDQIBkMMgWhBHniM37KYt5WZivu9GzbEhekoRS8ehWQVSlxnbERnyc8FR\nBGoH9NXD4S/hzv+Yvf1Pgsg/uwHn+FmM/x0EMgmMgoYdkK/BA4+ixOWhGJ+Ezi5MMVNh0ONgywOx\nE3O4kN6xtxGJi8bW/R2i6xSSuxuHZqE5s4/9WRczrnMbcWoUwlKIMG9HuqOO9p77iNl1mNTePq7Y\n0M+R+FxSnE6C500lrWwDwSgF83AD/sBM9H2bUKwmRFw0UkovWquLhovcxDhtxLhNiL4oIqs+xRoM\n4vz0IUTaTFDMkBEH6x5HNTVBvIo0fRNhbTcFNSqew3sxu3301t9ERxwkt/v5tPYWOqUkJq+aiDTg\nB00hkpqAO9NE9iNn8YccmC5OQVUOYV7VQDB3LCIlgexIKoR2Q7+ObpYRJh/YY5AKernQsBvRYYf4\noVB6ApHtIHJBO8GSKvynR2IvS8XcaYNOD6RIcP5OUKJQtz6Lnn2aqtBIhugewr/aRWRKD/WN00iV\nagjf2o8ck4XhzBSE/Qio+XSOXwXqxcR+8zUaMmEhY39zC+iAYz9YXoESK1xkgxGTwP0lWOs5aCjh\n8s5vUPwh5NCnDCRY6dBvJVE2MCBLjG4uIv71WyB1HGz/DgonIvl2IbVuhdjroORy+ppeIL2yizED\nvYTmXY4hqZyU5Ua8uTVobw8gHVkB6BBthbmPs7YrmqvGfw3du9Cv7Ue8GEEOnkfP+DC2M27Y8ReI\n7obCJqgrQT/dhGP352gmK32mZJIOVhI5puKT+knTamB7LYxpQN81CIp70Q56kYZ4ED0VMNgMHVlw\nzwNQ+SVMeRriizFuf5zk3XvZf9copvoeBnsR0qYXkQ68A9cXgXzTuenPG38Hfj/0hKBiJ/zxZsga\nAq1noWA0zLgBFMM/M2z/7/nPi7H46/a/O/Yf4mcx/j+ReQGEd0P1c+ArBNsUyB0GdZvQU2zo2inU\nQQOojkVEm9NoVmqISvoLUpIJdj1L21gPsaf3c2P1MaTKL4nIRsKBdtTJCfjKHkbNaUJBBdmEFu1j\nxCtHab1gCF8/WErBDSMpNKZQ8M0yzGNL0aIFWnQPknMwHWoxSYYFWJc/TvfEEs5eEYOxNw5pZCqB\nthaEvRPFfgKHYyb15bkca+llQd4bVB4bTmbLNdjjw1hPrSCquxz/rABy0IJllxHnNx6ucnzND4kX\nEMmSEfUyIstG6zAnyc/XoJpcGKPbkJr243nEjOXVAJF9PuzFDug7imoMQr+MsIbQ84Lo47/BGZyI\nCAyFi6Jh/Gb46NeImAMY95vxDrZj+6waU9MRtDlJSCYTJNwKko0BvRf3BaWY22pI3OJDGr0eg0cj\nRV1KU/I6QtZ2ZF8uhrg7kYrG8D/Ye+8wKaqs8f9T1Xmmuyd1T04MTGAGGMKQ4xBFkiBBEVlF3TWn\nNe3qKoY1rhkTKooiimIAEZCccxgYmACTc049nbvq/v4Yf7vv7rvBfd11w9fP89TzVN+6Vfd21T3n\nqTr33HNIaEB9Lo2W0TH07czEO2Uhgd6JoFYgTeiAwauh5lUo+wwM18OqmxE+LVJkF9z1KQuqSlGL\nTxAYEIfm4gakmEyqHnITOsxMZNMeRO3zdGvjMGcuQ/72RpAd8OQJ2DIT4nIQHYfwhDnJanJRr1dw\nV+RhN0YjEjQY2nR0hkSzafFENJ56+lccIr7sYSaIBKT90YhaNxiBW7SoZz+ifnQEfb4phJR+cNoF\n31QiRXRjHD4Q+bpDeFZO5OQ9tzC5ew5tMS5CfZH4nnkCQ/17CF8F9KrAXyvRfUUqoYfLYb4MbXak\nsyN6QqQG26E+D6QQzF+/QfW8SxgU9DAXg9aTwRCkOY/CrEeg6TAYXoWlE8G0Cjob4HezoO4MuC9A\n3Aw4vgUqC6ClBhbeCwbTv1Zm/y/8AGUsSdJaYAIQIUlSFfAI0E6Ph4UN2CRJUp4QYvrfvJYQ4od0\nJAxYR0+8pgpgoRCi80/qxNPj9hEFqMDbQohX/so1xQ/p0/dl7dq1LF68+PtV7joLQSnQsgsaN4G7\nBsXUjZLWH033QOSaLqRBv8RfcwTtzg1Ixm6EvoaqSdHElNQhayvQlphQi0106Itou6U/Sc+cR9N/\nLKq3FM2ZPEQvoAXQRNISr+FA7nDCDFpGPLAR4xUBVI0eTBoUfwB3pIng9k40hSpUBnH05j7ojAEi\n5amoe3eRFJxAwBtJl01Px6AwvK5tcMyHrUVCMbVilRyY2kfQ3pBPd78E4vq9Q8ua+YRVyjh/vojb\nq3qzunkptJvA6qYsIZ74VT4cYWMJX3ozUt57uOVv0EV0IN7Uo3/pIaTwkYj6wygX3kZT0QrxflyX\n3IcueAv6oFd7FpH4K6E+HR6dC8PsqHIa55NlNNUt9J5fg6sjgkMZ96G/sIEwpZ1WyUSqoZrTlVdy\n+dQn4MMJsMVBW1Y5RydMYHLCJiRrOBrX60ix87jw+WRWOOfxSv3tBML64RrVhBB+QvbbocAJ6UPh\n9OeQkooQCmpsEpo4CfreB4eWwaDX4L3fwuQCOizZNNXUEtwmiDO4ERfDaDYb8LZKxJ8+j5QoEGbw\njNTjdQYhR6m4Qk2YLDcRFDiNLvYefMdeIKCvJyjlJjj6HE5NNLdlLSDf3pefF61h3MULpA/KheYi\nGPs4WBPx7rgUaeVu9AUBuPEhmLcM4nvB3vfh0AMoHgXv/lb8jmgsXxxFTkigzOek7OFfMK54D7qB\nQaiWJpoWaIn6ygEGLWR7oF5C2iYQE61Q50HyGSB2LtKw6+js3k9Ir6W0hJTg4By96qaBIQgiYntc\nBd3vg3cLWH8Hih30f13h/l2y9QOQJAkhxA+aOZQkSXDke+qbET+8vb/GD53AewDYIYRIB3YBv/oz\ndQLA3UKILGAkcIskSRk/sN0fF+sA0JohejZkr4ThX6NJeBp9uRmNMRep6RQc3oDurmlIOz8AcRbs\nA4ivy0FXLuPTVSCKTyCZ9hBibiTy61NIGheyowjFnYd7ignR0BcPNgL9phBpjmde+BtkmkfQNC6R\nZkd/3AMDCG0YpIYSCPYhHbFCqAm/PkBMpUrmb2qxnTlGZya0yGdR/B9glNcQv+V3pK2uo9epavw0\n0TgwkvxLkqlLPkVIi5Yg02jyO26nfaSdbW/ejCF7GkM8ldAnBUnxoig6EvOGoHO0U5HTQHXZUzhG\njUVOWYS+LA3dtFD8v1sHXe8ipccjBRvxTrZBQELb8Cpa7WLQDYfQK8BfA+HHwBYEJX7U/lMIG1HP\nyf6Debvh13SW2mhxHWdAwMNAbz/Gnj9Dr+jN+BvT4KMVsL0EBiQQbotnuGY2u2tuwv18H9S8FYgn\nB1LhjaHdEEp3wnS6Jk3CH+KiLSELugshZzCMvBqWn4GHLuBdOh7VW4SwaWH3i2DtA4k58MBLkLSA\noAC094ohSmqHzNuR9BrqxqZgi2rFPT4cX2YOwgnyRQOWODeG/g52ZUwk5OhedKWnYPdUNKIXYkIf\nyLgaLvuK4DArqzqKOO7sYnFwX7q9driYBwfXwsdXQ/1ZDNHz0E8W8IwB5oT0KGKApFSE2YB/rwP/\nOTD96kpkUxHNBz9gvquWk6MG4Y0ZA4/k03jzRGwH2pFP6pBFDvJZA3JYXzDEIJ3uwJesoMYHoWQf\nQE10EJKwELY/jI3xaLFQa/oM18fXQcDf47sddC1YX4Cu+yCwBnwH/7Xy+I9G+Z7bP5kfqoznAKu/\n218NXPanFYQQDUKIvO/2u4FCIO4HtvuvRdJA+CjIfAYsfUAJQO+BsLYJHngeOoqQUi9Dk3Ed6thn\n8BnDUSxxqM5BuAdmETToVlouC0fknUHSWWkZEoS/3Il30DLqbV0Ux4VzwnQCS6CbmDESYaNPIooU\n/Ksc6A6bCDvqQBatqL0klMRRRO4rwVTegWnfIXqvKUPTHuBCbhKtZhvuKQJfPw/6fD/RJ2Lp86Wf\njDVerPsCKJ212LpOkh24nnBrDl7PdjoOPcW4mq8JfJVC6/B+yAE/+slXEYiJIWewFd38p6io24/p\no9dpr3ciSVokKQjl/FDExXXIwe0ItQqfMYCI9CId3P2H+xbIgVefQmQNgUV349I4iHuzhDSpmpVd\ns5Gb3AQ0o4g6Z0HbXk/QmQlI317LpLbHISoGbh8CuYB5KOF5+8gyj+TdRxfQetQC/hJGtRRy6/g3\naJjQRoflDD6rnuiQt2DYfZAxEvashoT+uL1+GuU8atv1uL9pAVWCaVt7pKF8NZR+AtJpendVIiKN\nUPMYDLxInKsRv3kmO3PuRJztRL5pGQZPJvIpH0Ix4rNeAgunwOBkRKjAnb0N05nRoKoQkgKXftbj\nbXPmcYK7jlNsnwXz3oLlHXDTQYgbDPGxMDgHEi+HhuWw42F4fQrKuvtxv1dD64jecG0iOu9H8NkM\nDB/dSnprDQMKPZhue5AW/61YlWa0IWnQLOCK1YhpXyAuNiNZAzAhm0C6FgUPXSUGJN00iOgNlmio\nOEgM86iyfk31HBU+ePAPz04TD6Efg+8wtE4E36EfWej+iQS+5/ZP5ocq40ghRCP0KF0g8q9VliQp\nGRgIHP2B7f5rcTmhqgRxahtuZTnY+oHcBToDHHgTTjQReHgiO2qepKz2LfQXQlGtCp60EOThz6BN\nuo9IdwJujZGGqDDC8gJoZ9bizDVgG/4kaZ7e9C4pxalupNmtg3q42D2aC33sKF80IxUIRIiECO9G\nte+DEAvi8XtgZCbCI2P9po6Y83EoZvB1DETyqLjuNeC62oHeVYG5phiL3IZmgERAPYwovJrQuvdJ\n23sB84Zt9OouZMc9A2kdbEP2CfwFRajaLKTst4ireJrMYblUJc1h5wMvcWzhk0hpYfiuvxM1cA8k\nTEUJDUYZL9B6vCi2rYjzJ2D/x/DJRkSvoVTMcdMyNp6ggpWIIBjAET43zuX+4StJLygCbzEYB8AN\nOvA10OJMAd4HpYRA0HHUxc9ASzVxpw6QW9bNtluDcQ5WceWUE9VeSiTT6cXVyKoF1C5Q0qHuEJ0x\ng3n3t3tY8eAmTKskEgt9BDVfhPT5oDWBMR714DkK1JE0WkLZVzSRU1/3RuwOgjoFS2k3zw6IZfyH\na/EaWnENeAQm/AI1eTK69RLm8hMQ9gD02YV/+Fy0EQbkPoXgPN/zud/dAYnTIf0qcDczKvAaBIWD\nMeQPY8tTDIYM0MyAkzmIL17G+3oevt8dxDRHj9bdTPuoLKTU58E/lDevfZBbKmqYdPJtXN2TkIs3\nEHxjFVLdWJCDQTbCxtuhNBEmPY2aKKE3WXCNjkTbXIuz9XBPuxN+BXufQauaSJRTcYY78Yfo4eAX\nf+ibJIH1JQhdA54vQP0ji+R/Lp7vuf2T+ZsTeJIkbafH3vv7Inrmph/6M9X/ovFFkiQzsB6447s3\n5H9/1E5wfwVqKxhyQTgh4ICtW2Dlx3jnONGeSQFXKLQEYGIf6DucTm0d20cNZmhFO0kfvoogCDU2\nGMclDYQ1G+DcKOTmM2gjtSS8XorHbqPwhVnkuyuwGu8ksd9N9P96BdK6MyiTjEhlkNl0iqaRZtoH\naQm2j8UUfQpvWwgkNlM+yIS+82Ps2kY8t9jRauOw1lQSXlqN5CtDMmQiLl4AWzti/lzkAwUQ8KGp\nquhZrVcio8dPmihBo4C20c2wlWuoWZqMopHxH1qJcc4jYIhDyXoNd/OvSMw4S2LUONBFIG4ZjfC8\nhrp3P/IDr2Ms241s6IYTKiLLivrxWET6QpqXLuKieyMipIX+Z19C2+TAPdGCPt9DwoQKRkXvY9D6\n1xCDf4F0eg3E1sGcvYibnoHkaSiBe/GpYKr4GtVci8N3jKDmOPoEYvlq0EwmbjyH5HLhmrmaYGcc\nmq426gbciEFqY0/FbQyxbGL0ZdPIOPcO3fP7Ir+eCec2w6tvg/MIRBxHDKnEGdaLfUcXMLpsH+GX\nteOLGY1OG0DrUxi59hvev3MqC+U9XNDfRN+xQwnecwBti5YJX38DlhsQcUn4bF6COdxjJz+yAQ4+\nAd3t0FgBy56FOVsJ7MiF9s8gfFHPePO54FQNnN8B5iKUEgveb1R0xi4MY+1wpAVfvzASDpVB4EY6\nQmMotI3kl7vG43skFqc5jujVWUiu/XDwBKjtiNXT4NuLMGw2YlQvhKsCSZqLou5GF2ege9t8DPPP\noTNEQPYVcGIVpmFGBlaNwD/1UnjtZUjuD3GpPX2UrWBa0LP9t/Cf4tomhJjyl45JktQoSVKUEKJR\nkqRo4M+GdZIkSUuPIv5QCLHhb7V5+eWX/36/b9++ZGZm/q1T/m4OHvzrdi+dxkVG/GZiw84AUNV8\ngoBqIKAY0Oi8xM+3YRjnp/4jE6JJIXH/q2hff5HuIAsdsVYGnS0jJOUADtmP1hgAcyemrlYcR5Zg\nwImvUIfriERksMT57N5ckPX0bj5P3O46nFsfZc+AaEaOVmgbFom4kITdWUzVwGGM3L6L4/38EJvB\n0B0nqbDkkFJ3lhZ7OEa9wIUDX5QDua8Ht6zFvFtB8RbjG6LB8G0A/5KvYKSG7p3RREgKcpXAGRSG\nobsLj9lEbUI0pk4P74g7uOGdT5GtCrqUKto2PE6e8yL2kXvRnNKhlg8kfPtULrqmUO8bgGZAFtnB\nHxNUuAmNJxy/YsYWWkPw8+0cvmICaY6NiMat1AfNIul0KMGunTgjDBAQqKkCV4MBk/kCF0UqH7Vl\nc3vcfuLP1LKzYAvVYR6sF9/CcJcWY6UP78GbkIIEB8LH0FwbxrTHNmPSxrDx3jFEfFHHkIrTtGY2\n4jktUO7Op3tHFJltn5PQdoGvi/YS1nAUtdWvSQPqAAAgAElEQVSLp9SI1xaLHNRF5bcqvdo8HJiX\nS/cBM32Dz7DmsblkXSymt8WN4rOid7tJzKyhQHWz1TYBm6uIunObSEkQdH0RSvvPTHS/fj3+0TIX\nNJfS2frpd6MpGhJnk9x5AJOIwLt9Mw3nqkiNLKfp/CPsqPYjSwoogtyz39IUP4iwrUWYCosJtTrx\nG80c7TuPwfmfcKzfHRjboP/Zz1mRu4jFXU9Tp0vEJVuo2zKB3b2HoI0czvjtD4A3gGXTObSLDOzr\nyITtWwk5N5uShPHkTDuKPbQNTaVM5VfjOOZ/AJAZW7mSthALh89MJen8cxRHXsKIRxdxYMxtKNrv\nl9L+b8nW/5WCggIKCwv/8Rf+T1HGf4ONwDXAM8DPgL+kaFcBBUKIl7/PRT///MdxKP/bM77XA6AK\nF0qmg0oqaaAaP34s7k4StimkJ4SgRmvwu8ahy9tMqOTDZNCCpxKDS0KbAFKlE49TR1dab2xnGvAd\nFQTipxEVfwo1vBNzZgaT9l7EEKJBmvEajsu3MPBoBEr+Dhzh42i/spGgbe2MOlGCpKpkNmpx1rYQ\n0BpIKjxDICWM2PJmiEgiTG3EIHmgRUFqNkKjBk1oLLoLfaEuD91bSfifuAXzLzYhrTUg5Vdgfqwc\n6vLQXHgBnaOAyAQdta5LSA49iFoN2mQJmy6X8YU7qZ3bRLirFatlPmLaAeIqnwDtW2A1guluqnVW\n5M6r8FdK1A9PJ+HMeUbGaAg0zkbzqw+Yveo89D8NRf3QJp5H71ApGTCXxvJWBlWcJT4znIkDvsTY\n9mukw7cwOekYzUMqCQ2U4c4zYNL6kHVjkDWVZMtFPHnp3VxWa6XX6i/YUW2l44UIovdAUsQZtNGC\nmAky+k8ycVzRjVtMYeTeDawYdB0PBB8kWC6C5N4E0i5Ht28/n85+nWsfmUXV0IHsmDaSPu1lzDm5\nj5Bra3qGw6cvI46007WzmfffX0hmm4f+oecgyUTJY/chHAcYdftI/M7DpIbMAaFCxADAB+23gGkO\nmGb9fnRt/aKTrBQPVw63waproNiPv81J8JelKBNS4Z0NaJrNaL79gjEH3oUxy5h35U2gbaPZ56FF\n9GP4/etpfVQm9mAW6dNvwVF0DE48TWNxgPgILYYnhkJBXyYfWgd7uiD3UoZe8TP8DMVnnU5onBlf\n7BByu9cTM+pjaMrEfvpnZF/xAByfS7+c62H0IBZtexeueQos4f8g2frh/J2hH/4y/yXK+BngU0mS\nlgGVwEIASZJi6HFhmylJ0mjgKiBfkqTT9Jgyfi2E2PoD2/6noKBwlCOEE04TTTTSgJAEdiJJIokB\nZKPDgcu0FfPM8wihUOo/SWfxe2SfD0Y7KgKD0EEJ0GBEifciEvrgNXchP9+JmtQL0yVWOt63UJlv\nJnSwi3TZhjThOZQgIxXcjJNaqj/oIDPLT3reu6ghM3AqDnbe+AhaNY+wQhMD93Thz52Md2QTndWt\nuOZfS/iuLQTV1yM8CrIpGuZdAv4J4IpB2vwq3PUbOLAf3Z4GNNlT8Yw/iF5KQbtzFSTF4Ok7CPuB\nvTiHxuPd2QIDZyI1bkYKeEGzG2QbwTs1WA46EbN3IFrSUCMi0IglcP4liPyWmNhIpNbeRLaWoO6q\noPWqYOxnyvAknsUwcxL6DxsJjPUjyEOvyNCtJ2XXBvbmLmTJmv10ZwcTHJFK8ObHIG0m5BVzoS6X\njCVthO7oRhMHvH0MsmzUXTKLqLpW3If3YV9gQRtYSEZhBYGQExi7QwjrHonG8iFS9Q5sVUPpSuzA\nnVXHm+5lTG6PYHzdFpTQUO6cv5iqznDeemQhBZOG4Td7uXHTBcSew6gZDhiZDxn94cBmGmZDL+UM\nc452UhOThMHvwZmvoW/Ra6j2BKTO+zGEzgDnbVC3F5JnQ44d/OsgbMUfjTV3eyjK7pdQClbg1/hw\nzA/FY7JS9FQiUbpwsu2jIUoDZ76Be99AbF4DN6ahToEV83aybOW3dKZ2orFm0N2VSPmlY7CMMBAz\n/3ZSSl5CmpgGoeVIyw7B9BK4eyjs2wlH09F9eB6NOhtCC4hVPqReTMGxNhXDVechoEBDPiBD3nUw\n8D34+lX4VS68mvcfm9HjL+L/V3eghx80gSeEaBNCTBZCpAshpgohOr4rrxdCzPxu/6AQQiOEGCiE\nGCSEGPzvqoibaOQ93mUbWymmiFRSuZwFLOQKcplICr0xYMDDSxi5CzQ6TmiPst60HkvYRLQdRsi5\nH9ypkL0Ulz8e3zGBbCrGGt1JZJAbfVsJkm0hUXMN+JtKaD2sw1OQiP/p53HtfJEwJmPvGI7nQDGK\nVwNBwchnDmAucTDlue0M1E4jsfYT/BYT2qEb0NisYAbz0U8pnh1KY0YysnMUDH4Yer0HaT9D0Xlh\ncDoc/RK062Hd/ciHVmMyfowyNQV3/Toc59bjD49Ej5OCuDhEyiBUZKQgE7QLiLDT/dDVmPf5QSsQ\n6Q24De0EakAq+gRJikCqXYOu6WW0lU40+73ICRKhzWG0pHSic2vRZ7vRGC5g2KeiK5CRFCMiSEdb\neCiSXouISsdQKBNxqB39JyVwyg51AQb0+RDTCQn5Wx/iHSDGj1rYTM7rq7A5AtjjqtG21TKu/F6G\nr3mIsRePEV53F9rhi1HsNjAI2F+KVXkBfVkCB6LGEdlYCVHJ/C7nBj50qgzPqmLjHbMIzlcYfuw0\nmqk3oHllB01TeyPyjoDXCxfPEeM0Eiv0BF7x05QwD/+pLCxva9mSNJi6aUEExi5B6j8bclfC0iqY\nsQG6k8H0JkhGaKmGTS/Bc/PJqvuC6pmCmuWJ1N40hGprOkp6bwYErmDgugpkNFBwGDqbESPnICaU\noL5yCU2WpTQcv0C/1Svxy3Y6F2jwdQQRc/w6YpOb4cxq2u6KpHvocTztzTQVD8W/8zGYci2sOg0r\n9oBWi5x0O7TUQ+8P0Efo8OOhsfAJ3JOHwM7HgDRw5Pco33n39Li6nd//rxXSfwb/Ja5t/1VEEsX1\n/JxHeIwZzCKGWOQ/uUUqdQia0JJNOy2008yS1nmkvvgAeDqhpQ5u/owGxwAqLxbROd+KaJWR97kQ\nrg4UjxPlneUENrQQlWXEPC6EurVr6I5JwjjxJgLU4Qs5hCEjHW1QMmqzH9GnExEDSAWE7HmdztRY\nvro2l1apBkNjJhExzXT1hzqNhePaBXDHFih4DL78Jfi9VK54j4b9hTDtcci5C8aYoLYGqWUvhup4\nPFd9jKfoCOcdDXQpIQxZuI9bCpbgl+Jh+CIIGKC9C86/gzFQB9pQ5JdS0TUuoDElGnf6jWCfCQ49\nfJUCa8Jg8Xoki5f2sLn4yvW4ey8EMQaR3Rv/9ATk0AQk20hkexxvTrsGs+rFr7ZibmtDIwUQIywQ\ncgElSRAYImOMasczzog/Q4tolpAUP7I7gCU0mdLoWVTc9jafmZcRfJlA45yN9tU1SJoP0KQPQHjT\nYcF2qD1FdEsoSbV1xHS8Tfu0K4iIDuaR2M+YGf4B1zs30XvZYbp+C+72RQj3tRSOn4K43AKf/Ryi\nWiE+CnHZMTouH8rQsbewZsadMG4GUmQ0fso4Eh2Cv3kNWBLBkgDtlbD/IKx8Dp6bDxufh5QhiLvX\nUDW9L4ocSVtAS5tNYM7KJGAMgoRQHJoOlLYKWPckLHsaAOmkD03wO7w88T4WFZfT1d1J3GYnWTde\nT9KNj2D/OgS9JxvDteuwTTyDOX0axvMRRMZtR3ehAsxWWPUAvHk7PDwRik72JD4IX0xE5jr0V+XT\nbQwg9BZoLYULZWC/pGfgp2TDSyd+VHn80fg3cW37aTn0n0FCQvtnbo3Aj4fnMfJLAMKwMdU5BZ4a\nDPpoSJFg3M8RGg0RyacJc0moyzronmrDPMSHZOhCrtZDpxfD/UswEIuU9kuqpG9wPL+TsKU7iB/b\nn9BWI2WG43QkxuJblY5tgRbd0lxY8zJyt4kgW3/QNKP/9loIr0Grd6C32End3Imz80SP37O7AUq/\nhLNb6NXazpm6HIK7rFgG/Baaz4NDguLnIGI0YboESB3MhA/2URVrougXfcgcVInXNgjDvvchqC/+\n/hOw7nwLqdAGX1TAug/QrzpAfH899aPfIsR2E5a1EZB3EtY+SaC3D7nETVvLfpK2dOG6YgfuYS+i\n1zyMrFZA0VTQx0JzC65gOyOLigjuboAwPZq2XnQaO4i4ej0OdSmaQ6XINSnoI20oUSeQ8roh3AaD\nFhJ94Aiy1c2+0GYmDj+ApjYaKXQW7LsOnL9BjngXvPUQM7DHZ7dxB4bBEpruDor67SJZ7maMbwKG\nQAvuKA1+nRGNx45WdOPvymdkaSFe/VvoD3vRTEhCDHga9zdL8IQlIIpbSa/q4mifeCalR1LDdIZf\nDNC9bQvVaR+SyZVo1k5FTa9Etg9Dmv4pyDIKXqoCm8BcgalIS0riL/FbVWT3ewhpMG5LX3zRwWgf\nHUbNkiG4zsxGV6MjOt6L+/giaj3XMiCuA3P+WPCHIj7bhnj4M6QR45F+/jxc2I6wxyNMEnJAgtOf\nQZwGap+C2GEQngJRkyBxONTGQscuCJuCGTPxqZm4+RL1qtXIn94Iaf8jZroxCPqN+1Fk8EflR3Bb\n+z78pIz/Djw8ToBTmEjsKVAVWPULuPVzOHEe3GtBaUaqvwXtiCXwWCbi0Hp8uW66I6KxWq8C4zh4\n/36U/DuRQ/pgKX+BoXVm/O3dNMQ0UfhxPlGjhmP1+DFKP8e/tpNW2U1k4RlkYyzCfjW2nZ8wXypC\naBUwCQKVvfEkycSkh9O2uxaOHYSYwdAdjKiooy3Jj3lUb4rvu5fs5dPQpV4DvRbCkduh/gPYfxdE\n98XvOEHirmpMl5nQGNM5wQ5SKcAfK4jqOoSqi8KckY1Uuh0Mh6CzCM3GBuImbaXB8Tv812cRnrYR\nEpJp9X+NXQSI9nUSpKpo94fS2nspkVX9kCLqUVNakOp3IA95jen1m7DvO4Nik0CrEJHSQmtnGPqa\nZ9DpvkGVdJB+OZo9X8KIJ+ge+SLGy5LRrqsmytdC8xAts0vX8Eb3Aib7t0BvDUyZALwJ5pvA8Btw\nO6CrFAZdgdRUQ1tjGZruXuRodqJ4L6IqbZhOuNElGvDKHrxRfTEEPUj7q/cTOe8iyuU6FNEEB/oR\n6G1hUns5kV88Q/v2d7n/zltZWh/GjI3rwGLDcOtR/HIxrZ+PRR6/lAhbP9TAHQjfw1S2C1q95+m1\nowNreRKxC+ZByafoY3fiDw9Cy9tYH5wP5wsQfjBffxIlWEPdtlS8zWH42yq5e7iNCMNDyOgRncdQ\nwx8kMKUVSSpFPmNDKjyDqFyJkJyILjfS3schbTBEToVQC+Su/sOgjhsFp2+HcXtBG4mEgoQBOaI/\nLP60xzSh+X5eFP+x/JtM4P1kpvg78LMbPTOQ+G5t/qcPwYgrIHkg5J+C5A5oXA5xK5BC5yElZiNH\neLDGxNAd3YUrNgPCE1DnT0BNi0La7YXdRWBpQDc+iNgUN3azlbbPPsfbpEGcPYJN+xARmrvpjA7F\nk+XDF/YicpYdacRkxOgHkMomo2+JQufV444vQ5cGHD8Cr1XhTajnwbnX89xNz5F4ag/ps62cv+9Z\nRMLcnv5nPwCXVoLnJETUYLzmczpmDcNeVIteq2Ni8wDs1bXsGzyJTblJrFswljN1xyh5YzW+ccvh\n9SMw6kakqy4nWr0HMWkqDQmfIFAp9xTgM4UjKV6kcUMwxJuxHzPR1SsUTWE3UmsEGG2on9/G4M+2\ncPwLBx7tJBSzjLHlAvL0Fjy+FzC1BDB1t0LFo9BehMZ2GqMwUpHmRkyOJOpUCXWWBELSFzNPDoEo\nC1RtgRtMUOgDtwy9BsPO+yD7MvA0QXRvXBnTecc4kzf2rOCgZRXO3ACBm55AmT8AvTYbofoIdC0i\n6ukXaKv4NeTLyOVZePubUYLbie0uwep8iYRBg7jiSCmnS3fgMPTHN+5m8HYS+cly7KPfoq3fYA5G\nl+DzLsNZV01M+wsM2b4H2yVvEFebB4tvgDe3I0qugXID0trliNRReILicfn1xGR0kHr9ZQyvvZVB\nzznoGz+DML7hHL/jYtm1qC9NQCm/gOwYQyDpbjrGZ9B6rcAxopyKhXYaF1kQSjIs/Armfg1aAb6O\nPwzqphI4VwSiZxZLz1CC+c4TIiQODOYfR7j+lfybmCl+UsbfE0EAPXMxck9Pwf4PwWiBoZdB6yoo\n+QLC/JC4FnSxPXX0ZgiNQ2o6QrT8Ci2sxKceR4ncjPbbXtBvMtTuQwy4FceBKDo/dRIxvobol1/H\nlGmn9PNdVLwzFqF2Eqq7DH1WK1pfC63JoKasQPaPQpwuhtwXsbV3o2rdNFn7wm3LOb/hIkU5Njqj\no7jPMoWCvASCE/KJuWoJF5c/2tM/UywYQmDqfohehHR2Hs7RKUgaN0FvbuXsqffZNHA6WaqJzKoz\npOY10HhXFtWVBzj55hwU1QujJ0BULdLKlzF9oMHkzKSWe+l37gBahw1X2ACk4CYkcRj9qUzMykxw\ntSCXK8hnq3AX1XLq4waGLm/DrLtAQ2o8ugaBxhqEoWIW7YeeYnPbU1A/HBKSweFFq4XErzpRzn2A\n7V43DYPCcBYfIz1EQMRQ0FWBZiOUNENXCyREQ9WunslVXRxkvkGvpJu48+RdnD6ex/jev0HIEn53\nK4a1xcgXd6PrPoWIH4pyYRY1SwpAjaT1cAP6fbn420yUJmSi+OqQD3zO7G4zI57aTteu4wROb4GN\nN8GcN5HjhpHOdPqpSzloKae+8zD5xnfhiltQgyZhfaAakSwhklXE1u1o3rfjD/SiYf1J/OOzCXow\nDMmkRVvxEeanliINMmKtuEDyoXcZcKSSlG9241c0SPY6fN5V1LqepaWlDEPdzXTeZiZeGoElvBrP\n9cUo6umeZ55+M1x44w8De9wKCI7k/4/0qKM/Rmb+8wXq34mflPF/GjIG7urZvXgECnbDnF9B6zuw\n9zoQViAF5P/xSZc4DHKuBXc4cvEqoo/V4XFOQS5NQqILKEO06vD/ahba7NGE3z0A7S35eFuasYwZ\nTeLXe7BbFLzld6Lo8lE6QlGMYNXEEehYjlJzG8rlRpTAPRhCO7FJEqpfzyfaai7ufJOubgv3ndpH\n67Mvk3zfnciaEKLGRyEJQf369X/891rOQf+16JvOILsDHPIP4On0CeSkHWGIV0uvKB2jjnXQq08F\ntvVWbMEeCm5YgGjYBEYHSqgLz/pXCKk4RXhxI3UDCmlJ02IOWwKmIJhbDkEyhm/vRuoM4MkJo8Eo\nOLUThi2Nw5Q0CVntgyX7W9oyphFc3YU1+wGcIwtJK9sGoVHg9UCrAVyt6CwhdC26DPoGUW7JZs2g\nG2HEXeAJh/gIRMJHBLyTwdsFjZ+AJwsc1WCJ7/m/+nC66m08vHw8+rZqNC4/BqML6efn0fZ7G+NL\nIRi2tRIIMZPS8hWdC7sIvcODZtxSguw++n6Th1w9EGn2g2iCixk3NQxTx2a0p3+HmPoYWGN/f2vD\ntr5K1icHwa2hJewDWva+j39LOJJBRX0mBDEjG//jF+nKCcW5/yD6gdFYSlqQGr1w4y0gpUB2fyhz\nQKUfkieB1guWbMhcgjvnbjSj36G3u54+5ySMj29ErwoMhYsJXrsM04VXkKWBPZ2JHNuT9SPg7vlt\nTYQJb/esrIMeEwX/Xdmf/yb+77n9k/nJZvw9kZB74gsU7IatL8Ftn4DSCkoblN4O+Z8CGf/bB7N0\nA4w/hjCGIvmWodW8REPqMWKTQ/Bs/BqtS4d29iL0xk9h7MtgskP1DjSjZhAWORAWnIBHr4LgEvwT\nQ/B8nYo1tAlafBA6HTSdiICEZuImtkqdlBpXMW/dlSj7mzHZ9Vj6BlHvsxLatxed76YScux++hxJ\n58zeHZjdlVimzAZZC1tW4jN8jW+ch1eyb8Vut3JH0oMEdanQWIMrsZXmn0cS3pqM8dh+grKa6FSr\nUTQBNCOm0rG/nu2/vZ2a6GCazbnc6Q6mK2U/EQcWgdEOW8aBUgCtoF56BbXuamrfvciwmzMxrm8D\n4xiQDhAmp+Lt/yGGXXfAkWzirToCIV6IHgXBPjjZ2BMbJKqesPZT1PWeQpJaSaycjWi9gFR2FHKv\nx10agst/DtvpJ0EfCvOXQ1cV6MzQ2UbV+ifpUMMYc2wizHwF2edDCbKh0epgyNWw6hI0z11P/ZCr\nUaRXiY+qojPEiHHFZXhcJhqGZtN731nUXuVgeQJZuRvpwAkqPw2QOvUzyHsKguN6TEG7niHGayIq\n1Uj0juFUypmsjtXgrIpiVlcTfTKqMXQcwtD7dgKfTuE1inmwIwY+mgYfb6T76kswe8yIK6/FX7Ab\nvX0BbMtA45uN3NWN4erXkKofBPMI5Kkv4eAe9C+cg6VzYesJCI/54wUSvZdB6SpIv+W737N/LDH6\n9+RHcFv7PvykjP8eivbBbyfC3V99F9PVBJH3Qey7cHc6GP9kGainAxBgCkPxPYd8UiLogy14x3fQ\nODGZsDw7urvegn2/hWEv9KSPB7Te8+haK6FsCDQcg5ByHGkXaDiSTFy/qai7XkXW9IE9r8E1mUjj\njrNP6uZ5ihgt9cXb62sMBQqxJ4qpP1+JeGE73d4Wtt3/COP2pxHV5wxZjuHUvvIQus/vwWjQQZqR\nlxe/wRuRCaywv01K2yHMUiyqWkBp7g4sVT5iy2zo+x+Axma4Mh9Tyh68Xz7BxtHTMZXW8kH/KCaV\nV/PUWzciaXVEDB+Aoteho74nJoPJjKvYT2NTFTV3nWDEL6dgiDJAygQoa4QTxWB8Ed3wBIScgNTn\ncuT9aygdP5i08Bg0khkGNyHaPFBfjVIZQpDnOFfpDnE4oR9ScT2U74SI03RsyMV3bDW2JRpID4O2\n83DuVdDo4eRztJVEkTz3XnBsgYp9aOwyUs1W8JVCSzlUnIf6Vvp8sI2TNw7H/LWKKbiFxpuCqD2e\nhDgTh318BWXlh3AdWcwOaShpD1/K2A27EcMfRZK04GqHtVeDqoHYIGSdIMT0KelDo0hzuFDf9dO5\n7NeU2xuoC+RSNiyeYjUPs6IgrCacNheGBSlUzDVh+eZbWiJ0WCdmkHrwQ8j5Bbz1DkqLC6pvQ6sm\nw/ksxOs5+BK8GMOCIdQIb7wGy5/943EZdynsnQupPwf5PzQ7xz+SfxNvip/MFH8Pu9+Bxc/B4D+x\nqQ0ZAXNHQfdhaN/4h/ILn0HaQlRlG1CHZuS7kDsd89Z2Okx7aJvghuMPw/TbITLn96eFP1yJZnAw\nbJgFm3+FOriKrgo9+m9aMH72Il2ShDeqGTEkATHvCzw6A7W4+MiXQ0q1n8bESOyTknEZQwgeEErM\nmsUYXh2D5cBK1ib3AfUgOnEefe4C9m6S8Nh1nLQN5DOtnevU94lxHcEd0oqvpgR3SIDoml7Yd1nQ\nZm6mRG/keMY1fFK+nVfShuLWxzP8wS0cu28uj4uXuCm8FKlvOAQLNIcOIVwXUUyhMPUdxNArOdel\nUvbrowxc8y6GEAPUbAL5DOSfg1ljIDmDwLGnUU58CUocpI2iJnQ0Pk8+yFEQHIdiNdMdlYBn+nys\n6bsJPahhyGuPwXvvgMMEtUDpWjShAegXDpq7Ie/Lnk/yfj+jrM8rBEsO4p1fwtBbQDIhmQfiHzII\nGnrh2HsSp19FvXUNbn88Rm8sUlguGqOB8H3dRA9uYFBoEyH97KQGJWEbcQ8n429jV90b7D4yF9Hl\nBI8DPr0OJj0M+hyY/iKE3wodCi2RA/GlLMfXGkxs5S4Gerczw3OABftn09F4HtF9ivzj82jp6KZ4\nlpmYUzE4E/0MqBpHqiMXKvbD4OUoQcGIeC1aEQ+hqdB3EuqCpZjtGvRL7oAxerjhJlD+5NVPkiBp\nEVR88k8Tlf8o/k1sxj+9GX9fVAXmPQLRff73sYwsUP0QaAVj+u+LRfVmxLTHUQKPoNV/0iMEi65B\nN3cxKbp6GjJyUcsbkN/eBK7PYFA/sNdCVx70a4Yr22E3dOZ5qSq0MVR3EVnEYVAj0F4sxp0cguOL\nW7F3p3Gloxpq27hm6DG021W6XEkwWmDUxNE6ewkrXHVkllahsSegTliB7KkmeulluLqd1Gm0xA2z\ncNTsp8x9FOFwYKzrxJjsI9h1J0I9z/mFEVx0+/AFMhmS/QsWfbOcg944qso1hMxcyJOHtoI5gHA+\nh7DNwtedhtx/MsZ9S3CPLENq/Bl1+/RUfOQj8fkomk2/wdrgRMRm4TVtRTtag7bcinT1xyjZ76Kc\nOY321S0wLopM5wHaoyPQd+/HOciGyDcR3O9LtOZU0FoRM9YS4v8ZXZqFBF9Yh6ahiiCPH8kUCRn7\n4LknoE87SDLk5OB94xpspqEw5Q04+TwoLegPVKGvcIPZhDl3Fh9PGUFb9SEutYYQVBuAuIVIwVdj\n2nglCab7Ed5XoHAy5kG3kZ7Rl42De565R7oMzyt3E2Q6BgvehvxiGDYL2qsgbRoceojIranohs3i\nQvBXhFXYkcqnQvp+LE2zSR0Vyf2f3I1+eyMdz79B/Edr0X7+DqasMsqf3ULqN58gTXkM0dFIILIN\n/TAdaJ6EoN2QkYNf3YkoMKEbPRFOPQL2qD+fvTlpAey7HGIv6TEj/b/Mf8Ny6P+nkDV/XhH//rgO\nou8A03fKuL0ENcmL3z8Vje5JJOl/fA7q9eiri0gomwiXPgNzdTD4GHS/2ZP8cbsf3jTDB+BtDONU\nWjbDOmvRxCYj7E0Y6wuRy71o1SCaB7po961DWCyQMQh3UgRED6QrrwZnbxPayrM4PZ+S1Tcb3eSl\n6FUVee8OqNuEXH8ffUb4iLv/BaJxI8UMwxNiIGmnnqD+JmRrMtqq1yhIGs+X1j70Kaxg3rc72W8K\n4zAekra8Tcx7R2jt66DLLKB5NyJyGS13VtH1/Lfo4lxIo+diOhdCrUdLg05myrHXGJKbS2ReLE6/\nAzm/GI1b0H1NK4p0EZ5LBkkHo2bCurHO+YIAACAASURBVDMI6qhgGEGpDbRMDca0PpaQQ5PRNrjh\n7O0ASLZQTF0Bgis/Q93pxL09Co8+EUltQs17H/rbIKo/tHXifm0O/uB47LoyPPmPodhGQL4e2urB\neAoSrDDrXaauyMds9FB1fQ5BJ4+hHH+B6s2vo1bq4KtHkPOrwToGov44aY1h8mR0jV8hwnpBaCLs\n3wQTl0BHFU5rEX5rFvr9JUhfPE2YtwI6G2HApZA2g10L5jKKnRhO6ZHvXEN4VzbaV3bCJZkEZcUR\nqD5Bc5ITf2Q03sML0IwJQQrSQ+izoM9BCIEqClG6LMixsTDhgb8SR0ICxQOHlv5fJeK/h5+WQ/8X\nEvPAH/YLP0RJakCjWYzU9d03TsANTaeg8EOo2Epg/0b8L9yJd9dFPIb78LWMQLl3N76ENLisBDXY\nxt5hYxh9oA5NcgY+xQFmBcL9eBKiKU8Npu97p1DWuvH59sOISBoDyVTunoDc2B/brAsE5jyLfn8z\nU3e+jL3oZULiLnBgrp1SQxvtzVtx5yzEENuTeKXDu5OE90/jvTqSEN0MhKmB7UFTEAdruPe+SlId\nMRxJD+UV2siO6EdMwSGsr6wkaOoIjmf5+XzWNWwN6aT4/hTEK6NQTj6Dr3QDDttg5P4Wku+IQBP7\nS/wJtZjGPoy2DYRTQn/ajHllKJq6JBAGtFu3otm3H0/Tg/hCquk9+EtMruuRVmrR1O2BnFPg3wBb\nVsPmaNh2P6w2Ijub0E16HdMHFUgzboA+cQRK36Zl6AV85Qc41JzGhvIhJCx9ECoL0G5YS3fZVTAi\nDvqkQeoYRO0eeDmciLiPWLrtKGGlZ1EUL9KJEt7anoxrxr1wiYBnj8PIK/6XspPaK1Fzbsfpmw5u\nBcIiISQa1VWDQ96GzjITWrZB0TH2Df0lTPoFNOWjdNRyyHOSCeWZSNfPBPEGHB8JT5jBsIlKswdn\nfTsVfQupr52LNuIUmqQbYPtYCL4RIQSi6SRa12gkWUDxXTDxoR4b+Z9D1sCA5dBd/o+Wgv88foCZ\nQpKkd78LJXz2f5SFSZK0TZKkYkmSvpUkKeTPn/3H/GSm+Eei+c4lSKiIzhNondcgb98A7WshbnxP\nNomwDAjPgoF3oBv7PMIfIHD2LDw3B8lRT9uUYqyPpyFOBqhNC6VvRRuG/6+9+w6PqtgbOP6dc7Ym\nm2TTOyGElkBCld6kSkes2LD3Ll57V+zXK/ar4lWvBUUFRRClNykBAyEkkEJ678lusnXeP8IrcKWK\nYtTzeZ59nt1zZs/OcCY/Zmen7Mgl996bKfE1Myx9E7YyX9xKKOpeF7YGieXx9zFOmAR7JuFeUUHh\n8y8xYO0qjMIfOfx2bpGX87D/RWSF9abjnmyGlmXgUMx4VAdbxX/5wmXgYn+V7u9fg3uKEasJig3D\naNDtIC6mC0l3LELs3MHW2QvoEP0O671NWBwCr68N35lJJO55mtjOd2OfMw/H2c34WEogLxRe3Mai\nZZPwmA2cURRK5Nt2xKgQGsdX4VJvwXLBy8hPbsLZYxBGuwn2/Beq+yAuseDwCmyBr+Ln34rfMonB\nuJCQH1PbfmxpCQLrx+AR8EkDeNcjbvknMuKfyE2fI7L8EXY9+mkvYXDvJnj3HpxR0Zizt/NU1m0M\n2Po41tpmdCk2hN6Mu2k3uqEP482tw7vsaRjWguLIob65H86NFdTmhtJYY2dCgpn8j18nNkiiLL8W\nxVWHOvAcmvbsR+hUAgcmoZqNGMItuN97FVm+GBHfDZY/h6d2K2EfDUKMmAgF6yDbjdNoAcWObJ5L\nRWM4keURKPuWQ6UCK4tAb4Xp1+MY3Y895tcZt2gXlT1CqQqpIPptBfc9aajuLXhd9wF6xIJPcCVc\nj6vGiLTlI463/nDoYOj7Ingcf/1Zdsdyav3B79G2E/Qh88Z/3hv0OSHEPbTtDXrvkd58KC0Y/x5W\n3YJQoxD6MdAnAWylkHg5qId0VUgJW1chfvgcfa/BSFGFCAnB396MIXsnqcMmsHHYGZy9YzPVtyUS\nuvAzgvqa8DRHoeSVYN2djn5aJLWP3EPQT5+BexxEBlK43UnvJ8Pxj9kJ3qGgqLyQ3MwLe7/k6bJR\nrA8M55zud9KnIo0bd2fRK2cnoU1PE16cj5IYiSniaTw+EykV8+lCBX7KQMQ5LVC8g776VbhKVmHa\n0wfP3gjUcaMgbx5enzk07biYsNEJGLo+iDN3Ic6Fn9PYxUr/xZLWBWtJiLIituXB1hD8i0PxXHgr\n3pgAdCHDqONHAi5YjjmtGe6cjWycgiKMhG0CUe/F2eRC7MvC8dgP7NO9QPIHSYhmW9vEjng3qAmQ\n/RV0vgB6voZcuA2v62qU+D6w/jPErPkYn+nGF1mX8N1HCYS0zMK9V6C3f4dvZSBNsavxTw1GdPgY\n9aFQqsoTMey24Nf6Fd5Rw+jUcw41hemwbSE+UUXUfiNQxrhACUBNq6Bh/U4CfEsornARdfPV6IOs\nKKNaaF32MebZD0Djp+gN7rbxvJ4C6DAG0tr+dt3dI7HHqHxbej6tH/jjbnCj6irwhruQigO1dhs/\nmvYyYO121IEmQqsd6JcAeSb0FQ8hlbmouhvwNP4XWuw0b/mCpk+rsV587onV1ZjJv3Hl/xM6hT5j\nKeUGIUTc/xyeDow88Px9YA1aMP6DFPwAKddCcI+2x//yemHRfFj6X6gqheQ+iOkPIMsysXl1uO98\nmfXqkzQ4wnA32QlamUXrle+hu/E6amo9mMOBZLB09eDLebRkj8fzQV+Ucy+kYeJwIu64g+qKzyn7\naSKZ8Tex2cfOtsa+XNn0EP+NuoiBgbfhKt9L2aDxmHPy6JC6CxQv+pYyDDtvRATqGRDgQ91ZNejW\nPASBVuhvQbcqD2e/Ydi9HrzONCyd4vCsbUTJm4yfwYrdWY5+8VUYKqow6CWNI84jfdl3OO5PIljt\nhTXcifGMKxArH0Xn913bBBlPIKGfbsIT+hJM74NMfQpCfFAsLpwbh2Fwb0HfbAO7xLTiFToPvRpP\nUim69L1Q1wQ0ge9ZUJkKq4tgtB2cdry7V6JYbgaTPyx9CM59i/vD3sBn3S64+Aa4viN87UEZHIdS\nsB1XwUIMtq4IXQwNcaHE1regv+pZRMx6LBXDsOR9gys5hBW9U8gfZKTfkDpiQzvRTBCxdefin1ML\nhTsheSjoTZiu7IAcUwdyHnS4AmyfwU/ToXkzlPWGSIWAygK8m9/GvTaWj0afhz4rmn+MCwUpEQkK\njndeoGycE//aCoI61yG+d+P20WOMNSOCYyEuqW38sBKH7oklMOQOzJ449LeWIzrPPs2V/k/M8Ztf\n8bC9QYUQx9wb9P9pwfj3EDMC+t119POKAjOvbntA2yprzhbc907CNP4aatRa/Dzj6bR5JWEDXsS5\nciwlc15Ab4sjJjQHfYwCCV6QlYjUsciUsyH1LcSKuQwZ3I89nnN4LXwQjcGJ3LftDs5lNW/UPMXt\n3jnU+X1Lhy3PIvpfC/ZtuJx9qavIIWdsPP2yduE2m1AjZ6JgxbznG5QaE4SmIM8dg3jjH5gzTLRO\n6k3daBMt2yPQZ6gEvFyFueoD9lkW4L8mBbF4HVzXgdIMF7kr6jjn0Uewla5GzBCEPfE8XPsYVKTC\ntW/CD0+g9Pbi3rgeu8eAbnUx9hR/1AE6LLV1iCIXMhCgB+To8VGzIDwebnsY8npB1jswJAGCb0Ts\nzER+kQsj++PdmYbi6wtmK96SrxGDK/CZ4QtpP8B2PyiVSK+CfGEPPkMn05y8AmPIXEicAuuuRD/0\nZogZQrIzDj68Aa58A/1nN5HS53UemvUsSd2zCQ2dRzAWSv3epizuQyJyyvG/5xVEByNC54MwJYE+\nFNTPIVwPdZ0gYhBM8IFl/2X8J4+iG/4fslMyUApCubjDMigrhgefw9u8D1feYtK6+pLsX0JxfjyW\nAS7cHVqo1z9K0xldiBYCYsKh1Q77tsIlT2AK7IwuJKRtcovmxPz+w9aOujfoobRg/HsY+c+T2w1B\n1SFbmmhdswEiJhFLF7psX8SQsMkYrANxj38B56O1hOfoUd5ZCjvWArGgFzAiBNOij7CN6YjN1ZWQ\ntzYR27KG175Y3Lbi1iX9wLeEmyL+Taiziq2vqMR2r0aUPIq3qRldZi5hUX0Jq++LvOIz3NtvwCbW\nYpadMNtHwvALaSjbTk3mVwRN6oYzupnWvkbqg/xRun5LzKUP0LxvNs7grkQqd+L8+hFMr66F9GnU\n7qthykefEdd/DOtWzic+Tg8xXfCaonHEJeF84GF06gZ0kWbU9Fr0SX1RenpQusTgDspFOI3QaRAe\n94/oTPvxBtagdL8AtqTD9Fngfz7seRqadkLCwzByIFgjYc1beCsKUVoroXIv7qJg3He0ovRIwXB+\nPMqil6DeDIpE5BjQTZyA6u+gsfom9IUK5r3ZMLXtW6X5wydg2n1IHDgHWQkI/jcLFi7iyYeG0P9f\nFnyllY5rs3EXF1DTxYzfcjuOFDB2vwmRfCP4Rrbd421XtQXImiDI2A4JdyAq70SEDeYFeyR3VG7n\nLPkITZcPw8VVKJYQdgzzI0Y5k12rMnFNVGmO8sEZNYEqmsgVeQxGMMtbi7WuHK58HvqM5Sg/12mO\n5bcf2nZCe4P+Ly0Y/x6M/if/HlsDwhKAz403QuZaRuzKQbn6bgB0o68lSXjItNxJgkWgjIyGqlJE\nhg4yIlA7tuK/yo6ht7etFfnGNdAQAHe+DIOS4Icl0Pclzq9bR8usMiofdyC6ZRPmbsZVq+Cw5eNn\n8yJGNtI84jpam76n2rYSj18jevsuVJ8KWkZ1o9g8jKgHPkK8nE7oCxOxVm/BrP4Xb8pzbLTcRt/b\n1lJ8fy86Vb6JYs+kc/CthEyahHS34F/ixj/cjbNPOeK9R/Ce/w98b7keXXkBjqJa1JJRiH4jYc1H\nKFtWQEc/6N0Vb0A99h3B+JcbqeurErj0EpTwAeC8Cwz+4KqHqINfyWX3UXicAv79A+LDS+DqrzB8\ncTOGpz/Hk7YdlFG4S7yo/k1gDwJ3LZTmo0ycQJPYgO7V6wjcVkPzoFQCMtdDTA/oOhjpLUPZkE9L\nynr8LM3cfmshztpLcJkcKLF70IVHErrYgWhQaQgNpSYxg+D912N0mAjwuxCxehkkt0LXr2DMfSC9\nbD17N7qdzTjC9ZwVtgXHlER8svUI5SJWR+UQLL30VobSq3kn3qe+xTxlNESNpkTUY8FIAD7QMh+C\nImHGHb9J1f1bOvVha4L/X2mpzYnuDXoYLRi3F/ZGTOfOQqnPh6fGoNzx5cFzQqCgo6vrYmzeC1CS\nu1PnF4m/4sSoN6KcdQasC8VUHklJVSXRrlLo1w1Gnw1lb0Jwd4iYDhHTMSeCN+9dcC+mya3g99k3\nGJKs0LMvLHgQXR8nQU4TesbjalyM0lKO7PcoUeogGtbOxq9fLfr9dtQfc/B2cUDcZSh+iXR/00D1\nmSpBnWdTXnE/UQ3V6POXUXtjDoF3mWntZsFvmx4RCnVjumCN+gjFVoItpS+G4nxEUxNUl4PBD0xW\nkHZIng0VT2IcUY13kRv7sHEoVQEExsyE7y+DuAlg7Nq2ywggnS3UXDWeoOExBPQNhLy9UFoAdj3M\nuw7VkIWc6kbp2xvZugvXdl8cahRy+Y/4X7MKyotQPGvRN5XjuzcTakpwX3MhTtcwRGsxTWMa8N81\nEGPvrwiLLkQ+NgDZ0khzZDLGHr64BsQhfHZjHJlAZN4qHBGRZIaasRY9TdfIINSOVkg48LuOq44O\nY9eSUd7KAjZRntiCO6ID8eEfUbLrKnIj9XQKmQKbl2C85Fbs9xXgKatHddYRbQyCb9+G3DTIzgCH\nHYzm019n/ypOoZtCCPExMAoIFkIUAo8AzwCf/+/eoMejBeN2Qngc6Hr0gl3ft41f7XXWL9LojOH4\nuhIpHqHDGPQPcrmPxPl7oCUFdBUwvie5nc8kOiUW7Fmw5mqkJxcG337Yf9u+V12Fbd77eBL9QCeQ\nY29BZHwD21biN3IVRIZC/lqMmd/g9bPgyvwnrrh4LMkD2Snd9GzYjWdVDkWXX0MXdSrqj2sI2efL\n/vFByJo3qA9NJiLUhOpTgm15CZ4LmogwdkPJ3Q9JXdk/QhDxZSEh56Vha9yPscoGjhYwToAHv0JU\n7UXmnQvdRyH6n01tUSeCO9rwmlUcvf3AlQLBg6H4W2R2Bp57fNHl+eCtdBFY1YSy30JAeFeolvDQ\nLJgwA6ItkKSyNuJD/FrOo2PSUILigyjOyCXo1XQc987BZ9oQvLtW4QnqCF8/iffBx5G269AJJzbf\nXuirS9EVS0QvA7J5C7VnRtNUaCWmtRl1SRxqvy4w/HIMxlGQdhvO3I9Rrx6BJ+YSqi6qJ6RqPa49\n76Ks3oHYl03q4B5MDFyI6iNpibNi2SuQZFEZPYJpK5YREWOH8u2gvw/zkx/guncEIufatt07aorB\nXgu2siPPsNOcuFMIxlLKo22DPfZkr6UF4/aieA847W0L1U+Z88s+Z0cu8pvrEVtXEVuahHflbPx9\nO2L38cE4JRODfhbULMWiT0QGTcOR6saxeDHmpB240r/BW7YFr/Tg6ZkPOhem0DLkK1sofjIE360v\n06yfiI85k5D/XA/dQqEyDXoMRxn/Oka/OBzOn9Dn302KLY/9U7rRxeEmaMVuHHXX4vPyAsTN19Px\n9QXsnjuSWN01VCQuIFyJxlWTh2zYRmh1OkxZAF0txDZeS3WAH/6rnYSsy0OMHgpX3glZuW1lDemE\n4RM75F2LiIvFtS8Ena6BoN078exzw79GQ3I0dI0HQvGcUYGIctHa0AO11oGOQrgiBVr6w95S2L4L\nugqIb2LU7rtw6vdQ5O7N3NE380CXmahFSZj2/QCeXij6UqqiJKHXdsET8Ck60xdIJRIfkYkxaCj4\nPAZCpWH/BlpM4QR2spA2QEfnHD0Bb3+B2LoD79AVtKo5lF4Tj1/dTwQ0rqEhwUCjMQJ90w7UIgPe\nfXbsSQNo9p+I4r+J+OXltJbp8To/IVka0TmToP55CK2BRzshnDr0HcCj/ABDhqKYIsC2HapCwZ0O\nDD/NFfYvpJ1Mh9aCcXuRurgtAE+588jnDfFIMQhp24a+40M0psQgNl2CEhlBSbaF8D678Im7krjc\nfyKyCzFYDOjO8OLtkIJpwO14DT40W57BZUxF50hC+SAY/+nnYvtxMWpmIZ5+Kwme3BuceujXE1o6\nQVNa2zKQgNHQBzovRb/mAlriJ1M59RPC3luOy1dFjuuPSP8RRSfooL+fGjbg9XMSZjbiY91L0+tu\nbO/djG/8DFjQlVBTOZt6TaKoxsaYc1PQhY+DjiHw6ScACHSopYmw9zuIfYKNuluZVfIdav9U3Gcm\nwNAp0PwNdIhAFHXBkHw+dvPd8Eohpu79ILUESrZCYAVQBPd9BTdOh6pWkDsxxHpJqE5nbsbtlFmt\nvHHJJG7+ZBUx/czU9bCR6+1KoHE8Bp/H8QpJnnMTCe4EyH0JRDH7nCsIrPqJKLUnSuBQgkvXUey3\nnQAyIbkGZdVSfGzRxAdfj3vSZegdgtDcWSjBs5ExK3DMOZtbHo7ixpF3UB/RTMhSJ4pOwddWhNvy\nKYbpr0D4ZHA/CE/FQ1AC3L4eUbMDljxD/agvCdi+AjXuzLa/YP+hp6eO/lX99kPbfhUtGLcXSaMg\nKPrwYw37Yf29ULEdooehDJ0J+zpD1VYsshDHNl8qppsJ+U8mxX2TCfKuw2C2QdRl8OnDKI1V6MYl\ngMWL6pdEoPgQ6WzF9uqFVF56P83vPIrji3zCYpz49YqhfsB0AtWrwdUAGyeBUgc7ZoG1P4RNBjUI\njJGkuM6kYPOH2LoNR2ddh0xpQLyyF86bgzU/g6qwaoL2NOEp+QSjNZL6lnoC9RaQXnDXIqSHnMTh\nzMp6ntxOSXQzToR5j0N9Ntx5ftvaFC4zbDdC8wKmleeA14B5uQ8G4ypkwfeIsWfC+DAwlSB2P4az\nxIJ6nhU6zoPdyeCJgmX54NsE754JfX0g1w4+ddCvO6i5GMrTkQWJXB1bx6vXTSHGWUlv6xkYKr0Y\n1RsocWVQseQuEj/bjegnkVUVlOUEEZW6FN/44YjYQbAtjZisQux9h2MLt+MbPhzRbxuu8XOo3/E0\nIZPfRQztAaGxyKDXaBxWhMO2jSfu8uCM9mBp9GJIvhBRv53mEDvGRfvhigPfcHVGuHEtzD8LytaA\nbyxq5wCMN91C6/zF+L74EkTLkxu5o/mldrIHnhaM24s+EyEs/vBjAfEw/l1IexX0FoR7H0qvGshY\nCxU/YQoIIdbeA29dKPEb97MnScGe3JkBxoXI/R4I64QI6YSoXgr5z4CpDyXbsvl6soXYq6+jT3Qt\n4Q9E0nBfA35vNGAbDFU8R4huDqI1BM5cBIZgaNgOZQsh90sok4idmURe+DZvBOZwxVPZqDHliAFm\nRAcdpL1K/MfpZM8dTNBmB+6ocTResB2/+W+Rd8cWwo0C2e1CmkJ7El1QQ+n+fbT6vo7pkX9D82rQ\nFULgP9rKX7wfgsP58dsnGbPnP4iYiYi+U6mLvQGLaySGFx6Cac8i/YbSNO8TgkbvxPFtd4yKEVZv\nBVTIM+CMDcHQpQt02gS5wKossMbSelYrEcZo9DE9uc/1IHl5QaT1HsTqoKvxf/V6Ipor8PYIRjWa\ncAy6l323Po66uxRrwEDEXV/AFx/AD9vRd+hEwAUv0PpqDk1V6fjfsJXq+otxjzG29eemdYAIiRg0\nF2vtTKQYTHlrMc48Gzq/aDzJnfFWd8ay9SXcPv7wwdXgckBIPHQeAue8D9vuBnMowuSL74tv4965\nEynl4YvGa34drZtCc5i4lCMfN1hgwMGZlEJuR4adhdhxPSLYi+LTj8q5lUSss5NSsIniDp3IGuGi\n+wO1yMpmKIqFPo9SXL+DnemP4fNjNv3fLiZ5mBtleEfse7PQ39oV3ZqdBH77DU2dOlFuTyJ0s57W\nuGIcVONqlniLowlLLUY3+SkIycGYezGXiCByE/3o83I+3ptSUHf4QJYF3YwexJR3ozU8H920F6mz\n30fQ7CXEpe2CwgaKYtbhaepBbXE4AV3raPSPwrTxSqhNb2u1+s4AQ1eIiUd6ttE5eQWsHwJjU1B/\nepWAst7Uj5uP5dzp6GsbKKr4jhXTetP9Gze9h/2EPr0JxaZCQhxUm9k90oLet5Gk5Q2og4CvAyGw\nmAZnd4L7P4+36FJ09nCs3noGbV5Pv2WpbB7cA2u8jnHv7UCd+x2phvVYl79Gt/pIlI+eh2tnIM+a\niveaiXi+fBZx9zcY3BHUPnoz9Ybl+AVNRTa9BSH+MGQrqH6QuhQ5ZC5NurVYFzdgD5QEJL2IKvtC\nbTJIBy1DkzFM/bBtunxNAeRugozvoUKFxnXQPQKWv4CuY3+tRfxb0Xb60ByX/P+JOxJsP0LtpyA2\nwvqdeEbeiC7sH9gLZ2GyJECAAh2TCPDfTWR+DbWZQQR/UY3buo7cS4dT8mwtoXuKiDtTJWy8jarh\nRirur8K+BTwB++jQXSVk5TIMqQJvR5W82RF4C2biYzRiaqjCmOlLY1w41v49UfTXQeYYglt3sS/p\nIlzmXHTbdyFNvoiHN8GKKzAXZpN6QTjFumuJ1vVFCW5G2saic3+PvTGBcLKpP9Of+OZR+GZ/ANGX\nQ6MZsrcCF0HUOqQ+Fa/zNbZvvpyOns0QPpT6fa/S0OwgZqmkYfgq1N3LKTF3QulzPfjPxqTPwzn9\nUoyvqYjMYLikJ30ZTMW2x9l4xRl025uP45KOhKklGDZWIfRzUBwjKNGn0hLlQ+LqIvZPGcDg3hvR\nKW5kyggybB/TZb+H0MJ1yLTFyDwbrmoz8qVtqKN80U30IMrtiBI3kQ3fYq8tRPW0YnRWg7kb0r4D\nV7k/hp4TcO7/GJPHhcG3gmjhRdFtgxYveOuhIQRv3wML9ggBIR3bHgMP/GBftRNWngnpL8J3Lhh6\nJUx9FIx/sz3rfmtaN4XmmNz1UHgjGGLBkQ2+QyDiH7BnF17Vgxp1KxRtoslSRpjcDF310P12li5S\nuXAIhBpmw6UDaXXlYP1gI4GNCrohVgKnJOAYmYvRIIn4zI1S6IPfdw5Eswe6e2n10dOY7ybk6yZ0\n3zVi9otCd9driO5PIbtEgq5z2yLtnd5BVP2HIYUrqLj+KcK+eA3vObtRstdiLy9i9fggor0tROR4\n6FTWgKWjSvPblVgTa6m1e0lqriA88lWExa9tB+fGNJpXFmHZVgVRVXjnfY70W46yaxaDP3sY/H2o\nSL2d3SOjGBb2b9Tv7sDn80XUX20hIMOPGZ0jCPAaEPbRGJ7z4Anwoj6/ErHqRmThPAJ6XMzAZf9i\nX3g89p4urPnNlA7vjGNDNlKXh39YAi0DRlA+Yw1msvBzTsb4xFZ8zvenW+pK1OU5OLPcKCGhKKFR\n6J//BhHXpW1qe8lu8FRD7EiabP/EWZWGQ24jwuZBFU3UZ8QjG6tRk3bRlHweIT6PQtJutufdwdCy\nGAiphoRt8PEVGAY345HVqCLkl3UitBcEJsGw+7QFfn5L7SQYa+sZt0cteyB3BrSkg7knJHwJEXPA\nY0WW7IFONYhVt+LwbsbfzxfhmgBViYiivUipQNxlMGEfdJ+BKaM7fmckEbRwJnUv3IStTzBCdwG6\n+WB9xYu5TMV1QTLOy+JQVT0Wr5Nwj4rBdwq2Hj0pzHWR88ICSufvxlnbBCKiLY+mBIh9AtJ0hH/3\nFmJRCXKBC9eGcXi+zmVA4TaS76ql3xchhKQuxpjQCVdROg6TL7ndkxnkfwW+hpFg7A1F9Xz9nB9P\n5VwI0olMTIKXH0N5RiLq6mi2Wsl/dDR7+0hGRn+K0TeWoon/4uu+D2C4348obz1O5/14XC8gXp4B\nHeNwXGPCsXgYDZ4NNCc10xKWjz7SS3LeELp7mtjRvReR3zcRvrEEvTOS+oRW3PJ7dvmF42ztge+K\nZai967Gn6ij5oIXsM++maG0W5IT8MwAAGDJJREFU6qSpKJbAtqUxlQN/PtE9ocMoEIJmyzjq42sI\nixhHXUQCTX7zsKX1xho7AnVbESEFbe9rDQjHFhZEy4bXQR0O+kho0WGq2Iu75j9HrxuJd4K15+9d\nA/9etN2hNUdlToJuaw4/VpoDX04An2qUQmBGKnX6pwjma8R/bodrVkLDCgZbnoWGXpCXCnkZKIl9\n8HUF0xB5Ec6GOZjXh+BZshS9Ph5d5g68+QYUTxneQifOi2bhPDsXT2Qt9rVLCOtiQd8hAJpzcSt9\nwaUc/oNR3nYIH0PjBWdhLByLVzWg71GHt58O/7VuWpJKEcpS1MI4GFKE7GOmNnYgYRk5mMRUWD0f\nVn3NR6bpXNLwDzYPH4rcqCIN+xEzP0N8/wWOaTOo7PgWwrOWoSHPoHq9ZO98k41WyeSWUXjcNgI9\nFbhcH+Ex3o5z2CM4rHGYVtrwdKnAPKAJg7Ie1gyA7okgexJgW8/ADZvZN2kkPz0wgQElMzAvuJ4S\nU3cm+syhuGgLlf/O5tsPhpPUmsbL1z9JdxHClZgQfcZC2P+umHhQJQVEcCk2cxn+rkSq3ryA0AED\nEWdMgN1ZsPcz6HExIDFFF+Lo0AnzXWPg9vngDUQiaXEtwMicI39AzHStr/i3pg1t05yUHd9Dfhmc\nGQhn/YhbtSFxoc8vh4iuYPSFsOlkt2yn44d9ocoDV66FuOHIx6dT78khqzyBmD0eGPIP/CZPwjmi\nL96SJpTJ/hgmJCEK0jG9rMdLFZaARlqxYXcG4GeoQ5VmIA5yt4FoqzbuxU+RPqoX1d6tjOg+GvN0\nE/ay1RjjvRi7TkDfkEfRzHScO/YSaG8gIGEY3xp8sDrLqbj3GqJCzXivvI8l31/I/KtvJuCsFrIv\nSyZu6yCUu6bSsOAV9uybgWgwMTjPi7PmXEz7KuncDAlnTAV1PWXlP+DYXktzQTCtHV4huqgY894E\nlOuy4N/DYG9c2zjcSh049oF8EwqqcegFYfXNRL2ey6qJT2O/K5GSHT1Itf/EGVkFhM+6mrFh59Gx\n8R4i6EkX4hAISBkJnfsc9TbVUkwK11LtuR7HfRuwDvTiHfcapM2Bi3+Cbf+E1jpMpggclVEE9LoN\n/jsR3psHmzbieOh2pP2Do14fRZtt95trJ90UWjD+syjJwXvDfLy8gE7tTB0vEFg6ED6ZA1e+/XMy\npckFXc+DpD0QGoPEScugenRZnSlz6Kh/Yz7RGRmg12MIC8b7eiRKY2/E+o1QWQApMxGWPug2fYSP\nbxiekELEOhdYHaA0I3PGIqwXUuCjgq6CTkVu+uz7CVBwR96BLWALgS2teIfXoBqWEJTeBXtQM9Lt\nj3F3DmEilmCfCsJGGODCL3n8Py7OUZ9hZpe3qS9Nob7MRsvqBVQ8EEVJ03+IKfWn46bVeBQvhbUW\nXIWgt/SkZac/FVkbSZrTgqz1w7g2DndyMYqrCe++esSroxEd6pANjYiPfeB2wFSDI2Q/jcRSXQcZ\nOl/Ke8YxaPV67BX+xCaUMfBDF2Le2+hGDUckDIQ+PejqcYLuQGvUEtD2OIIidmGnDlfmEkwLf6Lm\n4gb8YgIxOd6F2HFtI2OGPvzzD7P1qaMQkwbB88vhq4/h5vvwsV5Oq1/T71yZNIfRhrZpTsqlj+HV\nPY7HXY2QpThFHqb94VC0q20yxQEVPskw/mmQErfrJ5p5Gp8RD+Fd9BIiKJaIFStQw9rWupZPPQU+\njyKbdyAaasEG2Fsg41uI0KPUN6EIA5xhhdY68IlC1IVA9XYiQ7zoe5+HSJgADfXI/DnUGZ4hsPha\nlG598bZegbT9iH9jIYYsf2qmTiTklqHE5H5IaJkD7FUsePd1XOHPMXPscqQ7msDHivBrdJHfw0BR\nQiwR+8uRsSVkT+6GM+Q2DLEzidyaSk3sa1SvqKTL/R3xd5+Hd10ujWFRVMVmYN5XSGF+K9H9sygv\n7kRgfR2Nb55Na1MmBHXAGBqK/3eZrO6axIjq5cw0WFF8BiC/L4aQDNxfViF8zXDzvTB4JLRWgicT\ndF2Pfm+yf4TOg/DkrKRL2pcojgrKlpRimhOIw2nHZ9f3MGTTwfQHuhlc9Qd2ZU4cAHYvpPRDoCdQ\nvee3rj2aY9GGtmlOio8fOJ3ojG9QIv6BP9Ogbj/c8TVEdPlFcpfIosYwET8ex9CSjL2+EN9B/TF0\n7HUw0dhzkeW3ICrdMHEg0B2achAGI9gqIS4Chr4KUVORVclgN0HwcnhwCoZZ/wSzCXKWI7d9Q+OA\nMnz3BKPThYN+IkL5FgpfgqTPMZV8jks3gMa+9XRcnghhQ6iyreCLXSOZ+++boMCNmn4T3rKnKR1h\nZuNDwzjzvxuIrahHyTDxXfz59Ohjx87j1KeEk2erJ2RaPWFmf0zqNegMe/HfOA9Dj56IwN3EDs8j\nrVsf/PLdBK7zEvLMVqznzMawoxW+exPueYdBtQvJ8BtCeEYOofkmhLEUOdYHpaEWMWYswroBctLB\nsQViYsE4/ej3ZvtXsPVDOm54g/Jz7mX/DQsJmfMI/uZJ1NeOBq/n566do+o36OenCn4nWTk0p0Tr\nptCcLEU3G48SgZ2bCOJKGDUWLEFHTGvjeXy5ATMXQKCV2hnnE1jecFga0VKHkgrCGwGVqyGgBBo9\nkFoEqg6uGA/h57Ul9hsIq/Oh06q27eXDE8ESCFF9qe3wPV5rMv5fF0P5B9D7aoTSF2JfA10AKIvw\nM9xEjfIILQ9dgbO0J1nz9vPW8P8SkJ5DWaQZp2kVSpyOXdck0WfFLoLsfrhC3aiJM6nL7Uk0F7K3\n4T3ycj4ltlMEkYv2Yty6G/fI0agtXRGOTYTs3IRHuHC2BNJL2Y+/pRXDjDEwphrU0XgXXgrmjii3\nX0GP80ex5LLz6TV0PaE5A2HpXYiE9Yh7dyNan0V6h0LhUtwVq9CbDMDFEHCELbQAhsxCvn42jsve\npGH6gxiTehB07bUICcHrXDDqJa2vtz3TgrHmZClKX1rZTDiPYWE0HGVnHYkDP55A5eBaF3URoYSo\niYcndDQhxu8ExQEyAFw2eHsyDOoBATngf/PBtOr5UPQ65LwKUWHItH9CmR/ecy7DHrWXQOYiWh+H\n+DPa0gvRFogBrLFQX0RQ0MNUeq5kzusPMq+bhUBPNV5HJvvCRhPwUybmiQaSbS4icyrw2gzU9tOj\nV3fRL3g9W8oX4Xi3jqEXP4Pe9Ci6yDuR4d/j0dtw9RmIoWAtSivsPSOW7dahnOV5EcOKK2HoDMi+\nF7lmEjWPBVIa4Yd0z6bDU0sYsKYSi8xiV+xuotWO5JvWE0ICAepcLPb7UZMeJi0F+jUWoPgfJRAD\ndOhDfZe7qb7sQYSE8Oeeaxt14qhHGfYuRJ75K+625rTR+ow1v4aRJHwYdMw0AuNhgRignlY6h/Y6\nPGFQx8PWOcZohRvWwGcDIcgL+52QdOCa5mlw+1iYdz6EW8D4AVjmwpwLCO+QhOHWy6Dmfkhs+WWG\nghKgJgeRsYTg+V/z4GUbCdigQICDam8EHf+VgXVvCz79XOi+KoEe8WRGd8Ol1xEWXIk/pURu3Y3v\n2CBE3aXIjC4I2w5E9xBMdR7kumchwhdF9ZBR0pf40ALIvxm6XANrPoItTYjHC/HoV+BfeB+xmzeg\nhvrjrYqmbk84jS9YCN1VTnHrZ2SZJL5qEnrLDLzudyn1CvxJp1J+w0AxHgO/3NJeut2UPjcPvT6A\nuGfOxThgwMF/z9gxx76hmj/eX6FlLIQIBBYAcUA+cL6UsuEoaRUgFSiWUk47lc/9O1M5+S2dWqii\nhhr01B8/sckKvaLBcTasXQxJ/Q+eM/pAv/FIVypUByBGzUCVDtQX7oT62RARDd5maKkE8yEb4gYn\nQHU2DL8ZXecZxL01k7oRdgK/sBO0KxR5SSUBw25BZn2A4ilARucR6eegSR+MaXkr1r21SCUEpe9A\nGPkkoldHcK0C5w9gr0Q4z4NKM5tFJCuCRnOL/VUq1HTCXnoCOpvhortg5xtE7JqPdAvctSF47/yR\njhWpbL3EQM/au7FGncmo0kHoOg3DTDiK0CF1V5LunEqM14/OniQU1XD45joH2H78kejnniNg2jTE\n+zdBzmbo0AsM2u4bmhN3qi3je4EVUsrnhBD3APcdOHYktwF74FdEE80pMWKliVZ8OYF+S68dgnQQ\nPRcWntc2DOvQSQbp38DgPGi5EzYugbBI6GCFvI2QUQNx02D78zDgobY96qAtGO/7ru15ZAymR7bg\nSn+IjPiVRFelk+/fi9DFT5IaN5Ag30A+7DGFJEcUEa7uNJt3kTG7jqEJIxnoXI/e/RTYAkA/Bnwf\nA7+2oF/t+JLP6rLpHxBEzx+70/LdWjy6GNQQJ3LVw8jGrohL3kFU5KA3WcAcRteCnXxmTeasyIdx\nRu8hoMQJnQ5+oxBCEGicjMG1GcV2VduPeKZf7jVnGX7Iwu4zHoa5I2H0DXDW7Sd9rzR/X6c6HXo6\n8P6B5+8DM46USAgRA0wC3jnFz9P8KipmQgml1/GTlp8JlkHgdEBNOfz70cNOy5AIiE9EDLgcNnwD\nkZ2h9xg49zpIaoXnXoGXX4W98w++yV4HeWuhOqfttRBY1vxApy07KZsajrOqOwW3ptKn31349n+I\nGP1AYgs3IKpfQk7+iazOkSxUqvjKNAan/yfg9w6YZoFysPW9LGoQF+Qu5Mq6LMSqZ/FJ6YO44woY\n/SLi8kyY/A6ux+chd3wJvduqqW74PSj1RQRXJ+Fj6Qc/fQvNdYeV10g0LjUKPEVguxfpTj/2v58l\nGPpOh80fH//fWqM5xKkG4zApZQWAlLIcCDtKupeAuwF5lPOa39F2MqnGhzJqj53Q2wTOnWAaCyYz\n9BgApfsPTzOmEwRcD24XFGTBmkVQXQYT74BhN8ITH0B4d1j61SFvklC0BQy+ba8W3UNtq4PSwVGU\nDb2QgRPeJj64L4aUc4jqOZPuIoLohDsYSXemlgYx9ZsW/qVM5nx1KIajDBEbXpfHwPLd6GproP8l\ncNFHKOY0iOwCQd1QzhiGevYoPGmFSO+BaqgoJEWNIjP1RfQlJbB5ARhMh13XRBRuqhGyFazp4ElD\nymNUY50eLnwOpj8MjSe0Q7vmD3dqi1MIIW4TQqQfeNz6a3Nx3G4KIcQPQPihh2gLqg8eIfkvaqkQ\nYjJQIaVME0KM4oi9boc755xzfn6emJhIUlLS8d5y0jZu3PibX7M9OFK5moKdeAa4Wb3s+7YpvUdh\n9SnA33wthZuKgY9Rg1NIzP+W3R8fbOUN67OUjau6IFnKYGnGuORDChIHs//zhei8PXErtTB4DnHu\njRQc+r6AAWz4eiU9CxYRbdvO3oHDCU7ZSc3CTix0fXpYPuqjWkgzeAnJ7w142bhxE60tx97tXEgP\nwV5/tqflU24ai/XbdxnX9WPW7wijtLEvAIMyP6UwfBQRk8awY/a1oCjUWFXWxw/n8h8+ITqoGysW\nfnXYdVWfRgK7eOnmjWXj8lRABT45Zl5+lrnimKf/TnXwt7Bnzx4yMzN/hyv/+l/whBA9gKuA/gcu\ntEwIsURKmXfSF5NS/uoHkAmEH3geAWQeIc1coBDIA8qAZuCDY1xTng4fffTRafmc0+1I5bLLVrlZ\nph//zV7XL4+1thxyepv02h87eG7p21LOjJKyruL4196zXMqPb5HynelSvjNMVu+KkYWOntItq3+R\ntELWyGVy3c+vT/he7f1Syvr8g6/L75Sy4fO253UlUr5/jZRSSveyJdJx87XS6/HIZbJEDpPfy4ot\nL0u5/MlfXNIr3TJb3i+lu+DE8nAS/k518PdwIFacagyT0HCCj19+HnAu8PYhrx8E5vyavJxqN8XX\nwOUHns8GftF8kVLeL6XsIKXsBFwIrJJSXnaKn6s5CWaMnMEJfLs4UheA8ZCv7Y53wXjVwdf56dCp\nC5iPM2OsKg9emQ6KDtQmiOyDx2gn3DYXleBfJA8igBqOOCjn2LrMAL+Yg69D54Ihoe352jdh5PUA\nqGdNRh0zHtecWxmzKoMUrJjOuB7q98CauYddUqAi8YLa4eTzo/mTaDnBxxHtBoYLIQKFED60/TYW\n+2tycarB+FlgnBBiLzAGeAZACBEphFhyitfW/IaUU7zV0vEReIvh0EXPTb7w+BIwHmcI17KnYdpj\nMO1RiOwLox4m0H4NhsCpR0yuQ8WL94jnjkmIw2e6KUYw9WnbT640A+L6Hjw1aSpy9y48d9/GHZ6u\nGIUKccPghwegau/P6RxU4KSCBradfH40fxK/vs9YSplFWxz8AVgK/MSvXO3ilIa2SSlrgbFHOF4G\nTDnC8bXA2lP5TM0fxPklbXXskCpzyaOgNxz7fR43XDjv4JjbMU+BAH3Kk79TRo/gnYug66jDDgmD\nAcOi73C//ALxy9agTpkOA28A/xjY+y2EdgNAj5VWCnCfyBhtzZ/Uqc36kFK+B7wHIIR4Cij6NdfR\nZuBpTozwB593EeKQlufxAjG0rXGhHlLNdMd/j0RixEAtDQRx5OUqT1hrM+z4Evqe84tTwscH/X0P\nI12HtHoSp4Kr9eeXCkbCOR8znU4tH5p27NTmQwshQqWUVUKIDsDZcJwpskehBWPNifF5HqFYT8tH\nefCQTQESyQVMPLWL1RXD5AcPbup5BEKvP/yA/vDhbeGch6qtpPYXdsrzob8QQgTRFtVvlFI2/pqL\naMFYc0KEcoQNMn8nOnQMJAX1t9iiMSCirb/6FOg58sp4mr+KU2sZSylH/Ba50IKxpl0aQh9KqTj1\nC/mcnta85s/sqCMlTistGGvaJR9MJKANJ9OcDu1j2TYtGGvarWPNFtRofjvtY0FjLRhrNJq/Oa1l\nrNFoNO2A1jLWaDSadkBrGWs0Gk07oLWMNRqNph3QhrZpNBpNO6C1jDUajaYd0PqMNRqNph3QWsZ/\nqD179vzRWfhd/BXL9VcsE2jlaj+0lvEf6vfZS+uP91cs11+xTKCVq/3QWsYajUbTDmgtY41Go2kH\n2sfQNnFgR9N2QwjRvjKk0WjaLSnlKa0mJYTIB+JOMHmBlLLjqXzeMfPS3oKxRqPR/B39BlspaDQa\njeZUacFYo9Fo2oG/TTAWQgQKIb4XQuwVQiwXQhx122EhhCKE2CGE+Pp05vHXOJFyCSFihBCrhBAZ\nQoh0IcStf0Rej0cIcZYQIksIsU8Icc9R0swTQmQLIdKEEL1Pdx5/jeOVSwhxkRBi54HHBiFE8h+R\nz5NxIvfqQLozhBAuIcTM05m/P6O/TTAG7gVWSCm7AauA+46R9jbgzzJy/UTK5QbulFL2AAYDNwkh\nup/GPB6XEEIBXgUmAD2AWf+bRyHERCBBStkFuA5487Rn9CSdSLmAPGCElLIX8CTw9unN5ck5wTL9\nf7pngOWnN4d/Tn+nYDwdeP/A8/eBGUdKJISIASYB75ymfJ2q45ZLSlkupUw78LwZyASiT1sOT8wA\nIFtKWSCldAGf0la2Q00HPgCQUm4BAoQQ4ac3myftuOWSUm6WUjYceLmZ9ndv/teJ3CuAW4CFQOXp\nzNyf1d8pGIdJKSugLTgBYUdJ9xJwN/BnGWZyouUCQAjREegNbPndc3ZyooGiQ14X88ug9L9pSo6Q\npr05kXId6mpg2e+ao1N33DIJIaKAGVLKN0DbzPBE/KUmfQghfgAObSkJ2oLqg0dI/otgK4SYDFRI\nKdOEEKNoJ5XoVMt1yHUstLVUbjvQQta0I0KIM4ErgGF/dF5+A/8CDu1Lbhd/S+3ZXyoYSynHHe2c\nEKJCCBEupawQQkRw5K9OQ4FpQohJgBnwE0J8IKW87HfK8gn5DcqFEEJHWyD+UEq5+HfK6qkoAToc\n8jrmwLH/TRN7nDTtzYmUCyFECvBv4CwpZd1pytuvdSJl6g98KoQQQAgwUQjhklK2+x/F/yh/p26K\nr4HLDzyfDfwiIEkp75dSdpBSdgIuBFb90YH4BBy3XAfMB/ZIKV8+HZn6FbYBnYUQcUIIA23//v/7\nh/s1cBmAEGIQUP//XTTt2HHLJYToAHwBXCqlzP0D8niyjlsmKWWnA4942hoBN2qB+Nj+TsH4WWCc\nEGIvMIa2X3kRQkQKIZb8oTk7NcctlxBiKHAxMFoI8dOBYXtn/WE5PgIppQe4GfgeyAA+lVJmCiGu\nE0JceyDNUmC/ECIHeAu48Q/L8Ak6kXIBDwFBwOsH7s/WPyi7J+QEy3TYW05rBv+ktOnQGo1G0w78\nnVrGGo1G025pwVij0WjaAS0YazQaTTugBWONRqNpB7RgrNFoNO2AFow1Go2mHdCCsUaj0bQDWjDW\naDSaduD/ALG6QX/cNAZ+AAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1116,7 +1081,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.6.0" } }, "nbformat": 4, diff --git a/examples/jupyter/tally-arithmetic.ipynb b/examples/jupyter/tally-arithmetic.ipynb index de5917d7b..391dc809a 100644 --- a/examples/jupyter/tally-arithmetic.ipynb +++ b/examples/jupyter/tally-arithmetic.ipynb @@ -33,7 +33,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "First we need to define materials that will be used in the problem. Before defining a material, we must create nuclides that are used in the material." + "First we need to define materials that will be used in the problem. We'll create three materials for the fuel, water, and cladding of the fuel pin." ] }, { @@ -43,49 +43,25 @@ "collapsed": true }, "outputs": [], - "source": [ - "# Instantiate some Nuclides\n", - "h1 = openmc.Nuclide('H1')\n", - "b10 = openmc.Nuclide('B10')\n", - "o16 = openmc.Nuclide('O16')\n", - "u235 = openmc.Nuclide('U235')\n", - "u238 = openmc.Nuclide('U238')\n", - "zr90 = openmc.Nuclide('Zr90')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With the nuclides we defined, we will now create three materials for the fuel, water, and cladding of the fuel pin." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, - "outputs": [], "source": [ "# 1.6 enriched fuel\n", "fuel = openmc.Material(name='1.6% Fuel')\n", "fuel.set_density('g/cm3', 10.31341)\n", - "fuel.add_nuclide(u235, 3.7503e-4)\n", - "fuel.add_nuclide(u238, 2.2625e-2)\n", - "fuel.add_nuclide(o16, 4.6007e-2)\n", + "fuel.add_nuclide('U235', 3.7503e-4)\n", + "fuel.add_nuclide('U238', 2.2625e-2)\n", + "fuel.add_nuclide('O16', 4.6007e-2)\n", "\n", "# borated water\n", "water = openmc.Material(name='Borated Water')\n", "water.set_density('g/cm3', 0.740582)\n", - "water.add_nuclide(h1, 4.9457e-2)\n", - "water.add_nuclide(o16, 2.4732e-2)\n", - "water.add_nuclide(b10, 8.0042e-6)\n", + "water.add_nuclide('H1', 4.9457e-2)\n", + "water.add_nuclide('O16', 2.4732e-2)\n", + "water.add_nuclide('B10', 8.0042e-6)\n", "\n", "# zircaloy\n", "zircaloy = openmc.Material(name='Zircaloy')\n", "zircaloy.set_density('g/cm3', 6.55)\n", - "zircaloy.add_nuclide(zr90, 7.2758e-3)" + "zircaloy.add_nuclide('Zr90', 7.2758e-3)" ] }, { @@ -97,7 +73,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "collapsed": true }, @@ -119,7 +95,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "collapsed": true }, @@ -148,7 +124,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "collapsed": true }, @@ -185,7 +161,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "collapsed": true }, @@ -212,20 +188,19 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# Create Geometry and set root Universe\n", - "geometry = openmc.Geometry()\n", - "geometry.root_universe = root_universe" + "geometry = openmc.Geometry(root_universe)" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": { "collapsed": true }, @@ -244,7 +219,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": { "collapsed": true }, @@ -280,7 +255,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": { "collapsed": true }, @@ -308,8 +283,10 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, + "execution_count": 11, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -317,7 +294,7 @@ "0" ] }, - "execution_count": 12, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -329,17 +306,19 @@ }, { "cell_type": "code", - "execution_count": 13, - "metadata": {}, + "execution_count": 12, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///9yEhLpgJFNv8Tq\nQYT7AAAAAWJLR0QAiAUdSAAAAAd0SU1FB+EKGA0jE/weoLoAAALKSURBVGje7dpLcqQwDAbgHHE2\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/4vzcvgeY10sY0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMTAtMjRUMTM6MzU6\nMTktMDU6MDCdcfAWAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTEwLTI0VDEzOjM1OjE5LTA1OjAw\n7CxIqgAAAABJRU5ErkJggg==\n", + "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", "text/plain": [ "" ] }, - "execution_count": 13, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -361,7 +340,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": { "collapsed": true }, @@ -373,7 +352,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": { "collapsed": true }, @@ -396,7 +375,7 @@ "tally.filters = [openmc.CellFilter(fuel_cell)]\n", "tally.filters.append(energy_filter)\n", "tally.scores = ['nu-fission', 'scatter']\n", - "tally.nuclides = [u238, u235]\n", + "tally.nuclides = ['U238', 'U235']\n", "tallies_file.append(tally)\n", "\n", "# Instantiate reaction rate Tally in moderator\n", @@ -404,7 +383,7 @@ "tally.filters = [openmc.CellFilter(moderator_cell)]\n", "tally.filters.append(energy_filter)\n", "tally.scores = ['absorption', 'total']\n", - "tally.nuclides = [o16, h1]\n", + "tally.nuclides = ['O16', 'H1']\n", "tallies_file.append(tally)\n", "\n", "# Instantiate a tally mesh\n", @@ -434,7 +413,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": { "collapsed": true }, @@ -450,7 +429,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": { "collapsed": true }, @@ -465,7 +444,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": { "collapsed": true }, @@ -481,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": { "collapsed": true }, @@ -496,7 +475,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": { "collapsed": true }, @@ -510,23 +489,21 @@ "tally.filters = [openmc.CellFilter([fuel_cell, moderator_cell])]\n", "tally.filters.append(fine_energy_filter)\n", "tally.scores = ['nu-fission', 'scatter']\n", - "tally.nuclides = [h1, u238]\n", + "tally.nuclides = ['H1', 'U238']\n", "tallies_file.append(tally)" ] }, { "cell_type": "code", - "execution_count": 21, - "metadata": {}, + "execution_count": 20, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=1.\n", - " warn(msg, IDWarning)\n", - "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another MeshFilter instance already exists with id=5.\n", - " warn(msg, IDWarning)\n", "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter 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", @@ -550,8 +527,9 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "metadata": { + "collapsed": false, "scrolled": true }, "outputs": [ @@ -588,13 +566,13 @@ " Copyright | 2011-2017 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", " Version | 0.9.0\n", - " Git SHA1 | 5ca1d06b0c6ac3b56060ef289b7e5215210e7332\n", - " Date/Time | 2017-10-24 13:35:19\n", + " Git SHA1 | 9b7cebf7bc34d60e0f1750c3d6cb103df11e8dc4\n", + " Date/Time | 2017-12-04 20:43:15\n", " OpenMP Threads | 4\n", "\n", " Reading settings XML file...\n", - " Reading materials 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", @@ -605,6 +583,7 @@ " Reading Zr90 from /home/romano/openmc/scripts/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", " Initializing source particles...\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -635,20 +614,20 @@ "\n", " =======================> TIMING STATISTICS <=======================\n", "\n", - " Total time for initialization = 4.1497E-01 seconds\n", - " Reading cross sections = 3.6232E-01 seconds\n", - " Total time in simulation = 3.6447E+00 seconds\n", - " Time in transport only = 3.5939E+00 seconds\n", - " Time in inactive batches = 4.4241E-01 seconds\n", - " Time in active batches = 3.2022E+00 seconds\n", - " Time synchronizing fission bank = 2.7734E-03 seconds\n", - " Sampling source sites = 1.1981E-03 seconds\n", - " SEND/RECV source sites = 1.5506E-03 seconds\n", - " Time accumulating tallies = 1.2237E-04 seconds\n", - " Total time for finalization = 1.4924E-03 seconds\n", - " Total time elapsed = 4.0823E+00 seconds\n", - " Calculation Rate (inactive) = 28254.0 neutrons/second\n", - " Calculation Rate (active) = 11710.5 neutrons/second\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", "\n", " ============================> RESULTS <============================\n", "\n", @@ -666,15 +645,12 @@ "0" ] }, - "execution_count": 22, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# Remove old HDF5 (summary, statepoint) files\n", - "!rm statepoint.*\n", - "\n", "# Run OpenMC!\n", "openmc.run()" ] @@ -695,7 +671,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "metadata": { "collapsed": true, "scrolled": true @@ -717,26 +693,15 @@ }, { "cell_type": "code", - "execution_count": 24, - "metadata": {}, + "execution_count": 23, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "
\n", - "\n", "\n", " \n", " \n", @@ -764,7 +729,7 @@ "0 total (nu-fission / (absorption + current)) 1.02e+00 6.65e-03" ] }, - "execution_count": 24, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -795,26 +760,15 @@ }, { "cell_type": "code", - "execution_count": 25, - "metadata": {}, + "execution_count": 24, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "
\n", - "\n", "
\n", " \n", " \n", @@ -849,7 +803,7 @@ "0 ((absorption + current) / (absorption + current)) 6.94e-01 4.61e-03 " ] }, - "execution_count": 25, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -874,26 +828,15 @@ }, { "cell_type": "code", - "execution_count": 26, - "metadata": {}, + "execution_count": 25, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "
\n", - "\n", "
\n", " \n", " \n", @@ -928,7 +871,7 @@ "0 1.20e+00 9.61e-03 " ] }, - "execution_count": 26, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -951,26 +894,15 @@ }, { "cell_type": "code", - "execution_count": 27, - "metadata": {}, + "execution_count": 26, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "
\n", - "\n", "
\n", " \n", " \n", @@ -1007,7 +939,7 @@ "0 7.49e-01 6.09e-03 " ] }, - "execution_count": 27, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -1028,26 +960,15 @@ }, { "cell_type": "code", - "execution_count": 28, - "metadata": {}, + "execution_count": 27, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "
\n", - "\n", "
\n", " \n", " \n", @@ -1084,7 +1005,7 @@ "0 1.66e+00 1.44e-02 " ] }, - "execution_count": 28, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -1104,26 +1025,15 @@ }, { "cell_type": "code", - "execution_count": 29, - "metadata": {}, + "execution_count": 28, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "
\n", - "\n", "
\n", " \n", " \n", @@ -1158,7 +1068,7 @@ "0 ((absorption + current) / (absorption + current)) 9.85e-01 5.51e-03 " ] }, - "execution_count": 29, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -1177,26 +1087,15 @@ }, { "cell_type": "code", - "execution_count": 30, - "metadata": {}, + "execution_count": 29, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "
\n", - "\n", "
\n", " \n", " \n", @@ -1231,7 +1130,7 @@ "0 (absorption / (absorption + current)) 9.97e-01 7.55e-03 " ] }, - "execution_count": 30, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -1250,26 +1149,15 @@ }, { "cell_type": "code", - "execution_count": 31, - "metadata": {}, + "execution_count": 30, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "
\n", - "\n", "
\n", " \n", " \n", @@ -1306,7 +1194,7 @@ "0 (((((((absorption + current) / (absorption + c... 1.02e+00 1.88e-02 " ] }, - "execution_count": 31, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -1327,7 +1215,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 31, "metadata": { "collapsed": true, "scrolled": true @@ -1343,26 +1231,15 @@ }, { "cell_type": "code", - "execution_count": 33, - "metadata": {}, + "execution_count": 32, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "
\n", - "\n", "
\n", " \n", " \n", @@ -1483,7 +1360,7 @@ "7 (scatter / flux) 3.36e-03 1.34e-05 " ] }, - "execution_count": 33, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -1502,8 +1379,10 @@ }, { "cell_type": "code", - "execution_count": 34, - "metadata": {}, + "execution_count": 33, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", @@ -1532,8 +1411,10 @@ }, { "cell_type": "code", - "execution_count": 35, - "metadata": {}, + "execution_count": 34, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", @@ -1554,8 +1435,10 @@ }, { "cell_type": "code", - "execution_count": 36, - "metadata": {}, + "execution_count": 35, + "metadata": { + "collapsed": false + }, "outputs": [ { "name": "stdout", @@ -1583,26 +1466,15 @@ }, { "cell_type": "code", - "execution_count": 37, - "metadata": {}, + "execution_count": 36, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "
\n", - "\n", "
\n", " \n", " \n", @@ -1675,7 +1547,7 @@ "3 5.98e-04 " ] }, - "execution_count": 37, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -1688,26 +1560,15 @@ }, { "cell_type": "code", - "execution_count": 38, - "metadata": {}, + "execution_count": 37, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { "text/html": [ "
\n", - "\n", "
\n", " \n", " \n", @@ -1840,7 +1701,7 @@ "8 2.90e-03 " ] }, - "execution_count": 38, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } @@ -1870,7 +1731,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.1" + "version": "3.6.0" } }, "nbformat": 4, diff --git a/openmc/examples.py b/openmc/examples.py index 4eeae7799..3d6a06827 100644 --- a/openmc/examples.py +++ b/openmc/examples.py @@ -573,16 +573,16 @@ def slab_mg(reps=None, as_macro=True): for mat in mat_names: for rep in reps: i += 1 + name = mat + '_' + rep + xs.append(name) if as_macro: - xs.append(openmc.Macroscopic(mat + '_' + rep)) m = openmc.Material(name=str(i)) m.set_density('macro', 1.) - m.add_macroscopic(xs[-1]) + m.add_macroscopic(name) else: - xs.append(openmc.Nuclide(mat + '_' + rep)) m = openmc.Material(name=str(i)) m.set_density('atom/b-cm', 1.) - m.add_nuclide(xs[-1].name, 1.0, 'ao') + m.add_nuclide(name, 1.0, 'ao') model.materials.append(m) # Define the materials file diff --git a/openmc/plotter.py b/openmc/plotter.py index 9e356b1ed..810ee5d27 100644 --- a/openmc/plotter.py +++ b/openmc/plotter.py @@ -1,7 +1,9 @@ from numbers import Integral, Real -from six import string_types from itertools import chain +import string +from six import string_types +import matplotlib.pyplot as plt import numpy as np import openmc.checkvalue as cv @@ -63,15 +65,15 @@ _MIN_E = 1.e-5 _MAX_E = 20.e6 -def plot_xs(this, types, divisor_types=None, temperature=294., axis=None, - sab_name=None, ce_cross_sections=None, mg_cross_sections=None, - enrichment=None, plot_CE=True, orders=None, divisor_orders=None, - **kwargs): +def plot_xs(this, types, divisor_types=None, temperature=294., data_type=None, + axis=None, sab_name=None, ce_cross_sections=None, + mg_cross_sections=None, enrichment=None, plot_CE=True, orders=None, + divisor_orders=None, **kwargs): """Creates a figure of continuous-energy cross sections for this item. Parameters ---------- - this : openmc.Element, openmc.Nuclide, or openmc.Material + this : str or openmc.Material Object to source data from types : Iterable of values of PLOT_TYPES The type of cross sections to include in the plot. @@ -84,6 +86,9 @@ def plot_xs(this, types, divisor_types=None, temperature=294., axis=None, temperature of 294K will be plotted. Note that the nearest temperature in the library for each nuclide will be used as opposed to using any interpolation. + data_type : {'nuclide', 'element', 'material', 'macroscopic'}, optional + Type of object to plot. If not specified, a guess is made based on the + `this` argument. axis : matplotlib.axes, optional A previously generated axis to use for plotting. If not specified, a new axis and figure will be generated. @@ -121,25 +126,28 @@ def plot_xs(this, types, divisor_types=None, temperature=294., axis=None, generated. """ - - from matplotlib import pyplot as plt - cv.check_type("plot_CE", plot_CE, bool) - if isinstance(this, openmc.Nuclide): - data_type = 'nuclide' - elif isinstance(this, openmc.Element): - data_type = 'element' - elif isinstance(this, openmc.Material): - data_type = 'material' - elif isinstance(this, openmc.Macroscopic): - data_type = 'macroscopic' - else: - raise TypeError("Invalid type for plotting") + if data_type is None: + if isinstance(this, openmc.Nuclide): + data_type = 'nuclide' + elif isinstance(this, openmc.Element): + data_type = 'element' + elif isinstance(this, openmc.Material): + data_type = 'material' + elif isinstance(this, openmc.Macroscopic): + data_type = 'macroscopic' + elif isinstance(this, string_types): + if this[-1] in string.digits: + data_type = 'nuclide' + else: + data_type = 'element' + else: + raise TypeError("Invalid type for plotting") if plot_CE: # Calculate for the CE cross sections - E, data = calculate_cexs(this, types, temperature, sab_name, + E, data = calculate_cexs(this, data_type, types, temperature, sab_name, ce_cross_sections, enrichment) if divisor_types: cv.check_length('divisor types', divisor_types, len(types)) @@ -220,23 +228,25 @@ def plot_xs(this, types, divisor_types=None, temperature=294., axis=None, ylabel = 'Macroscopic Cross Section [1/cm]' ax.set_ylabel(ylabel) ax.legend(loc='best') - if this.name is not None and this.name != '': - if len(types) > 1: - ax.set_title('Cross Sections for ' + this.name) - else: - ax.set_title('Cross Section for ' + this.name) + name = this.name if data_type == 'material' else this + if len(types) > 1: + ax.set_title('Cross Sections for ' + name) + else: + ax.set_title('Cross Section for ' + name) return fig -def calculate_cexs(this, types, temperature=294., sab_name=None, +def calculate_cexs(this, data_type, types, temperature=294., sab_name=None, cross_sections=None, enrichment=None): """Calculates continuous-energy cross sections of a requested type. Parameters ---------- - this : openmc.Element, openmc.Nuclide, or openmc.Material + this : str or openmc.Material Object to source data from + data_type : {'nuclide', 'element', material'} + Type of object to plot types : Iterable of values of PLOT_TYPES The type of cross sections to calculate temperature : float, optional @@ -269,7 +279,7 @@ def calculate_cexs(this, types, temperature=294., sab_name=None, if enrichment: cv.check_type('enrichment', enrichment, Real) - if isinstance(this, openmc.Nuclide): + if data_type == 'nuclide': energy_grid, xs = _calculate_cexs_nuclide(this, types, temperature, sab_name, cross_sections) # Convert xs (Iterable of Callable) to a grid of cross section values @@ -278,11 +288,11 @@ def calculate_cexs(this, types, temperature=294., sab_name=None, data = np.zeros((len(types), len(energy_grid))) for line in range(len(types)): data[line, :] = xs[line](energy_grid) - elif isinstance(this, openmc.Element): + elif data_type == 'element': energy_grid, data = _calculate_cexs_elem_mat(this, types, temperature, cross_sections, sab_name, enrichment) - elif isinstance(this, openmc.Material): + elif data_type == 'material': energy_grid, data = _calculate_cexs_elem_mat(this, types, temperature, cross_sections) else: @@ -352,7 +362,7 @@ def _calculate_cexs_nuclide(this, types, temperature=294., sab_name=None, # Now we can create the data sets to be plotted energy_grid = [] xs = [] - lib = library.get_by_material(this.name) + lib = library.get_by_material(this) if lib is not None: nuc = openmc.data.IncidentNeutron.from_hdf5(lib['path']) # Obtain the nearest temperature @@ -464,7 +474,7 @@ def _calculate_cexs_nuclide(this, types, temperature=294., sab_name=None, funcs.append(lambda x: 0.) xs.append(openmc.data.Combination(funcs, op)) else: - raise ValueError(this.name + " not in library") + raise ValueError(this + " not in library") return energy_grid, xs @@ -476,7 +486,7 @@ def _calculate_cexs_elem_mat(this, types, temperature=294., Parameters ---------- - this : {openmc.Material, openmc.Element} + this : openmc.Material or openmc.Element Object to source data from types : Iterable of values of PLOT_TYPES The type of cross sections to calculate @@ -508,8 +518,10 @@ 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) @@ -518,23 +530,23 @@ def _calculate_cexs_elem_mat(this, types, temperature=294., # Expand elements in to nuclides with atomic densities nuclides = this.get_nuclide_atom_densities() # For ease of processing split out the nuclide and its fraction - nuc_fractions = {nuclide[1][0].name: nuclide[1][1] + nuc_fractions = {nuclide[1][0]: nuclide[1][1] for nuclide in nuclides.items()} # Create a dict of [nuclide name] = nuclide object to carry forward # with a common nuclides format between openmc.Material and # openmc.Element objects - nuclides = {nuclide[1][0].name: nuclide[1][0] + nuclides = {nuclide[1][0]: nuclide[1][0] for nuclide in nuclides.items()} else: # Expand elements in to nuclides with atomic densities nuclides = this.expand(1., 'ao', enrichment=enrichment, cross_sections=cross_sections) # For ease of processing split out the nuclide and its fraction - nuc_fractions = {nuclide[0].name: nuclide[1] for nuclide in nuclides} + nuc_fractions = {nuclide[0]: nuclide[1] for nuclide in nuclides} # Create a dict of [nuclide name] = nuclide object to carry forward # with a common nuclides format between openmc.Material and # openmc.Element objects - nuclides = {nuclide[0].name: nuclide[0] for nuclide in nuclides} + nuclides = {nuclide[0]: nuclide[0] for nuclide in nuclides} # Identify the nuclides which have S(a,b) data sabs = {} @@ -559,7 +571,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, types, T, sab_tab, + temp_E, temp_xs = calculate_cexs(nuc, data_type, types, T, sab_tab, cross_sections) E.append(temp_E) # Since the energy grids are different, store the cross sections as @@ -587,10 +599,10 @@ def _calculate_cexs_elem_mat(this, types, temperature=294., return energy_grid, data -def calculate_mgxs(this, types, orders=None, temperature=294., +def calculate_mgxs(this, data_type, types, orders=None, temperature=294., cross_sections=None, ce_cross_sections=None, enrichment=None): - """Calculates continuous-energy cross sections of a requested type. + """Calculates multi-group cross sections of a requested type. If the data for the nuclide or macroscopic object in the library is represented as angle-dependent data then this method will return the @@ -598,8 +610,10 @@ def calculate_mgxs(this, types, orders=None, temperature=294., Parameters ---------- - this : openmc.Element, openmc.Nuclide, openmc.Material, or openmc.Macroscopic + this : str or openmc.Material Object to source data from + data_type : {'nuclide', 'element', material', 'macroscopic'} + Type of object to plot types : Iterable of values of PLOT_TYPES_MGXS The type of cross sections to calculate orders : Iterable of Integral, optional @@ -639,10 +653,10 @@ def calculate_mgxs(this, types, orders=None, temperature=294., cv.check_type("cross_sections", cross_sections, str) library = openmc.MGXSLibrary.from_hdf5(cross_sections) - if isinstance(this, (openmc.Nuclide, openmc.Macroscopic)): + if data_type in ('nuclide', 'macroscopic'): mgxs = _calculate_mgxs_nuc_macro(this, types, library, orders, temperature) - elif isinstance(this, (openmc.Element, openmc.Material)): + elif data_type in ('element', 'material'): mgxs = _calculate_mgxs_elem_mat(this, types, library, orders, temperature, ce_cross_sections, enrichment) @@ -713,7 +727,7 @@ def _calculate_mgxs_nuc_macro(this, types, library, orders=None, if orders[i]: cv.check_greater_than("order value", orders[i], 0, equality=True) - xsdata = library.get_by_name(this.name) + xsdata = library.get_by_name(this) if xsdata is not None: # Obtain the nearest temperature @@ -799,7 +813,7 @@ def _calculate_mgxs_nuc_macro(this, types, library, orders=None, data[i, :] = temp_data[:, order] else: raise ValueError("{} not present in provided MGXS " - "library".format(this.name)) + "library".format(this)) return data diff --git a/openmc/settings.py b/openmc/settings.py index c608994fa..9ead82ec6 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -9,7 +9,7 @@ import numpy as np from openmc.clean_xml import clean_xml_indentation import openmc.checkvalue as cv -from openmc import Nuclide, VolumeCalculation, Source, Mesh +from openmc import VolumeCalculation, Source, Mesh _RUN_MODES = ['eigenvalue', 'fixed source', 'plot', 'volume', 'particle restart'] _RES_SCAT_METHODS = ['dbrc', 'wcm', 'ares'] diff --git a/openmc/tallies.py b/openmc/tallies.py index 2cf358987..e049d6983 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3437,12 +3437,12 @@ class Tallies(cv.CheckedList): for tally in self: for f in tally.filters: if isinstance(f, openmc.MeshFilter): - if f.mesh not in already_written: + if f.mesh.id not in already_written: if len(f.mesh.name) > 0: root_element.append(ET.Comment(f.mesh.name)) root_element.append(f.mesh.to_xml_element()) - already_written.add(f.mesh) + already_written.add(f.mesh.id) def _create_filter_subelements(self, root_element): already_written = dict() diff --git a/openmc/universe.py b/openmc/universe.py index 7d9f5a29f..92b152ca0 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -6,6 +6,7 @@ import random import sys from six import string_types +import matplotlib.pyplot as plt import numpy as np import openmc @@ -225,8 +226,6 @@ class Universe(IDManagerMixin): :func:`matplotlib.pyplot.imshow`. """ - import matplotlib.pyplot as plt - # Seed the random number generator if seed is not None: random.seed(seed) diff --git a/tests/test_mg_tallies/test_mg_tallies.py b/tests/test_mg_tallies/test_mg_tallies.py index 57767fd6b..aeafae318 100644 --- a/tests/test_mg_tallies/test_mg_tallies.py +++ b/tests/test_mg_tallies/test_mg_tallies.py @@ -28,10 +28,10 @@ if __name__ == '__main__': mat_filter = openmc.MaterialFilter(model.materials) - nuclides = [xs.name for xs in model.xs_data] + nuclides = model.xs_data - scores= {False: ['total', 'absorption', 'flux', 'fission', 'nu-fission'], - True: ['total', 'absorption', 'fission', 'nu-fission']} + scores = {False: ['total', 'absorption', 'flux', 'fission', 'nu-fission'], + True: ['total', 'absorption', 'fission', 'nu-fission']} for do_nuclides in [False, True]: t = openmc.Tally() From f754357aeaf0d097872b0d7c64d37f9f4f6a3b45 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 16 Dec 2017 19:56:34 -0500 Subject: [PATCH 057/282] Use 'earliest' libver in h5py for backwards-compat --- openmc/data/fission_energy.py | 2 +- openmc/data/neutron.py | 2 +- openmc/data/thermal.py | 2 +- openmc/mgxs/library.py | 2 +- openmc/mgxs/mgxs.py | 4 ++-- openmc/mgxs_library.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openmc/data/fission_energy.py b/openmc/data/fission_energy.py index 5ce9f47d5..a7cf3dfed 100644 --- a/openmc/data/fission_energy.py +++ b/openmc/data/fission_energy.py @@ -107,7 +107,7 @@ def write_compact_458_library(endf_files, output_name='fission_Q_data.h5', """ # Open the output file. - out = h5py.File(output_name, 'w', libver='latest') + out = h5py.File(output_name, 'w', libver='earliest') # Write comments, if given. This commented out comment is the one used for # the library distributed with OpenMC. diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index ed2f4f8b7..b0968fdfa 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -496,7 +496,7 @@ class IncidentNeutron(EqualityMixin): 'originated from an ENDF file.') # Open file and write version - f = h5py.File(path, mode, libver='latest') + f = h5py.File(path, mode, libver='earliest') f.attrs['version'] = np.array(HDF5_VERSION) # Write basic data diff --git a/openmc/data/thermal.py b/openmc/data/thermal.py index 6e7b0fe02..0234839ac 100644 --- a/openmc/data/thermal.py +++ b/openmc/data/thermal.py @@ -258,7 +258,7 @@ class ThermalScattering(EqualityMixin): """ # Open file and write version - f = h5py.File(path, mode, libver='latest') + f = h5py.File(path, mode, libver='earliest') f.attrs['version'] = np.array(HDF5_VERSION) # Write basic data diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index 3cdd0fbf5..5cbb56c2b 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -823,7 +823,7 @@ class Library(object): # Add an attribute for the number of energy groups to the HDF5 file full_filename = os.path.join(directory, filename) full_filename = full_filename.replace(' ', '-') - f = h5py.File(full_filename, 'w') + f = h5py.File(full_filename, 'w', libver='earliest') f.attrs['# groups'] = self.num_groups f.close() diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 192f484a5..f9eb0cdd1 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -1693,9 +1693,9 @@ class MGXS(object): filename = filename.replace(' ', '-') if append and os.path.isfile(filename): - xs_results = h5py.File(filename, 'a') + xs_results = h5py.File(filename, 'a', libver='earliest') else: - xs_results = h5py.File(filename, 'w') + xs_results = h5py.File(filename, 'w', libver='earliest') # Construct a collection of the subdomains to report if not isinstance(subdomains, string_types): diff --git a/openmc/mgxs_library.py b/openmc/mgxs_library.py index 93a3607d9..1ac5f1468 100644 --- a/openmc/mgxs_library.py +++ b/openmc/mgxs_library.py @@ -2517,7 +2517,7 @@ class MGXSLibrary(object): check_type('filename', filename, string_types) # Create and write to the HDF5 file - file = h5py.File(filename, "w") + file = h5py.File(filename, "w", libver='earliest') file.attrs['filetype'] = np.string_(_FILETYPE_MGXS_LIBRARY) file.attrs['version'] = [_VERSION_MGXS_LIBRARY, 0] file.attrs['energy_groups'] = self.energy_groups.num_groups From 0460d9a538ba236abbb11b08d83f95af827296eb Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 17 Dec 2017 19:48:08 -0500 Subject: [PATCH 058/282] Allow optional 'latest' libver for HDF5 xs data --- openmc/data/neutron.py | 7 +++++-- openmc/data/thermal.py | 7 +++++-- openmc/mgxs/library.py | 7 +++++-- openmc/mgxs/mgxs.py | 10 +++++++--- openmc/mgxs_library.py | 7 +++++-- scripts/openmc-ace-to-hdf5 | 31 ++++++++++++++++++++---------- scripts/openmc-convert-mcnp70-data | 11 +++++++++-- scripts/openmc-convert-mcnp71-data | 9 ++++++++- scripts/openmc-get-jeff-data | 11 +++++++++-- scripts/openmc-get-nndc-data | 11 +++++++++-- 10 files changed, 83 insertions(+), 28 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index b0968fdfa..08d12106c 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -478,7 +478,7 @@ class IncidentNeutron(EqualityMixin): mts = new_mts return mts - def export_to_hdf5(self, path, mode='a'): + def export_to_hdf5(self, path, mode='a', libver='earliest'): """Export incident neutron data to an HDF5 file. Parameters @@ -488,6 +488,9 @@ class IncidentNeutron(EqualityMixin): mode : {'r', r+', 'w', 'x', 'a'} Mode that is used to open the HDF5 file. This is the second argument to the :class:`h5py.File` constructor. + libver : {'earliest', 'latest'} + Compatibility mode for the HDF5 file. 'latest' will produce files + that are less backwards compatible but have performance benefits. """ # If data come from ENDF, don't allow exporting to HDF5 @@ -496,7 +499,7 @@ class IncidentNeutron(EqualityMixin): 'originated from an ENDF file.') # Open file and write version - f = h5py.File(path, mode, libver='earliest') + f = h5py.File(path, mode, libver=libver) f.attrs['version'] = np.array(HDF5_VERSION) # Write basic data diff --git a/openmc/data/thermal.py b/openmc/data/thermal.py index 0234839ac..99e4b5a53 100644 --- a/openmc/data/thermal.py +++ b/openmc/data/thermal.py @@ -245,7 +245,7 @@ class ThermalScattering(EqualityMixin): def temperatures(self): return ["{}K".format(int(round(kT / K_BOLTZMANN))) for kT in self.kTs] - def export_to_hdf5(self, path, mode='a'): + def export_to_hdf5(self, path, mode='a', libver='earliest'): """Export table to an HDF5 file. Parameters @@ -255,10 +255,13 @@ class ThermalScattering(EqualityMixin): mode : {'r', r+', 'w', 'x', 'a'} Mode that is used to open the HDF5 file. This is the second argument to the :class:`h5py.File` constructor. + 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 - f = h5py.File(path, mode, libver='earliest') + f = h5py.File(path, mode, libver=libver) f.attrs['version'] = np.array(HDF5_VERSION) # Write basic data diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index 5cbb56c2b..b83305783 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -760,7 +760,7 @@ class Library(object): def build_hdf5_store(self, filename='mgxs.h5', directory='mgxs', subdomains='all', nuclides='all', xs_type='macro', - row_column='inout'): + row_column='inout', libver='earliest'): """Export the multi-group cross section library to an HDF5 binary file. This method constructs an HDF5 file which stores the library's @@ -794,6 +794,9 @@ class Library(object): Store scattering matrices indexed first by incoming group and second by outgoing group ('inout'), or vice versa ('outin'). Defaults to 'inout'. + libver : {'earliest', 'latest'} + Compatibility mode for the HDF5 file. 'latest' will produce files + that are less backwards compatible but have performance benefits. Raises ------ @@ -823,7 +826,7 @@ class Library(object): # Add an attribute for the number of energy groups to the HDF5 file full_filename = os.path.join(directory, filename) full_filename = full_filename.replace(' ', '-') - f = h5py.File(full_filename, 'w', libver='earliest') + f = h5py.File(full_filename, 'w', libver=libver) f.attrs['# groups'] = self.num_groups f.close() diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index f9eb0cdd1..b7ec652c7 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -1636,7 +1636,8 @@ class MGXS(object): def build_hdf5_store(self, filename='mgxs.h5', directory='mgxs', subdomains='all', nuclides='all', - xs_type='macro', row_column='inout', append=True): + xs_type='macro', row_column='inout', append=True, + libver='earliest'): """Export the multi-group cross section data to an HDF5 binary file. This method constructs an HDF5 file which stores the multi-group @@ -1672,6 +1673,9 @@ class MGXS(object): append : bool If true, appends to an existing HDF5 file with the same filename directory (if one exists). Defaults to True. + libver : {'earliest', 'latest'} + Compatibility mode for the HDF5 file. 'latest' will produce files + that are less backwards compatible but have performance benefits. Raises ------ @@ -1693,9 +1697,9 @@ class MGXS(object): filename = filename.replace(' ', '-') if append and os.path.isfile(filename): - xs_results = h5py.File(filename, 'a', libver='earliest') + xs_results = h5py.File(filename, 'a') else: - xs_results = h5py.File(filename, 'w', libver='earliest') + xs_results = h5py.File(filename, 'w', libver=libver) # Construct a collection of the subdomains to report if not isinstance(subdomains, string_types): diff --git a/openmc/mgxs_library.py b/openmc/mgxs_library.py index 1ac5f1468..ebfae2fb0 100644 --- a/openmc/mgxs_library.py +++ b/openmc/mgxs_library.py @@ -2504,20 +2504,23 @@ class MGXSLibrary(object): return library - def export_to_hdf5(self, filename='mgxs.h5'): + def export_to_hdf5(self, filename='mgxs.h5', libver='earliest'): """Create an hdf5 file that can be used for a simulation. Parameters ---------- filename : str Filename of file, default is mgxs.h5. + libver : {'earliest', 'latest'} + Compatibility mode for the HDF5 file. 'latest' will produce files + that are less backwards compatible but have performance benefits. """ check_type('filename', filename, string_types) # Create and write to the HDF5 file - file = h5py.File(filename, "w", libver='earliest') + file = h5py.File(filename, "w", libver=libver) file.attrs['filetype'] = np.string_(_FILETYPE_MGXS_LIBRARY) file.attrs['version'] = [_VERSION_MGXS_LIBRARY, 0] file.attrs['energy_groups'] = self.energy_groups.num_groups diff --git a/scripts/openmc-ace-to-hdf5 b/scripts/openmc-ace-to-hdf5 index bfc1c1a54..d6b746890 100755 --- a/scripts/openmc-ace-to-hdf5 +++ b/scripts/openmc-ace-to-hdf5 @@ -46,7 +46,8 @@ parser.add_argument('libraries', nargs='*', help='ACE libraries to convert to HDF5') parser.add_argument('-d', '--destination', default='.', help='Directory to create new library in') -parser.add_argument('-m', '--metastable', choices=['mcnp', 'nndc'], default='nndc', +parser.add_argument('-m', '--metastable', choices=['mcnp', 'nndc'], + default='nndc', help='How to interpret ZAIDs for metastable nuclides') parser.add_argument('--xml', help='Old-style cross_sections.xml that ' 'lists ACE libraries') @@ -56,6 +57,9 @@ parser.add_argument('--xsdata', help='Serpent xsdata file that lists ' 'ACE libraries') parser.add_argument('--fission_energy_release', help='HDF5 file containing ' 'fission energy release data') +parser.add_argument('--latest_hdf5', help='Use latest HDF5 format for ' + 'performance over backwards compatibility', + action='store_true') args = parser.parse_args() if not os.path.isdir(args.destination): @@ -117,6 +121,10 @@ else: nuclides = {} library = openmc.data.DataLibrary() +if args.latest_hdf5: + libver = 'latest' +else: + libver = 'earliest' for filename in ace_libraries: # Check that ACE library exists @@ -150,7 +158,7 @@ for filename in ace_libraries: # Determine filename outfile = os.path.join(args.destination, neutron.name.replace('.', '_') + '.h5') - neutron.export_to_hdf5(outfile, 'w') + neutron.export_to_hdf5(outfile, 'w', libver=libver) # Register with library library.register_file(outfile) @@ -162,10 +170,11 @@ for filename in ace_libraries: try: neutron = \ openmc.data.IncidentNeutron.from_hdf5(nuclides[name]) - print('Converting {} (ACE) to {} (HDF5)'.format(table.name, - neutron.name)) + print('Converting {} (ACE) to {} (HDF5)' + .format(table.name, neutron.name)) neutron.add_temperature_from_ace(table, args.metastable) - neutron.export_to_hdf5(nuclides[name] + '_1', 'w') + neutron.export_to_hdf5(nuclides[name] + '_1', 'w', + libver=libver) os.rename(nuclides[name] + '_1', nuclides[name]) except Exception as e: print('Failed to convert {}: {}'.format(table.name, e)) @@ -187,7 +196,7 @@ for filename in ace_libraries: # Determine filename outfile = os.path.join(args.destination, thermal.name.replace('.', '_') + '.h5') - thermal.export_to_hdf5(outfile, 'w') + thermal.export_to_hdf5(outfile, 'w', libver=libver) # Register with library library.register_file(outfile) @@ -198,11 +207,13 @@ for filename in ace_libraries: else: # Then we only need to append the data try: - thermal = openmc.data.ThermalScattering.from_hdf5(nuclides[name]) - print('Converting {} (ACE) to {} (HDF5)'.format(table.name, - thermal.name)) + thermal = openmc.data.ThermalScattering.from_hdf5( + nuclides[name]) + print('Converting {} (ACE) to {} (HDF5)' + .format(table.name,thermal.name)) thermal.add_temperature_from_ace(table) - thermal.export_to_hdf5(nuclides[name] + '_1', 'w') + thermal.export_to_hdf5(nuclides[name] + '_1', 'w', + libver=libver) os.rename(nuclides[name] + '_1', nuclides[name]) except Exception as e: print('Failed to convert {}: {}'.format(table.name, e)) diff --git a/scripts/openmc-convert-mcnp70-data b/scripts/openmc-convert-mcnp70-data index a3081e3ee..0618e51ea 100755 --- a/scripts/openmc-convert-mcnp70-data +++ b/scripts/openmc-convert-mcnp70-data @@ -26,6 +26,9 @@ parser = argparse.ArgumentParser( ) parser.add_argument('-d', '--destination', default='mcnp_endfb70', help='Directory to create new library in') +parser.add_argument('--latest_hdf5', help='Use latest HDF5 format for ' + 'performance over backwards compatibility', + action='store_true') parser.add_argument('mcnpdata', help='Directory containing endf70[a-k] and endf70sab') args = parser.parse_args() assert os.path.isdir(args.mcnpdata) @@ -38,6 +41,10 @@ if not os.path.isdir(args.destination): os.mkdir(args.destination) library = openmc.data.DataLibrary() +if args.latest_hdf5: + libver = 'latest' +else: + libver = 'earliest' for path in sorted(endf70): print('Loading data from {}...'.format(path)) @@ -62,7 +69,7 @@ for path in sorted(endf70): # Export HDF5 file h5_file = os.path.join(args.destination, data.name + '.h5') print('Writing {}...'.format(h5_file)) - data.export_to_hdf5(h5_file, 'w') + data.export_to_hdf5(h5_file, 'w', libver=libver) # Register with library library.register_file(h5_file) @@ -91,7 +98,7 @@ if os.path.exists(endf70sab): # Export HDF5 file h5_file = os.path.join(args.destination, data.name + '.h5') print('Writing {}...'.format(h5_file)) - data.export_to_hdf5(h5_file, 'w') + data.export_to_hdf5(h5_file, 'w', libver=libver) # Register with library library.register_file(h5_file) diff --git a/scripts/openmc-convert-mcnp71-data b/scripts/openmc-convert-mcnp71-data index 944095919..fe4b06368 100755 --- a/scripts/openmc-convert-mcnp71-data +++ b/scripts/openmc-convert-mcnp71-data @@ -28,6 +28,9 @@ parser.add_argument('-d', '--destination', default='mcnp_endfb71', help='Directory to create new library in') parser.add_argument('-f', '--fission_energy_release', help='HDF5 file containing fission energy release data') +parser.add_argument('--latest_hdf5', help='Use latest HDF5 format for ' + 'performance over backwards compatibility', + action='store_true') parser.add_argument('mcnpdata', help='Directory containing endf71x and ENDF71SaB') args = parser.parse_args() assert os.path.isdir(args.mcnpdata) @@ -51,6 +54,10 @@ if not os.path.isdir(args.destination): os.mkdir(args.destination) library = openmc.data.DataLibrary() +if args.latest_hdf5: + libver = 'latest' +else: + libver = 'earliest' for basename, xs_list in sorted(suffixes.items()): # Convert first temperature for the table @@ -80,7 +87,7 @@ for basename, xs_list in sorted(suffixes.items()): # Export HDF5 file h5_file = os.path.join(args.destination, data.name + '.h5') print('Writing {}...'.format(h5_file)) - data.export_to_hdf5(h5_file, 'w') + data.export_to_hdf5(h5_file, 'w', libver=libver) # Register with library library.register_file(h5_file) diff --git a/scripts/openmc-get-jeff-data b/scripts/openmc-get-jeff-data index 3166b3854..a8fb64acb 100755 --- a/scripts/openmc-get-jeff-data +++ b/scripts/openmc-get-jeff-data @@ -43,6 +43,9 @@ parser.add_argument('-b', '--batch', action='store_true', help='supresses standard in') parser.add_argument('-d', '--destination', default='jeff-3.2-hdf5', help='Directory to create new library in') +parser.add_argument('--latest_hdf5', help='Use latest HDF5 format for ' + 'performance over backwards compatibility', + action='store_true') args = parser.parse_args() response = input(download_warning) if not args.batch else 'y' @@ -165,6 +168,10 @@ if not os.path.isdir(args.destination): os.mkdir(args.destination) library = openmc.data.DataLibrary() +if args.latest_hdf5: + libver = 'latest' +else: + libver = 'earliest' for name, filenames in sorted(tables.items()): # Convert first temperature for the table @@ -179,7 +186,7 @@ for name, filenames in sorted(tables.items()): # Export HDF5 file h5_file = os.path.join(args.destination, data.name + '.h5') print('Writing {}...'.format(h5_file)) - data.export_to_hdf5(h5_file, 'w') + data.export_to_hdf5(h5_file, 'w', libver=libver) # Register with library library.register_file(h5_file) @@ -222,7 +229,7 @@ for name, filenames in sorted(tables.items()): # Export HDF5 file h5_file = os.path.join(args.destination, data.name + '.h5') print('Writing {}...'.format(h5_file)) - data.export_to_hdf5(h5_file, 'w') + data.export_to_hdf5(h5_file, 'w', libver=libver) # Register with library library.register_file(h5_file) diff --git a/scripts/openmc-get-nndc-data b/scripts/openmc-get-nndc-data index f1da2241a..43a528b41 100755 --- a/scripts/openmc-get-nndc-data +++ b/scripts/openmc-get-nndc-data @@ -33,6 +33,9 @@ parser = argparse.ArgumentParser( ) parser.add_argument('-b', '--batch', action='store_true', help='supresses standard in') +parser.add_argument('--latest_hdf5', help='Use latest HDF5 format for ' + 'performance over backwards compatibility', + action='store_true') args = parser.parse_args() @@ -154,7 +157,11 @@ ace_files = sorted(glob.glob(os.path.join('nndc', '**', '*.ace*'))) data_dir = os.path.dirname(sys.modules['openmc.data'].__file__) fer_file = os.path.join(data_dir, 'fission_Q_data_endfb71.h5') +# Call the ace-to-hdf5 conversion script pwd = os.path.dirname(os.path.realpath(__file__)) ace2hdf5 = os.path.join(pwd, 'openmc-ace-to-hdf5') -subprocess.call([ace2hdf5, '-d', 'nndc_hdf5', '--fission_energy_release', - fer_file] + ace_files) +args_out = [ace2hdf5, '-d', 'nndc_hdf5', '--fission_energy_release', fer_file] +if args.latest_hdf5: + args_out += ['--latest_hdf5'] +args_out += ace_files +subprocess.call(args_out) From e698403076f8ed6476b15e314285a05b9bcd7c79 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 18 Dec 2017 10:33:30 +0700 Subject: [PATCH 059/282] Don't allocate/deallocate p0 from openmc_material_set_densities --- src/material_header.F90 | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/material_header.F90 b/src/material_header.F90 index a3c14f8ca..9d93131ac 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -455,8 +455,8 @@ contains associate (m => materials(index)) ! If nuclide/density arrays are not correct size, reallocate if (n /= m % n_nuclides) then - deallocate(m % nuclide, m % atom_density, m % p0, STAT=stat) - allocate(m % nuclide(n), m % atom_density(n), m % p0(n)) + deallocate(m % nuclide, m % atom_density, STAT=stat) + allocate(m % nuclide(n), m % atom_density(n)) end if do i = 1, n @@ -474,9 +474,6 @@ contains end do m % n_nuclides = n - ! Set isotropic flags to flags - m % p0(:) = .false. - ! Set total density to the sum of the vector err = m % set_density(sum(density)) From c76e77f7f89566b7ab6d88d74d929f7fc2bbc4bf Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 17 Dec 2017 23:43:46 -0500 Subject: [PATCH 060/282] Use argparse choices for libver in xsgen scripts --- scripts/openmc-ace-to-hdf5 | 19 ++++++++----------- scripts/openmc-convert-mcnp70-data | 15 ++++++--------- scripts/openmc-convert-mcnp71-data | 13 +++++-------- scripts/openmc-get-jeff-data | 15 ++++++--------- scripts/openmc-get-nndc-data | 16 ++++++++-------- 5 files changed, 33 insertions(+), 45 deletions(-) diff --git a/scripts/openmc-ace-to-hdf5 b/scripts/openmc-ace-to-hdf5 index d6b746890..3235a2da6 100755 --- a/scripts/openmc-ace-to-hdf5 +++ b/scripts/openmc-ace-to-hdf5 @@ -57,9 +57,10 @@ parser.add_argument('--xsdata', help='Serpent xsdata file that lists ' 'ACE libraries') parser.add_argument('--fission_energy_release', help='HDF5 file containing ' 'fission energy release data') -parser.add_argument('--latest_hdf5', help='Use latest HDF5 format for ' - 'performance over backwards compatibility', - action='store_true') +parser.add_argument('--libver', choices=['earliest', 'latest'], + default='earliest', help="Output HDF5 versioning. Use " + "'earliest' for backwards compatibility or 'latest' for " + "performance") args = parser.parse_args() if not os.path.isdir(args.destination): @@ -121,10 +122,6 @@ else: nuclides = {} library = openmc.data.DataLibrary() -if args.latest_hdf5: - libver = 'latest' -else: - libver = 'earliest' for filename in ace_libraries: # Check that ACE library exists @@ -158,7 +155,7 @@ for filename in ace_libraries: # Determine filename outfile = os.path.join(args.destination, neutron.name.replace('.', '_') + '.h5') - neutron.export_to_hdf5(outfile, 'w', libver=libver) + neutron.export_to_hdf5(outfile, 'w', libver=args.libver) # Register with library library.register_file(outfile) @@ -174,7 +171,7 @@ for filename in ace_libraries: .format(table.name, neutron.name)) neutron.add_temperature_from_ace(table, args.metastable) neutron.export_to_hdf5(nuclides[name] + '_1', 'w', - libver=libver) + libver=args.libver) os.rename(nuclides[name] + '_1', nuclides[name]) except Exception as e: print('Failed to convert {}: {}'.format(table.name, e)) @@ -196,7 +193,7 @@ for filename in ace_libraries: # Determine filename outfile = os.path.join(args.destination, thermal.name.replace('.', '_') + '.h5') - thermal.export_to_hdf5(outfile, 'w', libver=libver) + thermal.export_to_hdf5(outfile, 'w', libver=args.libver) # Register with library library.register_file(outfile) @@ -213,7 +210,7 @@ for filename in ace_libraries: .format(table.name,thermal.name)) thermal.add_temperature_from_ace(table) thermal.export_to_hdf5(nuclides[name] + '_1', 'w', - libver=libver) + libver=args.libver) os.rename(nuclides[name] + '_1', nuclides[name]) except Exception as e: print('Failed to convert {}: {}'.format(table.name, e)) diff --git a/scripts/openmc-convert-mcnp70-data b/scripts/openmc-convert-mcnp70-data index 0618e51ea..0489b25de 100755 --- a/scripts/openmc-convert-mcnp70-data +++ b/scripts/openmc-convert-mcnp70-data @@ -26,9 +26,10 @@ parser = argparse.ArgumentParser( ) parser.add_argument('-d', '--destination', default='mcnp_endfb70', help='Directory to create new library in') -parser.add_argument('--latest_hdf5', help='Use latest HDF5 format for ' - 'performance over backwards compatibility', - action='store_true') +parser.add_argument('--libver', choices=['earliest', 'latest'], + default='earliest', help="Output HDF5 versioning. Use " + "'earliest' for backwards compatibility or 'latest' for " + "performance") parser.add_argument('mcnpdata', help='Directory containing endf70[a-k] and endf70sab') args = parser.parse_args() assert os.path.isdir(args.mcnpdata) @@ -41,10 +42,6 @@ if not os.path.isdir(args.destination): os.mkdir(args.destination) library = openmc.data.DataLibrary() -if args.latest_hdf5: - libver = 'latest' -else: - libver = 'earliest' for path in sorted(endf70): print('Loading data from {}...'.format(path)) @@ -69,7 +66,7 @@ for path in sorted(endf70): # Export HDF5 file h5_file = os.path.join(args.destination, data.name + '.h5') print('Writing {}...'.format(h5_file)) - data.export_to_hdf5(h5_file, 'w', libver=libver) + data.export_to_hdf5(h5_file, 'w', libver=args.libver) # Register with library library.register_file(h5_file) @@ -98,7 +95,7 @@ if os.path.exists(endf70sab): # Export HDF5 file h5_file = os.path.join(args.destination, data.name + '.h5') print('Writing {}...'.format(h5_file)) - data.export_to_hdf5(h5_file, 'w', libver=libver) + data.export_to_hdf5(h5_file, 'w', libver=args.libver) # Register with library library.register_file(h5_file) diff --git a/scripts/openmc-convert-mcnp71-data b/scripts/openmc-convert-mcnp71-data index fe4b06368..061f8f46f 100755 --- a/scripts/openmc-convert-mcnp71-data +++ b/scripts/openmc-convert-mcnp71-data @@ -28,9 +28,10 @@ parser.add_argument('-d', '--destination', default='mcnp_endfb71', help='Directory to create new library in') parser.add_argument('-f', '--fission_energy_release', help='HDF5 file containing fission energy release data') -parser.add_argument('--latest_hdf5', help='Use latest HDF5 format for ' - 'performance over backwards compatibility', - action='store_true') +parser.add_argument('--libver', choices=['earliest', 'latest'], + default='earliest', help="Output HDF5 versioning. Use " + "'earliest' for backwards compatibility or 'latest' for " + "performance") parser.add_argument('mcnpdata', help='Directory containing endf71x and ENDF71SaB') args = parser.parse_args() assert os.path.isdir(args.mcnpdata) @@ -54,10 +55,6 @@ if not os.path.isdir(args.destination): os.mkdir(args.destination) library = openmc.data.DataLibrary() -if args.latest_hdf5: - libver = 'latest' -else: - libver = 'earliest' for basename, xs_list in sorted(suffixes.items()): # Convert first temperature for the table @@ -87,7 +84,7 @@ for basename, xs_list in sorted(suffixes.items()): # Export HDF5 file h5_file = os.path.join(args.destination, data.name + '.h5') print('Writing {}...'.format(h5_file)) - data.export_to_hdf5(h5_file, 'w', libver=libver) + data.export_to_hdf5(h5_file, 'w', libver=args.libver) # Register with library library.register_file(h5_file) diff --git a/scripts/openmc-get-jeff-data b/scripts/openmc-get-jeff-data index a8fb64acb..6d9e086a9 100755 --- a/scripts/openmc-get-jeff-data +++ b/scripts/openmc-get-jeff-data @@ -43,9 +43,10 @@ parser.add_argument('-b', '--batch', action='store_true', help='supresses standard in') parser.add_argument('-d', '--destination', default='jeff-3.2-hdf5', help='Directory to create new library in') -parser.add_argument('--latest_hdf5', help='Use latest HDF5 format for ' - 'performance over backwards compatibility', - action='store_true') +parser.add_argument('--libver', choices=['earliest', 'latest'], + default='earliest', help="Output HDF5 versioning. Use " + "'earliest' for backwards compatibility or 'latest' for " + "performance") args = parser.parse_args() response = input(download_warning) if not args.batch else 'y' @@ -168,10 +169,6 @@ if not os.path.isdir(args.destination): os.mkdir(args.destination) library = openmc.data.DataLibrary() -if args.latest_hdf5: - libver = 'latest' -else: - libver = 'earliest' for name, filenames in sorted(tables.items()): # Convert first temperature for the table @@ -186,7 +183,7 @@ for name, filenames in sorted(tables.items()): # Export HDF5 file h5_file = os.path.join(args.destination, data.name + '.h5') print('Writing {}...'.format(h5_file)) - data.export_to_hdf5(h5_file, 'w', libver=libver) + data.export_to_hdf5(h5_file, 'w', libver=args.libver) # Register with library library.register_file(h5_file) @@ -229,7 +226,7 @@ for name, filenames in sorted(tables.items()): # Export HDF5 file h5_file = os.path.join(args.destination, data.name + '.h5') print('Writing {}...'.format(h5_file)) - data.export_to_hdf5(h5_file, 'w', libver=libver) + data.export_to_hdf5(h5_file, 'w', libver=args.libver) # Register with library library.register_file(h5_file) diff --git a/scripts/openmc-get-nndc-data b/scripts/openmc-get-nndc-data index 43a528b41..e2c1e7ce3 100755 --- a/scripts/openmc-get-nndc-data +++ b/scripts/openmc-get-nndc-data @@ -33,9 +33,10 @@ parser = argparse.ArgumentParser( ) parser.add_argument('-b', '--batch', action='store_true', help='supresses standard in') -parser.add_argument('--latest_hdf5', help='Use latest HDF5 format for ' - 'performance over backwards compatibility', - action='store_true') +parser.add_argument('--libver', choices=['earliest', 'latest'], + default='earliest', help="Output HDF5 versioning. Use " + "'earliest' for backwards compatibility or 'latest' for " + "performance") args = parser.parse_args() @@ -160,8 +161,7 @@ fer_file = os.path.join(data_dir, 'fission_Q_data_endfb71.h5') # Call the ace-to-hdf5 conversion script pwd = os.path.dirname(os.path.realpath(__file__)) ace2hdf5 = os.path.join(pwd, 'openmc-ace-to-hdf5') -args_out = [ace2hdf5, '-d', 'nndc_hdf5', '--fission_energy_release', fer_file] -if args.latest_hdf5: - args_out += ['--latest_hdf5'] -args_out += ace_files -subprocess.call(args_out) +subprocess.call([ace2hdf5, + '-d', 'nndc_hdf5', + '--fission_energy_release', fer_file, + '--libver', args.libver] + ace_files) From b666d9b49ef21004f81e74dea66ff85022dfe4e0 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 18 Dec 2017 00:23:57 -0500 Subject: [PATCH 061/282] Improve references to Core Guidelines in style doc --- docs/source/devguide/styleguide.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/source/devguide/styleguide.rst b/docs/source/devguide/styleguide.rst index a0500965e..89d567394 100644 --- a/docs/source/devguide/styleguide.rst +++ b/docs/source/devguide/styleguide.rst @@ -174,13 +174,16 @@ Follow the `C++ Core Guidelines`_ except when they conflict with another guideline listed here. For convenience, many important guidelines from that list are repeated here. -Conform to the C++11 standard. +Conform to the C++11 standard. Note that this is a significant difference +between our style and the C++ Core Guidelines. Many suggestions in those +Guidelines require C++14. Always use C++-style comments (``//``) as opposed to C-style (``/**/``). (It is more difficult to comment out a large section of code that uses C-style comments.) -Header files should always use include guards with the following style: +Header files should always use include guards with the following style (See +`SF.8 `_: .. code-block:: C++ @@ -191,10 +194,8 @@ Header files should always use include guards with the following style: ... #endif // MODULE_NAME_H -Do not use using-directives e.g. ``using namespace foobar;`` - Do not use C-style casting. Always use the C++-style casts ``static_cast``, -``const_cast``, or ``reinterpret_cast``. +``const_cast``, or ``reinterpret_cast``. (See `ES.49 `_) Naming ------ From e6b5803f709f43e2b06cfa6917cff51a001898f4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 19 Dec 2017 15:58:14 +0700 Subject: [PATCH 062/282] Make filter_strides a property of Tally --- openmc/arithmetic.py | 32 ++----- openmc/filter.py | 210 ++++++++++++------------------------------- openmc/mgxs/mgxs.py | 2 - openmc/statepoint.py | 3 - openmc/tallies.py | 49 +++------- 5 files changed, 71 insertions(+), 225 deletions(-) diff --git a/openmc/arithmetic.py b/openmc/arithmetic.py index 6a781cb5a..fe002f1e3 100644 --- a/openmc/arithmetic.py +++ b/openmc/arithmetic.py @@ -237,9 +237,6 @@ class CrossFilter(object): left / right filters num_bins : Integral The number of filter bins (always 1 if aggregate_filter is defined) - stride : Integral - The number of filter, nuclide and score bins within each of this - crossfilter's bins. """ @@ -250,7 +247,6 @@ class CrossFilter(object): self._type = '({0} {1} {2})'.format(left_type, binary_op, right_type) self._bins = {} - self._stride = None self._left_filter = None self._right_filter = None @@ -314,10 +310,6 @@ class CrossFilter(object): else: return 0 - @property - def stride(self): - return self._stride - @type.setter def type(self, filter_type): if filter_type not in _FILTER_TYPES: @@ -347,10 +339,6 @@ class CrossFilter(object): cv.check_value('binary_op', binary_op, _TALLY_ARITHMETIC_OPS) self._binary_op = binary_op - @stride.setter - def stride(self, stride): - self._stride = stride - def get_bin_index(self, filter_bin): """Returns the index in the CrossFilter for some bin. @@ -611,9 +599,6 @@ class AggregateFilter(object): The filter bins included in the aggregation num_bins : Integral The number of filter bins (always 1 if aggregate_filter is defined) - stride : Integral - The number of filter, nuclide and score bins within each of this - aggregatefilter's bins. """ @@ -622,7 +607,6 @@ class AggregateFilter(object): self._type = '{0}({1})'.format(aggregate_op, aggregate_filter.short_name.lower()) self._bins = None - self._stride = None self._aggregate_filter = None self._aggregate_op = None @@ -684,10 +668,6 @@ class AggregateFilter(object): def num_bins(self): return len(self.bins) if self.aggregate_filter else 0 - @property - def stride(self): - return self._stride - @type.setter def type(self, filter_type): if filter_type not in _FILTER_TYPES: @@ -714,10 +694,6 @@ class AggregateFilter(object): cv.check_value('aggregate_op', aggregate_op, _TALLY_AGGREGATE_OPS) self._aggregate_op = aggregate_op - @stride.setter - def stride(self, stride): - self._stride = stride - def get_bin_index(self, filter_bin): """Returns the index in the AggregateFilter for some bin. @@ -753,7 +729,7 @@ class AggregateFilter(object): else: return self.bins.index(filter_bin) - def get_pandas_dataframe(self, data_size, summary=None, **kwargs): + def get_pandas_dataframe(self, data_size, stride, summary=None, **kwargs): """Builds a Pandas DataFrame for the AggregateFilter's bins. This method constructs a Pandas DataFrame object for the AggregateFilter @@ -762,8 +738,10 @@ class AggregateFilter(object): Parameters ---------- - data_size : Integral + data_size : int The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter summary : None or Summary An optional Summary object to be used to construct columns for distribcell tally filters (default is None). NOTE: This parameter @@ -793,7 +771,7 @@ class AggregateFilter(object): filter_bins[i] = bin # Repeat and tile bins as needed for DataFrame - filter_bins = np.repeat(filter_bins, self.stride) + filter_bins = np.repeat(filter_bins, stride) tile_factor = data_size / len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) diff --git a/openmc/filter.py b/openmc/filter.py index 2b329ef0e..fc133168d 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -86,9 +86,6 @@ class Filter(IDManagerMixin): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @@ -99,7 +96,6 @@ class Filter(IDManagerMixin): self.bins = bins self.id = filter_id self._num_bins = 0 - self._stride = None def __eq__(self, other): if type(self) is not type(other): @@ -194,10 +190,6 @@ class Filter(IDManagerMixin): def num_bins(self): return self._num_bins - @property - def stride(self): - return self._stride - @bins.setter def bins(self, bins): # Format the bins as a 1D numpy array. @@ -214,16 +206,6 @@ class Filter(IDManagerMixin): cv.check_greater_than('filter num_bins', num_bins, 0, equality=True) self._num_bins = num_bins - @stride.setter - def stride(self, stride): - cv.check_type('filter stride', stride, Integral) - if stride < 0: - msg = 'Unable to set stride "{0}" for a "{1}" since it ' \ - 'is a negative value'.format(stride, type(self)) - raise ValueError(msg) - - self._stride = stride - def check_bins(self, bins): """Make sure given bins are valid for this filter. @@ -270,11 +252,7 @@ class Filter(IDManagerMixin): Whether the filter can be merged """ - - if type(self) is not type(other): - return False - - return True + return type(self) is type(other) def merge(self, other): """Merge this filter with another. @@ -402,7 +380,7 @@ class Filter(IDManagerMixin): # Return a 1-tuple of the bin. return (self.bins[bin_index],) - def get_pandas_dataframe(self, data_size, **kwargs): + def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. This method constructs a Pandas DataFrame object for the filter with @@ -411,13 +389,15 @@ class Filter(IDManagerMixin): Parameters ---------- - data_size : Integral + data_size : int The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter Keyword arguments ----------------- paths : bool - Only used for DistirbcellFilter. If True (default), expand + Only used for DistribcellFilter. If True (default), expand distribcell indices into multi-index columns describing the path to that distribcell through the CSG tree. NOTE: This option assumes that all distribcell paths are of the same length and do not have @@ -431,11 +411,6 @@ class Filter(IDManagerMixin): the total number of bins in the corresponding tally, with the filter bin appropriately tiled to map to the corresponding tally bins. - Raises - ------ - ImportError - When Pandas is not installed - See also -------- Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() @@ -444,7 +419,7 @@ class Filter(IDManagerMixin): # Initialize Pandas DataFrame df = pd.DataFrame() - filter_bins = np.repeat(self.bins, self.stride) + filter_bins = np.repeat(self.bins, stride) tile_factor = data_size // len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) df = pd.concat([df, pd.DataFrame( @@ -502,9 +477,6 @@ class UniverseFilter(WithIDFilter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @property @@ -535,9 +507,6 @@ class MaterialFilter(WithIDFilter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @property @@ -568,15 +537,12 @@ class CellFilter(WithIDFilter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @property def bins(self): return self._bins - + @bins.setter def bins(self, bins): self._smart_set_bins(bins, openmc.Cell) @@ -601,15 +567,12 @@ class CellFromFilter(WithIDFilter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @property def bins(self): return self._bins - + @bins.setter def bins(self, bins): self._smart_set_bins(bins, openmc.Cell) @@ -634,9 +597,6 @@ class CellbornFilter(WithIDFilter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @property @@ -668,9 +628,6 @@ class SurfaceFilter(Filter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @property @@ -696,7 +653,7 @@ class SurfaceFilter(Filter): @num_bins.setter def num_bins(self, num_bins): pass - def get_pandas_dataframe(self, data_size, **kwargs): + def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. This method constructs a Pandas DataFrame object for the filter with @@ -705,8 +662,10 @@ class SurfaceFilter(Filter): Parameters ---------- - data_size : Integral + data_size : int The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter Returns ------- @@ -717,11 +676,6 @@ class SurfaceFilter(Filter): the corresponding tally, with the filter bin appropriately tiled to map to the corresponding tally bins. - Raises - ------ - ImportError - When Pandas is not installed - See also -------- Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() @@ -730,7 +684,7 @@ class SurfaceFilter(Filter): # Initialize Pandas DataFrame df = pd.DataFrame() - filter_bins = np.repeat(self.bins, self.stride) + filter_bins = np.repeat(self.bins, stride) tile_factor = data_size // len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) filter_bins = [_CURRENT_NAMES[x] for x in filter_bins] @@ -760,9 +714,6 @@ class MeshFilter(Filter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @@ -854,7 +805,7 @@ class MeshFilter(Filter): y = bin_index - (x * ny) return (x, y) - def get_pandas_dataframe(self, data_size, **kwargs): + def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. This method constructs a Pandas DataFrame object for the filter with @@ -863,8 +814,10 @@ class MeshFilter(Filter): Parameters ---------- - data_size : Integral + data_size : int The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter Returns ------- @@ -875,11 +828,6 @@ class MeshFilter(Filter): corresponding tally, with the filter bin appropriately tiled to map to the corresponding tally bins. - Raises - ------ - ImportError - When Pandas is not installed - See also -------- Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() @@ -906,7 +854,7 @@ class MeshFilter(Filter): # Generate multi-index sub-column for x-axis filter_bins = np.arange(1, nx + 1) - repeat_factor = ny * nz * self.stride + repeat_factor = ny * nz * stride filter_bins = np.repeat(filter_bins, repeat_factor) tile_factor = data_size // len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) @@ -914,7 +862,7 @@ class MeshFilter(Filter): # Generate multi-index sub-column for y-axis filter_bins = np.arange(1, ny + 1) - repeat_factor = nz * self.stride + repeat_factor = nz * stride filter_bins = np.repeat(filter_bins, repeat_factor) tile_factor = data_size // len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) @@ -922,7 +870,7 @@ class MeshFilter(Filter): # Generate multi-index sub-column for z-axis filter_bins = np.arange(1, nz + 1) - repeat_factor = self.stride + repeat_factor = stride filter_bins = np.repeat(filter_bins, repeat_factor) tile_factor = data_size // len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) @@ -952,9 +900,6 @@ class RealFilter(Filter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @@ -1063,9 +1008,6 @@ class EnergyFilter(RealFilter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @@ -1100,7 +1042,7 @@ class EnergyFilter(RealFilter): 'increasing'.format(bins, type(self)) raise ValueError(msg) - def get_pandas_dataframe(self, data_size, **kwargs): + def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. This method constructs a Pandas DataFrame object for the filter with @@ -1109,8 +1051,10 @@ class EnergyFilter(RealFilter): Parameters ---------- - data_size : Integral + data_size : int The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter Returns ------- @@ -1121,11 +1065,6 @@ class EnergyFilter(RealFilter): corresponding tally, with the filter bin appropriately tiled to map to the corresponding tally bins. - Raises - ------ - ImportError - When Pandas is not installed - See also -------- Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() @@ -1136,8 +1075,8 @@ class EnergyFilter(RealFilter): # Extract the lower and upper energy bounds, then repeat and tile # them as necessary to account for other filters. - lo_bins = np.repeat(self.bins[:-1], self.stride) - hi_bins = np.repeat(self.bins[1:], self.stride) + lo_bins = np.repeat(self.bins[:-1], stride) + hi_bins = np.repeat(self.bins[1:], stride) tile_factor = data_size // len(lo_bins) lo_bins = np.tile(lo_bins, tile_factor) hi_bins = np.tile(hi_bins, tile_factor) @@ -1167,9 +1106,6 @@ class EnergyoutFilter(EnergyFilter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @@ -1229,9 +1165,6 @@ class DistribcellFilter(Filter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. paths : list of str The paths traversed through the CSG tree to reach each distribcell instance (for 'distribcell' filters only) @@ -1296,7 +1229,7 @@ class DistribcellFilter(Filter): # the Cell in the Geometry (consecutive integers starting at 0). return filter_bin - def get_pandas_dataframe(self, data_size, **kwargs): + def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. This method constructs a Pandas DataFrame object for the filter with @@ -1305,8 +1238,10 @@ class DistribcellFilter(Filter): Parameters ---------- - data_size : Integral + data_size : int The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter Keyword arguments ----------------- @@ -1331,11 +1266,6 @@ class DistribcellFilter(Filter): of bins in the corresponding tally, with the filter bin appropriately tiled to map to the corresponding tally bins. - Raises - ------ - ImportError - When Pandas is not installed - See also -------- Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() @@ -1418,7 +1348,7 @@ class DistribcellFilter(Filter): # Tile the Multi-index columns for level_key, level_bins in level_dict.items(): - level_bins = np.repeat(level_bins, self.stride) + level_bins = np.repeat(level_bins, stride) tile_factor = data_size // len(level_bins) level_bins = np.tile(level_bins, tile_factor) level_dict[level_key] = level_bins @@ -1434,7 +1364,7 @@ class DistribcellFilter(Filter): # NOTE: This is performed regardless of whether the user # requests Summary geometric information filter_bins = np.arange(self.num_bins) - filter_bins = np.repeat(filter_bins, self.stride) + filter_bins = np.repeat(filter_bins, stride) tile_factor = data_size // len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) df = pd.DataFrame({self.short_name.lower() : filter_bins}) @@ -1474,9 +1404,6 @@ class MuFilter(RealFilter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @@ -1504,7 +1431,7 @@ class MuFilter(RealFilter): 'increasing'.format(bins, type(self)) raise ValueError(msg) - def get_pandas_dataframe(self, data_size, **kwargs): + def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. This method constructs a Pandas DataFrame object for the filter with @@ -1513,8 +1440,10 @@ class MuFilter(RealFilter): Parameters ---------- - data_size : Integral + data_size : int The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter Returns ------- @@ -1525,11 +1454,6 @@ class MuFilter(RealFilter): corresponding tally, with the filter bin appropriately tiled to map to the corresponding tally bins. - Raises - ------ - ImportError - When Pandas is not installed - See also -------- Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() @@ -1540,8 +1464,8 @@ class MuFilter(RealFilter): # Extract the lower and upper energy bounds, then repeat and tile # them as necessary to account for other filters. - lo_bins = np.repeat(self.bins[:-1], self.stride) - hi_bins = np.repeat(self.bins[1:], self.stride) + lo_bins = np.repeat(self.bins[:-1], stride) + hi_bins = np.repeat(self.bins[1:], stride) tile_factor = data_size // len(lo_bins) lo_bins = np.tile(lo_bins, tile_factor) hi_bins = np.tile(hi_bins, tile_factor) @@ -1579,9 +1503,6 @@ class PolarFilter(RealFilter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @@ -1609,7 +1530,7 @@ class PolarFilter(RealFilter): 'increasing'.format(bins, type(self)) raise ValueError(msg) - def get_pandas_dataframe(self, data_size, **kwargs): + def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. This method constructs a Pandas DataFrame object for the filter with @@ -1618,8 +1539,10 @@ class PolarFilter(RealFilter): Parameters ---------- - data_size : Integral + data_size : int The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter Returns ------- @@ -1630,11 +1553,6 @@ class PolarFilter(RealFilter): corresponding tally, with the filter bin appropriately tiled to map to the corresponding tally bins. - Raises - ------ - ImportError - When Pandas is not installed - See also -------- Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() @@ -1645,8 +1563,8 @@ class PolarFilter(RealFilter): # Extract the lower and upper angle bounds, then repeat and tile # them as necessary to account for other filters. - lo_bins = np.repeat(self.bins[:-1], self.stride) - hi_bins = np.repeat(self.bins[1:], self.stride) + lo_bins = np.repeat(self.bins[:-1], stride) + hi_bins = np.repeat(self.bins[1:], stride) tile_factor = data_size // len(lo_bins) lo_bins = np.tile(lo_bins, tile_factor) hi_bins = np.tile(hi_bins, tile_factor) @@ -1684,9 +1602,6 @@ class AzimuthalFilter(RealFilter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @@ -1714,7 +1629,7 @@ class AzimuthalFilter(RealFilter): 'increasing'.format(bins, type(self)) raise ValueError(msg) - def get_pandas_dataframe(self, data_size, paths=True): + def get_pandas_dataframe(self, data_size, stride, paths=True): """Builds a Pandas DataFrame for the Filter's bins. This method constructs a Pandas DataFrame object for the filter with @@ -1723,8 +1638,10 @@ class AzimuthalFilter(RealFilter): Parameters ---------- - data_size : Integral + data_size : int The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter Returns ------- @@ -1735,11 +1652,6 @@ class AzimuthalFilter(RealFilter): corresponding tally, with the filter bin appropriately tiled to map to the corresponding tally bins. - Raises - ------ - ImportError - When Pandas is not installed - See also -------- Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() @@ -1750,8 +1662,8 @@ class AzimuthalFilter(RealFilter): # Extract the lower and upper angle bounds, then repeat and tile # them as necessary to account for other filters. - lo_bins = np.repeat(self.bins[:-1], self.stride) - hi_bins = np.repeat(self.bins[1:], self.stride) + lo_bins = np.repeat(self.bins[:-1], stride) + hi_bins = np.repeat(self.bins[1:], stride) tile_factor = data_size // len(lo_bins) lo_bins = np.tile(lo_bins, tile_factor) hi_bins = np.tile(hi_bins, tile_factor) @@ -1785,9 +1697,6 @@ class DelayedGroupFilter(Filter): Unique identifier for the filter num_bins : Integral The number of filter bins - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @property @@ -1840,9 +1749,6 @@ class EnergyFunctionFilter(Filter): Unique identifier for the filter num_bins : Integral The number of filter bins (always 1 for this filter) - stride : Integral - The number of filter, nuclide and score bins within each of this - filter's bins. """ @@ -1850,7 +1756,6 @@ class EnergyFunctionFilter(Filter): self.energy = energy self.y = y self.id = filter_id - self._stride = None def __eq__(self, other): if type(self) is not type(other): @@ -2006,7 +1911,7 @@ class EnergyFunctionFilter(Filter): """This function is invalid for EnergyFunctionFilters.""" raise RuntimeError('EnergyFunctionFilters have no get_bin() method') - def get_pandas_dataframe(self, data_size, **kwargs): + def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. This method constructs a Pandas DataFrame object for the filter with @@ -2015,8 +1920,10 @@ class EnergyFunctionFilter(Filter): Parameters ---------- - data_size : Integral + data_size : int The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter Returns ------- @@ -2027,11 +1934,6 @@ class EnergyFunctionFilter(Filter): EnergyFunctionFilters. The number of rows in the DataFrame is the same as the total number of bins in the corresponding tally. - Raises - ------ - ImportError - When Pandas is not installed - See also -------- Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() @@ -2052,7 +1954,7 @@ class EnergyFunctionFilter(Filter): # hex characters) of the digest are probably sufficient. out = out[:14] - filter_bins = np.repeat(out, self.stride) + filter_bins = np.repeat(out, stride) tile_factor = data_size // len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) df = pd.concat([df, pd.DataFrame( diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 192f484a5..6a74ee231 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -2751,7 +2751,6 @@ class TransportXS(MGXS): # Switch EnergyoutFilter to EnergyFilter. old_filt = self.tallies['scatter-1'].filters[-1] new_filt = openmc.EnergyFilter(old_filt.bins) - new_filt.stride = old_filt.stride self.tallies['scatter-1'].filters[-1] = new_filt self._rxn_rate_tally = \ @@ -2771,7 +2770,6 @@ class TransportXS(MGXS): # Switch EnergyoutFilter to EnergyFilter. old_filt = self.tallies['scatter-1'].filters[-1] new_filt = openmc.EnergyFilter(old_filt.bins) - new_filt.stride = old_filt.stride self.tallies['scatter-1'].filters[-1] = new_filt # Compute total cross section diff --git a/openmc/statepoint.py b/openmc/statepoint.py index 16d5b5d84..4656e0856 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -415,9 +415,6 @@ class StatePoint(object): tally.scores.append(score) - # Compute and set the filter strides - tally._update_filter_strides() - # Add Tally to the global dictionary of all Tallies tally.sparse = self.sparse self._tallies[tally_id] = tally diff --git a/openmc/tallies.py b/openmc/tallies.py index e049d6983..9f8f7e505 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -78,6 +78,8 @@ class Tally(IDManagerMixin): shape : 3-tuple of int The shape of the tally data array ordered as the number of filter bins, nuclide bins and score bins + filter_strides : list of int + Stride in memory for each filter num_realizations : int Total number of realizations with_summary : bool @@ -818,10 +820,6 @@ class Tally(IDManagerMixin): # Differentiate Tally with a new auto-generated Tally ID merged_tally.id = None - # If the two tallies are equal, simply return copy - if self == other: - return merged_tally - # Create deep copy of other tally to use for array concatenation other_copy = copy.deepcopy(other) @@ -877,9 +875,6 @@ class Tally(IDManagerMixin): else: self._derived = True - # Update filter strides in merged tally - merged_tally._update_filter_strides() - # Concatenate sum arrays if present in both tallies if self.sum is not None and other_copy.sum is not None: self_sum = self.get_reshaped_data(value='sum') @@ -1538,11 +1533,10 @@ class Tally(IDManagerMixin): # Build DataFrame columns for filters if user requested them if filters: - # Append each Filter's DataFrame to the overall DataFrame - for self_filter in self.filters: - filter_df = self_filter.get_pandas_dataframe( - data_size, paths=paths) + for f, stride in zip(self.filters, self.filter_strides): + filter_df = f.get_pandas_dataframe( + data_size, stride, paths=paths) df = pd.concat([df, filter_df], axis=1) # Include DataFrame column for nuclides if user requested it @@ -1867,20 +1861,16 @@ class Tally(IDManagerMixin): new_score = cross_score(self_score, other_score, binary_op) new_tally.scores.append(new_score) - # Update the new tally's filter strides - new_tally._update_filter_strides() - return new_tally - def _update_filter_strides(self): - """Update each filter's stride based on the tally's nuclides and scores - for derived tallies created by tally arithmetic. - """ - + @property + def filter_strides(self): + all_strides = [] stride = self.num_nuclides * self.num_scores for self_filter in reversed(self.filters): - self_filter.stride = stride + all_strides.append(stride) stride *= self_filter.num_bins + return all_strides[::-1] def _align_tally_data(self, other, filter_product, nuclide_product, score_product): @@ -2019,10 +2009,6 @@ class Tally(IDManagerMixin): if other_index != i: other._swap_scores(score, other.scores[i]) - # Update the tallies' filter strides - other._update_filter_strides() - self._update_filter_strides() - data = {} data['self'] = {} data['other'] = {} @@ -2107,9 +2093,6 @@ class Tally(IDManagerMixin): self.filters[filter1_index] = filter2 self.filters[filter2_index] = filter1 - # Update the tally's filter strides - self._update_filter_strides() - # Realign the data for i, (bin1, bin2) in enumerate(product(filter1_bins, filter2_bins)): filter_bins = [(bin1,), (bin2,)] @@ -2861,9 +2844,6 @@ class Tally(IDManagerMixin): find_filter.bins = np.unique(find_filter.bins[bin_indices]) find_filter.num_bins = num_bins - # Update the new tally's filter strides - new_tally._update_filter_strides() - # If original tally was sparse, sparsify the sliced tally new_tally.sparse = self.sparse return new_tally @@ -3010,9 +2990,6 @@ class Tally(IDManagerMixin): else: tally_sum._scores = copy.deepcopy(self.scores) - # Update the tally sum's filter strides - tally_sum._update_filter_strides() - # Reshape condensed data arrays with one dimension for all filters mean = np.reshape(mean, tally_sum.shape) std_dev = np.reshape(std_dev, tally_sum.shape) @@ -3170,9 +3147,6 @@ class Tally(IDManagerMixin): else: tally_avg._scores = copy.deepcopy(self.scores) - # Update the tally avg's filter strides - tally_avg._update_filter_strides() - # Reshape condensed data arrays with one dimension for all filters mean = np.reshape(mean, tally_avg.shape) std_dev = np.reshape(std_dev, tally_avg.shape) @@ -3246,9 +3220,6 @@ class Tally(IDManagerMixin): new_tally._std_dev = np.zeros(new_tally.shape, dtype=np.float64) new_tally._std_dev[diag_indices, :, :] = self.std_dev - # Update the new tally's filter strides - new_tally._update_filter_strides() - # If original tally was sparse, sparsify the diagonalized tally new_tally.sparse = self.sparse return new_tally From 2ef1c23d5a5d04ee733d28c1d7540c5dace8aad1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 19 Dec 2017 21:54:08 +0700 Subject: [PATCH 063/282] Make Filter.num_bins a (usually) computed property --- openmc/filter.py | 74 +++++++++++++++++++---------------------------- openmc/tallies.py | 17 ++++------- 2 files changed, 34 insertions(+), 57 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index fc133168d..260354d19 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -2,8 +2,10 @@ from __future__ import division from abc import ABCMeta from collections import Iterable, OrderedDict import copy +from functools import reduce import hashlib from numbers import Real, Integral +import operator from xml.etree import ElementTree as ET from six import add_metaclass @@ -95,7 +97,6 @@ class Filter(IDManagerMixin): def __init__(self, bins, filter_id=None): self.bins = bins self.id = filter_id - self._num_bins = 0 def __eq__(self, other): if type(self) is not type(other): @@ -170,7 +171,7 @@ class Filter(IDManagerMixin): # there is no overriden from_hdf5 method. Pass the bins to __init__. if group['type'].value.decode() == cls.short_name.lower(): out = cls(group['bins'].value, filter_id) - out.num_bins = group['n_bins'].value + out._num_bins = group['n_bins'].value return out # Search through all subclasses and find the one matching the HDF5 @@ -188,7 +189,7 @@ class Filter(IDManagerMixin): @property def num_bins(self): - return self._num_bins + return len(self.bins) @bins.setter def bins(self, bins): @@ -200,12 +201,6 @@ class Filter(IDManagerMixin): self._bins = bins - @num_bins.setter - def num_bins(self, num_bins): - cv.check_type('filter num_bins', num_bins, Integral) - cv.check_greater_than('filter num_bins', num_bins, 0, equality=True) - self._num_bins = num_bins - def check_bins(self, bins): """Make sure given bins are valid for this filter. @@ -430,17 +425,6 @@ class Filter(IDManagerMixin): class WithIDFilter(Filter): """Abstract parent for filters of types with ids (Cell, Material, etc.).""" - @property - def num_bins(self): - return len(self.bins) - - # Since num_bins property is declared, also need a num_bins.setter, but - # we don't want it to do anything since num_bins is completely determined - # by len(self.bins). We also don't want to raise an error because that - # makes importing from HDF5 more complicated. - @num_bins.setter - def num_bins(self, num_bins): pass - def _smart_set_bins(self, bins, bin_type): # Format the bins as a 1D numpy array. bins = np.atleast_1d(bins) @@ -634,10 +618,6 @@ class SurfaceFilter(Filter): def bins(self): return self._bins - @property - def num_bins(self): - return len(self.bins) - @bins.setter def bins(self, bins): # Format the bins as a 1D numpy array. @@ -650,8 +630,12 @@ class SurfaceFilter(Filter): self._bins = bins - @num_bins.setter - def num_bins(self, num_bins): pass + @property + def num_bins(self): + # Need to handle number of bins carefully -- for surface current + # tallies, the number of bins depends on the mesh, which we don't have a + # reference to in this filter + return self._num_bins def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. @@ -737,7 +721,7 @@ class MeshFilter(Filter): filter_id = int(group.name.split('/')[-1].lstrip('filter ')) out = cls(mesh_obj, filter_id) - out.num_bins = group['n_bins'].value + out._num_bins = group['n_bins'].value return out @@ -751,6 +735,13 @@ class MeshFilter(Filter): self._mesh = mesh self.bins = mesh.id + @property + def num_bins(self): + try: + return self._num_bins + except AttributeError: + return reduce(operator.mul, self.mesh.dimension) + def check_bins(self, bins): if not len(bins) == 1: msg = 'Unable to add bins "{0}" to a MeshFilter since ' \ @@ -898,7 +889,7 @@ class RealFilter(Filter): A grid of bin values. id : int Unique identifier for the filter - num_bins : Integral + num_bins : int The number of filter bins """ @@ -915,12 +906,6 @@ class RealFilter(Filter): def num_bins(self): return len(self.bins) - 1 - @num_bins.setter - def num_bins(self, num_bins): - cv.check_type('filter num_bins', num_bins, Integral) - cv.check_greater_than('filter num_bins', num_bins, 0, equality=True) - self._num_bins = num_bins - def can_merge(self, other): if type(self) is not type(other): return False @@ -1006,7 +991,7 @@ class EnergyFilter(RealFilter): A grid of energy values in eV. id : int Unique identifier for the filter - num_bins : Integral + num_bins : int The number of filter bins """ @@ -1104,7 +1089,7 @@ class EnergyoutFilter(EnergyFilter): A grid of energy values in eV. id : int Unique identifier for the filter - num_bins : Integral + num_bins : int The number of filter bins """ @@ -1163,7 +1148,7 @@ class DistribcellFilter(Filter): An iterable with one element---the ID of the distributed Cell. id : int Unique identifier for the filter - num_bins : Integral + num_bins : int The number of filter bins paths : list of str The paths traversed through the CSG tree to reach each distribcell @@ -1185,7 +1170,7 @@ class DistribcellFilter(Filter): filter_id = int(group.name.split('/')[-1].lstrip('filter ')) out = cls(group['bins'].value, filter_id) - out.num_bins = group['n_bins'].value + out._num_bins = group['n_bins'].value return out @@ -1193,6 +1178,12 @@ class DistribcellFilter(Filter): def bins(self): return self._bins + @property + def num_bins(self): + # Need to handle number of bins carefully -- for distribcell tallies, we + # need to know how many instances of the cell there are + return self._num_bins + @property def paths(self): return self._paths @@ -1703,10 +1694,6 @@ class DelayedGroupFilter(Filter): def bins(self): return self._bins - @property - def num_bins(self): - return len(self.bins) - @bins.setter def bins(self, bins): # Format the bins as a 1D numpy array. @@ -1719,9 +1706,6 @@ class DelayedGroupFilter(Filter): self._bins = bins - @num_bins.setter - def num_bins(self, num_bins): pass - class EnergyFunctionFilter(Filter): """Multiplies tally scores by an arbitrary function of incident energy. diff --git a/openmc/tallies.py b/openmc/tallies.py index 9f8f7e505..52e7a03c5 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3,9 +3,10 @@ from __future__ import division from collections import Iterable, MutableSequence import copy import re -from functools import partial +from functools import partial, reduce from itertools import product from numbers import Integral, Real +import operator import warnings from xml.etree import ElementTree as ET @@ -185,19 +186,11 @@ class Tally(IDManagerMixin): @property def num_filter_bins(self): - num_bins = 1 - - for self_filter in self.filters: - num_bins *= self_filter.num_bins - - return num_bins + return reduce(operator.mul, (f.num_bins for f in self.filters), 1) @property def num_bins(self): - num_bins = self.num_filter_bins - num_bins *= self.num_nuclides - num_bins *= self.num_scores - return num_bins + return self.num_filter_bins * self.num_nuclides * self.num_scores @property def shape(self): @@ -2842,7 +2835,7 @@ class Tally(IDManagerMixin): num_bins += 1 find_filter.bins = np.unique(find_filter.bins[bin_indices]) - find_filter.num_bins = num_bins + find_filter._num_bins = num_bins # If original tally was sparse, sparsify the sliced tally new_tally.sparse = self.sparse From 55f7260b2a2fe3b2a3e03786bd65f6da52298410 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Dec 2017 10:12:07 +0700 Subject: [PATCH 064/282] Remove _smart_set_bins in favor of overriding Filter.bins.setter --- openmc/filter.py | 78 +++++++++++++---------------------------------- openmc/tallies.py | 40 ++++++++++++++---------- 2 files changed, 45 insertions(+), 73 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index 260354d19..21bb64f8d 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -14,7 +14,10 @@ import pandas as pd import openmc import openmc.checkvalue as cv +from .cell import Cell +from .material import Material from .mixin import IDManagerMixin +from .universe import Universe _FILTER_TYPES = ['universe', 'material', 'cell', 'cellborn', 'surface', @@ -170,7 +173,7 @@ class Filter(IDManagerMixin): # If the HDF5 'type' variable matches this class's short_name, then # there is no overriden from_hdf5 method. Pass the bins to __init__. if group['type'].value.decode() == cls.short_name.lower(): - out = cls(group['bins'].value, filter_id) + out = cls(group['bins'].value, filter_id=filter_id) out._num_bins = group['n_bins'].value return out @@ -425,12 +428,15 @@ class Filter(IDManagerMixin): class WithIDFilter(Filter): """Abstract parent for filters of types with ids (Cell, Material, etc.).""" - def _smart_set_bins(self, bins, bin_type): + + @Filter.bins.setter + def bins(self, bins): # Format the bins as a 1D numpy array. bins = np.atleast_1d(bins) # Check the bin values. - cv.check_iterable_type('filter bins', bins, (Integral, bin_type)) + cv.check_iterable_type('filter bins', bins, + (Integral, self.expected_type)) for edge in bins: if isinstance(edge, Integral): cv.check_greater_than('filter bin', edge, 0, equality=True) @@ -463,13 +469,7 @@ class UniverseFilter(WithIDFilter): The number of filter bins """ - @property - def bins(self): - return self._bins - - @bins.setter - def bins(self, bins): - self._smart_set_bins(bins, openmc.Universe) + expected_type = Universe class MaterialFilter(WithIDFilter): @@ -493,13 +493,7 @@ class MaterialFilter(WithIDFilter): The number of filter bins """ - @property - def bins(self): - return self._bins - - @bins.setter - def bins(self, bins): - self._smart_set_bins(bins, openmc.Material) + expected_type = Material class CellFilter(WithIDFilter): @@ -523,13 +517,7 @@ class CellFilter(WithIDFilter): The number of filter bins """ - @property - def bins(self): - return self._bins - - @bins.setter - def bins(self, bins): - self._smart_set_bins(bins, openmc.Cell) + expected_type = Cell class CellFromFilter(WithIDFilter): @@ -553,13 +541,7 @@ class CellFromFilter(WithIDFilter): The number of filter bins """ - @property - def bins(self): - return self._bins - - @bins.setter - def bins(self, bins): - self._smart_set_bins(bins, openmc.Cell) + expected_type = Cell class CellbornFilter(WithIDFilter): @@ -583,13 +565,7 @@ class CellbornFilter(WithIDFilter): The number of filter bins """ - @property - def bins(self): - return self._bins - - @bins.setter - def bins(self, bins): - self._smart_set_bins(bins, openmc.Cell) + expected_type = Cell class SurfaceFilter(Filter): @@ -614,11 +590,7 @@ class SurfaceFilter(Filter): The number of filter bins """ - @property - def bins(self): - return self._bins - - @bins.setter + @Filter.bins.setter def bins(self, bins): # Format the bins as a 1D numpy array. bins = np.atleast_1d(bins) @@ -720,7 +692,7 @@ class MeshFilter(Filter): mesh_obj = kwargs['meshes'][mesh_id] filter_id = int(group.name.split('/')[-1].lstrip('filter ')) - out = cls(mesh_obj, filter_id) + out = cls(mesh_obj, filter_id=filter_id) out._num_bins = group['n_bins'].value return out @@ -1169,15 +1141,11 @@ class DistribcellFilter(Filter): filter_id = int(group.name.split('/')[-1].lstrip('filter ')) - out = cls(group['bins'].value, filter_id) + out = cls(group['bins'].value, filter_id=filter_id) out._num_bins = group['n_bins'].value return out - @property - def bins(self): - return self._bins - @property def num_bins(self): # Need to handle number of bins carefully -- for distribcell tallies, we @@ -1188,7 +1156,7 @@ class DistribcellFilter(Filter): def paths(self): return self._paths - @bins.setter + @Filter.bins.setter def bins(self, bins): # Format the bins as a 1D numpy array. bins = np.atleast_1d(bins) @@ -1690,11 +1658,7 @@ class DelayedGroupFilter(Filter): The number of filter bins """ - @property - def bins(self): - return self._bins - - @bins.setter + @Filter.bins.setter def bins(self, bins): # Format the bins as a 1D numpy array. bins = np.atleast_1d(bins) @@ -1799,7 +1763,7 @@ class EnergyFunctionFilter(Filter): y = group['y'].value filter_id = int(group.name.split('/')[-1].lstrip('filter ')) - return cls(energy, y, filter_id) + return cls(energy, y, filter_id=filter_id) @classmethod def from_tabulated1d(cls, tab1d): @@ -1836,7 +1800,7 @@ class EnergyFunctionFilter(Filter): @property def bins(self): - raise RuntimeError('EnergyFunctionFilters have no bins.') + raise AttributeError('EnergyFunctionFilters have no bins.') @property def num_bins(self): diff --git a/openmc/tallies.py b/openmc/tallies.py index 52e7a03c5..235ad2472 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -237,10 +237,10 @@ class Tally(IDManagerMixin): # Convert NumPy arrays to SciPy sparse LIL matrices if self.sparse: - self._sum = \ - sps.lil_matrix(self._sum.flatten(), self._sum.shape) - self._sum_sq = \ - sps.lil_matrix(self._sum_sq.flatten(), self._sum_sq.shape) + self._sum = sps.lil_matrix(self._sum.flatten(), + self._sum.shape) + self._sum_sq = sps.lil_matrix(self._sum_sq.flatten(), + self._sum_sq.shape) # Indicate that Tally results have been read self._results_read = True @@ -2817,25 +2817,33 @@ class Tally(IDManagerMixin): # Remove and/or reorder filter bins to user specifications bin_indices = [] - num_bins = 0 for filter_bin in filter_bins[i]: bin_index = find_filter.get_bin_index(filter_bin) - if filter_type in [openmc.EnergyFilter, - openmc.EnergyoutFilter]: - bin_indices.extend([bin_index]) + if issubclass(filter_type, openmc.RealFilter): bin_indices.extend([bin_index, bin_index+1]) - num_bins += 1 - elif filter_type in [openmc.DistribcellFilter, - openmc.MeshFilter]: - bin_indices = [0] - num_bins = find_filter.num_bins else: bin_indices.append(bin_index) - num_bins += 1 - find_filter.bins = np.unique(find_filter.bins[bin_indices]) - find_filter._num_bins = num_bins + # 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 number of bins manually for mesh/distribcell filters + if filter_type in (openmc.DistribcellFilter, openmc.MeshFilter): + new_filter._num_bins = find_filter._num_bins + + # Replace existing filter with new one + for j, test_filter in enumerate(new_tally.filters): + if isinstance(test_filter, filter_type): + new_tally.filters[j] = new_filter # If original tally was sparse, sparsify the sliced tally new_tally.sparse = self.sparse From d4f366cbd40d05f0981ef890ed0e61ae2b3d91f4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 11 Dec 2017 14:02:45 +0700 Subject: [PATCH 065/282] Move particle restart writing / lost particle handling to particle_header --- CMakeLists.txt | 1 - src/api.F90 | 1 + src/geometry.F90 | 62 ++++------------ src/particle_header.F90 | 125 +++++++++++++++++++++++++++++---- src/particle_restart_write.F90 | 74 ------------------- src/physics.F90 | 11 ++- src/physics_mg.F90 | 3 +- src/simulation_header.F90 | 2 +- 8 files changed, 132 insertions(+), 147 deletions(-) delete mode 100644 src/particle_restart_write.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ca62b3e5..c5150745b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -377,7 +377,6 @@ set(LIBOPENMC_FORTRAN_SRC src/output.F90 src/particle_header.F90 src/particle_restart.F90 - src/particle_restart_write.F90 src/physics_common.F90 src/physics.F90 src/physics_mg.F90 diff --git a/src/api.F90 b/src/api.F90 index 4538bbf3e..326b755b6 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -112,6 +112,7 @@ contains legendre_to_tabular = .true. legendre_to_tabular_points = 33 n_batch_interval = 1 + n_lost_particles = 0 n_particles = 0 n_source_points = 0 n_state_points = 0 diff --git a/src/geometry.F90 b/src/geometry.F90 index 1cc27e09e..1d99273de 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -5,7 +5,6 @@ module geometry use geometry_header use output, only: write_message use particle_header, only: LocalCoord, Particle - use particle_restart_write, only: write_particle_restart use simulation_header use settings use surface_header @@ -357,7 +356,7 @@ contains else ! Particle is outside the lattice. if (lat % outer == NO_OUTER_UNIVERSE) then - call handle_lost_particle(p, "Particle " // trim(to_str(p %id)) & + call p % mark_as_lost("Particle " // trim(to_str(p %id)) & // " is outside lattice " // trim(to_str(lat % id)) & // " but the lattice has no defined outer universe.") return @@ -440,8 +439,8 @@ contains ! Do not handle reflective boundary conditions on lower universes if (p % n_coord /= 1) then - call handle_lost_particle(p, "Cannot reflect particle " & - &// trim(to_str(p % id)) // " off surface in a lower universe.") + call p % mark_as_lost("Cannot reflect particle " & + // trim(to_str(p % id)) // " off surface in a lower universe.") return end if @@ -477,8 +476,8 @@ contains p % n_coord = 1 call find_cell(p, found) if (.not. found) then - call handle_lost_particle(p, "Couldn't find particle after reflecting& - & from surface " // trim(to_str(surf%id)) // ".") + call p % mark_as_lost("Couldn't find particle after reflecting& + & from surface " // trim(to_str(surf % id)) // ".") return end if @@ -497,7 +496,7 @@ contains ! Do not handle periodic boundary conditions on lower universes if (p % n_coord /= 1) then - call handle_lost_particle(p, "Cannot transfer particle " & + call p % mark_as_lost("Cannot transfer particle " & // trim(to_str(p % id)) // " across surface in a lower universe.& & Boundary conditions must be applied to universe 0.") return @@ -584,8 +583,8 @@ contains p % n_coord = 1 call find_cell(p, found) if (.not. found) then - call handle_lost_particle(p, "Couldn't find particle after hitting & - &periodic boundary on surface " // trim(to_str(surf%id)) // ".") + call p % mark_as_lost("Couldn't find particle after hitting & + &periodic boundary on surface " // trim(to_str(surf % id)) // ".") return end if @@ -641,8 +640,8 @@ contains ! undefined region in the geometry. if (.not. found) then - call handle_lost_particle(p, "After particle " // trim(to_str(p % id)) & - // " crossed surface " // trim(to_str(surf%id)) & + call p % mark_as_lost("After particle " // trim(to_str(p % id)) & + // " crossed surface " // trim(to_str(surf % id)) & // " it could not be located in any cell and it did not leak.") return end if @@ -690,7 +689,7 @@ contains call find_cell(p, found) if (.not. found) then if (p % alive) then ! Particle may have been killed in find_cell - call handle_lost_particle(p, "Could not locate particle " & + call p % mark_as_lost("Could not locate particle " & // trim(to_str(p % id)) // " after crossing a lattice boundary.") return end if @@ -713,9 +712,8 @@ contains ! Search for particle call find_cell(p, found) if (.not. found) then - call handle_lost_particle(p, "Could not locate particle " & - // trim(to_str(p % id)) & - // " after crossing a lattice boundary.") + call p % mark_as_lost("Could not locate particle " // & + trim(to_str(p % id)) // " after crossing a lattice boundary.") return end if end if @@ -992,7 +990,7 @@ contains end select LAT_TYPE if (d_lat < ZERO) then - call handle_lost_particle(p, "Particle " // trim(to_str(p % id)) & + call p % mark_as_lost("Particle " // trim(to_str(p % id)) & //" had a negative distance to a lattice boundary. d = " & //trim(to_str(d_lat))) end if @@ -1093,38 +1091,6 @@ contains end subroutine neighbor_lists -!=============================================================================== -! HANDLE_LOST_PARTICLE -!=============================================================================== - - subroutine handle_lost_particle(p, message) - - type(Particle), intent(inout) :: p - character(*) :: message - - integer(8) :: tot_n_particles - - ! Print warning and write lost particle file - call warning(message) - call write_particle_restart(p) - - ! Increment number of lost particles - p % alive = .false. -!$omp atomic - n_lost_particles = n_lost_particles + 1 - - ! Count the total number of simulated particles (on this processor) - tot_n_particles = n_batches * gen_per_batch * work - - ! Abort the simulation if the maximum number of lost particles has been - ! reached - if (n_lost_particles >= MAX_LOST_PARTICLES .and. & - n_lost_particles >= REL_MAX_LOST_PARTICLES * tot_n_particles) then - call fatal_error("Maximum number of lost particles has been reached.") - end if - - end subroutine handle_lost_particle - !=============================================================================== ! CALC_OFFSETS calculates and stores the offsets in all fill cells. This ! routine is called once upon initialization. diff --git a/src/particle_header.F90 b/src/particle_header.F90 index bda7f03b4..66b50c134 100644 --- a/src/particle_header.F90 +++ b/src/particle_header.F90 @@ -1,20 +1,27 @@ module particle_header - use bank_header, only: Bank - use constants, only: NEUTRON, ONE, NONE, ZERO, MAX_SECONDARY, & - MAX_DELAYED_GROUPS, ERROR_REAL - use error, only: fatal_error + use hdf5, only: HID_T + + use bank_header, only: Bank, source_bank + use constants + use error, only: fatal_error, warning use geometry_header, only: root_universe + use hdf5_interface + use settings + use simulation_header + use string, only: to_str implicit none + private + !=============================================================================== ! LOCALCOORD describes the location of a particle local to a single ! universe. When the geometry consists of nested universes, a particle will have ! a list of coordinates in each level !=============================================================================== - type LocalCoord + type, public :: LocalCoord ! Indices in various arrays for this level integer :: cell = NONE @@ -39,7 +46,7 @@ module particle_header ! geometry !=============================================================================== - type Particle + type, public :: Particle ! Basic data integer(8) :: id ! Unique ID integer :: type ! Particle type (n, p, e, etc) @@ -107,10 +114,12 @@ module particle_header type(Bank) :: secondary_bank(MAX_SECONDARY) contains - procedure :: initialize => initialize_particle - procedure :: clear => clear_particle - procedure :: initialize_from_source + procedure :: clear procedure :: create_secondary + procedure :: initialize + procedure :: initialize_from_source + procedure :: mark_as_lost + procedure :: write_restart end type Particle contains @@ -120,7 +129,7 @@ contains ! bank !=============================================================================== - subroutine initialize_particle(this) + subroutine initialize(this) class(Particle) :: this @@ -154,23 +163,22 @@ contains this % n_coord = 1 this % last_n_coord = 1 - end subroutine initialize_particle + end subroutine initialize !=============================================================================== ! CLEAR_PARTICLE resets all coordinate levels for the particle !=============================================================================== - subroutine clear_particle(this) - + subroutine clear(this) class(Particle) :: this + integer :: i ! remove any coordinate levels do i = 1, MAX_COORD call this % coord(i) % reset() end do - - end subroutine clear_particle + end subroutine clear !=============================================================================== ! RESET_COORD clears data from a single coordinate level @@ -255,4 +263,91 @@ contains end subroutine create_secondary +!=============================================================================== +! MARK_AS_LOST +!=============================================================================== + + subroutine mark_as_lost(this, message) + class(Particle), intent(inout) :: this + character(*) :: message + + integer(8) :: tot_n_particles + + ! Print warning and write lost particle file + call warning(message) + call this % write_restart() + + ! Increment number of lost particles + this % alive = .false. +!$omp atomic + n_lost_particles = n_lost_particles + 1 + + ! Count the total number of simulated particles (on this processor) + tot_n_particles = current_batch * gen_per_batch * work + + ! Abort the simulation if the maximum number of lost particles has been + ! reached + if (n_lost_particles >= MAX_LOST_PARTICLES .and. & + n_lost_particles >= REL_MAX_LOST_PARTICLES * tot_n_particles) then + call fatal_error("Maximum number of lost particles has been reached.") + end if + + end subroutine mark_as_lost + +!=============================================================================== +! WRITE_RESTART creates a particle restart file +!=============================================================================== + + subroutine write_restart(this) + class(Particle), intent(in) :: this + + integer(HID_T) :: file_id + character(MAX_FILE_LEN) :: filename + + ! Dont write another restart file if in particle restart mode + if (run_mode == MODE_PARTICLE) return + + ! Set up file name + filename = trim(path_output) // 'particle_' // trim(to_str(current_batch)) & + // '_' // trim(to_str(this % id)) // '.h5' + +!$omp critical (WriteParticleRestart) + ! Create file + file_id = file_create(filename) + + associate (src => source_bank(current_work)) + ! Write filetype and version info + call write_attribute(file_id, 'filetype', 'particle restart') + call write_attribute(file_id, 'version', VERSION_PARTICLE_RESTART) + call write_attribute(file_id, "openmc_version", VERSION) +#ifdef GIT_SHA1 + call write_attribute(file_id, "git_sha1", GIT_SHA1) +#endif + + ! Write data to file + call write_dataset(file_id, 'current_batch', current_batch) + call write_dataset(file_id, 'generations_per_batch', gen_per_batch) + call write_dataset(file_id, 'current_generation', current_gen) + call write_dataset(file_id, 'n_particles', n_particles) + select case(run_mode) + case (MODE_FIXEDSOURCE) + call write_dataset(file_id, 'run_mode', 'fixed source') + case (MODE_EIGENVALUE) + call write_dataset(file_id, 'run_mode', 'eigenvalue') + case (MODE_PARTICLE) + call write_dataset(file_id, 'run_mode', 'particle restart') + end select + call write_dataset(file_id, 'id', this % id) + call write_dataset(file_id, 'weight', src % wgt) + call write_dataset(file_id, 'energy', src % E) + call write_dataset(file_id, 'xyz', src % xyz) + call write_dataset(file_id, 'uvw', src % uvw) + end associate + + ! Close file + call file_close(file_id) +!$omp end critical (WriteParticleRestart) + + end subroutine write_restart + end module particle_header diff --git a/src/particle_restart_write.F90 b/src/particle_restart_write.F90 deleted file mode 100644 index 5addf6056..000000000 --- a/src/particle_restart_write.F90 +++ /dev/null @@ -1,74 +0,0 @@ -module particle_restart_write - - use bank_header, only: Bank, source_bank - use hdf5_interface - use particle_header, only: Particle - use settings - use simulation_header - use string, only: to_str - - use hdf5 - - implicit none - private - public :: write_particle_restart - -contains - -!=============================================================================== -! WRITE_PARTICLE_RESTART is the main routine that writes out the particle file -!=============================================================================== - - subroutine write_particle_restart(p) - type(Particle), intent(in) :: p - - integer(HID_T) :: file_id - character(MAX_FILE_LEN) :: filename - - ! Dont write another restart file if in particle restart mode - if (run_mode == MODE_PARTICLE) return - - ! Set up file name - filename = trim(path_output) // 'particle_' // trim(to_str(current_batch)) & - // '_' // trim(to_str(p%id)) // '.h5' - -!$omp critical (WriteParticleRestart) - ! Create file - file_id = file_create(filename) - - associate (src => source_bank(current_work)) - ! Write filetype and version info - call write_attribute(file_id, 'filetype', 'particle restart') - call write_attribute(file_id, 'version', VERSION_PARTICLE_RESTART) - call write_attribute(file_id, "openmc_version", VERSION) -#ifdef GIT_SHA1 - call write_attribute(file_id, "git_sha1", GIT_SHA1) -#endif - - ! Write data to file - call write_dataset(file_id, 'current_batch', current_batch) - call write_dataset(file_id, 'generations_per_batch', gen_per_batch) - call write_dataset(file_id, 'current_generation', current_gen) - call write_dataset(file_id, 'n_particles', n_particles) - select case(run_mode) - case (MODE_FIXEDSOURCE) - call write_dataset(file_id, 'run_mode', 'fixed source') - case (MODE_EIGENVALUE) - call write_dataset(file_id, 'run_mode', 'eigenvalue') - case (MODE_PARTICLE) - call write_dataset(file_id, 'run_mode', 'particle restart') - end select - call write_dataset(file_id, 'id', p%id) - call write_dataset(file_id, 'weight', src%wgt) - call write_dataset(file_id, 'energy', src%E) - call write_dataset(file_id, 'xyz', src%xyz) - call write_dataset(file_id, 'uvw', src%uvw) - end associate - - ! Close file - call file_close(file_id) -!$omp end critical (WriteParticleRestart) - - end subroutine write_particle_restart - -end module particle_restart_write diff --git a/src/physics.F90 b/src/physics.F90 index fd6a0087c..96dabd542 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -12,7 +12,6 @@ module physics use nuclide_header use output, only: write_message use particle_header, only: Particle - use particle_restart_write, only: write_particle_restart use physics_common use random_lcg, only: prn, advance_prn_seed, prn_set_stream use reaction_header, only: Reaction @@ -172,7 +171,7 @@ contains ! Check to make sure that a nuclide was sampled if (i_nuc_mat > mat % n_nuclides) then - call write_particle_restart(p) + call p % write_restart() call fatal_error("Did not sample any nuclide during collision.") end if @@ -384,7 +383,7 @@ contains ! Check to make sure inelastic scattering reaction sampled if (i > size(nuc % reactions)) then - call write_particle_restart(p) + call p % write_restart() call fatal_error("Did not sample any reaction for nuclide " & &// trim(nuc % name)) end if @@ -1098,7 +1097,7 @@ contains ! Determine indices on ufs mesh for current location call m % get_bin(p % coord(1) % xyz, mesh_bin) if (mesh_bin == NO_BIN_FOUND) then - call write_particle_restart(p) + call p % write_restart() call fatal_error("Source site outside UFS mesh!") end if @@ -1251,7 +1250,7 @@ contains ! check for large number of resamples n_sample = n_sample + 1 if (n_sample == MAX_SAMPLE) then - ! call write_particle_restart(p) + ! call p % write_restart() call fatal_error("Resampled energy distribution maximum number of " & // "times for nuclide " // nuc % name) end if @@ -1275,7 +1274,7 @@ contains ! check for large number of resamples n_sample = n_sample + 1 if (n_sample == MAX_SAMPLE) then - ! call write_particle_restart(p) + ! call p % write_restart() call fatal_error("Resampled energy distribution maximum number of " & // "times for nuclide " // nuc % name) end if diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index 42094f6d0..53a28cd4d 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -13,7 +13,6 @@ module physics_mg use nuclide_header, only: material_xs use output, only: write_message use particle_header, only: Particle - use particle_restart_write, only: write_particle_restart use physics_common use random_lcg, only: prn use scattdata_header @@ -196,7 +195,7 @@ contains call m % get_bin(p % coord(1) % xyz, mesh_bin) if (mesh_bin == NO_BIN_FOUND) then - call write_particle_restart(p) + call p % write_restart() call fatal_error("Source site outside UFS mesh!") end if diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index 5041f0793..825b88129 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -13,7 +13,7 @@ module simulation_header ! GEOMETRY-RELATED VARIABLES ! Number of lost particles - integer :: n_lost_particles + integer :: n_lost_particles = 0 real(8) :: log_spacing ! spacing on logarithmic grid From c771d0f0ae28c251112781249c4512eb51c77c07 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 11 Dec 2017 14:04:42 +0700 Subject: [PATCH 066/282] Rearrange type-bound procedures in particle_header --- src/particle_header.F90 | 129 ++++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/src/particle_header.F90 b/src/particle_header.F90 index 66b50c134..552f9b042 100644 --- a/src/particle_header.F90 +++ b/src/particle_header.F90 @@ -125,8 +125,70 @@ module particle_header contains !=============================================================================== -! INITIALIZE_PARTICLE sets default attributes for a particle from the source -! bank +! RESET_COORD clears data from a single coordinate level +!=============================================================================== + + elemental subroutine reset_coord(this) + class(LocalCoord), intent(inout) :: this + + this % cell = NONE + this % universe = NONE + this % lattice = NONE + this % lattice_x = NONE + this % lattice_y = NONE + this % lattice_z = NONE + this % rotated = .false. + + end subroutine reset_coord + +!=============================================================================== +! CLEAR_PARTICLE resets all coordinate levels for the particle +!=============================================================================== + + subroutine clear(this) + class(Particle) :: this + + integer :: i + + ! remove any coordinate levels + do i = 1, MAX_COORD + call this % coord(i) % reset() + end do + end subroutine clear + +!=============================================================================== +! CREATE_SECONDARY stores the current phase space attributes of the particle in +! the secondary bank and increments the number of sites in the secondary bank. +!=============================================================================== + + subroutine create_secondary(this, uvw, type, run_CE) + class(Particle), intent(inout) :: this + real(8), intent(in) :: uvw(3) + integer, intent(in) :: type + logical, intent(in) :: run_CE + + integer(8) :: n + + ! Check to make sure that the hard-limit on secondary particles is not + ! exceeded. + if (this % n_secondary == MAX_SECONDARY) then + call fatal_error("Too many secondary particles created.") + end if + + n = this % n_secondary + 1 + this % secondary_bank(n) % wgt = this % wgt + this % secondary_bank(n) % xyz(:) = this % coord(1) % xyz + this % secondary_bank(n) % uvw(:) = uvw + this % n_secondary = n + this % secondary_bank(this % n_secondary) % E = this % E + if (.not. run_CE) then + this % secondary_bank(this % n_secondary) % E = real(this % g, 8) + end if + + end subroutine create_secondary + +!=============================================================================== +! INITIALIZE sets default attributes for a particle from the source bank !=============================================================================== subroutine initialize(this) @@ -165,38 +227,6 @@ contains end subroutine initialize -!=============================================================================== -! CLEAR_PARTICLE resets all coordinate levels for the particle -!=============================================================================== - - subroutine clear(this) - class(Particle) :: this - - integer :: i - - ! remove any coordinate levels - do i = 1, MAX_COORD - call this % coord(i) % reset() - end do - end subroutine clear - -!=============================================================================== -! RESET_COORD clears data from a single coordinate level -!=============================================================================== - - elemental subroutine reset_coord(this) - class(LocalCoord), intent(inout) :: this - - this % cell = NONE - this % universe = NONE - this % lattice = NONE - this % lattice_x = NONE - this % lattice_y = NONE - this % lattice_z = NONE - this % rotated = .false. - - end subroutine reset_coord - !=============================================================================== ! INITIALIZE_FROM_SOURCE initializes a particle from data stored in a source ! site. The source site may have been produced from an external source, from @@ -232,37 +262,6 @@ contains end subroutine initialize_from_source -!=============================================================================== -! CREATE_SECONDARY stores the current phase space attributes of the particle in -! the secondary bank and increments the number of sites in the secondary bank. -!=============================================================================== - - subroutine create_secondary(this, uvw, type, run_CE) - class(Particle), intent(inout) :: this - real(8), intent(in) :: uvw(3) - integer, intent(in) :: type - logical, intent(in) :: run_CE - - integer(8) :: n - - ! Check to make sure that the hard-limit on secondary particles is not - ! exceeded. - if (this % n_secondary == MAX_SECONDARY) then - call fatal_error("Too many secondary particles created.") - end if - - n = this % n_secondary + 1 - this % secondary_bank(n) % wgt = this % wgt - this % secondary_bank(n) % xyz(:) = this % coord(1) % xyz - this % secondary_bank(n) % uvw(:) = uvw - this % n_secondary = n - this % secondary_bank(this % n_secondary) % E = this % E - if (.not. run_CE) then - this % secondary_bank(this % n_secondary) % E = real(this % g, 8) - end if - - end subroutine create_secondary - !=============================================================================== ! MARK_AS_LOST !=============================================================================== From fa891043509f72b6833f24daa6cde364ede6d7e3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 11 Dec 2017 14:21:34 +0700 Subject: [PATCH 067/282] Move cross_surface to tracking (break geometry -> tally dependence) --- src/geometry.F90 | 271 ---------------------------------------------- src/tracking.F90 | 275 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 273 insertions(+), 273 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index 1d99273de..151a49285 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -10,7 +10,6 @@ module geometry use surface_header use stl_vector, only: VectorInt use string, only: to_str - use tally, only: score_surface_current use tally_header implicit none @@ -379,276 +378,6 @@ contains end subroutine find_cell -!=============================================================================== -! CROSS_SURFACE handles all surface crossings, whether the particle leaks out of -! the geometry, is reflected, or crosses into a new lattice or cell -!=============================================================================== - - subroutine cross_surface(p) - type(Particle), intent(inout) :: p - - real(8) :: u ! x-component of direction - real(8) :: v ! y-component of direction - real(8) :: w ! z-component of direction - real(8) :: norm ! "norm" of surface normal - real(8) :: d ! distance between point and plane - real(8) :: xyz(3) ! Saved global coordinate - integer :: i_surface ! index in surfaces - logical :: rotational ! if rotational periodic BC applied - logical :: found ! particle found in universe? - class(Surface), pointer :: surf - - i_surface = abs(p % surface) - surf => surfaces(i_surface)%obj - if (verbosity >= 10 .or. trace) then - call write_message(" Crossing surface " // trim(to_str(surf % id))) - end if - - if (surf % bc == BC_VACUUM .and. (run_mode /= MODE_PLOTTING)) then - ! ======================================================================= - ! PARTICLE LEAKS OUT OF PROBLEM - - ! Kill particle - p % alive = .false. - - ! Score any surface current tallies -- note that the particle is moved - ! forward slightly so that if the mesh boundary is on the surface, it is - ! still processed - - if (active_current_tallies % size() > 0) then - ! TODO: Find a better solution to score surface currents than - ! physically moving the particle forward slightly - - p % coord(1) % xyz = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw - call score_surface_current(p) - end if - - ! Score to global leakage tally - global_tally_leakage = global_tally_leakage + p % wgt - - ! Display message - if (verbosity >= 10 .or. trace) then - call write_message(" Leaked out of surface " & - &// trim(to_str(surf % id))) - end if - return - - elseif (surf % bc == BC_REFLECT .and. (run_mode /= MODE_PLOTTING)) then - ! ======================================================================= - ! PARTICLE REFLECTS FROM SURFACE - - ! Do not handle reflective boundary conditions on lower universes - if (p % n_coord /= 1) then - call p % mark_as_lost("Cannot reflect particle " & - // trim(to_str(p % id)) // " off surface in a lower universe.") - return - end if - - ! Score surface currents since reflection causes the direction of the - ! particle to change -- artificially move the particle slightly back in - ! case the surface crossing is coincident with a mesh boundary - - if (active_current_tallies % size() > 0) then - xyz = p % coord(1) % xyz - p % coord(1) % xyz = p % coord(1) % xyz - TINY_BIT * p % coord(1) % uvw - call score_surface_current(p) - p % coord(1) % xyz = xyz - end if - - ! Reflect particle off surface - call surf%reflect(p%coord(1)%xyz, p%coord(1)%uvw) - - ! Make sure new particle direction is normalized - u = p%coord(1)%uvw(1) - v = p%coord(1)%uvw(2) - w = p%coord(1)%uvw(3) - norm = sqrt(u*u + v*v + w*w) - p%coord(1)%uvw(:) = [u, v, w] / norm - - ! Reassign particle's cell and surface - p % coord(1) % cell = p % last_cell(p % last_n_coord) - p % surface = -p % surface - - ! If a reflective surface is coincident with a lattice or universe - ! boundary, it is necessary to redetermine the particle's coordinates in - ! the lower universes. - - p % n_coord = 1 - call find_cell(p, found) - if (.not. found) then - call p % mark_as_lost("Couldn't find particle after reflecting& - & from surface " // trim(to_str(surf % id)) // ".") - return - end if - - ! Set previous coordinate going slightly past surface crossing - p % last_xyz_current = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw - - ! Diagnostic message - if (verbosity >= 10 .or. trace) then - call write_message(" Reflected from surface " & - &// trim(to_str(surf%id))) - end if - return - elseif (surf % bc == BC_PERIODIC .and. run_mode /= MODE_PLOTTING) then - ! ======================================================================= - ! PERIODIC BOUNDARY - - ! Do not handle periodic boundary conditions on lower universes - if (p % n_coord /= 1) then - call p % mark_as_lost("Cannot transfer particle " & - // trim(to_str(p % id)) // " across surface in a lower universe.& - & Boundary conditions must be applied to universe 0.") - return - end if - - ! Score surface currents since reflection causes the direction of the - ! particle to change -- artificially move the particle slightly back in - ! case the surface crossing is coincident with a mesh boundary - - if (active_current_tallies % size() > 0) then - xyz = p % coord(1) % xyz - p % coord(1) % xyz = p % coord(1) % xyz - TINY_BIT * p % coord(1) % uvw - call score_surface_current(p) - p % coord(1) % xyz = xyz - end if - - rotational = .false. - select type (surf) - type is (SurfaceXPlane) - select type (opposite => surfaces(surf % i_periodic) % obj) - type is (SurfaceXPlane) - p % coord(1) % xyz(1) = opposite % x0 - type is (SurfaceYPlane) - rotational = .true. - - ! Rotate direction - u = p % coord(1) % uvw(1) - v = p % coord(1) % uvw(2) - p % coord(1) % uvw(1) = v - p % coord(1) % uvw(2) = -u - - ! Rotate position - p % coord(1) % xyz(1) = surf % x0 + p % coord(1) % xyz(2) - opposite % y0 - p % coord(1) % xyz(2) = opposite % y0 - end select - - type is (SurfaceYPlane) - select type (opposite => surfaces(surf % i_periodic) % obj) - type is (SurfaceYPlane) - p % coord(1) % xyz(2) = opposite % y0 - type is (SurfaceXPlane) - rotational = .true. - - ! Rotate direction - u = p % coord(1) % uvw(1) - v = p % coord(1) % uvw(2) - p % coord(1) % uvw(1) = -v - p % coord(1) % uvw(2) = u - - ! Rotate position - p % coord(1) % xyz(2) = surf % y0 + p % coord(1) % xyz(1) - opposite % x0 - p % coord(1) % xyz(1) = opposite % x0 - end select - - type is (SurfaceZPlane) - select type (opposite => surfaces(surf % i_periodic) % obj) - type is (SurfaceZPlane) - p % coord(1) % xyz(3) = opposite % z0 - end select - - type is (SurfacePlane) - select type (opposite => surfaces(surf % i_periodic) % obj) - type is (SurfacePlane) - ! Get surface normal for opposite plane - xyz(:) = opposite % normal(p % coord(1) % xyz) - - ! Determine distance to plane - norm = xyz(1)*xyz(1) + xyz(2)*xyz(2) + xyz(3)*xyz(3) - d = opposite % evaluate(p % coord(1) % xyz) / norm - - ! Move particle along normal vector based on distance - p % coord(1) % xyz(:) = p % coord(1) % xyz(:) - d*xyz - end select - end select - - ! Reassign particle's surface - if (rotational) then - p % surface = surf % i_periodic - else - p % surface = sign(surf % i_periodic, p % surface) - end if - - ! Figure out what cell particle is in now - p % n_coord = 1 - call find_cell(p, found) - if (.not. found) then - call p % mark_as_lost("Couldn't find particle after hitting & - &periodic boundary on surface " // trim(to_str(surf % id)) // ".") - return - end if - - ! Set previous coordinate going slightly past surface crossing - p % last_xyz_current = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw - - ! Diagnostic message - if (verbosity >= 10 .or. trace) then - call write_message(" Hit periodic boundary on surface " & - // trim(to_str(surf%id))) - end if - return - end if - - ! ========================================================================== - ! SEARCH NEIGHBOR LISTS FOR NEXT CELL - - if (p % surface > 0 .and. allocated(surf%neighbor_pos)) then - ! If coming from negative side of surface, search all the neighboring - ! cells on the positive side - - call find_cell(p, found, surf%neighbor_pos) - if (found) return - - elseif (p % surface < 0 .and. allocated(surf%neighbor_neg)) then - ! If coming from positive side of surface, search all the neighboring - ! cells on the negative side - - call find_cell(p, found, surf%neighbor_neg) - if (found) return - - end if - - ! ========================================================================== - ! COULDN'T FIND PARTICLE IN NEIGHBORING CELLS, SEARCH ALL CELLS - - ! Remove lower coordinate levels and assignment of surface - p % surface = NONE - p % n_coord = 1 - call find_cell(p, found) - - if (run_mode /= MODE_PLOTTING .and. (.not. found)) then - ! If a cell is still not found, there are two possible causes: 1) there is - ! a void in the model, and 2) the particle hit a surface at a tangent. If - ! the particle is really traveling tangent to a surface, if we move it - ! forward a tiny bit it should fix the problem. - - p % n_coord = 1 - p % coord(1) % xyz = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw - call find_cell(p, found) - - ! Couldn't find next cell anywhere! This probably means there is an actual - ! undefined region in the geometry. - - if (.not. found) then - call p % mark_as_lost("After particle " // trim(to_str(p % id)) & - // " crossed surface " // trim(to_str(surf % id)) & - // " it could not be located in any cell and it did not leak.") - return - end if - end if - - end subroutine cross_surface - !=============================================================================== ! CROSS_LATTICE moves a particle into a new lattice element !=============================================================================== diff --git a/src/tracking.F90 b/src/tracking.F90 index 726a263ad..d110af462 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -4,8 +4,8 @@ module tracking use cross_section, only: calculate_xs use error, only: fatal_error, warning use geometry_header, only: cells - use geometry, only: find_cell, distance_to_boundary, cross_surface, & - cross_lattice, check_cell_overlap + use geometry, only: find_cell, distance_to_boundary, cross_lattice, & + check_cell_overlap use output, only: write_message use message_passing use mgxs_header @@ -17,6 +17,7 @@ module tracking use settings use simulation_header use string, only: to_str + use surface_header use tally_header use tally, only: score_analog_tally, score_tracklength_tally, & score_collision_tally, score_surface_current, & @@ -278,4 +279,274 @@ contains end subroutine transport +!=============================================================================== +! CROSS_SURFACE handles all surface crossings, whether the particle leaks out of +! the geometry, is reflected, or crosses into a new lattice or cell +!=============================================================================== + + subroutine cross_surface(p) + type(Particle), intent(inout) :: p + + real(8) :: u ! x-component of direction + real(8) :: v ! y-component of direction + real(8) :: w ! z-component of direction + real(8) :: norm ! "norm" of surface normal + real(8) :: d ! distance between point and plane + real(8) :: xyz(3) ! Saved global coordinate + integer :: i_surface ! index in surfaces + logical :: rotational ! if rotational periodic BC applied + logical :: found ! particle found in universe? + class(Surface), pointer :: surf + + i_surface = abs(p % surface) + surf => surfaces(i_surface)%obj + if (verbosity >= 10 .or. trace) then + call write_message(" Crossing surface " // trim(to_str(surf % id))) + end if + + if (surf % bc == BC_VACUUM .and. (run_mode /= MODE_PLOTTING)) then + ! ======================================================================= + ! PARTICLE LEAKS OUT OF PROBLEM + + ! Kill particle + p % alive = .false. + + ! Score any surface current tallies -- note that the particle is moved + ! forward slightly so that if the mesh boundary is on the surface, it is + ! still processed + + if (active_current_tallies % size() > 0) then + ! TODO: Find a better solution to score surface currents than + ! physically moving the particle forward slightly + + p % coord(1) % xyz = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw + call score_surface_current(p) + end if + + ! Score to global leakage tally + global_tally_leakage = global_tally_leakage + p % wgt + + ! Display message + if (verbosity >= 10 .or. trace) then + call write_message(" Leaked out of surface " & + &// trim(to_str(surf % id))) + end if + return + + elseif (surf % bc == BC_REFLECT .and. (run_mode /= MODE_PLOTTING)) then + ! ======================================================================= + ! PARTICLE REFLECTS FROM SURFACE + + ! Do not handle reflective boundary conditions on lower universes + if (p % n_coord /= 1) then + call p % mark_as_lost("Cannot reflect particle " & + // trim(to_str(p % id)) // " off surface in a lower universe.") + return + end if + + ! Score surface currents since reflection causes the direction of the + ! particle to change -- artificially move the particle slightly back in + ! case the surface crossing is coincident with a mesh boundary + + if (active_current_tallies % size() > 0) then + xyz = p % coord(1) % xyz + p % coord(1) % xyz = p % coord(1) % xyz - TINY_BIT * p % coord(1) % uvw + call score_surface_current(p) + p % coord(1) % xyz = xyz + end if + + ! Reflect particle off surface + call surf%reflect(p%coord(1)%xyz, p%coord(1)%uvw) + + ! Make sure new particle direction is normalized + u = p%coord(1)%uvw(1) + v = p%coord(1)%uvw(2) + w = p%coord(1)%uvw(3) + norm = sqrt(u*u + v*v + w*w) + p%coord(1)%uvw(:) = [u, v, w] / norm + + ! Reassign particle's cell and surface + p % coord(1) % cell = p % last_cell(p % last_n_coord) + p % surface = -p % surface + + ! If a reflective surface is coincident with a lattice or universe + ! boundary, it is necessary to redetermine the particle's coordinates in + ! the lower universes. + + p % n_coord = 1 + call find_cell(p, found) + if (.not. found) then + call p % mark_as_lost("Couldn't find particle after reflecting& + & from surface " // trim(to_str(surf % id)) // ".") + return + end if + + ! Set previous coordinate going slightly past surface crossing + p % last_xyz_current = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw + + ! Diagnostic message + if (verbosity >= 10 .or. trace) then + call write_message(" Reflected from surface " & + &// trim(to_str(surf%id))) + end if + return + elseif (surf % bc == BC_PERIODIC .and. run_mode /= MODE_PLOTTING) then + ! ======================================================================= + ! PERIODIC BOUNDARY + + ! Do not handle periodic boundary conditions on lower universes + if (p % n_coord /= 1) then + call p % mark_as_lost("Cannot transfer particle " & + // trim(to_str(p % id)) // " across surface in a lower universe.& + & Boundary conditions must be applied to universe 0.") + return + end if + + ! Score surface currents since reflection causes the direction of the + ! particle to change -- artificially move the particle slightly back in + ! case the surface crossing is coincident with a mesh boundary + + if (active_current_tallies % size() > 0) then + xyz = p % coord(1) % xyz + p % coord(1) % xyz = p % coord(1) % xyz - TINY_BIT * p % coord(1) % uvw + call score_surface_current(p) + p % coord(1) % xyz = xyz + end if + + rotational = .false. + select type (surf) + type is (SurfaceXPlane) + select type (opposite => surfaces(surf % i_periodic) % obj) + type is (SurfaceXPlane) + p % coord(1) % xyz(1) = opposite % x0 + type is (SurfaceYPlane) + rotational = .true. + + ! Rotate direction + u = p % coord(1) % uvw(1) + v = p % coord(1) % uvw(2) + p % coord(1) % uvw(1) = v + p % coord(1) % uvw(2) = -u + + ! Rotate position + p % coord(1) % xyz(1) = surf % x0 + p % coord(1) % xyz(2) - opposite % y0 + p % coord(1) % xyz(2) = opposite % y0 + end select + + type is (SurfaceYPlane) + select type (opposite => surfaces(surf % i_periodic) % obj) + type is (SurfaceYPlane) + p % coord(1) % xyz(2) = opposite % y0 + type is (SurfaceXPlane) + rotational = .true. + + ! Rotate direction + u = p % coord(1) % uvw(1) + v = p % coord(1) % uvw(2) + p % coord(1) % uvw(1) = -v + p % coord(1) % uvw(2) = u + + ! Rotate position + p % coord(1) % xyz(2) = surf % y0 + p % coord(1) % xyz(1) - opposite % x0 + p % coord(1) % xyz(1) = opposite % x0 + end select + + type is (SurfaceZPlane) + select type (opposite => surfaces(surf % i_periodic) % obj) + type is (SurfaceZPlane) + p % coord(1) % xyz(3) = opposite % z0 + end select + + type is (SurfacePlane) + select type (opposite => surfaces(surf % i_periodic) % obj) + type is (SurfacePlane) + ! Get surface normal for opposite plane + xyz(:) = opposite % normal(p % coord(1) % xyz) + + ! Determine distance to plane + norm = xyz(1)*xyz(1) + xyz(2)*xyz(2) + xyz(3)*xyz(3) + d = opposite % evaluate(p % coord(1) % xyz) / norm + + ! Move particle along normal vector based on distance + p % coord(1) % xyz(:) = p % coord(1) % xyz(:) - d*xyz + end select + end select + + ! Reassign particle's surface + if (rotational) then + p % surface = surf % i_periodic + else + p % surface = sign(surf % i_periodic, p % surface) + end if + + ! Figure out what cell particle is in now + p % n_coord = 1 + call find_cell(p, found) + if (.not. found) then + call p % mark_as_lost("Couldn't find particle after hitting & + &periodic boundary on surface " // trim(to_str(surf % id)) // ".") + return + end if + + ! Set previous coordinate going slightly past surface crossing + p % last_xyz_current = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw + + ! Diagnostic message + if (verbosity >= 10 .or. trace) then + call write_message(" Hit periodic boundary on surface " & + // trim(to_str(surf%id))) + end if + return + end if + + ! ========================================================================== + ! SEARCH NEIGHBOR LISTS FOR NEXT CELL + + if (p % surface > 0 .and. allocated(surf%neighbor_pos)) then + ! If coming from negative side of surface, search all the neighboring + ! cells on the positive side + + call find_cell(p, found, surf%neighbor_pos) + if (found) return + + elseif (p % surface < 0 .and. allocated(surf%neighbor_neg)) then + ! If coming from positive side of surface, search all the neighboring + ! cells on the negative side + + call find_cell(p, found, surf%neighbor_neg) + if (found) return + + end if + + ! ========================================================================== + ! COULDN'T FIND PARTICLE IN NEIGHBORING CELLS, SEARCH ALL CELLS + + ! Remove lower coordinate levels and assignment of surface + p % surface = NONE + p % n_coord = 1 + call find_cell(p, found) + + if (run_mode /= MODE_PLOTTING .and. (.not. found)) then + ! If a cell is still not found, there are two possible causes: 1) there is + ! a void in the model, and 2) the particle hit a surface at a tangent. If + ! the particle is really traveling tangent to a surface, if we move it + ! forward a tiny bit it should fix the problem. + + p % n_coord = 1 + p % coord(1) % xyz = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw + call find_cell(p, found) + + ! Couldn't find next cell anywhere! This probably means there is an actual + ! undefined region in the geometry. + + if (.not. found) then + call p % mark_as_lost("After particle " // trim(to_str(p % id)) & + // " crossed surface " // trim(to_str(surf % id)) & + // " it could not be located in any cell and it did not leak.") + return + end if + end if + + end subroutine cross_surface + end module tracking From 3431ebd8fea65390d6df1dce27f95bb41e73f16a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 11 Dec 2017 14:35:31 +0700 Subject: [PATCH 068/282] Move write_message to error module (break geometry -> output dependence) --- src/cmfd_data.F90 | 2 +- src/cmfd_execute.F90 | 2 +- src/cmfd_input.F90 | 3 +-- src/error.F90 | 57 ++++++++++++++++++++++++++++++++++++++++ src/geometry.F90 | 4 +-- src/initialize.F90 | 4 +-- src/input_xml.F90 | 4 +-- src/mgxs_data.F90 | 3 +-- src/output.F90 | 55 -------------------------------------- src/particle_restart.F90 | 3 ++- src/physics.F90 | 3 +-- src/physics_mg.F90 | 3 +-- src/plot.F90 | 4 +-- src/simulation.F90 | 4 +-- src/state_point.F90 | 4 +-- src/summary.F90 | 3 ++- src/tallies/trigger.F90 | 2 +- src/tracking.F90 | 3 +-- src/volume_calc.F90 | 3 ++- 19 files changed, 82 insertions(+), 84 deletions(-) diff --git a/src/cmfd_data.F90 b/src/cmfd_data.F90 index 5eb2158e9..521be5e3f 100644 --- a/src/cmfd_data.F90 +++ b/src/cmfd_data.F90 @@ -644,7 +644,7 @@ contains subroutine compute_dhat() use constants, only: CMFD_NOACCEL, ZERO - use output, only: write_message + use error, only: write_message use string, only: to_str integer :: nx ! maximum number of cells in x direction diff --git a/src/cmfd_execute.F90 b/src/cmfd_execute.F90 index 140166e0a..9cdb751a4 100644 --- a/src/cmfd_execute.F90 +++ b/src/cmfd_execute.F90 @@ -366,7 +366,7 @@ contains subroutine cmfd_tally_reset() - use output, only: write_message + use error, only: write_message integer :: i ! loop counter diff --git a/src/cmfd_input.F90 b/src/cmfd_input.F90 index a1827febd..dbfabb254 100644 --- a/src/cmfd_input.F90 +++ b/src/cmfd_input.F90 @@ -41,8 +41,7 @@ contains subroutine read_cmfd_xml() use constants, only: ZERO, ONE - use error, only: fatal_error, warning - use output, only: write_message + use error, only: fatal_error, warning, write_message use string, only: to_lower use xml_interface use, intrinsic :: ISO_FORTRAN_ENV diff --git a/src/error.F90 b/src/error.F90 index 92902a04c..27004d729 100644 --- a/src/error.F90 +++ b/src/error.F90 @@ -5,12 +5,14 @@ module error use constants use message_passing + use settings, only: verbosity implicit none private public :: fatal_error public :: warning + public :: write_message ! Error codes integer(C_INT), public, bind(C) :: E_UNASSIGNED = -1 @@ -192,4 +194,59 @@ contains end subroutine fatal_error +!=============================================================================== +! WRITE_MESSAGE displays an informational message to the log file and the +! standard output stream. +!=============================================================================== + + subroutine write_message(message, level) + character(*), intent(in) :: message ! message to write + integer, intent(in), optional :: level ! verbosity level + + integer :: i_start ! starting position + integer :: i_end ! ending position + integer :: line_wrap ! length of line + integer :: length ! length of message + integer :: last_space ! index of last space (relative to start) + + ! Set length of line + line_wrap = 80 + + ! Only allow master to print to screen + if (.not. master .and. present(level)) return + + if (.not. present(level) .or. level <= verbosity) then + ! Determine length of message + length = len_trim(message) + + i_start = 0 + do + if (length - i_start < line_wrap + 1) then + ! Remainder of message will fit on line + write(OUTPUT_UNIT, fmt='(1X,A)') message(i_start+1:length) + exit + + else + ! Determine last space in current line + last_space = index(message(i_start+1:i_start+line_wrap), & + ' ', BACK=.true.) + if (last_space == 0) then + i_end = min(length + 1, i_start+line_wrap) - 1 + write(OUTPUT_UNIT, fmt='(1X,A)') message(i_start+1:i_end) + else + i_end = i_start + last_space + write(OUTPUT_UNIT, fmt='(1X,A)') message(i_start+1:i_end-1) + end if + + ! Write up to last space + + ! Advance starting position + i_start = i_end + if (i_start > length) exit + end if + end do + end if + + end subroutine write_message + end module error diff --git a/src/geometry.F90 b/src/geometry.F90 index 151a49285..ced62e681 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -1,16 +1,14 @@ module geometry use constants - use error, only: fatal_error, warning + use error, only: fatal_error, warning, write_message use geometry_header - use output, only: write_message use particle_header, only: LocalCoord, Particle use simulation_header use settings use surface_header use stl_vector, only: VectorInt use string, only: to_str - use tally_header implicit none diff --git a/src/initialize.F90 b/src/initialize.F90 index 21d568c72..392e8d6ba 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -10,7 +10,7 @@ module initialize use bank_header, only: Bank use constants use set_header, only: SetInt - use error, only: fatal_error, warning + use error, only: fatal_error, warning, write_message use geometry_header, only: Cell, Universe, Lattice, RectLattice, HexLattice,& root_universe use hdf5_interface, only: file_open, read_attribute, file_close, & @@ -19,7 +19,7 @@ module initialize use material_header, only: Material use message_passing use mgxs_data, only: read_mgxs, create_macro_xs - use output, only: print_version, write_message, print_usage + use output, only: print_version, print_usage use random_lcg, only: openmc_set_seed use settings #ifdef _OPENMP diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 91cd57ae2..e95762a0b 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -10,7 +10,7 @@ module input_xml use distribution_multivariate use distribution_univariate use endf, only: reaction_name - use error, only: fatal_error, warning + use error, only: fatal_error, warning, write_message use geometry, only: calc_offsets, maximum_levels, count_instance, & neighbor_lists use geometry_header @@ -23,7 +23,7 @@ module input_xml use mgxs_header use multipole, only: multipole_read use nuclide_header - use output, only: write_message, title, header, print_plot + use output, only: title, header, print_plot use plot_header use random_lcg, only: prn, openmc_set_seed use surface_header diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index f2aaa00de..1b14e6e9f 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -3,13 +3,12 @@ module mgxs_data use constants use algorithm, only: find use dict_header, only: DictCharInt - use error, only: fatal_error + use error, only: fatal_error, write_message use geometry_header, only: get_temperatures, cells use hdf5_interface use material_header, only: Material, materials, n_materials use mgxs_header use nuclide_header, only: n_nuclides - use output, only: write_message use set_header, only: SetChar use settings use stl_vector, only: VectorReal diff --git a/src/output.F90 b/src/output.F90 index c20077584..d09beda1b 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -202,61 +202,6 @@ contains end subroutine print_usage -!=============================================================================== -! WRITE_MESSAGE displays an informational message to the log file and the -! standard output stream. -!=============================================================================== - - subroutine write_message(message, level) - character(*), intent(in) :: message ! message to write - integer, intent(in), optional :: level ! verbosity level - - integer :: i_start ! starting position - integer :: i_end ! ending position - integer :: line_wrap ! length of line - integer :: length ! length of message - integer :: last_space ! index of last space (relative to start) - - ! Set length of line - line_wrap = 80 - - ! Only allow master to print to screen - if (.not. master .and. present(level)) return - - if (.not. present(level) .or. level <= verbosity) then - ! Determine length of message - length = len_trim(message) - - i_start = 0 - do - if (length - i_start < line_wrap + 1) then - ! Remainder of message will fit on line - write(ou, fmt='(1X,A)') message(i_start+1:length) - exit - - else - ! Determine last space in current line - last_space = index(message(i_start+1:i_start+line_wrap), & - ' ', BACK=.true.) - if (last_space == 0) then - i_end = min(length + 1, i_start+line_wrap) - 1 - write(ou, fmt='(1X,A)') message(i_start+1:i_end) - else - i_end = i_start + last_space - write(ou, fmt='(1X,A)') message(i_start+1:i_end-1) - end if - - ! Write up to last space - - ! Advance starting position - i_start = i_end - if (i_start > length) exit - end if - end do - end if - - end subroutine write_message - !=============================================================================== ! PRINT_PARTICLE displays the attributes of a particle !=============================================================================== diff --git a/src/particle_restart.F90 b/src/particle_restart.F90 index 53332c794..2d15f2ddc 100644 --- a/src/particle_restart.F90 +++ b/src/particle_restart.F90 @@ -4,10 +4,11 @@ module particle_restart use bank_header, only: Bank use constants + use error, only: write_message use hdf5_interface, only: file_open, file_close, read_dataset use mgxs_header, only: energy_bin_avg use nuclide_header, only: micro_xs, n_nuclides - use output, only: write_message, print_particle + use output, only: print_particle use particle_header, only: Particle use random_lcg, only: set_particle_seed use settings diff --git a/src/physics.F90 b/src/physics.F90 index 96dabd542..d13313e05 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -4,13 +4,12 @@ module physics use constants use cross_section, only: elastic_xs_0K use endf, only: reaction_name - use error, only: fatal_error, warning + use error, only: fatal_error, warning, write_message use material_header, only: Material, materials use math use mesh_header, only: meshes use message_passing use nuclide_header - use output, only: write_message use particle_header, only: Particle use physics_common use random_lcg, only: prn, advance_prn_seed, prn_set_stream diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index 53a28cd4d..eb8208594 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -4,14 +4,13 @@ module physics_mg use bank_header use constants - use error, only: fatal_error, warning + use error, only: fatal_error, warning, write_message use material_header, only: Material, materials use math, only: rotate_angle use mesh_header, only: meshes use mgxs_header use message_passing use nuclide_header, only: material_xs - use output, only: write_message use particle_header, only: Particle use physics_common use random_lcg, only: prn diff --git a/src/plot.F90 b/src/plot.F90 index fe2ddd13b..5eb894ce8 100644 --- a/src/plot.F90 +++ b/src/plot.F90 @@ -5,11 +5,11 @@ module plot use hdf5 use constants - use error, only: fatal_error + use error, only: fatal_error, write_message use geometry, only: find_cell, check_cell_overlap use geometry_header, only: Cell, root_universe, cells use hdf5_interface - use output, only: write_message, time_stamp + use output, only: time_stamp use material_header, only: materials use particle_header, only: LocalCoord, Particle use plot_header diff --git a/src/simulation.F90 b/src/simulation.F90 index 5eacbd40b..b04e967f2 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -16,12 +16,12 @@ module simulation #ifdef _OPENMP use eigenvalue, only: join_bank_from_threads #endif - use error, only: fatal_error + use error, only: fatal_error, write_message use geometry_header, only: n_cells use message_passing use mgxs_header, only: energy_bins, energy_bin_avg use nuclide_header, only: micro_xs, n_nuclides - use output, only: write_message, header, print_columns, & + use output, only: header, print_columns, & print_batch_keff, print_generation, print_runtime, & print_results, print_overlap_check, write_tallies use particle_header, only: Particle diff --git a/src/state_point.F90 b/src/state_point.F90 index 6d647b72e..f7a892898 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -19,13 +19,13 @@ module state_point use constants use eigenvalue, only: openmc_get_keff use endf, only: reaction_name - use error, only: fatal_error, warning + use error, only: fatal_error, warning, write_message use hdf5_interface use mesh_header, only: RegularMesh, meshes, n_meshes use message_passing use mgxs_header, only: nuclides_MG use nuclide_header, only: nuclides - use output, only: write_message, time_stamp + use output, only: time_stamp use random_lcg, only: seed use settings use simulation_header diff --git a/src/summary.F90 b/src/summary.F90 index 248b48e86..0f45ef1bb 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -4,6 +4,7 @@ module summary use constants use endf, only: reaction_name + use error, only: write_message use geometry_header use hdf5_interface use material_header, only: Material, n_materials @@ -11,7 +12,7 @@ module summary use message_passing use mgxs_header, only: nuclides_MG use nuclide_header - use output, only: time_stamp, write_message + use output, only: time_stamp use settings, only: run_CE use surface_header use string, only: to_str diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index 772e739ba..9e9b06d20 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -8,8 +8,8 @@ module trigger use constants use eigenvalue, only: openmc_get_keff + use error, only: warning, write_message use string, only: to_str - use output, only: warning, write_message use mesh_header, only: RegularMesh, meshes use message_passing, only: master use settings diff --git a/src/tracking.F90 b/src/tracking.F90 index d110af462..65769a7f1 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -2,11 +2,10 @@ module tracking use constants use cross_section, only: calculate_xs - use error, only: fatal_error, warning + use error, only: fatal_error, warning, write_message use geometry_header, only: cells use geometry, only: find_cell, distance_to_boundary, cross_lattice, & check_cell_overlap - use output, only: write_message use message_passing use mgxs_header use nuclide_header diff --git a/src/volume_calc.F90 b/src/volume_calc.F90 index 48f93f0e6..5985a236b 100644 --- a/src/volume_calc.F90 +++ b/src/volume_calc.F90 @@ -8,11 +8,12 @@ module volume_calc #endif use constants + use error, only: write_message use geometry, only: find_cell use geometry_header, only: universes, cells use hdf5_interface, only: file_create, file_close, write_attribute, & create_group, close_group, write_dataset - use output, only: write_message, header, time_stamp + use output, only: header, time_stamp use material_header, only: materials use message_passing use nuclide_header, only: nuclides From 48812172f070c3e786c5ba709146fc2cfefb7013 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 11 Dec 2017 14:53:56 +0700 Subject: [PATCH 069/282] Normalize fixed source calculations by total external source strength --- src/tallies/tally_header.F90 | 13 ++++++-- tests/test_fixed_source/geometry.xml | 8 ----- tests/test_fixed_source/inputs_true.dat | 31 ++++++++++++++++++ tests/test_fixed_source/materials.xml | 10 ------ tests/test_fixed_source/results_true.dat | 4 +-- tests/test_fixed_source/settings.xml | 14 -------- tests/test_fixed_source/tallies.xml | 8 ----- tests/test_fixed_source/test_fixed_source.py | 34 +++++++++++++++++--- 8 files changed, 73 insertions(+), 49 deletions(-) delete mode 100644 tests/test_fixed_source/geometry.xml create mode 100644 tests/test_fixed_source/inputs_true.dat delete mode 100644 tests/test_fixed_source/materials.xml delete mode 100644 tests/test_fixed_source/settings.xml delete mode 100644 tests/test_fixed_source/tallies.xml diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 1778474e0..21b4c8a58 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -9,7 +9,8 @@ module tally_header use dict_header, only: DictIntInt use message_passing, only: n_procs use nuclide_header, only: nuclide_dict - use settings, only: reduce_tallies + use settings, only: reduce_tallies, run_mode + use source_header, only: external_source use stl_vector, only: VectorInt use string, only: to_lower, to_f_string, str_to_int, to_str use tally_filter_header, only: TallyFilterContainer, filters, n_filters @@ -161,6 +162,7 @@ contains integer :: i, j real(C_DOUBLE) :: val + real(C_DOUBLE) :: total_source ! Increment number of realizations if (reduce_tallies) then @@ -169,10 +171,17 @@ contains this % n_realizations = this % n_realizations + n_procs end if + ! Calculate total source strength for normalization + if (run_mode == MODE_FIXEDSOURCE) then + total_source = sum(external_source(:) % strength) + else + total_source = ONE + end if + ! Accumulate each result do j = 1, size(this % results, 3) do i = 1, size(this % results, 2) - val = this % results(RESULT_VALUE, i, j)/total_weight + val = this % results(RESULT_VALUE, i, j)/total_weight * total_source this % results(RESULT_VALUE, i, j) = ZERO this % results(RESULT_SUM, i, j) = & diff --git a/tests/test_fixed_source/geometry.xml b/tests/test_fixed_source/geometry.xml deleted file mode 100644 index bc56030e1..000000000 --- a/tests/test_fixed_source/geometry.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/tests/test_fixed_source/inputs_true.dat b/tests/test_fixed_source/inputs_true.dat new file mode 100644 index 000000000..2e0d57b0c --- /dev/null +++ b/tests/test_fixed_source/inputs_true.dat @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + fixed source + 100 + 10 + + + 0.0 0.0 0.0 + + + 294 + + + + + flux + + diff --git a/tests/test_fixed_source/materials.xml b/tests/test_fixed_source/materials.xml deleted file mode 100644 index 6e4249da3..000000000 --- a/tests/test_fixed_source/materials.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/tests/test_fixed_source/results_true.dat b/tests/test_fixed_source/results_true.dat index 6246dfaf6..c911b4fd2 100644 --- a/tests/test_fixed_source/results_true.dat +++ b/tests/test_fixed_source/results_true.dat @@ -1,6 +1,6 @@ tally 1: -4.448476E+02 -1.984373E+04 +4.448476E+03 +1.984373E+06 leakage: 9.790000E+00 9.588300E+00 diff --git a/tests/test_fixed_source/settings.xml b/tests/test_fixed_source/settings.xml deleted file mode 100644 index e7e7f2f5b..000000000 --- a/tests/test_fixed_source/settings.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - fixed source - 10 - 100 - - 294 - - - - - - diff --git a/tests/test_fixed_source/tallies.xml b/tests/test_fixed_source/tallies.xml deleted file mode 100644 index 87e08d6c7..000000000 --- a/tests/test_fixed_source/tallies.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - flux - - - diff --git a/tests/test_fixed_source/test_fixed_source.py b/tests/test_fixed_source/test_fixed_source.py index 8d61a46d0..db58e2f70 100644 --- a/tests/test_fixed_source/test_fixed_source.py +++ b/tests/test_fixed_source/test_fixed_source.py @@ -5,17 +5,18 @@ import os import sys import numpy as np sys.path.insert(0, os.pardir) -from testing_harness import TestHarness -from openmc import StatePoint +from testing_harness import PyAPITestHarness +import openmc +import openmc.stats -class FixedSourceTestHarness(TestHarness): +class FixedSourceTestHarness(PyAPITestHarness): def _get_results(self): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] outstr = '' - with StatePoint(statepoint) as sp: + with openmc.StatePoint(statepoint) as sp: # Write out tally data. for i, tally_ind in enumerate(sp.tallies): tally = sp.tallies[tally_ind] @@ -36,5 +37,28 @@ class FixedSourceTestHarness(TestHarness): if __name__ == '__main__': - harness = FixedSourceTestHarness('statepoint.10.h5') + mat = openmc.Material() + mat.add_nuclide('O16', 1.0) + mat.add_nuclide('U238', 0.0001) + mat.set_density('g/cc', 7.5) + + surf = openmc.Sphere(R=10.0, boundary_type='vacuum') + cell = openmc.Cell(fill=mat, region=-surf) + + model = openmc.model.Model() + model.geometry.root_universe = openmc.Universe(cells=[cell]) + model.materials.append(mat) + + model.settings.run_mode = 'fixed source' + model.settings.batches = 10 + model.settings.particles = 100 + model.settings.temperature = {'default': 294} + model.settings.source = openmc.Source(space=openmc.stats.Point(), + strength=10.0) + + tally = openmc.Tally() + tally.scores = ['flux'] + model.tallies.append(tally) + + harness = FixedSourceTestHarness('statepoint.10.h5', model) harness.main() From 7f83000812b04149903fdb22cac1b00b8b549257 Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Mon, 17 Jul 2017 17:56:27 +0000 Subject: [PATCH 070/282] Adding direct-mapping implementation. --- src/input_xml.F90 | 10 ++++++++++ src/material_header.F90 | 1 + src/tallies/tally.F90 | 25 ++++++++----------------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 91cd57ae2..8dcd9335f 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2450,6 +2450,16 @@ contains n_nuclides = index_nuclide n_sab_tables = index_sab + + do i=1, n_materials + mat => materials(i) + allocate(mat % mat_nuclide_list(n_nuclides_total)) + mat % mat_nuclide_list(:) = 0 + do j=1, mat % n_nuclides + mat % mat_nuclide_list(mat % nuclide(j))=j + end do + end do + ! Close materials XML file call doc % clear() diff --git a/src/material_header.F90 b/src/material_header.F90 index 9d93131ac..4f4b66e78 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -32,6 +32,7 @@ module material_header character(len=104) :: name = "" ! User-defined name integer :: n_nuclides = 0 ! number of nuclides integer, allocatable :: nuclide(:) ! index in nuclides array + integer, allocatable :: mat_nuclide_list(:) real(8) :: density ! total atom density in atom/b-cm real(8), allocatable :: atom_density(:) ! nuclide atom density in atom/b-cm real(8) :: density_gpcc ! total density in g/cm^3 diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 8b1e7ca9a..fbbccfde1 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -2730,7 +2730,7 @@ contains type(Particle), intent(in) :: p real(8), intent(in) :: distance - + integer :: check_value integer :: i integer :: i_tally integer :: i_filt @@ -2744,7 +2744,7 @@ contains real(8) :: filter_weight ! combined weight of all filters logical :: finished ! found all valid bin combinations type(Material), pointer :: mat - + ! Determine track-length estimate of flux flux = p % wgt * distance @@ -2811,26 +2811,17 @@ contains if (p % material /= MATERIAL_VOID) then ! Get pointer to current material mat => materials(p % material) + check_value= mat % mat_nuclide_list(i_nuclide) + + if (check_value == 0) then + cycle NUCLIDE_BIN_LOOP + end if - ! Determine if nuclide is actually in material - NUCLIDE_MAT_LOOP: do j = 1, mat % n_nuclides - ! If index of nuclide matches the j-th nuclide listed in the - ! material, break out of the loop - if (i_nuclide == mat % nuclide(j)) exit - - ! If we've reached the last nuclide in the material, it means - ! the specified nuclide to be tallied is not in this material - if (j == mat % n_nuclides) then - cycle NUCLIDE_BIN_LOOP - end if - end do NUCLIDE_MAT_LOOP - - atom_density = mat % atom_density(j) + atom_density = mat % atom_density(check_value) else atom_density = ZERO end if end if - ! Determine score for each bin call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & i_nuclide, atom_density, flux * filter_weight) From 4e40788128aca5bbd1e108443081a039ad9782e7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 21 Dec 2017 17:16:26 +0700 Subject: [PATCH 071/282] Fix up material nuclide mapping --- src/input_xml.F90 | 13 ++++---- src/material_header.F90 | 7 +++- src/tallies/tally.F90 | 72 ++++++++++++++++------------------------- 3 files changed, 41 insertions(+), 51 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 8dcd9335f..363c08867 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2450,13 +2450,14 @@ contains n_nuclides = index_nuclide n_sab_tables = index_sab - - do i=1, n_materials + ! Create direct address tables for tally optimization purposes + do i = 1, n_materials mat => materials(i) - allocate(mat % mat_nuclide_list(n_nuclides_total)) - mat % mat_nuclide_list(:) = 0 - do j=1, mat % n_nuclides - mat % mat_nuclide_list(mat % nuclide(j))=j + allocate(mat % mat_nuclide_index(n_nuclides)) + mat % mat_nuclide_index(:) = 0 + + do j = 1, mat % n_nuclides + mat % mat_nuclide_index(mat % nuclide(j)) = j end do end do diff --git a/src/material_header.F90 b/src/material_header.F90 index 4f4b66e78..3287e796f 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -32,11 +32,16 @@ module material_header character(len=104) :: name = "" ! User-defined name integer :: n_nuclides = 0 ! number of nuclides integer, allocatable :: nuclide(:) ! index in nuclides array - integer, allocatable :: mat_nuclide_list(:) real(8) :: density ! total atom density in atom/b-cm real(8), allocatable :: atom_density(:) ! nuclide atom density in atom/b-cm real(8) :: density_gpcc ! total density in g/cm^3 + ! To improve performance of tallying, we store an array (direct address + ! table) that indicates for each nuclide in the global nuclides(:) array the + ! index of the corresponding nuclide in the Material % nuclide(:) array. If + ! it is not present in the material, the entry is set to zero. + integer, allocatable :: mat_nuclide_index(:) + ! Energy grid information integer :: n_grid ! # of union material grid points real(8), allocatable :: e_grid(:) ! union material grid energies diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index fbbccfde1..eaee96fa7 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -2377,10 +2377,6 @@ contains i_tally = active_analog_tallies % data(i) associate (t => tallies(i_tally) % obj) - ! Get pointer to current material. We need this in order to determine what - ! nuclides are in the material - mat => materials(p % material) - ! Find all valid bins in each filter if they have not already been found ! for a previous tally. do j = 1, size(t % filter) @@ -2423,33 +2419,28 @@ contains ! Nuclide logic ! Check for nuclide bins - k = 0 - NUCLIDE_LOOP: do while (k < t % n_nuclide_bins) - - ! Increment the index in the list of nuclide bins - k = k + 1 - + NUCLIDE_LOOP: do k = 1, t % n_nuclide_bins + ! Get index of nuclide in nuclides array i_nuclide = t % nuclide_bins(k) if (i_nuclide > 0) then - atom_density = -ONE - ! Check to see if this nuclide was in the material of our collision - do m = 1, mat % n_nuclides - if (mat % nuclide(m) == i_nuclide) then - atom_density = mat % atom_density(m) - exit - end if - end do + if (p % material /= MATERIAL_VOID) then + ! Get pointer to current material + mat => materials(p % material) + + ! Determine index of nuclide in Material % atom_density array + j = mat % mat_nuclide_index(i_nuclide) + if (j == 0) cycle NUCLIDE_LOOP + + ! Copy corresponding atom density + atom_density = mat % atom_density(j) + end if else atom_density = ZERO end if - ! If we found the nuclide, determine the score for each bin - if (atom_density >= ZERO) then - call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & - i_nuclide, atom_density, filter_weight) - end if - + call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & + i_nuclide, atom_density, filter_weight) end do NUCLIDE_LOOP ! ====================================================================== @@ -2730,7 +2721,7 @@ contains type(Particle), intent(in) :: p real(8), intent(in) :: distance - integer :: check_value + integer :: i integer :: i_tally integer :: i_filt @@ -2744,7 +2735,7 @@ contains real(8) :: filter_weight ! combined weight of all filters logical :: finished ! found all valid bin combinations type(Material), pointer :: mat - + ! Determine track-length estimate of flux flux = p % wgt * distance @@ -2811,17 +2802,18 @@ contains if (p % material /= MATERIAL_VOID) then ! Get pointer to current material mat => materials(p % material) - check_value= mat % mat_nuclide_list(i_nuclide) - - if (check_value == 0) then - cycle NUCLIDE_BIN_LOOP - end if - atom_density = mat % atom_density(check_value) + ! Determine index of nuclide in Material % atom_density array + j = mat % mat_nuclide_index(i_nuclide) + if (j == 0) cycle NUCLIDE_BIN_LOOP + + ! Copy corresponding atom density + atom_density = mat % atom_density(j) else atom_density = ZERO end if end if + ! Determine score for each bin call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & i_nuclide, atom_density, flux * filter_weight) @@ -2968,19 +2960,11 @@ contains ! Get pointer to current material mat => materials(p % material) - ! Determine if nuclide is actually in material - NUCLIDE_MAT_LOOP: do j = 1, mat % n_nuclides - ! If index of nuclide matches the j-th nuclide listed in the - ! material, break out of the loop - if (i_nuclide == mat % nuclide(j)) exit - - ! If we've reached the last nuclide in the material, it means - ! the specified nuclide to be tallied is not in this material - if (j == mat % n_nuclides) then - cycle NUCLIDE_BIN_LOOP - end if - end do NUCLIDE_MAT_LOOP + ! Determine index of nuclide in Material % atom_density array + j = mat % mat_nuclide_index(i_nuclide) + if (j == 0) cycle NUCLIDE_BIN_LOOP + ! Copy corresponding atom density atom_density = mat % atom_density(j) else atom_density = ZERO From 29a8737b25e78e5f2cdb3f9a87c85bff2f4a941b Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Tue, 15 Aug 2017 14:46:04 +0000 Subject: [PATCH 072/282] Use direct address table for reactions in nuclides --- src/nuclide_header.F90 | 10 ++++++---- src/tallies/tally.F90 | 14 +++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 676e1b500..1831f0a89 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -87,9 +87,9 @@ module nuclide_header ! Reactions type(Reaction), allocatable :: reactions(:) - type(DictIntInt) :: reaction_index ! map MT values to index in reactions + !type(DictIntInt) :: reaction_index ! map MT values to index in reactions ! array; used at tally-time - + integer, allocatable :: rxn_index_MT(:) ! Fission energy release class(Function1D), allocatable :: fission_q_prompt ! prompt neutrons, gammas class(Function1D), allocatable :: fission_q_recov ! neutrons, gammas, betas @@ -561,7 +561,8 @@ contains n_temperature = size(this % kTs) allocate(this % sum_xs(n_temperature)) - + allocate(this % rxn_index_MT(1000)) + this % rxn_index_MT(:)=0 do i = 1, n_temperature ! Allocate and initialize derived cross sections n_grid = size(this % grid(i) % energy) @@ -580,8 +581,9 @@ contains i_fission = 0 do i = 1, size(this % reactions) + call MTs % push_back(this % reactions(i) % MT) - call this % reaction_index % set(this % reactions(i) % MT, i) + this % rxn_index_MT(this % reactions(i) % MT) = i associate (rx => this % reactions(i)) ! Skip total inelastic level scattering, gas production cross sections diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index eaee96fa7..c47f94aa7 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -247,7 +247,7 @@ contains ! multiplicities of one. score = p % last_wgt * flux else - m = nuclides(p % event_nuclide) % reaction_index % get(p % event_MT) + m = nuclides(p % event_nuclide) % rxn_index_MT(p % event_MT) ! Get yield and apply to score associate (rxn => nuclides(p % event_nuclide) % reactions(m)) @@ -273,7 +273,7 @@ contains ! multiplicities of one. score = p % last_wgt * flux else - m = nuclides(p % event_nuclide) % reaction_index % get(p % event_MT) + m = nuclides(p % event_nuclide) % rxn_index_MT(p % event_MT) ! Get yield and apply to score associate (rxn => nuclides(p % event_nuclide) % reactions(m)) @@ -299,7 +299,7 @@ contains ! multiplicities of one. score = p % last_wgt * flux else - m = nuclides(p % event_nuclide) % reaction_index % get(p % event_MT) + m = nuclides(p % event_nuclide) % rxn_index_MT(p % event_MT) ! Get yield and apply to score associate (rxn => nuclides(p%event_nuclide)%reactions(m)) @@ -1148,8 +1148,8 @@ contains score = ZERO if (i_nuclide > 0) then - m = nuclides(i_nuclide) % reaction_index % get(score_bin) - if (m /= EMPTY) then + m = nuclides(i_nuclide) % rxn_index_MT(score_bin) + if (m /= 0) then ! Retrieve temperature and energy grid index and interpolation ! factor i_temp = micro_xs(i_nuclide) % index_temp @@ -1187,8 +1187,8 @@ contains ! Get index in nuclides array i_nuc = materials(p % material) % nuclide(l) - m = nuclides(i_nuc) % reaction_index % get(score_bin) - if (m /= EMPTY) then + m = nuclides(i_nuc) % rxn_index_MT(score_bin) + if (m /= 0) then ! Retrieve temperature and energy grid index and ! interpolation factor i_temp = micro_xs(i_nuc) % index_temp From fe19b9b6dd7fec5ce5ebae806419d5dca0f18cd3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 22 Dec 2017 13:11:17 +0700 Subject: [PATCH 073/282] Make rxn_index_MT allocated as part of Nuclide type --- src/nuclide_header.F90 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 1831f0a89..2c5de1b14 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -87,9 +87,11 @@ module nuclide_header ! Reactions type(Reaction), allocatable :: reactions(:) - !type(DictIntInt) :: reaction_index ! map MT values to index in reactions - ! array; used at tally-time - integer, allocatable :: rxn_index_MT(:) + + ! Array that maps MT values to index in reactions; used at tally-time. Note + ! that ENDF-102 does not assign any MT values above 891. + integer :: rxn_index_MT(891) + ! Fission energy release class(Function1D), allocatable :: fission_q_prompt ! prompt neutrons, gammas class(Function1D), allocatable :: fission_q_recov ! neutrons, gammas, betas @@ -561,8 +563,7 @@ contains n_temperature = size(this % kTs) allocate(this % sum_xs(n_temperature)) - allocate(this % rxn_index_MT(1000)) - this % rxn_index_MT(:)=0 + this % rxn_index_MT(:) = 0 do i = 1, n_temperature ! Allocate and initialize derived cross sections n_grid = size(this % grid(i) % energy) From b10b7204b2cf17ae1a3eb58f1cdc58732f6c488d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 22 Dec 2017 13:37:15 +0700 Subject: [PATCH 074/282] Create mat_nuclide_index as part of simulation_init --- src/input_xml.F90 | 11 ---------- src/material_header.F90 | 47 ++++++++++++++++++++++++++++++----------- src/simulation.F90 | 19 ++++++++++++----- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 363c08867..91cd57ae2 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2450,17 +2450,6 @@ contains n_nuclides = index_nuclide n_sab_tables = index_sab - ! Create direct address tables for tally optimization purposes - do i = 1, n_materials - mat => materials(i) - allocate(mat % mat_nuclide_index(n_nuclides)) - mat % mat_nuclide_index(:) = 0 - - do j = 1, mat % n_nuclides - mat % mat_nuclide_index(mat % nuclide(j)) = j - end do - end do - ! Close materials XML file call doc % clear() diff --git a/src/material_header.F90 b/src/material_header.F90 index 3287e796f..d77220415 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -69,6 +69,7 @@ module material_header contains procedure :: set_density => material_set_density + procedure :: init_nuclide_index => material_init_nuclide_index procedure :: assign_sab_tables => material_assign_sab_tables end type Material @@ -85,8 +86,8 @@ contains ! MATERIAL_SET_DENSITY sets the total density of a material in atom/b-cm. !=============================================================================== - function material_set_density(m, density) result(err) - class(Material), intent(inout) :: m + function material_set_density(this, density) result(err) + class(Material), intent(inout) :: this real(8), intent(in) :: density integer :: err @@ -94,23 +95,23 @@ contains real(8) :: sum_percent real(8) :: awr - if (allocated(m % atom_density)) then + if (allocated(this % atom_density)) then ! Set total density based on value provided - m % density = density + this % density = density ! Determine normalized atom percents - sum_percent = sum(m % atom_density) - m % atom_density(:) = m % atom_density / sum_percent + sum_percent = sum(this % atom_density) + this % atom_density(:) = this % atom_density / sum_percent ! Recalculate nuclide atom densities based on given density - m % atom_density(:) = density * m % atom_density + this % atom_density(:) = density * this % atom_density ! Calculate density in g/cm^3. - m % density_gpcc = ZERO - do i = 1, m % n_nuclides - awr = nuclides(m % nuclide(i)) % awr - m % density_gpcc = m % density_gpcc & - + m % atom_density(i) * awr * MASS_NEUTRON / N_AVOGADRO + this % density_gpcc = ZERO + do i = 1, this % n_nuclides + awr = nuclides(this % nuclide(i)) % awr + this % density_gpcc = this % density_gpcc & + + this % atom_density(i) * awr * MASS_NEUTRON / N_AVOGADRO end do err = 0 else @@ -119,6 +120,28 @@ contains end if end function material_set_density +!=============================================================================== +! INIT_NUCLIDE_INDEX creates a mapping from indices in the global nuclides(:) +! array to the Material % nuclides array +!=============================================================================== + + subroutine material_init_nuclide_index(this) + class(Material), intent(inout) :: this + + integer :: i + + ! Allocate nuclide index array and set to zeros + if (allocated(this % mat_nuclide_index)) & + deallocate(this % mat_nuclide_index) + allocate(this % mat_nuclide_index(n_nuclides)) + this % mat_nuclide_index(:) = 0 + + ! Assign entries in the index array + do i = 1, this % n_nuclides + this % mat_nuclide_index(this % nuclide(i)) = i + end do + end subroutine material_init_nuclide_index + !=============================================================================== ! ASSIGN_SAB_TABLES assigns S(alpha,beta) tables to specific nuclides within ! materials so the code knows when to apply bound thermal scattering data diff --git a/src/simulation.F90 b/src/simulation.F90 index 5eacbd40b..ade209e36 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -18,6 +18,7 @@ module simulation #endif use error, only: fatal_error use geometry_header, only: n_cells + use material_header, only: n_materials, materials use message_passing use mgxs_header, only: energy_bins, energy_bin_avg use nuclide_header, only: micro_xs, n_nuclides @@ -401,6 +402,7 @@ contains !=============================================================================== subroutine openmc_simulation_init() bind(C) + integer :: i ! Skip if simulation has already been initialized if (simulation_initialized) return @@ -418,6 +420,11 @@ contains ! Allocate tally results arrays if they're not allocated yet call configure_tallies() + ! Set up material nuclide index mapping + do i = 1, n_materials + call materials(i) % init_nuclide_index() + end do + !$omp parallel ! Allocate array for microscopic cross section cache allocate(micro_xs(n_nuclides)) @@ -459,8 +466,8 @@ contains subroutine openmc_simulation_finalize() bind(C) + integer :: i ! loop index #ifdef MPI - integer :: i ! loop index for tallies integer :: n ! size of arrays integer :: mpi_err ! MPI error code integer(8) :: temp @@ -470,9 +477,14 @@ contains ! Skip if simulation was never run if (.not. simulation_initialized) return - ! Stop active batch timer + ! Stop active batch timer and start finalization timer call time_active % stop() + call time_finalize % start() + ! Free up simulation-specific memory + do i = 1, n_materials + deallocate(materials(i) % mat_nuclide_index) + end do !$omp parallel deallocate(micro_xs, filter_matches) !$omp end parallel @@ -480,9 +492,6 @@ contains ! Increment total number of generations total_gen = total_gen + current_batch*gen_per_batch - ! Start finalization timer - call time_finalize % start() - #ifdef MPI ! Broadcast tally results so that each process has access to results if (allocated(tallies)) then From aa5d093a592f641720671b9e832f799fd78a4262 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 22 Dec 2017 13:40:24 +0700 Subject: [PATCH 075/282] Remove unused components of the Material derived type --- src/cross_section.F90 | 22 ---------------------- src/material_header.F90 | 7 ------- 2 files changed, 29 deletions(-) diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 0bdadd87e..46cbb93ab 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -573,28 +573,6 @@ contains end subroutine calculate_urr_xs -!=============================================================================== -! FIND_ENERGY_INDEX determines the index on the union energy grid at a certain -! energy -!=============================================================================== - - pure function find_energy_index(mat, E) result(i) - type(Material), intent(in) :: mat ! pointer to current material - real(8), intent(in) :: E ! energy of particle - integer :: i ! energy grid index - - ! if the energy is outside of energy grid range, set to first or last - ! index. Otherwise, do a binary search through the union energy grid. - if (E <= mat % e_grid(1)) then - i = 1 - elseif (E > mat % e_grid(mat % n_grid)) then - i = mat % n_grid - 1 - else - i = binary_search(mat % e_grid, mat % n_grid, E) - end if - - end function find_energy_index - !=============================================================================== ! MULTIPOLE_EVAL evaluates the windowed multipole equations for cross ! sections in the resolved resonance regions diff --git a/src/material_header.F90 b/src/material_header.F90 index d77220415..a3ce32db3 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -42,13 +42,6 @@ module material_header ! it is not present in the material, the entry is set to zero. integer, allocatable :: mat_nuclide_index(:) - ! Energy grid information - integer :: n_grid ! # of union material grid points - real(8), allocatable :: e_grid(:) ! union material grid energies - - ! Unionized energy grid information - integer, allocatable :: nuclide_grid_index(:,:) ! nuclide e_grid pointers - ! S(a,b) data integer :: n_sab = 0 ! number of S(a,b) tables integer, allocatable :: i_sab_nuclides(:) ! index of corresponding nuclide From f774c29254313b026120ba34b31bcdba5ee629ce Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Sat, 2 Sep 2017 19:15:19 +0000 Subject: [PATCH 076/282] Cache cross sections for depletion-related reactions (ngamma, n2n, etc.) --- src/constants.F90 | 11 +- src/cross_section.F90 | 111 ++++++++++++++++- src/endf.F90 | 36 ++++-- src/input_xml.F90 | 33 +++-- src/nuclide_header.F90 | 21 ++++ src/tallies/tally.F90 | 231 ++++++++++++++++++++++++++++++++++- src/tallies/tally_header.F90 | 1 + 7 files changed, 414 insertions(+), 30 deletions(-) diff --git a/src/constants.F90 b/src/constants.F90 index 853a40c8d..936c84499 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -308,7 +308,7 @@ 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 = 30 integer, parameter :: & SCORE_FLUX = -1, & ! flux SCORE_TOTAL = -2, & ! total reaction rate @@ -333,8 +333,13 @@ module constants 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_DECAY_RATE = -24, & ! delayed neutron precursor decay rate + SCORE_N2N = -25, & + SCORE_NGAMMA = -26, & + SCORE_N3N = -27, & + SCORE_N4N = -28, & + SCORE_NP = -29, & + SCORE_NALPHA = -30 ! Maximum scattering order supported integer, parameter :: MAX_ANG_ORDER = 10 diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 46cbb93ab..2484be51b 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -2,6 +2,7 @@ module cross_section use algorithm, only: binary_search use constants + use endf use error, only: fatal_error use list_header, only: ListElemInt use material_header, only: Material, materials @@ -45,7 +46,12 @@ contains material_xs % absorption = ZERO material_xs % fission = ZERO material_xs % nu_fission = ZERO - + material_xs % n2n = ZERO + material_xs % n3n = ZERO + material_xs % n4n = ZERO + material_xs % ngamma = ZERO + material_xs % np = ZERO + material_xs % nalpha = ZERO ! Exit subroutine if material is void if (p % material == MATERIAL_VOID) return @@ -129,6 +135,35 @@ contains ! Add contributions to material macroscopic nu-fission cross section material_xs % nu_fission = material_xs % nu_fission + & atom_density * micro_xs(i_nuclide) % nu_fission + + + ! Add contributions to material macroscopic n2n cross section + material_xs % n2n = material_xs % n2n + & + atom_density * micro_xs(i_nuclide) % n2n + + + ! Add contributions to material macroscopic ngamma cross section + material_xs % ngamma = material_xs % ngamma + & + atom_density * micro_xs(i_nuclide) % ngamma + + ! Add contributions to material macroscopic n3n cross section + material_xs % n3n = material_xs % n3n + & + atom_density * micro_xs(i_nuclide) % n3n + + ! Add contributions to material macroscopic n4n cross section + material_xs % n4n = material_xs % n4n + & + atom_density * micro_xs(i_nuclide) % n4n + + ! Add contributions to material macroscopic np cross section + material_xs % np = material_xs % np + & + atom_density * micro_xs(i_nuclide) % np + + + ! Add contributions to material macroscopic nalpha cross section + material_xs % nalpha = material_xs % nalpha + & + atom_density * micro_xs(i_nuclide) % nalpha + + end do end associate @@ -157,7 +192,9 @@ contains real(8) :: f ! interp factor on nuclide energy grid real(8) :: kT ! temperature in eV real(8) :: sigT, sigA, sigF ! Intermediate multipole variables - + integer :: rxn + type(TallyObject) :: t + character(15) :: threshold_rxn_type associate (nuc => nuclides(i_nuclide)) ! Check to see if there is multipole data present at this energy use_mp = .false. @@ -202,6 +239,8 @@ contains else ! Find the appropriate temperature index. kT = sqrtkT**2 + + !rxn = nuc % rxn_index_MT(p % event_MT) select case (temperature_method) case (TEMPERATURE_NEAREST) i_temp = minloc(abs(nuclides(i_nuclide) % kTs - kT), dim=1) @@ -247,12 +286,19 @@ contains (grid % energy(i_grid + 1) - grid % energy(i_grid)) micro_xs(i_nuclide) % index_temp = i_temp + micro_xs(i_nuclide) % index_grid = i_grid + micro_xs(i_nuclide) % interp_factor = f ! Initialize nuclide cross-sections to zero micro_xs(i_nuclide) % fission = ZERO micro_xs(i_nuclide) % nu_fission = ZERO + micro_xs(i_nuclide) % n2n = ZERO + micro_xs(i_nuclide) % n3n = ZERO + micro_xs(i_nuclide) % n4n = ZERO + micro_xs(i_nuclide) % np = ZERO + micro_xs(i_nuclide) % nalpha = ZERO micro_xs(i_nuclide) % thermal = ZERO micro_xs(i_nuclide) % thermal_elastic = ZERO @@ -274,11 +320,66 @@ contains + f * xs % fission(i_grid + 1) ! Calculate microscopic nuclide nu-fission cross section - micro_xs(i_nuclide) % nu_fission = (ONE - f) * xs % nu_fission( & + micro_xs(i_nuclide) % nu_fission = (ONE - f) * xs % nu_fission(& i_grid) + f * xs % nu_fission(i_grid + 1) + end if - end associate - end if + + micro_xs(i_nuclide) % ngamma = (micro_xs(i_nuclide) % absorption - & + micro_xs(i_nuclide) % fission) + if (t % has_threshold_rxn) then + !if (nuclides(i_nuclide)%reaction_index%has_key(p % event_MT)) the + rxn = nuclides(i_nuclide) % rxn_index_MT(p % event_MT) + associate(xs_threshold => nuc % reactions(rxn) % xs(i_temp)) + !end if + threshold_rxn_type = reaction_name(p % event_MT) + if (threshold_rxn_type == 'n2n') then + if (i_grid >= xs_threshold % threshold) then + micro_xs(i_nuclide) % n2n = (ONE - f) * xs_threshold % value(i_grid - xs_threshold & + % threshold + 1) + f * xs_threshold % value(i_grid - xs_threshold & + % threshold + 2) + end if + end if + if (threshold_rxn_type == 'n3n') then + if (i_grid >= xs_threshold % threshold) then + micro_xs(i_nuclide) % n3n = (ONE - f) * xs_threshold % value(i_grid - xs_threshold & + % threshold + 1) + f * xs_threshold % value(i_grid - xs_threshold & + % threshold + 2) + end if + end if + + + if (threshold_rxn_type == 'n4n') then + if (i_grid >= xs_threshold % threshold) then + micro_xs(i_nuclide) % n4n = (ONE - f) * xs_threshold % value(i_grid - xs_threshold & + % threshold + 1) + f * xs_threshold % value(i_grid - xs_threshold & + % threshold + 2) + end if + end if + + if (threshold_rxn_type == 'np') then + if (i_grid >= xs_threshold % threshold) then + micro_xs(i_nuclide) % np = (ONE - f) * xs_threshold % value(i_grid - xs_threshold & + % threshold + 1) + f * xs_threshold % value(i_grid - xs_threshold & + % threshold + 2) + end if + end if + + if (threshold_rxn_type == 'nalpha') then + if (i_grid >= xs_threshold % threshold) then + micro_xs(i_nuclide) % nalpha = (ONE - f) * xs_threshold % value(i_grid - xs_threshold & + % threshold + 1) + f * xs_threshold % value(i_grid - xs_threshold & + % threshold + 2) + end if + end if + + + + end associate + + end if + end associate + end if ! Initialize sab treatment to false micro_xs(i_nuclide) % index_sab = NONE diff --git a/src/endf.F90 b/src/endf.F90 index 0ba2db388..9aace42d7 100644 --- a/src/endf.F90 +++ b/src/endf.F90 @@ -66,6 +66,18 @@ contains string = "fission-q-prompt" case (SCORE_FISS_Q_RECOV) string = "fission-q-recoverable" + case (SCORE_N2N) + string = "n2n" + case (SCORE_N3N) + string = "n3n" + case (SCORE_N4N) + string = "n4n" + case (SCORE_NGAMMA) + string = "ngamma" + case (SCORE_NP) + string = "np" + case (SCORE_NALPHA) + string = "nalpha" ! Normal ENDF-based reactions case (TOTAL_XS) @@ -76,12 +88,12 @@ contains string = '(n,level)' case (N_2ND) string = '(n,2nd)' - case (N_2N) - string = '(n,2n)' - case (N_3N) - string = '(n,3n)' - case (N_FISSION) - string = '(n,fission)' + !case (N_2N) + !string = '(n,2n)' + !case (N_3N) + !string = '(n,3n)' + !case (N_FISSION) + ! string = '(n,fission)' case (N_F) string = '(n,f)' case (N_NF) @@ -130,18 +142,18 @@ contains string = '(n,nc)' case (N_DISAPPEAR) string = '(n,disappear)' - case (N_GAMMA) - string = '(n,gamma)' - case (N_P) - string = '(n,p)' + !case (N_GAMMA) + ! string = '(n,gamma)' + !case (N_P) + !string = '(n,p)' case (N_D) string = '(n,d)' case (N_T) string = '(n,t)' case (N_3HE) string = '(n,3He)' - case (N_A) - string = '(n,a)' + !case (N_A) + !string = '(n,a)' case (N_2A) string = '(n,2a)' case (N_3A) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 91cd57ae2..4587072f5 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2450,6 +2450,17 @@ contains n_nuclides = index_nuclide n_sab_tables = index_sab + + do i=1, n_materials + mat => materials(i) + allocate(mat % mat_nuclide_list(n_nuclides_total)) + mat % mat_nuclide_list(:) = 0 + do j=1, mat % n_nuclides + mat % mat_nuclide_list(mat % nuclide(j))=j + end do + end do + + ! Close materials XML file call doc % clear() @@ -3047,14 +3058,14 @@ contains call fatal_error("n1n score no longer supported for tallies, & &please remove") case ('n2n', '(n,2n)') - t % score_bins(j) = N_2N - + t % score_bins(j) = SCORE_N2N + t % has_threshold_rxn = .true. case ('n3n', '(n,3n)') - t % score_bins(j) = N_3N - + t % score_bins(j) = SCORE_N3N + t % has_threshold_rxn = .true. case ('n4n', '(n,4n)') - t % score_bins(j) = N_4N - + t % score_bins(j) = SCORE_N4N + t % has_threshold_rxn = .true. case ('absorption') t % score_bins(j) = SCORE_ABSORPTION if (t % find_filter(FILTER_ENERGYOUT) > 0) then @@ -3238,9 +3249,12 @@ contains case ('(n,nc)') t % score_bins(j) = N_NC case ('(n,gamma)') - t % score_bins(j) = N_GAMMA + t % score_bins(j) = SCORE_NGAMMA case ('(n,p)') - t % score_bins(j) = N_P + t % score_bins(j) = SCORE_NP + + t % has_threshold_rxn = .true. + case ('(n,d)') t % score_bins(j) = N_D case ('(n,t)') @@ -3248,7 +3262,8 @@ contains case ('(n,3He)') t % score_bins(j) = N_3HE case ('(n,a)') - t % score_bins(j) = N_A + t % score_bins(j) = SCORE_NALPHA + t % has_threshold_rxn = .true. case ('(n,2a)') t % score_bins(j) = N_2A case ('(n,3a)') diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 2c5de1b14..1f6d45165 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -43,6 +43,14 @@ module nuclide_header real(8), allocatable :: nu_fission(:) ! neutron production real(8), allocatable :: absorption(:) ! absorption (MT > 100) real(8), allocatable :: heating(:) ! heating + real(8), allocatable :: ngamma(:) + real(8), allocatable :: n2n(:) + real(8), allocatable :: n3n(:) + real(8), allocatable :: n4n(:) + + real(8), allocatable :: np(:) + real(8), allocatable :: nalpha(:) + end type SumXS type :: Nuclide @@ -118,6 +126,12 @@ module nuclide_header real(8) :: absorption real(8) :: fission real(8) :: nu_fission + real(8) :: n2n + real(8) :: ngamma + real(8) :: n3n + real(8) :: n4n + real(8) :: np + real(8) :: nalpha real(8) :: thermal ! Bound thermal elastic & inelastic scattering real(8) :: thermal_elastic ! Bound thermal elastic scattering @@ -148,6 +162,13 @@ module nuclide_header real(8) :: absorption ! macroscopic absorption xs real(8) :: fission ! macroscopic fission xs real(8) :: nu_fission ! macroscopic production xs + real(8) :: n2n + real(8) :: n3n + real(8) :: n4n + real(8) :: ngamma + real(8) :: np + real(8) :: nalpha + end type MaterialMacroXS !=============================================================================== diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index c47f94aa7..811ead4f8 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -1131,6 +1131,234 @@ contains end if end if + + case (SCORE_N2N) + + score = ZERO + + if (i_nuclide > 0) then + !if (nuclides(i_nuclide)%reaction_index%has_key(score_bin)) then + !m = nuclides(i_nuclide)%reaction_index%get_key(score_bin) + + ! Retrieve temperature and energy grid index and interpolation + ! factor + !i_temp = micro_xs(i_nuclide) % index_temp + !if (i_temp > 0) then + !i_energy = micro_xs(i_nuclide) % index_grid + !f = micro_xs(i_nuclide) % interp_factor + + !associate (xs => nuclides(i_nuclide) % reactions(m) % xs(i_temp)) + !if (i_energy >= xs % threshold) then + score = micro_xs(i_nuclide) % n2n * atom_density * flux + + + !else + ! This block is reached if multipole is turned on and we're in + ! the resolved range. For (n,gamma), use absorption - + ! fission. For everything else, assume it's zero. + + ! score = ZERO + + else + do l = 1, materials(p % material) % n_nuclides + ! Get atom density + atom_density_ = materials(p % material) % atom_density(l) + + ! Get index in nuclides array + i_nuc = materials(p % material) % nuclide(l) + + score = score + micro_xs(i_nuc) % n2n * atom_density_ * flux + end do + end if + + + case (SCORE_N3N) + + score = ZERO + + if (i_nuclide > 0) then + !if (nuclides(i_nuclide)%reaction_index%has_key(score_bin)) then + !m = nuclides(i_nuclide)%reaction_index%get_key(score_bin) + + ! Retrieve temperature and energy grid index and interpolation + ! factor + !i_temp = micro_xs(i_nuclide) % index_temp + !if (i_temp > 0) then + !i_energy = micro_xs(i_nuclide) % index_grid + !f = micro_xs(i_nuclide) % interp_factor + + !associate (xs => nuclides(i_nuclide) % reactions(m) % + !xs(i_temp)) + !if (i_energy >= xs % threshold) then + score = micro_xs(i_nuclide) % n3n * atom_density * flux + + + !else + ! This block is reached if multipole is turned on and we're in + ! the resolved range. For (n,gamma), use absorption - + ! fission. For everything else, assume it's zero. + + ! score = ZERO + + else + do l = 1, materials(p % material) % n_nuclides + ! Get atom density + atom_density_ = materials(p % material) % atom_density(l) + + ! Get index in nuclides array + i_nuc = materials(p % material) % nuclide(l) + + score = score + micro_xs(i_nuc) % n3n * atom_density_ * flux + end do + end if + + + case (SCORE_N4N) + + score = ZERO + + if (i_nuclide > 0) then + !if (nuclides(i_nuclide)%reaction_index%has_key(score_bin)) then + !m = nuclides(i_nuclide)%reaction_index%get_key(score_bin) + + ! Retrieve temperature and energy grid index and interpolation + ! factor + !i_temp = micro_xs(i_nuclide) % index_temp + !if (i_temp > 0) then + !i_energy = micro_xs(i_nuclide) % index_grid + !f = micro_xs(i_nuclide) % interp_factor + + !associate (xs => nuclides(i_nuclide) % reactions(m) % + !xs(i_temp)) + !if (i_energy >= xs % threshold) then + score = micro_xs(i_nuclide) % n4n * atom_density * flux + + + !else + ! This block is reached if multipole is turned on and we're in + ! the resolved range. For (n,gamma), use absorption - + ! fission. For everything else, assume it's zero. + + ! score = ZERO + + else + do l = 1, materials(p % material) % n_nuclides + ! Get atom density + atom_density_ = materials(p % material) % atom_density(l) + + ! Get index in nuclides array + i_nuc = materials(p % material) % nuclide(l) + + score = score + micro_xs(i_nuc) % n4n * atom_density_ * flux + end do + end if + + case (SCORE_NP) + + score = ZERO + + if (i_nuclide > 0) then + !if (nuclides(i_nuclide)%reaction_index%has_key(score_bin)) then + !m = nuclides(i_nuclide)%reaction_index%get_key(score_bin) + + ! Retrieve temperature and energy grid index and interpolation + ! factor + !i_temp = micro_xs(i_nuclide) % index_temp + !if (i_temp > 0) then + !i_energy = micro_xs(i_nuclide) % index_grid + !f = micro_xs(i_nuclide) % interp_factor + + !associate (xs => nuclides(i_nuclide) % reactions(m) % + !xs(i_temp)) + !if (i_energy >= xs % threshold) then + score = micro_xs(i_nuclide) % np * atom_density * flux + + + !else + ! This block is reached if multipole is turned on and we're in + ! the resolved range. For (n,gamma), use absorption - + ! fission. For everything else, assume it's zero. + + ! score = ZERO + + else + do l = 1, materials(p % material) % n_nuclides + ! Get atom density + atom_density_ = materials(p % material) % atom_density(l) + + ! Get index in nuclides array + i_nuc = materials(p % material) % nuclide(l) + + score = score + micro_xs(i_nuc) % np * atom_density_ * flux + end do + end if + + case (SCORE_NALPHA) + + score = ZERO + + if (i_nuclide > 0) then + !if (nuclides(i_nuclide)%reaction_index%has_key(score_bin)) then + !m = nuclides(i_nuclide)%reaction_index%get_key(score_bin) + + ! Retrieve temperature and energy grid index and interpolation + ! factor + !i_temp = micro_xs(i_nuclide) % index_temp + !if (i_temp > 0) then + !i_energy = micro_xs(i_nuclide) % index_grid + !f = micro_xs(i_nuclide) % interp_factor + + !associate (xs => nuclides(i_nuclide) % reactions(m) % + !xs(i_temp)) + !if (i_energy >= xs % threshold) then + score = micro_xs(i_nuclide) % nalpha * atom_density * flux + + + !else + ! This block is reached if multipole is turned on and we're in + ! the resolved range. For (n,gamma), use absorption - + ! fission. For everything else, assume it's zero. + + ! score = ZERO + + else + do l = 1, materials(p % material) % n_nuclides + ! Get atom density + atom_density_ = materials(p % material) % atom_density(l) + + ! Get index in nuclides array + i_nuc = materials(p % material) % nuclide(l) + + score = score + micro_xs(i_nuc) % nalpha * atom_density_ * flux + end do + end if + + + + case (SCORE_NGAMMA) + if (t % estimator == ESTIMATOR_ANALOG) then + if (survival_biasing) then + ! No absorption events actually occur if survival biasing is on -- + ! just use weight absorbed in survival biasing + score = p % absorb_wgt * flux + else + ! Skip any event where the particle wasn't absorbed + if (p % event == EVENT_SCATTER) cycle SCORE_LOOP + ! All fission and absorption events will contribute here, so we + ! can just use the particle's weight entering the collision + score = p % last_wgt * flux + end if + + else + if (i_nuclide > 0) then + score = micro_xs(i_nuclide) % ngamma * atom_density * flux + else + score = material_xs % ngamma * flux + end if + end if + + + case default if (t % estimator == ESTIMATOR_ANALOG) then ! Any other score is assumed to be a MT number. Thus, we just need @@ -2721,7 +2949,7 @@ contains type(Particle), intent(in) :: p real(8), intent(in) :: distance - + integer :: check_value integer :: i integer :: i_tally integer :: i_filt @@ -2802,6 +3030,7 @@ contains if (p % material /= MATERIAL_VOID) then ! Get pointer to current material mat => materials(p % material) + check_value= mat % mat_nuclide_list(i_nuclide) ! Determine index of nuclide in Material % atom_density array j = mat % mat_nuclide_index(i_nuclide) diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 1778474e0..c283c3f16 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -88,6 +88,7 @@ module tally_header ! reset property - allows a tally to be reset after every batch logical :: reset = .false. + logical :: has_threshold_rxn = .false. ! Number of realizations of tally random variables integer :: n_realizations = 0 From 9ff4e620b6786e8ad0d3cf0afab15d5a3e873ddc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 22 Dec 2017 14:47:42 +0700 Subject: [PATCH 077/282] Improvements to depletion reaction caching --- src/constants.F90 | 11 +- src/cross_section.F90 | 141 ++++++---------- src/endf.F90 | 36 ++-- src/input_xml.F90 | 33 +--- src/nuclide_header.F90 | 23 +-- src/tallies/tally.F90 | 313 +++++++++-------------------------- src/tallies/tally_header.F90 | 1 - 7 files changed, 162 insertions(+), 396 deletions(-) diff --git a/src/constants.F90 b/src/constants.F90 index 936c84499..853a40c8d 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -308,7 +308,7 @@ 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 = 30 + integer, parameter :: N_SCORE_TYPES = 24 integer, parameter :: & SCORE_FLUX = -1, & ! flux SCORE_TOTAL = -2, & ! total reaction rate @@ -333,13 +333,8 @@ module constants 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_N2N = -25, & - SCORE_NGAMMA = -26, & - SCORE_N3N = -27, & - SCORE_N4N = -28, & - SCORE_NP = -29, & - SCORE_NALPHA = -30 + SCORE_DECAY_RATE = -24 ! delayed neutron precursor decay rate + ! Maximum scattering order supported integer, parameter :: MAX_ANG_ORDER = 10 diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 2484be51b..7066d9642 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -2,7 +2,6 @@ module cross_section use algorithm, only: binary_search use constants - use endf use error, only: fatal_error use list_header, only: ListElemInt use material_header, only: Material, materials @@ -52,6 +51,7 @@ contains material_xs % ngamma = ZERO material_xs % np = ZERO material_xs % nalpha = ZERO + ! Exit subroutine if material is void if (p % material == MATERIAL_VOID) return @@ -136,17 +136,11 @@ contains material_xs % nu_fission = material_xs % nu_fission + & atom_density * micro_xs(i_nuclide) % nu_fission - - ! Add contributions to material macroscopic n2n cross section + ! Add contributions to material macroscopic n2n cross section material_xs % n2n = material_xs % n2n + & atom_density * micro_xs(i_nuclide) % n2n - - ! Add contributions to material macroscopic ngamma cross section - material_xs % ngamma = material_xs % ngamma + & - atom_density * micro_xs(i_nuclide) % ngamma - - ! Add contributions to material macroscopic n3n cross section + ! Add contributions to material macroscopic n3n cross section material_xs % n3n = material_xs % n3n + & atom_density * micro_xs(i_nuclide) % n3n @@ -154,16 +148,17 @@ contains material_xs % n4n = material_xs % n4n + & atom_density * micro_xs(i_nuclide) % n4n - ! Add contributions to material macroscopic np cross section + ! Add contributions to material macroscopic ngamma cross section + material_xs % ngamma = material_xs % ngamma + & + atom_density * micro_xs(i_nuclide) % ngamma + + ! Add contributions to material macroscopic np cross section material_xs % np = material_xs % np + & atom_density * micro_xs(i_nuclide) % np - - ! Add contributions to material macroscopic nalpha cross section + ! Add contributions to material macroscopic nalpha cross section material_xs % nalpha = material_xs % nalpha + & atom_density * micro_xs(i_nuclide) % nalpha - - end do end associate @@ -189,12 +184,23 @@ contains integer :: i_grid ! index on nuclide energy grid integer :: i_low ! lower logarithmic mapping index integer :: i_high ! upper logarithmic mapping index + integer :: i_rxn ! reaction index + integer :: j + real(8) :: val real(8) :: f ! interp factor on nuclide energy grid real(8) :: kT ! temperature in eV real(8) :: sigT, sigA, sigF ! Intermediate multipole variables - integer :: rxn - type(TallyObject) :: t - character(15) :: threshold_rxn_type + integer, parameter :: DEPLETION_RX(6) = [N_2N, N_3N, N_4N, N_GAMMA, N_P, N_A] + + ! Initialize cached cross sections to zero + micro_xs(i_nuclide) % n2n = ZERO + micro_xs(i_nuclide) % n3n = ZERO + micro_xs(i_nuclide) % n4n = ZERO + micro_xs(i_nuclide) % np = ZERO + micro_xs(i_nuclide) % nalpha = ZERO + micro_xs(i_nuclide) % thermal = ZERO + micro_xs(i_nuclide) % thermal_elastic = ZERO + associate (nuc => nuclides(i_nuclide)) ! Check to see if there is multipole data present at this energy use_mp = .false. @@ -221,6 +227,7 @@ contains micro_xs(i_nuclide) % fission = ZERO micro_xs(i_nuclide) % nu_fission = ZERO end if + micro_xs(i_nuclide) % ngamma = sigF - sigA ! Ensure these values are set ! Note, the only time either is used is in one of 4 places: @@ -239,8 +246,6 @@ contains else ! Find the appropriate temperature index. kT = sqrtkT**2 - - !rxn = nuc % rxn_index_MT(p % event_MT) select case (temperature_method) case (TEMPERATURE_NEAREST) i_temp = minloc(abs(nuclides(i_nuclide) % kTs - kT), dim=1) @@ -286,22 +291,9 @@ contains (grid % energy(i_grid + 1) - grid % energy(i_grid)) micro_xs(i_nuclide) % index_temp = i_temp - micro_xs(i_nuclide) % index_grid = i_grid - micro_xs(i_nuclide) % interp_factor = f - ! Initialize nuclide cross-sections to zero - micro_xs(i_nuclide) % fission = ZERO - micro_xs(i_nuclide) % nu_fission = ZERO - micro_xs(i_nuclide) % n2n = ZERO - micro_xs(i_nuclide) % n3n = ZERO - micro_xs(i_nuclide) % n4n = ZERO - micro_xs(i_nuclide) % np = ZERO - micro_xs(i_nuclide) % nalpha = ZERO - micro_xs(i_nuclide) % thermal = ZERO - micro_xs(i_nuclide) % thermal_elastic = ZERO - ! Calculate microscopic nuclide total cross section micro_xs(i_nuclide) % total = (ONE - f) * xs % total(i_grid) & + f * xs % total(i_grid + 1) @@ -320,66 +312,41 @@ contains + f * xs % fission(i_grid + 1) ! Calculate microscopic nuclide nu-fission cross section - micro_xs(i_nuclide) % nu_fission = (ONE - f) * xs % nu_fission(& + micro_xs(i_nuclide) % nu_fission = (ONE - f) * xs % nu_fission( & i_grid) + f * xs % nu_fission(i_grid + 1) - + else + micro_xs(i_nuclide) % fission = ZERO + micro_xs(i_nuclide) % nu_fission = ZERO end if + end associate - micro_xs(i_nuclide) % ngamma = (micro_xs(i_nuclide) % absorption - & - micro_xs(i_nuclide) % fission) - if (t % has_threshold_rxn) then - !if (nuclides(i_nuclide)%reaction_index%has_key(p % event_MT)) the - rxn = nuclides(i_nuclide) % rxn_index_MT(p % event_MT) - associate(xs_threshold => nuc % reactions(rxn) % xs(i_temp)) - !end if - threshold_rxn_type = reaction_name(p % event_MT) - if (threshold_rxn_type == 'n2n') then - if (i_grid >= xs_threshold % threshold) then - micro_xs(i_nuclide) % n2n = (ONE - f) * xs_threshold % value(i_grid - xs_threshold & - % threshold + 1) + f * xs_threshold % value(i_grid - xs_threshold & - % threshold + 2) - end if - end if - if (threshold_rxn_type == 'n3n') then - if (i_grid >= xs_threshold % threshold) then - micro_xs(i_nuclide) % n3n = (ONE - f) * xs_threshold % value(i_grid - xs_threshold & - % threshold + 1) + f * xs_threshold % value(i_grid - xs_threshold & - % threshold + 2) - end if - end if - - - if (threshold_rxn_type == 'n4n') then - if (i_grid >= xs_threshold % threshold) then - micro_xs(i_nuclide) % n4n = (ONE - f) * xs_threshold % value(i_grid - xs_threshold & - % threshold + 1) + f * xs_threshold % value(i_grid - xs_threshold & - % threshold + 2) - end if - end if - - if (threshold_rxn_type == 'np') then - if (i_grid >= xs_threshold % threshold) then - micro_xs(i_nuclide) % np = (ONE - f) * xs_threshold % value(i_grid - xs_threshold & - % threshold + 1) + f * xs_threshold % value(i_grid - xs_threshold & - % threshold + 2) - end if - end if - - if (threshold_rxn_type == 'nalpha') then - if (i_grid >= xs_threshold % threshold) then - micro_xs(i_nuclide) % nalpha = (ONE - f) * xs_threshold % value(i_grid - xs_threshold & - % threshold + 1) + f * xs_threshold % value(i_grid - xs_threshold & - % threshold + 2) - end if - end if - - - + ! Depletion-related reactions + do j = 1, 6 + i_rxn = nuc % rxn_index_MT(DEPLETION_RX(j)) + if (i_rxn > 0) then + associate (xs => nuc % reactions(i_rxn) % xs(i_temp)) + if (i_grid >= xs % threshold) then + val = (ONE - f) * xs % value(i_grid - xs % threshold + 1) + & + f * xs % value(i_grid - xs % threshold + 2) + select case (DEPLETION_RX(j)) + case (N_2N) + micro_xs(i_nuclide) % n2n = val + case (N_3N) + micro_xs(i_nuclide) % n3n = val + case (N_4N) + micro_xs(i_nuclide) % n4n = val + case (N_GAMMA) + micro_xs(i_nuclide) % ngamma = val + case (N_P) + micro_xs(i_nuclide) % np = val + case (N_A) + micro_xs(i_nuclide) % nalpha = val + end select + end if end associate - end if - end associate - end if + end do + end if ! Initialize sab treatment to false micro_xs(i_nuclide) % index_sab = NONE diff --git a/src/endf.F90 b/src/endf.F90 index 9aace42d7..0ba2db388 100644 --- a/src/endf.F90 +++ b/src/endf.F90 @@ -66,18 +66,6 @@ contains string = "fission-q-prompt" case (SCORE_FISS_Q_RECOV) string = "fission-q-recoverable" - case (SCORE_N2N) - string = "n2n" - case (SCORE_N3N) - string = "n3n" - case (SCORE_N4N) - string = "n4n" - case (SCORE_NGAMMA) - string = "ngamma" - case (SCORE_NP) - string = "np" - case (SCORE_NALPHA) - string = "nalpha" ! Normal ENDF-based reactions case (TOTAL_XS) @@ -88,12 +76,12 @@ contains string = '(n,level)' case (N_2ND) string = '(n,2nd)' - !case (N_2N) - !string = '(n,2n)' - !case (N_3N) - !string = '(n,3n)' - !case (N_FISSION) - ! string = '(n,fission)' + case (N_2N) + string = '(n,2n)' + case (N_3N) + string = '(n,3n)' + case (N_FISSION) + string = '(n,fission)' case (N_F) string = '(n,f)' case (N_NF) @@ -142,18 +130,18 @@ contains string = '(n,nc)' case (N_DISAPPEAR) string = '(n,disappear)' - !case (N_GAMMA) - ! string = '(n,gamma)' - !case (N_P) - !string = '(n,p)' + case (N_GAMMA) + string = '(n,gamma)' + case (N_P) + string = '(n,p)' case (N_D) string = '(n,d)' case (N_T) string = '(n,t)' case (N_3HE) string = '(n,3He)' - !case (N_A) - !string = '(n,a)' + case (N_A) + string = '(n,a)' case (N_2A) string = '(n,2a)' case (N_3A) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 4587072f5..91cd57ae2 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2450,17 +2450,6 @@ contains n_nuclides = index_nuclide n_sab_tables = index_sab - - do i=1, n_materials - mat => materials(i) - allocate(mat % mat_nuclide_list(n_nuclides_total)) - mat % mat_nuclide_list(:) = 0 - do j=1, mat % n_nuclides - mat % mat_nuclide_list(mat % nuclide(j))=j - end do - end do - - ! Close materials XML file call doc % clear() @@ -3058,14 +3047,14 @@ contains call fatal_error("n1n score no longer supported for tallies, & &please remove") case ('n2n', '(n,2n)') - t % score_bins(j) = SCORE_N2N - t % has_threshold_rxn = .true. + t % score_bins(j) = N_2N + case ('n3n', '(n,3n)') - t % score_bins(j) = SCORE_N3N - t % has_threshold_rxn = .true. + t % score_bins(j) = N_3N + case ('n4n', '(n,4n)') - t % score_bins(j) = SCORE_N4N - t % has_threshold_rxn = .true. + t % score_bins(j) = N_4N + case ('absorption') t % score_bins(j) = SCORE_ABSORPTION if (t % find_filter(FILTER_ENERGYOUT) > 0) then @@ -3249,12 +3238,9 @@ contains case ('(n,nc)') t % score_bins(j) = N_NC case ('(n,gamma)') - t % score_bins(j) = SCORE_NGAMMA + t % score_bins(j) = N_GAMMA case ('(n,p)') - t % score_bins(j) = SCORE_NP - - t % has_threshold_rxn = .true. - + t % score_bins(j) = N_P case ('(n,d)') t % score_bins(j) = N_D case ('(n,t)') @@ -3262,8 +3248,7 @@ contains case ('(n,3He)') t % score_bins(j) = N_3HE case ('(n,a)') - t % score_bins(j) = SCORE_NALPHA - t % has_threshold_rxn = .true. + t % score_bins(j) = N_A case ('(n,2a)') t % score_bins(j) = N_2A case ('(n,3a)') diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 1f6d45165..14e16b268 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -43,14 +43,6 @@ module nuclide_header real(8), allocatable :: nu_fission(:) ! neutron production real(8), allocatable :: absorption(:) ! absorption (MT > 100) real(8), allocatable :: heating(:) ! heating - real(8), allocatable :: ngamma(:) - real(8), allocatable :: n2n(:) - real(8), allocatable :: n3n(:) - real(8), allocatable :: n4n(:) - - real(8), allocatable :: np(:) - real(8), allocatable :: nalpha(:) - end type SumXS type :: Nuclide @@ -127,9 +119,9 @@ module nuclide_header real(8) :: fission real(8) :: nu_fission real(8) :: n2n - real(8) :: ngamma real(8) :: n3n real(8) :: n4n + real(8) :: ngamma real(8) :: np real(8) :: nalpha real(8) :: thermal ! Bound thermal elastic & inelastic scattering @@ -162,13 +154,12 @@ module nuclide_header real(8) :: absorption ! macroscopic absorption xs real(8) :: fission ! macroscopic fission xs real(8) :: nu_fission ! macroscopic production xs - real(8) :: n2n - real(8) :: n3n - real(8) :: n4n - real(8) :: ngamma - real(8) :: np - real(8) :: nalpha - + real(8) :: n2n ! macroscopic (n,2n) xs + real(8) :: n3n ! macroscopic (n,3n) xs + real(8) :: n4n ! macroscopic (n,4n) xs + real(8) :: ngamma ! macroscopic (n,gamma) xs + real(8) :: np ! macroscopic (n,p) xs + real(8) :: nalpha ! macroscopic (n,alpha) xs end type MaterialMacroXS !=============================================================================== diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 811ead4f8..373c69510 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -1131,223 +1131,81 @@ contains end if end if - - case (SCORE_N2N) - - score = ZERO - - if (i_nuclide > 0) then - !if (nuclides(i_nuclide)%reaction_index%has_key(score_bin)) then - !m = nuclides(i_nuclide)%reaction_index%get_key(score_bin) - - ! Retrieve temperature and energy grid index and interpolation - ! factor - !i_temp = micro_xs(i_nuclide) % index_temp - !if (i_temp > 0) then - !i_energy = micro_xs(i_nuclide) % index_grid - !f = micro_xs(i_nuclide) % interp_factor - - !associate (xs => nuclides(i_nuclide) % reactions(m) % xs(i_temp)) - !if (i_energy >= xs % threshold) then - score = micro_xs(i_nuclide) % n2n * atom_density * flux - - - !else - ! This block is reached if multipole is turned on and we're in - ! the resolved range. For (n,gamma), use absorption - - ! fission. For everything else, assume it's zero. - - ! score = ZERO - - else - do l = 1, materials(p % material) % n_nuclides - ! Get atom density - atom_density_ = materials(p % material) % atom_density(l) - - ! Get index in nuclides array - i_nuc = materials(p % material) % nuclide(l) - - score = score + micro_xs(i_nuc) % n2n * atom_density_ * flux - end do - end if - - - case (SCORE_N3N) - - score = ZERO - - if (i_nuclide > 0) then - !if (nuclides(i_nuclide)%reaction_index%has_key(score_bin)) then - !m = nuclides(i_nuclide)%reaction_index%get_key(score_bin) - - ! Retrieve temperature and energy grid index and interpolation - ! factor - !i_temp = micro_xs(i_nuclide) % index_temp - !if (i_temp > 0) then - !i_energy = micro_xs(i_nuclide) % index_grid - !f = micro_xs(i_nuclide) % interp_factor - - !associate (xs => nuclides(i_nuclide) % reactions(m) % - !xs(i_temp)) - !if (i_energy >= xs % threshold) then - score = micro_xs(i_nuclide) % n3n * atom_density * flux - - - !else - ! This block is reached if multipole is turned on and we're in - ! the resolved range. For (n,gamma), use absorption - - ! fission. For everything else, assume it's zero. - - ! score = ZERO - - else - do l = 1, materials(p % material) % n_nuclides - ! Get atom density - atom_density_ = materials(p % material) % atom_density(l) - - ! Get index in nuclides array - i_nuc = materials(p % material) % nuclide(l) - - score = score + micro_xs(i_nuc) % n3n * atom_density_ * flux - end do - end if - - - case (SCORE_N4N) - - score = ZERO - - if (i_nuclide > 0) then - !if (nuclides(i_nuclide)%reaction_index%has_key(score_bin)) then - !m = nuclides(i_nuclide)%reaction_index%get_key(score_bin) - - ! Retrieve temperature and energy grid index and interpolation - ! factor - !i_temp = micro_xs(i_nuclide) % index_temp - !if (i_temp > 0) then - !i_energy = micro_xs(i_nuclide) % index_grid - !f = micro_xs(i_nuclide) % interp_factor - - !associate (xs => nuclides(i_nuclide) % reactions(m) % - !xs(i_temp)) - !if (i_energy >= xs % threshold) then - score = micro_xs(i_nuclide) % n4n * atom_density * flux - - - !else - ! This block is reached if multipole is turned on and we're in - ! the resolved range. For (n,gamma), use absorption - - ! fission. For everything else, assume it's zero. - - ! score = ZERO - - else - do l = 1, materials(p % material) % n_nuclides - ! Get atom density - atom_density_ = materials(p % material) % atom_density(l) - - ! Get index in nuclides array - i_nuc = materials(p % material) % nuclide(l) - - score = score + micro_xs(i_nuc) % n4n * atom_density_ * flux - end do - end if - - case (SCORE_NP) - - score = ZERO - - if (i_nuclide > 0) then - !if (nuclides(i_nuclide)%reaction_index%has_key(score_bin)) then - !m = nuclides(i_nuclide)%reaction_index%get_key(score_bin) - - ! Retrieve temperature and energy grid index and interpolation - ! factor - !i_temp = micro_xs(i_nuclide) % index_temp - !if (i_temp > 0) then - !i_energy = micro_xs(i_nuclide) % index_grid - !f = micro_xs(i_nuclide) % interp_factor - - !associate (xs => nuclides(i_nuclide) % reactions(m) % - !xs(i_temp)) - !if (i_energy >= xs % threshold) then - score = micro_xs(i_nuclide) % np * atom_density * flux - - - !else - ! This block is reached if multipole is turned on and we're in - ! the resolved range. For (n,gamma), use absorption - - ! fission. For everything else, assume it's zero. - - ! score = ZERO - - else - do l = 1, materials(p % material) % n_nuclides - ! Get atom density - atom_density_ = materials(p % material) % atom_density(l) - - ! Get index in nuclides array - i_nuc = materials(p % material) % nuclide(l) - - score = score + micro_xs(i_nuc) % np * atom_density_ * flux - end do - end if - - case (SCORE_NALPHA) - - score = ZERO - - if (i_nuclide > 0) then - !if (nuclides(i_nuclide)%reaction_index%has_key(score_bin)) then - !m = nuclides(i_nuclide)%reaction_index%get_key(score_bin) - - ! Retrieve temperature and energy grid index and interpolation - ! factor - !i_temp = micro_xs(i_nuclide) % index_temp - !if (i_temp > 0) then - !i_energy = micro_xs(i_nuclide) % index_grid - !f = micro_xs(i_nuclide) % interp_factor - - !associate (xs => nuclides(i_nuclide) % reactions(m) % - !xs(i_temp)) - !if (i_energy >= xs % threshold) then - score = micro_xs(i_nuclide) % nalpha * atom_density * flux - - - !else - ! This block is reached if multipole is turned on and we're in - ! the resolved range. For (n,gamma), use absorption - - ! fission. For everything else, assume it's zero. - - ! score = ZERO - - else - do l = 1, materials(p % material) % n_nuclides - ! Get atom density - atom_density_ = materials(p % material) % atom_density(l) - - ! Get index in nuclides array - i_nuc = materials(p % material) % nuclide(l) - - score = score + micro_xs(i_nuc) % nalpha * atom_density_ * flux - end do - end if - - - - case (SCORE_NGAMMA) + case (N_2N) if (t % estimator == ESTIMATOR_ANALOG) then - if (survival_biasing) then - ! No absorption events actually occur if survival biasing is on -- - ! just use weight absorbed in survival biasing - score = p % absorb_wgt * flux + ! Check if event MT matches + if (p % event_MT /= N_2N) cycle SCORE_LOOP + score = p % last_wgt * flux + + else + if (i_nuclide > 0) then + score = micro_xs(i_nuclide) % n2n * atom_density * flux else - ! Skip any event where the particle wasn't absorbed - if (p % event == EVENT_SCATTER) cycle SCORE_LOOP - ! All fission and absorption events will contribute here, so we - ! can just use the particle's weight entering the collision - score = p % last_wgt * flux + score = material_xs % n2n * flux end if + end if + + case (N_3N) + if (t % estimator == ESTIMATOR_ANALOG) then + ! Check if event MT matches + if (p % event_MT /= N_3N) cycle SCORE_LOOP + score = p % last_wgt * flux + + else + if (i_nuclide > 0) then + score = micro_xs(i_nuclide) % n3n * atom_density * flux + else + score = material_xs % n3n * flux + end if + end if + + case (N_4N) + if (t % estimator == ESTIMATOR_ANALOG) then + ! Check if event MT matches + if (p % event_MT /= N_4N) cycle SCORE_LOOP + score = p % last_wgt * flux + + else + if (i_nuclide > 0) then + score = micro_xs(i_nuclide) % n4n * atom_density * flux + else + score = material_xs % n4n * flux + end if + end if + + case (N_P) + if (t % estimator == ESTIMATOR_ANALOG) then + ! Check if event MT matches + if (p % event_MT /= N_P) cycle SCORE_LOOP + score = p % last_wgt * flux + + else + if (i_nuclide > 0) then + score = micro_xs(i_nuclide) % np * atom_density * flux + else + score = material_xs % np * flux + end if + end if + + case (N_A) + if (t % estimator == ESTIMATOR_ANALOG) then + ! Check if event MT matches + if (p % event_MT /= N_A) cycle SCORE_LOOP + score = p % last_wgt * flux + + else + if (i_nuclide > 0) then + score = micro_xs(i_nuclide) % nalpha * atom_density * flux + else + score = material_xs % nalpha * flux + end if + end if + + case (N_GAMMA) + if (t % estimator == ESTIMATOR_ANALOG) then + ! Check if event MT matches + if (p % event_MT /= N_GAMMA) cycle SCORE_LOOP + score = p % last_wgt * flux else if (i_nuclide > 0) then @@ -1357,8 +1215,6 @@ contains end if end if - - case default if (t % estimator == ESTIMATOR_ANALOG) then ! Any other score is assumed to be a MT number. Thus, we just need @@ -1395,14 +1251,8 @@ contains end associate else ! This block is reached if multipole is turned on and we're in - ! the resolved range. For (n,gamma), use absorption - - ! fission. For everything else, assume it's zero. - if (score_bin == N_GAMMA) then - score = (micro_xs(i_nuclide) % absorption - & - micro_xs(i_nuclide) % fission) * atom_density * flux - else - score = ZERO - end if + ! the resolved range. Assume xs is zero. + score = ZERO end if end if @@ -1434,16 +1284,8 @@ contains end associate else ! This block is reached if multipole is turned on and - ! we're in the resolved range. For (n,gamma), use - ! absorption - fission. For everything else, assume it's - ! zero. - if (score_bin == N_GAMMA) then - score = (micro_xs(i_nuc) % absorption & - - micro_xs(i_nuc) % fission) & - * atom_density_ * flux - else - score = ZERO - end if + ! we're in the resolved range. Assume xs is zero. + score = ZERO end if end if end do @@ -2949,7 +2791,7 @@ contains type(Particle), intent(in) :: p real(8), intent(in) :: distance - integer :: check_value + integer :: i integer :: i_tally integer :: i_filt @@ -3030,7 +2872,6 @@ contains if (p % material /= MATERIAL_VOID) then ! Get pointer to current material mat => materials(p % material) - check_value= mat % mat_nuclide_list(i_nuclide) ! Determine index of nuclide in Material % atom_density array j = mat % mat_nuclide_index(i_nuclide) diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index c283c3f16..1778474e0 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -88,7 +88,6 @@ module tally_header ! reset property - allows a tally to be reset after every batch logical :: reset = .false. - logical :: has_threshold_rxn = .false. ! Number of realizations of tally random variables integer :: n_realizations = 0 From 1a5ebc7aaba19542d29912acaededcd99930f0e0 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 22 Dec 2017 15:14:23 +0700 Subject: [PATCH 078/282] Don't pre-calculate depletion reactions during inactive batches --- src/cross_section.F90 | 124 ++++++++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 53 deletions(-) diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 7066d9642..5e3d992c3 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -15,6 +15,7 @@ module cross_section use sab_header, only: SAlphaBeta, sab_tables use settings use simulation_header + use tally_header, only: active_tallies implicit none @@ -38,6 +39,9 @@ contains real(8) :: atom_density ! atom density of a nuclide real(8) :: sab_frac ! fraction of atoms affected by S(a,b) logical :: check_sab ! should we check for S(a,b) table? + logical :: in_active ! are we in active batches? + + in_active = (active_tallies % size() > 0) ! Set all material macroscopic cross sections to zero material_xs % total = ZERO @@ -107,7 +111,7 @@ contains .or. i_sab /= micro_xs(i_nuclide) % index_sab & .or. sab_frac /= micro_xs(i_nuclide) % sab_frac) then call calculate_nuclide_xs(i_nuclide, i_sab, p % E, i_grid, & - p % sqrtkT, sab_frac) + p % sqrtkT, sab_frac, in_active) end if ! ====================================================================== @@ -136,29 +140,31 @@ contains material_xs % nu_fission = material_xs % nu_fission + & atom_density * micro_xs(i_nuclide) % nu_fission - ! Add contributions to material macroscopic n2n cross section - material_xs % n2n = material_xs % n2n + & - atom_density * micro_xs(i_nuclide) % n2n + if (in_active) then + ! Add contributions to material macroscopic n2n cross section + material_xs % n2n = material_xs % n2n + & + atom_density * micro_xs(i_nuclide) % n2n - ! Add contributions to material macroscopic n3n cross section - material_xs % n3n = material_xs % n3n + & - atom_density * micro_xs(i_nuclide) % n3n + ! Add contributions to material macroscopic n3n cross section + material_xs % n3n = material_xs % n3n + & + atom_density * micro_xs(i_nuclide) % n3n - ! Add contributions to material macroscopic n4n cross section - material_xs % n4n = material_xs % n4n + & - atom_density * micro_xs(i_nuclide) % n4n + ! Add contributions to material macroscopic n4n cross section + material_xs % n4n = material_xs % n4n + & + atom_density * micro_xs(i_nuclide) % n4n - ! Add contributions to material macroscopic ngamma cross section - material_xs % ngamma = material_xs % ngamma + & - atom_density * micro_xs(i_nuclide) % ngamma + ! Add contributions to material macroscopic ngamma cross section + material_xs % ngamma = material_xs % ngamma + & + atom_density * micro_xs(i_nuclide) % ngamma - ! Add contributions to material macroscopic np cross section - material_xs % np = material_xs % np + & - atom_density * micro_xs(i_nuclide) % np + ! Add contributions to material macroscopic np cross section + material_xs % np = material_xs % np + & + atom_density * micro_xs(i_nuclide) % np - ! Add contributions to material macroscopic nalpha cross section - material_xs % nalpha = material_xs % nalpha + & - atom_density * micro_xs(i_nuclide) % nalpha + ! Add contributions to material macroscopic nalpha cross section + material_xs % nalpha = material_xs % nalpha + & + atom_density * micro_xs(i_nuclide) % nalpha + end if end do end associate @@ -170,7 +176,7 @@ contains !=============================================================================== subroutine calculate_nuclide_xs(i_nuclide, i_sab, E, i_log_union, sqrtkT, & - sab_frac) + sab_frac, in_active) integer, intent(in) :: i_nuclide ! index into nuclides array integer, intent(in) :: i_sab ! index into sab_tables array real(8), intent(in) :: E ! energy @@ -178,6 +184,7 @@ contains ! material union energy grid real(8), intent(in) :: sqrtkT ! square root of kT, material dependent real(8), intent(in) :: sab_frac ! fraction of atoms affected by S(a,b) + logical, intent(in) :: in_active logical :: use_mp ! true if XS can be calculated with windowed multipole integer :: i_temp ! index for temperature @@ -185,19 +192,14 @@ contains integer :: i_low ! lower logarithmic mapping index integer :: i_high ! upper logarithmic mapping index integer :: i_rxn ! reaction index - integer :: j - real(8) :: val + integer :: j ! index in DEPLETION_RX + real(8) :: val ! temporary xs value real(8) :: f ! interp factor on nuclide energy grid real(8) :: kT ! temperature in eV real(8) :: sigT, sigA, sigF ! Intermediate multipole variables integer, parameter :: DEPLETION_RX(6) = [N_2N, N_3N, N_4N, N_GAMMA, N_P, N_A] ! Initialize cached cross sections to zero - micro_xs(i_nuclide) % n2n = ZERO - micro_xs(i_nuclide) % n3n = ZERO - micro_xs(i_nuclide) % n4n = ZERO - micro_xs(i_nuclide) % np = ZERO - micro_xs(i_nuclide) % nalpha = ZERO micro_xs(i_nuclide) % thermal = ZERO micro_xs(i_nuclide) % thermal_elastic = ZERO @@ -227,7 +229,15 @@ contains micro_xs(i_nuclide) % fission = ZERO micro_xs(i_nuclide) % nu_fission = ZERO end if - micro_xs(i_nuclide) % ngamma = sigF - sigA + + if (in_active) then + micro_xs(i_nuclide) % ngamma = sigA - sigF + micro_xs(i_nuclide) % n2n = ZERO + micro_xs(i_nuclide) % n3n = ZERO + micro_xs(i_nuclide) % n4n = ZERO + micro_xs(i_nuclide) % np = ZERO + micro_xs(i_nuclide) % nalpha = ZERO + end if ! Ensure these values are set ! Note, the only time either is used is in one of 4 places: @@ -321,31 +331,39 @@ contains end associate ! Depletion-related reactions - do j = 1, 6 - i_rxn = nuc % rxn_index_MT(DEPLETION_RX(j)) - if (i_rxn > 0) then - associate (xs => nuc % reactions(i_rxn) % xs(i_temp)) - if (i_grid >= xs % threshold) then - val = (ONE - f) * xs % value(i_grid - xs % threshold + 1) + & - f * xs % value(i_grid - xs % threshold + 2) - select case (DEPLETION_RX(j)) - case (N_2N) - micro_xs(i_nuclide) % n2n = val - case (N_3N) - micro_xs(i_nuclide) % n3n = val - case (N_4N) - micro_xs(i_nuclide) % n4n = val - case (N_GAMMA) - micro_xs(i_nuclide) % ngamma = val - case (N_P) - micro_xs(i_nuclide) % np = val - case (N_A) - micro_xs(i_nuclide) % nalpha = val - end select - end if - end associate - end if - end do + if (in_active) then + do j = 1, 6 + i_rxn = nuc % rxn_index_MT(DEPLETION_RX(j)) + if (i_rxn > 0) then + associate (xs => nuc % reactions(i_rxn) % xs(i_temp)) + if (i_grid >= xs % threshold) then + val = (ONE - f) * xs % value(i_grid - xs % threshold + 1) + & + f * xs % value(i_grid - xs % threshold + 2) + else + val = ZERO + end if + end associate + else + val = ZERO + end if + + select case (DEPLETION_RX(j)) + case (N_2N) + micro_xs(i_nuclide) % n2n = val + case (N_3N) + micro_xs(i_nuclide) % n3n = val + case (N_4N) + micro_xs(i_nuclide) % n4n = val + case (N_GAMMA) + micro_xs(i_nuclide) % ngamma = val + case (N_P) + micro_xs(i_nuclide) % np = val + case (N_A) + micro_xs(i_nuclide) % nalpha = val + end select + end do + end if + end if ! Initialize sab treatment to false From 12ce15978d123a588c1f2fad6be5e997a47cb0c8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 22 Dec 2017 16:04:59 +0700 Subject: [PATCH 079/282] Fix repr bug in plots.py --- openmc/plots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/plots.py b/openmc/plots.py index ad0d48d11..a60b2a309 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -439,7 +439,7 @@ class Plot(IDManagerMixin): string += '{: <16}=\t{}\n'.format('\tWidth', self._width) string += '{: <16}=\t{}\n'.format('\tOrigin', self._origin) string += '{: <16}=\t{}\n'.format('\tPixels', self._origin) - string += '{: <16}=\t{}\n'.format('\tColor by', self._color) + string += '{: <16}=\t{}\n'.format('\tColor by', self._color_by) string += '{: <16}=\t{}\n'.format('\tBackground', self._background) string += '{: <16}=\t{}\n'.format('\tMask components', self._mask_components) From 26eedca6620843e5272f75996d1b9b4f7d0f7fab Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 22 Dec 2017 21:17:38 +0700 Subject: [PATCH 080/282] Revert to reaction_index name --- src/cross_section.F90 | 2 +- src/nuclide_header.F90 | 7 +++---- src/tallies/tally.F90 | 10 +++++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 5e3d992c3..ebd8a342a 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -333,7 +333,7 @@ contains ! Depletion-related reactions if (in_active) then do j = 1, 6 - i_rxn = nuc % rxn_index_MT(DEPLETION_RX(j)) + i_rxn = nuc % reaction_index(DEPLETION_RX(j)) if (i_rxn > 0) then associate (xs => nuc % reactions(i_rxn) % xs(i_temp)) if (i_grid >= xs % threshold) then diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 14e16b268..f2a53faa5 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -90,7 +90,7 @@ module nuclide_header ! Array that maps MT values to index in reactions; used at tally-time. Note ! that ENDF-102 does not assign any MT values above 891. - integer :: rxn_index_MT(891) + integer :: reaction_index(891) ! Fission energy release class(Function1D), allocatable :: fission_q_prompt ! prompt neutrons, gammas @@ -575,7 +575,7 @@ contains n_temperature = size(this % kTs) allocate(this % sum_xs(n_temperature)) - this % rxn_index_MT(:) = 0 + this % reaction_index(:) = 0 do i = 1, n_temperature ! Allocate and initialize derived cross sections n_grid = size(this % grid(i) % energy) @@ -594,9 +594,8 @@ contains i_fission = 0 do i = 1, size(this % reactions) - call MTs % push_back(this % reactions(i) % MT) - this % rxn_index_MT(this % reactions(i) % MT) = i + this % reaction_index(this % reactions(i) % MT) = i associate (rx => this % reactions(i)) ! Skip total inelastic level scattering, gas production cross sections diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 373c69510..42aaf8e67 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -247,7 +247,7 @@ contains ! multiplicities of one. score = p % last_wgt * flux else - m = nuclides(p % event_nuclide) % rxn_index_MT(p % event_MT) + m = nuclides(p % event_nuclide) % reaction_index(p % event_MT) ! Get yield and apply to score associate (rxn => nuclides(p % event_nuclide) % reactions(m)) @@ -273,7 +273,7 @@ contains ! multiplicities of one. score = p % last_wgt * flux else - m = nuclides(p % event_nuclide) % rxn_index_MT(p % event_MT) + m = nuclides(p % event_nuclide) % reaction_index(p % event_MT) ! Get yield and apply to score associate (rxn => nuclides(p % event_nuclide) % reactions(m)) @@ -299,7 +299,7 @@ contains ! multiplicities of one. score = p % last_wgt * flux else - m = nuclides(p % event_nuclide) % rxn_index_MT(p % event_MT) + m = nuclides(p % event_nuclide) % reaction_index(p % event_MT) ! Get yield and apply to score associate (rxn => nuclides(p%event_nuclide)%reactions(m)) @@ -1232,7 +1232,7 @@ contains score = ZERO if (i_nuclide > 0) then - m = nuclides(i_nuclide) % rxn_index_MT(score_bin) + m = nuclides(i_nuclide) % reaction_index(score_bin) if (m /= 0) then ! Retrieve temperature and energy grid index and interpolation ! factor @@ -1265,7 +1265,7 @@ contains ! Get index in nuclides array i_nuc = materials(p % material) % nuclide(l) - m = nuclides(i_nuc) % rxn_index_MT(score_bin) + m = nuclides(i_nuc) % reaction_index(score_bin) if (m /= 0) then ! Retrieve temperature and energy grid index and ! interpolation factor From e137187510efee5befba8bb353b09b9b134460d1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 23 Dec 2017 12:53:54 +0700 Subject: [PATCH 081/282] Don't cache macroscopic cross sections for depletion reactions --- src/cross_section.F90 | 32 -------------------- src/nuclide_header.F90 | 24 ++++++--------- src/tallies/tally.F90 | 66 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 69 insertions(+), 53 deletions(-) diff --git a/src/cross_section.F90 b/src/cross_section.F90 index ebd8a342a..0ac083538 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -49,12 +49,6 @@ contains material_xs % absorption = ZERO material_xs % fission = ZERO material_xs % nu_fission = ZERO - material_xs % n2n = ZERO - material_xs % n3n = ZERO - material_xs % n4n = ZERO - material_xs % ngamma = ZERO - material_xs % np = ZERO - material_xs % nalpha = ZERO ! Exit subroutine if material is void if (p % material == MATERIAL_VOID) return @@ -139,32 +133,6 @@ contains ! Add contributions to material macroscopic nu-fission cross section material_xs % nu_fission = material_xs % nu_fission + & atom_density * micro_xs(i_nuclide) % nu_fission - - if (in_active) then - ! Add contributions to material macroscopic n2n cross section - material_xs % n2n = material_xs % n2n + & - atom_density * micro_xs(i_nuclide) % n2n - - ! Add contributions to material macroscopic n3n cross section - material_xs % n3n = material_xs % n3n + & - atom_density * micro_xs(i_nuclide) % n3n - - ! Add contributions to material macroscopic n4n cross section - material_xs % n4n = material_xs % n4n + & - atom_density * micro_xs(i_nuclide) % n4n - - ! Add contributions to material macroscopic ngamma cross section - material_xs % ngamma = material_xs % ngamma + & - atom_density * micro_xs(i_nuclide) % ngamma - - ! Add contributions to material macroscopic np cross section - material_xs % np = material_xs % np + & - atom_density * micro_xs(i_nuclide) % np - - ! Add contributions to material macroscopic nalpha cross section - material_xs % nalpha = material_xs % nalpha + & - atom_density * micro_xs(i_nuclide) % nalpha - end if end do end associate diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index f2a53faa5..661ea4850 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -115,15 +115,15 @@ module nuclide_header real(8) :: total real(8) :: elastic ! If sab_frac is not 1 or 0, then this value is ! averaged over bound and non-bound nuclei - real(8) :: absorption - real(8) :: fission - real(8) :: nu_fission - real(8) :: n2n - real(8) :: n3n - real(8) :: n4n - real(8) :: ngamma - real(8) :: np - real(8) :: nalpha + real(8) :: absorption ! absorption (disappearance) + real(8) :: fission ! fission + real(8) :: nu_fission ! neutron production from fission + real(8) :: n2n ! (n,2n) + real(8) :: n3n ! (n,3n) + real(8) :: n4n ! (n,4n) + real(8) :: ngamma ! (n,gamma) + real(8) :: np ! (n,p) + real(8) :: nalpha ! (n,alpha) real(8) :: thermal ! Bound thermal elastic & inelastic scattering real(8) :: thermal_elastic ! Bound thermal elastic scattering @@ -154,12 +154,6 @@ module nuclide_header real(8) :: absorption ! macroscopic absorption xs real(8) :: fission ! macroscopic fission xs real(8) :: nu_fission ! macroscopic production xs - real(8) :: n2n ! macroscopic (n,2n) xs - real(8) :: n3n ! macroscopic (n,3n) xs - real(8) :: n4n ! macroscopic (n,4n) xs - real(8) :: ngamma ! macroscopic (n,gamma) xs - real(8) :: np ! macroscopic (n,p) xs - real(8) :: nalpha ! macroscopic (n,alpha) xs end type MaterialMacroXS !=============================================================================== diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 42aaf8e67..d96ddb7cb 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -1141,7 +1141,16 @@ contains if (i_nuclide > 0) then score = micro_xs(i_nuclide) % n2n * atom_density * flux else - score = material_xs % n2n * flux + score = ZERO + if (p % material /= MATERIAL_VOID) then + associate (mat => materials(p % material)) + do l = 1, materials(p % material) % n_nuclides + i_nuc = mat % nuclide(l) + atom_density_ = mat % atom_density(l) + score = score + micro_xs(i_nuc) % n2n * atom_density_ * flux + end do + end associate + end if end if end if @@ -1155,7 +1164,16 @@ contains if (i_nuclide > 0) then score = micro_xs(i_nuclide) % n3n * atom_density * flux else - score = material_xs % n3n * flux + score = ZERO + if (p % material /= MATERIAL_VOID) then + associate (mat => materials(p % material)) + do l = 1, materials(p % material) % n_nuclides + i_nuc = mat % nuclide(l) + atom_density_ = mat % atom_density(l) + score = score + micro_xs(i_nuc) % n3n * atom_density_ * flux + end do + end associate + end if end if end if @@ -1169,7 +1187,16 @@ contains if (i_nuclide > 0) then score = micro_xs(i_nuclide) % n4n * atom_density * flux else - score = material_xs % n4n * flux + score = ZERO + if (p % material /= MATERIAL_VOID) then + associate (mat => materials(p % material)) + do l = 1, materials(p % material) % n_nuclides + i_nuc = mat % nuclide(l) + atom_density_ = mat % atom_density(l) + score = score + micro_xs(i_nuc) % n4n * atom_density_ * flux + end do + end associate + end if end if end if @@ -1183,7 +1210,16 @@ contains if (i_nuclide > 0) then score = micro_xs(i_nuclide) % np * atom_density * flux else - score = material_xs % np * flux + score = ZERO + if (p % material /= MATERIAL_VOID) then + associate (mat => materials(p % material)) + do l = 1, materials(p % material) % n_nuclides + i_nuc = mat % nuclide(l) + atom_density_ = mat % atom_density(l) + score = score + micro_xs(i_nuc) % np * atom_density_ * flux + end do + end associate + end if end if end if @@ -1197,7 +1233,16 @@ contains if (i_nuclide > 0) then score = micro_xs(i_nuclide) % nalpha * atom_density * flux else - score = material_xs % nalpha * flux + score = ZERO + if (p % material /= MATERIAL_VOID) then + associate (mat => materials(p % material)) + do l = 1, materials(p % material) % n_nuclides + i_nuc = mat % nuclide(l) + atom_density_ = mat % atom_density(l) + score = score + micro_xs(i_nuc) % nalpha * atom_density_ * flux + end do + end associate + end if end if end if @@ -1211,7 +1256,16 @@ contains if (i_nuclide > 0) then score = micro_xs(i_nuclide) % ngamma * atom_density * flux else - score = material_xs % ngamma * flux + score = ZERO + if (p % material /= MATERIAL_VOID) then + associate (mat => materials(p % material)) + do l = 1, materials(p % material) % n_nuclides + i_nuc = mat % nuclide(l) + atom_density_ = mat % atom_density(l) + score = score + micro_xs(i_nuc) % ngamma * atom_density_ * flux + end do + end associate + end if end if end if From ae2d8721438c5a5641e884c92519d9585d8438f4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 23 Dec 2017 13:11:17 +0700 Subject: [PATCH 082/282] Simplify depletion xs cache by making it an array in NuclideMicroXS --- src/constants.F90 | 3 + src/cross_section.F90 | 39 ++++-------- src/nuclide_header.F90 | 10 ++- src/tallies/tally.F90 | 139 ++++++----------------------------------- 4 files changed, 39 insertions(+), 152 deletions(-) diff --git a/src/constants.F90 b/src/constants.F90 index 853a40c8d..3968781e9 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -219,6 +219,9 @@ module constants N_D0 = 650, N_DC = 699, N_T0 = 700, N_TC = 749, N_3HE0 = 750, & 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] + ! ACE table types integer, parameter :: & ACE_NEUTRON = 1, & ! continuous-energy neutron diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 0ac083538..0bda4a4f1 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -165,7 +165,6 @@ contains real(8) :: f ! interp factor on nuclide energy grid real(8) :: kT ! temperature in eV real(8) :: sigT, sigA, sigF ! Intermediate multipole variables - integer, parameter :: DEPLETION_RX(6) = [N_2N, N_3N, N_4N, N_GAMMA, N_P, N_A] ! Initialize cached cross sections to zero micro_xs(i_nuclide) % thermal = ZERO @@ -199,12 +198,11 @@ contains end if if (in_active) then - micro_xs(i_nuclide) % ngamma = sigA - sigF - micro_xs(i_nuclide) % n2n = ZERO - micro_xs(i_nuclide) % n3n = ZERO - micro_xs(i_nuclide) % n4n = ZERO - micro_xs(i_nuclide) % np = ZERO - micro_xs(i_nuclide) % nalpha = ZERO + ! Initialize all reaction cross sections to zero + micro_xs(i_nuclide) % reaction(:) = ZERO + + ! Only non-zero reaction is (n,gamma) + micro_xs(i_nuclide) % reaction(4) = sigA - sigF end if ! Ensure these values are set @@ -301,34 +299,21 @@ contains ! Depletion-related reactions if (in_active) then do j = 1, 6 + ! Initialize reaction xs to zero + micro_xs(i_nuclide) % reaction(j) = ZERO + + ! If reaction is present and energy is greater than threshold, set + ! the reaction xs appropriately i_rxn = nuc % reaction_index(DEPLETION_RX(j)) if (i_rxn > 0) then associate (xs => nuc % reactions(i_rxn) % xs(i_temp)) if (i_grid >= xs % threshold) then - val = (ONE - f) * xs % value(i_grid - xs % threshold + 1) + & + micro_xs(i_nuclide) % reaction(j) = (ONE - f) * & + xs % value(i_grid - xs % threshold + 1) + & f * xs % value(i_grid - xs % threshold + 2) - else - val = ZERO end if end associate - else - val = ZERO end if - - select case (DEPLETION_RX(j)) - case (N_2N) - micro_xs(i_nuclide) % n2n = val - case (N_3N) - micro_xs(i_nuclide) % n3n = val - case (N_4N) - micro_xs(i_nuclide) % n4n = val - case (N_GAMMA) - micro_xs(i_nuclide) % ngamma = val - case (N_P) - micro_xs(i_nuclide) % np = val - case (N_A) - micro_xs(i_nuclide) % nalpha = val - end select end do end if diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 661ea4850..38e89b648 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -118,15 +118,13 @@ module nuclide_header real(8) :: absorption ! absorption (disappearance) real(8) :: fission ! fission real(8) :: nu_fission ! neutron production from fission - real(8) :: n2n ! (n,2n) - real(8) :: n3n ! (n,3n) - real(8) :: n4n ! (n,4n) - real(8) :: ngamma ! (n,gamma) - real(8) :: np ! (n,p) - real(8) :: nalpha ! (n,alpha) real(8) :: thermal ! Bound thermal elastic & inelastic scattering real(8) :: thermal_elastic ! Bound thermal elastic scattering + ! Cross sections for depletion reactions (note that these are not stored in + ! macroscopic cache) + real(8) :: reaction(size(DEPLETION_RX)) + ! Indicies and factors needed to compute cross sections from the data tables integer :: index_grid ! Index on nuclide energy grid integer :: index_temp ! Temperature index for nuclide diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index d96ddb7cb..c5219a390 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -1131,15 +1131,31 @@ contains end if end if - case (N_2N) + case (N_2N, N_3N, N_4N, N_GAMMA, N_P, N_A) if (t % estimator == ESTIMATOR_ANALOG) then ! Check if event MT matches - if (p % event_MT /= N_2N) cycle SCORE_LOOP + if (p % event_MT /= score_bin) cycle SCORE_LOOP score = p % last_wgt * flux else + ! Determine index in NuclideMicroXS % reaction array + select case (score_bin) + case (N_2N) + m = 1 + case (N_3N) + m = 2 + case (N_4N) + m = 3 + case (N_GAMMA) + m = 4 + case (N_P) + m = 5 + case (N_A) + m = 6 + end select + if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % n2n * atom_density * flux + score = micro_xs(i_nuclide) % reaction(m) * atom_density * flux else score = ZERO if (p % material /= MATERIAL_VOID) then @@ -1147,122 +1163,7 @@ contains do l = 1, materials(p % material) % n_nuclides i_nuc = mat % nuclide(l) atom_density_ = mat % atom_density(l) - score = score + micro_xs(i_nuc) % n2n * atom_density_ * flux - end do - end associate - end if - end if - end if - - case (N_3N) - if (t % estimator == ESTIMATOR_ANALOG) then - ! Check if event MT matches - if (p % event_MT /= N_3N) cycle SCORE_LOOP - score = p % last_wgt * flux - - else - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % n3n * atom_density * flux - else - score = ZERO - if (p % material /= MATERIAL_VOID) then - associate (mat => materials(p % material)) - do l = 1, materials(p % material) % n_nuclides - i_nuc = mat % nuclide(l) - atom_density_ = mat % atom_density(l) - score = score + micro_xs(i_nuc) % n3n * atom_density_ * flux - end do - end associate - end if - end if - end if - - case (N_4N) - if (t % estimator == ESTIMATOR_ANALOG) then - ! Check if event MT matches - if (p % event_MT /= N_4N) cycle SCORE_LOOP - score = p % last_wgt * flux - - else - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % n4n * atom_density * flux - else - score = ZERO - if (p % material /= MATERIAL_VOID) then - associate (mat => materials(p % material)) - do l = 1, materials(p % material) % n_nuclides - i_nuc = mat % nuclide(l) - atom_density_ = mat % atom_density(l) - score = score + micro_xs(i_nuc) % n4n * atom_density_ * flux - end do - end associate - end if - end if - end if - - case (N_P) - if (t % estimator == ESTIMATOR_ANALOG) then - ! Check if event MT matches - if (p % event_MT /= N_P) cycle SCORE_LOOP - score = p % last_wgt * flux - - else - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % np * atom_density * flux - else - score = ZERO - if (p % material /= MATERIAL_VOID) then - associate (mat => materials(p % material)) - do l = 1, materials(p % material) % n_nuclides - i_nuc = mat % nuclide(l) - atom_density_ = mat % atom_density(l) - score = score + micro_xs(i_nuc) % np * atom_density_ * flux - end do - end associate - end if - end if - end if - - case (N_A) - if (t % estimator == ESTIMATOR_ANALOG) then - ! Check if event MT matches - if (p % event_MT /= N_A) cycle SCORE_LOOP - score = p % last_wgt * flux - - else - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % nalpha * atom_density * flux - else - score = ZERO - if (p % material /= MATERIAL_VOID) then - associate (mat => materials(p % material)) - do l = 1, materials(p % material) % n_nuclides - i_nuc = mat % nuclide(l) - atom_density_ = mat % atom_density(l) - score = score + micro_xs(i_nuc) % nalpha * atom_density_ * flux - end do - end associate - end if - end if - end if - - case (N_GAMMA) - if (t % estimator == ESTIMATOR_ANALOG) then - ! Check if event MT matches - if (p % event_MT /= N_GAMMA) cycle SCORE_LOOP - score = p % last_wgt * flux - - else - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % ngamma * atom_density * flux - else - score = ZERO - if (p % material /= MATERIAL_VOID) then - associate (mat => materials(p % material)) - do l = 1, materials(p % material) % n_nuclides - i_nuc = mat % nuclide(l) - atom_density_ = mat % atom_density(l) - score = score + micro_xs(i_nuc) % ngamma * atom_density_ * flux + score = score + micro_xs(i_nuc) % reaction(m) * atom_density_ * flux end do end associate end if From a793b9a0878984c3e9b89d557d699c2d52f37c88 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 23 Dec 2017 16:02:48 +0700 Subject: [PATCH 083/282] Don't calculate depletion reaction cross sections unless tallies need them --- src/cross_section.F90 | 12 ++++-------- src/input_xml.F90 | 6 ++++++ src/simulation.F90 | 6 ++++-- src/simulation_header.F90 | 7 ++++--- src/tallies/tally.F90 | 3 +++ src/tallies/tally_header.F90 | 1 + 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 0bda4a4f1..2b3b32a9c 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -39,9 +39,6 @@ contains real(8) :: atom_density ! atom density of a nuclide real(8) :: sab_frac ! fraction of atoms affected by S(a,b) logical :: check_sab ! should we check for S(a,b) table? - logical :: in_active ! are we in active batches? - - in_active = (active_tallies % size() > 0) ! Set all material macroscopic cross sections to zero material_xs % total = ZERO @@ -105,7 +102,7 @@ contains .or. i_sab /= micro_xs(i_nuclide) % index_sab & .or. sab_frac /= micro_xs(i_nuclide) % sab_frac) then call calculate_nuclide_xs(i_nuclide, i_sab, p % E, i_grid, & - p % sqrtkT, sab_frac, in_active) + p % sqrtkT, sab_frac) end if ! ====================================================================== @@ -144,7 +141,7 @@ contains !=============================================================================== subroutine calculate_nuclide_xs(i_nuclide, i_sab, E, i_log_union, sqrtkT, & - sab_frac, in_active) + sab_frac) integer, intent(in) :: i_nuclide ! index into nuclides array integer, intent(in) :: i_sab ! index into sab_tables array real(8), intent(in) :: E ! energy @@ -152,7 +149,6 @@ contains ! material union energy grid real(8), intent(in) :: sqrtkT ! square root of kT, material dependent real(8), intent(in) :: sab_frac ! fraction of atoms affected by S(a,b) - logical, intent(in) :: in_active logical :: use_mp ! true if XS can be calculated with windowed multipole integer :: i_temp ! index for temperature @@ -197,7 +193,7 @@ contains micro_xs(i_nuclide) % nu_fission = ZERO end if - if (in_active) then + if (need_depletion_rx) then ! Initialize all reaction cross sections to zero micro_xs(i_nuclide) % reaction(:) = ZERO @@ -297,7 +293,7 @@ contains end associate ! Depletion-related reactions - if (in_active) then + if (need_depletion_rx) then do j = 1, 6 ! Initialize reaction xs to zero micro_xs(i_nuclide) % reaction(j) = ZERO diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 91cd57ae2..4dfdb178d 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -3048,12 +3048,15 @@ contains &please remove") case ('n2n', '(n,2n)') t % score_bins(j) = N_2N + t % depletion_rx = .true. case ('n3n', '(n,3n)') t % score_bins(j) = N_3N + t % depletion_rx = .true. case ('n4n', '(n,4n)') t % score_bins(j) = N_4N + t % depletion_rx = .true. case ('absorption') t % score_bins(j) = SCORE_ABSORPTION @@ -3239,8 +3242,10 @@ contains t % score_bins(j) = N_NC case ('(n,gamma)') t % score_bins(j) = N_GAMMA + t % depletion_rx = .true. case ('(n,p)') t % score_bins(j) = N_P + t % depletion_rx = .true. case ('(n,d)') t % score_bins(j) = N_D case ('(n,t)') @@ -3249,6 +3254,7 @@ contains t % score_bins(j) = N_3HE case ('(n,a)') t % score_bins(j) = N_A + t % depletion_rx = .true. case ('(n,2a)') t % score_bins(j) = N_2A case ('(n,3a)') diff --git a/src/simulation.F90 b/src/simulation.F90 index ade209e36..dde2332db 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -451,8 +451,9 @@ contains end if end if - ! Reset current batch + ! Reset global variables current_batch = 0 + need_depletion_rx = .false. ! Set flag indicating initialization is done simulation_initialized = .true. @@ -535,7 +536,8 @@ contains if (check_overlaps) call print_overlap_check() end if - ! Reset initialization flag + ! Reset flags + need_depletion_rx = .false. simulation_initialized = .false. end subroutine openmc_simulation_finalize diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index 5041f0793..3aaa213a9 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -20,10 +20,11 @@ module simulation_header ! ============================================================================ ! SIMULATION VARIABLES - integer :: current_batch ! current batch - integer :: current_gen ! current generation within a batch - integer :: total_gen = 0 ! total number of generations simulated + integer :: current_batch ! current batch + integer :: current_gen ! current generation within a batch + integer :: total_gen = 0 ! total number of generations simulated logical(C_BOOL), bind(C) :: simulation_initialized = .false. + logical :: need_depletion_rx ! need to calculate depletion reaction rx? ! ============================================================================ ! TALLY PRECISION TRIGGER VARIABLES diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index c5219a390..a05cf3984 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -4381,6 +4381,9 @@ contains elseif (t % type == TALLY_SURFACE) then call active_surface_tallies % push_back(i) end if + + ! Check if tally contains depletion reactions and if so, set flag + if (t % depletion_rx) need_depletion_rx = .true. end if end associate end do diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 1778474e0..5c0256dfb 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -48,6 +48,7 @@ module tally_header integer :: estimator = ESTIMATOR_TRACKLENGTH ! collision, track-length real(8) :: volume ! volume of region logical :: active = .false. + logical :: depletion_rx = .false. ! has depletion reactions, e.g. (n,2n) integer, allocatable :: filter(:) ! index in filters array ! The stride attribute is used for determining the index in the results From 8f95199fa1f1784a8b278b622843c13368c95b9b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 24 Dec 2017 17:43:14 +0700 Subject: [PATCH 084/282] Update release notes, release version, fix doc build bug --- docs/source/conf.py | 6 +- docs/source/pythonapi/capi.rst | 56 ++++++------ docs/source/releasenotes.rst | 151 +++++++++++---------------------- openmc/__init__.py | 2 +- src/constants.F90 | 2 +- src/output.F90 | 2 +- 6 files changed, 84 insertions(+), 135 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index ae8bfe6b5..834c47a40 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -27,7 +27,7 @@ except ImportError: MOCK_MODULES = ['numpy', 'numpy.polynomial', 'numpy.polynomial.polynomial', 'numpy.ctypeslib', 'scipy', 'scipy.sparse', 'scipy.interpolate', 'scipy.integrate', 'scipy.optimize', 'scipy.special', - 'scipy.stats', 'h5py', 'pandas', 'uncertainties', + 'scipy.stats', 'h5py', 'pandas', 'uncertainties', 'matplotlib', 'matplotlib.pyplot','openmoc', 'openmc.data.reconstruct'] sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES) @@ -76,9 +76,9 @@ copyright = u'2011-2017, Massachusetts Institute of Technology' # built documents. # # The short X.Y version. -version = "0.9" +version = "0.10" # The full version, including alpha/beta/rc tags. -release = "0.9.0" +release = "0.10.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index 3c6300bb6..f35823399 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -1,6 +1,6 @@ ---------------------------------------------------- -:data:`openmc.capi` -- Python bindings to the C API ---------------------------------------------------- +-------------------------------------------------- +:mod:`openmc.capi` -- Python bindings to the C API +-------------------------------------------------- .. automodule:: openmc.capi @@ -12,25 +12,25 @@ Functions :nosignatures: :template: myfunction.rst - openmc.capi.calculate_volumes - openmc.capi.finalize - openmc.capi.find_cell - openmc.capi.find_material - openmc.capi.hard_reset - openmc.capi.init - openmc.capi.iter_batches - openmc.capi.keff - openmc.capi.load_nuclide - openmc.capi.next_batch - openmc.capi.num_realizations - openmc.capi.plot_geometry - openmc.capi.reset - openmc.capi.run - openmc.capi.run_in_memory - openmc.capi.simulation_init - openmc.capi.simulation_finalize - openmc.capi.source_bank - openmc.capi.statepoint_write + calculate_volumes + finalize + find_cell + find_material + hard_reset + init + iter_batches + keff + load_nuclide + next_batch + num_realizations + plot_geometry + reset + run + run_in_memory + simulation_init + simulation_finalize + source_bank + statepoint_write Classes ------- @@ -40,9 +40,9 @@ Classes :nosignatures: :template: myclass.rst - openmc.capi.Cell - openmc.capi.EnergyFilter - openmc.capi.MaterialFilter - openmc.capi.Material - openmc.capi.Nuclide - openmc.capi.Tally + Cell + EnergyFilter + MaterialFilter + Material + Nuclide + Tally diff --git a/docs/source/releasenotes.rst b/docs/source/releasenotes.rst index d647f1ff3..4cacd4a7f 100644 --- a/docs/source/releasenotes.rst +++ b/docs/source/releasenotes.rst @@ -1,59 +1,38 @@ .. _releasenotes: -============================== -Release Notes for OpenMC 0.9.0 -============================== +=============================== +Release Notes for OpenMC 0.10.0 +=============================== .. currentmodule:: openmc -This release of OpenMC is the first release to use a new native HDF5 cross -section format rather than ACE format cross sections. Other significant new -features include a nuclear data interface in the Python API (:mod:`openmc.data`) -a stochastic volume calculation capability, a random sphere packing algorithm -that can handle packing fractions up to 60%, and a new XML parser with -significantly better performance than the parser used previously. - -.. caution:: With the new cross section format, the default energy units are now - **electronvolts (eV)** rather than megaelectronvolts (MeV)! If you - are specifying an energy filter for a tally, make sure you use - units of eV now. +This release of OpenMC includes several new features, performance improvements, +and bug fixes compared to version 0.9.0. Notably, a C API has been added that +enables in-memory coupling of neutronics to other physics fields, e.g., burnup +calculations and thermal-hydraulics. The C API is also backed by Python bindings +in a new :mod:`openmc.capi` package. Users should be forewarned that the C API +is still in an experimental state and the interface is likely to undergo changes +in future versions. The Python API continues to improve over time; several backwards incompatible changes were made in the API which users of previous versions should take note of: -- Each type of tally filter is now specified with a separate class. For example:: +- To indicate that nuclides in a material should be treated such that elastic + scattering is isotropic in the laboratory system, there is a new + :attr:`Material.isotropic` property:: - energy_filter = openmc.EnergyFilter([0.0, 0.625, 4.0, 1.0e6, 20.0e6]) + mat = openmc.Material() + mat.add_nuclide('H1', 1.0) + mat.isotropic = ['H1'] -- Several attributes of the :class:`Plot` class have changed (``color`` -> - ``color_by`` and ``col_spec`` > ``colors``). :attr:`Plot.colors` now accepts a - dictionary mapping :class:`Cell` or :class:`Material` instances to RGB - 3-tuples or string colors names, e.g.:: +- The initializers for :class:`openmc.Intersection` and :class:`openmc.Union` + now expect an iterable. - plot.colors = { - fuel: 'yellow', - water: 'blue' - } +- Auto-generated unique IDs for classes now start from 1 rather than 10000. -- ``make_hexagon_region`` is now :func:`get_hexagonal_prism` -- Several changes in :class:`Settings` attributes: - - - ``weight`` is now set as ``Settings.cutoff['weight']`` - - Shannon entropy is now specified by passing a :class:`openmc.Mesh` to - :attr:`Settings.entropy_mesh` - - Uniform fission site method is now specified by passing a - :class:`openmc.Mesh` to :attr:`Settings.ufs_mesh` - - All ``sourcepoint_*`` options are now specified in a - :attr:`Settings.sourcepoint` dictionary - - Resonance scattering method is now specified as a dictionary in - :attr:`Settings.resonance_scattering` - - Multipole is now turned on by setting ``Settings.temperature['multipole'] = - True`` - - The ``output_path`` attribute is now ``Settings.output['path']`` - -- All the ``openmc.mgxs.Nu*`` classes are gone. Instead, a ``nu`` argument was - added to the constructor of the corresponding classes. +.. attention:: This is the last release of OpenMC that will support Python + 2.7. Future releases of OpenMC will require Python 3.4 or later. ------------------- System Requirements @@ -69,69 +48,34 @@ problem at hand (mostly on the number of nuclides and tallies in the problem). New Features ------------ -- Stochastic volume calculations -- Multi-delayed group cross section generation -- Ability to calculate multi-group cross sections over meshes -- Temperature interpolation on cross section data -- Nuclear data interface in Python API, :mod:`openmc.data` -- Allow cutoff energy via :attr:`Settings.cutoff` -- Ability to define fuel by enrichment (see :meth:`Material.add_element`) -- Random sphere packing for TRISO particle generation, - :func:`openmc.model.pack_trisos` -- Critical eigenvalue search, :func:`openmc.search_for_keff` -- Model container, :class:`openmc.model.Model` -- In-line plotting in Jupyter, :func:`openmc.plot_inline` -- Energy function tally filters, :class:`openmc.EnergyFunctionFilter` -- Replaced FoX XML parser with `pugixml `_ -- Cell/material instance counting, :meth:`Geometry.determine_paths` -- Differential tallies (see :class:`openmc.TallyDerivative`) -- Consistent multi-group scattering matrices -- Improved documentation and new Jupyter notebooks -- OpenMOC compatibility module, :mod:`openmc.openmoc_compatible` +- Rotationally-periodic boundary conditions +- C API (with Python bindings) for in-memory coupling +- Improved correlation for Uranium enrichment +- Support for partial S(a,b) tables +- Improved handling of autogenerated IDs +- Many performance/memory improvements --------- Bug Fixes --------- -- c5df6c_: Fix mesh filter max iterator check -- 1cfa39_: Reject external source only if 95% of sites are rejected -- 335359_: Fix bug in plotting meshlines -- 17c678_: Make sure system_clock uses high-resolution timer -- 23ec0b_: Fix use of S(a,b) with multipole data -- 7eefb7_: Fix several bugs in tally module -- 7880d4_: Allow plotting calculation with no boundary conditions -- ad2d9f_: Fix filter weight missing when scoring all nuclides -- 59fdca_: Fix use of source files for fixed source calculations -- 9eff5b_: Fix thermal scattering bugs -- 7848a9_: Fix combined k-eff estimator producing NaN -- f139ce_: Fix printing bug for tallies with AggregateNuclide -- b8ddfa_: Bugfix for short tracks near tally mesh edges -- ec3cfb_: Fix inconsistency in filter weights -- 5e9b06_: Fix XML representation for verbosity -- c39990_: Fix bug tallying reaction rates with multipole on -- c6b67e_: Fix fissionable source sampling bug -- 489540_: Check for void materials in tracklength tallies -- f0214f_: Fixes/improvements to the ARES algorithm +- 937469_: Fix energy group sampling for multi-group simulations +- a149ef_: Ensure mutable objects are not hashable +- 2c9b21_: Preserve backwards compatibility for generated HDF5 libraries +- 8047f6_: Handle units of division for tally arithmetic correctly +- 0beb4c_: Compatibility with newer versions of Pandas +- f124be_: Fix generating 0K data with openmc.data.njoy module +- 0c6915_: Bugfix for generating thermal scattering data +- 61ecb4_: Fix bugs in Python multipole objects -.. _c5df6c: https://github.com/mit-crpg/openmc/commit/c5df6c -.. _1cfa39: https://github.com/mit-crpg/openmc/commit/1cfa39 -.. _335359: https://github.com/mit-crpg/openmc/commit/335359 -.. _17c678: https://github.com/mit-crpg/openmc/commit/17c678 -.. _23ec0b: https://github.com/mit-crpg/openmc/commit/23ec0b -.. _7eefb7: https://github.com/mit-crpg/openmc/commit/7eefb7 -.. _7880d4: https://github.com/mit-crpg/openmc/commit/7880d4 -.. _ad2d9f: https://github.com/mit-crpg/openmc/commit/ad2d9f -.. _59fdca: https://github.com/mit-crpg/openmc/commit/59fdca -.. _9eff5b: https://github.com/mit-crpg/openmc/commit/9eff5b -.. _7848a9: https://github.com/mit-crpg/openmc/commit/7848a9 -.. _f139ce: https://github.com/mit-crpg/openmc/commit/f139ce -.. _b8ddfa: https://github.com/mit-crpg/openmc/commit/b8ddfa -.. _ec3cfb: https://github.com/mit-crpg/openmc/commit/ec3cfb -.. _5e9b06: https://github.com/mit-crpg/openmc/commit/5e9b06 -.. _c39990: https://github.com/mit-crpg/openmc/commit/c39990 -.. _c6b67e: https://github.com/mit-crpg/openmc/commit/c6b67e -.. _489540: https://github.com/mit-crpg/openmc/commit/489540 -.. _f0214f: https://github.com/mit-crpg/openmc/commit/f0214f +.. _937469: https://github.com/mit-crpg/openmc/commit/937469 +.. _a149ef: https://github.com/mit-crpg/openmc/commit/a149ef +.. _2c9b21: https://github.com/mit-crpg/openmc/commit/2c9b21 +.. _8047f6: https://github.com/mit-crpg/openmc/commit/8047f6 +.. _0beb4c: https://github.com/mit-crpg/openmc/commit/0beb4c +.. _f124be: https://github.com/mit-crpg/openmc/commit/f124be +.. _0c6915: https://github.com/mit-crpg/openmc/commit/0c6915 +.. _61ecb4: https://github.com/mit-crpg/openmc/commit/61ecb4 ------------ Contributors @@ -139,14 +83,19 @@ Contributors This release contains new contributions from the following people: +- `Brody Bassett `_ - `Will Boyd `_ +- `Guillaume Giudicelli `_ +- `Brittany Grayson `_ - `Sterling Harper `_ -- `Qingming He <906459647@qq.com>`_ - `Colin Josey `_ - `Travis Labossiere-Hickman `_ - `Jingang Liang `_ +- `Alex Lindsay `_ +- `Johnny Liu `_ - `Amanda Lund `_ +- `April Novak `_ - `Adam Nelson `_ +- `Jose Salcedo Perez `_ - `Paul Romano `_ - `Sam Shaner `_ -- `Jon Walsh `_ diff --git a/openmc/__init__.py b/openmc/__init__.py index 13dac05e7..9b13fa692 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -32,4 +32,4 @@ from . import examples # Import a few convencience functions that used to be here from openmc.model import get_rectangular_prism, get_hexagonal_prism -__version__ = '0.9.0' +__version__ = '0.10.0' diff --git a/src/constants.F90 b/src/constants.F90 index 3968781e9..331a644f0 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -9,7 +9,7 @@ module constants ! OpenMC major, minor, and release numbers integer, parameter :: VERSION_MAJOR = 0 - integer, parameter :: VERSION_MINOR = 9 + integer, parameter :: VERSION_MINOR = 10 integer, parameter :: VERSION_RELEASE = 0 integer, parameter :: & VERSION(3) = [VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE] diff --git a/src/output.F90 b/src/output.F90 index d09beda1b..f9660638a 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -79,7 +79,7 @@ contains ' Copyright | 2011-2017 Massachusetts Institute of Technology' write(UNIT=OUTPUT_UNIT, FMT=*) & ' License | http://openmc.readthedocs.io/en/latest/license.html' - write(UNIT=OUTPUT_UNIT, FMT='(11X,"Version | ",I1,".",I1,".",I1)') & + write(UNIT=OUTPUT_UNIT, FMT='(11X,"Version | ",I1,".",I2,".",I1)') & VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE #ifdef GIT_SHA1 write(UNIT=OUTPUT_UNIT, FMT='(10X,"Git SHA1 | ",A)') GIT_SHA1 From 98b2bcb275136d460aa10939b95ec0efbd4c2479 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 24 Dec 2017 22:04:27 +0700 Subject: [PATCH 085/282] Various fixes/updates in documentation --- docs/source/devguide/docbuild.rst | 3 +- docs/source/quickinstall.rst | 8 +-- docs/source/usersguide/cross_sections.rst | 7 +++ docs/source/usersguide/install.rst | 75 +++++++++-------------- docs/source/usersguide/settings.rst | 2 +- 5 files changed, 40 insertions(+), 55 deletions(-) diff --git a/docs/source/devguide/docbuild.rst b/docs/source/devguide/docbuild.rst index 079a69a2c..be4607cc6 100644 --- a/docs/source/devguide/docbuild.rst +++ b/docs/source/devguide/docbuild.rst @@ -40,7 +40,7 @@ on your computer as well as Inkscape_, which is used to convert .svg files to .. code-block:: sh - sudo apt-get install inkscape + sudo apt install inkscape One the pre-requisites are installed, simply go to the ``docs`` directory and run: @@ -50,6 +50,5 @@ run: make latexpdf .. _Sphinx: http://sphinx-doc.org -.. _sphinxcontrib-tikz: https://bitbucket.org/philexander/tikz .. _Numfig: https://pypi.python.org/pypi/sphinx_numfig .. _Inkscape: https://inkscape.org diff --git a/docs/source/quickinstall.rst b/docs/source/quickinstall.rst index 6bc09c044..97980037c 100644 --- a/docs/source/quickinstall.rst +++ b/docs/source/quickinstall.rst @@ -44,13 +44,13 @@ Next, resynchronize the package index files: .. code-block:: sh - sudo apt-get update + sudo apt update Now OpenMC should be recognized within the repository and can be installed: .. code-block:: sh - sudo apt-get install openmc + sudo apt install openmc Binary packages from this PPA may exist for earlier versions of Ubuntu, but they are no longer supported. @@ -69,9 +69,7 @@ installed directly from the package manager. .. code-block:: sh - sudo apt-get install gfortran - sudo apt-get install cmake - sudo apt-get install libhdf5-dev + sudo apt install gfortran g++ cmake libhdf5-dev After the packages have been installed, follow the instructions below for building and installing OpenMC from source. diff --git a/docs/source/usersguide/cross_sections.rst b/docs/source/usersguide/cross_sections.rst index 48cccfb67..058daf23a 100644 --- a/docs/source/usersguide/cross_sections.rst +++ b/docs/source/usersguide/cross_sections.rst @@ -222,6 +222,13 @@ named ``njoy`` available on your path. If you want to explicitly name the executable, the ``njoy_exec`` optional argument can be used. Additionally, the ``stdout`` argument can be used to show the progress of the NJOY run. +To generate a thermal scattering file, you need to specify both an ENDF incident +neutron sub-library file as well as a thermal neutron scattering sub-library +file; for example:: + + light_water = openmc.data.ThermalScattering.from_njoy( + 'neutrons/n-001_H_001.endf', 'thermal_scatt/tsl-HinH2O.endf') + Once you have instances of :class:`IncidentNeutron` and :class:`ThermalScattering`, a library can be created by using the ``export_to_hdf5()`` methods and the :class:`DataLibrary` class as described in diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 09fc4520c..1fee22e71 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -95,10 +95,10 @@ Prerequisites * A C/C++ compiler such as gcc_ - OpenMC includes two libraries written in C and C++, respectively. These - libraries have been tested to work with a wide variety of compilers. If - you are using a Debian-based distribution, you can install the g++ - compiler using the following command:: + OpenMC includes various source files written in C and C++, + respectively. These source files have been tested to work with a wide + variety of compilers. If you are using a Debian-based distribution, you + can install the g++ compiler using the following command:: sudo apt install g++ @@ -113,34 +113,32 @@ Prerequisites * HDF5_ Library for portable binary output format - OpenMC uses HDF5 for binary output files. As such, you will need to have - HDF5 installed on your computer. The installed version will need to have - been compiled with the same compiler you intend to compile OpenMC with. If - you are using HDF5 in conjunction with MPI, we recommend that your HDF5 - installation be built with parallel I/O features. An example of - configuring HDF5_ is listed below:: + OpenMC uses HDF5 for many input/output files. As such, you will need to + have HDF5 installed on your computer. The installed version will need to + have been compiled with the same compiler you intend to compile OpenMC + with. On Debian derivatives, HDF5 and/or parallel HDF5 can be installed + through the APT package manager: - FC=/opt/mpich/3.1/bin/mpif90 CC=/opt/mpich/3.1/bin/mpicc \ - ./configure --prefix=/opt/hdf5/1.8.12 --enable-fortran \ - --enable-fortran2003 --enable-parallel + .. code-block:: sh + + sudo apt install libhdf5-dev + + Note that the exact package names may vary depending on your particular + distribution and version. If you are using HDF5 in conjunction with MPI, + we recommend that your HDF5 installation be built with parallel I/O + features. An example of configuring HDF5_ is listed below:: + + FC=/opt/mpich/3.2/bin/mpif90 CC=/opt/mpich/3.2/bin/mpicc \ + ./configure --prefix=/opt/hdf5/1.10.1 --enable-fortran \ + --enable-parallel You may omit ``--enable-parallel`` if you want to compile HDF5_ in serial. .. important:: - OpenMC uses various parts of the HDF5 Fortran 2003 API; as such you - must include ``--enable-fortran2003`` or else OpenMC will not be able - to compile. - - On Debian derivatives, HDF5 and/or parallel HDF5 can be installed through - the APT package manager: - - .. code-block:: sh - - sudo apt install libhdf5-dev hdf5-helpers - - Note that the exact package names may vary depending on your particular - distribution and version. + 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 @@ -163,7 +161,7 @@ Prerequisites .. _CMake: http://www.cmake.org .. _OpenMPI: http://www.open-mpi.org .. _MPICH: http://www.mpich.org -.. _HDF5: http://www.hdfgroup.org/HDF5/ +.. _HDF5: https://www.hdfgroup.org/solutions/hdf5/ Obtaining the Source -------------------- @@ -358,26 +356,9 @@ workarounds. Testing Build ------------- -If you have ENDF/B-VII.1 cross sections from NNDC_ you can test your build. -Make sure the **OPENMC_CROSS_SECTIONS** environmental variable is set to the -*cross_sections.xml* file in the *data/nndc* directory. -There are two ways to run tests. The first is to use the Makefile present in -the source directory and run the following: - -.. code-block:: sh - - make test - -If you want more options for testing you can use ctest_ command. For example, -if we wanted to run only the plot tests with 4 processors, we run: - -.. code-block:: sh - - cd build - ctest -j 4 -R plot - -If you want to run the full test suite with different build options please -refer to our :ref:`test suite` documentation. +To run the test suite, you will first need to download a pre-generated cross +section library along with windowed multipole data. Please refer to our +:ref:`test suite` documentation for further details. -------------------- Python Prerequisites diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index 6e49777f0..978649a2f 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -39,7 +39,7 @@ be specified: 'plot' Generates slice or voxel plots (see :ref:`usersguide_plots`). -'particle_restart' +'particle restart' Simulate a single source particle using a particle restart file. From 6b48fe9f6ba61acbed6966b5a3aa57117e347222 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 29 Dec 2017 15:10:55 -0600 Subject: [PATCH 086/282] Use sphinx.ext.imgconverter for converting SVG images for PDF doc builds --- docs/Makefile | 12 ------------ docs/source/conf.py | 3 ++- docs/source/devguide/docbuild.rst | 24 +++++++----------------- docs/source/methods/geometry.rst | 8 ++++---- 4 files changed, 13 insertions(+), 34 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 2f3c029db..a93338df3 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -13,10 +13,6 @@ PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# SVG to PDF conversion -SVG2PDF = inkscape -PDFS = $(patsubst %.svg,%.pdf,$(wildcard $(IMAGEDIR)/*.svg)) - # Tikz to PNG conversion PNGS = $(patsubst %.tex,%.png,$(wildcard $(IMAGEDIR)/*.tex)) @@ -41,21 +37,13 @@ help: @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" -# Pattern rule for converting SVG to PDF -%.pdf: %.svg - $(SVG2PDF) -f $< -A $@ - %.png: %.tex pdflatex --interaction=nonstopmode --output-directory=$(IMAGEDIR) $< pdftoppm -r 120 -singlefile $(patsubst %.tex,%.pdf, $<) $(basename $<) convert -trim -fuzz 2% -transparent white $(patsubst %.tex,%.ppm,$<) $@ -# Rule to build PDFs -images: $(PDFS) $(PNGS) - clean: -rm -rf $(BUILDDIR)/* - -rm -rf $(PDFS) -rm -rf source/pythonapi/generated/ html: diff --git a/docs/source/conf.py b/docs/source/conf.py index 834c47a40..928755cf8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -52,6 +52,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', + 'sphinx.ext.imgconverter', 'sphinx_numfig', 'notebook_sphinxext'] @@ -253,5 +254,5 @@ intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), 'numpy': ('https://docs.scipy.org/doc/numpy/', None), 'pandas': ('http://pandas.pydata.org/pandas-docs/stable/', None), - 'matplotlib': ('http://matplotlib.org/', None) + 'matplotlib': ('https://matplotlib.org/', None) } diff --git a/docs/source/devguide/docbuild.rst b/docs/source/devguide/docbuild.rst index be4607cc6..5cbbb65bb 100644 --- a/docs/source/devguide/docbuild.rst +++ b/docs/source/devguide/docbuild.rst @@ -5,15 +5,16 @@ Building Sphinx Documentation ============================= In order to build the documentation in the ``docs`` directory, you will need to -have the Sphinx_ third-party Python package. The easiest way to install Sphinx -is via pip: +have the `Sphinx `_ third-party Python +package. The easiest way to install Sphinx is via pip: .. code-block:: sh sudo pip install sphinx Additionally, you will also need a Sphinx extension for numbering figures. The -Numfig_ package can be installed directly with pip: +`Numfig `_ package can be installed +directly with pip: .. code-block:: sh @@ -24,7 +25,7 @@ Building Documentation as a Webpage ----------------------------------- To build the documentation as a webpage (what appears at -http://mit-crpg.github.io/openmc), simply go to the ``docs`` directory and run: +http://openmc.readthedocs.io), simply go to the ``docs`` directory and run: .. code-block:: sh @@ -35,20 +36,9 @@ Building Documentation as a PDF ------------------------------- To build PDF documentation, you will need to have a LaTeX distribution installed -on your computer as well as Inkscape_, which is used to convert .svg files to -.pdf files. Inkscape can be installed in a Debian-derivative with: - -.. code-block:: sh - - sudo apt install inkscape - -One the pre-requisites are installed, simply go to the ``docs`` directory and -run: +on your computer. Once you have a LaTeX distribution installed, simply go to the +``docs`` directory and run: .. code-block:: sh make latexpdf - -.. _Sphinx: http://sphinx-doc.org -.. _Numfig: https://pypi.python.org/pypi/sphinx_numfig -.. _Inkscape: https://inkscape.org diff --git a/docs/source/methods/geometry.rst b/docs/source/methods/geometry.rst index 36c252bb1..4e90dd992 100644 --- a/docs/source/methods/geometry.rst +++ b/docs/source/methods/geometry.rst @@ -47,7 +47,7 @@ dividing space into two half-spaces. .. _fig-halfspace: -.. figure:: ../_images/halfspace.* +.. figure:: ../_images/halfspace.svg :align: center :figclass: align-center @@ -63,7 +63,7 @@ defined as the intersection of an ellipse and two planes. .. _fig-union: -.. figure:: ../_images/union.* +.. figure:: ../_images/union.svg :align: center :figclass: align-center @@ -482,7 +482,7 @@ upper-right tiles, respectively. .. _fig-rect-lat: -.. figure:: ../_images/rect_lat.* +.. figure:: ../_images/rect_lat.svg :align: center :figclass: align-center :width: 400px @@ -521,7 +521,7 @@ right side. .. _fig-hex-lat: -.. figure:: ../_images/hex_lat.* +.. figure:: ../_images/hex_lat.svg :align: center :figclass: align-center :width: 400px From 7e19b82f9388b5beb8079fbb6a3f056a0e7f748a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 29 Dec 2017 17:20:02 -0600 Subject: [PATCH 087/282] Update C API documentation --- docs/source/capi/index.rst | 417 +++++++++++++++++++++++++---------- include/openmc.h | 104 +++++++++ openmc/capi/cell.py | 2 + src/api.F90 | 7 +- src/geometry_header.F90 | 2 +- src/simulation.F90 | 1 + src/tallies/tally_header.F90 | 1 + 7 files changed, 417 insertions(+), 117 deletions(-) create mode 100644 include/openmc.h diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index 581f358d4..ff19758bb 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -8,14 +8,43 @@ C API Run a stochastic volume calculation +.. c:function:: int openmc_cell_get_fill(int32_t index, int* type, int32_t** indices, int32_t* n) + + Get the fill for a cell + + :param int32_t index: Index in the cells array + :param int* type: Type of the fill + :param int32_t** indices: Array of material indices for cell + :param int32_t n: Length of indices array + :return: Return status (negative if an error occurred) + :rtype: int + .. c:function:: int openmc_cell_get_id(int32_t index, int32_t* id) Get the ID of a cell - :param index: Index in the cells array - :type index: int32_t - :param id: ID of the cell - :type id: int32_t* + :param int32_t index: Index in the cells array + :param int32_t* id: ID of the cell + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_cell_set_fill(int32_t index, int type, int32_t n, int32_t* indices) + + Set the fill for a cell + + :param int32_t index: Index in the cells array + :param int type: Type of the fill + :param int32_t n: Length of indices array + :param int32_t* indices: Array of material indices for cell + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_cell_set_id(int32_t index, int32_t id) + + Set the ID of a cell + + :param int32_t index: Index in the cells array + :param int32_t id: ID of the cell :return: Return status (negative if an error occurred) :rtype: int @@ -23,13 +52,88 @@ C API Set the temperature of a cell. - :param index: Index in the cells array - :type index: int32_t - :param T: Temperature in Kelvin - :type T: double - :param instance: Which instance of the cell. To set the temperature for all - instances, pass a null pointer. - :type instance: int32_t* + :param int32_t index: Index in the cells array + :param double T: Temperature in Kelvin + :param int32_t* instance: Which instance of the cell. To set the temperature + for all instances, pass a null pointer. + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_energy_filter_get_bins(int32_t index, double** energies, int32_t* n) + + Return the bounding energies for an energy filter + + :param int32_t index: Index in the filters array + :param double** energies: Bounding energies of the bins for the energy filter + :param int32_t* n: Number of energies specified + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_energy_filter_set_bins(int32_t index, int32_t n, double* energies) + + Set the bounding energies for an energy filter + + :param int32_t index: Index in the filters array + :param int32_t n: Number of energies specified + :param double* energies: Bounding energies of the bins for the energy filter + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_extend_cells(int32_t n, int32_t* index_start, int32_t* index_end) + + Extend the cells array by n elements + + :param int32_t n: Number of cells to create + :param int32_t* index_start: Index of first new cell + :param int32_t* index_end: Index of last new cell + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_extend_filters(int32_t n, int32_t* index_start, int32_t* index_end) + + Extend the filters array by n elements + + :param int32_t n: Number of filters to create + :param int32_t* index_start: Index of first new filter + :param int32_t* index_end: Index of last new filter + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_extend_materials(int32_t n, int32_t* index_start, int32_t* index_end) + + Extend the materials array by n elements + + :param int32_t n: Number of materials to create + :param int32_t* index_start: Index of first new material + :param int32_t* index_end: Index of last new material + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_extend_tallies(int32_t n, int32_t* index_start, int32_t* index_end) + + Extend the tallies array by n elements + + :param int32_t n: Number of tallies to create + :param int32_t* index_start: Index of first new tally + :param int32_t* index_end: Index of last new tally + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_filter_get_id(int32_t index, int32_t* id) + + Get the ID of a filter + + :param int32_t index: Index in the filters array + :param int32_t* id: ID of the filter + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_filter_set_id(int32_t index, int32_t id) + + Set the ID of a filter + + :param int32_t index: Index in the filters array + :param int32_t id: ID of the filter :return: Return status (negative if an error occurred) :rtype: int @@ -41,54 +145,41 @@ C API Determine the ID of the cell/material containing a given point - :param xyz: Cartesian coordinates - :type xyz: double[3] - :param rtype: Which ID to return (1=cell, 2=material) - :type rtype: int - :param id: ID of the cell/material found. If a material is requested and the - point is in a void, the ID is 0. If an error occurs, the ID is -1. - :type id: int32_t* - :param instance: If a cell is repetaed in the geometry, the instance of the - cell that was found and zero otherwise. - :type instance: int32_t* + :param double[3] xyz: Cartesian coordinates + :param int rtype: Which ID to return (1=cell, 2=material) + :param int32_t* id: ID of the cell/material found. If a material is requested + and the point is in a void, the ID is 0. If an error + occurs, the ID is -1. + :param int32_t* instance: If a cell is repetaed in the geometry, the instance + of the cell that was found and zero otherwise. .. c:function:: int openmc_get_cell_index(int32_t id, int32_t* index) Get the index in the cells array for a cell with a given ID - :param id: ID of the cell - :type id: int32_t - :param index: Index in the cells array - :type index: int32_t* + :param int32_t id: ID of the cell + :param int32_t* index: Index in the cells array :return: Return status (negative if an error occurs) :rtype: int +.. c:function:: int openmc_get_filter_index(int32_t id, int32_t* index) + + Get the index in the filters array for a filter with a given ID + + :param int32_t id: ID of the filter + :param int32_t* index: Index in the filters array + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: void openmc_get_filter_next_id(int32_t* id) + + Get an integer ID that has not been used by any filters. + + :param int32_t* id: Unused integer ID + .. c:function:: int openmc_get_keff(double k_combined[]) - :param k_combined: Combined estimate of k-effective - :type k_combined: double[2] - :return: Return status (negative if an error occurs) - :rtype: int - -.. c:function:: int openmc_get_nuclide_index(char name[], int* index) - - Get the index in the nuclides array for a nuclide with a given name - - :param name: Name of the nuclide - :type name: char[] - :param index: Index in the nuclides array - :type index: int* - :return: Return status (negative if an error occurs) - :rtype: int - -.. c:function:: int openmc_get_tally_index(int32_t id, int32_t* index) - - Get the index in the tallies array for a tally with a given ID - - :param id: ID of the tally - :type id: int32_t - :param index: Index in the tallies array - :type index: int32_t* + :param double[2] k_combined: Combined estimate of k-effective :return: Return status (negative if an error occurs) :rtype: int @@ -96,10 +187,26 @@ C API Get the index in the materials array for a material with a given ID - :param id: ID of the material - :type id: int32_t - :param index: Index in the materials array - :type index: int32_t* + :param int32_t id: ID of the material + :param int32_t* index: Index in the materials array + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_get_nuclide_index(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 int* index: Index in the nuclides array + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_get_tally_index(int32_t id, int32_t* index) + + Get the index in the tallies array for a tally with a given ID + + :param int32_t id: ID of the tally + :param int32_t* index: Index in the tallies array :return: Return status (negative if an error occurs) :rtype: int @@ -111,15 +218,13 @@ C API Initialize OpenMC - :param intracomm: MPI intracommunicator - :type intracomm: int + :param int intracomm: MPI intracommunicator .. c:function:: int openmc_load_nuclide(char name[]) Load data for a nuclide from the HDF5 data library. - :param name: Name of the nuclide. - :type name: char[] + :param char[] name: Name of the nuclide. :return: Return status (negative if an error occurs) :rtype: int @@ -128,27 +233,20 @@ C API Add a nuclide to an existing material. If the nuclide already exists, the density is overwritten. - :param index: Index in the materials array - :type index: int32_t - :param name: Name of the nuclide - :type name: char[] - :param density: Density in atom/b-cm - :type density: double + :param int32_t index: Index in the materials array + :param char[] name: Name of the nuclide + :param double density: Density in atom/b-cm :return: Return status (negative if an error occurs) :rtype: int -.. c:function:: int openmc_material_get_densities(int32_t index, int* nuclides[], double* densities[]) +.. c:function:: int openmc_material_get_densities(int32_t index, int* nuclides[], double* densities[], int* n) Get density for each nuclide in a material. - :param index: Index in the materials array - :type index: int32_t - :param nuclides: Pointer to array of nuclide indices - :type nuclides: int** - :param densities: Pointer to the array of densities - :type densities: double** - :param n: Length of the array - :type n: int + :param int32_t index: Index in the materials array + :param int** nuclides: Pointer to array of nuclide indices + :param double** densities: Pointer to the array of densities + :param int* n: Length of the array :return: Return status (negative if an error occurs) :rtype: int @@ -156,10 +254,8 @@ C API Get the ID of a material - :param index: Index in the materials array - :type index: int32_t - :param id: ID of the material - :type id: int32_t* + :param int32_t index: Index in the materials array + :param int32_t* id: ID of the material :return: Return status (negative if an error occurred) :rtype: int @@ -167,34 +263,72 @@ C API Set the density of a material. - :param index: Index in the materials array - :type index: int32_t - :param density: Density of the material in atom/b-cm - :type density: double + :param int32_t index: Index in the materials array + :param double density: Density of the material in atom/b-cm :return: Return status (negative if an error occurs) :rtype: int -.. c:function:: int openmc_material_set_densities(int32_t, n, char* name[], double density[]) +.. c:function:: int openmc_material_set_densities(int32_t index, int n, char* name[], double density[]) - :param index: Index in the materials array - :type index: int32_t - :param n: Length of name/density - :type n: int - :param name: Array of nuclide names - :type name: char** - :param density: Array of densities - :type density: double[] + :param int32_t index: Index in the materials array + :param int n: Length of name/density + :param char** name: Array of nuclide names + :param double[] density: Array of densities :return: Return status (negative if an error occurs) :rtype: int +.. c:function:: int openmc_material_set_id(int32_t index, int32_t id) + + Set the ID of a material + + :param int32_t index: Index in the materials array + :param int32_t id: ID of the material + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_material_filter_get_bins(int32_t index, int32_t** bins, int32_t* n) + + Get the bins for a material filter + + :param int32_t index: Index in the filters array + :param int32_t** bins: Index in the materials array for each bin + :param int32_t* n: Number of bins + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_material_filter_set_bins(int32_t index, int32_t n, int32_t* bins) + + Set the bins for a material filter + + :param int32_t index: Index in the filters array + :param int32_t n: Number of bins + :param int32_t* bins: Index in the materials array for each bin + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_mesh_filter_set_mesh(int32_t index, int32_t index_mesh) + + Set the mesh for a mesh filter + + :param int32_t index: Index in the filters array + :param int32_t index_mesh: Index in the meshes array + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_next_batch() + + Simulate next batch of particles. Must be called after openmc_simulation_init(). + + :return: Integer indicating whether simulation has finished (negative) or not + finished (zero). + :rtype: int + .. c:function:: int openmc_nuclide_name(int index, char* name[]) Get name of a nuclide - :param index: Index in the nuclides array - :type index: int - :param name: Name of the nuclide - :type name: char** + :param int index: Index in the nuclides array + :param char** name: Name of the nuclide :return: Return status (negative if an error occurs) :rtype: int @@ -210,14 +344,37 @@ C API Run a simulation +.. c:function:: void openmc_simulation_finalize() + + Finalize a simulation. + +.. c:function:: void openmc_simulation_init() + + Initialize a simulation. Must be called after openmc_init(). + .. c:function:: int openmc_tally_get_id(int32_t index, int32_t* id) Get the ID of a tally - :param index: Index in the tallies array - :type index: int32_t - :param id: ID of the tally - :type id: int32_t* + :param int32_t index: Index in the tallies array + :param int32_t* id: ID of the tally + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_tally_get_filters(int32_t index, int32_t** indices, int* n) + + Get filters specified in a tally + + :param int32_t index: Index in the tallies array + :param int32_t** indices: Array of filter indices + :param int* n: Number of filters + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_tally_get_n_realizations(int32_t index, int32_t* n) + + :param int32_t index: Index in the tallies array + :param int32_t* n: Number of realizations :return: Return status (negative if an error occurred) :rtype: int @@ -225,12 +382,19 @@ C API Get nuclides specified in a tally - :param index: Index in the tallies array - :type index: int32_t - :param nuclides: Array of nuclide indices - :type nuclides: int** - :param n: Number of nuclides - :type n: int* + :param int32_t index: Index in the tallies array + :param int** nuclides: Array of nuclide indices + :param int* n: Number of nuclides + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_tally_get_scores(int32_t index, int** scores, int* n) + + Get scores specified for a tally + + :param int32_t index: Index in the tallies array + :param int** scores: Array of scores + :param int* n: Number of scores :return: Return status (negative if an error occurred) :rtype: int @@ -238,12 +402,28 @@ C API Get a pointer to tally results array. - :param index: Index in the tallies array - :type index: int32_t - :param ptr: Pointer to the results array - :type ptr: double** - :param shape_: Shape of the results array - :type shape_: int[3] + :param int32_t index: Index in the tallies array + :param double** ptr: Pointer to the results array + :param int[3] shape_: Shape of the results array + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_tally_set_filters(int32_t index, int n, int32_t* indices) + + Set filters for a tally + + :param int32_t index: Index in the tallies array + :param int n: Number of filters + :param int32_t* indices: Array of filter indices + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_tally_set_id(int32_t index, int32_t id) + + Set the ID of a tally + + :param int32_t index: Index in the tallies array + :param int32_t id: ID of the tally :return: Return status (negative if an error occurred) :rtype: int @@ -251,11 +431,18 @@ C API Set the nuclides for a tally - :param index: Index in the tallies array - :type index: int32_t - :param n: Number of nuclides - :type n: int - :param nuclides: Array of nuclide names - :type nuclides: char** + :param int32_t index: Index in the tallies array + :param int n: Number of nuclides + :param char** nuclides: Array of nuclide names + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_tally_set_scores(int32_t index, int n, int* scores) + + Set scores for a tally + + :param int32_t index: Index in the tallies array + :param int n: Number of scores + :param int* scores: Array of scores :return: Return status (negative if an error occurred) :rtype: int diff --git a/include/openmc.h b/include/openmc.h new file mode 100644 index 000000000..5f72e99ed --- /dev/null +++ b/include/openmc.h @@ -0,0 +1,104 @@ +#ifndef OPENMC_H +#define OPENMC_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + void openmc_calculate_voumes(); + void openmc_finalize(); + void openmc_hard_reset(); + int openmc_next_batch(); + void openmc_plot_geometry(); + void openmc_reset(); + void openmc_run(); + void openmc_simulation_finalize(); + void openmc_simulation_init(); + + 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, int32_t* indices); + int openmc_cell_set_id(int32_t index, int32_t id); + int openmc_cell_set_temperature(int32_t index, double T, int32_t* instance); + int openmc_energy_filter_get_bins(int32_t index, double** energies, int32_t* n); + int openmc_energy_filter_set_bins(int32_t index, int32_t n, double* energies); + 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_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_set_id(int32_t index, int32_t id); + 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_tally_tally(int32_t id, int32_t* index); + int openmc_load_nuclide(char name[]); + int openmc_material_add_nuclide(int32_t index, char name[], double density); + int openmc_material_get_densities(int32_t index, int* nuclides[], double* densities[], int* n); + int openmc_material_get_id(int32_t index, int32_t* id); + int openmc_material_set_density(int32_t index, double density); + int openmc_material_set_densities(int32_t index, int n, char* name[], double density[]); + 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, int32_t* bins); + int openmc_mesh_filter_set_mesh(int32_t index, int32_t index_mesh); + int openmc_nuclide_name(int index, char* name[]); + 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_filters(int32_t index, int n, int32_t* indices); + int openmc_tally_set_id(int32_t index, int32_t id); + int openmc_tally_set_nuclides(int32_t index, int n, char* nuclides[]); + int openmc_tally_set_scores(int32_t index, int n, int* scores); + + // 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; + + // Global variables + extern char openmc_err_msg[256]; + extern double keff; + extern double keff_std; + extern int32_t n_batches; + extern int32_t n_cells; + extern int32_t n_filters; + extern int32_t n_inactive; + extern int32_t n_lattices; + extern int32_t n_materials; + extern int32_t n_meshes; + extern int64_t n_particles; + extern int32_t n_plots; + extern int32_t n_realizations; + extern int32_t n_sab_tables; + extern int32_t n_sources; + 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; + +#ifdef __cplusplus +} +#endif + +#endif // OPENMC_H diff --git a/openmc/capi/cell.py b/openmc/capi/cell.py index f13f64a04..0c4b72c74 100644 --- a/openmc/capi/cell.py +++ b/openmc/capi/cell.py @@ -21,6 +21,8 @@ _dll.openmc_cell_get_id.restype = c_int _dll.openmc_cell_get_id.errcheck = _error_handler _dll.openmc_cell_get_fill.argtypes = [ c_int32, POINTER(c_int), POINTER(POINTER(c_int32)), POINTER(c_int32)] +_dll.openmc_cell_get_fill.restype = c_int +_dll.openmc_cell_get_fill.errcheck = _error_handler _dll.openmc_cell_set_fill.argtypes = [ c_int32, c_int, c_int32, POINTER(c_int32)] _dll.openmc_cell_set_fill.restype = c_int diff --git a/src/api.F90 b/src/api.F90 index 326b755b6..9db06ac20 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -24,7 +24,7 @@ module openmc_api use tally_filter_header use tally_filter use tally, only: openmc_tally_set_type - use simulation, only: openmc_run + use simulation use string, only: to_f_string use timer_header use volume_calc, only: openmc_calculate_volumes @@ -57,6 +57,7 @@ module openmc_api public :: openmc_get_material_index public :: openmc_get_nuclide_index public :: openmc_get_tally_index + public :: openmc_global_tallies public :: openmc_hard_reset public :: openmc_init public :: openmc_load_nuclide @@ -69,12 +70,16 @@ module openmc_api public :: openmc_material_filter_get_bins public :: openmc_material_filter_set_bins public :: openmc_mesh_filter_set_mesh + public :: openmc_next_batch public :: openmc_nuclide_name public :: openmc_plot_geometry public :: openmc_reset public :: openmc_run + public :: openmc_simulation_finalize + public :: openmc_simulation_init public :: openmc_tally_get_id public :: openmc_tally_get_filters + public :: openmc_tally_get_n_realizations public :: openmc_tally_get_nuclides public :: openmc_tally_get_scores public :: openmc_tally_results diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index b827c055c..e71916d09 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -524,7 +524,7 @@ contains function openmc_cell_set_fill(index, type, n, indices) result(err) bind(C) - ! Set the fill for a fill + ! Set the fill for a cell integer(C_INT32_T), value, intent(in) :: index ! index in cells integer(C_INT), value, intent(in) :: type integer(c_INT32_T), value, intent(in) :: n diff --git a/src/simulation.F90 b/src/simulation.F90 index e8997fc9b..026c9c61e 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -43,6 +43,7 @@ module simulation implicit none private + public :: openmc_next_batch public :: openmc_run public :: openmc_simulation_init public :: openmc_simulation_finalize diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index fa397632f..474e77d1c 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -26,6 +26,7 @@ module tally_header public :: openmc_global_tallies public :: openmc_tally_get_id public :: openmc_tally_get_filters + public :: openmc_tally_get_n_realizations public :: openmc_tally_get_nuclides public :: openmc_tally_get_scores public :: openmc_tally_results From e28f16eefd9666cdd1728cdc51167f0b38edac3a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 1 Jan 2018 15:19:51 -0600 Subject: [PATCH 088/282] Happy new year 2018! --- LICENSE | 2 +- docs/source/conf.py | 2 +- docs/source/license.rst | 2 +- man/man1/openmc.1 | 2 +- src/output.F90 | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LICENSE b/LICENSE index 660806c88..a11dc44a2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011-2017 Massachusetts Institute of Technology +Copyright (c) 2011-2018 Massachusetts Institute of Technology Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/docs/source/conf.py b/docs/source/conf.py index 928755cf8..933031590 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -70,7 +70,7 @@ master_doc = 'index' # General information about the project. project = u'OpenMC' -copyright = u'2011-2017, Massachusetts Institute of Technology' +copyright = u'2011-2018, Massachusetts Institute of Technology' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/source/license.rst b/docs/source/license.rst index 1e5f88bdc..e9cba3d8a 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -4,7 +4,7 @@ License Agreement ================= -Copyright © 2011-2017 Massachusetts Institute of Technology +Copyright © 2011-2018 Massachusetts Institute of Technology Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/man/man1/openmc.1 b/man/man1/openmc.1 index 8384da9a8..f49d80bad 100644 --- a/man/man1/openmc.1 +++ b/man/man1/openmc.1 @@ -59,7 +59,7 @@ Indicates the default path to a directory containing windowed multipole data if the user has not specified the tag in .I materials.xml\fP. .SH LICENSE -Copyright \(co 2011-2017 Massachusetts Institute of Technology. +Copyright \(co 2011-2018 Massachusetts Institute of Technology. .PP Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/src/output.F90 b/src/output.F90 index f9660638a..9a144721d 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -76,7 +76,7 @@ contains write(UNIT=OUTPUT_UNIT, FMT=*) & ' | The OpenMC Monte Carlo Code' write(UNIT=OUTPUT_UNIT, FMT=*) & - ' Copyright | 2011-2017 Massachusetts Institute of Technology' + ' Copyright | 2011-2018 Massachusetts Institute of Technology' write(UNIT=OUTPUT_UNIT, FMT=*) & ' License | http://openmc.readthedocs.io/en/latest/license.html' write(UNIT=OUTPUT_UNIT, FMT='(11X,"Version | ",I1,".",I2,".",I1)') & From ec0604b354e867b22c01cc6a9ee39003f9f2c6b8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 8 Jan 2018 08:04:57 -0600 Subject: [PATCH 089/282] Update C API documentation and include file --- docs/source/capi/index.rst | 147 ++++++++++++++++++++++++++++++------- include/openmc.h | 56 ++++++++------ src/api.F90 | 6 ++ src/source_header.F90 | 2 + 4 files changed, 161 insertions(+), 50 deletions(-) diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index ff19758bb..d63ed1f3a 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -4,6 +4,48 @@ C API ===== +The libopenmc shared library that is built when installing OpenMC exports a +number of C interoperable functions and global variables that can be used for +in-memory coupling. While it is possible to directly use the C API as documented +here for coupling, most advanced users will find it easier to work with the +Python bindings in the :py:mod:`openmc.capi` module. + +.. warning:: The C API is still experimental and may undergo substantial changes + in future releases. + +---------------- +Type Definitions +---------------- + +.. c:type:: Bank + + Attributes of a source particle. + + .. c:member:: double wgt + + Weight of the particle + + .. c:member:: double xyz[3] + + Position of the particle (units of cm) + + .. c:member:: double uvw[3] + + Unit vector indicating direction of the particle + + .. c:member:: double E + + Energy of the particle in eV + + .. c:member:: int delayed_group + + If the particle is a delayed neutron, indicates which delayed precursor + group it was born from. If not a delayed neutron, this member is zero. + +--------- +Functions +--------- + .. c:function:: void openmc_calculate_volumes() Run a stochastic volume calculation @@ -15,7 +57,7 @@ C API :param int32_t index: Index in the cells array :param int* type: Type of the fill :param int32_t** indices: Array of material indices for cell - :param int32_t n: Length of indices array + :param int32_t* n: Length of indices array :return: Return status (negative if an error occurred) :rtype: int @@ -28,14 +70,15 @@ C API :return: Return status (negative if an error occurred) :rtype: int -.. c:function:: int openmc_cell_set_fill(int32_t index, int type, int32_t n, int32_t* indices) +.. c:function:: int openmc_cell_set_fill(int32_t index, int type, int32_t n, const int32_t* indices) Set the fill for a cell :param int32_t index: Index in the cells array :param int type: Type of the fill :param int32_t n: Length of indices array - :param int32_t* indices: Array of material indices for cell + :param indices: Array of material indices for cell + :type indices: const int32_t* :return: Return status (negative if an error occurred) :rtype: int @@ -48,14 +91,15 @@ C API :return: Return status (negative if an error occurred) :rtype: int -.. c:function:: int openmc_cell_set_temperature(index index, double T, int32_t* instance) +.. c:function:: int openmc_cell_set_temperature(index index, double T, const int32_t* instance) Set the temperature of a cell. :param int32_t index: Index in the cells array :param double T: Temperature in Kelvin - :param int32_t* instance: Which instance of the cell. To set the temperature - for all instances, pass a null pointer. + :param instance: Which instance of the cell. To set the temperature for all + instances, pass a null pointer. + :type instance: const int32_t* :return: Return status (negative if an error occurred) :rtype: int @@ -69,13 +113,14 @@ C API :return: Return status (negative if an error occurred) :rtype: int -.. c:function:: int openmc_energy_filter_set_bins(int32_t index, int32_t n, double* energies) +.. c:function:: int openmc_energy_filter_set_bins(int32_t index, int32_t n, const double* energies) Set the bounding energies for an energy filter :param int32_t index: Index in the filters array :param int32_t n: Number of energies specified - :param double* energies: Bounding energies of the bins for the energy filter + :param energies: Bounding energies of the bins for the energy filter + :type energies: const double* :return: Return status (negative if an error occurred) :rtype: int @@ -109,6 +154,16 @@ C API :return: Return status (negative if an error occurred) :rtype: int +.. c:function:: int openmc_extend_sources(int32_t n, int32_t* index_start, int32_t* index_end) + + Extend the external sources array by n elements + + :param int32_t n: Number of sources to create + :param int32_t* index_start: Index of first new source + :param int32_t* index_end: Index of last new source + :return: Return status (negative if an error occurred) + :rtype: int + .. c:function:: int openmc_extend_tallies(int32_t n, int32_t* index_start, int32_t* index_end) Extend the tallies array by n elements @@ -150,7 +205,7 @@ C API :param int32_t* id: ID of the cell/material found. If a material is requested and the point is in a void, the ID is 0. If an error occurs, the ID is -1. - :param int32_t* instance: If a cell is repetaed in the geometry, the instance + :param int32_t* instance: If a cell is repeated in the geometry, the instance of the cell that was found and zero otherwise. .. c:function:: int openmc_get_cell_index(int32_t id, int32_t* index) @@ -177,7 +232,7 @@ C API :param int32_t* id: Unused integer ID -.. c:function:: int openmc_get_keff(double k_combined[]) +.. c:function:: int openmc_get_keff(double k_combined[2]) :param double[2] k_combined: Combined estimate of k-effective :return: Return status (negative if an error occurs) @@ -214,11 +269,13 @@ C API Reset tallies, timers, and pseudo-random number generator state -.. c:function:: void openmc_init(int intracomm) +.. c:function:: void openmc_init(const int* intracomm) Initialize OpenMC - :param int intracomm: MPI intracommunicator + :param intracomm: MPI intracommunicator. If MPI is not being used, a null + pointer should be passed. + :type intracomm: const int* .. c:function:: int openmc_load_nuclide(char name[]) @@ -228,18 +285,19 @@ C API :return: Return status (negative if an error occurs) :rtype: int -.. c:function:: int openmc_material_add_nuclide(int32_t index, char name[], double density) +.. c:function:: int openmc_material_add_nuclide(int32_t index, const char name[], double density) Add a nuclide to an existing material. If the nuclide already exists, the density is overwritten. :param int32_t index: Index in the materials array - :param char[] name: Name of the nuclide + :param name: Name of the nuclide + :type name: const char[] :param double density: Density in atom/b-cm :return: Return status (negative if an error occurs) :rtype: int -.. c:function:: int openmc_material_get_densities(int32_t index, int* nuclides[], double* densities[], int* n) +.. c:function:: int openmc_material_get_densities(int32_t index, int** nuclides, double** densities, int* n) Get density for each nuclide in a material. @@ -268,12 +326,14 @@ C API :return: Return status (negative if an error occurs) :rtype: int -.. c:function:: int openmc_material_set_densities(int32_t index, int n, char* name[], double density[]) +.. c:function:: int openmc_material_set_densities(int32_t index, int n, const char** name, const double density*) :param int32_t index: Index in the materials array :param int n: Length of name/density - :param char** name: Array of nuclide names - :param double[] density: Array of densities + :param name: Array of nuclide names + :type name: const char** + :param density: Array of densities + :type density: const double* :return: Return status (negative if an error occurs) :rtype: int @@ -296,13 +356,14 @@ C API :return: Return status (negative if an error occurred) :rtype: int -.. c:function:: int openmc_material_filter_set_bins(int32_t index, int32_t n, int32_t* bins) +.. c:function:: int openmc_material_filter_set_bins(int32_t index, int32_t n, const int32_t* bins) Set the bins for a material filter :param int32_t index: Index in the filters array :param int32_t n: Number of bins - :param int32_t* bins: Index in the materials array for each bin + :param bins: Index in the materials array for each bin + :type bins: const int32_t* :return: Return status (negative if an error occurred) :rtype: int @@ -323,7 +384,7 @@ C API finished (zero). :rtype: int -.. c:function:: int openmc_nuclide_name(int index, char* name[]) +.. c:function:: int openmc_nuclide_name(int index, char** name) Get name of a nuclide @@ -352,6 +413,33 @@ C API Initialize a simulation. Must be called after openmc_init(). +.. c:function:: int openmc_source_bank(struct Bank** ptr, int64_t* n) + + Return a pointer to the source bank array. + + :param ptr: Pointer to the source bank array + :type ptr: struct Bank** + :param int64_t* n: Length of the source bank array + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: int openmc_source_set_strength(int32_t index, double strength) + + Set the strength of an external source + + :param int32_t index: Index in the external source array + :param double strength: Source strength + :return: Return status (negative if an error occurred) + :rtype: int + +.. c:function:: void 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[] + .. c:function:: int openmc_tally_get_id(int32_t index, int32_t* id) Get the ID of a tally @@ -378,7 +466,7 @@ C API :return: Return status (negative if an error occurred) :rtype: int -.. c:function:: int openmc_tally_get_nuclides(int32_t index, int* nuclides[], int* n) +.. c:function:: int openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n) Get nuclides specified in a tally @@ -408,13 +496,14 @@ C API :return: Return status (negative if an error occurred) :rtype: int -.. c:function:: int openmc_tally_set_filters(int32_t index, int n, int32_t* indices) +.. c:function:: int openmc_tally_set_filters(int32_t index, int n, const int32_t* indices) Set filters for a tally :param int32_t index: Index in the tallies array :param int n: Number of filters - :param int32_t* indices: Array of filter indices + :param indices: Array of filter indices + :type indices: const int32_t* :return: Return status (negative if an error occurred) :rtype: int @@ -427,22 +516,24 @@ C API :return: Return status (negative if an error occurred) :rtype: int -.. c:function:: int openmc_tally_set_nuclides(int32_t index, int n, char* nuclides[]) +.. c:function:: int openmc_tally_set_nuclides(int32_t index, int n, const char** nuclides) Set the nuclides for a tally :param int32_t index: Index in the tallies array :param int n: Number of nuclides - :param char** nuclides: Array of nuclide names + :param nuclides: Array of nuclide names + :type nuclides: const char** :return: Return status (negative if an error occurred) :rtype: int -.. c:function:: int openmc_tally_set_scores(int32_t index, int n, int* scores) +.. c:function:: int openmc_tally_set_scores(int32_t index, int n, const int* scores) Set scores for a tally :param int32_t index: Index in the tallies array :param int n: Number of scores - :param int* scores: Array of scores + :param scores: Array of scores + :type scores: const int* :return: Return status (negative if an error occurred) :rtype: int diff --git a/include/openmc.h b/include/openmc.h index 5f72e99ed..bb04907a5 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -8,29 +8,30 @@ extern "C" { #endif - void openmc_calculate_voumes(); - void openmc_finalize(); - void openmc_hard_reset(); - int openmc_next_batch(); - void openmc_plot_geometry(); - void openmc_reset(); - void openmc_run(); - void openmc_simulation_finalize(); - void openmc_simulation_init(); + struct Bank { + double wgt; + double xyz[3]; + double uvw[3]; + double E; + int delayed_group; + }; + void openmc_calculate_voumes(); 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, int32_t* indices); + int openmc_cell_set_fill(int32_t index, int type, int32_t n, const int32_t* indices); int openmc_cell_set_id(int32_t index, int32_t id); - int openmc_cell_set_temperature(int32_t index, double T, int32_t* instance); + int openmc_cell_set_temperature(int32_t index, double T, const int32_t* instance); int openmc_energy_filter_get_bins(int32_t index, double** energies, int32_t* n); - int openmc_energy_filter_set_bins(int32_t index, int32_t n, double* energies); + int openmc_energy_filter_set_bins(int32_t index, int32_t n, const double* energies); 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_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_set_id(int32_t index, int32_t id); + void 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); @@ -38,28 +39,39 @@ extern "C" { 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_tally_tally(int32_t id, int32_t* index); + int openmc_get_tally_index(int32_t id, int32_t* index); + void openmc_hard_reset(); + void openmc_init(const int* intracomm); int openmc_load_nuclide(char name[]); - int openmc_material_add_nuclide(int32_t index, char name[], double density); - int openmc_material_get_densities(int32_t index, int* nuclides[], double* densities[], int* n); + 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); int openmc_material_get_id(int32_t index, int32_t* id); int openmc_material_set_density(int32_t index, double density); - int openmc_material_set_densities(int32_t index, int n, char* name[], double density[]); + int openmc_material_set_densities(int32_t index, int n, const char** name, const double* density); 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, int32_t* bins); + int openmc_material_filter_set_bins(int32_t index, int32_t n, const int32_t* bins); int openmc_mesh_filter_set_mesh(int32_t index, int32_t index_mesh); - int openmc_nuclide_name(int index, char* name[]); + int openmc_next_batch(); + 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_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_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_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_filters(int32_t index, int n, int32_t* indices); + 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, char* nuclides[]); - int openmc_tally_set_scores(int32_t index, int n, int* scores); + 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); // Error codes extern int E_UNASSIGNED; diff --git a/src/api.F90 b/src/api.F90 index 9db06ac20..eb9a99695 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -4,6 +4,7 @@ module openmc_api 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 use error @@ -20,6 +21,8 @@ module openmc_api use random_lcg, only: seed, openmc_set_seed use settings use simulation_header + use source_header, only: openmc_extend_sources, openmc_source_set_strength + use state_point, only: openmc_statepoint_write use tally_header use tally_filter_header use tally_filter @@ -43,6 +46,7 @@ module openmc_api public :: openmc_extend_filters public :: openmc_extend_cells public :: openmc_extend_materials + public :: openmc_extend_sources public :: openmc_extend_tallies public :: openmc_filter_get_id public :: openmc_filter_get_type @@ -77,6 +81,8 @@ module openmc_api public :: openmc_run public :: openmc_simulation_finalize public :: openmc_simulation_init + public :: openmc_source_bank + public :: openmc_source_set_strength public :: openmc_tally_get_id public :: openmc_tally_get_filters public :: openmc_tally_get_n_realizations diff --git a/src/source_header.F90 b/src/source_header.F90 index fca05d816..da15046ab 100644 --- a/src/source_header.F90 +++ b/src/source_header.F90 @@ -17,6 +17,8 @@ module source_header implicit none private public :: free_memory_source + public :: openmc_extend_sources + public :: openmc_source_set_strength integer :: n_accept = 0 ! Number of samples accepted integer :: n_reject = 0 ! Number of samples rejected From 59b15fe0be0ef5389df3b1af61408dc500f63add Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 8 Jan 2018 08:34:57 -0600 Subject: [PATCH 090/282] Address a few @nelsonag comments in review --- docs/source/releasenotes.rst | 3 +++ docs/source/usersguide/install.rst | 24 +++++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/source/releasenotes.rst b/docs/source/releasenotes.rst index 4cacd4a7f..28af52f5f 100644 --- a/docs/source/releasenotes.rst +++ b/docs/source/releasenotes.rst @@ -26,6 +26,9 @@ of: mat.add_nuclide('H1', 1.0) mat.isotropic = ['H1'] + To treat all nuclides in a material this way, the + :meth:`Material.make_isotropic_in_lab` method can still be used. + - The initializers for :class:`openmc.Intersection` and :class:`openmc.Union` now expect an iterable. diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 1fee22e71..b59572923 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -116,21 +116,27 @@ Prerequisites OpenMC uses HDF5 for many input/output files. As such, you will need to have HDF5 installed on your computer. The installed version will need to have been compiled with the same compiler you intend to compile OpenMC - with. On Debian derivatives, HDF5 and/or parallel HDF5 can be installed - through the APT package manager: - - .. code-block:: sh + with. If compiling with gcc from the APT repositories, users of Debian + derivatives can install HDF5 and/or parallel HDF5 through the package + manager:: sudo apt install libhdf5-dev + Parallel versions of the HDF5 library called `libhdf5-mpich-dev` and + `libhdf5-openmpi-dev` exist which are built against MPICH and OpenMPI, + respectively. To link against a parallel HDF5 library, make sure to set + the HDF5_PREFER_PARALLEL CMake option, e.g.:: + + FC=mpifort.mpich cmake -DHDF5_PREFER_PARALLEL=on .. + Note that the exact package names may vary depending on your particular - distribution and version. If you are using HDF5 in conjunction with MPI, - we recommend that your HDF5 installation be built with parallel I/O + distribution and version. + + If you are using building HDF5 from source in conjunction with MPI, we + recommend that your HDF5 installation be built with parallel I/O features. An example of configuring HDF5_ is listed below:: - FC=/opt/mpich/3.2/bin/mpif90 CC=/opt/mpich/3.2/bin/mpicc \ - ./configure --prefix=/opt/hdf5/1.10.1 --enable-fortran \ - --enable-parallel + FC=mpifort ./configure --enable-fortran --enable-parallel You may omit ``--enable-parallel`` if you want to compile HDF5_ in serial. From a859b734ea53fcc798a8e403854f239d87c3a018 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 8 Jan 2018 13:01:32 -0600 Subject: [PATCH 091/282] Allow OMP_SCHEDULE to control schedule of main parallel do loop --- src/initialize.F90 | 11 +++++++++++ src/simulation.F90 | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/initialize.F90 b/src/initialize.F90 index 392e8d6ba..dd7894f96 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -45,6 +45,9 @@ contains integer, intent(in), optional :: intracomm ! MPI intracommunicator integer :: err +#ifdef _OPENMP + character(MAX_WORD_LEN) :: envvar +#endif ! Copy the communicator to a new variable. This is done to avoid changing ! the signature of this subroutine. If MPI is being used but no communicator @@ -76,6 +79,14 @@ contains call initialize_mpi(comm) #endif +#ifdef _OPENMP + ! Change schedule of main parallel-do loop if OMP_SCHEDULE is set + call get_environment_variable("OMP_SCHEDULE", envvar) + if (len_trim(envvar) == 0) then + call omp_set_schedule(omp_sched_static, 0) + end if +#endif + ! Initialize HDF5 interface call hdf5_initialize() diff --git a/src/simulation.F90 b/src/simulation.F90 index e8997fc9b..c80954f3f 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -100,7 +100,7 @@ contains ! ==================================================================== ! LOOP OVER PARTICLES -!$omp parallel do schedule(static) firstprivate(p) copyin(tally_derivs) +!$omp parallel do schedule(runtime) firstprivate(p) copyin(tally_derivs) PARTICLE_LOOP: do i_work = 1, work current_work = i_work From 64ca0b752ffce91f9f36732ad603818884561e5c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Dec 2017 15:03:39 +0700 Subject: [PATCH 092/282] Try installing NJOY on Travis, separate things into scripts --- .travis.yml | 48 +++++++++++++------------------- tools/ci/travis-before-script.sh | 17 +++++++++++ tools/ci/travis-install-njoy.sh | 8 ++++++ tools/ci/travis-script.sh | 9 ++++++ 4 files changed, 54 insertions(+), 28 deletions(-) create mode 100755 tools/ci/travis-before-script.sh create mode 100755 tools/ci/travis-install-njoy.sh create mode 100755 tools/ci/travis-script.sh diff --git a/.travis.yml b/.travis.yml index 49b6ed2d1..7ff7a1598 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,18 +8,29 @@ addons: apt: packages: - gfortran - - g++ - mpich - libmpich-dev cache: directories: - $HOME/nndc_hdf5 + - $HOME/endf-b-vii.1 env: - - OPENMC_CONFIG="check_source" - - OPENMC_CONFIG="^hdf5-debug$" - - OPENMC_CONFIG="^omp-hdf5-debug$" - - OPENMC_CONFIG="^mpi-hdf5-debug$" - - OPENMC_CONFIG="^phdf5-debug$" + global: + - FC=gfortran + - MPI_DIR=/usr + - PHDF5_DIR=/usr + - HDF5_DIR=/usr + - OMP_NUM_THREADS=2 + - OPENMC_CROSS_SECTIONS=$HOME/nndc_hdf5/cross_sections.xml + - OPENMC_ENDF_DATA=$HOME/endf-b-vii.1 + - OPENMC_MULTIPOLE_LIBRARY=$HOME/multipole_lib + - PATH=$PATH:$HOME/NJOY2016/build + matrix: + - OPENMC_CONFIG="check_source" + - OPENMC_CONFIG="^hdf5-debug$" + - OPENMC_CONFIG="^omp-hdf5-debug$" + - OPENMC_CONFIG="^mpi-hdf5-debug$" + - OPENMC_CONFIG="^phdf5-debug$" # We aren't testing the check_source script so just run it with Python 3. matrix: @@ -32,37 +43,18 @@ before_install: sudo add-apt-repository ppa:nschloe/hdf5-backports -y; sudo apt-get update -q; sudo apt-get install libhdf5-serial-dev libhdf5-mpich-dev -y; - export FC=gfortran; - export MPI_DIR=/usr; - export PHDF5_DIR=/usr; - export HDF5_DIR=/usr; fi install: - if [[ $OPENMC_CONFIG != "check_source" ]]; then + ./tools/ci/travis-install-njoy.sh pip install numpy cython; pip install --upgrade pytest; pip install -e .[test]; fi before_script: - - if [[ $OPENMC_CONFIG != "check_source" ]]; then - if [[ ! -e $HOME/nndc_hdf5/cross_sections.xml ]]; then - wget https://anl.box.com/shared/static/a6sw2cep34wlz6b9i9jwiotaqoayxcxt.xz -O - | tar -C $HOME -xvJ; - fi; - export OPENMC_CROSS_SECTIONS=$HOME/nndc_hdf5/cross_sections.xml; - git clone --branch=master git://github.com/smharper/windowed_multipole_library.git wmp_lib; - tar xzvf wmp_lib/multipole_lib.tar.gz; - export OPENMC_MULTIPOLE_LIBRARY=$PWD/multipole_lib; - fi + - ./tools/ci/travis-before-script.sh script: - - cd tests - - export OMP_NUM_THREADS=2 - - if [[ $OPENMC_CONFIG == "check_source" ]]; then - ./check_source.py; - else - ./run_tests.py -C $OPENMC_CONFIG -j 2 && - pytest --cov=../openmc -v unit_tests/; - fi - - cd .. + - ./tools/ci/travis-script.sh diff --git a/tools/ci/travis-before-script.sh b/tools/ci/travis-before-script.sh new file mode 100755 index 000000000..6a56fcfe7 --- /dev/null +++ b/tools/ci/travis-before-script.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -ex +if [[ $OPENMC_CONFIG != "check_source" ]]; then + # Download NNDC HDF5 data + if [[ ! -e $HOME/nndc_hdf5/cross_sections.xml ]]; then + wget https://anl.box.com/shared/static/a6sw2cep34wlz6b9i9jwiotaqoayxcxt.xz -O - | tar -C $HOME -xvJ + fi + + # Download ENDF/B-VII.1 distribution + if [[ ! -d $HOME/endf-b-vii.1/neutrons ]]; then + wget https://anl.box.com/shared/static/4kd2gxnf4gtk4w1c8eua5fsua22kvgjb.xz -O - | tar -C $HOME -xvJ + fi + + # Download multipole library + git clone --branch=master git://github.com/smharper/windowed_multipole_library.git wmp_lib + tar -C $HOME -xzvf wmp_lib/multipole_lib.tar.gz +fi diff --git a/tools/ci/travis-install-njoy.sh b/tools/ci/travis-install-njoy.sh new file mode 100755 index 000000000..a338cbf4b --- /dev/null +++ b/tools/ci/travis-install-njoy.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -ex +cd $HOME +git clone https://github.com/njoy/NJOY2016 +cd NJOY2016 +sed -i -e 's/5\.1/4.8/' CMakeLists.txt +mkdir build && cd build +cmake .. && make 2>/dev/null && sudo make install diff --git a/tools/ci/travis-script.sh b/tools/ci/travis-script.sh new file mode 100755 index 000000000..71a3c6d74 --- /dev/null +++ b/tools/ci/travis-script.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -ex +cd tests +if [[ $OPENMC_CONFIG == "check_source" ]]; then + ./check_source.py; +else + ./run_tests.py -C $OPENMC_CONFIG -j 2 + pytest --cov=../openmc -v unit_tests/ +fi From f455ede48232e6490c4f9223fea950b84c54d6fd Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Dec 2017 16:42:24 +0700 Subject: [PATCH 093/282] Remove check_source Travis configuration, make other simplifications --- .travis.yml | 22 ++++------------------ tools/ci/travis-before-script.sh | 25 ++++++++++++------------- tools/ci/travis-install.sh | 19 +++++++++++++++++++ tools/ci/travis-script.sh | 9 ++++----- 4 files changed, 39 insertions(+), 36 deletions(-) create mode 100755 tools/ci/travis-install.sh diff --git a/.travis.yml b/.travis.yml index 7ff7a1598..de8b7dcc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,32 +26,18 @@ env: - OPENMC_MULTIPOLE_LIBRARY=$HOME/multipole_lib - PATH=$PATH:$HOME/NJOY2016/build matrix: - - OPENMC_CONFIG="check_source" - OPENMC_CONFIG="^hdf5-debug$" - OPENMC_CONFIG="^omp-hdf5-debug$" - OPENMC_CONFIG="^mpi-hdf5-debug$" - OPENMC_CONFIG="^phdf5-debug$" -# We aren't testing the check_source script so just run it with Python 3. -matrix: - exclude: - - python: "2.7" - env: OPENMC_CONFIG="check_source" - before_install: - - if [[ $OPENMC_CONFIG != "check_source" ]]; then - sudo add-apt-repository ppa:nschloe/hdf5-backports -y; - sudo apt-get update -q; - sudo apt-get install libhdf5-serial-dev libhdf5-mpich-dev -y; - fi + - sudo add-apt-repository ppa:nschloe/hdf5-backports -y + - sudo apt-get update -q + - sudo apt-get install libhdf5-serial-dev libhdf5-mpich-dev -y install: - - if [[ $OPENMC_CONFIG != "check_source" ]]; then - ./tools/ci/travis-install-njoy.sh - pip install numpy cython; - pip install --upgrade pytest; - pip install -e .[test]; - fi + - ./tools/ci/travis-install.sh before_script: - ./tools/ci/travis-before-script.sh diff --git a/tools/ci/travis-before-script.sh b/tools/ci/travis-before-script.sh index 6a56fcfe7..bbf4980b0 100755 --- a/tools/ci/travis-before-script.sh +++ b/tools/ci/travis-before-script.sh @@ -1,17 +1,16 @@ #!/bin/bash set -ex -if [[ $OPENMC_CONFIG != "check_source" ]]; then - # Download NNDC HDF5 data - if [[ ! -e $HOME/nndc_hdf5/cross_sections.xml ]]; then - wget https://anl.box.com/shared/static/a6sw2cep34wlz6b9i9jwiotaqoayxcxt.xz -O - | tar -C $HOME -xvJ - fi - # Download ENDF/B-VII.1 distribution - if [[ ! -d $HOME/endf-b-vii.1/neutrons ]]; then - wget https://anl.box.com/shared/static/4kd2gxnf4gtk4w1c8eua5fsua22kvgjb.xz -O - | tar -C $HOME -xvJ - fi - - # Download multipole library - git clone --branch=master git://github.com/smharper/windowed_multipole_library.git wmp_lib - tar -C $HOME -xzvf wmp_lib/multipole_lib.tar.gz +# Download NNDC HDF5 data +if [[ ! -e $HOME/nndc_hdf5/cross_sections.xml ]]; then + wget https://anl.box.com/shared/static/a6sw2cep34wlz6b9i9jwiotaqoayxcxt.xz -O - | tar -C $HOME -xvJ fi + +# Download ENDF/B-VII.1 distribution +if [[ ! -d $HOME/endf-b-vii.1/neutrons ]]; then + wget https://anl.box.com/shared/static/4kd2gxnf4gtk4w1c8eua5fsua22kvgjb.xz -O - | tar -C $HOME -xvJ +fi + +# Download multipole library +git clone --branch=master git://github.com/smharper/windowed_multipole_library.git wmp_lib +tar -C $HOME -xzvf wmp_lib/multipole_lib.tar.gz diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh new file mode 100755 index 000000000..354ae7f7f --- /dev/null +++ b/tools/ci/travis-install.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -ex + +# Install NJOY 2016 +./tools/ci/travis-install-njoy.sh + +# Running OpenMC's setup.py requires numpy/cython already +pip install numpy cython + +# pytest installed by default -- make sure we get latest +pip install --upgrade pytest + +# Pandas stopped supporting Python 3.4 with version 0.21 +if [[ "$TRAVIS_PYTHON_VERSION" == "3.4" ]]; then + pip install pandas==0.20.3 +fi + +# Install OpenMC in editable mode +pip install -e .[test] diff --git a/tools/ci/travis-script.sh b/tools/ci/travis-script.sh index 71a3c6d74..978a2811c 100755 --- a/tools/ci/travis-script.sh +++ b/tools/ci/travis-script.sh @@ -1,9 +1,8 @@ #!/bin/bash set -ex cd tests -if [[ $OPENMC_CONFIG == "check_source" ]]; then - ./check_source.py; -else - ./run_tests.py -C $OPENMC_CONFIG -j 2 - pytest --cov=../openmc -v unit_tests/ +if [[ $TRAVIS_PYTHON_VERSION == "3.4" && $OPENMC_CONFIG == '^hdf5-debug$' ]]; then + ./check_source.py fi +./run_tests.py -C $OPENMC_CONFIG -j 2 +pytest --cov=../openmc -v unit_tests/ From 4e9ee6c2e26772d74d8c5355a34b93819ac9ff5c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 14 Dec 2017 22:04:42 +0700 Subject: [PATCH 094/282] Start adding unit tests for openmc.data --- openmc/data/neutron.py | 2 + tests/unit_tests/test_data_neutron.py | 121 ++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 tests/unit_tests/test_data_neutron.py diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 08d12106c..b44c97afc 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -465,6 +465,8 @@ class IncidentNeutron(EqualityMixin): return [mt] elif mt in SUM_RULES: mts = SUM_RULES[mt] + else: + return [] complete = False while not complete: new_mts = [] diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py new file mode 100644 index 000000000..9c27a5152 --- /dev/null +++ b/tests/unit_tests/test_data_neutron.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +from collections import Mapping +import os + +import numpy as np +import pandas as pd +import pytest +import openmc.data + + +@pytest.fixture +def pu239(): + directory = os.path.dirname(os.environ['OPENMC_CROSS_SECTIONS']) + filename = os.path.join(directory, 'Pu239.h5') + return openmc.data.IncidentNeutron.from_hdf5(filename) + + +@pytest.fixture +def gd154(): + directory = os.environ['OPENMC_ENDF_DATA'] + filename = os.path.join(directory, 'neutrons', 'n-064_Gd_154.endf') + return openmc.data.IncidentNeutron.from_endf(filename) + + +@pytest.fixture +def ace_file(): + directory = os.environ['OPENMC_ENDF_DATA'] + h1 = os.path.join(directory, 'neutrons', 'n-001_H_001.endf') + retcode = openmc.data.njoy.make_ace(h1, ace='h1.ace') + assert retcode == 0 + return os.path.join(os.getcwd(), 'h1.ace') + + +@pytest.fixture +def h1(ace_file): + return openmc.data.IncidentNeutron.from_ace(ace_file) + + +def test_attributes(pu239): + assert pu239.name == 'Pu239' + assert pu239.mass_number == 239 + assert pu239.metastable == 0 + assert pu239.atomic_symbol == 'Pu' + assert pu239.atomic_weight_ratio == pytest.approx(236.9986) + + +def test_energy_grid(pu239): + assert isinstance(pu239.energy, Mapping) + for temp, grid in pu239.energy.items(): + assert temp.endswith('K') + assert np.all(np.diff(grid) >= 0.0) + + +def test_elastic(pu239): + elastic = pu239.reactions[2] + assert elastic.center_of_mass + assert elastic.q_value == 0.0 + assert elastic.mt == 2 + assert '0K' in elastic.xs + assert '294K' in elastic.xs + assert len(elastic.products) == 1 + p = elastic.products[0] + assert isinstance(p, openmc.data.Product) + assert p.particle == 'neutron' + assert p.emission_mode == 'prompt' + assert len(p.distribution) == 1 + d = p.distribution[0] + assert isinstance(d, openmc.data.UncorrelatedAngleEnergy) + assert isinstance(d.angle, openmc.data.AngleDistribution) + assert d.energy is None + assert p.yield_(0.0) == 1.0 + + +def test_fission(pu239): + fission = pu239.reactions[18] + assert not fission.center_of_mass + assert fission.q_value == pytest.approx(198902000.0) + assert fission.mt == 18 + assert '294K' in fission.xs + assert len(fission.products) == 8 + prompt = fission.products[0] + assert prompt.particle == 'neutron' + assert prompt.yield_(1.0e-5) == pytest.approx(2.874262) + delayed = [p for p in fission.products if p.emission_mode == 'delayed'] + assert len(delayed) == 6 + assert all(d.particle == 'neutron' for d in delayed) + assert sum(d.decay_rate for d in delayed) == pytest.approx(4.037212) + assert sum(d.yield_(1.0) for d in delayed) == pytest.approx(0.00645) + photon = fission.products[-1] + assert photon.particle == 'photon' + + +def test_get_reaction_components(h1): + assert h1.get_reaction_components(1) == [2, 102] + assert h1.get_reaction_components(101) == [102] + assert h1.get_reaction_components(102) == [102] + assert h1.get_reaction_components(51) == [] + + +def test_resonances(gd154): + res = gd154.resonances + assert isinstance(res, openmc.data.Resonances) + assert len(res.ranges) == 2 + resolved, unresolved = res.ranges + assert isinstance(resolved, openmc.data.ReichMoore) + assert isinstance(unresolved, openmc.data.Unresolved) + assert resolved.energy_min == pytest.approx(1e-5) + assert resolved.energy_max == pytest.approx(2760.) + assert resolved.target_spin == 0.0 + assert resolved.channel_radius[0](1.0) == pytest.approx(0.74) + assert isinstance(resolved.parameters, pd.DataFrame) + assert (resolved.parameters['L'] == 0).all() + assert (resolved.parameters['J'] <= 0.5).all() + assert (resolved.parameters['fissionWidthA'] == 0.0).all() + + +def test_reconstruct(gd154): + elastic = gd154.reactions[2].xs['0K'] + assert isinstance(elastic, openmc.data.ResonancesWithBackground) + assert elastic(0.0253) == pytest.approx(5.7228949796394524) From e94dd55b31424c02afc41c7dabb1846ab6247b93 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 16 Dec 2017 21:24:25 +0700 Subject: [PATCH 095/282] More unit tests for openmc.data and a few small fixes --- openmc/data/multipole.py | 3 +- openmc/data/neutron.py | 6 - tests/unit_tests/test_data_multipole.py | 41 +++++ tests/unit_tests/test_data_neutron.py | 204 +++++++++++++++++++++--- 4 files changed, 223 insertions(+), 31 deletions(-) create mode 100644 tests/unit_tests/test_data_multipole.py diff --git a/openmc/data/multipole.py b/openmc/data/multipole.py index 8e7f7e18d..653ff0825 100644 --- a/openmc/data/multipole.py +++ b/openmc/data/multipole.py @@ -531,7 +531,8 @@ class WindowedMultipole(EqualityMixin): sqrtkT = sqrt(K_BOLTZMANN * T) sqrtE = sqrt(E) invE = 1.0 / E - dopp = self.sqrtAWR / sqrtkT + if sqrtkT > 0.0: + dopp = self.sqrtAWR / sqrtkT # Locate us. The i_window calc omits a + 1 present in F90 because of # the 1-based vs. 0-based indexing. Similarly startw needs to be diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index b44c97afc..7dcac5d6e 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -529,12 +529,6 @@ class IncidentNeutron(EqualityMixin): rx_group = rxs_group.create_group('reaction_{:03}'.format(rx.mt)) rx.to_hdf5(rx_group) - # Write 0K elastic scattering if needed - if '0K' in rx.xs and '0K' not in rx_group: - group = rx_group.create_group('0K') - dset = group.create_dataset('xs', data=rx.xs['0K'].y) - dset.attrs['threshold_idx'] = 1 - # Write total nu data if available if len(rx.derived_products) > 0 and 'total_nu' not in g: tgroup = g.create_group('total_nu') diff --git a/tests/unit_tests/test_data_multipole.py b/tests/unit_tests/test_data_multipole.py new file mode 100644 index 000000000..c485abb4f --- /dev/null +++ b/tests/unit_tests/test_data_multipole.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +from collections import Callable +import os + +import numpy as np +import pandas as pd +import pytest +import openmc.data + + +@pytest.fixture(scope='module') +def u235(): + directory = os.environ['OPENMC_MULTIPOLE_LIBRARY'] + filename = os.path.join(directory, '092235.h5') + return openmc.data.WindowedMultipole.from_hdf5(filename) + + +@pytest.fixture(scope='module') +def fe56(): + directory = os.environ['OPENMC_MULTIPOLE_LIBRARY'] + filename = os.path.join(directory, '026056.h5') + return openmc.data.WindowedMultipole.from_hdf5(filename) + + +def test_evaluate(u235): + """Make sure multipole object can be called.""" + energies = [1e-3, 1.0, 10.0, 50.] + total, absorption, fission = u235(energies, 0.0) + assert total[1] == pytest.approx(90.64895383) + total, absorption, fission = u235(energies, 300.0) + assert total[1] == pytest.approx(91.12534964) + + +def test_high_l(fe56): + """Test a nuclide (Fe56) with a high l-value (4).""" + energies = [1e-3, 1.0, 10.0, 1e3, 1e5] + total, absorption, fission = fe56(energies, 0.0) + assert total[0] == pytest.approx(25.072619556789267) + total, absorption, fission = fe56(energies, 300.0) + assert total[0] == pytest.approx(27.85535792368082) diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index 9c27a5152..4809c5638 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from collections import Mapping +from collections import Mapping, Callable import os import numpy as np @@ -9,32 +9,79 @@ import pytest import openmc.data -@pytest.fixture +_TEMPERATURES = [300., 600., 900.] +_ENDF_DATA = os.environ['OPENMC_ENDF_DATA'] + + +@pytest.fixture(scope='module') def pu239(): + """Pu239 HDF5 data.""" directory = os.path.dirname(os.environ['OPENMC_CROSS_SECTIONS']) filename = os.path.join(directory, 'Pu239.h5') return openmc.data.IncidentNeutron.from_hdf5(filename) -@pytest.fixture -def gd154(): - directory = os.environ['OPENMC_ENDF_DATA'] - filename = os.path.join(directory, 'neutrons', 'n-064_Gd_154.endf') +@pytest.fixture(scope='module') +def xe135(): + """Xe135 ENDF data (contains SLBW resonance range)""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-054_Xe_135.endf') return openmc.data.IncidentNeutron.from_endf(filename) -@pytest.fixture -def ace_file(): - directory = os.environ['OPENMC_ENDF_DATA'] - h1 = os.path.join(directory, 'neutrons', 'n-001_H_001.endf') - retcode = openmc.data.njoy.make_ace(h1, ace='h1.ace') - assert retcode == 0 - return os.path.join(os.getcwd(), 'h1.ace') +@pytest.fixture(scope='module') +def sm150(): + """Sm150 ENDF data (contains MLBW resonance range)""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-062_Sm_150.endf') + return openmc.data.IncidentNeutron.from_endf(filename) -@pytest.fixture -def h1(ace_file): - return openmc.data.IncidentNeutron.from_ace(ace_file) +@pytest.fixture(scope='module') +def gd154(): + """Gd154 ENDF data (contains Reich Moore resonance range)""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-064_Gd_154.endf') + return openmc.data.IncidentNeutron.from_endf(filename) + + +@pytest.fixture(scope='module') +def cl35(): + """Cl35 ENDF data (contains RML resonance range)""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-017_Cl_035.endf') + return openmc.data.IncidentNeutron.from_endf(filename) + + +@pytest.fixture(scope='module') +def am241(): + """Am241 ENDF data (contains Madland-Nix fission energy distribution).""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-095_Am_241.endf') + return openmc.data.IncidentNeutron.from_endf(filename) + + +@pytest.fixture(scope='module') +def u233(): + """U233 ENDF data (contains Watt fission energy distribution).""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-092_U_233.endf') + return openmc.data.IncidentNeutron.from_endf(filename) + + +@pytest.fixture(scope='module') +def u236(): + """U236 ENDF data (contains Watt fission energy distribution).""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-092_U_236.endf') + return openmc.data.IncidentNeutron.from_endf(filename) + + +@pytest.fixture(scope='module') +def na22(): + """Na22 ENDF data (contains evaporation spectrum).""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-011_Na_022.endf') + return openmc.data.IncidentNeutron.from_endf(filename) + + +@pytest.fixture(scope='module') +def h2(): + endf_file = os.path.join(_ENDF_DATA, 'neutrons', 'n-001_H_002.endf') + return openmc.data.IncidentNeutron.from_njoy( + endf_file, temperatures=_TEMPERATURES) def test_attributes(pu239): @@ -45,6 +92,16 @@ def test_attributes(pu239): assert pu239.atomic_weight_ratio == pytest.approx(236.9986) +def test_fission_energy(pu239): + fer = pu239.fission_energy + assert isinstance(fer, openmc.data.FissionEnergyRelease) + components = ['betas', 'delayed_neutrons', 'delayed_photons', 'fragments', + 'neutrinos', 'prompt_neutrons', 'prompt_photons', 'recoverable', + 'total', 'q_prompt', 'q_recoverable', 'q_total'] + for c in components: + assert isinstance(getattr(fer, c), Callable) + + def test_energy_grid(pu239): assert isinstance(pu239.energy, Mapping) for temp, grid in pu239.energy.items(): @@ -52,6 +109,13 @@ def test_energy_grid(pu239): assert np.all(np.diff(grid) >= 0.0) +def test_reactions(pu239): + assert 2 in pu239.reactions + assert isinstance(pu239.reactions[2], openmc.data.Reaction) + with pytest.raises(KeyError): + pu239.reactions[14] + + def test_elastic(pu239): elastic = pu239.reactions[2] assert elastic.center_of_mass @@ -91,18 +155,72 @@ def test_fission(pu239): assert photon.particle == 'photon' -def test_get_reaction_components(h1): - assert h1.get_reaction_components(1) == [2, 102] - assert h1.get_reaction_components(101) == [102] - assert h1.get_reaction_components(102) == [102] - assert h1.get_reaction_components(51) == [] +def test_urr(pu239): + for T, ptable in pu239.urr.items(): + assert T.endswith('K') + assert isinstance(ptable, openmc.data.ProbabilityTables) + ptable = pu239.urr['294K'] + assert ptable.absorption_flag == -1 + assert ptable.energy[0] == pytest.approx(2500.001) + assert ptable.energy[-1] == pytest.approx(29999.99) + assert ptable.inelastic_flag == 51 + assert ptable.interpolation == 2 + assert not ptable.multiply_smooth + assert ptable.table.shape == (70, 6, 20) + assert ptable.table.shape[0] == ptable.energy.size -def test_resonances(gd154): +def test_get_reaction_components(h2): + assert h2.get_reaction_components(1) == [2, 16, 102] + assert h2.get_reaction_components(101) == [102] + assert h2.get_reaction_components(16) == [16] + assert h2.get_reaction_components(51) == [] + + +def test_export_to_hdf5(tmpdir, pu239, gd154): + filename = str(tmpdir.join('pu239.h5')) + pu239.export_to_hdf5(filename) + assert os.path.exists(filename) + with pytest.raises(NotImplementedError): + gd154.export_to_hdf5('gd154.h5') + +def test_slbw(xe135): + res = xe135.resonances + assert isinstance(res, openmc.data.Resonances) + assert len(res.ranges) == 2 + resolved = res.resolved + assert isinstance(resolved, openmc.data.SingleLevelBreitWigner) + assert resolved.energy_min == pytest.approx(1e-5) + assert resolved.energy_max == pytest.approx(190.) + assert resolved.target_spin == pytest.approx(1.5) + assert isinstance(resolved.parameters, pd.DataFrame) + s = resolved.parameters.iloc[0] + assert s['energy'] == pytest.approx(0.084) + + xs = resolved.reconstruct([10., 30., 100.]) + assert sorted(xs.keys()) == [2, 18, 102] + assert np.all(xs[18] == 0.0) + + +def test_mlbw(sm150): + resolved = sm150.resonances.resolved + assert isinstance(resolved, openmc.data.MultiLevelBreitWigner) + assert resolved.energy_min == pytest.approx(1e-5) + assert resolved.energy_max == pytest.approx(1570.) + assert resolved.target_spin == 0.0 + + xs = resolved.reconstruct([10., 100., 1000.]) + assert sorted(xs.keys()) == [2, 18, 102] + assert np.all(xs[18] == 0.0) + + +def test_reichmoore(gd154): res = gd154.resonances assert isinstance(res, openmc.data.Resonances) assert len(res.ranges) == 2 resolved, unresolved = res.ranges + assert resolved is res.resolved + assert unresolved is res.unresolved assert isinstance(resolved, openmc.data.ReichMoore) assert isinstance(unresolved, openmc.data.Unresolved) assert resolved.energy_min == pytest.approx(1e-5) @@ -114,8 +232,46 @@ def test_resonances(gd154): assert (resolved.parameters['J'] <= 0.5).all() assert (resolved.parameters['fissionWidthA'] == 0.0).all() - -def test_reconstruct(gd154): elastic = gd154.reactions[2].xs['0K'] assert isinstance(elastic, openmc.data.ResonancesWithBackground) assert elastic(0.0253) == pytest.approx(5.7228949796394524) + + +def test_rml(cl35): + resolved = cl35.resonances.resolved + assert isinstance(resolved, openmc.data.RMatrixLimited) + assert resolved.energy_min == pytest.approx(1e-5) + assert resolved.energy_max == pytest.approx(1.2e6) + assert resolved.target_spin == 0.0 + for group in resolved.spin_groups: + assert isinstance(group, openmc.data.SpinGroup) + + +def test_madland_nix(am241): + fission = am241.reactions[18] + prompt_neutron = fission.products[0] + dist = prompt_neutron.distribution[0].energy + assert isinstance(dist, openmc.data.MadlandNix) + assert dist.efl == pytest.approx(1029979.0) + assert dist.efh == pytest.approx(546729.7) + assert isinstance(dist.tm, Callable) + + +def test_watt(u233): + fission = u233.reactions[18] + prompt_neutron = fission.products[0] + dist = prompt_neutron.distribution[0].energy + assert isinstance(dist, openmc.data.WattEnergy) + + +def test_maxwell(u236): + fission = u236.reactions[18] + prompt_neutron = fission.products[0] + dist = prompt_neutron.distribution[0].energy + assert isinstance(dist, openmc.data.MaxwellEnergy) + + +def test_evaporation(na22): + n2n = na22.reactions[16] + dist = n2n.products[0].distribution[0].energy + assert isinstance(dist, openmc.data.Evaporation) From 2e5bfecdaf050ab129771da4744ddb11581f99e5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 18 Dec 2017 13:40:30 +0700 Subject: [PATCH 096/282] Add tests for openmc.data.thermal --- openmc/data/thermal.py | 25 +++--- tests/unit_tests/test_data_multipole.py | 2 - tests/unit_tests/test_data_thermal.py | 100 ++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 tests/unit_tests/test_data_thermal.py diff --git a/openmc/data/thermal.py b/openmc/data/thermal.py index 99e4b5a53..fefb71518 100644 --- a/openmc/data/thermal.py +++ b/openmc/data/thermal.py @@ -408,30 +408,27 @@ class ThermalScattering(EqualityMixin): # Cross section elastic_xs_type = elastic_group['xs'].attrs['type'].decode() if elastic_xs_type == 'Tabulated1D': - table.elastic_xs[T] = \ - Tabulated1D.from_hdf5(elastic_group['xs']) + table.elastic_xs[T] = Tabulated1D.from_hdf5( + elastic_group['xs']) elif elastic_xs_type == 'bragg': - table.elastic_xs[T] = \ - CoherentElastic.from_hdf5(elastic_group['xs']) + table.elastic_xs[T] = CoherentElastic.from_hdf5( + elastic_group['xs']) # Angular distribution if 'mu_out' in elastic_group: - table.elastic_mu_out[T] = \ - elastic_group['mu_out'].value + table.elastic_mu_out[T] = elastic_group['mu_out'].value # Read thermal inelastic scattering if 'inelastic' in Tgroup: inelastic_group = Tgroup['inelastic'] - table.inelastic_xs[T] = \ - Tabulated1D.from_hdf5(inelastic_group['xs']) + table.inelastic_xs[T] = Tabulated1D.from_hdf5( + inelastic_group['xs']) if table.secondary_mode in ('equal', 'skewed'): - table.inelastic_e_out[T] = \ - inelastic_group['energy_out'] - table.inelastic_mu_out[T] = \ - inelastic_group['mu_out'] + table.inelastic_e_out[T] = inelastic_group['energy_out'].value + table.inelastic_mu_out[T] = inelastic_group['mu_out'].value elif table.secondary_mode == 'continuous': - table.inelastic_dist[T] = \ - AngleEnergy.from_hdf5(inelastic_group) + table.inelastic_dist[T] = AngleEnergy.from_hdf5( + inelastic_group) return table diff --git a/tests/unit_tests/test_data_multipole.py b/tests/unit_tests/test_data_multipole.py index c485abb4f..dc30677e8 100644 --- a/tests/unit_tests/test_data_multipole.py +++ b/tests/unit_tests/test_data_multipole.py @@ -1,10 +1,8 @@ #!/usr/bin/env python -from collections import Callable import os import numpy as np -import pandas as pd import pytest import openmc.data diff --git a/tests/unit_tests/test_data_thermal.py b/tests/unit_tests/test_data_thermal.py new file mode 100644 index 000000000..4af815c88 --- /dev/null +++ b/tests/unit_tests/test_data_thermal.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python + +from collections import Callable +import os + +import numpy as np +import pytest +import openmc.data + + +_ENDF_DATA = os.environ['OPENMC_ENDF_DATA'] + + +@pytest.fixture(scope='module') +def h2o(): + """H in H2O thermal scattering data.""" + directory = os.path.dirname(os.environ['OPENMC_CROSS_SECTIONS']) + filename = os.path.join(directory, 'c_H_in_H2O.h5') + return openmc.data.ThermalScattering.from_hdf5(filename) + + +@pytest.fixture(scope='module') +def graphite(): + """Graphite thermal scattering data.""" + directory = os.path.dirname(os.environ['OPENMC_CROSS_SECTIONS']) + filename = os.path.join(directory, 'c_Graphite.h5') + return openmc.data.ThermalScattering.from_hdf5(filename) + + +@pytest.fixture(scope='module') +def h2o_njoy(): + path_h1 = os.path.join(_ENDF_DATA, 'neutrons', 'n-001_H_001.endf') + path_h2o = os.path.join(_ENDF_DATA, 'thermal_scatt', 'tsl-HinH2O.endf') + return openmc.data.ThermalScattering.from_njoy( + path_h1, path_h2o, temperatures=[293.6, 500.0]) + + +def test_h2o_attributes(h2o): + assert h2o.name == 'c_H_in_H2O' + assert h2o.nuclides == ['H1'] + assert h2o.secondary_mode == 'skewed' + assert h2o.temperatures == ['294K'] + assert h2o.atomic_weight_ratio == pytest.approx(0.999167) + + +def test_h2o_xs(h2o): + assert not h2o.elastic_xs + for temperature, func in h2o.inelastic_xs.items(): + assert temperature.endswith('K') + assert isinstance(func, Callable) + + +def test_graphite_attributes(graphite): + assert graphite.name == 'c_Graphite' + assert graphite.nuclides == ['C0', 'C12', 'C13'] + assert graphite.secondary_mode == 'skewed' + assert graphite.temperatures == ['296K'] + assert graphite.atomic_weight_ratio == pytest.approx(11.898) + + +def test_graphite_xs(graphite): + for temperature, func in graphite.elastic_xs.items(): + assert temperature.endswith('K') + assert isinstance(func, openmc.data.CoherentElastic) + for temperature, func in graphite.inelastic_xs.items(): + assert temperature.endswith('K') + assert isinstance(func, Callable) + elastic = graphite.elastic_xs['296K'] + assert elastic([1e-3, 1.0]) == pytest.approx([13.47464936, 0.62590156]) + + +def test_export_to_hdf5(tmpdir, h2o_njoy, graphite): + filename = str(tmpdir.join('water.h5')) + h2o_njoy.export_to_hdf5(filename) + assert os.path.exists(filename) + + filename = str(tmpdir.join('graphite.h5')) + graphite.export_to_hdf5(filename) + assert os.path.exists(filename) + + +def test_continuous_dist(h2o_njoy): + for temperature, dist in h2o_njoy.inelastic_dist.items(): + assert temperature.endswith('K') + assert isinstance(dist, openmc.data.CorrelatedAngleEnergy) + + +def test_get_thermal_name(): + f = openmc.data.get_thermal_name + # Names which are recognized + assert f('lwtr') == 'c_H_in_H2O' + assert f('hh2o') == 'c_H_in_H2O' + + with pytest.warns(UserWarning, match='is not recognized'): + # Names which can be guessed + assert f('lw00') == 'c_H_in_H2O' + assert f('graphite') == 'c_Graphite' + + # Names that don't remotely match anything + assert f('boogie_monster') == 'c_boogie_monster' From 6996bbde5c9cd46642c4239f600d785727c32be4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 18 Dec 2017 17:06:25 +0700 Subject: [PATCH 097/282] Improve coverage of openmc.data (decay, neutron, miscellaneous) --- openmc/data/grid.py | 3 + tests/unit_tests/test_data_decay.py | 82 +++++++++++++++++++++++++ tests/unit_tests/test_data_misc.py | 50 +++++++++++++++ tests/unit_tests/test_data_neutron.py | 88 +++++++++++++++++++++++++++ 4 files changed, 223 insertions(+) create mode 100644 tests/unit_tests/test_data_decay.py create mode 100644 tests/unit_tests/test_data_misc.py diff --git a/openmc/data/grid.py b/openmc/data/grid.py index f08bac999..e63919ac2 100644 --- a/openmc/data/grid.py +++ b/openmc/data/grid.py @@ -21,6 +21,9 @@ def linearize(x, f, tolerance=0.001): Tabulated values of the dependent variable """ + # Make sure x is a numpy array + x = np.asarray(x) + # Initialize output arrays x_out = [] y_out = [] diff --git a/tests/unit_tests/test_data_decay.py b/tests/unit_tests/test_data_decay.py new file mode 100644 index 000000000..be8ad7d64 --- /dev/null +++ b/tests/unit_tests/test_data_decay.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +from collections import Mapping +import os +from math import log + +import numpy as np +import pytest +from uncertainties import ufloat +import openmc.data + + +_ENDF_DATA = os.environ['OPENMC_ENDF_DATA'] + + +def ufloat_close(a, b): + assert a.nominal_value == pytest.approx(b.nominal_value) + assert a.std_dev == pytest.approx(b.std_dev) + + +@pytest.fixture(scope='module') +def nb90(): + """Nb90 decay data.""" + filename = os.path.join(_ENDF_DATA, 'decay', 'dec-041_Nb_090.endf') + return openmc.data.Decay.from_endf(filename) + + +@pytest.fixture(scope='module') +def u235_yields(): + """U235 fission product yield data.""" + filename = os.path.join(_ENDF_DATA, 'nfy', 'nfy-092_U_235.endf') + return openmc.data.FissionProductYields.from_endf(filename) + + +def test_nb90_halflife(nb90): + ufloat_close(nb90.half_life, ufloat(52560.0, 180.0)) + ufloat_close(nb90.decay_constant, log(2.)/nb90.half_life) + + +def test_nb90_nuclide(nb90): + assert nb90.nuclide['atomic_number'] == 41 + assert nb90.nuclide['mass_number'] == 90 + assert nb90.nuclide['isomeric_state'] == 0 + assert nb90.nuclide['parity'] == 1.0 + assert nb90.nuclide['spin'] == 8.0 + assert not nb90.nuclide['stable'] + assert nb90.nuclide['mass'] == pytest.approx(89.13888) + + +def test_nb90_modes(nb90): + assert len(nb90.modes) == 2 + ec = nb90.modes[0] + assert ec.modes == ['ec/beta+'] + assert ec.parent == 'Nb90' + assert ec.daughter == 'Zr90' + assert 'Nb90 -> Zr90' in str(ec) + ufloat_close(ec.branching_ratio, ufloat(0.0147633, 0.0003386195)) + ufloat_close(ec.energy, ufloat(6111000., 4000.)) + + # Make sure branching ratios sum to 1 + total = sum(m.branching_ratio for m in nb90.modes) + assert total.nominal_value == pytest.approx(1.0) + + +def test_nb90_spectra(nb90): + assert sorted(nb90.spectra.keys()) == ['e-', 'ec/beta+', 'gamma', 'xray'] + + +def test_fpy(u235_yields): + assert u235_yields.nuclide['atomic_number'] == 92 + assert u235_yields.nuclide['mass_number'] == 235 + assert u235_yields.nuclide['isomeric_state'] == 0 + assert u235_yields.nuclide['name'] == 'U235' + assert u235_yields.energies == pytest.approx([0.0253, 500.e3, 1.4e7]) + + assert len(u235_yields.cumulative) == 3 + thermal = u235_yields.cumulative[0] + ufloat_close(thermal['I135'], ufloat(0.0628187, 0.000879461)) + + assert len(u235_yields.independent) == 3 + thermal = u235_yields.independent[0] + ufloat_close(thermal['I135'], ufloat(0.0292737, 0.000819663)) diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py new file mode 100644 index 000000000..aeeb04c0f --- /dev/null +++ b/tests/unit_tests/test_data_misc.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +from collections import Mapping +import os + +import numpy as np +import pytest +import openmc.data + + +def test_data_library(tmpdir): + lib = openmc.data.DataLibrary.from_xml() + for f in lib.libraries: + assert sorted(f.keys()) == ['materials', 'path', 'type'] + + f = lib.get_by_material('U235') + assert f['type'] == 'neutron' + assert 'U235' in f['materials'] + + f = lib.get_by_material('c_H_in_H2O') + assert f['type'] == 'thermal' + assert 'c_H_in_H2O' in f['materials'] + + filename = str(tmpdir.join('test.xml')) + lib.export_to_xml(filename) + assert os.path.exists(filename) + + new_lib = openmc.data.DataLibrary() + directory = os.path.dirname(os.environ['OPENMC_CROSS_SECTIONS']) + new_lib.register_file(os.path.join(directory, 'H1.h5')) + assert new_lib.libraries[-1]['type'] == 'neutron' + new_lib.register_file(os.path.join(directory, 'c_Zr_in_ZrH.h5')) + assert new_lib.libraries[-1]['type'] == 'thermal' + + +def test_linearize(): + """Test linearization of a continuous function.""" + x, y = openmc.data.linearize([-1., 1.], lambda x: 1 - x*x) + f = openmc.data.Tabulated1D(x, y) + assert f(-0.5) == pytest.approx(1 - 0.5*0.5, 0.001) + assert f(0.32) == pytest.approx(1 - 0.32*0.32, 0.001) + + +def test_thin(): + """Test thinning of a tabulated function.""" + x = np.linspace(0., 2*np.pi, 1000) + y = np.sin(x) + x_thin, y_thin = openmc.data.thin(x, y) + f = openmc.data.Tabulated1D(x_thin, y_thin) + assert f(1.0) == pytest.approx(np.sin(1.0), 0.001) diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index 4809c5638..502442f5b 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -77,6 +77,13 @@ def na22(): return openmc.data.IncidentNeutron.from_endf(filename) +@pytest.fixture(scope='module') +def be9(): + """Be9 ENDF data (contains laboratory angle-energy distribution).""" + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-004_Be_009.endf') + return openmc.data.IncidentNeutron.from_endf(filename) + + @pytest.fixture(scope='module') def h2(): endf_file = os.path.join(_ENDF_DATA, 'neutrons', 'n-001_H_002.endf') @@ -84,6 +91,12 @@ def h2(): endf_file, temperatures=_TEMPERATURES) +@pytest.fixture(scope='module') +def am244(): + endf_file = os.path.join(_ENDF_DATA, 'neutrons', 'n-095_Am_244.endf') + return openmc.data.IncidentNeutron.from_njoy(endf_file) + + def test_attributes(pu239): assert pu239.name == 'Pu239' assert pu239.mass_number == 239 @@ -102,6 +115,15 @@ def test_fission_energy(pu239): assert isinstance(getattr(fer, c), Callable) +def test_compact_fission_energy(tmpdir): + files = [os.path.join(_ENDF_DATA, 'neutrons', 'n-090_Th_232.endf'), + os.path.join(_ENDF_DATA, 'neutrons', 'n-094_Pu_240.endf'), + os.path.join(_ENDF_DATA, 'neutrons', 'n-094_Pu_241.endf')] + output = str(tmpdir.join('compact_lib.h5')) + openmc.data.write_compact_458_library(files, output) + assert os.path.exists(output) + + def test_energy_grid(pu239): assert isinstance(pu239.energy, Mapping) for temp, grid in pu239.energy.items(): @@ -155,6 +177,13 @@ def test_fission(pu239): assert photon.particle == 'photon' +def test_derived_products(am244): + fission = am244.reactions[18] + total_neutron = fission.derived_products[0] + assert total_neutron.emission_mode == 'total' + assert total_neutron.yield_(6e6) == pytest.approx(4.2558) + + def test_urr(pu239): for T, ptable in pu239.urr.items(): assert T.endswith('K') @@ -275,3 +304,62 @@ def test_evaporation(na22): n2n = na22.reactions[16] dist = n2n.products[0].distribution[0].energy assert isinstance(dist, openmc.data.Evaporation) + + +def test_laboratory(be9): + n2n = be9.reactions[16] + dist = n2n.products[0].distribution[0] + assert isinstance(dist, openmc.data.LaboratoryAngleEnergy) + assert list(dist.breakpoints) == [18] + assert list(dist.interpolation) == [2] + assert dist.energy[0] == pytest.approx(1748830.) + assert dist.energy[-1] == pytest.approx(20.e6) + assert len(dist.energy) == len(dist.energy_out) == len(dist.mu) + for eout, mu in zip(dist.energy_out, dist.mu): + assert len(eout) == len(mu) + assert np.all((-1. <= mu.x) & (mu.x <= 1.)) + + +def test_correlated(tmpdir): + endf_file = os.path.join(_ENDF_DATA, 'neutrons', 'n-014_Si_030.endf') + si30 = openmc.data.IncidentNeutron.from_njoy(endf_file, heatr=False) + + # Convert to HDF5 and read back + filename = str(tmpdir.join('si30.h5')) + si30.export_to_hdf5(filename) + si30_copy = openmc.data.IncidentNeutron.from_hdf5(filename) + + +def test_nbody(tmpdir, h2): + # Convert to HDF5 and read back + filename = str(tmpdir.join('h2.h5')) + h2.export_to_hdf5(filename) + h2_copy = openmc.data.IncidentNeutron.from_hdf5(filename) + + # Compare distributions + nbody1 = h2[16].products[0].distribution[0] + nbody2 = h2_copy[16].products[0].distribution[0] + assert nbody1.total_mass == nbody2.total_mass + assert nbody1.n_particles == nbody2.n_particles + assert nbody1.q_value == nbody2.q_value + + +def test_ace_convert(tmpdir): + filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-001_H_001.endf') + ace_ascii = str(tmpdir.join('ace_ascii')) + ace_binary = str(tmpdir.join('ace_binary')) + retcode = openmc.data.njoy.make_ace(filename, ace=ace_ascii) + assert retcode == 0 + + # Convert to binary + openmc.data.ace.ascii_to_binary(ace_ascii, ace_binary) + + # Make sure conversion worked + lib_ascii = openmc.data.ace.Library(ace_ascii) + lib_binary = openmc.data.ace.Library(ace_binary) + for tab_a, tab_b in zip(lib_ascii.tables, lib_binary.tables): + assert tab_a.name == tab_b.name + assert tab_a.atomic_weight_ratio == pytest.approx(tab_b.atomic_weight_ratio) + assert tab_a.temperature == pytest.approx(tab_b.temperature) + assert np.all(tab_a.nxs == tab_b.nxs) + assert np.all(tab_a.jxs == tab_b.jxs) From 530f33907b82fc8c8f1373146e27fd832cc41501 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 18 Dec 2017 20:20:22 +0700 Subject: [PATCH 098/282] Raise CalledProcessError when NJOY fails. Py2 fix in laboratory.py --- openmc/data/laboratory.py | 2 +- openmc/data/njoy.py | 86 +++++++++++++++++++-------------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/openmc/data/laboratory.py b/openmc/data/laboratory.py index 0a8908362..3c240b88d 100644 --- a/openmc/data/laboratory.py +++ b/openmc/data/laboratory.py @@ -44,7 +44,7 @@ class LaboratoryAngleEnergy(AngleEnergy): """ def __init__(self, breakpoints, interpolation, energy, mu, energy_out): - super(LaboratoryAngleEnergy).__init__() + super(LaboratoryAngleEnergy, self).__init__() self.breakpoints = breakpoints self.interpolation = interpolation self.energy = energy diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index ac98af7fb..b08b3bdfb 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -4,7 +4,7 @@ from collections import namedtuple from io import StringIO import os import shutil -from subprocess import Popen, PIPE, STDOUT +from subprocess import Popen, PIPE, STDOUT, CalledProcessError import sys import tempfile @@ -139,10 +139,10 @@ def run(commands, tapein, tapeout, input_filename=None, stdout=False, njoy_exec : str, optional Path to NJOY executable - Returns - ------- - int - Return code of NJOY process + Raises + ------ + subprocess.CalledProcessError + If the NJOY process returns with a non-zero status """ @@ -165,16 +165,23 @@ def run(commands, tapein, tapeout, input_filename=None, stdout=False, njoy.stdin.write(commands) njoy.stdin.flush() + lines = [] while True: # If process is finished, break loop line = njoy.stdout.readline() if not line and njoy.poll() is not None: break + lines.append(line) if stdout: # If user requested output, print to screen print(line, end='') + # Check for error + if njoy.returncode != 0: + raise CalledProcessError(njoy.returncode, njoy_exec, + ''.join(lines)) + # Copy output files back to original directory for tape_num, filename in tapeout.items(): tmpfilename = os.path.join(tmpdir, 'tape{}'.format(tape_num)) @@ -183,8 +190,6 @@ def run(commands, tapein, tapeout, input_filename=None, stdout=False, finally: shutil.rmtree(tmpdir) - return njoy.returncode - def make_pendf(filename, pendf='pendf', error=0.001, stdout=False): """Generate ACE file from an ENDF file @@ -200,15 +205,15 @@ def make_pendf(filename, pendf='pendf', error=0.001, stdout=False): stdout : bool Whether to display NJOY standard output - Returns - ------- - int - Return code of NJOY process + Raises + ------ + subprocess.CalledProcessError + If the NJOY process returns with a non-zero status """ - return make_ace(filename, pendf=pendf, error=error, broadr=False, - heatr=False, purr=False, acer=False, stdout=stdout) + make_ace(filename, pendf=pendf, error=error, broadr=False, + heatr=False, purr=False, acer=False, stdout=stdout) def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, @@ -238,14 +243,14 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, purr : bool, optional Indicating whether to add probability table when running NJOY acer : bool, optional - Indicating whether to generate ACE file when running NJOY + Indicating whether to generate ACE file when running NJOY **kwargs Keyword arguments passed to :func:`openmc.data.njoy.run` - Returns - ------- - int - Return code of NJOY process + Raises + ------ + subprocess.CalledProcessError + If the NJOY process returns with a non-zero status """ ev = endf.Evaluation(filename) @@ -285,7 +290,7 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, nheatr = nheatr_in + 1 commands += _TEMPLATE_HEATR nlast = nheatr - + # purr if purr: npurr_in = nlast @@ -310,9 +315,9 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, tapeout[nace] = fname.format(ace, temperature) tapeout[ndir] = fname.format(xsdir, temperature) commands += 'stop\n' - retcode = run(commands, tapein, tapeout, **kwargs) + run(commands, tapein, tapeout, **kwargs) - if acer and retcode == 0: + if acer: with open(ace, 'w') as ace_file, open(xsdir, 'w') as xsdir_file: for temperature in temperatures: # Get contents of ACE file @@ -337,10 +342,8 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, os.remove(fname.format(ace, temperature)) os.remove(fname.format(xsdir, temperature)) - return retcode - -def make_ace_thermal(filename, filename_thermal, temperatures=None, +def make_ace_thermal(filename, filename_thermal, temperatures=None, ace='ace', xsdir='xsdir', error=0.001, **kwargs): """Generate thermal scattering ACE file from ENDF files @@ -362,10 +365,10 @@ def make_ace_thermal(filename, filename_thermal, temperatures=None, **kwargs Keyword arguments passed to :func:`openmc.data.njoy.run` - Returns - ------- - int - Return code of NJOY process + Raises + ------ + subprocess.CalledProcessError + If the NJOY process returns with a non-zero status """ ev = endf.Evaluation(filename) @@ -461,21 +464,18 @@ def make_ace_thermal(filename, filename_thermal, temperatures=None, tapeout[nace] = fname.format(ace, temperature) tapeout[ndir] = fname.format(xsdir, temperature) commands += 'stop\n' - retcode = run(commands, tapein, tapeout, **kwargs) + run(commands, tapein, tapeout, **kwargs) - if retcode == 0: - with open(ace, 'w') as ace_file, open(xsdir, 'w') as xsdir_file: - # Concatenate ACE and xsdir files together - for temperature in temperatures: - text = open(fname.format(ace, temperature), 'r').read() - ace_file.write(text) - - text = open(fname.format(xsdir, temperature), 'r').read() - xsdir_file.write(text) - - # Remove ACE/xsdir files for each temperature + with open(ace, 'w') as ace_file, open(xsdir, 'w') as xsdir_file: + # Concatenate ACE and xsdir files together for temperature in temperatures: - os.remove(fname.format(ace, temperature)) - os.remove(fname.format(xsdir, temperature)) + text = open(fname.format(ace, temperature), 'r').read() + ace_file.write(text) - return retcode + text = open(fname.format(xsdir, temperature), 'r').read() + xsdir_file.write(text) + + # Remove ACE/xsdir files for each temperature + for temperature in temperatures: + os.remove(fname.format(ace, temperature)) + os.remove(fname.format(xsdir, temperature)) From 39f3ea9327ca080cc5e3e80f801adb868a71369f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 18 Dec 2017 20:58:31 +0700 Subject: [PATCH 099/282] Build NJOY without shared library --- tools/ci/travis-install-njoy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/travis-install-njoy.sh b/tools/ci/travis-install-njoy.sh index a338cbf4b..788cfc7a7 100755 --- a/tools/ci/travis-install-njoy.sh +++ b/tools/ci/travis-install-njoy.sh @@ -5,4 +5,4 @@ git clone https://github.com/njoy/NJOY2016 cd NJOY2016 sed -i -e 's/5\.1/4.8/' CMakeLists.txt mkdir build && cd build -cmake .. && make 2>/dev/null && sudo make install +cmake -Dstatic=on .. && make 2>/dev/null && sudo make install From 9f7e5e85e777f942a48645403c94067d4a2eb6ea Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 19 Dec 2017 14:08:26 +0700 Subject: [PATCH 100/282] Improve guessing in openmc.data.get_thermal_name --- openmc/data/thermal.py | 94 +++++++++++++++------------ tests/unit_tests/test_data_neutron.py | 3 +- tests/unit_tests/test_data_thermal.py | 1 + 3 files changed, 55 insertions(+), 43 deletions(-) diff --git a/openmc/data/thermal.py b/openmc/data/thermal.py index fefb71518..cc549c80e 100644 --- a/openmc/data/thermal.py +++ b/openmc/data/thermal.py @@ -23,40 +23,40 @@ 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_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_Graphite': ('graph', 'grph', 'gr'), - 'c_H_in_CaH2': ('hcah2',), - 'c_H_in_CH2': ('hch2', 'poly', 'pol'), - '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_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_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_Al27': ['al', 'al27'], + 'c_Be': ['be', 'be-metal'], + 'c_BeO': ['beo'], + 'c_Be_in_BeO': ['bebeo', '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_Graphite': ['graph', 'grph', 'gr'], + 'c_H_in_CaH2': ['hcah2'], + 'c_H_in_CH2': ['hch2', 'poly', 'pol'], + '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_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_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'] } @@ -84,13 +84,25 @@ def get_thermal_name(name): # Make an educated guess?? This actually works well for # JEFF-3.2 which stupidly uses names like lw00.32t, # lw01.32t, etc. for different temperatures - for proper_name, names in _THERMAL_NAMES.items(): - matches = get_close_matches( - name.lower(), names, cutoff=0.5) - if len(matches) > 0: - warn('Thermal scattering material "{}" is not recognized. ' - 'Assigning a name of {}.'.format(name, proper_name)) - return proper_name + + # First, construct a list of all the values/keys in the names + # dictionary + all_names = sum(_THERMAL_NAMES.values(), []) + all_names += _THERMAL_NAMES.keys() + + matches = get_close_matches(name, all_names, cutoff=0.5) + if len(matches) > 0: + # Figure out the key for the corresponding match + match = matches[0] + if match not in _THERMAL_NAMES: + for key, value_list in _THERMAL_NAMES.items(): + if match in value_list: + match = key + break + + warn('Thermal scattering material "{}" is not recognized. ' + 'Assigning a name of {}.'.format(name, match)) + return match else: # OK, we give up. Just use the ACE name. warn('Thermal scattering material "{0}" is not recognized. ' diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index 502442f5b..e314d32a4 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -348,8 +348,7 @@ def test_ace_convert(tmpdir): filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-001_H_001.endf') ace_ascii = str(tmpdir.join('ace_ascii')) ace_binary = str(tmpdir.join('ace_binary')) - retcode = openmc.data.njoy.make_ace(filename, ace=ace_ascii) - assert retcode == 0 + openmc.data.njoy.make_ace(filename, ace=ace_ascii) # Convert to binary openmc.data.ace.ascii_to_binary(ace_ascii, ace_binary) diff --git a/tests/unit_tests/test_data_thermal.py b/tests/unit_tests/test_data_thermal.py index 4af815c88..9def78b38 100644 --- a/tests/unit_tests/test_data_thermal.py +++ b/tests/unit_tests/test_data_thermal.py @@ -95,6 +95,7 @@ def test_get_thermal_name(): # Names which can be guessed assert f('lw00') == 'c_H_in_H2O' assert f('graphite') == 'c_Graphite' + assert f('D_in_D2O') == 'c_D_in_D2O' # Names that don't remotely match anything assert f('boogie_monster') == 'c_boogie_monster' From 4d3da2e5b1420e352523f473a6003b9dec37a7e9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 11 Jan 2018 06:22:07 -0600 Subject: [PATCH 101/282] Use itertools.chain in get_thermal_name --- openmc/data/thermal.py | 73 +++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/openmc/data/thermal.py b/openmc/data/thermal.py index cc549c80e..68192de03 100644 --- a/openmc/data/thermal.py +++ b/openmc/data/thermal.py @@ -1,6 +1,7 @@ from collections import Iterable from difflib import get_close_matches from numbers import Real +import itertools import os import re import shutil @@ -23,40 +24,40 @@ 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_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_Graphite': ['graph', 'grph', 'gr'], - 'c_H_in_CaH2': ['hcah2'], - 'c_H_in_CH2': ['hch2', 'poly', 'pol'], - '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_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_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_Al27': ('al', 'al27'), + 'c_Be': ('be', 'be-metal'), + 'c_BeO': ('beo'), + 'c_Be_in_BeO': ('bebeo', '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_Graphite': ('graph', 'grph', 'gr'), + 'c_H_in_CaH2': ('hcah2'), + 'c_H_in_CH2': ('hch2', 'poly', 'pol'), + '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_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_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') } @@ -87,8 +88,8 @@ def get_thermal_name(name): # First, construct a list of all the values/keys in the names # dictionary - all_names = sum(_THERMAL_NAMES.values(), []) - all_names += _THERMAL_NAMES.keys() + all_names = itertools.chain(_THERMAL_NAMES.keys(), + *_THERMAL_NAMES.values()) matches = get_close_matches(name, all_names, cutoff=0.5) if len(matches) > 0: From d5eef5d9f0d1c422eaa4759e4b3bba4227a7611d Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 11 Jan 2018 22:12:21 -0500 Subject: [PATCH 102/282] Cleanup naming and a divide-by-zero for multipole --- openmc/data/multipole.py | 72 ++++++++++---------- src/cross_section.F90 | 142 +++++++++++++++++++-------------------- src/tallies/tally.F90 | 102 ++++++++++++++-------------- 3 files changed, 158 insertions(+), 158 deletions(-) diff --git a/openmc/data/multipole.py b/openmc/data/multipole.py index 653ff0825..4e77e16ea 100644 --- a/openmc/data/multipole.py +++ b/openmc/data/multipole.py @@ -404,7 +404,7 @@ class WindowedMultipole(EqualityMixin): cv.check_type('curvefit', curvefit, np.ndarray) if len(curvefit.shape) != 3: raise ValueError('Multipole curvefit arrays must be 3D') - if curvefit.shape[2] not in (2, 3): # sigT, sigA (and maybe sigF) + if curvefit.shape[2] not in (2, 3): # sig_t, sig_a (maybe sig_f) raise ValueError('The third dimension of multipole curvefit' ' arrays must have a length of 2 or 3') if not np.issubdtype(curvefit.dtype, float): @@ -531,8 +531,6 @@ class WindowedMultipole(EqualityMixin): sqrtkT = sqrt(K_BOLTZMANN * T) sqrtE = sqrt(E) invE = 1.0 / E - if sqrtkT > 0.0: - dopp = self.sqrtAWR / sqrtkT # Locate us. The i_window calc omits a + 1 present in F90 because of # the 1-based vs. 0-based indexing. Similarly startw needs to be @@ -547,7 +545,7 @@ class WindowedMultipole(EqualityMixin): # not appear in the absorption and fission equations. if startw <= endw: twophi = np.zeros(self.num_l, dtype=np.float) - sigT_factor = np.zeros(self.num_l, dtype=np.cfloat) + sig_t_factor = np.zeros(self.num_l, dtype=np.cfloat) for iL in range(self.num_l): twophi[iL] = self.pseudo_k0RS[iL] * sqrtE @@ -562,35 +560,36 @@ class WindowedMultipole(EqualityMixin): twophi[iL] = twophi[iL] - np.arctan(arg) twophi = 2.0 * twophi - sigT_factor = np.cos(twophi) - 1j*np.sin(twophi) + sig_t_factor = np.cos(twophi) - 1j*np.sin(twophi) # Initialize the ouptut cross sections. - sigT = 0.0 - sigA = 0.0 - sigF = 0.0 + sig_t = 0.0 + sig_a = 0.0 + sig_f = 0.0 # ====================================================================== # Add the contribution from the curvefit polynomial. if sqrtkT != 0 and self.broaden_poly[i_window]: # Broaden the curvefit. + dopp = self.sqrtAWR / sqrtkT broadened_polynomials = _broaden_wmp_polynomials(E, dopp, self.fit_order + 1) for i_poly in range(self.fit_order+1): - sigT += (self.curvefit[i_window, i_poly, _FIT_T] - * broadened_polynomials[i_poly]) - sigA += (self.curvefit[i_window, i_poly, _FIT_A] - * broadened_polynomials[i_poly]) + sig_t += (self.curvefit[i_window, i_poly, _FIT_T] + * broadened_polynomials[i_poly]) + sig_a += (self.curvefit[i_window, i_poly, _FIT_A] + * broadened_polynomials[i_poly]) if self.fissionable: - sigF += (self.curvefit[i_window, i_poly, _FIT_F] - * broadened_polynomials[i_poly]) + sig_f += (self.curvefit[i_window, i_poly, _FIT_F] + * broadened_polynomials[i_poly]) else: temp = invE for i_poly in range(self.fit_order+1): - sigT += self.curvefit[i_window, i_poly, _FIT_T] * temp - sigA += self.curvefit[i_window, i_poly, _FIT_A] * temp + sig_t += self.curvefit[i_window, i_poly, _FIT_T] * temp + sig_a += self.curvefit[i_window, i_poly, _FIT_A] * temp if self.fissionable: - sigF += self.curvefit[i_window, i_poly, _FIT_F] * temp + sig_f += self.curvefit[i_window, i_poly, _FIT_F] * temp temp *= sqrtE # ====================================================================== @@ -602,45 +601,46 @@ class WindowedMultipole(EqualityMixin): psi_chi = -1j / (self.data[i_pole, _MP_EA] - sqrtE) c_temp = psi_chi / E if self.formalism == 'MLBW': - sigT += ((self.data[i_pole, _MLBW_RT] * c_temp * - sigT_factor[self.l_value[i_pole]-1]).real - + (self.data[i_pole, _MLBW_RX] * c_temp).real) - sigA += (self.data[i_pole, _MLBW_RA] * c_temp).real + sig_t += ((self.data[i_pole, _MLBW_RT] * c_temp * + sig_t_factor[self.l_value[i_pole]-1]).real + + (self.data[i_pole, _MLBW_RX] * c_temp).real) + sig_a += (self.data[i_pole, _MLBW_RA] * c_temp).real if self.fissionable: - sigF += (self.data[i_pole, _MLBW_RF] * c_temp).real + sig_f += (self.data[i_pole, _MLBW_RF] * c_temp).real elif self.formalism == 'RM': - sigT += (self.data[i_pole, _RM_RT] * c_temp * - sigT_factor[self.l_value[i_pole]-1]).real - sigA += (self.data[i_pole, _RM_RA] * c_temp).real + sig_t += (self.data[i_pole, _RM_RT] * c_temp * + sig_t_factor[self.l_value[i_pole]-1]).real + sig_a += (self.data[i_pole, _RM_RA] * c_temp).real if self.fissionable: - sigF += (self.data[i_pole, _RM_RF] * c_temp).real + sig_f += (self.data[i_pole, _RM_RF] * c_temp).real else: raise ValueError('Unrecognized/Unsupported R-matrix' ' formalism') else: # At temperature, use Faddeeva function-based form. + dopp = self.sqrtAWR / sqrtkT for i_pole in range(startw, endw): Z = (sqrtE - self.data[i_pole, _MP_EA]) * dopp w_val = _faddeeva(Z) * dopp * invE * sqrt(pi) if self.formalism == 'MLBW': - sigT += ((self.data[i_pole, _MLBW_RT] * - sigT_factor[self.l_value[i_pole]-1] + - self.data[i_pole, _MLBW_RX]) * w_val).real - sigA += (self.data[i_pole, _MLBW_RA] * w_val).real + sig_t += ((self.data[i_pole, _MLBW_RT] * + sig_t_factor[self.l_value[i_pole]-1] + + self.data[i_pole, _MLBW_RX]) * w_val).real + sig_a += (self.data[i_pole, _MLBW_RA] * w_val).real if self.fissionable: - sigF += (self.data[i_pole, _MLBW_RF] * w_val).real + sig_f += (self.data[i_pole, _MLBW_RF] * w_val).real elif self.formalism == 'RM': - sigT += (self.data[i_pole, _RM_RT] * w_val * - sigT_factor[self.l_value[i_pole]-1]).real - sigA += (self.data[i_pole, _RM_RA] * w_val).real + sig_t += (self.data[i_pole, _RM_RT] * w_val * + sig_t_factor[self.l_value[i_pole]-1]).real + sig_a += (self.data[i_pole, _RM_RA] * w_val).real if self.fissionable: - sigF += (self.data[i_pole, _RM_RF] * w_val).real + sig_f += (self.data[i_pole, _RM_RF] * w_val).real else: raise ValueError('Unrecognized/Unsupported R-matrix' ' formalism') - return sigT, sigA, sigF + return sig_t, sig_a, sig_f def __call__(self, E, T): """Compute total, absorption, and fission cross sections. diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 2b3b32a9c..69e577966 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -157,10 +157,9 @@ contains integer :: i_high ! upper logarithmic mapping index integer :: i_rxn ! reaction index integer :: j ! index in DEPLETION_RX - real(8) :: val ! temporary xs value real(8) :: f ! interp factor on nuclide energy grid real(8) :: kT ! temperature in eV - real(8) :: sigT, sigA, sigF ! Intermediate multipole variables + real(8) :: sig_t, sig_a, sig_f ! Intermediate multipole variables ! Initialize cached cross sections to zero micro_xs(i_nuclide) % thermal = ZERO @@ -179,15 +178,15 @@ contains ! Evaluate multipole or interpolate if (use_mp) then ! Call multipole kernel - call multipole_eval(nuc % multipole, E, sqrtkT, sigT, sigA, sigF) + call multipole_eval(nuc % multipole, E, sqrtkT, sig_t, sig_a, sig_f) - micro_xs(i_nuclide) % total = sigT - micro_xs(i_nuclide) % absorption = sigA - micro_xs(i_nuclide) % elastic = sigT - sigA + micro_xs(i_nuclide) % total = sig_t + micro_xs(i_nuclide) % absorption = sig_a + micro_xs(i_nuclide) % elastic = sig_t - sig_a if (nuc % fissionable) then - micro_xs(i_nuclide) % fission = sigF - micro_xs(i_nuclide) % nu_fission = sigF * nuc % nu(E, EMISSION_TOTAL) + micro_xs(i_nuclide) % fission = sig_f + micro_xs(i_nuclide) % nu_fission = sig_f * nuc % nu(E, EMISSION_TOTAL) else micro_xs(i_nuclide) % fission = ZERO micro_xs(i_nuclide) % nu_fission = ZERO @@ -198,7 +197,7 @@ contains micro_xs(i_nuclide) % reaction(:) = ZERO ! Only non-zero reaction is (n,gamma) - micro_xs(i_nuclide) % reaction(4) = sigA - sigF + micro_xs(i_nuclide) % reaction(4) = sig_a - sig_f end if ! Ensure these values are set @@ -613,7 +612,7 @@ contains ! sections in the resolved resonance regions !=============================================================================== - subroutine multipole_eval(multipole, E, sqrtkT, sigT, sigA, sigF) + subroutine multipole_eval(multipole, E, sqrtkT, sig_t, sig_a, sig_f) type(MultipoleArray), intent(in) :: multipole ! The windowed multipole ! object to process. real(8), intent(in) :: E ! The energy at which to @@ -621,15 +620,15 @@ contains real(8), intent(in) :: sqrtkT ! The temperature in the form ! sqrt(kT), at which ! to evaluate the XS. - real(8), intent(out) :: sigT ! Total cross section - real(8), intent(out) :: sigA ! Absorption cross section - real(8), intent(out) :: sigF ! Fission cross section + real(8), intent(out) :: sig_t ! Total cross section + real(8), intent(out) :: sig_a ! Absorption cross section + real(8), intent(out) :: sig_f ! Fission cross section complex(8) :: psi_chi ! The value of the psi-chi function for the ! asymptotic form complex(8) :: c_temp ! complex temporary variable complex(8) :: w_val ! The faddeeva function evaluated at Z complex(8) :: Z ! sqrt(atomic weight ratio / kT) * (sqrt(E) - pole) - complex(8) :: sigT_factor(multipole % num_l) + complex(8) :: sig_t_factor(multipole % num_l) real(8) :: broadened_polynomials(multipole % fit_order + 1) real(8) :: sqrtE ! sqrt(E), eV real(8) :: invE ! 1/E, eV @@ -647,7 +646,6 @@ contains ! Define some frequently used variables. sqrtE = sqrt(E) invE = ONE / E - dopp = multipole % sqrtAWR / sqrtkT ! Locate us. i_window = floor((sqrtE - sqrt(multipole % start_E)) / multipole % spacing & @@ -657,38 +655,39 @@ contains ! Fill in factors. if (startw <= endw) then - call compute_sigT_factor(multipole, sqrtE, sigT_factor) + call compute_sig_t_factor(multipole, sqrtE, sig_t_factor) end if ! Initialize the ouptut cross sections. - sigT = ZERO - sigA = ZERO - sigF = ZERO + sig_t = ZERO + sig_a = ZERO + sig_f = ZERO ! ========================================================================== ! Add the contribution from the curvefit polynomial. if (sqrtkT /= ZERO .and. multipole % broaden_poly(i_window) == 1) then ! Broaden the curvefit. + dopp = multipole % sqrtAWR / sqrtkT call broaden_wmp_polynomials(E, dopp, multipole % fit_order + 1, & broadened_polynomials) do i_poly = 1, multipole % fit_order+1 - sigT = sigT + multipole % curvefit(FIT_T, i_poly, i_window) & + sig_t = sig_t + multipole % curvefit(FIT_T, i_poly, i_window) & * broadened_polynomials(i_poly) - sigA = sigA + multipole % curvefit(FIT_A, i_poly, i_window) & + sig_a = sig_a + multipole % curvefit(FIT_A, i_poly, i_window) & * broadened_polynomials(i_poly) if (multipole % fissionable) then - sigF = sigF + multipole % curvefit(FIT_F, i_poly, i_window) & + sig_f = sig_f + multipole % curvefit(FIT_F, i_poly, i_window) & * broadened_polynomials(i_poly) end if end do else ! Evaluate as if it were a polynomial temp = invE do i_poly = 1, multipole % fit_order+1 - sigT = sigT + multipole % curvefit(FIT_T, i_poly, i_window) * temp - sigA = sigA + multipole % curvefit(FIT_A, i_poly, i_window) * temp + sig_t = sig_t + multipole % curvefit(FIT_T, i_poly, i_window) * temp + sig_a = sig_a + multipole % curvefit(FIT_A, i_poly, i_window) * temp if (multipole % fissionable) then - sigF = sigF + multipole % curvefit(FIT_F, i_poly, i_window) * temp + sig_f = sig_f + multipole % curvefit(FIT_F, i_poly, i_window) * temp end if temp = temp * sqrtE end do @@ -703,42 +702,43 @@ contains psi_chi = -ONEI / (multipole % data(MP_EA, i_pole) - sqrtE) c_temp = psi_chi / E if (multipole % formalism == FORM_MLBW) then - sigT = sigT + real(multipole % data(MLBW_RT, i_pole) * c_temp * & - sigT_factor(multipole % l_value(i_pole))) & - + real(multipole % data(MLBW_RX, i_pole) * c_temp) - sigA = sigA + real(multipole % data(MLBW_RA, i_pole) * c_temp) + sig_t = sig_t + real(multipole % data(MLBW_RT, i_pole) * c_temp * & + sig_t_factor(multipole % l_value(i_pole))) & + + real(multipole % data(MLBW_RX, i_pole) * c_temp) + sig_a = sig_a + real(multipole % data(MLBW_RA, i_pole) * c_temp) if (multipole % fissionable) then - sigF = sigF + real(multipole % data(MLBW_RF, i_pole) * c_temp) + sig_f = sig_f + real(multipole % data(MLBW_RF, i_pole) * c_temp) end if else if (multipole % formalism == FORM_RM) then - sigT = sigT + real(multipole % data(RM_RT, i_pole) * c_temp * & - sigT_factor(multipole % l_value(i_pole))) - sigA = sigA + real(multipole % data(RM_RA, i_pole) * c_temp) + sig_t = sig_t + real(multipole % data(RM_RT, i_pole) * c_temp * & + sig_t_factor(multipole % l_value(i_pole))) + sig_a = sig_a + real(multipole % data(RM_RA, i_pole) * c_temp) if (multipole % fissionable) then - sigF = sigF + real(multipole % data(RM_RF, i_pole) * c_temp) + sig_f = sig_f + real(multipole % data(RM_RF, i_pole) * c_temp) end if end if end do else ! At temperature, use Faddeeva function-based form. + dopp = multipole % sqrtAWR / sqrtkT if (endw >= startw) then do i_pole = startw, endw Z = (sqrtE - multipole % data(MP_EA, i_pole)) * dopp w_val = faddeeva(Z) * dopp * invE * SQRT_PI if (multipole % formalism == FORM_MLBW) then - sigT = sigT + real((multipole % data(MLBW_RT, i_pole) * & - sigT_factor(multipole % l_value(i_pole)) + & - multipole % data(MLBW_RX, i_pole)) * w_val) - sigA = sigA + real(multipole % data(MLBW_RA, i_pole) * w_val) + sig_t = sig_t + real((multipole % data(MLBW_RT, i_pole) * & + sig_t_factor(multipole % l_value(i_pole)) + & + multipole % data(MLBW_RX, i_pole)) * w_val) + sig_a = sig_a + real(multipole % data(MLBW_RA, i_pole) * w_val) if (multipole % fissionable) then - sigF = sigF + real(multipole % data(MLBW_RF, i_pole) * w_val) + sig_f = sig_f + real(multipole % data(MLBW_RF, i_pole) * w_val) end if else if (multipole % formalism == FORM_RM) then - sigT = sigT + real(multipole % data(RM_RT, i_pole) * w_val * & - sigT_factor(multipole % l_value(i_pole))) - sigA = sigA + real(multipole % data(RM_RA, i_pole) * w_val) + sig_t = sig_t + real(multipole % data(RM_RT, i_pole) * w_val * & + sig_t_factor(multipole % l_value(i_pole))) + sig_a = sig_a + real(multipole % data(RM_RA, i_pole) * w_val) if (multipole % fissionable) then - sigF = sigF + real(multipole % data(RM_RF, i_pole) * w_val) + sig_f = sig_f + real(multipole % data(RM_RF, i_pole) * w_val) end if end if end do @@ -752,7 +752,7 @@ contains ! temperature. !=============================================================================== - subroutine multipole_deriv_eval(multipole, E, sqrtkT, sigT, sigA, sigF) + subroutine multipole_deriv_eval(multipole, E, sqrtkT, sig_t, sig_a, sig_f) type(MultipoleArray), intent(in) :: multipole ! The windowed multipole ! object to process. real(8), intent(in) :: E ! The energy at which to @@ -760,12 +760,12 @@ contains real(8), intent(in) :: sqrtkT ! The temperature in the form ! sqrt(kT), at which to ! evaluate the XS. - real(8), intent(out) :: sigT ! Total cross section - real(8), intent(out) :: sigA ! Absorption cross section - real(8), intent(out) :: sigF ! Fission cross section + real(8), intent(out) :: sig_t ! Total cross section + real(8), intent(out) :: sig_a ! Absorption cross section + real(8), intent(out) :: sig_f ! Fission cross section complex(8) :: w_val ! The faddeeva function evaluated at Z complex(8) :: Z ! sqrt(atomic weight ratio / kT) * (sqrt(E) - pole) - complex(8) :: sigT_factor(multipole % num_l) + complex(8) :: sig_t_factor(multipole % num_l) real(8) :: sqrtE ! sqrt(E), eV real(8) :: invE ! 1/E, eV real(8) :: dopp ! sqrt(atomic weight ratio / kT) @@ -781,7 +781,6 @@ contains ! Define some frequently used variables. sqrtE = sqrt(E) invE = ONE / E - dopp = multipole % sqrtAWR / sqrtkT T = sqrtkT**2 / K_BOLTZMANN if (sqrtkT == ZERO) call fatal_error("Windowed multipole temperature & @@ -795,13 +794,13 @@ contains ! Fill in factors. if (startw <= endw) then - call compute_sigT_factor(multipole, sqrtE, sigT_factor) + call compute_sig_t_factor(multipole, sqrtE, sig_t_factor) end if ! Initialize the ouptut cross sections. - sigT = ZERO - sigA = ZERO - sigF = ZERO + sig_t = ZERO + sig_a = ZERO + sig_f = ZERO ! TODO Polynomials: Some of the curvefit polynomials Doppler broaden so ! rigorously we should be computing the derivative of those. But in @@ -811,42 +810,43 @@ contains ! ========================================================================== ! Add the contribution from the poles in this window. + dopp = multipole % sqrtAWR / sqrtkT if (endw >= startw) then do i_pole = startw, endw Z = (sqrtE - multipole % data(MP_EA, i_pole)) * dopp w_val = -invE * SQRT_PI * HALF * w_derivative(Z, 2) if (multipole % formalism == FORM_MLBW) then - sigT = sigT + real((multipole % data(MLBW_RT, i_pole) * & - sigT_factor(multipole%l_value(i_pole)) + & - multipole % data(MLBW_RX, i_pole)) * w_val) - sigA = sigA + real(multipole % data(MLBW_RA, i_pole) * w_val) + sig_t = sig_t + real((multipole % data(MLBW_RT, i_pole) * & + sig_t_factor(multipole%l_value(i_pole)) + & + multipole % data(MLBW_RX, i_pole)) * w_val) + sig_a = sig_a + real(multipole % data(MLBW_RA, i_pole) * w_val) if (multipole % fissionable) then - sigF = sigF + real(multipole % data(MLBW_RF, i_pole) * w_val) + sig_f = sig_f + real(multipole % data(MLBW_RF, i_pole) * w_val) end if else if (multipole % formalism == FORM_RM) then - sigT = sigT + real(multipole % data(RM_RT, i_pole) * w_val * & - sigT_factor(multipole % l_value(i_pole))) - sigA = sigA + real(multipole % data(RM_RA, i_pole) * w_val) + sig_t = sig_t + real(multipole % data(RM_RT, i_pole) * w_val * & + sig_t_factor(multipole % l_value(i_pole))) + sig_a = sig_a + real(multipole % data(RM_RA, i_pole) * w_val) if (multipole % fissionable) then - sigF = sigF + real(multipole % data(RM_RF, i_pole) * w_val) + sig_f = sig_f + real(multipole % data(RM_RF, i_pole) * w_val) end if end if end do - sigT = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sigT - sigA = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sigA - sigF = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sigF + sig_t = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sig_t + sig_a = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sig_a + sig_f = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sig_f end if end subroutine multipole_deriv_eval !=============================================================================== -! COMPUTE_SIGT_FACTOR calculates the sigT_factor, a factor inside of the sigT -! equation not present in the sigA and sigF equations. +! COMPUTE_SIG_T_FACTOR calculates the sig_t_factor, a factor inside of the sig_t +! equation not present in the sig_a and sig_f equations. !=============================================================================== - subroutine compute_sigT_factor(multipole, sqrtE, sigT_factor) + subroutine compute_sig_t_factor(multipole, sqrtE, sig_t_factor) type(MultipoleArray), intent(in) :: multipole real(8), intent(in) :: sqrtE - complex(8), intent(out) :: sigT_factor(multipole % num_l) + complex(8), intent(out) :: sig_t_factor(multipole % num_l) integer :: iL real(8) :: twophi(multipole % num_l) @@ -867,8 +867,8 @@ contains end do twophi = 2.0_8 * twophi - sigT_factor = cmplx(cos(twophi), -sin(twophi), KIND=8) - end subroutine compute_sigT_factor + sig_t_factor = cmplx(cos(twophi), -sin(twophi), KIND=8) + end subroutine compute_sig_t_factor !=============================================================================== ! 0K_ELASTIC_XS determines the microscopic 0K elastic cross section diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index a05cf3984..7684a36aa 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -2382,7 +2382,7 @@ contains type(Particle), intent(in) :: p - integer :: i, j, m + integer :: i, j integer :: i_tally integer :: i_filt integer :: i_bin @@ -3472,7 +3472,7 @@ contains integer :: l logical :: scoring_diff_nuclide real(8) :: flux_deriv - real(8) :: dsigT, dsigA, dsigF, cum_dsig + real(8) :: dsig_t, dsig_a, dsig_f, cum_dsig if (score == ZERO) return @@ -3713,17 +3713,17 @@ contains if (mat % nuclide(l) == p % event_nuclide) exit end do - dsigT = ZERO + dsig_t = ZERO associate (nuc => nuclides(p % event_nuclide)) if (nuc % mp_present .and. & p % last_E >= nuc % multipole % start_E .and. & p % last_E <= nuc % multipole % end_E) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) end if end associate score = score * (flux_deriv & - + dsigT * mat % atom_density(l) / material_xs % total) + + dsig_t * mat % atom_density(l) / material_xs % total) end associate else score = score * flux_deriv @@ -3739,17 +3739,17 @@ contains if (mat % nuclide(l) == p % event_nuclide) exit end do - dsigT = ZERO - dsigA = ZERO + dsig_t = ZERO + dsig_a = ZERO associate (nuc => nuclides(p % event_nuclide)) if (nuc % mp_present .and. & p % last_E >= nuc % multipole % start_E .and. & p % last_E <= nuc % multipole % end_E) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) end if end associate - score = score * (flux_deriv + (dsigT - dsigA) & + score = score * (flux_deriv + (dsig_t - dsig_a) & * mat % atom_density(l) / & (material_xs % total - material_xs % absorption)) end associate @@ -3766,17 +3766,17 @@ contains if (mat % nuclide(l) == p % event_nuclide) exit end do - dsigA = ZERO + dsig_a = ZERO associate (nuc => nuclides(p % event_nuclide)) if (nuc % mp_present .and. & p % last_E >= nuc % multipole % start_E .and. & p % last_E <= nuc % multipole % end_E) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) end if end associate - score = score * (flux_deriv & - + dsigA * mat % atom_density(l) / material_xs % absorption) + score = score * (flux_deriv + dsig_a * mat % atom_density(l) & + / material_xs % absorption) end associate else score = score * flux_deriv @@ -3791,17 +3791,17 @@ contains if (mat % nuclide(l) == p % event_nuclide) exit end do - dsigF = ZERO + dsig_f = ZERO associate (nuc => nuclides(p % event_nuclide)) if (nuc % mp_present .and. & p % last_E >= nuc % multipole % start_E .and. & p % last_E <= nuc % multipole % end_E) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) end if end associate score = score * (flux_deriv & - + dsigF * mat % atom_density(l) / material_xs % fission) + + dsig_f * mat % atom_density(l) / material_xs % fission) end associate else score = score * flux_deriv @@ -3816,17 +3816,17 @@ contains if (mat % nuclide(l) == p % event_nuclide) exit end do - dsigF = ZERO + dsig_f = ZERO associate (nuc => nuclides(p % event_nuclide)) if (nuc % mp_present .and. & p % last_E >= nuc % multipole % start_E .and. & p % last_E <= nuc % multipole % end_E) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) end if end associate score = score * (flux_deriv & - + dsigF * mat % atom_density(l) / material_xs % nu_fission& + + dsig_f * mat % atom_density(l) / material_xs % nu_fission& * micro_xs(p % event_nuclide) % nu_fission & / micro_xs(p % event_nuclide) % fission) end associate @@ -3859,8 +3859,8 @@ contains p % last_E <= nuc % multipole % end_E .and. & micro_xs(mat % nuclide(l)) % total > ZERO) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) - cum_dsig = cum_dsig + dsigT * mat % atom_density(l) + p % sqrtkT, dsig_t, dsig_a, dsig_f) + cum_dsig = cum_dsig + dsig_t * mat % atom_density(l) end if end associate end do @@ -3869,17 +3869,17 @@ contains + cum_dsig / material_xs % total) else if (materials(p % material) % id == deriv % diff_material & .and. material_xs % total > ZERO) then - dsigT = ZERO + dsig_t = ZERO associate (nuc => nuclides(i_nuclide)) if (nuc % mp_present .and. & p % last_E >= nuc % multipole % start_E .and. & p % last_E <= nuc % multipole % end_E) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) end if end associate score = score * (flux_deriv & - + dsigT / micro_xs(i_nuclide) % total) + + dsig_t / micro_xs(i_nuclide) % total) else score = score * flux_deriv end if @@ -3898,9 +3898,9 @@ contains (micro_xs(mat % nuclide(l)) % total & - micro_xs(mat % nuclide(l)) % absorption) > ZERO) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) cum_dsig = cum_dsig & - + (dsigT - dsigA) * mat % atom_density(l) + + (dsig_t - dsig_a) * mat % atom_density(l) end if end associate end do @@ -3910,17 +3910,17 @@ contains else if ( materials(p % material) % id == deriv % diff_material & .and. (material_xs % total - material_xs % absorption) > ZERO)& then - dsigT = ZERO - dsigA = ZERO + dsig_t = ZERO + dsig_a = ZERO associate (nuc => nuclides(i_nuclide)) if (nuc % mp_present .and. & p % last_E >= nuc % multipole % start_E .and. & p % last_E <= nuc % multipole % end_E) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) end if end associate - score = score * (flux_deriv + (dsigT - dsigA) & + score = score * (flux_deriv + (dsig_t - dsig_a) & / (micro_xs(i_nuclide) % total & - micro_xs(i_nuclide) % absorption)) else @@ -3940,8 +3940,8 @@ contains p % last_E <= nuc % multipole % end_E .and. & micro_xs(mat % nuclide(l)) % absorption > ZERO) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) - cum_dsig = cum_dsig + dsigA * mat % atom_density(l) + p % sqrtkT, dsig_t, dsig_a, dsig_f) + cum_dsig = cum_dsig + dsig_a * mat % atom_density(l) end if end associate end do @@ -3950,17 +3950,17 @@ contains + cum_dsig / material_xs % absorption) else if (materials(p % material) % id == deriv % diff_material & .and. material_xs % absorption > ZERO) then - dsigA = ZERO + dsig_a = ZERO associate (nuc => nuclides(i_nuclide)) if (nuc % mp_present .and. & p % last_E >= nuc % multipole % start_E .and. & p % last_E <= nuc % multipole % end_E) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) end if end associate score = score * (flux_deriv & - + dsigA / micro_xs(i_nuclide) % absorption) + + dsig_a / micro_xs(i_nuclide) % absorption) else score = score * flux_deriv end if @@ -3978,8 +3978,8 @@ contains p % last_E <= nuc % multipole % end_E .and. & micro_xs(mat % nuclide(l)) % fission > ZERO) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) - cum_dsig = cum_dsig + dsigF * mat % atom_density(l) + p % sqrtkT, dsig_t, dsig_a, dsig_f) + cum_dsig = cum_dsig + dsig_f * mat % atom_density(l) end if end associate end do @@ -3988,17 +3988,17 @@ contains + cum_dsig / material_xs % fission) else if (materials(p % material) % id == deriv % diff_material & .and. material_xs % fission > ZERO) then - dsigF = ZERO + dsig_f = ZERO associate (nuc => nuclides(i_nuclide)) if (nuc % mp_present .and. & p % last_E >= nuc % multipole % start_E .and. & p % last_E <= nuc % multipole % end_E) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) end if end associate score = score * (flux_deriv & - + dsigF / micro_xs(i_nuclide) % fission) + + dsig_f / micro_xs(i_nuclide) % fission) else score = score * flux_deriv end if @@ -4016,8 +4016,8 @@ contains p % last_E <= nuc % multipole % end_E .and. & micro_xs(mat % nuclide(l)) % nu_fission > ZERO) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) - cum_dsig = cum_dsig + dsigF * mat % atom_density(l) & + p % sqrtkT, dsig_t, dsig_a, dsig_f) + cum_dsig = cum_dsig + dsig_f * mat % atom_density(l) & * micro_xs(mat % nuclide(l)) % nu_fission & / micro_xs(mat % nuclide(l)) % fission end if @@ -4028,17 +4028,17 @@ contains + cum_dsig / material_xs % nu_fission) else if (materials(p % material) % id == deriv % diff_material & .and. material_xs % nu_fission > ZERO) then - dsigF = ZERO + dsig_f = ZERO associate (nuc => nuclides(i_nuclide)) if (nuc % mp_present .and. & p % last_E >= nuc % multipole % start_E .and. & p % last_E <= nuc % multipole % end_E) then call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) end if end associate score = score * (flux_deriv & - + dsigF / micro_xs(i_nuclide) % fission) + + dsig_f / micro_xs(i_nuclide) % fission) else score = score * flux_deriv end if @@ -4066,7 +4066,7 @@ contains real(8), intent(in) :: distance ! Neutron flight distance integer :: i, l - real(8) :: dsigT, dsigA, dsigF + real(8) :: dsig_t, dsig_a, dsig_f ! A void material cannot be perturbed so it will not affect flux derivatives if (p % material == MATERIAL_VOID) return @@ -4109,9 +4109,9 @@ contains ! (1 / phi) * (d_phi / d_T) = - (d_Sigma_tot / d_T) * dist ! (1 / phi) * (d_phi / d_T) = - N (d_sigma_tot / d_T) * dist call multipole_deriv_eval(nuc % multipole, p % E, & - p % sqrtkT, dsigT, dsigA, dsigF) + p % sqrtkT, dsig_t, dsig_a, dsig_f) deriv % flux_deriv = deriv % flux_deriv & - - distance * dsigT * mat % atom_density(l) + - distance * dsig_t * mat % atom_density(l) end if end associate end do @@ -4142,7 +4142,7 @@ contains type(Particle), intent(in) :: p integer :: i, j, l - real(8) :: dsigT, dsigA, dsigF + real(8) :: dsig_t, dsig_a, dsig_f ! A void material cannot be perturbed so it will not affect flux derivatives if (p % material == MATERIAL_VOID) return @@ -4196,8 +4196,8 @@ contains ! (1 / phi) * (d_phi / d_T) = (d_Sigma_s / d_T) / Sigma_s ! (1 / phi) * (d_phi / d_T) = (d_sigma_s / d_T) / sigma_s call multipole_deriv_eval(nuc % multipole, p % last_E, & - p % sqrtkT, dsigT, dsigA, dsigF) - deriv % flux_deriv = deriv % flux_deriv + (dsigT - dsigA)& + p % sqrtkT, dsig_t, dsig_a, dsig_f) + deriv % flux_deriv = deriv % flux_deriv + (dsig_t - dsig_a)& / (micro_xs(mat % nuclide(l)) % total & - micro_xs(mat % nuclide(l)) % absorption) ! Note that this is an approximation! The real scattering From ac29b7c719a7d07e2f3a761fca8564edf3cb3633 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 12 Jan 2018 12:36:53 -0600 Subject: [PATCH 103/282] Make cross section arrays in sum_xs contiguous --- src/cross_section.F90 | 24 ++++++++--------- src/nuclide_header.F90 | 60 +++++++++++++++++++++--------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 69e577966..f0d81f892 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -266,29 +266,29 @@ contains micro_xs(i_nuclide) % interp_factor = f ! Calculate microscopic nuclide total cross section - micro_xs(i_nuclide) % total = (ONE - f) * xs % total(i_grid) & - + f * xs % total(i_grid + 1) + micro_xs(i_nuclide) % total = (ONE - f) * xs % value(SUM_XS_TOTAL,i_grid) & + + f * xs % value(SUM_XS_TOTAL,i_grid + 1) ! Calculate microscopic nuclide elastic cross section - micro_xs(i_nuclide) % elastic = (ONE - f) * xs % elastic(i_grid) & - + f * xs % elastic(i_grid + 1) - - ! Calculate microscopic nuclide absorption cross section - micro_xs(i_nuclide) % absorption = (ONE - f) * xs % absorption( & - i_grid) + f * xs % absorption(i_grid + 1) + micro_xs(i_nuclide) % elastic = (ONE - f) * xs % value(SUM_XS_ELASTIC,i_grid) & + + f * xs % value(SUM_XS_ELASTIC,i_grid + 1) if (nuc % fissionable) then ! Calculate microscopic nuclide total cross section - micro_xs(i_nuclide) % fission = (ONE - f) * xs % fission(i_grid) & - + f * xs % fission(i_grid + 1) + micro_xs(i_nuclide) % fission = (ONE - f) * xs % value(SUM_XS_FISSION,i_grid) & + + f * xs % value(SUM_XS_FISSION,i_grid + 1) ! Calculate microscopic nuclide nu-fission cross section - micro_xs(i_nuclide) % nu_fission = (ONE - f) * xs % nu_fission( & - i_grid) + f * xs % nu_fission(i_grid + 1) + micro_xs(i_nuclide) % nu_fission = (ONE - f) * xs % value(SUM_XS_NU_FISSION, & + i_grid) + f * xs % value(SUM_XS_NU_FISSION,i_grid + 1) else micro_xs(i_nuclide) % fission = ZERO micro_xs(i_nuclide) % nu_fission = ZERO end if + + ! Calculate microscopic nuclide absorption cross section + micro_xs(i_nuclide) % absorption = (ONE - f) * xs % value(SUM_XS_ABSORPTION, & + i_grid) + f * xs % value(SUM_XS_ABSORPTION,i_grid + 1) end associate ! Depletion-related reactions diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 38e89b648..e204f68de 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -36,13 +36,22 @@ module nuclide_header real(8), allocatable :: energy(:) ! energy values corresponding to xs end type EnergyGrid + integer, parameter :: & + SUM_XS_TOTAL = 1, & + SUM_XS_ELASTIC = 2, & + SUM_XS_FISSION = 3, & + SUM_XS_NU_FISSION = 4, & + SUM_XS_ABSORPTION = 5, & + SUM_XS_HEATING = 6 + type SumXS - real(8), allocatable :: total(:) ! total cross section - real(8), allocatable :: elastic(:) ! elastic scattering - real(8), allocatable :: fission(:) ! fission - real(8), allocatable :: nu_fission(:) ! neutron production - real(8), allocatable :: absorption(:) ! absorption (MT > 100) - real(8), allocatable :: heating(:) ! heating + real(8), allocatable :: value(:,:) +!!$ real(8), allocatable :: total(:) ! total cross section +!!$ real(8), allocatable :: elastic(:) ! elastic scattering +!!$ real(8), allocatable :: fission(:) ! fission +!!$ real(8), allocatable :: nu_fission(:) ! neutron production +!!$ real(8), allocatable :: absorption(:) ! absorption (MT > 100) +!!$ real(8), allocatable :: heating(:) ! heating end type SumXS type :: Nuclide @@ -571,16 +580,8 @@ contains do i = 1, n_temperature ! Allocate and initialize derived cross sections n_grid = size(this % grid(i) % energy) - allocate(this % sum_xs(i) % total(n_grid)) - allocate(this % sum_xs(i) % elastic(n_grid)) - allocate(this % sum_xs(i) % fission(n_grid)) - allocate(this % sum_xs(i) % nu_fission(n_grid)) - allocate(this % sum_xs(i) % absorption(n_grid)) - this % sum_xs(i) % total(:) = ZERO - this % sum_xs(i) % elastic(:) = ZERO - this % sum_xs(i) % fission(:) = ZERO - this % sum_xs(i) % nu_fission(:) = ZERO - this % sum_xs(i) % absorption(:) = ZERO + allocate(this % sum_xs(i) % value(6,n_grid)) + this % sum_xs(i) % value(:,:) = ZERO end do i_fission = 0 @@ -608,16 +609,17 @@ contains n = size(rx % xs(t) % value) ! Copy elastic - if (rx % MT == ELASTIC) this % sum_xs(t) % elastic(:) = rx % xs(t) % value + if (rx % MT == ELASTIC) this % sum_xs(t) % value(SUM_XS_ELASTIC,:) = & + rx % xs(t) % value ! Add contribution to total cross section - this % sum_xs(t) % total(j:j+n-1) = this % sum_xs(t) % total(j:j+n-1) + & - rx % xs(t) % value + this % sum_xs(t) % value(SUM_XS_TOTAL,j:j+n-1) = this % sum_xs(t) % & + value(SUM_XS_TOTAL,j:j+n-1) + rx % xs(t) % value ! Add contribution to absorption cross section if (is_disappearance(rx % MT)) then - this % sum_xs(t) % absorption(j:j+n-1) = this % sum_xs(t) % & - absorption(j:j+n-1) + rx % xs(t) % value + this % sum_xs(t) % value(SUM_XS_ABSORPTION,j:j+n-1) = this % sum_xs(t) % & + value(SUM_XS_ABSORPTION,j:j+n-1) + rx % xs(t) % value end if ! Information about fission reactions @@ -633,12 +635,12 @@ contains ! Add contribution to fission cross section if (is_fission(rx % MT)) then this % fissionable = .true. - this % sum_xs(t) % fission(j:j+n-1) = this % sum_xs(t) % & - fission(j:j+n-1) + rx % xs(t) % value + this % sum_xs(t) % value(SUM_XS_FISSION,j:j+n-1) = this % sum_xs(t) % & + value(SUM_XS_FISSION,j:j+n-1) + rx % xs(t) % value ! Also need to add fission cross sections to absorption - this % sum_xs(t) % absorption(j:j+n-1) = this % sum_xs(t) % & - absorption(j:j+n-1) + rx % xs(t) % value + this % sum_xs(t) % value(SUM_XS_ABSORPTION,j:j+n-1) = this % sum_xs(t) % & + value(SUM_XS_ABSORPTION,j:j+n-1) + rx % xs(t) % value ! If total fission reaction is present, there's no need to store the ! reaction cross-section since it was copied to this % fission @@ -689,12 +691,10 @@ contains ! Calculate nu-fission cross section do t = 1, n_temperature if (this % fissionable) then - do i = 1, size(this % sum_xs(t) % fission) - this % sum_xs(t) % nu_fission(i) = this % nu(this % grid(t) % energy(i), & - EMISSION_TOTAL) * this % sum_xs(t) % fission(i) + do i = 1, n_grid + this % sum_xs(t) % value(SUM_XS_NU_FISSION,i) = this % nu(this % grid(t) % energy(i), & + EMISSION_TOTAL) * this % sum_xs(t) % value(SUM_XS_FISSION,i) end do - else - this % sum_xs(t) % nu_fission(:) = ZERO end if end do end subroutine nuclide_create_derived From d2d1703e7671285efb855af929d9ff57cb293131 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 12 Jan 2018 15:06:04 -0600 Subject: [PATCH 104/282] Aesthetic changes. Rename sum_xs, rename SUM_XS -> XS constants --- src/cross_section.F90 | 22 +++++++++--------- src/nuclide_header.F90 | 53 +++++++++++++++++++++--------------------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/cross_section.F90 b/src/cross_section.F90 index f0d81f892..1dfa7f52b 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -233,7 +233,7 @@ contains if (f > prn()) i_temp = i_temp + 1 end select - associate (grid => nuc % grid(i_temp), xs => nuc % sum_xs(i_temp)) + associate (grid => nuc % grid(i_temp), xs => nuc % xs(i_temp)) ! Determine the energy grid index using a logarithmic mapping to ! reduce the energy range over which a binary search needs to be ! performed @@ -266,29 +266,29 @@ contains micro_xs(i_nuclide) % interp_factor = f ! Calculate microscopic nuclide total cross section - micro_xs(i_nuclide) % total = (ONE - f) * xs % value(SUM_XS_TOTAL,i_grid) & - + f * xs % value(SUM_XS_TOTAL,i_grid + 1) + micro_xs(i_nuclide) % total = (ONE - f) * xs % value(XS_TOTAL,i_grid) & + + f * xs % value(XS_TOTAL,i_grid + 1) ! Calculate microscopic nuclide elastic cross section - micro_xs(i_nuclide) % elastic = (ONE - f) * xs % value(SUM_XS_ELASTIC,i_grid) & - + f * xs % value(SUM_XS_ELASTIC,i_grid + 1) + micro_xs(i_nuclide) % elastic = (ONE - f) * xs % value(XS_ELASTIC,i_grid) & + + f * xs % value(XS_ELASTIC,i_grid + 1) if (nuc % fissionable) then ! Calculate microscopic nuclide total cross section - micro_xs(i_nuclide) % fission = (ONE - f) * xs % value(SUM_XS_FISSION,i_grid) & - + f * xs % value(SUM_XS_FISSION,i_grid + 1) + micro_xs(i_nuclide) % fission = (ONE - f) * xs % value(XS_FISSION,i_grid) & + + f * xs % value(XS_FISSION,i_grid + 1) ! Calculate microscopic nuclide nu-fission cross section - micro_xs(i_nuclide) % nu_fission = (ONE - f) * xs % value(SUM_XS_NU_FISSION, & - i_grid) + f * xs % value(SUM_XS_NU_FISSION,i_grid + 1) + micro_xs(i_nuclide) % nu_fission = (ONE - f) * xs % value(XS_NU_FISSION, & + i_grid) + f * xs % value(XS_NU_FISSION,i_grid + 1) else micro_xs(i_nuclide) % fission = ZERO micro_xs(i_nuclide) % nu_fission = ZERO end if ! Calculate microscopic nuclide absorption cross section - micro_xs(i_nuclide) % absorption = (ONE - f) * xs % value(SUM_XS_ABSORPTION, & - i_grid) + f * xs % value(SUM_XS_ABSORPTION,i_grid + 1) + micro_xs(i_nuclide) % absorption = (ONE - f) * xs % value(XS_ABSORPTION, & + i_grid) + f * xs % value(XS_ABSORPTION,i_grid + 1) end associate ! Depletion-related reactions diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index e204f68de..88e934856 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -36,22 +36,20 @@ module nuclide_header real(8), allocatable :: energy(:) ! energy values corresponding to xs end type EnergyGrid + ! Positions for first dimension of Nuclide % xs integer, parameter :: & - SUM_XS_TOTAL = 1, & - SUM_XS_ELASTIC = 2, & - SUM_XS_FISSION = 3, & - SUM_XS_NU_FISSION = 4, & - SUM_XS_ABSORPTION = 5, & - SUM_XS_HEATING = 6 + XS_TOTAL = 1, & + XS_ELASTIC = 2, & + XS_FISSION = 3, & + XS_NU_FISSION = 4, & + XS_ABSORPTION = 5, & + XS_HEATING = 6 + ! The array within SumXS is of shape (6, n_energy) where the first dimension + ! corresponds to the following values: 1) total, 2) elastic scattering, 3) + ! fission, 4) neutron production, 5) absorption (MT > 100), 6) heating type SumXS real(8), allocatable :: value(:,:) -!!$ real(8), allocatable :: total(:) ! total cross section -!!$ real(8), allocatable :: elastic(:) ! elastic scattering -!!$ real(8), allocatable :: fission(:) ! fission -!!$ real(8), allocatable :: nu_fission(:) ! neutron production -!!$ real(8), allocatable :: absorption(:) ! absorption (MT > 100) -!!$ real(8), allocatable :: heating(:) ! heating end type SumXS type :: Nuclide @@ -70,7 +68,7 @@ module nuclide_header type(EnergyGrid), allocatable :: grid(:) ! Microscopic cross sections - type(SumXS), allocatable :: sum_xs(:) + type(SumXS), allocatable :: xs(:) ! Resonance scattering info logical :: resonant = .false. ! resonant scatterer? @@ -575,13 +573,13 @@ contains type(VectorInt) :: MTs n_temperature = size(this % kTs) - allocate(this % sum_xs(n_temperature)) + allocate(this % xs(n_temperature)) this % reaction_index(:) = 0 do i = 1, n_temperature ! Allocate and initialize derived cross sections n_grid = size(this % grid(i) % energy) - allocate(this % sum_xs(i) % value(6,n_grid)) - this % sum_xs(i) % value(:,:) = ZERO + allocate(this % xs(i) % value(6,n_grid)) + this % xs(i) % value(:,:) = ZERO end do i_fission = 0 @@ -609,17 +607,17 @@ contains n = size(rx % xs(t) % value) ! Copy elastic - if (rx % MT == ELASTIC) this % sum_xs(t) % value(SUM_XS_ELASTIC,:) = & + if (rx % MT == ELASTIC) this % xs(t) % value(XS_ELASTIC,:) = & rx % xs(t) % value ! Add contribution to total cross section - this % sum_xs(t) % value(SUM_XS_TOTAL,j:j+n-1) = this % sum_xs(t) % & - value(SUM_XS_TOTAL,j:j+n-1) + rx % xs(t) % value + this % xs(t) % value(XS_TOTAL,j:j+n-1) = this % xs(t) % & + value(XS_TOTAL,j:j+n-1) + rx % xs(t) % value ! Add contribution to absorption cross section if (is_disappearance(rx % MT)) then - this % sum_xs(t) % value(SUM_XS_ABSORPTION,j:j+n-1) = this % sum_xs(t) % & - value(SUM_XS_ABSORPTION,j:j+n-1) + rx % xs(t) % value + this % xs(t) % value(XS_ABSORPTION,j:j+n-1) = this % xs(t) % & + value(XS_ABSORPTION,j:j+n-1) + rx % xs(t) % value end if ! Information about fission reactions @@ -635,12 +633,12 @@ contains ! Add contribution to fission cross section if (is_fission(rx % MT)) then this % fissionable = .true. - this % sum_xs(t) % value(SUM_XS_FISSION,j:j+n-1) = this % sum_xs(t) % & - value(SUM_XS_FISSION,j:j+n-1) + rx % xs(t) % value + this % xs(t) % value(XS_FISSION,j:j+n-1) = this % xs(t) % & + value(XS_FISSION,j:j+n-1) + rx % xs(t) % value ! Also need to add fission cross sections to absorption - this % sum_xs(t) % value(SUM_XS_ABSORPTION,j:j+n-1) = this % sum_xs(t) % & - value(SUM_XS_ABSORPTION,j:j+n-1) + rx % xs(t) % value + this % xs(t) % value(XS_ABSORPTION,j:j+n-1) = this % xs(t) % & + value(XS_ABSORPTION,j:j+n-1) + rx % xs(t) % value ! If total fission reaction is present, there's no need to store the ! reaction cross-section since it was copied to this % fission @@ -692,8 +690,9 @@ contains do t = 1, n_temperature if (this % fissionable) then do i = 1, n_grid - this % sum_xs(t) % value(SUM_XS_NU_FISSION,i) = this % nu(this % grid(t) % energy(i), & - EMISSION_TOTAL) * this % sum_xs(t) % value(SUM_XS_FISSION,i) + this % xs(t) % value(XS_NU_FISSION,i) = & + this % nu(this % grid(t) % energy(i), EMISSION_TOTAL) * & + this % xs(t) % value(XS_FISSION,i) end do end if end do From df40af793b94db0580ca42567403e1562a8bb909 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 12 Jan 2018 15:09:09 -0600 Subject: [PATCH 105/282] Minor simplification on multipole branch in calculate_nuclide_xs --- src/cross_section.F90 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 1dfa7f52b..0a944f211 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -181,14 +181,13 @@ contains call multipole_eval(nuc % multipole, E, sqrtkT, sig_t, sig_a, sig_f) micro_xs(i_nuclide) % total = sig_t - micro_xs(i_nuclide) % absorption = sig_a micro_xs(i_nuclide) % elastic = sig_t - sig_a + micro_xs(i_nuclide) % absorption = sig_a + micro_xs(i_nuclide) % fission = sig_f if (nuc % fissionable) then - micro_xs(i_nuclide) % fission = sig_f micro_xs(i_nuclide) % nu_fission = sig_f * nuc % nu(E, EMISSION_TOTAL) else - micro_xs(i_nuclide) % fission = ZERO micro_xs(i_nuclide) % nu_fission = ZERO end if From 21585e937cbcae426324f497ce8492155807104a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 12 Jan 2018 15:44:30 -0600 Subject: [PATCH 106/282] Remove elastic from sum_xs and caches --- src/cross_section.F90 | 29 ++++++++++++++++------------- src/nuclide_header.F90 | 16 +++++----------- src/physics.F90 | 12 ++++++++++++ src/tallies/tally.F90 | 14 -------------- 4 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 0a944f211..82e3c00e8 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -42,7 +42,6 @@ contains ! Set all material macroscopic cross sections to zero material_xs % total = ZERO - material_xs % elastic = ZERO material_xs % absorption = ZERO material_xs % fission = ZERO material_xs % nu_fission = ZERO @@ -115,10 +114,6 @@ contains material_xs % total = material_xs % total + & atom_density * micro_xs(i_nuclide) % total - ! Add contributions to material macroscopic scattering cross section - material_xs % elastic = material_xs % elastic + & - atom_density * micro_xs(i_nuclide) % elastic - ! Add contributions to material macroscopic absorption cross section material_xs % absorption = material_xs % absorption + & atom_density * micro_xs(i_nuclide) % absorption @@ -162,6 +157,7 @@ contains real(8) :: sig_t, sig_a, sig_f ! Intermediate multipole variables ! Initialize cached cross sections to zero + micro_xs(i_nuclide) % elastic = ZERO micro_xs(i_nuclide) % thermal = ZERO micro_xs(i_nuclide) % thermal_elastic = ZERO @@ -181,7 +177,6 @@ contains call multipole_eval(nuc % multipole, E, sqrtkT, sig_t, sig_a, sig_f) micro_xs(i_nuclide) % total = sig_t - micro_xs(i_nuclide) % elastic = sig_t - sig_a micro_xs(i_nuclide) % absorption = sig_a micro_xs(i_nuclide) % fission = sig_f @@ -268,9 +263,9 @@ contains micro_xs(i_nuclide) % total = (ONE - f) * xs % value(XS_TOTAL,i_grid) & + f * xs % value(XS_TOTAL,i_grid + 1) - ! Calculate microscopic nuclide elastic cross section - micro_xs(i_nuclide) % elastic = (ONE - f) * xs % value(XS_ELASTIC,i_grid) & - + f * xs % value(XS_ELASTIC,i_grid + 1) + ! Calculate microscopic nuclide absorption cross section + micro_xs(i_nuclide) % absorption = (ONE - f) * xs % value(XS_ABSORPTION, & + i_grid) + f * xs % value(XS_ABSORPTION,i_grid + 1) if (nuc % fissionable) then ! Calculate microscopic nuclide total cross section @@ -284,10 +279,6 @@ contains micro_xs(i_nuclide) % fission = ZERO micro_xs(i_nuclide) % nu_fission = ZERO end if - - ! Calculate microscopic nuclide absorption cross section - micro_xs(i_nuclide) % absorption = (ONE - f) * xs % value(XS_ABSORPTION, & - i_grid) + f * xs % value(XS_ABSORPTION,i_grid + 1) end associate ! Depletion-related reactions @@ -450,6 +441,18 @@ contains micro_xs(i_nuclide) % thermal = sab_frac * (elastic + inelastic) micro_xs(i_nuclide) % thermal_elastic = sab_frac * elastic + ! Calculate free atom elastic cross section + f = micro_xs(i_nuclide) % interp_factor + i_grid = micro_xs(i_nuclide) % index_grid + i_temp = micro_xs(i_nuclide) % index_temp + if (i_temp > 0) then + associate (xs => nuclides(i_nuclide) % reactions(1) % xs(i_temp) % value) + micro_xs(i_nuclide) % elastic = (ONE - f)*xs(i_grid) + f*xs(i_grid + 1) + end associate + else + micro_xs(i_nuclide) % elastic = ZERO + end if + ! Correct total and elastic cross sections micro_xs(i_nuclide) % total = micro_xs(i_nuclide) % total & + micro_xs(i_nuclide) % thermal & diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 88e934856..23e6e5825 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -39,11 +39,9 @@ module nuclide_header ! Positions for first dimension of Nuclide % xs integer, parameter :: & XS_TOTAL = 1, & - XS_ELASTIC = 2, & + XS_ABSORPTION = 2, & XS_FISSION = 3, & - XS_NU_FISSION = 4, & - XS_ABSORPTION = 5, & - XS_HEATING = 6 + XS_NU_FISSION = 4 ! The array within SumXS is of shape (6, n_energy) where the first dimension ! corresponds to the following values: 1) total, 2) elastic scattering, 3) @@ -120,11 +118,12 @@ module nuclide_header type NuclideMicroXS ! Microscopic cross sections in barns real(8) :: total - real(8) :: elastic ! If sab_frac is not 1 or 0, then this value is - ! averaged over bound and non-bound nuclei real(8) :: absorption ! absorption (disappearance) real(8) :: fission ! fission real(8) :: nu_fission ! neutron production from fission + + real(8) :: elastic ! If sab_frac is not 1 or 0, then this value is + ! averaged over bound and non-bound nuclei real(8) :: thermal ! Bound thermal elastic & inelastic scattering real(8) :: thermal_elastic ! Bound thermal elastic scattering @@ -155,7 +154,6 @@ module nuclide_header type MaterialMacroXS real(8) :: total ! macroscopic total xs - real(8) :: elastic ! macroscopic elastic scattering xs real(8) :: absorption ! macroscopic absorption xs real(8) :: fission ! macroscopic fission xs real(8) :: nu_fission ! macroscopic production xs @@ -606,10 +604,6 @@ contains j = rx % xs(t) % threshold n = size(rx % xs(t) % value) - ! Copy elastic - if (rx % MT == ELASTIC) this % xs(t) % value(XS_ELASTIC,:) = & - rx % xs(t) % value - ! Add contribution to total cross section this % xs(t) % value(XS_TOTAL,j:j+n-1) = this % xs(t) % & value(XS_TOTAL,j:j+n-1) + rx % xs(t) % value diff --git a/src/physics.F90 b/src/physics.F90 index d13313e05..5d31d87d7 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -338,6 +338,18 @@ contains micro_xs(i_nuclide) % absorption) sampled = .false. + ! Calculate elastic cross section if need be + if (micro_xs(i_nuclide) % elastic == ZERO) then + if (i_temp > 0) then + associate (xs => nuc % reactions(1) % xs(i_temp) % value) + micro_xs(i_nuclide) % elastic = (ONE - f)*xs(i_grid) + f*xs(i_grid + 1) + end associate + else + micro_xs(i_nuclide) % elastic = micro_xs(i_nuclide) % total - & + micro_xs(i_nuclide) % absorption + end if + end if + prob = micro_xs(i_nuclide) % elastic - micro_xs(i_nuclide) % thermal if (prob > cutoff) then ! ======================================================================= diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 7684a36aa..d3dbf888d 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -1009,20 +1009,6 @@ contains ! Simply count number of scoring events score = ONE - case (ELASTIC) - if (t % estimator == ESTIMATOR_ANALOG) then - ! Check if event MT matches - if (p % event_MT /= ELASTIC) cycle SCORE_LOOP - score = p % last_wgt * flux - - else - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % elastic * atom_density * flux - else - score = material_xs % elastic * flux - end if - end if - case (SCORE_FISS_Q_PROMPT) score = ZERO From 0cf1f918f5386ffc77654b181ea5caecab713a82 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 15 Jan 2018 16:44:18 -0600 Subject: [PATCH 107/282] Put automatic arrays on stack when using gfortran (for multipole performance) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5150745b..51bdcc4d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,9 +116,9 @@ if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU) endif() # GCC compiler options - list(APPEND f90flags -cpp -std=f2008ts -fbacktrace -O2) + list(APPEND f90flags -cpp -std=f2008ts -fbacktrace -O2 -fstack-arrays) if(debug) - list(REMOVE_ITEM f90flags -O2) + list(REMOVE_ITEM f90flags -O2 -fstack-arrays) list(APPEND f90flags -g -Wall -Wno-unused-dummy-argument -pedantic -fbounds-check -ffpe-trap=invalid,overflow,underflow) list(APPEND ldflags -g) From 7d7453b8ffbbfa4e8c82b152c9e00a5b644e29bd Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 15 Jan 2018 17:25:21 -0600 Subject: [PATCH 108/282] Make sure elastic scattering cross section is precalculated for use in URR --- src/cross_section.F90 | 44 +++++++++++++++++++++++++++++++----------- src/nuclide_header.F90 | 8 ++++---- src/physics.F90 | 15 ++++---------- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 82e3c00e8..22598dc42 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -157,7 +157,7 @@ contains real(8) :: sig_t, sig_a, sig_f ! Intermediate multipole variables ! Initialize cached cross sections to zero - micro_xs(i_nuclide) % elastic = ZERO + micro_xs(i_nuclide) % elastic = -ONE micro_xs(i_nuclide) % thermal = ZERO micro_xs(i_nuclide) % thermal_elastic = ZERO @@ -442,16 +442,7 @@ contains micro_xs(i_nuclide) % thermal_elastic = sab_frac * elastic ! Calculate free atom elastic cross section - f = micro_xs(i_nuclide) % interp_factor - i_grid = micro_xs(i_nuclide) % index_grid - i_temp = micro_xs(i_nuclide) % index_temp - if (i_temp > 0) then - associate (xs => nuclides(i_nuclide) % reactions(1) % xs(i_temp) % value) - micro_xs(i_nuclide) % elastic = (ONE - f)*xs(i_grid) + f*xs(i_grid + 1) - end associate - else - micro_xs(i_nuclide) % elastic = ZERO - end if + call calculate_elastic_xs(i_nuclide) ! Correct total and elastic cross sections micro_xs(i_nuclide) % total = micro_xs(i_nuclide) % total & @@ -581,6 +572,7 @@ contains ! Multiply by smooth cross-section if needed if (urr % multiply_smooth) then + call calculate_elastic_xs(i_nuclide) elastic = elastic * micro_xs(i_nuclide) % elastic capture = capture * (micro_xs(i_nuclide) % absorption - & micro_xs(i_nuclide) % fission) @@ -609,6 +601,36 @@ contains end subroutine calculate_urr_xs +!=============================================================================== +! CALCULATE_ELASTIC_XS precalculates the free atom elastic scattering cross +! section. Normally it is not needed until a collision actually occurs in a +! material. However, in the thermal and unresolved resonance regions, we have to +! calculate it early to adjust the total cross section correctly. +!=============================================================================== + + subroutine calculate_elastic_xs(i_nuclide) + integer, intent(in) :: i_nuclide + + integer :: i_temp + integer :: i_grid + real(8) :: f + + ! Get temperature index, grid index, and interpolation factor + i_temp = micro_xs(i_nuclide) % index_temp + i_grid = micro_xs(i_nuclide) % index_grid + f = micro_xs(i_nuclide) % interp_factor + + if (i_temp > 0) then + associate (xs => nuclides(i_nuclide) % reactions(1) % xs(i_temp) % value) + micro_xs(i_nuclide) % elastic = (ONE - f)*xs(i_grid) + f*xs(i_grid + 1) + end associate + else + ! For multipole, elastic is total - absorption + micro_xs(i_nuclide) % elastic = micro_xs(i_nuclide) % total - & + micro_xs(i_nuclide) % absorption + end if + end subroutine calculate_elastic_xs + !=============================================================================== ! MULTIPOLE_EVAL evaluates the windowed multipole equations for cross ! sections in the resolved resonance regions diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 23e6e5825..328fe123a 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -43,9 +43,9 @@ module nuclide_header XS_FISSION = 3, & XS_NU_FISSION = 4 - ! The array within SumXS is of shape (6, n_energy) where the first dimension - ! corresponds to the following values: 1) total, 2) elastic scattering, 3) - ! fission, 4) neutron production, 5) absorption (MT > 100), 6) heating + ! The array within SumXS is of shape (4, n_energy) where the first dimension + ! corresponds to the following values: 1) total, 2) absorption (MT > 100), 3) + ! fission, 4) neutron production type SumXS real(8), allocatable :: value(:,:) end type SumXS @@ -576,7 +576,7 @@ contains do i = 1, n_temperature ! Allocate and initialize derived cross sections n_grid = size(this % grid(i) % energy) - allocate(this % xs(i) % value(6,n_grid)) + allocate(this % xs(i) % value(4,n_grid)) this % xs(i) % value(:,:) = ZERO end do diff --git a/src/physics.F90 b/src/physics.F90 index 5d31d87d7..cb2877824 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -2,7 +2,7 @@ module physics use algorithm, only: binary_search use constants - use cross_section, only: elastic_xs_0K + use cross_section, only: elastic_xs_0K, calculate_elastic_xs use endf, only: reaction_name use error, only: fatal_error, warning, write_message use material_header, only: Material, materials @@ -338,16 +338,9 @@ contains micro_xs(i_nuclide) % absorption) sampled = .false. - ! Calculate elastic cross section if need be - if (micro_xs(i_nuclide) % elastic == ZERO) then - if (i_temp > 0) then - associate (xs => nuc % reactions(1) % xs(i_temp) % value) - micro_xs(i_nuclide) % elastic = (ONE - f)*xs(i_grid) + f*xs(i_grid + 1) - end associate - else - micro_xs(i_nuclide) % elastic = micro_xs(i_nuclide) % total - & - micro_xs(i_nuclide) % absorption - end if + ! Calculate elastic cross section if it wasn't precalculated + if (micro_xs(i_nuclide) % elastic < ZERO) then + call calculate_elastic_xs(i_nuclide) end if prob = micro_xs(i_nuclide) % elastic - micro_xs(i_nuclide) % thermal From f74c2fae563ff944902059c0d786940723a9a885 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 15 Jan 2018 17:54:34 -0600 Subject: [PATCH 109/282] Fix elastic scattering rate tallies --- src/tallies/tally.F90 | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index d3dbf888d..eb17070c1 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -4,7 +4,7 @@ module tally use algorithm, only: binary_search use constants - use cross_section, only: multipole_deriv_eval + use cross_section, only: multipole_deriv_eval, calculate_elastic_xs use dict_header, only: EMPTY use error, only: fatal_error use geometry_header @@ -1009,6 +1009,37 @@ contains ! Simply count number of scoring events score = ONE + case (ELASTIC) + if (t % estimator == ESTIMATOR_ANALOG) then + ! Check if event MT matches + if (p % event_MT /= ELASTIC) cycle SCORE_LOOP + score = p % last_wgt * flux + + else + if (i_nuclide > 0) then + if (micro_xs(i_nuclide) % elastic < ZERO) then + call calculate_elastic_xs(i_nuclide) + end if + score = micro_xs(i_nuclide) % elastic * atom_density * flux + else + score = ZERO + if (p % material /= MATERIAL_VOID) then + do l = 1, materials(p % material) % n_nuclides + ! Get atom density + atom_density_ = materials(p % material) % atom_density(l) + + ! Get index in nuclides array + i_nuc = materials(p % material) % nuclide(l) + if (micro_xs(i_nuc) % elastic < ZERO) then + call calculate_elastic_xs(i_nuc) + end if + + score = score + micro_xs(i_nuc) % elastic * atom_density_ * flux + end do + end if + end if + end if + case (SCORE_FISS_Q_PROMPT) score = ZERO From e300c84db5618d74fa636b0a61185a16245398b1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 17 Jan 2018 12:02:01 -0600 Subject: [PATCH 110/282] Replace -ONE with an arbitrary value in named constant CACHE_INVALID --- src/cross_section.F90 | 2 +- src/nuclide_header.F90 | 4 ++++ src/physics.F90 | 2 +- src/tallies/tally.F90 | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/cross_section.F90 b/src/cross_section.F90 index 22598dc42..47e1c4fc5 100644 --- a/src/cross_section.F90 +++ b/src/cross_section.F90 @@ -157,7 +157,7 @@ contains real(8) :: sig_t, sig_a, sig_f ! Intermediate multipole variables ! Initialize cached cross sections to zero - micro_xs(i_nuclide) % elastic = -ONE + micro_xs(i_nuclide) % elastic = CACHE_INVALID micro_xs(i_nuclide) % thermal = ZERO micro_xs(i_nuclide) % thermal_elastic = ZERO diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 328fe123a..1ca584914 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -115,6 +115,10 @@ module nuclide_header ! nuclide at the current energy !=============================================================================== + ! Arbitrary value to indicate invalid cache state for elastic scattering + ! (NuclideMicroXS % elastic) + real(8), parameter :: CACHE_INVALID = dble(Z"FFE0000000000000") + type NuclideMicroXS ! Microscopic cross sections in barns real(8) :: total diff --git a/src/physics.F90 b/src/physics.F90 index cb2877824..fe242dbb7 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -339,7 +339,7 @@ contains sampled = .false. ! Calculate elastic cross section if it wasn't precalculated - if (micro_xs(i_nuclide) % elastic < ZERO) then + if (micro_xs(i_nuclide) % elastic == CACHE_INVALID) then call calculate_elastic_xs(i_nuclide) end if diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index eb17070c1..027e335e9 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -1017,7 +1017,7 @@ contains else if (i_nuclide > 0) then - if (micro_xs(i_nuclide) % elastic < ZERO) then + if (micro_xs(i_nuclide) % elastic == CACHE_INVALID) then call calculate_elastic_xs(i_nuclide) end if score = micro_xs(i_nuclide) % elastic * atom_density * flux @@ -1030,7 +1030,7 @@ contains ! Get index in nuclides array i_nuc = materials(p % material) % nuclide(l) - if (micro_xs(i_nuc) % elastic < ZERO) then + if (micro_xs(i_nuc) % elastic == CACHE_INVALID) then call calculate_elastic_xs(i_nuc) end if From 2821ce589f6dc81b40c593e2a20e0f6165dddde4 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 18 Jan 2018 18:04:15 -0500 Subject: [PATCH 111/282] Add to rng and rnc --- src/relaxng/settings.rnc | 2 ++ src/relaxng/settings.rng | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/relaxng/settings.rnc b/src/relaxng/settings.rnc index 19a2948c5..282c685db 100644 --- a/src/relaxng/settings.rnc +++ b/src/relaxng/settings.rnc @@ -3,6 +3,8 @@ element settings { element confidence_intervals { xsd:boolean }? & + element create_fission_neutrons { xsd:boolean }? & + element cutoff { (element weight { xsd:double } | attribute weight { xsd:double })? & (element weight_avg { xsd:double } | attribute weight_avg { xsd:double })? diff --git a/src/relaxng/settings.rng b/src/relaxng/settings.rng index 606b97829..0018cb16b 100644 --- a/src/relaxng/settings.rng +++ b/src/relaxng/settings.rng @@ -11,6 +11,11 @@ + + + + + From 91ccc4da0051a3d1f75286bbc4c2305cd720867f Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 8 Aug 2017 18:10:53 -0400 Subject: [PATCH 112/282] Implement z planes and cylinders in C++ --- CMakeLists.txt | 5 +- src/geometry.F90 | 47 +++++++- src/input_xml.F90 | 9 ++ src/surface_header.C | 280 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 338 insertions(+), 3 deletions(-) create mode 100644 src/surface_header.C diff --git a/CMakeLists.txt b/CMakeLists.txt index c5150745b..200d836fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -436,7 +436,10 @@ set(LIBOPENMC_FORTRAN_SRC ) set(LIBOPENMC_CXX_SRC src/random_lcg.h - src/random_lcg.cpp) + src/random_lcg.cpp + src/surface_header.C + src/pugixml/pugixml.hpp + src/pugixml/pugixml.cpp) add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC} ${LIBOPENMC_CXX_SRC}) set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc) add_executable(${program} src/main.F90) diff --git a/src/geometry.F90 b/src/geometry.F90 index ced62e681..bc1eca2ae 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -10,8 +10,33 @@ module geometry use stl_vector, only: VectorInt use string, only: to_str + use, intrinsic :: ISO_C_BINDING + implicit none + interface + function surface_sense_c(surf_ind, xyz, uvw) & + bind(C, name='surface_sense') result(sense) + use ISO_C_BINDING + implicit none + integer(C_INT), value :: surf_ind; + real(C_DOUBLE) :: xyz(3); + real(C_DOUBLE) :: uvw(3); + logical(C_BOOL) :: sense; + end function surface_sense_c + + function surface_distance_c(surf_ind, xyz, uvw, coincident) & + bind(C, name='surface_distance') result(d) + use ISO_C_BINDING + implicit none + integer(C_INT), value :: surf_ind; + real(C_DOUBLE) :: xyz(3); + real(C_DOUBLE) :: uvw(3); + logical(C_BOOL), value :: coincident; + real(C_DOUBLE) :: d; + end function surface_distance_c + end interface + contains !=============================================================================== @@ -28,7 +53,8 @@ contains ! expression using a stack, similar to how a RPN calculator would work. !=============================================================================== - pure function cell_contains(c, p) result(in_cell) + !pure function cell_contains(c, p) result(in_cell) + function cell_contains(c, p) result(in_cell) type(Cell), intent(in) :: c type(Particle), intent(in) :: p logical :: in_cell @@ -40,7 +66,8 @@ contains end if end function cell_contains - pure function simple_cell_contains(c, p) result(in_cell) + !pure function simple_cell_contains(c, p) result(in_cell) + function simple_cell_contains(c, p) result(in_cell) type(Cell), intent(in) :: c type(Particle), intent(in) :: p logical :: in_cell @@ -48,6 +75,7 @@ contains integer :: i integer :: token logical :: actual_sense ! sense of particle wrt surface + logical :: alt_sense in_cell = .true. do i = 1, size(c%rpn) @@ -65,6 +93,15 @@ contains else actual_sense = surfaces(abs(token))%obj%sense(& p%coord(p%n_coord)%xyz, p%coord(p%n_coord)%uvw) + alt_sense = surface_sense_c(abs(token)-1, & + p%coord(p%n_coord)%xyz, p%coord(p%n_coord)%uvw) + if (actual_sense .neqv. alt_sense) then + write(*, *) surfaces(abs(token)) % obj % id + write(*, *) p % coord (p % n_coord) % xyz + write(*, *) p % coord (p % n_coord) % uvw + write(*, *) actual_sense, alt_sense + call fatal_error("") + end if if (actual_sense .neqv. (token > 0)) then in_cell = .false. exit @@ -484,6 +521,7 @@ contains type(Cell), pointer :: c class(Surface), pointer :: surf class(Lattice), pointer :: lat + real(8) :: d_alt ! inialize distance to infinity (huge) dist = INFINITY @@ -519,6 +557,11 @@ contains ! Calculate distance to surface surf => surfaces(index_surf) % obj d = surf % distance(p % coord(j) % xyz, p % coord(j) % uvw, coincident) + d_alt = surface_distance_c(index_surf-1, p % coord(j) % xyz, & + p % coord(j) % uvw, logical(coincident, kind=C_BOOL)) + if (d /= d_alt) then + write(*, *) d, d_alt + end if ! Check if calculated distance is new minimum if (d < d_surf) then diff --git a/src/input_xml.F90 b/src/input_xml.F90 index bd56422cb..4994ff76b 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -48,6 +48,14 @@ module input_xml implicit none save + interface + subroutine read_surfaces(node_ptr) bind(C, name='read_surfaces') + use ISO_C_BINDING + implicit none + type(C_PTR) :: node_ptr + end subroutine read_surfaces + end interface + contains !=============================================================================== @@ -966,6 +974,7 @@ contains ! get pointer to list of xml call get_node_list(root, "surface", node_surf_list) + call read_surfaces(root % ptr) ! Get number of tags n_surfaces = size(node_surf_list) diff --git a/src/surface_header.C b/src/surface_header.C new file mode 100644 index 000000000..08287da88 --- /dev/null +++ b/src/surface_header.C @@ -0,0 +1,280 @@ +#include // For strcmp +#include +#include // For numeric_limits +#include // For fabs +#include "pugixml/pugixml.hpp" + +//============================================================================== +// Constants +//============================================================================== + +const double FP_COINCIDENT = 1e-12; +const double INFTY = std::numeric_limits::max(); + +//============================================================================== +// Global array of surfaces +//============================================================================== + +class Surface; +Surface **surfaces_c; + +//============================================================================== +// SURFACE type defines a first- or second-order surface that can be used to +// construct closed volumes (cells) +//============================================================================== + +class Surface +{ + int id; // Unique ID + int neighbor_pos[], // List of cells on positive side + neighbor_neg[]; // List of cells on negative side + int bc; // Boundary condition + //TODO: swith that zero to a NONE constant. + int i_periodic = 0; // Index of corresponding periodic surface + char name[104]; // User-defined name + +public: + bool sense(double xyz[3], double uvw[3]); + void reflect(double xyz[3], double uvw[3]); + virtual double evaluate(double xyz[3]) = 0; + virtual double distance(double xyz[3], double uvw[3], bool coincident) = 0; + virtual void normal(double xyz[3], double uvw[3]) = 0; +}; + +bool +Surface::sense(double xyz[3], double uvw[3]) +{ + // Evaluate the surface equation at the particle's coordinates to determine + // which side the particle is on. + const double f = evaluate(xyz); + + // Check which side of surface the point is on. + bool s; + if (fabs(f) < FP_COINCIDENT) + { + // Particle may be coincident with this surface. To determine the sense, we + // look at the direction of the particle relative to the surface normal (by + // default in the positive direction) via their dot product. + double norm[3]; + normal(xyz, norm); + s = (uvw[0] * norm[0] + uvw[1] * norm[1] + uvw[2] * norm[2] > 0.0); + } + else + { + s = (f > 0.0); + } + return s; +} + +void +Surface::reflect(double xyz[3], double uvw[3]) +{ + + // Determine projection of direction onto normal and squared magnitude of + // normal. + double norm[3]; + normal(xyz, norm); + const double projection = norm[0]*uvw[0] + norm[1]*uvw[1] + norm[2]*uvw[2]; + const double magnitude = norm[0]*norm[0] + norm[1]*norm[1] + norm[2]*norm[2]; + + // Reflect direction according to normal. + uvw[0] -= 2.0 * projection / magnitude * norm[0]; + uvw[1] -= 2.0 * projection / magnitude * norm[1]; + uvw[2] -= 2.0 * projection / magnitude * norm[2]; +} + +//============================================================================== + +class SurfaceZPlane : public Surface +{ + double z0; +public: + SurfaceZPlane(pugi::xml_node surf_node); + double evaluate(double xyz[3]); + double distance(double xyz[3], double uvw[3], bool coincident); + void normal(double xyz[3], double uvw[3]); +}; + +SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) +{ + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf", &z0); + if (stat != 1) + { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +double +SurfaceZPlane::evaluate(double xyz[3]) +{ + double f = xyz[2] - z0; + return f; +} + +double +SurfaceZPlane::distance(double xyz[3], double uvw[3], bool coincident) +{ + double f = z0 - xyz[2]; + double d; + if (coincident or fabs(f) < FP_COINCIDENT or uvw[2] == 0.0) + { + d = INFTY; + } + else + { + d = f / uvw[2]; + if (d < 0.0) d = INFTY; + } + return d; +} + +void +SurfaceZPlane::normal(double xyz[3], double uvw[3]) +{ + uvw[0] = 0.0; + uvw[1] = 0.0; + uvw[2] = 1.0; +} + +//============================================================================== + +class SurfaceZCylinder : public Surface +{ + double x0, y0, r; +public: + SurfaceZCylinder(pugi::xml_node surf_node); + double evaluate(double xyz[3]); + double distance(double xyz[3], double uvw[3], bool coincident); + void normal(double xyz[3], double uvw[3]); +}; + +SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) +{ + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf %lf %lf", &x0, &y0, &r); + if (stat != 3) + { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +double +SurfaceZCylinder::evaluate(double xyz[3]) +{ + const double x = xyz[0] - x0; + const double y = xyz[1] - y0; + double f = x*x + y*y - r*r; + return f; +} + +double +SurfaceZCylinder::distance(double xyz[3], double uvw[3], bool coincident) +{ + double a = 1.0 - uvw[2]*uvw[2]; // u^2 + v^2 + + if (a == 0.0) return INFTY; + + double x = xyz[0] - x0; + double y = xyz[1] - y0; + double k = x*uvw[0] + y*uvw[1]; + double c = x*x + y*y - r*r; + double quad = k*k - a*c; + + if (quad < 0.0) + { + // No intersection with cylinder. + return INFTY; + } + else if (coincident or fabs(c) < FP_COINCIDENT) + { + // Particle is on the cylinder, thus one distance is positive/negative + // and the other is zero. The sign of k determines if we are facing in or + // out. + if (k >= 0.0) return INFTY; + else return (-k + sqrt(quad)) / a; + } + else if (c < 0.0) + { + // Particle is inside the cylinder, thus one distance must be negative + // and one must be positive. The positive distance will be the one with + // negative sign on sqrt(quad) + return (-k + sqrt(quad)) / a; + } + else + { + // Particle is outside the cylinder, thus both distances are either + // positive or negative. If positive, the smaller distance is the one + // with positive sign on sqrt(quad) + + double d = (-k - sqrt(quad)) / a; + if (d < 0.0) return INFTY; + return d; + } +} + +void +SurfaceZCylinder::normal(double xyz[3], double uvw[3]) +{ + uvw[0] = 2.0 * (xyz[0] - x0); + uvw[1] = 2.0 * (xyz[1] - y0); + uvw[2] = 0.0; +} + +//============================================================================== + +extern "C" void +read_surfaces(pugi::xml_node *node) +{ + // Count the number of surfaces. + int n_surfaces = 0; + for (pugi::xml_node surf_node = node->child("surface"); surf_node; + surf_node = surf_node.next_sibling("surface")) + { + n_surfaces += 1; + } + + // Allocate the array of Surface pointers. + surfaces_c = new Surface* [n_surfaces]; + + // Loop over XML surface elements and populate the array. + { + pugi::xml_node surf_node; + int i_surf; + for (surf_node = node->child("surface"), i_surf = 0; surf_node; + surf_node = surf_node.next_sibling("surface"), i_surf++) + { + if (surf_node.attribute("type")) + { + const pugi::char_t *surf_type = surf_node.attribute("type").value(); + if (strcmp(surf_type, "z-cylinder") == 0) + { + surfaces_c[i_surf] = new SurfaceZCylinder(surf_node); + } + else if (strcmp(surf_type, "z-plane") == 0) + { + surfaces_c[i_surf] = new SurfaceZPlane(surf_node); + } + else + { + std::cout << "Call error or handle uppercase here!" << std::endl; + std::cout << surf_type << std::endl; + } + } + } + } +} + +//============================================================================== + +extern "C" bool +surface_sense(int surf_ind, double xyz[3], double uvw[3]) +{ + return surfaces_c[surf_ind]->sense(xyz, uvw); +} + +extern "C" double +surface_distance(int surf_ind, double xyz[3], double uvw[3], bool coincident) +{ + return surfaces_c[surf_ind]->distance(xyz, uvw, coincident); +} From c5708e6932b37f34a29274dbc6c98a34e74c66cf Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 18 Jan 2018 21:16:50 -0500 Subject: [PATCH 113/282] Move brackets to match proposed C++ styleguide --- src/surface_header.C | 90 +++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 60 deletions(-) diff --git a/src/surface_header.C b/src/surface_header.C index 08287da88..34ac7cf33 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -23,8 +23,7 @@ Surface **surfaces_c; // construct closed volumes (cells) //============================================================================== -class Surface -{ +class Surface { int id; // Unique ID int neighbor_pos[], // List of cells on positive side neighbor_neg[]; // List of cells on negative side @@ -42,34 +41,28 @@ public: }; bool -Surface::sense(double xyz[3], double uvw[3]) -{ +Surface::sense(double xyz[3], double uvw[3]) { // Evaluate the surface equation at the particle's coordinates to determine // which side the particle is on. const double f = evaluate(xyz); // Check which side of surface the point is on. bool s; - if (fabs(f) < FP_COINCIDENT) - { + if (fabs(f) < FP_COINCIDENT) { // Particle may be coincident with this surface. To determine the sense, we // look at the direction of the particle relative to the surface normal (by // default in the positive direction) via their dot product. double norm[3]; normal(xyz, norm); s = (uvw[0] * norm[0] + uvw[1] * norm[1] + uvw[2] * norm[2] > 0.0); - } - else - { + } else { s = (f > 0.0); } return s; } void -Surface::reflect(double xyz[3], double uvw[3]) -{ - +Surface::reflect(double xyz[3], double uvw[3]) { // Determine projection of direction onto normal and squared magnitude of // normal. double norm[3]; @@ -85,8 +78,7 @@ Surface::reflect(double xyz[3], double uvw[3]) //============================================================================== -class SurfaceZPlane : public Surface -{ +class SurfaceZPlane : public Surface { double z0; public: SurfaceZPlane(pugi::xml_node surf_node); @@ -95,26 +87,22 @@ public: void normal(double xyz[3], double uvw[3]); }; -SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) -{ +SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) { const char *coeffs = surf_node.attribute("coeffs").value(); int stat = sscanf(coeffs, "%lf", &z0); - if (stat != 1) - { + if (stat != 1) { std::cout << "Something went wrong reading surface coeffs!" << std::endl; } } double -SurfaceZPlane::evaluate(double xyz[3]) -{ +SurfaceZPlane::evaluate(double xyz[3]) { double f = xyz[2] - z0; return f; } double -SurfaceZPlane::distance(double xyz[3], double uvw[3], bool coincident) -{ +SurfaceZPlane::distance(double xyz[3], double uvw[3], bool coincident) { double f = z0 - xyz[2]; double d; if (coincident or fabs(f) < FP_COINCIDENT or uvw[2] == 0.0) @@ -130,8 +118,7 @@ SurfaceZPlane::distance(double xyz[3], double uvw[3], bool coincident) } void -SurfaceZPlane::normal(double xyz[3], double uvw[3]) -{ +SurfaceZPlane::normal(double xyz[3], double uvw[3]) { uvw[0] = 0.0; uvw[1] = 0.0; uvw[2] = 1.0; @@ -139,8 +126,7 @@ SurfaceZPlane::normal(double xyz[3], double uvw[3]) //============================================================================== -class SurfaceZCylinder : public Surface -{ +class SurfaceZCylinder : public Surface { double x0, y0, r; public: SurfaceZCylinder(pugi::xml_node surf_node); @@ -149,19 +135,16 @@ public: void normal(double xyz[3], double uvw[3]); }; -SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) -{ +SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) { const char *coeffs = surf_node.attribute("coeffs").value(); int stat = sscanf(coeffs, "%lf %lf %lf", &x0, &y0, &r); - if (stat != 3) - { + if (stat != 3) { std::cout << "Something went wrong reading surface coeffs!" << std::endl; } } double -SurfaceZCylinder::evaluate(double xyz[3]) -{ +SurfaceZCylinder::evaluate(double xyz[3]) { const double x = xyz[0] - x0; const double y = xyz[1] - y0; double f = x*x + y*y - r*r; @@ -169,8 +152,7 @@ SurfaceZCylinder::evaluate(double xyz[3]) } double -SurfaceZCylinder::distance(double xyz[3], double uvw[3], bool coincident) -{ +SurfaceZCylinder::distance(double xyz[3], double uvw[3], bool coincident) { double a = 1.0 - uvw[2]*uvw[2]; // u^2 + v^2 if (a == 0.0) return INFTY; @@ -181,32 +163,27 @@ SurfaceZCylinder::distance(double xyz[3], double uvw[3], bool coincident) double c = x*x + y*y - r*r; double quad = k*k - a*c; - if (quad < 0.0) - { + if (quad < 0.0) { // No intersection with cylinder. return INFTY; - } - else if (coincident or fabs(c) < FP_COINCIDENT) - { + + } else if (coincident or fabs(c) < FP_COINCIDENT) { // Particle is on the cylinder, thus one distance is positive/negative // and the other is zero. The sign of k determines if we are facing in or // out. if (k >= 0.0) return INFTY; else return (-k + sqrt(quad)) / a; - } - else if (c < 0.0) - { + + } else if (c < 0.0) { // Particle is inside the cylinder, thus one distance must be negative // and one must be positive. The positive distance will be the one with // negative sign on sqrt(quad) return (-k + sqrt(quad)) / a; - } - else - { + + } else { // Particle is outside the cylinder, thus both distances are either // positive or negative. If positive, the smaller distance is the one // with positive sign on sqrt(quad) - double d = (-k - sqrt(quad)) / a; if (d < 0.0) return INFTY; return d; @@ -214,8 +191,7 @@ SurfaceZCylinder::distance(double xyz[3], double uvw[3], bool coincident) } void -SurfaceZCylinder::normal(double xyz[3], double uvw[3]) -{ +SurfaceZCylinder::normal(double xyz[3], double uvw[3]) { uvw[0] = 2.0 * (xyz[0] - x0); uvw[1] = 2.0 * (xyz[1] - y0); uvw[2] = 0.0; @@ -224,13 +200,11 @@ SurfaceZCylinder::normal(double xyz[3], double uvw[3]) //============================================================================== extern "C" void -read_surfaces(pugi::xml_node *node) -{ +read_surfaces(pugi::xml_node *node) { // Count the number of surfaces. int n_surfaces = 0; for (pugi::xml_node surf_node = node->child("surface"); surf_node; - surf_node = surf_node.next_sibling("surface")) - { + surf_node = surf_node.next_sibling("surface")) { n_surfaces += 1; } @@ -242,10 +216,8 @@ read_surfaces(pugi::xml_node *node) pugi::xml_node surf_node; int i_surf; for (surf_node = node->child("surface"), i_surf = 0; surf_node; - surf_node = surf_node.next_sibling("surface"), i_surf++) - { - if (surf_node.attribute("type")) - { + surf_node = surf_node.next_sibling("surface"), i_surf++) { + if (surf_node.attribute("type")) { const pugi::char_t *surf_type = surf_node.attribute("type").value(); if (strcmp(surf_type, "z-cylinder") == 0) { @@ -268,13 +240,11 @@ read_surfaces(pugi::xml_node *node) //============================================================================== extern "C" bool -surface_sense(int surf_ind, double xyz[3], double uvw[3]) -{ +surface_sense(int surf_ind, double xyz[3], double uvw[3]) { return surfaces_c[surf_ind]->sense(xyz, uvw); } extern "C" double -surface_distance(int surf_ind, double xyz[3], double uvw[3], bool coincident) -{ +surface_distance(int surf_ind, double xyz[3], double uvw[3], bool coincident) { return surfaces_c[surf_ind]->distance(xyz, uvw, coincident); } From 4cd335f6a93b21d5010e9f9981093a05a5d8435e Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 11 Aug 2017 22:05:33 -0400 Subject: [PATCH 114/282] Add x-, y- planes and cylinders to C++ Introudce templated generic functions to reduce code repetition for surfaces --- src/surface_header.C | 358 +++++++++++++++++++++++++++++++++---------- 1 file changed, 276 insertions(+), 82 deletions(-) diff --git a/src/surface_header.C b/src/surface_header.C index 34ac7cf33..f62be77ab 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -33,36 +33,34 @@ class Surface { char name[104]; // User-defined name public: - bool sense(double xyz[3], double uvw[3]); - void reflect(double xyz[3], double uvw[3]); - virtual double evaluate(double xyz[3]) = 0; - virtual double distance(double xyz[3], double uvw[3], bool coincident) = 0; - virtual void normal(double xyz[3], double uvw[3]) = 0; + bool sense(const double xyz[3], const double uvw[3]) const; + void reflect(const double xyz[3], double uvw[3]) const; + virtual double evaluate(const double xyz[3]) const = 0; + virtual double distance(const double xyz[3], const double uvw[3], + bool coincident) const = 0; + virtual void normal(const double xyz[3], double uvw[3]) const = 0; }; bool -Surface::sense(double xyz[3], double uvw[3]) { +Surface::sense(const double xyz[3], const double uvw[3]) const { // Evaluate the surface equation at the particle's coordinates to determine // which side the particle is on. const double f = evaluate(xyz); // Check which side of surface the point is on. - bool s; if (fabs(f) < FP_COINCIDENT) { // Particle may be coincident with this surface. To determine the sense, we // look at the direction of the particle relative to the surface normal (by // default in the positive direction) via their dot product. double norm[3]; normal(xyz, norm); - s = (uvw[0] * norm[0] + uvw[1] * norm[1] + uvw[2] * norm[2] > 0.0); - } else { - s = (f > 0.0); + return uvw[0] * norm[0] + uvw[1] * norm[1] + uvw[2] * norm[2] > 0.0; } - return s; + return f > 0.0; } void -Surface::reflect(double xyz[3], double uvw[3]) { +Surface::reflect(const double xyz[3], double uvw[3]) const { // Determine projection of direction onto normal and squared magnitude of // normal. double norm[3]; @@ -76,15 +74,113 @@ Surface::reflect(double xyz[3], double uvw[3]) { uvw[2] -= 2.0 * projection / magnitude * norm[2]; } +//============================================================================== +// Generic functions for X-, Y-, and Z-, planes +//============================================================================== + +template double +axis_aligned_plane_evaluate(const double xyz[3], double offset) { + return xyz[i] - offset;} + +template double +axis_aligned_plane_distance(const double xyz[3], const double uvw[3], + bool coincident, double offset) { + const double f = offset - xyz[i]; + if (coincident or fabs(f) < FP_COINCIDENT or uvw[i] == 0.0) return INFTY; + const double d = f / uvw[i]; + if (d < 0.0) return INFTY; + return d; +} + +template void +axis_aligned_plane_normal(const double xyz[3], double uvw[3]) { + uvw[i1] = 1.0; + uvw[i2] = 0.0; + uvw[i3] = 0.0; +} + +//============================================================================== +// SurfaceXPlane +//============================================================================== + +class SurfaceXPlane : public Surface { + double x0; +public: + SurfaceXPlane(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + const bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; +}; + +SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) { + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf", &x0); + if (stat != 1) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +inline double SurfaceXPlane::evaluate(const double xyz[3]) const { + return axis_aligned_plane_evaluate<0>(xyz, x0); +} + +inline double SurfaceXPlane::distance(const double xyz[3], const double uvw[3], + bool coincident) const { + return axis_aligned_plane_distance<0>(xyz, uvw, coincident, x0); +} + +inline void SurfaceXPlane::normal(const double xyz[3], double uvw[3]) const { + axis_aligned_plane_normal<0, 1, 2>(xyz, uvw); +} + +//============================================================================== +// SurfaceYPlane +//============================================================================== + +class SurfaceYPlane : public Surface { + double y0; +public: + SurfaceYPlane(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; +}; + +SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) { + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf", &y0); + if (stat != 1) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +inline double SurfaceYPlane::evaluate(const double xyz[3]) const { + return axis_aligned_plane_evaluate<1>(xyz, y0); +} + +inline double SurfaceYPlane::distance(const double xyz[3], const double uvw[3], + bool coincident) const { + return axis_aligned_plane_distance<1>(xyz, uvw, coincident, y0); +} + +inline void SurfaceYPlane::normal(const double xyz[3], double uvw[3]) const { + axis_aligned_plane_normal<1, 0, 2>(xyz, uvw); +} + +//============================================================================== +// SurfaceZPlane //============================================================================== class SurfaceZPlane : public Surface { double z0; public: SurfaceZPlane(pugi::xml_node surf_node); - double evaluate(double xyz[3]); - double distance(double xyz[3], double uvw[3], bool coincident); - void normal(double xyz[3], double uvw[3]); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; }; SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) { @@ -95,73 +191,49 @@ SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) { } } -double -SurfaceZPlane::evaluate(double xyz[3]) { - double f = xyz[2] - z0; - return f; +inline double SurfaceZPlane::evaluate(const double xyz[3]) const { + return axis_aligned_plane_evaluate<2>(xyz, z0); } -double -SurfaceZPlane::distance(double xyz[3], double uvw[3], bool coincident) { - double f = z0 - xyz[2]; - double d; - if (coincident or fabs(f) < FP_COINCIDENT or uvw[2] == 0.0) - { - d = INFTY; - } - else - { - d = f / uvw[2]; - if (d < 0.0) d = INFTY; - } - return d; +inline double SurfaceZPlane::distance(const double xyz[3], const double uvw[3], + bool coincident) const { + return axis_aligned_plane_distance<2>(xyz, uvw, coincident, z0); } -void -SurfaceZPlane::normal(double xyz[3], double uvw[3]) { - uvw[0] = 0.0; - uvw[1] = 0.0; - uvw[2] = 1.0; +inline void SurfaceZPlane::normal(const double xyz[3], double uvw[3]) const { + axis_aligned_plane_normal<2, 0, 1>(xyz, uvw); } +//============================================================================== +// SurfacePlane //============================================================================== -class SurfaceZCylinder : public Surface { - double x0, y0, r; -public: - SurfaceZCylinder(pugi::xml_node surf_node); - double evaluate(double xyz[3]); - double distance(double xyz[3], double uvw[3], bool coincident); - void normal(double xyz[3], double uvw[3]); -}; +// TODO -SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf %lf %lf", &x0, &y0, &r); - if (stat != 3) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } +//============================================================================== +// Generic functions for X-, Y-, and Z-, cylinders +//============================================================================== + +template double +axis_aligned_cylinder_evaluate(const double xyz[3], double offset1, + double offset2, double radius) { + const double xyz1 = xyz[i1] - offset1; + const double xyz2 = xyz[i2] - offset2; + return xyz1*xyz1 + xyz2*xyz2 - radius*radius; } -double -SurfaceZCylinder::evaluate(double xyz[3]) { - const double x = xyz[0] - x0; - const double y = xyz[1] - y0; - double f = x*x + y*y - r*r; - return f; -} - -double -SurfaceZCylinder::distance(double xyz[3], double uvw[3], bool coincident) { - double a = 1.0 - uvw[2]*uvw[2]; // u^2 + v^2 +template double +axis_aligned_cylinder_distance(const double xyz[3], const double uvw[3], + double offset1, double offset2, double radius, bool coincident) { + const double a = 1.0 - uvw[i1]*uvw[i1]; // u^2 + v^2 if (a == 0.0) return INFTY; - double x = xyz[0] - x0; - double y = xyz[1] - y0; - double k = x*uvw[0] + y*uvw[1]; - double c = x*x + y*y - r*r; - double quad = k*k - a*c; + const double xyz2 = xyz[i2] - offset1; + const double xyz3 = xyz[i3] - offset2; + const double k = xyz2 * uvw[i2] + xyz3 * uvw[i3]; + const double c = xyz2*xyz2 + xyz3*xyz3 - radius*radius; + const double quad = k*k - a*c; if (quad < 0.0) { // No intersection with cylinder. @@ -190,11 +262,123 @@ SurfaceZCylinder::distance(double xyz[3], double uvw[3], bool coincident) { } } -void -SurfaceZCylinder::normal(double xyz[3], double uvw[3]) { - uvw[0] = 2.0 * (xyz[0] - x0); - uvw[1] = 2.0 * (xyz[1] - y0); - uvw[2] = 0.0; +template void +axis_aligned_cylinder_normal(const double xyz[3], double uvw[3], double offset1, + double offset2) { + uvw[i2] = 2.0 * (xyz[i2] - offset1); + uvw[i3] = 2.0 * (xyz[i3] - offset2); + uvw[i1] = 0.0; +} + +//============================================================================== +// SurfaceXCylinder +//============================================================================== + +class SurfaceXCylinder : public Surface { + double y0, z0, r; +public: + SurfaceXCylinder(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; +}; + +SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node) { + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf %lf %lf", &y0, &z0, &r); + if (stat != 3) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +inline double +SurfaceXCylinder::evaluate(const double xyz[3]) const { + return axis_aligned_cylinder_evaluate<1, 2>(xyz, y0, z0, r); +} + +inline double SurfaceXCylinder::distance(const double xyz[3], + const double uvw[3], bool coincident) const { + return axis_aligned_cylinder_distance<0, 1, 2>(xyz, uvw, coincident, y0, z0, + r); +} + +inline void SurfaceXCylinder::normal(const double xyz[3], double uvw[3]) const { + axis_aligned_cylinder_normal<0, 1, 2>(xyz, uvw, y0, z0); +} + +//============================================================================== +// SurfaceYCylinder +//============================================================================== + +class SurfaceYCylinder : public Surface { + double x0, z0, r; +public: + SurfaceYCylinder(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; +}; + +SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node) { + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf %lf %lf", &x0, &z0, &r); + if (stat != 3) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +inline double +SurfaceYCylinder::evaluate(const double xyz[3]) const { + return axis_aligned_cylinder_evaluate<0, 2>(xyz, x0, z0, r); +} + +inline double SurfaceYCylinder::distance(const double xyz[3], + const double uvw[3], bool coincident) const { + return axis_aligned_cylinder_distance<1, 0, 2>(xyz, uvw, coincident, x0, z0, + r); +} + +inline void SurfaceYCylinder::normal(const double xyz[3], double uvw[3]) const { + axis_aligned_cylinder_normal<1, 0, 2>(xyz, uvw, x0, z0); +} + +//============================================================================== +// SurfaceZCylinder +//============================================================================== + +class SurfaceZCylinder : public Surface { + double x0, y0, r; +public: + SurfaceZCylinder(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; +}; + +SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) { + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf %lf %lf", &x0, &y0, &r); + if (stat != 3) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +inline double +SurfaceZCylinder::evaluate(const double xyz[3]) const { + return axis_aligned_cylinder_evaluate<0, 1>(xyz, x0, y0, r); +} + +inline double SurfaceZCylinder::distance(const double xyz[3], + const double uvw[3], bool coincident) const { + return axis_aligned_cylinder_distance<2, 0, 1>(xyz, uvw, coincident, x0, y0, + r); +} + +inline void SurfaceZCylinder::normal(const double xyz[3], double uvw[3]) const { + axis_aligned_cylinder_normal<2, 0, 1>(xyz, uvw, x0, y0); } //============================================================================== @@ -219,16 +403,26 @@ read_surfaces(pugi::xml_node *node) { surf_node = surf_node.next_sibling("surface"), i_surf++) { if (surf_node.attribute("type")) { const pugi::char_t *surf_type = surf_node.attribute("type").value(); - if (strcmp(surf_type, "z-cylinder") == 0) - { - surfaces_c[i_surf] = new SurfaceZCylinder(surf_node); - } - else if (strcmp(surf_type, "z-plane") == 0) - { + + if (strcmp(surf_type, "x-plane") == 0) { + surfaces_c[i_surf] = new SurfaceXPlane(surf_node); + + } else if (strcmp(surf_type, "y-plane") == 0) { + surfaces_c[i_surf] = new SurfaceYPlane(surf_node); + + } else if (strcmp(surf_type, "z-plane") == 0) { surfaces_c[i_surf] = new SurfaceZPlane(surf_node); - } - else - { + + } else if (strcmp(surf_type, "x-cylinder") == 0) { + surfaces_c[i_surf] = new SurfaceXCylinder(surf_node); + + } else if (strcmp(surf_type, "y-cylinder") == 0) { + surfaces_c[i_surf] = new SurfaceYCylinder(surf_node); + + } else if (strcmp(surf_type, "z-cylinder") == 0) { + surfaces_c[i_surf] = new SurfaceZCylinder(surf_node); + + } else { std::cout << "Call error or handle uppercase here!" << std::endl; std::cout << surf_type << std::endl; } From 303ee3486e001e7ea3452775fc4ee142022b7320 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 11 Aug 2017 22:38:38 -0400 Subject: [PATCH 115/282] Add SurfaceSphere to C++ --- src/surface_header.C | 113 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 5 deletions(-) diff --git a/src/surface_header.C b/src/surface_header.C index f62be77ab..42fb948af 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -243,20 +243,23 @@ axis_aligned_cylinder_distance(const double xyz[3], const double uvw[3], // Particle is on the cylinder, thus one distance is positive/negative // and the other is zero. The sign of k determines if we are facing in or // out. - if (k >= 0.0) return INFTY; - else return (-k + sqrt(quad)) / a; + if (k >= 0.0) { + return INFTY; + } else { + return (-k + sqrt(quad)) / a; + } } else if (c < 0.0) { // Particle is inside the cylinder, thus one distance must be negative // and one must be positive. The positive distance will be the one with - // negative sign on sqrt(quad) + // negative sign on sqrt(quad). return (-k + sqrt(quad)) / a; } else { // Particle is outside the cylinder, thus both distances are either // positive or negative. If positive, the smaller distance is the one - // with positive sign on sqrt(quad) - double d = (-k - sqrt(quad)) / a; + // with positive sign on sqrt(quad). + const double d = (-k - sqrt(quad)) / a; if (d < 0.0) return INFTY; return d; } @@ -381,6 +384,103 @@ inline void SurfaceZCylinder::normal(const double xyz[3], double uvw[3]) const { axis_aligned_cylinder_normal<2, 0, 1>(xyz, uvw, x0, y0); } +//============================================================================== +// SurfaceSphere +//============================================================================== + +class SurfaceSphere : public Surface { + double x0, y0, z0, r; +public: + SurfaceSphere(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; +}; + +SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) { + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf %lf %lf %lf", &x0, &y0, &z0, &r); + if (stat != 4) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +double SurfaceSphere::evaluate(const double xyz[3]) const { + const double x = xyz[0] - x0; + const double y = xyz[1] - y0; + const double z = xyz[2] - z0; + return x*x + y*y + z*z - r*r; +} + +double SurfaceSphere::distance(const double xyz[3], const double uvw[3], + bool coincident) const { + const double x = xyz[0] - x0; + const double y = xyz[1] - y0; + const double z = xyz[2] - z0; + const double k = x*uvw[0] + y*uvw[1] + z*uvw[2]; + const double c = x*x + y*y + z*z - r*r; + const double quad = k*k - c; + + if (quad < 0.0) { + // No intersection with sphere. + return INFTY; + + } else if (coincident or fabs(c) < FP_COINCIDENT) { + // Particle is on the sphere, thus one distance is positive/negative and + // the other is zero. The sign of k determines if we are facing in or out. + if (k >= 0.0) { + return INFTY; + } else { + return -k + sqrt(quad); + } + + } else if (c < 0.0) { + // Particle is inside the sphere, thus one distance must be negative and + // one must be positive. The positive distance will be the one with + // negative sign on sqrt(quad) + return -k + sqrt(quad); + + } else { + // Particle is outside the sphere, thus both distances are either positive + // or negative. If positive, the smaller distance is the one with positive + // sign on sqrt(quad). + const double d = -k - sqrt(quad); + if (d < 0.0) return INFTY; + return d; + } +} + +inline void SurfaceSphere::normal(const double xyz[3], double uvw[3]) const { + uvw[0] = 2.0 * (xyz[0] - x0); + uvw[1] = 2.0 * (xyz[1] - y0); + uvw[2] = 2.0 * (xyz[2] - z0); +} + +//============================================================================== +// SurfaceXCone +//============================================================================== + +// TODO + +//============================================================================== +// SurfaceYCone +//============================================================================== + +// TODO + +//============================================================================== +// SurfaceZCone +//============================================================================== + +// TODO + +//============================================================================== +// SurfaceQuadric +//============================================================================== + +// TODO + //============================================================================== extern "C" void @@ -422,6 +522,9 @@ read_surfaces(pugi::xml_node *node) { } else if (strcmp(surf_type, "z-cylinder") == 0) { surfaces_c[i_surf] = new SurfaceZCylinder(surf_node); + } else if (strcmp(surf_type, "sphere") == 0) { + surfaces_c[i_surf] = new SurfaceSphere(surf_node); + } else { std::cout << "Call error or handle uppercase here!" << std::endl; std::cout << surf_type << std::endl; From 2d0938284b28db1e007797c2ad5368404d2fd6f2 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 12 Aug 2017 17:29:44 -0400 Subject: [PATCH 116/282] Add remaining surfaces to C++ --- src/surface_header.C | 355 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 340 insertions(+), 15 deletions(-) diff --git a/src/surface_header.C b/src/surface_header.C index 42fb948af..397be2cff 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -75,7 +75,7 @@ Surface::reflect(const double xyz[3], double uvw[3]) const { } //============================================================================== -// Generic functions for X-, Y-, and Z-, planes +// Generic functions for x-, y-, and z-, planes //============================================================================== template double @@ -104,6 +104,7 @@ axis_aligned_plane_normal(const double xyz[3], double uvw[3]) { //============================================================================== class SurfaceXPlane : public Surface { + // x = x0 double x0; public: SurfaceXPlane(pugi::xml_node surf_node); @@ -139,6 +140,7 @@ inline void SurfaceXPlane::normal(const double xyz[3], double uvw[3]) const { //============================================================================== class SurfaceYPlane : public Surface { + // y = y0 double y0; public: SurfaceYPlane(pugi::xml_node surf_node); @@ -174,6 +176,7 @@ inline void SurfaceYPlane::normal(const double xyz[3], double uvw[3]) const { //============================================================================== class SurfaceZPlane : public Surface { + // z = z0 double z0; public: SurfaceZPlane(pugi::xml_node surf_node); @@ -208,10 +211,53 @@ inline void SurfaceZPlane::normal(const double xyz[3], double uvw[3]) const { // SurfacePlane //============================================================================== -// TODO +class SurfacePlane : public Surface { + // Ax + By + Cz = D + double A, B, C, D; +public: + SurfacePlane(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + const bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; +}; + +SurfacePlane::SurfacePlane(pugi::xml_node surf_node) { + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf %lf %lf %lf", &A, &B, &C, &D); + if (stat != 4) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +double +SurfacePlane::evaluate(const double xyz[3]) const { + return A*xyz[0] + B*xyz[1] + C*xyz[2] - D; +} + +double +SurfacePlane::distance(const double xyz[3], const double uvw[3], + bool coincident) const { + const double f = A*xyz[0] + B*xyz[1] + C*xyz[2] - D; + const double projection = A*uvw[0] + B*uvw[1] + C*uvw[2]; + if (coincident or fabs(f) < FP_COINCIDENT or projection == 0.0) { + return INFTY; + } else { + const double d = -f / projection; + if (d < 0.0) return INFTY; + return d; + } +} + +void +SurfacePlane::normal(const double xyz[3], double uvw[3]) const { + uvw[0] = A; + uvw[1] = B; + uvw[2] = C; +} //============================================================================== -// Generic functions for X-, Y-, and Z-, cylinders +// Generic functions for x-, y-, and z-, cylinders //============================================================================== template double @@ -224,9 +270,8 @@ axis_aligned_cylinder_evaluate(const double xyz[3], double offset1, template double axis_aligned_cylinder_distance(const double xyz[3], const double uvw[3], - double offset1, double offset2, double radius, bool coincident) { + bool coincident, double offset1, double offset2, double radius) { const double a = 1.0 - uvw[i1]*uvw[i1]; // u^2 + v^2 - if (a == 0.0) return INFTY; const double xyz2 = xyz[i2] - offset1; @@ -277,7 +322,10 @@ axis_aligned_cylinder_normal(const double xyz[3], double uvw[3], double offset1, // SurfaceXCylinder //============================================================================== +// TODO: Test this implementation! + class SurfaceXCylinder : public Surface { + // (y - y0)^2 + (z - z0)^2 = R^2 double y0, z0, r; public: SurfaceXCylinder(pugi::xml_node surf_node); @@ -295,8 +343,7 @@ SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node) { } } -inline double -SurfaceXCylinder::evaluate(const double xyz[3]) const { +inline double SurfaceXCylinder::evaluate(const double xyz[3]) const { return axis_aligned_cylinder_evaluate<1, 2>(xyz, y0, z0, r); } @@ -314,7 +361,10 @@ inline void SurfaceXCylinder::normal(const double xyz[3], double uvw[3]) const { // SurfaceYCylinder //============================================================================== +// TODO: Test this implementation! + class SurfaceYCylinder : public Surface { + // (x - x0)^2 + (z - z0)^2 = R^2 double x0, z0, r; public: SurfaceYCylinder(pugi::xml_node surf_node); @@ -332,8 +382,7 @@ SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node) { } } -inline double -SurfaceYCylinder::evaluate(const double xyz[3]) const { +inline double SurfaceYCylinder::evaluate(const double xyz[3]) const { return axis_aligned_cylinder_evaluate<0, 2>(xyz, x0, z0, r); } @@ -352,6 +401,7 @@ inline void SurfaceYCylinder::normal(const double xyz[3], double uvw[3]) const { //============================================================================== class SurfaceZCylinder : public Surface { + // (x - x0)^2 + (y - y0)^2 = R^2 double x0, y0, r; public: SurfaceZCylinder(pugi::xml_node surf_node); @@ -369,8 +419,7 @@ SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) { } } -inline double -SurfaceZCylinder::evaluate(const double xyz[3]) const { +inline double SurfaceZCylinder::evaluate(const double xyz[3]) const { return axis_aligned_cylinder_evaluate<0, 1>(xyz, x0, y0, r); } @@ -389,6 +438,7 @@ inline void SurfaceZCylinder::normal(const double xyz[3], double uvw[3]) const { //============================================================================== class SurfaceSphere : public Surface { + // (x - x0)^2 + (y - y0)^2 + (z - z0)^2 = R^2 double x0, y0, z0, r; public: SurfaceSphere(pugi::xml_node surf_node); @@ -457,29 +507,289 @@ inline void SurfaceSphere::normal(const double xyz[3], double uvw[3]) const { uvw[2] = 2.0 * (xyz[2] - z0); } +//============================================================================== +// Generic functions for x-, y-, and z-, cones +//============================================================================== + +template double +axis_aligned_cone_evaluate(const double xyz[3], double offset1, + double offset2, double offset3, double radius_sq) { + const double xyz1 = xyz[i1] - offset1; + const double xyz2 = xyz[i2] - offset2; + const double xyz3 = xyz[i3] - offset3; + return xyz2*xyz2 + xyz3*xyz3 - radius_sq*xyz1*xyz1; +} + +template double +axis_aligned_cone_distance(const double xyz[3], const double uvw[3], + bool coincident, double offset1, double offset2, double offset3, + double radius_sq) { + const double xyz1 = xyz[i1] - offset1; + const double xyz2 = xyz[i2] - offset2; + const double xyz3 = xyz[i3] - offset3; + const double a = uvw[i2]*uvw[i2] + uvw[i3]*uvw[i3] + - radius_sq*uvw[i1]*uvw[i1]; + const double k = xyz2*uvw[i2] + xyz3*uvw[i3] - radius_sq*xyz1*uvw[i1]; + const double c = xyz2*xyz2 + xyz3*xyz3 - radius_sq*xyz1*xyz1; + double quad = k*k - a*c; + + double d; + + if (quad < 0.0) { + // No intersection with cone. + return INFTY; + + } else if (coincident or fabs(c) < FP_COINCIDENT) { + // Particle is on the cone, thus one distance is positive/negative + // and the other is zero. The sign of k determines if we are facing in or + // out. + if (k >= 0.0) { + d = (-k - sqrt(quad)) / a; + } else { + d = (-k + sqrt(quad)) / a; + } + + } else { + // Calculate both solutions to the quadratic. + quad = sqrt(quad); + d = (-k - quad) / a; + const double b = (-k + quad) / a; + + // Determine the smallest positive solution. + if (d < 0.0) { + if (b > 0.0) d = b; + } else { + if (b > 0.0) { + if (b < d) d = b; + } + } + } + + // If the distance was negative, set boundary distance to infinity. + if (d <= 0.0) return INFTY; + return d; +} + +template void +axis_aligned_cone_normal(const double xyz[3], double uvw[3], double offset1, + double offset2, double offset3, double radius_sq) { + uvw[i1] = -2.0 * radius_sq * (xyz[i1] - offset1); + uvw[i2] = 2.0 * (xyz[i2] - offset2); + uvw[i3] = 2.0 * (xyz[i3] - offset3); +} + //============================================================================== // SurfaceXCone //============================================================================== -// TODO +// TODO: Test this implementation! + +class SurfaceXCone : public Surface { + // (y - y0)^2 + (z - z0)^2 = R^2*(x - x0)^2 + double x0, y0, z0, r_sq; +public: + SurfaceXCone(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; +}; + +SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) { + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf %lf %lf %lf", &x0, &y0, &z0, &r_sq); + if (stat != 4) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +inline double SurfaceXCone::evaluate(const double xyz[3]) const { + return axis_aligned_cone_evaluate<0, 1, 2>(xyz, x0, y0, z0, r_sq); +} + +inline double SurfaceXCone::distance(const double xyz[3], + const double uvw[3], bool coincident) const { + return axis_aligned_cone_distance<0, 1, 2>(xyz, uvw, coincident, x0, y0, z0, + r_sq); +} + +inline void SurfaceXCone::normal(const double xyz[3], double uvw[3]) const { + axis_aligned_cone_normal<0, 1, 2>(xyz, uvw, x0, y0, z0, r_sq); +} //============================================================================== // SurfaceYCone //============================================================================== -// TODO +// TODO: Test this implementation! + +class SurfaceYCone : public Surface { + // (x - x0)^2 + (z - z0)^2 = R^2*(y - y0)^2 + double x0, y0, z0, r_sq; +public: + SurfaceYCone(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; +}; + +SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) { + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf %lf %lf %lf", &x0, &y0, &z0, &r_sq); + if (stat != 4) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +inline double SurfaceYCone::evaluate(const double xyz[3]) const { + return axis_aligned_cone_evaluate<1, 0, 2>(xyz, y0, x0, z0, r_sq); +} + +inline double SurfaceYCone::distance(const double xyz[3], + const double uvw[3], bool coincident) const { + return axis_aligned_cone_distance<1, 0, 2>(xyz, uvw, coincident, y0, x0, z0, + r_sq); +} + +inline void SurfaceYCone::normal(const double xyz[3], double uvw[3]) const { + axis_aligned_cone_normal<1, 0, 2>(xyz, uvw, y0, x0, z0, r_sq); +} //============================================================================== // SurfaceZCone //============================================================================== -// TODO +class SurfaceZCone : public Surface { + // (x - x0)^2 + (y - y0)^2 = R^2*(z - z0)^2 + double x0, y0, z0, r_sq; +public: + SurfaceZCone(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; +}; + +SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) { + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf %lf %lf %lf", &x0, &y0, &z0, &r_sq); + if (stat != 4) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +inline double SurfaceZCone::evaluate(const double xyz[3]) const { + return axis_aligned_cone_evaluate<2, 0, 1>(xyz, z0, x0, y0, r_sq); +} + +inline double SurfaceZCone::distance(const double xyz[3], + const double uvw[3], bool coincident) const { + return axis_aligned_cone_distance<2, 0, 1>(xyz, uvw, coincident, z0, x0, y0, + r_sq); +} + +inline void SurfaceZCone::normal(const double xyz[3], double uvw[3]) const { + axis_aligned_cone_normal<2, 0, 1>(xyz, uvw, z0, x0, y0, r_sq); +} //============================================================================== // SurfaceQuadric //============================================================================== -// TODO +class SurfaceQuadric : public Surface { + // Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0 + double A, B, C, D, E, F, G, H, J, K; +public: + SurfaceQuadric(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; +}; + +SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) { + const char *coeffs = surf_node.attribute("coeffs").value(); + int stat = sscanf(coeffs, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", + &A, &B, &C, &D, &E, &F, &G, &H, &J, &K); + if (stat != 10) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +double +SurfaceQuadric::evaluate(const double xyz[3]) const { + const double &x = xyz[0]; + const double &y = xyz[1]; + const double &z = xyz[2]; + return x*(A*x + D*y + G) + + y*(B*y + E*z + H) + + z*(C*z + F*x + J) + K; +} + +double +SurfaceQuadric::distance(const double xyz[3], + const double uvw[3], bool coincident) const { + const double &x = xyz[0]; + const double &y = xyz[1]; + const double &z = xyz[2]; + const double &u = uvw[0]; + const double &v = uvw[1]; + const double &w = uvw[2]; + + const double a = A*u*u + B*v*v + C*w*w + D*u*v + E*v*w + F*u*w; + const double k = (A*u*x + B*v*y + C*w*z + 0.5*(D*(u*y + v*x) + + E*(v*z + w*y) + F*(w*x + u*z) + G*u + H*v + J*w)); + const double c = A*x*x + B*y*y + C*z*z + D*x*y + E*y*z + F*x*z + G*x + H*y + + J*z + K; + double quad = k*k - a*c; + + double d; + + if (quad < 0.0) { + // No intersection with surface. + return INFTY; + + } else if (coincident or fabs(c) < FP_COINCIDENT) { + // Particle is on the surface, thus one distance is positive/negative and + // the other is zero. The sign of k determines which distance is zero and + // which is not. + if (k >= 0.0) { + d = (-k - sqrt(quad)) / a; + } else { + d = (-k + sqrt(quad)) / a; + } + + } else { + // Calculate both solutions to the quadratic. + quad = sqrt(quad); + d = (-k - quad) / a; + double b = (-k + quad) / a; + + // Determine the smallest positive solution. + if (d < 0.0) { + if (b > 0.0) d = b; + } else { + if (b > 0.0) { + if (b < d) d = b; + } + } + } + + // If the distance was negative, set boundary distance to infinity. + if (d <= 0.0) return INFTY; + return d; +} + +void +SurfaceQuadric::normal(const double xyz[3], double uvw[3]) const { + const double &x = xyz[0]; + const double &y = xyz[1]; + const double &z = xyz[2]; + uvw[0] = 2.0*A*x + D*y + F*z + G; + uvw[1] = 2.0*B*y + D*x + E*z + H; + uvw[2] = 2.0*C*z + E*y + F*x + J; +} //============================================================================== @@ -513,6 +823,9 @@ read_surfaces(pugi::xml_node *node) { } else if (strcmp(surf_type, "z-plane") == 0) { surfaces_c[i_surf] = new SurfaceZPlane(surf_node); + } else if (strcmp(surf_type, "plane") == 0) { + surfaces_c[i_surf] = new SurfacePlane(surf_node); + } else if (strcmp(surf_type, "x-cylinder") == 0) { surfaces_c[i_surf] = new SurfaceXCylinder(surf_node); @@ -525,6 +838,18 @@ read_surfaces(pugi::xml_node *node) { } else if (strcmp(surf_type, "sphere") == 0) { surfaces_c[i_surf] = new SurfaceSphere(surf_node); + } else if (strcmp(surf_type, "x-cone") == 0) { + surfaces_c[i_surf] = new SurfaceXCone(surf_node); + + } else if (strcmp(surf_type, "y-cone") == 0) { + surfaces_c[i_surf] = new SurfaceYCone(surf_node); + + } else if (strcmp(surf_type, "z-cone") == 0) { + surfaces_c[i_surf] = new SurfaceZCone(surf_node); + + } else if (strcmp(surf_type, "quadric") == 0) { + surfaces_c[i_surf] = new SurfaceQuadric(surf_node); + } else { std::cout << "Call error or handle uppercase here!" << std::endl; std::cout << surf_type << std::endl; From d4a1f1834b42c78f574525e564a09a537071b3ca Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 2 Sep 2017 18:44:06 -0400 Subject: [PATCH 117/282] Handle both XML nodes and attributes in C++ surfs --- src/surface_header.C | 213 +++++++++++++++++++++++++------------------ 1 file changed, 123 insertions(+), 90 deletions(-) diff --git a/src/surface_header.C b/src/surface_header.C index 397be2cff..3ac3d3c53 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -18,6 +18,82 @@ const double INFTY = std::numeric_limits::max(); class Surface; Surface **surfaces_c; +//============================================================================== +// Helper functions +//============================================================================== + +void read_coeffs(pugi::xml_node surf_node, double &c1) +{ + const char *coeffs; + if (surf_node.attribute("coeffs")) { + coeffs = surf_node.attribute("coeffs").value(); + } else if (surf_node.child("coeffs")) { + coeffs = surf_node.child_value("coeffs"); + } else { + std::cout << "ERROR: Found a surface with no coefficients" << std::endl; + } + + int stat = sscanf(coeffs, "%lf", &c1); + if (stat != 1) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3) +{ + const char *coeffs; + if (surf_node.attribute("coeffs")) { + coeffs = surf_node.attribute("coeffs").value(); + } else if (surf_node.child("coeffs")) { + coeffs = surf_node.child_value("coeffs"); + } else { + std::cout << "ERROR: Found a surface with no coefficients" << std::endl; + } + + int stat = sscanf(coeffs, "%lf %lf %lf", &c1, &c2, &c3); + if (stat != 3) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3, + double &c4) +{ + const char *coeffs; + if (surf_node.attribute("coeffs")) { + coeffs = surf_node.attribute("coeffs").value(); + } else if (surf_node.child("coeffs")) { + coeffs = surf_node.child_value("coeffs"); + } else { + std::cout << "ERROR: Found a surface with no coefficients" << std::endl; + } + + int stat = sscanf(coeffs, "%lf %lf %lf %lf", &c1, &c2, &c3, &c4); + if (stat != 4) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + +void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3, + double &c4, double &c5, double &c6, double &c7, double &c8, + double &c9, double &c10) +{ + const char *coeffs; + if (surf_node.attribute("coeffs")) { + coeffs = surf_node.attribute("coeffs").value(); + } else if (surf_node.child("coeffs")) { + coeffs = surf_node.child_value("coeffs"); + } else { + std::cout << "ERROR: Found a surface with no coefficients" << std::endl; + } + + int stat = sscanf(coeffs, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", + &c1, &c2, &c3, &c4, &c5, &c6, &c7, &c8, &c9, &c10); + if (stat != 10) { + std::cout << "Something went wrong reading surface coeffs!" << std::endl; + } +} + //============================================================================== // SURFACE type defines a first- or second-order surface that can be used to // construct closed volumes (cells) @@ -115,11 +191,7 @@ public: }; SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf", &x0); - if (stat != 1) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, x0); } inline double SurfaceXPlane::evaluate(const double xyz[3]) const { @@ -151,11 +223,7 @@ public: }; SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf", &y0); - if (stat != 1) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, y0); } inline double SurfaceYPlane::evaluate(const double xyz[3]) const { @@ -187,11 +255,7 @@ public: }; SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf", &z0); - if (stat != 1) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, z0); } inline double SurfaceZPlane::evaluate(const double xyz[3]) const { @@ -223,11 +287,7 @@ public: }; SurfacePlane::SurfacePlane(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf %lf %lf %lf", &A, &B, &C, &D); - if (stat != 4) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, A, B, C, D); } double @@ -336,11 +396,7 @@ public: }; SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf %lf %lf", &y0, &z0, &r); - if (stat != 3) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, y0, z0, r); } inline double SurfaceXCylinder::evaluate(const double xyz[3]) const { @@ -375,11 +431,7 @@ public: }; SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf %lf %lf", &x0, &z0, &r); - if (stat != 3) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, x0, z0, r); } inline double SurfaceYCylinder::evaluate(const double xyz[3]) const { @@ -412,11 +464,7 @@ public: }; SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf %lf %lf", &x0, &y0, &r); - if (stat != 3) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, x0, y0, r); } inline double SurfaceZCylinder::evaluate(const double xyz[3]) const { @@ -449,11 +497,7 @@ public: }; SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf %lf %lf %lf", &x0, &y0, &z0, &r); - if (stat != 4) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, x0, y0, z0, r); } double SurfaceSphere::evaluate(const double xyz[3]) const { @@ -596,11 +640,7 @@ public: }; SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf %lf %lf %lf", &x0, &y0, &z0, &r_sq); - if (stat != 4) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, x0, y0, z0, r_sq); } inline double SurfaceXCone::evaluate(const double xyz[3]) const { @@ -635,11 +675,7 @@ public: }; SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf %lf %lf %lf", &x0, &y0, &z0, &r_sq); - if (stat != 4) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, x0, y0, z0, r_sq); } inline double SurfaceYCone::evaluate(const double xyz[3]) const { @@ -672,11 +708,7 @@ public: }; SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf %lf %lf %lf", &x0, &y0, &z0, &r_sq); - if (stat != 4) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, x0, y0, z0, r_sq); } inline double SurfaceZCone::evaluate(const double xyz[3]) const { @@ -709,12 +741,7 @@ public: }; SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) { - const char *coeffs = surf_node.attribute("coeffs").value(); - int stat = sscanf(coeffs, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", - &A, &B, &C, &D, &E, &F, &G, &H, &J, &K); - if (stat != 10) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; - } + read_coeffs(surf_node, A, B, C, D, E, F, G, H, J, K); } double @@ -811,49 +838,55 @@ read_surfaces(pugi::xml_node *node) { int i_surf; for (surf_node = node->child("surface"), i_surf = 0; surf_node; surf_node = surf_node.next_sibling("surface"), i_surf++) { + const pugi::char_t *surf_type; if (surf_node.attribute("type")) { - const pugi::char_t *surf_type = surf_node.attribute("type").value(); + surf_type = surf_node.attribute("type").value(); + } else if (surf_node.child("type")) { + surf_type = surf_node.child_value("type"); + } else { + std::cout << "ERROR: Found a surface with no type attribute/node" + << std::endl; + } - if (strcmp(surf_type, "x-plane") == 0) { - surfaces_c[i_surf] = new SurfaceXPlane(surf_node); + if (strcmp(surf_type, "x-plane") == 0) { + surfaces_c[i_surf] = new SurfaceXPlane(surf_node); - } else if (strcmp(surf_type, "y-plane") == 0) { - surfaces_c[i_surf] = new SurfaceYPlane(surf_node); + } else if (strcmp(surf_type, "y-plane") == 0) { + surfaces_c[i_surf] = new SurfaceYPlane(surf_node); - } else if (strcmp(surf_type, "z-plane") == 0) { - surfaces_c[i_surf] = new SurfaceZPlane(surf_node); + } else if (strcmp(surf_type, "z-plane") == 0) { + surfaces_c[i_surf] = new SurfaceZPlane(surf_node); - } else if (strcmp(surf_type, "plane") == 0) { - surfaces_c[i_surf] = new SurfacePlane(surf_node); + } else if (strcmp(surf_type, "plane") == 0) { + surfaces_c[i_surf] = new SurfacePlane(surf_node); - } else if (strcmp(surf_type, "x-cylinder") == 0) { - surfaces_c[i_surf] = new SurfaceXCylinder(surf_node); + } else if (strcmp(surf_type, "x-cylinder") == 0) { + surfaces_c[i_surf] = new SurfaceXCylinder(surf_node); - } else if (strcmp(surf_type, "y-cylinder") == 0) { - surfaces_c[i_surf] = new SurfaceYCylinder(surf_node); + } else if (strcmp(surf_type, "y-cylinder") == 0) { + surfaces_c[i_surf] = new SurfaceYCylinder(surf_node); - } else if (strcmp(surf_type, "z-cylinder") == 0) { - surfaces_c[i_surf] = new SurfaceZCylinder(surf_node); + } else if (strcmp(surf_type, "z-cylinder") == 0) { + surfaces_c[i_surf] = new SurfaceZCylinder(surf_node); - } else if (strcmp(surf_type, "sphere") == 0) { - surfaces_c[i_surf] = new SurfaceSphere(surf_node); + } else if (strcmp(surf_type, "sphere") == 0) { + surfaces_c[i_surf] = new SurfaceSphere(surf_node); - } else if (strcmp(surf_type, "x-cone") == 0) { - surfaces_c[i_surf] = new SurfaceXCone(surf_node); + } else if (strcmp(surf_type, "x-cone") == 0) { + surfaces_c[i_surf] = new SurfaceXCone(surf_node); - } else if (strcmp(surf_type, "y-cone") == 0) { - surfaces_c[i_surf] = new SurfaceYCone(surf_node); + } else if (strcmp(surf_type, "y-cone") == 0) { + surfaces_c[i_surf] = new SurfaceYCone(surf_node); - } else if (strcmp(surf_type, "z-cone") == 0) { - surfaces_c[i_surf] = new SurfaceZCone(surf_node); + } else if (strcmp(surf_type, "z-cone") == 0) { + surfaces_c[i_surf] = new SurfaceZCone(surf_node); - } else if (strcmp(surf_type, "quadric") == 0) { - surfaces_c[i_surf] = new SurfaceQuadric(surf_node); + } else if (strcmp(surf_type, "quadric") == 0) { + surfaces_c[i_surf] = new SurfaceQuadric(surf_node); - } else { - std::cout << "Call error or handle uppercase here!" << std::endl; - std::cout << surf_type << std::endl; - } + } else { + std::cout << "Call error or handle uppercase here!" << std::endl; + std::cout << surf_type << std::endl; } } } From 57dbb70d0c2b123cb2938eba7187204bd9fe9991 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 2 Sep 2017 19:12:46 -0400 Subject: [PATCH 118/282] Add test code for C++ surface normals --- src/geometry.F90 | 18 ++++++++++++++++++ src/surface_header.C | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/src/geometry.F90 b/src/geometry.F90 index bc1eca2ae..8a449a284 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -35,6 +35,15 @@ module geometry logical(C_BOOL), value :: coincident; real(C_DOUBLE) :: d; end function surface_distance_c + + subroutine surface_normal_c(surf_ind, xyz, uvw) & + bind(C, name='surface_normal') + use ISO_C_BINDING + implicit none + integer(C_INT), value :: surf_ind; + real(C_DOUBLE) :: xyz(3); + real(C_DOUBLE) :: uvw(3); + end subroutine surface_normal_c end interface contains @@ -76,6 +85,7 @@ contains integer :: token logical :: actual_sense ! sense of particle wrt surface logical :: alt_sense + real(8) :: uvw1(3), uvw2(3) in_cell = .true. do i = 1, size(c%rpn) @@ -102,6 +112,14 @@ contains write(*, *) actual_sense, alt_sense call fatal_error("") end if + uvw1 = surfaces(abs(token))%obj%normal(p%coord(p%n_coord)%xyz) + call surface_normal_c(abs(token)-1, p%coord(p%n_coord)%xyz, uvw2) + if (.not. all(uvw1 - uvw2 == ZERO)) then + write(*, *) "Surface normals don't match" + write(*, *) uvw1 + write(*, *) uvw2 + call fatal_error("") + end if if (actual_sense .neqv. (token > 0)) then in_cell = .false. exit diff --git a/src/surface_header.C b/src/surface_header.C index 3ac3d3c53..43209fc80 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -903,3 +903,8 @@ extern "C" double surface_distance(int surf_ind, double xyz[3], double uvw[3], bool coincident) { return surfaces_c[surf_ind]->distance(xyz, uvw, coincident); } + +extern "C" void +surface_normal(int surf_ind, double xyz[3], double uvw[3]) { + return surfaces_c[surf_ind]->normal(xyz, uvw); +} From 3752fea1e9e3c45baf5aebf410ebc595f032596f Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 3 Sep 2017 13:28:04 -0400 Subject: [PATCH 119/282] Use Doxygen-style comments for C++ surface code --- src/surface_header.C | 195 ++++++++++++++++++++++++++++--------------- 1 file changed, 126 insertions(+), 69 deletions(-) diff --git a/src/surface_header.C b/src/surface_header.C index 43209fc80..297335827 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -1,9 +1,12 @@ #include // For strcmp -#include #include // For numeric_limits #include // For fabs + #include "pugixml/pugixml.hpp" +// DEBUGGING +#include + //============================================================================== // Constants //============================================================================== @@ -19,20 +22,24 @@ class Surface; Surface **surfaces_c; //============================================================================== -// Helper functions +// Helper functions for reading the "coeffs" node of an XML surface element //============================================================================== +const char* get_coeff_str(pugi::xml_node surf_node) +{ + if (surf_node.attribute("coeffs")) { + return surf_node.attribute("coeffs").value(); + } else if (surf_node.child("coeffs")) { + return surf_node.child_value("coeffs"); + } else { + std::cout << "ERROR: Found a surface with no coefficients" << std::endl; + return NULL; + } +} + void read_coeffs(pugi::xml_node surf_node, double &c1) { - const char *coeffs; - if (surf_node.attribute("coeffs")) { - coeffs = surf_node.attribute("coeffs").value(); - } else if (surf_node.child("coeffs")) { - coeffs = surf_node.child_value("coeffs"); - } else { - std::cout << "ERROR: Found a surface with no coefficients" << std::endl; - } - + const char *coeffs = get_coeff_str(surf_node); int stat = sscanf(coeffs, "%lf", &c1); if (stat != 1) { std::cout << "Something went wrong reading surface coeffs!" << std::endl; @@ -41,15 +48,7 @@ void read_coeffs(pugi::xml_node surf_node, double &c1) void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3) { - const char *coeffs; - if (surf_node.attribute("coeffs")) { - coeffs = surf_node.attribute("coeffs").value(); - } else if (surf_node.child("coeffs")) { - coeffs = surf_node.child_value("coeffs"); - } else { - std::cout << "ERROR: Found a surface with no coefficients" << std::endl; - } - + const char *coeffs = get_coeff_str(surf_node); int stat = sscanf(coeffs, "%lf %lf %lf", &c1, &c2, &c3); if (stat != 3) { std::cout << "Something went wrong reading surface coeffs!" << std::endl; @@ -59,15 +58,7 @@ void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3) void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3, double &c4) { - const char *coeffs; - if (surf_node.attribute("coeffs")) { - coeffs = surf_node.attribute("coeffs").value(); - } else if (surf_node.child("coeffs")) { - coeffs = surf_node.child_value("coeffs"); - } else { - std::cout << "ERROR: Found a surface with no coefficients" << std::endl; - } - + const char *coeffs = get_coeff_str(surf_node); int stat = sscanf(coeffs, "%lf %lf %lf %lf", &c1, &c2, &c3, &c4); if (stat != 4) { std::cout << "Something went wrong reading surface coeffs!" << std::endl; @@ -78,15 +69,7 @@ void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3, double &c4, double &c5, double &c6, double &c7, double &c8, double &c9, double &c10) { - const char *coeffs; - if (surf_node.attribute("coeffs")) { - coeffs = surf_node.attribute("coeffs").value(); - } else if (surf_node.child("coeffs")) { - coeffs = surf_node.child_value("coeffs"); - } else { - std::cout << "ERROR: Found a surface with no coefficients" << std::endl; - } - + const char *coeffs = get_coeff_str(surf_node); int stat = sscanf(coeffs, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", &c1, &c2, &c3, &c4, &c5, &c6, &c7, &c8, &c9, &c10); if (stat != 10) { @@ -95,25 +78,52 @@ void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3, } //============================================================================== -// SURFACE type defines a first- or second-order surface that can be used to -// construct closed volumes (cells) +//! A geometry primitive used to define regions of 3D space. //============================================================================== class Surface { - int id; // Unique ID - int neighbor_pos[], // List of cells on positive side - neighbor_neg[]; // List of cells on negative side - int bc; // Boundary condition - //TODO: swith that zero to a NONE constant. - int i_periodic = 0; // Index of corresponding periodic surface - char name[104]; // User-defined name - public: + int id; //!< Unique ID + int neighbor_pos[], //!< List of cells on positive side + neighbor_neg[]; //!< List of cells on negative side + int bc; //!< Boundary condition + //TODO: switch that zero to a NONE constant. + int i_periodic = 0; //!< Index of corresponding periodic surface + char name[104]; //!< User-defined name + + //! Determine which side of a surface a point lies on. + //! @param xyz[3] The 3D Cartesian coordinate of a point. + //! @param uvw[3] A direction used to "break ties" and pick a sense when the + //! point is very close to the surface. + //! @return True if the point is on the "positive" side of the surface and + //! false otherwise. bool sense(const double xyz[3], const double uvw[3]) const; + + //! Determine the direction of a ray reflected from the surface. + //! @param xyz[3] The point at which the ray is incident. + //! @param uvw[3] A direction. This is both an input and an output parameter. + //! It specifies the icident direction on input and the reflected direction + //! on output. void reflect(const double xyz[3], double uvw[3]) const; + + //! Evaluate the equation describing the surface. + //! + //! Surfaces can be described by some function f(x, y, z) = 0. This member + //! function evaluates that mathematical function. + //! @param xyz[3] A 3D Cartesian coordinate. virtual double evaluate(const double xyz[3]) const = 0; + + //! Compute the distance between a point and the surface along a ray. + //! @param xyz[3] A 3D Cartesian coordinate. + //! @param uvw[3] The direction of the ray. + //! @param coincident A hint to the code that the given point should lie + //! exactly on the surface. virtual double distance(const double xyz[3], const double uvw[3], bool coincident) const = 0; + + //! Compute the local outward normal direction of the surface. + //! @param xyz[3] A 3D Cartesian coordinate. + //! @param uvw[3] This output argument provides the normal. virtual void normal(const double xyz[3], double uvw[3]) const = 0; }; @@ -151,13 +161,16 @@ Surface::reflect(const double xyz[3], double uvw[3]) const { } //============================================================================== -// Generic functions for x-, y-, and z-, planes +// Generic functions for x-, y-, and z-, planes. //============================================================================== +// The template parameter indicates the axis normal to the plane. template double axis_aligned_plane_evaluate(const double xyz[3], double offset) { - return xyz[i] - offset;} + return xyz[i] - offset; +} +// The template parameter indicates the axis normal to the plane. template double axis_aligned_plane_distance(const double xyz[3], const double uvw[3], bool coincident, double offset) { @@ -168,6 +181,8 @@ axis_aligned_plane_distance(const double xyz[3], const double uvw[3], return d; } +// The first template parameter indicates the axis normal to the plane. The +// other two parameters indicate the other two axes. template void axis_aligned_plane_normal(const double xyz[3], double uvw[3]) { uvw[i1] = 1.0; @@ -177,10 +192,12 @@ axis_aligned_plane_normal(const double xyz[3], double uvw[3]) { //============================================================================== // SurfaceXPlane +//! A plane perpendicular to the x-axis. +// +//! The plane is described by the equation \f$x - x_0 = 0\f$ //============================================================================== class SurfaceXPlane : public Surface { - // x = x0 double x0; public: SurfaceXPlane(pugi::xml_node surf_node); @@ -209,10 +226,12 @@ inline void SurfaceXPlane::normal(const double xyz[3], double uvw[3]) const { //============================================================================== // SurfaceYPlane +//! A plane perpendicular to the y-axis. +// +//! The plane is described by the equation \f$y - y_0 = 0\f$ //============================================================================== class SurfaceYPlane : public Surface { - // y = y0 double y0; public: SurfaceYPlane(pugi::xml_node surf_node); @@ -241,10 +260,12 @@ inline void SurfaceYPlane::normal(const double xyz[3], double uvw[3]) const { //============================================================================== // SurfaceZPlane +//! A plane perpendicular to the z-axis. +// +//! The plane is described by the equation \f$z - z_0 = 0\f$ //============================================================================== class SurfaceZPlane : public Surface { - // z = z0 double z0; public: SurfaceZPlane(pugi::xml_node surf_node); @@ -273,10 +294,12 @@ inline void SurfaceZPlane::normal(const double xyz[3], double uvw[3]) const { //============================================================================== // SurfacePlane +//! A general plane. +// +//! The plane is described by the equation \f$A x + B y + C z - D = 0\f$ //============================================================================== class SurfacePlane : public Surface { - // Ax + By + Cz = D double A, B, C, D; public: SurfacePlane(pugi::xml_node surf_node); @@ -320,6 +343,9 @@ SurfacePlane::normal(const double xyz[3], double uvw[3]) const { // Generic functions for x-, y-, and z-, cylinders //============================================================================== +// The template parameters indicate the axes perpendicular to the axis of the +// cylinder. offset1 and offset2 should correspond with i1 and i2, +// respectively. template double axis_aligned_cylinder_evaluate(const double xyz[3], double offset1, double offset2, double radius) { @@ -328,6 +354,9 @@ axis_aligned_cylinder_evaluate(const double xyz[3], double offset1, return xyz1*xyz1 + xyz2*xyz2 - radius*radius; } +// The first template parameter indicates which axis the cylinder is aligned to. +// The other two parameters indicate the other two axes. offset1 and offset2 +// should correspond with i2 and i3, respectively. template double axis_aligned_cylinder_distance(const double xyz[3], const double uvw[3], bool coincident, double offset1, double offset2, double radius) { @@ -370,6 +399,9 @@ axis_aligned_cylinder_distance(const double xyz[3], const double uvw[3], } } +// The first template parameter indicates which axis the cylinder is aligned to. +// The other two parameters indicate the other two axes. offset1 and offset2 +// should correspond with i2 and i3, respectively. template void axis_aligned_cylinder_normal(const double xyz[3], double uvw[3], double offset1, double offset2) { @@ -380,12 +412,13 @@ axis_aligned_cylinder_normal(const double xyz[3], double uvw[3], double offset1, //============================================================================== // SurfaceXCylinder +//! A cylinder aligned along the x-axis. +// +//! The cylinder is described by the equation +//! \f$(y - y_0)^2 + (z - z_0)^2 - R^2 = 0\f$ //============================================================================== -// TODO: Test this implementation! - class SurfaceXCylinder : public Surface { - // (y - y0)^2 + (z - z0)^2 = R^2 double y0, z0, r; public: SurfaceXCylinder(pugi::xml_node surf_node); @@ -415,12 +448,13 @@ inline void SurfaceXCylinder::normal(const double xyz[3], double uvw[3]) const { //============================================================================== // SurfaceYCylinder +//! A cylinder aligned along the y-axis. +// +//! The cylinder is described by the equation +//! \f$(x - x_0)^2 + (z - z_0)^2 - R^2 = 0\f$ //============================================================================== -// TODO: Test this implementation! - class SurfaceYCylinder : public Surface { - // (x - x0)^2 + (z - z0)^2 = R^2 double x0, z0, r; public: SurfaceYCylinder(pugi::xml_node surf_node); @@ -450,10 +484,13 @@ inline void SurfaceYCylinder::normal(const double xyz[3], double uvw[3]) const { //============================================================================== // SurfaceZCylinder +//! A cylinder aligned along the z-axis. +// +//! The cylinder is described by the equation +//! \f$(x - x_0)^2 + (y - y_0)^2 - R^2 = 0\f$ //============================================================================== class SurfaceZCylinder : public Surface { - // (x - x0)^2 + (y - y0)^2 = R^2 double x0, y0, r; public: SurfaceZCylinder(pugi::xml_node surf_node); @@ -483,10 +520,13 @@ inline void SurfaceZCylinder::normal(const double xyz[3], double uvw[3]) const { //============================================================================== // SurfaceSphere +//! A sphere. +// +//! The cylinder is described by the equation +//! \f$(x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 - R^2 = 0\f$ //============================================================================== class SurfaceSphere : public Surface { - // (x - x0)^2 + (y - y0)^2 + (z - z0)^2 = R^2 double x0, y0, z0, r; public: SurfaceSphere(pugi::xml_node surf_node); @@ -555,6 +595,9 @@ inline void SurfaceSphere::normal(const double xyz[3], double uvw[3]) const { // Generic functions for x-, y-, and z-, cones //============================================================================== +// The first template parameter indicates which axis the cone is aligned to. +// The other two parameters indicate the other two axes. offset1, offset2, +// and offset3 should correspond with i1, i2, and i3, respectively. template double axis_aligned_cone_evaluate(const double xyz[3], double offset1, double offset2, double offset3, double radius_sq) { @@ -564,6 +607,9 @@ axis_aligned_cone_evaluate(const double xyz[3], double offset1, return xyz2*xyz2 + xyz3*xyz3 - radius_sq*xyz1*xyz1; } +// The first template parameter indicates which axis the cone is aligned to. +// The other two parameters indicate the other two axes. offset1, offset2, +// and offset3 should correspond with i1, i2, and i3, respectively. template double axis_aligned_cone_distance(const double xyz[3], const double uvw[3], bool coincident, double offset1, double offset2, double offset3, @@ -614,6 +660,9 @@ axis_aligned_cone_distance(const double xyz[3], const double uvw[3], return d; } +// The first template parameter indicates which axis the cone is aligned to. +// The other two parameters indicate the other two axes. offset1, offset2, +// and offset3 should correspond with i1, i2, and i3, respectively. template void axis_aligned_cone_normal(const double xyz[3], double uvw[3], double offset1, double offset2, double offset3, double radius_sq) { @@ -624,12 +673,13 @@ axis_aligned_cone_normal(const double xyz[3], double uvw[3], double offset1, //============================================================================== // SurfaceXCone +//! A cone aligned along the x-axis. +// +//! The cylinder is described by the equation +//! \f$(y - y_0)^2 + (z - z_0)^2 - R^2 (x - x_0)^2 = 0\f$ //============================================================================== -// TODO: Test this implementation! - class SurfaceXCone : public Surface { - // (y - y0)^2 + (z - z0)^2 = R^2*(x - x0)^2 double x0, y0, z0, r_sq; public: SurfaceXCone(pugi::xml_node surf_node); @@ -659,12 +709,13 @@ inline void SurfaceXCone::normal(const double xyz[3], double uvw[3]) const { //============================================================================== // SurfaceYCone +//! A cone aligned along the y-axis. +// +//! The cylinder is described by the equation +//! \f$(x - x_0)^2 + (z - z_0)^2 - R^2 (y - y_0)^2 = 0\f$ //============================================================================== -// TODO: Test this implementation! - class SurfaceYCone : public Surface { - // (x - x0)^2 + (z - z0)^2 = R^2*(y - y0)^2 double x0, y0, z0, r_sq; public: SurfaceYCone(pugi::xml_node surf_node); @@ -694,10 +745,13 @@ inline void SurfaceYCone::normal(const double xyz[3], double uvw[3]) const { //============================================================================== // SurfaceZCone +//! A cone aligned along the z-axis. +// +//! The cylinder is described by the equation +//! \f$(x - x_0)^2 + (y - y_0)^2 - R^2 (z - z_0)^2 = 0\f$ //============================================================================== class SurfaceZCone : public Surface { - // (x - x0)^2 + (y - y0)^2 = R^2*(z - z0)^2 double x0, y0, z0, r_sq; public: SurfaceZCone(pugi::xml_node surf_node); @@ -727,6 +781,9 @@ inline void SurfaceZCone::normal(const double xyz[3], double uvw[3]) const { //============================================================================== // SurfaceQuadric +//! A general surface described by a quadratic equation. +// +//! \f$A x^2 + B y^2 + C z^2 + D x y + E y z + F x z + G x + H y + J z + K = 0\f$ //============================================================================== class SurfaceQuadric : public Surface { From 72cb25e6776280fef9eb4d42e6cf712a9a36cf91 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 11 Sep 2017 16:42:40 -0400 Subject: [PATCH 120/282] Use consistent brace placement in C++ --- src/surface_header.C | 228 ++++++++++++++++++++++++++++--------------- 1 file changed, 152 insertions(+), 76 deletions(-) diff --git a/src/surface_header.C b/src/surface_header.C index 297335827..6992c2e2a 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -81,7 +81,8 @@ void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3, //! A geometry primitive used to define regions of 3D space. //============================================================================== -class Surface { +class Surface +{ public: int id; //!< Unique ID int neighbor_pos[], //!< List of cells on positive side @@ -128,7 +129,8 @@ public: }; bool -Surface::sense(const double xyz[3], const double uvw[3]) const { +Surface::sense(const double xyz[3], const double uvw[3]) const +{ // Evaluate the surface equation at the particle's coordinates to determine // which side the particle is on. const double f = evaluate(xyz); @@ -146,7 +148,8 @@ Surface::sense(const double xyz[3], const double uvw[3]) const { } void -Surface::reflect(const double xyz[3], double uvw[3]) const { +Surface::reflect(const double xyz[3], double uvw[3]) const +{ // Determine projection of direction onto normal and squared magnitude of // normal. double norm[3]; @@ -166,14 +169,16 @@ Surface::reflect(const double xyz[3], double uvw[3]) const { // The template parameter indicates the axis normal to the plane. template double -axis_aligned_plane_evaluate(const double xyz[3], double offset) { +axis_aligned_plane_evaluate(const double xyz[3], double offset) +{ return xyz[i] - offset; } // The template parameter indicates the axis normal to the plane. template double axis_aligned_plane_distance(const double xyz[3], const double uvw[3], - bool coincident, double offset) { + bool coincident, double offset) +{ const double f = offset - xyz[i]; if (coincident or fabs(f) < FP_COINCIDENT or uvw[i] == 0.0) return INFTY; const double d = f / uvw[i]; @@ -184,7 +189,8 @@ axis_aligned_plane_distance(const double xyz[3], const double uvw[3], // The first template parameter indicates the axis normal to the plane. The // other two parameters indicate the other two axes. template void -axis_aligned_plane_normal(const double xyz[3], double uvw[3]) { +axis_aligned_plane_normal(const double xyz[3], double uvw[3]) +{ uvw[i1] = 1.0; uvw[i2] = 0.0; uvw[i3] = 0.0; @@ -197,7 +203,8 @@ axis_aligned_plane_normal(const double xyz[3], double uvw[3]) { //! The plane is described by the equation \f$x - x_0 = 0\f$ //============================================================================== -class SurfaceXPlane : public Surface { +class SurfaceXPlane : public Surface +{ double x0; public: SurfaceXPlane(pugi::xml_node surf_node); @@ -207,20 +214,24 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) { +SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) +{ read_coeffs(surf_node, x0); } -inline double SurfaceXPlane::evaluate(const double xyz[3]) const { +inline double SurfaceXPlane::evaluate(const double xyz[3]) const +{ return axis_aligned_plane_evaluate<0>(xyz, x0); } inline double SurfaceXPlane::distance(const double xyz[3], const double uvw[3], - bool coincident) const { + bool coincident) const +{ return axis_aligned_plane_distance<0>(xyz, uvw, coincident, x0); } -inline void SurfaceXPlane::normal(const double xyz[3], double uvw[3]) const { +inline void SurfaceXPlane::normal(const double xyz[3], double uvw[3]) const +{ axis_aligned_plane_normal<0, 1, 2>(xyz, uvw); } @@ -231,7 +242,8 @@ inline void SurfaceXPlane::normal(const double xyz[3], double uvw[3]) const { //! The plane is described by the equation \f$y - y_0 = 0\f$ //============================================================================== -class SurfaceYPlane : public Surface { +class SurfaceYPlane : public Surface +{ double y0; public: SurfaceYPlane(pugi::xml_node surf_node); @@ -241,20 +253,24 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) { +SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) +{ read_coeffs(surf_node, y0); } -inline double SurfaceYPlane::evaluate(const double xyz[3]) const { +inline double SurfaceYPlane::evaluate(const double xyz[3]) const +{ return axis_aligned_plane_evaluate<1>(xyz, y0); } inline double SurfaceYPlane::distance(const double xyz[3], const double uvw[3], - bool coincident) const { + bool coincident) const +{ return axis_aligned_plane_distance<1>(xyz, uvw, coincident, y0); } -inline void SurfaceYPlane::normal(const double xyz[3], double uvw[3]) const { +inline void SurfaceYPlane::normal(const double xyz[3], double uvw[3]) const +{ axis_aligned_plane_normal<1, 0, 2>(xyz, uvw); } @@ -265,7 +281,8 @@ inline void SurfaceYPlane::normal(const double xyz[3], double uvw[3]) const { //! The plane is described by the equation \f$z - z_0 = 0\f$ //============================================================================== -class SurfaceZPlane : public Surface { +class SurfaceZPlane : public Surface +{ double z0; public: SurfaceZPlane(pugi::xml_node surf_node); @@ -275,20 +292,24 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) { +SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) +{ read_coeffs(surf_node, z0); } -inline double SurfaceZPlane::evaluate(const double xyz[3]) const { +inline double SurfaceZPlane::evaluate(const double xyz[3]) const +{ return axis_aligned_plane_evaluate<2>(xyz, z0); } inline double SurfaceZPlane::distance(const double xyz[3], const double uvw[3], - bool coincident) const { + bool coincident) const +{ return axis_aligned_plane_distance<2>(xyz, uvw, coincident, z0); } -inline void SurfaceZPlane::normal(const double xyz[3], double uvw[3]) const { +inline void SurfaceZPlane::normal(const double xyz[3], double uvw[3]) const +{ axis_aligned_plane_normal<2, 0, 1>(xyz, uvw); } @@ -299,7 +320,8 @@ inline void SurfaceZPlane::normal(const double xyz[3], double uvw[3]) const { //! The plane is described by the equation \f$A x + B y + C z - D = 0\f$ //============================================================================== -class SurfacePlane : public Surface { +class SurfacePlane : public Surface +{ double A, B, C, D; public: SurfacePlane(pugi::xml_node surf_node); @@ -309,18 +331,21 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfacePlane::SurfacePlane(pugi::xml_node surf_node) { +SurfacePlane::SurfacePlane(pugi::xml_node surf_node) +{ read_coeffs(surf_node, A, B, C, D); } double -SurfacePlane::evaluate(const double xyz[3]) const { +SurfacePlane::evaluate(const double xyz[3]) const +{ return A*xyz[0] + B*xyz[1] + C*xyz[2] - D; } double SurfacePlane::distance(const double xyz[3], const double uvw[3], - bool coincident) const { + bool coincident) const +{ const double f = A*xyz[0] + B*xyz[1] + C*xyz[2] - D; const double projection = A*uvw[0] + B*uvw[1] + C*uvw[2]; if (coincident or fabs(f) < FP_COINCIDENT or projection == 0.0) { @@ -333,7 +358,8 @@ SurfacePlane::distance(const double xyz[3], const double uvw[3], } void -SurfacePlane::normal(const double xyz[3], double uvw[3]) const { +SurfacePlane::normal(const double xyz[3], double uvw[3]) const +{ uvw[0] = A; uvw[1] = B; uvw[2] = C; @@ -348,7 +374,8 @@ SurfacePlane::normal(const double xyz[3], double uvw[3]) const { // respectively. template double axis_aligned_cylinder_evaluate(const double xyz[3], double offset1, - double offset2, double radius) { + double offset2, double radius) +{ const double xyz1 = xyz[i1] - offset1; const double xyz2 = xyz[i2] - offset2; return xyz1*xyz1 + xyz2*xyz2 - radius*radius; @@ -359,7 +386,8 @@ axis_aligned_cylinder_evaluate(const double xyz[3], double offset1, // should correspond with i2 and i3, respectively. template double axis_aligned_cylinder_distance(const double xyz[3], const double uvw[3], - bool coincident, double offset1, double offset2, double radius) { + bool coincident, double offset1, double offset2, double radius) +{ const double a = 1.0 - uvw[i1]*uvw[i1]; // u^2 + v^2 if (a == 0.0) return INFTY; @@ -404,7 +432,8 @@ axis_aligned_cylinder_distance(const double xyz[3], const double uvw[3], // should correspond with i2 and i3, respectively. template void axis_aligned_cylinder_normal(const double xyz[3], double uvw[3], double offset1, - double offset2) { + double offset2) +{ uvw[i2] = 2.0 * (xyz[i2] - offset1); uvw[i3] = 2.0 * (xyz[i3] - offset2); uvw[i1] = 0.0; @@ -418,7 +447,8 @@ axis_aligned_cylinder_normal(const double xyz[3], double uvw[3], double offset1, //! \f$(y - y_0)^2 + (z - z_0)^2 - R^2 = 0\f$ //============================================================================== -class SurfaceXCylinder : public Surface { +class SurfaceXCylinder : public Surface +{ double y0, z0, r; public: SurfaceXCylinder(pugi::xml_node surf_node); @@ -428,21 +458,25 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node) { +SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node) +{ read_coeffs(surf_node, y0, z0, r); } -inline double SurfaceXCylinder::evaluate(const double xyz[3]) const { +inline double SurfaceXCylinder::evaluate(const double xyz[3]) const +{ return axis_aligned_cylinder_evaluate<1, 2>(xyz, y0, z0, r); } inline double SurfaceXCylinder::distance(const double xyz[3], - const double uvw[3], bool coincident) const { + const double uvw[3], bool coincident) const +{ return axis_aligned_cylinder_distance<0, 1, 2>(xyz, uvw, coincident, y0, z0, r); } -inline void SurfaceXCylinder::normal(const double xyz[3], double uvw[3]) const { +inline void SurfaceXCylinder::normal(const double xyz[3], double uvw[3]) const +{ axis_aligned_cylinder_normal<0, 1, 2>(xyz, uvw, y0, z0); } @@ -454,7 +488,8 @@ inline void SurfaceXCylinder::normal(const double xyz[3], double uvw[3]) const { //! \f$(x - x_0)^2 + (z - z_0)^2 - R^2 = 0\f$ //============================================================================== -class SurfaceYCylinder : public Surface { +class SurfaceYCylinder : public Surface +{ double x0, z0, r; public: SurfaceYCylinder(pugi::xml_node surf_node); @@ -464,21 +499,25 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node) { +SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node) +{ read_coeffs(surf_node, x0, z0, r); } -inline double SurfaceYCylinder::evaluate(const double xyz[3]) const { +inline double SurfaceYCylinder::evaluate(const double xyz[3]) const +{ return axis_aligned_cylinder_evaluate<0, 2>(xyz, x0, z0, r); } inline double SurfaceYCylinder::distance(const double xyz[3], - const double uvw[3], bool coincident) const { + const double uvw[3], bool coincident) const +{ return axis_aligned_cylinder_distance<1, 0, 2>(xyz, uvw, coincident, x0, z0, r); } -inline void SurfaceYCylinder::normal(const double xyz[3], double uvw[3]) const { +inline void SurfaceYCylinder::normal(const double xyz[3], double uvw[3]) const +{ axis_aligned_cylinder_normal<1, 0, 2>(xyz, uvw, x0, z0); } @@ -490,7 +529,8 @@ inline void SurfaceYCylinder::normal(const double xyz[3], double uvw[3]) const { //! \f$(x - x_0)^2 + (y - y_0)^2 - R^2 = 0\f$ //============================================================================== -class SurfaceZCylinder : public Surface { +class SurfaceZCylinder : public Surface +{ double x0, y0, r; public: SurfaceZCylinder(pugi::xml_node surf_node); @@ -500,21 +540,25 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) { +SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) +{ read_coeffs(surf_node, x0, y0, r); } -inline double SurfaceZCylinder::evaluate(const double xyz[3]) const { +inline double SurfaceZCylinder::evaluate(const double xyz[3]) const +{ return axis_aligned_cylinder_evaluate<0, 1>(xyz, x0, y0, r); } inline double SurfaceZCylinder::distance(const double xyz[3], - const double uvw[3], bool coincident) const { + const double uvw[3], bool coincident) const +{ return axis_aligned_cylinder_distance<2, 0, 1>(xyz, uvw, coincident, x0, y0, r); } -inline void SurfaceZCylinder::normal(const double xyz[3], double uvw[3]) const { +inline void SurfaceZCylinder::normal(const double xyz[3], double uvw[3]) const +{ axis_aligned_cylinder_normal<2, 0, 1>(xyz, uvw, x0, y0); } @@ -526,7 +570,8 @@ inline void SurfaceZCylinder::normal(const double xyz[3], double uvw[3]) const { //! \f$(x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 - R^2 = 0\f$ //============================================================================== -class SurfaceSphere : public Surface { +class SurfaceSphere : public Surface +{ double x0, y0, z0, r; public: SurfaceSphere(pugi::xml_node surf_node); @@ -536,11 +581,13 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) { +SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) +{ read_coeffs(surf_node, x0, y0, z0, r); } -double SurfaceSphere::evaluate(const double xyz[3]) const { +double SurfaceSphere::evaluate(const double xyz[3]) const +{ const double x = xyz[0] - x0; const double y = xyz[1] - y0; const double z = xyz[2] - z0; @@ -548,7 +595,8 @@ double SurfaceSphere::evaluate(const double xyz[3]) const { } double SurfaceSphere::distance(const double xyz[3], const double uvw[3], - bool coincident) const { + bool coincident) const +{ const double x = xyz[0] - x0; const double y = xyz[1] - y0; const double z = xyz[2] - z0; @@ -585,7 +633,8 @@ double SurfaceSphere::distance(const double xyz[3], const double uvw[3], } } -inline void SurfaceSphere::normal(const double xyz[3], double uvw[3]) const { +inline void SurfaceSphere::normal(const double xyz[3], double uvw[3]) const +{ uvw[0] = 2.0 * (xyz[0] - x0); uvw[1] = 2.0 * (xyz[1] - y0); uvw[2] = 2.0 * (xyz[2] - z0); @@ -600,7 +649,8 @@ inline void SurfaceSphere::normal(const double xyz[3], double uvw[3]) const { // and offset3 should correspond with i1, i2, and i3, respectively. template double axis_aligned_cone_evaluate(const double xyz[3], double offset1, - double offset2, double offset3, double radius_sq) { + double offset2, double offset3, double radius_sq) +{ const double xyz1 = xyz[i1] - offset1; const double xyz2 = xyz[i2] - offset2; const double xyz3 = xyz[i3] - offset3; @@ -613,7 +663,8 @@ axis_aligned_cone_evaluate(const double xyz[3], double offset1, template double axis_aligned_cone_distance(const double xyz[3], const double uvw[3], bool coincident, double offset1, double offset2, double offset3, - double radius_sq) { + double radius_sq) +{ const double xyz1 = xyz[i1] - offset1; const double xyz2 = xyz[i2] - offset2; const double xyz3 = xyz[i3] - offset3; @@ -665,7 +716,8 @@ axis_aligned_cone_distance(const double xyz[3], const double uvw[3], // and offset3 should correspond with i1, i2, and i3, respectively. template void axis_aligned_cone_normal(const double xyz[3], double uvw[3], double offset1, - double offset2, double offset3, double radius_sq) { + double offset2, double offset3, double radius_sq) +{ uvw[i1] = -2.0 * radius_sq * (xyz[i1] - offset1); uvw[i2] = 2.0 * (xyz[i2] - offset2); uvw[i3] = 2.0 * (xyz[i3] - offset3); @@ -679,7 +731,8 @@ axis_aligned_cone_normal(const double xyz[3], double uvw[3], double offset1, //! \f$(y - y_0)^2 + (z - z_0)^2 - R^2 (x - x_0)^2 = 0\f$ //============================================================================== -class SurfaceXCone : public Surface { +class SurfaceXCone : public Surface +{ double x0, y0, z0, r_sq; public: SurfaceXCone(pugi::xml_node surf_node); @@ -689,21 +742,25 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) { +SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) +{ read_coeffs(surf_node, x0, y0, z0, r_sq); } -inline double SurfaceXCone::evaluate(const double xyz[3]) const { +inline double SurfaceXCone::evaluate(const double xyz[3]) const +{ return axis_aligned_cone_evaluate<0, 1, 2>(xyz, x0, y0, z0, r_sq); } inline double SurfaceXCone::distance(const double xyz[3], - const double uvw[3], bool coincident) const { + const double uvw[3], bool coincident) const +{ return axis_aligned_cone_distance<0, 1, 2>(xyz, uvw, coincident, x0, y0, z0, r_sq); } -inline void SurfaceXCone::normal(const double xyz[3], double uvw[3]) const { +inline void SurfaceXCone::normal(const double xyz[3], double uvw[3]) const +{ axis_aligned_cone_normal<0, 1, 2>(xyz, uvw, x0, y0, z0, r_sq); } @@ -715,7 +772,8 @@ inline void SurfaceXCone::normal(const double xyz[3], double uvw[3]) const { //! \f$(x - x_0)^2 + (z - z_0)^2 - R^2 (y - y_0)^2 = 0\f$ //============================================================================== -class SurfaceYCone : public Surface { +class SurfaceYCone : public Surface +{ double x0, y0, z0, r_sq; public: SurfaceYCone(pugi::xml_node surf_node); @@ -725,21 +783,25 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) { +SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) +{ read_coeffs(surf_node, x0, y0, z0, r_sq); } -inline double SurfaceYCone::evaluate(const double xyz[3]) const { +inline double SurfaceYCone::evaluate(const double xyz[3]) const +{ return axis_aligned_cone_evaluate<1, 0, 2>(xyz, y0, x0, z0, r_sq); } inline double SurfaceYCone::distance(const double xyz[3], - const double uvw[3], bool coincident) const { + const double uvw[3], bool coincident) const +{ return axis_aligned_cone_distance<1, 0, 2>(xyz, uvw, coincident, y0, x0, z0, r_sq); } -inline void SurfaceYCone::normal(const double xyz[3], double uvw[3]) const { +inline void SurfaceYCone::normal(const double xyz[3], double uvw[3]) const +{ axis_aligned_cone_normal<1, 0, 2>(xyz, uvw, y0, x0, z0, r_sq); } @@ -751,7 +813,8 @@ inline void SurfaceYCone::normal(const double xyz[3], double uvw[3]) const { //! \f$(x - x_0)^2 + (y - y_0)^2 - R^2 (z - z_0)^2 = 0\f$ //============================================================================== -class SurfaceZCone : public Surface { +class SurfaceZCone : public Surface +{ double x0, y0, z0, r_sq; public: SurfaceZCone(pugi::xml_node surf_node); @@ -761,21 +824,25 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) { +SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) +{ read_coeffs(surf_node, x0, y0, z0, r_sq); } -inline double SurfaceZCone::evaluate(const double xyz[3]) const { +inline double SurfaceZCone::evaluate(const double xyz[3]) const +{ return axis_aligned_cone_evaluate<2, 0, 1>(xyz, z0, x0, y0, r_sq); } inline double SurfaceZCone::distance(const double xyz[3], - const double uvw[3], bool coincident) const { + const double uvw[3], bool coincident) const +{ return axis_aligned_cone_distance<2, 0, 1>(xyz, uvw, coincident, z0, x0, y0, r_sq); } -inline void SurfaceZCone::normal(const double xyz[3], double uvw[3]) const { +inline void SurfaceZCone::normal(const double xyz[3], double uvw[3]) const +{ axis_aligned_cone_normal<2, 0, 1>(xyz, uvw, z0, x0, y0, r_sq); } @@ -786,7 +853,8 @@ inline void SurfaceZCone::normal(const double xyz[3], double uvw[3]) const { //! \f$A x^2 + B y^2 + C z^2 + D x y + E y z + F x z + G x + H y + J z + K = 0\f$ //============================================================================== -class SurfaceQuadric : public Surface { +class SurfaceQuadric : public Surface +{ // Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0 double A, B, C, D, E, F, G, H, J, K; public: @@ -797,12 +865,14 @@ public: void normal(const double xyz[3], double uvw[3]) const; }; -SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) { +SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) +{ read_coeffs(surf_node, A, B, C, D, E, F, G, H, J, K); } double -SurfaceQuadric::evaluate(const double xyz[3]) const { +SurfaceQuadric::evaluate(const double xyz[3]) const +{ const double &x = xyz[0]; const double &y = xyz[1]; const double &z = xyz[2]; @@ -813,7 +883,8 @@ SurfaceQuadric::evaluate(const double xyz[3]) const { double SurfaceQuadric::distance(const double xyz[3], - const double uvw[3], bool coincident) const { + const double uvw[3], bool coincident) const +{ const double &x = xyz[0]; const double &y = xyz[1]; const double &z = xyz[2]; @@ -866,7 +937,8 @@ SurfaceQuadric::distance(const double xyz[3], } void -SurfaceQuadric::normal(const double xyz[3], double uvw[3]) const { +SurfaceQuadric::normal(const double xyz[3], double uvw[3]) const +{ const double &x = xyz[0]; const double &y = xyz[1]; const double &z = xyz[2]; @@ -878,7 +950,8 @@ SurfaceQuadric::normal(const double xyz[3], double uvw[3]) const { //============================================================================== extern "C" void -read_surfaces(pugi::xml_node *node) { +read_surfaces(pugi::xml_node *node) +{ // Count the number of surfaces. int n_surfaces = 0; for (pugi::xml_node surf_node = node->child("surface"); surf_node; @@ -952,16 +1025,19 @@ read_surfaces(pugi::xml_node *node) { //============================================================================== extern "C" bool -surface_sense(int surf_ind, double xyz[3], double uvw[3]) { +surface_sense(int surf_ind, double xyz[3], double uvw[3]) +{ return surfaces_c[surf_ind]->sense(xyz, uvw); } extern "C" double -surface_distance(int surf_ind, double xyz[3], double uvw[3], bool coincident) { +surface_distance(int surf_ind, double xyz[3], double uvw[3], bool coincident) +{ return surfaces_c[surf_ind]->distance(xyz, uvw, coincident); } extern "C" void -surface_normal(int surf_ind, double xyz[3], double uvw[3]) { +surface_normal(int surf_ind, double xyz[3], double uvw[3]) +{ return surfaces_c[surf_ind]->normal(xyz, uvw); } From 163b6e3de5a7235e1152926880e5474ec44af8fb Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 11 Oct 2017 16:58:00 -0400 Subject: [PATCH 121/282] Remove Fortran code for surface distance and sense --- src/geometry.F90 | 67 ++--- src/surface_header.C | 2 +- src/surface_header.F90 | 560 +---------------------------------------- 3 files changed, 23 insertions(+), 606 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index 8a449a284..873582dec 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -15,34 +15,34 @@ module geometry implicit none interface - function surface_sense_c(surf_ind, xyz, uvw) & + pure function surface_sense_c(surf_ind, xyz, uvw) & bind(C, name='surface_sense') result(sense) use ISO_C_BINDING implicit none - integer(C_INT), value :: surf_ind; - real(C_DOUBLE) :: xyz(3); - real(C_DOUBLE) :: uvw(3); - logical(C_BOOL) :: sense; + integer(C_INT), intent(in), value :: surf_ind; + real(C_DOUBLE), intent(in) :: xyz(3); + real(C_DOUBLE), intent(in) :: uvw(3); + logical(C_BOOL) :: sense; end function surface_sense_c - function surface_distance_c(surf_ind, xyz, uvw, coincident) & + pure function surface_distance_c(surf_ind, xyz, uvw, coincident) & bind(C, name='surface_distance') result(d) use ISO_C_BINDING implicit none - integer(C_INT), value :: surf_ind; - real(C_DOUBLE) :: xyz(3); - real(C_DOUBLE) :: uvw(3); - logical(C_BOOL), value :: coincident; - real(C_DOUBLE) :: d; + integer(C_INT), intent(in), value :: surf_ind; + real(C_DOUBLE), intent(in) :: xyz(3); + real(C_DOUBLE), intent(in) :: uvw(3); + logical(C_BOOL), intent(in), value :: coincident; + real(C_DOUBLE) :: d; end function surface_distance_c - subroutine surface_normal_c(surf_ind, xyz, uvw) & + pure subroutine surface_normal_c(surf_ind, xyz, uvw) & bind(C, name='surface_normal') use ISO_C_BINDING implicit none - integer(C_INT), value :: surf_ind; - real(C_DOUBLE) :: xyz(3); - real(C_DOUBLE) :: uvw(3); + integer(C_INT), intent(in), value :: surf_ind; + real(C_DOUBLE), intent(in) :: xyz(3); + real(C_DOUBLE), intent(out) :: uvw(3); end subroutine surface_normal_c end interface @@ -62,8 +62,7 @@ contains ! expression using a stack, similar to how a RPN calculator would work. !=============================================================================== - !pure function cell_contains(c, p) result(in_cell) - function cell_contains(c, p) result(in_cell) + pure function cell_contains(c, p) result(in_cell) type(Cell), intent(in) :: c type(Particle), intent(in) :: p logical :: in_cell @@ -75,8 +74,7 @@ contains end if end function cell_contains - !pure function simple_cell_contains(c, p) result(in_cell) - function simple_cell_contains(c, p) result(in_cell) + pure function simple_cell_contains(c, p) result(in_cell) type(Cell), intent(in) :: c type(Particle), intent(in) :: p logical :: in_cell @@ -84,8 +82,6 @@ contains integer :: i integer :: token logical :: actual_sense ! sense of particle wrt surface - logical :: alt_sense - real(8) :: uvw1(3), uvw2(3) in_cell = .true. do i = 1, size(c%rpn) @@ -101,25 +97,8 @@ contains in_cell = .false. exit else - actual_sense = surfaces(abs(token))%obj%sense(& + actual_sense = surface_sense_c(abs(token)-1, & p%coord(p%n_coord)%xyz, p%coord(p%n_coord)%uvw) - alt_sense = surface_sense_c(abs(token)-1, & - p%coord(p%n_coord)%xyz, p%coord(p%n_coord)%uvw) - if (actual_sense .neqv. alt_sense) then - write(*, *) surfaces(abs(token)) % obj % id - write(*, *) p % coord (p % n_coord) % xyz - write(*, *) p % coord (p % n_coord) % uvw - write(*, *) actual_sense, alt_sense - call fatal_error("") - end if - uvw1 = surfaces(abs(token))%obj%normal(p%coord(p%n_coord)%xyz) - call surface_normal_c(abs(token)-1, p%coord(p%n_coord)%xyz, uvw2) - if (.not. all(uvw1 - uvw2 == ZERO)) then - write(*, *) "Surface normals don't match" - write(*, *) uvw1 - write(*, *) uvw2 - call fatal_error("") - end if if (actual_sense .neqv. (token > 0)) then in_cell = .false. exit @@ -167,7 +146,7 @@ contains elseif (-token == p%surface) then stack(i_stack) = .false. else - actual_sense = surfaces(abs(token))%obj%sense(& + actual_sense = surface_sense_c(abs(token)-1, & p%coord(p%n_coord)%xyz, p%coord(p%n_coord)%uvw) stack(i_stack) = (actual_sense .eqv. (token > 0)) end if @@ -539,7 +518,6 @@ contains type(Cell), pointer :: c class(Surface), pointer :: surf class(Lattice), pointer :: lat - real(8) :: d_alt ! inialize distance to infinity (huge) dist = INFINITY @@ -573,13 +551,8 @@ contains if (index_surf >= OP_UNION) cycle ! Calculate distance to surface - surf => surfaces(index_surf) % obj - d = surf % distance(p % coord(j) % xyz, p % coord(j) % uvw, coincident) - d_alt = surface_distance_c(index_surf-1, p % coord(j) % xyz, & + d = surface_distance_c(index_surf-1, p % coord(j) % xyz, & p % coord(j) % uvw, logical(coincident, kind=C_BOOL)) - if (d /= d_alt) then - write(*, *) d, d_alt - end if ! Check if calculated distance is new minimum if (d < d_surf) then diff --git a/src/surface_header.C b/src/surface_header.C index 6992c2e2a..cf200e5e4 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -33,7 +33,7 @@ const char* get_coeff_str(pugi::xml_node surf_node) return surf_node.child_value("coeffs"); } else { std::cout << "ERROR: Found a surface with no coefficients" << std::endl; - return NULL; + return nullptr; } } diff --git a/src/surface_header.F90 b/src/surface_header.F90 index ed4ef86e0..b0da37c1e 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -21,10 +21,8 @@ module surface_header integer :: i_periodic = NONE ! Index of corresponding periodic surface character(len=104) :: name = "" ! User-defined name contains - procedure :: sense procedure :: reflect procedure(surface_evaluate_), deferred :: evaluate - procedure(surface_distance_), deferred :: distance procedure(surface_normal_), deferred :: normal end type Surface @@ -36,15 +34,6 @@ module surface_header real(8) :: f end function surface_evaluate_ - pure function surface_distance_(this, xyz, uvw, coincident) result(d) - import Surface - class(Surface), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - end function surface_distance_ - pure function surface_normal_(this, xyz) result(uvw) import Surface class(Surface), intent(in) :: this @@ -63,8 +52,8 @@ module surface_header !=============================================================================== ! All the derived types below are extensions of the abstract Surface type. They -! inherent the reflect() and sense() type-bound procedures and must implement -! evaluate(), distance(), and normal() +! inherent the reflect() type-bound procedure and must implement evaluate(), +! and normal() !=============================================================================== type, extends(Surface) :: SurfaceXPlane @@ -72,7 +61,6 @@ module surface_header real(8) :: x0 contains procedure :: evaluate => x_plane_evaluate - procedure :: distance => x_plane_distance procedure :: normal => x_plane_normal end type SurfaceXPlane @@ -81,7 +69,6 @@ module surface_header real(8) :: y0 contains procedure :: evaluate => y_plane_evaluate - procedure :: distance => y_plane_distance procedure :: normal => y_plane_normal end type SurfaceYPlane @@ -90,7 +77,6 @@ module surface_header real(8) :: z0 contains procedure :: evaluate => z_plane_evaluate - procedure :: distance => z_plane_distance procedure :: normal => z_plane_normal end type SurfaceZPlane @@ -102,7 +88,6 @@ module surface_header real(8) :: D contains procedure :: evaluate => plane_evaluate - procedure :: distance => plane_distance procedure :: normal => plane_normal end type SurfacePlane @@ -113,7 +98,6 @@ module surface_header real(8) :: r contains procedure :: evaluate => x_cylinder_evaluate - procedure :: distance => x_cylinder_distance procedure :: normal => x_cylinder_normal end type SurfaceXCylinder @@ -124,7 +108,6 @@ module surface_header real(8) :: r contains procedure :: evaluate => y_cylinder_evaluate - procedure :: distance => y_cylinder_distance procedure :: normal => y_cylinder_normal end type SurfaceYCylinder @@ -135,7 +118,6 @@ module surface_header real(8) :: r contains procedure :: evaluate => z_cylinder_evaluate - procedure :: distance => z_cylinder_distance procedure :: normal => z_cylinder_normal end type SurfaceZCylinder @@ -147,7 +129,6 @@ module surface_header real(8) :: r contains procedure :: evaluate => sphere_evaluate - procedure :: distance => sphere_distance procedure :: normal => sphere_normal end type SurfaceSphere @@ -159,7 +140,6 @@ module surface_header real(8) :: r2 contains procedure :: evaluate => x_cone_evaluate - procedure :: distance => x_cone_distance procedure :: normal => x_cone_normal end type SurfaceXCone @@ -171,7 +151,6 @@ module surface_header real(8) :: r2 contains procedure :: evaluate => y_cone_evaluate - procedure :: distance => y_cone_distance procedure :: normal => y_cone_normal end type SurfaceYCone @@ -183,7 +162,6 @@ module surface_header real(8) :: r2 contains procedure :: evaluate => z_cone_evaluate - procedure :: distance => z_cone_distance procedure :: normal => z_cone_normal end type SurfaceZCone @@ -192,7 +170,6 @@ module surface_header real(8) :: A, B, C, D, E, F, G, H, J, K contains procedure :: evaluate => quadric_evaluate - procedure :: distance => quadric_distance procedure :: normal => quadric_normal end type SurfaceQuadric @@ -205,36 +182,6 @@ module surface_header contains -!=============================================================================== -! SENSE determines whether a point is on the 'positive' or 'negative' side of a -! surface. This routine is crucial for determining what cell a particular point -! is in. The positive side is indicated by a returned value of .true. and the -! negative side is indicated by a returned value of .false. -!=============================================================================== - - pure function sense(this, xyz, uvw) result(s) - class(Surface), intent(in) :: this ! surface - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical :: s ! sense of particle - - real(8) :: f ! surface function evaluated at point - - ! Evaluate the surface equation at the particle's coordinates to determine - ! which side the particle is on - f = this%evaluate(xyz) - - ! Check which side of surface the point is on - if (abs(f) < FP_COINCIDENT) then - ! Particle may be coincident with this surface. To determine the sense, we - ! look at the direction of the particle relative to the surface normal (by - ! default in the positive direction) via their dot product. - s = (dot_product(uvw, this%normal(xyz)) > ZERO) - else - s = (f > ZERO) - end if - end function sense - !=============================================================================== ! REFLECT determines the direction a particle will travel if it is specularly ! reflected from the surface at a given position and direction. The position is @@ -275,24 +222,6 @@ contains f = xyz(1) - this%x0 end function x_plane_evaluate - pure function x_plane_distance(this, xyz, uvw, coincident) result(d) - class(SurfaceXPlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: f - - f = this%x0 - xyz(1) - if (coincident .or. abs(f) < FP_COINCIDENT .or. uvw(1) == ZERO) then - d = INFINITY - else - d = f/uvw(1) - if (d < ZERO) d = INFINITY - end if - end function x_plane_distance - pure function x_plane_normal(this, xyz) result(uvw) class(SurfaceXPlane), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -313,24 +242,6 @@ contains f = xyz(2) - this%y0 end function y_plane_evaluate - pure function y_plane_distance(this, xyz, uvw, coincident) result(d) - class(SurfaceYPlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: f - - f = this%y0 - xyz(2) - if (coincident .or. abs(f) < FP_COINCIDENT .or. uvw(2) == ZERO) then - d = INFINITY - else - d = f/uvw(2) - if (d < ZERO) d = INFINITY - end if - end function y_plane_distance - pure function y_plane_normal(this, xyz) result(uvw) class(SurfaceYPlane), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -353,24 +264,6 @@ contains end function z_plane_evaluate - pure function z_plane_distance(this, xyz, uvw, coincident) result(d) - class(SurfaceZPlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: f - - f = this%z0 - xyz(3) - if (coincident .or. abs(f) < FP_COINCIDENT .or. uvw(3) == ZERO) then - d = INFINITY - else - d = f/uvw(3) - if (d < ZERO) d = INFINITY - end if - end function z_plane_distance - pure function z_plane_normal(this, xyz) result(uvw) class(SurfaceZPlane), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -391,26 +284,6 @@ contains f = this%A*xyz(1) + this%B*xyz(2) + this%C*xyz(3) - this%D end function plane_evaluate - pure function plane_distance(this, xyz, uvw, coincident) result(d) - class(SurfacePlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: f - real(8) :: projection - - f = this%A*xyz(1) + this%B*xyz(2) + this%C*xyz(3) - this%D - projection = this%A*uvw(1) + this%B*uvw(2) + this%C*uvw(3) - if (coincident .or. abs(f) < FP_COINCIDENT .or. projection == ZERO) then - d = INFINITY - else - d = -f/projection - if (d < ZERO) d = INFINITY - end if - end function plane_distance - pure function plane_normal(this, xyz) result(uvw) class(SurfacePlane), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -435,60 +308,6 @@ contains f = y*y + z*z - this%r*this%r end function x_cylinder_evaluate - pure function x_cylinder_distance(this, xyz, uvw, coincident) result(d) - class(SurfaceXCylinder), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: y, z, k, a, c, quad - - a = ONE - uvw(1)*uvw(1) ! v^2 + w^2 - if (a == ZERO) then - d = INFINITY - else - y = xyz(2) - this%y0 - z = xyz(3) - this%z0 - k = y*uvw(2) + z*uvw(3) - c = y*y + z*z - this%r*this%r - quad = k*k - a*c - - if (quad < ZERO) then - ! no intersection with cylinder - - d = INFINITY - - elseif (coincident .or. abs(c) < FP_COINCIDENT) then - ! particle is on the cylinder, thus one distance is positive/negative - ! and the other is zero. The sign of k determines if we are facing in or - ! out - - if (k >= ZERO) then - d = INFINITY - else - d = (-k + sqrt(quad))/a - end if - - elseif (c < ZERO) then - ! particle is inside the cylinder, thus one distance must be negative - ! and one must be positive. The positive distance will be the one with - ! negative sign on sqrt(quad) - - d = (-k + sqrt(quad))/a - - else - ! particle is outside the cylinder, thus both distances are either - ! positive or negative. If positive, the smaller distance is the one - ! with positive sign on sqrt(quad) - - d = (-k - sqrt(quad))/a - if (d < ZERO) d = INFINITY - - end if - end if - end function x_cylinder_distance - pure function x_cylinder_normal(this, xyz) result(uvw) class(SurfaceXCylinder), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -515,60 +334,6 @@ contains f = x*x + z*z - this%r*this%r end function y_cylinder_evaluate - pure function y_cylinder_distance(this, xyz, uvw, coincident) result(d) - class(SurfaceYCylinder), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: x, z, k, a, c, quad - - a = ONE - uvw(2)*uvw(2) ! u^2 + w^2 - if (a == ZERO) then - d = INFINITY - else - x = xyz(1) - this%x0 - z = xyz(3) - this%z0 - k = x*uvw(1) + z*uvw(3) - c = x*x + z*z - this%r*this%r - quad = k*k - a*c - - if (quad < ZERO) then - ! no intersection with cylinder - - d = INFINITY - - elseif (coincident .or. abs(c) < FP_COINCIDENT) then - ! particle is on the cylinder, thus one distance is positive/negative - ! and the other is zero. The sign of k determines if we are facing in or - ! out - - if (k >= ZERO) then - d = INFINITY - else - d = (-k + sqrt(quad))/a - end if - - elseif (c < ZERO) then - ! particle is inside the cylinder, thus one distance must be negative - ! and one must be positive. The positive distance will be the one with - ! negative sign on sqrt(quad) - - d = (-k + sqrt(quad))/a - - else - ! particle is outside the cylinder, thus both distances are either - ! positive or negative. If positive, the smaller distance is the one - ! with positive sign on sqrt(quad) - - d = (-k - sqrt(quad))/a - if (d < ZERO) d = INFINITY - - end if - end if - end function y_cylinder_distance - pure function y_cylinder_normal(this, xyz) result(uvw) class(SurfaceYCylinder), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -595,60 +360,6 @@ contains f = x*x + y*y - this%r*this%r end function z_cylinder_evaluate - pure function z_cylinder_distance(this, xyz, uvw, coincident) result(d) - class(SurfaceZCylinder), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: x, y, k, a, c, quad - - a = ONE - uvw(3)*uvw(3) ! u^2 + v^2 - if (a == ZERO) then - d = INFINITY - else - x = xyz(1) - this%x0 - y = xyz(2) - this%y0 - k = x*uvw(1) + y*uvw(2) - c = x*x + y*y - this%r*this%r - quad = k*k - a*c - - if (quad < ZERO) then - ! no intersection with cylinder - - d = INFINITY - - elseif (coincident .or. abs(c) < FP_COINCIDENT) then - ! particle is on the cylinder, thus one distance is positive/negative - ! and the other is zero. The sign of k determines if we are facing in or - ! out - - if (k >= ZERO) then - d = INFINITY - else - d = (-k + sqrt(quad))/a - end if - - elseif (c < ZERO) then - ! particle is inside the cylinder, thus one distance must be negative - ! and one must be positive. The positive distance will be the one with - ! negative sign on sqrt(quad) - - d = (-k + sqrt(quad))/a - - else - ! particle is outside the cylinder, thus both distances are either - ! positive or negative. If positive, the smaller distance is the one - ! with positive sign on sqrt(quad) - - d = (-k - sqrt(quad))/a - if (d < ZERO) d = INFINITY - - end if - end if - end function z_cylinder_distance - pure function z_cylinder_normal(this, xyz) result(uvw) class(SurfaceZCylinder), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -676,55 +387,6 @@ contains f = x*x + y*y + z*z - this%r*this%r end function sphere_evaluate - pure function sphere_distance(this, xyz, uvw, coincident) result(d) - class(SurfaceSphere), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: x, y, z, k, c, quad - - x = xyz(1) - this%x0 - y = xyz(2) - this%y0 - z = xyz(3) - this%z0 - k = x*uvw(1) + y*uvw(2) + z*uvw(3) - c = x*x + y*y + z*z - this%r*this%r - quad = k*k - c - - if (quad < ZERO) then - ! no intersection with sphere - - d = INFINITY - - elseif (coincident .or. abs(c) < FP_COINCIDENT) then - ! particle is on the sphere, thus one distance is positive/negative and - ! the other is zero. The sign of k determines if we are facing in or out - - if (k >= ZERO) then - d = INFINITY - else - d = -k + sqrt(quad) - end if - - elseif (c < ZERO) then - ! particle is inside the sphere, thus one distance must be negative and - ! one must be positive. The positive distance will be the one with - ! negative sign on sqrt(quad) - - d = -k + sqrt(quad) - - else - ! particle is outside the sphere, thus both distances are either positive - ! or negative. If positive, the smaller distance is the one with positive - ! sign on sqrt(quad) - - d = -k - sqrt(quad) - if (d < ZERO) d = INFINITY - - end if - end function sphere_distance - pure function sphere_normal(this, xyz) result(uvw) class(SurfaceSphere), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -750,59 +412,6 @@ contains f = y*y + z*z - this%r2*x*x end function x_cone_evaluate - pure function x_cone_distance(this, xyz, uvw, coincident) result(d) - class(SurfaceXCone), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: x, y, z, k, a, b, c, quad - - x = xyz(1) - this%x0 - y = xyz(2) - this%y0 - z = xyz(3) - this%z0 - a = uvw(2)*uvw(2) + uvw(3)*uvw(3) - this%r2*uvw(1)*uvw(1) - k = y*uvw(2) + z*uvw(3) - this%r2*x*uvw(1) - c = y*y + z*z - this%r2*x*x - quad = k*k - a*c - - if (quad < ZERO) then - ! no intersection with cone - - d = INFINITY - - elseif (coincident .or. abs(c) < FP_COINCIDENT) then - ! particle is on the cone, thus one distance is positive/negative and the - ! other is zero. The sign of k determines which distance is zero and which - ! is not. - - if (k >= ZERO) then - d = (-k - sqrt(quad))/a - else - d = (-k + sqrt(quad))/a - end if - - else - ! calculate both solutions to the quadratic - quad = sqrt(quad) - d = (-k - quad)/a - b = (-k + quad)/a - - ! determine the smallest positive solution - if (d < ZERO) then - if (b > ZERO) then - d = b - end if - else - if (b > ZERO) d = min(d, b) - end if - end if - - ! If the distance was negative, set boundary distance to infinity - if (d <= ZERO) d = INFINITY - end function x_cone_distance - pure function x_cone_normal(this, xyz) result(uvw) class(SurfaceXCone), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -830,59 +439,6 @@ contains f = x*x + z*z - this%r2*y*y end function y_cone_evaluate - pure function y_cone_distance(this, xyz, uvw, coincident) result(d) - class(SurfaceYCone), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: x, y, z, k, a, b, c, quad - - x = xyz(1) - this%x0 - y = xyz(2) - this%y0 - z = xyz(3) - this%z0 - a = uvw(1)*uvw(1) + uvw(3)*uvw(3) - this%r2*uvw(2)*uvw(2) - k = x*uvw(1) + z*uvw(3) - this%r2*y*uvw(2) - c = x*x + z*z - this%r2*y*y - quad = k*k - a*c - - if (quad < ZERO) then - ! no intersection with cone - - d = INFINITY - - elseif (coincident .or. abs(c) < FP_COINCIDENT) then - ! particle is on the cone, thus one distance is positive/negative and the - ! other is zero. The sign of k determines which distance is zero and which - ! is not. - - if (k >= ZERO) then - d = (-k - sqrt(quad))/a - else - d = (-k + sqrt(quad))/a - end if - - else - ! calculate both solutions to the quadratic - quad = sqrt(quad) - d = (-k - quad)/a - b = (-k + quad)/a - - ! determine the smallest positive solution - if (d < ZERO) then - if (b > ZERO) then - d = b - end if - else - if (b > ZERO) d = min(d, b) - end if - end if - - ! If the distance was negative, set boundary distance to infinity - if (d <= ZERO) d = INFINITY - end function y_cone_distance - pure function y_cone_normal(this, xyz) result(uvw) class(SurfaceYCone), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -910,59 +466,6 @@ contains f = x*x + y*y - this%r2*z*z end function z_cone_evaluate - pure function z_cone_distance(this, xyz, uvw, coincident) result(d) - class(SurfaceZCone), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: x, y, z, k, a, b, c, quad - - x = xyz(1) - this%x0 - y = xyz(2) - this%y0 - z = xyz(3) - this%z0 - a = uvw(1)*uvw(1) + uvw(2)*uvw(2) - this%r2*uvw(3)*uvw(3) - k = x*uvw(1) + y*uvw(2) - this%r2*z*uvw(3) - c = x*x + y*y - this%r2*z*z - quad = k*k - a*c - - if (quad < ZERO) then - ! no intersection with cone - - d = INFINITY - - elseif (coincident .or. abs(c) < FP_COINCIDENT) then - ! particle is on the cone, thus one distance is positive/negative and the - ! other is zero. The sign of k determines which distance is zero and which - ! is not. - - if (k >= ZERO) then - d = (-k - sqrt(quad))/a - else - d = (-k + sqrt(quad))/a - end if - - else - ! calculate both solutions to the quadratic - quad = sqrt(quad) - d = (-k - quad)/a - b = (-k + quad)/a - - ! determine the smallest positive solution - if (d < ZERO) then - if (b > ZERO) then - d = b - end if - else - if (b > ZERO) d = min(d, b) - end if - end if - - ! If the distance was negative, set boundary distance to infinity - if (d <= ZERO) d = INFINITY - end function z_cone_distance - pure function z_cone_normal(this, xyz) result(uvw) class(SurfaceZCone), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -989,65 +492,6 @@ contains end associate end function quadric_evaluate - pure function quadric_distance(this, xyz, uvw, coincident) result(d) - class(SurfaceQuadric), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(in) :: uvw(3) - logical, intent(in) :: coincident - real(8) :: d - - real(8) :: a, k, c - real(8) :: quad, b - - associate (x => xyz(1), y => xyz(2), z => xyz(3), & - u => uvw(1), v => uvw(2), w => uvw(3)) - - a = this%A*u*u + this%B*v*v + this%C*w*w + this%D*u*v + this%E*v*w + & - this%F*u*w - k = (this%A*u*x + this%B*v*y + this%C*w*z + HALF*(this%D*(u*y + v*x) + & - this%E*(v*z + w*y) + this%F*(w*x + u*z) + this%G*u + this%H*v + & - this%J*w)) - c = this%A*x*x + this%B*y*y + this%C*z*z + this%D*x*y + this%E*y*z + & - this%F*x*z + this%G*x + this%H*y + this%J*z + this%K - quad = k*k - a*c - - if (quad < ZERO) then - ! no intersection with surface - - d = INFINITY - - elseif (coincident .or. abs(c) < FP_COINCIDENT) then - ! particle is on the surface, thus one distance is positive/negative and - ! the other is zero. The sign of k determines which distance is zero and - ! which is not. - - if (k >= ZERO) then - d = (-k - sqrt(quad))/a - else - d = (-k + sqrt(quad))/a - end if - - else - ! calculate both solutions to the quadratic - quad = sqrt(quad) - d = (-k - quad)/a - b = (-k + quad)/a - - ! determine the smallest positive solution - if (d < ZERO) then - if (b > ZERO) then - d = b - end if - else - if (b > ZERO) d = min(d, b) - end if - end if - - ! If the distance was negative, set boundary distance to infinity - if (d <= ZERO) d = INFINITY - end associate - end function quadric_distance - pure function quadric_normal(this, xyz) result(uvw) class(SurfaceQuadric), intent(in) :: this real(8), intent(in) :: xyz(3) From 38aa76c9dc9cdab0c10f35c2355d20f9842b65bc Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 19 Jan 2018 00:54:39 -0500 Subject: [PATCH 122/282] Implement C++ periodic BCs --- src/geometry.F90 | 11 +++ src/surface_header.C | 136 +++++++++++++++++++++++++++++++++-- src/surface_header.F90 | 157 +---------------------------------------- src/tracking.F90 | 63 +---------------- 4 files changed, 145 insertions(+), 222 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index 873582dec..a0a19f1bf 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -44,6 +44,17 @@ module geometry real(C_DOUBLE), intent(in) :: xyz(3); real(C_DOUBLE), intent(out) :: uvw(3); end subroutine surface_normal_c + + function surface_periodic_c(surf_ind1, surf_ind2, xyz, uvw) & + bind(C, name="surface_periodic") result(rotational) + use ISO_C_BINDING + implicit none + integer(C_INT), intent(in), value :: surf_ind1; + integer(C_INT), intent(in), value :: surf_ind2; + real(C_DOUBLE), intent(inout) :: xyz(3); + real(C_DOUBLE), intent(inout) :: uvw(3); + logical(C_BOOL) :: rotational + end function surface_periodic_c end interface contains diff --git a/src/surface_header.C b/src/surface_header.C index cf200e5e4..23fe5adb3 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -5,6 +5,7 @@ #include "pugixml/pugixml.hpp" // DEBUGGING +#include #include //============================================================================== @@ -89,14 +90,14 @@ public: neighbor_neg[]; //!< List of cells on negative side int bc; //!< Boundary condition //TODO: switch that zero to a NONE constant. - int i_periodic = 0; //!< Index of corresponding periodic surface + //int i_periodic = 0; //!< Index of corresponding periodic surface char name[104]; //!< User-defined name //! Determine which side of a surface a point lies on. //! @param xyz[3] The 3D Cartesian coordinate of a point. //! @param uvw[3] A direction used to "break ties" and pick a sense when the //! point is very close to the surface. - //! @return True if the point is on the "positive" side of the surface and + //! @return true if the point is on the "positive" side of the surface and //! false otherwise. bool sense(const double xyz[3], const double uvw[3]) const; @@ -163,6 +164,29 @@ Surface::reflect(const double xyz[3], double uvw[3]) const uvw[2] -= 2.0 * projection / magnitude * norm[2]; } +//============================================================================== +//! A `Surface` that supports periodic boundary conditions. +//! +//! Translational periodicity is supported for the `XPlane`, `YPlane`, `ZPlane`, +//! and `Plane` types. Rotational periodicity is supported for +//! `XPlane`-`YPlane` pairs. +//============================================================================== + +class PeriodicSurface : public Surface +{ +public: + //! Translate a particle onto this surface from a periodic partner surface. + //! @param other A pointer to the partner surface in this periodic BC. + //! @param xyz[3] A point on the partner surface that will be translated onto + //! this surface. + //! @param uvw[3] A direction that will be rotated for systems with rotational + //! periodicity. + //! @return true if this surface and its partner make a rotationally-periodic + //! boundary condition. + virtual bool periodic_translate(PeriodicSurface *other, double xyz[3], + double uvw[3]) const = 0; +}; + //============================================================================== // Generic functions for x-, y-, and z-, planes. //============================================================================== @@ -203,7 +227,7 @@ axis_aligned_plane_normal(const double xyz[3], double uvw[3]) //! The plane is described by the equation \f$x - x_0 = 0\f$ //============================================================================== -class SurfaceXPlane : public Surface +class SurfaceXPlane : public PeriodicSurface { double x0; public: @@ -212,6 +236,8 @@ public: double distance(const double xyz[3], const double uvw[3], const bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) + const; }; SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) @@ -235,6 +261,32 @@ inline void SurfaceXPlane::normal(const double xyz[3], double uvw[3]) const axis_aligned_plane_normal<0, 1, 2>(xyz, uvw); } +bool SurfaceXPlane::periodic_translate(PeriodicSurface *other, double xyz[3], + double uvw[3]) const +{ + double other_norm[3]; + other->normal(xyz, other_norm); + if (other_norm[0] == 1 and other_norm[1] == 0 and other_norm[2] == 0) { + xyz[0] = x0; + return false; + } else { + // Assume the partner is an YPlane (the only supported partner). Use the + // evaluate function to find y0, then adjust xyz and uvw for rotational + // symmetry. + double xyz_test[3] {0, 0, 0}; + double y0 = -other->evaluate(xyz_test); + xyz[1] = xyz[0] - x0 + y0; + xyz[0] = x0; + xyz[2] = xyz[2]; + + double u = uvw[0]; + uvw[0] = -uvw[1]; + uvw[1] = u; + + return true; + } +}; + //============================================================================== // SurfaceYPlane //! A plane perpendicular to the y-axis. @@ -242,7 +294,7 @@ inline void SurfaceXPlane::normal(const double xyz[3], double uvw[3]) const //! The plane is described by the equation \f$y - y_0 = 0\f$ //============================================================================== -class SurfaceYPlane : public Surface +class SurfaceYPlane : public PeriodicSurface { double y0; public: @@ -251,6 +303,8 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) + const; }; SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) @@ -274,6 +328,32 @@ inline void SurfaceYPlane::normal(const double xyz[3], double uvw[3]) const axis_aligned_plane_normal<1, 0, 2>(xyz, uvw); } +bool SurfaceYPlane::periodic_translate(PeriodicSurface *other, double xyz[3], + double uvw[3]) const +{ + double other_norm[3]; + other->normal(xyz, other_norm); + if (other_norm[0] == 0 and other_norm[1] == 1 and other_norm[2] == 0) { + // The periodic partner is also aligned along y. Just change the y coord. + xyz[1] = y0; + return false; + } else { + // Assume the partner is an XPlane (the only supported partner). Use the + // evaluate function to find x0, then adjust xyz and uvw for rotational + // symmetry. + double xyz_test[3] {0, 0, 0}; + double x0 = -other->evaluate(xyz_test); + xyz[0] = xyz[1] - y0 + x0; + xyz[1] = y0; + + double u = uvw[0]; + uvw[0] = uvw[1]; + uvw[1] = -u; + + return true; + } +}; + //============================================================================== // SurfaceZPlane //! A plane perpendicular to the z-axis. @@ -281,7 +361,7 @@ inline void SurfaceYPlane::normal(const double xyz[3], double uvw[3]) const //! The plane is described by the equation \f$z - z_0 = 0\f$ //============================================================================== -class SurfaceZPlane : public Surface +class SurfaceZPlane : public PeriodicSurface { double z0; public: @@ -290,6 +370,8 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) + const; }; SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) @@ -313,6 +395,14 @@ inline void SurfaceZPlane::normal(const double xyz[3], double uvw[3]) const axis_aligned_plane_normal<2, 0, 1>(xyz, uvw); } +bool SurfaceZPlane::periodic_translate(PeriodicSurface *other, double xyz[3], + double uvw[3]) const +{ + // Assume the other plane is aligned along z. Just change the z coord. + xyz[2] = z0; + return false; +}; + //============================================================================== // SurfacePlane //! A general plane. @@ -320,7 +410,7 @@ inline void SurfaceZPlane::normal(const double xyz[3], double uvw[3]) const //! The plane is described by the equation \f$A x + B y + C z - D = 0\f$ //============================================================================== -class SurfacePlane : public Surface +class SurfacePlane : public PeriodicSurface { double A, B, C, D; public: @@ -329,6 +419,8 @@ public: double distance(const double xyz[3], const double uvw[3], const bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) + const; }; SurfacePlane::SurfacePlane(pugi::xml_node surf_node) @@ -365,6 +457,22 @@ SurfacePlane::normal(const double xyz[3], double uvw[3]) const uvw[2] = C; } +bool SurfacePlane::periodic_translate(PeriodicSurface *other, double xyz[3], + double uvw[3]) const +{ + // This function assumes the other plane shares this plane's normal direction. + + // Determine the distance to intersection. + double d = evaluate(xyz) / (A*A + B*B + C*C); + + // Move the particle that distance along the normal vector. + xyz[0] -= d * A; + xyz[1] -= d * B; + xyz[2] -= d * C; + + return false; +} + //============================================================================== // Generic functions for x-, y-, and z-, cylinders //============================================================================== @@ -1041,3 +1149,19 @@ surface_normal(int surf_ind, double xyz[3], double uvw[3]) { return surfaces_c[surf_ind]->normal(xyz, uvw); } + +extern "C" bool +surface_periodic(int surf_ind1, int surf_ind2, double xyz[3], double uvw[3]) +{ + // Hopefully this function has only been called on a pair of surfaces that + // support periodic BCs (checking should have been done when reading the + // geometry XML). Downcast the surfaces to the PeriodicSurface type so we + // can call the periodic_translate method. + Surface *surf1_gen = surfaces_c[surf_ind1]; + PeriodicSurface *surf1 = dynamic_cast(surf1_gen); + Surface *surf2_gen = surfaces_c[surf_ind2]; + PeriodicSurface *surf2 = dynamic_cast(surf2_gen); + + // Call the type-bound methods. + return surf2->periodic_translate(surf1, xyz, uvw); +} diff --git a/src/surface_header.F90 b/src/surface_header.F90 index b0da37c1e..d9a00170b 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -22,18 +22,10 @@ module surface_header character(len=104) :: name = "" ! User-defined name contains procedure :: reflect - procedure(surface_evaluate_), deferred :: evaluate procedure(surface_normal_), deferred :: normal end type Surface abstract interface - pure function surface_evaluate_(this, xyz) result(f) - import Surface - class(Surface), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - end function surface_evaluate_ - pure function surface_normal_(this, xyz) result(uvw) import Surface class(Surface), intent(in) :: this @@ -52,15 +44,13 @@ module surface_header !=============================================================================== ! All the derived types below are extensions of the abstract Surface type. They -! inherent the reflect() type-bound procedure and must implement evaluate(), -! and normal() +! inherent the reflect() type-bound procedure and must implement normal() !=============================================================================== type, extends(Surface) :: SurfaceXPlane ! x = x0 real(8) :: x0 contains - procedure :: evaluate => x_plane_evaluate procedure :: normal => x_plane_normal end type SurfaceXPlane @@ -68,7 +58,6 @@ module surface_header ! y = y0 real(8) :: y0 contains - procedure :: evaluate => y_plane_evaluate procedure :: normal => y_plane_normal end type SurfaceYPlane @@ -76,7 +65,6 @@ module surface_header ! z = z0 real(8) :: z0 contains - procedure :: evaluate => z_plane_evaluate procedure :: normal => z_plane_normal end type SurfaceZPlane @@ -87,7 +75,6 @@ module surface_header real(8) :: C real(8) :: D contains - procedure :: evaluate => plane_evaluate procedure :: normal => plane_normal end type SurfacePlane @@ -97,7 +84,6 @@ module surface_header real(8) :: z0 real(8) :: r contains - procedure :: evaluate => x_cylinder_evaluate procedure :: normal => x_cylinder_normal end type SurfaceXCylinder @@ -107,7 +93,6 @@ module surface_header real(8) :: z0 real(8) :: r contains - procedure :: evaluate => y_cylinder_evaluate procedure :: normal => y_cylinder_normal end type SurfaceYCylinder @@ -117,7 +102,6 @@ module surface_header real(8) :: y0 real(8) :: r contains - procedure :: evaluate => z_cylinder_evaluate procedure :: normal => z_cylinder_normal end type SurfaceZCylinder @@ -128,7 +112,6 @@ module surface_header real(8) :: z0 real(8) :: r contains - procedure :: evaluate => sphere_evaluate procedure :: normal => sphere_normal end type SurfaceSphere @@ -139,7 +122,6 @@ module surface_header real(8) :: z0 real(8) :: r2 contains - procedure :: evaluate => x_cone_evaluate procedure :: normal => x_cone_normal end type SurfaceXCone @@ -150,7 +132,6 @@ module surface_header real(8) :: z0 real(8) :: r2 contains - procedure :: evaluate => y_cone_evaluate procedure :: normal => y_cone_normal end type SurfaceYCone @@ -161,7 +142,6 @@ module surface_header real(8) :: z0 real(8) :: r2 contains - procedure :: evaluate => z_cone_evaluate procedure :: normal => z_cone_normal end type SurfaceZCone @@ -169,7 +149,6 @@ module surface_header ! Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0 real(8) :: A, B, C, D, E, F, G, H, J, K contains - procedure :: evaluate => quadric_evaluate procedure :: normal => quadric_normal end type SurfaceQuadric @@ -214,14 +193,6 @@ contains ! SurfaceXPlane Implementation !=============================================================================== - pure function x_plane_evaluate(this, xyz) result(f) - class(SurfaceXPlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - f = xyz(1) - this%x0 - end function x_plane_evaluate - pure function x_plane_normal(this, xyz) result(uvw) class(SurfaceXPlane), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -234,14 +205,6 @@ contains ! SurfaceYPlane Implementation !=============================================================================== - pure function y_plane_evaluate(this, xyz) result(f) - class(SurfaceYPlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - f = xyz(2) - this%y0 - end function y_plane_evaluate - pure function y_plane_normal(this, xyz) result(uvw) class(SurfaceYPlane), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -254,16 +217,6 @@ contains ! SurfaceZPlane Implementation !=============================================================================== - pure function z_plane_evaluate(this, xyz) result(f) - - class(SurfaceZPlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - f = xyz(3) - this%z0 - - end function z_plane_evaluate - pure function z_plane_normal(this, xyz) result(uvw) class(SurfaceZPlane), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -276,14 +229,6 @@ contains ! SurfacePlane Implementation !=============================================================================== - pure function plane_evaluate(this, xyz) result(f) - class(SurfacePlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - f = this%A*xyz(1) + this%B*xyz(2) + this%C*xyz(3) - this%D - end function plane_evaluate - pure function plane_normal(this, xyz) result(uvw) class(SurfacePlane), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -296,18 +241,6 @@ contains ! SurfaceXCylinder Implementation !=============================================================================== - pure function x_cylinder_evaluate(this, xyz) result(f) - class(SurfaceXCylinder), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - real(8) :: y, z - - y = xyz(2) - this%y0 - z = xyz(3) - this%z0 - f = y*y + z*z - this%r*this%r - end function x_cylinder_evaluate - pure function x_cylinder_normal(this, xyz) result(uvw) class(SurfaceXCylinder), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -322,18 +255,6 @@ contains ! SurfaceYCylinder Implementation !=============================================================================== - pure function y_cylinder_evaluate(this, xyz) result(f) - class(SurfaceYCylinder), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - real(8) :: x, z - - x = xyz(1) - this%x0 - z = xyz(3) - this%z0 - f = x*x + z*z - this%r*this%r - end function y_cylinder_evaluate - pure function y_cylinder_normal(this, xyz) result(uvw) class(SurfaceYCylinder), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -348,18 +269,6 @@ contains ! SurfaceZCylinder Implementation !=============================================================================== - pure function z_cylinder_evaluate(this, xyz) result(f) - class(SurfaceZCylinder), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - real(8) :: x, y - - x = xyz(1) - this%x0 - y = xyz(2) - this%y0 - f = x*x + y*y - this%r*this%r - end function z_cylinder_evaluate - pure function z_cylinder_normal(this, xyz) result(uvw) class(SurfaceZCylinder), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -374,19 +283,6 @@ contains ! SurfaceSphere Implementation !=============================================================================== - pure function sphere_evaluate(this, xyz) result(f) - class(SurfaceSphere), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - real(8) :: x, y, z - - x = xyz(1) - this%x0 - y = xyz(2) - this%y0 - z = xyz(3) - this%z0 - f = x*x + y*y + z*z - this%r*this%r - end function sphere_evaluate - pure function sphere_normal(this, xyz) result(uvw) class(SurfaceSphere), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -399,19 +295,6 @@ contains ! SurfaceXCone Implementation !=============================================================================== - pure function x_cone_evaluate(this, xyz) result(f) - class(SurfaceXCone), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - real(8) :: x, y, z - - x = xyz(1) - this%x0 - y = xyz(2) - this%y0 - z = xyz(3) - this%z0 - f = y*y + z*z - this%r2*x*x - end function x_cone_evaluate - pure function x_cone_normal(this, xyz) result(uvw) class(SurfaceXCone), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -426,19 +309,6 @@ contains ! SurfaceYCone Implementation !=============================================================================== - pure function y_cone_evaluate(this, xyz) result(f) - class(SurfaceYCone), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - real(8) :: x, y, z - - x = xyz(1) - this%x0 - y = xyz(2) - this%y0 - z = xyz(3) - this%z0 - f = x*x + z*z - this%r2*y*y - end function y_cone_evaluate - pure function y_cone_normal(this, xyz) result(uvw) class(SurfaceYCone), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -453,19 +323,6 @@ contains ! SurfaceZCone Implementation !=============================================================================== - pure function z_cone_evaluate(this, xyz) result(f) - class(SurfaceZCone), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - real(8) :: x, y, z - - x = xyz(1) - this%x0 - y = xyz(2) - this%y0 - z = xyz(3) - this%z0 - f = x*x + y*y - this%r2*z*z - end function z_cone_evaluate - pure function z_cone_normal(this, xyz) result(uvw) class(SurfaceZCone), intent(in) :: this real(8), intent(in) :: xyz(3) @@ -480,18 +337,6 @@ contains ! SurfaceQuadric Implementation !=============================================================================== - pure function quadric_evaluate(this, xyz) result(f) - class(SurfaceQuadric), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: f - - associate (x => xyz(1), y => xyz(2), z => xyz(3)) - f = x*(this%A*x + this%D*y + this%G) + & - y*(this%B*y + this%E*z + this%H) + & - z*(this%C*z + this%F*x + this%J) + this%K - end associate - end function quadric_evaluate - pure function quadric_normal(this, xyz) result(uvw) class(SurfaceQuadric), intent(in) :: this real(8), intent(in) :: xyz(3) diff --git a/src/tracking.F90 b/src/tracking.F90 index 65769a7f1..4f0f110bc 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -5,7 +5,7 @@ module tracking use error, only: fatal_error, warning, write_message use geometry_header, only: cells use geometry, only: find_cell, distance_to_boundary, cross_lattice, & - check_cell_overlap + check_cell_overlap, surface_periodic_c use message_passing use mgxs_header use nuclide_header @@ -290,7 +290,6 @@ contains real(8) :: v ! y-component of direction real(8) :: w ! z-component of direction real(8) :: norm ! "norm" of surface normal - real(8) :: d ! distance between point and plane real(8) :: xyz(3) ! Saved global coordinate integer :: i_surface ! index in surfaces logical :: rotational ! if rotational periodic BC applied @@ -412,64 +411,8 @@ contains p % coord(1) % xyz = xyz end if - rotational = .false. - select type (surf) - type is (SurfaceXPlane) - select type (opposite => surfaces(surf % i_periodic) % obj) - type is (SurfaceXPlane) - p % coord(1) % xyz(1) = opposite % x0 - type is (SurfaceYPlane) - rotational = .true. - - ! Rotate direction - u = p % coord(1) % uvw(1) - v = p % coord(1) % uvw(2) - p % coord(1) % uvw(1) = v - p % coord(1) % uvw(2) = -u - - ! Rotate position - p % coord(1) % xyz(1) = surf % x0 + p % coord(1) % xyz(2) - opposite % y0 - p % coord(1) % xyz(2) = opposite % y0 - end select - - type is (SurfaceYPlane) - select type (opposite => surfaces(surf % i_periodic) % obj) - type is (SurfaceYPlane) - p % coord(1) % xyz(2) = opposite % y0 - type is (SurfaceXPlane) - rotational = .true. - - ! Rotate direction - u = p % coord(1) % uvw(1) - v = p % coord(1) % uvw(2) - p % coord(1) % uvw(1) = -v - p % coord(1) % uvw(2) = u - - ! Rotate position - p % coord(1) % xyz(2) = surf % y0 + p % coord(1) % xyz(1) - opposite % x0 - p % coord(1) % xyz(1) = opposite % x0 - end select - - type is (SurfaceZPlane) - select type (opposite => surfaces(surf % i_periodic) % obj) - type is (SurfaceZPlane) - p % coord(1) % xyz(3) = opposite % z0 - end select - - type is (SurfacePlane) - select type (opposite => surfaces(surf % i_periodic) % obj) - type is (SurfacePlane) - ! Get surface normal for opposite plane - xyz(:) = opposite % normal(p % coord(1) % xyz) - - ! Determine distance to plane - norm = xyz(1)*xyz(1) + xyz(2)*xyz(2) + xyz(3)*xyz(3) - d = opposite % evaluate(p % coord(1) % xyz) / norm - - ! Move particle along normal vector based on distance - p % coord(1) % xyz(:) = p % coord(1) % xyz(:) - d*xyz - end select - end select + rotational = surface_periodic_c(i_surface-1, surf % i_periodic-1, & + p % coord(1) % xyz, p % coord(1) % uvw) ! Reassign particle's surface if (rotational) then From 7af296902bbb9ae7b0409eafb5060feacfb1d2ec Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 19 Jan 2018 01:27:04 -0500 Subject: [PATCH 123/282] Remove Fortran surface reflection code --- src/geometry.F90 | 15 ++- src/surface_header.C | 14 ++- src/surface_header.F90 | 226 +---------------------------------------- src/tracking.F90 | 5 +- 4 files changed, 26 insertions(+), 234 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index a0a19f1bf..c19a6c8dd 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -25,6 +25,15 @@ module geometry logical(C_BOOL) :: sense; end function surface_sense_c + pure subroutine surface_reflect_c(surf_ind, xyz, uvw) & + bind(C, name='surface_reflect') + use ISO_C_BINDING + implicit none + integer(C_INT), intent(in), value :: surf_ind; + real(C_DOUBLE), intent(in) :: xyz(3); + real(C_DOUBLE), intent(inout) :: uvw(3); + end subroutine surface_reflect_c + pure function surface_distance_c(surf_ind, xyz, uvw, coincident) & bind(C, name='surface_distance') result(d) use ISO_C_BINDING @@ -525,6 +534,7 @@ contains real(8) :: d_surf ! distance to surface real(8) :: x0,y0,z0 ! coefficients for surface real(8) :: xyz_cross(3) ! coordinates at projected surface crossing + real(8) :: surf_uvw(3) ! surface normal direction logical :: coincident ! is particle on surface? type(Cell), pointer :: c class(Surface), pointer :: surf @@ -782,9 +792,8 @@ contains ! traveling into if the surface is crossed if (.not. c % simple) then xyz_cross(:) = p % coord(j) % xyz + d_surf*p % coord(j) % uvw - surf => surfaces(abs(level_surf_cross)) % obj - if (dot_product(p % coord(j) % uvw, & - surf % normal(xyz_cross)) > ZERO) then + call surface_normal_c(abs(level_surf_cross)-1, xyz_cross, surf_uvw) + if (dot_product(p % coord(j) % uvw, surf_uvw) > ZERO) then surface_crossed = abs(level_surf_cross) else surface_crossed = -abs(level_surf_cross) diff --git a/src/surface_header.C b/src/surface_header.C index 23fe5adb3..45fb62399 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -233,8 +233,8 @@ class SurfaceXPlane : public PeriodicSurface public: SurfaceXPlane(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - const bool coincident) const; + double distance(const double xyz[3], const double uvw[3], bool coincident) + const; void normal(const double xyz[3], double uvw[3]) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; @@ -416,8 +416,8 @@ class SurfacePlane : public PeriodicSurface public: SurfacePlane(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - const bool coincident) const; + double distance(const double xyz[3], const double uvw[3], bool coincident) + const; void normal(const double xyz[3], double uvw[3]) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; @@ -1138,6 +1138,12 @@ surface_sense(int surf_ind, double xyz[3], double uvw[3]) return surfaces_c[surf_ind]->sense(xyz, uvw); } +extern "C" void +surface_reflect(int surf_ind, double xyz[3], double uvw[3]) +{ + surfaces_c[surf_ind]->reflect(xyz, uvw); +} + extern "C" double surface_distance(int surf_ind, double xyz[3], double uvw[3], bool coincident) { diff --git a/src/surface_header.F90 b/src/surface_header.F90 index d9a00170b..d63290387 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -2,7 +2,7 @@ module surface_header use, intrinsic :: ISO_C_BINDING - use constants, only: NONE, ONE, TWO, ZERO, HALF, INFINITY, FP_COINCIDENT + use constants, only: NONE use dict_header, only: DictIntInt implicit none @@ -20,20 +20,8 @@ module surface_header integer :: bc ! Boundary condition integer :: i_periodic = NONE ! Index of corresponding periodic surface character(len=104) :: name = "" ! User-defined name - contains - procedure :: reflect - procedure(surface_normal_), deferred :: normal end type Surface - abstract interface - pure function surface_normal_(this, xyz) result(uvw) - import Surface - class(Surface), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - end function surface_normal_ - end interface - !=============================================================================== ! SURFACECONTAINER allows us to store an array of different types of surfaces !=============================================================================== @@ -50,22 +38,16 @@ module surface_header type, extends(Surface) :: SurfaceXPlane ! x = x0 real(8) :: x0 - contains - procedure :: normal => x_plane_normal end type SurfaceXPlane type, extends(Surface) :: SurfaceYPlane ! y = y0 real(8) :: y0 - contains - procedure :: normal => y_plane_normal end type SurfaceYPlane type, extends(Surface) :: SurfaceZPlane ! z = z0 real(8) :: z0 - contains - procedure :: normal => z_plane_normal end type SurfaceZPlane type, extends(Surface) :: SurfacePlane @@ -74,8 +56,6 @@ module surface_header real(8) :: B real(8) :: C real(8) :: D - contains - procedure :: normal => plane_normal end type SurfacePlane type, extends(Surface) :: SurfaceXCylinder @@ -83,8 +63,6 @@ module surface_header real(8) :: y0 real(8) :: z0 real(8) :: r - contains - procedure :: normal => x_cylinder_normal end type SurfaceXCylinder type, extends(Surface) :: SurfaceYCylinder @@ -92,8 +70,6 @@ module surface_header real(8) :: x0 real(8) :: z0 real(8) :: r - contains - procedure :: normal => y_cylinder_normal end type SurfaceYCylinder type, extends(Surface) :: SurfaceZCylinder @@ -101,8 +77,6 @@ module surface_header real(8) :: x0 real(8) :: y0 real(8) :: r - contains - procedure :: normal => z_cylinder_normal end type SurfaceZCylinder type, extends(Surface) :: SurfaceSphere @@ -111,8 +85,6 @@ module surface_header real(8) :: y0 real(8) :: z0 real(8) :: r - contains - procedure :: normal => sphere_normal end type SurfaceSphere type, extends(Surface) :: SurfaceXCone @@ -121,8 +93,6 @@ module surface_header real(8) :: y0 real(8) :: z0 real(8) :: r2 - contains - procedure :: normal => x_cone_normal end type SurfaceXCone type, extends(Surface) :: SurfaceYCone @@ -131,8 +101,6 @@ module surface_header real(8) :: y0 real(8) :: z0 real(8) :: r2 - contains - procedure :: normal => y_cone_normal end type SurfaceYCone type, extends(Surface) :: SurfaceZCone @@ -141,15 +109,11 @@ module surface_header real(8) :: y0 real(8) :: z0 real(8) :: r2 - contains - procedure :: normal => z_cone_normal end type SurfaceZCone type, extends(Surface) :: SurfaceQuadric ! Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0 real(8) :: A, B, C, D, E, F, G, H, J, K - contains - procedure :: normal => quadric_normal end type SurfaceQuadric integer(C_INT32_T), bind(C) :: n_surfaces ! # of surfaces @@ -161,194 +125,6 @@ module surface_header contains -!=============================================================================== -! REFLECT determines the direction a particle will travel if it is specularly -! reflected from the surface at a given position and direction. The position is -! needed because the reflection is performed using the surface normal, which -! depends on the position for second-order surfaces. -!=============================================================================== - - pure subroutine reflect(this, xyz, uvw) - class(Surface), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8), intent(inout) :: uvw(3) - - real(8) :: projection - real(8) :: magnitude - real(8) :: n(3) - - ! Construct normal vector - n(:) = this%normal(xyz) - - ! Determine projection of direction onto normal and squared magnitude of - ! normal - projection = n(1)*uvw(1) + n(2)*uvw(2) + n(3)*uvw(3) - magnitude = n(1)*n(1) + n(2)*n(2) + n(3)*n(3) - - ! Reflect direction according to normal - uvw(:) = uvw - TWO*projection/magnitude * n - end subroutine reflect - -!=============================================================================== -! SurfaceXPlane Implementation -!=============================================================================== - - pure function x_plane_normal(this, xyz) result(uvw) - class(SurfaceXPlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - uvw(:) = [ONE, ZERO, ZERO] - end function x_plane_normal - -!=============================================================================== -! SurfaceYPlane Implementation -!=============================================================================== - - pure function y_plane_normal(this, xyz) result(uvw) - class(SurfaceYPlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - uvw(:) = [ZERO, ONE, ZERO] - end function y_plane_normal - -!=============================================================================== -! SurfaceZPlane Implementation -!=============================================================================== - - pure function z_plane_normal(this, xyz) result(uvw) - class(SurfaceZPlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - uvw(:) = [ZERO, ZERO, ONE] - end function z_plane_normal - -!=============================================================================== -! SurfacePlane Implementation -!=============================================================================== - - pure function plane_normal(this, xyz) result(uvw) - class(SurfacePlane), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - uvw(:) = [this%A, this%B, this%C] - end function plane_normal - -!=============================================================================== -! SurfaceXCylinder Implementation -!=============================================================================== - - pure function x_cylinder_normal(this, xyz) result(uvw) - class(SurfaceXCylinder), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - uvw(1) = ZERO - uvw(2) = TWO*(xyz(2) - this%y0) - uvw(3) = TWO*(xyz(3) - this%z0) - end function x_cylinder_normal - -!=============================================================================== -! SurfaceYCylinder Implementation -!=============================================================================== - - pure function y_cylinder_normal(this, xyz) result(uvw) - class(SurfaceYCylinder), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - uvw(1) = TWO*(xyz(1) - this%x0) - uvw(2) = ZERO - uvw(3) = TWO*(xyz(3) - this%z0) - end function y_cylinder_normal - -!=============================================================================== -! SurfaceZCylinder Implementation -!=============================================================================== - - pure function z_cylinder_normal(this, xyz) result(uvw) - class(SurfaceZCylinder), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - uvw(1) = TWO*(xyz(1) - this%x0) - uvw(2) = TWO*(xyz(2) - this%y0) - uvw(3) = ZERO - end function z_cylinder_normal - -!=============================================================================== -! SurfaceSphere Implementation -!=============================================================================== - - pure function sphere_normal(this, xyz) result(uvw) - class(SurfaceSphere), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - uvw(:) = TWO*(xyz - [this%x0, this%y0, this%z0]) - end function sphere_normal - -!=============================================================================== -! SurfaceXCone Implementation -!=============================================================================== - - pure function x_cone_normal(this, xyz) result(uvw) - class(SurfaceXCone), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - uvw(1) = -TWO*this%r2*(xyz(1) - this%x0) - uvw(2) = TWO*(xyz(2) - this%y0) - uvw(3) = TWO*(xyz(3) - this%z0) - end function x_cone_normal - -!=============================================================================== -! SurfaceYCone Implementation -!=============================================================================== - - pure function y_cone_normal(this, xyz) result(uvw) - class(SurfaceYCone), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - uvw(1) = TWO*(xyz(1) - this%x0) - uvw(2) = -TWO*this%r2*(xyz(2) - this%y0) - uvw(3) = TWO*(xyz(3) - this%z0) - end function y_cone_normal - -!=============================================================================== -! SurfaceZCone Implementation -!=============================================================================== - - pure function z_cone_normal(this, xyz) result(uvw) - class(SurfaceZCone), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - uvw(1) = TWO*(xyz(1) - this%x0) - uvw(2) = TWO*(xyz(2) - this%y0) - uvw(3) = -TWO*this%r2*(xyz(3) - this%z0) - end function z_cone_normal - -!=============================================================================== -! SurfaceQuadric Implementation -!=============================================================================== - - pure function quadric_normal(this, xyz) result(uvw) - class(SurfaceQuadric), intent(in) :: this - real(8), intent(in) :: xyz(3) - real(8) :: uvw(3) - - associate (x => xyz(1), y => xyz(2), z => xyz(3)) - uvw(1) = TWO*this%A*x + this%D*y + this%F*z + this%G - uvw(2) = TWO*this%B*y + this%D*x + this%E*z + this%H - uvw(3) = TWO*this%C*z + this%E*y + this%F*x + this%J - end associate - end function quadric_normal - !=============================================================================== ! FREE_MEMORY_SURFACES deallocates global arrays defined in this module !=============================================================================== diff --git a/src/tracking.F90 b/src/tracking.F90 index 4f0f110bc..d616212ec 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -5,7 +5,8 @@ module tracking use error, only: fatal_error, warning, write_message use geometry_header, only: cells use geometry, only: find_cell, distance_to_boundary, cross_lattice, & - check_cell_overlap, surface_periodic_c + check_cell_overlap, surface_reflect_c, & + surface_periodic_c use message_passing use mgxs_header use nuclide_header @@ -354,7 +355,7 @@ contains end if ! Reflect particle off surface - call surf%reflect(p%coord(1)%xyz, p%coord(1)%uvw) + call surface_reflect_c(i_surface-1, p%coord(1)%xyz, p%coord(1)%uvw) ! Make sure new particle direction is normalized u = p%coord(1)%uvw(1) From 81ba835d145700fb298b24c25414ca9864b7767b Mon Sep 17 00:00:00 2001 From: Giud Date: Fri, 19 Jan 2018 10:14:43 -0500 Subject: [PATCH 124/282] if cant find cell in which particle is, calls mark_as_lost instead of fatal_error --- src/tracking.F90 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tracking.F90 b/src/tracking.F90 index 65769a7f1..f65151701 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -2,7 +2,7 @@ module tracking use constants use cross_section, only: calculate_xs - use error, only: fatal_error, warning, write_message + use error, only: warning, write_message use geometry_header, only: cells use geometry, only: find_cell, distance_to_boundary, cross_lattice, & check_cell_overlap @@ -85,7 +85,9 @@ contains if (p % coord(p % n_coord) % cell == NONE) then call find_cell(p, found_cell) if (.not. found_cell) then - call fatal_error("Could not locate particle " // trim(to_str(p % id))) + call p % mark_as_lost("Could not find the cell containing" & + // " particle " // trim(to_str(p %id))) + return end if ! set birth cell attribute From 57991b271db4828e8fbc92fbdf78b5892f83fb7d Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 19 Jan 2018 18:12:57 -0500 Subject: [PATCH 125/282] Start moving surface->summary.h5 to C++ --- CMakeLists.txt | 5 +- src/api.F90 | 2 +- src/cmfd_execute.F90 | 8 +- src/eigenvalue.F90 | 18 ++-- src/error.F90 | 4 +- src/geometry.F90 | 1 - src/hdf5_interface.F90 | 4 +- src/hdf5_interface.h | 47 ++++++++++ src/initialize.F90 | 12 +-- src/input_xml.F90 | 80 +++-------------- src/main.F90 | 8 +- src/mesh.F90 | 4 +- src/message_passing.F90 | 6 +- src/output.F90 | 2 +- src/simulation.F90 | 8 +- src/source.F90 | 2 +- src/state_point.F90 | 18 ++-- src/summary.F90 | 92 +++----------------- src/surface_header.C | 184 +++++++++++++++++++++++++++++++++++++++- src/surface_header.F90 | 45 ---------- src/tallies/tally.F90 | 4 +- src/tallies/trigger.F90 | 2 +- src/volume_calc.F90 | 6 +- 23 files changed, 311 insertions(+), 251 deletions(-) create mode 100644 src/hdf5_interface.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 200d836fe..58fb55e79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,14 +48,14 @@ add_definitions(-DMAX_COORD=${maxcoord}) set(MPI_ENABLED FALSE) if($ENV{FC} MATCHES "(mpi[^/]*|ftn)$") message("-- Detected MPI wrapper: $ENV{FC}") - add_definitions(-DMPI) + add_definitions(-DOPENMC_MPI) set(MPI_ENABLED TRUE) endif() # Check for Fortran 2008 MPI interface if(MPI_ENABLED AND mpif08) message("-- Using Fortran 2008 MPI bindings") - add_definitions(-DMPIF08) + add_definitions(-DOPENMC_MPIF08) endif() #=============================================================================== @@ -435,6 +435,7 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/trigger_header.F90 ) set(LIBOPENMC_CXX_SRC + src/hdf5_interface.h src/random_lcg.h src/random_lcg.cpp src/surface_header.C diff --git a/src/api.F90 b/src/api.F90 index eb9a99695..5d56b857c 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -171,7 +171,7 @@ contains ! Close FORTRAN interface. call h5close_f(err) -#ifdef MPI +#ifdef OPENMC_MPI ! Free all MPI types call MPI_TYPE_FREE(MPI_BANK, err) #endif diff --git a/src/cmfd_execute.F90 b/src/cmfd_execute.F90 index 9cdb751a4..af8d57a80 100644 --- a/src/cmfd_execute.F90 +++ b/src/cmfd_execute.F90 @@ -105,7 +105,7 @@ contains real(8) :: hxyz(3) ! cell dimensions of current ijk cell real(8) :: vol ! volume of cell real(8),allocatable :: source(:,:,:,:) ! tmp source array for entropy -#ifdef MPI +#ifdef OPENMC_MPI integer :: mpi_err ! MPI error code #endif @@ -197,7 +197,7 @@ contains end if -#ifdef MPI +#ifdef OPENMC_MPI ! Broadcast full source to all procs call MPI_BCAST(cmfd % cmfd_src, n, MPI_REAL8, 0, mpi_intracomm, mpi_err) #endif @@ -234,7 +234,7 @@ contains real(8) :: norm ! normalization factor logical :: outside ! any source sites outside mesh logical :: in_mesh ! source site is inside mesh -#ifdef MPI +#ifdef OPENMC_MPI integer :: mpi_err #endif @@ -291,7 +291,7 @@ contains if (.not. cmfd_feedback) return ! Broadcast weight factors to all procs -#ifdef MPI +#ifdef OPENMC_MPI call MPI_BCAST(cmfd % weightfactors, ng*nx*ny*nz, MPI_REAL8, 0, & mpi_intracomm, mpi_err) #endif diff --git a/src/eigenvalue.F90 b/src/eigenvalue.F90 index 27ae4063e..7a11d1fb4 100644 --- a/src/eigenvalue.F90 +++ b/src/eigenvalue.F90 @@ -42,11 +42,11 @@ contains type(Bank), save, allocatable :: & & temp_sites(:) ! local array of extra sites on each node -#ifdef MPI +#ifdef OPENMC_MPI integer :: mpi_err ! MPI error code integer(8) :: n ! number of sites to send/recv integer :: neighbor ! processor to send/recv data from -#ifdef MPIF08 +#ifdef OPENMC_MPIF08 type(MPI_Request) :: request(20) #else integer :: request(20) ! communication request for send/recving sites @@ -66,7 +66,7 @@ contains ! fission bank its own sites starts in order to ensure reproducibility by ! skipping ahead to the proper seed. -#ifdef MPI +#ifdef OPENMC_MPI start = 0_8 call MPI_EXSCAN(n_bank, start, 1, MPI_INTEGER8, MPI_SUM, & mpi_intracomm, mpi_err) @@ -148,7 +148,7 @@ contains ! neighboring processors, we have to perform an ALLGATHER to determine the ! indices for all processors -#ifdef MPI +#ifdef OPENMC_MPI ! First do an exclusive scan to get the starting indices for start = 0_8 call MPI_EXSCAN(index_temp, start, 1, MPI_INTEGER8, MPI_SUM, & @@ -191,7 +191,7 @@ contains call time_bank_sample % stop() call time_bank_sendrecv % start() -#ifdef MPI +#ifdef OPENMC_MPI ! ========================================================================== ! SEND BANK SITES TO NEIGHBORS @@ -343,14 +343,14 @@ contains subroutine calculate_generation_keff() real(8) :: keff_reduced -#ifdef MPI +#ifdef OPENMC_MPI integer :: mpi_err ! MPI error code #endif ! Get keff for this generation by subtracting off the starting value keff_generation = global_tallies(RESULT_VALUE, K_TRACKLENGTH) - keff_generation -#ifdef MPI +#ifdef OPENMC_MPI ! Combine values across all processors call MPI_ALLREDUCE(keff_generation, keff_reduced, 1, MPI_REAL8, & MPI_SUM, mpi_intracomm, mpi_err) @@ -584,7 +584,7 @@ contains real(8) :: total ! total weight in source bank logical :: sites_outside ! were there sites outside the ufs mesh? -#ifdef MPI +#ifdef OPENMC_MPI integer :: n ! total number of ufs mesh cells integer :: mpi_err ! MPI error code #endif @@ -608,7 +608,7 @@ contains call fatal_error("Source sites outside of the UFS mesh!") end if -#ifdef MPI +#ifdef OPENMC_MPI ! Send source fraction to all processors n = product(m % dimension) call MPI_BCAST(source_frac, n, MPI_REAL8, 0, mpi_intracomm, mpi_err) diff --git a/src/error.F90 b/src/error.F90 index 27004d729..ae02e0897 100644 --- a/src/error.F90 +++ b/src/error.F90 @@ -128,7 +128,7 @@ contains integer :: line_wrap ! length of line integer :: length ! length of message integer :: indent ! length of indentation -#ifdef MPI +#ifdef OPENMC_MPI integer :: mpi_err #endif @@ -180,7 +180,7 @@ contains end if end do -#ifdef MPI +#ifdef OPENMC_MPI ! Abort MPI call MPI_ABORT(mpi_intracomm, code, mpi_err) #endif diff --git a/src/geometry.F90 b/src/geometry.F90 index c19a6c8dd..f1feb3ba3 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -537,7 +537,6 @@ contains real(8) :: surf_uvw(3) ! surface normal direction logical :: coincident ! is particle on surface? type(Cell), pointer :: c - class(Surface), pointer :: surf class(Lattice), pointer :: lat ! inialize distance to infinity (huge) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index e8c39b923..410149d9e 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -124,7 +124,7 @@ contains ! Setup file access property list with parallel I/O access call h5pcreate_f(H5P_FILE_ACCESS_F, plist, hdf5_err) #ifdef PHDF5 -#ifdef MPIF08 +#ifdef OPENMC_MPIF08 call h5pset_fapl_mpio_f(plist, mpi_intracomm%MPI_VAL, & MPI_INFO_NULL%MPI_VAL, hdf5_err) #else @@ -174,7 +174,7 @@ contains ! Setup file access property list with parallel I/O access call h5pcreate_f(H5P_FILE_ACCESS_F, plist, hdf5_err) #ifdef PHDF5 -#ifdef MPIF08 +#ifdef OPENMC_MPIF08 call h5pset_fapl_mpio_f(plist, mpi_intracomm%MPI_VAL, & MPI_INFO_NULL%MPI_VAL, hdf5_err) #else diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h new file mode 100644 index 000000000..38bb7d88b --- /dev/null +++ b/src/hdf5_interface.h @@ -0,0 +1,47 @@ +#ifndef HDF5_INTERFACE_H +#define HDF5_INTERFACE_H + +#include // For std::array +#include // For strlen + +#include "hdf5.h" + + +template void +write_double_1D(hid_t group_id, char const *name, + std::array &buffer) +{ + hsize_t dims[1]{array_len}; + hid_t dataspace = H5Screate_simple(1, dims, NULL); + + hid_t dataset = H5Dcreate(group_id, name, H5T_NATIVE_DOUBLE, dataspace, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + + H5Dwrite(dataset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, + &buffer[0]); + + H5Sclose(dataspace); + H5Dclose(dataset); +} + + +void +write_string(hid_t group_id, char const *name, char const *buffer) +{ + size_t buffer_len = strlen(buffer); + hid_t datatype = H5Tcopy(H5T_C_S1); + H5Tset_size(datatype, buffer_len); + + hid_t dataspace = H5Screate(H5S_SCALAR); + + hid_t dataset = H5Dcreate(group_id, name, datatype, dataspace, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + + H5Dwrite(dataset, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); + + H5Tclose(datatype); + H5Sclose(dataspace); + H5Dclose(dataset); +} + +#endif //HDF5_INTERFACE_H diff --git a/src/initialize.F90 b/src/initialize.F90 index dd7894f96..2af93627e 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -52,8 +52,8 @@ contains ! Copy the communicator to a new variable. This is done to avoid changing ! the signature of this subroutine. If MPI is being used but no communicator ! was passed, assume MPI_COMM_WORLD. -#ifdef MPI -#ifdef MPIF08 +#ifdef OPENMC_MPI +#ifdef OPENMC_MPIF08 type(MPI_Comm), intent(in) :: comm ! MPI intracommunicator if (present(intracomm)) then comm % MPI_VAL = intracomm @@ -74,7 +74,7 @@ contains call time_total%start() call time_initialize%start() -#ifdef MPI +#ifdef OPENMC_MPI ! Setup MPI call initialize_mpi(comm) #endif @@ -108,7 +108,7 @@ contains end subroutine openmc_init -#ifdef MPI +#ifdef OPENMC_MPI !=============================================================================== ! INITIALIZE_MPI starts up the Message Passing Interface (MPI) and determines ! the number of processors the problem is being run with as well as the rank of @@ -116,7 +116,7 @@ contains !=============================================================================== subroutine initialize_mpi(intracomm) -#ifdef MPIF08 +#ifdef OPENMC_MPIF08 type(MPI_Comm), intent(in) :: intracomm ! MPI intracommunicator #else integer, intent(in) :: intracomm ! MPI intracommunicator @@ -124,7 +124,7 @@ contains integer :: mpi_err ! MPI error code integer :: bank_blocks(5) ! Count for each datatype -#ifdef MPIF08 +#ifdef OPENMC_MPIF08 type(MPI_Datatype) :: bank_types(5) #else integer :: bank_types(5) ! Datatypes diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 4994ff76b..462b9708f 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -54,7 +54,7 @@ module input_xml implicit none type(C_PTR) :: node_ptr end subroutine read_surfaces - end interface + end interface contains @@ -1081,77 +1081,23 @@ contains select type(s) type is (SurfaceXPlane) - s%x0 = coeffs(1) - ! Determine outer surfaces - xmin = min(xmin, s % x0) - xmax = max(xmax, s % x0) - if (xmin == s % x0) i_xmin = i - if (xmax == s % x0) i_xmax = i + xmin = min(xmin, coeffs(1)) + xmax = max(xmax, coeffs(1)) + if (xmin == coeffs(1)) i_xmin = i + if (xmax == coeffs(1)) i_xmax = i type is (SurfaceYPlane) - s%y0 = coeffs(1) - ! Determine outer surfaces - ymin = min(ymin, s % y0) - ymax = max(ymax, s % y0) - if (ymin == s % y0) i_ymin = i - if (ymax == s % y0) i_ymax = i + ymin = min(ymin, coeffs(1)) + ymax = max(ymax, coeffs(1)) + if (ymin == coeffs(1)) i_ymin = i + if (ymax == coeffs(1)) i_ymax = i type is (SurfaceZPlane) - s%z0 = coeffs(1) - ! Determine outer surfaces - zmin = min(zmin, s % z0) - zmax = max(zmax, s % z0) - if (zmin == s % z0) i_zmin = i - if (zmax == s % z0) i_zmax = i - type is (SurfacePlane) - s%A = coeffs(1) - s%B = coeffs(2) - s%C = coeffs(3) - s%D = coeffs(4) - type is (SurfaceXCylinder) - s%y0 = coeffs(1) - s%z0 = coeffs(2) - s%r = coeffs(3) - type is (SurfaceYCylinder) - s%x0 = coeffs(1) - s%z0 = coeffs(2) - s%r = coeffs(3) - type is (SurfaceZCylinder) - s%x0 = coeffs(1) - s%y0 = coeffs(2) - s%r = coeffs(3) - type is (SurfaceSphere) - s%x0 = coeffs(1) - s%y0 = coeffs(2) - s%z0 = coeffs(3) - s%r = coeffs(4) - type is (SurfaceXCone) - s%x0 = coeffs(1) - s%y0 = coeffs(2) - s%z0 = coeffs(3) - s%r2 = coeffs(4) - type is (SurfaceYCone) - s%x0 = coeffs(1) - s%y0 = coeffs(2) - s%z0 = coeffs(3) - s%r2 = coeffs(4) - type is (SurfaceZCone) - s%x0 = coeffs(1) - s%y0 = coeffs(2) - s%z0 = coeffs(3) - s%r2 = coeffs(4) - type is (SurfaceQuadric) - s%A = coeffs(1) - s%B = coeffs(2) - s%C = coeffs(3) - s%D = coeffs(4) - s%E = coeffs(5) - s%F = coeffs(6) - s%G = coeffs(7) - s%H = coeffs(8) - s%J = coeffs(9) - s%K = coeffs(10) + zmin = min(zmin, coeffs(1)) + zmax = max(zmax, coeffs(1)) + if (zmin == coeffs(1)) i_zmin = i + if (zmax == coeffs(1)) i_zmax = i end select ! No longer need coefficients diff --git a/src/main.F90 b/src/main.F90 index d8e52643c..120bdd1e0 100644 --- a/src/main.F90 +++ b/src/main.F90 @@ -9,13 +9,13 @@ program main implicit none -#ifdef MPI +#ifdef OPENMC_MPI integer :: mpi_err ! MPI error code #endif ! Initialize run -- when run with MPI, pass communicator -#ifdef MPI -#ifdef MPIF08 +#ifdef OPENMC_MPI +#ifdef OPENMC_MPIF08 call openmc_init(MPI_COMM_WORLD % MPI_VAL) #else call openmc_init(MPI_COMM_WORLD) @@ -39,7 +39,7 @@ program main ! finalize run call openmc_finalize() -#ifdef MPI +#ifdef OPENMC_MPI ! If MPI is in use and enabled, terminate it call MPI_FINALIZE(mpi_err) #endif diff --git a/src/mesh.F90 b/src/mesh.F90 index e997890a4..790dc7d88 100644 --- a/src/mesh.F90 +++ b/src/mesh.F90 @@ -34,7 +34,7 @@ contains integer :: n ! number of energy groups / size integer :: mesh_bin ! mesh bin integer :: e_bin ! energy bin -#ifdef MPI +#ifdef OPENMC_MPI integer :: mpi_err ! MPI error code #endif logical :: outside ! was any site outside mesh? @@ -86,7 +86,7 @@ contains cnt_(e_bin, mesh_bin) = cnt_(e_bin, mesh_bin) + bank_array(i) % wgt end do FISSION_SITES -#ifdef MPI +#ifdef OPENMC_MPI ! collect values from all processors n = size(cnt_) call MPI_REDUCE(cnt_, cnt, n, MPI_REAL8, MPI_SUM, 0, mpi_intracomm, mpi_err) diff --git a/src/message_passing.F90 b/src/message_passing.F90 index 0f2b94d1a..7391a632c 100644 --- a/src/message_passing.F90 +++ b/src/message_passing.F90 @@ -1,7 +1,7 @@ module message_passing -#ifdef MPI -#ifdef MPIF08 +#ifdef OPENMC_MPI +#ifdef OPENMC_MPIF08 use mpi_f08 #else use mpi @@ -16,7 +16,7 @@ module message_passing integer :: rank = 0 ! rank of process logical :: master = .true. ! master process? logical :: mpi_enabled = .false. ! is MPI in use and initialized? -#ifdef MPIF08 +#ifdef OPENMC_MPIF08 type(MPI_Datatype) :: MPI_BANK ! MPI datatype for fission bank type(MPI_Comm) :: mpi_intracomm ! MPI intra-communicator #else diff --git a/src/output.F90 b/src/output.F90 index 9a144721d..1b7cae121 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -88,7 +88,7 @@ contains ! Write the date and time write(UNIT=OUTPUT_UNIT, FMT='(9X,"Date/Time | ",A)') time_stamp() -#ifdef MPI +#ifdef OPENMC_MPI ! Write number of processors write(UNIT=OUTPUT_UNIT, FMT='(5X,"MPI Processes | ",A)') & trim(to_str(n_procs)) diff --git a/src/simulation.F90 b/src/simulation.F90 index ef29e7832..24e9b29fc 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -320,7 +320,7 @@ contains subroutine finalize_batch() -#ifdef MPI +#ifdef OPENMC_MPI integer :: mpi_err ! MPI error code #endif @@ -342,7 +342,7 @@ contains ! Check_triggers if (master) call check_triggers() -#ifdef MPI +#ifdef OPENMC_MPI call MPI_BCAST(satisfy_triggers, 1, MPI_LOGICAL, 0, & mpi_intracomm, mpi_err) #endif @@ -469,7 +469,7 @@ contains subroutine openmc_simulation_finalize() bind(C) integer :: i ! loop index -#ifdef MPI +#ifdef OPENMC_MPI integer :: n ! size of arrays integer :: mpi_err ! MPI error code integer(8) :: temp @@ -494,7 +494,7 @@ contains ! Increment total number of generations total_gen = total_gen + current_batch*gen_per_batch -#ifdef MPI +#ifdef OPENMC_MPI ! Broadcast tally results so that each process has access to results if (allocated(tallies)) then do i = 1, size(tallies) diff --git a/src/source.F90 b/src/source.F90 index 557120e97..5beecd887 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -1,7 +1,7 @@ module source use hdf5, only: HID_T -#ifdef MPI +#ifdef OPENMC_MPI use message_passing #endif diff --git a/src/state_point.F90 b/src/state_point.F90 index f7a892898..ba7bcef19 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -523,7 +523,7 @@ contains integer(HID_T) :: tallies_group, tally_group real(8), allocatable :: tally_temp(:,:,:) ! contiguous array of results real(8), target :: global_temp(3,N_GLOBAL_TALLIES) -#ifdef MPI +#ifdef OPENMC_MPI integer :: mpi_err ! MPI error code real(8) :: dummy ! temporary receive buffer for non-root reduces #endif @@ -543,7 +543,7 @@ contains end if -#ifdef MPI +#ifdef OPENMC_MPI ! Reduce global tallies n_bins = size(global_tallies) call MPI_REDUCE(global_tallies, global_temp, n_bins, MPI_REAL8, MPI_SUM, & @@ -586,7 +586,7 @@ contains ! The MPI_IN_PLACE specifier allows the master to copy values into ! a receive buffer without having a temporary variable -#ifdef MPI +#ifdef OPENMC_MPI call MPI_REDUCE(MPI_IN_PLACE, tally_temp, n_bins, MPI_REAL8, & MPI_SUM, 0, mpi_intracomm, mpi_err) #endif @@ -610,7 +610,7 @@ contains deallocate(dummy_tally % results) else ! Receive buffer not significant at other processors -#ifdef MPI +#ifdef OPENMC_MPI call MPI_REDUCE(tally_temp, dummy, n_bins, MPI_REAL8, MPI_SUM, & 0, mpi_intracomm, mpi_err) #endif @@ -849,7 +849,7 @@ contains integer(HID_T) :: plist ! property list #else integer :: i -#ifdef MPI +#ifdef OPENMC_MPI integer :: mpi_err ! MPI error code type(Bank), allocatable, target :: temp_source(:) #endif @@ -897,7 +897,7 @@ contains dspace, dset, hdf5_err) ! Save source bank sites since the souce_bank array is overwritten below -#ifdef MPI +#ifdef OPENMC_MPI allocate(temp_source(work)) temp_source(:) = source_bank(:) #endif @@ -907,7 +907,7 @@ contains dims(1) = work_index(i+1) - work_index(i) call h5screate_simple_f(1, dims, memspace, hdf5_err) -#ifdef MPI +#ifdef OPENMC_MPI ! Receive source sites from other processes if (i > 0) then call MPI_RECV(source_bank, int(dims(1)), MPI_BANK, i, i, & @@ -933,12 +933,12 @@ contains call h5dclose_f(dset, hdf5_err) ! Restore state of source bank -#ifdef MPI +#ifdef OPENMC_MPI source_bank(:) = temp_source(:) deallocate(temp_source) #endif else -#ifdef MPI +#ifdef OPENMC_MPI call MPI_SEND(source_bank, int(work), MPI_BANK, 0, rank, & mpi_intracomm, mpi_err) #endif diff --git a/src/summary.F90 b/src/summary.F90 index 0f45ef1bb..ca7276407 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -24,6 +24,17 @@ module summary public :: write_summary + interface + subroutine surface_to_hdf5_c(surf_ind, group) & + bind(C, name='surface_to_hdf5') + use ISO_C_BINDING + use hdf5 + implicit none + integer(C_INT), intent(in), value :: surf_ind + integer(HID_T), intent(in), value :: group + end subroutine surface_to_hdf5_c + end interface + contains !=============================================================================== @@ -125,7 +136,6 @@ contains integer(HID_T) :: surfaces_group, surface_group integer(HID_T) :: universes_group, univ_group integer(HID_T) :: lattices_group, lattice_group - real(8), allocatable :: coeffs(:) character(:), allocatable :: region_spec type(Cell), pointer :: c class(Surface), pointer :: s @@ -245,87 +255,11 @@ contains surface_group = create_group(surfaces_group, "surface " // & trim(to_str(s%id))) + call surface_to_hdf5_c(i-1, surface_group) + ! Write name for this surface call write_dataset(surface_group, "name", s%name) - ! Write surface type - select type (s) - type is (SurfaceXPlane) - call write_dataset(surface_group, "type", "x-plane") - allocate(coeffs(1)) - coeffs(1) = s%x0 - - type is (SurfaceYPlane) - call write_dataset(surface_group, "type", "y-plane") - allocate(coeffs(1)) - coeffs(1) = s%y0 - - type is (SurfaceZPlane) - call write_dataset(surface_group, "type", "z-plane") - allocate(coeffs(1)) - coeffs(1) = s%z0 - - type is (SurfacePlane) - call write_dataset(surface_group, "type", "plane") - allocate(coeffs(4)) - coeffs(:) = [s%A, s%B, s%C, s%D] - - type is (SurfaceXCylinder) - call write_dataset(surface_group, "type", "x-cylinder") - allocate(coeffs(3)) - coeffs(:) = [s%y0, s%z0, s%r] - - type is (SurfaceYCylinder) - call write_dataset(surface_group, "type", "y-cylinder") - allocate(coeffs(3)) - coeffs(:) = [s%x0, s%z0, s%r] - - type is (SurfaceZCylinder) - call write_dataset(surface_group, "type", "z-cylinder") - allocate(coeffs(3)) - coeffs(:) = [s%x0, s%y0, s%r] - - type is (SurfaceSphere) - call write_dataset(surface_group, "type", "sphere") - allocate(coeffs(4)) - coeffs(:) = [s%x0, s%y0, s%z0, s%r] - - type is (SurfaceXCone) - call write_dataset(surface_group, "type", "x-cone") - allocate(coeffs(4)) - coeffs(:) = [s%x0, s%y0, s%z0, s%r2] - - type is (SurfaceYCone) - call write_dataset(surface_group, "type", "y-cone") - allocate(coeffs(4)) - coeffs(:) = [s%x0, s%y0, s%z0, s%r2] - - type is (SurfaceZCone) - call write_dataset(surface_group, "type", "z-cone") - allocate(coeffs(4)) - coeffs(:) = [s%x0, s%y0, s%z0, s%r2] - - type is (SurfaceQuadric) - call write_dataset(surface_group, "type", "quadric") - allocate(coeffs(10)) - coeffs(:) = [s%A, s%B, s%C, s%D, s%E, s%F, s%G, s%H, s%J, s%K] - - end select - call write_dataset(surface_group, "coefficients", coeffs) - deallocate(coeffs) - - ! Write boundary type - select case (s%bc) - case (BC_TRANSMIT) - call write_dataset(surface_group, "boundary_type", "transmission") - case (BC_VACUUM) - call write_dataset(surface_group, "boundary_type", "vacuum") - case (BC_REFLECT) - call write_dataset(surface_group, "boundary_type", "reflective") - case (BC_PERIODIC) - call write_dataset(surface_group, "boundary_type", "periodic") - end select - call close_group(surface_group) end do SURFACE_LOOP diff --git a/src/surface_header.C b/src/surface_header.C index 45fb62399..db53c8982 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -1,8 +1,12 @@ +#include // For std::array #include // For strcmp #include // For numeric_limits #include // For fabs #include "pugixml/pugixml.hpp" +#include "hdf5.h" + +#include "hdf5_interface.h" // DEBUGGING #include @@ -15,6 +19,11 @@ const double FP_COINCIDENT = 1e-12; const double INFTY = std::numeric_limits::max(); +const int BC_TRANSMIT{0}; +const int BC_VACUUM{1}; +const int BC_REFLECT{2}; +const int BC_PERIODIC{3}; + //============================================================================== // Global array of surfaces //============================================================================== @@ -127,6 +136,13 @@ public: //! @param xyz[3] A 3D Cartesian coordinate. //! @param uvw[3] This output argument provides the normal. virtual void normal(const double xyz[3], double uvw[3]) const = 0; + + //! Write all information needed to reconstruct the surface to an HDF5 group. + //! @param group_id An HDF5 group id. + virtual void to_hdf5(hid_t group_id) const = 0; + +protected: + void write_bc_to_hdf5(hid_t group_id) const; }; bool @@ -164,6 +180,25 @@ Surface::reflect(const double xyz[3], double uvw[3]) const uvw[2] -= 2.0 * projection / magnitude * norm[2]; } +void +Surface::write_bc_to_hdf5(hid_t group_id) const +{ + switch(bc) { + case BC_TRANSMIT : + write_string(group_id, "boundary_type", "transmission"); + break; + case BC_VACUUM : + write_string(group_id, "boundary_type", "vacuum"); + break; + case BC_REFLECT : + write_string(group_id, "boundary_type", "reflective"); + break; + case BC_PERIODIC : + write_string(group_id, "boundary_type", "periodic"); + break; + } +} + //============================================================================== //! A `Surface` that supports periodic boundary conditions. //! @@ -236,6 +271,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; }; @@ -261,6 +297,14 @@ inline void SurfaceXPlane::normal(const double xyz[3], double uvw[3]) const axis_aligned_plane_normal<0, 1, 2>(xyz, uvw); } +void SurfaceXPlane::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "x-plane"); + std::array coeffs{{x0}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + bool SurfaceXPlane::periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const { @@ -285,7 +329,7 @@ bool SurfaceXPlane::periodic_translate(PeriodicSurface *other, double xyz[3], return true; } -}; +} //============================================================================== // SurfaceYPlane @@ -303,6 +347,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; }; @@ -328,6 +373,14 @@ inline void SurfaceYPlane::normal(const double xyz[3], double uvw[3]) const axis_aligned_plane_normal<1, 0, 2>(xyz, uvw); } +void SurfaceYPlane::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "y-plane"); + std::array coeffs{{y0}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + bool SurfaceYPlane::periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const { @@ -352,7 +405,7 @@ bool SurfaceYPlane::periodic_translate(PeriodicSurface *other, double xyz[3], return true; } -}; +} //============================================================================== // SurfaceZPlane @@ -370,6 +423,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; }; @@ -395,13 +449,21 @@ inline void SurfaceZPlane::normal(const double xyz[3], double uvw[3]) const axis_aligned_plane_normal<2, 0, 1>(xyz, uvw); } +void SurfaceZPlane::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "z-plane"); + std::array coeffs{{z0}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + bool SurfaceZPlane::periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const { // Assume the other plane is aligned along z. Just change the z coord. xyz[2] = z0; return false; -}; +} //============================================================================== // SurfacePlane @@ -419,6 +481,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; }; @@ -457,6 +520,14 @@ SurfacePlane::normal(const double xyz[3], double uvw[3]) const uvw[2] = C; } +void SurfacePlane::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "plane"); + std::array coeffs{{A, B, C, D}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + bool SurfacePlane::periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const { @@ -564,6 +635,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; }; SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node) @@ -588,6 +660,15 @@ inline void SurfaceXCylinder::normal(const double xyz[3], double uvw[3]) const axis_aligned_cylinder_normal<0, 1, 2>(xyz, uvw, y0, z0); } + +void SurfaceXCylinder::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "x-cylinder"); + std::array coeffs{{y0, z0, r}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + //============================================================================== // SurfaceYCylinder //! A cylinder aligned along the y-axis. @@ -605,6 +686,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; }; SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node) @@ -629,6 +711,14 @@ inline void SurfaceYCylinder::normal(const double xyz[3], double uvw[3]) const axis_aligned_cylinder_normal<1, 0, 2>(xyz, uvw, x0, z0); } +void SurfaceYCylinder::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "y-cylinder"); + std::array coeffs{{x0, z0, r}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + //============================================================================== // SurfaceZCylinder //! A cylinder aligned along the z-axis. @@ -646,6 +736,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; }; SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) @@ -670,6 +761,14 @@ inline void SurfaceZCylinder::normal(const double xyz[3], double uvw[3]) const axis_aligned_cylinder_normal<2, 0, 1>(xyz, uvw, x0, y0); } +void SurfaceZCylinder::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "z-cylinder"); + std::array coeffs{{x0, y0, r}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + //============================================================================== // SurfaceSphere //! A sphere. @@ -687,6 +786,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; }; SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) @@ -748,6 +848,14 @@ inline void SurfaceSphere::normal(const double xyz[3], double uvw[3]) const uvw[2] = 2.0 * (xyz[2] - z0); } +void SurfaceSphere::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "sphere"); + std::array coeffs{{x0, y0, z0, r}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + //============================================================================== // Generic functions for x-, y-, and z-, cones //============================================================================== @@ -848,6 +956,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; }; SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) @@ -872,6 +981,14 @@ inline void SurfaceXCone::normal(const double xyz[3], double uvw[3]) const axis_aligned_cone_normal<0, 1, 2>(xyz, uvw, x0, y0, z0, r_sq); } +void SurfaceXCone::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "x-cone"); + std::array coeffs{{x0, y0, z0, r_sq}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + //============================================================================== // SurfaceYCone //! A cone aligned along the y-axis. @@ -889,6 +1006,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; }; SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) @@ -913,6 +1031,14 @@ inline void SurfaceYCone::normal(const double xyz[3], double uvw[3]) const axis_aligned_cone_normal<1, 0, 2>(xyz, uvw, y0, x0, z0, r_sq); } +void SurfaceYCone::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "y-cone"); + std::array coeffs{{x0, y0, z0, r_sq}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + //============================================================================== // SurfaceZCone //! A cone aligned along the z-axis. @@ -930,6 +1056,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; }; SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) @@ -954,6 +1081,14 @@ inline void SurfaceZCone::normal(const double xyz[3], double uvw[3]) const axis_aligned_cone_normal<2, 0, 1>(xyz, uvw, z0, x0, y0, r_sq); } +void SurfaceZCone::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "z-cone"); + std::array coeffs{{x0, y0, z0, r_sq}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + //============================================================================== // SurfaceQuadric //! A general surface described by a quadratic equation. @@ -971,6 +1106,7 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5(hid_t group_id) const; }; SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) @@ -1055,6 +1191,14 @@ SurfaceQuadric::normal(const double xyz[3], double uvw[3]) const uvw[2] = 2.0*C*z + E*y + F*x + J; } +void SurfaceQuadric::to_hdf5(hid_t group_id) const +{ + write_string(group_id, "type", "quadric"); + std::array coeffs{{A, B, C, D, E, F, G, H, J, K}}; + write_double_1D(group_id, "coefficients", coeffs); + write_bc_to_hdf5(group_id); +} + //============================================================================== extern "C" void @@ -1084,6 +1228,7 @@ read_surfaces(pugi::xml_node *node) } else { std::cout << "ERROR: Found a surface with no type attribute/node" << std::endl; + //TODO: call fatal_error } if (strcmp(surf_type, "x-plane") == 0) { @@ -1125,6 +1270,33 @@ read_surfaces(pugi::xml_node *node) } else { std::cout << "Call error or handle uppercase here!" << std::endl; std::cout << surf_type << std::endl; + //TODO: call fatal_error + } + + const pugi::char_t *surf_bc{""}; + if (surf_node.attribute("boundary")) { + surf_bc = surf_node.attribute("boundary").value(); + } else if (surf_node.child("boundary")) { + surf_bc = surf_node.attribute("boundary").value(); + } + + if (strcmp(surf_bc, "transmission") == 0 + or strcmp(surf_bc, "transmit") == 0 + or strcmp(surf_bc, "") == 0) { + surfaces_c[i_surf]->bc = BC_TRANSMIT; + + } else if (strcmp(surf_bc, "vacuum") == 0) { + surfaces_c[i_surf]->bc = BC_VACUUM; + + } else if (strcmp(surf_bc, "reflective") == 0 + or strcmp(surf_bc, "reflect") == 0 + or strcmp(surf_bc, "reflecting") == 0) { + surfaces_c[i_surf]->bc = BC_REFLECT; + } else if (strcmp(surf_bc, "periodic") == 0) { + surfaces_c[i_surf]->bc = BC_PERIODIC; + } else { + std::cout << "Unknown boundary condition" << std::endl; + //TODO: call fatal_error } } } @@ -1156,6 +1328,12 @@ surface_normal(int surf_ind, double xyz[3], double uvw[3]) return surfaces_c[surf_ind]->normal(xyz, uvw); } +extern "C" void +surface_to_hdf5(int surf_ind, hid_t group) +{ + surfaces_c[surf_ind]->to_hdf5(group); +} + extern "C" bool surface_periodic(int surf_ind1, int surf_ind2, double xyz[3], double uvw[3]) { diff --git a/src/surface_header.F90 b/src/surface_header.F90 index d63290387..98e2423b6 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -36,84 +36,39 @@ module surface_header !=============================================================================== type, extends(Surface) :: SurfaceXPlane - ! x = x0 - real(8) :: x0 end type SurfaceXPlane type, extends(Surface) :: SurfaceYPlane - ! y = y0 - real(8) :: y0 end type SurfaceYPlane type, extends(Surface) :: SurfaceZPlane - ! z = z0 - real(8) :: z0 end type SurfaceZPlane type, extends(Surface) :: SurfacePlane - ! Ax + By + Cz = D - real(8) :: A - real(8) :: B - real(8) :: C - real(8) :: D end type SurfacePlane type, extends(Surface) :: SurfaceXCylinder - ! (y - y0)^2 + (z - z0)^2 = R^2 - real(8) :: y0 - real(8) :: z0 - real(8) :: r end type SurfaceXCylinder type, extends(Surface) :: SurfaceYCylinder - ! (x - x0)^2 + (z - z0)^2 = R^2 - real(8) :: x0 - real(8) :: z0 - real(8) :: r end type SurfaceYCylinder type, extends(Surface) :: SurfaceZCylinder - ! (x - x0)^2 + (y - y0)^2 = R^2 - real(8) :: x0 - real(8) :: y0 - real(8) :: r end type SurfaceZCylinder type, extends(Surface) :: SurfaceSphere - ! (x - x0)^2 + (y - y0)^2 + (z - z0)^2 = R^2 - real(8) :: x0 - real(8) :: y0 - real(8) :: z0 - real(8) :: r end type SurfaceSphere type, extends(Surface) :: SurfaceXCone - ! (y - y0)^2 + (z - z0)^2 = R^2*(x - x0)^2 - real(8) :: x0 - real(8) :: y0 - real(8) :: z0 - real(8) :: r2 end type SurfaceXCone type, extends(Surface) :: SurfaceYCone - ! (x - x0)^2 + (z - z0)^2 = R^2*(y - y0)^2 - real(8) :: x0 - real(8) :: y0 - real(8) :: z0 - real(8) :: r2 end type SurfaceYCone type, extends(Surface) :: SurfaceZCone - ! (x - x0)^2 + (y - y0)^2 = R^2*(z - z0)^2 - real(8) :: x0 - real(8) :: y0 - real(8) :: z0 - real(8) :: r2 end type SurfaceZCone type, extends(Surface) :: SurfaceQuadric - ! Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0 - real(8) :: A, B, C, D, E, F, G, H, J, K end type SurfaceQuadric integer(C_INT32_T), bind(C) :: n_surfaces ! # of surfaces diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 7684a36aa..7c234f173 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -4241,7 +4241,7 @@ contains real(C_DOUBLE) :: k_tra ! Copy of batch tracklength estimate of keff real(C_DOUBLE) :: val -#ifdef MPI +#ifdef OPENMC_MPI ! Combine tally results onto master process if (reduce_tallies) call reduce_tally_results() #endif @@ -4289,7 +4289,7 @@ contains ! REDUCE_TALLY_RESULTS collects all the results from tallies onto one processor !=============================================================================== -#ifdef MPI +#ifdef OPENMC_MPI subroutine reduce_tally_results() integer :: i diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index 9e9b06d20..ee2259ed4 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -2,7 +2,7 @@ module trigger use, intrinsic :: ISO_C_BINDING -#ifdef MPI +#ifdef OPENMC_MPI use message_passing #endif diff --git a/src/volume_calc.F90 b/src/volume_calc.F90 index 5985a236b..07dc5b418 100644 --- a/src/volume_calc.F90 +++ b/src/volume_calc.F90 @@ -136,7 +136,7 @@ contains integer :: total_hits ! total hits for a single domain (summed over materials) integer :: min_samples ! minimum number of samples per process integer :: remainder ! leftover samples from uneven divide -#ifdef MPI +#ifdef OPENMC_MPI integer :: mpi_err ! MPI error code integer :: m ! index over materials integer :: n ! number of materials @@ -279,7 +279,7 @@ contains total_hits = 0 if (master) then -#ifdef MPI +#ifdef OPENMC_MPI do j = 1, n_procs - 1 call MPI_RECV(n, 1, MPI_INTEGER, j, 0, mpi_intracomm, & MPI_STATUS_IGNORE, mpi_err) @@ -341,7 +341,7 @@ contains end do else -#ifdef MPI +#ifdef OPENMC_MPI n = master_indices(i_domain) % size() allocate(data(2*n)) do k = 0, n - 1 From 1f816bb919e960b9fb843595e813a27776311f65 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 23 Jan 2018 06:21:42 -0600 Subject: [PATCH 126/282] Raise CalledProcessError if openmc.run fails --- openmc/executor.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/openmc/executor.py b/openmc/executor.py index 51215e8b7..46ad53ec2 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -15,18 +15,22 @@ def _run(args, output, cwd): stderr=subprocess.STDOUT, universal_newlines=True) # Capture and re-print OpenMC output in real-time + lines = [] while True: # If OpenMC is finished, break loop line = p.stdout.readline() if not line and p.poll() is not None: break + lines.append(line) if output: # If user requested output, print to screen print(line, end='') - # Return the returncode (integer, zero if no problems encountered) - return p.returncode + # Raise an exception if return status is non-zero + if p.returncode != 0: + raise subprocess.CalledProcessError(p.returncode, ' '.join(args), + ''.join(lines)) def plot_geometry(output=True, openmc_exec='openmc', cwd='.'): @@ -42,7 +46,7 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.'): Path to working directory to run in """ - return _run([openmc_exec, '-p'], output, cwd) + _run([openmc_exec, '-p'], output, cwd) def plot_inline(plots, openmc_exec='openmc', cwd='.', convert_exec='convert'): @@ -133,7 +137,7 @@ def calculate_volumes(threads=None, output=True, cwd='.', if mpi_args is not None: args = mpi_args + args - return _run(args, output, cwd) + _run(args, output, cwd) def run(particles=None, threads=None, geometry_debug=False, @@ -189,4 +193,4 @@ def run(particles=None, threads=None, geometry_debug=False, if mpi_args is not None: args = mpi_args + args - return _run(args, output, cwd) + _run(args, output, cwd) From 126cb65113f3b461dd2be9b2934b1d2293fffa9c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 23 Jan 2018 07:18:33 -0600 Subject: [PATCH 127/282] Don't check returncode in tests --- openmc/executor.py | 21 ++++++++++++++++++- tests/test_mg_convert/test_mg_convert.py | 7 ++----- .../test_mgxs_library_ce_to_mg.py | 13 ++++-------- tests/test_plot/test_plot.py | 3 +-- .../test_statepoint_restart.py | 10 +++------ tests/testing_harness.py | 16 +++++--------- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/openmc/executor.py b/openmc/executor.py index 46ad53ec2..ada662a12 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -45,6 +45,11 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.'): cwd : str, optional Path to working directory to run in + Raises + ------ + subprocess.CalledProcessError + If the `openmc` executable returns a non-zero status + """ _run([openmc_exec, '-p'], output, cwd) @@ -67,6 +72,11 @@ def plot_inline(plots, openmc_exec='openmc', cwd='.', convert_exec='convert'): convert_exec : str, optional Command that can convert PPM files into PNG files + Raises + ------ + subprocess.CalledProcessError + If the `openmc` executable returns a non-zero status + """ from IPython.display import Image, display @@ -125,6 +135,11 @@ def calculate_volumes(threads=None, output=True, cwd='.', Path to working directory to run in. Defaults to the current working directory. + Raises + ------ + subprocess.CalledProcessError + If the `openmc` executable returns a non-zero status + See Also -------- openmc.VolumeCalculation @@ -171,8 +186,12 @@ def run(particles=None, threads=None, geometry_debug=False, MPI execute command and any additional MPI arguments to pass, e.g. ['mpiexec', '-n', '8']. - """ + Raises + ------ + subprocess.CalledProcessError + If the `openmc` executable returns a non-zero status + """ args = [openmc_exec] if isinstance(particles, Integral) and particles > 0: diff --git a/tests/test_mg_convert/test_mg_convert.py b/tests/test_mg_convert/test_mg_convert.py index 9d1371458..36bfe5338 100755 --- a/tests/test_mg_convert/test_mg_convert.py +++ b/tests/test_mg_convert/test_mg_convert.py @@ -139,13 +139,10 @@ class MGXSTestHarness(PyAPITestHarness): if self._opts.mpi_exec is not None: mpi_args = [self._opts.mpi_exec, '-n', self._opts.mpi_np] - returncode = openmc.run(openmc_exec=self._opts.exe, - mpi_args=mpi_args) + openmc.run(openmc_exec=self._opts.exe, mpi_args=mpi_args) else: - returncode = openmc.run(openmc_exec=self._opts.exe) - - assert returncode == 0, 'OpenMC did not exit successfully.' + openmc.run(openmc_exec=self._opts.exe) sp = openmc.StatePoint('statepoint.' + str(batches) + '.h5') diff --git a/tests/test_mgxs_library_ce_to_mg/test_mgxs_library_ce_to_mg.py b/tests/test_mgxs_library_ce_to_mg/test_mgxs_library_ce_to_mg.py index aaa83a70d..7ae47636e 100644 --- a/tests/test_mgxs_library_ce_to_mg/test_mgxs_library_ce_to_mg.py +++ b/tests/test_mgxs_library_ce_to_mg/test_mgxs_library_ce_to_mg.py @@ -36,13 +36,9 @@ class MGXSTestHarness(PyAPITestHarness): # Initial run if self._opts.mpi_exec is not None: mpi_args = [self._opts.mpi_exec, '-n', self._opts.mpi_np] - returncode = openmc.run(openmc_exec=self._opts.exe, - mpi_args=mpi_args) + openmc.run(openmc_exec=self._opts.exe, mpi_args=mpi_args) else: - returncode = openmc.run(openmc_exec=self._opts.exe) - - assert returncode == 0, 'CE OpenMC calculation did not exit' \ - 'successfully.' + openmc.run(openmc_exec=self._opts.exe) # Build MG Inputs # Get data needed to execute Library calculations. @@ -73,10 +69,9 @@ class MGXSTestHarness(PyAPITestHarness): # Re-run MG mode. if self._opts.mpi_exec is not None: mpi_args = [self._opts.mpi_exec, '-n', self._opts.mpi_np] - returncode = openmc.run(openmc_exec=self._opts.exe, - mpi_args=mpi_args) + openmc.run(openmc_exec=self._opts.exe, mpi_args=mpi_args) else: - returncode = openmc.run(openmc_exec=self._opts.exe) + openmc.run(openmc_exec=self._opts.exe) def _cleanup(self): super(MGXSTestHarness, self)._cleanup() diff --git a/tests/test_plot/test_plot.py b/tests/test_plot/test_plot.py index d484925cb..1a99c2488 100644 --- a/tests/test_plot/test_plot.py +++ b/tests/test_plot/test_plot.py @@ -19,8 +19,7 @@ class PlotTestHarness(TestHarness): self._plot_names = plot_names def _run_openmc(self): - returncode = openmc.plot_geometry(openmc_exec=self._opts.exe) - assert returncode == 0, 'OpenMC did not exit successfully.' + openmc.plot_geometry(openmc_exec=self._opts.exe) def _test_output_created(self): """Make sure *.ppm has been created.""" diff --git a/tests/test_statepoint_restart/test_statepoint_restart.py b/tests/test_statepoint_restart/test_statepoint_restart.py index 2c1c44495..9c10551de 100644 --- a/tests/test_statepoint_restart/test_statepoint_restart.py +++ b/tests/test_statepoint_restart/test_statepoint_restart.py @@ -50,14 +50,10 @@ class StatepointRestartTestHarness(TestHarness): # Run OpenMC if self._opts.mpi_exec is not None: mpi_args = [self._opts.mpi_exec, '-n', self._opts.mpi_np] - returncode = openmc.run(restart_file=statepoint, - openmc_exec=self._opts.exe, - mpi_args=mpi_args) + openmc.run(restart_file=statepoint, openmc_exec=self._opts.exe, + mpi_args=mpi_args) else: - returncode = openmc.run(openmc_exec=self._opts.exe, - restart_file=statepoint) - - assert returncode == 0, 'OpenMC did not exit successfully.' + openmc.run(openmc_exec=self._opts.exe, restart_file=statepoint) if __name__ == '__main__': diff --git a/tests/testing_harness.py b/tests/testing_harness.py index 428bf5c4b..ad3b8ac1e 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -62,14 +62,10 @@ class TestHarness(object): def _run_openmc(self): if self._opts.mpi_exec is not None: - returncode = openmc.run( - openmc_exec=self._opts.exe, - mpi_args=[self._opts.mpi_exec, '-n', self._opts.mpi_np]) - + openmc.run(openmc_exec=self._opts.exe, + mpi_args=[self._opts.mpi_exec, '-n', self._opts.mpi_np]) else: - returncode = openmc.run(openmc_exec=self._opts.exe) - - assert returncode == 0, 'OpenMC did not exit successfully.' + openmc.run(openmc_exec=self._opts.exe) def _test_output_created(self): """Make sure statepoint.* and tallies.out have been created.""" @@ -189,13 +185,11 @@ class ParticleRestartTestHarness(TestHarness): args['mpi_args'] = [self._opts.mpi_exec, '-n', self._opts.mpi_np] # Initial run - returncode = openmc.run(**args) - assert returncode == 0, 'OpenMC did not exit successfully.' + openmc.run(**args) # Run particle restart args.update({'restart_file': self._sp_name}) - returncode = openmc.run(**args) - assert returncode == 0, 'OpenMC did not exit successfully.' + openmc.run(**args) def _test_output_created(self): """Make sure the restart file has been created.""" From 7624888df34577b9ed735039ff5b33bab8c0e44c Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 23 Jan 2018 12:37:28 -0500 Subject: [PATCH 128/282] Finish C++ surface->summary.h5 capability --- CMakeLists.txt | 1 + src/constants.F90 | 15 ++- src/error.F90 | 8 ++ src/error.h | 21 ++++ src/hdf5_interface.h | 44 +++++++- src/summary.F90 | 11 +- src/surface_header.C | 263 +++++++++++++++++++++++++++---------------- 7 files changed, 243 insertions(+), 120 deletions(-) create mode 100644 src/error.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 58fb55e79..e59713755 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,6 +435,7 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/trigger_header.F90 ) set(LIBOPENMC_CXX_SRC + src/error.h src/hdf5_interface.h src/random_lcg.h src/random_lcg.cpp diff --git a/src/constants.F90 b/src/constants.F90 index 331a644f0..3334a4cc7 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -43,9 +43,9 @@ module constants real(8), parameter :: TINY_BIT = 1e-8_8 ! User for precision in geometry - real(8), parameter :: FP_PRECISION = 1e-14_8 - real(8), parameter :: FP_REL_PRECISION = 1e-5_8 - real(8), parameter :: FP_COINCIDENT = 1e-12_8 + real(C_DOUBLE), bind(C, name='FP_PRECISION') :: FP_PRECISION = 1e-14_8 + real(C_DOUBLE), bind(C, name='FP_REL_PRECISION') :: FP_REL_PRECISION = 1e-5_8 + real(C_DOUBLE), bind(C, name='FP_COINCIDENT') :: FP_COINCIDENT = 1e-12_8 ! Maximum number of collisions/crossings integer, parameter :: MAX_EVENTS = 1000000 @@ -95,11 +95,10 @@ module constants ! GEOMETRY-RELATED CONSTANTS ! Boundary conditions - integer, parameter :: & - BC_TRANSMIT = 0, & ! Transmission boundary condition (default) - BC_VACUUM = 1, & ! Vacuum boundary condition - BC_REFLECT = 2, & ! Reflecting boundary condition - BC_PERIODIC = 3 ! Periodic boundary condition + integer(C_INT), bind(C, name="BC_TRANSMIT") :: BC_TRANSMIT + integer(C_INT), bind(C, name="BC_VACUUM") :: BC_VACUUM + integer(C_INT), bind(C, name="BC_REFLECT") :: BC_REFLECT + integer(C_INT), bind(C, name="BC_PERIODIC") :: BC_PERIODIC ! Logical operators for cell definitions integer, parameter :: & diff --git a/src/error.F90 b/src/error.F90 index ae02e0897..cba137291 100644 --- a/src/error.F90 +++ b/src/error.F90 @@ -194,6 +194,14 @@ contains end subroutine fatal_error + subroutine fatal_error_from_c(message, message_len) bind(C) + integer(C_INT), intent(in), value :: message_len + character(C_CHAR), intent(in) :: message(message_len) + character(message_len+1) :: message_out + write(message_out, *) message + call fatal_error(message_out) + end subroutine + !=============================================================================== ! WRITE_MESSAGE displays an informational message to the log file and the ! standard output stream. diff --git a/src/error.h b/src/error.h new file mode 100644 index 000000000..77f0252e1 --- /dev/null +++ b/src/error.h @@ -0,0 +1,21 @@ +#ifndef ERROR_H +#define ERROR_H + +#include + + +extern "C" void fatal_error_from_c(const char *message, int message_len); + + +void fatal_error(const char *message) +{ + fatal_error_from_c(message, strlen(message)); +} + + +void fatal_error(const std::string &message) +{ + fatal_error_from_c(message.c_str(), message.length()); +} + +#endif // ERROR_H diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 38bb7d88b..2cded21f6 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -1,11 +1,44 @@ #ifndef HDF5_INTERFACE_H #define HDF5_INTERFACE_H -#include // For std::array -#include // For strlen +#include +#include #include "hdf5.h" +#include "error.h" + + +hid_t +create_group(hid_t parent_id, char const *name) +{ + hid_t out = H5Gcreate(parent_id, name, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + if (out < 0) { + std::string err_msg{"Failed to create HDF5 group \""}; + err_msg += name; + err_msg += "\""; + fatal_error(err_msg); + } + return out; +} + + +hid_t +create_group(hid_t parent_id, const std::string &name) +{ + return create_group(parent_id, name.c_str()); +} + + +void +close_group(hid_t group_id) +{ + herr_t err = H5Gclose(group_id); + if (err < 0) { + fatal_error("Failed to close HDF5 group"); + } +} + template void write_double_1D(hid_t group_id, char const *name, @@ -44,4 +77,11 @@ write_string(hid_t group_id, char const *name, char const *buffer) H5Dclose(dataset); } + +void +write_string(hid_t group_id, char const *name, const std::string &buffer) +{ + write_string(group_id, name, buffer.c_str()); +} + #endif //HDF5_INTERFACE_H diff --git a/src/summary.F90 b/src/summary.F90 index ca7276407..77bded983 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -251,16 +251,7 @@ contains ! Write information on each surface SURFACE_LOOP: do i = 1, n_surfaces - s => surfaces(i)%obj - surface_group = create_group(surfaces_group, "surface " // & - trim(to_str(s%id))) - - call surface_to_hdf5_c(i-1, surface_group) - - ! Write name for this surface - call write_dataset(surface_group, "name", s%name) - - call close_group(surface_group) + call surface_to_hdf5_c(i-1, surfaces_group) end do SURFACE_LOOP call close_group(surfaces_group) diff --git a/src/surface_header.C b/src/surface_header.C index db53c8982..4cfda86ef 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -1,3 +1,4 @@ +#include // for std::transform #include // For std::array #include // For strcmp #include // For numeric_limits @@ -6,23 +7,61 @@ #include "pugixml/pugixml.hpp" #include "hdf5.h" +#include "error.h" #include "hdf5_interface.h" // DEBUGGING #include #include +bool +check_for_node(const pugi::xml_node &node, const char *name) +{ + if (node.attribute(name)) { + return true; + } else if (node.child(name)) { + return true; + } else { + return false; + } +} + +std::string +get_node_value(const pugi::xml_node &node, const char *name) +{ + const pugi::char_t *value_char; + if (node.attribute(name)) { + value_char = node.attribute(name).value(); + } else if (node.child(name)) { + value_char = node.child_value(name); + } else { + std::string err_msg("Node \""); + err_msg += name; + err_msg += "\" is not a memeber of the \""; + err_msg += node.name(); + err_msg += "\" XML node"; + fatal_error(err_msg); + } + + std::string value(value_char); + std::transform(value.begin(), value.end(), value.begin(), ::tolower); + return value; +} + //============================================================================== // Constants //============================================================================== -const double FP_COINCIDENT = 1e-12; -const double INFTY = std::numeric_limits::max(); +//extern "C" const double FP_COINCIDENT{1e-12}; +//extern "C" const double INFTY{std::numeric_limits::max()}; +extern "C" double FP_COINCIDENT; +//extern "C" double INFTY; +const double INFTY{std::numeric_limits::max()}; -const int BC_TRANSMIT{0}; -const int BC_VACUUM{1}; -const int BC_REFLECT{2}; -const int BC_PERIODIC{3}; +extern "C" const int BC_TRANSMIT{0}; +extern "C" const int BC_VACUUM{1}; +extern "C" const int BC_REFLECT{2}; +extern "C" const int BC_PERIODIC{3}; //============================================================================== // Global array of surfaces @@ -95,12 +134,14 @@ class Surface { public: int id; //!< Unique ID - int neighbor_pos[], //!< List of cells on positive side - neighbor_neg[]; //!< List of cells on negative side + //int neighbor_pos[], //!< List of cells on positive side + // neighbor_neg[]; //!< List of cells on negative side int bc; //!< Boundary condition //TODO: switch that zero to a NONE constant. //int i_periodic = 0; //!< Index of corresponding periodic surface - char name[104]; //!< User-defined name + std::string name{""}; //!< User-defined name + + Surface(pugi::xml_node surf_node); //! Determine which side of a surface a point lies on. //! @param xyz[3] The 3D Cartesian coordinate of a point. @@ -139,12 +180,54 @@ public: //! Write all information needed to reconstruct the surface to an HDF5 group. //! @param group_id An HDF5 group id. - virtual void to_hdf5(hid_t group_id) const = 0; + void to_hdf5(hid_t group_id) const; protected: - void write_bc_to_hdf5(hid_t group_id) const; + virtual void to_hdf5_inner(hid_t group_id) const = 0; }; +Surface::Surface(pugi::xml_node surf_node) +{ + if (check_for_node(surf_node, "id")) { + id = stoi(get_node_value(surf_node, "id")); + } else { + fatal_error("Must specify id of surface in geometry XML file."); + } + //TODO: check for duplicate IDs + + if (check_for_node(surf_node, "name")) { + name = get_node_value(surf_node, "name"); + } + + if (check_for_node(surf_node, "boundary")) { + std::string surf_bc = get_node_value(surf_node, "boundary"); + + if (surf_bc.compare("transmission") == 0 + or surf_bc.compare("transmit") == 0 + or surf_bc.compare("") == 0) { + bc = BC_TRANSMIT; + + } else if (surf_bc.compare("vacuum") == 0) { + bc = BC_VACUUM; + + } else if (surf_bc.compare("reflective") == 0 + or surf_bc.compare("reflect") == 0 + or surf_bc.compare("reflecting") == 0) { + bc = BC_REFLECT; + } else if (surf_bc.compare("periodic") == 0) { + bc = BC_PERIODIC; + } else { + std::string err_msg("Unknown boundary condition \""); + err_msg += surf_bc + "\" specified on surface " + std::to_string(id); + fatal_error(err_msg); + } + + } else { + bc = BC_TRANSMIT; + } + +} + bool Surface::sense(const double xyz[3], const double uvw[3]) const { @@ -181,22 +264,35 @@ Surface::reflect(const double xyz[3], double uvw[3]) const } void -Surface::write_bc_to_hdf5(hid_t group_id) const +Surface::to_hdf5(hid_t group_id) const { + std::string group_name{"surface "}; + group_name += std::to_string(id); + + hid_t surf_group = create_group(group_id, group_name); + switch(bc) { case BC_TRANSMIT : - write_string(group_id, "boundary_type", "transmission"); + write_string(surf_group, "boundary_type", "transmission"); break; case BC_VACUUM : - write_string(group_id, "boundary_type", "vacuum"); + write_string(surf_group, "boundary_type", "vacuum"); break; case BC_REFLECT : - write_string(group_id, "boundary_type", "reflective"); + write_string(surf_group, "boundary_type", "reflective"); break; case BC_PERIODIC : - write_string(group_id, "boundary_type", "periodic"); + write_string(surf_group, "boundary_type", "periodic"); break; } + + if (name.compare("")) { + write_string(surf_group, "name", name); + } + + to_hdf5_inner(surf_group); + + close_group(surf_group); } //============================================================================== @@ -210,6 +306,8 @@ Surface::write_bc_to_hdf5(hid_t group_id) const class PeriodicSurface : public Surface { public: + PeriodicSurface(pugi::xml_node surf_node) : Surface(surf_node) {} + //! Translate a particle onto this surface from a periodic partner surface. //! @param other A pointer to the partner surface in this periodic BC. //! @param xyz[3] A point on the partner surface that will be translated onto @@ -271,12 +369,13 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; }; SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) + : PeriodicSurface(surf_node) { read_coeffs(surf_node, x0); } @@ -297,12 +396,11 @@ inline void SurfaceXPlane::normal(const double xyz[3], double uvw[3]) const axis_aligned_plane_normal<0, 1, 2>(xyz, uvw); } -void SurfaceXPlane::to_hdf5(hid_t group_id) const +void SurfaceXPlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "x-plane"); std::array coeffs{{x0}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } bool SurfaceXPlane::periodic_translate(PeriodicSurface *other, double xyz[3], @@ -347,12 +445,13 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; }; SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) + : PeriodicSurface(surf_node) { read_coeffs(surf_node, y0); } @@ -373,12 +472,11 @@ inline void SurfaceYPlane::normal(const double xyz[3], double uvw[3]) const axis_aligned_plane_normal<1, 0, 2>(xyz, uvw); } -void SurfaceYPlane::to_hdf5(hid_t group_id) const +void SurfaceYPlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "y-plane"); std::array coeffs{{y0}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } bool SurfaceYPlane::periodic_translate(PeriodicSurface *other, double xyz[3], @@ -423,12 +521,13 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; }; SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) + : PeriodicSurface(surf_node) { read_coeffs(surf_node, z0); } @@ -449,12 +548,11 @@ inline void SurfaceZPlane::normal(const double xyz[3], double uvw[3]) const axis_aligned_plane_normal<2, 0, 1>(xyz, uvw); } -void SurfaceZPlane::to_hdf5(hid_t group_id) const +void SurfaceZPlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "z-plane"); std::array coeffs{{z0}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } bool SurfaceZPlane::periodic_translate(PeriodicSurface *other, double xyz[3], @@ -481,12 +579,13 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; }; SurfacePlane::SurfacePlane(pugi::xml_node surf_node) + : PeriodicSurface(surf_node) { read_coeffs(surf_node, A, B, C, D); } @@ -520,12 +619,11 @@ SurfacePlane::normal(const double xyz[3], double uvw[3]) const uvw[2] = C; } -void SurfacePlane::to_hdf5(hid_t group_id) const +void SurfacePlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "plane"); std::array coeffs{{A, B, C, D}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } bool SurfacePlane::periodic_translate(PeriodicSurface *other, double xyz[3], @@ -635,10 +733,11 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; }; SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node) + : Surface(surf_node) { read_coeffs(surf_node, y0, z0, r); } @@ -661,12 +760,11 @@ inline void SurfaceXCylinder::normal(const double xyz[3], double uvw[3]) const } -void SurfaceXCylinder::to_hdf5(hid_t group_id) const +void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "x-cylinder"); std::array coeffs{{y0, z0, r}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } //============================================================================== @@ -686,10 +784,11 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; }; SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node) + : Surface(surf_node) { read_coeffs(surf_node, x0, z0, r); } @@ -711,12 +810,11 @@ inline void SurfaceYCylinder::normal(const double xyz[3], double uvw[3]) const axis_aligned_cylinder_normal<1, 0, 2>(xyz, uvw, x0, z0); } -void SurfaceYCylinder::to_hdf5(hid_t group_id) const +void SurfaceYCylinder::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "y-cylinder"); std::array coeffs{{x0, z0, r}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } //============================================================================== @@ -736,10 +834,11 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; }; SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) + : Surface(surf_node) { read_coeffs(surf_node, x0, y0, r); } @@ -761,12 +860,11 @@ inline void SurfaceZCylinder::normal(const double xyz[3], double uvw[3]) const axis_aligned_cylinder_normal<2, 0, 1>(xyz, uvw, x0, y0); } -void SurfaceZCylinder::to_hdf5(hid_t group_id) const +void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "z-cylinder"); std::array coeffs{{x0, y0, r}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } //============================================================================== @@ -786,10 +884,11 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; }; SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) + : Surface(surf_node) { read_coeffs(surf_node, x0, y0, z0, r); } @@ -848,12 +947,11 @@ inline void SurfaceSphere::normal(const double xyz[3], double uvw[3]) const uvw[2] = 2.0 * (xyz[2] - z0); } -void SurfaceSphere::to_hdf5(hid_t group_id) const +void SurfaceSphere::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "sphere"); std::array coeffs{{x0, y0, z0, r}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } //============================================================================== @@ -956,10 +1054,11 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; }; SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) + : Surface(surf_node) { read_coeffs(surf_node, x0, y0, z0, r_sq); } @@ -981,12 +1080,11 @@ inline void SurfaceXCone::normal(const double xyz[3], double uvw[3]) const axis_aligned_cone_normal<0, 1, 2>(xyz, uvw, x0, y0, z0, r_sq); } -void SurfaceXCone::to_hdf5(hid_t group_id) const +void SurfaceXCone::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "x-cone"); std::array coeffs{{x0, y0, z0, r_sq}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } //============================================================================== @@ -1006,10 +1104,11 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; }; SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) + : Surface(surf_node) { read_coeffs(surf_node, x0, y0, z0, r_sq); } @@ -1031,12 +1130,11 @@ inline void SurfaceYCone::normal(const double xyz[3], double uvw[3]) const axis_aligned_cone_normal<1, 0, 2>(xyz, uvw, y0, x0, z0, r_sq); } -void SurfaceYCone::to_hdf5(hid_t group_id) const +void SurfaceYCone::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "y-cone"); std::array coeffs{{x0, y0, z0, r_sq}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } //============================================================================== @@ -1056,10 +1154,11 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; }; SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) + : Surface(surf_node) { read_coeffs(surf_node, x0, y0, z0, r_sq); } @@ -1081,12 +1180,11 @@ inline void SurfaceZCone::normal(const double xyz[3], double uvw[3]) const axis_aligned_cone_normal<2, 0, 1>(xyz, uvw, z0, x0, y0, r_sq); } -void SurfaceZCone::to_hdf5(hid_t group_id) const +void SurfaceZCone::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "z-cone"); std::array coeffs{{x0, y0, z0, r_sq}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } //============================================================================== @@ -1106,10 +1204,11 @@ public: double distance(const double xyz[3], const double uvw[3], bool coincident) const; void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5(hid_t group_id) const; + void to_hdf5_inner(hid_t group_id) const; }; SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) + : Surface(surf_node) { read_coeffs(surf_node, A, B, C, D, E, F, G, H, J, K); } @@ -1191,12 +1290,11 @@ SurfaceQuadric::normal(const double xyz[3], double uvw[3]) const uvw[2] = 2.0*C*z + E*y + F*x + J; } -void SurfaceQuadric::to_hdf5(hid_t group_id) const +void SurfaceQuadric::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "quadric"); std::array coeffs{{A, B, C, D, E, F, G, H, J, K}}; write_double_1D(group_id, "coefficients", coeffs); - write_bc_to_hdf5(group_id); } //============================================================================== @@ -1220,51 +1318,42 @@ read_surfaces(pugi::xml_node *node) int i_surf; for (surf_node = node->child("surface"), i_surf = 0; surf_node; surf_node = surf_node.next_sibling("surface"), i_surf++) { - const pugi::char_t *surf_type; - if (surf_node.attribute("type")) { - surf_type = surf_node.attribute("type").value(); - } else if (surf_node.child("type")) { - surf_type = surf_node.child_value("type"); - } else { - std::cout << "ERROR: Found a surface with no type attribute/node" - << std::endl; - //TODO: call fatal_error - } + std::string surf_type = get_node_value(surf_node, "type"); - if (strcmp(surf_type, "x-plane") == 0) { + if (surf_type.compare("x-plane") == 0) { surfaces_c[i_surf] = new SurfaceXPlane(surf_node); - } else if (strcmp(surf_type, "y-plane") == 0) { + } else if (surf_type.compare("y-plane") == 0) { surfaces_c[i_surf] = new SurfaceYPlane(surf_node); - } else if (strcmp(surf_type, "z-plane") == 0) { + } else if (surf_type.compare("z-plane") == 0) { surfaces_c[i_surf] = new SurfaceZPlane(surf_node); - } else if (strcmp(surf_type, "plane") == 0) { + } else if (surf_type.compare("plane") == 0) { surfaces_c[i_surf] = new SurfacePlane(surf_node); - } else if (strcmp(surf_type, "x-cylinder") == 0) { + } else if (surf_type.compare("x-cylinder") == 0) { surfaces_c[i_surf] = new SurfaceXCylinder(surf_node); - } else if (strcmp(surf_type, "y-cylinder") == 0) { + } else if (surf_type.compare("y-cylinder") == 0) { surfaces_c[i_surf] = new SurfaceYCylinder(surf_node); - } else if (strcmp(surf_type, "z-cylinder") == 0) { + } else if (surf_type.compare("z-cylinder") == 0) { surfaces_c[i_surf] = new SurfaceZCylinder(surf_node); - } else if (strcmp(surf_type, "sphere") == 0) { + } else if (surf_type.compare("sphere") == 0) { surfaces_c[i_surf] = new SurfaceSphere(surf_node); - } else if (strcmp(surf_type, "x-cone") == 0) { + } else if (surf_type.compare("x-cone") == 0) { surfaces_c[i_surf] = new SurfaceXCone(surf_node); - } else if (strcmp(surf_type, "y-cone") == 0) { + } else if (surf_type.compare("y-cone") == 0) { surfaces_c[i_surf] = new SurfaceYCone(surf_node); - } else if (strcmp(surf_type, "z-cone") == 0) { + } else if (surf_type.compare("z-cone") == 0) { surfaces_c[i_surf] = new SurfaceZCone(surf_node); - } else if (strcmp(surf_type, "quadric") == 0) { + } else if (surf_type.compare("quadric") == 0) { surfaces_c[i_surf] = new SurfaceQuadric(surf_node); } else { @@ -1272,32 +1361,6 @@ read_surfaces(pugi::xml_node *node) std::cout << surf_type << std::endl; //TODO: call fatal_error } - - const pugi::char_t *surf_bc{""}; - if (surf_node.attribute("boundary")) { - surf_bc = surf_node.attribute("boundary").value(); - } else if (surf_node.child("boundary")) { - surf_bc = surf_node.attribute("boundary").value(); - } - - if (strcmp(surf_bc, "transmission") == 0 - or strcmp(surf_bc, "transmit") == 0 - or strcmp(surf_bc, "") == 0) { - surfaces_c[i_surf]->bc = BC_TRANSMIT; - - } else if (strcmp(surf_bc, "vacuum") == 0) { - surfaces_c[i_surf]->bc = BC_VACUUM; - - } else if (strcmp(surf_bc, "reflective") == 0 - or strcmp(surf_bc, "reflect") == 0 - or strcmp(surf_bc, "reflecting") == 0) { - surfaces_c[i_surf]->bc = BC_REFLECT; - } else if (strcmp(surf_bc, "periodic") == 0) { - surfaces_c[i_surf]->bc = BC_PERIODIC; - } else { - std::cout << "Unknown boundary condition" << std::endl; - //TODO: call fatal_error - } } } } From afe2f619c8dd855e9ff0503e4eb9af944b2fba3d Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 23 Jan 2018 15:21:21 -0500 Subject: [PATCH 129/282] Improve surface coeff error handling --- src/input_xml.F90 | 7 --- src/surface_header.C | 134 ++++++++++++++++++++++++++++++------------- 2 files changed, 95 insertions(+), 46 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 462b9708f..3f22522c2 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1068,13 +1068,6 @@ contains ! surface coordinates. n = node_word_count(node_surf, "coeffs") - if (n < coeffs_reqd) then - call fatal_error("Not enough coefficients specified for surface: " & - // trim(to_str(s%id))) - elseif (n > coeffs_reqd) then - call fatal_error("Too many coefficients specified for surface: " & - // trim(to_str(s%id))) - end if allocate(coeffs(n)) call get_node_array(node_surf, "coeffs", coeffs) diff --git a/src/surface_header.C b/src/surface_header.C index 4cfda86ef..565352623 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -74,55 +74,111 @@ Surface **surfaces_c; // Helper functions for reading the "coeffs" node of an XML surface element //============================================================================== -const char* get_coeff_str(pugi::xml_node surf_node) +int word_count(const std::string &text) { - if (surf_node.attribute("coeffs")) { - return surf_node.attribute("coeffs").value(); - } else if (surf_node.child("coeffs")) { - return surf_node.child_value("coeffs"); - } else { - std::cout << "ERROR: Found a surface with no coefficients" << std::endl; - return nullptr; + bool in_word = false; + int count{0}; + for (auto c = text.begin(); c != text.end(); c++) { + switch(*c) { + case ' ' : + case '\t' : + case '\r' : + case '\n' : + if (in_word) { + in_word = false; + count++; + } + break; + default : + in_word = true; + } } + if (in_word) count++; + return count; } -void read_coeffs(pugi::xml_node surf_node, double &c1) +void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1) { - const char *coeffs = get_coeff_str(surf_node); - int stat = sscanf(coeffs, "%lf", &c1); + // Check the given number of coefficients. + std::string coeffs = get_node_value(surf_node, "coeffs"); + int n_words = word_count(coeffs); + if (n_words != 1) { + std::string err_msg{"Surface "}; + err_msg += std::to_string(surf_id); + err_msg += " expects 1 coeff but was given "; + err_msg += std::to_string(n_words); + fatal_error(err_msg); + } + + // Parse the coefficients. + int stat = sscanf(coeffs.c_str(), "%lf", &c1); if (stat != 1) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; + fatal_error("Something went wrong reading surface coeffs"); } } -void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3) +void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1, double &c2, + double &c3) { - const char *coeffs = get_coeff_str(surf_node); - int stat = sscanf(coeffs, "%lf %lf %lf", &c1, &c2, &c3); + // Check the given number of coefficients. + std::string coeffs = get_node_value(surf_node, "coeffs"); + int n_words = word_count(coeffs); + if (n_words != 3) { + std::string err_msg{"Surface "}; + err_msg += std::to_string(surf_id); + err_msg += " expects 3 coeffs but was given "; + err_msg += std::to_string(n_words); + fatal_error(err_msg); + } + + // Parse the coefficients. + int stat = sscanf(coeffs.c_str(), "%lf %lf %lf", &c1, &c2, &c3); if (stat != 3) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; + fatal_error("Something went wrong reading surface coeffs"); } } -void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3, - double &c4) +void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1, double &c2, + double &c3, double &c4) { - const char *coeffs = get_coeff_str(surf_node); - int stat = sscanf(coeffs, "%lf %lf %lf %lf", &c1, &c2, &c3, &c4); + // Check the given number of coefficients. + std::string coeffs = get_node_value(surf_node, "coeffs"); + int n_words = word_count(coeffs); + if (n_words != 4) { + std::string err_msg{"Surface "}; + err_msg += std::to_string(surf_id); + err_msg += " expects 4 coeffs but was given "; + err_msg += std::to_string(n_words); + fatal_error(err_msg); + } + + // Parse the coefficients. + int stat = sscanf(coeffs.c_str(), "%lf %lf %lf %lf", &c1, &c2, &c3, &c4); if (stat != 4) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; + fatal_error("Something went wrong reading surface coeffs"); } } -void read_coeffs(pugi::xml_node surf_node, double &c1, double &c2, double &c3, - double &c4, double &c5, double &c6, double &c7, double &c8, - double &c9, double &c10) +void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1, double &c2, + double &c3, double &c4, double &c5, double &c6, double &c7, + double &c8, double &c9, double &c10) { - const char *coeffs = get_coeff_str(surf_node); - int stat = sscanf(coeffs, "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", + // Check the given number of coefficients. + std::string coeffs = get_node_value(surf_node, "coeffs"); + int n_words = word_count(coeffs); + if (n_words != 10) { + std::string err_msg{"Surface "}; + err_msg += std::to_string(surf_id); + err_msg += " expects 10 coeffs but was given "; + err_msg += std::to_string(n_words); + fatal_error(err_msg); + } + + // Parse the coefficients. + int stat = sscanf(coeffs.c_str(), "%lf %lf %lf %lf %lf %lf %lf %lf %lf %lf", &c1, &c2, &c3, &c4, &c5, &c6, &c7, &c8, &c9, &c10); if (stat != 10) { - std::cout << "Something went wrong reading surface coeffs!" << std::endl; + fatal_error("Something went wrong reading surface coeffs"); } } @@ -377,7 +433,7 @@ public: SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) : PeriodicSurface(surf_node) { - read_coeffs(surf_node, x0); + read_coeffs(surf_node, id, x0); } inline double SurfaceXPlane::evaluate(const double xyz[3]) const @@ -453,7 +509,7 @@ public: SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) : PeriodicSurface(surf_node) { - read_coeffs(surf_node, y0); + read_coeffs(surf_node, id, y0); } inline double SurfaceYPlane::evaluate(const double xyz[3]) const @@ -529,7 +585,7 @@ public: SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) : PeriodicSurface(surf_node) { - read_coeffs(surf_node, z0); + read_coeffs(surf_node, id, z0); } inline double SurfaceZPlane::evaluate(const double xyz[3]) const @@ -587,7 +643,7 @@ public: SurfacePlane::SurfacePlane(pugi::xml_node surf_node) : PeriodicSurface(surf_node) { - read_coeffs(surf_node, A, B, C, D); + read_coeffs(surf_node, id, A, B, C, D); } double @@ -739,7 +795,7 @@ public: SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node) : Surface(surf_node) { - read_coeffs(surf_node, y0, z0, r); + read_coeffs(surf_node, id, y0, z0, r); } inline double SurfaceXCylinder::evaluate(const double xyz[3]) const @@ -790,7 +846,7 @@ public: SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node) : Surface(surf_node) { - read_coeffs(surf_node, x0, z0, r); + read_coeffs(surf_node, id, x0, z0, r); } inline double SurfaceYCylinder::evaluate(const double xyz[3]) const @@ -840,7 +896,7 @@ public: SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) : Surface(surf_node) { - read_coeffs(surf_node, x0, y0, r); + read_coeffs(surf_node, id, x0, y0, r); } inline double SurfaceZCylinder::evaluate(const double xyz[3]) const @@ -890,7 +946,7 @@ public: SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) : Surface(surf_node) { - read_coeffs(surf_node, x0, y0, z0, r); + read_coeffs(surf_node, id, x0, y0, z0, r); } double SurfaceSphere::evaluate(const double xyz[3]) const @@ -1060,7 +1116,7 @@ public: SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) : Surface(surf_node) { - read_coeffs(surf_node, x0, y0, z0, r_sq); + read_coeffs(surf_node, id, x0, y0, z0, r_sq); } inline double SurfaceXCone::evaluate(const double xyz[3]) const @@ -1110,7 +1166,7 @@ public: SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) : Surface(surf_node) { - read_coeffs(surf_node, x0, y0, z0, r_sq); + read_coeffs(surf_node, id, x0, y0, z0, r_sq); } inline double SurfaceYCone::evaluate(const double xyz[3]) const @@ -1160,7 +1216,7 @@ public: SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) : Surface(surf_node) { - read_coeffs(surf_node, x0, y0, z0, r_sq); + read_coeffs(surf_node, id, x0, y0, z0, r_sq); } inline double SurfaceZCone::evaluate(const double xyz[3]) const @@ -1210,7 +1266,7 @@ public: SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) : Surface(surf_node) { - read_coeffs(surf_node, A, B, C, D, E, F, G, H, J, K); + read_coeffs(surf_node, id, A, B, C, D, E, F, G, H, J, K); } double From 164e6c0ef4c7732a6245da2597fb6ff229deefe5 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 23 Jan 2018 18:29:05 -0500 Subject: [PATCH 130/282] Move more Surface implementation to C++ --- src/geometry.F90 | 20 ++- src/input_xml.F90 | 168 +--------------------- src/output.F90 | 2 +- src/summary.F90 | 5 +- src/surface_header.C | 201 +++++++++++++++++++++++++-- src/surface_header.F90 | 55 +------- src/tallies/tally_filter_surface.F90 | 2 +- src/tracking.F90 | 12 +- 8 files changed, 217 insertions(+), 248 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index f1feb3ba3..d1519f8d2 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -54,16 +54,24 @@ module geometry real(C_DOUBLE), intent(out) :: uvw(3); end subroutine surface_normal_c - function surface_periodic_c(surf_ind1, surf_ind2, xyz, uvw) & + function surface_periodic_c(surf_ind1, xyz, uvw) & bind(C, name="surface_periodic") result(rotational) use ISO_C_BINDING implicit none integer(C_INT), intent(in), value :: surf_ind1; - integer(C_INT), intent(in), value :: surf_ind2; real(C_DOUBLE), intent(inout) :: xyz(3); real(C_DOUBLE), intent(inout) :: uvw(3); logical(C_BOOL) :: rotational end function surface_periodic_c + + function surface_i_periodic_c(surf_ind) bind(C, name="surface_i_periodic") & + result(i_periodic) + use ISO_C_BINDING + implicit none + integer(C_INT), intent(in), value :: surf_ind + integer(C_INT) :: i_periodic + end function + end interface contains @@ -857,15 +865,15 @@ contains ! Copy positive neighbors to Surface instance n = neighbor_pos(i)%size() if (n > 0) then - allocate(surfaces(i)%obj%neighbor_pos(n)) - surfaces(i)%obj%neighbor_pos(:) = neighbor_pos(i)%data(1:n) + allocate(surfaces(i)%neighbor_pos(n)) + surfaces(i)%neighbor_pos(:) = neighbor_pos(i)%data(1:n) end if ! Copy negative neighbors to Surface instance n = neighbor_neg(i)%size() if (n > 0) then - allocate(surfaces(i)%obj%neighbor_neg(n)) - surfaces(i)%obj%neighbor_neg(:) = neighbor_neg(i)%data(1:n) + allocate(surfaces(i)%neighbor_neg(n)) + surfaces(i)%neighbor_neg(:) = neighbor_neg(i)%data(1:n) end if end do diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 3f22522c2..9b6e1c6fc 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -919,12 +919,8 @@ contains integer :: id integer :: univ_id integer :: n_cells_in_univ - integer :: coeffs_reqd - integer :: i_xmin, i_xmax, i_ymin, i_ymax, i_zmin, i_zmax - real(8) :: xmin, xmax, ymin, ymax, zmin, zmax integer, allocatable :: temp_int_array(:) real(8) :: phi, theta, psi - real(8), allocatable :: coeffs(:) logical :: file_exists logical :: boundary_exists character(MAX_LINE_LEN) :: filename @@ -984,13 +980,6 @@ contains call fatal_error("No surfaces found in geometry.xml!") end if - xmin = INFINITY - xmax = -INFINITY - ymin = INFINITY - ymax = -INFINITY - zmin = INFINITY - zmax = -INFINITY - ! Allocate cells array allocate(surfaces(n_surfaces)) @@ -998,52 +987,7 @@ contains ! Get pointer to i-th surface node node_surf = node_surf_list(i) - ! Copy and interpret surface type - word = '' - if (check_for_node(node_surf, "type")) & - call get_node_value(node_surf, "type", word) - select case(to_lower(word)) - case ('x-plane') - coeffs_reqd = 1 - allocate(SurfaceXPlane :: surfaces(i)%obj) - case ('y-plane') - coeffs_reqd = 1 - allocate(SurfaceYPlane :: surfaces(i)%obj) - case ('z-plane') - coeffs_reqd = 1 - allocate(SurfaceZPlane :: surfaces(i)%obj) - case ('plane') - coeffs_reqd = 4 - allocate(SurfacePlane :: surfaces(i)%obj) - case ('x-cylinder') - coeffs_reqd = 3 - allocate(SurfaceXCylinder :: surfaces(i)%obj) - case ('y-cylinder') - coeffs_reqd = 3 - allocate(SurfaceYCylinder :: surfaces(i)%obj) - case ('z-cylinder') - coeffs_reqd = 3 - allocate(SurfaceZCylinder :: surfaces(i)%obj) - case ('sphere') - coeffs_reqd = 4 - allocate(SurfaceSphere :: surfaces(i)%obj) - case ('x-cone') - coeffs_reqd = 4 - allocate(SurfaceXCone :: surfaces(i)%obj) - case ('y-cone') - coeffs_reqd = 4 - allocate(SurfaceYCone :: surfaces(i)%obj) - case ('z-cone') - coeffs_reqd = 4 - allocate(SurfaceZCone :: surfaces(i)%obj) - case ('quadric') - coeffs_reqd = 10 - allocate(SurfaceQuadric :: surfaces(i)%obj) - case default - call fatal_error("Invalid surface type: " // trim(word)) - end select - - s => surfaces(i)%obj + s => surfaces(i) ! Copy data into cells if (check_for_node(node_surf, "id")) then @@ -1058,44 +1002,10 @@ contains // to_str(s%id)) end if - ! Copy surface name - if (check_for_node(node_surf, "name")) then - call get_node_value(node_surf, "name", s%name) - end if - ! Check to make sure that the proper number of coefficients ! have been specified for the given type of surface. Then copy ! surface coordinates. - n = node_word_count(node_surf, "coeffs") - - allocate(coeffs(n)) - call get_node_array(node_surf, "coeffs", coeffs) - - select type(s) - type is (SurfaceXPlane) - ! Determine outer surfaces - xmin = min(xmin, coeffs(1)) - xmax = max(xmax, coeffs(1)) - if (xmin == coeffs(1)) i_xmin = i - if (xmax == coeffs(1)) i_xmax = i - type is (SurfaceYPlane) - ! Determine outer surfaces - ymin = min(ymin, coeffs(1)) - ymax = max(ymax, coeffs(1)) - if (ymin == coeffs(1)) i_ymin = i - if (ymax == coeffs(1)) i_ymax = i - type is (SurfaceZPlane) - ! Determine outer surfaces - zmin = min(zmin, coeffs(1)) - zmax = max(zmax, coeffs(1)) - if (zmin == coeffs(1)) i_zmin = i - if (zmax == coeffs(1)) i_zmax = i - end select - - ! No longer need coefficients - deallocate(coeffs) - ! Boundary conditions word = '' if (check_for_node(node_surf, "boundary")) & @@ -1112,12 +1022,6 @@ contains case ('periodic') s%bc = BC_PERIODIC boundary_exists = .true. - - ! Check for specification of periodic surface - if (check_for_node(node_surf, "periodic_surface_id")) then - call get_node_value(node_surf, "periodic_surface_id", & - s % i_periodic) - end if case default call fatal_error("Unknown boundary condition '" // trim(word) // & &"' specified on surface " // trim(to_str(s%id))) @@ -1134,76 +1038,6 @@ contains end if end if - ! Determine opposite side for periodic boundaries - do i = 1, size(surfaces) - if (surfaces(i) % obj % bc == BC_PERIODIC) then - select type (surf => surfaces(i) % obj) - type is (SurfaceXPlane) - if (surf % i_periodic == NONE) then - if (i == i_xmin) then - surf % i_periodic = i_xmax - elseif (i == i_xmax) then - surf % i_periodic = i_xmin - else - call fatal_error("Periodic boundary condition applied to & - &interior surface.") - end if - else - surf % i_periodic = surface_dict % get(surf % i_periodic) - end if - - type is (SurfaceYPlane) - if (surf % i_periodic == NONE) then - if (i == i_ymin) then - surf % i_periodic = i_ymax - elseif (i == i_ymax) then - surf % i_periodic = i_ymin - else - call fatal_error("Periodic boundary condition applied to & - &interior surface.") - end if - else - surf % i_periodic = surface_dict % get(surf % i_periodic) - end if - - type is (SurfaceZPlane) - if (surf % i_periodic == NONE) then - if (i == i_zmin) then - surf % i_periodic = i_zmax - elseif (i == i_zmax) then - surf % i_periodic = i_zmin - else - call fatal_error("Periodic boundary condition applied to & - &interior surface.") - end if - else - surf % i_periodic = surface_dict % get(surf % i_periodic) - end if - - type is (SurfacePlane) - if (surf % i_periodic == NONE) then - call fatal_error("No matching periodic surface specified for & - &periodic boundary condition on surface " // & - trim(to_str(surf % id)) // ".") - else - surf % i_periodic = surface_dict % get(surf % i_periodic) - end if - - class default - call fatal_error("Periodic boundary condition applied to & - &non-planar surface.") - end select - - ! Make sure opposite surface is also periodic - associate (surf => surfaces(i) % obj) - if (surfaces(surf % i_periodic) % obj % bc /= BC_PERIODIC) then - call fatal_error("Could not find matching surface for periodic & - &boundary on surface " // trim(to_str(surf % id)) // ".") - end if - end associate - end if - end do - ! ========================================================================== ! READ CELLS FROM GEOMETRY.XML diff --git a/src/output.F90 b/src/output.F90 index 1b7cae121..4113eea47 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -259,7 +259,7 @@ contains ! Print surface if (p % surface /= NONE) then - write(ou,*) ' Surface = ' // to_str(sign(surfaces(i)%obj%id, p % surface)) + write(ou,*) ' Surface = ' // to_str(sign(surfaces(i)%id, p % surface)) end if ! Display weight, energy, grid index, and interpolation factor diff --git a/src/summary.F90 b/src/summary.F90 index 77bded983..9b82a0396 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -133,12 +133,11 @@ contains real(8), allocatable :: cell_temperatures(:) integer(HID_T) :: geom_group integer(HID_T) :: cells_group, cell_group - integer(HID_T) :: surfaces_group, surface_group + integer(HID_T) :: surfaces_group integer(HID_T) :: universes_group, univ_group integer(HID_T) :: lattices_group, lattice_group character(:), allocatable :: region_spec type(Cell), pointer :: c - class(Surface), pointer :: s class(Lattice), pointer :: lat ! Use H5LT interface to write number of geometry objects @@ -233,7 +232,7 @@ contains region_spec = trim(region_spec) // " |" case default region_spec = trim(region_spec) // " " // to_str(& - sign(surfaces(abs(k))%obj%id, k)) + sign(surfaces(abs(k))%id, k)) end select end do call write_dataset(cell_group, "region", adjustl(region_spec)) diff --git a/src/surface_header.C b/src/surface_header.C index 565352623..afea2b43d 100644 --- a/src/surface_header.C +++ b/src/surface_header.C @@ -1,7 +1,8 @@ #include // for std::transform -#include // For std::array +#include #include // For strcmp #include // For numeric_limits +#include #include // For fabs #include "pugixml/pugixml.hpp" @@ -45,6 +46,7 @@ get_node_value(const pugi::xml_node &node, const char *name) std::string value(value_char); std::transform(value.begin(), value.end(), value.begin(), ::tolower); + //TODO: trim whitespace return value; } @@ -57,6 +59,7 @@ get_node_value(const pugi::xml_node &node, const char *name) extern "C" double FP_COINCIDENT; //extern "C" double INFTY; const double INFTY{std::numeric_limits::max()}; +const int C_NONE{-1}; extern "C" const int BC_TRANSMIT{0}; extern "C" const int BC_VACUUM{1}; @@ -70,6 +73,8 @@ extern "C" const int BC_PERIODIC{3}; class Surface; Surface **surfaces_c; +std::map surface_dict; + //============================================================================== // Helper functions for reading the "coeffs" node of an XML surface element //============================================================================== @@ -182,6 +187,19 @@ void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1, double &c2, } } +//============================================================================== +//============================================================================== + +struct BoundingBox +{ + double xmin; + double xmax; + double ymin; + double ymax; + double zmin; + double zmax; +}; + //============================================================================== //! A geometry primitive used to define regions of 3D space. //============================================================================== @@ -193,8 +211,6 @@ public: //int neighbor_pos[], //!< List of cells on positive side // neighbor_neg[]; //!< List of cells on negative side int bc; //!< Boundary condition - //TODO: switch that zero to a NONE constant. - //int i_periodic = 0; //!< Index of corresponding periodic surface std::string name{""}; //!< User-defined name Surface(pugi::xml_node surf_node); @@ -249,7 +265,6 @@ Surface::Surface(pugi::xml_node surf_node) } else { fatal_error("Must specify id of surface in geometry XML file."); } - //TODO: check for duplicate IDs if (check_for_node(surf_node, "name")) { name = get_node_value(surf_node, "name"); @@ -362,7 +377,9 @@ Surface::to_hdf5(hid_t group_id) const class PeriodicSurface : public Surface { public: - PeriodicSurface(pugi::xml_node surf_node) : Surface(surf_node) {} + int i_periodic{C_NONE}; //!< Index of corresponding periodic surface + + PeriodicSurface(pugi::xml_node surf_node); //! Translate a particle onto this surface from a periodic partner surface. //! @param other A pointer to the partner surface in this periodic BC. @@ -374,8 +391,18 @@ public: //! boundary condition. virtual bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const = 0; + + virtual struct BoundingBox bounding_box() const = 0; }; +PeriodicSurface::PeriodicSurface(pugi::xml_node surf_node) + : Surface(surf_node) +{ + if (check_for_node(surf_node, "periodic_surface_id")) { + i_periodic = stoi(get_node_value(surf_node, "periodic_surface_id")); + } +} + //============================================================================== // Generic functions for x-, y-, and z-, planes. //============================================================================== @@ -428,6 +455,7 @@ public: void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; + struct BoundingBox bounding_box() const; }; SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) @@ -485,6 +513,13 @@ bool SurfaceXPlane::periodic_translate(PeriodicSurface *other, double xyz[3], } } +struct BoundingBox +SurfaceXPlane::bounding_box() const +{ + struct BoundingBox out{x0, x0, -INFTY, INFTY, -INFTY, INFTY}; + return out; +} + //============================================================================== // SurfaceYPlane //! A plane perpendicular to the y-axis. @@ -504,6 +539,7 @@ public: void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; + struct BoundingBox bounding_box() const; }; SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) @@ -561,6 +597,13 @@ bool SurfaceYPlane::periodic_translate(PeriodicSurface *other, double xyz[3], } } +struct BoundingBox +SurfaceYPlane::bounding_box() const +{ + struct BoundingBox out{-INFTY, INFTY, y0, y0, -INFTY, INFTY}; + return out; +} + //============================================================================== // SurfaceZPlane //! A plane perpendicular to the z-axis. @@ -580,6 +623,7 @@ public: void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; + struct BoundingBox bounding_box() const; }; SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) @@ -619,6 +663,13 @@ bool SurfaceZPlane::periodic_translate(PeriodicSurface *other, double xyz[3], return false; } +struct BoundingBox +SurfaceZPlane::bounding_box() const +{ + struct BoundingBox out{-INFTY, INFTY, -INFTY, INFTY, z0, z0}; + return out; +} + //============================================================================== // SurfacePlane //! A general plane. @@ -638,6 +689,7 @@ public: void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; + struct BoundingBox bounding_box() const; }; SurfacePlane::SurfacePlane(pugi::xml_node surf_node) @@ -698,6 +750,13 @@ bool SurfacePlane::periodic_translate(PeriodicSurface *other, double xyz[3], return false; } +struct BoundingBox +SurfacePlane::bounding_box() const +{ + struct BoundingBox out{-INFTY, INFTY, -INFTY, INFTY, -INFTY, INFTY}; + return out; +} + //============================================================================== // Generic functions for x-, y-, and z-, cylinders //============================================================================== @@ -1359,10 +1418,10 @@ extern "C" void read_surfaces(pugi::xml_node *node) { // Count the number of surfaces. - int n_surfaces = 0; + int n_surfaces{0}; for (pugi::xml_node surf_node = node->child("surface"); surf_node; surf_node = surf_node.next_sibling("surface")) { - n_surfaces += 1; + n_surfaces++; } // Allocate the array of Surface pointers. @@ -1413,12 +1472,125 @@ read_surfaces(pugi::xml_node *node) surfaces_c[i_surf] = new SurfaceQuadric(surf_node); } else { - std::cout << "Call error or handle uppercase here!" << std::endl; + std::cout << "Call error here!" << std::endl; std::cout << surf_type << std::endl; //TODO: call fatal_error } } } + + // Fill the surface dictionary. + for (int i_surf = 0; i_surf < n_surfaces; i_surf++) { + int id = surfaces_c[i_surf]->id; + auto in_dict = surface_dict.find(id); + if (in_dict == surface_dict.end()) { + surface_dict[id] = i_surf; + } else { + std::string err_msg{"Two or more surfaces use the same unique ID: "}; + err_msg += std::to_string(id); + fatal_error(err_msg); + } + } + + // Find the global bounding box (of periodic BC surfaces). + double xmin{INFTY}, xmax{-INFTY}, ymin{INFTY}, ymax{-INFTY}, + zmin{INFTY}, zmax{-INFTY}; + int i_xmin, i_xmax, i_ymin, i_ymax, i_zmin, i_zmax; + for (int i_surf = 0; i_surf < n_surfaces; i_surf++) { + if (surfaces_c[i_surf]->bc == BC_PERIODIC) { + // Downcast to the PeriodicSurface type. + Surface *surf_base = surfaces_c[i_surf]; + PeriodicSurface *surf = dynamic_cast(surf_base); + //TODO: check for null pointer + + // See if this surface makes part of the global bounding box. + struct BoundingBox bb = surf->bounding_box(); + if (bb.xmin > -INFTY and bb.xmin < xmin) { + xmin = bb.xmin; + i_xmin = i_surf; + } + if (bb.xmax < INFTY and bb.xmax > xmax) { + xmax = bb.xmax; + i_xmax = i_surf; + } + if (bb.ymin > -INFTY and bb.ymin < ymin) { + ymin = bb.ymin; + i_ymin = i_surf; + } + if (bb.ymax < INFTY and bb.ymax > ymax) { + ymax = bb.ymax; + i_ymax = i_surf; + } + if (bb.zmin > -INFTY and bb.zmin < zmin) { + zmin = bb.zmin; + i_zmin = i_surf; + } + if (bb.zmax < INFTY and bb.zmax > zmax) { + zmax = bb.zmax; + i_zmax = i_surf; + } + } + } + + // Set i_periodic for periodic BC surfaces. + for (int i_surf = 0; i_surf < n_surfaces; i_surf++) { + if (surfaces_c[i_surf]->bc == BC_PERIODIC) { + // Downcast to the PeriodicSurface type. + Surface *surf_base = surfaces_c[i_surf]; + PeriodicSurface *surf = dynamic_cast(surf_base); + + // Also try downcasting to the SurfacePlane type (which must be handled + // differently). + SurfacePlane *surf_p = dynamic_cast(surf); + + if (surf_p == NULL) { + // This is not a SurfacePlane. + if (surf->i_periodic == C_NONE) { + // The user did not specify the matching periodic surface. See if we + // can find the partnered surface from the bounding box information. + if (i_surf == i_xmin) { + surf->i_periodic = i_xmax; + } else if (i_surf == i_xmax) { + surf->i_periodic = i_xmin; + } else if (i_surf == i_ymin) { + surf->i_periodic = i_ymax; + } else if (i_surf == i_ymax) { + surf->i_periodic = i_ymin; + } else if (i_surf == i_zmin) { + surf->i_periodic = i_zmax; + } else if (i_surf == i_zmax) { + surf->i_periodic = i_zmin; + } else { + fatal_error("Periodic boundary condition applied to interior " + "surface"); + } + } else { + // Convert the surface id to an index. + surf->i_periodic = surface_dict[surf->i_periodic]; + } + } else { + // This is a SurfacePlane. We won't try to find it's partner if the + // user didn't specify one. + if (surf->i_periodic == C_NONE) { + std::string err_msg{"No matching periodic surface specified for " + "periodic boundary condition on surface "}; + err_msg += std::to_string(surf->id); + fatal_error(err_msg); + } else { + // Convert the surface id to an index. + surf->i_periodic = surface_dict[surf->i_periodic]; + } + } + + // Make sure the opposite surface is also periodic. + if (surfaces_c[surf->i_periodic]->bc != BC_PERIODIC) { + std::string err_msg{"Could not find matching surface for periodic " + "boundary condition on surface "}; + err_msg += std::to_string(surf->id); + fatal_error(err_msg); + } + } + } } //============================================================================== @@ -1454,17 +1626,24 @@ surface_to_hdf5(int surf_ind, hid_t group) } extern "C" bool -surface_periodic(int surf_ind1, int surf_ind2, double xyz[3], double uvw[3]) +surface_periodic(int surf_ind1, double xyz[3], double uvw[3]) { - // Hopefully this function has only been called on a pair of surfaces that + // Hopefully this function has only been called for a pair of surfaces that // support periodic BCs (checking should have been done when reading the // geometry XML). Downcast the surfaces to the PeriodicSurface type so we // can call the periodic_translate method. Surface *surf1_gen = surfaces_c[surf_ind1]; PeriodicSurface *surf1 = dynamic_cast(surf1_gen); - Surface *surf2_gen = surfaces_c[surf_ind2]; + Surface *surf2_gen = surfaces_c[surf1->i_periodic]; PeriodicSurface *surf2 = dynamic_cast(surf2_gen); // Call the type-bound methods. return surf2->periodic_translate(surf1, xyz, uvw); } + +extern "C" int +surface_i_periodic(int surf_ind) +{ + PeriodicSurface *surf = dynamic_cast(surfaces_c[surf_ind]); + return surf->i_periodic; +} diff --git a/src/surface_header.F90 b/src/surface_header.F90 index 98e2423b6..ffe0a6db4 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -12,68 +12,17 @@ module surface_header ! construct closed volumes (cells) !=============================================================================== - type, abstract :: Surface + type :: Surface integer :: id ! Unique ID integer, allocatable :: & neighbor_pos(:), & ! List of cells on positive side neighbor_neg(:) ! List of cells on negative side integer :: bc ! Boundary condition - integer :: i_periodic = NONE ! Index of corresponding periodic surface - character(len=104) :: name = "" ! User-defined name end type Surface -!=============================================================================== -! SURFACECONTAINER allows us to store an array of different types of surfaces -!=============================================================================== - - type :: SurfaceContainer - class(Surface), allocatable :: obj - end type SurfaceContainer - -!=============================================================================== -! All the derived types below are extensions of the abstract Surface type. They -! inherent the reflect() type-bound procedure and must implement normal() -!=============================================================================== - - type, extends(Surface) :: SurfaceXPlane - end type SurfaceXPlane - - type, extends(Surface) :: SurfaceYPlane - end type SurfaceYPlane - - type, extends(Surface) :: SurfaceZPlane - end type SurfaceZPlane - - type, extends(Surface) :: SurfacePlane - end type SurfacePlane - - type, extends(Surface) :: SurfaceXCylinder - end type SurfaceXCylinder - - type, extends(Surface) :: SurfaceYCylinder - end type SurfaceYCylinder - - type, extends(Surface) :: SurfaceZCylinder - end type SurfaceZCylinder - - type, extends(Surface) :: SurfaceSphere - end type SurfaceSphere - - type, extends(Surface) :: SurfaceXCone - end type SurfaceXCone - - type, extends(Surface) :: SurfaceYCone - end type SurfaceYCone - - type, extends(Surface) :: SurfaceZCone - end type SurfaceZCone - - type, extends(Surface) :: SurfaceQuadric - end type SurfaceQuadric - integer(C_INT32_T), bind(C) :: n_surfaces ! # of surfaces - type(SurfaceContainer), allocatable, target :: surfaces(:) + type(Surface), allocatable, target :: surfaces(:) ! Dictionary that maps user IDs to indices in 'surfaces' type(DictIntInt) :: surface_dict diff --git a/src/tallies/tally_filter_surface.F90 b/src/tallies/tally_filter_surface.F90 index daecd8891..6a2afa3ec 100644 --- a/src/tallies/tally_filter_surface.F90 +++ b/src/tallies/tally_filter_surface.F90 @@ -105,7 +105,7 @@ contains integer, intent(in) :: bin character(MAX_LINE_LEN) :: label - label = "Surface " // to_str(surfaces(this % surfaces(bin)) % obj % id) + label = "Surface " // to_str(surfaces(this % surfaces(bin)) % id) end function text_label_surface end module tally_filter_surface diff --git a/src/tracking.F90 b/src/tracking.F90 index d616212ec..98bca2a27 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -6,7 +6,7 @@ module tracking use geometry_header, only: cells use geometry, only: find_cell, distance_to_boundary, cross_lattice, & check_cell_overlap, surface_reflect_c, & - surface_periodic_c + surface_periodic_c, surface_i_periodic_c use message_passing use mgxs_header use nuclide_header @@ -298,7 +298,7 @@ contains class(Surface), pointer :: surf i_surface = abs(p % surface) - surf => surfaces(i_surface)%obj + surf => surfaces(i_surface) if (verbosity >= 10 .or. trace) then call write_message(" Crossing surface " // trim(to_str(surf % id))) end if @@ -412,14 +412,14 @@ contains p % coord(1) % xyz = xyz end if - rotational = surface_periodic_c(i_surface-1, surf % i_periodic-1, & - p % coord(1) % xyz, p % coord(1) % uvw) + rotational = surface_periodic_c(i_surface-1, p % coord(1) % xyz, & + p % coord(1) % uvw) ! Reassign particle's surface if (rotational) then - p % surface = surf % i_periodic + p % surface = surface_i_periodic_c(i_surface-1) + 1 else - p % surface = sign(surf % i_periodic, p % surface) + p % surface = sign(surface_i_periodic_c(i_surface-1) + 1, p % surface) end if ! Figure out what cell particle is in now From 983cbd48c65df2eb5578dd52d423caed915c26c2 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 23 Jan 2018 22:26:48 -0500 Subject: [PATCH 131/282] Clean up C++ surface file structure --- CMakeLists.txt | 10 +- src/error.h | 1 + src/{surface_header.C => surface.cpp} | 473 ++------------------------ src/surface.h | 431 +++++++++++++++++++++++ src/xml_interface.h | 52 +++ 5 files changed, 517 insertions(+), 450 deletions(-) rename src/{surface_header.C => surface.cpp} (72%) create mode 100644 src/surface.h create mode 100644 src/xml_interface.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e59713755..625f00136 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -437,11 +437,13 @@ set(LIBOPENMC_FORTRAN_SRC set(LIBOPENMC_CXX_SRC src/error.h src/hdf5_interface.h - src/random_lcg.h src/random_lcg.cpp - src/surface_header.C - src/pugixml/pugixml.hpp - src/pugixml/pugixml.cpp) + src/random_lcg.h + 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) diff --git a/src/error.h b/src/error.h index 77f0252e1..0fd78a6c5 100644 --- a/src/error.h +++ b/src/error.h @@ -1,6 +1,7 @@ #ifndef ERROR_H #define ERROR_H +#include #include diff --git a/src/surface_header.C b/src/surface.cpp similarity index 72% rename from src/surface_header.C rename to src/surface.cpp index afea2b43d..ed111b037 100644 --- a/src/surface_header.C +++ b/src/surface.cpp @@ -1,79 +1,11 @@ -#include // for std::transform #include -#include // For strcmp -#include // For numeric_limits -#include #include // For fabs -#include "pugixml/pugixml.hpp" -#include "hdf5.h" - #include "error.h" #include "hdf5_interface.h" +#include "xml_interface.h" -// DEBUGGING -#include -#include - -bool -check_for_node(const pugi::xml_node &node, const char *name) -{ - if (node.attribute(name)) { - return true; - } else if (node.child(name)) { - return true; - } else { - return false; - } -} - -std::string -get_node_value(const pugi::xml_node &node, const char *name) -{ - const pugi::char_t *value_char; - if (node.attribute(name)) { - value_char = node.attribute(name).value(); - } else if (node.child(name)) { - value_char = node.child_value(name); - } else { - std::string err_msg("Node \""); - err_msg += name; - err_msg += "\" is not a memeber of the \""; - err_msg += node.name(); - err_msg += "\" XML node"; - fatal_error(err_msg); - } - - std::string value(value_char); - std::transform(value.begin(), value.end(), value.begin(), ::tolower); - //TODO: trim whitespace - return value; -} - -//============================================================================== -// Constants -//============================================================================== - -//extern "C" const double FP_COINCIDENT{1e-12}; -//extern "C" const double INFTY{std::numeric_limits::max()}; -extern "C" double FP_COINCIDENT; -//extern "C" double INFTY; -const double INFTY{std::numeric_limits::max()}; -const int C_NONE{-1}; - -extern "C" const int BC_TRANSMIT{0}; -extern "C" const int BC_VACUUM{1}; -extern "C" const int BC_REFLECT{2}; -extern "C" const int BC_PERIODIC{3}; - -//============================================================================== -// Global array of surfaces -//============================================================================== - -class Surface; -Surface **surfaces_c; - -std::map surface_dict; +#include "surface.h" //============================================================================== // Helper functions for reading the "coeffs" node of an XML surface element @@ -84,18 +16,13 @@ int word_count(const std::string &text) bool in_word = false; int count{0}; for (auto c = text.begin(); c != text.end(); c++) { - switch(*c) { - case ' ' : - case '\t' : - case '\r' : - case '\n' : - if (in_word) { - in_word = false; - count++; - } - break; - default : - in_word = true; + if (std::isspace(*c)) { + if (in_word) { + in_word = false; + count++; + } + } else { + in_word = true; } } if (in_word) count++; @@ -188,76 +115,9 @@ void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1, double &c2, } //============================================================================== +// Surface implementation //============================================================================== -struct BoundingBox -{ - double xmin; - double xmax; - double ymin; - double ymax; - double zmin; - double zmax; -}; - -//============================================================================== -//! A geometry primitive used to define regions of 3D space. -//============================================================================== - -class Surface -{ -public: - int id; //!< Unique ID - //int neighbor_pos[], //!< List of cells on positive side - // neighbor_neg[]; //!< List of cells on negative side - int bc; //!< Boundary condition - std::string name{""}; //!< User-defined name - - Surface(pugi::xml_node surf_node); - - //! Determine which side of a surface a point lies on. - //! @param xyz[3] The 3D Cartesian coordinate of a point. - //! @param uvw[3] A direction used to "break ties" and pick a sense when the - //! point is very close to the surface. - //! @return true if the point is on the "positive" side of the surface and - //! false otherwise. - bool sense(const double xyz[3], const double uvw[3]) const; - - //! Determine the direction of a ray reflected from the surface. - //! @param xyz[3] The point at which the ray is incident. - //! @param uvw[3] A direction. This is both an input and an output parameter. - //! It specifies the icident direction on input and the reflected direction - //! on output. - void reflect(const double xyz[3], double uvw[3]) const; - - //! Evaluate the equation describing the surface. - //! - //! Surfaces can be described by some function f(x, y, z) = 0. This member - //! function evaluates that mathematical function. - //! @param xyz[3] A 3D Cartesian coordinate. - virtual double evaluate(const double xyz[3]) const = 0; - - //! Compute the distance between a point and the surface along a ray. - //! @param xyz[3] A 3D Cartesian coordinate. - //! @param uvw[3] The direction of the ray. - //! @param coincident A hint to the code that the given point should lie - //! exactly on the surface. - virtual double distance(const double xyz[3], const double uvw[3], - bool coincident) const = 0; - - //! Compute the local outward normal direction of the surface. - //! @param xyz[3] A 3D Cartesian coordinate. - //! @param uvw[3] This output argument provides the normal. - virtual void normal(const double xyz[3], double uvw[3]) const = 0; - - //! Write all information needed to reconstruct the surface to an HDF5 group. - //! @param group_id An HDF5 group id. - void to_hdf5(hid_t group_id) const; - -protected: - virtual void to_hdf5_inner(hid_t group_id) const = 0; -}; - Surface::Surface(pugi::xml_node surf_node) { if (check_for_node(surf_node, "id")) { @@ -367,34 +227,9 @@ Surface::to_hdf5(hid_t group_id) const } //============================================================================== -//! A `Surface` that supports periodic boundary conditions. -//! -//! Translational periodicity is supported for the `XPlane`, `YPlane`, `ZPlane`, -//! and `Plane` types. Rotational periodicity is supported for -//! `XPlane`-`YPlane` pairs. +// PeriodicSurface implementation //============================================================================== -class PeriodicSurface : public Surface -{ -public: - int i_periodic{C_NONE}; //!< Index of corresponding periodic surface - - PeriodicSurface(pugi::xml_node surf_node); - - //! Translate a particle onto this surface from a periodic partner surface. - //! @param other A pointer to the partner surface in this periodic BC. - //! @param xyz[3] A point on the partner surface that will be translated onto - //! this surface. - //! @param uvw[3] A direction that will be rotated for systems with rotational - //! periodicity. - //! @return true if this surface and its partner make a rotationally-periodic - //! boundary condition. - virtual bool periodic_translate(PeriodicSurface *other, double xyz[3], - double uvw[3]) const = 0; - - virtual struct BoundingBox bounding_box() const = 0; -}; - PeriodicSurface::PeriodicSurface(pugi::xml_node surf_node) : Surface(surf_node) { @@ -437,27 +272,9 @@ axis_aligned_plane_normal(const double xyz[3], double uvw[3]) } //============================================================================== -// SurfaceXPlane -//! A plane perpendicular to the x-axis. -// -//! The plane is described by the equation \f$x - x_0 = 0\f$ +// SurfaceXPlane implementation //============================================================================== -class SurfaceXPlane : public PeriodicSurface -{ - double x0; -public: - SurfaceXPlane(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], bool coincident) - const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; - bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) - const; - struct BoundingBox bounding_box() const; -}; - SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) : PeriodicSurface(surf_node) { @@ -521,27 +338,9 @@ SurfaceXPlane::bounding_box() const } //============================================================================== -// SurfaceYPlane -//! A plane perpendicular to the y-axis. -// -//! The plane is described by the equation \f$y - y_0 = 0\f$ +// SurfaceYPlane implementation //============================================================================== -class SurfaceYPlane : public PeriodicSurface -{ - double y0; -public: - SurfaceYPlane(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - bool coincident) const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; - bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) - const; - struct BoundingBox bounding_box() const; -}; - SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) : PeriodicSurface(surf_node) { @@ -605,27 +404,9 @@ SurfaceYPlane::bounding_box() const } //============================================================================== -// SurfaceZPlane -//! A plane perpendicular to the z-axis. -// -//! The plane is described by the equation \f$z - z_0 = 0\f$ +// SurfaceZPlane implementation //============================================================================== -class SurfaceZPlane : public PeriodicSurface -{ - double z0; -public: - SurfaceZPlane(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - bool coincident) const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; - bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) - const; - struct BoundingBox bounding_box() const; -}; - SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) : PeriodicSurface(surf_node) { @@ -671,27 +452,9 @@ SurfaceZPlane::bounding_box() const } //============================================================================== -// SurfacePlane -//! A general plane. -// -//! The plane is described by the equation \f$A x + B y + C z - D = 0\f$ +// SurfacePlane implementation //============================================================================== -class SurfacePlane : public PeriodicSurface -{ - double A, B, C, D; -public: - SurfacePlane(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], bool coincident) - const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; - bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) - const; - struct BoundingBox bounding_box() const; -}; - SurfacePlane::SurfacePlane(pugi::xml_node surf_node) : PeriodicSurface(surf_node) { @@ -832,25 +595,9 @@ axis_aligned_cylinder_normal(const double xyz[3], double uvw[3], double offset1, } //============================================================================== -// SurfaceXCylinder -//! A cylinder aligned along the x-axis. -// -//! The cylinder is described by the equation -//! \f$(y - y_0)^2 + (z - z_0)^2 - R^2 = 0\f$ +// SurfaceXCylinder implementation //============================================================================== -class SurfaceXCylinder : public Surface -{ - double y0, z0, r; -public: - SurfaceXCylinder(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - bool coincident) const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; -}; - SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node) : Surface(surf_node) { @@ -883,25 +630,9 @@ void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const } //============================================================================== -// SurfaceYCylinder -//! A cylinder aligned along the y-axis. -// -//! The cylinder is described by the equation -//! \f$(x - x_0)^2 + (z - z_0)^2 - R^2 = 0\f$ +// SurfaceYCylinder implementation //============================================================================== -class SurfaceYCylinder : public Surface -{ - double x0, z0, r; -public: - SurfaceYCylinder(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - bool coincident) const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; -}; - SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node) : Surface(surf_node) { @@ -933,25 +664,9 @@ void SurfaceYCylinder::to_hdf5_inner(hid_t group_id) const } //============================================================================== -// SurfaceZCylinder -//! A cylinder aligned along the z-axis. -// -//! The cylinder is described by the equation -//! \f$(x - x_0)^2 + (y - y_0)^2 - R^2 = 0\f$ +// SurfaceZCylinder implementation //============================================================================== -class SurfaceZCylinder : public Surface -{ - double x0, y0, r; -public: - SurfaceZCylinder(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - bool coincident) const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; -}; - SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node) : Surface(surf_node) { @@ -983,25 +698,9 @@ void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const } //============================================================================== -// SurfaceSphere -//! A sphere. -// -//! The cylinder is described by the equation -//! \f$(x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 - R^2 = 0\f$ +// SurfaceSphere implementation //============================================================================== -class SurfaceSphere : public Surface -{ - double x0, y0, z0, r; -public: - SurfaceSphere(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - bool coincident) const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; -}; - SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) : Surface(surf_node) { @@ -1153,25 +852,9 @@ axis_aligned_cone_normal(const double xyz[3], double uvw[3], double offset1, } //============================================================================== -// SurfaceXCone -//! A cone aligned along the x-axis. -// -//! The cylinder is described by the equation -//! \f$(y - y_0)^2 + (z - z_0)^2 - R^2 (x - x_0)^2 = 0\f$ +// SurfaceXCone implementation //============================================================================== -class SurfaceXCone : public Surface -{ - double x0, y0, z0, r_sq; -public: - SurfaceXCone(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - bool coincident) const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; -}; - SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) : Surface(surf_node) { @@ -1203,25 +886,9 @@ void SurfaceXCone::to_hdf5_inner(hid_t group_id) const } //============================================================================== -// SurfaceYCone -//! A cone aligned along the y-axis. -// -//! The cylinder is described by the equation -//! \f$(x - x_0)^2 + (z - z_0)^2 - R^2 (y - y_0)^2 = 0\f$ +// SurfaceYCone implementation //============================================================================== -class SurfaceYCone : public Surface -{ - double x0, y0, z0, r_sq; -public: - SurfaceYCone(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - bool coincident) const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; -}; - SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) : Surface(surf_node) { @@ -1253,25 +920,9 @@ void SurfaceYCone::to_hdf5_inner(hid_t group_id) const } //============================================================================== -// SurfaceZCone -//! A cone aligned along the z-axis. -// -//! The cylinder is described by the equation -//! \f$(x - x_0)^2 + (y - y_0)^2 - R^2 (z - z_0)^2 = 0\f$ +// SurfaceZCone implementation //============================================================================== -class SurfaceZCone : public Surface -{ - double x0, y0, z0, r_sq; -public: - SurfaceZCone(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - bool coincident) const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; -}; - SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) : Surface(surf_node) { @@ -1303,25 +954,9 @@ void SurfaceZCone::to_hdf5_inner(hid_t group_id) const } //============================================================================== -// SurfaceQuadric -//! A general surface described by a quadratic equation. -// -//! \f$A x^2 + B y^2 + C z^2 + D x y + E y z + F x z + G x + H y + J z + K = 0\f$ +// SurfaceQuadric implementation //============================================================================== -class SurfaceQuadric : public Surface -{ - // Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0 - double A, B, C, D, E, F, G, H, J, K; -public: - SurfaceQuadric(pugi::xml_node surf_node); - double evaluate(const double xyz[3]) const; - double distance(const double xyz[3], const double uvw[3], - bool coincident) const; - void normal(const double xyz[3], double uvw[3]) const; - void to_hdf5_inner(hid_t group_id) const; -}; - SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) : Surface(surf_node) { @@ -1472,9 +1107,10 @@ read_surfaces(pugi::xml_node *node) surfaces_c[i_surf] = new SurfaceQuadric(surf_node); } else { - std::cout << "Call error here!" << std::endl; - std::cout << surf_type << std::endl; - //TODO: call fatal_error + std::string err_msg{"Invalid surface type, \""}; + err_msg += surf_type; + err_msg += "\""; + fatal_error(err_msg); } } } @@ -1592,58 +1228,3 @@ read_surfaces(pugi::xml_node *node) } } } - -//============================================================================== - -extern "C" bool -surface_sense(int surf_ind, double xyz[3], double uvw[3]) -{ - return surfaces_c[surf_ind]->sense(xyz, uvw); -} - -extern "C" void -surface_reflect(int surf_ind, double xyz[3], double uvw[3]) -{ - surfaces_c[surf_ind]->reflect(xyz, uvw); -} - -extern "C" double -surface_distance(int surf_ind, double xyz[3], double uvw[3], bool coincident) -{ - return surfaces_c[surf_ind]->distance(xyz, uvw, coincident); -} - -extern "C" void -surface_normal(int surf_ind, double xyz[3], double uvw[3]) -{ - return surfaces_c[surf_ind]->normal(xyz, uvw); -} - -extern "C" void -surface_to_hdf5(int surf_ind, hid_t group) -{ - surfaces_c[surf_ind]->to_hdf5(group); -} - -extern "C" bool -surface_periodic(int surf_ind1, double xyz[3], double uvw[3]) -{ - // Hopefully this function has only been called for a pair of surfaces that - // support periodic BCs (checking should have been done when reading the - // geometry XML). Downcast the surfaces to the PeriodicSurface type so we - // can call the periodic_translate method. - Surface *surf1_gen = surfaces_c[surf_ind1]; - PeriodicSurface *surf1 = dynamic_cast(surf1_gen); - Surface *surf2_gen = surfaces_c[surf1->i_periodic]; - PeriodicSurface *surf2 = dynamic_cast(surf2_gen); - - // Call the type-bound methods. - return surf2->periodic_translate(surf1, xyz, uvw); -} - -extern "C" int -surface_i_periodic(int surf_ind) -{ - PeriodicSurface *surf = dynamic_cast(surfaces_c[surf_ind]); - return surf->i_periodic; -} diff --git a/src/surface.h b/src/surface.h new file mode 100644 index 000000000..22eb798dc --- /dev/null +++ b/src/surface.h @@ -0,0 +1,431 @@ +#ifndef SURFACE_H +#define SURFACE_H + +#include +#include // For numeric_limits + +#include "hdf5.h" +#include "pugixml/pugixml.hpp" + +//============================================================================== +// Module constants +//============================================================================== + +extern "C" const int BC_TRANSMIT{0}; +extern "C" const int BC_VACUUM{1}; +extern "C" const int BC_REFLECT{2}; +extern "C" const int BC_PERIODIC{3}; + +//============================================================================== +// Constants that should eventually be moved out of this file +//============================================================================== + +extern "C" double FP_COINCIDENT; +const double INFTY{std::numeric_limits::max()}; +const int C_NONE{-1}; + +//============================================================================== +// Global variables +//============================================================================== + +class Surface; +Surface **surfaces_c; + +std::map surface_dict; + +//============================================================================== +//! Coordinates for an axis-aligned cube that bounds a geometric object. +//============================================================================== + +struct BoundingBox +{ + double xmin; + double xmax; + double ymin; + double ymax; + double zmin; + double zmax; +}; + +//============================================================================== +//! A geometry primitive used to define regions of 3D space. +//============================================================================== + +class Surface +{ +public: + int id; //!< Unique ID + //int neighbor_pos[], //!< List of cells on positive side + // neighbor_neg[]; //!< List of cells on negative side + int bc; //!< Boundary condition + std::string name{""}; //!< User-defined name + + Surface(pugi::xml_node surf_node); + + //! Determine which side of a surface a point lies on. + //! @param xyz[3] The 3D Cartesian coordinate of a point. + //! @param uvw[3] A direction used to "break ties" and pick a sense when the + //! point is very close to the surface. + //! @return true if the point is on the "positive" side of the surface and + //! false otherwise. + bool sense(const double xyz[3], const double uvw[3]) const; + + //! Determine the direction of a ray reflected from the surface. + //! @param xyz[3] The point at which the ray is incident. + //! @param uvw[3] A direction. This is both an input and an output parameter. + //! It specifies the icident direction on input and the reflected direction + //! on output. + void reflect(const double xyz[3], double uvw[3]) const; + + //! Evaluate the equation describing the surface. + //! + //! Surfaces can be described by some function f(x, y, z) = 0. This member + //! function evaluates that mathematical function. + //! @param xyz[3] A 3D Cartesian coordinate. + virtual double evaluate(const double xyz[3]) const = 0; + + //! Compute the distance between a point and the surface along a ray. + //! @param xyz[3] A 3D Cartesian coordinate. + //! @param uvw[3] The direction of the ray. + //! @param coincident A hint to the code that the given point should lie + //! exactly on the surface. + virtual double distance(const double xyz[3], const double uvw[3], + bool coincident) const = 0; + + //! Compute the local outward normal direction of the surface. + //! @param xyz[3] A 3D Cartesian coordinate. + //! @param uvw[3] This output argument provides the normal. + virtual void normal(const double xyz[3], double uvw[3]) const = 0; + + //! Write all information needed to reconstruct the surface to an HDF5 group. + //! @param group_id An HDF5 group id. + void to_hdf5(hid_t group_id) const; + +protected: + virtual void to_hdf5_inner(hid_t group_id) const = 0; +}; + +//============================================================================== +//! A `Surface` that supports periodic boundary conditions. +//! +//! Translational periodicity is supported for the `XPlane`, `YPlane`, `ZPlane`, +//! and `Plane` types. Rotational periodicity is supported for +//! `XPlane`-`YPlane` pairs. +//============================================================================== + +class PeriodicSurface : public Surface +{ +public: + int i_periodic{C_NONE}; //!< Index of corresponding periodic surface + + PeriodicSurface(pugi::xml_node surf_node); + + //! Translate a particle onto this surface from a periodic partner surface. + //! @param other A pointer to the partner surface in this periodic BC. + //! @param xyz[3] A point on the partner surface that will be translated onto + //! this surface. + //! @param uvw[3] A direction that will be rotated for systems with rotational + //! periodicity. + //! @return true if this surface and its partner make a rotationally-periodic + //! boundary condition. + virtual bool periodic_translate(PeriodicSurface *other, double xyz[3], + double uvw[3]) const = 0; + + //! Get the bounding box for this surface. + virtual struct BoundingBox bounding_box() const = 0; +}; + +//============================================================================== +//! A plane perpendicular to the x-axis. +// +//! The plane is described by the equation \f$x - x_0 = 0\f$ +//============================================================================== + +class SurfaceXPlane : public PeriodicSurface +{ + double x0; +public: + SurfaceXPlane(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], bool coincident) + const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; + bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) + const; + struct BoundingBox bounding_box() const; +}; + +//============================================================================== +//! A plane perpendicular to the y-axis. +// +//! The plane is described by the equation \f$y - y_0 = 0\f$ +//============================================================================== + +class SurfaceYPlane : public PeriodicSurface +{ + double y0; +public: + SurfaceYPlane(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; + bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) + const; + struct BoundingBox bounding_box() const; +}; + +//============================================================================== +//! A plane perpendicular to the z-axis. +// +//! The plane is described by the equation \f$z - z_0 = 0\f$ +//============================================================================== + +class SurfaceZPlane : public PeriodicSurface +{ + double z0; +public: + SurfaceZPlane(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; + bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) + const; + struct BoundingBox bounding_box() const; +}; + +//============================================================================== +//! A general plane. +// +//! The plane is described by the equation \f$A x + B y + C z - D = 0\f$ +//============================================================================== + +class SurfacePlane : public PeriodicSurface +{ + double A, B, C, D; +public: + SurfacePlane(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], bool coincident) + const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; + bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) + const; + struct BoundingBox bounding_box() const; +}; + +//============================================================================== +//! A cylinder aligned along the x-axis. +// +//! The cylinder is described by the equation +//! \f$(y - y_0)^2 + (z - z_0)^2 - R^2 = 0\f$ +//============================================================================== + +class SurfaceXCylinder : public Surface +{ + double y0, z0, r; +public: + SurfaceXCylinder(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; +}; + +//============================================================================== +//! A cylinder aligned along the y-axis. +// +//! The cylinder is described by the equation +//! \f$(x - x_0)^2 + (z - z_0)^2 - R^2 = 0\f$ +//============================================================================== + +class SurfaceYCylinder : public Surface +{ + double x0, z0, r; +public: + SurfaceYCylinder(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; +}; + +//============================================================================== +//! A cylinder aligned along the z-axis. +// +//! The cylinder is described by the equation +//! \f$(x - x_0)^2 + (y - y_0)^2 - R^2 = 0\f$ +//============================================================================== + +class SurfaceZCylinder : public Surface +{ + double x0, y0, r; +public: + SurfaceZCylinder(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; +}; + +//============================================================================== +//! A sphere. +// +//! The cylinder is described by the equation +//! \f$(x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 - R^2 = 0\f$ +//============================================================================== + +class SurfaceSphere : public Surface +{ + double x0, y0, z0, r; +public: + SurfaceSphere(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; +}; + +//============================================================================== +//! A cone aligned along the x-axis. +// +//! The cylinder is described by the equation +//! \f$(y - y_0)^2 + (z - z_0)^2 - R^2 (x - x_0)^2 = 0\f$ +//============================================================================== + +class SurfaceXCone : public Surface +{ + double x0, y0, z0, r_sq; +public: + SurfaceXCone(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; +}; + +//============================================================================== +//! A cone aligned along the y-axis. +// +//! The cylinder is described by the equation +//! \f$(x - x_0)^2 + (z - z_0)^2 - R^2 (y - y_0)^2 = 0\f$ +//============================================================================== + +class SurfaceYCone : public Surface +{ + double x0, y0, z0, r_sq; +public: + SurfaceYCone(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; +}; + +//============================================================================== +//! A cone aligned along the z-axis. +// +//! The cylinder is described by the equation +//! \f$(x - x_0)^2 + (y - y_0)^2 - R^2 (z - z_0)^2 = 0\f$ +//============================================================================== + +class SurfaceZCone : public Surface +{ + double x0, y0, z0, r_sq; +public: + SurfaceZCone(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; +}; + +//============================================================================== +//! A general surface described by a quadratic equation. +// +//! \f$A x^2 + B y^2 + C z^2 + D x y + E y z + F x z + G x + H y + J z + K = 0\f$ +//============================================================================== + +class SurfaceQuadric : public Surface +{ + // Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0 + double A, B, C, D, E, F, G, H, J, K; +public: + SurfaceQuadric(pugi::xml_node surf_node); + double evaluate(const double xyz[3]) const; + double distance(const double xyz[3], const double uvw[3], + bool coincident) const; + void normal(const double xyz[3], double uvw[3]) const; + void to_hdf5_inner(hid_t group_id) const; +}; + +//============================================================================== +// Fortran compatibility functions +//============================================================================== + +extern "C" bool +surface_sense(int surf_ind, double xyz[3], double uvw[3]) +{ + return surfaces_c[surf_ind]->sense(xyz, uvw); +} + +extern "C" void +surface_reflect(int surf_ind, double xyz[3], double uvw[3]) +{ + surfaces_c[surf_ind]->reflect(xyz, uvw); +} + +extern "C" double +surface_distance(int surf_ind, double xyz[3], double uvw[3], bool coincident) +{ + return surfaces_c[surf_ind]->distance(xyz, uvw, coincident); +} + +extern "C" void +surface_normal(int surf_ind, double xyz[3], double uvw[3]) +{ + return surfaces_c[surf_ind]->normal(xyz, uvw); +} + +extern "C" void +surface_to_hdf5(int surf_ind, hid_t group) +{ + surfaces_c[surf_ind]->to_hdf5(group); +} + +extern "C" bool +surface_periodic(int surf_ind1, double xyz[3], double uvw[3]) +{ + // Hopefully this function has only been called for a pair of surfaces that + // support periodic BCs (checking should have been done when reading the + // geometry XML). Downcast the surfaces to the PeriodicSurface type so we + // can call the periodic_translate method. + Surface *surf1_gen = surfaces_c[surf_ind1]; + PeriodicSurface *surf1 = dynamic_cast(surf1_gen); + Surface *surf2_gen = surfaces_c[surf1->i_periodic]; + PeriodicSurface *surf2 = dynamic_cast(surf2_gen); + + // Call the type-bound methods. + return surf2->periodic_translate(surf1, xyz, uvw); +} + +extern "C" int +surface_i_periodic(int surf_ind) +{ + PeriodicSurface *surf = dynamic_cast(surfaces_c[surf_ind]); + return surf->i_periodic; +} + +#endif // SURFACE_H diff --git a/src/xml_interface.h b/src/xml_interface.h new file mode 100644 index 000000000..fbc82e056 --- /dev/null +++ b/src/xml_interface.h @@ -0,0 +1,52 @@ +#ifndef XML_INTERFACE_H +#define XML_INTERFACE_H + +#include // for std::transform +#include + +#include "pugixml/pugixml.hpp" + + +bool +check_for_node(const pugi::xml_node &node, const char *name) +{ + if (node.attribute(name)) { + return true; + } else if (node.child(name)) { + return true; + } else { + return false; + } +} + + +std::string +get_node_value(const pugi::xml_node &node, const char *name) +{ + // Search for either an attribute or child tag and get the data as a char*. + const pugi::char_t *value_char; + if (node.attribute(name)) { + value_char = node.attribute(name).value(); + } else if (node.child(name)) { + value_char = node.child_value(name); + } else { + std::string err_msg("Node \""); + err_msg += name; + err_msg += "\" is not a memeber of the \""; + err_msg += node.name(); + err_msg += "\" XML node"; + fatal_error(err_msg); + } + + // Convert to lowercase string. + std::string value(value_char); + std::transform(value.begin(), value.end(), value.begin(), ::tolower); + + // Remove whitespace. + value.erase(0, value.find_first_not_of(" \t\r\n")); + value.erase(value.find_last_not_of(" \t\r\n") + 1); + + return value; +} + +#endif // XML_INTERFACE_H From 75501a08e931ed64011205bbd612e708eb0bb670 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 24 Jan 2018 11:56:20 -0500 Subject: [PATCH 132/282] Add C++ Surface pointers to Fortran Surfaces --- src/geometry.F90 | 68 +--------- src/input_xml.F90 | 60 +-------- src/output.F90 | 2 +- src/summary.F90 | 15 +-- src/surface.cpp | 13 +- src/surface.h | 72 +++++------ src/surface_header.F90 | 180 ++++++++++++++++++++++++++- src/tallies/tally_filter_surface.F90 | 2 +- src/tracking.F90 | 43 ++++--- 9 files changed, 252 insertions(+), 203 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index d1519f8d2..0bacd7b5d 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -14,66 +14,6 @@ module geometry implicit none - interface - pure function surface_sense_c(surf_ind, xyz, uvw) & - bind(C, name='surface_sense') result(sense) - use ISO_C_BINDING - implicit none - integer(C_INT), intent(in), value :: surf_ind; - real(C_DOUBLE), intent(in) :: xyz(3); - real(C_DOUBLE), intent(in) :: uvw(3); - logical(C_BOOL) :: sense; - end function surface_sense_c - - pure subroutine surface_reflect_c(surf_ind, xyz, uvw) & - bind(C, name='surface_reflect') - use ISO_C_BINDING - implicit none - integer(C_INT), intent(in), value :: surf_ind; - real(C_DOUBLE), intent(in) :: xyz(3); - real(C_DOUBLE), intent(inout) :: uvw(3); - end subroutine surface_reflect_c - - pure function surface_distance_c(surf_ind, xyz, uvw, coincident) & - bind(C, name='surface_distance') result(d) - use ISO_C_BINDING - implicit none - integer(C_INT), intent(in), value :: surf_ind; - real(C_DOUBLE), intent(in) :: xyz(3); - real(C_DOUBLE), intent(in) :: uvw(3); - logical(C_BOOL), intent(in), value :: coincident; - real(C_DOUBLE) :: d; - end function surface_distance_c - - pure subroutine surface_normal_c(surf_ind, xyz, uvw) & - bind(C, name='surface_normal') - use ISO_C_BINDING - implicit none - integer(C_INT), intent(in), value :: surf_ind; - real(C_DOUBLE), intent(in) :: xyz(3); - real(C_DOUBLE), intent(out) :: uvw(3); - end subroutine surface_normal_c - - function surface_periodic_c(surf_ind1, xyz, uvw) & - bind(C, name="surface_periodic") result(rotational) - use ISO_C_BINDING - implicit none - integer(C_INT), intent(in), value :: surf_ind1; - real(C_DOUBLE), intent(inout) :: xyz(3); - real(C_DOUBLE), intent(inout) :: uvw(3); - logical(C_BOOL) :: rotational - end function surface_periodic_c - - function surface_i_periodic_c(surf_ind) bind(C, name="surface_i_periodic") & - result(i_periodic) - use ISO_C_BINDING - implicit none - integer(C_INT), intent(in), value :: surf_ind - integer(C_INT) :: i_periodic - end function - - end interface - contains !=============================================================================== @@ -125,7 +65,7 @@ contains in_cell = .false. exit else - actual_sense = surface_sense_c(abs(token)-1, & + actual_sense = surfaces(abs(token)) % sense( & p%coord(p%n_coord)%xyz, p%coord(p%n_coord)%uvw) if (actual_sense .neqv. (token > 0)) then in_cell = .false. @@ -174,7 +114,7 @@ contains elseif (-token == p%surface) then stack(i_stack) = .false. else - actual_sense = surface_sense_c(abs(token)-1, & + actual_sense = surfaces(abs(token)) % sense( & p%coord(p%n_coord)%xyz, p%coord(p%n_coord)%uvw) stack(i_stack) = (actual_sense .eqv. (token > 0)) end if @@ -579,7 +519,7 @@ contains if (index_surf >= OP_UNION) cycle ! Calculate distance to surface - d = surface_distance_c(index_surf-1, p % coord(j) % xyz, & + d = surfaces(index_surf) % distance(p % coord(j) % xyz, & p % coord(j) % uvw, logical(coincident, kind=C_BOOL)) ! Check if calculated distance is new minimum @@ -799,7 +739,7 @@ contains ! traveling into if the surface is crossed if (.not. c % simple) then xyz_cross(:) = p % coord(j) % xyz + d_surf*p % coord(j) % uvw - call surface_normal_c(abs(level_surf_cross)-1, xyz_cross, surf_uvw) + call surfaces(abs(level_surf_cross)) % normal(xyz_cross, surf_uvw) if (dot_product(p % coord(j) % uvw, surf_uvw) > ZERO) then surface_crossed = abs(level_surf_cross) else diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 9b6e1c6fc..55eeca9e1 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -924,19 +924,15 @@ contains logical :: file_exists logical :: boundary_exists character(MAX_LINE_LEN) :: filename - character(MAX_WORD_LEN) :: word character(MAX_WORD_LEN), allocatable :: sarray(:) character(:), allocatable :: region_spec type(Cell), pointer :: c - class(Surface), pointer :: s class(Lattice), pointer :: lat type(XMLDocument) :: doc type(XMLNode) :: root type(XMLNode) :: node_cell - type(XMLNode) :: node_surf type(XMLNode) :: node_lat type(XMLNode), allocatable :: node_cell_list(:) - type(XMLNode), allocatable :: node_surf_list(:) type(XMLNode), allocatable :: node_rlat_list(:) type(XMLNode), allocatable :: node_hlat_list(:) type(VectorInt) :: tokens @@ -968,66 +964,18 @@ contains ! applied to a surface boundary_exists = .false. - ! get pointer to list of xml - call get_node_list(root, "surface", node_surf_list) call read_surfaces(root % ptr) - ! Get number of tags - n_surfaces = size(node_surf_list) - - ! Check for no surfaces - if (n_surfaces == 0) then - call fatal_error("No surfaces found in geometry.xml!") - end if - - ! Allocate cells array + ! Allocate surfaces array allocate(surfaces(n_surfaces)) do i = 1, n_surfaces - ! Get pointer to i-th surface node - node_surf = node_surf_list(i) + surfaces(i) % ptr = surface_pointer_c(i - 1); - s => surfaces(i) + if (surfaces(i) % bc() /= BC_TRANSMIT) boundary_exists = .true. - ! Copy data into cells - if (check_for_node(node_surf, "id")) then - call get_node_value(node_surf, "id", s%id) - else - call fatal_error("Must specify id of surface in geometry XML file.") - end if - - ! Check to make sure 'id' hasn't been used - if (surface_dict % has(s%id)) then - call fatal_error("Two or more surfaces use the same unique ID: " & - // to_str(s%id)) - end if - - ! Check to make sure that the proper number of coefficients - ! have been specified for the given type of surface. Then copy - ! surface coordinates. - - ! Boundary conditions - word = '' - if (check_for_node(node_surf, "boundary")) & - call get_node_value(node_surf, "boundary", word) - select case (to_lower(word)) - case ('transmission', 'transmit', '') - s%bc = BC_TRANSMIT - case ('vacuum') - s%bc = BC_VACUUM - boundary_exists = .true. - case ('reflective', 'reflect', 'reflecting') - s%bc = BC_REFLECT - boundary_exists = .true. - case ('periodic') - s%bc = BC_PERIODIC - boundary_exists = .true. - case default - call fatal_error("Unknown boundary condition '" // trim(word) // & - &"' specified on surface " // trim(to_str(s%id))) - end select ! Add surface to dictionary - call surface_dict % set(s%id, i) + call surface_dict % set(surfaces(i) % id(), i) end do ! Check to make sure a boundary condition was applied to at least one diff --git a/src/output.F90 b/src/output.F90 index 4113eea47..316fe9594 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -259,7 +259,7 @@ contains ! Print surface if (p % surface /= NONE) then - write(ou,*) ' Surface = ' // to_str(sign(surfaces(i)%id, p % surface)) + write(ou,*) ' Surface = ' // to_str(sign(surfaces(i)%id(), p % surface)) end if ! Display weight, energy, grid index, and interpolation factor diff --git a/src/summary.F90 b/src/summary.F90 index 9b82a0396..3aeb42178 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -24,17 +24,6 @@ module summary public :: write_summary - interface - subroutine surface_to_hdf5_c(surf_ind, group) & - bind(C, name='surface_to_hdf5') - use ISO_C_BINDING - use hdf5 - implicit none - integer(C_INT), intent(in), value :: surf_ind - integer(HID_T), intent(in), value :: group - end subroutine surface_to_hdf5_c - end interface - contains !=============================================================================== @@ -232,7 +221,7 @@ contains region_spec = trim(region_spec) // " |" case default region_spec = trim(region_spec) // " " // to_str(& - sign(surfaces(abs(k))%id, k)) + sign(surfaces(abs(k))%id(), k)) end select end do call write_dataset(cell_group, "region", adjustl(region_spec)) @@ -250,7 +239,7 @@ contains ! Write information on each surface SURFACE_LOOP: do i = 1, n_surfaces - call surface_to_hdf5_c(i-1, surfaces_group) + call surfaces(i) % to_hdf5(surfaces_group) end do SURFACE_LOOP call close_group(surfaces_group) diff --git a/src/surface.cpp b/src/surface.cpp index ed111b037..369334d50 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -1053,11 +1053,13 @@ extern "C" void read_surfaces(pugi::xml_node *node) { // Count the number of surfaces. - int n_surfaces{0}; for (pugi::xml_node surf_node = node->child("surface"); surf_node; surf_node = surf_node.next_sibling("surface")) { n_surfaces++; } + if (n_surfaces == 0) { + fatal_error("No surfaces found in geometry.xml!"); + } // Allocate the array of Surface pointers. surfaces_c = new Surface* [n_surfaces]; @@ -1137,7 +1139,14 @@ read_surfaces(pugi::xml_node *node) // Downcast to the PeriodicSurface type. Surface *surf_base = surfaces_c[i_surf]; PeriodicSurface *surf = dynamic_cast(surf_base); - //TODO: check for null pointer + + // Make sure this surface inherits from PeriodicSurface. + if (surf == NULL) { + std::string err_msg{"Periodic boundary condition not supported for " + "surface "}; + err_msg += std::to_string(surf_base->id); + err_msg += ". Periodic BCs are only supported for planar surfaces."; + } // See if this surface makes part of the global bounding box. struct BoundingBox bb = surf->bounding_box(); diff --git a/src/surface.h b/src/surface.h index 22eb798dc..469d88486 100644 --- a/src/surface.h +++ b/src/surface.h @@ -28,6 +28,8 @@ const int C_NONE{-1}; // Global variables //============================================================================== +int n_surfaces{0}; + class Surface; Surface **surfaces_c; @@ -375,57 +377,43 @@ public: // Fortran compatibility functions //============================================================================== -extern "C" bool -surface_sense(int surf_ind, double xyz[3], double uvw[3]) -{ - return surfaces_c[surf_ind]->sense(xyz, uvw); -} +extern "C" Surface* surface_pointer(int surf_ind) {return surfaces_c[surf_ind];} -extern "C" void -surface_reflect(int surf_ind, double xyz[3], double uvw[3]) -{ - surfaces_c[surf_ind]->reflect(xyz, uvw); -} +extern "C" int surface_id(Surface *surf) {return surf->id;} + +extern "C" int surface_bc(Surface *surf) {return surf->bc;} + +extern "C" bool surface_sense(Surface *surf, double xyz[3], double uvw[3]) +{return surf->sense(xyz, uvw);} + +extern "C" void surface_reflect(Surface *surf, double xyz[3], double uvw[3]) +{surf->reflect(xyz, uvw);} extern "C" double -surface_distance(int surf_ind, double xyz[3], double uvw[3], bool coincident) -{ - return surfaces_c[surf_ind]->distance(xyz, uvw, coincident); -} +surface_distance(Surface *surf, double xyz[3], double uvw[3], bool coincident) +{return surf->distance(xyz, uvw, coincident);} -extern "C" void -surface_normal(int surf_ind, double xyz[3], double uvw[3]) -{ - return surfaces_c[surf_ind]->normal(xyz, uvw); -} +extern "C" void surface_normal(Surface *surf, double xyz[3], double uvw[3]) +{return surf->normal(xyz, uvw);} -extern "C" void -surface_to_hdf5(int surf_ind, hid_t group) -{ - surfaces_c[surf_ind]->to_hdf5(group); -} +extern "C" void surface_to_hdf5(Surface *surf, hid_t group) +{surf->to_hdf5(group);} + +extern "C" int surface_i_periodic(PeriodicSurface *surf) +{return surf->i_periodic;} extern "C" bool -surface_periodic(int surf_ind1, double xyz[3], double uvw[3]) -{ - // Hopefully this function has only been called for a pair of surfaces that - // support periodic BCs (checking should have been done when reading the - // geometry XML). Downcast the surfaces to the PeriodicSurface type so we - // can call the periodic_translate method. - Surface *surf1_gen = surfaces_c[surf_ind1]; - PeriodicSurface *surf1 = dynamic_cast(surf1_gen); - Surface *surf2_gen = surfaces_c[surf1->i_periodic]; - PeriodicSurface *surf2 = dynamic_cast(surf2_gen); +surface_periodic(PeriodicSurface *surf, PeriodicSurface *other, double xyz[3], + double uvw[3]) +{return surf->periodic_translate(other, xyz, uvw);} - // Call the type-bound methods. - return surf2->periodic_translate(surf1, xyz, uvw); -} - -extern "C" int -surface_i_periodic(int surf_ind) +extern "C" void free_memory_surfaces_c() { - PeriodicSurface *surf = dynamic_cast(surfaces_c[surf_ind]); - return surf->i_periodic; + for (int i = 0; i < n_surfaces; i++) {delete surfaces_c[i];} + delete surfaces_c; + surfaces_c = NULL; + n_surfaces = 0; + surface_dict.clear(); } #endif // SURFACE_H diff --git a/src/surface_header.F90 b/src/surface_header.F90 index ffe0a6db4..8985e8406 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -1,26 +1,132 @@ module surface_header use, intrinsic :: ISO_C_BINDING + use hdf5 - use constants, only: NONE use dict_header, only: DictIntInt implicit none + interface + pure function surface_pointer_c(surf_ind) & + bind(C, name='surface_pointer') result(ptr) + use ISO_C_BINDING + implicit none + integer(C_INT), intent(in), value :: surf_ind + type(C_PTR) :: ptr + end function surface_pointer_c + + pure function surface_id_c(surf_ptr) bind(C, name='surface_id') result(id) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: surf_ptr + integer(C_INT) :: id + end function surface_id_c + + pure function surface_bc_c(surf_ptr) bind(C, name='surface_bc') result(bc) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: surf_ptr + integer(C_INT) :: bc + end function surface_bc_c + + pure function surface_sense_c(surf_ptr, xyz, uvw) & + bind(C, name='surface_sense') result(sense) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: surf_ptr + real(C_DOUBLE), intent(in) :: xyz(3) + real(C_DOUBLE), intent(in) :: uvw(3) + logical(C_BOOL) :: sense + end function surface_sense_c + + pure subroutine surface_reflect_c(surf_ptr, xyz, uvw) & + bind(C, name='surface_reflect') + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: surf_ptr + real(C_DOUBLE), intent(in) :: xyz(3); + real(C_DOUBLE), intent(inout) :: uvw(3); + end subroutine surface_reflect_c + + pure function surface_distance_c(surf_ptr, xyz, uvw, coincident) & + bind(C, name='surface_distance') result(d) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: surf_ptr + real(C_DOUBLE), intent(in) :: xyz(3); + real(C_DOUBLE), intent(in) :: uvw(3); + logical(C_BOOL), intent(in), value :: coincident; + real(C_DOUBLE) :: d; + end function surface_distance_c + + pure subroutine surface_normal_c(surf_ptr, xyz, uvw) & + bind(C, name='surface_normal') + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: surf_ptr + real(C_DOUBLE), intent(in) :: xyz(3); + real(C_DOUBLE), intent(out) :: uvw(3); + end subroutine surface_normal_c + + subroutine surface_to_hdf5_c(surf_ptr, group) & + bind(C, name='surface_to_hdf5') + use ISO_C_BINDING + use hdf5 + implicit none + type(C_PTR), intent(in), value :: surf_ptr + integer(HID_T), intent(in), value :: group + end subroutine surface_to_hdf5_c + + pure function surface_i_periodic_c(surf_ptr) & + bind(C, name="surface_i_periodic") result(i_periodic) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: surf_ptr + integer(C_INT) :: i_periodic + end function surface_i_periodic_c + + function surface_periodic_c(surf_ptr, other_ptr, xyz, uvw) & + bind(C, name="surface_periodic") result(rotational) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: surf_ptr + type(C_PTR), intent(in), value :: other_ptr + real(C_DOUBLE), intent(inout) :: xyz(3); + real(C_DOUBLE), intent(inout) :: uvw(3); + logical(C_BOOL) :: rotational + end function surface_periodic_c + + subroutine free_memory_surfaces_c() bind(C) + end subroutine free_memory_surfaces_c + end interface + !=============================================================================== ! SURFACE type defines a first- or second-order surface that can be used to ! construct closed volumes (cells) !=============================================================================== type :: Surface - integer :: id ! Unique ID integer, allocatable :: & neighbor_pos(:), & ! List of cells on positive side neighbor_neg(:) ! List of cells on negative side - integer :: bc ! Boundary condition + type(C_PTR) :: ptr + + contains + + procedure :: id => surface_id + procedure :: bc => surface_bc + procedure :: sense => surface_sense + procedure :: reflect => surface_reflect + procedure :: distance => surface_distance + procedure :: normal => surface_normal + procedure :: to_hdf5 => surface_to_hdf5 + procedure :: i_periodic => surface_i_periodic + procedure :: periodic_translate => surface_periodic + end type Surface - integer(C_INT32_T), bind(C) :: n_surfaces ! # of surfaces + integer(C_INT), bind(C) :: n_surfaces ! # of surfaces type(Surface), allocatable, target :: surfaces(:) @@ -29,14 +135,78 @@ module surface_header contains + pure function surface_id(this) result(id) + class(Surface), intent(in) :: this + integer(C_INT) :: id + id = surface_id_c(this % ptr) + end function surface_id + + pure function surface_bc(this) result(bc) + class(Surface), intent(in) :: this + integer(C_INT) :: bc + bc = surface_bc_c(this % ptr) + end function surface_bc + + pure function surface_sense(this, xyz, uvw) result(sense) + class(Surface), intent(in) :: this + real(C_DOUBLE), intent(in) :: xyz(3) + real(C_DOUBLE), intent(in) :: uvw(3) + logical(C_BOOL) :: sense + sense = surface_sense_c(this % ptr, xyz, uvw) + end function surface_sense + + pure subroutine surface_reflect(this, xyz, uvw) + class(Surface), intent(in) :: this + real(C_DOUBLE), intent(in) :: xyz(3); + real(C_DOUBLE), intent(inout) :: uvw(3); + call surface_reflect_c(this % ptr, xyz, uvw) + end subroutine surface_reflect + + pure function surface_distance(this, xyz, uvw, coincident) result(d) + class(Surface), intent(in) :: this + real(C_DOUBLE), intent(in) :: xyz(3); + real(C_DOUBLE), intent(in) :: uvw(3); + logical(C_BOOL), intent(in) :: coincident; + real(C_DOUBLE) :: d; + d = surface_distance_c(this % ptr, xyz, uvw, coincident) + end function surface_distance + + pure subroutine surface_normal(this, xyz, uvw) + class(Surface), intent(in) :: this + real(C_DOUBLE), intent(in) :: xyz(3); + real(C_DOUBLE), intent(out) :: uvw(3); + call surface_normal_c(this % ptr, xyz, uvw) + end subroutine surface_normal + + subroutine surface_to_hdf5(this, group) + class(Surface), intent(in) :: this + integer(HID_T), intent(in) :: group + call surface_to_hdf5_c(this % ptr, group) + end subroutine surface_to_hdf5 + + pure function surface_i_periodic(this) result(i_periodic) + class(Surface), intent(in) :: this + integer(C_INT) :: i_periodic + i_periodic = surface_i_periodic_c(this % ptr) + end function surface_i_periodic + + function surface_periodic(this, other, xyz, uvw) result(rotational) + class(Surface), intent(in) :: this + class(Surface), intent(in) :: other + real(C_DOUBLE), intent(inout) :: xyz(3); + real(C_DOUBLE), intent(inout) :: uvw(3); + logical(C_BOOL) :: rotational + rotational = surface_periodic_c(this % ptr, other % ptr, xyz, uvw) + end function surface_periodic + !=============================================================================== ! FREE_MEMORY_SURFACES deallocates global arrays defined in this module !=============================================================================== subroutine free_memory_surfaces() - n_surfaces = 0 if (allocated(surfaces)) deallocate(surfaces) call surface_dict % clear() + call free_memory_surfaces_c() end subroutine free_memory_surfaces end module surface_header diff --git a/src/tallies/tally_filter_surface.F90 b/src/tallies/tally_filter_surface.F90 index 6a2afa3ec..df3bf3603 100644 --- a/src/tallies/tally_filter_surface.F90 +++ b/src/tallies/tally_filter_surface.F90 @@ -105,7 +105,7 @@ contains integer, intent(in) :: bin character(MAX_LINE_LEN) :: label - label = "Surface " // to_str(surfaces(this % surfaces(bin)) % id) + label = "Surface " // to_str(surfaces(this % surfaces(bin)) % id()) end function text_label_surface end module tally_filter_surface diff --git a/src/tracking.F90 b/src/tracking.F90 index 98bca2a27..75555c797 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -4,9 +4,8 @@ module tracking use cross_section, only: calculate_xs use error, only: fatal_error, warning, write_message use geometry_header, only: cells - use geometry, only: find_cell, distance_to_boundary, cross_lattice, & - check_cell_overlap, surface_reflect_c, & - surface_periodic_c, surface_i_periodic_c + use geometry, only: find_cell, distance_to_boundary, cross_lattice,& + check_cell_overlap use message_passing use mgxs_header use nuclide_header @@ -296,14 +295,15 @@ contains logical :: rotational ! if rotational periodic BC applied logical :: found ! particle found in universe? class(Surface), pointer :: surf + class(Surface), pointer :: surf2 ! periodic partner surface i_surface = abs(p % surface) surf => surfaces(i_surface) if (verbosity >= 10 .or. trace) then - call write_message(" Crossing surface " // trim(to_str(surf % id))) + call write_message(" Crossing surface " // trim(to_str(surf % id()))) end if - if (surf % bc == BC_VACUUM .and. (run_mode /= MODE_PLOTTING)) then + if (surf % bc() == BC_VACUUM .and. (run_mode /= MODE_PLOTTING)) then ! ======================================================================= ! PARTICLE LEAKS OUT OF PROBLEM @@ -328,11 +328,11 @@ contains ! Display message if (verbosity >= 10 .or. trace) then call write_message(" Leaked out of surface " & - &// trim(to_str(surf % id))) + &// trim(to_str(surf % id()))) end if return - elseif (surf % bc == BC_REFLECT .and. (run_mode /= MODE_PLOTTING)) then + elseif (surf % bc() == BC_REFLECT .and. (run_mode /= MODE_PLOTTING)) then ! ======================================================================= ! PARTICLE REFLECTS FROM SURFACE @@ -355,7 +355,7 @@ contains end if ! Reflect particle off surface - call surface_reflect_c(i_surface-1, p%coord(1)%xyz, p%coord(1)%uvw) + call surf % reflect(p%coord(1)%xyz, p%coord(1)%uvw) ! Make sure new particle direction is normalized u = p%coord(1)%uvw(1) @@ -376,7 +376,7 @@ contains call find_cell(p, found) if (.not. found) then call p % mark_as_lost("Couldn't find particle after reflecting& - & from surface " // trim(to_str(surf % id)) // ".") + & from surface " // trim(to_str(surf % id())) // ".") return end if @@ -386,10 +386,10 @@ contains ! Diagnostic message if (verbosity >= 10 .or. trace) then call write_message(" Reflected from surface " & - &// trim(to_str(surf%id))) + &// trim(to_str(surf%id()))) end if return - elseif (surf % bc == BC_PERIODIC .and. run_mode /= MODE_PLOTTING) then + elseif (surf % bc() == BC_PERIODIC .and. run_mode /= MODE_PLOTTING) then ! ======================================================================= ! PERIODIC BOUNDARY @@ -404,7 +404,6 @@ contains ! Score surface currents since reflection causes the direction of the ! particle to change -- artificially move the particle slightly back in ! case the surface crossing is coincident with a mesh boundary - if (active_current_tallies % size() > 0) then xyz = p % coord(1) % xyz p % coord(1) % xyz = p % coord(1) % xyz - TINY_BIT * p % coord(1) % uvw @@ -412,14 +411,19 @@ contains p % coord(1) % xyz = xyz end if - rotational = surface_periodic_c(i_surface-1, p % coord(1) % xyz, & - p % coord(1) % uvw) + ! Get a pointer to the partner periodic surface. Offset the index to + ! correct for C vs. Fortran indexing. + surf2 => surfaces(surf % i_periodic() + 1) + + ! Adjust the particle's location and direction. + rotational = surf2 % periodic_translate(surf, p % coord(1) % xyz, & + p % coord(1) % uvw) ! Reassign particle's surface if (rotational) then - p % surface = surface_i_periodic_c(i_surface-1) + 1 + p % surface = surf % i_periodic() + 1 else - p % surface = sign(surface_i_periodic_c(i_surface-1) + 1, p % surface) + p % surface = sign(surf % i_periodic() + 1, p % surface) end if ! Figure out what cell particle is in now @@ -427,7 +431,8 @@ contains call find_cell(p, found) if (.not. found) then call p % mark_as_lost("Couldn't find particle after hitting & - &periodic boundary on surface " // trim(to_str(surf % id)) // ".") + &periodic boundary on surface " // trim(to_str(surf % id())) & + // ".") return end if @@ -437,7 +442,7 @@ contains ! Diagnostic message if (verbosity >= 10 .or. trace) then call write_message(" Hit periodic boundary on surface " & - // trim(to_str(surf%id))) + // trim(to_str(surf%id()))) end if return end if @@ -484,7 +489,7 @@ contains if (.not. found) then call p % mark_as_lost("After particle " // trim(to_str(p % id)) & - // " crossed surface " // trim(to_str(surf % id)) & + // " crossed surface " // trim(to_str(surf % id())) & // " it could not be located in any cell and it did not leak.") return end if From ced4c05400d3c3b96f8037366163748cd7ab406d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 26 Jan 2018 13:24:10 -0600 Subject: [PATCH 133/282] Use contiguous derived MPI type to avoid broadcasting counts > 2**31 - 1 --- CMakeLists.txt | 5 ++++- src/simulation.F90 | 21 ++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 51bdcc4d9..8104b5fb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,9 @@ if($ENV{FC} MATCHES "(mpi[^/]*|ftn)$") message("-- Detected MPI wrapper: $ENV{FC}") add_definitions(-DMPI) set(MPI_ENABLED TRUE) + + # Get directory containing MPI wrapper + get_filename_component(MPI_DIR $ENV{FC} DIRECTORY) endif() # Check for Fortran 2008 MPI interface @@ -552,7 +555,7 @@ foreach(test ${TESTS}) add_test(NAME ${TEST_NAME} WORKING_DIRECTORY ${TEST_PATH} COMMAND ${PYTHON_EXECUTABLE} ${TEST_NAME} --exe $ - --mpi_exec $ENV{MPI_DIR}/bin/mpiexec) + --mpi_exec ${MPI_DIR}/mpiexec) else() # Perform a serial test add_test(NAME ${TEST_NAME} diff --git a/src/simulation.F90 b/src/simulation.F90 index ef29e7832..061ca2fda 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -472,8 +472,14 @@ contains #ifdef MPI integer :: n ! size of arrays integer :: mpi_err ! MPI error code + integer :: count_per_filter ! number of result values for one filter bin integer(8) :: temp real(8) :: tempr(3) ! temporary array for communication +#ifdef MPIF08 + type(MPI_Datatype) :: result_block +#else + integer :: result_block +#endif #endif ! Skip if simulation was never run @@ -498,9 +504,18 @@ contains ! Broadcast tally results so that each process has access to results if (allocated(tallies)) then do i = 1, size(tallies) - n = size(tallies(i) % obj % results) - call MPI_BCAST(tallies(i) % obj % results, n, MPI_DOUBLE, 0, & - mpi_intracomm, mpi_err) + associate (results => tallies(i) % obj % results) + ! Create a new datatype that consists of all values for a given filter + ! bin and then use that to broadcast. This is done to minimize the + ! chance of the 'count' argument of MPI_BCAST exceeding 2**31 + n = size(results, 3) + count_per_filter = size(results, 1) * size(results, 2) + call MPI_TYPE_CONTIGUOUS(count_per_filter, MPI_DOUBLE, & + result_block, mpi_err) + call MPI_TYPE_COMMIT(result_block, mpi_err) + call MPI_BCAST(results, n, result_block, 0, mpi_intracomm, mpi_err) + call MPI_TYPE_FREE(result_block, mpi_err) + end associate end do end if From 52905d4364bfead5d1c11fb1acbcd7128d08b95d Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 31 Jan 2018 15:28:36 -0500 Subject: [PATCH 134/282] Address #959 comments --- src/error.F90 | 6 +- src/error.h | 12 +++ src/hdf5_interface.h | 4 + src/random_lcg.cpp | 5 ++ src/random_lcg.h | 4 + src/surface.cpp | 188 ++++++++++++++++++++--------------------- src/surface.h | 24 ++++-- src/surface_header.F90 | 2 +- src/xml_interface.h | 24 +++--- 9 files changed, 147 insertions(+), 122 deletions(-) diff --git a/src/error.F90 b/src/error.F90 index cba137291..0a5af302d 100644 --- a/src/error.F90 +++ b/src/error.F90 @@ -195,9 +195,9 @@ contains end subroutine fatal_error subroutine fatal_error_from_c(message, message_len) bind(C) - integer(C_INT), intent(in), value :: message_len - character(C_CHAR), intent(in) :: message(message_len) - character(message_len+1) :: message_out + integer(C_INT), intent(in), value :: message_len + character(kind=C_CHAR), intent(in) :: message(message_len) + character(message_len+1) :: message_out write(message_out, *) message call fatal_error(message_out) end subroutine diff --git a/src/error.h b/src/error.h index 0fd78a6c5..4c3373b3e 100644 --- a/src/error.h +++ b/src/error.h @@ -3,6 +3,10 @@ #include #include +#include + + +namespace openmc { extern "C" void fatal_error_from_c(const char *message, int message_len); @@ -19,4 +23,12 @@ void fatal_error(const std::string &message) fatal_error_from_c(message.c_str(), message.length()); } + +void fatal_error(const std::stringstream &message) +{ + std::string out {message.str()}; + fatal_error_from_c(out.c_str(), out.length()); +} + +} // namespace openmc #endif // ERROR_H diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 2cded21f6..7ec9ada9b 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -9,6 +9,9 @@ #include "error.h" +namespace openmc { + + hid_t create_group(hid_t parent_id, char const *name) { @@ -84,4 +87,5 @@ write_string(hid_t group_id, char const *name, const std::string &buffer) write_string(group_id, name, buffer.c_str()); } +} // namespace openmc #endif //HDF5_INTERFACE_H diff --git a/src/random_lcg.cpp b/src/random_lcg.cpp index 047bda8e5..2dcdd86a3 100644 --- a/src/random_lcg.cpp +++ b/src/random_lcg.cpp @@ -2,6 +2,9 @@ #include +namespace openmc { + + // Constants extern "C" const int N_STREAMS {5}; extern "C" const int STREAM_TRACKING {0}; @@ -143,3 +146,5 @@ openmc_set_seed(int64_t new_seed) } return 0; } + +} // namespace openmc diff --git a/src/random_lcg.h b/src/random_lcg.h index 04191254f..cd065474e 100644 --- a/src/random_lcg.h +++ b/src/random_lcg.h @@ -3,6 +3,9 @@ #include + +namespace openmc { + //============================================================================== // Module constants. //============================================================================== @@ -82,4 +85,5 @@ extern "C" void prn_set_stream(int n); extern "C" int openmc_set_seed(int64_t new_seed); +} // namespace openmc #endif // RANDOM_LCG_H diff --git a/src/surface.cpp b/src/surface.cpp index 369334d50..16f333f7e 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -1,11 +1,15 @@ +#include "surface.h" + #include -#include // For fabs +#include #include "error.h" #include "hdf5_interface.h" #include "xml_interface.h" +#include -#include "surface.h" + +namespace openmc { //============================================================================== // Helper functions for reading the "coeffs" node of an XML surface element @@ -14,7 +18,7 @@ int word_count(const std::string &text) { bool in_word = false; - int count{0}; + int count {0}; for (auto c = text.begin(); c != text.end(); c++) { if (std::isspace(*c)) { if (in_word) { @@ -35,10 +39,9 @@ void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1) std::string coeffs = get_node_value(surf_node, "coeffs"); int n_words = word_count(coeffs); if (n_words != 1) { - std::string err_msg{"Surface "}; - err_msg += std::to_string(surf_id); - err_msg += " expects 1 coeff but was given "; - err_msg += std::to_string(n_words); + std::stringstream err_msg; + err_msg << "Surface " << surf_id << " expects 1 coeff but was given " + << n_words; fatal_error(err_msg); } @@ -56,10 +59,9 @@ void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1, double &c2, std::string coeffs = get_node_value(surf_node, "coeffs"); int n_words = word_count(coeffs); if (n_words != 3) { - std::string err_msg{"Surface "}; - err_msg += std::to_string(surf_id); - err_msg += " expects 3 coeffs but was given "; - err_msg += std::to_string(n_words); + std::stringstream err_msg; + err_msg << "Surface " << surf_id << " expects 3 coeffs but was given " + << n_words; fatal_error(err_msg); } @@ -77,10 +79,9 @@ void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1, double &c2, std::string coeffs = get_node_value(surf_node, "coeffs"); int n_words = word_count(coeffs); if (n_words != 4) { - std::string err_msg{"Surface "}; - err_msg += std::to_string(surf_id); - err_msg += " expects 4 coeffs but was given "; - err_msg += std::to_string(n_words); + std::stringstream err_msg; + err_msg << "Surface " << surf_id << " expects 4 coeffs but was given " + << n_words; fatal_error(err_msg); } @@ -99,10 +100,9 @@ void read_coeffs(pugi::xml_node surf_node, int surf_id, double &c1, double &c2, std::string coeffs = get_node_value(surf_node, "coeffs"); int n_words = word_count(coeffs); if (n_words != 10) { - std::string err_msg{"Surface "}; - err_msg += std::to_string(surf_id); - err_msg += " expects 10 coeffs but was given "; - err_msg += std::to_string(n_words); + std::stringstream err_msg; + err_msg << "Surface " << surf_id << " expects 10 coeffs but was given " + << n_words; fatal_error(err_msg); } @@ -133,23 +133,21 @@ Surface::Surface(pugi::xml_node surf_node) if (check_for_node(surf_node, "boundary")) { std::string surf_bc = get_node_value(surf_node, "boundary"); - if (surf_bc.compare("transmission") == 0 - or surf_bc.compare("transmit") == 0 - or surf_bc.compare("") == 0) { + if (surf_bc == "transmission" || surf_bc == "transmit" ||surf_bc.empty()) { bc = BC_TRANSMIT; - } else if (surf_bc.compare("vacuum") == 0) { + } else if (surf_bc == "vacuum") { bc = BC_VACUUM; - } else if (surf_bc.compare("reflective") == 0 - or surf_bc.compare("reflect") == 0 - or surf_bc.compare("reflecting") == 0) { + } else if (surf_bc == "reflective" || surf_bc == "reflect" + || surf_bc == "reflecting") { bc = BC_REFLECT; - } else if (surf_bc.compare("periodic") == 0) { + } else if (surf_bc == "periodic") { bc = BC_PERIODIC; } else { - std::string err_msg("Unknown boundary condition \""); - err_msg += surf_bc + "\" specified on surface " + std::to_string(id); + std::stringstream err_msg; + err_msg << "Unknown boundary condition \"" << surf_bc + << "\" specified on surface " << id; fatal_error(err_msg); } @@ -167,7 +165,7 @@ Surface::sense(const double xyz[3], const double uvw[3]) const const double f = evaluate(xyz); // Check which side of surface the point is on. - if (fabs(f) < FP_COINCIDENT) { + if (std::abs(f) < FP_COINCIDENT) { // Particle may be coincident with this surface. To determine the sense, we // look at the direction of the particle relative to the surface normal (by // default in the positive direction) via their dot product. @@ -197,7 +195,7 @@ Surface::reflect(const double xyz[3], double uvw[3]) const void Surface::to_hdf5(hid_t group_id) const { - std::string group_name{"surface "}; + std::string group_name {"surface "}; group_name += std::to_string(id); hid_t surf_group = create_group(group_id, group_name); @@ -217,7 +215,7 @@ Surface::to_hdf5(hid_t group_id) const break; } - if (name.compare("")) { + if (!name.empty()) { write_string(surf_group, "name", name); } @@ -231,7 +229,7 @@ Surface::to_hdf5(hid_t group_id) const //============================================================================== PeriodicSurface::PeriodicSurface(pugi::xml_node surf_node) - : Surface(surf_node) + : Surface {surf_node} { if (check_for_node(surf_node, "periodic_surface_id")) { i_periodic = stoi(get_node_value(surf_node, "periodic_surface_id")); @@ -255,7 +253,7 @@ axis_aligned_plane_distance(const double xyz[3], const double uvw[3], bool coincident, double offset) { const double f = offset - xyz[i]; - if (coincident or fabs(f) < FP_COINCIDENT or uvw[i] == 0.0) return INFTY; + if (coincident or std::abs(f) < FP_COINCIDENT or uvw[i] == 0.0) return INFTY; const double d = f / uvw[i]; if (d < 0.0) return INFTY; return d; @@ -300,7 +298,7 @@ inline void SurfaceXPlane::normal(const double xyz[3], double uvw[3]) const void SurfaceXPlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "x-plane"); - std::array coeffs{{x0}}; + std::array coeffs {{x0}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -320,7 +318,6 @@ bool SurfaceXPlane::periodic_translate(PeriodicSurface *other, double xyz[3], double y0 = -other->evaluate(xyz_test); xyz[1] = xyz[0] - x0 + y0; xyz[0] = x0; - xyz[2] = xyz[2]; double u = uvw[0]; uvw[0] = -uvw[1]; @@ -330,10 +327,10 @@ bool SurfaceXPlane::periodic_translate(PeriodicSurface *other, double xyz[3], } } -struct BoundingBox +BoundingBox SurfaceXPlane::bounding_box() const { - struct BoundingBox out{x0, x0, -INFTY, INFTY, -INFTY, INFTY}; + BoundingBox out {x0, x0, -INFTY, INFTY, -INFTY, INFTY}; return out; } @@ -366,7 +363,7 @@ inline void SurfaceYPlane::normal(const double xyz[3], double uvw[3]) const void SurfaceYPlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "y-plane"); - std::array coeffs{{y0}}; + std::array coeffs {{y0}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -396,10 +393,10 @@ bool SurfaceYPlane::periodic_translate(PeriodicSurface *other, double xyz[3], } } -struct BoundingBox +BoundingBox SurfaceYPlane::bounding_box() const { - struct BoundingBox out{-INFTY, INFTY, y0, y0, -INFTY, INFTY}; + BoundingBox out {-INFTY, INFTY, y0, y0, -INFTY, INFTY}; return out; } @@ -432,7 +429,7 @@ inline void SurfaceZPlane::normal(const double xyz[3], double uvw[3]) const void SurfaceZPlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "z-plane"); - std::array coeffs{{z0}}; + std::array coeffs {{z0}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -444,10 +441,10 @@ bool SurfaceZPlane::periodic_translate(PeriodicSurface *other, double xyz[3], return false; } -struct BoundingBox +BoundingBox SurfaceZPlane::bounding_box() const { - struct BoundingBox out{-INFTY, INFTY, -INFTY, INFTY, z0, z0}; + BoundingBox out {-INFTY, INFTY, -INFTY, INFTY, z0, z0}; return out; } @@ -473,7 +470,7 @@ SurfacePlane::distance(const double xyz[3], const double uvw[3], { const double f = A*xyz[0] + B*xyz[1] + C*xyz[2] - D; const double projection = A*uvw[0] + B*uvw[1] + C*uvw[2]; - if (coincident or fabs(f) < FP_COINCIDENT or projection == 0.0) { + if (coincident or std::abs(f) < FP_COINCIDENT or projection == 0.0) { return INFTY; } else { const double d = -f / projection; @@ -493,7 +490,7 @@ SurfacePlane::normal(const double xyz[3], double uvw[3]) const void SurfacePlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "plane"); - std::array coeffs{{A, B, C, D}}; + std::array coeffs {{A, B, C, D}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -513,10 +510,10 @@ bool SurfacePlane::periodic_translate(PeriodicSurface *other, double xyz[3], return false; } -struct BoundingBox +BoundingBox SurfacePlane::bounding_box() const { - struct BoundingBox out{-INFTY, INFTY, -INFTY, INFTY, -INFTY, INFTY}; + BoundingBox out {-INFTY, INFTY, -INFTY, INFTY, -INFTY, INFTY}; return out; } @@ -556,7 +553,7 @@ axis_aligned_cylinder_distance(const double xyz[3], const double uvw[3], // No intersection with cylinder. return INFTY; - } else if (coincident or fabs(c) < FP_COINCIDENT) { + } else if (coincident or std::abs(c) < FP_COINCIDENT) { // Particle is on the cylinder, thus one distance is positive/negative // and the other is zero. The sign of k determines if we are facing in or // out. @@ -625,7 +622,7 @@ inline void SurfaceXCylinder::normal(const double xyz[3], double uvw[3]) const void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "x-cylinder"); - std::array coeffs{{y0, z0, r}}; + std::array coeffs {{y0, z0, r}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -659,7 +656,7 @@ inline void SurfaceYCylinder::normal(const double xyz[3], double uvw[3]) const void SurfaceYCylinder::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "y-cylinder"); - std::array coeffs{{x0, z0, r}}; + std::array coeffs {{x0, z0, r}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -693,7 +690,7 @@ inline void SurfaceZCylinder::normal(const double xyz[3], double uvw[3]) const void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "z-cylinder"); - std::array coeffs{{x0, y0, r}}; + std::array coeffs {{x0, y0, r}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -729,7 +726,7 @@ double SurfaceSphere::distance(const double xyz[3], const double uvw[3], // No intersection with sphere. return INFTY; - } else if (coincident or fabs(c) < FP_COINCIDENT) { + } else if (coincident or std::abs(c) < FP_COINCIDENT) { // Particle is on the sphere, thus one distance is positive/negative and // the other is zero. The sign of k determines if we are facing in or out. if (k >= 0.0) { @@ -764,7 +761,7 @@ inline void SurfaceSphere::normal(const double xyz[3], double uvw[3]) const void SurfaceSphere::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "sphere"); - std::array coeffs{{x0, y0, z0, r}}; + std::array coeffs {{x0, y0, z0, r}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -808,7 +805,7 @@ axis_aligned_cone_distance(const double xyz[3], const double uvw[3], // No intersection with cone. return INFTY; - } else if (coincident or fabs(c) < FP_COINCIDENT) { + } else if (coincident or std::abs(c) < FP_COINCIDENT) { // Particle is on the cone, thus one distance is positive/negative // and the other is zero. The sign of k determines if we are facing in or // out. @@ -881,7 +878,7 @@ inline void SurfaceXCone::normal(const double xyz[3], double uvw[3]) const void SurfaceXCone::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "x-cone"); - std::array coeffs{{x0, y0, z0, r_sq}}; + std::array coeffs {{x0, y0, z0, r_sq}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -915,7 +912,7 @@ inline void SurfaceYCone::normal(const double xyz[3], double uvw[3]) const void SurfaceYCone::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "y-cone"); - std::array coeffs{{x0, y0, z0, r_sq}}; + std::array coeffs {{x0, y0, z0, r_sq}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -949,7 +946,7 @@ inline void SurfaceZCone::normal(const double xyz[3], double uvw[3]) const void SurfaceZCone::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "z-cone"); - std::array coeffs{{x0, y0, z0, r_sq}}; + std::array coeffs {{x0, y0, z0, r_sq}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -998,7 +995,7 @@ SurfaceQuadric::distance(const double xyz[3], // No intersection with surface. return INFTY; - } else if (coincident or fabs(c) < FP_COINCIDENT) { + } else if (coincident or std::abs(c) < FP_COINCIDENT) { // Particle is on the surface, thus one distance is positive/negative and // the other is zero. The sign of k determines which distance is zero and // which is not. @@ -1043,7 +1040,7 @@ SurfaceQuadric::normal(const double xyz[3], double uvw[3]) const void SurfaceQuadric::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "quadric"); - std::array coeffs{{A, B, C, D, E, F, G, H, J, K}}; + std::array coeffs {{A, B, C, D, E, F, G, H, J, K}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -1053,10 +1050,7 @@ extern "C" void read_surfaces(pugi::xml_node *node) { // Count the number of surfaces. - for (pugi::xml_node surf_node = node->child("surface"); surf_node; - surf_node = surf_node.next_sibling("surface")) { - n_surfaces++; - } + for (pugi::xml_node surf_node: node->children("surface")) {n_surfaces++;} if (n_surfaces == 0) { fatal_error("No surfaces found in geometry.xml!"); } @@ -1072,46 +1066,45 @@ read_surfaces(pugi::xml_node *node) surf_node = surf_node.next_sibling("surface"), i_surf++) { std::string surf_type = get_node_value(surf_node, "type"); - if (surf_type.compare("x-plane") == 0) { + if (surf_type == "x-plane") { surfaces_c[i_surf] = new SurfaceXPlane(surf_node); - } else if (surf_type.compare("y-plane") == 0) { + } else if (surf_type == "y-plane") { surfaces_c[i_surf] = new SurfaceYPlane(surf_node); - } else if (surf_type.compare("z-plane") == 0) { + } else if (surf_type == "z-plane") { surfaces_c[i_surf] = new SurfaceZPlane(surf_node); - } else if (surf_type.compare("plane") == 0) { + } else if (surf_type == "plane") { surfaces_c[i_surf] = new SurfacePlane(surf_node); - } else if (surf_type.compare("x-cylinder") == 0) { + } else if (surf_type == "x-cylinder") { surfaces_c[i_surf] = new SurfaceXCylinder(surf_node); - } else if (surf_type.compare("y-cylinder") == 0) { + } else if (surf_type == "y-cylinder") { surfaces_c[i_surf] = new SurfaceYCylinder(surf_node); - } else if (surf_type.compare("z-cylinder") == 0) { + } else if (surf_type == "z-cylinder") { surfaces_c[i_surf] = new SurfaceZCylinder(surf_node); - } else if (surf_type.compare("sphere") == 0) { + } else if (surf_type == "sphere") { surfaces_c[i_surf] = new SurfaceSphere(surf_node); - } else if (surf_type.compare("x-cone") == 0) { + } else if (surf_type == "x-cone") { surfaces_c[i_surf] = new SurfaceXCone(surf_node); - } else if (surf_type.compare("y-cone") == 0) { + } else if (surf_type == "y-cone") { surfaces_c[i_surf] = new SurfaceYCone(surf_node); - } else if (surf_type.compare("z-cone") == 0) { + } else if (surf_type == "z-cone") { surfaces_c[i_surf] = new SurfaceZCone(surf_node); - } else if (surf_type.compare("quadric") == 0) { + } else if (surf_type == "quadric") { surfaces_c[i_surf] = new SurfaceQuadric(surf_node); } else { - std::string err_msg{"Invalid surface type, \""}; - err_msg += surf_type; - err_msg += "\""; + std::stringstream err_msg; + err_msg << "Invalid surface type, \"" << surf_type << "\""; fatal_error(err_msg); } } @@ -1124,15 +1117,15 @@ read_surfaces(pugi::xml_node *node) if (in_dict == surface_dict.end()) { surface_dict[id] = i_surf; } else { - std::string err_msg{"Two or more surfaces use the same unique ID: "}; - err_msg += std::to_string(id); + std::stringstream err_msg; + err_msg << "Two or more surfaces use the same unique ID: " << id; fatal_error(err_msg); } } // Find the global bounding box (of periodic BC surfaces). - double xmin{INFTY}, xmax{-INFTY}, ymin{INFTY}, ymax{-INFTY}, - zmin{INFTY}, zmax{-INFTY}; + double xmin {INFTY}, xmax {-INFTY}, ymin {INFTY}, ymax {-INFTY}, + zmin {INFTY}, zmax {-INFTY}; int i_xmin, i_xmax, i_ymin, i_ymax, i_zmin, i_zmax; for (int i_surf = 0; i_surf < n_surfaces; i_surf++) { if (surfaces_c[i_surf]->bc == BC_PERIODIC) { @@ -1141,15 +1134,16 @@ read_surfaces(pugi::xml_node *node) PeriodicSurface *surf = dynamic_cast(surf_base); // Make sure this surface inherits from PeriodicSurface. - if (surf == NULL) { - std::string err_msg{"Periodic boundary condition not supported for " - "surface "}; - err_msg += std::to_string(surf_base->id); - err_msg += ". Periodic BCs are only supported for planar surfaces."; + if (!surf) { + std::stringstream err_msg; + err_msg << "Periodic boundary condition not supported for surface " + << surf_base->id + << ". Periodic BCs are only supported for planar surfaces."; + fatal_error(err_msg); } // See if this surface makes part of the global bounding box. - struct BoundingBox bb = surf->bounding_box(); + BoundingBox bb = surf->bounding_box(); if (bb.xmin > -INFTY and bb.xmin < xmin) { xmin = bb.xmin; i_xmin = i_surf; @@ -1188,7 +1182,7 @@ read_surfaces(pugi::xml_node *node) // differently). SurfacePlane *surf_p = dynamic_cast(surf); - if (surf_p == NULL) { + if (!surf_p) { // This is not a SurfacePlane. if (surf->i_periodic == C_NONE) { // The user did not specify the matching periodic surface. See if we @@ -1217,9 +1211,9 @@ read_surfaces(pugi::xml_node *node) // This is a SurfacePlane. We won't try to find it's partner if the // user didn't specify one. if (surf->i_periodic == C_NONE) { - std::string err_msg{"No matching periodic surface specified for " - "periodic boundary condition on surface "}; - err_msg += std::to_string(surf->id); + std::stringstream err_msg; + err_msg << "No matching periodic surface specified for periodic " + "boundary condition on surface " << surf->id; fatal_error(err_msg); } else { // Convert the surface id to an index. @@ -1229,11 +1223,13 @@ read_surfaces(pugi::xml_node *node) // Make sure the opposite surface is also periodic. if (surfaces_c[surf->i_periodic]->bc != BC_PERIODIC) { - std::string err_msg{"Could not find matching surface for periodic " - "boundary condition on surface "}; - err_msg += std::to_string(surf->id); + std::stringstream err_msg; + err_msg << "Could not find matching surface for periodic boundary " + "condition on surface " << surf->id; fatal_error(err_msg); } } } } + +} // namespace openmc diff --git a/src/surface.h b/src/surface.h index 469d88486..72cfd6202 100644 --- a/src/surface.h +++ b/src/surface.h @@ -3,32 +3,37 @@ #include #include // For numeric_limits +#include #include "hdf5.h" #include "pugixml/pugixml.hpp" + +namespace openmc { + //============================================================================== // Module constants //============================================================================== -extern "C" const int BC_TRANSMIT{0}; -extern "C" const int BC_VACUUM{1}; -extern "C" const int BC_REFLECT{2}; -extern "C" const int BC_PERIODIC{3}; +extern "C" const int BC_TRANSMIT {0}; +extern "C" const int BC_VACUUM {1}; +extern "C" const int BC_REFLECT {2}; +extern "C" const int BC_PERIODIC {3}; //============================================================================== // Constants that should eventually be moved out of this file //============================================================================== extern "C" double FP_COINCIDENT; -const double INFTY{std::numeric_limits::max()}; -const int C_NONE{-1}; +constexpr double INFTY{std::numeric_limits::max()}; +constexpr int C_NONE {-1}; //============================================================================== // Global variables //============================================================================== -int n_surfaces{0}; +// Braces force n_surfaces to be defined here, not just declared. +extern "C" {int32_t n_surfaces {0};} class Surface; Surface **surfaces_c; @@ -64,6 +69,8 @@ public: Surface(pugi::xml_node surf_node); + virtual ~Surface() {} + //! Determine which side of a surface a point lies on. //! @param xyz[3] The 3D Cartesian coordinate of a point. //! @param uvw[3] A direction used to "break ties" and pick a sense when the @@ -411,9 +418,10 @@ extern "C" void free_memory_surfaces_c() { for (int i = 0; i < n_surfaces; i++) {delete surfaces_c[i];} delete surfaces_c; - surfaces_c = NULL; + surfaces_c = nullptr; n_surfaces = 0; surface_dict.clear(); } +} // namespace openmc #endif // SURFACE_H diff --git a/src/surface_header.F90 b/src/surface_header.F90 index 8985e8406..9ed89a14e 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -126,7 +126,7 @@ module surface_header end type Surface - integer(C_INT), bind(C) :: n_surfaces ! # of surfaces + integer(C_INT32_T), bind(C) :: n_surfaces ! # of surfaces type(Surface), allocatable, target :: surfaces(:) diff --git a/src/xml_interface.h b/src/xml_interface.h index fbc82e056..778497562 100644 --- a/src/xml_interface.h +++ b/src/xml_interface.h @@ -2,26 +2,23 @@ #define XML_INTERFACE_H #include // for std::transform +#include #include #include "pugixml/pugixml.hpp" +namespace openmc { + bool -check_for_node(const pugi::xml_node &node, const char *name) +check_for_node(pugi::xml_node node, const char *name) { - if (node.attribute(name)) { - return true; - } else if (node.child(name)) { - return true; - } else { - return false; - } + return node.attribute(name) || node.child(name); } std::string -get_node_value(const pugi::xml_node &node, const char *name) +get_node_value(pugi::xml_node node, const char *name) { // Search for either an attribute or child tag and get the data as a char*. const pugi::char_t *value_char; @@ -30,11 +27,9 @@ get_node_value(const pugi::xml_node &node, const char *name) } else if (node.child(name)) { value_char = node.child_value(name); } else { - std::string err_msg("Node \""); - err_msg += name; - err_msg += "\" is not a memeber of the \""; - err_msg += node.name(); - err_msg += "\" XML node"; + std::stringstream err_msg; + err_msg << "Node \"" << name << "\" is not a member of the \"" + << node.name() << "\" XML node"; fatal_error(err_msg); } @@ -49,4 +44,5 @@ get_node_value(const pugi::xml_node &node, const char *name) return value; } +} // namespace openmc #endif // XML_INTERFACE_H From 48ac683a0cec3724c80629ac549a0eb4a4f7b4d1 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 31 Jan 2018 17:52:52 -0500 Subject: [PATCH 135/282] Remove C++/Fortran interoperable "seed" --- openmc/capi/settings.py | 5 ++--- src/api.F90 | 10 +++++----- src/initialize.F90 | 3 +-- src/input_xml.F90 | 2 +- src/random_lcg.F90 | 13 ++++++++----- src/random_lcg.cpp | 5 +++-- src/random_lcg.h | 8 +++++++- src/state_point.F90 | 6 ++++-- tests/unit_tests/test_capi.py | 10 +++++----- 9 files changed, 36 insertions(+), 26 deletions(-) diff --git a/openmc/capi/settings.py b/openmc/capi/settings.py index eede6cf47..1063d6463 100644 --- a/openmc/capi/settings.py +++ b/openmc/capi/settings.py @@ -11,8 +11,7 @@ _RUN_MODES = {1: 'fixed source', 5: 'volume'} _dll.openmc_set_seed.argtypes = [c_int64] -_dll.openmc_set_seed.restype = c_int -_dll.openmc_set_seed.errcheck = _error_handler +_dll.openmc_get_seed.restype = c_int64 class _Settings(object): @@ -43,7 +42,7 @@ class _Settings(object): @property def seed(self): - return c_int64.in_dll(_dll, 'seed').value + return _dll.openmc_get_seed() @seed.setter def seed(self, seed): diff --git a/src/api.F90 b/src/api.F90 index 5d56b857c..f07e5e38a 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -18,7 +18,7 @@ module openmc_api use initialize, only: openmc_init use particle_header, only: Particle use plot, only: openmc_plot_geometry - use random_lcg, only: seed, openmc_set_seed + use random_lcg, only: openmc_get_seed, openmc_set_seed use settings use simulation_header use source_header, only: openmc_extend_sources, openmc_source_set_strength @@ -60,6 +60,7 @@ module openmc_api public :: openmc_get_filter_next_id public :: openmc_get_material_index public :: openmc_get_nuclide_index + public :: openmc_get_seed public :: openmc_get_tally_index public :: openmc_global_tallies public :: openmc_hard_reset @@ -79,6 +80,7 @@ module openmc_api public :: openmc_plot_geometry public :: openmc_reset public :: openmc_run + public :: openmc_set_seed public :: openmc_simulation_finalize public :: openmc_simulation_init public :: openmc_source_bank @@ -142,7 +144,7 @@ contains run_CE = .true. run_mode = NONE satisfy_triggers = .false. - seed = 1_8 + call openmc_set_seed(DEFAULT_SEED) source_latest = .false. source_separate = .false. source_write = .true. @@ -228,8 +230,6 @@ contains !=============================================================================== subroutine openmc_hard_reset() bind(C) - integer :: err - ! Reset all tallies and timers call openmc_reset() @@ -238,7 +238,7 @@ contains total_gen = 0 ! Reset the random number generator state - err = openmc_set_seed(1_8) + call openmc_set_seed(DEFAULT_SEED) end subroutine openmc_hard_reset !=============================================================================== diff --git a/src/initialize.F90 b/src/initialize.F90 index 2af93627e..7b699b9cb 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -44,7 +44,6 @@ contains subroutine openmc_init(intracomm) bind(C) integer, intent(in), optional :: intracomm ! MPI intracommunicator - integer :: err #ifdef _OPENMP character(MAX_WORD_LEN) :: envvar #endif @@ -95,7 +94,7 @@ contains ! Initialize random number generator -- if the user specifies a seed, it ! will be re-initialized later - err = openmc_set_seed(DEFAULT_SEED) + call openmc_set_seed(DEFAULT_SEED) ! Read XML input files call read_input_xml() diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 55eeca9e1..0b7790a51 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -349,7 +349,7 @@ contains ! Copy random number seed if specified if (check_for_node(root, "seed")) then call get_node_value(root, "seed", seed) - err = openmc_set_seed(seed) + call openmc_set_seed(seed) end if ! Number of bins for logarithmic grid diff --git a/src/random_lcg.F90 b/src/random_lcg.F90 index 839bd729a..d64717cf4 100644 --- a/src/random_lcg.F90 +++ b/src/random_lcg.F90 @@ -4,8 +4,6 @@ module random_lcg implicit none - integer(C_INT64_T), bind(C) :: seed - interface function prn() result(pseudo_rn) bind(C) use ISO_C_BINDING @@ -38,11 +36,16 @@ module random_lcg integer(C_INT), value :: n end subroutine prn_set_stream - function openmc_set_seed(new_seed) result(err) bind(C) + function openmc_get_seed() result(seed) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT64_T) :: seed + end function openmc_get_seed + + subroutine openmc_set_seed(new_seed) bind(C) use ISO_C_BINDING implicit none integer(C_INT64_T), value :: new_seed - integer(C_INT) :: err - end function openmc_set_seed + end subroutine openmc_set_seed end interface end module random_lcg diff --git a/src/random_lcg.cpp b/src/random_lcg.cpp index 2dcdd86a3..2a57316cb 100644 --- a/src/random_lcg.cpp +++ b/src/random_lcg.cpp @@ -133,7 +133,9 @@ prn_set_stream(int i) // API FUNCTIONS //============================================================================== -extern "C" int +extern "C" int64_t openmc_get_seed() {return seed;} + +extern "C" void openmc_set_seed(int64_t new_seed) { seed = new_seed; @@ -144,7 +146,6 @@ openmc_set_seed(int64_t new_seed) } prn_set_stream(STREAM_TRACKING); } - return 0; } } // namespace openmc diff --git a/src/random_lcg.h b/src/random_lcg.h index cd065474e..29b3fca47 100644 --- a/src/random_lcg.h +++ b/src/random_lcg.h @@ -77,13 +77,19 @@ extern "C" void prn_set_stream(int n); // API FUNCTIONS //============================================================================== +//============================================================================== +//! Get OpenMC's master seed. +//============================================================================== + +extern "C" int64_t openmc_get_seed(); + //============================================================================== //! Set OpenMC's master seed. //! @param new_seed The master seed. All other seeds will be derived from this //! one. //============================================================================== -extern "C" int openmc_set_seed(int64_t new_seed); +extern "C" void openmc_set_seed(int64_t new_seed); } // namespace openmc #endif // RANDOM_LCG_H diff --git a/src/state_point.F90 b/src/state_point.F90 index ba7bcef19..980a7333c 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -26,7 +26,7 @@ module state_point use mgxs_header, only: nuclides_MG use nuclide_header, only: nuclides use output, only: time_stamp - use random_lcg, only: seed + use random_lcg, only: openmc_get_seed, openmc_set_seed use settings use simulation_header use string, only: to_str, count_digits, zero_padded, to_f_string @@ -97,7 +97,7 @@ contains call write_attribute(file_id, "path", path_input) ! Write out random number seed - call write_dataset(file_id, "seed", seed) + call write_dataset(file_id, "seed", openmc_get_seed()) ! Write run information if (run_CE) then @@ -642,6 +642,7 @@ contains integer :: n integer :: int_array(3) integer, allocatable :: array(:) + integer(C_INT64_T) :: seed integer(HID_T) :: file_id integer(HID_T) :: cmfd_group integer(HID_T) :: tallies_group @@ -672,6 +673,7 @@ contains ! Read and overwrite random number seed call read_dataset(seed, file_id, "seed") + call openmc_set_seed(seed) ! It is not impossible for a state point to be generated from a CE run but ! to be loaded in to an MG run (or vice versa), check to prevent that. diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index ba9ac5c9e..cd24314ad 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -29,11 +29,11 @@ def pincell_model(): yield # Delete generated files - files = ['geometry.xml', 'materials.xml', 'settings.xml', 'tallies.xml', - 'statepoint.10.h5', 'summary.h5', 'test_sp.h5'] - for f in files: - if os.path.exists(f): - os.remove(f) + #files = ['geometry.xml', 'materials.xml', 'settings.xml', 'tallies.xml', + # 'statepoint.10.h5', 'summary.h5', 'test_sp.h5'] + #for f in files: + # if os.path.exists(f): + # os.remove(f) @pytest.fixture(scope='module') From 7acf3f91dbb74c7ca0aad9bedfc2cdc14c2c2463 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 2 Feb 2018 19:56:23 -0500 Subject: [PATCH 136/282] Address #959 comments --- src/surface.cpp | 2 +- src/surface.h | 38 +++++++++++++++++------------------ tests/unit_tests/test_capi.py | 10 ++++----- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/surface.cpp b/src/surface.cpp index 16f333f7e..9abfd23d2 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -1,12 +1,12 @@ #include "surface.h" #include +#include #include #include "error.h" #include "hdf5_interface.h" #include "xml_interface.h" -#include namespace openmc { diff --git a/src/surface.h b/src/surface.h index 72cfd6202..627a8fbae 100644 --- a/src/surface.h +++ b/src/surface.h @@ -67,7 +67,7 @@ public: int bc; //!< Boundary condition std::string name{""}; //!< User-defined name - Surface(pugi::xml_node surf_node); + explicit Surface(pugi::xml_node surf_node); virtual ~Surface() {} @@ -127,7 +127,7 @@ class PeriodicSurface : public Surface public: int i_periodic{C_NONE}; //!< Index of corresponding periodic surface - PeriodicSurface(pugi::xml_node surf_node); + explicit PeriodicSurface(pugi::xml_node surf_node); //! Translate a particle onto this surface from a periodic partner surface. //! @param other A pointer to the partner surface in this periodic BC. @@ -141,7 +141,7 @@ public: double uvw[3]) const = 0; //! Get the bounding box for this surface. - virtual struct BoundingBox bounding_box() const = 0; + virtual BoundingBox bounding_box() const = 0; }; //============================================================================== @@ -154,7 +154,7 @@ class SurfaceXPlane : public PeriodicSurface { double x0; public: - SurfaceXPlane(pugi::xml_node surf_node); + explicit SurfaceXPlane(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; @@ -162,7 +162,7 @@ public: void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; - struct BoundingBox bounding_box() const; + BoundingBox bounding_box() const; }; //============================================================================== @@ -175,7 +175,7 @@ class SurfaceYPlane : public PeriodicSurface { double y0; public: - SurfaceYPlane(pugi::xml_node surf_node); + explicit SurfaceYPlane(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; @@ -183,7 +183,7 @@ public: void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; - struct BoundingBox bounding_box() const; + BoundingBox bounding_box() const; }; //============================================================================== @@ -196,7 +196,7 @@ class SurfaceZPlane : public PeriodicSurface { double z0; public: - SurfaceZPlane(pugi::xml_node surf_node); + explicit SurfaceZPlane(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; @@ -204,7 +204,7 @@ public: void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; - struct BoundingBox bounding_box() const; + BoundingBox bounding_box() const; }; //============================================================================== @@ -217,7 +217,7 @@ class SurfacePlane : public PeriodicSurface { double A, B, C, D; public: - SurfacePlane(pugi::xml_node surf_node); + explicit SurfacePlane(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; @@ -225,7 +225,7 @@ public: void to_hdf5_inner(hid_t group_id) const; bool periodic_translate(PeriodicSurface *other, double xyz[3], double uvw[3]) const; - struct BoundingBox bounding_box() const; + BoundingBox bounding_box() const; }; //============================================================================== @@ -239,7 +239,7 @@ class SurfaceXCylinder : public Surface { double y0, z0, r; public: - SurfaceXCylinder(pugi::xml_node surf_node); + explicit SurfaceXCylinder(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; @@ -258,7 +258,7 @@ class SurfaceYCylinder : public Surface { double x0, z0, r; public: - SurfaceYCylinder(pugi::xml_node surf_node); + explicit SurfaceYCylinder(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; @@ -277,7 +277,7 @@ class SurfaceZCylinder : public Surface { double x0, y0, r; public: - SurfaceZCylinder(pugi::xml_node surf_node); + explicit SurfaceZCylinder(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; @@ -296,7 +296,7 @@ class SurfaceSphere : public Surface { double x0, y0, z0, r; public: - SurfaceSphere(pugi::xml_node surf_node); + explicit SurfaceSphere(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; @@ -315,7 +315,7 @@ class SurfaceXCone : public Surface { double x0, y0, z0, r_sq; public: - SurfaceXCone(pugi::xml_node surf_node); + explicit SurfaceXCone(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; @@ -334,7 +334,7 @@ class SurfaceYCone : public Surface { double x0, y0, z0, r_sq; public: - SurfaceYCone(pugi::xml_node surf_node); + explicit SurfaceYCone(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; @@ -353,7 +353,7 @@ class SurfaceZCone : public Surface { double x0, y0, z0, r_sq; public: - SurfaceZCone(pugi::xml_node surf_node); + explicit SurfaceZCone(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; @@ -372,7 +372,7 @@ class SurfaceQuadric : public Surface // Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0 double A, B, C, D, E, F, G, H, J, K; public: - SurfaceQuadric(pugi::xml_node surf_node); + explicit SurfaceQuadric(pugi::xml_node surf_node); double evaluate(const double xyz[3]) const; double distance(const double xyz[3], const double uvw[3], bool coincident) const; diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index cd24314ad..ba9ac5c9e 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -29,11 +29,11 @@ def pincell_model(): yield # Delete generated files - #files = ['geometry.xml', 'materials.xml', 'settings.xml', 'tallies.xml', - # 'statepoint.10.h5', 'summary.h5', 'test_sp.h5'] - #for f in files: - # if os.path.exists(f): - # os.remove(f) + files = ['geometry.xml', 'materials.xml', 'settings.xml', 'tallies.xml', + 'statepoint.10.h5', 'summary.h5', 'test_sp.h5'] + for f in files: + if os.path.exists(f): + os.remove(f) @pytest.fixture(scope='module') From cb076c8e4fa81b45e09de37be41362cf17c1d1c9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 3 Feb 2018 12:38:01 -0600 Subject: [PATCH 137/282] Update ifdef for MPI F08 --- src/simulation.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simulation.F90 b/src/simulation.F90 index faf3c4016..424c55e9b 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -475,7 +475,7 @@ contains integer :: count_per_filter ! number of result values for one filter bin integer(8) :: temp real(8) :: tempr(3) ! temporary array for communication -#ifdef MPIF08 +#ifdef OPENMC_MPIF08 type(MPI_Datatype) :: result_block #else integer :: result_block From 9149e0e0ef3420049a64bb03a515c9d01b03e483 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Sun, 4 Feb 2018 15:39:54 -0500 Subject: [PATCH 138/282] Moved cross_sections.F90 functionality into Material and Nuclide --- CMakeLists.txt | 1 - src/cross_section.F90 | 936 ---------------------------------------- src/material_header.F90 | 111 +++++ src/mgxs_header.F90 | 21 +- src/nuclide_header.F90 | 764 +++++++++++++++++++++++++++++++- src/physics.F90 | 3 +- src/sab_header.F90 | 103 ++++- src/tallies/tally.F90 | 5 +- src/tracking.F90 | 45 +- 9 files changed, 1006 insertions(+), 983 deletions(-) delete mode 100644 src/cross_section.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f5b2168f..cf44c500a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -347,7 +347,6 @@ set(LIBOPENMC_FORTRAN_SRC src/cmfd_prod_operator.F90 src/cmfd_solver.F90 src/constants.F90 - src/cross_section.F90 src/dict_header.F90 src/distribution_multivariate.F90 src/distribution_univariate.F90 diff --git a/src/cross_section.F90 b/src/cross_section.F90 deleted file mode 100644 index 47e1c4fc5..000000000 --- a/src/cross_section.F90 +++ /dev/null @@ -1,936 +0,0 @@ -module cross_section - - use algorithm, only: binary_search - use constants - use error, only: fatal_error - use list_header, only: ListElemInt - use material_header, only: Material, materials - use math, only: faddeeva, w_derivative, broaden_wmp_polynomials - use multipole_header, only: FORM_RM, FORM_MLBW, MP_EA, RM_RT, RM_RA, RM_RF, & - MLBW_RT, MLBW_RX, MLBW_RA, MLBW_RF, FIT_T, FIT_A,& - FIT_F, MultipoleArray - use nuclide_header - use particle_header, only: Particle - use random_lcg, only: prn, future_prn, prn_set_stream - use sab_header, only: SAlphaBeta, sab_tables - use settings - use simulation_header - use tally_header, only: active_tallies - - implicit none - -contains - -!=============================================================================== -! CALCULATE_XS determines the macroscopic cross sections for the material the -! particle is currently traveling through. -!=============================================================================== - - subroutine calculate_xs(p) - - type(Particle), intent(inout) :: p - - integer :: i ! loop index over nuclides - integer :: i_nuclide ! index into nuclides array - integer :: i_sab ! index into sab_tables array - integer :: j ! index in mat % i_sab_nuclides - integer :: i_grid ! index into logarithmic mapping array or material - ! union grid - real(8) :: atom_density ! atom density of a nuclide - real(8) :: sab_frac ! fraction of atoms affected by S(a,b) - logical :: check_sab ! should we check for S(a,b) table? - - ! Set all material macroscopic cross sections to zero - material_xs % total = ZERO - material_xs % absorption = ZERO - material_xs % fission = ZERO - material_xs % nu_fission = ZERO - - ! Exit subroutine if material is void - if (p % material == MATERIAL_VOID) return - - associate (mat => materials(p % material)) - ! Find energy index on energy grid - i_grid = int(log(p % E/energy_min_neutron)/log_spacing) - - ! Determine if this material has S(a,b) tables - check_sab = (mat % n_sab > 0) - - ! Initialize position in i_sab_nuclides - j = 1 - - ! Add contribution from each nuclide in material - do i = 1, mat % n_nuclides - ! ====================================================================== - ! CHECK FOR S(A,B) TABLE - - i_sab = 0 - sab_frac = ZERO - - ! Check if this nuclide matches one of the S(a,b) tables specified. - ! This relies on i_sab_nuclides being in sorted order - if (check_sab) then - if (i == mat % i_sab_nuclides(j)) then - ! Get index in sab_tables - i_sab = mat % i_sab_tables(j) - sab_frac = mat % sab_fracs(j) - - ! If particle energy is greater than the highest energy for the - ! S(a,b) table, then don't use the S(a,b) table - if (p % E > sab_tables(i_sab) % data(1) % threshold_inelastic) then - i_sab = 0 - end if - - ! Increment position in i_sab_nuclides - j = j + 1 - - ! Don't check for S(a,b) tables if there are no more left - if (j > size(mat % i_sab_tables)) check_sab = .false. - end if - end if - - ! ====================================================================== - ! CALCULATE MICROSCOPIC CROSS SECTION - - ! Determine microscopic cross sections for this nuclide - i_nuclide = mat % nuclide(i) - - ! Calculate microscopic cross section for this nuclide - if (p % E /= micro_xs(i_nuclide) % last_E & - .or. p % sqrtkT /= micro_xs(i_nuclide) % last_sqrtkT & - .or. i_sab /= micro_xs(i_nuclide) % index_sab & - .or. sab_frac /= micro_xs(i_nuclide) % sab_frac) then - call calculate_nuclide_xs(i_nuclide, i_sab, p % E, i_grid, & - p % sqrtkT, sab_frac) - end if - - ! ====================================================================== - ! ADD TO MACROSCOPIC CROSS SECTION - - ! Copy atom density of nuclide in material - atom_density = mat % atom_density(i) - - ! Add contributions to material macroscopic total cross section - material_xs % total = material_xs % total + & - atom_density * micro_xs(i_nuclide) % total - - ! Add contributions to material macroscopic absorption cross section - material_xs % absorption = material_xs % absorption + & - atom_density * micro_xs(i_nuclide) % absorption - - ! Add contributions to material macroscopic fission cross section - material_xs % fission = material_xs % fission + & - atom_density * micro_xs(i_nuclide) % fission - - ! Add contributions to material macroscopic nu-fission cross section - material_xs % nu_fission = material_xs % nu_fission + & - atom_density * micro_xs(i_nuclide) % nu_fission - end do - end associate - - end subroutine calculate_xs - -!=============================================================================== -! CALCULATE_NUCLIDE_XS determines microscopic cross sections for a nuclide of a -! given index in the nuclides array at the energy of the given particle -!=============================================================================== - - subroutine calculate_nuclide_xs(i_nuclide, i_sab, E, i_log_union, sqrtkT, & - sab_frac) - integer, intent(in) :: i_nuclide ! index into nuclides array - integer, intent(in) :: i_sab ! index into sab_tables array - real(8), intent(in) :: E ! energy - integer, intent(in) :: i_log_union ! index into logarithmic mapping array or - ! material union energy grid - real(8), intent(in) :: sqrtkT ! square root of kT, material dependent - real(8), intent(in) :: sab_frac ! fraction of atoms affected by S(a,b) - - logical :: use_mp ! true if XS can be calculated with windowed multipole - integer :: i_temp ! index for temperature - integer :: i_grid ! index on nuclide energy grid - integer :: i_low ! lower logarithmic mapping index - integer :: i_high ! upper logarithmic mapping index - integer :: i_rxn ! reaction index - integer :: j ! index in DEPLETION_RX - real(8) :: f ! interp factor on nuclide energy grid - real(8) :: kT ! temperature in eV - real(8) :: sig_t, sig_a, sig_f ! Intermediate multipole variables - - ! Initialize cached cross sections to zero - micro_xs(i_nuclide) % elastic = CACHE_INVALID - micro_xs(i_nuclide) % thermal = ZERO - micro_xs(i_nuclide) % thermal_elastic = ZERO - - associate (nuc => nuclides(i_nuclide)) - ! Check to see if there is multipole data present at this energy - use_mp = .false. - if (nuc % mp_present) then - if (E >= nuc % multipole % start_E .and. & - E <= nuc % multipole % end_E) then - use_mp = .true. - end if - end if - - ! Evaluate multipole or interpolate - if (use_mp) then - ! Call multipole kernel - call multipole_eval(nuc % multipole, E, sqrtkT, sig_t, sig_a, sig_f) - - micro_xs(i_nuclide) % total = sig_t - micro_xs(i_nuclide) % absorption = sig_a - micro_xs(i_nuclide) % fission = sig_f - - if (nuc % fissionable) then - micro_xs(i_nuclide) % nu_fission = sig_f * nuc % nu(E, EMISSION_TOTAL) - else - micro_xs(i_nuclide) % nu_fission = ZERO - end if - - if (need_depletion_rx) then - ! Initialize all reaction cross sections to zero - micro_xs(i_nuclide) % reaction(:) = ZERO - - ! Only non-zero reaction is (n,gamma) - micro_xs(i_nuclide) % reaction(4) = sig_a - sig_f - end if - - ! Ensure these values are set - ! Note, the only time either is used is in one of 4 places: - ! 1. physics.F90 - scatter - For inelastic scatter. - ! 2. physics.F90 - sample_fission - For partial fissions. - ! 3. tally.F90 - score_general - For tallying on MTxxx reactions. - ! 4. cross_section.F90 - calculate_urr_xs - For unresolved purposes. - ! It is worth noting that none of these occur in the resolved - ! resonance range, so the value here does not matter. index_temp is - ! set to -1 to force a segfault in case a developer messes up and tries - ! to use it with multipole. - micro_xs(i_nuclide) % index_temp = -1 - micro_xs(i_nuclide) % index_grid = 0 - micro_xs(i_nuclide) % interp_factor = ZERO - - else - ! Find the appropriate temperature index. - kT = sqrtkT**2 - select case (temperature_method) - case (TEMPERATURE_NEAREST) - i_temp = minloc(abs(nuclides(i_nuclide) % kTs - kT), dim=1) - - case (TEMPERATURE_INTERPOLATION) - ! Find temperatures that bound the actual temperature - do i_temp = 1, size(nuc % kTs) - 1 - if (nuc % kTs(i_temp) <= kT .and. kT < nuc % kTs(i_temp + 1)) exit - end do - - ! Randomly sample between temperature i and i+1 - f = (kT - nuc % kTs(i_temp)) / & - (nuc % kTs(i_temp + 1) - nuc % kTs(i_temp)) - if (f > prn()) i_temp = i_temp + 1 - end select - - associate (grid => nuc % grid(i_temp), xs => nuc % xs(i_temp)) - ! Determine the energy grid index using a logarithmic mapping to - ! reduce the energy range over which a binary search needs to be - ! performed - - if (E < grid % energy(1)) then - i_grid = 1 - elseif (E > grid % energy(size(grid % energy))) then - i_grid = size(grid % energy) - 1 - else - ! Determine bounding indices based on which equal log-spaced - ! interval the energy is in - i_low = grid % grid_index(i_log_union) - i_high = grid % grid_index(i_log_union + 1) + 1 - - ! Perform binary search over reduced range - i_grid = binary_search(grid % energy(i_low:i_high), & - i_high - i_low + 1, E) + i_low - 1 - end if - - ! check for rare case where two energy points are the same - if (grid % energy(i_grid) == grid % energy(i_grid + 1)) & - i_grid = i_grid + 1 - - ! calculate interpolation factor - f = (E - grid % energy(i_grid)) / & - (grid % energy(i_grid + 1) - grid % energy(i_grid)) - - micro_xs(i_nuclide) % index_temp = i_temp - micro_xs(i_nuclide) % index_grid = i_grid - micro_xs(i_nuclide) % interp_factor = f - - ! Calculate microscopic nuclide total cross section - micro_xs(i_nuclide) % total = (ONE - f) * xs % value(XS_TOTAL,i_grid) & - + f * xs % value(XS_TOTAL,i_grid + 1) - - ! Calculate microscopic nuclide absorption cross section - micro_xs(i_nuclide) % absorption = (ONE - f) * xs % value(XS_ABSORPTION, & - i_grid) + f * xs % value(XS_ABSORPTION,i_grid + 1) - - if (nuc % fissionable) then - ! Calculate microscopic nuclide total cross section - micro_xs(i_nuclide) % fission = (ONE - f) * xs % value(XS_FISSION,i_grid) & - + f * xs % value(XS_FISSION,i_grid + 1) - - ! Calculate microscopic nuclide nu-fission cross section - micro_xs(i_nuclide) % nu_fission = (ONE - f) * xs % value(XS_NU_FISSION, & - i_grid) + f * xs % value(XS_NU_FISSION,i_grid + 1) - else - micro_xs(i_nuclide) % fission = ZERO - micro_xs(i_nuclide) % nu_fission = ZERO - end if - end associate - - ! Depletion-related reactions - if (need_depletion_rx) then - do j = 1, 6 - ! Initialize reaction xs to zero - micro_xs(i_nuclide) % reaction(j) = ZERO - - ! If reaction is present and energy is greater than threshold, set - ! the reaction xs appropriately - i_rxn = nuc % reaction_index(DEPLETION_RX(j)) - if (i_rxn > 0) then - associate (xs => nuc % reactions(i_rxn) % xs(i_temp)) - if (i_grid >= xs % threshold) then - micro_xs(i_nuclide) % reaction(j) = (ONE - f) * & - xs % value(i_grid - xs % threshold + 1) + & - f * xs % value(i_grid - xs % threshold + 2) - end if - end associate - end if - end do - end if - - end if - - ! Initialize sab treatment to false - micro_xs(i_nuclide) % index_sab = NONE - micro_xs(i_nuclide) % sab_frac = ZERO - - ! Initialize URR probability table treatment to false - micro_xs(i_nuclide) % use_ptable = .false. - - ! If there is S(a,b) data for this nuclide, we need to set the sab_scatter - ! and sab_elastic cross sections and correct the total and elastic cross - ! sections. - - if (i_sab > 0) then - call calculate_sab_xs(i_nuclide, i_sab, E, sqrtkT, sab_frac) - end if - - ! If the particle is in the unresolved resonance range and there are - ! probability tables, we need to determine cross sections from the table - - if (urr_ptables_on .and. nuc % urr_present .and. .not. use_mp) then - if (E > nuc % urr_data(i_temp) % energy(1) .and. E < nuc % & - urr_data(i_temp) % energy(nuc % urr_data(i_temp) % n_energy)) then - call calculate_urr_xs(i_nuclide, i_temp, E) - end if - end if - - micro_xs(i_nuclide) % last_E = E - micro_xs(i_nuclide) % last_sqrtkT = sqrtkT - end associate - - end subroutine calculate_nuclide_xs - -!=============================================================================== -! CALCULATE_SAB_XS determines the elastic and inelastic scattering -! cross-sections in the thermal energy range. These cross sections replace a -! fraction of whatever data were taken from the normal Nuclide table. -!=============================================================================== - - subroutine calculate_sab_xs(i_nuclide, i_sab, E, sqrtkT, sab_frac) - integer, intent(in) :: i_nuclide ! index into nuclides array - integer, intent(in) :: i_sab ! index into sab_tables array - real(8), intent(in) :: E ! energy - real(8), intent(in) :: sqrtkT ! temperature - real(8), intent(in) :: sab_frac ! fraction of atoms affected by S(a,b) - - integer :: i_grid ! index on S(a,b) energy grid - integer :: i_temp ! temperature index - real(8) :: f ! interp factor on S(a,b) energy grid - real(8) :: inelastic ! S(a,b) inelastic cross section - real(8) :: elastic ! S(a,b) elastic cross section - real(8) :: kT - - ! Set flag that S(a,b) treatment should be used for scattering - micro_xs(i_nuclide) % index_sab = i_sab - - ! Determine temperature for S(a,b) table - kT = sqrtkT**2 - if (temperature_method == TEMPERATURE_NEAREST) then - ! If using nearest temperature, do linear search on temperature - do i_temp = 1, size(sab_tables(i_sab) % kTs) - if (abs(sab_tables(i_sab) % kTs(i_temp) - kT) < & - K_BOLTZMANN*temperature_tolerance) exit - end do - else - ! Find temperatures that bound the actual temperature - do i_temp = 1, size(sab_tables(i_sab) % kTs) - 1 - if (sab_tables(i_sab) % kTs(i_temp) <= kT .and. & - kT < sab_tables(i_sab) % kTs(i_temp + 1)) exit - end do - - ! Randomly sample between temperature i and i+1 - f = (kT - sab_tables(i_sab) % kTs(i_temp)) / & - (sab_tables(i_sab) % kTs(i_temp + 1) & - - sab_tables(i_sab) % kTs(i_temp)) - if (f > prn()) i_temp = i_temp + 1 - end if - - - ! Get pointer to S(a,b) table - associate (sab => sab_tables(i_sab) % data(i_temp)) - - ! Get index and interpolation factor for inelastic grid - if (E < sab % inelastic_e_in(1)) then - i_grid = 1 - f = ZERO - else - i_grid = binary_search(sab % inelastic_e_in, sab % n_inelastic_e_in, E) - f = (E - sab%inelastic_e_in(i_grid)) / & - (sab%inelastic_e_in(i_grid+1) - sab%inelastic_e_in(i_grid)) - end if - - ! Calculate S(a,b) inelastic scattering cross section - inelastic = (ONE - f) * sab % inelastic_sigma(i_grid) + & - f * sab % inelastic_sigma(i_grid + 1) - - ! Check for elastic data - if (E < sab % threshold_elastic) then - ! Determine whether elastic scattering is given in the coherent or - ! incoherent approximation. For coherent, the cross section is - ! represented as P/E whereas for incoherent, it is simply P - - if (sab % elastic_mode == SAB_ELASTIC_EXACT) then - if (E < sab % elastic_e_in(1)) then - ! If energy is below that of the lowest Bragg peak, the elastic - ! cross section will be zero - elastic = ZERO - else - i_grid = binary_search(sab % elastic_e_in, & - sab % n_elastic_e_in, E) - elastic = sab % elastic_P(i_grid) / E - end if - else - ! Determine index on elastic energy grid - if (E < sab % elastic_e_in(1)) then - i_grid = 1 - else - i_grid = binary_search(sab % elastic_e_in, & - sab % n_elastic_e_in, E) - end if - - ! Get interpolation factor for elastic grid - f = (E - sab%elastic_e_in(i_grid))/(sab%elastic_e_in(i_grid+1) - & - sab%elastic_e_in(i_grid)) - - ! Calculate S(a,b) elastic scattering cross section - elastic = (ONE - f) * sab % elastic_P(i_grid) + & - f * sab % elastic_P(i_grid + 1) - end if - else - ! No elastic data - elastic = ZERO - end if - end associate - - ! Store the S(a,b) cross sections. - micro_xs(i_nuclide) % thermal = sab_frac * (elastic + inelastic) - micro_xs(i_nuclide) % thermal_elastic = sab_frac * elastic - - ! Calculate free atom elastic cross section - call calculate_elastic_xs(i_nuclide) - - ! Correct total and elastic cross sections - micro_xs(i_nuclide) % total = micro_xs(i_nuclide) % total & - + micro_xs(i_nuclide) % thermal & - - sab_frac * micro_xs(i_nuclide) % elastic - micro_xs(i_nuclide) % elastic = micro_xs(i_nuclide) % thermal & - + (ONE - sab_frac) * micro_xs(i_nuclide) % elastic - - ! Save temperature index and thermal fraction - micro_xs(i_nuclide) % index_temp_sab = i_temp - micro_xs(i_nuclide) % sab_frac = sab_frac - - end subroutine calculate_sab_xs - -!=============================================================================== -! CALCULATE_URR_XS determines cross sections in the unresolved resonance range -! from probability tables -!=============================================================================== - - subroutine calculate_urr_xs(i_nuclide, i_temp, E) - integer, intent(in) :: i_nuclide ! index into nuclides array - integer, intent(in) :: i_temp ! temperature index - real(8), intent(in) :: E ! energy - - integer :: i_energy ! index for energy - integer :: i_low ! band index at lower bounding energy - integer :: i_up ! band index at upper bounding energy - real(8) :: f ! interpolation factor - real(8) :: r ! pseudo-random number - real(8) :: elastic ! elastic cross section - real(8) :: capture ! (n,gamma) cross section - real(8) :: fission ! fission cross section - real(8) :: inelastic ! inelastic cross section - - micro_xs(i_nuclide) % use_ptable = .true. - - associate (nuc => nuclides(i_nuclide), urr => nuclides(i_nuclide) % urr_data(i_temp)) - ! determine energy table - i_energy = 1 - do - if (E < urr % energy(i_energy + 1)) exit - i_energy = i_energy + 1 - end do - - ! determine interpolation factor on table - f = (E - urr % energy(i_energy)) / & - (urr % energy(i_energy + 1) - urr % energy(i_energy)) - - ! sample probability table using the cumulative distribution - - ! Random numbers for xs calculation are sampled from a separated stream. - ! This guarantees the randomness and, at the same time, makes sure we reuse - ! random number for the same nuclide at different temperatures, therefore - ! preserving correlation of temperature in probability tables. - call prn_set_stream(STREAM_URR_PTABLE) - r = future_prn(int(i_nuclide, 8)) - call prn_set_stream(STREAM_TRACKING) - - i_low = 1 - do - if (urr % prob(i_energy, URR_CUM_PROB, i_low) > r) exit - i_low = i_low + 1 - end do - i_up = 1 - do - if (urr % prob(i_energy + 1, URR_CUM_PROB, i_up) > r) exit - i_up = i_up + 1 - end do - - ! determine elastic, fission, and capture cross sections from probability - ! table - if (urr % interp == LINEAR_LINEAR) then - elastic = (ONE - f) * urr % prob(i_energy, URR_ELASTIC, i_low) + & - f * urr % prob(i_energy + 1, URR_ELASTIC, i_up) - fission = (ONE - f) * urr % prob(i_energy, URR_FISSION, i_low) + & - f * urr % prob(i_energy + 1, URR_FISSION, i_up) - capture = (ONE - f) * urr % prob(i_energy, URR_N_GAMMA, i_low) + & - f * urr % prob(i_energy + 1, URR_N_GAMMA, i_up) - elseif (urr % interp == LOG_LOG) then - ! Get logarithmic interpolation factor - f = log(E / urr % energy(i_energy)) / & - log(urr % energy(i_energy + 1) / urr % energy(i_energy)) - - ! Calculate elastic cross section/factor - elastic = ZERO - if (urr % prob(i_energy, URR_ELASTIC, i_low) > ZERO .and. & - urr % prob(i_energy + 1, URR_ELASTIC, i_up) > ZERO) then - elastic = exp((ONE - f) * log(urr % prob(i_energy, URR_ELASTIC, & - i_low)) + f * log(urr % prob(i_energy + 1, URR_ELASTIC, & - i_up))) - end if - - ! Calculate fission cross section/factor - fission = ZERO - if (urr % prob(i_energy, URR_FISSION, i_low) > ZERO .and. & - urr % prob(i_energy + 1, URR_FISSION, i_up) > ZERO) then - fission = exp((ONE - f) * log(urr % prob(i_energy, URR_FISSION, & - i_low)) + f * log(urr % prob(i_energy + 1, URR_FISSION, & - i_up))) - end if - - ! Calculate capture cross section/factor - capture = ZERO - if (urr % prob(i_energy, URR_N_GAMMA, i_low) > ZERO .and. & - urr % prob(i_energy + 1, URR_N_GAMMA, i_up) > ZERO) then - capture = exp((ONE - f) * log(urr % prob(i_energy, URR_N_GAMMA, & - i_low)) + f * log(urr % prob(i_energy + 1, URR_N_GAMMA, & - i_up))) - end if - end if - - ! Determine treatment of inelastic scattering - inelastic = ZERO - if (urr % inelastic_flag > 0) then - ! Get index on energy grid and interpolation factor - i_energy = micro_xs(i_nuclide) % index_grid - f = micro_xs(i_nuclide) % interp_factor - - ! Determine inelastic scattering cross section - associate (xs => nuc % reactions(nuc % urr_inelastic) % xs(i_temp)) - if (i_energy >= xs % threshold) then - inelastic = (ONE - f) * xs % value(i_energy - xs % threshold + 1) + & - f * xs % value(i_energy - xs % threshold + 2) - end if - end associate - end if - - ! Multiply by smooth cross-section if needed - if (urr % multiply_smooth) then - call calculate_elastic_xs(i_nuclide) - elastic = elastic * micro_xs(i_nuclide) % elastic - capture = capture * (micro_xs(i_nuclide) % absorption - & - micro_xs(i_nuclide) % fission) - fission = fission * micro_xs(i_nuclide) % fission - end if - - ! Check for negative values - if (elastic < ZERO) elastic = ZERO - if (fission < ZERO) fission = ZERO - if (capture < ZERO) capture = ZERO - - ! Set elastic, absorption, fission, and total cross sections. Note that the - ! total cross section is calculated as sum of partials rather than using the - ! table-provided value - micro_xs(i_nuclide) % elastic = elastic - micro_xs(i_nuclide) % absorption = capture + fission - micro_xs(i_nuclide) % fission = fission - micro_xs(i_nuclide) % total = elastic + inelastic + capture + fission - - ! Determine nu-fission cross section - if (nuc % fissionable) then - micro_xs(i_nuclide) % nu_fission = nuc % nu(E, EMISSION_TOTAL) * & - micro_xs(i_nuclide) % fission - end if - end associate - - end subroutine calculate_urr_xs - -!=============================================================================== -! CALCULATE_ELASTIC_XS precalculates the free atom elastic scattering cross -! section. Normally it is not needed until a collision actually occurs in a -! material. However, in the thermal and unresolved resonance regions, we have to -! calculate it early to adjust the total cross section correctly. -!=============================================================================== - - subroutine calculate_elastic_xs(i_nuclide) - integer, intent(in) :: i_nuclide - - integer :: i_temp - integer :: i_grid - real(8) :: f - - ! Get temperature index, grid index, and interpolation factor - i_temp = micro_xs(i_nuclide) % index_temp - i_grid = micro_xs(i_nuclide) % index_grid - f = micro_xs(i_nuclide) % interp_factor - - if (i_temp > 0) then - associate (xs => nuclides(i_nuclide) % reactions(1) % xs(i_temp) % value) - micro_xs(i_nuclide) % elastic = (ONE - f)*xs(i_grid) + f*xs(i_grid + 1) - end associate - else - ! For multipole, elastic is total - absorption - micro_xs(i_nuclide) % elastic = micro_xs(i_nuclide) % total - & - micro_xs(i_nuclide) % absorption - end if - end subroutine calculate_elastic_xs - -!=============================================================================== -! MULTIPOLE_EVAL evaluates the windowed multipole equations for cross -! sections in the resolved resonance regions -!=============================================================================== - - subroutine multipole_eval(multipole, E, sqrtkT, sig_t, sig_a, sig_f) - type(MultipoleArray), intent(in) :: multipole ! The windowed multipole - ! object to process. - real(8), intent(in) :: E ! The energy at which to - ! evaluate the cross section - real(8), intent(in) :: sqrtkT ! The temperature in the form - ! sqrt(kT), at which - ! to evaluate the XS. - real(8), intent(out) :: sig_t ! Total cross section - real(8), intent(out) :: sig_a ! Absorption cross section - real(8), intent(out) :: sig_f ! Fission cross section - complex(8) :: psi_chi ! The value of the psi-chi function for the - ! asymptotic form - complex(8) :: c_temp ! complex temporary variable - complex(8) :: w_val ! The faddeeva function evaluated at Z - complex(8) :: Z ! sqrt(atomic weight ratio / kT) * (sqrt(E) - pole) - complex(8) :: sig_t_factor(multipole % num_l) - real(8) :: broadened_polynomials(multipole % fit_order + 1) - real(8) :: sqrtE ! sqrt(E), eV - real(8) :: invE ! 1/E, eV - real(8) :: dopp ! sqrt(atomic weight ratio / kT) = 1 / (2 sqrt(xi)) - real(8) :: temp ! real temporary value - integer :: i_pole ! index of pole - integer :: i_poly ! index of curvefit - integer :: i_window ! index of window - integer :: startw ! window start pointer (for poles) - integer :: endw ! window end pointer - - ! ========================================================================== - ! Bookkeeping - - ! Define some frequently used variables. - sqrtE = sqrt(E) - invE = ONE / E - - ! Locate us. - i_window = floor((sqrtE - sqrt(multipole % start_E)) / multipole % spacing & - + ONE) - startw = multipole % w_start(i_window) - endw = multipole % w_end(i_window) - - ! Fill in factors. - if (startw <= endw) then - call compute_sig_t_factor(multipole, sqrtE, sig_t_factor) - end if - - ! Initialize the ouptut cross sections. - sig_t = ZERO - sig_a = ZERO - sig_f = ZERO - - ! ========================================================================== - ! Add the contribution from the curvefit polynomial. - - if (sqrtkT /= ZERO .and. multipole % broaden_poly(i_window) == 1) then - ! Broaden the curvefit. - dopp = multipole % sqrtAWR / sqrtkT - call broaden_wmp_polynomials(E, dopp, multipole % fit_order + 1, & - broadened_polynomials) - do i_poly = 1, multipole % fit_order+1 - sig_t = sig_t + multipole % curvefit(FIT_T, i_poly, i_window) & - * broadened_polynomials(i_poly) - sig_a = sig_a + multipole % curvefit(FIT_A, i_poly, i_window) & - * broadened_polynomials(i_poly) - if (multipole % fissionable) then - sig_f = sig_f + multipole % curvefit(FIT_F, i_poly, i_window) & - * broadened_polynomials(i_poly) - end if - end do - else ! Evaluate as if it were a polynomial - temp = invE - do i_poly = 1, multipole % fit_order+1 - sig_t = sig_t + multipole % curvefit(FIT_T, i_poly, i_window) * temp - sig_a = sig_a + multipole % curvefit(FIT_A, i_poly, i_window) * temp - if (multipole % fissionable) then - sig_f = sig_f + multipole % curvefit(FIT_F, i_poly, i_window) * temp - end if - temp = temp * sqrtE - end do - end if - - ! ========================================================================== - ! Add the contribution from the poles in this window. - - if (sqrtkT == ZERO) then - ! If at 0K, use asymptotic form. - do i_pole = startw, endw - psi_chi = -ONEI / (multipole % data(MP_EA, i_pole) - sqrtE) - c_temp = psi_chi / E - if (multipole % formalism == FORM_MLBW) then - sig_t = sig_t + real(multipole % data(MLBW_RT, i_pole) * c_temp * & - sig_t_factor(multipole % l_value(i_pole))) & - + real(multipole % data(MLBW_RX, i_pole) * c_temp) - sig_a = sig_a + real(multipole % data(MLBW_RA, i_pole) * c_temp) - if (multipole % fissionable) then - sig_f = sig_f + real(multipole % data(MLBW_RF, i_pole) * c_temp) - end if - else if (multipole % formalism == FORM_RM) then - sig_t = sig_t + real(multipole % data(RM_RT, i_pole) * c_temp * & - sig_t_factor(multipole % l_value(i_pole))) - sig_a = sig_a + real(multipole % data(RM_RA, i_pole) * c_temp) - if (multipole % fissionable) then - sig_f = sig_f + real(multipole % data(RM_RF, i_pole) * c_temp) - end if - end if - end do - else - ! At temperature, use Faddeeva function-based form. - dopp = multipole % sqrtAWR / sqrtkT - if (endw >= startw) then - do i_pole = startw, endw - Z = (sqrtE - multipole % data(MP_EA, i_pole)) * dopp - w_val = faddeeva(Z) * dopp * invE * SQRT_PI - if (multipole % formalism == FORM_MLBW) then - sig_t = sig_t + real((multipole % data(MLBW_RT, i_pole) * & - sig_t_factor(multipole % l_value(i_pole)) + & - multipole % data(MLBW_RX, i_pole)) * w_val) - sig_a = sig_a + real(multipole % data(MLBW_RA, i_pole) * w_val) - if (multipole % fissionable) then - sig_f = sig_f + real(multipole % data(MLBW_RF, i_pole) * w_val) - end if - else if (multipole % formalism == FORM_RM) then - sig_t = sig_t + real(multipole % data(RM_RT, i_pole) * w_val * & - sig_t_factor(multipole % l_value(i_pole))) - sig_a = sig_a + real(multipole % data(RM_RA, i_pole) * w_val) - if (multipole % fissionable) then - sig_f = sig_f + real(multipole % data(RM_RF, i_pole) * w_val) - end if - end if - end do - end if - end if - end subroutine multipole_eval - -!=============================================================================== -! MULTIPOLE_DERIV_EVAL evaluates the windowed multipole equations for the -! derivative of cross sections in the resolved resonance regions with respect to -! temperature. -!=============================================================================== - - subroutine multipole_deriv_eval(multipole, E, sqrtkT, sig_t, sig_a, sig_f) - type(MultipoleArray), intent(in) :: multipole ! The windowed multipole - ! object to process. - real(8), intent(in) :: E ! The energy at which to - ! evaluate the cross section - real(8), intent(in) :: sqrtkT ! The temperature in the form - ! sqrt(kT), at which to - ! evaluate the XS. - real(8), intent(out) :: sig_t ! Total cross section - real(8), intent(out) :: sig_a ! Absorption cross section - real(8), intent(out) :: sig_f ! Fission cross section - complex(8) :: w_val ! The faddeeva function evaluated at Z - complex(8) :: Z ! sqrt(atomic weight ratio / kT) * (sqrt(E) - pole) - complex(8) :: sig_t_factor(multipole % num_l) - real(8) :: sqrtE ! sqrt(E), eV - real(8) :: invE ! 1/E, eV - real(8) :: dopp ! sqrt(atomic weight ratio / kT) - integer :: i_pole ! index of pole - integer :: i_window ! index of window - integer :: startw ! window start pointer (for poles) - integer :: endw ! window end pointer - real(8) :: T - - ! ========================================================================== - ! Bookkeeping - - ! Define some frequently used variables. - sqrtE = sqrt(E) - invE = ONE / E - T = sqrtkT**2 / K_BOLTZMANN - - if (sqrtkT == ZERO) call fatal_error("Windowed multipole temperature & - &derivatives are not implemented for 0 Kelvin cross sections.") - - ! Locate us - i_window = floor((sqrtE - sqrt(multipole % start_E)) / multipole % spacing & - + ONE) - startw = multipole % w_start(i_window) - endw = multipole % w_end(i_window) - - ! Fill in factors. - if (startw <= endw) then - call compute_sig_t_factor(multipole, sqrtE, sig_t_factor) - end if - - ! Initialize the ouptut cross sections. - sig_t = ZERO - sig_a = ZERO - sig_f = ZERO - - ! TODO Polynomials: Some of the curvefit polynomials Doppler broaden so - ! rigorously we should be computing the derivative of those. But in - ! practice, those derivatives are only large at very low energy and they - ! have no effect on reactor calculations. - - ! ========================================================================== - ! Add the contribution from the poles in this window. - - dopp = multipole % sqrtAWR / sqrtkT - if (endw >= startw) then - do i_pole = startw, endw - Z = (sqrtE - multipole % data(MP_EA, i_pole)) * dopp - w_val = -invE * SQRT_PI * HALF * w_derivative(Z, 2) - if (multipole % formalism == FORM_MLBW) then - sig_t = sig_t + real((multipole % data(MLBW_RT, i_pole) * & - sig_t_factor(multipole%l_value(i_pole)) + & - multipole % data(MLBW_RX, i_pole)) * w_val) - sig_a = sig_a + real(multipole % data(MLBW_RA, i_pole) * w_val) - if (multipole % fissionable) then - sig_f = sig_f + real(multipole % data(MLBW_RF, i_pole) * w_val) - end if - else if (multipole % formalism == FORM_RM) then - sig_t = sig_t + real(multipole % data(RM_RT, i_pole) * w_val * & - sig_t_factor(multipole % l_value(i_pole))) - sig_a = sig_a + real(multipole % data(RM_RA, i_pole) * w_val) - if (multipole % fissionable) then - sig_f = sig_f + real(multipole % data(RM_RF, i_pole) * w_val) - end if - end if - end do - sig_t = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sig_t - sig_a = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sig_a - sig_f = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sig_f - end if - end subroutine multipole_deriv_eval - -!=============================================================================== -! COMPUTE_SIG_T_FACTOR calculates the sig_t_factor, a factor inside of the sig_t -! equation not present in the sig_a and sig_f equations. -!=============================================================================== - - subroutine compute_sig_t_factor(multipole, sqrtE, sig_t_factor) - type(MultipoleArray), intent(in) :: multipole - real(8), intent(in) :: sqrtE - complex(8), intent(out) :: sig_t_factor(multipole % num_l) - - integer :: iL - real(8) :: twophi(multipole % num_l) - real(8) :: arg - - do iL = 1, multipole % num_l - twophi(iL) = multipole % pseudo_k0RS(iL) * sqrtE - if (iL == 2) then - twophi(iL) = twophi(iL) - atan(twophi(iL)) - else if (iL == 3) then - arg = 3.0_8 * twophi(iL) / (3.0_8 - twophi(iL)**2) - twophi(iL) = twophi(iL) - atan(arg) - else if (iL == 4) then - arg = twophi(iL) * (15.0_8 - twophi(iL)**2) & - / (15.0_8 - 6.0_8 * twophi(iL)**2) - twophi(iL) = twophi(iL) - atan(arg) - end if - end do - - twophi = 2.0_8 * twophi - sig_t_factor = cmplx(cos(twophi), -sin(twophi), KIND=8) - end subroutine compute_sig_t_factor - -!=============================================================================== -! 0K_ELASTIC_XS determines the microscopic 0K elastic cross section -! for a given nuclide at the trial relative energy used in resonance scattering -!=============================================================================== - - pure function elastic_xs_0K(E, nuc) result(xs_out) - real(8), intent(in) :: E ! trial energy - type(Nuclide), intent(in) :: nuc ! target nuclide at temperature - real(8) :: xs_out ! 0K xs at trial energy - - integer :: i_grid ! index on nuclide energy grid - integer :: n_grid - real(8) :: f ! interp factor on nuclide energy grid - - ! Determine index on nuclide energy grid - n_grid = size(nuc % energy_0K) - if (E < nuc % energy_0K(1)) then - i_grid = 1 - elseif (E > nuc % energy_0K(n_grid)) then - i_grid = n_grid - 1 - else - i_grid = binary_search(nuc % energy_0K, n_grid, E) - end if - - ! check for rare case where two energy points are the same - if (nuc % energy_0K(i_grid) == nuc % energy_0K(i_grid+1)) then - i_grid = i_grid + 1 - end if - - ! calculate interpolation factor - f = (E - nuc % energy_0K(i_grid)) & - & / (nuc % energy_0K(i_grid + 1) - nuc % energy_0K(i_grid)) - - ! Calculate microscopic nuclide elastic cross section - xs_out = (ONE - f) * nuc % elastic_0K(i_grid) & - & + f * nuc % elastic_0K(i_grid + 1) - - end function elastic_xs_0K - -end module cross_section diff --git a/src/material_header.F90 b/src/material_header.F90 index a3ce32db3..ae15ec570 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -7,6 +7,7 @@ module material_header use error use nuclide_header use sab_header + use simulation_header, only: log_spacing use stl_vector, only: VectorReal, VectorInt use string, only: to_str @@ -64,6 +65,7 @@ module material_header procedure :: set_density => material_set_density procedure :: init_nuclide_index => material_init_nuclide_index procedure :: assign_sab_tables => material_assign_sab_tables + procedure :: calculate_xs => material_calculate_xs end type Material integer(C_INT32_T), public, bind(C) :: n_materials ! # of materials @@ -248,6 +250,115 @@ contains if (allocated(this % names)) deallocate(this % names) end subroutine material_assign_sab_tables +!=============================================================================== +! MATERIAL_CALCULATE_XS determines the macroscopic cross sections for the material the +! particle is currently traveling through. +!=============================================================================== + + subroutine material_calculate_xs(this, E, sqrtkT, micro_xs, nuclides, & + material_xs) + class(Material), intent(in) :: this + real(8), intent(in) :: E ! Particle energy + real(8), intent(in) :: sqrtkT ! Last temperature sampled + type(Nuclide), allocatable, intent(in) :: nuclides(:) + type(NuclideMicroXS), allocatable, intent(inout) :: micro_xs(:) ! Cache for each nuclide + type(MaterialMacroXS), intent(inout) :: material_xs ! Cache for current material + + integer :: i ! loop index over nuclides + integer :: i_nuclide ! index into nuclides array + integer :: i_sab ! index into sab_tables array + integer :: j ! index in this % i_sab_nuclides + integer :: i_grid ! index into logarithmic mapping array or material + ! union grid + real(8) :: atom_density ! atom density of a nuclide + real(8) :: sab_frac ! fraction of atoms affected by S(a,b) + logical :: check_sab ! should we check for S(a,b) table? + + ! Set all material macroscopic cross sections to zero + material_xs % total = ZERO + material_xs % absorption = ZERO + material_xs % fission = ZERO + material_xs % nu_fission = ZERO + + ! Find energy index on energy grid + i_grid = int(log(E/energy_min_neutron)/log_spacing) + + ! Determine if this material has S(a,b) tables + check_sab = (this % n_sab > 0) + + ! Initialize position in i_sab_nuclides + j = 1 + + ! Add contribution from each nuclide in material + do i = 1, this % n_nuclides + ! ====================================================================== + ! CHECK FOR S(A,B) TABLE + + i_sab = 0 + sab_frac = ZERO + + ! Check if this nuclide matches one of the S(a,b) tables specified. + ! This relies on i_sab_nuclides being in sorted order + if (check_sab) then + if (i == this % i_sab_nuclides(j)) then + ! Get index in sab_tables + i_sab = this % i_sab_tables(j) + sab_frac = this % sab_fracs(j) + + ! If particle energy is greater than the highest energy for the + ! S(a,b) table, then don't use the S(a,b) table + if (E > sab_tables(i_sab) % data(1) % threshold_inelastic) then + i_sab = 0 + end if + + ! Increment position in i_sab_nuclides + j = j + 1 + + ! Don't check for S(a,b) tables if there are no more left + if (j > size(this % i_sab_tables)) check_sab = .false. + end if + end if + + ! ====================================================================== + ! CALCULATE MICROSCOPIC CROSS SECTION + + ! Determine microscopic cross sections for this nuclide + i_nuclide = this % nuclide(i) + + ! Calculate microscopic cross section for this nuclide + if (E /= micro_xs(i_nuclide) % last_E & + .or. sqrtkT /= micro_xs(i_nuclide) % last_sqrtkT & + .or. i_sab /= micro_xs(i_nuclide) % index_sab & + .or. sab_frac /= micro_xs(i_nuclide) % sab_frac) then + call nuclides(i_nuclide) % calculate_xs(i_sab, E, i_grid, & + sqrtkT, sab_frac, i_nuclide, micro_xs(i_nuclide)) + end if + + ! ====================================================================== + ! ADD TO MACROSCOPIC CROSS SECTION + + ! Copy atom density of nuclide in material + atom_density = this % atom_density(i) + + ! Add contributions to material macroscopic total cross section + material_xs % total = material_xs % total + & + atom_density * micro_xs(i_nuclide) % total + + ! Add contributions to material macroscopic absorption cross section + material_xs % absorption = material_xs % absorption + & + atom_density * micro_xs(i_nuclide) % absorption + + ! Add contributions to material macroscopic fission cross section + material_xs % fission = material_xs % fission + & + atom_density * micro_xs(i_nuclide) % fission + + ! Add contributions to material macroscopic nu-fission cross section + material_xs % nu_fission = material_xs % nu_fission + & + atom_density * micro_xs(i_nuclide) % nu_fission + end do + + end subroutine material_calculate_xs + !=============================================================================== ! FREE_MEMORY_MATERIAL deallocates global arrays defined in this module !=============================================================================== diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index a99939f4c..6eabefae3 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -163,10 +163,11 @@ module mgxs_header real(8), intent(inout) :: wgt ! Particle weight end subroutine mgxs_sample_scatter_ - subroutine mgxs_calculate_xs_(this, gin, uvw, xs) + subroutine mgxs_calculate_xs_(this, gin, sqrtkT, uvw, xs) import Mgxs, MaterialMacroXS - class(Mgxs), intent(in) :: this + class(Mgxs), intent(inout) :: this integer, intent(in) :: gin ! Incoming neutron group + real(8), intent(in) :: sqrtkT ! Material temperature real(8), intent(in) :: uvw(3) ! Incoming neutron direction type(MaterialMacroXS), intent(inout) :: xs ! Resultant Mgxs Data end subroutine mgxs_calculate_xs_ @@ -3481,12 +3482,16 @@ contains ! for the material the particle is currently traveling through. !=============================================================================== - subroutine mgxsiso_calculate_xs(this, gin, uvw, xs) - class(MgxsIso), intent(in) :: this + subroutine mgxsiso_calculate_xs(this, gin, sqrtkT, uvw, xs) + class(MgxsIso), intent(inout) :: this integer, intent(in) :: gin ! Incoming neutron group + real(8), intent(in) :: sqrtkT ! Material temperature real(8), intent(in) :: uvw(3) ! Incoming neutron direction type(MaterialMacroXS), intent(inout) :: xs ! Resultant Mgxs Data + ! Update the temperature index + call this % find_temperature(sqrtkT) + xs % total = this % xs(this % index_temp) % total(gin) xs % absorption = this % xs(this % index_temp) % absorption(gin) xs % nu_fission = & @@ -3495,15 +3500,19 @@ contains end subroutine mgxsiso_calculate_xs - subroutine mgxsang_calculate_xs(this, gin, uvw, xs) - class(MgxsAngle), intent(in) :: this + subroutine mgxsang_calculate_xs(this, gin, sqrtkT, uvw, xs) + class(MgxsAngle), intent(inout) :: this integer, intent(in) :: gin ! Incoming neutron group + real(8), intent(in) :: sqrtkT ! Material temperature real(8), intent(in) :: uvw(3) ! Incoming neutron direction type(MaterialMacroXS), intent(inout) :: xs ! Resultant Mgxs Data integer :: iazi, ipol + ! Update the temperature and angle indices + call this % find_temperature(sqrtkT) call find_angle(this % polar, this % azimuthal, uvw, iazi, ipol) + xs % total = this % xs(this % index_temp) % & total(gin, iazi, ipol) xs % absorption = this % xs(this % index_temp) % & diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 1ca584914..994961025 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -3,26 +3,33 @@ module nuclide_header use, intrinsic :: ISO_FORTRAN_ENV use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T, HSIZE_T, SIZE_T + use hdf5, only: HID_T, HSIZE_T, SIZE_T - use algorithm, only: sort, find + use algorithm, only: sort, find, binary_search use constants - use dict_header, only: DictIntInt, DictCharInt - use endf, only: reaction_name, is_fission, is_disappearance - use endf_header, only: Function1D, Polynomial, Tabulated1D + use dict_header, only: DictIntInt, DictCharInt + use endf, only: reaction_name, is_fission, is_disappearance + use endf_header, only: Function1D, Polynomial, Tabulated1D use error use hdf5_interface - use list_header, only: ListInt - use math, only: evaluate_legendre + use list_header, only: ListInt + use math, only: faddeeva, w_derivative, & + broaden_wmp_polynomials + use multipole_header, only: FORM_RM, FORM_MLBW, MP_EA, RM_RT, RM_RA, & + RM_RF, MLBW_RT, MLBW_RX, MLBW_RA, MLBW_RF, & + FIT_T, FIT_A, FIT_F, MultipoleArray use message_passing - use multipole_header, only: MultipoleArray - use product_header, only: AngleEnergyContainer - use reaction_header, only: Reaction + use multipole_header, only: MultipoleArray + use product_header, only: AngleEnergyContainer + use random_lcg, only: prn, future_prn, prn_set_stream + use reaction_header, only: Reaction + use sab_header, only: SAlphaBeta, sab_tables use secondary_uncorrelated, only: UncorrelatedAngleEnergy use settings - use stl_vector, only: VectorInt, VectorReal + use stl_vector, only: VectorInt, VectorReal use string - use urr_header, only: UrrData + use simulation_header, only: need_depletion_rx + use urr_header, only: UrrData implicit none @@ -108,6 +115,8 @@ module nuclide_header procedure :: init_grid => nuclide_init_grid procedure :: nu => nuclide_nu procedure, private :: create_derived => nuclide_create_derived + procedure :: calculate_xs => nuclide_calculate_xs + procedure :: calculate_elastic_xs => nuclide_calculate_elastic_xs end type Nuclide !=============================================================================== @@ -800,6 +809,737 @@ contains end subroutine nuclide_init_grid +!=============================================================================== +! NUCLIDE_CALCULATE_XS determines microscopic cross sections for the nuclide +! at the energy of the given particle +!=============================================================================== + + subroutine nuclide_calculate_xs(this, i_sab, E, i_log_union, sqrtkT, & + sab_frac, i_nuclide, micro_xs) + class(Nuclide), intent(in) :: this ! Nuclide object + integer, intent(in) :: i_sab ! index into sab_tables array + real(8), intent(in) :: E ! energy + integer, intent(in) :: i_log_union ! index into logarithmic mapping array or + ! material union energy grid + real(8), intent(in) :: sqrtkT ! square root of kT, material dependent + real(8), intent(in) :: sab_frac ! fraction of atoms affected by S(a,b) + !!!TODO: i_nuclide is only needed to keep the URR prn stream consistent + !!! to ensure tests pass; this can be removed when this requirement can be + !!! relaxed + integer, intent(in) :: i_nuclide ! Index of this in the nuclides array + type(NuclideMicroXS), intent(inout) :: micro_xs ! Cross section cache + + logical :: use_mp ! true if XS can be calculated with windowed multipole + integer :: i_temp ! index for temperature + integer :: i_grid ! index on nuclide energy grid + integer :: i_low ! lower logarithmic mapping index + integer :: i_high ! upper logarithmic mapping index + integer :: i_rxn ! reaction index + integer :: j ! index in DEPLETION_RX + real(8) :: f ! interp factor on nuclide energy grid + real(8) :: kT ! temperature in eV + real(8) :: sig_t, sig_a, sig_f ! Intermediate multipole variables + + ! Initialize cached cross sections to zero + micro_xs % elastic = CACHE_INVALID + micro_xs % thermal = ZERO + micro_xs % thermal_elastic = ZERO + + ! Check to see if there is multipole data present at this energy + use_mp = .false. + if (this % mp_present) then + if (E >= this % multipole % start_E .and. & + E <= this % multipole % end_E) then + use_mp = .true. + end if + end if + + ! Evaluate multipole or interpolate + if (use_mp) then + ! Call multipole kernel + call multipole_eval(this % multipole, E, sqrtkT, sig_t, sig_a, sig_f) + + micro_xs % total = sig_t + micro_xs % absorption = sig_a + micro_xs % fission = sig_f + + if (this % fissionable) then + micro_xs % nu_fission = sig_f * this % nu(E, EMISSION_TOTAL) + else + micro_xs % nu_fission = ZERO + end if + + if (need_depletion_rx) then + ! Initialize all reaction cross sections to zero + micro_xs % reaction(:) = ZERO + + ! Only non-zero reaction is (n,gamma) + micro_xs % reaction(4) = sig_a - sig_f + end if + + ! Ensure these values are set + ! Note, the only time either is used is in one of 4 places: + ! 1. physics.F90 - scatter - For inelastic scatter. + ! 2. physics.F90 - sample_fission - For partial fissions. + ! 3. tally.F90 - score_general - For tallying on MTxxx reactions. + ! 4. cross_section.F90 - calculate_urr_xs - For unresolved purposes. + ! It is worth noting that none of these occur in the resolved + ! resonance range, so the value here does not matter. index_temp is + ! set to -1 to force a segfault in case a developer messes up and tries + ! to use it with multipole. + micro_xs % index_temp = -1 + micro_xs % index_grid = 0 + micro_xs % interp_factor = ZERO + + else + ! Find the appropriate temperature index. + kT = sqrtkT**2 + select case (temperature_method) + case (TEMPERATURE_NEAREST) + i_temp = minloc(abs(this % kTs - kT), dim=1) + + case (TEMPERATURE_INTERPOLATION) + ! Find temperatures that bound the actual temperature + do i_temp = 1, size(this % kTs) - 1 + if (this % kTs(i_temp) <= kT .and. kT < this % kTs(i_temp + 1)) exit + end do + + ! Randomly sample between temperature i and i+1 + f = (kT - this % kTs(i_temp)) / & + (this % kTs(i_temp + 1) - this % kTs(i_temp)) + if (f > prn()) i_temp = i_temp + 1 + end select + + associate (grid => this % grid(i_temp), xs => this % xs(i_temp)) + ! Determine the energy grid index using a logarithmic mapping to + ! reduce the energy range over which a binary search needs to be + ! performed + + if (E < grid % energy(1)) then + i_grid = 1 + elseif (E > grid % energy(size(grid % energy))) then + i_grid = size(grid % energy) - 1 + else + ! Determine bounding indices based on which equal log-spaced + ! interval the energy is in + i_low = grid % grid_index(i_log_union) + i_high = grid % grid_index(i_log_union + 1) + 1 + + ! Perform binary search over reduced range + i_grid = binary_search(grid % energy(i_low:i_high), & + i_high - i_low + 1, E) + i_low - 1 + end if + + ! check for rare case where two energy points are the same + if (grid % energy(i_grid) == grid % energy(i_grid + 1)) & + i_grid = i_grid + 1 + + ! calculate interpolation factor + f = (E - grid % energy(i_grid)) / & + (grid % energy(i_grid + 1) - grid % energy(i_grid)) + + micro_xs % index_temp = i_temp + micro_xs % index_grid = i_grid + micro_xs % interp_factor = f + + ! Calculate microscopic nuclide total cross section + micro_xs % total = (ONE - f) * xs % value(XS_TOTAL,i_grid) & + + f * xs % value(XS_TOTAL,i_grid + 1) + + ! Calculate microscopic nuclide absorption cross section + micro_xs % absorption = (ONE - f) * xs % value(XS_ABSORPTION, & + i_grid) + f * xs % value(XS_ABSORPTION,i_grid + 1) + + if (this % fissionable) then + ! Calculate microscopic nuclide total cross section + micro_xs % fission = (ONE - f) * xs % value(XS_FISSION,i_grid) & + + f * xs % value(XS_FISSION,i_grid + 1) + + ! Calculate microscopic nuclide nu-fission cross section + micro_xs % nu_fission = (ONE - f) * xs % value(XS_NU_FISSION, & + i_grid) + f * xs % value(XS_NU_FISSION,i_grid + 1) + else + micro_xs % fission = ZERO + micro_xs % nu_fission = ZERO + end if + end associate + + ! Depletion-related reactions + if (need_depletion_rx) then + do j = 1, 6 + ! Initialize reaction xs to zero + micro_xs % reaction(j) = ZERO + + ! If reaction is present and energy is greater than threshold, set + ! the reaction xs appropriately + i_rxn = this % reaction_index(DEPLETION_RX(j)) + if (i_rxn > 0) then + associate (xs => this % reactions(i_rxn) % xs(i_temp)) + if (i_grid >= xs % threshold) then + micro_xs % reaction(j) = (ONE - f) * & + xs % value(i_grid - xs % threshold + 1) + & + f * xs % value(i_grid - xs % threshold + 2) + end if + end associate + end if + end do + end if + + end if + + ! Initialize sab treatment to false + micro_xs % index_sab = NONE + micro_xs % sab_frac = ZERO + + ! Initialize URR probability table treatment to false + micro_xs % use_ptable = .false. + + ! If there is S(a,b) data for this nuclide, we need to set the sab_scatter + ! and sab_elastic cross sections and correct the total and elastic cross + ! sections. + + if (i_sab > 0) then + call calculate_sab_xs(this, i_sab, E, sqrtkT, sab_frac, micro_xs) + end if + + ! If the particle is in the unresolved resonance range and there are + ! probability tables, we need to determine cross sections from the table + + if (urr_ptables_on .and. this % urr_present .and. .not. use_mp) then + if (E > this % urr_data(i_temp) % energy(1) .and. E < this % & + urr_data(i_temp) % energy(this % urr_data(i_temp) % n_energy)) then + call calculate_urr_xs(this, i_temp, E, i_nuclide, micro_xs) + end if + end if + + micro_xs % last_E = E + micro_xs % last_sqrtkT = sqrtkT + + end subroutine nuclide_calculate_xs + +!=============================================================================== +! NUCLIDE_CALCULATE_ELASTIC_XS precalculates the free atom elastic scattering +! cross section. Normally it is not needed until a collision actually occurs in +! a material. However, in the thermal and unresolved resonance regions, we have +! to calculate it early to adjust the total cross section correctly. +!=============================================================================== + + subroutine nuclide_calculate_elastic_xs(this, micro_xs) + class(Nuclide), intent(in) :: this + type(NuclideMicroXS), intent(inout) :: micro_xs ! Cross section cache + + integer :: i_temp + integer :: i_grid + real(8) :: f + + ! Get temperature index, grid index, and interpolation factor + i_temp = micro_xs % index_temp + i_grid = micro_xs % index_grid + f = micro_xs % interp_factor + + if (i_temp > 0) then + associate (xs => this % reactions(1) % xs(i_temp) % value) + micro_xs % elastic = (ONE - f) * xs(i_grid) + f * xs(i_grid + 1) + end associate + else + ! For multipole, elastic is total - absorption + micro_xs % elastic = micro_xs % total - micro_xs % absorption + end if + end subroutine nuclide_calculate_elastic_xs + +!=============================================================================== +! CALCULATE_SAB_XS determines the elastic and inelastic scattering +! cross-sections in the thermal energy range. These cross sections replace a +! fraction of whatever data were taken from the normal Nuclide table. +!=============================================================================== + + subroutine calculate_sab_xs(this, i_sab, E, sqrtkT, sab_frac, micro_xs) + class(Nuclide), intent(in) :: this ! Nuclide object + integer, intent(in) :: i_sab ! index into sab_tables array + real(8), intent(in) :: E ! energy + real(8), intent(in) :: sqrtkT ! temperature + real(8), intent(in) :: sab_frac ! fraction of atoms affected by S(a,b) + type(NuclideMicroXS), intent(inout) :: micro_xs ! Cross section cache + + integer :: i_temp ! temperature index + real(8) :: inelastic ! S(a,b) inelastic cross section + real(8) :: elastic ! S(a,b) elastic cross section + + ! Set flag that S(a,b) treatment should be used for scattering + micro_xs % index_sab = i_sab + + ! Calculate the S(a,b) cross section + call sab_tables(i_sab) % calculate_xs(E, sqrtkT, i_temp, elastic, inelastic) + + ! Store the S(a,b) cross sections. + micro_xs % thermal = sab_frac * (elastic + inelastic) + micro_xs % thermal_elastic = sab_frac * elastic + + ! Calculate free atom elastic cross section + call this % calculate_elastic_xs(micro_xs) + + ! Correct total and elastic cross sections + micro_xs % total = micro_xs % total + micro_xs % thermal - & + sab_frac * micro_xs % elastic + micro_xs % elastic = micro_xs % thermal + (ONE - sab_frac) * & + micro_xs % elastic + + ! Save temperature index and thermal fraction + micro_xs % index_temp_sab = i_temp + micro_xs % sab_frac = sab_frac + + end subroutine calculate_sab_xs + +!=============================================================================== +! MULTIPOLE_EVAL evaluates the windowed multipole equations for cross +! sections in the resolved resonance regions +!=============================================================================== + + subroutine multipole_eval(multipole, E, sqrtkT, sig_t, sig_a, sig_f) + type(MultipoleArray), intent(in) :: multipole ! The windowed multipole + ! object to process. + real(8), intent(in) :: E ! The energy at which to + ! evaluate the cross section + real(8), intent(in) :: sqrtkT ! The temperature in the form + ! sqrt(kT), at which + ! to evaluate the XS. + real(8), intent(out) :: sig_t ! Total cross section + real(8), intent(out) :: sig_a ! Absorption cross section + real(8), intent(out) :: sig_f ! Fission cross section + complex(8) :: psi_chi ! The value of the psi-chi function for the + ! asymptotic form + complex(8) :: c_temp ! complex temporary variable + complex(8) :: w_val ! The faddeeva function evaluated at Z + complex(8) :: Z ! sqrt(atomic weight ratio / kT) * (sqrt(E) - pole) + complex(8) :: sig_t_factor(multipole % num_l) + real(8) :: broadened_polynomials(multipole % fit_order + 1) + real(8) :: sqrtE ! sqrt(E), eV + real(8) :: invE ! 1/E, eV + real(8) :: dopp ! sqrt(atomic weight ratio / kT) = 1 / (2 sqrt(xi)) + real(8) :: temp ! real temporary value + integer :: i_pole ! index of pole + integer :: i_poly ! index of curvefit + integer :: i_window ! index of window + integer :: startw ! window start pointer (for poles) + integer :: endw ! window end pointer + + ! ========================================================================== + ! Bookkeeping + + ! Define some frequently used variables. + sqrtE = sqrt(E) + invE = ONE / E + + ! Locate us. + i_window = floor((sqrtE - sqrt(multipole % start_E)) / multipole % spacing & + + ONE) + startw = multipole % w_start(i_window) + endw = multipole % w_end(i_window) + + ! Fill in factors. + if (startw <= endw) then + call compute_sig_t_factor(multipole, sqrtE, sig_t_factor) + end if + + ! Initialize the ouptut cross sections. + sig_t = ZERO + sig_a = ZERO + sig_f = ZERO + + ! ========================================================================== + ! Add the contribution from the curvefit polynomial. + + if (sqrtkT /= ZERO .and. multipole % broaden_poly(i_window) == 1) then + ! Broaden the curvefit. + dopp = multipole % sqrtAWR / sqrtkT + call broaden_wmp_polynomials(E, dopp, multipole % fit_order + 1, & + broadened_polynomials) + do i_poly = 1, multipole % fit_order+1 + sig_t = sig_t + multipole % curvefit(FIT_T, i_poly, i_window) & + * broadened_polynomials(i_poly) + sig_a = sig_a + multipole % curvefit(FIT_A, i_poly, i_window) & + * broadened_polynomials(i_poly) + if (multipole % fissionable) then + sig_f = sig_f + multipole % curvefit(FIT_F, i_poly, i_window) & + * broadened_polynomials(i_poly) + end if + end do + else ! Evaluate as if it were a polynomial + temp = invE + do i_poly = 1, multipole % fit_order+1 + sig_t = sig_t + multipole % curvefit(FIT_T, i_poly, i_window) * temp + sig_a = sig_a + multipole % curvefit(FIT_A, i_poly, i_window) * temp + if (multipole % fissionable) then + sig_f = sig_f + multipole % curvefit(FIT_F, i_poly, i_window) * temp + end if + temp = temp * sqrtE + end do + end if + + ! ========================================================================== + ! Add the contribution from the poles in this window. + + if (sqrtkT == ZERO) then + ! If at 0K, use asymptotic form. + do i_pole = startw, endw + psi_chi = -ONEI / (multipole % data(MP_EA, i_pole) - sqrtE) + c_temp = psi_chi / E + if (multipole % formalism == FORM_MLBW) then + sig_t = sig_t + real(multipole % data(MLBW_RT, i_pole) * c_temp * & + sig_t_factor(multipole % l_value(i_pole))) & + + real(multipole % data(MLBW_RX, i_pole) * c_temp) + sig_a = sig_a + real(multipole % data(MLBW_RA, i_pole) * c_temp) + if (multipole % fissionable) then + sig_f = sig_f + real(multipole % data(MLBW_RF, i_pole) * c_temp) + end if + else if (multipole % formalism == FORM_RM) then + sig_t = sig_t + real(multipole % data(RM_RT, i_pole) * c_temp * & + sig_t_factor(multipole % l_value(i_pole))) + sig_a = sig_a + real(multipole % data(RM_RA, i_pole) * c_temp) + if (multipole % fissionable) then + sig_f = sig_f + real(multipole % data(RM_RF, i_pole) * c_temp) + end if + end if + end do + else + ! At temperature, use Faddeeva function-based form. + dopp = multipole % sqrtAWR / sqrtkT + if (endw >= startw) then + do i_pole = startw, endw + Z = (sqrtE - multipole % data(MP_EA, i_pole)) * dopp + w_val = faddeeva(Z) * dopp * invE * SQRT_PI + if (multipole % formalism == FORM_MLBW) then + sig_t = sig_t + real((multipole % data(MLBW_RT, i_pole) * & + sig_t_factor(multipole % l_value(i_pole)) + & + multipole % data(MLBW_RX, i_pole)) * w_val) + sig_a = sig_a + real(multipole % data(MLBW_RA, i_pole) * w_val) + if (multipole % fissionable) then + sig_f = sig_f + real(multipole % data(MLBW_RF, i_pole) * w_val) + end if + else if (multipole % formalism == FORM_RM) then + sig_t = sig_t + real(multipole % data(RM_RT, i_pole) * w_val * & + sig_t_factor(multipole % l_value(i_pole))) + sig_a = sig_a + real(multipole % data(RM_RA, i_pole) * w_val) + if (multipole % fissionable) then + sig_f = sig_f + real(multipole % data(RM_RF, i_pole) * w_val) + end if + end if + end do + end if + end if + end subroutine multipole_eval + +!=============================================================================== +! MULTIPOLE_DERIV_EVAL evaluates the windowed multipole equations for the +! derivative of cross sections in the resolved resonance regions with respect to +! temperature. +!=============================================================================== + + subroutine multipole_deriv_eval(multipole, E, sqrtkT, sig_t, sig_a, sig_f) + type(MultipoleArray), intent(in) :: multipole ! The windowed multipole + ! object to process. + real(8), intent(in) :: E ! The energy at which to + ! evaluate the cross section + real(8), intent(in) :: sqrtkT ! The temperature in the form + ! sqrt(kT), at which to + ! evaluate the XS. + real(8), intent(out) :: sig_t ! Total cross section + real(8), intent(out) :: sig_a ! Absorption cross section + real(8), intent(out) :: sig_f ! Fission cross section + complex(8) :: w_val ! The faddeeva function evaluated at Z + complex(8) :: Z ! sqrt(atomic weight ratio / kT) * (sqrt(E) - pole) + complex(8) :: sig_t_factor(multipole % num_l) + real(8) :: sqrtE ! sqrt(E), eV + real(8) :: invE ! 1/E, eV + real(8) :: dopp ! sqrt(atomic weight ratio / kT) + integer :: i_pole ! index of pole + integer :: i_window ! index of window + integer :: startw ! window start pointer (for poles) + integer :: endw ! window end pointer + real(8) :: T + + ! ========================================================================== + ! Bookkeeping + + ! Define some frequently used variables. + sqrtE = sqrt(E) + invE = ONE / E + T = sqrtkT**2 / K_BOLTZMANN + + if (sqrtkT == ZERO) call fatal_error("Windowed multipole temperature & + &derivatives are not implemented for 0 Kelvin cross sections.") + + ! Locate us + i_window = floor((sqrtE - sqrt(multipole % start_E)) / multipole % spacing & + + ONE) + startw = multipole % w_start(i_window) + endw = multipole % w_end(i_window) + + ! Fill in factors. + if (startw <= endw) then + call compute_sig_t_factor(multipole, sqrtE, sig_t_factor) + end if + + ! Initialize the ouptut cross sections. + sig_t = ZERO + sig_a = ZERO + sig_f = ZERO + + ! TODO Polynomials: Some of the curvefit polynomials Doppler broaden so + ! rigorously we should be computing the derivative of those. But in + ! practice, those derivatives are only large at very low energy and they + ! have no effect on reactor calculations. + + ! ========================================================================== + ! Add the contribution from the poles in this window. + + dopp = multipole % sqrtAWR / sqrtkT + if (endw >= startw) then + do i_pole = startw, endw + Z = (sqrtE - multipole % data(MP_EA, i_pole)) * dopp + w_val = -invE * SQRT_PI * HALF * w_derivative(Z, 2) + if (multipole % formalism == FORM_MLBW) then + sig_t = sig_t + real((multipole % data(MLBW_RT, i_pole) * & + sig_t_factor(multipole%l_value(i_pole)) + & + multipole % data(MLBW_RX, i_pole)) * w_val) + sig_a = sig_a + real(multipole % data(MLBW_RA, i_pole) * w_val) + if (multipole % fissionable) then + sig_f = sig_f + real(multipole % data(MLBW_RF, i_pole) * w_val) + end if + else if (multipole % formalism == FORM_RM) then + sig_t = sig_t + real(multipole % data(RM_RT, i_pole) * w_val * & + sig_t_factor(multipole % l_value(i_pole))) + sig_a = sig_a + real(multipole % data(RM_RA, i_pole) * w_val) + if (multipole % fissionable) then + sig_f = sig_f + real(multipole % data(RM_RF, i_pole) * w_val) + end if + end if + end do + sig_t = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sig_t + sig_a = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sig_a + sig_f = -HALF*multipole % sqrtAWR / sqrt(K_BOLTZMANN) * T**(-1.5) * sig_f + end if + end subroutine multipole_deriv_eval + +!=============================================================================== +! COMPUTE_SIG_T_FACTOR calculates the sig_t_factor, a factor inside of the sig_t +! equation not present in the sig_a and sig_f equations. +!=============================================================================== + + subroutine compute_sig_t_factor(multipole, sqrtE, sig_t_factor) + type(MultipoleArray), intent(in) :: multipole + real(8), intent(in) :: sqrtE + complex(8), intent(out) :: sig_t_factor(multipole % num_l) + + integer :: iL + real(8) :: twophi(multipole % num_l) + real(8) :: arg + + do iL = 1, multipole % num_l + twophi(iL) = multipole % pseudo_k0RS(iL) * sqrtE + if (iL == 2) then + twophi(iL) = twophi(iL) - atan(twophi(iL)) + else if (iL == 3) then + arg = 3.0_8 * twophi(iL) / (3.0_8 - twophi(iL)**2) + twophi(iL) = twophi(iL) - atan(arg) + else if (iL == 4) then + arg = twophi(iL) * (15.0_8 - twophi(iL)**2) & + / (15.0_8 - 6.0_8 * twophi(iL)**2) + twophi(iL) = twophi(iL) - atan(arg) + end if + end do + + twophi = 2.0_8 * twophi + sig_t_factor = cmplx(cos(twophi), -sin(twophi), KIND=8) + end subroutine compute_sig_t_factor + +!=============================================================================== +! 0K_ELASTIC_XS determines the microscopic 0K elastic cross section +! for a given nuclide at the trial relative energy used in resonance scattering +!=============================================================================== + + pure function elastic_xs_0K(E, nuc) result(xs_out) + real(8), intent(in) :: E ! trial energy + type(Nuclide), intent(in) :: nuc ! target nuclide at temperature + real(8) :: xs_out ! 0K xs at trial energy + + integer :: i_grid ! index on nuclide energy grid + integer :: n_grid + real(8) :: f ! interp factor on nuclide energy grid + + ! Determine index on nuclide energy grid + n_grid = size(nuc % energy_0K) + if (E < nuc % energy_0K(1)) then + i_grid = 1 + elseif (E > nuc % energy_0K(n_grid)) then + i_grid = n_grid - 1 + else + i_grid = binary_search(nuc % energy_0K, n_grid, E) + end if + + ! check for rare case where two energy points are the same + if (nuc % energy_0K(i_grid) == nuc % energy_0K(i_grid+1)) then + i_grid = i_grid + 1 + end if + + ! calculate interpolation factor + f = (E - nuc % energy_0K(i_grid)) & + & / (nuc % energy_0K(i_grid + 1) - nuc % energy_0K(i_grid)) + + ! Calculate microscopic nuclide elastic cross section + xs_out = (ONE - f) * nuc % elastic_0K(i_grid) & + & + f * nuc % elastic_0K(i_grid + 1) + + end function elastic_xs_0K + +!=============================================================================== +! CALCULATE_URR_XS determines cross sections in the unresolved resonance range +! from probability tables +!=============================================================================== + + subroutine calculate_urr_xs(this, i_temp, E, i_nuclide, micro_xs) + class(Nuclide), intent(in) :: this ! Nuclide object + integer, intent(in) :: i_temp ! temperature index + real(8), intent(in) :: E ! energy + !!!TODO: i_nuclide is only needed to keep the URR prn stream consistent + !!! to ensure tests pass; this can be removed when this requirement can be + !!! relaxed + integer, intent(in) :: i_nuclide ! Index of this in the nuclides array + type(NuclideMicroXS), intent(inout) :: micro_xs ! Cross section cache + + integer :: i_energy ! index for energy + integer :: i_low ! band index at lower bounding energy + integer :: i_up ! band index at upper bounding energy + real(8) :: f ! interpolation factor + real(8) :: r ! pseudo-random number + real(8) :: elastic ! elastic cross section + real(8) :: capture ! (n,gamma) cross section + real(8) :: fission ! fission cross section + real(8) :: inelastic ! inelastic cross section + + micro_xs % use_ptable = .true. + + associate (urr => this % urr_data(i_temp)) + ! determine energy table + i_energy = 1 + do + if (E < urr % energy(i_energy + 1)) exit + i_energy = i_energy + 1 + end do + + ! determine interpolation factor on table + f = (E - urr % energy(i_energy)) / & + (urr % energy(i_energy + 1) - urr % energy(i_energy)) + + ! sample probability table using the cumulative distribution + + ! Random numbers for xs calculation are sampled from a separated stream. + ! This guarantees the randomness and, at the same time, makes sure we reuse + ! random number for the same nuclide at different temperatures, therefore + ! preserving correlation of temperature in probability tables. + call prn_set_stream(STREAM_URR_PTABLE) + r = future_prn(int(i_nuclide, 8)) + call prn_set_stream(STREAM_TRACKING) + + i_low = 1 + do + if (urr % prob(i_energy, URR_CUM_PROB, i_low) > r) exit + i_low = i_low + 1 + end do + i_up = 1 + do + if (urr % prob(i_energy + 1, URR_CUM_PROB, i_up) > r) exit + i_up = i_up + 1 + end do + + ! determine elastic, fission, and capture cross sections from probability + ! table + if (urr % interp == LINEAR_LINEAR) then + elastic = (ONE - f) * urr % prob(i_energy, URR_ELASTIC, i_low) + & + f * urr % prob(i_energy + 1, URR_ELASTIC, i_up) + fission = (ONE - f) * urr % prob(i_energy, URR_FISSION, i_low) + & + f * urr % prob(i_energy + 1, URR_FISSION, i_up) + capture = (ONE - f) * urr % prob(i_energy, URR_N_GAMMA, i_low) + & + f * urr % prob(i_energy + 1, URR_N_GAMMA, i_up) + elseif (urr % interp == LOG_LOG) then + ! Get logarithmic interpolation factor + f = log(E / urr % energy(i_energy)) / & + log(urr % energy(i_energy + 1) / urr % energy(i_energy)) + + ! Calculate elastic cross section/factor + elastic = ZERO + if (urr % prob(i_energy, URR_ELASTIC, i_low) > ZERO .and. & + urr % prob(i_energy + 1, URR_ELASTIC, i_up) > ZERO) then + elastic = exp((ONE - f) * log(urr % prob(i_energy, URR_ELASTIC, & + i_low)) + f * log(urr % prob(i_energy + 1, URR_ELASTIC, & + i_up))) + end if + + ! Calculate fission cross section/factor + fission = ZERO + if (urr % prob(i_energy, URR_FISSION, i_low) > ZERO .and. & + urr % prob(i_energy + 1, URR_FISSION, i_up) > ZERO) then + fission = exp((ONE - f) * log(urr % prob(i_energy, URR_FISSION, & + i_low)) + f * log(urr % prob(i_energy + 1, URR_FISSION, & + i_up))) + end if + + ! Calculate capture cross section/factor + capture = ZERO + if (urr % prob(i_energy, URR_N_GAMMA, i_low) > ZERO .and. & + urr % prob(i_energy + 1, URR_N_GAMMA, i_up) > ZERO) then + capture = exp((ONE - f) * log(urr % prob(i_energy, URR_N_GAMMA, & + i_low)) + f * log(urr % prob(i_energy + 1, URR_N_GAMMA, & + i_up))) + end if + end if + + ! Determine treatment of inelastic scattering + inelastic = ZERO + if (urr % inelastic_flag > 0) then + ! Get index on energy grid and interpolation factor + i_energy = micro_xs % index_grid + f = micro_xs % interp_factor + + ! Determine inelastic scattering cross section + associate (xs => this % reactions(this % urr_inelastic) % xs(i_temp)) + if (i_energy >= xs % threshold) then + inelastic = (ONE - f) * xs % value(i_energy - xs % threshold + 1) + & + f * xs % value(i_energy - xs % threshold + 2) + end if + end associate + end if + + ! Multiply by smooth cross-section if needed + if (urr % multiply_smooth) then + call this % calculate_elastic_xs(micro_xs) + elastic = elastic * micro_xs % elastic + capture = capture * (micro_xs % absorption - micro_xs % fission) + fission = fission * micro_xs % fission + end if + + ! Check for negative values + if (elastic < ZERO) elastic = ZERO + if (fission < ZERO) fission = ZERO + if (capture < ZERO) capture = ZERO + + ! Set elastic, absorption, fission, and total cross sections. Note that the + ! total cross section is calculated as sum of partials rather than using the + ! table-provided value + micro_xs % elastic = elastic + micro_xs % absorption = capture + fission + micro_xs % fission = fission + micro_xs % total = elastic + inelastic + capture + fission + + ! Determine nu-fission cross section + if (this % fissionable) then + micro_xs % nu_fission = this % nu(E, EMISSION_TOTAL) * & + micro_xs % fission + end if + end associate + + end subroutine calculate_urr_xs + !=============================================================================== ! CHECK_DATA_VERSION checks for the right version of nuclear data within HDF5 ! files diff --git a/src/physics.F90 b/src/physics.F90 index fe242dbb7..98fef8b92 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -2,7 +2,6 @@ module physics use algorithm, only: binary_search use constants - use cross_section, only: elastic_xs_0K, calculate_elastic_xs use endf, only: reaction_name use error, only: fatal_error, warning, write_message use material_header, only: Material, materials @@ -340,7 +339,7 @@ contains ! Calculate elastic cross section if it wasn't precalculated if (micro_xs(i_nuclide) % elastic == CACHE_INVALID) then - call calculate_elastic_xs(i_nuclide) + call nuc % calculate_elastic_xs(micro_xs(i_nuclide)) end if prob = micro_xs(i_nuclide) % elastic - micro_xs(i_nuclide) % thermal diff --git a/src/sab_header.F90 b/src/sab_header.F90 index 6dd617790..eca8e555a 100644 --- a/src/sab_header.F90 +++ b/src/sab_header.F90 @@ -3,7 +3,7 @@ module sab_header use, intrinsic :: ISO_C_BINDING use, intrinsic :: ISO_FORTRAN_ENV - use algorithm, only: find, sort + use algorithm, only: find, sort, binary_search use constants use dict_header, only: DictIntInt, DictCharInt use distribution_univariate, only: Tabular @@ -12,7 +12,9 @@ module sab_header use hdf5_interface, only: read_attribute, get_shape, open_group, close_group, & open_dataset, read_dataset, close_dataset, get_datasets, object_exists, & get_name + use random_lcg, only: prn use secondary_correlated, only: CorrelatedAngleEnergy + use settings use stl_vector, only: VectorInt, VectorReal use string, only: to_str, str_to_int @@ -77,6 +79,7 @@ module sab_header type(SabData), allocatable :: data(:) contains procedure :: from_hdf5 => salphabeta_from_hdf5 + procedure :: calculate_xs => sab_calculate_xs end type SAlphaBeta ! S(a,b) tables @@ -360,6 +363,104 @@ contains call close_group(kT_group) end subroutine salphabeta_from_hdf5 +!=============================================================================== +! SAB_CALCULATE_XS determines the elastic and inelastic scattering +! cross-sections in the thermal energy range. +!=============================================================================== + + subroutine sab_calculate_xs(this, E, sqrtkT, i_temp, elastic, inelastic) + class(SAlphaBeta), intent(in) :: this ! S(a,b) object + real(8), intent(in) :: E ! energy + real(8), intent(in) :: sqrtkT ! temperature + integer, intent(out) :: i_temp ! index in the S(a,b)'s temperature + real(8), intent(out) :: elastic ! thermal elastic cross section + real(8), intent(out) :: inelastic ! thermal inelastic cross section + + integer :: i_grid ! index on S(a,b) energy grid + real(8) :: f ! interp factor on S(a,b) energy grid + real(8) :: kT + + ! Determine temperature for S(a,b) table + kT = sqrtkT**2 + if (temperature_method == TEMPERATURE_NEAREST) then + ! If using nearest temperature, do linear search on temperature + do i_temp = 1, size(this % kTs) + if (abs(this % kTs(i_temp) - kT) < & + K_BOLTZMANN*temperature_tolerance) exit + end do + else + ! Find temperatures that bound the actual temperature + do i_temp = 1, size(this % kTs) - 1 + if (this % kTs(i_temp) <= kT .and. & + kT < this % kTs(i_temp + 1)) exit + end do + + ! Randomly sample between temperature i and i+1 + f = (kT - this % kTs(i_temp)) / & + (this % kTs(i_temp + 1) - this % kTs(i_temp)) + if (f > prn()) i_temp = i_temp + 1 + end if + + + ! Get pointer to S(a,b) table + associate (sab => this % data(i_temp)) + + ! Get index and interpolation factor for inelastic grid + if (E < sab % inelastic_e_in(1)) then + i_grid = 1 + f = ZERO + else + i_grid = binary_search(sab % inelastic_e_in, sab % n_inelastic_e_in, E) + f = (E - sab%inelastic_e_in(i_grid)) / & + (sab%inelastic_e_in(i_grid+1) - sab%inelastic_e_in(i_grid)) + end if + + ! Calculate S(a,b) inelastic scattering cross section + inelastic = (ONE - f) * sab % inelastic_sigma(i_grid) + & + f * sab % inelastic_sigma(i_grid + 1) + + ! Check for elastic data + if (E < sab % threshold_elastic) then + ! Determine whether elastic scattering is given in the coherent or + ! incoherent approximation. For coherent, the cross section is + ! represented as P/E whereas for incoherent, it is simply P + + if (sab % elastic_mode == SAB_ELASTIC_EXACT) then + if (E < sab % elastic_e_in(1)) then + ! If energy is below that of the lowest Bragg peak, the elastic + ! cross section will be zero + elastic = ZERO + else + i_grid = binary_search(sab % elastic_e_in, & + sab % n_elastic_e_in, E) + elastic = sab % elastic_P(i_grid) / E + end if + else + ! Determine index on elastic energy grid + if (E < sab % elastic_e_in(1)) then + i_grid = 1 + else + i_grid = binary_search(sab % elastic_e_in, & + sab % n_elastic_e_in, E) + end if + + ! Get interpolation factor for elastic grid + f = (E - sab%elastic_e_in(i_grid))/(sab%elastic_e_in(i_grid+1) - & + sab%elastic_e_in(i_grid)) + + ! Calculate S(a,b) elastic scattering cross section + elastic = (ONE - f) * sab % elastic_P(i_grid) + & + f * sab % elastic_P(i_grid + 1) + end if + else + ! No elastic data + elastic = ZERO + end if + end associate + + end subroutine sab_calculate_xs + + !=============================================================================== ! FREE_MEMORY_SAB deallocates global arrays defined in this module !=============================================================================== diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index da0d8929c..a9a5fd8bf 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -4,7 +4,6 @@ module tally use algorithm, only: binary_search use constants - use cross_section, only: multipole_deriv_eval, calculate_elastic_xs use dict_header, only: EMPTY use error, only: fatal_error use geometry_header @@ -1018,7 +1017,7 @@ contains else if (i_nuclide > 0) then if (micro_xs(i_nuclide) % elastic == CACHE_INVALID) then - call calculate_elastic_xs(i_nuclide) + call nuclides(i_nuclide) % calculate_elastic_xs(micro_xs(i_nuclide)) end if score = micro_xs(i_nuclide) % elastic * atom_density * flux else @@ -1031,7 +1030,7 @@ contains ! Get index in nuclides array i_nuc = materials(p % material) % nuclide(l) if (micro_xs(i_nuc) % elastic == CACHE_INVALID) then - call calculate_elastic_xs(i_nuc) + call nuclides(i_nuc) % calculate_elastic_xs(micro_xs(i_nuc)) end if score = score + micro_xs(i_nuc) % elastic * atom_density_ * flux diff --git a/src/tracking.F90 b/src/tracking.F90 index 127da0222..40f20bbb0 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -1,11 +1,11 @@ module tracking use constants - use cross_section, only: calculate_xs use error, only: warning, write_message use geometry_header, only: cells use geometry, only: find_cell, distance_to_boundary, cross_lattice,& check_cell_overlap + use material_header, only: materials, Material use message_passing use mgxs_header use nuclide_header @@ -100,29 +100,30 @@ contains if (check_overlaps) call check_cell_overlap(p) ! Calculate microscopic and macroscopic cross sections - if (run_CE) then - ! If the material is the same as the last material and the temperature - ! hasn't changed, we don't need to lookup cross sections again. - if (p % material /= p % last_material .or. & - p % sqrtkT /= p % last_sqrtkT) call calculate_xs(p) - else - ! Since the MGXS can be angle dependent, this needs to be done - ! After every collision for the MGXS mode - if (p % material /= MATERIAL_VOID) then - ! Update the temperature index - call macro_xs(p % material) % obj % find_temperature(p % sqrtkT) - ! Get the data - call macro_xs(p % material) % obj % calculate_xs(p % g, & - p % coord(p % n_coord) % uvw, material_xs) + if (p % material /= MATERIAL_VOID) then + if (run_CE) then + if (p % material /= p % last_material .or. & + p % sqrtkT /= p % last_sqrtkT) then + ! If the material is the same as the last material and the + ! temperature hasn't changed, we don't need to lookup cross + ! sections again. + call materials(p % material) % calculate_xs(p % E, p % sqrtkT, & + micro_xs, nuclides, material_xs) + end if else - material_xs % total = ZERO - material_xs % absorption = ZERO - material_xs % nu_fission = ZERO - end if + ! Get the MG data + call macro_xs(p % material) % obj % calculate_xs(p % g, p % sqrtkT, & + p % coord(p % n_coord) % uvw, material_xs) - ! Finally, update the particle group while we have already checked for - ! if multi-group - p % last_g = p % g + ! Finally, update the particle group while we have already checked + ! for if multi-group + p % last_g = p % g + end if + else + material_xs % total = ZERO + material_xs % absorption = ZERO + material_xs % fission = ZERO + material_xs % nu_fission = ZERO end if ! Find the distance to the nearest boundary From 467f203bdb0a43a4a324346a6eff7dcb071c15f6 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Sun, 4 Feb 2018 17:27:58 -0500 Subject: [PATCH 139/282] Cleaning up --- src/input_xml.F90 | 2 +- src/material_header.F90 | 2 +- src/nuclide_header.F90 | 27 ++++++++++++--------------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 0b7790a51..34a2fdd71 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -4191,7 +4191,7 @@ contains group_id = open_group(file_id, name) call nuclides(i_nuclide) % from_hdf5(group_id, nuc_temps(i_nuclide), & temperature_method, temperature_tolerance, temperature_range, & - master) + master, i_nuclide) call close_group(group_id) call file_close(file_id) diff --git a/src/material_header.F90 b/src/material_header.F90 index ae15ec570..038e529be 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -331,7 +331,7 @@ contains .or. i_sab /= micro_xs(i_nuclide) % index_sab & .or. sab_frac /= micro_xs(i_nuclide) % sab_frac) then call nuclides(i_nuclide) % calculate_xs(i_sab, E, i_grid, & - sqrtkT, sab_frac, i_nuclide, micro_xs(i_nuclide)) + sqrtkT, sab_frac, micro_xs(i_nuclide)) end if ! ====================================================================== diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 994961025..fd2577508 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -64,6 +64,7 @@ module nuclide_header integer :: A ! mass number integer :: metastable ! metastable state real(8) :: awr ! Atomic Weight Ratio + integer :: i_nuclide ! The nuclides index in the nuclides array real(8), allocatable :: kTs(:) ! temperature in eV (k*T) ! Fission information @@ -268,7 +269,7 @@ contains end subroutine nuclide_clear subroutine nuclide_from_hdf5(this, group_id, temperature, method, tolerance, & - minmax, master) + minmax, master, i_nuclide) class(Nuclide), intent(inout) :: this integer(HID_T), intent(in) :: group_id type(VectorReal), intent(in) :: temperature ! list of desired temperatures @@ -276,6 +277,7 @@ contains real(8), intent(in) :: tolerance real(8), intent(in) :: minmax(2) ! range of temperatures logical, intent(in) :: master ! if this is the master proc + integer, intent(in) :: i_nuclide ! Nuclide index in nuclides integer :: i integer :: i_closest @@ -569,6 +571,9 @@ contains ! Create derived cross section data call this % create_derived() + ! Finalize with the nuclide index + this % i_nuclide = i_nuclide + end subroutine nuclide_from_hdf5 subroutine nuclide_create_derived(this) @@ -815,7 +820,7 @@ contains !=============================================================================== subroutine nuclide_calculate_xs(this, i_sab, E, i_log_union, sqrtkT, & - sab_frac, i_nuclide, micro_xs) + sab_frac, micro_xs) class(Nuclide), intent(in) :: this ! Nuclide object integer, intent(in) :: i_sab ! index into sab_tables array real(8), intent(in) :: E ! energy @@ -823,10 +828,6 @@ contains ! material union energy grid real(8), intent(in) :: sqrtkT ! square root of kT, material dependent real(8), intent(in) :: sab_frac ! fraction of atoms affected by S(a,b) - !!!TODO: i_nuclide is only needed to keep the URR prn stream consistent - !!! to ensure tests pass; this can be removed when this requirement can be - !!! relaxed - integer, intent(in) :: i_nuclide ! Index of this in the nuclides array type(NuclideMicroXS), intent(inout) :: micro_xs ! Cross section cache logical :: use_mp ! true if XS can be calculated with windowed multipole @@ -882,7 +883,7 @@ contains ! 1. physics.F90 - scatter - For inelastic scatter. ! 2. physics.F90 - sample_fission - For partial fissions. ! 3. tally.F90 - score_general - For tallying on MTxxx reactions. - ! 4. cross_section.F90 - calculate_urr_xs - For unresolved purposes. + ! 4. nuclide_header.F90 - calculate_urr_xs - For unresolved purposes. ! It is worth noting that none of these occur in the resolved ! resonance range, so the value here does not matter. index_temp is ! set to -1 to force a segfault in case a developer messes up and tries @@ -1008,7 +1009,7 @@ contains if (urr_ptables_on .and. this % urr_present .and. .not. use_mp) then if (E > this % urr_data(i_temp) % energy(1) .and. E < this % & urr_data(i_temp) % energy(this % urr_data(i_temp) % n_energy)) then - call calculate_urr_xs(this, i_temp, E, i_nuclide, micro_xs) + call calculate_urr_xs(this, i_temp, E, micro_xs) end if end if @@ -1397,14 +1398,10 @@ contains ! from probability tables !=============================================================================== - subroutine calculate_urr_xs(this, i_temp, E, i_nuclide, micro_xs) + subroutine calculate_urr_xs(this, i_temp, E, micro_xs) class(Nuclide), intent(in) :: this ! Nuclide object integer, intent(in) :: i_temp ! temperature index real(8), intent(in) :: E ! energy - !!!TODO: i_nuclide is only needed to keep the URR prn stream consistent - !!! to ensure tests pass; this can be removed when this requirement can be - !!! relaxed - integer, intent(in) :: i_nuclide ! Index of this in the nuclides array type(NuclideMicroXS), intent(inout) :: micro_xs ! Cross section cache integer :: i_energy ! index for energy @@ -1438,7 +1435,7 @@ contains ! random number for the same nuclide at different temperatures, therefore ! preserving correlation of temperature in probability tables. call prn_set_stream(STREAM_URR_PTABLE) - r = future_prn(int(i_nuclide, 8)) + r = future_prn(int(this % i_nuclide, 8)) call prn_set_stream(STREAM_TRACKING) i_low = 1 @@ -1657,7 +1654,7 @@ contains group_id = open_group(file_id, name_) call nuclides(n) % from_hdf5(group_id, temperature, & temperature_method, temperature_tolerance, minmax, & - master) + master, n) call close_group(group_id) call file_close(file_id) From 160c533ae984987c17a5db5e8d6b64166cbda3a1 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Sun, 4 Feb 2018 19:00:39 -0500 Subject: [PATCH 140/282] fixed mis-indented line --- src/tracking.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tracking.F90 b/src/tracking.F90 index 40f20bbb0..f5839eb8e 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -103,7 +103,7 @@ contains if (p % material /= MATERIAL_VOID) then if (run_CE) then if (p % material /= p % last_material .or. & - p % sqrtkT /= p % last_sqrtkT) then + p % sqrtkT /= p % last_sqrtkT) then ! If the material is the same as the last material and the ! temperature hasn't changed, we don't need to lookup cross ! sections again. From 894ec54ad7659194ed53cc806444e88cd164d649 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 23 Jan 2018 23:01:03 -0600 Subject: [PATCH 141/282] Add unit tests for material classes --- openmc/material.py | 6 +- tests/unit_tests/test_material.py | 128 ++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 tests/unit_tests/test_material.py diff --git a/openmc/material.py b/openmc/material.py index e22fe0b8b..a59fdaa28 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -15,7 +15,7 @@ from .mixin import IDManagerMixin # Units for density supported by OpenMC -DENSITY_UNITS = ['g/cm3', 'g/cc', 'kg/cm3', 'atom/b-cm', 'atom/cm3', 'sum', +DENSITY_UNITS = ['g/cm3', 'g/cc', 'kg/m3', 'atom/b-cm', 'atom/cm3', 'sum', 'macro'] @@ -49,7 +49,7 @@ class Material(IDManagerMixin): density : float Density of the material (units defined separately) density_units : str - Units used for `density`. Can be one of 'g/cm3', 'g/cc', 'kg/cm3', + Units used for `density`. Can be one of 'g/cm3', 'g/cc', 'kg/m3', 'atom/b-cm', 'atom/cm3', 'sum', or 'macro'. The 'macro' unit only applies in the case of a multi-group calculation. depletable : bool @@ -312,7 +312,7 @@ class Material(IDManagerMixin): Parameters ---------- - units : {'g/cm3', 'g/cc', 'kg/cm3', 'atom/b-cm', 'atom/cm3', 'sum', 'macro'} + units : {'g/cm3', 'g/cc', 'kg/m3', 'atom/b-cm', 'atom/cm3', 'sum', 'macro'} Physical units of density. density : float, optional Value of the density. Must be specified unless units is given as diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py new file mode 100644 index 000000000..46241e83d --- /dev/null +++ b/tests/unit_tests/test_material.py @@ -0,0 +1,128 @@ +import openmc +import pytest + + +@pytest.fixture(scope='module') +def uo2(): + m = openmc.Material(material_id=100, name='UO2') + m.add_nuclide('U235', 1.0) + m.add_nuclide('O16', 2.0) + m.set_density('g/cm3', 10.0) + m.depletable = True + return m + + +def test_attributes(uo2): + assert uo2.name == 'UO2' + assert uo2.id == 100 + assert uo2.depletable + + +def test_nuclides(uo2): + """Test adding/removing nuclides.""" + m = openmc.Material() + m.add_nuclide('U235', 1.0) + with pytest.raises(ValueError): + m.add_nuclide('H1', '1.0') + with pytest.raises(ValueError): + m.add_nuclide(1.0, 'H1') + with pytest.raises(ValueError): + m.add_nuclide('H1', 1.0, 'oa') + m.remove_nuclide('U235') + + +def test_elements(): + """Test adding elements.""" + m = openmc.Material() + m.add_element('Zr', 1.0) + m.add_element('U', 1.0, enrichment=4.5) + with pytest.raises(ValueError): + m.add_element('U', 1.0, enrichment=100.0) + with pytest.raises(ValueError): + m.add_element('Pu', 1.0, enrichment=3.0) + + +def test_density(): + m = openmc.Material() + for unit in ['g/cm3', 'g/cc', 'kg/m3', 'atom/b-cm', 'atom/cm3']: + m.set_density(unit, 1.0) + with pytest.raises(ValueError): + m.set_density('g/litre', 1.0) + + +def test_salphabeta(): + m = openmc.Material() + m.add_s_alpha_beta('c_H_in_H2O', 0.5) + + +def test_repr(): + m = openmc.Material() + m.add_nuclide('Zr90', 1.0) + m.add_nuclide('H2', 0.5) + m.add_s_alpha_beta('c_D_in_D2O') + m.set_density('sum') + m.temperature = 600.0 + repr(m) + + +def test_macroscopic(): + m = openmc.Material(name='UO2') + m.add_macroscopic('UO2') + with pytest.raises(ValueError): + m.add_nuclide('H1', 1.0) + with pytest.raises(ValueError): + m.add_element('O', 1.0) + with pytest.raises(ValueError): + m.add_macroscopic('Other') + + m2 = openmc.Material() + m2.add_nuclide('He4', 1.0) + with pytest.raises(ValueError): + m2.add_macroscopic('UO2') + + m.remove_macroscopic('UO2') + + +def test_isotropic(): + m = openmc.Material() + m.add_nuclide('U235', 1.0) + m.add_nuclide('O16', 2.0) + m.make_isotropic_in_lab() + assert m.isotropic == ['U235', 'O16'] + m.isotropic = ['O16'] + assert m.isotropic == ['O16'] + + +def test_get_nuclide_densities(uo2): + nucs = uo2.get_nuclide_densities() + for nuc, density, density_type in nucs.values(): + assert nuc in ('U235', 'O16') + assert density > 0 + assert density_type in ('ao', 'wo') + + +def test_get_nuclide_atom_densities(uo2): + nucs = uo2.get_nuclide_atom_densities() + for nuc, density in nucs.values(): + assert nuc in ('U235', 'O16') + assert density > 0 + + +def test_materials(tmpdir): + m1 = openmc.Material() + m1.add_nuclide('U235', 1.0, 'wo') + m1.add_nuclide('O16', 2.0, 'wo') + m1.set_density('g/cm3', 10.0) + m1.depletable = True + m1.temperature = 900.0 + + m2 = openmc.Material() + m2.add_nuclide('H1', 2.0) + m2.add_nuclide('O16', 1.0) + m2.add_s_alpha_beta('c_H_in_H2O') + m2.set_density('kg/m3', 1000.0) + + mats = openmc.Materials([m1, m2]) + mats.cross_sections = '/some/fake/cross_sections.xml' + mats.multipole_library = '/some/awesome/mp_lib/' + mats.export_to_xml(str(tmpdir.join('materials.xml'))) From b81cbc29fb054353742c564c4d5d3b69b07d4b0e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 24 Jan 2018 13:41:41 -0600 Subject: [PATCH 142/282] Add test for Material.add_volume_information --- src/volume_calc.F90 | 2 +- tests/unit_tests/test_material.py | 48 +++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/volume_calc.F90 b/src/volume_calc.F90 index 07dc5b418..15c049769 100644 --- a/src/volume_calc.F90 +++ b/src/volume_calc.F90 @@ -194,7 +194,7 @@ contains if (this % domain_type == FILTER_MATERIAL) then i_material = p % material do i_domain = 1, size(this % domain_id) - if (i_material == materials(i_domain) % id) then + if (materials(i_material) % id == this % domain_id(i_domain)) then call check_hit(i_domain, i_material, indices, hits, n_mat) end if end do diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 46241e83d..2da3c797f 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -1,4 +1,6 @@ import openmc +import openmc.model +import openmc.stats import pytest @@ -12,6 +14,40 @@ def uo2(): return m +@pytest.fixture(scope='module') +def sphere_model(): + model = openmc.model.Model() + + m = openmc.Material() + m.add_nuclide('U235', 1.0) + m.set_density('g/cm3', 1.0) + model.materials.append(m) + + sph = openmc.Sphere(boundary_type='vacuum') + c = openmc.Cell(fill=m, region=-sph) + model.geometry.root_universe = openmc.Universe(cells=[c]) + + model.settings.particles = 100 + model.settings.batches = 10 + model.settings.run_mode = 'fixed source' + model.settings.source = openmc.Source(space=openmc.stats.Point()) + ll, ur = c.region.bounding_box + model.settings.volume_calculations = [ + openmc.VolumeCalculation(domains=[m], samples=1000, + lower_left=ll, upper_right=ur) + ] + return model + + +@pytest.fixture +def run_in_tmpdir(tmpdir): + orig = tmpdir.chdir() + try: + yield + finally: + orig.chdir() + + def test_attributes(uo2): assert uo2.name == 'UO2' assert uo2.id == 100 @@ -93,6 +129,14 @@ def test_isotropic(): assert m.isotropic == ['O16'] +def test_volume(run_in_tmpdir, sphere_model): + """Test adding volume information from a volume calculation.""" + sphere_model.export_to_xml() + openmc.calculate_volumes() + volume_calc = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + sphere_model.materials[0].add_volume_information(volume_calc) + + def test_get_nuclide_densities(uo2): nucs = uo2.get_nuclide_densities() for nuc, density, density_type in nucs.values(): @@ -108,7 +152,7 @@ def test_get_nuclide_atom_densities(uo2): assert density > 0 -def test_materials(tmpdir): +def test_materials(run_in_tmpdir): m1 = openmc.Material() m1.add_nuclide('U235', 1.0, 'wo') m1.add_nuclide('O16', 2.0, 'wo') @@ -125,4 +169,4 @@ def test_materials(tmpdir): mats = openmc.Materials([m1, m2]) mats.cross_sections = '/some/fake/cross_sections.xml' mats.multipole_library = '/some/awesome/mp_lib/' - mats.export_to_xml(str(tmpdir.join('materials.xml'))) + mats.export_to_xml() From 1432742fbfb37a2a1d1682aa795d8090672263c6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 24 Jan 2018 14:29:31 -0600 Subject: [PATCH 143/282] More tests for material --- tests/unit_tests/test_material.py | 37 ++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 2da3c797f..84627ba8c 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -1,6 +1,7 @@ import openmc import openmc.model import openmc.stats +import openmc.examples import pytest @@ -101,7 +102,7 @@ def test_repr(): repr(m) -def test_macroscopic(): +def test_macroscopic(run_in_tmpdir): m = openmc.Material(name='UO2') m.add_macroscopic('UO2') with pytest.raises(ValueError): @@ -116,17 +117,37 @@ def test_macroscopic(): with pytest.raises(ValueError): m2.add_macroscopic('UO2') + # Make sure we can remove/add macroscopic m.remove_macroscopic('UO2') + m.add_macroscopic('UO2') + repr(m) + + # Make sure we can export a material with macroscopic data + mats = openmc.Materials([m]) + mats.export_to_xml() + + +def test_paths(): + model = openmc.examples.pwr_assembly() + model.geometry.determine_paths() + fuel = model.materials[0] + assert fuel.num_instances == 264 + assert len(fuel.paths) == 264 def test_isotropic(): - m = openmc.Material() - m.add_nuclide('U235', 1.0) - m.add_nuclide('O16', 2.0) - m.make_isotropic_in_lab() - assert m.isotropic == ['U235', 'O16'] - m.isotropic = ['O16'] - assert m.isotropic == ['O16'] + m1 = openmc.Material() + m1.add_nuclide('U235', 1.0) + m1.add_nuclide('O16', 2.0) + m1.isotropic = ['O16'] + assert m1.isotropic == ['O16'] + + m2 = openmc.Material() + m2.add_nuclide('H1', 1.0) + mats = openmc.Materials([m1, m2]) + mats.make_isotropic_in_lab() + assert m1.isotropic == ['U235', 'O16'] + assert m2.isotropic == ['H1'] def test_volume(run_in_tmpdir, sphere_model): From b0d35268f3ed656498bb40707243d115fe2bf5ea Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 25 Jan 2018 14:04:01 -0600 Subject: [PATCH 144/282] Add unit tests for surfaces --- openmc/surface.py | 2 +- tests/unit_tests/test_surface.py | 258 +++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/test_surface.py diff --git a/openmc/surface.py b/openmc/surface.py index 6c29ed458..eb44a4fe8 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -1376,7 +1376,7 @@ class Cone(Surface): @property def r2(self): - return self.coefficients['r2'] + return self.coefficients['R2'] @x0.setter def x0(self, x0): diff --git a/tests/unit_tests/test_surface.py b/tests/unit_tests/test_surface.py new file mode 100644 index 000000000..ded6cf1f2 --- /dev/null +++ b/tests/unit_tests/test_surface.py @@ -0,0 +1,258 @@ +import numpy as np +import openmc +import pytest + + +def assert_infinite_bb(s): + ll, ur = (-s).bounding_box + assert np.all(np.isinf(ll)) + assert np.all(np.isinf(ur)) + ll, ur = (+s).bounding_box + assert np.all(np.isinf(ll)) + assert np.all(np.isinf(ur)) + + +def test_plane(): + s = openmc.Plane(A=1, B=2, C=-1, D=3, name='my plane') + assert s.a == 1 + assert s.b == 2 + assert s.c == -1 + assert s.d == 3 + assert s.boundary_type == 'transmission' + assert s.name == 'my plane' + assert s.type == 'plane' + + # Generic planes don't have well-defined bounding boxes + assert_infinite_bb(s) + + # evaluate method + x, y, z = (4, 3, 6) + assert s.evaluate((x, y, z)) == pytest.approx(s.a*x + s.b*y + s.c*z - s.d) + + # Make sure repr works + repr(s) + + +def test_xplane(): + s = openmc.XPlane(x0=3., boundary_type='reflective') + assert s.x0 == 3. + assert s.boundary_type == 'reflective' + + # Check bounding box + ll, ur = (+s).bounding_box + assert ll == pytest.approx((3., -np.inf, -np.inf)) + assert np.all(np.isinf(ur)) + ll, ur = (-s).bounding_box + assert ur == pytest.approx((3., np.inf, np.inf)) + assert np.all(np.isinf(ll)) + + # __contains__ on associated half-spaces + assert (5, 0, 0) in +s + assert (5, 0, 0) not in -s + assert (-2, 1, 10) in -s + assert (-2, 1, 10) not in +s + + # evaluate method + assert s.evaluate((5., 0., 0.)) == pytest.approx(2.) + + # Make sure repr works + repr(s) + + +def test_yplane(): + s = openmc.YPlane(y0=3.) + assert s.y0 == 3. + + # Check bounding box + ll, ur = (+s).bounding_box + assert ll == pytest.approx((-np.inf, 3., -np.inf)) + assert np.all(np.isinf(ur)) + ll, ur = s.bounding_box('-') + assert ur == pytest.approx((np.inf, 3., np.inf)) + assert np.all(np.isinf(ll)) + + # __contains__ on associated half-spaces + assert (0, 5, 0) in +s + assert (0, 5, 0) not in -s + assert (-2, 1, 10) in -s + assert (-2, 1, 10) not in +s + + # evaluate method + assert s.evaluate((0., 0., 0.)) == pytest.approx(-3.) + +def test_zplane(): + s = openmc.ZPlane(z0=3.) + assert s.z0 == 3. + + # Check bounding box + ll, ur = (+s).bounding_box + assert ll == pytest.approx((-np.inf, -np.inf, 3.)) + assert np.all(np.isinf(ur)) + ll, ur = (-s).bounding_box + assert ur == pytest.approx((np.inf, np.inf, 3.)) + assert np.all(np.isinf(ll)) + + # __contains__ on associated half-spaces + assert (0, 0, 5) in +s + assert (0, 0, 5) not in -s + assert (-2, 1, -10) in -s + assert (-2, 1, -10) not in +s + + # evaluate method + assert s.evaluate((0., 0., 10.)) == pytest.approx(7.) + + # Make sure repr works + repr(s) + + +def test_xcylinder(): + y, z, r = 3, 5, 2 + s = openmc.XCylinder(y0=y, z0=z, R=r) + assert s.y0 == y + assert s.z0 == z + assert s.r == r + + # Check bounding box + ll, ur = (+s).bounding_box + assert np.all(np.isinf(ll)) + assert np.all(np.isinf(ur)) + ll, ur = (-s).bounding_box + assert ll == pytest.approx((-np.inf, y-r, z-r)) + assert ur == pytest.approx((np.inf, y+r, z+r)) + + # evaluate method + assert s.evaluate((0, y, z)) == pytest.approx(-r**2) + + # Make sure repr works + repr(s) + + +def test_periodic(): + x = openmc.XPlane(boundary_type='periodic') + y = openmc.YPlane(boundary_type='periodic') + x.periodic_surface = y + assert y.periodic_surface == x + with pytest.raises(TypeError): + x.periodic_surface = openmc.Sphere() + + +def test_ycylinder(): + x, z, r = 3, 5, 2 + s = openmc.YCylinder(x0=x, z0=z, R=r) + assert s.x0 == x + assert s.z0 == z + assert s.r == r + + # Check bounding box + ll, ur = (+s).bounding_box + assert np.all(np.isinf(ll)) + assert np.all(np.isinf(ur)) + ll, ur = (-s).bounding_box + assert ll == pytest.approx((x-r, -np.inf, z-r)) + assert ur == pytest.approx((x+r, np.inf, z+r)) + + # evaluate method + assert s.evaluate((x, 0, z)) == pytest.approx(-r**2) + + +def test_zcylinder(): + x, y, r = 3, 5, 2 + s = openmc.ZCylinder(x0=x, y0=y, R=r) + assert s.x0 == x + assert s.y0 == y + assert s.r == r + + # Check bounding box + ll, ur = (+s).bounding_box + assert np.all(np.isinf(ll)) + assert np.all(np.isinf(ur)) + ll, ur = (-s).bounding_box + assert ll == pytest.approx((x-r, y-r, -np.inf)) + assert ur == pytest.approx((x+r, y+r, np.inf)) + + # evaluate method + assert s.evaluate((x, y, 0)) == pytest.approx(-r**2) + + # Make sure repr works + repr(s) + + +def test_sphere(): + x, y, z, r = -3, 5, 6, 2 + s = openmc.Sphere(x0=x, y0=y, z0=z, R=r) + assert s.x0 == x + assert s.y0 == y + assert s.z0 == z + assert s.r == r + + # Check bounding box + ll, ur = (+s).bounding_box + assert np.all(np.isinf(ll)) + assert np.all(np.isinf(ur)) + ll, ur = (-s).bounding_box + assert ll == pytest.approx((x-r, y-r, z-r)) + assert ur == pytest.approx((x+r, y+r, z+r)) + + # evaluate method + assert s.evaluate((x, y, z)) == pytest.approx(-r**2) + + # Make sure repr works + repr(s) + + +def cone_common(apex, r2, cls): + x, y, z = apex + s = cls(x0=x, y0=y, z0=z, R2=r2) + assert s.x0 == x + assert s.y0 == y + assert s.z0 == z + assert s.r2 == r2 + + # Check bounding box + assert_infinite_bb(s) + + # evaluate method -- should be zero at apex + assert s.evaluate((x, y, z)) == pytest.approx(0.0) + + # Make sure repr works + repr(s) + + +def test_xcone(): + apex = (10, 0, 0) + r2 = 4 + cone_common(apex, r2, openmc.XCone) + + +def test_ycone(): + apex = (10, 0, 0) + r2 = 4 + cone_common(apex, r2, openmc.YCone) + + +def test_zcone(): + apex = (10, 0, 0) + r2 = 4 + cone_common(apex, r2, openmc.ZCone) + + +def test_quadric(): + # Make a sphere from a quadric + r = 10.0 + coeffs = {'a': 1, 'b': 1, 'c': 1, 'k': -r**2} + s = openmc.Quadric(**coeffs) + assert s.a == coeffs['a'] + assert s.b == coeffs['b'] + assert s.c == coeffs['c'] + assert s.k == coeffs['k'] + + # All other coeffs should be zero + for coeff in ('d', 'e', 'f', 'g', 'h', 'j'): + assert getattr(s, coeff) == 0.0 + + # Check bounding box + assert_infinite_bb(s) + + # evaluate method + assert s.evaluate((0., 0., 0.)) == pytest.approx(coeffs['k']) + assert s.evaluate((1., 1., 1.)) == pytest.approx(3 + coeffs['k']) From d26165f524d7f2eaed7b4dcb3c0757f78a29bf26 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 25 Jan 2018 16:26:40 -0600 Subject: [PATCH 145/282] Add tests for regions --- openmc/region.py | 8 +- tests/unit_tests/test_region.py | 134 +++++++++++++++++++++++++++++++ tests/unit_tests/test_surface.py | 1 + 3 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 tests/unit_tests/test_region.py diff --git a/openmc/region.py b/openmc/region.py index eeafd2832..54797f568 100644 --- a/openmc/region.py +++ b/openmc/region.py @@ -39,10 +39,8 @@ class Region(object): def __eq__(self, other): if not isinstance(other, type(self)): return False - elif str(self) != str(other): - return False else: - return True + return str(self) == str(other) def __ne__(self, other): return not self == other @@ -463,7 +461,7 @@ class Union(Region, MutableSequence): if memo is None: memo = {} - clone = copy.deepcopy(self) + clone = deepcopy(self) clone[:] = [n.clone(memo) for n in self] return clone @@ -584,6 +582,6 @@ class Complement(Region): if memo is None: memo = {} - clone = copy.deepcopy(self) + clone = deepcopy(self) clone.node = self.node.clone(memo) return clone diff --git a/tests/unit_tests/test_region.py b/tests/unit_tests/test_region.py new file mode 100644 index 000000000..346ec7d95 --- /dev/null +++ b/tests/unit_tests/test_region.py @@ -0,0 +1,134 @@ +import numpy as np +import pytest +import openmc + + +@pytest.fixture +def reset(): + openmc.reset_auto_ids() + + +def assert_unbounded(region): + ll, ur = region.bounding_box + assert ll == pytest.approx((-np.inf, -np.inf, -np.inf)) + assert ur == pytest.approx((np.inf, np.inf, np.inf)) + + +def test_union(reset): + s1 = openmc.XPlane(surface_id=1, x0=5) + s2 = openmc.XPlane(surface_id=2, x0=-5) + region = +s1 | -s2 + assert isinstance(region, openmc.Union) + + # Check bounding box + assert_unbounded(region) + + # __contains__ + assert (6, 0, 0) in region + assert (-6, 0, 0) in region + assert (0, 0, 0) not in region + + # string representation + assert str(region) == '(1 | -2)' + + # Combining region with intersection + s3 = openmc.YPlane(surface_id=3) + reg2 = region & +s3 + assert (6, 1, 0) in reg2 + assert (6, -1, 0) not in reg2 + assert str(reg2) == '((1 | -2) 3)' + + +def test_intersection(reset): + s1 = openmc.XPlane(surface_id=1, x0=5) + s2 = openmc.XPlane(surface_id=2, x0=-5) + region = -s1 & +s2 + assert isinstance(region, openmc.Intersection) + + # Check bounding box + ll, ur = region.bounding_box + assert ll == pytest.approx((-5, -np.inf, -np.inf)) + assert ur == pytest.approx((5, np.inf, np.inf)) + + # __contains__ + assert (6, 0, 0) not in region + assert (-6, 0, 0) not in region + assert (0, 0, 0) in region + + # string representation + assert str(region) == '(-1 2)' + + # Combining region with union + s3 = openmc.YPlane(surface_id=3) + reg2 = region | +s3 + assert (-6, 2, 0) in reg2 + assert (-6, -2, 0) not in reg2 + assert str(reg2) == '((-1 2) | 3)' + + +def test_complement(reset): + zcyl = openmc.ZCylinder(surface_id=1, R=1.) + z0 = openmc.ZPlane(surface_id=2, z0=-5.) + z1 = openmc.ZPlane(surface_id=3, z0=5.) + outside = +zcyl | -z0 | +z1 + inside = ~outside + outside_equiv = ~(-zcyl & +z0 & -z1) + inside_equiv = ~outside_equiv + + # Check bounding box + for region in (inside, inside_equiv): + ll, ur = region.bounding_box + assert ll == pytest.approx((-1., -1., -5.)) + assert ur == pytest.approx((1., 1., 5.)) + assert_unbounded(outside) + assert_unbounded(outside_equiv) + + # string represention + assert str(inside) == '~(1 | -2 | 3)' + + # evaluate method + assert (0, 0, 0) in inside + assert (0, 0, 0) not in outside + assert (0, 0, 6) not in inside + assert (0, 0, 6) in outside + + +def test_get_surfaces(): + s1 = openmc.XPlane() + s2 = openmc.YPlane() + s3 = openmc.ZPlane() + region = (+s1 & -s2) | +s3 + + # Make sure get_surfaces() returns all surfaces + surfs = set(region.get_surfaces().values()) + assert not (surfs ^ {s1, s2, s3}) + + inverse = ~region + surfs = set(inverse.get_surfaces().values()) + assert not (surfs ^ {s1, s2, s3}) + + +def test_extend_clone(): + s1 = openmc.XPlane() + s2 = openmc.YPlane() + s3 = openmc.ZPlane() + s4 = openmc.ZCylinder() + + # extend intersection + r1 = +s1 & -s2 + r1 &= +s3 & -s4 + assert r1[:] == [+s1, -s2, +s3, -s4] + + # extend union + r2 = +s1 | -s2 + r2 |= +s3 | -s4 + assert r2[:] == [+s1, -s2, +s3, -s4] + + # clone methods + r3 = r1.clone() + assert len(r3) == len(r1) + r4 = r2.clone() + assert len(r4) == len(r2) + + r5 = ~r1 + r6 = r5.clone() diff --git a/tests/unit_tests/test_surface.py b/tests/unit_tests/test_surface.py index ded6cf1f2..3db84cc05 100644 --- a/tests/unit_tests/test_surface.py +++ b/tests/unit_tests/test_surface.py @@ -80,6 +80,7 @@ def test_yplane(): # evaluate method assert s.evaluate((0., 0., 0.)) == pytest.approx(-3.) + def test_zplane(): s = openmc.ZPlane(z0=3.) assert s.z0 == 3. From 4180d632a466c788d39594918f237c476a51ebbc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 26 Jan 2018 08:27:28 -0600 Subject: [PATCH 146/282] Add tests for univariate probability distributions --- openmc/stats/univariate.py | 19 ++++--- tests/unit_tests/test_stats.py | 90 ++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 tests/unit_tests/test_stats.py diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index c0476ce45..38683e692 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -194,12 +194,12 @@ class Maxwell(Univariate): Parameters ---------- theta : float - Effective temperature for distribution + Effective temperature for distribution in eV Attributes ---------- theta : float - Effective temperature for distribution + Effective temperature for distribution in eV """ @@ -250,16 +250,16 @@ class Watt(Univariate): Parameters ---------- a : float - First parameter of distribution + First parameter of distribution in units of eV b : float - Second parameter of distribution + Second parameter of distribution in units of 1/eV Attributes ---------- a : float - First parameter of distribution + First parameter of distribution in units of eV b : float - Second parameter of distribution + Second parameter of distribution in units of 1/eV """ @@ -444,10 +444,9 @@ class Legendre(Univariate): def coefficients(self, coefficients): cv.check_type('Legendre expansion coefficients', coefficients, Iterable, Real) - for l in range(len(coefficients)): - coefficients[l] *= (2.*l + 1.)/2. - self._legendre_polynomial = np.polynomial.legendre.Legendre( - coefficients) + l = np.arange(len(coefficients)) + coeffs = (2.*l + 1.)/2. * np.array(coefficients) + self._legendre_polynomial = np.polynomial.Legendre(coeffs) def to_xml_element(self, element_name): raise NotImplementedError diff --git a/tests/unit_tests/test_stats.py b/tests/unit_tests/test_stats.py new file mode 100644 index 000000000..cef001526 --- /dev/null +++ b/tests/unit_tests/test_stats.py @@ -0,0 +1,90 @@ +import numpy as np +import pytest +import openmc +import openmc.stats + + +def test_discrete(): + x = [0.0, 1.0, 10.0] + p = [0.3, 0.2, 0.5] + d = openmc.stats.Discrete(x, p) + assert d.x == x + assert d.p == p + assert len(d) == len(x) + d.to_xml_element('distribution') + + # Single point + d2 = openmc.stats.Discrete(1e6, 1.0) + assert d2.x == [1e6] + assert d2.p == [1.0] + assert len(d2) == 1 + + +def test_uniform(): + a, b = 10, 20 + d = openmc.stats.Uniform(a, b) + assert d.a == a + assert d.b == b + assert len(d) == 2 + + t = d.to_tabular() + assert t.x == [a, b] + assert t.p == [1/(b-a), 1/(b-a)] + assert t.interpolation == 'histogram' + + d.to_xml_element('distribution') + + +def test_maxwell(): + theta = 1.2895e6 + d = openmc.stats.Maxwell(theta) + assert d.theta == theta + assert len(d) == 1 + d.to_xml_element('distribution') + + +def test_watt(): + a, b = 0.965e6, 2.29e-6 + d = openmc.stats.Watt(a, b) + assert d.a == a + assert d.b == b + assert len(d) == 2 + d.to_xml_element('distribution') + + +def test_tabular(): + x = [0.0, 5.0, 7.0] + p = [0.1, 0.2, 0.05] + d = openmc.stats.Tabular(x, p, 'linear-linear') + assert d.x == x + assert d.p == p + assert d.interpolation == 'linear-linear' + assert len(d) == len(x) + d.to_xml_element('distribution') + + +def test_legendre(): + # Pu239 elastic scattering at 100 keV + coeffs = [1.000e+0, 1.536e-1, 1.772e-2, 5.945e-4, 3.497e-5, 1.881e-5] + d = openmc.stats.Legendre(coeffs) + assert d.coefficients == pytest.approx(coeffs) + assert len(d) == len(coeffs) + + # Integrating distribution should yield one + mu = np.linspace(-1., 1., 1000) + assert np.trapz(d(mu), mu) == pytest.approx(1.0, rel=1e-4) + + with pytest.raises(NotImplementedError): + d.to_xml_element('distribution') + +def test_mixture(): + d1 = openmc.stats.Uniform(0, 5) + d2 = openmc.stats.Uniform(3, 7) + p = [0.5, 0.5] + mix = openmc.stats.Mixture(p, [d1, d2]) + assert mix.probability == p + assert mix.distribution == [d1, d2] + assert len(mix) == 4 + + with pytest.raises(NotImplementedError): + mix.to_xml_element('distribution') From 058402954f5fedc455723c13b44c326009f5bb95 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 26 Jan 2018 10:50:29 -0600 Subject: [PATCH 147/282] Add tests for multivariate distributions in openmc.stats --- tests/unit_tests/test_stats.py | 89 ++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/tests/unit_tests/test_stats.py b/tests/unit_tests/test_stats.py index cef001526..3a91ecd8f 100644 --- a/tests/unit_tests/test_stats.py +++ b/tests/unit_tests/test_stats.py @@ -1,3 +1,5 @@ +from math import pi + import numpy as np import pytest import openmc @@ -77,6 +79,7 @@ def test_legendre(): with pytest.raises(NotImplementedError): d.to_xml_element('distribution') + def test_mixture(): d1 = openmc.stats.Uniform(0, 5) d2 = openmc.stats.Uniform(3, 7) @@ -88,3 +91,89 @@ def test_mixture(): with pytest.raises(NotImplementedError): mix.to_xml_element('distribution') + + +def test_polar_azimuthal(): + # default polar-azimuthal should be uniform in mu and phi + d = openmc.stats.PolarAzimuthal() + assert isinstance(d.mu, openmc.stats.Uniform) + assert d.mu.a == -1. + assert d.mu.b == 1. + assert isinstance(d.phi, openmc.stats.Uniform) + assert d.phi.a == 0. + assert d.phi.b == 2*pi + + mu = openmc.stats.Discrete(1., 1.) + phi = openmc.stats.Discrete(0., 1.) + d = openmc.stats.PolarAzimuthal(mu, phi) + assert d.mu == mu + assert d.phi == phi + + elem = d.to_xml_element() + assert elem.tag == 'angle' + assert elem.attrib['type'] == 'mu-phi' + assert elem.find('mu') is not None + assert elem.find('phi') is not None + + +def test_isotropic(): + d = openmc.stats.Isotropic() + elem = d.to_xml_element() + assert elem.tag == 'angle' + assert elem.attrib['type'] == 'isotropic' + + +def test_monodirectional(): + d = openmc.stats.Monodirectional((1., 0., 0.)) + assert d.reference_uvw == pytest.approx((1., 0., 0.)) + + elem = d.to_xml_element() + assert elem.tag == 'angle' + assert elem.attrib['type'] == 'monodirectional' + + +def test_cartesian(): + x = openmc.stats.Uniform(-10., 10.) + y = openmc.stats.Uniform(-10., 10.) + z = openmc.stats.Uniform(0., 20.) + d = openmc.stats.CartesianIndependent(x, y, z) + assert d.x == x + assert d.y == y + assert d.z == z + + elem = d.to_xml_element() + assert elem.tag == 'space' + assert elem.attrib['type'] == 'cartesian' + assert elem.find('x') is not None + assert elem.find('y') is not None + + +def test_box(): + lower_left = (-10., -10., -10.) + upper_right = (10., 10., 10.) + d = openmc.stats.Box(lower_left, upper_right) + assert d.lower_left == pytest.approx(lower_left) + assert d.upper_right == pytest.approx(upper_right) + assert not d.only_fissionable + + elem = d.to_xml_element() + assert elem.tag == 'space' + assert elem.attrib['type'] == 'box' + assert elem.find('parameters') is not None + + # only fissionable parameter + d2 = openmc.stats.Box(lower_left, upper_right, True) + assert d2.only_fissionable + elem = d2.to_xml_element() + assert elem.attrib['type'] == 'fission' + + +def test_point(): + p = (-4., 2., 10.) + d = openmc.stats.Point(p) + assert d.xyz == pytest.approx(p) + + elem = d.to_xml_element() + assert elem.tag == 'space' + assert elem.attrib['type'] == 'point' + assert elem.find('parameters') is not None From 39cd77a756c6122fae93e5fd2af820d4fd9bb8ef Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 26 Jan 2018 16:01:20 -0600 Subject: [PATCH 148/282] Have Travis install OpenMC during install step and have script call ctest --- .travis.yml | 11 +- tests/run_tests.py | 552 ------------------------------------- tools/ci/travis-install.py | 65 +++++ tools/ci/travis-install.sh | 3 + tools/ci/travis-script.sh | 13 +- 5 files changed, 83 insertions(+), 561 deletions(-) delete mode 100755 tests/run_tests.py create mode 100644 tools/ci/travis-install.py diff --git a/.travis.yml b/.travis.yml index de8b7dcc7..14e248411 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,18 +18,17 @@ env: global: - FC=gfortran - MPI_DIR=/usr - - PHDF5_DIR=/usr - - HDF5_DIR=/usr + - HDF5_ROOT=/usr - OMP_NUM_THREADS=2 - OPENMC_CROSS_SECTIONS=$HOME/nndc_hdf5/cross_sections.xml - OPENMC_ENDF_DATA=$HOME/endf-b-vii.1 - OPENMC_MULTIPOLE_LIBRARY=$HOME/multipole_lib - PATH=$PATH:$HOME/NJOY2016/build matrix: - - OPENMC_CONFIG="^hdf5-debug$" - - OPENMC_CONFIG="^omp-hdf5-debug$" - - OPENMC_CONFIG="^mpi-hdf5-debug$" - - OPENMC_CONFIG="^phdf5-debug$" + - OMP=n MPI=n PHDF5=n + - OMP=y MPI=n PHDF5=n + - OMP=n MPI=y PHDF5=n + - OMP=n MPI=y PHDF5=y before_install: - sudo add-apt-repository ppa:nschloe/hdf5-backports -y diff --git a/tests/run_tests.py b/tests/run_tests.py deleted file mode 100755 index 6e6b886a5..000000000 --- a/tests/run_tests.py +++ /dev/null @@ -1,552 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function - -import os -import sys -import shutil -import re -import glob -import socket -from subprocess import call, check_output -from collections import OrderedDict -from argparse import ArgumentParser - -# Command line parsing -parser = ArgumentParser() -parser.add_argument('-j', '--parallel', dest='n_procs', default='1', - help="Number of parallel jobs.") -parser.add_argument('-R', '--tests-regex', dest='regex_tests', - help="Run tests matching regular expression. \ - Test names are the directories present in tests folder.\ - This uses standard regex syntax to select tests.") -parser.add_argument('-C', '--build-config', dest='build_config', - help="Build configurations matching regular expression. \ - Specific build configurations can be printed out with \ - optional argument -p, --print. This uses standard \ - regex syntax to select build configurations.") -parser.add_argument('-l', '--list', action="store_true", - dest="list_build_configs", default=False, - help="List out build configurations.") -parser.add_argument("-p", "--project", dest="project", default="", - help="project name for build") -parser.add_argument("-D", "--dashboard", dest="dash", - help="Dash name -- Experimental, Nightly, Continuous") -parser.add_argument("-u", "--update", action="store_true", dest="update", - help="Allow CTest to update repo. (WARNING: may overwrite\ - changes that were not pushed.") -parser.add_argument("-s", "--script", action="store_true", dest="script", - help="Activate CTest scripting mode for coverage, valgrind\ - and dashboard capability.") -args = parser.parse_args() - -# Default compiler paths -FC = 'gfortran' -CC = 'gcc' -CXX = 'g++' -MPI_DIR = '/opt/mpich/3.2-gnu' -HDF5_DIR = '/opt/hdf5/1.8.16-gnu' -PHDF5_DIR = '/opt/phdf5/1.8.16-gnu' - -# Script mode for extra capability -script_mode = False - -# Override default compiler paths if environmental vars are found -if 'FC' in os.environ: - FC = os.environ['FC'] -if 'CC' in os.environ: - CC = os.environ['CC'] -if 'CXX' in os.environ: - CXX = os.environ['CXX'] -if 'MPI_DIR' in os.environ: - MPI_DIR = os.environ['MPI_DIR'] -if 'HDF5_DIR' in os.environ: - HDF5_DIR = os.environ['HDF5_DIR'] -if 'PHDF5_DIR' in os.environ: - PHDF5_DIR = os.environ['PHDF5_DIR'] - -# CTest script template -ctest_str = """set (CTEST_SOURCE_DIRECTORY "{source_dir}") -set (CTEST_BINARY_DIRECTORY "{build_dir}") - -set(CTEST_SITE "{host_name}") -set (CTEST_BUILD_NAME "{build_name}") -set (CTEST_CMAKE_GENERATOR "Unix Makefiles") -set (CTEST_BUILD_OPTIONS "{build_opts}") - -set(CTEST_UPDATE_COMMAND "git") - -set(CTEST_CONFIGURE_COMMAND "${{CMAKE_COMMAND}} -H${{CTEST_SOURCE_DIRECTORY}} -B${{CTEST_BINARY_DIRECTORY}} ${{CTEST_BUILD_OPTIONS}}") -set(CTEST_MEMORYCHECK_COMMAND "{valgrind_cmd}") -set(CTEST_MEMORYCHECK_COMMAND_OPTIONS "--tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes") -#set(CTEST_MEMORYCHECK_SUPPRESSIONS_FILE ${{CTEST_SOURCE_DIRECTORY}}/../tests/valgrind.supp) -set(MEM_CHECK {mem_check}) -if(MEM_CHECK) -set(ENV{{MEM_CHECK}} ${{MEM_CHECK}}) -endif() - -set(CTEST_COVERAGE_COMMAND "gcov") -set(COVERAGE {coverage}) -set(ENV{{COVERAGE}} ${{COVERAGE}}) - -{subproject} - -ctest_start("{dashboard}") -ctest_configure(RETURN_VALUE res) -{update} -ctest_build(RETURN_VALUE res) -if(NOT MEM_CHECK) -ctest_test({tests} PARALLEL_LEVEL {n_procs}, RETURN_VALUE res) -endif() -if(MEM_CHECK) -ctest_memcheck({tests} RETURN_VALUE res) -endif(MEM_CHECK) -if(COVERAGE) -ctest_coverage(RETURN_VALUE res) -endif(COVERAGE) -{submit} - -if (res EQUAL 0) -else() -message(FATAL_ERROR "") -endif() -""" - -# Define test data structure -tests = OrderedDict() - - -def cleanup(path): - """Remove generated output files.""" - for dirpath, dirnames, filenames in os.walk(path): - for fname in filenames: - for ext in ['.h5', '.ppm', '.voxel']: - if fname.endswith(ext) and fname != '1d_mgxs.h5': - os.remove(os.path.join(dirpath, fname)) - - -def which(program): - def is_exe(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - - fpath, fname = os.path.split(program) - if fpath: - if is_exe(program): - return program - else: - for path in os.environ["PATH"].split(os.pathsep): - path = path.strip('"') - exe_file = os.path.join(path, program) - if is_exe(exe_file): - return exe_file - return None - - -class Test(object): - def __init__(self, name, debug=False, optimize=False, mpi=False, openmp=False, - phdf5=False, valgrind=False, coverage=False): - self.name = name - self.debug = debug - self.optimize = optimize - self.mpi = mpi - self.openmp = openmp - self.phdf5 = phdf5 - self.valgrind = valgrind - self.coverage = coverage - self.success = True - self.msg = None - self.skipped = False - self.cmake = ['cmake', '-H..', '-Bbuild', - '-DPYTHON_EXECUTABLE=' + sys.executable] - - # Check for MPI - if self.mpi: - if os.path.exists(os.path.join(MPI_DIR, 'bin', 'mpifort')): - self.fc = os.path.join(MPI_DIR, 'bin', 'mpifort') - else: - self.fc = os.path.join(MPI_DIR, 'bin', 'mpif90') - self.cc = os.path.join(MPI_DIR, 'bin', 'mpicc') - self.cxx = os.path.join(MPI_DIR, 'bin', 'mpicxx') - else: - self.fc = FC - self.cc = CC - self.cxx = CXX - - # Sets the build name that will show up on the CDash - def get_build_name(self): - self.build_name = args.project + '_' + self.name - return self.build_name - - # Sets up build options for various tests. It is used both - # in script and non-script modes - def get_build_opts(self): - build_str = "" - if self.debug: - build_str += "-Ddebug=ON " - if self.optimize: - build_str += "-Doptimize=ON " - if not self.openmp: - build_str += "-Dopenmp=OFF " - if self.coverage: - build_str += "-Dcoverage=ON " - if self.phdf5: - build_str += "-DHDF5_PREFER_PARALLEL=ON " - else: - build_str += "-DHDF5_PREFER_PARALLEL=OFF " - self.build_opts = build_str - return self.build_opts - - # Write out the ctest script to tests directory - def create_ctest_script(self, ctest_vars): - with open('ctestscript.run', 'w') as fh: - fh.write(ctest_str.format(**ctest_vars)) - - # Runs the ctest script which performs all the cmake/ctest/cdash - def run_ctest_script(self): - os.environ['FC'] = self.fc - os.environ['CC'] = self.cc - os.environ['CXX'] = self.cxx - if self.mpi: - os.environ['MPI_DIR'] = MPI_DIR - if self.phdf5: - os.environ['HDF5_ROOT'] = PHDF5_DIR - else: - os.environ['HDF5_ROOT'] = HDF5_DIR - rc = call(['ctest', '-S', 'ctestscript.run', '-V']) - if rc != 0: - self.success = False - self.msg = 'Failed on ctest script.' - - # Runs cmake when in non-script mode - def run_cmake(self): - build_opts = self.build_opts.split() - self.cmake += build_opts - - os.environ['FC'] = self.fc - os.environ['CC'] = self.cc - os.environ['CXX'] = self.cxx - if self.mpi: - os.environ['MPI_DIR'] = MPI_DIR - if self.phdf5: - os.environ['HDF5_ROOT'] = PHDF5_DIR - self.cmake.append('-DHDF5_PREFER_PARALLEL=ON') - else: - os.environ['HDF5_ROOT'] = HDF5_DIR - self.cmake.append('-DHDF5_PREFER_PARALLEL=OFF') - rc = call(self.cmake) - if rc != 0: - self.success = False - self.msg = 'Failed on cmake.' - - # Runs make when in non-script mode - def run_make(self): - if not self.success: - return - - # Default make string - make_list = ['make', '-s'] - - # Check for parallel - if args.n_procs is not None: - make_list.append('-j') - make_list.append(args.n_procs) - - # Run make - rc = call(make_list) - if rc != 0: - self.success = False - self.msg = 'Failed on make.' - - # Runs ctest when in non-script mode - def run_ctests(self): - if not self.success: - return - - # Default ctest string - ctest_list = ['ctest'] - - # Check for parallel - if args.n_procs is not None: - ctest_list.append('-j') - ctest_list.append(args.n_procs) - - # Check for subset of tests - if args.regex_tests is not None: - ctest_list.append('-R') - ctest_list.append(args.regex_tests) - - # Run ctests - rc = call(ctest_list) - if rc != 0: - self.success = False - self.msg = 'Failed on testing.' - - -# Simple function to add a test to the global tests dictionary -def add_test(name, debug=False, optimize=False, mpi=False, openmp=False, - phdf5=False, valgrind=False, coverage=False): - tests.update({name: Test(name, debug, optimize, mpi, openmp, phdf5, - valgrind, coverage)}) - -# List of all tests that may be run. User can add -C to command line to specify -# a subset of these configurations -add_test('hdf5-normal') -add_test('hdf5-debug', debug=True) -add_test('hdf5-optimize', optimize=True) -add_test('omp-hdf5-normal', openmp=True) -add_test('omp-hdf5-debug', openmp=True, debug=True) -add_test('omp-hdf5-optimize', openmp=True, optimize=True) -add_test('mpi-hdf5-normal', mpi=True) -add_test('mpi-hdf5-debug', mpi=True, debug=True) -add_test('mpi-hdf5-optimize', mpi=True, optimize=True) -add_test('phdf5-normal', mpi=True, phdf5=True) -add_test('phdf5-debug', mpi=True, phdf5=True, debug=True) -add_test('phdf5-optimize', mpi=True, phdf5=True, optimize=True) -add_test('phdf5-omp-normal', mpi=True, phdf5=True, openmp=True) -add_test('phdf5-omp-debug', mpi=True, phdf5=True, openmp=True, debug=True) -add_test('phdf5-omp-optimize', mpi=True, phdf5=True, openmp=True, optimize=True) -add_test('hdf5-debug_valgrind', debug=True, valgrind=True) -add_test('hdf5-debug_coverage', debug=True, coverage=True) - -# Check to see if we should just print build configuration information to user -if args.list_build_configs: - for key in tests: - print('Configuration Name: {0}'.format(key)) - print(' Debug Flags:..........{0}'.format(tests[key].debug)) - print(' Optimization Flags:...{0}'.format(tests[key].optimize)) - print(' MPI Active:...........{0}'.format(tests[key].mpi)) - print(' OpenMP Active:........{0}'.format(tests[key].openmp)) - print(' Valgrind Test:........{0}'.format(tests[key].valgrind)) - print(' Coverage Test:........{0}\n'.format(tests[key].coverage)) - exit() - -# Delete items of dictionary that don't match regular expression -if args.build_config is not None: - to_delete = [] - for key in tests: - if not re.search(args.build_config, key): - to_delete.append(key) - for key in to_delete: - del tests[key] - -# Check for dashboard and determine whether to push results to server -# Note that there are only 3 basic dashboards: -# Experimental, Nightly, Continuous. On the CDash end, these can be -# reorganized into groups when a hostname, dashboard and build name -# are matched. -if args.dash is None: - dash = 'Experimental' - submit = '' -else: - dash = args.dash - submit = 'ctest_submit()' - -# Check for update command, which will run git fetch/merge and will delete -# any changes to repo that were not pushed to remote origin -update = 'ctest_update()' if args.update else '' - -# Check for CTest scipts mode -# Sets up whether we should use just the basic ctest command or use -# CTest scripting to perform tests. -script_mode = (args.dash is not None or args.script) - -# Setup CTest script vars. Not used in non-script mode -pwd = os.getcwd() -ctest_vars = { - 'source_dir': os.path.join(pwd, os.pardir), - 'build_dir': os.path.join(pwd, 'build'), - 'host_name': socket.gethostname(), - 'dashboard': dash, - 'submit': submit, - 'update': update, - 'n_procs': args.n_procs -} - -# Check project name -subprop = "set_property(GLOBAL PROPERTY SubProject {0})" -if args.project == "": - ctest_vars.update({'subproject': ''}) -elif args.project == 'develop': - ctest_vars.update({'subproject': ''}) -else: - ctest_vars.update({'subproject': subprop.format(args.project)}) - -# Set up default valgrind tests (subset of all tests) -# Currently takes too long to run all the tests with valgrind -# Only used in script mode -valgrind_default_tests = "cmfd_feed|confidence_intervals|\ -density|eigenvalue_genperbatch|energy_grid|entropy|\ -lattice_multiple|output|plotreflective_plane|\ -rotation|salphabetascore_absorption|seed|source_energy_mono|\ -sourcepoint_batch|statepoint_interval|survival_biasing|\ -tally_assumesep|translation|uniform_fs|universe|void" - -# Delete items of dictionary if valgrind or coverage and not in script mode -to_delete = [] -if not script_mode: - for key in tests: - if re.search('valgrind|coverage', key): - to_delete.append(key) - -for key in to_delete: - del tests[key] - -# Check if tests is empty -if not tests: - print('No tests to run.') - exit() - -# Begin testing -shutil.rmtree('build', ignore_errors=True) -cleanup('.') -for key in iter(tests): - test = tests[key] - - # Extra display if not in script mode - if not script_mode: - print('-'*(len(key) + 6)) - print(key + ' tests') - print('-'*(len(key) + 6)) - sys.stdout.flush() - - # Verify fortran compiler exists - if which(test.fc) is None: - test.msg = 'Compiler not found: {0}'.format(test.fc) - test.success = False - continue - - # Verify valgrind command exists - if test.valgrind: - valgrind_cmd = which('valgrind') - if valgrind_cmd is None: - test.msg = 'No valgrind executable found.' - test.success = False - continue - else: - valgrind_cmd = '' - - # Verify gcov/lcov exist - if test.coverage: - if which('gcov') is None: - test.msg = 'No {} executable found.'.format(exe) - test.success = False - continue - - # Set test specific CTest script vars. Not used in non-script mode - ctest_vars.update({'build_name': test.get_build_name()}) - ctest_vars.update({'build_opts': test.get_build_opts()}) - ctest_vars.update({'mem_check': test.valgrind}) - ctest_vars.update({'coverage': test.coverage}) - ctest_vars.update({'valgrind_cmd': valgrind_cmd}) - - # Check for user custom tests - # INCLUDE is a CTest command that allows for a subset - # of tests to be executed. Only used in script mode. - if args.regex_tests is None: - ctest_vars.update({'tests': ''}) - - # No user tests, use default valgrind tests - if test.valgrind: - ctest_vars.update({'tests': 'INCLUDE {0}'. - format(valgrind_default_tests)}) - else: - ctest_vars.update({'tests': 'INCLUDE {0}'. - format(args.regex_tests)}) - - # Main part of code that does the ctest execution. - # It is broken up by two modes, script and non-script - if script_mode: - - # Create ctest script - test.create_ctest_script(ctest_vars) - - # Run test - test.run_ctest_script() - - else: - - # Run CMAKE to configure build - test.run_cmake() - - # Go into build directory - os.chdir('build') - - # Build OpenMC - test.run_make() - - # Run tests - test.run_ctests() - - # Leave build directory - os.chdir(os.pardir) - - # Copy over log file - if script_mode: - logfile = glob.glob('build/Testing/Temporary/LastTest_*.log') - else: - logfile = glob.glob('build/Testing/Temporary/LastTest.log') - if len(logfile) > 0: - logfilename = os.path.split(logfile[0])[1] - logfilename = os.path.splitext(logfilename)[0] - logfilename = logfilename + '_{0}.log'.format(test.name) - shutil.copy(logfile[0], logfilename) - - # For coverage builds, use lcov to generate HTML output - if test.coverage: - if which('lcov') is None or which('genhtml') is None: - print('No lcov/genhtml command found. ' - 'Could not generate coverage report.') - else: - shutil.rmtree('coverage', ignore_errors=True) - call(['lcov', '--directory', '.', '--capture', - '--output-file', 'coverage.info']) - call(['genhtml', '--output-directory', 'coverage', 'coverage.info']) - os.remove('coverage.info') - - if test.valgrind: - # Copy memcheck output to memcheck directory - shutil.rmtree('memcheck', ignore_errors=True) - os.mkdir('memcheck') - memcheck_out = glob.glob('build/Testing/Temporary/MemoryChecker.*.log') - for fname in memcheck_out: - shutil.copy(fname, 'memcheck/') - - # Remove generated XML files - xml_files = check_output(['git', 'ls-files', '.', '--exclude-standard', - '--others']).split() - for f in xml_files: - os.remove(f) - - # Clear build directory and remove binary and hdf5 files - shutil.rmtree('build', ignore_errors=True) - if script_mode: - os.remove('ctestscript.run') - cleanup('.') - -# Print out summary of results -print('\n' + '='*54) -print('Summary of Compilation Option Testing:\n') - -if sys.stdout.isatty(): - OK = '\033[92m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' -else: - OK = '' - FAIL = '' - ENDC = '' - BOLD = '' - -return_code = 0 - -for test in tests: - print(test + '.'*(50 - len(test)), end='') - if tests[test].success: - print(BOLD + OK + '[OK]' + ENDC) - else: - print(BOLD + FAIL + '[FAILED]' + ENDC) - print(' '*len(test)+tests[test].msg) - return_code = 1 - -sys.exit(return_code) diff --git a/tools/ci/travis-install.py b/tools/ci/travis-install.py new file mode 100644 index 000000000..0e4dbdc01 --- /dev/null +++ b/tools/ci/travis-install.py @@ -0,0 +1,65 @@ +import os +import shutil +import subprocess + + +def which(program): + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None + + +def install(omp=False, mpi=False, phdf5=False): + # Create build directory and change to it + shutil.rmtree('build', ignore_errors=True) + os.mkdir('build') + os.chdir('build') + + # Build in debug mode by default + cmake_cmd = ['cmake', '-Ddebug=on'] + + # Turn off OpenMP if specified + if not omp: + cmake_cmd.append('-Dopenmp=off') + + # For MPI, we just need to change the Fortran compiler + if mpi: + os.environ['FC'] = 'mpifort' if which('mpifort') else 'mpif90' + + # Tell CMake to prefer parallel HDF5 if specified + if phdf5: + if not mpi: + raise ValueError('Parallel HDF5 must be used in ' + 'conjunction with MPI.') + cmake_cmd.append('-DHDF5_PREFER_PARALLEL=on') + + # Build and install + cmake_cmd.append('..') + subprocess.call(cmake_cmd) + subprocess.call(['make', '-j']) + subprocess.call(['make', 'install']) + + +def main(): + # Convert Travis matrix environment variables into arguments for install() + omp = (os.environ.get('OMP') == 'y') + mpi = (os.environ.get('MPI') == 'y') + phdf5 = (os.environ.get('PHDF5') == 'y') + + # Build and install + install(omp, mpi, phdf5) + + +if __name__ == '__main__': + main() diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index 354ae7f7f..6051f03aa 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -15,5 +15,8 @@ if [[ "$TRAVIS_PYTHON_VERSION" == "3.4" ]]; then pip install pandas==0.20.3 fi +# Build and install +python tools/ci/travis-install.py + # Install OpenMC in editable mode pip install -e .[test] diff --git a/tools/ci/travis-script.sh b/tools/ci/travis-script.sh index 978a2811c..2934cb59a 100755 --- a/tools/ci/travis-script.sh +++ b/tools/ci/travis-script.sh @@ -1,8 +1,15 @@ #!/bin/bash set -ex -cd tests -if [[ $TRAVIS_PYTHON_VERSION == "3.4" && $OPENMC_CONFIG == '^hdf5-debug$' ]]; then + +# Run regression test suite +cd build +ctest + +# Run source check +cd ../tests +if [[ $TRAVIS_PYTHON_VERSION == "3.4" && $OMP == 'n' && $MPI == 'n' ]]; then ./check_source.py fi -./run_tests.py -C $OPENMC_CONFIG -j 2 + +# Run unit tests pytest --cov=../openmc -v unit_tests/ From c3af1c915b8b799ce59ec1accd124a1595c0cd41 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 28 Jan 2018 15:08:24 -0600 Subject: [PATCH 149/282] Move regression tests into separate directory --- CMakeLists.txt | 8 ++++---- openmc/examples.py | 2 +- .../asymmetric_lattice}/inputs_true.dat | 0 .../asymmetric_lattice}/results_true.dat | 0 .../asymmetric_lattice/test.py} | 2 +- .../cmfd_feed}/cmfd.xml | 0 .../cmfd_feed}/geometry.xml | 0 .../cmfd_feed}/materials.xml | 0 .../cmfd_feed}/results_true.dat | 0 .../cmfd_feed}/settings.xml | 0 .../cmfd_feed}/tallies.xml | 0 .../cmfd_feed/test.py} | 2 +- .../cmfd_nofeed}/cmfd.xml | 0 .../cmfd_nofeed}/geometry.xml | 0 .../cmfd_nofeed}/materials.xml | 0 .../cmfd_nofeed}/results_true.dat | 0 .../cmfd_nofeed}/settings.xml | 0 .../cmfd_nofeed}/tallies.xml | 0 .../cmfd_nofeed/test.py} | 2 +- .../complex_cell}/geometry.xml | 0 .../complex_cell}/materials.xml | 0 .../complex_cell}/results_true.dat | 0 .../complex_cell}/settings.xml | 0 .../complex_cell}/tallies.xml | 0 .../complex_cell/test.py} | 2 +- .../confidence_intervals}/geometry.xml | 0 .../confidence_intervals}/materials.xml | 0 .../confidence_intervals}/results_true.dat | 0 .../confidence_intervals}/settings.xml | 0 .../confidence_intervals}/tallies.xml | 0 .../confidence_intervals/test.py} | 2 +- .../create_fission_neutrons}/inputs_true.dat | 0 .../create_fission_neutrons}/results_true.dat | 0 .../create_fission_neutrons/test.py} | 2 +- .../density}/geometry.xml | 0 .../density}/materials.xml | 0 .../density}/results_true.dat | 0 .../density}/settings.xml | 0 .../density/test.py} | 2 +- .../diff_tally}/inputs_true.dat | 0 .../diff_tally}/results_true.dat | 0 .../diff_tally/test.py} | 2 +- .../distribmat}/inputs_true.dat | 0 .../distribmat}/results_true.dat | 0 .../distribmat/test.py} | 2 +- .../eigenvalue_genperbatch}/geometry.xml | 0 .../eigenvalue_genperbatch}/materials.xml | 0 .../eigenvalue_genperbatch}/results_true.dat | 0 .../eigenvalue_genperbatch}/settings.xml | 0 .../eigenvalue_genperbatch/test.py} | 2 +- .../eigenvalue_no_inactive}/geometry.xml | 0 .../eigenvalue_no_inactive}/materials.xml | 0 .../eigenvalue_no_inactive}/results_true.dat | 0 .../eigenvalue_no_inactive}/settings.xml | 0 .../eigenvalue_no_inactive/test.py} | 2 +- .../energy_cutoff}/inputs_true.dat | 0 .../energy_cutoff}/results_true.dat | 0 .../energy_cutoff/test.py} | 2 +- .../energy_grid}/geometry.xml | 0 .../energy_grid}/materials.xml | 0 .../energy_grid}/results_true.dat | 0 .../energy_grid}/settings.xml | 0 tests/regression_tests/energy_grid/test.py | 11 +++++++++++ .../energy_laws}/geometry.xml | 0 .../energy_laws}/materials.xml | 0 .../energy_laws}/results_true.dat | 0 .../energy_laws}/settings.xml | 0 .../energy_laws/test.py} | 2 +- .../enrichment/test.py} | 1 - .../entropy}/geometry.xml | 0 .../entropy}/materials.xml | 0 .../entropy}/results_true.dat | 0 .../entropy}/settings.xml | 0 .../entropy/test.py} | 2 +- .../filter_distribcell}/case-1/geometry.xml | 0 .../filter_distribcell}/case-1/materials.xml | 0 .../filter_distribcell}/case-1/results_true.dat | 0 .../filter_distribcell}/case-1/settings.xml | 0 .../filter_distribcell}/case-1/tallies.xml | 0 .../filter_distribcell}/case-2/geometry.xml | 0 .../filter_distribcell}/case-2/materials.xml | 0 .../filter_distribcell}/case-2/results_true.dat | 0 .../filter_distribcell}/case-2/settings.xml | 0 .../filter_distribcell}/case-2/tallies.xml | 0 .../filter_distribcell}/case-3/geometry.xml | 0 .../filter_distribcell}/case-3/materials.xml | 0 .../filter_distribcell}/case-3/results_true.dat | 0 .../filter_distribcell}/case-3/settings.xml | 0 .../filter_distribcell}/case-3/tallies.xml | 0 .../filter_distribcell}/case-4/geometry.xml | 0 .../filter_distribcell}/case-4/materials.xml | 0 .../filter_distribcell}/case-4/results_true.dat | 0 .../filter_distribcell}/case-4/settings.xml | 0 .../filter_distribcell}/case-4/tallies.xml | 0 .../filter_distribcell/test.py} | 2 +- .../filter_energyfun}/inputs_true.dat | 0 .../filter_energyfun}/results_true.dat | 0 .../filter_energyfun/test.py} | 2 +- .../filter_mesh}/inputs_true.dat | 0 .../filter_mesh}/results_true.dat | 0 .../filter_mesh/test.py} | 2 +- .../fixed_source}/inputs_true.dat | 0 .../fixed_source}/results_true.dat | 0 .../fixed_source/test.py} | 2 +- .../infinite_cell}/geometry.xml | 0 .../infinite_cell}/materials.xml | 0 .../infinite_cell}/results_true.dat | 0 .../infinite_cell}/settings.xml | 0 tests/regression_tests/infinite_cell/test.py | 11 +++++++++++ .../iso_in_lab}/inputs_true.dat | 0 .../iso_in_lab}/results_true.dat | 0 .../iso_in_lab/test.py} | 2 +- .../lattice}/geometry.xml | 0 .../lattice}/materials.xml | 0 .../lattice}/results_true.dat | 0 .../lattice}/settings.xml | 0 tests/regression_tests/lattice/test.py | 11 +++++++++++ .../lattice_hex}/geometry.xml | 0 .../lattice_hex}/materials.xml | 0 .../lattice_hex}/plots.xml | 0 .../lattice_hex}/results_true.dat | 0 .../lattice_hex}/settings.xml | 0 tests/regression_tests/lattice_hex/test.py | 11 +++++++++++ .../lattice_mixed}/geometry.xml | 0 .../lattice_mixed}/materials.xml | 0 .../lattice_mixed}/plots.xml | 0 .../lattice_mixed}/results_true.dat | 0 .../lattice_mixed}/settings.xml | 0 tests/regression_tests/lattice_mixed/test.py | 11 +++++++++++ .../lattice_multiple}/geometry.xml | 0 .../lattice_multiple}/materials.xml | 0 .../lattice_multiple}/results_true.dat | 0 .../lattice_multiple}/settings.xml | 0 tests/regression_tests/lattice_multiple/test.py | 11 +++++++++++ .../mg_basic}/inputs_true.dat | 2 +- .../mg_basic}/results_true.dat | 0 .../mg_basic/test.py} | 2 +- .../mg_convert}/inputs_true.dat | 0 .../mg_convert}/results_true.dat | 0 .../mg_convert/test.py} | 2 +- .../mg_legendre}/inputs_true.dat | 2 +- .../mg_legendre}/results_true.dat | 0 .../mg_legendre/test.py} | 2 +- .../mg_max_order}/inputs_true.dat | 2 +- .../mg_max_order}/results_true.dat | 0 .../mg_max_order/test.py} | 2 +- .../mg_nuclide}/inputs_true.dat | 2 +- .../mg_nuclide}/results_true.dat | 0 .../mg_nuclide/test.py} | 2 +- .../mg_survival_biasing}/inputs_true.dat | 2 +- .../mg_survival_biasing}/results_true.dat | 0 .../mg_survival_biasing/test.py} | 2 +- .../mg_tallies}/inputs_true.dat | 2 +- .../mg_tallies}/results_true.dat | 0 .../mg_tallies/test.py} | 2 +- .../mgxs_library_ce_to_mg}/inputs_true.dat | 0 .../mgxs_library_ce_to_mg}/results_true.dat | 0 .../mgxs_library_ce_to_mg/test.py} | 2 +- .../mgxs_library_condense}/inputs_true.dat | 0 .../mgxs_library_condense}/results_true.dat | 0 .../mgxs_library_condense/test.py} | 2 +- .../mgxs_library_distribcell}/inputs_true.dat | 0 .../mgxs_library_distribcell}/results_true.dat | 0 .../mgxs_library_distribcell/test.py} | 2 +- .../mgxs_library_hdf5}/inputs_true.dat | 0 .../mgxs_library_hdf5}/results_true.dat | 0 .../mgxs_library_hdf5/test.py} | 2 +- .../mgxs_library_mesh}/inputs_true.dat | 0 .../mgxs_library_mesh}/results_true.dat | 0 .../mgxs_library_mesh/test.py} | 2 +- .../mgxs_library_no_nuclides}/inputs_true.dat | 0 .../mgxs_library_no_nuclides}/results_true.dat | 0 .../mgxs_library_no_nuclides/test.py} | 2 +- .../mgxs_library_nuclides}/inputs_true.dat | 0 .../mgxs_library_nuclides}/results_true.dat | 0 .../mgxs_library_nuclides/test.py} | 2 +- .../multipole}/inputs_true.dat | 0 .../multipole}/results_true.dat | 0 .../multipole/test.py} | 2 +- .../output}/geometry.xml | 0 .../output}/materials.xml | 0 .../output}/results_true.dat | 0 .../output}/settings.xml | 0 .../output/test.py} | 2 +- .../particle_restart_eigval}/geometry.xml | 0 .../particle_restart_eigval}/materials.xml | 0 .../particle_restart_eigval}/results_true.dat | 0 .../particle_restart_eigval}/settings.xml | 0 .../particle_restart_eigval/test.py} | 2 +- .../particle_restart_fixed}/geometry.xml | 0 .../particle_restart_fixed}/materials.xml | 0 .../particle_restart_fixed}/results_true.dat | 0 .../particle_restart_fixed}/settings.xml | 0 .../particle_restart_fixed/test.py} | 2 +- .../periodic}/inputs_true.dat | 0 .../periodic}/results_true.dat | 0 .../periodic/test.py} | 2 +- .../{test_plot => regression_tests/plot}/geometry.xml | 0 .../plot}/materials.xml | 0 tests/{test_plot => regression_tests/plot}/plots.xml | 0 .../plot}/results_true.dat | 0 .../{test_plot => regression_tests/plot}/settings.xml | 0 .../test_plot.py => regression_tests/plot/test.py} | 2 +- .../ptables_off}/geometry.xml | 0 .../ptables_off}/materials.xml | 0 .../ptables_off}/results_true.dat | 0 .../ptables_off}/settings.xml | 0 tests/regression_tests/ptables_off/test.py | 11 +++++++++++ .../quadric_surfaces}/geometry.xml | 0 .../quadric_surfaces}/materials.xml | 0 .../quadric_surfaces}/results_true.dat | 0 .../quadric_surfaces}/settings.xml | 0 tests/regression_tests/quadric_surfaces/test.py | 11 +++++++++++ .../reflective_plane}/geometry.xml | 0 .../reflective_plane}/materials.xml | 0 .../reflective_plane}/results_true.dat | 0 .../reflective_plane}/settings.xml | 0 tests/regression_tests/reflective_plane/test.py | 11 +++++++++++ .../resonance_scattering}/inputs_true.dat | 0 .../resonance_scattering}/results_true.dat | 0 .../resonance_scattering/test.py} | 2 +- .../rotation}/geometry.xml | 0 .../rotation}/materials.xml | 0 .../rotation}/results_true.dat | 0 .../rotation}/settings.xml | 0 tests/regression_tests/rotation/test.py | 11 +++++++++++ .../salphabeta}/inputs_true.dat | 0 .../salphabeta}/results_true.dat | 0 .../salphabeta/test.py} | 2 +- .../score_current}/geometry.xml | 0 .../score_current}/materials.xml | 0 .../score_current}/results_true.dat | 0 .../score_current}/settings.xml | 0 .../score_current}/tallies.xml | 0 .../score_current/test.py} | 2 +- .../{test_seed => regression_tests/seed}/geometry.xml | 0 .../seed}/materials.xml | 0 .../seed}/results_true.dat | 0 .../{test_seed => regression_tests/seed}/settings.xml | 0 tests/regression_tests/seed/test.py | 11 +++++++++++ .../source}/inputs_true.dat | 0 .../source}/results_true.dat | 0 .../source/test.py} | 2 +- .../source_file}/geometry.xml | 0 .../source_file}/materials.xml | 0 .../source_file}/results_true.dat | 0 .../source_file}/settings.xml | 0 .../source_file/test.py} | 2 +- .../sourcepoint_batch}/geometry.xml | 0 .../sourcepoint_batch}/materials.xml | 0 .../sourcepoint_batch}/results_true.dat | 0 .../sourcepoint_batch}/settings.xml | 0 .../sourcepoint_batch/test.py} | 2 +- .../sourcepoint_latest}/geometry.xml | 0 .../sourcepoint_latest}/materials.xml | 0 .../sourcepoint_latest}/results_true.dat | 0 .../sourcepoint_latest}/settings.xml | 0 .../sourcepoint_latest/test.py} | 2 +- .../sourcepoint_restart}/geometry.xml | 0 .../sourcepoint_restart}/materials.xml | 0 .../sourcepoint_restart}/results_true.dat | 0 .../sourcepoint_restart}/settings.xml | 0 .../sourcepoint_restart}/tallies.xml | 0 tests/regression_tests/sourcepoint_restart/test.py | 11 +++++++++++ .../statepoint_batch}/geometry.xml | 0 .../statepoint_batch}/materials.xml | 0 .../statepoint_batch}/results_true.dat | 0 .../statepoint_batch}/settings.xml | 0 .../statepoint_batch/test.py} | 2 +- .../statepoint_restart}/geometry.xml | 0 .../statepoint_restart}/materials.xml | 0 .../statepoint_restart}/results_true.dat | 0 .../statepoint_restart}/settings.xml | 0 .../statepoint_restart}/tallies.xml | 0 .../statepoint_restart/test.py} | 2 +- .../statepoint_sourcesep}/geometry.xml | 0 .../statepoint_sourcesep}/materials.xml | 0 .../statepoint_sourcesep}/results_true.dat | 0 .../statepoint_sourcesep}/settings.xml | 0 .../statepoint_sourcesep/test.py} | 2 +- .../surface_tally}/inputs_true.dat | 0 .../surface_tally}/results_true.dat | 0 .../surface_tally/test.py} | 2 +- .../survival_biasing}/geometry.xml | 0 .../survival_biasing}/materials.xml | 0 .../survival_biasing}/results_true.dat | 0 .../survival_biasing}/settings.xml | 0 .../survival_biasing}/tallies.xml | 0 tests/regression_tests/survival_biasing/test.py | 11 +++++++++++ .../tallies}/inputs_true.dat | 0 .../tallies}/results_true.dat | 0 .../tallies/test.py} | 2 +- .../tally_aggregation}/inputs_true.dat | 0 .../tally_aggregation}/results_true.dat | 0 .../tally_aggregation/test.py} | 2 +- .../tally_arithmetic}/inputs_true.dat | 0 .../tally_arithmetic}/results_true.dat | 0 .../tally_arithmetic/test.py} | 2 +- .../tally_assumesep}/geometry.xml | 0 .../tally_assumesep}/materials.xml | 0 .../tally_assumesep}/results_true.dat | 0 .../tally_assumesep}/settings.xml | 0 .../tally_assumesep}/tallies.xml | 0 tests/regression_tests/tally_assumesep/test.py | 11 +++++++++++ .../tally_nuclides}/geometry.xml | 0 .../tally_nuclides}/materials.xml | 0 .../tally_nuclides}/results_true.dat | 0 .../tally_nuclides}/settings.xml | 0 .../tally_nuclides}/tallies.xml | 0 tests/regression_tests/tally_nuclides/test.py | 11 +++++++++++ .../tally_slice_merge}/inputs_true.dat | 0 .../tally_slice_merge}/results_true.dat | 0 .../tally_slice_merge/test.py} | 2 +- .../trace}/geometry.xml | 0 .../trace}/materials.xml | 0 .../trace}/results_true.dat | 0 .../trace}/settings.xml | 0 tests/regression_tests/trace/test.py | 11 +++++++++++ .../track_output}/geometry.xml | 0 .../track_output}/materials.xml | 0 .../track_output}/results_true.dat | 0 .../track_output}/settings.xml | 0 .../track_output/test.py} | 2 +- .../translation}/geometry.xml | 0 .../translation}/materials.xml | 0 .../translation}/results_true.dat | 0 .../translation}/settings.xml | 0 tests/regression_tests/translation/test.py | 11 +++++++++++ .../trigger_batch_interval}/geometry.xml | 0 .../trigger_batch_interval}/materials.xml | 0 .../trigger_batch_interval}/results_true.dat | 0 .../trigger_batch_interval}/settings.xml | 0 .../trigger_batch_interval}/tallies.xml | 0 .../trigger_batch_interval/test.py} | 2 +- .../trigger_no_batch_interval}/geometry.xml | 0 .../trigger_no_batch_interval}/materials.xml | 0 .../trigger_no_batch_interval}/results_true.dat | 0 .../trigger_no_batch_interval}/settings.xml | 0 .../trigger_no_batch_interval}/tallies.xml | 0 .../trigger_no_batch_interval/test.py} | 2 +- .../trigger_no_status}/geometry.xml | 0 .../trigger_no_status}/materials.xml | 0 .../trigger_no_status}/results_true.dat | 0 .../trigger_no_status}/settings.xml | 0 .../trigger_no_status}/tallies.xml | 0 tests/regression_tests/trigger_no_status/test.py | 11 +++++++++++ .../trigger_tallies}/geometry.xml | 0 .../trigger_tallies}/materials.xml | 0 .../trigger_tallies}/results_true.dat | 0 .../trigger_tallies}/settings.xml | 0 .../trigger_tallies}/tallies.xml | 0 .../trigger_tallies/test.py} | 2 +- .../triso}/inputs_true.dat | 0 .../triso}/results_true.dat | 0 .../test_triso.py => regression_tests/triso/test.py} | 2 +- .../uniform_fs}/geometry.xml | 0 .../uniform_fs}/materials.xml | 0 .../uniform_fs}/results_true.dat | 0 .../uniform_fs}/settings.xml | 0 tests/regression_tests/uniform_fs/test.py | 11 +++++++++++ .../universe}/geometry.xml | 0 .../universe}/materials.xml | 0 .../universe}/results_true.dat | 0 .../universe}/settings.xml | 0 tests/regression_tests/universe/test.py | 11 +++++++++++ .../{test_void => regression_tests/void}/geometry.xml | 0 .../void}/materials.xml | 0 .../void}/results_true.dat | 0 .../{test_void => regression_tests/void}/settings.xml | 0 tests/regression_tests/void/test.py | 11 +++++++++++ .../volume_calc}/inputs_true.dat | 0 .../volume_calc}/results_true.dat | 0 .../volume_calc/test.py} | 2 +- tests/test_complex_cell/test_complex_cell.py | 10 ---------- tests/test_infinite_cell/test_infinite_cell.py | 11 ----------- tests/test_lattice/test_lattice.py | 11 ----------- tests/test_lattice_hex/test_lattice_hex.py | 11 ----------- tests/test_lattice_mixed/test_lattice_mixed.py | 11 ----------- tests/test_lattice_multiple/test_lattice_multiple.py | 11 ----------- tests/test_ptables_off/test_ptables_off.py | 11 ----------- tests/test_quadric_surfaces/test_quadric_surfaces.py | 11 ----------- tests/test_reflective_plane/test_reflective_plane.py | 11 ----------- tests/test_rotation/test_rotation.py | 11 ----------- tests/test_seed/test_seed.py | 11 ----------- .../test_sourcepoint_restart.py | 11 ----------- tests/test_survival_biasing/test_survival_biasing.py | 11 ----------- tests/test_tally_assumesep/test_tally_assumesep.py | 11 ----------- tests/test_tally_nuclides/test_tally_nuclides.py | 11 ----------- tests/test_trace/test_trace.py | 11 ----------- tests/test_translation/test_translation.py | 11 ----------- .../test_trigger_no_status/test_trigger_no_status.py | 11 ----------- tests/test_uniform_fs/test_uniform_fs.py | 11 ----------- tests/test_universe/test_universe.py | 11 ----------- tests/test_void/test_void.py | 11 ----------- 394 files changed, 302 insertions(+), 302 deletions(-) rename tests/{test_asymmetric_lattice => regression_tests/asymmetric_lattice}/inputs_true.dat (100%) rename tests/{test_asymmetric_lattice => regression_tests/asymmetric_lattice}/results_true.dat (100%) rename tests/{test_asymmetric_lattice/test_asymmetric_lattice.py => regression_tests/asymmetric_lattice/test.py} (98%) rename tests/{test_cmfd_feed => regression_tests/cmfd_feed}/cmfd.xml (100%) rename tests/{test_cmfd_feed => regression_tests/cmfd_feed}/geometry.xml (100%) rename tests/{test_cmfd_feed => regression_tests/cmfd_feed}/materials.xml (100%) rename tests/{test_cmfd_feed => regression_tests/cmfd_feed}/results_true.dat (100%) rename tests/{test_cmfd_feed => regression_tests/cmfd_feed}/settings.xml (100%) rename tests/{test_cmfd_feed => regression_tests/cmfd_feed}/tallies.xml (100%) rename tests/{test_cmfd_feed/test_cmfd_feed.py => regression_tests/cmfd_feed/test.py} (77%) rename tests/{test_cmfd_nofeed => regression_tests/cmfd_nofeed}/cmfd.xml (100%) rename tests/{test_cmfd_nofeed => regression_tests/cmfd_nofeed}/geometry.xml (100%) rename tests/{test_cmfd_nofeed => regression_tests/cmfd_nofeed}/materials.xml (100%) rename tests/{test_cmfd_nofeed => regression_tests/cmfd_nofeed}/results_true.dat (100%) rename tests/{test_cmfd_nofeed => regression_tests/cmfd_nofeed}/settings.xml (100%) rename tests/{test_cmfd_nofeed => regression_tests/cmfd_nofeed}/tallies.xml (100%) rename tests/{test_cmfd_nofeed/test_cmfd_nofeed.py => regression_tests/cmfd_nofeed/test.py} (77%) rename tests/{test_complex_cell => regression_tests/complex_cell}/geometry.xml (100%) rename tests/{test_complex_cell => regression_tests/complex_cell}/materials.xml (100%) rename tests/{test_complex_cell => regression_tests/complex_cell}/results_true.dat (100%) rename tests/{test_complex_cell => regression_tests/complex_cell}/settings.xml (100%) rename tests/{test_complex_cell => regression_tests/complex_cell}/tallies.xml (100%) rename tests/{test_confidence_intervals/test_confidence_intervals.py => regression_tests/complex_cell/test.py} (76%) rename tests/{test_confidence_intervals => regression_tests/confidence_intervals}/geometry.xml (100%) rename tests/{test_confidence_intervals => regression_tests/confidence_intervals}/materials.xml (100%) rename tests/{test_confidence_intervals => regression_tests/confidence_intervals}/results_true.dat (100%) rename tests/{test_confidence_intervals => regression_tests/confidence_intervals}/settings.xml (100%) rename tests/{test_confidence_intervals => regression_tests/confidence_intervals}/tallies.xml (100%) rename tests/{test_density/test_density.py => regression_tests/confidence_intervals/test.py} (76%) mode change 100644 => 100755 rename tests/{test_create_fission_neutrons => regression_tests/create_fission_neutrons}/inputs_true.dat (100%) rename tests/{test_create_fission_neutrons => regression_tests/create_fission_neutrons}/results_true.dat (100%) rename tests/{test_create_fission_neutrons/test_create_fission_neutrons.py => regression_tests/create_fission_neutrons/test.py} (97%) rename tests/{test_density => regression_tests/density}/geometry.xml (100%) rename tests/{test_density => regression_tests/density}/materials.xml (100%) rename tests/{test_density => regression_tests/density}/results_true.dat (100%) rename tests/{test_density => regression_tests/density}/settings.xml (100%) rename tests/{test_eigenvalue_no_inactive/test_eigenvalue_no_inactive.py => regression_tests/density/test.py} (76%) rename tests/{test_diff_tally => regression_tests/diff_tally}/inputs_true.dat (100%) rename tests/{test_diff_tally => regression_tests/diff_tally}/results_true.dat (100%) rename tests/{test_diff_tally/test_diff_tally.py => regression_tests/diff_tally/test.py} (98%) rename tests/{test_distribmat => regression_tests/distribmat}/inputs_true.dat (100%) rename tests/{test_distribmat => regression_tests/distribmat}/results_true.dat (100%) rename tests/{test_distribmat/test_distribmat.py => regression_tests/distribmat/test.py} (98%) rename tests/{test_eigenvalue_genperbatch => regression_tests/eigenvalue_genperbatch}/geometry.xml (100%) rename tests/{test_eigenvalue_genperbatch => regression_tests/eigenvalue_genperbatch}/materials.xml (100%) rename tests/{test_eigenvalue_genperbatch => regression_tests/eigenvalue_genperbatch}/results_true.dat (100%) rename tests/{test_eigenvalue_genperbatch => regression_tests/eigenvalue_genperbatch}/settings.xml (100%) rename tests/{test_eigenvalue_genperbatch/test_eigenvalue_genperbatch.py => regression_tests/eigenvalue_genperbatch/test.py} (76%) rename tests/{test_eigenvalue_no_inactive => regression_tests/eigenvalue_no_inactive}/geometry.xml (100%) rename tests/{test_eigenvalue_no_inactive => regression_tests/eigenvalue_no_inactive}/materials.xml (100%) rename tests/{test_eigenvalue_no_inactive => regression_tests/eigenvalue_no_inactive}/results_true.dat (100%) rename tests/{test_eigenvalue_no_inactive => regression_tests/eigenvalue_no_inactive}/settings.xml (100%) rename tests/{test_energy_grid/test_energy_grid.py => regression_tests/eigenvalue_no_inactive/test.py} (76%) rename tests/{test_energy_cutoff => regression_tests/energy_cutoff}/inputs_true.dat (100%) rename tests/{test_energy_cutoff => regression_tests/energy_cutoff}/results_true.dat (100%) rename tests/{test_energy_cutoff/test_energy_cutoff.py => regression_tests/energy_cutoff/test.py} (97%) rename tests/{test_energy_grid => regression_tests/energy_grid}/geometry.xml (100%) rename tests/{test_energy_grid => regression_tests/energy_grid}/materials.xml (100%) rename tests/{test_energy_grid => regression_tests/energy_grid}/results_true.dat (100%) rename tests/{test_energy_grid => regression_tests/energy_grid}/settings.xml (100%) create mode 100644 tests/regression_tests/energy_grid/test.py rename tests/{test_energy_laws => regression_tests/energy_laws}/geometry.xml (100%) rename tests/{test_energy_laws => regression_tests/energy_laws}/materials.xml (100%) rename tests/{test_energy_laws => regression_tests/energy_laws}/results_true.dat (100%) rename tests/{test_energy_laws => regression_tests/energy_laws}/settings.xml (100%) rename tests/{test_energy_laws/test_energy_laws.py => regression_tests/energy_laws/test.py} (93%) rename tests/{test_enrichment/test_enrichment.py => regression_tests/enrichment/test.py} (97%) rename tests/{test_entropy => regression_tests/entropy}/geometry.xml (100%) rename tests/{test_entropy => regression_tests/entropy}/materials.xml (100%) rename tests/{test_entropy => regression_tests/entropy}/results_true.dat (100%) rename tests/{test_entropy => regression_tests/entropy}/settings.xml (100%) rename tests/{test_entropy/test_entropy.py => regression_tests/entropy/test.py} (94%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-1/geometry.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-1/materials.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-1/results_true.dat (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-1/settings.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-1/tallies.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-2/geometry.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-2/materials.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-2/results_true.dat (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-2/settings.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-2/tallies.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-3/geometry.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-3/materials.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-3/results_true.dat (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-3/settings.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-3/tallies.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-4/geometry.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-4/materials.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-4/results_true.dat (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-4/settings.xml (100%) rename tests/{test_filter_distribcell => regression_tests/filter_distribcell}/case-4/tallies.xml (100%) rename tests/{test_filter_distribcell/test_filter_distribcell.py => regression_tests/filter_distribcell/test.py} (98%) rename tests/{test_filter_energyfun => regression_tests/filter_energyfun}/inputs_true.dat (100%) rename tests/{test_filter_energyfun => regression_tests/filter_energyfun}/results_true.dat (100%) rename tests/{test_filter_energyfun/test_filter_energyfun.py => regression_tests/filter_energyfun/test.py} (97%) rename tests/{test_filter_mesh => regression_tests/filter_mesh}/inputs_true.dat (100%) rename tests/{test_filter_mesh => regression_tests/filter_mesh}/results_true.dat (100%) rename tests/{test_filter_mesh/test_filter_mesh.py => regression_tests/filter_mesh/test.py} (97%) rename tests/{test_fixed_source => regression_tests/fixed_source}/inputs_true.dat (100%) rename tests/{test_fixed_source => regression_tests/fixed_source}/results_true.dat (100%) rename tests/{test_fixed_source/test_fixed_source.py => regression_tests/fixed_source/test.py} (97%) rename tests/{test_infinite_cell => regression_tests/infinite_cell}/geometry.xml (100%) rename tests/{test_infinite_cell => regression_tests/infinite_cell}/materials.xml (100%) rename tests/{test_infinite_cell => regression_tests/infinite_cell}/results_true.dat (100%) rename tests/{test_infinite_cell => regression_tests/infinite_cell}/settings.xml (100%) create mode 100644 tests/regression_tests/infinite_cell/test.py rename tests/{test_iso_in_lab => regression_tests/iso_in_lab}/inputs_true.dat (100%) rename tests/{test_iso_in_lab => regression_tests/iso_in_lab}/results_true.dat (100%) rename tests/{test_iso_in_lab/test_iso_in_lab.py => regression_tests/iso_in_lab/test.py} (83%) rename tests/{test_lattice => regression_tests/lattice}/geometry.xml (100%) rename tests/{test_lattice => regression_tests/lattice}/materials.xml (100%) rename tests/{test_lattice => regression_tests/lattice}/results_true.dat (100%) rename tests/{test_lattice => regression_tests/lattice}/settings.xml (100%) create mode 100644 tests/regression_tests/lattice/test.py rename tests/{test_lattice_hex => regression_tests/lattice_hex}/geometry.xml (100%) rename tests/{test_lattice_hex => regression_tests/lattice_hex}/materials.xml (100%) rename tests/{test_lattice_hex => regression_tests/lattice_hex}/plots.xml (100%) rename tests/{test_lattice_hex => regression_tests/lattice_hex}/results_true.dat (100%) rename tests/{test_lattice_hex => regression_tests/lattice_hex}/settings.xml (100%) create mode 100644 tests/regression_tests/lattice_hex/test.py rename tests/{test_lattice_mixed => regression_tests/lattice_mixed}/geometry.xml (100%) rename tests/{test_lattice_mixed => regression_tests/lattice_mixed}/materials.xml (100%) rename tests/{test_lattice_mixed => regression_tests/lattice_mixed}/plots.xml (100%) rename tests/{test_lattice_mixed => regression_tests/lattice_mixed}/results_true.dat (100%) rename tests/{test_lattice_mixed => regression_tests/lattice_mixed}/settings.xml (100%) create mode 100644 tests/regression_tests/lattice_mixed/test.py rename tests/{test_lattice_multiple => regression_tests/lattice_multiple}/geometry.xml (100%) rename tests/{test_lattice_multiple => regression_tests/lattice_multiple}/materials.xml (100%) rename tests/{test_lattice_multiple => regression_tests/lattice_multiple}/results_true.dat (100%) rename tests/{test_lattice_multiple => regression_tests/lattice_multiple}/settings.xml (100%) create mode 100644 tests/regression_tests/lattice_multiple/test.py rename tests/{test_mg_basic => regression_tests/mg_basic}/inputs_true.dat (98%) rename tests/{test_mg_basic => regression_tests/mg_basic}/results_true.dat (100%) rename tests/{test_mg_basic/test_mg_basic.py => regression_tests/mg_basic/test.py} (82%) rename tests/{test_mg_convert => regression_tests/mg_convert}/inputs_true.dat (100%) rename tests/{test_mg_convert => regression_tests/mg_convert}/results_true.dat (100%) rename tests/{test_mg_convert/test_mg_convert.py => regression_tests/mg_convert/test.py} (99%) rename tests/{test_mg_legendre => regression_tests/mg_legendre}/inputs_true.dat (96%) rename tests/{test_mg_legendre => regression_tests/mg_legendre}/results_true.dat (100%) rename tests/{test_mg_legendre/test_mg_legendre.py => regression_tests/mg_legendre/test.py} (85%) rename tests/{test_mg_max_order => regression_tests/mg_max_order}/inputs_true.dat (96%) rename tests/{test_mg_max_order => regression_tests/mg_max_order}/results_true.dat (100%) rename tests/{test_mg_max_order/test_mg_max_order.py => regression_tests/mg_max_order/test.py} (84%) rename tests/{test_mg_nuclide => regression_tests/mg_nuclide}/inputs_true.dat (98%) rename tests/{test_mg_nuclide => regression_tests/mg_nuclide}/results_true.dat (100%) rename tests/{test_mg_nuclide/test_mg_nuclide.py => regression_tests/mg_nuclide/test.py} (82%) rename tests/{test_mg_survival_biasing => regression_tests/mg_survival_biasing}/inputs_true.dat (98%) rename tests/{test_mg_survival_biasing => regression_tests/mg_survival_biasing}/results_true.dat (100%) rename tests/{test_mg_survival_biasing/test_mg_survival_biasing.py => regression_tests/mg_survival_biasing/test.py} (84%) rename tests/{test_mg_tallies => regression_tests/mg_tallies}/inputs_true.dat (99%) rename tests/{test_mg_tallies => regression_tests/mg_tallies}/results_true.dat (100%) rename tests/{test_mg_tallies/test_mg_tallies.py => regression_tests/mg_tallies/test.py} (98%) rename tests/{test_mgxs_library_ce_to_mg => regression_tests/mgxs_library_ce_to_mg}/inputs_true.dat (100%) rename tests/{test_mgxs_library_ce_to_mg => regression_tests/mgxs_library_ce_to_mg}/results_true.dat (100%) rename tests/{test_mgxs_library_ce_to_mg/test_mgxs_library_ce_to_mg.py => regression_tests/mgxs_library_ce_to_mg/test.py} (98%) rename tests/{test_mgxs_library_condense => regression_tests/mgxs_library_condense}/inputs_true.dat (100%) rename tests/{test_mgxs_library_condense => regression_tests/mgxs_library_condense}/results_true.dat (100%) rename tests/{test_mgxs_library_condense/test_mgxs_library_condense.py => regression_tests/mgxs_library_condense/test.py} (97%) rename tests/{test_mgxs_library_distribcell => regression_tests/mgxs_library_distribcell}/inputs_true.dat (100%) rename tests/{test_mgxs_library_distribcell => regression_tests/mgxs_library_distribcell}/results_true.dat (100%) rename tests/{test_mgxs_library_distribcell/test_mgxs_library_distribcell.py => regression_tests/mgxs_library_distribcell/test.py} (97%) rename tests/{test_mgxs_library_hdf5 => regression_tests/mgxs_library_hdf5}/inputs_true.dat (100%) rename tests/{test_mgxs_library_hdf5 => regression_tests/mgxs_library_hdf5}/results_true.dat (100%) rename tests/{test_mgxs_library_hdf5/test_mgxs_library_hdf5.py => regression_tests/mgxs_library_hdf5/test.py} (98%) rename tests/{test_mgxs_library_mesh => regression_tests/mgxs_library_mesh}/inputs_true.dat (100%) rename tests/{test_mgxs_library_mesh => regression_tests/mgxs_library_mesh}/results_true.dat (100%) rename tests/{test_mgxs_library_mesh/test_mgxs_library_mesh.py => regression_tests/mgxs_library_mesh/test.py} (97%) rename tests/{test_mgxs_library_no_nuclides => regression_tests/mgxs_library_no_nuclides}/inputs_true.dat (100%) rename tests/{test_mgxs_library_no_nuclides => regression_tests/mgxs_library_no_nuclides}/results_true.dat (100%) rename tests/{test_mgxs_library_no_nuclides/test_mgxs_library_no_nuclides.py => regression_tests/mgxs_library_no_nuclides/test.py} (97%) rename tests/{test_mgxs_library_nuclides => regression_tests/mgxs_library_nuclides}/inputs_true.dat (100%) rename tests/{test_mgxs_library_nuclides => regression_tests/mgxs_library_nuclides}/results_true.dat (100%) rename tests/{test_mgxs_library_nuclides/test_mgxs_library_nuclides.py => regression_tests/mgxs_library_nuclides/test.py} (97%) rename tests/{test_multipole => regression_tests/multipole}/inputs_true.dat (100%) rename tests/{test_multipole => regression_tests/multipole}/results_true.dat (100%) rename tests/{test_multipole/test_multipole.py => regression_tests/multipole/test.py} (98%) rename tests/{test_output => regression_tests/output}/geometry.xml (100%) rename tests/{test_output => regression_tests/output}/materials.xml (100%) rename tests/{test_output => regression_tests/output}/results_true.dat (100%) rename tests/{test_output => regression_tests/output}/settings.xml (100%) rename tests/{test_output/test_output.py => regression_tests/output/test.py} (94%) rename tests/{test_particle_restart_eigval => regression_tests/particle_restart_eigval}/geometry.xml (100%) rename tests/{test_particle_restart_eigval => regression_tests/particle_restart_eigval}/materials.xml (100%) rename tests/{test_particle_restart_eigval => regression_tests/particle_restart_eigval}/results_true.dat (100%) rename tests/{test_particle_restart_eigval => regression_tests/particle_restart_eigval}/settings.xml (100%) rename tests/{test_particle_restart_eigval/test_particle_restart_eigval.py => regression_tests/particle_restart_eigval/test.py} (79%) rename tests/{test_particle_restart_fixed => regression_tests/particle_restart_fixed}/geometry.xml (100%) rename tests/{test_particle_restart_fixed => regression_tests/particle_restart_fixed}/materials.xml (100%) rename tests/{test_particle_restart_fixed => regression_tests/particle_restart_fixed}/results_true.dat (100%) rename tests/{test_particle_restart_fixed => regression_tests/particle_restart_fixed}/settings.xml (100%) rename tests/{test_particle_restart_fixed/test_particle_restart_fixed.py => regression_tests/particle_restart_fixed/test.py} (79%) rename tests/{test_periodic => regression_tests/periodic}/inputs_true.dat (100%) rename tests/{test_periodic => regression_tests/periodic}/results_true.dat (100%) rename tests/{test_periodic/test_periodic.py => regression_tests/periodic/test.py} (97%) rename tests/{test_plot => regression_tests/plot}/geometry.xml (100%) rename tests/{test_plot => regression_tests/plot}/materials.xml (100%) rename tests/{test_plot => regression_tests/plot}/plots.xml (100%) rename tests/{test_plot => regression_tests/plot}/results_true.dat (100%) rename tests/{test_plot => regression_tests/plot}/settings.xml (100%) rename tests/{test_plot/test_plot.py => regression_tests/plot/test.py} (97%) rename tests/{test_ptables_off => regression_tests/ptables_off}/geometry.xml (100%) rename tests/{test_ptables_off => regression_tests/ptables_off}/materials.xml (100%) rename tests/{test_ptables_off => regression_tests/ptables_off}/results_true.dat (100%) rename tests/{test_ptables_off => regression_tests/ptables_off}/settings.xml (100%) create mode 100644 tests/regression_tests/ptables_off/test.py rename tests/{test_quadric_surfaces => regression_tests/quadric_surfaces}/geometry.xml (100%) rename tests/{test_quadric_surfaces => regression_tests/quadric_surfaces}/materials.xml (100%) rename tests/{test_quadric_surfaces => regression_tests/quadric_surfaces}/results_true.dat (100%) rename tests/{test_quadric_surfaces => regression_tests/quadric_surfaces}/settings.xml (100%) create mode 100755 tests/regression_tests/quadric_surfaces/test.py rename tests/{test_reflective_plane => regression_tests/reflective_plane}/geometry.xml (100%) rename tests/{test_reflective_plane => regression_tests/reflective_plane}/materials.xml (100%) rename tests/{test_reflective_plane => regression_tests/reflective_plane}/results_true.dat (100%) rename tests/{test_reflective_plane => regression_tests/reflective_plane}/settings.xml (100%) create mode 100644 tests/regression_tests/reflective_plane/test.py rename tests/{test_resonance_scattering => regression_tests/resonance_scattering}/inputs_true.dat (100%) rename tests/{test_resonance_scattering => regression_tests/resonance_scattering}/results_true.dat (100%) rename tests/{test_resonance_scattering/test_resonance_scattering.py => regression_tests/resonance_scattering/test.py} (96%) rename tests/{test_rotation => regression_tests/rotation}/geometry.xml (100%) rename tests/{test_rotation => regression_tests/rotation}/materials.xml (100%) rename tests/{test_rotation => regression_tests/rotation}/results_true.dat (100%) rename tests/{test_rotation => regression_tests/rotation}/settings.xml (100%) create mode 100644 tests/regression_tests/rotation/test.py rename tests/{test_salphabeta => regression_tests/salphabeta}/inputs_true.dat (100%) rename tests/{test_salphabeta => regression_tests/salphabeta}/results_true.dat (100%) rename tests/{test_salphabeta/test_salphabeta.py => regression_tests/salphabeta/test.py} (97%) rename tests/{test_score_current => regression_tests/score_current}/geometry.xml (100%) rename tests/{test_score_current => regression_tests/score_current}/materials.xml (100%) rename tests/{test_score_current => regression_tests/score_current}/results_true.dat (100%) rename tests/{test_score_current => regression_tests/score_current}/settings.xml (100%) rename tests/{test_score_current => regression_tests/score_current}/tallies.xml (100%) rename tests/{test_score_current/test_score_current.py => regression_tests/score_current/test.py} (77%) rename tests/{test_seed => regression_tests/seed}/geometry.xml (100%) rename tests/{test_seed => regression_tests/seed}/materials.xml (100%) rename tests/{test_seed => regression_tests/seed}/results_true.dat (100%) rename tests/{test_seed => regression_tests/seed}/settings.xml (100%) create mode 100644 tests/regression_tests/seed/test.py rename tests/{test_source => regression_tests/source}/inputs_true.dat (100%) rename tests/{test_source => regression_tests/source}/results_true.dat (100%) rename tests/{test_source/test_source.py => regression_tests/source/test.py} (97%) rename tests/{test_source_file => regression_tests/source_file}/geometry.xml (100%) rename tests/{test_source_file => regression_tests/source_file}/materials.xml (100%) rename tests/{test_source_file => regression_tests/source_file}/results_true.dat (100%) rename tests/{test_source_file => regression_tests/source_file}/settings.xml (100%) rename tests/{test_source_file/test_source_file.py => regression_tests/source_file/test.py} (98%) rename tests/{test_sourcepoint_batch => regression_tests/sourcepoint_batch}/geometry.xml (100%) rename tests/{test_sourcepoint_batch => regression_tests/sourcepoint_batch}/materials.xml (100%) rename tests/{test_sourcepoint_batch => regression_tests/sourcepoint_batch}/results_true.dat (100%) rename tests/{test_sourcepoint_batch => regression_tests/sourcepoint_batch}/settings.xml (100%) rename tests/{test_sourcepoint_batch/test_sourcepoint_batch.py => regression_tests/sourcepoint_batch/test.py} (95%) rename tests/{test_sourcepoint_latest => regression_tests/sourcepoint_latest}/geometry.xml (100%) rename tests/{test_sourcepoint_latest => regression_tests/sourcepoint_latest}/materials.xml (100%) rename tests/{test_sourcepoint_latest => regression_tests/sourcepoint_latest}/results_true.dat (100%) rename tests/{test_sourcepoint_latest => regression_tests/sourcepoint_latest}/settings.xml (100%) rename tests/{test_sourcepoint_latest/test_sourcepoint_latest.py => regression_tests/sourcepoint_latest/test.py} (91%) rename tests/{test_sourcepoint_restart => regression_tests/sourcepoint_restart}/geometry.xml (100%) rename tests/{test_sourcepoint_restart => regression_tests/sourcepoint_restart}/materials.xml (100%) rename tests/{test_sourcepoint_restart => regression_tests/sourcepoint_restart}/results_true.dat (100%) rename tests/{test_sourcepoint_restart => regression_tests/sourcepoint_restart}/settings.xml (100%) rename tests/{test_sourcepoint_restart => regression_tests/sourcepoint_restart}/tallies.xml (100%) create mode 100644 tests/regression_tests/sourcepoint_restart/test.py rename tests/{test_statepoint_batch => regression_tests/statepoint_batch}/geometry.xml (100%) rename tests/{test_statepoint_batch => regression_tests/statepoint_batch}/materials.xml (100%) rename tests/{test_statepoint_batch => regression_tests/statepoint_batch}/results_true.dat (100%) rename tests/{test_statepoint_batch => regression_tests/statepoint_batch}/settings.xml (100%) rename tests/{test_statepoint_batch/test_statepoint_batch.py => regression_tests/statepoint_batch/test.py} (91%) rename tests/{test_statepoint_restart => regression_tests/statepoint_restart}/geometry.xml (100%) rename tests/{test_statepoint_restart => regression_tests/statepoint_restart}/materials.xml (100%) rename tests/{test_statepoint_restart => regression_tests/statepoint_restart}/results_true.dat (100%) rename tests/{test_statepoint_restart => regression_tests/statepoint_restart}/settings.xml (100%) rename tests/{test_statepoint_restart => regression_tests/statepoint_restart}/tallies.xml (100%) rename tests/{test_statepoint_restart/test_statepoint_restart.py => regression_tests/statepoint_restart/test.py} (97%) rename tests/{test_statepoint_sourcesep => regression_tests/statepoint_sourcesep}/geometry.xml (100%) rename tests/{test_statepoint_sourcesep => regression_tests/statepoint_sourcesep}/materials.xml (100%) rename tests/{test_statepoint_sourcesep => regression_tests/statepoint_sourcesep}/results_true.dat (100%) rename tests/{test_statepoint_sourcesep => regression_tests/statepoint_sourcesep}/settings.xml (100%) rename tests/{test_statepoint_sourcesep/test_statepoint_sourcesep.py => regression_tests/statepoint_sourcesep/test.py} (94%) rename tests/{test_surface_tally => regression_tests/surface_tally}/inputs_true.dat (100%) rename tests/{test_surface_tally => regression_tests/surface_tally}/results_true.dat (100%) rename tests/{test_surface_tally/test_surface_tally.py => regression_tests/surface_tally/test.py} (99%) rename tests/{test_survival_biasing => regression_tests/survival_biasing}/geometry.xml (100%) rename tests/{test_survival_biasing => regression_tests/survival_biasing}/materials.xml (100%) rename tests/{test_survival_biasing => regression_tests/survival_biasing}/results_true.dat (100%) rename tests/{test_survival_biasing => regression_tests/survival_biasing}/settings.xml (100%) rename tests/{test_survival_biasing => regression_tests/survival_biasing}/tallies.xml (100%) create mode 100644 tests/regression_tests/survival_biasing/test.py rename tests/{test_tallies => regression_tests/tallies}/inputs_true.dat (100%) rename tests/{test_tallies => regression_tests/tallies}/results_true.dat (100%) rename tests/{test_tallies/test_tallies.py => regression_tests/tallies/test.py} (99%) rename tests/{test_tally_aggregation => regression_tests/tally_aggregation}/inputs_true.dat (100%) rename tests/{test_tally_aggregation => regression_tests/tally_aggregation}/results_true.dat (100%) rename tests/{test_tally_aggregation/test_tally_aggregation.py => regression_tests/tally_aggregation/test.py} (97%) rename tests/{test_tally_arithmetic => regression_tests/tally_arithmetic}/inputs_true.dat (100%) rename tests/{test_tally_arithmetic => regression_tests/tally_arithmetic}/results_true.dat (100%) rename tests/{test_tally_arithmetic/test_tally_arithmetic.py => regression_tests/tally_arithmetic/test.py} (98%) rename tests/{test_tally_assumesep => regression_tests/tally_assumesep}/geometry.xml (100%) rename tests/{test_tally_assumesep => regression_tests/tally_assumesep}/materials.xml (100%) rename tests/{test_tally_assumesep => regression_tests/tally_assumesep}/results_true.dat (100%) rename tests/{test_tally_assumesep => regression_tests/tally_assumesep}/settings.xml (100%) rename tests/{test_tally_assumesep => regression_tests/tally_assumesep}/tallies.xml (100%) create mode 100644 tests/regression_tests/tally_assumesep/test.py rename tests/{test_tally_nuclides => regression_tests/tally_nuclides}/geometry.xml (100%) rename tests/{test_tally_nuclides => regression_tests/tally_nuclides}/materials.xml (100%) rename tests/{test_tally_nuclides => regression_tests/tally_nuclides}/results_true.dat (100%) rename tests/{test_tally_nuclides => regression_tests/tally_nuclides}/settings.xml (100%) rename tests/{test_tally_nuclides => regression_tests/tally_nuclides}/tallies.xml (100%) create mode 100644 tests/regression_tests/tally_nuclides/test.py rename tests/{test_tally_slice_merge => regression_tests/tally_slice_merge}/inputs_true.dat (100%) rename tests/{test_tally_slice_merge => regression_tests/tally_slice_merge}/results_true.dat (100%) rename tests/{test_tally_slice_merge/test_tally_slice_merge.py => regression_tests/tally_slice_merge/test.py} (99%) rename tests/{test_trace => regression_tests/trace}/geometry.xml (100%) rename tests/{test_trace => regression_tests/trace}/materials.xml (100%) rename tests/{test_trace => regression_tests/trace}/results_true.dat (100%) rename tests/{test_trace => regression_tests/trace}/settings.xml (100%) create mode 100644 tests/regression_tests/trace/test.py rename tests/{test_track_output => regression_tests/track_output}/geometry.xml (100%) rename tests/{test_track_output => regression_tests/track_output}/materials.xml (100%) rename tests/{test_track_output => regression_tests/track_output}/results_true.dat (100%) rename tests/{test_track_output => regression_tests/track_output}/settings.xml (100%) rename tests/{test_track_output/test_track_output.py => regression_tests/track_output/test.py} (96%) rename tests/{test_translation => regression_tests/translation}/geometry.xml (100%) rename tests/{test_translation => regression_tests/translation}/materials.xml (100%) rename tests/{test_translation => regression_tests/translation}/results_true.dat (100%) rename tests/{test_translation => regression_tests/translation}/settings.xml (100%) create mode 100644 tests/regression_tests/translation/test.py rename tests/{test_trigger_batch_interval => regression_tests/trigger_batch_interval}/geometry.xml (100%) rename tests/{test_trigger_batch_interval => regression_tests/trigger_batch_interval}/materials.xml (100%) rename tests/{test_trigger_batch_interval => regression_tests/trigger_batch_interval}/results_true.dat (100%) rename tests/{test_trigger_batch_interval => regression_tests/trigger_batch_interval}/settings.xml (100%) rename tests/{test_trigger_batch_interval => regression_tests/trigger_batch_interval}/tallies.xml (100%) rename tests/{test_trigger_batch_interval/test_trigger_batch_interval.py => regression_tests/trigger_batch_interval/test.py} (76%) rename tests/{test_trigger_no_batch_interval => regression_tests/trigger_no_batch_interval}/geometry.xml (100%) rename tests/{test_trigger_no_batch_interval => regression_tests/trigger_no_batch_interval}/materials.xml (100%) rename tests/{test_trigger_no_batch_interval => regression_tests/trigger_no_batch_interval}/results_true.dat (100%) rename tests/{test_trigger_no_batch_interval => regression_tests/trigger_no_batch_interval}/settings.xml (100%) rename tests/{test_trigger_no_batch_interval => regression_tests/trigger_no_batch_interval}/tallies.xml (100%) rename tests/{test_trigger_no_batch_interval/test_trigger_no_batch_interval.py => regression_tests/trigger_no_batch_interval/test.py} (76%) rename tests/{test_trigger_no_status => regression_tests/trigger_no_status}/geometry.xml (100%) rename tests/{test_trigger_no_status => regression_tests/trigger_no_status}/materials.xml (100%) rename tests/{test_trigger_no_status => regression_tests/trigger_no_status}/results_true.dat (100%) rename tests/{test_trigger_no_status => regression_tests/trigger_no_status}/settings.xml (100%) rename tests/{test_trigger_no_status => regression_tests/trigger_no_status}/tallies.xml (100%) create mode 100644 tests/regression_tests/trigger_no_status/test.py rename tests/{test_trigger_tallies => regression_tests/trigger_tallies}/geometry.xml (100%) rename tests/{test_trigger_tallies => regression_tests/trigger_tallies}/materials.xml (100%) rename tests/{test_trigger_tallies => regression_tests/trigger_tallies}/results_true.dat (100%) rename tests/{test_trigger_tallies => regression_tests/trigger_tallies}/settings.xml (100%) rename tests/{test_trigger_tallies => regression_tests/trigger_tallies}/tallies.xml (100%) rename tests/{test_trigger_tallies/test_trigger_tallies.py => regression_tests/trigger_tallies/test.py} (76%) rename tests/{test_triso => regression_tests/triso}/inputs_true.dat (100%) rename tests/{test_triso => regression_tests/triso}/results_true.dat (100%) rename tests/{test_triso/test_triso.py => regression_tests/triso/test.py} (98%) rename tests/{test_uniform_fs => regression_tests/uniform_fs}/geometry.xml (100%) rename tests/{test_uniform_fs => regression_tests/uniform_fs}/materials.xml (100%) rename tests/{test_uniform_fs => regression_tests/uniform_fs}/results_true.dat (100%) rename tests/{test_uniform_fs => regression_tests/uniform_fs}/settings.xml (100%) create mode 100644 tests/regression_tests/uniform_fs/test.py rename tests/{test_universe => regression_tests/universe}/geometry.xml (100%) rename tests/{test_universe => regression_tests/universe}/materials.xml (100%) rename tests/{test_universe => regression_tests/universe}/results_true.dat (100%) rename tests/{test_universe => regression_tests/universe}/settings.xml (100%) create mode 100644 tests/regression_tests/universe/test.py rename tests/{test_void => regression_tests/void}/geometry.xml (100%) rename tests/{test_void => regression_tests/void}/materials.xml (100%) rename tests/{test_void => regression_tests/void}/results_true.dat (100%) rename tests/{test_void => regression_tests/void}/settings.xml (100%) create mode 100644 tests/regression_tests/void/test.py rename tests/{test_volume_calc => regression_tests/volume_calc}/inputs_true.dat (100%) rename tests/{test_volume_calc => regression_tests/volume_calc}/results_true.dat (100%) rename tests/{test_volume_calc/test_volume_calc.py => regression_tests/volume_calc/test.py} (98%) delete mode 100755 tests/test_complex_cell/test_complex_cell.py delete mode 100644 tests/test_infinite_cell/test_infinite_cell.py delete mode 100644 tests/test_lattice/test_lattice.py delete mode 100644 tests/test_lattice_hex/test_lattice_hex.py delete mode 100644 tests/test_lattice_mixed/test_lattice_mixed.py delete mode 100644 tests/test_lattice_multiple/test_lattice_multiple.py delete mode 100644 tests/test_ptables_off/test_ptables_off.py delete mode 100755 tests/test_quadric_surfaces/test_quadric_surfaces.py delete mode 100644 tests/test_reflective_plane/test_reflective_plane.py delete mode 100644 tests/test_rotation/test_rotation.py delete mode 100644 tests/test_seed/test_seed.py delete mode 100644 tests/test_sourcepoint_restart/test_sourcepoint_restart.py delete mode 100644 tests/test_survival_biasing/test_survival_biasing.py delete mode 100644 tests/test_tally_assumesep/test_tally_assumesep.py delete mode 100644 tests/test_tally_nuclides/test_tally_nuclides.py delete mode 100644 tests/test_trace/test_trace.py delete mode 100644 tests/test_translation/test_translation.py delete mode 100644 tests/test_trigger_no_status/test_trigger_no_status.py delete mode 100644 tests/test_uniform_fs/test_uniform_fs.py delete mode 100644 tests/test_universe/test_universe.py delete mode 100644 tests/test_void/test_void.py diff --git a/CMakeLists.txt b/CMakeLists.txt index cb4f9bb3d..5d0fcb0ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -531,7 +531,7 @@ endif() include(CTest) # Get a list of all the tests to run -file(GLOB_RECURSE TESTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_*.py) +file(GLOB_RECURSE TESTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/test.py) # Loop through all the tests foreach(test ${TESTS}) @@ -552,20 +552,20 @@ foreach(test ${TESTS}) endif() # Add serial test - add_test(NAME ${TEST_NAME} + add_test(NAME ${TEST_PATH} WORKING_DIRECTORY ${TEST_PATH} COMMAND $) else() # Check serial/parallel if (${MPI_ENABLED}) # Preform a parallel test - add_test(NAME ${TEST_NAME} + add_test(NAME ${TEST_PATH} WORKING_DIRECTORY ${TEST_PATH} COMMAND ${PYTHON_EXECUTABLE} ${TEST_NAME} --exe $ --mpi_exec ${MPI_DIR}/mpiexec) else() # Perform a serial test - add_test(NAME ${TEST_NAME} + add_test(NAME ${TEST_PATH} WORKING_DIRECTORY ${TEST_PATH} COMMAND ${PYTHON_EXECUTABLE} ${TEST_NAME} --exe $) endif() diff --git a/openmc/examples.py b/openmc/examples.py index 3d6a06827..d48d26839 100644 --- a/openmc/examples.py +++ b/openmc/examples.py @@ -587,7 +587,7 @@ def slab_mg(reps=None, as_macro=True): # Define the materials file model.xs_data = xs - model.materials.cross_sections = "../1d_mgxs.h5" + model.materials.cross_sections = "../../1d_mgxs.h5" # Define surfaces. # Assembly/Problem Boundary diff --git a/tests/test_asymmetric_lattice/inputs_true.dat b/tests/regression_tests/asymmetric_lattice/inputs_true.dat similarity index 100% rename from tests/test_asymmetric_lattice/inputs_true.dat rename to tests/regression_tests/asymmetric_lattice/inputs_true.dat diff --git a/tests/test_asymmetric_lattice/results_true.dat b/tests/regression_tests/asymmetric_lattice/results_true.dat similarity index 100% rename from tests/test_asymmetric_lattice/results_true.dat rename to tests/regression_tests/asymmetric_lattice/results_true.dat diff --git a/tests/test_asymmetric_lattice/test_asymmetric_lattice.py b/tests/regression_tests/asymmetric_lattice/test.py similarity index 98% rename from tests/test_asymmetric_lattice/test_asymmetric_lattice.py rename to tests/regression_tests/asymmetric_lattice/test.py index 58c1b22ab..f7cbf35c6 100644 --- a/tests/test_asymmetric_lattice/test_asymmetric_lattice.py +++ b/tests/regression_tests/asymmetric_lattice/test.py @@ -4,7 +4,7 @@ import os import sys import glob import hashlib -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_cmfd_feed/cmfd.xml b/tests/regression_tests/cmfd_feed/cmfd.xml similarity index 100% rename from tests/test_cmfd_feed/cmfd.xml rename to tests/regression_tests/cmfd_feed/cmfd.xml diff --git a/tests/test_cmfd_feed/geometry.xml b/tests/regression_tests/cmfd_feed/geometry.xml similarity index 100% rename from tests/test_cmfd_feed/geometry.xml rename to tests/regression_tests/cmfd_feed/geometry.xml diff --git a/tests/test_cmfd_feed/materials.xml b/tests/regression_tests/cmfd_feed/materials.xml similarity index 100% rename from tests/test_cmfd_feed/materials.xml rename to tests/regression_tests/cmfd_feed/materials.xml diff --git a/tests/test_cmfd_feed/results_true.dat b/tests/regression_tests/cmfd_feed/results_true.dat similarity index 100% rename from tests/test_cmfd_feed/results_true.dat rename to tests/regression_tests/cmfd_feed/results_true.dat diff --git a/tests/test_cmfd_feed/settings.xml b/tests/regression_tests/cmfd_feed/settings.xml similarity index 100% rename from tests/test_cmfd_feed/settings.xml rename to tests/regression_tests/cmfd_feed/settings.xml diff --git a/tests/test_cmfd_feed/tallies.xml b/tests/regression_tests/cmfd_feed/tallies.xml similarity index 100% rename from tests/test_cmfd_feed/tallies.xml rename to tests/regression_tests/cmfd_feed/tallies.xml diff --git a/tests/test_cmfd_feed/test_cmfd_feed.py b/tests/regression_tests/cmfd_feed/test.py similarity index 77% rename from tests/test_cmfd_feed/test_cmfd_feed.py rename to tests/regression_tests/cmfd_feed/test.py index 17bebf68c..30e61d03f 100644 --- a/tests/test_cmfd_feed/test_cmfd_feed.py +++ b/tests/regression_tests/cmfd_feed/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import CMFDTestHarness diff --git a/tests/test_cmfd_nofeed/cmfd.xml b/tests/regression_tests/cmfd_nofeed/cmfd.xml similarity index 100% rename from tests/test_cmfd_nofeed/cmfd.xml rename to tests/regression_tests/cmfd_nofeed/cmfd.xml diff --git a/tests/test_cmfd_nofeed/geometry.xml b/tests/regression_tests/cmfd_nofeed/geometry.xml similarity index 100% rename from tests/test_cmfd_nofeed/geometry.xml rename to tests/regression_tests/cmfd_nofeed/geometry.xml diff --git a/tests/test_cmfd_nofeed/materials.xml b/tests/regression_tests/cmfd_nofeed/materials.xml similarity index 100% rename from tests/test_cmfd_nofeed/materials.xml rename to tests/regression_tests/cmfd_nofeed/materials.xml diff --git a/tests/test_cmfd_nofeed/results_true.dat b/tests/regression_tests/cmfd_nofeed/results_true.dat similarity index 100% rename from tests/test_cmfd_nofeed/results_true.dat rename to tests/regression_tests/cmfd_nofeed/results_true.dat diff --git a/tests/test_cmfd_nofeed/settings.xml b/tests/regression_tests/cmfd_nofeed/settings.xml similarity index 100% rename from tests/test_cmfd_nofeed/settings.xml rename to tests/regression_tests/cmfd_nofeed/settings.xml diff --git a/tests/test_cmfd_nofeed/tallies.xml b/tests/regression_tests/cmfd_nofeed/tallies.xml similarity index 100% rename from tests/test_cmfd_nofeed/tallies.xml rename to tests/regression_tests/cmfd_nofeed/tallies.xml diff --git a/tests/test_cmfd_nofeed/test_cmfd_nofeed.py b/tests/regression_tests/cmfd_nofeed/test.py similarity index 77% rename from tests/test_cmfd_nofeed/test_cmfd_nofeed.py rename to tests/regression_tests/cmfd_nofeed/test.py index 17bebf68c..30e61d03f 100644 --- a/tests/test_cmfd_nofeed/test_cmfd_nofeed.py +++ b/tests/regression_tests/cmfd_nofeed/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import CMFDTestHarness diff --git a/tests/test_complex_cell/geometry.xml b/tests/regression_tests/complex_cell/geometry.xml similarity index 100% rename from tests/test_complex_cell/geometry.xml rename to tests/regression_tests/complex_cell/geometry.xml diff --git a/tests/test_complex_cell/materials.xml b/tests/regression_tests/complex_cell/materials.xml similarity index 100% rename from tests/test_complex_cell/materials.xml rename to tests/regression_tests/complex_cell/materials.xml diff --git a/tests/test_complex_cell/results_true.dat b/tests/regression_tests/complex_cell/results_true.dat similarity index 100% rename from tests/test_complex_cell/results_true.dat rename to tests/regression_tests/complex_cell/results_true.dat diff --git a/tests/test_complex_cell/settings.xml b/tests/regression_tests/complex_cell/settings.xml similarity index 100% rename from tests/test_complex_cell/settings.xml rename to tests/regression_tests/complex_cell/settings.xml diff --git a/tests/test_complex_cell/tallies.xml b/tests/regression_tests/complex_cell/tallies.xml similarity index 100% rename from tests/test_complex_cell/tallies.xml rename to tests/regression_tests/complex_cell/tallies.xml diff --git a/tests/test_confidence_intervals/test_confidence_intervals.py b/tests/regression_tests/complex_cell/test.py similarity index 76% rename from tests/test_confidence_intervals/test_confidence_intervals.py rename to tests/regression_tests/complex_cell/test.py index b04fcc6eb..43f8e5ff0 100755 --- a/tests/test_confidence_intervals/test_confidence_intervals.py +++ b/tests/regression_tests/complex_cell/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_confidence_intervals/geometry.xml b/tests/regression_tests/confidence_intervals/geometry.xml similarity index 100% rename from tests/test_confidence_intervals/geometry.xml rename to tests/regression_tests/confidence_intervals/geometry.xml diff --git a/tests/test_confidence_intervals/materials.xml b/tests/regression_tests/confidence_intervals/materials.xml similarity index 100% rename from tests/test_confidence_intervals/materials.xml rename to tests/regression_tests/confidence_intervals/materials.xml diff --git a/tests/test_confidence_intervals/results_true.dat b/tests/regression_tests/confidence_intervals/results_true.dat similarity index 100% rename from tests/test_confidence_intervals/results_true.dat rename to tests/regression_tests/confidence_intervals/results_true.dat diff --git a/tests/test_confidence_intervals/settings.xml b/tests/regression_tests/confidence_intervals/settings.xml similarity index 100% rename from tests/test_confidence_intervals/settings.xml rename to tests/regression_tests/confidence_intervals/settings.xml diff --git a/tests/test_confidence_intervals/tallies.xml b/tests/regression_tests/confidence_intervals/tallies.xml similarity index 100% rename from tests/test_confidence_intervals/tallies.xml rename to tests/regression_tests/confidence_intervals/tallies.xml diff --git a/tests/test_density/test_density.py b/tests/regression_tests/confidence_intervals/test.py old mode 100644 new mode 100755 similarity index 76% rename from tests/test_density/test_density.py rename to tests/regression_tests/confidence_intervals/test.py index b04fcc6eb..43f8e5ff0 --- a/tests/test_density/test_density.py +++ b/tests/regression_tests/confidence_intervals/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_create_fission_neutrons/inputs_true.dat b/tests/regression_tests/create_fission_neutrons/inputs_true.dat similarity index 100% rename from tests/test_create_fission_neutrons/inputs_true.dat rename to tests/regression_tests/create_fission_neutrons/inputs_true.dat diff --git a/tests/test_create_fission_neutrons/results_true.dat b/tests/regression_tests/create_fission_neutrons/results_true.dat similarity index 100% rename from tests/test_create_fission_neutrons/results_true.dat rename to tests/regression_tests/create_fission_neutrons/results_true.dat diff --git a/tests/test_create_fission_neutrons/test_create_fission_neutrons.py b/tests/regression_tests/create_fission_neutrons/test.py similarity index 97% rename from tests/test_create_fission_neutrons/test_create_fission_neutrons.py rename to tests/regression_tests/create_fission_neutrons/test.py index 87abeda7f..080a0ab0d 100755 --- a/tests/test_create_fission_neutrons/test_create_fission_neutrons.py +++ b/tests/regression_tests/create_fission_neutrons/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_density/geometry.xml b/tests/regression_tests/density/geometry.xml similarity index 100% rename from tests/test_density/geometry.xml rename to tests/regression_tests/density/geometry.xml diff --git a/tests/test_density/materials.xml b/tests/regression_tests/density/materials.xml similarity index 100% rename from tests/test_density/materials.xml rename to tests/regression_tests/density/materials.xml diff --git a/tests/test_density/results_true.dat b/tests/regression_tests/density/results_true.dat similarity index 100% rename from tests/test_density/results_true.dat rename to tests/regression_tests/density/results_true.dat diff --git a/tests/test_density/settings.xml b/tests/regression_tests/density/settings.xml similarity index 100% rename from tests/test_density/settings.xml rename to tests/regression_tests/density/settings.xml diff --git a/tests/test_eigenvalue_no_inactive/test_eigenvalue_no_inactive.py b/tests/regression_tests/density/test.py similarity index 76% rename from tests/test_eigenvalue_no_inactive/test_eigenvalue_no_inactive.py rename to tests/regression_tests/density/test.py index b04fcc6eb..43f8e5ff0 100644 --- a/tests/test_eigenvalue_no_inactive/test_eigenvalue_no_inactive.py +++ b/tests/regression_tests/density/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_diff_tally/inputs_true.dat b/tests/regression_tests/diff_tally/inputs_true.dat similarity index 100% rename from tests/test_diff_tally/inputs_true.dat rename to tests/regression_tests/diff_tally/inputs_true.dat diff --git a/tests/test_diff_tally/results_true.dat b/tests/regression_tests/diff_tally/results_true.dat similarity index 100% rename from tests/test_diff_tally/results_true.dat rename to tests/regression_tests/diff_tally/results_true.dat diff --git a/tests/test_diff_tally/test_diff_tally.py b/tests/regression_tests/diff_tally/test.py similarity index 98% rename from tests/test_diff_tally/test_diff_tally.py rename to tests/regression_tests/diff_tally/test.py index 02798e70e..5f1da9c30 100644 --- a/tests/test_diff_tally/test_diff_tally.py +++ b/tests/regression_tests/diff_tally/test.py @@ -6,7 +6,7 @@ import sys import pandas as pd -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_distribmat/inputs_true.dat b/tests/regression_tests/distribmat/inputs_true.dat similarity index 100% rename from tests/test_distribmat/inputs_true.dat rename to tests/regression_tests/distribmat/inputs_true.dat diff --git a/tests/test_distribmat/results_true.dat b/tests/regression_tests/distribmat/results_true.dat similarity index 100% rename from tests/test_distribmat/results_true.dat rename to tests/regression_tests/distribmat/results_true.dat diff --git a/tests/test_distribmat/test_distribmat.py b/tests/regression_tests/distribmat/test.py similarity index 98% rename from tests/test_distribmat/test_distribmat.py rename to tests/regression_tests/distribmat/test.py index 9dd5d319a..d18bb1543 100644 --- a/tests/test_distribmat/test_distribmat.py +++ b/tests/regression_tests/distribmat/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness, PyAPITestHarness import openmc diff --git a/tests/test_eigenvalue_genperbatch/geometry.xml b/tests/regression_tests/eigenvalue_genperbatch/geometry.xml similarity index 100% rename from tests/test_eigenvalue_genperbatch/geometry.xml rename to tests/regression_tests/eigenvalue_genperbatch/geometry.xml diff --git a/tests/test_eigenvalue_genperbatch/materials.xml b/tests/regression_tests/eigenvalue_genperbatch/materials.xml similarity index 100% rename from tests/test_eigenvalue_genperbatch/materials.xml rename to tests/regression_tests/eigenvalue_genperbatch/materials.xml diff --git a/tests/test_eigenvalue_genperbatch/results_true.dat b/tests/regression_tests/eigenvalue_genperbatch/results_true.dat similarity index 100% rename from tests/test_eigenvalue_genperbatch/results_true.dat rename to tests/regression_tests/eigenvalue_genperbatch/results_true.dat diff --git a/tests/test_eigenvalue_genperbatch/settings.xml b/tests/regression_tests/eigenvalue_genperbatch/settings.xml similarity index 100% rename from tests/test_eigenvalue_genperbatch/settings.xml rename to tests/regression_tests/eigenvalue_genperbatch/settings.xml diff --git a/tests/test_eigenvalue_genperbatch/test_eigenvalue_genperbatch.py b/tests/regression_tests/eigenvalue_genperbatch/test.py similarity index 76% rename from tests/test_eigenvalue_genperbatch/test_eigenvalue_genperbatch.py rename to tests/regression_tests/eigenvalue_genperbatch/test.py index 59a60b63a..a36c2ae37 100644 --- a/tests/test_eigenvalue_genperbatch/test_eigenvalue_genperbatch.py +++ b/tests/regression_tests/eigenvalue_genperbatch/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_eigenvalue_no_inactive/geometry.xml b/tests/regression_tests/eigenvalue_no_inactive/geometry.xml similarity index 100% rename from tests/test_eigenvalue_no_inactive/geometry.xml rename to tests/regression_tests/eigenvalue_no_inactive/geometry.xml diff --git a/tests/test_eigenvalue_no_inactive/materials.xml b/tests/regression_tests/eigenvalue_no_inactive/materials.xml similarity index 100% rename from tests/test_eigenvalue_no_inactive/materials.xml rename to tests/regression_tests/eigenvalue_no_inactive/materials.xml diff --git a/tests/test_eigenvalue_no_inactive/results_true.dat b/tests/regression_tests/eigenvalue_no_inactive/results_true.dat similarity index 100% rename from tests/test_eigenvalue_no_inactive/results_true.dat rename to tests/regression_tests/eigenvalue_no_inactive/results_true.dat diff --git a/tests/test_eigenvalue_no_inactive/settings.xml b/tests/regression_tests/eigenvalue_no_inactive/settings.xml similarity index 100% rename from tests/test_eigenvalue_no_inactive/settings.xml rename to tests/regression_tests/eigenvalue_no_inactive/settings.xml diff --git a/tests/test_energy_grid/test_energy_grid.py b/tests/regression_tests/eigenvalue_no_inactive/test.py similarity index 76% rename from tests/test_energy_grid/test_energy_grid.py rename to tests/regression_tests/eigenvalue_no_inactive/test.py index b04fcc6eb..43f8e5ff0 100644 --- a/tests/test_energy_grid/test_energy_grid.py +++ b/tests/regression_tests/eigenvalue_no_inactive/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_energy_cutoff/inputs_true.dat b/tests/regression_tests/energy_cutoff/inputs_true.dat similarity index 100% rename from tests/test_energy_cutoff/inputs_true.dat rename to tests/regression_tests/energy_cutoff/inputs_true.dat diff --git a/tests/test_energy_cutoff/results_true.dat b/tests/regression_tests/energy_cutoff/results_true.dat similarity index 100% rename from tests/test_energy_cutoff/results_true.dat rename to tests/regression_tests/energy_cutoff/results_true.dat diff --git a/tests/test_energy_cutoff/test_energy_cutoff.py b/tests/regression_tests/energy_cutoff/test.py similarity index 97% rename from tests/test_energy_cutoff/test_energy_cutoff.py rename to tests/regression_tests/energy_cutoff/test.py index 3aa3400c7..74f7b2ab2 100755 --- a/tests/test_energy_cutoff/test_energy_cutoff.py +++ b/tests/regression_tests/energy_cutoff/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_energy_grid/geometry.xml b/tests/regression_tests/energy_grid/geometry.xml similarity index 100% rename from tests/test_energy_grid/geometry.xml rename to tests/regression_tests/energy_grid/geometry.xml diff --git a/tests/test_energy_grid/materials.xml b/tests/regression_tests/energy_grid/materials.xml similarity index 100% rename from tests/test_energy_grid/materials.xml rename to tests/regression_tests/energy_grid/materials.xml diff --git a/tests/test_energy_grid/results_true.dat b/tests/regression_tests/energy_grid/results_true.dat similarity index 100% rename from tests/test_energy_grid/results_true.dat rename to tests/regression_tests/energy_grid/results_true.dat diff --git a/tests/test_energy_grid/settings.xml b/tests/regression_tests/energy_grid/settings.xml similarity index 100% rename from tests/test_energy_grid/settings.xml rename to tests/regression_tests/energy_grid/settings.xml diff --git a/tests/regression_tests/energy_grid/test.py b/tests/regression_tests/energy_grid/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/energy_grid/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_energy_laws/geometry.xml b/tests/regression_tests/energy_laws/geometry.xml similarity index 100% rename from tests/test_energy_laws/geometry.xml rename to tests/regression_tests/energy_laws/geometry.xml diff --git a/tests/test_energy_laws/materials.xml b/tests/regression_tests/energy_laws/materials.xml similarity index 100% rename from tests/test_energy_laws/materials.xml rename to tests/regression_tests/energy_laws/materials.xml diff --git a/tests/test_energy_laws/results_true.dat b/tests/regression_tests/energy_laws/results_true.dat similarity index 100% rename from tests/test_energy_laws/results_true.dat rename to tests/regression_tests/energy_laws/results_true.dat diff --git a/tests/test_energy_laws/settings.xml b/tests/regression_tests/energy_laws/settings.xml similarity index 100% rename from tests/test_energy_laws/settings.xml rename to tests/regression_tests/energy_laws/settings.xml diff --git a/tests/test_energy_laws/test_energy_laws.py b/tests/regression_tests/energy_laws/test.py similarity index 93% rename from tests/test_energy_laws/test_energy_laws.py rename to tests/regression_tests/energy_laws/test.py index 254126ff3..5180344da 100644 --- a/tests/test_energy_laws/test_energy_laws.py +++ b/tests/regression_tests/energy_laws/test.py @@ -21,7 +21,7 @@ that use linear-linear interpolation. import glob import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_enrichment/test_enrichment.py b/tests/regression_tests/enrichment/test.py similarity index 97% rename from tests/test_enrichment/test_enrichment.py rename to tests/regression_tests/enrichment/test.py index fa65d6dce..395fafdf5 100644 --- a/tests/test_enrichment/test_enrichment.py +++ b/tests/regression_tests/enrichment/test.py @@ -5,7 +5,6 @@ import sys import numpy as np -sys.path.insert(0, os.pardir) sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from openmc import Material from openmc.data import NATURAL_ABUNDANCE, atomic_mass diff --git a/tests/test_entropy/geometry.xml b/tests/regression_tests/entropy/geometry.xml similarity index 100% rename from tests/test_entropy/geometry.xml rename to tests/regression_tests/entropy/geometry.xml diff --git a/tests/test_entropy/materials.xml b/tests/regression_tests/entropy/materials.xml similarity index 100% rename from tests/test_entropy/materials.xml rename to tests/regression_tests/entropy/materials.xml diff --git a/tests/test_entropy/results_true.dat b/tests/regression_tests/entropy/results_true.dat similarity index 100% rename from tests/test_entropy/results_true.dat rename to tests/regression_tests/entropy/results_true.dat diff --git a/tests/test_entropy/settings.xml b/tests/regression_tests/entropy/settings.xml similarity index 100% rename from tests/test_entropy/settings.xml rename to tests/regression_tests/entropy/settings.xml diff --git a/tests/test_entropy/test_entropy.py b/tests/regression_tests/entropy/test.py similarity index 94% rename from tests/test_entropy/test_entropy.py rename to tests/regression_tests/entropy/test.py index 36e2170af..bbc6c0c7f 100644 --- a/tests/test_entropy/test_entropy.py +++ b/tests/regression_tests/entropy/test.py @@ -3,7 +3,7 @@ import glob import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness from openmc import StatePoint diff --git a/tests/test_filter_distribcell/case-1/geometry.xml b/tests/regression_tests/filter_distribcell/case-1/geometry.xml similarity index 100% rename from tests/test_filter_distribcell/case-1/geometry.xml rename to tests/regression_tests/filter_distribcell/case-1/geometry.xml diff --git a/tests/test_filter_distribcell/case-1/materials.xml b/tests/regression_tests/filter_distribcell/case-1/materials.xml similarity index 100% rename from tests/test_filter_distribcell/case-1/materials.xml rename to tests/regression_tests/filter_distribcell/case-1/materials.xml diff --git a/tests/test_filter_distribcell/case-1/results_true.dat b/tests/regression_tests/filter_distribcell/case-1/results_true.dat similarity index 100% rename from tests/test_filter_distribcell/case-1/results_true.dat rename to tests/regression_tests/filter_distribcell/case-1/results_true.dat diff --git a/tests/test_filter_distribcell/case-1/settings.xml b/tests/regression_tests/filter_distribcell/case-1/settings.xml similarity index 100% rename from tests/test_filter_distribcell/case-1/settings.xml rename to tests/regression_tests/filter_distribcell/case-1/settings.xml diff --git a/tests/test_filter_distribcell/case-1/tallies.xml b/tests/regression_tests/filter_distribcell/case-1/tallies.xml similarity index 100% rename from tests/test_filter_distribcell/case-1/tallies.xml rename to tests/regression_tests/filter_distribcell/case-1/tallies.xml diff --git a/tests/test_filter_distribcell/case-2/geometry.xml b/tests/regression_tests/filter_distribcell/case-2/geometry.xml similarity index 100% rename from tests/test_filter_distribcell/case-2/geometry.xml rename to tests/regression_tests/filter_distribcell/case-2/geometry.xml diff --git a/tests/test_filter_distribcell/case-2/materials.xml b/tests/regression_tests/filter_distribcell/case-2/materials.xml similarity index 100% rename from tests/test_filter_distribcell/case-2/materials.xml rename to tests/regression_tests/filter_distribcell/case-2/materials.xml diff --git a/tests/test_filter_distribcell/case-2/results_true.dat b/tests/regression_tests/filter_distribcell/case-2/results_true.dat similarity index 100% rename from tests/test_filter_distribcell/case-2/results_true.dat rename to tests/regression_tests/filter_distribcell/case-2/results_true.dat diff --git a/tests/test_filter_distribcell/case-2/settings.xml b/tests/regression_tests/filter_distribcell/case-2/settings.xml similarity index 100% rename from tests/test_filter_distribcell/case-2/settings.xml rename to tests/regression_tests/filter_distribcell/case-2/settings.xml diff --git a/tests/test_filter_distribcell/case-2/tallies.xml b/tests/regression_tests/filter_distribcell/case-2/tallies.xml similarity index 100% rename from tests/test_filter_distribcell/case-2/tallies.xml rename to tests/regression_tests/filter_distribcell/case-2/tallies.xml diff --git a/tests/test_filter_distribcell/case-3/geometry.xml b/tests/regression_tests/filter_distribcell/case-3/geometry.xml similarity index 100% rename from tests/test_filter_distribcell/case-3/geometry.xml rename to tests/regression_tests/filter_distribcell/case-3/geometry.xml diff --git a/tests/test_filter_distribcell/case-3/materials.xml b/tests/regression_tests/filter_distribcell/case-3/materials.xml similarity index 100% rename from tests/test_filter_distribcell/case-3/materials.xml rename to tests/regression_tests/filter_distribcell/case-3/materials.xml diff --git a/tests/test_filter_distribcell/case-3/results_true.dat b/tests/regression_tests/filter_distribcell/case-3/results_true.dat similarity index 100% rename from tests/test_filter_distribcell/case-3/results_true.dat rename to tests/regression_tests/filter_distribcell/case-3/results_true.dat diff --git a/tests/test_filter_distribcell/case-3/settings.xml b/tests/regression_tests/filter_distribcell/case-3/settings.xml similarity index 100% rename from tests/test_filter_distribcell/case-3/settings.xml rename to tests/regression_tests/filter_distribcell/case-3/settings.xml diff --git a/tests/test_filter_distribcell/case-3/tallies.xml b/tests/regression_tests/filter_distribcell/case-3/tallies.xml similarity index 100% rename from tests/test_filter_distribcell/case-3/tallies.xml rename to tests/regression_tests/filter_distribcell/case-3/tallies.xml diff --git a/tests/test_filter_distribcell/case-4/geometry.xml b/tests/regression_tests/filter_distribcell/case-4/geometry.xml similarity index 100% rename from tests/test_filter_distribcell/case-4/geometry.xml rename to tests/regression_tests/filter_distribcell/case-4/geometry.xml diff --git a/tests/test_filter_distribcell/case-4/materials.xml b/tests/regression_tests/filter_distribcell/case-4/materials.xml similarity index 100% rename from tests/test_filter_distribcell/case-4/materials.xml rename to tests/regression_tests/filter_distribcell/case-4/materials.xml diff --git a/tests/test_filter_distribcell/case-4/results_true.dat b/tests/regression_tests/filter_distribcell/case-4/results_true.dat similarity index 100% rename from tests/test_filter_distribcell/case-4/results_true.dat rename to tests/regression_tests/filter_distribcell/case-4/results_true.dat diff --git a/tests/test_filter_distribcell/case-4/settings.xml b/tests/regression_tests/filter_distribcell/case-4/settings.xml similarity index 100% rename from tests/test_filter_distribcell/case-4/settings.xml rename to tests/regression_tests/filter_distribcell/case-4/settings.xml diff --git a/tests/test_filter_distribcell/case-4/tallies.xml b/tests/regression_tests/filter_distribcell/case-4/tallies.xml similarity index 100% rename from tests/test_filter_distribcell/case-4/tallies.xml rename to tests/regression_tests/filter_distribcell/case-4/tallies.xml diff --git a/tests/test_filter_distribcell/test_filter_distribcell.py b/tests/regression_tests/filter_distribcell/test.py similarity index 98% rename from tests/test_filter_distribcell/test_filter_distribcell.py rename to tests/regression_tests/filter_distribcell/test.py index f0fc27039..320a808a5 100644 --- a/tests/test_filter_distribcell/test_filter_distribcell.py +++ b/tests/regression_tests/filter_distribcell/test.py @@ -4,7 +4,7 @@ import glob import hashlib import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import * diff --git a/tests/test_filter_energyfun/inputs_true.dat b/tests/regression_tests/filter_energyfun/inputs_true.dat similarity index 100% rename from tests/test_filter_energyfun/inputs_true.dat rename to tests/regression_tests/filter_energyfun/inputs_true.dat diff --git a/tests/test_filter_energyfun/results_true.dat b/tests/regression_tests/filter_energyfun/results_true.dat similarity index 100% rename from tests/test_filter_energyfun/results_true.dat rename to tests/regression_tests/filter_energyfun/results_true.dat diff --git a/tests/test_filter_energyfun/test_filter_energyfun.py b/tests/regression_tests/filter_energyfun/test.py similarity index 97% rename from tests/test_filter_energyfun/test_filter_energyfun.py rename to tests/regression_tests/filter_energyfun/test.py index 7e787edff..1de522f54 100644 --- a/tests/test_filter_energyfun/test_filter_energyfun.py +++ b/tests/regression_tests/filter_energyfun/test.py @@ -3,7 +3,7 @@ import os import sys import glob -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat similarity index 100% rename from tests/test_filter_mesh/inputs_true.dat rename to tests/regression_tests/filter_mesh/inputs_true.dat diff --git a/tests/test_filter_mesh/results_true.dat b/tests/regression_tests/filter_mesh/results_true.dat similarity index 100% rename from tests/test_filter_mesh/results_true.dat rename to tests/regression_tests/filter_mesh/results_true.dat diff --git a/tests/test_filter_mesh/test_filter_mesh.py b/tests/regression_tests/filter_mesh/test.py similarity index 97% rename from tests/test_filter_mesh/test_filter_mesh.py rename to tests/regression_tests/filter_mesh/test.py index 7b9e8bd16..4ad5e9005 100644 --- a/tests/test_filter_mesh/test_filter_mesh.py +++ b/tests/regression_tests/filter_mesh/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import HashedPyAPITestHarness import openmc diff --git a/tests/test_fixed_source/inputs_true.dat b/tests/regression_tests/fixed_source/inputs_true.dat similarity index 100% rename from tests/test_fixed_source/inputs_true.dat rename to tests/regression_tests/fixed_source/inputs_true.dat diff --git a/tests/test_fixed_source/results_true.dat b/tests/regression_tests/fixed_source/results_true.dat similarity index 100% rename from tests/test_fixed_source/results_true.dat rename to tests/regression_tests/fixed_source/results_true.dat diff --git a/tests/test_fixed_source/test_fixed_source.py b/tests/regression_tests/fixed_source/test.py similarity index 97% rename from tests/test_fixed_source/test_fixed_source.py rename to tests/regression_tests/fixed_source/test.py index db58e2f70..3e28610fb 100644 --- a/tests/test_fixed_source/test_fixed_source.py +++ b/tests/regression_tests/fixed_source/test.py @@ -4,7 +4,7 @@ import glob import os import sys import numpy as np -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc import openmc.stats diff --git a/tests/test_infinite_cell/geometry.xml b/tests/regression_tests/infinite_cell/geometry.xml similarity index 100% rename from tests/test_infinite_cell/geometry.xml rename to tests/regression_tests/infinite_cell/geometry.xml diff --git a/tests/test_infinite_cell/materials.xml b/tests/regression_tests/infinite_cell/materials.xml similarity index 100% rename from tests/test_infinite_cell/materials.xml rename to tests/regression_tests/infinite_cell/materials.xml diff --git a/tests/test_infinite_cell/results_true.dat b/tests/regression_tests/infinite_cell/results_true.dat similarity index 100% rename from tests/test_infinite_cell/results_true.dat rename to tests/regression_tests/infinite_cell/results_true.dat diff --git a/tests/test_infinite_cell/settings.xml b/tests/regression_tests/infinite_cell/settings.xml similarity index 100% rename from tests/test_infinite_cell/settings.xml rename to tests/regression_tests/infinite_cell/settings.xml diff --git a/tests/regression_tests/infinite_cell/test.py b/tests/regression_tests/infinite_cell/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/infinite_cell/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_iso_in_lab/inputs_true.dat b/tests/regression_tests/iso_in_lab/inputs_true.dat similarity index 100% rename from tests/test_iso_in_lab/inputs_true.dat rename to tests/regression_tests/iso_in_lab/inputs_true.dat diff --git a/tests/test_iso_in_lab/results_true.dat b/tests/regression_tests/iso_in_lab/results_true.dat similarity index 100% rename from tests/test_iso_in_lab/results_true.dat rename to tests/regression_tests/iso_in_lab/results_true.dat diff --git a/tests/test_iso_in_lab/test_iso_in_lab.py b/tests/regression_tests/iso_in_lab/test.py similarity index 83% rename from tests/test_iso_in_lab/test_iso_in_lab.py rename to tests/regression_tests/iso_in_lab/test.py index 60b5bc2de..8cc9c7b7d 100644 --- a/tests/test_iso_in_lab/test_iso_in_lab.py +++ b/tests/regression_tests/iso_in_lab/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness diff --git a/tests/test_lattice/geometry.xml b/tests/regression_tests/lattice/geometry.xml similarity index 100% rename from tests/test_lattice/geometry.xml rename to tests/regression_tests/lattice/geometry.xml diff --git a/tests/test_lattice/materials.xml b/tests/regression_tests/lattice/materials.xml similarity index 100% rename from tests/test_lattice/materials.xml rename to tests/regression_tests/lattice/materials.xml diff --git a/tests/test_lattice/results_true.dat b/tests/regression_tests/lattice/results_true.dat similarity index 100% rename from tests/test_lattice/results_true.dat rename to tests/regression_tests/lattice/results_true.dat diff --git a/tests/test_lattice/settings.xml b/tests/regression_tests/lattice/settings.xml similarity index 100% rename from tests/test_lattice/settings.xml rename to tests/regression_tests/lattice/settings.xml diff --git a/tests/regression_tests/lattice/test.py b/tests/regression_tests/lattice/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/lattice/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_lattice_hex/geometry.xml b/tests/regression_tests/lattice_hex/geometry.xml similarity index 100% rename from tests/test_lattice_hex/geometry.xml rename to tests/regression_tests/lattice_hex/geometry.xml diff --git a/tests/test_lattice_hex/materials.xml b/tests/regression_tests/lattice_hex/materials.xml similarity index 100% rename from tests/test_lattice_hex/materials.xml rename to tests/regression_tests/lattice_hex/materials.xml diff --git a/tests/test_lattice_hex/plots.xml b/tests/regression_tests/lattice_hex/plots.xml similarity index 100% rename from tests/test_lattice_hex/plots.xml rename to tests/regression_tests/lattice_hex/plots.xml diff --git a/tests/test_lattice_hex/results_true.dat b/tests/regression_tests/lattice_hex/results_true.dat similarity index 100% rename from tests/test_lattice_hex/results_true.dat rename to tests/regression_tests/lattice_hex/results_true.dat diff --git a/tests/test_lattice_hex/settings.xml b/tests/regression_tests/lattice_hex/settings.xml similarity index 100% rename from tests/test_lattice_hex/settings.xml rename to tests/regression_tests/lattice_hex/settings.xml diff --git a/tests/regression_tests/lattice_hex/test.py b/tests/regression_tests/lattice_hex/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/lattice_hex/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_lattice_mixed/geometry.xml b/tests/regression_tests/lattice_mixed/geometry.xml similarity index 100% rename from tests/test_lattice_mixed/geometry.xml rename to tests/regression_tests/lattice_mixed/geometry.xml diff --git a/tests/test_lattice_mixed/materials.xml b/tests/regression_tests/lattice_mixed/materials.xml similarity index 100% rename from tests/test_lattice_mixed/materials.xml rename to tests/regression_tests/lattice_mixed/materials.xml diff --git a/tests/test_lattice_mixed/plots.xml b/tests/regression_tests/lattice_mixed/plots.xml similarity index 100% rename from tests/test_lattice_mixed/plots.xml rename to tests/regression_tests/lattice_mixed/plots.xml diff --git a/tests/test_lattice_mixed/results_true.dat b/tests/regression_tests/lattice_mixed/results_true.dat similarity index 100% rename from tests/test_lattice_mixed/results_true.dat rename to tests/regression_tests/lattice_mixed/results_true.dat diff --git a/tests/test_lattice_mixed/settings.xml b/tests/regression_tests/lattice_mixed/settings.xml similarity index 100% rename from tests/test_lattice_mixed/settings.xml rename to tests/regression_tests/lattice_mixed/settings.xml diff --git a/tests/regression_tests/lattice_mixed/test.py b/tests/regression_tests/lattice_mixed/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/lattice_mixed/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_lattice_multiple/geometry.xml b/tests/regression_tests/lattice_multiple/geometry.xml similarity index 100% rename from tests/test_lattice_multiple/geometry.xml rename to tests/regression_tests/lattice_multiple/geometry.xml diff --git a/tests/test_lattice_multiple/materials.xml b/tests/regression_tests/lattice_multiple/materials.xml similarity index 100% rename from tests/test_lattice_multiple/materials.xml rename to tests/regression_tests/lattice_multiple/materials.xml diff --git a/tests/test_lattice_multiple/results_true.dat b/tests/regression_tests/lattice_multiple/results_true.dat similarity index 100% rename from tests/test_lattice_multiple/results_true.dat rename to tests/regression_tests/lattice_multiple/results_true.dat diff --git a/tests/test_lattice_multiple/settings.xml b/tests/regression_tests/lattice_multiple/settings.xml similarity index 100% rename from tests/test_lattice_multiple/settings.xml rename to tests/regression_tests/lattice_multiple/settings.xml diff --git a/tests/regression_tests/lattice_multiple/test.py b/tests/regression_tests/lattice_multiple/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/lattice_multiple/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_mg_basic/inputs_true.dat b/tests/regression_tests/mg_basic/inputs_true.dat similarity index 98% rename from tests/test_mg_basic/inputs_true.dat rename to tests/regression_tests/mg_basic/inputs_true.dat index 220b1de24..a0efdbde0 100644 --- a/tests/test_mg_basic/inputs_true.dat +++ b/tests/regression_tests/mg_basic/inputs_true.dat @@ -32,7 +32,7 @@ - ../1d_mgxs.h5 + ../../1d_mgxs.h5 diff --git a/tests/test_mg_basic/results_true.dat b/tests/regression_tests/mg_basic/results_true.dat similarity index 100% rename from tests/test_mg_basic/results_true.dat rename to tests/regression_tests/mg_basic/results_true.dat diff --git a/tests/test_mg_basic/test_mg_basic.py b/tests/regression_tests/mg_basic/test.py similarity index 82% rename from tests/test_mg_basic/test_mg_basic.py rename to tests/regression_tests/mg_basic/test.py index 21871efd7..054503f9a 100644 --- a/tests/test_mg_basic/test_mg_basic.py +++ b/tests/regression_tests/mg_basic/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness from openmc.examples import slab_mg diff --git a/tests/test_mg_convert/inputs_true.dat b/tests/regression_tests/mg_convert/inputs_true.dat similarity index 100% rename from tests/test_mg_convert/inputs_true.dat rename to tests/regression_tests/mg_convert/inputs_true.dat diff --git a/tests/test_mg_convert/results_true.dat b/tests/regression_tests/mg_convert/results_true.dat similarity index 100% rename from tests/test_mg_convert/results_true.dat rename to tests/regression_tests/mg_convert/results_true.dat diff --git a/tests/test_mg_convert/test_mg_convert.py b/tests/regression_tests/mg_convert/test.py similarity index 99% rename from tests/test_mg_convert/test_mg_convert.py rename to tests/regression_tests/mg_convert/test.py index 36bfe5338..45f701ff1 100755 --- a/tests/test_mg_convert/test_mg_convert.py +++ b/tests/regression_tests/mg_convert/test.py @@ -3,7 +3,7 @@ import os import sys import hashlib -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) import numpy as np diff --git a/tests/test_mg_legendre/inputs_true.dat b/tests/regression_tests/mg_legendre/inputs_true.dat similarity index 96% rename from tests/test_mg_legendre/inputs_true.dat rename to tests/regression_tests/mg_legendre/inputs_true.dat index 9b63fb944..754808095 100644 --- a/tests/test_mg_legendre/inputs_true.dat +++ b/tests/regression_tests/mg_legendre/inputs_true.dat @@ -14,7 +14,7 @@ - ../1d_mgxs.h5 + ../../1d_mgxs.h5 diff --git a/tests/test_mg_legendre/results_true.dat b/tests/regression_tests/mg_legendre/results_true.dat similarity index 100% rename from tests/test_mg_legendre/results_true.dat rename to tests/regression_tests/mg_legendre/results_true.dat diff --git a/tests/test_mg_legendre/test_mg_legendre.py b/tests/regression_tests/mg_legendre/test.py similarity index 85% rename from tests/test_mg_legendre/test_mg_legendre.py rename to tests/regression_tests/mg_legendre/test.py index eee0f0800..2e574afb6 100644 --- a/tests/test_mg_legendre/test_mg_legendre.py +++ b/tests/regression_tests/mg_legendre/test.py @@ -3,7 +3,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness from openmc.examples import slab_mg diff --git a/tests/test_mg_max_order/inputs_true.dat b/tests/regression_tests/mg_max_order/inputs_true.dat similarity index 96% rename from tests/test_mg_max_order/inputs_true.dat rename to tests/regression_tests/mg_max_order/inputs_true.dat index 3954bc73a..023d468d4 100644 --- a/tests/test_mg_max_order/inputs_true.dat +++ b/tests/regression_tests/mg_max_order/inputs_true.dat @@ -14,7 +14,7 @@ - ../1d_mgxs.h5 + ../../1d_mgxs.h5 diff --git a/tests/test_mg_max_order/results_true.dat b/tests/regression_tests/mg_max_order/results_true.dat similarity index 100% rename from tests/test_mg_max_order/results_true.dat rename to tests/regression_tests/mg_max_order/results_true.dat diff --git a/tests/test_mg_max_order/test_mg_max_order.py b/tests/regression_tests/mg_max_order/test.py similarity index 84% rename from tests/test_mg_max_order/test_mg_max_order.py rename to tests/regression_tests/mg_max_order/test.py index 8ac0f5878..21fd2c0b6 100644 --- a/tests/test_mg_max_order/test_mg_max_order.py +++ b/tests/regression_tests/mg_max_order/test.py @@ -3,7 +3,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness from openmc.examples import slab_mg diff --git a/tests/test_mg_nuclide/inputs_true.dat b/tests/regression_tests/mg_nuclide/inputs_true.dat similarity index 98% rename from tests/test_mg_nuclide/inputs_true.dat rename to tests/regression_tests/mg_nuclide/inputs_true.dat index d42b15480..e11b9e3f0 100644 --- a/tests/test_mg_nuclide/inputs_true.dat +++ b/tests/regression_tests/mg_nuclide/inputs_true.dat @@ -32,7 +32,7 @@ - ../1d_mgxs.h5 + ../../1d_mgxs.h5 diff --git a/tests/test_mg_nuclide/results_true.dat b/tests/regression_tests/mg_nuclide/results_true.dat similarity index 100% rename from tests/test_mg_nuclide/results_true.dat rename to tests/regression_tests/mg_nuclide/results_true.dat diff --git a/tests/test_mg_nuclide/test_mg_nuclide.py b/tests/regression_tests/mg_nuclide/test.py similarity index 82% rename from tests/test_mg_nuclide/test_mg_nuclide.py rename to tests/regression_tests/mg_nuclide/test.py index 0f3c9dd6d..d1c06cd41 100644 --- a/tests/test_mg_nuclide/test_mg_nuclide.py +++ b/tests/regression_tests/mg_nuclide/test.py @@ -3,7 +3,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness from openmc.examples import slab_mg diff --git a/tests/test_mg_survival_biasing/inputs_true.dat b/tests/regression_tests/mg_survival_biasing/inputs_true.dat similarity index 98% rename from tests/test_mg_survival_biasing/inputs_true.dat rename to tests/regression_tests/mg_survival_biasing/inputs_true.dat index 057af6810..4bc79d48e 100644 --- a/tests/test_mg_survival_biasing/inputs_true.dat +++ b/tests/regression_tests/mg_survival_biasing/inputs_true.dat @@ -32,7 +32,7 @@ - ../1d_mgxs.h5 + ../../1d_mgxs.h5 diff --git a/tests/test_mg_survival_biasing/results_true.dat b/tests/regression_tests/mg_survival_biasing/results_true.dat similarity index 100% rename from tests/test_mg_survival_biasing/results_true.dat rename to tests/regression_tests/mg_survival_biasing/results_true.dat diff --git a/tests/test_mg_survival_biasing/test_mg_survival_biasing.py b/tests/regression_tests/mg_survival_biasing/test.py similarity index 84% rename from tests/test_mg_survival_biasing/test_mg_survival_biasing.py rename to tests/regression_tests/mg_survival_biasing/test.py index 9886ad3e4..2669201c0 100644 --- a/tests/test_mg_survival_biasing/test_mg_survival_biasing.py +++ b/tests/regression_tests/mg_survival_biasing/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness from openmc.examples import slab_mg diff --git a/tests/test_mg_tallies/inputs_true.dat b/tests/regression_tests/mg_tallies/inputs_true.dat similarity index 99% rename from tests/test_mg_tallies/inputs_true.dat rename to tests/regression_tests/mg_tallies/inputs_true.dat index 0c71689ae..7b5067014 100644 --- a/tests/test_mg_tallies/inputs_true.dat +++ b/tests/regression_tests/mg_tallies/inputs_true.dat @@ -32,7 +32,7 @@ - ../1d_mgxs.h5 + ../../1d_mgxs.h5 diff --git a/tests/test_mg_tallies/results_true.dat b/tests/regression_tests/mg_tallies/results_true.dat similarity index 100% rename from tests/test_mg_tallies/results_true.dat rename to tests/regression_tests/mg_tallies/results_true.dat diff --git a/tests/test_mg_tallies/test_mg_tallies.py b/tests/regression_tests/mg_tallies/test.py similarity index 98% rename from tests/test_mg_tallies/test_mg_tallies.py rename to tests/regression_tests/mg_tallies/test.py index aeafae318..ec7081e2a 100644 --- a/tests/test_mg_tallies/test_mg_tallies.py +++ b/tests/regression_tests/mg_tallies/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import HashedPyAPITestHarness import openmc from openmc.examples import slab_mg diff --git a/tests/test_mgxs_library_ce_to_mg/inputs_true.dat b/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat similarity index 100% rename from tests/test_mgxs_library_ce_to_mg/inputs_true.dat rename to tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat diff --git a/tests/test_mgxs_library_ce_to_mg/results_true.dat b/tests/regression_tests/mgxs_library_ce_to_mg/results_true.dat similarity index 100% rename from tests/test_mgxs_library_ce_to_mg/results_true.dat rename to tests/regression_tests/mgxs_library_ce_to_mg/results_true.dat diff --git a/tests/test_mgxs_library_ce_to_mg/test_mgxs_library_ce_to_mg.py b/tests/regression_tests/mgxs_library_ce_to_mg/test.py similarity index 98% rename from tests/test_mgxs_library_ce_to_mg/test_mgxs_library_ce_to_mg.py rename to tests/regression_tests/mgxs_library_ce_to_mg/test.py index 7ae47636e..e29cf5831 100644 --- a/tests/test_mgxs_library_ce_to_mg/test_mgxs_library_ce_to_mg.py +++ b/tests/regression_tests/mgxs_library_ce_to_mg/test.py @@ -3,7 +3,7 @@ import os import sys import glob -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc import openmc.mgxs diff --git a/tests/test_mgxs_library_condense/inputs_true.dat b/tests/regression_tests/mgxs_library_condense/inputs_true.dat similarity index 100% rename from tests/test_mgxs_library_condense/inputs_true.dat rename to tests/regression_tests/mgxs_library_condense/inputs_true.dat diff --git a/tests/test_mgxs_library_condense/results_true.dat b/tests/regression_tests/mgxs_library_condense/results_true.dat similarity index 100% rename from tests/test_mgxs_library_condense/results_true.dat rename to tests/regression_tests/mgxs_library_condense/results_true.dat diff --git a/tests/test_mgxs_library_condense/test_mgxs_library_condense.py b/tests/regression_tests/mgxs_library_condense/test.py similarity index 97% rename from tests/test_mgxs_library_condense/test_mgxs_library_condense.py rename to tests/regression_tests/mgxs_library_condense/test.py index 127980da6..fd144636f 100644 --- a/tests/test_mgxs_library_condense/test_mgxs_library_condense.py +++ b/tests/regression_tests/mgxs_library_condense/test.py @@ -4,7 +4,7 @@ import os import sys import glob import hashlib -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc import openmc.mgxs diff --git a/tests/test_mgxs_library_distribcell/inputs_true.dat b/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat similarity index 100% rename from tests/test_mgxs_library_distribcell/inputs_true.dat rename to tests/regression_tests/mgxs_library_distribcell/inputs_true.dat diff --git a/tests/test_mgxs_library_distribcell/results_true.dat b/tests/regression_tests/mgxs_library_distribcell/results_true.dat similarity index 100% rename from tests/test_mgxs_library_distribcell/results_true.dat rename to tests/regression_tests/mgxs_library_distribcell/results_true.dat diff --git a/tests/test_mgxs_library_distribcell/test_mgxs_library_distribcell.py b/tests/regression_tests/mgxs_library_distribcell/test.py similarity index 97% rename from tests/test_mgxs_library_distribcell/test_mgxs_library_distribcell.py rename to tests/regression_tests/mgxs_library_distribcell/test.py index 38f16c588..e59f4c757 100644 --- a/tests/test_mgxs_library_distribcell/test_mgxs_library_distribcell.py +++ b/tests/regression_tests/mgxs_library_distribcell/test.py @@ -4,7 +4,7 @@ import os import sys import glob import hashlib -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc import openmc.mgxs diff --git a/tests/test_mgxs_library_hdf5/inputs_true.dat b/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat similarity index 100% rename from tests/test_mgxs_library_hdf5/inputs_true.dat rename to tests/regression_tests/mgxs_library_hdf5/inputs_true.dat diff --git a/tests/test_mgxs_library_hdf5/results_true.dat b/tests/regression_tests/mgxs_library_hdf5/results_true.dat similarity index 100% rename from tests/test_mgxs_library_hdf5/results_true.dat rename to tests/regression_tests/mgxs_library_hdf5/results_true.dat diff --git a/tests/test_mgxs_library_hdf5/test_mgxs_library_hdf5.py b/tests/regression_tests/mgxs_library_hdf5/test.py similarity index 98% rename from tests/test_mgxs_library_hdf5/test_mgxs_library_hdf5.py rename to tests/regression_tests/mgxs_library_hdf5/test.py index 31a9d9612..8daf828ce 100644 --- a/tests/test_mgxs_library_hdf5/test_mgxs_library_hdf5.py +++ b/tests/regression_tests/mgxs_library_hdf5/test.py @@ -8,7 +8,7 @@ import hashlib import numpy as np import h5py -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc import openmc.mgxs diff --git a/tests/test_mgxs_library_mesh/inputs_true.dat b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat similarity index 100% rename from tests/test_mgxs_library_mesh/inputs_true.dat rename to tests/regression_tests/mgxs_library_mesh/inputs_true.dat diff --git a/tests/test_mgxs_library_mesh/results_true.dat b/tests/regression_tests/mgxs_library_mesh/results_true.dat similarity index 100% rename from tests/test_mgxs_library_mesh/results_true.dat rename to tests/regression_tests/mgxs_library_mesh/results_true.dat diff --git a/tests/test_mgxs_library_mesh/test_mgxs_library_mesh.py b/tests/regression_tests/mgxs_library_mesh/test.py similarity index 97% rename from tests/test_mgxs_library_mesh/test_mgxs_library_mesh.py rename to tests/regression_tests/mgxs_library_mesh/test.py index e0a3307bc..06cb10601 100644 --- a/tests/test_mgxs_library_mesh/test_mgxs_library_mesh.py +++ b/tests/regression_tests/mgxs_library_mesh/test.py @@ -4,7 +4,7 @@ import os import sys import glob import hashlib -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc import openmc.mgxs diff --git a/tests/test_mgxs_library_no_nuclides/inputs_true.dat b/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat similarity index 100% rename from tests/test_mgxs_library_no_nuclides/inputs_true.dat rename to tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat diff --git a/tests/test_mgxs_library_no_nuclides/results_true.dat b/tests/regression_tests/mgxs_library_no_nuclides/results_true.dat similarity index 100% rename from tests/test_mgxs_library_no_nuclides/results_true.dat rename to tests/regression_tests/mgxs_library_no_nuclides/results_true.dat diff --git a/tests/test_mgxs_library_no_nuclides/test_mgxs_library_no_nuclides.py b/tests/regression_tests/mgxs_library_no_nuclides/test.py similarity index 97% rename from tests/test_mgxs_library_no_nuclides/test_mgxs_library_no_nuclides.py rename to tests/regression_tests/mgxs_library_no_nuclides/test.py index 793cfc714..ede916059 100644 --- a/tests/test_mgxs_library_no_nuclides/test_mgxs_library_no_nuclides.py +++ b/tests/regression_tests/mgxs_library_no_nuclides/test.py @@ -4,7 +4,7 @@ import os import sys import glob import hashlib -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc import openmc.mgxs diff --git a/tests/test_mgxs_library_nuclides/inputs_true.dat b/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat similarity index 100% rename from tests/test_mgxs_library_nuclides/inputs_true.dat rename to tests/regression_tests/mgxs_library_nuclides/inputs_true.dat diff --git a/tests/test_mgxs_library_nuclides/results_true.dat b/tests/regression_tests/mgxs_library_nuclides/results_true.dat similarity index 100% rename from tests/test_mgxs_library_nuclides/results_true.dat rename to tests/regression_tests/mgxs_library_nuclides/results_true.dat diff --git a/tests/test_mgxs_library_nuclides/test_mgxs_library_nuclides.py b/tests/regression_tests/mgxs_library_nuclides/test.py similarity index 97% rename from tests/test_mgxs_library_nuclides/test_mgxs_library_nuclides.py rename to tests/regression_tests/mgxs_library_nuclides/test.py index 9e54e1bb6..e668ab52c 100644 --- a/tests/test_mgxs_library_nuclides/test_mgxs_library_nuclides.py +++ b/tests/regression_tests/mgxs_library_nuclides/test.py @@ -4,7 +4,7 @@ import os import sys import glob import hashlib -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc import openmc.mgxs diff --git a/tests/test_multipole/inputs_true.dat b/tests/regression_tests/multipole/inputs_true.dat similarity index 100% rename from tests/test_multipole/inputs_true.dat rename to tests/regression_tests/multipole/inputs_true.dat diff --git a/tests/test_multipole/results_true.dat b/tests/regression_tests/multipole/results_true.dat similarity index 100% rename from tests/test_multipole/results_true.dat rename to tests/regression_tests/multipole/results_true.dat diff --git a/tests/test_multipole/test_multipole.py b/tests/regression_tests/multipole/test.py similarity index 98% rename from tests/test_multipole/test_multipole.py rename to tests/regression_tests/multipole/test.py index 294185312..20da97f7c 100644 --- a/tests/test_multipole/test_multipole.py +++ b/tests/regression_tests/multipole/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness, PyAPITestHarness import openmc import openmc.model diff --git a/tests/test_output/geometry.xml b/tests/regression_tests/output/geometry.xml similarity index 100% rename from tests/test_output/geometry.xml rename to tests/regression_tests/output/geometry.xml diff --git a/tests/test_output/materials.xml b/tests/regression_tests/output/materials.xml similarity index 100% rename from tests/test_output/materials.xml rename to tests/regression_tests/output/materials.xml diff --git a/tests/test_output/results_true.dat b/tests/regression_tests/output/results_true.dat similarity index 100% rename from tests/test_output/results_true.dat rename to tests/regression_tests/output/results_true.dat diff --git a/tests/test_output/settings.xml b/tests/regression_tests/output/settings.xml similarity index 100% rename from tests/test_output/settings.xml rename to tests/regression_tests/output/settings.xml diff --git a/tests/test_output/test_output.py b/tests/regression_tests/output/test.py similarity index 94% rename from tests/test_output/test_output.py rename to tests/regression_tests/output/test.py index 475b3e9f0..0092f5491 100644 --- a/tests/test_output/test_output.py +++ b/tests/regression_tests/output/test.py @@ -3,7 +3,7 @@ import glob import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_particle_restart_eigval/geometry.xml b/tests/regression_tests/particle_restart_eigval/geometry.xml similarity index 100% rename from tests/test_particle_restart_eigval/geometry.xml rename to tests/regression_tests/particle_restart_eigval/geometry.xml diff --git a/tests/test_particle_restart_eigval/materials.xml b/tests/regression_tests/particle_restart_eigval/materials.xml similarity index 100% rename from tests/test_particle_restart_eigval/materials.xml rename to tests/regression_tests/particle_restart_eigval/materials.xml diff --git a/tests/test_particle_restart_eigval/results_true.dat b/tests/regression_tests/particle_restart_eigval/results_true.dat similarity index 100% rename from tests/test_particle_restart_eigval/results_true.dat rename to tests/regression_tests/particle_restart_eigval/results_true.dat diff --git a/tests/test_particle_restart_eigval/settings.xml b/tests/regression_tests/particle_restart_eigval/settings.xml similarity index 100% rename from tests/test_particle_restart_eigval/settings.xml rename to tests/regression_tests/particle_restart_eigval/settings.xml diff --git a/tests/test_particle_restart_eigval/test_particle_restart_eigval.py b/tests/regression_tests/particle_restart_eigval/test.py similarity index 79% rename from tests/test_particle_restart_eigval/test_particle_restart_eigval.py rename to tests/regression_tests/particle_restart_eigval/test.py index 1ebbd3ea5..455ff9e92 100644 --- a/tests/test_particle_restart_eigval/test_particle_restart_eigval.py +++ b/tests/regression_tests/particle_restart_eigval/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import ParticleRestartTestHarness diff --git a/tests/test_particle_restart_fixed/geometry.xml b/tests/regression_tests/particle_restart_fixed/geometry.xml similarity index 100% rename from tests/test_particle_restart_fixed/geometry.xml rename to tests/regression_tests/particle_restart_fixed/geometry.xml diff --git a/tests/test_particle_restart_fixed/materials.xml b/tests/regression_tests/particle_restart_fixed/materials.xml similarity index 100% rename from tests/test_particle_restart_fixed/materials.xml rename to tests/regression_tests/particle_restart_fixed/materials.xml diff --git a/tests/test_particle_restart_fixed/results_true.dat b/tests/regression_tests/particle_restart_fixed/results_true.dat similarity index 100% rename from tests/test_particle_restart_fixed/results_true.dat rename to tests/regression_tests/particle_restart_fixed/results_true.dat diff --git a/tests/test_particle_restart_fixed/settings.xml b/tests/regression_tests/particle_restart_fixed/settings.xml similarity index 100% rename from tests/test_particle_restart_fixed/settings.xml rename to tests/regression_tests/particle_restart_fixed/settings.xml diff --git a/tests/test_particle_restart_fixed/test_particle_restart_fixed.py b/tests/regression_tests/particle_restart_fixed/test.py similarity index 79% rename from tests/test_particle_restart_fixed/test_particle_restart_fixed.py rename to tests/regression_tests/particle_restart_fixed/test.py index df0398c6e..e473fdb59 100644 --- a/tests/test_particle_restart_fixed/test_particle_restart_fixed.py +++ b/tests/regression_tests/particle_restart_fixed/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import ParticleRestartTestHarness diff --git a/tests/test_periodic/inputs_true.dat b/tests/regression_tests/periodic/inputs_true.dat similarity index 100% rename from tests/test_periodic/inputs_true.dat rename to tests/regression_tests/periodic/inputs_true.dat diff --git a/tests/test_periodic/results_true.dat b/tests/regression_tests/periodic/results_true.dat similarity index 100% rename from tests/test_periodic/results_true.dat rename to tests/regression_tests/periodic/results_true.dat diff --git a/tests/test_periodic/test_periodic.py b/tests/regression_tests/periodic/test.py similarity index 97% rename from tests/test_periodic/test_periodic.py rename to tests/regression_tests/periodic/test.py index 3883654c1..ddd7cd89b 100644 --- a/tests/test_periodic/test_periodic.py +++ b/tests/regression_tests/periodic/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_plot/geometry.xml b/tests/regression_tests/plot/geometry.xml similarity index 100% rename from tests/test_plot/geometry.xml rename to tests/regression_tests/plot/geometry.xml diff --git a/tests/test_plot/materials.xml b/tests/regression_tests/plot/materials.xml similarity index 100% rename from tests/test_plot/materials.xml rename to tests/regression_tests/plot/materials.xml diff --git a/tests/test_plot/plots.xml b/tests/regression_tests/plot/plots.xml similarity index 100% rename from tests/test_plot/plots.xml rename to tests/regression_tests/plot/plots.xml diff --git a/tests/test_plot/results_true.dat b/tests/regression_tests/plot/results_true.dat similarity index 100% rename from tests/test_plot/results_true.dat rename to tests/regression_tests/plot/results_true.dat diff --git a/tests/test_plot/settings.xml b/tests/regression_tests/plot/settings.xml similarity index 100% rename from tests/test_plot/settings.xml rename to tests/regression_tests/plot/settings.xml diff --git a/tests/test_plot/test_plot.py b/tests/regression_tests/plot/test.py similarity index 97% rename from tests/test_plot/test_plot.py rename to tests/regression_tests/plot/test.py index 1a99c2488..d0c362ef2 100644 --- a/tests/test_plot/test_plot.py +++ b/tests/regression_tests/plot/test.py @@ -4,7 +4,7 @@ import glob import hashlib import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness import h5py diff --git a/tests/test_ptables_off/geometry.xml b/tests/regression_tests/ptables_off/geometry.xml similarity index 100% rename from tests/test_ptables_off/geometry.xml rename to tests/regression_tests/ptables_off/geometry.xml diff --git a/tests/test_ptables_off/materials.xml b/tests/regression_tests/ptables_off/materials.xml similarity index 100% rename from tests/test_ptables_off/materials.xml rename to tests/regression_tests/ptables_off/materials.xml diff --git a/tests/test_ptables_off/results_true.dat b/tests/regression_tests/ptables_off/results_true.dat similarity index 100% rename from tests/test_ptables_off/results_true.dat rename to tests/regression_tests/ptables_off/results_true.dat diff --git a/tests/test_ptables_off/settings.xml b/tests/regression_tests/ptables_off/settings.xml similarity index 100% rename from tests/test_ptables_off/settings.xml rename to tests/regression_tests/ptables_off/settings.xml diff --git a/tests/regression_tests/ptables_off/test.py b/tests/regression_tests/ptables_off/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/ptables_off/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_quadric_surfaces/geometry.xml b/tests/regression_tests/quadric_surfaces/geometry.xml similarity index 100% rename from tests/test_quadric_surfaces/geometry.xml rename to tests/regression_tests/quadric_surfaces/geometry.xml diff --git a/tests/test_quadric_surfaces/materials.xml b/tests/regression_tests/quadric_surfaces/materials.xml similarity index 100% rename from tests/test_quadric_surfaces/materials.xml rename to tests/regression_tests/quadric_surfaces/materials.xml diff --git a/tests/test_quadric_surfaces/results_true.dat b/tests/regression_tests/quadric_surfaces/results_true.dat similarity index 100% rename from tests/test_quadric_surfaces/results_true.dat rename to tests/regression_tests/quadric_surfaces/results_true.dat diff --git a/tests/test_quadric_surfaces/settings.xml b/tests/regression_tests/quadric_surfaces/settings.xml similarity index 100% rename from tests/test_quadric_surfaces/settings.xml rename to tests/regression_tests/quadric_surfaces/settings.xml diff --git a/tests/regression_tests/quadric_surfaces/test.py b/tests/regression_tests/quadric_surfaces/test.py new file mode 100755 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/quadric_surfaces/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_reflective_plane/geometry.xml b/tests/regression_tests/reflective_plane/geometry.xml similarity index 100% rename from tests/test_reflective_plane/geometry.xml rename to tests/regression_tests/reflective_plane/geometry.xml diff --git a/tests/test_reflective_plane/materials.xml b/tests/regression_tests/reflective_plane/materials.xml similarity index 100% rename from tests/test_reflective_plane/materials.xml rename to tests/regression_tests/reflective_plane/materials.xml diff --git a/tests/test_reflective_plane/results_true.dat b/tests/regression_tests/reflective_plane/results_true.dat similarity index 100% rename from tests/test_reflective_plane/results_true.dat rename to tests/regression_tests/reflective_plane/results_true.dat diff --git a/tests/test_reflective_plane/settings.xml b/tests/regression_tests/reflective_plane/settings.xml similarity index 100% rename from tests/test_reflective_plane/settings.xml rename to tests/regression_tests/reflective_plane/settings.xml diff --git a/tests/regression_tests/reflective_plane/test.py b/tests/regression_tests/reflective_plane/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/reflective_plane/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_resonance_scattering/inputs_true.dat b/tests/regression_tests/resonance_scattering/inputs_true.dat similarity index 100% rename from tests/test_resonance_scattering/inputs_true.dat rename to tests/regression_tests/resonance_scattering/inputs_true.dat diff --git a/tests/test_resonance_scattering/results_true.dat b/tests/regression_tests/resonance_scattering/results_true.dat similarity index 100% rename from tests/test_resonance_scattering/results_true.dat rename to tests/regression_tests/resonance_scattering/results_true.dat diff --git a/tests/test_resonance_scattering/test_resonance_scattering.py b/tests/regression_tests/resonance_scattering/test.py similarity index 96% rename from tests/test_resonance_scattering/test_resonance_scattering.py rename to tests/regression_tests/resonance_scattering/test.py index 94c9f5c3d..3ed7fe227 100644 --- a/tests/test_resonance_scattering/test_resonance_scattering.py +++ b/tests/regression_tests/resonance_scattering/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_rotation/geometry.xml b/tests/regression_tests/rotation/geometry.xml similarity index 100% rename from tests/test_rotation/geometry.xml rename to tests/regression_tests/rotation/geometry.xml diff --git a/tests/test_rotation/materials.xml b/tests/regression_tests/rotation/materials.xml similarity index 100% rename from tests/test_rotation/materials.xml rename to tests/regression_tests/rotation/materials.xml diff --git a/tests/test_rotation/results_true.dat b/tests/regression_tests/rotation/results_true.dat similarity index 100% rename from tests/test_rotation/results_true.dat rename to tests/regression_tests/rotation/results_true.dat diff --git a/tests/test_rotation/settings.xml b/tests/regression_tests/rotation/settings.xml similarity index 100% rename from tests/test_rotation/settings.xml rename to tests/regression_tests/rotation/settings.xml diff --git a/tests/regression_tests/rotation/test.py b/tests/regression_tests/rotation/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/rotation/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_salphabeta/inputs_true.dat b/tests/regression_tests/salphabeta/inputs_true.dat similarity index 100% rename from tests/test_salphabeta/inputs_true.dat rename to tests/regression_tests/salphabeta/inputs_true.dat diff --git a/tests/test_salphabeta/results_true.dat b/tests/regression_tests/salphabeta/results_true.dat similarity index 100% rename from tests/test_salphabeta/results_true.dat rename to tests/regression_tests/salphabeta/results_true.dat diff --git a/tests/test_salphabeta/test_salphabeta.py b/tests/regression_tests/salphabeta/test.py similarity index 97% rename from tests/test_salphabeta/test_salphabeta.py rename to tests/regression_tests/salphabeta/test.py index 600c70332..6ced79a8d 100644 --- a/tests/test_salphabeta/test_salphabeta.py +++ b/tests/regression_tests/salphabeta/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_score_current/geometry.xml b/tests/regression_tests/score_current/geometry.xml similarity index 100% rename from tests/test_score_current/geometry.xml rename to tests/regression_tests/score_current/geometry.xml diff --git a/tests/test_score_current/materials.xml b/tests/regression_tests/score_current/materials.xml similarity index 100% rename from tests/test_score_current/materials.xml rename to tests/regression_tests/score_current/materials.xml diff --git a/tests/test_score_current/results_true.dat b/tests/regression_tests/score_current/results_true.dat similarity index 100% rename from tests/test_score_current/results_true.dat rename to tests/regression_tests/score_current/results_true.dat diff --git a/tests/test_score_current/settings.xml b/tests/regression_tests/score_current/settings.xml similarity index 100% rename from tests/test_score_current/settings.xml rename to tests/regression_tests/score_current/settings.xml diff --git a/tests/test_score_current/tallies.xml b/tests/regression_tests/score_current/tallies.xml similarity index 100% rename from tests/test_score_current/tallies.xml rename to tests/regression_tests/score_current/tallies.xml diff --git a/tests/test_score_current/test_score_current.py b/tests/regression_tests/score_current/test.py similarity index 77% rename from tests/test_score_current/test_score_current.py rename to tests/regression_tests/score_current/test.py index ea5886a63..70ddc2219 100644 --- a/tests/test_score_current/test_score_current.py +++ b/tests/regression_tests/score_current/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import HashedTestHarness diff --git a/tests/test_seed/geometry.xml b/tests/regression_tests/seed/geometry.xml similarity index 100% rename from tests/test_seed/geometry.xml rename to tests/regression_tests/seed/geometry.xml diff --git a/tests/test_seed/materials.xml b/tests/regression_tests/seed/materials.xml similarity index 100% rename from tests/test_seed/materials.xml rename to tests/regression_tests/seed/materials.xml diff --git a/tests/test_seed/results_true.dat b/tests/regression_tests/seed/results_true.dat similarity index 100% rename from tests/test_seed/results_true.dat rename to tests/regression_tests/seed/results_true.dat diff --git a/tests/test_seed/settings.xml b/tests/regression_tests/seed/settings.xml similarity index 100% rename from tests/test_seed/settings.xml rename to tests/regression_tests/seed/settings.xml diff --git a/tests/regression_tests/seed/test.py b/tests/regression_tests/seed/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/seed/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_source/inputs_true.dat b/tests/regression_tests/source/inputs_true.dat similarity index 100% rename from tests/test_source/inputs_true.dat rename to tests/regression_tests/source/inputs_true.dat diff --git a/tests/test_source/results_true.dat b/tests/regression_tests/source/results_true.dat similarity index 100% rename from tests/test_source/results_true.dat rename to tests/regression_tests/source/results_true.dat diff --git a/tests/test_source/test_source.py b/tests/regression_tests/source/test.py similarity index 97% rename from tests/test_source/test_source.py rename to tests/regression_tests/source/test.py index a872ab481..7a3d054d8 100644 --- a/tests/test_source/test_source.py +++ b/tests/regression_tests/source/test.py @@ -6,7 +6,7 @@ import sys import numpy as np -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_source_file/geometry.xml b/tests/regression_tests/source_file/geometry.xml similarity index 100% rename from tests/test_source_file/geometry.xml rename to tests/regression_tests/source_file/geometry.xml diff --git a/tests/test_source_file/materials.xml b/tests/regression_tests/source_file/materials.xml similarity index 100% rename from tests/test_source_file/materials.xml rename to tests/regression_tests/source_file/materials.xml diff --git a/tests/test_source_file/results_true.dat b/tests/regression_tests/source_file/results_true.dat similarity index 100% rename from tests/test_source_file/results_true.dat rename to tests/regression_tests/source_file/results_true.dat diff --git a/tests/test_source_file/settings.xml b/tests/regression_tests/source_file/settings.xml similarity index 100% rename from tests/test_source_file/settings.xml rename to tests/regression_tests/source_file/settings.xml diff --git a/tests/test_source_file/test_source_file.py b/tests/regression_tests/source_file/test.py similarity index 98% rename from tests/test_source_file/test_source_file.py rename to tests/regression_tests/source_file/test.py index 5631814b4..f04441a20 100644 --- a/tests/test_source_file/test_source_file.py +++ b/tests/regression_tests/source_file/test.py @@ -3,7 +3,7 @@ import glob import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import * diff --git a/tests/test_sourcepoint_batch/geometry.xml b/tests/regression_tests/sourcepoint_batch/geometry.xml similarity index 100% rename from tests/test_sourcepoint_batch/geometry.xml rename to tests/regression_tests/sourcepoint_batch/geometry.xml diff --git a/tests/test_sourcepoint_batch/materials.xml b/tests/regression_tests/sourcepoint_batch/materials.xml similarity index 100% rename from tests/test_sourcepoint_batch/materials.xml rename to tests/regression_tests/sourcepoint_batch/materials.xml diff --git a/tests/test_sourcepoint_batch/results_true.dat b/tests/regression_tests/sourcepoint_batch/results_true.dat similarity index 100% rename from tests/test_sourcepoint_batch/results_true.dat rename to tests/regression_tests/sourcepoint_batch/results_true.dat diff --git a/tests/test_sourcepoint_batch/settings.xml b/tests/regression_tests/sourcepoint_batch/settings.xml similarity index 100% rename from tests/test_sourcepoint_batch/settings.xml rename to tests/regression_tests/sourcepoint_batch/settings.xml diff --git a/tests/test_sourcepoint_batch/test_sourcepoint_batch.py b/tests/regression_tests/sourcepoint_batch/test.py similarity index 95% rename from tests/test_sourcepoint_batch/test_sourcepoint_batch.py rename to tests/regression_tests/sourcepoint_batch/test.py index d5ea0e48b..1239a00dc 100644 --- a/tests/test_sourcepoint_batch/test_sourcepoint_batch.py +++ b/tests/regression_tests/sourcepoint_batch/test.py @@ -3,7 +3,7 @@ import glob import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness from openmc import StatePoint diff --git a/tests/test_sourcepoint_latest/geometry.xml b/tests/regression_tests/sourcepoint_latest/geometry.xml similarity index 100% rename from tests/test_sourcepoint_latest/geometry.xml rename to tests/regression_tests/sourcepoint_latest/geometry.xml diff --git a/tests/test_sourcepoint_latest/materials.xml b/tests/regression_tests/sourcepoint_latest/materials.xml similarity index 100% rename from tests/test_sourcepoint_latest/materials.xml rename to tests/regression_tests/sourcepoint_latest/materials.xml diff --git a/tests/test_sourcepoint_latest/results_true.dat b/tests/regression_tests/sourcepoint_latest/results_true.dat similarity index 100% rename from tests/test_sourcepoint_latest/results_true.dat rename to tests/regression_tests/sourcepoint_latest/results_true.dat diff --git a/tests/test_sourcepoint_latest/settings.xml b/tests/regression_tests/sourcepoint_latest/settings.xml similarity index 100% rename from tests/test_sourcepoint_latest/settings.xml rename to tests/regression_tests/sourcepoint_latest/settings.xml diff --git a/tests/test_sourcepoint_latest/test_sourcepoint_latest.py b/tests/regression_tests/sourcepoint_latest/test.py similarity index 91% rename from tests/test_sourcepoint_latest/test_sourcepoint_latest.py rename to tests/regression_tests/sourcepoint_latest/test.py index c64cc20cc..4af176eec 100644 --- a/tests/test_sourcepoint_latest/test_sourcepoint_latest.py +++ b/tests/regression_tests/sourcepoint_latest/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_sourcepoint_restart/geometry.xml b/tests/regression_tests/sourcepoint_restart/geometry.xml similarity index 100% rename from tests/test_sourcepoint_restart/geometry.xml rename to tests/regression_tests/sourcepoint_restart/geometry.xml diff --git a/tests/test_sourcepoint_restart/materials.xml b/tests/regression_tests/sourcepoint_restart/materials.xml similarity index 100% rename from tests/test_sourcepoint_restart/materials.xml rename to tests/regression_tests/sourcepoint_restart/materials.xml diff --git a/tests/test_sourcepoint_restart/results_true.dat b/tests/regression_tests/sourcepoint_restart/results_true.dat similarity index 100% rename from tests/test_sourcepoint_restart/results_true.dat rename to tests/regression_tests/sourcepoint_restart/results_true.dat diff --git a/tests/test_sourcepoint_restart/settings.xml b/tests/regression_tests/sourcepoint_restart/settings.xml similarity index 100% rename from tests/test_sourcepoint_restart/settings.xml rename to tests/regression_tests/sourcepoint_restart/settings.xml diff --git a/tests/test_sourcepoint_restart/tallies.xml b/tests/regression_tests/sourcepoint_restart/tallies.xml similarity index 100% rename from tests/test_sourcepoint_restart/tallies.xml rename to tests/regression_tests/sourcepoint_restart/tallies.xml diff --git a/tests/regression_tests/sourcepoint_restart/test.py b/tests/regression_tests/sourcepoint_restart/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/sourcepoint_restart/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_statepoint_batch/geometry.xml b/tests/regression_tests/statepoint_batch/geometry.xml similarity index 100% rename from tests/test_statepoint_batch/geometry.xml rename to tests/regression_tests/statepoint_batch/geometry.xml diff --git a/tests/test_statepoint_batch/materials.xml b/tests/regression_tests/statepoint_batch/materials.xml similarity index 100% rename from tests/test_statepoint_batch/materials.xml rename to tests/regression_tests/statepoint_batch/materials.xml diff --git a/tests/test_statepoint_batch/results_true.dat b/tests/regression_tests/statepoint_batch/results_true.dat similarity index 100% rename from tests/test_statepoint_batch/results_true.dat rename to tests/regression_tests/statepoint_batch/results_true.dat diff --git a/tests/test_statepoint_batch/settings.xml b/tests/regression_tests/statepoint_batch/settings.xml similarity index 100% rename from tests/test_statepoint_batch/settings.xml rename to tests/regression_tests/statepoint_batch/settings.xml diff --git a/tests/test_statepoint_batch/test_statepoint_batch.py b/tests/regression_tests/statepoint_batch/test.py similarity index 91% rename from tests/test_statepoint_batch/test_statepoint_batch.py rename to tests/regression_tests/statepoint_batch/test.py index e3e2391ba..0820aa6f0 100644 --- a/tests/test_statepoint_batch/test_statepoint_batch.py +++ b/tests/regression_tests/statepoint_batch/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_statepoint_restart/geometry.xml b/tests/regression_tests/statepoint_restart/geometry.xml similarity index 100% rename from tests/test_statepoint_restart/geometry.xml rename to tests/regression_tests/statepoint_restart/geometry.xml diff --git a/tests/test_statepoint_restart/materials.xml b/tests/regression_tests/statepoint_restart/materials.xml similarity index 100% rename from tests/test_statepoint_restart/materials.xml rename to tests/regression_tests/statepoint_restart/materials.xml diff --git a/tests/test_statepoint_restart/results_true.dat b/tests/regression_tests/statepoint_restart/results_true.dat similarity index 100% rename from tests/test_statepoint_restart/results_true.dat rename to tests/regression_tests/statepoint_restart/results_true.dat diff --git a/tests/test_statepoint_restart/settings.xml b/tests/regression_tests/statepoint_restart/settings.xml similarity index 100% rename from tests/test_statepoint_restart/settings.xml rename to tests/regression_tests/statepoint_restart/settings.xml diff --git a/tests/test_statepoint_restart/tallies.xml b/tests/regression_tests/statepoint_restart/tallies.xml similarity index 100% rename from tests/test_statepoint_restart/tallies.xml rename to tests/regression_tests/statepoint_restart/tallies.xml diff --git a/tests/test_statepoint_restart/test_statepoint_restart.py b/tests/regression_tests/statepoint_restart/test.py similarity index 97% rename from tests/test_statepoint_restart/test_statepoint_restart.py rename to tests/regression_tests/statepoint_restart/test.py index 9c10551de..f3a52c1cf 100644 --- a/tests/test_statepoint_restart/test_statepoint_restart.py +++ b/tests/regression_tests/statepoint_restart/test.py @@ -3,7 +3,7 @@ import glob import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness import openmc diff --git a/tests/test_statepoint_sourcesep/geometry.xml b/tests/regression_tests/statepoint_sourcesep/geometry.xml similarity index 100% rename from tests/test_statepoint_sourcesep/geometry.xml rename to tests/regression_tests/statepoint_sourcesep/geometry.xml diff --git a/tests/test_statepoint_sourcesep/materials.xml b/tests/regression_tests/statepoint_sourcesep/materials.xml similarity index 100% rename from tests/test_statepoint_sourcesep/materials.xml rename to tests/regression_tests/statepoint_sourcesep/materials.xml diff --git a/tests/test_statepoint_sourcesep/results_true.dat b/tests/regression_tests/statepoint_sourcesep/results_true.dat similarity index 100% rename from tests/test_statepoint_sourcesep/results_true.dat rename to tests/regression_tests/statepoint_sourcesep/results_true.dat diff --git a/tests/test_statepoint_sourcesep/settings.xml b/tests/regression_tests/statepoint_sourcesep/settings.xml similarity index 100% rename from tests/test_statepoint_sourcesep/settings.xml rename to tests/regression_tests/statepoint_sourcesep/settings.xml diff --git a/tests/test_statepoint_sourcesep/test_statepoint_sourcesep.py b/tests/regression_tests/statepoint_sourcesep/test.py similarity index 94% rename from tests/test_statepoint_sourcesep/test_statepoint_sourcesep.py rename to tests/regression_tests/statepoint_sourcesep/test.py index f4bdcfb7b..904fa471e 100644 --- a/tests/test_statepoint_sourcesep/test_statepoint_sourcesep.py +++ b/tests/regression_tests/statepoint_sourcesep/test.py @@ -3,7 +3,7 @@ import glob import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_surface_tally/inputs_true.dat b/tests/regression_tests/surface_tally/inputs_true.dat similarity index 100% rename from tests/test_surface_tally/inputs_true.dat rename to tests/regression_tests/surface_tally/inputs_true.dat diff --git a/tests/test_surface_tally/results_true.dat b/tests/regression_tests/surface_tally/results_true.dat similarity index 100% rename from tests/test_surface_tally/results_true.dat rename to tests/regression_tests/surface_tally/results_true.dat diff --git a/tests/test_surface_tally/test_surface_tally.py b/tests/regression_tests/surface_tally/test.py similarity index 99% rename from tests/test_surface_tally/test_surface_tally.py rename to tests/regression_tests/surface_tally/test.py index ae525a683..9729fdf20 100644 --- a/tests/test_surface_tally/test_surface_tally.py +++ b/tests/regression_tests/surface_tally/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import numpy as np import openmc diff --git a/tests/test_survival_biasing/geometry.xml b/tests/regression_tests/survival_biasing/geometry.xml similarity index 100% rename from tests/test_survival_biasing/geometry.xml rename to tests/regression_tests/survival_biasing/geometry.xml diff --git a/tests/test_survival_biasing/materials.xml b/tests/regression_tests/survival_biasing/materials.xml similarity index 100% rename from tests/test_survival_biasing/materials.xml rename to tests/regression_tests/survival_biasing/materials.xml diff --git a/tests/test_survival_biasing/results_true.dat b/tests/regression_tests/survival_biasing/results_true.dat similarity index 100% rename from tests/test_survival_biasing/results_true.dat rename to tests/regression_tests/survival_biasing/results_true.dat diff --git a/tests/test_survival_biasing/settings.xml b/tests/regression_tests/survival_biasing/settings.xml similarity index 100% rename from tests/test_survival_biasing/settings.xml rename to tests/regression_tests/survival_biasing/settings.xml diff --git a/tests/test_survival_biasing/tallies.xml b/tests/regression_tests/survival_biasing/tallies.xml similarity index 100% rename from tests/test_survival_biasing/tallies.xml rename to tests/regression_tests/survival_biasing/tallies.xml diff --git a/tests/regression_tests/survival_biasing/test.py b/tests/regression_tests/survival_biasing/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/survival_biasing/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_tallies/inputs_true.dat b/tests/regression_tests/tallies/inputs_true.dat similarity index 100% rename from tests/test_tallies/inputs_true.dat rename to tests/regression_tests/tallies/inputs_true.dat diff --git a/tests/test_tallies/results_true.dat b/tests/regression_tests/tallies/results_true.dat similarity index 100% rename from tests/test_tallies/results_true.dat rename to tests/regression_tests/tallies/results_true.dat diff --git a/tests/test_tallies/test_tallies.py b/tests/regression_tests/tallies/test.py similarity index 99% rename from tests/test_tallies/test_tallies.py rename to tests/regression_tests/tallies/test.py index 577d2babc..693e941af 100644 --- a/tests/test_tallies/test_tallies.py +++ b/tests/regression_tests/tallies/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import HashedPyAPITestHarness from openmc.filter import * diff --git a/tests/test_tally_aggregation/inputs_true.dat b/tests/regression_tests/tally_aggregation/inputs_true.dat similarity index 100% rename from tests/test_tally_aggregation/inputs_true.dat rename to tests/regression_tests/tally_aggregation/inputs_true.dat diff --git a/tests/test_tally_aggregation/results_true.dat b/tests/regression_tests/tally_aggregation/results_true.dat similarity index 100% rename from tests/test_tally_aggregation/results_true.dat rename to tests/regression_tests/tally_aggregation/results_true.dat diff --git a/tests/test_tally_aggregation/test_tally_aggregation.py b/tests/regression_tests/tally_aggregation/test.py similarity index 97% rename from tests/test_tally_aggregation/test_tally_aggregation.py rename to tests/regression_tests/tally_aggregation/test.py index cf291e026..62d3a3f04 100644 --- a/tests/test_tally_aggregation/test_tally_aggregation.py +++ b/tests/regression_tests/tally_aggregation/test.py @@ -4,7 +4,7 @@ import os import sys import glob import hashlib -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_tally_arithmetic/inputs_true.dat b/tests/regression_tests/tally_arithmetic/inputs_true.dat similarity index 100% rename from tests/test_tally_arithmetic/inputs_true.dat rename to tests/regression_tests/tally_arithmetic/inputs_true.dat diff --git a/tests/test_tally_arithmetic/results_true.dat b/tests/regression_tests/tally_arithmetic/results_true.dat similarity index 100% rename from tests/test_tally_arithmetic/results_true.dat rename to tests/regression_tests/tally_arithmetic/results_true.dat diff --git a/tests/test_tally_arithmetic/test_tally_arithmetic.py b/tests/regression_tests/tally_arithmetic/test.py similarity index 98% rename from tests/test_tally_arithmetic/test_tally_arithmetic.py rename to tests/regression_tests/tally_arithmetic/test.py index 593a0a291..fab1b0fb2 100644 --- a/tests/test_tally_arithmetic/test_tally_arithmetic.py +++ b/tests/regression_tests/tally_arithmetic/test.py @@ -4,7 +4,7 @@ import os import sys import glob import hashlib -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_tally_assumesep/geometry.xml b/tests/regression_tests/tally_assumesep/geometry.xml similarity index 100% rename from tests/test_tally_assumesep/geometry.xml rename to tests/regression_tests/tally_assumesep/geometry.xml diff --git a/tests/test_tally_assumesep/materials.xml b/tests/regression_tests/tally_assumesep/materials.xml similarity index 100% rename from tests/test_tally_assumesep/materials.xml rename to tests/regression_tests/tally_assumesep/materials.xml diff --git a/tests/test_tally_assumesep/results_true.dat b/tests/regression_tests/tally_assumesep/results_true.dat similarity index 100% rename from tests/test_tally_assumesep/results_true.dat rename to tests/regression_tests/tally_assumesep/results_true.dat diff --git a/tests/test_tally_assumesep/settings.xml b/tests/regression_tests/tally_assumesep/settings.xml similarity index 100% rename from tests/test_tally_assumesep/settings.xml rename to tests/regression_tests/tally_assumesep/settings.xml diff --git a/tests/test_tally_assumesep/tallies.xml b/tests/regression_tests/tally_assumesep/tallies.xml similarity index 100% rename from tests/test_tally_assumesep/tallies.xml rename to tests/regression_tests/tally_assumesep/tallies.xml diff --git a/tests/regression_tests/tally_assumesep/test.py b/tests/regression_tests/tally_assumesep/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/tally_assumesep/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_tally_nuclides/geometry.xml b/tests/regression_tests/tally_nuclides/geometry.xml similarity index 100% rename from tests/test_tally_nuclides/geometry.xml rename to tests/regression_tests/tally_nuclides/geometry.xml diff --git a/tests/test_tally_nuclides/materials.xml b/tests/regression_tests/tally_nuclides/materials.xml similarity index 100% rename from tests/test_tally_nuclides/materials.xml rename to tests/regression_tests/tally_nuclides/materials.xml diff --git a/tests/test_tally_nuclides/results_true.dat b/tests/regression_tests/tally_nuclides/results_true.dat similarity index 100% rename from tests/test_tally_nuclides/results_true.dat rename to tests/regression_tests/tally_nuclides/results_true.dat diff --git a/tests/test_tally_nuclides/settings.xml b/tests/regression_tests/tally_nuclides/settings.xml similarity index 100% rename from tests/test_tally_nuclides/settings.xml rename to tests/regression_tests/tally_nuclides/settings.xml diff --git a/tests/test_tally_nuclides/tallies.xml b/tests/regression_tests/tally_nuclides/tallies.xml similarity index 100% rename from tests/test_tally_nuclides/tallies.xml rename to tests/regression_tests/tally_nuclides/tallies.xml diff --git a/tests/regression_tests/tally_nuclides/test.py b/tests/regression_tests/tally_nuclides/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/tally_nuclides/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_tally_slice_merge/inputs_true.dat b/tests/regression_tests/tally_slice_merge/inputs_true.dat similarity index 100% rename from tests/test_tally_slice_merge/inputs_true.dat rename to tests/regression_tests/tally_slice_merge/inputs_true.dat diff --git a/tests/test_tally_slice_merge/results_true.dat b/tests/regression_tests/tally_slice_merge/results_true.dat similarity index 100% rename from tests/test_tally_slice_merge/results_true.dat rename to tests/regression_tests/tally_slice_merge/results_true.dat diff --git a/tests/test_tally_slice_merge/test_tally_slice_merge.py b/tests/regression_tests/tally_slice_merge/test.py similarity index 99% rename from tests/test_tally_slice_merge/test_tally_slice_merge.py rename to tests/regression_tests/tally_slice_merge/test.py index d917d2dad..84908b5cb 100644 --- a/tests/test_tally_slice_merge/test_tally_slice_merge.py +++ b/tests/regression_tests/tally_slice_merge/test.py @@ -7,7 +7,7 @@ import sys import glob import hashlib import itertools -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_trace/geometry.xml b/tests/regression_tests/trace/geometry.xml similarity index 100% rename from tests/test_trace/geometry.xml rename to tests/regression_tests/trace/geometry.xml diff --git a/tests/test_trace/materials.xml b/tests/regression_tests/trace/materials.xml similarity index 100% rename from tests/test_trace/materials.xml rename to tests/regression_tests/trace/materials.xml diff --git a/tests/test_trace/results_true.dat b/tests/regression_tests/trace/results_true.dat similarity index 100% rename from tests/test_trace/results_true.dat rename to tests/regression_tests/trace/results_true.dat diff --git a/tests/test_trace/settings.xml b/tests/regression_tests/trace/settings.xml similarity index 100% rename from tests/test_trace/settings.xml rename to tests/regression_tests/trace/settings.xml diff --git a/tests/regression_tests/trace/test.py b/tests/regression_tests/trace/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/trace/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_track_output/geometry.xml b/tests/regression_tests/track_output/geometry.xml similarity index 100% rename from tests/test_track_output/geometry.xml rename to tests/regression_tests/track_output/geometry.xml diff --git a/tests/test_track_output/materials.xml b/tests/regression_tests/track_output/materials.xml similarity index 100% rename from tests/test_track_output/materials.xml rename to tests/regression_tests/track_output/materials.xml diff --git a/tests/test_track_output/results_true.dat b/tests/regression_tests/track_output/results_true.dat similarity index 100% rename from tests/test_track_output/results_true.dat rename to tests/regression_tests/track_output/results_true.dat diff --git a/tests/test_track_output/settings.xml b/tests/regression_tests/track_output/settings.xml similarity index 100% rename from tests/test_track_output/settings.xml rename to tests/regression_tests/track_output/settings.xml diff --git a/tests/test_track_output/test_track_output.py b/tests/regression_tests/track_output/test.py similarity index 96% rename from tests/test_track_output/test_track_output.py rename to tests/regression_tests/track_output/test.py index 0357aae19..c7a2df4eb 100644 --- a/tests/test_track_output/test_track_output.py +++ b/tests/regression_tests/track_output/test.py @@ -5,7 +5,7 @@ import os from subprocess import call import shutil import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_translation/geometry.xml b/tests/regression_tests/translation/geometry.xml similarity index 100% rename from tests/test_translation/geometry.xml rename to tests/regression_tests/translation/geometry.xml diff --git a/tests/test_translation/materials.xml b/tests/regression_tests/translation/materials.xml similarity index 100% rename from tests/test_translation/materials.xml rename to tests/regression_tests/translation/materials.xml diff --git a/tests/test_translation/results_true.dat b/tests/regression_tests/translation/results_true.dat similarity index 100% rename from tests/test_translation/results_true.dat rename to tests/regression_tests/translation/results_true.dat diff --git a/tests/test_translation/settings.xml b/tests/regression_tests/translation/settings.xml similarity index 100% rename from tests/test_translation/settings.xml rename to tests/regression_tests/translation/settings.xml diff --git a/tests/regression_tests/translation/test.py b/tests/regression_tests/translation/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/translation/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_trigger_batch_interval/geometry.xml b/tests/regression_tests/trigger_batch_interval/geometry.xml similarity index 100% rename from tests/test_trigger_batch_interval/geometry.xml rename to tests/regression_tests/trigger_batch_interval/geometry.xml diff --git a/tests/test_trigger_batch_interval/materials.xml b/tests/regression_tests/trigger_batch_interval/materials.xml similarity index 100% rename from tests/test_trigger_batch_interval/materials.xml rename to tests/regression_tests/trigger_batch_interval/materials.xml diff --git a/tests/test_trigger_batch_interval/results_true.dat b/tests/regression_tests/trigger_batch_interval/results_true.dat similarity index 100% rename from tests/test_trigger_batch_interval/results_true.dat rename to tests/regression_tests/trigger_batch_interval/results_true.dat diff --git a/tests/test_trigger_batch_interval/settings.xml b/tests/regression_tests/trigger_batch_interval/settings.xml similarity index 100% rename from tests/test_trigger_batch_interval/settings.xml rename to tests/regression_tests/trigger_batch_interval/settings.xml diff --git a/tests/test_trigger_batch_interval/tallies.xml b/tests/regression_tests/trigger_batch_interval/tallies.xml similarity index 100% rename from tests/test_trigger_batch_interval/tallies.xml rename to tests/regression_tests/trigger_batch_interval/tallies.xml diff --git a/tests/test_trigger_batch_interval/test_trigger_batch_interval.py b/tests/regression_tests/trigger_batch_interval/test.py similarity index 76% rename from tests/test_trigger_batch_interval/test_trigger_batch_interval.py rename to tests/regression_tests/trigger_batch_interval/test.py index fb88ada00..542c2ffde 100644 --- a/tests/test_trigger_batch_interval/test_trigger_batch_interval.py +++ b/tests/regression_tests/trigger_batch_interval/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_trigger_no_batch_interval/geometry.xml b/tests/regression_tests/trigger_no_batch_interval/geometry.xml similarity index 100% rename from tests/test_trigger_no_batch_interval/geometry.xml rename to tests/regression_tests/trigger_no_batch_interval/geometry.xml diff --git a/tests/test_trigger_no_batch_interval/materials.xml b/tests/regression_tests/trigger_no_batch_interval/materials.xml similarity index 100% rename from tests/test_trigger_no_batch_interval/materials.xml rename to tests/regression_tests/trigger_no_batch_interval/materials.xml diff --git a/tests/test_trigger_no_batch_interval/results_true.dat b/tests/regression_tests/trigger_no_batch_interval/results_true.dat similarity index 100% rename from tests/test_trigger_no_batch_interval/results_true.dat rename to tests/regression_tests/trigger_no_batch_interval/results_true.dat diff --git a/tests/test_trigger_no_batch_interval/settings.xml b/tests/regression_tests/trigger_no_batch_interval/settings.xml similarity index 100% rename from tests/test_trigger_no_batch_interval/settings.xml rename to tests/regression_tests/trigger_no_batch_interval/settings.xml diff --git a/tests/test_trigger_no_batch_interval/tallies.xml b/tests/regression_tests/trigger_no_batch_interval/tallies.xml similarity index 100% rename from tests/test_trigger_no_batch_interval/tallies.xml rename to tests/regression_tests/trigger_no_batch_interval/tallies.xml diff --git a/tests/test_trigger_no_batch_interval/test_trigger_no_batch_interval.py b/tests/regression_tests/trigger_no_batch_interval/test.py similarity index 76% rename from tests/test_trigger_no_batch_interval/test_trigger_no_batch_interval.py rename to tests/regression_tests/trigger_no_batch_interval/test.py index fb88ada00..542c2ffde 100644 --- a/tests/test_trigger_no_batch_interval/test_trigger_no_batch_interval.py +++ b/tests/regression_tests/trigger_no_batch_interval/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_trigger_no_status/geometry.xml b/tests/regression_tests/trigger_no_status/geometry.xml similarity index 100% rename from tests/test_trigger_no_status/geometry.xml rename to tests/regression_tests/trigger_no_status/geometry.xml diff --git a/tests/test_trigger_no_status/materials.xml b/tests/regression_tests/trigger_no_status/materials.xml similarity index 100% rename from tests/test_trigger_no_status/materials.xml rename to tests/regression_tests/trigger_no_status/materials.xml diff --git a/tests/test_trigger_no_status/results_true.dat b/tests/regression_tests/trigger_no_status/results_true.dat similarity index 100% rename from tests/test_trigger_no_status/results_true.dat rename to tests/regression_tests/trigger_no_status/results_true.dat diff --git a/tests/test_trigger_no_status/settings.xml b/tests/regression_tests/trigger_no_status/settings.xml similarity index 100% rename from tests/test_trigger_no_status/settings.xml rename to tests/regression_tests/trigger_no_status/settings.xml diff --git a/tests/test_trigger_no_status/tallies.xml b/tests/regression_tests/trigger_no_status/tallies.xml similarity index 100% rename from tests/test_trigger_no_status/tallies.xml rename to tests/regression_tests/trigger_no_status/tallies.xml diff --git a/tests/regression_tests/trigger_no_status/test.py b/tests/regression_tests/trigger_no_status/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/trigger_no_status/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_trigger_tallies/geometry.xml b/tests/regression_tests/trigger_tallies/geometry.xml similarity index 100% rename from tests/test_trigger_tallies/geometry.xml rename to tests/regression_tests/trigger_tallies/geometry.xml diff --git a/tests/test_trigger_tallies/materials.xml b/tests/regression_tests/trigger_tallies/materials.xml similarity index 100% rename from tests/test_trigger_tallies/materials.xml rename to tests/regression_tests/trigger_tallies/materials.xml diff --git a/tests/test_trigger_tallies/results_true.dat b/tests/regression_tests/trigger_tallies/results_true.dat similarity index 100% rename from tests/test_trigger_tallies/results_true.dat rename to tests/regression_tests/trigger_tallies/results_true.dat diff --git a/tests/test_trigger_tallies/settings.xml b/tests/regression_tests/trigger_tallies/settings.xml similarity index 100% rename from tests/test_trigger_tallies/settings.xml rename to tests/regression_tests/trigger_tallies/settings.xml diff --git a/tests/test_trigger_tallies/tallies.xml b/tests/regression_tests/trigger_tallies/tallies.xml similarity index 100% rename from tests/test_trigger_tallies/tallies.xml rename to tests/regression_tests/trigger_tallies/tallies.xml diff --git a/tests/test_trigger_tallies/test_trigger_tallies.py b/tests/regression_tests/trigger_tallies/test.py similarity index 76% rename from tests/test_trigger_tallies/test_trigger_tallies.py rename to tests/regression_tests/trigger_tallies/test.py index fb88ada00..542c2ffde 100644 --- a/tests/test_trigger_tallies/test_trigger_tallies.py +++ b/tests/regression_tests/trigger_tallies/test.py @@ -2,7 +2,7 @@ import os import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import TestHarness diff --git a/tests/test_triso/inputs_true.dat b/tests/regression_tests/triso/inputs_true.dat similarity index 100% rename from tests/test_triso/inputs_true.dat rename to tests/regression_tests/triso/inputs_true.dat diff --git a/tests/test_triso/results_true.dat b/tests/regression_tests/triso/results_true.dat similarity index 100% rename from tests/test_triso/results_true.dat rename to tests/regression_tests/triso/results_true.dat diff --git a/tests/test_triso/test_triso.py b/tests/regression_tests/triso/test.py similarity index 98% rename from tests/test_triso/test_triso.py rename to tests/regression_tests/triso/test.py index e5c823c01..c92495ace 100644 --- a/tests/test_triso/test_triso.py +++ b/tests/regression_tests/triso/test.py @@ -8,7 +8,7 @@ from math import sqrt import numpy as np -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc import openmc.model diff --git a/tests/test_uniform_fs/geometry.xml b/tests/regression_tests/uniform_fs/geometry.xml similarity index 100% rename from tests/test_uniform_fs/geometry.xml rename to tests/regression_tests/uniform_fs/geometry.xml diff --git a/tests/test_uniform_fs/materials.xml b/tests/regression_tests/uniform_fs/materials.xml similarity index 100% rename from tests/test_uniform_fs/materials.xml rename to tests/regression_tests/uniform_fs/materials.xml diff --git a/tests/test_uniform_fs/results_true.dat b/tests/regression_tests/uniform_fs/results_true.dat similarity index 100% rename from tests/test_uniform_fs/results_true.dat rename to tests/regression_tests/uniform_fs/results_true.dat diff --git a/tests/test_uniform_fs/settings.xml b/tests/regression_tests/uniform_fs/settings.xml similarity index 100% rename from tests/test_uniform_fs/settings.xml rename to tests/regression_tests/uniform_fs/settings.xml diff --git a/tests/regression_tests/uniform_fs/test.py b/tests/regression_tests/uniform_fs/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/uniform_fs/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_universe/geometry.xml b/tests/regression_tests/universe/geometry.xml similarity index 100% rename from tests/test_universe/geometry.xml rename to tests/regression_tests/universe/geometry.xml diff --git a/tests/test_universe/materials.xml b/tests/regression_tests/universe/materials.xml similarity index 100% rename from tests/test_universe/materials.xml rename to tests/regression_tests/universe/materials.xml diff --git a/tests/test_universe/results_true.dat b/tests/regression_tests/universe/results_true.dat similarity index 100% rename from tests/test_universe/results_true.dat rename to tests/regression_tests/universe/results_true.dat diff --git a/tests/test_universe/settings.xml b/tests/regression_tests/universe/settings.xml similarity index 100% rename from tests/test_universe/settings.xml rename to tests/regression_tests/universe/settings.xml diff --git a/tests/regression_tests/universe/test.py b/tests/regression_tests/universe/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/universe/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_void/geometry.xml b/tests/regression_tests/void/geometry.xml similarity index 100% rename from tests/test_void/geometry.xml rename to tests/regression_tests/void/geometry.xml diff --git a/tests/test_void/materials.xml b/tests/regression_tests/void/materials.xml similarity index 100% rename from tests/test_void/materials.xml rename to tests/regression_tests/void/materials.xml diff --git a/tests/test_void/results_true.dat b/tests/regression_tests/void/results_true.dat similarity index 100% rename from tests/test_void/results_true.dat rename to tests/regression_tests/void/results_true.dat diff --git a/tests/test_void/settings.xml b/tests/regression_tests/void/settings.xml similarity index 100% rename from tests/test_void/settings.xml rename to tests/regression_tests/void/settings.xml diff --git a/tests/regression_tests/void/test.py b/tests/regression_tests/void/test.py new file mode 100644 index 000000000..43f8e5ff0 --- /dev/null +++ b/tests/regression_tests/void/test.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +import os +import sys +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) +from testing_harness import TestHarness + + +if __name__ == '__main__': + harness = TestHarness('statepoint.10.h5') + harness.main() diff --git a/tests/test_volume_calc/inputs_true.dat b/tests/regression_tests/volume_calc/inputs_true.dat similarity index 100% rename from tests/test_volume_calc/inputs_true.dat rename to tests/regression_tests/volume_calc/inputs_true.dat diff --git a/tests/test_volume_calc/results_true.dat b/tests/regression_tests/volume_calc/results_true.dat similarity index 100% rename from tests/test_volume_calc/results_true.dat rename to tests/regression_tests/volume_calc/results_true.dat diff --git a/tests/test_volume_calc/test_volume_calc.py b/tests/regression_tests/volume_calc/test.py similarity index 98% rename from tests/test_volume_calc/test_volume_calc.py rename to tests/regression_tests/volume_calc/test.py index 003528b0d..47ce27a12 100644 --- a/tests/test_volume_calc/test_volume_calc.py +++ b/tests/regression_tests/volume_calc/test.py @@ -3,7 +3,7 @@ import os import glob import sys -sys.path.insert(0, os.pardir) +sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from testing_harness import PyAPITestHarness import openmc diff --git a/tests/test_complex_cell/test_complex_cell.py b/tests/test_complex_cell/test_complex_cell.py deleted file mode 100755 index 0669165e2..000000000 --- a/tests/test_complex_cell/test_complex_cell.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python - -import sys -sys.path.insert(0, '..') -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_infinite_cell/test_infinite_cell.py b/tests/test_infinite_cell/test_infinite_cell.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_infinite_cell/test_infinite_cell.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_lattice/test_lattice.py b/tests/test_lattice/test_lattice.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_lattice/test_lattice.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_lattice_hex/test_lattice_hex.py b/tests/test_lattice_hex/test_lattice_hex.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_lattice_hex/test_lattice_hex.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_lattice_mixed/test_lattice_mixed.py b/tests/test_lattice_mixed/test_lattice_mixed.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_lattice_mixed/test_lattice_mixed.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_lattice_multiple/test_lattice_multiple.py b/tests/test_lattice_multiple/test_lattice_multiple.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_lattice_multiple/test_lattice_multiple.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_ptables_off/test_ptables_off.py b/tests/test_ptables_off/test_ptables_off.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_ptables_off/test_ptables_off.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_quadric_surfaces/test_quadric_surfaces.py b/tests/test_quadric_surfaces/test_quadric_surfaces.py deleted file mode 100755 index b04fcc6eb..000000000 --- a/tests/test_quadric_surfaces/test_quadric_surfaces.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_reflective_plane/test_reflective_plane.py b/tests/test_reflective_plane/test_reflective_plane.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_reflective_plane/test_reflective_plane.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_rotation/test_rotation.py b/tests/test_rotation/test_rotation.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_rotation/test_rotation.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_seed/test_seed.py b/tests/test_seed/test_seed.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_seed/test_seed.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_sourcepoint_restart/test_sourcepoint_restart.py b/tests/test_sourcepoint_restart/test_sourcepoint_restart.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_sourcepoint_restart/test_sourcepoint_restart.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_survival_biasing/test_survival_biasing.py b/tests/test_survival_biasing/test_survival_biasing.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_survival_biasing/test_survival_biasing.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_tally_assumesep/test_tally_assumesep.py b/tests/test_tally_assumesep/test_tally_assumesep.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_tally_assumesep/test_tally_assumesep.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_tally_nuclides/test_tally_nuclides.py b/tests/test_tally_nuclides/test_tally_nuclides.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_tally_nuclides/test_tally_nuclides.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_trace/test_trace.py b/tests/test_trace/test_trace.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_trace/test_trace.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_translation/test_translation.py b/tests/test_translation/test_translation.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_translation/test_translation.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_trigger_no_status/test_trigger_no_status.py b/tests/test_trigger_no_status/test_trigger_no_status.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_trigger_no_status/test_trigger_no_status.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_uniform_fs/test_uniform_fs.py b/tests/test_uniform_fs/test_uniform_fs.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_uniform_fs/test_uniform_fs.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_universe/test_universe.py b/tests/test_universe/test_universe.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_universe/test_universe.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() diff --git a/tests/test_void/test_void.py b/tests/test_void/test_void.py deleted file mode 100644 index b04fcc6eb..000000000 --- a/tests/test_void/test_void.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.pardir) -from testing_harness import TestHarness - - -if __name__ == '__main__': - harness = TestHarness('statepoint.10.h5') - harness.main() From 920afa123edf490a558099f21e0fe23a904db78c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 28 Jan 2018 15:57:52 -0600 Subject: [PATCH 150/282] Make tests directory a package (for pytest) --- setup.py | 2 +- tests/__init__.py | 0 tests/regression_tests/__init__.py | 0 tests/regression_tests/asymmetric_lattice/__init__.py | 0 tests/regression_tests/cmfd_feed/__init__.py | 0 tests/regression_tests/cmfd_nofeed/__init__.py | 0 tests/regression_tests/complex_cell/__init__.py | 0 tests/regression_tests/confidence_intervals/__init__.py | 0 tests/regression_tests/create_fission_neutrons/__init__.py | 0 tests/regression_tests/density/__init__.py | 0 tests/regression_tests/diff_tally/__init__.py | 0 tests/regression_tests/distribmat/__init__.py | 0 tests/regression_tests/eigenvalue_genperbatch/__init__.py | 0 tests/regression_tests/eigenvalue_no_inactive/__init__.py | 0 tests/regression_tests/energy_cutoff/__init__.py | 0 tests/regression_tests/energy_grid/__init__.py | 0 tests/regression_tests/energy_laws/__init__.py | 0 tests/regression_tests/enrichment/__init__.py | 0 tests/regression_tests/entropy/__init__.py | 0 tests/regression_tests/filter_distribcell/__init__.py | 0 tests/regression_tests/filter_energyfun/__init__.py | 0 tests/regression_tests/filter_mesh/__init__.py | 0 tests/regression_tests/fixed_source/__init__.py | 0 tests/regression_tests/infinite_cell/__init__.py | 0 tests/regression_tests/iso_in_lab/__init__.py | 0 tests/regression_tests/lattice/__init__.py | 0 tests/regression_tests/lattice_hex/__init__.py | 0 tests/regression_tests/lattice_mixed/__init__.py | 0 tests/regression_tests/lattice_multiple/__init__.py | 0 tests/regression_tests/mg_basic/__init__.py | 0 tests/regression_tests/mg_convert/__init__.py | 0 tests/regression_tests/mg_legendre/__init__.py | 0 tests/regression_tests/mg_max_order/__init__.py | 0 tests/regression_tests/mg_nuclide/__init__.py | 0 tests/regression_tests/mg_survival_biasing/__init__.py | 0 tests/regression_tests/mg_tallies/__init__.py | 0 tests/regression_tests/mgxs_library_ce_to_mg/__init__.py | 0 tests/regression_tests/mgxs_library_condense/__init__.py | 0 tests/regression_tests/mgxs_library_distribcell/__init__.py | 0 tests/regression_tests/mgxs_library_hdf5/__init__.py | 0 tests/regression_tests/mgxs_library_mesh/__init__.py | 0 tests/regression_tests/mgxs_library_no_nuclides/__init__.py | 0 tests/regression_tests/mgxs_library_nuclides/__init__.py | 0 tests/regression_tests/multipole/__init__.py | 0 tests/regression_tests/output/__init__.py | 0 tests/regression_tests/particle_restart_eigval/__init__.py | 0 tests/regression_tests/particle_restart_fixed/__init__.py | 0 tests/regression_tests/periodic/__init__.py | 0 tests/regression_tests/plot/__init__.py | 0 tests/regression_tests/ptables_off/__init__.py | 0 tests/regression_tests/quadric_surfaces/__init__.py | 0 tests/regression_tests/reflective_plane/__init__.py | 0 tests/regression_tests/resonance_scattering/__init__.py | 0 tests/regression_tests/rotation/__init__.py | 0 tests/regression_tests/salphabeta/__init__.py | 0 tests/regression_tests/score_current/__init__.py | 0 tests/regression_tests/seed/__init__.py | 0 tests/regression_tests/source/__init__.py | 0 tests/regression_tests/source_file/__init__.py | 0 tests/regression_tests/sourcepoint_batch/__init__.py | 0 tests/regression_tests/sourcepoint_latest/__init__.py | 0 tests/regression_tests/sourcepoint_restart/__init__.py | 0 tests/regression_tests/statepoint_batch/__init__.py | 0 tests/regression_tests/statepoint_restart/__init__.py | 0 tests/regression_tests/statepoint_sourcesep/__init__.py | 0 tests/regression_tests/surface_tally/__init__.py | 0 tests/regression_tests/survival_biasing/__init__.py | 0 tests/regression_tests/tallies/__init__.py | 0 tests/regression_tests/tally_aggregation/__init__.py | 0 tests/regression_tests/tally_arithmetic/__init__.py | 0 tests/regression_tests/tally_assumesep/__init__.py | 0 tests/regression_tests/tally_nuclides/__init__.py | 0 tests/regression_tests/tally_slice_merge/__init__.py | 0 tests/regression_tests/trace/__init__.py | 0 tests/regression_tests/track_output/__init__.py | 0 tests/regression_tests/translation/__init__.py | 0 tests/regression_tests/trigger_batch_interval/__init__.py | 0 tests/regression_tests/trigger_no_batch_interval/__init__.py | 0 tests/regression_tests/trigger_no_status/__init__.py | 0 tests/regression_tests/trigger_tallies/__init__.py | 0 tests/regression_tests/triso/__init__.py | 0 tests/regression_tests/uniform_fs/__init__.py | 0 tests/regression_tests/universe/__init__.py | 0 tests/regression_tests/void/__init__.py | 0 tests/regression_tests/volume_calc/__init__.py | 0 tests/unit_tests/__init__.py | 0 86 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/__init__.py create mode 100644 tests/regression_tests/__init__.py create mode 100644 tests/regression_tests/asymmetric_lattice/__init__.py create mode 100644 tests/regression_tests/cmfd_feed/__init__.py create mode 100644 tests/regression_tests/cmfd_nofeed/__init__.py create mode 100644 tests/regression_tests/complex_cell/__init__.py create mode 100644 tests/regression_tests/confidence_intervals/__init__.py create mode 100644 tests/regression_tests/create_fission_neutrons/__init__.py create mode 100644 tests/regression_tests/density/__init__.py create mode 100644 tests/regression_tests/diff_tally/__init__.py create mode 100644 tests/regression_tests/distribmat/__init__.py create mode 100644 tests/regression_tests/eigenvalue_genperbatch/__init__.py create mode 100644 tests/regression_tests/eigenvalue_no_inactive/__init__.py create mode 100644 tests/regression_tests/energy_cutoff/__init__.py create mode 100644 tests/regression_tests/energy_grid/__init__.py create mode 100644 tests/regression_tests/energy_laws/__init__.py create mode 100644 tests/regression_tests/enrichment/__init__.py create mode 100644 tests/regression_tests/entropy/__init__.py create mode 100644 tests/regression_tests/filter_distribcell/__init__.py create mode 100644 tests/regression_tests/filter_energyfun/__init__.py create mode 100644 tests/regression_tests/filter_mesh/__init__.py create mode 100644 tests/regression_tests/fixed_source/__init__.py create mode 100644 tests/regression_tests/infinite_cell/__init__.py create mode 100644 tests/regression_tests/iso_in_lab/__init__.py create mode 100644 tests/regression_tests/lattice/__init__.py create mode 100644 tests/regression_tests/lattice_hex/__init__.py create mode 100644 tests/regression_tests/lattice_mixed/__init__.py create mode 100644 tests/regression_tests/lattice_multiple/__init__.py create mode 100644 tests/regression_tests/mg_basic/__init__.py create mode 100644 tests/regression_tests/mg_convert/__init__.py create mode 100644 tests/regression_tests/mg_legendre/__init__.py create mode 100644 tests/regression_tests/mg_max_order/__init__.py create mode 100644 tests/regression_tests/mg_nuclide/__init__.py create mode 100644 tests/regression_tests/mg_survival_biasing/__init__.py create mode 100644 tests/regression_tests/mg_tallies/__init__.py create mode 100644 tests/regression_tests/mgxs_library_ce_to_mg/__init__.py create mode 100644 tests/regression_tests/mgxs_library_condense/__init__.py create mode 100644 tests/regression_tests/mgxs_library_distribcell/__init__.py create mode 100644 tests/regression_tests/mgxs_library_hdf5/__init__.py create mode 100644 tests/regression_tests/mgxs_library_mesh/__init__.py create mode 100644 tests/regression_tests/mgxs_library_no_nuclides/__init__.py create mode 100644 tests/regression_tests/mgxs_library_nuclides/__init__.py create mode 100644 tests/regression_tests/multipole/__init__.py create mode 100644 tests/regression_tests/output/__init__.py create mode 100644 tests/regression_tests/particle_restart_eigval/__init__.py create mode 100644 tests/regression_tests/particle_restart_fixed/__init__.py create mode 100644 tests/regression_tests/periodic/__init__.py create mode 100644 tests/regression_tests/plot/__init__.py create mode 100644 tests/regression_tests/ptables_off/__init__.py create mode 100644 tests/regression_tests/quadric_surfaces/__init__.py create mode 100644 tests/regression_tests/reflective_plane/__init__.py create mode 100644 tests/regression_tests/resonance_scattering/__init__.py create mode 100644 tests/regression_tests/rotation/__init__.py create mode 100644 tests/regression_tests/salphabeta/__init__.py create mode 100644 tests/regression_tests/score_current/__init__.py create mode 100644 tests/regression_tests/seed/__init__.py create mode 100644 tests/regression_tests/source/__init__.py create mode 100644 tests/regression_tests/source_file/__init__.py create mode 100644 tests/regression_tests/sourcepoint_batch/__init__.py create mode 100644 tests/regression_tests/sourcepoint_latest/__init__.py create mode 100644 tests/regression_tests/sourcepoint_restart/__init__.py create mode 100644 tests/regression_tests/statepoint_batch/__init__.py create mode 100644 tests/regression_tests/statepoint_restart/__init__.py create mode 100644 tests/regression_tests/statepoint_sourcesep/__init__.py create mode 100644 tests/regression_tests/surface_tally/__init__.py create mode 100644 tests/regression_tests/survival_biasing/__init__.py create mode 100644 tests/regression_tests/tallies/__init__.py create mode 100644 tests/regression_tests/tally_aggregation/__init__.py create mode 100644 tests/regression_tests/tally_arithmetic/__init__.py create mode 100644 tests/regression_tests/tally_assumesep/__init__.py create mode 100644 tests/regression_tests/tally_nuclides/__init__.py create mode 100644 tests/regression_tests/tally_slice_merge/__init__.py create mode 100644 tests/regression_tests/trace/__init__.py create mode 100644 tests/regression_tests/track_output/__init__.py create mode 100644 tests/regression_tests/translation/__init__.py create mode 100644 tests/regression_tests/trigger_batch_interval/__init__.py create mode 100644 tests/regression_tests/trigger_no_batch_interval/__init__.py create mode 100644 tests/regression_tests/trigger_no_status/__init__.py create mode 100644 tests/regression_tests/trigger_tallies/__init__.py create mode 100644 tests/regression_tests/triso/__init__.py create mode 100644 tests/regression_tests/uniform_fs/__init__.py create mode 100644 tests/regression_tests/universe/__init__.py create mode 100644 tests/regression_tests/void/__init__.py create mode 100644 tests/regression_tests/volume_calc/__init__.py create mode 100644 tests/unit_tests/__init__.py diff --git a/setup.py b/setup.py index 338fe5fec..7bffc516f 100755 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ with open('openmc/__init__.py', 'r') as f: kwargs = { 'name': 'openmc', 'version': version, - 'packages': find_packages(), + 'packages': find_packages(exclude=['tests*']), 'scripts': glob.glob('scripts/openmc-*'), # Data files and librarries diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/__init__.py b/tests/regression_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/asymmetric_lattice/__init__.py b/tests/regression_tests/asymmetric_lattice/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/cmfd_feed/__init__.py b/tests/regression_tests/cmfd_feed/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/cmfd_nofeed/__init__.py b/tests/regression_tests/cmfd_nofeed/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/complex_cell/__init__.py b/tests/regression_tests/complex_cell/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/confidence_intervals/__init__.py b/tests/regression_tests/confidence_intervals/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/create_fission_neutrons/__init__.py b/tests/regression_tests/create_fission_neutrons/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/density/__init__.py b/tests/regression_tests/density/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/diff_tally/__init__.py b/tests/regression_tests/diff_tally/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/distribmat/__init__.py b/tests/regression_tests/distribmat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/eigenvalue_genperbatch/__init__.py b/tests/regression_tests/eigenvalue_genperbatch/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/eigenvalue_no_inactive/__init__.py b/tests/regression_tests/eigenvalue_no_inactive/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/energy_cutoff/__init__.py b/tests/regression_tests/energy_cutoff/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/energy_grid/__init__.py b/tests/regression_tests/energy_grid/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/energy_laws/__init__.py b/tests/regression_tests/energy_laws/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/enrichment/__init__.py b/tests/regression_tests/enrichment/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/entropy/__init__.py b/tests/regression_tests/entropy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/filter_distribcell/__init__.py b/tests/regression_tests/filter_distribcell/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/filter_energyfun/__init__.py b/tests/regression_tests/filter_energyfun/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/filter_mesh/__init__.py b/tests/regression_tests/filter_mesh/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/fixed_source/__init__.py b/tests/regression_tests/fixed_source/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/infinite_cell/__init__.py b/tests/regression_tests/infinite_cell/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/iso_in_lab/__init__.py b/tests/regression_tests/iso_in_lab/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/lattice/__init__.py b/tests/regression_tests/lattice/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/lattice_hex/__init__.py b/tests/regression_tests/lattice_hex/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/lattice_mixed/__init__.py b/tests/regression_tests/lattice_mixed/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/lattice_multiple/__init__.py b/tests/regression_tests/lattice_multiple/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mg_basic/__init__.py b/tests/regression_tests/mg_basic/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mg_convert/__init__.py b/tests/regression_tests/mg_convert/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mg_legendre/__init__.py b/tests/regression_tests/mg_legendre/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mg_max_order/__init__.py b/tests/regression_tests/mg_max_order/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mg_nuclide/__init__.py b/tests/regression_tests/mg_nuclide/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mg_survival_biasing/__init__.py b/tests/regression_tests/mg_survival_biasing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mg_tallies/__init__.py b/tests/regression_tests/mg_tallies/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mgxs_library_ce_to_mg/__init__.py b/tests/regression_tests/mgxs_library_ce_to_mg/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mgxs_library_condense/__init__.py b/tests/regression_tests/mgxs_library_condense/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mgxs_library_distribcell/__init__.py b/tests/regression_tests/mgxs_library_distribcell/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mgxs_library_hdf5/__init__.py b/tests/regression_tests/mgxs_library_hdf5/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mgxs_library_mesh/__init__.py b/tests/regression_tests/mgxs_library_mesh/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mgxs_library_no_nuclides/__init__.py b/tests/regression_tests/mgxs_library_no_nuclides/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mgxs_library_nuclides/__init__.py b/tests/regression_tests/mgxs_library_nuclides/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/multipole/__init__.py b/tests/regression_tests/multipole/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/output/__init__.py b/tests/regression_tests/output/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/particle_restart_eigval/__init__.py b/tests/regression_tests/particle_restart_eigval/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/particle_restart_fixed/__init__.py b/tests/regression_tests/particle_restart_fixed/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/periodic/__init__.py b/tests/regression_tests/periodic/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/plot/__init__.py b/tests/regression_tests/plot/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/ptables_off/__init__.py b/tests/regression_tests/ptables_off/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/quadric_surfaces/__init__.py b/tests/regression_tests/quadric_surfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/reflective_plane/__init__.py b/tests/regression_tests/reflective_plane/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/resonance_scattering/__init__.py b/tests/regression_tests/resonance_scattering/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/rotation/__init__.py b/tests/regression_tests/rotation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/salphabeta/__init__.py b/tests/regression_tests/salphabeta/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/score_current/__init__.py b/tests/regression_tests/score_current/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/seed/__init__.py b/tests/regression_tests/seed/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/source/__init__.py b/tests/regression_tests/source/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/source_file/__init__.py b/tests/regression_tests/source_file/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/sourcepoint_batch/__init__.py b/tests/regression_tests/sourcepoint_batch/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/sourcepoint_latest/__init__.py b/tests/regression_tests/sourcepoint_latest/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/sourcepoint_restart/__init__.py b/tests/regression_tests/sourcepoint_restart/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/statepoint_batch/__init__.py b/tests/regression_tests/statepoint_batch/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/statepoint_restart/__init__.py b/tests/regression_tests/statepoint_restart/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/statepoint_sourcesep/__init__.py b/tests/regression_tests/statepoint_sourcesep/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/surface_tally/__init__.py b/tests/regression_tests/surface_tally/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/survival_biasing/__init__.py b/tests/regression_tests/survival_biasing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/tallies/__init__.py b/tests/regression_tests/tallies/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/tally_aggregation/__init__.py b/tests/regression_tests/tally_aggregation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/tally_arithmetic/__init__.py b/tests/regression_tests/tally_arithmetic/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/tally_assumesep/__init__.py b/tests/regression_tests/tally_assumesep/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/tally_nuclides/__init__.py b/tests/regression_tests/tally_nuclides/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/tally_slice_merge/__init__.py b/tests/regression_tests/tally_slice_merge/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/trace/__init__.py b/tests/regression_tests/trace/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/track_output/__init__.py b/tests/regression_tests/track_output/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/translation/__init__.py b/tests/regression_tests/translation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/trigger_batch_interval/__init__.py b/tests/regression_tests/trigger_batch_interval/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/trigger_no_batch_interval/__init__.py b/tests/regression_tests/trigger_no_batch_interval/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/trigger_no_status/__init__.py b/tests/regression_tests/trigger_no_status/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/trigger_tallies/__init__.py b/tests/regression_tests/trigger_tallies/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/triso/__init__.py b/tests/regression_tests/triso/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/uniform_fs/__init__.py b/tests/regression_tests/uniform_fs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/universe/__init__.py b/tests/regression_tests/universe/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/void/__init__.py b/tests/regression_tests/void/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/volume_calc/__init__.py b/tests/regression_tests/volume_calc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit_tests/__init__.py b/tests/unit_tests/__init__.py new file mode 100644 index 000000000..e69de29bb From 72aebfccf262d798e0ae574eb9c3989ec15be285 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 10:52:36 -0600 Subject: [PATCH 151/282] Fix subtle bug with next_id not getting reset for autogenerated IDs --- openmc/mixin.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openmc/mixin.py b/openmc/mixin.py index d724fc32a..09a63ae4e 100644 --- a/openmc/mixin.py +++ b/openmc/mixin.py @@ -45,14 +45,24 @@ class IDManagerMixin(object): @id.setter def id(self, uid): - cls = type(self) - name = cls.__name__ + # The first time this is called for a class, we search through the MRO + # to determine which class actually holds next_id and used_ids. Since + # next_id is an integer (immutable), we can't modify it directly through + # the instance without just creating a new attribute + try: + cls = self._id_class + except AttributeError: + for cls in self.__class__.__mro__: + if 'next_id' in cls.__dict__: + break + if uid is None: while cls.next_id in cls.used_ids: cls.next_id += 1 self._id = cls.next_id cls.used_ids.add(cls.next_id) else: + name = cls.__name__ cv.check_type('{} ID'.format(name), uid, Integral) cv.check_greater_than('{} ID'.format(name), uid, 0, equality=True) if uid in cls.used_ids: From 98d5496102b418613665f5caa7b447cd8ecdaeae Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 10:53:46 -0600 Subject: [PATCH 152/282] Ability to run regression tests using pytest --- pytest.ini | 4 +++ .../asymmetric_lattice/test.py | 11 ++++--- tests/regression_tests/cmfd_feed/test.py | 10 ++----- tests/regression_tests/cmfd_nofeed/test.py | 10 ++----- tests/regression_tests/complex_cell/test.py | 10 ++----- .../confidence_intervals/test.py | 10 ++----- .../create_fission_neutrons/test.py | 11 +++---- tests/regression_tests/density/test.py | 10 ++----- tests/regression_tests/diff_tally/test.py | 12 ++++---- tests/regression_tests/distribmat/test.py | 11 +++---- .../eigenvalue_genperbatch/test.py | 10 ++----- .../eigenvalue_no_inactive/test.py | 10 ++----- tests/regression_tests/energy_cutoff/test.py | 11 +++---- tests/regression_tests/energy_grid/test.py | 10 ++----- tests/regression_tests/energy_laws/test.py | 11 ++----- tests/regression_tests/enrichment/test.py | 5 +--- tests/regression_tests/entropy/test.py | 11 ++++--- .../filter_distribcell/test.py | 11 +++---- .../regression_tests/filter_energyfun/test.py | 15 ++++------ tests/regression_tests/filter_mesh/test.py | 11 +++---- tests/regression_tests/fixed_source/test.py | 16 ++++------ tests/regression_tests/infinite_cell/test.py | 10 ++----- tests/regression_tests/iso_in_lab/test.py | 10 ++----- tests/regression_tests/lattice/test.py | 10 ++----- tests/regression_tests/lattice_hex/test.py | 10 ++----- tests/regression_tests/lattice_mixed/test.py | 10 ++----- .../regression_tests/lattice_multiple/test.py | 10 ++----- tests/regression_tests/mg_basic/test.py | 11 +++---- tests/regression_tests/mg_convert/test.py | 11 +++---- tests/regression_tests/mg_legendre/test.py | 12 +++----- tests/regression_tests/mg_max_order/test.py | 12 +++----- tests/regression_tests/mg_nuclide/test.py | 12 +++----- .../mg_survival_biasing/test.py | 11 +++---- tests/regression_tests/mg_tallies/test.py | 11 +++---- .../mgxs_library_ce_to_mg/test.py | 15 ++++------ .../mgxs_library_condense/test.py | 16 ++++------ .../mgxs_library_distribcell/test.py | 16 ++++------ .../mgxs_library_hdf5/test.py | 17 ++++------- .../mgxs_library_mesh/test.py | 16 ++++------ .../mgxs_library_no_nuclides/test.py | 13 +++----- .../mgxs_library_nuclides/test.py | 13 +++----- tests/regression_tests/multipole/test.py | 10 +++---- tests/regression_tests/output/test.py | 20 +++++-------- .../particle_restart_eigval/test.py | 10 ++----- .../particle_restart_fixed/test.py | 10 ++----- tests/regression_tests/periodic/test.py | 11 +++---- tests/regression_tests/plot/test.py | 11 +++---- tests/regression_tests/ptables_off/test.py | 10 ++----- .../regression_tests/quadric_surfaces/test.py | 10 ++----- .../regression_tests/reflective_plane/test.py | 10 ++----- .../resonance_scattering/test.py | 11 +++---- tests/regression_tests/rotation/test.py | 10 ++----- tests/regression_tests/salphabeta/test.py | 12 +++----- tests/regression_tests/score_current/test.py | 10 ++----- tests/regression_tests/seed/test.py | 10 ++----- tests/regression_tests/source/test.py | 12 +++----- tests/regression_tests/source_file/test.py | 8 ++--- .../sourcepoint_batch/test.py | 23 ++++++-------- .../sourcepoint_latest/test.py | 13 ++++---- .../sourcepoint_restart/test.py | 10 ++----- .../regression_tests/statepoint_batch/test.py | 10 ++----- .../statepoint_restart/test.py | 11 ++++--- .../statepoint_sourcesep/test.py | 16 ++++------ tests/regression_tests/surface_tally/test.py | 11 +++---- .../regression_tests/survival_biasing/test.py | 10 ++----- tests/regression_tests/tallies/test.py | 12 +++----- .../tally_aggregation/test.py | 16 ++++------ .../regression_tests/tally_arithmetic/test.py | 16 ++++------ .../regression_tests/tally_assumesep/test.py | 10 ++----- tests/regression_tests/tally_nuclides/test.py | 10 ++----- .../tally_slice_merge/test.py | 16 ++++------ tests/regression_tests/trace/test.py | 10 ++----- tests/regression_tests/track_output/test.py | 19 +++++------- tests/regression_tests/translation/test.py | 10 ++----- .../trigger_batch_interval/test.py | 10 ++----- .../trigger_no_batch_interval/test.py | 10 ++----- .../trigger_no_status/test.py | 10 ++----- .../regression_tests/trigger_tallies/test.py | 10 ++----- tests/regression_tests/triso/test.py | 13 +++----- tests/regression_tests/uniform_fs/test.py | 10 ++----- tests/regression_tests/universe/test.py | 10 ++----- tests/regression_tests/void/test.py | 10 ++----- tests/regression_tests/volume_calc/test.py | 13 ++++---- tests/testing_harness.py | 30 ++++++++++++------- 84 files changed, 351 insertions(+), 639 deletions(-) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..d960ba8d2 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +python_files = test*.py +python_classes = NoThanks +filterwarnings = ignore::UserWarning diff --git a/tests/regression_tests/asymmetric_lattice/test.py b/tests/regression_tests/asymmetric_lattice/test.py index f7cbf35c6..8bb238485 100644 --- a/tests/regression_tests/asymmetric_lattice/test.py +++ b/tests/regression_tests/asymmetric_lattice/test.py @@ -1,13 +1,11 @@ -#!/usr/bin/env python - import os -import sys import glob import hashlib -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness + import openmc +from tests.testing_harness import PyAPITestHarness + class AsymmetricLatticeTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): @@ -90,6 +88,7 @@ class AsymmetricLatticeTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_asymmetric_lattice(request): harness = AsymmetricLatticeTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/cmfd_feed/test.py b/tests/regression_tests/cmfd_feed/test.py index 30e61d03f..677c58c2a 100644 --- a/tests/regression_tests/cmfd_feed/test.py +++ b/tests/regression_tests/cmfd_feed/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import CMFDTestHarness +from tests.testing_harness import CMFDTestHarness -if __name__ == '__main__': +def test_cmfd_feed(request): harness = CMFDTestHarness('statepoint.20.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/cmfd_nofeed/test.py b/tests/regression_tests/cmfd_nofeed/test.py index 30e61d03f..113efc051 100644 --- a/tests/regression_tests/cmfd_nofeed/test.py +++ b/tests/regression_tests/cmfd_nofeed/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import CMFDTestHarness +from tests.testing_harness import CMFDTestHarness -if __name__ == '__main__': +def test_cmfd_nofeed(request): harness = CMFDTestHarness('statepoint.20.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/complex_cell/test.py b/tests/regression_tests/complex_cell/test.py index 43f8e5ff0..0e12eccf6 100755 --- a/tests/regression_tests/complex_cell/test.py +++ b/tests/regression_tests/complex_cell/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_complex_cell(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/confidence_intervals/test.py b/tests/regression_tests/confidence_intervals/test.py index 43f8e5ff0..9d87e596b 100755 --- a/tests/regression_tests/confidence_intervals/test.py +++ b/tests/regression_tests/confidence_intervals/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_confidence_intervals(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/create_fission_neutrons/test.py b/tests/regression_tests/create_fission_neutrons/test.py index 080a0ab0d..86ff9a29f 100755 --- a/tests/regression_tests/create_fission_neutrons/test.py +++ b/tests/regression_tests/create_fission_neutrons/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness import openmc +from tests.testing_harness import PyAPITestHarness + class CreateFissionNeutronsTestHarness(PyAPITestHarness): def _build_inputs(self): @@ -69,6 +65,7 @@ class CreateFissionNeutronsTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_create_fission_neutrons(request): harness = CreateFissionNeutronsTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/density/test.py b/tests/regression_tests/density/test.py index 43f8e5ff0..8ce3f5898 100644 --- a/tests/regression_tests/density/test.py +++ b/tests/regression_tests/density/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_density(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/diff_tally/test.py b/tests/regression_tests/diff_tally/test.py index 5f1da9c30..87dcd77b8 100644 --- a/tests/regression_tests/diff_tally/test.py +++ b/tests/regression_tests/diff_tally/test.py @@ -1,15 +1,12 @@ -#!/usr/bin/env python - import glob import os -import sys import pandas as pd - -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness import openmc +from tests.testing_harness import PyAPITestHarness + + class DiffTallyTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): super(DiffTallyTestHarness, self).__init__(*args, **kwargs) @@ -125,6 +122,7 @@ class DiffTallyTestHarness(PyAPITestHarness): return df.to_csv(None, columns=cols, index=False, float_format='%.7e') -if __name__ == '__main__': +def test_diff_tally(request): harness = DiffTallyTestHarness('statepoint.3.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/distribmat/test.py b/tests/regression_tests/distribmat/test.py index d18bb1543..fdb77669e 100644 --- a/tests/regression_tests/distribmat/test.py +++ b/tests/regression_tests/distribmat/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness, PyAPITestHarness import openmc +from tests.testing_harness import TestHarness, PyAPITestHarness + class DistribmatTestHarness(PyAPITestHarness): def _build_inputs(self): @@ -103,6 +99,7 @@ class DistribmatTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_distribmat(request): harness = DistribmatTestHarness('statepoint.5.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/eigenvalue_genperbatch/test.py b/tests/regression_tests/eigenvalue_genperbatch/test.py index a36c2ae37..1ba8e0d63 100644 --- a/tests/regression_tests/eigenvalue_genperbatch/test.py +++ b/tests/regression_tests/eigenvalue_genperbatch/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_eigenvalue_genperbatch(request): harness = TestHarness('statepoint.7.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/eigenvalue_no_inactive/test.py b/tests/regression_tests/eigenvalue_no_inactive/test.py index 43f8e5ff0..a7ed17e0d 100644 --- a/tests/regression_tests/eigenvalue_no_inactive/test.py +++ b/tests/regression_tests/eigenvalue_no_inactive/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_eigenvalue_no_inactive(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/energy_cutoff/test.py b/tests/regression_tests/energy_cutoff/test.py index 74f7b2ab2..d597b331f 100755 --- a/tests/regression_tests/energy_cutoff/test.py +++ b/tests/regression_tests/energy_cutoff/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness import openmc +from tests.testing_harness import PyAPITestHarness + class EnergyCutoffTestHarness(PyAPITestHarness): def _build_inputs(self): @@ -73,6 +69,7 @@ class EnergyCutoffTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_energy_cutoff(request): harness = EnergyCutoffTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/energy_grid/test.py b/tests/regression_tests/energy_grid/test.py index 43f8e5ff0..f03e7de66 100644 --- a/tests/regression_tests/energy_grid/test.py +++ b/tests/regression_tests/energy_grid/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_energy_grid(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/energy_laws/test.py b/tests/regression_tests/energy_laws/test.py index 5180344da..02a58f763 100644 --- a/tests/regression_tests/energy_laws/test.py +++ b/tests/regression_tests/energy_laws/test.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """The purpose of this test is to provide coverage of energy distributions that are not covered in other tests. It has a single material with the following nuclides: @@ -18,13 +16,10 @@ that use linear-linear interpolation. """ -import glob -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_energy_laws(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/enrichment/test.py b/tests/regression_tests/enrichment/test.py index 395fafdf5..a20838c7c 100644 --- a/tests/regression_tests/enrichment/test.py +++ b/tests/regression_tests/enrichment/test.py @@ -1,16 +1,13 @@ -#!/usr/bin/env python - import os import sys import numpy as np -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) from openmc import Material from openmc.data import NATURAL_ABUNDANCE, atomic_mass -if __name__ == '__main__': +def test_enrichment(): # This test doesn't require an OpenMC run. We just need to make sure the # element.expand() method expands Uranium to the proper enrichment. diff --git a/tests/regression_tests/entropy/test.py b/tests/regression_tests/entropy/test.py index bbc6c0c7f..c2ccceffe 100644 --- a/tests/regression_tests/entropy/test.py +++ b/tests/regression_tests/entropy/test.py @@ -1,12 +1,10 @@ -#!/usr/bin/env python - import glob import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness + from openmc import StatePoint +from tests.testing_harness import TestHarness + class EntropyTestHarness(TestHarness): def _get_results(self): @@ -26,6 +24,7 @@ class EntropyTestHarness(TestHarness): return outstr -if __name__ == '__main__': +def test_entropy(request): harness = EntropyTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/filter_distribcell/test.py b/tests/regression_tests/filter_distribcell/test.py index 320a808a5..a1354f56f 100644 --- a/tests/regression_tests/filter_distribcell/test.py +++ b/tests/regression_tests/filter_distribcell/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - import glob -import hashlib import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import * + +from tests.testing_harness import * class DistribcellTestHarness(TestHarness): @@ -72,6 +68,7 @@ class DistribcellTestHarness(TestHarness): 'Tally output file does not exist.' -if __name__ == '__main__': +def test_filter_distribcell(request): harness = DistribcellTestHarness() + harness.request = request harness.main() diff --git a/tests/regression_tests/filter_energyfun/test.py b/tests/regression_tests/filter_energyfun/test.py index 1de522f54..c4d0d60f9 100644 --- a/tests/regression_tests/filter_energyfun/test.py +++ b/tests/regression_tests/filter_energyfun/test.py @@ -1,12 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -import glob -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness import openmc +from tests.testing_harness import PyAPITestHarness + class FilterEnergyFunHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): @@ -38,8 +33,7 @@ class FilterEnergyFunHarness(PyAPITestHarness): def _get_results(self): # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - sp = openmc.StatePoint(statepoint) + sp = openmc.StatePoint(self._sp_name) # Use tally arithmetic to compute the branching ratio. br_tally = sp.tallies[2] / sp.tallies[1] @@ -48,6 +42,7 @@ class FilterEnergyFunHarness(PyAPITestHarness): return br_tally.get_pandas_dataframe().to_string() + '\n' -if __name__ == '__main__': +def test_filter_energyfun(request): harness = FilterEnergyFunHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/filter_mesh/test.py b/tests/regression_tests/filter_mesh/test.py index 4ad5e9005..e5a0baba1 100644 --- a/tests/regression_tests/filter_mesh/test.py +++ b/tests/regression_tests/filter_mesh/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import HashedPyAPITestHarness import openmc +from tests.testing_harness import HashedPyAPITestHarness + class FilterMeshTestHarness(HashedPyAPITestHarness): def __init__(self, *args, **kwargs): @@ -67,6 +63,7 @@ class FilterMeshTestHarness(HashedPyAPITestHarness): self._model.tallies.append(tally) -if __name__ == '__main__': +def test_filter_mesh(request): harness = FilterMeshTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/fixed_source/test.py b/tests/regression_tests/fixed_source/test.py index 3e28610fb..caef20a4c 100644 --- a/tests/regression_tests/fixed_source/test.py +++ b/tests/regression_tests/fixed_source/test.py @@ -1,22 +1,17 @@ -#!/usr/bin/env python - -import glob -import os -import sys import numpy as np -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness + import openmc import openmc.stats +from tests.testing_harness import PyAPITestHarness + class FixedSourceTestHarness(PyAPITestHarness): def _get_results(self): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] outstr = '' - with openmc.StatePoint(statepoint) as sp: + with openmc.StatePoint(self._sp_name) as sp: # Write out tally data. for i, tally_ind in enumerate(sp.tallies): tally = sp.tallies[tally_ind] @@ -36,7 +31,7 @@ class FixedSourceTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_fixed_source(request): mat = openmc.Material() mat.add_nuclide('O16', 1.0) mat.add_nuclide('U238', 0.0001) @@ -61,4 +56,5 @@ if __name__ == '__main__': model.tallies.append(tally) harness = FixedSourceTestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/infinite_cell/test.py b/tests/regression_tests/infinite_cell/test.py index 43f8e5ff0..0e2d8454d 100644 --- a/tests/regression_tests/infinite_cell/test.py +++ b/tests/regression_tests/infinite_cell/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_infinite_cell(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/iso_in_lab/test.py b/tests/regression_tests/iso_in_lab/test.py index 8cc9c7b7d..37bf05a06 100644 --- a/tests/regression_tests/iso_in_lab/test.py +++ b/tests/regression_tests/iso_in_lab/test.py @@ -1,13 +1,9 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness +from tests.testing_harness import PyAPITestHarness -if __name__ == '__main__': +def test_iso_in_lab(request): # Force iso-in-lab scattering. harness = PyAPITestHarness('statepoint.10.h5') harness._model.materials.make_isotropic_in_lab() + harness.request = request harness.main() diff --git a/tests/regression_tests/lattice/test.py b/tests/regression_tests/lattice/test.py index 43f8e5ff0..34c598680 100644 --- a/tests/regression_tests/lattice/test.py +++ b/tests/regression_tests/lattice/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_lattice(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/lattice_hex/test.py b/tests/regression_tests/lattice_hex/test.py index 43f8e5ff0..b0028f3c5 100644 --- a/tests/regression_tests/lattice_hex/test.py +++ b/tests/regression_tests/lattice_hex/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_lattice_hex(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/lattice_mixed/test.py b/tests/regression_tests/lattice_mixed/test.py index 43f8e5ff0..2feb9a138 100644 --- a/tests/regression_tests/lattice_mixed/test.py +++ b/tests/regression_tests/lattice_mixed/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_lattice_mixed(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/lattice_multiple/test.py b/tests/regression_tests/lattice_multiple/test.py index 43f8e5ff0..15daf0bea 100644 --- a/tests/regression_tests/lattice_multiple/test.py +++ b/tests/regression_tests/lattice_multiple/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_lattice_multiple(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/mg_basic/test.py b/tests/regression_tests/mg_basic/test.py index 054503f9a..9fb2681ff 100644 --- a/tests/regression_tests/mg_basic/test.py +++ b/tests/regression_tests/mg_basic/test.py @@ -1,13 +1,10 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness from openmc.examples import slab_mg +from tests.testing_harness import PyAPITestHarness -if __name__ == '__main__': + +def test_mg_basic(request): model = slab_mg() harness = PyAPITestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/mg_convert/test.py b/tests/regression_tests/mg_convert/test.py index 45f701ff1..f51e4e87b 100755 --- a/tests/regression_tests/mg_convert/test.py +++ b/tests/regression_tests/mg_convert/test.py @@ -1,15 +1,11 @@ -#!/usr/bin/env python - import os -import sys import hashlib -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) import numpy as np - -from testing_harness import PyAPITestHarness import openmc +from tests.testing_harness import PyAPITestHarness + # OpenMC simulation parameters batches = 10 inactive = 5 @@ -202,6 +198,7 @@ class MGXSTestHarness(PyAPITestHarness): self._cleanup() -if __name__ == '__main__': +def test_mg_convert(request): harness = MGXSTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/mg_legendre/test.py b/tests/regression_tests/mg_legendre/test.py index 2e574afb6..a15cd5f94 100644 --- a/tests/regression_tests/mg_legendre/test.py +++ b/tests/regression_tests/mg_legendre/test.py @@ -1,16 +1,12 @@ -#!/usr/bin/env python - -import os -import sys - -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness from openmc.examples import slab_mg +from tests.testing_harness import PyAPITestHarness -if __name__ == '__main__': + +def test_mg_legendre(request): model = slab_mg(reps=['iso']) model.settings.tabular_legendre = {'enable': False} harness = PyAPITestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/mg_max_order/test.py b/tests/regression_tests/mg_max_order/test.py index 21fd2c0b6..c8f1ee728 100644 --- a/tests/regression_tests/mg_max_order/test.py +++ b/tests/regression_tests/mg_max_order/test.py @@ -1,15 +1,11 @@ -#!/usr/bin/env python - -import os -import sys - -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness from openmc.examples import slab_mg +from tests.testing_harness import PyAPITestHarness -if __name__ == '__main__': + +def test_mg_max_order(request): model = slab_mg(reps=['iso']) model.settings.max_order = 1 harness = PyAPITestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/mg_nuclide/test.py b/tests/regression_tests/mg_nuclide/test.py index d1c06cd41..fd298b368 100644 --- a/tests/regression_tests/mg_nuclide/test.py +++ b/tests/regression_tests/mg_nuclide/test.py @@ -1,14 +1,10 @@ -#!/usr/bin/env python - -import os -import sys - -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness from openmc.examples import slab_mg +from tests.testing_harness import PyAPITestHarness -if __name__ == '__main__': + +def test_mg_nuclide(request): model = slab_mg(as_macro=False) harness = PyAPITestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/mg_survival_biasing/test.py b/tests/regression_tests/mg_survival_biasing/test.py index 2669201c0..a4c6e4809 100644 --- a/tests/regression_tests/mg_survival_biasing/test.py +++ b/tests/regression_tests/mg_survival_biasing/test.py @@ -1,14 +1,11 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness from openmc.examples import slab_mg +from tests.testing_harness import PyAPITestHarness -if __name__ == '__main__': + +def test_mg_survival_biasing(request): model = slab_mg() model.settings.survival_biasing = True harness = PyAPITestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/mg_tallies/test.py b/tests/regression_tests/mg_tallies/test.py index ec7081e2a..2adf34f7a 100644 --- a/tests/regression_tests/mg_tallies/test.py +++ b/tests/regression_tests/mg_tallies/test.py @@ -1,14 +1,10 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import HashedPyAPITestHarness import openmc from openmc.examples import slab_mg +from tests.testing_harness import HashedPyAPITestHarness -if __name__ == '__main__': + +def test_mg_tallies(request): model = slab_mg(as_macro=False) # Instantiate a tally mesh @@ -92,4 +88,5 @@ if __name__ == '__main__': model.tallies.append(t) harness = HashedPyAPITestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_ce_to_mg/test.py b/tests/regression_tests/mgxs_library_ce_to_mg/test.py index e29cf5831..c8bcef172 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg/test.py +++ b/tests/regression_tests/mgxs_library_ce_to_mg/test.py @@ -1,14 +1,9 @@ -#!/usr/bin/env python - -import os -import sys -import glob -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness import openmc import openmc.mgxs from openmc.examples import pwr_pin_cell +from tests.testing_harness import PyAPITestHarness + class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): @@ -42,8 +37,7 @@ class MGXSTestHarness(PyAPITestHarness): # Build MG Inputs # Get data needed to execute Library calculations. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - sp = openmc.StatePoint(statepoint) + sp = openmc.StatePoint(self._sp_name) self.mgxs_lib.load_from_statepoint(sp) self._model.mgxs_file, self._model.materials, \ self._model.geometry = self.mgxs_lib.create_mg_mode() @@ -80,9 +74,10 @@ class MGXSTestHarness(PyAPITestHarness): os.remove(f) -if __name__ == '__main__': +def test_mgxs_library_ce_to_mg(request): # Set the input set to use the pincell model model = pwr_pin_cell() harness = MGXSTestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_condense/test.py b/tests/regression_tests/mgxs_library_condense/test.py index fd144636f..c88c52341 100644 --- a/tests/regression_tests/mgxs_library_condense/test.py +++ b/tests/regression_tests/mgxs_library_condense/test.py @@ -1,15 +1,11 @@ -#!/usr/bin/env python - -import os -import sys -import glob import hashlib -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness + import openmc import openmc.mgxs from openmc.examples import pwr_pin_cell +from tests.testing_harness import PyAPITestHarness + class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): @@ -38,8 +34,7 @@ class MGXSTestHarness(PyAPITestHarness): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - sp = openmc.StatePoint(statepoint) + sp = openmc.StatePoint(self._sp_name) # Load the MGXS library from the statepoint self.mgxs_lib.load_from_statepoint(sp) @@ -65,8 +60,9 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_mgxs_library_condense(request): # Use the pincell model model = pwr_pin_cell() harness = MGXSTestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_distribcell/test.py b/tests/regression_tests/mgxs_library_distribcell/test.py index e59f4c757..f18ae54b1 100644 --- a/tests/regression_tests/mgxs_library_distribcell/test.py +++ b/tests/regression_tests/mgxs_library_distribcell/test.py @@ -1,15 +1,11 @@ -#!/usr/bin/env python - -import os -import sys -import glob import hashlib -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness + import openmc import openmc.mgxs from openmc.examples import pwr_assembly +from tests.testing_harness import PyAPITestHarness + class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): @@ -43,8 +39,7 @@ class MGXSTestHarness(PyAPITestHarness): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - sp = openmc.StatePoint(statepoint) + sp = openmc.StatePoint(self._sp_name) # Load the MGXS library from the statepoint self.mgxs_lib.load_from_statepoint(sp) @@ -69,7 +64,8 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_mgxs_library_distribcell(request): model = pwr_assembly() harness = MGXSTestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_hdf5/test.py b/tests/regression_tests/mgxs_library_hdf5/test.py index 8daf828ce..b070f22e8 100644 --- a/tests/regression_tests/mgxs_library_hdf5/test.py +++ b/tests/regression_tests/mgxs_library_hdf5/test.py @@ -1,19 +1,14 @@ -#!/usr/bin/env python - import os -import sys -import glob import hashlib import numpy as np import h5py - -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness import openmc import openmc.mgxs from openmc.examples import pwr_pin_cell +from tests.testing_harness import PyAPITestHarness + np.set_printoptions(formatter={'float_kind': '{:.8e}'.format}) @@ -46,8 +41,7 @@ class MGXSTestHarness(PyAPITestHarness): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - sp = openmc.StatePoint(statepoint) + sp = openmc.StatePoint(self._sp_name) # Load the MGXS library from the statepoint self.mgxs_lib.load_from_statepoint(sp) @@ -77,12 +71,13 @@ class MGXSTestHarness(PyAPITestHarness): def _cleanup(self): super(MGXSTestHarness, self)._cleanup() - f = os.path.join(os.getcwd(), 'mgxs.h5') + f = 'mgxs.h5' if os.path.exists(f): os.remove(f) -if __name__ == '__main__': +def test_mgxs_library_hdf5(request): model = pwr_pin_cell() harness = MGXSTestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_mesh/test.py b/tests/regression_tests/mgxs_library_mesh/test.py index 06cb10601..809782467 100644 --- a/tests/regression_tests/mgxs_library_mesh/test.py +++ b/tests/regression_tests/mgxs_library_mesh/test.py @@ -1,14 +1,10 @@ -#!/usr/bin/env python - -import os -import sys -import glob import hashlib -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness + import openmc import openmc.mgxs +from tests.testing_harness import PyAPITestHarness + class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): @@ -47,8 +43,7 @@ class MGXSTestHarness(PyAPITestHarness): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - sp = openmc.StatePoint(statepoint) + sp = openmc.StatePoint(self._sp_name) # Load the MGXS library from the statepoint self.mgxs_lib.load_from_statepoint(sp) @@ -70,6 +65,7 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_mgxs_library_mesh(request): harness = MGXSTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_no_nuclides/test.py b/tests/regression_tests/mgxs_library_no_nuclides/test.py index ede916059..743be994f 100644 --- a/tests/regression_tests/mgxs_library_no_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_no_nuclides/test.py @@ -1,15 +1,11 @@ -#!/usr/bin/env python - -import os -import sys -import glob import hashlib -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness + import openmc import openmc.mgxs from openmc.examples import pwr_pin_cell +from tests.testing_harness import PyAPITestHarness + class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): @@ -39,8 +35,7 @@ class MGXSTestHarness(PyAPITestHarness): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - sp = openmc.StatePoint(statepoint) + sp = openmc.StatePoint(self._sp_name) # Load the MGXS library from the statepoint self.mgxs_lib.load_from_statepoint(sp) diff --git a/tests/regression_tests/mgxs_library_nuclides/test.py b/tests/regression_tests/mgxs_library_nuclides/test.py index e668ab52c..23ebb2329 100644 --- a/tests/regression_tests/mgxs_library_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_nuclides/test.py @@ -1,15 +1,11 @@ -#!/usr/bin/env python - -import os -import sys -import glob import hashlib -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness + import openmc import openmc.mgxs from openmc.examples import pwr_pin_cell +from tests.testing_harness import PyAPITestHarness + class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): @@ -35,8 +31,7 @@ class MGXSTestHarness(PyAPITestHarness): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - sp = openmc.StatePoint(statepoint) + sp = openmc.StatePoint(self._sp_name) # Load the MGXS library from the statepoint self.mgxs_lib.load_from_statepoint(sp) diff --git a/tests/regression_tests/multipole/test.py b/tests/regression_tests/multipole/test.py index 20da97f7c..a91bdae5e 100644 --- a/tests/regression_tests/multipole/test.py +++ b/tests/regression_tests/multipole/test.py @@ -1,11 +1,10 @@ -#!/usr/bin/env python import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness, PyAPITestHarness + import openmc import openmc.model +from tests.testing_harness import TestHarness, PyAPITestHarness + def make_model(): model = openmc.model.Model() @@ -81,7 +80,8 @@ class MultipoleTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_multipole(request): model = make_model() harness = MultipoleTestHarness('statepoint.5.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/output/test.py b/tests/regression_tests/output/test.py index 0092f5491..30fcc3a80 100644 --- a/tests/regression_tests/output/test.py +++ b/tests/regression_tests/output/test.py @@ -1,10 +1,7 @@ -#!/usr/bin/env python - -import glob import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +import glob + +from tests.testing_harness import TestHarness class OutputTestHarness(TestHarness): @@ -21,13 +18,12 @@ class OutputTestHarness(TestHarness): def _cleanup(self): TestHarness._cleanup(self) - output = glob.glob(os.path.join(os.getcwd(), 'summary.*')) - output.append(os.path.join(os.getcwd(), 'cross_sections.out')) - for f in output: - if os.path.exists(f): - os.remove(f) + f = 'summary.h5' + if os.path.exists(f): + os.remove(f) -if __name__ == '__main__': +def test_output(request): harness = OutputTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/particle_restart_eigval/test.py b/tests/regression_tests/particle_restart_eigval/test.py index 455ff9e92..6b9d103fd 100644 --- a/tests/regression_tests/particle_restart_eigval/test.py +++ b/tests/regression_tests/particle_restart_eigval/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import ParticleRestartTestHarness +from tests.testing_harness import ParticleRestartTestHarness -if __name__ == '__main__': +def test_particle_restart_eigval(request): harness = ParticleRestartTestHarness('particle_10_1030.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/particle_restart_fixed/test.py b/tests/regression_tests/particle_restart_fixed/test.py index e473fdb59..428d9586d 100644 --- a/tests/regression_tests/particle_restart_fixed/test.py +++ b/tests/regression_tests/particle_restart_fixed/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import ParticleRestartTestHarness +from tests.testing_harness import ParticleRestartTestHarness -if __name__ == '__main__': +def test_particle_restart_fixed(request): harness = ParticleRestartTestHarness('particle_7_144.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/periodic/test.py b/tests/regression_tests/periodic/test.py index ddd7cd89b..188827a5d 100644 --- a/tests/regression_tests/periodic/test.py +++ b/tests/regression_tests/periodic/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness import openmc +from tests.testing_harness import PyAPITestHarness + class PeriodicTest(PyAPITestHarness): def _build_inputs(self): @@ -55,6 +51,7 @@ class PeriodicTest(PyAPITestHarness): settings.export_to_xml() -if __name__ == '__main__': +def test_periodic(request): harness = PeriodicTest('statepoint.4.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/plot/test.py b/tests/regression_tests/plot/test.py index d0c362ef2..1e7773ea6 100644 --- a/tests/regression_tests/plot/test.py +++ b/tests/regression_tests/plot/test.py @@ -1,16 +1,12 @@ -#!/usr/bin/env python - import glob import hashlib import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness import h5py - import openmc +from tests.testing_harness import TestHarness + class PlotTestHarness(TestHarness): """Specialized TestHarness for running OpenMC plotting tests.""" @@ -60,7 +56,8 @@ class PlotTestHarness(TestHarness): return outstr -if __name__ == '__main__': +def test_plot(request): harness = PlotTestHarness(('plot_1.ppm', 'plot_2.ppm', 'plot_3.ppm', 'plot_4.h5')) + harness.request = request harness.main() diff --git a/tests/regression_tests/ptables_off/test.py b/tests/regression_tests/ptables_off/test.py index 43f8e5ff0..8d67cf400 100644 --- a/tests/regression_tests/ptables_off/test.py +++ b/tests/regression_tests/ptables_off/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_ptables_off(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/quadric_surfaces/test.py b/tests/regression_tests/quadric_surfaces/test.py index 43f8e5ff0..f919f1648 100755 --- a/tests/regression_tests/quadric_surfaces/test.py +++ b/tests/regression_tests/quadric_surfaces/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_quadric_surfaces(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/reflective_plane/test.py b/tests/regression_tests/reflective_plane/test.py index 43f8e5ff0..ba693c61a 100644 --- a/tests/regression_tests/reflective_plane/test.py +++ b/tests/regression_tests/reflective_plane/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_reflective_plane(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/resonance_scattering/test.py b/tests/regression_tests/resonance_scattering/test.py index 3ed7fe227..98096a0ca 100644 --- a/tests/regression_tests/resonance_scattering/test.py +++ b/tests/regression_tests/resonance_scattering/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness import openmc +from tests.testing_harness import PyAPITestHarness + class ResonanceScatteringTestHarness(PyAPITestHarness): def _build_inputs(self): @@ -46,6 +42,7 @@ class ResonanceScatteringTestHarness(PyAPITestHarness): settings.export_to_xml() -if __name__ == '__main__': +def test_resonance_scattering(request): harness = ResonanceScatteringTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/rotation/test.py b/tests/regression_tests/rotation/test.py index 43f8e5ff0..b27e7faa4 100644 --- a/tests/regression_tests/rotation/test.py +++ b/tests/regression_tests/rotation/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_rotation(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/salphabeta/test.py b/tests/regression_tests/salphabeta/test.py index 6ced79a8d..4ab213fce 100644 --- a/tests/regression_tests/salphabeta/test.py +++ b/tests/regression_tests/salphabeta/test.py @@ -1,13 +1,8 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) - -from testing_harness import PyAPITestHarness import openmc import openmc.model +from tests.testing_harness import PyAPITestHarness + def make_model(): model = openmc.model.Model() @@ -78,7 +73,8 @@ def make_model(): return model -if __name__ == '__main__': +def test_salphabeta(request): model = make_model() harness = PyAPITestHarness('statepoint.5.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/score_current/test.py b/tests/regression_tests/score_current/test.py index 70ddc2219..1acc7c604 100644 --- a/tests/regression_tests/score_current/test.py +++ b/tests/regression_tests/score_current/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import HashedTestHarness +from tests.testing_harness import HashedTestHarness -if __name__ == '__main__': +def test_score_current(request): harness = HashedTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/seed/test.py b/tests/regression_tests/seed/test.py index 43f8e5ff0..4c6bf95dc 100644 --- a/tests/regression_tests/seed/test.py +++ b/tests/regression_tests/seed/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_seed(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/source/test.py b/tests/regression_tests/source/test.py index 7a3d054d8..aa17143f0 100644 --- a/tests/regression_tests/source/test.py +++ b/tests/regression_tests/source/test.py @@ -1,15 +1,10 @@ -#!/usr/bin/env python - from math import pi -import os -import sys import numpy as np - -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness import openmc +from tests.testing_harness import PyAPITestHarness + class SourceTestHarness(PyAPITestHarness): def _build_inputs(self): @@ -63,6 +58,7 @@ class SourceTestHarness(PyAPITestHarness): settings.export_to_xml() -if __name__ == '__main__': +def test_source(request): harness = SourceTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/source_file/test.py b/tests/regression_tests/source_file/test.py index f04441a20..1989de4c9 100644 --- a/tests/regression_tests/source_file/test.py +++ b/tests/regression_tests/source_file/test.py @@ -2,9 +2,8 @@ import glob import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import * + +from tests.testing_harness import * settings1=""" @@ -97,6 +96,7 @@ class SourceFileTestHarness(TestHarness): fh.write(settings1) -if __name__ == '__main__': +def test_source_file(request): harness = SourceFileTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/sourcepoint_batch/test.py b/tests/regression_tests/sourcepoint_batch/test.py index 1239a00dc..96326d15b 100644 --- a/tests/regression_tests/sourcepoint_batch/test.py +++ b/tests/regression_tests/sourcepoint_batch/test.py @@ -1,20 +1,15 @@ -#!/usr/bin/env python - import glob -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness + from openmc import StatePoint +from tests.testing_harness import TestHarness + class SourcepointTestHarness(TestHarness): def _test_output_created(self): - """Make sure statepoint.* files have been created.""" - statepoint = glob.glob(os.path.join(os.getcwd(), 'statepoint.*')) - assert len(statepoint) == 5, '5 statepoint files must exist.' - assert statepoint[0].endswith('h5'), \ - 'Statepoint file is not a HDF5 file.' + """Make sure statepoint files have been created.""" + statepoint = glob.glob('statepoint.*.h5') + assert len(statepoint) == 5, 'Five statepoint files must exist.' def _get_results(self): """Digest info in the statepoint and return as a string.""" @@ -22,8 +17,7 @@ class SourcepointTestHarness(TestHarness): outstr = TestHarness._get_results(self) # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - with StatePoint(statepoint) as sp: + with StatePoint(self._sp_name) as sp: # Add the source information. xyz = sp.source[0]['xyz'] outstr += ' '.join(['{0:12.6E}'.format(x) for x in xyz]) @@ -32,6 +26,7 @@ class SourcepointTestHarness(TestHarness): return outstr -if __name__ == '__main__': +def test_sourcepoint_batch(request): harness = SourcepointTestHarness('statepoint.08.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/sourcepoint_latest/test.py b/tests/regression_tests/sourcepoint_latest/test.py index 4af176eec..15082e6d8 100644 --- a/tests/regression_tests/sourcepoint_latest/test.py +++ b/tests/regression_tests/sourcepoint_latest/test.py @@ -1,22 +1,19 @@ -#!/usr/bin/env python - import os import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness + +from tests.testing_harness import TestHarness class SourcepointTestHarness(TestHarness): def _test_output_created(self): """Make sure statepoint.* and source* have been created.""" TestHarness._test_output_created(self) - source = glob.glob(os.path.join(os.getcwd(), 'source.*')) + source = glob.glob(os.path.join(os.getcwd(), 'source.*.h5')) assert len(source) == 1, 'Either multiple or no source files ' \ 'exist.' - assert source[0].endswith('h5'), \ - 'Source file is not a HDF5 file.' -if __name__ == '__main__': +def test_sourcepoint_latest(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/sourcepoint_restart/test.py b/tests/regression_tests/sourcepoint_restart/test.py index 43f8e5ff0..b0d2e9069 100644 --- a/tests/regression_tests/sourcepoint_restart/test.py +++ b/tests/regression_tests/sourcepoint_restart/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_sourcepoint_restart(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/statepoint_batch/test.py b/tests/regression_tests/statepoint_batch/test.py index 0820aa6f0..aeb91e2a6 100644 --- a/tests/regression_tests/statepoint_batch/test.py +++ b/tests/regression_tests/statepoint_batch/test.py @@ -1,9 +1,4 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness class StatepointTestHarness(TestHarness): @@ -18,6 +13,7 @@ class StatepointTestHarness(TestHarness): TestHarness._test_output_created(self) -if __name__ == '__main__': +def test_statepoint_batch(request): harness = StatepointTestHarness() + harness.request = request harness.main() diff --git a/tests/regression_tests/statepoint_restart/test.py b/tests/regression_tests/statepoint_restart/test.py index f3a52c1cf..219a8a7ed 100644 --- a/tests/regression_tests/statepoint_restart/test.py +++ b/tests/regression_tests/statepoint_restart/test.py @@ -1,12 +1,10 @@ -#!/usr/bin/env python - import glob import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness + import openmc +from tests.testing_harness import TestHarness + class StatepointRestartTestHarness(TestHarness): def __init__(self, final_sp, restart_sp): @@ -56,7 +54,8 @@ class StatepointRestartTestHarness(TestHarness): openmc.run(openmc_exec=self._opts.exe, restart_file=statepoint) -if __name__ == '__main__': +def test_statepoint_restart(request): harness = StatepointRestartTestHarness('statepoint.10.h5', 'statepoint.07.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/statepoint_sourcesep/test.py b/tests/regression_tests/statepoint_sourcesep/test.py index 904fa471e..44a45d406 100644 --- a/tests/regression_tests/statepoint_sourcesep/test.py +++ b/tests/regression_tests/statepoint_sourcesep/test.py @@ -1,30 +1,26 @@ -#!/usr/bin/env python - import glob import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness + +from tests.testing_harness import TestHarness class SourcepointTestHarness(TestHarness): def _test_output_created(self): """Make sure statepoint.* and source* have been created.""" TestHarness._test_output_created(self) - source = glob.glob(os.path.join(os.getcwd(), 'source.*')) + source = glob.glob('source.*.h5') assert len(source) == 1, 'Either multiple or no source files ' \ 'exist.' - assert source[0].endswith('h5'), \ - 'Source file is not a HDF5 file.' def _cleanup(self): TestHarness._cleanup(self) - output = glob.glob(os.path.join(os.getcwd(), 'source.*')) + output = glob.glob('source.*.h5') for f in output: if os.path.exists(f): os.remove(f) -if __name__ == '__main__': +def test_statepoint_sourcesep(request): harness = SourcepointTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/surface_tally/test.py b/tests/regression_tests/surface_tally/test.py index 9729fdf20..2d499cb29 100644 --- a/tests/regression_tests/surface_tally/test.py +++ b/tests/regression_tests/surface_tally/test.py @@ -1,13 +1,9 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness import numpy as np import openmc import pandas as pd +from tests.testing_harness import PyAPITestHarness + class SurfaceTallyTestHarness(PyAPITestHarness): def _build_inputs(self): @@ -178,6 +174,7 @@ class SurfaceTallyTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_surface_tally(request): harness = SurfaceTallyTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/survival_biasing/test.py b/tests/regression_tests/survival_biasing/test.py index 43f8e5ff0..2dc7c86ab 100644 --- a/tests/regression_tests/survival_biasing/test.py +++ b/tests/regression_tests/survival_biasing/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_survival_biasing(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/tallies/test.py b/tests/regression_tests/tallies/test.py index 693e941af..25545dd82 100644 --- a/tests/regression_tests/tallies/test.py +++ b/tests/regression_tests/tallies/test.py @@ -1,16 +1,12 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) - -from testing_harness import HashedPyAPITestHarness from openmc.filter import * from openmc import Mesh, Tally, Tallies +from tests.testing_harness import HashedPyAPITestHarness -if __name__ == '__main__': + +def test_tallies(request): harness = HashedPyAPITestHarness('statepoint.5.h5') + harness.request = request model = harness._model # Set settings explicitly diff --git a/tests/regression_tests/tally_aggregation/test.py b/tests/regression_tests/tally_aggregation/test.py index 62d3a3f04..dfa026690 100644 --- a/tests/regression_tests/tally_aggregation/test.py +++ b/tests/regression_tests/tally_aggregation/test.py @@ -1,13 +1,9 @@ -#!/usr/bin/env python - -import os -import sys -import glob import hashlib -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness + import openmc +from tests.testing_harness import PyAPITestHarness + class TallyAggregationTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): @@ -28,8 +24,7 @@ class TallyAggregationTestHarness(PyAPITestHarness): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - sp = openmc.StatePoint(statepoint) + sp = openmc.StatePoint(self._sp_name) # Extract the tally of interest tally = sp.get_tally(name='distribcell tally') @@ -66,6 +61,7 @@ class TallyAggregationTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_tally_aggregation(request): harness = TallyAggregationTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/tally_arithmetic/test.py b/tests/regression_tests/tally_arithmetic/test.py index fab1b0fb2..c87d0149f 100644 --- a/tests/regression_tests/tally_arithmetic/test.py +++ b/tests/regression_tests/tally_arithmetic/test.py @@ -1,13 +1,9 @@ -#!/usr/bin/env python - -import os -import sys -import glob import hashlib -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness + import openmc +from tests.testing_harness import PyAPITestHarness + class TallyArithmeticTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): @@ -43,8 +39,7 @@ class TallyArithmeticTestHarness(PyAPITestHarness): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - sp = openmc.StatePoint(statepoint) + sp = openmc.StatePoint(self._sp_name) # Load the tallies tally_1 = sp.get_tally(name='tally 1') @@ -80,6 +75,7 @@ class TallyArithmeticTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_tally_arithmetic(request): harness = TallyArithmeticTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/tally_assumesep/test.py b/tests/regression_tests/tally_assumesep/test.py index 43f8e5ff0..c4248c10a 100644 --- a/tests/regression_tests/tally_assumesep/test.py +++ b/tests/regression_tests/tally_assumesep/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_tally_assumesep(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/tally_nuclides/test.py b/tests/regression_tests/tally_nuclides/test.py index 43f8e5ff0..7eec5ef3f 100644 --- a/tests/regression_tests/tally_nuclides/test.py +++ b/tests/regression_tests/tally_nuclides/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_tally_nuclides(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/tally_slice_merge/test.py b/tests/regression_tests/tally_slice_merge/test.py index 84908b5cb..4ee99d34a 100644 --- a/tests/regression_tests/tally_slice_merge/test.py +++ b/tests/regression_tests/tally_slice_merge/test.py @@ -1,16 +1,12 @@ -#!/usr/bin/env python - from __future__ import division -import os -import sys -import glob import hashlib import itertools -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness + import openmc +from tests.testing_harness import PyAPITestHarness + class TallySliceMergeTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): @@ -85,8 +81,7 @@ class TallySliceMergeTestHarness(PyAPITestHarness): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. - statepoint = glob.glob(os.path.join(os.getcwd(), self._sp_name))[0] - sp = openmc.StatePoint(statepoint) + sp = openmc.StatePoint(self._sp_name) # Extract the cell tally tallies = [sp.get_tally(name='cell tally')] @@ -171,6 +166,7 @@ class TallySliceMergeTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_tally_slice_merge(request): harness = TallySliceMergeTestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/trace/test.py b/tests/regression_tests/trace/test.py index 43f8e5ff0..c037bb23b 100644 --- a/tests/regression_tests/trace/test.py +++ b/tests/regression_tests/trace/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_trace(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/track_output/test.py b/tests/regression_tests/track_output/test.py index c7a2df4eb..b0abb7ecc 100644 --- a/tests/regression_tests/track_output/test.py +++ b/tests/regression_tests/track_output/test.py @@ -1,12 +1,11 @@ -#!/usr/bin/env python - import glob import os from subprocess import call import shutil -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness + +import pytest + +from tests.testing_harness import TestHarness class TrackTestHarness(TestHarness): @@ -39,14 +38,10 @@ class TrackTestHarness(TestHarness): os.remove(f) -if __name__ == '__main__': +def test_track_output(request): # If vtk python module is not available, we can't run track.py so skip this # test. - try: - import vtk - except ImportError: - print('----------------Skipping test-------------') - shutil.copy('results_true.dat', 'results_test.dat') - exit() + vtk = pytest.importerskip('vtk') harness = TrackTestHarness('statepoint.2.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/translation/test.py b/tests/regression_tests/translation/test.py index 43f8e5ff0..29ef1c011 100644 --- a/tests/regression_tests/translation/test.py +++ b/tests/regression_tests/translation/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_translation(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/trigger_batch_interval/test.py b/tests/regression_tests/trigger_batch_interval/test.py index 542c2ffde..1ac01a08b 100644 --- a/tests/regression_tests/trigger_batch_interval/test.py +++ b/tests/regression_tests/trigger_batch_interval/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_trigger_batch_interval(request): harness = TestHarness('statepoint.15.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/trigger_no_batch_interval/test.py b/tests/regression_tests/trigger_no_batch_interval/test.py index 542c2ffde..d38304fee 100644 --- a/tests/regression_tests/trigger_no_batch_interval/test.py +++ b/tests/regression_tests/trigger_no_batch_interval/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_trigger_no_batch_interval(request): harness = TestHarness('statepoint.15.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/trigger_no_status/test.py b/tests/regression_tests/trigger_no_status/test.py index 43f8e5ff0..98173f8dd 100644 --- a/tests/regression_tests/trigger_no_status/test.py +++ b/tests/regression_tests/trigger_no_status/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_trigger_no_status(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/trigger_tallies/test.py b/tests/regression_tests/trigger_tallies/test.py index 542c2ffde..fde7298ff 100644 --- a/tests/regression_tests/trigger_tallies/test.py +++ b/tests/regression_tests/trigger_tallies/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_trigger_tallies(request): harness = TestHarness('statepoint.15.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/triso/test.py b/tests/regression_tests/triso/test.py index c92495ace..2da26d1d3 100644 --- a/tests/regression_tests/triso/test.py +++ b/tests/regression_tests/triso/test.py @@ -1,18 +1,12 @@ -#!/usr/bin/env python - -import os -import sys -import glob import random from math import sqrt import numpy as np - -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness import openmc import openmc.model +from tests.testing_harness import PyAPITestHarness + class TRISOTestHarness(PyAPITestHarness): def _build_inputs(self): @@ -96,6 +90,7 @@ class TRISOTestHarness(PyAPITestHarness): mats.export_to_xml() -if __name__ == '__main__': +def test_triso(request): harness = TRISOTestHarness('statepoint.5.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/uniform_fs/test.py b/tests/regression_tests/uniform_fs/test.py index 43f8e5ff0..553484e0a 100644 --- a/tests/regression_tests/uniform_fs/test.py +++ b/tests/regression_tests/uniform_fs/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_uniform_fs(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/universe/test.py b/tests/regression_tests/universe/test.py index 43f8e5ff0..5da34d90c 100644 --- a/tests/regression_tests/universe/test.py +++ b/tests/regression_tests/universe/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_universe(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/void/test.py b/tests/regression_tests/void/test.py index 43f8e5ff0..a08813c45 100644 --- a/tests/regression_tests/void/test.py +++ b/tests/regression_tests/void/test.py @@ -1,11 +1,7 @@ -#!/usr/bin/env python - -import os -import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import TestHarness +from tests.testing_harness import TestHarness -if __name__ == '__main__': +def test_void(request): harness = TestHarness('statepoint.10.h5') + harness.request = request harness.main() diff --git a/tests/regression_tests/volume_calc/test.py b/tests/regression_tests/volume_calc/test.py index 47ce27a12..da3a66c36 100644 --- a/tests/regression_tests/volume_calc/test.py +++ b/tests/regression_tests/volume_calc/test.py @@ -1,12 +1,11 @@ -#!/usr/bin/env python - import os import glob import sys -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) -from testing_harness import PyAPITestHarness + import openmc +from tests.testing_harness import PyAPITestHarness + class VolumeTest(PyAPITestHarness): def _build_inputs(self): @@ -57,8 +56,7 @@ class VolumeTest(PyAPITestHarness): def _get_results(self): outstr = '' - for i, filename in enumerate(sorted(glob.glob(os.path.join( - os.getcwd(), 'volume_*.h5')))): + for i, filename in enumerate(sorted(glob.glob('volume_*.h5'))): outstr += 'Volume calculation {}\n'.format(i) # Read volume calculation results @@ -75,6 +73,7 @@ class VolumeTest(PyAPITestHarness): def _test_output_created(self): pass -if __name__ == '__main__': +def test_volume_calc(request): harness = VolumeTest('') + harness.request = request harness.main() diff --git a/tests/testing_harness.py b/tests/testing_harness.py index ad3b8ac1e..9aee4f741 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -11,7 +11,6 @@ import sys import numpy as np -sys.path.insert(0, os.path.join(os.pardir, os.pardir)) import openmc from openmc.examples import pwr_core @@ -33,10 +32,14 @@ class TestHarness(object): def main(self): """Accept commandline arguments and either run or update tests.""" (self._opts, self._args) = self.parser.parse_args() - if self._opts.update: - self.update_results() - else: - self.execute_test() + try: + olddir = self.request.fspath.dirpath().chdir() + if self._opts.update: + self.update_results() + else: + self.execute_test() + finally: + olddir.chdir() def execute_test(self): """Run OpenMC with the appropriate arguments and check the outputs.""" @@ -234,6 +237,7 @@ class PyAPITestHarness(TestHarness): super(PyAPITestHarness, self).__init__(statepoint_name) self.parser.add_option('-b', '--build-inputs', dest='build_only', action='store_true', default=False) + openmc.reset_auto_ids() if model is None: self._model = pwr_core() else: @@ -244,12 +248,16 @@ class PyAPITestHarness(TestHarness): def main(self): """Accept commandline arguments and either run or update tests.""" (self._opts, self._args) = self.parser.parse_args() - if self._opts.build_only: - self._build_inputs() - elif self._opts.update: - self.update_results() - else: - self.execute_test() + try: + olddir = self.request.fspath.dirpath().chdir() + if self._opts.build_only: + self._build_inputs() + elif self._opts.update: + self.update_results() + else: + self.execute_test() + finally: + olddir.chdir() def execute_test(self): """Build input XMLs, run OpenMC, and verify correct results.""" From 73947ad7de5061d094f12a7feebb7e55ca876f31 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 11:39:37 -0600 Subject: [PATCH 153/282] Reset IDs for tests that instantiate models directly --- tests/regression_tests/fixed_source/test.py | 2 +- tests/regression_tests/mg_basic/test.py | 2 +- tests/regression_tests/mg_legendre/test.py | 2 +- tests/regression_tests/mg_max_order/test.py | 2 +- tests/regression_tests/mg_nuclide/test.py | 2 +- .../mg_survival_biasing/test.py | 2 +- tests/regression_tests/mg_tallies/test.py | 2 +- .../mgxs_library_ce_to_mg/test.py | 10 ++++++---- .../mgxs_library_condense/test.py | 2 +- .../mgxs_library_distribcell/test.py | 2 +- .../regression_tests/mgxs_library_hdf5/test.py | 17 +++++++++-------- .../mgxs_library_no_nuclides/test.py | 3 ++- .../mgxs_library_nuclides/test.py | 3 ++- tests/regression_tests/multipole/test.py | 2 +- tests/regression_tests/salphabeta/test.py | 2 +- tests/regression_tests/tallies/test.py | 2 +- tests/regression_tests/track_output/test.py | 2 +- 17 files changed, 32 insertions(+), 27 deletions(-) diff --git a/tests/regression_tests/fixed_source/test.py b/tests/regression_tests/fixed_source/test.py index caef20a4c..e652ed3f8 100644 --- a/tests/regression_tests/fixed_source/test.py +++ b/tests/regression_tests/fixed_source/test.py @@ -31,7 +31,7 @@ class FixedSourceTestHarness(PyAPITestHarness): return outstr -def test_fixed_source(request): +def test_fixed_source(request, reset_ids): mat = openmc.Material() mat.add_nuclide('O16', 1.0) mat.add_nuclide('U238', 0.0001) diff --git a/tests/regression_tests/mg_basic/test.py b/tests/regression_tests/mg_basic/test.py index 9fb2681ff..7b4ec247a 100644 --- a/tests/regression_tests/mg_basic/test.py +++ b/tests/regression_tests/mg_basic/test.py @@ -3,7 +3,7 @@ from openmc.examples import slab_mg from tests.testing_harness import PyAPITestHarness -def test_mg_basic(request): +def test_mg_basic(request, reset_ids): model = slab_mg() harness = PyAPITestHarness('statepoint.10.h5', model) harness.request = request diff --git a/tests/regression_tests/mg_legendre/test.py b/tests/regression_tests/mg_legendre/test.py index a15cd5f94..58ea4a1dc 100644 --- a/tests/regression_tests/mg_legendre/test.py +++ b/tests/regression_tests/mg_legendre/test.py @@ -3,7 +3,7 @@ from openmc.examples import slab_mg from tests.testing_harness import PyAPITestHarness -def test_mg_legendre(request): +def test_mg_legendre(request, reset_ids): model = slab_mg(reps=['iso']) model.settings.tabular_legendre = {'enable': False} diff --git a/tests/regression_tests/mg_max_order/test.py b/tests/regression_tests/mg_max_order/test.py index c8f1ee728..72bca3231 100644 --- a/tests/regression_tests/mg_max_order/test.py +++ b/tests/regression_tests/mg_max_order/test.py @@ -3,7 +3,7 @@ from openmc.examples import slab_mg from tests.testing_harness import PyAPITestHarness -def test_mg_max_order(request): +def test_mg_max_order(request, reset_ids): model = slab_mg(reps=['iso']) model.settings.max_order = 1 harness = PyAPITestHarness('statepoint.10.h5', model) diff --git a/tests/regression_tests/mg_nuclide/test.py b/tests/regression_tests/mg_nuclide/test.py index fd298b368..53eac5e8d 100644 --- a/tests/regression_tests/mg_nuclide/test.py +++ b/tests/regression_tests/mg_nuclide/test.py @@ -3,7 +3,7 @@ from openmc.examples import slab_mg from tests.testing_harness import PyAPITestHarness -def test_mg_nuclide(request): +def test_mg_nuclide(request, reset_ids): model = slab_mg(as_macro=False) harness = PyAPITestHarness('statepoint.10.h5', model) harness.request = request diff --git a/tests/regression_tests/mg_survival_biasing/test.py b/tests/regression_tests/mg_survival_biasing/test.py index a4c6e4809..09a210d04 100644 --- a/tests/regression_tests/mg_survival_biasing/test.py +++ b/tests/regression_tests/mg_survival_biasing/test.py @@ -3,7 +3,7 @@ from openmc.examples import slab_mg from tests.testing_harness import PyAPITestHarness -def test_mg_survival_biasing(request): +def test_mg_survival_biasing(request, reset_ids): model = slab_mg() model.settings.survival_biasing = True harness = PyAPITestHarness('statepoint.10.h5', model) diff --git a/tests/regression_tests/mg_tallies/test.py b/tests/regression_tests/mg_tallies/test.py index 2adf34f7a..3f9bcae79 100644 --- a/tests/regression_tests/mg_tallies/test.py +++ b/tests/regression_tests/mg_tallies/test.py @@ -4,7 +4,7 @@ from openmc.examples import slab_mg from tests.testing_harness import HashedPyAPITestHarness -def test_mg_tallies(request): +def test_mg_tallies(request, reset_ids): model = slab_mg(as_macro=False) # Instantiate a tally mesh diff --git a/tests/regression_tests/mgxs_library_ce_to_mg/test.py b/tests/regression_tests/mgxs_library_ce_to_mg/test.py index c8bcef172..9246694ea 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg/test.py +++ b/tests/regression_tests/mgxs_library_ce_to_mg/test.py @@ -1,3 +1,5 @@ +import os + import openmc import openmc.mgxs from openmc.examples import pwr_pin_cell @@ -52,8 +54,8 @@ class MGXSTestHarness(PyAPITestHarness): self._model.materials.export_to_xml() self._model.mgxs_file.export_to_hdf5() # Dont need tallies.xml, so remove the file - if os.path.exists('./tallies.xml'): - os.remove('./tallies.xml') + if os.path.exists('tallies.xml'): + os.remove('tallies.xml') # Enforce closing statepoint and summary files so HDF5 # does not throw an error during the next OpenMC execution @@ -69,12 +71,12 @@ class MGXSTestHarness(PyAPITestHarness): def _cleanup(self): super(MGXSTestHarness, self)._cleanup() - f = os.path.join(os.getcwd(), 'mgxs.h5') + f = 'mgxs.h5' if os.path.exists(f): os.remove(f) -def test_mgxs_library_ce_to_mg(request): +def test_mgxs_library_ce_to_mg(request, reset_ids): # Set the input set to use the pincell model model = pwr_pin_cell() diff --git a/tests/regression_tests/mgxs_library_condense/test.py b/tests/regression_tests/mgxs_library_condense/test.py index c88c52341..b7ecf8925 100644 --- a/tests/regression_tests/mgxs_library_condense/test.py +++ b/tests/regression_tests/mgxs_library_condense/test.py @@ -60,7 +60,7 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -def test_mgxs_library_condense(request): +def test_mgxs_library_condense(request, reset_ids): # Use the pincell model model = pwr_pin_cell() harness = MGXSTestHarness('statepoint.10.h5', model) diff --git a/tests/regression_tests/mgxs_library_distribcell/test.py b/tests/regression_tests/mgxs_library_distribcell/test.py index f18ae54b1..b9f343eaf 100644 --- a/tests/regression_tests/mgxs_library_distribcell/test.py +++ b/tests/regression_tests/mgxs_library_distribcell/test.py @@ -64,7 +64,7 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -def test_mgxs_library_distribcell(request): +def test_mgxs_library_distribcell(request, reset_ids): model = pwr_assembly() harness = MGXSTestHarness('statepoint.10.h5', model) harness.request = request diff --git a/tests/regression_tests/mgxs_library_hdf5/test.py b/tests/regression_tests/mgxs_library_hdf5/test.py index b070f22e8..38dbeef1a 100644 --- a/tests/regression_tests/mgxs_library_hdf5/test.py +++ b/tests/regression_tests/mgxs_library_hdf5/test.py @@ -10,9 +10,6 @@ from openmc.examples import pwr_pin_cell from tests.testing_harness import PyAPITestHarness -np.set_printoptions(formatter={'float_kind': '{:.8e}'.format}) - - class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): # Generate inputs using parent class routine @@ -76,8 +73,12 @@ class MGXSTestHarness(PyAPITestHarness): os.remove(f) -def test_mgxs_library_hdf5(request): - model = pwr_pin_cell() - harness = MGXSTestHarness('statepoint.10.h5', model) - harness.request = request - harness.main() +def test_mgxs_library_hdf5(request, reset_ids): + try: + np.set_printoptions(formatter={'float_kind': '{:.8e}'.format}) + model = pwr_pin_cell() + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.request = request + harness.main() + finally: + np.set_printoptions(formatter=None) diff --git a/tests/regression_tests/mgxs_library_no_nuclides/test.py b/tests/regression_tests/mgxs_library_no_nuclides/test.py index 743be994f..3e6e23648 100644 --- a/tests/regression_tests/mgxs_library_no_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_no_nuclides/test.py @@ -57,7 +57,8 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_mgxs_library_no_nuclides(request, reset_ids): model = pwr_pin_cell() harness = MGXSTestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_nuclides/test.py b/tests/regression_tests/mgxs_library_nuclides/test.py index 23ebb2329..c670e18e3 100644 --- a/tests/regression_tests/mgxs_library_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_nuclides/test.py @@ -53,7 +53,8 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -if __name__ == '__main__': +def test_mgxs_library_nuclides(request, reset_ids): model = pwr_pin_cell() harness = MGXSTestHarness('statepoint.10.h5', model) + harness.request = request harness.main() diff --git a/tests/regression_tests/multipole/test.py b/tests/regression_tests/multipole/test.py index a91bdae5e..d7ed3ca7c 100644 --- a/tests/regression_tests/multipole/test.py +++ b/tests/regression_tests/multipole/test.py @@ -80,7 +80,7 @@ class MultipoleTestHarness(PyAPITestHarness): return outstr -def test_multipole(request): +def test_multipole(request, reset_ids): model = make_model() harness = MultipoleTestHarness('statepoint.5.h5', model) harness.request = request diff --git a/tests/regression_tests/salphabeta/test.py b/tests/regression_tests/salphabeta/test.py index 4ab213fce..62ccbb226 100644 --- a/tests/regression_tests/salphabeta/test.py +++ b/tests/regression_tests/salphabeta/test.py @@ -73,7 +73,7 @@ def make_model(): return model -def test_salphabeta(request): +def test_salphabeta(request, reset_ids): model = make_model() harness = PyAPITestHarness('statepoint.5.h5', model) harness.request = request diff --git a/tests/regression_tests/tallies/test.py b/tests/regression_tests/tallies/test.py index 25545dd82..3f6f7de74 100644 --- a/tests/regression_tests/tallies/test.py +++ b/tests/regression_tests/tallies/test.py @@ -4,7 +4,7 @@ from openmc import Mesh, Tally, Tallies from tests.testing_harness import HashedPyAPITestHarness -def test_tallies(request): +def test_tallies(request, reset_ids): harness = HashedPyAPITestHarness('statepoint.5.h5') harness.request = request model = harness._model diff --git a/tests/regression_tests/track_output/test.py b/tests/regression_tests/track_output/test.py index b0abb7ecc..f3d665e11 100644 --- a/tests/regression_tests/track_output/test.py +++ b/tests/regression_tests/track_output/test.py @@ -41,7 +41,7 @@ class TrackTestHarness(TestHarness): def test_track_output(request): # If vtk python module is not available, we can't run track.py so skip this # test. - vtk = pytest.importerskip('vtk') + vtk = pytest.importorskip('vtk') harness = TrackTestHarness('statepoint.2.h5') harness.request = request harness.main() From 740f03fce15760433a90835c2197cb5bc42f5160 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 12:10:24 -0600 Subject: [PATCH 154/282] Use single package-level fixture for regression tests --- .../asymmetric_lattice/test.py | 3 +- tests/regression_tests/cmfd_feed/test.py | 3 +- tests/regression_tests/cmfd_nofeed/test.py | 3 +- tests/regression_tests/complex_cell/test.py | 3 +- .../confidence_intervals/test.py | 3 +- tests/regression_tests/conftest.py | 15 ++++++++++ .../create_fission_neutrons/test.py | 3 +- tests/regression_tests/density/test.py | 3 +- tests/regression_tests/diff_tally/test.py | 3 +- tests/regression_tests/distribmat/test.py | 3 +- .../eigenvalue_genperbatch/test.py | 3 +- .../eigenvalue_no_inactive/test.py | 3 +- tests/regression_tests/energy_cutoff/test.py | 3 +- tests/regression_tests/energy_grid/test.py | 3 +- tests/regression_tests/energy_laws/test.py | 3 +- tests/regression_tests/entropy/test.py | 3 +- .../filter_distribcell/test.py | 3 +- .../regression_tests/filter_energyfun/test.py | 3 +- tests/regression_tests/filter_mesh/test.py | 3 +- tests/regression_tests/fixed_source/test.py | 3 +- tests/regression_tests/infinite_cell/test.py | 3 +- tests/regression_tests/iso_in_lab/test.py | 3 +- tests/regression_tests/lattice/test.py | 3 +- tests/regression_tests/lattice_hex/test.py | 3 +- tests/regression_tests/lattice_mixed/test.py | 3 +- .../regression_tests/lattice_multiple/test.py | 3 +- tests/regression_tests/mg_basic/test.py | 3 +- tests/regression_tests/mg_convert/test.py | 3 +- tests/regression_tests/mg_legendre/test.py | 3 +- tests/regression_tests/mg_max_order/test.py | 3 +- tests/regression_tests/mg_nuclide/test.py | 3 +- .../mg_survival_biasing/test.py | 3 +- tests/regression_tests/mg_tallies/test.py | 3 +- .../mgxs_library_ce_to_mg/test.py | 3 +- .../mgxs_library_condense/test.py | 3 +- .../mgxs_library_distribcell/test.py | 3 +- .../mgxs_library_hdf5/test.py | 3 +- .../mgxs_library_mesh/test.py | 3 +- .../mgxs_library_no_nuclides/test.py | 3 +- .../mgxs_library_nuclides/test.py | 3 +- tests/regression_tests/multipole/test.py | 3 +- tests/regression_tests/output/test.py | 3 +- .../particle_restart_eigval/test.py | 3 +- .../particle_restart_fixed/test.py | 3 +- tests/regression_tests/periodic/test.py | 3 +- tests/regression_tests/plot/test.py | 3 +- tests/regression_tests/ptables_off/test.py | 3 +- .../regression_tests/quadric_surfaces/test.py | 3 +- .../regression_tests/reflective_plane/test.py | 3 +- .../resonance_scattering/test.py | 3 +- tests/regression_tests/rotation/test.py | 3 +- tests/regression_tests/salphabeta/test.py | 3 +- tests/regression_tests/score_current/test.py | 3 +- tests/regression_tests/seed/test.py | 3 +- tests/regression_tests/source/test.py | 3 +- tests/regression_tests/source_file/test.py | 3 +- .../sourcepoint_batch/test.py | 3 +- .../sourcepoint_latest/test.py | 3 +- .../sourcepoint_restart/test.py | 3 +- .../regression_tests/statepoint_batch/test.py | 3 +- .../statepoint_restart/test.py | 3 +- .../statepoint_sourcesep/test.py | 3 +- tests/regression_tests/surface_tally/test.py | 3 +- .../regression_tests/survival_biasing/test.py | 3 +- tests/regression_tests/tallies/test.py | 3 +- .../tally_aggregation/test.py | 3 +- .../regression_tests/tally_arithmetic/test.py | 3 +- .../regression_tests/tally_assumesep/test.py | 3 +- tests/regression_tests/tally_nuclides/test.py | 3 +- .../tally_slice_merge/test.py | 3 +- tests/regression_tests/trace/test.py | 3 +- tests/regression_tests/track_output/test.py | 3 +- tests/regression_tests/translation/test.py | 3 +- .../trigger_batch_interval/test.py | 3 +- .../trigger_no_batch_interval/test.py | 3 +- .../trigger_no_status/test.py | 3 +- .../regression_tests/trigger_tallies/test.py | 3 +- tests/regression_tests/triso/test.py | 3 +- tests/regression_tests/uniform_fs/test.py | 3 +- tests/regression_tests/universe/test.py | 3 +- tests/regression_tests/void/test.py | 3 +- tests/regression_tests/volume_calc/test.py | 3 +- tests/testing_harness.py | 29 +++++++------------ 83 files changed, 106 insertions(+), 181 deletions(-) create mode 100644 tests/regression_tests/conftest.py diff --git a/tests/regression_tests/asymmetric_lattice/test.py b/tests/regression_tests/asymmetric_lattice/test.py index 8bb238485..0318d7b92 100644 --- a/tests/regression_tests/asymmetric_lattice/test.py +++ b/tests/regression_tests/asymmetric_lattice/test.py @@ -88,7 +88,6 @@ class AsymmetricLatticeTestHarness(PyAPITestHarness): return outstr -def test_asymmetric_lattice(request): +def test_asymmetric_lattice(): harness = AsymmetricLatticeTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/cmfd_feed/test.py b/tests/regression_tests/cmfd_feed/test.py index 677c58c2a..4fc39d96a 100644 --- a/tests/regression_tests/cmfd_feed/test.py +++ b/tests/regression_tests/cmfd_feed/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import CMFDTestHarness -def test_cmfd_feed(request): +def test_cmfd_feed(): harness = CMFDTestHarness('statepoint.20.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/cmfd_nofeed/test.py b/tests/regression_tests/cmfd_nofeed/test.py index 113efc051..a3c730173 100644 --- a/tests/regression_tests/cmfd_nofeed/test.py +++ b/tests/regression_tests/cmfd_nofeed/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import CMFDTestHarness -def test_cmfd_nofeed(request): +def test_cmfd_nofeed(): harness = CMFDTestHarness('statepoint.20.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/complex_cell/test.py b/tests/regression_tests/complex_cell/test.py index 0e12eccf6..77cbd6cb7 100755 --- a/tests/regression_tests/complex_cell/test.py +++ b/tests/regression_tests/complex_cell/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_complex_cell(request): +def test_complex_cell(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/confidence_intervals/test.py b/tests/regression_tests/confidence_intervals/test.py index 9d87e596b..322741828 100755 --- a/tests/regression_tests/confidence_intervals/test.py +++ b/tests/regression_tests/confidence_intervals/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_confidence_intervals(request): +def test_confidence_intervals(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/conftest.py b/tests/regression_tests/conftest.py new file mode 100644 index 000000000..00ef247dc --- /dev/null +++ b/tests/regression_tests/conftest.py @@ -0,0 +1,15 @@ +import openmc +import pytest + + +@pytest.fixture(scope='module', autouse=True) +def setup_regression_test(request): + # Reset autogenerated IDs assigned to OpenMC objects + openmc.reset_auto_ids() + + # Change to test directory + olddir = request.fspath.dirpath().chdir() + try: + yield + finally: + olddir.chdir() diff --git a/tests/regression_tests/create_fission_neutrons/test.py b/tests/regression_tests/create_fission_neutrons/test.py index 86ff9a29f..f1c1d072c 100755 --- a/tests/regression_tests/create_fission_neutrons/test.py +++ b/tests/regression_tests/create_fission_neutrons/test.py @@ -65,7 +65,6 @@ class CreateFissionNeutronsTestHarness(PyAPITestHarness): return outstr -def test_create_fission_neutrons(request): +def test_create_fission_neutrons(): harness = CreateFissionNeutronsTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/density/test.py b/tests/regression_tests/density/test.py index 8ce3f5898..f3ae6b414 100644 --- a/tests/regression_tests/density/test.py +++ b/tests/regression_tests/density/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_density(request): +def test_density(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/diff_tally/test.py b/tests/regression_tests/diff_tally/test.py index 87dcd77b8..e383b726e 100644 --- a/tests/regression_tests/diff_tally/test.py +++ b/tests/regression_tests/diff_tally/test.py @@ -122,7 +122,6 @@ class DiffTallyTestHarness(PyAPITestHarness): return df.to_csv(None, columns=cols, index=False, float_format='%.7e') -def test_diff_tally(request): +def test_diff_tally(): harness = DiffTallyTestHarness('statepoint.3.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/distribmat/test.py b/tests/regression_tests/distribmat/test.py index fdb77669e..69bb7a67b 100644 --- a/tests/regression_tests/distribmat/test.py +++ b/tests/regression_tests/distribmat/test.py @@ -99,7 +99,6 @@ class DistribmatTestHarness(PyAPITestHarness): return outstr -def test_distribmat(request): +def test_distribmat(): harness = DistribmatTestHarness('statepoint.5.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/eigenvalue_genperbatch/test.py b/tests/regression_tests/eigenvalue_genperbatch/test.py index 1ba8e0d63..0dfb11242 100644 --- a/tests/regression_tests/eigenvalue_genperbatch/test.py +++ b/tests/regression_tests/eigenvalue_genperbatch/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_eigenvalue_genperbatch(request): +def test_eigenvalue_genperbatch(): harness = TestHarness('statepoint.7.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/eigenvalue_no_inactive/test.py b/tests/regression_tests/eigenvalue_no_inactive/test.py index a7ed17e0d..5ab490015 100644 --- a/tests/regression_tests/eigenvalue_no_inactive/test.py +++ b/tests/regression_tests/eigenvalue_no_inactive/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_eigenvalue_no_inactive(request): +def test_eigenvalue_no_inactive(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/energy_cutoff/test.py b/tests/regression_tests/energy_cutoff/test.py index d597b331f..8b1614994 100755 --- a/tests/regression_tests/energy_cutoff/test.py +++ b/tests/regression_tests/energy_cutoff/test.py @@ -69,7 +69,6 @@ class EnergyCutoffTestHarness(PyAPITestHarness): return outstr -def test_energy_cutoff(request): +def test_energy_cutoff(): harness = EnergyCutoffTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/energy_grid/test.py b/tests/regression_tests/energy_grid/test.py index f03e7de66..889bdfbc6 100644 --- a/tests/regression_tests/energy_grid/test.py +++ b/tests/regression_tests/energy_grid/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_energy_grid(request): +def test_energy_grid(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/energy_laws/test.py b/tests/regression_tests/energy_laws/test.py index 02a58f763..8f9bbde35 100644 --- a/tests/regression_tests/energy_laws/test.py +++ b/tests/regression_tests/energy_laws/test.py @@ -19,7 +19,6 @@ that use linear-linear interpolation. from tests.testing_harness import TestHarness -def test_energy_laws(request): +def test_energy_laws(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/entropy/test.py b/tests/regression_tests/entropy/test.py index c2ccceffe..0f1052b6b 100644 --- a/tests/regression_tests/entropy/test.py +++ b/tests/regression_tests/entropy/test.py @@ -24,7 +24,6 @@ class EntropyTestHarness(TestHarness): return outstr -def test_entropy(request): +def test_entropy(): harness = EntropyTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/filter_distribcell/test.py b/tests/regression_tests/filter_distribcell/test.py index a1354f56f..bdd134bf5 100644 --- a/tests/regression_tests/filter_distribcell/test.py +++ b/tests/regression_tests/filter_distribcell/test.py @@ -68,7 +68,6 @@ class DistribcellTestHarness(TestHarness): 'Tally output file does not exist.' -def test_filter_distribcell(request): +def test_filter_distribcell(): harness = DistribcellTestHarness() - harness.request = request harness.main() diff --git a/tests/regression_tests/filter_energyfun/test.py b/tests/regression_tests/filter_energyfun/test.py index c4d0d60f9..d9148dd35 100644 --- a/tests/regression_tests/filter_energyfun/test.py +++ b/tests/regression_tests/filter_energyfun/test.py @@ -42,7 +42,6 @@ class FilterEnergyFunHarness(PyAPITestHarness): return br_tally.get_pandas_dataframe().to_string() + '\n' -def test_filter_energyfun(request): +def test_filter_energyfun(): harness = FilterEnergyFunHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/filter_mesh/test.py b/tests/regression_tests/filter_mesh/test.py index e5a0baba1..66cd1ac19 100644 --- a/tests/regression_tests/filter_mesh/test.py +++ b/tests/regression_tests/filter_mesh/test.py @@ -63,7 +63,6 @@ class FilterMeshTestHarness(HashedPyAPITestHarness): self._model.tallies.append(tally) -def test_filter_mesh(request): +def test_filter_mesh(): harness = FilterMeshTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/fixed_source/test.py b/tests/regression_tests/fixed_source/test.py index e652ed3f8..7d98de9fc 100644 --- a/tests/regression_tests/fixed_source/test.py +++ b/tests/regression_tests/fixed_source/test.py @@ -31,7 +31,7 @@ class FixedSourceTestHarness(PyAPITestHarness): return outstr -def test_fixed_source(request, reset_ids): +def test_fixed_source(): mat = openmc.Material() mat.add_nuclide('O16', 1.0) mat.add_nuclide('U238', 0.0001) @@ -56,5 +56,4 @@ def test_fixed_source(request, reset_ids): model.tallies.append(tally) harness = FixedSourceTestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/infinite_cell/test.py b/tests/regression_tests/infinite_cell/test.py index 0e2d8454d..59abec300 100644 --- a/tests/regression_tests/infinite_cell/test.py +++ b/tests/regression_tests/infinite_cell/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_infinite_cell(request): +def test_infinite_cell(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/iso_in_lab/test.py b/tests/regression_tests/iso_in_lab/test.py index 37bf05a06..0e8edc218 100644 --- a/tests/regression_tests/iso_in_lab/test.py +++ b/tests/regression_tests/iso_in_lab/test.py @@ -1,9 +1,8 @@ from tests.testing_harness import PyAPITestHarness -def test_iso_in_lab(request): +def test_iso_in_lab(): # Force iso-in-lab scattering. harness = PyAPITestHarness('statepoint.10.h5') harness._model.materials.make_isotropic_in_lab() - harness.request = request harness.main() diff --git a/tests/regression_tests/lattice/test.py b/tests/regression_tests/lattice/test.py index 34c598680..a32b9a629 100644 --- a/tests/regression_tests/lattice/test.py +++ b/tests/regression_tests/lattice/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_lattice(request): +def test_lattice(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/lattice_hex/test.py b/tests/regression_tests/lattice_hex/test.py index b0028f3c5..eb63f84df 100644 --- a/tests/regression_tests/lattice_hex/test.py +++ b/tests/regression_tests/lattice_hex/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_lattice_hex(request): +def test_lattice_hex(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/lattice_mixed/test.py b/tests/regression_tests/lattice_mixed/test.py index 2feb9a138..3df1d7a4f 100644 --- a/tests/regression_tests/lattice_mixed/test.py +++ b/tests/regression_tests/lattice_mixed/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_lattice_mixed(request): +def test_lattice_mixed(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/lattice_multiple/test.py b/tests/regression_tests/lattice_multiple/test.py index 15daf0bea..f0672d9a4 100644 --- a/tests/regression_tests/lattice_multiple/test.py +++ b/tests/regression_tests/lattice_multiple/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_lattice_multiple(request): +def test_lattice_multiple(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/mg_basic/test.py b/tests/regression_tests/mg_basic/test.py index 7b4ec247a..3c22e6040 100644 --- a/tests/regression_tests/mg_basic/test.py +++ b/tests/regression_tests/mg_basic/test.py @@ -3,8 +3,7 @@ from openmc.examples import slab_mg from tests.testing_harness import PyAPITestHarness -def test_mg_basic(request, reset_ids): +def test_mg_basic(): model = slab_mg() harness = PyAPITestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/mg_convert/test.py b/tests/regression_tests/mg_convert/test.py index f51e4e87b..e6a8690c2 100755 --- a/tests/regression_tests/mg_convert/test.py +++ b/tests/regression_tests/mg_convert/test.py @@ -198,7 +198,6 @@ class MGXSTestHarness(PyAPITestHarness): self._cleanup() -def test_mg_convert(request): +def test_mg_convert(): harness = MGXSTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/mg_legendre/test.py b/tests/regression_tests/mg_legendre/test.py index 58ea4a1dc..5a57f758e 100644 --- a/tests/regression_tests/mg_legendre/test.py +++ b/tests/regression_tests/mg_legendre/test.py @@ -3,10 +3,9 @@ from openmc.examples import slab_mg from tests.testing_harness import PyAPITestHarness -def test_mg_legendre(request, reset_ids): +def test_mg_legendre(): model = slab_mg(reps=['iso']) model.settings.tabular_legendre = {'enable': False} harness = PyAPITestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/mg_max_order/test.py b/tests/regression_tests/mg_max_order/test.py index 72bca3231..20cc4f805 100644 --- a/tests/regression_tests/mg_max_order/test.py +++ b/tests/regression_tests/mg_max_order/test.py @@ -3,9 +3,8 @@ from openmc.examples import slab_mg from tests.testing_harness import PyAPITestHarness -def test_mg_max_order(request, reset_ids): +def test_mg_max_order(): model = slab_mg(reps=['iso']) model.settings.max_order = 1 harness = PyAPITestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/mg_nuclide/test.py b/tests/regression_tests/mg_nuclide/test.py index 53eac5e8d..44206ef28 100644 --- a/tests/regression_tests/mg_nuclide/test.py +++ b/tests/regression_tests/mg_nuclide/test.py @@ -3,8 +3,7 @@ from openmc.examples import slab_mg from tests.testing_harness import PyAPITestHarness -def test_mg_nuclide(request, reset_ids): +def test_mg_nuclide(): model = slab_mg(as_macro=False) harness = PyAPITestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/mg_survival_biasing/test.py b/tests/regression_tests/mg_survival_biasing/test.py index 09a210d04..3c6c77a37 100644 --- a/tests/regression_tests/mg_survival_biasing/test.py +++ b/tests/regression_tests/mg_survival_biasing/test.py @@ -3,9 +3,8 @@ from openmc.examples import slab_mg from tests.testing_harness import PyAPITestHarness -def test_mg_survival_biasing(request, reset_ids): +def test_mg_survival_biasing(): model = slab_mg() model.settings.survival_biasing = True harness = PyAPITestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/mg_tallies/test.py b/tests/regression_tests/mg_tallies/test.py index 3f9bcae79..8952cc4ad 100644 --- a/tests/regression_tests/mg_tallies/test.py +++ b/tests/regression_tests/mg_tallies/test.py @@ -4,7 +4,7 @@ from openmc.examples import slab_mg from tests.testing_harness import HashedPyAPITestHarness -def test_mg_tallies(request, reset_ids): +def test_mg_tallies(): model = slab_mg(as_macro=False) # Instantiate a tally mesh @@ -88,5 +88,4 @@ def test_mg_tallies(request, reset_ids): model.tallies.append(t) harness = HashedPyAPITestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_ce_to_mg/test.py b/tests/regression_tests/mgxs_library_ce_to_mg/test.py index 9246694ea..9dc640acd 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg/test.py +++ b/tests/regression_tests/mgxs_library_ce_to_mg/test.py @@ -76,10 +76,9 @@ class MGXSTestHarness(PyAPITestHarness): os.remove(f) -def test_mgxs_library_ce_to_mg(request, reset_ids): +def test_mgxs_library_ce_to_mg(): # Set the input set to use the pincell model model = pwr_pin_cell() harness = MGXSTestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_condense/test.py b/tests/regression_tests/mgxs_library_condense/test.py index b7ecf8925..06b470478 100644 --- a/tests/regression_tests/mgxs_library_condense/test.py +++ b/tests/regression_tests/mgxs_library_condense/test.py @@ -60,9 +60,8 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -def test_mgxs_library_condense(request, reset_ids): +def test_mgxs_library_condense(): # Use the pincell model model = pwr_pin_cell() harness = MGXSTestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_distribcell/test.py b/tests/regression_tests/mgxs_library_distribcell/test.py index b9f343eaf..d7bc84bf5 100644 --- a/tests/regression_tests/mgxs_library_distribcell/test.py +++ b/tests/regression_tests/mgxs_library_distribcell/test.py @@ -64,8 +64,7 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -def test_mgxs_library_distribcell(request, reset_ids): +def test_mgxs_library_distribcell(): model = pwr_assembly() harness = MGXSTestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_hdf5/test.py b/tests/regression_tests/mgxs_library_hdf5/test.py index 38dbeef1a..8eed0258a 100644 --- a/tests/regression_tests/mgxs_library_hdf5/test.py +++ b/tests/regression_tests/mgxs_library_hdf5/test.py @@ -73,12 +73,11 @@ class MGXSTestHarness(PyAPITestHarness): os.remove(f) -def test_mgxs_library_hdf5(request, reset_ids): +def test_mgxs_library_hdf5(): try: np.set_printoptions(formatter={'float_kind': '{:.8e}'.format}) model = pwr_pin_cell() harness = MGXSTestHarness('statepoint.10.h5', model) - harness.request = request harness.main() finally: np.set_printoptions(formatter=None) diff --git a/tests/regression_tests/mgxs_library_mesh/test.py b/tests/regression_tests/mgxs_library_mesh/test.py index 809782467..70bd2113c 100644 --- a/tests/regression_tests/mgxs_library_mesh/test.py +++ b/tests/regression_tests/mgxs_library_mesh/test.py @@ -65,7 +65,6 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -def test_mgxs_library_mesh(request): +def test_mgxs_library_mesh(): harness = MGXSTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_no_nuclides/test.py b/tests/regression_tests/mgxs_library_no_nuclides/test.py index 3e6e23648..c59b52189 100644 --- a/tests/regression_tests/mgxs_library_no_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_no_nuclides/test.py @@ -57,8 +57,7 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -def test_mgxs_library_no_nuclides(request, reset_ids): +def test_mgxs_library_no_nuclides(): model = pwr_pin_cell() harness = MGXSTestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/mgxs_library_nuclides/test.py b/tests/regression_tests/mgxs_library_nuclides/test.py index c670e18e3..a65fb9a6d 100644 --- a/tests/regression_tests/mgxs_library_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_nuclides/test.py @@ -53,8 +53,7 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -def test_mgxs_library_nuclides(request, reset_ids): +def test_mgxs_library_nuclides(): model = pwr_pin_cell() harness = MGXSTestHarness('statepoint.10.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/multipole/test.py b/tests/regression_tests/multipole/test.py index d7ed3ca7c..43aa9963a 100644 --- a/tests/regression_tests/multipole/test.py +++ b/tests/regression_tests/multipole/test.py @@ -80,8 +80,7 @@ class MultipoleTestHarness(PyAPITestHarness): return outstr -def test_multipole(request, reset_ids): +def test_multipole(): model = make_model() harness = MultipoleTestHarness('statepoint.5.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/output/test.py b/tests/regression_tests/output/test.py index 30fcc3a80..d9568ba4b 100644 --- a/tests/regression_tests/output/test.py +++ b/tests/regression_tests/output/test.py @@ -23,7 +23,6 @@ class OutputTestHarness(TestHarness): os.remove(f) -def test_output(request): +def test_output(): harness = OutputTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/particle_restart_eigval/test.py b/tests/regression_tests/particle_restart_eigval/test.py index 6b9d103fd..a30eb9787 100644 --- a/tests/regression_tests/particle_restart_eigval/test.py +++ b/tests/regression_tests/particle_restart_eigval/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import ParticleRestartTestHarness -def test_particle_restart_eigval(request): +def test_particle_restart_eigval(): harness = ParticleRestartTestHarness('particle_10_1030.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/particle_restart_fixed/test.py b/tests/regression_tests/particle_restart_fixed/test.py index 428d9586d..463568ecc 100644 --- a/tests/regression_tests/particle_restart_fixed/test.py +++ b/tests/regression_tests/particle_restart_fixed/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import ParticleRestartTestHarness -def test_particle_restart_fixed(request): +def test_particle_restart_fixed(): harness = ParticleRestartTestHarness('particle_7_144.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/periodic/test.py b/tests/regression_tests/periodic/test.py index 188827a5d..d746ebdd5 100644 --- a/tests/regression_tests/periodic/test.py +++ b/tests/regression_tests/periodic/test.py @@ -51,7 +51,6 @@ class PeriodicTest(PyAPITestHarness): settings.export_to_xml() -def test_periodic(request): +def test_periodic(): harness = PeriodicTest('statepoint.4.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/plot/test.py b/tests/regression_tests/plot/test.py index 1e7773ea6..64f2d4d31 100644 --- a/tests/regression_tests/plot/test.py +++ b/tests/regression_tests/plot/test.py @@ -56,8 +56,7 @@ class PlotTestHarness(TestHarness): return outstr -def test_plot(request): +def test_plot(): harness = PlotTestHarness(('plot_1.ppm', 'plot_2.ppm', 'plot_3.ppm', 'plot_4.h5')) - harness.request = request harness.main() diff --git a/tests/regression_tests/ptables_off/test.py b/tests/regression_tests/ptables_off/test.py index 8d67cf400..cc316f8c3 100644 --- a/tests/regression_tests/ptables_off/test.py +++ b/tests/regression_tests/ptables_off/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_ptables_off(request): +def test_ptables_off(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/quadric_surfaces/test.py b/tests/regression_tests/quadric_surfaces/test.py index f919f1648..862ee9bf4 100755 --- a/tests/regression_tests/quadric_surfaces/test.py +++ b/tests/regression_tests/quadric_surfaces/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_quadric_surfaces(request): +def test_quadric_surfaces(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/reflective_plane/test.py b/tests/regression_tests/reflective_plane/test.py index ba693c61a..906174f33 100644 --- a/tests/regression_tests/reflective_plane/test.py +++ b/tests/regression_tests/reflective_plane/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_reflective_plane(request): +def test_reflective_plane(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/resonance_scattering/test.py b/tests/regression_tests/resonance_scattering/test.py index 98096a0ca..15e9d6894 100644 --- a/tests/regression_tests/resonance_scattering/test.py +++ b/tests/regression_tests/resonance_scattering/test.py @@ -42,7 +42,6 @@ class ResonanceScatteringTestHarness(PyAPITestHarness): settings.export_to_xml() -def test_resonance_scattering(request): +def test_resonance_scattering(): harness = ResonanceScatteringTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/rotation/test.py b/tests/regression_tests/rotation/test.py index b27e7faa4..31c3d8d65 100644 --- a/tests/regression_tests/rotation/test.py +++ b/tests/regression_tests/rotation/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_rotation(request): +def test_rotation(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/salphabeta/test.py b/tests/regression_tests/salphabeta/test.py index 62ccbb226..e69ada5ba 100644 --- a/tests/regression_tests/salphabeta/test.py +++ b/tests/regression_tests/salphabeta/test.py @@ -73,8 +73,7 @@ def make_model(): return model -def test_salphabeta(request, reset_ids): +def test_salphabeta(): model = make_model() harness = PyAPITestHarness('statepoint.5.h5', model) - harness.request = request harness.main() diff --git a/tests/regression_tests/score_current/test.py b/tests/regression_tests/score_current/test.py index 1acc7c604..21c5d0881 100644 --- a/tests/regression_tests/score_current/test.py +++ b/tests/regression_tests/score_current/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import HashedTestHarness -def test_score_current(request): +def test_score_current(): harness = HashedTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/seed/test.py b/tests/regression_tests/seed/test.py index 4c6bf95dc..8b2f031b3 100644 --- a/tests/regression_tests/seed/test.py +++ b/tests/regression_tests/seed/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_seed(request): +def test_seed(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/source/test.py b/tests/regression_tests/source/test.py index aa17143f0..00171a255 100644 --- a/tests/regression_tests/source/test.py +++ b/tests/regression_tests/source/test.py @@ -58,7 +58,6 @@ class SourceTestHarness(PyAPITestHarness): settings.export_to_xml() -def test_source(request): +def test_source(): harness = SourceTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/source_file/test.py b/tests/regression_tests/source_file/test.py index 1989de4c9..b4d9c4a31 100644 --- a/tests/regression_tests/source_file/test.py +++ b/tests/regression_tests/source_file/test.py @@ -96,7 +96,6 @@ class SourceFileTestHarness(TestHarness): fh.write(settings1) -def test_source_file(request): +def test_source_file(): harness = SourceFileTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/sourcepoint_batch/test.py b/tests/regression_tests/sourcepoint_batch/test.py index 96326d15b..3086b5610 100644 --- a/tests/regression_tests/sourcepoint_batch/test.py +++ b/tests/regression_tests/sourcepoint_batch/test.py @@ -26,7 +26,6 @@ class SourcepointTestHarness(TestHarness): return outstr -def test_sourcepoint_batch(request): +def test_sourcepoint_batch(): harness = SourcepointTestHarness('statepoint.08.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/sourcepoint_latest/test.py b/tests/regression_tests/sourcepoint_latest/test.py index 15082e6d8..d14b566ad 100644 --- a/tests/regression_tests/sourcepoint_latest/test.py +++ b/tests/regression_tests/sourcepoint_latest/test.py @@ -13,7 +13,6 @@ class SourcepointTestHarness(TestHarness): 'exist.' -def test_sourcepoint_latest(request): +def test_sourcepoint_latest(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/sourcepoint_restart/test.py b/tests/regression_tests/sourcepoint_restart/test.py index b0d2e9069..32f53ec23 100644 --- a/tests/regression_tests/sourcepoint_restart/test.py +++ b/tests/regression_tests/sourcepoint_restart/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_sourcepoint_restart(request): +def test_sourcepoint_restart(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/statepoint_batch/test.py b/tests/regression_tests/statepoint_batch/test.py index aeb91e2a6..6c5e4de31 100644 --- a/tests/regression_tests/statepoint_batch/test.py +++ b/tests/regression_tests/statepoint_batch/test.py @@ -13,7 +13,6 @@ class StatepointTestHarness(TestHarness): TestHarness._test_output_created(self) -def test_statepoint_batch(request): +def test_statepoint_batch(): harness = StatepointTestHarness() - harness.request = request harness.main() diff --git a/tests/regression_tests/statepoint_restart/test.py b/tests/regression_tests/statepoint_restart/test.py index 219a8a7ed..2ee2e7be7 100644 --- a/tests/regression_tests/statepoint_restart/test.py +++ b/tests/regression_tests/statepoint_restart/test.py @@ -54,8 +54,7 @@ class StatepointRestartTestHarness(TestHarness): openmc.run(openmc_exec=self._opts.exe, restart_file=statepoint) -def test_statepoint_restart(request): +def test_statepoint_restart(): harness = StatepointRestartTestHarness('statepoint.10.h5', 'statepoint.07.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/statepoint_sourcesep/test.py b/tests/regression_tests/statepoint_sourcesep/test.py index 44a45d406..3d63ca8ee 100644 --- a/tests/regression_tests/statepoint_sourcesep/test.py +++ b/tests/regression_tests/statepoint_sourcesep/test.py @@ -20,7 +20,6 @@ class SourcepointTestHarness(TestHarness): os.remove(f) -def test_statepoint_sourcesep(request): +def test_statepoint_sourcesep(): harness = SourcepointTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/surface_tally/test.py b/tests/regression_tests/surface_tally/test.py index 2d499cb29..6995b7780 100644 --- a/tests/regression_tests/surface_tally/test.py +++ b/tests/regression_tests/surface_tally/test.py @@ -174,7 +174,6 @@ class SurfaceTallyTestHarness(PyAPITestHarness): return outstr -def test_surface_tally(request): +def test_surface_tally(): harness = SurfaceTallyTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/survival_biasing/test.py b/tests/regression_tests/survival_biasing/test.py index 2dc7c86ab..39e6faed8 100644 --- a/tests/regression_tests/survival_biasing/test.py +++ b/tests/regression_tests/survival_biasing/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_survival_biasing(request): +def test_survival_biasing(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/tallies/test.py b/tests/regression_tests/tallies/test.py index 3f6f7de74..e3aebfd98 100644 --- a/tests/regression_tests/tallies/test.py +++ b/tests/regression_tests/tallies/test.py @@ -4,9 +4,8 @@ from openmc import Mesh, Tally, Tallies from tests.testing_harness import HashedPyAPITestHarness -def test_tallies(request, reset_ids): +def test_tallies(): harness = HashedPyAPITestHarness('statepoint.5.h5') - harness.request = request model = harness._model # Set settings explicitly diff --git a/tests/regression_tests/tally_aggregation/test.py b/tests/regression_tests/tally_aggregation/test.py index dfa026690..f72316073 100644 --- a/tests/regression_tests/tally_aggregation/test.py +++ b/tests/regression_tests/tally_aggregation/test.py @@ -61,7 +61,6 @@ class TallyAggregationTestHarness(PyAPITestHarness): return outstr -def test_tally_aggregation(request): +def test_tally_aggregation(): harness = TallyAggregationTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/tally_arithmetic/test.py b/tests/regression_tests/tally_arithmetic/test.py index c87d0149f..469ed00de 100644 --- a/tests/regression_tests/tally_arithmetic/test.py +++ b/tests/regression_tests/tally_arithmetic/test.py @@ -75,7 +75,6 @@ class TallyArithmeticTestHarness(PyAPITestHarness): return outstr -def test_tally_arithmetic(request): +def test_tally_arithmetic(): harness = TallyArithmeticTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/tally_assumesep/test.py b/tests/regression_tests/tally_assumesep/test.py index c4248c10a..64595ced2 100644 --- a/tests/regression_tests/tally_assumesep/test.py +++ b/tests/regression_tests/tally_assumesep/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_tally_assumesep(request): +def test_tally_assumesep(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/tally_nuclides/test.py b/tests/regression_tests/tally_nuclides/test.py index 7eec5ef3f..ee1f4b600 100644 --- a/tests/regression_tests/tally_nuclides/test.py +++ b/tests/regression_tests/tally_nuclides/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_tally_nuclides(request): +def test_tally_nuclides(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/tally_slice_merge/test.py b/tests/regression_tests/tally_slice_merge/test.py index 4ee99d34a..c198411f7 100644 --- a/tests/regression_tests/tally_slice_merge/test.py +++ b/tests/regression_tests/tally_slice_merge/test.py @@ -166,7 +166,6 @@ class TallySliceMergeTestHarness(PyAPITestHarness): return outstr -def test_tally_slice_merge(request): +def test_tally_slice_merge(): harness = TallySliceMergeTestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/trace/test.py b/tests/regression_tests/trace/test.py index c037bb23b..79dcaa106 100644 --- a/tests/regression_tests/trace/test.py +++ b/tests/regression_tests/trace/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_trace(request): +def test_trace(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/track_output/test.py b/tests/regression_tests/track_output/test.py index f3d665e11..22eac03bf 100644 --- a/tests/regression_tests/track_output/test.py +++ b/tests/regression_tests/track_output/test.py @@ -38,10 +38,9 @@ class TrackTestHarness(TestHarness): os.remove(f) -def test_track_output(request): +def test_track_output(): # If vtk python module is not available, we can't run track.py so skip this # test. vtk = pytest.importorskip('vtk') harness = TrackTestHarness('statepoint.2.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/translation/test.py b/tests/regression_tests/translation/test.py index 29ef1c011..876db736b 100644 --- a/tests/regression_tests/translation/test.py +++ b/tests/regression_tests/translation/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_translation(request): +def test_translation(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/trigger_batch_interval/test.py b/tests/regression_tests/trigger_batch_interval/test.py index 1ac01a08b..e16174502 100644 --- a/tests/regression_tests/trigger_batch_interval/test.py +++ b/tests/regression_tests/trigger_batch_interval/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_trigger_batch_interval(request): +def test_trigger_batch_interval(): harness = TestHarness('statepoint.15.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/trigger_no_batch_interval/test.py b/tests/regression_tests/trigger_no_batch_interval/test.py index d38304fee..4a40def5a 100644 --- a/tests/regression_tests/trigger_no_batch_interval/test.py +++ b/tests/regression_tests/trigger_no_batch_interval/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_trigger_no_batch_interval(request): +def test_trigger_no_batch_interval(): harness = TestHarness('statepoint.15.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/trigger_no_status/test.py b/tests/regression_tests/trigger_no_status/test.py index 98173f8dd..75ae9a8cc 100644 --- a/tests/regression_tests/trigger_no_status/test.py +++ b/tests/regression_tests/trigger_no_status/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_trigger_no_status(request): +def test_trigger_no_status(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/trigger_tallies/test.py b/tests/regression_tests/trigger_tallies/test.py index fde7298ff..f8493c0c6 100644 --- a/tests/regression_tests/trigger_tallies/test.py +++ b/tests/regression_tests/trigger_tallies/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_trigger_tallies(request): +def test_trigger_tallies(): harness = TestHarness('statepoint.15.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/triso/test.py b/tests/regression_tests/triso/test.py index 2da26d1d3..174bba94f 100644 --- a/tests/regression_tests/triso/test.py +++ b/tests/regression_tests/triso/test.py @@ -90,7 +90,6 @@ class TRISOTestHarness(PyAPITestHarness): mats.export_to_xml() -def test_triso(request): +def test_triso(): harness = TRISOTestHarness('statepoint.5.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/uniform_fs/test.py b/tests/regression_tests/uniform_fs/test.py index 553484e0a..64d997909 100644 --- a/tests/regression_tests/uniform_fs/test.py +++ b/tests/regression_tests/uniform_fs/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_uniform_fs(request): +def test_uniform_fs(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/universe/test.py b/tests/regression_tests/universe/test.py index 5da34d90c..d5ed9645b 100644 --- a/tests/regression_tests/universe/test.py +++ b/tests/regression_tests/universe/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_universe(request): +def test_universe(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/void/test.py b/tests/regression_tests/void/test.py index a08813c45..af16f2ac8 100644 --- a/tests/regression_tests/void/test.py +++ b/tests/regression_tests/void/test.py @@ -1,7 +1,6 @@ from tests.testing_harness import TestHarness -def test_void(request): +def test_void(): harness = TestHarness('statepoint.10.h5') - harness.request = request harness.main() diff --git a/tests/regression_tests/volume_calc/test.py b/tests/regression_tests/volume_calc/test.py index da3a66c36..a6b62b9be 100644 --- a/tests/regression_tests/volume_calc/test.py +++ b/tests/regression_tests/volume_calc/test.py @@ -73,7 +73,6 @@ class VolumeTest(PyAPITestHarness): def _test_output_created(self): pass -def test_volume_calc(request): +def test_volume_calc(): harness = VolumeTest('') - harness.request = request harness.main() diff --git a/tests/testing_harness.py b/tests/testing_harness.py index 9aee4f741..9901dcb71 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -32,14 +32,10 @@ class TestHarness(object): def main(self): """Accept commandline arguments and either run or update tests.""" (self._opts, self._args) = self.parser.parse_args() - try: - olddir = self.request.fspath.dirpath().chdir() - if self._opts.update: - self.update_results() - else: - self.execute_test() - finally: - olddir.chdir() + if self._opts.update: + self.update_results() + else: + self.execute_test() def execute_test(self): """Run OpenMC with the appropriate arguments and check the outputs.""" @@ -237,7 +233,6 @@ class PyAPITestHarness(TestHarness): super(PyAPITestHarness, self).__init__(statepoint_name) self.parser.add_option('-b', '--build-inputs', dest='build_only', action='store_true', default=False) - openmc.reset_auto_ids() if model is None: self._model = pwr_core() else: @@ -248,16 +243,12 @@ class PyAPITestHarness(TestHarness): def main(self): """Accept commandline arguments and either run or update tests.""" (self._opts, self._args) = self.parser.parse_args() - try: - olddir = self.request.fspath.dirpath().chdir() - if self._opts.build_only: - self._build_inputs() - elif self._opts.update: - self.update_results() - else: - self.execute_test() - finally: - olddir.chdir() + if self._opts.build_only: + self._build_inputs() + elif self._opts.update: + self.update_results() + else: + self.execute_test() def execute_test(self): """Build input XMLs, run OpenMC, and verify correct results.""" From 0d3845c32744b59dc1c99b3bb28111b6ad793a0f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 13:46:40 -0600 Subject: [PATCH 155/282] Fix command-line arguments for regression tests --- tests/conftest.py | 17 +++++++++ tests/regression_tests/__init__.py | 9 +++++ tests/regression_tests/mg_convert/test.py | 25 ++++++------- .../mgxs_library_ce_to_mg/test.py | 17 ++++----- tests/regression_tests/plot/test.py | 11 +++--- .../statepoint_restart/test.py | 9 ++--- tests/testing_harness.py | 35 +++++++------------ tests/unit_tests/test_capi.py | 1 + tools/ci/travis-script.sh | 14 ++++---- 9 files changed, 75 insertions(+), 63 deletions(-) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..422c036fb --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,17 @@ +from tests.regression_tests import config as regression_config + + +def pytest_addoption(parser): + parser.addoption('--exe') + parser.addoption('--mpi', action='store_true') + parser.addoption('--mpiexec') + parser.addoption('--mpi-np') + parser.addoption('--update', action='store_true') + parser.addoption('--build-inputs', action='store_true') + + +def pytest_configure(config): + opts = ['exe', 'mpi', 'mpiexec', 'mpi_np', 'update', 'build_inputs'] + for opt in opts: + if config.getoption(opt) is not None: + regression_config[opt] = config.getoption(opt) diff --git a/tests/regression_tests/__init__.py b/tests/regression_tests/__init__.py index e69de29bb..965a7f94d 100644 --- a/tests/regression_tests/__init__.py +++ b/tests/regression_tests/__init__.py @@ -0,0 +1,9 @@ +# Test configuration options for regression tests +config = { + 'exe': 'openmc', + 'mpi': False, + 'mpiexec': 'mpiexec', + 'mpi_np': '2', + 'update': False, + 'build_inputs': False +} diff --git a/tests/regression_tests/mg_convert/test.py b/tests/regression_tests/mg_convert/test.py index e6a8690c2..4bbbcbeb9 100755 --- a/tests/regression_tests/mg_convert/test.py +++ b/tests/regression_tests/mg_convert/test.py @@ -5,6 +5,7 @@ import numpy as np import openmc from tests.testing_harness import PyAPITestHarness +from tests.regression_tests import config # OpenMC simulation parameters batches = 10 @@ -133,24 +134,18 @@ class MGXSTestHarness(PyAPITestHarness): for case in cases: build_mgxs_library(case) - if self._opts.mpi_exec is not None: - mpi_args = [self._opts.mpi_exec, '-n', self._opts.mpi_np] - openmc.run(openmc_exec=self._opts.exe, mpi_args=mpi_args) + if config['mpi']: + mpi_args = [config['mpiexec'], '-n', config['mpi_np']] + openmc.run(openmc_exec=config['exe'], mpi_args=mpi_args) else: - openmc.run(openmc_exec=self._opts.exe) + openmc.run(openmc_exec=config['exe']) - sp = openmc.StatePoint('statepoint.' + str(batches) + '.h5') - - # Write out k-combined. - outstr += 'k-combined:\n' - form = '{0:12.6E} {1:12.6E}\n' - outstr += form.format(sp.k_combined[0], sp.k_combined[1]) - - # Enforce closing statepoint and summary files so HDF5 - # does not throw an error during the next OpenMC execution - sp._f.close() - sp._summary._f.close() + with openmc.StatePoint('statepoint.{}.h5'.format(batches)) as sp: + # Write out k-combined. + outstr += 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.k_combined[0], sp.k_combined[1]) return outstr diff --git a/tests/regression_tests/mgxs_library_ce_to_mg/test.py b/tests/regression_tests/mgxs_library_ce_to_mg/test.py index 9dc640acd..922b2c302 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg/test.py +++ b/tests/regression_tests/mgxs_library_ce_to_mg/test.py @@ -5,6 +5,7 @@ import openmc.mgxs from openmc.examples import pwr_pin_cell from tests.testing_harness import PyAPITestHarness +from tests.regression_tests import config class MGXSTestHarness(PyAPITestHarness): @@ -31,11 +32,11 @@ class MGXSTestHarness(PyAPITestHarness): def _run_openmc(self): # Initial run - if self._opts.mpi_exec is not None: - mpi_args = [self._opts.mpi_exec, '-n', self._opts.mpi_np] - openmc.run(openmc_exec=self._opts.exe, mpi_args=mpi_args) + if config['mpi']: + mpi_args = [config['mpiexec'], '-n', config['mpi_np']] + openmc.run(openmc_exec=config['exe'], mpi_args=mpi_args) else: - openmc.run(openmc_exec=self._opts.exe) + openmc.run(openmc_exec=config['exe']) # Build MG Inputs # Get data needed to execute Library calculations. @@ -63,11 +64,11 @@ class MGXSTestHarness(PyAPITestHarness): sp._summary._f.close() # Re-run MG mode. - if self._opts.mpi_exec is not None: - mpi_args = [self._opts.mpi_exec, '-n', self._opts.mpi_np] - openmc.run(openmc_exec=self._opts.exe, mpi_args=mpi_args) + if config['mpi']: + mpi_args = [config['mpiexec'], '-n', config['mpi_np']] + openmc.run(openmc_exec=config['exe'], mpi_args=mpi_args) else: - openmc.run(openmc_exec=self._opts.exe) + openmc.run(openmc_exec=config['exe']) def _cleanup(self): super(MGXSTestHarness, self)._cleanup() diff --git a/tests/regression_tests/plot/test.py b/tests/regression_tests/plot/test.py index 64f2d4d31..e7115c4dd 100644 --- a/tests/regression_tests/plot/test.py +++ b/tests/regression_tests/plot/test.py @@ -6,6 +6,7 @@ import h5py import openmc from tests.testing_harness import TestHarness +from tests.regression_tests import config class PlotTestHarness(TestHarness): @@ -15,20 +16,18 @@ class PlotTestHarness(TestHarness): self._plot_names = plot_names def _run_openmc(self): - openmc.plot_geometry(openmc_exec=self._opts.exe) + openmc.plot_geometry(openmc_exec=config['exe']) def _test_output_created(self): """Make sure *.ppm has been created.""" for fname in self._plot_names: - assert os.path.exists(os.path.join(os.getcwd(), fname)), \ - 'Plot output file does not exist.' + assert os.path.exists(fname), 'Plot output file does not exist.' def _cleanup(self): super(PlotTestHarness, self)._cleanup() for fname in self._plot_names: - path = os.path.join(os.getcwd(), fname) - if os.path.exists(path): - os.remove(path) + if os.path.exists(fname): + os.remove(fname) def _get_results(self): """Return a string hash of the plot files.""" diff --git a/tests/regression_tests/statepoint_restart/test.py b/tests/regression_tests/statepoint_restart/test.py index 2ee2e7be7..d36ff9395 100644 --- a/tests/regression_tests/statepoint_restart/test.py +++ b/tests/regression_tests/statepoint_restart/test.py @@ -4,6 +4,7 @@ import os import openmc from tests.testing_harness import TestHarness +from tests.regression_tests import config class StatepointRestartTestHarness(TestHarness): @@ -46,12 +47,12 @@ class StatepointRestartTestHarness(TestHarness): statepoint = statepoint[0] # Run OpenMC - if self._opts.mpi_exec is not None: - mpi_args = [self._opts.mpi_exec, '-n', self._opts.mpi_np] - openmc.run(restart_file=statepoint, openmc_exec=self._opts.exe, + if config['mpi']: + mpi_args = [config['mpiexec'], '-n', config['mpi_np']] + openmc.run(restart_file=statepoint, openmc_exec=config['exe'], mpi_args=mpi_args) else: - openmc.run(openmc_exec=self._opts.exe, restart_file=statepoint) + openmc.run(openmc_exec=config['exe'], restart_file=statepoint) def test_statepoint_restart(): diff --git a/tests/testing_harness.py b/tests/testing_harness.py index 9901dcb71..55a5660f1 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -10,29 +10,21 @@ import shutil import sys import numpy as np - import openmc from openmc.examples import pwr_core +from tests.regression_tests import config + class TestHarness(object): """General class for running OpenMC regression tests.""" def __init__(self, statepoint_name): self._sp_name = statepoint_name - self.parser = OptionParser() - self.parser.add_option('--exe', dest='exe', default='openmc') - self.parser.add_option('--mpi_exec', dest='mpi_exec', default=None) - self.parser.add_option('--mpi_np', dest='mpi_np', default='2') - self.parser.add_option('--update', dest='update', action='store_true', - default=False) - self._opts = None - self._args = None def main(self): """Accept commandline arguments and either run or update tests.""" - (self._opts, self._args) = self.parser.parse_args() - if self._opts.update: + if config['update']: self.update_results() else: self.execute_test() @@ -60,11 +52,11 @@ class TestHarness(object): self._cleanup() def _run_openmc(self): - if self._opts.mpi_exec is not None: - openmc.run(openmc_exec=self._opts.exe, - mpi_args=[self._opts.mpi_exec, '-n', self._opts.mpi_np]) + if config['mpi']: + mpi_args = [config['mpiexec'], '-n', config['mpi_np']] + openmc.run(openmc_exec=config['exe'], mpi_args=mpi_args) else: - openmc.run(openmc_exec=self._opts.exe) + openmc.run(openmc_exec=config['exe']) def _test_output_created(self): """Make sure statepoint.* and tallies.out have been created.""" @@ -179,9 +171,9 @@ class ParticleRestartTestHarness(TestHarness): def _run_openmc(self): # Set arguments - args = {'openmc_exec': self._opts.exe} - if self._opts.mpi_exec is not None: - args['mpi_args'] = [self._opts.mpi_exec, '-n', self._opts.mpi_np] + args = {'openmc_exec': config['exe']} + if config['mpi']: + args['mpi_args'] = [config['mpiexec'], '-n', config['mpi_np']] # Initial run openmc.run(**args) @@ -231,8 +223,6 @@ class ParticleRestartTestHarness(TestHarness): class PyAPITestHarness(TestHarness): def __init__(self, statepoint_name, model=None): super(PyAPITestHarness, self).__init__(statepoint_name) - self.parser.add_option('-b', '--build-inputs', dest='build_only', - action='store_true', default=False) if model is None: self._model = pwr_core() else: @@ -242,10 +232,9 @@ class PyAPITestHarness(TestHarness): def main(self): """Accept commandline arguments and either run or update tests.""" - (self._opts, self._args) = self.parser.parse_args() - if self._opts.build_only: + if config['build_inputs']: self._build_inputs() - elif self._opts.update: + elif config['update']: self.update_results() else: self.execute_test() diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index ba9ac5c9e..e1e87e27b 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -12,6 +12,7 @@ import openmc.capi @pytest.fixture(scope='module') def pincell_model(): """Set up a model to test with and delete files when done""" + openmc.reset_auto_ids() pincell = openmc.examples.pwr_pin_cell() # Add a tally diff --git a/tools/ci/travis-script.sh b/tools/ci/travis-script.sh index 2934cb59a..468cd0cc8 100755 --- a/tools/ci/travis-script.sh +++ b/tools/ci/travis-script.sh @@ -1,15 +1,15 @@ #!/bin/bash set -ex -# Run regression test suite -cd build -ctest - # Run source check -cd ../tests +cd tests if [[ $TRAVIS_PYTHON_VERSION == "3.4" && $OMP == 'n' && $MPI == 'n' ]]; then ./check_source.py fi -# Run unit tests -pytest --cov=../openmc -v unit_tests/ +# Run regression and unit tests +if [[ $MPI == 'y' ]]; then + pytest --cov=../openmc -v --mpi +else + pytest --cov=../openmc -v +fi From ea15a931042f231500cb2b853825f35d619f162c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 14:05:48 -0600 Subject: [PATCH 156/282] Remove use of ctest / update_results.py --- CMakeLists.txt | 49 ----------------------------- tests/convert_precision.py | 23 -------------- tests/update_results.py | 60 ------------------------------------ tests/valgrind.supp | 63 -------------------------------------- 4 files changed, 195 deletions(-) delete mode 100755 tests/convert_precision.py delete mode 100755 tests/update_results.py delete mode 100644 tests/valgrind.supp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d0fcb0ab..78db0ab45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -522,52 +522,3 @@ if(PYTHONINTERP_FOUND) WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})") endif() endif() - -#=============================================================================== -# Regression tests -#=============================================================================== - -# This allows for dashboard configuration -include(CTest) - -# Get a list of all the tests to run -file(GLOB_RECURSE TESTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/test.py) - -# Loop through all the tests -foreach(test ${TESTS}) - # Remove unit tests - if(test MATCHES ".*unit_tests.*") - continue() - endif() - - # Get test information - get_filename_component(TEST_NAME ${test} NAME) - get_filename_component(TEST_PATH ${test} PATH) - - if (DEFINED ENV{MEM_CHECK}) - # Generate input files if needed - if (NOT EXISTS "${TEST_PATH}/geometry.xml") - execute_process(COMMAND ${PYTHON_EXECUTABLE} ${TEST_NAME} --build-inputs - WORKING_DIRECTORY ${TEST_PATH}) - endif() - - # Add serial test - add_test(NAME ${TEST_PATH} - WORKING_DIRECTORY ${TEST_PATH} - COMMAND $) - else() - # Check serial/parallel - if (${MPI_ENABLED}) - # Preform a parallel test - add_test(NAME ${TEST_PATH} - WORKING_DIRECTORY ${TEST_PATH} - COMMAND ${PYTHON_EXECUTABLE} ${TEST_NAME} --exe $ - --mpi_exec ${MPI_DIR}/mpiexec) - else() - # Perform a serial test - add_test(NAME ${TEST_PATH} - WORKING_DIRECTORY ${TEST_PATH} - COMMAND ${PYTHON_EXECUTABLE} ${TEST_NAME} --exe $) - endif() - endif() -endforeach(test) diff --git a/tests/convert_precision.py b/tests/convert_precision.py deleted file mode 100755 index 625fe49ca..000000000 --- a/tests/convert_precision.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python - -import os -import glob - -dirs = glob.glob('test_*') - -for adir in dirs: - - os.chdir(adir) - - files = glob.glob('results.py') - - if len(files) > 0: - - files = files[0] - with open(files, 'r') as fh: - intxt = fh.read() - intxt = intxt.replace('14.8E', '12.6E') - with open(files, 'w') as fh: - fh.write(intxt) - - os.chdir('..') diff --git a/tests/update_results.py b/tests/update_results.py deleted file mode 100755 index 565600333..000000000 --- a/tests/update_results.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function - -import os -import re -from subprocess import Popen, call, STDOUT, PIPE -from glob import glob -from optparse import OptionParser - -parser = OptionParser() -parser.add_option('-R', '--tests-regex', dest='regex_tests', - help="Run tests matching regular expression. \ - Test names are the directories present in tests folder.\ - This uses standard regex syntax to select tests.") -(opts, args) = parser.parse_args() -cwd = os.getcwd() - -# Terminal color configurations -OKGREEN = '\033[92m' -FAIL = '\033[91m' -ENDC = '\033[0m' -BOLD = '\033[1m' - -# Get a list of all test folders -folders = glob('test_*') - -# Check to see if a subset of tests is specified on command line -if opts.regex_tests is not None: - folders = [item for item in folders if re.search(opts.regex_tests, item)] - -# Loop around directories -for adir in sorted(folders): - - # Go into that directory - os.chdir(adir) - pwd = os.path.abspath(os.path.dirname('settings.xml')) - os.putenv('PWD', pwd) - - # Print status to screen - print(adir, end="") - sz = len(adir) - for i in range(35 - sz): - print('.', end="") - - # Find the test executable - test_exec = glob('test_*.py') - assert len(test_exec) == 1, 'There must be only one test executable per ' \ - 'test directory' - - # Update the test results - proc = Popen(['python', test_exec[0], '--update']) - returncode = proc.wait() - if returncode == 0: - print(BOLD + OKGREEN + "[OK]" + ENDC) - else: - print(BOLD + FAIL + "[FAILED]" + ENDC) - - # Go back a directory - os.chdir('..') diff --git a/tests/valgrind.supp b/tests/valgrind.supp deleted file mode 100644 index ad302c539..000000000 --- a/tests/valgrind.supp +++ /dev/null @@ -1,63 +0,0 @@ -{ - Create HDF5 statepoint file - Memcheck:Addr4 - fun:H5_build_extpath - fun:H5F_open - fun:H5Fcreate - fun:h5fcreate_c_ - fun:__h5f_MOD_h5fcreate_f - fun:__hdf5_interface_MOD_hdf5_file_create - fun:__output_interface_MOD_file_create - fun:__state_point_MOD_write_state_point - fun:__eigenvalue_MOD_finalize_batch - fun:__eigenvalue_MOD_run_eigenvalue - fun:MAIN__ - fun:main -} -{ - Open HDF5 statepoint file - Memcheck:Addr4 - fun:H5_build_extpath - fun:H5F_open - fun:H5Fopen - fun:h5fopen_c_ - fun:__h5f_MOD_h5fopen_f - fun:__hdf5_interface_MOD_hdf5_file_open - fun:__output_interface_MOD_file_open - fun:__state_point_MOD_write_source_point - fun:__eigenvalue_MOD_finalize_batch - fun:__eigenvalue_MOD_run_eigenvalue - fun:MAIN__ - fun:main -} -{ - Create HDF5 summary file - Memcheck:Addr4 - fun:H5_build_extpath - fun:H5F_open - fun:H5Fcreate - fun:h5fcreate_c_ - fun:__h5f_MOD_h5fcreate_f - fun:__hdf5_interface_MOD_hdf5_file_create - fun:__output_interface_MOD_file_create - fun:__hdf5_summary_MOD_hdf5_write_summary - fun:__initialize_MOD_initialize_run - fun:MAIN__ - fun:main -} -{ - Create HDF5 track file - Memcheck:Addr4 - fun:H5_build_extpath - fun:H5F_open - fun:H5Fcreate - fun:h5fcreate_c_ - fun:__h5f_MOD_h5fcreate_f - fun:__hdf5_interface_MOD_hdf5_file_create - fun:__output_interface_MOD_file_create - fun:__track_output_MOD_finalize_particle_track - fun:__tracking_MOD_transport - fun:__eigenvalue_MOD_run_eigenvalue - fun:MAIN__ - fun:main -} From dd2415d35065019d3b1be232dd2babe0b1eb44ce Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 15:26:29 -0600 Subject: [PATCH 157/282] Make sure non-PHDF5 builds turn off prefer_parallel --- tools/ci/travis-install.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/ci/travis-install.py b/tools/ci/travis-install.py index 0e4dbdc01..8fda09b09 100644 --- a/tools/ci/travis-install.py +++ b/tools/ci/travis-install.py @@ -42,13 +42,16 @@ def install(omp=False, mpi=False, phdf5=False): if not mpi: raise ValueError('Parallel HDF5 must be used in ' 'conjunction with MPI.') - cmake_cmd.append('-DHDF5_PREFER_PARALLEL=on') + cmake_cmd.append('-DHDF5_PREFER_PARALLEL=ON') + else: + cmake_cmd.append('-DHDF5_PREFER_PARALLEL=OFF') # Build and install cmake_cmd.append('..') + print(' '.join(cmake_cmd)) subprocess.call(cmake_cmd) subprocess.call(['make', '-j']) - subprocess.call(['make', 'install']) + subprocess.call(['sudo', 'make', 'install']) def main(): From fe353aff401e02a4c3880c49476271ab9cd7150d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 15:45:03 -0600 Subject: [PATCH 158/282] Update documentation relating to test suite --- docs/source/devguide/index.rst | 3 +- docs/source/devguide/tests.rst | 73 ++++++++++++++++ docs/source/devguide/workflow.rst | 133 ----------------------------- docs/source/usersguide/install.rst | 2 +- docs/source/usersguide/scripts.rst | 4 +- tests/readme.rst | 59 +------------ 6 files changed, 79 insertions(+), 195 deletions(-) create mode 100644 docs/source/devguide/tests.rst diff --git a/docs/source/devguide/index.rst b/docs/source/devguide/index.rst index 1a8252383..e40e7f635 100644 --- a/docs/source/devguide/index.rst +++ b/docs/source/devguide/index.rst @@ -10,9 +10,10 @@ as debugging. .. toctree:: :numbered: - :maxdepth: 3 + :maxdepth: 2 styleguide workflow + tests user-input docbuild diff --git a/docs/source/devguide/tests.rst b/docs/source/devguide/tests.rst new file mode 100644 index 000000000..749737b5c --- /dev/null +++ b/docs/source/devguide/tests.rst @@ -0,0 +1,73 @@ +.. _devguide_tests: + +========== +Test Suite +========== + +Running Tests +------------- + +The OpenMC test suite consists of two parts, a regression test suite and a unit +test suite. The regression test suite is based on regression or integrated +testing where different types of input files are configured and the full OpenMC +code is executed. Results from simulations are compared with expected +results. The unit tests are primarily intended to test individual +functions/classes in the OpenMC Python API. + +The test suite relies on the third-party `pytest `_ +package. To run either or both the regression and unit test suites, it is +assumed that you have OpenMC fully installed, i.e., the :ref:`scripts_openmc` +executable is available on your :envvar:`PATH` and the :mod:`openmc` Python +module is importable. In development where it would be onerous to continually +install OpenMC every time a small change is made, it is recommended to install +OpenMC in development/editable mode. With setuptools, this is accomplished by +running:: + + python setup.py develop + +or using pip (recommended):: + + pip install -e .[test] + +It is also assumed that you have cross section data available that is pointed to +by the :envvar:`OPENMC_CROSS_SECTIONS` and :envvar:`OPENMC_MULTIPOLE_LIBRARY` +environment variables. Furthermore, to run unit tests for the :mod:`openmc.data` +module, it is necessary to have ENDF/B-VII.1 data available and pointed to by +the :envvar:`OPENMC_ENDF_DATA` environment variable. All data sources can be +obtained using the ``tools/ci/travis-before-script.sh`` script. + +To execute the test suite, go to the ``tests/`` directory and run:: + + pytest + +If you want to collect information about source line coverage in the Python API, +you must have the `pytest-cov `_ plugin +installed and run:: + + pytest --cov=../openmc --cov-report=html + +Adding Tests to the Regression Suite +------------------------------------ + +To add a new test to the regression test suite, create a sub-directory in the +``tests/regression_tests/`` directory. To configure a test you need to add the +following files to your new test directory: + + * OpenMC input XML files, if they are not generated through the Python API + * **test.py** - Python test driver script; please refer to other tests to + see how to construct. Any output files that are generated during testing + must be removed at the end of this script. + * **inputs_true.dat** - ASCII file that contains Python API-generated XML + files concatenated together. When the test is run, inputs that are + generated are compared to this file. + * **results_true.dat** - ASCII file that contains the expected results from + the test. The file *results_test.dat* is compared to this file during the + execution of the python test driver script. When the above files have been + created, generate a *results_test.dat* file and copy it to this name and + commit. It should be noted that this file should be generated with basic + compiler options during openmc configuration and build (e.g., no MPI, no + debug/optimization). + +In addition to this description, please see the various types of tests that are +already included in the test suite to see how to create them. If all is +implemented correctly, the new test will automatically be discovered by pytest. diff --git a/docs/source/devguide/workflow.rst b/docs/source/devguide/workflow.rst index 942cee55b..fd1ae0455 100644 --- a/docs/source/devguide/workflow.rst +++ b/docs/source/devguide/workflow.rst @@ -89,139 +89,6 @@ features and bug fixes. The general steps for contributing are as follows: 6. After the pull request has been thoroughly vetted, it is merged back into the *develop* branch of mit-crpg/openmc. -.. _test suite: - -OpenMC Test Suite ------------------ - -The purpose of this test suite is to ensure that OpenMC compiles using various -combinations of compiler flags and options, and that all user input options can -be used successfully without breaking the code. The test suite is comprised of -regression tests where different types of input files are configured and the -full OpenMC code is executed. Results from simulations are compared with -expected results. The test suite is comprised of many build configurations -(e.g. debug, mpi, hdf5) and the actual tests which reside in sub-directories -in the tests directory. We recommend to developers to test their branches -before submitting a formal pull request using gfortran and Intel compilers -if available. - -The test suite is designed to integrate with cmake using ctest_. It is -configured to run with cross sections from NNDC_ augmented with 0 K elastic -scattering data for select nuclides as well as multipole data. To download the -proper data, run the following commands: - -.. code-block:: sh - - wget -O nndc_hdf5.tar.xz $(cat /.travis.yml | grep anl.box | awk '{print $2}') - tar xJvf nndc_hdf5.tar.xz - export OPENMC_CROSS_SECTIONS=$(pwd)/nndc_hdf5/cross_sections.xml - - git clone --branch=master git://github.com/smharper/windowed_multipole_library.git wmp_lib - tar xzvf wmp_lib/multipole_lib.tar.gz - export OPENMC_MULTIPOLE_LIBRARY=$(pwd)/multipole_lib - -The test suite can be run on an already existing build using: - -.. code-block:: sh - - cd build - make test - -or - -.. code-block:: sh - - cd build - ctest - -There are numerous ctest_ command line options that can be set to have -more control over which tests are executed. - -Before running the test suite python script, the following environmental -variables should be set if the default paths are incorrect: - - * **FC** - The command for a Fortran compiler (e.g. gfotran, ifort). - - * Default - *gfortran* - - * **CC** - The command for a C compiler (e.g. gcc, icc). - - * Default - *gcc* - - * **CXX** - The command for a C++ compiler (e.g. g++, icpc). - - * Default - *g++* - - * **MPI_DIR** - The path to the MPI directory. - - * Default - */opt/mpich/3.2-gnu* - - * **HDF5_DIR** - The path to the HDF5 directory. - - * Default - */opt/hdf5/1.8.16-gnu* - - * **PHDF5_DIR** - The path to the parallel HDF5 directory. - - * Default - */opt/phdf5/1.8.16-gnu* - -To run the full test suite, the following command can be executed in the -tests directory: - -.. code-block:: sh - - python run_tests.py - -A subset of build configurations and/or tests can be run. To see how to use -the script run: - -.. code-block:: sh - - python run_tests.py --help - -As an example, say we want to run all tests with debug flags only on tests -that have cone and plot in their name. Also, we would like to run this on -4 processors. We can run: - -.. code-block:: sh - - python run_tests.py -j 4 -C debug -R "cone|plot" - -Note that standard regular expression syntax is used for selecting build -configurations and tests. To print out a list of build configurations, we -can run: - -.. code-block:: sh - - python run_tests.py -p - -Adding tests to test suite -++++++++++++++++++++++++++ - -To add a new test to the test suite, create a sub-directory in the tests -directory that conforms to the regular expression *test_*. To configure -a test you need to add the following files to your new test directory, -*test_name* for example: - - * OpenMC input XML files - * **test_name.py** - Python test driver script, please refer to other - tests to see how to construct. Any output files that are generated - during testing must be removed at the end of this script. - * **inputs_true.dat** - ASCII file that contains Python API-generated XML - files concatenated together. When the test is run, inputs that are - generated are compared to this file. - * **results_true.dat** - ASCII file that contains the expected results - from the test. The file *results_test.dat* is compared to this file - during the execution of the python test driver script. When the - above files have been created, generate a *results_test.dat* file and - copy it to this name and commit. It should be noted that this file - should be generated with basic compiler options during openmc - configuration and build (e.g., no MPI/HDF5, no debug/optimization). - -In addition to this description, please see the various types of tests that -are already included in the test suite to see how to create them. If all is -implemented correctly, the new test directory will automatically be added -to the CTest framework. - Private Development ------------------- diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index b59572923..6bb685f48 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -364,7 +364,7 @@ Testing Build To run the test suite, you will first need to download a pre-generated cross section library along with windowed multipole data. Please refer to our -:ref:`test suite` documentation for further details. +:ref:`devguide_tests` documentation for further details. -------------------- Python Prerequisites diff --git a/docs/source/usersguide/scripts.rst b/docs/source/usersguide/scripts.rst index 90ca6886d..2f199fc25 100644 --- a/docs/source/usersguide/scripts.rst +++ b/docs/source/usersguide/scripts.rst @@ -168,8 +168,8 @@ ENDF/B-VII.1. It has the following optional arguments: This script downloads `ENDF/B-VII.1 ACE data `_ from NNDC and converts it to -an HDF5 library for use with OpenMC. This data is used for OpenMC's regression -test suite. This script has the following optional arguments: +an HDF5 library for use with OpenMC. This script has the following optional +arguments: -b, --batch Suppress standard in diff --git a/tests/readme.rst b/tests/readme.rst index f2e2bb1c1..5e4b0227b 100644 --- a/tests/readme.rst +++ b/tests/readme.rst @@ -1,58 +1 @@ -================= -OpenMC Test Suite -================= - -The purpose of this test suite is to ensure that OpenMC compiles using various -combinations of compiler flags and options and that all user input options can -be used successfully without breaking the code. The test suite is based on -regression or integrated testing where different types of input files are -configured and the full OpenMC code is executed. Results from simulations -are compared with expected results. The test suite is comprised of many -build configurations (e.g. debug, mpi, hdf5) and the actual tests which -reside in sub-directories in the tests directory. - -The test suite is designed to integrate with cmake using ctest_. To run the -full test suite run: - -.. code-block:: sh - - python run_tests.py - -The test suite is configured to run with cross sections from NNDC_. To -download these cross sections please do the following: - -.. code-block:: sh - - cd ../data - python get_nndc.py - export CROSS_SECTIONS=/nndc/cross_sections.xml - -The environmental variable **CROSS_SECTIONS** can be used to quickly switch -between the cross sections set for the test suite and cross section set for -your simulations. - -A subset of build configurations and/or tests can be run. To see how to use -the script run: - -.. code-block:: sh - - python run_tests.py --help - -As an example, say we want to run all tests with debug flags only on tests -that have cone and plot in their name. Also, we would like to run this on -4 processors. We can run: - -.. code-block:: sh - - python run_tests.py -j 4 -C debug -R "cone|plot" - -Note that standard regular expression syntax is used for selecting build -configurations and tests. To print out a list of build configurations, we -can run: - -.. code-block:: sh - - python run_tests.py -p - -.. _ctest: http://www.cmake.org/cmake/help/v2.8.12/ctest.html -.. _NNDC: http://http://www.nndc.bnl.gov/endf/b7.1/acefiles.html +See docs/source/devguide/tests.rst for information on the OpenMC test suite. From 60909945459dfc76faace3306d767dd9be56b857 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 15:49:30 -0600 Subject: [PATCH 159/282] Make sure IPython<6 is used for Python 2.7 --- tools/ci/travis-install.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index 6051f03aa..3ee54af76 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -10,6 +10,11 @@ pip install numpy cython # pytest installed by default -- make sure we get latest pip install --upgrade pytest +# IPython stopped supporting Python 2.7 with version 6 +if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then + pip install "ipython<6" +fi + # Pandas stopped supporting Python 3.4 with version 0.21 if [[ "$TRAVIS_PYTHON_VERSION" == "3.4" ]]; then pip install pandas==0.20.3 From 18695c2878a0750814af0e4a7d9ace0da9bdebd8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 16:30:57 -0600 Subject: [PATCH 160/282] Don't install Python package from CMakeLists.txt --- CMakeLists.txt | 16 ---------------- tools/ci/travis-install.py | 6 +++--- tools/ci/travis-install.sh | 6 +++--- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 78db0ab45..7ab012ef6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -506,19 +506,3 @@ install(TARGETS ${program} libopenmc 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) - -find_package(PythonInterp) -if(PYTHONINTERP_FOUND) - if(debian) - install(CODE "execute_process( - COMMAND ${PYTHON_EXECUTABLE} setup.py install - --root=debian/openmc --install-layout=deb - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})") - else() - install(CODE "set(ENV{PYTHONPATH} \"${CMAKE_INSTALL_PREFIX}/lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages\")") - install(CODE "execute_process( - COMMAND ${PYTHON_EXECUTABLE} setup.py install - --prefix=${CMAKE_INSTALL_PREFIX} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})") - endif() -endif() diff --git a/tools/ci/travis-install.py b/tools/ci/travis-install.py index 8fda09b09..bd7f886db 100644 --- a/tools/ci/travis-install.py +++ b/tools/ci/travis-install.py @@ -49,9 +49,9 @@ def install(omp=False, mpi=False, phdf5=False): # Build and install cmake_cmd.append('..') print(' '.join(cmake_cmd)) - subprocess.call(cmake_cmd) - subprocess.call(['make', '-j']) - subprocess.call(['sudo', 'make', 'install']) + subprocess.check_call(cmake_cmd) + subprocess.check_call(['make', '-j']) + subprocess.check_call(['sudo', 'make', 'install']) def main(): diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index 3ee54af76..3723315a4 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -20,8 +20,8 @@ if [[ "$TRAVIS_PYTHON_VERSION" == "3.4" ]]; then pip install pandas==0.20.3 fi -# Build and install +# Build and install OpenMC executable python tools/ci/travis-install.py -# Install OpenMC in editable mode -pip install -e .[test] +# Install Python API +pip install .[test] From d75b715026ef881d5d8b62dc5f5abb41a8d10694 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 17:41:10 -0600 Subject: [PATCH 161/282] Add source tests and fix one failing test --- tests/unit_tests/test_source.py | 30 ++++++++++++++++++++++++++++++ tests/unit_tests/test_stats.py | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/test_source.py diff --git a/tests/unit_tests/test_source.py b/tests/unit_tests/test_source.py new file mode 100644 index 000000000..3c963d052 --- /dev/null +++ b/tests/unit_tests/test_source.py @@ -0,0 +1,30 @@ +import openmc +import openmc.stats + + +def test_source(): + space = openmc.stats.Point() + energy = openmc.stats.Discrete([1.0e6], [1.0]) + angle = openmc.stats.Isotropic() + + src = openmc.Source(space=space, angle=angle, energy=energy) + assert src.space == space + assert src.angle == angle + assert src.energy == energy + assert src.strength == 1.0 + + elem = src.to_xml_element() + assert 'strength' in elem.attrib + assert elem.find('space') is not None + assert elem.find('angle') is not None + assert elem.find('energy') is not None + + +def test_source_file(): + filename = 'source.h5' + src = openmc.Source(filename=filename) + assert src.file == filename + + elem = src.to_xml_element() + assert 'strength' in elem.attrib + assert 'file' in elem.attrib diff --git a/tests/unit_tests/test_stats.py b/tests/unit_tests/test_stats.py index 3a91ecd8f..388408bb2 100644 --- a/tests/unit_tests/test_stats.py +++ b/tests/unit_tests/test_stats.py @@ -23,7 +23,7 @@ def test_discrete(): def test_uniform(): - a, b = 10, 20 + a, b = 10.0, 20.0 d = openmc.stats.Uniform(a, b) assert d.a == a assert d.b == b From 54cd34f33e7b9c3e1876ab06e4011482439c4313 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 18:02:03 -0600 Subject: [PATCH 162/282] Install Python package in editable mode once again --- tools/ci/travis-install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index 3723315a4..cef34a2e0 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -23,5 +23,5 @@ fi # Build and install OpenMC executable python tools/ci/travis-install.py -# Install Python API -pip install .[test] +# Install Python API in editable mode +pip install -e .[test] From 4ad4feecc688f311d1e4ae3555dff3f48c011690 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 22:31:44 -0600 Subject: [PATCH 163/282] Update minimum CMake version to 3.0 --- CMakeLists.txt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ab012ef6..fc8b25295 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) project(openmc Fortran C CXX) # Setup output directories @@ -21,11 +21,6 @@ if (${UNIX}) add_definitions(-DUNIX) endif() -# Set MACOSX_RPATH -if(POLICY CMP0042) - cmake_policy(SET CMP0042 NEW) -endif() - #=============================================================================== # Command line options #=============================================================================== From f6fc24c808215694b7880a1c4f69c6e59861b5ca Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 29 Jan 2018 23:23:48 -0600 Subject: [PATCH 164/282] Add unit tests for plots --- tests/unit_tests/conftest.py | 10 ++++ tests/unit_tests/test_material.py | 9 ---- tests/unit_tests/test_plots.py | 90 +++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 tests/unit_tests/conftest.py create mode 100644 tests/unit_tests/test_plots.py diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py new file mode 100644 index 000000000..aad0ac058 --- /dev/null +++ b/tests/unit_tests/conftest.py @@ -0,0 +1,10 @@ +import pytest + + +@pytest.fixture +def run_in_tmpdir(tmpdir): + orig = tmpdir.chdir() + try: + yield + finally: + orig.chdir() diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 84627ba8c..b96cbfb4c 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -40,15 +40,6 @@ def sphere_model(): return model -@pytest.fixture -def run_in_tmpdir(tmpdir): - orig = tmpdir.chdir() - try: - yield - finally: - orig.chdir() - - def test_attributes(uo2): assert uo2.name == 'UO2' assert uo2.id == 100 diff --git a/tests/unit_tests/test_plots.py b/tests/unit_tests/test_plots.py new file mode 100644 index 000000000..d48b609c1 --- /dev/null +++ b/tests/unit_tests/test_plots.py @@ -0,0 +1,90 @@ +import openmc +import openmc.examples +import pytest + + +@pytest.fixture(scope='module') +def myplot(): + plot = openmc.Plot(name='myplot') + plot.width = (100., 100.) + plot.origin = (2., 3., -10.) + plot.pixels = (500, 500) + plot.filename = 'myplot' + plot.type = 'slice' + plot.basis = 'yz' + plot.background = (0, 0, 0) + plot.background = 'black' + + plot.color_by = 'material' + m1, m2 = openmc.Material(), openmc.Material() + plot.colors = {m1: (0, 255, 0), m2: (0, 0, 255)} + plot.colors = {m1: 'green', m2: 'blue'} + + plot.mask_components = [openmc.Material()] + plot.mask_background = (255, 255, 255) + plot.mask_background = 'white' + + plot.level = 1 + plot.meshlines = { + 'type': 'tally', + 'id': 1, + 'linewidth': 2, + 'color': (40, 30, 20) + } + return plot + + +def test_attributes(myplot): + assert myplot.name == 'myplot' + + +def test_repr(myplot): + r = repr(myplot) + assert isinstance(r, str) + + +def test_from_geometry(): + width = 25. + s = openmc.Sphere(R=width/2, boundary_type='vacuum') + c = openmc.Cell(region=-s) + univ = openmc.Universe(cells=[c]) + geom = openmc.Geometry(univ) + + for basis in ('xy', 'yz', 'xz'): + plot = openmc.Plot.from_geometry(geom, basis) + assert plot.origin == pytest.approx((0., 0., 0.)) + assert plot.width == pytest.approx((width, width)) + + +def test_highlight_domains(): + plot = openmc.Plot() + plot.color_by = 'material' + plots = openmc.Plots([plot]) + + model = openmc.examples.pwr_pin_cell() + mats = {m for m in model.materials if 'UO2' in m.name} + plots.highlight_domains(model.geometry, mats) + + +def test_to_xml_element(myplot): + elem = myplot.to_xml_element() + assert 'id' in elem.attrib + assert 'color_by' in elem.attrib + assert 'type' in elem.attrib + assert elem.find('origin') is not None + assert elem.find('width') is not None + assert elem.find('pixels') is not None + assert elem.find('background').text == '0 0 0' + + +def test_plots(run_in_tmpdir): + p1 = openmc.Plot(name='plot1') + p2 = openmc.Plot(name='plot2') + plots = openmc.Plots([p1, p2]) + assert len(plots) == 2 + + p3 = openmc.Plot(name='plot3') + plots.append(p3) + assert len(plots) == 3 + + plots.export_to_xml() From ebd4f72b4075f17ed7f33f2b18e4f6a8318346df Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 31 Jan 2018 13:13:15 -0600 Subject: [PATCH 165/282] Update installation instructions for Python API --- docs/source/devguide/workflow.rst | 22 ++++++++ docs/source/quickinstall.rst | 24 +++++---- docs/source/usersguide/install.rst | 85 ++++++++++++++++++++++-------- 3 files changed, 101 insertions(+), 30 deletions(-) diff --git a/docs/source/devguide/workflow.rst b/docs/source/devguide/workflow.rst index fd1ae0455..9b27fd655 100644 --- a/docs/source/devguide/workflow.rst +++ b/docs/source/devguide/workflow.rst @@ -103,6 +103,27 @@ changes you've made in your private repository back to mit-crpg/openmc repository, simply follow the steps above with an extra step of pulling a branch from your private repository into a public fork. +.. _devguide_editable: + +Working in "Development" Mode +----------------------------- + +If you are making changes to the Python API during development, it is highly +suggested to install the Python API in development/editable mode using +pip_. From the root directory of the OpenMC repository, run: + +.. code-block:: sh + + pip install -e .[test] + +This installs the OpenMC Python package in `"editable" mode +`_ so +that 1) it can be imported from a Python interpreter and 2) any changes made are +immediately reflected in the installed version (that is, you don't need to keep +reinstalling it). While the same effect can be achieved using the +:envvar:`PYTHONPATH` environment variable, this is generally discouraged as it +can interfere with virtual environments. + .. _git: http://git-scm.com/ .. _GitHub: https://github.com/ .. _git flow: http://nvie.com/git-model @@ -114,3 +135,4 @@ from your private repository into a public fork. .. _Bitbucket: https://bitbucket.org .. _ctest: http://www.cmake.org/cmake/help/v2.8.12/ctest.html .. _NNDC: http://www.nndc.bnl.gov/endf/b7.1/acefiles.html +.. _pip: https://pip.pypa.io/en/stable/ diff --git a/docs/source/quickinstall.rst b/docs/source/quickinstall.rst index 97980037c..7176b67be 100644 --- a/docs/source/quickinstall.rst +++ b/docs/source/quickinstall.rst @@ -57,7 +57,6 @@ are no longer supported. .. _Personal Package Archive: https://launchpad.net/~paulromano/+archive/staging .. _APT package manager: https://help.ubuntu.com/community/AptGet/Howto -.. _HDF5: http://www.hdfgroup.org/HDF5/ --------------------------------------- Installing from Source on Ubuntu 15.04+ @@ -83,9 +82,12 @@ building and installing OpenMC from source. Installing from Source on Linux or Mac OS X ------------------------------------------- -All OpenMC source code is hosted on GitHub_. If you have git_, the gfortran_ -compiler, CMake_, and HDF5_ installed, you can download and install OpenMC be -entering the following commands in a terminal: +All OpenMC source code is hosted on `GitHub +`_. If you have `git +`_, the `gcc `_ compiler suite, +`CMake `_, and `HDF5 `_ +installed, you can download and install OpenMC be entering the following +commands in a terminal: .. code-block:: sh @@ -104,11 +106,15 @@ should specify an installation directory where you have write access, e.g. cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local .. +The :mod:`openmc` Python package must be installed separately. The easiest way +to install it is using `pip `_, which is +included by default in Python 2.7 and Python 3.4+. From the root directory of +the OpenMC distribution/repository, run: + +.. code-block:: sh + + pip install . + If you want to build a parallel version of OpenMC (using OpenMP or MPI), directions can be found in the :ref:`detailed installation instructions `. - -.. _GitHub: https://github.com/mit-crpg/openmc -.. _git: http://git-scm.com -.. _gfortran: http://gcc.gnu.org/wiki/GFortran -.. _CMake: http://www.cmake.org diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 6bb685f48..48188928c 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -6,17 +6,18 @@ Installation and Configuration .. currentmodule:: openmc +.. _install_conda: + ---------------------------------------- Installing on Linux/Mac with conda-forge ---------------------------------------- -`Conda `_ is an open source package management -system and environment management system for installing multiple versions of -software packages and their dependencies and switching easily between -them. `conda-forge `_ is a community-led conda -channel of installable packages. For instructions on installing conda, please -consult their `documentation -`_. +Conda_ is an open source package management system and environment management +system for installing multiple versions of software packages and their +dependencies and switching easily between them. `conda-forge +`_ is a community-led conda channel of +installable packages. For instructions on installing conda, please consult their +`documentation `_. Once you have `conda` installed on your system, add the `conda-forge` channel to your configuration with: @@ -38,6 +39,8 @@ It is possible to list all of the versions of OpenMC available on your platform conda search openmc --channel conda-forge +.. _install_ppa: + ----------------------------- Installing on Ubuntu with PPA ----------------------------- @@ -68,9 +71,11 @@ are no longer supported. .. _Personal Package Archive: https://launchpad.net/~paulromano/+archive/staging .. _APT package manager: https://help.ubuntu.com/community/AptGet/Howto --------------------- -Building from Source --------------------- +.. _install_source: + +---------------------- +Installing from Source +---------------------- .. _prerequisites: @@ -191,8 +196,8 @@ switch to the source of the latest stable release, run the following commands:: git checkout master .. _GitHub: https://github.com/mit-crpg/openmc -.. _git: http://git-scm.com -.. _ssh: http://en.wikipedia.org/wiki/Secure_Shell +.. _git: https://git-scm.com +.. _ssh: https://en.wikipedia.org/wiki/Secure_Shell .. _usersguide_build: @@ -366,16 +371,52 @@ To run the test suite, you will first need to download a pre-generated cross section library along with windowed multipole data. Please refer to our :ref:`devguide_tests` documentation for further details. --------------------- -Python Prerequisites --------------------- +--------------------- +Installing Python API +--------------------- -OpenMC's :ref:`Python API ` works with either Python 2.7 or Python -3.2+. 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. +If you installed OpenMC using :ref:`Conda ` or :ref:`PPA +`, no further steps are necessary in order to use OpenMC's +:ref:`Python API `. However, if you are :ref:`installing from source +`, the Python API is not installed by default when ``make +install`` is run because in many situations it doesn't make sense to install a +Python package in the same location as the ``openmc`` executable (for example, +if you are installing the package into a `virtual environment +`_). The easiest way to install +the :mod:`openmc` Python package is to use pip_, which is included by default in +Python 2.7 and Python 3.4+. From the root directory of the OpenMC +distribution/repository, run: + +.. code-block:: sh + + pip install . + +pip will first check that all :ref:`required third-party packages +` have been installed, and if they are not present, +they will be installed by downloading the appropriate packages from the Python +Package Index (`PyPI `_). However, do note that since pip +runs the ``setup.py`` script which requires NumPy, you will have to first +install NumPy: + +.. code-block:: sh + + pip install numpy + +Installing in "Development" Mode +-------------------------------- + +If you are primarily doing development with OpenMC, it is strongly recommended +to install the Python package in :ref:`"editable" mode `. + +.. _usersguide_python_prereqs: + +Prerequisites +------------- + +The Python API works with either Python 2.7 or Python 3.2+. 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. .. admonition:: Required :class: error @@ -457,3 +498,5 @@ schemas.xml file in your own OpenMC source directory. .. _RELAX NG: http://relaxng.org/ .. _NNDC: http://www.nndc.bnl.gov/endf/b7.1/acefiles.html .. _ctest: http://www.cmake.org/cmake/help/v2.8.12/ctest.html +.. _Conda: https://conda.io/docs/ +.. _pip: https://pip.pypa.io/en/stable/ From bf8c556b5afea43be0973ec1af6514e1d4b24d87 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 31 Jan 2018 16:17:42 -0600 Subject: [PATCH 166/282] Unit tests for openmc.Cell --- openmc/cell.py | 9 +- tests/unit_tests/__init__.py | 9 ++ tests/unit_tests/conftest.py | 31 +++++ tests/unit_tests/test_cell.py | 204 ++++++++++++++++++++++++++++++ tests/unit_tests/test_material.py | 40 +----- tests/unit_tests/test_region.py | 8 +- 6 files changed, 252 insertions(+), 49 deletions(-) create mode 100644 tests/unit_tests/test_cell.py diff --git a/openmc/cell.py b/openmc/cell.py index 5975d7f70..074fadc06 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -211,14 +211,7 @@ class Cell(IDManagerMixin): @fill.setter def fill(self, fill): if fill is not None: - if isinstance(fill, string_types): - if fill.strip().lower() != 'void': - msg = 'Unable to set Cell ID="{0}" to use a non-Material ' \ - 'or Universe fill "{1}"'.format(self._id, fill) - raise ValueError(msg) - fill = None - - elif isinstance(fill, Iterable): + if isinstance(fill, Iterable): for i, f in enumerate(fill): if f is not None: cv.check_type('cell.fill[i]', f, openmc.Material) diff --git a/tests/unit_tests/__init__.py b/tests/unit_tests/__init__.py index e69de29bb..59a520c12 100644 --- a/tests/unit_tests/__init__.py +++ b/tests/unit_tests/__init__.py @@ -0,0 +1,9 @@ +import numpy as np +import pytest + + +def assert_unbounded(obj): + """Assert that a region/cell is unbounded.""" + ll, ur = obj.bounding_box + assert ll == pytest.approx((-np.inf, -np.inf, -np.inf)) + assert ur == pytest.approx((np.inf, np.inf, np.inf)) diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index aad0ac058..334bb5fb9 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -1,3 +1,4 @@ +import openmc import pytest @@ -8,3 +9,33 @@ def run_in_tmpdir(tmpdir): yield finally: orig.chdir() + + +@pytest.fixture(scope='module') +def uo2(): + m = openmc.Material(material_id=100, name='UO2') + m.add_nuclide('U235', 1.0) + m.add_nuclide('O16', 2.0) + m.set_density('g/cm3', 10.0) + m.depletable = True + return m + + +@pytest.fixture(scope='module') +def sphere_model(): + model = openmc.model.Model() + + m = openmc.Material() + m.add_nuclide('U235', 1.0) + m.set_density('g/cm3', 1.0) + model.materials.append(m) + + sph = openmc.Sphere(boundary_type='vacuum') + c = openmc.Cell(fill=m, region=-sph) + model.geometry.root_universe = openmc.Universe(cells=[c]) + + model.settings.particles = 100 + model.settings.batches = 10 + model.settings.run_mode = 'fixed source' + model.settings.source = openmc.Source(space=openmc.stats.Point()) + return model diff --git a/tests/unit_tests/test_cell.py b/tests/unit_tests/test_cell.py new file mode 100644 index 000000000..6e6483a28 --- /dev/null +++ b/tests/unit_tests/test_cell.py @@ -0,0 +1,204 @@ +import xml.etree. ElementTree as ET + +import numpy as np +import openmc +import pytest + +from tests.unit_tests import assert_unbounded + + +@pytest.fixture +def cell_with_lattice(): + m_inside = [openmc.Material(), openmc.Material(), None, openmc.Material()] + m_outside = openmc.Material() + + cyl = openmc.ZCylinder(R=1.0) + inside_cyl = openmc.Cell(fill=m_inside, region=-cyl) + outside_cyl = openmc.Cell(fill=m_outside, region=+cyl) + univ = openmc.Universe(cells=[inside_cyl, outside_cyl]) + + lattice = openmc.RectLattice() + lattice.lower_left = (-4.0, -4.0) + lattice.pitch = (4.0, 4.0) + lattice.dimension = (2, 2) + lattice.universes = [[univ, univ], [univ, univ]] + main_cell = openmc.Cell(fill=lattice) + + return ([inside_cyl, outside_cyl, main_cell], + [m_inside[0], m_inside[1], m_inside[3], m_outside], + univ, lattice) + + +def test_contains(): + # Cell with specified region + s = openmc.XPlane() + c = openmc.Cell(region=+s) + assert (1.0, 0.0, 0.0) in c + assert (-1.0, 0.0, 0.0) not in c + + # Cell with no region + c = openmc.Cell() + assert (10.0, -4., 2.0) in c + + +def test_repr(cell_with_lattice): + cells, mats, univ, lattice = cell_with_lattice + repr(cells[0]) # cell with distributed materials + repr(cells[1]) # cell with material + repr(cells[2]) # cell with lattice + + # Empty cell + c = openmc.Cell() + repr(c) + + +def test_bounding_box(): + zcyl = openmc.ZCylinder() + c = openmc.Cell(region=-zcyl) + ll, ur = c.bounding_box + assert ll == pytest.approx((-1., -1., -np.inf)) + assert ur == pytest.approx((1., 1., np.inf)) + + # Cell with no region specified + c = openmc.Cell() + assert_unbounded(c) + + +def test_clone(): + m = openmc.Material() + cyl = openmc.ZCylinder() + c = openmc.Cell(fill=m, region=-cyl) + c.temperature = 650. + + c2 = c.clone() + assert c2.id != c.id + assert c2.fill != c.fill + assert c2.region != c.region + assert c2.temperature == c.temperature + + +def test_temperature(cell_with_lattice): + # Make sure temperature propagates through universes + m = openmc.Material() + s = openmc.XPlane() + c1 = openmc.Cell(fill=m, region=+s) + c2 = openmc.Cell(fill=m, region=-s) + u1 = openmc.Universe(cells=[c1, c2]) + c = openmc.Cell(fill=u1) + + c.temperature = 400.0 + assert c1.temperature == 400.0 + assert c2.temperature == 400.0 + with pytest.raises(ValueError): + c.temperature = -100. + + # distributed temperature + cells, _, _, _ = cell_with_lattice + c = cells[0] + c.temperature = (300., 600., 900.) + + +def test_rotation(): + u = openmc.Universe() + c = openmc.Cell(fill=u) + c.rotation = (180.0, 0.0, 0.0) + assert np.allclose(c.rotation_matrix, [ + [1., 0., 0.], + [0., -1., 0.], + [0., 0., -1.] + ]) + + c.rotation = (0.0, 90.0, 0.0) + assert np.allclose(c.rotation_matrix, [ + [0., 0., -1.], + [0., 1., 0.], + [1., 0., 0.] + ]) + + +def test_volume(run_in_tmpdir, sphere_model): + """Test adding volume information from a volume calculation.""" + ll, ur = sphere_model.geometry.bounding_box + cells = list(sphere_model.geometry.root_universe.cells.values()) + sphere_model.settings.volume_calculations = [ + openmc.VolumeCalculation(domains=cells, samples=1000, + lower_left=ll, upper_right=ur) + ] + sphere_model.export_to_xml() + openmc.calculate_volumes() + volume_calc = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + cells[0].add_volume_information(volume_calc) + + +def test_get_nuclides(uo2): + c = openmc.Cell(fill=uo2) + nucs = c.get_nuclides() + assert nucs == ['U235', 'O16'] + + +def test_nuclide_densities(uo2): + c = openmc.Cell(fill=uo2) + expected_nucs = ['U235', 'O16'] + expected_density = [1.0, 2.0] + tuples = list(c.get_nuclide_densities().values()) + for nuc, density, t in zip(expected_nucs, expected_density, tuples): + assert nuc == t[0] + assert density == t[1] + + # Empty cell + c = openmc.Cell() + assert not c.get_nuclide_densities() + + +def test_get_all_universes(cell_with_lattice): + # Cell with nested universes + c1 = openmc.Cell() + u1 = openmc.Universe(cells=[c1]) + c2 = openmc.Cell(fill=u1) + u2 = openmc.Universe(cells=[c2]) + c3 = openmc.Cell(fill=u2) + univs = set(c3.get_all_universes().values()) + assert not (univs ^ {u1, u2}) + + # Cell with lattice + cells, mats, univ, lattice = cell_with_lattice + univs = set(cells[-1].get_all_universes().values()) + assert not (univs ^ {univ}) + + +def test_get_all_materials(cell_with_lattice): + # Normal cell + m = openmc.Material() + c = openmc.Cell(fill=m) + test_mats = set(c.get_all_materials().values()) + assert not(test_mats ^ {m}) + + # Cell filled with distributed materials + cells, mats, univ, lattice = cell_with_lattice + c = cells[0] + test_mats = set(c.get_all_materials().values()) + assert not (test_mats ^ set(m for m in c.fill if m is not None)) + + # Cell filled with universe + c = cells[-1] + test_mats = set(c.get_all_materials().values()) + assert not (test_mats ^ set(mats)) + + +def test_to_xml_element(cell_with_lattice): + cells, mats, univ, lattice = cell_with_lattice + + c = cells[-1] + root = ET.Element('geometry') + elem = c.create_xml_subelement(root) + assert elem.tag == 'cell' + assert elem.get('id') == str(c.id) + assert elem.get('region') is None + surf_elem = root.find('surface') + assert surf_elem.get('id') == str(cells[0].region.surface.id) + + c = cells[0] + c.temperature = 900.0 + elem = c.create_xml_subelement(root) + assert elem.get('region') == str(c.region) + assert elem.get('temperature') == str(c.temperature) diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index b96cbfb4c..ed2f7698a 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -5,41 +5,6 @@ import openmc.examples import pytest -@pytest.fixture(scope='module') -def uo2(): - m = openmc.Material(material_id=100, name='UO2') - m.add_nuclide('U235', 1.0) - m.add_nuclide('O16', 2.0) - m.set_density('g/cm3', 10.0) - m.depletable = True - return m - - -@pytest.fixture(scope='module') -def sphere_model(): - model = openmc.model.Model() - - m = openmc.Material() - m.add_nuclide('U235', 1.0) - m.set_density('g/cm3', 1.0) - model.materials.append(m) - - sph = openmc.Sphere(boundary_type='vacuum') - c = openmc.Cell(fill=m, region=-sph) - model.geometry.root_universe = openmc.Universe(cells=[c]) - - model.settings.particles = 100 - model.settings.batches = 10 - model.settings.run_mode = 'fixed source' - model.settings.source = openmc.Source(space=openmc.stats.Point()) - ll, ur = c.region.bounding_box - model.settings.volume_calculations = [ - openmc.VolumeCalculation(domains=[m], samples=1000, - lower_left=ll, upper_right=ur) - ] - return model - - def test_attributes(uo2): assert uo2.name == 'UO2' assert uo2.id == 100 @@ -143,6 +108,11 @@ def test_isotropic(): def test_volume(run_in_tmpdir, sphere_model): """Test adding volume information from a volume calculation.""" + ll, ur = sphere_model.geometry.bounding_box + sphere_model.settings.volume_calculations = [ + openmc.VolumeCalculation(domains=sphere_model.materials, samples=1000, + lower_left=ll, upper_right=ur) + ] sphere_model.export_to_xml() openmc.calculate_volumes() volume_calc = openmc.VolumeCalculation.from_hdf5('volume_1.h5') diff --git a/tests/unit_tests/test_region.py b/tests/unit_tests/test_region.py index 346ec7d95..c4f4065bf 100644 --- a/tests/unit_tests/test_region.py +++ b/tests/unit_tests/test_region.py @@ -2,18 +2,14 @@ import numpy as np import pytest import openmc +from tests.unit_tests import assert_unbounded + @pytest.fixture def reset(): openmc.reset_auto_ids() -def assert_unbounded(region): - ll, ur = region.bounding_box - assert ll == pytest.approx((-np.inf, -np.inf, -np.inf)) - assert ur == pytest.approx((np.inf, np.inf, np.inf)) - - def test_union(reset): s1 = openmc.XPlane(surface_id=1, x0=5) s2 = openmc.XPlane(surface_id=2, x0=-5) From b573ad50cf26450a7070d606127e74db7d84ba11 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 31 Jan 2018 21:50:29 -0600 Subject: [PATCH 167/282] Add test for Region.from_expression --- tests/unit_tests/test_region.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/unit_tests/test_region.py b/tests/unit_tests/test_region.py index c4f4065bf..377c8fbd5 100644 --- a/tests/unit_tests/test_region.py +++ b/tests/unit_tests/test_region.py @@ -128,3 +128,35 @@ def test_extend_clone(): r5 = ~r1 r6 = r5.clone() + + +def test_from_expression(reset): + # Create surface dictionary + s1 = openmc.ZCylinder(surface_id=1) + s2 = openmc.ZPlane(surface_id=2, z0=-10.) + s3 = openmc.ZPlane(surface_id=3, z0=10.) + surfs = {1: s1, 2: s2, 3: s3} + + r = openmc.Region.from_expression('-1 2 -3', surfs) + assert isinstance(r, openmc.Intersection) + assert r[:] == [-s1, +s2, -s3] + + r = openmc.Region.from_expression('+1 | -2 | +3', surfs) + assert isinstance(r, openmc.Union) + assert r[:] == [+s1, -s2, +s3] + + r = openmc.Region.from_expression('~(-1)', surfs) + assert r == +s1 + + # Since & has higher precendence than |, the resulting region should be an + # instance of Union + r = openmc.Region.from_expression('1 -2 | 3', surfs) + assert isinstance(r, openmc.Union) + assert isinstance(r[0], openmc.Intersection) + assert r[0][:] == [+s1, -s2] + + # ...but not if we use parentheses + r = openmc.Region.from_expression('1 (-2 | 3)', surfs) + assert isinstance(r, openmc.Intersection) + assert isinstance(r[1], openmc.Union) + assert r[1][:] == [-s2, +s3] From 1a93883b670421e37ac226849df8dfc98abed0fb Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 1 Feb 2018 07:17:34 -0600 Subject: [PATCH 168/282] Add tests for openmc.Universe --- openmc/universe.py | 8 +- tests/unit_tests/conftest.py | 22 ++++ tests/unit_tests/test_cell.py | 22 ---- tests/unit_tests/test_geometry.py | 17 ++++ tests/unit_tests/test_universe.py | 160 ++++++++++++++++++++++++++++++ 5 files changed, 203 insertions(+), 26 deletions(-) create mode 100644 tests/unit_tests/test_geometry.py create mode 100644 tests/unit_tests/test_universe.py diff --git a/openmc/universe.py b/openmc/universe.py index 92b152ca0..4a0a1a9aa 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -92,7 +92,7 @@ class Universe(IDManagerMixin): return openmc.Union(regions).bounding_box else: # Infinite bounding box - return openmc.Intersection().bounding_box + return openmc.Intersection([]).bounding_box @name.setter def name(self, name): @@ -322,7 +322,7 @@ class Universe(IDManagerMixin): if not isinstance(cell, openmc.Cell): msg = 'Unable to add a Cell to Universe ID="{0}" since "{1}" is not ' \ 'a Cell'.format(self._id, cell) - raise ValueError(msg) + raise TypeError(msg) cell_id = cell.id @@ -342,7 +342,7 @@ class Universe(IDManagerMixin): if not isinstance(cells, Iterable): msg = 'Unable to add Cells to Universe ID="{0}" since "{1}" is not ' \ 'iterable'.format(self._id, cells) - raise ValueError(msg) + raise TypeError(msg) for cell in cells: self.add_cell(cell) @@ -360,7 +360,7 @@ class Universe(IDManagerMixin): if not isinstance(cell, openmc.Cell): msg = 'Unable to remove a Cell from Universe ID="{0}" since "{1}" is ' \ 'not a Cell'.format(self._id, cell) - raise ValueError(msg) + raise TypeError(msg) # If the Cell is in the Universe's list of Cells, delete it if cell.id in self._cells: diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 334bb5fb9..621006471 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -39,3 +39,25 @@ def sphere_model(): model.settings.run_mode = 'fixed source' model.settings.source = openmc.Source(space=openmc.stats.Point()) return model + + +@pytest.fixture +def cell_with_lattice(): + m_inside = [openmc.Material(), openmc.Material(), None, openmc.Material()] + m_outside = openmc.Material() + + cyl = openmc.ZCylinder(R=1.0) + inside_cyl = openmc.Cell(fill=m_inside, region=-cyl) + outside_cyl = openmc.Cell(fill=m_outside, region=+cyl) + univ = openmc.Universe(cells=[inside_cyl, outside_cyl]) + + lattice = openmc.RectLattice() + lattice.lower_left = (-4.0, -4.0) + lattice.pitch = (4.0, 4.0) + lattice.dimension = (2, 2) + lattice.universes = [[univ, univ], [univ, univ]] + main_cell = openmc.Cell(fill=lattice) + + return ([inside_cyl, outside_cyl, main_cell], + [m_inside[0], m_inside[1], m_inside[3], m_outside], + univ, lattice) diff --git a/tests/unit_tests/test_cell.py b/tests/unit_tests/test_cell.py index 6e6483a28..b9470a3a6 100644 --- a/tests/unit_tests/test_cell.py +++ b/tests/unit_tests/test_cell.py @@ -7,28 +7,6 @@ import pytest from tests.unit_tests import assert_unbounded -@pytest.fixture -def cell_with_lattice(): - m_inside = [openmc.Material(), openmc.Material(), None, openmc.Material()] - m_outside = openmc.Material() - - cyl = openmc.ZCylinder(R=1.0) - inside_cyl = openmc.Cell(fill=m_inside, region=-cyl) - outside_cyl = openmc.Cell(fill=m_outside, region=+cyl) - univ = openmc.Universe(cells=[inside_cyl, outside_cyl]) - - lattice = openmc.RectLattice() - lattice.lower_left = (-4.0, -4.0) - lattice.pitch = (4.0, 4.0) - lattice.dimension = (2, 2) - lattice.universes = [[univ, univ], [univ, univ]] - main_cell = openmc.Cell(fill=lattice) - - return ([inside_cyl, outside_cyl, main_cell], - [m_inside[0], m_inside[1], m_inside[3], m_outside], - univ, lattice) - - def test_contains(): # Cell with specified region s = openmc.XPlane() diff --git a/tests/unit_tests/test_geometry.py b/tests/unit_tests/test_geometry.py new file mode 100644 index 000000000..fc681c8d8 --- /dev/null +++ b/tests/unit_tests/test_geometry.py @@ -0,0 +1,17 @@ +import xml.etree.ElementTree as ET + +import openmc +import pytest + + +def test_determine_paths(cell_with_lattice): + cells, mats, univ, lattice = cell_with_lattice + u = openmc.Universe(cells=[cells[-1]]) + geom = openmc.Geometry(u) + + geom.determine_paths() + assert len(cells[0].paths) == 4 + assert len(cells[1].paths) == 4 + assert len(cells[2].paths) == 1 + assert len(mats[0].paths) == 1 + assert len(mats[-1].paths) == 4 diff --git a/tests/unit_tests/test_universe.py b/tests/unit_tests/test_universe.py new file mode 100644 index 000000000..656b419cf --- /dev/null +++ b/tests/unit_tests/test_universe.py @@ -0,0 +1,160 @@ +import xml.etree.ElementTree as ET + +import numpy as np +import openmc +import pytest + +from tests.unit_tests import assert_unbounded + + +def test_basic(): + c1 = openmc.Cell() + c2 = openmc.Cell() + c3 = openmc.Cell() + u = openmc.Universe(name='cool', cells=(c1, c2, c3)) + assert u.name == 'cool' + + cells = set(u.cells.values()) + assert not (cells ^ {c1, c2, c3}) + + # Test __repr__ + repr(u) + + with pytest.raises(TypeError): + u.add_cell(openmc.Material()) + with pytest.raises(TypeError): + u.add_cells(c1) + + u.remove_cell(c3) + cells = set(u.cells.values()) + assert not (cells ^ {c1, c2}) + + u.clear_cells() + assert not set(u.cells) + + +def test_bounding_box(): + cyl1 = openmc.ZCylinder(R=1.0) + cyl2 = openmc.ZCylinder(R=2.0) + c1 = openmc.Cell(region=-cyl1) + c2 = openmc.Cell(region=+cyl1 & -cyl2) + + u = openmc.Universe(cells=[c1, c2]) + ll, ur = u.bounding_box + assert ll == pytest.approx((-2., -2., -np.inf)) + assert ur == pytest.approx((2., 2., np.inf)) + + u = openmc.Universe() + assert_unbounded(u) + + +def test_find(uo2): + xp = openmc.XPlane() + c1 = openmc.Cell(fill=uo2, region=+xp) + c2 = openmc.Cell(region=-xp) + u1 = openmc.Universe(cells=(c1, c2)) + + cyl = openmc.ZCylinder() + c3 = openmc.Cell(fill=u1, region=-cyl) + c4 = openmc.Cell(region=+cyl) + u = openmc.Universe(cells=(c3, c4)) + + seq = u.find((0.5, 0., 0.)) + assert seq[-1] == c1 + seq = u.find((-0.5, 0., 0.)) + assert seq[-1] == c2 + seq = u.find((-1.5, 0., 0.)) + assert seq[-1] == c4 + + +def test_volume(run_in_tmpdir, sphere_model): + """Test adding volume information from a volume calculation.""" + univ = sphere_model.geometry.root_universe + ll, ur = univ.bounding_box + sphere_model.settings.volume_calculations = [ + openmc.VolumeCalculation(domains=[univ], samples=1000, + lower_left=ll, upper_right=ur) + ] + sphere_model.export_to_xml() + openmc.calculate_volumes() + volume_calc = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + univ.add_volume_information(volume_calc) + + # get_nuclide_densities relies on volume information + nucs = set(univ.get_nuclide_densities()) + assert not (nucs ^ {'U235'}) + + +def test_plot(run_in_tmpdir, sphere_model): + m = sphere_model.materials[0] + univ = sphere_model.geometry.root_universe + + colors = {m: 'limegreen'} + for basis in ('xy', 'yz', 'xz'): + univ.plot( + basis=basis, + pixels=(10, 10), + color_by='material', + colors=colors, + filename='test.png' + ) + + +def test_get_nuclides(uo2): + c = openmc.Cell(fill=uo2) + univ = openmc.Universe(cells=[c]) + nucs = univ.get_nuclides() + assert nucs == ['U235', 'O16'] + + +def test_cells(): + cells = [openmc.Cell() for i in range(5)] + cells2 = [openmc.Cell() for i in range(3)] + cells[0].fill = openmc.Universe(cells=cells2) + u = openmc.Universe(cells=cells) + assert not (set(u.cells.values()) ^ set(cells)) + + all_cells = set(u.get_all_cells().values()) + assert not (all_cells ^ set(cells + cells2)) + + +def test_get_all_materials(cell_with_lattice): + cells, mats, univ, lattice = cell_with_lattice + test_mats = set(univ.get_all_materials().values()) + assert not (test_mats ^ set(mats)) + + +def test_get_all_universes(): + c1 = openmc.Cell() + u1 = openmc.Universe(cells=[c1]) + c2 = openmc.Cell() + u2 = openmc.Universe(cells=[c2]) + c3 = openmc.Cell(fill=u1) + c4 = openmc.Cell(fill=u2) + u3 = openmc.Universe(cells=[c3, c4]) + + univs = set(u3.get_all_universes().values()) + assert not (univs ^ {u1, u2}) + + +def test_clone(): + c1 = openmc.Cell() + c2 = openmc.Cell() + u = openmc.Universe(cells=[c1, c2]) + + u_clone = u.clone() + assert u_clone.id != u.id + assert not (set(u.cells) & set(u_clone.cells)) + + +def test_create_xml(cell_with_lattice): + cells = [openmc.Cell() for i in range(5)] + u = openmc.Universe(cells=cells) + + geom = ET.Element('geom') + u.create_xml_subelement(geom) + cell_elems = geom.findall('cell') + assert len(cell_elems) == len(cells) + assert all(c.get('universe') == str(u.id) for c in cell_elems) + assert not (set(c.get('id') for c in cell_elems) ^ + set(str(c.id) for c in cells)) From 347243876f5c6d50a13acaa13a136dc57eb3f536 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 1 Feb 2018 10:00:50 -0600 Subject: [PATCH 169/282] Combine volume calculations into a single unit test --- tests/unit_tests/test_cell.py | 14 ----- tests/unit_tests/test_geometry.py | 98 +++++++++++++++++++++++++++++++ tests/unit_tests/test_material.py | 13 ---- tests/unit_tests/test_universe.py | 18 ------ 4 files changed, 98 insertions(+), 45 deletions(-) diff --git a/tests/unit_tests/test_cell.py b/tests/unit_tests/test_cell.py index b9470a3a6..d01d94a32 100644 --- a/tests/unit_tests/test_cell.py +++ b/tests/unit_tests/test_cell.py @@ -94,20 +94,6 @@ def test_rotation(): ]) -def test_volume(run_in_tmpdir, sphere_model): - """Test adding volume information from a volume calculation.""" - ll, ur = sphere_model.geometry.bounding_box - cells = list(sphere_model.geometry.root_universe.cells.values()) - sphere_model.settings.volume_calculations = [ - openmc.VolumeCalculation(domains=cells, samples=1000, - lower_left=ll, upper_right=ur) - ] - sphere_model.export_to_xml() - openmc.calculate_volumes() - volume_calc = openmc.VolumeCalculation.from_hdf5('volume_1.h5') - cells[0].add_volume_information(volume_calc) - - def test_get_nuclides(uo2): c = openmc.Cell(fill=uo2) nucs = c.get_nuclides() diff --git a/tests/unit_tests/test_geometry.py b/tests/unit_tests/test_geometry.py index fc681c8d8..839453787 100644 --- a/tests/unit_tests/test_geometry.py +++ b/tests/unit_tests/test_geometry.py @@ -4,6 +4,104 @@ import openmc import pytest +def test_volume(uo2): + """Test adding volume information from a volume calculation.""" + # Create model with nested spheres + model = openmc.model.Model() + model.materials.append(uo2) + inner = openmc.Sphere(R=1.) + outer = openmc.Sphere(R=2., boundary_type='vacuum') + c1 = openmc.Cell(fill=uo2, region=-inner) + c2 = openmc.Cell(region=+inner & -outer) + u = openmc.Universe(cells=[c1, c2]) + model.geometry.root_universe = u + model.settings.particles = 100 + model.settings.batches = 10 + model.settings.run_mode = 'fixed source' + model.settings.source = openmc.Source(space=openmc.stats.Point()) + + ll, ur = model.geometry.bounding_box + assert ll == pytest.approx((-outer.r, -outer.r, -outer.r)) + assert ur == pytest.approx((outer.r, outer.r, outer.r)) + model.settings.volume_calculations + + for domain in (c1, uo2, u): + # Run stochastic volume calculation + volume_calc = openmc.VolumeCalculation( + domains=[domain], samples=1000, lower_left=ll, upper_right=ur) + model.settings.volume_calculations = [volume_calc] + model.export_to_xml() + openmc.calculate_volumes() + + # Load results and add volume information + volume_calc.load_results('volume_1.h5') + model.geometry.add_volume_information(volume_calc) + + # get_nuclide_densities relies on volume information + nucs = set(domain.get_nuclide_densities()) + assert not (nucs ^ {'U235', 'O16'}) + + +def test_export_xml(): + pass + + +def test_find(): + pass + + +def test_get_instances(): + pass + + +def test_get_all_universes(): + pass + + +def test_get_all_materials(): + pass + + +def test_get_all_material_cells(): + pass + + +def test_get_all_material_universes(): + pass + + +def test_get_all_lattices(): + pass + + +def test_get_all_surfaces(): + pass + + +def test_get_materials_by_name(): + pass + + +def test_get_cells_by_name(): + pass + + +def test_get_cells_by_fill_name(): + pass + + +def test_get_universes_by_name(): + pass + + +def test_get_lattices_by_name(): + pass + + +def test_clone(): + pass + + def test_determine_paths(cell_with_lattice): cells, mats, univ, lattice = cell_with_lattice u = openmc.Universe(cells=[cells[-1]]) diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index ed2f7698a..e92a2ac08 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -106,19 +106,6 @@ def test_isotropic(): assert m2.isotropic == ['H1'] -def test_volume(run_in_tmpdir, sphere_model): - """Test adding volume information from a volume calculation.""" - ll, ur = sphere_model.geometry.bounding_box - sphere_model.settings.volume_calculations = [ - openmc.VolumeCalculation(domains=sphere_model.materials, samples=1000, - lower_left=ll, upper_right=ur) - ] - sphere_model.export_to_xml() - openmc.calculate_volumes() - volume_calc = openmc.VolumeCalculation.from_hdf5('volume_1.h5') - sphere_model.materials[0].add_volume_information(volume_calc) - - def test_get_nuclide_densities(uo2): nucs = uo2.get_nuclide_densities() for nuc, density, density_type in nucs.values(): diff --git a/tests/unit_tests/test_universe.py b/tests/unit_tests/test_universe.py index 656b419cf..ac384e9e4 100644 --- a/tests/unit_tests/test_universe.py +++ b/tests/unit_tests/test_universe.py @@ -67,24 +67,6 @@ def test_find(uo2): assert seq[-1] == c4 -def test_volume(run_in_tmpdir, sphere_model): - """Test adding volume information from a volume calculation.""" - univ = sphere_model.geometry.root_universe - ll, ur = univ.bounding_box - sphere_model.settings.volume_calculations = [ - openmc.VolumeCalculation(domains=[univ], samples=1000, - lower_left=ll, upper_right=ur) - ] - sphere_model.export_to_xml() - openmc.calculate_volumes() - volume_calc = openmc.VolumeCalculation.from_hdf5('volume_1.h5') - univ.add_volume_information(volume_calc) - - # get_nuclide_densities relies on volume information - nucs = set(univ.get_nuclide_densities()) - assert not (nucs ^ {'U235'}) - - def test_plot(run_in_tmpdir, sphere_model): m = sphere_model.materials[0] univ = sphere_model.geometry.root_universe From b9ae59620003f66018c45e7c6566ddf5d4549662 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 1 Feb 2018 11:23:17 -0600 Subject: [PATCH 170/282] Add tests for openmc.Geometry and remove __eq__ on openmc.Lattice --- openmc/geometry.py | 39 +++--- openmc/lattice.py | 19 --- tests/unit_tests/conftest.py | 2 +- tests/unit_tests/test_geometry.py | 199 +++++++++++++++++++++++++----- tests/unit_tests/test_universe.py | 29 ----- 5 files changed, 184 insertions(+), 104 deletions(-) diff --git a/openmc/geometry.py b/openmc/geometry.py index 0738ae615..ac068e2b0 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -14,8 +14,9 @@ class Geometry(object): Parameters ---------- - root_universe : openmc.Universe, optional - Root universe which contains all others + root : openmc.Universe or Iterable of openmc.Cell, optional + Root universe which contains all others, or an iterable of cells that + should be used to create a root universe. Attributes ---------- @@ -27,11 +28,17 @@ class Geometry(object): """ - def __init__(self, root_universe=None): + def __init__(self, root=None): self._root_universe = None self._offsets = {} - if root_universe is not None: - self.root_universe = root_universe + if root is not None: + if isinstance(root, openmc.Universe): + self.root_universe = root + else: + univ = openmc.Universe() + for cell in root: + univ.add_cell(cell) + self._root_universe = univ @property def root_universe(self): @@ -249,7 +256,7 @@ class Geometry(object): for cell in self.get_all_cells().values(): if cell.fill_type == 'lattice': - if cell.fill not in lattices: + if cell.fill.id not in lattices: lattices[cell.fill.id] = cell.fill return lattices @@ -306,9 +313,7 @@ class Geometry(object): elif not matching and name in material_name: materials.add(material) - materials = list(materials) - materials.sort(key=lambda x: x.id) - return materials + return sorted(materials, key=lambda x: x.id) def get_cells_by_name(self, name, case_sensitive=False, matching=False): """Return a list of cells with matching names. @@ -346,9 +351,7 @@ class Geometry(object): elif not matching and name in cell_name: cells.add(cell) - cells = list(cells) - cells.sort(key=lambda x: x.id) - return cells + return sorted(cells, key=lambda x: x.id) def get_cells_by_fill_name(self, name, case_sensitive=False, matching=False): """Return a list of cells with fills with matching names. @@ -393,9 +396,7 @@ class Geometry(object): elif not matching and name in fill_name: cells.add(cell) - cells = list(cells) - cells.sort(key=lambda x: x.id) - return cells + return sorted(cells, key=lambda x: x.id) def get_universes_by_name(self, name, case_sensitive=False, matching=False): """Return a list of universes with matching names. @@ -433,9 +434,7 @@ class Geometry(object): elif not matching and name in universe_name: universes.add(universe) - universes = list(universes) - universes.sort(key=lambda x: x.id) - return universes + return sorted(universes, key=lambda x: x.id) def get_lattices_by_name(self, name, case_sensitive=False, matching=False): """Return a list of lattices with matching names. @@ -473,9 +472,7 @@ class Geometry(object): elif not matching and name in lattice_name: lattices.add(lattice) - lattices = list(lattices) - lattices.sort(key=lambda x: x.id) - return lattices + return sorted(lattices, key=lambda x: x.id) def determine_paths(self, instances_only=False): """Determine paths through CSG tree for cells and materials. diff --git a/openmc/lattice.py b/openmc/lattice.py index 79f0d43f3..933f51af3 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -54,25 +54,6 @@ class Lattice(IDManagerMixin): self._outer = None self._universes = None - def __eq__(self, other): - if not isinstance(other, Lattice): - return False - elif self.id != other.id: - return False - elif self.name != other.name: - return False - elif np.any(self.pitch != other.pitch): - return False - elif self.outer != other.outer: - return False - elif np.any(self.universes != other.universes): - return False - else: - return True - - def __ne__(self, other): - return not self == other - @property def name(self): return self._name diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 621006471..af534afe3 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -51,7 +51,7 @@ def cell_with_lattice(): outside_cyl = openmc.Cell(fill=m_outside, region=+cyl) univ = openmc.Universe(cells=[inside_cyl, outside_cyl]) - lattice = openmc.RectLattice() + lattice = openmc.RectLattice(name='My Lattice') lattice.lower_left = (-4.0, -4.0) lattice.pitch = (4.0, 4.0) lattice.dimension = (2, 2) diff --git a/tests/unit_tests/test_geometry.py b/tests/unit_tests/test_geometry.py index 839453787..93d2fa634 100644 --- a/tests/unit_tests/test_geometry.py +++ b/tests/unit_tests/test_geometry.py @@ -1,10 +1,11 @@ import xml.etree.ElementTree as ET +import numpy as np import openmc import pytest -def test_volume(uo2): +def test_volume(run_in_tmpdir, uo2): """Test adding volume information from a volume calculation.""" # Create model with nested spheres model = openmc.model.Model() @@ -39,67 +40,192 @@ def test_volume(uo2): # get_nuclide_densities relies on volume information nucs = set(domain.get_nuclide_densities()) - assert not (nucs ^ {'U235', 'O16'}) + assert not nucs ^ {'U235', 'O16'} -def test_export_xml(): - pass +def test_export_xml(run_in_tmpdir, uo2): + s1 = openmc.Sphere(R=1.) + s2 = openmc.Sphere(R=2., boundary_type='reflective') + c1 = openmc.Cell(fill=uo2, region=-s1) + c2 = openmc.Cell(fill=uo2, region=+s1 & -s2) + geom = openmc.Geometry([c1, c2]) + geom.export_to_xml() + + doc = ET.parse('geometry.xml') + root = doc.getroot() + assert root.tag == 'geometry' + cells = root.findall('cell') + assert [int(c.get('id')) for c in cells] == [c1.id, c2.id] + surfs = root.findall('surface') + assert [int(s.get('id')) for s in surfs] == [s1.id, s2.id] -def test_find(): - pass +def test_find(uo2): + xp = openmc.XPlane() + c1 = openmc.Cell(fill=uo2, region=+xp) + c2 = openmc.Cell(region=-xp) + u1 = openmc.Universe(cells=(c1, c2)) + + cyl = openmc.ZCylinder() + c3 = openmc.Cell(fill=u1, region=-cyl) + c4 = openmc.Cell(region=+cyl) + geom = openmc.Geometry((c3, c4)) + + seq = geom.find((0.5, 0., 0.)) + assert seq[-1] == c1 + seq = geom.find((-0.5, 0., 0.)) + assert seq[-1] == c2 + seq = geom.find((-1.5, 0., 0.)) + assert seq[-1] == c4 -def test_get_instances(): - pass +def test_get_all_cells(): + cells = [openmc.Cell() for i in range(5)] + cells2 = [openmc.Cell() for i in range(3)] + cells[0].fill = openmc.Universe(cells=cells2) + geom = openmc.Geometry(cells) - -def test_get_all_universes(): - pass + all_cells = set(geom.get_all_cells().values()) + assert not all_cells ^ set(cells + cells2) def test_get_all_materials(): - pass + m1 = openmc.Material() + m2 = openmc.Material() + c1 = openmc.Cell(fill=m1) + u1 = openmc.Universe(cells=[c1]) + + s = openmc.Sphere() + c2 = openmc.Cell(fill=u1, region=-s) + c3 = openmc.Cell(fill=m2, region=+s) + geom = openmc.Geometry([c2, c3]) + + all_mats = set(geom.get_all_materials().values()) + assert not all_mats ^ {m1, m2} def test_get_all_material_cells(): - pass + m1 = openmc.Material() + m2 = openmc.Material() + c1 = openmc.Cell(fill=m1) + u1 = openmc.Universe(cells=[c1]) + + s = openmc.Sphere() + c2 = openmc.Cell(fill=u1, region=-s) + c3 = openmc.Cell(fill=m2, region=+s) + geom = openmc.Geometry([c2, c3]) + + all_cells = set(geom.get_all_material_cells().values()) + assert not all_cells ^ {c1, c3} def test_get_all_material_universes(): - pass + m1 = openmc.Material() + m2 = openmc.Material() + c1 = openmc.Cell(fill=m1) + u1 = openmc.Universe(cells=[c1]) + + s = openmc.Sphere() + c2 = openmc.Cell(fill=u1, region=-s) + c3 = openmc.Cell(fill=m2, region=+s) + geom = openmc.Geometry([c2, c3]) + + all_univs = set(geom.get_all_material_universes().values()) + assert not all_univs ^ {u1, geom.root_universe} -def test_get_all_lattices(): - pass +def test_get_all_lattices(cell_with_lattice): + cells, mats, univ, lattice = cell_with_lattice + geom = openmc.Geometry([cells[-1]]) + + lats = list(geom.get_all_lattices().values()) + assert lats == [lattice] -def test_get_all_surfaces(): - pass +def test_get_all_surfaces(uo2): + planes = [openmc.ZPlane(z0=z) for z in np.linspace(-100., 100.)] + slabs = [] + for region in openmc.model.subdivide(planes): + slabs.append(openmc.Cell(fill=uo2, region=region)) + geom = openmc.Geometry(slabs) + + surfs = set(geom.get_all_surfaces().values()) + assert not surfs ^ set(planes) -def test_get_materials_by_name(): - pass +def test_get_by_name(): + m1 = openmc.Material(name='zircaloy') + m1.add_element('Zr', 1.0) + m2 = openmc.Material(name='Zirconium') + m2.add_element('Zr', 1.0) + + c1 = openmc.Cell(fill=m1, name='cell1') + u1 = openmc.Universe(name='Zircaloy universe', cells=[c1]) + + cyl = openmc.ZCylinder() + c2 = openmc.Cell(fill=u1, region=-cyl, name='cell2') + c3 = openmc.Cell(fill=m2, region=+cyl, name='Cell3') + root = openmc.Universe(name='root Universe', cells=[c2, c3]) + geom = openmc.Geometry(root) + + mats = set(geom.get_materials_by_name('zirc')) + assert not mats ^ {m1, m2} + mats = set(geom.get_materials_by_name('zirc', True)) + assert not mats ^ {m1} + mats = set(geom.get_materials_by_name('zirconium', False, True)) + assert not mats ^ {m2} + mats = geom.get_materials_by_name('zirconium', True, True) + assert not mats + + cells = set(geom.get_cells_by_name('cell')) + assert not cells ^ {c1, c2, c3} + cells = set(geom.get_cells_by_name('cell', True)) + assert not cells ^ {c1, c2} + cells = set(geom.get_cells_by_name('cell3', False, True)) + assert not cells ^ {c3} + cells = geom.get_cells_by_name('cell3', True, True) + assert not cells + + cells = set(geom.get_cells_by_fill_name('Zircaloy')) + assert not cells ^ {c1, c2} + cells = set(geom.get_cells_by_fill_name('Zircaloy', True)) + assert not cells ^ {c2} + cells = set(geom.get_cells_by_fill_name('Zircaloy', False, True)) + assert not cells ^ {c1} + cells = geom.get_cells_by_fill_name('Zircaloy', True, True) + assert not cells + + univs = set(geom.get_universes_by_name('universe')) + assert not univs ^ {u1, root} + univs = set(geom.get_universes_by_name('universe', True)) + assert not univs ^ {u1} + univs = set(geom.get_universes_by_name('universe', True, True)) + assert not univs -def test_get_cells_by_name(): - pass +def test_get_lattice_by_name(cell_with_lattice): + cells, _, _, lattice = cell_with_lattice + geom = openmc.Geometry([cells[-1]]) - -def test_get_cells_by_fill_name(): - pass - - -def test_get_universes_by_name(): - pass - - -def test_get_lattices_by_name(): - pass + f = geom.get_lattices_by_name + assert f('lattice') == [lattice] + assert f('lattice', True) == [] + assert f('Lattice', True) == [lattice] + assert f('my lattice', False, True) == [lattice] + assert f('my lattice', True, True) == [] def test_clone(): - pass + c1 = openmc.Cell() + c2 = openmc.Cell() + root = openmc.Universe(cells=[c1, c2]) + geom = openmc.Geometry(root) + + clone = geom.clone() + root_clone = clone.root_universe + + assert root.id != root_clone.id + assert not (set(root.cells) & set(root_clone.cells)) def test_determine_paths(cell_with_lattice): @@ -113,3 +239,8 @@ def test_determine_paths(cell_with_lattice): assert len(cells[2].paths) == 1 assert len(mats[0].paths) == 1 assert len(mats[-1].paths) == 4 + + # Test get_instances + for i in range(4): + assert geom.get_instances(cells[0].paths[i]) == i + assert geom.get_instances(mats[-1].paths[i]) == i diff --git a/tests/unit_tests/test_universe.py b/tests/unit_tests/test_universe.py index ac384e9e4..59e34e201 100644 --- a/tests/unit_tests/test_universe.py +++ b/tests/unit_tests/test_universe.py @@ -48,25 +48,6 @@ def test_bounding_box(): assert_unbounded(u) -def test_find(uo2): - xp = openmc.XPlane() - c1 = openmc.Cell(fill=uo2, region=+xp) - c2 = openmc.Cell(region=-xp) - u1 = openmc.Universe(cells=(c1, c2)) - - cyl = openmc.ZCylinder() - c3 = openmc.Cell(fill=u1, region=-cyl) - c4 = openmc.Cell(region=+cyl) - u = openmc.Universe(cells=(c3, c4)) - - seq = u.find((0.5, 0., 0.)) - assert seq[-1] == c1 - seq = u.find((-0.5, 0., 0.)) - assert seq[-1] == c2 - seq = u.find((-1.5, 0., 0.)) - assert seq[-1] == c4 - - def test_plot(run_in_tmpdir, sphere_model): m = sphere_model.materials[0] univ = sphere_model.geometry.root_universe @@ -119,16 +100,6 @@ def test_get_all_universes(): assert not (univs ^ {u1, u2}) -def test_clone(): - c1 = openmc.Cell() - c2 = openmc.Cell() - u = openmc.Universe(cells=[c1, c2]) - - u_clone = u.clone() - assert u_clone.id != u.id - assert not (set(u.cells) & set(u_clone.cells)) - - def test_create_xml(cell_with_lattice): cells = [openmc.Cell() for i in range(5)] u = openmc.Universe(cells=cells) From ef16a6cb575e9f907a2c2babc091aca5d9c7d86d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 1 Feb 2018 12:35:29 -0600 Subject: [PATCH 171/282] Fix bug in stochastic volume calculation with void cell --- src/volume_calc.F90 | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/volume_calc.F90 b/src/volume_calc.F90 index 15c049769..e374f2106 100644 --- a/src/volume_calc.F90 +++ b/src/volume_calc.F90 @@ -193,11 +193,13 @@ contains if (this % domain_type == FILTER_MATERIAL) then i_material = p % material - do i_domain = 1, size(this % domain_id) - if (materials(i_material) % id == this % domain_id(i_domain)) then - call check_hit(i_domain, i_material, indices, hits, n_mat) - end if - end do + if (i_material /= MATERIAL_VOID) then + do i_domain = 1, size(this % domain_id) + if (materials(i_material) % id == this % domain_id(i_domain)) then + call check_hit(i_domain, i_material, indices, hits, n_mat) + end if + end do + end if elseif (this % domain_type == FILTER_CELL) THEN do level = 1, p % n_coord From 07d853dd1d1275eea957095d2fd8a8d7d8148561 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 1 Feb 2018 12:40:54 -0600 Subject: [PATCH 172/282] Allow tests that require GUI (matplotlib) --- .travis.yml | 1 + tools/ci/travis-before-script.sh | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 14e248411..9094637c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ env: - OPENMC_ENDF_DATA=$HOME/endf-b-vii.1 - OPENMC_MULTIPOLE_LIBRARY=$HOME/multipole_lib - PATH=$PATH:$HOME/NJOY2016/build + - DISPLAY=:99.0 matrix: - OMP=n MPI=n PHDF5=n - OMP=y MPI=n PHDF5=n diff --git a/tools/ci/travis-before-script.sh b/tools/ci/travis-before-script.sh index bbf4980b0..d0df06f2f 100755 --- a/tools/ci/travis-before-script.sh +++ b/tools/ci/travis-before-script.sh @@ -1,6 +1,10 @@ #!/bin/bash set -ex +# Allow tests that require GUI as described at: +# https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-xvfb-to-Run-Tests-That-Require-a-GUI +sh -e /etc/init.d/xvfb start + # Download NNDC HDF5 data if [[ ! -e $HOME/nndc_hdf5/cross_sections.xml ]]; then wget https://anl.box.com/shared/static/a6sw2cep34wlz6b9i9jwiotaqoayxcxt.xz -O - | tar -C $HOME -xvJ From 2027920fdd4e692405c939be5bf0932c54f982d9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 1 Feb 2018 14:52:53 -0600 Subject: [PATCH 173/282] Add unit tests for openmc.RectLattice and openmc.HexLattice --- openmc/lattice.py | 6 +- tests/unit_tests/conftest.py | 12 +- tests/unit_tests/test_data_neutron.py | 1 + tests/unit_tests/test_lattice.py | 344 ++++++++++++++++++++++++++ 4 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 tests/unit_tests/test_lattice.py diff --git a/openmc/lattice.py b/openmc/lattice.py index 933f51af3..730d7dc4a 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -560,7 +560,11 @@ class RectLattice(Lattice): @property def ndim(self): - return len(self.pitch) + if self.pitch is not None: + return len(self.pitch) + else: + raise ValueError('Number of dimensions cannot be determined until ' + 'the lattice pitch has been set.') @property def shape(self): diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index af534afe3..d618b85de 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -21,10 +21,19 @@ def uo2(): return m +@pytest.fixture(scope='module') +def water(): + m = openmc.Material(name='light water') + m.add_nuclide('H1', 2.0) + m.add_nuclide('O16', 1.0) + m.set_density('g/cm3', 1.0) + m.add_s_alpha_beta('c_H_in_H2O') + return m + + @pytest.fixture(scope='module') def sphere_model(): model = openmc.model.Model() - m = openmc.Material() m.add_nuclide('U235', 1.0) m.set_density('g/cm3', 1.0) @@ -54,7 +63,6 @@ def cell_with_lattice(): lattice = openmc.RectLattice(name='My Lattice') lattice.lower_left = (-4.0, -4.0) lattice.pitch = (4.0, 4.0) - lattice.dimension = (2, 2) lattice.universes = [[univ, univ], [univ, univ]] main_cell = openmc.Cell(fill=lattice) diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index e314d32a4..5713bfbc5 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -213,6 +213,7 @@ def test_export_to_hdf5(tmpdir, pu239, gd154): with pytest.raises(NotImplementedError): gd154.export_to_hdf5('gd154.h5') + def test_slbw(xe135): res = xe135.resonances assert isinstance(res, openmc.data.Resonances) diff --git a/tests/unit_tests/test_lattice.py b/tests/unit_tests/test_lattice.py new file mode 100644 index 000000000..47dc1c029 --- /dev/null +++ b/tests/unit_tests/test_lattice.py @@ -0,0 +1,344 @@ +from math import sqrt +import xml.etree.ElementTree as ET + +import openmc +import pytest + + +@pytest.fixture(scope='module') +def pincell1(uo2, water): + cyl = openmc.ZCylinder(R=0.35) + fuel = openmc.Cell(fill=uo2, region=-cyl) + moderator = openmc.Cell(fill=water, region=+cyl) + + univ = openmc.Universe(cells=[fuel, moderator]) + univ.fuel = fuel + univ.moderator = moderator + return univ + + +@pytest.fixture(scope='module') +def pincell2(uo2, water): + cyl = openmc.ZCylinder(R=0.4) + fuel = openmc.Cell(fill=uo2, region=-cyl) + moderator = openmc.Cell(fill=water, region=+cyl) + + univ = openmc.Universe(cells=[fuel, moderator]) + univ.fuel = fuel + univ.moderator = moderator + return univ + + +@pytest.fixture(scope='module') +def zr(): + zr = openmc.Material() + zr.add_element('Zr', 1.0) + zr.set_density('g/cm3', 1.0) + return zr + + +@pytest.fixture(scope='module') +def rlat2(pincell1, pincell2, uo2, water, zr): + """2D Rectangular lattice for testing.""" + all_zr = openmc.Cell(fill=zr) + pitch = 1.2 + n = 3 + u1, u2 = pincell1, pincell2 + lattice = openmc.RectLattice() + lattice.lower_left = (-pitch*n/2, -pitch*n/2) + lattice.pitch = (pitch, pitch) + lattice.outer = openmc.Universe(cells=[all_zr]) + lattice.universes = [ + [u1, u2, u1], + [u2, u1, u2], + [u2, u1, u1] + ] + + # Add extra attributes for comparison purpose + lattice.cells = [u1.fuel, u1.moderator, u2.fuel, u2.moderator, all_zr] + lattice.mats = [uo2, water, zr] + lattice.univs = [u1, u2, lattice.outer] + return lattice + + +@pytest.fixture(scope='module') +def rlat3(pincell1, pincell2, uo2, water, zr): + """3D Rectangular lattice for testing.""" + + # Create another universe for top layer + hydrogen = openmc.Material() + hydrogen.add_element('H', 1.0) + hydrogen.set_density('g/cm3', 0.09) + h_cell = openmc.Cell(fill=hydrogen) + u3 = openmc.Universe(cells=[h_cell]) + + all_zr = openmc.Cell(fill=zr) + pitch = 1.2 + n = 3 + u1, u2 = pincell1, pincell2 + lattice = openmc.RectLattice() + lattice.lower_left = (-pitch*n/2, -pitch*n/2, -10.0) + lattice.pitch = (pitch, pitch, 10.0) + lattice.outer = openmc.Universe(cells=[all_zr]) + lattice.universes = [ + [[u1, u2, u1], + [u2, u1, u2], + [u2, u1, u1]], + [[u3, u1, u2], + [u1, u3, u2], + [u2, u1, u1]] + ] + + # Add extra attributes for comparison purpose + lattice.cells = [u1.fuel, u1.moderator, u2.fuel, u2.moderator, + h_cell, all_zr] + lattice.mats = [uo2, water, zr, hydrogen] + lattice.univs = [u1, u2, u3, lattice.outer] + return lattice + + +@pytest.fixture(scope='module') +def hlat2(pincell1, pincell2, uo2, water, zr): + """2D Hexagonal lattice for testing.""" + all_zr = openmc.Cell(fill=zr) + pitch = 1.2 + u1, u2 = pincell1, pincell2 + lattice = openmc.HexLattice() + lattice.center = (0., 0.) + lattice.pitch = (pitch,) + lattice.outer = openmc.Universe(cells=[all_zr]) + lattice.universes = [ + [u2, u1, u1, u1, u1, u1, u1, u1, u1, u1, u1, u1], + [u2, u1, u1, u1, u1, u1], + [u2] + ] + + # Add extra attributes for comparison purpose + lattice.cells = [u1.fuel, u1.moderator, u2.fuel, u2.moderator, all_zr] + lattice.mats = [uo2, water, zr] + lattice.univs = [u1, u2, lattice.outer] + return lattice + + +@pytest.fixture(scope='module') +def hlat3(pincell1, pincell2, uo2, water, zr): + """3D Hexagonal lattice for testing.""" + + # Create another universe for top layer + hydrogen = openmc.Material() + hydrogen.add_element('H', 1.0) + hydrogen.set_density('g/cm3', 0.09) + h_cell = openmc.Cell(fill=hydrogen) + u3 = openmc.Universe(cells=[h_cell]) + + all_zr = openmc.Cell(fill=zr) + pitch = 1.2 + u1, u2 = pincell1, pincell2 + lattice = openmc.HexLattice() + lattice.center = (0., 0., 0.) + lattice.pitch = (pitch, 10.0) + lattice.outer = openmc.Universe(cells=[all_zr]) + lattice.universes = [ + [[u2, u1, u1, u1, u1, u1, u1, u1, u1, u1, u1, u1], + [u2, u1, u1, u1, u1, u1], + [u2]], + [[u1, u1, u1, u1, u1, u1, u3, u1, u1, u1, u1, u1], + [u1, u1, u1, u3, u1, u1], + [u3]] + ] + + # Add extra attributes for comparison purpose + lattice.cells = [u1.fuel, u1.moderator, u2.fuel, u2.moderator, + h_cell, all_zr] + lattice.mats = [uo2, water, zr, hydrogen] + lattice.univs = [u1, u2, u3, lattice.outer] + return lattice + + +def test_get_nuclides(rlat2, rlat3, hlat2, hlat3): + for lat in (rlat2, hlat2): + nucs = rlat2.get_nuclides() + assert sorted(nucs) == ['H1', 'O16', 'U235', + 'Zr90', 'Zr91', 'Zr92', 'Zr94', 'Zr96'] + for lat in (rlat3, hlat3): + nucs = rlat3.get_nuclides() + assert sorted(nucs) == ['H1', 'H2', 'O16', 'U235', + 'Zr90', 'Zr91', 'Zr92', 'Zr94', 'Zr96'] + + +def test_get_all_cells(rlat2, rlat3, hlat2, hlat3): + for lat in (rlat2, rlat3, hlat2, hlat3): + cells = set(lat.get_all_cells().values()) + assert not cells ^ set(lat.cells) + + +def test_get_all_materials(rlat2, rlat3, hlat2, hlat3): + for lat in (rlat2, rlat3, hlat2, hlat3): + mats = set(lat.get_all_materials().values()) + assert not mats ^ set(lat.mats) + + +def test_get_all_universes(rlat2, rlat3, hlat2, hlat3): + for lat in (rlat2, rlat3, hlat2, hlat3): + univs = set(lat.get_all_universes().values()) + assert not univs ^ set(lat.univs) + + +def test_get_universe(rlat2, rlat3, hlat2, hlat3): + u1, u2, outer = rlat2.univs + assert rlat2.get_universe((0, 0)) == u2 + assert rlat2.get_universe((1, 0)) == u1 + assert rlat2.get_universe((0, 1)) == u2 + + u1, u2, u3, outer = rlat3.univs + assert rlat3.get_universe((0, 0, 0)) == u2 + assert rlat3.get_universe((2, 2, 0)) == u1 + assert rlat3.get_universe((0, 2, 1)) == u3 + assert rlat3.get_universe((2, 1, 1)) == u2 + + u1, u2, outer = hlat2.univs + assert hlat2.get_universe((0, 0)) == u2 + assert hlat2.get_universe((0, 2)) == u2 + assert hlat2.get_universe((1, 0)) == u1 + assert hlat2.get_universe((-2, 2)) == u1 + + u1, u2, u3, outer = hlat3.univs + assert hlat3.get_universe((0, 0, 0)) == u2 + assert hlat3.get_universe((0, 0, 1)) == u3 + assert hlat3.get_universe((0, 2, 0)) == u2 + assert hlat3.get_universe((0, 2, 1)) == u1 + assert hlat3.get_universe((0, -2, 0)) == u1 + assert hlat3.get_universe((0, -2, 1)) == u3 + + +def test_find(rlat2, rlat3, hlat2, hlat3): + pitch = rlat2.pitch[0] + seq = rlat2.find((0., 0., 0.)) + assert seq[-1] == rlat2.cells[0] + seq = rlat2.find((pitch, 0., 0.)) + assert seq[-1] == rlat2.cells[2] + seq = rlat2.find((0., -pitch, 0.)) + assert seq[-1] == rlat2.cells[0] + seq = rlat2.find((pitch*100, 0., 0.)) + assert seq[-1] == rlat2.cells[-1] + seq = rlat3.find((-pitch, pitch, 5.0)) + assert seq[-1] == rlat3.cells[-2] + + pitch = hlat2.pitch[0] + seq = hlat2.find((0., 0., 0.)) + assert seq[-1] == hlat2.cells[2] + seq = hlat2.find((0.5, 0., 0.)) + assert seq[-1] == hlat2.cells[3] + seq = hlat2.find((sqrt(3)*pitch, 0., 0.)) + assert seq[-1] == hlat2.cells[0] + seq = hlat2.find((0., pitch, 0.)) + assert seq[-1] == hlat2.cells[2] + + # bottom of 3D lattice + seq = hlat3.find((0., 0., -5.)) + assert seq[-1] == hlat3.cells[2] + seq = hlat3.find((0., pitch, -5.)) + assert seq[-1] == hlat3.cells[2] + seq = hlat3.find((0., -pitch, -5.)) + assert seq[-1] == hlat3.cells[0] + seq = hlat3.find((sqrt(3)*pitch, 0., -5.)) + assert seq[-1] == hlat3.cells[0] + + # top of 3D lattice + seq = hlat3.find((0., 0., 5.)) + assert seq[-1] == hlat3.cells[-2] + seq = hlat3.find((0., pitch, 5.)) + assert seq[-1] == hlat3.cells[0] + seq = hlat3.find((0., -pitch, 5.)) + assert seq[-1] == hlat3.cells[-2] + seq = hlat3.find((sqrt(3)*pitch, 0., 5.)) + assert seq[-1] == hlat3.cells[0] + + +def test_clone(rlat2, hlat2, hlat3): + rlat_clone = rlat2.clone() + assert rlat_clone.id != rlat2.id + assert rlat_clone.lower_left == rlat2.lower_left + assert rlat_clone.pitch == rlat2.pitch + + hlat_clone = hlat2.clone() + assert hlat_clone.id != hlat2.id + assert hlat_clone.center == hlat2.center + assert hlat_clone.pitch == hlat2.pitch + + hlat_clone = hlat3.clone() + assert hlat_clone.id != hlat3.id + assert hlat_clone.center == hlat3.center + assert hlat_clone.pitch == hlat3.pitch + + +def test_repr(rlat2, rlat3, hlat2, hlat3): + repr(rlat2) + repr(rlat3) + repr(hlat2) + repr(hlat3) + + +def test_indices_rect(rlat2, rlat3): + # (y, x) indices + assert rlat2.indices == [(0, 0), (0, 1), (0, 2), + (1, 0), (1, 1), (1, 2), + (2, 0), (2, 1), (2, 2)] + # (z, y, x) indices + assert rlat3.indices == [ + (0, 0, 0), (0, 0, 1), (0, 0, 2), + (0, 1, 0), (0, 1, 1), (0, 1, 2), + (0, 2, 0), (0, 2, 1), (0, 2, 2), + (1, 0, 0), (1, 0, 1), (1, 0, 2), + (1, 1, 0), (1, 1, 1), (1, 1, 2), + (1, 2, 0), (1, 2, 1), (1, 2, 2) + ] + + +def test_indices_hex(hlat2, hlat3): + # (r, i) indices + assert hlat2.indices == ( + [(0, i) for i in range(12)] + + [(1, i) for i in range(6)] + + [(2, 0)] + ) + + # (z, r, i) indices + assert hlat3.indices == ( + [(0, 0, i) for i in range(12)] + + [(0, 1, i) for i in range(6)] + + [(0, 2, 0)] + + [(1, 0, i) for i in range(12)] + + [(1, 1, i) for i in range(6)] + + [(1, 2, 0)] + ) + + +def test_xml_rect(rlat2, rlat3): + for lat in (rlat2, rlat3): + geom = ET.Element('geometry') + lat.create_xml_subelement(geom) + elem = geom.find('lattice') + assert elem.tag == 'lattice' + assert elem.get('id') == str(lat.id) + assert len(elem.find('pitch').text.split()) == lat.ndim + assert len(elem.find('lower_left').text.split()) == lat.ndim + assert len(elem.find('universes').text.split()) == len(lat.indices) + + +def test_xml_hex(hlat2, hlat3): + for lat in (hlat2, hlat3): + geom = ET.Element('geometry') + lat.create_xml_subelement(geom) + elem = geom.find('hex_lattice') + assert elem.tag == 'hex_lattice' + assert elem.get('id') == str(lat.id) + assert len(elem.find('center').text.split()) == lat.ndim + assert len(elem.find('pitch').text.split()) == lat.ndim - 1 + assert len(elem.find('universes').text.split()) == len(lat.indices) + + +def test_show_indices(): + for i in range(1, 11): + lines = openmc.HexLattice.show_indices(i).split('\n') + assert len(lines) == 4*i - 3 From 68a01b8fc15e130c842bf7188f94ba946da38bf2 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Feb 2018 06:22:18 -0600 Subject: [PATCH 174/282] Explicitly use int(floor(...)) for Python 2 compatibility --- docs/source/conf.py | 5 +++-- openmc/lattice.py | 12 ++++++------ openmc/mgxs/mgxs.py | 6 +----- openmc/model/triso.py | 12 ++---------- openmc/tallies.py | 2 -- 5 files changed, 12 insertions(+), 25 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 933031590..b97ff782d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -27,8 +27,9 @@ except ImportError: MOCK_MODULES = ['numpy', 'numpy.polynomial', 'numpy.polynomial.polynomial', 'numpy.ctypeslib', 'scipy', 'scipy.sparse', 'scipy.interpolate', 'scipy.integrate', 'scipy.optimize', 'scipy.special', - 'scipy.stats', 'h5py', 'pandas', 'uncertainties', 'matplotlib', - 'matplotlib.pyplot','openmoc', 'openmc.data.reconstruct'] + '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 diff --git a/openmc/lattice.py b/openmc/lattice.py index 730d7dc4a..89cfcfe52 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -607,12 +607,12 @@ class RectLattice(Lattice): element coordinate system """ - ix = floor((point[0] - self.lower_left[0])/self.pitch[0]) - iy = floor((point[1] - self.lower_left[1])/self.pitch[1]) + ix = int(floor((point[0] - self.lower_left[0])/self.pitch[0])) + iy = int(floor((point[1] - self.lower_left[1])/self.pitch[1])) if self.ndim == 2: idx = (ix, iy) else: - iz = floor((point[2] - self.lower_left[2])/self.pitch[2]) + iz = int(floor((point[2] - self.lower_left[2])/self.pitch[2])) idx = (ix, iy, iz) return idx, self.get_local_coordinates(point, idx) @@ -1019,10 +1019,10 @@ class HexLattice(Lattice): iz = 1 else: z = point[2] - self.center[2] - iz = floor(z/self.pitch[1] + 0.5*self.num_axial) + iz = int(floor(z/self.pitch[1] + 0.5*self.num_axial)) alpha = y - x/sqrt(3.) - ix = floor(x/(sqrt(0.75) * self.pitch[0])) - ia = floor(alpha/self.pitch[0]) + ix = int(floor(x/(sqrt(0.75) * self.pitch[0]))) + ia = int(floor(alpha/self.pitch[0])) # Check four lattice elements to see which one is closest based on local # coordinates diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index a557e51cc..0c0b15ad9 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -10,6 +10,7 @@ import itertools from six import add_metaclass, string_types import numpy as np +import h5py import openmc import openmc.checkvalue as cv @@ -1682,13 +1683,8 @@ class MGXS(object): ValueError When this method is called before the multi-group cross section is computed from tally data. - ImportError - When h5py is not installed. """ - - import h5py - # Make directory if it does not exist if not os.path.exists(directory): os.makedirs(directory) diff --git a/openmc/model/triso.py b/openmc/model/triso.py index 67bf3bc44..7258a86f2 100644 --- a/openmc/model/triso.py +++ b/openmc/model/triso.py @@ -12,11 +12,7 @@ from abc import ABCMeta, abstractproperty, abstractmethod from six import add_metaclass import numpy as np -try: - import scipy.spatial - _SCIPY_AVAILABLE = True -except ImportError: - _SCIPY_AVAILABLE = False +import scipy.spatial import openmc import openmc.checkvalue as cv @@ -742,7 +738,7 @@ def _close_random_pack(domain, particles, contraction_rate): outer_pf = (4/3 * pi * (outer_diameter[0]/2)**3 * n_particles / domain.volume) - j = floor(-log10(outer_pf - inner_pf)) + j = int(floor(-log10(outer_pf - inner_pf))) outer_diameter[0] = (outer_diameter[0] - 0.5**j * contraction_rate * initial_outer_diameter / n_particles) @@ -837,10 +833,6 @@ def _close_random_pack(domain, particles, contraction_rate): if rods: inner_diameter[0] = rods[0][0] - if not _SCIPY_AVAILABLE: - raise ImportError('SciPy must be installed to perform ' - 'close random packing.') - n_particles = len(particles) diameter = 2*domain.particle_radius diff --git a/openmc/tallies.py b/openmc/tallies.py index 235ad2472..b685dcae3 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1508,8 +1508,6 @@ class Tally(IDManagerMixin): ------ KeyError When this method is called before the Tally is populated with data - ImportError - When Pandas can not be found on the caller's system """ From d45856157626ef8c21957991baafbcec933e5827 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Feb 2018 07:09:51 -0600 Subject: [PATCH 175/282] Upload test coverage results to coveralls --- .travis.yml | 6 ++---- tools/ci/travis-install.sh | 3 +++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9094637c8..8a745c5d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,17 +30,15 @@ env: - OMP=y MPI=n PHDF5=n - OMP=n MPI=y PHDF5=n - OMP=n MPI=y PHDF5=y - before_install: - sudo add-apt-repository ppa:nschloe/hdf5-backports -y - sudo apt-get update -q - sudo apt-get install libhdf5-serial-dev libhdf5-mpich-dev -y - install: - ./tools/ci/travis-install.sh - before_script: - ./tools/ci/travis-before-script.sh - script: - ./tools/ci/travis-script.sh +after_success: + - coveralls -d tests/.coverage diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index cef34a2e0..58bcfb789 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -25,3 +25,6 @@ python tools/ci/travis-install.py # Install Python API in editable mode pip install -e .[test] + +# For uploading to coveralls +pip install python-coveralls From 6044edc24a5dd39e3b71d1a7e67a30762c2619e0 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Feb 2018 09:24:41 -0600 Subject: [PATCH 176/282] Add simple test for openmc.Settings --- .gitignore | 2 ++ readme.rst | 6 +++- tests/unit_tests/test_settings.py | 54 +++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/test_settings.py diff --git a/.gitignore b/.gitignore index 35f45e747..8c7cc3e36 100644 --- a/.gitignore +++ b/.gitignore @@ -101,3 +101,5 @@ examples/jupyter/plots .cache/ .tox/ .python-version +.coverage +htmlcov \ No newline at end of file diff --git a/readme.rst b/readme.rst index b7366fe3e..32cbb34e1 100644 --- a/readme.rst +++ b/readme.rst @@ -2,7 +2,7 @@ OpenMC Monte Carlo Particle Transport Code ========================================== -|licensebadge| |travisbadge| +|licensebadge| |travisbadge| |coverallsbadge| The OpenMC project aims to provide a fully-featured Monte Carlo particle transport code based on modern methods. It is a constructive solid geometry, @@ -74,3 +74,7 @@ OpenMC is distributed under the MIT/X license_. .. |travisbadge| image:: https://travis-ci.org/mit-crpg/openmc.svg?branch=develop :target: https://travis-ci.org/mit-crpg/openmc :alt: Travis CI build status (Linux) + +.. |coverallsbadge| image:: https://coveralls.io/repos/github/mit-crpg/openmc/badge.svg?branch=develop + :target: https://coveralls.io/github/mit-crpg/openmc?branch=develop + :alt: Code Coverage diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py new file mode 100644 index 000000000..f72840be2 --- /dev/null +++ b/tests/unit_tests/test_settings.py @@ -0,0 +1,54 @@ +import openmc +import openmc.stats + + +def test_export_to_xml(run_in_tmpdir): + s = openmc.Settings() + s.run_mode = 'fixed source' + s.batches = 1000 + s.generations_per_batch = 10 + s.inactive = 100 + s.particles = 1000000 + s.keff_trigger = {'type': 'std_dev', 'threshold': 0.001} + s.energy_mode = 'continuous-energy' + s.max_order = 5 + s.source = openmc.Source(space=openmc.stats.Point()) + s.output = {'summary': True, 'tallies': False, 'path': 'here'} + s.verbosity = 7 + s.sourcepoint = {'batches': [50, 150, 500, 1000], 'separate': True, + 'write': True, 'overwrite': True} + s.statepoint = {'batches': [50, 150, 500, 1000]} + s.confidence_intervals = True + s.cross_sections = '/path/to/cross_sections.xml' + s.multipole_library = '/path/to/wmp/' + s.ptables = True + s.run_cmfd = False + s.seed = 17 + s.survival_biasing = True + s.cutoff = {'weight': 0.25, 'weight_avg': 0.5, 'energy': 1.0e-5} + mesh = openmc.Mesh() + mesh.lower_left = (-10., -10., -10.) + mesh.upper_right = (10., 10., 10.) + mesh.dimension = (5, 5, 5) + s.entropy_mesh = mesh + s.trigger_active = True + s.trigger_max_batches = 10000 + s.trigger_batch_interval = 50 + s.no_reduce = False + s.tabular_legendre = {'enable': True, 'num_points': 50} + s.temperature = {'default': 293.6, 'method': 'interpolation', + 'multipole': True, 'range': (200., 1000.)} + s.threads = 8 + s.trace = (10, 1, 20) + s.track = [1, 1, 1, 2, 1, 1] + s.ufs_mesh = mesh + s.resonance_scattering = {'enable': True, 'method': 'ares', + 'energy_min': 1.0, 'energy_max': 1000.0, + 'nuclides': ['U235', 'U238', 'Pu239']} + s.volume_calculations = openmc.VolumeCalculation( + domains=[openmc.Cell()], samples=1000, lower_left=(-10., -10., -10.), + upper_right = (10., 10., 10.)) + s.create_fission_neutrons = True + + # Make sure exporting XML works + s.export_to_xml() From bc4c7d9372c3c4cc33f6606c972423ed0aaddb4f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Feb 2018 09:30:49 -0600 Subject: [PATCH 177/282] Add option in openmc.Settings for log_grid_bins (and remove DD) --- openmc/settings.py | 158 ++++-------------------------- tests/unit_tests/test_settings.py | 1 + 2 files changed, 20 insertions(+), 139 deletions(-) diff --git a/openmc/settings.py b/openmc/settings.py index 9ead82ec6..3c46d0c2c 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -60,6 +60,8 @@ class Settings(object): type are 'variance', 'std_dev', and 'rel_err'. The threshold value should be a float indicating the variance, standard deviation, or relative error used. + log_grid_bins : int + Number of bins for logarithmic energy grid search max_order : None or int Maximum scattering order to apply globally when in multi-group mode. multipole_library : str @@ -220,19 +222,12 @@ class Settings(object): # Uniform fission source subelement self._ufs_mesh = None - # Domain decomposition subelement - self._dd_mesh_dimension = None - self._dd_mesh_lower_left = None - self._dd_mesh_upper_right = None - self._dd_nodemap = None - self._dd_allow_leakage = False - self._dd_count_interactions = False - self._resonance_scattering = {} self._volume_calculations = cv.CheckedList( VolumeCalculation, 'volume calculations') self._create_fission_neutrons = None + self._log_grid_bins = None @property def run_mode(self): @@ -362,30 +357,6 @@ class Settings(object): def ufs_mesh(self): return self._ufs_mesh - @property - def dd_mesh_dimension(self): - return self._dd_mesh_dimension - - @property - def dd_mesh_lower_left(self): - return self._dd_mesh_lower_left - - @property - def dd_mesh_upper_right(self): - return self._dd_mesh_upper_right - - @property - def dd_nodemap(self): - return self._dd_nodemap - - @property - def dd_allow_leakage(self): - return self._dd_allow_leakage - - @property - def dd_count_interactions(self): - return self._dd_count_interactions - @property def resonance_scattering(self): return self._resonance_scattering @@ -398,6 +369,10 @@ class Settings(object): def create_fission_neutrons(self): return self._create_fission_neutrons + @property + def log_grid_bins(self): + return self._log_grid_bins + @run_mode.setter def run_mode(self, run_mode): cv.check_value('run mode', run_mode, _RUN_MODES) @@ -696,85 +671,6 @@ class Settings(object): cv.check_length('UFS mesh upper-right corner', ufs_mesh.upper_right, 3) self._ufs_mesh = ufs_mesh - @dd_mesh_dimension.setter - def dd_mesh_dimension(self, dimension): - # TODO: remove this when domain decomposition is merged - warnings.warn('This feature is not yet implemented in a release ' - 'version of openmc') - - cv.check_type('DD mesh dimension', dimension, Iterable, Integral) - cv.check_length('DD mesh dimension', dimension, 3) - - self._dd_mesh_dimension = dimension - - @dd_mesh_lower_left.setter - def dd_mesh_lower_left(self, lower_left): - # TODO: remove this when domain decomposition is merged - warnings.warn('This feature is not yet implemented in a release ' - 'version of openmc') - - cv.check_type('DD mesh lower left corner', lower_left, Iterable, Real) - cv.check_length('DD mesh lower left corner', lower_left, 3) - - self._dd_mesh_lower_left = lower_left - - @dd_mesh_upper_right.setter - def dd_mesh_upper_right(self, upper_right): - # TODO: remove this when domain decomposition is merged - warnings.warn('This feature is not yet implemented in a release ' - 'version of openmc') - - cv.check_type('DD mesh upper right corner', upper_right, Iterable, Real) - cv.check_length('DD mesh upper right corner', upper_right, 3) - - self._dd_mesh_upper_right = upper_right - - @dd_nodemap.setter - def dd_nodemap(self, nodemap): - # TODO: remove this when domain decomposition is merged - warnings.warn('This feature is not yet implemented in a release ' - 'version of openmc') - - cv.check_type('DD nodemap', nodemap, Iterable) - - nodemap = np.array(nodemap).flatten() - - if self._dd_mesh_dimension is None: - msg = 'Must set DD mesh dimension before setting the nodemap' - raise ValueError(msg) - else: - len_nodemap = np.prod(self._dd_mesh_dimension) - - if len(nodemap) < len_nodemap or len(nodemap) > len_nodemap: - msg = 'Unable to set DD nodemap with length "{0}" which ' \ - 'does not have the same dimensionality as the domain ' \ - 'mesh'.format(len(nodemap)) - raise ValueError(msg) - - self._dd_nodemap = nodemap - - @dd_allow_leakage.setter - def dd_allow_leakage(self, allow): - - # TODO: remove this when domain decomposition is merged - warnings.warn('This feature is not yet implemented in a release ' - 'version of openmc') - - cv.check_type('DD allow leakage', allow, bool) - - self._dd_allow_leakage = allow - - @dd_count_interactions.setter - def dd_count_interactions(self, interactions): - - # TODO: remove this when domain decomposition is merged - warnings.warn('This feature is not yet implemented in a release ' - 'version of openmc') - - cv.check_type('DD count interactions', interactions, bool) - - self._dd_count_interactions = interactions - @resonance_scattering.setter def resonance_scattering(self, res): cv.check_type('resonance scattering settings', res, Mapping) @@ -812,6 +708,12 @@ class Settings(object): create_fission_neutrons, bool) self._create_fission_neutrons = create_fission_neutrons + @log_grid_bins.setter + def log_grid_bins(self, log_grid_bins): + cv.check_type('log grid bins', log_grid_bins, Real) + cv.check_greater_than('log grid bins', log_grid_bins, 0) + self._log_grid_bins = log_grid_bins + def _create_run_mode_subelement(self, root): elem = ET.SubElement(root, "run_mode") elem.text = self._run_mode @@ -1033,33 +935,6 @@ class Settings(object): subelement = ET.SubElement(root, "ufs_mesh") subelement.text = str(self.ufs_mesh.id) - def _create_dd_subelement(self, root): - if self._dd_mesh_lower_left is not None and \ - self._dd_mesh_upper_right is not None and \ - self._dd_mesh_dimension is not None: - - element = ET.SubElement(root, "domain_decomposition") - - subelement = ET.SubElement(element, "mesh") - subsubelement = ET.SubElement(subelement, "dimension") - subsubelement.text = ' '.join(map(str, self._dd_mesh_dimension)) - - subsubelement = ET.SubElement(subelement, "lower_left") - subsubelement.text = ' '.join(map(str, self._dd_mesh_lower_left)) - - subsubelement = ET.SubElement(subelement, "upper_right") - subsubelement.text = ' '.join(map(str, self._dd_mesh_upper_right)) - - if self._dd_nodemap is not None: - subelement = ET.SubElement(element, "nodemap") - subelement.text = ' '.join(map(str, self._dd_nodemap)) - - subelement = ET.SubElement(element, "allow_leakage") - subelement.text = str(self._dd_allow_leakage).lower() - - subelement = ET.SubElement(element, "count_interactions") - subelement.text = str(self._dd_count_interactions).lower() - def _create_resonance_scattering_subelement(self, root): res = self.resonance_scattering if res: @@ -1085,6 +960,11 @@ class Settings(object): elem = ET.SubElement(root, "create_fission_neutrons") elem.text = str(self._create_fission_neutrons).lower() + def _create_log_grid_bins_subelement(self, root): + if self._log_grid_bins is not None: + elem = ET.SubElement(root, "log_grid_bins") + elem.text = str(self._log_grid_bins) + def export_to_xml(self, path='settings.xml'): """Export simulation settings to an XML file. @@ -1128,10 +1008,10 @@ class Settings(object): self._create_trace_subelement(root_element) self._create_track_subelement(root_element) self._create_ufs_mesh_subelement(root_element) - self._create_dd_subelement(root_element) self._create_resonance_scattering_subelement(root_element) self._create_volume_calcs_subelement(root_element) self._create_create_fission_neutrons_subelement(root_element) + self._create_log_grid_bins_subelement(root_element) # Clean the indentation in the file to be user-readable clean_xml_indentation(root_element) diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index f72840be2..e3bfc697e 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -49,6 +49,7 @@ def test_export_to_xml(run_in_tmpdir): domains=[openmc.Cell()], samples=1000, lower_left=(-10., -10., -10.), upper_right = (10., 10., 10.)) s.create_fission_neutrons = True + s.log_grid_bins = 2000 # Make sure exporting XML works s.export_to_xml() From c710184fba7b2eb1aa8063f082013b04dc7cd6f7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Feb 2018 09:46:16 -0600 Subject: [PATCH 178/282] Run tests from root directory --- .travis.yml | 2 +- tools/ci/travis-script.sh | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8a745c5d6..05e239025 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,4 +41,4 @@ before_script: script: - ./tools/ci/travis-script.sh after_success: - - coveralls -d tests/.coverage + - coveralls diff --git a/tools/ci/travis-script.sh b/tools/ci/travis-script.sh index 468cd0cc8..ee445b517 100755 --- a/tools/ci/travis-script.sh +++ b/tools/ci/travis-script.sh @@ -2,14 +2,13 @@ set -ex # Run source check -cd tests if [[ $TRAVIS_PYTHON_VERSION == "3.4" && $OMP == 'n' && $MPI == 'n' ]]; then - ./check_source.py + pushd tests && python check_source.py && popd fi # Run regression and unit tests if [[ $MPI == 'y' ]]; then - pytest --cov=../openmc -v --mpi + pytest --cov=openmc -v --mpi tests else - pytest --cov=../openmc -v + pytest --cov=openmc -v tests fi From 36244692fd0e4d40b0d55a7cb90f6e6e68514e22 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 5 Feb 2018 08:09:47 -0600 Subject: [PATCH 179/282] Use mpicc and mpicxx for MPI configurations --- docs/source/usersguide/install.rst | 13 +++++++------ tools/ci/travis-install.py | 4 +++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 48188928c..638d42501 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -263,14 +263,15 @@ should be used: Compiling with MPI ++++++++++++++++++ -To compile with MPI, set the :envvar:`FC` and :envvar:`CC` environment variables -to the path to the MPI Fortran and C wrappers, respectively. For example, in a -bash shell: +To compile with MPI, set the :envvar:`FC`, :envvar:`CC`, and :envvar:`CXX` +environment variables to the path to the MPI Fortran, C, and C++ wrappers, +respectively. For example, in a bash shell: .. code-block:: sh - export FC=mpif90 + export FC=mpifort export CC=mpicc + export CXX=mpicxx cmake /path/to/openmc Note that in many shells, environment variables can be set for a single command, @@ -278,7 +279,7 @@ i.e. .. code-block:: sh - FC=mpif90 CC=mpicc cmake /path/to/openmc + FC=mpifort CC=mpicc CXX=mpicxx cmake /path/to/openmc Selecting HDF5 Installation +++++++++++++++++++++++++++ @@ -354,7 +355,7 @@ follows: .. code-block:: sh mkdir build && cd build - FC=ifort CC=icc FFLAGS=-mmic cmake -Dopenmp=on .. + FC=ifort CC=icc CXX=icpc FFLAGS=-mmic cmake -Dopenmp=on .. make Note that unless an HDF5 build for the Intel Xeon Phi (Knights Corner) is diff --git a/tools/ci/travis-install.py b/tools/ci/travis-install.py index bd7f886db..15e4d576b 100644 --- a/tools/ci/travis-install.py +++ b/tools/ci/travis-install.py @@ -33,9 +33,11 @@ def install(omp=False, mpi=False, phdf5=False): if not omp: cmake_cmd.append('-Dopenmp=off') - # For MPI, we just need to change the Fortran compiler + # Use MPI wrappers when building in parallel if mpi: os.environ['FC'] = 'mpifort' if which('mpifort') else 'mpif90' + os.environ['CC'] = 'mpicc' + os.environ['CXX'] = 'mpicxx' # Tell CMake to prefer parallel HDF5 if specified if phdf5: From c428cee667fcc7341c3cb3eca5798b03d50d13f4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 24 Dec 2017 16:06:05 +0700 Subject: [PATCH 180/282] Remove __future__ and six imports --- openmc/arithmetic.py | 22 +++++----- openmc/cell.py | 3 +- openmc/cmfd.py | 4 +- openmc/data/ace.py | 4 +- openmc/data/angle_energy.py | 5 +-- openmc/data/decay.py | 5 +-- openmc/data/endf.py | 5 +-- openmc/data/energy_distribution.py | 4 +- openmc/data/function.py | 4 +- openmc/data/library.py | 3 +- openmc/data/multipole.py | 3 +- openmc/data/neutron.py | 6 +-- openmc/data/njoy.py | 1 - openmc/data/product.py | 3 +- openmc/data/reaction.py | 4 +- openmc/element.py | 4 +- openmc/executor.py | 5 +-- openmc/filter.py | 5 +-- openmc/geometry.py | 4 +- openmc/lattice.py | 8 +--- openmc/macroscopic.py | 4 +- openmc/material.py | 23 +++++------ openmc/mesh.py | 5 +-- openmc/mgxs/library.py | 23 +++++------ openmc/mgxs/mdgxs.py | 49 ++++++++++------------ openmc/mgxs/mgxs.py | 65 ++++++++++++++---------------- openmc/mgxs_library.py | 5 +-- openmc/model/funcs.py | 1 - openmc/model/triso.py | 5 +-- openmc/nuclide.py | 2 - openmc/plots.py | 21 +++++----- openmc/plotter.py | 7 ++-- openmc/region.py | 4 +- openmc/settings.py | 9 ++--- openmc/source.py | 4 +- openmc/stats/multivariate.py | 7 +--- openmc/stats/univariate.py | 4 +- openmc/surface.py | 12 ++---- openmc/tallies.py | 22 +++++----- openmc/tally_derivative.py | 8 +--- openmc/trigger.py | 4 +- openmc/universe.py | 6 +-- scripts/openmc-ace-to-hdf5 | 2 +- scripts/openmc-convert-mcnp70-data | 3 +- scripts/openmc-convert-mcnp71-data | 3 +- scripts/openmc-get-jeff-data | 7 +--- scripts/openmc-get-multipole-data | 7 +--- scripts/openmc-get-nndc-data | 5 +-- scripts/openmc-plot-mesh-tally | 12 +++--- scripts/openmc-track-to-vtk | 2 +- scripts/openmc-update-inputs | 4 +- scripts/openmc-update-mgxs | 3 +- scripts/openmc-validate-xml | 4 +- scripts/openmc-voxel-to-silovtk | 3 +- setup.py | 6 +-- 55 files changed, 171 insertions(+), 282 deletions(-) diff --git a/openmc/arithmetic.py b/openmc/arithmetic.py index fe002f1e3..4a5e7486a 100644 --- a/openmc/arithmetic.py +++ b/openmc/arithmetic.py @@ -2,7 +2,6 @@ import sys import copy from collections import Iterable -from six import string_types import numpy as np import pandas as pd @@ -86,18 +85,18 @@ class CrossScore(object): @left_score.setter def left_score(self, left_score): cv.check_type('left_score', left_score, - string_types + (CrossScore, AggregateScore)) + (str, CrossScore, AggregateScore)) self._left_score = left_score @right_score.setter def right_score(self, right_score): cv.check_type('right_score', right_score, - string_types + (CrossScore, AggregateScore)) + (str, CrossScore, AggregateScore)) self._right_score = right_score @binary_op.setter def binary_op(self, binary_op): - cv.check_type('binary_op', binary_op, string_types) + cv.check_type('binary_op', binary_op, str) cv.check_value('binary_op', binary_op, _TALLY_ARITHMETIC_OPS) self._binary_op = binary_op @@ -202,7 +201,7 @@ class CrossNuclide(object): @binary_op.setter def binary_op(self, binary_op): - cv.check_type('binary_op', binary_op, string_types) + cv.check_type('binary_op', binary_op, str) cv.check_value('binary_op', binary_op, _TALLY_ARITHMETIC_OPS) self._binary_op = binary_op @@ -335,7 +334,7 @@ class CrossFilter(object): @binary_op.setter def binary_op(self, binary_op): - cv.check_type('binary_op', binary_op, string_types) + cv.check_type('binary_op', binary_op, str) cv.check_value('binary_op', binary_op, _TALLY_ARITHMETIC_OPS) self._binary_op = binary_op @@ -482,12 +481,12 @@ class AggregateScore(object): @scores.setter def scores(self, scores): - cv.check_iterable_type('scores', scores, string_types) + cv.check_iterable_type('scores', scores, str) self._scores = scores @aggregate_op.setter def aggregate_op(self, aggregate_op): - cv.check_type('aggregate_op', aggregate_op, string_types +(CrossScore,)) + cv.check_type('aggregate_op', aggregate_op, (str, CrossScore)) cv.check_value('aggregate_op', aggregate_op, _TALLY_AGGREGATE_OPS) self._aggregate_op = aggregate_op @@ -561,13 +560,12 @@ class AggregateNuclide(object): @nuclides.setter def nuclides(self, nuclides): - cv.check_iterable_type('nuclides', nuclides, - string_types + (openmc.Nuclide, CrossNuclide)) + cv.check_iterable_type('nuclides', nuclides, (str, CrossNuclide)) self._nuclides = nuclides @aggregate_op.setter def aggregate_op(self, aggregate_op): - cv.check_type('aggregate_op', aggregate_op, string_types) + cv.check_type('aggregate_op', aggregate_op, str) cv.check_value('aggregate_op', aggregate_op, _TALLY_AGGREGATE_OPS) self._aggregate_op = aggregate_op @@ -690,7 +688,7 @@ class AggregateFilter(object): @aggregate_op.setter def aggregate_op(self, aggregate_op): - cv.check_type('aggregate_op', aggregate_op, string_types) + cv.check_type('aggregate_op', aggregate_op, str) cv.check_value('aggregate_op', aggregate_op, _TALLY_AGGREGATE_OPS) self._aggregate_op = aggregate_op diff --git a/openmc/cell.py b/openmc/cell.py index 074fadc06..087f2816d 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -6,7 +6,6 @@ from xml.etree import ElementTree as ET import sys import warnings -from six import string_types import numpy as np import openmc @@ -203,7 +202,7 @@ class Cell(IDManagerMixin): @name.setter def name(self, name): if name is not None: - cv.check_type('cell name', name, string_types) + cv.check_type('cell name', name, str) self._name = name else: self._name = '' diff --git a/openmc/cmfd.py b/openmc/cmfd.py index c3df3f104..236491923 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -15,8 +15,6 @@ from numbers import Real, Integral from xml.etree import ElementTree as ET import sys -from six import string_types - from openmc.clean_xml import clean_xml_indentation from openmc.checkvalue import (check_type, check_length, check_value, check_greater_than, check_less_than) @@ -338,7 +336,7 @@ class CMFD(object): @display.setter def display(self, display): - check_type('CMFD display', display, string_types) + check_type('CMFD display', display, str) check_value('CMFD display', display, ['balance', 'dominance', 'entropy', 'source']) self._display = display diff --git a/openmc/data/ace.py b/openmc/data/ace.py index 9827df5b9..385408bd4 100644 --- a/openmc/data/ace.py +++ b/openmc/data/ace.py @@ -15,12 +15,10 @@ generates ACE-format cross sections. """ -from __future__ import division, unicode_literals from os import SEEK_CUR import struct import sys -from six import string_types import numpy as np from openmc.mixin import EqualityMixin @@ -153,7 +151,7 @@ class Library(EqualityMixin): """ def __init__(self, filename, table_names=None, verbose=False): - if isinstance(table_names, string_types): + if isinstance(table_names, str): table_names = [table_names] if table_names is not None: table_names = set(table_names) diff --git a/openmc/data/angle_energy.py b/openmc/data/angle_energy.py index 8bf95152a..d67cc6b26 100644 --- a/openmc/data/angle_energy.py +++ b/openmc/data/angle_energy.py @@ -1,14 +1,11 @@ from abc import ABCMeta, abstractmethod from io import StringIO -from six import add_metaclass - import openmc.data from openmc.mixin import EqualityMixin -@add_metaclass(ABCMeta) -class AngleEnergy(EqualityMixin): +class AngleEnergy(EqualityMixin, metaclass=ABCMeta): """Distribution in angle and energy of a secondary particle.""" @abstractmethod def to_hdf5(self, group): diff --git a/openmc/data/decay.py b/openmc/data/decay.py index 4327b2fc3..3d365b6c7 100644 --- a/openmc/data/decay.py +++ b/openmc/data/decay.py @@ -5,7 +5,6 @@ from numbers import Real import re from warnings import warn -from six import string_types import numpy as np try: from uncertainties import ufloat, unumpy, UFloat @@ -278,12 +277,12 @@ class DecayMode(EqualityMixin): @modes.setter def modes(self, modes): - cv.check_type('decay modes', modes, Iterable, string_types) + cv.check_type('decay modes', modes, Iterable, str) self._modes = modes @parent.setter def parent(self, parent): - cv.check_type('parent nuclide', parent, string_types) + cv.check_type('parent nuclide', parent, str) self._parent = parent diff --git a/openmc/data/endf.py b/openmc/data/endf.py index 0fa75ded8..882874b59 100644 --- a/openmc/data/endf.py +++ b/openmc/data/endf.py @@ -6,15 +6,12 @@ Data File ENDF-6". The latest version from June 2009 can be found at http://www-nds.iaea.org/ndspub/documents/endf/endf102/endf102.pdf """ -from __future__ import print_function, division, unicode_literals - import io import re import os from math import pi from collections import OrderedDict, Iterable -from six import string_types import numpy as np from numpy.polynomial.polynomial import Polynomial @@ -301,7 +298,7 @@ class Evaluation(object): """ def __init__(self, filename_or_obj): - if isinstance(filename_or_obj, string_types): + if isinstance(filename_or_obj, str): fh = open(filename_or_obj, 'r') else: fh = filename_or_obj diff --git a/openmc/data/energy_distribution.py b/openmc/data/energy_distribution.py index c3740beaf..e8c92801c 100644 --- a/openmc/data/energy_distribution.py +++ b/openmc/data/energy_distribution.py @@ -3,7 +3,6 @@ from collections import Iterable from numbers import Integral, Real from warnings import warn -from six import add_metaclass import numpy as np from .function import Tabulated1D, INTERPOLATION_SCHEME @@ -14,8 +13,7 @@ from .data import EV_PER_MEV from .endf import get_tab1_record, get_tab2_record -@add_metaclass(ABCMeta) -class EnergyDistribution(EqualityMixin): +class EnergyDistribution(EqualityMixin, metaclass=ABCMeta): """Abstract superclass for all energy distributions.""" def __init__(self): pass diff --git a/openmc/data/function.py b/openmc/data/function.py index 0515a57c0..2d3a8ce9c 100644 --- a/openmc/data/function.py +++ b/openmc/data/function.py @@ -2,7 +2,6 @@ from abc import ABCMeta, abstractmethod from collections import Iterable, Callable from numbers import Real, Integral -from six import add_metaclass import numpy as np import openmc.data @@ -14,8 +13,7 @@ INTERPOLATION_SCHEME = {1: 'histogram', 2: 'linear-linear', 3: 'linear-log', 4: 'log-linear', 5: 'log-log'} -@add_metaclass(ABCMeta) -class Function1D(EqualityMixin): +class Function1D(EqualityMixin, metaclass=ABCMeta): """A function of one independent variable with HDF5 support.""" @abstractmethod def __call__(self): pass diff --git a/openmc/data/library.py b/openmc/data/library.py index c179f78f8..34cd380a5 100644 --- a/openmc/data/library.py +++ b/openmc/data/library.py @@ -1,6 +1,5 @@ import os import xml.etree.ElementTree as ET -from six import string_types import h5py @@ -125,7 +124,7 @@ class DataLibrary(EqualityMixin): raise ValueError("Either path or OPENMC_CROSS_SECTIONS " "environmental variable must be set") - check_type('path', path, string_types) + check_type('path', path, str) tree = ET.parse(path) root = tree.getroot() diff --git a/openmc/data/multipole.py b/openmc/data/multipole.py index 4e77e16ea..33078f504 100644 --- a/openmc/data/multipole.py +++ b/openmc/data/multipole.py @@ -3,7 +3,6 @@ from math import exp, erf, pi, sqrt import h5py import numpy as np -from six import string_types from . import WMP_VERSION from .data import K_BOLTZMANN @@ -300,7 +299,7 @@ class WindowedMultipole(EqualityMixin): @formalism.setter def formalism(self, formalism): if formalism is not None: - cv.check_type('formalism', formalism, string_types) + cv.check_type('formalism', formalism, str) cv.check_value('formalism', formalism, ('MLBW', 'RM')) self._formalism = formalism diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 7dcac5d6e..8d5931689 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -1,4 +1,3 @@ -from __future__ import division, unicode_literals import sys from collections import OrderedDict, Iterable, Mapping, MutableMapping from io import StringIO @@ -10,7 +9,6 @@ import shutil import tempfile from warnings import warn -from six import string_types import numpy as np import h5py @@ -245,7 +243,7 @@ class IncidentNeutron(EqualityMixin): @name.setter def name(self, name): - cv.check_type('name', name, string_types) + cv.check_type('name', name, str) self._name = name @property @@ -301,7 +299,7 @@ class IncidentNeutron(EqualityMixin): def urr(self, urr): cv.check_type('probability table dictionary', urr, MutableMapping) for key, value in urr: - cv.check_type('probability table temperature', key, string_types) + cv.check_type('probability table temperature', key, str) cv.check_type('probability tables', value, ProbabilityTables) self._urr = urr diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index b08b3bdfb..be16d96f8 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -1,4 +1,3 @@ -from __future__ import print_function import argparse from collections import namedtuple from io import StringIO diff --git a/openmc/data/product.py b/openmc/data/product.py index bcffec0da..b7d89ba0e 100644 --- a/openmc/data/product.py +++ b/openmc/data/product.py @@ -3,7 +3,6 @@ from io import StringIO from numbers import Real import sys -from six import string_types import numpy as np import openmc.checkvalue as cv @@ -113,7 +112,7 @@ class Product(EqualityMixin): @particle.setter def particle(self, particle): - cv.check_type('product particle type', particle, string_types) + cv.check_type('product particle type', particle, str) self._particle = particle @yield_.setter diff --git a/openmc/data/reaction.py b/openmc/data/reaction.py index ac3a049ab..737885f35 100644 --- a/openmc/data/reaction.py +++ b/openmc/data/reaction.py @@ -1,11 +1,9 @@ -from __future__ import division, unicode_literals from collections import Iterable, Callable, MutableMapping from copy import deepcopy from numbers import Real, Integral from warnings import warn from io import StringIO -from six import string_types import numpy as np import openmc.checkvalue as cv @@ -863,7 +861,7 @@ class Reaction(EqualityMixin): def xs(self, xs): cv.check_type('reaction cross section dictionary', xs, MutableMapping) for key, value in xs.items(): - cv.check_type('reaction cross section temperature', key, string_types) + cv.check_type('reaction cross section temperature', key, str) cv.check_type('reaction cross section', value, Callable) self._xs = xs diff --git a/openmc/element.py b/openmc/element.py index 5ba19c63c..25cb97538 100644 --- a/openmc/element.py +++ b/openmc/element.py @@ -1,8 +1,6 @@ from collections import OrderedDict import re import os - -from six import string_types from xml.etree import ElementTree as ET import openmc @@ -29,7 +27,7 @@ class Element(str): """ def __new__(cls, name): - cv.check_type('element name', name, string_types) + cv.check_type('element name', name, str) cv.check_length('element name', name, 1, 2) return super(Element, cls).__new__(cls, name) diff --git a/openmc/executor.py b/openmc/executor.py index ada662a12..e3768f4c6 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -1,10 +1,7 @@ -from __future__ import print_function from collections import Iterable import subprocess from numbers import Integral -from six import string_types - import openmc from openmc import VolumeCalculation @@ -203,7 +200,7 @@ def run(particles=None, threads=None, geometry_debug=False, if geometry_debug: args.append('-g') - if isinstance(restart_file, string_types): + if isinstance(restart_file, str): args += ['-r', restart_file] if tracks: diff --git a/openmc/filter.py b/openmc/filter.py index 21bb64f8d..8f57a8090 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -1,4 +1,3 @@ -from __future__ import division from abc import ABCMeta from collections import Iterable, OrderedDict import copy @@ -8,7 +7,6 @@ from numbers import Real, Integral import operator from xml.etree import ElementTree as ET -from six import add_metaclass import numpy as np import pandas as pd @@ -70,8 +68,7 @@ class FilterMeta(ABCMeta): **kwargs) -@add_metaclass(FilterMeta) -class Filter(IDManagerMixin): +class Filter(IDManagerMixin, metaclass=FilterMeta): """Tally modifier that describes phase-space and other characteristics. Parameters diff --git a/openmc/geometry.py b/openmc/geometry.py index ac068e2b0..7e1427269 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -2,8 +2,6 @@ from collections import OrderedDict, Iterable from copy import deepcopy from xml.etree import ElementTree as ET -from six import string_types - import openmc from openmc.clean_xml import clean_xml_indentation from openmc.checkvalue import check_type @@ -139,7 +137,7 @@ class Geometry(object): """ # Make sure we are working with an iterable return_list = (isinstance(paths, Iterable) and - not isinstance(paths, string_types)) + not isinstance(paths, str)) path_list = paths if return_list else [paths] indices = [] diff --git a/openmc/lattice.py b/openmc/lattice.py index 89cfcfe52..1bb82f628 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -1,5 +1,3 @@ -from __future__ import division - from abc import ABCMeta from collections import OrderedDict, Iterable from copy import deepcopy @@ -7,7 +5,6 @@ from math import sqrt, floor from numbers import Real, Integral from xml.etree import ElementTree as ET -from six import add_metaclass, string_types import numpy as np import openmc.checkvalue as cv @@ -15,8 +12,7 @@ import openmc from openmc.mixin import IDManagerMixin -@add_metaclass(ABCMeta) -class Lattice(IDManagerMixin): +class Lattice(IDManagerMixin, metaclass=ABCMeta): """A repeating structure wherein each element is a universe. Parameters @@ -73,7 +69,7 @@ class Lattice(IDManagerMixin): @name.setter def name(self, name): if name is not None: - cv.check_type('lattice name', name, string_types) + cv.check_type('lattice name', name, str) self._name = name else: self._name = '' diff --git a/openmc/macroscopic.py b/openmc/macroscopic.py index cdb5cbb39..f5bd90f3a 100644 --- a/openmc/macroscopic.py +++ b/openmc/macroscopic.py @@ -1,5 +1,3 @@ -from six import string_types - from openmc.checkvalue import check_type @@ -19,7 +17,7 @@ class Macroscopic(str): """ def __new__(cls, name): - check_type('name', name, string_types) + check_type('name', name, str) return super(Macroscopic, cls).__new__(cls, name) @property diff --git a/openmc/material.py b/openmc/material.py index a59fdaa28..cb3024447 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -4,7 +4,6 @@ from numbers import Real, Integral import warnings from xml.etree import ElementTree as ET -from six import string_types import numpy as np import openmc @@ -217,7 +216,7 @@ class Material(IDManagerMixin): def name(self, name): if name is not None: cv.check_type('name for Material ID="{}"'.format(self._id), - name, string_types) + name, str) self._name = name else: self._name = '' @@ -243,7 +242,7 @@ class Material(IDManagerMixin): @isotropic.setter def isotropic(self, isotropic): cv.check_iterable_type('Isotropic scattering nuclides', isotropic, - string_types) + str) self._isotropic = list(isotropic) @classmethod @@ -345,7 +344,7 @@ class Material(IDManagerMixin): warnings.warn('This feature is not yet implemented in a release ' 'version of openmc') - if not isinstance(filename, string_types) and filename is not None: + if not isinstance(filename, str) and filename is not None: msg = 'Unable to add OTF material file to Material ID="{}" with a ' \ 'non-string name "{}"'.format(self._id, filename) raise ValueError(msg) @@ -379,7 +378,7 @@ class Material(IDManagerMixin): 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) - if not isinstance(nuclide, string_types): + 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) @@ -405,7 +404,7 @@ class Material(IDManagerMixin): Nuclide to remove """ - cv.check_type('nuclide', nuclide, string_types) + cv.check_type('nuclide', nuclide, str) # If the Material contains the Nuclide, delete it for nuc in self._nuclides: @@ -434,7 +433,7 @@ class Material(IDManagerMixin): 'has already been added'.format(self._id, macroscopic) raise ValueError(msg) - if not isinstance(macroscopic, string_types): + if not isinstance(macroscopic, str): msg = 'Unable to add a Macroscopic to Material ID="{}" with a ' \ 'non-string value "{}"'.format(self._id, macroscopic) raise ValueError(msg) @@ -465,7 +464,7 @@ class Material(IDManagerMixin): """ - if not isinstance(macroscopic, string_types): + if not isinstance(macroscopic, str): msg = 'Unable to remove a Macroscopic "{}" in Material ID="{}" ' \ 'since it is not a string'.format(self._id, macroscopic) raise ValueError(msg) @@ -498,7 +497,7 @@ class Material(IDManagerMixin): 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) - if not isinstance(element, string_types): + 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) @@ -563,7 +562,7 @@ class Material(IDManagerMixin): 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) - if not isinstance(name, string_types): + if not isinstance(name, str): msg = 'Unable to add an S(a,b) table to Material ID="{}" with a ' \ 'non-string table name "{}"'.format(self._id, name) raise ValueError(msg) @@ -903,12 +902,12 @@ class Materials(cv.CheckedList): @cross_sections.setter def cross_sections(self, cross_sections): - cv.check_type('cross sections', cross_sections, string_types) + cv.check_type('cross sections', cross_sections, str) self._cross_sections = cross_sections @multipole_library.setter def multipole_library(self, multipole_library): - cv.check_type('cross sections', multipole_library, string_types) + cv.check_type('cross sections', multipole_library, str) self._multipole_library = multipole_library def add_material(self, material): diff --git a/openmc/mesh.py b/openmc/mesh.py index b315b2628..d937e25ce 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -3,7 +3,6 @@ from numbers import Real, Integral from xml.etree import ElementTree as ET import sys -from six import string_types import numpy as np import openmc.checkvalue as cv @@ -87,7 +86,7 @@ class Mesh(IDManagerMixin): def name(self, name): if name is not None: cv.check_type('name for mesh ID="{0}"'.format(self._id), - name, string_types) + name, str) self._name = name else: self._name = '' @@ -95,7 +94,7 @@ class Mesh(IDManagerMixin): @type.setter def type(self, meshtype): cv.check_type('type for mesh ID="{0}"'.format(self._id), - meshtype, string_types) + meshtype, str) cv.check_value('type for mesh ID="{0}"'.format(self._id), meshtype, ['regular']) self._type = meshtype diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index b83305783..c8b151e02 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -6,7 +6,6 @@ from numbers import Integral from collections import OrderedDict, Iterable from warnings import warn -from six import string_types import numpy as np import openmc @@ -271,7 +270,7 @@ class Library(object): @name.setter def name(self, name): - cv.check_type('name', name, string_types) + cv.check_type('name', name, str) self._name = name @mgxs_types.setter @@ -280,7 +279,7 @@ class Library(object): if mgxs_types == 'all': self._mgxs_types = all_mgxs_types else: - cv.check_iterable_type('mgxs_types', mgxs_types, string_types) + cv.check_iterable_type('mgxs_types', mgxs_types, str) for mgxs_type in mgxs_types: cv.check_value('mgxs_type', mgxs_type, all_mgxs_types) self._mgxs_types = mgxs_types @@ -814,8 +813,8 @@ class Library(object): 'since a statepoint has not yet been loaded' raise ValueError(msg) - cv.check_type('filename', filename, string_types) - cv.check_type('directory', directory, string_types) + cv.check_type('filename', filename, str) + cv.check_type('directory', directory, str) import h5py @@ -857,8 +856,8 @@ class Library(object): """ - cv.check_type('filename', filename, string_types) - cv.check_type('directory', directory, string_types) + cv.check_type('filename', filename, str) + cv.check_type('directory', directory, str) # Make directory if it does not exist if not os.path.exists(directory): @@ -892,8 +891,8 @@ class Library(object): """ - cv.check_type('filename', filename, string_types) - cv.check_type('directory', directory, string_types) + cv.check_type('filename', filename, str) + cv.check_type('directory', directory, str) # Make directory if it does not exist if not os.path.exists(directory): @@ -953,8 +952,8 @@ class Library(object): cv.check_type('domain', domain, (openmc.Material, openmc.Cell, openmc.Universe, openmc.Mesh)) - cv.check_type('xsdata_name', xsdata_name, string_types) - cv.check_type('nuclide', nuclide, string_types) + cv.check_type('xsdata_name', xsdata_name, str) + cv.check_type('nuclide', nuclide, str) cv.check_value('xs_type', xs_type, ['macro', 'micro']) if subdomain is not None: cv.check_iterable_type('subdomain', subdomain, Integral, @@ -1213,7 +1212,7 @@ class Library(object): cv.check_value('xs_type', xs_type, ['macro', 'micro']) if xsdata_names is not None: - cv.check_iterable_type('xsdata_names', xsdata_names, string_types) + cv.check_iterable_type('xsdata_names', xsdata_names, str) # If gathering material-specific data, set the xs_type to macro if not self.by_nuclide: diff --git a/openmc/mgxs/mdgxs.py b/openmc/mgxs/mdgxs.py index d3f1a0646..7799183ff 100644 --- a/openmc/mgxs/mdgxs.py +++ b/openmc/mgxs/mdgxs.py @@ -1,5 +1,3 @@ -from __future__ import division - from collections import Iterable, OrderedDict import itertools from numbers import Integral @@ -9,7 +7,6 @@ import sys import copy from abc import ABCMeta -from six import add_metaclass, string_types import numpy as np import openmc @@ -29,7 +26,6 @@ MDGXS_TYPES = ['delayed-nu-fission', MAX_DELAYED_GROUPS = 8 -@add_metaclass(ABCMeta) class MDGXS(MGXS): """An abstract multi-delayed-group cross section for some energy and delayed group structures within some spatial domain. @@ -355,7 +351,7 @@ class MDGXS(MGXS): filter_bins = [] # Construct a collection of the domain filter bins - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3) for subdomain in subdomains: @@ -363,7 +359,7 @@ class MDGXS(MGXS): filter_bins.append((subdomain,)) # Construct list of energy group bounds tuples for all requested groups - if not isinstance(groups, string_types): + if not isinstance(groups, str): cv.check_iterable_type('groups', groups, Integral) for group in groups: filters.append(openmc.EnergyFilter) @@ -371,7 +367,7 @@ class MDGXS(MGXS): (self.energy_groups.get_group_bounds(group),)) # Construct list of delayed group tuples for all requested groups - if not isinstance(delayed_groups, string_types): + if not isinstance(delayed_groups, str): cv.check_type('delayed groups', delayed_groups, list, int) for delayed_group in delayed_groups: filters.append(openmc.DelayedGroupFilter) @@ -475,7 +471,7 @@ class MDGXS(MGXS): """ - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) cv.check_iterable_type('energy_groups', groups, Integral) cv.check_type('delayed groups', delayed_groups, list, int) @@ -585,7 +581,7 @@ class MDGXS(MGXS): return # Construct a collection of the subdomains to report - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) @@ -602,7 +598,7 @@ class MDGXS(MGXS): elif nuclides == 'sum': nuclides = ['sum'] else: - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) else: nuclides = ['sum'] @@ -725,8 +721,8 @@ class MDGXS(MGXS): """ - cv.check_type('filename', filename, string_types) - cv.check_type('directory', directory, string_types) + cv.check_type('filename', filename, str) + cv.check_type('directory', directory, str) cv.check_value('format', format, ['csv', 'excel', 'pickle', 'latex']) cv.check_value('xs_type', xs_type, ['macro', 'micro']) @@ -816,11 +812,11 @@ class MDGXS(MGXS): """ - if not isinstance(groups, string_types): + if not isinstance(groups, str): cv.check_iterable_type('groups', groups, Integral) if nuclides != 'all' and nuclides != 'sum': - cv.check_iterable_type('nuclides', nuclides, string_types) - if not isinstance(delayed_groups, string_types): + cv.check_iterable_type('nuclides', nuclides, str) + if not isinstance(delayed_groups, str): cv.check_type('delayed groups', delayed_groups, list, int) cv.check_value('xs_type', xs_type, ['macro', 'micro']) @@ -858,7 +854,7 @@ class MDGXS(MGXS): columns = self._df_convert_columns_to_bins(df) # Select out those groups the user requested - if not isinstance(groups, string_types): + if not isinstance(groups, str): if 'group in' in df: df = df[df['group in'].isin(groups)] if 'group out' in df: @@ -1288,7 +1284,7 @@ class ChiDelayed(MDGXS): filter_bins = [] # Construct a collection of the domain filter bins - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3) for subdomain in subdomains: @@ -1296,7 +1292,7 @@ class ChiDelayed(MDGXS): filter_bins.append((subdomain,)) # Construct list of energy group bounds tuples for all requested groups - if not isinstance(groups, string_types): + if not isinstance(groups, str): cv.check_iterable_type('groups', groups, Integral) for group in groups: filters.append(openmc.EnergyoutFilter) @@ -1304,7 +1300,7 @@ class ChiDelayed(MDGXS): (self.energy_groups.get_group_bounds(group),)) # Construct list of delayed group tuples for all requested groups - if not isinstance(delayed_groups, string_types): + if not isinstance(delayed_groups, str): cv.check_type('delayed groups', delayed_groups, list, int) for delayed_group in delayed_groups: filters.append(openmc.DelayedGroupFilter) @@ -1352,7 +1348,7 @@ class ChiDelayed(MDGXS): # Get chi delayed for user-specified nuclides in the domain else: - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) xs = self.xs_tally.get_values(filters=filters, filter_bins=filter_bins, nuclides=nuclides, value=value) @@ -1914,7 +1910,6 @@ class DecayRate(MDGXS): return self._get_homogenized_mgxs(other_mgxs, 'delayed-nu-fission') -@add_metaclass(ABCMeta) class MatrixMDGXS(MDGXS): """An abstract multi-delayed-group cross section for some energy group and delayed group structure within some spatial domain. This class is @@ -2117,7 +2112,7 @@ class MatrixMDGXS(MDGXS): filter_bins = [] # Construct a collection of the domain filter bins - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3) for subdomain in subdomains: @@ -2125,7 +2120,7 @@ class MatrixMDGXS(MDGXS): filter_bins.append((subdomain,)) # Construct list of energy group bounds tuples for all requested groups - if not isinstance(in_groups, string_types): + if not isinstance(in_groups, str): cv.check_iterable_type('groups', in_groups, Integral) for group in in_groups: filters.append(openmc.EnergyFilter) @@ -2133,7 +2128,7 @@ class MatrixMDGXS(MDGXS): self.energy_groups.get_group_bounds(group),)) # Construct list of energy group bounds tuples for all requested groups - if not isinstance(out_groups, string_types): + if not isinstance(out_groups, str): cv.check_iterable_type('groups', out_groups, Integral) for group in out_groups: filters.append(openmc.EnergyoutFilter) @@ -2141,7 +2136,7 @@ class MatrixMDGXS(MDGXS): self.energy_groups.get_group_bounds(group),)) # Construct list of delayed group tuples for all requested groups - if not isinstance(delayed_groups, string_types): + if not isinstance(delayed_groups, str): cv.check_type('delayed groups', delayed_groups, list, int) for delayed_group in delayed_groups: filters.append(openmc.DelayedGroupFilter) @@ -2312,7 +2307,7 @@ class MatrixMDGXS(MDGXS): """ # Construct a collection of the subdomains to report - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) @@ -2329,7 +2324,7 @@ class MatrixMDGXS(MDGXS): if nuclides == 'sum': nuclides = ['sum'] else: - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) else: nuclides = ['sum'] diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 0c0b15ad9..949f0fba0 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -1,5 +1,3 @@ -from __future__ import division - from collections import OrderedDict from numbers import Integral import warnings @@ -8,7 +6,6 @@ import copy from abc import ABCMeta import itertools -from six import add_metaclass, string_types import numpy as np import h5py @@ -116,8 +113,7 @@ def _df_column_convert_to_bin(df, current_name, new_name, values_to_bin, df.rename(columns={current_name: new_name}, inplace=True) -@add_metaclass(ABCMeta) -class MGXS(object): +class MGXS(metaclass=ABCMeta): """An abstract multi-group cross section for some energy group structure within some spatial domain. @@ -580,7 +576,7 @@ class MGXS(object): @name.setter def name(self, name): - cv.check_type('name', name, string_types) + cv.check_type('name', name, str) self._name = name @by_nuclide.setter @@ -590,7 +586,7 @@ class MGXS(object): @nuclides.setter def nuclides(self, nuclides): - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) self._nuclides = nuclides @estimator.setter @@ -806,7 +802,7 @@ class MGXS(object): """ - cv.check_type('nuclide', nuclide, string_types) + cv.check_type('nuclide', nuclide, str) # Get list of all nuclides in the spatial domain nuclides = self.domain.get_nuclide_densities() @@ -1033,7 +1029,7 @@ class MGXS(object): filter_bins = [] # Construct a collection of the domain filter bins - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3) @@ -1044,7 +1040,7 @@ class MGXS(object): filter_bins.append(tuple(subdomain_bins)) # Construct list of energy group bounds tuples for all requested groups - if not isinstance(groups, string_types): + if not isinstance(groups, str): cv.check_iterable_type('groups', groups, Integral) filters.append(openmc.EnergyFilter) energy_bins = [] @@ -1219,7 +1215,7 @@ class MGXS(object): """ # Construct a collection of the subdomain filter bins to average across - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral) subdomains = [(subdomain,) for subdomain in subdomains] subdomains = [tuple(subdomains)] @@ -1376,7 +1372,7 @@ class MGXS(object): """ - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) cv.check_iterable_type('energy_groups', groups, Integral) # Build lists of filters and filter bins to slice @@ -1530,7 +1526,7 @@ class MGXS(object): """ # Construct a collection of the subdomains to report - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) @@ -1547,7 +1543,7 @@ class MGXS(object): elif nuclides == 'sum': nuclides = ['sum'] else: - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) else: nuclides = ['sum'] @@ -1698,7 +1694,7 @@ class MGXS(object): xs_results = h5py.File(filename, 'w', libver=libver) # Construct a collection of the subdomains to report - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) @@ -1719,7 +1715,7 @@ class MGXS(object): elif nuclides == 'sum': nuclides = ['sum'] else: - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) else: nuclides = ['sum'] @@ -1797,8 +1793,8 @@ class MGXS(object): """ - cv.check_type('filename', filename, string_types) - cv.check_type('directory', directory, string_types) + cv.check_type('filename', filename, str) + cv.check_type('directory', directory, str) cv.check_value('format', format, ['csv', 'excel', 'pickle', 'latex']) cv.check_value('xs_type', xs_type, ['macro', 'micro']) @@ -1884,10 +1880,10 @@ class MGXS(object): """ - if not isinstance(groups, string_types): + if not isinstance(groups, str): cv.check_iterable_type('groups', groups, Integral) if nuclides != 'all' and nuclides != 'sum': - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) cv.check_value('xs_type', xs_type, ['macro', 'micro']) # Get a Pandas DataFrame from the derived xs tally @@ -1923,7 +1919,7 @@ class MGXS(object): columns = self._df_convert_columns_to_bins(df) # Select out those groups the user requested - if not isinstance(groups, string_types): + if not isinstance(groups, str): if 'group in' in df: df = df[df['group in'].isin(groups)] if 'group out' in df: @@ -1976,7 +1972,6 @@ class MGXS(object): return 'cm^-1' if xs_type == 'macro' else 'barns' -@add_metaclass(ABCMeta) class MatrixMGXS(MGXS): """An abstract multi-group cross section for some energy group structure within some spatial domain. This class is specifically intended for @@ -2164,7 +2159,7 @@ class MatrixMGXS(MGXS): filter_bins = [] # Construct a collection of the domain filter bins - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3) filters.append(_DOMAIN_TO_FILTER[self.domain_type]) @@ -2174,7 +2169,7 @@ class MatrixMGXS(MGXS): filter_bins.append(tuple(subdomain_bins)) # Construct list of energy group bounds tuples for all requested groups - if not isinstance(in_groups, string_types): + if not isinstance(in_groups, str): cv.check_iterable_type('groups', in_groups, Integral) filters.append(openmc.EnergyFilter) for group in in_groups: @@ -2182,7 +2177,7 @@ class MatrixMGXS(MGXS): filter_bins.append(tuple(energy_bins)) # Construct list of energy group bounds tuples for all requested groups - if not isinstance(out_groups, string_types): + if not isinstance(out_groups, str): cv.check_iterable_type('groups', out_groups, Integral) for group in out_groups: filters.append(openmc.EnergyoutFilter) @@ -2342,7 +2337,7 @@ class MatrixMGXS(MGXS): """ # Construct a collection of the subdomains to report - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) @@ -2359,7 +2354,7 @@ class MatrixMGXS(MGXS): if nuclides == 'sum': nuclides = ['sum'] else: - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) else: nuclides = ['sum'] @@ -4307,7 +4302,7 @@ class ScatterMatrixXS(MatrixMGXS): filter_bins = [] # Construct a collection of the domain filter bins - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3) filters.append(_DOMAIN_TO_FILTER[self.domain_type]) subdomain_bins = [] @@ -4316,7 +4311,7 @@ class ScatterMatrixXS(MatrixMGXS): filter_bins.append(tuple(subdomain_bins)) # Construct list of energy group bounds tuples for all requested groups - if not isinstance(in_groups, string_types): + if not isinstance(in_groups, str): cv.check_iterable_type('groups', in_groups, Integral) filters.append(openmc.EnergyFilter) energy_bins = [] @@ -4326,7 +4321,7 @@ class ScatterMatrixXS(MatrixMGXS): filter_bins.append(tuple(energy_bins)) # Construct list of energy group bounds tuples for all requested groups - if not isinstance(out_groups, string_types): + if not isinstance(out_groups, str): cv.check_iterable_type('groups', out_groups, Integral) for group in out_groups: filters.append(openmc.EnergyoutFilter) @@ -4539,7 +4534,7 @@ class ScatterMatrixXS(MatrixMGXS): """ # Construct a collection of the subdomains to report - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral) elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) @@ -4556,7 +4551,7 @@ class ScatterMatrixXS(MatrixMGXS): if nuclides == 'sum': nuclides = ['sum'] else: - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) else: nuclides = ['sum'] @@ -5582,7 +5577,7 @@ class Chi(MGXS): filter_bins = [] # Construct a collection of the domain filter bins - if not isinstance(subdomains, string_types): + if not isinstance(subdomains, str): cv.check_iterable_type('subdomains', subdomains, Integral, max_depth=3) filters.append(_DOMAIN_TO_FILTER[self.domain_type]) @@ -5592,7 +5587,7 @@ class Chi(MGXS): filter_bins.append(tuple(subdomain_bins)) # Construct list of energy group bounds tuples for all requested groups - if not isinstance(groups, string_types): + if not isinstance(groups, str): cv.check_iterable_type('groups', groups, Integral) filters.append(openmc.EnergyoutFilter) energy_bins = [] @@ -5640,7 +5635,7 @@ class Chi(MGXS): # Get chi for user-specified nuclides in the domain else: - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) xs = self.xs_tally.get_values(filters=filters, filter_bins=filter_bins, nuclides=nuclides, value=value) diff --git a/openmc/mgxs_library.py b/openmc/mgxs_library.py index ebfae2fb0..e315f3314 100644 --- a/openmc/mgxs_library.py +++ b/openmc/mgxs_library.py @@ -2,7 +2,6 @@ import copy from numbers import Real, Integral import os -from six import string_types import numpy as np import h5py from scipy.interpolate import interp1d @@ -381,7 +380,7 @@ class XSdata(object): @name.setter def name(self, name): - check_type('name for XSdata', name, string_types) + check_type('name for XSdata', name, str) self._name = name @energy_groups.setter @@ -2517,7 +2516,7 @@ class MGXSLibrary(object): """ - check_type('filename', filename, string_types) + check_type('filename', filename, str) # Create and write to the HDF5 file file = h5py.File(filename, "w", libver=libver) diff --git a/openmc/model/funcs.py b/openmc/model/funcs.py index 589765d2c..b5afdf08d 100644 --- a/openmc/model/funcs.py +++ b/openmc/model/funcs.py @@ -1,4 +1,3 @@ -from __future__ import division from collections import Iterable, OrderedDict from math import sqrt from numbers import Real diff --git a/openmc/model/triso.py b/openmc/model/triso.py index 7258a86f2..0fe138bd7 100644 --- a/openmc/model/triso.py +++ b/openmc/model/triso.py @@ -1,4 +1,3 @@ -from __future__ import division import copy import warnings import itertools @@ -10,7 +9,6 @@ from heapq import heappush, heappop from math import pi, sin, cos, floor, log10, sqrt from abc import ABCMeta, abstractproperty, abstractmethod -from six import add_metaclass import numpy as np import scipy.spatial @@ -92,8 +90,7 @@ class TRISO(openmc.Cell): k_min:k_max+1, j_min:j_max+1, i_min:i_max+1])) -@add_metaclass(ABCMeta) -class _Domain(object): +class _Domain(metaclass=ABCMeta): """Container in which to pack particles. Parameters diff --git a/openmc/nuclide.py b/openmc/nuclide.py index 2e5a8e119..73247e1aa 100644 --- a/openmc/nuclide.py +++ b/openmc/nuclide.py @@ -1,7 +1,5 @@ import warnings -from six import string_types - import openmc.checkvalue as cv diff --git a/openmc/plots.py b/openmc/plots.py index a60b2a309..bfff2d7e9 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -4,7 +4,6 @@ from xml.etree import ElementTree as ET import sys import warnings -from six import string_types import numpy as np import openmc @@ -297,7 +296,7 @@ class Plot(IDManagerMixin): @name.setter def name(self, name): - cv.check_type('plot name', name, string_types) + cv.check_type('plot name', name, str) self._name = name @width.setter @@ -322,7 +321,7 @@ class Plot(IDManagerMixin): @filename.setter def filename(self, filename): - cv.check_type('filename', filename, string_types) + cv.check_type('filename', filename, str) self._filename = filename @color_by.setter @@ -343,7 +342,7 @@ class Plot(IDManagerMixin): @background.setter def background(self, background): cv.check_type('plot background', background, Iterable) - if isinstance(background, string_types): + if isinstance(background, str): if background.lower() not in _SVG_COLORS: raise ValueError("'{}' is not a valid color.".format(background)) else: @@ -359,7 +358,7 @@ class Plot(IDManagerMixin): for key, value in colors.items(): cv.check_type('plot color key', key, (openmc.Cell, openmc.Material)) cv.check_type('plot color value', value, Iterable) - if isinstance(value, string_types): + if isinstance(value, str): if value.lower() not in _SVG_COLORS: raise ValueError("'{}' is not a valid color.".format(value)) else: @@ -380,7 +379,7 @@ class Plot(IDManagerMixin): @mask_background.setter def mask_background(self, mask_background): cv.check_type('plot mask background', mask_background, Iterable) - if isinstance(mask_background, string_types): + if isinstance(mask_background, str): if mask_background.lower() not in _SVG_COLORS: raise ValueError("'{}' is not a valid color.".format(mask_background)) else: @@ -558,7 +557,7 @@ class Plot(IDManagerMixin): cv.check_type('background', background, Iterable) # Get a background (R,G,B) tuple to apply in alpha compositing - if isinstance(background, string_types): + if isinstance(background, str): if background.lower() not in _SVG_COLORS: raise ValueError("'{}' is not a valid color.".format(background)) background = _SVG_COLORS[background.lower()] @@ -570,7 +569,7 @@ class Plot(IDManagerMixin): # other than those the user wishes to highlight for domain, color in self.colors.items(): if domain not in domains: - if isinstance(color, string_types): + if isinstance(color, str): color = _SVG_COLORS[color.lower()] r, g, b = color r = int(((1-alpha) * background[0]) + (alpha * r)) @@ -610,7 +609,7 @@ class Plot(IDManagerMixin): if self._background is not None: subelement = ET.SubElement(element, "background") color = self._background - if isinstance(color, string_types): + if isinstance(color, str): color = _SVG_COLORS[color.lower()] subelement.text = ' '.join(str(x) for x in color) @@ -619,7 +618,7 @@ class Plot(IDManagerMixin): key=lambda x: x[0].id): subelement = ET.SubElement(element, "color") subelement.set("id", str(domain.id)) - if isinstance(color, string_types): + if isinstance(color, str): color = _SVG_COLORS[color.lower()] subelement.set("rgb", ' '.join(str(x) for x in color)) @@ -629,7 +628,7 @@ class Plot(IDManagerMixin): str(d.id) for d in self._mask_components)) color = self._mask_background if color is not None: - if isinstance(color, string_types): + if isinstance(color, str): color = _SVG_COLORS[color.lower()] subelement.set("background", ' '.join( str(x) for x in color)) diff --git a/openmc/plotter.py b/openmc/plotter.py index 810ee5d27..1bf6fe46f 100644 --- a/openmc/plotter.py +++ b/openmc/plotter.py @@ -2,7 +2,6 @@ from numbers import Integral, Real from itertools import chain import string -from six import string_types import matplotlib.pyplot as plt import numpy as np @@ -137,7 +136,7 @@ def plot_xs(this, types, divisor_types=None, temperature=294., data_type=None, data_type = 'material' elif isinstance(this, openmc.Macroscopic): data_type = 'macroscopic' - elif isinstance(this, string_types): + elif isinstance(this, str): if this[-1] in string.digits: data_type = 'nuclide' else: @@ -275,7 +274,7 @@ def calculate_cexs(this, data_type, types, temperature=294., sab_name=None, # Check types cv.check_type('temperature', temperature, Real) if sab_name: - cv.check_type('sab_name', sab_name, string_types) + cv.check_type('sab_name', sab_name, str) if enrichment: cv.check_type('enrichment', enrichment, Real) @@ -648,7 +647,7 @@ def calculate_mgxs(this, data_type, types, orders=None, temperature=294., cv.check_type('temperature', temperature, Real) if enrichment: cv.check_type('enrichment', enrichment, Real) - cv.check_iterable_type('types', types, string_types) + cv.check_iterable_type('types', types, str) cv.check_type("cross_sections", cross_sections, str) library = openmc.MGXSLibrary.from_hdf5(cross_sections) diff --git a/openmc/region.py b/openmc/region.py index 54797f568..c9bd3b5fc 100644 --- a/openmc/region.py +++ b/openmc/region.py @@ -2,14 +2,12 @@ from abc import ABCMeta, abstractmethod from collections import Iterable, OrderedDict, MutableSequence from copy import deepcopy -from six import add_metaclass import numpy as np from openmc.checkvalue import check_type -@add_metaclass(ABCMeta) -class Region(object): +class Region(metaclass=ABCMeta): """Region of space that can be assigned to a cell. Region is an abstract base class that is inherited by diff --git a/openmc/settings.py b/openmc/settings.py index 3c46d0c2c..1bbc7a4ec 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -4,7 +4,6 @@ import warnings from xml.etree import ElementTree as ET import sys -from six import string_types import numpy as np from openmc.clean_xml import clean_xml_indentation @@ -459,7 +458,7 @@ class Settings(object): if key in ('summary', 'tallies'): cv.check_type("output['{}']".format(key), value, bool) else: - cv.check_type("output['path']", value, string_types) + cv.check_type("output['path']", value, str) self._output = output @verbosity.setter @@ -511,7 +510,7 @@ class Settings(object): warnings.warn('Settings.cross_sections has been deprecated and will be ' 'removed in a future version. Materials.cross_sections ' 'should defined instead.', DeprecationWarning) - cv.check_type('cross sections', cross_sections, string_types) + cv.check_type('cross sections', cross_sections, str) self._cross_sections = cross_sections @multipole_library.setter @@ -520,7 +519,7 @@ class Settings(object): 'be removed in a future version. ' 'Materials.multipole_library should defined instead.', DeprecationWarning) - cv.check_type('multipole library', multipole_library, string_types) + cv.check_type('multipole library', multipole_library, str) self._multipole_library = multipole_library @ptables.setter @@ -692,7 +691,7 @@ class Settings(object): cv.check_greater_than(name, value, 0) elif key == 'nuclides': cv.check_type('resonance scattering nuclides', value, - Iterable, string_types) + Iterable, str) self._resonance_scattering = res @volume_calculations.setter diff --git a/openmc/source.py b/openmc/source.py index 1aee43514..932062278 100644 --- a/openmc/source.py +++ b/openmc/source.py @@ -2,8 +2,6 @@ from numbers import Real import sys from xml.etree import ElementTree as ET -from six import string_types - from openmc.stats.univariate import Univariate from openmc.stats.multivariate import UnitSphere, Spatial import openmc.checkvalue as cv @@ -78,7 +76,7 @@ class Source(object): @file.setter def file(self, filename): - cv.check_type('source file', filename, string_types) + cv.check_type('source file', filename, str) self._file = filename @space.setter diff --git a/openmc/stats/multivariate.py b/openmc/stats/multivariate.py index 0ab11b7c5..ba2debaea 100644 --- a/openmc/stats/multivariate.py +++ b/openmc/stats/multivariate.py @@ -5,15 +5,13 @@ from numbers import Real import sys from xml.etree import ElementTree as ET -from six import add_metaclass import numpy as np import openmc.checkvalue as cv from openmc.stats.univariate import Univariate, Uniform -@add_metaclass(ABCMeta) -class UnitSphere(object): +class UnitSphere(metaclass=ABCMeta): """Distribution of points on the unit sphere. This abstract class is used for angular distributions, since a direction is @@ -181,8 +179,7 @@ class Monodirectional(UnitSphere): return element -@add_metaclass(ABCMeta) -class Spatial(object): +class Spatial(metaclass=ABCMeta): """Distribution of locations in three-dimensional Euclidean space. Classes derived from this abstract class can be used for spatial diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index 38683e692..47a281d21 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -4,7 +4,6 @@ from numbers import Real import sys from xml.etree import ElementTree as ET -from six import add_metaclass import numpy as np import openmc.checkvalue as cv @@ -15,8 +14,7 @@ _INTERPOLATION_SCHEMES = ['histogram', 'linear-linear', 'linear-log', 'log-linear', 'log-log'] -@add_metaclass(ABCMeta) -class Univariate(EqualityMixin): +class Univariate(EqualityMixin, metaclass=ABCMeta): """Probability distribution of a single random variable. The Univariate class is an abstract class that can be derived to implement a diff --git a/openmc/surface.py b/openmc/surface.py index eb44a4fe8..694e9fd22 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -1,4 +1,3 @@ -from __future__ import division from abc import ABCMeta from collections import OrderedDict from copy import deepcopy @@ -6,7 +5,6 @@ from functools import partial from numbers import Real, Integral from xml.etree import ElementTree as ET -from six import add_metaclass, string_types import numpy as np from openmc.checkvalue import check_type, check_value @@ -115,14 +113,14 @@ class Surface(IDManagerMixin): @name.setter def name(self, name): if name is not None: - check_type('surface name', name, string_types) + check_type('surface name', name, str) self._name = name else: self._name = '' @boundary_type.setter def boundary_type(self, boundary_type): - check_type('boundary type', boundary_type, string_types) + check_type('boundary type', boundary_type, str) check_value('boundary type', boundary_type, _BOUNDARY_TYPES) self._boundary_type = boundary_type @@ -738,8 +736,7 @@ class ZPlane(Plane): return point[2] - self.z0 -@add_metaclass(ABCMeta) -class Cylinder(Surface): +class Cylinder(Surface, metaclass=ABCMeta): """A cylinder whose length is parallel to the x-, y-, or z-axis. Parameters @@ -1305,8 +1302,7 @@ class Sphere(Surface): return x**2 + y**2 + z**2 - self.r**2 -@add_metaclass(ABCMeta) -class Cone(Surface): +class Cone(Surface, metaclass=ABCMeta): """A conical surface parallel to the x-, y-, or z-axis. Parameters diff --git a/openmc/tallies.py b/openmc/tallies.py index b685dcae3..17c298a8d 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1,5 +1,3 @@ -from __future__ import division - from collections import Iterable, MutableSequence import copy import re @@ -10,7 +8,6 @@ import operator import warnings from xml.etree import ElementTree as ET -from six import string_types import numpy as np import pandas as pd import scipy.sparse as sps @@ -31,9 +28,8 @@ _PRODUCT_TYPES = ['tensor', 'entrywise'] # The following indicate acceptable types when setting Tally.scores, # Tally.nuclides, and Tally.filters -_SCORE_CLASSES = string_types + (openmc.CrossScore, openmc.AggregateScore) -_NUCLIDE_CLASSES = string_types + (openmc.Nuclide, openmc.CrossNuclide, - openmc.AggregateNuclide) +_SCORE_CLASSES = (str, openmc.CrossScore, openmc.AggregateScore) +_NUCLIDE_CLASSES = (str, openmc.CrossNuclide, openmc.AggregateNuclide) _FILTER_CLASSES = (openmc.Filter, openmc.CrossFilter, openmc.AggregateFilter) # Valid types of estimators @@ -359,7 +355,7 @@ class Tally(IDManagerMixin): @name.setter def name(self, name): if name is not None: - cv.check_type('tally name', name, string_types) + cv.check_type('tally name', name, str) self._name = name else: self._name = '' @@ -412,7 +408,7 @@ class Tally(IDManagerMixin): raise ValueError(msg) # If score is a string, strip whitespace - if isinstance(score, string_types): + if isinstance(score, str): scores[i] = score.strip() self._scores = cv.CheckedList(_SCORE_CLASSES, 'tally scores', scores) @@ -1327,7 +1323,7 @@ class Tally(IDManagerMixin): """ - cv.check_iterable_type('nuclides', nuclides, string_types) + cv.check_iterable_type('nuclides', nuclides, str) # Determine the score indices from any of the requested scores if nuclides: @@ -1362,7 +1358,7 @@ class Tally(IDManagerMixin): """ for score in scores: - if not isinstance(score, string_types + (openmc.CrossScore,)): + if not isinstance(score, (str, openmc.CrossScore)): msg = 'Unable to get score indices for score "{0}" in Tally ' \ 'ID="{1}" since it is not a string or CrossScore'\ .format(score, self.id) @@ -1555,7 +1551,7 @@ class Tally(IDManagerMixin): column_name = 'score' for score in self.scores: - if isinstance(score, string_types + (openmc.CrossScore,)): + if isinstance(score, (str, openmc.CrossScore)): scores.append(str(score)) elif isinstance(score, openmc.AggregateScore): scores.append(score.name) @@ -2192,11 +2188,11 @@ class Tally(IDManagerMixin): raise ValueError(msg) # Check that the scores are valid - if not isinstance(score1, string_types + (openmc.CrossScore,)): + if not isinstance(score1, (str, openmc.CrossScore)): msg = 'Unable to swap score1 "{0}" in Tally ID="{1}" since it is ' \ 'not a string or CrossScore'.format(score1, self.id) raise ValueError(msg) - elif not isinstance(score2, string_types + (openmc.CrossScore,)): + elif not isinstance(score2, (str, openmc.CrossScore)): msg = 'Unable to swap score2 "{0}" in Tally ID="{1}" since it is ' \ 'not a string or CrossScore'.format(score2, self.id) raise ValueError(msg) diff --git a/openmc/tally_derivative.py b/openmc/tally_derivative.py index facc56f44..9be748b2c 100644 --- a/openmc/tally_derivative.py +++ b/openmc/tally_derivative.py @@ -1,11 +1,7 @@ -from __future__ import division - import sys from numbers import Integral from xml.etree import ElementTree as ET -from six import string_types - import openmc.checkvalue as cv from openmc.mixin import EqualityMixin, IDManagerMixin @@ -81,7 +77,7 @@ class TallyDerivative(EqualityMixin, IDManagerMixin): @variable.setter def variable(self, var): if var is not None: - cv.check_type('derivative variable', var, string_types) + cv.check_type('derivative variable', var, str) cv.check_value('derivative variable', var, ('density', 'nuclide_density', 'temperature')) self._variable = var @@ -95,7 +91,7 @@ class TallyDerivative(EqualityMixin, IDManagerMixin): @nuclide.setter def nuclide(self, nuc): if nuc is not None: - cv.check_type('derivative nuclide', nuc, string_types) + cv.check_type('derivative nuclide', nuc, str) self._nuclide = nuc def to_xml_element(self): diff --git a/openmc/trigger.py b/openmc/trigger.py index 69ec8c620..a5ac1d1e8 100644 --- a/openmc/trigger.py +++ b/openmc/trigger.py @@ -4,8 +4,6 @@ import sys import warnings from collections import Iterable -from six import string_types - import openmc.checkvalue as cv @@ -76,7 +74,7 @@ class Trigger(object): @scores.setter def scores(self, scores): - cv.check_type('trigger scores', scores, Iterable, string_types) + cv.check_type('trigger scores', scores, Iterable, str) # Set scores making sure not to have duplicates self._scores = [] diff --git a/openmc/universe.py b/openmc/universe.py index 4a0a1a9aa..55f574c53 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -1,11 +1,9 @@ -from __future__ import division from collections import OrderedDict, Iterable from copy import copy, deepcopy from numbers import Integral, Real import random import sys -from six import string_types import matplotlib.pyplot as plt import numpy as np @@ -97,7 +95,7 @@ class Universe(IDManagerMixin): @name.setter def name(self, name): if name is not None: - cv.check_type('universe name', name, string_types) + cv.check_type('universe name', name, str) self._name = name else: self._name = '' @@ -237,7 +235,7 @@ class Universe(IDManagerMixin): # Convert to RGBA if necessary colors = copy(colors) for obj, color in colors.items(): - if isinstance(color, string_types): + if isinstance(color, str): if color.lower() not in _SVG_COLORS: raise ValueError("'{}' is not a valid color." .format(color)) diff --git a/scripts/openmc-ace-to-hdf5 b/scripts/openmc-ace-to-hdf5 index 3235a2da6..29c987e07 100755 --- a/scripts/openmc-ace-to-hdf5 +++ b/scripts/openmc-ace-to-hdf5 @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import argparse import os diff --git a/scripts/openmc-convert-mcnp70-data b/scripts/openmc-convert-mcnp70-data index 0489b25de..75b3b0a5a 100755 --- a/scripts/openmc-convert-mcnp70-data +++ b/scripts/openmc-convert-mcnp70-data @@ -1,6 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -from __future__ import print_function import argparse from collections import defaultdict import glob diff --git a/scripts/openmc-convert-mcnp71-data b/scripts/openmc-convert-mcnp71-data index 061f8f46f..ce8c427d6 100755 --- a/scripts/openmc-convert-mcnp71-data +++ b/scripts/openmc-convert-mcnp71-data @@ -1,6 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -from __future__ import print_function import argparse from collections import defaultdict import glob diff --git a/scripts/openmc-get-jeff-data b/scripts/openmc-get-jeff-data index 6d9e086a9..9f426a493 100755 --- a/scripts/openmc-get-jeff-data +++ b/scripts/openmc-get-jeff-data @@ -1,6 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -from __future__ import print_function import os from collections import defaultdict import sys @@ -9,9 +8,7 @@ import zipfile import glob import argparse from string import digits - -from six.moves import input -from six.moves.urllib.request import urlopen +from urllib.request import urlopen import openmc.data diff --git a/scripts/openmc-get-multipole-data b/scripts/openmc-get-multipole-data index bf0add284..ea2539648 100755 --- a/scripts/openmc-get-multipole-data +++ b/scripts/openmc-get-multipole-data @@ -1,6 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -from __future__ import print_function import os import shutil import subprocess @@ -9,9 +8,7 @@ import tarfile import glob import hashlib import argparse - -from six.moves import input -from six.moves.urllib.request import urlopen +from urllib.request import urlopen description = """ diff --git a/scripts/openmc-get-nndc-data b/scripts/openmc-get-nndc-data index e2c1e7ce3..a04eed6cd 100755 --- a/scripts/openmc-get-nndc-data +++ b/scripts/openmc-get-nndc-data @@ -1,6 +1,5 @@ #!/usr/bin/env python -from __future__ import print_function import os import shutil import subprocess @@ -9,9 +8,7 @@ import tarfile import glob import hashlib import argparse - -from six.moves import input -from six.moves.urllib.request import urlopen +from urllib.request import urlopen import openmc.data diff --git a/scripts/openmc-plot-mesh-tally b/scripts/openmc-plot-mesh-tally index c273a6d59..0dfb29b9b 100755 --- a/scripts/openmc-plot-mesh-tally +++ b/scripts/openmc-plot-mesh-tally @@ -1,16 +1,16 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """Python script to plot tally data generated by OpenMC.""" import os import sys import argparse +import tkinter as tk +import tkinter.filedialog as filedialog +import tkinter.font as font +import tkinter.messagebox as messagebox +import tkinter.ttk as ttk -import six.moves.tkinter as tk -import six.moves.tkinter_filedialog as filedialog -import six.moves.tkinter_font as font -import six.moves.tkinter_messagebox as messagebox -import six.moves.tkinter_ttk as ttk from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg from matplotlib.figure import Figure diff --git a/scripts/openmc-track-to-vtk b/scripts/openmc-track-to-vtk index 1dd632f9d..fa154a2a7 100755 --- a/scripts/openmc-track-to-vtk +++ b/scripts/openmc-track-to-vtk @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """Convert HDF5 particle track to VTK poly data. diff --git a/scripts/openmc-update-inputs b/scripts/openmc-update-inputs index 3eb850207..ef7cdf47b 100755 --- a/scripts/openmc-update-inputs +++ b/scripts/openmc-update-inputs @@ -1,10 +1,8 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """Update OpenMC's input XML files to the latest format. """ -from __future__ import print_function - import argparse from difflib import get_close_matches from itertools import chain diff --git a/scripts/openmc-update-mgxs b/scripts/openmc-update-mgxs index f33adfc00..8559affb9 100755 --- a/scripts/openmc-update-mgxs +++ b/scripts/openmc-update-mgxs @@ -1,10 +1,9 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """Update OpenMC's deprecated multi-group cross section XML files to the latest HDF5-based format. """ -from __future__ import print_function import os import warnings import xml.etree.ElementTree as ET diff --git a/scripts/openmc-validate-xml b/scripts/openmc-validate-xml index e5a45f469..f36ba2b5d 100755 --- a/scripts/openmc-validate-xml +++ b/scripts/openmc-validate-xml @@ -1,6 +1,4 @@ -#!/usr/bin/env python - -from __future__ import print_function +#!/usr/bin/env python3 import os import sys diff --git a/scripts/openmc-voxel-to-silovtk b/scripts/openmc-voxel-to-silovtk index 9748cc3bb..e0cfc0a5b 100755 --- a/scripts/openmc-voxel-to-silovtk +++ b/scripts/openmc-voxel-to-silovtk @@ -1,6 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 -from __future__ import division, print_function import struct import sys from argparse import ArgumentParser diff --git a/setup.py b/setup.py index 7bffc516f..2a42dd65d 100755 --- a/setup.py +++ b/setup.py @@ -48,11 +48,7 @@ kwargs = { 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Topic :: Scientific/Engineering' - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', @@ -60,7 +56,7 @@ kwargs = { # Required dependencies 'install_requires': [ - 'six', 'numpy>=1.9', 'h5py', 'scipy', 'ipython', 'matplotlib', + 'numpy>=1.9', 'h5py', 'scipy', 'ipython', 'matplotlib', 'pandas', 'lxml', 'uncertainties' ], From 1ccf6c3e5e3656cc6a1425425f8dbb30b89eec8e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 24 Dec 2017 16:18:22 +0700 Subject: [PATCH 181/282] Use tempfile.TemporaryDirectory() --- openmc/data/neutron.py | 9 +-------- openmc/data/njoy.py | 7 +------ openmc/data/thermal.py | 8 +------- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 8d5931689..4f02af1a4 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -840,10 +840,7 @@ class IncidentNeutron(EqualityMixin): Incident neutron continuous-energy data """ - # Create temporary directory -- it would be preferable to use - # TemporaryDirectory(), but it is only available in Python 3.2 - tmpdir = tempfile.mkdtemp() - try: + with tempfile.TemporaryDirectory() as tmpdir: # Run NJOY to create an ACE library ace_file = os.path.join(tmpdir, 'ace') xsdir_file = os.path.join(tmpdir, 'xsdir') @@ -871,8 +868,4 @@ class IncidentNeutron(EqualityMixin): data.energy['0K'] = xs.x data[2].xs['0K'] = xs - finally: - # Get rid of temporary files - shutil.rmtree(tmpdir) - return data diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index be16d96f8..b2894b3d1 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -149,10 +149,7 @@ def run(commands, tapein, tapeout, input_filename=None, stdout=False, with open(input_filename, 'w') as f: f.write(commands) - # Create temporary directory -- it would be preferable to use - # TemporaryDirectory(), but it is only available in Python 3.2 - tmpdir = tempfile.mkdtemp() - try: + with tempfile.TemporaryDirectory() as tmpdir: # Copy evaluations to appropriates 'tapes' for tape_num, filename in tapein.items(): tmpfilename = os.path.join(tmpdir, 'tape{}'.format(tape_num)) @@ -186,8 +183,6 @@ def run(commands, tapein, tapeout, input_filename=None, stdout=False, tmpfilename = os.path.join(tmpdir, 'tape{}'.format(tape_num)) if os.path.isfile(tmpfilename): shutil.move(tmpfilename, filename) - finally: - shutil.rmtree(tmpdir) def make_pendf(filename, pendf='pendf', error=0.001, stdout=False): diff --git a/openmc/data/thermal.py b/openmc/data/thermal.py index 68192de03..4f89d8e83 100644 --- a/openmc/data/thermal.py +++ b/openmc/data/thermal.py @@ -623,10 +623,7 @@ class ThermalScattering(EqualityMixin): Thermal scattering data """ - # Create temporary directory -- it would be preferable to use - # TemporaryDirectory(), but it is only available in Python 3.2 - tmpdir = tempfile.mkdtemp() - try: + with tempfile.TemporaryDirectory() as tmpdir: # Run NJOY to create an ACE library ace_file = os.path.join(tmpdir, 'ace') xsdir_file = os.path.join(tmpdir, 'xsdir') @@ -638,8 +635,5 @@ class ThermalScattering(EqualityMixin): data = cls.from_ace(lib.tables[0]) for table in lib.tables[1:]: data.add_temperature_from_ace(table) - finally: - # Get rid of temporary files - shutil.rmtree(tmpdir) return data From deab4b3d337ee91b4411089562e6b8824a25ce90 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 24 Dec 2017 16:34:25 +0700 Subject: [PATCH 182/282] Use empty-argument form of super() --- .travis.yml | 3 +- openmc/capi/cell.py | 2 +- openmc/capi/filter.py | 6 +- openmc/capi/nuclide.py | 2 +- openmc/capi/tally.py | 2 +- openmc/checkvalue.py | 6 +- openmc/data/angle_distribution.py | 2 +- openmc/data/correlated.py | 2 +- openmc/data/energy_distribution.py | 18 +++--- openmc/data/kalbach_mann.py | 2 +- openmc/data/laboratory.py | 2 +- openmc/data/resonance.py | 18 +++--- openmc/element.py | 2 +- openmc/filter.py | 9 ++- openmc/lattice.py | 4 +- openmc/macroscopic.py | 2 +- openmc/material.py | 6 +- openmc/mgxs/mdgxs.py | 49 ++++++--------- openmc/mgxs/mgxs.py | 96 +++++++++++++----------------- openmc/model/triso.py | 8 +-- openmc/nuclide.py | 2 +- openmc/plots.py | 6 +- openmc/stats/multivariate.py | 12 ++-- openmc/stats/univariate.py | 12 ++-- openmc/surface.py | 30 +++++----- openmc/tallies.py | 8 +-- 26 files changed, 142 insertions(+), 169 deletions(-) diff --git a/.travis.yml b/.travis.yml index 05e239025..de83325e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,9 @@ sudo: required dist: trusty language: python python: - - "2.7" - "3.4" + - "3.5" + - "3.6" addons: apt: packages: diff --git a/openmc/capi/cell.py b/openmc/capi/cell.py index 0c4b72c74..0cadfd6af 100644 --- a/openmc/capi/cell.py +++ b/openmc/capi/cell.py @@ -81,7 +81,7 @@ class Cell(_FortranObjectWithID): index = mapping[uid]._index if index not in cls.__instances: - instance = super(Cell, cls).__new__(cls) + instance = super().__new__(cls) instance._index = index if uid is not None: instance.id = uid diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index 1b52af6db..74c6828d6 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -87,7 +87,7 @@ class Filter(_FortranObjectWithID): index = mapping[uid]._index if index not in cls.__instances: - instance = super(Filter, cls).__new__(cls) + instance = super().__new__(cls) instance._index = index if uid is not None: instance.id = uid @@ -110,7 +110,7 @@ class EnergyFilter(Filter): filter_type = 'energy' def __init__(self, bins=None, uid=None, new=True, index=None): - super(EnergyFilter, self).__init__(uid, new, index) + super().__init__(uid, new, index) if bins is not None: self.bins = bins @@ -167,7 +167,7 @@ class MaterialFilter(Filter): filter_type = 'material' def __init__(self, bins=None, uid=None, new=True, index=None): - super(MaterialFilter, self).__init__(uid, new, index) + super().__init__(uid, new, index) if bins is not None: self.bins = bins diff --git a/openmc/capi/nuclide.py b/openmc/capi/nuclide.py index e07872e58..54d131498 100644 --- a/openmc/capi/nuclide.py +++ b/openmc/capi/nuclide.py @@ -58,7 +58,7 @@ class Nuclide(_FortranObject): def __new__(cls, *args): if args not in cls.__instances: - instance = super(Nuclide, cls).__new__(cls) + instance = super().__new__(cls) cls.__instances[args] = instance return cls.__instances[args] diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index 751f0e603..aaf709e61 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -172,7 +172,7 @@ class Tally(_FortranObjectWithID): index = mapping[uid]._index if index not in cls.__instances: - instance = super(Tally, cls).__new__(cls) + instance = super().__new__(cls) instance._index = index if uid is not None: instance.id = uid diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index aa4dc067f..643ea4820 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -288,7 +288,7 @@ class CheckedList(list): """ def __init__(self, expected_type, name, items=[]): - super(CheckedList, self).__init__() + super().__init__() self.expected_type = expected_type self.name = name for item in items: @@ -319,7 +319,7 @@ class CheckedList(list): """ check_type(self.name, item, self.expected_type) - super(CheckedList, self).append(item) + super().append(item) def insert(self, index, item): """Insert item before index @@ -333,4 +333,4 @@ class CheckedList(list): """ check_type(self.name, item, self.expected_type) - super(CheckedList, self).insert(index, item) + super().insert(index, item) diff --git a/openmc/data/angle_distribution.py b/openmc/data/angle_distribution.py index ad3ba8919..71e3d2f30 100644 --- a/openmc/data/angle_distribution.py +++ b/openmc/data/angle_distribution.py @@ -34,7 +34,7 @@ class AngleDistribution(EqualityMixin): """ def __init__(self, energy, mu): - super(AngleDistribution, self).__init__() + super().__init__() self.energy = energy self.mu = mu diff --git a/openmc/data/correlated.py b/openmc/data/correlated.py index 49d116efd..ba69e6aa6 100644 --- a/openmc/data/correlated.py +++ b/openmc/data/correlated.py @@ -45,7 +45,7 @@ class CorrelatedAngleEnergy(AngleEnergy): """ def __init__(self, breakpoints, interpolation, energy, energy_out, mu): - super(CorrelatedAngleEnergy, self).__init__() + super().__init__() self.breakpoints = breakpoints self.interpolation = interpolation self.energy = energy diff --git a/openmc/data/energy_distribution.py b/openmc/data/energy_distribution.py index e8c92801c..55588bd64 100644 --- a/openmc/data/energy_distribution.py +++ b/openmc/data/energy_distribution.py @@ -114,7 +114,7 @@ class ArbitraryTabulated(EnergyDistribution): """ def __init__(self, energy, pdf): - super(ArbitraryTabulated, self).__init__() + super().__init__() self.energy = energy self.pdf = pdf @@ -182,7 +182,7 @@ class GeneralEvaporation(EnergyDistribution): """ def __init__(self, theta, g, u): - super(GeneralEvaporation, self).__init__() + super().__init__() self.theta = theta self.g = g self.u = u @@ -245,7 +245,7 @@ class MaxwellEnergy(EnergyDistribution): """ def __init__(self, theta, u): - super(MaxwellEnergy, self).__init__() + super().__init__() self.theta = theta self.u = u @@ -378,7 +378,7 @@ class Evaporation(EnergyDistribution): """ def __init__(self, theta, u): - super(Evaporation, self).__init__() + super().__init__() self.theta = theta self.u = u @@ -514,7 +514,7 @@ class WattEnergy(EnergyDistribution): """ def __init__(self, a, b, u): - super(WattEnergy, self).__init__() + super().__init__() self.a = a self.b = b self.u = u @@ -682,7 +682,7 @@ class MadlandNix(EnergyDistribution): """ def __init__(self, efl, efh, tm): - super(MadlandNix, self).__init__() + super().__init__() self.efl = efl self.efh = efh self.tm = tm @@ -805,7 +805,7 @@ class DiscretePhoton(EnergyDistribution): """ def __init__(self, primary_flag, energy, atomic_weight_ratio): - super(DiscretePhoton, self).__init__() + super().__init__() self.primary_flag = primary_flag self.energy = energy self.atomic_weight_ratio = atomic_weight_ratio @@ -914,7 +914,7 @@ class LevelInelastic(EnergyDistribution): """ def __init__(self, threshold, mass_ratio): - super(LevelInelastic, self).__init__() + super().__init__() self.threshold = threshold self.mass_ratio = mass_ratio @@ -1019,7 +1019,7 @@ class ContinuousTabular(EnergyDistribution): """ def __init__(self, breakpoints, interpolation, energy, energy_out): - super(ContinuousTabular, self).__init__() + super().__init__() self.breakpoints = breakpoints self.interpolation = interpolation self.energy = energy diff --git a/openmc/data/kalbach_mann.py b/openmc/data/kalbach_mann.py index de9809df3..30a1c01cd 100644 --- a/openmc/data/kalbach_mann.py +++ b/openmc/data/kalbach_mann.py @@ -53,7 +53,7 @@ class KalbachMann(AngleEnergy): def __init__(self, breakpoints, interpolation, energy, energy_out, precompound, slope): - super(KalbachMann, self).__init__() + super().__init__() self.breakpoints = breakpoints self.interpolation = interpolation self.energy = energy diff --git a/openmc/data/laboratory.py b/openmc/data/laboratory.py index 3c240b88d..18516d9b8 100644 --- a/openmc/data/laboratory.py +++ b/openmc/data/laboratory.py @@ -44,7 +44,7 @@ class LaboratoryAngleEnergy(AngleEnergy): """ def __init__(self, breakpoints, interpolation, energy, mu, energy_out): - super(LaboratoryAngleEnergy, self).__init__() + super().__init__() self.breakpoints = breakpoints self.interpolation = interpolation self.energy = energy diff --git a/openmc/data/resonance.py b/openmc/data/resonance.py index 8feda92df..5e7e0c44d 100644 --- a/openmc/data/resonance.py +++ b/openmc/data/resonance.py @@ -288,8 +288,8 @@ class MultiLevelBreitWigner(ResonanceRange): """ def __init__(self, target_spin, energy_min, energy_max, channel, scattering): - super(MultiLevelBreitWigner, self).__init__( - target_spin, energy_min, energy_max, channel, scattering) + super().__init__(target_spin, energy_min, energy_max, channel, + scattering) self.parameters = None self.q_value = {} self.atomic_weight_ratio = None @@ -490,8 +490,8 @@ class SingleLevelBreitWigner(MultiLevelBreitWigner): """ def __init__(self, target_spin, energy_min, energy_max, channel, scattering): - super(SingleLevelBreitWigner, self).__init__( - target_spin, energy_min, energy_max, channel, scattering) + super().__init__(target_spin, energy_min, energy_max, channel, + scattering) # Set resonance reconstruction function if _reconstruct: @@ -549,8 +549,8 @@ class ReichMoore(ResonanceRange): """ def __init__(self, target_spin, energy_min, energy_max, channel, scattering): - super(ReichMoore, self).__init__( - target_spin, energy_min, energy_max, channel, scattering) + super().__init__(target_spin, energy_min, energy_max, channel, + scattering) self.parameters = None self.angle_distribution = False self.num_l_convergence = 0 @@ -724,8 +724,7 @@ class RMatrixLimited(ResonanceRange): """ def __init__(self, energy_min, energy_max, particle_pairs, spin_groups): - super(RMatrixLimited, self).__init__(0.0, energy_min, energy_max, - None, None) + super().__init__(0.0, energy_min, energy_max, None, None) self.reduced_width = False self.formalism = 3 self.particle_pairs = particle_pairs @@ -931,8 +930,7 @@ class Unresolved(ResonanceRange): """ def __init__(self, target_spin, energy_min, energy_max, scatter): - super(Unresolved, self).__init__( - target_spin, energy_min, energy_max, None, scatter) + super().__init__(target_spin, energy_min, energy_max, None, scatter) self.energies = None self.parameters = None self.add_to_background = False diff --git a/openmc/element.py b/openmc/element.py index 25cb97538..336d1f028 100644 --- a/openmc/element.py +++ b/openmc/element.py @@ -29,7 +29,7 @@ class Element(str): def __new__(cls, name): cv.check_type('element name', name, str) cv.check_length('element name', name, 1, 2) - return super(Element, cls).__new__(cls, name) + return super().__new__(cls, name) @property def name(self): diff --git a/openmc/filter.py b/openmc/filter.py index 8f57a8090..8763515f4 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -64,8 +64,7 @@ class FilterMeta(ABCMeta): namespace[func_name].__doc__ = old_doc # Make the class. - return super(FilterMeta, cls).__new__(cls, name, bases, namespace, - **kwargs) + return super().__new__(cls, name, bases, namespace, **kwargs) class Filter(IDManagerMixin, metaclass=FilterMeta): @@ -672,7 +671,7 @@ class MeshFilter(Filter): def __init__(self, mesh, filter_id=None): self.mesh = mesh - super(MeshFilter, self).__init__(mesh.id, filter_id) + super().__init__(mesh.id, filter_id) @classmethod def from_hdf5(cls, group, **kwargs): @@ -869,7 +868,7 @@ class RealFilter(Filter): # This logic is used when merging tallies with real filters return self.bins[0] >= other.bins[-1] else: - return super(RealFilter, self).__gt__(other) + return super().__gt__(other) @property def num_bins(self): @@ -1127,7 +1126,7 @@ class DistribcellFilter(Filter): def __init__(self, cell, filter_id=None): self._paths = None - super(DistribcellFilter, self).__init__(cell, filter_id) + super().__init__(cell, filter_id) @classmethod def from_hdf5(cls, group, **kwargs): diff --git a/openmc/lattice.py b/openmc/lattice.py index 1bb82f628..0819a8591 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -489,7 +489,7 @@ class RectLattice(Lattice): """ def __init__(self, lattice_id=None, name=''): - super(RectLattice, self).__init__(lattice_id, name) + super().__init__(lattice_id, name) # Initialize Lattice class attributes self._lower_left = None @@ -820,7 +820,7 @@ class HexLattice(Lattice): """ def __init__(self, lattice_id=None, name=''): - super(HexLattice, self).__init__(lattice_id, name) + super().__init__(lattice_id, name) # Initialize Lattice class attributes self._num_rings = None diff --git a/openmc/macroscopic.py b/openmc/macroscopic.py index f5bd90f3a..2a5a22752 100644 --- a/openmc/macroscopic.py +++ b/openmc/macroscopic.py @@ -18,7 +18,7 @@ class Macroscopic(str): def __new__(cls, name): check_type('name', name, str) - return super(Macroscopic, cls).__new__(cls, name) + return super().__new__(cls, name) @property def name(self): diff --git a/openmc/material.py b/openmc/material.py index cb3024447..a3fa85453 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -885,7 +885,7 @@ class Materials(cv.CheckedList): """ def __init__(self, materials=None): - super(Materials, self).__init__(Material, 'materials collection') + super().__init__(Material, 'materials collection') self._cross_sections = None self._multipole_library = None @@ -954,7 +954,7 @@ class Materials(cv.CheckedList): Material to append """ - super(Materials, self).append(material) + super().append(material) def insert(self, index, material): """Insert material before index @@ -967,7 +967,7 @@ class Materials(cv.CheckedList): Material to insert """ - super(Materials, self).insert(index, material) + super().insert(index, material) def remove_material(self, material): """Remove a material from the file diff --git a/openmc/mgxs/mdgxs.py b/openmc/mgxs/mdgxs.py index 7799183ff..dfd0d192d 100644 --- a/openmc/mgxs/mdgxs.py +++ b/openmc/mgxs/mdgxs.py @@ -129,8 +129,8 @@ class MDGXS(MGXS): def __init__(self, domain=None, domain_type=None, energy_groups=None, delayed_groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(MDGXS, self).__init__(domain, domain_type, energy_groups, - by_nuclide, name, num_polar, num_azimuthal) + super().__init__(domain, domain_type, energy_groups, by_nuclide, name, + num_polar, num_azimuthal) self._delayed_groups = None @@ -547,7 +547,7 @@ class MDGXS(MGXS): """ - merged_mdgxs = super(MDGXS, self).merge(other) + merged_mdgxs = super().merge(other) # Merge delayed groups if self.delayed_groups != other.delayed_groups: @@ -577,7 +577,7 @@ class MDGXS(MGXS): """ if self.delayed_groups is None: - super(MDGXS, self).print_xs(subdomains, nuclides, xs_type) + super().print_xs(subdomains, nuclides, xs_type) return # Construct a collection of the subdomains to report @@ -1007,9 +1007,8 @@ class ChiDelayed(MDGXS): def __init__(self, domain=None, domain_type=None, energy_groups=None, delayed_groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(ChiDelayed, self).__init__(domain, domain_type, energy_groups, - delayed_groups, by_nuclide, name, - num_polar, num_azimuthal) + super().__init__(domain, domain_type, energy_groups, delayed_groups, + by_nuclide, name, num_polar, num_azimuthal) self._rxn_type = 'chi-delayed' self._estimator = 'analog' @@ -1055,7 +1054,7 @@ class ChiDelayed(MDGXS): # Compute chi self._xs_tally = self.rxn_rate_tally / delayed_nu_fission_in - super(ChiDelayed, self)._compute_xs() + super()._compute_xs() # Add the coarse energy filter back to the nu-fission tally delayed_nu_fission_in.filters.append(energy_filter) @@ -1127,8 +1126,7 @@ class ChiDelayed(MDGXS): delayed_nu_fission_in.remove_filter(energy_filter) # Call super class method and null out derived tallies - slice_xs = super(ChiDelayed, self).get_slice(nuclides, groups, - delayed_groups) + slice_xs = super().get_slice(nuclides, groups, delayed_groups) slice_xs._rxn_rate_tally = None slice_xs._xs_tally = None @@ -1521,10 +1519,8 @@ class DelayedNuFissionXS(MDGXS): def __init__(self, domain=None, domain_type=None, energy_groups=None, delayed_groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(DelayedNuFissionXS, self).__init__(domain, domain_type, - energy_groups, delayed_groups, - by_nuclide, name, num_polar, - num_azimuthal) + super().__init__(domain, domain_type, energy_groups, delayed_groups, + by_nuclide, name, num_polar, num_azimuthal) self._rxn_type = 'delayed-nu-fission' @@ -1657,9 +1653,8 @@ class Beta(MDGXS): def __init__(self, domain=None, domain_type=None, energy_groups=None, delayed_groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(Beta, self).__init__(domain, domain_type, energy_groups, - delayed_groups, by_nuclide, name, num_polar, - num_azimuthal) + super().__init__(domain, domain_type, energy_groups, delayed_groups, + by_nuclide, name, num_polar, num_azimuthal) self._rxn_type = 'beta' @property @@ -1685,7 +1680,7 @@ class Beta(MDGXS): # Compute beta self._xs_tally = self.rxn_rate_tally / nu_fission - super(Beta, self)._compute_xs() + super()._compute_xs() return self._xs_tally @@ -1841,9 +1836,8 @@ class DecayRate(MDGXS): def __init__(self, domain=None, domain_type=None, energy_groups=None, delayed_groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(DecayRate, self).__init__(domain, domain_type, energy_groups, - delayed_groups, by_nuclide, name, - num_polar, num_azimuthal) + super().__init__(domain, domain_type, energy_groups, delayed_groups, + by_nuclide, name, num_polar, num_azimuthal) self._rxn_type = 'decay-rate' @property @@ -1878,7 +1872,7 @@ class DecayRate(MDGXS): # Compute the decay rate self._xs_tally = self.rxn_rate_tally / delayed_nu_fission - super(DecayRate, self)._compute_xs() + super()._compute_xs() return self._xs_tally @@ -2261,8 +2255,7 @@ class MatrixMDGXS(MDGXS): """ # Call super class method and null out derived tallies - slice_xs = super(MatrixMDGXS, self).get_slice(nuclides, in_groups, - delayed_groups) + slice_xs = super().get_slice(nuclides, in_groups, delayed_groups) slice_xs._rxn_rate_tally = None slice_xs._xs_tally = None @@ -2609,12 +2602,8 @@ class DelayedNuFissionMatrixXS(MatrixMDGXS): def __init__(self, domain=None, domain_type=None, energy_groups=None, delayed_groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(DelayedNuFissionMatrixXS, self).__init__(domain, domain_type, - energy_groups, - delayed_groups, - by_nuclide, name, - num_polar, - num_azimuthal) + super().__init__(domain, domain_type, energy_groups, delayed_groups, + by_nuclide, name, num_polar, num_azimuthal) self._rxn_type = 'delayed-nu-fission' self._hdf5_key = 'delayed-nu-fission matrix' self._estimator = 'analog' diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 949f0fba0..ecedc8e63 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -2292,7 +2292,7 @@ class MatrixMGXS(MGXS): """ # Call super class method and null out derived tallies - slice_xs = super(MatrixMGXS, self).get_slice(nuclides, in_groups) + slice_xs = super().get_slice(nuclides, in_groups) slice_xs._rxn_rate_tally = None slice_xs._xs_tally = None @@ -2567,9 +2567,8 @@ class TotalXS(MGXS): def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(TotalXS, self).__init__(domain, domain_type, - groups, by_nuclide, name, num_polar, - num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'total' @@ -2704,9 +2703,8 @@ class TransportXS(MGXS): def __init__(self, domain=None, domain_type=None, groups=None, nu=False, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(TransportXS, self).__init__(domain, domain_type, - groups, by_nuclide, name, num_polar, - num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) # Use tracklength estimators for the total MGXS term, and # analog estimators for the transport correction term @@ -2715,7 +2713,7 @@ class TransportXS(MGXS): self.nu = nu def __deepcopy__(self, memo): - clone = super(TransportXS, self).__deepcopy__(memo) + clone = super().__deepcopy__(memo) clone._nu = self.nu return clone @@ -2912,9 +2910,8 @@ class AbsorptionXS(MGXS): def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(AbsorptionXS, self).__init__(domain, domain_type, - groups, by_nuclide, name, num_polar, - num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'absorption' @@ -3039,9 +3036,8 @@ class CaptureXS(MGXS): def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(CaptureXS, self).__init__(domain, domain_type, - groups, by_nuclide, name, num_polar, - num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'capture' @property @@ -3194,16 +3190,15 @@ class FissionXS(MGXS): def __init__(self, domain=None, domain_type=None, groups=None, nu=False, prompt=False, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(FissionXS, self).__init__(domain, domain_type, - groups, by_nuclide, name, num_polar, - num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) self._nu = False self._prompt = False self.nu = nu self.prompt = prompt def __deepcopy__(self, memo): - clone = super(FissionXS, self).__deepcopy__(memo) + clone = super().__deepcopy__(memo) clone._nu = self.nu clone._prompt = self.prompt return clone @@ -3362,9 +3357,8 @@ class KappaFissionXS(MGXS): def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(KappaFissionXS, self).__init__(domain, domain_type, - groups, by_nuclide, name, - num_polar, num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'kappa-fission' @@ -3495,13 +3489,12 @@ class ScatterXS(MGXS): def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1, nu=False): - super(ScatterXS, self).__init__(domain, domain_type, - groups, by_nuclide, name, - num_polar, num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) self.nu = nu def __deepcopy__(self, memo): - clone = super(ScatterXS, self).__deepcopy__(memo) + clone = super().__deepcopy__(memo) clone._nu = self.nu return clone @@ -3712,9 +3705,8 @@ class ScatterMatrixXS(MatrixMGXS): def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1, nu=False): - super(ScatterMatrixXS, self).__init__(domain, domain_type, - groups, by_nuclide, name, - num_polar, num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) self._formulation = 'simple' self._correction = 'P0' self._scatter_format = 'legendre' @@ -3725,7 +3717,7 @@ class ScatterMatrixXS(MatrixMGXS): self.nu = nu def __deepcopy__(self, memo): - clone = super(ScatterMatrixXS, self).__deepcopy__(memo) + clone = super().__deepcopy__(memo) clone._formulation = self.formulation clone._correction = self.correction clone._scatter_format = self.scatter_format @@ -3816,7 +3808,7 @@ class ScatterMatrixXS(MatrixMGXS): @property def tally_keys(self): if self.formulation == 'simple': - return super(ScatterMatrixXS, self).tally_keys + return super().tally_keys else: # Add keys for groupwise scattering cross section tally_keys = ['flux (tracklength)', 'scatter'] @@ -4146,7 +4138,7 @@ class ScatterMatrixXS(MatrixMGXS): [score_prefix + '{}'.format(i) for i in range(self.legendre_order + 1)] - super(ScatterMatrixXS, self).load_from_statepoint(statepoint) + super().load_from_statepoint(statepoint) def get_slice(self, nuclides=[], in_groups=[], out_groups=[], legendre_order='same'): @@ -4186,7 +4178,7 @@ class ScatterMatrixXS(MatrixMGXS): """ # Call super class method and null out derived tallies - slice_xs = super(ScatterMatrixXS, self).get_slice(nuclides, in_groups) + slice_xs = super().get_slice(nuclides, in_groups) slice_xs._rxn_rate_tally = None slice_xs._xs_tally = None @@ -4477,8 +4469,7 @@ class ScatterMatrixXS(MatrixMGXS): """ - df = super(ScatterMatrixXS, self).get_pandas_dataframe( - groups, nuclides, xs_type, paths) + df = super().get_pandas_dataframe(groups, nuclides, xs_type, paths) if self.scatter_format == 'legendre': # Add a moment column to dataframe @@ -4802,9 +4793,8 @@ class MultiplicityMatrixXS(MatrixMGXS): def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(MultiplicityMatrixXS, self).__init__(domain, domain_type, groups, - by_nuclide, name, num_polar, - num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'multiplicity matrix' self._estimator = 'analog' self._valid_estimators = ['analog'] @@ -4839,7 +4829,7 @@ class MultiplicityMatrixXS(MatrixMGXS): # Compute the multiplicity self._xs_tally = self.rxn_rate_tally / scatter - super(MultiplicityMatrixXS, self)._compute_xs() + super()._compute_xs() return self._xs_tally @@ -4968,9 +4958,8 @@ class ScatterProbabilityMatrix(MatrixMGXS): def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(ScatterProbabilityMatrix, self).__init__( - domain, domain_type, groups, by_nuclide, - name, num_polar, num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, + name, num_polar, num_azimuthal) self._rxn_type = 'scatter' self._hdf5_key = 'scatter probability matrix' @@ -5012,7 +5001,7 @@ class ScatterProbabilityMatrix(MatrixMGXS): # Compute the group-to-group probabilities self._xs_tally = self.tallies[self.rxn_type] / norm - super(ScatterProbabilityMatrix, self)._compute_xs() + super()._compute_xs() return self._xs_tally @@ -5142,9 +5131,8 @@ class NuFissionMatrixXS(MatrixMGXS): def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1, prompt=False): - super(NuFissionMatrixXS, self).__init__(domain, domain_type, - groups, by_nuclide, name, - num_polar, num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) if not prompt: self._rxn_type = 'nu-fission' self._hdf5_key = 'nu-fission matrix' @@ -5165,7 +5153,7 @@ class NuFissionMatrixXS(MatrixMGXS): self._prompt = prompt def __deepcopy__(self, memo): - clone = super(NuFissionMatrixXS, self).__deepcopy__(memo) + clone = super().__deepcopy__(memo) clone._prompt = self.prompt return clone @@ -5299,8 +5287,8 @@ class Chi(MGXS): def __init__(self, domain=None, domain_type=None, groups=None, prompt=False, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(Chi, self).__init__(domain, domain_type, groups, by_nuclide, - name, num_polar, num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) if not prompt: self._rxn_type = 'chi' else: @@ -5310,7 +5298,7 @@ class Chi(MGXS): self.prompt = prompt def __deepcopy__(self, memo): - clone = super(Chi, self).__deepcopy__(memo) + clone = super().__deepcopy__(memo) clone._prompt = self.prompt return clone @@ -5440,7 +5428,7 @@ class Chi(MGXS): nu_fission_in.remove_filter(energy_filter) # Call super class method and null out derived tallies - slice_xs = super(Chi, self).get_slice(nuclides, groups) + slice_xs = super().get_slice(nuclides, groups) slice_xs._rxn_rate_tally = None slice_xs._xs_tally = None @@ -5718,8 +5706,7 @@ class Chi(MGXS): """ # Build the dataframe using the parent class method - df = super(Chi, self).get_pandas_dataframe( - groups, nuclides, xs_type, paths=paths) + df = super().get_pandas_dataframe(groups, nuclides, xs_type, paths=paths) # If user requested micro cross sections, multiply by the atom # densities to cancel out division made by the parent class method @@ -5877,9 +5864,8 @@ class InverseVelocity(MGXS): def __init__(self, domain=None, domain_type=None, groups=None, by_nuclide=False, name='', num_polar=1, num_azimuthal=1): - super(InverseVelocity, self).__init__(domain, domain_type, - groups, by_nuclide, name, - num_polar, num_azimuthal) + super().__init__(domain, domain_type, groups, by_nuclide, name, + num_polar, num_azimuthal) self._rxn_type = 'inverse-velocity' def get_units(self, xs_type='macro'): diff --git a/openmc/model/triso.py b/openmc/model/triso.py index 0fe138bd7..fab2f54cc 100644 --- a/openmc/model/triso.py +++ b/openmc/model/triso.py @@ -45,7 +45,7 @@ class TRISO(openmc.Cell): def __init__(self, outer_radius, fill, center=(0., 0., 0.)): self._surface = openmc.Sphere(R=outer_radius) - super(TRISO, self).__init__(fill=fill, region=-self._surface) + super().__init__(fill=fill, region=-self._surface) self.center = np.asarray(center) @property @@ -245,7 +245,7 @@ class _CubicDomain(_Domain): """ def __init__(self, length, particle_radius, center=[0., 0., 0.]): - super(_CubicDomain, self).__init__(particle_radius, center) + super().__init__(particle_radius, center) self.length = length @property @@ -324,7 +324,7 @@ class _CylindricalDomain(_Domain): """ def __init__(self, length, radius, particle_radius, center=[0., 0., 0.]): - super(_CylindricalDomain, self).__init__(particle_radius, center) + super().__init__(particle_radius, center) self.length = length self.radius = radius @@ -414,7 +414,7 @@ class _SphericalDomain(_Domain): """ def __init__(self, radius, particle_radius, center=[0., 0., 0.]): - super(_SphericalDomain, self).__init__(particle_radius, center) + super().__init__(particle_radius, center) self.radius = radius @property diff --git a/openmc/nuclide.py b/openmc/nuclide.py index 73247e1aa..f7c725040 100644 --- a/openmc/nuclide.py +++ b/openmc/nuclide.py @@ -32,7 +32,7 @@ class Nuclide(str): '"{}" is being renamed as "{}".'.format(orig_name, name) warnings.warn(msg) - return super(Nuclide, cls).__new__(cls, name) + return super().__new__(cls, name) @property def name(self): diff --git a/openmc/plots.py b/openmc/plots.py index bfff2d7e9..44d68d72f 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -673,7 +673,7 @@ class Plots(cv.CheckedList): """ def __init__(self, plots=None): - super(Plots, self).__init__(Plot, 'plots collection') + super().__init__(Plot, 'plots collection') self._plots_file = ET.Element("plots") if plots is not None: self += plots @@ -704,7 +704,7 @@ class Plots(cv.CheckedList): Plot to append """ - super(Plots, self).append(plot) + super().append(plot) def insert(self, index, plot): """Insert plot before index @@ -717,7 +717,7 @@ class Plots(cv.CheckedList): Plot to insert """ - super(Plots, self).insert(index, plot) + super().insert(index, plot) def remove_plot(self, plot): """Remove a plot from the file. diff --git a/openmc/stats/multivariate.py b/openmc/stats/multivariate.py index ba2debaea..1fe883866 100644 --- a/openmc/stats/multivariate.py +++ b/openmc/stats/multivariate.py @@ -75,7 +75,7 @@ class PolarAzimuthal(UnitSphere): """ def __init__(self, mu=None, phi=None, reference_uvw=[0., 0., 1.]): - super(PolarAzimuthal, self).__init__(reference_uvw) + super().__init__(reference_uvw) if mu is not None: self.mu = mu else: @@ -128,7 +128,7 @@ class Isotropic(UnitSphere): """ def __init__(self): - super(Isotropic, self).__init__() + super().__init__() def to_xml_element(self): """Return XML representation of the isotropic distribution @@ -161,7 +161,7 @@ class Monodirectional(UnitSphere): def __init__(self, reference_uvw=[1., 0., 0.]): - super(Monodirectional, self).__init__(reference_uvw) + super().__init__(reference_uvw) def to_xml_element(self): """Return XML representation of the monodirectional distribution @@ -222,7 +222,7 @@ class CartesianIndependent(Spatial): def __init__(self, x, y, z): - super(CartesianIndependent, self).__init__() + super().__init__() self.x = x self.y = y self.z = z @@ -298,7 +298,7 @@ class Box(Spatial): def __init__(self, lower_left, upper_right, only_fissionable=False): - super(Box, self).__init__() + super().__init__() self.lower_left = lower_left self.upper_right = upper_right self.only_fissionable = only_fissionable @@ -371,7 +371,7 @@ class Point(Spatial): """ def __init__(self, xyz=(0., 0., 0.)): - super(Point, self).__init__() + super().__init__() self.xyz = xyz @property diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index 47a281d21..6996970af 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -57,7 +57,7 @@ class Discrete(Univariate): """ def __init__(self, x, p): - super(Discrete, self).__init__() + super().__init__() self.x = x self.p = p @@ -131,7 +131,7 @@ class Uniform(Univariate): """ def __init__(self, a=0.0, b=1.0): - super(Uniform, self).__init__() + super().__init__() self.a = a self.b = b @@ -202,7 +202,7 @@ class Maxwell(Univariate): """ def __init__(self, theta): - super(Maxwell, self).__init__() + super().__init__() self.theta = theta def __len__(self): @@ -262,7 +262,7 @@ class Watt(Univariate): """ def __init__(self, a=0.988e6, b=2.249e-6): - super(Watt, self).__init__() + super().__init__() self.a = a self.b = b @@ -342,7 +342,7 @@ class Tabular(Univariate): def __init__(self, x, p, interpolation='linear-linear', ignore_negative=False): - super(Tabular, self).__init__() + super().__init__() self._ignore_negative = ignore_negative self.x = x self.p = p @@ -470,7 +470,7 @@ class Mixture(Univariate): """ def __init__(self, probability, distribution): - super(Mixture, self).__init__() + super().__init__() self.probability = probability self.distribution = distribution diff --git a/openmc/surface.py b/openmc/surface.py index 694e9fd22..ad5053e37 100644 --- a/openmc/surface.py +++ b/openmc/surface.py @@ -326,7 +326,7 @@ class Plane(Surface): def __init__(self, surface_id=None, boundary_type='transmission', A=1., B=0., C=0., D=0., name=''): - super(Plane, self).__init__(surface_id, boundary_type, name=name) + super().__init__(surface_id, boundary_type, name=name) self._type = 'plane' self._coeff_keys = ['A', 'B', 'C', 'D'] @@ -410,7 +410,7 @@ class Plane(Surface): XML element containing source data """ - element = super(Plane, self).to_xml_element() + element = super().to_xml_element() # Add periodic surface pair information if self.boundary_type == 'periodic': @@ -460,7 +460,7 @@ class XPlane(Plane): def __init__(self, surface_id=None, boundary_type='transmission', x0=0., name=''): - super(XPlane, self).__init__(surface_id, boundary_type, name=name) + super().__init__(surface_id, boundary_type, name=name) self._type = 'x-plane' self._coeff_keys = ['x0'] @@ -566,7 +566,7 @@ class YPlane(Plane): def __init__(self, surface_id=None, boundary_type='transmission', y0=0., name=''): # Initialize YPlane class attributes - super(YPlane, self).__init__(surface_id, boundary_type, name=name) + super().__init__(surface_id, boundary_type, name=name) self._type = 'y-plane' self._coeff_keys = ['y0'] @@ -672,7 +672,7 @@ class ZPlane(Plane): def __init__(self, surface_id=None, boundary_type='transmission', z0=0., name=''): # Initialize ZPlane class attributes - super(ZPlane, self).__init__(surface_id, boundary_type, name=name) + super().__init__(surface_id, boundary_type, name=name) self._type = 'z-plane' self._coeff_keys = ['z0'] @@ -773,7 +773,7 @@ class Cylinder(Surface, metaclass=ABCMeta): """ def __init__(self, surface_id=None, boundary_type='transmission', R=1., name=''): - super(Cylinder, self).__init__(surface_id, boundary_type, name=name) + super().__init__(surface_id, boundary_type, name=name) self._coeff_keys = ['R'] self.r = R @@ -833,7 +833,7 @@ class XCylinder(Cylinder): def __init__(self, surface_id=None, boundary_type='transmission', y0=0., z0=0., R=1., name=''): - super(XCylinder, self).__init__(surface_id, boundary_type, R, name=name) + super().__init__(surface_id, boundary_type, R, name=name) self._type = 'x-cylinder' self._coeff_keys = ['y0', 'z0', 'R'] @@ -955,7 +955,7 @@ class YCylinder(Cylinder): def __init__(self, surface_id=None, boundary_type='transmission', x0=0., z0=0., R=1., name=''): - super(YCylinder, self).__init__(surface_id, boundary_type, R, name=name) + super().__init__(surface_id, boundary_type, R, name=name) self._type = 'y-cylinder' self._coeff_keys = ['x0', 'z0', 'R'] @@ -1077,7 +1077,7 @@ class ZCylinder(Cylinder): def __init__(self, surface_id=None, boundary_type='transmission', x0=0., y0=0., R=1., name=''): - super(ZCylinder, self).__init__(surface_id, boundary_type, R, name=name) + super().__init__(surface_id, boundary_type, R, name=name) self._type = 'z-cylinder' self._coeff_keys = ['x0', 'y0', 'R'] @@ -1203,7 +1203,7 @@ class Sphere(Surface): def __init__(self, surface_id=None, boundary_type='transmission', x0=0., y0=0., z0=0., R=1., name=''): - super(Sphere, self).__init__(surface_id, boundary_type, name=name) + super().__init__(surface_id, boundary_type, name=name) self._type = 'sphere' self._coeff_keys = ['x0', 'y0', 'z0', 'R'] @@ -1350,7 +1350,7 @@ class Cone(Surface, metaclass=ABCMeta): """ def __init__(self, surface_id=None, boundary_type='transmission', x0=0., y0=0., z0=0., R2=1., name=''): - super(Cone, self).__init__(surface_id, boundary_type, name=name) + super().__init__(surface_id, boundary_type, name=name) self._coeff_keys = ['x0', 'y0', 'z0', 'R2'] self.x0 = x0 @@ -1445,7 +1445,7 @@ class XCone(Cone): def __init__(self, surface_id=None, boundary_type='transmission', x0=0., y0=0., z0=0., R2=1., name=''): - super(XCone, self).__init__(surface_id, boundary_type, x0, y0, + super().__init__(surface_id, boundary_type, x0, y0, z0, R2, name=name) self._type = 'x-cone' @@ -1521,7 +1521,7 @@ class YCone(Cone): def __init__(self, surface_id=None, boundary_type='transmission', x0=0., y0=0., z0=0., R2=1., name=''): - super(YCone, self).__init__(surface_id, boundary_type, x0, y0, z0, + super().__init__(surface_id, boundary_type, x0, y0, z0, R2, name=name) self._type = 'y-cone' @@ -1597,7 +1597,7 @@ class ZCone(Cone): def __init__(self, surface_id=None, boundary_type='transmission', x0=0., y0=0., z0=0., R2=1., name=''): - super(ZCone, self).__init__(surface_id, boundary_type, x0, y0, z0, + super().__init__(surface_id, boundary_type, x0, y0, z0, R2, name=name) self._type = 'z-cone' @@ -1662,7 +1662,7 @@ class Quadric(Surface): def __init__(self, surface_id=None, boundary_type='transmission', a=0., b=0., c=0., d=0., e=0., f=0., g=0., h=0., j=0., k=0., name=''): - super(Quadric, self).__init__(surface_id, boundary_type, name=name) + super().__init__(surface_id, boundary_type, name=name) self._type = 'quadric' self._coeff_keys = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k'] diff --git a/openmc/tallies.py b/openmc/tallies.py index 17c298a8d..e838de665 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3242,7 +3242,7 @@ class Tallies(cv.CheckedList): """ def __init__(self, tallies=None): - super(Tallies, self).__init__(Tally, 'tallies collection') + super().__init__(Tally, 'tallies collection') if tallies is not None: self += tallies @@ -3299,10 +3299,10 @@ class Tallies(cv.CheckedList): # If no mergeable tally was found, simply add this tally if not merged: - super(Tallies, self).append(tally) + super().append(tally) else: - super(Tallies, self).append(tally) + super().append(tally) def insert(self, index, item): """Insert tally before index @@ -3315,7 +3315,7 @@ class Tallies(cv.CheckedList): Tally to insert """ - super(Tallies, self).insert(index, item) + super().insert(index, item) def remove_tally(self, tally): """Remove a tally from the collection From 7263d80c2c231ac35325ebb7bae35569589c10a1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 24 Dec 2017 16:36:59 +0700 Subject: [PATCH 183/282] Update installation instructions --- docs/source/usersguide/install.rst | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 638d42501..9b11d1dce 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -385,8 +385,7 @@ Python package in the same location as the ``openmc`` executable (for example, if you are installing the package into a `virtual environment `_). The easiest way to install the :mod:`openmc` Python package is to use pip_, which is included by default in -Python 2.7 and Python 3.4+. From the root directory of the OpenMC -distribution/repository, run: +Python 3.4+. From the root directory of the OpenMC distribution/repository, run: .. code-block:: sh @@ -414,18 +413,14 @@ to install the Python package in :ref:`"editable" mode `. Prerequisites ------------- -The Python API works with either Python 2.7 or Python 3.2+. 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. +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. .. admonition:: Required :class: error - `six `_ - The Python API works with both Python 2.7+ and 3.2+. To do so, the six - compatibility library is used. - `NumPy `_ NumPy is used extensively within the Python API for its powerful N-dimensional array. From 0aee52c5ab8ad077e20542a832a6aaccf34f48e1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 29 Dec 2017 14:03:11 -0600 Subject: [PATCH 184/282] Remove deprecated methods --- openmc/cell.py | 44 ------ openmc/material.py | 52 ------- openmc/plots.py | 34 ----- openmc/settings.py | 49 ------- openmc/tallies.py | 167 ---------------------- openmc/trigger.py | 17 --- tests/regression_tests/diff_tally/test.py | 36 ++--- 7 files changed, 13 insertions(+), 386 deletions(-) diff --git a/openmc/cell.py b/openmc/cell.py index 087f2816d..838d419b6 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -283,50 +283,6 @@ class Cell(IDManagerMixin): cv.check_type('cell volume', volume, Real) self._volume = volume - def add_surface(self, surface, halfspace): - """Add a half-space to the list of half-spaces whose intersection defines the - cell. - - .. deprecated:: 0.7.1 - Use the :attr:`Cell.region` property to directly specify a Region - expression. - - Parameters - ---------- - surface : openmc.Surface - Quadric surface dividing space - halfspace : {-1, 1} - Indicate whether the negative or positive half-space is to be used - - """ - - warnings.warn("Cell.add_surface(...) has been deprecated and may be " - "removed in a future version. The region for a Cell " - "should be defined using the region property directly.", - DeprecationWarning) - - if not isinstance(surface, openmc.Surface): - msg = 'Unable to add Surface "{0}" to Cell ID="{1}" since it is ' \ - 'not a Surface object'.format(surface, self._id) - raise ValueError(msg) - - if halfspace not in [-1, +1]: - msg = 'Unable to add Surface "{0}" to Cell ID="{1}" with halfspace ' \ - '"{2}" since it is not +/-1'.format(surface, self._id, halfspace) - raise ValueError(msg) - - # If no region has been assigned, simply use the half-space. Otherwise, - # take the intersection of the current region and the half-space - # specified - region = +surface if halfspace == 1 else -surface - if self.region is None: - self.region = region - else: - if isinstance(self.region, Intersection): - self.region &= region - else: - self.region = Intersection(self.region, region) - def add_volume_information(self, volume_calc): """Add volume information to a cell. diff --git a/openmc/material.py b/openmc/material.py index a3fa85453..e409d6536 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -910,41 +910,6 @@ class Materials(cv.CheckedList): cv.check_type('cross sections', multipole_library, str) self._multipole_library = multipole_library - def add_material(self, material): - """Append material to collection - - .. deprecated:: 0.8 - Use :meth:`Materials.append` instead. - - Parameters - ---------- - material : openmc.Material - Material to add - - """ - warnings.warn("Materials.add_material(...) has been deprecated and may be " - "removed in a future version. Use Material.append(...) " - "instead.", DeprecationWarning) - self.append(material) - - def add_materials(self, materials): - """Add multiple materials to the collection - - .. deprecated:: 0.8 - Use compound assignment instead. - - Parameters - ---------- - materials : Iterable of openmc.Material - Materials to add - - """ - warnings.warn("Materials.add_materials(...) has been deprecated and may be " - "removed in a future version. Use compound assignment " - "instead.", DeprecationWarning) - for material in materials: - self.append(material) - def append(self, material): """Append material to collection @@ -969,23 +934,6 @@ class Materials(cv.CheckedList): """ super().insert(index, material) - def remove_material(self, material): - """Remove a material from the file - - .. deprecated:: 0.8 - Use :meth:`Materials.remove` instead. - - Parameters - ---------- - material : openmc.Material - Material to remove - - """ - warnings.warn("Materials.remove_material(...) has been deprecated and " - "may be removed in a future version. Use " - "Materials.remove(...) instead.", DeprecationWarning) - self.remove(material) - def make_isotropic_in_lab(self): for material in self: material.make_isotropic_in_lab() diff --git a/openmc/plots.py b/openmc/plots.py index 44d68d72f..159e92078 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -678,23 +678,6 @@ class Plots(cv.CheckedList): if plots is not None: self += plots - def add_plot(self, plot): - """Add a plot to the file. - - .. deprecated:: 0.8 - Use :meth:`Plots.append` instead. - - Parameters - ---------- - plot : openmc.Plot - Plot to add - - """ - warnings.warn("Plots.add_plot(...) has been deprecated and may be " - "removed in a future version. Use Plots.append(...) " - "instead.", DeprecationWarning) - self.append(plot) - def append(self, plot): """Append plot to collection @@ -719,23 +702,6 @@ class Plots(cv.CheckedList): """ super().insert(index, plot) - def remove_plot(self, plot): - """Remove a plot from the file. - - .. deprecated:: 0.8 - Use :meth:`Plots.remove` instead. - - Parameters - ---------- - plot : openmc.Plot - Plot to remove - - """ - warnings.warn("Plots.remove_plot(...) has been deprecated and may be " - "removed in a future version. Use Plots.remove(...) " - "instead.", DeprecationWarning) - self.remove(plot) - def colorize(self, geometry, seed=1): """Generate a consistent color scheme for each domain in each plot. diff --git a/openmc/settings.py b/openmc/settings.py index 1bbc7a4ec..b9562b7dd 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -28,13 +28,6 @@ class Settings(object): deviation. create_fission_neutrons : bool Indicate whether fission neutrons should be created or not. - cross_sections : str - Indicates the path to an XML cross section listing file (usually named - cross_sections.xml). If it is not set, the - :envvar:`OPENMC_CROSS_SECTIONS` environment variable will be used for - continuous-energy calculations and - :envvar:`OPENMC_MG_CROSS_SECTIONS` will be used for multi-group - calculations to find the path to the XML cross section file. cutoff : dict Dictionary defining weight cutoff and energy cutoff. The dictionary may have three keys, 'weight', 'weight_avg' and 'energy'. Value for 'weight' @@ -63,11 +56,6 @@ class Settings(object): Number of bins for logarithmic energy grid search max_order : None or int Maximum scattering order to apply globally when in multi-group mode. - multipole_library : str - Indicates the path to a directory containing a windowed multipole - cross section library. If it is not set, the - :envvar:`OPENMC_MULTIPOLE_LIBRARY` environment variable will be used. A - multipole library is optional. no_reduce : bool Indicate that all user-defined and global tallies should not be reduced across processes in a parallel calculation. @@ -268,14 +256,6 @@ class Settings(object): def confidence_intervals(self): return self._confidence_intervals - @property - def cross_sections(self): - return self._cross_sections - - @property - def multipole_library(self): - return self._multipole_library - @property def ptables(self): return self._ptables @@ -505,23 +485,6 @@ class Settings(object): cv.check_type('confidence interval', confidence_intervals, bool) self._confidence_intervals = confidence_intervals - @cross_sections.setter - def cross_sections(self, cross_sections): - warnings.warn('Settings.cross_sections has been deprecated and will be ' - 'removed in a future version. Materials.cross_sections ' - 'should defined instead.', DeprecationWarning) - cv.check_type('cross sections', cross_sections, str) - self._cross_sections = cross_sections - - @multipole_library.setter - def multipole_library(self, multipole_library): - warnings.warn('Settings.multipole_library has been deprecated and will ' - 'be removed in a future version. ' - 'Materials.multipole_library should defined instead.', - DeprecationWarning) - cv.check_type('multipole library', multipole_library, str) - self._multipole_library = multipole_library - @ptables.setter def ptables(self, ptables): cv.check_type('probability tables', ptables, bool) @@ -814,16 +777,6 @@ class Settings(object): element = ET.SubElement(root, "confidence_intervals") element.text = str(self._confidence_intervals).lower() - def _create_cross_sections_subelement(self, root): - if self._cross_sections is not None: - element = ET.SubElement(root, "cross_sections") - element.text = str(self._cross_sections) - - def _create_multipole_library_subelement(self, root): - if self._multipole_library is not None: - element = ET.SubElement(root, "multipole_library") - element.text = str(self._multipole_library) - def _create_ptables_subelement(self, root): if self._ptables is not None: element = ET.SubElement(root, "ptables") @@ -988,8 +941,6 @@ class Settings(object): self._create_statepoint_subelement(root_element) self._create_sourcepoint_subelement(root_element) self._create_confidence_intervals(root_element) - self._create_cross_sections_subelement(root_element) - self._create_multipole_library_subelement(root_element) self._create_energy_mode_subelement(root_element) self._create_max_order_subelement(root_element) self._create_ptables_subelement(root_element) diff --git a/openmc/tallies.py b/openmc/tallies.py index e838de665..61be38fb2 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -332,26 +332,6 @@ class Tally(IDManagerMixin): self._triggers = cv.CheckedList(openmc.Trigger, 'tally triggers', triggers) - def add_trigger(self, trigger): - """Add a tally trigger to the tally - - .. deprecated:: 0.8 - Use the Tally.triggers property directly, i.e., - Tally.triggers.append(...) - - Parameters - ---------- - trigger : openmc.Trigger - Trigger to add - - """ - - warnings.warn('Tally.add_trigger(...) has been deprecated and may be ' - 'removed in a future version. Tally triggers should be ' - 'defined using the triggers property directly.', - DeprecationWarning) - self.triggers.append(trigger) - @name.setter def name(self, name): if name is not None: @@ -413,80 +393,6 @@ class Tally(IDManagerMixin): self._scores = cv.CheckedList(_SCORE_CLASSES, 'tally scores', scores) - def add_filter(self, new_filter): - """Add a filter to the tally - - .. deprecated:: 0.8 - Use the Tally.filters property directly, i.e., - Tally.filters.append(...) - - Parameters - ---------- - new_filter : Filter, CrossFilter or AggregateFilter - A filter to specify a discretization of the tally across some - dimension (e.g., 'energy', 'cell'). The filter should be a Filter - object when a user is adding filters to a Tally for input file - generation or when the Tally is created from a StatePoint. The - filter may be a CrossFilter or AggregateFilter for derived tallies - created by tally arithmetic. - - """ - - warnings.warn('Tally.add_filter(...) has been deprecated and may be ' - 'removed in a future version. Tally filters should be ' - 'defined using the filters property directly.', - DeprecationWarning) - self.filters.append(new_filter) - - def add_nuclide(self, nuclide): - """Specify that scores for a particular nuclide should be accumulated - - .. deprecated:: 0.8 - Use the Tally.nuclides property directly, i.e., - Tally.nuclides.append(...) - - Parameters - ---------- - nuclide : str, openmc.Nuclide, CrossNuclide or AggregateNuclide - Nuclide to add to the tally. The nuclide should be a Nuclide object - when a user is adding nuclides to a Tally for input file generation. - The nuclide is a str when a Tally is created from a StatePoint file - (e.g., 'H1', 'U235') unless a Summary has been linked with the - StatePoint. The nuclide may be a CrossNuclide or AggregateNuclide - for derived tallies created by tally arithmetic. - - """ - - warnings.warn('Tally.add_nuclide(...) has been deprecated and may be ' - 'removed in a future version. Tally nuclides should be ' - 'defined using the nuclides property directly.', - DeprecationWarning) - self.nuclides.append(nuclide) - - def add_score(self, score): - """Specify a quantity to be scored - - .. deprecated:: 0.8 - Use the Tally.scores property directly, i.e., - Tally.scores.append(...) - - Parameters - ---------- - score : str, CrossScore or AggregateScore - A score to be accumulated (e.g., 'flux', 'nu-fission'). The score - should be a str when a user is adding scores to a Tally for input - file generation or when the Tally is created from a StatePoint. The - score may be a CrossScore or AggregateScore for derived tallies - created by tally arithmetic. - - """ - - warnings.warn('Tally.add_score(...) has been deprecated and may be ' - 'removed in a future version. Tally scores should be ' - 'defined using the scores property directly.', - DeprecationWarning) - self.scores.append(score) - @num_realizations.setter def num_realizations(self, num_realizations): cv.check_type('number of realizations', num_realizations, Integral) @@ -3246,26 +3152,6 @@ class Tallies(cv.CheckedList): if tallies is not None: self += tallies - def add_tally(self, tally, merge=False): - """Append tally to collection - - .. deprecated:: 0.8 - Use :meth:`Tallies.append` instead. - - Parameters - ---------- - tally : openmc.Tally - Tally to add - merge : bool - Indicate whether the tally should be merged with an existing tally, - if possible. Defaults to False. - - """ - warnings.warn("Tallies.add_tally(...) has been deprecated and may be " - "removed in a future version. Use Tallies.append(...) " - "instead.", DeprecationWarning) - self.append(tally, merge) - def append(self, tally, merge=False): """Append tally to collection @@ -3317,24 +3203,6 @@ class Tallies(cv.CheckedList): """ super().insert(index, item) - def remove_tally(self, tally): - """Remove a tally from the collection - - .. deprecated:: 0.8 - Use :meth:`Tallies.remove` instead. - - Parameters - ---------- - tally : openmc.Tally - Tally to remove - - """ - warnings.warn("Tallies.remove_tally(...) has been deprecated and may " - "be removed in a future version. Use Tallies.remove(...) " - "instead.", DeprecationWarning) - - self.remove(tally) - def merge_tallies(self): """Merge any mergeable tallies together. Note that n-way merges are possible. @@ -3359,41 +3227,6 @@ class Tallies(cv.CheckedList): # Continue iterating from the first loop break - def add_mesh(self, mesh): - """Add a mesh to the file - - .. deprecated:: 0.8 - Meshes that appear in a tally are automatically added to the - collection. - - Parameters - ---------- - mesh : openmc.Mesh - Mesh to add to the file - - """ - - warnings.warn("Tallies.add_mesh(...) has been deprecated and may be " - "removed in a future version. Meshes that appear in a " - "tally are automatically added to the collection.", - DeprecationWarning) - - def remove_mesh(self, mesh): - """Remove a mesh from the file - - .. deprecated:: 0.8 - Meshes do not need to be managed explicitly. - - Parameters - ---------- - mesh : openmc.Mesh - Mesh to remove from the file - - """ - warnings.warn("Tallies.remove_mesh(...) has been deprecated and may be " - "removed in a future version. Meshes do not need to be " - "managed explicitly.", DeprecationWarning) - def _create_tally_subelements(self, root_element): for tally in self: root_element.append(tally.to_xml_element()) diff --git a/openmc/trigger.py b/openmc/trigger.py index a5ac1d1e8..c6a0c0b25 100644 --- a/openmc/trigger.py +++ b/openmc/trigger.py @@ -82,23 +82,6 @@ class Trigger(object): if score not in self._scores: self._scores.append(score) - - def add_score(self, score): - """Add a score to the list of scores to be checked against the trigger. - - Parameters - ---------- - score : str - Score to append - - """ - - warnings.warn('Trigger.add_score(...) has been deprecated and may be ' - 'removed in a future version. Tally trigger scores should ' - 'be defined using the scores property directly.', - DeprecationWarning) - self.scores.append(score) - def get_trigger_xml(self, element): """Return XML representation of the trigger diff --git a/tests/regression_tests/diff_tally/test.py b/tests/regression_tests/diff_tally/test.py index e383b726e..c106e810f 100644 --- a/tests/regression_tests/diff_tally/test.py +++ b/tests/regression_tests/diff_tally/test.py @@ -55,30 +55,25 @@ class DiffTallyTestHarness(PyAPITestHarness): # Cover the flux score. for i in range(5): t = openmc.Tally() - t.add_score('flux') - t.add_filter(filt_mats) + t.scores = ['flux'] + t.filters = [filt_mats] t.derivative = derivs[i] self._model.tallies.append(t) # Cover supported scores with a collision estimator. for i in range(5): t = openmc.Tally() - t.add_score('total') - t.add_score('absorption') - t.add_score('scatter') - t.add_score('fission') - t.add_score('nu-fission') - t.add_filter(filt_mats) - t.add_nuclide('total') - t.add_nuclide('U235') + t.scores = ['total', 'absorption', 'scatter', 'fission', 'nu-fission'] + t.filters = [filt_mats] + t.nuclides = ['total', 'U235'] t.derivative = derivs[i] self._model.tallies.append(t) # Cover an analog estimator. for i in range(5): t = openmc.Tally() - t.add_score('absorption') - t.add_filter(filt_mats) + t.scores = ['absorption'] + t.filters = [filt_mats] t.estimator = 'analog' t.derivative = derivs[i] self._model.tallies.append(t) @@ -86,23 +81,18 @@ class DiffTallyTestHarness(PyAPITestHarness): # Energyout filter and total nuclide for the density derivatives. for i in range(2): t = openmc.Tally() - t.add_score('nu-fission') - t.add_score('scatter') - t.add_filter(filt_mats) - t.add_filter(filt_eout) - t.add_nuclide('total') - t.add_nuclide('U235') + t.scores = ['nu-fission', 'scatter'] + t.filters = [filt_mats, filt_eout] + t.nuclides = ['total', 'U235'] t.derivative = derivs[i] self._model.tallies.append(t) # Energyout filter without total nuclide for other derivatives. for i in range(2, 5): t = openmc.Tally() - t.add_score('nu-fission') - t.add_score('scatter') - t.add_filter(filt_mats) - t.add_filter(filt_eout) - t.add_nuclide('U235') + t.scores = ['nu-fission', 'scatter'] + t.filters = [filt_mats, filt_eout] + t.nuclides = ['U235'] t.derivative = derivs[i] self._model.tallies.append(t) From d3d6dc87dcb0323290a835877c91af795652f954 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Feb 2018 10:06:45 -0600 Subject: [PATCH 185/282] Remove a few more __future__ imports. Use collections.abc --- openmc/capi/cell.py | 2 +- openmc/capi/filter.py | 2 +- openmc/capi/material.py | 2 +- openmc/capi/nuclide.py | 2 +- openmc/capi/tally.py | 2 +- openmc/cell.py | 3 ++- openmc/checkvalue.py | 2 +- openmc/cmfd.py | 2 +- openmc/data/angle_distribution.py | 2 +- openmc/data/correlated.py | 2 +- openmc/data/decay.py | 3 ++- openmc/data/endf.py | 3 ++- openmc/data/energy_distribution.py | 2 +- openmc/data/fission_energy.py | 2 +- openmc/data/function.py | 2 +- openmc/data/kalbach_mann.py | 2 +- openmc/data/laboratory.py | 2 +- openmc/data/neutron.py | 3 ++- openmc/data/product.py | 2 +- openmc/data/reaction.py | 2 +- openmc/data/resonance.py | 3 ++- openmc/data/thermal.py | 2 +- openmc/data/urr.py | 2 +- openmc/executor.py | 2 +- openmc/geometry.py | 3 ++- openmc/lattice.py | 3 ++- openmc/mgxs/library.py | 3 ++- openmc/mgxs/mdgxs.py | 3 ++- openmc/model/funcs.py | 3 ++- openmc/model/model.py | 2 +- openmc/model/triso.py | 3 ++- openmc/plots.py | 2 +- openmc/region.py | 3 ++- openmc/search.py | 2 +- openmc/settings.py | 2 +- openmc/stats/multivariate.py | 2 +- openmc/stats/univariate.py | 2 +- openmc/summary.py | 2 +- openmc/tallies.py | 2 +- openmc/trigger.py | 2 +- openmc/volume.py | 3 ++- tests/check_source.py | 4 +--- tests/regression_tests/tally_slice_merge/test.py | 2 -- tests/testing_harness.py | 2 -- 44 files changed, 55 insertions(+), 48 deletions(-) diff --git a/openmc/capi/cell.py b/openmc/capi/cell.py index 0cadfd6af..3c14aae52 100644 --- a/openmc/capi/cell.py +++ b/openmc/capi/cell.py @@ -1,4 +1,4 @@ -from collections import Mapping, Iterable +from collections.abc import Mapping, Iterable from ctypes import c_int, c_int32, c_double, c_char_p, POINTER from weakref import WeakValueDictionary diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index 74c6828d6..ac78c0a34 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -1,4 +1,4 @@ -from collections import Mapping +from collections.abc import Mapping from ctypes import c_int, c_int32, c_double, c_char_p, POINTER, \ create_string_buffer from weakref import WeakValueDictionary diff --git a/openmc/capi/material.py b/openmc/capi/material.py index af0e9893e..8a55c2717 100644 --- a/openmc/capi/material.py +++ b/openmc/capi/material.py @@ -1,4 +1,4 @@ -from collections import Mapping +from collections.abc import Mapping from ctypes import c_int, c_int32, c_double, c_char_p, POINTER from weakref import WeakValueDictionary diff --git a/openmc/capi/nuclide.py b/openmc/capi/nuclide.py index 54d131498..f66212c97 100644 --- a/openmc/capi/nuclide.py +++ b/openmc/capi/nuclide.py @@ -1,4 +1,4 @@ -from collections import Mapping +from collections.abc import Mapping from ctypes import c_int, c_char_p, POINTER from weakref import WeakValueDictionary diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index aaf709e61..f50a19001 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -1,4 +1,4 @@ -from collections import Mapping +from collections.abc import Mapping from ctypes import c_int, c_int32, c_double, c_char_p, POINTER from weakref import WeakValueDictionary diff --git a/openmc/cell.py b/openmc/cell.py index 838d419b6..c7587e136 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -1,4 +1,5 @@ -from collections import OrderedDict, Iterable +from collections import OrderedDict +from collections.abc import Iterable from copy import deepcopy from math import cos, sin, pi from numbers import Real, Integral diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index 643ea4820..dd32aa566 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -1,5 +1,5 @@ import copy -from collections import Iterable +from collections.abc import Iterable import numpy as np diff --git a/openmc/cmfd.py b/openmc/cmfd.py index 236491923..5444334b2 100644 --- a/openmc/cmfd.py +++ b/openmc/cmfd.py @@ -10,7 +10,7 @@ References """ -from collections import Iterable +from collections.abc import Iterable from numbers import Real, Integral from xml.etree import ElementTree as ET import sys diff --git a/openmc/data/angle_distribution.py b/openmc/data/angle_distribution.py index 71e3d2f30..a1f498ba6 100644 --- a/openmc/data/angle_distribution.py +++ b/openmc/data/angle_distribution.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable from io import StringIO from numbers import Real from warnings import warn diff --git a/openmc/data/correlated.py b/openmc/data/correlated.py index ba69e6aa6..8cc4509ce 100644 --- a/openmc/data/correlated.py +++ b/openmc/data/correlated.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable from numbers import Real, Integral from warnings import warn diff --git a/openmc/data/decay.py b/openmc/data/decay.py index 3d365b6c7..d83338d02 100644 --- a/openmc/data/decay.py +++ b/openmc/data/decay.py @@ -1,4 +1,5 @@ -from collections import Iterable, namedtuple +from collections import namedtuple +from collections.abc import Iterable from io import StringIO from math import log from numbers import Real diff --git a/openmc/data/endf.py b/openmc/data/endf.py index 882874b59..160ab6151 100644 --- a/openmc/data/endf.py +++ b/openmc/data/endf.py @@ -10,7 +10,8 @@ import io import re import os from math import pi -from collections import OrderedDict, Iterable +from collections import OrderedDict +from collections.abc import Iterable import numpy as np from numpy.polynomial.polynomial import Polynomial diff --git a/openmc/data/energy_distribution.py b/openmc/data/energy_distribution.py index 55588bd64..9e01a4b30 100644 --- a/openmc/data/energy_distribution.py +++ b/openmc/data/energy_distribution.py @@ -1,5 +1,5 @@ from abc import ABCMeta, abstractmethod -from collections import Iterable +from collections.abc import Iterable from numbers import Integral, Real from warnings import warn diff --git a/openmc/data/fission_energy.py b/openmc/data/fission_energy.py index a7cf3dfed..c602ba2c6 100644 --- a/openmc/data/fission_energy.py +++ b/openmc/data/fission_energy.py @@ -1,4 +1,4 @@ -from collections import Callable +from collections.abc import Callable from copy import deepcopy from io import StringIO import sys diff --git a/openmc/data/function.py b/openmc/data/function.py index 2d3a8ce9c..3d09e44fc 100644 --- a/openmc/data/function.py +++ b/openmc/data/function.py @@ -1,5 +1,5 @@ from abc import ABCMeta, abstractmethod -from collections import Iterable, Callable +from collections.abc import Iterable, Callable from numbers import Real, Integral import numpy as np diff --git a/openmc/data/kalbach_mann.py b/openmc/data/kalbach_mann.py index 30a1c01cd..4be0c15d5 100644 --- a/openmc/data/kalbach_mann.py +++ b/openmc/data/kalbach_mann.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable from numbers import Real, Integral from warnings import warn diff --git a/openmc/data/laboratory.py b/openmc/data/laboratory.py index 18516d9b8..cfedb292b 100644 --- a/openmc/data/laboratory.py +++ b/openmc/data/laboratory.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable from numbers import Real, Integral import numpy as np diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 4f02af1a4..0aa1fe7d8 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -1,5 +1,6 @@ import sys -from collections import OrderedDict, Iterable, Mapping, MutableMapping +from collections import OrderedDict +from collections.abc import Iterable, Mapping, MutableMapping from io import StringIO from itertools import chain from math import log10 diff --git a/openmc/data/product.py b/openmc/data/product.py index b7d89ba0e..5b8652d77 100644 --- a/openmc/data/product.py +++ b/openmc/data/product.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable from io import StringIO from numbers import Real import sys diff --git a/openmc/data/reaction.py b/openmc/data/reaction.py index 737885f35..d3df038f5 100644 --- a/openmc/data/reaction.py +++ b/openmc/data/reaction.py @@ -1,4 +1,4 @@ -from collections import Iterable, Callable, MutableMapping +from collections.abc import Iterable, Callable, MutableMapping from copy import deepcopy from numbers import Real, Integral from warnings import warn diff --git a/openmc/data/resonance.py b/openmc/data/resonance.py index 5e7e0c44d..d58f706eb 100644 --- a/openmc/data/resonance.py +++ b/openmc/data/resonance.py @@ -1,4 +1,5 @@ -from collections import defaultdict, MutableSequence, Iterable +from collections import defaultdict +from collections.abc import MutableSequence, Iterable import io import numpy as np diff --git a/openmc/data/thermal.py b/openmc/data/thermal.py index 4f89d8e83..a036a2680 100644 --- a/openmc/data/thermal.py +++ b/openmc/data/thermal.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable from difflib import get_close_matches from numbers import Real import itertools diff --git a/openmc/data/urr.py b/openmc/data/urr.py index 1f915974b..0edccf6f0 100644 --- a/openmc/data/urr.py +++ b/openmc/data/urr.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable from numbers import Integral, Real import numpy as np diff --git a/openmc/executor.py b/openmc/executor.py index e3768f4c6..8ad2bd896 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable import subprocess from numbers import Integral diff --git a/openmc/geometry.py b/openmc/geometry.py index 7e1427269..1ee93dfd6 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -1,4 +1,5 @@ -from collections import OrderedDict, Iterable +from collections import OrderedDict +from collections.abc import Iterable from copy import deepcopy from xml.etree import ElementTree as ET diff --git a/openmc/lattice.py b/openmc/lattice.py index 0819a8591..2f7fcf56d 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -1,5 +1,6 @@ from abc import ABCMeta -from collections import OrderedDict, Iterable +from collections import OrderedDict +from collections.abc import Iterable from copy import deepcopy from math import sqrt, floor from numbers import Real, Integral diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index c8b151e02..9add7004b 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -3,7 +3,8 @@ import os import copy import pickle from numbers import Integral -from collections import OrderedDict, Iterable +from collections import OrderedDict +from collections.abc import Iterable from warnings import warn import numpy as np diff --git a/openmc/mgxs/mdgxs.py b/openmc/mgxs/mdgxs.py index dfd0d192d..2d278d62d 100644 --- a/openmc/mgxs/mdgxs.py +++ b/openmc/mgxs/mdgxs.py @@ -1,4 +1,5 @@ -from collections import Iterable, OrderedDict +from collections import OrderedDict +from collections.abc import Iterable import itertools from numbers import Integral import warnings diff --git a/openmc/model/funcs.py b/openmc/model/funcs.py index b5afdf08d..6013f6cae 100644 --- a/openmc/model/funcs.py +++ b/openmc/model/funcs.py @@ -1,4 +1,5 @@ -from collections import Iterable, OrderedDict +from collections import OrderedDict +from collections.abc import Iterable from math import sqrt from numbers import Real diff --git a/openmc/model/model.py b/openmc/model/model.py index 17de6fc2e..89fa84e60 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable import openmc from openmc.checkvalue import check_type diff --git a/openmc/model/triso.py b/openmc/model/triso.py index fab2f54cc..2786216f7 100644 --- a/openmc/model/triso.py +++ b/openmc/model/triso.py @@ -2,7 +2,8 @@ import copy import warnings import itertools import random -from collections import Iterable, defaultdict +from collections import defaultdict +from collections.abc import Iterable from numbers import Real from random import uniform, gauss from heapq import heappush, heappop diff --git a/openmc/plots.py b/openmc/plots.py index 159e92078..4414a39e1 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -1,4 +1,4 @@ -from collections import Iterable, Mapping +from collections.abc import Iterable, Mapping from numbers import Real, Integral from xml.etree import ElementTree as ET import sys diff --git a/openmc/region.py b/openmc/region.py index c9bd3b5fc..f82db8553 100644 --- a/openmc/region.py +++ b/openmc/region.py @@ -1,5 +1,6 @@ from abc import ABCMeta, abstractmethod -from collections import Iterable, OrderedDict, MutableSequence +from collections import OrderedDict +from collections.abc import Iterable, MutableSequence from copy import deepcopy import numpy as np diff --git a/openmc/search.py b/openmc/search.py index 7f91438fd..75935097e 100644 --- a/openmc/search.py +++ b/openmc/search.py @@ -1,4 +1,4 @@ -from collections import Callable +from collections.abc import Callable from numbers import Real import scipy.optimize as sopt diff --git a/openmc/settings.py b/openmc/settings.py index b9562b7dd..62cfc27aa 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1,4 +1,4 @@ -from collections import Iterable, MutableSequence, Mapping +from collections.abc import Iterable, MutableSequence, Mapping from numbers import Real, Integral import warnings from xml.etree import ElementTree as ET diff --git a/openmc/stats/multivariate.py b/openmc/stats/multivariate.py index 1fe883866..ac788c344 100644 --- a/openmc/stats/multivariate.py +++ b/openmc/stats/multivariate.py @@ -1,5 +1,5 @@ from abc import ABCMeta, abstractmethod -from collections import Iterable +from collections.abc import Iterable from math import pi from numbers import Real import sys diff --git a/openmc/stats/univariate.py b/openmc/stats/univariate.py index 6996970af..16a9dbb9e 100644 --- a/openmc/stats/univariate.py +++ b/openmc/stats/univariate.py @@ -1,5 +1,5 @@ from abc import ABCMeta, abstractmethod -from collections import Iterable +from collections.abc import Iterable from numbers import Real import sys from xml.etree import ElementTree as ET diff --git a/openmc/summary.py b/openmc/summary.py index 34743757b..aa98025c0 100644 --- a/openmc/summary.py +++ b/openmc/summary.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections.abc import Iterable import re import warnings diff --git a/openmc/tallies.py b/openmc/tallies.py index 61be38fb2..d22cfdcb4 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1,4 +1,4 @@ -from collections import Iterable, MutableSequence +from collections.abc import Iterable, MutableSequence import copy import re from functools import partial, reduce diff --git a/openmc/trigger.py b/openmc/trigger.py index c6a0c0b25..71f9c0b92 100644 --- a/openmc/trigger.py +++ b/openmc/trigger.py @@ -2,7 +2,7 @@ from numbers import Real from xml.etree import ElementTree as ET import sys import warnings -from collections import Iterable +from collections.abc import Iterable import openmc.checkvalue as cv diff --git a/openmc/volume.py b/openmc/volume.py index cccb3c6a2..d61093a17 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -1,4 +1,5 @@ -from collections import Iterable, Mapping, OrderedDict +from collections import OrderedDict +from collections.abc import Iterable, Mapping from numbers import Real, Integral from xml.etree import ElementTree as ET import warnings diff --git a/tests/check_source.py b/tests/check_source.py index 3a79d44c2..78cef2a0c 100755 --- a/tests/check_source.py +++ b/tests/check_source.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python - -from __future__ import print_function +#!/usr/bin/env python3 import glob from string import whitespace diff --git a/tests/regression_tests/tally_slice_merge/test.py b/tests/regression_tests/tally_slice_merge/test.py index c198411f7..ff35c177f 100644 --- a/tests/regression_tests/tally_slice_merge/test.py +++ b/tests/regression_tests/tally_slice_merge/test.py @@ -1,5 +1,3 @@ -from __future__ import division - import hashlib import itertools diff --git a/tests/testing_harness.py b/tests/testing_harness.py index 55a5660f1..6c9e25b45 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from difflib import unified_diff import filecmp import glob From 19ddb532bc3ae5a801e4a689010799b762539fcc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 6 Feb 2018 06:53:43 -0500 Subject: [PATCH 186/282] Remove check for Python 2.7 in travis-install --- tools/ci/travis-install.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index 58bcfb789..3efadd6f3 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -10,11 +10,6 @@ pip install numpy cython # pytest installed by default -- make sure we get latest pip install --upgrade pytest -# IPython stopped supporting Python 2.7 with version 6 -if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then - pip install "ipython<6" -fi - # Pandas stopped supporting Python 3.4 with version 0.21 if [[ "$TRAVIS_PYTHON_VERSION" == "3.4" ]]; then pip install pandas==0.20.3 From b053e054156cfbd314e515db2b1bdab53263575c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 6 Feb 2018 07:25:21 -0500 Subject: [PATCH 187/282] Remove try/except for importing unittest.mock --- docs/source/conf.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index b97ff782d..db44e34f4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,10 +18,7 @@ on_rtd = os.environ.get('READTHEDOCS', None) == 'True' # On Read the Docs, we need to mock a few third-party modules so we don't get # ImportErrors when building documentation -try: - from unittest.mock import MagicMock -except ImportError: - from mock import Mock as MagicMock +from unittest.mock import MagicMock MOCK_MODULES = ['numpy', 'numpy.polynomial', 'numpy.polynomial.polynomial', @@ -254,6 +251,6 @@ napoleon_use_ivar = True intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), 'numpy': ('https://docs.scipy.org/doc/numpy/', None), - 'pandas': ('http://pandas.pydata.org/pandas-docs/stable/', None), + 'pandas': ('https://pandas.pydata.org/pandas-docs/stable/', None), 'matplotlib': ('https://matplotlib.org/', None) } From cba083103c27df2924efc5a9c70073b0963d1d0d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 6 Feb 2018 07:29:39 -0500 Subject: [PATCH 188/282] Use default argument of max() in capi bindings --- openmc/capi/cell.py | 5 +---- openmc/capi/filter.py | 5 +---- openmc/capi/material.py | 5 +---- openmc/capi/tally.py | 5 +---- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/openmc/capi/cell.py b/openmc/capi/cell.py index 3c14aae52..0ab3f2583 100644 --- a/openmc/capi/cell.py +++ b/openmc/capi/cell.py @@ -65,10 +65,7 @@ class Cell(_FortranObjectWithID): if new: # Determine ID to assign if uid is None: - try: - uid = max(mapping) + 1 - except ValueError: - uid = 1 + uid = max(mapping, default=0) + 1 else: if uid in mapping: raise AllocationError('A cell with ID={} has already ' diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index ac78c0a34..79c19a624 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -66,10 +66,7 @@ class Filter(_FortranObjectWithID): if new: # Determine ID to assign if uid is None: - try: - uid = max(mapping) + 1 - except ValueError: - uid = 1 + uid = max(mapping, default=0) + 1 else: if uid in mapping: raise AllocationError('A filter with ID={} has already ' diff --git a/openmc/capi/material.py b/openmc/capi/material.py index 8a55c2717..62d6df012 100644 --- a/openmc/capi/material.py +++ b/openmc/capi/material.py @@ -78,10 +78,7 @@ class Material(_FortranObjectWithID): if new: # Determine ID to assign if uid is None: - try: - uid = max(mapping) + 1 - except ValueError: - uid = 1 + uid = max(mapping, default=0) + 1 else: if uid in mapping: raise AllocationError('A material with ID={} has already ' diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index f50a19001..a78347177 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -155,10 +155,7 @@ class Tally(_FortranObjectWithID): if new: # Determine ID to assign if uid is None: - try: - uid = max(mapping) + 1 - except ValueError: - uid = 1 + uid = max(mapping, default=0) + 1 else: if uid in mapping: raise AllocationError('A tally with ID={} has already ' From 59861c9507142d0d2f2120c96cbcec19f82b9cf8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 6 Feb 2018 07:35:45 -0500 Subject: [PATCH 189/282] No need to call int(floor(...)) in Python 3 --- openmc/lattice.py | 12 ++++++------ openmc/model/triso.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openmc/lattice.py b/openmc/lattice.py index 2f7fcf56d..86cfd5c41 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -604,12 +604,12 @@ class RectLattice(Lattice): element coordinate system """ - ix = int(floor((point[0] - self.lower_left[0])/self.pitch[0])) - iy = int(floor((point[1] - self.lower_left[1])/self.pitch[1])) + ix = floor((point[0] - self.lower_left[0])/self.pitch[0]) + iy = floor((point[1] - self.lower_left[1])/self.pitch[1]) if self.ndim == 2: idx = (ix, iy) else: - iz = int(floor((point[2] - self.lower_left[2])/self.pitch[2])) + iz = floor((point[2] - self.lower_left[2])/self.pitch[2]) idx = (ix, iy, iz) return idx, self.get_local_coordinates(point, idx) @@ -1016,10 +1016,10 @@ class HexLattice(Lattice): iz = 1 else: z = point[2] - self.center[2] - iz = int(floor(z/self.pitch[1] + 0.5*self.num_axial)) + iz = floor(z/self.pitch[1] + 0.5*self.num_axial) alpha = y - x/sqrt(3.) - ix = int(floor(x/(sqrt(0.75) * self.pitch[0]))) - ia = int(floor(alpha/self.pitch[0])) + ix = floor(x/(sqrt(0.75) * self.pitch[0])) + ia = floor(alpha/self.pitch[0]) # Check four lattice elements to see which one is closest based on local # coordinates diff --git a/openmc/model/triso.py b/openmc/model/triso.py index 2786216f7..5fe51b664 100644 --- a/openmc/model/triso.py +++ b/openmc/model/triso.py @@ -736,7 +736,7 @@ def _close_random_pack(domain, particles, contraction_rate): outer_pf = (4/3 * pi * (outer_diameter[0]/2)**3 * n_particles / domain.volume) - j = int(floor(-log10(outer_pf - inner_pf))) + j = floor(-log10(outer_pf - inner_pf)) outer_diameter[0] = (outer_diameter[0] - 0.5**j * contraction_rate * initial_outer_diameter / n_particles) From b7c63be018c765092377a463b3fd25cc559d8d92 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 6 Feb 2018 13:14:17 -0500 Subject: [PATCH 190/282] Use super() in Python classes in tests package --- tests/regression_tests/asymmetric_lattice/test.py | 2 +- tests/regression_tests/diff_tally/test.py | 2 +- tests/regression_tests/distribmat/test.py | 2 +- tests/regression_tests/filter_distribcell/test.py | 2 +- tests/regression_tests/filter_energyfun/test.py | 2 +- tests/regression_tests/filter_mesh/test.py | 2 +- tests/regression_tests/mg_convert/test.py | 2 +- tests/regression_tests/mgxs_library_ce_to_mg/test.py | 4 ++-- tests/regression_tests/mgxs_library_condense/test.py | 2 +- .../regression_tests/mgxs_library_distribcell/test.py | 2 +- tests/regression_tests/mgxs_library_hdf5/test.py | 4 ++-- tests/regression_tests/mgxs_library_mesh/test.py | 2 +- .../regression_tests/mgxs_library_no_nuclides/test.py | 2 +- tests/regression_tests/mgxs_library_nuclides/test.py | 2 +- tests/regression_tests/multipole/test.py | 4 ++-- tests/regression_tests/plot/test.py | 4 ++-- tests/regression_tests/statepoint_batch/test.py | 2 +- tests/regression_tests/statepoint_restart/test.py | 2 +- tests/regression_tests/tally_aggregation/test.py | 2 +- tests/regression_tests/tally_arithmetic/test.py | 2 +- tests/regression_tests/tally_slice_merge/test.py | 2 +- tests/testing_harness.py | 10 +++++----- 22 files changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/regression_tests/asymmetric_lattice/test.py b/tests/regression_tests/asymmetric_lattice/test.py index 0318d7b92..d6a0ab9b7 100644 --- a/tests/regression_tests/asymmetric_lattice/test.py +++ b/tests/regression_tests/asymmetric_lattice/test.py @@ -9,7 +9,7 @@ from tests.testing_harness import PyAPITestHarness class AsymmetricLatticeTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): - super(AsymmetricLatticeTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Extract universes encapsulating fuel and water assemblies geometry = self._model.geometry diff --git a/tests/regression_tests/diff_tally/test.py b/tests/regression_tests/diff_tally/test.py index c106e810f..bd6792414 100644 --- a/tests/regression_tests/diff_tally/test.py +++ b/tests/regression_tests/diff_tally/test.py @@ -9,7 +9,7 @@ from tests.testing_harness import PyAPITestHarness class DiffTallyTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): - super(DiffTallyTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Set settings explicitly self._model.settings.batches = 3 diff --git a/tests/regression_tests/distribmat/test.py b/tests/regression_tests/distribmat/test.py index 69bb7a67b..de1b77877 100644 --- a/tests/regression_tests/distribmat/test.py +++ b/tests/regression_tests/distribmat/test.py @@ -93,7 +93,7 @@ class DistribmatTestHarness(PyAPITestHarness): plots.export_to_xml() def _get_results(self): - outstr = super(DistribmatTestHarness, self)._get_results() + outstr = super()._get_results() su = openmc.Summary('summary.h5') outstr += str(su.geometry.get_all_cells()[11]) return outstr diff --git a/tests/regression_tests/filter_distribcell/test.py b/tests/regression_tests/filter_distribcell/test.py index bdd134bf5..028c6a779 100644 --- a/tests/regression_tests/filter_distribcell/test.py +++ b/tests/regression_tests/filter_distribcell/test.py @@ -6,7 +6,7 @@ from tests.testing_harness import * class DistribcellTestHarness(TestHarness): def __init__(self): - super(DistribcellTestHarness, self).__init__(None) + super().__init__(None) def execute_test(self): """Run OpenMC with the appropriate arguments and check the outputs.""" diff --git a/tests/regression_tests/filter_energyfun/test.py b/tests/regression_tests/filter_energyfun/test.py index d9148dd35..7c5645b65 100644 --- a/tests/regression_tests/filter_energyfun/test.py +++ b/tests/regression_tests/filter_energyfun/test.py @@ -5,7 +5,7 @@ from tests.testing_harness import PyAPITestHarness class FilterEnergyFunHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): - super(FilterEnergyFunHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Add Am241 to the fuel. self._model.materials[1].add_nuclide('Am241', 1e-7) diff --git a/tests/regression_tests/filter_mesh/test.py b/tests/regression_tests/filter_mesh/test.py index 66cd1ac19..e0c6dd0f2 100644 --- a/tests/regression_tests/filter_mesh/test.py +++ b/tests/regression_tests/filter_mesh/test.py @@ -5,7 +5,7 @@ from tests.testing_harness import HashedPyAPITestHarness class FilterMeshTestHarness(HashedPyAPITestHarness): def __init__(self, *args, **kwargs): - super(FilterMeshTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Initialize Meshes mesh_1d = openmc.Mesh(mesh_id=1) diff --git a/tests/regression_tests/mg_convert/test.py b/tests/regression_tests/mg_convert/test.py index 4bbbcbeb9..5b816a1cd 100755 --- a/tests/regression_tests/mg_convert/test.py +++ b/tests/regression_tests/mg_convert/test.py @@ -159,7 +159,7 @@ class MGXSTestHarness(PyAPITestHarness): return outstr def _cleanup(self): - super(MGXSTestHarness, self)._cleanup() + super()._cleanup() f = os.path.join(os.getcwd(), 'mgxs.h5') if os.path.exists(f): os.remove(f) diff --git a/tests/regression_tests/mgxs_library_ce_to_mg/test.py b/tests/regression_tests/mgxs_library_ce_to_mg/test.py index 922b2c302..72f052e68 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg/test.py +++ b/tests/regression_tests/mgxs_library_ce_to_mg/test.py @@ -11,7 +11,7 @@ from tests.regression_tests import config class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): # Generate inputs using parent class routine - super(MGXSTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Initialize a two-group structure energy_groups = openmc.mgxs.EnergyGroups(group_edges=[0, 0.625, 20.e6]) @@ -71,7 +71,7 @@ class MGXSTestHarness(PyAPITestHarness): openmc.run(openmc_exec=config['exe']) def _cleanup(self): - super(MGXSTestHarness, self)._cleanup() + super()._cleanup() f = 'mgxs.h5' if os.path.exists(f): os.remove(f) diff --git a/tests/regression_tests/mgxs_library_condense/test.py b/tests/regression_tests/mgxs_library_condense/test.py index 06b470478..167772e2f 100644 --- a/tests/regression_tests/mgxs_library_condense/test.py +++ b/tests/regression_tests/mgxs_library_condense/test.py @@ -9,7 +9,7 @@ from tests.testing_harness import PyAPITestHarness class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): - super(MGXSTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Initialize a two-group structure energy_groups = openmc.mgxs.EnergyGroups(group_edges=[0, 0.625, 20.e6]) diff --git a/tests/regression_tests/mgxs_library_distribcell/test.py b/tests/regression_tests/mgxs_library_distribcell/test.py index d7bc84bf5..5290d313b 100644 --- a/tests/regression_tests/mgxs_library_distribcell/test.py +++ b/tests/regression_tests/mgxs_library_distribcell/test.py @@ -10,7 +10,7 @@ from tests.testing_harness import PyAPITestHarness class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): # Generate inputs using parent class routine - super(MGXSTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Initialize a one-group structure energy_groups = openmc.mgxs.EnergyGroups(group_edges=[0, 20.e6]) diff --git a/tests/regression_tests/mgxs_library_hdf5/test.py b/tests/regression_tests/mgxs_library_hdf5/test.py index 8eed0258a..9811ab215 100644 --- a/tests/regression_tests/mgxs_library_hdf5/test.py +++ b/tests/regression_tests/mgxs_library_hdf5/test.py @@ -13,7 +13,7 @@ from tests.testing_harness import PyAPITestHarness class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): # Generate inputs using parent class routine - super(MGXSTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Initialize a two-group structure energy_groups = openmc.mgxs.EnergyGroups(group_edges=[0, 0.625, 20.e6]) @@ -67,7 +67,7 @@ class MGXSTestHarness(PyAPITestHarness): return outstr def _cleanup(self): - super(MGXSTestHarness, self)._cleanup() + super()._cleanup() f = 'mgxs.h5' if os.path.exists(f): os.remove(f) diff --git a/tests/regression_tests/mgxs_library_mesh/test.py b/tests/regression_tests/mgxs_library_mesh/test.py index 70bd2113c..a9d24da93 100644 --- a/tests/regression_tests/mgxs_library_mesh/test.py +++ b/tests/regression_tests/mgxs_library_mesh/test.py @@ -8,7 +8,7 @@ from tests.testing_harness import PyAPITestHarness class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): - super(MGXSTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Initialize a one-group structure energy_groups = openmc.mgxs.EnergyGroups(group_edges=[0, 20.e6]) diff --git a/tests/regression_tests/mgxs_library_no_nuclides/test.py b/tests/regression_tests/mgxs_library_no_nuclides/test.py index c59b52189..506ac238f 100644 --- a/tests/regression_tests/mgxs_library_no_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_no_nuclides/test.py @@ -10,7 +10,7 @@ from tests.testing_harness import PyAPITestHarness class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): # Generate inputs using parent class routine - super(MGXSTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Initialize a two-group structure energy_groups = openmc.mgxs.EnergyGroups(group_edges=[0, 0.625, 20.e6]) diff --git a/tests/regression_tests/mgxs_library_nuclides/test.py b/tests/regression_tests/mgxs_library_nuclides/test.py index a65fb9a6d..c64c27709 100644 --- a/tests/regression_tests/mgxs_library_nuclides/test.py +++ b/tests/regression_tests/mgxs_library_nuclides/test.py @@ -9,7 +9,7 @@ from tests.testing_harness import PyAPITestHarness class MGXSTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): - super(MGXSTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Initialize a two-group structure energy_groups = openmc.mgxs.EnergyGroups(group_edges=[0, 0.625, 20.e6]) diff --git a/tests/regression_tests/multipole/test.py b/tests/regression_tests/multipole/test.py index 43aa9963a..82e6bb4cd 100644 --- a/tests/regression_tests/multipole/test.py +++ b/tests/regression_tests/multipole/test.py @@ -71,10 +71,10 @@ class MultipoleTestHarness(PyAPITestHarness): raise RuntimeError("The 'OPENMC_MULTIPOLE_LIBRARY' environment " "variable must be specified for this test.") else: - super(MultipoleTestHarness, self).execute_test() + super().execute_test() def _get_results(self): - outstr = super(MultipoleTestHarness, self)._get_results() + outstr = super()._get_results() su = openmc.Summary('summary.h5') outstr += str(su.geometry.get_all_cells()[11]) return outstr diff --git a/tests/regression_tests/plot/test.py b/tests/regression_tests/plot/test.py index e7115c4dd..bfaef019c 100644 --- a/tests/regression_tests/plot/test.py +++ b/tests/regression_tests/plot/test.py @@ -12,7 +12,7 @@ from tests.regression_tests import config class PlotTestHarness(TestHarness): """Specialized TestHarness for running OpenMC plotting tests.""" def __init__(self, plot_names): - super(PlotTestHarness, self).__init__(None) + super().__init__(None) self._plot_names = plot_names def _run_openmc(self): @@ -24,7 +24,7 @@ class PlotTestHarness(TestHarness): assert os.path.exists(fname), 'Plot output file does not exist.' def _cleanup(self): - super(PlotTestHarness, self)._cleanup() + super()._cleanup() for fname in self._plot_names: if os.path.exists(fname): os.remove(fname) diff --git a/tests/regression_tests/statepoint_batch/test.py b/tests/regression_tests/statepoint_batch/test.py index 6c5e4de31..323b28fc6 100644 --- a/tests/regression_tests/statepoint_batch/test.py +++ b/tests/regression_tests/statepoint_batch/test.py @@ -3,7 +3,7 @@ from tests.testing_harness import TestHarness class StatepointTestHarness(TestHarness): def __init__(self): - super(StatepointTestHarness, self).__init__(None) + super().__init__(None) def _test_output_created(self): """Make sure statepoint files have been created.""" diff --git a/tests/regression_tests/statepoint_restart/test.py b/tests/regression_tests/statepoint_restart/test.py index d36ff9395..4575607f7 100644 --- a/tests/regression_tests/statepoint_restart/test.py +++ b/tests/regression_tests/statepoint_restart/test.py @@ -9,7 +9,7 @@ from tests.regression_tests import config class StatepointRestartTestHarness(TestHarness): def __init__(self, final_sp, restart_sp): - super(StatepointRestartTestHarness, self).__init__(final_sp) + super().__init__(final_sp) self._restart_sp = restart_sp def execute_test(self): diff --git a/tests/regression_tests/tally_aggregation/test.py b/tests/regression_tests/tally_aggregation/test.py index f72316073..f7df543de 100644 --- a/tests/regression_tests/tally_aggregation/test.py +++ b/tests/regression_tests/tally_aggregation/test.py @@ -7,7 +7,7 @@ from tests.testing_harness import PyAPITestHarness class TallyAggregationTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): - super(TallyAggregationTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Initialize the filters energy_filter = openmc.EnergyFilter([0.0, 0.253, 1.0e3, 1.0e6, 20.0e6]) diff --git a/tests/regression_tests/tally_arithmetic/test.py b/tests/regression_tests/tally_arithmetic/test.py index 469ed00de..3adf0c5be 100644 --- a/tests/regression_tests/tally_arithmetic/test.py +++ b/tests/regression_tests/tally_arithmetic/test.py @@ -7,7 +7,7 @@ from tests.testing_harness import PyAPITestHarness class TallyArithmeticTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): - super(TallyArithmeticTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Initialize Mesh mesh = openmc.Mesh(mesh_id=1) diff --git a/tests/regression_tests/tally_slice_merge/test.py b/tests/regression_tests/tally_slice_merge/test.py index ff35c177f..f79c8b268 100644 --- a/tests/regression_tests/tally_slice_merge/test.py +++ b/tests/regression_tests/tally_slice_merge/test.py @@ -8,7 +8,7 @@ from tests.testing_harness import PyAPITestHarness class TallySliceMergeTestHarness(PyAPITestHarness): def __init__(self, *args, **kwargs): - super(TallySliceMergeTestHarness, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Define nuclides and scores to add to both tallies self.nuclides = ['U235', 'U238'] diff --git a/tests/testing_harness.py b/tests/testing_harness.py index 6c9e25b45..92d477e23 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -127,7 +127,7 @@ class HashedTestHarness(TestHarness): def _get_results(self): """Digest info in the statepoint and return as a string.""" - return super(HashedTestHarness, self)._get_results(True) + return super()._get_results(True) class CMFDTestHarness(TestHarness): @@ -137,7 +137,7 @@ class CMFDTestHarness(TestHarness): """Digest info in the statepoint and return as a string.""" # Write out the eigenvalue and tallies. - outstr = super(CMFDTestHarness, self)._get_results() + outstr = super()._get_results() # Read the statepoint file. statepoint = glob.glob(self._sp_name)[0] @@ -220,7 +220,7 @@ class ParticleRestartTestHarness(TestHarness): class PyAPITestHarness(TestHarness): def __init__(self, statepoint_name, model=None): - super(PyAPITestHarness, self).__init__(statepoint_name) + super().__init__(statepoint_name) if model is None: self._model = pwr_core() else: @@ -300,7 +300,7 @@ class PyAPITestHarness(TestHarness): def _cleanup(self): """Delete XMLs, statepoints, tally, and test files.""" - super(PyAPITestHarness, self)._cleanup() + super()._cleanup() output = ['materials.xml', 'geometry.xml', 'settings.xml', 'tallies.xml', 'plots.xml', 'inputs_test.dat'] for f in output: @@ -311,4 +311,4 @@ class PyAPITestHarness(TestHarness): class HashedPyAPITestHarness(PyAPITestHarness): def _get_results(self): """Digest info in the statepoint and return as a string.""" - return super(HashedPyAPITestHarness, self)._get_results(True) + return super()._get_results(True) From 848305b8d38ceb490655f049a7b49f618484a666 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 6 Feb 2018 13:27:01 -0500 Subject: [PATCH 191/282] Skip multipole-related tests if OPENMC_MULTIPOLE_LIBRARY is not set --- pytest.ini | 1 + tests/regression_tests/diff_tally/test.py | 4 ++++ tests/regression_tests/multipole/test.py | 11 ++++------- tests/unit_tests/test_data_multipole.py | 7 +++++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/pytest.ini b/pytest.ini index d960ba8d2..cdd367ea9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,3 +2,4 @@ python_files = test*.py python_classes = NoThanks filterwarnings = ignore::UserWarning +addopts = -rs diff --git a/tests/regression_tests/diff_tally/test.py b/tests/regression_tests/diff_tally/test.py index bd6792414..de5423fd9 100644 --- a/tests/regression_tests/diff_tally/test.py +++ b/tests/regression_tests/diff_tally/test.py @@ -3,6 +3,7 @@ import os import pandas as pd import openmc +import pytest from tests.testing_harness import PyAPITestHarness @@ -112,6 +113,9 @@ class DiffTallyTestHarness(PyAPITestHarness): return df.to_csv(None, columns=cols, index=False, float_format='%.7e') +@pytest.mark.skipif('OPENMC_MULTIPOLE_LIBRARY' not in os.environ, + reason='OPENMC_MULTIPOLE_LIBRARY environment variable ' + 'must be set') def test_diff_tally(): harness = DiffTallyTestHarness('statepoint.3.h5') harness.main() diff --git a/tests/regression_tests/multipole/test.py b/tests/regression_tests/multipole/test.py index 82e6bb4cd..5c79d4d6b 100644 --- a/tests/regression_tests/multipole/test.py +++ b/tests/regression_tests/multipole/test.py @@ -2,6 +2,7 @@ import os import openmc import openmc.model +import pytest from tests.testing_harness import TestHarness, PyAPITestHarness @@ -66,13 +67,6 @@ def make_model(): class MultipoleTestHarness(PyAPITestHarness): - def execute_test(self): - if not 'OPENMC_MULTIPOLE_LIBRARY' in os.environ: - raise RuntimeError("The 'OPENMC_MULTIPOLE_LIBRARY' environment " - "variable must be specified for this test.") - else: - super().execute_test() - def _get_results(self): outstr = super()._get_results() su = openmc.Summary('summary.h5') @@ -80,6 +74,9 @@ class MultipoleTestHarness(PyAPITestHarness): return outstr +@pytest.mark.skipif('OPENMC_MULTIPOLE_LIBRARY' not in os.environ, + reason='OPENMC_MULTIPOLE_LIBRARY environment variable ' + 'must be set') def test_multipole(): model = make_model() harness = MultipoleTestHarness('statepoint.5.h5', model) diff --git a/tests/unit_tests/test_data_multipole.py b/tests/unit_tests/test_data_multipole.py index dc30677e8..4a4a9f0d2 100644 --- a/tests/unit_tests/test_data_multipole.py +++ b/tests/unit_tests/test_data_multipole.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - import os import numpy as np @@ -7,6 +5,11 @@ import pytest import openmc.data +pytestmark = pytest.mark.skipif( + 'OPENMC_MULTIPOLE_LIBRARY' not in os.environ, + reason='OPENMC_MULTIPOLE_LIBRARY environment variable must be set') + + @pytest.fixture(scope='module') def u235(): directory = os.environ['OPENMC_MULTIPOLE_LIBRARY'] From ddc60ec6f0521b5d67f8a5bc99205118efb61038 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Tue, 6 Feb 2018 19:27:39 -0500 Subject: [PATCH 192/282] Inelastic scattering index list --- src/nuclide_header.F90 | 18 ++++++++++++++++++ src/physics.F90 | 22 ++++++---------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index fd2577508..8bbf103e3 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -100,6 +100,7 @@ module nuclide_header ! Reactions type(Reaction), allocatable :: reactions(:) + integer, allocatable :: index_inelastic_scatter(:) ! Array that maps MT values to index in reactions; used at tally-time. Note ! that ENDF-102 does not assign any MT values above 891. @@ -302,6 +303,7 @@ contains real(8) :: temp_actual type(VectorInt) :: MTs type(VectorInt) :: temps_to_read + type(VectorInt) :: index_inelastic_scatter_vector ! Get name of nuclide from group name_len = len(this % name) @@ -469,10 +471,26 @@ contains end if end if + ! Add the reaction index to the scattering array if this is an inelastic + ! scatter reaction + if (MTs % data(i) /= N_FISSION .and. MTs % data(i) /= N_F .and. & + MTs % data(i) /= N_NF .and. MTs % data(i) /= N_2NF .and. & + MTs % data(i) /= N_3NF .and. MTs % data(i) < 200 .and. & + MTs % data(i) /= N_LEVEL .and. MTs % data(i) /= ELASTIC) then + + call index_inelastic_scatter_vector % push_back(i) + end if + call close_group(rx_group) end do call close_group(rxs_group) + ! Recast to a regular array to save space + allocate(this % index_inelastic_scatter( & + index_inelastic_scatter_vector % size())) + this % index_inelastic_scatter = index_inelastic_scatter_vector % data(:) + call index_inelastic_scatter_vector % clear() + ! Read unresolved resonance probability tables if present if (object_exists(group_id, 'urr')) then this % urr_present = .true. diff --git a/src/physics.F90 b/src/physics.F90 index 98fef8b92..721e3dd6c 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -310,6 +310,7 @@ contains integer, intent(in) :: i_nuc_mat integer :: i + integer :: j integer :: i_temp integer :: i_grid real(8) :: f @@ -378,11 +379,10 @@ contains ! ======================================================================= ! INELASTIC SCATTERING - ! note that indexing starts from 2 since nuc % reactions(1) is elastic - ! scattering - i = 1 + j = 0 do while (prob < cutoff) - i = i + 1 + j = j + 1 + i = nuc % index_inelastic_scatter(j) ! Check to make sure inelastic scattering reaction sampled if (i > size(nuc % reactions)) then @@ -391,24 +391,14 @@ contains &// trim(nuc % name)) end if - associate (rx => nuc % reactions(i)) - ! Skip fission reactions - if (rx % MT == N_FISSION .or. rx % MT == N_F .or. rx % MT == N_NF & - .or. rx % MT == N_2NF .or. rx % MT == N_3NF) cycle - - ! Some materials have gas production cross sections with MT > 200 that - ! are duplicates. Also MT=4 is total level inelastic scattering which - ! should be skipped - if (rx % MT >= 200 .or. rx % MT == N_LEVEL) cycle - - associate (xs => rx % xs(i_temp)) + associate (rx => nuc % reactions(i), & + xs => nuc % reactions(i) % xs(i_temp)) ! if energy is below threshold for this reaction, skip it if (i_grid < xs % threshold) cycle ! add to cumulative probability prob = prob + ((ONE - f)*xs % value(i_grid - xs % threshold + 1) & + f*(xs % value(i_grid - xs % threshold + 2))) - end associate end associate end do From 7d4cc0094eb2dc885710d3691e4f52c3fd1e50a8 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Tue, 6 Feb 2018 19:33:58 -0500 Subject: [PATCH 193/282] Fixing further misidentations --- src/nuclide_header.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 8bbf103e3..c0c5c2627 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -474,9 +474,9 @@ contains ! Add the reaction index to the scattering array if this is an inelastic ! scatter reaction if (MTs % data(i) /= N_FISSION .and. MTs % data(i) /= N_F .and. & - MTs % data(i) /= N_NF .and. MTs % data(i) /= N_2NF .and. & - MTs % data(i) /= N_3NF .and. MTs % data(i) < 200 .and. & - MTs % data(i) /= N_LEVEL .and. MTs % data(i) /= ELASTIC) then + MTs % data(i) /= N_NF .and. MTs % data(i) /= N_2NF .and. & + MTs % data(i) /= N_3NF .and. MTs % data(i) < 200 .and. & + MTs % data(i) /= N_LEVEL .and. MTs % data(i) /= ELASTIC) then call index_inelastic_scatter_vector % push_back(i) end if From dacf650eaf74ce6b138c0225cfcc83d4c622a39d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 8 Feb 2018 14:32:28 -0600 Subject: [PATCH 194/282] Fix bug with clearing k_generation and entropy --- src/simulation.F90 | 2 ++ tests/__init__.py | 15 +++++++++++++++ tests/unit_tests/test_capi.py | 33 ++++++++++++++++++++------------- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/simulation.F90 b/src/simulation.F90 index 424c55e9b..0204aaf79 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -454,6 +454,8 @@ contains ! Reset global variables current_batch = 0 + call k_generation % clear() + call entropy % clear() need_depletion_rx = .false. ! Set flag indicating initialization is done diff --git a/tests/__init__.py b/tests/__init__.py index e69de29bb..5c21cd938 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,15 @@ +from contextlib import contextmanager +import os +import tempfile + + +@contextmanager +def cdtemp(): + """Context manager to change to/return from a tmpdir.""" + with tempfile.TemporaryDirectory() as tmpdir: + cwd = os.getcwd() + try: + os.chdir(tmpdir) + yield + finally: + os.chdir(cwd) diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index e1e87e27b..618f54e4f 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - from collections import Mapping import os @@ -8,12 +6,15 @@ import pytest import openmc import openmc.capi +from tests import cdtemp + @pytest.fixture(scope='module') def pincell_model(): """Set up a model to test with and delete files when done""" openmc.reset_auto_ids() pincell = openmc.examples.pwr_pin_cell() + pincell.settings.verbosity = 1 # Add a tally filter1 = openmc.MaterialFilter(pincell.materials) @@ -24,17 +25,10 @@ def pincell_model(): mat_tally.scores = ['total', 'elastic', '(n,gamma)'] pincell.tallies.append(mat_tally) - # Write XML files - pincell.export_to_xml() - - yield - - # Delete generated files - files = ['geometry.xml', 'materials.xml', 'settings.xml', 'tallies.xml', - 'statepoint.10.h5', 'summary.h5', 'test_sp.h5'] - for f in files: - if os.path.exists(f): - os.remove(f) + # Write XML files in tmpdir + with cdtemp(): + pincell.export_to_xml() + yield @pytest.fixture(scope='module') @@ -229,6 +223,19 @@ def test_by_batch(capi_run): openmc.capi.simulation_finalize() +def test_reproduce_keff(capi_init): + # Get k-effective after run + openmc.capi.hard_reset() + openmc.capi.run() + keff0 = openmc.capi.keff() + + # Reset, run again, and get k-effective again. they should match + openmc.capi.hard_reset() + openmc.capi.run() + keff1 = openmc.capi.keff() + assert keff0 == pytest.approx(keff1) + + def test_find_cell(capi_init): cell, instance = openmc.capi.find_cell((0., 0., 0.)) assert cell is openmc.capi.cells[1] From 899b6b67996489ae6e3369156ce7017e902031a1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 8 Feb 2018 15:34:39 -0600 Subject: [PATCH 195/282] Make sure k_generation and entropy are cleared before loading state point --- src/simulation.F90 | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/simulation.F90 b/src/simulation.F90 index 0204aaf79..34c433694 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -434,6 +434,13 @@ contains allocate(filter_matches(n_filters)) !$omp end parallel + ! Reset global variables -- this is done before loading state point (as that + ! will potentially populate k_generation and entropy) + current_batch = 0 + call k_generation % clear() + call entropy % clear() + need_depletion_rx = .false. + ! If this is a restart run, load the state point data and binary source ! file if (restart_run) then @@ -452,12 +459,6 @@ contains end if end if - ! Reset global variables - current_batch = 0 - call k_generation % clear() - call entropy % clear() - need_depletion_rx = .false. - ! Set flag indicating initialization is done simulation_initialized = .true. From 4298c50c3fdf2796b0d6020b00889b8c1db691d9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 9 Feb 2018 09:28:09 -0600 Subject: [PATCH 196/282] Use numpy 1.13 when testing since float formatting changed in 1.14 --- tools/ci/travis-install.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index 3efadd6f3..4921534db 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -4,8 +4,11 @@ set -ex # Install NJOY 2016 ./tools/ci/travis-install-njoy.sh -# Running OpenMC's setup.py requires numpy/cython already -pip install numpy cython +# Running OpenMC's setup.py requires numpy/cython already. NumPy float +# formatting changed in version 1.14, so stick with a lower version until we can +# handle it in our test suite +pip install 'numpy<1.14' +pip install cython # pytest installed by default -- make sure we get latest pip install --upgrade pytest From 563b1658919c43409e99c9618b3860d9efe54714 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Fri, 9 Feb 2018 18:56:39 -0500 Subject: [PATCH 197/282] Adjusted indentation --- src/physics.F90 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/physics.F90 b/src/physics.F90 index 721e3dd6c..53a2cc232 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -392,13 +392,13 @@ contains end if associate (rx => nuc % reactions(i), & - xs => nuc % reactions(i) % xs(i_temp)) - ! if energy is below threshold for this reaction, skip it - if (i_grid < xs % threshold) cycle + xs => nuc % reactions(i) % xs(i_temp)) + ! if energy is below threshold for this reaction, skip it + if (i_grid < xs % threshold) cycle - ! add to cumulative probability - prob = prob + ((ONE - f)*xs % value(i_grid - xs % threshold + 1) & - + f*(xs % value(i_grid - xs % threshold + 2))) + ! add to cumulative probability + prob = prob + ((ONE - f)*xs % value(i_grid - xs % threshold + 1) & + + f*(xs % value(i_grid - xs % threshold + 2))) end associate end do From bb4ac5a7b2365983bca8250d97d151426b7cbedb Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 10 Feb 2018 01:04:45 -0500 Subject: [PATCH 198/282] Add IAPWS water density data --- openmc/data/data.py | 107 +++++++++++++++++++++++++++++ tests/unit_tests/test_data_misc.py | 10 +++ 2 files changed, 117 insertions(+) diff --git a/openmc/data/data.py b/openmc/data/data.py index ded687018..8c0ed6372 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -1,6 +1,9 @@ import itertools import os import re +from warnings import warn + +from numpy import sqrt # Isotopic abundances from Meija J, Coplen T B, et al, "Isotopic compositions @@ -208,6 +211,110 @@ def atomic_weight(element): return None if weight == 0. else weight +def water_density(temperature, pressure=0.1013): + """Return the density of liquid water at a given temperature and pressure. + + The density is calculated from a polynomial fit using equations and values + from the 2012 version of the IAPWS-IF97 formulation. Only the equations + for region 1 are implemented here. + + Results are invalid for water vapor; pressures above 100 [MPa]; and + temperatures below 273.15 [K], above 623.15 [K], or above saturation. + + Reference: International Association for the Properties of Water and Steam, + "Revised Release on the IAPWS Industrial Formulation 1997 for the + Thermodynamic Properties of Water and Steam", IAPWS R7-97(2012). + + Parameters + ---------- + temperature : float + Water temperature in units of [K] + pressure : float + Water pressure in units of [MPa] + + Returns + ------- + float + Water density in units of [g / cm^3] + + """ + + # Make sure the temperature and pressure are inside the min/max region 1 + # bounds. (Relax the 273.15 bound to 273 in case a user wants 0 deg C data + # but they only use 3 digits for their conversion to K.) + if pressure > 100.0: + warn("Results are not valid for pressures above 100 MPa.") + if pressure < 0.0: + warn("Results are not valid for pressures below zero.") + if temperature < 273: + warn("Results are not valid for temperatures below 273.15 K.") + if temperature > 623.15: + warn("Results are not valid for temperatures above 623.15 K.") + + # IAPWS region 4 parameters + _n4 = [0.11670521452767e4, -0.72421316703206e6, -0.17073846940092e2, + 0.12020824702470e5, -0.32325550322333e7, 0.14915108613530e2, + -0.48232657361591e4, 0.40511340542057e6, -0.23855557567849, + 0.65017534844798e3] + + # Compute the saturation temperature at the given pressure. + beta = pressure**(0.25) + E = beta**2 + _n4[2] * beta + _n4[5] + F = _n4[0] * beta**2 + _n4[3] * beta + _n4[6] + G = _n4[1] * beta**2 + _n4[4] * beta + _n4[7] + D = 2.0 * G / (-F - sqrt(F**2 - 4 * E * G)) + T_sat = 0.5 * (_n4[9] + D + - sqrt((_n4[9] + D)**2 - 4.0 * (_n4[8] + _n4[9] * D))) + + # Make sure we aren't above saturation. (Relax this bound by .2 degrees + # for deg C to K conversions.) + if temperature > T_sat + 0.2: + warn("Results are not valid for temperatures above saturation " + "(above the boiling point).") + + # IAPWS region 1 parameters + R_GAS_CONSTANT = 0.461526 # kJ / kg / K + _ref_p = 16.53 # MPa + _ref_T = 1386 # K + _n1f = [0.14632971213167, -0.84548187169114, -0.37563603672040e1, + 0.33855169168385e1, -0.95791963387872, 0.15772038513228, + -0.16616417199501e-1, 0.81214629983568e-3, 0.28319080123804e-3, + -0.60706301565874e-3, -0.18990068218419e-1, -0.32529748770505e-1, + -0.21841717175414e-1, -0.52838357969930e-4, -0.47184321073267e-3, + -0.30001780793026e-3, 0.47661393906987e-4, -0.44141845330846e-5, + -0.72694996297594e-15, -0.31679644845054e-4, -0.28270797985312e-5, + -0.85205128120103e-9, -0.22425281908000e-5, -0.65171222895601e-6, + -0.14341729937924e-12, -0.40516996860117e-6, -0.12734301741641e-8, + -0.17424871230634e-9, -0.68762131295531e-18, 0.14478307828521e-19, + 0.26335781662795e-22, -0.11947622640071e-22, 0.18228094581404e-23, + -0.93537087292458e-25] + _I1f = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, + 4, 4, 5, 8, 8, 21, 23, 29, 30, 31, 32] + _J1f = [-2, -1, 0, 1, 2, 3, 4, 5, -9, -7, -1, 0, 1, 3, -3, 0, 1, 3, 17, -4, + 0, 6, -5, -2, 10, -8, -11, -6, -29, -31, -38, -39, -40, -41] + + # Nondimensionalize the pressure and temperature. + pi = pressure / _ref_p + tau = _ref_T / temperature + + # Compute the derivative of gamma (dimensionless Gibbs free energy) with + # respect to pi. + gamma1_pi = 0.0 + for i in range(34): + gamma1_pi -= (_n1f[i] * _I1f[i] * (7.1 - pi)**(_I1f[i] - 1) + * (tau - 1.222)**_J1f[i]) + + # Compute the leading coefficient. This sets the units at + # 1 [MPa] * [kg K / kJ] / [1 / K] + # = 1e6 [N / m^2] * 1e-3 [kg K / N / m] * [1 / K] + # = 1e3 [kg / m^3] + # = 1 [g / cm^3] + coeff = pressure / R_GAS_CONSTANT / temperature + + # Compute and return the density. + return coeff / pi / gamma1_pi + + # Values here are from the Committee on Data for Science and Technology # (CODATA) 2014 recommendation (doi:10.1103/RevModPhys.88.035009). diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py index aeeb04c0f..b33ef996f 100644 --- a/tests/unit_tests/test_data_misc.py +++ b/tests/unit_tests/test_data_misc.py @@ -48,3 +48,13 @@ def test_thin(): x_thin, y_thin = openmc.data.thin(x, y) f = openmc.data.Tabulated1D(x_thin, y_thin) assert f(1.0) == pytest.approx(np.sin(1.0), 0.001) + + +def test_water_density(): + dens = openmc.data.water_density + # These test values are from IAPWS R7-97(2012). They are actually specific + # volumes so they need to be inverted. They also need to be divided by 1000 + # to convert from [kg / m^3] to [g / cm^2]. + assert dens(300.0, 3.0) == pytest.approx(1e-3/0.100215168e-2, 1e-6) + assert dens(300.0, 80.0) == pytest.approx(1e-3/0.971180894e-3, 1e-6) + assert dens(500.0, 3.0) == pytest.approx(1e-3/0.120241800e-2, 1e-6) From a06190ee407c14fd7a82cd0edf2fd89ce7e956d1 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 10 Feb 2018 02:23:50 -0500 Subject: [PATCH 199/282] Add convenience function for boric acid Materials --- openmc/material.py | 70 +++++++++++++++++++++++++++++++ tests/unit_tests/test_material.py | 18 ++++++++ 2 files changed, 88 insertions(+) diff --git a/openmc/material.py b/openmc/material.py index e409d6536..32b7d3a80 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -973,3 +973,73 @@ class Materials(cv.CheckedList): # Write the XML Tree to the materials.xml file tree = ET.ElementTree(root_element) tree.write(path, xml_declaration=True, encoding='utf-8') + + +def make_boric_acid(boron_ppm, temperature=293., pressure=0.1013, density=None, + **kwargs): + """Return a Material with the composition of boric acid. + + The water density can either be given directly, or it can be determined from + a temperature and pressure. + + Parameters + ---------- + boron_ppm : float + The weight fraction in parts-per-million of elemental boron in the acid. + temperature : float + Water temperature in [K] used to compute water density. + pressure : float + Water pressure in [MPa] used to compute water density. + density : float + Water density in [g / cm^3]. If specified, this value overrides the + temperature and pressure arguments. + **kwargs + All keyword arguments are passed to the created Material object. + + Returns + ------- + openmc.Material + + """ + # Set the density of water, either from an explicitly given density or from + # temperature and pressure. + if density is not None: + water_density = density + else: + water_density = openmc.data.water_density(temperature, pressure) + + # Compute the density of the boric acid. + acid_density = water_density / (1 - boron_ppm * 1e-6) + + # Compute the molar mass of pure water. + hydrogen = openmc.Element('H') + oxygen = openmc.Element('O') + M_H2O = 0.0 + for iso_name, frac, junk in hydrogen.expand(2.0, 'ao'): + M_H2O += frac * openmc.data.atomic_mass(iso_name) + for iso_name, frac, junk in oxygen.expand(1.0, 'ao'): + M_H2O += frac * openmc.data.atomic_mass(iso_name) + + # Compute the molar mass of boron. + boron = openmc.Element('B') + M_B = 0.0 + for iso_name, frac, junk in boron.expand(1.0, 'ao'): + M_B += frac * openmc.data.atomic_mass(iso_name) + + # Compute the number fractions of each element. + frac_H2O = (1 - boron_ppm * 1e-6) / M_H2O + frac_H = 2 * frac_H2O + frac_O = frac_H2O + frac_B = boron_ppm * 1e-6 / M_B + + # Build the material. + if density is None: + out = openmc.Material(temperature=temperature, **kwargs) + else: + out = openmc.Material(**kwargs) + out.add_element('H', frac_H, 'ao') + out.add_element('O', frac_O, 'ao') + out.add_element('B', frac_B, 'ao') + out.set_density('g/cc', acid_density) + out.add_s_alpha_beta('c_H_in_H2O') + return out diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index e92a2ac08..38495cb1a 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -139,3 +139,21 @@ def test_materials(run_in_tmpdir): mats.cross_sections = '/some/fake/cross_sections.xml' mats.multipole_library = '/some/awesome/mp_lib/' mats.export_to_xml() + + +def test_boric_acid(): + # Test against reference values from the BEAVRS benchmark. + m = openmc.make_boric_acid(975, 566.5, 15.51, material_id=50) + assert m.density == pytest.approx(0.7405, 1e-3) + assert m.temperature == pytest.approx(566.5) + assert m._sab[0][0] == 'c_H_in_H2O' + ref_dens = {'B10':8.0023e-06, 'B11':3.2210e-05, 'H1':4.9458e-02, + 'O16':2.4672e-02} + nuc_dens = m.get_nuclide_atom_densities() + for nuclide in ref_dens: + assert nuc_dens[nuclide][1] == pytest.approx(ref_dens[nuclide], 1e-2) + assert m.id == 50 + + # Make sure the density override works + m = openmc.make_boric_acid(975, 566.5, 15.51, 0.9) + assert m.density == pytest.approx(0.9, 1e-3) From 9c53c085d6b81125f8cd3835ee40c2fc7f207f63 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 11 Feb 2018 15:01:51 -0500 Subject: [PATCH 200/282] Change boric_acid to borated_water; add more units --- openmc/data/data.py | 7 +-- openmc/material.py | 70 ----------------------- openmc/model/funcs.py | 95 +++++++++++++++++++++++++++++++ tests/unit_tests/test_material.py | 16 ++++-- 4 files changed, 110 insertions(+), 78 deletions(-) diff --git a/openmc/data/data.py b/openmc/data/data.py index 8c0ed6372..45a4406db 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -216,10 +216,9 @@ def water_density(temperature, pressure=0.1013): The density is calculated from a polynomial fit using equations and values from the 2012 version of the IAPWS-IF97 formulation. Only the equations - for region 1 are implemented here. - - Results are invalid for water vapor; pressures above 100 [MPa]; and - temperatures below 273.15 [K], above 623.15 [K], or above saturation. + for region 1 are implemented here. Region 1 is limited to liquid water + below 100 [MPa] with a temperature above 273.15 [K], below 623.15 [K], and + below saturation. Reference: International Association for the Properties of Water and Steam, "Revised Release on the IAPWS Industrial Formulation 1997 for the diff --git a/openmc/material.py b/openmc/material.py index 32b7d3a80..e409d6536 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -973,73 +973,3 @@ class Materials(cv.CheckedList): # Write the XML Tree to the materials.xml file tree = ET.ElementTree(root_element) tree.write(path, xml_declaration=True, encoding='utf-8') - - -def make_boric_acid(boron_ppm, temperature=293., pressure=0.1013, density=None, - **kwargs): - """Return a Material with the composition of boric acid. - - The water density can either be given directly, or it can be determined from - a temperature and pressure. - - Parameters - ---------- - boron_ppm : float - The weight fraction in parts-per-million of elemental boron in the acid. - temperature : float - Water temperature in [K] used to compute water density. - pressure : float - Water pressure in [MPa] used to compute water density. - density : float - Water density in [g / cm^3]. If specified, this value overrides the - temperature and pressure arguments. - **kwargs - All keyword arguments are passed to the created Material object. - - Returns - ------- - openmc.Material - - """ - # Set the density of water, either from an explicitly given density or from - # temperature and pressure. - if density is not None: - water_density = density - else: - water_density = openmc.data.water_density(temperature, pressure) - - # Compute the density of the boric acid. - acid_density = water_density / (1 - boron_ppm * 1e-6) - - # Compute the molar mass of pure water. - hydrogen = openmc.Element('H') - oxygen = openmc.Element('O') - M_H2O = 0.0 - for iso_name, frac, junk in hydrogen.expand(2.0, 'ao'): - M_H2O += frac * openmc.data.atomic_mass(iso_name) - for iso_name, frac, junk in oxygen.expand(1.0, 'ao'): - M_H2O += frac * openmc.data.atomic_mass(iso_name) - - # Compute the molar mass of boron. - boron = openmc.Element('B') - M_B = 0.0 - for iso_name, frac, junk in boron.expand(1.0, 'ao'): - M_B += frac * openmc.data.atomic_mass(iso_name) - - # Compute the number fractions of each element. - frac_H2O = (1 - boron_ppm * 1e-6) / M_H2O - frac_H = 2 * frac_H2O - frac_O = frac_H2O - frac_B = boron_ppm * 1e-6 / M_B - - # Build the material. - if density is None: - out = openmc.Material(temperature=temperature, **kwargs) - else: - out = openmc.Material(**kwargs) - out.add_element('H', frac_H, 'ao') - out.add_element('O', frac_O, 'ao') - out.add_element('B', frac_B, 'ao') - out.set_density('g/cc', acid_density) - out.add_s_alpha_beta('c_H_in_H2O') - return out diff --git a/openmc/model/funcs.py b/openmc/model/funcs.py index 6013f6cae..b8984ada0 100644 --- a/openmc/model/funcs.py +++ b/openmc/model/funcs.py @@ -5,6 +5,7 @@ from numbers import Real from openmc import XPlane, YPlane, Plane, ZCylinder from openmc.checkvalue import check_type, check_value +import openmc.data def get_rectangular_prism(width, height, axis='z', origin=(0., 0.), @@ -252,6 +253,100 @@ def get_hexagonal_prism(edge_length=1., orientation='y', origin=(0., 0.), return prism +def make_borated_water(boron_ppm, temperature=293., pressure=0.1013, + temp_unit='K', press_unit='MPa', density=None, **kwargs): + """Return a Material with the composition of boron dissolved in water. + + The water density can be determined from a temperature and pressure, or it + can be set directly. + + The concentration of boron has no effect on the stoichometric ratio of H + and O---they are fixed at 2-1. + + Parameters + ---------- + boron_ppm : float + The weight fraction in parts-per-million of elemental boron in the + water. + temperature : float + Temperature in [K] used to compute water density. + pressure : float + Pressure in [MPa] used to compute water density. + temp_unit : str + The units used for the `temperature` argument. Valid units are 'K', + 'C', and 'F'. + press_unit : str + The units used for the `pressure` argument. Valid units are 'MPa' and + 'psi'. + density : float + Water density in [g / cm^3]. If specified, this value overrides the + temperature and pressure arguments. + **kwargs + All keyword arguments are passed to the created Material object. + + Returns + ------- + openmc.Material + + """ + # Perform any necessary unit conversions. + check_value('temperature unit', temp_unit, ('K', 'C', 'F')) + if temp_unit == 'K': + T = temperature + elif temp_unit == 'C': + T = temperature + 273.15 + elif temp_unit == 'F': + T = (temperature + 459.67) * 5.0 / 9.0 + check_value('pressure unit', press_unit, ('MPa', 'psi')) + if press_unit == 'MPa': + P = pressure + elif press_unit == 'psi': + P = pressure * 0.006895 + + # Set the density of water, either from an explicitly given density or from + # temperature and pressure. + if density is not None: + water_density = density + else: + water_density = openmc.data.water_density(T, P) + + # Compute the density of the solution. + solution_density = water_density / (1 - boron_ppm * 1e-6) + + # Compute the molar mass of pure water. + hydrogen = openmc.Element('H') + oxygen = openmc.Element('O') + M_H2O = 0.0 + for iso_name, frac, junk in hydrogen.expand(2.0, 'ao'): + M_H2O += frac * openmc.data.atomic_mass(iso_name) + for iso_name, frac, junk in oxygen.expand(1.0, 'ao'): + M_H2O += frac * openmc.data.atomic_mass(iso_name) + + # Compute the molar mass of boron. + boron = openmc.Element('B') + M_B = 0.0 + for iso_name, frac, junk in boron.expand(1.0, 'ao'): + M_B += frac * openmc.data.atomic_mass(iso_name) + + # Compute the number fractions of each element. + frac_H2O = (1 - boron_ppm * 1e-6) / M_H2O + frac_H = 2 * frac_H2O + frac_O = frac_H2O + frac_B = boron_ppm * 1e-6 / M_B + + # Build the material. + if density is None: + out = openmc.Material(temperature=T, **kwargs) + else: + out = openmc.Material(**kwargs) + out.add_element('H', frac_H, 'ao') + out.add_element('O', frac_O, 'ao') + out.add_element('B', frac_B, 'ao') + out.set_density('g/cc', solution_density) + out.add_s_alpha_beta('c_H_in_H2O') + return out + + def subdivide(surfaces): """Create regions separated by a series of surfaces. diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 38495cb1a..295153a9c 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -141,9 +141,9 @@ def test_materials(run_in_tmpdir): mats.export_to_xml() -def test_boric_acid(): +def test_borated_water(): # Test against reference values from the BEAVRS benchmark. - m = openmc.make_boric_acid(975, 566.5, 15.51, material_id=50) + m = openmc.model.make_borated_water(975, 566.5, 15.51, material_id=50) assert m.density == pytest.approx(0.7405, 1e-3) assert m.temperature == pytest.approx(566.5) assert m._sab[0][0] == 'c_H_in_H2O' @@ -154,6 +154,14 @@ def test_boric_acid(): assert nuc_dens[nuclide][1] == pytest.approx(ref_dens[nuclide], 1e-2) assert m.id == 50 - # Make sure the density override works - m = openmc.make_boric_acid(975, 566.5, 15.51, 0.9) + # Test the Celsius conversion. + m = openmc.model.make_borated_water(975, 293.35, 15.51, 'C') + assert m.density == pytest.approx(0.7405, 1e-3) + + # Test Fahrenheit and psi conversions. + m = openmc.model.make_borated_water(975, 560.0, 2250.0, 'F', 'psi') + assert m.density == pytest.approx(0.7405, 1e-3) + + # Test the density override + m = openmc.model.make_borated_water(975, 566.5, 15.51, density=0.9) assert m.density == pytest.approx(0.9, 1e-3) From e0d6640c4fb43edf3213efe62c05c1106575c5d9 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 11 Feb 2018 15:03:48 -0500 Subject: [PATCH 201/282] Add make_borated_water and water_density to docs --- docs/source/pythonapi/data.rst | 1 + docs/source/pythonapi/model.rst | 6 +----- tests/unit_tests/test_data_misc.py | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/source/pythonapi/data.rst b/docs/source/pythonapi/data.rst index 7f75ddaaa..3c221906d 100644 --- a/docs/source/pythonapi/data.rst +++ b/docs/source/pythonapi/data.rst @@ -34,6 +34,7 @@ Core Functions openmc.data.atomic_mass openmc.data.linearize openmc.data.thin + openmc.data.water_density openmc.data.write_compact_458_library Angle-Energy Distributions diff --git a/docs/source/pythonapi/model.rst b/docs/source/pythonapi/model.rst index 4ba247468..6aff6d4c2 100644 --- a/docs/source/pythonapi/model.rst +++ b/docs/source/pythonapi/model.rst @@ -5,11 +5,6 @@ Convenience Functions --------------------- -Several helper functions are available here. Ther first two create rectangular -and hexagonal prisms defined by the intersection of four and six surface -half-spaces, respectively. The last function takes a sequence of surfaces and -returns the regions that separate them. - .. autosummary:: :toctree: generated :nosignatures: @@ -17,6 +12,7 @@ returns the regions that separate them. openmc.model.get_hexagonal_prism openmc.model.get_rectangular_prism + openmc.model.make_borated_water openmc.model.subdivide TRISO Fuel Modeling diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py index b33ef996f..433d34adb 100644 --- a/tests/unit_tests/test_data_misc.py +++ b/tests/unit_tests/test_data_misc.py @@ -54,7 +54,7 @@ def test_water_density(): dens = openmc.data.water_density # These test values are from IAPWS R7-97(2012). They are actually specific # volumes so they need to be inverted. They also need to be divided by 1000 - # to convert from [kg / m^3] to [g / cm^2]. + # to convert from [kg / m^3] to [g / cm^3]. assert dens(300.0, 3.0) == pytest.approx(1e-3/0.100215168e-2, 1e-6) assert dens(300.0, 80.0) == pytest.approx(1e-3/0.971180894e-3, 1e-6) assert dens(500.0, 3.0) == pytest.approx(1e-3/0.120241800e-2, 1e-6) From d34bdf9b98bd2c06da5e5282d9516cce041be4cc Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 11 Feb 2018 18:39:06 -0500 Subject: [PATCH 202/282] Address #967 comments --- docs/source/pythonapi/model.rst | 2 +- openmc/data/data.py | 65 +++++------ openmc/model/funcs.py | 186 +++++++++++++++--------------- tests/unit_tests/test_material.py | 8 +- 4 files changed, 129 insertions(+), 132 deletions(-) diff --git a/docs/source/pythonapi/model.rst b/docs/source/pythonapi/model.rst index 6aff6d4c2..9ed77f366 100644 --- a/docs/source/pythonapi/model.rst +++ b/docs/source/pythonapi/model.rst @@ -10,9 +10,9 @@ Convenience Functions :nosignatures: :template: myfunction.rst + openmc.model.borated_water openmc.model.get_hexagonal_prism openmc.model.get_rectangular_prism - openmc.model.make_borated_water openmc.model.subdivide TRISO Fuel Modeling diff --git a/openmc/data/data.py b/openmc/data/data.py index 45a4406db..a7c0e536f 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -251,19 +251,19 @@ def water_density(temperature, pressure=0.1013): warn("Results are not valid for temperatures above 623.15 K.") # IAPWS region 4 parameters - _n4 = [0.11670521452767e4, -0.72421316703206e6, -0.17073846940092e2, - 0.12020824702470e5, -0.32325550322333e7, 0.14915108613530e2, - -0.48232657361591e4, 0.40511340542057e6, -0.23855557567849, - 0.65017534844798e3] + n4 = [0.11670521452767e4, -0.72421316703206e6, -0.17073846940092e2, + 0.12020824702470e5, -0.32325550322333e7, 0.14915108613530e2, + -0.48232657361591e4, 0.40511340542057e6, -0.23855557567849, + 0.65017534844798e3] # Compute the saturation temperature at the given pressure. beta = pressure**(0.25) - E = beta**2 + _n4[2] * beta + _n4[5] - F = _n4[0] * beta**2 + _n4[3] * beta + _n4[6] - G = _n4[1] * beta**2 + _n4[4] * beta + _n4[7] + E = beta**2 + n4[2] * beta + n4[5] + F = n4[0] * beta**2 + n4[3] * beta + n4[6] + G = n4[1] * beta**2 + n4[4] * beta + n4[7] D = 2.0 * G / (-F - sqrt(F**2 - 4 * E * G)) - T_sat = 0.5 * (_n4[9] + D - - sqrt((_n4[9] + D)**2 - 4.0 * (_n4[8] + _n4[9] * D))) + T_sat = 0.5 * (n4[9] + D + - sqrt((n4[9] + D)**2 - 4.0 * (n4[8] + n4[9] * D))) # Make sure we aren't above saturation. (Relax this bound by .2 degrees # for deg C to K conversions.) @@ -273,38 +273,37 @@ def water_density(temperature, pressure=0.1013): # IAPWS region 1 parameters R_GAS_CONSTANT = 0.461526 # kJ / kg / K - _ref_p = 16.53 # MPa - _ref_T = 1386 # K - _n1f = [0.14632971213167, -0.84548187169114, -0.37563603672040e1, - 0.33855169168385e1, -0.95791963387872, 0.15772038513228, - -0.16616417199501e-1, 0.81214629983568e-3, 0.28319080123804e-3, - -0.60706301565874e-3, -0.18990068218419e-1, -0.32529748770505e-1, - -0.21841717175414e-1, -0.52838357969930e-4, -0.47184321073267e-3, - -0.30001780793026e-3, 0.47661393906987e-4, -0.44141845330846e-5, - -0.72694996297594e-15, -0.31679644845054e-4, -0.28270797985312e-5, - -0.85205128120103e-9, -0.22425281908000e-5, -0.65171222895601e-6, - -0.14341729937924e-12, -0.40516996860117e-6, -0.12734301741641e-8, - -0.17424871230634e-9, -0.68762131295531e-18, 0.14478307828521e-19, - 0.26335781662795e-22, -0.11947622640071e-22, 0.18228094581404e-23, - -0.93537087292458e-25] - _I1f = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, - 4, 4, 5, 8, 8, 21, 23, 29, 30, 31, 32] - _J1f = [-2, -1, 0, 1, 2, 3, 4, 5, -9, -7, -1, 0, 1, 3, -3, 0, 1, 3, 17, -4, - 0, 6, -5, -2, 10, -8, -11, -6, -29, -31, -38, -39, -40, -41] + ref_p = 16.53 # MPa + ref_T = 1386 # K + n1f = [0.14632971213167, -0.84548187169114, -0.37563603672040e1, + 0.33855169168385e1, -0.95791963387872, 0.15772038513228, + -0.16616417199501e-1, 0.81214629983568e-3, 0.28319080123804e-3, + -0.60706301565874e-3, -0.18990068218419e-1, -0.32529748770505e-1, + -0.21841717175414e-1, -0.52838357969930e-4, -0.47184321073267e-3, + -0.30001780793026e-3, 0.47661393906987e-4, -0.44141845330846e-5, + -0.72694996297594e-15, -0.31679644845054e-4, -0.28270797985312e-5, + -0.85205128120103e-9, -0.22425281908000e-5, -0.65171222895601e-6, + -0.14341729937924e-12, -0.40516996860117e-6, -0.12734301741641e-8, + -0.17424871230634e-9, -0.68762131295531e-18, 0.14478307828521e-19, + 0.26335781662795e-22, -0.11947622640071e-22, 0.18228094581404e-23, + -0.93537087292458e-25] + I1f = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, + 4, 4, 5, 8, 8, 21, 23, 29, 30, 31, 32] + J1f = [-2, -1, 0, 1, 2, 3, 4, 5, -9, -7, -1, 0, 1, 3, -3, 0, 1, 3, 17, -4, + 0, 6, -5, -2, 10, -8, -11, -6, -29, -31, -38, -39, -40, -41] # Nondimensionalize the pressure and temperature. - pi = pressure / _ref_p - tau = _ref_T / temperature + pi = pressure / ref_p + tau = ref_T / temperature # Compute the derivative of gamma (dimensionless Gibbs free energy) with # respect to pi. gamma1_pi = 0.0 - for i in range(34): - gamma1_pi -= (_n1f[i] * _I1f[i] * (7.1 - pi)**(_I1f[i] - 1) - * (tau - 1.222)**_J1f[i]) + for n, I, J in zip(n1f, I1f, J1f): + gamma1_pi -= n * I * (7.1 - pi)**(I - 1) * (tau - 1.222)**J # Compute the leading coefficient. This sets the units at - # 1 [MPa] * [kg K / kJ] / [1 / K] + # 1 [MPa] * [kg K / kJ] * [1 / K] # = 1e6 [N / m^2] * 1e-3 [kg K / N / m] * [1 / K] # = 1e3 [kg / m^3] # = 1 [g / cm^3] diff --git a/openmc/model/funcs.py b/openmc/model/funcs.py index b8984ada0..e974f93b1 100644 --- a/openmc/model/funcs.py +++ b/openmc/model/funcs.py @@ -8,6 +8,98 @@ from openmc.checkvalue import check_type, check_value import openmc.data +def borated_water(boron_ppm, temperature=293., pressure=0.1013, temp_unit='K', + press_unit='MPa', density=None, **kwargs): + """Return a Material with the composition of boron dissolved in water. + + The water density can be determined from a temperature and pressure, or it + can be set directly. + + The concentration of boron has no effect on the stoichiometric ratio of H + and O---they are fixed at 2-1. + + Parameters + ---------- + boron_ppm : float + The weight fraction in parts-per-million of elemental boron in the + water. + temperature : float + Temperature in [K] used to compute water density. + pressure : float + Pressure in [MPa] used to compute water density. + temp_unit : {'K', 'C', 'F'} + The units used for the `temperature` argument. + press_unit : {'MPa', 'psi'} + The units used for the `pressure` argument. + density : float + Water density in [g / cm^3]. If specified, this value overrides the + temperature and pressure arguments. + **kwargs + All keyword arguments are passed to the created Material object. + + Returns + ------- + openmc.Material + + """ + # Perform any necessary unit conversions. + check_value('temperature unit', temp_unit, ('K', 'C', 'F')) + if temp_unit == 'K': + T = temperature + elif temp_unit == 'C': + T = temperature + 273.15 + elif temp_unit == 'F': + T = (temperature + 459.67) * 5.0 / 9.0 + check_value('pressure unit', press_unit, ('MPa', 'psi')) + if press_unit == 'MPa': + P = pressure + elif press_unit == 'psi': + P = pressure * 0.006895 + + # Set the density of water, either from an explicitly given density or from + # temperature and pressure. + if density is not None: + water_density = density + else: + water_density = openmc.data.water_density(T, P) + + # Compute the density of the solution. + solution_density = water_density / (1 - boron_ppm * 1e-6) + + # Compute the molar mass of pure water. + hydrogen = openmc.Element('H') + oxygen = openmc.Element('O') + M_H2O = 0.0 + for iso_name, frac, junk in hydrogen.expand(2.0, 'ao'): + M_H2O += frac * openmc.data.atomic_mass(iso_name) + for iso_name, frac, junk in oxygen.expand(1.0, 'ao'): + M_H2O += frac * openmc.data.atomic_mass(iso_name) + + # Compute the molar mass of boron. + boron = openmc.Element('B') + M_B = 0.0 + for iso_name, frac, junk in boron.expand(1.0, 'ao'): + M_B += frac * openmc.data.atomic_mass(iso_name) + + # Compute the number fractions of each element. + frac_H2O = (1 - boron_ppm * 1e-6) / M_H2O + frac_H = 2 * frac_H2O + frac_O = frac_H2O + frac_B = boron_ppm * 1e-6 / M_B + + # Build the material. + if density is None: + out = openmc.Material(temperature=T, **kwargs) + else: + out = openmc.Material(**kwargs) + out.add_element('H', frac_H, 'ao') + out.add_element('O', frac_O, 'ao') + out.add_element('B', frac_B, 'ao') + out.set_density('g/cc', solution_density) + out.add_s_alpha_beta('c_H_in_H2O') + return out + + def get_rectangular_prism(width, height, axis='z', origin=(0., 0.), boundary_type='transmission', corner_radius=0.): """Get an infinite rectangular prism from four planar surfaces. @@ -253,100 +345,6 @@ def get_hexagonal_prism(edge_length=1., orientation='y', origin=(0., 0.), return prism -def make_borated_water(boron_ppm, temperature=293., pressure=0.1013, - temp_unit='K', press_unit='MPa', density=None, **kwargs): - """Return a Material with the composition of boron dissolved in water. - - The water density can be determined from a temperature and pressure, or it - can be set directly. - - The concentration of boron has no effect on the stoichometric ratio of H - and O---they are fixed at 2-1. - - Parameters - ---------- - boron_ppm : float - The weight fraction in parts-per-million of elemental boron in the - water. - temperature : float - Temperature in [K] used to compute water density. - pressure : float - Pressure in [MPa] used to compute water density. - temp_unit : str - The units used for the `temperature` argument. Valid units are 'K', - 'C', and 'F'. - press_unit : str - The units used for the `pressure` argument. Valid units are 'MPa' and - 'psi'. - density : float - Water density in [g / cm^3]. If specified, this value overrides the - temperature and pressure arguments. - **kwargs - All keyword arguments are passed to the created Material object. - - Returns - ------- - openmc.Material - - """ - # Perform any necessary unit conversions. - check_value('temperature unit', temp_unit, ('K', 'C', 'F')) - if temp_unit == 'K': - T = temperature - elif temp_unit == 'C': - T = temperature + 273.15 - elif temp_unit == 'F': - T = (temperature + 459.67) * 5.0 / 9.0 - check_value('pressure unit', press_unit, ('MPa', 'psi')) - if press_unit == 'MPa': - P = pressure - elif press_unit == 'psi': - P = pressure * 0.006895 - - # Set the density of water, either from an explicitly given density or from - # temperature and pressure. - if density is not None: - water_density = density - else: - water_density = openmc.data.water_density(T, P) - - # Compute the density of the solution. - solution_density = water_density / (1 - boron_ppm * 1e-6) - - # Compute the molar mass of pure water. - hydrogen = openmc.Element('H') - oxygen = openmc.Element('O') - M_H2O = 0.0 - for iso_name, frac, junk in hydrogen.expand(2.0, 'ao'): - M_H2O += frac * openmc.data.atomic_mass(iso_name) - for iso_name, frac, junk in oxygen.expand(1.0, 'ao'): - M_H2O += frac * openmc.data.atomic_mass(iso_name) - - # Compute the molar mass of boron. - boron = openmc.Element('B') - M_B = 0.0 - for iso_name, frac, junk in boron.expand(1.0, 'ao'): - M_B += frac * openmc.data.atomic_mass(iso_name) - - # Compute the number fractions of each element. - frac_H2O = (1 - boron_ppm * 1e-6) / M_H2O - frac_H = 2 * frac_H2O - frac_O = frac_H2O - frac_B = boron_ppm * 1e-6 / M_B - - # Build the material. - if density is None: - out = openmc.Material(temperature=T, **kwargs) - else: - out = openmc.Material(**kwargs) - out.add_element('H', frac_H, 'ao') - out.add_element('O', frac_O, 'ao') - out.add_element('B', frac_B, 'ao') - out.set_density('g/cc', solution_density) - out.add_s_alpha_beta('c_H_in_H2O') - return out - - def subdivide(surfaces): """Create regions separated by a series of surfaces. diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 295153a9c..226521541 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -143,7 +143,7 @@ def test_materials(run_in_tmpdir): def test_borated_water(): # Test against reference values from the BEAVRS benchmark. - m = openmc.model.make_borated_water(975, 566.5, 15.51, material_id=50) + m = openmc.model.borated_water(975, 566.5, 15.51, material_id=50) assert m.density == pytest.approx(0.7405, 1e-3) assert m.temperature == pytest.approx(566.5) assert m._sab[0][0] == 'c_H_in_H2O' @@ -155,13 +155,13 @@ def test_borated_water(): assert m.id == 50 # Test the Celsius conversion. - m = openmc.model.make_borated_water(975, 293.35, 15.51, 'C') + m = openmc.model.borated_water(975, 293.35, 15.51, 'C') assert m.density == pytest.approx(0.7405, 1e-3) # Test Fahrenheit and psi conversions. - m = openmc.model.make_borated_water(975, 560.0, 2250.0, 'F', 'psi') + m = openmc.model.borated_water(975, 560.0, 2250.0, 'F', 'psi') assert m.density == pytest.approx(0.7405, 1e-3) # Test the density override - m = openmc.model.make_borated_water(975, 566.5, 15.51, density=0.9) + m = openmc.model.borated_water(975, 566.5, 15.51, density=0.9) assert m.density == pytest.approx(0.9, 1e-3) From 718474fb494cf9db307594d1fff867cd7ed80b31 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Tue, 13 Feb 2018 06:35:50 -0500 Subject: [PATCH 203/282] Cleaned up index_inelastic_scatter a bit to address comments in #963" --- src/nuclide_header.F90 | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index c0c5c2627..8362f5d41 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -303,7 +303,7 @@ contains real(8) :: temp_actual type(VectorInt) :: MTs type(VectorInt) :: temps_to_read - type(VectorInt) :: index_inelastic_scatter_vector + type(VectorInt) :: index_inelastic_scatter ! Get name of nuclide from group name_len = len(this % name) @@ -478,7 +478,7 @@ contains MTs % data(i) /= N_3NF .and. MTs % data(i) < 200 .and. & MTs % data(i) /= N_LEVEL .and. MTs % data(i) /= ELASTIC) then - call index_inelastic_scatter_vector % push_back(i) + call index_inelastic_scatter % push_back(i) end if call close_group(rx_group) @@ -486,10 +486,9 @@ contains call close_group(rxs_group) ! Recast to a regular array to save space - allocate(this % index_inelastic_scatter( & - index_inelastic_scatter_vector % size())) - this % index_inelastic_scatter = index_inelastic_scatter_vector % data(:) - call index_inelastic_scatter_vector % clear() + allocate(this % index_inelastic_scatter(index_inelastic_scatter % size())) + this % index_inelastic_scatter = & + index_inelastic_scatter % data(1: index_inelastic_scatter % size()) ! Read unresolved resonance probability tables if present if (object_exists(group_id, 'urr')) then From f8764416d2c727c5b1693c297c3f7994c8314d1b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 9 Feb 2018 13:29:04 -0600 Subject: [PATCH 204/282] Copy OpenDeplete files from commit 2d804c227a --- chains/chain_simple.xml | 49 + chains/chain_test.xml | 23 + docs/source/pythonapi/deplete/index.rst | 56 ++ .../pythonapi/deplete/integrator.CRAM16.rst | 6 + .../pythonapi/deplete/integrator.CRAM48.rst | 6 + .../pythonapi/deplete/integrator.cecm.rst | 6 + .../deplete/integrator.predictor.rst | 6 + .../deplete/integrator.save_results.rst | 6 + .../deplete/opendeplete.Concentrations.rst | 30 + .../deplete/opendeplete.ReactionRates.rst | 30 + .../pythonapi/deplete/opendeplete.Results.rst | 22 + openmc/deplete/__init__.py | 24 + openmc/deplete/atom_number.py | 236 +++++ openmc/deplete/depletion_chain.py | 472 ++++++++++ openmc/deplete/dummy_comm.py | 27 + openmc/deplete/function.py | 114 +++ openmc/deplete/integrator/__init__.py | 11 + openmc/deplete/integrator/cecm.py | 133 +++ openmc/deplete/integrator/cram.py | 185 ++++ openmc/deplete/integrator/predictor.py | 100 ++ openmc/deplete/integrator/save_results.py | 46 + openmc/deplete/nuclide.py | 178 ++++ openmc/deplete/openmc_wrapper.py | 853 ++++++++++++++++++ openmc/deplete/reaction_rates.py | 113 +++ openmc/deplete/results.py | 454 ++++++++++ openmc/deplete/utilities.py | 98 ++ scripts/example_geometry.py | 358 ++++++++ scripts/example_plot.py | 46 + scripts/example_run.py | 39 + scripts/make_chain.py | 60 ++ tests/deplete_tests/__init__.py | 0 tests/deplete_tests/dummy_geometry.py | 165 ++++ tests/deplete_tests/example_geometry.py | 1 + tests/deplete_tests/test_atom_number.py | 180 ++++ tests/deplete_tests/test_cecm_regression.py | 69 ++ tests/deplete_tests/test_cram.py | 48 + tests/deplete_tests/test_depletion_chain.py | 197 ++++ tests/deplete_tests/test_full.py | 119 +++ tests/deplete_tests/test_integrator.py | 116 +++ tests/deplete_tests/test_nuclide.py | 121 +++ .../test_predictor_regression.py | 68 ++ tests/deplete_tests/test_reaction_rates.py | 86 ++ tests/deplete_tests/test_reference.h5 | Bin 0 -> 165384 bytes tests/deplete_tests/test_utilities.py | 68 ++ 44 files changed, 5025 insertions(+) create mode 100644 chains/chain_simple.xml create mode 100644 chains/chain_test.xml create mode 100644 docs/source/pythonapi/deplete/index.rst create mode 100644 docs/source/pythonapi/deplete/integrator.CRAM16.rst create mode 100644 docs/source/pythonapi/deplete/integrator.CRAM48.rst create mode 100644 docs/source/pythonapi/deplete/integrator.cecm.rst create mode 100644 docs/source/pythonapi/deplete/integrator.predictor.rst create mode 100644 docs/source/pythonapi/deplete/integrator.save_results.rst create mode 100644 docs/source/pythonapi/deplete/opendeplete.Concentrations.rst create mode 100644 docs/source/pythonapi/deplete/opendeplete.ReactionRates.rst create mode 100644 docs/source/pythonapi/deplete/opendeplete.Results.rst create mode 100644 openmc/deplete/__init__.py create mode 100644 openmc/deplete/atom_number.py create mode 100644 openmc/deplete/depletion_chain.py create mode 100644 openmc/deplete/dummy_comm.py create mode 100644 openmc/deplete/function.py create mode 100644 openmc/deplete/integrator/__init__.py create mode 100644 openmc/deplete/integrator/cecm.py create mode 100644 openmc/deplete/integrator/cram.py create mode 100644 openmc/deplete/integrator/predictor.py create mode 100644 openmc/deplete/integrator/save_results.py create mode 100644 openmc/deplete/nuclide.py create mode 100644 openmc/deplete/openmc_wrapper.py create mode 100644 openmc/deplete/reaction_rates.py create mode 100644 openmc/deplete/results.py create mode 100644 openmc/deplete/utilities.py create mode 100644 scripts/example_geometry.py create mode 100644 scripts/example_plot.py create mode 100644 scripts/example_run.py create mode 100644 scripts/make_chain.py create mode 100644 tests/deplete_tests/__init__.py create mode 100644 tests/deplete_tests/dummy_geometry.py create mode 120000 tests/deplete_tests/example_geometry.py create mode 100644 tests/deplete_tests/test_atom_number.py create mode 100644 tests/deplete_tests/test_cecm_regression.py create mode 100644 tests/deplete_tests/test_cram.py create mode 100644 tests/deplete_tests/test_depletion_chain.py create mode 100644 tests/deplete_tests/test_full.py create mode 100644 tests/deplete_tests/test_integrator.py create mode 100644 tests/deplete_tests/test_nuclide.py create mode 100644 tests/deplete_tests/test_predictor_regression.py create mode 100644 tests/deplete_tests/test_reaction_rates.py create mode 100644 tests/deplete_tests/test_reference.h5 create mode 100644 tests/deplete_tests/test_utilities.py diff --git a/chains/chain_simple.xml b/chains/chain_simple.xml new file mode 100644 index 000000000..345da2237 --- /dev/null +++ b/chains/chain_simple.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + 2.53000e-02 + + Gd157 Gd156 I135 Xe135 Xe136 Cs135 + 1.093250e-04 2.087260e-04 2.780820e-02 6.759540e-03 2.392300e-02 4.356330e-05 + + + + + + + 2.53000e-02 + + Gd157 Gd156 I135 Xe135 Xe136 Cs135 + 6.142710e-5 1.483250e-04 0.0292737 0.002566345 0.0219242 4.9097e-6 + + + + + + + 2.53000e-02 + + Gd157 Gd156 I135 Xe135 Xe136 Cs135 + 4.141120e-04 7.605360e-04 0.0135457 0.00026864 0.0024432 3.7100E-07 + + + + diff --git a/chains/chain_test.xml b/chains/chain_test.xml new file mode 100644 index 000000000..598570406 --- /dev/null +++ b/chains/chain_test.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + 0.0253 + + A B + 0.0292737 0.002566345 + + + + diff --git a/docs/source/pythonapi/deplete/index.rst b/docs/source/pythonapi/deplete/index.rst new file mode 100644 index 000000000..55380c7a1 --- /dev/null +++ b/docs/source/pythonapi/deplete/index.rst @@ -0,0 +1,56 @@ +.. _api: + +================= +API Documentation +================= + +Integrators +----------- + +.. toctree:: + :maxdepth: 2 + + integrator.predictor + integrator.cecm + +Integrator Helper Functions +--------------------------- +.. toctree:: + :maxdepth: 2 + + integrator.CRAM16 + integrator.CRAM48 + integrator.save_results + +Metaclasses +----------- + +.. autosummary:: + :toctree: generated + :nosignatures: + + opendeplete.Settings + opendeplete.Operator + +OpenMC Classes +-------------- + +.. autosummary:: + :toctree: generated + :nosignatures: + + opendeplete.OpenMCSettings + opendeplete.Materials + opendeplete.OpenMCOperator + +Data Classes +------------ +.. autosummary:: + :toctree: generated + :nosignatures: + + opendeplete.AtomNumber + opendeplete.DepletionChain + opendeplete.Nuclide + opendeplete.ReactionRates + opendeplete.Results diff --git a/docs/source/pythonapi/deplete/integrator.CRAM16.rst b/docs/source/pythonapi/deplete/integrator.CRAM16.rst new file mode 100644 index 000000000..f9eba273e --- /dev/null +++ b/docs/source/pythonapi/deplete/integrator.CRAM16.rst @@ -0,0 +1,6 @@ +integrator\.CRAM16 +================== + +.. currentmodule:: opendeplete.integrator + +.. autofunction:: CRAM16 diff --git a/docs/source/pythonapi/deplete/integrator.CRAM48.rst b/docs/source/pythonapi/deplete/integrator.CRAM48.rst new file mode 100644 index 000000000..d7467a418 --- /dev/null +++ b/docs/source/pythonapi/deplete/integrator.CRAM48.rst @@ -0,0 +1,6 @@ +integrator\.CRAM48 +================== + +.. currentmodule:: opendeplete.integrator + +.. autofunction:: CRAM48 diff --git a/docs/source/pythonapi/deplete/integrator.cecm.rst b/docs/source/pythonapi/deplete/integrator.cecm.rst new file mode 100644 index 000000000..507a638f6 --- /dev/null +++ b/docs/source/pythonapi/deplete/integrator.cecm.rst @@ -0,0 +1,6 @@ +integrator\.cecm +================= + +.. currentmodule:: opendeplete.integrator + +.. autofunction:: cecm diff --git a/docs/source/pythonapi/deplete/integrator.predictor.rst b/docs/source/pythonapi/deplete/integrator.predictor.rst new file mode 100644 index 000000000..d6c0fd827 --- /dev/null +++ b/docs/source/pythonapi/deplete/integrator.predictor.rst @@ -0,0 +1,6 @@ +integrator\.predictor +===================== + +.. currentmodule:: opendeplete.integrator + +.. autofunction:: predictor diff --git a/docs/source/pythonapi/deplete/integrator.save_results.rst b/docs/source/pythonapi/deplete/integrator.save_results.rst new file mode 100644 index 000000000..5c21dcb66 --- /dev/null +++ b/docs/source/pythonapi/deplete/integrator.save_results.rst @@ -0,0 +1,6 @@ +integrator\.save_results +======================== + +.. currentmodule:: opendeplete.integrator + +.. autofunction:: save_results diff --git a/docs/source/pythonapi/deplete/opendeplete.Concentrations.rst b/docs/source/pythonapi/deplete/opendeplete.Concentrations.rst new file mode 100644 index 000000000..6fa07a970 --- /dev/null +++ b/docs/source/pythonapi/deplete/opendeplete.Concentrations.rst @@ -0,0 +1,30 @@ +opendeplete.Concentrations +========================== + +.. currentmodule:: opendeplete + +.. autoclass:: Concentrations + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~Concentrations.__init__ + ~Concentrations.convert_nested_dict + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~Concentrations.n_cell + ~Concentrations.n_nuc + + \ No newline at end of file diff --git a/docs/source/pythonapi/deplete/opendeplete.ReactionRates.rst b/docs/source/pythonapi/deplete/opendeplete.ReactionRates.rst new file mode 100644 index 000000000..99e048b56 --- /dev/null +++ b/docs/source/pythonapi/deplete/opendeplete.ReactionRates.rst @@ -0,0 +1,30 @@ +opendeplete.ReactionRates +========================= + +.. currentmodule:: opendeplete + +.. autoclass:: ReactionRates + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~ReactionRates.__init__ + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~ReactionRates.n_cell + ~ReactionRates.n_nuc + ~ReactionRates.n_react + + \ No newline at end of file diff --git a/docs/source/pythonapi/deplete/opendeplete.Results.rst b/docs/source/pythonapi/deplete/opendeplete.Results.rst new file mode 100644 index 000000000..0ab8a1f71 --- /dev/null +++ b/docs/source/pythonapi/deplete/opendeplete.Results.rst @@ -0,0 +1,22 @@ +opendeplete.Results +=================== + +.. currentmodule:: opendeplete + +.. autoclass:: Results + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~Results.__init__ + + + + + + \ No newline at end of file diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py new file mode 100644 index 000000000..994a51e12 --- /dev/null +++ b/openmc/deplete/__init__.py @@ -0,0 +1,24 @@ +""" +OpenDeplete +=========== + +A simple 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 .depletion_chain import * +from .openmc_wrapper import * +from .reaction_rates import * +from .function import * +from .results import * +from .integrator import * +from .utilities import * diff --git a/openmc/deplete/atom_number.py b/openmc/deplete/atom_number.py new file mode 100644 index 000000000..03bedbf53 --- /dev/null +++ b/openmc/deplete/atom_number.py @@ -0,0 +1,236 @@ +"""AtomNumber module. + +An ndarray to store atom densities with string, integer, or slice indexing. +""" + +import numpy as np + + +class AtomNumber(object): + """ AtomNumber module. + + An ndarray to store atom densities with string, integer, or slice indexing. + + Parameters + ---------- + mat_to_ind : OrderedDict of str to int + A dictionary mapping material ID as string to index. + nuc_to_ind : OrderedDict of str to int + A dictionary mapping nuclide name as string to index. + volume : OrderedDict of int to float + Volume of geometry. + n_mat_burn : int + Number of materials to be burned. + n_nuc_burn : int + Number of nuclides to be burned. + + Attributes + ---------- + mat_to_ind : OrderedDict of str to int + A dictionary mapping cell ID as string to index. + nuc_to_ind : OrderedDict of str to int + A dictionary mapping nuclide name as string to index. + volume : numpy.array + Volume of geometry indexed by mat_to_ind. If a volume is not found, + it defaults to 1 so that reading density still works correctly. + n_mat_burn : int + Number of materials to be burned. + n_nuc_burn : int + Number of nuclides to be burned. + n_mat : int + Number of materials. + n_nuc : int + Number of nucs. + number : numpy.array + Array storing total atoms indexed by the above dictionaries. + burn_nuc_list : list of str + A list of all nuclide material names. Used for sorting the simulation. + burn_mat_list : list of str + A list of all burning material names. Used for sorting the simulation. + """ + + def __init__(self, mat_to_ind, nuc_to_ind, volume, n_mat_burn, n_nuc_burn): + + self.mat_to_ind = mat_to_ind + self.nuc_to_ind = nuc_to_ind + + self.volume = np.ones(self.n_mat) + + for mat in volume: + if str(mat) in self.mat_to_ind: + ind = self.mat_to_ind[str(mat)] + self.volume[ind] = volume[mat] + + self.n_mat_burn = n_mat_burn + self.n_nuc_burn = n_nuc_burn + + self.number = np.zeros((self.n_mat, self.n_nuc)) + + # For performance, create storage for burn_nuc_list, burn_mat_list + self._burn_nuc_list = None + self._burn_mat_list = None + + 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.array + The value indexed from self.number. + """ + + 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.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 to set the array to. + """ + + mat, nuc = pos + if isinstance(mat, str): + mat = self.mat_to_ind[mat] + if isinstance(nuc, str): + nuc = self.nuc_to_ind[nuc] + + self.number[mat, nuc] = val + + 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.array + The density indexed. + """ + + if isinstance(mat, str): + mat = self.mat_to_ind[mat] + if isinstance(nuc, str): + nuc = self.nuc_to_ind[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.array + Array of values to set. + """ + + if isinstance(mat, str): + mat = self.mat_to_ind[mat] + if isinstance(nuc, str): + nuc = self.nuc_to_ind[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.array + The slice requested. + """ + + if isinstance(mat, str): + mat = self.mat_to_ind[mat] + + return self[mat, 0: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.array + The slice to set. + """ + + if isinstance(mat, str): + mat = self.mat_to_ind[mat] + + self[mat, 0:self.n_nuc_burn] = val + + @property + def n_mat(self): + """Number of materials.""" + return len(self.mat_to_ind) + + @property + def n_nuc(self): + """Number of nuclides.""" + return len(self.nuc_to_ind) + + @property + def burn_nuc_list(self): + """ burn_nuc_list : list of str + A list of all nuclide material names. Used for sorting the simulation. + """ + + if self._burn_nuc_list is None: + self._burn_nuc_list = [None] * self.n_nuc_burn + + for nuc in self.nuc_to_ind: + ind = self.nuc_to_ind[nuc] + if ind < self.n_nuc_burn: + self._burn_nuc_list[ind] = nuc + + return self._burn_nuc_list + + @property + def burn_mat_list(self): + """ burn_mat_list : list of str + A list of all burning material names. Used for sorting the simulation. + """ + + if self._burn_mat_list is None: + self._burn_mat_list = [None] * self.n_mat_burn + + for mat in self.mat_to_ind: + ind = self.mat_to_ind[mat] + if ind < self.n_mat_burn: + self._burn_mat_list[ind] = mat + + return self._burn_mat_list diff --git a/openmc/deplete/depletion_chain.py b/openmc/deplete/depletion_chain.py new file mode 100644 index 000000000..05cc9db43 --- /dev/null +++ b/openmc/deplete/depletion_chain.py @@ -0,0 +1,472 @@ +"""depletion_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 + +from tqdm import tqdm +import scipy.sparse as sp +import openmc.data +# 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 + from openmc.clean_xml import clean_xml_indentation + _have_lxml = False + +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 _get_zai(s): + """Get ZAI value (10000*z + 10*A + metastable state) for sorting purposes""" + symbol, A, state = re.match(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)', s).groups() + Z = openmc.data.ATOMIC_NUMBER[symbol] + A = int(A) + state = int(state[2:]) if state else 0 + return 10000*Z + 10*A + state + + +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. + + """ + + symbol, A, state = re.match(r'([A-Zn][a-z]*)(\d+)((?:_m\d+)?)', + product).groups() + Z = openmc.data.ATOMIC_NUMBER[symbol] + A = int(A) + + # First check if ground state is available + if state: + metastable_state = int(state[2:]) + 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 DepletionChain(object): + """ The DepletionChain class. + + This class contains a full representation of a depletion chain. + + Attributes + ---------- + n_nuclides : int + Number of nuclides in chain. + nuclides : list of Nuclide + List of nuclides in chain. + nuclide_dict : OrderedDict of str to int + Maps a nuclide name to an index in nuclides. + nuc_to_react_ind : OrderedDict of str to int + Dictionary mapping a nuclide name to an index in ReactionRates. + react_to_ind : OrderedDict of str to int + Dictionary mapping a reaction name to an index in ReactionRates. + + """ + + def __init__(self): + self.nuclides = [] + self.nuclide_dict = OrderedDict() + self.nuc_to_react_ind = OrderedDict() + self.react_to_ind = OrderedDict() + + @property + def n_nuclides(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 + + """ + depl_chain = cls() + + # Create dictionary mapping target to filename + reactions = {} + with tqdm(neutron_files) as pbar: + for f in pbar: + pbar.set_description('Processing {}'.format(os.path.basename(f))) + 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 + decay_data = {} + with tqdm(decay_files) as pbar: + for f in pbar: + pbar.set_description('Processing {}'.format(os.path.basename(f))) + data = openmc.data.Decay(f) + decay_data[data.nuclide['name']] = data + + fpy_data = {} + with tqdm(fpy_files) as pbar: + for f in pbar: + pbar.set_description('Processing {}'.format(os.path.basename(f))) + data = openmc.data.FissionProductYields(f) + fpy_data[data.nuclide['name']] = data + + print('Creating depletion_chain...') + missing_daughter = [] + missing_rx_product = [] + missing_fpy = [] + missing_fp = [] + + reaction_index = 0 + for idx, parent in enumerate(sorted(decay_data, key=_get_zai)): + data = decay_data[parent] + + nuclide = Nuclide() + nuclide.name = parent + + depl_chain.nuclides.append(nuclide) + depl_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 depl_chain.react_to_ind: + depl_chain.react_to_ind[name] = reaction_index + reaction_index += 1 + + 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 depl_chain.react_to_ind: + depl_chain.react_to_ind['fission'] = reaction_index + reaction_index += 1 + 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=_get_zai): + 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 depl_chain + + @classmethod + def xml_read(cls, filename): + """Reads a depletion chain XML file. + + Parameters + ---------- + filename : str + The path to the depletion chain XML file. + + Todo + ---- + Allow for branching on capture, etc. + """ + depl_chain = cls() + + # Load XML tree + try: + root = ET.parse(filename) + except: + if filename is None: + print("No chain specified, either manually or in environment variable OPENDEPLETE_CHAIN.") + else: + print('Decay chain "', filename, '" is invalid.') + raise + + reaction_index = 0 + for i, nuclide_elem in enumerate(root.findall('nuclide_table')): + nuc = Nuclide.xml_read(nuclide_elem) + depl_chain.nuclide_dict[nuc.name] = i + + # Check for reaction paths + for rx in nuc.reactions: + if rx.type not in depl_chain.react_to_ind: + depl_chain.react_to_ind[rx.type] = reaction_index + reaction_index += 1 + + depl_chain.nuclides.append(nuc) + + return depl_chain + + def xml_write(self, filename): + """Writes a depletion chain XML file. + + Parameters + ---------- + filename : str + The path to the depletion chain XML file. + + """ + + root_elem = ET.Element('depletion') + for nuclide in self.nuclides: + root_elem.append(nuclide.xml_write()) + + tree = ET.ElementTree(root_elem) + if _have_lxml: + tree.write(filename, encoding='utf-8', pretty_print=True) + else: + clean_xml_indentation(root_elem, spaces_per_level=2) + tree.write(filename, encoding='utf-8') + + def form_matrix(self, rates): + """ Forms depletion matrix. + + Parameters + ---------- + rates : numpy.ndarray + 2D array indexed by nuclide then by cell. + + 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 self.nuc_to_react_ind: + # Extract all reactions for this nuclide in this cell + nuc_ind = self.nuc_to_react_ind[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 = self.react_to_ind[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 + matrix_dok = sp.dok_matrix((self.n_nuclides, self.n_nuclides)) + dict.update(matrix_dok, matrix) + return matrix_dok.tocsr() + + def nuc_by_ind(self, ind): + """ Extracts nuclides from the list by dictionary key. + + Parameters + ---------- + ind : str + Name of nuclide. + + Returns + ------- + Nuclide + Nuclide object that corresponds to ind. + """ + return self.nuclides[self.nuclide_dict[ind]] diff --git a/openmc/deplete/dummy_comm.py b/openmc/deplete/dummy_comm.py new file mode 100644 index 000000000..b3fa27264 --- /dev/null +++ b/openmc/deplete/dummy_comm.py @@ -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] diff --git a/openmc/deplete/function.py b/openmc/deplete/function.py new file mode 100644 index 000000000..74eb92422 --- /dev/null +++ b/openmc/deplete/function.py @@ -0,0 +1,114 @@ +"""function module. + +This module contains the Operator class, which is then passed to an integrator +to run a full depletion simulation. +""" + +from abc import ABCMeta, abstractmethod + +class Settings(object): + """ The Settings class. + + Contains all parameters necessary for the integrator. + + Attributes + ---------- + dt_vec : numpy.array + Array of time steps to take. + output_dir : str + Path to output directory to save results. + """ + + def __init__(self): + # Integrator specific + self.dt_vec = None + self.output_dir = None + +class Operator(metaclass=ABCMeta): + """ The Operator metaclass. + + This defines all functions that the integrator needs to operate. + + Attributes + ---------- + settings : Settings + Settings object. + """ + + def __init__(self, settings): + self.settings = settings + + @abstractmethod + def initial_condition(self): + """ Performs final setup and returns initial condition. + + Returns + ------- + list of numpy.array + Total density for initial conditions. + """ + + pass + + @abstractmethod + def eval(self, vec, print_out=True): + """ Runs a simulation. + + Parameters + ---------- + vec : list of numpy.array + Total atoms to be used in function. + print_out : bool, optional + Whether or not to print out time. + + Returns + ------- + k : float + Eigenvalue of the problem. + rates : ReactionRates + Reaction rates from this simulation. + seed : int + Seed for this simulation. + """ + + 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 + + @abstractmethod + def form_matrix(self, y, mat): + """ Forms the f(y) matrix in y' = f(y)y. + + Nominally a depletion matrix, this is abstracted on the off chance + that the function f has nothing to do with depletion at all. + + Parameters + ---------- + y : numpy.ndarray + An array representing y. + mat : int + Material id. + + Returns + ------- + scipy.sparse.csr_matrix + Sparse matrix representing f(y). + """ + + pass diff --git a/openmc/deplete/integrator/__init__.py b/openmc/deplete/integrator/__init__.py new file mode 100644 index 000000000..607650dc6 --- /dev/null +++ b/openmc/deplete/integrator/__init__.py @@ -0,0 +1,11 @@ +""" +Integrator +=========== + +The integrator subcomponents. +""" + +from .cecm import * +from .cram import * +from .predictor import * +from .save_results import * diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py new file mode 100644 index 000000000..4d9baebb2 --- /dev/null +++ b/openmc/deplete/integrator/cecm.py @@ -0,0 +1,133 @@ +""" The CE/CM integrator.""" + +import copy +from itertools import repeat +import os +from multiprocessing import Pool +import time + +from .. import comm +from .cram import CRAM48, cram_wrapper +from .save_results import save_results + + +def cecm(operator, print_out=True): + """The CE/CM integrator. + + Implements the second order CE/CM Predictor-Corrector algorithm [ref]_. + 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 + + .. [ref] + Isotalo, Aarno. "Comparison of Neutronics-Depletion Coupling Schemes + for Burnup Calculations—Continued Study." Nuclear Science and + Engineering 180.3 (2015): 286-300. + + Parameters + ---------- + operator : Operator + The operator object to simulate on. + print_out : bool, optional + Whether or not to print out time. + """ + + # Save current directory + dir_home = os.getcwd() + + # Move to folder + os.makedirs(operator.settings.output_dir, exist_ok=True) + os.chdir(operator.settings.output_dir) + + # Generate initial conditions + vec = operator.initial_condition() + + n_mats = len(vec) + + t = 0.0 + + for i, dt in enumerate(operator.settings.dt_vec): + # Create vectors + x = [copy.deepcopy(vec)] + seeds = [] + eigvls = [] + rates_array = [] + + eigvl, rates, seed = operator.eval(x[0]) + + eigvls.append(eigvl) + seeds.append(seed) + rates_array.append(rates) + + t_start = time.time() + + chains = repeat(operator.chain, n_mats) + vecs = (x[0][i] for i in range(n_mats)) + rates = (rates_array[0][i, :, :] for i in range(n_mats)) + dts = repeat(dt/2, n_mats) + + 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) + + x.append(x_result) + + eigvl, rates, seed = operator.eval(x[1]) + + eigvls.append(eigvl) + seeds.append(seed) + rates_array.append(rates) + + t_start = time.time() + + chains = repeat(operator.chain, n_mats) + vecs = (x[0][i] for i in range(n_mats)) + rates = (rates_array[1][i, :, :] for i in range(n_mats)) + dts = repeat(dt, n_mats) + + 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) + + # Create results, write to disk + save_results(operator, x, rates_array, eigvls, seeds, [t, t + dt], i) + + t += dt + vec = copy.deepcopy(x_result) + + # Perform one last simulation + x = [copy.deepcopy(vec)] + seeds = [] + eigvls = [] + rates_array = [] + eigvl, rates, seed = operator.eval(x[0]) + + eigvls.append(eigvl) + seeds.append(seed) + rates_array.append(rates) + + # Create results, write to disk + save_results(operator, x, rates_array, eigvls, seeds, [t, t], + len(operator.settings.dt_vec)) + + # Return to origin + os.chdir(dir_home) diff --git a/openmc/deplete/integrator/cram.py b/openmc/deplete/integrator/cram.py new file mode 100644 index 000000000..a18d8450c --- /dev/null +++ b/openmc/deplete/integrator/cram.py @@ -0,0 +1,185 @@ +""" Chebyshev Rational Approximation Method module + +Implements two different forms of CRAM for use in opendeplete. +""" + +import numpy as np +import scipy.sparse as sp +import scipy.sparse.linalg as sla + + +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) form + [cram16]_. + + .. [cram16] + Pusa, Maria. "Higher-Order Chebyshev Rational Approximation Method and + Application to Burnup Equations." Nuclear Science and Engineering 182.3 + (2016). + + 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) form + [cram48]_. + + .. [cram48] + Pusa, Maria. "Higher-Order Chebyshev Rational Approximation Method and + Application to Burnup Equations." Nuclear Science and Engineering 182.3 + (2016). + + 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 diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py new file mode 100644 index 000000000..6c9d538fd --- /dev/null +++ b/openmc/deplete/integrator/predictor.py @@ -0,0 +1,100 @@ +""" The Predictor algorithm.""" + +import copy +from itertools import repeat +import os +from multiprocessing import Pool +import time + +from .. import comm +from .cram import CRAM48, cram_wrapper +from .save_results import save_results + + +def predictor(operator, print_out=True): + """The basic predictor integrator. + + 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 : Operator + The operator object to simulate on. + print_out : bool, optional + Whether or not to print out time. + """ + + # Save current directory + dir_home = os.getcwd() + + # Move to folder + os.makedirs(operator.settings.output_dir, exist_ok=True) + os.chdir(operator.settings.output_dir) + + # Generate initial conditions + vec = operator.initial_condition() + + n_mats = len(vec) + + t = 0.0 + + for i, dt in enumerate(operator.settings.dt_vec): + # Create vectors + x = [copy.deepcopy(vec)] + seeds = [] + eigvls = [] + rates_array = [] + + eigvl, rates, seed = operator.eval(x[0]) + + eigvls.append(eigvl) + seeds.append(seed) + rates_array.append(rates) + + # Create results, write to disk + save_results(operator, x, rates_array, eigvls, seeds, [t, t + dt], i) + + t_start = time.time() + + chains = repeat(operator.chain, n_mats) + vecs = (x[0][i] for i in range(n_mats)) + rates = (rates_array[0][i, :, :] for i in range(n_mats)) + dts = repeat(dt, n_mats) + + 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) + + t += dt + vec = copy.deepcopy(x_result) + + # Perform one last simulation + x = [copy.deepcopy(vec)] + seeds = [] + eigvls = [] + rates_array = [] + eigvl, rates, seed = operator.eval(x[0]) + + eigvls.append(eigvl) + seeds.append(seed) + rates_array.append(rates) + + # Create results, write to disk + save_results(operator, x, rates_array, eigvls, seeds, [t, t], + len(operator.settings.dt_vec)) + + # Return to origin + os.chdir(dir_home) diff --git a/openmc/deplete/integrator/save_results.py b/openmc/deplete/integrator/save_results.py new file mode 100644 index 000000000..35cbc7f3f --- /dev/null +++ b/openmc/deplete/integrator/save_results.py @@ -0,0 +1,46 @@ +""" Generic result saving code for integrators. + +""" +from opendeplete.results import Results, write_results + +def save_results(op, x, rates, eigvls, seeds, t, step_ind): + """ Creates and writes results to disk + + Parameters + ---------- + op : Function + 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. + rates : list of ReactionRates + The reaction rates for each substep. + eigvls : list of float + Eigenvalue for each substep + seeds : list of int + Seeds for each substep. + t : list of float + Time indices. + step_ind : int + Step index. + """ + + # Get indexing terms + vol_list, nuc_list, burn_list, full_burn_list = op.get_results_info() + + # Create results + stages = len(x) + results = Results() + results.allocate(vol_list, 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[i][mat_i][:] + + results.k = eigvls + results.seeds = seeds + results.time = t + results.rates = rates + + write_results(results, "results.h5", step_ind) diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py new file mode 100644 index 000000000..1208a9b3c --- /dev/null +++ b/openmc/deplete/nuclide.py @@ -0,0 +1,178 @@ +"""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') +ReactionTuple = namedtuple('ReactionTuple', 'type target Q branching_ratio') + + +class Nuclide(object): + """The Nuclide class. + + Contains everything in a depletion chain relating to a single nuclide. + + Attributes + ---------- + name : str + Name of nuclide. + half_life : float + Half life of nuclide in s^-1. + decay_energy : float + Energy deposited from decay in eV. + n_decay_modes : int + Number of decay pathways. + decay_modes : list of 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 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): + """Number of decay modes.""" + return len(self.decay_modes) + + @property + def n_reaction_paths(self): + """Number of possible reaction pathways.""" + return len(self.reactions) + + @classmethod + def xml_read(cls, element): + """Read nuclide from an XML element. + + Parameters + ---------- + element : xml.etree.ElementTree.Element + XML element to write nuclide data to + + Returns + ------- + nuc : 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_type'): + 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_type'): + 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 xml_write(self): + """Write nuclide to XML element. + + Returns + ------- + elem : xml.etree.ElementTree.Element + XML element to write nuclide data to + + """ + elem = ET.Element('nuclide_table') + 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_type') + 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_type') + 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 diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py new file mode 100644 index 000000000..347dc7185 --- /dev/null +++ b/openmc/deplete/openmc_wrapper.py @@ -0,0 +1,853 @@ +""" The OpenMC wrapper module. + +This module implements the OpenDeplete -> OpenMC linkage. +""" + +import copy +from collections import OrderedDict +import os +import random +import sys +import time +try: + import lxml.etree as ET + _have_lxml = True +except ImportError: + import xml.etree.ElementTree as ET + from openmc.clean_xml import clean_xml_indentation + _have_lxml = False + +import h5py +import numpy as np +import openmc +import openmc.capi + +from . import comm +from .atom_number import AtomNumber +from .depletion_chain import DepletionChain +from .reaction_rates import ReactionRates +from .function import Settings, Operator + + +_JOULE_PER_EV = 1.6021766208e-19 + + +def chunks(items, n): + min_size, extra = divmod(len(items), n) + j = 0 + chunk_list = [] + for i in range(n): + chunk_size = min_size + int(i < extra) + chunk_list.append(items[j:j + chunk_size]) + j += chunk_size + return chunk_list + + +class OpenMCSettings(Settings): + """The OpenMCSettings class. + + Extends Settings to provide information OpenMC needs to run. + + Attributes + ---------- + dt_vec : numpy.array + Array of time steps to take. (From Settings) + tol : float + Tolerance for adaptive time stepping. (From Settings) + output_dir : str + Path to output directory to save results. (From Settings) + chain_file : str + Path to the depletion chain xml file. Defaults to the environment + variable "OPENDEPLETE_CHAIN" if it exists. + openmc_call : str + OpenMC executable path. Defaults to "openmc". + particles : int + Number of particles to simulate per batch. + batches : int + Number of batches. + inactive : int + Number of inactive batches. + lower_left : list of float + Coordinate of lower left of bounding box of geometry. + upper_right : list of float + Coordinate of upper right of bounding box of geometry. + entropy_dimension : list of int + Grid size of entropy. + dilute_initial : float, default 1.0e3 + 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. + 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. + constant_seed : int + If present, all runs will be performed with this seed. + power : float + Power of the reactor in W. 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. + """ + + def __init__(self): + super().__init__() + # OpenMC specific + try: + self.chain_file = os.environ["OPENDEPLETE_CHAIN"] + except KeyError: + self.chain_file = None + self.openmc_call = "openmc" + self.particles = None + self.batches = None + self.inactive = None + self.lower_left = None + self.upper_right = None + self.entropy_dimension = None + self.dilute_initial = 1.0e3 + + # OpenMC testing specific + self.round_number = False + self.constant_seed = None + + # Depletion problem specific + self.power = None + + +class Materials(object): + """The Materials class. + + Contains information about cross sections for a cell. + + Attributes + ---------- + temperature : float + Temperature in Kelvin for each region. + sab : str or list of str + ENDF S(a,b) name for a region that needs S(a,b) data. Not set if no + S(a,b) needed for region. + """ + + def __init__(self): + self.temperature = None + self.sab = None + + +class OpenMCOperator(Operator): + """The OpenMC Operator class. + + Provides Operator functions for OpenMC. + + Parameters + ---------- + geometry : openmc.Geometry + The OpenMC geometry object. + settings : OpenMCSettings + Settings object. + + Attributes + ---------- + settings : OpenMCSettings + Settings object. (From Operator) + geometry : openmc.Geometry + The OpenMC geometry object. + materials : list of Materials + Materials to be used for this simulation. + seed : int + The RNG seed used in last OpenMC run. + number : AtomNumber + Total number of atoms in simulation. + participating_nuclides : set of str + A set listing all unique nuclides available from cross_sections.xml. + chain : DepletionChain + The depletion chain information necessary to form matrices and tallies. + reaction_rates : ReactionRates + Reaction rates from the last operator step. + power : OrderedDict of str to float + Material-by-Material power. Indexed by material ID. + mat_name : OrderedDict of str to int + The name of region each material is set to. Indexed by material ID. + burn_mat_to_id : OrderedDict of str to int + Dictionary mapping material ID (as a string) to an index in reaction_rates. + burn_nuc_to_id : OrderedDict of str to int + Dictionary mapping nuclide name (as a string) to an index in + reaction_rates. + n_nuc : int + Number of nuclides considered in the decay chain. + mat_tally_ind : OrderedDict of str to int + Dictionary mapping material ID to index in tally. + """ + + def __init__(self, geometry, settings): + super().__init__(settings) + + self.geometry = geometry + self.seed = 0 + self.number = None + self.participating_nuclides = None + self.reaction_rates = None + self.power = None + self.mat_name = OrderedDict() + self.burn_mat_to_ind = OrderedDict() + self.burn_nuc_to_ind = None + + # Read depletion chain + self.chain = DepletionChain.xml_read(settings.chain_file) + + # Clear out OpenMC, create task lists, distribute + if comm.rank == 0: + clean_up_openmc() + mat_burn_list, mat_not_burn_list, volume, self.mat_tally_ind, \ + nuc_dict = self.extract_mat_ids() + else: + # Dummy variables + mat_burn_list = None + mat_not_burn_list = None + volume = None + nuc_dict = None + self.mat_tally_ind = None + + mat_burn = comm.scatter(mat_burn_list) + mat_not_burn = comm.scatter(mat_not_burn_list) + nuc_dict = comm.bcast(nuc_dict) + volume = comm.bcast(volume) + self.mat_tally_ind = comm.bcast(self.mat_tally_ind) + + # Load participating nuclides + self.load_participating() + + # Extract number densities from the geometry + self.extract_number(mat_burn, mat_not_burn, volume, nuc_dict) + + # Create reaction rate tables + self.initialize_reaction_rates() + + def __del__(self): + openmc.capi.finalize() + + def extract_mat_ids(self): + """ Extracts materials and assigns them to processes. + + Returns + ------- + mat_burn_lists : list of list of int + List of burnable materials indexed by rank. + mat_not_burn_lists : list of list of int + List of non-burnable materials indexed by rank. + volume : OrderedDict of str to float + Volume of each cell + mat_tally_ind : OrderedDict of str to int + Dictionary mapping material ID to index in tally. + nuc_dict : OrderedDict of str to int + Nuclides in order of how they'll appear in the simulation. + """ + + mat_burn = set() + mat_not_burn = set() + nuc_set = set() + + volume = OrderedDict() + + # Iterate once through the geometry to get dictionaries + cells = self.geometry.get_all_material_cells() + for cell in cells.values(): + name = cell.name + + if isinstance(cell.fill, openmc.Material): + mat = cell.fill + for nuclide in mat.get_nuclide_densities(): + nuc_set.add(nuclide) + if mat.depletable: + mat_burn.add(str(mat.id)) + volume[str(mat.id)] = mat.volume + else: + mat_not_burn.add(str(mat.id)) + self.mat_name[mat.id] = name + else: + for mat in cell.fill: + for nuclide in mat.get_nuclide_densities(): + nuc_set.add(nuclide) + if mat.depletable: + mat_burn.add(str(mat.id)) + volume[str(mat.id)] = mat.volume + else: + mat_not_burn.add(str(mat.id)) + self.mat_name[mat.id] = name + + need_vol = [] + + for mat_id in volume: + if volume[mat_id] is None: + need_vol.append(mat_id) + + if need_vol: + exit("Need volumes for materials: " + str(need_vol)) + + # Sort the sets + mat_burn = sorted(mat_burn, key=int) + mat_not_burn = sorted(mat_not_burn, key=int) + nuc_set = sorted(nuc_set) + + # Construct a global nuclide dictionary, burned first + nuc_dict = copy.deepcopy(self.chain.nuclide_dict) + + i = len(nuc_dict) + + for nuc in nuc_set: + if nuc not in nuc_dict: + nuc_dict[nuc] = i + i += 1 + + # Decompose geometry + mat_burn_lists = chunks(mat_burn, comm.size) + mat_not_burn_lists = chunks(mat_not_burn, comm.size) + + mat_tally_ind = OrderedDict() + + for i, mat in enumerate(mat_burn): + mat_tally_ind[mat] = i + + return mat_burn_lists, mat_not_burn_lists, volume, mat_tally_ind, nuc_dict + + def extract_number(self, mat_burn, mat_not_burn, volume, nuc_dict): + """ Construct self.number read from geometry + + Parameters + ---------- + mat_burn : list of int + Materials to be burned managed by this thread. + mat_not_burn + Materials not to be burned managed by this thread. + volume : OrderedDict of str to float + Volumes for the above materials. + nuc_dict : OrderedDict of str to int + Nuclides to be used in the simulation. + """ + + # Same with materials + mat_dict = OrderedDict() + self.burn_mat_to_ind = OrderedDict() + i = 0 + for mat in mat_burn: + mat_dict[mat] = i + self.burn_mat_to_ind[mat] = i + i += 1 + + for mat in mat_not_burn: + mat_dict[mat] = i + i += 1 + + n_mat_burn = len(mat_burn) + n_nuc_burn = len(self.chain.nuclide_dict) + + self.number = AtomNumber(mat_dict, nuc_dict, volume, n_mat_burn, n_nuc_burn) + + if self.settings.dilute_initial != 0.0: + for nuc in self.burn_nuc_to_ind: + self.number.set_atom_density(np.s_[:], nuc, self.settings.dilute_initial) + + # Now extract the number densities and store + cells = self.geometry.get_all_material_cells() + for cell in cells.values(): + if isinstance(cell.fill, openmc.Material): + if str(cell.fill.id) in mat_dict: + self.set_number_from_mat(cell.fill) + else: + for mat in cell.fill: + if str(mat.id) in mat_dict: + self.set_number_from_mat(mat) + + def set_number_from_mat(self, mat): + """ Extracts material and number densities from openmc.Material + + Parameters + ---------- + mat : openmc.Materials + The material to read from + """ + + mat_id = str(mat.id) + mat_ind = self.number.mat_to_ind[mat_id] + + nuc_dens = mat.get_nuclide_atom_densities() + for nuclide in nuc_dens: + name = nuclide.name + number = nuc_dens[nuclide][1] * 1.0e24 + self.number.set_atom_density(mat_id, name, number) + + def initialize_reaction_rates(self): + """ Create reaction rates object. """ + self.reaction_rates = ReactionRates( + self.burn_mat_to_ind, + self.burn_nuc_to_ind, + self.chain.react_to_ind) + + self.chain.nuc_to_react_ind = self.burn_nuc_to_ind + + def eval(self, vec, print_out=True): + """ Runs a simulation. + + Parameters + ---------- + vec : list of numpy.array + Total atoms to be used in function. + print_out : bool, optional + Whether or not to print out time. + + Returns + ------- + mat : list of scipy.sparse.csr_matrix + Matrices for the next step. + k : float + Eigenvalue of the problem. + rates : ReactionRates + Reaction rates from this simulation. + seed : int + Seed for this simulation. + """ + + # Prevent OpenMC from complaining about re-creating tallies + clean_up_openmc() + + # Update status + self.set_density(vec) + + time_start = time.time() + + # Update material compositions and tally nuclides + self._update_materials() + openmc.capi.tallies[1].nuclides = self._get_tally_nuclides() + + # Run OpenMC + openmc.capi.reset() + openmc.capi.run() + + time_openmc = time.time() + + # Extract results + k = self.unpack_tallies_and_normalize() + + 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 k, copy.deepcopy(self.reaction_rates), self.seed + + def form_matrix(self, y, mat): + """ Forms the depletion matrix. + + Parameters + ---------- + y : numpy.ndarray + An array representing reaction rates for this cell. + mat : int + Material id. + + Returns + ------- + scipy.sparse.csr_matrix + Sparse matrix representing the depletion matrix. + """ + + return copy.deepcopy(self.chain.form_matrix(y[mat, :, :])) + + def initial_condition(self): + """ Performs final setup and returns initial condition. + + Returns + ------- + list of numpy.array + Total density for initial conditions. + """ + + # Create XML files + if comm.rank == 0: + self.geometry.export_to_xml() + self.generate_settings_xml() + self.generate_materials_xml() + + # Initialize OpenMC library + comm.barrier() + openmc.capi.init(comm) + + # Generate tallies in memory + self.generate_tallies() + + # Return number density vector + return self.total_density_list() + + 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.mat_to_ind: + nuclides = [] + densities = [] + for nuc in number_i.nuc_to_ind: + if nuc in self.participating_nuclides: + 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.settings.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 + + mat_internal = openmc.capi.materials[int(mat)] + mat_internal.set_densities(nuclides, densities) + + 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.nuc_to_ind.keys()) + for mat in materials: + mat._nuclides.sort(key=lambda x: nuclides.index(x[0])) + + materials.export_to_xml() + + def generate_settings_xml(self): + """ Generates settings.xml. + + This function creates settings.xml using the value of the settings + variable. + + Todo + ---- + Rewrite to generalize source box. + """ + + batches = self.settings.batches + inactive = self.settings.inactive + particles = self.settings.particles + + # Just a generic settings file to get it running. + settings_file = openmc.Settings() + settings_file.batches = batches + settings_file.inactive = inactive + settings_file.particles = particles + settings_file.source = openmc.Source(space=openmc.stats.Box( + self.settings.lower_left, self.settings.upper_right)) + + if self.settings.entropy_dimension is not None: + entropy_mesh = openmc.Mesh() + entropy_mesh.lower_left = self.settings.lower_left + entropy_mesh.upper_right = self.settings.upper_right + entropy_mesh.dimension = self.settings.entropy_dimension + settings_file.entropy_mesh = entropy_mesh + + # Set seed + if self.settings.constant_seed is not None: + seed = self.settings.constant_seed + else: + seed = random.randint(1, sys.maxsize-1) + + settings_file.seed = self.seed = seed + + settings_file.export_to_xml() + + def _get_tally_nuclides(self): + nuc_set = set() + + # Create the set of all nuclides in the decay chain in cells marked for + # burning in which the number density is greater than zero. + for nuc in self.number.nuc_to_ind: + if nuc in self.participating_nuclides: + 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.nuc_to_ind + if nuc in nuc_set] + else: + nuc_list = None + + # Store list of tally nuclides on each process + nuc_list = comm.bcast(nuc_list, root=0) + tally_nuclides = [nuc for nuc in nuc_list + if nuc in self.chain.nuclide_dict] + + return tally_nuclides + + def generate_tallies(self): + """Generates depletion tallies. + + Using information from self.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.mat_tally_ind] + mat_filter = openmc.capi.MaterialFilter(materials, 1) + + # 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. + tally_dep = openmc.capi.Tally(1) + tally_dep.scores = self.chain.react_to_ind.keys() + tally_dep.filters = [mat_filter] + + def total_density_list(self): + """ Returns a list of total density lists. + + This list is in the exact same order as depletion_matrix_list, so that + matrix exponentiation can be done easily. + + Returns + ------- + list of numpy.array + A list of np.arrays containing total atoms of each cell. + """ + + total_density = [self.number.get_mat_slice(i) for i in range(self.number.n_mat_burn)] + + return total_density + + 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.array + Total atoms. + """ + + # Fill in values + for i in range(self.number.n_mat_burn): + self.number.set_mat_slice(i, total_density[i]) + + def unpack_tallies_and_normalize(self): + """ Unpack tallies from OpenMC + + This function reads the tallies generated by OpenMC (from the tally.xml + file generated in generate_tally_xml) normalizes them so that the total + power generated is new_power, and then stores them in the reaction rate + database. + + Returns + ------- + k : float + Eigenvalue of the last simulation. + + Todo + ---- + Provide units for power + """ + + rates = self.reaction_rates + rates[:, :, :] = 0.0 + + k_combined = openmc.capi.keff()[0] + + # Extract tally bins + materials = list(self.mat_tally_ind.keys()) + nuclides = openmc.capi.tallies[1].nuclides + reactions = list(self.chain.react_to_ind.keys()) + + # Form fast map + nuc_ind = [rates.nuc_to_ind[nuc] for nuc in nuclides] + react_ind = [rates.react_to_ind[react] for react in 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.react_to_ind["fission"] + + for nuclide in self.chain.nuclides: + if nuclide.name in rates.nuc_to_ind: + for rx in nuclide.reactions: + if rx.type == 'fission': + ind = rates.nuc_to_ind[nuclide.name] + fission_Q[ind] = rx.Q + break + + # Extract results + for i, mat in enumerate(self.number.burn_mat_list): + # Get tally index + slab = materials.index(mat) + + # Get material results hyperslab + results = openmc.capi.tallies[1].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.rates[i, :, :] = rates_expanded + + # Reduce energy produced from all processes + energy = comm.allreduce(energy) + + # Determine power in eV/s + power = self.settings.power / _JOULE_PER_EV + + # Scale reaction rates to obtain units of reactions/sec + rates[:, :, :] *= power / energy + + return k_combined + + def load_participating(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 + + self.participating_nuclides = set() + + try: + tree = ET.parse(filename) + except: + 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() + self.burn_nuc_to_ind = OrderedDict() + nuc_ind = 0 + + 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 self.participating_nuclides: + self.participating_nuclides.add(name) + if name in self.chain.nuclide_dict: + self.burn_nuc_to_ind[name] = nuc_ind + nuc_ind += 1 + + @property + def n_nuc(self): + """Number of nuclides considered in the decay chain.""" + return len(self.chain.nuclides) + + def get_results_info(self): + """ Returns volume list, cell 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 cell IDs to be burned. Used for sorting the simulation. + full_burn_dict : OrderedDict of str to int + Maps cell name to index in global geometry. + """ + + nuc_list = self.number.burn_nuc_list + burn_list = self.number.burn_mat_list + + 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.mat_tally_ind + +def density_to_mat(dens_dict): + """ Generates an OpenMC material from a cell ID and self.number_density. + Parameters + ---------- + m_id : int + Cell ID. + Returns + ------- + openmc.Material + The OpenMC material filled with nuclides. + """ + + mat = openmc.Material() + for key in dens_dict: + mat.add_nuclide(key, 1.0e-24*dens_dict[key]) + mat.set_density('sum') + + return mat + +def clean_up_openmc(): + """ Resets all automatic indexing in OpenMC, as these get in the way. """ + openmc.reset_auto_ids() diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py new file mode 100644 index 000000000..7b934027a --- /dev/null +++ b/openmc/deplete/reaction_rates.py @@ -0,0 +1,113 @@ +"""ReactionRates module. + +An ndarray to store reaction rates with string, integer, or slice indexing. +""" + +import numpy as np + + +class ReactionRates(object): + """ ReactionRates class. + + An ndarray to store reaction rates with string, integer, or slice indexing. + + Parameters + ---------- + mat_to_ind : OrderedDict of str to int + A dictionary mapping material ID as string to index. + nuc_to_ind : OrderedDict of str to int + A dictionary mapping nuclide name as string to index. + react_to_ind : OrderedDict of str to int + A dictionary mapping reaction name as string to index. + + Attributes + ---------- + mat_to_ind : OrderedDict of str to int + A dictionary mapping cell ID as string to index. + nuc_to_ind : OrderedDict of str to int + A dictionary mapping nuclide name as string to index. + react_to_ind : 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. + rates : numpy.array + Array storing rates indexed by the above dictionaries. + """ + + def __init__(self, mat_to_ind, nuc_to_ind, react_to_ind): + + self.mat_to_ind = mat_to_ind + self.nuc_to_ind = nuc_to_ind + self.react_to_ind = react_to_ind + + self.rates = np.zeros((self.n_mat, self.n_nuc, self.n_react)) + + def __getitem__(self, pos): + """ Retrieves an item from reaction_rates. + + Parameters + ---------- + pos : tuple + A three-length tuple containing a material index, a nuc index, and a + reaction index. These indexes can be strings (which get converted + to integers via the dictionaries), integers used directly, or + slices. + + Returns + ------- + numpy.array + The value indexed from self.rates. + """ + + mat, nuc, react = pos + if isinstance(mat, str): + mat = self.mat_to_ind[mat] + if isinstance(nuc, str): + nuc = self.nuc_to_ind[nuc] + if isinstance(react, str): + react = self.react_to_ind[react] + + return self.rates[mat, nuc, react] + + def __setitem__(self, pos, val): + """ Sets an item from reaction_rates. + + Parameters + ---------- + pos : tuple + A three-length tuple containing a material index, a nuc index, and a + reaction index. These indexes can be strings (which get converted + to integers via the dictionaries), integers used directly, or + slices. + val : float + The value to set the array to. + """ + + mat, nuc, react = pos + if isinstance(mat, str): + mat = self.mat_to_ind[mat] + if isinstance(nuc, str): + nuc = self.nuc_to_ind[nuc] + if isinstance(react, str): + react = self.react_to_ind[react] + + self.rates[mat, nuc, react] = val + + @property + def n_mat(self): + """Number of cells.""" + return len(self.mat_to_ind) + + @property + def n_nuc(self): + """Number of nucs.""" + return len(self.nuc_to_ind) + + @property + def n_react(self): + """Number of reactions.""" + return len(self.react_to_ind) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py new file mode 100644 index 000000000..c0ec1627e --- /dev/null +++ b/openmc/deplete/results.py @@ -0,0 +1,454 @@ +""" The results module. + +Contains results generation and saving capabilities. +""" + +from collections import OrderedDict +import copy + +import numpy as np +import h5py + +from . import comm, have_mpi +from .reaction_rates import ReactionRates + +RESULTS_VERSION = 2 + +class Results(object): + """ Contains output of opendeplete. + + Attributes + ---------- + k : list of float + Eigenvalue for each substep. + seeds : list of int + Seeds for each substep. + time : list of float + Time at beginning, end of step, in seconds. + 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.array + Atom quantity, stored by stage, mat, then by nuclide. + """ + + def __init__(self): + self.k = None + self.seeds = None + self.time = None + self.p_terms = 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 allocate(self, volume, nuc_list, burn_list, full_burn_dict, 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_dict : dict of str to int + Map of material name to id in global geometry. + stages : int + Number of stages in simulation. + """ + + self.volume = copy.deepcopy(volume) + self.nuc_to_ind = OrderedDict() + self.mat_to_ind = OrderedDict() + self.mat_to_hdf5_ind = copy.deepcopy(full_burn_dict) + + for i, mat in enumerate(burn_list): + self.mat_to_ind[mat] = i + + for i, nuc in enumerate(nuc_list): + self.nuc_to_ind[nuc] = i + + # Create storage array + self.data = np.zeros((stages, self.n_mat, self.n_nuc)) + + @property + def n_mat(self): + """Number of mats.""" + return len(self.mat_to_ind) + + @property + def n_nuc(self): + """Number of nuclides.""" + return len(self.nuc_to_ind) + + @property + def n_hdf5_mats(self): + """Number of materials in entire geometry.""" + return len(self.mat_to_hdf5_ind) + + @property + def n_stages(self): + """Number of stages in simulation.""" + return self.data.shape[0] + + 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 + + def create_hdf5(self, handle): + """ Creates file structure for a blank 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.create_dataset("version", data=RESULTS_VERSION) + + mat_int = sorted([int(mat) for mat in self.mat_to_hdf5_ind]) + mat_list = [str(mat) for mat in mat_int] + nuc_list = sorted(self.nuc_to_ind.keys()) + rxn_list = sorted(self.rates[0].react_to_ind.keys()) + + n_mats = self.n_hdf5_mats + n_nuc_number = len(nuc_list) + n_nuc_rxn = len(self.rates[0].nuc_to_ind) + n_rxn = len(rxn_list) + n_stages = self.n_stages + + mat_group = handle.create_group("cells") + + 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].nuc_to_ind: + nuc_single_group.attrs["reaction rate index"] = self.rates[0].nuc_to_ind[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].react_to_ind[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("seeds", (1, n_stages), maxshape=(None, n_stages), dtype='int64') + + handle.create_dataset("time", (1, 2), maxshape=(None, 2), 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.create_hdf5(handle) + + comm.barrier() + + # Grab handles + number_dset = handle["/number"] + rxn_dset = handle["/reaction rates"] + eigenvalues_dset = handle["/eigenvalues"] + seeds_dset = handle["/seeds"] + time_dset = handle["/time"] + + # 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) + + seeds_shape = list(seeds_dset.shape) + seeds_shape[0] = new_shape + seeds_dset.resize(seeds_shape) + + time_shape = list(time_dset.shape) + time_shape[0] = new_shape + time_dset.resize(time_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] + seeds_dset[index, i] = self.seeds[i] + if comm.rank == 0: + time_dset[index, :] = self.time + + def from_hdf5(self, handle, index): + """ Loads results object from HDF5. + + Parameters + ---------- + handle : h5py.File or h5py.Group + An hdf5 file or group type to load from. + index : int + What step is this? + """ + + # Grab handles + number_dset = handle["/number"] + eigenvalues_dset = handle["/eigenvalues"] + seeds_dset = handle["/seeds"] + time_dset = handle["/time"] + + self.data = number_dset[index, :, :, :] + self.k = eigenvalues_dset[index, :] + self.seeds = seeds_dset[index, :] + self.time = time_dset[index, :] + + # Reconstruct dictionaries + self.volume = OrderedDict() + self.mat_to_ind = OrderedDict() + self.nuc_to_ind = OrderedDict() + rxn_nuc_to_ind = OrderedDict() + rxn_to_ind = OrderedDict() + + for mat in handle["/cells"]: + mat_handle = handle["/cells/" + mat] + vol = mat_handle.attrs["volume"] + ind = mat_handle.attrs["index"] + + self.volume[mat] = vol + self.mat_to_ind[mat] = ind + + for nuc in handle["/nuclides"]: + nuc_handle = handle["/nuclides/" + nuc] + ind_atom = nuc_handle.attrs["atom number index"] + self.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 in handle["/reactions"]: + rxn_handle = handle["/reactions/" + rxn] + rxn_to_ind[rxn] = rxn_handle.attrs["index"] + + self.rates = [] + # Reconstruct reactions + for i in range(self.n_stages): + rate = ReactionRates(self.mat_to_ind, rxn_nuc_to_ind, rxn_to_ind) + + rate.rates = handle["/reaction rates"][index, i, :, :, :] + self.rates.append(rate) + + +def get_dict(number): + """ Given an operator nested dictionary, output indexing dictionaries. + + These indexing dictionaries map mat IDs and nuclide names to indices + inside of Results.data. + + Parameters + ---------- + number : AtomNumber + The object to extract dictionaries from + + Returns + ------- + mat_to_ind : OrderedDict of str to int + Maps mat strings to index in array. + nuc_to_ind : OrderedDict of str to int + Maps nuclide strings to index in array. + """ + mat_to_ind = OrderedDict() + nuc_to_ind = OrderedDict() + + for nuc in number.nuc_to_ind: + nuc_ind = number.nuc_to_ind[nuc] + if nuc_ind < number.n_nuc_burn: + nuc_to_ind[nuc] = nuc_ind + + for mat in number.mat_to_ind: + mat_ind = number.mat_to_ind[mat] + if mat_ind < number.n_mat_burn: + mat_to_ind[mat] = mat_ind + + return mat_to_ind, nuc_to_ind + + +def write_results(result, filename, index): + """ Outputs result to an .hdf5 file. + + Parameters + ---------- + result : Results + Object to be stored in a file. + filename : String + Target filename. + index : int + What step is this? + """ + + if have_mpi and h5py.get_config().mpi: + kwargs = {'driver': 'mpio', 'comm': comm} + else: + kwargs = {} + + kwargs['mode'] = "w" if index == 0 else "a" + + with h5py.File(filename, **kwargs) as handle: + result.to_hdf5(handle, index) + + +def read_results(filename): + """ Reads out a list of results objects from an hdf5 file. + + Parameters + ---------- + filename : str + The filename to read from. + + Returns + ------- + results : list of Results + The result objects. + """ + + file = h5py.File(filename, "r") + + assert file["/version"].value == RESULTS_VERSION + + # Grab handles + number_dset = file["/number"] + + # Get number of results stored + number_shape = list(number_dset.shape) + number_results = number_shape[0] + + results = [] + + for i in range(number_results): + result = Results() + result.from_hdf5(file, i) + results.append(result) + + file.close() + + return results diff --git a/openmc/deplete/utilities.py b/openmc/deplete/utilities.py new file mode 100644 index 000000000..54632ed9c --- /dev/null +++ b/openmc/deplete/utilities.py @@ -0,0 +1,98 @@ +""" The utilities module. + +Contains functions that can be used to post-process objects that come out of +the results module. +""" + +import numpy as np + +def evaluate_single_nuclide(results, cell, nuc): + """ Evaluates a single nuclide in a single cell from a results list. + + Parameters + ---------- + results : list of results + The results to extract data from. Must be sorted and continuous. + cell : str + Cell name to evaluate + nuc : str + Nuclide name to evaluate + + Returns + ------- + time : numpy.array + Time vector. + concentration : numpy.array + Total number of atoms in the cell. + """ + + n_points = len(results) + time = np.zeros(n_points) + concentration = np.zeros(n_points) + + # Evaluate value in each region + for i, result in enumerate(results): + time[i] = result.time[0] + concentration[i] = result[0, cell, nuc] + + return time, concentration + +def evaluate_reaction_rate(results, cell, nuc, rxn): + """ Evaluates a single nuclide reaction rate in a single cell from a results list. + + Parameters + ---------- + results : list of Results + The results to extract data from. Must be sorted and continuous. + cell : str + Cell name to evaluate + nuc : str + Nuclide name to evaluate + rxn : str + Reaction rate to evaluate + + Returns + ------- + time : numpy.array + Time vector. + rate : numpy.array + Reaction rate. + """ + + n_points = len(results) + time = np.zeros(n_points) + rate = np.zeros(n_points) + # Evaluate value in each region + for i, result in enumerate(results): + time[i] = result.time[0] + rate[i] = result.rates[0][cell, nuc, rxn] * result[0, cell, nuc] + + return time, rate + +def evaluate_eigenvalue(results): + """ Evaluates the eigenvalue from a results list. + + Parameters + ---------- + results : list of Results + The results to extract data from. Must be sorted and continuous. + + Returns + ------- + time : numpy.array + Time vector. + eigenvalue : numpy.array + Eigenvalue. + """ + + n_points = len(results) + time = np.zeros(n_points) + eigenvalue = np.zeros(n_points) + + # Evaluate value in each region + for i, result in enumerate(results): + + time[i] = result.time[0] + eigenvalue[i] = result.k[0] + + return time, eigenvalue diff --git a/scripts/example_geometry.py b/scripts/example_geometry.py new file mode 100644 index 000000000..9afcc0d46 --- /dev/null +++ b/scripts/example_geometry.py @@ -0,0 +1,358 @@ +"""An example file showing how to make a geometry. + +This particular example creates a 3x3 geometry, with 8 regular pins and one +Gd-157 2 wt-percent enriched. All pins are segmented. +""" + +from collections import OrderedDict +import math + +import numpy as np +import openmc + +from opendeplete import density_to_mat + + +def generate_initial_number_density(): + """ Generates initial number density. + + These results were from a CASMO5 run in which the gadolinium pin was + loaded with 2 wt percent of Gd-157. + """ + + # Concentration to be used for all fuel pins + fuel_dict = OrderedDict() + fuel_dict['U235'] = 1.05692e21 + fuel_dict['U234'] = 1.00506e19 + fuel_dict['U238'] = 2.21371e22 + fuel_dict['O16'] = 4.62954e22 + fuel_dict['O17'] = 1.127684e20 + fuel_dict['I135'] = 1.0e10 + fuel_dict['Xe135'] = 1.0e10 + fuel_dict['Xe136'] = 1.0e10 + fuel_dict['Cs135'] = 1.0e10 + fuel_dict['Gd156'] = 1.0e10 + fuel_dict['Gd157'] = 1.0e10 + # fuel_dict['O18'] = 9.51352e19 # Does not exist in ENDF71, merged into 17 + + # Concentration to be used for the gadolinium fuel pin + fuel_gd_dict = OrderedDict() + fuel_gd_dict['U235'] = 1.03579e21 + fuel_gd_dict['U238'] = 2.16943e22 + fuel_gd_dict['Gd156'] = 3.95517E+10 + fuel_gd_dict['Gd157'] = 1.08156e20 + fuel_gd_dict['O16'] = 4.64035e22 + fuel_dict['I135'] = 1.0e10 + fuel_dict['Xe136'] = 1.0e10 + fuel_dict['Xe135'] = 1.0e10 + fuel_dict['Cs135'] = 1.0e10 + # There are a whole bunch of 1e-10 stuff here. + + # Concentration to be used for cladding + clad_dict = OrderedDict() + clad_dict['O16'] = 3.07427e20 + clad_dict['O17'] = 7.48868e17 + clad_dict['Cr50'] = 3.29620e18 + clad_dict['Cr52'] = 6.35639e19 + clad_dict['Cr53'] = 7.20763e18 + clad_dict['Cr54'] = 1.79413e18 + clad_dict['Fe54'] = 5.57350e18 + clad_dict['Fe56'] = 8.74921e19 + clad_dict['Fe57'] = 2.02057e18 + clad_dict['Fe58'] = 2.68901e17 + clad_dict['Cr50'] = 3.29620e18 + clad_dict['Cr52'] = 6.35639e19 + clad_dict['Cr53'] = 7.20763e18 + clad_dict['Cr54'] = 1.79413e18 + clad_dict['Ni58'] = 2.51631e19 + clad_dict['Ni60'] = 9.69278e18 + clad_dict['Ni61'] = 4.21338e17 + clad_dict['Ni62'] = 1.34341e18 + clad_dict['Ni64'] = 3.43127e17 + clad_dict['Zr90'] = 2.18320e22 + clad_dict['Zr91'] = 4.76104e21 + clad_dict['Zr92'] = 7.27734e21 + clad_dict['Zr94'] = 7.37494e21 + clad_dict['Zr96'] = 1.18814e21 + clad_dict['Sn112'] = 4.67352e18 + clad_dict['Sn114'] = 3.17992e18 + clad_dict['Sn115'] = 1.63814e18 + clad_dict['Sn116'] = 7.00546e19 + clad_dict['Sn117'] = 3.70027e19 + clad_dict['Sn118'] = 1.16694e20 + clad_dict['Sn119'] = 4.13872e19 + clad_dict['Sn120'] = 1.56973e20 + clad_dict['Sn122'] = 2.23076e19 + clad_dict['Sn124'] = 2.78966e19 + + # Gap concentration + # Funny enough, the example problem uses air. + gap_dict = OrderedDict() + gap_dict['O16'] = 7.86548e18 + gap_dict['O17'] = 2.99548e15 + gap_dict['N14'] = 3.38646e19 + gap_dict['N15'] = 1.23717e17 + + # Concentration to be used for coolant + # No boron + cool_dict = OrderedDict() + cool_dict['H1'] = 4.68063e22 + cool_dict['O16'] = 2.33427e22 + cool_dict['O17'] = 8.89086e18 + + # Store these dictionaries in the initial conditions dictionary + initial_density = OrderedDict() + initial_density['fuel_gd'] = fuel_gd_dict + initial_density['fuel'] = fuel_dict + initial_density['gap'] = gap_dict + initial_density['clad'] = clad_dict + initial_density['cool'] = cool_dict + + # Set up libraries to use + temperature = OrderedDict() + sab = OrderedDict() + + # Toggle betweeen MCNP and NNDC data + MCNP = False + + if MCNP: + temperature['fuel_gd'] = 900.0 + temperature['fuel'] = 900.0 + # We approximate temperature of everything as 600K, even though it was + # actually 580K. + temperature['gap'] = 600.0 + temperature['clad'] = 600.0 + temperature['cool'] = 600.0 + else: + temperature['fuel_gd'] = 293.6 + temperature['fuel'] = 293.6 + temperature['gap'] = 293.6 + temperature['clad'] = 293.6 + temperature['cool'] = 293.6 + + sab['cool'] = 'c_H_in_H2O' + + # Set up burnable materials + burn = OrderedDict() + burn['fuel_gd'] = True + burn['fuel'] = True + burn['gap'] = False + burn['clad'] = False + burn['cool'] = False + + return temperature, sab, initial_density, burn + +def segment_pin(n_rings, n_wedges, r_fuel, r_gap, r_clad): + """ Calculates a segmented pin. + + Separates a pin with n_rings and n_wedges. All cells have equal volume. + Pin is centered at origin. + """ + + # Calculate all the volumes of interest + v_fuel = math.pi * r_fuel**2 + v_gap = math.pi * r_gap**2 - v_fuel + v_clad = math.pi * r_clad**2 - v_fuel - v_gap + v_ring = v_fuel / n_rings + v_segment = v_ring / n_wedges + + # Compute ring radiuses + r_rings = np.zeros(n_rings) + + for i in range(n_rings): + r_rings[i] = math.sqrt(1.0/(math.pi) * v_ring * (i+1)) + + # Compute thetas + theta = np.linspace(0, 2*math.pi, n_wedges + 1) + + # Compute surfaces + fuel_rings = [openmc.ZCylinder(x0=0, y0=0, R=r_rings[i]) + for i in range(n_rings)] + + fuel_wedges = [openmc.Plane(A=math.cos(theta[i]), B=math.sin(theta[i])) + for i in range(n_wedges)] + + gap_ring = openmc.ZCylinder(x0=0, y0=0, R=r_gap) + clad_ring = openmc.ZCylinder(x0=0, y0=0, R=r_clad) + + # Create cells + fuel_cells = [] + if n_wedges == 1: + for i in range(n_rings): + cell = openmc.Cell(name='fuel') + if i == 0: + cell.region = -fuel_rings[0] + else: + cell.region = +fuel_rings[i-1] & -fuel_rings[i] + fuel_cells.append(cell) + else: + for i in range(n_rings): + for j in range(n_wedges): + cell = openmc.Cell(name='fuel') + if i == 0: + if j != n_wedges-1: + cell.region = (-fuel_rings[0] + & +fuel_wedges[j] + & -fuel_wedges[j+1]) + else: + cell.region = (-fuel_rings[0] + & +fuel_wedges[j] + & -fuel_wedges[0]) + else: + if j != n_wedges-1: + cell.region = (+fuel_rings[i-1] + & -fuel_rings[i] + & +fuel_wedges[j] + & -fuel_wedges[j+1]) + else: + cell.region = (+fuel_rings[i-1] + & -fuel_rings[i] + & +fuel_wedges[j] + & -fuel_wedges[0]) + fuel_cells.append(cell) + + # Gap ring + gap_cell = openmc.Cell(name='gap') + gap_cell.region = +fuel_rings[-1] & -gap_ring + fuel_cells.append(gap_cell) + + # Clad ring + clad_cell = openmc.Cell(name='clad') + clad_cell.region = +gap_ring & -clad_ring + fuel_cells.append(clad_cell) + + # Moderator + mod_cell = openmc.Cell(name='cool') + mod_cell.region = +clad_ring + fuel_cells.append(mod_cell) + + # Form universe + fuel_u = openmc.Universe() + fuel_u.add_cells(fuel_cells) + + return fuel_u, v_segment, v_gap, v_clad + +def generate_geometry(n_rings, n_wedges): + """ Generates example geometry. + + This function creates the initial geometry, a 9 pin reflective problem. + One pin, containing gadolinium, is discretized into sectors. + + In addition to what one would do with the general OpenMC geometry code, it + is necessary to create a dictionary, volume, that maps a cell ID to a + volume. Further, by naming cells the same as the above materials, the code + can automatically handle the mapping. + + Parameters + ---------- + n_rings : int + Number of rings to generate for the geometry + n_wedges : int + Number of wedges to generate for the geometry + """ + + pitch = 1.26197 + r_fuel = 0.412275 + r_gap = 0.418987 + r_clad = 0.476121 + + n_pin = 3 + + # This table describes the 'fuel' to actual type mapping + # It's not necessary to do it this way. Just adjust the initial conditions + # below. + mapping = ['fuel', 'fuel', 'fuel', + 'fuel', 'fuel_gd', 'fuel', + 'fuel', 'fuel', 'fuel'] + + # Form pin cell + fuel_u, v_segment, v_gap, v_clad = segment_pin(n_rings, n_wedges, r_fuel, r_gap, r_clad) + + # Form lattice + all_water_c = openmc.Cell(name='cool') + all_water_u = openmc.Universe(cells=(all_water_c, )) + + lattice = openmc.RectLattice() + lattice.pitch = [pitch]*2 + lattice.lower_left = [-pitch*n_pin/2, -pitch*n_pin/2] + lattice_array = [[fuel_u for i in range(n_pin)] for j in range(n_pin)] + lattice.universes = lattice_array + lattice.outer = all_water_u + + # Bound universe + x_low = openmc.XPlane(x0=-pitch*n_pin/2, boundary_type='reflective') + x_high = openmc.XPlane(x0=pitch*n_pin/2, boundary_type='reflective') + y_low = openmc.YPlane(y0=-pitch*n_pin/2, boundary_type='reflective') + y_high = openmc.YPlane(y0=pitch*n_pin/2, boundary_type='reflective') + z_low = openmc.ZPlane(z0=-10, boundary_type='reflective') + z_high = openmc.ZPlane(z0=10, boundary_type='reflective') + + # Compute bounding box + lower_left = [-pitch*n_pin/2, -pitch*n_pin/2, -10] + upper_right = [pitch*n_pin/2, pitch*n_pin/2, 10] + + root_c = openmc.Cell(fill=lattice) + root_c.region = (+x_low & -x_high + & +y_low & -y_high + & +z_low & -z_high) + root_u = openmc.Universe(universe_id=0, cells=(root_c, )) + geometry = openmc.Geometry(root_u) + + v_cool = pitch**2 - (v_gap + v_clad + n_rings * n_wedges * v_segment) + + # Store volumes for later usage + volume = {'fuel': v_segment, 'gap':v_gap, 'clad':v_clad, 'cool':v_cool} + + return geometry, volume, mapping, lower_left, upper_right + +def generate_problem(n_rings=5, n_wedges=8): + """ Merges geometry and materials. + + This function initializes the materials for each cell using the dictionaries + provided by generate_initial_number_density. It is assumed a cell named + 'fuel' will have further region differentiation (see mapping). + + Parameters + ---------- + n_rings : int, optional + Number of rings to generate for the geometry + n_wedges : int, optional + Number of wedges to generate for the geometry + """ + + # Get materials dictionary, geometry, and volumes + temperature, sab, initial_density, burn = generate_initial_number_density() + geometry, volume, mapping, lower_left, upper_right = generate_geometry(n_rings, n_wedges) + + # Apply distribmats, fill geometry + cells = geometry.root_universe.get_all_cells() + for cell_id in cells: + cell = cells[cell_id] + if cell.name == 'fuel': + + omc_mats = [] + + for cell_type in mapping: + omc_mat = density_to_mat(initial_density[cell_type]) + + if cell_type in sab: + omc_mat.add_s_alpha_beta(sab[cell_type]) + omc_mat.temperature = temperature[cell_type] + omc_mat.depletable = burn[cell_type] + omc_mat.volume = volume['fuel'] + + omc_mats.append(omc_mat) + + cell.fill = omc_mats + elif cell.name != '': + omc_mat = density_to_mat(initial_density[cell.name]) + + if cell.name in sab: + omc_mat.add_s_alpha_beta(sab[cell.name]) + omc_mat.temperature = temperature[cell.name] + omc_mat.depletable = burn[cell.name] + omc_mat.volume = volume[cell.name] + + cell.fill = omc_mat + + return geometry, lower_left, upper_right diff --git a/scripts/example_plot.py b/scripts/example_plot.py new file mode 100644 index 000000000..d2c6ee9d6 --- /dev/null +++ b/scripts/example_plot.py @@ -0,0 +1,46 @@ +"""An example file showing how to plot data from a simulation.""" + +import matplotlib.pyplot as plt + +from opendeplete import read_results, \ + evaluate_single_nuclide, \ + evaluate_reaction_rate, \ + evaluate_eigenvalue + +# Set variables for where the data is, and what we want to read out. +result_folder = "test" + +# Load data +results = read_results(result_folder + "/results.h5") + +cell = "5" +nuc = "Gd157" +rxn = "(n,gamma)" + +# Total number of nuclides +plt.figure() +# Pointwise data +x, y = evaluate_single_nuclide(results, cell, nuc) +plt.semilogy(x, y) + +plt.xlabel("Time, s") +plt.ylabel("Total Number") +plt.savefig("number.pdf") + +# Reaction rate +plt.figure() +x, y = evaluate_reaction_rate(results, cell, nuc, rxn) +plt.plot(x, y) +plt.xlabel("Time, s") +plt.ylabel("Reaction Rate, 1/s") + +plt.savefig("rate.pdf") + +# Eigenvalue +plt.figure() +x, y = evaluate_eigenvalue(results) +plt.plot(x, y) +plt.xlabel("Time, s") +plt.ylabel("Eigenvalue") + +plt.savefig("eigvl.pdf") diff --git a/scripts/example_run.py b/scripts/example_run.py new file mode 100644 index 000000000..bb80f6582 --- /dev/null +++ b/scripts/example_run.py @@ -0,0 +1,39 @@ +"""An example file showing how to run a simulation.""" + +import numpy as np +import opendeplete + +import example_geometry + +# Load geometry from example +geometry, lower_left, upper_right = example_geometry.generate_problem() + +# Create dt vector for 5.5 months with 15 day timesteps +dt1 = 15*24*60*60 # 15 days +dt2 = 5.5*30*24*60*60 # 5.5 months +N = np.floor(dt2/dt1) + +dt = np.repeat([dt1], N) + +# Create settings variable +settings = opendeplete.OpenMCSettings() + +settings.openmc_call = "openmc" +# An example for mpiexec: +# settings.openmc_call = ["mpiexec", "openmc"] +settings.particles = 1000 +settings.batches = 100 +settings.inactive = 40 +settings.lower_left = lower_left +settings.upper_right = upper_right +settings.entropy_dimension = [10, 10, 1] + +joule_per_mev = 1.6021766208e-13 +settings.power = 2.337e15*4*joule_per_mev # MeV/second cm from CASMO +settings.dt_vec = dt +settings.output_dir = 'test' + +op = opendeplete.OpenMCOperator(geometry, settings) + +# Perform simulation using the MCNPX/MCNP6 algorithm +opendeplete.integrator.cecm(op) diff --git a/scripts/make_chain.py b/scripts/make_chain.py new file mode 100644 index 000000000..2e0d9d3bc --- /dev/null +++ b/scripts/make_chain.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +import glob +import os +from zipfile import ZipFile + +import requests +from tqdm import tqdm +import opendeplete + + +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 download_file(url): + response = requests.get(url, stream=True) + filesize = int(response.headers.get('content-length')) + + # Check if file already downloaded + basename = url.split('/')[-1] + if os.path.exists(basename): + if os.path.getsize(basename) == filesize: + return basename + else: + overwrite = input('Overwrite {}? ([y]/n) '.format(basename)) + if overwrite.lower().startswith('n'): + return basename + + with open(basename, 'wb') as f: + with tqdm(desc='Downloading {}'.format(basename), + total=filesize, unit='B', unit_scale=True) as pbar: + for i, chunk in enumerate(response.iter_content(chunk_size=4096)): + pbar.update(4096) + if chunk: + f.write(chunk) + + return basename + + +def main(): + for url in urls: + basename = download_file(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 = opendeplete.DepletionChain.from_endf(decay_files, nfy_files, neutron_files) + chain.xml_write('chain_endfb71.xml') + + +if __name__ == '__main__': + main() diff --git a/tests/deplete_tests/__init__.py b/tests/deplete_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/deplete_tests/dummy_geometry.py b/tests/deplete_tests/dummy_geometry.py new file mode 100644 index 000000000..610151941 --- /dev/null +++ b/tests/deplete_tests/dummy_geometry.py @@ -0,0 +1,165 @@ +""" The OpenMC wrapper module. + +This module implements the OpenDeplete -> OpenMC linkage. +""" + +import numpy as np +import scipy.sparse as sp + +from opendeplete.reaction_rates import ReactionRates +from opendeplete.function import Operator + +class DummyGeometry(Operator): + """ This is a dummy geometry class with no statistical uncertainty. + + y_1' = sin(y_2) y_1 + cos(y_1) y_2 + y_2' = -cos(y_2) y_1 + sin(y_1) y_2 + + y_1(0) = 1 + y_2(0) = 1 + + y_1(1.5) ~ 2.3197067076743316 + y_2(1.5) ~ 3.1726475740397628 + + """ + + def __init__(self, settings): + Operator.__init__(self, settings) + + @property + def chain(self): + return self + + def eval(self, vec, print_out=False): + """ Evaluates F(y) + + Parameters + ---------- + vec : list of numpy.array + Total atoms to be used in function. + print_out : bool, optional, ignored + Whether or not to print out time. + + Returns + ------- + k : float + Zero. + rates : ReactionRates + Reaction rates from this simulation. + seed : int + Zero. + """ + + cell_to_ind = {"1" : 0} + nuc_to_ind = {"1" : 0, "2" : 1} + react_to_ind = {"1" : 0} + + reaction_rates = ReactionRates(cell_to_ind, nuc_to_ind, react_to_ind) + + reaction_rates[0, 0, 0] = vec[0][0] + reaction_rates[0, 1, 0] = vec[0][1] + + # Create a fake rates object + + return 0.0, reaction_rates, 0 + + def form_matrix(self, rates): + """ Forms the f(y) matrix in y' = f(y)y. + + Nominally a depletion matrix, this is abstracted on the off chance + that the function f has nothing to do with depletion at all. + + Parameters + ---------- + rates : numpy.ndarray + Slice of reaction rates for a single material + + Returns + ------- + scipy.sparse.csr_matrix + Sparse matrix representing f(y). + """ + + y_1 = rates[0, 0] + y_2 = rates[1, 0] + + mat = np.zeros((2, 2)) + a11 = np.sin(y_2) + a12 = np.cos(y_1) + a21 = -np.cos(y_2) + a22 = np.sin(y_1) + + return sp.csr_matrix(np.array([[a11, a12], [a21, a22]])) + + @property + def volume(self): + """ + volume : dict of str float + Volumes of material + """ + + return {"1": 0.0} + + @property + def nuc_list(self): + """ + nuc_list : list of str + A list of all nuclide names. Used for sorting the simulation. + """ + + return ["1", "2"] + + @property + def burn_list(self): + """ + burn_list : list of str + A list of all cell IDs to be burned. Used for sorting the simulation. + """ + + return ["1"] + + @property + def mat_tally_ind(self): + """Maps cell name to index in global geometry.""" + return {"1": 0} + + + @property + def reaction_rates(self): + """ + reaction_rates : ReactionRates + Reaction rates from the last operator step. + """ + cell_to_ind = {"1" : 0} + nuc_to_ind = {"1" : 0, "2" : 1} + react_to_ind = {"1" : 0} + + return ReactionRates(cell_to_ind, nuc_to_ind, react_to_ind) + + def initial_condition(self): + """ Returns initial vector. + + Returns + ------- + list of numpy.array + Total density for initial conditions. + """ + + return [np.array((1.0, 1.0))] + + def get_results_info(self): + """ Returns volume list, cell 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 cell IDs to be burned. Used for sorting the simulation. + full_burn_dict : OrderedDict of str to int + Maps cell name to index in global geometry. + """ + + return self.volume, self.nuc_list, self.burn_list, self.mat_tally_ind diff --git a/tests/deplete_tests/example_geometry.py b/tests/deplete_tests/example_geometry.py new file mode 120000 index 000000000..1071aabc0 --- /dev/null +++ b/tests/deplete_tests/example_geometry.py @@ -0,0 +1 @@ +../../scripts/example_geometry.py \ No newline at end of file diff --git a/tests/deplete_tests/test_atom_number.py b/tests/deplete_tests/test_atom_number.py new file mode 100644 index 000000000..9a17230f8 --- /dev/null +++ b/tests/deplete_tests/test_atom_number.py @@ -0,0 +1,180 @@ +""" Tests for atom_number.py. """ + +import unittest + +import numpy as np + +from opendeplete import atom_number + +class TestAtomNumber(unittest.TestCase): + """ Tests for the AtomNumber class. """ + + def test_indexing(self): + """Tests the __getitem__ and __setitem__ routines simultaneously.""" + + mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} + nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + number["10000", "U238"] = 1.0 + number["10001", "U238"] = 2.0 + number["10000", "U235"] = 3.0 + number["10001", "U235"] = 4.0 + + # String indexing + self.assertEqual(number["10000", "U238"], 1.0) + self.assertEqual(number["10001", "U238"], 2.0) + self.assertEqual(number["10000", "U235"], 3.0) + self.assertEqual(number["10001", "U235"], 4.0) + + # Int indexing + self.assertEqual(number[0, 0], 1.0) + self.assertEqual(number[1, 0], 2.0) + self.assertEqual(number[0, 1], 3.0) + self.assertEqual(number[1, 1], 4.0) + + number[0, 0] = 5.0 + + self.assertEqual(number[0, 0], 5.0) + self.assertEqual(number["10000", "U238"], 5.0) + + def test_n_mat(self): + """ Test number of materials property. """ + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + self.assertEqual(number.n_mat, 2) + + def test_n_nuc(self): + """ Test number of nuclides property. """ + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + self.assertEqual(number.n_nuc, 3) + + def test_burn_nuc_list(self): + """ Test the list of burned nuclides property """ + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + self.assertEqual(number.burn_nuc_list, ["U238", "U235"]) + + def test_burn_mat_list(self): + """ Test the list of burned nuclides property """ + mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + self.assertEqual(number.burn_mat_list, ["10000", "10001"]) + + def test_density_indexing(self): + """Tests the get and set_atom_density routines simultaneously.""" + + mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} + nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + number.set_atom_density("10000", "U238", 1.0) + number.set_atom_density("10001", "U238", 2.0) + number.set_atom_density("10002", "U238", 3.0) + number.set_atom_density("10000", "U235", 4.0) + number.set_atom_density("10001", "U235", 5.0) + number.set_atom_density("10002", "U235", 6.0) + number.set_atom_density("10000", "U234", 7.0) + number.set_atom_density("10001", "U234", 8.0) + number.set_atom_density("10002", "U234", 9.0) + + # String indexing + self.assertEqual(number.get_atom_density("10000", "U238"), 1.0) + self.assertEqual(number.get_atom_density("10001", "U238"), 2.0) + self.assertEqual(number.get_atom_density("10002", "U238"), 3.0) + self.assertEqual(number.get_atom_density("10000", "U235"), 4.0) + self.assertEqual(number.get_atom_density("10001", "U235"), 5.0) + self.assertEqual(number.get_atom_density("10002", "U235"), 6.0) + self.assertEqual(number.get_atom_density("10000", "U234"), 7.0) + self.assertEqual(number.get_atom_density("10001", "U234"), 8.0) + self.assertEqual(number.get_atom_density("10002", "U234"), 9.0) + + # Int indexing + self.assertEqual(number.get_atom_density(0, 0), 1.0) + self.assertEqual(number.get_atom_density(1, 0), 2.0) + self.assertEqual(number.get_atom_density(2, 0), 3.0) + self.assertEqual(number.get_atom_density(0, 1), 4.0) + self.assertEqual(number.get_atom_density(1, 1), 5.0) + self.assertEqual(number.get_atom_density(2, 1), 6.0) + self.assertEqual(number.get_atom_density(0, 2), 7.0) + self.assertEqual(number.get_atom_density(1, 2), 8.0) + self.assertEqual(number.get_atom_density(2, 2), 9.0) + + + number.set_atom_density(0, 0, 5.0) + + self.assertEqual(number.get_atom_density(0, 0), 5.0) + + # Verify volume is used correctly + self.assertEqual(number[0, 0], 5.0 * 0.38) + self.assertEqual(number[1, 0], 2.0 * 0.21) + self.assertEqual(number[2, 0], 3.0 * 1.0) + self.assertEqual(number[0, 1], 4.0 * 0.38) + self.assertEqual(number[1, 1], 5.0 * 0.21) + self.assertEqual(number[2, 1], 6.0 * 1.0) + self.assertEqual(number[0, 2], 7.0 * 0.38) + self.assertEqual(number[1, 2], 8.0 * 0.21) + self.assertEqual(number[2, 2], 9.0 * 1.0) + + def test_get_mat_slice(self): + """Tests getting slices.""" + + mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} + nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + number.number = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]) + + sl = number.get_mat_slice(0) + + np.testing.assert_array_equal(sl, np.array([1.0, 2.0])) + + sl = number.get_mat_slice("10000") + + np.testing.assert_array_equal(sl, np.array([1.0, 2.0])) + + def test_set_mat_slice(self): + """Tests getting slices.""" + + mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} + nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + number.set_mat_slice(0, [1.0, 2.0]) + + self.assertEqual(number[0, 0], 1.0) + self.assertEqual(number[0, 1], 2.0) + + number.set_mat_slice("10000", [3.0, 4.0]) + + self.assertEqual(number[0, 0], 3.0) + self.assertEqual(number[0, 1], 4.0) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/deplete_tests/test_cecm_regression.py b/tests/deplete_tests/test_cecm_regression.py new file mode 100644 index 000000000..23a634200 --- /dev/null +++ b/tests/deplete_tests/test_cecm_regression.py @@ -0,0 +1,69 @@ +""" Regression tests for cecm.py""" + +import os +import unittest + +import numpy as np + +import opendeplete +from opendeplete import results +from opendeplete import utilities +import test.dummy_geometry as dummy_geometry + + +class TestCECMRegression(unittest.TestCase): + """ Regression tests for opendeplete.integrator.cecm algorithm. + + These tests integrate a simple test problem described in dummy_geometry.py. + """ + + @classmethod + def setUpClass(cls): + """ Save current directory in case integrator crashes.""" + cls.cwd = os.getcwd() + cls.results = "test_integrator_regression" + + def test_cecm(self): + """ Integral regression test of integrator algorithm using CE/CM. """ + + settings = opendeplete.Settings() + settings.dt_vec = [0.75, 0.75] + settings.output_dir = self.results + + op = dummy_geometry.DummyGeometry(settings) + + # Perform simulation using the MCNPX/MCNP6 algorithm + opendeplete.cecm(op, print_out=False) + + # Load the files + res = results.read_results(settings.output_dir + "/results.h5") + + _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") + _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") + + # Mathematica solution + s1 = [1.86872629872102, 1.395525772416039] + s2 = [2.18097439443550, 2.69429754646747] + + tol = 1.0e-13 + + self.assertLess(np.absolute(y1[1] - s1[0]), tol) + self.assertLess(np.absolute(y2[1] - s1[1]), tol) + + self.assertLess(np.absolute(y1[2] - s2[0]), tol) + self.assertLess(np.absolute(y2[2] - s2[1]), tol) + + @classmethod + def tearDownClass(cls): + """ Clean up files""" + + os.chdir(cls.cwd) + + opendeplete.comm.barrier() + if opendeplete.comm.rank == 0: + os.remove(os.path.join(cls.results, "results.h5")) + os.rmdir(cls.results) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/deplete_tests/test_cram.py b/tests/deplete_tests/test_cram.py new file mode 100644 index 000000000..2744adbf4 --- /dev/null +++ b/tests/deplete_tests/test_cram.py @@ -0,0 +1,48 @@ +""" Tests for cram.py """ + +import unittest + +import numpy as np +import scipy.sparse as sp + +from opendeplete.integrator import CRAM16, CRAM48 + +class TestCram(unittest.TestCase): + """ Tests for cram.py + + Compares a few Mathematica matrix exponentials to CRAM16/CRAM48. + """ + + def test_CRAM16(self): + """ Test 16-term CRAM. """ + x = np.array([1.0, 1.0]) + mat = sp.csr_matrix([[-1.0, 0.0], [-2.0, -3.0]]) + dt = 0.1 + + z = CRAM16(mat, x, dt) + + # Solution from mathematica + z0 = np.array((0.904837418035960, 0.576799023327476)) + + tol = 1.0e-15 + + self.assertLess(np.linalg.norm(z - z0), tol) + + def test_CRAM48(self): + """ Test 48-term CRAM. """ + x = np.array([1.0, 1.0]) + mat = sp.csr_matrix([[-1.0, 0.0], [-2.0, -3.0]]) + dt = 0.1 + + z = CRAM48(mat, x, dt) + + # Solution from mathematica + z0 = np.array((0.904837418035960, 0.576799023327476)) + + tol = 1.0e-15 + + self.assertLess(np.linalg.norm(z - z0), tol) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/deplete_tests/test_depletion_chain.py b/tests/deplete_tests/test_depletion_chain.py new file mode 100644 index 000000000..216d1e68f --- /dev/null +++ b/tests/deplete_tests/test_depletion_chain.py @@ -0,0 +1,197 @@ +""" Tests for depletion_chain.py""" + +from collections import OrderedDict +import os +import unittest + +import numpy as np + +from opendeplete import comm, depletion_chain, reaction_rates, nuclide + + +class TestDepletionChain(unittest.TestCase): + """ Tests for DepletionChain class.""" + + def test__init__(self): + """ Test depletion chain initialization.""" + dep = depletion_chain.DepletionChain() + + self.assertIsInstance(dep.nuclides, list) + self.assertIsInstance(dep.nuclide_dict, OrderedDict) + self.assertIsInstance(dep.react_to_ind, OrderedDict) + + def test_n_nuclides(self): + """ Test depletion chain n_nuclides parameter. """ + dep = depletion_chain.DepletionChain() + + dep.nuclides = ["NucA", "NucB", "NucC"] + + self.assertEqual(dep.n_nuclides, 3) + + def test_from_endf(self): + """Test depletion chain building from ENDF. Empty at the moment until we figure + out a good way to unit-test this.""" + pass + + def test_xml_read(self): + """ Read chain_test.xml and ensure all values are correct. """ + # Unfortunately, this routine touches a lot of the code, but most of + # the components external to depletion_chain.py are simple storage + # types. + + dep = depletion_chain.DepletionChain.xml_read("chains/chain_test.xml") + + # Basic checks + self.assertEqual(dep.n_nuclides, 3) + + # A tests + nuc = dep.nuclides[dep.nuclide_dict["A"]] + + self.assertEqual(nuc.name, "A") + self.assertEqual(nuc.half_life, 2.36520E+04) + self.assertEqual(nuc.n_decay_modes, 2) + modes = nuc.decay_modes + self.assertEqual([m.target for m in modes], ["B", "C"]) + self.assertEqual([m.type for m in modes], ["beta1", "beta2"]) + self.assertEqual([m.branching_ratio for m in modes], [0.6, 0.4]) + self.assertEqual(nuc.n_reaction_paths, 1) + self.assertEqual([r.target for r in nuc.reactions], ["C"]) + self.assertEqual([r.type for r in nuc.reactions], ["(n,gamma)"]) + self.assertEqual([r.branching_ratio for r in nuc.reactions], [1.0]) + + # B tests + nuc = dep.nuclides[dep.nuclide_dict["B"]] + + self.assertEqual(nuc.name, "B") + self.assertEqual(nuc.half_life, 3.29040E+04) + self.assertEqual(nuc.n_decay_modes, 1) + modes = nuc.decay_modes + self.assertEqual([m.target for m in modes], ["A"]) + self.assertEqual([m.type for m in modes], ["beta"]) + self.assertEqual([m.branching_ratio for m in modes], [1.0]) + self.assertEqual(nuc.n_reaction_paths, 1) + self.assertEqual([r.target for r in nuc.reactions], ["C"]) + self.assertEqual([r.type for r in nuc.reactions], ["(n,gamma)"]) + self.assertEqual([r.branching_ratio for r in nuc.reactions], [1.0]) + + # C tests + nuc = dep.nuclides[dep.nuclide_dict["C"]] + + self.assertEqual(nuc.name, "C") + self.assertEqual(nuc.n_decay_modes, 0) + self.assertEqual(nuc.n_reaction_paths, 3) + self.assertEqual([r.target for r in nuc.reactions], [None, "A", "B"]) + self.assertEqual([r.type for r in nuc.reactions], ["fission", "(n,gamma)", "(n,gamma)"]) + self.assertEqual([r.branching_ratio for r in nuc.reactions], [1.0, 0.7, 0.3]) + + # Yield tests + self.assertEqual(nuc.yield_energies, [0.0253]) + self.assertEqual(list(nuc.yield_data.keys()), [0.0253]) + self.assertEqual(nuc.yield_data[0.0253], + [("A", 0.0292737), ("B", 0.002566345)]) + + def test_xml_write(self): + """Test writing a depletion chain to XML.""" + + # Prevent different MPI ranks from conflicting + filename = 'test%u.xml' % comm.rank + + A = nuclide.Nuclide() + A.name = "A" + A.half_life = 2.36520e4 + A.decay_modes = [ + nuclide.DecayTuple("beta1", "B", 0.6), + nuclide.DecayTuple("beta2", "C", 0.4) + ] + A.reactions = [nuclide.ReactionTuple("(n,gamma)", "C", 0.0, 1.0)] + + B = nuclide.Nuclide() + B.name = "B" + B.half_life = 3.29040e4 + B.decay_modes = [nuclide.DecayTuple("beta", "A", 1.0)] + B.reactions = [nuclide.ReactionTuple("(n,gamma)", "C", 0.0, 1.0)] + + C = nuclide.Nuclide() + C.name = "C" + C.reactions = [ + nuclide.ReactionTuple("fission", None, 2.0e8, 1.0), + nuclide.ReactionTuple("(n,gamma)", "A", 0.0, 0.7), + nuclide.ReactionTuple("(n,gamma)", "B", 0.0, 0.3) + ] + C.yield_energies = [0.0253] + C.yield_data = {0.0253: [("A", 0.0292737), ("B", 0.002566345)]} + + chain = depletion_chain.DepletionChain() + chain.nuclides = [A, B, C] + chain.xml_write(filename) + + original = open('chains/chain_test.xml', 'r').read() + chain_xml = open(filename, 'r').read() + self.assertEqual(original, chain_xml) + + os.remove(filename) + + def test_form_matrix(self): + """ Using chain_test, and a dummy reaction rate, compute the matrix. """ + # Relies on test_xml_read passing. + + dep = depletion_chain.DepletionChain.xml_read("chains/chain_test.xml") + + cell_ind = {"10000": 0, "10001": 1} + nuc_ind = {"A": 0, "B": 1, "C": 2} + react_ind = dep.react_to_ind + + react = reaction_rates.ReactionRates(cell_ind, nuc_ind, react_ind) + + dep.nuc_to_react_ind = nuc_ind + + react["10000", "C", "fission"] = 1.0 + react["10000", "A", "(n,gamma)"] = 2.0 + react["10000", "B", "(n,gamma)"] = 3.0 + react["10000", "C", "(n,gamma)"] = 4.0 + + mat = dep.form_matrix(react[0, :, :]) + # Loss A, decay, (n, gamma) + mat00 = -np.log(2) / 2.36520E+04 - 2 + # A -> B, decay, 0.6 branching ratio + mat10 = np.log(2) / 2.36520E+04 * 0.6 + # A -> C, decay, 0.4 branching ratio + (n,gamma) + mat20 = np.log(2) / 2.36520E+04 * 0.4 + 2 + + # B -> A, decay, 1.0 branching ratio + mat01 = np.log(2)/3.29040E+04 + # Loss B, decay, (n, gamma) + mat11 = -np.log(2)/3.29040E+04 - 3 + # B -> C, (n, gamma) + mat21 = 3 + + # C -> A fission, (n, gamma) + mat02 = 0.0292737 * 1.0 + 4.0 * 0.7 + # C -> B fission, (n, gamma) + mat12 = 0.002566345 * 1.0 + 4.0 * 0.3 + # Loss C, fission, (n, gamma) + mat22 = -1.0 - 4.0 + + self.assertEqual(mat[0, 0], mat00) + self.assertEqual(mat[1, 0], mat10) + self.assertEqual(mat[2, 0], mat20) + self.assertEqual(mat[0, 1], mat01) + self.assertEqual(mat[1, 1], mat11) + self.assertEqual(mat[2, 1], mat21) + self.assertEqual(mat[0, 2], mat02) + self.assertEqual(mat[1, 2], mat12) + self.assertEqual(mat[2, 2], mat22) + + def test_nuc_by_ind(self): + """ Test nuc_by_ind converter function. """ + dep = depletion_chain.DepletionChain() + + dep.nuclides = ["NucA", "NucB", "NucC"] + dep.nuclide_dict = {"NucA" : 0, "NucB" : 1, "NucC" : 2} + + self.assertEqual("NucA", dep.nuc_by_ind("NucA")) + self.assertEqual("NucB", dep.nuc_by_ind("NucB")) + self.assertEqual("NucC", dep.nuc_by_ind("NucC")) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/deplete_tests/test_full.py b/tests/deplete_tests/test_full.py new file mode 100644 index 000000000..f9a6c7493 --- /dev/null +++ b/tests/deplete_tests/test_full.py @@ -0,0 +1,119 @@ +""" Full system test suite. """ + +import shutil +import unittest + +import numpy as np + +import opendeplete +from opendeplete import results +from opendeplete import utilities +import test.example_geometry as example_geometry + + +class TestFull(unittest.TestCase): + """ Full system test suite. + + Runs an entire OpenMC simulation with depletion coupling and verifies + that the outputs match a reference file. Sensitive to changes in + OpenMC. + """ + + def test_full(self): + """ + This test runs a complete OpenMC simulation and tests the outputs. + It will take a while. + """ + + n_rings = 2 + n_wedges = 4 + + # Load geometry from example + geometry, lower_left, upper_right = \ + example_geometry.generate_problem(n_rings=n_rings, n_wedges=n_wedges) + + # Create dt vector for 3 steps with 15 day timesteps + dt1 = 15*24*60*60 # 15 days + dt2 = 1.5*30*24*60*60 # 1.5 months + N = np.floor(dt2/dt1) + + dt = np.repeat([dt1], N) + + # Create settings variable + settings = opendeplete.OpenMCSettings() + + settings.chain_file = "chains/chain_simple.xml" + settings.openmc_call = "openmc" + settings.openmc_npernode = 2 + settings.particles = 100 + settings.batches = 100 + settings.inactive = 40 + settings.lower_left = lower_left + settings.upper_right = upper_right + settings.entropy_dimension = [10, 10, 1] + + settings.round_number = True + settings.constant_seed = 1 + + joule_per_mev = 1.6021766208e-13 + settings.power = 2.337e15*4*joule_per_mev # MeV/second cm from CASMO + settings.dt_vec = dt + settings.output_dir = "test_full" + + op = opendeplete.OpenMCOperator(geometry, settings) + + # Perform simulation using the predictor algorithm + opendeplete.integrator.predictor(op) + + # Load the files + res_test = results.read_results(settings.output_dir + "/results.h5") + + # Load the reference + res_old = results.read_results("test/test_reference.h5") + + # Assert same mats + for mat in res_old[0].mat_to_ind: + self.assertIn(mat, res_test[0].mat_to_ind, + msg="Cell " + mat + " not in new results.") + for nuc in res_old[0].nuc_to_ind: + self.assertIn(nuc, res_test[0].nuc_to_ind, + msg="Nuclide " + nuc + " not in new results.") + + for mat in res_test[0].mat_to_ind: + self.assertIn(mat, res_old[0].mat_to_ind, + msg="Cell " + mat + " not in old results.") + for nuc in res_test[0].nuc_to_ind: + self.assertIn(nuc, res_old[0].nuc_to_ind, + msg="Nuclide " + nuc + " not in old results.") + + for mat in res_test[0].mat_to_ind: + for nuc in res_test[0].nuc_to_ind: + _, y_test = utilities.evaluate_single_nuclide(res_test, mat, nuc) + _, y_old = utilities.evaluate_single_nuclide(res_old, mat, nuc) + + # Test each point + + tol = 1.0e-6 + + correct = True + for i, ref in enumerate(y_old): + if ref != y_test[i]: + if ref != 0.0: + if np.abs(y_test[i] - ref) / ref > tol: + correct = False + else: + correct = False + + self.assertTrue(correct, + msg="Discrepancy in mat " + mat + " and nuc " + nuc + + "\n" + str(y_old) + "\n" + str(y_test)) + + def tearDown(self): + """ Clean up files""" + opendeplete.comm.barrier() + if opendeplete.comm.rank == 0: + shutil.rmtree("test_full", ignore_errors=True) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/deplete_tests/test_integrator.py b/tests/deplete_tests/test_integrator.py new file mode 100644 index 000000000..7e121ce16 --- /dev/null +++ b/tests/deplete_tests/test_integrator.py @@ -0,0 +1,116 @@ +""" Tests for integrator.py """ + +import copy +import os +import unittest +from unittest.mock import MagicMock + +import numpy as np + +from opendeplete import integrator, ReactionRates, results, comm + + +class TestIntegrator(unittest.TestCase): + """ Tests for integrator.py + + It is worth noting that opendeplete.integrate is extremely complex, to + the point I am unsure if it can be reasonably unit-tested. For the time + being, it will be left unimplemented and testing will be done via + regression (in test_integrator_regression.py) + """ + + def test_save_results(self): + """ Test data save module """ + + stages = 3 + + np.random.seed(comm.rank) + + # Mock geometry + op = MagicMock() + + vol_dict = {} + full_burn_dict = {} + + j = 0 + for i in range(comm.size): + vol_dict[str(2*i)] = 1.2 + vol_dict[str(2*i + 1)] = 1.2 + full_burn_dict[str(2*i)] = j + full_burn_dict[str(2*i + 1)] = j + 1 + j += 2 + + burn_list = [str(i) for i in range(2*comm.rank, 2*comm.rank + 2)] + nuc_list = ["na", "nb"] + + op.get_results_info.return_value = vol_dict, nuc_list, burn_list, full_burn_dict + + # Construct x + x1 = [] + x2 = [] + + for i in range(stages): + x1.append([np.random.rand(2), np.random.rand(2)]) + x2.append([np.random.rand(2), np.random.rand(2)]) + + # Construct r + cell_dict = {s:i for i, s in enumerate(burn_list)} + r1 = ReactionRates(cell_dict, {"na":0, "nb":1}, {"ra":0, "rb":1}) + r1.rates = np.random.rand(2, 2, 2) + + rate1 = [] + rate2 = [] + + for i in range(stages): + rate1.append(copy.deepcopy(r1)) + r1.rates = np.random.rand(2, 2, 2) + rate2.append(copy.deepcopy(r1)) + r1.rates = np.random.rand(2, 2, 2) + + # Create global terms + eigvl1 = np.random.rand(stages) + eigvl2 = np.random.rand(stages) + seed1 = [np.random.randint(100) for i in range(stages)] + seed2 = [np.random.randint(100) for i in range(stages)] + + eigvl1 = comm.bcast(eigvl1, root=0) + eigvl2 = comm.bcast(eigvl2, root=0) + seed1 = comm.bcast(seed1, root=0) + seed2 = comm.bcast(seed2, root=0) + + t1 = [0.0, 1.0] + t2 = [1.0, 2.0] + + integrator.save_results(op, x1, rate1, eigvl1, seed1, t1, 0) + integrator.save_results(op, x2, rate2, eigvl2, seed2, t2, 1) + + # Load the files + res = results.read_results("results.h5") + + for i in range(stages): + for mat_i, mat in enumerate(burn_list): + + for nuc_i, nuc in enumerate(nuc_list): + self.assertEqual(res[0][i, mat, nuc], x1[i][mat_i][nuc_i]) + self.assertEqual(res[1][i, mat, nuc], x2[i][mat_i][nuc_i]) + np.testing.assert_array_equal(res[0].rates[i][mat, nuc, :], + rate1[i][mat, nuc, :]) + np.testing.assert_array_equal(res[1].rates[i][mat, nuc, :], + rate2[i][mat, nuc, :]) + + np.testing.assert_array_equal(res[0].k, eigvl1) + np.testing.assert_array_equal(res[0].seeds, seed1) + np.testing.assert_array_equal(res[0].time, t1) + + np.testing.assert_array_equal(res[1].k, eigvl2) + np.testing.assert_array_equal(res[1].seeds, seed2) + np.testing.assert_array_equal(res[1].time, t2) + + # Delete files + comm.barrier() + if comm.rank == 0: + os.remove("results.h5") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/deplete_tests/test_nuclide.py b/tests/deplete_tests/test_nuclide.py new file mode 100644 index 000000000..c5439b2aa --- /dev/null +++ b/tests/deplete_tests/test_nuclide.py @@ -0,0 +1,121 @@ +""" Tests for nuclide.py. """ + +import unittest +import xml.etree.ElementTree as ET + +from opendeplete import nuclide + + +class TestNuclide(unittest.TestCase): + """ Tests for the nuclide class. """ + + def test_n_decay_modes(self): + """ Test the decay mode count parameter. """ + + nuc = nuclide.Nuclide() + + nuc.decay_modes = [ + nuclide.DecayTuple("beta1", "a", 0.5), + nuclide.DecayTuple("beta2", "b", 0.3), + nuclide.DecayTuple("beta3", "c", 0.2) + ] + + self.assertEqual(nuc.n_decay_modes, 3) + + def test_n_reaction_paths(self): + """ Test the reaction path count parameter. """ + + nuc = nuclide.Nuclide() + + nuc.reactions = [ + nuclide.ReactionTuple("(n,2n)", "a", 0.0, 1.0), + nuclide.ReactionTuple("(n,3n)", "b", 0.0, 1.0), + nuclide.ReactionTuple("(n,4n)", "c", 0.0, 1.0) + ] + + self.assertEqual(nuc.n_reaction_paths, 3) + + def test_xml_read(self): + """Test reading nuclide data from an XML element.""" + + data = """ + + + + + + + + + + 0.0253 + + Te134 Zr100 Xe138 + 0.062155 0.0497641 0.0481413 + + + + """ + + element = ET.fromstring(data) + u235 = nuclide.Nuclide.xml_read(element) + + self.assertEqual(u235.decay_modes, [ + nuclide.DecayTuple('sf', 'U235', 7.2e-11), + nuclide.DecayTuple('alpha', 'Th231', 1 - 7.2e-11) + ]) + self.assertEqual(u235.reactions, [ + nuclide.ReactionTuple('(n,2n)', 'U234', -5297781.0, 1.0), + nuclide.ReactionTuple('(n,3n)', 'U233', -12142300.0, 1.0), + nuclide.ReactionTuple('(n,4n)', 'U232', -17885600.0, 1.0), + nuclide.ReactionTuple('(n,gamma)', 'U236', 6545200.0, 1.0), + nuclide.ReactionTuple('fission', None, 193405400.0, 1.0), + ]) + self.assertEqual(u235.yield_energies, [0.0253]) + self.assertEqual(u235.yield_data, { + 0.0253: [('Te134', 0.062155), ('Zr100', 0.0497641), + ('Xe138', 0.0481413)] + }) + + def test_xml_write(self): + """Test writing nuclide data to an XML element.""" + + C = nuclide.Nuclide() + C.name = "C" + C.half_life = 0.123 + C.decay_modes = [ + nuclide.DecayTuple('beta-', 'B', 0.99), + nuclide.DecayTuple('alpha', 'D', 0.01) + ] + C.reactions = [ + nuclide.ReactionTuple('fission', None, 2.0e8, 1.0), + nuclide.ReactionTuple('(n,gamma)', 'A', 0.0, 1.0) + ] + C.yield_energies = [0.0253] + C.yield_data = {0.0253: [("A", 0.0292737), ("B", 0.002566345)]} + element = C.xml_write() + + self.assertEqual(element.get("half_life"), "0.123") + + decay_elems = element.findall("decay_type") + self.assertEqual(len(decay_elems), 2) + self.assertEqual(decay_elems[0].get("type"), "beta-") + self.assertEqual(decay_elems[0].get("target"), "B") + self.assertEqual(decay_elems[0].get("branching_ratio"), "0.99") + self.assertEqual(decay_elems[1].get("type"), "alpha") + self.assertEqual(decay_elems[1].get("target"), "D") + self.assertEqual(decay_elems[1].get("branching_ratio"), "0.01") + + rx_elems = element.findall("reaction_type") + self.assertEqual(len(rx_elems), 2) + self.assertEqual(rx_elems[0].get("type"), "fission") + self.assertEqual(float(rx_elems[0].get("Q")), 2.0e8) + self.assertEqual(rx_elems[1].get("type"), "(n,gamma)") + self.assertEqual(rx_elems[1].get("target"), "A") + self.assertEqual(float(rx_elems[1].get("Q")), 0.0) + + self.assertIsNotNone(element.find('neutron_fission_yields')) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/deplete_tests/test_predictor_regression.py b/tests/deplete_tests/test_predictor_regression.py new file mode 100644 index 000000000..c72ae8a47 --- /dev/null +++ b/tests/deplete_tests/test_predictor_regression.py @@ -0,0 +1,68 @@ +""" Regression tests for predictor.py""" + +import os +import unittest + +import numpy as np + +import opendeplete +from opendeplete import results +from opendeplete import utilities +import test.dummy_geometry as dummy_geometry + +class TestPredictorRegression(unittest.TestCase): + """ Regression tests for opendeplete.integrator.predictor algorithm. + + These tests integrate a simple test problem described in dummy_geometry.py. + """ + + @classmethod + def setUpClass(cls): + """ Save current directory in case integrator crashes.""" + cls.cwd = os.getcwd() + cls.results = "test_integrator_regression" + + def test_predictor(self): + """ Integral regression test of integrator algorithm using CE/CM. """ + + settings = opendeplete.Settings() + settings.dt_vec = [0.75, 0.75] + settings.output_dir = self.results + + op = dummy_geometry.DummyGeometry(settings) + + # Perform simulation using the predictor algorithm + opendeplete.predictor(op, print_out=False) + + # Load the files + res = results.read_results(settings.output_dir + "/results.h5") + + _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") + _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") + + # Mathematica solution + s1 = [2.46847546272295, 0.986431226850467] + s2 = [4.11525874568034, -0.0581692232513460] + + tol = 1.0e-13 + + self.assertLess(np.absolute(y1[1] - s1[0]), tol) + self.assertLess(np.absolute(y2[1] - s1[1]), tol) + + self.assertLess(np.absolute(y1[2] - s2[0]), tol) + self.assertLess(np.absolute(y2[2] - s2[1]), tol) + + @classmethod + def tearDownClass(cls): + """ Clean up files""" + + os.chdir(cls.cwd) + + opendeplete.comm.barrier() + if opendeplete.comm.rank == 0: + os.remove(os.path.join(cls.results, "results.h5")) + os.rmdir(cls.results) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/deplete_tests/test_reaction_rates.py b/tests/deplete_tests/test_reaction_rates.py new file mode 100644 index 000000000..4821ec18c --- /dev/null +++ b/tests/deplete_tests/test_reaction_rates.py @@ -0,0 +1,86 @@ +""" Tests for reaction_rates.py. """ + +import unittest + +from opendeplete import reaction_rates + + +class TestReactionRates(unittest.TestCase): + """ Tests for the ReactionRates class. """ + + def test_indexing(self): + """Tests the __getitem__ and __setitem__ routines simultaneously.""" + + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1} + react_to_ind = {"fission" : 0, "(n,gamma)" : 1} + + rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + + rates["10000", "U238", "fission"] = 1.0 + rates["10001", "U238", "fission"] = 2.0 + rates["10000", "U235", "fission"] = 3.0 + rates["10001", "U235", "fission"] = 4.0 + rates["10000", "U238", "(n,gamma)"] = 5.0 + rates["10001", "U238", "(n,gamma)"] = 6.0 + rates["10000", "U235", "(n,gamma)"] = 7.0 + rates["10001", "U235", "(n,gamma)"] = 8.0 + + # String indexing + self.assertEqual(rates["10000", "U238", "fission"], 1.0) + self.assertEqual(rates["10001", "U238", "fission"], 2.0) + self.assertEqual(rates["10000", "U235", "fission"], 3.0) + self.assertEqual(rates["10001", "U235", "fission"], 4.0) + self.assertEqual(rates["10000", "U238", "(n,gamma)"], 5.0) + self.assertEqual(rates["10001", "U238", "(n,gamma)"], 6.0) + self.assertEqual(rates["10000", "U235", "(n,gamma)"], 7.0) + self.assertEqual(rates["10001", "U235", "(n,gamma)"], 8.0) + + # Int indexing + self.assertEqual(rates[0, 0, 0], 1.0) + self.assertEqual(rates[1, 0, 0], 2.0) + self.assertEqual(rates[0, 1, 0], 3.0) + self.assertEqual(rates[1, 1, 0], 4.0) + self.assertEqual(rates[0, 0, 1], 5.0) + self.assertEqual(rates[1, 0, 1], 6.0) + self.assertEqual(rates[0, 1, 1], 7.0) + self.assertEqual(rates[1, 1, 1], 8.0) + + rates[0, 0, 0] = 5.0 + + self.assertEqual(rates[0, 0, 0], 5.0) + self.assertEqual(rates["10000", "U238", "fission"], 5.0) + + def test_n_mat(self): + """ Test number of materials property. """ + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} + + rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + + self.assertEqual(rates.n_mat, 2) + + def test_n_nuc(self): + """ Test number of nuclides property. """ + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} + + rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + + self.assertEqual(rates.n_nuc, 3) + + def test_n_react(self): + """ Test number of reactions property. """ + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} + + rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + + self.assertEqual(rates.n_react, 4) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/deplete_tests/test_reference.h5 b/tests/deplete_tests/test_reference.h5 new file mode 100644 index 0000000000000000000000000000000000000000..ef3ae0090943bc7ecdc01a1afaa70c0216e029f0 GIT binary patch literal 165384 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

+|}q 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 literal 0 HcmV?d00001 diff --git a/tests/deplete_tests/test_utilities.py b/tests/deplete_tests/test_utilities.py new file mode 100644 index 000000000..faa1a500b --- /dev/null +++ b/tests/deplete_tests/test_utilities.py @@ -0,0 +1,68 @@ +""" Full system test suite. """ + +import unittest + +import numpy as np + +from opendeplete import results +from opendeplete import utilities + + +class TestUtilities(unittest.TestCase): + """ Tests the utilities classes. + + This also tests the results read/write code. + """ + + def test_evaluate_single_nuclide(self): + """ Tests evaluating single nuclide utility code. + """ + + # Load the reference + res = results.read_results("test/test_reference.h5") + + x, y = utilities.evaluate_single_nuclide(res, "1", "Xe135") + + x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] + y_ref = [6.6747328233649218e+08, 3.5519299354458244e+14, + 3.4599104054580338e+14, 3.3821165110278112e+14] + + np.testing.assert_array_equal(x, x_ref) + np.testing.assert_array_equal(y, y_ref) + + def test_evaluate_reaction_rate(self): + """ Tests evaluating reaction rate utility code. + """ + + # Load the reference + res = results.read_results("test/test_reference.h5") + + x, y = utilities.evaluate_reaction_rate(res, "1", "Xe135", "(n,gamma)") + + x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] + xe_ref = np.array([6.6747328233649218e+08, 3.5519299354458244e+14, + 3.4599104054580338e+14, 3.3821165110278112e+14]) + r_ref = np.array([4.0643598574337784e-05, 4.1457730544386974e-05, + 3.4121248544056681e-05, 3.9204686657643301e-05]) + + np.testing.assert_array_equal(x, x_ref) + np.testing.assert_array_equal(y, xe_ref * r_ref) + + def test_evaluate_eigenvalue(self): + """ Tests evaluating eigenvalue + """ + + # Load the reference + res = results.read_results("test/test_reference.h5") + + x, y = utilities.evaluate_eigenvalue(res) + + x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] + y_ref = [1.1921986054449838, 1.1712785643938586, 1.1927099024502694, 1.2269183590698847] + + np.testing.assert_array_equal(x, x_ref) + np.testing.assert_array_equal(y, y_ref) + + +if __name__ == '__main__': + unittest.main() From 37f552a5dcdcb2d16d9db21e95f099c214c3bda7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 9 Feb 2018 14:01:59 -0600 Subject: [PATCH 205/282] Fix deplete imports --- openmc/deplete/__init__.py | 6 +-- openmc/deplete/atom_number.py | 18 +++---- openmc/deplete/depletion_chain.py | 18 +++---- openmc/deplete/function.py | 14 ++--- openmc/deplete/integrator/save_results.py | 3 +- openmc/deplete/openmc_wrapper.py | 52 +++++++++---------- openmc/deplete/reaction_rates.py | 6 +-- openmc/deplete/results.py | 23 ++++---- openmc/deplete/utilities.py | 10 ++-- scripts/example_geometry.py | 3 +- scripts/example_plot.py | 7 +-- scripts/example_run.py | 8 +-- scripts/make_chain.py | 4 +- tests/deplete_tests/dummy_geometry.py | 22 +++----- tests/deplete_tests/test_atom_number.py | 12 ++--- tests/deplete_tests/test_cecm_regression.py | 16 +++--- tests/deplete_tests/test_cram.py | 2 +- tests/deplete_tests/test_depletion_chain.py | 3 +- tests/deplete_tests/test_full.py | 22 ++++---- tests/deplete_tests/test_integrator.py | 3 +- tests/deplete_tests/test_nuclide.py | 2 +- .../test_predictor_regression.py | 16 +++--- tests/deplete_tests/test_reaction_rates.py | 2 +- tests/deplete_tests/test_utilities.py | 15 +++--- 24 files changed, 141 insertions(+), 146 deletions(-) diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index 994a51e12..4bdde3935 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -1,8 +1,8 @@ """ -OpenDeplete -=========== +openmc.deplete +============== -A simple depletion front-end tool. +A depletion front-end tool. """ from .dummy_comm import DummyCommunicator diff --git a/openmc/deplete/atom_number.py b/openmc/deplete/atom_number.py index 03bedbf53..63c9af836 100644 --- a/openmc/deplete/atom_number.py +++ b/openmc/deplete/atom_number.py @@ -7,7 +7,7 @@ import numpy as np class AtomNumber(object): - """ AtomNumber module. + """AtomNumber module. An ndarray to store atom densities with string, integer, or slice indexing. @@ -71,7 +71,7 @@ class AtomNumber(object): self._burn_mat_list = None def __getitem__(self, pos): - """ Retrieves total atom number from AtomNumber. + """Retrieves total atom number from AtomNumber. Parameters ---------- @@ -95,7 +95,7 @@ class AtomNumber(object): return self.number[mat, nuc] def __setitem__(self, pos, val): - """ Sets total atom number into AtomNumber. + """Sets total atom number into AtomNumber. Parameters ---------- @@ -116,7 +116,7 @@ class AtomNumber(object): self.number[mat, nuc] = val def get_atom_density(self, mat, nuc): - """ Accesses atom density instead of total number. + """Accesses atom density instead of total number. Parameters ---------- @@ -139,7 +139,7 @@ class AtomNumber(object): return self[mat, nuc] / self.volume[mat] def set_atom_density(self, mat, nuc, val): - """ Sets atom density instead of total number. + """Sets atom density instead of total number. Parameters ---------- @@ -159,7 +159,7 @@ class AtomNumber(object): self[mat, nuc] = val * self.volume[mat] def get_mat_slice(self, mat): - """ Gets atom quantity indexed by mats for all burned nuclides + """Gets atom quantity indexed by mats for all burned nuclides Parameters ---------- @@ -178,7 +178,7 @@ class AtomNumber(object): return self[mat, 0:self.n_nuc_burn] def set_mat_slice(self, mat, val): - """ Sets atom quantity indexed by mats for all burned nuclides + """Sets atom quantity indexed by mats for all burned nuclides Parameters ---------- @@ -205,7 +205,7 @@ class AtomNumber(object): @property def burn_nuc_list(self): - """ burn_nuc_list : list of str + """burn_nuc_list : list of str A list of all nuclide material names. Used for sorting the simulation. """ @@ -221,7 +221,7 @@ class AtomNumber(object): @property def burn_mat_list(self): - """ burn_mat_list : list of str + """burn_mat_list : list of str A list of all burning material names. Used for sorting the simulation. """ diff --git a/openmc/deplete/depletion_chain.py b/openmc/deplete/depletion_chain.py index 05cc9db43..af126035f 100644 --- a/openmc/deplete/depletion_chain.py +++ b/openmc/deplete/depletion_chain.py @@ -11,9 +11,6 @@ import math import re import os -from tqdm import tqdm -import scipy.sparse as sp -import openmc.data # 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. @@ -22,9 +19,12 @@ try: _have_lxml = True except ImportError: import xml.etree.ElementTree as ET - from openmc.clean_xml import clean_xml_indentation _have_lxml = False +from tqdm import tqdm +import scipy.sparse as sp +import openmc.data +from openmc.clean_xml import clean_xml_indentation from .nuclide import Nuclide, DecayTuple, ReactionTuple @@ -109,7 +109,7 @@ def replace_missing(product, decay_data): class DepletionChain(object): - """ The DepletionChain class. + """The DepletionChain class. This class contains a full representation of a depletion chain. @@ -334,7 +334,7 @@ class DepletionChain(object): # Load XML tree try: root = ET.parse(filename) - except: + except Exception: if filename is None: print("No chain specified, either manually or in environment variable OPENDEPLETE_CHAIN.") else: @@ -374,11 +374,11 @@ class DepletionChain(object): if _have_lxml: tree.write(filename, encoding='utf-8', pretty_print=True) else: - clean_xml_indentation(root_elem, spaces_per_level=2) + clean_xml_indentation(root_elem) tree.write(filename, encoding='utf-8') def form_matrix(self, rates): - """ Forms depletion matrix. + """Forms depletion matrix. Parameters ---------- @@ -457,7 +457,7 @@ class DepletionChain(object): return matrix_dok.tocsr() def nuc_by_ind(self, ind): - """ Extracts nuclides from the list by dictionary key. + """Extracts nuclides from the list by dictionary key. Parameters ---------- diff --git a/openmc/deplete/function.py b/openmc/deplete/function.py index 74eb92422..bcc055e67 100644 --- a/openmc/deplete/function.py +++ b/openmc/deplete/function.py @@ -6,8 +6,9 @@ to run a full depletion simulation. from abc import ABCMeta, abstractmethod + class Settings(object): - """ The Settings class. + """The Settings class. Contains all parameters necessary for the integrator. @@ -24,8 +25,9 @@ class Settings(object): self.dt_vec = None self.output_dir = None + class Operator(metaclass=ABCMeta): - """ The Operator metaclass. + """The Operator metaclass. This defines all functions that the integrator needs to operate. @@ -40,7 +42,7 @@ class Operator(metaclass=ABCMeta): @abstractmethod def initial_condition(self): - """ Performs final setup and returns initial condition. + """Performs final setup and returns initial condition. Returns ------- @@ -52,7 +54,7 @@ class Operator(metaclass=ABCMeta): @abstractmethod def eval(self, vec, print_out=True): - """ Runs a simulation. + """Runs a simulation. Parameters ---------- @@ -75,7 +77,7 @@ class Operator(metaclass=ABCMeta): @abstractmethod def get_results_info(self): - """ Returns volume list, cell lists, and nuc lists. + """Returns volume list, cell lists, and nuc lists. Returns ------- @@ -93,7 +95,7 @@ class Operator(metaclass=ABCMeta): @abstractmethod def form_matrix(self, y, mat): - """ Forms the f(y) matrix in y' = f(y)y. + """Forms the f(y) matrix in y' = f(y)y. Nominally a depletion matrix, this is abstracted on the off chance that the function f has nothing to do with depletion at all. diff --git a/openmc/deplete/integrator/save_results.py b/openmc/deplete/integrator/save_results.py index 35cbc7f3f..4f20b52fd 100644 --- a/openmc/deplete/integrator/save_results.py +++ b/openmc/deplete/integrator/save_results.py @@ -1,7 +1,8 @@ """ Generic result saving code for integrators. """ -from opendeplete.results import Results, write_results +from ..results import Results, write_results + def save_results(op, x, rates, eigvls, seeds, t, step_ind): """ Creates and writes results to disk diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 347dc7185..05b5057d2 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -1,6 +1,6 @@ -""" The OpenMC wrapper module. +"""The OpenMC wrapper module. -This module implements the OpenDeplete -> OpenMC linkage. +This module implements the depletion -> OpenMC linkage. """ import copy @@ -14,14 +14,13 @@ try: _have_lxml = True except ImportError: import xml.etree.ElementTree as ET - from openmc.clean_xml import clean_xml_indentation _have_lxml = False import h5py import numpy as np + import openmc import openmc.capi - from . import comm from .atom_number import AtomNumber from .depletion_chain import DepletionChain @@ -194,7 +193,7 @@ class OpenMCOperator(Operator): # Clear out OpenMC, create task lists, distribute if comm.rank == 0: - clean_up_openmc() + openmc.reset_auto_ids() mat_burn_list, mat_not_burn_list, volume, self.mat_tally_ind, \ nuc_dict = self.extract_mat_ids() else: @@ -224,7 +223,7 @@ class OpenMCOperator(Operator): openmc.capi.finalize() def extract_mat_ids(self): - """ Extracts materials and assigns them to processes. + """Extracts materials and assigns them to processes. Returns ------- @@ -308,7 +307,7 @@ class OpenMCOperator(Operator): return mat_burn_lists, mat_not_burn_lists, volume, mat_tally_ind, nuc_dict def extract_number(self, mat_burn, mat_not_burn, volume, nuc_dict): - """ Construct self.number read from geometry + """Construct self.number read from geometry Parameters ---------- @@ -356,7 +355,7 @@ class OpenMCOperator(Operator): self.set_number_from_mat(mat) def set_number_from_mat(self, mat): - """ Extracts material and number densities from openmc.Material + """Extracts material and number densities from openmc.Material Parameters ---------- @@ -369,12 +368,11 @@ class OpenMCOperator(Operator): nuc_dens = mat.get_nuclide_atom_densities() for nuclide in nuc_dens: - name = nuclide.name number = nuc_dens[nuclide][1] * 1.0e24 - self.number.set_atom_density(mat_id, name, number) + self.number.set_atom_density(mat_id, nuclide, number) def initialize_reaction_rates(self): - """ Create reaction rates object. """ + """Create reaction rates object. """ self.reaction_rates = ReactionRates( self.burn_mat_to_ind, self.burn_nuc_to_ind, @@ -383,7 +381,7 @@ class OpenMCOperator(Operator): self.chain.nuc_to_react_ind = self.burn_nuc_to_ind def eval(self, vec, print_out=True): - """ Runs a simulation. + """Runs a simulation. Parameters ---------- @@ -405,7 +403,7 @@ class OpenMCOperator(Operator): """ # Prevent OpenMC from complaining about re-creating tallies - clean_up_openmc() + openmc.reset_auto_ids() # Update status self.set_density(vec) @@ -435,7 +433,7 @@ class OpenMCOperator(Operator): return k, copy.deepcopy(self.reaction_rates), self.seed def form_matrix(self, y, mat): - """ Forms the depletion matrix. + """Forms the depletion matrix. Parameters ---------- @@ -453,7 +451,7 @@ class OpenMCOperator(Operator): return copy.deepcopy(self.chain.form_matrix(y[mat, :, :])) def initial_condition(self): - """ Performs final setup and returns initial condition. + """Performs final setup and returns initial condition. Returns ------- @@ -513,7 +511,7 @@ class OpenMCOperator(Operator): mat_internal.set_densities(nuclides, densities) def generate_materials_xml(self): - """ Creates materials.xml from self.number. + """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 @@ -531,7 +529,7 @@ class OpenMCOperator(Operator): materials.export_to_xml() def generate_settings_xml(self): - """ Generates settings.xml. + """Generates settings.xml. This function creates settings.xml using the value of the settings variable. @@ -625,7 +623,7 @@ class OpenMCOperator(Operator): tally_dep.filters = [mat_filter] def total_density_list(self): - """ Returns a list of total density lists. + """Returns a list of total density lists. This list is in the exact same order as depletion_matrix_list, so that matrix exponentiation can be done easily. @@ -641,7 +639,7 @@ class OpenMCOperator(Operator): return total_density def set_density(self, total_density): - """ Sets density. + """Sets density. Sets the density in the exact same order as total_density_list outputs, allowing for internal consistency @@ -657,7 +655,7 @@ class OpenMCOperator(Operator): self.number.set_mat_slice(i, total_density[i]) def unpack_tallies_and_normalize(self): - """ Unpack tallies from OpenMC + """Unpack tallies from OpenMC This function reads the tallies generated by OpenMC (from the tally.xml file generated in generate_tally_xml) normalizes them so that the total @@ -754,7 +752,7 @@ class OpenMCOperator(Operator): return k_combined def load_participating(self): - """ Loads a cross_sections.xml file to find participating nuclides. + """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. @@ -772,7 +770,7 @@ class OpenMCOperator(Operator): try: tree = ET.parse(filename) - except: + except Exception: if filename is None: msg = "No cross_sections.xml specified in materials." else: @@ -802,7 +800,7 @@ class OpenMCOperator(Operator): return len(self.chain.nuclides) def get_results_info(self): - """ Returns volume list, cell lists, and nuc lists. + """Returns volume list, cell lists, and nuc lists. Returns ------- @@ -829,8 +827,10 @@ class OpenMCOperator(Operator): return volume, nuc_list, burn_list, self.mat_tally_ind + def density_to_mat(dens_dict): - """ Generates an OpenMC material from a cell ID and self.number_density. + """Generates an OpenMC material from a cell ID and self.number_density. + Parameters ---------- m_id : int @@ -847,7 +847,3 @@ def density_to_mat(dens_dict): mat.set_density('sum') return mat - -def clean_up_openmc(): - """ Resets all automatic indexing in OpenMC, as these get in the way. """ - openmc.reset_auto_ids() diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py index 7b934027a..de3a6a728 100644 --- a/openmc/deplete/reaction_rates.py +++ b/openmc/deplete/reaction_rates.py @@ -7,7 +7,7 @@ import numpy as np class ReactionRates(object): - """ ReactionRates class. + """ReactionRates class. An ndarray to store reaction rates with string, integer, or slice indexing. @@ -47,7 +47,7 @@ class ReactionRates(object): self.rates = np.zeros((self.n_mat, self.n_nuc, self.n_react)) def __getitem__(self, pos): - """ Retrieves an item from reaction_rates. + """Retrieves an item from reaction_rates. Parameters ---------- @@ -74,7 +74,7 @@ class ReactionRates(object): return self.rates[mat, nuc, react] def __setitem__(self, pos, val): - """ Sets an item from reaction_rates. + """Sets an item from reaction_rates. Parameters ---------- diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index c0ec1627e..ae096b8ce 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -1,4 +1,4 @@ -""" The results module. +"""The results module. Contains results generation and saving capabilities. """ @@ -14,8 +14,9 @@ from .reaction_rates import ReactionRates RESULTS_VERSION = 2 + class Results(object): - """ Contains output of opendeplete. + """Contains output of opendeplete. Attributes ---------- @@ -62,7 +63,7 @@ class Results(object): self.data = None def allocate(self, volume, nuc_list, burn_list, full_burn_dict, stages): - """ Allocates memory of Results. + """Allocates memory of Results. Parameters ---------- @@ -113,7 +114,7 @@ class Results(object): return self.data.shape[0] def __getitem__(self, pos): - """ Retrieves an item from results. + """Retrieves an item from results. Parameters ---------- @@ -137,7 +138,7 @@ class Results(object): return self.data[stage, mat, nuc] def __setitem__(self, pos, val): - """ Sets an item from results. + """Sets an item from results. Parameters ---------- @@ -159,7 +160,7 @@ class Results(object): self.data[stage, mat, nuc] = val def create_hdf5(self, handle): - """ Creates file structure for a blank HDF5 file. + """Creates file structure for a blank HDF5 file. Parameters ---------- @@ -232,7 +233,7 @@ class Results(object): handle.create_dataset("time", (1, 2), maxshape=(None, 2), dtype='float64') def to_hdf5(self, handle, index): - """ Converts results object into an hdf5 object. + """Converts results object into an hdf5 object. Parameters ---------- @@ -302,7 +303,7 @@ class Results(object): time_dset[index, :] = self.time def from_hdf5(self, handle, index): - """ Loads results object from HDF5. + """Loads results object from HDF5. Parameters ---------- @@ -360,7 +361,7 @@ class Results(object): def get_dict(number): - """ Given an operator nested dictionary, output indexing dictionaries. + """Given an operator nested dictionary, output indexing dictionaries. These indexing dictionaries map mat IDs and nuclide names to indices inside of Results.data. @@ -394,7 +395,7 @@ def get_dict(number): def write_results(result, filename, index): - """ Outputs result to an .hdf5 file. + """Outputs result to an .hdf5 file. Parameters ---------- @@ -418,7 +419,7 @@ def write_results(result, filename, index): def read_results(filename): - """ Reads out a list of results objects from an hdf5 file. + """Reads out a list of results objects from an hdf5 file. Parameters ---------- diff --git a/openmc/deplete/utilities.py b/openmc/deplete/utilities.py index 54632ed9c..5433edce4 100644 --- a/openmc/deplete/utilities.py +++ b/openmc/deplete/utilities.py @@ -1,4 +1,4 @@ -""" The utilities module. +"""The utilities module. Contains functions that can be used to post-process objects that come out of the results module. @@ -6,8 +6,9 @@ the results module. import numpy as np + def evaluate_single_nuclide(results, cell, nuc): - """ Evaluates a single nuclide in a single cell from a results list. + """Evaluates a single nuclide in a single cell from a results list. Parameters ---------- @@ -38,7 +39,7 @@ def evaluate_single_nuclide(results, cell, nuc): return time, concentration def evaluate_reaction_rate(results, cell, nuc, rxn): - """ Evaluates a single nuclide reaction rate in a single cell from a results list. + """Evaluates a single nuclide reaction rate in a single cell from a results list. Parameters ---------- @@ -69,8 +70,9 @@ def evaluate_reaction_rate(results, cell, nuc, rxn): return time, rate + def evaluate_eigenvalue(results): - """ Evaluates the eigenvalue from a results list. + """Evaluates the eigenvalue from a results list. Parameters ---------- diff --git a/scripts/example_geometry.py b/scripts/example_geometry.py index 9afcc0d46..09ce0576f 100644 --- a/scripts/example_geometry.py +++ b/scripts/example_geometry.py @@ -9,8 +9,7 @@ import math import numpy as np import openmc - -from opendeplete import density_to_mat +from openmc.deplete import density_to_mat def generate_initial_number_density(): diff --git a/scripts/example_plot.py b/scripts/example_plot.py index d2c6ee9d6..c92fef6bf 100644 --- a/scripts/example_plot.py +++ b/scripts/example_plot.py @@ -1,11 +1,8 @@ """An example file showing how to plot data from a simulation.""" import matplotlib.pyplot as plt - -from opendeplete import read_results, \ - evaluate_single_nuclide, \ - evaluate_reaction_rate, \ - evaluate_eigenvalue +from openmc.deplete import (read_results, evaluate_single_nuclide, + evaluate_reaction_rate, evaluate_eigenvalue) # Set variables for where the data is, and what we want to read out. result_folder = "test" diff --git a/scripts/example_run.py b/scripts/example_run.py index bb80f6582..82d0883c3 100644 --- a/scripts/example_run.py +++ b/scripts/example_run.py @@ -1,7 +1,7 @@ """An example file showing how to run a simulation.""" import numpy as np -import opendeplete +import openmc.deplete import example_geometry @@ -16,7 +16,7 @@ N = np.floor(dt2/dt1) dt = np.repeat([dt1], N) # Create settings variable -settings = opendeplete.OpenMCSettings() +settings = openmc.deplete.OpenMCSettings() settings.openmc_call = "openmc" # An example for mpiexec: @@ -33,7 +33,7 @@ settings.power = 2.337e15*4*joule_per_mev # MeV/second cm from CASMO settings.dt_vec = dt settings.output_dir = 'test' -op = opendeplete.OpenMCOperator(geometry, settings) +op = openmc.deplete.OpenMCOperator(geometry, settings) # Perform simulation using the MCNPX/MCNP6 algorithm -opendeplete.integrator.cecm(op) +openmc.deplete.integrator.cecm(op) diff --git a/scripts/make_chain.py b/scripts/make_chain.py index 2e0d9d3bc..ccf4ef9b7 100644 --- a/scripts/make_chain.py +++ b/scripts/make_chain.py @@ -6,7 +6,7 @@ from zipfile import ZipFile import requests from tqdm import tqdm -import opendeplete +import openmc.deplete urls = [ @@ -52,7 +52,7 @@ def main(): nfy_files = glob.glob(os.path.join('nfy', '*.endf')) neutron_files = glob.glob(os.path.join('neutrons', '*.endf')) - chain = opendeplete.DepletionChain.from_endf(decay_files, nfy_files, neutron_files) + chain = openmc.deplete.DepletionChain.from_endf(decay_files, nfy_files, neutron_files) chain.xml_write('chain_endfb71.xml') diff --git a/tests/deplete_tests/dummy_geometry.py b/tests/deplete_tests/dummy_geometry.py index 610151941..614cc726e 100644 --- a/tests/deplete_tests/dummy_geometry.py +++ b/tests/deplete_tests/dummy_geometry.py @@ -1,16 +1,11 @@ -""" The OpenMC wrapper module. - -This module implements the OpenDeplete -> OpenMC linkage. -""" - import numpy as np import scipy.sparse as sp +from openmc.deplete.reaction_rates import ReactionRates +from openmc.deplete.function import Operator -from opendeplete.reaction_rates import ReactionRates -from opendeplete.function import Operator class DummyGeometry(Operator): - """ This is a dummy geometry class with no statistical uncertainty. + """This is a dummy geometry class with no statistical uncertainty. y_1' = sin(y_2) y_1 + cos(y_1) y_2 y_2' = -cos(y_2) y_1 + sin(y_1) y_2 @@ -24,14 +19,14 @@ class DummyGeometry(Operator): """ def __init__(self, settings): - Operator.__init__(self, settings) + super().__init__(settings) @property def chain(self): return self def eval(self, vec, print_out=False): - """ Evaluates F(y) + """Evaluates F(y) Parameters ---------- @@ -60,11 +55,10 @@ class DummyGeometry(Operator): reaction_rates[0, 1, 0] = vec[0][1] # Create a fake rates object - return 0.0, reaction_rates, 0 def form_matrix(self, rates): - """ Forms the f(y) matrix in y' = f(y)y. + """Forms the f(y) matrix in y' = f(y)y. Nominally a depletion matrix, this is abstracted on the off chance that the function f has nothing to do with depletion at all. @@ -137,7 +131,7 @@ class DummyGeometry(Operator): return ReactionRates(cell_to_ind, nuc_to_ind, react_to_ind) def initial_condition(self): - """ Returns initial vector. + """Returns initial vector. Returns ------- @@ -148,7 +142,7 @@ class DummyGeometry(Operator): return [np.array((1.0, 1.0))] def get_results_info(self): - """ Returns volume list, cell lists, and nuc lists. + """Returns volume list, cell lists, and nuc lists. Returns ------- diff --git a/tests/deplete_tests/test_atom_number.py b/tests/deplete_tests/test_atom_number.py index 9a17230f8..d36b96d38 100644 --- a/tests/deplete_tests/test_atom_number.py +++ b/tests/deplete_tests/test_atom_number.py @@ -3,11 +3,11 @@ import unittest import numpy as np +from openmc.deplete import atom_number -from opendeplete import atom_number class TestAtomNumber(unittest.TestCase): - """ Tests for the AtomNumber class. """ + """Tests for the AtomNumber class.""" def test_indexing(self): """Tests the __getitem__ and __setitem__ routines simultaneously.""" @@ -41,7 +41,7 @@ class TestAtomNumber(unittest.TestCase): self.assertEqual(number["10000", "U238"], 5.0) def test_n_mat(self): - """ Test number of materials property. """ + """Test number of materials property. """ mat_to_ind = {"10000" : 0, "10001" : 1} nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} volume = {"10000" : 0.38, "10001" : 0.21} @@ -51,7 +51,7 @@ class TestAtomNumber(unittest.TestCase): self.assertEqual(number.n_mat, 2) def test_n_nuc(self): - """ Test number of nuclides property. """ + """Test number of nuclides property.""" mat_to_ind = {"10000" : 0, "10001" : 1} nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} volume = {"10000" : 0.38, "10001" : 0.21} @@ -61,7 +61,7 @@ class TestAtomNumber(unittest.TestCase): self.assertEqual(number.n_nuc, 3) def test_burn_nuc_list(self): - """ Test the list of burned nuclides property """ + """Test the list of burned nuclides property""" mat_to_ind = {"10000" : 0, "10001" : 1} nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} volume = {"10000" : 0.38, "10001" : 0.21} @@ -71,7 +71,7 @@ class TestAtomNumber(unittest.TestCase): self.assertEqual(number.burn_nuc_list, ["U238", "U235"]) def test_burn_mat_list(self): - """ Test the list of burned nuclides property """ + """Test the list of burned nuclides property""" mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} volume = {"10000" : 0.38, "10001" : 0.21} diff --git a/tests/deplete_tests/test_cecm_regression.py b/tests/deplete_tests/test_cecm_regression.py index 23a634200..0d6966c82 100644 --- a/tests/deplete_tests/test_cecm_regression.py +++ b/tests/deplete_tests/test_cecm_regression.py @@ -4,11 +4,11 @@ import os import unittest import numpy as np +import openmc.deplete +from openmc.deplete import results +from openmc.deplete import utilities -import opendeplete -from opendeplete import results -from opendeplete import utilities -import test.dummy_geometry as dummy_geometry +from . import dummy_geometry class TestCECMRegression(unittest.TestCase): @@ -26,14 +26,14 @@ class TestCECMRegression(unittest.TestCase): def test_cecm(self): """ Integral regression test of integrator algorithm using CE/CM. """ - settings = opendeplete.Settings() + settings = openmc.deplete.Settings() settings.dt_vec = [0.75, 0.75] settings.output_dir = self.results op = dummy_geometry.DummyGeometry(settings) # Perform simulation using the MCNPX/MCNP6 algorithm - opendeplete.cecm(op, print_out=False) + openmc.deplete.cecm(op, print_out=False) # Load the files res = results.read_results(settings.output_dir + "/results.h5") @@ -59,8 +59,8 @@ class TestCECMRegression(unittest.TestCase): os.chdir(cls.cwd) - opendeplete.comm.barrier() - if opendeplete.comm.rank == 0: + openmc.deplete.comm.barrier() + if openmc.deplete.comm.rank == 0: os.remove(os.path.join(cls.results, "results.h5")) os.rmdir(cls.results) diff --git a/tests/deplete_tests/test_cram.py b/tests/deplete_tests/test_cram.py index 2744adbf4..10f41fe2b 100644 --- a/tests/deplete_tests/test_cram.py +++ b/tests/deplete_tests/test_cram.py @@ -4,8 +4,8 @@ import unittest import numpy as np import scipy.sparse as sp +from openmc.deplete.integrator import CRAM16, CRAM48 -from opendeplete.integrator import CRAM16, CRAM48 class TestCram(unittest.TestCase): """ Tests for cram.py diff --git a/tests/deplete_tests/test_depletion_chain.py b/tests/deplete_tests/test_depletion_chain.py index 216d1e68f..06abbba0f 100644 --- a/tests/deplete_tests/test_depletion_chain.py +++ b/tests/deplete_tests/test_depletion_chain.py @@ -5,8 +5,7 @@ import os import unittest import numpy as np - -from opendeplete import comm, depletion_chain, reaction_rates, nuclide +from openmc.deplete import comm, depletion_chain, reaction_rates, nuclide class TestDepletionChain(unittest.TestCase): diff --git a/tests/deplete_tests/test_full.py b/tests/deplete_tests/test_full.py index f9a6c7493..88c409554 100644 --- a/tests/deplete_tests/test_full.py +++ b/tests/deplete_tests/test_full.py @@ -2,13 +2,14 @@ import shutil import unittest +from os.path import join, dirname import numpy as np +import openmc.deplete +from openmc.deplete import results +from openmc.deplete import utilities -import opendeplete -from opendeplete import results -from opendeplete import utilities -import test.example_geometry as example_geometry +from . import example_geometry class TestFull(unittest.TestCase): @@ -40,7 +41,7 @@ class TestFull(unittest.TestCase): dt = np.repeat([dt1], N) # Create settings variable - settings = opendeplete.OpenMCSettings() + settings = openmc.deplete.OpenMCSettings() settings.chain_file = "chains/chain_simple.xml" settings.openmc_call = "openmc" @@ -60,16 +61,17 @@ class TestFull(unittest.TestCase): settings.dt_vec = dt settings.output_dir = "test_full" - op = opendeplete.OpenMCOperator(geometry, settings) + op = openmc.deplete.OpenMCOperator(geometry, settings) # Perform simulation using the predictor algorithm - opendeplete.integrator.predictor(op) + openmc.deplete.integrator.predictor(op) # Load the files res_test = results.read_results(settings.output_dir + "/results.h5") # Load the reference - res_old = results.read_results("test/test_reference.h5") + filename = join(dirname(__file__), 'test_reference.h5') + res_old = results.read_results(filename) # Assert same mats for mat in res_old[0].mat_to_ind: @@ -110,8 +112,8 @@ class TestFull(unittest.TestCase): def tearDown(self): """ Clean up files""" - opendeplete.comm.barrier() - if opendeplete.comm.rank == 0: + openmc.deplete.comm.barrier() + if openmc.deplete.comm.rank == 0: shutil.rmtree("test_full", ignore_errors=True) diff --git a/tests/deplete_tests/test_integrator.py b/tests/deplete_tests/test_integrator.py index 7e121ce16..9b4cbe780 100644 --- a/tests/deplete_tests/test_integrator.py +++ b/tests/deplete_tests/test_integrator.py @@ -6,8 +6,7 @@ import unittest from unittest.mock import MagicMock import numpy as np - -from opendeplete import integrator, ReactionRates, results, comm +from openmc.deplete import integrator, ReactionRates, results, comm class TestIntegrator(unittest.TestCase): diff --git a/tests/deplete_tests/test_nuclide.py b/tests/deplete_tests/test_nuclide.py index c5439b2aa..2d379030d 100644 --- a/tests/deplete_tests/test_nuclide.py +++ b/tests/deplete_tests/test_nuclide.py @@ -3,7 +3,7 @@ import unittest import xml.etree.ElementTree as ET -from opendeplete import nuclide +from openmc.deplete import nuclide class TestNuclide(unittest.TestCase): diff --git a/tests/deplete_tests/test_predictor_regression.py b/tests/deplete_tests/test_predictor_regression.py index c72ae8a47..a41ca9ce7 100644 --- a/tests/deplete_tests/test_predictor_regression.py +++ b/tests/deplete_tests/test_predictor_regression.py @@ -4,11 +4,11 @@ import os import unittest import numpy as np +import openmc.deplete +from openmc.deplete import results +from openmc.deplete import utilities -import opendeplete -from opendeplete import results -from opendeplete import utilities -import test.dummy_geometry as dummy_geometry +from . import dummy_geometry class TestPredictorRegression(unittest.TestCase): """ Regression tests for opendeplete.integrator.predictor algorithm. @@ -25,14 +25,14 @@ class TestPredictorRegression(unittest.TestCase): def test_predictor(self): """ Integral regression test of integrator algorithm using CE/CM. """ - settings = opendeplete.Settings() + settings = openmc.deplete.Settings() settings.dt_vec = [0.75, 0.75] settings.output_dir = self.results op = dummy_geometry.DummyGeometry(settings) # Perform simulation using the predictor algorithm - opendeplete.predictor(op, print_out=False) + openmc.deplete.predictor(op, print_out=False) # Load the files res = results.read_results(settings.output_dir + "/results.h5") @@ -58,8 +58,8 @@ class TestPredictorRegression(unittest.TestCase): os.chdir(cls.cwd) - opendeplete.comm.barrier() - if opendeplete.comm.rank == 0: + openmc.deplete.comm.barrier() + if openmc.deplete.comm.rank == 0: os.remove(os.path.join(cls.results, "results.h5")) os.rmdir(cls.results) diff --git a/tests/deplete_tests/test_reaction_rates.py b/tests/deplete_tests/test_reaction_rates.py index 4821ec18c..2139be16c 100644 --- a/tests/deplete_tests/test_reaction_rates.py +++ b/tests/deplete_tests/test_reaction_rates.py @@ -2,7 +2,7 @@ import unittest -from opendeplete import reaction_rates +from openmc.deplete import reaction_rates class TestReactionRates(unittest.TestCase): diff --git a/tests/deplete_tests/test_utilities.py b/tests/deplete_tests/test_utilities.py index faa1a500b..fb60df5b7 100644 --- a/tests/deplete_tests/test_utilities.py +++ b/tests/deplete_tests/test_utilities.py @@ -1,11 +1,11 @@ """ Full system test suite. """ import unittest +from os.path import join, dirname import numpy as np - -from opendeplete import results -from opendeplete import utilities +from openmc.deplete import results +from openmc.deplete import utilities class TestUtilities(unittest.TestCase): @@ -19,7 +19,8 @@ class TestUtilities(unittest.TestCase): """ # Load the reference - res = results.read_results("test/test_reference.h5") + filename = join(dirname(__file__), 'test_reference.h5') + res = results.read_results(filename) x, y = utilities.evaluate_single_nuclide(res, "1", "Xe135") @@ -35,7 +36,8 @@ class TestUtilities(unittest.TestCase): """ # Load the reference - res = results.read_results("test/test_reference.h5") + filename = join(dirname(__file__), 'test_reference.h5') + res = results.read_results(filename) x, y = utilities.evaluate_reaction_rate(res, "1", "Xe135", "(n,gamma)") @@ -53,7 +55,8 @@ class TestUtilities(unittest.TestCase): """ # Load the reference - res = results.read_results("test/test_reference.h5") + filename = join(dirname(__file__), 'test_reference.h5') + res = results.read_results(filename) x, y = utilities.evaluate_eigenvalue(res) From 0a772252cc4c64295b25832555f397bc3e431007 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 9 Feb 2018 14:51:36 -0600 Subject: [PATCH 206/282] Fix bug in openmc_tally_set_scores (for depletion reactions) --- src/tallies/tally_header.F90 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 474e77d1c..9604d2382 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -732,8 +732,10 @@ contains integer :: MT character(C_CHAR), pointer :: string(:) character(len=:, kind=C_CHAR), allocatable :: score_ + logical :: depletion_rx err = E_UNASSIGNED + depletion_rx = .false. if (index >= 1 .and. index <= size(tallies)) then associate (t => tallies(index) % obj) if (allocated(t % score_bins)) deallocate(t % score_bins) @@ -757,10 +759,13 @@ contains t % score_bins(i) = SCORE_NU_SCATTER case ('(n,2n)') t % score_bins(i) = N_2N + depletion_rx = .true. case ('(n,3n)') t % score_bins(i) = N_3N + depletion_rx = .true. case ('(n,4n)') t % score_bins(i) = N_4N + depletion_rx = .true. case ('absorption') t % score_bins(i) = SCORE_ABSORPTION case ('fission', '18') @@ -829,8 +834,10 @@ contains t % score_bins(i) = N_NC case ('(n,gamma)') t % score_bins(i) = N_GAMMA + depletion_rx = .true. case ('(n,p)') t % score_bins(i) = N_P + depletion_rx = .true. case ('(n,d)') t % score_bins(i) = N_D case ('(n,t)') @@ -839,6 +846,7 @@ contains t % score_bins(i) = N_3HE case ('(n,a)') t % score_bins(i) = N_A + depletion_rx = .true. case ('(n,2a)') t % score_bins(i) = N_2A case ('(n,3a)') @@ -879,6 +887,7 @@ contains end do err = 0 + t % depletion_rx = depletion_rx end associate else err = E_OUT_OF_BOUNDS From 8549f22e1408dd6e87184746b833b82f50e07d66 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 9 Feb 2018 15:42:00 -0600 Subject: [PATCH 207/282] Move depletion tests into normal regression/unit test directories --- tests/deplete_tests/__init__.py | 0 tests/{deplete_tests => }/dummy_geometry.py | 0 .../example_geometry.py | 0 .../test_deplete_full.py} | 0 .../test_deplete_utilities.py} | 0 .../test_reference.h5 | Bin .../test_deplete_atom_number.py} | 0 .../test_deplete_cecm.py} | 2 +- .../test_deplete_cram.py} | 0 .../test_deplete_integrator.py} | 0 .../test_deplete_nuclide.py} | 0 .../test_deplete_predictor.py} | 2 +- .../test_deplete_reaction.py} | 0 .../test_depletion_chain.py | 0 14 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 tests/deplete_tests/__init__.py rename tests/{deplete_tests => }/dummy_geometry.py (100%) rename tests/{deplete_tests => regression_tests}/example_geometry.py (100%) rename tests/{deplete_tests/test_full.py => regression_tests/test_deplete_full.py} (100%) rename tests/{deplete_tests/test_utilities.py => regression_tests/test_deplete_utilities.py} (100%) rename tests/{deplete_tests => regression_tests}/test_reference.h5 (100%) rename tests/{deplete_tests/test_atom_number.py => unit_tests/test_deplete_atom_number.py} (100%) rename tests/{deplete_tests/test_cecm_regression.py => unit_tests/test_deplete_cecm.py} (98%) rename tests/{deplete_tests/test_cram.py => unit_tests/test_deplete_cram.py} (100%) rename tests/{deplete_tests/test_integrator.py => unit_tests/test_deplete_integrator.py} (100%) rename tests/{deplete_tests/test_nuclide.py => unit_tests/test_deplete_nuclide.py} (100%) rename tests/{deplete_tests/test_predictor_regression.py => unit_tests/test_deplete_predictor.py} (98%) rename tests/{deplete_tests/test_reaction_rates.py => unit_tests/test_deplete_reaction.py} (100%) rename tests/{deplete_tests => unit_tests}/test_depletion_chain.py (100%) diff --git a/tests/deplete_tests/__init__.py b/tests/deplete_tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/deplete_tests/dummy_geometry.py b/tests/dummy_geometry.py similarity index 100% rename from tests/deplete_tests/dummy_geometry.py rename to tests/dummy_geometry.py diff --git a/tests/deplete_tests/example_geometry.py b/tests/regression_tests/example_geometry.py similarity index 100% rename from tests/deplete_tests/example_geometry.py rename to tests/regression_tests/example_geometry.py diff --git a/tests/deplete_tests/test_full.py b/tests/regression_tests/test_deplete_full.py similarity index 100% rename from tests/deplete_tests/test_full.py rename to tests/regression_tests/test_deplete_full.py diff --git a/tests/deplete_tests/test_utilities.py b/tests/regression_tests/test_deplete_utilities.py similarity index 100% rename from tests/deplete_tests/test_utilities.py rename to tests/regression_tests/test_deplete_utilities.py diff --git a/tests/deplete_tests/test_reference.h5 b/tests/regression_tests/test_reference.h5 similarity index 100% rename from tests/deplete_tests/test_reference.h5 rename to tests/regression_tests/test_reference.h5 diff --git a/tests/deplete_tests/test_atom_number.py b/tests/unit_tests/test_deplete_atom_number.py similarity index 100% rename from tests/deplete_tests/test_atom_number.py rename to tests/unit_tests/test_deplete_atom_number.py diff --git a/tests/deplete_tests/test_cecm_regression.py b/tests/unit_tests/test_deplete_cecm.py similarity index 98% rename from tests/deplete_tests/test_cecm_regression.py rename to tests/unit_tests/test_deplete_cecm.py index 0d6966c82..34c3435a7 100644 --- a/tests/deplete_tests/test_cecm_regression.py +++ b/tests/unit_tests/test_deplete_cecm.py @@ -8,7 +8,7 @@ import openmc.deplete from openmc.deplete import results from openmc.deplete import utilities -from . import dummy_geometry +from tests import dummy_geometry class TestCECMRegression(unittest.TestCase): diff --git a/tests/deplete_tests/test_cram.py b/tests/unit_tests/test_deplete_cram.py similarity index 100% rename from tests/deplete_tests/test_cram.py rename to tests/unit_tests/test_deplete_cram.py diff --git a/tests/deplete_tests/test_integrator.py b/tests/unit_tests/test_deplete_integrator.py similarity index 100% rename from tests/deplete_tests/test_integrator.py rename to tests/unit_tests/test_deplete_integrator.py diff --git a/tests/deplete_tests/test_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py similarity index 100% rename from tests/deplete_tests/test_nuclide.py rename to tests/unit_tests/test_deplete_nuclide.py diff --git a/tests/deplete_tests/test_predictor_regression.py b/tests/unit_tests/test_deplete_predictor.py similarity index 98% rename from tests/deplete_tests/test_predictor_regression.py rename to tests/unit_tests/test_deplete_predictor.py index a41ca9ce7..6ad2007d9 100644 --- a/tests/deplete_tests/test_predictor_regression.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -8,7 +8,7 @@ import openmc.deplete from openmc.deplete import results from openmc.deplete import utilities -from . import dummy_geometry +from tests import dummy_geometry class TestPredictorRegression(unittest.TestCase): """ Regression tests for opendeplete.integrator.predictor algorithm. diff --git a/tests/deplete_tests/test_reaction_rates.py b/tests/unit_tests/test_deplete_reaction.py similarity index 100% rename from tests/deplete_tests/test_reaction_rates.py rename to tests/unit_tests/test_deplete_reaction.py diff --git a/tests/deplete_tests/test_depletion_chain.py b/tests/unit_tests/test_depletion_chain.py similarity index 100% rename from tests/deplete_tests/test_depletion_chain.py rename to tests/unit_tests/test_depletion_chain.py From 592fae536f31521d09bd68a80d465550e6b34978 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 9 Feb 2018 16:17:12 -0600 Subject: [PATCH 208/282] Fix file locations for depletion tests. Convert regression ones to pytest --- tests/conftest.py | 11 ++ tests/regression_tests/test_deplete_full.py | 156 ++++++++---------- .../test_deplete_utilities.py | 107 +++++------- tests/unit_tests/conftest.py | 9 - tests/unit_tests/test_depletion_chain.py | 10 +- 5 files changed, 133 insertions(+), 160 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 422c036fb..dc55e8e1e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +import pytest + from tests.regression_tests import config as regression_config @@ -15,3 +17,12 @@ def pytest_configure(config): for opt in opts: if config.getoption(opt) is not None: regression_config[opt] = config.getoption(opt) + + +@pytest.fixture +def run_in_tmpdir(tmpdir): + orig = tmpdir.chdir() + try: + yield + finally: + orig.chdir() diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index 88c409554..78039abc7 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -1,121 +1,105 @@ """ Full system test suite. """ +from math import floor import shutil import unittest -from os.path import join, dirname +from pathlib import Path import numpy as np import openmc.deplete from openmc.deplete import results from openmc.deplete import utilities -from . import example_geometry +from .example_geometry import generate_problem -class TestFull(unittest.TestCase): - """ Full system test suite. +def test_full(run_in_tmpdir): + """Full system test suite. Runs an entire OpenMC simulation with depletion coupling and verifies that the outputs match a reference file. Sensitive to changes in OpenMC. + + This test runs a complete OpenMC simulation and tests the outputs. + It will take a while. """ - def test_full(self): - """ - This test runs a complete OpenMC simulation and tests the outputs. - It will take a while. - """ + n_rings = 2 + n_wedges = 4 - n_rings = 2 - n_wedges = 4 + # Load geometry from example + geometry, lower_left, upper_right = generate_problem(n_rings, n_wedges) - # Load geometry from example - geometry, lower_left, upper_right = \ - example_geometry.generate_problem(n_rings=n_rings, n_wedges=n_wedges) + # Create dt vector for 3 steps with 15 day timesteps + dt1 = 15.*24*60*60 # 15 days + dt2 = 1.5*30*24*60*60 # 1.5 months + N = floor(dt2/dt1) + dt = np.full(N, dt1) - # Create dt vector for 3 steps with 15 day timesteps - dt1 = 15*24*60*60 # 15 days - dt2 = 1.5*30*24*60*60 # 1.5 months - N = np.floor(dt2/dt1) + # Create settings variable + settings = openmc.deplete.OpenMCSettings() - dt = np.repeat([dt1], N) - # Create settings variable - settings = openmc.deplete.OpenMCSettings() + chain_file = str(Path(__file__).parents[2] / 'chains' / 'chain_simple.xml') + settings.chain_file = chain_file + settings.openmc_call = "openmc" + settings.openmc_npernode = 2 + settings.particles = 100 + settings.batches = 100 + settings.inactive = 40 + settings.lower_left = lower_left + settings.upper_right = upper_right + settings.entropy_dimension = [10, 10, 1] - settings.chain_file = "chains/chain_simple.xml" - settings.openmc_call = "openmc" - settings.openmc_npernode = 2 - settings.particles = 100 - settings.batches = 100 - settings.inactive = 40 - settings.lower_left = lower_left - settings.upper_right = upper_right - settings.entropy_dimension = [10, 10, 1] + settings.round_number = True + settings.constant_seed = 1 - settings.round_number = True - settings.constant_seed = 1 + joule_per_mev = 1.6021766208e-13 + settings.power = 2.337e15*4*joule_per_mev # MeV/second cm from CASMO + settings.dt_vec = dt + settings.output_dir = "test_full" - joule_per_mev = 1.6021766208e-13 - settings.power = 2.337e15*4*joule_per_mev # MeV/second cm from CASMO - settings.dt_vec = dt - settings.output_dir = "test_full" + op = openmc.deplete.OpenMCOperator(geometry, settings) - op = openmc.deplete.OpenMCOperator(geometry, settings) + # Perform simulation using the predictor algorithm + openmc.deplete.integrator.predictor(op) - # Perform simulation using the predictor algorithm - openmc.deplete.integrator.predictor(op) + # Load the files + res_test = results.read_results(settings.output_dir + "/results.h5") - # Load the files - res_test = results.read_results(settings.output_dir + "/results.h5") + # Load the reference + filename = str(Path(__file__).with_name('test_reference.h5')) + res_old = results.read_results(filename) - # Load the reference - filename = join(dirname(__file__), 'test_reference.h5') - res_old = results.read_results(filename) + # Assert same mats + for mat in res_old[0].mat_to_ind: + assert mat in res_test[0].mat_to_ind, \ + "Material {} not in new results.".format(mat) + for nuc in res_old[0].nuc_to_ind: + assert nuc in res_test[0].nuc_to_ind, \ + "Nuclide {} not in new results.".format(nuc) - # Assert same mats - for mat in res_old[0].mat_to_ind: - self.assertIn(mat, res_test[0].mat_to_ind, - msg="Cell " + mat + " not in new results.") - for nuc in res_old[0].nuc_to_ind: - self.assertIn(nuc, res_test[0].nuc_to_ind, - msg="Nuclide " + nuc + " not in new results.") + for mat in res_test[0].mat_to_ind: + assert mat in res_old[0].mat_to_ind, \ + "Material {} not in old results.".format(mat) + for nuc in res_test[0].nuc_to_ind: + assert nuc in res_old[0].nuc_to_ind, \ + "Nuclide {} not in old results.".format(nuc) - for mat in res_test[0].mat_to_ind: - self.assertIn(mat, res_old[0].mat_to_ind, - msg="Cell " + mat + " not in old results.") + tol = 1.0e-6 + for mat in res_test[0].mat_to_ind: for nuc in res_test[0].nuc_to_ind: - self.assertIn(nuc, res_old[0].nuc_to_ind, - msg="Nuclide " + nuc + " not in old results.") + _, y_test = utilities.evaluate_single_nuclide(res_test, mat, nuc) + _, y_old = utilities.evaluate_single_nuclide(res_old, mat, nuc) - for mat in res_test[0].mat_to_ind: - for nuc in res_test[0].nuc_to_ind: - _, y_test = utilities.evaluate_single_nuclide(res_test, mat, nuc) - _, y_old = utilities.evaluate_single_nuclide(res_old, mat, nuc) + # Test each point + correct = True + for i, ref in enumerate(y_old): + if ref != y_test[i]: + if ref != 0.0: + correct = np.abs(y_test[i] - ref) / ref <= tol + else: + correct = False - # Test each point - - tol = 1.0e-6 - - correct = True - for i, ref in enumerate(y_old): - if ref != y_test[i]: - if ref != 0.0: - if np.abs(y_test[i] - ref) / ref > tol: - correct = False - else: - correct = False - - self.assertTrue(correct, - msg="Discrepancy in mat " + mat + " and nuc " + nuc - + "\n" + str(y_old) + "\n" + str(y_test)) - - def tearDown(self): - """ Clean up files""" - openmc.deplete.comm.barrier() - if openmc.deplete.comm.rank == 0: - shutil.rmtree("test_full", ignore_errors=True) - - -if __name__ == '__main__': - unittest.main() + assert correct, "Discrepancy in mat {} and nuc {}\n{}\n{}".format( + mat, nuc, y_old, y_test) diff --git a/tests/regression_tests/test_deplete_utilities.py b/tests/regression_tests/test_deplete_utilities.py index fb60df5b7..f9d34aa75 100644 --- a/tests/regression_tests/test_deplete_utilities.py +++ b/tests/regression_tests/test_deplete_utilities.py @@ -1,71 +1,54 @@ -""" Full system test suite. """ +""" Tests the utilities classes. -import unittest -from os.path import join, dirname +This also tests the results read/write code. +""" + +from pathlib import Path import numpy as np +import pytest from openmc.deplete import results from openmc.deplete import utilities -class TestUtilities(unittest.TestCase): - """ Tests the utilities classes. - - This also tests the results read/write code. - """ - - def test_evaluate_single_nuclide(self): - """ Tests evaluating single nuclide utility code. - """ - - # Load the reference - filename = join(dirname(__file__), 'test_reference.h5') - res = results.read_results(filename) - - x, y = utilities.evaluate_single_nuclide(res, "1", "Xe135") - - x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] - y_ref = [6.6747328233649218e+08, 3.5519299354458244e+14, - 3.4599104054580338e+14, 3.3821165110278112e+14] - - np.testing.assert_array_equal(x, x_ref) - np.testing.assert_array_equal(y, y_ref) - - def test_evaluate_reaction_rate(self): - """ Tests evaluating reaction rate utility code. - """ - - # Load the reference - filename = join(dirname(__file__), 'test_reference.h5') - res = results.read_results(filename) - - x, y = utilities.evaluate_reaction_rate(res, "1", "Xe135", "(n,gamma)") - - x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] - xe_ref = np.array([6.6747328233649218e+08, 3.5519299354458244e+14, - 3.4599104054580338e+14, 3.3821165110278112e+14]) - r_ref = np.array([4.0643598574337784e-05, 4.1457730544386974e-05, - 3.4121248544056681e-05, 3.9204686657643301e-05]) - - np.testing.assert_array_equal(x, x_ref) - np.testing.assert_array_equal(y, xe_ref * r_ref) - - def test_evaluate_eigenvalue(self): - """ Tests evaluating eigenvalue - """ - - # Load the reference - filename = join(dirname(__file__), 'test_reference.h5') - res = results.read_results(filename) - - x, y = utilities.evaluate_eigenvalue(res) - - x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] - y_ref = [1.1921986054449838, 1.1712785643938586, 1.1927099024502694, 1.2269183590698847] - - np.testing.assert_array_equal(x, x_ref) - np.testing.assert_array_equal(y, y_ref) +@pytest.fixture +def res(): + """Load the reference results""" + filename = str(Path(__file__).with_name('test_reference.h5')) + return results.read_results(filename) -if __name__ == '__main__': - unittest.main() +def test_evaluate_single_nuclide(res): + """Tests evaluating single nuclide utility code.""" + x, y = utilities.evaluate_single_nuclide(res, "1", "Xe135") + + x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] + y_ref = [6.6747328233649218e+08, 3.5519299354458244e+14, + 3.4599104054580338e+14, 3.3821165110278112e+14] + + np.testing.assert_array_equal(x, x_ref) + np.testing.assert_array_equal(y, y_ref) + +def test_evaluate_reaction_rate(res): + """Tests evaluating reaction rate utility code.""" + x, y = utilities.evaluate_reaction_rate(res, "1", "Xe135", "(n,gamma)") + + x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] + xe_ref = np.array([6.6747328233649218e+08, 3.5519299354458244e+14, + 3.4599104054580338e+14, 3.3821165110278112e+14]) + r_ref = np.array([4.0643598574337784e-05, 4.1457730544386974e-05, + 3.4121248544056681e-05, 3.9204686657643301e-05]) + + np.testing.assert_array_equal(x, x_ref) + np.testing.assert_array_equal(y, xe_ref * r_ref) + + +def test_evaluate_eigenvalue(res): + """Tests evaluating eigenvalue.""" + x, y = utilities.evaluate_eigenvalue(res) + + x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] + y_ref = [1.1921986054449838, 1.1712785643938586, 1.1927099024502694, 1.2269183590698847] + + np.testing.assert_array_equal(x, x_ref) + np.testing.assert_array_equal(y, y_ref) diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index d618b85de..1434eaf3b 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -2,15 +2,6 @@ import openmc import pytest -@pytest.fixture -def run_in_tmpdir(tmpdir): - orig = tmpdir.chdir() - try: - yield - finally: - orig.chdir() - - @pytest.fixture(scope='module') def uo2(): m = openmc.Material(material_id=100, name='UO2') diff --git a/tests/unit_tests/test_depletion_chain.py b/tests/unit_tests/test_depletion_chain.py index 06abbba0f..de7180e88 100644 --- a/tests/unit_tests/test_depletion_chain.py +++ b/tests/unit_tests/test_depletion_chain.py @@ -3,11 +3,15 @@ from collections import OrderedDict import os import unittest +from pathlib import Path import numpy as np from openmc.deplete import comm, depletion_chain, reaction_rates, nuclide +_test_filename = str(Path(__file__).parents[2] / 'chains' / 'chain_test.xml') + + class TestDepletionChain(unittest.TestCase): """ Tests for DepletionChain class.""" @@ -38,7 +42,7 @@ class TestDepletionChain(unittest.TestCase): # the components external to depletion_chain.py are simple storage # types. - dep = depletion_chain.DepletionChain.xml_read("chains/chain_test.xml") + dep = depletion_chain.DepletionChain.xml_read(_test_filename) # Basic checks self.assertEqual(dep.n_nuclides, 3) @@ -124,7 +128,7 @@ class TestDepletionChain(unittest.TestCase): chain.nuclides = [A, B, C] chain.xml_write(filename) - original = open('chains/chain_test.xml', 'r').read() + original = open(_test_filename, 'r').read() chain_xml = open(filename, 'r').read() self.assertEqual(original, chain_xml) @@ -134,7 +138,7 @@ class TestDepletionChain(unittest.TestCase): """ Using chain_test, and a dummy reaction rate, compute the matrix. """ # Relies on test_xml_read passing. - dep = depletion_chain.DepletionChain.xml_read("chains/chain_test.xml") + dep = depletion_chain.DepletionChain.xml_read(_test_filename) cell_ind = {"10000": 0, "10001": 1} nuc_ind = {"A": 0, "B": 1, "C": 2} From 2e358a2ca474371298dbbbe954527a3bcd764f78 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 9 Feb 2018 16:48:32 -0600 Subject: [PATCH 209/282] Release resources properly from Operator class --- openmc/deplete/integrator/cecm.py | 3 +++ openmc/deplete/integrator/predictor.py | 3 +++ openmc/deplete/openmc_wrapper.py | 7 ++++--- tests/dummy_geometry.py | 3 +++ tests/unit_tests/test_capi.py | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 4d9baebb2..699ccc203 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -131,3 +131,6 @@ def cecm(operator, print_out=True): # Return to origin os.chdir(dir_home) + + # Release resources + operator.finalize() diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 6c9d538fd..7b41e6649 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -98,3 +98,6 @@ def predictor(operator, print_out=True): # Return to origin os.chdir(dir_home) + + # Release resources + operator.finalize() diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 05b5057d2..5955a5def 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -219,9 +219,6 @@ class OpenMCOperator(Operator): # Create reaction rate tables self.initialize_reaction_rates() - def __del__(self): - openmc.capi.finalize() - def extract_mat_ids(self): """Extracts materials and assigns them to processes. @@ -475,6 +472,10 @@ class OpenMCOperator(Operator): # Return number density vector return self.total_density_list() + 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.""" diff --git a/tests/dummy_geometry.py b/tests/dummy_geometry.py index 614cc726e..ecdce567d 100644 --- a/tests/dummy_geometry.py +++ b/tests/dummy_geometry.py @@ -21,6 +21,9 @@ class DummyGeometry(Operator): def __init__(self, settings): super().__init__(settings) + def finalize(self): + pass + @property def chain(self): return self diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 618f54e4f..8bc6c3d15 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -1,4 +1,4 @@ -from collections import Mapping +from collections.abc import Mapping import os import numpy as np From 3b31892816831c72354ca8de457c4bbb1afa39e0 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 12 Feb 2018 13:34:35 -0600 Subject: [PATCH 210/282] Make sure entropy/UFS mesh index get cleared during finalize --- src/api.F90 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api.F90 b/src/api.F90 index f07e5e38a..d79a32d94 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -121,6 +121,8 @@ 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 From ef99788ff47cd7ff75c06e61f514fc6fe516f913 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 14 Feb 2018 06:37:17 -0600 Subject: [PATCH 211/282] Get rid of OpenMCSettings.openmc_call, which doesn't make sense anymore --- openmc/deplete/openmc_wrapper.py | 3 --- scripts/example_run.py | 3 --- tests/regression_tests/test_deplete_full.py | 2 -- 3 files changed, 8 deletions(-) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 5955a5def..66de945cd 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -58,8 +58,6 @@ class OpenMCSettings(Settings): chain_file : str Path to the depletion chain xml file. Defaults to the environment variable "OPENDEPLETE_CHAIN" if it exists. - openmc_call : str - OpenMC executable path. Defaults to "openmc". particles : int Number of particles to simulate per batch. batches : int @@ -94,7 +92,6 @@ class OpenMCSettings(Settings): self.chain_file = os.environ["OPENDEPLETE_CHAIN"] except KeyError: self.chain_file = None - self.openmc_call = "openmc" self.particles = None self.batches = None self.inactive = None diff --git a/scripts/example_run.py b/scripts/example_run.py index 82d0883c3..78d7dcedd 100644 --- a/scripts/example_run.py +++ b/scripts/example_run.py @@ -18,9 +18,6 @@ dt = np.repeat([dt1], N) # Create settings variable settings = openmc.deplete.OpenMCSettings() -settings.openmc_call = "openmc" -# An example for mpiexec: -# settings.openmc_call = ["mpiexec", "openmc"] settings.particles = 1000 settings.batches = 100 settings.inactive = 40 diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index 78039abc7..dda1501fb 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -42,8 +42,6 @@ def test_full(run_in_tmpdir): chain_file = str(Path(__file__).parents[2] / 'chains' / 'chain_simple.xml') settings.chain_file = chain_file - settings.openmc_call = "openmc" - settings.openmc_npernode = 2 settings.particles = 100 settings.batches = 100 settings.inactive = 40 From 26852f79f478a67a45ff790a8af2502f01116233 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 14 Feb 2018 07:22:16 -0600 Subject: [PATCH 212/282] Add tqdm to dependencies. Install mpi4py on Travis --- setup.py | 2 +- tools/ci/travis-install.sh | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2a42dd65d..ee11f414b 100755 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ kwargs = { # Required dependencies 'install_requires': [ 'numpy>=1.9', 'h5py', 'scipy', 'ipython', 'matplotlib', - 'pandas', 'lxml', 'uncertainties' + 'pandas', 'lxml', 'uncertainties', 'tqdm' ], # Optional dependencies diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index 4921534db..2342cdadb 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -14,10 +14,15 @@ pip install cython pip install --upgrade pytest # Pandas stopped supporting Python 3.4 with version 0.21 -if [[ "$TRAVIS_PYTHON_VERSION" == "3.4" ]]; then +if [[ $TRAVIS_PYTHON_VERSION == "3.4" ]]; then pip install pandas==0.20.3 fi +# Install mpi4py for MPI configurations +if [[ $MPI == 'y' ]]; then + pip install --no-binary=mpi4py mpi4py +fi + # Build and install OpenMC executable python tools/ci/travis-install.py From 43147b70eb62ce983443ee3f17ac7bee49b6dd60 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 14 Feb 2018 10:17:30 -0600 Subject: [PATCH 213/282] Change xml_write -> export_to_xml, xml_read -> from_xml for consistency --- openmc/deplete/depletion_chain.py | 8 ++++---- openmc/deplete/nuclide.py | 4 ++-- openmc/deplete/openmc_wrapper.py | 2 +- scripts/make_chain.py | 2 +- tests/unit_tests/test_deplete_nuclide.py | 8 ++++---- tests/unit_tests/test_depletion_chain.py | 15 ++++++++------- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/openmc/deplete/depletion_chain.py b/openmc/deplete/depletion_chain.py index af126035f..9f6b7cfed 100644 --- a/openmc/deplete/depletion_chain.py +++ b/openmc/deplete/depletion_chain.py @@ -317,7 +317,7 @@ class DepletionChain(object): return depl_chain @classmethod - def xml_read(cls, filename): + def from_xml(cls, filename): """Reads a depletion chain XML file. Parameters @@ -343,7 +343,7 @@ class DepletionChain(object): reaction_index = 0 for i, nuclide_elem in enumerate(root.findall('nuclide_table')): - nuc = Nuclide.xml_read(nuclide_elem) + nuc = Nuclide.from_xml(nuclide_elem) depl_chain.nuclide_dict[nuc.name] = i # Check for reaction paths @@ -356,7 +356,7 @@ class DepletionChain(object): return depl_chain - def xml_write(self, filename): + def export_to_xml(self, filename): """Writes a depletion chain XML file. Parameters @@ -368,7 +368,7 @@ class DepletionChain(object): root_elem = ET.Element('depletion') for nuclide in self.nuclides: - root_elem.append(nuclide.xml_write()) + root_elem.append(nuclide.to_xml_element()) tree = ET.ElementTree(root_elem) if _have_lxml: diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 1208a9b3c..17cf4d9b8 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -71,7 +71,7 @@ class Nuclide(object): return len(self.reactions) @classmethod - def xml_read(cls, element): + def from_xml(cls, element): """Read nuclide from an XML element. Parameters @@ -129,7 +129,7 @@ class Nuclide(object): return nuc - def xml_write(self): + def to_xml_element(self): """Write nuclide to XML element. Returns diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 66de945cd..88b497122 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -186,7 +186,7 @@ class OpenMCOperator(Operator): self.burn_nuc_to_ind = None # Read depletion chain - self.chain = DepletionChain.xml_read(settings.chain_file) + self.chain = DepletionChain.from_xml(settings.chain_file) # Clear out OpenMC, create task lists, distribute if comm.rank == 0: diff --git a/scripts/make_chain.py b/scripts/make_chain.py index ccf4ef9b7..e2b0a2340 100644 --- a/scripts/make_chain.py +++ b/scripts/make_chain.py @@ -53,7 +53,7 @@ def main(): neutron_files = glob.glob(os.path.join('neutrons', '*.endf')) chain = openmc.deplete.DepletionChain.from_endf(decay_files, nfy_files, neutron_files) - chain.xml_write('chain_endfb71.xml') + chain.export_to_xml('chain_endfb71.xml') if __name__ == '__main__': diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index 2d379030d..ebcabc5ca 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -35,7 +35,7 @@ class TestNuclide(unittest.TestCase): self.assertEqual(nuc.n_reaction_paths, 3) - def test_xml_read(self): + def test_from_xml(self): """Test reading nuclide data from an XML element.""" data = """ @@ -58,7 +58,7 @@ class TestNuclide(unittest.TestCase): """ element = ET.fromstring(data) - u235 = nuclide.Nuclide.xml_read(element) + u235 = nuclide.Nuclide.from_xml(element) self.assertEqual(u235.decay_modes, [ nuclide.DecayTuple('sf', 'U235', 7.2e-11), @@ -77,7 +77,7 @@ class TestNuclide(unittest.TestCase): ('Xe138', 0.0481413)] }) - def test_xml_write(self): + def test_to_xml_element(self): """Test writing nuclide data to an XML element.""" C = nuclide.Nuclide() @@ -93,7 +93,7 @@ class TestNuclide(unittest.TestCase): ] C.yield_energies = [0.0253] C.yield_data = {0.0253: [("A", 0.0292737), ("B", 0.002566345)]} - element = C.xml_write() + element = C.to_xml_element() self.assertEqual(element.get("half_life"), "0.123") diff --git a/tests/unit_tests/test_depletion_chain.py b/tests/unit_tests/test_depletion_chain.py index de7180e88..ba9e32db4 100644 --- a/tests/unit_tests/test_depletion_chain.py +++ b/tests/unit_tests/test_depletion_chain.py @@ -36,13 +36,13 @@ class TestDepletionChain(unittest.TestCase): out a good way to unit-test this.""" pass - def test_xml_read(self): + def test_from_xml(self): """ Read chain_test.xml and ensure all values are correct. """ # Unfortunately, this routine touches a lot of the code, but most of # the components external to depletion_chain.py are simple storage # types. - dep = depletion_chain.DepletionChain.xml_read(_test_filename) + dep = depletion_chain.DepletionChain.from_xml(_test_filename) # Basic checks self.assertEqual(dep.n_nuclides, 3) @@ -93,11 +93,11 @@ class TestDepletionChain(unittest.TestCase): self.assertEqual(nuc.yield_data[0.0253], [("A", 0.0292737), ("B", 0.002566345)]) - def test_xml_write(self): + def test_export_to_xml(self): """Test writing a depletion chain to XML.""" # Prevent different MPI ranks from conflicting - filename = 'test%u.xml' % comm.rank + filename = 'test{}.xml'.format(comm.rank) A = nuclide.Nuclide() A.name = "A" @@ -126,7 +126,7 @@ class TestDepletionChain(unittest.TestCase): chain = depletion_chain.DepletionChain() chain.nuclides = [A, B, C] - chain.xml_write(filename) + chain.export_to_xml(filename) original = open(_test_filename, 'r').read() chain_xml = open(filename, 'r').read() @@ -136,9 +136,9 @@ class TestDepletionChain(unittest.TestCase): def test_form_matrix(self): """ Using chain_test, and a dummy reaction rate, compute the matrix. """ - # Relies on test_xml_read passing. + # Relies on test_from_xml passing. - dep = depletion_chain.DepletionChain.xml_read(_test_filename) + dep = depletion_chain.DepletionChain.from_xml(_test_filename) cell_ind = {"10000": 0, "10001": 1} nuc_ind = {"A": 0, "B": 1, "C": 2} @@ -196,5 +196,6 @@ class TestDepletionChain(unittest.TestCase): self.assertEqual("NucB", dep.nuc_by_ind("NucB")) self.assertEqual("NucC", dep.nuc_by_ind("NucC")) + if __name__ == '__main__': unittest.main() From 1ee27edc8c935f48894dbefb37bfe48f2db06583 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 14 Feb 2018 10:45:39 -0600 Subject: [PATCH 214/282] Rename DepletionChain -> Chain --- docs/source/pythonapi/deplete/index.rst | 24 +++++++-------- .../pythonapi/deplete/integrator.CRAM16.rst | 2 +- .../pythonapi/deplete/integrator.CRAM48.rst | 2 +- .../pythonapi/deplete/integrator.cecm.rst | 2 +- .../deplete/integrator.predictor.rst | 2 +- .../deplete/integrator.save_results.rst | 2 +- .../deplete/opendeplete.Concentrations.rst | 30 ------------------- .../deplete/opendeplete.ReactionRates.rst | 30 ------------------- .../pythonapi/deplete/opendeplete.Results.rst | 22 -------------- openmc/data/data.py | 3 +- openmc/deplete/__init__.py | 2 +- .../deplete/{depletion_chain.py => chain.py} | 8 ++--- openmc/deplete/openmc_wrapper.py | 20 ++++++------- scripts/make_chain.py | 2 +- ...pletion_chain.py => test_deplete_chain.py} | 20 ++++++------- 15 files changed, 44 insertions(+), 127 deletions(-) delete mode 100644 docs/source/pythonapi/deplete/opendeplete.Concentrations.rst delete mode 100644 docs/source/pythonapi/deplete/opendeplete.ReactionRates.rst delete mode 100644 docs/source/pythonapi/deplete/opendeplete.Results.rst rename openmc/deplete/{depletion_chain.py => chain.py} (99%) rename tests/unit_tests/{test_depletion_chain.py => test_deplete_chain.py} (92%) diff --git a/docs/source/pythonapi/deplete/index.rst b/docs/source/pythonapi/deplete/index.rst index 55380c7a1..30d2d4261 100644 --- a/docs/source/pythonapi/deplete/index.rst +++ b/docs/source/pythonapi/deplete/index.rst @@ -17,7 +17,7 @@ Integrator Helper Functions --------------------------- .. toctree:: :maxdepth: 2 - + integrator.CRAM16 integrator.CRAM48 integrator.save_results @@ -29,8 +29,8 @@ Metaclasses :toctree: generated :nosignatures: - opendeplete.Settings - opendeplete.Operator + openmc.deplete.Settings + openmc.deplete.Operator OpenMC Classes -------------- @@ -39,18 +39,18 @@ OpenMC Classes :toctree: generated :nosignatures: - opendeplete.OpenMCSettings - opendeplete.Materials - opendeplete.OpenMCOperator + openmc.deplete.OpenMCSettings + openmc.deplete.Materials + openmc.deplete.OpenMCOperator Data Classes ------------ .. autosummary:: :toctree: generated :nosignatures: - - opendeplete.AtomNumber - opendeplete.DepletionChain - opendeplete.Nuclide - opendeplete.ReactionRates - opendeplete.Results + + openmc.deplete.AtomNumber + openmc.deplete.Chain + openmc.deplete.Nuclide + openmc.deplete.ReactionRates + openmc.deplete.Results diff --git a/docs/source/pythonapi/deplete/integrator.CRAM16.rst b/docs/source/pythonapi/deplete/integrator.CRAM16.rst index f9eba273e..a0dc64805 100644 --- a/docs/source/pythonapi/deplete/integrator.CRAM16.rst +++ b/docs/source/pythonapi/deplete/integrator.CRAM16.rst @@ -1,6 +1,6 @@ integrator\.CRAM16 ================== -.. currentmodule:: opendeplete.integrator +.. currentmodule:: openmc.deplete.integrator .. autofunction:: CRAM16 diff --git a/docs/source/pythonapi/deplete/integrator.CRAM48.rst b/docs/source/pythonapi/deplete/integrator.CRAM48.rst index d7467a418..f9720f7ad 100644 --- a/docs/source/pythonapi/deplete/integrator.CRAM48.rst +++ b/docs/source/pythonapi/deplete/integrator.CRAM48.rst @@ -1,6 +1,6 @@ integrator\.CRAM48 ================== -.. currentmodule:: opendeplete.integrator +.. currentmodule:: openmc.deplete.integrator .. autofunction:: CRAM48 diff --git a/docs/source/pythonapi/deplete/integrator.cecm.rst b/docs/source/pythonapi/deplete/integrator.cecm.rst index 507a638f6..4851b20b3 100644 --- a/docs/source/pythonapi/deplete/integrator.cecm.rst +++ b/docs/source/pythonapi/deplete/integrator.cecm.rst @@ -1,6 +1,6 @@ integrator\.cecm ================= -.. currentmodule:: opendeplete.integrator +.. currentmodule:: openmc.deplete.integrator .. autofunction:: cecm diff --git a/docs/source/pythonapi/deplete/integrator.predictor.rst b/docs/source/pythonapi/deplete/integrator.predictor.rst index d6c0fd827..2243e77f7 100644 --- a/docs/source/pythonapi/deplete/integrator.predictor.rst +++ b/docs/source/pythonapi/deplete/integrator.predictor.rst @@ -1,6 +1,6 @@ integrator\.predictor ===================== -.. currentmodule:: opendeplete.integrator +.. currentmodule:: openmc.deplete.integrator .. autofunction:: predictor diff --git a/docs/source/pythonapi/deplete/integrator.save_results.rst b/docs/source/pythonapi/deplete/integrator.save_results.rst index 5c21dcb66..f9c830cd5 100644 --- a/docs/source/pythonapi/deplete/integrator.save_results.rst +++ b/docs/source/pythonapi/deplete/integrator.save_results.rst @@ -1,6 +1,6 @@ integrator\.save_results ======================== -.. currentmodule:: opendeplete.integrator +.. currentmodule:: openmc.deplete.integrator .. autofunction:: save_results diff --git a/docs/source/pythonapi/deplete/opendeplete.Concentrations.rst b/docs/source/pythonapi/deplete/opendeplete.Concentrations.rst deleted file mode 100644 index 6fa07a970..000000000 --- a/docs/source/pythonapi/deplete/opendeplete.Concentrations.rst +++ /dev/null @@ -1,30 +0,0 @@ -opendeplete.Concentrations -========================== - -.. currentmodule:: opendeplete - -.. autoclass:: Concentrations - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Concentrations.__init__ - ~Concentrations.convert_nested_dict - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Concentrations.n_cell - ~Concentrations.n_nuc - - \ No newline at end of file diff --git a/docs/source/pythonapi/deplete/opendeplete.ReactionRates.rst b/docs/source/pythonapi/deplete/opendeplete.ReactionRates.rst deleted file mode 100644 index 99e048b56..000000000 --- a/docs/source/pythonapi/deplete/opendeplete.ReactionRates.rst +++ /dev/null @@ -1,30 +0,0 @@ -opendeplete.ReactionRates -========================= - -.. currentmodule:: opendeplete - -.. autoclass:: ReactionRates - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~ReactionRates.__init__ - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~ReactionRates.n_cell - ~ReactionRates.n_nuc - ~ReactionRates.n_react - - \ No newline at end of file diff --git a/docs/source/pythonapi/deplete/opendeplete.Results.rst b/docs/source/pythonapi/deplete/opendeplete.Results.rst deleted file mode 100644 index 0ab8a1f71..000000000 --- a/docs/source/pythonapi/deplete/opendeplete.Results.rst +++ /dev/null @@ -1,22 +0,0 @@ -opendeplete.Results -=================== - -.. currentmodule:: opendeplete - -.. autoclass:: Results - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Results.__init__ - - - - - - \ No newline at end of file diff --git a/openmc/data/data.py b/openmc/data/data.py index a7c0e536f..523ac9769 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -319,8 +319,9 @@ def water_density(temperature, pressure=0.1013): # 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 diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index 4bdde3935..19d1d1320 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -15,7 +15,7 @@ except ImportError: have_mpi = False from .nuclide import * -from .depletion_chain import * +from .chain import * from .openmc_wrapper import * from .reaction_rates import * from .function import * diff --git a/openmc/deplete/depletion_chain.py b/openmc/deplete/chain.py similarity index 99% rename from openmc/deplete/depletion_chain.py rename to openmc/deplete/chain.py index 9f6b7cfed..03decb72a 100644 --- a/openmc/deplete/depletion_chain.py +++ b/openmc/deplete/chain.py @@ -1,4 +1,4 @@ -"""depletion_chain module. +"""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. @@ -108,10 +108,8 @@ def replace_missing(product, decay_data): return product -class DepletionChain(object): - """The DepletionChain class. - - This class contains a full representation of a depletion chain. +class Chain(object): + """Full representation of a depletion chain. Attributes ---------- diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 88b497122..5c7a1aeba 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -21,14 +21,14 @@ import numpy as np import openmc import openmc.capi +from openmc.data import JOULE_PER_EV from . import comm from .atom_number import AtomNumber -from .depletion_chain import DepletionChain +from .chain import Chain from .reaction_rates import ReactionRates from .function import Settings, Operator -_JOULE_PER_EV = 1.6021766208e-19 def chunks(items, n): @@ -149,13 +149,13 @@ class OpenMCOperator(Operator): Materials to be used for this simulation. seed : int The RNG seed used in last OpenMC run. - number : AtomNumber + number : openmc.deplete.AtomNumber Total number of atoms in simulation. participating_nuclides : set of str A set listing all unique nuclides available from cross_sections.xml. - chain : DepletionChain + chain : openmc.deplete.Chain The depletion chain information necessary to form matrices and tallies. - reaction_rates : ReactionRates + reaction_rates : openmc.deplete.ReactionRates Reaction rates from the last operator step. power : OrderedDict of str to float Material-by-Material power. Indexed by material ID. @@ -186,7 +186,7 @@ class OpenMCOperator(Operator): self.burn_nuc_to_ind = None # Read depletion chain - self.chain = DepletionChain.from_xml(settings.chain_file) + self.chain = Chain.from_xml(settings.chain_file) # Clear out OpenMC, create task lists, distribute if comm.rank == 0: @@ -390,7 +390,7 @@ class OpenMCOperator(Operator): Matrices for the next step. k : float Eigenvalue of the problem. - rates : ReactionRates + rates : openmc.deplete.ReactionRates Reaction rates from this simulation. seed : int Seed for this simulation. @@ -602,11 +602,11 @@ class OpenMCOperator(Operator): def generate_tallies(self): """Generates depletion tallies. - Using information from self.depletion_chain as well as the nuclides + 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.mat_tally_ind] @@ -742,7 +742,7 @@ class OpenMCOperator(Operator): energy = comm.allreduce(energy) # Determine power in eV/s - power = self.settings.power / _JOULE_PER_EV + power = self.settings.power / JOULE_PER_EV # Scale reaction rates to obtain units of reactions/sec rates[:, :, :] *= power / energy diff --git a/scripts/make_chain.py b/scripts/make_chain.py index e2b0a2340..6d64f34b3 100644 --- a/scripts/make_chain.py +++ b/scripts/make_chain.py @@ -52,7 +52,7 @@ def main(): nfy_files = glob.glob(os.path.join('nfy', '*.endf')) neutron_files = glob.glob(os.path.join('neutrons', '*.endf')) - chain = openmc.deplete.DepletionChain.from_endf(decay_files, nfy_files, neutron_files) + chain = openmc.deplete.Chain.from_endf(decay_files, nfy_files, neutron_files) chain.export_to_xml('chain_endfb71.xml') diff --git a/tests/unit_tests/test_depletion_chain.py b/tests/unit_tests/test_deplete_chain.py similarity index 92% rename from tests/unit_tests/test_depletion_chain.py rename to tests/unit_tests/test_deplete_chain.py index ba9e32db4..6166f1561 100644 --- a/tests/unit_tests/test_depletion_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -1,4 +1,4 @@ -""" Tests for depletion_chain.py""" +"""Tests for depletion chains""" from collections import OrderedDict import os @@ -6,18 +6,18 @@ import unittest from pathlib import Path import numpy as np -from openmc.deplete import comm, depletion_chain, reaction_rates, nuclide +from openmc.deplete import comm, Chain, reaction_rates, nuclide _test_filename = str(Path(__file__).parents[2] / 'chains' / 'chain_test.xml') -class TestDepletionChain(unittest.TestCase): - """ Tests for DepletionChain class.""" +class TestChain(unittest.TestCase): + """ Tests for Chain class.""" def test__init__(self): """ Test depletion chain initialization.""" - dep = depletion_chain.DepletionChain() + dep = Chain() self.assertIsInstance(dep.nuclides, list) self.assertIsInstance(dep.nuclide_dict, OrderedDict) @@ -25,7 +25,7 @@ class TestDepletionChain(unittest.TestCase): def test_n_nuclides(self): """ Test depletion chain n_nuclides parameter. """ - dep = depletion_chain.DepletionChain() + dep = Chain() dep.nuclides = ["NucA", "NucB", "NucC"] @@ -42,7 +42,7 @@ class TestDepletionChain(unittest.TestCase): # the components external to depletion_chain.py are simple storage # types. - dep = depletion_chain.DepletionChain.from_xml(_test_filename) + dep = Chain.from_xml(_test_filename) # Basic checks self.assertEqual(dep.n_nuclides, 3) @@ -124,7 +124,7 @@ class TestDepletionChain(unittest.TestCase): C.yield_energies = [0.0253] C.yield_data = {0.0253: [("A", 0.0292737), ("B", 0.002566345)]} - chain = depletion_chain.DepletionChain() + chain = Chain() chain.nuclides = [A, B, C] chain.export_to_xml(filename) @@ -138,7 +138,7 @@ class TestDepletionChain(unittest.TestCase): """ Using chain_test, and a dummy reaction rate, compute the matrix. """ # Relies on test_from_xml passing. - dep = depletion_chain.DepletionChain.from_xml(_test_filename) + dep = Chain.from_xml(_test_filename) cell_ind = {"10000": 0, "10001": 1} nuc_ind = {"A": 0, "B": 1, "C": 2} @@ -187,7 +187,7 @@ class TestDepletionChain(unittest.TestCase): def test_nuc_by_ind(self): """ Test nuc_by_ind converter function. """ - dep = depletion_chain.DepletionChain() + dep = Chain() dep.nuclides = ["NucA", "NucB", "NucC"] dep.nuclide_dict = {"NucA" : 0, "NucB" : 1, "NucC" : 2} From dba919c6109009670cc41decae356588c47c305f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 14 Feb 2018 11:24:24 -0600 Subject: [PATCH 215/282] Convert deplete unit tests to use pytest --- openmc/deplete/integrator/cram.py | 4 +- openmc/deplete/results.py | 2 +- tests/regression_tests/test_deplete_full.py | 1 - tests/unit_tests/test_deplete_atom_number.py | 321 ++++++++-------- tests/unit_tests/test_deplete_cecm.py | 75 ++-- tests/unit_tests/test_deplete_chain.py | 367 +++++++++---------- tests/unit_tests/test_deplete_cram.py | 61 ++- tests/unit_tests/test_deplete_integrator.py | 156 ++++---- tests/unit_tests/test_deplete_nuclide.py | 165 ++++----- tests/unit_tests/test_deplete_predictor.py | 74 ++-- tests/unit_tests/test_deplete_reaction.py | 140 ++++--- 11 files changed, 631 insertions(+), 735 deletions(-) diff --git a/openmc/deplete/integrator/cram.py b/openmc/deplete/integrator/cram.py index a18d8450c..56476384c 100644 --- a/openmc/deplete/integrator/cram.py +++ b/openmc/deplete/integrator/cram.py @@ -1,6 +1,6 @@ -""" Chebyshev Rational Approximation Method module +"""Chebyshev Rational Approximation Method module -Implements two different forms of CRAM for use in opendeplete. +Implements two different forms of CRAM for use in openmc.deplete. """ import numpy as np diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index ae096b8ce..c08b3ef40 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -16,7 +16,7 @@ RESULTS_VERSION = 2 class Results(object): - """Contains output of opendeplete. + """Contains output of a depletion run. Attributes ---------- diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index dda1501fb..5f4af7a73 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -2,7 +2,6 @@ from math import floor import shutil -import unittest from pathlib import Path import numpy as np diff --git a/tests/unit_tests/test_deplete_atom_number.py b/tests/unit_tests/test_deplete_atom_number.py index d36b96d38..e3eb22aa5 100644 --- a/tests/unit_tests/test_deplete_atom_number.py +++ b/tests/unit_tests/test_deplete_atom_number.py @@ -1,180 +1,177 @@ -""" Tests for atom_number.py. """ - -import unittest +""" Tests for the AtomNumber class """ import numpy as np from openmc.deplete import atom_number -class TestAtomNumber(unittest.TestCase): - """Tests for the AtomNumber class.""" +def test_indexing(): + """Tests the __getitem__ and __setitem__ routines simultaneously.""" - def test_indexing(self): - """Tests the __getitem__ and __setitem__ routines simultaneously.""" + mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} + nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} - mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} - nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} - volume = {"10000" : 0.38, "10001" : 0.21} + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + number["10000", "U238"] = 1.0 + number["10001", "U238"] = 2.0 + number["10000", "U235"] = 3.0 + number["10001", "U235"] = 4.0 - number["10000", "U238"] = 1.0 - number["10001", "U238"] = 2.0 - number["10000", "U235"] = 3.0 - number["10001", "U235"] = 4.0 + # String indexing + assert number["10000", "U238"] == 1.0 + assert number["10001", "U238"] == 2.0 + assert number["10000", "U235"] == 3.0 + assert number["10001", "U235"] == 4.0 - # String indexing - self.assertEqual(number["10000", "U238"], 1.0) - self.assertEqual(number["10001", "U238"], 2.0) - self.assertEqual(number["10000", "U235"], 3.0) - self.assertEqual(number["10001", "U235"], 4.0) + # Int indexing + assert number[0, 0] == 1.0 + assert number[1, 0] == 2.0 + assert number[0, 1] == 3.0 + assert number[1, 1] == 4.0 - # Int indexing - self.assertEqual(number[0, 0], 1.0) - self.assertEqual(number[1, 0], 2.0) - self.assertEqual(number[0, 1], 3.0) - self.assertEqual(number[1, 1], 4.0) + number[0, 0] = 5.0 - number[0, 0] = 5.0 - - self.assertEqual(number[0, 0], 5.0) - self.assertEqual(number["10000", "U238"], 5.0) - - def test_n_mat(self): - """Test number of materials property. """ - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - volume = {"10000" : 0.38, "10001" : 0.21} - - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) - - self.assertEqual(number.n_mat, 2) - - def test_n_nuc(self): - """Test number of nuclides property.""" - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - volume = {"10000" : 0.38, "10001" : 0.21} - - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) - - self.assertEqual(number.n_nuc, 3) - - def test_burn_nuc_list(self): - """Test the list of burned nuclides property""" - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - volume = {"10000" : 0.38, "10001" : 0.21} - - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) - - self.assertEqual(number.burn_nuc_list, ["U238", "U235"]) - - def test_burn_mat_list(self): - """Test the list of burned nuclides property""" - mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - volume = {"10000" : 0.38, "10001" : 0.21} - - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) - - self.assertEqual(number.burn_mat_list, ["10000", "10001"]) - - def test_density_indexing(self): - """Tests the get and set_atom_density routines simultaneously.""" - - mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} - nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} - volume = {"10000" : 0.38, "10001" : 0.21} - - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) - - number.set_atom_density("10000", "U238", 1.0) - number.set_atom_density("10001", "U238", 2.0) - number.set_atom_density("10002", "U238", 3.0) - number.set_atom_density("10000", "U235", 4.0) - number.set_atom_density("10001", "U235", 5.0) - number.set_atom_density("10002", "U235", 6.0) - number.set_atom_density("10000", "U234", 7.0) - number.set_atom_density("10001", "U234", 8.0) - number.set_atom_density("10002", "U234", 9.0) - - # String indexing - self.assertEqual(number.get_atom_density("10000", "U238"), 1.0) - self.assertEqual(number.get_atom_density("10001", "U238"), 2.0) - self.assertEqual(number.get_atom_density("10002", "U238"), 3.0) - self.assertEqual(number.get_atom_density("10000", "U235"), 4.0) - self.assertEqual(number.get_atom_density("10001", "U235"), 5.0) - self.assertEqual(number.get_atom_density("10002", "U235"), 6.0) - self.assertEqual(number.get_atom_density("10000", "U234"), 7.0) - self.assertEqual(number.get_atom_density("10001", "U234"), 8.0) - self.assertEqual(number.get_atom_density("10002", "U234"), 9.0) - - # Int indexing - self.assertEqual(number.get_atom_density(0, 0), 1.0) - self.assertEqual(number.get_atom_density(1, 0), 2.0) - self.assertEqual(number.get_atom_density(2, 0), 3.0) - self.assertEqual(number.get_atom_density(0, 1), 4.0) - self.assertEqual(number.get_atom_density(1, 1), 5.0) - self.assertEqual(number.get_atom_density(2, 1), 6.0) - self.assertEqual(number.get_atom_density(0, 2), 7.0) - self.assertEqual(number.get_atom_density(1, 2), 8.0) - self.assertEqual(number.get_atom_density(2, 2), 9.0) + assert number[0, 0] == 5.0 + assert number["10000", "U238"] == 5.0 - number.set_atom_density(0, 0, 5.0) +def test_n_mat(): + """Test number of materials property. """ + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} - self.assertEqual(number.get_atom_density(0, 0), 5.0) + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) - # Verify volume is used correctly - self.assertEqual(number[0, 0], 5.0 * 0.38) - self.assertEqual(number[1, 0], 2.0 * 0.21) - self.assertEqual(number[2, 0], 3.0 * 1.0) - self.assertEqual(number[0, 1], 4.0 * 0.38) - self.assertEqual(number[1, 1], 5.0 * 0.21) - self.assertEqual(number[2, 1], 6.0 * 1.0) - self.assertEqual(number[0, 2], 7.0 * 0.38) - self.assertEqual(number[1, 2], 8.0 * 0.21) - self.assertEqual(number[2, 2], 9.0 * 1.0) - - def test_get_mat_slice(self): - """Tests getting slices.""" - - mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} - nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} - volume = {"10000" : 0.38, "10001" : 0.21} - - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) - - number.number = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]) - - sl = number.get_mat_slice(0) - - np.testing.assert_array_equal(sl, np.array([1.0, 2.0])) - - sl = number.get_mat_slice("10000") - - np.testing.assert_array_equal(sl, np.array([1.0, 2.0])) - - def test_set_mat_slice(self): - """Tests getting slices.""" - - mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} - nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} - volume = {"10000" : 0.38, "10001" : 0.21} - - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) - - number.set_mat_slice(0, [1.0, 2.0]) - - self.assertEqual(number[0, 0], 1.0) - self.assertEqual(number[0, 1], 2.0) - - number.set_mat_slice("10000", [3.0, 4.0]) - - self.assertEqual(number[0, 0], 3.0) - self.assertEqual(number[0, 1], 4.0) + assert number.n_mat == 2 -if __name__ == '__main__': - unittest.main() +def test_n_nuc(): + """Test number of nuclides property.""" + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + assert number.n_nuc == 3 + + +def test_burn_nuc_list(): + """Test the list of burned nuclides property""" + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + assert number.burn_nuc_list == ["U238", "U235"] + + +def test_burn_mat_list(): + """Test the list of burned nuclides property""" + mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + assert number.burn_mat_list == ["10000", "10001"] + + +def test_density_indexing(): + """Tests the get and set_atom_density routines simultaneously.""" + + mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} + nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + number.set_atom_density("10000", "U238", 1.0) + number.set_atom_density("10001", "U238", 2.0) + number.set_atom_density("10002", "U238", 3.0) + number.set_atom_density("10000", "U235", 4.0) + number.set_atom_density("10001", "U235", 5.0) + number.set_atom_density("10002", "U235", 6.0) + number.set_atom_density("10000", "U234", 7.0) + number.set_atom_density("10001", "U234", 8.0) + number.set_atom_density("10002", "U234", 9.0) + + # String indexing + assert number.get_atom_density("10000", "U238") == 1.0 + assert number.get_atom_density("10001", "U238") == 2.0 + assert number.get_atom_density("10002", "U238") == 3.0 + assert number.get_atom_density("10000", "U235") == 4.0 + assert number.get_atom_density("10001", "U235") == 5.0 + assert number.get_atom_density("10002", "U235") == 6.0 + assert number.get_atom_density("10000", "U234") == 7.0 + assert number.get_atom_density("10001", "U234") == 8.0 + assert number.get_atom_density("10002", "U234") == 9.0 + + # Int indexing + assert number.get_atom_density(0, 0) == 1.0 + assert number.get_atom_density(1, 0) == 2.0 + assert number.get_atom_density(2, 0) == 3.0 + assert number.get_atom_density(0, 1) == 4.0 + assert number.get_atom_density(1, 1) == 5.0 + assert number.get_atom_density(2, 1) == 6.0 + assert number.get_atom_density(0, 2) == 7.0 + assert number.get_atom_density(1, 2) == 8.0 + assert number.get_atom_density(2, 2) == 9.0 + + + number.set_atom_density(0, 0, 5.0) + assert number.get_atom_density(0, 0) == 5.0 + + # Verify volume is used correctly + assert number[0, 0] == 5.0 * 0.38 + assert number[1, 0] == 2.0 * 0.21 + assert number[2, 0] == 3.0 * 1.0 + assert number[0, 1] == 4.0 * 0.38 + assert number[1, 1] == 5.0 * 0.21 + assert number[2, 1] == 6.0 * 1.0 + assert number[0, 2] == 7.0 * 0.38 + assert number[1, 2] == 8.0 * 0.21 + assert number[2, 2] == 9.0 * 1.0 + + +def test_get_mat_slice(): + """Tests getting slices.""" + + mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} + nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + number.number = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]) + + sl = number.get_mat_slice(0) + + np.testing.assert_array_equal(sl, np.array([1.0, 2.0])) + + sl = number.get_mat_slice("10000") + + np.testing.assert_array_equal(sl, np.array([1.0, 2.0])) + + +def test_set_mat_slice(): + """Tests getting slices.""" + + mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} + nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + volume = {"10000" : 0.38, "10001" : 0.21} + + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + + number.set_mat_slice(0, [1.0, 2.0]) + + assert number[0, 0] == 1.0 + assert number[0, 1] == 2.0 + + number.set_mat_slice("10000", [3.0, 4.0]) + + assert number[0, 0] == 3.0 + assert number[0, 1] == 4.0 diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py index 34c3435a7..264ad2167 100644 --- a/tests/unit_tests/test_deplete_cecm.py +++ b/tests/unit_tests/test_deplete_cecm.py @@ -1,9 +1,9 @@ -""" Regression tests for cecm.py""" +"""Regression tests for openmc.deplete.integrator.cecm algorithm. -import os -import unittest +These tests integrate a simple test problem described in dummy_geometry.py. +""" -import numpy as np +from pytest import approx import openmc.deplete from openmc.deplete import results from openmc.deplete import utilities @@ -11,59 +11,30 @@ from openmc.deplete import utilities from tests import dummy_geometry -class TestCECMRegression(unittest.TestCase): - """ Regression tests for opendeplete.integrator.cecm algorithm. +def test_cecm(run_in_tmpdir): + """Integral regression test of integrator algorithm using CE/CM.""" - These tests integrate a simple test problem described in dummy_geometry.py. - """ + settings = openmc.deplete.Settings() + settings.dt_vec = [0.75, 0.75] + settings.output_dir = "test_integrator_regression" - @classmethod - def setUpClass(cls): - """ Save current directory in case integrator crashes.""" - cls.cwd = os.getcwd() - cls.results = "test_integrator_regression" + op = dummy_geometry.DummyGeometry(settings) - def test_cecm(self): - """ Integral regression test of integrator algorithm using CE/CM. """ + # Perform simulation using the MCNPX/MCNP6 algorithm + openmc.deplete.cecm(op, print_out=False) - settings = openmc.deplete.Settings() - settings.dt_vec = [0.75, 0.75] - settings.output_dir = self.results + # Load the files + res = results.read_results(settings.output_dir + "/results.h5") - op = dummy_geometry.DummyGeometry(settings) + _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") + _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") - # Perform simulation using the MCNPX/MCNP6 algorithm - openmc.deplete.cecm(op, print_out=False) + # Mathematica solution + s1 = [1.86872629872102, 1.395525772416039] + s2 = [2.18097439443550, 2.69429754646747] - # Load the files - res = results.read_results(settings.output_dir + "/results.h5") + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) - _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") - _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") - - # Mathematica solution - s1 = [1.86872629872102, 1.395525772416039] - s2 = [2.18097439443550, 2.69429754646747] - - tol = 1.0e-13 - - self.assertLess(np.absolute(y1[1] - s1[0]), tol) - self.assertLess(np.absolute(y2[1] - s1[1]), tol) - - self.assertLess(np.absolute(y1[2] - s2[0]), tol) - self.assertLess(np.absolute(y2[2] - s2[1]), tol) - - @classmethod - def tearDownClass(cls): - """ Clean up files""" - - os.chdir(cls.cwd) - - openmc.deplete.comm.barrier() - if openmc.deplete.comm.rank == 0: - os.remove(os.path.join(cls.results, "results.h5")) - os.rmdir(cls.results) - - -if __name__ == '__main__': - unittest.main() + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 6166f1561..064b878af 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -1,8 +1,7 @@ -"""Tests for depletion chains""" +"""Tests for openmc.deplete.Chain class.""" -from collections import OrderedDict +from collections.abc import Mapping import os -import unittest from pathlib import Path import numpy as np @@ -12,190 +11,184 @@ from openmc.deplete import comm, Chain, reaction_rates, nuclide _test_filename = str(Path(__file__).parents[2] / 'chains' / 'chain_test.xml') -class TestChain(unittest.TestCase): - """ Tests for Chain class.""" +def test_init(): + """Test depletion chain initialization.""" + dep = Chain() - def test__init__(self): - """ Test depletion chain initialization.""" - dep = Chain() - - self.assertIsInstance(dep.nuclides, list) - self.assertIsInstance(dep.nuclide_dict, OrderedDict) - self.assertIsInstance(dep.react_to_ind, OrderedDict) - - def test_n_nuclides(self): - """ Test depletion chain n_nuclides parameter. """ - dep = Chain() - - dep.nuclides = ["NucA", "NucB", "NucC"] - - self.assertEqual(dep.n_nuclides, 3) - - def test_from_endf(self): - """Test depletion chain building from ENDF. Empty at the moment until we figure - out a good way to unit-test this.""" - pass - - def test_from_xml(self): - """ Read chain_test.xml and ensure all values are correct. """ - # Unfortunately, this routine touches a lot of the code, but most of - # the components external to depletion_chain.py are simple storage - # types. - - dep = Chain.from_xml(_test_filename) - - # Basic checks - self.assertEqual(dep.n_nuclides, 3) - - # A tests - nuc = dep.nuclides[dep.nuclide_dict["A"]] - - self.assertEqual(nuc.name, "A") - self.assertEqual(nuc.half_life, 2.36520E+04) - self.assertEqual(nuc.n_decay_modes, 2) - modes = nuc.decay_modes - self.assertEqual([m.target for m in modes], ["B", "C"]) - self.assertEqual([m.type for m in modes], ["beta1", "beta2"]) - self.assertEqual([m.branching_ratio for m in modes], [0.6, 0.4]) - self.assertEqual(nuc.n_reaction_paths, 1) - self.assertEqual([r.target for r in nuc.reactions], ["C"]) - self.assertEqual([r.type for r in nuc.reactions], ["(n,gamma)"]) - self.assertEqual([r.branching_ratio for r in nuc.reactions], [1.0]) - - # B tests - nuc = dep.nuclides[dep.nuclide_dict["B"]] - - self.assertEqual(nuc.name, "B") - self.assertEqual(nuc.half_life, 3.29040E+04) - self.assertEqual(nuc.n_decay_modes, 1) - modes = nuc.decay_modes - self.assertEqual([m.target for m in modes], ["A"]) - self.assertEqual([m.type for m in modes], ["beta"]) - self.assertEqual([m.branching_ratio for m in modes], [1.0]) - self.assertEqual(nuc.n_reaction_paths, 1) - self.assertEqual([r.target for r in nuc.reactions], ["C"]) - self.assertEqual([r.type for r in nuc.reactions], ["(n,gamma)"]) - self.assertEqual([r.branching_ratio for r in nuc.reactions], [1.0]) - - # C tests - nuc = dep.nuclides[dep.nuclide_dict["C"]] - - self.assertEqual(nuc.name, "C") - self.assertEqual(nuc.n_decay_modes, 0) - self.assertEqual(nuc.n_reaction_paths, 3) - self.assertEqual([r.target for r in nuc.reactions], [None, "A", "B"]) - self.assertEqual([r.type for r in nuc.reactions], ["fission", "(n,gamma)", "(n,gamma)"]) - self.assertEqual([r.branching_ratio for r in nuc.reactions], [1.0, 0.7, 0.3]) - - # Yield tests - self.assertEqual(nuc.yield_energies, [0.0253]) - self.assertEqual(list(nuc.yield_data.keys()), [0.0253]) - self.assertEqual(nuc.yield_data[0.0253], - [("A", 0.0292737), ("B", 0.002566345)]) - - def test_export_to_xml(self): - """Test writing a depletion chain to XML.""" - - # Prevent different MPI ranks from conflicting - filename = 'test{}.xml'.format(comm.rank) - - A = nuclide.Nuclide() - A.name = "A" - A.half_life = 2.36520e4 - A.decay_modes = [ - nuclide.DecayTuple("beta1", "B", 0.6), - nuclide.DecayTuple("beta2", "C", 0.4) - ] - A.reactions = [nuclide.ReactionTuple("(n,gamma)", "C", 0.0, 1.0)] - - B = nuclide.Nuclide() - B.name = "B" - B.half_life = 3.29040e4 - B.decay_modes = [nuclide.DecayTuple("beta", "A", 1.0)] - B.reactions = [nuclide.ReactionTuple("(n,gamma)", "C", 0.0, 1.0)] - - C = nuclide.Nuclide() - C.name = "C" - C.reactions = [ - nuclide.ReactionTuple("fission", None, 2.0e8, 1.0), - nuclide.ReactionTuple("(n,gamma)", "A", 0.0, 0.7), - nuclide.ReactionTuple("(n,gamma)", "B", 0.0, 0.3) - ] - C.yield_energies = [0.0253] - C.yield_data = {0.0253: [("A", 0.0292737), ("B", 0.002566345)]} - - chain = Chain() - chain.nuclides = [A, B, C] - chain.export_to_xml(filename) - - original = open(_test_filename, 'r').read() - chain_xml = open(filename, 'r').read() - self.assertEqual(original, chain_xml) - - os.remove(filename) - - def test_form_matrix(self): - """ Using chain_test, and a dummy reaction rate, compute the matrix. """ - # Relies on test_from_xml passing. - - dep = Chain.from_xml(_test_filename) - - cell_ind = {"10000": 0, "10001": 1} - nuc_ind = {"A": 0, "B": 1, "C": 2} - react_ind = dep.react_to_ind - - react = reaction_rates.ReactionRates(cell_ind, nuc_ind, react_ind) - - dep.nuc_to_react_ind = nuc_ind - - react["10000", "C", "fission"] = 1.0 - react["10000", "A", "(n,gamma)"] = 2.0 - react["10000", "B", "(n,gamma)"] = 3.0 - react["10000", "C", "(n,gamma)"] = 4.0 - - mat = dep.form_matrix(react[0, :, :]) - # Loss A, decay, (n, gamma) - mat00 = -np.log(2) / 2.36520E+04 - 2 - # A -> B, decay, 0.6 branching ratio - mat10 = np.log(2) / 2.36520E+04 * 0.6 - # A -> C, decay, 0.4 branching ratio + (n,gamma) - mat20 = np.log(2) / 2.36520E+04 * 0.4 + 2 - - # B -> A, decay, 1.0 branching ratio - mat01 = np.log(2)/3.29040E+04 - # Loss B, decay, (n, gamma) - mat11 = -np.log(2)/3.29040E+04 - 3 - # B -> C, (n, gamma) - mat21 = 3 - - # C -> A fission, (n, gamma) - mat02 = 0.0292737 * 1.0 + 4.0 * 0.7 - # C -> B fission, (n, gamma) - mat12 = 0.002566345 * 1.0 + 4.0 * 0.3 - # Loss C, fission, (n, gamma) - mat22 = -1.0 - 4.0 - - self.assertEqual(mat[0, 0], mat00) - self.assertEqual(mat[1, 0], mat10) - self.assertEqual(mat[2, 0], mat20) - self.assertEqual(mat[0, 1], mat01) - self.assertEqual(mat[1, 1], mat11) - self.assertEqual(mat[2, 1], mat21) - self.assertEqual(mat[0, 2], mat02) - self.assertEqual(mat[1, 2], mat12) - self.assertEqual(mat[2, 2], mat22) - - def test_nuc_by_ind(self): - """ Test nuc_by_ind converter function. """ - dep = Chain() - - dep.nuclides = ["NucA", "NucB", "NucC"] - dep.nuclide_dict = {"NucA" : 0, "NucB" : 1, "NucC" : 2} - - self.assertEqual("NucA", dep.nuc_by_ind("NucA")) - self.assertEqual("NucB", dep.nuc_by_ind("NucB")) - self.assertEqual("NucC", dep.nuc_by_ind("NucC")) + assert isinstance(dep.nuclides, list) + assert isinstance(dep.nuclide_dict, Mapping) + assert isinstance(dep.react_to_ind, Mapping) -if __name__ == '__main__': - unittest.main() +def test_n_nuclides(): + """Test depletion chain n_nuclides parameter.""" + dep = Chain() + dep.nuclides = ["NucA", "NucB", "NucC"] + + assert dep.n_nuclides == 3 + + +def test_from_endf(): + """Test depletion chain building from ENDF. Empty at the moment until we figure + out a good way to unit-test this.""" + pass + + +def test_from_xml(): + """Read chain_test.xml and ensure all values are correct.""" + # Unfortunately, this routine touches a lot of the code, but most of + # the components external to depletion_chain.py are simple storage + # types. + + dep = Chain.from_xml(_test_filename) + + # Basic checks + assert dep.n_nuclides == 3 + + # A tests + nuc = dep.nuclides[dep.nuclide_dict["A"]] + + assert nuc.name == "A" + assert nuc.half_life == 2.36520E+04 + assert nuc.n_decay_modes == 2 + modes = nuc.decay_modes + assert [m.target for m in modes] == ["B", "C"] + assert [m.type for m in modes] == ["beta1", "beta2"] + assert [m.branching_ratio for m in modes] == [0.6, 0.4] + assert nuc.n_reaction_paths == 1 + assert [r.target for r in nuc.reactions] == ["C"] + assert [r.type for r in nuc.reactions] == ["(n,gamma)"] + assert [r.branching_ratio for r in nuc.reactions] == [1.0] + + # B tests + nuc = dep.nuclides[dep.nuclide_dict["B"]] + + assert nuc.name == "B" + assert nuc.half_life == 3.29040E+04 + assert nuc.n_decay_modes == 1 + modes = nuc.decay_modes + assert [m.target for m in modes] == ["A"] + assert [m.type for m in modes] == ["beta"] + assert [m.branching_ratio for m in modes] == [1.0] + assert nuc.n_reaction_paths == 1 + assert [r.target for r in nuc.reactions] == ["C"] + assert [r.type for r in nuc.reactions] == ["(n,gamma)"] + assert [r.branching_ratio for r in nuc.reactions] == [1.0] + + # C tests + nuc = dep.nuclides[dep.nuclide_dict["C"]] + + assert nuc.name == "C" + assert nuc.n_decay_modes == 0 + assert nuc.n_reaction_paths == 3 + assert [r.target for r in nuc.reactions] == [None, "A", "B"] + assert [r.type for r in nuc.reactions] == ["fission", "(n,gamma)", "(n,gamma)"] + assert [r.branching_ratio for r in nuc.reactions] == [1.0, 0.7, 0.3] + + # Yield tests + assert nuc.yield_energies == [0.0253] + assert list(nuc.yield_data) == [0.0253] + assert nuc.yield_data[0.0253] == [("A", 0.0292737), ("B", 0.002566345)] + + +def test_export_to_xml(run_in_tmpdir): + """Test writing a depletion chain to XML.""" + + # Prevent different MPI ranks from conflicting + filename = 'test{}.xml'.format(comm.rank) + + A = nuclide.Nuclide() + A.name = "A" + A.half_life = 2.36520e4 + A.decay_modes = [ + nuclide.DecayTuple("beta1", "B", 0.6), + nuclide.DecayTuple("beta2", "C", 0.4) + ] + A.reactions = [nuclide.ReactionTuple("(n,gamma)", "C", 0.0, 1.0)] + + B = nuclide.Nuclide() + B.name = "B" + B.half_life = 3.29040e4 + B.decay_modes = [nuclide.DecayTuple("beta", "A", 1.0)] + B.reactions = [nuclide.ReactionTuple("(n,gamma)", "C", 0.0, 1.0)] + + C = nuclide.Nuclide() + C.name = "C" + C.reactions = [ + nuclide.ReactionTuple("fission", None, 2.0e8, 1.0), + nuclide.ReactionTuple("(n,gamma)", "A", 0.0, 0.7), + nuclide.ReactionTuple("(n,gamma)", "B", 0.0, 0.3) + ] + C.yield_energies = [0.0253] + C.yield_data = {0.0253: [("A", 0.0292737), ("B", 0.002566345)]} + + chain = Chain() + chain.nuclides = [A, B, C] + chain.export_to_xml(filename) + + original = open(_test_filename, 'r').read() + chain_xml = open(filename, 'r').read() + assert original == chain_xml + + +def test_form_matrix(): + """ Using chain_test, and a dummy reaction rate, compute the matrix. """ + # Relies on test_from_xml passing. + + dep = Chain.from_xml(_test_filename) + + cell_ind = {"10000": 0, "10001": 1} + nuc_ind = {"A": 0, "B": 1, "C": 2} + react_ind = dep.react_to_ind + + react = reaction_rates.ReactionRates(cell_ind, nuc_ind, react_ind) + + dep.nuc_to_react_ind = nuc_ind + + react["10000", "C", "fission"] = 1.0 + react["10000", "A", "(n,gamma)"] = 2.0 + react["10000", "B", "(n,gamma)"] = 3.0 + react["10000", "C", "(n,gamma)"] = 4.0 + + mat = dep.form_matrix(react[0, :, :]) + # Loss A, decay, (n, gamma) + mat00 = -np.log(2) / 2.36520E+04 - 2 + # A -> B, decay, 0.6 branching ratio + mat10 = np.log(2) / 2.36520E+04 * 0.6 + # A -> C, decay, 0.4 branching ratio + (n,gamma) + mat20 = np.log(2) / 2.36520E+04 * 0.4 + 2 + + # B -> A, decay, 1.0 branching ratio + mat01 = np.log(2)/3.29040E+04 + # Loss B, decay, (n, gamma) + mat11 = -np.log(2)/3.29040E+04 - 3 + # B -> C, (n, gamma) + mat21 = 3 + + # C -> A fission, (n, gamma) + mat02 = 0.0292737 * 1.0 + 4.0 * 0.7 + # C -> B fission, (n, gamma) + mat12 = 0.002566345 * 1.0 + 4.0 * 0.3 + # Loss C, fission, (n, gamma) + mat22 = -1.0 - 4.0 + + assert mat[0, 0] == mat00 + assert mat[1, 0] == mat10 + assert mat[2, 0] == mat20 + assert mat[0, 1] == mat01 + assert mat[1, 1] == mat11 + assert mat[2, 1] == mat21 + assert mat[0, 2] == mat02 + assert mat[1, 2] == mat12 + assert mat[2, 2] == mat22 + + +def test_nuc_by_ind(): + """ Test nuc_by_ind converter function. """ + dep = Chain() + dep.nuclides = ["NucA", "NucB", "NucC"] + dep.nuclide_dict = {"NucA" : 0, "NucB" : 1, "NucC" : 2} + + assert "NucA" == dep.nuc_by_ind("NucA") + assert "NucB" == dep.nuc_by_ind("NucB") + assert "NucC" == dep.nuc_by_ind("NucC") diff --git a/tests/unit_tests/test_deplete_cram.py b/tests/unit_tests/test_deplete_cram.py index 10f41fe2b..21b93d17e 100644 --- a/tests/unit_tests/test_deplete_cram.py +++ b/tests/unit_tests/test_deplete_cram.py @@ -1,48 +1,37 @@ -""" Tests for cram.py """ +""" Tests for cram.py -import unittest +Compares a few Mathematica matrix exponentials to CRAM16/CRAM48. +""" +from pytest import approx import numpy as np import scipy.sparse as sp from openmc.deplete.integrator import CRAM16, CRAM48 -class TestCram(unittest.TestCase): - """ Tests for cram.py +def test_CRAM16(): + """Test 16-term CRAM.""" + x = np.array([1.0, 1.0]) + mat = sp.csr_matrix([[-1.0, 0.0], [-2.0, -3.0]]) + dt = 0.1 - Compares a few Mathematica matrix exponentials to CRAM16/CRAM48. - """ + z = CRAM16(mat, x, dt) - def test_CRAM16(self): - """ Test 16-term CRAM. """ - x = np.array([1.0, 1.0]) - mat = sp.csr_matrix([[-1.0, 0.0], [-2.0, -3.0]]) - dt = 0.1 + # Solution from mathematica + z0 = np.array((0.904837418035960, 0.576799023327476)) - z = CRAM16(mat, x, dt) - - # Solution from mathematica - z0 = np.array((0.904837418035960, 0.576799023327476)) - - tol = 1.0e-15 - - self.assertLess(np.linalg.norm(z - z0), tol) - - def test_CRAM48(self): - """ Test 48-term CRAM. """ - x = np.array([1.0, 1.0]) - mat = sp.csr_matrix([[-1.0, 0.0], [-2.0, -3.0]]) - dt = 0.1 - - z = CRAM48(mat, x, dt) - - # Solution from mathematica - z0 = np.array((0.904837418035960, 0.576799023327476)) - - tol = 1.0e-15 - - self.assertLess(np.linalg.norm(z - z0), tol) + assert z == approx(z0) -if __name__ == '__main__': - unittest.main() +def test_CRAM48(): + """Test 48-term CRAM.""" + x = np.array([1.0, 1.0]) + mat = sp.csr_matrix([[-1.0, 0.0], [-2.0, -3.0]]) + dt = 0.1 + + z = CRAM48(mat, x, dt) + + # Solution from mathematica + z0 = np.array((0.904837418035960, 0.576799023327476)) + + assert z == approx(z0) diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 9b4cbe780..3b6eed42d 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -1,115 +1,101 @@ -""" Tests for integrator.py """ +"""Tests for integrator.py + +It is worth noting that openmc.deplete.integrate is extremely complex, to the +point I am unsure if it can be reasonably unit-tested. For the time being, it +will be left unimplemented and testing will be done via regression. + +""" import copy import os -import unittest from unittest.mock import MagicMock import numpy as np from openmc.deplete import integrator, ReactionRates, results, comm -class TestIntegrator(unittest.TestCase): - """ Tests for integrator.py +def test_save_results(run_in_tmpdir): + """Test data save module""" - It is worth noting that opendeplete.integrate is extremely complex, to - the point I am unsure if it can be reasonably unit-tested. For the time - being, it will be left unimplemented and testing will be done via - regression (in test_integrator_regression.py) - """ + stages = 3 - def test_save_results(self): - """ Test data save module """ + np.random.seed(comm.rank) - stages = 3 + # Mock geometry + op = MagicMock() - np.random.seed(comm.rank) + vol_dict = {} + full_burn_dict = {} - # Mock geometry - op = MagicMock() + j = 0 + for i in range(comm.size): + vol_dict[str(2*i)] = 1.2 + vol_dict[str(2*i + 1)] = 1.2 + full_burn_dict[str(2*i)] = j + full_burn_dict[str(2*i + 1)] = j + 1 + j += 2 - vol_dict = {} - full_burn_dict = {} + burn_list = [str(i) for i in range(2*comm.rank, 2*comm.rank + 2)] + nuc_list = ["na", "nb"] - j = 0 - for i in range(comm.size): - vol_dict[str(2*i)] = 1.2 - vol_dict[str(2*i + 1)] = 1.2 - full_burn_dict[str(2*i)] = j - full_burn_dict[str(2*i + 1)] = j + 1 - j += 2 + op.get_results_info.return_value = vol_dict, nuc_list, burn_list, full_burn_dict - burn_list = [str(i) for i in range(2*comm.rank, 2*comm.rank + 2)] - nuc_list = ["na", "nb"] + # Construct x + x1 = [] + x2 = [] - op.get_results_info.return_value = vol_dict, nuc_list, burn_list, full_burn_dict + for i in range(stages): + x1.append([np.random.rand(2), np.random.rand(2)]) + x2.append([np.random.rand(2), np.random.rand(2)]) - # Construct x - x1 = [] - x2 = [] + # Construct r + cell_dict = {s:i for i, s in enumerate(burn_list)} + r1 = ReactionRates(cell_dict, {"na":0, "nb":1}, {"ra":0, "rb":1}) + r1.rates = np.random.rand(2, 2, 2) - for i in range(stages): - x1.append([np.random.rand(2), np.random.rand(2)]) - x2.append([np.random.rand(2), np.random.rand(2)]) + rate1 = [] + rate2 = [] - # Construct r - cell_dict = {s:i for i, s in enumerate(burn_list)} - r1 = ReactionRates(cell_dict, {"na":0, "nb":1}, {"ra":0, "rb":1}) + for i in range(stages): + rate1.append(copy.deepcopy(r1)) + r1.rates = np.random.rand(2, 2, 2) + rate2.append(copy.deepcopy(r1)) r1.rates = np.random.rand(2, 2, 2) - rate1 = [] - rate2 = [] + # Create global terms + eigvl1 = np.random.rand(stages) + eigvl2 = np.random.rand(stages) + seed1 = [np.random.randint(100) for i in range(stages)] + seed2 = [np.random.randint(100) for i in range(stages)] - for i in range(stages): - rate1.append(copy.deepcopy(r1)) - r1.rates = np.random.rand(2, 2, 2) - rate2.append(copy.deepcopy(r1)) - r1.rates = np.random.rand(2, 2, 2) + eigvl1 = comm.bcast(eigvl1, root=0) + eigvl2 = comm.bcast(eigvl2, root=0) + seed1 = comm.bcast(seed1, root=0) + seed2 = comm.bcast(seed2, root=0) - # Create global terms - eigvl1 = np.random.rand(stages) - eigvl2 = np.random.rand(stages) - seed1 = [np.random.randint(100) for i in range(stages)] - seed2 = [np.random.randint(100) for i in range(stages)] + t1 = [0.0, 1.0] + t2 = [1.0, 2.0] - eigvl1 = comm.bcast(eigvl1, root=0) - eigvl2 = comm.bcast(eigvl2, root=0) - seed1 = comm.bcast(seed1, root=0) - seed2 = comm.bcast(seed2, root=0) + integrator.save_results(op, x1, rate1, eigvl1, seed1, t1, 0) + integrator.save_results(op, x2, rate2, eigvl2, seed2, t2, 1) - t1 = [0.0, 1.0] - t2 = [1.0, 2.0] + # Load the files + res = results.read_results("results.h5") - integrator.save_results(op, x1, rate1, eigvl1, seed1, t1, 0) - integrator.save_results(op, x2, rate2, eigvl2, seed2, t2, 1) + for i in range(stages): + for mat_i, mat in enumerate(burn_list): + for nuc_i, nuc in enumerate(nuc_list): + assert res[0][i, mat, nuc] == x1[i][mat_i][nuc_i] + assert res[1][i, mat, nuc] == x2[i][mat_i][nuc_i] + np.testing.assert_array_equal(res[0].rates[i][mat, nuc, :], + rate1[i][mat, nuc, :]) + np.testing.assert_array_equal(res[1].rates[i][mat, nuc, :], + rate2[i][mat, nuc, :]) - # Load the files - res = results.read_results("results.h5") + np.testing.assert_array_equal(res[0].k, eigvl1) + np.testing.assert_array_equal(res[0].seeds, seed1) + np.testing.assert_array_equal(res[0].time, t1) - for i in range(stages): - for mat_i, mat in enumerate(burn_list): - - for nuc_i, nuc in enumerate(nuc_list): - self.assertEqual(res[0][i, mat, nuc], x1[i][mat_i][nuc_i]) - self.assertEqual(res[1][i, mat, nuc], x2[i][mat_i][nuc_i]) - np.testing.assert_array_equal(res[0].rates[i][mat, nuc, :], - rate1[i][mat, nuc, :]) - np.testing.assert_array_equal(res[1].rates[i][mat, nuc, :], - rate2[i][mat, nuc, :]) - - np.testing.assert_array_equal(res[0].k, eigvl1) - np.testing.assert_array_equal(res[0].seeds, seed1) - np.testing.assert_array_equal(res[0].time, t1) - - np.testing.assert_array_equal(res[1].k, eigvl2) - np.testing.assert_array_equal(res[1].seeds, seed2) - np.testing.assert_array_equal(res[1].time, t2) - - # Delete files - comm.barrier() - if comm.rank == 0: - os.remove("results.h5") - - -if __name__ == '__main__': - unittest.main() + np.testing.assert_array_equal(res[1].k, eigvl2) + np.testing.assert_array_equal(res[1].seeds, seed2) + np.testing.assert_array_equal(res[1].time, t2) diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index ebcabc5ca..2add13f86 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -1,44 +1,42 @@ -""" Tests for nuclide.py. """ +"""Tests for the openmc.deplete.Nuclide class.""" -import unittest import xml.etree.ElementTree as ET from openmc.deplete import nuclide -class TestNuclide(unittest.TestCase): - """ Tests for the nuclide class. """ +def test_n_decay_modes(): + """ Test the decay mode count parameter. """ - def test_n_decay_modes(self): - """ Test the decay mode count parameter. """ + nuc = nuclide.Nuclide() - nuc = nuclide.Nuclide() + nuc.decay_modes = [ + nuclide.DecayTuple("beta1", "a", 0.5), + nuclide.DecayTuple("beta2", "b", 0.3), + nuclide.DecayTuple("beta3", "c", 0.2) + ] - nuc.decay_modes = [ - nuclide.DecayTuple("beta1", "a", 0.5), - nuclide.DecayTuple("beta2", "b", 0.3), - nuclide.DecayTuple("beta3", "c", 0.2) - ] + assert nuc.n_decay_modes == 3 - self.assertEqual(nuc.n_decay_modes, 3) - def test_n_reaction_paths(self): - """ Test the reaction path count parameter. """ +def test_n_reaction_paths(): + """ Test the reaction path count parameter. """ - nuc = nuclide.Nuclide() + nuc = nuclide.Nuclide() - nuc.reactions = [ - nuclide.ReactionTuple("(n,2n)", "a", 0.0, 1.0), - nuclide.ReactionTuple("(n,3n)", "b", 0.0, 1.0), - nuclide.ReactionTuple("(n,4n)", "c", 0.0, 1.0) - ] + nuc.reactions = [ + nuclide.ReactionTuple("(n,2n)", "a", 0.0, 1.0), + nuclide.ReactionTuple("(n,3n)", "b", 0.0, 1.0), + nuclide.ReactionTuple("(n,4n)", "c", 0.0, 1.0) + ] - self.assertEqual(nuc.n_reaction_paths, 3) + assert nuc.n_reaction_paths == 3 - def test_from_xml(self): - """Test reading nuclide data from an XML element.""" - data = """ +def test_from_xml(): + """Test reading nuclide data from an XML element.""" + + data = """ @@ -55,67 +53,64 @@ class TestNuclide(unittest.TestCase): - """ + """ - element = ET.fromstring(data) - u235 = nuclide.Nuclide.from_xml(element) + element = ET.fromstring(data) + u235 = nuclide.Nuclide.from_xml(element) - self.assertEqual(u235.decay_modes, [ - nuclide.DecayTuple('sf', 'U235', 7.2e-11), - nuclide.DecayTuple('alpha', 'Th231', 1 - 7.2e-11) - ]) - self.assertEqual(u235.reactions, [ - nuclide.ReactionTuple('(n,2n)', 'U234', -5297781.0, 1.0), - nuclide.ReactionTuple('(n,3n)', 'U233', -12142300.0, 1.0), - nuclide.ReactionTuple('(n,4n)', 'U232', -17885600.0, 1.0), - nuclide.ReactionTuple('(n,gamma)', 'U236', 6545200.0, 1.0), - nuclide.ReactionTuple('fission', None, 193405400.0, 1.0), - ]) - self.assertEqual(u235.yield_energies, [0.0253]) - self.assertEqual(u235.yield_data, { - 0.0253: [('Te134', 0.062155), ('Zr100', 0.0497641), - ('Xe138', 0.0481413)] - }) - - def test_to_xml_element(self): - """Test writing nuclide data to an XML element.""" - - C = nuclide.Nuclide() - C.name = "C" - C.half_life = 0.123 - C.decay_modes = [ - nuclide.DecayTuple('beta-', 'B', 0.99), - nuclide.DecayTuple('alpha', 'D', 0.01) - ] - C.reactions = [ - nuclide.ReactionTuple('fission', None, 2.0e8, 1.0), - nuclide.ReactionTuple('(n,gamma)', 'A', 0.0, 1.0) - ] - C.yield_energies = [0.0253] - C.yield_data = {0.0253: [("A", 0.0292737), ("B", 0.002566345)]} - element = C.to_xml_element() - - self.assertEqual(element.get("half_life"), "0.123") - - decay_elems = element.findall("decay_type") - self.assertEqual(len(decay_elems), 2) - self.assertEqual(decay_elems[0].get("type"), "beta-") - self.assertEqual(decay_elems[0].get("target"), "B") - self.assertEqual(decay_elems[0].get("branching_ratio"), "0.99") - self.assertEqual(decay_elems[1].get("type"), "alpha") - self.assertEqual(decay_elems[1].get("target"), "D") - self.assertEqual(decay_elems[1].get("branching_ratio"), "0.01") - - rx_elems = element.findall("reaction_type") - self.assertEqual(len(rx_elems), 2) - self.assertEqual(rx_elems[0].get("type"), "fission") - self.assertEqual(float(rx_elems[0].get("Q")), 2.0e8) - self.assertEqual(rx_elems[1].get("type"), "(n,gamma)") - self.assertEqual(rx_elems[1].get("target"), "A") - self.assertEqual(float(rx_elems[1].get("Q")), 0.0) - - self.assertIsNotNone(element.find('neutron_fission_yields')) + assert u235.decay_modes == [ + nuclide.DecayTuple('sf', 'U235', 7.2e-11), + nuclide.DecayTuple('alpha', 'Th231', 1 - 7.2e-11) + ] + assert u235.reactions == [ + nuclide.ReactionTuple('(n,2n)', 'U234', -5297781.0, 1.0), + nuclide.ReactionTuple('(n,3n)', 'U233', -12142300.0, 1.0), + nuclide.ReactionTuple('(n,4n)', 'U232', -17885600.0, 1.0), + nuclide.ReactionTuple('(n,gamma)', 'U236', 6545200.0, 1.0), + nuclide.ReactionTuple('fission', None, 193405400.0, 1.0), + ] + assert u235.yield_energies == [0.0253] + assert u235.yield_data == { + 0.0253: [('Te134', 0.062155), ('Zr100', 0.0497641), + ('Xe138', 0.0481413)] + } -if __name__ == '__main__': - unittest.main() +def test_to_xml_element(): + """Test writing nuclide data to an XML element.""" + + C = nuclide.Nuclide() + C.name = "C" + C.half_life = 0.123 + C.decay_modes = [ + nuclide.DecayTuple('beta-', 'B', 0.99), + nuclide.DecayTuple('alpha', 'D', 0.01) + ] + C.reactions = [ + nuclide.ReactionTuple('fission', None, 2.0e8, 1.0), + nuclide.ReactionTuple('(n,gamma)', 'A', 0.0, 1.0) + ] + C.yield_energies = [0.0253] + C.yield_data = {0.0253: [("A", 0.0292737), ("B", 0.002566345)]} + element = C.to_xml_element() + + assert element.get("half_life") == "0.123" + + decay_elems = element.findall("decay_type") + assert len(decay_elems) == 2 + assert decay_elems[0].get("type") == "beta-" + assert decay_elems[0].get("target") == "B" + assert decay_elems[0].get("branching_ratio") == "0.99" + assert decay_elems[1].get("type") == "alpha" + assert decay_elems[1].get("target") == "D" + assert decay_elems[1].get("branching_ratio") == "0.01" + + rx_elems = element.findall("reaction_type") + assert len(rx_elems) == 2 + assert rx_elems[0].get("type") == "fission" + assert float(rx_elems[0].get("Q")) == 2.0e8 + assert rx_elems[1].get("type") == "(n,gamma)" + assert rx_elems[1].get("target") == "A" + assert float(rx_elems[1].get("Q")) == 0.0 + + assert element.find('neutron_fission_yields') is not None diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index 6ad2007d9..d4b2efd33 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -1,68 +1,40 @@ -""" Regression tests for predictor.py""" +"""Regression tests for openmc.deplete.integrator.predictor algorithm. -import os -import unittest +These tests integrate a simple test problem described in dummy_geometry.py. +""" -import numpy as np +from pytest import approx import openmc.deplete from openmc.deplete import results from openmc.deplete import utilities from tests import dummy_geometry -class TestPredictorRegression(unittest.TestCase): - """ Regression tests for opendeplete.integrator.predictor algorithm. - These tests integrate a simple test problem described in dummy_geometry.py. - """ +def test_predictor(): + """Integral regression test of integrator algorithm using predictor/corrector""" - @classmethod - def setUpClass(cls): - """ Save current directory in case integrator crashes.""" - cls.cwd = os.getcwd() - cls.results = "test_integrator_regression" + settings = openmc.deplete.Settings() + settings.dt_vec = [0.75, 0.75] + settings.output_dir = "test_integrator_regression" - def test_predictor(self): - """ Integral regression test of integrator algorithm using CE/CM. """ + op = dummy_geometry.DummyGeometry(settings) - settings = openmc.deplete.Settings() - settings.dt_vec = [0.75, 0.75] - settings.output_dir = self.results + # Perform simulation using the predictor algorithm + openmc.deplete.predictor(op, print_out=False) - op = dummy_geometry.DummyGeometry(settings) + # Load the files + res = results.read_results(settings.output_dir + "/results.h5") - # Perform simulation using the predictor algorithm - openmc.deplete.predictor(op, print_out=False) + _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") + _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") - # Load the files - res = results.read_results(settings.output_dir + "/results.h5") + # Mathematica solution + s1 = [2.46847546272295, 0.986431226850467] + s2 = [4.11525874568034, -0.0581692232513460] - _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") - _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) - # Mathematica solution - s1 = [2.46847546272295, 0.986431226850467] - s2 = [4.11525874568034, -0.0581692232513460] - - tol = 1.0e-13 - - self.assertLess(np.absolute(y1[1] - s1[0]), tol) - self.assertLess(np.absolute(y2[1] - s1[1]), tol) - - self.assertLess(np.absolute(y1[2] - s2[0]), tol) - self.assertLess(np.absolute(y2[2] - s2[1]), tol) - - @classmethod - def tearDownClass(cls): - """ Clean up files""" - - os.chdir(cls.cwd) - - openmc.deplete.comm.barrier() - if openmc.deplete.comm.rank == 0: - os.remove(os.path.join(cls.results, "results.h5")) - os.rmdir(cls.results) - - -if __name__ == '__main__': - unittest.main() + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) diff --git a/tests/unit_tests/test_deplete_reaction.py b/tests/unit_tests/test_deplete_reaction.py index 2139be16c..a98535e1d 100644 --- a/tests/unit_tests/test_deplete_reaction.py +++ b/tests/unit_tests/test_deplete_reaction.py @@ -1,86 +1,80 @@ -""" Tests for reaction_rates.py. """ - -import unittest +"""Tests for the openmc.deplete.ReactionRates class.""" from openmc.deplete import reaction_rates -class TestReactionRates(unittest.TestCase): - """ Tests for the ReactionRates class. """ +def test_indexing(): + """Tests the __getitem__ and __setitem__ routines simultaneously.""" - def test_indexing(self): - """Tests the __getitem__ and __setitem__ routines simultaneously.""" + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1} + react_to_ind = {"fission" : 0, "(n,gamma)" : 1} - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1} - react_to_ind = {"fission" : 0, "(n,gamma)" : 1} + rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) - rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + rates["10000", "U238", "fission"] = 1.0 + rates["10001", "U238", "fission"] = 2.0 + rates["10000", "U235", "fission"] = 3.0 + rates["10001", "U235", "fission"] = 4.0 + rates["10000", "U238", "(n,gamma)"] = 5.0 + rates["10001", "U238", "(n,gamma)"] = 6.0 + rates["10000", "U235", "(n,gamma)"] = 7.0 + rates["10001", "U235", "(n,gamma)"] = 8.0 - rates["10000", "U238", "fission"] = 1.0 - rates["10001", "U238", "fission"] = 2.0 - rates["10000", "U235", "fission"] = 3.0 - rates["10001", "U235", "fission"] = 4.0 - rates["10000", "U238", "(n,gamma)"] = 5.0 - rates["10001", "U238", "(n,gamma)"] = 6.0 - rates["10000", "U235", "(n,gamma)"] = 7.0 - rates["10001", "U235", "(n,gamma)"] = 8.0 + # String indexing + assert rates["10000", "U238", "fission"] == 1.0 + assert rates["10001", "U238", "fission"] == 2.0 + assert rates["10000", "U235", "fission"] == 3.0 + assert rates["10001", "U235", "fission"] == 4.0 + assert rates["10000", "U238", "(n,gamma)"] == 5.0 + assert rates["10001", "U238", "(n,gamma)"] == 6.0 + assert rates["10000", "U235", "(n,gamma)"] == 7.0 + assert rates["10001", "U235", "(n,gamma)"] == 8.0 - # String indexing - self.assertEqual(rates["10000", "U238", "fission"], 1.0) - self.assertEqual(rates["10001", "U238", "fission"], 2.0) - self.assertEqual(rates["10000", "U235", "fission"], 3.0) - self.assertEqual(rates["10001", "U235", "fission"], 4.0) - self.assertEqual(rates["10000", "U238", "(n,gamma)"], 5.0) - self.assertEqual(rates["10001", "U238", "(n,gamma)"], 6.0) - self.assertEqual(rates["10000", "U235", "(n,gamma)"], 7.0) - self.assertEqual(rates["10001", "U235", "(n,gamma)"], 8.0) + # Int indexing + assert rates[0, 0, 0] == 1.0 + assert rates[1, 0, 0] == 2.0 + assert rates[0, 1, 0] == 3.0 + assert rates[1, 1, 0] == 4.0 + assert rates[0, 0, 1] == 5.0 + assert rates[1, 0, 1] == 6.0 + assert rates[0, 1, 1] == 7.0 + assert rates[1, 1, 1] == 8.0 - # Int indexing - self.assertEqual(rates[0, 0, 0], 1.0) - self.assertEqual(rates[1, 0, 0], 2.0) - self.assertEqual(rates[0, 1, 0], 3.0) - self.assertEqual(rates[1, 1, 0], 4.0) - self.assertEqual(rates[0, 0, 1], 5.0) - self.assertEqual(rates[1, 0, 1], 6.0) - self.assertEqual(rates[0, 1, 1], 7.0) - self.assertEqual(rates[1, 1, 1], 8.0) + rates[0, 0, 0] = 5.0 - rates[0, 0, 0] = 5.0 - - self.assertEqual(rates[0, 0, 0], 5.0) - self.assertEqual(rates["10000", "U238", "fission"], 5.0) - - def test_n_mat(self): - """ Test number of materials property. """ - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} - - rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) - - self.assertEqual(rates.n_mat, 2) - - def test_n_nuc(self): - """ Test number of nuclides property. """ - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} - - rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) - - self.assertEqual(rates.n_nuc, 3) - - def test_n_react(self): - """ Test number of reactions property. """ - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} - - rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) - - self.assertEqual(rates.n_react, 4) + assert rates[0, 0, 0] == 5.0 + assert rates["10000", "U238", "fission"] == 5.0 -if __name__ == '__main__': - unittest.main() +def test_n_mat(): + """Test number of materials property.""" + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} + + rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + + assert rates.n_mat == 2 + + +def test_n_nuc(): + """Test number of nuclides property.""" + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} + + rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + + assert rates.n_nuc == 3 + + +def test_n_react(): + """ Test number of reactions property. """ + mat_to_ind = {"10000" : 0, "10001" : 1} + nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} + react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} + + rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + + assert rates.n_react == 4 From 939d47cffa59fd8570a60fbb255cb4a4a8164dff Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 14 Feb 2018 12:46:23 -0600 Subject: [PATCH 216/282] Simplify OpenMCSettings class (can properly delegate to openmc.Settings) --- openmc/deplete/openmc_wrapper.py | 116 ++++++-------------- scripts/example_run.py | 18 +-- tests/regression_tests/test_deplete_full.py | 29 +++-- 3 files changed, 56 insertions(+), 107 deletions(-) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 5c7a1aeba..bf0a12471 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -31,7 +31,7 @@ from .function import Settings, Operator -def chunks(items, n): +def _chunks(items, n): min_size, extra = divmod(len(items), n) j = 0 chunk_list = [] @@ -43,70 +43,61 @@ def chunks(items, n): class OpenMCSettings(Settings): - """The OpenMCSettings class. - - Extends Settings to provide information OpenMC needs to run. + """Extends Settings to provide information OpenMC needs to run. Attributes ---------- dt_vec : numpy.array - Array of time steps to take. (From Settings) - tol : float - Tolerance for adaptive time stepping. (From Settings) + Array of time steps to in units of [s] output_dir : str - Path to output directory to save results. (From Settings) + Path to output directory to save results. chain_file : str - Path to the depletion chain xml file. Defaults to the environment - variable "OPENDEPLETE_CHAIN" if it exists. - particles : int - Number of particles to simulate per batch. - batches : int - Number of batches. - inactive : int - Number of inactive batches. - lower_left : list of float - Coordinate of lower left of bounding box of geometry. - upper_right : list of float - Coordinate of upper right of bounding box of geometry. - entropy_dimension : list of int - Grid size of entropy. - dilute_initial : float, default 1.0e3 + Path to the depletion chain xml file. Defaults to the + :envvar:`OPENDEPLETE_CHAIN` environment variable if it exists. + 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. + nuclides with reaction rates. Defaults to 1.0e3. 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. - constant_seed : int - If present, all runs will be performed with this seed. power : float - Power of the reactor in W. For a 2D problem, the power can be given in + Power of the reactor in [W]. 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. + settings : openmc.Settings + Settings for OpenMC simulations + """ + _depletion_attrs = {'dt_vec', 'output_dir', 'chain_file', 'dilute_initial', + 'round_number', 'power'} + def __init__(self): super().__init__() - # OpenMC specific try: self.chain_file = os.environ["OPENDEPLETE_CHAIN"] except KeyError: self.chain_file = None - self.particles = None - self.batches = None - self.inactive = None - self.lower_left = None - self.upper_right = None - self.entropy_dimension = None self.dilute_initial = 1.0e3 - - # OpenMC testing specific self.round_number = False - self.constant_seed = None - - # Depletion problem specific self.power = None + # Avoid setattr to create OpenMC settings + self.__dict__['settings'] = openmc.Settings() + + def __setattr__(self, name, value): + if name in self._depletion_attrs: + self.__dict__[name] = value + else: + setattr(self.__dict__['settings'], name, value) + + def __getattr__(self, name): + if name in self._depletion_attrs: + return self.__dict__[name] + else: + return getattr(self.__dict__['settings'], name) + class Materials(object): """The Materials class. @@ -290,8 +281,8 @@ class OpenMCOperator(Operator): i += 1 # Decompose geometry - mat_burn_lists = chunks(mat_burn, comm.size) - mat_not_burn_lists = chunks(mat_not_burn, comm.size) + mat_burn_lists = _chunks(mat_burn, comm.size) + mat_not_burn_lists = _chunks(mat_not_burn, comm.size) mat_tally_ind = OrderedDict() @@ -456,7 +447,7 @@ class OpenMCOperator(Operator): # Create XML files if comm.rank == 0: self.geometry.export_to_xml() - self.generate_settings_xml() + self.settings.settings.export_to_xml() self.generate_materials_xml() # Initialize OpenMC library @@ -526,46 +517,6 @@ class OpenMCOperator(Operator): materials.export_to_xml() - def generate_settings_xml(self): - """Generates settings.xml. - - This function creates settings.xml using the value of the settings - variable. - - Todo - ---- - Rewrite to generalize source box. - """ - - batches = self.settings.batches - inactive = self.settings.inactive - particles = self.settings.particles - - # Just a generic settings file to get it running. - settings_file = openmc.Settings() - settings_file.batches = batches - settings_file.inactive = inactive - settings_file.particles = particles - settings_file.source = openmc.Source(space=openmc.stats.Box( - self.settings.lower_left, self.settings.upper_right)) - - if self.settings.entropy_dimension is not None: - entropy_mesh = openmc.Mesh() - entropy_mesh.lower_left = self.settings.lower_left - entropy_mesh.upper_right = self.settings.upper_right - entropy_mesh.dimension = self.settings.entropy_dimension - settings_file.entropy_mesh = entropy_mesh - - # Set seed - if self.settings.constant_seed is not None: - seed = self.settings.constant_seed - else: - seed = random.randint(1, sys.maxsize-1) - - settings_file.seed = self.seed = seed - - settings_file.export_to_xml() - def _get_tally_nuclides(self): nuc_set = set() @@ -581,7 +532,6 @@ class OpenMCOperator(Operator): 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) diff --git a/scripts/example_run.py b/scripts/example_run.py index 78d7dcedd..56d42b21a 100644 --- a/scripts/example_run.py +++ b/scripts/example_run.py @@ -1,6 +1,8 @@ """An example file showing how to run a simulation.""" import numpy as np +import openmc +from openmc.data import JOULE_PER_EV import openmc.deplete import example_geometry @@ -15,20 +17,18 @@ N = np.floor(dt2/dt1) dt = np.repeat([dt1], N) -# Create settings variable +# Depletion settings settings = openmc.deplete.OpenMCSettings() +settings.power = 2.337e15*4*JOULE_PER_EV*1e6 # MeV/second cm from CASMO +settings.dt_vec = dt +settings.output_dir = 'test' +# OpenMC-delegated settings settings.particles = 1000 settings.batches = 100 settings.inactive = 40 -settings.lower_left = lower_left -settings.upper_right = upper_right -settings.entropy_dimension = [10, 10, 1] - -joule_per_mev = 1.6021766208e-13 -settings.power = 2.337e15*4*joule_per_mev # MeV/second cm from CASMO -settings.dt_vec = dt -settings.output_dir = 'test' +settings.source = openmc.Source(space=openmc.stats.Box(lower_left, upper_right)) +settings.verbosity = 3 op = openmc.deplete.OpenMCOperator(geometry, settings) diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index 5f4af7a73..fcaa60eee 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -5,6 +5,8 @@ import shutil from pathlib import Path import numpy as np +import openmc +from openmc.data import JOULE_PER_EV import openmc.deplete from openmc.deplete import results from openmc.deplete import utilities @@ -35,26 +37,23 @@ def test_full(run_in_tmpdir): N = floor(dt2/dt1) dt = np.full(N, dt1) - # Create settings variable + # Depletion settings settings = openmc.deplete.OpenMCSettings() + settings.chain_file = str(Path(__file__).parents[2] / 'chains' / + 'chain_simple.xml') + settings.power = 2.337e15*4*JOULE_PER_EV*1e6 # MeV/second cm from CASMO + settings.dt_vec = dt + settings.output_dir = "test_full" + settings.round_number = True - - chain_file = str(Path(__file__).parents[2] / 'chains' / 'chain_simple.xml') - settings.chain_file = chain_file + # Add OpenMC-specific settings settings.particles = 100 settings.batches = 100 settings.inactive = 40 - settings.lower_left = lower_left - settings.upper_right = upper_right - settings.entropy_dimension = [10, 10, 1] - - settings.round_number = True - settings.constant_seed = 1 - - joule_per_mev = 1.6021766208e-13 - settings.power = 2.337e15*4*joule_per_mev # MeV/second cm from CASMO - settings.dt_vec = dt - settings.output_dir = "test_full" + space = openmc.stats.Box(lower_left, upper_right) + settings.source = openmc.Source(space=space) + settings.seed = 1 + settings.verbosity = 3 op = openmc.deplete.OpenMCOperator(geometry, settings) From f494fecf21b7e8de0066c27acdd1258e06641694 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 14 Feb 2018 12:57:56 -0600 Subject: [PATCH 217/282] Change Operator.eval() -> Operator.__call__() --- openmc/deplete/function.py | 35 +++---- openmc/deplete/integrator/cecm.py | 6 +- openmc/deplete/integrator/predictor.py | 4 +- openmc/deplete/openmc_wrapper.py | 104 ++++++++++----------- tests/dummy_geometry.py | 16 ++-- tests/unit_tests/test_deplete_predictor.py | 2 +- 6 files changed, 84 insertions(+), 83 deletions(-) diff --git a/openmc/deplete/function.py b/openmc/deplete/function.py index bcc055e67..b9694d88b 100644 --- a/openmc/deplete/function.py +++ b/openmc/deplete/function.py @@ -27,33 +27,19 @@ class Settings(object): class Operator(metaclass=ABCMeta): - """The Operator metaclass. - - This defines all functions that the integrator needs to operate. + """Abstract class defining all methods needed for the integrator. Attributes ---------- settings : Settings Settings object. - """ + """ def __init__(self, settings): self.settings = settings @abstractmethod - def initial_condition(self): - """Performs final setup and returns initial condition. - - Returns - ------- - list of numpy.array - Total density for initial conditions. - """ - - pass - - @abstractmethod - def eval(self, vec, print_out=True): + def __call__(self, vec, print_out=True): """Runs a simulation. Parameters @@ -72,6 +58,17 @@ class Operator(metaclass=ABCMeta): seed : int Seed for this simulation. """ + pass + + @abstractmethod + def initial_condition(self): + """Performs final setup and returns initial condition. + + Returns + ------- + list of numpy.array + Total density for initial conditions. + """ pass @@ -114,3 +111,7 @@ class Operator(metaclass=ABCMeta): """ pass + + @abstractmethod + def finalize(self): + pass diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 699ccc203..57a87c703 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -62,7 +62,7 @@ def cecm(operator, print_out=True): eigvls = [] rates_array = [] - eigvl, rates, seed = operator.eval(x[0]) + eigvl, rates, seed = operator(x[0]) eigvls.append(eigvl) seeds.append(seed) @@ -86,7 +86,7 @@ def cecm(operator, print_out=True): x.append(x_result) - eigvl, rates, seed = operator.eval(x[1]) + eigvl, rates, seed = operator(x[1]) eigvls.append(eigvl) seeds.append(seed) @@ -119,7 +119,7 @@ def cecm(operator, print_out=True): seeds = [] eigvls = [] rates_array = [] - eigvl, rates, seed = operator.eval(x[0]) + eigvl, rates, seed = operator(x[0]) eigvls.append(eigvl) seeds.append(seed) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 7b41e6649..1b8d00600 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -53,7 +53,7 @@ def predictor(operator, print_out=True): eigvls = [] rates_array = [] - eigvl, rates, seed = operator.eval(x[0]) + eigvl, rates, seed = operator(x[0]) eigvls.append(eigvl) seeds.append(seed) @@ -86,7 +86,7 @@ def predictor(operator, print_out=True): seeds = [] eigvls = [] rates_array = [] - eigvl, rates, seed = operator.eval(x[0]) + eigvl, rates, seed = operator(x[0]) eigvls.append(eigvl) seeds.append(seed) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index bf0a12471..3dce9db7b 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -207,6 +207,58 @@ class OpenMCOperator(Operator): # Create reaction rate tables self.initialize_reaction_rates() + def __call__(self, vec, print_out=True): + """Runs a simulation. + + Parameters + ---------- + vec : list of numpy.array + Total atoms to be used in function. + print_out : bool, optional + Whether or not to print out time. + + Returns + ------- + mat : list of scipy.sparse.csr_matrix + Matrices for the next step. + k : float + Eigenvalue of the problem. + rates : openmc.deplete.ReactionRates + Reaction rates from this simulation. + seed : int + Seed for this simulation. + """ + + # Prevent OpenMC from complaining about re-creating tallies + openmc.reset_auto_ids() + + # Update status + self.set_density(vec) + + time_start = time.time() + + # Update material compositions and tally nuclides + self._update_materials() + openmc.capi.tallies[1].nuclides = self._get_tally_nuclides() + + # Run OpenMC + openmc.capi.reset() + openmc.capi.run() + + time_openmc = time.time() + + # Extract results + k = self.unpack_tallies_and_normalize() + + 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 k, copy.deepcopy(self.reaction_rates), self.seed + def extract_mat_ids(self): """Extracts materials and assigns them to processes. @@ -365,58 +417,6 @@ class OpenMCOperator(Operator): self.chain.nuc_to_react_ind = self.burn_nuc_to_ind - def eval(self, vec, print_out=True): - """Runs a simulation. - - Parameters - ---------- - vec : list of numpy.array - Total atoms to be used in function. - print_out : bool, optional - Whether or not to print out time. - - Returns - ------- - mat : list of scipy.sparse.csr_matrix - Matrices for the next step. - k : float - Eigenvalue of the problem. - rates : openmc.deplete.ReactionRates - Reaction rates from this simulation. - seed : int - Seed for this simulation. - """ - - # Prevent OpenMC from complaining about re-creating tallies - openmc.reset_auto_ids() - - # Update status - self.set_density(vec) - - time_start = time.time() - - # Update material compositions and tally nuclides - self._update_materials() - openmc.capi.tallies[1].nuclides = self._get_tally_nuclides() - - # Run OpenMC - openmc.capi.reset() - openmc.capi.run() - - time_openmc = time.time() - - # Extract results - k = self.unpack_tallies_and_normalize() - - 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 k, copy.deepcopy(self.reaction_rates), self.seed - def form_matrix(self, y, mat): """Forms the depletion matrix. diff --git a/tests/dummy_geometry.py b/tests/dummy_geometry.py index ecdce567d..585c1b11c 100644 --- a/tests/dummy_geometry.py +++ b/tests/dummy_geometry.py @@ -21,14 +21,7 @@ class DummyGeometry(Operator): def __init__(self, settings): super().__init__(settings) - def finalize(self): - pass - - @property - def chain(self): - return self - - def eval(self, vec, print_out=False): + def __call__(self, vec, print_out=False): """Evaluates F(y) Parameters @@ -60,6 +53,13 @@ class DummyGeometry(Operator): # Create a fake rates object return 0.0, reaction_rates, 0 + def finalize(self): + pass + + @property + def chain(self): + return self + def form_matrix(self, rates): """Forms the f(y) matrix in y' = f(y)y. diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index d4b2efd33..d808c46b8 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -11,7 +11,7 @@ from openmc.deplete import utilities from tests import dummy_geometry -def test_predictor(): +def test_predictor(run_in_tmpdir): """Integral regression test of integrator algorithm using predictor/corrector""" settings = openmc.deplete.Settings() From fc6b3bd9d9a36b23efced294eb32b0e9a30b9a76 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 14 Feb 2018 13:39:40 -0600 Subject: [PATCH 218/282] Make Results.from_hdf5 a classmethod --- openmc/deplete/integrator/cecm.py | 2 +- openmc/deplete/results.py | 62 +++++++++++++------------------ 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 57a87c703..5432f172d 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -30,7 +30,7 @@ def cecm(operator, print_out=True): .. [ref] Isotalo, Aarno. "Comparison of Neutronics-Depletion Coupling Schemes - for Burnup Calculations—Continued Study." Nuclear Science and + for Burnup Calculations-Continued Study." Nuclear Science and Engineering 180.3 (2015): 286-300. Parameters diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index c08b3ef40..1a2ee4e43 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -302,7 +302,8 @@ class Results(object): if comm.rank == 0: time_dset[index, :] = self.time - def from_hdf5(self, handle, index): + @classmethod + def from_hdf5(cls, handle, index): """Loads results object from HDF5. Parameters @@ -312,6 +313,7 @@ class Results(object): index : int What step is this? """ + results = cls() # Grab handles number_dset = handle["/number"] @@ -319,15 +321,15 @@ class Results(object): seeds_dset = handle["/seeds"] time_dset = handle["/time"] - self.data = number_dset[index, :, :, :] - self.k = eigenvalues_dset[index, :] - self.seeds = seeds_dset[index, :] - self.time = time_dset[index, :] + results.data = number_dset[index, :, :, :] + results.k = eigenvalues_dset[index, :] + results.seeds = seeds_dset[index, :] + results.time = time_dset[index, :] # Reconstruct dictionaries - self.volume = OrderedDict() - self.mat_to_ind = OrderedDict() - self.nuc_to_ind = OrderedDict() + results.volume = OrderedDict() + results.mat_to_ind = OrderedDict() + results.nuc_to_ind = OrderedDict() rxn_nuc_to_ind = OrderedDict() rxn_to_ind = OrderedDict() @@ -336,13 +338,13 @@ class Results(object): vol = mat_handle.attrs["volume"] ind = mat_handle.attrs["index"] - self.volume[mat] = vol - self.mat_to_ind[mat] = ind + results.volume[mat] = vol + results.mat_to_ind[mat] = ind for nuc in handle["/nuclides"]: nuc_handle = handle["/nuclides/" + nuc] ind_atom = nuc_handle.attrs["atom number index"] - self.nuc_to_ind[nuc] = ind_atom + 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"] @@ -351,13 +353,15 @@ class Results(object): rxn_handle = handle["/reactions/" + rxn] rxn_to_ind[rxn] = rxn_handle.attrs["index"] - self.rates = [] + results.rates = [] # Reconstruct reactions - for i in range(self.n_stages): - rate = ReactionRates(self.mat_to_ind, rxn_nuc_to_ind, rxn_to_ind) + for i in range(results.n_stages): + rate = ReactionRates(results.mat_to_ind, rxn_nuc_to_ind, rxn_to_ind) rate.rates = handle["/reaction rates"][index, i, :, :, :] - self.rates.append(rate) + results.rates.append(rate) + + return results def get_dict(number): @@ -419,7 +423,7 @@ def write_results(result, filename, index): def read_results(filename): - """Reads out a list of results objects from an hdf5 file. + """Return a list of Results objects from an HDF5 file. Parameters ---------- @@ -430,26 +434,12 @@ def read_results(filename): ------- results : list of Results The result objects. + """ + with h5py.File(filename, "r") as fh: + assert fh["version"].value == RESULTS_VERSION - file = h5py.File(filename, "r") + # Get number of results stored + n = fh["number"].value.shape[0] - assert file["/version"].value == RESULTS_VERSION - - # Grab handles - number_dset = file["/number"] - - # Get number of results stored - number_shape = list(number_dset.shape) - number_results = number_shape[0] - - results = [] - - for i in range(number_results): - result = Results() - result.from_hdf5(file, i) - results.append(result) - - file.close() - - return results + return [Results.from_hdf5(fh, i) for i in range(n)] From 730623246f53e25fd6875b04953ae3633aab4e75 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 14 Feb 2018 15:36:00 -0600 Subject: [PATCH 219/282] Rename results.h5 -> depletion_results.h5. Use /materials in HDF5 file --- openmc/deplete/integrator/save_results.py | 4 ++-- openmc/deplete/results.py | 12 ++++-------- scripts/example_plot.py | 2 +- scripts/example_run.py | 1 - tests/regression_tests/test_deplete_full.py | 2 +- tests/regression_tests/test_reference.h5 | Bin 165384 -> 231608 bytes tests/unit_tests/test_deplete_cecm.py | 2 +- tests/unit_tests/test_deplete_integrator.py | 2 +- tests/unit_tests/test_deplete_predictor.py | 2 +- 9 files changed, 11 insertions(+), 16 deletions(-) diff --git a/openmc/deplete/integrator/save_results.py b/openmc/deplete/integrator/save_results.py index 4f20b52fd..f580af838 100644 --- a/openmc/deplete/integrator/save_results.py +++ b/openmc/deplete/integrator/save_results.py @@ -9,7 +9,7 @@ def save_results(op, x, rates, eigvls, seeds, t, step_ind): Parameters ---------- - op : Function + op : openmc.deplete.Operator 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. @@ -44,4 +44,4 @@ def save_results(op, x, rates, eigvls, seeds, t, step_ind): results.time = t results.rates = rates - write_results(results, "results.h5", step_ind) + write_results(results, "depletion_results.h5", step_ind) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 1a2ee4e43..fd48bb4df 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -52,7 +52,6 @@ class Results(object): self.k = None self.seeds = None self.time = None - self.p_terms = None self.rates = None self.volume = None @@ -192,7 +191,7 @@ class Results(object): n_rxn = len(rxn_list) n_stages = self.n_stages - mat_group = handle.create_group("cells") + mat_group = handle.create_group("materials") for mat in mat_list: mat_single_group = mat_group.create_group(mat) @@ -333,24 +332,21 @@ class Results(object): rxn_nuc_to_ind = OrderedDict() rxn_to_ind = OrderedDict() - for mat in handle["/cells"]: - mat_handle = handle["/cells/" + mat] + 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 in handle["/nuclides"]: - nuc_handle = handle["/nuclides/" + nuc] + 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 in handle["/reactions"]: - rxn_handle = handle["/reactions/" + rxn] + for rxn, rxn_handle in handle["/reactions"].items(): rxn_to_ind[rxn] = rxn_handle.attrs["index"] results.rates = [] diff --git a/scripts/example_plot.py b/scripts/example_plot.py index c92fef6bf..ab5ac204d 100644 --- a/scripts/example_plot.py +++ b/scripts/example_plot.py @@ -8,7 +8,7 @@ from openmc.deplete import (read_results, evaluate_single_nuclide, result_folder = "test" # Load data -results = read_results(result_folder + "/results.h5") +results = read_results(result_folder + "/deplete_results.h5") cell = "5" nuc = "Gd157" diff --git a/scripts/example_run.py b/scripts/example_run.py index 56d42b21a..30b6bdc2e 100644 --- a/scripts/example_run.py +++ b/scripts/example_run.py @@ -28,7 +28,6 @@ settings.particles = 1000 settings.batches = 100 settings.inactive = 40 settings.source = openmc.Source(space=openmc.stats.Box(lower_left, upper_right)) -settings.verbosity = 3 op = openmc.deplete.OpenMCOperator(geometry, settings) diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index fcaa60eee..e775e7af8 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -61,7 +61,7 @@ def test_full(run_in_tmpdir): openmc.deplete.integrator.predictor(op) # Load the files - res_test = results.read_results(settings.output_dir + "/results.h5") + res_test = results.read_results(settings.output_dir + "/depletion_results.h5") # Load the reference filename = str(Path(__file__).with_name('test_reference.h5')) diff --git a/tests/regression_tests/test_reference.h5 b/tests/regression_tests/test_reference.h5 index ef3ae0090943bc7ecdc01a1afaa70c0216e029f0..f832e3e2635c8d25a09609c38830227a3a551958 100644 GIT binary patch delta 27268 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 Q!*hq`vcUB6KWAq7$Bg66+$yY`3sm<}8}c9W2{B cSQs0YbHWrkOgv~leZqD|r|n&980&5V0K4QAp8x;= diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py index 264ad2167..7fe8c6a04 100644 --- a/tests/unit_tests/test_deplete_cecm.py +++ b/tests/unit_tests/test_deplete_cecm.py @@ -24,7 +24,7 @@ def test_cecm(run_in_tmpdir): openmc.deplete.cecm(op, print_out=False) # Load the files - res = results.read_results(settings.output_dir + "/results.h5") + res = results.read_results(settings.output_dir + "/depletion_results.h5") _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 3b6eed42d..964f99eee 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -80,7 +80,7 @@ def test_save_results(run_in_tmpdir): integrator.save_results(op, x2, rate2, eigvl2, seed2, t2, 1) # Load the files - res = results.read_results("results.h5") + res = results.read_results("depletion_results.h5") for i in range(stages): for mat_i, mat in enumerate(burn_list): diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index d808c46b8..8ec8964bb 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -24,7 +24,7 @@ def test_predictor(run_in_tmpdir): openmc.deplete.predictor(op, print_out=False) # Load the files - res = results.read_results(settings.output_dir + "/results.h5") + res = results.read_results(settings.output_dir + "/depletion_results.h5") _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") From b3106a65044a4978ad36fe75e9ca8ab0aae0092b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Feb 2018 06:37:46 -0600 Subject: [PATCH 220/282] Make Operator a context manager. Smart handling of output_dir --- openmc/deplete/__init__.py | 2 +- openmc/deplete/{function.py => abc.py} | 32 ++++- openmc/deplete/integrator/cecm.py | 142 +++++++++----------- openmc/deplete/integrator/predictor.py | 94 ++++++------- openmc/deplete/openmc_wrapper.py | 13 +- tests/dummy_geometry.py | 5 +- tests/regression_tests/test_deplete_full.py | 3 +- tests/unit_tests/test_deplete_cecm.py | 2 +- tests/unit_tests/test_deplete_predictor.py | 2 +- 9 files changed, 144 insertions(+), 151 deletions(-) rename openmc/deplete/{function.py => abc.py} (78%) diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index 19d1d1320..2467b973a 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -18,7 +18,7 @@ from .nuclide import * from .chain import * from .openmc_wrapper import * from .reaction_rates import * -from .function import * +from .abc import * from .results import * from .integrator import * from .utilities import * diff --git a/openmc/deplete/function.py b/openmc/deplete/abc.py similarity index 78% rename from openmc/deplete/function.py rename to openmc/deplete/abc.py index b9694d88b..63a80b45c 100644 --- a/openmc/deplete/function.py +++ b/openmc/deplete/abc.py @@ -4,6 +4,9 @@ This module contains the Operator class, which is then passed to an integrator to run a full depletion simulation. """ +import os +from pathlib import Path + from abc import ABCMeta, abstractmethod @@ -16,14 +19,22 @@ class Settings(object): ---------- dt_vec : numpy.array Array of time steps to take. - output_dir : str + output_dir : pathlib.Path Path to output directory to save results. - """ + """ def __init__(self): # Integrator specific self.dt_vec = None - self.output_dir = None + self.output_dir = Path('.') + + @property + def output_dir(self): + return self._output_dir + + @output_dir.setter + def output_dir(self, output_dir): + self._output_dir = Path(output_dir) class Operator(metaclass=ABCMeta): @@ -60,6 +71,20 @@ class Operator(metaclass=ABCMeta): """ pass + def __enter__(self): + # Save current directory and move to specific output directory + self._orig_dir = os.getcwd() + self.settings.output_dir.mkdir(exist_ok=True) + + # In Python 3.6+, chdir accepts a Path directly + os.chdir(str(self.settings.output_dir)) + + return self.initial_condition() + + def __exit__(self, exc_type, exc_value, traceback): + self.finalize() + os.chdir(self._orig_dir) + @abstractmethod def initial_condition(self): """Performs final setup and returns initial condition. @@ -112,6 +137,5 @@ class Operator(metaclass=ABCMeta): pass - @abstractmethod def finalize(self): pass diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 5432f172d..148b02b4a 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -41,96 +41,82 @@ def cecm(operator, print_out=True): Whether or not to print out time. """ - # Save current directory - dir_home = os.getcwd() - - # Move to folder - os.makedirs(operator.settings.output_dir, exist_ok=True) - os.chdir(operator.settings.output_dir) - # Generate initial conditions - vec = operator.initial_condition() + with operator as vec: + n_mats = len(vec) - n_mats = len(vec) + t = 0.0 - t = 0.0 + for i, dt in enumerate(operator.settings.dt_vec): + # Create vectors + x = [copy.deepcopy(vec)] + seeds = [] + eigvls = [] + rates_array = [] - for i, dt in enumerate(operator.settings.dt_vec): - # Create vectors + eigvl, rates, seed = operator(x[0]) + + eigvls.append(eigvl) + seeds.append(seed) + rates_array.append(rates) + + t_start = time.time() + + chains = repeat(operator.chain, n_mats) + vecs = (x[0][i] for i in range(n_mats)) + rates = (rates_array[0][i, :, :] for i in range(n_mats)) + dts = repeat(dt/2, n_mats) + + 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) + + x.append(x_result) + + eigvl, rates, seed = operator(x[1]) + + eigvls.append(eigvl) + seeds.append(seed) + rates_array.append(rates) + + t_start = time.time() + + chains = repeat(operator.chain, n_mats) + vecs = (x[0][i] for i in range(n_mats)) + rates = (rates_array[1][i, :, :] for i in range(n_mats)) + dts = repeat(dt, n_mats) + + 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) + + # Create results, write to disk + save_results(operator, x, rates_array, eigvls, seeds, [t, t + dt], i) + + t += dt + vec = copy.deepcopy(x_result) + + # Perform one last simulation x = [copy.deepcopy(vec)] seeds = [] eigvls = [] rates_array = [] - eigvl, rates, seed = operator(x[0]) eigvls.append(eigvl) seeds.append(seed) rates_array.append(rates) - t_start = time.time() - - chains = repeat(operator.chain, n_mats) - vecs = (x[0][i] for i in range(n_mats)) - rates = (rates_array[0][i, :, :] for i in range(n_mats)) - dts = repeat(dt/2, n_mats) - - 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) - - x.append(x_result) - - eigvl, rates, seed = operator(x[1]) - - eigvls.append(eigvl) - seeds.append(seed) - rates_array.append(rates) - - t_start = time.time() - - chains = repeat(operator.chain, n_mats) - vecs = (x[0][i] for i in range(n_mats)) - rates = (rates_array[1][i, :, :] for i in range(n_mats)) - dts = repeat(dt, n_mats) - - 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) - # Create results, write to disk - save_results(operator, x, rates_array, eigvls, seeds, [t, t + dt], i) - - t += dt - vec = copy.deepcopy(x_result) - - # Perform one last simulation - x = [copy.deepcopy(vec)] - seeds = [] - eigvls = [] - rates_array = [] - eigvl, rates, seed = operator(x[0]) - - eigvls.append(eigvl) - seeds.append(seed) - rates_array.append(rates) - - # Create results, write to disk - save_results(operator, x, rates_array, eigvls, seeds, [t, t], - len(operator.settings.dt_vec)) - - # Return to origin - os.chdir(dir_home) - - # Release resources - operator.finalize() + save_results(operator, x, rates_array, eigvls, seeds, [t, t], + len(operator.settings.dt_vec)) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 1b8d00600..8d499d1ff 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -32,27 +32,52 @@ def predictor(operator, print_out=True): Whether or not to print out time. """ - # Save current directory - dir_home = os.getcwd() - - # Move to folder - os.makedirs(operator.settings.output_dir, exist_ok=True) - os.chdir(operator.settings.output_dir) - # Generate initial conditions - vec = operator.initial_condition() + with operator as vec: + n_mats = len(vec) - n_mats = len(vec) + t = 0.0 - t = 0.0 + for i, dt in enumerate(operator.settings.dt_vec): + # Create vectors + x = [copy.deepcopy(vec)] + seeds = [] + eigvls = [] + rates_array = [] - for i, dt in enumerate(operator.settings.dt_vec): - # Create vectors + eigvl, rates, seed = operator(x[0]) + + eigvls.append(eigvl) + seeds.append(seed) + rates_array.append(rates) + + # Create results, write to disk + save_results(operator, x, rates_array, eigvls, seeds, [t, t + dt], i) + + t_start = time.time() + + chains = repeat(operator.chain, n_mats) + vecs = (x[0][i] for i in range(n_mats)) + rates = (rates_array[0][i, :, :] for i in range(n_mats)) + dts = repeat(dt, n_mats) + + 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) + + t += dt + vec = copy.deepcopy(x_result) + + # Perform one last simulation x = [copy.deepcopy(vec)] seeds = [] eigvls = [] rates_array = [] - eigvl, rates, seed = operator(x[0]) eigvls.append(eigvl) @@ -60,44 +85,5 @@ def predictor(operator, print_out=True): rates_array.append(rates) # Create results, write to disk - save_results(operator, x, rates_array, eigvls, seeds, [t, t + dt], i) - - t_start = time.time() - - chains = repeat(operator.chain, n_mats) - vecs = (x[0][i] for i in range(n_mats)) - rates = (rates_array[0][i, :, :] for i in range(n_mats)) - dts = repeat(dt, n_mats) - - 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) - - t += dt - vec = copy.deepcopy(x_result) - - # Perform one last simulation - x = [copy.deepcopy(vec)] - seeds = [] - eigvls = [] - rates_array = [] - eigvl, rates, seed = operator(x[0]) - - eigvls.append(eigvl) - seeds.append(seed) - rates_array.append(rates) - - # Create results, write to disk - save_results(operator, x, rates_array, eigvls, seeds, [t, t], - len(operator.settings.dt_vec)) - - # Return to origin - os.chdir(dir_home) - - # Release resources - operator.finalize() + save_results(operator, x, rates_array, eigvls, seeds, [t, t], + len(operator.settings.dt_vec)) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 3dce9db7b..701a35b7d 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -23,12 +23,10 @@ import openmc import openmc.capi from openmc.data import JOULE_PER_EV from . import comm +from .abc import Settings, Operator from .atom_number import AtomNumber from .chain import Chain from .reaction_rates import ReactionRates -from .function import Settings, Operator - - def _chunks(items, n): @@ -49,7 +47,7 @@ class OpenMCSettings(Settings): ---------- dt_vec : numpy.array Array of time steps to in units of [s] - output_dir : str + output_dir : pathlib.Path Path to output directory to save results. chain_file : str Path to the depletion chain xml file. Defaults to the @@ -70,7 +68,7 @@ class OpenMCSettings(Settings): """ - _depletion_attrs = {'dt_vec', 'output_dir', 'chain_file', 'dilute_initial', + _depletion_attrs = {'dt_vec', '_output_dir', 'chain_file', 'dilute_initial', 'round_number', 'power'} def __init__(self): @@ -87,7 +85,10 @@ class OpenMCSettings(Settings): self.__dict__['settings'] = openmc.Settings() def __setattr__(self, name, value): - if name in self._depletion_attrs: + if hasattr(self.__class__, name): + prop = getattr(self.__class__, name) + prop.fset(self, value) + elif name in self._depletion_attrs: self.__dict__[name] = value else: setattr(self.__dict__['settings'], name, value) diff --git a/tests/dummy_geometry.py b/tests/dummy_geometry.py index 585c1b11c..aab396b85 100644 --- a/tests/dummy_geometry.py +++ b/tests/dummy_geometry.py @@ -1,7 +1,7 @@ import numpy as np import scipy.sparse as sp from openmc.deplete.reaction_rates import ReactionRates -from openmc.deplete.function import Operator +from openmc.deplete.abc import Operator class DummyGeometry(Operator): @@ -53,9 +53,6 @@ class DummyGeometry(Operator): # Create a fake rates object return 0.0, reaction_rates, 0 - def finalize(self): - pass - @property def chain(self): return self diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index e775e7af8..c809ba053 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -43,7 +43,6 @@ def test_full(run_in_tmpdir): 'chain_simple.xml') settings.power = 2.337e15*4*JOULE_PER_EV*1e6 # MeV/second cm from CASMO settings.dt_vec = dt - settings.output_dir = "test_full" settings.round_number = True # Add OpenMC-specific settings @@ -61,7 +60,7 @@ def test_full(run_in_tmpdir): openmc.deplete.integrator.predictor(op) # Load the files - res_test = results.read_results(settings.output_dir + "/depletion_results.h5") + res_test = results.read_results(settings.output_dir / "depletion_results.h5") # Load the reference filename = str(Path(__file__).with_name('test_reference.h5')) diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py index 7fe8c6a04..66c3ee156 100644 --- a/tests/unit_tests/test_deplete_cecm.py +++ b/tests/unit_tests/test_deplete_cecm.py @@ -24,7 +24,7 @@ def test_cecm(run_in_tmpdir): openmc.deplete.cecm(op, print_out=False) # Load the files - res = results.read_results(settings.output_dir + "/depletion_results.h5") + res = results.read_results(settings.output_dir / "depletion_results.h5") _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index 8ec8964bb..f1133f87d 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -24,7 +24,7 @@ def test_predictor(run_in_tmpdir): openmc.deplete.predictor(op, print_out=False) # Load the files - res = results.read_results(settings.output_dir + "/depletion_results.h5") + res = results.read_results(settings.output_dir / "depletion_results.h5") _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") From 484a0238888315242c5327d9a4ef7163ba806e64 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Feb 2018 07:03:01 -0600 Subject: [PATCH 221/282] Move more attributes to abc.Settings --- openmc/deplete/abc.py | 20 ++++++++++++++++++-- openmc/deplete/openmc_wrapper.py | 15 ++++++--------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 63a80b45c..e2b286d6a 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -21,12 +21,28 @@ class Settings(object): Array of time steps to take. output_dir : pathlib.Path Path to output directory to save results. + chain_file : str + Path to the depletion chain xml file. Defaults to the + :envvar:`OPENDEPLETE_CHAIN` environment variable if it exists. + 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. + power : float + Power of the reactor in [W]. 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. """ def __init__(self): - # Integrator specific + try: + self.chain_file = os.environ["OPENDEPLETE_CHAIN"] + except KeyError: + self.chain_file = None self.dt_vec = None - self.output_dir = Path('.') + self.output_dir = '.' + self.power = None + self.dilute_initial = 1.0e3 @property def output_dir(self): diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 701a35b7d..b5b91f2ac 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -56,13 +56,13 @@ class OpenMCSettings(Settings): 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. - 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. power : float Power of the reactor in [W]. 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. + 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. settings : openmc.Settings Settings for OpenMC simulations @@ -73,24 +73,21 @@ class OpenMCSettings(Settings): def __init__(self): super().__init__() - try: - self.chain_file = os.environ["OPENDEPLETE_CHAIN"] - except KeyError: - self.chain_file = None - self.dilute_initial = 1.0e3 self.round_number = False - self.power = None # Avoid setattr to create OpenMC settings self.__dict__['settings'] = openmc.Settings() def __setattr__(self, name, value): if hasattr(self.__class__, name): + # Use properties when appropriate prop = getattr(self.__class__, name) prop.fset(self, value) elif name in self._depletion_attrs: + # For known attributes, store in dictionary self.__dict__[name] = value else: + # otherwise, delegate to openmc.Settings setattr(self.__dict__['settings'], name, value) def __getattr__(self, name): From 998a562a33b214fa41f62166ebd78c83eb77aed8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Feb 2018 09:47:37 -0600 Subject: [PATCH 222/282] Have Operator() return a namedtuple (simplifies integrators quite a bit) --- openmc/deplete/abc.py | 4 ++ openmc/deplete/integrator/cecm.py | 58 ++++++--------------- openmc/deplete/integrator/predictor.py | 42 +++++---------- openmc/deplete/integrator/save_results.py | 20 +++---- openmc/deplete/openmc_wrapper.py | 4 +- tests/dummy_geometry.py | 4 +- tests/unit_tests/test_deplete_integrator.py | 15 ++++-- 7 files changed, 55 insertions(+), 92 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index e2b286d6a..155261742 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -4,6 +4,7 @@ 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 @@ -53,6 +54,9 @@ class Settings(object): self._output_dir = Path(output_dir) +OperatorResult = namedtuple('OperatorResult', ['k', 'rates', 'seed']) + + class Operator(metaclass=ABCMeta): """Abstract class defining all methods needed for the integrator. diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 148b02b4a..16fa299fd 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -12,7 +12,7 @@ from .save_results import save_results def cecm(operator, print_out=True): - """The CE/CM integrator. + r"""The CE/CM integrator. Implements the second order CE/CM Predictor-Corrector algorithm [ref]_. This algorithm is mathematically defined as: @@ -22,11 +22,11 @@ def cecm(operator, print_out=True): A_p &= A(y_n, t_n) - y_m &= \\text{expm}(A_p h/2) y_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 + y_{n+1} &= \text{expm}(A_c h) y_n .. [ref] Isotalo, Aarno. "Comparison of Neutronics-Depletion Coupling Schemes @@ -35,88 +35,64 @@ def cecm(operator, print_out=True): Parameters ---------- - operator : Operator + operator : openmc.deplete.Operator The operator object to simulate on. print_out : bool, optional Whether or not to print out time. - """ + """ # Generate initial conditions with operator as vec: n_mats = len(vec) t = 0.0 - for i, dt in enumerate(operator.settings.dt_vec): - # Create vectors + # Get beginning-of-timestep reaction rates x = [copy.deepcopy(vec)] - seeds = [] - eigvls = [] - rates_array = [] - - eigvl, rates, seed = operator(x[0]) - - eigvls.append(eigvl) - seeds.append(seed) - rates_array.append(rates) + results = [operator(x[0])] + # Deplete for first half of timestep t_start = time.time() - chains = repeat(operator.chain, n_mats) vecs = (x[0][i] for i in range(n_mats)) - rates = (rates_array[0][i, :, :] for i in range(n_mats)) + rates = (results[0].rates[i, :, :] for i in range(n_mats)) dts = repeat(dt/2, n_mats) - 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) + # Get middle-of-timestep reaction rates x.append(x_result) + results.append(operator(x_result)) - eigvl, rates, seed = operator(x[1]) - - eigvls.append(eigvl) - seeds.append(seed) - rates_array.append(rates) - + # Deplete for second half of timestep t_start = time.time() - chains = repeat(operator.chain, n_mats) vecs = (x[0][i] for i in range(n_mats)) - rates = (rates_array[1][i, :, :] for i in range(n_mats)) + rates = (results[1].rates[i, :, :] for i in range(n_mats)) dts = repeat(dt, n_mats) - 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) # Create results, write to disk - save_results(operator, x, rates_array, eigvls, seeds, [t, t + dt], i) + save_results(operator, x, results, [t, t + dt], i) + # Advance time, update vector t += dt vec = copy.deepcopy(x_result) # Perform one last simulation x = [copy.deepcopy(vec)] - seeds = [] - eigvls = [] - rates_array = [] - eigvl, rates, seed = operator(x[0]) - - eigvls.append(eigvl) - seeds.append(seed) - rates_array.append(rates) + results = [operator(x[0])] # Create results, write to disk - save_results(operator, x, rates_array, eigvls, seeds, [t, t], - len(operator.settings.dt_vec)) + save_results(operator, x, results, [t, t], len(operator.settings.dt_vec)) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 8d499d1ff..872b5ebb1 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -12,7 +12,7 @@ from .save_results import save_results def predictor(operator, print_out=True): - """The basic predictor integrator. + r"""The basic predictor integrator. Implements the first order predictor algorithm. This algorithm is mathematically defined as: @@ -22,68 +22,50 @@ def predictor(operator, print_out=True): A_p &= A(y_n, t_n) - y_{n+1} &= \\text{expm}(A_p h) y_n + y_{n+1} &= \text{expm}(A_p h) y_n Parameters ---------- - operator : Operator + operator : openmc.deplete.Operator The operator object to simulate on. print_out : bool, optional Whether or not to print out time. - """ + """ # Generate initial conditions with operator as vec: n_mats = len(vec) t = 0.0 - for i, dt in enumerate(operator.settings.dt_vec): - # Create vectors + # Get beginning-of-timestep reaction rates x = [copy.deepcopy(vec)] - seeds = [] - eigvls = [] - rates_array = [] - - eigvl, rates, seed = operator(x[0]) - - eigvls.append(eigvl) - seeds.append(seed) - rates_array.append(rates) + results = [operator(x[0])] # Create results, write to disk - save_results(operator, x, rates_array, eigvls, seeds, [t, t + dt], i) + save_results(operator, x, results, [t, t + dt], i) + # Deplete for full timestep t_start = time.time() - chains = repeat(operator.chain, n_mats) vecs = (x[0][i] for i in range(n_mats)) - rates = (rates_array[0][i, :, :] for i in range(n_mats)) + rates = (results[0].rates[i, :, :] for i in range(n_mats)) dts = repeat(dt, n_mats) - 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) + # Advance time, update vector t += dt vec = copy.deepcopy(x_result) # Perform one last simulation x = [copy.deepcopy(vec)] - seeds = [] - eigvls = [] - rates_array = [] - eigvl, rates, seed = operator(x[0]) - - eigvls.append(eigvl) - seeds.append(seed) - rates_array.append(rates) + results = [operator(x[0])] # Create results, write to disk - save_results(operator, x, rates_array, eigvls, seeds, [t, t], - len(operator.settings.dt_vec)) + save_results(operator, x, results, [t, t], len(operator.settings.dt_vec)) diff --git a/openmc/deplete/integrator/save_results.py b/openmc/deplete/integrator/save_results.py index f580af838..31cd9b288 100644 --- a/openmc/deplete/integrator/save_results.py +++ b/openmc/deplete/integrator/save_results.py @@ -4,8 +4,8 @@ from ..results import Results, write_results -def save_results(op, x, rates, eigvls, seeds, t, step_ind): - """ Creates and writes results to disk +def save_results(op, x, op_results, t, step_ind): + """Creates and writes depletion results to disk Parameters ---------- @@ -13,18 +13,14 @@ def save_results(op, x, rates, eigvls, seeds, t, step_ind): 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. - rates : list of ReactionRates - The reaction rates for each substep. - eigvls : list of float - Eigenvalue for each substep - seeds : list of int - Seeds for each substep. + op_results : list of openmc.deplete.OperatorResult + Results of applying transport operator t : list of float Time indices. step_ind : int Step index. - """ + """ # Get indexing terms vol_list, nuc_list, burn_list, full_burn_list = op.get_results_info() @@ -39,9 +35,9 @@ def save_results(op, x, rates, eigvls, seeds, t, step_ind): for mat_i in range(n_mat): results[i, mat_i, :] = x[i][mat_i][:] - results.k = eigvls - results.seeds = seeds + results.k = [r.k for r in op_results] + results.seeds = [r.seed for r in op_results] + results.rates = [r.rates for r in op_results] results.time = t - results.rates = rates write_results(results, "depletion_results.h5", step_ind) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index b5b91f2ac..da397c014 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -23,7 +23,7 @@ import openmc import openmc.capi from openmc.data import JOULE_PER_EV from . import comm -from .abc import Settings, Operator +from .abc import Settings, Operator, OperatorResult from .atom_number import AtomNumber from .chain import Chain from .reaction_rates import ReactionRates @@ -255,7 +255,7 @@ class OpenMCOperator(Operator): print("Time to openmc: ", time_openmc - time_start) print("Time to unpack: ", time_unpack - time_openmc) - return k, copy.deepcopy(self.reaction_rates), self.seed + return OperatorResult(k, copy.deepcopy(self.reaction_rates), self.seed) def extract_mat_ids(self): """Extracts materials and assigns them to processes. diff --git a/tests/dummy_geometry.py b/tests/dummy_geometry.py index aab396b85..ceafc3fdc 100644 --- a/tests/dummy_geometry.py +++ b/tests/dummy_geometry.py @@ -1,7 +1,7 @@ import numpy as np import scipy.sparse as sp from openmc.deplete.reaction_rates import ReactionRates -from openmc.deplete.abc import Operator +from openmc.deplete.abc import Operator, OperatorResult class DummyGeometry(Operator): @@ -51,7 +51,7 @@ class DummyGeometry(Operator): reaction_rates[0, 1, 0] = vec[0][1] # Create a fake rates object - return 0.0, reaction_rates, 0 + return OperatorResult(0.0, reaction_rates, 0) @property def chain(self): diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 964f99eee..faccd3392 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -11,7 +11,8 @@ import os from unittest.mock import MagicMock import numpy as np -from openmc.deplete import integrator, ReactionRates, results, comm +from openmc.deplete import (integrator, ReactionRates, results, comm, + OperatorResult) def test_save_results(run_in_tmpdir): @@ -49,8 +50,8 @@ def test_save_results(run_in_tmpdir): x2.append([np.random.rand(2), np.random.rand(2)]) # Construct r - cell_dict = {s:i for i, s in enumerate(burn_list)} - r1 = ReactionRates(cell_dict, {"na":0, "nb":1}, {"ra":0, "rb":1}) + cell_dict = {s: i for i, s in enumerate(burn_list)} + r1 = ReactionRates(cell_dict, {"na": 0, "nb": 1}, {"ra": 0, "rb": 1}) r1.rates = np.random.rand(2, 2, 2) rate1 = [] @@ -76,8 +77,12 @@ def test_save_results(run_in_tmpdir): t1 = [0.0, 1.0] t2 = [1.0, 2.0] - integrator.save_results(op, x1, rate1, eigvl1, seed1, t1, 0) - integrator.save_results(op, x2, rate2, eigvl2, seed2, t2, 1) + op_result1 = [OperatorResult(k, rates, seed) + for k, rates, seed in zip(eigvl1, rate1, seed1)] + op_result2 = [OperatorResult(k, rates, seed) + for k, rates, seed in zip(eigvl2, rate2, seed2)] + integrator.save_results(op, x1, op_result1, t1, 0) + integrator.save_results(op, x2, op_result2, t2, 1) # Load the files res = results.read_results("depletion_results.h5") From b62e25bcf5f84e5178990519a5e6c2ec433cb7bd Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Feb 2018 10:39:16 -0600 Subject: [PATCH 223/282] Simplify integrator implementations by separating out function for depletion --- openmc/deplete/integrator/cecm.py | 46 +++++------------------- openmc/deplete/integrator/cram.py | 50 ++++++++++++++++++++++++++ openmc/deplete/integrator/predictor.py | 29 ++++----------- 3 files changed, 65 insertions(+), 60 deletions(-) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 16fa299fd..760e3c89d 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -1,13 +1,8 @@ -""" The CE/CM integrator.""" +"""The CE/CM integrator.""" import copy -from itertools import repeat -import os -from multiprocessing import Pool -import time -from .. import comm -from .cram import CRAM48, cram_wrapper +from .cram import deplete from .save_results import save_results @@ -43,8 +38,7 @@ def cecm(operator, print_out=True): """ # Generate initial conditions with operator as vec: - n_mats = len(vec) - + chain = operator.chain t = 0.0 for i, dt in enumerate(operator.settings.dt_vec): # Get beginning-of-timestep reaction rates @@ -52,43 +46,21 @@ def cecm(operator, print_out=True): results = [operator(x[0])] # Deplete for first half of timestep - t_start = time.time() - chains = repeat(operator.chain, n_mats) - vecs = (x[0][i] for i in range(n_mats)) - rates = (results[0].rates[i, :, :] for i in range(n_mats)) - dts = repeat(dt/2, n_mats) - 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) + x_middle = deplete(chain, x[0], results[0], dt/2, print_out) # Get middle-of-timestep reaction rates - x.append(x_result) - results.append(operator(x_result)) + x.append(x_middle) + results.append(operator(x_middle)) - # Deplete for second half of timestep - t_start = time.time() - chains = repeat(operator.chain, n_mats) - vecs = (x[0][i] for i in range(n_mats)) - rates = (results[1].rates[i, :, :] for i in range(n_mats)) - dts = repeat(dt, n_mats) - 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) + # Deplete for full timestep using beginning-of-step materials + x_end = deplete(chain, x[0], results[1], dt, print_out) # Create results, write to disk save_results(operator, x, results, [t, t + dt], i) # Advance time, update vector t += dt - vec = copy.deepcopy(x_result) + vec = copy.deepcopy(x_end) # Perform one last simulation x = [copy.deepcopy(vec)] diff --git a/openmc/deplete/integrator/cram.py b/openmc/deplete/integrator/cram.py index 56476384c..09207fbc6 100644 --- a/openmc/deplete/integrator/cram.py +++ b/openmc/deplete/integrator/cram.py @@ -3,10 +3,60 @@ 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 diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 872b5ebb1..064078331 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -1,20 +1,15 @@ -""" The Predictor algorithm.""" +"""The Predictor algorithm.""" import copy -from itertools import repeat -import os -from multiprocessing import Pool -import time -from .. import comm -from .cram import CRAM48, cram_wrapper +from .cram import deplete from .save_results import save_results def predictor(operator, print_out=True): r"""The basic predictor integrator. - Implements the first order predictor algorithm. This algorithm is + Implements the first-order predictor algorithm. This algorithm is mathematically defined as: .. math:: @@ -34,8 +29,7 @@ def predictor(operator, print_out=True): """ # Generate initial conditions with operator as vec: - n_mats = len(vec) - + chain = operator.chain t = 0.0 for i, dt in enumerate(operator.settings.dt_vec): # Get beginning-of-timestep reaction rates @@ -46,22 +40,11 @@ def predictor(operator, print_out=True): save_results(operator, x, results, [t, t + dt], i) # Deplete for full timestep - t_start = time.time() - chains = repeat(operator.chain, n_mats) - vecs = (x[0][i] for i in range(n_mats)) - rates = (results[0].rates[i, :, :] for i in range(n_mats)) - dts = repeat(dt, n_mats) - 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) + x_end = deplete(chain, x[0], results[0], dt, print_out) # Advance time, update vector t += dt - vec = copy.deepcopy(x_result) + vec = copy.deepcopy(x_end) # Perform one last simulation x = [copy.deepcopy(vec)] From bc4d631883032791249d8e63824a0fbe159d7af9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Feb 2018 11:02:48 -0600 Subject: [PATCH 224/282] Get rid of deplete.Materials class that wasn't used --- openmc/deplete/openmc_wrapper.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index da397c014..4668249b1 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -97,25 +97,6 @@ class OpenMCSettings(Settings): return getattr(self.__dict__['settings'], name) -class Materials(object): - """The Materials class. - - Contains information about cross sections for a cell. - - Attributes - ---------- - temperature : float - Temperature in Kelvin for each region. - sab : str or list of str - ENDF S(a,b) name for a region that needs S(a,b) data. Not set if no - S(a,b) needed for region. - """ - - def __init__(self): - self.temperature = None - self.sab = None - - class OpenMCOperator(Operator): """The OpenMC Operator class. @@ -134,8 +115,6 @@ class OpenMCOperator(Operator): Settings object. (From Operator) geometry : openmc.Geometry The OpenMC geometry object. - materials : list of Materials - Materials to be used for this simulation. seed : int The RNG seed used in last OpenMC run. number : openmc.deplete.AtomNumber From 8a41bac17abdb16868dcbb6a4666e80a3f9b493c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Feb 2018 14:31:03 -0600 Subject: [PATCH 225/282] Removing a few attributes on OpenMCOperator --- openmc/deplete/abc.py | 2 +- openmc/deplete/integrator/save_results.py | 1 - openmc/deplete/openmc_wrapper.py | 27 +++------------------ openmc/deplete/results.py | 15 +----------- tests/dummy_geometry.py | 2 +- tests/unit_tests/test_deplete_integrator.py | 12 ++------- 6 files changed, 9 insertions(+), 50 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 155261742..23322d91c 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -54,7 +54,7 @@ class Settings(object): self._output_dir = Path(output_dir) -OperatorResult = namedtuple('OperatorResult', ['k', 'rates', 'seed']) +OperatorResult = namedtuple('OperatorResult', ['k', 'rates']) class Operator(metaclass=ABCMeta): diff --git a/openmc/deplete/integrator/save_results.py b/openmc/deplete/integrator/save_results.py index 31cd9b288..8a0ae9204 100644 --- a/openmc/deplete/integrator/save_results.py +++ b/openmc/deplete/integrator/save_results.py @@ -36,7 +36,6 @@ def save_results(op, x, op_results, t, step_ind): results[i, mat_i, :] = x[i][mat_i][:] results.k = [r.k for r in op_results] - results.seeds = [r.seed for r in op_results] results.rates = [r.rates for r in op_results] results.time = t diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 4668249b1..d9a5d405a 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -115,8 +115,6 @@ class OpenMCOperator(Operator): Settings object. (From Operator) geometry : openmc.Geometry The OpenMC geometry object. - seed : int - The RNG seed used in last OpenMC run. number : openmc.deplete.AtomNumber Total number of atoms in simulation. participating_nuclides : set of str @@ -125,10 +123,6 @@ class OpenMCOperator(Operator): The depletion chain information necessary to form matrices and tallies. reaction_rates : openmc.deplete.ReactionRates Reaction rates from the last operator step. - power : OrderedDict of str to float - Material-by-Material power. Indexed by material ID. - mat_name : OrderedDict of str to int - The name of region each material is set to. Indexed by material ID. burn_mat_to_id : OrderedDict of str to int Dictionary mapping material ID (as a string) to an index in reaction_rates. burn_nuc_to_id : OrderedDict of str to int @@ -144,12 +138,9 @@ class OpenMCOperator(Operator): super().__init__(settings) self.geometry = geometry - self.seed = 0 self.number = None self.participating_nuclides = None self.reaction_rates = None - self.power = None - self.mat_name = OrderedDict() self.burn_mat_to_ind = OrderedDict() self.burn_nuc_to_ind = None @@ -196,16 +187,10 @@ class OpenMCOperator(Operator): Returns ------- - mat : list of scipy.sparse.csr_matrix - Matrices for the next step. - k : float - Eigenvalue of the problem. - rates : openmc.deplete.ReactionRates - Reaction rates from this simulation. - seed : int - Seed for this simulation. - """ + openmc.deplete.OperatorResult + Eigenvalue and reaction rates resulting from transport operator + """ # Prevent OpenMC from complaining about re-creating tallies openmc.reset_auto_ids() @@ -234,7 +219,7 @@ class OpenMCOperator(Operator): print("Time to openmc: ", time_openmc - time_start) print("Time to unpack: ", time_unpack - time_openmc) - return OperatorResult(k, copy.deepcopy(self.reaction_rates), self.seed) + return OperatorResult(k, copy.deepcopy(self.reaction_rates)) def extract_mat_ids(self): """Extracts materials and assigns them to processes. @@ -262,8 +247,6 @@ class OpenMCOperator(Operator): # Iterate once through the geometry to get dictionaries cells = self.geometry.get_all_material_cells() for cell in cells.values(): - name = cell.name - if isinstance(cell.fill, openmc.Material): mat = cell.fill for nuclide in mat.get_nuclide_densities(): @@ -273,7 +256,6 @@ class OpenMCOperator(Operator): volume[str(mat.id)] = mat.volume else: mat_not_burn.add(str(mat.id)) - self.mat_name[mat.id] = name else: for mat in cell.fill: for nuclide in mat.get_nuclide_densities(): @@ -283,7 +265,6 @@ class OpenMCOperator(Operator): volume[str(mat.id)] = mat.volume else: mat_not_burn.add(str(mat.id)) - self.mat_name[mat.id] = name need_vol = [] diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index fd48bb4df..37c4b6e92 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -22,8 +22,6 @@ class Results(object): ---------- k : list of float Eigenvalue for each substep. - seeds : list of int - Seeds for each substep. time : list of float Time at beginning, end of step, in seconds. n_mat : int @@ -46,11 +44,10 @@ class Results(object): Number of stages in simulation. data : numpy.array Atom quantity, stored by stage, mat, then by nuclide. - """ + """ def __init__(self): self.k = None - self.seeds = None self.time = None self.rates = None self.volume = None @@ -227,8 +224,6 @@ class Results(object): handle.create_dataset("eigenvalues", (1, n_stages), maxshape=(None, n_stages), dtype='float64') - handle.create_dataset("seeds", (1, n_stages), maxshape=(None, n_stages), dtype='int64') - handle.create_dataset("time", (1, 2), maxshape=(None, 2), dtype='float64') def to_hdf5(self, handle, index): @@ -252,7 +247,6 @@ class Results(object): number_dset = handle["/number"] rxn_dset = handle["/reaction rates"] eigenvalues_dset = handle["/eigenvalues"] - seeds_dset = handle["/seeds"] time_dset = handle["/time"] # Get number of results stored @@ -274,10 +268,6 @@ class Results(object): eigenvalues_shape[0] = new_shape eigenvalues_dset.resize(eigenvalues_shape) - seeds_shape = list(seeds_dset.shape) - seeds_shape[0] = new_shape - seeds_dset.resize(seeds_shape) - time_shape = list(time_dset.shape) time_shape[0] = new_shape time_dset.resize(time_shape) @@ -297,7 +287,6 @@ class Results(object): rxn_dset[index, i, low:high+1, :, :] = self.rates[i][:, :, :] if comm.rank == 0: eigenvalues_dset[index, i] = self.k[i] - seeds_dset[index, i] = self.seeds[i] if comm.rank == 0: time_dset[index, :] = self.time @@ -317,12 +306,10 @@ class Results(object): # Grab handles number_dset = handle["/number"] eigenvalues_dset = handle["/eigenvalues"] - seeds_dset = handle["/seeds"] time_dset = handle["/time"] results.data = number_dset[index, :, :, :] results.k = eigenvalues_dset[index, :] - results.seeds = seeds_dset[index, :] results.time = time_dset[index, :] # Reconstruct dictionaries diff --git a/tests/dummy_geometry.py b/tests/dummy_geometry.py index ceafc3fdc..c013bb005 100644 --- a/tests/dummy_geometry.py +++ b/tests/dummy_geometry.py @@ -51,7 +51,7 @@ class DummyGeometry(Operator): reaction_rates[0, 1, 0] = vec[0][1] # Create a fake rates object - return OperatorResult(0.0, reaction_rates, 0) + return OperatorResult(0.0, reaction_rates) @property def chain(self): diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index faccd3392..59d08b484 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -66,21 +66,15 @@ def test_save_results(run_in_tmpdir): # Create global terms eigvl1 = np.random.rand(stages) eigvl2 = np.random.rand(stages) - seed1 = [np.random.randint(100) for i in range(stages)] - seed2 = [np.random.randint(100) for i in range(stages)] eigvl1 = comm.bcast(eigvl1, root=0) eigvl2 = comm.bcast(eigvl2, root=0) - seed1 = comm.bcast(seed1, root=0) - seed2 = comm.bcast(seed2, root=0) t1 = [0.0, 1.0] t2 = [1.0, 2.0] - op_result1 = [OperatorResult(k, rates, seed) - for k, rates, seed in zip(eigvl1, rate1, seed1)] - op_result2 = [OperatorResult(k, rates, seed) - for k, rates, seed in zip(eigvl2, rate2, seed2)] + op_result1 = [OperatorResult(k, rates) for k, rates in zip(eigvl1, rate1)] + op_result2 = [OperatorResult(k, rates) for k, rates in zip(eigvl2, rate2)] integrator.save_results(op, x1, op_result1, t1, 0) integrator.save_results(op, x2, op_result2, t2, 1) @@ -98,9 +92,7 @@ def test_save_results(run_in_tmpdir): rate2[i][mat, nuc, :]) np.testing.assert_array_equal(res[0].k, eigvl1) - np.testing.assert_array_equal(res[0].seeds, seed1) np.testing.assert_array_equal(res[0].time, t1) np.testing.assert_array_equal(res[1].k, eigvl2) - np.testing.assert_array_equal(res[1].seeds, seed2) np.testing.assert_array_equal(res[1].time, t2) From 3bbd1774537bd2cfd5c0d775caefd7cfdd0f290f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Feb 2018 14:53:11 -0600 Subject: [PATCH 226/282] Have unpack_tallies_and_normalize return an OperatorResult --- openmc/deplete/abc.py | 11 ++++------- openmc/deplete/openmc_wrapper.py | 28 +++++++++++----------------- openmc/deplete/reaction_rates.py | 2 +- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 23322d91c..b2594d2c8 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -58,7 +58,7 @@ OperatorResult = namedtuple('OperatorResult', ['k', 'rates']) class Operator(metaclass=ABCMeta): - """Abstract class defining all methods needed for the integrator. + """Abstract class defining a transport operator Attributes ---------- @@ -82,12 +82,9 @@ class Operator(metaclass=ABCMeta): Returns ------- - k : float - Eigenvalue of the problem. - rates : ReactionRates - Reaction rates from this simulation. - seed : int - Seed for this simulation. + openmc.deplete.OperatorResult + Eigenvalue and reaction rates resulting from transport operator + """ pass diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index d9a5d405a..65ed4793d 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -98,9 +98,7 @@ class OpenMCSettings(Settings): class OpenMCOperator(Operator): - """The OpenMC Operator class. - - Provides Operator functions for OpenMC. + """OpenMC transport operator Parameters ---------- @@ -210,7 +208,7 @@ class OpenMCOperator(Operator): time_openmc = time.time() # Extract results - k = self.unpack_tallies_and_normalize() + op_result = self.unpack_tallies_and_normalize() if comm.rank == 0: time_unpack = time.time() @@ -219,7 +217,7 @@ class OpenMCOperator(Operator): print("Time to openmc: ", time_openmc - time_start) print("Time to unpack: ", time_unpack - time_openmc) - return OperatorResult(k, copy.deepcopy(self.reaction_rates)) + return copy.deepcopy(op_result) def extract_mat_ids(self): """Extracts materials and assigns them to processes. @@ -561,23 +559,19 @@ class OpenMCOperator(Operator): self.number.set_mat_slice(i, total_density[i]) def unpack_tallies_and_normalize(self): - """Unpack tallies from OpenMC + """Unpack tallies from OpenMC and return an operator result - This function reads the tallies generated by OpenMC (from the tally.xml - file generated in generate_tally_xml) normalizes them so that the total - power generated is new_power, and then stores them in the reaction rate - database. + 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. Returns ------- - k : float - Eigenvalue of the last simulation. + openmc.deplete.OperatorResult + Eigenvalue and reaction rates resulting from transport operator - Todo - ---- - Provide units for power """ - rates = self.reaction_rates rates[:, :, :] = 0.0 @@ -655,7 +649,7 @@ class OpenMCOperator(Operator): # Scale reaction rates to obtain units of reactions/sec rates[:, :, :] *= power / energy - return k_combined + return OperatorResult(k_combined, rates) def load_participating(self): """Loads a cross_sections.xml file to find participating nuclides. diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py index de3a6a728..e8bab101b 100644 --- a/openmc/deplete/reaction_rates.py +++ b/openmc/deplete/reaction_rates.py @@ -23,7 +23,7 @@ class ReactionRates(object): Attributes ---------- mat_to_ind : OrderedDict of str to int - A dictionary mapping cell ID as string to index. + A dictionary mapping material ID as string to index. nuc_to_ind : OrderedDict of str to int A dictionary mapping nuclide name as string to index. react_to_ind : OrderedDict of str to int From 05afc55a88c11200d387519de6a71be9063dbbbc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Feb 2018 15:34:00 -0600 Subject: [PATCH 227/282] Give deplete.Chain some special methods to make it more Pythonic --- openmc/deplete/chain.py | 34 +++++++++----------------- openmc/deplete/openmc_wrapper.py | 9 +++---- tests/unit_tests/test_deplete_chain.py | 25 ++++++++++--------- 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 03decb72a..846b3af56 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -113,8 +113,6 @@ class Chain(object): Attributes ---------- - n_nuclides : int - Number of nuclides in chain. nuclides : list of Nuclide List of nuclides in chain. nuclide_dict : OrderedDict of str to int @@ -132,8 +130,14 @@ class Chain(object): self.nuc_to_react_ind = OrderedDict() self.react_to_ind = OrderedDict() - @property - def n_nuclides(self): + 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) @@ -381,14 +385,14 @@ class Chain(object): Parameters ---------- rates : numpy.ndarray - 2D array indexed by nuclide then by cell. + 2D array indexed by (nuclide, reaction) Returns ------- scipy.sparse.csr_matrix Sparse matrix representing depletion. - """ + """ matrix = defaultdict(float) reactions = set() @@ -450,21 +454,7 @@ class Chain(object): reactions.clear() # Use DOK matrix as intermediate representation, then convert to CSR and return - matrix_dok = sp.dok_matrix((self.n_nuclides, self.n_nuclides)) + n = len(self) + matrix_dok = sp.dok_matrix((n, n)) dict.update(matrix_dok, matrix) return matrix_dok.tocsr() - - def nuc_by_ind(self, ind): - """Extracts nuclides from the list by dictionary key. - - Parameters - ---------- - ind : str - Name of nuclide. - - Returns - ------- - Nuclide - Nuclide object that corresponds to ind. - """ - return self.nuclides[self.nuclide_dict[ind]] diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 65ed4793d..b8b906527 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -328,7 +328,7 @@ class OpenMCOperator(Operator): i += 1 n_mat_burn = len(mat_burn) - n_nuc_burn = len(self.chain.nuclide_dict) + n_nuc_burn = len(self.chain) self.number = AtomNumber(mat_dict, nuc_dict, volume, n_mat_burn, n_nuc_burn) @@ -500,8 +500,7 @@ class OpenMCOperator(Operator): # Store list of tally nuclides on each process nuc_list = comm.bcast(nuc_list, root=0) - tally_nuclides = [nuc for nuc in nuc_list - if nuc in self.chain.nuclide_dict] + tally_nuclides = [nuc for nuc in nuc_list if nuc in self.chain] return tally_nuclides @@ -690,14 +689,14 @@ class OpenMCOperator(Operator): # and nuclides in depletion chain. if name not in self.participating_nuclides: self.participating_nuclides.add(name) - if name in self.chain.nuclide_dict: + if name in self.chain: self.burn_nuc_to_ind[name] = nuc_ind nuc_ind += 1 @property def n_nuc(self): """Number of nuclides considered in the decay chain.""" - return len(self.chain.nuclides) + return len(self.chain) def get_results_info(self): """Returns volume list, cell lists, and nuc lists. diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 064b878af..3a065fb46 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -20,12 +20,12 @@ def test_init(): assert isinstance(dep.react_to_ind, Mapping) -def test_n_nuclides(): - """Test depletion chain n_nuclides parameter.""" +def test_len(): + """Test depletion chain length.""" dep = Chain() dep.nuclides = ["NucA", "NucB", "NucC"] - assert dep.n_nuclides == 3 + assert len(dep) == 3 def test_from_endf(): @@ -43,10 +43,10 @@ def test_from_xml(): dep = Chain.from_xml(_test_filename) # Basic checks - assert dep.n_nuclides == 3 + assert len(dep) == 3 # A tests - nuc = dep.nuclides[dep.nuclide_dict["A"]] + nuc = dep["A"] assert nuc.name == "A" assert nuc.half_life == 2.36520E+04 @@ -61,7 +61,7 @@ def test_from_xml(): assert [r.branching_ratio for r in nuc.reactions] == [1.0] # B tests - nuc = dep.nuclides[dep.nuclide_dict["B"]] + nuc = dep["B"] assert nuc.name == "B" assert nuc.half_life == 3.29040E+04 @@ -76,7 +76,7 @@ def test_from_xml(): assert [r.branching_ratio for r in nuc.reactions] == [1.0] # C tests - nuc = dep.nuclides[dep.nuclide_dict["C"]] + nuc = dep["C"] assert nuc.name == "C" assert nuc.n_decay_modes == 0 @@ -183,12 +183,13 @@ def test_form_matrix(): assert mat[2, 2] == mat22 -def test_nuc_by_ind(): +def test_getitem(): """ Test nuc_by_ind converter function. """ dep = Chain() dep.nuclides = ["NucA", "NucB", "NucC"] - dep.nuclide_dict = {"NucA" : 0, "NucB" : 1, "NucC" : 2} + dep.nuclide_dict = {nuc: dep.nuclides.index(nuc) + for nuc in dep.nuclides} - assert "NucA" == dep.nuc_by_ind("NucA") - assert "NucB" == dep.nuc_by_ind("NucB") - assert "NucC" == dep.nuc_by_ind("NucC") + assert "NucA" == dep["NucA"] + assert "NucB" == dep["NucB"] + assert "NucC" == dep["NucC"] From a6c095c4e9dd480b382d48ce0d3301648f8942b5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Feb 2018 15:37:41 -0600 Subject: [PATCH 228/282] Bugfix for Python 3.4 --- openmc/deplete/abc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index b2594d2c8..5441829b4 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -91,7 +91,8 @@ class Operator(metaclass=ABCMeta): def __enter__(self): # Save current directory and move to specific output directory self._orig_dir = os.getcwd() - self.settings.output_dir.mkdir(exist_ok=True) + if not self.settings.output_dir.exists(): + self.settings.output_dir.mkdir() # exist_ok parameter is 3.5+ # In Python 3.6+, chdir accepts a Path directly os.chdir(str(self.settings.output_dir)) From c9abcfc0a1890da0263642e22b3cd9675f28242c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 17 Feb 2018 10:28:24 -0600 Subject: [PATCH 229/282] Make ReactionRates a subclass of ndarray, simplifying mapping dictionaries --- openmc/deplete/chain.py | 41 +++++++-------- openmc/deplete/openmc_wrapper.py | 20 ++++---- openmc/deplete/reaction_rates.py | 86 +++++++++++--------------------- openmc/deplete/results.py | 12 ++--- 4 files changed, 63 insertions(+), 96 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 846b3af56..2187b0486 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -117,9 +117,7 @@ class Chain(object): List of nuclides in chain. nuclide_dict : OrderedDict of str to int Maps a nuclide name to an index in nuclides. - nuc_to_react_ind : OrderedDict of str to int - Dictionary mapping a nuclide name to an index in ReactionRates. - react_to_ind : OrderedDict of str to int + index_reaction : OrderedDict of str to int Dictionary mapping a reaction name to an index in ReactionRates. """ @@ -127,8 +125,7 @@ class Chain(object): def __init__(self): self.nuclides = [] self.nuclide_dict = OrderedDict() - self.nuc_to_react_ind = OrderedDict() - self.react_to_ind = OrderedDict() + self.index_reaction = OrderedDict() def __contains__(self, nuclide): return nuclide in self.nuclide_dict @@ -155,7 +152,7 @@ class Chain(object): List of ENDF neutron reaction sub-library files """ - depl_chain = cls() + chain = cls() # Create dictionary mapping target to filename reactions = {} @@ -200,8 +197,8 @@ class Chain(object): nuclide = Nuclide() nuclide.name = parent - depl_chain.nuclides.append(nuclide) - depl_chain.nuclide_dict[parent] = idx + 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 @@ -235,8 +232,8 @@ class Chain(object): Z = data.nuclide['atomic_number'] + delta_Z daughter = '{}{}'.format(openmc.data.ATOMIC_SYMBOL[Z], A) - if name not in depl_chain.react_to_ind: - depl_chain.react_to_ind[name] = reaction_index + if name not in chain.index_reaction: + chain.index_reaction[name] = reaction_index reaction_index += 1 if daughter not in decay_data: @@ -259,8 +256,8 @@ class Chain(object): nuclide.reactions.append( ReactionTuple('fission', 0, q_value, 1.0)) - if 'fission' not in depl_chain.react_to_ind: - depl_chain.react_to_ind['fission'] = reaction_index + if 'fission' not in chain.index_reaction: + chain.index_reaction['fission'] = reaction_index reaction_index += 1 else: missing_fpy.append(parent) @@ -316,7 +313,7 @@ class Chain(object): for vals in missing_fp: print(' {}, E={} eV (total yield={})'.format(*vals)) - return depl_chain + return chain @classmethod def from_xml(cls, filename): @@ -331,7 +328,7 @@ class Chain(object): ---- Allow for branching on capture, etc. """ - depl_chain = cls() + chain = cls() # Load XML tree try: @@ -346,17 +343,17 @@ class Chain(object): reaction_index = 0 for i, nuclide_elem in enumerate(root.findall('nuclide_table')): nuc = Nuclide.from_xml(nuclide_elem) - depl_chain.nuclide_dict[nuc.name] = i + chain.nuclide_dict[nuc.name] = i # Check for reaction paths for rx in nuc.reactions: - if rx.type not in depl_chain.react_to_ind: - depl_chain.react_to_ind[rx.type] = reaction_index + if rx.type not in chain.index_reaction: + chain.index_reaction[rx.type] = reaction_index reaction_index += 1 - depl_chain.nuclides.append(nuc) + chain.nuclides.append(nuc) - return depl_chain + return chain def export_to_xml(self, filename): """Writes a depletion chain XML file. @@ -416,14 +413,14 @@ class Chain(object): k = self.nuclide_dict[target] matrix[k, i] += branch_val - if nuc.name in self.nuc_to_react_ind: + if nuc.name in rates.index_nuc: # Extract all reactions for this nuclide in this cell - nuc_ind = self.nuc_to_react_ind[nuc.name] + 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 = self.react_to_ind[r_type] + r_id = rates.index_rx[r_type] path_rate = nuc_rates[r_id] # Loss term -- make sure we only count loss once for diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index b8b906527..46d07a23f 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -369,9 +369,7 @@ class OpenMCOperator(Operator): self.reaction_rates = ReactionRates( self.burn_mat_to_ind, self.burn_nuc_to_ind, - self.chain.react_to_ind) - - self.chain.nuc_to_react_ind = self.burn_nuc_to_ind + self.chain.index_reaction) def form_matrix(self, y, mat): """Forms the depletion matrix. @@ -522,7 +520,7 @@ class OpenMCOperator(Operator): # transmutation. The nuclides for the tally are set later when eval() is # called. tally_dep = openmc.capi.Tally(1) - tally_dep.scores = self.chain.react_to_ind.keys() + tally_dep.scores = self.chain.index_reaction.keys() tally_dep.filters = [mat_filter] def total_density_list(self): @@ -579,11 +577,11 @@ class OpenMCOperator(Operator): # Extract tally bins materials = list(self.mat_tally_ind.keys()) nuclides = openmc.capi.tallies[1].nuclides - reactions = list(self.chain.react_to_ind.keys()) + reactions = list(self.chain.index_reaction.keys()) # Form fast map - nuc_ind = [rates.nuc_to_ind[nuc] for nuc in nuclides] - react_ind = [rates.react_to_ind[react] for react in reactions] + nuc_ind = [rates.index_nuc[nuc] for nuc in nuclides] + react_ind = [rates.index_rx[react] for react in reactions] # Compute fission power # TODO : improve this calculation @@ -598,13 +596,13 @@ class OpenMCOperator(Operator): rates_expanded = np.zeros((rates.n_nuc, rates.n_react)) number = np.zeros(rates.n_nuc) - fission_ind = rates.react_to_ind["fission"] + fission_ind = rates.index_rx["fission"] for nuclide in self.chain.nuclides: - if nuclide.name in rates.nuc_to_ind: + if nuclide.name in rates.index_nuc: for rx in nuclide.reactions: if rx.type == 'fission': - ind = rates.nuc_to_ind[nuclide.name] + ind = rates.index_nuc[nuclide.name] fission_Q[ind] = rx.Q break @@ -637,7 +635,7 @@ class OpenMCOperator(Operator): for react in react_ind: rates_expanded[i_nuc_results, react] /= number[i_nuc_results] - rates.rates[i, :, :] = rates_expanded + rates[i, :, :] = rates_expanded # Reduce energy produced from all processes energy = comm.allreduce(energy) diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py index e8bab101b..b43777382 100644 --- a/openmc/deplete/reaction_rates.py +++ b/openmc/deplete/reaction_rates.py @@ -6,7 +6,7 @@ An ndarray to store reaction rates with string, integer, or slice indexing. import numpy as np -class ReactionRates(object): +class ReactionRates(np.ndarray): """ReactionRates class. An ndarray to store reaction rates with string, integer, or slice indexing. @@ -38,76 +38,48 @@ class ReactionRates(object): Array storing rates indexed by the above dictionaries. """ - def __init__(self, mat_to_ind, nuc_to_ind, react_to_ind): + def __new__(cls, index_mat, index_nuc, index_rx): + # Create appropriately-sized zeroed-out ndarray + shape = (len(index_mat), len(index_nuc), len(index_rx)) + obj = super().__new__(cls, shape) + obj[:] = 0.0 - self.mat_to_ind = mat_to_ind - self.nuc_to_ind = nuc_to_ind - self.react_to_ind = react_to_ind + # Add mapping attributes + obj.index_mat = index_mat + obj.index_nuc = index_nuc + obj.index_rx = index_rx - self.rates = np.zeros((self.n_mat, self.n_nuc, self.n_react)) + return obj - def __getitem__(self, pos): - """Retrieves an item from reaction_rates. + 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) - Parameters - ---------- - pos : tuple - A three-length tuple containing a material index, a nuc index, and a - reaction index. These indexes can be strings (which get converted - to integers via the dictionaries), integers used directly, or - slices. + 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) - Returns - ------- - numpy.array - The value indexed from self.rates. - """ - - mat, nuc, react = pos - if isinstance(mat, str): - mat = self.mat_to_ind[mat] - if isinstance(nuc, str): - nuc = self.nuc_to_ind[nuc] - if isinstance(react, str): - react = self.react_to_ind[react] - - return self.rates[mat, nuc, react] - - def __setitem__(self, pos, val): - """Sets an item from reaction_rates. - - Parameters - ---------- - pos : tuple - A three-length tuple containing a material index, a nuc index, and a - reaction index. These indexes can be strings (which get converted - to integers via the dictionaries), integers used directly, or - slices. - val : float - The value to set the array to. - """ - - mat, nuc, react = pos - if isinstance(mat, str): - mat = self.mat_to_ind[mat] - if isinstance(nuc, str): - nuc = self.nuc_to_ind[nuc] - if isinstance(react, str): - react = self.react_to_ind[react] - - self.rates[mat, nuc, react] = val + 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): """Number of cells.""" - return len(self.mat_to_ind) + return len(self.index_mat) @property def n_nuc(self): """Number of nucs.""" - return len(self.nuc_to_ind) + return len(self.index_nuc) @property def n_react(self): """Number of reactions.""" - return len(self.react_to_ind) + return len(self.index_rx) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 37c4b6e92..4a5ca2435 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -180,11 +180,11 @@ class Results(object): mat_int = sorted([int(mat) for mat in self.mat_to_hdf5_ind]) mat_list = [str(mat) for mat in mat_int] nuc_list = sorted(self.nuc_to_ind.keys()) - rxn_list = sorted(self.rates[0].react_to_ind.keys()) + rxn_list = sorted(self.rates[0].index_rx.keys()) n_mats = self.n_hdf5_mats n_nuc_number = len(nuc_list) - n_nuc_rxn = len(self.rates[0].nuc_to_ind) + n_nuc_rxn = len(self.rates[0].index_nuc) n_rxn = len(rxn_list) n_stages = self.n_stages @@ -200,14 +200,14 @@ class Results(object): 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].nuc_to_ind: - nuc_single_group.attrs["reaction rate index"] = self.rates[0].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].react_to_ind[rxn] + rxn_single_group.attrs["index"] = self.rates[0].index_rx[rxn] # Construct array storage @@ -341,7 +341,7 @@ class Results(object): for i in range(results.n_stages): rate = ReactionRates(results.mat_to_ind, rxn_nuc_to_ind, rxn_to_ind) - rate.rates = handle["/reaction rates"][index, i, :, :, :] + rate[:] = handle["/reaction rates"][index, i, :, :, :] results.rates.append(rate) return results From 5c4ea0d640a41daa9da0a65d134525a7a6589a09 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 17 Feb 2018 14:31:04 -0600 Subject: [PATCH 230/282] Replace Chain.index_reaction with Chain.reactions. Now the ReactionRates controls all the indexing needed to build a depletion matrix. Got all tests fixed here too. Decided to add get/set methods on ReactionRates which take strings. --- openmc/deplete/chain.py | 27 ++++------ openmc/deplete/openmc_wrapper.py | 12 ++--- openmc/deplete/reaction_rates.py | 59 +++++++++++++++++---- openmc/deplete/utilities.py | 48 ++++++++--------- tests/unit_tests/test_deplete_chain.py | 57 ++++++++++---------- tests/unit_tests/test_deplete_integrator.py | 6 +-- tests/unit_tests/test_deplete_reaction.py | 51 +++++++++--------- 7 files changed, 147 insertions(+), 113 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 2187b0486..2af64e172 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -113,19 +113,19 @@ class Chain(object): Attributes ---------- - nuclides : list of Nuclide - List of nuclides in chain. + 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. - index_reaction : OrderedDict of str to int - Dictionary mapping a reaction name to an index in ReactionRates. """ def __init__(self): self.nuclides = [] + self.reactions = [] self.nuclide_dict = OrderedDict() - self.index_reaction = OrderedDict() def __contains__(self, nuclide): return nuclide in self.nuclide_dict @@ -190,7 +190,6 @@ class Chain(object): missing_fpy = [] missing_fp = [] - reaction_index = 0 for idx, parent in enumerate(sorted(decay_data, key=_get_zai)): data = decay_data[parent] @@ -232,9 +231,8 @@ class Chain(object): Z = data.nuclide['atomic_number'] + delta_Z daughter = '{}{}'.format(openmc.data.ATOMIC_SYMBOL[Z], A) - if name not in chain.index_reaction: - chain.index_reaction[name] = reaction_index - reaction_index += 1 + if name not in chain.reactions: + chain.reactions.append(name) if daughter not in decay_data: missing_rx_product.append((parent, name, daughter)) @@ -256,9 +254,8 @@ class Chain(object): nuclide.reactions.append( ReactionTuple('fission', 0, q_value, 1.0)) - if 'fission' not in chain.index_reaction: - chain.index_reaction['fission'] = reaction_index - reaction_index += 1 + if 'fission' not in chain.reactions: + chain.reactions.append('fission') else: missing_fpy.append(parent) @@ -340,16 +337,14 @@ class Chain(object): print('Decay chain "', filename, '" is invalid.') raise - reaction_index = 0 for i, nuclide_elem in enumerate(root.findall('nuclide_table')): 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.index_reaction: - chain.index_reaction[rx.type] = reaction_index - reaction_index += 1 + if rx.type not in chain.reactions: + chain.reactions.append(rx.type) chain.nuclides.append(nuc) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 46d07a23f..c95170a46 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -366,10 +366,11 @@ class OpenMCOperator(Operator): def initialize_reaction_rates(self): """Create reaction rates object. """ + # Create dictionary to map reactions to indices + index_rx = {rx: i for i, rx in enumerate(self.chain.reactions)} + self.reaction_rates = ReactionRates( - self.burn_mat_to_ind, - self.burn_nuc_to_ind, - self.chain.index_reaction) + self.burn_mat_to_ind, self.burn_nuc_to_ind, index_rx) def form_matrix(self, y, mat): """Forms the depletion matrix. @@ -520,7 +521,7 @@ class OpenMCOperator(Operator): # transmutation. The nuclides for the tally are set later when eval() is # called. tally_dep = openmc.capi.Tally(1) - tally_dep.scores = self.chain.index_reaction.keys() + tally_dep.scores = self.chain.reactions tally_dep.filters = [mat_filter] def total_density_list(self): @@ -577,11 +578,10 @@ class OpenMCOperator(Operator): # Extract tally bins materials = list(self.mat_tally_ind.keys()) nuclides = openmc.capi.tallies[1].nuclides - reactions = list(self.chain.index_reaction.keys()) # Form fast map nuc_ind = [rates.index_nuc[nuc] for nuc in nuclides] - react_ind = [rates.index_rx[react] for react in reactions] + react_ind = [rates.index_rx[react] for react in self.chain.reactions] # Compute fission power # TODO : improve this calculation diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py index b43777382..a47908517 100644 --- a/openmc/deplete/reaction_rates.py +++ b/openmc/deplete/reaction_rates.py @@ -13,20 +13,20 @@ class ReactionRates(np.ndarray): Parameters ---------- - mat_to_ind : OrderedDict of str to int + index_mat : OrderedDict of str to int A dictionary mapping material ID as string to index. - nuc_to_ind : OrderedDict of str to int + index_nuc : OrderedDict of str to int A dictionary mapping nuclide name as string to index. - react_to_ind : OrderedDict of str to int + index_rx : OrderedDict of str to int A dictionary mapping reaction name as string to index. Attributes ---------- - mat_to_ind : OrderedDict of str to int + index_mat : OrderedDict of str to int A dictionary mapping material ID as string to index. - nuc_to_ind : OrderedDict of str to int + index_nuc : OrderedDict of str to int A dictionary mapping nuclide name as string to index. - react_to_ind : OrderedDict of str to int + index_rx : OrderedDict of str to int A dictionary mapping reaction name as string to index. n_mat : int Number of materials. @@ -34,10 +34,8 @@ class ReactionRates(np.ndarray): Number of nucs. n_react : int Number of reactions. - rates : numpy.array - Array storing rates indexed by the above dictionaries. - """ + """ def __new__(cls, index_mat, index_nuc, index_rx): # Create appropriately-sized zeroed-out ndarray shape = (len(index_mat), len(index_nuc), len(index_rx)) @@ -83,3 +81,46 @@ class ReactionRates(np.ndarray): def n_react(self): """Number of reactions.""" 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 diff --git a/openmc/deplete/utilities.py b/openmc/deplete/utilities.py index 5433edce4..d155f321d 100644 --- a/openmc/deplete/utilities.py +++ b/openmc/deplete/utilities.py @@ -7,26 +7,26 @@ the results module. import numpy as np -def evaluate_single_nuclide(results, cell, nuc): - """Evaluates a single nuclide in a single cell from a results list. +def evaluate_single_nuclide(results, mat, nuc): + """Evaluates a single nuclide in a single material from a results list. Parameters ---------- results : list of results The results to extract data from. Must be sorted and continuous. - cell : str - Cell name to evaluate + mat : str + Material name to evaluate nuc : str Nuclide name to evaluate Returns ------- - time : numpy.array - Time vector. - concentration : numpy.array - Total number of atoms in the cell. - """ + time : numpy.ndarray + Time vector + concentration : numpy.ndarray + Total number of atoms in the material + """ n_points = len(results) time = np.zeros(n_points) concentration = np.zeros(n_points) @@ -34,39 +34,39 @@ def evaluate_single_nuclide(results, cell, nuc): # Evaluate value in each region for i, result in enumerate(results): time[i] = result.time[0] - concentration[i] = result[0, cell, nuc] + concentration[i] = result[0, mat, nuc] return time, concentration -def evaluate_reaction_rate(results, cell, nuc, rxn): - """Evaluates a single nuclide reaction rate in a single cell from a results list. +def evaluate_reaction_rate(results, mat, nuc, rx): + """Return reaction rate in a single material/nuclide from a results list. Parameters ---------- - results : list of Results + results : list of openmc.deplete.Results The results to extract data from. Must be sorted and continuous. - cell : str - Cell name to evaluate + mat : str + Material name to evaluate nuc : str Nuclide name to evaluate - rxn : str + rx : str Reaction rate to evaluate Returns ------- - time : numpy.array + time : numpy.ndarray Time vector. - rate : numpy.array + rate : numpy.ndarray Reaction rate. - """ + """ n_points = len(results) time = np.zeros(n_points) rate = np.zeros(n_points) # Evaluate value in each region for i, result in enumerate(results): time[i] = result.time[0] - rate[i] = result.rates[0][cell, nuc, rxn] * result[0, cell, nuc] + rate[i] = result.rates[0].get(mat, nuc, rx) * result[0, mat, nuc] return time, rate @@ -76,17 +76,17 @@ def evaluate_eigenvalue(results): Parameters ---------- - results : list of Results + results : list of openmc.deplete.Results The results to extract data from. Must be sorted and continuous. Returns ------- - time : numpy.array + time : numpy.ndarray Time vector. - eigenvalue : numpy.array + eigenvalue : numpy.ndarray Eigenvalue. - """ + """ n_points = len(results) time = np.zeros(n_points) eigenvalue = np.zeros(n_points) diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 3a065fb46..ae900e62d 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -13,19 +13,18 @@ _test_filename = str(Path(__file__).parents[2] / 'chains' / 'chain_test.xml') def test_init(): """Test depletion chain initialization.""" - dep = Chain() + chain = Chain() - assert isinstance(dep.nuclides, list) - assert isinstance(dep.nuclide_dict, Mapping) - assert isinstance(dep.react_to_ind, Mapping) + assert isinstance(chain.nuclides, list) + assert isinstance(chain.nuclide_dict, Mapping) def test_len(): """Test depletion chain length.""" - dep = Chain() - dep.nuclides = ["NucA", "NucB", "NucC"] + chain = Chain() + chain.nuclides = ["NucA", "NucB", "NucC"] - assert len(dep) == 3 + assert len(chain) == 3 def test_from_endf(): @@ -40,13 +39,13 @@ def test_from_xml(): # the components external to depletion_chain.py are simple storage # types. - dep = Chain.from_xml(_test_filename) + chain = Chain.from_xml(_test_filename) # Basic checks - assert len(dep) == 3 + assert len(chain) == 3 # A tests - nuc = dep["A"] + nuc = chain["A"] assert nuc.name == "A" assert nuc.half_life == 2.36520E+04 @@ -61,7 +60,7 @@ def test_from_xml(): assert [r.branching_ratio for r in nuc.reactions] == [1.0] # B tests - nuc = dep["B"] + nuc = chain["B"] assert nuc.name == "B" assert nuc.half_life == 3.29040E+04 @@ -76,7 +75,7 @@ def test_from_xml(): assert [r.branching_ratio for r in nuc.reactions] == [1.0] # C tests - nuc = dep["C"] + nuc = chain["C"] assert nuc.name == "C" assert nuc.n_decay_modes == 0 @@ -135,22 +134,20 @@ def test_form_matrix(): """ Using chain_test, and a dummy reaction rate, compute the matrix. """ # Relies on test_from_xml passing. - dep = Chain.from_xml(_test_filename) + chain = Chain.from_xml(_test_filename) - cell_ind = {"10000": 0, "10001": 1} + mat_ind = {"10000": 0, "10001": 1} nuc_ind = {"A": 0, "B": 1, "C": 2} - react_ind = dep.react_to_ind + react_ind = {rx: i for i, rx in enumerate(chain.reactions)} - react = reaction_rates.ReactionRates(cell_ind, nuc_ind, react_ind) + react = reaction_rates.ReactionRates(mat_ind, nuc_ind, react_ind) - dep.nuc_to_react_ind = nuc_ind + react.set("10000", "C", "fission", 1.0) + react.set("10000", "A", "(n,gamma)", 2.0) + react.set("10000", "B", "(n,gamma)", 3.0) + react.set("10000", "C", "(n,gamma)", 4.0) - react["10000", "C", "fission"] = 1.0 - react["10000", "A", "(n,gamma)"] = 2.0 - react["10000", "B", "(n,gamma)"] = 3.0 - react["10000", "C", "(n,gamma)"] = 4.0 - - mat = dep.form_matrix(react[0, :, :]) + mat = chain.form_matrix(react[0, :, :]) # Loss A, decay, (n, gamma) mat00 = -np.log(2) / 2.36520E+04 - 2 # A -> B, decay, 0.6 branching ratio @@ -185,11 +182,11 @@ def test_form_matrix(): def test_getitem(): """ Test nuc_by_ind converter function. """ - dep = Chain() - dep.nuclides = ["NucA", "NucB", "NucC"] - dep.nuclide_dict = {nuc: dep.nuclides.index(nuc) - for nuc in dep.nuclides} + chain = Chain() + chain.nuclides = ["NucA", "NucB", "NucC"] + chain.nuclide_dict = {nuc: chain.nuclides.index(nuc) + for nuc in chain.nuclides} - assert "NucA" == dep["NucA"] - assert "NucB" == dep["NucB"] - assert "NucC" == dep["NucC"] + assert "NucA" == chain["NucA"] + assert "NucB" == chain["NucB"] + assert "NucC" == chain["NucC"] diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 59d08b484..78dd6bcef 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -86,10 +86,8 @@ def test_save_results(run_in_tmpdir): for nuc_i, nuc in enumerate(nuc_list): assert res[0][i, mat, nuc] == x1[i][mat_i][nuc_i] assert res[1][i, mat, nuc] == x2[i][mat_i][nuc_i] - np.testing.assert_array_equal(res[0].rates[i][mat, nuc, :], - rate1[i][mat, nuc, :]) - np.testing.assert_array_equal(res[1].rates[i][mat, nuc, :], - rate2[i][mat, nuc, :]) + np.testing.assert_array_equal(res[0].rates[i], rate1[i]) + np.testing.assert_array_equal(res[1].rates[i], rate2[i]) np.testing.assert_array_equal(res[0].k, eigvl1) np.testing.assert_array_equal(res[0].time, t1) diff --git a/tests/unit_tests/test_deplete_reaction.py b/tests/unit_tests/test_deplete_reaction.py index a98535e1d..de628f8c6 100644 --- a/tests/unit_tests/test_deplete_reaction.py +++ b/tests/unit_tests/test_deplete_reaction.py @@ -1,35 +1,38 @@ """Tests for the openmc.deplete.ReactionRates class.""" -from openmc.deplete import reaction_rates +import numpy as np +from openmc.deplete import ReactionRates -def test_indexing(): - """Tests the __getitem__ and __setitem__ routines simultaneously.""" +def test_get_set(): + """Tests the get/set methods.""" mat_to_ind = {"10000" : 0, "10001" : 1} nuc_to_ind = {"U238" : 0, "U235" : 1} react_to_ind = {"fission" : 0, "(n,gamma)" : 1} - rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + rates = ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + assert rates.shape == (2, 2, 2) + assert np.all(rates == 0.0) - rates["10000", "U238", "fission"] = 1.0 - rates["10001", "U238", "fission"] = 2.0 - rates["10000", "U235", "fission"] = 3.0 - rates["10001", "U235", "fission"] = 4.0 - rates["10000", "U238", "(n,gamma)"] = 5.0 - rates["10001", "U238", "(n,gamma)"] = 6.0 - rates["10000", "U235", "(n,gamma)"] = 7.0 - rates["10001", "U235", "(n,gamma)"] = 8.0 + rates.set("10000", "U238", "fission", 1.0) + rates.set("10001", "U238", "fission", 2.0) + rates.set("10000", "U235", "fission", 3.0) + rates.set("10001", "U235", "fission", 4.0) + rates.set("10000", "U238", "(n,gamma)", 5.0) + rates.set("10001", "U238", "(n,gamma)", 6.0) + rates.set("10000", "U235", "(n,gamma)", 7.0) + rates.set("10001", "U235", "(n,gamma)", 8.0) # String indexing - assert rates["10000", "U238", "fission"] == 1.0 - assert rates["10001", "U238", "fission"] == 2.0 - assert rates["10000", "U235", "fission"] == 3.0 - assert rates["10001", "U235", "fission"] == 4.0 - assert rates["10000", "U238", "(n,gamma)"] == 5.0 - assert rates["10001", "U238", "(n,gamma)"] == 6.0 - assert rates["10000", "U235", "(n,gamma)"] == 7.0 - assert rates["10001", "U235", "(n,gamma)"] == 8.0 + assert rates.get("10000", "U238", "fission") == 1.0 + assert rates.get("10001", "U238", "fission") == 2.0 + assert rates.get("10000", "U235", "fission") == 3.0 + assert rates.get("10001", "U235", "fission") == 4.0 + assert rates.get("10000", "U238", "(n,gamma)") == 5.0 + assert rates.get("10001", "U238", "(n,gamma)") == 6.0 + assert rates.get("10000", "U235", "(n,gamma)") == 7.0 + assert rates.get("10001", "U235", "(n,gamma)") == 8.0 # Int indexing assert rates[0, 0, 0] == 1.0 @@ -44,7 +47,7 @@ def test_indexing(): rates[0, 0, 0] = 5.0 assert rates[0, 0, 0] == 5.0 - assert rates["10000", "U238", "fission"] == 5.0 + assert rates.get("10000", "U238", "fission") == 5.0 def test_n_mat(): @@ -53,7 +56,7 @@ def test_n_mat(): nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} - rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + rates = ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) assert rates.n_mat == 2 @@ -64,7 +67,7 @@ def test_n_nuc(): nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} - rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + rates = ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) assert rates.n_nuc == 3 @@ -75,6 +78,6 @@ def test_n_react(): nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} - rates = reaction_rates.ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + rates = ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) assert rates.n_react == 4 From 9ec044ec2ed49d44470182a628770bf3e31f42ce Mon Sep 17 00:00:00 2001 From: walshjon Date: Sat, 17 Feb 2018 14:09:37 -0800 Subject: [PATCH 231/282] disallow target energy greater than is used in SIGMA1 --- src/physics.F90 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/physics.F90 b/src/physics.F90 index 53a2cc232..6c267d875 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -925,11 +925,14 @@ contains maxval(nuc % elastic_0K(i_E_low + 1 : i_E_up)), xs_up) DBRC_REJECT_LOOP: do - ! sample target velocity with the constant cross section (cxs) approx. - call sample_cxs_target_velocity(nuc, v_target, E, uvw, kT) - + TARGET_ENERGY_LOOP: do + ! sample target velocity with the constant cross section (cxs) approx. + call sample_cxs_target_velocity(nuc, v_target, E, uvw, kT) + E_rel = dot_product((v_neut - v_target), (v_neut - v_target)) + if (E_rel < E_up) exit TARGET_ENERGY_LOOP + end do TARGET_ENERGY_LOOP + ! perform Doppler broadening rejection correction (dbrc) - E_rel = dot_product((v_neut - v_target), (v_neut - v_target)) xs_0K = elastic_xs_0K(E_rel, nuc) R = xs_0K / xs_max if (prn() < R) exit DBRC_REJECT_LOOP From 7704390a3b3e0f5aef46df8b68041a9f022f637d Mon Sep 17 00:00:00 2001 From: walshjon Date: Sat, 17 Feb 2018 15:39:55 -0800 Subject: [PATCH 232/282] remove . --- src/physics.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/physics.F90 b/src/physics.F90 index 6c267d875..b614b8185 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -931,7 +931,7 @@ contains E_rel = dot_product((v_neut - v_target), (v_neut - v_target)) if (E_rel < E_up) exit TARGET_ENERGY_LOOP end do TARGET_ENERGY_LOOP - + ! perform Doppler broadening rejection correction (dbrc) xs_0K = elastic_xs_0K(E_rel, nuc) R = xs_0K / xs_max From 82fea220c40615bd44b61d120f7059b7efed3aaf Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 07:23:26 -0600 Subject: [PATCH 233/282] Use get_all_materials() to simplify logic --- openmc/deplete/openmc_wrapper.py | 71 ++++++++++---------------------- 1 file changed, 21 insertions(+), 50 deletions(-) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index c95170a46..9a895a508 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -104,7 +104,7 @@ class OpenMCOperator(Operator): ---------- geometry : openmc.Geometry The OpenMC geometry object. - settings : OpenMCSettings + settings : openmc.deplete.OpenMCSettings Settings object. Attributes @@ -130,8 +130,8 @@ class OpenMCOperator(Operator): Number of nuclides considered in the decay chain. mat_tally_ind : OrderedDict of str to int Dictionary mapping material ID to index in tally. - """ + """ def __init__(self, geometry, settings): super().__init__(settings) @@ -170,8 +170,10 @@ class OpenMCOperator(Operator): # Extract number densities from the geometry self.extract_number(mat_burn, mat_not_burn, volume, nuc_dict) - # Create reaction rate tables - self.initialize_reaction_rates() + # Create reaction rates array + index_rx = {rx: i for i, rx in enumerate(self.chain.reactions)} + self.reaction_rates = ReactionRates( + self.burn_mat_to_ind, self.burn_nuc_to_ind, index_rx) def __call__(self, vec, print_out=True): """Runs a simulation. @@ -243,35 +245,17 @@ class OpenMCOperator(Operator): volume = OrderedDict() # Iterate once through the geometry to get dictionaries - cells = self.geometry.get_all_material_cells() - for cell in cells.values(): - if isinstance(cell.fill, openmc.Material): - mat = cell.fill - for nuclide in mat.get_nuclide_densities(): - nuc_set.add(nuclide) - if mat.depletable: - mat_burn.add(str(mat.id)) - volume[str(mat.id)] = mat.volume - else: - mat_not_burn.add(str(mat.id)) + for mat in self.geometry.get_all_materials().values(): + for nuclide in mat.get_nuclide_densities(): + nuc_set.add(nuclide) + if mat.depletable: + mat_burn.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 else: - for mat in cell.fill: - for nuclide in mat.get_nuclide_densities(): - nuc_set.add(nuclide) - if mat.depletable: - mat_burn.add(str(mat.id)) - volume[str(mat.id)] = mat.volume - else: - mat_not_burn.add(str(mat.id)) - - need_vol = [] - - for mat_id in volume: - if volume[mat_id] is None: - need_vol.append(mat_id) - - if need_vol: - exit("Need volumes for materials: " + str(need_vol)) + mat_not_burn.add(str(mat.id)) # Sort the sets mat_burn = sorted(mat_burn, key=int) @@ -334,18 +318,13 @@ class OpenMCOperator(Operator): if self.settings.dilute_initial != 0.0: for nuc in self.burn_nuc_to_ind: - self.number.set_atom_density(np.s_[:], nuc, self.settings.dilute_initial) + self.number.set_atom_density(np.s_[:], nuc, + self.settings.dilute_initial) # Now extract the number densities and store - cells = self.geometry.get_all_material_cells() - for cell in cells.values(): - if isinstance(cell.fill, openmc.Material): - if str(cell.fill.id) in mat_dict: - self.set_number_from_mat(cell.fill) - else: - for mat in cell.fill: - if str(mat.id) in mat_dict: - self.set_number_from_mat(mat) + for mat in self.geometry.get_all_materials().values(): + if str(mat.id) in mat_dict: + self.set_number_from_mat(mat) def set_number_from_mat(self, mat): """Extracts material and number densities from openmc.Material @@ -364,14 +343,6 @@ class OpenMCOperator(Operator): number = nuc_dens[nuclide][1] * 1.0e24 self.number.set_atom_density(mat_id, nuclide, number) - def initialize_reaction_rates(self): - """Create reaction rates object. """ - # Create dictionary to map reactions to indices - index_rx = {rx: i for i, rx in enumerate(self.chain.reactions)} - - self.reaction_rates = ReactionRates( - self.burn_mat_to_ind, self.burn_nuc_to_ind, index_rx) - def form_matrix(self, y, mat): """Forms the depletion matrix. From afd4ad61b0717c3566053bc6ca294df4ee8cde59 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 08:53:39 -0600 Subject: [PATCH 234/282] Support --update for depletion regression test --- openmc/deplete/results.py | 2 +- tests/regression_tests/test_deplete_full.py | 27 +++++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 4a5ca2435..539ce5dd6 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -419,7 +419,7 @@ def read_results(filename): The result objects. """ - with h5py.File(filename, "r") as fh: + with h5py.File(str(filename), "r") as fh: assert fh["version"].value == RESULTS_VERSION # Get number of results stored diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index c809ba053..6033e3f75 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -11,6 +11,7 @@ import openmc.deplete from openmc.deplete import results from openmc.deplete import utilities +from tests.regression_tests import config from .example_geometry import generate_problem @@ -59,33 +60,39 @@ def test_full(run_in_tmpdir): # Perform simulation using the predictor algorithm openmc.deplete.integrator.predictor(op) - # Load the files - res_test = results.read_results(settings.output_dir / "depletion_results.h5") + # Get path to test and reference results + path_test = settings.output_dir / 'depletion_results.h5' + path_reference = Path(__file__).with_name('test_reference.h5') - # Load the reference - filename = str(Path(__file__).with_name('test_reference.h5')) - res_old = results.read_results(filename) + # If updating results, do so and return + if config['update']: + shutil.copyfile(str(path_test), str(path_reference)) + return + + # Load the reference/test results + res_test = results.read_results(path_test) + res_ref = results.read_results(path_reference) # Assert same mats - for mat in res_old[0].mat_to_ind: + for mat in res_ref[0].mat_to_ind: assert mat in res_test[0].mat_to_ind, \ "Material {} not in new results.".format(mat) - for nuc in res_old[0].nuc_to_ind: + for nuc in res_ref[0].nuc_to_ind: assert nuc in res_test[0].nuc_to_ind, \ "Nuclide {} not in new results.".format(nuc) for mat in res_test[0].mat_to_ind: - assert mat in res_old[0].mat_to_ind, \ + assert mat in res_ref[0].mat_to_ind, \ "Material {} not in old results.".format(mat) for nuc in res_test[0].nuc_to_ind: - assert nuc in res_old[0].nuc_to_ind, \ + assert nuc in res_ref[0].nuc_to_ind, \ "Nuclide {} not in old results.".format(nuc) tol = 1.0e-6 for mat in res_test[0].mat_to_ind: for nuc in res_test[0].nuc_to_ind: _, y_test = utilities.evaluate_single_nuclide(res_test, mat, nuc) - _, y_old = utilities.evaluate_single_nuclide(res_old, mat, nuc) + _, y_old = utilities.evaluate_single_nuclide(res_ref, mat, nuc) # Test each point correct = True From f113205ab91bb28976314d2cc0f306186410f9cd Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 09:15:09 -0600 Subject: [PATCH 235/282] Don't update non-depletable materials (changes test results) --- openmc/deplete/openmc_wrapper.py | 3 ++ .../test_deplete_utilities.py | 39 +++++++++--------- tests/regression_tests/test_reference.h5 | Bin 231608 -> 162120 bytes 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 9a895a508..e5eaf52dc 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -397,6 +397,9 @@ class OpenMCOperator(Operator): number_i = comm.bcast(self.number, root=rank) for mat in number_i.mat_to_ind: + if number_i.mat_to_ind[mat] >= number_i.n_mat_burn: + continue + nuclides = [] densities = [] for nuc in number_i.nuc_to_ind: diff --git a/tests/regression_tests/test_deplete_utilities.py b/tests/regression_tests/test_deplete_utilities.py index f9d34aa75..f38129cc7 100644 --- a/tests/regression_tests/test_deplete_utilities.py +++ b/tests/regression_tests/test_deplete_utilities.py @@ -20,35 +20,36 @@ def res(): def test_evaluate_single_nuclide(res): """Tests evaluating single nuclide utility code.""" - x, y = utilities.evaluate_single_nuclide(res, "1", "Xe135") + t, n = utilities.evaluate_single_nuclide(res, "1", "Xe135") - x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] - y_ref = [6.6747328233649218e+08, 3.5519299354458244e+14, - 3.4599104054580338e+14, 3.3821165110278112e+14] + t_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] + n_ref = [6.6747328233649218e+08, 3.5421791038348462e+14, + 3.6208592242443462e+14, 3.3799758969347038e+14] - np.testing.assert_array_equal(x, x_ref) - np.testing.assert_array_equal(y, y_ref) + np.testing.assert_array_equal(t, t_ref) + np.testing.assert_array_equal(n, n_ref) def test_evaluate_reaction_rate(res): """Tests evaluating reaction rate utility code.""" - x, y = utilities.evaluate_reaction_rate(res, "1", "Xe135", "(n,gamma)") + t, r = utilities.evaluate_reaction_rate(res, "1", "Xe135", "(n,gamma)") - x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] - xe_ref = np.array([6.6747328233649218e+08, 3.5519299354458244e+14, - 3.4599104054580338e+14, 3.3821165110278112e+14]) - r_ref = np.array([4.0643598574337784e-05, 4.1457730544386974e-05, - 3.4121248544056681e-05, 3.9204686657643301e-05]) + t_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] + n_ref = np.array([6.6747328233649218e+08, 3.5421791038348462e+14, + 3.6208592242443462e+14, 3.3799758969347038e+14]) + xs_ref = np.array([4.0594392323131994e-05, 3.9249546927524987e-05, + 3.8394587728581798e-05, 4.1521845978371697e-05]) - np.testing.assert_array_equal(x, x_ref) - np.testing.assert_array_equal(y, xe_ref * r_ref) + np.testing.assert_array_equal(t, t_ref) + np.testing.assert_array_equal(r, n_ref * xs_ref) def test_evaluate_eigenvalue(res): """Tests evaluating eigenvalue.""" - x, y = utilities.evaluate_eigenvalue(res) + t, k = utilities.evaluate_eigenvalue(res) - x_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] - y_ref = [1.1921986054449838, 1.1712785643938586, 1.1927099024502694, 1.2269183590698847] + t_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] + k_ref = [1.181281798790367, 1.1798750921988739, 1.1965943696058159, + 1.2207119847790813] - np.testing.assert_array_equal(x, x_ref) - np.testing.assert_array_equal(y, y_ref) + np.testing.assert_array_equal(t, t_ref) + np.testing.assert_array_equal(k, k_ref) diff --git a/tests/regression_tests/test_reference.h5 b/tests/regression_tests/test_reference.h5 index f832e3e2635c8d25a09609c38830227a3a551958..6e724c597107560c4155a257f4e854210f36f08e 100644 GIT binary patch literal 162120 zcmeEv2|QKZ*Z(!E%u1S#g;M4Wo#Q$OX%K1Bj8cdUr8Fy)22rUr5K2@k4TQ+msF6z1 zY^IDEN>cvkK4+i1bKh>yqhDUn|NWhw&tspp&)RFRz1I4!z4tl$-n-q(+)}*vz}^h@ zKT%PJAVcb}OX^Pt_-D0-|F0^FrtUj{3qDW=gEE0+XYl>?AA=ze>ZO5vZocVu78VSG zmy%DFpcz9;kh~J5D(LdR$^upvX144BCxKCWfzog)6?vfI4;Oq4VInGtf1n#a1{r}= zuL49ov#+IyYVr<~&CL)ZD0V$RKmT8>q67li7Xo|^AcR4|W+wj0{2+I6`HndZMi_bH zU-oy}6dQB44eWtlYPNq3kEfPW;EtaVc|mbU1DYj5)++$M)S)B?#a9Gqh9X(R1bm`X zC=O7;RagO9-sabQT7XXh$fvj)aPmsZk}O>SU)A5l8|7660WO8ONM3o7+b(2Yl;W8e zTV+VW4nTecMqc;>I(jO3LHWVnk)%_>eFrb{q7cw7o5+g>Kzr>bFQkBB{31zOfw~V2 zXbhVkaPo}lfIL*6qr9!#mO6nWHo~|n4~HHg6jX; zg3605U@t3B`X`bm;Uh8;+hX#-LQ>THu1{7ZfqGtiR>1HSA7CeB{#-7zZ)dqc9mdvJ zyKCDj8cnJ8?9%_XuGxc1oo>GpNkI37pzB? zoN;i;O9Jcdys;#o16eRy!TOq#r(i_hV447ie|bSY&*Qe27(3 zf!JTjQ=)!a^4hl~^(Wb>eGZ+sNXvror1#xi(Znln*@HNu*s1uW@&{G_yCsD;`F^AX zL(pGW@kZ&@Q+QJtMCwoDjkE*(oa@(!;DxuGft}k?as8+977V791khhs@kTud>nXg2 z4W;qMwgCNHxiemPQM~YG2;zup*IjsP1@nVE=&!4Iqh7!C6y6w0q=o4DjkE(jzfp+! zdC3cJ35uQhy9;kF!^nPuL4RGv8#tap?DcwP|K_Di>QCd1v;&Q|&bZ;Gc;QVR#1X~c zU3e=5^Fyl&*;7~X298hk7~YE1NWE#ik#?Z*Mj__sB`>@MgE*pEbr;^OhLb(Gfd0CQ zH({V)Pt9+(qeum4ypeXG@dg5tN5Knkts^@(=q|jas+0W`g8sUSH}IOJ$L6LS_1={U3{ezIc1+nt^f*bY z#gU~IxM6#;GoK%zBNE7Z>KHQXRA;^_K#PL&kkmO#E;H!4SLK@AUgrGScYxsXxaQul$(+&Q(yYD7@NI75`yL;fVVjM^|yw z3+O@Ph%^M1XX(!a{D%SB#pb+l)G)TQpzgvE_c^Do;)whpBBSTl{ovdcz3wODgc3vUiw=l8k`Z`|hvyNb6yU_jkHZ}B>Jrv^R|MB|Od8#lIoM)Sg39@?3^yYLn^ zj_fB1^w(9qi2*fx3U5U?&>!eQ@5{(Yq4#AVAh-%%cw=In8UHlisOLm3pc@x}JE|XU z9C`u1LNK3Dd<-UeLjmaP2l&Fw$qR}v70`v2WW7J&V_0?OQvitH86PL&4p{8>@K`<&&yrKn`95;O=}9N0F5^qZ@)$wd6yU73avZ0?k>DV zOagua{dE;@Qb5g~n%}DINCjxT0YB2;vjigY=LIjkS%L4@qFViFyixn$V9KVv`KRqk z`H3ody}z!S-&tS!eH!Tr`h6M|Ih{*h@f-%?h~oa!@l4IfwsXlIyg+|l%@1H3&|~=_ z+KJSk$`3T&XuMIF`B}*eZw$xI+Z<5zc_&UtUmoi+g-s&0FB>vU)6UNi87_P5F<`TV&+Y z>x7>JkXPPH<-#q6f7_pn>2fWFH|{*qRlLc8o_lKF?(a$}K;wT6Y)T?A^#7{6K$Q z#hW}(u&3s?1aDFSdVVAAK+kU=AbAwL@Fwcjxxt^t8?_H+fo@n}o}>EVp7(M9pBBI; z#m9};Jir(2L0(XNIe@P6B+)bfa>z8^NITGY`!xi3 z;Y|y~5!Jf8@YVq42Px2BSMfFoDA-f;TkASf0UB?l9ca8!i1~TR3vbaNj;L1Mg*S&a zWDowJzpmnKFi^0k@RsCHDnR3nv;&Pd5Rg0yUU-w*)VaZ*#v8Q{E&|=;fO(GUhkM?u z0(^!5pA;WAUgZIw1Gu30xPPyt0q`ZPCod?zJU};W1PuT3f?Ahz>)qA7H3W>ar}CCU zAgKVIw@5qCdF$7}OYh>nh$9fSNruzomwf3eb2X?LgxV1SF4w7vAJ|cW&^f@kZ@~TR}Gt zU|mV|!@bU=zMwr1%!d>o_xvadZZH9kDL!soOMx3nV1AR*i(5+Z6B!sowrCk(0S|Eh~t&F^7eLaN8#GGbMTN63gDRH3bI&b|NA-wWdA;2Tmn!=$iRq-E|6yCV;-&MQ~2YU1r-lDnh$x0R?*sZ><@m0yN%8JJ5Ke5cBhr7v7>l98s;h3vUkT zWDowJzpmm<6DZhIcuUGA6`=7(+JVL!2uL0UFT6>yIyd;!cq8}0A`Ay0*dDAaseZWE znJ(Z)4ww%qKJNL^3*0aSIHvfxaqS0gBxI7~r1{N|NQ zT8PFQX$N|KqY(4+k{90OK^#%7x(jdI?{m9~H*oBs$M9AJ-Y?U5qwz*zp^wc`R5BQnRTcjiCy!C5D@yc5YAdaZk0KN=cs^UK^DZFv#r>^470O-+E zc&jQV6`=7(+JVN~uYth}Z($&gsMg(uH|}}9t9Sz+%jvOoxqUf}H?{@n-x;Qm^6Qcp z-WVW`sMg(uH}3UvSMf%DE~2OAw;Zs3r{^~^Qt0`OLd?%gUU>5YaYVK1F1&H?JG+WE zBcNbU;myC6RDi}CX$Kl_6k>i}^1@pY*YlJ=jW_bSlORJ2sFkEdUQqoM0=gA^A!HG_ zp;gzJ&mPcz4P<>4;EVp)nJ)*>RZV1l1K^Wt>C9&cX#3A({lEP$wGZZg4%XGYWef(| zQ+cZa_>az8q$B9O^=stu%3Bd2j;PjDKCz`L{=<^O8+YF6D&9 zXuSOz7`*T%3gU=r-CcO&p1-?_w{bwhp2AxmSWnP+qwz*z>!3%FyZJpb77v2(D$$oM`e_h2J3e@bW`K`5sRDi}C zX$Kl_ARu`ZyzplKy>o*nxej5n*^^!&zl0k!U;@%DRoYv@HGjBHO)e|mmP5+Nn$f&RLRHw+A~r{*_? zB#k$=3uwH7K>VA6*Zk%nP9cmGKv91RZ`3|m9_(9#0q&@NxN)ce_*%hyLh&i|A+MM~ zUp2s&1aLs{6#}|dnyjJDB`Sa)@}l^x0PUjoH-`I{+@{v$+;-?{-on9ndMa;u$&enP z*X5)m=yk%c0mdtD$%8neT2r|GQ+bQR8+ShGD&EX{7;i=WXuPpqK(7-hr2M+%g|}c3 zM^x+X!kd*WDcJ?|*Hye(00nz$ezP4wUFtR;G{ptBFRgRQg z2>RF$R(x6rUQPt-!dLz%F6xoyV67=tA%VI97m<0e&!mBM;D4)E5L( zeE-WOwJzr#Usv)o0nlE%$qOk`EW2`14vNnK(Ee&<4TZBL z_0D{SfNmX0)++$T)igWv*#g>Y3|arXb8 zAciO?Pm?vg@>eVHBb~oUJJHXfVFdl`idX)M2605Sq2l^agKxyhf2NXxxzwpm9eb=jSCa+{uGDqFPaL_@{BlJ)c9o{2uPub0zy9l=FMI zqqhD36n9l%ok8P{v_FkI(hj`-^1@vh*LtO zDL(FX029a)1$m0%m}}dOP(j;<9QxRVFGDsd0bNa=K z>zq?Cz(oSkgUXwQ;M^aj4`UK}rC`me`M>QjuF+BRCAYo0dcGKnr7R6=e!Sv!9y0>i z-wQC)d54TgDqpj}xpr#(^`lMy8?Slg9VQo!DOCTdyhGuIJ8yIqFO**J1oO|m$j1;S z=Wft|k3mKNANXE7>OB!9kBX08Kpb_vp4WW=;siDOp0t7~=e(j`ABJ8Bc=0Pl)UxmM z3y^AvG8F#05a1VTwIhYH|FZvQcg^lrl=z=G9r;)B*A2Jw9dj7Oxye8A?|<6E{x7k7 zLZmW(XAf!}|95tw__}T%HDDiMrsL1;!xl{%hq(Bkc2et-nf|9e$o72Tdl9+q!9DJ- zkB^KO0e*on@6O}NcfIqRVR zI5FAN{$tPk>@@;4PO4<~?P6qXB?m{fqx|%DQdrkJx1;R*ckLoNI=6%8W`5n$Yh33Z zsCJaTR-ieR&;J71uSL>#X$#O6pe;aKfVKc_0onqz1!xP<7N9NgUu6Ma`$&Ja-!lXA zFu5b6+Ee?pe=Mz_bNkM_zQ31JC`-1W`lt4Zf8UbiNV4tRBh{W72lqac;->eT|J1@X zA8i5J0<;Ba3(yvzEkIj;34gagb^#0&AGP1+wuwJ@e@yXF z`)_VOd+@%P;-mKQ+zrp(A}x3U{%|FpbrYh-gSDi+U+xU?6IkTAEKy=I?>@O1Nk+MeS3 z|3yxG+5@Bf^R`T0<&dmfH{SMnfrqKz=$Lh;)QsY*tY^8M`YLW-zhm9gl4H~U&)SFb z=RYm~fARZ29S_CV-Q%L-idRX;A)UwAd7O^JU&ldr+lR^r|Fop_`8|K}%7auroezI) zSMKOz?SKj!$cHH;>f00AQ z8?Ta%Lpl!WeahcIZ~OlR@1@JzbQw4JQR-==_x3yICi%;9Xt(f3IYv#{9J<2q#oEFu zISyTtyQ*dST|G4N^RyjKfn|v9hFPz*7b~M?=8xyj-C>RvRNC|(Ik=c6Rl$7%3}5mukSa`Y=&t#oAu0_|nN_;SIl*L6<+vv2Q+imrzG zJbM2@yrPkbo-|ZBoKjGVG@ZySUM;7LX7n>$zVkkg3ZBP8#E*PL&M&k3I8*F_nq&li~)k=G}6aP+yw@09JocL1k z__NiM&??6#xU5b&a!y+KT{ysA}dciM82fNRH^JxeIo)kbKiDY4tEc zj(t`Hw$znuGQ4MGe1jXcH5a+P%{99DO!jOg-C6+?PX3UTx8Nqrl+M0Zv+v z7pQ8Xri(^9Jh)ndWW3*SbaqHTG-Y7So#uqm=((Cv?Y=`kAQ9)1MNS1?Lq^$Gt_T#q z%W1#9sP>xI_YIu>uSDNCbi(%&hu)<8DPLr}2#4PzWt{(`j%ytH(l*aR6<6plf99Tq zZ5_I3@-RiqyLdTL_+g)nz&#~YwraAlQ5lAwlR5ak@N*SX`%to>xFZ`m@#&cPmTS;o z%)3%w$CvNr^nc-5G*U8)kJI1AaWTy^Z$o`DzqH!J$d*65uWn;{mv6Cr21jSgaSF}uN!8& zz1zZJ{Agy}-8o=JFOEJD_t#2op99Cgpnlf1IWFm(`wLd6Y_~WA^TD0<7iNFZF+#r; z+>MHjC_^p>E5GwJQbesnhqF8q%}`YzLxJ*n9}uHrkGlNp*@(l`?^3oeJ2>(RM$R0S zac~dEJ|nc1x4Xu}_}SJxaB<$Fz8rp}zR~tu5wwp$(P`V*E6_e$SlO=bYjjc7N1I-s z^DaYDrU$mn8l{X@z8>;%xS~1w-S+*6MM1U5$*{#Ob>~>fw!4=YQ?#IcN+ymz#eWal z=cvAt=d1()PXC8e@{J#d_uf?Vd zJ#NopaZdlP_&0Qw52T-Dy&oaEALbvkrx`ZwOZs#86Ysvts1DOW8%L?1(k(1QMppT` zOj@pt-d|Mqy!53x%C|i5c8W-~Q(eZT-=lE-hpmD5o z6~ynwfM}oh4c|ETpC`_WR(S*cHF>Y^_NgJzKADzV;)PEdqeX9%q@9kIA*s6#MJv8h zMzdEQ_IZk%qaD*GT(6O?LE1Fe$XUN;A$t5iYjxH@eWV8!->g3c?PGbZ-^DT)=$|6T zGr3L1lAQ4uoQyxwpZKwqj@gUecvQ% zp&yb)?y8>m9{E&saqqw-S;!d4e)A2nha7n&u{ja1<2P~o&;7m;xl#JVd~tt@@zDUN zPver~j{Jk6zpl+F(;UAH`m11-*2~>;s;FF4P-7fgfyBu-^;_~z1ufW^&^N3dM`i7Y zskWq5BR5;N^x58c9Wm1~v$Q?}^Wj;w_m39#hw-Bxb18fIE|{OEKh*2{BwT`{|H`2j zGY_xNDojQFO6?>Aul@bw)(X=I}w`uh8_mQlofYJKuJ%c4dReeF4{T+b6ncbIt2v)uIo zS>2KKNgmHe>b@M7IyDE{rtO$Zdk(8Xe}v0EIBxa<>a!-{ z-t(zH_)Ai7d+qr?<4|Gmj1eD=%8}%lOLNi|sStQO9&mEDIr`ewsNZ~n2BajyEyMIA z3z-nvM_i~MKgT{LV@|C48U*9nd;7QK8}&k*@%LSLD5oV>j6-LAczY)E1B@TXfUu1( zK0tq6E*et$NE1bCyn-~FJj;;4NBU746%|nn=gG&W+`>@1QyoU_y{nO@d(|8w6RMh|C=G46X;|XP*$;I0NP%L+L{-8C{rvnlBxBUA`Uqv(!p&X?~~` zDxM~!;a+J;vlU)tiuWyWVCiDM%ADolj==T(8j zl`lu2zsirs6g_DA&auz?#G)#Z7t$PmZAvYQSaCapL$7F7sl7S@`s>&h^LbadDx=>5 zZkbucmmwb}Dmm$tDWc!CS2g=C#85fM4d2D8YY}BhbCC#Q{oGG!#J-sZ&_0dZL=v9n z!1y6+gOsTa$%oplWwW%NVp}%J2S0o(OgZ>(^MPq|Rn+Dp<603|zDnka% z8~pfjh%)*@?m*5hF>};m)WHw7wbjV|cL%RIrm>J|&;2&djDqnjli!wkIt}`3S?RE8 z8;k`x`ifNEa(tr-*JpNz<^1A$L4CIDM2E&JL49np`lO|wGC|`rqSo2_mLcc(lW#At zQ$qC;++4H1 zgcucbG_a;rC!w?k`8Z&;9=d^re197CoOuiC^RfKQRQWupk6XdbF-8F}o?q9T3aYOj z!m-!#2bD1~58(P|v*^boIg(M51g+g z4)qDzp7kh31?I!v`GK-;`s<;$FK+lWVrm&8ZrgsC&qEoVG{NMp?;sov`gp>uU-AdU zsTVeVbR!EXex`~mv_iZ$DJ4JC-T?EV-<`+KcUqb__R{mZU*LJMH^*PhVZ#@#X@dG( zY@eOxW()OMv-I|-Yi|tD6l;U?b41Dz{_hLtwtiDYhc5Oxy-gKI>slKhO#fJq+zZ5J zyb56C z{k3fOsAmx!qtWl@byDz0WysdElMV}%E1}I-J;zF7=IC{+uZI_2c#n)VkR7{bI}2fz z^jeei62`Oj?6VBRNzh;6ftNPUEMai$k;CY-aZBkB>oJjZ%W5%S&V5Tgq4H5#?Hqe5 zo^!R<+@XzXopqWQFs&4+pQC#){Xkz-P|nDrX`m)*b7^T&V!#K)-*Nbtw&EiEcnUFOC83~ zk(mO4cSb;ejdyd%lt|#?=o@IG9PmsMKHo@@syy$Q5BKloZm-QE)M5PO7h2;w-Pe%z%4&@VtDru$Q_tLe zq^1+5DseVKe-oNuQpY@DieAP?NdEbO=$l^9aKcj`;yz! zGDK(i?I;5iB{Xo>o$~727`j))&Bedq15#phQE6>83sDGnQeJNY^{H-HZ21(0`YfJ% zv8sUu_gjr0ntDkth3nNJ&Lwlc_J{boBO2!%BMkL@FE(CV;<^qxWZVGjoR6i*cZK~a z1;l=NdYkUnm*F@X7IH3QR9X#^mT|-O%UKrExX(|*Vib($sPdab8}wm3PaFJIDx?Um z4^K5nDHfn`f7)w?n6KnXn4eF6U9!&K4(?BzI;MstiJG7(s|}iRSY^m?v}#qMz6#oG zx8>G2eG7C&z^Tm2;Ezb5Ty8*n2@Bbt5_^cwLHu_NiawA6%ge?_W?iZTJ#pgx<)EaNoYO6gfOuXGdQjC3J>eT<$k% z96h0KeXwMFBVy#~8GrH1HDvM`W@)Vow9n8@cP_sbf%a+Ltfa+!0`u|4DQl2h+n|1` znFES*r{CnnyX_Ofj(xWv{w^dcm~0-5qT2>Fn95m|A@eO3q^gIjphKfGo<`SOpxau$ z)v%_1Mh>3}XQhs1A@kfAh|^FJ&U$`L*6aug^zD=^~tEv?A*Cz28EIl3iD z=ju#EYv;gGRyoBSc^lTwl0@|aIQdX6P~-E^*$`i!=d0KZvWDxiS5^<6+?Wpc7b}OX zza9I-^MuX)N)vQt%+L{-x|-ze*<>Yajg# z{ne28z*BdHA?hpiLcHIYa%5cYhuVJOs_4x2*#)=a%+bl)r^>9G@e!HicXaNne%Z)@ zjSuZUO@;cX4AqO1lYssTiAfGtR)_nYSKHrLjvP6NW6$FW^Ik@eg7LgBN@vcaVK9GZ zxkksom$XES?Z=HuIZ}ok3)y_;;9OPo>T;#l(504Wkk~UdVUN$qpcv<>wY{6gzNdtjDr@8T-cv^`!J*PD`7leTBtdG;W3zhB$m7E z-ZR(`eVvqwCkA=Z9+qSUwHV zKHiHTSl8_Q!9GS-8R~H`p0|1AG(V7r`LH5E;ksKo^q2a3->K_{L45@8^*Xy?n-1z| zdE=>MQz>FHKs@z)trDu9`K-uixjCAvIA`;Y_$EZ>?#=ouy;z8NnQVr|MQERQ#{LV| z41oGfzu^^fS^(zf_p#c^((~bZBR3=P{JQs0A1(KwqI4&y&zKUS`%jkXqHh;UXSGI` zBH@eomsO~%pbEF+i!EEt(Jk^j43a!skT%2p?$Sg)JZt)`a;F^B=ab((6F(oQ&;8fY z39MMS{?WOUbGu|IT#s1})9@--0pmw0#If2z6Z-Q&*|Yppf`;g!f$9sUyOtpqOH5^7 zOR1vR*YCBavnGV%Qbf z|Gd3Zmwc3h_SviAqVz-v#*a_j)X;mu5MN*Y)5DToOi}Nxva1pd$`JI--d?kJDWd`9 z%Boen%~7)skKC`Y|}!7BXqlkZ8ftr5t(oX0?v}hQNGS$%stXoDchdFL&_qMt?pI zzuRG@&<;CjpU;=--7-Ys{B`xt$x-XZsiQMZlRI)h5zp&Bx#_DPRz+V(r)3V`WPzr* ze*JEd+=9$7-Y$z~Wg~&u_1MAFS8j%8LVOL_VV<#J?J$mgR;)Cr73>G& z`Pj*mBVC5U=O4;(@;URaGSTa;;tNh5EJrS!J>DGiRRxXp_1iekX*}xvRnO>vatqQv zK~L$?#cV`oo93`UO&HJZSD*Ebh=%Xu7Pt(!Zkq_#8zHpX^jB4A zU7TPJ^jCjp)!?ly#^_tU&#F%A%Mpu_C#^TQsG^mj7Y$MuTB4#+n#Ts$eMHXMj#-*D zH5=LTVjgqmW0(&|@4Aj=2S9&G#vU9zuoynyEjd0cRy-HtZGy_^Z9~GKK1w2kY!2o_ zeM9Fmp4PlEMK`9880i^WhHTomF&q6ft zmJ|1Wc)x5tulDBTe26bTL+N8@ufzDs!Yq~7xkG&nkM*75k#URTzr~mNia+*+`Dfex z+2`zI^wHrLddZBeC`F3pLQl?!9F7j&v0!(o@OV_-#jZ9aq!~GtU0J+@z`N#-2V~IQm#5uFmqj3jK9n%-3)e zW`>%*y{0(iSUHk0=#)nDTNQMd{v(G)Z5C+A2RrR^6C03mdko*c6SI(nhz8-|X^EqX9W%nrHcH84HQYsqvU2P{e6(=IyrFM`tr< zy)h;ud3VYnXrI@Kq7%F}L3;0j9p~?Eg7(RbTRS1DOGuAmceJlgw^ueVh0PkuHv@C_y)w+=IQ%;VTo`)O&K5a z#XcSOk4Xy^$=k#A{dm#FX|B#tpBUwrTcS*$K6M_>TeL3dqpiYnk);#Lko{Xn+B!ul zqo-$1R2Dg6jt=nJTy16Z5kWR;FD{8-A@A+>O!L?X^$G8{O!2!8)aR4qFvP3?#_#0H zwR>MdvPV8VBc#Gy2iFy)&Rb2gCeiWM07a{9JwE5w4%$ ze#d=j)}y`JP@ltt7H6-mg!))n2sdfk8KdbAi%$d{Cg%5mJE2dTR8Xg|j4C@f3$#c) zF=qGkCM3G8U}se;3$YkIa7fh@s85XO#$#u0Kz#;&dDTe#ayTcx=IhG#4)TQaQ;vnt zEC&myk7($O!mCH2{RW7W% zbXE1+DNzS4KIq(Pm6YZ(kMknBX>pDh~_vlV~k;b4z&rl zVA{ic_(o#27bDyp#io~iJh8J3dHm*ZSj!?+bWGfo%~O;u(F>2R$-Ua%ic}#(k9!jD z-=}`^KXm<#FlWD9v;ng?Xb$t?pnbRbpIX3tqQE@0>bwhF-vAuqVbt57<=*6S3ZCS{- zO9gwL7DIfwejF#CTm<(!nIFF$6VZYDU#s13-YuOtloLg=_8JM`0Pe2!)Eo`_}F17)Q8I=0aUxkIpd)3?9 zohjzXo0C5-KQ930!?8~T25%9A@w|OunVD-fjMuB7lRmhwg!^Tg%)4L5`u=eLu#v3$ z1U*z&FCu7LT`8h{q2AzLtO|N3I9{zHb#aP^RX@V@*33QMqt8!;`LL)wMtJTJ=r8nhoyOrjCc00? z(0ff}DWV>7=Ct`0W%RK{0uqv7jtUXr^XlJ-WcHu@ME2Y@#OO++=4u1zFT?S(W=xWW z@jUyshjqY$AD(9nM5 znrGknIR1+I0x!;%37s*%U1(bW%=!*`NV$bodebj zovMPaDI51}xw!=zbj{Rz?b8OtW0n5k*T-1Ms%t81d;O4q%nmKntv?R+F&_W@i|7^s z{NC7|J9jBHW73~Z3$=GT;7KL&@<&cGaqarpyu`~_u*a3#590evF^MmWO|M7B;vV{W z!?MRpn1_VVE-)rOzued3O!2FI{P^~<-r7<#n=!WqsN4MZ1$eRb&=jW!Onj$as`4|b zRBWC~Wt(1c30AwW|MnY%oIWS7EZ#m&!aUb}MJ!v6&v%2FhI#yWXhTwHCDx3UM24y) z?^uW@7$2KnpUK4E$MpJqigyV(@iwyYX% z9{n7@zHIedE!I=)^8;N;S>p4~4tAdk#}$g3JBRlVyG(q3*>&RP4ds&jxF34KYUZ9M z?E0LYcgKxbfO{7_X^B;5;{HmOpGMcDVVj=x9+X*8g2~?Sh)W~%5jirfV5O;q`O1A? zOZ5qTP~@CmXnz5G{l}uL8@|n0uJi1f?m`Z@$IH9t_Es_RMZrFsBat-B)}t=(&Gb_2 z>*(~EnS`8E?y_S=Hi<6&c{P= zGu$-j6Ca*?6$_HiFL!Jy#*Cl3&&(#|Ts-~3bGU_s`B0me(FTMZVddFLxhewq&M_`? zkrmCD?X4XJ>U|w>6VU-vvnrVQq;WBYw`RnUw?LZ7>K zi#{0ur;KFVCnmAhy*Dwgt4h7x z*m6QtW-q82Ab?xO3NDu|Zo>BLo4-4DxC5S&_E6f%lZi*KD@vdC^$O;?NA>c+C8gL4 z2dUC5Le5b05kj?uoJTE79oTZ-%vty%joEqC5)p?ITaf~Oc>#Qg>qn2N zcbc(Pb0RMoyjX}QeLFNgTf+=L+R}dO%>Hz&@8nOWS6fQ4z$5Mp&$0E9+^9Q?z=c%7 zrD`so&8d zMBey1=zQFpwP~2HY0wKx$x_S^6Y*l>)TD9r%4#BSgx?YzWk|>=9$&Qj>ns7B?IH4#jz zVdA$kB)yHAQZXlu{}Ci#lm6o zjBTHHsZwL!2lL^gvLek(zc*nF=|!b4-p#{HJ=9!#`xE+{$^PCrEFBAyNotczzYsT1v{l@bM8x6FZI_ zIU5(ZX6s)X&o6)*zGiqho@vHvB8Ih$&sm5+8ig8E%9`PoLu?B!8>V9G-&xe@jxNPs zzjv)+&tG>-ojN8Ha>lK#lVJNRF?x*pxZwhL$NW1PEmxbdiU-d}^NBd%%bHBi^&#>G zYn$pqWN{kia`su`Ga|1_8BL8~<23(LUH_v5PS1)B31i!5xW<=TNjn8`L+>pMhm|yA z#-(L;vjQCPN%>D4l#eNn0kn< zPf%3hXfyn4!)OodorJ$y_*B2UmtuEYeQMeH zc|-NqL>a=)53()Y*m7n!=xgR*sKKVYCl}YZHDWcVk1Ss|WgLEI$CKgJQ7E4Nh`DM! zF)v4H%au-!e2O`HwKa+naVQ;amn8C1++1l==*Ejg99ro`hgOI2;rsfG-d`u#jA>d5 zpN=n^hwJZE512>fSDC8!(<95%u}2fU4nLSsf|WmQ9l`c{qw)O7F9}?X9Q$k@8(*5S zt$~{+@Z;8uq0(p1He-7X7`>9w1-RAeIT7wMX1I3M#9n)Z(y)26&P|NAE5Q;H6>mHt zj>vk2=JYw9gQT>ZFui^#-gEa9qw zA!HoAvJ6YXzTQbL-yT$k9lX}Rg&og9hb$@)ZNkn=wU)B+bvin2 zu*6ycJZG`>qpL_W#$V$&Cv$@XzUQz6D=~wK?=@`?%O8IQyE8~3N_u@MW^wUb?kz%& z^~z)3Z-{koq=ouldER4lmABreMvU+DP2%xZ6Q=n1xwOJWOI)PSFd;Th&?!H4}jD)$noB3O|oYO7`*Zwu;+J^(D+rzSNIr<6ZpF z+C8(J1#tX)kmTZS$It?P8^}Qp7 zr#ay&cJDqITxQ~#=Vpa$O;5pm1fR*?7A(h(NXD;XuM|w*<90dVN}8dw4bL(0f|ol#T5Y?6ePBdQ9K5X*vs`&NlO2cV z(xN422!FX$1c$N5HLKs^vP6|8tj%uigOQ5O*uV%!jorZMxG_@jo^?u?>G zxMhin*~fte*k-4()5VE6Y)G|szV=Yu+`vri8hd@1F~c>i)?E-EpO~h#IldWFThjC( z-^CH1vDx)Sg&bj@THEugM^mw(b{_&xC6-~*+ddCu?=O@^keG?O66SAeo6oTK1q$K#jy`dlaGQ`^Suu!++b;@gl3!HX5l@Yhe?q=t=8#S~u`i=97Qf(1=%c$-Ve zIX@w_PKlVmnq%$bObI#C?PKPeDhuP|yidOxaG(YA7Z2b0P(aGC(F8MkvGhO20uSsG8QqrR&rX?+_}jz!7X4!lChnJ%q0M3?ZFq|U|fV+lE# zSI=hJY!kpA`R~seq1KG~O@Aeny~hDhc>vFj`U1-yP0Nq%;coV zap}pJ+r-|fgDuOkrm{nG?-FwMRj`cJM-l6~CC-2OU1$BM{oCFN;<(izEp_2$?D2g0 z3pT<|c<9&6m&M^s{Nbud%D97`yUM?+tELLRWtV1rz6zj&_aCaM0KZC#Jg9|>$?S0rG*VtFwfH0UM0Kjz>v2t(&faT^Qx=)yyEha}>2FM|4`r5L zPZxcRKY6iLHpcKdhQHNs>rKSryqE_G^Ph;Di)%{szCgqw(^6jDr8P^=}UMnKQYqibr_7f|@#{{Kf`3-5o7?H=K$}&H&^Zd}8SeQDok5h?zxRjkY zY`%;wYu_t?kGneU&a&iYtUO?$;%y?&%NgC!j~{A=->dP_JL;2)t+=nYpw9(DPTSlf zwjA|%iODMne?3V)WXzT`O?>r-s=+-SlWgacqCi{T?XTu%wH7hh`j!8GeKi_n_ zyhI}nTl#sg_U<=jSmE)T$?W~<)RQ)4=LtL4zLXrujvwiLnZj*$f_T<|i(8aFHDm85 z3@*BkI^w%E?)JY*_5h2KL)&ka{mgLb%mZPK zYp!6i4@KK|dzWF~=LIFQ$8}(`K>s6zK7IMaJ=uBwQ}OCci`oS7VLlJ)<4!bV#|JHs zd?@6EKWsuKwh?(_&OWtCu?5N4;P!P!#&63o*>6SF>~Wd98oZvYMeGMX-`cXrB^~J^ zlACyh&$^Zb--#Z0>3olX=Ac)JyWt1NQTu z^`@<@62y8VY1^BJ>~URl|Nd~7b0gNgTGIV&ZWAV<=UskX))F7~G000=o{6(8inZt2 z<>8{*o(WS5o?~Ibs?qy2d z&Z>p@EtC5A?T8uv((Bd@>{}W(&b(RT$)r;3)F+p(Y&$1wTz&V1uya+3=yrDeOxpG2 z#`}E&xToaEzAr19vG2>K>s|44z$I?RXAL6u1^z?BqeXh9V)7|R7aW^aifKl@KgOPS zRKx~p=n-*PlVCNJy*{)n%27%ED1e9hO+PEDG2_?7Ihw?X<}- zPQ~zhR(QKf8TMhJ=9{a89Jig3q|(}6m}fGtrCivrzwa_N{L?66K}yvo#Vx4 z$T;H3g_jR~bTPxvYGk=B7@3MaBo;#pMwVe4HZ`-@^Ny{PO4K~U&UTZ9RN3PiDi`(o z$t`$(_0*Bb!J|dt`8s2l1Nrle;q%OP?D99+S@3!0`qQ&VTTC)Tw?2M&N7}j^2{_m4 zwzp6j9UOdMY2X8M^xP7)0XZc#$nyQHW9182NO;Uk_4a-6d9QeX**yoQ!{?bUYbSWd z%oOH4Up*athcS}){1){-V}4Sff;b&`esyu8f%1V<@cqx^gOYVi&9u?tggue&-^vip z{i<)*X)B{r$;>{JSK{cm)lwy@murwIImK>IIs?y(MkEDy?C^u{FGTV# zr&v#i@4r^u6=(4ufafKiOu6~3I17GWLOEy4&^py0&U4lCeHpfCB>Lrpws3AiITC#P z+w-EP;b_^p?eA>^t^1(uO{noKKG}d=R%b2;)K05c8rfvQ@7WJBc>{Ri+GGy8PxeIz)*v>Yk8Qp^k2nuGg5O4W27F(zc)C&Jj7)fbL-;}K zGnp(HKLUx{&I(L}&%?cA2alb%bv;L)ZSxp*yIbJMbC-extMuyG-}V({JwGz`!*pt`^w2>Ycfn9bL3TZtjs(k?hoI0@wfYo zP-)@NDJmmP%YXR1u*HbSBg5vx_^H;m-I!?&?c?Zx$2-rkM8{MM%pSP89GRVVNoGZ! zGJ0HCJ7N4R99?#(;Z&qx4WjuZ#3{-x8~I{1^U18YFn&J7PCI|>EVK{0PfB3xZ1_Gd z?s!oRUom`tBk9~Ac7Xx)nKHkRrK$ydzY%@cV$)be3w?CwgM_bnIkGn4h`eL7B6|M& z#7K4u^;fM4zp(TQD zq452e|FU!aAMJ$tyeQBR9L#5d>P@Z}ySktPslW7D=(4yn8jmU+sd2zjnV7KwO|mt} zwZJnoE)nl@!+Onlx&9!uPxBnTlW)Jk_|dVbRvNGi=EK_uhC3dyh504*@q57z5BPrY z{gV&VHy1&Du6G!$Uw=sljf@I%2v09Zs!qO0F4(DzMzx=o6?lT9=bWN6K{gr$xh|eLj7AyWo5>JWm_Fs=nT=4?GXBdtUjYhtBZ47T>$+ zN0q+9_shrU7zS$ak3mJ(rC_EW<;cJ$`+4<8Rnh9^g2khvEzyl3ufoS966Zzp#o|lE zvJsn(FNx>P;CY*l8O^%}{c!$AI)7u<<|FX^`|LwT6^Z(? z|An-Mo6ui zR|PyUMn`PSZL{20j$B!)ToLj>8TJ0wUKU4uABbV%)UV@iS0iY)K9Y7N8?nTSGd|3Q z_R&m|&K}kQ?ekrBgvsKsu>WUAmZe5k!t)6XGcVEiUhwlEyVB+dR8NEXP(yD|#x6@^ z^z{Ag>+!_-gdKj%H{Cs`i0<*~SXwXwL!&~nJ~-X|fZW(vXel&_g=FNL+*>gW#`D^o z!n>ZiFdxpB*zA7jD~#uK!)cQX*1*q$1nN#${8bm~qc=WQc84z1XI{v(+={!(=&Ane z{q7>=NT2qi*l$%z=-K%B&eJgrU4CML)wZWK$d;H8ua*JVktfAgKFYh{=c%8n>E*_G zLVTq>Q1zL!1o~^vwHpz`7D4<>wVJ;{H4w(L*~hxme)pljdIzo@>G!}4EwbJ4c12}5 za?ITJmec=Z>bv8y{J;Ng*(FLcvRC#V*M;l4&B$JrqHNhj$*PRVNQ6j8Dhick&yW!^ zvfnbZ*Nw>fT|STB*Q-D8$6d#{&v~BbIj?cftCb5bEa7QKW>MIuOt`JsU=HeZ)FTq7 zt%aOs*L_}T0)I*J1)g9O2KOQBXnt2Mrh|HLmWSkH`BleDruz+5MmX z?y3fYXFo<@SSY#sw2{aZG_~`Q_K_NP-d(r7cn!PH*3h|G(UkieS|%yVmc3I0@r+*O z%+mn+4Bj#*(;xu$X;RY_Hn0bLoWiBzu=gC~)3yhlMb9+LkK*BV$HcR%4}rflsc&W1 zcqqXsGy)!_0#guWGfVW1RSuXLDye^Jh`_@(qzsF_vrvM#bx~`6E!1i&mA{Y&{3WB& z_F!!l;K!Atwd~wEP>*%Kcmh+<0{n!Z%S|Lvu0PV})84?vQ@r3lB)_8A*cy%-CbWm% zJ`J6OHXUaT%rtr7PC>iml}2^gkS_c9gQW$?O$iAb#rn}AnPNL8)&U=e#Elcx_k((M z?|fOsD;(e(PHyP|B>sbGLDR))V z@FEwi^$n_3TSVZ2MO0S#_dN9E&!jmi*3Vd;*Q#{K5!i=X@}5-{Gl+LTd99T1CxJdK zj&CyGEQ9+Kc102kjP#&B6sKHwE|vw~pX%7Wm!hu(E9aAr`}<5mOQx!}f)uA=-1Hl5 z*Rx{qNoS=GJhvtw#_!ckOUkuS6GauXsCDO&y^FYLGdrmOALeYE>Rhb@^%(ah?(z@| zuA_fHED@slED_XWXCr>>uP6e1S-;q+?!2u6U%D*0ThM{sAFB-791Y}w9lw5vwZ{6F zrO#wH7WXbe6vr>>IO)|uwo09*y4!$%8lQeUj-v+titMFMaN`Di*m~@i^Ur_gH+4li z(&yWQ{=rUtRz!mW_$%>y`(o;*60DjNbilkl1?`sHRTi7X=Iv1OyuW@4fy*f~rtUA! zLSc*Tj;Us~(1z)k37vAF5AmMX1+_AO=b2*_l-rys-Mz1fb~xBxf)>>KA?{p+)OAm0^)Cu`4NYm zoi;2~UQ{_iI1RB(JMFap<%BmAt9PCZpfLA|c?SE6c_=wF_t_F%6X%5Rz+cylChrZC0Q+nW zIP^UU0_(5Z&R-VOxJJUAMoKEWb8Iq0pg2ZM-bK|1^#jj z>0Ec60l%Lltco;~9@IZq9yunY-2&gITjtOmYlGkp$IhMg)+s1bK=$Kz4la0%^vYb- zH55({3)Xu~zXajxLT6J4YN1<}58XJofPJ{++ey}I03U|9Qcxm^Af7yHOqO2qgZ^sS zu@Z6KZNP`K*5iXGj{*GW>`zp#uZhB%hVCD~|DA*eU$SgQ|73&}4jwB}*oeYcIDf@{ zdN&2#xHfYB#m^e(q%eD@e;MGzjB7TpRjPxI>OoTR;XWG?{LyD~(-GoqbMX7Wxu5t( zh64W4aAKke&@;2j&W7^R$6ZBLzQL7 zbMmXH0FRoO}h_$x-2$ILjA;3&Sg#h!+yO#yt6#irbk+6Mfy z|He(lhzsaTM8e+n$OVFRqny8a6i-7I3;aU$wOp`aCuKYy=NqK%KPpx$72FFv1`2K-g2&Z@$D8rv`Wvxqv?2Diz^5 zzJL!ea+pw!e-nrEX_oGA8&5&O<$;qf6&$ecT-M86Sp-haC|Zle`n%CIVZ29DEp*xc z)EFHJi1)>=evgeMz|Y?u?+)cPg82!nayO*ZJXw$QVGg9&Xo~~&p?2r9gPl~6zY34N z?xL!Iu?}Nz)4So=JhV{N3Nv0Vcw?mS{bh9=w$I?;F6%b~4b0qjlaQ~0d@j&65_5w6 zx+z8GVlpWZ?~p~z(D5FC&!2VI%X-*BJ)dK`sXjIg;@x^`C_%g$)U#IFblIXiIJok^ zsym)xQxIq9OBzNd9++imFiA{Z9mcPG9+%Gk3mJbc4;`(qfyy!l9XT%meh!(m$tC@# zKbQ8@>P+o4s5fp%*T2mDo4?smPr58xU7Pq!qO z2=H@X^cH{L63Ab_12kBRzc(HEk8M_=D?kfoKbKJ$+kuCsSPm*4-K~KZ8w+a#K7;*333BPEuP(_)@f~3_`M$Ce`0E6( zLK6`M_P^iLA1oBs$LGRts2cRCi-Y&kPAp+L( zV_!V?U#6!X*=OUEiC#<<+Nm)S*etXr!lnFae zp_6n8QqrhqI*H?|UwGbiog8vUZh<7v78xPrMK)h?H3CCFpfc!Q5 zIJ9Jo9Q?hZvHDH&6OBju+!`b%Te%4O56Wf6lG?OHVU>CXK{x+NNFeC@`QaD5aNRFY zPTP77Sm>Pl>-YIv(4tL%t7bzDl>gu24AskHNA<>)ajuq%YOufYXyRts`X7+5?nXRZ z{HO%_Ew3|a=BY1$e1Z4RfPVc`Kj)v*37F6pgTD{e6Y+~qLi9z~R5enuc?E{vGa?=+ zygT5=v z1s=LQZ^V`{R0{=~Xt0GR%pb{{S+YGfZWn&Uhtw>ZJfExqKfi%*X^zZ*_%iA$7ms)f z@DnJDPg{`&cn-n^&)YFb!QQcgB!Rh;(0muI&>l4pym-}>MHP$p>E8DLh)=IVE@lYZ zm-HGaZ@bJa<)3&DT{$LyO$6Y11822VQVin#%Aw;q@RP55ES@DSGY88r4E0SxJmv zFM#^TScLUt`MGCD_EDyO{xU%p=tJD(M0AA%#JkP%l`pFkz&>tCQbNU)z&;7faWTr# z;;<)Ls!wxu3ThJ22#+-2fhRIw&)XAW{a-c$ANJ4TAt{E;05%k6$pE2OCn?hZQ24w(0-kSWuqLyHP-c;M9>fSq0Uw;wG9;2v7;9lql zQ9{!c)H^8m>C`?C{8(?+Blxlg?3QKJKyhUiTB=)i5y#f=O&{__*O|fk>x>;&W2Pyn zH@tWMq%=JR``bJ$+|m{t;QWGM=F^k`3ix?1;+~@$9pLBDkHKZYKFap9yaSA)wl-|b7jz~+I3N;BL^t%2HVm&Ek10X{r!pi-F@WKS1 zrK2|^)M3G-PdUP3>yWLA(WbI!Eo3OAaA4C1^f9{JaY@V*_-pgp8|pm@zq`~Ntqv2! zZOK3y>rhjBLx%V~Ht)1Tg|o#0_-k3>al(JZfDa8r9El%rZ5`n|uiioKYZ>6fH$hC5 z+5Dj1$ZJ*ekLd>S_4oxF+%PT;CmwXiT4ql|-Sq}s48`2=LCvE~wrv!?Ga~Ny!xRtI z_|_5gWnulE@A6Gw2mwB{VzV5nCjBNTeroebAM3#6h@Dm9BfeVxGC}9ElYaEM zpYvRy@C$%&Jgx1-!mo@4uC zwBzLy|I9{x9W7A9v#!`s?x_zUs->>-JJcVcbAx!{=ReT`dHD6Hr)8tA663GEu0yrCtlg-V1AJdLwJ zen+*6I4LxNJ|Bv&zUAZs=Wo5@cA{SY>?gQQRv){32Jqpp8OWSp0r269 z`QPPDBCOtsth_bl$_-03F;L@1ad1rGpQdm{Jmh{ka$G;Y78)ad^OiCV=rio%p~7bm z;%iH-$FBD#u+O8Jm(nt4KtJJFxXq&IJmAAi4kRJJ$pAmy;5w01JEH9@c9Z+xZ5=V>9JV-k7SB1pS z{2Ot=KL5R)i>dYm`1u=sIw-s0rAd3w}17-kP18}pd)UL;n}F0&Hw5{ zZkWl=q;H}~9o8DTWpxL?0SWr@#cGGtK(1x~of3Kk;+=tRMEMUji1&{rEOaMV!2F-p zEgPlx!hnBh8usdnJ_GzjIyjG>G6MQiuV$xg|CEH&c8ssIeVu@$NYhf!DssU>g(pk? zoX5ehFKm7}&c6YjF>O%dw8Hw|8`vr!DMx{12uRo zG(%E2bp>)Q3Fp}rs)1sP@?>(_z<$Exub26lya9g1Jv0);ECD|UJHI{f$O83`x8CU) ziUP3zgUxd`x#SMwtIW3JLI;BwjN28P*fN-cisM}Gh;3r?hmXqjN$*s=uHUoA~90&t(e`YS_xnX`pT`iq;bQ^w#VM8VDlk)xrwMMy30`c{y;P}1DhlEG^lJ3TTQa1(ui15Ah$?yr_pSh0`&P(F}&&OX# z-0Egggo6`%=Tw5Hq4&?F8a`-q!#0(3(<_xYn2M1bB9d5#MwKE@E8MArEJW#L9-9Jv zUMYHSPtyTBTgxpu#w-B-;lAE6+_?k(-r0m;57-UVtAsb}`?b!2?>kU~WSXD+aDP4P z=WiBM&~1m1$O>sLSRJ<7Cq4KDm`86)ko1eJVrYelen1MO^|HM|Mpi`Boa|0@xQFZycRqOZT! z{#VZiH%h7=*0sTnsOoaJbmNl{VpP!n(RnJY|0Q#4z&YZTJ1VDA5y!}HfTnv#Uq3%k zg8WQBqkbtKkD#h6GT+m((8W6CHKdOQXZ-i7#4+rCW8Y-EzJUqWpJ+ZHEOID^pwQy& zsw-+#k$msCq5%q-1kQHVRUm6G_(rz|){wV~xeQ&HoWDrn1F>-$+{C?$RiuaaHeXPF zo*W}X>o*82sjAiyuX8Oi_fC4Ex8x^TC1C^f<%L1Mv9HAluS+4x+5C0nwzyJdJ0{2C z*zJnP%`~`M)PEbv4)0r9|NibHM}$%gEmRB;ts~ae9_&K*+|VBCA^(Td`ly944|xq- zhJ-W52RMwaAuNKAkycDj|G4`xOzn`_Aa^vr@! z{zY_oc;3F0Ss#sOYtT9Kr5o*tPk(K5aT0kR)%KrszP`#|E|zhc&onsalyc5s?0)QB z#5br}nFz%{!52?ktRn_n`TjDuUD30-_W5k>`Y1IWlKJ6V1@hq~c|`yC8e(@auU?1g z6JA}2rcBY`TK4;i2rzx3rN1{k)Fec;tef9h8{-k@+U`8XXRat|-(+G)gFZ@-9vf>e zP=OexxAi?MSwl1!5`3-S$-!@QdmPM!^Peo`b&ObmCS6B21kpKauZ^5BL5&K zZaNU5UcQd|%hor1)2Bc9exxx#HyBhDE~=Cv(GIt_m00jd9DD!&{NBG^ zxH0`74KA==xbe{M8@IOw2T&rkv`5O}4%Q!Zn(x0*{B1Y%_XXr<$V+{6?hRS^pWPDV zZ0@D6Uc`7L{@&22>m`@9GQ>xODcu=DG;l< zqcZKq@kDnGQ0esS+ib}dh=BSzb>3V&LOhR#f5qf%N(Gb{V*PH+G{0pIf7ej_QwyK- zq^L3?;{V{2K5AzxvMV`IhUk=T zH4UG^BjR@c1cx}xxJp_}jp5L*GXgr4L!^|^HN~-lY>D6LW@^PF-{&qDRPN}bk!(?V z^PBo;)U45IlUH4+xAcp}MTaTG2d%?NVmP!p$hNrNhoss_zc(NH`hsB%lJ7}IAt{FQU*C=E{f zbso+6xQPms&ny4h(?|Q-K1Fj(lp`HX0oI8# ztB8Z{WLY4him$Ck!#CfQKtlI^(TD8KF z9XCL~vrAD+^Hd^6rhXyB`D=(SXYJ`mOpbwD`-@XEG&sw*?&lBV!Urp#{*)7;BVulW z?^D+iiz~rbKlQqyRfIbqTnZ^{4w;!(~=w^tSU56{0J~PaTZ`Mcu*0I`I#+D*Ks1+f_r)vn? zt@QgHn4Ey7n0uclXmEy)ggzhg;l=UFUx}uKsOg(M$dPLu(bK$9CjA+zJQBKyKfEEmA zS_KbjG~GPV0+G?zw=v#WS<&;jzFC44O+}l#aIPb5AyEAx4*wcGev*&juo7NvKjcH= z*!kDyBZO%2My4OKx{k2qWJL{XxuB;5at3(h4bUJuDlTD>N@P4xlcx_}Lr#8)3TeaS z+>83^I)mwh%eo_dDCd0=vHH2VWh7n)-uIzdM=US5&(hwyh=!G~3DfH7qh+Mz7g|@k zQ6*ffa@gldgipLFLI}elZ`?6xu80OVa2%(07sH{ZLc-&hy+r7;apEJH<#lAGF(OCl zmmB)Pf$`_p7z5OKFK@W}7X5Z6BsT+z|L#;<3m^wGVhs4#2mO60z|44U(14PhGM5;(+9ib0UW zaf}aB`C=Rn{mx%NQ1WAk0M&WAsO`2*tNDu0wy_7?^0|oyK@Rvx@o9K5bCPhS@MNI!(N7`*s|UL?uZ|ciMWOEMqN0 zcu@m1wsXK1yg}6rXVPOOG!t*<4u?{4pS}D8W%g!2dYs56@(C;xDm1sE@r$WJx zXb$c3OH=mMz#t*o;z8--8nuo%&J5itKJJQUcf2&ajL8XZneCadsz8EY80w1qt|8}N zGt2j3ayBwWHLbDVwWP!=eJJPhuP{Nz4HA?qJMD%D)|bjre^ck-m^-?1v$ZfcQ6DY% z$-+z8RDzJRroD~-h{yC|P8-4G1QYxhVD*y*_gsFzhHL%YBpXQP zvNikmO*gc63cNA?`2Z~(;+ZFe{@D$zUviJ0#P9HToxdD?OTV266>J^< zpmBEtkrd~&(;sj}8*IlfKf7aqUST$}43w!r_zLrO`I^>{?vpQee`0c$+n+|xV)@<6 zz@+R@pYs(Wk3N4OM+GkhHpV{KKtg#}W`(C&BcqONg;9AM zNWONQu%P|c{5~1N1srE){>xfMWdT#ZcJ1X@!mF)IO z1GIHHUH<;5GUUx2&cQGatZq3KM{W=Ct+S$6YV>`F=**)#Y%CL)*b8zhl9_gN( zsyW2r9Lu})25f!~)2*Vx11v8~T_yYLgVh@hy$926f3diDOwRxjx}%!>?Ge-%&oc&& z(uy{fA?8kk3}-Ijk%zMkvxhwY9|3=_$p{UuRff#yu-@Pit4n>5NQ`dN`1ES}t|Jk0 z@ggPO9%zPms0CxM0cyX(cWpGY6ruEf?r`xQHV1;|R@$MR`5a^}UTdep^bim^ElVHc;S&ut)leR{u#ZhD|Dn-!)Z?+wtZvFI0UgeAzTcRFjd>N;Y`aDB4} zlXLvOmuCegXLkFJ#-ZQ4ZkImsEFeZB8C^5Nu4Db|SHz5*E8WovckAcFbNZ+PdHj^3 zUMW)Dzxh4sH6B4uid1!Da>N|fiT5%7xx{{d=8%83r9UsvOB185V?~Q1*!%%9QYQ}E zba%AwsvwoQqXGIP$N$r~Tp6;})t7FQgh#wyB`6MHa**^tZX1}KyWgq%5A|_kN4Sx373E6gYf+|l6uPd`sG8=!YTdr#4REkk&t zsf-uM@W?Vtdh{U^$*8A1w~$6Qzx;L}T3zPX|1jLHv!{5Md05jwjoy8lKVM9Gy9z9oC057w5gH>NV<5^;U=VPs-U7PouHE z)M~Y4)kA*1&%{!8DUb+_c-JI!tpJZOH=@VR2DqWpw)c!uNeobplD7t_apg#C3&Z9} z#~R|BwATL%lcUPHp+JPyy{cA0)RdSU)|Z<_$I6M(9OX2VypDBblC8AzE0HIfL8u$M zJ&nm(B{HQpE<+eMC5#7IH;{nH+l)1s9PcVxW(jOPX#DV_>^>G3B>j0d@7qXGGo}Fk z5Zw*LA<9!+Q5=9ZC=doAlFCCL0Kip7G-fOwJ|$0F<(x24@u5 z7IFByNKgg0ZVM9B$6$NCV{IMDI~IINn85>0BUfR0p>KftY%~?!b}m7X_WRwn{dh$9 ziukL;x`jPt?^+#ZAD=ZvAz)1`JR; zvE$!d+)EKUD>%8BV*?@XqbBah#ma z3NLk86UK-0S=0fZ8^~uH4cu0Y8|v^f*Rw0f0Bzyi?$_fhN51D@^zGVTL&{1ky$|bo zy3{gmek@-+KOd!X_`8w*hJKuPbBoP zdZ3I&-<}#W8lv_}Bn)4%IXt4@vJTAI*O8KM?4F1AQPN_*_8P0BZiK!kJdD#i;-6%r z2}CG&iT0}=sdXeZKHU61pF7Ia;Jkhj+fRtQ|K|Lvd^z%7S*L#%TTjPl(aIdk(O*Bo z`eO*ogW31u4&!v`%o#(U6k@dDgE#A-@dncLqs1$Z(*xCz3$T$qW{5JbT)T+lEJr?v zUQ3X~=3sdo-y}R-$0ZxJlq_NTT%cg7I$Wo$3E@LqwaL&+2juE6u|DD)dAva1eNU9P z(a(x3%m5uNQMUNNfc5DY`4m;3*+6p7KpThk&o{~i{*|vZI5Gk|lS6%iV4k^f1yZzB zC`Rh*opq#fE_lM--UAh+ma}HY*6$Gxw_S+nuz3+gB?Vr@>qtS(3zEa%6(pFHX@%)S zcHS)W&|k87Yr`qaL};z#TUvAab%fR7}LaX*EY)E$gTou8ct!IbH*cFvH?-On4JIiv2m~1`dzimP4qA> z>O+ms9b?!)GRA&87H#4YTLqKK7-kF9D&*2?Z>&D5M9WBPnpBJGePShO{O>oCjST*l z$8bpSS4n{IISp?4^aSN$UUm@a9)}o-(XaabS3c=ub;9dm(HJgwRQCGYKx-M#GYvnIyXOHrp^}{%&Fi0hi zcuRz`1l+zHUcZjCHxA?-H+DzWnfoQq1R9|4d4+_0RVxsyqHEgCMR+6&YezY(6FBdN zd(~odrTzqbxp26@v5$VbZ2^&>kuL*@jYQUw&pjPPXDmI?pIPiU>uUz+l$Wg!;{i5D zE_h_nwY}sReyE5(HXPPbdSrKn|04zWI$U+UowA>Sa}>h-KNWXs zz`k^9$Q#^21lT8jt=M7Ot)&6SG_J<}`aA{6{JzA6m&EQx`#B;kuPG1dtUlMrdI&F4&6Zn#>a&4m6e4j%aL7O#2jGDLgu?$;`| z5A4XEb_)N`J)8z*Rh0+-yo;L3p1XWn1Du2LHwZkbxC-nQHBB68sSEa{Wz87s*OP#K zo>0863HqV{Yws3liNsGsdoAVGYN}JtdUJqR)y{}`+ z4$kS;c{2U8CV+h)6_u0|XTiR`N97m!MrCks^Rv-!H!m5WPw+spXwbApIQh0xUmj;*7xJ%KTN2_2wZV(Pza6HE7Nb`(z~5u|`^zIB0-TFIaf`k8vli%kqP|%(=RgI1F&tm9c@CR{ z>cUaEH^z;yU>*>WpR+YuanU*ccy z;>iW>XD&{#$0G``-dUe^10iIuepWjx~2j|WP8;?_Xok1VM zeoduxMhQ6gxi)cnrAH5(`v|Zp2?lN{!FiqWC+<5-X``Y!nP=Vly~J~s4&2*FZs^OH zyadka7=$hpbF2YA46KrFwT?w$!-{10KEEl5DfzLNc@r0G-`6!KS%ZVyRO*BL5~rcZ z9y=pd&uSs_wx3_4p82|>U%-$9@`aHr z4b`Y5&}TA2FECqyOA{>3rgUZifhhcUjb< z?EA4kH51Vy31M!y*tkAQy99;H^jj%}KFmW8E+=)iVRQQnyr(Mud;|6=e}#8G7y|J{ zE8oQ$NCWOI2+u^bG>?Pd&-PmP`^PvC@7kgK)RjfRUtdB^r6CnX*d#MvVSjuQs*Sln zl<|cVW_`H?4|AjN#4*~$lZA6o4pFxgqFDIaYJg=qZOb}$``iv{tl4O z;frfFS8Lgh?8zjTStC^s_)xy4U36*`@Zk-*kcY2FMd9yr(l1#(CLx8SAN1Apobd3+ ze(^O|1eUk9c8t3=2NjZkl!P2>p!1UI7V?ikU(aq^Y(ugY=v#sDfzBsS0z7NFk48$2 z0=_#@t$$U^68I}f_~)cS7_iS+RL+~nDq`>rE>-fAuP30*pvm>XC!8>|{5#}HF$%MV z1x6*f&q8$W2baD0v3H#W+@@uOfxrG5*xlmj1NPxQW5y>_0M5bFK3NN~#{#}H?i($% z&j9=rVxOhP@Daqj?s2j3EiD9ocgx3<@ctC^=Bt3fi3GzD8R|D-3{`s#n2G~bj zd!kX_6X=ssyXG_Dle*Zp%Z?;xgb_aWhVxF~kA z&%XxiligPl>geHuwJYjwx!l0Pz6Z3?pTpLmY?qwn%029!$!|yJ^G?7%QY?0!V-}#^ z_>VD{=ZyFI5g&>;EzNAifO{~Boa(H{--RB>LQt7{;&>_(STwsB1W>CZW&(*gFZ*>V2i|155yO;%cbM^8t|VD zz3;&2FsRRt(FKVolK}jvRH;78dj{g2FY$Dhp&JC#<)7DIqL_k$h7S}!VfS2#lJ0)E z5Q@Sv52er^oh7Kz_-|^Pd@c0nF>c13AMhcs%WbCf|E#}ob#{+Tx`2IH6k~Qoz5>4c z{b|{uIRxM*_eH9d(~+%FH$8B@K`shEKL7)V1hvAztQMPi-La@f7iczP<~TT2jI zxxEQHeGMcdwHWp3AALCTSANL50Ds-Y>9$3m0QfZU+M{j30sP1+eCqVP4D2Jj9Q$4@ z0qA2-f3kgfLJf96u1Y8+OhGH9?>y))bHctSpMDRCKw)Cs9U_yU70B?c#FKFB9os@) zr$sSF5bq*ViF^rzKp#tWcA>jopuR|U5|zqy0QpRhO)Mj4AK*uouE?Fp3;1id5SKhQ zjDrbFb5?R2ry-BXA23@v7kuvl-n|if-$a(aV_vLv9y(KGq$~Md0PE*Pe6;@L-_j$0lAE$~ZP@^P-Ef}SQ^MZq2v|54 zA^Lm@s-pfS^NWH5ZhkitV|7&(e#0u{e{o>}Vx1yq|MamIGAmbiF2I3&IuVYnTTp;} zYIjj0#?}qg8-3Ol0}e~z@2x)AP;lM@_6hHn5&nw+e+8tv_pJ8H!LN2rubtJHf?!$H z>|q!eJgwi=%wvzjh>P%3BK8%?z~;i0SbwY!Lffg8l?nKZ+#mPnNh0vqjqqL1;aspD zWG_0KU;i55$NeJTkg(IkBm0Cqb~*9=!-r_lOWUF*CHSY2tnQ>S)|Vlr*ZwSt2_`it zX!~g-0UK|S{9+QAhKdZzvzt3>APNtKIVQWIBYD-4!Cmh0fDeln6dnpo06xx_8n0r` z1@Z5y{YH}PD&WJ8=+^4MU&8N(Q|F~Z@6Gp+2pEpB@})- zOI}-TI}4ew3?q*1HPEwfF4Rh6fDd&G=C4$E0{i5cDc(^b2L5_1XQO3v8mxah;xF+2 z5eM;Q`7Mc?+#9TyC7zU>*$Y;K=Yze!uQE+Tiw6ne7I(PeZi;jEJGyFcc5AMOiS7cF z@Gd6w_OUvs+FX8YJR0Dcr%T^XH5BBp!p!O6_t@(i|JO&t_4A6zxqtecZREzChWmki zF1&Hnhz$by0v=)LsB{#9ulS^@esY?G_ybly32$)1DXLvV@z`;oUYNt}0?QSMrmy$H z;6x4diHYGNM=h|=iO&iA+`j>y_pDj|*1ZSuCGgQNECmMj>Q3_=gOVL!ALrpF2F`I{ zAC<4fOt>6CRxUa06xt7;vENx#ja%0nM?`@%tIGCE`41|x{gkaB}cctMq(ARnLE!;nSPb)37iX=9G55vM+ z6op@be6jTQhLnpmh%fOg{@LkYfj%widWP90K%eKdJAD+^s&L@!)s`u24v+dAw6BTv zeb*`}xb5cSV74{%^ta#Z&<(Q}#umrxp!J7+qQ+)`f5IcKrvID;`jF&ll?6NieW>w| zC|=@<0Uyqt-9LEM3-E(aWc?s32J}Ji>z0oXO2NdF5f@gSu{mj69MCyyZkUI@lzGAs z2NQ4%DvD*VLW#F7jTN@kLghb8Ez{e9KF!Cx$aKDcDJj0!b?pB*mz-HugoZ#=j& zj`77%&JliG-|UJ1AqDxQ^m+9F6M;0$KGm^(onR80vA1>%Z()V4SH{cJJLKTIe#XaE zU#vi34s#5h*t^x@Ig1QI>&r*-R)j4?6a*rU>eFUvsu9m1Fb5;ZVX&(v9n|wo9Z#i< z!|IOye`BK}*T3(Wt-b41vWb&{M+tg1=;{~?0XB2h zp3lSjWLuZ6R{Txkf>R42fz2N{_?_V2JKI@*ph#80*}qR}An6!VszgtqPY``XSxXYo zCp&+j#a;&J!!W=(^@9WWYvSwl?%b`fNA?Obh#T8g1m{1RWXA3q4YKfi>d8x0wAfq) zeOQL-H4nUQV$YTKOC44ye)8F}c^%?5aLFA~tcB=GSJQizz&-XIrt%@@F|eQcMWg0J zkS^dq8GRSnC<@?dNKw?RJOkLLOii!4HxBsgvsyXPsZ1&O^g|Z;pkEUZ&U@x&03#1f zK&4X^@eRvglM7tU3oDQ+m92};`5K6@^1BAY1N7m3ukkKb7w8kf*PPNO3gYYj{rgt6 zEP#KQ%t=RXJO+GNbJsT9UKaSXSUty4i%%Y&zlW=McWe?8N_owZHOdV?spBUk>c_!Z zLi1g`ts9VHb64Av8TKwS%Ba$64dVTC?o;EOtH57*j$Umq9e_Q0$O2|J{(#Thr09ij zy17UGB3ijO#kL9Rw=$;eTXwbz@Z&U8j7D?<(Nzh0vNeAunyD)h`3#Migtj}2VxpuV9LyVIZl&%1J8 zdXc&lA_$Ok3iMOn_)QkZ3(@(Ic^QgrKh?bqlxje(>02# zVZdK2dGUdNe}nu*rbX(Reg)J!JVf?q_q70CoyKd1RCR$q?9ZO~C;0qP*)-y>p#B2X~Dr5u1`yvHVa`Fq1zpBic&wFeTz8uPsh_a)>hw-p**^eyB#7U@eWWQsghX*cS@!ayo`uHf{FHzm2 zT!!YN+v~pa)IfNyt#8Znz&_muo*_DBfDbb|YG*Aa!Fq=&XI*2-hvmp#N$xvh33>HL zpH~)=bn2);zM$D-U3IpTf>E`sM|kYK@;|ruq~*sv@Ir``zg~el+!aQ`kVLf!sg7Ry zwkur={g<1+ll~g$^DyDMX3iAQXM~?#So8*%gCV(L87Fxj)UOYT<#{z~-yG><)7V4) z>;TkCN&wNy^J->H%S`)Gxu zo!YVTa9FxtE`iM?bZq9i0q+10%=jnZiqva$xGYON;+x7Eq~iJTFE2Jn&I0ym_s{|M z5i=>hIbaR=P)%UHa#fn}NZeBe4d2jQ`CD@2?eT zw3@}~_b~QOv`N8P11Ye+c&*4)5jz6nJ&h*z`Slf`k581Oq9Ze?7a8v#kD>Adc%Hd& zy^C=X*r)%TNPD=SBFtsOM=BCM1$9Iy(wF;j!3rnR3g-Q=KI{Fn>nHrzAR0E-cTFE^ zAs@**4NJSgK9y0=6|2@je#a|6kSSIK_DFTxK80?BdP{GytUM_g;D^m2=FJQ%z)!c> zQ1m%ARrrR?&jnNwn@cbM%yLSB2iA(96E7@PgV#?*?zLj|20oQiP7j+a<=3ux)6XB^ zN5C<2r~03{9QK=L;UB2L{*PE={!QwC^1%)6lXuhpg7tDA%Zl6EU7)Xp`MtNfe^vpm3bp9Nd{%hsVpPiXrW_pUt7ok2kB3&R{P*@^Yan_qiZSN7)gyjUw?7^s zM+^Ay%uSo>d^Hg7nvZTteQyN&Eluqw1vMSOd2X=}-i-PM$p6l&CuY|wRN&=v7rM&L zCLt?@<G{+XIQ zpB5y3W1{HMMW~XBHnB#o+v^WRwm!0VqCHa5mT)DfP@8i=2`CLm; z`U*)Xz!P4^r#bO5h2w_A_xX-G z3)cb=?_c>~)p%wQ??1RBdAz>Lz;r7Cx`7WSpmu>4`G-=xFw8JA9#)|aYkRLW|9OIk z{x}3rtP*4EFQ-3wk8!|0C0qQt=i5NMOT?&fsqoxAgE zst)&k8SEbaJ|kO|fgf5j&eM1ErUg1o=9S^CT!!TG=ceV1?IO~M!9hjUqD`kEoGDPUgxw-3quzS&`KJ;E=X}ygK z-m{F;Sg}C!)7gFFb1IOIskC?hUD`*QQxbSvF*$MOKW-_y)8JkdK3r_Ugv*!{al#tH_yb>ISFWb%Zol=*x9e3zX;ArJkw@bCl-%gP6B&9Vj~? z{VDePWyE;rqsNu^`YMCwwE2pd9HUiJ=mK^>cEtN_y4e^pYOi+v&o}IyxW|J1>KDKJ zq5&tUYr?a!-zE3l@vLeoG9(kb)IYL|L=X3MhAXka)=`% z_v;>D_haV^OTJ@r%A2oA2t6V|*~@2ISbMSexnf?O4L|9F-ch35p2b_BlSxdnB-<5; znAj`kZ@>N`Cc8EVQ<$7bPUV5LX*4*#HIvjsIS0RGqj39#Xx7ZSxE%K0T$WKny7Dbw zR4C$AR_SL8)L60U+Nub>Fye zGfGn8F_IC=NP?LLzLCQ( zAV=l~>si&S%-RG0C3chXEFe&+AU#iskM`;omS!~D;`P!I4!ftAZ zoBfa?;Bq~iF9$wYfO*`W&ffIxxWwZC_cGpD%RX($ZFvFW#<5d&KRN1)hMWB{jb)$C$4ZMrZ&Izm+*&X#o1T zuA5b9fH<{ElWS`P`uNK$22%`D;!lpG?W+fKPM@;ZuB2^T#iOs~Kj69t{Kd#-E~!z4 zmAxOnoiDYHg)(MYPXIYFLWfQGlbE#=?PI;VftpFob|{UWpO zFlKE{uQd}g4lhd|PQSE9g{xQoeNh1Bj@g%rf9Ll1#o4{`eR@Eg?tRyxmCyJf)*K7^JB;3ClD~^Hm@e$VMJha&BWqrW zgM{VBZ3e#3y@va8W@yrRTj1-e!;hO;E3ith-EZH;Zel4F4QI%_%9N7C*AH+w_NlU@ z6UZ6MQgS0vQsd3_x5e8JkuaU2=)%DUA3RuL@Fmu7ft$rqvP zvrZs~!O$)o1NtlmSKKD!uyB?ldHK;C_Pc-KmU|ZoBMHjYu>NO(N9>C_FbweQewT4^ zPO=04>aJf}Q9pxmmaPU_101GKiCqxYDIP@vfkM-0D>o zgQKG_u9@?sS=f<)f8*mw_E4_Gj7nD&osMi^GDFp7<3OJ^eTL{g0EYric^hAWJ~^2$ zwa>^?;V!b%mjaqd*nwmxn#0-5l{tD(tf8m<6IH9nJ-HVE+CW!gs`&*YLGrc{to7S z|2UEA9s%;H{%N<0o1VTnV{Vr&(^m`JbE#o&#k~x(EbjIAkFbIHM5~&R{q@!FXBaQQ z;f*$vUUFXcEBmtcLyj7^Q`psc2nT&VnX}qk8oszv{mlTjpCG=9-O+t%71*a<1=~yR zfPb1Db-w^P7Mwwve*kZ6e3A1e<8W$gk3%6&fqz)O6ZHKD=r?XkFus`JixXWnZ9g2g zz;j2Q8&ovC$CiHns@AvPz&=&8(vkCptOoCBInXE2#+H?AAI}wYFAFXz{A@z3&=3s? z3rk}_k6QcU2iLe5o+uIU3q$gJN>b&Rr;u})cqz#bI1_{`Tb2$J3yX3d!=B(6PYP#2%QvNH@ouZpo;mHn!mxeT@JPFLC$}+AUDxgR;M;c#FWCQ||HHrIM?7S*RxCU0`Py3Z-+x z@s(J|o}V&X+Ur<+`$aEuygyut4Ar;?cr1cxksKGf18Tij+Ntpl_w^39aS}Gi%r+!Y z?~9+u#BWS85^y?y=??#l3e4lalBi6}23AUJF(-f5;7u{k(k3`>4k1k*>lQtX*sA^#Z3qE~2wS zWyeRrt@__~;bx^+p?|TQ<@qhFM4K&yY@f!u)#+|m@V>{P8wKP%s7jx73(Qf$C9>Ss zW+F(~van&4uJ^}Bp_gq(%uE2WwjE2rfZ)0;d ztDpOUoE7uRgS}SF+R0I(H_14B&FK7qXN(HJCK}4}6!dRO6_!4omGZ+4PXyVxf3v_3 zy&E`9juqJ)EICK;SG3o z{dGvzeg}NTGJtC8qy_#NjAPp+_!eh~s05P-=CQ&&mS^Vy4zq3alW-?y?Z5QA6(<1> zH}4Eq_%;E5Np|{1KLUNiCa9JS^ELb|FE7LDss+Arb86DRvlNpcXz^=SZemVmdV9$H zQ|p=F*zUuu?X@+VNXC!A=_IDacv}3snt((tcwZ<%q zxdgj%PxYL|*d`XpmL@v_`J!JXsHkX6;{wcHFflLA$ZrtiKELc zjPV+tt=d8Q3-T$W?&QH|+hy35fLpHpwVPN@r}P3@&WiYtp3@-jWE4L5{eSu5BXaKK#w=ks*7TN_M=k>*m zUS}6(ZF;3B!EZnx58|dnrXe-H-!S)_W5)tEyH0z$i1QlmK;Su9+1yL}!mvD5i7w8KMuoPPi`gsk1 zTlKkf>I4Cg_0S1>zNZuuIknsH9q$%)My_u+`Maf(@Q#XzZfmbK zv-XRyvxa1Sq(XRSCr?r1OUa#V#y3b9Pe#onemy^&GX$5E`(uG0Ip!Ki-CKqw7HThD zdbfcwwgieS13A8$C5`<-%-WgETavv%PENZ3M@08BCT_8JO##eVZvSyi_mqbt{@PdH zz1H0VFI0Pm(>Y|}G6zPFnt*qll$wHWIRPB1`^}oR*)nSfu?BFFaVWN{-OQ|w8qca1 z$(;m!%XaLNFR4qf;gs9+f8#eSaH*5F@WM7*sd%KTmU;vUGHCx>EB_mZgSkf z_^FGXwt<{yiq$ngKs~l=mG?FIyRLjWPO=XNbLWw&gwj7G%+YvdL8$#2F8uvd{I5X* zK3?#jGOIes@7%lZM8s@ill>ES$$60WMEpBn6B07v}5!*jPyEN$SkH5bZc5B#_fNROoou_3l#dhnIhg+#^VP87E zo{`r(EaNlc1ZQUL*(@9V??4Vk>pr$F4jNpecFIh2k%R>wxDuI z_{;DF;Oa9h-qOmWvE*7M{FCb^3)n*-D>k^$MT?>=+D^EieH=T^<}Q-l87L!V#a zrcdTXB=hF6_BLlzM}R{YHG)QgBeQmlh$w-amnWZI$IcUJ@LlFu4aF#Uf2pO1fkh%cY`12iAp{P4%ddc)!%FEgZ7^7)#UW0Za<=_S`D)|eCgge+%)ZnrD}tSeng z;#9~y-*bk`l6{&QPnz6l?$BJs;!5h8#9m#)SAsQ?H~zE4CnM2@$^B)RFHMu3rtucm zm}H(nw$I1O;B+;>8-DV3!DRgG3S4eq52L}UP6!<3KSsi;KOSFn2nxXE`MIe)v+WlO$(xvV=shN~-x>TL?LO_ztleOebC@j0 zbgd|&S&0^}O;HzE2kUOHzO3?VT>iKir?qb_9RVNy#q-jQ`yG~q%!(nRTUcG$SCvU1 zCq$Wa<1NVV4q^+ueaJ2R!s+7w5TYbG$z4-0#-qS9n(hO~lXKd2Ia5){jd7ht@xd zQG6gSz~nS@!-xoH26{?(e&k85;odY#+)2^4bN+2 zL)l#kc%gbHjnbDg?7t(&QR6khtKSK+mh5zM~ zcyvXcgza;5IbLDthr67(o#Q4(z@wgo8wC4SV3ujJj#3(%nCFX{2J*V{qGNS;_@fWi# z%#d-`hMd1*i5q-$fX9j-B&d@4xi*0{wABWl8+vpg_Ugv)90)tz!iO2beS60d_XxHc zcn%fiSbv`5wi2qiEOTbI8uVS>UU|G9EfO&Ikaol5@M%y(4#sbAFRN9K*cJPHWsiC9FevE!nh(M<*Y_3kgBNqIYVM_=u+l}9e`A-;@uGdt)UlSlLZc1n&J z4I#y@Y*Yod+~|Ph)t<31d9>YPnB$)EU*z6+Wm4@%C9;+q^!+gwW7WX%YNMTBze*w8iMiIAY-WRyzAYEuzL6Ju{O8@gq-ieyev0;>R5`?Fo1d z&*eTfhAItRv1EBZ&TC6ZyD`8bsu@=*^9z&|lJ&m-)x7npuL5aIoUnCZV(68ldG?HIkop_7ZD zs9=_x^63fW$^3KS7;rCofqP0{_8)!rsa-#K|0C1~^NkVDVuSbYgvIMN7sg?HS(tFB z-FJZTMRd0As*Ho@P*`4v&3zAbH0ET_!sG2BTxE{YxlF+L`u(7wxT_c5d$~T7xpANb>Qio1!7zIh-m9nctU2Jf3GcCc zm-_f*e1!H%elu+Nt?(c^l=$b;UaMh*Bff1*D_Iz=ik$uGb_z#tox4qS6wJR1G4QCe zFRDRKq?kSDkAe4;gu^2xzRyGZJT+$#9`Awrd}>INo4*X>ogu~1#ijw;=W~Fu@%tI* z&()`&!+D}rP=E8feER!;kQ$DT{iT$msP)G}<;*iWsMx~yPvb!|i1ikhM&zwSwCB6S z?}fl~s6y9_3j+V#!%2GKGcZR9^~oF)3s|Or=Wrb=7X=6ApgxR}7`x2I z5wzs{v9V5IpBqOGU1zfb^A{-n^K0aE(d0gd#IE3omCR)0zK!o}eu z`QW|!iOpiB_ka(ZrgOZ@265ES%D_%1dk*pavyq$|UW4Q;Nmh~mK!3fo5F;KognSs< z%4EXw&$)9~gef;qI`migiFJxWzN8)g>P1de{*j00o(Z+9yxeAT=sI!KssHm3Vj_2| z^h6Ffy3zG4U2{VLm3|OAa`DL@B(UGaX>O_-$tdj+7!MfSkvF*~OrD6t`BV3cIv>rq zL%xk=rI>j8&wQ!QZ_k*1{PS+W66w{wv3!_My>h)@9lUo0rOE2Y;thupmi~?l4XMIt z>E6QC4Q~wXPvvY=Ak2lK%aX3N#iMq)D{6Q9sFBRH`JfC%WlqRl1;)e~GFWIZ((Bm$Qj{2%%Inp?4 zq48p7SI00?Qu0LB=us^){o6bFrW52tf?%A$*)xp`s zlu>`Ek8!%a>w~+{J`HJ>Er)G1P}Qf*^IaU^-GEKyg{2fBl&4Uy{$Xe$iuGkE*&H`$XAKO;Xb7r$DB_v&lM@`@GzgMFzy4;nAIWWaN9 zCjA9A=c5qM3$Lc%9tZO`z2j2u?S4W;c;ad#inBnU(Qh%=8V+rg-pL_Ga_=nS8DMwr zfGe2CM-iW0X8`X7CF>tQ$)X4OkoCFjv>_MFUwsutt9};HUz2W}99upxpN8e`Uz?bM z_W6DK!tGIYVKk*xX zhkY_&K9ylSP;*)r`s=uyAn%bZcyIGj-PT*LEExZp+$DRAaF|akPJAA`n+ff6%T?8- z;yK%bH0xA$H-KSBKyY`ne%?vdtgV{41$)5v7qE{QunwMaxoeVREn%oj2o zHC^l_&|hKS=U0#>7++)VwF7kjL3=d_%~vaBh<_1YY;Jt+_W42%-8mNzHRhj4BBV% z_V(~@Iv8KcNjhzfp)eoZo%kICu+rm(*uL?90_h*nl&J&`iI%|;aRI1;`|IGiTo#`~OxeoPtl_-6XQFeN# z-mtwz`BTjh>cc`h{-%I*XNSMuP!d!xMZx;k+Qe^mu;MTpHpC_ICS?fe5&qAxPJ{<- zYT0#Az)Jz08aeiQopS{F+A`=G=2(qLmYlXUR~y)o=WSoiG?)hKL*AV2c-3*}pY5`O z$YB}&oxhK=X*gJ20r6b=b9;6^0peM=-ZS|Er7YTQ;z#DF2Iam9IPd91n|@u-59kjO zYYm0KREX#7BD!U6b!a~g)>+1UEp;@(;fqjp6%naw|7#-aFM=ATg^4ABzSOL)<(`bd zS;SsLV4DThKPm>FpN-=X&q2B#=1Hd^KTrKPYP-J`-YdKDoJ0F+9lWP>*`JnLU>fQ( z_+pEm={}6F-@7#}OrIV_Z&0<>DT2NO!=LF5ub+eY0)Nk$@DsFAzDU_JLxoX9NTaGG z1I*_-X2e~g{sZ#QFGmU$<#cEt*RcMyVOdyj9KOMS*xk8O#@o z$mFDL7>qCBL*nt;3h@6|jQn9`o`CBe!MGT|#>>!Ohj^_j5i5wF+Ss)8n!PG$Qg)|f z4h5(y{&*328b#558FjV(d)jEa1B>`V!~}Bo&aAaPxCa=LCMR-J4Ek$)g!@H@CB)B| z($O<-Z$N%NqS?NYEC}(_!}I>9emlHZ7VrJ2TACi_+mJNH=Y_tKsO81dAy=nix~M#N+&*gpX*qUWiY5l!v)!6)py7dd*2xeFitC5^6xT1Jl=-l4 zrOy7BWF`yLr-Sw8wAdL~ua+x^FnpMR_PJaAugW5ja`{t?bEW(O4hj@h5B3UWFw7T7)KI*cq6XBImpZ#qXwpjDr4j<<4-~W|& z1Fi@6a?l-m)1JJ8Z;pM*6@&DUuTmq*ufLrTLhq+8oDhCIgaisjH#!CHLzUdq8wD;a zpgMx{asNe)Ak%sM0{(l!`m6K&heHg+9eH~7m*0tnhVT6U`qf=LZ%1MNs&+r1E-eA$ z`3&D3iXtz#-ckRS*+g#x<4e#<^t0M-bu_d1tF`eg5&3E7ect`-Rc<`3Fa)vv4%RDwcM1U=kUMa`kSUuR**^ zxGi7E!Fu(x`P;jtyCEN@cA4ZPctAWDq_(tC$iV%UJCr)Ia}Qv>(KeMK{NSt^fQR?@5zBDrt5pR~BxevL*Xns4R7O4?O>uM6^;vS77qL!($RM>kYtf|&>8T0%NqBe7OIFZFoT%R^ z61FvrxXN>LK4=p~DOYIqxAS#T>)AD~M^f|1dV|7S9laW)G(Du3NfP>tu6TRjtr5u2 z>2Gkw$Pri%+6~)>(m#j#fY*D2)9yn1tmi&Ut$h#uwfM(PgUF$Z2J!VhbMGc1Pku}J z=`f0*p<0=yXbX-CT-bh8o4Ah5=@PBBvuY6M#K%{n|Cx^?Z{S7u^q+S^c%u(=J+_1I z7(5Ge`?kjo<_|ag%56i|PdoAXVKua=_#|Av`i$o2eI-hPd&e#bI?lrg+S9N7An73L zsoLUp|D_&!{7|HOY}7JR<>7NP2+S*V_ix3Pq~W~g*O&Zy|7(Qx{3>n633h*IpJTt= zl4Dt+eXi$a1RTHraYvtvud^q60-?WHX&sbal*psM5`;N#3KJ0)*)yM)yg{E4-%`Hn zmM&UEk_u}&x{5G1RNJi5*B}_}m_^A6c#nNt|5ebjFb!Y!BL9{t4hW5Us8d~bR zR&;H37Wv6wWs5PsM=psOf17_eza#JZMa7!ut5G|A7!z~m`{aGtHxqq-fcEl9SRYnha8eks(7vPJ=&BWVarcoPw#S^q)e8Bz4I`v-GsFmLyG1T(`2wK-(Q+_gB> z4$Qk0Wz}M*hWY(mgYwmlhcMnho1ChuiH7&;w_{#WNov7-T1|9+!pRHuQ4t$dwxovn zbRyGRKK6xo~F>YxXiMsl_L-vy}+IT6f^rE^p`pKx1v&3K#xe_jub``uk`XDDk zimnFgBm71uv1twJv-D6YuPh73({tC$hfUAH{g&wM-^!hP8+QEdVj24-vJ1u+W&9zo zOd&b+sU-WauWLg{rUB05 zD|9Ug+8qu1jMhq{V+;H!cm7|vpX>QsbTB_XXJ65eb%gas|9Sb}1-Z~Z^Vb$1Ca4xGYKpSX2mEx-}fQ~E@qzvy=-9q{ml`nXu50@>eH&|b~DInU@} z8@Z>YFo` z8=|O|Z$(z?gf7~u(>n4D%)7LUwNXpCJ0Ws{CYI9fFps zk*oq=%p-$r73@b}R3T?w%ep;lmv`i8KiO6|<^S#DV4Dl$>s(fkc>|Ng&fouh zk~YpO3GFk&r`Iy}&wTa`db?D%iwdaA?KW{rH6pSqv7rAR%;O{V(lzntX`@dFd6HJ! zbI2~|w@U`%HHf2n>T@j?XrE{Kf{o)65I+;X-r4>XkPkyees&I3LcU8n#?i=W3i(;b z!#pcQ9{ygID6(0)E{*2cYtV`U{Lov_BB-$ln)x~J{FMP5{Z{>gO~8H{iF%vGaT~nb z=J{-R58e;?8Gp8#d&>jz&!g6J0sJj6U+r3PdAQUA>xseeiSO1YV7&iXuM_q3f_Q(P zURYWQ`Uh3Qd(L|;3?l+{Ym&UZBIqPtLi+G49aNbuTiX}B%MrK_Tn~9ti{xLOr1g`6 z_0MdXa$=n%#EB!>nNHEMh{Xc!9$L>cg&ad-ruB z^w-~qeuivQa6L$>YajFa=N&npHmZp36A(X(c?Xk}zEx>$s_R1UlKSj|x z3&$^qMZmkq@wd3+H|G(RnEYisz=ydPg<>49Li?1Kp7%#4p+36u9U?VGu$~9AjxWjN zLH;p48n99n4C^tr04Ju3WAOL1{G#YPQ)zUX@7@qLNy(Mjg)z& zQoj@br*7P9eYy(q6CmBKANxZN6@Q4iy*~!{Srbi-RS-oN($`NKfj-dPU%zr*csP%& zTv%uxBFiEp7Xb5V<(1~I0{U>h9G;)+my-eckVk|&;1)x{jy|_O z>YW*#gY_FKxe?vwDvAE)&K&&eFoX=&+rDv?6GZ2Q!Z@$cI$V<>)*P#O~CGLO%}i3N*s~Rdd;3ZV55?{esL743`c-{>kFory2eQ z@=q@1V?V#E(&)>pKgI?diO6@?<2=U$L{Q#qODF4tw9&Fz-)ycwQ%G~cDKA0rj^%RV zs;Gtt%%?fKBdME@L4TRb+~@6Wg86ilJ8ODjm(Y$qa@RAh%yJ-pm}te5f1QT;^huh{ z?i}-@U_Y$MsYz)V*;{J*F|Jn>9VuCsaS+x+^RMNz>KtA|!d+;;Yv$J=PZsV>zI_PS z7vq1w7v!};{#j&cjO=EF=eHT}PQB9J57!(0o_9$NMKIpu0*=I8z60akt3Kx*@v{=z z&1*Np_;&~qjTmSqXo;dM$2s%))pgMR$o5uFyII6?qh!8E6V&tcB|(pOL;H{l?XJ`L z!}v7@ViPNKN`KWDfafK{+p2;{nv?Z{aVg zoUaf+iqfAudo-Ya59gDbi~FH{h_%-u&77cpT3Y{#?fI&P-h5m-MI0DLUTJSomR%G@ zZ%;q%Iw`4xHkuH#{F6zD9`(KoelYKwBXA_+76R?_PI21V;Uv_@P?I#zg2MQwVXcat z^MwApvF}aUQYXZ-W#;@@<3CUz#ja$5yF&746hAL@)omhDaGm|c?+H=#biEE!FSlX4%Dj zbI3P2?Om4#!Mo5i6z?wn(`Wsaa&cd&FT``-uyIL8A@oxT}_deujDw3ki{z0fu$)3}CXYVcU@X`8*H+8q(K)iajGVWFghJ1Kb@1p&} zCgj7oqB9+ADiWx;qLt~dJ4471GpSMsz5^(C=ozICx$5YJ$=$;KXBUt?cERjYUDZg+ zSw&^-rsW-fsBcv(giAm^4D_1P2@-+t%v5t(Rq16z`&d`yCT|{u^@c5DteSWR{5?CT zd~^I!ZM4}T=>9s9h^Xwh-Lt$7>iOs%ch<|`{m)b5QY_%z30A+3{|4Kuk;h09i}g8( zAH}sewm~0=pJ<8o7UDe^PfT}T&SgKO-qHWEdR2-J0p_o>2ee*3*aP|dg}bQ1emZ$H z1a)g&zd%IF`#wz_jTS}6u2NmP^bOo+`&C=AVmXb-tUbPIuTX>h@Qq38{AWMP!&ijh z`U~n)rR}7DeGcMz%!TIEZ&k=YqLL(f-4qz_xv8U^P1I1IsjW4&T5naf-uBeb=zT;a zEc07bk+dk9tjm;Xq^*NaUOuZ(+8@SY#+Uc`Ws;V^FTD8TONh>>A%rj*VVSLI~&qRQ2VL8t7TNfh_0A8yHLSF zlxggx)2&)vlK1aEj)$!YwWz(&$lq)Bv literal 231608 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 z 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 Wt^QlMVXT)}#SI@A@UPy#^8Wzjt8qmD From fe547feb74329804218084883de8edae76591b84 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 09:17:12 -0600 Subject: [PATCH 236/282] Don't keep track of non-burnable materials --- openmc/deplete/atom_number.py | 38 +++--------- openmc/deplete/openmc_wrapper.py | 64 ++++++-------------- tests/unit_tests/test_deplete_atom_number.py | 27 +++------ 3 files changed, 34 insertions(+), 95 deletions(-) diff --git a/openmc/deplete/atom_number.py b/openmc/deplete/atom_number.py index 63c9af836..f9d85091a 100644 --- a/openmc/deplete/atom_number.py +++ b/openmc/deplete/atom_number.py @@ -19,8 +19,6 @@ class AtomNumber(object): A dictionary mapping nuclide name as string to index. volume : OrderedDict of int to float Volume of geometry. - n_mat_burn : int - Number of materials to be burned. n_nuc_burn : int Number of nuclides to be burned. @@ -33,8 +31,6 @@ class AtomNumber(object): volume : numpy.array Volume of geometry indexed by mat_to_ind. If a volume is not found, it defaults to 1 so that reading density still works correctly. - n_mat_burn : int - Number of materials to be burned. n_nuc_burn : int Number of nuclides to be burned. n_mat : int @@ -45,30 +41,26 @@ class AtomNumber(object): Array storing total atoms indexed by the above dictionaries. burn_nuc_list : list of str A list of all nuclide material names. Used for sorting the simulation. - burn_mat_list : list of str - A list of all burning material names. Used for sorting the simulation. - """ - def __init__(self, mat_to_ind, nuc_to_ind, volume, n_mat_burn, n_nuc_burn): + """ + def __init__(self, mat_to_ind, nuc_to_ind, volume, n_nuc_burn): self.mat_to_ind = mat_to_ind self.nuc_to_ind = nuc_to_ind - self.volume = np.ones(self.n_mat) + self.volume = np.ones(len(mat_to_ind)) for mat in volume: - if str(mat) in self.mat_to_ind: - ind = self.mat_to_ind[str(mat)] + if mat in self.mat_to_ind: + ind = self.mat_to_ind[mat] self.volume[ind] = volume[mat] - self.n_mat_burn = n_mat_burn self.n_nuc_burn = n_nuc_burn self.number = np.zeros((self.n_mat, self.n_nuc)) # For performance, create storage for burn_nuc_list, burn_mat_list self._burn_nuc_list = None - self._burn_mat_list = None def __getitem__(self, pos): """Retrieves total atom number from AtomNumber. @@ -175,7 +167,7 @@ class AtomNumber(object): if isinstance(mat, str): mat = self.mat_to_ind[mat] - return self[mat, 0:self.n_nuc_burn] + return self[mat, :self.n_nuc_burn] def set_mat_slice(self, mat, val): """Sets atom quantity indexed by mats for all burned nuclides @@ -191,7 +183,7 @@ class AtomNumber(object): if isinstance(mat, str): mat = self.mat_to_ind[mat] - self[mat, 0:self.n_nuc_burn] = val + self[mat, :self.n_nuc_burn] = val @property def n_mat(self): @@ -218,19 +210,3 @@ class AtomNumber(object): self._burn_nuc_list[ind] = nuc return self._burn_nuc_list - - @property - def burn_mat_list(self): - """burn_mat_list : list of str - A list of all burning material names. Used for sorting the simulation. - """ - - if self._burn_mat_list is None: - self._burn_mat_list = [None] * self.n_mat_burn - - for mat in self.mat_to_ind: - ind = self.mat_to_ind[mat] - if ind < self.n_mat_burn: - self._burn_mat_list[ind] = mat - - return self._burn_mat_list diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index e5eaf52dc..b25afb79b 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -121,9 +121,9 @@ class OpenMCOperator(Operator): The depletion chain information necessary to form matrices and tallies. reaction_rates : openmc.deplete.ReactionRates Reaction rates from the last operator step. - burn_mat_to_id : OrderedDict of str to int + burn_mat_to_ind : OrderedDict of str to int Dictionary mapping material ID (as a string) to an index in reaction_rates. - burn_nuc_to_id : OrderedDict of str to int + burn_nuc_to_ind : OrderedDict of str to int Dictionary mapping nuclide name (as a string) to an index in reaction_rates. n_nuc : int @@ -148,18 +148,16 @@ class OpenMCOperator(Operator): # Clear out OpenMC, create task lists, distribute if comm.rank == 0: openmc.reset_auto_ids() - mat_burn_list, mat_not_burn_list, volume, self.mat_tally_ind, \ + mat_burn_list, volume, self.mat_tally_ind, \ nuc_dict = self.extract_mat_ids() else: # Dummy variables mat_burn_list = None - mat_not_burn_list = None volume = None nuc_dict = None self.mat_tally_ind = None mat_burn = comm.scatter(mat_burn_list) - mat_not_burn = comm.scatter(mat_not_burn_list) nuc_dict = comm.bcast(nuc_dict) volume = comm.bcast(volume) self.mat_tally_ind = comm.bcast(self.mat_tally_ind) @@ -168,7 +166,7 @@ class OpenMCOperator(Operator): self.load_participating() # Extract number densities from the geometry - self.extract_number(mat_burn, mat_not_burn, volume, nuc_dict) + self.extract_number(mat_burn, volume, nuc_dict) # Create reaction rates array index_rx = {rx: i for i, rx in enumerate(self.chain.reactions)} @@ -239,9 +237,7 @@ class OpenMCOperator(Operator): """ mat_burn = set() - mat_not_burn = set() nuc_set = set() - volume = OrderedDict() # Iterate once through the geometry to get dictionaries @@ -254,19 +250,14 @@ class OpenMCOperator(Operator): raise RuntimeError("Volume not specified for depletable " "material with ID={}.".format(mat.id)) volume[str(mat.id)] = mat.volume - else: - mat_not_burn.add(str(mat.id)) # Sort the sets mat_burn = sorted(mat_burn, key=int) - mat_not_burn = sorted(mat_not_burn, key=int) nuc_set = sorted(nuc_set) # Construct a global nuclide dictionary, burned first nuc_dict = copy.deepcopy(self.chain.nuclide_dict) - i = len(nuc_dict) - for nuc in nuc_set: if nuc not in nuc_dict: nuc_dict[nuc] = i @@ -274,47 +265,35 @@ class OpenMCOperator(Operator): # Decompose geometry mat_burn_lists = _chunks(mat_burn, comm.size) - mat_not_burn_lists = _chunks(mat_not_burn, comm.size) mat_tally_ind = OrderedDict() - for i, mat in enumerate(mat_burn): mat_tally_ind[mat] = i - return mat_burn_lists, mat_not_burn_lists, volume, mat_tally_ind, nuc_dict + return mat_burn_lists, volume, mat_tally_ind, nuc_dict - def extract_number(self, mat_burn, mat_not_burn, volume, nuc_dict): + def extract_number(self, mat_burn, volume, nuc_dict): """Construct self.number read from geometry Parameters ---------- mat_burn : list of int Materials to be burned managed by this thread. - mat_not_burn - Materials not to be burned managed by this thread. volume : OrderedDict of str to float Volumes for the above materials. nuc_dict : OrderedDict of str to int Nuclides to be used in the simulation. + """ - # Same with materials - mat_dict = OrderedDict() self.burn_mat_to_ind = OrderedDict() - i = 0 - for mat in mat_burn: - mat_dict[mat] = i + for i, mat in enumerate(mat_burn): self.burn_mat_to_ind[mat] = i - i += 1 - for mat in mat_not_burn: - mat_dict[mat] = i - i += 1 - - n_mat_burn = len(mat_burn) n_nuc_burn = len(self.chain) - self.number = AtomNumber(mat_dict, nuc_dict, volume, n_mat_burn, n_nuc_burn) + self.number = AtomNumber(self.burn_mat_to_ind, nuc_dict, volume, + n_nuc_burn) if self.settings.dilute_initial != 0.0: for nuc in self.burn_nuc_to_ind: @@ -323,7 +302,7 @@ class OpenMCOperator(Operator): # Now extract the number densities and store for mat in self.geometry.get_all_materials().values(): - if str(mat.id) in mat_dict: + if str(mat.id) in self.burn_mat_to_ind: self.set_number_from_mat(mat) def set_number_from_mat(self, mat): @@ -397,9 +376,6 @@ class OpenMCOperator(Operator): number_i = comm.bcast(self.number, root=rank) for mat in number_i.mat_to_ind: - if number_i.mat_to_ind[mat] >= number_i.n_mat_burn: - continue - nuclides = [] densities = [] for nuc in number_i.nuc_to_ind: @@ -507,12 +483,10 @@ class OpenMCOperator(Operator): Returns ------- list of numpy.array - A list of np.arrays containing total atoms of each cell. + A list of arrays containing total atoms of each material + """ - - total_density = [self.number.get_mat_slice(i) for i in range(self.number.n_mat_burn)] - - return total_density + return list(self.number.get_mat_slice(np.s_[:])) def set_density(self, total_density): """Sets density. @@ -522,12 +496,12 @@ class OpenMCOperator(Operator): Parameters ---------- - total_density : list of numpy.array + total_density : list of numpy.ndarray Total atoms. - """ + """ # Fill in values - for i in range(self.number.n_mat_burn): + for i in range(self.number.n_mat): self.number.set_mat_slice(i, total_density[i]) def unpack_tallies_and_normalize(self): @@ -581,7 +555,7 @@ class OpenMCOperator(Operator): break # Extract results - for i, mat in enumerate(self.number.burn_mat_list): + for i, mat in enumerate(self.burn_mat_to_ind): # Get tally index slab = materials.index(mat) @@ -686,7 +660,7 @@ class OpenMCOperator(Operator): """ nuc_list = self.number.burn_nuc_list - burn_list = self.number.burn_mat_list + burn_list = list(self.burn_mat_to_ind) volume = {} for i, mat in enumerate(burn_list): diff --git a/tests/unit_tests/test_deplete_atom_number.py b/tests/unit_tests/test_deplete_atom_number.py index e3eb22aa5..887e9af1b 100644 --- a/tests/unit_tests/test_deplete_atom_number.py +++ b/tests/unit_tests/test_deplete_atom_number.py @@ -7,11 +7,11 @@ from openmc.deplete import atom_number def test_indexing(): """Tests the __getitem__ and __setitem__ routines simultaneously.""" - mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} + mat_to_ind = {"10000" : 0, "10001" : 1} nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) number["10000", "U238"] = 1.0 number["10001", "U238"] = 2.0 @@ -42,7 +42,7 @@ def test_n_mat(): nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) assert number.n_mat == 2 @@ -53,7 +53,7 @@ def test_n_nuc(): nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) assert number.n_nuc == 3 @@ -64,22 +64,11 @@ def test_burn_nuc_list(): nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) assert number.burn_nuc_list == ["U238", "U235"] -def test_burn_mat_list(): - """Test the list of burned nuclides property""" - mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - volume = {"10000" : 0.38, "10001" : 0.21} - - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) - - assert number.burn_mat_list == ["10000", "10001"] - - def test_density_indexing(): """Tests the get and set_atom_density routines simultaneously.""" @@ -87,7 +76,7 @@ def test_density_indexing(): nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) number.set_atom_density("10000", "U238", 1.0) number.set_atom_density("10001", "U238", 2.0) @@ -144,7 +133,7 @@ def test_get_mat_slice(): nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) number.number = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]) @@ -164,7 +153,7 @@ def test_set_mat_slice(): nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2, 2) + number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) number.set_mat_slice(0, [1.0, 2.0]) From 2cd9aecf21628cdda83d5fd5c9606c21ccaafb73 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 10:15:48 -0600 Subject: [PATCH 237/282] Get rid of Operator.mat_tally_ind --- openmc/deplete/openmc_wrapper.py | 42 +++++++++++--------------------- openmc/deplete/results.py | 17 ++++++------- 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index b25afb79b..7cdfbd4d8 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -5,6 +5,7 @@ This module implements the depletion -> OpenMC linkage. import copy from collections import OrderedDict +from itertools import chain import os import random import sys @@ -126,10 +127,8 @@ class OpenMCOperator(Operator): burn_nuc_to_ind : OrderedDict of str to int Dictionary mapping nuclide name (as a string) to an index in reaction_rates. - n_nuc : int - Number of nuclides considered in the decay chain. - mat_tally_ind : OrderedDict of str to int - Dictionary mapping material ID to index in tally. + burnable_mats : list of str + All burnable material IDs """ def __init__(self, geometry, settings): @@ -138,7 +137,6 @@ class OpenMCOperator(Operator): self.geometry = geometry self.number = None self.participating_nuclides = None - self.reaction_rates = None self.burn_mat_to_ind = OrderedDict() self.burn_nuc_to_ind = None @@ -148,19 +146,18 @@ class OpenMCOperator(Operator): # Clear out OpenMC, create task lists, distribute if comm.rank == 0: openmc.reset_auto_ids() - mat_burn_list, volume, self.mat_tally_ind, \ - nuc_dict = self.extract_mat_ids() + mat_burn_list, volume, nuc_dict = self.extract_mat_ids() else: # Dummy variables mat_burn_list = None volume = None nuc_dict = None - self.mat_tally_ind = None - mat_burn = comm.scatter(mat_burn_list) + mat_burn_list = comm.bcast(mat_burn_list) nuc_dict = comm.bcast(nuc_dict) volume = comm.bcast(volume) - self.mat_tally_ind = comm.bcast(self.mat_tally_ind) + mat_burn = mat_burn_list[comm.rank] + self.burnable_mats = list(chain(*mat_burn_list)) # Load participating nuclides self.load_participating() @@ -230,8 +227,6 @@ class OpenMCOperator(Operator): List of non-burnable materials indexed by rank. volume : OrderedDict of str to float Volume of each cell - mat_tally_ind : OrderedDict of str to int - Dictionary mapping material ID to index in tally. nuc_dict : OrderedDict of str to int Nuclides in order of how they'll appear in the simulation. """ @@ -266,11 +261,7 @@ class OpenMCOperator(Operator): # Decompose geometry mat_burn_lists = _chunks(mat_burn, comm.size) - mat_tally_ind = OrderedDict() - for i, mat in enumerate(mat_burn): - mat_tally_ind[mat] = i - - return mat_burn_lists, volume, mat_tally_ind, nuc_dict + return mat_burn_lists, volume, nuc_dict def extract_number(self, mat_burn, volume, nuc_dict): """Construct self.number read from geometry @@ -463,7 +454,7 @@ class OpenMCOperator(Operator): """ # Create tallies for depleting regions materials = [openmc.capi.materials[int(i)] - for i in self.mat_tally_ind] + for i in self.burnable_mats] mat_filter = openmc.capi.MaterialFilter(materials, 1) # Set up a tally that has a material filter covering each depletable @@ -524,7 +515,7 @@ class OpenMCOperator(Operator): k_combined = openmc.capi.keff()[0] # Extract tally bins - materials = list(self.mat_tally_ind.keys()) + materials = self.burnable_mats nuclides = openmc.capi.tallies[1].nuclides # Form fast map @@ -639,11 +630,6 @@ class OpenMCOperator(Operator): self.burn_nuc_to_ind[name] = nuc_ind nuc_ind += 1 - @property - def n_nuc(self): - """Number of nuclides considered in the decay chain.""" - return len(self.chain) - def get_results_info(self): """Returns volume list, cell lists, and nuc lists. @@ -655,10 +641,10 @@ class OpenMCOperator(Operator): 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_dict : OrderedDict of str to int - Maps cell name to index in global geometry. - """ + full_burn_list : list + List of all burnable material IDs + """ nuc_list = self.number.burn_nuc_list burn_list = list(self.burn_mat_to_ind) @@ -670,7 +656,7 @@ class OpenMCOperator(Operator): 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.mat_tally_ind + return volume, nuc_list, burn_list, self.burnable_mats def density_to_mat(dens_dict): diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 539ce5dd6..f7daca5b2 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -58,7 +58,7 @@ class Results(object): self.data = None - def allocate(self, volume, nuc_list, burn_list, full_burn_dict, stages): + def allocate(self, volume, nuc_list, burn_list, full_burn_list, stages): """Allocates memory of Results. Parameters @@ -69,16 +69,16 @@ class Results(object): 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_dict : dict of str to int - Map of material name to id in global geometry. + 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 = OrderedDict() self.mat_to_ind = OrderedDict() - self.mat_to_hdf5_ind = copy.deepcopy(full_burn_dict) + self.mat_to_hdf5_ind = {mat: i for i, mat in enumerate(full_burn_list)} for i, mat in enumerate(burn_list): self.mat_to_ind[mat] = i @@ -177,10 +177,9 @@ class Results(object): handle.create_dataset("version", data=RESULTS_VERSION) - mat_int = sorted([int(mat) for mat in self.mat_to_hdf5_ind]) - mat_list = [str(mat) for mat in mat_int] - nuc_list = sorted(self.nuc_to_ind.keys()) - rxn_list = sorted(self.rates[0].index_rx.keys()) + 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) From 4e21e398c83f0997fc20b7c4a08952cfc73b482b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 10:19:02 -0600 Subject: [PATCH 238/282] Move density_to_mat to example_geometry.py --- openmc/deplete/openmc_wrapper.py | 21 --------------------- scripts/example_geometry.py | 23 ++++++++++++++++++++++- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 7cdfbd4d8..c8a3bcca9 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -657,24 +657,3 @@ class OpenMCOperator(Operator): volume = {k: v for d in volume_list for k, v in d.items()} return volume, nuc_list, burn_list, self.burnable_mats - - -def density_to_mat(dens_dict): - """Generates an OpenMC material from a cell ID and self.number_density. - - Parameters - ---------- - m_id : int - Cell ID. - Returns - ------- - openmc.Material - The OpenMC material filled with nuclides. - """ - - mat = openmc.Material() - for key in dens_dict: - mat.add_nuclide(key, 1.0e-24*dens_dict[key]) - mat.set_density('sum') - - return mat diff --git a/scripts/example_geometry.py b/scripts/example_geometry.py index 09ce0576f..ca10c1f72 100644 --- a/scripts/example_geometry.py +++ b/scripts/example_geometry.py @@ -9,7 +9,28 @@ import math import numpy as np import openmc -from openmc.deplete import density_to_mat + + +def density_to_mat(dens_dict): + """Generates an OpenMC material from a cell ID and self.number_density. + + Parameters + ---------- + dens_dict : dict + Dictionary mapping nuclide names to densities + + Returns + ------- + openmc.Material + The OpenMC material filled with nuclides. + + """ + mat = openmc.Material() + for key in dens_dict: + mat.add_nuclide(key, 1.0e-24*dens_dict[key]) + mat.set_density('sum') + + return mat def generate_initial_number_density(): From ea335e06961a94c6b680462e24224b998920f5dc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 10:24:10 -0600 Subject: [PATCH 239/282] Change OPENDEPLETE_CHAIN -> OPENMC_DEPLETE_CHAIN --- openmc/deplete/abc.py | 6 +++--- openmc/deplete/chain.py | 4 ++-- openmc/deplete/openmc_wrapper.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 5441829b4..e8f65f887 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -23,8 +23,8 @@ class Settings(object): output_dir : pathlib.Path Path to output directory to save results. chain_file : str - Path to the depletion chain xml file. Defaults to the - :envvar:`OPENDEPLETE_CHAIN` environment variable if it exists. + Path to the depletion chain XML file. Defaults to the + :envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists. 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 @@ -37,7 +37,7 @@ class Settings(object): """ def __init__(self): try: - self.chain_file = os.environ["OPENDEPLETE_CHAIN"] + self.chain_file = os.environ["OPENMC_DEPLETE_CHAIN"] except KeyError: self.chain_file = None self.dt_vec = None diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 2af64e172..2d2e71a77 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -332,9 +332,9 @@ class Chain(object): root = ET.parse(filename) except Exception: if filename is None: - print("No chain specified, either manually or in environment variable OPENDEPLETE_CHAIN.") + print("No chain specified, either manually or in environment variable OPENMC_DEPLETE_CHAIN.") else: - print('Decay chain "', filename, '" is invalid.') + print('Decay chain "{}" is invalid.'.format(filename)) raise for i, nuclide_elem in enumerate(root.findall('nuclide_table')): diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index c8a3bcca9..a01949a91 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -51,8 +51,8 @@ class OpenMCSettings(Settings): output_dir : pathlib.Path Path to output directory to save results. chain_file : str - Path to the depletion chain xml file. Defaults to the - :envvar:`OPENDEPLETE_CHAIN` environment variable if it exists. + Path to the depletion chain XML file. Defaults to the + :envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists. 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 From 78e1afb0ffb6e07c32714967ea0001e120303b28 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 13:10:28 -0600 Subject: [PATCH 240/282] Rename participating_nuclides -> nuclides_with_data --- openmc/deplete/atom_number.py | 14 ++--- openmc/deplete/chain.py | 7 ++- openmc/deplete/openmc_wrapper.py | 103 +++++++++++++++---------------- 3 files changed, 60 insertions(+), 64 deletions(-) diff --git a/openmc/deplete/atom_number.py b/openmc/deplete/atom_number.py index f9d85091a..fc9cb8433 100644 --- a/openmc/deplete/atom_number.py +++ b/openmc/deplete/atom_number.py @@ -96,9 +96,9 @@ class AtomNumber(object): These indexes can be strings (which get converted to integers via the dictionaries), integers used directly, or slices. val : float - The value to set the array to. - """ + The value [atom] to set the array to. + """ mat, nuc = pos if isinstance(mat, str): mat = self.mat_to_ind[mat] @@ -119,10 +119,10 @@ class AtomNumber(object): Returns ------- - numpy.array - The density indexed. - """ + numpy.ndarray + Density in [atom/cm^3] + """ if isinstance(mat, str): mat = self.mat_to_ind[mat] if isinstance(nuc, str): @@ -140,9 +140,9 @@ class AtomNumber(object): nuc : str, int or slice Nuclide index. val : numpy.array - Array of values to set. - """ + Array of densities to set in [atom/cm^3] + """ if isinstance(mat, str): mat = self.mat_to_ind[mat] if isinstance(nuc, str): diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 2d2e71a77..ec8d79f81 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -332,10 +332,11 @@ class Chain(object): root = ET.parse(filename) except Exception: if filename is None: - print("No chain specified, either manually or in environment variable OPENMC_DEPLETE_CHAIN.") + msg = ("No chain specified, either manually or in environment " + "variable OPENMC_DEPLETE_CHAIN.") else: - print('Decay chain "{}" is invalid.'.format(filename)) - raise + msg = 'Decay chain "{}" is invalid.'.format(filename) + raise IOError(msg) for i, nuclide_elem in enumerate(root.findall('nuclide_table')): nuc = Nuclide.from_xml(nuclide_elem) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index a01949a91..7120926bc 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -116,7 +116,7 @@ class OpenMCOperator(Operator): The OpenMC geometry object. number : openmc.deplete.AtomNumber Total number of atoms in simulation. - participating_nuclides : set of str + 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. @@ -126,7 +126,8 @@ class OpenMCOperator(Operator): Dictionary mapping material ID (as a string) to an index in reaction_rates. burn_nuc_to_ind : OrderedDict of str to int Dictionary mapping nuclide name (as a string) to an index in - reaction_rates. + reaction_rates. Consists of all nuclides with neutron data and appearing + in the depletion chain. burnable_mats : list of str All burnable material IDs @@ -136,7 +137,6 @@ class OpenMCOperator(Operator): self.geometry = geometry self.number = None - self.participating_nuclides = None self.burn_mat_to_ind = OrderedDict() self.burn_nuc_to_ind = None @@ -146,7 +146,7 @@ class OpenMCOperator(Operator): # Clear out OpenMC, create task lists, distribute if comm.rank == 0: openmc.reset_auto_ids() - mat_burn_list, volume, nuc_dict = self.extract_mat_ids() + mat_burn_list, volume, nuc_dict = self._extract_mat_ids() else: # Dummy variables mat_burn_list = None @@ -160,10 +160,10 @@ class OpenMCOperator(Operator): self.burnable_mats = list(chain(*mat_burn_list)) # Load participating nuclides - self.load_participating() + self._load_participating() # Extract number densities from the geometry - self.extract_number(mat_burn, volume, nuc_dict) + self._extract_number(mat_burn, volume, nuc_dict) # Create reaction rates array index_rx = {rx: i for i, rx in enumerate(self.chain.reactions)} @@ -190,7 +190,7 @@ class OpenMCOperator(Operator): openmc.reset_auto_ids() # Update status - self.set_density(vec) + self._set_density(vec) time_start = time.time() @@ -205,7 +205,7 @@ class OpenMCOperator(Operator): time_openmc = time.time() # Extract results - op_result = self.unpack_tallies_and_normalize() + op_result = self._unpack_tallies_and_normalize() if comm.rank == 0: time_unpack = time.time() @@ -216,7 +216,7 @@ class OpenMCOperator(Operator): return copy.deepcopy(op_result) - def extract_mat_ids(self): + def _extract_mat_ids(self): """Extracts materials and assigns them to processes. Returns @@ -229,15 +229,15 @@ class OpenMCOperator(Operator): Volume of each cell nuc_dict : OrderedDict of str to int Nuclides in order of how they'll appear in the simulation. - """ + """ mat_burn = set() nuc_set = 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_nuclide_densities(): + for nuclide in mat.get_nuclides(): nuc_set.add(nuclide) if mat.depletable: mat_burn.add(str(mat.id)) @@ -263,7 +263,7 @@ class OpenMCOperator(Operator): return mat_burn_lists, volume, nuc_dict - def extract_number(self, mat_burn, volume, nuc_dict): + def _extract_number(self, mat_burn, volume, nuc_dict): """Construct self.number read from geometry Parameters @@ -281,10 +281,8 @@ class OpenMCOperator(Operator): for i, mat in enumerate(mat_burn): self.burn_mat_to_ind[mat] = i - n_nuc_burn = len(self.chain) - self.number = AtomNumber(self.burn_mat_to_ind, nuc_dict, volume, - n_nuc_burn) + len(self.chain)) if self.settings.dilute_initial != 0.0: for nuc in self.burn_nuc_to_ind: @@ -294,23 +292,22 @@ class OpenMCOperator(Operator): # Now extract the number densities and store for mat in self.geometry.get_all_materials().values(): if str(mat.id) in self.burn_mat_to_ind: - self.set_number_from_mat(mat) + self._set_number_from_mat(mat) - def set_number_from_mat(self, mat): + def _set_number_from_mat(self, mat): """Extracts material and number densities from openmc.Material Parameters ---------- - mat : openmc.Materials + mat : openmc.Material The material to read from - """ + """ mat_id = str(mat.id) mat_ind = self.number.mat_to_ind[mat_id] - nuc_dens = mat.get_nuclide_atom_densities() - for nuclide in nuc_dens: - number = nuc_dens[nuclide][1] * 1.0e24 + for nuclide, density in mat.get_nuclide_atom_densities().values(): + number = density * 1.0e24 self.number.set_atom_density(mat_id, nuclide, number) def form_matrix(self, y, mat): @@ -344,17 +341,17 @@ class OpenMCOperator(Operator): if comm.rank == 0: self.geometry.export_to_xml() self.settings.settings.export_to_xml() - self.generate_materials_xml() + self._generate_materials_xml() # Initialize OpenMC library comm.barrier() openmc.capi.init(comm) # Generate tallies in memory - self.generate_tallies() + self._generate_tallies() # Return number density vector - return self.total_density_list() + return list(self.number.get_mat_slice(np.s_[:])) def finalize(self): """Finalize a depletion simulation and release resources.""" @@ -370,7 +367,7 @@ class OpenMCOperator(Operator): nuclides = [] densities = [] for nuc in number_i.nuc_to_ind: - if nuc in self.participating_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. @@ -395,14 +392,14 @@ class OpenMCOperator(Operator): mat_internal = openmc.capi.materials[int(mat)] mat_internal.set_densities(nuclides, densities) - def generate_materials_xml(self): + 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()) @@ -414,12 +411,26 @@ class OpenMCOperator(Operator): 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 cells marked for # burning in which the number density is greater than zero. for nuc in self.number.nuc_to_ind: - if nuc in self.participating_nuclides: + if nuc in self.nuclides_with_data: if np.sum(self.number[:, nuc]) > 0.0: nuc_set.add(nuc) @@ -439,12 +450,10 @@ class OpenMCOperator(Operator): nuc_list = None # Store list of tally nuclides on each process - nuc_list = comm.bcast(nuc_list, root=0) - tally_nuclides = [nuc for nuc in nuc_list if nuc in self.chain] + nuc_list = comm.bcast(nuc_list) + return [nuc for nuc in nuc_list if nuc in self.chain] - return tally_nuclides - - def generate_tallies(self): + def _generate_tallies(self): """Generates depletion tallies. Using information from the depletion chain as well as the nuclides @@ -465,21 +474,7 @@ class OpenMCOperator(Operator): tally_dep.scores = self.chain.reactions tally_dep.filters = [mat_filter] - def total_density_list(self): - """Returns a list of total density lists. - - This list is in the exact same order as depletion_matrix_list, so that - matrix exponentiation can be done easily. - - Returns - ------- - list of numpy.array - A list of arrays containing total atoms of each material - - """ - return list(self.number.get_mat_slice(np.s_[:])) - - def set_density(self, total_density): + def _set_density(self, total_density): """Sets density. Sets the density in the exact same order as total_density_list outputs, @@ -495,7 +490,7 @@ class OpenMCOperator(Operator): for i in range(self.number.n_mat): self.number.set_mat_slice(i, total_density[i]) - def unpack_tallies_and_normalize(self): + def _unpack_tallies_and_normalize(self): """Unpack tallies from OpenMC and return an operator result This method uses OpenMC's C API bindings to determine the k-effective @@ -587,7 +582,7 @@ class OpenMCOperator(Operator): return OperatorResult(k_combined, rates) - def load_participating(self): + def _load_participating(self): """Loads a cross_sections.xml file to find participating nuclides. This allows for nuclides that are important in the decay chain but not @@ -602,7 +597,7 @@ class OpenMCOperator(Operator): except KeyError: filename = None - self.participating_nuclides = set() + self.nuclides_with_data = set() try: tree = ET.parse(filename) @@ -624,8 +619,8 @@ class OpenMCOperator(Operator): 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 self.participating_nuclides: - self.participating_nuclides.add(name) + if name not in self.nuclides_with_data: + self.nuclides_with_data.add(name) if name in self.chain: self.burn_nuc_to_ind[name] = nuc_ind nuc_ind += 1 From 6832071b4cf8a5f1b01d20079a902b83abac7e4c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 13:35:51 -0600 Subject: [PATCH 241/282] Move set_density method to AtomNumber --- openmc/deplete/atom_number.py | 69 +++++++++++++++++++------------- openmc/deplete/openmc_wrapper.py | 18 +-------- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/openmc/deplete/atom_number.py b/openmc/deplete/atom_number.py index fc9cb8433..f1b0155d7 100644 --- a/openmc/deplete/atom_number.py +++ b/openmc/deplete/atom_number.py @@ -107,6 +107,32 @@ class AtomNumber(object): self.number[mat, nuc] = val + @property + def n_mat(self): + """Number of materials.""" + return len(self.mat_to_ind) + + @property + def n_nuc(self): + """Number of nuclides.""" + return len(self.nuc_to_ind) + + @property + def burn_nuc_list(self): + """burn_nuc_list : list of str + A list of all nuclide material names. Used for sorting the simulation. + """ + + if self._burn_nuc_list is None: + self._burn_nuc_list = [None] * self.n_nuc_burn + + for nuc in self.nuc_to_ind: + ind = self.nuc_to_ind[nuc] + if ind < self.n_nuc_burn: + self._burn_nuc_list[ind] = nuc + + return self._burn_nuc_list + def get_atom_density(self, mat, nuc): """Accesses atom density instead of total number. @@ -160,10 +186,10 @@ class AtomNumber(object): Returns ------- - numpy.array - The slice requested. - """ + numpy.ndarray + The slice requested in [atom]. + """ if isinstance(mat, str): mat = self.mat_to_ind[mat] @@ -177,36 +203,25 @@ class AtomNumber(object): mat : str, int or slice Material index. val : numpy.array - The slice to set. - """ + The slice to set in [atom] + """ if isinstance(mat, str): mat = self.mat_to_ind[mat] self[mat, :self.n_nuc_burn] = val - @property - def n_mat(self): - """Number of materials.""" - return len(self.mat_to_ind) + def set_density(self, total_density): + """Sets density. - @property - def n_nuc(self): - """Number of nuclides.""" - return len(self.nuc_to_ind) + 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. - @property - def burn_nuc_list(self): - """burn_nuc_list : list of str - A list of all nuclide material names. Used for sorting the simulation. """ - - if self._burn_nuc_list is None: - self._burn_nuc_list = [None] * self.n_nuc_burn - - for nuc in self.nuc_to_ind: - ind = self.nuc_to_ind[nuc] - if ind < self.n_nuc_burn: - self._burn_nuc_list[ind] = nuc - - return self._burn_nuc_list + for i in range(self.n_mat): + self.set_mat_slice(i, total_density[i]) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 7120926bc..43a171afc 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -190,7 +190,7 @@ class OpenMCOperator(Operator): openmc.reset_auto_ids() # Update status - self._set_density(vec) + self.number.set_density(vec) time_start = time.time() @@ -474,22 +474,6 @@ class OpenMCOperator(Operator): tally_dep.scores = self.chain.reactions tally_dep.filters = [mat_filter] - 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. - - """ - # Fill in values - for i in range(self.number.n_mat): - self.number.set_mat_slice(i, total_density[i]) - def _unpack_tallies_and_normalize(self): """Unpack tallies from OpenMC and return an operator result From 2a1c66913ad0a22c91757bf231e0023825f01110 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 13:47:49 -0600 Subject: [PATCH 242/282] Don't use hard-wired IDs for MaterialFilter and Tally --- openmc/deplete/openmc_wrapper.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 43a171afc..42bd0e600 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -196,7 +196,7 @@ class OpenMCOperator(Operator): # Update material compositions and tally nuclides self._update_materials() - openmc.capi.tallies[1].nuclides = self._get_tally_nuclides() + self._tally.nuclides = self._get_tally_nuclides() # Run OpenMC openmc.capi.reset() @@ -464,15 +464,15 @@ class OpenMCOperator(Operator): # Create tallies for depleting regions materials = [openmc.capi.materials[int(i)] for i in self.burnable_mats] - mat_filter = openmc.capi.MaterialFilter(materials, 1) + 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. - tally_dep = openmc.capi.Tally(1) - tally_dep.scores = self.chain.reactions - tally_dep.filters = [mat_filter] + self._tally = openmc.capi.Tally() + self._tally.scores = self.chain.reactions + self._tally.filters = [mat_filter] def _unpack_tallies_and_normalize(self): """Unpack tallies from OpenMC and return an operator result @@ -495,7 +495,7 @@ class OpenMCOperator(Operator): # Extract tally bins materials = self.burnable_mats - nuclides = openmc.capi.tallies[1].nuclides + nuclides = self._tally.nuclides # Form fast map nuc_ind = [rates.index_nuc[nuc] for nuc in nuclides] @@ -530,7 +530,7 @@ class OpenMCOperator(Operator): slab = materials.index(mat) # Get material results hyperslab - results = openmc.capi.tallies[1].results[slab, :, 1] + results = self._tally.results[slab, :, 1] # Zero out reaction rates and nuclide numbers rates_expanded[:] = 0.0 From 945675aaaa5d27b7347c81fc17c97a945487f1f9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 14:14:56 -0600 Subject: [PATCH 243/282] Simplification of OpenMCOperator.__init__ --- openmc/deplete/atom_number.py | 2 +- openmc/deplete/openmc_wrapper.py | 79 ++++++++++++-------------------- 2 files changed, 30 insertions(+), 51 deletions(-) diff --git a/openmc/deplete/atom_number.py b/openmc/deplete/atom_number.py index f1b0155d7..3ad4968a8 100644 --- a/openmc/deplete/atom_number.py +++ b/openmc/deplete/atom_number.py @@ -18,7 +18,7 @@ class AtomNumber(object): nuc_to_ind : OrderedDict of str to int A dictionary mapping nuclide name as string to index. volume : OrderedDict of int to float - Volume of geometry. + Volume of each material in [cm^3] n_nuc_burn : int Number of nuclides to be burned. diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 42bd0e600..f3afedb29 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -30,15 +30,14 @@ from .chain import Chain from .reaction_rates import ReactionRates -def _chunks(items, n): - min_size, extra = divmod(len(items), n) +def _distribute(items): + min_size, extra = divmod(len(items), comm.size) j = 0 - chunk_list = [] - for i in range(n): + for i in range(comm.size): chunk_size = min_size + int(i < extra) - chunk_list.append(items[j:j + chunk_size]) + if comm.rank == i: + return items[j:j + chunk_size] j += chunk_size - return chunk_list class OpenMCSettings(Settings): @@ -134,36 +133,21 @@ class OpenMCOperator(Operator): """ def __init__(self, geometry, settings): super().__init__(settings) - self.geometry = geometry - self.number = None - self.burn_mat_to_ind = OrderedDict() - self.burn_nuc_to_ind = None # Read depletion chain self.chain = Chain.from_xml(settings.chain_file) # Clear out OpenMC, create task lists, distribute - if comm.rank == 0: - openmc.reset_auto_ids() - mat_burn_list, volume, nuc_dict = self._extract_mat_ids() - else: - # Dummy variables - mat_burn_list = None - volume = None - nuc_dict = None + openmc.reset_auto_ids() + self.burnable_mats, volume, nuc_dict = self._get_burnable_mats() + local_mats = _distribute(self.burnable_mats) - mat_burn_list = comm.bcast(mat_burn_list) - nuc_dict = comm.bcast(nuc_dict) - volume = comm.bcast(volume) - mat_burn = mat_burn_list[comm.rank] - self.burnable_mats = list(chain(*mat_burn_list)) - - # Load participating nuclides + # Determine which nuclides have incident neutron data self._load_participating() # Extract number densities from the geometry - self._extract_number(mat_burn, volume, nuc_dict) + self._extract_number(local_mats, volume, nuc_dict) # Create reaction rates array index_rx = {rx: i for i, rx in enumerate(self.chain.reactions)} @@ -216,69 +200,64 @@ class OpenMCOperator(Operator): return copy.deepcopy(op_result) - def _extract_mat_ids(self): - """Extracts materials and assigns them to processes. + def _get_burnable_mats(self): + """Determine depletable materials, volumes, and nuclids Returns ------- - mat_burn_lists : list of list of int - List of burnable materials indexed by rank. - mat_not_burn_lists : list of list of int - List of non-burnable materials indexed by rank. + burnable_mats : list of str + List of burnable material IDs volume : OrderedDict of str to float - Volume of each cell + Volume of each material in [cm^3] nuc_dict : OrderedDict of str to int Nuclides in order of how they'll appear in the simulation. """ - mat_burn = set() - nuc_set = set() + 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(): - nuc_set.add(nuclide) + model_nuclides.add(nuclide) if mat.depletable: - mat_burn.add(str(mat.id)) + 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 # Sort the sets - mat_burn = sorted(mat_burn, key=int) - nuc_set = sorted(nuc_set) + burnable_mats = sorted(burnable_mats, key=int) + model_nuclides = sorted(model_nuclides) # Construct a global nuclide dictionary, burned first nuc_dict = copy.deepcopy(self.chain.nuclide_dict) i = len(nuc_dict) - for nuc in nuc_set: + for nuc in model_nuclides: if nuc not in nuc_dict: nuc_dict[nuc] = i i += 1 - # Decompose geometry - mat_burn_lists = _chunks(mat_burn, comm.size) + return burnable_mats, volume, nuc_dict - return mat_burn_lists, volume, nuc_dict - - def _extract_number(self, mat_burn, volume, nuc_dict): - """Construct self.number read from geometry + def _extract_number(self, local_mats, volume, nuc_dict): + """Construct AtomNumber using geometry Parameters ---------- - mat_burn : list of int - Materials to be burned managed by this thread. + local_mats : list of str + Material IDs to be managed by this process volume : OrderedDict of str to float - Volumes for the above materials. + Volumes for the above materials in [cm^3] nuc_dict : OrderedDict of str to int Nuclides to be used in the simulation. """ # Same with materials self.burn_mat_to_ind = OrderedDict() - for i, mat in enumerate(mat_burn): + for i, mat in enumerate(local_mats): self.burn_mat_to_ind[mat] = i self.number = AtomNumber(self.burn_mat_to_ind, nuc_dict, volume, From 65061bdadcb04904edb3775fb837bc1037cfc8bf Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 14:24:48 -0600 Subject: [PATCH 244/282] Change burn_nuc_to_ind to _burnable_nucs --- openmc/deplete/openmc_wrapper.py | 28 +++++++++++----------------- openmc/deplete/reaction_rates.py | 10 +++++----- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index f3afedb29..3c3b974c9 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -123,10 +123,6 @@ class OpenMCOperator(Operator): Reaction rates from the last operator step. burn_mat_to_ind : OrderedDict of str to int Dictionary mapping material ID (as a string) to an index in reaction_rates. - burn_nuc_to_ind : OrderedDict of str to int - Dictionary mapping nuclide name (as a string) to an index in - reaction_rates. Consists of all nuclides with neutron data and appearing - in the depletion chain. burnable_mats : list of str All burnable material IDs @@ -144,7 +140,9 @@ class OpenMCOperator(Operator): local_mats = _distribute(self.burnable_mats) # Determine which nuclides have incident neutron data - self._load_participating() + self.nuclides_with_data = self._get_nuclides_with_data() + self._burnable_nucs = [nuc for nuc in self.nuclides_with_data + if nuc in self.chain] # Extract number densities from the geometry self._extract_number(local_mats, volume, nuc_dict) @@ -152,7 +150,7 @@ class OpenMCOperator(Operator): # Create reaction rates array index_rx = {rx: i for i, rx in enumerate(self.chain.reactions)} self.reaction_rates = ReactionRates( - self.burn_mat_to_ind, self.burn_nuc_to_ind, index_rx) + self.burn_mat_to_ind, self._burnable_nucs, index_rx) def __call__(self, vec, print_out=True): """Runs a simulation. @@ -264,7 +262,7 @@ class OpenMCOperator(Operator): len(self.chain)) if self.settings.dilute_initial != 0.0: - for nuc in self.burn_nuc_to_ind: + for nuc in self._burnable_nucs: self.number.set_atom_density(np.s_[:], nuc, self.settings.dilute_initial) @@ -545,7 +543,7 @@ class OpenMCOperator(Operator): return OperatorResult(k_combined, rates) - def _load_participating(self): + 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 @@ -560,7 +558,7 @@ class OpenMCOperator(Operator): except KeyError: filename = None - self.nuclides_with_data = set() + nuclides = set() try: tree = ET.parse(filename) @@ -572,9 +570,6 @@ class OpenMCOperator(Operator): raise IOError(msg) root = tree.getroot() - self.burn_nuc_to_ind = OrderedDict() - nuc_ind = 0 - for nuclide_node in root.findall('library'): mats = nuclide_node.get('materials') if not mats: @@ -582,11 +577,10 @@ class OpenMCOperator(Operator): 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 self.nuclides_with_data: - self.nuclides_with_data.add(name) - if name in self.chain: - self.burn_nuc_to_ind[name] = nuc_ind - nuc_ind += 1 + if name not in nuclides: + nuclides.add(name) + + return nuclides def get_results_info(self): """Returns volume list, cell lists, and nuc lists. diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py index a47908517..d1e1b0f1e 100644 --- a/openmc/deplete/reaction_rates.py +++ b/openmc/deplete/reaction_rates.py @@ -15,8 +15,8 @@ class ReactionRates(np.ndarray): ---------- 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. + nuclides : list of str + Depletable nuclides index_rx : OrderedDict of str to int A dictionary mapping reaction name as string to index. @@ -36,15 +36,15 @@ class ReactionRates(np.ndarray): Number of reactions. """ - def __new__(cls, index_mat, index_nuc, index_rx): + def __new__(cls, index_mat, nuclides, index_rx): # Create appropriately-sized zeroed-out ndarray - shape = (len(index_mat), len(index_nuc), len(index_rx)) + shape = (len(index_mat), len(nuclides), len(index_rx)) obj = super().__new__(cls, shape) obj[:] = 0.0 # Add mapping attributes obj.index_mat = index_mat - obj.index_nuc = index_nuc + obj.index_nuc = {nuc: i for i, nuc in enumerate(nuclides)} obj.index_rx = index_rx return obj From 704a131f2d2039138e566216639011df5ddc102c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 16:38:45 -0600 Subject: [PATCH 245/282] Some refactoring of AtomNumber. Get rid of burn_mat_to_ind --- openmc/deplete/atom_number.py | 122 +++++++++---------- openmc/deplete/openmc_wrapper.py | 65 +++++----- openmc/deplete/results.py | 34 ------ tests/unit_tests/test_deplete_atom_number.py | 59 +++------ 4 files changed, 103 insertions(+), 177 deletions(-) diff --git a/openmc/deplete/atom_number.py b/openmc/deplete/atom_number.py index 3ad4968a8..1a142676c 100644 --- a/openmc/deplete/atom_number.py +++ b/openmc/deplete/atom_number.py @@ -7,60 +7,55 @@ import numpy as np class AtomNumber(object): - """AtomNumber module. - - An ndarray to store atom densities with string, integer, or slice indexing. + """Stores local material compositions (atoms of each nuclide). Parameters ---------- - mat_to_ind : OrderedDict of str to int - A dictionary mapping material ID as string to index. - nuc_to_ind : OrderedDict of str to int - A dictionary mapping nuclide name as string to index. - volume : OrderedDict of int to float + 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 ---------- - mat_to_ind : OrderedDict of str to int - A dictionary mapping cell ID as string to index. - nuc_to_ind : OrderedDict of str to int - A dictionary mapping nuclide name as string to index. - volume : numpy.array - Volume of geometry indexed by mat_to_ind. If a volume is not found, - it defaults to 1 so that reading density still works correctly. + 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 nuclides to be burned. - n_mat : int - Number of materials. + Number of burnable nuclides. n_nuc : int - Number of nucs. - number : numpy.array - Array storing total atoms indexed by the above dictionaries. - burn_nuc_list : list of str - A list of all nuclide material names. Used for sorting the simulation. + Number of nuclidess. """ - def __init__(self, mat_to_ind, nuc_to_ind, volume, n_nuc_burn): + def __init__(self, local_mats, nuclides, volume, n_nuc_burn): + self.index_mat = {mat: i for i, mat in enumerate(local_mats)} + self.index_nuc = {nuc: i for i, nuc in enumerate(nuclides)} - self.mat_to_ind = mat_to_ind - self.nuc_to_ind = nuc_to_ind - - self.volume = np.ones(len(mat_to_ind)) - - for mat in volume: - if mat in self.mat_to_ind: - ind = self.mat_to_ind[mat] - self.volume[ind] = volume[mat] + 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((self.n_mat, self.n_nuc)) - - # For performance, create storage for burn_nuc_list, burn_mat_list - self._burn_nuc_list = None + self.number = np.zeros((len(local_mats), self.n_nuc)) def __getitem__(self, pos): """Retrieves total atom number from AtomNumber. @@ -80,9 +75,9 @@ class AtomNumber(object): mat, nuc = pos if isinstance(mat, str): - mat = self.mat_to_ind[mat] + mat = self.index_mat[mat] if isinstance(nuc, str): - nuc = self.nuc_to_ind[nuc] + nuc = self.index_nuc[nuc] return self.number[mat, nuc] @@ -101,37 +96,30 @@ class AtomNumber(object): """ mat, nuc = pos if isinstance(mat, str): - mat = self.mat_to_ind[mat] + mat = self.index_mat[mat] if isinstance(nuc, str): - nuc = self.nuc_to_ind[nuc] + nuc = self.index_nuc[nuc] self.number[mat, nuc] = val @property - def n_mat(self): - """Number of materials.""" - return len(self.mat_to_ind) + def materials(self): + return self.index_mat.keys() + + @property + def nuclides(self): + return self.index_nuc.keys() @property def n_nuc(self): """Number of nuclides.""" - return len(self.nuc_to_ind) + return len(self.index_nuc) @property - def burn_nuc_list(self): - """burn_nuc_list : list of str - A list of all nuclide material names. Used for sorting the simulation. - """ - - if self._burn_nuc_list is None: - self._burn_nuc_list = [None] * self.n_nuc_burn - - for nuc in self.nuc_to_ind: - ind = self.nuc_to_ind[nuc] - if ind < self.n_nuc_burn: - self._burn_nuc_list[ind] = nuc - - return self._burn_nuc_list + def burnable_nuclides(self): + """All burnable nuclide names. Used for sorting the simulation.""" + 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. @@ -150,9 +138,9 @@ class AtomNumber(object): """ if isinstance(mat, str): - mat = self.mat_to_ind[mat] + mat = self.index_mat[mat] if isinstance(nuc, str): - nuc = self.nuc_to_ind[nuc] + nuc = self.index_nuc[nuc] return self[mat, nuc] / self.volume[mat] @@ -170,9 +158,9 @@ class AtomNumber(object): """ if isinstance(mat, str): - mat = self.mat_to_ind[mat] + mat = self.index_mat[mat] if isinstance(nuc, str): - nuc = self.nuc_to_ind[nuc] + nuc = self.index_nuc[nuc] self[mat, nuc] = val * self.volume[mat] @@ -191,7 +179,7 @@ class AtomNumber(object): """ if isinstance(mat, str): - mat = self.mat_to_ind[mat] + mat = self.index_mat[mat] return self[mat, :self.n_nuc_burn] @@ -207,7 +195,7 @@ class AtomNumber(object): """ if isinstance(mat, str): - mat = self.mat_to_ind[mat] + mat = self.index_mat[mat] self[mat, :self.n_nuc_burn] = val @@ -223,5 +211,5 @@ class AtomNumber(object): Total atoms. """ - for i in range(self.n_mat): - self.set_mat_slice(i, total_density[i]) + for i, density_slice in enumerate(total_density): + self.set_mat_slice(i, density_slice) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 3c3b974c9..7d776825b 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -121,10 +121,10 @@ class OpenMCOperator(Operator): The depletion chain information necessary to form matrices and tallies. reaction_rates : openmc.deplete.ReactionRates Reaction rates from the last operator step. - burn_mat_to_ind : OrderedDict of str to int - Dictionary mapping material ID (as a string) to an index in reaction_rates. burnable_mats : list of str All burnable material IDs + local_mats : list of str + All burnable material IDs being managed by a single process """ def __init__(self, geometry, settings): @@ -136,8 +136,8 @@ class OpenMCOperator(Operator): # Clear out OpenMC, create task lists, distribute openmc.reset_auto_ids() - self.burnable_mats, volume, nuc_dict = self._get_burnable_mats() - local_mats = _distribute(self.burnable_mats) + 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() @@ -145,12 +145,12 @@ class OpenMCOperator(Operator): if nuc in self.chain] # Extract number densities from the geometry - self._extract_number(local_mats, volume, nuc_dict) + self._extract_number(self.local_mats, volume, nuclides) # Create reaction rates array index_rx = {rx: i for i, rx in enumerate(self.chain.reactions)} self.reaction_rates = ReactionRates( - self.burn_mat_to_ind, self._burnable_nucs, index_rx) + self.local_mats, self._burnable_nucs, index_rx) def __call__(self, vec, print_out=True): """Runs a simulation. @@ -207,7 +207,7 @@ class OpenMCOperator(Operator): List of burnable material IDs volume : OrderedDict of str to float Volume of each material in [cm^3] - nuc_dict : OrderedDict of str to int + nuclides : list of str Nuclides in order of how they'll appear in the simulation. """ @@ -231,16 +231,14 @@ class OpenMCOperator(Operator): model_nuclides = sorted(model_nuclides) # Construct a global nuclide dictionary, burned first - nuc_dict = copy.deepcopy(self.chain.nuclide_dict) - i = len(nuc_dict) + nuclides = list(self.chain.nuclide_dict) for nuc in model_nuclides: - if nuc not in nuc_dict: - nuc_dict[nuc] = i - i += 1 + if nuc not in nuclides: + nuclides.append(nuc) - return burnable_mats, volume, nuc_dict + return burnable_mats, volume, nuclides - def _extract_number(self, local_mats, volume, nuc_dict): + def _extract_number(self, local_mats, volume, nuclides): """Construct AtomNumber using geometry Parameters @@ -249,17 +247,11 @@ class OpenMCOperator(Operator): Material IDs to be managed by this process volume : OrderedDict of str to float Volumes for the above materials in [cm^3] - nuc_dict : OrderedDict of str to int + nuclides : list of str Nuclides to be used in the simulation. """ - # Same with materials - self.burn_mat_to_ind = OrderedDict() - for i, mat in enumerate(local_mats): - self.burn_mat_to_ind[mat] = i - - self.number = AtomNumber(self.burn_mat_to_ind, nuc_dict, volume, - len(self.chain)) + self.number = AtomNumber(local_mats, nuclides, volume, len(self.chain)) if self.settings.dilute_initial != 0.0: for nuc in self._burnable_nucs: @@ -268,7 +260,7 @@ class OpenMCOperator(Operator): # Now extract the number densities and store for mat in self.geometry.get_all_materials().values(): - if str(mat.id) in self.burn_mat_to_ind: + if str(mat.id) in local_mats: self._set_number_from_mat(mat) def _set_number_from_mat(self, mat): @@ -281,7 +273,6 @@ class OpenMCOperator(Operator): """ mat_id = str(mat.id) - mat_ind = self.number.mat_to_ind[mat_id] for nuclide, density in mat.get_nuclide_atom_densities().values(): number = density * 1.0e24 @@ -293,7 +284,7 @@ class OpenMCOperator(Operator): Parameters ---------- y : numpy.ndarray - An array representing reaction rates for this cell. + An array representing reaction rates for this material. mat : int Material id. @@ -340,10 +331,10 @@ class OpenMCOperator(Operator): for rank in range(comm.size): number_i = comm.bcast(self.number, root=rank) - for mat in number_i.mat_to_ind: + for mat in number_i.materials: nuclides = [] densities = [] - for nuc in number_i.nuc_to_ind: + for nuc in number_i.nuclides: if nuc in self.nuclides_with_data: val = 1.0e-24 * number_i.get_atom_density(mat, nuc) @@ -381,7 +372,7 @@ class OpenMCOperator(Operator): .values()) # Sort nuclides according to order in AtomNumber object - nuclides = list(self.number.nuc_to_ind.keys()) + nuclides = list(self.number.nuclides) for mat in materials: mat._nuclides.sort(key=lambda x: nuclides.index(x[0])) @@ -404,9 +395,9 @@ class OpenMCOperator(Operator): """ nuc_set = set() - # Create the set of all nuclides in the decay chain in cells marked for - # burning in which the number density is greater than zero. - for nuc in self.number.nuc_to_ind: + # 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) @@ -421,7 +412,7 @@ class OpenMCOperator(Operator): if comm.rank == 0: # Sort nuclides in the same order as self.number - nuc_list = [nuc for nuc in self.number.nuc_to_ind + nuc_list = [nuc for nuc in self.number.nuclides if nuc in nuc_set] else: nuc_list = None @@ -502,7 +493,7 @@ class OpenMCOperator(Operator): break # Extract results - for i, mat in enumerate(self.burn_mat_to_ind): + for i, mat in enumerate(self.local_mats): # Get tally index slab = materials.index(mat) @@ -583,7 +574,7 @@ class OpenMCOperator(Operator): return nuclides def get_results_info(self): - """Returns volume list, cell lists, and nuc lists. + """Returns volume list, material lists, and nuc lists. Returns ------- @@ -592,13 +583,13 @@ class OpenMCOperator(Operator): 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. + 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.burn_nuc_list - burn_list = list(self.burn_mat_to_ind) + nuc_list = self.number.burnable_nuclides + burn_list = self.local_mats volume = {} for i, mat in enumerate(burn_list): diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index f7daca5b2..e18051d9d 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -346,40 +346,6 @@ class Results(object): return results -def get_dict(number): - """Given an operator nested dictionary, output indexing dictionaries. - - These indexing dictionaries map mat IDs and nuclide names to indices - inside of Results.data. - - Parameters - ---------- - number : AtomNumber - The object to extract dictionaries from - - Returns - ------- - mat_to_ind : OrderedDict of str to int - Maps mat strings to index in array. - nuc_to_ind : OrderedDict of str to int - Maps nuclide strings to index in array. - """ - mat_to_ind = OrderedDict() - nuc_to_ind = OrderedDict() - - for nuc in number.nuc_to_ind: - nuc_ind = number.nuc_to_ind[nuc] - if nuc_ind < number.n_nuc_burn: - nuc_to_ind[nuc] = nuc_ind - - for mat in number.mat_to_ind: - mat_ind = number.mat_to_ind[mat] - if mat_ind < number.n_mat_burn: - mat_to_ind[mat] = mat_ind - - return mat_to_ind, nuc_to_ind - - def write_results(result, filename, index): """Outputs result to an .hdf5 file. diff --git a/tests/unit_tests/test_deplete_atom_number.py b/tests/unit_tests/test_deplete_atom_number.py index 887e9af1b..4cc9207ca 100644 --- a/tests/unit_tests/test_deplete_atom_number.py +++ b/tests/unit_tests/test_deplete_atom_number.py @@ -7,11 +7,11 @@ from openmc.deplete import atom_number def test_indexing(): """Tests the __getitem__ and __setitem__ routines simultaneously.""" - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + local_mats = ["10000", "10001"] + nuclides = ["U238", "U235", "U234"] volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) + number = atom_number.AtomNumber(local_mats, nuclides, volume, 2) number["10000", "U238"] = 1.0 number["10001", "U238"] = 2.0 @@ -36,47 +36,28 @@ def test_indexing(): assert number["10000", "U238"] == 5.0 -def test_n_mat(): - """Test number of materials property. """ - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} +def test_properties(): + """Test properties. """ + local_mats = ["10000", "10001"] + nuclides = ["U238", "U235", "Gd157"] volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) - - assert number.n_mat == 2 - - -def test_n_nuc(): - """Test number of nuclides property.""" - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - volume = {"10000" : 0.38, "10001" : 0.21} - - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) + number = atom_number.AtomNumber(local_mats, nuclides, volume, 2) + assert list(number.materials) == ["10000", "10001"] assert number.n_nuc == 3 - - -def test_burn_nuc_list(): - """Test the list of burned nuclides property""" - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - volume = {"10000" : 0.38, "10001" : 0.21} - - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) - - assert number.burn_nuc_list == ["U238", "U235"] + assert list(number.nuclides) == ["U238", "U235", "Gd157"] + assert number.burnable_nuclides == ["U238", "U235"] def test_density_indexing(): """Tests the get and set_atom_density routines simultaneously.""" - mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} - nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + local_mats = ["10000", "10001", "10002"] + nuclides = ["U238", "U235", "U234"] volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) + number = atom_number.AtomNumber(local_mats, nuclides, volume, 2) number.set_atom_density("10000", "U238", 1.0) number.set_atom_density("10001", "U238", 2.0) @@ -129,11 +110,11 @@ def test_density_indexing(): def test_get_mat_slice(): """Tests getting slices.""" - mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} - nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + local_mats = ["10000", "10001", "10002"] + nuclides = ["U238", "U235", "U234"] volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) + number = atom_number.AtomNumber(local_mats, nuclides, volume, 2) number.number = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]) @@ -149,11 +130,11 @@ def test_get_mat_slice(): def test_set_mat_slice(): """Tests getting slices.""" - mat_to_ind = {"10000" : 0, "10001" : 1, "10002" : 2} - nuc_to_ind = {"U238" : 0, "U235" : 1, "U234" : 2} + local_mats = ["10000", "10001", "10002"] + nuclides = ["U238", "U235", "U234"] volume = {"10000" : 0.38, "10001" : 0.21} - number = atom_number.AtomNumber(mat_to_ind, nuc_to_ind, volume, 2) + number = atom_number.AtomNumber(local_mats, nuclides, volume, 2) number.set_mat_slice(0, [1.0, 2.0]) From c19f396aa77b78efe0658ba4d4cc7356b1d8d780 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 16:49:51 -0600 Subject: [PATCH 246/282] Remove Operator.form_matrix methods (Chain owns these) --- openmc/deplete/abc.py | 22 ---------------------- openmc/deplete/openmc_wrapper.py | 18 ------------------ 2 files changed, 40 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index e8f65f887..691606681 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -133,27 +133,5 @@ class Operator(metaclass=ABCMeta): pass - @abstractmethod - def form_matrix(self, y, mat): - """Forms the f(y) matrix in y' = f(y)y. - - Nominally a depletion matrix, this is abstracted on the off chance - that the function f has nothing to do with depletion at all. - - Parameters - ---------- - y : numpy.ndarray - An array representing y. - mat : int - Material id. - - Returns - ------- - scipy.sparse.csr_matrix - Sparse matrix representing f(y). - """ - - pass - def finalize(self): pass diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 7d776825b..56a248c30 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -278,24 +278,6 @@ class OpenMCOperator(Operator): number = density * 1.0e24 self.number.set_atom_density(mat_id, nuclide, number) - def form_matrix(self, y, mat): - """Forms the depletion matrix. - - Parameters - ---------- - y : numpy.ndarray - An array representing reaction rates for this material. - mat : int - Material id. - - Returns - ------- - scipy.sparse.csr_matrix - Sparse matrix representing the depletion matrix. - """ - - return copy.deepcopy(self.chain.form_matrix(y[mat, :, :])) - def initial_condition(self): """Performs final setup and returns initial condition. From ccfee55115a10144a04e63eac2e744b7411d1c82 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 17:26:44 -0600 Subject: [PATCH 247/282] Make timesteps and power specified in integrator functions --- openmc/deplete/abc.py | 8 ------- openmc/deplete/integrator/cecm.py | 24 ++++++++++++++----- openmc/deplete/integrator/predictor.py | 26 +++++++++++++++------ openmc/deplete/openmc_wrapper.py | 23 +++++++++++------- tests/dummy_geometry.py | 4 +++- tests/regression_tests/test_deplete_full.py | 17 +++++++------- tests/unit_tests/test_deplete_cecm.py | 5 ++-- tests/unit_tests/test_deplete_predictor.py | 5 ++-- 8 files changed, 68 insertions(+), 44 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 691606681..7fda93aa2 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -18,8 +18,6 @@ class Settings(object): Attributes ---------- - dt_vec : numpy.array - Array of time steps to take. output_dir : pathlib.Path Path to output directory to save results. chain_file : str @@ -29,10 +27,6 @@ class Settings(object): 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. - power : float - Power of the reactor in [W]. 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. """ def __init__(self): @@ -40,9 +34,7 @@ class Settings(object): self.chain_file = os.environ["OPENMC_DEPLETE_CHAIN"] except KeyError: self.chain_file = None - self.dt_vec = None self.output_dir = '.' - self.power = None self.dilute_initial = 1.0e3 @property diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 760e3c89d..7d6c11190 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -1,12 +1,13 @@ """The CE/CM integrator.""" import copy +from collections.abc import Iterable from .cram import deplete from .save_results import save_results -def cecm(operator, print_out=True): +def cecm(operator, timesteps, power, print_out=True): r"""The CE/CM integrator. Implements the second order CE/CM Predictor-Corrector algorithm [ref]_. @@ -32,25 +33,36 @@ def cecm(operator, print_out=True): ---------- operator : openmc.deplete.Operator The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s] + 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 t = 0.0 - for i, dt in enumerate(operator.settings.dt_vec): + for i, (dt, p) in enumerate(zip(timesteps, power)): # Get beginning-of-timestep reaction rates x = [copy.deepcopy(vec)] - results = [operator(x[0])] + results = [operator(x[0], p)] # Deplete for first half of timestep x_middle = deplete(chain, x[0], results[0], dt/2, print_out) # Get middle-of-timestep reaction rates x.append(x_middle) - results.append(operator(x_middle)) + results.append(operator(x_middle, p)) # Deplete for full timestep using beginning-of-step materials x_end = deplete(chain, x[0], results[1], dt, print_out) @@ -64,7 +76,7 @@ def cecm(operator, print_out=True): # Perform one last simulation x = [copy.deepcopy(vec)] - results = [operator(x[0])] + results = [operator(x[0], power[-1])] # Create results, write to disk - save_results(operator, x, results, [t, t], len(operator.settings.dt_vec)) + save_results(operator, x, results, [t, t], len(timesteps)) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 064078331..038c0778d 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -1,13 +1,14 @@ -"""The Predictor algorithm.""" +"""First-order predictor algorithm.""" import copy +from collections.abc import Iterable from .cram import deplete from .save_results import save_results -def predictor(operator, print_out=True): - r"""The basic predictor integrator. +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: @@ -23,18 +24,29 @@ def predictor(operator, print_out=True): ---------- operator : openmc.deplete.Operator The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s] + 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 t = 0.0 - for i, dt in enumerate(operator.settings.dt_vec): + for i, (dt, p) in enumerate(zip(timesteps, power)): # Get beginning-of-timestep reaction rates x = [copy.deepcopy(vec)] - results = [operator(x[0])] + results = [operator(x[0], p)] # Create results, write to disk save_results(operator, x, results, [t, t + dt], i) @@ -48,7 +60,7 @@ def predictor(operator, print_out=True): # Perform one last simulation x = [copy.deepcopy(vec)] - results = [operator(x[0])] + results = [operator(x[0], power[-1])] # Create results, write to disk - save_results(operator, x, results, [t, t], len(operator.settings.dt_vec)) + save_results(operator, x, results, [t, t], len(timesteps)) diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 56a248c30..e5898997b 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -45,8 +45,6 @@ class OpenMCSettings(Settings): Attributes ---------- - dt_vec : numpy.array - Array of time steps to in units of [s] output_dir : pathlib.Path Path to output directory to save results. chain_file : str @@ -68,8 +66,8 @@ class OpenMCSettings(Settings): """ - _depletion_attrs = {'dt_vec', '_output_dir', 'chain_file', 'dilute_initial', - 'round_number', 'power'} + _depletion_attrs = {'_output_dir', 'chain_file', 'dilute_initial', + 'round_number'} def __init__(self): super().__init__() @@ -152,13 +150,15 @@ class OpenMCOperator(Operator): self.reaction_rates = ReactionRates( self.local_mats, self._burnable_nucs, index_rx) - def __call__(self, vec, print_out=True): + def __call__(self, vec, power, print_out=True): """Runs a simulation. Parameters ---------- vec : list of numpy.array 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. @@ -187,7 +187,7 @@ class OpenMCOperator(Operator): time_openmc = time.time() # Extract results - op_result = self._unpack_tallies_and_normalize() + op_result = self._unpack_tallies_and_normalize(power) if comm.rank == 0: time_unpack = time.time() @@ -424,7 +424,7 @@ class OpenMCOperator(Operator): self._tally.scores = self.chain.reactions self._tally.filters = [mat_filter] - def _unpack_tallies_and_normalize(self): + 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 @@ -432,6 +432,11 @@ class OpenMCOperator(Operator): 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 @@ -509,10 +514,10 @@ class OpenMCOperator(Operator): energy = comm.allreduce(energy) # Determine power in eV/s - power = self.settings.power / JOULE_PER_EV + power /= JOULE_PER_EV # Scale reaction rates to obtain units of reactions/sec - rates[:, :, :] *= power / energy + rates *= power / energy return OperatorResult(k_combined, rates) diff --git a/tests/dummy_geometry.py b/tests/dummy_geometry.py index c013bb005..d66261669 100644 --- a/tests/dummy_geometry.py +++ b/tests/dummy_geometry.py @@ -21,13 +21,15 @@ class DummyGeometry(Operator): def __init__(self, settings): super().__init__(settings) - def __call__(self, vec, print_out=False): + def __call__(self, vec, power, print_out=False): """Evaluates F(y) Parameters ---------- vec : list of numpy.array Total atoms to be used in function. + power : float + Power in [W] print_out : bool, optional, ignored Whether or not to print out time. diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index 6033e3f75..1ae2cee1f 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -32,18 +32,10 @@ def test_full(run_in_tmpdir): # Load geometry from example geometry, lower_left, upper_right = generate_problem(n_rings, n_wedges) - # Create dt vector for 3 steps with 15 day timesteps - dt1 = 15.*24*60*60 # 15 days - dt2 = 1.5*30*24*60*60 # 1.5 months - N = floor(dt2/dt1) - dt = np.full(N, dt1) - # Depletion settings settings = openmc.deplete.OpenMCSettings() settings.chain_file = str(Path(__file__).parents[2] / 'chains' / 'chain_simple.xml') - settings.power = 2.337e15*4*JOULE_PER_EV*1e6 # MeV/second cm from CASMO - settings.dt_vec = dt settings.round_number = True # Add OpenMC-specific settings @@ -57,8 +49,15 @@ def test_full(run_in_tmpdir): op = openmc.deplete.OpenMCOperator(geometry, settings) + # Power and timesteps + dt1 = 15.*24*60*60 # 15 days + dt2 = 1.5*30*24*60*60 # 1.5 months + N = floor(dt2/dt1) + dt = np.full(N, dt1) + power = 2.337e15*4*JOULE_PER_EV*1e6 # MeV/second cm from CASMO + # Perform simulation using the predictor algorithm - openmc.deplete.integrator.predictor(op) + openmc.deplete.integrator.predictor(op, dt, power) # Get path to test and reference results path_test = settings.output_dir / 'depletion_results.h5' diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py index 66c3ee156..3daa7a048 100644 --- a/tests/unit_tests/test_deplete_cecm.py +++ b/tests/unit_tests/test_deplete_cecm.py @@ -15,13 +15,14 @@ def test_cecm(run_in_tmpdir): """Integral regression test of integrator algorithm using CE/CM.""" settings = openmc.deplete.Settings() - settings.dt_vec = [0.75, 0.75] settings.output_dir = "test_integrator_regression" op = dummy_geometry.DummyGeometry(settings) # Perform simulation using the MCNPX/MCNP6 algorithm - openmc.deplete.cecm(op, print_out=False) + dt = [0.75, 0.75] + power = 1.0 + openmc.deplete.cecm(op, dt, power, print_out=False) # Load the files res = results.read_results(settings.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index f1133f87d..be8497f5f 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -15,13 +15,14 @@ def test_predictor(run_in_tmpdir): """Integral regression test of integrator algorithm using predictor/corrector""" settings = openmc.deplete.Settings() - settings.dt_vec = [0.75, 0.75] settings.output_dir = "test_integrator_regression" op = dummy_geometry.DummyGeometry(settings) # Perform simulation using the predictor algorithm - openmc.deplete.predictor(op, print_out=False) + dt = [0.75, 0.75] + power = 1.0 + openmc.deplete.predictor(op, dt, power, print_out=False) # Load the files res = results.read_results(settings.output_dir / "depletion_results.h5") From a460782691b86bf932c2acbcdba76ceb6c914659 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 17:52:17 -0600 Subject: [PATCH 248/282] Get rid of extra Settings class --- docs/source/pythonapi/deplete/index.rst | 2 - openmc/deplete/abc.py | 75 +++++++-------- openmc/deplete/chain.py | 14 +-- openmc/deplete/openmc_wrapper.py | 100 ++++++-------------- tests/dummy_geometry.py | 5 +- tests/regression_tests/test_deplete_full.py | 16 ++-- tests/unit_tests/test_deplete_cecm.py | 8 +- tests/unit_tests/test_deplete_predictor.py | 8 +- 8 files changed, 77 insertions(+), 151 deletions(-) diff --git a/docs/source/pythonapi/deplete/index.rst b/docs/source/pythonapi/deplete/index.rst index 30d2d4261..f3212bd61 100644 --- a/docs/source/pythonapi/deplete/index.rst +++ b/docs/source/pythonapi/deplete/index.rst @@ -29,7 +29,6 @@ Metaclasses :toctree: generated :nosignatures: - openmc.deplete.Settings openmc.deplete.Operator OpenMC Classes @@ -39,7 +38,6 @@ OpenMC Classes :toctree: generated :nosignatures: - openmc.deplete.OpenMCSettings openmc.deplete.Materials openmc.deplete.OpenMCOperator diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 7fda93aa2..9d53f40bf 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -7,44 +7,9 @@ to run a full depletion simulation. from collections import namedtuple import os from pathlib import Path - from abc import ABCMeta, abstractmethod - -class Settings(object): - """The Settings class. - - Contains all parameters necessary for the integrator. - - Attributes - ---------- - output_dir : pathlib.Path - Path to output directory to save results. - chain_file : str - Path to the depletion chain XML file. Defaults to the - :envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists. - 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): - try: - self.chain_file = os.environ["OPENMC_DEPLETE_CHAIN"] - except KeyError: - self.chain_file = None - self.output_dir = '.' - self.dilute_initial = 1.0e3 - - @property - def output_dir(self): - return self._output_dir - - @output_dir.setter - def output_dir(self, output_dir): - self._output_dir = Path(output_dir) - +from .chain import Chain OperatorResult = namedtuple('OperatorResult', ['k', 'rates']) @@ -52,14 +17,30 @@ OperatorResult = namedtuple('OperatorResult', ['k', 'rates']) class Operator(metaclass=ABCMeta): """Abstract class defining a transport operator + Parameters + ---------- + chain_file : str, optional + + Attributes ---------- - settings : Settings - 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. """ - def __init__(self, settings): - self.settings = settings + 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): @@ -83,11 +64,11 @@ class Operator(metaclass=ABCMeta): def __enter__(self): # Save current directory and move to specific output directory self._orig_dir = os.getcwd() - if not self.settings.output_dir.exists(): - self.settings.output_dir.mkdir() # exist_ok parameter is 3.5+ + 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.settings.output_dir)) + os.chdir(str(self.output_dir)) return self.initial_condition() @@ -95,6 +76,14 @@ class Operator(metaclass=ABCMeta): 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. diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index ec8d79f81..9ffc3d93e 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -328,15 +328,7 @@ class Chain(object): chain = cls() # Load XML tree - try: - root = ET.parse(filename) - except Exception: - if filename is None: - msg = ("No chain specified, either manually or in environment " - "variable OPENMC_DEPLETE_CHAIN.") - else: - msg = 'Decay chain "{}" is invalid.'.format(filename) - raise IOError(msg) + root = ET.parse(str(filename)) for i, nuclide_elem in enumerate(root.findall('nuclide_table')): nuc = Nuclide.from_xml(nuclide_elem) @@ -367,10 +359,10 @@ class Chain(object): tree = ET.ElementTree(root_elem) if _have_lxml: - tree.write(filename, encoding='utf-8', pretty_print=True) + tree.write(str(filename), encoding='utf-8', pretty_print=True) else: clean_xml_indentation(root_elem) - tree.write(filename, encoding='utf-8') + tree.write(str(filename), encoding='utf-8') def form_matrix(self, rates): """Forms depletion matrix. diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index e5898997b..12ed27f22 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -24,9 +24,8 @@ import openmc import openmc.capi from openmc.data import JOULE_PER_EV from . import comm -from .abc import Settings, Operator, OperatorResult +from .abc import Operator, OperatorResult from .atom_number import AtomNumber -from .chain import Chain from .reaction_rates import ReactionRates @@ -40,77 +39,34 @@ def _distribute(items): j += chunk_size -class OpenMCSettings(Settings): - """Extends Settings to provide information OpenMC needs to run. - - Attributes - ---------- - output_dir : pathlib.Path - Path to output directory to save results. - chain_file : str - Path to the depletion chain XML file. Defaults to the - :envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists. - 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. - power : float - Power of the reactor in [W]. 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. - 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. - settings : openmc.Settings - Settings for OpenMC simulations - - """ - - _depletion_attrs = {'_output_dir', 'chain_file', 'dilute_initial', - 'round_number'} - - def __init__(self): - super().__init__() - self.round_number = False - - # Avoid setattr to create OpenMC settings - self.__dict__['settings'] = openmc.Settings() - - def __setattr__(self, name, value): - if hasattr(self.__class__, name): - # Use properties when appropriate - prop = getattr(self.__class__, name) - prop.fset(self, value) - elif name in self._depletion_attrs: - # For known attributes, store in dictionary - self.__dict__[name] = value - else: - # otherwise, delegate to openmc.Settings - setattr(self.__dict__['settings'], name, value) - - def __getattr__(self, name): - if name in self._depletion_attrs: - return self.__dict__[name] - else: - return getattr(self.__dict__['settings'], name) - - class OpenMCOperator(Operator): """OpenMC transport operator Parameters ---------- geometry : openmc.Geometry - The OpenMC geometry object. - settings : openmc.deplete.OpenMCSettings - Settings object. + 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. Attributes ---------- - settings : OpenMCSettings - Settings object. (From Operator) geometry : openmc.Geometry - The OpenMC geometry object. + 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 @@ -125,13 +81,12 @@ class OpenMCOperator(Operator): All burnable material IDs being managed by a single process """ - def __init__(self, geometry, settings): - super().__init__(settings) + def __init__(self, geometry, settings, chain_file=None): + super().__init__(chain_file) + self.round_number = False + self.settings = settings self.geometry = geometry - # Read depletion chain - self.chain = Chain.from_xml(settings.chain_file) - # Clear out OpenMC, create task lists, distribute openmc.reset_auto_ids() self.burnable_mats, volume, nuclides = self._get_burnable_mats() @@ -253,10 +208,9 @@ class OpenMCOperator(Operator): """ self.number = AtomNumber(local_mats, nuclides, volume, len(self.chain)) - if self.settings.dilute_initial != 0.0: + if self.dilute_initial != 0.0: for nuc in self._burnable_nucs: - self.number.set_atom_density(np.s_[:], nuc, - self.settings.dilute_initial) + self.number.set_atom_density(np.s_[:], nuc, self.dilute_initial) # Now extract the number densities and store for mat in self.geometry.get_all_materials().values(): @@ -290,7 +244,7 @@ class OpenMCOperator(Operator): # Create XML files if comm.rank == 0: self.geometry.export_to_xml() - self.settings.settings.export_to_xml() + self.settings.export_to_xml() self._generate_materials_xml() # Initialize OpenMC library @@ -322,7 +276,7 @@ class OpenMCOperator(Operator): # If nuclide is zero, do not add to the problem. if val > 0.0: - if self.settings.round_number: + if self.round_number: val_magnitude = np.floor(np.log10(val)) val_scaled = val / 10**val_magnitude val_round = round(val_scaled, 8) diff --git a/tests/dummy_geometry.py b/tests/dummy_geometry.py index d66261669..ca2efd663 100644 --- a/tests/dummy_geometry.py +++ b/tests/dummy_geometry.py @@ -17,9 +17,8 @@ class DummyGeometry(Operator): y_2(1.5) ~ 3.1726475740397628 """ - - def __init__(self, settings): - super().__init__(settings) + def __init__(self): + pass def __call__(self, vec, power, print_out=False): """Evaluates F(y) diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index 1ae2cee1f..22125d039 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -32,13 +32,8 @@ def test_full(run_in_tmpdir): # Load geometry from example geometry, lower_left, upper_right = generate_problem(n_rings, n_wedges) - # Depletion settings - settings = openmc.deplete.OpenMCSettings() - settings.chain_file = str(Path(__file__).parents[2] / 'chains' / - 'chain_simple.xml') - settings.round_number = True - - # Add OpenMC-specific settings + # OpenMC-specific settings + settings = openmc.Settings() settings.particles = 100 settings.batches = 100 settings.inactive = 40 @@ -47,7 +42,10 @@ def test_full(run_in_tmpdir): settings.seed = 1 settings.verbosity = 3 - op = openmc.deplete.OpenMCOperator(geometry, settings) + # Create operator + chain_file = Path(__file__).parents[2] / 'chains' / 'chain_simple.xml' + op = openmc.deplete.OpenMCOperator(geometry, settings, chain_file) + op.round_number = True # Power and timesteps dt1 = 15.*24*60*60 # 15 days @@ -60,7 +58,7 @@ def test_full(run_in_tmpdir): openmc.deplete.integrator.predictor(op, dt, power) # Get path to test and reference results - path_test = settings.output_dir / 'depletion_results.h5' + path_test = op.output_dir / 'depletion_results.h5' path_reference = Path(__file__).with_name('test_reference.h5') # If updating results, do so and return diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py index 3daa7a048..97659b892 100644 --- a/tests/unit_tests/test_deplete_cecm.py +++ b/tests/unit_tests/test_deplete_cecm.py @@ -14,10 +14,8 @@ from tests import dummy_geometry def test_cecm(run_in_tmpdir): """Integral regression test of integrator algorithm using CE/CM.""" - settings = openmc.deplete.Settings() - settings.output_dir = "test_integrator_regression" - - op = dummy_geometry.DummyGeometry(settings) + op = dummy_geometry.DummyGeometry() + op.output_dir = "test_integrator_regression" # Perform simulation using the MCNPX/MCNP6 algorithm dt = [0.75, 0.75] @@ -25,7 +23,7 @@ def test_cecm(run_in_tmpdir): openmc.deplete.cecm(op, dt, power, print_out=False) # Load the files - res = results.read_results(settings.output_dir / "depletion_results.h5") + res = results.read_results(op.output_dir / "depletion_results.h5") _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index be8497f5f..42a3c13a3 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -14,10 +14,8 @@ from tests import dummy_geometry def test_predictor(run_in_tmpdir): """Integral regression test of integrator algorithm using predictor/corrector""" - settings = openmc.deplete.Settings() - settings.output_dir = "test_integrator_regression" - - op = dummy_geometry.DummyGeometry(settings) + op = dummy_geometry.DummyGeometry() + op.output_dir = "test_integrator_regression" # Perform simulation using the predictor algorithm dt = [0.75, 0.75] @@ -25,7 +23,7 @@ def test_predictor(run_in_tmpdir): openmc.deplete.predictor(op, dt, power, print_out=False) # Load the files - res = results.read_results(settings.output_dir / "depletion_results.h5") + res = results.read_results(op.output_dir / "depletion_results.h5") _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") From a9df0465f0d7344d7156473657a575badf354c77 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 22:51:53 -0600 Subject: [PATCH 249/282] Start cleaning up documentation --- docs/source/conf.py | 14 +++-- docs/source/pythonapi/deplete.rst | 62 +++++++++++++++++++ docs/source/pythonapi/deplete/index.rst | 54 ---------------- .../pythonapi/deplete/integrator.CRAM16.rst | 6 -- .../pythonapi/deplete/integrator.CRAM48.rst | 6 -- .../pythonapi/deplete/integrator.cecm.rst | 6 -- .../deplete/integrator.predictor.rst | 6 -- .../deplete/integrator.save_results.rst | 6 -- docs/source/pythonapi/index.rst | 6 +- openmc/deplete/abc.py | 22 ++++++- openmc/deplete/chain.py | 3 - openmc/deplete/integrator/cecm.py | 16 ++--- openmc/deplete/integrator/cram.py | 28 +++------ openmc/deplete/integrator/predictor.py | 4 +- openmc/deplete/integrator/save_results.py | 2 +- openmc/deplete/openmc_wrapper.py | 14 +++-- .../{dummy_geometry.py => dummy_operator.py} | 6 +- tests/regression_tests/test_deplete_full.py | 2 +- .../test_deplete_utilities.py | 2 +- tests/unit_tests/test_deplete_cecm.py | 4 +- tests/unit_tests/test_deplete_predictor.py | 4 +- 21 files changed, 131 insertions(+), 142 deletions(-) create mode 100644 docs/source/pythonapi/deplete.rst delete mode 100644 docs/source/pythonapi/deplete/index.rst delete mode 100644 docs/source/pythonapi/deplete/integrator.CRAM16.rst delete mode 100644 docs/source/pythonapi/deplete/integrator.CRAM48.rst delete mode 100644 docs/source/pythonapi/deplete/integrator.cecm.rst delete mode 100644 docs/source/pythonapi/deplete/integrator.predictor.rst delete mode 100644 docs/source/pythonapi/deplete/integrator.save_results.rst rename tests/{dummy_geometry.py => dummy_operator.py} (95%) diff --git a/docs/source/conf.py b/docs/source/conf.py index db44e34f4..b25d5abbe 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -21,12 +21,14 @@ 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', 'tqdm', 'openmoc', + 'openmc.data.reconstruct' +] sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES) import numpy as np diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst new file mode 100644 index 000000000..13f2fd155 --- /dev/null +++ b/docs/source/pythonapi/deplete.rst @@ -0,0 +1,62 @@ +.. _pythonapi_deplete: + +---------------------------------- +:mod:`openmc.deplete` -- Depletion +---------------------------------- + +Integrators +----------- + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myfunction.rst + + openmc.deplete.integrator.predictor + openmc.deplete.integrator.cecm + +Integrator Helper Functions +--------------------------- + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myfunction.rst + + openmc.deplete.integrator.CRAM16 + openmc.deplete.integrator.CRAM48 + openmc.deplete.integrator.save_results + +Metaclasses +----------- + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myclass.rst + + openmc.deplete.TransportOperator + +OpenMC Classes +-------------- + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myclass.rst + + openmc.deplete.Operator + openmc.deplete.OperatorResult + +Data Classes +------------ +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myclass.rst + + openmc.deplete.AtomNumber + openmc.deplete.Chain + openmc.deplete.Nuclide + openmc.deplete.ReactionRates + openmc.deplete.Results diff --git a/docs/source/pythonapi/deplete/index.rst b/docs/source/pythonapi/deplete/index.rst deleted file mode 100644 index f3212bd61..000000000 --- a/docs/source/pythonapi/deplete/index.rst +++ /dev/null @@ -1,54 +0,0 @@ -.. _api: - -================= -API Documentation -================= - -Integrators ------------ - -.. toctree:: - :maxdepth: 2 - - integrator.predictor - integrator.cecm - -Integrator Helper Functions ---------------------------- -.. toctree:: - :maxdepth: 2 - - integrator.CRAM16 - integrator.CRAM48 - integrator.save_results - -Metaclasses ------------ - -.. autosummary:: - :toctree: generated - :nosignatures: - - openmc.deplete.Operator - -OpenMC Classes --------------- - -.. autosummary:: - :toctree: generated - :nosignatures: - - openmc.deplete.Materials - openmc.deplete.OpenMCOperator - -Data Classes ------------- -.. autosummary:: - :toctree: generated - :nosignatures: - - openmc.deplete.AtomNumber - openmc.deplete.Chain - openmc.deplete.Nuclide - openmc.deplete.ReactionRates - openmc.deplete.Results diff --git a/docs/source/pythonapi/deplete/integrator.CRAM16.rst b/docs/source/pythonapi/deplete/integrator.CRAM16.rst deleted file mode 100644 index a0dc64805..000000000 --- a/docs/source/pythonapi/deplete/integrator.CRAM16.rst +++ /dev/null @@ -1,6 +0,0 @@ -integrator\.CRAM16 -================== - -.. currentmodule:: openmc.deplete.integrator - -.. autofunction:: CRAM16 diff --git a/docs/source/pythonapi/deplete/integrator.CRAM48.rst b/docs/source/pythonapi/deplete/integrator.CRAM48.rst deleted file mode 100644 index f9720f7ad..000000000 --- a/docs/source/pythonapi/deplete/integrator.CRAM48.rst +++ /dev/null @@ -1,6 +0,0 @@ -integrator\.CRAM48 -================== - -.. currentmodule:: openmc.deplete.integrator - -.. autofunction:: CRAM48 diff --git a/docs/source/pythonapi/deplete/integrator.cecm.rst b/docs/source/pythonapi/deplete/integrator.cecm.rst deleted file mode 100644 index 4851b20b3..000000000 --- a/docs/source/pythonapi/deplete/integrator.cecm.rst +++ /dev/null @@ -1,6 +0,0 @@ -integrator\.cecm -================= - -.. currentmodule:: openmc.deplete.integrator - -.. autofunction:: cecm diff --git a/docs/source/pythonapi/deplete/integrator.predictor.rst b/docs/source/pythonapi/deplete/integrator.predictor.rst deleted file mode 100644 index 2243e77f7..000000000 --- a/docs/source/pythonapi/deplete/integrator.predictor.rst +++ /dev/null @@ -1,6 +0,0 @@ -integrator\.predictor -===================== - -.. currentmodule:: openmc.deplete.integrator - -.. autofunction:: predictor diff --git a/docs/source/pythonapi/deplete/integrator.save_results.rst b/docs/source/pythonapi/deplete/integrator.save_results.rst deleted file mode 100644 index f9c830cd5..000000000 --- a/docs/source/pythonapi/deplete/integrator.save_results.rst +++ /dev/null @@ -1,6 +0,0 @@ -integrator\.save_results -======================== - -.. currentmodule:: openmc.deplete.integrator - -.. autofunction:: save_results diff --git a/docs/source/pythonapi/index.rst b/docs/source/pythonapi/index.rst index 862acb238..3c28a2185 100644 --- a/docs/source/pythonapi/index.rst +++ b/docs/source/pythonapi/index.rst @@ -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 diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 9d53f40bf..53e71c195 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -12,15 +12,33 @@ 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 + +""" -class Operator(metaclass=ABCMeta): +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 ---------- diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 9ffc3d93e..348103ea9 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -321,9 +321,6 @@ class Chain(object): filename : str The path to the depletion chain XML file. - Todo - ---- - Allow for branching on capture, etc. """ chain = cls() diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 7d6c11190..db58d5d52 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -8,10 +8,11 @@ from .save_results import save_results def cecm(operator, timesteps, power, print_out=True): - r"""The CE/CM integrator. + r"""Deplete using the CE/CM algorithm. - Implements the second order CE/CM Predictor-Corrector algorithm [ref]_. - This algorithm is mathematically defined as: + Implements the second order `CE/CM Predictor-Corrector algorithm + `_. This algorithm is mathematically + defined as: .. math:: y' &= A(y, t) y(t) @@ -24,17 +25,12 @@ def cecm(operator, timesteps, power, print_out=True): y_{n+1} &= \text{expm}(A_c h) y_n - .. [ref] - Isotalo, Aarno. "Comparison of Neutronics-Depletion Coupling Schemes - for Burnup Calculations-Continued Study." Nuclear Science and - Engineering 180.3 (2015): 286-300. - Parameters ---------- - operator : openmc.deplete.Operator + operator : openmc.deplete.TransportOperator The operator object to simulate on. timesteps : iterable of float - Array of timesteps in units of [s] + 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 diff --git a/openmc/deplete/integrator/cram.py b/openmc/deplete/integrator/cram.py index 09207fbc6..85954603a 100644 --- a/openmc/deplete/integrator/cram.py +++ b/openmc/deplete/integrator/cram.py @@ -48,7 +48,7 @@ def deplete(chain, x, op_result, dt, print_out): # Use multiprocessing pool to distribute work with Pool() as pool: iters = zip(chains, vecs, rates, dts) - x_result = list(pool.starmap(cram_wrapper, iters)) + x_result = list(pool.starmap(_cram_wrapper, iters)) t_end = time.time() if comm.rank == 0: @@ -58,7 +58,7 @@ def deplete(chain, x, op_result, dt, print_out): return x_result -def cram_wrapper(chain, n0, rates, dt): +def _cram_wrapper(chain, n0, rates, dt): """Wraps depletion matrix creation / CRAM solve for multiprocess execution Parameters @@ -82,16 +82,11 @@ def cram_wrapper(chain, n0, rates, dt): def CRAM16(A, n0, dt): - """ Chebyshev Rational Approximation Method, order 16 + """Chebyshev Rational Approximation Method, order 16 Algorithm is the 16th order Chebyshev Rational Approximation Method, - implemented in the more stable incomplete partial fraction (IPF) form - [cram16]_. - - .. [cram16] - Pusa, Maria. "Higher-Order Chebyshev Rational Approximation Method and - Application to Burnup Equations." Nuclear Science and Engineering 182.3 - (2016). + implemented in the more stable `incomplete partial fraction (IPF) + `_ form. Parameters ---------- @@ -106,6 +101,7 @@ def CRAM16(A, n0, dt): ------- numpy.array Results of the matrix exponent. + """ alpha = np.array([+2.124853710495224e-16, @@ -144,16 +140,11 @@ def CRAM16(A, n0, dt): def CRAM48(A, n0, dt): - """ Chebyshev Rational Approximation Method, order 48 + """Chebyshev Rational Approximation Method, order 48 Algorithm is the 48th order Chebyshev Rational Approximation Method, - implemented in the more stable incomplete partial fraction (IPF) form - [cram48]_. - - .. [cram48] - Pusa, Maria. "Higher-Order Chebyshev Rational Approximation Method and - Application to Burnup Equations." Nuclear Science and Engineering 182.3 - (2016). + implemented in the more stable `incomplete partial fraction (IPF) + `_ form. Parameters ---------- @@ -168,6 +159,7 @@ def CRAM48(A, n0, dt): ------- numpy.array Results of the matrix exponent. + """ theta_r = np.array([-4.465731934165702e+1, -5.284616241568964e+0, diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 038c0778d..444ca7baa 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -22,10 +22,10 @@ def predictor(operator, timesteps, power, print_out=True): Parameters ---------- - operator : openmc.deplete.Operator + operator : openmc.deplete.TransportOperator The operator object to simulate on. timesteps : iterable of float - Array of timesteps in units of [s] + 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 diff --git a/openmc/deplete/integrator/save_results.py b/openmc/deplete/integrator/save_results.py index 8a0ae9204..40d2f70b6 100644 --- a/openmc/deplete/integrator/save_results.py +++ b/openmc/deplete/integrator/save_results.py @@ -9,7 +9,7 @@ def save_results(op, x, op_results, t, step_ind): Parameters ---------- - op : openmc.deplete.Operator + 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. diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/openmc_wrapper.py index 12ed27f22..5104ab2f1 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/openmc_wrapper.py @@ -1,6 +1,10 @@ -"""The OpenMC wrapper module. +"""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. -This module implements the depletion -> OpenMC linkage. """ import copy @@ -24,7 +28,7 @@ import openmc import openmc.capi from openmc.data import JOULE_PER_EV from . import comm -from .abc import Operator, OperatorResult +from .abc import TransportOperator, OperatorResult from .atom_number import AtomNumber from .reaction_rates import ReactionRates @@ -39,8 +43,8 @@ def _distribute(items): j += chunk_size -class OpenMCOperator(Operator): - """OpenMC transport operator +class Operator(TransportOperator): + """OpenMC transport operator for depletion Parameters ---------- diff --git a/tests/dummy_geometry.py b/tests/dummy_operator.py similarity index 95% rename from tests/dummy_geometry.py rename to tests/dummy_operator.py index ca2efd663..706793d93 100644 --- a/tests/dummy_geometry.py +++ b/tests/dummy_operator.py @@ -1,11 +1,11 @@ import numpy as np import scipy.sparse as sp from openmc.deplete.reaction_rates import ReactionRates -from openmc.deplete.abc import Operator, OperatorResult +from openmc.deplete.abc import TransportOperator, OperatorResult -class DummyGeometry(Operator): - """This is a dummy geometry class with no statistical uncertainty. +class DummyOperator(TransportOperator): + """This is a dummy operator class with no statistical uncertainty. y_1' = sin(y_2) y_1 + cos(y_1) y_2 y_2' = -cos(y_2) y_1 + sin(y_1) y_2 diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index 22125d039..2286af908 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -44,7 +44,7 @@ def test_full(run_in_tmpdir): # Create operator chain_file = Path(__file__).parents[2] / 'chains' / 'chain_simple.xml' - op = openmc.deplete.OpenMCOperator(geometry, settings, chain_file) + op = openmc.deplete.Operator(geometry, settings, chain_file) op.round_number = True # Power and timesteps diff --git a/tests/regression_tests/test_deplete_utilities.py b/tests/regression_tests/test_deplete_utilities.py index f38129cc7..82d4d56a4 100644 --- a/tests/regression_tests/test_deplete_utilities.py +++ b/tests/regression_tests/test_deplete_utilities.py @@ -14,7 +14,7 @@ from openmc.deplete import utilities @pytest.fixture def res(): """Load the reference results""" - filename = str(Path(__file__).with_name('test_reference.h5')) + filename = Path(__file__).with_name('test_reference.h5') return results.read_results(filename) diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py index 97659b892..6deb6cd3d 100644 --- a/tests/unit_tests/test_deplete_cecm.py +++ b/tests/unit_tests/test_deplete_cecm.py @@ -8,13 +8,13 @@ import openmc.deplete from openmc.deplete import results from openmc.deplete import utilities -from tests import dummy_geometry +from tests import dummy_operator def test_cecm(run_in_tmpdir): """Integral regression test of integrator algorithm using CE/CM.""" - op = dummy_geometry.DummyGeometry() + op = dummy_operator.DummyOperator() op.output_dir = "test_integrator_regression" # Perform simulation using the MCNPX/MCNP6 algorithm diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index 42a3c13a3..0d283855c 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -8,13 +8,13 @@ import openmc.deplete from openmc.deplete import results from openmc.deplete import utilities -from tests import dummy_geometry +from tests import dummy_operator def test_predictor(run_in_tmpdir): """Integral regression test of integrator algorithm using predictor/corrector""" - op = dummy_geometry.DummyGeometry() + op = dummy_operator.DummyOperator() op.output_dir = "test_integrator_regression" # Perform simulation using the predictor algorithm From c9f8daa0aba77274bdd7cf29c8c3a6ecb66970f6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 23:17:50 -0600 Subject: [PATCH 250/282] Fix in ReactionRates class. More documentation updates --- openmc/deplete/__init__.py | 2 +- openmc/deplete/abc.py | 4 +- openmc/deplete/atom_number.py | 8 ++-- .../{openmc_wrapper.py => operator.py} | 26 ++++++++----- openmc/deplete/reaction_rates.py | 31 +++++++++++---- openmc/deplete/results.py | 2 +- openmc/deplete/utilities.py | 1 + tests/unit_tests/test_deplete_reaction.py | 38 +++++-------------- 8 files changed, 57 insertions(+), 55 deletions(-) rename openmc/deplete/{openmc_wrapper.py => operator.py} (98%) diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index 2467b973a..33b6d9af1 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -16,7 +16,7 @@ except ImportError: from .nuclide import * from .chain import * -from .openmc_wrapper import * +from .operator import * from .reaction_rates import * from .abc import * from .results import * diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 53e71c195..f2fa73650 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -66,7 +66,7 @@ class TransportOperator(metaclass=ABCMeta): Parameters ---------- - vec : list of numpy.array + vec : list of numpy.ndarray Total atoms to be used in function. print_out : bool, optional Whether or not to print out time. @@ -108,7 +108,7 @@ class TransportOperator(metaclass=ABCMeta): Returns ------- - list of numpy.array + list of numpy.ndarray Total density for initial conditions. """ diff --git a/openmc/deplete/atom_number.py b/openmc/deplete/atom_number.py index 1a142676c..5179224c2 100644 --- a/openmc/deplete/atom_number.py +++ b/openmc/deplete/atom_number.py @@ -55,7 +55,7 @@ class AtomNumber(object): self.n_nuc_burn = n_nuc_burn - self.number = np.zeros((len(local_mats), self.n_nuc)) + self.number = np.zeros((len(local_mats), len(nuclides))) def __getitem__(self, pos): """Retrieves total atom number from AtomNumber. @@ -69,7 +69,7 @@ class AtomNumber(object): Returns ------- - numpy.array + numpy.ndarray The value indexed from self.number. """ @@ -153,7 +153,7 @@ class AtomNumber(object): Material index. nuc : str, int or slice Nuclide index. - val : numpy.array + val : numpy.ndarray Array of densities to set in [atom/cm^3] """ @@ -190,7 +190,7 @@ class AtomNumber(object): ---------- mat : str, int or slice Material index. - val : numpy.array + val : numpy.ndarray The slice to set in [atom] """ diff --git a/openmc/deplete/openmc_wrapper.py b/openmc/deplete/operator.py similarity index 98% rename from openmc/deplete/openmc_wrapper.py rename to openmc/deplete/operator.py index 5104ab2f1..af08727ea 100644 --- a/openmc/deplete/openmc_wrapper.py +++ b/openmc/deplete/operator.py @@ -11,15 +11,8 @@ import copy from collections import OrderedDict from itertools import chain import os -import random -import sys import time -try: - import lxml.etree as ET - _have_lxml = True -except ImportError: - import xml.etree.ElementTree as ET - _have_lxml = False +import xml.etree.ElementTree as ET import h5py import numpy as np @@ -34,6 +27,19 @@ 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): @@ -114,7 +120,7 @@ class Operator(TransportOperator): Parameters ---------- - vec : list of numpy.array + vec : list of numpy.ndarray Total atoms to be used in function. power : float Power of the reactor in [W] @@ -241,7 +247,7 @@ class Operator(TransportOperator): Returns ------- - list of numpy.array + list of numpy.ndarray Total density for initial conditions. """ diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py index d1e1b0f1e..c66003530 100644 --- a/openmc/deplete/reaction_rates.py +++ b/openmc/deplete/reaction_rates.py @@ -7,14 +7,16 @@ import numpy as np class ReactionRates(np.ndarray): - """ReactionRates class. + """Reaction rates resulting from a transport operator call - An ndarray to store reaction rates with string, integer, or slice indexing. + 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 ---------- - index_mat : OrderedDict of str to int - A dictionary mapping material ID as string to index. + local_mats : list of str + Material IDs nuclides : list of str Depletable nuclides index_rx : OrderedDict of str to int @@ -36,14 +38,22 @@ class ReactionRates(np.ndarray): Number of reactions. """ - def __new__(cls, index_mat, nuclides, index_rx): + + # 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, index_rx): # Create appropriately-sized zeroed-out ndarray - shape = (len(index_mat), len(nuclides), len(index_rx)) + shape = (len(local_mats), len(nuclides), len(index_rx)) obj = super().__new__(cls, shape) obj[:] = 0.0 # Add mapping attributes - obj.index_mat = index_mat + 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 = index_rx @@ -56,6 +66,11 @@ class ReactionRates(np.ndarray): 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) @@ -69,7 +84,7 @@ class ReactionRates(np.ndarray): @property def n_mat(self): - """Number of cells.""" + """Number of materials.""" return len(self.index_mat) @property diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index e18051d9d..177158dbe 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -42,7 +42,7 @@ class Results(object): Number of materials in entire geometry. n_stages : int Number of stages in simulation. - data : numpy.array + data : numpy.ndarray Atom quantity, stored by stage, mat, then by nuclide. """ diff --git a/openmc/deplete/utilities.py b/openmc/deplete/utilities.py index d155f321d..59d530546 100644 --- a/openmc/deplete/utilities.py +++ b/openmc/deplete/utilities.py @@ -38,6 +38,7 @@ def evaluate_single_nuclide(results, mat, nuc): return time, concentration + def evaluate_reaction_rate(results, mat, nuc, rx): """Return reaction rate in a single material/nuclide from a results list. diff --git a/tests/unit_tests/test_deplete_reaction.py b/tests/unit_tests/test_deplete_reaction.py index de628f8c6..e46a7b13d 100644 --- a/tests/unit_tests/test_deplete_reaction.py +++ b/tests/unit_tests/test_deplete_reaction.py @@ -7,11 +7,11 @@ from openmc.deplete import ReactionRates def test_get_set(): """Tests the get/set methods.""" - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1} - react_to_ind = {"fission" : 0, "(n,gamma)" : 1} + local_mats = ["10000", "10001"] + nuclides = ["U238", "U235"] + react_to_ind = {"fission": 0, "(n,gamma)": 1} - rates = ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + rates = ReactionRates(local_mats, nuclides, react_to_ind) assert rates.shape == (2, 2, 2) assert np.all(rates == 0.0) @@ -50,34 +50,14 @@ def test_get_set(): assert rates.get("10000", "U238", "fission") == 5.0 -def test_n_mat(): +def test_properties(): """Test number of materials property.""" - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} + local_mats = ["10000", "10001"] + nuclides = ["U238", "U235", "Gd157"] + react_to_ind = {"fission": 0, "(n,gamma)": 1, "(n,2n)": 2, "(n,3n)": 3} - rates = ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) + rates = ReactionRates(local_mats, nuclides, react_to_ind) assert rates.n_mat == 2 - - -def test_n_nuc(): - """Test number of nuclides property.""" - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} - - rates = ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) - assert rates.n_nuc == 3 - - -def test_n_react(): - """ Test number of reactions property. """ - mat_to_ind = {"10000" : 0, "10001" : 1} - nuc_to_ind = {"U238" : 0, "U235" : 1, "Gd157" : 2} - react_to_ind = {"fission" : 0, "(n,gamma)" : 1, "(n,2n)" : 2, "(n,3n)" : 3} - - rates = ReactionRates(mat_to_ind, nuc_to_ind, react_to_ind) - assert rates.n_react == 4 From 236e7f8b9e626347eff4395508c0b95efe97493a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 20 Feb 2018 00:05:53 -0600 Subject: [PATCH 251/282] Add format spec for depletion chain XML file --- chains/chain_simple.xml | 54 +++++------ chains/chain_test.xml | 32 +++---- docs/source/io_formats/depletion_chain.rst | 102 +++++++++++++++++++++ docs/source/io_formats/index.rst | 1 + openmc/deplete/chain.py | 4 +- openmc/deplete/nuclide.py | 14 +-- tests/unit_tests/test_deplete_nuclide.py | 22 ++--- 7 files changed, 166 insertions(+), 63 deletions(-) create mode 100644 docs/source/io_formats/depletion_chain.rst diff --git a/chains/chain_simple.xml b/chains/chain_simple.xml index 345da2237..c2e50a370 100644 --- a/chains/chain_simple.xml +++ b/chains/chain_simple.xml @@ -1,23 +1,23 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + 2.53000e-02 @@ -25,9 +25,9 @@ 1.093250e-04 2.087260e-04 2.780820e-02 6.759540e-03 2.392300e-02 4.356330e-05 - - - + + + 2.53000e-02 @@ -35,9 +35,9 @@ 6.142710e-5 1.483250e-04 0.0292737 0.002566345 0.0219242 4.9097e-6 - - - + + + 2.53000e-02 @@ -45,5 +45,5 @@ 4.141120e-04 7.605360e-04 0.0135457 0.00026864 0.0024432 3.7100E-07 - - + + diff --git a/chains/chain_test.xml b/chains/chain_test.xml index 598570406..c8c75ad7b 100644 --- a/chains/chain_test.xml +++ b/chains/chain_test.xml @@ -1,17 +1,17 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + 0.0253 @@ -19,5 +19,5 @@ 0.0292737 0.002566345 - - + + diff --git a/docs/source/io_formats/depletion_chain.rst b/docs/source/io_formats/depletion_chain.rst new file mode 100644 index 000000000..00d95bdf5 --- /dev/null +++ b/docs/source/io_formats/depletion_chain.rst @@ -0,0 +1,102 @@ +.. _io_chain: + +============================ +Depletion Chain -- chain.xml +============================ + +A depletion chain file has a ```` root element with one or more +```` child elements. The decay, reaction, and fission product data for +each nuclide appears as child elements of ````. + +--------------------- +```` Element +--------------------- + +The ```` 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 +````. For each reaction present, a :ref:`io_chain_reaction` appears as +a child of ````. If the nuclide is fissionable, a :ref:`io_chain_nfy` +appears as well. + +.. _io_chain_decay: + +------------------- +```` Element +------------------- + +The ```` 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: + +---------------------- +```` Element +---------------------- + +The ```` 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: + +------------------------------------ +```` Element +------------------------------------ + +The ```` 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 diff --git a/docs/source/io_formats/index.rst b/docs/source/io_formats/index.rst index f3eea21de..106897333 100644 --- a/docs/source/io_formats/index.rst +++ b/docs/source/io_formats/index.rst @@ -30,6 +30,7 @@ Data Files :maxdepth: 2 cross_sections + depletion_chain nuclear_data mgxs_library data_wmp diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 348103ea9..0cd3fb0d7 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -327,7 +327,7 @@ class Chain(object): # Load XML tree root = ET.parse(str(filename)) - for i, nuclide_elem in enumerate(root.findall('nuclide_table')): + for i, nuclide_elem in enumerate(root.findall('nuclide')): nuc = Nuclide.from_xml(nuclide_elem) chain.nuclide_dict[nuc.name] = i @@ -350,7 +350,7 @@ class Chain(object): """ - root_elem = ET.Element('depletion') + root_elem = ET.Element('depletion_chain') for nuclide in self.nuclides: root_elem.append(nuclide.to_xml_element()) diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 17cf4d9b8..4a7b86f02 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -23,9 +23,9 @@ class Nuclide(object): name : str Name of nuclide. half_life : float - Half life of nuclide in s^-1. + Half life of nuclide in [s]. decay_energy : float - Energy deposited from decay in eV. + Energy deposited from decay in [eV]. n_decay_modes : int Number of decay pathways. decay_modes : list of DecayTuple @@ -94,14 +94,14 @@ class Nuclide(object): nuc.decay_energy = float(element.get('decay_energy', '0')) # Check for decay paths - for decay_elem in element.iter('decay_type'): + 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_type'): + 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')) @@ -138,7 +138,7 @@ class Nuclide(object): XML element to write nuclide data to """ - elem = ET.Element('nuclide_table') + elem = ET.Element('nuclide') elem.set('name', self.name) if self.half_life is not None: @@ -146,14 +146,14 @@ class Nuclide(object): 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_type') + 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_type') + rx_elem = ET.SubElement(elem, 'reaction') rx_elem.set('type', rx) rx_elem.set('Q', str(Q)) if rx != 'fission': diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index 2add13f86..f2a101d2a 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -37,14 +37,14 @@ def test_from_xml(): """Test reading nuclide data from an XML element.""" data = """ - - - - - - - - + + + + + + + + 0.0253 @@ -52,7 +52,7 @@ def test_from_xml(): 0.062155 0.0497641 0.0481413 - + """ element = ET.fromstring(data) @@ -96,7 +96,7 @@ def test_to_xml_element(): assert element.get("half_life") == "0.123" - decay_elems = element.findall("decay_type") + decay_elems = element.findall("decay") assert len(decay_elems) == 2 assert decay_elems[0].get("type") == "beta-" assert decay_elems[0].get("target") == "B" @@ -105,7 +105,7 @@ def test_to_xml_element(): assert decay_elems[1].get("target") == "D" assert decay_elems[1].get("branching_ratio") == "0.01" - rx_elems = element.findall("reaction_type") + rx_elems = element.findall("reaction") assert len(rx_elems) == 2 assert rx_elems[0].get("type") == "fission" assert float(rx_elems[0].get("Q")) == 2.0e8 From f3758e5330490e4f90df99938bb76efd2e9cebb6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 20 Feb 2018 07:41:50 -0600 Subject: [PATCH 252/282] Fix broken tests on Py3.4, 3.5 --- openmc/deplete/atom_number.py | 5 +++-- openmc/deplete/integrator/save_results.py | 4 ++-- openmc/deplete/operator.py | 3 +-- openmc/deplete/reaction_rates.py | 11 ++++++----- openmc/deplete/results.py | 21 ++++++++------------- tests/unit_tests/test_deplete_chain.py | 7 +++---- tests/unit_tests/test_deplete_integrator.py | 21 +++++++++------------ tests/unit_tests/test_deplete_reaction.py | 8 ++++---- 8 files changed, 36 insertions(+), 44 deletions(-) diff --git a/openmc/deplete/atom_number.py b/openmc/deplete/atom_number.py index 5179224c2..8f6a41911 100644 --- a/openmc/deplete/atom_number.py +++ b/openmc/deplete/atom_number.py @@ -2,6 +2,7 @@ An ndarray to store atom densities with string, integer, or slice indexing. """ +from collections import OrderedDict import numpy as np @@ -44,8 +45,8 @@ class AtomNumber(object): """ def __init__(self, local_mats, nuclides, volume, n_nuc_burn): - self.index_mat = {mat: i for i, mat in enumerate(local_mats)} - self.index_nuc = {nuc: i for i, nuc in enumerate(nuclides)} + 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(): diff --git a/openmc/deplete/integrator/save_results.py b/openmc/deplete/integrator/save_results.py index 40d2f70b6..98245fdea 100644 --- a/openmc/deplete/integrator/save_results.py +++ b/openmc/deplete/integrator/save_results.py @@ -22,12 +22,12 @@ def save_results(op, x, op_results, t, step_ind): """ # Get indexing terms - vol_list, nuc_list, burn_list, full_burn_list = op.get_results_info() + vol_dict, nuc_list, burn_list, full_burn_list = op.get_results_info() # Create results stages = len(x) results = Results() - results.allocate(vol_list, nuc_list, burn_list, full_burn_list, stages) + results.allocate(vol_dict, nuc_list, burn_list, full_burn_list, stages) n_mat = len(burn_list) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index af08727ea..d0a64a06d 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -111,9 +111,8 @@ class Operator(TransportOperator): self._extract_number(self.local_mats, volume, nuclides) # Create reaction rates array - index_rx = {rx: i for i, rx in enumerate(self.chain.reactions)} self.reaction_rates = ReactionRates( - self.local_mats, self._burnable_nucs, index_rx) + self.local_mats, self._burnable_nucs, self.chain.reactions) def __call__(self, vec, power, print_out=True): """Runs a simulation. diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py index c66003530..482c357a2 100644 --- a/openmc/deplete/reaction_rates.py +++ b/openmc/deplete/reaction_rates.py @@ -2,6 +2,7 @@ An ndarray to store reaction rates with string, integer, or slice indexing. """ +from collections import OrderedDict import numpy as np @@ -19,8 +20,8 @@ class ReactionRates(np.ndarray): Material IDs nuclides : list of str Depletable nuclides - index_rx : OrderedDict of str to int - A dictionary mapping reaction name as string to index. + reactions : list of str + Transmutation reactions being tracked Attributes ---------- @@ -46,16 +47,16 @@ class ReactionRates(np.ndarray): # the __array_finalize__ method (discussed here: # https://docs.scipy.org/doc/numpy/user/basics.subclassing.html) - def __new__(cls, local_mats, nuclides, index_rx): + def __new__(cls, local_mats, nuclides, reactions): # Create appropriately-sized zeroed-out ndarray - shape = (len(local_mats), len(nuclides), len(index_rx)) + shape = (len(local_mats), len(nuclides), len(reactions)) obj = super().__new__(cls, shape) obj[:] = 0.0 # Add mapping attributes 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 = index_rx + obj.index_rx = {rx: i for i, rx in enumerate(reactions)} return obj diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 177158dbe..307d18a40 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -76,16 +76,10 @@ class Results(object): """ self.volume = copy.deepcopy(volume) - self.nuc_to_ind = OrderedDict() - self.mat_to_ind = OrderedDict() + 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)} - for i, mat in enumerate(burn_list): - self.mat_to_ind[mat] = i - - for i, nuc in enumerate(nuc_list): - self.nuc_to_ind[nuc] = i - # Create storage array self.data = np.zeros((stages, self.n_mat, self.n_nuc)) @@ -123,8 +117,8 @@ class Results(object): ------- float The atoms for stage, mat, nuc - """ + """ stage, mat, nuc = pos if isinstance(mat, str): mat = self.mat_to_ind[mat] @@ -145,8 +139,8 @@ class Results(object): val : float The value to set data to. - """ + """ stage, mat, nuc = pos if isinstance(mat, str): mat = self.mat_to_ind[mat] @@ -162,8 +156,8 @@ class Results(object): ---------- 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) @@ -234,8 +228,8 @@ class Results(object): An hdf5 file or group type to store this in. index : int What step is this? - """ + """ if "/number" not in handle: comm.barrier() self.create_hdf5(handle) @@ -299,6 +293,7 @@ class Results(object): An hdf5 file or group type to load from. index : int What step is this? + """ results = cls() @@ -357,8 +352,8 @@ def write_results(result, filename, index): Target filename. index : int What step is this? - """ + """ if have_mpi and h5py.get_config().mpi: kwargs = {'driver': 'mpio', 'comm': comm} else: diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index ae900e62d..7ac3e2f8d 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -136,11 +136,10 @@ def test_form_matrix(): chain = Chain.from_xml(_test_filename) - mat_ind = {"10000": 0, "10001": 1} - nuc_ind = {"A": 0, "B": 1, "C": 2} - react_ind = {rx: i for i, rx in enumerate(chain.reactions)} + mats = ["10000", "10001"] + nuclides = ["A", "B", "C"] - react = reaction_rates.ReactionRates(mat_ind, nuc_ind, react_ind) + react = reaction_rates.ReactionRates(mats, nuclides, chain.reactions) react.set("10000", "C", "fission", 1.0) react.set("10000", "A", "(n,gamma)", 2.0) diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 78dd6bcef..b20990088 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -26,20 +26,18 @@ def test_save_results(run_in_tmpdir): op = MagicMock() vol_dict = {} - full_burn_dict = {} + full_burn_list = [] - j = 0 for i in range(comm.size): vol_dict[str(2*i)] = 1.2 vol_dict[str(2*i + 1)] = 1.2 - full_burn_dict[str(2*i)] = j - full_burn_dict[str(2*i + 1)] = j + 1 - j += 2 + full_burn_list.append(str(2*i)) + full_burn_list.append(str(2*i + 1)) - burn_list = [str(i) for i in range(2*comm.rank, 2*comm.rank + 2)] + burn_list = full_burn_list[2*comm.rank : 2*comm.rank + 2] nuc_list = ["na", "nb"] - op.get_results_info.return_value = vol_dict, nuc_list, burn_list, full_burn_dict + op.get_results_info.return_value = vol_dict, nuc_list, burn_list, full_burn_list # Construct x x1 = [] @@ -50,18 +48,17 @@ def test_save_results(run_in_tmpdir): x2.append([np.random.rand(2), np.random.rand(2)]) # Construct r - cell_dict = {s: i for i, s in enumerate(burn_list)} - r1 = ReactionRates(cell_dict, {"na": 0, "nb": 1}, {"ra": 0, "rb": 1}) - r1.rates = np.random.rand(2, 2, 2) + r1 = ReactionRates(burn_list, ["na", "nb"], ["ra", "rb"]) + r1[:] = np.random.rand(2, 2, 2) rate1 = [] rate2 = [] for i in range(stages): rate1.append(copy.deepcopy(r1)) - r1.rates = np.random.rand(2, 2, 2) + r1[:] = np.random.rand(2, 2, 2) rate2.append(copy.deepcopy(r1)) - r1.rates = np.random.rand(2, 2, 2) + r1[:] = np.random.rand(2, 2, 2) # Create global terms eigvl1 = np.random.rand(stages) diff --git a/tests/unit_tests/test_deplete_reaction.py b/tests/unit_tests/test_deplete_reaction.py index e46a7b13d..18639a27a 100644 --- a/tests/unit_tests/test_deplete_reaction.py +++ b/tests/unit_tests/test_deplete_reaction.py @@ -9,9 +9,9 @@ def test_get_set(): local_mats = ["10000", "10001"] nuclides = ["U238", "U235"] - react_to_ind = {"fission": 0, "(n,gamma)": 1} + reactions = ["fission", "(n,gamma)"] - rates = ReactionRates(local_mats, nuclides, react_to_ind) + rates = ReactionRates(local_mats, nuclides, reactions) assert rates.shape == (2, 2, 2) assert np.all(rates == 0.0) @@ -54,9 +54,9 @@ def test_properties(): """Test number of materials property.""" local_mats = ["10000", "10001"] nuclides = ["U238", "U235", "Gd157"] - react_to_ind = {"fission": 0, "(n,gamma)": 1, "(n,2n)": 2, "(n,3n)": 3} + reactions = ["fission", "(n,gamma)", "(n,2n)", "(n,3n)"] - rates = ReactionRates(local_mats, nuclides, react_to_ind) + rates = ReactionRates(local_mats, nuclides, reactions) assert rates.n_mat == 2 assert rates.n_nuc == 3 From 4aae63ff770d9db1d70b175ada41e24a867b3737 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 20 Feb 2018 14:38:45 -0500 Subject: [PATCH 253/282] Reorder printing calls to output CMFD data correctly --- src/output.F90 | 16 ++++++++++++---- src/simulation.F90 | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/output.F90 b/src/output.F90 index 9a144721d..11366b5c0 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -368,7 +368,7 @@ contains ! write out entropy info if (entropy_on) write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & entropy % data(i) - + ! write out accumulated k-effective if after first active batch if (n > 1) then write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5," +/-",F8.5)', ADVANCE='NO') & @@ -376,6 +376,15 @@ contains else write(UNIT=OUTPUT_UNIT, FMT='(23X)', ADVANCE='NO') end if + + end subroutine print_batch_keff + +!=============================================================================== +! PRINT_BATCH_KEFF displays the last batch's tallied value of the neutron +! multiplication factor as well as the average value if we're in active batches +!=============================================================================== + + subroutine print_cmfd() ! write out cmfd keff if it is active and other display info if (cmfd_on) then @@ -396,11 +405,10 @@ contains cmfd % dom(current_batch) end select end if - ! next line write(UNIT=OUTPUT_UNIT, FMT=*) - - end subroutine print_batch_keff + + end subroutine print_cmfd !=============================================================================== ! PRINT_PLOT displays selected options for plotting diff --git a/src/simulation.F90 b/src/simulation.F90 index ef29e7832..91c176b99 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -24,7 +24,8 @@ module simulation use nuclide_header, only: micro_xs, n_nuclides use output, only: header, print_columns, & print_batch_keff, print_generation, print_runtime, & - print_results, print_overlap_check, write_tallies + print_results, print_overlap_check, write_tallies, & + print_cmfd use particle_header, only: Particle use random_lcg, only: set_particle_seed use settings @@ -338,6 +339,7 @@ contains if (run_mode == MODE_EIGENVALUE) then ! Perform CMFD calculation if on if (cmfd_on) call execute_cmfd() + call print_cmfd() end if ! Check_triggers From 552a8b9c0c48adb124a79405c62bbaa66c849fc9 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 20 Feb 2018 14:46:03 -0500 Subject: [PATCH 254/282] Add description of print_cmfd --- src/output.F90 | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/output.F90 b/src/output.F90 index 11366b5c0..30219e98a 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -368,7 +368,7 @@ contains ! write out entropy info if (entropy_on) write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & entropy % data(i) - + ! write out accumulated k-effective if after first active batch if (n > 1) then write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5," +/-",F8.5)', ADVANCE='NO') & @@ -380,8 +380,9 @@ contains end subroutine print_batch_keff !=============================================================================== -! PRINT_BATCH_KEFF displays the last batch's tallied value of the neutron -! multiplication factor as well as the average value if we're in active batches +! PRINT_CMFD displays the CMFD related information to output after CMFD is +! executed. Will print blank line if CMFD is not on, to ensure consistent +! formatting of output columns !=============================================================================== subroutine print_cmfd() @@ -405,9 +406,10 @@ contains cmfd % dom(current_batch) end select end if + ! next line write(UNIT=OUTPUT_UNIT, FMT=*) - + end subroutine print_cmfd !=============================================================================== From f15f824c997aa214d11fb1efc85dc0604a17bcd6 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Tue, 20 Feb 2018 14:48:36 -0500 Subject: [PATCH 255/282] Minor spacing fix --- src/output.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/output.F90 b/src/output.F90 index 30219e98a..643a28b67 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -409,7 +409,7 @@ contains ! next line write(UNIT=OUTPUT_UNIT, FMT=*) - + end subroutine print_cmfd !=============================================================================== From 6fac1367ec15fc6ee45fcfd220be2205de2b5f88 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 20 Feb 2018 14:43:26 -0600 Subject: [PATCH 256/282] Improve depletion reference documentation --- docs/source/conf.py | 1 + docs/source/pythonapi/deplete.rst | 98 +++++++++++++++++-------------- openmc/deplete/abc.py | 2 + openmc/deplete/atom_number.py | 2 - openmc/deplete/chain.py | 7 +++ openmc/deplete/integrator/cecm.py | 2 +- openmc/deplete/nuclide.py | 50 +++++++++++++--- openmc/deplete/operator.py | 7 ++- openmc/deplete/reaction_rates.py | 3 - openmc/deplete/results.py | 86 +++++++++++++-------------- 10 files changed, 155 insertions(+), 103 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index b25d5abbe..a2fec39b7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -32,6 +32,7 @@ MOCK_MODULES = [ sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES) import numpy as np +np.ndarray = MagicMock np.polynomial.Polynomial = MagicMock diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 13f2fd155..1d1a1c0ee 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -4,59 +4,71 @@ :mod:`openmc.deplete` -- Depletion ---------------------------------- -Integrators ------------ +.. module:: openmc.deplete + +Two functions are provided that implement different time-integration algorithms +for depletion calculations. .. autosummary:: :toctree: generated :nosignatures: :template: myfunction.rst - openmc.deplete.integrator.predictor - openmc.deplete.integrator.cecm + integrator.predictor + integrator.cecm -Integrator Helper Functions ---------------------------- +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 + +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 + TransportOperator + +Each of the integrator functions also relies on a number of "helper" functions +as follows: .. autosummary:: :toctree: generated :nosignatures: :template: myfunction.rst - openmc.deplete.integrator.CRAM16 - openmc.deplete.integrator.CRAM48 - openmc.deplete.integrator.save_results - -Metaclasses ------------ - -.. autosummary:: - :toctree: generated - :nosignatures: - :template: myclass.rst - - openmc.deplete.TransportOperator - -OpenMC Classes --------------- - -.. autosummary:: - :toctree: generated - :nosignatures: - :template: myclass.rst - - openmc.deplete.Operator - openmc.deplete.OperatorResult - -Data Classes ------------- -.. autosummary:: - :toctree: generated - :nosignatures: - :template: myclass.rst - - openmc.deplete.AtomNumber - openmc.deplete.Chain - openmc.deplete.Nuclide - openmc.deplete.ReactionRates - openmc.deplete.Results + integrator.CRAM16 + integrator.CRAM48 + integrator.save_results diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index f2fa73650..8b42ee800 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -23,6 +23,8 @@ rates : openmc.deplete.ReactionRates Resulting reaction rates """ +OperatorResult.k.__doc__ = None +OperatorResult.rates.__doc__ = None class TransportOperator(metaclass=ABCMeta): diff --git a/openmc/deplete/atom_number.py b/openmc/deplete/atom_number.py index 8f6a41911..9a32dfa3a 100644 --- a/openmc/deplete/atom_number.py +++ b/openmc/deplete/atom_number.py @@ -113,12 +113,10 @@ class AtomNumber(object): @property def n_nuc(self): - """Number of nuclides.""" return len(self.index_nuc) @property def burnable_nuclides(self): - """All burnable nuclide names. Used for sorting the simulation.""" return [nuc for nuc, ind in self.index_nuc.items() if ind < self.n_nuc_burn] diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 0cd3fb0d7..23395329e 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -111,6 +111,13 @@ def replace_missing(product, decay_data): 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 diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index db58d5d52..9a07cd198 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -10,7 +10,7 @@ from .save_results import save_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 + Implements the second order `CE/CM predictor-corrector algorithm `_. This algorithm is mathematically defined as: diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 4a7b86f02..af10e5c2f 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -9,14 +9,50 @@ try: 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 + +""" +DecayTuple.type.__doc__ = None +DecayTuple.target.__doc__ = None +DecayTuple.branching_ratio.__doc__ = None + + 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 + +""" +ReactionTuple.type.__doc__ = None +ReactionTuple.target.__doc__ = None +ReactionTuple.Q.__doc__ = None +ReactionTuple.branching_ratio.__doc__ = None class Nuclide(object): - """The Nuclide class. - - Contains everything in a depletion chain relating to a single nuclide. + """Decay modes, reactions, and fission yields for a single nuclide. Attributes ---------- @@ -28,12 +64,12 @@ class Nuclide(object): Energy deposited from decay in [eV]. n_decay_modes : int Number of decay pathways. - decay_modes : list of DecayTuple + 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 ReactionTuple + 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 @@ -62,12 +98,10 @@ class Nuclide(object): @property def n_decay_modes(self): - """Number of decay modes.""" return len(self.decay_modes) @property def n_reaction_paths(self): - """Number of possible reaction pathways.""" return len(self.reactions) @classmethod @@ -81,7 +115,7 @@ class Nuclide(object): Returns ------- - nuc : Nuclide + nuc : openmc.deplete.Nuclide Instance of a nuclide """ diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index d0a64a06d..1c62bdd40 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -50,7 +50,12 @@ def _distribute(items): class Operator(TransportOperator): - """OpenMC transport operator for depletion + """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 ---------- diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py index 482c357a2..fddb88b19 100644 --- a/openmc/deplete/reaction_rates.py +++ b/openmc/deplete/reaction_rates.py @@ -85,17 +85,14 @@ class ReactionRates(np.ndarray): @property def n_mat(self): - """Number of materials.""" return len(self.index_mat) @property def n_nuc(self): - """Number of nucs.""" return len(self.index_nuc) @property def n_react(self): - """Number of reactions.""" return len(self.index_rx) def get(self, mat, nuc, rx): diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 307d18a40..f27353a4e 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -58,51 +58,6 @@ class Results(object): self.data = None - 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)) - - @property - def n_mat(self): - """Number of mats.""" - return len(self.mat_to_ind) - - @property - def n_nuc(self): - """Number of nuclides.""" - return len(self.nuc_to_ind) - - @property - def n_hdf5_mats(self): - """Number of materials in entire geometry.""" - return len(self.mat_to_hdf5_ind) - - @property - def n_stages(self): - """Number of stages in simulation.""" - return self.data.shape[0] - def __getitem__(self, pos): """Retrieves an item from results. @@ -149,6 +104,47 @@ class Results(object): 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 create_hdf5(self, handle): """Creates file structure for a blank HDF5 file. From 33dec88bd089876b36a73d0850ee594e1a7a49d6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 20 Feb 2018 15:35:39 -0600 Subject: [PATCH 257/282] Add format spec for depletion results HDF5 file (add necessary attributes too) --- docs/source/io_formats/depletion_chain.rst | 2 +- docs/source/io_formats/depletion_results.rst | 42 ++++++++++++++++++ docs/source/io_formats/index.rst | 7 +-- openmc/checkvalue.py | 4 +- openmc/deplete/results.py | 44 ++++++++++--------- tests/regression_tests/test_reference.h5 | Bin 162120 -> 162320 bytes 6 files changed, 72 insertions(+), 27 deletions(-) create mode 100644 docs/source/io_formats/depletion_results.rst diff --git a/docs/source/io_formats/depletion_chain.rst b/docs/source/io_formats/depletion_chain.rst index 00d95bdf5..b0dd72eb9 100644 --- a/docs/source/io_formats/depletion_chain.rst +++ b/docs/source/io_formats/depletion_chain.rst @@ -1,4 +1,4 @@ -.. _io_chain: +.. _io_depletion_chain: ============================ Depletion Chain -- chain.xml diff --git a/docs/source/io_formats/depletion_results.rst b/docs/source/io_formats/depletion_results.rst new file mode 100644 index 000000000..fcbc4fb99 --- /dev/null +++ b/docs/source/io_formats/depletion_results.rst @@ -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** (*float[][]*) -- k-eigenvalues at each + time/stage. This array has shape (number of timesteps, number of + stages). + - **number** (*float[][][][]*) -- Total number of atoms. This array + has shape (number of timesteps, number of stages, number of + materials, number of nuclides). + - **reaction rates** (*float[][][][][]*) -- 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** (*float[][2]*) -- Time in [s] at beginning/end of each + step. + +**/materials//** + +:Attributes: - **index** (*int*) -- Index used in results for this material + - **volume** (*float*) -- Volume of this material in [cm^3] + +**/nuclides//** + +: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//** + +:Attributes: - **index** (*int*) -- Index user in results for this reaction diff --git a/docs/source/io_formats/index.rst b/docs/source/io_formats/index.rst index 106897333..c1bb76a29 100644 --- a/docs/source/io_formats/index.rst +++ b/docs/source/io_formats/index.rst @@ -12,7 +12,7 @@ Input Files .. toctree:: :numbered: - :maxdepth: 2 + :maxdepth: 1 geometry materials @@ -27,7 +27,7 @@ Data Files .. toctree:: :numbered: - :maxdepth: 2 + :maxdepth: 1 cross_sections depletion_chain @@ -42,11 +42,12 @@ Output Files .. toctree:: :numbered: - :maxdepth: 2 + :maxdepth: 1 statepoint source summary + depletion_results particle_restart track voxel diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index dd32aa566..2f80ee4c0 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -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. """ diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index f27353a4e..b7e5623c7 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -11,12 +11,13 @@ import h5py from . import comm, have_mpi from .reaction_rates import ReactionRates +from openmc.checkvalue import check_filetype_version -RESULTS_VERSION = 2 +_VERSION_RESULTS = (1, 0) class Results(object): - """Contains output of a depletion run. + """Output of a depletion run Attributes ---------- @@ -145,8 +146,8 @@ class Results(object): # Create storage array self.data = np.zeros((stages, self.n_mat, self.n_nuc)) - def create_hdf5(self, handle): - """Creates file structure for a blank HDF5 file. + def _write_hdf5_metadata(self, handle): + """Writes result metadata in HDF5 file Parameters ---------- @@ -165,7 +166,8 @@ class Results(object): # Store concentration mat and nuclide dictionaries (along with volumes) - handle.create_dataset("version", data=RESULTS_VERSION) + 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) @@ -221,14 +223,14 @@ class Results(object): Parameters ---------- handle : h5py.File or h5py.Group - An hdf5 file or group type to store this in. + An HDF5 file or group type to store this in. index : int What step is this? """ if "/number" not in handle: comm.barrier() - self.create_hdf5(handle) + self._write_hdf5_metadata(handle) comm.barrier() @@ -280,14 +282,14 @@ class Results(object): time_dset[index, :] = self.time @classmethod - def from_hdf5(cls, handle, index): + 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. - index : int + An HDF5 file or group type to load from. + step : int What step is this? """ @@ -298,9 +300,9 @@ class Results(object): eigenvalues_dset = handle["/eigenvalues"] time_dset = handle["/time"] - results.data = number_dset[index, :, :, :] - results.k = eigenvalues_dset[index, :] - results.time = time_dset[index, :] + results.data = number_dset[step, :, :, :] + results.k = eigenvalues_dset[step, :] + results.time = time_dset[step, :] # Reconstruct dictionaries results.volume = OrderedDict() @@ -331,14 +333,14 @@ class Results(object): for i in range(results.n_stages): rate = ReactionRates(results.mat_to_ind, rxn_nuc_to_ind, rxn_to_ind) - rate[:] = handle["/reaction rates"][index, i, :, :, :] + rate[:] = handle["/reaction rates"][step, i, :, :, :] results.rates.append(rate) return results -def write_results(result, filename, index): - """Outputs result to an .hdf5 file. +def write_results(result, filename, step): + """Outputs result to an HDF5 file. Parameters ---------- @@ -346,7 +348,7 @@ def write_results(result, filename, index): Object to be stored in a file. filename : String Target filename. - index : int + step : int What step is this? """ @@ -355,10 +357,10 @@ def write_results(result, filename, index): else: kwargs = {} - kwargs['mode'] = "w" if index == 0 else "a" + kwargs['mode'] = "w" if step == 0 else "a" with h5py.File(filename, **kwargs) as handle: - result.to_hdf5(handle, index) + result.to_hdf5(handle, step) def read_results(filename): @@ -371,12 +373,12 @@ def read_results(filename): Returns ------- - results : list of Results + results : list of openmc.deplete.Results The result objects. """ with h5py.File(str(filename), "r") as fh: - assert fh["version"].value == RESULTS_VERSION + check_filetype_version(fh, 'depletion results', _VERSION_RESULTS[0]) # Get number of results stored n = fh["number"].value.shape[0] diff --git a/tests/regression_tests/test_reference.h5 b/tests/regression_tests/test_reference.h5 index 6e724c597107560c4155a257f4e854210f36f08e..5323a9a904e0fce1f806fbc49ad90324b4fe1aa9 100644 GIT binary patch delta 188 zcmX@{iF3ji&IuY!0#y^WEG9o<7vuD(WMTk;6VqQhGpaYPXkEd$bp_LciirghmOKm| z3@ku7Mg|TB9tH`9vecsD%=|nC0S*SB2naZUNk&FSFby$@fq`lI#X?4LZyumDL^~%? zIR`^pW=?8JWkD)fEszif>JkLf5X}q>DX9fO1wacFic*V9b4rR~3K;|@ZWILo?m{Cr delta 45 xcmbR6h4aKG&IuY!9+eZdET$_dGKz6^FhIZxrs=Po8PytBw60*>x`Jsz1prXj4y6D9 From 4a500df455aba407bb69d8c9980a834cccd09f3b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 20 Feb 2018 16:40:18 -0600 Subject: [PATCH 258/282] Add Model.deplete method to make user's life easier --- openmc/deplete/operator.py | 5 +++++ openmc/model/model.py | 39 +++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 1c62bdd40..a1d8ffb0b 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -195,6 +195,11 @@ class Operator(TransportOperator): "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) diff --git a/openmc/model/model.py b/openmc/model/model.py index 89fa84e60..d6e6ddce3 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -2,6 +2,10 @@ from collections.abc import Iterable import openmc from openmc.checkvalue import check_type +import openmc.deplete as dep + +_DEPLETE_METHODS = {'predictor': dep.integrator.predictor, + 'cecm': dep.integrator.cecm} class Model(object): @@ -136,9 +140,38 @@ 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`) + """ + # Create OpenMC transport operator + op = dep.Operator(self.geometry, self.settings, chain_file) + + # Perform depletion + _DEPLETE_METHODS[method](op, timesteps, power, **kwargs) + + def export_to_xml(self): + """Export model to XML files.""" self.settings.export_to_xml() self.geometry.export_to_xml() @@ -166,7 +199,7 @@ class Model(object): Parameters ---------- **kwargs - All keyword arguments are passed to openmc.run + All keyword arguments are passed to :func:`openmc.run` Returns ------- From b4719cf53fbabc9f1aa60dba9608f4f101f71674 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 20 Feb 2018 22:46:26 -0600 Subject: [PATCH 259/282] Fix for Python 3.4 (setting __doc__ on properties) --- openmc/deplete/abc.py | 8 ++++++-- openmc/deplete/nuclide.py | 21 ++++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 8b42ee800..8502909a2 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -23,8 +23,12 @@ rates : openmc.deplete.ReactionRates Resulting reaction rates """ -OperatorResult.k.__doc__ = None -OperatorResult.rates.__doc__ = None +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): diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index af10e5c2f..8a30214c2 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -24,9 +24,13 @@ branching_ratio : float Branching ratio of the decay mode """ -DecayTuple.type.__doc__ = None -DecayTuple.target.__doc__ = None -DecayTuple.branching_ratio.__doc__ = None +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') @@ -45,10 +49,13 @@ branching_ratio : float Branching ratio of the reaction """ -ReactionTuple.type.__doc__ = None -ReactionTuple.target.__doc__ = None -ReactionTuple.Q.__doc__ = None -ReactionTuple.branching_ratio.__doc__ = None +try: + ReactionTuple.type.__doc__ = None + ReactionTuple.target.__doc__ = None + ReactionTuple.Q.__doc__ = None + ReactionTuple.branching_ratio.__doc__ = None +except AttributeError: + pass class Nuclide(object): From 82d6c34d27e0021280581398be350e3f0df1a234 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 21 Feb 2018 06:51:51 -0600 Subject: [PATCH 260/282] Move make_chain.py to openmc-make-depletion-chain --- .gitignore | 7 +--- openmc/_utils.py | 49 +++++++++++++++++++++++ scripts/example_run.py | 16 ++++---- scripts/make_chain.py | 60 ----------------------------- scripts/openmc-make-depletion-chain | 33 ++++++++++++++++ 5 files changed, 91 insertions(+), 74 deletions(-) create mode 100644 openmc/_utils.py delete mode 100644 scripts/make_chain.py create mode 100755 scripts/openmc-make-depletion-chain diff --git a/.gitignore b/.gitignore index 8c7cc3e36..65c7285af 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/openmc/_utils.py b/openmc/_utils.py new file mode 100644 index 000000000..83455c17a --- /dev/null +++ b/openmc/_utils.py @@ -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 diff --git a/scripts/example_run.py b/scripts/example_run.py index 30b6bdc2e..36b6cce1a 100644 --- a/scripts/example_run.py +++ b/scripts/example_run.py @@ -14,22 +14,20 @@ geometry, lower_left, upper_right = example_geometry.generate_problem() dt1 = 15*24*60*60 # 15 days dt2 = 5.5*30*24*60*60 # 5.5 months N = np.floor(dt2/dt1) - dt = np.repeat([dt1], N) -# Depletion settings -settings = openmc.deplete.OpenMCSettings() -settings.power = 2.337e15*4*JOULE_PER_EV*1e6 # MeV/second cm from CASMO -settings.dt_vec = dt -settings.output_dir = 'test' +# Power for simulation +power = 2.337e15*4*JOULE_PER_EV*1e6 # MeV/second cm from CASMO -# OpenMC-delegated settings +# OpenMC settings +settings = openmc.Settings() settings.particles = 1000 settings.batches = 100 settings.inactive = 40 settings.source = openmc.Source(space=openmc.stats.Box(lower_left, upper_right)) -op = openmc.deplete.OpenMCOperator(geometry, settings) +op = openmc.deplete.Operator(geometry, settings) +op.output_dir = 'test' # Perform simulation using the MCNPX/MCNP6 algorithm -openmc.deplete.integrator.cecm(op) +openmc.deplete.integrator.cecm(op, dt, power) diff --git a/scripts/make_chain.py b/scripts/make_chain.py deleted file mode 100644 index 6d64f34b3..000000000 --- a/scripts/make_chain.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python - -import glob -import os -from zipfile import ZipFile - -import requests -from tqdm import tqdm -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 download_file(url): - response = requests.get(url, stream=True) - filesize = int(response.headers.get('content-length')) - - # Check if file already downloaded - basename = url.split('/')[-1] - if os.path.exists(basename): - if os.path.getsize(basename) == filesize: - return basename - else: - overwrite = input('Overwrite {}? ([y]/n) '.format(basename)) - if overwrite.lower().startswith('n'): - return basename - - with open(basename, 'wb') as f: - with tqdm(desc='Downloading {}'.format(basename), - total=filesize, unit='B', unit_scale=True) as pbar: - for i, chunk in enumerate(response.iter_content(chunk_size=4096)): - pbar.update(4096) - if chunk: - f.write(chunk) - - return basename - - -def main(): - for url in urls: - basename = download_file(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() diff --git a/scripts/openmc-make-depletion-chain b/scripts/openmc-make-depletion-chain new file mode 100755 index 000000000..7c08f984e --- /dev/null +++ b/scripts/openmc-make-depletion-chain @@ -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() From d981b34dc18236cf857d1249629b6437005e073f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 21 Feb 2018 07:00:20 -0600 Subject: [PATCH 261/282] Remove FutureWarning for capi import --- openmc/capi/__init__.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/openmc/capi/__init__.py b/openmc/capi/__init__.py index d302001c9..bc173f994 100644 --- a/openmc/capi/__init__.py +++ b/openmc/capi/__init__.py @@ -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 * @@ -50,6 +46,3 @@ from .cell 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 81b859ad4ff3995ac84e27b8bf15ba2f87cc4cc4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 21 Feb 2018 07:33:18 -0600 Subject: [PATCH 262/282] Get rid of example_run.py and example_plot.py --- scripts/example_geometry.py | 378 -------------------- scripts/example_plot.py | 43 --- scripts/example_run.py | 33 -- tests/regression_tests/example_geometry.py | 379 ++++++++++++++++++++- 4 files changed, 378 insertions(+), 455 deletions(-) delete mode 100644 scripts/example_geometry.py delete mode 100644 scripts/example_plot.py delete mode 100644 scripts/example_run.py mode change 120000 => 100644 tests/regression_tests/example_geometry.py diff --git a/scripts/example_geometry.py b/scripts/example_geometry.py deleted file mode 100644 index ca10c1f72..000000000 --- a/scripts/example_geometry.py +++ /dev/null @@ -1,378 +0,0 @@ -"""An example file showing how to make a geometry. - -This particular example creates a 3x3 geometry, with 8 regular pins and one -Gd-157 2 wt-percent enriched. All pins are segmented. -""" - -from collections import OrderedDict -import math - -import numpy as np -import openmc - - -def density_to_mat(dens_dict): - """Generates an OpenMC material from a cell ID and self.number_density. - - Parameters - ---------- - dens_dict : dict - Dictionary mapping nuclide names to densities - - Returns - ------- - openmc.Material - The OpenMC material filled with nuclides. - - """ - mat = openmc.Material() - for key in dens_dict: - mat.add_nuclide(key, 1.0e-24*dens_dict[key]) - mat.set_density('sum') - - return mat - - -def generate_initial_number_density(): - """ Generates initial number density. - - These results were from a CASMO5 run in which the gadolinium pin was - loaded with 2 wt percent of Gd-157. - """ - - # Concentration to be used for all fuel pins - fuel_dict = OrderedDict() - fuel_dict['U235'] = 1.05692e21 - fuel_dict['U234'] = 1.00506e19 - fuel_dict['U238'] = 2.21371e22 - fuel_dict['O16'] = 4.62954e22 - fuel_dict['O17'] = 1.127684e20 - fuel_dict['I135'] = 1.0e10 - fuel_dict['Xe135'] = 1.0e10 - fuel_dict['Xe136'] = 1.0e10 - fuel_dict['Cs135'] = 1.0e10 - fuel_dict['Gd156'] = 1.0e10 - fuel_dict['Gd157'] = 1.0e10 - # fuel_dict['O18'] = 9.51352e19 # Does not exist in ENDF71, merged into 17 - - # Concentration to be used for the gadolinium fuel pin - fuel_gd_dict = OrderedDict() - fuel_gd_dict['U235'] = 1.03579e21 - fuel_gd_dict['U238'] = 2.16943e22 - fuel_gd_dict['Gd156'] = 3.95517E+10 - fuel_gd_dict['Gd157'] = 1.08156e20 - fuel_gd_dict['O16'] = 4.64035e22 - fuel_dict['I135'] = 1.0e10 - fuel_dict['Xe136'] = 1.0e10 - fuel_dict['Xe135'] = 1.0e10 - fuel_dict['Cs135'] = 1.0e10 - # There are a whole bunch of 1e-10 stuff here. - - # Concentration to be used for cladding - clad_dict = OrderedDict() - clad_dict['O16'] = 3.07427e20 - clad_dict['O17'] = 7.48868e17 - clad_dict['Cr50'] = 3.29620e18 - clad_dict['Cr52'] = 6.35639e19 - clad_dict['Cr53'] = 7.20763e18 - clad_dict['Cr54'] = 1.79413e18 - clad_dict['Fe54'] = 5.57350e18 - clad_dict['Fe56'] = 8.74921e19 - clad_dict['Fe57'] = 2.02057e18 - clad_dict['Fe58'] = 2.68901e17 - clad_dict['Cr50'] = 3.29620e18 - clad_dict['Cr52'] = 6.35639e19 - clad_dict['Cr53'] = 7.20763e18 - clad_dict['Cr54'] = 1.79413e18 - clad_dict['Ni58'] = 2.51631e19 - clad_dict['Ni60'] = 9.69278e18 - clad_dict['Ni61'] = 4.21338e17 - clad_dict['Ni62'] = 1.34341e18 - clad_dict['Ni64'] = 3.43127e17 - clad_dict['Zr90'] = 2.18320e22 - clad_dict['Zr91'] = 4.76104e21 - clad_dict['Zr92'] = 7.27734e21 - clad_dict['Zr94'] = 7.37494e21 - clad_dict['Zr96'] = 1.18814e21 - clad_dict['Sn112'] = 4.67352e18 - clad_dict['Sn114'] = 3.17992e18 - clad_dict['Sn115'] = 1.63814e18 - clad_dict['Sn116'] = 7.00546e19 - clad_dict['Sn117'] = 3.70027e19 - clad_dict['Sn118'] = 1.16694e20 - clad_dict['Sn119'] = 4.13872e19 - clad_dict['Sn120'] = 1.56973e20 - clad_dict['Sn122'] = 2.23076e19 - clad_dict['Sn124'] = 2.78966e19 - - # Gap concentration - # Funny enough, the example problem uses air. - gap_dict = OrderedDict() - gap_dict['O16'] = 7.86548e18 - gap_dict['O17'] = 2.99548e15 - gap_dict['N14'] = 3.38646e19 - gap_dict['N15'] = 1.23717e17 - - # Concentration to be used for coolant - # No boron - cool_dict = OrderedDict() - cool_dict['H1'] = 4.68063e22 - cool_dict['O16'] = 2.33427e22 - cool_dict['O17'] = 8.89086e18 - - # Store these dictionaries in the initial conditions dictionary - initial_density = OrderedDict() - initial_density['fuel_gd'] = fuel_gd_dict - initial_density['fuel'] = fuel_dict - initial_density['gap'] = gap_dict - initial_density['clad'] = clad_dict - initial_density['cool'] = cool_dict - - # Set up libraries to use - temperature = OrderedDict() - sab = OrderedDict() - - # Toggle betweeen MCNP and NNDC data - MCNP = False - - if MCNP: - temperature['fuel_gd'] = 900.0 - temperature['fuel'] = 900.0 - # We approximate temperature of everything as 600K, even though it was - # actually 580K. - temperature['gap'] = 600.0 - temperature['clad'] = 600.0 - temperature['cool'] = 600.0 - else: - temperature['fuel_gd'] = 293.6 - temperature['fuel'] = 293.6 - temperature['gap'] = 293.6 - temperature['clad'] = 293.6 - temperature['cool'] = 293.6 - - sab['cool'] = 'c_H_in_H2O' - - # Set up burnable materials - burn = OrderedDict() - burn['fuel_gd'] = True - burn['fuel'] = True - burn['gap'] = False - burn['clad'] = False - burn['cool'] = False - - return temperature, sab, initial_density, burn - -def segment_pin(n_rings, n_wedges, r_fuel, r_gap, r_clad): - """ Calculates a segmented pin. - - Separates a pin with n_rings and n_wedges. All cells have equal volume. - Pin is centered at origin. - """ - - # Calculate all the volumes of interest - v_fuel = math.pi * r_fuel**2 - v_gap = math.pi * r_gap**2 - v_fuel - v_clad = math.pi * r_clad**2 - v_fuel - v_gap - v_ring = v_fuel / n_rings - v_segment = v_ring / n_wedges - - # Compute ring radiuses - r_rings = np.zeros(n_rings) - - for i in range(n_rings): - r_rings[i] = math.sqrt(1.0/(math.pi) * v_ring * (i+1)) - - # Compute thetas - theta = np.linspace(0, 2*math.pi, n_wedges + 1) - - # Compute surfaces - fuel_rings = [openmc.ZCylinder(x0=0, y0=0, R=r_rings[i]) - for i in range(n_rings)] - - fuel_wedges = [openmc.Plane(A=math.cos(theta[i]), B=math.sin(theta[i])) - for i in range(n_wedges)] - - gap_ring = openmc.ZCylinder(x0=0, y0=0, R=r_gap) - clad_ring = openmc.ZCylinder(x0=0, y0=0, R=r_clad) - - # Create cells - fuel_cells = [] - if n_wedges == 1: - for i in range(n_rings): - cell = openmc.Cell(name='fuel') - if i == 0: - cell.region = -fuel_rings[0] - else: - cell.region = +fuel_rings[i-1] & -fuel_rings[i] - fuel_cells.append(cell) - else: - for i in range(n_rings): - for j in range(n_wedges): - cell = openmc.Cell(name='fuel') - if i == 0: - if j != n_wedges-1: - cell.region = (-fuel_rings[0] - & +fuel_wedges[j] - & -fuel_wedges[j+1]) - else: - cell.region = (-fuel_rings[0] - & +fuel_wedges[j] - & -fuel_wedges[0]) - else: - if j != n_wedges-1: - cell.region = (+fuel_rings[i-1] - & -fuel_rings[i] - & +fuel_wedges[j] - & -fuel_wedges[j+1]) - else: - cell.region = (+fuel_rings[i-1] - & -fuel_rings[i] - & +fuel_wedges[j] - & -fuel_wedges[0]) - fuel_cells.append(cell) - - # Gap ring - gap_cell = openmc.Cell(name='gap') - gap_cell.region = +fuel_rings[-1] & -gap_ring - fuel_cells.append(gap_cell) - - # Clad ring - clad_cell = openmc.Cell(name='clad') - clad_cell.region = +gap_ring & -clad_ring - fuel_cells.append(clad_cell) - - # Moderator - mod_cell = openmc.Cell(name='cool') - mod_cell.region = +clad_ring - fuel_cells.append(mod_cell) - - # Form universe - fuel_u = openmc.Universe() - fuel_u.add_cells(fuel_cells) - - return fuel_u, v_segment, v_gap, v_clad - -def generate_geometry(n_rings, n_wedges): - """ Generates example geometry. - - This function creates the initial geometry, a 9 pin reflective problem. - One pin, containing gadolinium, is discretized into sectors. - - In addition to what one would do with the general OpenMC geometry code, it - is necessary to create a dictionary, volume, that maps a cell ID to a - volume. Further, by naming cells the same as the above materials, the code - can automatically handle the mapping. - - Parameters - ---------- - n_rings : int - Number of rings to generate for the geometry - n_wedges : int - Number of wedges to generate for the geometry - """ - - pitch = 1.26197 - r_fuel = 0.412275 - r_gap = 0.418987 - r_clad = 0.476121 - - n_pin = 3 - - # This table describes the 'fuel' to actual type mapping - # It's not necessary to do it this way. Just adjust the initial conditions - # below. - mapping = ['fuel', 'fuel', 'fuel', - 'fuel', 'fuel_gd', 'fuel', - 'fuel', 'fuel', 'fuel'] - - # Form pin cell - fuel_u, v_segment, v_gap, v_clad = segment_pin(n_rings, n_wedges, r_fuel, r_gap, r_clad) - - # Form lattice - all_water_c = openmc.Cell(name='cool') - all_water_u = openmc.Universe(cells=(all_water_c, )) - - lattice = openmc.RectLattice() - lattice.pitch = [pitch]*2 - lattice.lower_left = [-pitch*n_pin/2, -pitch*n_pin/2] - lattice_array = [[fuel_u for i in range(n_pin)] for j in range(n_pin)] - lattice.universes = lattice_array - lattice.outer = all_water_u - - # Bound universe - x_low = openmc.XPlane(x0=-pitch*n_pin/2, boundary_type='reflective') - x_high = openmc.XPlane(x0=pitch*n_pin/2, boundary_type='reflective') - y_low = openmc.YPlane(y0=-pitch*n_pin/2, boundary_type='reflective') - y_high = openmc.YPlane(y0=pitch*n_pin/2, boundary_type='reflective') - z_low = openmc.ZPlane(z0=-10, boundary_type='reflective') - z_high = openmc.ZPlane(z0=10, boundary_type='reflective') - - # Compute bounding box - lower_left = [-pitch*n_pin/2, -pitch*n_pin/2, -10] - upper_right = [pitch*n_pin/2, pitch*n_pin/2, 10] - - root_c = openmc.Cell(fill=lattice) - root_c.region = (+x_low & -x_high - & +y_low & -y_high - & +z_low & -z_high) - root_u = openmc.Universe(universe_id=0, cells=(root_c, )) - geometry = openmc.Geometry(root_u) - - v_cool = pitch**2 - (v_gap + v_clad + n_rings * n_wedges * v_segment) - - # Store volumes for later usage - volume = {'fuel': v_segment, 'gap':v_gap, 'clad':v_clad, 'cool':v_cool} - - return geometry, volume, mapping, lower_left, upper_right - -def generate_problem(n_rings=5, n_wedges=8): - """ Merges geometry and materials. - - This function initializes the materials for each cell using the dictionaries - provided by generate_initial_number_density. It is assumed a cell named - 'fuel' will have further region differentiation (see mapping). - - Parameters - ---------- - n_rings : int, optional - Number of rings to generate for the geometry - n_wedges : int, optional - Number of wedges to generate for the geometry - """ - - # Get materials dictionary, geometry, and volumes - temperature, sab, initial_density, burn = generate_initial_number_density() - geometry, volume, mapping, lower_left, upper_right = generate_geometry(n_rings, n_wedges) - - # Apply distribmats, fill geometry - cells = geometry.root_universe.get_all_cells() - for cell_id in cells: - cell = cells[cell_id] - if cell.name == 'fuel': - - omc_mats = [] - - for cell_type in mapping: - omc_mat = density_to_mat(initial_density[cell_type]) - - if cell_type in sab: - omc_mat.add_s_alpha_beta(sab[cell_type]) - omc_mat.temperature = temperature[cell_type] - omc_mat.depletable = burn[cell_type] - omc_mat.volume = volume['fuel'] - - omc_mats.append(omc_mat) - - cell.fill = omc_mats - elif cell.name != '': - omc_mat = density_to_mat(initial_density[cell.name]) - - if cell.name in sab: - omc_mat.add_s_alpha_beta(sab[cell.name]) - omc_mat.temperature = temperature[cell.name] - omc_mat.depletable = burn[cell.name] - omc_mat.volume = volume[cell.name] - - cell.fill = omc_mat - - return geometry, lower_left, upper_right diff --git a/scripts/example_plot.py b/scripts/example_plot.py deleted file mode 100644 index ab5ac204d..000000000 --- a/scripts/example_plot.py +++ /dev/null @@ -1,43 +0,0 @@ -"""An example file showing how to plot data from a simulation.""" - -import matplotlib.pyplot as plt -from openmc.deplete import (read_results, evaluate_single_nuclide, - evaluate_reaction_rate, evaluate_eigenvalue) - -# Set variables for where the data is, and what we want to read out. -result_folder = "test" - -# Load data -results = read_results(result_folder + "/deplete_results.h5") - -cell = "5" -nuc = "Gd157" -rxn = "(n,gamma)" - -# Total number of nuclides -plt.figure() -# Pointwise data -x, y = evaluate_single_nuclide(results, cell, nuc) -plt.semilogy(x, y) - -plt.xlabel("Time, s") -plt.ylabel("Total Number") -plt.savefig("number.pdf") - -# Reaction rate -plt.figure() -x, y = evaluate_reaction_rate(results, cell, nuc, rxn) -plt.plot(x, y) -plt.xlabel("Time, s") -plt.ylabel("Reaction Rate, 1/s") - -plt.savefig("rate.pdf") - -# Eigenvalue -plt.figure() -x, y = evaluate_eigenvalue(results) -plt.plot(x, y) -plt.xlabel("Time, s") -plt.ylabel("Eigenvalue") - -plt.savefig("eigvl.pdf") diff --git a/scripts/example_run.py b/scripts/example_run.py deleted file mode 100644 index 36b6cce1a..000000000 --- a/scripts/example_run.py +++ /dev/null @@ -1,33 +0,0 @@ -"""An example file showing how to run a simulation.""" - -import numpy as np -import openmc -from openmc.data import JOULE_PER_EV -import openmc.deplete - -import example_geometry - -# Load geometry from example -geometry, lower_left, upper_right = example_geometry.generate_problem() - -# Create dt vector for 5.5 months with 15 day timesteps -dt1 = 15*24*60*60 # 15 days -dt2 = 5.5*30*24*60*60 # 5.5 months -N = np.floor(dt2/dt1) -dt = np.repeat([dt1], N) - -# Power for simulation -power = 2.337e15*4*JOULE_PER_EV*1e6 # MeV/second cm from CASMO - -# OpenMC settings -settings = openmc.Settings() -settings.particles = 1000 -settings.batches = 100 -settings.inactive = 40 -settings.source = openmc.Source(space=openmc.stats.Box(lower_left, upper_right)) - -op = openmc.deplete.Operator(geometry, settings) -op.output_dir = 'test' - -# Perform simulation using the MCNPX/MCNP6 algorithm -openmc.deplete.integrator.cecm(op, dt, power) diff --git a/tests/regression_tests/example_geometry.py b/tests/regression_tests/example_geometry.py deleted file mode 120000 index 1071aabc0..000000000 --- a/tests/regression_tests/example_geometry.py +++ /dev/null @@ -1 +0,0 @@ -../../scripts/example_geometry.py \ No newline at end of file diff --git a/tests/regression_tests/example_geometry.py b/tests/regression_tests/example_geometry.py new file mode 100644 index 000000000..ca10c1f72 --- /dev/null +++ b/tests/regression_tests/example_geometry.py @@ -0,0 +1,378 @@ +"""An example file showing how to make a geometry. + +This particular example creates a 3x3 geometry, with 8 regular pins and one +Gd-157 2 wt-percent enriched. All pins are segmented. +""" + +from collections import OrderedDict +import math + +import numpy as np +import openmc + + +def density_to_mat(dens_dict): + """Generates an OpenMC material from a cell ID and self.number_density. + + Parameters + ---------- + dens_dict : dict + Dictionary mapping nuclide names to densities + + Returns + ------- + openmc.Material + The OpenMC material filled with nuclides. + + """ + mat = openmc.Material() + for key in dens_dict: + mat.add_nuclide(key, 1.0e-24*dens_dict[key]) + mat.set_density('sum') + + return mat + + +def generate_initial_number_density(): + """ Generates initial number density. + + These results were from a CASMO5 run in which the gadolinium pin was + loaded with 2 wt percent of Gd-157. + """ + + # Concentration to be used for all fuel pins + fuel_dict = OrderedDict() + fuel_dict['U235'] = 1.05692e21 + fuel_dict['U234'] = 1.00506e19 + fuel_dict['U238'] = 2.21371e22 + fuel_dict['O16'] = 4.62954e22 + fuel_dict['O17'] = 1.127684e20 + fuel_dict['I135'] = 1.0e10 + fuel_dict['Xe135'] = 1.0e10 + fuel_dict['Xe136'] = 1.0e10 + fuel_dict['Cs135'] = 1.0e10 + fuel_dict['Gd156'] = 1.0e10 + fuel_dict['Gd157'] = 1.0e10 + # fuel_dict['O18'] = 9.51352e19 # Does not exist in ENDF71, merged into 17 + + # Concentration to be used for the gadolinium fuel pin + fuel_gd_dict = OrderedDict() + fuel_gd_dict['U235'] = 1.03579e21 + fuel_gd_dict['U238'] = 2.16943e22 + fuel_gd_dict['Gd156'] = 3.95517E+10 + fuel_gd_dict['Gd157'] = 1.08156e20 + fuel_gd_dict['O16'] = 4.64035e22 + fuel_dict['I135'] = 1.0e10 + fuel_dict['Xe136'] = 1.0e10 + fuel_dict['Xe135'] = 1.0e10 + fuel_dict['Cs135'] = 1.0e10 + # There are a whole bunch of 1e-10 stuff here. + + # Concentration to be used for cladding + clad_dict = OrderedDict() + clad_dict['O16'] = 3.07427e20 + clad_dict['O17'] = 7.48868e17 + clad_dict['Cr50'] = 3.29620e18 + clad_dict['Cr52'] = 6.35639e19 + clad_dict['Cr53'] = 7.20763e18 + clad_dict['Cr54'] = 1.79413e18 + clad_dict['Fe54'] = 5.57350e18 + clad_dict['Fe56'] = 8.74921e19 + clad_dict['Fe57'] = 2.02057e18 + clad_dict['Fe58'] = 2.68901e17 + clad_dict['Cr50'] = 3.29620e18 + clad_dict['Cr52'] = 6.35639e19 + clad_dict['Cr53'] = 7.20763e18 + clad_dict['Cr54'] = 1.79413e18 + clad_dict['Ni58'] = 2.51631e19 + clad_dict['Ni60'] = 9.69278e18 + clad_dict['Ni61'] = 4.21338e17 + clad_dict['Ni62'] = 1.34341e18 + clad_dict['Ni64'] = 3.43127e17 + clad_dict['Zr90'] = 2.18320e22 + clad_dict['Zr91'] = 4.76104e21 + clad_dict['Zr92'] = 7.27734e21 + clad_dict['Zr94'] = 7.37494e21 + clad_dict['Zr96'] = 1.18814e21 + clad_dict['Sn112'] = 4.67352e18 + clad_dict['Sn114'] = 3.17992e18 + clad_dict['Sn115'] = 1.63814e18 + clad_dict['Sn116'] = 7.00546e19 + clad_dict['Sn117'] = 3.70027e19 + clad_dict['Sn118'] = 1.16694e20 + clad_dict['Sn119'] = 4.13872e19 + clad_dict['Sn120'] = 1.56973e20 + clad_dict['Sn122'] = 2.23076e19 + clad_dict['Sn124'] = 2.78966e19 + + # Gap concentration + # Funny enough, the example problem uses air. + gap_dict = OrderedDict() + gap_dict['O16'] = 7.86548e18 + gap_dict['O17'] = 2.99548e15 + gap_dict['N14'] = 3.38646e19 + gap_dict['N15'] = 1.23717e17 + + # Concentration to be used for coolant + # No boron + cool_dict = OrderedDict() + cool_dict['H1'] = 4.68063e22 + cool_dict['O16'] = 2.33427e22 + cool_dict['O17'] = 8.89086e18 + + # Store these dictionaries in the initial conditions dictionary + initial_density = OrderedDict() + initial_density['fuel_gd'] = fuel_gd_dict + initial_density['fuel'] = fuel_dict + initial_density['gap'] = gap_dict + initial_density['clad'] = clad_dict + initial_density['cool'] = cool_dict + + # Set up libraries to use + temperature = OrderedDict() + sab = OrderedDict() + + # Toggle betweeen MCNP and NNDC data + MCNP = False + + if MCNP: + temperature['fuel_gd'] = 900.0 + temperature['fuel'] = 900.0 + # We approximate temperature of everything as 600K, even though it was + # actually 580K. + temperature['gap'] = 600.0 + temperature['clad'] = 600.0 + temperature['cool'] = 600.0 + else: + temperature['fuel_gd'] = 293.6 + temperature['fuel'] = 293.6 + temperature['gap'] = 293.6 + temperature['clad'] = 293.6 + temperature['cool'] = 293.6 + + sab['cool'] = 'c_H_in_H2O' + + # Set up burnable materials + burn = OrderedDict() + burn['fuel_gd'] = True + burn['fuel'] = True + burn['gap'] = False + burn['clad'] = False + burn['cool'] = False + + return temperature, sab, initial_density, burn + +def segment_pin(n_rings, n_wedges, r_fuel, r_gap, r_clad): + """ Calculates a segmented pin. + + Separates a pin with n_rings and n_wedges. All cells have equal volume. + Pin is centered at origin. + """ + + # Calculate all the volumes of interest + v_fuel = math.pi * r_fuel**2 + v_gap = math.pi * r_gap**2 - v_fuel + v_clad = math.pi * r_clad**2 - v_fuel - v_gap + v_ring = v_fuel / n_rings + v_segment = v_ring / n_wedges + + # Compute ring radiuses + r_rings = np.zeros(n_rings) + + for i in range(n_rings): + r_rings[i] = math.sqrt(1.0/(math.pi) * v_ring * (i+1)) + + # Compute thetas + theta = np.linspace(0, 2*math.pi, n_wedges + 1) + + # Compute surfaces + fuel_rings = [openmc.ZCylinder(x0=0, y0=0, R=r_rings[i]) + for i in range(n_rings)] + + fuel_wedges = [openmc.Plane(A=math.cos(theta[i]), B=math.sin(theta[i])) + for i in range(n_wedges)] + + gap_ring = openmc.ZCylinder(x0=0, y0=0, R=r_gap) + clad_ring = openmc.ZCylinder(x0=0, y0=0, R=r_clad) + + # Create cells + fuel_cells = [] + if n_wedges == 1: + for i in range(n_rings): + cell = openmc.Cell(name='fuel') + if i == 0: + cell.region = -fuel_rings[0] + else: + cell.region = +fuel_rings[i-1] & -fuel_rings[i] + fuel_cells.append(cell) + else: + for i in range(n_rings): + for j in range(n_wedges): + cell = openmc.Cell(name='fuel') + if i == 0: + if j != n_wedges-1: + cell.region = (-fuel_rings[0] + & +fuel_wedges[j] + & -fuel_wedges[j+1]) + else: + cell.region = (-fuel_rings[0] + & +fuel_wedges[j] + & -fuel_wedges[0]) + else: + if j != n_wedges-1: + cell.region = (+fuel_rings[i-1] + & -fuel_rings[i] + & +fuel_wedges[j] + & -fuel_wedges[j+1]) + else: + cell.region = (+fuel_rings[i-1] + & -fuel_rings[i] + & +fuel_wedges[j] + & -fuel_wedges[0]) + fuel_cells.append(cell) + + # Gap ring + gap_cell = openmc.Cell(name='gap') + gap_cell.region = +fuel_rings[-1] & -gap_ring + fuel_cells.append(gap_cell) + + # Clad ring + clad_cell = openmc.Cell(name='clad') + clad_cell.region = +gap_ring & -clad_ring + fuel_cells.append(clad_cell) + + # Moderator + mod_cell = openmc.Cell(name='cool') + mod_cell.region = +clad_ring + fuel_cells.append(mod_cell) + + # Form universe + fuel_u = openmc.Universe() + fuel_u.add_cells(fuel_cells) + + return fuel_u, v_segment, v_gap, v_clad + +def generate_geometry(n_rings, n_wedges): + """ Generates example geometry. + + This function creates the initial geometry, a 9 pin reflective problem. + One pin, containing gadolinium, is discretized into sectors. + + In addition to what one would do with the general OpenMC geometry code, it + is necessary to create a dictionary, volume, that maps a cell ID to a + volume. Further, by naming cells the same as the above materials, the code + can automatically handle the mapping. + + Parameters + ---------- + n_rings : int + Number of rings to generate for the geometry + n_wedges : int + Number of wedges to generate for the geometry + """ + + pitch = 1.26197 + r_fuel = 0.412275 + r_gap = 0.418987 + r_clad = 0.476121 + + n_pin = 3 + + # This table describes the 'fuel' to actual type mapping + # It's not necessary to do it this way. Just adjust the initial conditions + # below. + mapping = ['fuel', 'fuel', 'fuel', + 'fuel', 'fuel_gd', 'fuel', + 'fuel', 'fuel', 'fuel'] + + # Form pin cell + fuel_u, v_segment, v_gap, v_clad = segment_pin(n_rings, n_wedges, r_fuel, r_gap, r_clad) + + # Form lattice + all_water_c = openmc.Cell(name='cool') + all_water_u = openmc.Universe(cells=(all_water_c, )) + + lattice = openmc.RectLattice() + lattice.pitch = [pitch]*2 + lattice.lower_left = [-pitch*n_pin/2, -pitch*n_pin/2] + lattice_array = [[fuel_u for i in range(n_pin)] for j in range(n_pin)] + lattice.universes = lattice_array + lattice.outer = all_water_u + + # Bound universe + x_low = openmc.XPlane(x0=-pitch*n_pin/2, boundary_type='reflective') + x_high = openmc.XPlane(x0=pitch*n_pin/2, boundary_type='reflective') + y_low = openmc.YPlane(y0=-pitch*n_pin/2, boundary_type='reflective') + y_high = openmc.YPlane(y0=pitch*n_pin/2, boundary_type='reflective') + z_low = openmc.ZPlane(z0=-10, boundary_type='reflective') + z_high = openmc.ZPlane(z0=10, boundary_type='reflective') + + # Compute bounding box + lower_left = [-pitch*n_pin/2, -pitch*n_pin/2, -10] + upper_right = [pitch*n_pin/2, pitch*n_pin/2, 10] + + root_c = openmc.Cell(fill=lattice) + root_c.region = (+x_low & -x_high + & +y_low & -y_high + & +z_low & -z_high) + root_u = openmc.Universe(universe_id=0, cells=(root_c, )) + geometry = openmc.Geometry(root_u) + + v_cool = pitch**2 - (v_gap + v_clad + n_rings * n_wedges * v_segment) + + # Store volumes for later usage + volume = {'fuel': v_segment, 'gap':v_gap, 'clad':v_clad, 'cool':v_cool} + + return geometry, volume, mapping, lower_left, upper_right + +def generate_problem(n_rings=5, n_wedges=8): + """ Merges geometry and materials. + + This function initializes the materials for each cell using the dictionaries + provided by generate_initial_number_density. It is assumed a cell named + 'fuel' will have further region differentiation (see mapping). + + Parameters + ---------- + n_rings : int, optional + Number of rings to generate for the geometry + n_wedges : int, optional + Number of wedges to generate for the geometry + """ + + # Get materials dictionary, geometry, and volumes + temperature, sab, initial_density, burn = generate_initial_number_density() + geometry, volume, mapping, lower_left, upper_right = generate_geometry(n_rings, n_wedges) + + # Apply distribmats, fill geometry + cells = geometry.root_universe.get_all_cells() + for cell_id in cells: + cell = cells[cell_id] + if cell.name == 'fuel': + + omc_mats = [] + + for cell_type in mapping: + omc_mat = density_to_mat(initial_density[cell_type]) + + if cell_type in sab: + omc_mat.add_s_alpha_beta(sab[cell_type]) + omc_mat.temperature = temperature[cell_type] + omc_mat.depletable = burn[cell_type] + omc_mat.volume = volume['fuel'] + + omc_mats.append(omc_mat) + + cell.fill = omc_mats + elif cell.name != '': + omc_mat = density_to_mat(initial_density[cell.name]) + + if cell.name in sab: + omc_mat.add_s_alpha_beta(sab[cell.name]) + omc_mat.temperature = temperature[cell.name] + omc_mat.depletable = burn[cell.name] + omc_mat.volume = volume[cell.name] + + cell.fill = omc_mat + + return geometry, lower_left, upper_right From d7f1904e41cb8e00578c476c7ec75a62d9174101 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 21 Feb 2018 10:15:42 -0600 Subject: [PATCH 263/282] Move simple chains into tests directory --- {chains => tests}/chain_simple.xml | 0 {chains => tests}/chain_test.xml | 0 tests/regression_tests/test_deplete_full.py | 2 +- tests/unit_tests/test_deplete_chain.py | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename {chains => tests}/chain_simple.xml (100%) rename {chains => tests}/chain_test.xml (100%) diff --git a/chains/chain_simple.xml b/tests/chain_simple.xml similarity index 100% rename from chains/chain_simple.xml rename to tests/chain_simple.xml diff --git a/chains/chain_test.xml b/tests/chain_test.xml similarity index 100% rename from chains/chain_test.xml rename to tests/chain_test.xml diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index 2286af908..fb31ac0c0 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -43,7 +43,7 @@ def test_full(run_in_tmpdir): settings.verbosity = 3 # Create operator - chain_file = Path(__file__).parents[2] / 'chains' / 'chain_simple.xml' + chain_file = Path(__file__).parents[1] / 'chain_simple.xml' op = openmc.deplete.Operator(geometry, settings, chain_file) op.round_number = True diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 7ac3e2f8d..8fc01b427 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -8,7 +8,7 @@ import numpy as np from openmc.deplete import comm, Chain, reaction_rates, nuclide -_test_filename = str(Path(__file__).parents[2] / 'chains' / 'chain_test.xml') +_test_filename = str(Path(__file__).parents[1] / 'chain_test.xml') def test_init(): From 7ef5174274fb4fb034d0fb710db6e5b5a1756858 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 21 Feb 2018 10:31:57 -0600 Subject: [PATCH 264/282] Move test chain directly into test_deplete_chain --- tests/chain_test.xml | 23 ------------ tests/unit_tests/test_deplete_chain.py | 48 ++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 30 deletions(-) delete mode 100644 tests/chain_test.xml diff --git a/tests/chain_test.xml b/tests/chain_test.xml deleted file mode 100644 index c8c75ad7b..000000000 --- a/tests/chain_test.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - 0.0253 - - A B - 0.0292737 0.002566345 - - - - diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 8fc01b427..f7e7899a3 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -6,9 +6,44 @@ from pathlib import Path import numpy as np from openmc.deplete import comm, Chain, reaction_rates, nuclide +import pytest + +from tests import cdtemp -_test_filename = str(Path(__file__).parents[1] / 'chain_test.xml') +_TEST_CHAIN = """\ + + + + + + + + + + + + + + + + 0.0253 + + A B + 0.0292737 0.002566345 + + + + +""" + + +@pytest.fixture(scope='module') +def simple_chain(): + with cdtemp(): + with open('chain_test.xml', 'w') as fh: + fh.write(_TEST_CHAIN) + yield Chain.from_xml('chain_test.xml') def test_init(): @@ -33,13 +68,13 @@ def test_from_endf(): pass -def test_from_xml(): +def test_from_xml(simple_chain): """Read chain_test.xml and ensure all values are correct.""" # Unfortunately, this routine touches a lot of the code, but most of # the components external to depletion_chain.py are simple storage # types. - chain = Chain.from_xml(_test_filename) + chain = simple_chain # Basic checks assert len(chain) == 3 @@ -125,16 +160,15 @@ def test_export_to_xml(run_in_tmpdir): chain.nuclides = [A, B, C] chain.export_to_xml(filename) - original = open(_test_filename, 'r').read() chain_xml = open(filename, 'r').read() - assert original == chain_xml + assert _TEST_CHAIN == chain_xml -def test_form_matrix(): +def test_form_matrix(simple_chain): """ Using chain_test, and a dummy reaction rate, compute the matrix. """ # Relies on test_from_xml passing. - chain = Chain.from_xml(_test_filename) + chain = simple_chain mats = ["10000", "10001"] nuclides = ["A", "B", "C"] From 8a4cdf39921ed6f5d509a42f87f5165d6a70e5c6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 21 Feb 2018 11:30:27 -0600 Subject: [PATCH 265/282] Fix two broken links in user's guide --- docs/source/usersguide/beginners.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/usersguide/beginners.rst b/docs/source/usersguide/beginners.rst index 22b09a24d..ec37d0825 100644 --- a/docs/source/usersguide/beginners.rst +++ b/docs/source/usersguide/beginners.rst @@ -151,8 +151,8 @@ and `Volume II`_. You may also find it helpful to review the following terms: .. _git: http://git-scm.com/ .. _git tutorials: http://git-scm.com/documentation .. _Reactor Concepts Manual: http://www.tayloredge.com/periodic/trivia/ReactorConcepts.pdf -.. _Volume I: http://energy.gov/sites/prod/files/2013/06/f2/h1019v1.pdf -.. _Volume II: http://energy.gov/sites/prod/files/2013/06/f2/h1019v2.pdf +.. _Volume I: https://www.standards.doe.gov/standards-documents/1000/1019-bhdbk-1993-v1 +.. _Volume II: https://www.standards.doe.gov/standards-documents/1000/1019-bhdbk-1993-v2 .. _OpenMC source code: https://github.com/mit-crpg/openmc .. _GitHub: https://github.com/ .. _bug reports: https://github.com/mit-crpg/openmc/issues From 92697ec06eae659cfd3a801d7bca52efee81cb2d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 21 Feb 2018 15:36:27 -0600 Subject: [PATCH 266/282] Introduce ResultsList class to replace scattered functions --- docs/source/pythonapi/deplete.rst | 2 +- openmc/deplete/__init__.py | 2 +- openmc/deplete/integrator/__init__.py | 1 - openmc/deplete/integrator/cecm.py | 16 +-- openmc/deplete/integrator/predictor.py | 12 +- openmc/deplete/integrator/save_results.py | 42 ------- openmc/deplete/results.py | 93 +++++++++------- openmc/deplete/results_list.py | 105 ++++++++++++++++++ openmc/deplete/utilities.py | 101 ----------------- tests/dummy_operator.py | 42 ++++--- tests/regression_tests/test_deplete_full.py | 10 +- tests/unit_tests/test_deplete_cecm.py | 8 +- tests/unit_tests/test_deplete_integrator.py | 12 +- tests/unit_tests/test_deplete_predictor.py | 8 +- .../test_deplete_resultslist.py} | 28 ++--- 15 files changed, 220 insertions(+), 262 deletions(-) delete mode 100644 openmc/deplete/integrator/save_results.py create mode 100644 openmc/deplete/results_list.py delete mode 100644 openmc/deplete/utilities.py rename tests/{regression_tests/test_deplete_utilities.py => unit_tests/test_deplete_resultslist.py} (62%) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 1d1a1c0ee..61c5dd18c 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -59,6 +59,7 @@ data, such as number densities and reaction rates for each material. OperatorResult ReactionRates Results + ResultsList TransportOperator Each of the integrator functions also relies on a number of "helper" functions @@ -71,4 +72,3 @@ as follows: integrator.CRAM16 integrator.CRAM48 - integrator.save_results diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index 33b6d9af1..e49c4e69c 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -20,5 +20,5 @@ from .operator import * from .reaction_rates import * from .abc import * from .results import * +from .results_list import * from .integrator import * -from .utilities import * diff --git a/openmc/deplete/integrator/__init__.py b/openmc/deplete/integrator/__init__.py index 607650dc6..cf8caffdf 100644 --- a/openmc/deplete/integrator/__init__.py +++ b/openmc/deplete/integrator/__init__.py @@ -8,4 +8,3 @@ The integrator subcomponents. from .cecm import * from .cram import * from .predictor import * -from .save_results import * diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 9a07cd198..61b58d0b9 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -4,7 +4,7 @@ import copy from collections.abc import Iterable from .cram import deplete -from .save_results import save_results +from ..results import Results def cecm(operator, timesteps, power, print_out=True): @@ -51,20 +51,20 @@ def cecm(operator, timesteps, power, print_out=True): for i, (dt, p) in enumerate(zip(timesteps, power)): # Get beginning-of-timestep reaction rates x = [copy.deepcopy(vec)] - results = [operator(x[0], p)] + op_results = [operator(x[0], p)] # Deplete for first half of timestep - x_middle = deplete(chain, x[0], results[0], dt/2, print_out) + x_middle = deplete(chain, x[0], op_results[0], dt/2, print_out) # Get middle-of-timestep reaction rates x.append(x_middle) - results.append(operator(x_middle, p)) + op_results.append(operator(x_middle, p)) # Deplete for full timestep using beginning-of-step materials - x_end = deplete(chain, x[0], results[1], dt, print_out) + x_end = deplete(chain, x[0], op_results[1], dt, print_out) # Create results, write to disk - save_results(operator, x, results, [t, t + dt], i) + Results.save(operator, x, op_results, [t, t + dt], i) # Advance time, update vector t += dt @@ -72,7 +72,7 @@ def cecm(operator, timesteps, power, print_out=True): # Perform one last simulation x = [copy.deepcopy(vec)] - results = [operator(x[0], power[-1])] + op_results = [operator(x[0], power[-1])] # Create results, write to disk - save_results(operator, x, results, [t, t], len(timesteps)) + Results.save(operator, x, op_results, [t, t], len(timesteps)) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 444ca7baa..9be992c16 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -4,7 +4,7 @@ import copy from collections.abc import Iterable from .cram import deplete -from .save_results import save_results +from ..results import Results def predictor(operator, timesteps, power, print_out=True): @@ -46,13 +46,13 @@ def predictor(operator, timesteps, power, print_out=True): for i, (dt, p) in enumerate(zip(timesteps, power)): # Get beginning-of-timestep reaction rates x = [copy.deepcopy(vec)] - results = [operator(x[0], p)] + op_results = [operator(x[0], p)] # Create results, write to disk - save_results(operator, x, results, [t, t + dt], i) + Results.save(operator, x, op_results, [t, t + dt], i) # Deplete for full timestep - x_end = deplete(chain, x[0], results[0], dt, print_out) + x_end = deplete(chain, x[0], op_results[0], dt, print_out) # Advance time, update vector t += dt @@ -60,7 +60,7 @@ def predictor(operator, timesteps, power, print_out=True): # Perform one last simulation x = [copy.deepcopy(vec)] - results = [operator(x[0], power[-1])] + op_results = [operator(x[0], power[-1])] # Create results, write to disk - save_results(operator, x, results, [t, t], len(timesteps)) + Results.save(operator, x, op_results, [t, t], len(timesteps)) diff --git a/openmc/deplete/integrator/save_results.py b/openmc/deplete/integrator/save_results.py deleted file mode 100644 index 98245fdea..000000000 --- a/openmc/deplete/integrator/save_results.py +++ /dev/null @@ -1,42 +0,0 @@ -""" Generic result saving code for integrators. - -""" -from ..results import Results, write_results - - -def save_results(op, x, op_results, t, 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. - step_ind : int - Step index. - - """ - # Get indexing terms - vol_dict, nuc_list, burn_list, full_burn_list = op.get_results_info() - - # Create results - stages = len(x) - 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[i][mat_i][:] - - results.k = [r.k for r in op_results] - results.rates = [r.rates for r in op_results] - results.time = t - - write_results(results, "depletion_results.h5", step_ind) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index b7e5623c7..ab740e61e 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -11,7 +11,6 @@ import h5py from . import comm, have_mpi from .reaction_rates import ReactionRates -from openmc.checkvalue import check_filetype_version _VERSION_RESULTS = (1, 0) @@ -146,6 +145,27 @@ class Results(object): # 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 = {} + + 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 @@ -217,7 +237,7 @@ class Results(object): handle.create_dataset("time", (1, 2), maxshape=(None, 2), dtype='float64') - def to_hdf5(self, handle, index): + def _to_hdf5(self, handle, index): """Converts results object into an hdf5 object. Parameters @@ -338,49 +358,40 @@ class Results(object): return results + @staticmethod + def save(op, x, op_results, t, step_ind): + """Creates and writes depletion results to disk -def write_results(result, filename, step): - """Outputs result to an HDF5 file. + 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. + step_ind : int + Step index. - Parameters - ---------- - result : Results - Object to be stored in a file. - filename : String - Target filename. - step : int - What step is this? + """ + # Get indexing terms + vol_dict, nuc_list, burn_list, full_burn_list = op.get_results_info() - """ - if have_mpi and h5py.get_config().mpi: - kwargs = {'driver': 'mpio', 'comm': comm} - else: - kwargs = {} + # Create results + stages = len(x) + results = Results() + results.allocate(vol_dict, nuc_list, burn_list, full_burn_list, stages) - kwargs['mode'] = "w" if step == 0 else "a" + n_mat = len(burn_list) - with h5py.File(filename, **kwargs) as handle: - result.to_hdf5(handle, step) + for i in range(stages): + for mat_i in range(n_mat): + results[i, mat_i, :] = x[i][mat_i][:] + results.k = [r.k for r in op_results] + results.rates = [r.rates for r in op_results] + results.time = t -def read_results(filename): - """Return a list of Results objects from an HDF5 file. - - Parameters - ---------- - filename : str - The filename to read from. - - Returns - ------- - results : list of openmc.deplete.Results - The result objects. - - """ - 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] - - return [Results.from_hdf5(fh, i) for i in range(n)] + results.export_to_hdf5("depletion_results.h5", step_ind) diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py new file mode 100644 index 000000000..d3d45e955 --- /dev/null +++ b/openmc/deplete/results_list.py @@ -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) + concentration = np.empty_like(self) + + # 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) + rate = np.empty_like(self) + + # 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) + eigenvalue = np.empty_like(self) + + # 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 diff --git a/openmc/deplete/utilities.py b/openmc/deplete/utilities.py deleted file mode 100644 index 59d530546..000000000 --- a/openmc/deplete/utilities.py +++ /dev/null @@ -1,101 +0,0 @@ -"""The utilities module. - -Contains functions that can be used to post-process objects that come out of -the results module. -""" - -import numpy as np - - -def evaluate_single_nuclide(results, mat, nuc): - """Evaluates a single nuclide in a single material from a results list. - - Parameters - ---------- - results : list of results - The results to extract data from. Must be sorted and continuous. - mat : str - Material name to evaluate - nuc : str - Nuclide name to evaluate - - Returns - ------- - time : numpy.ndarray - Time vector - concentration : numpy.ndarray - Total number of atoms in the material - - """ - n_points = len(results) - time = np.zeros(n_points) - concentration = np.zeros(n_points) - - # Evaluate value in each region - for i, result in enumerate(results): - time[i] = result.time[0] - concentration[i] = result[0, mat, nuc] - - return time, concentration - - -def evaluate_reaction_rate(results, mat, nuc, rx): - """Return reaction rate in a single material/nuclide from a results list. - - Parameters - ---------- - results : list of openmc.deplete.Results - The results to extract data from. Must be sorted and continuous. - mat : str - Material name to evaluate - nuc : str - Nuclide name to evaluate - rx : str - Reaction rate to evaluate - - Returns - ------- - time : numpy.ndarray - Time vector. - rate : numpy.ndarray - Reaction rate. - - """ - n_points = len(results) - time = np.zeros(n_points) - rate = np.zeros(n_points) - # Evaluate value in each region - for i, result in enumerate(results): - time[i] = result.time[0] - rate[i] = result.rates[0].get(mat, nuc, rx) * result[0, mat, nuc] - - return time, rate - - -def evaluate_eigenvalue(results): - """Evaluates the eigenvalue from a results list. - - Parameters - ---------- - results : list of openmc.deplete.Results - The results to extract data from. Must be sorted and continuous. - - Returns - ------- - time : numpy.ndarray - Time vector. - eigenvalue : numpy.ndarray - Eigenvalue. - - """ - n_points = len(results) - time = np.zeros(n_points) - eigenvalue = np.zeros(n_points) - - # Evaluate value in each region - for i, result in enumerate(results): - - time[i] = result.time[0] - eigenvalue[i] = result.k[0] - - return time, eigenvalue diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index 706793d93..05fe97a95 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -34,19 +34,15 @@ class DummyOperator(TransportOperator): Returns ------- - k : float - Zero. - rates : ReactionRates - Reaction rates from this simulation. - seed : int - Zero. + openmc.deplete.OperatorResult + Result of transport operator + """ + mats = ["1"] + nuclides = ["1", "2"] + reactions = ["1"] - cell_to_ind = {"1" : 0} - nuc_to_ind = {"1" : 0, "2" : 1} - react_to_ind = {"1" : 0} - - reaction_rates = ReactionRates(cell_to_ind, nuc_to_ind, react_to_ind) + reaction_rates = ReactionRates(mats, nuclides, reactions) reaction_rates[0, 0, 0] = vec[0][0] reaction_rates[0, 1, 0] = vec[0][1] @@ -105,18 +101,18 @@ class DummyOperator(TransportOperator): return ["1", "2"] @property - def burn_list(self): + def local_mats(self): """ - burn_list : list of str - A list of all cell IDs to be burned. Used for sorting the simulation. + local_mats : list of str + A list of all material IDs to be burned. Used for sorting the simulation. """ return ["1"] @property - def mat_tally_ind(self): + def burnable_mats(self): """Maps cell name to index in global geometry.""" - return {"1": 0} + return self.local_mats @property @@ -125,11 +121,11 @@ class DummyOperator(TransportOperator): reaction_rates : ReactionRates Reaction rates from the last operator step. """ - cell_to_ind = {"1" : 0} - nuc_to_ind = {"1" : 0, "2" : 1} - react_to_ind = {"1" : 0} + mats = ["1"] + nuclides = ["1", "2"] + reactions = ["1"] - return ReactionRates(cell_to_ind, nuc_to_ind, react_to_ind) + return ReactionRates(mats, nuclides, reactions) def initial_condition(self): """Returns initial vector. @@ -153,8 +149,8 @@ class DummyOperator(TransportOperator): 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_dict : OrderedDict of str to int + full_burn_list : OrderedDict of str to int Maps cell name to index in global geometry. - """ - return self.volume, self.nuc_list, self.burn_list, self.mat_tally_ind + """ + return self.volume, self.nuc_list, self.local_mats, self.burnable_mats diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index fb31ac0c0..19e2c7664 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -8,8 +8,6 @@ import numpy as np import openmc from openmc.data import JOULE_PER_EV import openmc.deplete -from openmc.deplete import results -from openmc.deplete import utilities from tests.regression_tests import config from .example_geometry import generate_problem @@ -67,8 +65,8 @@ def test_full(run_in_tmpdir): return # Load the reference/test results - res_test = results.read_results(path_test) - res_ref = results.read_results(path_reference) + res_test = openmc.deplete.ResultsList(path_test) + res_ref = openmc.deplete.ResultsList(path_reference) # Assert same mats for mat in res_ref[0].mat_to_ind: @@ -88,8 +86,8 @@ def test_full(run_in_tmpdir): tol = 1.0e-6 for mat in res_test[0].mat_to_ind: for nuc in res_test[0].nuc_to_ind: - _, y_test = utilities.evaluate_single_nuclide(res_test, mat, nuc) - _, y_old = utilities.evaluate_single_nuclide(res_ref, mat, nuc) + _, y_test = res_test.get_atoms(mat, nuc) + _, y_old = res_ref.get_atoms(mat, nuc) # Test each point correct = True diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py index 6deb6cd3d..466a2eec7 100644 --- a/tests/unit_tests/test_deplete_cecm.py +++ b/tests/unit_tests/test_deplete_cecm.py @@ -5,8 +5,6 @@ These tests integrate a simple test problem described in dummy_geometry.py. from pytest import approx import openmc.deplete -from openmc.deplete import results -from openmc.deplete import utilities from tests import dummy_operator @@ -23,10 +21,10 @@ def test_cecm(run_in_tmpdir): openmc.deplete.cecm(op, dt, power, print_out=False) # Load the files - res = results.read_results(op.output_dir / "depletion_results.h5") + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") - _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") # Mathematica solution s1 = [1.86872629872102, 1.395525772416039] diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index b20990088..a1768625b 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -1,4 +1,4 @@ -"""Tests for integrator.py +"""Tests for saving results It is worth noting that openmc.deplete.integrate is extremely complex, to the point I am unsure if it can be reasonably unit-tested. For the time being, it @@ -11,11 +11,11 @@ import os from unittest.mock import MagicMock import numpy as np -from openmc.deplete import (integrator, ReactionRates, results, comm, +from openmc.deplete import (ReactionRates, Results, ResultsList, comm, OperatorResult) -def test_save_results(run_in_tmpdir): +def test_results_save(run_in_tmpdir): """Test data save module""" stages = 3 @@ -72,11 +72,11 @@ def test_save_results(run_in_tmpdir): op_result1 = [OperatorResult(k, rates) for k, rates in zip(eigvl1, rate1)] op_result2 = [OperatorResult(k, rates) for k, rates in zip(eigvl2, rate2)] - integrator.save_results(op, x1, op_result1, t1, 0) - integrator.save_results(op, x2, op_result2, t2, 1) + Results.save(op, x1, op_result1, t1, 0) + Results.save(op, x2, op_result2, t2, 1) # Load the files - res = results.read_results("depletion_results.h5") + res = ResultsList("depletion_results.h5") for i in range(stages): for mat_i, mat in enumerate(burn_list): diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index 0d283855c..50803e508 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -5,8 +5,6 @@ These tests integrate a simple test problem described in dummy_geometry.py. from pytest import approx import openmc.deplete -from openmc.deplete import results -from openmc.deplete import utilities from tests import dummy_operator @@ -23,10 +21,10 @@ def test_predictor(run_in_tmpdir): openmc.deplete.predictor(op, dt, power, print_out=False) # Load the files - res = results.read_results(op.output_dir / "depletion_results.h5") + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - _, y1 = utilities.evaluate_single_nuclide(res, "1", "1") - _, y2 = utilities.evaluate_single_nuclide(res, "1", "2") + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") # Mathematica solution s1 = [2.46847546272295, 0.986431226850467] diff --git a/tests/regression_tests/test_deplete_utilities.py b/tests/unit_tests/test_deplete_resultslist.py similarity index 62% rename from tests/regression_tests/test_deplete_utilities.py rename to tests/unit_tests/test_deplete_resultslist.py index 82d4d56a4..aad8cd9f6 100644 --- a/tests/regression_tests/test_deplete_utilities.py +++ b/tests/unit_tests/test_deplete_resultslist.py @@ -1,26 +1,22 @@ -""" Tests the utilities classes. - -This also tests the results read/write code. -""" +"""Tests the ResultsList class""" from pathlib import Path import numpy as np import pytest -from openmc.deplete import results -from openmc.deplete import utilities +import openmc.deplete @pytest.fixture def res(): """Load the reference results""" - filename = Path(__file__).with_name('test_reference.h5') - return results.read_results(filename) + filename = Path(__file__).parents[1] / 'regression_tests' / 'test_reference.h5' + return openmc.deplete.ResultsList(filename) -def test_evaluate_single_nuclide(res): - """Tests evaluating single nuclide utility code.""" - t, n = utilities.evaluate_single_nuclide(res, "1", "Xe135") +def test_get_atoms(res): + """Tests evaluating single nuclide concentration.""" + t, n = res.get_atoms("1", "Xe135") t_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] n_ref = [6.6747328233649218e+08, 3.5421791038348462e+14, @@ -29,9 +25,9 @@ def test_evaluate_single_nuclide(res): np.testing.assert_array_equal(t, t_ref) np.testing.assert_array_equal(n, n_ref) -def test_evaluate_reaction_rate(res): - """Tests evaluating reaction rate utility code.""" - t, r = utilities.evaluate_reaction_rate(res, "1", "Xe135", "(n,gamma)") +def test_get_reaction_rate(res): + """Tests evaluating reaction rate.""" + t, r = res.get_reaction_rate("1", "Xe135", "(n,gamma)") t_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] n_ref = np.array([6.6747328233649218e+08, 3.5421791038348462e+14, @@ -43,9 +39,9 @@ def test_evaluate_reaction_rate(res): np.testing.assert_array_equal(r, n_ref * xs_ref) -def test_evaluate_eigenvalue(res): +def test_get_eigenvalue(res): """Tests evaluating eigenvalue.""" - t, k = utilities.evaluate_eigenvalue(res) + t, k = res.get_eigenvalue() t_ref = [0.0, 1296000.0, 2592000.0, 3888000.0] k_ref = [1.181281798790367, 1.1798750921988739, 1.1965943696058159, From 7622a1a39472a64fac950930423b6bedd2b8ff9a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 22 Feb 2018 16:00:32 -0600 Subject: [PATCH 267/282] Add methods on Material to get mass (if volume specified) --- docs/source/pythonapi/data.rst | 1 + openmc/data/data.py | 20 ++++++++++ openmc/deplete/chain.py | 13 +----- openmc/material.py | 64 +++++++++++++++++++++++++++++- tests/unit_tests/test_data_misc.py | 8 ++++ tests/unit_tests/test_material.py | 17 ++++++++ 6 files changed, 110 insertions(+), 13 deletions(-) diff --git a/docs/source/pythonapi/data.rst b/docs/source/pythonapi/data.rst index 3c221906d..52dc5173b 100644 --- a/docs/source/pythonapi/data.rst +++ b/docs/source/pythonapi/data.rst @@ -36,6 +36,7 @@ Core Functions openmc.data.thin openmc.data.water_density openmc.data.write_compact_458_library + openmc.data.zam Angle-Energy Distributions -------------------------- diff --git a/openmc/data/data.py b/openmc/data/data.py index 523ac9769..70bc00bd9 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -313,6 +313,26 @@ def water_density(temperature, pressure=0.1013): return coeff / pi / gamma1_pi +def zam(name): + """Return tuple of (atomic number, mass number, metastable state) + + Parameters + ---------- + name : str + Name of nuclide using GND convention, e.g., 'Am242m1' + + Returns + ------- + 3-tuple of int + Atomic number, mass number, and metastable state + + """ + symbol, A, state = re.match(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)', + name).groups() + 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). diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 23395329e..bcef5fff1 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -40,15 +40,6 @@ _REACTIONS = [ ] -def _get_zai(s): - """Get ZAI value (10000*z + 10*A + metastable state) for sorting purposes""" - symbol, A, state = re.match(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)', s).groups() - Z = openmc.data.ATOMIC_NUMBER[symbol] - A = int(A) - state = int(state[2:]) if state else 0 - return 10000*Z + 10*A + state - - def replace_missing(product, decay_data): """Replace missing product with suitable decay daughter. @@ -197,7 +188,7 @@ class Chain(object): missing_fpy = [] missing_fp = [] - for idx, parent in enumerate(sorted(decay_data, key=_get_zai)): + for idx, parent in enumerate(sorted(decay_data, key=openmc.data.zam)): data = decay_data[parent] nuclide = Nuclide() @@ -290,7 +281,7 @@ class Chain(object): missing_fp.append((parent, E, yield_replace)) nuclide.yield_data[E] = [] - for k in sorted(yields, key=_get_zai): + for k in sorted(yields, key=openmc.data.zam): nuclide.yield_data[E].append((k, yields[k])) # Display warnings diff --git a/openmc/material.py b/openmc/material.py index e409d6536..351e2db27 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -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. """ @@ -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 @@ -687,7 +701,53 @@ 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. Parameters diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py index 433d34adb..7d00b7bc7 100644 --- a/tests/unit_tests/test_data_misc.py +++ b/tests/unit_tests/test_data_misc.py @@ -58,3 +58,11 @@ def test_water_density(): assert dens(300.0, 3.0) == pytest.approx(1e-3/0.100215168e-2, 1e-6) assert dens(300.0, 80.0) == pytest.approx(1e-3/0.971180894e-3, 1e-6) assert dens(500.0, 3.0) == pytest.approx(1e-3/0.120241800e-2, 1e-6) + + +def test_zam(): + assert openmc.data.zam('H1') == (1, 1, 0) + assert openmc.data.zam('Zr90') == (40, 90, 0) + assert openmc.data.zam('Am242') == (95, 242, 0) + assert openmc.data.zam('Am242_m1') == (95, 242, 1) + assert openmc.data.zam('Am242_m10') == (95, 242, 10) diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index 226521541..c251df3a6 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -121,6 +121,23 @@ def test_get_nuclide_atom_densities(uo2): assert density > 0 +def test_mass(): + m = openmc.Material() + m.add_nuclide('Zr90', 1.0, 'wo') + m.add_nuclide('U235', 1.0, 'wo') + m.set_density('g/cm3', 2.0) + m.volume = 10.0 + + assert m.get_mass_density('Zr90') == pytest.approx(1.0) + assert m.get_mass_density('U235') == pytest.approx(1.0) + assert m.get_mass_density() == pytest.approx(2.0) + + assert m.get_mass('Zr90') == pytest.approx(10.0) + assert m.get_mass('U235') == pytest.approx(10.0) + assert m.get_mass() == pytest.approx(20.0) + assert m.fissionable_mass == pytest.approx(10.0) + + def test_materials(run_in_tmpdir): m1 = openmc.Material() m1.add_nuclide('U235', 1.0, 'wo') From ab00421c0eda42adb29a90e49e08230cbebaa9a5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 23 Feb 2018 11:37:55 -0600 Subject: [PATCH 268/282] Add test for Chain.from_endf. Remove tqdm dependence. --- docs/source/conf.py | 2 +- openmc/data/endf.py | 5 +-- openmc/deplete/chain.py | 42 ++++++++++++-------------- setup.py | 2 +- tests/unit_tests/test_deplete_chain.py | 14 +++++++-- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index a2fec39b7..eeecba23e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,7 +26,7 @@ MOCK_MODULES = [ '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', 'tqdm', 'openmoc', + 'matplotlib', 'matplotlib.pyplot', 'openmoc', 'openmc.data.reconstruct' ] sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES) diff --git a/openmc/data/endf.py b/openmc/data/endf.py index 160ab6151..db94e15be 100644 --- a/openmc/data/endf.py +++ b/openmc/data/endf.py @@ -10,6 +10,7 @@ import io import re import os from math import pi +from pathlib import PurePath from collections import OrderedDict from collections.abc import Iterable @@ -299,8 +300,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 = {} diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index bcef5fff1..612ec29cb 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -20,7 +20,6 @@ try: except ImportError: import xml.etree.ElementTree as ET _have_lxml = False -from tqdm import tqdm import scipy.sparse as sp import openmc.data @@ -153,34 +152,31 @@ class Chain(object): chain = cls() # Create dictionary mapping target to filename + print('Processing neutron sub-library files...') reactions = {} - with tqdm(neutron_files) as pbar: - for f in pbar: - pbar.set_description('Processing {}'.format(os.path.basename(f))) - 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 + 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 = {} - with tqdm(decay_files) as pbar: - for f in pbar: - pbar.set_description('Processing {}'.format(os.path.basename(f))) - data = openmc.data.Decay(f) - decay_data[data.nuclide['name']] = data + for f in decay_files: + data = openmc.data.Decay(f) + decay_data[data.nuclide['name']] = data + print('Processing fission product yield sub-library files...') fpy_data = {} - with tqdm(fpy_files) as pbar: - for f in pbar: - pbar.set_description('Processing {}'.format(os.path.basename(f))) - data = openmc.data.FissionProductYields(f) - fpy_data[data.nuclide['name']] = data + for f in fpy_files: + data = openmc.data.FissionProductYields(f) + fpy_data[data.nuclide['name']] = data print('Creating depletion_chain...') missing_daughter = [] diff --git a/setup.py b/setup.py index ee11f414b..2a42dd65d 100755 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ kwargs = { # Required dependencies 'install_requires': [ 'numpy>=1.9', 'h5py', 'scipy', 'ipython', 'matplotlib', - 'pandas', 'lxml', 'uncertainties', 'tqdm' + 'pandas', 'lxml', 'uncertainties' ], # Optional dependencies diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index f7e7899a3..4ec5415ee 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -5,11 +5,13 @@ import os from pathlib import Path import numpy as np +from openmc.data import zam, ATOMIC_SYMBOL from openmc.deplete import comm, Chain, reaction_rates, nuclide import pytest from tests import cdtemp +_ENDF_DATA = Path(os.environ['OPENMC_ENDF_DATA']) _TEST_CHAIN = """\ @@ -63,9 +65,15 @@ def test_len(): def test_from_endf(): - """Test depletion chain building from ENDF. Empty at the moment until we figure - out a good way to unit-test this.""" - pass + """Test depletion chain building from ENDF files""" + decay_data = (_ENDF_DATA / 'decay').glob('*.endf') + fpy_data = (_ENDF_DATA / 'nfy').glob('*.endf') + neutron_data = (_ENDF_DATA / 'neutrons').glob('*.endf') + chain = Chain.from_endf(decay_data, fpy_data, neutron_data) + + assert len(chain) == len(chain.nuclides) == len(chain.nuclide_dict) == 3821 + for nuc in chain.nuclides: + assert nuc == chain[nuc.name] def test_from_xml(simple_chain): From fc73f195a520bf39772d86fd8d6260f40c5842fc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 23 Feb 2018 11:54:16 -0600 Subject: [PATCH 269/282] Mention mpi4py as optional dependency in docs --- docs/source/usersguide/install.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 9b11d1dce..24bcc7164 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -452,6 +452,11 @@ distributions. .. admonition:: Optional :class: note + `mpi4py `_ + 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 `_ Cython is used for resonance reconstruction for ENDF data converted to :class:`openmc.data.IncidentNeutron`. From 13a167393d9bccb173e0c1e6584cbecf5b6e8ef0 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 23 Feb 2018 14:59:13 -0600 Subject: [PATCH 270/282] Add archive destination in CMakeLists.txt for building static --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 71934a47c..d3673df26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -496,7 +496,8 @@ add_custom_command(TARGET libopenmc POST_BUILD install(TARGETS ${program} libopenmc RUNTIME DESTINATION bin - LIBRARY DESTINATION lib) + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) 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) From 09a63ec3e71734daf1ae88f5be1b286b45396f04 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 24 Feb 2018 15:07:58 -0600 Subject: [PATCH 271/282] Remove blank line that was added --- openmc/material.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openmc/material.py b/openmc/material.py index 351e2db27..5fdcb7689 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -747,7 +747,6 @@ class Material(IDManagerMixin): return self.volume*self.get_mass_density(nuclide) def clone(self, memo=None): - """Create a copy of this material with a new unique ID. Parameters From cc57551bd3746f6e1a26a85f309193031474f27f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 25 Feb 2018 15:22:30 -0600 Subject: [PATCH 272/282] Fix bug in Model.run() --- openmc/model/model.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index d6e6ddce3..72a2c50dd 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -209,9 +209,7 @@ class Model(object): """ 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: From e3d6189cfa163579396377df7a91d0f76a2fc043 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 26 Feb 2018 22:12:14 -0600 Subject: [PATCH 273/282] Make sure Decay.half_life is set, even for stable nuclides --- openmc/data/decay.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openmc/data/decay.py b/openmc/data/decay.py index d83338d02..fa1875939 100644 --- a/openmc/data/decay.py +++ b/openmc/data/decay.py @@ -457,6 +457,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): From 5993a59196119db37ec9b7183887055480a26808 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 27 Feb 2018 07:22:27 -0600 Subject: [PATCH 274/282] Address first round of comments on #976 --- docs/source/io_formats/depletion_results.rst | 10 +++++----- docs/source/pythonapi/deplete.rst | 11 +++++++++++ openmc/data/data.py | 8 ++++++-- openmc/deplete/atom_number.py | 2 +- openmc/deplete/chain.py | 9 +++------ tests/unit_tests/test_data_misc.py | 2 ++ 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/docs/source/io_formats/depletion_results.rst b/docs/source/io_formats/depletion_results.rst index fcbc4fb99..d35e25146 100644 --- a/docs/source/io_formats/depletion_results.rst +++ b/docs/source/io_formats/depletion_results.rst @@ -12,23 +12,23 @@ The current version of the depletion results file format is 1.0. - **version** (*int[2]*) -- Major and minor version of the statepoint file format. -:Datasets: - **eigenvalues** (*float[][]*) -- k-eigenvalues at each +:Datasets: - **eigenvalues** (*double[][]*) -- k-eigenvalues at each time/stage. This array has shape (number of timesteps, number of stages). - - **number** (*float[][][][]*) -- Total number of atoms. This array + - **number** (*double[][][][]*) -- Total number of atoms. This array has shape (number of timesteps, number of stages, number of materials, number of nuclides). - - **reaction rates** (*float[][][][][]*) -- Reaction rates used to + - **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** (*float[][2]*) -- Time in [s] at beginning/end of each + - **time** (*double[][2]*) -- Time in [s] at beginning/end of each step. **/materials//** :Attributes: - **index** (*int*) -- Index used in results for this material - - **volume** (*float*) -- Volume of this material in [cm^3] + - **volume** (*double*) -- Volume of this material in [cm^3] **/nuclides//** diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 61c5dd18c..d4055f0fd 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -27,6 +27,17 @@ specific to OpenMC is available using the following class: Operator +When running in parallel using `mpi4py `_, 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 ------------------------------ diff --git a/openmc/data/data.py b/openmc/data/data.py index 70bc00bd9..d0bb65648 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -327,8 +327,12 @@ def zam(name): Atomic number, mass number, and metastable state """ - symbol, A, state = re.match(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)', - name).groups() + try: + symbol, A, state = re.match(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)', + 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) diff --git a/openmc/deplete/atom_number.py b/openmc/deplete/atom_number.py index 9a32dfa3a..b5357280c 100644 --- a/openmc/deplete/atom_number.py +++ b/openmc/deplete/atom_number.py @@ -41,7 +41,7 @@ class AtomNumber(object): n_nuc_burn : int Number of burnable nuclides. n_nuc : int - Number of nuclidess. + Number of nuclides. """ def __init__(self, local_mats, nuclides, volume, n_nuc_burn): diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 612ec29cb..fbe679222 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -55,15 +55,12 @@ def replace_missing(product, decay_data): Replacement for missing product in GND format. """ - - symbol, A, state = re.match(r'([A-Zn][a-z]*)(\d+)((?:_m\d+)?)', - product).groups() - Z = openmc.data.ATOMIC_NUMBER[symbol] - A = int(A) + # Determine atomic number, mass number, and metastable state + Z, A, state = openmc.data.zam(product) + symbol = openmc.data.ATOMIC_SYMBOL[Z] # First check if ground state is available if state: - metastable_state = int(state[2:]) product = '{}{}'.format(symbol, A) # Find isotope with longest half-life diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py index 7d00b7bc7..616744926 100644 --- a/tests/unit_tests/test_data_misc.py +++ b/tests/unit_tests/test_data_misc.py @@ -66,3 +66,5 @@ def test_zam(): assert openmc.data.zam('Am242') == (95, 242, 0) assert openmc.data.zam('Am242_m1') == (95, 242, 1) assert openmc.data.zam('Am242_m10') == (95, 242, 10) + with pytest.raises(ValueError): + openmc.data.zam('garbage') From bedf99d2fea7f92b5558119b1ebc4a6602e2c920 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Thu, 1 Mar 2018 15:56:24 -0500 Subject: [PATCH 275/282] remove print_cmfd and change order in which print_batch_keff is called --- src/output.F90 | 16 +++------------- src/simulation.F90 | 8 +++----- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/output.F90 b/src/output.F90 index 643a28b67..6e21d9143 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -356,9 +356,9 @@ contains integer :: n ! number of active generations ! Determine overall generation and number of active generations - i = overall_generation() + i = overall_generation() - 1 n = i - n_inactive*gen_per_batch - + ! write out information batch and option independent output write(UNIT=OUTPUT_UNIT, FMT='(2X,A9)', ADVANCE='NO') & trim(to_str(current_batch)) // "/" // trim(to_str(gen_per_batch)) @@ -377,16 +377,6 @@ contains write(UNIT=OUTPUT_UNIT, FMT='(23X)', ADVANCE='NO') end if - end subroutine print_batch_keff - -!=============================================================================== -! PRINT_CMFD displays the CMFD related information to output after CMFD is -! executed. Will print blank line if CMFD is not on, to ensure consistent -! formatting of output columns -!=============================================================================== - - subroutine print_cmfd() - ! write out cmfd keff if it is active and other display info if (cmfd_on) then write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & @@ -410,7 +400,7 @@ contains ! next line write(UNIT=OUTPUT_UNIT, FMT=*) - end subroutine print_cmfd + end subroutine print_batch_keff !=============================================================================== ! PRINT_PLOT displays selected options for plotting diff --git a/src/simulation.F90 b/src/simulation.F90 index 91c176b99..701cd191b 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -24,8 +24,7 @@ module simulation use nuclide_header, only: micro_xs, n_nuclides use output, only: header, print_columns, & print_batch_keff, print_generation, print_runtime, & - print_results, print_overlap_check, write_tallies, & - print_cmfd + print_results, print_overlap_check, write_tallies use particle_header, only: Particle use random_lcg, only: set_particle_seed use settings @@ -295,8 +294,6 @@ contains if (master .and. verbosity >= 7) then if (current_gen /= gen_per_batch) then call print_generation() - else - call print_batch_keff() end if end if @@ -339,7 +336,8 @@ contains if (run_mode == MODE_EIGENVALUE) then ! Perform CMFD calculation if on if (cmfd_on) call execute_cmfd() - call print_cmfd() + ! Write batch output + if (master .and. verbosity >= 7) call print_batch_keff() end if ! Check_triggers From a21947ff9804f2b6a7e0be376986ff22c99c6722 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Thu, 1 Mar 2018 16:15:53 -0500 Subject: [PATCH 276/282] Remove trailing whitespace 1 --- src/output.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/output.F90 b/src/output.F90 index 6e21d9143..349d0007e 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -358,7 +358,7 @@ contains ! Determine overall generation and number of active generations i = overall_generation() - 1 n = i - n_inactive*gen_per_batch - + ! write out information batch and option independent output write(UNIT=OUTPUT_UNIT, FMT='(2X,A9)', ADVANCE='NO') & trim(to_str(current_batch)) // "/" // trim(to_str(gen_per_batch)) From a96a192cd535429d0d38efb148fca60992935ffe Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Thu, 1 Mar 2018 16:34:16 -0500 Subject: [PATCH 277/282] Remove trailing whitespace 2 --- src/output.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/output.F90 b/src/output.F90 index 349d0007e..908f69814 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -376,7 +376,7 @@ contains else write(UNIT=OUTPUT_UNIT, FMT='(23X)', ADVANCE='NO') end if - + ! write out cmfd keff if it is active and other display info if (cmfd_on) then write(UNIT=OUTPUT_UNIT, FMT='(3X, F8.5)', ADVANCE='NO') & From 7ee08d5280c3195ba74102027f127f6ac18f26cb Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 1 Mar 2018 23:08:43 -0600 Subject: [PATCH 278/282] Add gnd_name function --- docs/source/pythonapi/data.rst | 1 + openmc/data/data.py | 26 +++++++++++++++++++++++++- openmc/data/endf.py | 13 +++++-------- openmc/material.py | 2 +- tests/unit_tests/test_data_misc.py | 8 ++++++++ 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/docs/source/pythonapi/data.rst b/docs/source/pythonapi/data.rst index 52dc5173b..7feaa8d60 100644 --- a/docs/source/pythonapi/data.rst +++ b/docs/source/pythonapi/data.rst @@ -32,6 +32,7 @@ Core Functions :template: myfunction.rst openmc.data.atomic_mass + openmc.data.gnd_name openmc.data.linearize openmc.data.thin openmc.data.water_density diff --git a/openmc/data/data.py b/openmc/data/data.py index d0bb65648..fd1329961 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -313,13 +313,37 @@ 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., 'Am242m1' + Name of nuclide using GND convention, e.g., 'Am242_m1' Returns ------- diff --git a/openmc/data/endf.py b/openmc/data/endf.py index db94e15be..c44c66be0 100644 --- a/openmc/data/endf.py +++ b/openmc/data/endf.py @@ -17,7 +17,7 @@ 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 @@ -249,6 +249,7 @@ def get_tab2_record(file_obj): return params, Tabulated2D(breakpoints, interpolation) + def get_evaluations(filename): """Return a list of all evaluations within an ENDF file. @@ -424,13 +425,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): diff --git a/openmc/material.py b/openmc/material.py index 5fdcb7689..d52d8a27b 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -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 diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py index 616744926..04ca0f103 100644 --- a/tests/unit_tests/test_data_misc.py +++ b/tests/unit_tests/test_data_misc.py @@ -60,6 +60,14 @@ def test_water_density(): assert dens(500.0, 3.0) == pytest.approx(1e-3/0.120241800e-2, 1e-6) +def test_gnd_name(): + assert openmc.data.gnd_name(1, 1) == 'H1' + assert openmc.data.gnd_name(40, 90) == ('Zr90') + assert openmc.data.gnd_name(95, 242, 0) == ('Am242') + assert openmc.data.gnd_name(95, 242, 1) == ('Am242_m1') + assert openmc.data.gnd_name(95, 242, 10) == ('Am242_m10') + + def test_zam(): assert openmc.data.zam('H1') == (1, 1, 0) assert openmc.data.zam('Zr90') == (40, 90, 0) From b9ff205090520b718bbec7a26a07fa7d9361374a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 1 Mar 2018 23:09:10 -0600 Subject: [PATCH 279/282] Don't put free neutron in depletion chain --- openmc/deplete/chain.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index fbe679222..1826ca9ca 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -59,6 +59,10 @@ def replace_missing(product, decay_data): 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) @@ -167,6 +171,9 @@ class Chain(object): 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...') From efb264da605b1b818e1e62b62decdbe10e8fa03d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Mar 2018 06:20:16 -0600 Subject: [PATCH 280/282] Fix depletion chain unit test --- tests/unit_tests/test_deplete_chain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 4ec5415ee..1fe83ad98 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -71,7 +71,7 @@ def test_from_endf(): neutron_data = (_ENDF_DATA / 'neutrons').glob('*.endf') chain = Chain.from_endf(decay_data, fpy_data, neutron_data) - assert len(chain) == len(chain.nuclides) == len(chain.nuclide_dict) == 3821 + assert len(chain) == len(chain.nuclides) == len(chain.nuclide_dict) == 3820 for nuc in chain.nuclides: assert nuc == chain[nuc.name] From 3120e15ad45aa203607705132f914169f1282b1b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 5 Mar 2018 19:57:34 -0600 Subject: [PATCH 281/282] Avoid segfault when nuclide is present in material that's not used --- src/input_xml.F90 | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 34a2fdd71..8c3669b2e 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -4265,12 +4265,16 @@ contains ! Show which nuclide results in lowest energy for neutron transport do i = 1, size(nuclides) - if (nuclides(i) % grid(1) % energy(size(nuclides(i) % grid(1) % energy)) & - == energy_max_neutron) then - call write_message("Maximum neutron transport energy: " // & - trim(to_str(energy_max_neutron)) // " eV for " // & - trim(adjustl(nuclides(i) % name)), 7) - exit + ! If a nuclide is present in a material that's not used in the model, its + ! grid has not been allocated + if (size(nuclides(i) % grid) > 0) then + if (nuclides(i) % grid(1) % energy(size(nuclides(i) % grid(1) % energy)) & + == energy_max_neutron) then + call write_message("Maximum neutron transport energy: " // & + trim(to_str(energy_max_neutron)) // " eV for " // & + trim(adjustl(nuclides(i) % name)), 7) + exit + end if end if end do From 3a90b147be78d57e0e795336740eb183295589e3 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Mon, 5 Mar 2018 22:40:16 -0500 Subject: [PATCH 282/282] redefine i as current_batch*gen_per_batch --- src/output.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/output.F90 b/src/output.F90 index 908f69814..25cb84d58 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -356,7 +356,7 @@ contains integer :: n ! number of active generations ! Determine overall generation and number of active generations - i = overall_generation() - 1 + i = current_batch*gen_per_batch n = i - n_inactive*gen_per_batch ! write out information batch and option independent output