From e8423ceca4d7670e36c39dffd364240758e2354f Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Sun, 21 Jan 2018 19:46:55 +0000 Subject: [PATCH 001/361] single-threaded original exec with tally-opts --- CMakeLists.txt | 8 ++++---- src/simulation.F90 | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5150745b..f0395d69f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ endif() # Command line options #=============================================================================== -option(openmp "Enable shared-memory parallelism with OpenMP" ON) +option(openmp "Enable shared-memory parallelism with OpenMP" OFF) option(profile "Compile with profiling flags" OFF) option(debug "Compile with debug flags" OFF) option(optimize "Turn on all compiler optimization flags" OFF) @@ -116,12 +116,12 @@ 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 -g) if(debug) list(REMOVE_ITEM f90flags -O2) - list(APPEND f90flags -g -Wall -Wno-unused-dummy-argument -pedantic + list(APPEND f90flags -g -enable-checking -fbacktrace -Wall -Wno-unused-dummy-argument -pedantic -fbounds-check -ffpe-trap=invalid,overflow,underflow) - list(APPEND ldflags -g) + list(APPEND ldflags -g -v -da -Q) endif() if(profile) list(APPEND f90flags -pg) diff --git a/src/simulation.F90 b/src/simulation.F90 index ef29e7832..e86ea960f 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -498,9 +498,9 @@ 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) + !n = size(tallies(i) % obj % results) + !call MPI_BCAST(tallies(i) % obj % results, n, MPI_DOUBLE, 0, & + ! mpi_intracomm, mpi_err) end do end if From 281d6a6aa4be11a0c87959983a0fcfc41be57507 Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Sat, 27 Jan 2018 17:33:53 +0000 Subject: [PATCH 002/361] adding shared-memory --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f0395d69f..787fb1b07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,10 +30,10 @@ endif() # Command line options #=============================================================================== -option(openmp "Enable shared-memory parallelism with OpenMP" OFF) +option(openmp "Enable shared-memory parallelism with OpenMP" ON) option(profile "Compile with profiling flags" OFF) option(debug "Compile with debug flags" OFF) -option(optimize "Turn on all compiler optimization flags" OFF) +option(optimize "Turn on all compiler optimization flags" ON) option(coverage "Compile with coverage analysis flags" OFF) option(mpif08 "Use Fortran 2008 MPI interface" OFF) From 22efc11d8f98355665f1e038cf039a2e4c6b8183 Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Sun, 28 Jan 2018 02:40:20 +0000 Subject: [PATCH 003/361] updated CMakelist --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 787fb1b07..409a1b52d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ endif() option(openmp "Enable shared-memory parallelism with OpenMP" ON) option(profile "Compile with profiling flags" OFF) option(debug "Compile with debug flags" OFF) -option(optimize "Turn on all compiler optimization flags" ON) +option(optimize "Turn on all compiler optimization flags" OFF) option(coverage "Compile with coverage analysis flags" OFF) option(mpif08 "Use Fortran 2008 MPI interface" OFF) From 42df6d37a6718645d60771b7650acf54111a611f Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 3 Feb 2018 16:27:33 -0500 Subject: [PATCH 004/361] Begin implementing C++ Cells; remove F90 Cell id --- CMakeLists.txt | 2 + src/api.F90 | 2 +- src/cell.cpp | 88 ++++++++++++++++++++++++ src/cell.h | 70 +++++++++++++++++++ src/error.h | 6 +- src/geometry.F90 | 6 +- src/geometry_header.F90 | 50 +++++++++++++- src/hdf5_interface.h | 12 ++-- src/input_xml.F90 | 53 +++++++------- src/output.F90 | 6 +- src/plot.F90 | 2 +- src/summary.F90 | 4 +- src/surface.h | 3 + src/tallies/tally_filter_cell.F90 | 4 +- src/tallies/tally_filter_cellborn.F90 | 4 +- src/tallies/tally_filter_cellfrom.F90 | 4 +- src/tallies/tally_filter_distribcell.F90 | 6 +- src/volume_calc.F90 | 3 +- src/xml_interface.h | 4 +- 19 files changed, 270 insertions(+), 59 deletions(-) create mode 100644 src/cell.cpp create mode 100644 src/cell.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f5b2168f..c1228c493 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,6 +435,8 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/trigger_header.F90 ) set(LIBOPENMC_CXX_SRC + src/cell.cpp + src/cell.h src/error.h src/hdf5_interface.h src/random_lcg.cpp diff --git a/src/api.F90 b/src/api.F90 index f07e5e38a..de44fb252 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -205,7 +205,7 @@ contains if (found) then if (rtype == 1) then - id = cells(p % coord(p % n_coord) % cell) % id + id = cells(p % coord(p % n_coord) % cell) % id() elseif (rtype == 2) then if (p % material == MATERIAL_VOID) then id = 0 diff --git a/src/cell.cpp b/src/cell.cpp new file mode 100644 index 000000000..21713471a --- /dev/null +++ b/src/cell.cpp @@ -0,0 +1,88 @@ +#include "cell.h" + +#include + +#include "error.h" +#include "hdf5_interface.h" +#include "xml_interface.h" + +//TODO: remove this include +#include + + +namespace openmc { + +//============================================================================== +// Cell implementation +//============================================================================== + +Cell::Cell(pugi::xml_node cell_node) +{ + if (check_for_node(cell_node, "id")) { + id = stoi(get_node_value(cell_node, "id")); + } else { + fatal_error("Must specify id of cell in geometry XML file."); + } + + //TODO: don't automatically lowercase cell and surface names + if (check_for_node(cell_node, "name")) { + name = get_node_value(cell_node, "name"); + } +} + +void +Cell::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(surf_group, "boundary_type", "transmission"); + break; + case BC_VACUUM : + write_string(surf_group, "boundary_type", "vacuum"); + break; + case BC_REFLECT : + write_string(surf_group, "boundary_type", "reflective"); + break; + case BC_PERIODIC : + write_string(surf_group, "boundary_type", "periodic"); + break; + } + + if (!name.empty()) { + write_string(surf_group, "name", name); + } + + to_hdf5_inner(surf_group); + + close_group(surf_group); +*/ +} + +//============================================================================== + +extern "C" void +read_cells(pugi::xml_node *node) +{ + // Count the number of cells. + for (pugi::xml_node cell_node: node->children("cell")) {n_cells++;} + if (n_cells == 0) { + fatal_error("No cells found in geometry.xml!"); + } + + // Allocate the vector of Cells. + cells_c.reserve(n_cells); + + // Loop over XML cell elements and populate the array. + for (pugi::xml_node cell_node: node->children("cell")) { + cells_c.push_back(Cell(cell_node)); + } + +} + +} // namespace openmc diff --git a/src/cell.h b/src/cell.h new file mode 100644 index 000000000..90ca923e3 --- /dev/null +++ b/src/cell.h @@ -0,0 +1,70 @@ +#ifndef CELL_H +#define CELL_H + +#include +#include +#include +#include + +#include "hdf5.h" +#include "pugixml/pugixml.hpp" + + +namespace openmc { + +//============================================================================== +// Module constants +//============================================================================== + +//============================================================================== +// Global variables +//============================================================================== + +// Braces force n_cells to be defined here, not just declared. +extern "C" {int32_t n_cells {0};} + +class Cell; +//Cell *cells_c; +std::vector cells_c; + +std::map cell_dict; + +//============================================================================== +//! A geometry primitive that links surfaces, universes, and materials +//============================================================================== + +class Cell +{ +public: + int32_t id; //!< Unique ID + std::string name{""}; //!< User-defined name + + explicit Cell(pugi::xml_node cell_node); + + //! Write all information needed to reconstruct the cell to an HDF5 group. + //! @param group_id An HDF5 group id. + void to_hdf5(hid_t group_id) const; +}; + +//============================================================================== +// Fortran compatibility functions +//============================================================================== + +extern "C" void read_cells(pugi::xml_node *node); + +extern "C" Cell* cell_pointer(int cell_ind) {return &cells_c[cell_ind];} + +extern "C" int32_t cell_id(Cell *c) {return c->id;} + +extern "C" void cell_set_id(Cell *c, int32_t id) {c->id = id;} + +//extern "C" void free_memory_cells_c() +//{ +// delete cells_c; +// cells_c = nullptr; +// n_cells = 0; +// cell_dict.clear(); +//} + +} // namespace openmc +#endif // CELL_H diff --git a/src/error.h b/src/error.h index 4c3373b3e..eedf9c8f5 100644 --- a/src/error.h +++ b/src/error.h @@ -12,19 +12,19 @@ namespace openmc { extern "C" void fatal_error_from_c(const char *message, int message_len); -void fatal_error(const char *message) +inline void fatal_error(const char *message) { fatal_error_from_c(message, strlen(message)); } -void fatal_error(const std::string &message) +inline void fatal_error(const std::string &message) { fatal_error_from_c(message.c_str(), message.length()); } -void fatal_error(const std::stringstream &message) +inline void fatal_error(const std::stringstream &message) { std::string out {message.str()}; fatal_error_from_c(out.c_str(), out.length()); diff --git a/src/geometry.F90 b/src/geometry.F90 index 0bacd7b5d..630c57c04 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -166,8 +166,8 @@ contains ! the particle should only be contained in one cell per level if (index_cell /= p % coord(j) % cell) then call fatal_error("Overlapping cells detected: " & - &// trim(to_str(cells(index_cell) % id)) // ", " & - &// trim(to_str(cells(p % coord(j) % cell) % id)) & + &// trim(to_str(cells(index_cell) % id())) // ", " & + &// trim(to_str(cells(p % coord(j) % cell) % id())) & &// " on universe " // trim(to_str(univ % id))) end if @@ -236,7 +236,7 @@ contains ! Show cell information on trace if (verbosity >= 10 .or. trace) then call write_message(" Entering cell " // trim(to_str(& - cells(i_cell) % id))) + cells(i_cell) % id()))) end if found = .true. diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index e71916d09..b938dddec 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -14,6 +14,29 @@ module geometry_header implicit none + interface + function cell_pointer_c(cell_ind) bind(C, name='cell_pointer') result(ptr) + use ISO_C_BINDING + implicit none + integer(C_INT), intent(in), value :: cell_ind + type(C_PTR) :: ptr + end function cell_pointer_c + + function cell_id_c(cell_ptr) bind(C, name='cell_id') result(id) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: cell_ptr + integer(C_INT32_T) :: id + end function cell_id_c + + subroutine cell_set_id_c(cell_ptr, id) bind(C, name='cell_set_id') + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: cell_ptr + integer(C_INT32_T), intent(in), value :: id + end subroutine cell_set_id_c + end interface + !=============================================================================== ! UNIVERSE defines a geometry that fills all phase space !=============================================================================== @@ -125,7 +148,8 @@ module geometry_header !=============================================================================== type Cell - integer :: id ! Unique ID + type(C_PTR) :: ptr + character(len=104) :: name = "" ! User-defined name integer :: type ! Type of cell (normal, universe, ! lattice) @@ -154,6 +178,12 @@ module geometry_header real(8), allocatable :: translation(:) real(8), allocatable :: rotation(:) real(8), allocatable :: rotation_matrix(:,:) + + contains + + procedure :: id => cell_id + procedure :: set_id => cell_set_id + end type Cell ! array index of the root universe @@ -341,6 +371,20 @@ contains end if end function get_local_hex +!=============================================================================== + + function cell_id(this) result(id) + class(Cell), intent(in) :: this + integer(C_INT32_T) :: id + id = cell_id_c(this % ptr) + end function cell_id + + subroutine cell_set_id(this, id) + class(Cell), intent(in) :: this + integer(C_INT32_T), intent(in) :: id + call cell_set_id_c(this % ptr, id) + end subroutine cell_set_id + !=============================================================================== ! GET_TEMPERATURES returns a list of temperatures that each nuclide/S(a,b) table ! appears at in the model. Later, this list is used to determine the actual @@ -514,7 +558,7 @@ contains integer(C_INT) :: err if (index >= 1 .and. index <= size(cells)) then - id = cells(index) % id + id = cells(index) % id() err = 0 else err = E_OUT_OF_BOUNDS @@ -577,7 +621,7 @@ contains integer(C_INT) :: err if (index >= 1 .and. index <= n_cells) then - cells(index) % id = id + call cells(index) % set_id(id) call cell_dict % set(id, index) err = 0 else diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 7ec9ada9b..89dce7ab2 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -12,7 +12,7 @@ namespace openmc { -hid_t +inline hid_t create_group(hid_t parent_id, char const *name) { hid_t out = H5Gcreate(parent_id, name, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); @@ -26,14 +26,14 @@ create_group(hid_t parent_id, char const *name) } -hid_t +inline hid_t create_group(hid_t parent_id, const std::string &name) { return create_group(parent_id, name.c_str()); } -void +inline void close_group(hid_t group_id) { herr_t err = H5Gclose(group_id); @@ -43,7 +43,7 @@ close_group(hid_t group_id) } -template void +template inline void write_double_1D(hid_t group_id, char const *name, std::array &buffer) { @@ -61,7 +61,7 @@ write_double_1D(hid_t group_id, char const *name, } -void +inline void write_string(hid_t group_id, char const *name, char const *buffer) { size_t buffer_len = strlen(buffer); @@ -81,7 +81,7 @@ write_string(hid_t group_id, char const *name, char const *buffer) } -void +inline void write_string(hid_t group_id, char const *name, const std::string &buffer) { write_string(group_id, name, buffer.c_str()); diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 0b7790a51..a9d687400 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -49,11 +49,17 @@ module input_xml save interface - subroutine read_surfaces(node_ptr) bind(C, name='read_surfaces') + subroutine read_surfaces(node_ptr) bind(C) use ISO_C_BINDING implicit none type(C_PTR) :: node_ptr end subroutine read_surfaces + + subroutine read_cells(node_ptr) bind(C) + use ISO_C_BINDING + implicit none + type(C_PTR) :: node_ptr + end subroutine read_cells end interface contains @@ -989,6 +995,8 @@ contains ! ========================================================================== ! READ CELLS FROM GEOMETRY.XML + call read_cells(root % ptr) + ! Get pointer to list of XML call get_node_list(root, "cell", node_cell_list) @@ -1012,6 +1020,8 @@ contains do i = 1, n_cells c => cells(i) + c % ptr = cell_pointer_c(i - 1) + ! Initialize distribcell instances and distribcell index c % instances = 0 c % distribcell_index = NONE @@ -1019,13 +1029,6 @@ contains ! Get pointer to i-th cell node node_cell = node_cell_list(i) - ! Copy data into cells - if (check_for_node(node_cell, "id")) then - call get_node_value(node_cell, "id", c % id) - else - call fatal_error("Must specify id of cell in geometry XML file.") - end if - ! Copy cell name if (check_for_node(node_cell, "name")) then call get_node_value(node_cell, "name", c % name) @@ -1045,9 +1048,9 @@ contains end if ! Check to make sure 'id' hasn't been used - if (cell_dict % has(c % id)) then + if (cell_dict % has(c % id())) then call fatal_error("Two or more cells use the same unique ID: " & - // to_str(c % id)) + // to_str(c % id())) end if ! Read material @@ -1069,7 +1072,7 @@ contains ! Check for error if (c % material(j) == ERROR_INT) then call fatal_error("Invalid material specified on cell " & - // to_str(c % id)) + // to_str(c % id())) end if end select end do @@ -1089,7 +1092,7 @@ contains ! Check to make sure that either material or fill was specified if (c % material(1) == NONE .and. c % fill == NONE) then call fatal_error("Neither material nor fill was specified for cell " & - // trim(to_str(c % id))) + // trim(to_str(c % id()))) end if ! Check to make sure that both material and fill haven't been @@ -1128,7 +1131,7 @@ contains end do ! Use shunting-yard algorithm to determine RPN for surface algorithm - call generate_rpn(c%id, tokens, rpn) + call generate_rpn(c%id(), tokens, rpn) ! Copy region spec and RPN form to cell arrays allocate(c % region(tokens%size())) @@ -1155,14 +1158,14 @@ contains ! another universe if (c % fill == NONE) then call fatal_error("Cannot apply a rotation to cell " // trim(to_str(& - &c % id)) // " because it is not filled with another universe") + &c % id())) // " because it is not filled with another universe") end if ! Read number of rotation parameters n = node_word_count(node_cell, "rotation") if (n /= 3) then call fatal_error("Incorrect number of rotation parameters on cell " & - // to_str(c % id)) + // to_str(c % id())) end if ! Copy rotation angles in x,y,z directions @@ -1190,7 +1193,7 @@ contains ! another universe if (c % fill == NONE) then call fatal_error("Cannot apply a translation to cell " & - // trim(to_str(c % id)) // " because it is not filled with & + // trim(to_str(c % id())) // " because it is not filled with & &another universe") end if @@ -1198,7 +1201,7 @@ contains n = node_word_count(node_cell, "translation") if (n /= 3) then call fatal_error("Incorrect number of translation parameters on & - &cell " // to_str(c % id)) + &cell " // to_str(c % id())) end if ! Copy translation vector @@ -1214,7 +1217,7 @@ contains if (n > 0) then ! Make sure this is a "normal" cell. if (c % material(1) == NONE) call fatal_error("Cell " & - // trim(to_str(c % id)) // " was specified with a temperature & + // trim(to_str(c % id())) // " was specified with a temperature & &but no material. Temperature specification is only valid for & &cells filled with a material.") @@ -1225,7 +1228,7 @@ contains ! Make sure all temperatues are positive do j = 1, size(c % sqrtkT) if (c % sqrtkT(j) < ZERO) call fatal_error("Cell " & - // trim(to_str(c % id)) // " was specified with a negative & + // trim(to_str(c % id())) // " was specified with a negative & &temperature. All cell temperatures must be non-negative.") end do @@ -1241,7 +1244,7 @@ contains end if ! Add cell to dictionary - call cell_dict % set(c % id, i) + call cell_dict % set(c % id(), i) ! For cells, we also need to check if there's a new universe -- ! also for every cell add 1 to the count of cells for the @@ -4408,7 +4411,7 @@ contains c % universe = universe_dict % get(id) else call fatal_error("Could not find universe " // trim(to_str(id)) & - &// " specified on cell " // trim(to_str(c % id))) + &// " specified on cell " // trim(to_str(c % id()))) end if ! ======================================================================= @@ -4425,7 +4428,7 @@ contains c % fill = lid else call fatal_error("Specified fill " // trim(to_str(id)) // " on cell "& - // trim(to_str(c % id)) // " is neither a universe nor a & + // trim(to_str(c % id())) // " is neither a universe nor a & &lattice.") end if else @@ -4438,7 +4441,7 @@ contains c % material(j) = material_dict % get(id) else call fatal_error("Could not find material " // trim(to_str(id)) & - // " specified on cell " // trim(to_str(c % id))) + // " specified on cell " // trim(to_str(c % id()))) end if end do end if @@ -4551,7 +4554,7 @@ contains associate (c => cells(i)) if (size(c % material) > 1) then if (size(c % material) /= c % instances) then - call fatal_error("Cell " // trim(to_str(c % id)) // " was & + call fatal_error("Cell " // trim(to_str(c % id())) // " was & &specified with " // trim(to_str(size(c % material))) & // " materials but has " // trim(to_str(c % instances)) & // " distributed instances. The number of materials must & @@ -4560,7 +4563,7 @@ contains end if if (size(c % sqrtkT) > 1) then if (size(c % sqrtkT) /= c % instances) then - call fatal_error("Cell " // trim(to_str(c % id)) // " was & + call fatal_error("Cell " // trim(to_str(c % id())) // " was & &specified with " // trim(to_str(size(c % sqrtkT))) & // " temperatures but has " // trim(to_str(c % instances)) & // " distributed instances. The number of temperatures must & diff --git a/src/output.F90 b/src/output.F90 index 316fe9594..c8efcecbc 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -234,7 +234,7 @@ contains ! Print cell for this level if (p % coord(i) % cell /= NONE) then c => cells(p % coord(i) % cell) - write(ou,*) ' Cell = ' // trim(to_str(c % id)) + write(ou,*) ' Cell = ' // trim(to_str(c % id())) end if ! Print universe for this level @@ -633,7 +633,7 @@ contains write(ou,100) 'Cell ID','No. Overlap Checks' do i = 1, n_cells - write(ou,101) cells(i) % id, overlap_check_cnt(i) + write(ou,101) cells(i) % id(), overlap_check_cnt(i) if (overlap_check_cnt(i) < 10) num_sparse = num_sparse + 1 end do write(ou,*) @@ -643,7 +643,7 @@ contains do i = 1, n_cells if (overlap_check_cnt(i) < 10) then j = j + 1 - write(ou,'(1X,A8)', advance='no') trim(to_str(cells(i) % id)) + write(ou,'(1X,A8)', advance='no') trim(to_str(cells(i) % id())) if (modulo(j,8) == 0) write(ou,*) end if end do diff --git a/src/plot.F90 b/src/plot.F90 index 5eb894ce8..d0c024f1a 100644 --- a/src/plot.F90 +++ b/src/plot.F90 @@ -101,7 +101,7 @@ contains else if (pl % color_by == PLOT_COLOR_CELLS) then ! Assign color based on cell rgb = pl % colors(p % coord(j) % cell) % rgb - id = cells(p % coord(j) % cell) % id + id = cells(p % coord(j) % cell) % id() else rgb = 0 id = -1 diff --git a/src/summary.F90 b/src/summary.F90 index 3aeb42178..d21b192a3 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -145,7 +145,7 @@ contains ! Write information on each cell CELL_LOOP: do i = 1, n_cells c => cells(i) - cell_group = create_group(cells_group, "cell " // trim(to_str(c%id))) + cell_group = create_group(cells_group, "cell " // trim(to_str(c%id()))) ! Write name for this cell call write_dataset(cell_group, "name", c%name) @@ -260,7 +260,7 @@ contains if (size(u % cells) > 0) then allocate(cell_ids(size(u % cells))) do j = 1, size(u % cells) - cell_ids(j) = cells(u % cells(j)) % id + cell_ids(j) = cells(u % cells(j)) % id() end do call write_dataset(univ_group, "cells", cell_ids) deallocate(cell_ids) diff --git a/src/surface.h b/src/surface.h index 627a8fbae..33139ace1 100644 --- a/src/surface.h +++ b/src/surface.h @@ -108,6 +108,7 @@ public: //! Write all information needed to reconstruct the surface to an HDF5 group. //! @param group_id An HDF5 group id. + //TODO: this probably needs to include i_periodic for PeriodicSurface void to_hdf5(hid_t group_id) const; protected: @@ -384,6 +385,8 @@ public: // Fortran compatibility functions //============================================================================== +extern "C" void read_surfaces(pugi::xml_node *node); + extern "C" Surface* surface_pointer(int surf_ind) {return surfaces_c[surf_ind];} extern "C" int surface_id(Surface *surf) {return surf->id;} diff --git a/src/tallies/tally_filter_cell.F90 b/src/tallies/tally_filter_cell.F90 index 8d2b93d28..34fba208a 100644 --- a/src/tallies/tally_filter_cell.F90 +++ b/src/tallies/tally_filter_cell.F90 @@ -81,7 +81,7 @@ contains allocate(cell_ids(size(this % cells))) do i = 1, size(this % cells) - cell_ids(i) = cells(this % cells(i)) % id + cell_ids(i) = cells(this % cells(i)) % id() end do call write_dataset(filter_group, "bins", cell_ids) end subroutine to_statepoint_cell @@ -115,7 +115,7 @@ contains integer, intent(in) :: bin character(MAX_LINE_LEN) :: label - label = "Cell " // to_str(cells(this % cells(bin)) % id) + label = "Cell " // to_str(cells(this % cells(bin)) % id()) end function text_label_cell end module tally_filter_cell diff --git a/src/tallies/tally_filter_cellborn.F90 b/src/tallies/tally_filter_cellborn.F90 index 450858b6b..c22c59c6b 100644 --- a/src/tallies/tally_filter_cellborn.F90 +++ b/src/tallies/tally_filter_cellborn.F90 @@ -76,7 +76,7 @@ contains call write_dataset(filter_group, "n_bins", this % n_bins) allocate(cell_ids(size(this % cells))) do i = 1, size(this % cells) - cell_ids(i) = cells(this % cells(i)) % id + cell_ids(i) = cells(this % cells(i)) % id() end do call write_dataset(filter_group, "bins", cell_ids) end subroutine to_statepoint_cellborn @@ -110,7 +110,7 @@ contains integer, intent(in) :: bin character(MAX_LINE_LEN) :: label - label = "Birth Cell " // to_str(cells(this % cells(bin)) % id) + label = "Birth Cell " // to_str(cells(this % cells(bin)) % id()) end function text_label_cellborn end module tally_filter_cellborn diff --git a/src/tallies/tally_filter_cellfrom.F90 b/src/tallies/tally_filter_cellfrom.F90 index 16cb294d5..2a6040c6e 100644 --- a/src/tallies/tally_filter_cellfrom.F90 +++ b/src/tallies/tally_filter_cellfrom.F90 @@ -66,7 +66,7 @@ contains allocate(cell_ids(size(this % cells))) do i = 1, size(this % cells) - cell_ids(i) = cells(this % cells(i)) % id + cell_ids(i) = cells(this % cells(i)) % id() end do call write_dataset(filter_group, "bins", cell_ids) end subroutine to_statepoint_cell_from @@ -76,7 +76,7 @@ contains integer, intent(in) :: bin character(MAX_LINE_LEN) :: label - label = "Cell from " // to_str(cells(this % cells(bin)) % id) + label = "Cell from " // to_str(cells(this % cells(bin)) % id()) end function text_label_cell_from end module tally_filter_cellfrom diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index 3cb34efe4..310af1cb6 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -90,7 +90,7 @@ contains call write_dataset(filter_group, "type", "distribcell") call write_dataset(filter_group, "n_bins", this % n_bins) - call write_dataset(filter_group, "bins", cells(this % cell) % id) + call write_dataset(filter_group, "bins", cells(this % cell) % id()) end subroutine to_statepoint_distribcell subroutine initialize_distribcell(this) @@ -175,7 +175,7 @@ contains ! geometry stack if (univ % cells(i) == i_cell .and. offset == target_offset) then c => cells(univ % cells(i)) - path = trim(path) // "->c" // to_str(c % id) + path = trim(path) // "->c" // to_str(c % id()) return end if end do @@ -243,7 +243,7 @@ contains cell_index = univ % cells(i) c => cells(cell_index) - path = trim(path) // "->c" // to_str(c%id) + path = trim(path) // "->c" // to_str(c%id()) ! ==================================================================== ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL diff --git a/src/volume_calc.F90 b/src/volume_calc.F90 index 07dc5b418..4a7d0ce23 100644 --- a/src/volume_calc.F90 +++ b/src/volume_calc.F90 @@ -202,7 +202,8 @@ contains elseif (this % domain_type == FILTER_CELL) THEN do level = 1, p % n_coord do i_domain = 1, size(this % domain_id) - if (cells(p % coord(level) % cell) % id == this % domain_id(i_domain)) then + if (cells(p % coord(level) % cell) % id() & + == this % domain_id(i_domain)) then i_material = p % material call check_hit(i_domain, i_material, indices, hits, n_mat) end if diff --git a/src/xml_interface.h b/src/xml_interface.h index 778497562..8dd1cf580 100644 --- a/src/xml_interface.h +++ b/src/xml_interface.h @@ -10,14 +10,14 @@ namespace openmc { -bool +inline bool check_for_node(pugi::xml_node node, const char *name) { return node.attribute(name) || node.child(name); } -std::string +inline std::string 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*. From 3b5c8495e122a8b84e37faf4fc6fa648dad3fd1d Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 3 Feb 2018 21:19:46 -0500 Subject: [PATCH 005/361] Move Cell % universe to C++ --- src/cell.cpp | 42 +++++++++++++++--------------------- src/cell.h | 8 +++++++ src/geometry.F90 | 2 +- src/geometry_header.F90 | 48 +++++++++++++++++++++++++++++++++++++++-- src/hdf5_interface.h | 30 ++++++++++++++++++++++++++ src/input_xml.F90 | 18 ++++------------ src/summary.F90 | 5 ++--- 7 files changed, 108 insertions(+), 45 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index 21713471a..2b2943fc4 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -1,6 +1,7 @@ #include "cell.h" #include +#include #include "error.h" #include "hdf5_interface.h" @@ -28,40 +29,31 @@ Cell::Cell(pugi::xml_node cell_node) if (check_for_node(cell_node, "name")) { name = get_node_value(cell_node, "name"); } + + if (check_for_node(cell_node, "universe")) { + universe = stoi(get_node_value(cell_node, "universe")); + } else { + universe = 0; + } } void -Cell::to_hdf5(hid_t group_id) const +//Cell::to_hdf5(hid_t group_id) const +Cell::to_hdf5(hid_t cell_group) 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(surf_group, "boundary_type", "transmission"); - break; - case BC_VACUUM : - write_string(surf_group, "boundary_type", "vacuum"); - break; - case BC_REFLECT : - write_string(surf_group, "boundary_type", "reflective"); - break; - case BC_PERIODIC : - write_string(surf_group, "boundary_type", "periodic"); - break; - } +// std::string group_name {"surface "}; +// group_name += std::to_string(id); +// +// hid_t surf_group = create_group(group_id, group_name); if (!name.empty()) { - write_string(surf_group, "name", name); + write_string(cell_group, "name", name); } - to_hdf5_inner(surf_group); + //TODO: Lookup universe id in universe_dict + //write_int(cell_group, "universe", universe); - close_group(surf_group); -*/ +// close_group(cell_group); } //============================================================================== diff --git a/src/cell.h b/src/cell.h index 90ca923e3..7aa2d4bb3 100644 --- a/src/cell.h +++ b/src/cell.h @@ -38,6 +38,7 @@ class Cell public: int32_t id; //!< Unique ID std::string name{""}; //!< User-defined name + int32_t universe; //!< Universe # this cell is in explicit Cell(pugi::xml_node cell_node); @@ -58,6 +59,13 @@ extern "C" int32_t cell_id(Cell *c) {return c->id;} extern "C" void cell_set_id(Cell *c, int32_t id) {c->id = id;} +extern "C" int32_t cell_universe(Cell *c) {return c->universe;} + +extern "C" void cell_set_universe(Cell *c, int32_t universe) +{c->universe = universe;} + +extern "C" void cell_to_hdf5(Cell *c, hid_t group) {c->to_hdf5(group);} + //extern "C" void free_memory_cells_c() //{ // delete cells_c; diff --git a/src/geometry.F90 b/src/geometry.F90 index 630c57c04..cdf286c74 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -223,7 +223,7 @@ contains if (use_search_cells) then i_cell = search_cells(i) ! check to make sure search cell is in same universe - if (cells(i_cell) % universe /= i_universe) cycle + if (cells(i_cell) % universe() /= i_universe) cycle else i_cell = universes(i_universe) % cells(i) end if diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index b938dddec..8c900430f 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -1,6 +1,7 @@ module geometry_header use, intrinsic :: ISO_C_BINDING + use hdf5 use algorithm, only: find use constants, only: HALF, TWO, THREE, INFINITY, K_BOLTZMANN, & @@ -35,6 +36,30 @@ module geometry_header type(C_PTR), intent(in), value :: cell_ptr integer(C_INT32_T), intent(in), value :: id end subroutine cell_set_id_c + + function cell_universe_c(cell_ptr) bind(C, name='cell_universe') & + result(universe) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: cell_ptr + integer(C_INT32_T) :: universe + end function cell_universe_c + + subroutine cell_set_universe_c(cell_ptr, universe) & + bind(C, name='cell_set_universe') + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: cell_ptr + integer(C_INT32_T), intent(in), value :: universe + end subroutine cell_set_universe_c + + subroutine cell_to_hdf5_c(cell_ptr, group) bind(C, name='cell_to_hdf5') + use ISO_C_BINDING + use hdf5 + implicit none + type(C_PTR), intent(in), value :: cell_ptr + integer(HID_T), intent(in), value :: group + end subroutine cell_to_hdf5_c end interface !=============================================================================== @@ -150,10 +175,8 @@ module geometry_header type Cell type(C_PTR) :: ptr - character(len=104) :: name = "" ! User-defined name integer :: type ! Type of cell (normal, universe, ! lattice) - integer :: universe ! universe # this cell is in integer :: fill ! universe # filling this cell integer :: instances ! number of instances of this cell in ! the geom @@ -183,6 +206,9 @@ module geometry_header procedure :: id => cell_id procedure :: set_id => cell_set_id + procedure :: universe => cell_universe + procedure :: set_universe => cell_set_universe + procedure :: to_hdf5 => cell_to_hdf5 end type Cell @@ -385,6 +411,24 @@ contains call cell_set_id_c(this % ptr, id) end subroutine cell_set_id + function cell_universe(this) result(universe) + class(Cell), intent(in) :: this + integer(C_INT32_T) :: universe + universe = cell_universe_c(this % ptr) + end function cell_universe + + subroutine cell_set_universe(this, universe) + class(Cell), intent(in) :: this + integer(C_INT32_T), intent(in) :: universe + call cell_set_universe_c(this % ptr, universe) + end subroutine cell_set_universe + + subroutine cell_to_hdf5(this, group) + class(Cell), intent(in) :: this + integer(HID_T), intent(in) :: group + call cell_to_hdf5_c(this % ptr, group) + end subroutine cell_to_hdf5 + !=============================================================================== ! GET_TEMPERATURES returns a list of temperatures that each nuclide/S(a,b) table ! appears at in the model. Later, this list is used to determine the actual diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 89dce7ab2..f5a302855 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -43,6 +43,36 @@ close_group(hid_t group_id) } +inline void +write_int(hid_t group_id, char const *name, int32_t buffer) +{ + hid_t dataspace = H5Screate(H5S_SCALAR); + + hid_t dataset = H5Dcreate(group_id, name, H5T_NATIVE_INT32, dataspace, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + + H5Dwrite(dataset, H5T_NATIVE_INT32, H5S_ALL, H5S_ALL, H5P_DEFAULT, &buffer); + + H5Sclose(dataspace); + H5Dclose(dataset); +} + + +//inline void +//write_double(hid_t group_id, char const *name, double buffer) +//{ +// hid_t dataspace = H5Screate(H5S_SCALAR); +// +// 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); +// +// H5Sclose(dataspace); +// H5Dclose(dataset); +//} + + template inline void write_double_1D(hid_t group_id, char const *name, std::array &buffer) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index a9d687400..7679fde2d 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1029,16 +1029,6 @@ contains ! Get pointer to i-th cell node node_cell = node_cell_list(i) - ! Copy cell name - if (check_for_node(node_cell, "name")) then - call get_node_value(node_cell, "name", c % name) - end if - - if (check_for_node(node_cell, "universe")) then - call get_node_value(node_cell, "universe", c % universe) - else - c % universe = 0 - end if if (check_for_node(node_cell, "fill")) then call get_node_value(node_cell, "fill", c % fill) if (find(fill_univ_ids, c % fill) == -1) & @@ -1249,7 +1239,7 @@ contains ! For cells, we also need to check if there's a new universe -- ! also for every cell add 1 to the count of cells for the ! specified universe - univ_id = c % universe + univ_id = c % universe() if (.not. cells_in_univ_dict % has(univ_id)) then n_universes = n_universes + 1 n_cells_in_univ = 1 @@ -1623,7 +1613,7 @@ contains do i = 1, n_cells ! Get index in universes array - j = universe_dict % get(cells(i) % universe) + j = universe_dict % get(cells(i) % universe()) ! Set the first zero entry in the universe cells array to the index in the ! global cells array @@ -4406,9 +4396,9 @@ contains ! ADJUST UNIVERSE INDEX FOR EACH CELL associate (c => cells(i)) - id = c % universe + id = c % universe() if (universe_dict % has(id)) then - c % universe = universe_dict % get(id) + call c % set_universe(universe_dict % get(id)) else call fatal_error("Could not find universe " // trim(to_str(id)) & &// " specified on cell " // trim(to_str(c % id()))) diff --git a/src/summary.F90 b/src/summary.F90 index d21b192a3..3aa851424 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -147,11 +147,10 @@ contains c => cells(i) cell_group = create_group(cells_group, "cell " // trim(to_str(c%id()))) - ! Write name for this cell - call write_dataset(cell_group, "name", c%name) + call c % to_hdf5(cell_group) ! Write universe for this cell - call write_dataset(cell_group, "universe", universes(c%universe)%id) + call write_dataset(cell_group, "universe", universes(c%universe())%id) ! Write information on what fills this cell select case (c%type) From a7c86c0ec6e195ccb8afacc46541900b8f474468 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 4 Feb 2018 17:44:04 -0500 Subject: [PATCH 006/361] Translate cell_contains to C++ --- src/cell.cpp | 322 +++++++++++++++++++++++++++++++++++++++- src/cell.h | 59 ++++---- src/geometry.F90 | 130 +++------------- src/geometry_header.F90 | 18 ++- src/input_xml.F90 | 102 ------------- src/output.F90 | 2 +- src/surface.cpp | 60 ++++++++ src/surface.h | 62 +------- src/surface_header.F90 | 19 --- src/tracking.F90 | 6 +- 10 files changed, 455 insertions(+), 325 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index 2b2943fc4..8d3cc832e 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -1,10 +1,14 @@ #include "cell.h" +#include +#include +#include #include #include #include "error.h" #include "hdf5_interface.h" +#include "surface.h" #include "xml_interface.h" //TODO: remove this include @@ -13,6 +17,178 @@ namespace openmc { +//============================================================================== +// Constants +//============================================================================== + +constexpr int32_t OP_LEFT_PAREN {std::numeric_limits::max()}; +constexpr int32_t OP_RIGHT_PAREN {std::numeric_limits::max() - 1}; +constexpr int32_t OP_COMPLEMENT {std::numeric_limits::max() - 2}; +constexpr int32_t OP_INTERSECTION {std::numeric_limits::max() - 3}; +constexpr int32_t OP_UNION {std::numeric_limits::max() - 4}; + +//============================================================================== +//! Convert region specification string to integer tokens. +//! +//! The characters (, ), |, and ~ count as separate tokens since they represent +//! operators. +//============================================================================== + +std::vector +tokenize(const std::string region_spec) { + // Check for an empty region_spec first. + if (region_spec.empty()) { + std::vector tokens; + return tokens; + } + + // Make a regex expression that matches halfspace tokens and every operator + // except for intersection (whitespace). + std::string re {"\\+?-?\\d+" // Matches halfspaces like 1, -13, and +42 + "|\\(|\\)" // Matches left and right parentheses + "|\\||~"}; // Matches | and ~ + + // Make another regex that includes whitespce, and use it to make sure there + // are no invalid characters. + { + std::string re_invalid {re + "|\\s+"}; + std::regex re_invalid_ {re_invalid}; + auto words_begin = std::sregex_token_iterator(region_spec.begin(), + region_spec.end(), re_invalid_, -1); + auto words_end = std::sregex_token_iterator(); + for (auto it = words_begin; it != words_end; it++) { + std::string match = it->str(); + if (match.length() > 0) { + std::stringstream err_msg; + err_msg << "Region specification contains invalid character(s): \"" + << match << "\""; + fatal_error(err_msg); + } + } + } + + // Use the regex to parse the string and convert all halfspaces and operators + // (except intersection) into integer tokens. + std::vector tokens; + std::regex re_(re); + auto words_begin = std::sregex_iterator(region_spec.begin(), + region_spec.end(), re_); + auto words_end = std::sregex_iterator(); + for (auto it = words_begin; it != words_end; it++) { + std::regex re_half("\\+?-?\\d+"); + if (std::regex_match(it->str(), re_half)) { + tokens.push_back(stoi(it->str())); + } else if (it->str() == "(") { + tokens.push_back(OP_LEFT_PAREN); + } else if (it->str() == ")") { + tokens.push_back(OP_RIGHT_PAREN); + } else if (it->str() == "|") { + tokens.push_back(OP_UNION); + } else if (it->str() == "~") { + tokens.push_back(OP_COMPLEMENT); + } + } + + // Add in intersection operators where a missing operator is needed. + int i = 0; + while (i < tokens.size()-1) { + bool left_compat {(tokens[i] < OP_UNION) || (tokens[i] == OP_RIGHT_PAREN)}; + bool right_compat {(tokens[i+1] < OP_UNION) + || (tokens[i+1] == OP_LEFT_PAREN) + || (tokens[i+1] == OP_COMPLEMENT)}; + if (left_compat && right_compat) { + tokens.insert(tokens.begin()+i+1, OP_INTERSECTION); + } + i++; + } + + return tokens; +} + +//============================================================================== +//! Convert infix region specification to Reverse Polish Notation (RPN) +//! +//! This function uses the shunting-yard algorithm. +//============================================================================== + +std::vector +generate_rpn(int32_t cell_id, std::vector infix) +{ + std::vector rpn; + std::vector stack; + + for (int32_t token : infix) { + if (token < OP_UNION) { + // If token is not an operator, add it to output + rpn.push_back(token); + + } else if (token < OP_RIGHT_PAREN) { + // Regular operators union, intersection, complement + while (stack.size() > 0) { + int op = stack.back(); + + if (op < OP_RIGHT_PAREN && + ((token == OP_COMPLEMENT && token < op) || + (token != OP_COMPLEMENT && token <= op))) { + // While there is an operator, op, on top of the stack, if the token + // is left-associative and its precedence is less than or equal to + // that of op or if the token is right-associative and its precedence + // is less than that of op, move op to the output queue and push the + // token on to the stack. Note that only complement is + // right-associative. + rpn.push_back(op); + stack.pop_back(); + } else { + break; + } + } + + stack.push_back(token); + + } else if (token == OP_LEFT_PAREN) { + // If the token is a left parenthesis, push it onto the stack + stack.push_back(token); + + } else { + // If the token is a right parenthesis, move operators from the stack to + // the output queue until reaching the left parenthesis. + for (auto it = stack.rbegin(); *it != OP_LEFT_PAREN; it++) { + // If we run out of operators without finding a left parenthesis, it + // means there are mismatched parentheses. + if (it == stack.rend()) { + std::stringstream err_msg; + err_msg << "Mismatched parentheses in region specification for cell " + << cell_id; + fatal_error(err_msg); + } + + rpn.push_back(stack.back()); + stack.pop_back(); + } + + // Pop the left parenthesis. + stack.pop_back(); + } + } + + while (stack.size() > 0) { + int op = stack.back(); + + // If the operator is a parenthesis it is mismatched. + if (op >= OP_RIGHT_PAREN) { + std::stringstream err_msg; + err_msg << "Mismatched parentheses in region specification for cell " + << cell_id; + fatal_error(err_msg); + } + + rpn.push_back(stack.back()); + stack.pop_back(); + } + + return rpn; +} + //============================================================================== // Cell implementation //============================================================================== @@ -35,10 +211,49 @@ Cell::Cell(pugi::xml_node cell_node) } else { universe = 0; } + + std::string region_spec {""}; + if (check_for_node(cell_node, "region")) { + region_spec = get_node_value(cell_node, "region"); + } + + // Get a tokenized representation of the region specification. + region = tokenize(region_spec); + region.shrink_to_fit(); + + // Convert user IDs to surface indices. + // Note that the index has 1 added to it in order to preserve the sign. + for (auto it = region.begin(); it != region.end(); it++) { + if (*it < OP_UNION) { + *it = copysign(surface_dict[abs(*it)]+1, *it); + } + } + + // Convert the infix region spec to RPN. + rpn = generate_rpn(id, region); + rpn.shrink_to_fit(); + + // Check if this is a simple cell. + simple = true; + for (int token : rpn) { + if ((token == OP_COMPLEMENT) || (token == OP_UNION)) { + simple = false; + break; + } + } +} + +bool +Cell::contains(const double xyz[3], const double uvw[3], int on_surface) const +{ + if (simple) { + return contains_simple(xyz, uvw, on_surface); + } else { + return contains_complex(xyz, uvw, on_surface); + } } void -//Cell::to_hdf5(hid_t group_id) const Cell::to_hdf5(hid_t cell_group) const { // std::string group_name {"surface "}; @@ -56,6 +271,79 @@ Cell::to_hdf5(hid_t cell_group) const // close_group(cell_group); } +bool +Cell::contains_simple(const double xyz[3], const double uvw[3], int on_surface) +const +{ + for (int token : rpn) { + if (token < OP_UNION) { + // If the token is not an operator, evaluate the sense of particle with + // respect to the surface and see if the token matches the sense. If the + // particle's surface attribute is set and matches the token, that + // overrides the determination based on sense(). + if (token == on_surface) { + } else if (-token == on_surface) { + return false; + } else { + // Note the off-by-one indexing + bool sense = surfaces_c[abs(token)-1]->sense(xyz, uvw); + if (sense != (token > 0)) {return false;} + } + } + } + return true; +} + +bool +Cell::contains_complex(const double xyz[3], const double uvw[3], int on_surface) +const +{ + // Make a stack of booleans. We don't know how big it needs to be, but we do + // know that rpn.size() is an upper-bound. + bool stack[rpn.size()]; + int i_stack = -1; + + for (int token : rpn) { + // If the token is a binary operator (intersection/union), apply it to + // the last two items on the stack. If the token is a unary operator + // (complement), apply it to the last item on the stack. + if (token == OP_UNION) { + stack[i_stack-1] = stack[i_stack-1] || stack[i_stack]; + i_stack --; + } else if (token == OP_INTERSECTION) { + stack[i_stack-1] = stack[i_stack-1] && stack[i_stack]; + i_stack --; + } else if (token == OP_COMPLEMENT) { + stack[i_stack] = !stack[i_stack]; + } else { + // If the token is not an operator, evaluate the sense of particle with + // respect to the surface and see if the token matches the sense. If the + // particle's surface attribute is set and matches the token, that + // overrides the determination based on sense(). + i_stack ++; + if (token == on_surface) { + stack[i_stack] = true; + } else if (-token == on_surface) { + stack[i_stack] = false; + } else { + // Note the off-by-one indexing + bool sense = surfaces_c[abs(token)-1]->sense(xyz, uvw);; + stack[i_stack] = (sense == (token > 0)); + } + } + } + + if (i_stack == 0) { + // The one remaining bool on the stack indicates whether the particle is + // in the cell. + return stack[i_stack]; + } else { + // This case occurs if there is no region specification since i_stack will + // still be -1. + return true; + } +} + //============================================================================== extern "C" void @@ -74,7 +362,37 @@ read_cells(pugi::xml_node *node) for (pugi::xml_node cell_node: node->children("cell")) { cells_c.push_back(Cell(cell_node)); } - } +//============================================================================== +// Fortran compatibility functions +//============================================================================== + +extern "C" { + Cell* cell_pointer(int cell_ind) {return &cells_c[cell_ind];} + + int32_t cell_id(Cell *c) {return c->id;} + + void cell_set_id(Cell *c, int32_t id) {c->id = id;} + + int32_t cell_universe(Cell *c) {return c->universe;} + + void cell_set_universe(Cell *c, int32_t universe) {c->universe = universe;} + + bool cell_simple(Cell *c) {return c->simple;} + + bool cell_contains(Cell *c, double xyz[3], double uvw[3], int on_surface) + {return c->contains(xyz, uvw, on_surface);} + + void cell_to_hdf5(Cell *c, hid_t group) {c->to_hdf5(group);} +} + +//extern "C" void free_memory_cells_c() +//{ +// delete cells_c; +// cells_c = nullptr; +// n_cells = 0; +// cell_dict.clear(); +//} + } // namespace openmc diff --git a/src/cell.h b/src/cell.h index 7aa2d4bb3..0531c23d0 100644 --- a/src/cell.h +++ b/src/cell.h @@ -40,39 +40,44 @@ public: std::string name{""}; //!< User-defined name int32_t universe; //!< Universe # this cell is in + //! Definition of spatial region as Boolean expression of half-spaces + std::vector region; + //! Reverse Polish notation for region expression + std::vector rpn; + bool simple; //!< Does the region contain only intersections? + explicit Cell(pugi::xml_node cell_node); + //! Determine if a cell contains the particle at a given location. + //! + //! The bounds of the cell are detemined by a logical expression involving + //! surface half-spaces. At initialization, the expression was converted + //! to RPN notation. + //! + //! The function is split into two cases, one for simple cells (those + //! involving only the intersection of half-spaces) and one for complex cells. + //! Simple cells can be evaluated with short circuit evaluation, i.e., as soon + //! as we know that one half-space is not satisfied, we can exit. This + //! provides a performance benefit for the common case. In + //! contains_complex, we evaluate the RPN expression using a stack, similar to //! how a RPN calculator would work. + //! @param xyz[3] The 3D Cartesian coordinate to check. + //! @param uvw[3] A direction used to "break ties" the coordinates are very + //! close to a surface. + //! @param on_surface The signed index of a surface that the coordinate is + //! known to be on. This index takes precedence over surface sense + //! calculations. + bool contains(const double xyz[3], const double uvw[3], int on_surface) const; + //! Write all information needed to reconstruct the cell to an HDF5 group. //! @param group_id An HDF5 group id. void to_hdf5(hid_t group_id) const; + +protected: + bool contains_simple(const double xyz[3], const double uvw[3], + int on_surface) const; + bool contains_complex(const double xyz[3], const double uvw[3], + int on_surface) const; }; -//============================================================================== -// Fortran compatibility functions -//============================================================================== - -extern "C" void read_cells(pugi::xml_node *node); - -extern "C" Cell* cell_pointer(int cell_ind) {return &cells_c[cell_ind];} - -extern "C" int32_t cell_id(Cell *c) {return c->id;} - -extern "C" void cell_set_id(Cell *c, int32_t id) {c->id = id;} - -extern "C" int32_t cell_universe(Cell *c) {return c->universe;} - -extern "C" void cell_set_universe(Cell *c, int32_t universe) -{c->universe = universe;} - -extern "C" void cell_to_hdf5(Cell *c, hid_t group) {c->to_hdf5(group);} - -//extern "C" void free_memory_cells_c() -//{ -// delete cells_c; -// cells_c = nullptr; -// n_cells = 0; -// cell_dict.clear(); -//} - } // namespace openmc #endif // CELL_H diff --git a/src/geometry.F90 b/src/geometry.F90 index cdf286c74..84f11ecc2 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -14,125 +14,29 @@ module geometry implicit none + interface + function cell_contains_c(cell_ptr, xyz, uvw, on_surface) & + bind(C, name="cell_contains") result(in_cell) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: cell_ptr + real(C_DOUBLE), intent(in) :: xyz(3) + real(C_DOUBLE), intent(in) :: uvw(3) + integer(C_INT), intent(in), value :: on_surface + logical(C_BOOL) :: in_cell + end function cell_contains_c + end interface + contains -!=============================================================================== -! CELL_CONTAINS determines if a cell contains the particle at a given -! location. The bounds of the cell are detemined by a logical expression -! involving surface half-spaces. At initialization, the expression was converted -! to RPN notation. -! -! The function is split into two cases, one for simple cells (those involving -! only the intersection of half-spaces) and one for complex cells. Simple cells -! can be evaluated with short circuit evaluation, i.e., as soon as we know that -! one half-space is not satisfied, we can exit. This provides a performance -! benefit for the common case. In complex_cell_contains, we evaluate the RPN -! 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) type(Cell), intent(in) :: c type(Particle), intent(in) :: p logical :: in_cell - - if (c%simple) then - in_cell = simple_cell_contains(c, p) - else - in_cell = complex_cell_contains(c, p) - end if + in_cell = cell_contains_c(c%ptr, p%coord(p%n_coord)%xyz, & + p%coord(p%n_coord)%uvw, p%surface) end function cell_contains - pure function simple_cell_contains(c, p) result(in_cell) - type(Cell), intent(in) :: c - type(Particle), intent(in) :: p - logical :: in_cell - - integer :: i - integer :: token - logical :: actual_sense ! sense of particle wrt surface - - in_cell = .true. - do i = 1, size(c%rpn) - token = c%rpn(i) - if (token < OP_UNION) then - ! If the token is not an operator, evaluate the sense of particle with - ! respect to the surface and see if the token matches the sense. If the - ! particle's surface attribute is set and matches the token, that - ! overrides the determination based on sense(). - if (token == p%surface) then - cycle - elseif (-token == p%surface) then - in_cell = .false. - exit - else - 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. - exit - end if - end if - end if - end do - end function simple_cell_contains - - pure function complex_cell_contains(c, p) result(in_cell) - type(Cell), intent(in) :: c - type(Particle), intent(in) :: p - logical :: in_cell - - integer :: i - integer :: token - integer :: i_stack - logical :: actual_sense ! sense of particle wrt surface - logical :: stack(size(c%rpn)) - - i_stack = 0 - do i = 1, size(c%rpn) - token = c%rpn(i) - - ! If the token is a binary operator (intersection/union), apply it to - ! the last two items on the stack. If the token is a unary operator - ! (complement), apply it to the last item on the stack. - select case (token) - case (OP_UNION) - stack(i_stack - 1) = stack(i_stack - 1) .or. stack(i_stack) - i_stack = i_stack - 1 - case (OP_INTERSECTION) - stack(i_stack - 1) = stack(i_stack - 1) .and. stack(i_stack) - i_stack = i_stack - 1 - case (OP_COMPLEMENT) - stack(i_stack) = .not. stack(i_stack) - case default - ! If the token is not an operator, evaluate the sense of particle with - ! respect to the surface and see if the token matches the sense. If the - ! particle's surface attribute is set and matches the token, that - ! overrides the determination based on sense(). - i_stack = i_stack + 1 - if (token == p%surface) then - stack(i_stack) = .true. - elseif (-token == p%surface) then - stack(i_stack) = .false. - else - 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 - end select - - end do - - if (i_stack == 1) then - ! The one remaining logical on the stack indicates whether the particle is - ! in the cell. - in_cell = stack(i_stack) - else - ! This case occurs if there is no region specification since i_stack will - ! still be zero. - in_cell = .true. - end if - end function complex_cell_contains - !=============================================================================== ! CHECK_CELL_OVERLAP checks for overlapping cells at the current particle's ! position using cell_contains and the LocalCoord's built up by find_cell @@ -737,7 +641,7 @@ contains ! positive half-space were given in the region specification. Thus, we ! have to explicitly check which half-space the particle would be ! traveling into if the surface is crossed - if (.not. c % simple) then + if (.not. c % simple()) then xyz_cross(:) = p % coord(j) % xyz + d_surf*p % coord(j) % uvw call surfaces(abs(level_surf_cross)) % normal(xyz_cross, surf_uvw) if (dot_product(p % coord(j) % uvw, surf_uvw) > ZERO) then diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 8c900430f..5c200c02d 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -53,6 +53,13 @@ module geometry_header integer(C_INT32_T), intent(in), value :: universe end subroutine cell_set_universe_c + function cell_simple_c(cell_ptr) bind(C, name='cell_simple') result(simple) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: cell_ptr + logical(C_BOOL) :: simple + end function cell_simple_c + subroutine cell_to_hdf5_c(cell_ptr, group) bind(C, name='cell_to_hdf5') use ISO_C_BINDING use hdf5 @@ -187,10 +194,6 @@ module geometry_header ! counter integer, allocatable :: region(:) ! Definition of spatial region as ! Boolean expression of half-spaces - integer, allocatable :: rpn(:) ! Reverse Polish notation for region - ! expression - logical :: simple ! Is the region simple (intersections - ! only) integer :: distribcell_index ! Index corresponding to this cell in ! distribcell arrays real(8), allocatable :: sqrtkT(:) ! Square root of k_Boltzmann * @@ -208,6 +211,7 @@ module geometry_header procedure :: set_id => cell_set_id procedure :: universe => cell_universe procedure :: set_universe => cell_set_universe + procedure :: simple => cell_simple procedure :: to_hdf5 => cell_to_hdf5 end type Cell @@ -429,6 +433,12 @@ contains call cell_to_hdf5_c(this % ptr, group) end subroutine cell_to_hdf5 + function cell_simple(this) result(simple) + class(Cell), intent(in) :: this + logical(C_BOOL) :: simple + simple = cell_simple_c(this % ptr) + end function cell_simple + !=============================================================================== ! GET_TEMPERATURES returns a list of temperatures that each nuclide/S(a,b) table ! appears at in the model. Later, this list is used to determine the actual diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 7679fde2d..71b3b8d64 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -942,7 +942,6 @@ contains type(XMLNode), allocatable :: node_rlat_list(:) type(XMLNode), allocatable :: node_hlat_list(:) type(VectorInt) :: tokens - type(VectorInt) :: rpn type(VectorInt) :: fill_univ_ids ! List of fill universe IDs type(VectorInt) :: univ_ids ! List of all universe IDs type(DictIntInt) :: cells_in_univ_dict ! Used to count how many cells each @@ -1120,27 +1119,13 @@ contains end if end do - ! Use shunting-yard algorithm to determine RPN for surface algorithm - call generate_rpn(c%id(), tokens, rpn) - ! Copy region spec and RPN form to cell arrays allocate(c % region(tokens%size())) - allocate(c % rpn(rpn%size())) c % region(:) = tokens%data(1:tokens%size()) - c % rpn(:) = rpn%data(1:rpn%size()) call tokens%clear() - call rpn%clear() end if if (.not. allocated(c%region)) allocate(c%region(0)) - if (.not. allocated(c%rpn)) allocate(c%rpn(0)) - - ! Check if this is a simple cell - if (any(c%rpn == OP_COMPLEMENT) .or. any(c%rpn == OP_UNION)) then - c%simple = .false. - else - c%simple = .true. - end if ! Rotation matrix if (check_for_node(node_cell, "rotation")) then @@ -3982,93 +3967,6 @@ contains end subroutine read_mg_cross_sections_header -!=============================================================================== -! GENERATE_RPN implements the shunting-yard algorithm to generate a Reverse -! Polish notation (RPN) expression for the region specification of a cell given -! the infix notation. -!=============================================================================== - - subroutine generate_rpn(cell_id, tokens, output) - integer, intent(in) :: cell_id - type(VectorInt), intent(in) :: tokens ! infix notation - type(VectorInt), intent(inout) :: output ! RPN notation - - integer :: i - integer :: token - integer :: op - type(VectorInt) :: stack - - do i = 1, tokens%size() - token = tokens%data(i) - - if (token < OP_UNION) then - ! If token is not an operator, add it to output - call output%push_back(token) - - elseif (token < OP_RIGHT_PAREN) then - ! Regular operators union, intersection, complement - do while (stack%size() > 0) - op = stack%data(stack%size()) - - if (op < OP_RIGHT_PAREN .and. & - ((token == OP_COMPLEMENT .and. token < op) .or. & - (token /= OP_COMPLEMENT .and. token <= op))) then - ! While there is an operator, op, on top of the stack, if the token - ! is left-associative and its precedence is less than or equal to - ! that of op or if the token is right-associative and its precedence - ! is less than that of op, move op to the output queue and push the - ! token on to the stack. Note that only complement is - ! right-associative. - call output%push_back(op) - call stack%pop_back() - else - exit - end if - end do - - call stack%push_back(token) - - elseif (token == OP_LEFT_PAREN) then - ! If the token is a left parenthesis, push it onto the stack - call stack%push_back(token) - - else - ! If the token is a right parenthesis, move operators from the stack to - ! the output queue until reaching the left parenthesis. - do - ! If we run out of operators without finding a left parenthesis, it - ! means there are mismatched parentheses. - if (stack%size() == 0) then - call fatal_error('Mimatched parentheses in region specification & - &for cell ' // trim(to_str(cell_id)) // '.') - end if - - op = stack%data(stack%size()) - if (op == OP_LEFT_PAREN) exit - call output%push_back(op) - call stack%pop_back() - end do - - ! Pop the left parenthesis. - call stack%pop_back() - end if - end do - - ! While there are operators on the stack, move them to the output queue - do while (stack%size() > 0) - op = stack%data(stack%size()) - - ! If the operator is a parenthesis, it is mismatched - if (op >= OP_RIGHT_PAREN) then - call fatal_error('Mimatched parentheses in region specification & - &for cell ' // trim(to_str(cell_id)) // '.') - end if - - call output%push_back(op) - call stack%pop_back() - end do - end subroutine generate_rpn - !=============================================================================== ! NORMALIZE_AO Normalize the nuclide atom percents !=============================================================================== diff --git a/src/output.F90 b/src/output.F90 index c8efcecbc..483d171ca 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -258,7 +258,7 @@ contains end do ! Print surface - if (p % surface /= NONE) then + if (p % surface /= ERROR_INT) then write(ou,*) ' Surface = ' // to_str(sign(surfaces(i)%id(), p % surface)) end if diff --git a/src/surface.cpp b/src/surface.cpp index 9abfd23d2..64c9568ba 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -11,6 +11,26 @@ namespace openmc { +//============================================================================== +// Module constant definitions +//============================================================================== + +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 variables +//============================================================================== + +// Braces force n_surfaces to be defined here, not just declared. +extern "C" {int32_t n_surfaces {0};} + +Surface **surfaces_c; + +std::map surface_dict; + //============================================================================== // Helper functions for reading the "coeffs" node of an XML surface element //============================================================================== @@ -1232,4 +1252,44 @@ read_surfaces(pugi::xml_node *node) } } +//============================================================================== +// Fortran compatibility functions +//============================================================================== + +extern "C" { + Surface* surface_pointer(int surf_ind) {return surfaces_c[surf_ind];} + + int surface_id(Surface *surf) {return surf->id;} + + int surface_bc(Surface *surf) {return surf->bc;} + + void surface_reflect(Surface *surf, double xyz[3], double uvw[3]) + {surf->reflect(xyz, uvw);} + + double + surface_distance(Surface *surf, double xyz[3], double uvw[3], bool coincident) + {return surf->distance(xyz, uvw, coincident);} + + void surface_normal(Surface *surf, double xyz[3], double uvw[3]) + {return surf->normal(xyz, uvw);} + + void surface_to_hdf5(Surface *surf, hid_t group) {surf->to_hdf5(group);} + + int surface_i_periodic(PeriodicSurface *surf) {return surf->i_periodic;} + + bool + surface_periodic(PeriodicSurface *surf, PeriodicSurface *other, double xyz[3], + double uvw[3]) + {return surf->periodic_translate(other, xyz, uvw);} + + void free_memory_surfaces_c() + { + for (int i = 0; i < n_surfaces; i++) {delete surfaces_c[i];} + delete surfaces_c; + surfaces_c = nullptr; + n_surfaces = 0; + surface_dict.clear(); + } +} + } // namespace openmc diff --git a/src/surface.h b/src/surface.h index 33139ace1..046610daa 100644 --- a/src/surface.h +++ b/src/surface.h @@ -12,13 +12,13 @@ namespace openmc { //============================================================================== -// Module constants +// Module constant declarations (defined in .cpp) //============================================================================== -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; +extern "C" const int BC_VACUUM; +extern "C" const int BC_REFLECT; +extern "C" const int BC_PERIODIC; //============================================================================== // Constants that should eventually be moved out of this file @@ -32,13 +32,12 @@ constexpr int C_NONE {-1}; // Global variables //============================================================================== -// Braces force n_surfaces to be defined here, not just declared. -extern "C" {int32_t n_surfaces {0};} +extern "C" int32_t n_surfaces; class Surface; -Surface **surfaces_c; +extern Surface **surfaces_c; -std::map surface_dict; +extern std::map surface_dict; //============================================================================== //! Coordinates for an axis-aligned cube that bounds a geometric object. @@ -381,50 +380,5 @@ public: void to_hdf5_inner(hid_t group_id) const; }; -//============================================================================== -// Fortran compatibility functions -//============================================================================== - -extern "C" void read_surfaces(pugi::xml_node *node); - -extern "C" Surface* surface_pointer(int surf_ind) {return surfaces_c[surf_ind];} - -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(Surface *surf, double xyz[3], double uvw[3], bool coincident) -{return surf->distance(xyz, uvw, coincident);} - -extern "C" void surface_normal(Surface *surf, double xyz[3], double uvw[3]) -{return surf->normal(xyz, uvw);} - -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(PeriodicSurface *surf, PeriodicSurface *other, double xyz[3], - double uvw[3]) -{return surf->periodic_translate(other, xyz, uvw);} - -extern "C" void free_memory_surfaces_c() -{ - for (int i = 0; i < n_surfaces; i++) {delete surfaces_c[i];} - delete surfaces_c; - 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 9ed89a14e..991decc3c 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -30,16 +30,6 @@ module surface_header 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 @@ -116,7 +106,6 @@ module surface_header procedure :: id => surface_id procedure :: bc => surface_bc - procedure :: sense => surface_sense procedure :: reflect => surface_reflect procedure :: distance => surface_distance procedure :: normal => surface_normal @@ -147,14 +136,6 @@ contains 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); diff --git a/src/tracking.F90 b/src/tracking.F90 index 127da0222..7a7f1fd94 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -173,7 +173,7 @@ contains p % coord(p % n_coord) % cell = NONE if (any(lattice_translation /= 0)) then ! Particle crosses lattice boundary - p % surface = NONE + p % surface = ERROR_INT call cross_lattice(p, lattice_translation) p % event = EVENT_LATTICE else @@ -202,7 +202,7 @@ contains if (active_current_tallies % size() > 0) call score_surface_current(p) ! Clear surface component - p % surface = NONE + p % surface = ERROR_INT if (run_CE) then call collision(p) @@ -472,7 +472,7 @@ contains ! COULDN'T FIND PARTICLE IN NEIGHBORING CELLS, SEARCH ALL CELLS ! Remove lower coordinate levels and assignment of surface - p % surface = NONE + p % surface = ERROR_INT p % n_coord = 1 call find_cell(p, found) From 41f84bddc8037f88123c7de5d19519679330ac3e Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 4 Feb 2018 21:11:12 -0500 Subject: [PATCH 007/361] Move all surface distance calls to C++ --- src/cell.cpp | 93 +++++++++++++++++++++++++++++++++-------- src/cell.h | 14 +++---- src/geometry.F90 | 36 ++++------------ src/geometry_header.F90 | 39 +++++++++++++---- src/summary.F90 | 22 ---------- src/surface.cpp | 4 -- src/surface_header.F90 | 21 ---------- 7 files changed, 121 insertions(+), 108 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index 8d3cc832e..83942cf73 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -21,11 +21,13 @@ namespace openmc { // Constants //============================================================================== -constexpr int32_t OP_LEFT_PAREN {std::numeric_limits::max()}; -constexpr int32_t OP_RIGHT_PAREN {std::numeric_limits::max() - 1}; -constexpr int32_t OP_COMPLEMENT {std::numeric_limits::max() - 2}; -constexpr int32_t OP_INTERSECTION {std::numeric_limits::max() - 3}; -constexpr int32_t OP_UNION {std::numeric_limits::max() - 4}; +constexpr int32_t OP_LEFT_PAREN {std::numeric_limits::max()}; +constexpr int32_t OP_RIGHT_PAREN {std::numeric_limits::max() - 1}; +constexpr int32_t OP_COMPLEMENT {std::numeric_limits::max() - 2}; +constexpr int32_t OP_INTERSECTION {std::numeric_limits::max() - 3}; +constexpr int32_t OP_UNION {std::numeric_limits::max() - 4}; + +extern "C" double FP_PRECISION; //============================================================================== //! Convert region specification string to integer tokens. @@ -125,7 +127,7 @@ generate_rpn(int32_t cell_id, std::vector infix) } else if (token < OP_RIGHT_PAREN) { // Regular operators union, intersection, complement while (stack.size() > 0) { - int op = stack.back(); + int32_t op = stack.back(); if (op < OP_RIGHT_PAREN && ((token == OP_COMPLEMENT && token < op) || @@ -172,7 +174,7 @@ generate_rpn(int32_t cell_id, std::vector infix) } while (stack.size() > 0) { - int op = stack.back(); + int32_t op = stack.back(); // If the operator is a parenthesis it is mismatched. if (op >= OP_RIGHT_PAREN) { @@ -235,7 +237,7 @@ Cell::Cell(pugi::xml_node cell_node) // Check if this is a simple cell. simple = true; - for (int token : rpn) { + for (int32_t token : rpn) { if ((token == OP_COMPLEMENT) || (token == OP_UNION)) { simple = false; break; @@ -244,7 +246,8 @@ Cell::Cell(pugi::xml_node cell_node) } bool -Cell::contains(const double xyz[3], const double uvw[3], int on_surface) const +Cell::contains(const double xyz[3], const double uvw[3], + int32_t on_surface) const { if (simple) { return contains_simple(xyz, uvw, on_surface); @@ -253,6 +256,35 @@ Cell::contains(const double xyz[3], const double uvw[3], int on_surface) const } } +std::pair +Cell::distance(const double xyz[3], const double uvw[3], + int32_t on_surface) const +{ + double min_dist {INFTY}; + int32_t i_surf {std::numeric_limits::max()}; + + for (int32_t token : rpn) { + // Ignore this token if it corresponds to an operator rather than a region. + if (token >= OP_UNION) {continue;} + + // Calculate the distance to this surface. + // Note the off-by-one indexing + bool coincident {token == on_surface}; + double d {surfaces_c[abs(token)-1]->distance(xyz, uvw, coincident)}; + //std::cout << token << " " << on_surface << " " << coincident << std::endl; + + // Check if this distance is the new minimum. + if (d < min_dist) { + if (std::abs(d - min_dist) / min_dist >= FP_PRECISION) { + min_dist = d; + i_surf = -token; + } + } + } + + return {min_dist, i_surf}; +} + void Cell::to_hdf5(hid_t cell_group) const { @@ -268,14 +300,33 @@ Cell::to_hdf5(hid_t cell_group) const //TODO: Lookup universe id in universe_dict //write_int(cell_group, "universe", universe); + // Write the region specification. + std::stringstream region_spec {}; + for (int32_t token : region) { + if (token == OP_LEFT_PAREN) { + region_spec << " ("; + } else if (token == OP_RIGHT_PAREN) { + region_spec << " )"; + } else if (token == OP_COMPLEMENT) { + region_spec << " ~"; + } else if (token == OP_INTERSECTION) { + } else if (token == OP_UNION) { + region_spec << " |"; + } else { + // Note the off-by-one indexing + region_spec << " " << copysign(surfaces_c[abs(token)-1]->id, token); + } + } + write_string(cell_group, "region", region_spec.str()); + // close_group(cell_group); } bool -Cell::contains_simple(const double xyz[3], const double uvw[3], int on_surface) -const +Cell::contains_simple(const double xyz[3], const double uvw[3], + int32_t on_surface) const { - for (int token : rpn) { + for (int32_t token : rpn) { if (token < OP_UNION) { // If the token is not an operator, evaluate the sense of particle with // respect to the surface and see if the token matches the sense. If the @@ -295,15 +346,15 @@ const } bool -Cell::contains_complex(const double xyz[3], const double uvw[3], int on_surface) -const +Cell::contains_complex(const double xyz[3], const double uvw[3], + int32_t on_surface) const { // Make a stack of booleans. We don't know how big it needs to be, but we do // know that rpn.size() is an upper-bound. bool stack[rpn.size()]; int i_stack = -1; - for (int token : rpn) { + for (int32_t token : rpn) { // If the token is a binary operator (intersection/union), apply it to // the last two items on the stack. If the token is a unary operator // (complement), apply it to the last item on the stack. @@ -369,7 +420,7 @@ read_cells(pugi::xml_node *node) //============================================================================== extern "C" { - Cell* cell_pointer(int cell_ind) {return &cells_c[cell_ind];} + Cell* cell_pointer(int32_t cell_ind) {return &cells_c[cell_ind];} int32_t cell_id(Cell *c) {return c->id;} @@ -381,9 +432,17 @@ extern "C" { bool cell_simple(Cell *c) {return c->simple;} - bool cell_contains(Cell *c, double xyz[3], double uvw[3], int on_surface) + bool cell_contains(Cell *c, double xyz[3], double uvw[3], int32_t on_surface) {return c->contains(xyz, uvw, on_surface);} + void cell_distance(Cell *c, double xyz[3], double uvw[3], int32_t on_surface, + double &min_dist, int32_t &i_surf) + { + std::pair out = c->distance(xyz, uvw, on_surface); + min_dist = out.first; + i_surf = out.second; + } + void cell_to_hdf5(Cell *c, hid_t group) {c->to_hdf5(group);} } diff --git a/src/cell.h b/src/cell.h index 0531c23d0..5f9369e55 100644 --- a/src/cell.h +++ b/src/cell.h @@ -12,10 +12,6 @@ namespace openmc { -//============================================================================== -// Module constants -//============================================================================== - //============================================================================== // Global variables //============================================================================== @@ -66,7 +62,11 @@ public: //! @param on_surface The signed index of a surface that the coordinate is //! known to be on. This index takes precedence over surface sense //! calculations. - bool contains(const double xyz[3], const double uvw[3], int on_surface) const; + bool + contains(const double xyz[3], const double uvw[3], int32_t on_surface) const; + + std::pair + distance(const double xyz[3], const double uvw[3], int32_t on_surface) const; //! Write all information needed to reconstruct the cell to an HDF5 group. //! @param group_id An HDF5 group id. @@ -74,9 +74,9 @@ public: protected: bool contains_simple(const double xyz[3], const double uvw[3], - int on_surface) const; + int32_t on_surface) const; bool contains_complex(const double xyz[3], const double uvw[3], - int on_surface) const; + int32_t on_surface) const; }; } // namespace openmc diff --git a/src/geometry.F90 b/src/geometry.F90 index 84f11ecc2..74e9731ff 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -19,11 +19,11 @@ module geometry bind(C, name="cell_contains") result(in_cell) use ISO_C_BINDING implicit none - type(C_PTR), intent(in), value :: cell_ptr - real(C_DOUBLE), intent(in) :: xyz(3) - real(C_DOUBLE), intent(in) :: uvw(3) - integer(C_INT), intent(in), value :: on_surface - logical(C_BOOL) :: in_cell + type(C_PTR), intent(in), value :: cell_ptr + real(C_DOUBLE), intent(in) :: xyz(3) + real(C_DOUBLE), intent(in) :: uvw(3) + integer(C_INT32_T), intent(in), value :: on_surface + logical(C_BOOL) :: in_cell end function cell_contains_c end interface @@ -368,9 +368,7 @@ contains integer, intent(out) :: lattice_translation(3) integer, intent(out) :: next_level - integer :: i ! index for surface in cell integer :: j - integer :: index_surf ! index in surfaces array (with sign) integer :: i_xyz(3) ! lattice indices integer :: level_surf_cross ! surface crossed on current level integer :: level_lat_trans(3) ! lattice translation on current level @@ -387,7 +385,6 @@ contains 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(Lattice), pointer :: lat @@ -413,27 +410,8 @@ contains ! ======================================================================= ! FIND MINIMUM DISTANCE TO SURFACE IN THIS CELL - SURFACE_LOOP: do i = 1, size(c % region) - index_surf = c % region(i) - coincident = (index_surf == p % surface) - - ! ignore this token if it corresponds to an operator rather than a - ! region. - index_surf = abs(index_surf) - if (index_surf >= OP_UNION) cycle - - ! Calculate distance to surface - 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 - if (d < d_surf) then - if (abs(d - d_surf)/d_surf >= FP_PRECISION) then - d_surf = d - level_surf_cross = -c % region(i) - end if - end if - end do SURFACE_LOOP + call c % distance(p % coord(j) % xyz, p % coord(j) % uvw, p % surface, & + d_surf, level_surf_cross) ! ======================================================================= ! FIND MINIMUM DISTANCE TO LATTICE SURFACES diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 5c200c02d..32511ea3d 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -19,8 +19,8 @@ module geometry_header function cell_pointer_c(cell_ind) bind(C, name='cell_pointer') result(ptr) use ISO_C_BINDING implicit none - integer(C_INT), intent(in), value :: cell_ind - type(C_PTR) :: ptr + integer(C_INT32_T), intent(in), value :: cell_ind + type(C_PTR) :: ptr end function cell_pointer_c function cell_id_c(cell_ptr) bind(C, name='cell_id') result(id) @@ -60,6 +60,18 @@ module geometry_header logical(C_BOOL) :: simple end function cell_simple_c + subroutine cell_distance_c(cell_ptr, xyz, uvw, on_surface, min_dist, & + i_surf) bind(C, name="cell_distance") + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: cell_ptr + real(C_DOUBLE), intent(in) :: xyz(3) + real(C_DOUBLE), intent(in) :: uvw(3) + integer(C_INT32_T), intent(in), value :: on_surface + real(C_DOUBLE), intent(out) :: min_dist + integer(C_INT32_T), intent(out) :: i_surf + end subroutine cell_distance_c + subroutine cell_to_hdf5_c(cell_ptr, group) bind(C, name='cell_to_hdf5') use ISO_C_BINDING use hdf5 @@ -212,6 +224,7 @@ module geometry_header procedure :: universe => cell_universe procedure :: set_universe => cell_set_universe procedure :: simple => cell_simple + procedure :: distance => cell_distance procedure :: to_hdf5 => cell_to_hdf5 end type Cell @@ -427,18 +440,28 @@ contains call cell_set_universe_c(this % ptr, universe) end subroutine cell_set_universe - subroutine cell_to_hdf5(this, group) - class(Cell), intent(in) :: this - integer(HID_T), intent(in) :: group - call cell_to_hdf5_c(this % ptr, group) - end subroutine cell_to_hdf5 - function cell_simple(this) result(simple) class(Cell), intent(in) :: this logical(C_BOOL) :: simple simple = cell_simple_c(this % ptr) end function cell_simple + subroutine cell_distance(this, xyz, uvw, on_surface, min_dist, i_surf) + class(Cell), intent(in) :: this + real(C_DOUBLE), intent(in) :: xyz(3) + real(C_DOUBLE), intent(in) :: uvw(3) + integer(C_INT32_T), intent(in) :: on_surface + real(C_DOUBLE), intent(out) :: min_dist + integer(C_INT32_T), intent(out) :: i_surf + call cell_distance_c(this % ptr, xyz, uvw, on_surface, min_dist, i_surf) + end subroutine cell_distance + + subroutine cell_to_hdf5(this, group) + class(Cell), intent(in) :: this + integer(HID_T), intent(in) :: group + call cell_to_hdf5_c(this % ptr, group) + end subroutine cell_to_hdf5 + !=============================================================================== ! GET_TEMPERATURES returns a list of temperatures that each nuclide/S(a,b) table ! appears at in the model. Later, this list is used to determine the actual diff --git a/src/summary.F90 b/src/summary.F90 index 3aa851424..5e5ab778a 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -125,7 +125,6 @@ contains 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(Lattice), pointer :: lat @@ -204,27 +203,6 @@ contains call write_dataset(cell_group, "lattice", lattices(c%fill)%obj%id) end select - ! Write list of bounding surfaces - region_spec = "" - do j = 1, size(c%region) - k = c%region(j) - select case(k) - case (OP_LEFT_PAREN) - region_spec = trim(region_spec) // " (" - case (OP_RIGHT_PAREN) - region_spec = trim(region_spec) // " )" - case (OP_COMPLEMENT) - region_spec = trim(region_spec) // " ~" - case (OP_INTERSECTION) - case (OP_UNION) - region_spec = trim(region_spec) // " |" - case default - region_spec = trim(region_spec) // " " // to_str(& - sign(surfaces(abs(k))%id(), k)) - end select - end do - call write_dataset(cell_group, "region", adjustl(region_spec)) - call close_group(cell_group) end do CELL_LOOP diff --git a/src/surface.cpp b/src/surface.cpp index 64c9568ba..32b1a3ce6 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -1266,10 +1266,6 @@ extern "C" { void surface_reflect(Surface *surf, double xyz[3], double uvw[3]) {surf->reflect(xyz, uvw);} - double - surface_distance(Surface *surf, double xyz[3], double uvw[3], bool coincident) - {return surf->distance(xyz, uvw, coincident);} - void surface_normal(Surface *surf, double xyz[3], double uvw[3]) {return surf->normal(xyz, uvw);} diff --git a/src/surface_header.F90 b/src/surface_header.F90 index 991decc3c..67f6cd06c 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -39,17 +39,6 @@ module surface_header 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 @@ -107,7 +96,6 @@ module surface_header procedure :: id => surface_id procedure :: bc => surface_bc procedure :: reflect => surface_reflect - procedure :: distance => surface_distance procedure :: normal => surface_normal procedure :: to_hdf5 => surface_to_hdf5 procedure :: i_periodic => surface_i_periodic @@ -143,15 +131,6 @@ contains 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); From 197e91cf90dae5e620d61637a02e734df6ccc871 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 4 Feb 2018 23:15:32 -0500 Subject: [PATCH 008/361] Add skeleton of C++ lattices --- CMakeLists.txt | 2 + src/cell.cpp | 32 +++---- src/cell.h | 13 +++ src/geometry.F90 | 4 +- src/geometry_header.F90 | 44 ++++++++- src/input_xml.F90 | 60 ++++--------- src/lattice.cpp | 109 +++++++++++++++++++++++ src/lattice.h | 77 ++++++++++++++++ src/output.F90 | 2 +- src/summary.F90 | 7 +- src/tallies/tally_filter_distribcell.F90 | 4 +- 11 files changed, 286 insertions(+), 68 deletions(-) create mode 100644 src/lattice.cpp create mode 100644 src/lattice.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c1228c493..ef9daabd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -439,6 +439,8 @@ set(LIBOPENMC_CXX_SRC src/cell.h src/error.h src/hdf5_interface.h + src/lattice.cpp + src/lattice.h src/random_lcg.cpp src/random_lcg.h src/surface.cpp diff --git a/src/cell.cpp b/src/cell.cpp index 83942cf73..427e30890 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -301,23 +301,25 @@ Cell::to_hdf5(hid_t cell_group) const //write_int(cell_group, "universe", universe); // Write the region specification. - std::stringstream region_spec {}; - for (int32_t token : region) { - if (token == OP_LEFT_PAREN) { - region_spec << " ("; - } else if (token == OP_RIGHT_PAREN) { - region_spec << " )"; - } else if (token == OP_COMPLEMENT) { - region_spec << " ~"; - } else if (token == OP_INTERSECTION) { - } else if (token == OP_UNION) { - region_spec << " |"; - } else { - // Note the off-by-one indexing - region_spec << " " << copysign(surfaces_c[abs(token)-1]->id, token); + if (!region.empty()) { + std::stringstream region_spec {}; + for (int32_t token : region) { + if (token == OP_LEFT_PAREN) { + region_spec << " ("; + } else if (token == OP_RIGHT_PAREN) { + region_spec << " )"; + } else if (token == OP_COMPLEMENT) { + region_spec << " ~"; + } else if (token == OP_INTERSECTION) { + } else if (token == OP_UNION) { + region_spec << " |"; + } else { + // Note the off-by-one indexing + region_spec << " " << copysign(surfaces_c[abs(token)-1]->id, token); + } } + write_string(cell_group, "region", region_spec.str()); } - write_string(cell_group, "region", region_spec.str()); // close_group(cell_group); } diff --git a/src/cell.h b/src/cell.h index 5f9369e55..59cec1729 100644 --- a/src/cell.h +++ b/src/cell.h @@ -25,6 +25,19 @@ std::vector cells_c; std::map cell_dict; +//============================================================================== +//! A geometry primitive that fills all space and contains cells. +//============================================================================== + +class Universe +{ + public: + int32_t id; //! Unique ID + int32_t type; + std::vector cells; //! Cells within this universe + double x0, y0, z0; //! Translation coordinates. +}; + //============================================================================== //! A geometry primitive that links surfaces, universes, and materials //============================================================================== diff --git a/src/geometry.F90 b/src/geometry.F90 index 74e9731ff..1f241f66d 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -260,7 +260,7 @@ contains ! Particle is outside the lattice. if (lat % outer == NO_OUTER_UNIVERSE) then call p % mark_as_lost("Particle " // trim(to_str(p %id)) & - // " is outside lattice " // trim(to_str(lat % id)) & + // " is outside lattice " // trim(to_str(lat % id())) & // " but the lattice has no defined outer universe.") return else @@ -299,7 +299,7 @@ contains lat => lattices(p % coord(j) % lattice) % obj if (verbosity >= 10 .or. trace) then - call write_message(" Crossing lattice " // trim(to_str(lat % id)) & + call write_message(" Crossing lattice " // trim(to_str(lat % id())) & &// ". Current position (" // trim(to_str(p % coord(j) % lattice_x)) & &// "," // trim(to_str(p % coord(j) % lattice_y)) // "," & &// trim(to_str(p % coord(j) % lattice_z)) // ")") diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 32511ea3d..ad34c735c 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -79,6 +79,29 @@ module geometry_header type(C_PTR), intent(in), value :: cell_ptr integer(HID_T), intent(in), value :: group end subroutine cell_to_hdf5_c + + function lattice_pointer_c(lat_ind) bind(C, name='lattice_pointer') & + result(ptr) + use ISO_C_BINDING + implicit none + integer(C_INT32_T), intent(in), value :: lat_ind + type(C_PTR) :: ptr + end function lattice_pointer_c + + function lattice_id_c(lat_ptr) bind(C, name='lattice_id') result(id) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: lat_ptr + integer(C_INT32_T) :: id + end function lattice_id_c + + subroutine lattice_to_hdf5_c(lat_ptr, group) bind(C, name='lattice_to_hdf5') + use ISO_C_BINDING + use hdf5 + implicit none + type(C_PTR), intent(in), value :: lat_ptr + integer(HID_T), intent(in), value :: group + end subroutine lattice_to_hdf5_c end interface !=============================================================================== @@ -99,8 +122,9 @@ module geometry_header !=============================================================================== type, abstract :: Lattice - integer :: id ! Universe number for lattice - character(len=104) :: name = "" ! User-defined name + type(C_PTR) :: ptr + + !integer :: id ! Universe number for lattice real(8), allocatable :: pitch(:) ! Pitch along each axis integer, allocatable :: universes(:,:,:) ! Specified universes integer :: outside ! Material to fill area outside @@ -108,6 +132,10 @@ module geometry_header logical :: is_3d ! Lattice has cells on z axis integer, allocatable :: offset(:,:,:,:) ! Distribcell offsets contains + + procedure :: id => lattice_id + procedure :: to_hdf5 => lattice_to_hdf5 + procedure(lattice_are_valid_indices_), deferred :: are_valid_indices procedure(lattice_get_indices_), deferred :: get_indices procedure(lattice_get_local_xyz_), deferred :: get_local_xyz @@ -247,6 +275,18 @@ module geometry_header contains + function lattice_id(this) result(id) + class(Lattice), intent(in) :: this + integer(C_INT32_T) :: id + id = lattice_id_c(this % ptr) + end function lattice_id + + subroutine lattice_to_hdf5(this, group) + class(Lattice), intent(in) :: this + integer(HID_T), intent(in) :: group + call lattice_to_hdf5_c(this % ptr, group) + end subroutine lattice_to_hdf5 + !=============================================================================== function valid_inds_rect(this, i_xyz) result(is_valid) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 71b3b8d64..78dd0bb87 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -60,6 +60,12 @@ module input_xml implicit none type(C_PTR) :: node_ptr end subroutine read_cells + + subroutine read_lattices(node_ptr) bind(C) + use ISO_C_BINDING + implicit none + type(C_PTR) :: node_ptr + end subroutine read_lattices end interface contains @@ -1240,6 +1246,8 @@ contains ! ========================================================================== ! READ LATTICES FROM GEOMETRY.XML + call read_lattices(root % ptr) + ! Get pointer to list of XML call get_node_list(root, "lattice", node_rlat_list) call get_node_list(root, "hex_lattice", node_hlat_list) @@ -1253,30 +1261,13 @@ contains RECT_LATTICES: do i = 1, n_rlats allocate(RectLattice::lattices(i) % obj) lat => lattices(i) % obj + lat % ptr = lattice_pointer_c(i - 1) select type(lat) type is (RectLattice) ! Get pointer to i-th lattice node_lat = node_rlat_list(i) - ! ID of lattice - if (check_for_node(node_lat, "id")) then - call get_node_value(node_lat, "id", lat % id) - else - call fatal_error("Must specify id of lattice in geometry XML file.") - end if - - ! Check to make sure 'id' hasn't been used - if (lattice_dict % has(lat % id)) then - call fatal_error("Two or more lattices use the same unique ID: " & - // to_str(lat % id)) - end if - - ! Copy lattice name - if (check_for_node(node_lat, "name")) then - call get_node_value(node_lat, "name", lat % name) - end if - ! Read number of lattice cells in each dimension n = node_word_count(node_lat, "dimension") if (n == 2) then @@ -1341,7 +1332,7 @@ contains n = node_word_count(node_lat, "universes") if (n /= n_x*n_y*n_z) then call fatal_error("Number of universes on does not match & - &size of lattice " // trim(to_str(lat % id)) // ".") + &size of lattice " // trim(to_str(lat % id())) // ".") end if allocate(temp_int_array(n)) @@ -1377,7 +1368,7 @@ contains end if ! Add lattice to dictionary - call lattice_dict % set(lat % id, i) + call lattice_dict % set(lat % id(), i) end select end do RECT_LATTICES @@ -1385,30 +1376,13 @@ contains HEX_LATTICES: do i = 1, n_hlats allocate(HexLattice::lattices(n_rlats + i) % obj) lat => lattices(n_rlats + i) % obj + lat % ptr = lattice_pointer_c(n_rlats + i - 1) select type (lat) type is (HexLattice) ! Get pointer to i-th lattice node_lat = node_hlat_list(i) - ! ID of lattice - if (check_for_node(node_lat, "id")) then - call get_node_value(node_lat, "id", lat % id) - else - call fatal_error("Must specify id of lattice in geometry XML file.") - end if - - ! Check to make sure 'id' hasn't been used - if (lattice_dict % has(lat % id)) then - call fatal_error("Two or more lattices use the same unique ID: " & - // to_str(lat % id)) - end if - - ! Copy lattice name - if (check_for_node(node_lat, "name")) then - call get_node_value(node_lat, "name", lat % name) - end if - ! Read number of lattice cells in each dimension call get_node_value(node_lat, "n_rings", lat % n_rings) if (check_for_node(node_lat, "n_axial")) then @@ -1454,7 +1428,7 @@ contains n = node_word_count(node_lat, "universes") if (n /= (3*n_rings**2 - 3*n_rings + 1)*n_z) then call fatal_error("Number of universes on does not match & - &size of lattice " // trim(to_str(lat % id)) // ".") + &size of lattice " // trim(to_str(lat % id())) // ".") end if allocate(temp_int_array(n)) @@ -1564,7 +1538,7 @@ contains end if ! Add lattice to dictionary - call lattice_dict % set(lat % id, n_rlats + i) + call lattice_dict % set(lat % id(), n_rlats + i) end select end do HEX_LATTICES @@ -4353,7 +4327,7 @@ contains else call fatal_error("Invalid universe number " & &// trim(to_str(id)) // " specified on lattice " & - &// trim(to_str(lat % id))) + &// trim(to_str(lat % id()))) end if end do end do @@ -4374,7 +4348,7 @@ contains else call fatal_error("Invalid universe number " & &// trim(to_str(id)) // " specified on lattice " & - &// trim(to_str(lat % id))) + &// trim(to_str(lat % id()))) end if end do end do @@ -4388,7 +4362,7 @@ contains else call fatal_error("Invalid universe number " & &// trim(to_str(lat % outer)) & - &// " specified on lattice " // trim(to_str(lat % id))) + &// " specified on lattice " // trim(to_str(lat % id()))) end if end if diff --git a/src/lattice.cpp b/src/lattice.cpp new file mode 100644 index 000000000..73758b110 --- /dev/null +++ b/src/lattice.cpp @@ -0,0 +1,109 @@ +#include "lattice.h" + +#include +#include + +#include "error.h" +#include "hdf5_interface.h" +#include "xml_interface.h" + +//TODO: remove this include +#include + + +namespace openmc { + +//============================================================================== +// Global variables +//============================================================================== + +// Braces force n_lattices to be defined here, not just declared. +//extern "C" {int32_t n_lattices {0};} + +//Lattice **lattices_c; +std::vector lattices_c; + +std::map lattice_dict; + +//============================================================================== +// Lattice implementation +//============================================================================== + +Lattice::Lattice(pugi::xml_node lat_node) +{ + if (check_for_node(lat_node, "id")) { + id = stoi(get_node_value(lat_node, "id")); + } else { + fatal_error("Must specify id of lattice in geometry XML file."); + } + + if (check_for_node(lat_node, "name")) { + name = get_node_value(lat_node, "name"); + } +} + +void +Lattice::to_hdf5(hid_t lat_group) const +{ + if (!name.empty()) { + write_string(lat_group, "name", name); + } +} + +//============================================================================== +// RectLattice implementation +//============================================================================== + +RectLattice::RectLattice(pugi::xml_node lat_node) + : Lattice {lat_node} +{ +} + +//============================================================================== +// HexLattice implementation +//============================================================================== + +HexLattice::HexLattice(pugi::xml_node lat_node) + : Lattice {lat_node} +{ +} + +//============================================================================== + +extern "C" void +read_lattices(pugi::xml_node *node) +{ + for (pugi::xml_node lat_node: node->children("lattice")) { + lattices_c.push_back(new RectLattice(lat_node)); + } + for (pugi::xml_node lat_node: node->children("hex_lattice")) { + lattices_c.push_back(new HexLattice(lat_node)); + } + + // Fill the lattice dictionary. + for (int i_lat = 0; i_lat < lattices_c.size(); i_lat++) { + int id = lattices_c[i_lat]->id; + auto in_dict = lattice_dict.find(id); + if (in_dict == lattice_dict.end()) { + lattice_dict[id] = i_lat; + } else { + std::stringstream err_msg; + err_msg << "Two or more lattices use the same unique ID: " << id; + fatal_error(err_msg); + } + } +} + +//============================================================================== +// Fortran compatibility functions +//============================================================================== + +extern "C" { + Lattice* lattice_pointer(int lat_ind) {return lattices_c[lat_ind];} + + int32_t lattice_id(Lattice *lat) {return lat->id;} + + void lattice_to_hdf5(Lattice *lat, hid_t group) {lat->to_hdf5(group);} +} + +} // namespace openmc diff --git a/src/lattice.h b/src/lattice.h new file mode 100644 index 000000000..d8e1ba39a --- /dev/null +++ b/src/lattice.h @@ -0,0 +1,77 @@ +#ifndef LATTICE_H +#define LATTICE_H + +#include +#include +#include +#include + +#include "hdf5.h" +#include "pugixml/pugixml.hpp" + + +namespace openmc { + +//============================================================================== +// Global variables +//============================================================================== + +//extern "C" int32_t n_lattice; + +class Lattice; +//extern Lattice **lattices_c; +extern std::vector lattices_c; + +extern std::map lattice_dict; + +//============================================================================== +//! Abstract type for ordered array of universes +//============================================================================== + +class Lattice +{ +public: + int32_t id; //! Universe ID number + std::string name; //! User-defined name + //std::vector pitch; //! Pitch along each basis + //std::vector universes; //! Universes filling each lattice tile + //int32_t outer; //! Universe tiled outside the lattice + //std::vector offset; //! Distribcell offsets + + explicit Lattice(pugi::xml_node lat_node); + + virtual ~Lattice() {} + + //virtual bool are_valid_indices(const int i_xyz[3]) const = 0; + + //virtual void get_indices(const double global_xyz[3], int i_xyz[3]) const = 0; + + //virtual void get_local_xyz(const double global_xyz[3], const int i_xyz[3], + // double local_xyz[3]) const = 0; + + //! Write all information needed to reconstruct the lattice to an HDF5 group. + //! @param group_id An HDF5 group id. + void to_hdf5(hid_t group_id) const; +}; + +//============================================================================== +//============================================================================== + +class RectLattice : public Lattice +{ +public: + explicit RectLattice(pugi::xml_node lat_node); + + virtual ~RectLattice() {} +}; + +class HexLattice : public Lattice +{ +public: + explicit HexLattice(pugi::xml_node lat_node); + + virtual ~HexLattice() {} +}; + +} // namespace openmc +#endif // LATTICE_H diff --git a/src/output.F90 b/src/output.F90 index 483d171ca..81f6e1292 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -246,7 +246,7 @@ contains ! Print information on lattice if (p % coord(i) % lattice /= NONE) then l => lattices(p % coord(i) % lattice) % obj - write(ou,*) ' Lattice = ' // trim(to_str(l % id)) + write(ou,*) ' Lattice = ' // trim(to_str(l % id())) write(ou,*) ' Lattice position = (' // trim(to_str(& p % coord(i) % lattice_x)) // ',' // trim(to_str(& p % coord(i) % lattice_y)) // ')' diff --git a/src/summary.F90 b/src/summary.F90 index 5e5ab778a..59bb90447 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -200,7 +200,7 @@ contains case (FILL_LATTICE) call write_dataset(cell_group, "fill_type", "lattice") - call write_dataset(cell_group, "lattice", lattices(c%fill)%obj%id) + call write_dataset(cell_group, "lattice", lattices(c%fill)%obj%id()) end select call close_group(cell_group) @@ -258,10 +258,11 @@ contains ! Write information on each lattice LATTICE_LOOP: do i = 1, n_lattices lat => lattices(i)%obj - lattice_group = create_group(lattices_group, "lattice " // trim(to_str(lat%id))) + lattice_group = create_group(lattices_group, "lattice " // trim(to_str(lat%id()))) + + call lat % to_hdf5(lattice_group) ! Write name, pitch, and outer universe - call write_dataset(lattice_group, "name", lat%name) call write_dataset(lattice_group, "pitch", lat%pitch) if (lat % outer > 0) then call write_dataset(lattice_group, "outer", universes(lat % outer) % id) diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index 310af1cb6..e7a9645ae 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -270,7 +270,7 @@ contains type is (RectLattice) ! Write to the geometry stack - path = trim(path) // "->l" // to_str(lat%id) + path = trim(path) // "->l" // to_str(lat%id()) n_x = lat % n_cells(1) n_y = lat % n_cells(2) @@ -332,7 +332,7 @@ contains type is (HexLattice) ! Write to the geometry stack - path = trim(path) // "->l" // to_str(lat%id) + path = trim(path) // "->l" // to_str(lat%id()) n_z = lat % n_axial n_y = 2 * lat % n_rings - 1 From e13c60ed4195b94d1f9afc1c355005cc9b36b6f0 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 7 Feb 2018 12:24:25 -0500 Subject: [PATCH 009/361] Fill in C++ RectLattice constructor --- src/lattice.cpp | 70 +++++++++++++++++++++++++++++++++++++++++++++ src/lattice.h | 27 +++++++++++++---- src/xml_interface.h | 15 ++++++++++ 3 files changed, 106 insertions(+), 6 deletions(-) diff --git a/src/lattice.cpp b/src/lattice.cpp index 73758b110..006b7bbdd 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -57,6 +57,76 @@ Lattice::to_hdf5(hid_t lat_group) const RectLattice::RectLattice(pugi::xml_node lat_node) : Lattice {lat_node} { + // Read the number of lattice cells in each dimension. + std::string dimension_str{get_node_value(lat_node, "dimension")}; + std::vector dimension_words{split(dimension_str)}; + if (dimension_words.size() == 2) { + n_cells[0] = stoi(dimension_words[0]); + n_cells[1] = stoi(dimension_words[1]); + n_cells[2] = 1; + is_3d = false; + } else if (dimension_words.size() == 3) { + n_cells[0] = stoi(dimension_words[0]); + n_cells[1] = stoi(dimension_words[1]); + n_cells[2] = stoi(dimension_words[2]); + is_3d = true; + } else { + fatal_error("Rectangular lattice must be two or three dimensions."); + } + + // Read the lattice lower-left location. + std::string ll_str{get_node_value(lat_node, "lower_left")}; + std::vector ll_words{split(ll_str)}; + if (ll_words.size() != dimension_words.size()) { + fatal_error("Number of entries on must be the same as the " + "number of entries on ."); + } + lower_left[0] = stoi(ll_words[0]); + lower_left[1] = stoi(ll_words[1]); + if (is_3d) {lower_left[2] = stoi(ll_words[2]);} + + // Read the lattice pitches. + std::string pitch_str{get_node_value(lat_node, "pitch")}; + std::vector pitch_words{split(pitch_str)}; + if (pitch_words.size() != dimension_words.size()) { + fatal_error("Number of entries on must be the same as the " + "number of entries on ."); + } + pitch[0] = stoi(pitch_words[0]); + pitch[1] = stoi(pitch_words[1]); + if (is_3d) {pitch[2] = stoi(pitch_words[2]);} + + // Read the universes and make sure the correct number was specified. + int nx = n_cells[0]; + int ny = n_cells[1]; + int nz = n_cells[2]; + std::string univ_str{get_node_value(lat_node, "universes")}; + std::vector univ_words{split(univ_str)}; + if (univ_words.size() != nx*ny*nz) { + std::stringstream err_msg; + err_msg << "Expected " << nx*ny*nz + << " universes for a rectangular lattice of size " + << nx << "x" << ny << "x" << nz << " but " << univ_words.size() + << " were specified."; + fatal_error(err_msg); + } + + // Parse the universes. + universes.resize(nx*ny*nz, -1); + for (int iz = 0; iz < nz; iz++) { + for (int iy = ny-1; iy > -1; iy--) { + for (int ix = 0; ix < nx; ix++) { + int indx1 = nx*ny*iz + nx*(ny-iy-1) + ix; + int indx2 = nx*ny*iz + nx*iy + ix; + universes[indx1] = stoi(univ_words[indx2]); + } + } + } + + // Read the outer universe for the area outside the lattice. + if (check_for_node(lat_node, "outer")) { + outer = stoi(get_node_value(lat_node, "outer")); + } } //============================================================================== diff --git a/src/lattice.h b/src/lattice.h index d8e1ba39a..05b642e20 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -1,6 +1,7 @@ #ifndef LATTICE_H #define LATTICE_H +#include #include #include #include @@ -12,6 +13,12 @@ namespace openmc { +//============================================================================== +// Constants +//============================================================================== + +constexpr int32_t NO_OUTER_UNIVERSE{-1}; + //============================================================================== // Global variables //============================================================================== @@ -31,12 +38,12 @@ extern std::map lattice_dict; class Lattice { public: - int32_t id; //! Universe ID number - std::string name; //! User-defined name - //std::vector pitch; //! Pitch along each basis - //std::vector universes; //! Universes filling each lattice tile - //int32_t outer; //! Universe tiled outside the lattice - //std::vector offset; //! Distribcell offsets + int32_t id; //! Universe ID number + std::string name; //! User-defined name + //std::vector pitch; //! Pitch along each basis + std::vector universes; //! Universes filling each lattice tile + int32_t outer{NO_OUTER_UNIVERSE}; //! Universe tiled outside the lattice + //std::vector offset; //! Distribcell offsets explicit Lattice(pugi::xml_node lat_node); @@ -52,6 +59,9 @@ public: //! Write all information needed to reconstruct the lattice to an HDF5 group. //! @param group_id An HDF5 group id. void to_hdf5(hid_t group_id) const; + +protected: + bool is_3d; //! Has divisions along the z-axis }; //============================================================================== @@ -63,6 +73,11 @@ public: explicit RectLattice(pugi::xml_node lat_node); virtual ~RectLattice() {} + +protected: + std::array n_cells; //! Number of cells along each axis + std::array lower_left; //! Global lower-left corner of the lattice + std::array pitch; //! Lattice tile width along each axis }; class HexLattice : public Lattice diff --git a/src/xml_interface.h b/src/xml_interface.h index 8dd1cf580..b619dce22 100644 --- a/src/xml_interface.h +++ b/src/xml_interface.h @@ -2,14 +2,29 @@ #define XML_INTERFACE_H #include // for std::transform +#include #include #include +#include #include "pugixml/pugixml.hpp" namespace openmc { +inline std::vector +split(const std::string in) +{ + std::vector out; + std::regex re("\\S+"); + for (auto it = std::sregex_iterator(in.begin(), in.end(), re); + it != std::sregex_iterator(); + it++) { + out.push_back(it->str()); + } + return out; +} + inline bool check_for_node(pugi::xml_node node, const char *name) { From 8b31730ec442d23963ba51747b9514b582c37dbb Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 7 Feb 2018 13:32:10 -0500 Subject: [PATCH 010/361] Update Travis GCC to v5 for C++11 regex --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index de83325e0..667594f66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,13 @@ python: - "3.6" addons: apt: + sources: + - ubuntu-toolchain-r-test packages: - gfortran - mpich - libmpich-dev + - g++-5 cache: directories: - $HOME/nndc_hdf5 From 0f49193b2935ce02b8cf020fa941d88338d9cb82 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 7 Feb 2018 16:37:36 -0500 Subject: [PATCH 011/361] Remove C++ regex to support GCC < 4.9 --- .travis.yml | 3 -- src/cell.cpp | 67 ++++++++++++++++++--------------------------- src/xml_interface.h | 24 ++++++++++++---- 3 files changed, 44 insertions(+), 50 deletions(-) diff --git a/.travis.yml b/.travis.yml index 667594f66..de83325e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,13 +7,10 @@ python: - "3.6" addons: apt: - sources: - - ubuntu-toolchain-r-test packages: - gfortran - mpich - libmpich-dev - - g++-5 cache: directories: - $HOME/nndc_hdf5 diff --git a/src/cell.cpp b/src/cell.cpp index 427e30890..6b37a6807 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -39,55 +38,41 @@ extern "C" double FP_PRECISION; std::vector tokenize(const std::string region_spec) { // Check for an empty region_spec first. + std::vector tokens; if (region_spec.empty()) { - std::vector tokens; return tokens; } - // Make a regex expression that matches halfspace tokens and every operator - // except for intersection (whitespace). - std::string re {"\\+?-?\\d+" // Matches halfspaces like 1, -13, and +42 - "|\\(|\\)" // Matches left and right parentheses - "|\\||~"}; // Matches | and ~ + // Split the region_spec into words delimited by whitespace. This removes + // intersection operators but we'll add them back later. + std::vector words{split(region_spec)}; - // Make another regex that includes whitespce, and use it to make sure there - // are no invalid characters. - { - std::string re_invalid {re + "|\\s+"}; - std::regex re_invalid_ {re_invalid}; - auto words_begin = std::sregex_token_iterator(region_spec.begin(), - region_spec.end(), re_invalid_, -1); - auto words_end = std::sregex_token_iterator(); - for (auto it = words_begin; it != words_end; it++) { - std::string match = it->str(); - if (match.length() > 0) { - std::stringstream err_msg; - err_msg << "Region specification contains invalid character(s): \"" - << match << "\""; - fatal_error(err_msg); - } - } - } - - // Use the regex to parse the string and convert all halfspaces and operators - // (except intersection) into integer tokens. - std::vector tokens; - std::regex re_(re); - auto words_begin = std::sregex_iterator(region_spec.begin(), - region_spec.end(), re_); - auto words_end = std::sregex_iterator(); - for (auto it = words_begin; it != words_end; it++) { - std::regex re_half("\\+?-?\\d+"); - if (std::regex_match(it->str(), re_half)) { - tokens.push_back(stoi(it->str())); - } else if (it->str() == "(") { + // Iterate over words in the region_spec. + for (std::string word : words) { + // First check to see if this word represents an operator token. + if (word == "(") { tokens.push_back(OP_LEFT_PAREN); - } else if (it->str() == ")") { + } else if (word == ")") { tokens.push_back(OP_RIGHT_PAREN); - } else if (it->str() == "|") { + } else if (word == "|") { tokens.push_back(OP_UNION); - } else if (it->str() == "~") { + } else if (word == "~") { tokens.push_back(OP_COMPLEMENT); + + } else { + // This word might represent a halfspace. Check to make sure we recognize + // all the characters in it. + for (char c : word) { + if (!std::isdigit(c) && c != '-') { + std::stringstream err_msg; + err_msg << "Region specification contains invalid character, \"" + << c << "\""; + fatal_error(err_msg); + } + } + + // It's a halfspace. Convert to integer token. + tokens.push_back(stoi(word)); } } diff --git a/src/xml_interface.h b/src/xml_interface.h index b619dce22..7ccd991b0 100644 --- a/src/xml_interface.h +++ b/src/xml_interface.h @@ -2,7 +2,6 @@ #define XML_INTERFACE_H #include // for std::transform -#include #include #include #include @@ -12,19 +11,32 @@ namespace openmc { + inline std::vector split(const std::string in) { std::vector out; - std::regex re("\\S+"); - for (auto it = std::sregex_iterator(in.begin(), in.end(), re); - it != std::sregex_iterator(); - it++) { - out.push_back(it->str()); + + for (int i = 0; i < in.size(); ) { + // Increment i until we find a non-whitespace character. + if (std::isspace(in[i])) { + i++; + + } else { + // Find the next whitespace character at j. + int j = i + 1; + while (j < in.size() && std::isspace(in[j]) == 0) {j++;} + + // Push-back everything between i and j. + out.push_back(in.substr(i, j-i)); + i = j + 1; // j is whitespace so leapfrog to j+1 + } } + return out; } + inline bool check_for_node(pugi::xml_node node, const char *name) { From f8764416d2c727c5b1693c297c3f7994c8314d1b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 9 Feb 2018 13:29:04 -0600 Subject: [PATCH 012/361] 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 zcmeEP2|QHY`yXo(5<+PakxKS8?ltZmOC%~qJC&lclonD_+O=p=+9Z{fii*%WC}|TF zX%{0zWi2i8ztcT;&TD?}^uBuY)9?S@>60_hxzByhbH3;KKIhz-bMI`hXW2?i_LgLD zUlI}wQHJdIx743j@RC?7{Jn~jspB5tjSwi;gEE0sX9#`&$6zRf`X0bOzn<$D8yf~g zm_ga6N^lxOPn3LT1}aqZ$QC6i1-kryjexz4wF|d{$)J}3po~tWA`evj;zEcaPDC*A z0?i09$cUp_6(Qo8`(Bo)CXX<=+6*y5;?@fb34d3WAQ-@XBSIMf+FV`kOYRSLM;eDt zU@#)d1Hakdv7?+>LVw5-L1LtX-#baXjRi85M!pXkQEz{e()jZZQTZXiAE`%aHv?ID z09nK1|0qi1&+Yy0wSw{@KYnChlogm4W#!49dVmfmW8|AKAg7Ne-^c|EF4&)#} zq+%e4tsvjj0(t!w@{KHjJMsW2t5V1H$T#lbco>_MwZQS9Gvu3OaJ=|2DHj9TrG}Ji zft>q|lx4yEkuOaipvJwp4=LM|YXADbLPVzFBBBhCkOjnZS0F2r{{kTZGh^{K$6{2z z!3qpRo3p}l46WN#&;pGBjR1`RjR1`Rjllmj0@QUC8!QCuK^c9L?3SSQg|abuoLXN9 z((?cdH%gBJi(J<-1g>i-J-YmNB0$BZJLo=nZOve$%eBRWTCcjat^R3=y1u2_V|O7< zQSAiDl5#ZggKAe5)Khw42BcoB5m{3Gu;_GjLuPh5~WkV*h!Ie7zWq z9;#O0lM^Pxs2jtmRfIf)I{$6SQ@4qVXRP2A5@oS%DJXyCDv^50K%LH8ZE>Tewg?Ey zTLB=BC~Yb}sr*6J|7=O|O|K_u!3DI}UcOO&br#>uR7n46zWr<@{JYK*#J7^(ZE>LD z`d9NUR+(&v4ccoj->A>uI*V@!{Yd|5zO{`PTKaPY1o6!U#1VB?NAXRz57~kqXs^9| z1It5V?b$i&No6(Cf0}Ppq|oJ$5D>&S_JFo$brj#i`jhR%g7(_WH?VD^)A+VtgY=)~ zTibY{r9VeN5a0Ab98qU=6yIvW{2&Y3YcJo#fPATVnG~H z=XMm|JT%D`!a#fN<(oKgu(SB)txY;W^NorWy8ICWg7_v2;)pu8qxhCTm~5eTC|S0b zZ{X{gPFqjr>yv)de4`?TE`Nl8Aijm^wmqw(_-5}$w&P9gDCWM}%QvvSw8QyEeecQy zKs3jZ{!+N89wuexQL?lL2fX9k>O}%MIf1Nq0eZ#9+v+h+kg_V+=RoaWjhxt4&pnBh z(?LBc{l0&x?}3=ZNFO~woV9n{B|sNDdR;?*pZfd8((mAzp!~@O`zk0OsXXCARs6$} z;t~Hoj`s4XEAWHn(LWpz`t*N%y?pBiy46{HV-h>|xG$P-Bo6f7A0r@$Z%+)`p={q)j|pTC2cYnq7u5AFKMw89 zThgGPot3w|?MMgcy!D$0+;8HZpu8mu;)rsS;=c=3@efOiZ~XbBy?m1aesmV!ia~y( z`SuS3;$L#QAijn1&66F)H~x9Ky?pE5$$YaPMLIz9?O#Hef7neye5-Y8Yq+EMmh4Ej zlMmW!FW+Q=o1HbkF~*V((0pqfFSPXM2ngbv2YBulb=I%u8+9GL9yG8X@Q!MSpNEk^ zuNllIl%A?Hd4LK0jRtzjfCrRbF_4?bkqQ(~CBXfrlpg>6r@-8ATTttAejM7Hw|amc zc2?dB7*9Gt=dIs1)P5_+1m!Jd5J!}=6t7*Viho#AeB;k2?d2Qw7?955TQS&IMf2?+ z2E@PQbU}Pu5B6nJFmx2(`1d=umv3O(cBk#XV4o!sqWC5V&tkrPn!g%PYMiK2&~sxL zQ``DW?{lOOqsu=-Ku|mgNAZm@n`}W9wAWs~ zQIDPMEWSzjlK#_tqauYae}sS_z9r9VdsavBEnp7WP9$iry?j#!Zgv*m*z-vTXuh?L z7h3vr1O)L-HK48jujU(d9n1#J6ocy^svZ7$FBj<91AbC^{Cv#^dg)-^q4Y|C%mDo= z26_Pt$TyT;G?3F50)^kapw{L5{cCUD>IM4QS$Ruu5$OP(w|?`0`%T;vl(&lccuVox zc+?k^a+sYa1`L z^ydf&;+xirw)(%CZ`5^g323GSTnAC@@XvddK+gs6lhWhot1{3F0&gfi{@*L91$t~S zKT&#>K$hU^KT!Ye3Tj=>?{|CiR$tJw&RQp!ttK6y^VV+{V!x$Bg7Q{Lcw2WVUc0od z{%MKgTkJ~G1RJ#1UcU7M9&{Gp64sFp(0u#pQ21A!D2Q(^AdV<7I*Ml98x<*Z`6C1b@r}K)?O7egw=i&nWh`i~y?j#zZgv*m^0$%>(0pqf zFSPXM2ngbv-j=rdznX8m^E$e}0qz2iyUVDLsB( z%Yp;BV1A$V zHN|U}w$(o^QG82?Bu(Uk_S(xgHQ+&K@hy25=>W~QpALn8)ro@m<__YB0;8k&ro5AE z!3?z5UcRXV2Rn;zs(VQXXueUALYF^6KoH+@_q08$qxiOdH`z`CXs^9|8wlL&EWQ=T zkPgs%Ya1`L^ydf&;+xrlw)(%CZ{&5bI71m24g>2-svZ7yrYbm43$B+aJ^uMo3motO zJf`&cd94QyZ4CMSnWW6eYs}gL$qV#0J1`$2#3m~BP zs|spe&hK}7^Ogp1sI&5xeH`fkowt5fWc>fOBPegx0zOi`r+DZ>Rs6$};u}Bz+siji z;74ciE&V9z0L{057!d!G(*^O(1H=&pLr3wA|94T_%eO(m!Or5F)^XASnr~F3(B+R1 z5X86qgtljO6yNxNU%0(|(*kaG7T-!HkPgs%Ya1`L^ydf&;v4g1Tm4_nH_Ey`cw+!c zLr@xl(ioH`pfm-g87R#`sjo-A(FX?gfkAy>P#+l72L|r0rpIQJriKh1lThH_Dq016JXC2*fRz8Oo2TrE`vZE{I?4FIbQ!Qr2jYD zAwTC6V?=@gb-zjWgNoNMAjjS&>!ZPe+&gXcYJn_!kF1XcdS>};^*n$acAuC*kJ{=nf$Z^^tnaM9)aMTFNu&qq6Unl@>#`xB1D&-l(>qT(kPQ5w z^9KNeF8&k&LF=;O)VAt?z6_VP)jus!e2YFunn(xjwU=*0fd`$%x7ZBQ0h(_=9SZ-d z69w_j9>fs^Mn~~YB8_Z83$)i>zG(voJBx3!mq`a`zEP1vmp?*45Z}@-wLPn&_!e}5 zY$qDD*IvF218#N}-*Pia2WY;vjTc(_a|8tOO^e-D|5x*kybkWd@BoGbz`BxZhku>v z4Gxrm>m^E$e|`)A2V4M;DLsB(2Y~}@Fh5axl|YsN{SN~Ng0e_FD7{!9v#*i$>%oD_ z>uvR9b4Xb)mn^C80o=h2nUuXSAjb|M_5SW(YF*CncYE`e4rs8m@|GFEjm}$tcjW)w zdV=y+3E(4z4#h7Qs^TA(6yNyy-(J3fpONdd=OiV7d_?o@9|pv~Qnr~F3(B+R15X3h&_+E%Qx1;#R|2xAJ@{UX z<{K3$bonC$1o2G|#1VCFNAZpS``q^O4QzYpG`=;1@0V%5QISHIKSDqd-(o==QRj9P z-}v`Mw3ly&z`@R1PkI-T4$yp~B84u0gn%Hv$$~hd&h03^@$WBcFW|4dnH&$ogDx zAi1=yUNMlHIb?l4&{KWgR?i;D-j!s1G0=;wYO9wH!YvpR}z{OjfR@(tYn z)@ghz0qb{~Z&akv<&O{$#JBYzj;M1xif{bao$ckDIdHJE)|1h1Ne5`YQISHIKSDqd z-i_(^t!fsB|v60 zlJ&JfPphe|o(qrzK9cpn`(NsFC;rdD+MBnAgN}Aq-jV?0L+7pEJ>q^3{{-c&WRM3a zFR6UuLRI|3lHwbG-f1u2EPx-K#kU}k2Wh_j!+`jgoGyrOsvwRi7&?k?{PTBv`DWS4 ze5(ZO37T(Iq|oJ$5D>(-NWOJxNAZn+9o$~NA)U-O_b;RaG~a#{W%Mx$0YQ9YfH(Ng42%n%w|=(>`#t>;l()1%98q3VEOwzP{$WY+jX$5Xmv2nqM`!V^ z7UVaYZ~rhL{w1di;#)M|JlRovp;~>#wo<9W)w2+CV}AdaXWP`v(Cd5hv3e?Dn1-&mc@x8|NCGBn?) zNTJIgAs~ovu^^79b32M}9`dB+FwkCm`DOzg?5z3CyBEzjDn{t?uMrT$H(3xzl!cDs zTfQP`xmKAh+sijw;9zI*O|>7*H!4Qx@~;sP#J8|M6vIdxl=Q3ClhpGs^uTqi8JOp& zcKGK#GoY6M_(|#U^OXtolzWpJlpYhv9-v=L08PT+w*AWoaxM4)9DAUr41O?xXAj68 z)B}Qn-haEG*5&;DYj57N1CDom-V*x$&Ac*bkMh46oQ42Fv)sX^%l0XbTWtf6?8JGiZ0Es$lk$$C}bICEHAJ#Qed*Cp$J zwtxSv^Qd`?A1^5elOaktA;gd)?){fwcrNt!RrOosJKbx6s*m4HuOQ%{EHMD&7%;!r zP`wrb$DtjC?{*e=dCpkq47++ z&ZXk;SM!d4K8JkyIo@&SO708V`8nQ!%YMSr-<$6kdL%+L@4mxN|M?391o19`Z@t=4 zyyIUVL%#eR@2KuVJ3q%eYQ6e*c^70z^NzxvF8>+jBy5r-+$7I zD8K(i3{jeK&vo}A6}Tn$Jjy>0?!gXtQTwH|fbDc}T}rjff1Roa^z^_wn$qK6SAXve zp_>5aeM*mi9l!+kR6(Ai^!W3ZJp5fZo`%4`IQtMY#tPrA9^0|EX#stVk)2m72TFSY!%XM6hkdV+0^ zj9ET`fyA}~UjCx;0j2*}CB4t-uMqloALoxtD!-N3x19$l=~vC~*nZ=X{v z-~}7_LFLU_u=41O(+BcRn6dto~JbhvEx=-e@mhD8C>R_!E7J+plQUjbYRZzW5a*YPrXS$?evJr21VJ z5f;;PCXI6ca{qIi<~I9%3XlWVxCh1?hfZJ+`zC+Ii<=j?IfUvLRWcRYXFX^N={$8F zDgCt#?|W^}qpl17>P&knQj=;Aey{%HGqpyvZGk$E@|S=)f04^4FiY{jc!-KlkOhfB*bcNQ~T>@mKqDs9hb@{uM#{IVipM@6*u& z`*g%}7m&uef4TpueL7szyzL_YxnD=?dw>4!A3^(ae)ji9l*37Ppu+ZL(wseW(=f%~HNX;Ip|0U;05e`y401ZV_k1ZV_k z1ZV_k1ZV_k1ZV_k1ZV{QUn7um!>D9Myf8|2a`B6x;HIs;c(VHu-EXlTSv=Y5+y*VB z21TBn*j;Fy#vpyvcjxr2UXmPS)^jyO6Hj$?f89 zAN>L!C1)`!s=WY}RVU8Y>_8~l9WiCto$bN1zcOyvi-KG!o;={u(^(r2UggQAV^$x0 z|4M-;e{qaIr#O8Gnm;|W>kz$iL{5LwhoUKJXm(MN*02W{YIg9w#0}SKM0UsQmm@#1 z5siph5f_#T^Zb#LXC{pqxt-_FNTtkUo4&U4+VC;eUKm;=?|aH|>^o%c%TvvZo!N-K zc=G!MQRvTm1BSYN-8NqPfr|pqelGdMlWR6iy7O&WU!Fffx4jkY%h)_QY@Xsc19LcD z%EO*#Sp++w0eMSzmU)&VTehuncwnoB-pRU`v;7E;o=hyU`JDF}so-3*8|9gagdDvg zJuy#&htDwf!qQ?_I9_I39KW=RHuL=1p?Jw-%6iy;jU^fy*#V1q{^VYEpAk0<;(2Mx z*}k5b5&G3|-_)~OWyrRx9-~7VmqS zjJu;CUs%mx@Dt6iF?8v16wjYoHN9F6bp%y9^3>-;H)d{3HjAO ze8#-J_7XgQWOVv@EJWpb^0d00)>jlEK1Wq{x#cOr_(2bw+XR2nL=}$e?mr|{jugfp z8f_P>j#|!`x%*fe3vKfAoEo{i8tL21aN>tIY(#EA*3SJd(4XM3hGI_#L3}=>xt8~} zhWTgdMUV8Nvk)(dGrf*Ah+N|NH+jNn`H!n0zb3IX2RtxE&{Ubp9-|g<5M0&wVb~-! z^ovlx>(j?$sQsD(c^QQoWJvCFwNw6VBm>{HMqd*0YmCO{8PW|fAKKVvPEWrh!fWsG z>%IEDXF8+^>c?vAF-)Ud%>F+tnN25iltHK7L7o`dY zHC)8dGjEN(iWI64L(N8C{0kd7rytVGwi)`<%c$4KgW@orv#s3jEbwdK@on3M)Y&Ry z;d~K(vLV1W2j)Y&1H;`oJ}~|-gamE;QZ^7RVkmm|IA4n7AAjXJ-$)TXuOohpxlgm}~Uin4Fvfp)s@$ zx4BO~LVO}u-qy(IZ;bB#cvy7r`!a-uJ!H)@QAaN{3d`N_wL$%{THon2YLI6~S}(+! zvXPz4lWz(UaUS2VjO{-${Vv4kef(f`p^Gpc@(awzt;vIYU;QrCB4`!#Cw$#*e{ols z4;vcK)rIb1qMy36x4TU)M{3tJ<@NY75G`K+pvS3f8`N}`hKf<;dt`6x*1i30G7-P( z5jXdohkRf6lCd$YZxku!an7Q?+m5u^SxA2%d@WRWhxk2cX>{oQ0yGy z{1x&`;#%ED`PXo~W?(rlBi2BFR*VfPX_1lR`7>s<(!I%6knfQbB*O}YVSYZhO?tk} z9t(77Rp0~VFb+}>we_Q%hdOE&5*Q`c9Y?jh9Ng^HuLk+jHL|iioQawOQsW199THPp4mOFdl?M+3$xkHOzpB9mJJ!{Ukc#@E$1x2$Ht@lsu!-FF}d z`r|cJuR-q|%zskNm!j(~LVOG>3^+OUFh6hDEu=b62#(jFZ~L+=Rtd zj9zfQc$szT)7;x|J#SKXA=q_JAP=uJ-PV{I>o2@`bN5QS7bByKR^CF~A22wGOwQ>d zuQl>$V(i7niK#=;wQh5ksRUIbvi6s^Z>nJ<3+7!taLf4y&)xu!ae={agL(GUXAF|i z=D>WTXYZDJ{ttX!Wi(~>>>+S|zj7ugPFM%#gUc1;GN7D|Bj8V1aAbajJibpM1 zM+f@pdpW$q(fdO$%`0fAK^nVmmos%^BYV9AWro^UqJ&hPdvxpNYIVSaXt z+dpRHcp0Amhn|Fwe&YcBNi-9>lJ*tG&zKd~Vb%LAP@Q?r$9%_dkXVzXp|%^<&|@d< zTN_{E=*@(e$KtwIAqg&T2cX}sBG2BGI;y{c{zQeG=+0Dw{yZAm!{?>}TyN|Q9MU~o z4f6dP=YY~t3z&c8*B@W%mIm{og3|lTYi}5!xi=K(0DG_TRd3$u)tEte*3!pQm{jUVi@4QaMoG0_MZ2^DiuGbKv}Sv#x2&=kBoozSa(* zyBEWJc=uSLLs%pnugaC;JCbk<^jd@HqshZKh@xWN+!411ptYrb51M=6=z^K6Gt;n2 zWMD$o#n*aQk?nWI8f)jn@tW!|H2n1q$oF`b@g~X1Fka-Yo}H0x(v645oO44TS>A+v zx7g$&;_x2MC$WoP>|XN92tCl6Shki}ALfK#ynNnV4b9TLb}&jBLk-Ux9az4!3Naej z!Z|Z96Pc!QO5W`;oKGDb_wDbx9P<6S^pX>y`X6|F6PhghxK}OYd;gd#$d@$`AIqp6 zD|@eo_@tPY`C3e}Ky|xpiA;6mAl(eQj%H0(L-z-vuN7=?)N17&R&SwNPkBjHaK&YaPyd7C9?s0_!o#EL^nTwLM1>P% z^K+HO=aN_^A+lA%I<>Uj7(nHbeo<;ZIA=)O)SCrgdHPmmxh5n4Hw}Qb2pF+u<7fwbAXfUTPcURv=c9NgqELvypou&mv4dzUrpfV+haQ$#2mE?Of7Fn-3wk7XVaf#YR< z?Bw>?H!wdt_D)&qq6+&jQnsLKwj{*ok_JmoE)@DR>8ZKS)YJNCy*FOk9Lqrl%|AI? z`;{6xGWwHL&uKV%@m$223jHc%d(J)g;%YYX8huzeO&9u8;c`*jG8f{r^h4x`=h<-m zvvZfmg*+jcf4V1xF)nw3`T2r;b;RjYFrF3amgb%6qmAzIQC$6E5C`!!i?ti3tB%I3 zJ{maJoP|p5DRX4*c#B-IzK|HEoryRopN+rd1mpR}_+6=nS0O(AKdm%=TLba&LvC9O z<-_#``#`v^M-BAHQhS2#j40^urLOqNC4@&`txki z@>w5N9mX3F1F;(G4+U(bB_hP^VmG*ce#BI9QdtJ)i_bfD7=O)x^H*r{u9QBL;duEp zJlVWB4X($IYE+$GS^J58)u_iQB?AZVW(YC76aC-h!cO&uvHS zwQE3%ZR(|tyR(rkK6T4p2Ez4Eb<_Cy7e~SIdUy6IqVy4d-m_5mnNrVgknj2vto`fm z!}&|u#bURtEu6oQ6WVS`s}VF*)8=V_6$hy~+pj)UbRg<@^SKL5A1`WIMp&Jh4BVh8tzA7W(dgHL6 zR8Z9jcs!zyQ)yAGIhuFj_SMClGGt#Lp;s8O{^@F0v(ph}p(g(KopUvs5L4%v0bhHw z5jz!MH%$W=KcW-H$2EJy_=&mOWAQsDh>y=E>&+Iba6R2pK7TH2Tof-K*u6qrv;TXr7`*UxL*uk`X=2iNzzh6snfItj;Xz&4e|B?3x$;zWlsC*jhCAd64>-g8Ghjf3n-zJnWBd_`~(Zr4%b{Pqryq-B)Y)*ZUkqsPb-AqSrul z+=;znLC!Yl^};cy7~>m|b*(3mPp+BBu+>wBci#l@(en>Wh&yXZF|{lDD~pSGL0-Z{O^-#Z8LeeY=Rw`#ZGd~tbVmj@maO}uzYF*&pI zi5K+G+|zhUi}@a2{O0-eUCVg(GM405HS31++FvZpwj8Mm<5xv_Q`lN< zDDVDyswwvgjOXfSLc>P;!2H7;IOp6qgQ4iq$q^GL5TDO49QifgYPbe^aLw&7p_eRl zf=9*9z%O-3-K>w<`;#(}`m!+j%g^9^A>vvvGE^G!%VE~MCgvZmmo@B%YQLz3*E@P8 z>za!n!SSlE&Fgiz7LHepifCGVg&BIrg*oEf?J~r@QSr-Bdv%nfI`d)pI~;Xcyyd8o z@H^znRiXZe>#iUZTe^#HcnQbLP(>nK%n|ZyVVbO@@g+E)Hzrgjo*D}C&mNV#S^HnY z{4+5?S*db5JpNj5)+WP6hUlL2ehbvcbC98%mk+|28fbTkWZxxW_UNM>l9%pRHXyjC z$2xnpOk`ey?mp|uU3u51H+BWxsXYes;WCq6?umYoUl$n*@0vJ5zK?v7x^R%vHQsnL zMVup7Plfq8d*ZUByNXQoU6pf0@Tqb{N%Ns8+eQ=hKltiO;VWBoO}xb>iNZRh)@A8e zvE)p|aM{koM{OX#mQ1|tx~UcN>)h-N%|4-!Pp0#}wJ=p6-uBlLoITxr$7+ zo}oItFN_~e@0&;V*~9UY6xsN2#R@oox%ebKj~xfEhh3K@RUM6n__S;)H=p|y=0iLC zdzR1LEYVwjYc^X==OCv|cW&vcrHSe(J+R-r(H2!sn|n#m?E?~fF{odv1{;~JvuaMV z37jupzKv*++6?hY8eDMx=^s8ntVWLv)3kus%jw@fdM$ee@tM?YpI=!8`895v{NQb- z*61iTb9pVi9Chuz(1ASya^=5&7(8}x0;M%9ydYmq5Mr8O2;GLg{-CNCep6Xs{l z#o-U+@4)$8UF*v8%U|L8%g$K$Sll1JAG|wU^hECiaQzcfuTWbb0^`f)icahnClmBg z#hD!+kC!1K9}a#@U=Bd-&9cuYFT&8xU&?N@nARdu-8T>TDslxmHd|}aQ+>F8E_kJW z{PrvOd4gZXIKB_Nn39mi?^K)sg^{)HNpg&uy-FmE?0R6FI@AOvasfXSu+%ROI1_w!4 z{A^~IS;TrQ{-sd!3LDhAW%U8SGj+(;#c3f0k!&P9f16KVHuPsiDK3@thwls2iwcX5 z4T1bVk*yoFXfoupXm5__{aEPFgR72)9AD^9$e1NglFRkb%gFPdJx6m8&E1#2y7_CM zx!3ya-D+=(HkRf`UYOW~NH~vA&{>{|2zLqMNZf?}ywviWh#EqEP0l}Rc`yv}JtA*e zt@j2PucLdnEL_Zi{>TkR-=PxFpQ@r-7t38{sE)f(UD!-we@ktte)F4w=yY@THSbL} z=$xIeQ&Wljyrz$W{;(uA@^t+9Z=nW|U!R2^7Y-_cypwf4t zwsIGV{Stnw=M1<5{fV)@vZd7l`qLVi8xs)*_lJDZ*yp@)CA^+b*Y192!f}XCQt{ee zrZ1pBtM52{JF^c*Q${o(!~=@hBAflNML|zSj%rk7tR)ipc@OynNusI@NS%5ad^R<(0ba!Vn*(-sNS5e$b!! zN#iHqPq9Gfw_2@N8&QUcrzY1dHd069XH?uTEN7t(TZI*+Pt+mLPV1N39=d{rZ`!7# zlMClBzn-oSzKnHCbX+AirwoZ5<1}^OU1d~7Vc3CrpA1mcPV&x~$L|qMxwZPnEH?5mO)}i|)hnL8 z45h^7=Rb$>@?q=L0MqCuXurm3I?`h^?Ej1&(;mpR!1bZ(%^L>>Pld-F9~)dvd2fJ* z-rOpWsViR;7GEPqWj%JM*;(Ka?{{R6E3p@;^=Fshg8utGMn`)i@2oI1ouIz&4} zX*9%V&*>1=1sfqgX8mt8IQYZ-d{Xi0!d^LWywc5*TUK_1`0ZHNH)zrYGqeg%JE?oP z43XSb_ANa{9X+!o!Eu?J4La-Iu8}KG)*?${RR*6PaurGX`a0G7ER3H=Jy>rmpF)3H z<#%&rkHYqsR_;93*bA;#&wY!CpPvH#i3s-DF6{&TsTiU$t2xmSz440WWBrDMI9WF~ zX&xSk?%0`C6&7a{1+Qn~1&EoRUkYTw^tuLa+KKV^p3 zym^5{rQf0%^L%D{ao>J_E%5T3v5@$I6{^B-k?(Y`1q=pNue!i=UNgDpEcEg9;u@mv3uJPIpLqS}{ix*qn?l5c+qp0P`%(GtJ8l2{rQ~ztMTAA% zPmxV<|8oC-zlHHX?#-ugKLJTnm6PQV#FK7iwDrf;cvjK1s%&n3j ztZ-jiwq)t?U1BgI(|&A^S|t}}{aBA$52^}UP=fY4s|Z+>vh(~75qx&Ev&dz=Mr_ED z+xsH+dEf?OTeFs(W#Z4ye(TC{O2eM&Cz_=mD8sV*-n($)Kpg&wGc#s}sSJzt*;m|x z_`S#HQfb>1J__Nj@yzIfj*Xb-eVyqBC#K+CVs5SrV>5BfC6OiXKc!=PpUxhs8c~XU z={t1Y1;WnY+>K$^tYlbui^s^C6Lt#BuR2cKEQFtz)_Po^(TH_@Vrts-crrd3-MdmZ zmWfMF+xh7BrgY3T{_fWd$x@6h|IzU}VaGGSad5V^3`^ekQeUn=#uhtf9m^2LHD5l< zIAqm`wTKV6W;c2o-hH_4({mS?_}CGlwH}AlvG<#-9W^Y=u#ky;9n%RrtZJ#j^%gR$ z&<&A!ri7jPUhn#JwXMN+GM5L5#?)g@;dA;MbhpGEmpwlqFNxyjRdcU-Mit@y3M1sx zv>sp&t$k0*5Wf#OELD1b&=YA^xXNU;6yo6dmaw+%zV&*Nm^QssJlEF zUl}ju@N6;@|6CBv)~w9HrdF!F zMDdGiTYOIKZp1<+R97AC>WQx|TO_kU!W!@Wwq`;6+6*k#*|$h-AqR^Wk>R6m z<*05f!?IoayqgJOM}O$PuFi78_{&qfEnIY)um-Usx%O+O;QOa-?XhmDHU3IjvviZh z1?&GM!e3wt~=IQl~m~GZ>CH1Wycvwo2#6uTr{P8F=+mxC# zY}bYyNss4c*uJ!|m`jA6v)OUS-lAJmzG5{~nu) zaL+ycDq_Aoz?c{89ps5Pd~~EaZrCGfmfbap9;rkeo_zM!b7+tVUUf*`XX5lG?CceM zl!2)SzVCLXOyvn{JZMmvocZz#*o~Y$GMm4aVPf6BZs+DK-^`Z!S&i83x3l_M>P*2U%>!1e7BTVOS3(cU=VV~R#~zYq*py;JVzw>G zChXMgl8?zG`qgb%>{Kp3b-p)o8)s2mw6JXI#L10Vqi@UMPg6YcGZRmpF?-C!FJ1C# z(b$}ZEpTwXdUFs5lN6SJpG(+zB%5$%9}zzf)0WQW^4)IZ&CNCOqIl1zOV7PJ+k~B0 znDYEWfG2L$&G5KJy)_=wb^TXnKsx5OJg>>}JO@)re7lj$_oa8@-$@$CuY? zUL)*Oe5$$qjIh(=xPzi4VQ2Nd^P@^MMDfcGo6nqG+K6pAcd>iyU{8FdoBAlbIwtNv zYh;+(w=}Hg`IYr3;(6`S7AaSE*1V8CD5%O)z)7g!gRFS3E3=r;IPXG+Vh5 zGnQ;Rn|j?7e>|CWLg+9PkC(h`5v!YqJyHLZTDX>jITbVuar@=FEl5n5h(oE7`I1~a zDeTd6pQsDtC-O&3d-Aven<=#(-^!Ybd*2Qy%W*8lN}p`h z$|39|F}rp@NQ}E|M)g>Pu(Ruw-ReiS#JE?_7CKwkfDPO;Kk~HmWc*gqohX-uOgv-s zB-V#f7qGB|gMD)clwwc&ryS(w!y>Je0Uks=&x&>$|vju%)QOUdFwrY>tsZRbz;rlb=>#~ zt?xI^w^bP5Jh*;EIWylxI*n7{S#Iw-nXJ_ z`Io{X{Pgwsgx7l>V3ugeLb_Ym{)y*=|!ogv~-iCMQi>4gy9Kjh6+tBOWU z<=G3haavRGrWc1RFQC@=+Nh6}$%ijs2A6L4(|lcuNsK}~x%1AJ8wT|eL>_w`>!fT% z_%kHkW1mN{Fm9byCH6k05t}jdrQE*R({T4(Tm4tPiFujPZ+-7s8CdVeh`OQu%doxv z`&isOrq&wdr$?+ej{2IJaK~NSFlWY~#lpD4Xh)H2(;BgA_oFv&rcA|O7#ABPJz(Nr zHJ`k=vG4*`xu8V^tt!QQ(eMx4`SkusyId0jXS;-Yb1ps=hMM`~tVHm#y`yf3FKfh_ z!V`*|hI!yG%N*W}dB?voGN z|0ZEaVVD$iAc2p}I3+gMPEDP=QSQt-Ec-~{VY@H&*y7!uiRtkye9g*Mo45B-e9gG< zamBmx@z762GCEfaF}b}@Jtc`ajJK-QHM}p)nir&X{Tva8ujYS=8-G$5kKg`PKA)IR z-4;uHOVF5x&sa6)i`Nq-p0_en-!U@-Tb9;W$XLA$Gahs!>nh>T!dAJ^A(k>M^|kM# zx#MM}5&Nx_A&OV7x;>%%TqCxj`Sc{YdJlZa%}I;R`dj1qYj=vydy$THbDt(7XT!m^ z4exn}I}ge|ofz&-tPcY}_gKZ{`_`#__MeUt!yjMKPQSFh3ESUb&VC&_9gm+9YBu

+|}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 013/361] 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 014/361] 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 015/361] 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 016/361] 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 017/361] 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 018/361] 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 019/361] 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 020/361] 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 021/361] 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 022/361] 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 98af5ed6becda38858f0d0b6baff65b64effd1da Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 14 Feb 2018 12:20:49 -0500 Subject: [PATCH 023/361] Fix complex cell tokenization --- src/cell.cpp | 53 ++++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index 6b37a6807..e7b76a994 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -43,36 +43,41 @@ tokenize(const std::string region_spec) { return tokens; } - // Split the region_spec into words delimited by whitespace. This removes - // intersection operators but we'll add them back later. - std::vector words{split(region_spec)}; - - // Iterate over words in the region_spec. - for (std::string word : words) { - // First check to see if this word represents an operator token. - if (word == "(") { + // Parse all halfspaces and operators except for intersection (whitespace). + for (int i = 0; i < region_spec.size(); ) { + if (region_spec[i] == '(') { tokens.push_back(OP_LEFT_PAREN); - } else if (word == ")") { + i++; + + } else if (region_spec[i] == ')') { tokens.push_back(OP_RIGHT_PAREN); - } else if (word == "|") { + i++; + + } else if (region_spec[i] == '|') { tokens.push_back(OP_UNION); - } else if (word == "~") { + i++; + + } else if (region_spec[i] == '~') { tokens.push_back(OP_COMPLEMENT); + i++; + + } else if (region_spec[i] == '-' || region_spec[i] == '+' + || std::isdigit(region_spec[i])) { + // This is the start of a halfspace specification. Iterate j until we + // find the end, then push-back everything between i and j. + int j = i + 1; + while (j < region_spec.size() && std::isdigit(region_spec[j])) {j++;} + tokens.push_back(std::stoi(region_spec.substr(i, j-i))); + i = j; + + } else if (std::isspace(region_spec[i])) { + i++; } else { - // This word might represent a halfspace. Check to make sure we recognize - // all the characters in it. - for (char c : word) { - if (!std::isdigit(c) && c != '-') { - std::stringstream err_msg; - err_msg << "Region specification contains invalid character, \"" - << c << "\""; - fatal_error(err_msg); - } - } - - // It's a halfspace. Convert to integer token. - tokens.push_back(stoi(word)); + std::stringstream err_msg; + err_msg << "Region specification contains invalid character, \"" + << region_spec[i] << "\""; + fatal_error(err_msg); } } From dba919c6109009670cc41decae356588c47c305f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 14 Feb 2018 11:24:24 -0600 Subject: [PATCH 024/361] 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 025/361] 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 026/361] 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 027/361] 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 028/361] 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 11336f875f67e8fca8c0ded359d7b66d2316cb22 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 14 Feb 2018 17:48:24 -0500 Subject: [PATCH 029/361] Add C++ RectLattice distance_to_boundary --- src/geometry.F90 | 66 +----------------------------- src/geometry_header.F90 | 21 ++++++++++ src/lattice.cpp | 89 ++++++++++++++++++++++++++++++++++++++--- src/lattice.h | 24 +++++++++-- 4 files changed, 127 insertions(+), 73 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index 1f241f66d..a6965cffb 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -422,70 +422,8 @@ contains LAT_TYPE: select type(lat) type is (RectLattice) - ! copy local coordinates - x = p % coord(j) % xyz(1) - y = p % coord(j) % xyz(2) - z = p % coord(j) % xyz(3) - - ! determine oncoming edge - x0 = sign(lat % pitch(1) * HALF, u) - y0 = sign(lat % pitch(2) * HALF, v) - - ! left and right sides - if (abs(x - x0) < FP_PRECISION) then - d = INFINITY - elseif (u == ZERO) then - d = INFINITY - else - d = (x0 - x)/u - end if - - d_lat = d - if (u > 0) then - level_lat_trans(:) = [1, 0, 0] - else - level_lat_trans(:) = [-1, 0, 0] - end if - - ! front and back sides - if (abs(y - y0) < FP_PRECISION) then - d = INFINITY - elseif (v == ZERO) then - d = INFINITY - else - d = (y0 - y)/v - end if - - if (d < d_lat) then - d_lat = d - if (v > 0) then - level_lat_trans(:) = [0, 1, 0] - else - level_lat_trans(:) = [0, -1, 0] - end if - end if - - if (lat % is_3d) then - z0 = sign(lat % pitch(3) * HALF, w) - - ! top and bottom sides - if (abs(z - z0) < FP_PRECISION) then - d = INFINITY - elseif (w == ZERO) then - d = INFINITY - else - d = (z0 - z)/w - end if - - if (d < d_lat) then - d_lat = d - if (w > 0) then - level_lat_trans(:) = [0, 0, 1] - else - level_lat_trans(:) = [0, 0, -1] - end if - end if - end if + call lat % distance(p % coord(j) % xyz, p % coord(j) % uvw, & + d_lat, level_lat_trans) type is (HexLattice) LAT_TYPE ! Copy local coordinates. diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index ad34c735c..101bf8f9a 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -95,6 +95,17 @@ module geometry_header integer(C_INT32_T) :: id end function lattice_id_c + subroutine lattice_distance_c(lat_ptr, xyz, uvw, d, lattice_trans) & + bind(C, name='lattice_distance') + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: lat_ptr + real(C_DOUBLE), intent(in) :: xyz(3) + real(C_DOUBLE), intent(in) :: uvw(3) + real(C_DOUBLE), intent(out) :: d + integer(C_INT), intent(out) :: lattice_trans(3) + end subroutine lattice_distance_c + subroutine lattice_to_hdf5_c(lat_ptr, group) bind(C, name='lattice_to_hdf5') use ISO_C_BINDING use hdf5 @@ -134,6 +145,7 @@ module geometry_header contains procedure :: id => lattice_id + procedure :: distance => lattice_distance procedure :: to_hdf5 => lattice_to_hdf5 procedure(lattice_are_valid_indices_), deferred :: are_valid_indices @@ -281,6 +293,15 @@ contains id = lattice_id_c(this % ptr) end function lattice_id + subroutine lattice_distance(this, xyz, uvw, d, lattice_trans) + class(Lattice), intent(in) :: this + real(C_DOUBLE), intent(in) :: xyz(3) + real(C_DOUBLE), intent(in) :: uvw(3) + real(C_DOUBLE), intent(out) :: d + integer(C_INT), intent(out) :: lattice_trans(3) + call lattice_distance_c(this % ptr, xyz, uvw, d, lattice_trans) + end subroutine lattice_distance + subroutine lattice_to_hdf5(this, group) class(Lattice), intent(in) :: this integer(HID_T), intent(in) :: group diff --git a/src/lattice.cpp b/src/lattice.cpp index 006b7bbdd..236e6d0ba 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -7,6 +7,9 @@ #include "hdf5_interface.h" #include "xml_interface.h" +//TODO: this is only inlcuded for constants that should be moved elsewhere +//#include "surface.h" + //TODO: remove this include #include @@ -81,9 +84,9 @@ RectLattice::RectLattice(pugi::xml_node lat_node) fatal_error("Number of entries on must be the same as the " "number of entries on ."); } - lower_left[0] = stoi(ll_words[0]); - lower_left[1] = stoi(ll_words[1]); - if (is_3d) {lower_left[2] = stoi(ll_words[2]);} + lower_left[0] = stod(ll_words[0]); + lower_left[1] = stod(ll_words[1]); + if (is_3d) {lower_left[2] = stod(ll_words[2]);} // Read the lattice pitches. std::string pitch_str{get_node_value(lat_node, "pitch")}; @@ -92,9 +95,9 @@ RectLattice::RectLattice(pugi::xml_node lat_node) fatal_error("Number of entries on must be the same as the " "number of entries on ."); } - pitch[0] = stoi(pitch_words[0]); - pitch[1] = stoi(pitch_words[1]); - if (is_3d) {pitch[2] = stoi(pitch_words[2]);} + pitch[0] = stod(pitch_words[0]); + pitch[1] = stod(pitch_words[1]); + if (is_3d) {pitch[2] = stod(pitch_words[2]);} // Read the universes and make sure the correct number was specified. int nx = n_cells[0]; @@ -129,6 +132,65 @@ RectLattice::RectLattice(pugi::xml_node lat_node) } } +std::pair> +RectLattice::distance(const double xyz[3], const double uvw[3]) const +{ + // Get short aliases to the coordinates. + double x {xyz[0]}; + double y {xyz[1]}; + double z {xyz[2]}; + double u {uvw[0]}; + double v {uvw[1]}; + + // Determine the oncoming edge. + double x0 {copysign(0.5 * pitch[0], u)}; + double y0 {copysign(0.5 * pitch[1], v)}; + + // Left and right sides + double d {INFTY}; + std::array lattice_trans; + if ((std::abs(x - x0) > FP_PRECISION) && u != 0) { + d = (x0 - x) / u; + if (u > 0) { + lattice_trans = {1, 0, 0}; + } else { + lattice_trans = {-1, 0, 0}; + } + } + + // Front and back sides + if ((std::abs(y - y0) > FP_PRECISION) && v != 0) { + double this_d = (y0 - y) / v; + if (this_d < d) { + d = this_d; + if (v > 0) { + lattice_trans = {0, 1, 0}; + } else { + lattice_trans = {0, -1, 0}; + } + } + } + + // Top and bottom sides + if (is_3d) { + double w {uvw[2]}; + double z0 {copysign(0.5 * pitch[2], w)}; + if ((std::abs(z - z0) > FP_PRECISION) && w != 0) { + double this_d = (z0 - z) / w; + if (this_d < d) { + d = this_d; + if (w > 0) { + lattice_trans = {0, 0, 1}; + } else { + lattice_trans = {0, 0, -1}; + } + } + } + } + + return {d, lattice_trans}; +} + //============================================================================== // HexLattice implementation //============================================================================== @@ -138,6 +200,11 @@ HexLattice::HexLattice(pugi::xml_node lat_node) { } +std::pair> +HexLattice::distance(const double xyz[3], const double uvw[3]) const +{ +} + //============================================================================== extern "C" void @@ -173,6 +240,16 @@ extern "C" { int32_t lattice_id(Lattice *lat) {return lat->id;} + void lattice_distance(Lattice *lat, const double xyz[3], const double uvw[3], + double *d, int lattice_trans[3]) + { + std::pair> ld {lat->distance(xyz, uvw)}; + *d = ld.first; + lattice_trans[0] = ld.second[0]; + lattice_trans[1] = ld.second[1]; + lattice_trans[2] = ld.second[2]; + } + void lattice_to_hdf5(Lattice *lat, hid_t group) {lat->to_hdf5(group);} } diff --git a/src/lattice.h b/src/lattice.h index 05b642e20..1825f396b 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -3,6 +3,7 @@ #include #include +#include // For numeric_limits #include #include #include @@ -13,6 +14,14 @@ namespace openmc { +//============================================================================== +// Constants that should eventually be moved out of this file +//============================================================================== + +extern "C" double FP_PRECISION; +constexpr double INFTY{std::numeric_limits::max()}; +constexpr int C_NONE {-1}; + //============================================================================== // Constants //============================================================================== @@ -51,6 +60,9 @@ public: //virtual bool are_valid_indices(const int i_xyz[3]) const = 0; + virtual std::pair> + distance(const double xyz[3], const double uvw[3]) const = 0; + //virtual void get_indices(const double global_xyz[3], int i_xyz[3]) const = 0; //virtual void get_local_xyz(const double global_xyz[3], const int i_xyz[3], @@ -74,10 +86,13 @@ public: virtual ~RectLattice() {} + std::pair> + distance(const double xyz[3], const double uvw[3]) const; + protected: - std::array n_cells; //! Number of cells along each axis - std::array lower_left; //! Global lower-left corner of the lattice - std::array pitch; //! Lattice tile width along each axis + std::array n_cells; //! Number of cells along each axis + std::array lower_left; //! Global lower-left corner of the lattice + std::array pitch; //! Lattice tile width along each axis }; class HexLattice : public Lattice @@ -86,6 +101,9 @@ public: explicit HexLattice(pugi::xml_node lat_node); virtual ~HexLattice() {} + + std::pair> + distance(const double xyz[3], const double uvw[3]) const; }; } // namespace openmc From b3106a65044a4978ad36fe75e9ca8ab0aae0092b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Feb 2018 06:37:46 -0600 Subject: [PATCH 030/361] 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 031/361] 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 032/361] 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 033/361] 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 034/361] 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 81cc98010bbd6264570ae06dbba3484e63222075 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 15 Feb 2018 14:55:06 -0500 Subject: [PATCH 035/361] Implement a C++ HexLattice constructor --- src/lattice.cpp | 167 ++++++++++++++++++++++++++++++++++++++++++++---- src/lattice.h | 6 ++ 2 files changed, 160 insertions(+), 13 deletions(-) diff --git a/src/lattice.cpp b/src/lattice.cpp index 236e6d0ba..463619a9f 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -43,6 +43,10 @@ Lattice::Lattice(pugi::xml_node lat_node) if (check_for_node(lat_node, "name")) { name = get_node_value(lat_node, "name"); } + + if (check_for_node(lat_node, "outer")) { + outer = stoi(get_node_value(lat_node, "outer")); + } } void @@ -61,8 +65,8 @@ RectLattice::RectLattice(pugi::xml_node lat_node) : Lattice {lat_node} { // Read the number of lattice cells in each dimension. - std::string dimension_str{get_node_value(lat_node, "dimension")}; - std::vector dimension_words{split(dimension_str)}; + std::string dimension_str {get_node_value(lat_node, "dimension")}; + std::vector dimension_words {split(dimension_str)}; if (dimension_words.size() == 2) { n_cells[0] = stoi(dimension_words[0]); n_cells[1] = stoi(dimension_words[1]); @@ -78,8 +82,8 @@ RectLattice::RectLattice(pugi::xml_node lat_node) } // Read the lattice lower-left location. - std::string ll_str{get_node_value(lat_node, "lower_left")}; - std::vector ll_words{split(ll_str)}; + std::string ll_str {get_node_value(lat_node, "lower_left")}; + std::vector ll_words {split(ll_str)}; if (ll_words.size() != dimension_words.size()) { fatal_error("Number of entries on must be the same as the " "number of entries on ."); @@ -89,8 +93,8 @@ RectLattice::RectLattice(pugi::xml_node lat_node) if (is_3d) {lower_left[2] = stod(ll_words[2]);} // Read the lattice pitches. - std::string pitch_str{get_node_value(lat_node, "pitch")}; - std::vector pitch_words{split(pitch_str)}; + std::string pitch_str {get_node_value(lat_node, "pitch")}; + std::vector pitch_words {split(pitch_str)}; if (pitch_words.size() != dimension_words.size()) { fatal_error("Number of entries on must be the same as the " "number of entries on ."); @@ -103,8 +107,8 @@ RectLattice::RectLattice(pugi::xml_node lat_node) int nx = n_cells[0]; int ny = n_cells[1]; int nz = n_cells[2]; - std::string univ_str{get_node_value(lat_node, "universes")}; - std::vector univ_words{split(univ_str)}; + std::string univ_str {get_node_value(lat_node, "universes")}; + std::vector univ_words {split(univ_str)}; if (univ_words.size() != nx*ny*nz) { std::stringstream err_msg; err_msg << "Expected " << nx*ny*nz @@ -125,11 +129,6 @@ RectLattice::RectLattice(pugi::xml_node lat_node) } } } - - // Read the outer universe for the area outside the lattice. - if (check_for_node(lat_node, "outer")) { - outer = stoi(get_node_value(lat_node, "outer")); - } } std::pair> @@ -198,6 +197,148 @@ RectLattice::distance(const double xyz[3], const double uvw[3]) const HexLattice::HexLattice(pugi::xml_node lat_node) : Lattice {lat_node} { + // Read the number of lattice cells in each dimension. + n_rings = stoi(get_node_value(lat_node, "n_rings")); + if (check_for_node(lat_node, "n_axial")) { + n_axial = stoi(get_node_value(lat_node, "n_axial")); + is_3d = true; + } else { + n_axial = 1; + is_3d = false; + } + + // Read the lattice center. + std::string center_str {get_node_value(lat_node, "center")}; + std::vector center_words {split(center_str)}; + if (is_3d && (center_words.size() != 3)) { + fatal_error("A hexagonal lattice with must have

" + "specified by 3 numbers."); + } else if (!is_3d && center_words.size() != 2) { + fatal_error("A hexagonal lattice without must have
" + "specified by 2 numbers."); + } + center[0] = stod(center_words[0]); + center[1] = stod(center_words[1]); + if (is_3d) {center[2] = stod(center_words[2]);} + + // Read the lattice pitches. + std::string pitch_str {get_node_value(lat_node, "pitch")}; + std::vector pitch_words {split(pitch_str)}; + if (is_3d && (pitch_words.size() != 2)) { + fatal_error("A hexagonal lattice with must have " + "specified by 2 numbers."); + } else if (!is_3d && (pitch_words.size() != 1)) { + fatal_error("A hexagonal lattice without must have
" + "specified by 1 number."); + } + pitch[0] = stod(pitch_words[0]); + if (is_3d) {pitch[1] = stod(pitch_words[1]);} + + // Read the universes and make sure the correct number was specified. + //int n_univ = (2*n_rings - 1) * (2*n_rings - 1) * n_axial; + int n_univ = (3*n_rings*n_rings - 3*n_rings + 1) * n_axial; + std::string univ_str {get_node_value(lat_node, "universes")}; + std::vector univ_words {split(univ_str)}; + if (univ_words.size() != n_univ) { + std::stringstream err_msg; + err_msg << "Expected " << n_univ + << " universes for a hexagonal lattice with " << n_rings + << " rings and " << n_axial << " axial levels" << " but " + << univ_words.size() << " were specified."; + fatal_error(err_msg); + } + + // Parse the universes. + // Universes in hexagonal lattices are stored in a manner that represents + // a skewed coordinate system: (x, alpha) rather than (x, y). There is + // no obvious, direct relationship between the order of universes in the + // input and the order that they will be stored in the skewed array so + // the following code walks a set of index values across the skewed array + // in a manner that matches the input order. Note that i_x = 0, i_a = 0 + // corresponds to the center of the hexagonal lattice. + + universes.resize((2*n_rings-1) * (2*n_rings-1) * n_axial, -1); + int input_index = 0; + for (int m = 0; m < n_axial; m++) { + // Initialize lattice indecies. + int i_x = 1; + int i_a = n_rings - 1; + + // Map upper triangular region of hexagonal lattice which is found in the + // first n_rings-1 rows of the input. + for (int k = 0; k < n_rings-1; k++) { + // Walk the index to lower-left neighbor of last row start. + i_x -= 1; + + // Iterate over the input columns. + for (int j = 0; j < k+1; j++) { + int indx = (2*n_rings-1)*(2*n_rings-1) * m + + (2*n_rings-1) * (i_a+n_rings-1) + + (i_x+n_rings-1); + universes[indx] = stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; + } + + // Return the lattice index to the start of the current row. + i_x -= 2 * (k+1); + i_a += (k+1); + } + + // Map the middle square region of the hexagonal lattice which is found in + // the next 2*n_rings-1 rows of the input. + for (int k = 0; k < 2*n_rings-1; k++) { + if ((k % 2) == 0) { + // Walk the index to the lower-left neighbor of the last row start. + i_x -= 1; + } else { + // Walk the index to the lower-right neighbor of the last row start. + i_x += 1; + i_a -= 1; + } + + // Iterate over the input columns. + for (int j = 0; j < n_rings - (k % 2); j++) { + int indx = (2*n_rings-1)*(2*n_rings-1) * m + + (2*n_rings-1) * (i_a+n_rings-1) + + (i_x+n_rings-1); + universes[indx] = stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; + } + + // Return the lattice index to the start of the current row. + i_x -= 2*(n_rings - (k % 2)); + i_a += n_rings - (k % 2); + } + + // Map the lower triangular region of the hexagonal lattice. + for (int k = 0; k < n_rings-1; k++) { + // Walk the index to the lower-right neighbor of the last row start. + i_x += 1; + i_a -= 1; + + // Iterate over the input columns. + for (int j = 0; j < n_rings-k-1; j++) { + int indx = (2*n_rings-1)*(2*n_rings-1) * m + + (2*n_rings-1) * (i_a+n_rings-1) + + (i_x+n_rings-1); + universes[indx] = stoi(univ_words[input_index]); + input_index++; + // Walk the index to the right neighbor (which is not adjacent). + i_x += 2; + i_a -= 1; + } + + // Return lattice index to start of current row. + i_x -= 2*(n_rings - k - 1); + i_a += n_rings - k - 1; + } + } } std::pair> diff --git a/src/lattice.h b/src/lattice.h index 1825f396b..18dbd53a4 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -104,6 +104,12 @@ public: std::pair> distance(const double xyz[3], const double uvw[3]) const; + +protected: + int n_rings; //! Number of radial tile positions + int n_axial; //! Number of axial tile positions + std::array center; //! Global center of lattice + std::array pitch; //! Lattice tile width and height }; } // namespace openmc From 8a41bac17abdb16868dcbb6a4666e80a3f9b493c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Feb 2018 14:31:03 -0600 Subject: [PATCH 036/361] 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 037/361] 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 038/361] 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 039/361] 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 ec14970cafb936ed3fac4df2f08a895d22f9d2bf Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 16 Feb 2018 15:22:08 -0500 Subject: [PATCH 040/361] Add C++ HexLattice distance_to_boundary --- src/geometry.F90 | 134 +++------------------------------------- src/geometry_header.F90 | 8 ++- src/lattice.cpp | 127 +++++++++++++++++++++++++++++++++++-- src/lattice.h | 10 ++- 4 files changed, 145 insertions(+), 134 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index a6965cffb..fff18d6c8 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -372,17 +372,9 @@ contains integer :: i_xyz(3) ! lattice indices integer :: level_surf_cross ! surface crossed on current level integer :: level_lat_trans(3) ! lattice translation on current level - real(8) :: x,y,z ! particle coordinates real(8) :: xyz_t(3) ! local particle coordinates - real(8) :: beta, gama ! skewed particle coordiantes - real(8) :: u,v,w ! particle directions - real(8) :: beta_dir ! skewed particle direction - real(8) :: gama_dir ! skewed particle direction - real(8) :: edge ! distance to oncoming edge - real(8) :: d ! evaluated distance real(8) :: d_lat ! distance to lattice boundary 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 type(Cell), pointer :: c @@ -402,11 +394,6 @@ contains ! get pointer to cell on this level c => cells(p % coord(j) % cell) - ! copy directional cosines - u = p % coord(j) % uvw(1) - v = p % coord(j) % uvw(2) - w = p % coord(j) % uvw(3) - ! ======================================================================= ! FIND MINIMUM DISTANCE TO SURFACE IN THIS CELL @@ -419,123 +406,22 @@ contains LAT_COORD: if (p % coord(j) % lattice /= NONE) then lat => lattices(p % coord(j) % lattice) % obj + i_xyz(1) = p % coord(j) % lattice_x + i_xyz(2) = p % coord(j) % lattice_y + i_xyz(3) = p % coord(j) % lattice_z + LAT_TYPE: select type(lat) type is (RectLattice) call lat % distance(p % coord(j) % xyz, p % coord(j) % uvw, & - d_lat, level_lat_trans) + i_xyz, d_lat, level_lat_trans) type is (HexLattice) LAT_TYPE - ! Copy local coordinates. - z = p % coord(j) % xyz(3) - i_xyz(1) = p % coord(j) % lattice_x - i_xyz(2) = p % coord(j) % lattice_y - i_xyz(3) = p % coord(j) % lattice_z - - ! Compute velocities along the hexagonal axes. - beta_dir = u*sqrt(THREE)/TWO + v/TWO - gama_dir = u*sqrt(THREE)/TWO - v/TWO - - ! Note that hexagonal lattice distance calculations are performed - ! using the particle's coordinates relative to the neighbor lattice - ! cells, not relative to the particle's current cell. This is done - ! because there is significant disagreement between neighboring cells - ! on where the lattice boundary is due to the worse finite precision - ! of hex lattices. - - ! Upper right and lower left sides. - edge = -sign(lat % pitch(1)/TWO, beta_dir) ! Oncoming edge - if (beta_dir > ZERO) then - xyz_t = lat % get_local_xyz(p % coord(j - 1) % xyz, i_xyz+[1, 0, 0]) - else - xyz_t = lat % get_local_xyz(p % coord(j - 1) % xyz, i_xyz+[-1, 0, 0]) - end if - beta = xyz_t(1)*sqrt(THREE)/TWO + xyz_t(2)/TWO - if (abs(beta - edge) < FP_PRECISION) then - d = INFINITY - else if (beta_dir == ZERO) then - d = INFINITY - else - d = (edge - beta)/beta_dir - end if - - d_lat = d - if (beta_dir > 0) then - level_lat_trans(:) = [1, 0, 0] - else - level_lat_trans(:) = [-1, 0, 0] - end if - - ! Lower right and upper left sides. - edge = -sign(lat % pitch(1)/TWO, gama_dir) ! Oncoming edge - if (gama_dir > ZERO) then - xyz_t = lat % get_local_xyz(p % coord(j - 1) % xyz, i_xyz+[1, -1, 0]) - else - xyz_t = lat % get_local_xyz(p % coord(j - 1) % xyz, i_xyz+[-1, 1, 0]) - end if - gama = xyz_t(1)*sqrt(THREE)/TWO - xyz_t(2)/TWO - if (abs(gama - edge) < FP_PRECISION) then - d = INFINITY - else if (gama_dir == ZERO) then - d = INFINITY - else - d = (edge - gama)/gama_dir - end if - - if (d < d_lat) then - d_lat = d - if (gama_dir > 0) then - level_lat_trans(:) = [1, -1, 0] - else - level_lat_trans(:) = [-1, 1, 0] - end if - end if - - ! Upper and lower sides. - edge = -sign(lat % pitch(1)/TWO, v) ! Oncoming edge - if (v > ZERO) then - xyz_t = lat % get_local_xyz(p % coord(j - 1) % xyz, i_xyz+[0, 1, 0]) - else - xyz_t = lat % get_local_xyz(p % coord(j - 1) % xyz, i_xyz+[0, -1, 0]) - end if - if (abs(xyz_t(2) - edge) < FP_PRECISION) then - d = INFINITY - else if (v == ZERO) then - d = INFINITY - else - d = (edge - xyz_t(2))/v - end if - - if (d < d_lat) then - d_lat = d - if (v > 0) then - level_lat_trans(:) = [0, 1, 0] - else - level_lat_trans(:) = [0, -1, 0] - end if - end if - - ! Top and bottom sides. - if (lat % is_3d) then - z0 = sign(lat % pitch(2) * HALF, w) - - if (abs(z - z0) < FP_PRECISION) then - d = INFINITY - elseif (w == ZERO) then - d = INFINITY - else - d = (z0 - z)/w - end if - - if (d < d_lat) then - d_lat = d - if (w > 0) then - level_lat_trans(:) = [0, 0, 1] - else - level_lat_trans(:) = [0, 0, -1] - end if - end if - end if + xyz_t(1) = p % coord(j-1) % xyz(1) + xyz_t(2) = p % coord(j-1) % xyz(2) + xyz_t(3) = p % coord(j) % xyz(3) + call lat % distance(xyz_t, p % coord(j) % uvw, & + i_xyz, d_lat, level_lat_trans) end select LAT_TYPE if (d_lat < ZERO) then diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 101bf8f9a..0cc08f5e6 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -95,13 +95,14 @@ module geometry_header integer(C_INT32_T) :: id end function lattice_id_c - subroutine lattice_distance_c(lat_ptr, xyz, uvw, d, lattice_trans) & + subroutine lattice_distance_c(lat_ptr, xyz, uvw, i_xyz, d, lattice_trans) & bind(C, name='lattice_distance') use ISO_C_BINDING implicit none type(C_PTR), intent(in), value :: lat_ptr real(C_DOUBLE), intent(in) :: xyz(3) real(C_DOUBLE), intent(in) :: uvw(3) + integer(C_INT), intent(in) :: i_xyz(3) real(C_DOUBLE), intent(out) :: d integer(C_INT), intent(out) :: lattice_trans(3) end subroutine lattice_distance_c @@ -293,13 +294,14 @@ contains id = lattice_id_c(this % ptr) end function lattice_id - subroutine lattice_distance(this, xyz, uvw, d, lattice_trans) + subroutine lattice_distance(this, xyz, uvw, i_xyz, d, lattice_trans) class(Lattice), intent(in) :: this real(C_DOUBLE), intent(in) :: xyz(3) real(C_DOUBLE), intent(in) :: uvw(3) + integer(C_INT), intent(in) :: i_xyz(3) real(C_DOUBLE), intent(out) :: d integer(C_INT), intent(out) :: lattice_trans(3) - call lattice_distance_c(this % ptr, xyz, uvw, d, lattice_trans) + call lattice_distance_c(this % ptr, xyz, uvw, i_xyz, d, lattice_trans) end subroutine lattice_distance subroutine lattice_to_hdf5(this, group) diff --git a/src/lattice.cpp b/src/lattice.cpp index 463619a9f..473099034 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -132,7 +132,8 @@ RectLattice::RectLattice(pugi::xml_node lat_node) } std::pair> -RectLattice::distance(const double xyz[3], const double uvw[3]) const +RectLattice::distance(const double xyz[3], const double uvw[3], + const int i_xyz[3]) const { // Get short aliases to the coordinates. double x {xyz[0]}; @@ -342,8 +343,126 @@ HexLattice::HexLattice(pugi::xml_node lat_node) } std::pair> -HexLattice::distance(const double xyz[3], const double uvw[3]) const +HexLattice::distance(const double xyz[3], const double uvw[3], + const int i_xyz[3]) const { + // Compute the direction on the hexagonal basis. + double beta_dir = uvw[0] * std::sqrt(3.0) / 2.0 + uvw[1] / 2.0; + double gamma_dir = uvw[0] * std::sqrt(3.0) / 2.0 - uvw[1] / 2.0; + + // Note that hexagonal lattice distance calculations are performed + // using the particle's coordinates relative to the neighbor lattice + // cells, not relative to the particle's current cell. This is done + // because there is significant disagreement between neighboring cells + // on where the lattice boundary is due to finite precision issues. + + // Upper-right and lower-left sides. + double d {INFTY}; + std::array lattice_trans; + double edge = -copysign(0.5*pitch[0], beta_dir); // Oncoming edge + std::array xyz_t; + if (beta_dir > 0) { + const int i_xyz_t[3] {i_xyz[0]+1, i_xyz[1], i_xyz[2]}; + xyz_t = get_local_xyz(xyz, i_xyz_t); + } else { + const int i_xyz_t[3] {i_xyz[0]-1, i_xyz[1], i_xyz[2]}; + xyz_t = get_local_xyz(xyz, i_xyz_t); + } + double beta = xyz_t[0] * std::sqrt(3.0) / 2.0 + xyz_t[1] / 2.0; + if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) { + d = (edge - beta) / beta_dir; + if (beta_dir > 0) { + lattice_trans = {1, 0, 0}; + } else { + lattice_trans = {-1, 0, 0}; + } + } + + // Lower-right and upper-left sides. + edge = -copysign(0.5*pitch[0], gamma_dir); + if (gamma_dir > 0) { + const int i_xyz_t[3] {i_xyz[0]+1, i_xyz[1]-1, i_xyz[2]}; + xyz_t = get_local_xyz(xyz, i_xyz_t); + } else { + const int i_xyz_t[3] {i_xyz[0]-1, i_xyz[1]+1, i_xyz[2]}; + xyz_t = get_local_xyz(xyz, i_xyz_t); + } + double gamma = xyz_t[0] * std::sqrt(3.0) / 2.0 - xyz_t[1] / 2.0; + if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) { + double this_d = (edge - gamma) / gamma_dir; + if (this_d < d) { + if (gamma_dir > 0) { + lattice_trans = {1, -1, 0}; + } else { + lattice_trans = {-1, 1, 0}; + } + d = this_d; + } + } + + // Upper and lower sides. + edge = -copysign(0.5*pitch[0], uvw[1]); + if (uvw[1] > 0) { + const int i_xyz_t[3] {i_xyz[0], i_xyz[1]+1, i_xyz[2]}; + xyz_t = get_local_xyz(xyz, i_xyz_t); + } else { + const int i_xyz_t[3] {i_xyz[0], i_xyz[1]-1, i_xyz[2]}; + xyz_t = get_local_xyz(xyz, i_xyz_t); + } + if ((std::abs(xyz_t[1] - edge) > FP_PRECISION) && uvw[1] != 0) { + double this_d = (edge - xyz_t[1]) / uvw[1]; + if (this_d < d) { + if (uvw[1] > 0) { + lattice_trans = {0, 1, 0}; + } else { + lattice_trans = {0, -1, 0}; + } + d = this_d; + } + } + + // Top and bottom sides + if (is_3d) { + double z {xyz[2]}; + double w {uvw[2]}; + double z0 {copysign(0.5 * pitch[1], w)}; + if ((std::abs(z - z0) > FP_PRECISION) && w != 0) { + double this_d = (z0 - z) / w; + if (this_d < d) { + d = this_d; + if (w > 0) { + lattice_trans = {0, 0, 1}; + } else { + lattice_trans = {0, 0, -1}; + } + d = this_d; + } + } + } + + return {d, lattice_trans}; +} + +std::array +HexLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const +{ + std::array local_xyz; + + // x_l = x_g - (center + pitch_x*cos(30)*index_x) + local_xyz[0] = global_xyz[0] - (center[0] + + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings) * pitch[0]); + // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y) + local_xyz[1] = global_xyz[1] - (center[1] + + (i_xyz[1] - n_rings) * pitch[0] + + (i_xyz[0] - n_rings) * pitch[0] / 2.0); + if (is_3d) { + local_xyz[2] = global_xyz[2] - center[2] + + (0.5 * n_axial - i_xyz[2] + 0.5) * pitch[1]; + } else { + local_xyz[2] = global_xyz[2]; + } + + return local_xyz; } //============================================================================== @@ -382,9 +501,9 @@ extern "C" { int32_t lattice_id(Lattice *lat) {return lat->id;} void lattice_distance(Lattice *lat, const double xyz[3], const double uvw[3], - double *d, int lattice_trans[3]) + const int i_xyz[3], double *d, int lattice_trans[3]) { - std::pair> ld {lat->distance(xyz, uvw)}; + std::pair> ld {lat->distance(xyz, uvw, i_xyz)}; *d = ld.first; lattice_trans[0] = ld.second[0]; lattice_trans[1] = ld.second[1]; diff --git a/src/lattice.h b/src/lattice.h index 18dbd53a4..2a4aa8865 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -61,7 +61,8 @@ public: //virtual bool are_valid_indices(const int i_xyz[3]) const = 0; virtual std::pair> - distance(const double xyz[3], const double uvw[3]) const = 0; + distance(const double xyz[3], const double uvw[3], const int i_xyz[3]) const + = 0; //virtual void get_indices(const double global_xyz[3], int i_xyz[3]) const = 0; @@ -87,7 +88,7 @@ public: virtual ~RectLattice() {} std::pair> - distance(const double xyz[3], const double uvw[3]) const; + distance(const double xyz[3], const double uvw[3], const int i_xyz[3]) const; protected: std::array n_cells; //! Number of cells along each axis @@ -103,7 +104,10 @@ public: virtual ~HexLattice() {} std::pair> - distance(const double xyz[3], const double uvw[3]) const; + distance(const double xyz[3], const double uvw[3], const int i_xyz[3]) const; + + std::array + get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const; protected: int n_rings; //! Number of radial tile positions From efc77b74fe1ed7d70179581bdf7f9d4c3cbd0409 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 16 Feb 2018 17:49:42 -0500 Subject: [PATCH 041/361] Move lattice get_indices and are_valid to C++ --- src/geometry_header.F90 | 177 ++++++++-------------------------------- src/lattice.cpp | 121 ++++++++++++++++++++++++++- src/lattice.h | 27 +++++- 3 files changed, 178 insertions(+), 147 deletions(-) diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 0cc08f5e6..c58ab1d6c 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -95,6 +95,15 @@ module geometry_header integer(C_INT32_T) :: id end function lattice_id_c + function lattice_are_valid_indices_c(lat_ptr, i_xyz) & + bind(C, name='lattice_are_valid_indices') result (is_valid) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: lat_ptr + integer(C_INT), intent(in) :: i_xyz(3) + logical(C_BOOL) :: is_valid + end function lattice_are_valid_indices_c + subroutine lattice_distance_c(lat_ptr, xyz, uvw, i_xyz, d, lattice_trans) & bind(C, name='lattice_distance') use ISO_C_BINDING @@ -107,6 +116,15 @@ module geometry_header integer(C_INT), intent(out) :: lattice_trans(3) end subroutine lattice_distance_c + subroutine lattice_get_indices_c(lat_ptr, xyz, i_xyz) & + bind(C, name='lattice_get_indices') + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: lat_ptr + real(C_DOUBLE), intent(in) :: xyz(3) + integer(C_INT), intent(out) :: i_xyz(3) + end subroutine lattice_get_indices_c + subroutine lattice_to_hdf5_c(lat_ptr, group) bind(C, name='lattice_to_hdf5') use ISO_C_BINDING use hdf5 @@ -146,37 +164,16 @@ module geometry_header contains procedure :: id => lattice_id + procedure :: are_valid_indices => lattice_are_valid_indices procedure :: distance => lattice_distance + procedure :: get_indices => lattice_get_indices procedure :: to_hdf5 => lattice_to_hdf5 - procedure(lattice_are_valid_indices_), deferred :: are_valid_indices - procedure(lattice_get_indices_), deferred :: get_indices procedure(lattice_get_local_xyz_), deferred :: get_local_xyz end type Lattice abstract interface -!=============================================================================== -! ARE_VALID_INDICES returns .true. if the given lattice indices fit within the -! bounds of the lattice. Returns false otherwise. - - function lattice_are_valid_indices_(this, i_xyz) result(is_valid) - import Lattice - class(Lattice), intent(in) :: this - integer, intent(in) :: i_xyz(3) - logical :: is_valid - end function lattice_are_valid_indices_ - -!=============================================================================== -! GET_INDICES returns the indices in a lattice for the given global xyz. - - function lattice_get_indices_(this, global_xyz) result(i_xyz) - import Lattice - class(Lattice), intent(in) :: this - real(8), intent(in) :: global_xyz(3) - integer :: i_xyz(3) - end function lattice_get_indices_ - !=============================================================================== ! GET_LOCAL_XYZ returns the translated local version of the given global xyz. @@ -199,8 +196,6 @@ module geometry_header contains - procedure :: are_valid_indices => valid_inds_rect - procedure :: get_indices => get_inds_rect procedure :: get_local_xyz => get_local_rect end type RectLattice @@ -215,8 +210,6 @@ module geometry_header contains - procedure :: are_valid_indices => valid_inds_hex - procedure :: get_indices => get_inds_hex procedure :: get_local_xyz => get_local_hex end type HexLattice @@ -294,6 +287,13 @@ contains id = lattice_id_c(this % ptr) end function lattice_id + function lattice_are_valid_indices(this, i_xyz) result (is_valid) + class(Lattice), intent(in) :: this + integer(C_INT), intent(in) :: i_xyz(3) + logical(C_BOOL) :: is_valid + is_valid = lattice_are_valid_indices_c(this % ptr, i_xyz) + end function lattice_are_valid_indices + subroutine lattice_distance(this, xyz, uvw, i_xyz, d, lattice_trans) class(Lattice), intent(in) :: this real(C_DOUBLE), intent(in) :: xyz(3) @@ -304,128 +304,19 @@ contains call lattice_distance_c(this % ptr, xyz, uvw, i_xyz, d, lattice_trans) end subroutine lattice_distance + function lattice_get_indices(this, xyz) result(i_xyz) + class(Lattice), intent(in) :: this + real(C_DOUBLE), intent(in) :: xyz(3) + integer(C_INT) :: i_xyz(3) + call lattice_get_indices_c(this % ptr, xyz, i_xyz) + end function lattice_get_indices + subroutine lattice_to_hdf5(this, group) class(Lattice), intent(in) :: this integer(HID_T), intent(in) :: group call lattice_to_hdf5_c(this % ptr, group) end subroutine lattice_to_hdf5 -!=============================================================================== - - function valid_inds_rect(this, i_xyz) result(is_valid) - class(RectLattice), intent(in) :: this - integer, intent(in) :: i_xyz(3) - logical :: is_valid - - is_valid = all(i_xyz > 0 .and. i_xyz <= this % n_cells) - end function valid_inds_rect - -!=============================================================================== - - function valid_inds_hex(this, i_xyz) result(is_valid) - class(HexLattice), intent(in) :: this - integer, intent(in) :: i_xyz(3) - logical :: is_valid - - is_valid = (all(i_xyz > 0) .and. & - &i_xyz(1) < 2*this % n_rings .and. & - &i_xyz(2) < 2*this % n_rings .and. & - &i_xyz(1) + i_xyz(2) > this % n_rings .and. & - &i_xyz(1) + i_xyz(2) < 3*this % n_rings .and. & - &i_xyz(3) <= this % n_axial) - end function valid_inds_hex - -!=============================================================================== - - function get_inds_rect(this, global_xyz) result(i_xyz) - class(RectLattice), intent(in) :: this - real(8), intent(in) :: global_xyz(3) - integer :: i_xyz(3) - - real(8) :: xyz(3) ! global_xyz alias - - xyz = global_xyz - - i_xyz(1) = ceiling((xyz(1) - this % lower_left(1))/this % pitch(1)) - i_xyz(2) = ceiling((xyz(2) - this % lower_left(2))/this % pitch(2)) - if (this % is_3d) then - i_xyz(3) = ceiling((xyz(3) - this % lower_left(3))/this % pitch(3)) - else - i_xyz(3) = 1 - end if - end function get_inds_rect - -!=============================================================================== - - function get_inds_hex(this, global_xyz) result(i_xyz) - class(HexLattice), intent(in) :: this - real(8), intent(in) :: global_xyz(3) - integer :: i_xyz(3) - - real(8) :: xyz(3) ! global xyz relative to the center - real(8) :: alpha ! Skewed coord axis - real(8) :: xyz_t(3) ! Local xyz - real(8) :: d, d_min ! Squared distance from cell centers - integer :: i, j, k ! Iterators - integer :: k_min ! Minimum distance index - - xyz(1) = global_xyz(1) - this % center(1) - xyz(2) = global_xyz(2) - this % center(2) - - ! Index z direction. - if (this % is_3d) then - xyz(3) = global_xyz(3) - this % center(3) - i_xyz(3) = ceiling(xyz(3)/this % pitch(2) + HALF*this % n_axial) - else - xyz(3) = global_xyz(3) - i_xyz(3) = 1 - end if - - ! Convert coordinates into skewed bases. The (x, alpha) basis is used to - ! find the index of the global coordinates to within 4 cells. - alpha = xyz(2) - xyz(1) / sqrt(THREE) - i_xyz(1) = floor(xyz(1) / (sqrt(THREE) / TWO * this % pitch(1))) - i_xyz(2) = floor(alpha / this % pitch(1)) - - ! Add offset to indices (the center cell is (i_x, i_alpha) = (0, 0) but - ! the array is offset so that the indices never go below 1). - i_xyz(1) = i_xyz(1) + this % n_rings - i_xyz(2) = i_xyz(2) + this % n_rings - - ! Calculate the (squared) distance between the particle and the centers of - ! the four possible cells. Regular hexagonal tiles form a centroidal - ! Voronoi tessellation so the global xyz should be in the hexagonal cell - ! that it is closest to the center of. This method is used over a - ! method that uses the remainders of the floor divisions above because it - ! provides better finite precision performance. Squared distances are - ! used becasue they are more computationally efficient than normal - ! distances. - k = 1 - d_min = INFINITY - do i = 0, 1 - do j = 0, 1 - xyz_t = this % get_local_xyz(global_xyz, i_xyz + [j, i, 0]) - d = xyz_t(1)**2 + xyz_t(2)**2 - if (d < d_min) then - d_min = d - k_min = k - end if - k = k + 1 - end do - end do - - ! Select the minimum squared distance which corresponds to the cell the - ! coordinates are in. - if (k_min == 2) then - i_xyz(1) = i_xyz(1) + 1 - else if (k_min == 3) then - i_xyz(2) = i_xyz(2) + 1 - else if (k_min == 4) then - i_xyz(1) = i_xyz(1) + 1 - i_xyz(2) = i_xyz(2) + 1 - end if - end function get_inds_hex - !=============================================================================== function get_local_rect(this, global_xyz, i_xyz) result(local_xyz) diff --git a/src/lattice.cpp b/src/lattice.cpp index 473099034..82f15401d 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -131,6 +131,18 @@ RectLattice::RectLattice(pugi::xml_node lat_node) } } +//============================================================================== + +bool +RectLattice::are_valid_indices(const int i_xyz[3]) const +{ + return ( (i_xyz[0] > 0) && (i_xyz[0] <= n_cells[0]) + && (i_xyz[1] > 0) && (i_xyz[1] <= n_cells[1]) + && (i_xyz[2] > 0) && (i_xyz[2] <= n_cells[2])); +} + +//============================================================================== + std::pair> RectLattice::distance(const double xyz[3], const double uvw[3], const int i_xyz[3]) const @@ -191,6 +203,22 @@ RectLattice::distance(const double xyz[3], const double uvw[3], return {d, lattice_trans}; } +//============================================================================== + +std::array +RectLattice::get_indices(const double xyz[3]) const +{ + int ix {static_cast(std::ceil((xyz[0] - lower_left[0]) / pitch[0]))}; + int iy {static_cast(std::ceil((xyz[1] - lower_left[1]) / pitch[1]))}; + int iz; + if (is_3d) { + iz = static_cast(std::ceil((xyz[2] - lower_left[2]) / pitch[2])); + } else { + iz = 1; + } + return {ix, iy, iz}; +} + //============================================================================== // HexLattice implementation //============================================================================== @@ -229,7 +257,7 @@ HexLattice::HexLattice(pugi::xml_node lat_node) fatal_error("A hexagonal lattice with must have " "specified by 2 numbers."); } else if (!is_3d && (pitch_words.size() != 1)) { - fatal_error("A hexagonal lattice without must have
" + fatal_error("A hexagonal lattice without must have " "specified by 1 number."); } pitch[0] = stod(pitch_words[0]); @@ -342,6 +370,18 @@ HexLattice::HexLattice(pugi::xml_node lat_node) } } +//============================================================================== + +bool +HexLattice::are_valid_indices(const int i_xyz[3]) const +{ + return ((i_xyz[0] > 0) && (i_xyz[1] > 0) && (i_xyz[2] > 0) + && (i_xyz[0] < 2*n_rings) && (i_xyz[1] < 2*n_rings) + && (i_xyz[0] + i_xyz[1] > n_rings) + && (i_xyz[0] + i_xyz[1] < 3*n_rings) + && (i_xyz[2] <= n_axial)); +} + std::pair> HexLattice::distance(const double xyz[3], const double uvw[3], const int i_xyz[3]) const @@ -443,6 +483,74 @@ HexLattice::distance(const double xyz[3], const double uvw[3], return {d, lattice_trans}; } +//============================================================================== + +std::array +HexLattice::get_indices(const double xyz[3]) const +{ + // Offset the xyz by the lattice center. + double xyz_o[3] {xyz[0] - center[0], xyz[1] - center[1], xyz[2]}; + if (is_3d) {xyz_o[2] -= center[2];} + + // Index the z direction. + std::array out; + if (is_3d) { + out[2] = static_cast(std::ceil(xyz_o[2] / pitch[1] + 0.5 * n_axial)); + } else { + out[2] = 1; + } + + // Convert coordinates into skewed bases. The (x, alpha) basis is used to + // find the index of the global coordinates to within 4 cells. + double alpha = xyz_o[1] - xyz_o[0] / std::sqrt(3.0); + out[0] = static_cast(std::floor(xyz_o[0] + / (0.5*std::sqrt(3.0) * pitch[0]))); + out[1] = static_cast(std::floor(alpha / pitch[0])); + + // Add offset to indices (the center cell is (i_x, i_alpha) = (0, 0) but + // the array is offset so that the indices never go below 1). + out[0] += n_rings; + out[1] += n_rings; + + // Calculate the (squared) distance between the particle and the centers of + // the four possible cells. Regular hexagonal tiles form a Voronoi + // tessellation so the xyz should be in the hexagonal cell that it is closest + // to the center of. This method is used over a method that uses the + // remainders of the floor divisions above because it provides better finite + // precision performance. Squared distances are used becasue they are more + // computationally efficient than normal distances. + int k {1}; + int k_min {1}; + double d_min {INFTY}; + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + int i_xyz[3] {out[0] + j, out[1] + i, 0}; + std::array xyz_t = get_local_xyz(xyz, i_xyz); + double d = xyz_t[0]*xyz_t[0] + xyz_t[1]*xyz_t[1]; + if (d < d_min) { + d_min = d; + k_min = k; + } + k++; + } + } + + // Select the minimum squared distance which corresponds to the cell the + // coordinates are in. + if (k_min == 2) { + out[0] += 1; + } else if (k_min == 3) { + out[1] += 1; + } else if (k_min == 4) { + out[0] += 1; + out[1] += 1; + } + + return out; +} + +//============================================================================== + std::array HexLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const { @@ -500,6 +608,9 @@ extern "C" { int32_t lattice_id(Lattice *lat) {return lat->id;} + bool lattice_are_valid_indices(Lattice *lat, const int i_xyz[3]) + {return lat->are_valid_indices(i_xyz);} + void lattice_distance(Lattice *lat, const double xyz[3], const double uvw[3], const int i_xyz[3], double *d, int lattice_trans[3]) { @@ -510,6 +621,14 @@ extern "C" { lattice_trans[2] = ld.second[2]; } + void lattice_get_indices(Lattice *lat, const double xyz[3], int i_xyz[3]) + { + std::array inds {lat->get_indices(xyz)}; + i_xyz[0] = inds[0]; + i_xyz[1] = inds[1]; + i_xyz[2] = inds[2]; + } + void lattice_to_hdf5(Lattice *lat, hid_t group) {lat->to_hdf5(group);} } diff --git a/src/lattice.h b/src/lattice.h index 2a4aa8865..81b0c2bdf 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -58,13 +58,26 @@ public: virtual ~Lattice() {} - //virtual bool are_valid_indices(const int i_xyz[3]) const = 0; + //! Check lattice indices. + //! @param i_xyz[3] The indices for a lattice tile. + //! @return true if the given indices fit within the lattice bounds. False + //! otherwise. + virtual bool are_valid_indices(const int i_xyz[3]) const = 0; + //! Find the next lattice surface crossing + //! @param xyz[3] A 3D Cartesian coordinate. + //! @param uvw[3] A 3D Cartesian direction. + //! @param i_xyz[3] The indices for a lattice tile. + //! @return The distance to the next crossing and an array indicating how the + //! lattice indices would change after crossing that boundary. virtual std::pair> distance(const double xyz[3], const double uvw[3], const int i_xyz[3]) const - = 0; + = 0; - //virtual void get_indices(const double global_xyz[3], int i_xyz[3]) const = 0; + //! Find the lattice tile indices for a given point. + //! @param xyz[3] A 3D Cartesian coordinate. + //! @return An array containing the indices of a lattice tile. + virtual std::array get_indices(const double xyz[3]) const = 0; //virtual void get_local_xyz(const double global_xyz[3], const int i_xyz[3], // double local_xyz[3]) const = 0; @@ -87,9 +100,13 @@ public: virtual ~RectLattice() {} + bool are_valid_indices(const int i_xyz[3]) const; + std::pair> distance(const double xyz[3], const double uvw[3], const int i_xyz[3]) const; + std::array get_indices(const double xyz[3]) const; + protected: std::array n_cells; //! Number of cells along each axis std::array lower_left; //! Global lower-left corner of the lattice @@ -103,9 +120,13 @@ public: virtual ~HexLattice() {} + bool are_valid_indices(const int i_xyz[3]) const; + std::pair> distance(const double xyz[3], const double uvw[3], const int i_xyz[3]) const; + std::array get_indices(const double xyz[3]) const; + std::array get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const; From ad4a5da040ef69325d990bbd4320fa0310a4f08c Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 16 Feb 2018 18:25:52 -0500 Subject: [PATCH 042/361] Move lattice get_local_xyz to C++ --- src/geometry_header.F90 | 95 +++++++++-------------------------------- src/lattice.cpp | 27 ++++++++++++ src/lattice.h | 11 ++++- 3 files changed, 56 insertions(+), 77 deletions(-) diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index c58ab1d6c..f5d79d0a6 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -125,6 +125,16 @@ module geometry_header integer(C_INT), intent(out) :: i_xyz(3) end subroutine lattice_get_indices_c + subroutine lattice_get_local_xyz_c(lat_ptr, global_xyz, i_xyz, local_xyz) & + bind(C, name='lattice_get_local_xyz') + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: lat_ptr + real(C_DOUBLE), intent(in) :: global_xyz(3) + integer(C_INT), intent(in) :: i_xyz(3) + real(C_DOUBLE), intent(out) :: local_xyz(3) + end subroutine lattice_get_local_xyz_c + subroutine lattice_to_hdf5_c(lat_ptr, group) bind(C, name='lattice_to_hdf5') use ISO_C_BINDING use hdf5 @@ -167,25 +177,10 @@ module geometry_header procedure :: are_valid_indices => lattice_are_valid_indices procedure :: distance => lattice_distance procedure :: get_indices => lattice_get_indices + procedure :: get_local_xyz => lattice_get_local_xyz procedure :: to_hdf5 => lattice_to_hdf5 - - procedure(lattice_get_local_xyz_), deferred :: get_local_xyz end type Lattice - abstract interface - -!=============================================================================== -! GET_LOCAL_XYZ returns the translated local version of the given global xyz. - - function lattice_get_local_xyz_(this, global_xyz, i_xyz) result(local_xyz) - import Lattice - class(Lattice), intent(in) :: this - real(8), intent(in) :: global_xyz(3) - integer, intent(in) :: i_xyz(3) - real(8) :: local_xyz(3) - end function lattice_get_local_xyz_ - end interface - !=============================================================================== ! RECTLATTICE extends LATTICE for rectilinear arrays. !=============================================================================== @@ -193,10 +188,6 @@ module geometry_header type, extends(Lattice) :: RectLattice integer :: n_cells(3) ! Number of cells along each axis real(8), allocatable :: lower_left(:) ! Global lower-left corner of lat - - contains - - procedure :: get_local_xyz => get_local_rect end type RectLattice !=============================================================================== @@ -207,10 +198,6 @@ module geometry_header integer :: n_rings ! Number of radial ring cell positoins integer :: n_axial ! Number of axial cell positions real(8), allocatable :: center(:) ! Global center of lattice - - contains - - procedure :: get_local_xyz => get_local_hex end type HexLattice !=============================================================================== @@ -311,63 +298,21 @@ contains call lattice_get_indices_c(this % ptr, xyz, i_xyz) end function lattice_get_indices + function lattice_get_local_xyz(this, global_xyz, i_xyz) & + result(local_xyz) + class(Lattice), intent(in) :: this + real(C_DOUBLE), intent(in) :: global_xyz(3) + integer(C_INT), intent(in) :: i_xyz(3) + real(C_DOUBLE) :: local_xyz(3) + call lattice_get_local_xyz_c(this % ptr, global_xyz, i_xyz, local_xyz) + end function lattice_get_local_xyz + subroutine lattice_to_hdf5(this, group) class(Lattice), intent(in) :: this integer(HID_T), intent(in) :: group call lattice_to_hdf5_c(this % ptr, group) end subroutine lattice_to_hdf5 -!=============================================================================== - - function get_local_rect(this, global_xyz, i_xyz) result(local_xyz) - class(RectLattice), intent(in) :: this - real(8), intent(in) :: global_xyz(3) - integer, intent(in) :: i_xyz(3) - real(8) :: local_xyz(3) - - real(8) :: xyz(3) ! global_xyz alias - - xyz = global_xyz - - local_xyz(1) = xyz(1) - (this % lower_left(1) + & - (i_xyz(1) - HALF)*this % pitch(1)) - local_xyz(2) = xyz(2) - (this % lower_left(2) + & - (i_xyz(2) - HALF)*this % pitch(2)) - if (this % is_3d) then - local_xyz(3) = xyz(3) - (this % lower_left(3) + & - (i_xyz(3) - HALF)*this % pitch(3)) - else - local_xyz(3) = xyz(3) - end if - end function get_local_rect - -!=============================================================================== - - function get_local_hex(this, global_xyz, i_xyz) result(local_xyz) - class(HexLattice), intent(in) :: this - real(8), intent(in) :: global_xyz(3) - integer, intent(in) :: i_xyz(3) - real(8) :: local_xyz(3) - - real(8) :: xyz(3) ! global_xyz alias - - xyz = global_xyz - - ! x_l = x_g - (center + pitch_x*cos(30)*index_x) - local_xyz(1) = xyz(1) - (this % center(1) + & - sqrt(THREE) / TWO * (i_xyz(1) - this % n_rings) * this % pitch(1)) - ! y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y) - local_xyz(2) = xyz(2) - (this % center(2) + & - (i_xyz(2) - this % n_rings) * this % pitch(1) + & - (i_xyz(1) - this % n_rings) * this % pitch(1) / TWO) - if (this % is_3d) then - local_xyz(3) = xyz(3) - this % center(3) & - + (HALF*this % n_axial - i_xyz(3) + HALF) * this % pitch(2) - else - local_xyz(3) = xyz(3) - end if - end function get_local_hex - !=============================================================================== function cell_id(this) result(id) diff --git a/src/lattice.cpp b/src/lattice.cpp index 82f15401d..fd89cbf1e 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -219,6 +219,22 @@ RectLattice::get_indices(const double xyz[3]) const return {ix, iy, iz}; } +//============================================================================== + +std::array +RectLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const +{ + std::array local_xyz; + local_xyz[0] = global_xyz[0] - (lower_left[0] + (i_xyz[0] - 0.5)*pitch[0]); + local_xyz[1] = global_xyz[1] - (lower_left[1] + (i_xyz[1] - 0.5)*pitch[1]); + if (is_3d) { + local_xyz[2] = global_xyz[2] - (lower_left[2] + (i_xyz[2] - 0.5)*pitch[2]); + } else { + local_xyz[2] = global_xyz[2]; + } + return local_xyz; +} + //============================================================================== // HexLattice implementation //============================================================================== @@ -573,6 +589,8 @@ HexLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const return local_xyz; } +//============================================================================== +// Non-method functions //============================================================================== extern "C" void @@ -629,6 +647,15 @@ extern "C" { i_xyz[2] = inds[2]; } + void lattice_get_local_xyz(Lattice *lat, const double global_xyz[3], + const int i_xyz[3], double local_xyz[3]) + { + std::array xyz {lat->get_local_xyz(global_xyz, i_xyz)}; + local_xyz[0] = xyz[0]; + local_xyz[1] = xyz[1]; + local_xyz[2] = xyz[2]; + } + void lattice_to_hdf5(Lattice *lat, hid_t group) {lat->to_hdf5(group);} } diff --git a/src/lattice.h b/src/lattice.h index 81b0c2bdf..9ade739c0 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -79,8 +79,12 @@ public: //! @return An array containing the indices of a lattice tile. virtual std::array get_indices(const double xyz[3]) const = 0; - //virtual void get_local_xyz(const double global_xyz[3], const int i_xyz[3], - // double local_xyz[3]) const = 0; + //! Get coordinates local to a lattice tile. + //! @param global_xyz[3] A 3D Cartesian coordinate. + //! @param i_xyz[3] The indices for a lattice tile. + //! @return Local 3D Cartesian coordinates. + virtual std::array + get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const = 0; //! Write all information needed to reconstruct the lattice to an HDF5 group. //! @param group_id An HDF5 group id. @@ -107,6 +111,9 @@ public: std::array get_indices(const double xyz[3]) const; + std::array + get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const; + protected: std::array n_cells; //! Number of cells along each axis std::array lower_left; //! Global lower-left corner of the lattice From c9abcfc0a1890da0263642e22b3cd9675f28242c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 17 Feb 2018 10:28:24 -0600 Subject: [PATCH 043/361] 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 044/361] 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 4c36446ffeda245e5741cfa9abeb671e898c14b8 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 17 Feb 2018 17:32:35 -0500 Subject: [PATCH 045/361] Remove array initializer braces to support GCC-4.8 --- src/lattice.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lattice.cpp b/src/lattice.cpp index fd89cbf1e..2902e3e47 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -1,5 +1,6 @@ #include "lattice.h" +#include #include #include @@ -7,9 +8,6 @@ #include "hdf5_interface.h" #include "xml_interface.h" -//TODO: this is only inlcuded for constants that should be moved elsewhere -//#include "surface.h" - //TODO: remove this include #include @@ -641,7 +639,7 @@ extern "C" { void lattice_get_indices(Lattice *lat, const double xyz[3], int i_xyz[3]) { - std::array inds {lat->get_indices(xyz)}; + std::array inds = lat->get_indices(xyz); i_xyz[0] = inds[0]; i_xyz[1] = inds[1]; i_xyz[2] = inds[2]; @@ -650,7 +648,7 @@ extern "C" { void lattice_get_local_xyz(Lattice *lat, const double global_xyz[3], const int i_xyz[3], double local_xyz[3]) { - std::array xyz {lat->get_local_xyz(global_xyz, i_xyz)}; + std::array xyz = lat->get_local_xyz(global_xyz, i_xyz); local_xyz[0] = xyz[0]; local_xyz[1] = xyz[1]; local_xyz[2] = xyz[2]; From 82fea220c40615bd44b61d120f7059b7efed3aaf Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Feb 2018 07:23:26 -0600 Subject: [PATCH 046/361] 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 047/361] 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 048/361] 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 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 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 049/361] 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 050/361] 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 051/361] 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 052/361] 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 053/361] 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 054/361] 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 055/361] 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 056/361] 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 057/361] 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 058/361] 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 059/361] 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 060/361] 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 061/361] 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 062/361] 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 063/361] 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 064/361] 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 065/361] 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 066/361] 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 067/361] 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 068/361] 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 069/361] 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 070/361] 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 071/361] 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 072/361] 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 073/361] 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 074/361] 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 075/361] 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 076/361] 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 077/361] 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 92697ec06eae659cfd3a801d7bca52efee81cb2d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 21 Feb 2018 15:36:27 -0600 Subject: [PATCH 078/361] 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 079/361] 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 080/361] 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 081/361] 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 082/361] 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 083/361] 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 084/361] 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 085/361] 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 086/361] 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 762313943d8bb07c5e55a70ead1d5fbfa493d33e Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 28 Feb 2018 13:22:03 -0500 Subject: [PATCH 087/361] Add C++ code for openmc_extend_cells --- src/cell.cpp | 13 +++++++++++-- src/cell.h | 5 +++-- src/geometry_header.F90 | 14 +++++++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index e7b76a994..fe04f8c69 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -403,7 +403,7 @@ read_cells(pugi::xml_node *node) // Loop over XML cell elements and populate the array. for (pugi::xml_node cell_node: node->children("cell")) { - cells_c.push_back(Cell(cell_node)); + cells_c.push_back(new Cell(cell_node)); } } @@ -412,7 +412,7 @@ read_cells(pugi::xml_node *node) //============================================================================== extern "C" { - Cell* cell_pointer(int32_t cell_ind) {return &cells_c[cell_ind];} + Cell* cell_pointer(int32_t cell_ind) {return cells_c[cell_ind];} int32_t cell_id(Cell *c) {return c->id;} @@ -436,6 +436,15 @@ extern "C" { } void cell_to_hdf5(Cell *c, hid_t group) {c->to_hdf5(group);} + + void extend_cells_c(int32_t n) + { + cells_c.reserve(cells_c.size() + n); + for (int32_t i = 0; i < n; i++) { + cells_c.push_back(new Cell()); + } + n_cells = cells_c.size(); + } } //extern "C" void free_memory_cells_c() diff --git a/src/cell.h b/src/cell.h index 59cec1729..de5e25aa7 100644 --- a/src/cell.h +++ b/src/cell.h @@ -20,8 +20,7 @@ namespace openmc { extern "C" {int32_t n_cells {0};} class Cell; -//Cell *cells_c; -std::vector cells_c; +std::vector cells_c; std::map cell_dict; @@ -55,6 +54,8 @@ public: std::vector rpn; bool simple; //!< Does the region contain only intersections? + Cell() {}; + explicit Cell(pugi::xml_node cell_node); //! Determine if a cell contains the particle at a given location. diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index f5d79d0a6..af63cad7f 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -142,6 +142,12 @@ module geometry_header type(C_PTR), intent(in), value :: lat_ptr integer(HID_T), intent(in), value :: group end subroutine lattice_to_hdf5_c + + subroutine extend_cells_c(n) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT32_T), intent(in), value :: n + end subroutine extend_cells_c end interface !=============================================================================== @@ -452,6 +458,7 @@ contains 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_INT32_T) :: i integer(C_INT) :: err type(Cell), allocatable :: temp(:) ! temporary cells array @@ -473,7 +480,12 @@ contains ! 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 + + ! Extend the C++ cells array and get pointers to the C++ objects + call extend_cells_c(n) + do i = n_cells - n, n_cells + cells(i) % ptr = cell_pointer_c(i - 1) + end do err = 0 end function openmc_extend_cells From bedf99d2fea7f92b5558119b1ebc4a6602e2c920 Mon Sep 17 00:00:00 2001 From: Shikhar Kumar Date: Thu, 1 Mar 2018 15:56:24 -0500 Subject: [PATCH 088/361] 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 089/361] 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 090/361] 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 091/361] 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 092/361] 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 a69baeaed55d8a738e25907ebd1380ee9a0e5333 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 2 Mar 2018 02:12:31 -0500 Subject: [PATCH 093/361] Populate C++ Universe vector --- src/cell.cpp | 21 +++++++++++++++++++++ src/cell.h | 10 +++++++--- src/constants.h | 13 +++++++++++++ src/surface.h | 10 ++-------- 4 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 src/constants.h diff --git a/src/cell.cpp b/src/cell.cpp index fe04f8c69..136f36e06 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -5,6 +5,7 @@ #include #include +#include "constants.h" #include "error.h" #include "hdf5_interface.h" #include "surface.h" @@ -204,6 +205,12 @@ Cell::Cell(pugi::xml_node cell_node) universe = 0; } + if (check_for_node(cell_node, "fill")) { + fill = stoi(get_node_value(cell_node, "fill")); + } else { + fill = C_NONE; + } + std::string region_spec {""}; if (check_for_node(cell_node, "region")) { region_spec = get_node_value(cell_node, "region"); @@ -405,6 +412,20 @@ read_cells(pugi::xml_node *node) for (pugi::xml_node cell_node: node->children("cell")) { cells_c.push_back(new Cell(cell_node)); } + + // Populate the Universe vector and dictionary. + for (Cell *c : cells_c) { + int32_t uid = c->universe; + auto it = universe_dict.find(uid); + if (it == universe_dict.end()) { + universes_c.push_back(new Universe()); + universes_c.back()->id = uid; + universes_c.back()->cells.push_back(c); + universe_dict[uid] = universes_c.size() - 1; + } else { + universes_c[it->second]->cells.push_back(c); + } + } } //============================================================================== diff --git a/src/cell.h b/src/cell.h index de5e25aa7..8b540be35 100644 --- a/src/cell.h +++ b/src/cell.h @@ -21,8 +21,11 @@ extern "C" {int32_t n_cells {0};} class Cell; std::vector cells_c; +std::map cell_dict; -std::map cell_dict; +class Universe; +std::vector universes_c; +std::map universe_dict; //============================================================================== //! A geometry primitive that fills all space and contains cells. @@ -30,10 +33,10 @@ std::map cell_dict; class Universe { - public: +public: int32_t id; //! Unique ID int32_t type; - std::vector cells; //! Cells within this universe + std::vector cells; //! Cells within this universe double x0, y0, z0; //! Translation coordinates. }; @@ -47,6 +50,7 @@ public: int32_t id; //!< Unique ID std::string name{""}; //!< User-defined name int32_t universe; //!< Universe # this cell is in + int32_t fill; //!< Universe # filling this cell //! Definition of spatial region as Boolean expression of half-spaces std::vector region; diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 000000000..b725809fe --- /dev/null +++ b/src/constants.h @@ -0,0 +1,13 @@ +#ifndef CONSTANTS_H +#define CONSTANTS_H + + +namespace openmc{ + +extern "C" double FP_COINCIDENT; +constexpr double INFTY{std::numeric_limits::max()}; +constexpr int C_NONE {-1}; + +} // namespace openmc + +#endif // CONSTANTS_H diff --git a/src/surface.h b/src/surface.h index 046610daa..2be21c063 100644 --- a/src/surface.h +++ b/src/surface.h @@ -8,6 +8,8 @@ #include "hdf5.h" #include "pugixml/pugixml.hpp" +#include "constants.h" + namespace openmc { @@ -20,14 +22,6 @@ extern "C" const int BC_VACUUM; extern "C" const int BC_REFLECT; extern "C" const int BC_PERIODIC; -//============================================================================== -// Constants that should eventually be moved out of this file -//============================================================================== - -extern "C" double FP_COINCIDENT; -constexpr double INFTY{std::numeric_limits::max()}; -constexpr int C_NONE {-1}; - //============================================================================== // Global variables //============================================================================== From efb264da605b1b818e1e62b62decdbe10e8fa03d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Mar 2018 06:20:16 -0600 Subject: [PATCH 094/361] 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 095/361] 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 096/361] 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 From 80959dc82bb0f3ba5a8b0f592eb8191721e7c410 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 13 Mar 2018 13:53:07 -0400 Subject: [PATCH 097/361] Dont import capi (through model) on import openmc --- openmc/model/model.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index 72a2c50dd..cd5153321 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -1,11 +1,7 @@ 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} +from openmc.checkvalue import check_type, check_value class Model(object): @@ -140,7 +136,8 @@ class Model(object): for plot in plots: self._plots.append(plot) - def deplete(self, timesteps, power, chain_file=None, method='cecm', **kwargs): + def deplete(self, timesteps, power, chain_file=None, method='cecm', + **kwargs): """Deplete model using specified timesteps/power Parameters @@ -164,11 +161,21 @@ class Model(object): :func:`openmc.deplete.integrator.cecm`) """ + # Import the depletion module. This is done here rather than the module + # header to delay importing openmc.capi (through openmc.deplete) which + # can be tough to install properly. + import openmc.deplete as dep + # Create OpenMC transport operator op = dep.Operator(self.geometry, self.settings, chain_file) # Perform depletion - _DEPLETE_METHODS[method](op, timesteps, power, **kwargs) + if method == 'predictor': + dep.integrator.predictor(op, timesteps, power, **kwargs) + elif method == 'cecm': + dep.integrator.cecm(op, timesteps, power, **kwargs) + else: + check_value('method', method, ('cecm', 'predictor')) def export_to_xml(self): """Export model to XML files.""" From 7bb066bc9aab65992055667ac2e641cd708c3203 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 13 Mar 2018 17:15:58 -0400 Subject: [PATCH 098/361] Infer WMP vales like num_l and fissionable --- openmc/data/multipole.py | 123 ++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 66 deletions(-) diff --git a/openmc/data/multipole.py b/openmc/data/multipole.py index 33078f504..fd5bf28b3 100644 --- a/openmc/data/multipole.py +++ b/openmc/data/multipole.py @@ -24,7 +24,7 @@ _RM_RF = 3 # Residue fission # Multi-level Breit Wigner indices _MLBW_RT = 1 # Residue total -_MLBW_RX = 2 # Residue compettitive +_MLBW_RX = 2 # Residue competitive _MLBW_RA = 3 # Residue absorption _MLBW_RF = 4 # Residue fission @@ -141,6 +141,12 @@ def _broaden_wmp_polynomials(E, dopp, n): class WindowedMultipole(EqualityMixin): """Resonant cross sections represented in the windowed multipole format. + Parameters + ---------- + formalism : {'MLBW', 'RM'} + The R-matrix formalism used to reconstruct resonances. Either 'MLBW' + for multi-level Breit Wigner or 'RM' for Reich-Moore. + Attributes ---------- num_l : Integral @@ -195,11 +201,9 @@ class WindowedMultipole(EqualityMixin): a/E + b/sqrt(E) + c + d sqrt(E) + ... """ - def __init__(self): - self.num_l = None - self.fit_order = None - self.fissionable = None - self.formalism = None + def __init__(self, formalism): + self._num_l = None + self.formalism = formalism self.spacing = None self.sqrtAWR = None self.start_E = None @@ -218,11 +222,15 @@ class WindowedMultipole(EqualityMixin): @property def fit_order(self): - return self._fit_order + return self.curvefit.shape[1] - 1 @property def fissionable(self): - return self._fissionable + if self.formalism == 'RM': + return self.data.shape[1] == 4 + else: + # Assume self.formalism == 'MLBW' + return self.data.shape[1] == 5 @property def formalism(self): @@ -272,35 +280,10 @@ class WindowedMultipole(EqualityMixin): def curvefit(self): return self._curvefit - @num_l.setter - def num_l(self, num_l): - if num_l is not None: - cv.check_type('num_l', num_l, Integral) - cv.check_greater_than('num_l', num_l, 1, equality=True) - cv.check_less_than('num_l', num_l, 4, equality=True) - # There is an if block in _evaluate that assumes num_l <= 4. - self._num_l = num_l - - @fit_order.setter - def fit_order(self, fit_order): - if fit_order is not None: - cv.check_type('fit_order', fit_order, Integral) - cv.check_greater_than('fit_order', fit_order, 2, equality=True) - # _broaden_wmp_polynomials assumes the curve fit has at least 3 - # terms. - self._fit_order = fit_order - - @fissionable.setter - def fissionable(self, fissionable): - if fissionable is not None: - cv.check_type('fissionable', fissionable, bool) - self._fissionable = fissionable - @formalism.setter def formalism(self, formalism): - if formalism is not None: - cv.check_type('formalism', formalism, str) - cv.check_value('formalism', formalism, ('MLBW', 'RM')) + cv.check_type('formalism', formalism, str) + cv.check_value('formalism', formalism, ('MLBW', 'RM')) self._formalism = formalism @spacing.setter @@ -337,9 +320,20 @@ class WindowedMultipole(EqualityMixin): cv.check_type('data', data, np.ndarray) if len(data.shape) != 2: raise ValueError('Multipole data arrays must be 2D') - if data.shape[1] not in (3, 4, 5): # 3 or 4 for RM, 4 or 5 for MLBW - raise ValueError('The second dimension of multipole data arrays' - ' must have a length of 3, 4 or 5') + if self.formalism == 'RM': + if data.shape[1] not in (3, 4): + raise ValueError('For the Reich-Moore formalism, ' + 'data.shape[1] must be 3 or 4. One value for the pole.' + ' One each for the total and absorption residues. ' + 'Possibly one more for a fission residue.') + else: + # Assume self.formalism == 'MLBW' + if data.shape[1] not in (4, 5): + raise ValueError('For the Multi-level Breit-Wigner ' + 'formalism, data.shape[1] must be 4 or 5. One value ' + 'for the pole. One each for the total, competitive, ' + 'and absorption residues. Possibly one mor efor a ' + 'fission residue.') if not np.issubdtype(data.dtype, complex): raise TypeError('Multipole data arrays must be complex dtype') self._data = data @@ -363,6 +357,12 @@ class WindowedMultipole(EqualityMixin): if not np.issubdtype(l_value.dtype, int): raise TypeError('Multipole l_value arrays must be integer' ' dtype') + + self._num_l = len(np.unique(l_value)) + + else: + self._num_l = None + self._l_value = l_value @w_start.setter @@ -442,20 +442,12 @@ class WindowedMultipole(EqualityMixin): 'Python API expects version ' + WMP_VERSION) group = h5file['nuclide'] - out = cls() - # Read scalar values. Note that group['max_w'] is ignored. - length = group['length'].value - windows = group['windows'].value - out.num_l = group['num_l'].value - out.fit_order = group['fit_order'].value - out.fissionable = bool(group['fissionable'].value) - if group['formalism'].value == _FORM_MLBW: - out.formalism = 'MLBW' + out = cls('MLBW') elif group['formalism'].value == _FORM_RM: - out.formalism = 'RM' + out = cls('RM') else: raise ValueError('Unrecognized/Unsupported R-matrix formalism') @@ -466,37 +458,36 @@ class WindowedMultipole(EqualityMixin): # Read arrays. - err = "WMP '{}' array shape is not consistent with the '{}' value" + err = "WMP '{}' array shape is not consistent with the '{}' array shape" out.data = group['data'].value - if out.data.shape[0] != length: - raise ValueError(err.format('data', 'length')) + + out.l_value = group['l_value'].value + if out.l_value.shape[0] != out.data.shape[0]: + raise ValueError(err.format('l_value', 'data')) out.pseudo_k0RS = group['pseudo_K0RS'].value if out.pseudo_k0RS.shape[0] != out.num_l: - raise ValueError(err.format('pseudo_k0RS', 'num_l')) - - out.l_value = group['l_value'].value - if out.l_value.shape[0] != length: - raise ValueError(err.format('l_value', 'length')) + raise ValueError(err.format('pseudo_k0RS', 'l_value')) out.w_start = group['w_start'].value - if out.w_start.shape[0] != windows: - raise ValueError(err.format('w_start', 'windows')) out.w_end = group['w_end'].value - if out.w_end.shape[0] != windows: - raise ValueError(err.format('w_end', 'windows')) + if out.w_end.shape[0] != out.w_start.shape[0]: + raise ValueError(err.format('w_end', 'w_start')) out.broaden_poly = group['broaden_poly'].value.astype(np.bool) - if out.broaden_poly.shape[0] != windows: - raise ValueError(err.format('broaden_poly', 'windows')) + if out.broaden_poly.shape[0] != out.w_start.shape[0]: + raise ValueError(err.format('broaden_poly', 'w_start')) out.curvefit = group['curvefit'].value - if out.curvefit.shape[0] != windows: - raise ValueError(err.format('curvefit', 'windows')) - if out.curvefit.shape[1] != out.fit_order + 1: - raise ValueError(err.format('curvefit', 'fit_order')) + if out.curvefit.shape[0] != out.w_start.shape[0]: + raise ValueError(err.format('curvefit', 'w_start')) + + # _broaden_wmp_polynomials assumes the curve fit has at least 3 terms. + if out.fit_order < 2: + raise ValueError("Windowed multipole is only supported for " + "curvefits with 3 or more terms.") # Note that all the file 3 data (group['reactions/MT...']) are ignored. From f2c2b4aac8c4591fa48c2f92828436b3ec836ec2 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 13 Mar 2018 17:19:55 -0400 Subject: [PATCH 099/361] Update WMP format docs --- docs/source/io_formats/data_wmp.rst | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/docs/source/io_formats/data_wmp.rst b/docs/source/io_formats/data_wmp.rst index d645c6e8f..4fa25ee3b 100644 --- a/docs/source/io_formats/data_wmp.rst +++ b/docs/source/io_formats/data_wmp.rst @@ -30,10 +30,6 @@ Windowed Multipole Library Format Highest energy the windowed multipole part of the library is valid for. - **energy_points** (*double[]*) Energy grid for the pointwise library in the reaction group. - - **fissionable** (*int*) - 1 if this nuclide has fission data. 0 if it does not. - - **fit_order** (*int*) - The order of the curve fit. - **formalism** (*int*) The formalism of the underlying data. Uses the `ENDF-6`_ format formalism numbers. @@ -51,18 +47,12 @@ Windowed Multipole Library Format - **l_value** (*int[]*) The index for a corresponding pole. Equivalent to the :math:`l` quantum number of the resonance the pole comes from :math:`+1`. - - **length** (*int*) - Total count of poles in `data`. - **max_w** (*int*) Maximum number of poles in a window. - **MT_count** (*int*) Number of pointwise tables in the library. - **MT_list** (*int[]*) A list of available MT identifiers. See `ENDF-6`_ for meaning. - - **n_grid** (*int*) - Total length of the pointwise data. - - **num_l** (*int*) - Number of possible :math:`l` quantum states for this nuclide. - **pseudo_K0RS** (*double[]*) :math:`l` dependent value of @@ -90,13 +80,6 @@ Windowed Multipole Library Format The pole to start from for each window. - **w_end** (*int[]*) The pole to end at for each window. - - **windows** (*int*) - Number of windows. - -**/nuclide/reactions/MT** - - **MT_sigma** (*double[]*) -- Cross section value for this reaction. - - **Q_value** (*double*) -- Energy released in this reaction, in eV. - - **threshold** (*int*) -- The first non-zero entry in ``MT_sigma``. .. _h5py: http://docs.h5py.org/en/latest/ .. _ENDF-6: https://www.oecd-nea.org/dbdata/data/manual-endf/endf102.pdf From f047e79540b08aeaa0bca0b72db8ac7529a63685 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 13 Mar 2018 20:48:47 -0400 Subject: [PATCH 100/361] Infer WMP array sizes in F90 --- CMakeLists.txt | 1 - src/input_xml.F90 | 3 +- src/multipole.F90 | 89 ------------------- src/multipole_header.F90 | 179 ++++++++++++++++++++++++++------------- 4 files changed, 120 insertions(+), 152 deletions(-) delete mode 100644 src/multipole.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index d3673df26..be62dcd57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -368,7 +368,6 @@ set(LIBOPENMC_FORTRAN_SRC src/message_passing.F90 src/mgxs_data.F90 src/mgxs_header.F90 - src/multipole.F90 src/multipole_header.F90 src/nuclide_header.F90 src/output.F90 diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 8c3669b2e..918d471d3 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -21,7 +21,6 @@ module input_xml use message_passing use mgxs_data, only: create_macro_xs, read_mgxs use mgxs_header - use multipole, only: multipole_read use nuclide_header use output, only: title, header, print_plot use plot_header @@ -4377,7 +4376,7 @@ contains allocate(nuc % multipole) ! Call the read routine - call multipole_read(filename, nuc % multipole, i_table) + call nuc % multipole % from_hdf5(filename) nuc % mp_present = .true. end associate diff --git a/src/multipole.F90 b/src/multipole.F90 deleted file mode 100644 index 0282adb7d..000000000 --- a/src/multipole.F90 +++ /dev/null @@ -1,89 +0,0 @@ -module multipole - - use hdf5 - - use constants - use error, only: fatal_error - use hdf5_interface - use multipole_header, only: MultipoleArray, FIT_T, FIT_A, FIT_F, & - MP_FISS, FORM_MLBW, FORM_RM - use nuclide_header, only: nuclides - - implicit none - -contains - -!=============================================================================== -! MULTIPOLE_READ Reads in a multipole HDF5 file with the original API -! specification. Subject to change as the library format matures. -!=============================================================================== - - subroutine multipole_read(filename, multipole, i_table) - character(len=*), intent(in) :: filename ! Filename of the - ! multipole library - ! to load - type(MultipoleArray), intent(out), target :: multipole ! The object to fill - integer, intent(in) :: i_table ! index in nuclides/ - ! sab_tables - - integer(HID_T) :: file_id - integer(HID_T) :: group_id - - ! Intermediate loading components - integer :: is_fissionable - character(len=10) :: version - - associate (nuc => nuclides(i_table)) - - ! Open file for reading and move into the /isotope group - file_id = file_open(filename, 'r', parallel=.true.) - group_id = open_group(file_id, "/nuclide") - - ! Check the file version number. - call read_dataset(version, file_id, "version") - if (version /= VERSION_MULTIPOLE) call fatal_error("The current multipole& - & format version is " // trim(VERSION_MULTIPOLE) // " but the file "& - // trim(filename) // " uses version " // trim(version) // ".") - - ! Load in all the array size scalars - call read_dataset(multipole % length, group_id, "length") - call read_dataset(multipole % windows, group_id, "windows") - call read_dataset(multipole % num_l, group_id, "num_l") - call read_dataset(multipole % fit_order, group_id, "fit_order") - call read_dataset(multipole % max_w, group_id, "max_w") - call read_dataset(is_fissionable, group_id, "fissionable") - if (is_fissionable == MP_FISS) then - multipole % fissionable = .true. - else - multipole % fissionable = .false. - end if - call read_dataset(multipole % formalism, group_id, "formalism") - - call read_dataset(multipole % spacing, group_id, "spacing") - call read_dataset(multipole % sqrtAWR, group_id, "sqrtAWR") - call read_dataset(multipole % start_E, group_id, "start_E") - call read_dataset(multipole % end_E, group_id, "end_E") - - ! Allocate the multipole array components - call multipole % allocate() - - ! Read in arrays - call read_dataset(multipole % data, group_id, "data") - call read_dataset(multipole % pseudo_k0RS, group_id, "pseudo_K0RS") - call read_dataset(multipole % l_value, group_id, "l_value") - call read_dataset(multipole % w_start, group_id, "w_start") - call read_dataset(multipole % w_end, group_id, "w_end") - call read_dataset(multipole % broaden_poly, group_id, "broaden_poly") - - call read_dataset(multipole % curvefit, group_id, "curvefit") - - call close_group(group_id) - - ! Close file - call file_close(file_id) - - end associate - - end subroutine multipole_read - -end module multipole diff --git a/src/multipole_header.F90 b/src/multipole_header.F90 index 143047b0b..ec82b16a4 100644 --- a/src/multipole_header.F90 +++ b/src/multipole_header.F90 @@ -1,5 +1,12 @@ module multipole_header + use hdf5 + + use constants + use dict_header, only: DictIntInt + use error, only: fatal_error + use hdf5_interface + implicit none !======================================================================== @@ -29,9 +36,6 @@ module multipole_header FIT_A = 2, & ! Absorption FIT_F = 3 ! Fission - ! Value of 'true' when checking if nuclide is fissionable - integer, parameter :: MP_FISS = 1 - !=============================================================================== ! MULTIPOLE contains all the components needed for the windowed multipole ! temperature dependent cross section libraries for the resolved resonance @@ -42,87 +46,142 @@ module multipole_header !========================================================================= ! Isotope Properties - logical :: fissionable = .false. ! Is this isotope fissionable? - integer :: length ! Number of poles - integer, allocatable :: l_value(:) ! The l index of the pole - real(8), allocatable :: pseudo_k0RS(:) ! The value (sqrt(2*mass neutron)/reduced planck constant) - ! * AWR/(AWR + 1) * scattering radius for each l - complex(8), allocatable :: data(:,:) ! Contains all of the pole-residue data - real(8) :: sqrtAWR ! Square root of the atomic weight ratio + + logical :: fissionable ! Is this isotope fissionable? + integer, allocatable :: l_value(:) ! The l index of the pole + integer :: num_l ! Number of unique l values + real(8), allocatable :: pseudo_k0RS(:) ! The value (sqrt(2*mass neutron + ! /reduced planck constant) + ! * AWR/(AWR + 1) + ! * scattering radius for + ! each l + complex(8), allocatable :: data(:,:) ! Poles and residues + real(8) :: sqrtAWR ! Square root of the atomic + ! weight ratio + integer :: formalism ! R-matrix formalism !========================================================================= ! Windows - integer :: windows ! Number of windows - integer :: fit_order ! Order of the fit. 1 linear, 2 quadratic, etc. + integer :: fit_order ! Order of the fit. 1 linear, + ! 2 quadratic, etc. real(8) :: start_E ! Start energy for the windows real(8) :: end_E ! End energy for the windows - real(8) :: spacing ! The actual spacing in sqrt(E) space. - ! spacing = sqrt(multipole_w%endE - multipole_w%startE)/multipole_w%windows - integer, allocatable :: w_start(:) ! Contains the index of the pole at the start of the window - integer, allocatable :: w_end(:) ! Contains the index of the pole at the end of the window - real(8), allocatable :: curvefit(:,:,:) ! Contains the fitting function. (reaction type, coeff index, window index) + real(8) :: spacing ! The actual spacing in sqrt(E) + ! space. + ! spacing = sqrt(multipole_w % endE - multipole_w % startE) + ! / multipole_w % windows + integer, allocatable :: w_start(:) ! Contains the index of the pole at + ! the start of the window + integer, allocatable :: w_end(:) ! Contains the index of the pole at + ! the end of the window + real(8), allocatable :: curvefit(:,:,:) ! Contains the fitting function. + ! (reaction type, coeff index, + ! window index) integer, allocatable :: broaden_poly(:) ! if 1, broaden, if 0, don't. - !========================================================================= - ! Storage Helpers - integer :: num_l - integer :: max_w + contains - integer :: formalism + procedure :: from_hdf5 => multipole_from_hdf5 - contains - procedure :: allocate => multipole_allocate ! Allocates Multipole end type MultipoleArray contains !=============================================================================== -! MULTIPOLE_ALLOCATE allocates necessary data for Multipole. +! FROM_HDF5 loads multipole data from an HDF5 file. !=============================================================================== - subroutine multipole_allocate(multipole) - class(MultipoleArray), intent(inout) :: multipole ! Multipole object to allocate. + subroutine multipole_from_hdf5(this, filename) + class(MultipoleArray), intent(inout) :: this + character(len=*), intent(in) :: filename - ! This function assumes length, numL, fissionable, windows, fitorder, - ! and formalism are known + character(len=10) :: version + integer :: i, n_poles, n_residue_types, n_windows + integer(HSIZE_T) :: dims_1d(1), dims_2d(2), dims_3d(3) + integer(HID_T) :: file_id + integer(HID_T) :: group_id + integer(HID_T) :: dset + type(DictIntInt) :: l_val_dict - ! Allocate the pole-residue storage. - ! MLBW has one more pole than Reich-Moore, and fissionable nuclides - ! have further one more. - if (multipole % formalism == FORM_MLBW) then - if (multipole % fissionable) then - allocate(multipole % data(5, multipole % length)) - else - allocate(multipole % data(4, multipole % length)) - end if - else if (multipole % formalism == FORM_RM) then - if (multipole % fissionable) then - allocate(multipole % data(4, multipole % length)) - else - allocate(multipole % data(3, multipole % length)) - end if + ! Open file for reading and move into the /isotope group + file_id = file_open(filename, 'r', parallel=.true.) + group_id = open_group(file_id, "/nuclide") + + ! Check the file version number. + call read_dataset(version, file_id, "version") + if (version /= VERSION_MULTIPOLE) call fatal_error("The current multipole& + & format version is " // trim(VERSION_MULTIPOLE) // " but the file "& + // trim(filename) // " uses version " // trim(version) // ".") + + ! Read scalar values. + call read_dataset(this % formalism, group_id, "formalism") + call read_dataset(this % spacing, group_id, "spacing") + call read_dataset(this % sqrtAWR, group_id, "sqrtAWR") + call read_dataset(this % start_E, group_id, "start_E") + call read_dataset(this % end_E, group_id, "end_E") + + ! Read the "data" array. Use its shape to figure out the number of poles + ! and residue types in this data. + dset = open_dataset(group_id, "data") + call get_shape(dset, dims_2d) + n_residue_types = int(dims_2d(1), 4) - 1 + n_poles = int(dims_2d(2), 4) + allocate(this % data(n_residue_types+1, n_poles)) + call read_dataset(this % data, dset) + call close_dataset(dset) + + ! Check to see if this data includes fission residues. + if (this % formalism == FORM_RM) then + this % fissionable = (n_residue_types == 3) + else + ! Assume FORM_MLBW. + this % fissionable = (n_residue_types == 4) + end if + + ! Read the "l_value" array. + allocate(this % l_value(n_poles)) + call read_dataset(this % l_value, group_id, "l_value") + + ! Figure out the number of unique l values in the l_value array. + do i = 1, n_poles + if (.not. l_val_dict % has(this % l_value(i))) then + call l_val_dict % set(i, 0) end if + end do + this % num_l = l_val_dict % size() + call l_val_dict % clear() - ! Allocate the l value table for each pole-residue set. - allocate(multipole % l_value(multipole % length)) + ! Read the "pseudo_K0RS" array. + allocate(this % pseudo_k0RS(this % num_l)) + call read_dataset(this % pseudo_k0RS, group_id, "pseudo_K0RS") - ! Allocate the table of pseudo_k0RS values at each l. - allocate(multipole % pseudo_k0RS(multipole % num_l)) + ! Read the "w_start" array and use its shape to figure out the number of + ! windows. + dset = open_dataset(group_id, "w_start") + call get_shape(dset, dims_1d) + n_windows = int(dims_1d(1), 4) + allocate(this % w_start(n_windows)) + call read_dataset(this % w_start, dset) + call close_dataset(dset) - ! Allocate window start, window end - allocate(multipole % w_start(multipole % windows)) - allocate(multipole % w_end(multipole % windows)) + ! Read the "w_end" and "broaden_poly" arrays. + allocate(this % w_end(n_windows)) + call read_dataset(this % w_end, group_id, "w_end") + allocate(this % broaden_poly(n_windows)) + call read_dataset(this % broaden_poly, group_id, "broaden_poly") - ! Allocate broaden_poly - allocate(multipole % broaden_poly(multipole % windows)) + ! Read the "curvefit" array. + dset = open_dataset(group_id, "curvefit") + call get_shape(dset, dims_3d) + allocate(this % curvefit(dims_3d(1), dims_3d(2), dims_3d(3))) + call read_dataset(this % curvefit, dset) + call close_dataset(dset) + this % fit_order = int(dims_3d(2), 4) - 1 - ! Allocate curvefit - if(multipole % fissionable) then - allocate(multipole % curvefit(FIT_F, multipole % fit_order+1, multipole % windows)) - else - allocate(multipole % curvefit(FIT_A, multipole % fit_order+1, multipole % windows)) - end if - end subroutine + ! Close the group and file. + call close_group(group_id) + call file_close(file_id) + end subroutine multipole_from_hdf5 end module multipole_header From ba15959728ed996e1fd11f6b6f7c7fd4d87af539 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 13 Mar 2018 21:21:12 -0400 Subject: [PATCH 101/361] Add WMP writing functionality to PyAPI --- openmc/data/multipole.py | 52 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/openmc/data/multipole.py b/openmc/data/multipole.py index fd5bf28b3..ac1e93447 100644 --- a/openmc/data/multipole.py +++ b/openmc/data/multipole.py @@ -332,7 +332,7 @@ class WindowedMultipole(EqualityMixin): raise ValueError('For the Multi-level Breit-Wigner ' 'formalism, data.shape[1] must be 4 or 5. One value ' 'for the pole. One each for the total, competitive, ' - 'and absorption residues. Possibly one mor efor a ' + 'and absorption residues. Possibly one more for a ' 'fission residue.') if not np.issubdtype(data.dtype, complex): raise TypeError('Multipole data arrays must be complex dtype') @@ -428,6 +428,7 @@ class WindowedMultipole(EqualityMixin): format. """ + if isinstance(group_or_filename, h5py.Group): group = group_or_filename else: @@ -442,7 +443,7 @@ class WindowedMultipole(EqualityMixin): 'Python API expects version ' + WMP_VERSION) group = h5file['nuclide'] - # Read scalar values. Note that group['max_w'] is ignored. + # Read scalars. if group['formalism'].value == _FORM_MLBW: out = cls('MLBW') @@ -489,8 +490,6 @@ class WindowedMultipole(EqualityMixin): raise ValueError("Windowed multipole is only supported for " "curvefits with 3 or more terms.") - # Note that all the file 3 data (group['reactions/MT...']) are ignored. - return out def _evaluate(self, E, T): @@ -652,3 +651,48 @@ class WindowedMultipole(EqualityMixin): fun = np.vectorize(lambda x: self._evaluate(x, T)) return fun(E) + + def to_hdf5(self, path, libver='earliest'): + """Export windowed multipole data to an HDF5 file. + + Parameters + ---------- + path : str + Path to write HDF5 file to + libver : {'earliest', 'latest'} + Compatibility mode for the HDF5 file. 'latest' will produce files + that are less backwards compatible but have performance benefits. + + """ + + # Open file and write version. + f = h5py.File(path, 'w', libver=libver) + f.create_dataset('version', (1, ), dtype='S10') + f['version'][:] = WMP_VERSION.encode('ASCII') + + # Make a nuclide group. + g = f.create_group('nuclide') + + # Write scalars. + if self.formalism == 'MLBW': + g.create_dataset('formalism', + data=np.array(_FORM_MLBW, dtype=np.int32)) + else: + # Assume RM. + g.create_dataset('formalism', + data=np.array(_FORM_RM, dtype=np.int32)) + g.create_dataset('spacing', data=np.array(self.spacing)) + g.create_dataset('sqrtAWR', data=np.array(self.sqrtAWR)) + g.create_dataset('start_E', data=np.array(self.start_E)) + g.create_dataset('end_E', data=np.array(self.end_E)) + + # Write arrays. + g.create_dataset('data', data=self.data) + g.create_dataset('l_value', data=self.l_value) + g.create_dataset('pseudo_K0RS', data=self.pseudo_k0RS) + g.create_dataset('w_start', data=self.w_start) + g.create_dataset('w_end', data=self.w_end) + g.create_dataset('broaden_poly', data=self.broaden_poly) + g.create_dataset('curvefit', data=self.curvefit) + + f.close() From d0b59a21be2d2d0e0f598db12214cc14502fa614 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 13 Mar 2018 21:44:54 -0400 Subject: [PATCH 102/361] Remove unused File 3 stuff from wmp format docs --- docs/source/io_formats/data_wmp.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/source/io_formats/data_wmp.rst b/docs/source/io_formats/data_wmp.rst index 4fa25ee3b..6174e85ff 100644 --- a/docs/source/io_formats/data_wmp.rst +++ b/docs/source/io_formats/data_wmp.rst @@ -28,8 +28,6 @@ Windowed Multipole Library Format ":math:`r`" and ":math:`i`" identifiers, similar to how `h5py`_ does it. - **end_E** (*double*) Highest energy the windowed multipole part of the library is valid for. - - **energy_points** (*double[]*) - Energy grid for the pointwise library in the reaction group. - **formalism** (*int*) The formalism of the underlying data. Uses the `ENDF-6`_ format formalism numbers. @@ -47,12 +45,6 @@ Windowed Multipole Library Format - **l_value** (*int[]*) The index for a corresponding pole. Equivalent to the :math:`l` quantum number of the resonance the pole comes from :math:`+1`. - - **max_w** (*int*) - Maximum number of poles in a window. - - **MT_count** (*int*) - Number of pointwise tables in the library. - - **MT_list** (*int[]*) - A list of available MT identifiers. See `ENDF-6`_ for meaning. - **pseudo_K0RS** (*double[]*) :math:`l` dependent value of From b71ad222d090497aab04a76b684a3c8e4c72cd02 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 13 Mar 2018 22:05:59 -0400 Subject: [PATCH 103/361] Expand windowed multipole unit test --- tests/unit_tests/test_data_multipole.py | 26 +++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_data_multipole.py b/tests/unit_tests/test_data_multipole.py index 4a4a9f0d2..de7c1cc93 100644 --- a/tests/unit_tests/test_data_multipole.py +++ b/tests/unit_tests/test_data_multipole.py @@ -17,6 +17,13 @@ def u235(): return openmc.data.WindowedMultipole.from_hdf5(filename) +@pytest.fixture(scope='module') +def u234(): + directory = os.environ['OPENMC_MULTIPOLE_LIBRARY'] + filename = os.path.join(directory, '092234.h5') + return openmc.data.WindowedMultipole.from_hdf5(filename) + + @pytest.fixture(scope='module') def fe56(): directory = os.environ['OPENMC_MULTIPOLE_LIBRARY'] @@ -24,8 +31,8 @@ def fe56(): return openmc.data.WindowedMultipole.from_hdf5(filename) -def test_evaluate(u235): - """Make sure multipole object can be called.""" +def test_evaluate_rm(u235): + """Make sure a Reich-Moore 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) @@ -33,6 +40,15 @@ def test_evaluate(u235): assert total[1] == pytest.approx(91.12534964) +def test_evaluate_mlbw(u234): + """Make sure a Multi-Level Breit-Wigner multipole object can be called.""" + energies = [1e-3, 1.0, 10.0, 50.] + total, absorption, fission = u234(energies, 0.0) + assert total[3] == pytest.approx(15.02827953) + total, absorption, fission = u234(energies, 300.0) + assert total[3] == pytest.approx(15.08269143) + + def test_high_l(fe56): """Test a nuclide (Fe56) with a high l-value (4).""" energies = [1e-3, 1.0, 10.0, 1e3, 1e5] @@ -40,3 +56,9 @@ def test_high_l(fe56): assert total[0] == pytest.approx(25.072619556789267) total, absorption, fission = fe56(energies, 300.0) assert total[0] == pytest.approx(27.85535792368082) + + +def test_to_hdf5(tmpdir, u235): + filename = str(tmpdir.join('092235.h5')) + u235.to_hdf5(filename) + assert os.path.exists(filename) From 1e49f3c889c379b667a9f23db1d64f5b2c1be17d Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 14 Mar 2018 12:51:19 -0400 Subject: [PATCH 104/361] Fix multipole l value counting --- src/multipole_header.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/multipole_header.F90 b/src/multipole_header.F90 index ec82b16a4..7fb438bce 100644 --- a/src/multipole_header.F90 +++ b/src/multipole_header.F90 @@ -147,7 +147,7 @@ contains ! Figure out the number of unique l values in the l_value array. do i = 1, n_poles if (.not. l_val_dict % has(this % l_value(i))) then - call l_val_dict % set(i, 0) + call l_val_dict % set(this % l_value(i), 0) end if end do this % num_l = l_val_dict % size() From 0508274a3dd97029b914c9031df5e9ff753f042b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Mar 2018 10:36:08 -0600 Subject: [PATCH 105/361] Add useful warnings for add_nuclide/add_element --- docs/source/usersguide/materials.rst | 14 ++++++-- openmc/data/ace.py | 4 +-- openmc/data/data.py | 5 +-- openmc/data/endf.py | 4 +-- openmc/material.py | 49 ++++++++++------------------ tests/unit_tests/test_material.py | 4 +-- 6 files changed, 38 insertions(+), 42 deletions(-) diff --git a/docs/source/usersguide/materials.rst b/docs/source/usersguide/materials.rst index b1e0c151f..847276848 100644 --- a/docs/source/usersguide/materials.rst +++ b/docs/source/usersguide/materials.rst @@ -43,14 +43,22 @@ of an element, you specify the element itself. For example, Internally, OpenMC stores data on the atomic masses and natural abundances of all known isotopes and then uses this data to determine what isotopes should be added to the material. When the material is later exported to XML for use by the -:ref:`scripts_openmc` executable, you'll see that any natural elements are +:ref:`scripts_openmc` executable, you'll see that any natural elements were expanded to the naturally-occurring isotopes. +The :meth:`Material.add_element` method can also be used to add uranium at a +specified enrichment through the `enrichment` argument. For example, the +following would add 3.2% enriched uranium to a material:: + + mat.add_element('U', 1.0, enrichment=3.2) + +In addition to U235 and U238, concentrations of U234 and U236 will be present +and are determined through a correlation based on measured data. + Often, cross section libraries don't actually have all naturally-occurring isotopes for a given element. For example, in ENDF/B-VII.1, cross section evaluations are given for O16 and O17 but not for O18. If OpenMC is aware of -what cross sections you will be using (either through the -:attr:`Materials.cross_sections` attribute or the +what cross sections you will be using (through the :envvar:`OPENMC_CROSS_SECTIONS` environment variable), it will attempt to only put isotopes in your model for which you have cross section data. In the case of oxygen in ENDF/B-VII.1, the abundance of O18 would end up being lumped with O16. diff --git a/openmc/data/ace.py b/openmc/data/ace.py index 385408bd4..fa2705220 100644 --- a/openmc/data/ace.py +++ b/openmc/data/ace.py @@ -22,7 +22,7 @@ import sys import numpy as np from openmc.mixin import EqualityMixin -from openmc.data.endf import ENDF_FLOAT_RE +from openmc.data.endf import _ENDF_FLOAT_RE def ascii_to_binary(ascii_file, binary_file): """Convert an ACE file in ASCII format (type 1) to binary format (type 2). @@ -349,7 +349,7 @@ class Library(EqualityMixin): # after it). If it's too short, then we apply the ENDF float regular # expression. We don't do this by default because it's expensive! if xss.size != nxs[1] + 1: - datastr = ENDF_FLOAT_RE.sub(r'\1e\2', datastr) + datastr = _ENDF_FLOAT_RE.sub(r'\1e\2', datastr) xss = np.fromstring(datastr, sep=' ') assert xss.size == nxs[1] + 1 diff --git a/openmc/data/data.py b/openmc/data/data.py index fd1329961..3a625b622 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -136,6 +136,8 @@ ATOMIC_NUMBER = {value: key for key, value in ATOMIC_SYMBOL.items()} _ATOMIC_MASS = {} +_GND_NAME_RE = re.compile(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)') + def atomic_mass(isotope): """Return atomic mass of isotope in atomic mass units. @@ -352,8 +354,7 @@ def zam(name): """ try: - symbol, A, state = re.match(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)', - name).groups() + symbol, A, state = _GND_NAME_RE.match(name).groups() except AttributeError: raise ValueError("'{}' does not appear to be a nuclide name in GND " "format.".format(name)) diff --git a/openmc/data/endf.py b/openmc/data/endf.py index c44c66be0..0d1f402c2 100644 --- a/openmc/data/endf.py +++ b/openmc/data/endf.py @@ -45,7 +45,7 @@ SUM_RULES = {1: [2, 3], 106: list(range(750, 800)), 107: list(range(800, 850))} -ENDF_FLOAT_RE = re.compile(r'([\s\-\+]?\d*\.\d+)([\+\-]\d+)') +_ENDF_FLOAT_RE = re.compile(r'([\s\-\+]?\d*\.\d+)([\+\-]\d+)') def float_endf(s): @@ -68,7 +68,7 @@ def float_endf(s): The number """ - return float(ENDF_FLOAT_RE.sub(r'\1e\2', s)) + return float(_ENDF_FLOAT_RE.sub(r'\1e\2', s)) def get_text_record(file_obj): diff --git a/openmc/material.py b/openmc/material.py index d52d8a27b..053e516fa 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -379,33 +379,27 @@ class Material(IDManagerMixin): Parameters ---------- nuclide : str - Nuclide to add + Nuclide to add, e.g., 'Mo95' percent : float Atom or weight percent percent_type : {'ao', 'wo'} 'ao' for atom percent and 'wo' for weight percent """ + cv.check_type('nuclide', nuclide, str) + cv.check_type('percent', percent, Real) + cv.check_value('percent type', percent_type, {'ao', 'wo'}) if self._macroscopic is not None: msg = 'Unable to add a Nuclide to Material ID="{}" as a ' \ 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) - if not isinstance(nuclide, str): - msg = 'Unable to add a Nuclide to Material ID="{}" with a ' \ - 'non-string value "{}"'.format(self._id, nuclide) - raise ValueError(msg) - - elif not isinstance(percent, Real): - msg = 'Unable to add a Nuclide to Material ID="{}" with a ' \ - 'non-floating point value "{}"'.format(self._id, percent) - raise ValueError(msg) - - elif percent_type not in ('ao', 'wo'): - msg = 'Unable to add a Nuclide to Material ID="{}" with a ' \ - 'percent type "{}"'.format(self._id, percent_type) - raise ValueError(msg) + # If nuclide name doesn't look valid, give a warning + try: + openmc.data.zam(nuclide) + except ValueError as e: + warnings.warn(str(e)) self._nuclides.append((nuclide, percent, percent_type)) @@ -493,7 +487,7 @@ class Material(IDManagerMixin): Parameters ---------- element : str - Element to add + Element to add, e.g., 'Zr' percent : float Atom or weight percent percent_type : {'ao', 'wo'}, optional @@ -505,27 +499,15 @@ class Material(IDManagerMixin): (natural composition). """ + cv.check_type('nuclide', element, str) + cv.check_type('percent', percent, Real) + cv.check_value('percent type', percent_type, {'ao', 'wo'}) if self._macroscopic is not None: msg = 'Unable to add an Element to Material ID="{}" as a ' \ 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) - if not isinstance(element, str): - msg = 'Unable to add an Element to Material ID="{}" with a ' \ - 'non-string value "{}"'.format(self._id, element) - raise ValueError(msg) - - if not isinstance(percent, Real): - msg = 'Unable to add an Element to Material ID="{}" with a ' \ - 'non-floating point value "{}"'.format(self._id, percent) - raise ValueError(msg) - - if percent_type not in ['ao', 'wo']: - msg = 'Unable to add an Element to Material ID="{}" with a ' \ - 'percent type "{}"'.format(self._id, percent_type) - raise ValueError(msg) - if enrichment is not None: if not isinstance(enrichment, Real): msg = 'Unable to add an Element to Material ID="{}" with a ' \ @@ -551,6 +533,11 @@ class Material(IDManagerMixin): format(enrichment, self._id) warnings.warn(msg) + # Make sure element name is just that + if not element.isalpha(): + raise ValueError("Element name should be given by the " + "element's symbol, e.g., 'Zr'") + # Add naturally-occuring isotopes element = openmc.Element(element) for nuclide in element.expand(percent, percent_type, enrichment): diff --git a/tests/unit_tests/test_material.py b/tests/unit_tests/test_material.py index c251df3a6..b7b745408 100644 --- a/tests/unit_tests/test_material.py +++ b/tests/unit_tests/test_material.py @@ -15,9 +15,9 @@ def test_nuclides(uo2): """Test adding/removing nuclides.""" m = openmc.Material() m.add_nuclide('U235', 1.0) - with pytest.raises(ValueError): + with pytest.raises(TypeError): m.add_nuclide('H1', '1.0') - with pytest.raises(ValueError): + with pytest.raises(TypeError): m.add_nuclide(1.0, 'H1') with pytest.raises(ValueError): m.add_nuclide('H1', 1.0, 'oa') From f415700c86d5e4bbb8498b216c95e5fa5a78e00c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Mar 2018 10:57:02 -0600 Subject: [PATCH 106/361] Make materials with actinides depletable by default --- openmc/material.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 053e516fa..88479d8df 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -88,7 +88,7 @@ class Material(IDManagerMixin): self.name = name self.temperature = temperature self._density = None - self._density_units = '' + self._density_units = 'sum' self._depletable = False self._paths = None self._num_instances = None @@ -397,9 +397,13 @@ class Material(IDManagerMixin): # If nuclide name doesn't look valid, give a warning try: - openmc.data.zam(nuclide) + Z, _, _ = openmc.data.zam(nuclide) except ValueError as e: warnings.warn(str(e)) + else: + # For actinides, have the material be depletable by default + if Z >= 89: + self.depletable = True self._nuclides.append((nuclide, percent, percent_type)) @@ -541,7 +545,7 @@ class Material(IDManagerMixin): # Add naturally-occuring isotopes element = openmc.Element(element) for nuclide in element.expand(percent, percent_type, enrichment): - self._nuclides.append(nuclide) + self.add_nuclide(*nuclide) def add_s_alpha_beta(self, name, fraction=1.0): r"""Add an :math:`S(\alpha,\beta)` table to the material From ed7edf414179f34ccb9247f847b931e507c3ec21 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Mar 2018 12:09:27 -0600 Subject: [PATCH 107/361] Have Universe.plot return AxesImage. Add Plot.to_image method --- openmc/plots.py | 37 +++++++++++++++++++++++++++++++++++++ openmc/plotter.py | 3 ++- openmc/universe.py | 27 ++++++++++++++------------- 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/openmc/plots.py b/openmc/plots.py index 4414a39e1..a948c2a48 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -1,6 +1,7 @@ from collections.abc import Iterable, Mapping from numbers import Real, Integral from xml.etree import ElementTree as ET +import subprocess import sys import warnings @@ -650,6 +651,42 @@ class Plot(IDManagerMixin): return element + def to_image(self, openmc_exec='openmc', cwd='.', convert_exec='convert'): + """Render plot as an image + + Parameters + ---------- + openmc_exec : str + Path to OpenMC executable + cwd : str, optional + Path to working directory to run in + convert_exec : str, optional + Command that can convert PPM files into PNG files + + Returns + ------- + IPython.display.Image + Image generated + + """ + from IPython.display import Image + + # Create plots.xml + Plots([self]).export_to_xml() + + # Run OpenMC in geometry plotting mode + openmc.plot_geometry(False, openmc_exec, cwd) + + # Convert to .png + if self.filename is not None: + ppm_file = '{}.ppm'.format(self.filename) + else: + ppm_file = 'plot_{}.ppm'.format(self.id) + png_file = ppm_file.replace('.ppm', '.png') + subprocess.check_call([convert_exec, ppm_file, png_file]) + + return Image(png_file) + class Plots(cv.CheckedList): """Collection of Plots used for an OpenMC simulation. diff --git a/openmc/plotter.py b/openmc/plotter.py index 1bf6fe46f..191b50c56 100644 --- a/openmc/plotter.py +++ b/openmc/plotter.py @@ -2,7 +2,6 @@ from numbers import Integral, Real from itertools import chain import string -import matplotlib.pyplot as plt import numpy as np import openmc.checkvalue as cv @@ -125,6 +124,8 @@ def plot_xs(this, types, divisor_types=None, temperature=294., data_type=None, generated. """ + import matplotlib.pyplot as plt + cv.check_type("plot_CE", plot_CE, bool) if data_type is None: diff --git a/openmc/universe.py b/openmc/universe.py index 55f574c53..77138adcc 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -4,7 +4,6 @@ from numbers import Integral, Real import random import sys -import matplotlib.pyplot as plt import numpy as np import openmc @@ -184,10 +183,14 @@ class Universe(IDManagerMixin): return [] def plot(self, origin=(0., 0., 0.), width=(1., 1.), pixels=(200, 200), - basis='xy', color_by='cell', colors=None, filename=None, seed=None, + basis='xy', color_by='cell', colors=None, seed=None, **kwargs): """Display a slice plot of the universe. + To display or save the plot, call :func:`matplotlib.pyplot.show` or + :func:`matplotlib.pyplot.savefig`. In a Jupyter notebook, enabling the + matplotlib inline backend will show the plot inline. + Parameters ---------- origin : Iterable of float @@ -212,9 +215,6 @@ class Universe(IDManagerMixin): water = openmc.Cell(fill=h2o) universe.plot(..., colors={water: (0., 0., 1.)) - filename : str or None - Filename to save plot to. If no filename is given, the plot will be - displayed using the currently enabled matplotlib backend. seed : hashable object or None Hashable object which is used to seed the random number generator used to select colors. If None, the generator is seeded from the @@ -223,7 +223,14 @@ class Universe(IDManagerMixin): All keyword arguments are passed to :func:`matplotlib.pyplot.imshow`. + Returns + ------- + matplotlib.image.AxesImage + Resulting image + """ + import matplotlib.pyplot as plt + # Seed the random number generator if seed is not None: random.seed(seed) @@ -298,14 +305,8 @@ class Universe(IDManagerMixin): img[j, i, :] = colors[obj] # Display image - plt.imshow(img, extent=(x_min, x_max, y_min, y_max), - interpolation='nearest', **kwargs) - - # Show or save the plot - if filename is None: - plt.show() - else: - plt.savefig(filename) + return plt.imshow(img, extent=(x_min, x_max, y_min, y_max), + interpolation='nearest', **kwargs) def add_cell(self, cell): """Add a cell to the universe. From 23324fe24b62b0f92e609a4e78c40847277ed3d8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Mar 2018 16:18:51 -0600 Subject: [PATCH 108/361] Fix broken tests --- tests/regression_tests/asymmetric_lattice/inputs_true.dat | 2 +- .../create_fission_neutrons/inputs_true.dat | 2 +- tests/regression_tests/diff_tally/inputs_true.dat | 2 +- tests/regression_tests/distribmat/inputs_true.dat | 4 ++-- tests/regression_tests/filter_energyfun/inputs_true.dat | 4 ++-- tests/regression_tests/filter_mesh/inputs_true.dat | 2 +- tests/regression_tests/fixed_source/inputs_true.dat | 2 +- tests/regression_tests/iso_in_lab/inputs_true.dat | 2 +- .../mgxs_library_ce_to_mg/inputs_true.dat | 2 +- .../mgxs_library_condense/inputs_true.dat | 2 +- .../mgxs_library_distribcell/inputs_true.dat | 2 +- tests/regression_tests/mgxs_library_hdf5/inputs_true.dat | 2 +- tests/regression_tests/mgxs_library_mesh/inputs_true.dat | 2 +- .../mgxs_library_no_nuclides/inputs_true.dat | 2 +- .../mgxs_library_nuclides/inputs_true.dat | 2 +- tests/regression_tests/multipole/inputs_true.dat | 2 +- tests/regression_tests/periodic/inputs_true.dat | 2 +- .../regression_tests/resonance_scattering/inputs_true.dat | 2 +- tests/regression_tests/salphabeta/inputs_true.dat | 8 ++++---- tests/regression_tests/source/inputs_true.dat | 2 +- tests/regression_tests/surface_tally/inputs_true.dat | 2 +- tests/regression_tests/tallies/inputs_true.dat | 2 +- tests/regression_tests/tally_aggregation/inputs_true.dat | 2 +- tests/regression_tests/tally_arithmetic/inputs_true.dat | 2 +- tests/regression_tests/tally_slice_merge/inputs_true.dat | 2 +- tests/regression_tests/triso/inputs_true.dat | 2 +- tests/regression_tests/volume_calc/inputs_true.dat | 2 +- tests/unit_tests/test_universe.py | 1 - 28 files changed, 32 insertions(+), 33 deletions(-) diff --git a/tests/regression_tests/asymmetric_lattice/inputs_true.dat b/tests/regression_tests/asymmetric_lattice/inputs_true.dat index 672f6bee1..bbdc79715 100644 --- a/tests/regression_tests/asymmetric_lattice/inputs_true.dat +++ b/tests/regression_tests/asymmetric_lattice/inputs_true.dat @@ -56,7 +56,7 @@ - + diff --git a/tests/regression_tests/create_fission_neutrons/inputs_true.dat b/tests/regression_tests/create_fission_neutrons/inputs_true.dat index 9aeabbb57..b0ca89647 100644 --- a/tests/regression_tests/create_fission_neutrons/inputs_true.dat +++ b/tests/regression_tests/create_fission_neutrons/inputs_true.dat @@ -10,7 +10,7 @@ - + diff --git a/tests/regression_tests/diff_tally/inputs_true.dat b/tests/regression_tests/diff_tally/inputs_true.dat index ff134bd5c..909475f96 100644 --- a/tests/regression_tests/diff_tally/inputs_true.dat +++ b/tests/regression_tests/diff_tally/inputs_true.dat @@ -148,7 +148,7 @@ - + diff --git a/tests/regression_tests/distribmat/inputs_true.dat b/tests/regression_tests/distribmat/inputs_true.dat index 85d35b081..39e611e16 100644 --- a/tests/regression_tests/distribmat/inputs_true.dat +++ b/tests/regression_tests/distribmat/inputs_true.dat @@ -26,11 +26,11 @@ - + - + diff --git a/tests/regression_tests/filter_energyfun/inputs_true.dat b/tests/regression_tests/filter_energyfun/inputs_true.dat index d857451cb..418354fc2 100644 --- a/tests/regression_tests/filter_energyfun/inputs_true.dat +++ b/tests/regression_tests/filter_energyfun/inputs_true.dat @@ -148,7 +148,7 @@ - + @@ -156,7 +156,7 @@ - + diff --git a/tests/regression_tests/filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat index 6d14f9e7e..ca063b7ce 100644 --- a/tests/regression_tests/filter_mesh/inputs_true.dat +++ b/tests/regression_tests/filter_mesh/inputs_true.dat @@ -148,7 +148,7 @@ - + diff --git a/tests/regression_tests/fixed_source/inputs_true.dat b/tests/regression_tests/fixed_source/inputs_true.dat index 2e0d57b0c..f1aebb3b2 100644 --- a/tests/regression_tests/fixed_source/inputs_true.dat +++ b/tests/regression_tests/fixed_source/inputs_true.dat @@ -5,7 +5,7 @@ - + diff --git a/tests/regression_tests/iso_in_lab/inputs_true.dat b/tests/regression_tests/iso_in_lab/inputs_true.dat index 9e30f245f..2a302ada6 100644 --- a/tests/regression_tests/iso_in_lab/inputs_true.dat +++ b/tests/regression_tests/iso_in_lab/inputs_true.dat @@ -148,7 +148,7 @@ - + diff --git a/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat b/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat index 8e8cde281..70996fe37 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat @@ -12,7 +12,7 @@ - + diff --git a/tests/regression_tests/mgxs_library_condense/inputs_true.dat b/tests/regression_tests/mgxs_library_condense/inputs_true.dat index d2f28d0fe..ef6c4c520 100644 --- a/tests/regression_tests/mgxs_library_condense/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_condense/inputs_true.dat @@ -12,7 +12,7 @@ - + diff --git a/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat b/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat index d7a4a186a..ba7dc05ff 100644 --- a/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat @@ -39,7 +39,7 @@ - + diff --git a/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat b/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat index d2f28d0fe..ef6c4c520 100644 --- a/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat @@ -12,7 +12,7 @@ - + diff --git a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat index 4756b27bf..aa2c904b1 100644 --- a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat @@ -148,7 +148,7 @@ - + diff --git a/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat b/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat index d2f28d0fe..ef6c4c520 100644 --- a/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat @@ -12,7 +12,7 @@ - + diff --git a/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat b/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat index 826eb5b62..b720bfcbb 100644 --- a/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat @@ -12,7 +12,7 @@ - + diff --git a/tests/regression_tests/multipole/inputs_true.dat b/tests/regression_tests/multipole/inputs_true.dat index 0d1fe99bd..d1ef3e00a 100644 --- a/tests/regression_tests/multipole/inputs_true.dat +++ b/tests/regression_tests/multipole/inputs_true.dat @@ -27,7 +27,7 @@ - + diff --git a/tests/regression_tests/periodic/inputs_true.dat b/tests/regression_tests/periodic/inputs_true.dat index 61958701b..6f613a160 100644 --- a/tests/regression_tests/periodic/inputs_true.dat +++ b/tests/regression_tests/periodic/inputs_true.dat @@ -18,7 +18,7 @@ - + diff --git a/tests/regression_tests/resonance_scattering/inputs_true.dat b/tests/regression_tests/resonance_scattering/inputs_true.dat index 70fe165fd..2301ccf76 100644 --- a/tests/regression_tests/resonance_scattering/inputs_true.dat +++ b/tests/regression_tests/resonance_scattering/inputs_true.dat @@ -5,7 +5,7 @@ - + diff --git a/tests/regression_tests/salphabeta/inputs_true.dat b/tests/regression_tests/salphabeta/inputs_true.dat index 02e6813f0..ede96d376 100644 --- a/tests/regression_tests/salphabeta/inputs_true.dat +++ b/tests/regression_tests/salphabeta/inputs_true.dat @@ -12,19 +12,19 @@ - + - + - + @@ -32,7 +32,7 @@ - + diff --git a/tests/regression_tests/source/inputs_true.dat b/tests/regression_tests/source/inputs_true.dat index da230e0e5..6dd913edc 100644 --- a/tests/regression_tests/source/inputs_true.dat +++ b/tests/regression_tests/source/inputs_true.dat @@ -5,7 +5,7 @@ - + 294 diff --git a/tests/regression_tests/surface_tally/inputs_true.dat b/tests/regression_tests/surface_tally/inputs_true.dat index e66d44273..fc10110ee 100644 --- a/tests/regression_tests/surface_tally/inputs_true.dat +++ b/tests/regression_tests/surface_tally/inputs_true.dat @@ -11,7 +11,7 @@ - + diff --git a/tests/regression_tests/tallies/inputs_true.dat b/tests/regression_tests/tallies/inputs_true.dat index a85491e94..2c33a8fa0 100644 --- a/tests/regression_tests/tallies/inputs_true.dat +++ b/tests/regression_tests/tallies/inputs_true.dat @@ -148,7 +148,7 @@ - + diff --git a/tests/regression_tests/tally_aggregation/inputs_true.dat b/tests/regression_tests/tally_aggregation/inputs_true.dat index 7a8bf4613..86806572a 100644 --- a/tests/regression_tests/tally_aggregation/inputs_true.dat +++ b/tests/regression_tests/tally_aggregation/inputs_true.dat @@ -148,7 +148,7 @@ - + diff --git a/tests/regression_tests/tally_arithmetic/inputs_true.dat b/tests/regression_tests/tally_arithmetic/inputs_true.dat index 743ca3c58..3605005ad 100644 --- a/tests/regression_tests/tally_arithmetic/inputs_true.dat +++ b/tests/regression_tests/tally_arithmetic/inputs_true.dat @@ -148,7 +148,7 @@ - + diff --git a/tests/regression_tests/tally_slice_merge/inputs_true.dat b/tests/regression_tests/tally_slice_merge/inputs_true.dat index b1c089c96..ccd3655f9 100644 --- a/tests/regression_tests/tally_slice_merge/inputs_true.dat +++ b/tests/regression_tests/tally_slice_merge/inputs_true.dat @@ -148,7 +148,7 @@ - + diff --git a/tests/regression_tests/triso/inputs_true.dat b/tests/regression_tests/triso/inputs_true.dat index fdbc1cb5f..6d674b450 100644 --- a/tests/regression_tests/triso/inputs_true.dat +++ b/tests/regression_tests/triso/inputs_true.dat @@ -393,7 +393,7 @@ - + diff --git a/tests/regression_tests/volume_calc/inputs_true.dat b/tests/regression_tests/volume_calc/inputs_true.dat index 28f1cbd9f..607921af2 100644 --- a/tests/regression_tests/volume_calc/inputs_true.dat +++ b/tests/regression_tests/volume_calc/inputs_true.dat @@ -18,7 +18,7 @@ - + diff --git a/tests/unit_tests/test_universe.py b/tests/unit_tests/test_universe.py index 59e34e201..89904524b 100644 --- a/tests/unit_tests/test_universe.py +++ b/tests/unit_tests/test_universe.py @@ -59,7 +59,6 @@ def test_plot(run_in_tmpdir, sphere_model): pixels=(10, 10), color_by='material', colors=colors, - filename='test.png' ) From 8487915f2a66e2875b9970e65f77be45b32982aa Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 2 Mar 2018 16:19:10 -0600 Subject: [PATCH 109/361] Use ufloat for k_combined in statepoint --- openmc/mgxs/library.py | 2 +- openmc/model/model.py | 2 +- openmc/search.py | 4 ++-- openmc/statepoint.py | 17 ++++++++++------- tests/regression_tests/entropy/test.py | 4 ++-- tests/regression_tests/mg_convert/test.py | 4 ++-- tests/testing_harness.py | 2 +- 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index 9add7004b..ed8e3f076 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -587,7 +587,7 @@ class Library(object): self._nuclides = statepoint.summary.nuclides if statepoint.run_mode == 'eigenvalue': - self._keff = statepoint.k_combined[0] + self._keff = statepoint.k_combined.n # Load tallies for each MGXS for each domain and mgxs type for domain in self.domains: diff --git a/openmc/model/model.py b/openmc/model/model.py index cd5153321..7a81292c1 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -210,7 +210,7 @@ class Model(object): Returns ------- - 2-tuple of float + uncertainties.UFloat Combined estimator of k-effective from the statepoint """ diff --git a/openmc/search.py b/openmc/search.py index 75935097e..6be8a50ea 100644 --- a/openmc/search.py +++ b/openmc/search.py @@ -60,9 +60,9 @@ def _search_keff(guess, target, model_builder, model_args, print_iterations, if print_iterations: text = 'Iteration: {}; Guess of {:.2e} produced a keff of ' + \ '{:1.5f} +/- {:1.5f}' - print(text.format(len(guesses), guess, keff[0], keff[1])) + print(text.format(len(guesses), guess, keff.n, keff.s)) - return (keff[0] - target) + return keff.n - target def search_for_keff(model_builder, initial_guess=None, target=1.0, diff --git a/openmc/statepoint.py b/openmc/statepoint.py index 4656e0856..eb011d874 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -1,3 +1,4 @@ +from datetime import datetime import re import os import warnings @@ -5,6 +6,7 @@ import glob import numpy as np import h5py +from uncertainties import ufloat import openmc import openmc.checkvalue as cv @@ -47,8 +49,8 @@ class StatePoint(object): CMFD fission source distribution over all mesh cells and energy groups. current_batch : int Number of batches simulated - date_and_time : str - Date and time when simulation began + date_and_time : datetime.datetime + Date and time at which statepoint was written entropy : numpy.ndarray Shannon entropy of fission source at each batch filters : dict @@ -59,8 +61,8 @@ class StatePoint(object): global_tallies : numpy.ndarray of compound datatype Global tallies for k-effective estimates and leakage. The compound datatype has fields 'name', 'sum', 'sum_sq', 'mean', and 'std_dev'. - k_combined : list - Combined estimator for k-effective and its uncertainty + k_combined : uncertainties.UFloat + Combined estimator for k-effective k_col_abs : float Cross-product of collision and absorption estimates of k-effective k_col_tra : float @@ -187,7 +189,8 @@ class StatePoint(object): @property def date_and_time(self): - return self._f.attrs['date_and_time'].decode() + s = self._f.attrs['date_and_time'].decode() + return datetime.strptime(s, '%Y-%m-%d %H:%M:%S') @property def entropy(self): @@ -255,7 +258,7 @@ class StatePoint(object): @property def k_combined(self): if self.run_mode == 'eigenvalue': - return self._f['k_combined'].value + return ufloat(*self._f['k_combined'].value) else: return None @@ -457,7 +460,7 @@ class StatePoint(object): @property def version(self): - return tuple(self._f.attrs['version']) + return tuple(self._f.attrs['openmc_version']) @property def summary(self): diff --git a/tests/regression_tests/entropy/test.py b/tests/regression_tests/entropy/test.py index 0f1052b6b..10a11e300 100644 --- a/tests/regression_tests/entropy/test.py +++ b/tests/regression_tests/entropy/test.py @@ -14,11 +14,11 @@ class EntropyTestHarness(TestHarness): with StatePoint(statepoint) as sp: # Write out k-combined. outstr = 'k-combined:\n' - outstr += '{0:12.6E} {1:12.6E}\n'.format(*sp.k_combined) + outstr += '{:12.6E} {:12.6E}\n'.format(sp.k_combined.n, sp.k_combined.s) # Write out entropy data. outstr += 'entropy:\n' - results = ['{0:12.6E}'.format(x) for x in sp.entropy] + results = ['{:12.6E}'.format(x) for x in sp.entropy] outstr += '\n'.join(results) + '\n' return outstr diff --git a/tests/regression_tests/mg_convert/test.py b/tests/regression_tests/mg_convert/test.py index 5b816a1cd..1ace10c80 100755 --- a/tests/regression_tests/mg_convert/test.py +++ b/tests/regression_tests/mg_convert/test.py @@ -144,8 +144,8 @@ class MGXSTestHarness(PyAPITestHarness): 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]) + form = '{:12.6E} {:12.6E}\n' + outstr += form.format(sp.k_combined.n, sp.k_combined.s) return outstr diff --git a/tests/testing_harness.py b/tests/testing_harness.py index 92d477e23..fb07575ce 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -75,7 +75,7 @@ class TestHarness(object): # 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]) + outstr += form.format(sp.k_combined.n, sp.k_combined.s) # Write out tally data. for i, tally_ind in enumerate(sp.tallies): From ac28407aa2d54b96ff911f69fdb982f2f14728e0 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 3 Mar 2018 15:55:12 -0600 Subject: [PATCH 110/361] Don't have atomic_mass/atomic_weight return None for invalid --- openmc/data/data.py | 29 +++++++++++++++-------------- tests/unit_tests/test_data_misc.py | 13 +++++++++++++ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/openmc/data/data.py b/openmc/data/data.py index 3a625b622..90d5903e7 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -1,10 +1,9 @@ import itertools +from math import sqrt import os import re from warnings import warn -from numpy import sqrt - # Isotopic abundances from Meija J, Coplen T B, et al, "Isotopic compositions # of the elements 2013 (IUPAC Technical Report)", Pure. Appl. Chem. 88 (3), @@ -142,19 +141,18 @@ _GND_NAME_RE = re.compile(r'([A-Zn][a-z]*)(\d+)((?:_[em]\d+)?)') def atomic_mass(isotope): """Return atomic mass of isotope in atomic mass units. - Atomic mass data comes from the Atomic Mass Evaluation 2012, published in - Chinese Physics C 36 (2012), 1287--1602. + Atomic mass data comes from the `Atomic Mass Evaluation 2012 + `_. Parameters ---------- isotope : str - Name of isotope, e.g. 'Pu239' + Name of isotope, e.g., 'Pu239' Returns ------- - float or None - Atomic mass of isotope in atomic mass units. If the isotope listed does - not have a known atomic mass, None is returned. + float + Atomic mass of isotope in [amu] """ if not _ATOMIC_MASS: @@ -185,7 +183,7 @@ def atomic_mass(isotope): if '_' in isotope: isotope = isotope[:isotope.find('_')] - return _ATOMIC_MASS.get(isotope.lower()) + return _ATOMIC_MASS[isotope.lower()] def atomic_weight(element): @@ -201,16 +199,19 @@ def atomic_weight(element): Returns ------- - float or None - Atomic weight of element in atomic mass units. If the element listed does - not exist, None is returned. + float + Atomic weight of element in [amu] """ weight = 0. for nuclide, abundance in NATURAL_ABUNDANCE.items(): if re.match(r'{}\d+'.format(element), nuclide): weight += atomic_mass(nuclide) * abundance - return None if weight == 0. else weight + if weight > 0.: + return weight + else: + raise ValueError("No naturally-occurring isotopes for element '{}'." + .format(element)) def water_density(temperature, pressure=0.1013): @@ -236,7 +237,7 @@ def water_density(temperature, pressure=0.1013): Returns ------- float - Water density in units of [g / cm^3] + Water density in units of [g/cm^3] """ diff --git a/tests/unit_tests/test_data_misc.py b/tests/unit_tests/test_data_misc.py index 04ca0f103..34686b567 100644 --- a/tests/unit_tests/test_data_misc.py +++ b/tests/unit_tests/test_data_misc.py @@ -50,6 +50,19 @@ def test_thin(): assert f(1.0) == pytest.approx(np.sin(1.0), 0.001) +def test_atomic_mass(): + assert openmc.data.atomic_mass('H1') == 1.00782503223 + assert openmc.data.atomic_mass('U235') == 235.043930131 + with pytest.raises(KeyError): + openmc.data.atomic_mass('U100') + + +def test_atomic_weight(): + assert openmc.data.atomic_weight('C') == 12.011115164862904 + with pytest.raises(ValueError): + openmc.data.atomic_weight('Qt') + + def test_water_density(): dens = openmc.data.water_density # These test values are from IAPWS R7-97(2012). They are actually specific From 0efd0424204b0568ff6b48875b3618cd954a4dcd Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sat, 3 Mar 2018 16:27:45 -0600 Subject: [PATCH 111/361] Fix display of version number and copyright year --- src/output.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/output.F90 b/src/output.F90 index 1bf0c46d0..964289126 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -165,12 +165,12 @@ contains subroutine print_version() if (master) then - write(UNIT=OUTPUT_UNIT, FMT='(1X,A,1X,I1,".",I1,".",I1)') & + write(UNIT=OUTPUT_UNIT, FMT='(1X,A,1X,I1,".",I2,".",I1)') & "OpenMC version", VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE #ifdef GIT_SHA1 write(UNIT=OUTPUT_UNIT, FMT='(1X,A,A)') "Git SHA1: ", GIT_SHA1 #endif - write(UNIT=OUTPUT_UNIT, FMT=*) "Copyright (c) 2011-2015 & + write(UNIT=OUTPUT_UNIT, FMT=*) "Copyright (c) 2011-2018 & &Massachusetts Institute of Technology" write(UNIT=OUTPUT_UNIT, FMT=*) "MIT/X license at & &" From dca39f87e2730aba4152e7d51849b42916319ab0 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 5 Mar 2018 19:56:42 -0600 Subject: [PATCH 112/361] Use gnd_name in _get_metadata --- openmc/data/neutron.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 0aa1fe7d8..1cc0e3887 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -15,7 +15,7 @@ import h5py from . import HDF5_VERSION, HDF5_VERSION_MAJOR from .ace import Library, Table, get_table -from .data import ATOMIC_SYMBOL, K_BOLTZMANN, EV_PER_MEV +from .data import ATOMIC_SYMBOL, K_BOLTZMANN, EV_PER_MEV, gnd_name from .endf import Evaluation, SUM_RULES, get_head_record, get_tab1_record from .fission_energy import FissionEnergyRelease from .function import Tabulated1D, Sum, ResonancesWithBackground @@ -93,9 +93,7 @@ def _get_metadata(zaid, metastable_scheme='nndc'): # Determine name element = ATOMIC_SYMBOL[Z] - name = '{}{}'.format(element, mass_number) - if metastable > 0: - name += '_m{}'.format(metastable) + name = gnd_name(Z, mass_number, metastable) return (name, element, Z, mass_number, metastable) From 9904e64910eec94e63c071f1c5d8077be9b756e7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 7 Mar 2018 06:31:27 -0600 Subject: [PATCH 113/361] Add three papers to list of publications --- docs/source/publications.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/source/publications.rst b/docs/source/publications.rst index 7578fee6b..b88bdc0f0 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -57,6 +57,11 @@ Benchmarking Coupling and Multi-physics -------------------------- +- Jun Chen, Liangzhi Cao, Chuanqi Zhao, and Zhouyu Liu, "`Development of + Subchannel Code SUBSC for high-fidelity multi-physics coupling application + `_", Energy Procedia, **127**, + 264-274 (2017). + - Tianliang Hu, Liangzhu Cao, Hongchun Wu, Xianan Du, and Mingtao He, "`Coupled neutrons and thermal-hydraulics simulation of molten salt reactors based on OpenMC/TANSY `_," @@ -98,6 +103,11 @@ Coupling and Multi-physics Geometry and Visualization -------------------------- +- Jin-Yang Li, Long Gu, Hu-Shan Xu, Nadezha Korepanova, Rui Yu, Yan-Lei Zhu, and + Chang-Ping Qin, "`CAD modeling study on FLUKA and OpenMC for accelerator + driven system simulation `_", + *Ann. Nucl. Energy*, **114**, 329-341 (2018). + - Logan Abel, William Boyd, Benoit Forget, and Kord Smith, "Interactive Visualization of Multi-Group Cross Sections on High-Fidelity Spatial Meshes," *Trans. Am. Nucl. Soc.*, **114**, 391-394 (2016). @@ -114,6 +124,11 @@ Geometry and Visualization Miscellaneous ------------- +- Bruno Merk, Dzianis Litskevich, R. Gregg, and A. R. Mount, "`Demand driven + salt clean-up in a molten salt fast reactor -- Defining a priority list + `_", *PLOS One*, **13**, + e0192020 (2018). + - Adam G. Nelson, Samuel Shaner, William Boyd, and Paul K. Romano, "Incorporation of a Multigroup Transport Capability in the OpenMC Monte Carlo Particle Transport Code," *Trans. Am. Nucl. Soc.*, **117**, 679-681 (2017). From 9e912a933dcadab2c240d928dd685088548d3193 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Mar 2018 10:41:43 -0500 Subject: [PATCH 114/361] Support energyout filters in C API --- openmc/capi/filter.py | 2 +- src/tallies/tally_filter_energy.F90 | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index 79c19a624..5a5df4814 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -128,7 +128,7 @@ class EnergyFilter(Filter): self._index, len(energies), energies_p) -class EnergyoutFilter(Filter): +class EnergyoutFilter(EnergyFilter): filter_type = 'energyout' diff --git a/src/tallies/tally_filter_energy.F90 b/src/tallies/tally_filter_energy.F90 index 2a247cf35..caba6755d 100644 --- a/src/tallies/tally_filter_energy.F90 +++ b/src/tallies/tally_filter_energy.F90 @@ -211,6 +211,10 @@ contains energies = C_LOC(f % bins) n = size(f % bins) err = 0 + type is (EnergyoutFilter) + energies = C_LOC(f % bins) + n = size(f % bins) + err = 0 class default err = E_INVALID_TYPE call set_errmsg("Tried to get energy bins on a non-energy filter.") @@ -242,6 +246,11 @@ contains if (allocated(f % bins)) deallocate(f % bins) allocate(f % bins(n)) f % bins(:) = energies + type is (EnergyoutFilter) + f % n_bins = n - 1 + if (allocated(f % bins)) deallocate(f % bins) + allocate(f % bins(n)) + f % bins(:) = energies class default err = E_INVALID_TYPE call set_errmsg("Tried to get energy bins on a non-energy filter.") From bd5b7afc2d6e64bf03fb9e1400f6778baf3bbe81 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 16 Mar 2018 13:47:01 -0500 Subject: [PATCH 115/361] Fix bug related to bin ordering for mesh filters --- openmc/filter.py | 50 ++-- openmc/mesh.py | 18 ++ openmc/mgxs/mgxs.py | 16 +- openmc/tallies.py | 4 +- .../mgxs_library_mesh/results_true.dat | 256 +++++++++--------- .../tally_slice_merge/results_true.dat | 34 +-- .../tally_slice_merge/test.py | 8 +- 7 files changed, 200 insertions(+), 186 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index 8763515f4..6cb81d97a 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -732,37 +732,40 @@ class MeshFilter(Filter): # Filter bins for a mesh are an (x,y,z) tuple. Convert (x,y,z) to a # single bin -- this is similar to subroutine mesh_indices_to_bin in # openmc/src/mesh.F90. - if len(self.mesh.dimension) == 3: + n_dim = len(self.mesh.dimension) + if n_dim == 3: + i, j, k = filter_bin nx, ny, nz = self.mesh.dimension - val = (filter_bin[0] - 1) * ny * nz + \ - (filter_bin[1] - 1) * nz + \ - (filter_bin[2] - 1) - else: + return (i - 1) + (j - 1)*nx + (k - 1)*nx*ny + elif n_dim == 2: + i, j, *_ = filter_bin nx, ny = self.mesh.dimension - val = (filter_bin[0] - 1) * ny + \ - (filter_bin[1] - 1) - - return val + return (i - 1) + (j - 1)*nx + else: + return filter_bin[0] - 1 def get_bin(self, bin_index): cv.check_type('bin_index', bin_index, Integral) cv.check_greater_than('bin_index', bin_index, 0, equality=True) cv.check_less_than('bin_index', bin_index, self.num_bins) - # Construct 3-tuple of x,y,z cell indices for a 3D mesh - if len(self.mesh.dimension) == 3: + n_dim = len(self.mesh.dimension) + if n_dim == 3: + # Construct 3-tuple of x,y,z cell indices for a 3D mesh nx, ny, nz = self.mesh.dimension - x = bin_index / (ny * nz) - y = (bin_index - (x * ny * nz)) / nz - z = bin_index - (x * ny * nz) - (y * nz) + x = bin_index % nx + 1 + y = (bin_index % (nx * ny)) // nx + 1 + z = bin_index // (nx * ny) + 1 return (x, y, z) - # Construct 2-tuple of x,y cell indices for a 2D mesh - else: + elif n_dim == 2: + # Construct 2-tuple of x,y cell indices for a 2D mesh nx, ny = self.mesh.dimension - x = bin_index / ny - y = bin_index - (x * ny) + x = bin_index % nx + 1 + y = bin_index // nx + 1 return (x, y) + else: + return (bin_index + 1,) def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. @@ -802,9 +805,10 @@ class MeshFilter(Filter): mesh_key = 'mesh {0}'.format(self.mesh.id) # Find mesh dimensions - use 3D indices for simplicity - if len(self.mesh.dimension) == 3: + n_dim = len(self.mesh.dimension) + if n_dim == 3: nx, ny, nz = self.mesh.dimension - elif len(self.mesh.dimension) == 2: + elif n_dim == 2: nx, ny = self.mesh.dimension nz = 1 else: @@ -813,7 +817,7 @@ class MeshFilter(Filter): # Generate multi-index sub-column for x-axis filter_bins = np.arange(1, nx + 1) - repeat_factor = ny * nz * 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) @@ -821,7 +825,7 @@ class MeshFilter(Filter): # Generate multi-index sub-column for y-axis filter_bins = np.arange(1, ny + 1) - repeat_factor = nz * stride + repeat_factor = nx * stride filter_bins = np.repeat(filter_bins, repeat_factor) tile_factor = data_size // len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) @@ -829,7 +833,7 @@ class MeshFilter(Filter): # Generate multi-index sub-column for z-axis filter_bins = np.arange(1, nz + 1) - repeat_factor = stride + repeat_factor = nx * ny * stride filter_bins = np.repeat(filter_bins, repeat_factor) tile_factor = data_size // len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) diff --git a/openmc/mesh.py b/openmc/mesh.py index d937e25ce..cd8eb3c5e 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -82,6 +82,24 @@ class Mesh(IDManagerMixin): def num_mesh_cells(self): return np.prod(self._dimension) + @property + def indices(self): + ndim = len(self._dimension) + if ndim == 3: + nx, ny, nz = self.dimension + return ((x, y, z) + for z in range(1, nz + 1) + for y in range(1, ny + 1) + for x in range(1, nx + 1)) + elif ndim == 2: + nx, ny = self.dimension + return ((x, y) + for y in range(1, ny + 1) + for x in range(1, nx + 1)) + else: + nx, = self.dimension + return ((x,) for x in range(1, nx + 1)) + @name.setter def name(self, name): if name is not None: diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index ecedc8e63..4932feff1 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -4,7 +4,6 @@ import warnings import os import copy from abc import ABCMeta -import itertools import numpy as np import h5py @@ -937,8 +936,7 @@ class MGXS(metaclass=ABCMeta): # NOTE: This is important if tally merging was used if self.domain_type == 'mesh': filters = [_DOMAIN_TO_FILTER[self.domain_type]] - xyz = [range(1, x + 1) for x in self.domain.dimension] - filter_bins = [tuple(itertools.product(*xyz))] + filter_bins = [tuple(self.domain.indices)] elif self.domain_type != 'distribcell': filters = [_DOMAIN_TO_FILTER[self.domain_type]] filter_bins = [(self.domain.id,)] @@ -1531,8 +1529,7 @@ class MGXS(metaclass=ABCMeta): elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) elif self.domain_type == 'mesh': - xyz = [range(1, x + 1) for x in self.domain.dimension] - subdomains = list(itertools.product(*xyz)) + subdomains = list(self.domain.indices) else: subdomains = [self.domain.id] @@ -1702,8 +1699,7 @@ class MGXS(metaclass=ABCMeta): domain_filter = self.xs_tally.find_filter('sum(distribcell)') subdomains = domain_filter.bins elif self.domain_type == 'mesh': - xyz = [range(1, x+1) for x in self.domain.dimension] - subdomains = list(itertools.product(*xyz)) + subdomains = list(self.domain.indices) else: subdomains = [self.domain.id] @@ -2342,8 +2338,7 @@ class MatrixMGXS(MGXS): elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) elif self.domain_type == 'mesh': - xyz = [range(1, x + 1) for x in self.domain.dimension] - subdomains = list(itertools.product(*xyz)) + subdomains = list(self.domain.indices) else: subdomains = [self.domain.id] @@ -4530,8 +4525,7 @@ class ScatterMatrixXS(MatrixMGXS): elif self.domain_type == 'distribcell': subdomains = np.arange(self.num_subdomains, dtype=np.int) elif self.domain_type == 'mesh': - xyz = [range(1, x + 1) for x in self.domain.dimension] - subdomains = list(itertools.product(*xyz)) + subdomains = list(self.domain.indices) else: subdomains = [self.domain.id] diff --git a/openmc/tallies.py b/openmc/tallies.py index d22cfdcb4..34424acb0 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1164,9 +1164,7 @@ class Tally(IDManagerMixin): if not user_filter: # Create list of 2- or 3-tuples tuples for mesh cell bins if isinstance(self_filter, openmc.MeshFilter): - dimension = self_filter.mesh.dimension - xyz = [range(1, x+1) for x in dimension] - bins = list(product(*xyz)) + bins = list(self_filter.mesh.indices) # Create list of 2-tuples for energy boundary bins elif isinstance(self_filter, (openmc.EnergyFilter, diff --git a/tests/regression_tests/mgxs_library_mesh/results_true.dat b/tests/regression_tests/mgxs_library_mesh/results_true.dat index c167628bc..4b9303fbf 100644 --- a/tests/regression_tests/mgxs_library_mesh/results_true.dat +++ b/tests/regression_tests/mgxs_library_mesh/results_true.dat @@ -1,62 +1,62 @@ mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 0.762544 0.085298 -1 1 2 1 1 total 0.653375 0.153317 -2 2 1 1 1 total 0.644837 0.088457 +2 1 2 1 1 total 0.644837 0.088457 +1 2 1 1 1 total 0.653375 0.153317 3 2 2 1 1 total 0.676480 0.094215 mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 0.473988 0.088732 -1 1 2 1 1 total 0.379821 0.167092 -2 2 1 1 1 total 0.399254 0.091318 +2 1 2 1 1 total 0.399254 0.091318 +1 2 1 1 1 total 0.379821 0.167092 3 2 2 1 1 total 0.424265 0.099551 mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 0.473988 0.088732 -1 1 2 1 1 total 0.379821 0.167092 -2 2 1 1 1 total 0.399254 0.091318 +2 1 2 1 1 total 0.399254 0.091318 +1 2 1 1 1 total 0.379821 0.167092 3 2 2 1 1 total 0.424265 0.099551 mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 0.027288 0.005813 -1 1 2 1 1 total 0.019449 0.004420 -2 2 1 1 1 total 0.020262 0.003701 +2 1 2 1 1 total 0.020262 0.003701 +1 2 1 1 1 total 0.019449 0.004420 3 2 2 1 1 total 0.021266 0.002869 mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 0.016037 0.006339 -1 1 2 1 1 total 0.012153 0.003804 -2 2 1 1 1 total 0.013018 0.003521 +2 1 2 1 1 total 0.013018 0.003521 +1 2 1 1 1 total 0.012153 0.003804 3 2 2 1 1 total 0.012965 0.002454 mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 0.011251 0.003050 -1 1 2 1 1 total 0.007296 0.001795 -2 2 1 1 1 total 0.007243 0.001219 +2 1 2 1 1 total 0.007243 0.001219 +1 2 1 1 1 total 0.007296 0.001795 3 2 2 1 1 total 0.008301 0.001066 mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 0.027498 0.007445 -1 1 2 1 1 total 0.017912 0.004426 -2 2 1 1 1 total 0.017954 0.003077 +2 1 2 1 1 total 0.017954 0.003077 +1 2 1 1 1 total 0.017912 0.004426 3 2 2 1 1 total 0.020469 0.002617 mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 2.177345e+06 589804.299388 -1 1 2 1 1 total 1.413154e+06 347806.623417 -2 2 1 1 1 total 1.404096e+06 236476.851953 +2 1 2 1 1 total 1.404096e+06 236476.851953 +1 2 1 1 1 total 1.413154e+06 347806.623417 3 2 2 1 1 total 1.608259e+06 206502.707865 mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 0.735256 0.080216 -1 1 2 1 1 total 0.633925 0.149098 -2 2 1 1 1 total 0.624575 0.084974 +2 1 2 1 1 total 0.624575 0.084974 +1 2 1 1 1 total 0.633925 0.149098 3 2 2 1 1 total 0.655214 0.091422 mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 0.763779 0.070696 -1 1 2 1 1 total 0.640809 0.158369 -2 2 1 1 1 total 0.628158 0.064356 +2 1 2 1 1 total 0.628158 0.064356 +1 2 1 1 1 total 0.640809 0.158369 3 2 2 1 1 total 0.645171 0.080467 mesh 1 group in group out nuclide moment mean std. dev. x y z @@ -64,14 +64,14 @@ 1 1 1 1 1 1 total P1 0.288556 0.024446 2 1 1 1 1 1 total P2 0.082441 0.011443 3 1 1 1 1 1 total P3 -0.005627 0.012638 -4 1 2 1 1 1 total P0 0.640809 0.158369 -5 1 2 1 1 1 total P1 0.273553 0.066437 -6 1 2 1 1 1 total P2 0.108446 0.024435 -7 1 2 1 1 1 total P3 0.012229 0.003785 -8 2 1 1 1 1 total P0 0.628158 0.064356 -9 2 1 1 1 1 total P1 0.245583 0.022676 -10 2 1 1 1 1 total P2 0.086370 0.007833 -11 2 1 1 1 1 total P3 0.019590 0.005345 +8 1 2 1 1 1 total P0 0.628158 0.064356 +9 1 2 1 1 1 total P1 0.245583 0.022676 +10 1 2 1 1 1 total P2 0.086370 0.007833 +11 1 2 1 1 1 total P3 0.019590 0.005345 +4 2 1 1 1 1 total P0 0.640809 0.158369 +5 2 1 1 1 1 total P1 0.273553 0.066437 +6 2 1 1 1 1 total P2 0.108446 0.024435 +7 2 1 1 1 1 total P3 0.012229 0.003785 12 2 2 1 1 1 total P0 0.645171 0.080467 13 2 2 1 1 1 total P1 0.252215 0.032154 14 2 2 1 1 1 total P2 0.089251 0.009734 @@ -82,14 +82,14 @@ 1 1 1 1 1 1 total P1 0.288556 0.024446 2 1 1 1 1 1 total P2 0.082441 0.011443 3 1 1 1 1 1 total P3 -0.005627 0.012638 -4 1 2 1 1 1 total P0 0.640809 0.158369 -5 1 2 1 1 1 total P1 0.273553 0.066437 -6 1 2 1 1 1 total P2 0.108446 0.024435 -7 1 2 1 1 1 total P3 0.012229 0.003785 -8 2 1 1 1 1 total P0 0.628158 0.064356 -9 2 1 1 1 1 total P1 0.245583 0.022676 -10 2 1 1 1 1 total P2 0.086370 0.007833 -11 2 1 1 1 1 total P3 0.019590 0.005345 +8 1 2 1 1 1 total P0 0.628158 0.064356 +9 1 2 1 1 1 total P1 0.245583 0.022676 +10 1 2 1 1 1 total P2 0.086370 0.007833 +11 1 2 1 1 1 total P3 0.019590 0.005345 +4 2 1 1 1 1 total P0 0.640809 0.158369 +5 2 1 1 1 1 total P1 0.273553 0.066437 +6 2 1 1 1 1 total P2 0.108446 0.024435 +7 2 1 1 1 1 total P3 0.012229 0.003785 12 2 2 1 1 1 total P0 0.645171 0.080467 13 2 2 1 1 1 total P1 0.252215 0.032154 14 2 2 1 1 1 total P2 0.089251 0.009734 @@ -97,20 +97,20 @@ mesh 1 group in group out nuclide mean std. dev. x y z 0 1 1 1 1 1 total 1.0 0.108337 -1 1 2 1 1 1 total 1.0 0.238517 -2 2 1 1 1 1 total 1.0 0.113128 +2 1 2 1 1 1 total 1.0 0.113128 +1 2 1 1 1 1 total 1.0 0.238517 3 2 2 1 1 1 total 1.0 0.132597 mesh 1 group in group out nuclide mean std. dev. x y z 0 1 1 1 1 1 total 0.015584 0.003404 -1 1 2 1 1 1 total 0.014200 0.003676 -2 2 1 1 1 1 total 0.017684 0.002499 +2 1 2 1 1 1 total 0.017684 0.002499 +1 2 1 1 1 1 total 0.014200 0.003676 3 2 2 1 1 1 total 0.022409 0.002481 mesh 1 group in group out nuclide mean std. dev. x y z 0 1 1 1 1 1 total 1.0 0.108337 -1 1 2 1 1 1 total 1.0 0.238517 -2 2 1 1 1 1 total 1.0 0.113128 +2 1 2 1 1 1 total 1.0 0.113128 +1 2 1 1 1 1 total 1.0 0.238517 3 2 2 1 1 1 total 1.0 0.132597 mesh 1 group in group out nuclide moment mean std. dev. x y z @@ -118,14 +118,14 @@ 1 1 1 1 1 1 total P1 0.277780 0.041434 2 1 1 1 1 1 total P2 0.079362 0.014706 3 1 1 1 1 1 total P3 -0.005417 0.012184 -4 1 2 1 1 1 total P0 0.633925 0.212349 -5 1 2 1 1 1 total P1 0.270615 0.089799 -6 1 2 1 1 1 total P2 0.107281 0.034246 -7 1 2 1 1 1 total P3 0.012098 0.004637 -8 2 1 1 1 1 total P0 0.624575 0.110512 -9 2 1 1 1 1 total P1 0.244182 0.041824 -10 2 1 1 1 1 total P2 0.085877 0.014634 -11 2 1 1 1 1 total P3 0.019478 0.006012 +8 1 2 1 1 1 total P0 0.624575 0.110512 +9 1 2 1 1 1 total P1 0.244182 0.041824 +10 1 2 1 1 1 total P2 0.085877 0.014634 +11 1 2 1 1 1 total P3 0.019478 0.006012 +4 2 1 1 1 1 total P0 0.633925 0.212349 +5 2 1 1 1 1 total P1 0.270615 0.089799 +6 2 1 1 1 1 total P2 0.107281 0.034246 +7 2 1 1 1 1 total P3 0.012098 0.004637 12 2 2 1 1 1 total P0 0.655214 0.126119 13 2 2 1 1 1 total P1 0.256141 0.049765 14 2 2 1 1 1 total P2 0.090641 0.016563 @@ -136,14 +136,14 @@ 1 1 1 1 1 1 total P1 0.277780 0.051210 2 1 1 1 1 1 total P2 0.079362 0.017035 3 1 1 1 1 1 total P3 -0.005417 0.012198 -4 1 2 1 1 1 total P0 0.633925 0.260681 -5 1 2 1 1 1 total P1 0.270615 0.110590 -6 1 2 1 1 1 total P2 0.107281 0.042750 -7 1 2 1 1 1 total P3 0.012098 0.005462 -8 2 1 1 1 1 total P0 0.624575 0.131169 -9 2 1 1 1 1 total P1 0.244182 0.050123 -10 2 1 1 1 1 total P2 0.085877 0.017565 -11 2 1 1 1 1 total P3 0.019478 0.006403 +8 1 2 1 1 1 total P0 0.624575 0.131169 +9 1 2 1 1 1 total P1 0.244182 0.050123 +10 1 2 1 1 1 total P2 0.085877 0.017565 +11 1 2 1 1 1 total P3 0.019478 0.006403 +4 2 1 1 1 1 total P0 0.633925 0.260681 +5 2 1 1 1 1 total P1 0.270615 0.110590 +6 2 1 1 1 1 total P2 0.107281 0.042750 +7 2 1 1 1 1 total P3 0.012098 0.005462 12 2 2 1 1 1 total P0 0.655214 0.153147 13 2 2 1 1 1 total P1 0.256141 0.060250 14 2 2 1 1 1 total P2 0.090641 0.020464 @@ -151,32 +151,32 @@ mesh 1 group out nuclide mean std. dev. x y z 0 1 1 1 1 total 1.0 0.300047 -1 1 2 1 1 total 1.0 0.262180 -2 2 1 1 1 total 1.0 0.178169 +2 1 2 1 1 total 1.0 0.178169 +1 2 1 1 1 total 1.0 0.262180 3 2 2 1 1 total 1.0 0.104797 mesh 1 group out nuclide mean std. dev. x y z 0 1 1 1 1 total 1.0 0.300047 -1 1 2 1 1 total 1.0 0.262180 -2 2 1 1 1 total 1.0 0.178169 +2 1 2 1 1 total 1.0 0.178169 +1 2 1 1 1 total 1.0 0.262180 3 2 2 1 1 total 1.0 0.108931 mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 7.097008e-07 1.458546e-07 -1 1 2 1 1 total 3.984535e-07 1.157576e-07 -2 2 1 1 1 total 4.407745e-07 7.903907e-08 +2 1 2 1 1 total 4.407745e-07 7.903907e-08 +1 2 1 1 1 total 3.984535e-07 1.157576e-07 3 2 2 1 1 total 4.750476e-07 6.207437e-08 mesh 1 group in nuclide mean std. dev. x y z 0 1 1 1 1 total 0.027311 0.007397 -1 1 2 1 1 total 0.017783 0.004394 -2 2 1 1 1 total 0.017820 0.003054 +2 1 2 1 1 total 0.017820 0.003054 +1 2 1 1 1 total 0.017783 0.004394 3 2 2 1 1 total 0.020320 0.002598 mesh 1 group in group out nuclide mean std. dev. x y z 0 1 1 1 1 1 total 0.015584 0.003404 -1 1 2 1 1 1 total 0.014200 0.003676 -2 2 1 1 1 1 total 0.017684 0.002499 +2 1 2 1 1 1 total 0.017684 0.002499 +1 2 1 1 1 1 total 0.014200 0.003676 3 2 2 1 1 1 total 0.022259 0.002508 mesh 1 delayedgroup group in nuclide mean std. dev. x y z @@ -186,18 +186,18 @@ 3 1 1 1 4 1 total 0.000072 1.866015e-05 4 1 1 1 5 1 total 0.000031 7.654909e-06 5 1 1 1 6 1 total 0.000013 3.206343e-06 -6 1 2 1 1 1 total 0.000004 1.003100e-06 -7 1 2 1 2 1 total 0.000022 5.425275e-06 -8 1 2 1 3 1 total 0.000021 5.324236e-06 -9 1 2 1 4 1 total 0.000050 1.251572e-05 -10 1 2 1 5 1 total 0.000022 5.762184e-06 -11 1 2 1 6 1 total 0.000009 2.391676e-06 -12 2 1 1 1 1 total 0.000004 6.723192e-07 -13 2 1 1 2 1 total 0.000022 3.706235e-06 -14 2 1 1 3 1 total 0.000022 3.674263e-06 -15 2 1 1 4 1 total 0.000052 8.774048e-06 -16 2 1 1 5 1 total 0.000024 4.168024e-06 -17 2 1 1 6 1 total 0.000010 1.726268e-06 +12 1 2 1 1 1 total 0.000004 6.723192e-07 +13 1 2 1 2 1 total 0.000022 3.706235e-06 +14 1 2 1 3 1 total 0.000022 3.674263e-06 +15 1 2 1 4 1 total 0.000052 8.774048e-06 +16 1 2 1 5 1 total 0.000024 4.168024e-06 +17 1 2 1 6 1 total 0.000010 1.726268e-06 +6 2 1 1 1 1 total 0.000004 1.003100e-06 +7 2 1 1 2 1 total 0.000022 5.425275e-06 +8 2 1 1 3 1 total 0.000021 5.324236e-06 +9 2 1 1 4 1 total 0.000050 1.251572e-05 +10 2 1 1 5 1 total 0.000022 5.762184e-06 +11 2 1 1 6 1 total 0.000009 2.391676e-06 18 2 2 1 1 1 total 0.000005 5.962367e-07 19 2 2 1 2 1 total 0.000025 3.200900e-06 20 2 2 1 3 1 total 0.000025 3.127442e-06 @@ -212,18 +212,18 @@ 3 1 1 1 4 1 total 0.0 0.000000 4 1 1 1 5 1 total 0.0 0.000000 5 1 1 1 6 1 total 0.0 0.000000 -6 1 2 1 1 1 total 0.0 0.000000 -7 1 2 1 2 1 total 0.0 0.000000 -8 1 2 1 3 1 total 0.0 0.000000 -9 1 2 1 4 1 total 0.0 0.000000 -10 1 2 1 5 1 total 0.0 0.000000 -11 1 2 1 6 1 total 0.0 0.000000 -12 2 1 1 1 1 total 0.0 0.000000 -13 2 1 1 2 1 total 0.0 0.000000 -14 2 1 1 3 1 total 0.0 0.000000 -15 2 1 1 4 1 total 0.0 0.000000 -16 2 1 1 5 1 total 0.0 0.000000 -17 2 1 1 6 1 total 0.0 0.000000 +12 1 2 1 1 1 total 0.0 0.000000 +13 1 2 1 2 1 total 0.0 0.000000 +14 1 2 1 3 1 total 0.0 0.000000 +15 1 2 1 4 1 total 0.0 0.000000 +16 1 2 1 5 1 total 0.0 0.000000 +17 1 2 1 6 1 total 0.0 0.000000 +6 2 1 1 1 1 total 0.0 0.000000 +7 2 1 1 2 1 total 0.0 0.000000 +8 2 1 1 3 1 total 0.0 0.000000 +9 2 1 1 4 1 total 0.0 0.000000 +10 2 1 1 5 1 total 0.0 0.000000 +11 2 1 1 6 1 total 0.0 0.000000 18 2 2 1 1 1 total 0.0 0.000000 19 2 2 1 2 1 total 0.0 0.000000 20 2 2 1 3 1 total 1.0 1.414214 @@ -238,18 +238,18 @@ 3 1 1 1 4 1 total 0.002629 0.000950 4 1 1 1 5 1 total 0.001125 0.000398 5 1 1 1 6 1 total 0.000470 0.000166 -6 1 2 1 1 1 total 0.000228 0.000057 -7 1 2 1 2 1 total 0.001222 0.000309 -8 1 2 1 3 1 total 0.001193 0.000304 -9 1 2 1 4 1 total 0.002780 0.000713 -10 1 2 1 5 1 total 0.001250 0.000328 -11 1 2 1 6 1 total 0.000520 0.000136 -12 2 1 1 1 1 total 0.000225 0.000044 -13 2 1 1 2 1 total 0.001232 0.000242 -14 2 1 1 3 1 total 0.001216 0.000239 -15 2 1 1 4 1 total 0.002882 0.000570 -16 2 1 1 5 1 total 0.001345 0.000270 -17 2 1 1 6 1 total 0.000558 0.000112 +12 1 2 1 1 1 total 0.000225 0.000044 +13 1 2 1 2 1 total 0.001232 0.000242 +14 1 2 1 3 1 total 0.001216 0.000239 +15 1 2 1 4 1 total 0.002882 0.000570 +16 1 2 1 5 1 total 0.001345 0.000270 +17 1 2 1 6 1 total 0.000558 0.000112 +6 2 1 1 1 1 total 0.000228 0.000057 +7 2 1 1 2 1 total 0.001222 0.000309 +8 2 1 1 3 1 total 0.001193 0.000304 +9 2 1 1 4 1 total 0.002780 0.000713 +10 2 1 1 5 1 total 0.001250 0.000328 +11 2 1 1 6 1 total 0.000520 0.000136 18 2 2 1 1 1 total 0.000227 0.000027 19 2 2 1 2 1 total 0.001225 0.000143 20 2 2 1 3 1 total 0.001201 0.000140 @@ -264,18 +264,18 @@ 3 1 1 1 4 1 total 0.304289 0.106753 4 1 1 1 5 1 total 0.855760 0.286466 5 1 1 1 6 1 total 2.874120 0.965609 -6 1 2 1 1 1 total 0.013357 0.003345 -7 1 2 1 2 1 total 0.032590 0.008273 -8 1 2 1 3 1 total 0.121103 0.031074 -9 1 2 1 4 1 total 0.306111 0.080011 -10 1 2 1 5 1 total 0.862660 0.235694 -11 1 2 1 6 1 total 2.897534 0.788926 -12 2 1 1 1 1 total 0.013367 0.002548 -13 2 1 1 2 1 total 0.032520 0.006266 -14 2 1 1 3 1 total 0.121250 0.023544 -15 2 1 1 4 1 total 0.307552 0.060464 -16 2 1 1 5 1 total 0.867665 0.175131 -17 2 1 1 6 1 total 2.914635 0.587161 +12 1 2 1 1 1 total 0.013367 0.002548 +13 1 2 1 2 1 total 0.032520 0.006266 +14 1 2 1 3 1 total 0.121250 0.023544 +15 1 2 1 4 1 total 0.307552 0.060464 +16 1 2 1 5 1 total 0.867665 0.175131 +17 1 2 1 6 1 total 2.914635 0.587161 +6 2 1 1 1 1 total 0.013357 0.003345 +7 2 1 1 2 1 total 0.032590 0.008273 +8 2 1 1 3 1 total 0.121103 0.031074 +9 2 1 1 4 1 total 0.306111 0.080011 +10 2 1 1 5 1 total 0.862660 0.235694 +11 2 1 1 6 1 total 2.897534 0.788926 18 2 2 1 1 1 total 0.013360 0.001587 19 2 2 1 2 1 total 0.032564 0.003810 20 2 2 1 3 1 total 0.121158 0.014038 @@ -290,18 +290,18 @@ 3 1 1 1 4 1 1 total 0.00000 0.000000 4 1 1 1 5 1 1 total 0.00000 0.000000 5 1 1 1 6 1 1 total 0.00000 0.000000 -6 1 2 1 1 1 1 total 0.00000 0.000000 -7 1 2 1 2 1 1 total 0.00000 0.000000 -8 1 2 1 3 1 1 total 0.00000 0.000000 -9 1 2 1 4 1 1 total 0.00000 0.000000 -10 1 2 1 5 1 1 total 0.00000 0.000000 -11 1 2 1 6 1 1 total 0.00000 0.000000 -12 2 1 1 1 1 1 total 0.00000 0.000000 -13 2 1 1 2 1 1 total 0.00000 0.000000 -14 2 1 1 3 1 1 total 0.00000 0.000000 -15 2 1 1 4 1 1 total 0.00000 0.000000 -16 2 1 1 5 1 1 total 0.00000 0.000000 -17 2 1 1 6 1 1 total 0.00000 0.000000 +12 1 2 1 1 1 1 total 0.00000 0.000000 +13 1 2 1 2 1 1 total 0.00000 0.000000 +14 1 2 1 3 1 1 total 0.00000 0.000000 +15 1 2 1 4 1 1 total 0.00000 0.000000 +16 1 2 1 5 1 1 total 0.00000 0.000000 +17 1 2 1 6 1 1 total 0.00000 0.000000 +6 2 1 1 1 1 1 total 0.00000 0.000000 +7 2 1 1 2 1 1 total 0.00000 0.000000 +8 2 1 1 3 1 1 total 0.00000 0.000000 +9 2 1 1 4 1 1 total 0.00000 0.000000 +10 2 1 1 5 1 1 total 0.00000 0.000000 +11 2 1 1 6 1 1 total 0.00000 0.000000 18 2 2 1 1 1 1 total 0.00000 0.000000 19 2 2 1 2 1 1 total 0.00000 0.000000 20 2 2 1 3 1 1 total 0.00015 0.000151 diff --git a/tests/regression_tests/tally_slice_merge/results_true.dat b/tests/regression_tests/tally_slice_merge/results_true.dat index 4f1d6c6e2..461c4faa8 100644 --- a/tests/regression_tests/tally_slice_merge/results_true.dat +++ b/tests/regression_tests/tally_slice_merge/results_true.dat @@ -48,20 +48,20 @@ 13 (500, 5000, 50000) 6.25e-01 2.00e+07 U235 nu-fission 0.00e+00 0.00e+00 14 (500, 5000, 50000) 6.25e-01 2.00e+07 U238 fission 0.00e+00 0.00e+00 15 (500, 5000, 50000) 6.25e-01 2.00e+07 U238 nu-fission 0.00e+00 0.00e+00 - sum(mesh) energy low [eV] energy high [eV] nuclide score mean std. dev. -0 ((1, 1, 1), (1, 2, 1)) 0.00e+00 6.25e-01 U235 fission 1.48e-02 3.65e-03 -1 ((1, 1, 1), (1, 2, 1)) 0.00e+00 6.25e-01 U235 nu-fission 3.60e-02 8.90e-03 -2 ((1, 1, 1), (1, 2, 1)) 0.00e+00 6.25e-01 U238 fission 2.06e-08 4.98e-09 -3 ((1, 1, 1), (1, 2, 1)) 0.00e+00 6.25e-01 U238 nu-fission 5.14e-08 1.24e-08 -4 ((1, 1, 1), (1, 2, 1)) 6.25e-01 2.00e+07 U235 fission 2.23e-03 3.92e-04 -5 ((1, 1, 1), (1, 2, 1)) 6.25e-01 2.00e+07 U235 nu-fission 5.45e-03 9.56e-04 -6 ((1, 1, 1), (1, 2, 1)) 6.25e-01 2.00e+07 U238 fission 5.58e-04 2.08e-04 -7 ((1, 1, 1), (1, 2, 1)) 6.25e-01 2.00e+07 U238 nu-fission 1.50e-03 5.43e-04 -8 ((2, 1, 1), (2, 2, 1)) 0.00e+00 6.25e-01 U235 fission 2.56e-02 5.50e-03 -9 ((2, 1, 1), (2, 2, 1)) 0.00e+00 6.25e-01 U235 nu-fission 6.24e-02 1.34e-02 -10 ((2, 1, 1), (2, 2, 1)) 0.00e+00 6.25e-01 U238 fission 3.55e-08 7.70e-09 -11 ((2, 1, 1), (2, 2, 1)) 0.00e+00 6.25e-01 U238 nu-fission 8.85e-08 1.92e-08 -12 ((2, 1, 1), (2, 2, 1)) 6.25e-01 2.00e+07 U235 fission 5.01e-03 1.38e-03 -13 ((2, 1, 1), (2, 2, 1)) 6.25e-01 2.00e+07 U235 nu-fission 1.22e-02 3.37e-03 -14 ((2, 1, 1), (2, 2, 1)) 6.25e-01 2.00e+07 U238 fission 2.40e-03 2.69e-04 -15 ((2, 1, 1), (2, 2, 1)) 6.25e-01 2.00e+07 U238 nu-fission 6.60e-03 7.63e-04 + sum(mesh) energy low [eV] energy high [eV] nuclide score mean std. dev. +0 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U235 fission 0.00e+00 0.00e+00 +1 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U235 nu-fission 0.00e+00 0.00e+00 +2 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U238 fission 0.00e+00 0.00e+00 +3 ((1, 1), (1, 2)) 0.00e+00 6.25e-01 U238 nu-fission 0.00e+00 0.00e+00 +4 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U235 fission 1.60e-04 1.60e-04 +5 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U235 nu-fission 3.91e-04 3.91e-04 +6 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U238 fission 5.12e-05 5.12e-05 +7 ((1, 1), (1, 2)) 6.25e-01 2.00e+07 U238 nu-fission 1.36e-04 1.36e-04 +8 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U235 fission 4.04e-02 6.60e-03 +9 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U235 nu-fission 9.85e-02 1.61e-02 +10 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U238 fission 5.61e-08 9.18e-09 +11 ((2, 1), (2, 2)) 0.00e+00 6.25e-01 U238 nu-fission 1.40e-07 2.29e-08 +12 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U235 fission 7.08e-03 1.43e-03 +13 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U235 nu-fission 1.73e-02 3.48e-03 +14 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U238 fission 2.91e-03 3.36e-04 +15 ((2, 1), (2, 2)) 6.25e-01 2.00e+07 U238 nu-fission 7.96e-03 9.27e-04 diff --git a/tests/regression_tests/tally_slice_merge/test.py b/tests/regression_tests/tally_slice_merge/test.py index f79c8b268..e52d0fde4 100644 --- a/tests/regression_tests/tally_slice_merge/test.py +++ b/tests/regression_tests/tally_slice_merge/test.py @@ -126,10 +126,10 @@ class TallySliceMergeTestHarness(PyAPITestHarness): # Sum up a few subdomains from the distribcell tally sum1 = distribcell_tally.summation(filter_type=openmc.DistribcellFilter, - filter_bins=[0,100,2000,30000]) + filter_bins=[0, 100, 2000, 30000]) # Sum up a few subdomains from the distribcell tally sum2 = distribcell_tally.summation(filter_type=openmc.DistribcellFilter, - filter_bins=[500,5000,50000]) + filter_bins=[500, 5000, 50000]) # Merge the distribcell tally slices merge_tally = sum1.merge(sum2) @@ -143,10 +143,10 @@ class TallySliceMergeTestHarness(PyAPITestHarness): # Sum up a few subdomains from the mesh tally sum1 = mesh_tally.summation(filter_type=openmc.MeshFilter, - filter_bins=[(1,1,1), (1,2,1)]) + filter_bins=[(1, 1), (1, 2)]) # Sum up a few subdomains from the mesh tally sum2 = mesh_tally.summation(filter_type=openmc.MeshFilter, - filter_bins=[(2,1,1), (2,2,1)]) + filter_bins=[(2, 1), (2, 2)]) # Merge the mesh tally slices merge_tally = sum1.merge(sum2) From 0b6333810f7f73317f459a701a7d5cbd487f31ff Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 16 Mar 2018 14:07:38 -0500 Subject: [PATCH 116/361] Avoid divide-by-zero warnings in tally arithmetic --- openmc/tallies.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 34424acb0..d8bc2136a 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -1674,19 +1674,22 @@ class Tally(IDManagerMixin): new_tally._std_dev = np.sqrt(data['self']['std. dev.']**2 + data['other']['std. dev.']**2) elif binary_op == '*': - self_rel_err = data['self']['std. dev.'] / data['self']['mean'] - other_rel_err = data['other']['std. dev.'] / data['other']['mean'] + with np.errstate(divide='ignore', invalid='ignore'): + self_rel_err = data['self']['std. dev.'] / data['self']['mean'] + other_rel_err = data['other']['std. dev.'] / data['other']['mean'] new_tally._mean = data['self']['mean'] * data['other']['mean'] new_tally._std_dev = np.abs(new_tally.mean) * \ np.sqrt(self_rel_err**2 + other_rel_err**2) elif binary_op == '/': - self_rel_err = data['self']['std. dev.'] / data['self']['mean'] - other_rel_err = data['other']['std. dev.'] / data['other']['mean'] - new_tally._mean = data['self']['mean'] / data['other']['mean'] + with np.errstate(divide='ignore', invalid='ignore'): + self_rel_err = data['self']['std. dev.'] / data['self']['mean'] + other_rel_err = data['other']['std. dev.'] / data['other']['mean'] + new_tally._mean = data['self']['mean'] / data['other']['mean'] new_tally._std_dev = np.abs(new_tally.mean) * \ np.sqrt(self_rel_err**2 + other_rel_err**2) elif binary_op == '^': - mean_ratio = data['other']['mean'] / data['self']['mean'] + with np.errstate(divide='ignore', invalid='ignore'): + mean_ratio = data['other']['mean'] / data['self']['mean'] first_term = mean_ratio * data['self']['std. dev.'] second_term = \ np.log(data['self']['mean']) * data['other']['std. dev.'] From c83b89086912d2363778b609224c42b7acab7038 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Mar 2018 10:13:51 -0500 Subject: [PATCH 117/361] Use ufloat for stochastic volume results --- openmc/cell.py | 4 +- openmc/data/decay.py | 5 +- openmc/material.py | 2 +- openmc/universe.py | 4 +- openmc/volume.py | 10 ++-- .../volume_calc/results_true.dat | 54 +++++++++---------- tests/regression_tests/volume_calc/test.py | 3 +- 7 files changed, 39 insertions(+), 43 deletions(-) diff --git a/openmc/cell.py b/openmc/cell.py index c7587e136..cbfd74b21 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -295,7 +295,7 @@ class Cell(IDManagerMixin): """ if volume_calc.domain_type == 'cell': if self.id in volume_calc.volumes: - self._volume = volume_calc.volumes[self.id][0] + self._volume = volume_calc.volumes[self.id].n self._atoms = volume_calc.atoms[self.id] else: raise ValueError('No volume information found for this cell.') @@ -335,7 +335,7 @@ class Cell(IDManagerMixin): volume = self.volume for name, atoms in self._atoms.items(): nuclide = openmc.Nuclide(name) - density = 1.0e-24 * atoms[0]/volume # density in atoms/b-cm + density = 1.0e-24 * atoms.n/volume # density in atoms/b-cm nuclides[name] = (nuclide, density) else: raise RuntimeError( diff --git a/openmc/data/decay.py b/openmc/data/decay.py index fa1875939..bbb059f4f 100644 --- a/openmc/data/decay.py +++ b/openmc/data/decay.py @@ -7,10 +7,7 @@ import re from warnings import warn import numpy as np -try: - from uncertainties import ufloat, unumpy, UFloat -except ImportError: - ufloat = UFloat = namedtuple('UFloat', ['nominal_value', 'std_dev']) +from uncertainties import ufloat, unumpy, UFloat import openmc.checkvalue as cv from openmc.mixin import EqualityMixin diff --git a/openmc/material.py b/openmc/material.py index 88479d8df..6bfe58f4a 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -313,7 +313,7 @@ class Material(IDManagerMixin): """ if volume_calc.domain_type == 'material': if self.id in volume_calc.volumes: - self._volume = volume_calc.volumes[self.id][0] + self._volume = volume_calc.volumes[self.id].n self._atoms = volume_calc.atoms[self.id] else: raise ValueError('No volume information found for this material.') diff --git a/openmc/universe.py b/openmc/universe.py index 77138adcc..294b114dd 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -145,7 +145,7 @@ class Universe(IDManagerMixin): """ if volume_calc.domain_type == 'universe': if self.id in volume_calc.volumes: - self._volume = volume_calc.volumes[self.id][0] + self._volume = volume_calc.volumes[self.id].n self._atoms = volume_calc.atoms[self.id] else: raise ValueError('No volume information found for this universe.') @@ -406,7 +406,7 @@ class Universe(IDManagerMixin): volume = self.volume for name, atoms in self._atoms.items(): nuclide = openmc.Nuclide(name) - density = 1.0e-24 * atoms[0]/volume # density in atoms/b-cm + density = 1.0e-24 * atoms.n/volume # density in atoms/b-cm nuclides[name] = (nuclide, density) else: raise RuntimeError( diff --git a/openmc/volume.py b/openmc/volume.py index d61093a17..af7c356ae 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -7,6 +7,7 @@ import warnings import numpy as np import pandas as pd import h5py +from uncertainties import ufloat import openmc import openmc.checkvalue as cv @@ -137,11 +138,10 @@ class VolumeCalculation(object): @property def atoms_dataframe(self): items = [] - columns = [self.domain_type.capitalize(), 'Nuclide', 'Atoms', - 'Uncertainty'] + columns = [self.domain_type.capitalize(), 'Nuclide', 'Atoms'] for uid, atoms_dict in self.atoms.items(): for name, atoms in atoms_dict.items(): - items.append((uid, name, atoms[0], atoms[1])) + items.append((uid, name, atoms)) return pd.DataFrame.from_records(items, columns=columns) @@ -211,13 +211,13 @@ class VolumeCalculation(object): domain_id = int(obj_name[7:]) ids.append(domain_id) group = f[obj_name] - volume = tuple(group['volume'].value) + volume = ufloat(*group['volume'].value) nucnames = group['nuclides'].value atoms_ = group['atoms'].value atom_dict = OrderedDict() for name_i, atoms_i in zip(nucnames, atoms_): - atom_dict[name_i.decode()] = tuple(atoms_i) + atom_dict[name_i.decode()] = ufloat(*atoms_i) volumes[domain_id] = volume atoms[domain_id] = atom_dict diff --git a/tests/regression_tests/volume_calc/results_true.dat b/tests/regression_tests/volume_calc/results_true.dat index 8eb2a61ac..466139cd6 100644 --- a/tests/regression_tests/volume_calc/results_true.dat +++ b/tests/regression_tests/volume_calc/results_true.dat @@ -1,30 +1,30 @@ Volume calculation 0 -Domain 1: 31.4693 +/- 0.0721 cm^3 -Domain 2: 2.0933 +/- 0.0310 cm^3 -Domain 3: 2.0486 +/- 0.0307 cm^3 - Cell Nuclide Atoms Uncertainty -0 1 U235 3.481769e+23 7.979991e+20 -1 1 Mo99 3.481769e+22 7.979991e+19 -2 2 H1 1.399770e+23 2.072914e+21 -3 2 O16 6.998852e+22 1.036457e+21 -4 2 B10 6.998852e+18 1.036457e+17 -5 3 H1 1.369920e+23 2.051689e+21 -6 3 O16 6.849599e+22 1.025844e+21 -7 3 B10 6.849599e+18 1.025844e+17 +Domain 1: 31.47+/-0.07 cm^3 +Domain 2: 2.093+/-0.031 cm^3 +Domain 3: 2.049+/-0.031 cm^3 + Cell Nuclide Atoms +0 1 U235 (3.482+/-0.008)e+23 +1 1 Mo99 (3.482+/-0.008)e+22 +2 2 H1 (1.400+/-0.021)e+23 +3 2 O16 (7.00+/-0.10)e+22 +4 2 B10 (7.00+/-0.10)e+18 +5 3 H1 (1.370+/-0.021)e+23 +6 3 O16 (6.85+/-0.10)e+22 +7 3 B10 (6.85+/-0.10)e+18 Volume calculation 1 -Domain 1: 4.1419 +/- 0.0426 cm^3 -Domain 2: 31.4693 +/- 0.0721 cm^3 - Material Nuclide Atoms Uncertainty -0 1 H1 2.769690e+23 2.850067e+21 -1 1 O16 1.384845e+23 1.425034e+21 -2 1 B10 1.384845e+19 1.425034e+17 -3 2 U235 3.481769e+23 7.979991e+20 -4 2 Mo99 3.481769e+22 7.979991e+19 +Domain 1: 4.14+/-0.04 cm^3 +Domain 2: 31.47+/-0.07 cm^3 + Material Nuclide Atoms +0 1 H1 (2.770+/-0.029)e+23 +1 1 O16 (1.385+/-0.014)e+23 +2 1 B10 (1.385+/-0.014)e+19 +3 2 U235 (3.482+/-0.008)e+23 +4 2 Mo99 (3.482+/-0.008)e+22 Volume calculation 2 -Domain 0: 35.6112 +/- 0.0664 cm^3 - Universe Nuclide Atoms Uncertainty -0 0 H1 2.769690e+23 2.850067e+21 -1 0 O16 1.384845e+23 1.425034e+21 -2 0 B10 1.384845e+19 1.425034e+17 -3 0 U235 3.481769e+23 7.979991e+20 -4 0 Mo99 3.481769e+22 7.979991e+19 +Domain 0: 35.61+/-0.07 cm^3 + Universe Nuclide Atoms +0 0 H1 (2.770+/-0.029)e+23 +1 0 O16 (1.385+/-0.014)e+23 +2 0 B10 (1.385+/-0.014)e+19 +3 0 U235 (3.482+/-0.008)e+23 +4 0 Mo99 (3.482+/-0.008)e+22 diff --git a/tests/regression_tests/volume_calc/test.py b/tests/regression_tests/volume_calc/test.py index a6b62b9be..fda4b3321 100644 --- a/tests/regression_tests/volume_calc/test.py +++ b/tests/regression_tests/volume_calc/test.py @@ -64,8 +64,7 @@ class VolumeTest(PyAPITestHarness): # Write cell volumes and total # of atoms for each nuclide for uid, volume in sorted(volume_calc.volumes.items()): - outstr += 'Domain {0}: {1[0]:.4f} +/- {1[1]:.4f} cm^3\n'.format( - uid, volume) + outstr += 'Domain {}: {} cm^3\n'.format(uid, volume) outstr += str(volume_calc.atoms_dataframe) + '\n' return outstr From 5dc25569f2b54d87e6c9cf7b263c06cdb1914094 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Mar 2018 10:15:08 -0500 Subject: [PATCH 118/361] Use latest HDF5 format for openmc-get-jeff-data --- scripts/openmc-get-jeff-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/openmc-get-jeff-data b/scripts/openmc-get-jeff-data index 9f426a493..03b58163b 100755 --- a/scripts/openmc-get-jeff-data +++ b/scripts/openmc-get-jeff-data @@ -41,7 +41,7 @@ parser.add_argument('-b', '--batch', action='store_true', parser.add_argument('-d', '--destination', default='jeff-3.2-hdf5', help='Directory to create new library in') parser.add_argument('--libver', choices=['earliest', 'latest'], - default='earliest', help="Output HDF5 versioning. Use " + default='latest', help="Output HDF5 versioning. Use " "'earliest' for backwards compatibility or 'latest' for " "performance") args = parser.parse_args() From efade329a8ccf2ca56ffda48e2dfe64c6a6aab43 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 19 Mar 2018 12:55:46 -0400 Subject: [PATCH 119/361] Address #983 comments --- openmc/data/multipole.py | 55 ++++++++++++------------- tests/unit_tests/test_data_multipole.py | 4 +- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/openmc/data/multipole.py b/openmc/data/multipole.py index ac1e93447..f7a78d953 100644 --- a/openmc/data/multipole.py +++ b/openmc/data/multipole.py @@ -652,7 +652,7 @@ class WindowedMultipole(EqualityMixin): fun = np.vectorize(lambda x: self._evaluate(x, T)) return fun(E) - def to_hdf5(self, path, libver='earliest'): + def export_to_hdf5(self, path, libver='earliest'): """Export windowed multipole data to an HDF5 file. Parameters @@ -666,33 +666,32 @@ class WindowedMultipole(EqualityMixin): """ # Open file and write version. - f = h5py.File(path, 'w', libver=libver) - f.create_dataset('version', (1, ), dtype='S10') - f['version'][:] = WMP_VERSION.encode('ASCII') + with h5py.File(path, 'w', libver=libver) as f: + f.create_dataset('version', (1, ), dtype='S10') + f['version'][:] = WMP_VERSION.encode('ASCII') - # Make a nuclide group. - g = f.create_group('nuclide') + # Make a nuclide group. + g = f.create_group('nuclide') - # Write scalars. - if self.formalism == 'MLBW': - g.create_dataset('formalism', - data=np.array(_FORM_MLBW, dtype=np.int32)) - else: - # Assume RM. - g.create_dataset('formalism', - data=np.array(_FORM_RM, dtype=np.int32)) - g.create_dataset('spacing', data=np.array(self.spacing)) - g.create_dataset('sqrtAWR', data=np.array(self.sqrtAWR)) - g.create_dataset('start_E', data=np.array(self.start_E)) - g.create_dataset('end_E', data=np.array(self.end_E)) + # Write scalars. + if self.formalism == 'MLBW': + g.create_dataset('formalism', + data=np.array(_FORM_MLBW, dtype=np.int32)) + else: + # Assume RM. + g.create_dataset('formalism', + data=np.array(_FORM_RM, dtype=np.int32)) + g.create_dataset('spacing', data=np.array(self.spacing)) + g.create_dataset('sqrtAWR', data=np.array(self.sqrtAWR)) + g.create_dataset('start_E', data=np.array(self.start_E)) + g.create_dataset('end_E', data=np.array(self.end_E)) - # Write arrays. - g.create_dataset('data', data=self.data) - g.create_dataset('l_value', data=self.l_value) - g.create_dataset('pseudo_K0RS', data=self.pseudo_k0RS) - g.create_dataset('w_start', data=self.w_start) - g.create_dataset('w_end', data=self.w_end) - g.create_dataset('broaden_poly', data=self.broaden_poly) - g.create_dataset('curvefit', data=self.curvefit) - - f.close() + # Write arrays. + g.create_dataset('data', data=self.data) + g.create_dataset('l_value', data=self.l_value) + g.create_dataset('pseudo_K0RS', data=self.pseudo_k0RS) + g.create_dataset('w_start', data=self.w_start) + g.create_dataset('w_end', data=self.w_end) + g.create_dataset('broaden_poly', + data=self.broaden_poly.astype(np.int8)) + g.create_dataset('curvefit', data=self.curvefit) diff --git a/tests/unit_tests/test_data_multipole.py b/tests/unit_tests/test_data_multipole.py index de7c1cc93..a1cc5bc02 100644 --- a/tests/unit_tests/test_data_multipole.py +++ b/tests/unit_tests/test_data_multipole.py @@ -58,7 +58,7 @@ def test_high_l(fe56): assert total[0] == pytest.approx(27.85535792368082) -def test_to_hdf5(tmpdir, u235): +def test_export_to_hdf5(tmpdir, u235): filename = str(tmpdir.join('092235.h5')) - u235.to_hdf5(filename) + u235.export_to_hdf5(filename) assert os.path.exists(filename) From 85af75c459da55b3d77cf15e5b556b61ca97aa4b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Mar 2018 13:58:58 -0500 Subject: [PATCH 120/361] Address comments on #985 --- openmc/filter.py | 4 ++-- openmc/plots.py | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index 6cb81d97a..26629dafe 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -753,7 +753,7 @@ class MeshFilter(Filter): if n_dim == 3: # Construct 3-tuple of x,y,z cell indices for a 3D mesh nx, ny, nz = self.mesh.dimension - x = bin_index % nx + 1 + x = (bin_index % nx) + 1 y = (bin_index % (nx * ny)) // nx + 1 z = bin_index // (nx * ny) + 1 return (x, y, z) @@ -761,7 +761,7 @@ class MeshFilter(Filter): elif n_dim == 2: # Construct 2-tuple of x,y cell indices for a 2D mesh nx, ny = self.mesh.dimension - x = bin_index % nx + 1 + x = (bin_index % nx) + 1 y = bin_index // nx + 1 return (x, y) else: diff --git a/openmc/plots.py b/openmc/plots.py index a948c2a48..8eff78b1d 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -651,9 +651,16 @@ class Plot(IDManagerMixin): return element - def to_image(self, openmc_exec='openmc', cwd='.', convert_exec='convert'): + def to_ipython_image(self, openmc_exec='openmc', cwd='.', + convert_exec='convert'): """Render plot as an image + This method runs OpenMC in plotting mode to produce a bitmap image which + is then converted to a .png file and loaded in as an + :class:`IPython.display.Image` object. As such, it requires that your + model geometry, materials, and settings have already been exported to + XML. + Parameters ---------- openmc_exec : str From 794ac9255ae077a1dc6e496f24df194d5d669099 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Mar 2018 14:07:42 -0500 Subject: [PATCH 121/361] Add initial Fortran implementation of MeshSurfaceFilter --- CMakeLists.txt | 1 + src/constants.F90 | 5 +- src/input_xml.F90 | 11 +- src/tallies/tally_filter.F90 | 5 + src/tallies/tally_filter_mesh.F90 | 3 - src/tallies/tally_filter_meshsurface.F90 | 392 +++++++++++++++++++++++ src/tallies/tally_header.F90 | 2 + 7 files changed, 411 insertions(+), 8 deletions(-) create mode 100644 src/tallies/tally_filter_meshsurface.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index be62dcd57..9517fe2ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -422,6 +422,7 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/tally_filter_energyfunc.F90 src/tallies/tally_filter_material.F90 src/tallies/tally_filter_mesh.F90 + src/tallies/tally_filter_meshsurface.F90 src/tallies/tally_filter_mu.F90 src/tallies/tally_filter_polar.F90 src/tallies/tally_filter_surface.F90 diff --git a/src/constants.F90 b/src/constants.F90 index 3334a4cc7..75a947149 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -358,7 +358,7 @@ module constants integer, parameter :: NO_BIN_FOUND = -1 ! Tally filter and map types - integer, parameter :: N_FILTER_TYPES = 15 + integer, parameter :: N_FILTER_TYPES = 16 integer, parameter :: & FILTER_UNIVERSE = 1, & FILTER_MATERIAL = 2, & @@ -374,7 +374,8 @@ module constants FILTER_AZIMUTHAL = 12, & FILTER_DELAYEDGROUP = 13, & FILTER_ENERGYFUNCTION = 14, & - FILTER_CELLFROM = 15 + FILTER_CELLFROM = 15, & + FILTER_MESHSURFACE = 16 ! Mesh types integer, parameter :: & diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 918d471d3..792450e51 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, write_message + use error, only: fatal_error, warning, write_message, openmc_err_msg use geometry, only: calc_offsets, maximum_levels, count_instance, & neighbor_lists use geometry_header @@ -2358,13 +2358,13 @@ contains call get_node_value(node_filt, "type", temp_str) temp_str = to_lower(temp_str) - ! Determine number of bins + ! Make sure bins have been set select case(temp_str) case ("energy", "energyout", "mu", "polar", "azimuthal") if (.not. check_for_node(node_filt, "bins")) then call fatal_error("Bins not set in filter " // trim(to_str(filter_id))) end if - case ("mesh", "universe", "material", "cell", "distribcell", & + case ("mesh", "meshsurface", "universe", "material", "cell", "distribcell", & "cellborn", "cellfrom", "surface", "delayedgroup") if (.not. check_for_node(node_filt, "bins")) then call fatal_error("Bins not set in filter " // trim(to_str(filter_id))) @@ -2373,6 +2373,9 @@ contains ! Allocate according to the filter type err = openmc_filter_set_type(i_start + i - 1, to_c_string(temp_str)) + if (err /= 0) then + call fatal_error(to_f_string(openmc_err_msg)) + end if ! Read filter data from XML call f % obj % from_xml(node_filt) @@ -2923,6 +2926,8 @@ contains ! Set filters err = openmc_tally_set_filters(i_start + i - 1, n_filter, temp_filter) deallocate(temp_filter) + else + t % score_bins(j) = SCORE_CURRENT end if case ('events') diff --git a/src/tallies/tally_filter.F90 b/src/tallies/tally_filter.F90 index 587f8dd90..3d6eaaef2 100644 --- a/src/tallies/tally_filter.F90 +++ b/src/tallies/tally_filter.F90 @@ -19,6 +19,7 @@ module tally_filter use tally_filter_energyfunc use tally_filter_material use tally_filter_mesh + use tally_filter_meshsurface use tally_filter_mu use tally_filter_polar use tally_filter_surface @@ -67,6 +68,8 @@ contains type_ = 'material' type is (MeshFilter) type_ = 'mesh' + type is (MeshSurfaceFilter) + type_ = 'meshsurface' type is (MuFilter) type_ = 'mu' type is (PolarFilter) @@ -136,6 +139,8 @@ contains allocate(MaterialFilter :: filters(index) % obj) case ('mesh') allocate(MeshFilter :: filters(index) % obj) + case ('meshsurface') + allocate(MeshSurfaceFilter :: filters(index) % obj) case ('mu') allocate(MuFilter :: filters(index) % obj) case ('polar') diff --git a/src/tallies/tally_filter_mesh.F90 b/src/tallies/tally_filter_mesh.F90 index 2092e7e71..3c0e6250c 100644 --- a/src/tallies/tally_filter_mesh.F90 +++ b/src/tallies/tally_filter_mesh.F90 @@ -84,7 +84,6 @@ contains integer :: ijk1(3) ! indices of ending coordinates integer :: search_iter ! loop count for intersection search integer :: bin - real(8) :: weight ! weight to be pushed back real(8) :: uvw(3) ! cosine of angle of particle real(8) :: xyz0(3) ! starting/intermediate coordinates real(8) :: xyz1(3) ! ending coordinates of particle @@ -96,8 +95,6 @@ contains logical :: end_in_mesh ! ending coordinates inside mesh? type(RegularMesh), pointer :: m - weight = ERROR_REAL - ! Get a pointer to the mesh. m => meshes(this % mesh) n = m % n_dimension diff --git a/src/tallies/tally_filter_meshsurface.F90 b/src/tallies/tally_filter_meshsurface.F90 new file mode 100644 index 000000000..e45261f44 --- /dev/null +++ b/src/tallies/tally_filter_meshsurface.F90 @@ -0,0 +1,392 @@ +module tally_filter_meshsurface + + use, intrinsic :: ISO_C_BINDING + + use hdf5 + + use constants + use dict_header, only: EMPTY + use error + use mesh_header, only: RegularMesh, meshes, n_meshes, mesh_dict + use hdf5_interface + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + public :: openmc_meshsurface_filter_set_mesh + +!=============================================================================== +! MESHFILTER indexes the location of particle events to a regular mesh. For +! tracklength tallies, it will produce multiple valid bins and the bin weight +! will correspond to the fraction of the track length that lies in that bin. +!=============================================================================== + + type, public, extends(TallyFilter) :: MeshSurfaceFilter + integer :: mesh + contains + procedure :: from_xml + procedure :: get_all_bins + procedure :: to_statepoint + procedure :: text_label + end type MeshSurfaceFilter + +contains + + subroutine from_xml(this, node) + class(MeshSurfaceFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: i_mesh + integer :: id + integer :: n + integer :: n_dim + integer :: val + + n = node_word_count(node, "bins") + + if (n /= 1) call fatal_error("Only one mesh can be & + &specified per mesh filter.") + + ! Determine id of mesh + call get_node_value(node, "bins", id) + + ! Get pointer to mesh + val = mesh_dict % get(id) + if (val /= EMPTY) then + i_mesh = val + else + call fatal_error("Could not find mesh " // trim(to_str(id)) & + // " specified on filter.") + end if + + ! Determine number of bins + n_dim = meshes(i_mesh) % n_dimension + this % n_bins = 4*n_dim*product(meshes(i_mesh) % dimension + 1) + + ! Store the index of the mesh + this % mesh = i_mesh + end subroutine from_xml + + subroutine get_all_bins(this, p, estimator, match) + class(MeshSurfaceFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: j ! loop indices + integer :: n_dim ! num dimensions of the mesh + integer :: d1 ! dimension index + integer :: d2 ! dimension index + integer :: d3 ! dimension index + integer :: ijk0(3) ! indices of starting coordinates + integer :: ijk1(3) ! indices of ending coordinates + integer :: n_cross ! number of surface crossings + integer :: i_mesh ! flattened mesh bin index + integer :: i_surf ! surface index (1--12) + integer :: i_bin ! actual index for filter + real(8) :: uvw(3) ! cosine of angle of particle + real(8) :: xyz0(3) ! starting/intermediate coordinates + real(8) :: xyz1(3) ! ending coordinates of particle + real(8) :: xyz_cross(3) ! coordinates of bounding surfaces + real(8) :: d(3) ! distance to each bounding surface + real(8) :: distance ! actual distance traveled + logical :: start_in_mesh ! particle's starting xyz in mesh? + logical :: end_in_mesh ! particle's ending xyz in mesh? + logical :: cross_surface ! whether the particle crosses a surface + + ! Copy starting and ending location of particle + xyz0 = p % last_xyz_current + xyz1 = p % coord(1) % xyz + + associate (m => meshes(this % mesh)) + n_dim = m % n_dimension + + ! Determine indices for starting and ending location + call m % get_indices(xyz0, ijk0, start_in_mesh) + call m % get_indices(xyz1, ijk1, end_in_mesh) + + ! Check to see if start or end is in mesh -- if not, check if track still + ! intersects with mesh + if ((.not. start_in_mesh) .and. (.not. end_in_mesh)) then + if (.not. m % intersects(xyz0, xyz1)) return + end if + + ! Calculate number of surface crossings + n_cross = sum(abs(ijk1(:n_dim) - ijk0(:n_dim))) + if (n_cross == 0) return + + ! Copy particle's direction + uvw = p % coord(1) % uvw + + ! Bounding coordinates + do d1 = 1, n_dim + if (uvw(d1) > 0) then + xyz_cross(d1) = m % lower_left(d1) + ijk0(d1) * m % width(d1) + else + xyz_cross(d1) = m % lower_left(d1) + (ijk0(d1) - 1) * m % width(d1) + end if + end do + + do j = 1, n_cross + ! Set the distances to infinity + d = INFINITY + + ! Calculate distance to each bounding surface. We need to treat + ! special case where the cosine of the angle is zero since this would + ! result in a divide-by-zero. + do d1 = 1, n_dim + if (uvw(d1) == 0) then + d(d1) = INFINITY + else + d(d1) = (xyz_cross(d1) - xyz0(d1))/uvw(d1) + end if + end do + + ! Determine the closest bounding surface of the mesh cell by + ! calculating the minimum distance. Then use the minimum distance and + ! direction of the particle to determine which surface was crossed. + distance = minval(d) + + ! Loop over the dimensions + do d1 = 1, n_dim + + ! Get the other dimensions. + if (d1 == 1) then + d2 = mod(d1, 3) + 1 + d3 = mod(d1 + 1, 3) + 1 + else + d2 = mod(d1 + 1, 3) + 1 + d3 = mod(d1, 3) + 1 + end if + + ! Check whether distance is the shortest distance + if (distance == d(d1)) then + + ! Check whether particle is moving in positive d1 direction + if (uvw(d1) > 0) then + + ! Outward current on d1 max surface + if (all(ijk0(:n_dim) >= 1) .and. & + all(ijk0(:n_dim) <= m % dimension)) then + i_surf = d1 * 4 - 1 + i_mesh = m % get_bin_from_indices(ijk0) + i_bin = 4*n_dim*i_mesh + i_surf + 1 + + call match % bins % push_back(i_bin) + call match % weights % push_back(ONE) + end if + + ! Inward current on d1 min surface + cross_surface = .false. + select case(n_dim) + + case (1) + if (ijk0(d1) >= 0 .and. ijk0(d1) < m % dimension(d1)) then + cross_surface = .true. + end if + + case (2) + if (ijk0(d1) >= 0 .and. ijk0(d1) < m % dimension(d1) .and. & + ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2)) then + cross_surface = .true. + end if + + case (3) + if (ijk0(d1) >= 0 .and. ijk0(d1) < m % dimension(d1) .and. & + ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2) .and. & + ijk0(d3) >= 1 .and. ijk0(d3) <= m % dimension(d3)) then + cross_surface = .true. + end if + end select + + ! If the particle crossed the surface, tally the current + if (cross_surface) then + ijk0(d1) = ijk0(d1) + 1 + i_surf = d1 * 4 - 2 + i_mesh = m % get_bin_from_indices(ijk0) + i_bin = 4*n_dim*i_mesh + i_surf + 1 + + call match % bins % push_back(i_bin) + call match % weights % push_back(ONE) + + ijk0(d1) = ijk0(d1) - 1 + end if + + ijk0(d1) = ijk0(d1) + 1 + xyz_cross(d1) = xyz_cross(d1) + m % width(d1) + + ! The particle is moving in the negative d1 direction + else + + ! Outward current on d1 min surface + if (all(ijk0(:n_dim) >= 1) .and. & + all(ijk0(:n_dim) <= m % dimension)) then + i_surf = d1 * 4 - 3 + i_mesh = m % get_bin_from_indices(ijk0) + i_bin = 4*n_dim*i_mesh + i_surf + 1 + + call match % bins % push_back(i_bin) + call match % weights % push_back(ONE) + end if + + ! Inward current on d1 max surface + cross_surface = .false. + select case(n_dim) + + case (1) + if (ijk0(d1) > 1 .and. ijk0(d1) <= m % dimension(d1) + 1) then + cross_surface = .true. + end if + + case (2) + if (ijk0(d1) > 1 .and. ijk0(d1) <= m % dimension(d1) + 1 .and.& + ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2)) then + cross_surface = .true. + end if + + case (3) + if (ijk0(d1) > 1 .and. ijk0(d1) <= m % dimension(d1) + 1 .and.& + ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2) .and. & + ijk0(d3) >= 1 .and. ijk0(d3) <= m % dimension(d3)) then + cross_surface = .true. + end if + end select + + ! If the particle crossed the surface, tally the current + if (cross_surface) then + ijk0(d1) = ijk0(d1) - 1 + i_surf = d1 * 4 + i_mesh = m % get_bin_from_indices(ijk0) + i_bin = 4*n_dim*i_mesh + i_surf + 1 + + call match % bins % push_back(i_bin) + call match % weights % push_back(ONE) + + ijk0(d1) = ijk0(d1) + 1 + end if + + ijk0(d1) = ijk0(d1) - 1 + xyz_cross(d1) = xyz_cross(d1) - m % width(d1) + end if + end if + end do + + ! Calculate new coordinates + xyz0 = xyz0 + distance * uvw + end do + end associate + + end subroutine get_all_bins + + subroutine to_statepoint(this, filter_group) + class(MeshSurfaceFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "meshsurface") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "bins", meshes(this % mesh) % id) + end subroutine to_statepoint + + function text_label(this, bin) result(label) + class(MeshSurfaceFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + integer :: i_mesh + integer :: i_surf + integer :: n_dim + integer, allocatable :: ijk(:) + + associate (m => meshes(this % mesh)) + n_dim = m % n_dimension + allocate(ijk(n_dim)) + + ! Get flattend mesh index and surface index + i_mesh = (bin - 1) / (4*n_dim) + i_surf = mod(bin - 1, 4*n_dim) + 1 + + ! Get mesh index part of label + call m % get_indices_from_bin(i_mesh, ijk) + if (m % n_dimension == 1) then + label = "Mesh Index (" // trim(to_str(ijk(1))) // ")" + elseif (m % n_dimension == 2) then + label = "Mesh Index (" // trim(to_str(ijk(1))) // ", " // & + trim(to_str(ijk(2))) // ")" + elseif (m % n_dimension == 3) then + label = "Mesh Index (" // trim(to_str(ijk(1))) // ", " // & + trim(to_str(ijk(2))) // ", " // trim(to_str(ijk(3))) // ")" + end if + + ! Get surface part of label + select case (i_surf) + case (OUT_LEFT) + label = trim(label) // " Outgoing, x-min" + case (IN_LEFT) + label = trim(label) // " Incoming, x-min" + case (OUT_RIGHT) + label = trim(label) // " Outgoing, x-max" + case (IN_RIGHT) + label = trim(label) // " Incoming, x-max" + case (OUT_BACK) + label = trim(label) // " Outgoing, y-min" + case (IN_BACK) + label = trim(label) // " Incoming, y-min" + case (OUT_FRONT) + label = trim(label) // " Outgoing, y-max" + case (IN_FRONT) + label = trim(label) // " Incoming, y-max" + case (OUT_BOTTOM) + label = trim(label) // " Outgoing, z-min" + case (IN_BOTTOM) + label = trim(label) // " Incoming, z-min" + case (OUT_TOP) + label = trim(label) // " Outgoing, z-max" + case (IN_TOP) + label = trim(label) // " Incoming, z-max" + end select + end associate + end function text_label + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_meshsurface_filter_set_mesh(index, index_mesh) result(err) bind(C) + ! Set the mesh for a mesh filter + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), value, intent(in) :: index_mesh + integer(C_INT) :: err + + integer :: n_dim + + err = 0 + if (index >= 1 .and. index <= n_filters) then + if (allocated(filters(index) % obj)) then + select type (f => filters(index) % obj) + type is (MeshSurfaceFilter) + if (index_mesh >= 1 .and. index_mesh <= n_meshes) then + f % mesh = index_mesh + n_dim = meshes(index_mesh) % n_dimension + f % n_bins = 4*n_dim*product(meshes(index_mesh) % dimension + 1) + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in 'meshes' array is out of bounds.") + end if + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set mesh on a non-mesh filter.") + end select + else + err = E_ALLOCATE + call set_errmsg("Filter type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in filters array out of bounds.") + end if + end function openmc_meshsurface_filter_set_mesh + +end module tally_filter_meshsurface diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 9604d2382..6266005c2 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -336,6 +336,8 @@ contains j = FILTER_SURFACE type is (MeshFilter) j = FILTER_MESH + type is (MeshSurfaceFilter) + j = FILTER_MESHSURFACE type is (EnergyFilter) j = FILTER_ENERGYIN type is (EnergyoutFilter) From 616d492d01aaeb6b5232408cbb58e40dbb1d04ce Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Mar 2018 15:52:57 -0500 Subject: [PATCH 122/361] Don't include 0-index mesh cells for mesh surface filter --- src/input_xml.F90 | 1 + src/output.F90 | 6 - src/tallies/tally.F90 | 350 +++++++---------------- src/tallies/tally_filter_meshsurface.F90 | 122 +++----- 4 files changed, 130 insertions(+), 349 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 792450e51..39369287e 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2927,6 +2927,7 @@ contains err = openmc_tally_set_filters(i_start + i - 1, n_filter, temp_filter) deallocate(temp_filter) else + t % type = TALLY_MESH_CURRENT t % score_bins(j) = SCORE_CURRENT end if diff --git a/src/output.F90 b/src/output.F90 index 964289126..ecfaa94d5 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -771,12 +771,6 @@ contains end associate end if - ! Handle surface current tallies separately - if (t % type == TALLY_MESH_CURRENT) then - call write_surface_current(t, unit_tally) - cycle - end if - ! WARNING: Admittedly, the logic for moving for printing results is ! extremely confusing and took quite a bit of time to get correct. The ! logic is structured this way since it is not practical to have a do diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index a9a5fd8bf..d11296139 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -3064,7 +3064,7 @@ contains ! tally total or partial currents between two cells !=============================================================================== - subroutine score_surface_tally(p) + subroutine score_surface_tally(p) type(Particle), intent(in) :: p @@ -3199,277 +3199,121 @@ contains integer :: i integer :: i_tally - integer :: j, k ! loop indices - integer :: n_dim ! num dimensions of the mesh - integer :: d1 ! dimension index - integer :: d2 ! dimension index - integer :: d3 ! dimension index - integer :: ijk0(3) ! indices of starting coordinates - integer :: ijk1(3) ! indices of ending coordinates - integer :: n_cross ! number of surface crossings - integer :: filter_index ! index of scoring bin - integer :: i_filter_mesh ! index of mesh filter in filters array - integer :: i_filter_surf ! index of surface filter in filters - integer :: i_filter_energy ! index of energy filter in filters - real(8) :: uvw(3) ! cosine of angle of particle - real(8) :: xyz0(3) ! starting/intermediate coordinates - real(8) :: xyz1(3) ! ending coordinates of particle - real(8) :: xyz_cross(3) ! coordinates of bounding surfaces - real(8) :: d(3) ! distance to each bounding surface - real(8) :: distance ! actual distance traveled - integer :: matching_bin ! next valid filter bin - logical :: start_in_mesh ! particle's starting xyz in mesh? - logical :: end_in_mesh ! particle's ending xyz in mesh? - logical :: cross_surface ! whether the particle crosses a surface - logical :: energy_filter ! energy filter present - type(RegularMesh), pointer :: m + integer :: i_filt + integer :: i_bin + integer :: q ! loop index for scoring bins + integer :: k ! working index for expand and score + integer :: score_bin ! scoring bin, e.g. SCORE_FLUX + integer :: score_index ! scoring bin index + integer :: j ! loop index for scoring bins + integer :: filter_index ! single index for single bin + real(8) :: flux ! collision estimate of flux + real(8) :: filter_weight ! combined weight of all filters + real(8) :: score ! analog tally score + logical :: finished ! found all valid bin combinations + + ! No collision, so no weight change when survival biasing + flux = p % wgt TALLY_LOOP: do i = 1, active_current_tallies % size() - ! Copy starting and ending location of particle - xyz0 = p % last_xyz_current - xyz1 = p % coord(1) % xyz - - ! Get pointer to tally + ! Get index of tally and pointer to tally i_tally = active_current_tallies % data(i) associate (t => tallies(i_tally) % obj) - ! Check for energy filter - energy_filter = (t % find_filter(FILTER_ENERGYIN) > 0) - - ! Get index for mesh, surface, and energy filters - i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) - i_filter_surf = t % filter(t % find_filter(FILTER_SURFACE)) - if (energy_filter) then - i_filter_energy = t % filter(t % find_filter(FILTER_ENERGYIN)) - end if - - ! Reset the matching bins arrays - call filter_matches(i_filter_mesh) % bins % resize(1) - call filter_matches(i_filter_surf) % bins % resize(1) - if (energy_filter) then - call filter_matches(i_filter_energy) % bins % resize(1) - end if - - ! Get pointer to mesh - select type(filt => filters(i_filter_mesh) % obj) - type is (MeshFilter) - m => meshes(filt % mesh) - end select - - n_dim = m % n_dimension - - ! Determine indices for starting and ending location - call m % get_indices(xyz0, ijk0, start_in_mesh) - call m % get_indices(xyz1, ijk1, end_in_mesh) - - ! Check to see if start or end is in mesh -- if not, check if track still - ! intersects with mesh - if ((.not. start_in_mesh) .and. (.not. end_in_mesh)) then - if (.not. m % intersects(xyz0, xyz1)) cycle - end if - - ! Calculate number of surface crossings - n_cross = sum(abs(ijk1(:n_dim) - ijk0(:n_dim))) - if (n_cross == 0) then - cycle - end if - - ! Copy particle's direction - uvw = p % coord(1) % uvw - - ! Determine incoming energy bin. We need to tell the energy filter this - ! is a tracklength tally so it uses the pre-collision energy. - if (energy_filter) then - call filter_matches(i_filter_energy) % bins % clear() - call filter_matches(i_filter_energy) % weights % clear() - call filters(i_filter_energy) % obj % get_all_bins(p, & - ESTIMATOR_TRACKLENGTH, filter_matches(i_filter_energy)) - if (filter_matches(i_filter_energy) % bins % size() == 0) cycle - matching_bin = filter_matches(i_filter_energy) % bins % data(1) - filter_matches(i_filter_energy) % bins % data(1) = matching_bin - end if - - ! Bounding coordinates - do d1 = 1, n_dim - if (uvw(d1) > 0) then - xyz_cross(d1) = m % lower_left(d1) + ijk0(d1) * m % width(d1) - else - xyz_cross(d1) = m % lower_left(d1) + (ijk0(d1) - 1) * m % width(d1) + ! Find all valid bins in each filter if they have not already been found + ! for a previous tally. + do j = 1, size(t % filter) + i_filt = t % filter(j) + if (.not. filter_matches(i_filt) % bins_present) then + call filter_matches(i_filt) % bins % clear() + call filter_matches(i_filt) % weights % clear() + call filters(i_filt) % obj % get_all_bins(p, t % estimator, & + filter_matches(i_filt)) + filter_matches(i_filt) % bins_present = .true. end if + ! If there are no valid bins for this filter, then there is nothing to + ! score and we can move on to the next tally. + if (filter_matches(i_filt) % bins % size() == 0) cycle TALLY_LOOP + + ! Set the index of the bin used in the first filter combination + filter_matches(i_filt) % i_bin = 1 end do - do j = 1, n_cross - ! Reset scoring bin index - filter_matches(i_filter_surf) % bins % data(1) = 0 + ! ======================================================================== + ! Loop until we've covered all valid bins on each of the filters. - ! Set the distances to infinity - d = INFINITY + FILTER_LOOP: do - ! Calculate distance to each bounding surface. We need to treat - ! special case where the cosine of the angle is zero since this would - ! result in a divide-by-zero. - do d1 = 1, n_dim - if (uvw(d1) == 0) then - d(d1) = INFINITY + ! Reset scoring index and weight + filter_index = 1 + filter_weight = ONE + + ! Determine scoring index and weight for this filter combination + do j = 1, size(t % filter) + i_filt = t % filter(j) + i_bin = filter_matches(i_filt) % i_bin + filter_index = filter_index + (filter_matches(i_filt) % bins % & + data(i_bin) - 1) * t % stride(j) + filter_weight = filter_weight * filter_matches(i_filt) % weights % & + data(i_bin) + end do + + ! Determine score + score = flux * filter_weight + + ! Currently only one score type + k = 0 + SCORE_LOOP: do q = 1, t % n_user_score_bins + k = k + 1 + + ! determine what type of score bin + score_bin = t % score_bins(q) + + ! determine scoring bin index, no offset from nuclide bins + score_index = q + + call expand_and_score(p, t, score_index, filter_index, score_bin, & + score, k) + end do SCORE_LOOP + + ! ====================================================================== + ! Filter logic + + ! Increment the filter bins, starting with the last filter to find the + ! next valid bin combination + finished = .true. + do j = size(t % filter), 1, -1 + i_filt = t % filter(j) + if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & + bins % size()) then + filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 + finished = .false. + exit else - d(d1) = (xyz_cross(d1) - xyz0(d1))/uvw(d1) + filter_matches(i_filt) % i_bin = 1 end if end do - ! Determine the closest bounding surface of the mesh cell by - ! calculating the minimum distance. Then use the minimum distance and - ! direction of the particle to determine which surface was crossed. - distance = minval(d) + ! Once we have finished all valid bins for each of the filters, exit + ! the loop. + if (finished) exit FILTER_LOOP - ! Loop over the dimensions - do d1 = 1, n_dim - - ! Get the other dimensions. - if (d1 == 1) then - d2 = mod(d1, 3) + 1 - d3 = mod(d1 + 1, 3) + 1 - else - d2 = mod(d1 + 1, 3) + 1 - d3 = mod(d1, 3) + 1 - end if - - ! Check whether distance is the shortest distance - if (distance == d(d1)) then - - ! Check whether particle is moving in positive d1 direction - if (uvw(d1) > 0) then - - ! Outward current on d1 max surface - if (all(ijk0(:n_dim) >= 1) .and. & - all(ijk0(:n_dim) <= m % dimension)) then - filter_matches(i_filter_surf) % bins % data(1) = d1 * 4 - 1 - filter_matches(i_filter_mesh) % bins % data(1) = & - m % get_bin_from_indices(ijk0) - filter_index = 1 - do k = 1, size(t % filter) - filter_index = filter_index + (filter_matches(t % & - filter(k)) % bins % data(1) - 1) * t % stride(k) - end do -!$omp atomic - t % results(RESULT_VALUE, 1, filter_index) = & - t % results(RESULT_VALUE, 1, filter_index) + p % wgt - end if - - ! Inward current on d1 min surface - cross_surface = .false. - select case(n_dim) - - case (1) - if (ijk0(d1) >= 0 .and. ijk0(d1) < m % dimension(d1)) then - cross_surface = .true. - end if - - case (2) - if (ijk0(d1) >= 0 .and. ijk0(d1) < m % dimension(d1) .and. & - ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2)) then - cross_surface = .true. - end if - - case (3) - if (ijk0(d1) >= 0 .and. ijk0(d1) < m % dimension(d1) .and. & - ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2) .and. & - ijk0(d3) >= 1 .and. ijk0(d3) <= m % dimension(d3)) then - cross_surface = .true. - end if - end select - - ! If the particle crossed the surface, tally the current - if (cross_surface) then - ijk0(d1) = ijk0(d1) + 1 - filter_matches(i_filter_surf) % bins % data(1) = d1 * 4 - 2 - filter_matches(i_filter_mesh) % bins % data(1) = & - m % get_bin_from_indices(ijk0) - filter_index = 1 - do k = 1, size(t % filter) - filter_index = filter_index + (filter_matches(t % & - filter(k)) % bins % data(1) - 1) * t % stride(k) - end do -!$omp atomic - t % results(RESULT_VALUE, 1, filter_index) = & - t % results(RESULT_VALUE, 1, filter_index) + p % wgt - ijk0(d1) = ijk0(d1) - 1 - end if - - ijk0(d1) = ijk0(d1) + 1 - xyz_cross(d1) = xyz_cross(d1) + m % width(d1) - - ! The particle is moving in the negative d1 direction - else - - ! Outward current on d1 min surface - if (all(ijk0(:n_dim) >= 1) .and. & - all(ijk0(:n_dim) <= m % dimension)) then - filter_matches(i_filter_surf) % bins % data(1) = d1 * 4 - 3 - filter_matches(i_filter_mesh) % bins % data(1) = & - m % get_bin_from_indices(ijk0) - filter_index = 1 - do k = 1, size(t % filter) - filter_index = filter_index + (filter_matches(t % & - filter(k)) % bins % data(1) - 1) * t % stride(k) - end do -!$omp atomic - t % results(RESULT_VALUE, 1, filter_index) = & - t % results(RESULT_VALUE, 1, filter_index) + p % wgt - end if - - ! Inward current on d1 max surface - cross_surface = .false. - select case(n_dim) - - case (1) - if (ijk0(d1) > 1 .and. ijk0(d1) <= m % dimension(d1) + 1) then - cross_surface = .true. - end if - - case (2) - if (ijk0(d1) > 1 .and. ijk0(d1) <= m % dimension(d1) + 1 .and.& - ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2)) then - cross_surface = .true. - end if - - case (3) - if (ijk0(d1) > 1 .and. ijk0(d1) <= m % dimension(d1) + 1 .and.& - ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2) .and. & - ijk0(d3) >= 1 .and. ijk0(d3) <= m % dimension(d3)) then - cross_surface = .true. - end if - end select - - ! If the particle crossed the surface, tally the current - if (cross_surface) then - ijk0(d1) = ijk0(d1) - 1 - filter_matches(i_filter_surf) % bins % data(1) = d1 * 4 - filter_matches(i_filter_mesh) % bins % data(1) = & - m % get_bin_from_indices(ijk0) - filter_index = 1 - do k = 1, size(t % filter) - filter_index = filter_index + (filter_matches(t % & - filter(k)) % bins % data(1) - 1) * t % stride(k) - end do -!$omp atomic - t % results(RESULT_VALUE, 1, filter_index) = & - t % results(RESULT_VALUE, 1, filter_index) + p % wgt - ijk0(d1) = ijk0(d1) + 1 - end if - - ijk0(d1) = ijk0(d1) - 1 - xyz_cross(d1) = xyz_cross(d1) - m % width(d1) - end if - end if - end do - - ! Calculate new coordinates - xyz0 = xyz0 + distance * uvw - end do + end do FILTER_LOOP end associate + + ! If the user has specified that we can assume all tallies are spatially + ! separate, this implies that once a tally has been scored to, we needn't + ! check the others. This cuts down on overhead when there are many + ! tallies specified + + if (assume_separate) exit TALLY_LOOP + end do TALLY_LOOP + ! Reset filter matches flag + filter_matches(:) % bins_present = .false. + end subroutine score_surface_current !=============================================================================== diff --git a/src/tallies/tally_filter_meshsurface.F90 b/src/tallies/tally_filter_meshsurface.F90 index e45261f44..1913c8b0d 100644 --- a/src/tallies/tally_filter_meshsurface.F90 +++ b/src/tallies/tally_filter_meshsurface.F90 @@ -64,7 +64,7 @@ contains ! Determine number of bins n_dim = meshes(i_mesh) % n_dimension - this % n_bins = 4*n_dim*product(meshes(i_mesh) % dimension + 1) + this % n_bins = 4*n_dim*product(meshes(i_mesh) % dimension) ! Store the index of the mesh this % mesh = i_mesh @@ -79,8 +79,6 @@ contains integer :: j ! loop indices integer :: n_dim ! num dimensions of the mesh integer :: d1 ! dimension index - integer :: d2 ! dimension index - integer :: d3 ! dimension index integer :: ijk0(3) ! indices of starting coordinates integer :: ijk1(3) ! indices of ending coordinates integer :: n_cross ! number of surface crossings @@ -95,7 +93,6 @@ contains real(8) :: distance ! actual distance traveled logical :: start_in_mesh ! particle's starting xyz in mesh? logical :: end_in_mesh ! particle's ending xyz in mesh? - logical :: cross_surface ! whether the particle crosses a surface ! Copy starting and ending location of particle xyz0 = p % last_xyz_current @@ -153,15 +150,6 @@ contains ! Loop over the dimensions do d1 = 1, n_dim - ! Get the other dimensions. - if (d1 == 1) then - d2 = mod(d1, 3) + 1 - d3 = mod(d1 + 1, 3) + 1 - else - d2 = mod(d1 + 1, 3) + 1 - d3 = mod(d1, 3) + 1 - end if - ! Check whether distance is the shortest distance if (distance == d(d1)) then @@ -173,103 +161,57 @@ contains all(ijk0(:n_dim) <= m % dimension)) then i_surf = d1 * 4 - 1 i_mesh = m % get_bin_from_indices(ijk0) - i_bin = 4*n_dim*i_mesh + i_surf + 1 + i_bin = 4*n_dim*(i_mesh - 1) + i_surf call match % bins % push_back(i_bin) call match % weights % push_back(ONE) end if - ! Inward current on d1 min surface - cross_surface = .false. - select case(n_dim) - - case (1) - if (ijk0(d1) >= 0 .and. ijk0(d1) < m % dimension(d1)) then - cross_surface = .true. - end if - - case (2) - if (ijk0(d1) >= 0 .and. ijk0(d1) < m % dimension(d1) .and. & - ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2)) then - cross_surface = .true. - end if - - case (3) - if (ijk0(d1) >= 0 .and. ijk0(d1) < m % dimension(d1) .and. & - ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2) .and. & - ijk0(d3) >= 1 .and. ijk0(d3) <= m % dimension(d3)) then - cross_surface = .true. - end if - end select - - ! If the particle crossed the surface, tally the current - if (cross_surface) then - ijk0(d1) = ijk0(d1) + 1 - i_surf = d1 * 4 - 2 - i_mesh = m % get_bin_from_indices(ijk0) - i_bin = 4*n_dim*i_mesh + i_surf + 1 - - call match % bins % push_back(i_bin) - call match % weights % push_back(ONE) - - ijk0(d1) = ijk0(d1) - 1 - end if - + ! Advance position ijk0(d1) = ijk0(d1) + 1 xyz_cross(d1) = xyz_cross(d1) + m % width(d1) - ! The particle is moving in the negative d1 direction + ! If the particle crossed the surface, tally the inward current on + ! d1 min surface + if (all(ijk0(:n_dim) >= 1) .and. & + all(ijk0(:n_dim) <= m % dimension)) then + i_surf = d1 * 4 - 2 + i_mesh = m % get_bin_from_indices(ijk0) + i_bin = 4*n_dim*(i_mesh - 1) + i_surf + + call match % bins % push_back(i_bin) + call match % weights % push_back(ONE) + end if + else + ! The particle is moving in the negative d1 direction ! Outward current on d1 min surface if (all(ijk0(:n_dim) >= 1) .and. & all(ijk0(:n_dim) <= m % dimension)) then i_surf = d1 * 4 - 3 i_mesh = m % get_bin_from_indices(ijk0) - i_bin = 4*n_dim*i_mesh + i_surf + 1 + i_bin = 4*n_dim*(i_mesh - 1) + i_surf call match % bins % push_back(i_bin) call match % weights % push_back(ONE) end if - ! Inward current on d1 max surface - cross_surface = .false. - select case(n_dim) - - case (1) - if (ijk0(d1) > 1 .and. ijk0(d1) <= m % dimension(d1) + 1) then - cross_surface = .true. - end if - - case (2) - if (ijk0(d1) > 1 .and. ijk0(d1) <= m % dimension(d1) + 1 .and.& - ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2)) then - cross_surface = .true. - end if - - case (3) - if (ijk0(d1) > 1 .and. ijk0(d1) <= m % dimension(d1) + 1 .and.& - ijk0(d2) >= 1 .and. ijk0(d2) <= m % dimension(d2) .and. & - ijk0(d3) >= 1 .and. ijk0(d3) <= m % dimension(d3)) then - cross_surface = .true. - end if - end select - - ! If the particle crossed the surface, tally the current - if (cross_surface) then - ijk0(d1) = ijk0(d1) - 1 - i_surf = d1 * 4 - i_mesh = m % get_bin_from_indices(ijk0) - i_bin = 4*n_dim*i_mesh + i_surf + 1 - - call match % bins % push_back(i_bin) - call match % weights % push_back(ONE) - - ijk0(d1) = ijk0(d1) + 1 - end if - + ! Advance position ijk0(d1) = ijk0(d1) - 1 xyz_cross(d1) = xyz_cross(d1) - m % width(d1) + + ! If the particle crossed the surface, tally the inward current on + ! d1 max surface + if (all(ijk0(:n_dim) >= 1) .and. & + all(ijk0(:n_dim) <= m % dimension)) then + i_surf = d1 * 4 + i_mesh = m % get_bin_from_indices(ijk0) + i_bin = 4*n_dim*(i_mesh - 1) + i_surf + + call match % bins % push_back(i_bin) + call match % weights % push_back(ONE) + end if end if end if end do @@ -305,7 +247,7 @@ contains allocate(ijk(n_dim)) ! Get flattend mesh index and surface index - i_mesh = (bin - 1) / (4*n_dim) + i_mesh = (bin - 1) / (4*n_dim) + 1 i_surf = mod(bin - 1, 4*n_dim) + 1 ! Get mesh index part of label @@ -370,7 +312,7 @@ contains if (index_mesh >= 1 .and. index_mesh <= n_meshes) then f % mesh = index_mesh n_dim = meshes(index_mesh) % n_dimension - f % n_bins = 4*n_dim*product(meshes(index_mesh) % dimension + 1) + f % n_bins = 4*n_dim*product(meshes(index_mesh) % dimension) else err = E_OUT_OF_BOUNDS call set_errmsg("Index in 'meshes' array is out of bounds.") From c51091a1d7ee8f9ce24650a5d5975e2199f29cd0 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Mar 2018 22:08:44 -0500 Subject: [PATCH 123/361] Use new MeshSurfaceFilter for CMFD (tests pass) --- include/openmc.h | 1 + openmc/filter.py | 4 + src/api.F90 | 1 + src/cmfd_data.F90 | 65 +- src/cmfd_input.F90 | 45 +- src/constants.F90 | 2 +- src/input_xml.F90 | 86 +- src/output.F90 | 182 ---- src/tallies/tally.F90 | 2 +- src/tallies/trigger.F90 | 2 +- .../cmfd_feed/results_true.dat | 816 ------------------ .../cmfd_nofeed/results_true.dat | 816 ------------------ 12 files changed, 51 insertions(+), 1971 deletions(-) diff --git a/include/openmc.h b/include/openmc.h index bb04907a5..10c0b2de8 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -52,6 +52,7 @@ extern "C" { int openmc_material_filter_get_bins(int32_t index, int32_t** bins, int32_t* n); int openmc_material_filter_set_bins(int32_t index, int32_t n, const int32_t* bins); int openmc_mesh_filter_set_mesh(int32_t index, int32_t index_mesh); + int openmc_meshsurface_filter_set_mesh(int32_t index, int32_t index_mesh); int openmc_next_batch(); int openmc_nuclide_name(int index, char** name); void openmc_plot_geometry(); diff --git a/openmc/filter.py b/openmc/filter.py index 26629dafe..d7b8267e9 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -845,6 +845,10 @@ class MeshFilter(Filter): return df +class MeshSurfaceFilter(MeshFilter): + pass + + class RealFilter(Filter): """Tally modifier that describes phase-space and other characteristics diff --git a/src/api.F90 b/src/api.F90 index d79a32d94..64324da3b 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -75,6 +75,7 @@ module openmc_api public :: openmc_material_filter_get_bins public :: openmc_material_filter_set_bins public :: openmc_mesh_filter_set_mesh + public :: openmc_meshsurface_filter_set_mesh public :: openmc_next_batch public :: openmc_nuclide_name public :: openmc_plot_geometry diff --git a/src/cmfd_data.F90 b/src/cmfd_data.F90 index 521be5e3f..3152f1eb2 100644 --- a/src/cmfd_data.F90 +++ b/src/cmfd_data.F90 @@ -73,12 +73,10 @@ contains integer :: ital ! tally object index integer :: ijk(3) ! indices for mesh cell integer :: score_index ! index to pull from tally object - integer :: i_filt ! index in filters array integer :: i_filter_mesh ! index for mesh filter integer :: i_filter_ein ! index for incoming energy filter integer :: i_filter_eout ! index for outgoing energy filter - integer :: i_filter_surf ! index for surface filter - integer :: stride_surf ! stride for surface filter + integer :: i_mesh ! flattend index for mesh logical :: energy_filters! energy filters present real(8) :: flux ! temp variable for flux type(RegularMesh), pointer :: m ! pointer for mesh object @@ -95,10 +93,10 @@ contains ! Associate tallies and mesh associate (t => cmfd_tallies(1) % obj) - i_filt = t % filter(t % find_filter(FILTER_MESH)) + i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) end associate - select type(filt => filters(i_filt) % obj) + select type(filt => filters(i_filter_mesh) % obj) type is (MeshFilter) m => meshes(filt % mesh) end select @@ -115,16 +113,16 @@ contains ! Associate tallies and mesh associate (t => cmfd_tallies(ital) % obj) - i_filt = t % filter(t % find_filter(FILTER_MESH)) - select type(filt => filters(i_filt) % obj) - type is (MeshFilter) - m => meshes(filt % mesh) - end select + + if (ital < 3) then + i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) + else + i_filter_mesh = t % filter(t % find_filter(FILTER_MESHSURFACE)) + end if ! Check for energy filters energy_filters = (t % find_filter(FILTER_ENERGYIN) > 0) - i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) if (energy_filters) then i_filter_ein = t % filter(t % find_filter(FILTER_ENERGYIN)) i_filter_eout = t % filter(t % find_filter(FILTER_ENERGYOUT)) @@ -247,65 +245,62 @@ contains else if (ital == 3) then - i_filter_surf = t % filter(t % find_filter(FILTER_SURFACE)) - stride_surf = t % stride(t % find_filter(FILTER_SURFACE)) - ! Initialize and filter for energy do l = 1, size(t % filter) call filter_matches(t % filter(l)) % bins % clear() call filter_matches(t % filter(l)) % bins % push_back(1) end do + + ! Set the bin for this mesh cell + i_mesh = m % get_bin_from_indices([ i, j, k ]) + filter_matches(i_filter_mesh) % bins % data(1) = 12*(i_mesh - 1) + 1 + + ! Set the energy bin if needed if (energy_filters) then filter_matches(i_filter_ein) % bins % data(1) = ng - h + 1 end if - ! Get the bin for this mesh cell - filter_matches(i_filter_mesh) % bins % data(1) = & - m % get_bin_from_indices([ i, j, k ]) - - score_index = 1 + score_index = 0 do l = 1, size(t % filter) - if (t % filter(l) == i_filter_surf) cycle score_index = score_index + (filter_matches(t % filter(l)) & % bins % data(1) - 1) * t % stride(l) end do ! Left surface cmfd % current(1,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (OUT_LEFT - 1) * stride_surf) + score_index + OUT_LEFT) cmfd % current(2,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (IN_LEFT - 1) * stride_surf) + score_index + IN_LEFT) ! Right surface cmfd % current(3,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (IN_RIGHT - 1) * stride_surf) + score_index + IN_RIGHT) cmfd % current(4,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (OUT_RIGHT - 1) * stride_surf) + score_index + OUT_RIGHT) ! Back surface cmfd % current(5,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (OUT_BACK - 1) * stride_surf) + score_index + OUT_BACK) cmfd % current(6,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (IN_BACK - 1) * stride_surf) + score_index + IN_BACK) ! Front surface cmfd % current(7,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (IN_FRONT - 1) * stride_surf) + score_index + IN_FRONT) cmfd % current(8,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (OUT_FRONT - 1) * stride_surf) + score_index + OUT_FRONT) - ! Bottom surface + ! Left surface cmfd % current(9,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (OUT_BOTTOM - 1) * stride_surf) + score_index + OUT_BOTTOM) cmfd % current(10,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (IN_BOTTOM - 1) * stride_surf) + score_index + IN_BOTTOM) - ! Top surface + ! Right surface cmfd % current(11,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (IN_TOP - 1) * stride_surf) + score_index + IN_TOP) cmfd % current(12,h,i,j,k) = t % results(RESULT_SUM, 1, & - score_index + (OUT_TOP - 1) * stride_surf) - + score_index + OUT_TOP) end if TALLY end do OUTGROUP diff --git a/src/cmfd_input.F90 b/src/cmfd_input.F90 index dbfabb254..86f4e2400 100644 --- a/src/cmfd_input.F90 +++ b/src/cmfd_input.F90 @@ -373,7 +373,7 @@ contains ! Determine number of filters energy_filters = check_for_node(node_mesh, "energy") - n = merge(5, 3, energy_filters) + n = merge(4, 2, energy_filters) ! Extend filters array so we can add CMFD filters err = openmc_extend_filters(n, i_filt_start, i_filt_end) @@ -409,44 +409,15 @@ contains ! Duplicate the mesh filter for the mesh current tally since other ! tallies use this filter and we need to change the dimension i_filt = i_filt + 1 - err = openmc_filter_set_type(i_filt, C_CHAR_'mesh' // C_NULL_CHAR) + err = openmc_filter_set_type(i_filt, C_CHAR_'meshsurface' // C_NULL_CHAR) call openmc_get_filter_next_id(filt_id) err = openmc_filter_set_id(i_filt, filt_id) - err = openmc_mesh_filter_set_mesh(i_filt, i_start) + err = openmc_meshsurface_filter_set_mesh(i_filt, i_start) - ! We need to increase the dimension by one since we also need - ! currents coming into and out of the boundary mesh cells. - filters(i_filt) % obj % n_bins = product(m % dimension + 1) - - ! Set up surface filter - i_filt = i_filt + 1 - allocate(SurfaceFilter :: filters(i_filt) % obj) - select type(filt => filters(i_filt) % obj) - type is(SurfaceFilter) - filt % id = i_filt - filt % n_bins = 4 * m % n_dimension - allocate(filt % surfaces(4 * m % n_dimension)) - if (m % n_dimension == 2) then - filt % surfaces = (/ OUT_LEFT, IN_LEFT, IN_RIGHT, OUT_RIGHT, & - OUT_BACK, IN_BACK, IN_FRONT, OUT_FRONT /) - elseif (m % n_dimension == 3) then - filt % surfaces = (/ OUT_LEFT, IN_LEFT, IN_RIGHT, OUT_RIGHT, & - OUT_BACK, IN_BACK, IN_FRONT, OUT_FRONT, & - OUT_BOTTOM, IN_BOTTOM, IN_TOP, OUT_TOP /) - end if - filt % current = .true. - ! Add filter to dictionary - call filter_dict % set(filt % id, i_filt) - end select ! Initialize filters do i = i_filt_start, i_filt_end - select type (filt => filters(i) % obj) - type is (SurfaceFilter) - ! Don't do anything - class default - call filt % initialize() - end select + call filters(i) % obj % initialize() end do ! Allocate tallies @@ -564,13 +535,9 @@ contains ! Set tally estimator to analog t % estimator = ESTIMATOR_ANALOG - ! Set the surface filter index in the tally find_filter array - n_filter = n_filter + 1 - ! Allocate and set filters allocate(filter_indices(n_filter)) - filter_indices(1) = i_filt_end - 1 - filter_indices(n_filter) = i_filt_end + filter_indices(1) = i_filt_end if (energy_filters) then filter_indices(2) = i_filt_start + 1 end if @@ -588,7 +555,7 @@ contains ! Set macro bins t % score_bins(1) = SCORE_CURRENT - t % type = TALLY_MESH_CURRENT + t % type = TALLY_MESH_SURFACE end if ! Make CMFD tallies active from the start diff --git a/src/constants.F90 b/src/constants.F90 index 75a947149..0e138f0ee 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -292,7 +292,7 @@ module constants ! Tally type integer, parameter :: & TALLY_VOLUME = 1, & - TALLY_MESH_CURRENT = 2, & + TALLY_MESH_SURFACE = 2, & TALLY_SURFACE = 3 ! Tally estimator types diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 39369287e..a0b077a94 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2197,7 +2197,6 @@ contains integer :: l ! another loop index integer :: filter_id ! user-specified identifier for filter integer :: i_filt ! index in filters array - integer :: i_filter_mesh ! index of mesh filter integer :: i_elem ! index of entry in dictionary integer :: n ! size of arrays in mesh specification integer :: n_words ! number of words read @@ -2209,7 +2208,6 @@ contains integer :: trig_ind ! index of triggers array for each tally integer :: user_trig_ind ! index of user-specified triggers for each tally integer :: i_start, i_end - integer :: i_filt_start, i_filt_end integer(C_INT) :: err real(8) :: threshold ! trigger convergence threshold integer :: n_order ! moment order requested @@ -2838,18 +2836,18 @@ contains &t % find_filter(FILTER_CELL) > 0 .or. & &t % find_filter(FILTER_CELLFROM) > 0) then - ! Check to make sure that mesh currents are not desired as well - if (t % find_filter(FILTER_MESH) > 0) then - call fatal_error("Cannot tally other mesh currents & - &in the same tally as surface currents") + ! Check to make sure that mesh surface currents are not desired as well + if (t % find_filter(FILTER_MESHSURFACE) > 0) then + call fatal_error("Cannot tally mesh surface currents & + &in the same tally as normal surface currents") end if t % type = TALLY_SURFACE t % score_bins(j) = SCORE_CURRENT - else if (t % find_filter(FILTER_MESH) > 0) then + else if (t % find_filter(FILTER_MESHSURFACE) > 0) then t % score_bins(j) = SCORE_CURRENT - t % type = TALLY_MESH_CURRENT + t % type = TALLY_MESH_SURFACE ! Check to make sure that current is the only desired response ! for this tally @@ -2857,78 +2855,6 @@ contains call fatal_error("Cannot tally other scores in the & &same tally as surface currents") end if - - ! Get index of mesh filter - i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) - - ! Check to make sure mesh filter was specified - if (i_filter_mesh == 0) then - call fatal_error("Cannot tally surface current without a mesh & - &filter.") - end if - - ! Get pointer to mesh - select type(filt => filters(i_filter_mesh) % obj) - type is (MeshFilter) - m => meshes(filt % mesh) - end select - - ! Extend the filters array so we can add a surface filter and - ! mesh filter - err = openmc_extend_filters(2, i_filt_start, i_filt_end) - - ! Duplicate the mesh filter since other tallies might use this - ! filter and we need to change the dimension - filters(i_filt_start) = filters(i_filter_mesh) - - ! We need to increase the dimension by one since we also need - ! currents coming into and out of the boundary mesh cells. - filters(i_filt_start) % obj % n_bins = product(m % dimension + 1) - - ! Set ID - call openmc_get_filter_next_id(filter_id) - err = openmc_filter_set_id(i_filt_start, filter_id) - - - ! Add surface filter - allocate(SurfaceFilter :: filters(i_filt_end) % obj) - select type (filt => filters(i_filt_end) % obj) - type is (SurfaceFilter) - filt % n_bins = 4 * m % n_dimension - allocate(filt % surfaces(4 * m % n_dimension)) - if (m % n_dimension == 1) then - filt % surfaces = (/ OUT_LEFT, IN_LEFT, OUT_RIGHT, IN_RIGHT /) - elseif (m % n_dimension == 2) then - filt % surfaces = (/ OUT_LEFT, IN_LEFT, OUT_RIGHT, IN_RIGHT, & - OUT_BACK, IN_BACK, OUT_FRONT, IN_FRONT /) - elseif (m % n_dimension == 3) then - filt % surfaces = (/ OUT_LEFT, IN_LEFT, OUT_RIGHT, IN_RIGHT, & - OUT_BACK, IN_BACK, OUT_FRONT, IN_FRONT, OUT_BOTTOM, & - IN_BOTTOM, OUT_TOP, IN_TOP /) - end if - filt % current = .true. - - ! Set ID - call openmc_get_filter_next_id(filter_id) - err = openmc_filter_set_id(i_filt_end, filter_id) - end select - - ! Copy filter indices to resized array - n_filter = size(t % filter) - allocate(temp_filter(n_filter + 1)) - temp_filter(1:size(t % filter)) = t % filter - n_filter = n_filter + 1 - - ! Set mesh and surface filters - temp_filter(t % find_filter(FILTER_MESH)) = i_filt_start - temp_filter(n_filter) = i_filt_end - - ! Set filters - err = openmc_tally_set_filters(i_start + i - 1, n_filter, temp_filter) - deallocate(temp_filter) - else - t % type = TALLY_MESH_CURRENT - t % score_bins(j) = SCORE_CURRENT end if case ('events') diff --git a/src/output.F90 b/src/output.F90 index ecfaa94d5..b6809ca82 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -932,188 +932,6 @@ contains end subroutine write_tallies -!=============================================================================== -! WRITE_SURFACE_CURRENT writes out surface current tallies over a mesh to the -! tallies.out file. -!=============================================================================== - - subroutine write_surface_current(t, unit_tally) - type(TallyObject), intent(in) :: t - integer, intent(in) :: unit_tally - - integer :: i ! mesh index - integer :: j ! loop index over tally filters - integer :: ijk(3) ! indices of mesh cells - integer :: n_dim ! number of mesh dimensions - integer :: n_cells ! number of mesh cells - integer :: l ! index for energy - integer :: i_filter_mesh ! index for mesh filter - integer :: i_filter_ein ! index for incoming energy filter - integer :: i_filter_surf ! index for surface filter - integer :: stride_surf ! stride for surface filter - integer :: n ! number of incoming energy bins - integer :: filter_index ! index in results array for filters - integer :: nr ! number of realizations - real(8) :: x(2) ! mean and standard deviation - logical :: print_ebin ! should incoming energy bin be displayed? - logical :: energy_filters ! energy filters present - character(MAX_LINE_LEN) :: string - type(RegularMesh), pointer :: m - type(TallyFilterMatch), allocatable :: matches(:) - - allocate(matches(n_filters)) - - nr = t % n_realizations - - ! Get pointer to mesh - i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) - select type(filt => filters(i_filter_mesh) % obj) - type is (MeshFilter) - m => meshes(filt % mesh) - end select - - ! Get surface filter index and stride - i_filter_surf = t % filter(t % find_filter(FILTER_SURFACE)) - stride_surf = t % stride(t % find_filter(FILTER_SURFACE)) - - ! initialize bins array - do j = 1, size(t % filter) - call matches(t % filter(j)) % bins % clear() - call matches(t % filter(j)) % bins % push_back(1) - end do - - ! determine how many energy in bins there are - energy_filters = (t % find_filter(FILTER_ENERGYIN) > 0) - if (energy_filters) then - print_ebin = .true. - i_filter_ein = t % filter(t % find_filter(FILTER_ENERGYIN)) - n = filters(i_filter_ein) % obj % n_bins - else - print_ebin = .false. - n = 1 - end if - - ! Get the dimensions and number of cells in the mesh - n_dim = m % n_dimension - n_cells = product(m % dimension) - - ! Loop over all the mesh cells - do i = 1, n_cells - - ! Get the indices for this cell - call m % get_indices_from_bin(i, ijk) - matches(i_filter_mesh) % bins % data(1) = i - - ! Write the header for this cell - if (n_dim == 1) then - string = "Mesh Index (" // trim(to_str(ijk(1))) // ")" - else if (n_dim == 2) then - string = "Mesh Index (" // trim(to_str(ijk(1))) // ", " & - // trim(to_str(ijk(2))) // ")" - else if (n_dim == 3) then - string = "Mesh Index (" // trim(to_str(ijk(1))) // ", " & - // trim(to_str(ijk(2))) // ", " // trim(to_str(ijk(3))) // ")" - end if - - write(UNIT=unit_tally, FMT='(1X,A)') trim(string) - - do l = 1, n - if (print_ebin) then - ! Set incoming energy bin - matches(i_filter_ein) % bins % data(1) = l - - ! Write incoming energy bin - write(UNIT=unit_tally, FMT='(3X,A)') & - trim(filters(i_filter_ein) % obj % text_label( & - matches(i_filter_ein) % bins % data(1))) - end if - - filter_index = 1 - do j = 1, size(t % filter) - if (t % filter(j) == i_filter_surf) cycle - filter_index = filter_index + (matches(t % filter(j)) & - % bins % data(1) - 1) * t % stride(j) - end do - - associate(r => t % results(RESULT_SUM:RESULT_SUM_SQ, :, :)) - - ! Left Surface - x(:) = mean_stdev(r(:, 1, filter_index + (OUT_LEFT - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Outgoing Current on Left", to_str(x(1)), trim(to_str(x(2))) - - x(:) = mean_stdev(r(:, 1, filter_index + (IN_LEFT - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Incoming Current on Left", to_str(x(1)), trim(to_str(x(2))) - - ! Right Surface - x(:) = mean_stdev(r(:, 1, filter_index + (OUT_RIGHT - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Outgoing Current on Right", to_str(x(1)), trim(to_str(x(2))) - - x(:) = mean_stdev(r(:, 1, filter_index + (IN_RIGHT - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Incoming Current on Right", to_str(x(1)), trim(to_str(x(2))) - - if (n_dim >= 2) then - - ! Back Surface - x(:) = mean_stdev(r(:, 1, filter_index + (OUT_BACK - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Outgoing Current on Back", to_str(x(1)), trim(to_str(x(2))) - - x(:) = mean_stdev(r(:, 1, filter_index + (IN_BACK - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Incoming Current on Back", to_str(x(1)), trim(to_str(x(2))) - - ! Front Surface - x(:) = mean_stdev(r(:, 1, filter_index + (OUT_FRONT - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Outgoing Current on Front", to_str(x(1)), trim(to_str(x(2))) - - x(:) = mean_stdev(r(:, 1, filter_index + (IN_FRONT - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Incoming Current on Front", to_str(x(1)), trim(to_str(x(2))) - end if - - if (n_dim == 3) then - - ! Bottom Surface - x(:) = mean_stdev(r(:, 1, filter_index + (OUT_BOTTOM - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Outgoing Current on Bottom", to_str(x(1)), trim(to_str(x(2))) - - x(:) = mean_stdev(r(:, 1, filter_index + (IN_BOTTOM - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Incoming Current on Bottom", to_str(x(1)), trim(to_str(x(2))) - - ! Top Surface - x(:) = mean_stdev(r(:, 1, filter_index + (OUT_TOP - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Outgoing Current on Top", to_str(x(1)), trim(to_str(x(2))) - - x(:) = mean_stdev(r(:, 1, filter_index + (IN_TOP - 1) * & - stride_surf), nr) - write(UNIT=unit_tally, FMT='(5X,A,T35,A,"+/- ",A)') & - "Incoming Current on Top", to_str(x(1)), trim(to_str(x(2))) - end if - end associate - end do - end do - - end subroutine write_surface_current - !=============================================================================== ! MEAN_STDEV computes the sample mean and standard deviation of the mean of a ! single tally score diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index d11296139..058c414f2 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -4236,7 +4236,7 @@ contains elseif (t % estimator == ESTIMATOR_COLLISION) then call active_collision_tallies % push_back(i) end if - elseif (t % type == TALLY_MESH_CURRENT) then + elseif (t % type == TALLY_MESH_SURFACE) then call active_current_tallies % push_back(i) elseif (t % type == TALLY_SURFACE) then call active_surface_tallies % push_back(i) diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index ee2259ed4..7dcb3d71e 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -168,7 +168,7 @@ contains trigger % variance = ZERO ! Mesh current tally triggers require special treatment - if (t % type == TALLY_MESH_CURRENT) then + if (t % type == TALLY_MESH_SURFACE) then call compute_tally_current(t, trigger) else diff --git a/tests/regression_tests/cmfd_feed/results_true.dat b/tests/regression_tests/cmfd_feed/results_true.dat index d5eed3d3c..5e6750fe6 100644 --- a/tests/regression_tests/cmfd_feed/results_true.dat +++ b/tests/regression_tests/cmfd_feed/results_true.dat @@ -364,822 +364,6 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 cmfd indices 1.000000E+01 1.000000E+00 diff --git a/tests/regression_tests/cmfd_nofeed/results_true.dat b/tests/regression_tests/cmfd_nofeed/results_true.dat index a3fbfb954..f00966cf8 100644 --- a/tests/regression_tests/cmfd_nofeed/results_true.dat +++ b/tests/regression_tests/cmfd_nofeed/results_true.dat @@ -364,822 +364,6 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 cmfd indices 1.000000E+01 1.000000E+00 From 206166cc41ac3de754d22584c9b03cacfc185419 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Mar 2018 22:30:07 -0500 Subject: [PATCH 124/361] Remove redundant surface tally subroutine. Fix broken tests --- openmc/capi/filter.py | 8 + src/api.F90 | 3 +- src/relaxng/tallies.rnc | 16 +- src/relaxng/tallies.rng | 2 + src/tallies/tally.F90 | 142 +----------------- src/tallies/tally_header.F90 | 4 +- src/tracking.F90 | 24 +-- .../filter_mesh/inputs_true.dat | 15 +- .../filter_mesh/results_true.dat | 2 +- tests/regression_tests/filter_mesh/test.py | 9 +- .../score_current/results_true.dat | 2 +- .../score_current/tallies.xml | 2 +- 12 files changed, 63 insertions(+), 166 deletions(-) diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index 5a5df4814..691ecc09c 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -55,6 +55,9 @@ _dll.openmc_material_filter_set_bins.errcheck = _error_handler _dll.openmc_mesh_filter_set_mesh.argtypes = [c_int32, c_int32] _dll.openmc_mesh_filter_set_mesh.restype = c_int _dll.openmc_mesh_filter_set_mesh.errcheck = _error_handler +_dll.openmc_meshsurface_filter_set_mesh.argtypes = [c_int32, c_int32] +_dll.openmc_meshsurface_filter_set_mesh.restype = c_int +_dll.openmc_meshsurface_filter_set_mesh.errcheck = _error_handler class Filter(_FortranObjectWithID): @@ -188,6 +191,10 @@ class MeshFilter(Filter): filter_type = 'mesh' +class MeshSurfaceFilter(Filter): + filter_type = 'meshsurface' + + class MuFilter(Filter): filter_type = 'mu' @@ -216,6 +223,7 @@ _FILTER_TYPE_MAP = { 'energyfunction': EnergyFunctionFilter, 'material': MaterialFilter, 'mesh': MeshFilter, + 'meshsurface': MeshSurfaceFilter, 'mu': MuFilter, 'polar': PolarFilter, 'surface': SurfaceFilter, diff --git a/src/api.F90 b/src/api.F90 index 64324da3b..df183c292 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -276,8 +276,9 @@ contains ! Clear active tally lists call active_analog_tallies % clear() call active_tracklength_tallies % clear() - call active_current_tallies % clear() + call active_meshsurf_tallies % clear() call active_collision_tallies % clear() + call active_surface_tallies % clear() call active_tallies % clear() ! Reset timers diff --git a/src/relaxng/tallies.rnc b/src/relaxng/tallies.rnc index 5ab8971e6..204284c48 100644 --- a/src/relaxng/tallies.rnc +++ b/src/relaxng/tallies.rnc @@ -35,14 +35,14 @@ element tallies { element filter { (element id { xsd:int } | attribute id { xsd:int }) & ( - ( (element type { ( "cell" | "cellfrom" | "cellborn" | "material" | - "universe" | "surface" | "distribcell" | "mesh" | "energy" | - "energyout" | "mu" | "polar" | "azimuthal" | "delayedgroup" | - "energyfunction") } | - attribute type { ( "cell" | "cellfrom" | "cellborn" | "material" | - "universe" | "surface" | "distribcell" | "mesh" | "energy" | - "energyout" | "mu" | "polar" | "azimuthal" | "delayedgroup" | - "energyfunction") }) & + ( (element type { ( "cell" | "cellfrom" | "cellborn" | "material" | + "universe" | "surface" | "distribcell" | "mesh" | "energy" | + "energyout" | "mu" | "polar" | "azimuthal" | "delayedgroup" | + "energyfunction" | "meshsurface") } | + attribute type { ( "cell" | "cellfrom" | "cellborn" | "material" | + "universe" | "surface" | "distribcell" | "mesh" | "energy" | + "energyout" | "mu" | "polar" | "azimuthal" | "delayedgroup" | + "energyfunction" | "meshsurface") }) & (element bins { list { xsd:double+ } } | attribute bins { list { xsd:double+ } }) ) | diff --git a/src/relaxng/tallies.rng b/src/relaxng/tallies.rng index bad2fdce9..15b6f5b24 100644 --- a/src/relaxng/tallies.rng +++ b/src/relaxng/tallies.rng @@ -182,6 +182,7 @@ azimuthal delayedgroup energyfunction + meshsurface @@ -201,6 +202,7 @@ azimuthal delayedgroup energyfunction + meshsurface diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 058c414f2..f4d12be7e 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -3064,9 +3064,9 @@ contains ! tally total or partial currents between two cells !=============================================================================== - subroutine score_surface_tally(p) - - type(Particle), intent(in) :: p + subroutine score_surface_tally(p, tally_vec) + type(Particle), intent(in) :: p + type(VectorInt), intent(in) :: tally_vec integer :: i integer :: i_tally @@ -3086,9 +3086,9 @@ contains ! No collision, so no weight change when survival biasing flux = p % wgt - TALLY_LOOP: do i = 1, active_surface_tallies % size() + TALLY_LOOP: do i = 1, tally_vec % size() ! Get index of tally and pointer to tally - i_tally = active_surface_tallies % data(i) + i_tally = tally_vec % data(i) associate (t => tallies(i_tally) % obj) ! Find all valid bins in each filter if they have not already been found @@ -3188,134 +3188,6 @@ contains end subroutine score_surface_tally -!=============================================================================== -! SCORE_SURFACE_CURRENT tallies surface crossings in a mesh tally by manually -! determining which mesh surfaces were crossed -!=============================================================================== - - subroutine score_surface_current(p) - - type(Particle), intent(in) :: p - - integer :: i - integer :: i_tally - integer :: i_filt - integer :: i_bin - integer :: q ! loop index for scoring bins - integer :: k ! working index for expand and score - integer :: score_bin ! scoring bin, e.g. SCORE_FLUX - integer :: score_index ! scoring bin index - integer :: j ! loop index for scoring bins - integer :: filter_index ! single index for single bin - real(8) :: flux ! collision estimate of flux - real(8) :: filter_weight ! combined weight of all filters - real(8) :: score ! analog tally score - logical :: finished ! found all valid bin combinations - - ! No collision, so no weight change when survival biasing - flux = p % wgt - - TALLY_LOOP: do i = 1, active_current_tallies % size() - ! Get index of tally and pointer to tally - i_tally = active_current_tallies % data(i) - associate (t => tallies(i_tally) % obj) - - ! Find all valid bins in each filter if they have not already been found - ! for a previous tally. - do j = 1, size(t % filter) - i_filt = t % filter(j) - if (.not. filter_matches(i_filt) % bins_present) then - call filter_matches(i_filt) % bins % clear() - call filter_matches(i_filt) % weights % clear() - call filters(i_filt) % obj % get_all_bins(p, t % estimator, & - filter_matches(i_filt)) - filter_matches(i_filt) % bins_present = .true. - end if - ! If there are no valid bins for this filter, then there is nothing to - ! score and we can move on to the next tally. - if (filter_matches(i_filt) % bins % size() == 0) cycle TALLY_LOOP - - ! Set the index of the bin used in the first filter combination - filter_matches(i_filt) % i_bin = 1 - end do - - ! ======================================================================== - ! Loop until we've covered all valid bins on each of the filters. - - FILTER_LOOP: do - - ! Reset scoring index and weight - filter_index = 1 - filter_weight = ONE - - ! Determine scoring index and weight for this filter combination - do j = 1, size(t % filter) - i_filt = t % filter(j) - i_bin = filter_matches(i_filt) % i_bin - filter_index = filter_index + (filter_matches(i_filt) % bins % & - data(i_bin) - 1) * t % stride(j) - filter_weight = filter_weight * filter_matches(i_filt) % weights % & - data(i_bin) - end do - - ! Determine score - score = flux * filter_weight - - ! Currently only one score type - k = 0 - SCORE_LOOP: do q = 1, t % n_user_score_bins - k = k + 1 - - ! determine what type of score bin - score_bin = t % score_bins(q) - - ! determine scoring bin index, no offset from nuclide bins - score_index = q - - call expand_and_score(p, t, score_index, filter_index, score_bin, & - score, k) - end do SCORE_LOOP - - ! ====================================================================== - ! Filter logic - - ! Increment the filter bins, starting with the last filter to find the - ! next valid bin combination - finished = .true. - do j = size(t % filter), 1, -1 - i_filt = t % filter(j) - if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & - bins % size()) then - filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 - finished = .false. - exit - else - filter_matches(i_filt) % i_bin = 1 - end if - end do - - ! Once we have finished all valid bins for each of the filters, exit - ! the loop. - if (finished) exit FILTER_LOOP - - end do FILTER_LOOP - - end associate - - ! If the user has specified that we can assume all tallies are spatially - ! separate, this implies that once a tally has been scored to, we needn't - ! check the others. This cuts down on overhead when there are many - ! tallies specified - - if (assume_separate) exit TALLY_LOOP - - end do TALLY_LOOP - - ! Reset filter matches flag - filter_matches(:) % bins_present = .false. - - end subroutine score_surface_current - !=============================================================================== ! APPLY_DERIVATIVE_TO_SCORE multiply the given score by its relative derivative !=============================================================================== @@ -4219,7 +4091,7 @@ contains call active_collision_tallies % clear() call active_tracklength_tallies % clear() call active_surface_tallies % clear() - call active_current_tallies % clear() + call active_meshsurf_tallies % clear() do i = 1, n_tallies associate (t => tallies(i) % obj) @@ -4237,7 +4109,7 @@ contains call active_collision_tallies % push_back(i) end if elseif (t % type == TALLY_MESH_SURFACE) then - call active_current_tallies % push_back(i) + call active_meshsurf_tallies % push_back(i) elseif (t % type == TALLY_SURFACE) then call active_surface_tallies % push_back(i) end if diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 6266005c2..0495ce14d 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -144,7 +144,7 @@ module tally_header ! Active tally lists type(VectorInt), public :: active_analog_tallies type(VectorInt), public :: active_tracklength_tallies - type(VectorInt), public :: active_current_tallies + type(VectorInt), public :: active_meshsurf_tallies type(VectorInt), public :: active_collision_tallies type(VectorInt), public :: active_tallies type(VectorInt), public :: active_surface_tallies @@ -418,7 +418,7 @@ contains ! Deallocate tally node lists call active_analog_tallies % clear() call active_tracklength_tallies % clear() - call active_current_tallies % clear() + call active_meshsurf_tallies % clear() call active_collision_tallies % clear() call active_surface_tallies % clear() call active_tallies % clear() diff --git a/src/tracking.F90 b/src/tracking.F90 index f5839eb8e..3fe6a065b 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -19,9 +19,9 @@ module tracking use surface_header use tally_header use tally, only: score_analog_tally, score_tracklength_tally, & - score_collision_tally, score_surface_current, & - score_track_derivative, score_surface_tally, & - score_collision_derivative, zero_flux_derivs + score_collision_tally, score_surface_tally, & + score_track_derivative, zero_flux_derivs, & + score_collision_derivative use track_output, only: initialize_particle_track, write_particle_track, & add_particle_track, finalize_particle_track @@ -185,7 +185,8 @@ contains p % event = EVENT_SURFACE end if ! Score cell to cell partial currents - if(active_surface_tallies % size() > 0) call score_surface_tally(p) + if(active_surface_tallies % size() > 0) & + call score_surface_tally(p, active_surface_tallies) else ! ==================================================================== ! PARTICLE HAS COLLISION @@ -200,7 +201,8 @@ contains ! since the direction of the particle will change and we need to use the ! pre-collision direction to figure out what mesh surfaces were crossed - if (active_current_tallies % size() > 0) call score_surface_current(p) + if (active_meshsurf_tallies % size() > 0) & + call score_surface_tally(p, active_meshsurf_tallies) ! Clear surface component p % surface = NONE @@ -317,12 +319,12 @@ contains ! forward slightly so that if the mesh boundary is on the surface, it is ! still processed - if (active_current_tallies % size() > 0) then + if (active_meshsurf_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) + call score_surface_tally(p, active_meshsurf_tallies) end if ! Score to global leakage tally @@ -350,10 +352,10 @@ contains ! 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 + if (active_meshsurf_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) + call score_surface_tally(p, active_meshsurf_tallies) p % coord(1) % xyz = xyz end if @@ -407,10 +409,10 @@ 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 + if (active_meshsurf_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) + call score_surface_tally(p, active_meshsurf_tallies) p % coord(1) % xyz = xyz end if diff --git a/tests/regression_tests/filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat index ca063b7ce..511b29848 100644 --- a/tests/regression_tests/filter_mesh/inputs_true.dat +++ b/tests/regression_tests/filter_mesh/inputs_true.dat @@ -327,18 +327,27 @@ 1 + + 1 + 2 + + 2 + 3 + + 3 + 1 total - 1 + 4 current @@ -346,7 +355,7 @@ total - 2 + 5 current @@ -354,7 +363,7 @@ total - 3 + 6 current diff --git a/tests/regression_tests/filter_mesh/results_true.dat b/tests/regression_tests/filter_mesh/results_true.dat index ac3439da5..f331238b7 100644 --- a/tests/regression_tests/filter_mesh/results_true.dat +++ b/tests/regression_tests/filter_mesh/results_true.dat @@ -1 +1 @@ -804d161cb8eae506d3247a533d122f44a01d3cedd566b3c65c71a0b51326dd9b97f8bbf45af7304b500476f3f854d91b10ccad94122d15f23641b05b835fada6 \ No newline at end of file +46950c046648faaa5ff3cb7b4fdd03667ae3c6da96a7ed8121761291de452cf88481f53e967ed52407d77d90109ae24839967a22ae216531a0e1491f5051ea72 \ No newline at end of file diff --git a/tests/regression_tests/filter_mesh/test.py b/tests/regression_tests/filter_mesh/test.py index e0c6dd0f2..8a7f7a6fc 100644 --- a/tests/regression_tests/filter_mesh/test.py +++ b/tests/regression_tests/filter_mesh/test.py @@ -30,6 +30,9 @@ class FilterMeshTestHarness(HashedPyAPITestHarness): mesh_1d_filter = openmc.MeshFilter(mesh_1d) mesh_2d_filter = openmc.MeshFilter(mesh_2d) mesh_3d_filter = openmc.MeshFilter(mesh_3d) + meshsurf_1d_filter = openmc.MeshSurfaceFilter(mesh_1d) + meshsurf_2d_filter = openmc.MeshSurfaceFilter(mesh_2d) + meshsurf_3d_filter = openmc.MeshSurfaceFilter(mesh_3d) # Initialized the tallies tally = openmc.Tally(name='tally 1') @@ -38,7 +41,7 @@ class FilterMeshTestHarness(HashedPyAPITestHarness): self._model.tallies.append(tally) tally = openmc.Tally(name='tally 2') - tally.filters = [mesh_1d_filter] + tally.filters = [meshsurf_1d_filter] tally.scores = ['current'] self._model.tallies.append(tally) @@ -48,7 +51,7 @@ class FilterMeshTestHarness(HashedPyAPITestHarness): self._model.tallies.append(tally) tally = openmc.Tally(name='tally 4') - tally.filters = [mesh_2d_filter] + tally.filters = [meshsurf_2d_filter] tally.scores = ['current'] self._model.tallies.append(tally) @@ -58,7 +61,7 @@ class FilterMeshTestHarness(HashedPyAPITestHarness): self._model.tallies.append(tally) tally = openmc.Tally(name='tally 6') - tally.filters = [mesh_3d_filter] + tally.filters = [meshsurf_3d_filter] tally.scores = ['current'] self._model.tallies.append(tally) diff --git a/tests/regression_tests/score_current/results_true.dat b/tests/regression_tests/score_current/results_true.dat index 75d61b718..dedc473de 100644 --- a/tests/regression_tests/score_current/results_true.dat +++ b/tests/regression_tests/score_current/results_true.dat @@ -1 +1 @@ -4675d5101f4f829369c39cb33d654430836b934ab07c165777ba6e214bdf3a698b8082f4f9bb9e78f1f0e495b30ea02cf9b3d14622c59915d818d678a1e5b7b1 \ No newline at end of file +138b312cdaa822c9b62f757b3259522004b679c4ed289a92798b29a6442c26d12c53256635be273f13e3703816ff50a1b9f52d79770eade01e482384ac0d389f \ No newline at end of file diff --git a/tests/regression_tests/score_current/tallies.xml b/tests/regression_tests/score_current/tallies.xml index 3b8f60adb..a381e4c2e 100644 --- a/tests/regression_tests/score_current/tallies.xml +++ b/tests/regression_tests/score_current/tallies.xml @@ -9,7 +9,7 @@ - mesh + meshsurface 1 From 22429dd389b5e6a297473ebcbc0fa1114dff7ad1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 16 Mar 2018 09:34:16 -0500 Subject: [PATCH 125/361] Implement MeshSurfaceFilter.get_pandas_dataframe. Fix surface/mesh filters --- openmc/filter.py | 164 ++++++++++++++++++++++++++++++++++++++-------- openmc/tallies.py | 2 +- 2 files changed, 136 insertions(+), 30 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index d7b8267e9..7fa47cb4d 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -22,12 +22,14 @@ _FILTER_TYPES = ['universe', 'material', 'cell', 'cellborn', 'surface', 'mesh', 'energy', 'energyout', 'mu', 'polar', 'azimuthal', 'distribcell', 'delayedgroup', 'energyfunction', 'cellfrom'] -_CURRENT_NAMES = {1: 'x-min out', 2: 'x-min in', - 3: 'x-max out', 4: 'x-max in', - 5: 'y-min out', 6: 'y-min in', - 7: 'y-max out', 8: 'y-max in', - 9: 'z-min out', 10: 'z-min in', - 11: 'z-max out', 12: 'z-max in'} +_CURRENT_NAMES = OrderedDict([ + (1, 'x-min out'), (2, 'x-min in'), + (3, 'x-max out'), (4, 'x-max in'), + (5, 'y-min out'), (6, 'y-min in'), + (7, 'y-max out'), (8, 'y-max in'), + (9, 'z-min out'), (10, 'z-min in'), + (11, 'z-max out'), (12, 'z-max in') +]) class FilterMeta(ABCMeta): @@ -497,9 +499,9 @@ class CellFilter(WithIDFilter): Parameters ---------- - bins : openmc.Cell, Integral, or iterable thereof - The Cells to tally. Either openmc.Cell objects or their - Integral ID numbers can be used. + bins : openmc.Cell, int, or iterable thereof + The cells to tally. Either openmc.Cell objects or their ID numbers can + be used. filter_id : int Unique identifier for the filter @@ -565,21 +567,21 @@ class CellbornFilter(WithIDFilter): class SurfaceFilter(Filter): - """Bins particle currents on Mesh surfaces. + """Filters particles by surface crossing Parameters ---------- - bins : Iterable of Integral - Indices corresponding to which face of a mesh cell the current is - crossing. + bins : openmc.Surface, int, or iterable of Integral + The surfaces to tally over. Either openmc.Surface objects of their ID + numbers can be used. filter_id : int Unique identifier for the filter Attributes ---------- bins : Iterable of Integral - Indices corresponding to which face of a mesh cell the current is - crossing. + The surfaces to tally over. Either openmc.Surface objects of their ID + numbers can be used. id : int Unique identifier for the filter num_bins : Integral @@ -598,13 +600,6 @@ class SurfaceFilter(Filter): self._bins = bins - @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. @@ -689,7 +684,6 @@ class MeshFilter(Filter): filter_id = int(group.name.split('/')[-1].lstrip('filter ')) out = cls(mesh_obj, filter_id=filter_id) - out._num_bins = group['n_bins'].value return out @@ -705,10 +699,7 @@ class MeshFilter(Filter): @property def num_bins(self): - try: - return self._num_bins - except AttributeError: - return reduce(operator.mul, self.mesh.dimension) + return reduce(operator.mul, self.mesh.dimension) def check_bins(self, bins): if not len(bins) == 1: @@ -802,7 +793,7 @@ class MeshFilter(Filter): filter_dict = {} # Append Mesh ID as outermost index of multi-index - mesh_key = 'mesh {0}'.format(self.mesh.id) + mesh_key = 'mesh {}'.format(self.mesh.id) # Find mesh dimensions - use 3D indices for simplicity n_dim = len(self.mesh.dimension) @@ -846,7 +837,122 @@ class MeshFilter(Filter): class MeshSurfaceFilter(MeshFilter): - pass + """Filter events by surface crossings on a regular, rectangular mesh. + + Parameters + ---------- + mesh : openmc.Mesh + The Mesh object that events will be tallied onto + filter_id : int + Unique identifier for the filter + + Attributes + ---------- + bins : Integral + The Mesh ID + mesh : openmc.Mesh + The Mesh object that events will be tallied onto + id : int + Unique identifier for the filter + num_bins : Integral + The number of filter bins + + """ + + @property + def num_bins(self): + n_dim = len(self.mesh.dimension) + return 4*n_dim*reduce(operator.mul, self.mesh.dimension) + + def get_bin_index(self, filter_bin): + raise NotImplementedError + + def get_bin(self, bin_index): + raise NotImplementedError + + 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 + columns annotated by filter bin information. This is a helper method for + :meth:`Tally.get_pandas_dataframe`. + + Parameters + ---------- + data_size : int + The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter + + Returns + ------- + pandas.DataFrame + A Pandas DataFrame with three columns describing the x,y,z mesh + cell indices corresponding to each filter bin. The number of rows + in the DataFrame is the same as the total number of bins in the + corresponding tally, with the filter bin appropriately tiled to map + to the corresponding tally bins. + + See also + -------- + Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() + + """ + # Initialize Pandas DataFrame + df = pd.DataFrame() + + # Initialize dictionary to build Pandas Multi-index column + filter_dict = {} + + # Append Mesh ID as outermost index of multi-index + mesh_key = 'mesh {}'.format(self.mesh.id) + + # Find mesh dimensions - use 3D indices for simplicity + if len(self.mesh.dimension) == 3: + nx, ny, nz = self.mesh.dimension + elif len(self.mesh.dimension) == 2: + nx, ny = self.mesh.dimension + nz = 1 + else: + nx = self.mesh.dimension + ny = nz = 1 + + # Generate multi-index sub-column for x-axis + filter_bins = np.arange(1, nx + 1) + repeat_factor = 12 * stride + filter_bins = np.repeat(filter_bins, repeat_factor) + tile_factor = data_size // len(filter_bins) + filter_bins = np.tile(filter_bins, tile_factor) + filter_dict[(mesh_key, 'x')] = filter_bins + + # Generate multi-index sub-column for y-axis + filter_bins = np.arange(1, ny + 1) + repeat_factor = 12 * nx * stride + filter_bins = np.repeat(filter_bins, repeat_factor) + tile_factor = data_size // len(filter_bins) + filter_bins = np.tile(filter_bins, tile_factor) + filter_dict[(mesh_key, 'y')] = filter_bins + + # Generate multi-index sub-column for z-axis + filter_bins = np.arange(1, nz + 1) + repeat_factor = 12 * nx * ny * stride + filter_bins = np.repeat(filter_bins, repeat_factor) + tile_factor = data_size // len(filter_bins) + filter_bins = np.tile(filter_bins, tile_factor) + filter_dict[(mesh_key, 'z')] = filter_bins + + # Generate multi-index sub-column for surface + filter_bins = list(_CURRENT_NAMES.values()) + 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) + filter_dict[(mesh_key, 'surf')] = filter_bins + + # Initialize a Pandas DataFrame from the mesh dictionary + df = pd.concat([df, pd.DataFrame(filter_dict)]) + + return df class RealFilter(Filter): diff --git a/openmc/tallies.py b/openmc/tallies.py index d8bc2136a..6c055d191 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -2738,7 +2738,7 @@ class Tally(IDManagerMixin): new_filter = filter_type(bins) # Set number of bins manually for mesh/distribcell filters - if filter_type in (openmc.DistribcellFilter, openmc.MeshFilter): + if filter_type is openmc.DistribcellFilter: new_filter._num_bins = find_filter._num_bins # Replace existing filter with new one From d393a74906d811e81f1700b8286dbb2b62e16e97 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 16 Mar 2018 14:38:37 -0500 Subject: [PATCH 126/361] Implement get_bin and get_bin_index for MeshSurfaceFilter --- docs/source/pythonapi/base.rst | 1 + openmc/filter.py | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index f1633f3f9..070b5bd20 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -109,6 +109,7 @@ Constructing Tallies openmc.CellbornFilter openmc.SurfaceFilter openmc.MeshFilter + openmc.MeshSurfaceFilter openmc.EnergyFilter openmc.EnergyoutFilter openmc.MuFilter diff --git a/openmc/filter.py b/openmc/filter.py index 7fa47cb4d..26d81112f 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -865,10 +865,25 @@ class MeshSurfaceFilter(MeshFilter): return 4*n_dim*reduce(operator.mul, self.mesh.dimension) def get_bin_index(self, filter_bin): - raise NotImplementedError + # Split bin into mesh/surface parts + *mesh_tuple, surf = filter_bin + + # Get index for mesh alone + mesh_index = super().get_bin_index(mesh_tuple) + + # Combine surface and mesh index + n_dim = len(self.mesh.dimension) + for surf_index, name in _CURRENT_NAMES.items(): + if surf == name: + return 4*n_dim*mesh_index + surf_index - 1 + else: + raise ValueError("'{}' is not a valid mesh surface.".format(surf)) def get_bin(self, bin_index): - raise NotImplementedError + n_dim = len(self.mesh.dimension) + mesh_index, surf_index = divmod(bin_index, 4*n_dim) + mesh_bin = super().get_bin(mesh_index) + return mesh_bin + (_CURRENT_NAMES[surf_index + 1],) def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. From 0da649fd903f2902fe44de471ae2906dacfbf462 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Mar 2018 18:12:59 -0500 Subject: [PATCH 127/361] Simplify use of _CURRENT_NAMES in filter.py --- examples/xml/pincell/tallies.xml | 2 +- openmc/filter.py | 81 +++++--------------------------- 2 files changed, 14 insertions(+), 69 deletions(-) diff --git a/examples/xml/pincell/tallies.xml b/examples/xml/pincell/tallies.xml index 0ae36eced..7e1e0dafe 100644 --- a/examples/xml/pincell/tallies.xml +++ b/examples/xml/pincell/tallies.xml @@ -8,7 +8,7 @@ - 1 + 2 diff --git a/openmc/filter.py b/openmc/filter.py index 26d81112f..2a36a66e0 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -15,6 +15,7 @@ import openmc.checkvalue as cv from .cell import Cell from .material import Material from .mixin import IDManagerMixin +from .surface import Surface from .universe import Universe @@ -22,14 +23,11 @@ _FILTER_TYPES = ['universe', 'material', 'cell', 'cellborn', 'surface', 'mesh', 'energy', 'energyout', 'mu', 'polar', 'azimuthal', 'distribcell', 'delayedgroup', 'energyfunction', 'cellfrom'] -_CURRENT_NAMES = OrderedDict([ - (1, 'x-min out'), (2, 'x-min in'), - (3, 'x-max out'), (4, 'x-max in'), - (5, 'y-min out'), (6, 'y-min in'), - (7, 'y-max out'), (8, 'y-max in'), - (9, 'z-min out'), (10, 'z-min in'), - (11, 'z-max out'), (12, 'z-max in') -]) +_CURRENT_NAMES = ( + 'x-min out', 'x-min in', 'x-max out', 'x-max in', + 'y-min out', 'y-min in', 'y-max out', 'y-max in', + 'z-min out', 'z-min in', 'z-max out', 'z-max in' +) class FilterMeta(ABCMeta): @@ -566,7 +564,7 @@ class CellbornFilter(WithIDFilter): expected_type = Cell -class SurfaceFilter(Filter): +class SurfaceFilter(WithIDFilter): """Filters particles by surface crossing Parameters @@ -588,57 +586,7 @@ class SurfaceFilter(Filter): The number of filter bins """ - @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) - for edge in bins: - cv.check_greater_than('filter bin', edge, 0, equality=True) - - self._bins = bins - - 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 - columns annotated by filter bin information. This is a helper method for - :meth:`Tally.get_pandas_dataframe`. - - Parameters - ---------- - data_size : int - The total number of bins in the tally corresponding to this filter - stride : int - Stride in memory for the filter - - Returns - ------- - pandas.DataFrame - A Pandas DataFrame with a column of strings describing which surface - the current is crossing and which direction it points. The number - of rows in the DataFrame is the same as the total number of bins in - the corresponding tally, with the filter bin appropriately tiled to - map to the corresponding tally bins. - - See also - -------- - Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() - - """ - # Initialize Pandas DataFrame - df = pd.DataFrame() - - 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] - df = pd.concat([df, pd.DataFrame( - {self.short_name.lower(): filter_bins})]) - - return df + expected_type = Surface class MeshFilter(Filter): @@ -873,9 +821,9 @@ class MeshSurfaceFilter(MeshFilter): # Combine surface and mesh index n_dim = len(self.mesh.dimension) - for surf_index, name in _CURRENT_NAMES.items(): + for surf_index, name in enumerate(_CURRENT_NAMES): if surf == name: - return 4*n_dim*mesh_index + surf_index - 1 + return 4*n_dim*mesh_index + surf_index else: raise ValueError("'{}' is not a valid mesh surface.".format(surf)) @@ -883,7 +831,7 @@ class MeshSurfaceFilter(MeshFilter): n_dim = len(self.mesh.dimension) mesh_index, surf_index = divmod(bin_index, 4*n_dim) mesh_bin = super().get_bin(mesh_index) - return mesh_bin + (_CURRENT_NAMES[surf_index + 1],) + return mesh_bin + (_CURRENT_NAMES[surf_index],) def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. @@ -957,17 +905,14 @@ class MeshSurfaceFilter(MeshFilter): filter_dict[(mesh_key, 'z')] = filter_bins # Generate multi-index sub-column for surface - filter_bins = list(_CURRENT_NAMES.values()) repeat_factor = stride - filter_bins = np.repeat(filter_bins, repeat_factor) + filter_bins = np.repeat(_CURRENT_NAMES, repeat_factor) tile_factor = data_size // len(filter_bins) filter_bins = np.tile(filter_bins, tile_factor) filter_dict[(mesh_key, 'surf')] = filter_bins # Initialize a Pandas DataFrame from the mesh dictionary - df = pd.concat([df, pd.DataFrame(filter_dict)]) - - return df + return pd.concat([df, pd.DataFrame(filter_dict)]) class RealFilter(Filter): From e1da965f707cbb66c1b099ce68d3a18c206ee0d1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 20 Mar 2018 14:15:56 -0500 Subject: [PATCH 128/361] Fix typos (thanks @smharper) --- openmc/filter.py | 4 ++-- src/input_xml.F90 | 4 +--- src/tallies/tally_filter_meshsurface.F90 | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index 2a36a66e0..c1b9f8dc9 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -570,7 +570,7 @@ class SurfaceFilter(WithIDFilter): Parameters ---------- bins : openmc.Surface, int, or iterable of Integral - The surfaces to tally over. Either openmc.Surface objects of their ID + The surfaces to tally over. Either openmc.Surface objects or their ID numbers can be used. filter_id : int Unique identifier for the filter @@ -578,7 +578,7 @@ class SurfaceFilter(WithIDFilter): Attributes ---------- bins : Iterable of Integral - The surfaces to tally over. Either openmc.Surface objects of their ID + The surfaces to tally over. Either openmc.Surface objects or their ID numbers can be used. id : int Unique identifier for the filter diff --git a/src/input_xml.F90 b/src/input_xml.F90 index a0b077a94..a29bb08e8 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2371,9 +2371,7 @@ contains ! Allocate according to the filter type err = openmc_filter_set_type(i_start + i - 1, to_c_string(temp_str)) - if (err /= 0) then - call fatal_error(to_f_string(openmc_err_msg)) - end if + if (err /= 0) call fatal_error(to_f_string(openmc_err_msg)) ! Read filter data from XML call f % obj % from_xml(node_filt) diff --git a/src/tallies/tally_filter_meshsurface.F90 b/src/tallies/tally_filter_meshsurface.F90 index 1913c8b0d..e1e8c4803 100644 --- a/src/tallies/tally_filter_meshsurface.F90 +++ b/src/tallies/tally_filter_meshsurface.F90 @@ -48,7 +48,7 @@ contains n = node_word_count(node, "bins") if (n /= 1) call fatal_error("Only one mesh can be & - &specified per mesh filter.") + &specified per meshsurface filter.") ! Determine id of mesh call get_node_value(node, "bins", id) @@ -297,7 +297,7 @@ contains !=============================================================================== function openmc_meshsurface_filter_set_mesh(index, index_mesh) result(err) bind(C) - ! Set the mesh for a mesh filter + ! Set the mesh for a mesh surface filter integer(C_INT32_T), value, intent(in) :: index integer(C_INT32_T), value, intent(in) :: index_mesh integer(C_INT) :: err From 109236710a9a90ecfbe3a8e5dedbc83bb0b1b1e1 Mon Sep 17 00:00:00 2001 From: guillaume Date: Sat, 24 Mar 2018 14:51:24 -0400 Subject: [PATCH 129/361] added python example for depletion --- .../python/pincell_depletion/chain_simple.xml | 49 ++++++ .../python/pincell_depletion/run_depletion.py | 161 ++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 examples/python/pincell_depletion/chain_simple.xml create mode 100644 examples/python/pincell_depletion/run_depletion.py diff --git a/examples/python/pincell_depletion/chain_simple.xml b/examples/python/pincell_depletion/chain_simple.xml new file mode 100644 index 000000000..c2e50a370 --- /dev/null +++ b/examples/python/pincell_depletion/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/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py new file mode 100644 index 000000000..0ee2a411f --- /dev/null +++ b/examples/python/pincell_depletion/run_depletion.py @@ -0,0 +1,161 @@ +import openmc +import numpy as np + +############################################################################### +# Simulation Input File Parameters +############################################################################### + +# OpenMC simulation parameters +batches = 100 +inactive = 10 +particles = 1000 + +# Depletion simulation parameters +time_step = 1.*24*60*60 # s +final_time = 45.*24*60*60 # s +time_steps = np.full(np.int(final_time / time_step), time_step) + +chain_file = './chain_simple.xml' +power = 174.1 # W/cm, for 2D simulations only + +############################################################################### +# Define materials +############################################################################### + +# Instantiate some Materials and register the appropriate Nuclides +uo2 = openmc.Material(material_id=1, name='UO2 fuel at 2.4% wt enrichment') +uo2.set_density('g/cm3', 10.29769) +uo2.add_element('U', 1., enrichment=2.4) +uo2.add_element('O', 2.) +uo2.depletable = True + +helium = openmc.Material(material_id=2, name='Helium for gap') +helium.set_density('g/cm3', 0.001598) +helium.add_element('He', 2.4044e-4) +helium.depletable = False + +zircaloy = openmc.Material(material_id=3, name='Zircaloy 4') +zircaloy.set_density('g/cm3', 6.55) +zircaloy.add_element('Sn', 0.014 , 'wo') +zircaloy.add_element('Fe', 0.00165, 'wo') +zircaloy.add_element('Cr', 0.001 , 'wo') +zircaloy.add_element('Zr', 0.98335, 'wo') +zircaloy.depletable = False + +borated_water = openmc.Material(material_id=4, name='Borated water') +borated_water.set_density('g/cm3', 0.740582) +borated_water.add_element('B', 4.0e-5) +borated_water.add_element('H', 5.0e-2) +borated_water.add_element('O', 2.4e-2) +borated_water.add_s_alpha_beta('c_H_in_H2O') +borated_water.depletable = False + +############################################################################### +# Exporting to OpenMC geometry.xml file +############################################################################### + +# Instantiate ZCylinder surfaces +fuel_or = openmc.ZCylinder(surface_id=1, x0=0, y0=0, R=0.39218, name='Fuel OR') +clad_ir = openmc.ZCylinder(surface_id=2, x0=0, y0=0, R=0.40005, name='Clad IR') +clad_or = openmc.ZCylinder(surface_id=3, x0=0, y0=0, R=0.45720, name='Clad OR') +left = openmc.XPlane(surface_id=4, x0=-0.62992, name='left') +right = openmc.XPlane(surface_id=5, x0=0.62992, name='right') +bottom = openmc.YPlane(surface_id=6, y0=-0.62992, name='bottom') +top = openmc.YPlane(surface_id=7, y0=0.62992, name='top') + +left.boundary_type = 'reflective' +right.boundary_type = 'reflective' +top.boundary_type = 'reflective' +bottom.boundary_type = 'reflective' + +# Instantiate Cells +fuel = openmc.Cell(cell_id=1, name='cell 1') +gap = openmc.Cell(cell_id=2, name='cell 2') +clad = openmc.Cell(cell_id=3, name='cell 3') +water = openmc.Cell(cell_id=4, name='cell 4') + +# Use surface half-spaces to define regions +fuel.region = -fuel_or +gap.region = +fuel_or & -clad_ir +clad.region = +clad_ir & -clad_or +water.region = +clad_or & +left & -right & +bottom & -top + +# Register Materials with Cells +fuel.fill = uo2 +gap.fill = helium +clad.fill = zircaloy +water.fill = borated_water + +# Instantiate Universe +root = openmc.Universe(universe_id=0, name='root universe') + +# Register Cells with Universe +root.add_cells([fuel, gap, clad, water]) + +# Instantiate a Geometry, register the root Universe, and export to XML +geometry = openmc.Geometry(root) +geometry.export_to_xml() + +############################################################################### +# Exporting to OpenMC materials.xml file +############################################################################### + +# Compute cell areas +area = {} +area[fuel] = np.pi * fuel_or.coefficients['R'] ** 2 + +# Set materials volume for depletion. Set to an area for 2D simulations +uo2.volume = area[fuel] + +# Instantiate a Materials collection and export to XML +materials_file = openmc.Materials([uo2, helium, zircaloy, borated_water]) +materials_file.export_to_xml() + +############################################################################### +# Exporting to OpenMC settings.xml file +############################################################################### + +# Instantiate a Settings object, set all runtime parameters, and export to XML +settings_file = openmc.Settings() +settings_file.batches = batches +settings_file.inactive = inactive +settings_file.particles = particles + +# Create an initial uniform spatial source distribution over fissionable zones +bounds = [-0.62992, -0.62992, -1, 0.62992, 0.62992, 1] +uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) +settings_file.source = openmc.source.Source(space=uniform_dist) + +entropy_mesh = openmc.Mesh() +entropy_mesh.lower_left = [-0.39218, -0.39218, -1.e50] +entropy_mesh.upper_right = [0.39218, 0.39218, 1.e50] +entropy_mesh.dimension = [10, 10, 1] +settings_file.entropy_mesh = entropy_mesh +settings_file.export_to_xml() + +############################################################################### +# Run depletion calculation +############################################################################### + +op = openmc.deplete.Operator(geometry, settings_file, chain_file) +op.round_number = True + +# Perform simulation using the predictor algorithm +openmc.deplete.integrator.predictor(op, time_steps, power) + +############################################################################### +# Read depletion calculation results +############################################################################### + +# Open results file +results = openmc.deplete.ResultsList("depletion_results.h5") + +# Dictionnary of materials and burned nuclides +mat_id_to_ind = results[0].mat_to_ind +nuc_to_ind = results[0].nuc_to_ind + +# Obtain K_eff as a function of time +time, keff = results.get_eigenvalue() + +# Obtain U235 concentration as a function of time +time, U5 = results.get_atoms(mat_id_to_ind['1'], nuc_to_ind['U235']) From 53da3af815aaf3c49fac9dc90fa422a06b602fbb Mon Sep 17 00:00:00 2001 From: guillaume Date: Sat, 24 Mar 2018 15:02:30 -0400 Subject: [PATCH 130/361] small adjustments --- examples/python/pincell_depletion/run_depletion.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py index 0ee2a411f..9d81812fe 100644 --- a/examples/python/pincell_depletion/run_depletion.py +++ b/examples/python/pincell_depletion/run_depletion.py @@ -12,11 +12,11 @@ particles = 1000 # Depletion simulation parameters time_step = 1.*24*60*60 # s -final_time = 45.*24*60*60 # s +final_time = 15.*24*60*60 # s time_steps = np.full(np.int(final_time / time_step), time_step) chain_file = './chain_simple.xml' -power = 174.1 # W/cm, for 2D simulations only +power = 174 # W/cm, for 2D simulations only (use W for 3D) ############################################################################### # Define materials @@ -134,7 +134,7 @@ settings_file.entropy_mesh = entropy_mesh settings_file.export_to_xml() ############################################################################### -# Run depletion calculation +# Initialize and run depletion calculation ############################################################################### op = openmc.deplete.Operator(geometry, settings_file, chain_file) @@ -158,4 +158,4 @@ nuc_to_ind = results[0].nuc_to_ind time, keff = results.get_eigenvalue() # Obtain U235 concentration as a function of time -time, U5 = results.get_atoms(mat_id_to_ind['1'], nuc_to_ind['U235']) +time, n_U235 = results.get_atoms(mat_id_to_ind['1'], nuc_to_ind['U235']) From 1ee5e56c6ab0157caa22d5d1fe78454aace649db Mon Sep 17 00:00:00 2001 From: guillaume Date: Sat, 24 Mar 2018 15:56:59 -0400 Subject: [PATCH 131/361] added install information to documentation --- docs/source/usersguide/install.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 24bcc7164..5171eb4ec 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -148,8 +148,8 @@ Prerequisites .. important:: If you are building HDF5 version 1.8.x or earlier, you must include - ``--enable-fortran2003`` when configuring HDF5 or else OpenMC will not - be able to compile. + ``--enable-fortran2003`` as well when configuring HDF5 or else OpenMC + will not be able to compile. .. admonition:: Optional :class: note @@ -416,7 +416,8 @@ Prerequisites The Python API works with Python 3.4+. In addition to Python itself, the API relies on a number of third-party packages. All prerequisites can be installed using Conda_ (recommended), pip_, or through the package manager in most Linux -distributions. +distributions. To run simulations in parallel using MPI, it is recommended to +build mpi4py, hdf5, h5py from source, in that order, using the same compilers. .. admonition:: Required :class: error @@ -455,7 +456,7 @@ distributions. `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. + simulations. `Cython `_ Cython is used for resonance reconstruction for ENDF data converted to From 627c10046789ff753eaa4f3000145597c29516bd Mon Sep 17 00:00:00 2001 From: guillaume Date: Sat, 24 Mar 2018 16:04:57 -0400 Subject: [PATCH 132/361] one more detail on installation --- docs/source/usersguide/install.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 5171eb4ec..6b24c64dd 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -417,7 +417,8 @@ 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. To run simulations in parallel using MPI, it is recommended to -build mpi4py, hdf5, h5py from source, in that order, using the same compilers. +build mpi4py, hdf5, h5py from source, in that order, using the same compilers +as for openmc. .. admonition:: Required :class: error From 98b6dc15a94e6911e73c40fd3d0b9a9163aa4513 Mon Sep 17 00:00:00 2001 From: amandalund Date: Sun, 25 Mar 2018 13:47:47 -0500 Subject: [PATCH 133/361] Fix that ensures TRISO particles are completely contained within the domain --- openmc/model/triso.py | 95 +++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/openmc/model/triso.py b/openmc/model/triso.py index 5fe51b664..b5ee74f90 100644 --- a/openmc/model/triso.py +++ b/openmc/model/triso.py @@ -113,8 +113,7 @@ class _Domain(metaclass=ABCMeta): Length in x-, y-, and z- directions of each cell in mesh overlaid on domain. limits : list of float - Minimum and maximum position in x-, y-, and z-directions where particle - center can be placed. + Constraint on where particle center can be placed. volume : float Volume of the container. @@ -158,8 +157,6 @@ class _Domain(metaclass=ABCMeta): raise ValueError('Unable to set domain center to {} since it must ' 'be of length 3'.format(center)) self._center = [float(x) for x in center] - self._limits = None - self._cell_length = None def mesh_cell(self, p): """Calculate the index of the cell in a mesh overlaid on the domain in @@ -211,6 +208,19 @@ class _Domain(metaclass=ABCMeta): """ pass + @abstractmethod + def enforce_boundary(self, particle): + """Update the position of the particle if necessary to ensure that the + particle remains entirely within the domain. + + Parameters + ---------- + particle : numpy.ndarray + Cartesian coordinates of particle center. + + """ + pass + class _CubicDomain(_Domain): """Cubic container in which to pack particles. @@ -238,7 +248,7 @@ class _CubicDomain(_Domain): Length in x-, y-, and z- directions of each cell in mesh overlaid on domain. limits : list of float - Minimum and maximum position in x-, y-, and z-directions where particle + Maximum distance from center in x-, y-, or z-direction where particle center can be placed. volume : float Volume of the container. @@ -256,9 +266,7 @@ class _CubicDomain(_Domain): @property def limits(self): if self._limits is None: - xlim = self.length/2 - self.particle_radius - self._limits = [[x - xlim for x in self.center], - [x + xlim for x in self.center]] + self._limits = [self.length/2 - self.particle_radius] return self._limits @property @@ -284,9 +292,14 @@ class _CubicDomain(_Domain): self._limits = limits def random_point(self): - return [uniform(self.limits[0][0], self.limits[1][0]), - uniform(self.limits[0][1], self.limits[1][1]), - uniform(self.limits[0][2], self.limits[1][2])] + x_max = self.limits[0] + return [uniform(-x_max, x_max), + uniform(-x_max, x_max), + uniform(-x_max, x_max)] + + def enforce_boundary(self, particle): + x_max = self.limits[0] + particle[:] = np.clip(particle, -x_max, x_max) class _CylindricalDomain(_Domain): @@ -317,8 +330,8 @@ class _CylindricalDomain(_Domain): Length in x-, y-, and z- directions of each cell in mesh overlaid on domain. limits : list of float - Minimum and maximum position in x-, y-, and z-directions where particle - center can be placed. + Maximum radial distance and maximum distance from center in z-direction + where particle center can be placed. volume : float Volume of the container. @@ -340,12 +353,8 @@ class _CylindricalDomain(_Domain): @property def limits(self): if self._limits is None: - xlim = self.length/2 - self.particle_radius - rlim = self.radius - self.particle_radius - self._limits = [[self.center[0] - rlim, self.center[1] - rlim, - self.center[2] - xlim], - [self.center[0] + rlim, self.center[1] + rlim, - self.center[2] + xlim]] + self._limits = [self.radius - self.particle_radius, + self.length/2 - self.particle_radius] return self._limits @property @@ -377,10 +386,20 @@ class _CylindricalDomain(_Domain): self._limits = limits def random_point(self): - r = sqrt(uniform(0, (self.radius - self.particle_radius)**2)) + r_max = self.limits[0] + z_max = self.limits[1] + r = sqrt(uniform(0, r_max**2)) t = uniform(0, 2*pi) - return [r*cos(t) + self.center[0], r*sin(t) + self.center[1], - uniform(self.limits[0][2], self.limits[1][2])] + return [r*cos(t), r*sin(t), uniform(-z_max, z_max)] + + def enforce_boundary(self, particle): + r_max = self.limits[0] + z_max = self.limits[1] + d = sqrt(particle[0]**2 + particle[1]**2) + if d > r_max: + particle[0] *= r_max / d + particle[1] *= r_max / d + particle[2] = np.clip(particle[2], -z_max, z_max) class _SphericalDomain(_Domain): @@ -407,8 +426,7 @@ class _SphericalDomain(_Domain): Length in x-, y-, and z- directions of each cell in mesh overlaid on domain. limits : list of float - Minimum and maximum position in x-, y-, and z-directions where particle - center can be placed. + Maximum radial distance where particle center can be placed. volume : float Volume of the container. @@ -425,9 +443,7 @@ class _SphericalDomain(_Domain): @property def limits(self): if self._limits is None: - rlim = self.radius - self.particle_radius - self._limits = [[x - rlim for x in self.center], - [x + rlim for x in self.center]] + self._limits = [self.radius - self.particle_radius] return self._limits @property @@ -453,10 +469,18 @@ class _SphericalDomain(_Domain): self._limits = limits def random_point(self): + r_max = self.limits[0] x = (gauss(0, 1), gauss(0, 1), gauss(0, 1)) - r = (uniform(0, (self.radius - self.particle_radius)**3)**(1/3) / - sqrt(x[0]**2 + x[1]**2 + x[2]**2)) - return [r*x[i] + self.center[i] for i in range(3)] + r = (uniform(0, r_max**3)**(1/3) / sqrt(x[0]**2 + x[1]**2 + x[2]**2)) + return [r*s for s in x] + + def enforce_boundary(self, particle): + r_max = self.limits[0] + d = sqrt(particle[0]**2 + particle[1]**2 + particle[2]**2) + if d > r_max: + particle[0] *= r_max / d + particle[1] *= r_max / d + particle[2] *= r_max / d def create_triso_lattice(trisos, lower_left, pitch, shape, background): @@ -767,9 +791,9 @@ def _close_random_pack(domain, particles, contraction_rate): particles[i] += r*v particles[j] -= r*v - # Apply reflective boundary conditions - particles[i] = particles[i].clip(domain.limits[0], domain.limits[1]) - particles[j] = particles[j].clip(domain.limits[0], domain.limits[1]) + # Apply boundary conditions + domain.enforce_boundary(particles[i]) + domain.enforce_boundary(particles[j]) update_mesh(i) update_mesh(j) @@ -1008,8 +1032,7 @@ def pack_trisos(radius, fill, domain_shape='cylinder', domain_length=None, # Recalculate the limits for the initial random sequential packing using # the desired final particle radius to ensure particles are fully contained # within the domain during the close random pack - domain.limits = [[x - initial_radius + radius for x in domain.limits[0]], - [x + initial_radius - radius for x in domain.limits[1]]] + domain.limits = [x + initial_radius - radius for x in domain.limits] # Generate non-overlapping particles for an initial inner radius using # random sequential packing algorithm @@ -1024,5 +1047,5 @@ def pack_trisos(radius, fill, domain_shape='cylinder', domain_length=None, trisos = [] for p in particles: - trisos.append(TRISO(radius, fill, p)) + trisos.append(TRISO(radius, fill, [x + c for x, c in zip(p, domain.center)])) return trisos From 00cb9149082e274dedacade9a0b171b467ab004c Mon Sep 17 00:00:00 2001 From: amandalund Date: Mon, 26 Mar 2018 17:14:26 -0500 Subject: [PATCH 134/361] Add unit tests for TRISO --- openmc/model/triso.py | 213 ++++++++++++++------------- tests/unit_tests/test_model_triso.py | 75 ++++++++++ 2 files changed, 187 insertions(+), 101 deletions(-) create mode 100644 tests/unit_tests/test_model_triso.py diff --git a/openmc/model/triso.py b/openmc/model/triso.py index b5ee74f90..77488b891 100644 --- a/openmc/model/triso.py +++ b/openmc/model/triso.py @@ -209,14 +209,21 @@ class _Domain(metaclass=ABCMeta): pass @abstractmethod - def enforce_boundary(self, particle): - """Update the position of the particle if necessary to ensure that the - particle remains entirely within the domain. + def repel_particles(self, p, q, d, d_new): + """Move particles p and q apart according to the following + transformation (accounting for boundary conditions on domain): + + r_i^(n+1) = r_i^(n) + 1/2(d_out^(n+1) - d^(n)) + r_j^(n+1) = r_j^(n) - 1/2(d_out^(n+1) - d^(n)) Parameters ---------- - particle : numpy.ndarray + p, q : numpy.ndarray Cartesian coordinates of particle center. + d : float + distance between centers of particles i and j. + d_new : float + final distance between centers of particles i and j. """ pass @@ -297,9 +304,20 @@ class _CubicDomain(_Domain): uniform(-x_max, x_max), uniform(-x_max, x_max)] - def enforce_boundary(self, particle): + def repel_particles(self, p, q, d, d_new): + # Moving each particle distance 'r' away from the other along the line + # joining the particle centers will ensure their final distance is + # equal to the outer diameter + r = (d_new - d)/2 + + v = (p - q)/d + p += r*v + q -= r*v + + # Apply boundary conditions x_max = self.limits[0] - particle[:] = np.clip(particle, -x_max, x_max) + p[:] = np.clip(p, -x_max, x_max) + q[:] = np.clip(q, -x_max, x_max) class _CylindricalDomain(_Domain): @@ -392,14 +410,29 @@ class _CylindricalDomain(_Domain): t = uniform(0, 2*pi) return [r*cos(t), r*sin(t), uniform(-z_max, z_max)] - def enforce_boundary(self, particle): + def repel_particles(self, p, q, d, d_new): + # Moving each particle distance 'r' away from the other along the line + # joining the particle centers will ensure their final distance is + # equal to the outer diameter + r = (d_new - d)/2 + + v = (p - q)/d + p += r*v + q -= r*v + + # Apply boundary conditions r_max = self.limits[0] z_max = self.limits[1] - d = sqrt(particle[0]**2 + particle[1]**2) + + d = sqrt(p[0]**2 + p[1]**2) if d > r_max: - particle[0] *= r_max / d - particle[1] *= r_max / d - particle[2] = np.clip(particle[2], -z_max, z_max) + p[0:2] *= r_max/d + p[2] = np.clip(p[2], -z_max, z_max) + + d = sqrt(q[0]**2 + q[1]**2) + if d > r_max: + q[0:2] *= r_max/d + q[2] = np.clip(q[2], -z_max, z_max) class _SphericalDomain(_Domain): @@ -474,13 +507,26 @@ class _SphericalDomain(_Domain): r = (uniform(0, r_max**3)**(1/3) / sqrt(x[0]**2 + x[1]**2 + x[2]**2)) return [r*s for s in x] - def enforce_boundary(self, particle): + def repel_particles(self, p, q, d, d_new): + # Moving each particle distance 'r' away from the other along the line + # joining the particle centers will ensure their final distance is + # equal to the outer diameter + r = (d_new - d)/2 + + v = (p - q)/d + p += r*v + q -= r*v + + # Apply boundary conditions r_max = self.limits[0] - d = sqrt(particle[0]**2 + particle[1]**2 + particle[2]**2) + + d = sqrt(p[0]**2 + p[1]**2 + p[2]**2) if d > r_max: - particle[0] *= r_max / d - particle[1] *= r_max / d - particle[2] *= r_max / d + p *= r_max/d + + d = sqrt(q[0]**2 + q[1]**2 + q[2]**2) + if d > r_max: + q *= r_max/d def create_triso_lattice(trisos, lower_left, pitch, shape, background): @@ -711,13 +757,8 @@ def _close_random_pack(domain, particles, contraction_rate): for d, i, j in r: add_rod(d, i, j) - # Inner diameter is set initially to the shortest center-to-center - # distance between any two particles - if rods: - inner_diameter[0] = rods[0][0] - - def update_mesh(i): - """Update which mesh cells the particle is in based on new particle + def update_mesh(indices): + """Update which mesh cells the particles are in based on new particle center coordinates. 'mesh'/'mesh_map' is a two way dictionary used to look up which @@ -727,22 +768,23 @@ def _close_random_pack(domain, particles, contraction_rate): Parameters ---------- - i : int - Index of particle in particles array. + indices : List of int + Indices of particles in particles array. """ - # Determine which mesh cells the particle is in and remove the - # particle id from those cells - for idx in mesh_map[i]: - mesh[idx].remove(i) - del mesh_map[i] + for i in indices: + # Determine which mesh cells the particle is in and remove the + # particle id from those cells + for idx in mesh_map[i]: + mesh[idx].remove(i) + del mesh_map[i] - # Determine which mesh cells are within one diameter of particle's - # center and add this particle to the list of particles in those cells - for idx in domain.nearby_mesh_cells(particles[i]): - mesh[idx].add(i) - mesh_map[i].add(idx) + # Determine which mesh cells are within one diameter of particle's + # center and add this particle to the list of particles in those cells + for idx in domain.nearby_mesh_cells(particles[i]): + mesh[idx].add(i) + mesh_map[i].add(idx) def reduce_outer_diameter(): """Reduce the outer diameter so that at the (i+1)-st iteration it is: @@ -753,50 +795,19 @@ def _close_random_pack(domain, particles, contraction_rate): j = floor(-log10(pf_out - pf_in)). + Returns + ------- + float + New outer diameter + """ - inner_pf = (4/3 * pi * (inner_diameter[0]/2)**3 * n_particles / - domain.volume) - outer_pf = (4/3 * pi * (outer_diameter[0]/2)**3 * n_particles / - domain.volume) + inner_pf = 4/3*pi*(inner_diameter/2)**3*n_particles/domain.volume + outer_pf = 4/3*pi*(outer_diameter/2)**3*n_particles/domain.volume j = floor(-log10(outer_pf - inner_pf)) - outer_diameter[0] = (outer_diameter[0] - 0.5**j * contraction_rate * - initial_outer_diameter / n_particles) - - - def repel_particles(i, j, d): - """Move particles p and q apart according to the following - transformation (accounting for reflective boundary conditions on - domain): - - r_i^(n+1) = r_i^(n) + 1/2(d_out^(n+1) - d^(n)) - r_j^(n+1) = r_j^(n) - 1/2(d_out^(n+1) - d^(n)) - - Parameters - ---------- - i, j : int - Index of particles in particles array. - d : float - distance between centers of particles i and j. - - """ - - # Moving each particle distance 'r' away from the other along the line - # joining the particle centers will ensure their final distance is equal - # to the outer diameter - r = (outer_diameter[0] - d)/2 - - v = (particles[i] - particles[j])/d - particles[i] += r*v - particles[j] -= r*v - - # Apply boundary conditions - domain.enforce_boundary(particles[i]) - domain.enforce_boundary(particles[j]) - - update_mesh(i) - update_mesh(j) + return (outer_diameter - 0.5**j * contraction_rate * + initial_outer_diameter / n_particles) def nearest(i): """Find index of nearest neighbor of particle i. @@ -827,33 +838,25 @@ def _close_random_pack(domain, particles, contraction_rate): else: return None, None - def update_rod_list(i, j): - """Update the rod list with the new nearest neighbors of particles i - and j since their overlap was eliminated. + def update_rod_list(indices): + """Update the rod list with the new nearest neighbors of particles in + list since their overlap was eliminated. Parameters ---------- - i, j : int - Index of particles in particles array. + indices : List of int + Indices of particles in particles array. """ # If the nearest neighbor k of particle i has no nearer neighbors, # remove the rod currently containing k from the rod list and add rod # k-i, keeping the rod list sorted - k, d_ik = nearest(i) - if k and nearest(k)[0] == i: - remove_rod(k) - add_rod(d_ik, i, k) - l, d_jl = nearest(j) - if l and nearest(l)[0] == j: - remove_rod(l) - add_rod(d_jl, j, l) - - # Set inner diameter to the shortest distance between two particle - # centers - if rods: - inner_diameter[0] = rods[0][0] + for i in indices: + k, d_ik = nearest(i) + if k and nearest(k)[0] == i: + remove_rod(k) + add_rod(d_ik, i, k) n_particles = len(particles) diameter = 2*domain.particle_radius @@ -865,8 +868,8 @@ def _close_random_pack(domain, particles, contraction_rate): initial_outer_diameter = 2*(domain.volume/(n_particles*4/3*pi))**(1/3) # Inner and outer diameter of particles will change during packing - outer_diameter = [initial_outer_diameter] - inner_diameter = [0] + outer_diameter = initial_outer_diameter + inner_diameter = 0. rods = [] rods_map = {} @@ -880,14 +883,22 @@ def _close_random_pack(domain, particles, contraction_rate): while True: create_rod_list() - if inner_diameter[0] >= diameter: + if rods: + # Set inner diameter to the shortest center-to-center distance + # between any two particles + inner_diameter = rods[0][0] + if inner_diameter >= diameter: break while True: d, i, j = pop_rod() - reduce_outer_diameter() - repel_particles(i, j, d) - update_rod_list(i, j) - if inner_diameter[0] >= diameter or not rods: + outer_diameter = reduce_outer_diameter() + domain.repel_particles(particles[i], particles[j], d, outer_diameter) + update_mesh([i, j]) + update_rod_list([i, j]) + if not rods: + break + inner_diameter = rods[0][0] + if inner_diameter >= diameter: break @@ -957,7 +968,7 @@ def pack_trisos(radius, fill, domain_shape='cylinder', domain_length=None, to speed up the nearest neighbor search by only searching for a particle's neighbors within that mesh cell. - In CRP, each particle is assigned two diameters, and inner and an outer, + In CRP, each particle is assigned two diameters, an inner and an outer, which approach each other during the simulation. The inner diameter, defined as the minimum center-to-center distance, is the true diameter of the particles and defines the pf. At each iteration the worst overlap diff --git a/tests/unit_tests/test_model_triso.py b/tests/unit_tests/test_model_triso.py new file mode 100644 index 000000000..c6f717d0b --- /dev/null +++ b/tests/unit_tests/test_model_triso.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +from math import pi + +import numpy as np +from numpy.linalg import norm +import openmc +import openmc.model +import pytest +import scipy.spatial + + +_PACKING_FRACTION = 0.35 +_RADIUS = 1. +_PARAMS = [{'shape' : 'cube', 'length' : 20., 'radius' : 0.}, + {'shape' : 'cylinder', 'length' : 10., 'radius' : 10.}, + {'shape' : 'sphere', 'length' : 0., 'radius' : 10.}] + + +@pytest.fixture(scope='module', params=_PARAMS) +def domain(request): + return request.param + + +@pytest.fixture(scope='module') +def trisos(domain): + return openmc.model.pack_trisos( + radius=_RADIUS, + fill=openmc.Universe(), + domain_shape=domain['shape'], + domain_length=domain['length'], + domain_radius=domain['radius'], + domain_center=[0., 0., 0.], + initial_packing_fraction=0.2, + packing_fraction=_PACKING_FRACTION + ) + + +def test_overlap(trisos): + """Check that no TRISO particles overlap.""" + tree = scipy.spatial.cKDTree([t.center for t in trisos]) + r = min(tree.query([t.center for t in trisos], k=2)[0][:,1])/2 + assert r > _RADIUS or r == pytest.approx(_RADIUS) + + +def test_contained(trisos, domain): + """Make sure all particles are entirely contained within the domain.""" + if domain['shape'] == 'cube': + x = 2*(max(np.hstack([abs(t.center) for t in trisos])) + _RADIUS) + assert x < domain['length'] or x == pytest.approx(domain['length']) + + elif domain['shape'] == 'cylinder': + r = max([norm(t.center[0:2]) for t in trisos]) + _RADIUS + z = 2*(max([abs(t.center[2]) for t in trisos]) + _RADIUS) + assert r < domain['radius'] or r == pytest.approx(domain['radius']) + assert z < domain['length'] or z == pytest.approx(domain['length']) + + elif domain['shape'] == 'sphere': + r = max([norm(t.center) for t in trisos]) + _RADIUS + assert r < domain['radius'] or r == pytest.approx(domain['radius']) + + +def test_packing_fraction(trisos, domain): + """Check that the actual PF is close to the requested PF.""" + if domain['shape'] == 'cube': + volume = domain['length']**3 + + elif domain['shape'] == 'cylinder': + volume = domain['length']*pi*domain['radius']**2 + + elif domain['shape'] == 'sphere': + volume = 4/3*pi*domain['radius']**3 + + pf = len(trisos)*4/3*pi*_RADIUS**3/volume + assert pf == pytest.approx(_PACKING_FRACTION, rel=1e-2) From 272d0a4b2be05277a9541099796c0f2af38804d9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 5 Sep 2017 10:14:46 -0500 Subject: [PATCH 135/361] Implement Legendre expansion filter for change in scattering angle --- CMakeLists.txt | 1 + docs/source/pythonapi/base.rst | 1 + openmc/__init__.py | 1 + openmc/filter_legendre.py | 131 ++++++++++++++++++++++++++ src/constants.F90 | 5 +- src/tallies/tally_filter.F90 | 5 + src/tallies/tally_filter_legendre.F90 | 86 +++++++++++++++++ src/tallies/tally_header.F90 | 3 + 8 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 openmc/filter_legendre.py create mode 100644 src/tallies/tally_filter_legendre.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9517fe2ec..bb3575c9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -420,6 +420,7 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/tally_filter_distribcell.F90 src/tallies/tally_filter_energy.F90 src/tallies/tally_filter_energyfunc.F90 + src/tallies/tally_filter_legendre.F90 src/tallies/tally_filter_material.F90 src/tallies/tally_filter_mesh.F90 src/tallies/tally_filter_meshsurface.F90 diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index 070b5bd20..84d2a39da 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -118,6 +118,7 @@ Constructing Tallies openmc.DistribcellFilter openmc.DelayedGroupFilter openmc.EnergyFunctionFilter + openmc.LegendreFilter openmc.Mesh openmc.Trigger openmc.TallyDerivative diff --git a/openmc/__init__.py b/openmc/__init__.py index 9b13fa692..243796a13 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -15,6 +15,7 @@ from openmc.surface import * from openmc.universe import * from openmc.lattice import * from openmc.filter import * +from openmc.filter_legendre import * from openmc.trigger import * from openmc.tally_derivative import * from openmc.tallies import * diff --git a/openmc/filter_legendre.py b/openmc/filter_legendre.py new file mode 100644 index 000000000..075fa02db --- /dev/null +++ b/openmc/filter_legendre.py @@ -0,0 +1,131 @@ +from numbers import Integral +from xml.etree import ElementTree as ET + +import numpy as np +import pandas as pd + +import openmc.checkvalue as cv +from . import Filter + + +class LegendreFilter(Filter): + r"""Score Legendre expansion moments up to specified order. + + This filter allows scores to be multiplied by Legendre polynomials of the + change in particle angle ($\mu$) up to a user-specified order. + + Parameters + ---------- + order : int + Maximum Legendre polynomial order + filter_id : int or None + Unique identifier for the filter + + Attributes + ---------- + order : int + Maximum Legendre polynomial order + id : int + Unique identifier for the filter + num_bins : int + The number of filter bins + + """ + + def __init__(self, order, filter_id=None): + self.order = order + self.id = filter_id + + def __hash__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + return hash(string) + + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tID', self.id) + return string + + @property + def order(self): + return self._order + + @order.setter + def order(self, order): + cv.check_type('Legendre order', order, Integral) + cv.check_greater_than('Legendre order', order, 0, equality=True) + self._order = order + + @property + def num_bins(self): + return self._order + 1 + + @classmethod + def from_hdf5(cls, group, **kwargs): + if group['type'].value.decode() != cls.short_name.lower(): + raise ValueError("Expected HDF5 data for filter type '" + + cls.short_name.lower() + "' but got '" + + group['type'].value.decode() + " instead") + + filter_id = int(group.name.split('/')[-1].lstrip('filter ')) + + out = cls(group['order'].value, filter_id) + + return out + + 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 + columns annotated by filter bin information. This is a helper method for + :meth:`Tally.get_pandas_dataframe`. + + Parameters + ---------- + data_size : Integral + The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter + + Returns + ------- + pandas.DataFrame + A Pandas DataFrame with a column that is filled with strings + indicating Legendre orders. The number of rows in the DataFrame is + the same as the total number of bins in the corresponding tally. + + See also + -------- + Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() + + """ + # Initialize Pandas DataFrame + df = pd.DataFrame() + + bins = np.array(['P{}'.format(i) for i in range(self.order + 1)]) + filter_bins = np.repeat(bins, stride) + tile_factor = data_size // len(filter_bins) + filter_bins = np.tile(filter_bins, tile_factor) + df = pd.concat([df, pd.DataFrame( + {self.short_name.lower(): filter_bins})]) + + return df + + def to_xml_element(self): + """Return XML Element representing the filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing Legendre filter data + + """ + element = ET.Element('filter') + element.set('id', str(self.id)) + element.set('type', self.short_name.lower()) + + subelement = ET.SubElement(element, 'order') + subelement.text = str(self.order) + + return element diff --git a/src/constants.F90 b/src/constants.F90 index 0e138f0ee..29b68ae1d 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -358,7 +358,7 @@ module constants integer, parameter :: NO_BIN_FOUND = -1 ! Tally filter and map types - integer, parameter :: N_FILTER_TYPES = 16 + integer, parameter :: N_FILTER_TYPES = 17 integer, parameter :: & FILTER_UNIVERSE = 1, & FILTER_MATERIAL = 2, & @@ -375,7 +375,8 @@ module constants FILTER_DELAYEDGROUP = 13, & FILTER_ENERGYFUNCTION = 14, & FILTER_CELLFROM = 15, & - FILTER_MESHSURFACE = 16 + FILTER_MESHSURFACE = 16, & + FILTER_LEGENDRE = 17 ! Mesh types integer, parameter :: & diff --git a/src/tallies/tally_filter.F90 b/src/tallies/tally_filter.F90 index 3d6eaaef2..056fc198a 100644 --- a/src/tallies/tally_filter.F90 +++ b/src/tallies/tally_filter.F90 @@ -17,6 +17,7 @@ module tally_filter use tally_filter_distribcell use tally_filter_energy use tally_filter_energyfunc + use tally_filter_legendre use tally_filter_material use tally_filter_mesh use tally_filter_meshsurface @@ -64,6 +65,8 @@ contains type_ = 'energyout' type is (EnergyFunctionFilter) type_ = 'energyfunction' + type is (LegendreFilter) + type_ = 'legendre' type is (MaterialFilter) type_ = 'material' type is (MeshFilter) @@ -135,6 +138,8 @@ contains allocate(EnergyoutFilter :: filters(index) % obj) case ('energyfunction') allocate(EnergyFunctionFilter :: filters(index) % obj) + case ('legendre') + allocate(LegendreFilter :: filters(index) % obj) case ('material') allocate(MaterialFilter :: filters(index) % obj) case ('mesh') diff --git a/src/tallies/tally_filter_legendre.F90 b/src/tallies/tally_filter_legendre.F90 new file mode 100644 index 000000000..59fb57a72 --- /dev/null +++ b/src/tallies/tally_filter_legendre.F90 @@ -0,0 +1,86 @@ +module tally_filter_legendre + + use, intrinsic :: ISO_C_BINDING + + use hdf5, only: HID_T + + use constants + use error + use hdf5_interface + use math, only: calc_pn + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! LEGENDREFILTER gives Legendre moments of the change in scattering angle +!=============================================================================== + + type, public, extends(TallyFilter) :: LegendreFilter + integer :: order + contains + procedure :: from_xml + procedure :: get_all_bins + procedure :: to_statepoint + procedure :: text_label + end type LegendreFilter + +contains + +!=============================================================================== +! LegendreFilter methods +!=============================================================================== + + subroutine from_xml(this, node) + class(LegendreFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + ! Get specified order + call get_node_value(node, "order", this % order) + this % n_bins = this % order + 1 + end subroutine from_xml + + subroutine get_all_bins(this, p, estimator, match) + class(LegendreFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: i + real(8) :: wgt + + ! TODO: Use recursive formula to calculate higher orders + do i = 0, this % order + wgt = calc_pn(i, p % mu) + call match % bins % push_back(i + 1) + call match % weights % push_back(wgt) + end do + end subroutine get_all_bins + + subroutine to_statepoint(this, filter_group) + class(LegendreFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "legendre") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "order", this % order) + end subroutine to_statepoint + + function text_label(this, bin) result(label) + class(LegendreFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Legendre expansion order " // trim(to_str(bin - 1)) + end function text_label + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + +end module tally_filter_legendre diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 0495ce14d..1f221c83b 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -354,6 +354,9 @@ contains j = FILTER_AZIMUTHAL type is (EnergyFunctionFilter) j = FILTER_ENERGYFUNCTION + type is (LegendreFilter) + j = FILTER_LEGENDRE + this % estimator = ESTIMATOR_ANALOG end select this % find_filter(j) = i end do From 15df889c6621e83718e8945bc918e2e89d5271e5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 5 Sep 2017 14:12:39 -0500 Subject: [PATCH 136/361] Implement spherical harmonics expansion filter --- CMakeLists.txt | 1 + openmc/__init__.py | 1 + openmc/filter_spherical_harmonics.py | 150 ++++++++++++++++++++++++++ src/constants.F90 | 5 +- src/tallies/tally_filter.F90 | 5 + src/tallies/tally_filter_legendre.F90 | 2 +- src/tallies/tally_filter_sph_harm.F90 | 135 +++++++++++++++++++++++ src/tallies/tally_header.F90 | 2 + 8 files changed, 298 insertions(+), 3 deletions(-) create mode 100644 openmc/filter_spherical_harmonics.py create mode 100644 src/tallies/tally_filter_sph_harm.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index bb3575c9d..654498002 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -426,6 +426,7 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/tally_filter_meshsurface.F90 src/tallies/tally_filter_mu.F90 src/tallies/tally_filter_polar.F90 + src/tallies/tally_filter_sph_harm.F90 src/tallies/tally_filter_surface.F90 src/tallies/tally_filter_universe.F90 src/tallies/tally_header.F90 diff --git a/openmc/__init__.py b/openmc/__init__.py index 243796a13..a23b406c9 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -16,6 +16,7 @@ from openmc.universe import * from openmc.lattice import * from openmc.filter import * from openmc.filter_legendre import * +from openmc.filter_spherical_harmonics import * from openmc.trigger import * from openmc.tally_derivative import * from openmc.tallies import * diff --git a/openmc/filter_spherical_harmonics.py b/openmc/filter_spherical_harmonics.py new file mode 100644 index 000000000..3b2e32b14 --- /dev/null +++ b/openmc/filter_spherical_harmonics.py @@ -0,0 +1,150 @@ +from numbers import Integral +from xml.etree import ElementTree as ET + +import numpy as np +import pandas as pd + +import openmc.checkvalue as cv +from . import Filter + + +class SphericalHarmonicsFilter(Filter): + r"""Score spherical harmonic expansion moments up to specified order. + + Parameters + ---------- + order : int + Maximum spherical harmonics order + filter_id : int or None + Unique identifier for the filter + + Attributes + ---------- + order : int + Maximum spherical harmonics order + id : int + Unique identifier for the filter + cosine : {'scatter', 'particle'} + How to handle the cosine term. + num_bins : int + The number of filter bins + + """ + + def __init__(self, order, filter_id=None): + self.order = order + self.id = filter_id + self._cosine = 'particle' + + def __hash__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tCosine', self.cosine) + return hash(string) + + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tCosine', self.cosine) + string += '{: <16}=\t{}\n'.format('\tID', self.id) + return string + + @property + def order(self): + return self._order + + @order.setter + def order(self, order): + cv.check_type('spherical harmonics order', order, Integral) + cv.check_greater_than('spherical harmonics order', order, 0, equality=True) + self._order = order + + @property + def cosine(self): + return self._cosine + + @cosine.setter + def cosine(self, cosine): + cv.check_value('Spherical harmonics cosine treatment', cosine, + ('scatter', 'particle')) + self._cosine = cosine + + @property + def num_bins(self): + return (self._order + 1)**2 + + @classmethod + def from_hdf5(cls, group, **kwargs): + if group['type'].value.decode() != cls.short_name.lower(): + raise ValueError("Expected HDF5 data for filter type '" + + cls.short_name.lower() + "' but got '" + + group['type'].value.decode() + " instead") + + filter_id = int(group.name.split('/')[-1].lstrip('filter ')) + + out = cls(group['order'].value, filter_id) + out.cosine = group['cosine'].value.decode() + + return out + + def 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 + columns annotated by filter bin information. This is a helper method for + :meth:`Tally.get_pandas_dataframe`. + + Parameters + ---------- + data_size : Integral + The total number of bins in the tally corresponding to this filter + stride : int + Stride in memory for the filter + + Returns + ------- + pandas.DataFrame + A Pandas DataFrame with a column that is filled with strings + indicating spherical harmonics orders. The number of rows in the + DataFrame is the same as the total number of bins in the + corresponding tally. + + See also + -------- + Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() + + """ + # Initialize Pandas DataFrame + df = pd.DataFrame() + + bins = [] + for n in range(self.order + 1): + bins.extend('Y{},{}'.format(n, m) for m in range(-n, n + 1)) + bins = np.array(bins) + + filter_bins = np.repeat(bins, stride) + tile_factor = data_size // len(filter_bins) + filter_bins = np.tile(filter_bins, tile_factor) + df = pd.concat([df, pd.DataFrame( + {self.short_name.lower(): filter_bins})]) + + return df + + def to_xml_element(self): + """Return XML Element representing the filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing spherical harmonics filter data + + """ + element = ET.Element('filter') + element.set('id', str(self.id)) + element.set('type', self.short_name.lower()) + element.set('cosine', self.cosine) + + subelement = ET.SubElement(element, 'order') + subelement.text = str(self.order) + + return element diff --git a/src/constants.F90 b/src/constants.F90 index 29b68ae1d..623b081eb 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -358,7 +358,7 @@ module constants integer, parameter :: NO_BIN_FOUND = -1 ! Tally filter and map types - integer, parameter :: N_FILTER_TYPES = 17 + integer, parameter :: N_FILTER_TYPES = 18 integer, parameter :: & FILTER_UNIVERSE = 1, & FILTER_MATERIAL = 2, & @@ -376,7 +376,8 @@ module constants FILTER_ENERGYFUNCTION = 14, & FILTER_CELLFROM = 15, & FILTER_MESHSURFACE = 16, & - FILTER_LEGENDRE = 17 + FILTER_LEGENDRE = 17, & + FILTER_SPH_HARMONICS = 18 ! Mesh types integer, parameter :: & diff --git a/src/tallies/tally_filter.F90 b/src/tallies/tally_filter.F90 index 056fc198a..d2ae63aea 100644 --- a/src/tallies/tally_filter.F90 +++ b/src/tallies/tally_filter.F90 @@ -23,6 +23,7 @@ module tally_filter use tally_filter_meshsurface use tally_filter_mu use tally_filter_polar + use tally_filter_sph_harm use tally_filter_surface use tally_filter_universe @@ -77,6 +78,8 @@ contains type_ = 'mu' type is (PolarFilter) type_ = 'polar' + type is (SphericalHarmonicsFilter) + type_ = 'sphericalharmonics' type is (SurfaceFilter) type_ = 'surface' type is (UniverseFilter) @@ -150,6 +153,8 @@ contains allocate(MuFilter :: filters(index) % obj) case ('polar') allocate(PolarFilter :: filters(index) % obj) + case ('sphericalharmonics') + allocate(SphericalHarmonicsFilter :: filters(index) % obj) case ('surface') allocate(SurfaceFilter :: filters(index) % obj) case ('universe') diff --git a/src/tallies/tally_filter_legendre.F90 b/src/tallies/tally_filter_legendre.F90 index 59fb57a72..a97ed7faf 100644 --- a/src/tallies/tally_filter_legendre.F90 +++ b/src/tallies/tally_filter_legendre.F90 @@ -75,7 +75,7 @@ contains integer, intent(in) :: bin character(MAX_LINE_LEN) :: label - label = "Legendre expansion order " // trim(to_str(bin - 1)) + label = "Legendre expansion, P" // trim(to_str(bin - 1)) end function text_label !=============================================================================== diff --git a/src/tallies/tally_filter_sph_harm.F90 b/src/tallies/tally_filter_sph_harm.F90 new file mode 100644 index 000000000..9ccfde422 --- /dev/null +++ b/src/tallies/tally_filter_sph_harm.F90 @@ -0,0 +1,135 @@ +module tally_filter_sph_harm + + use, intrinsic :: ISO_C_BINDING + + use hdf5, only: HID_T + + use constants + use error + use hdf5_interface + use math, only: calc_pn, calc_rn + use particle_header, only: Particle + use string, only: to_str, to_lower + use tally_filter_header + use xml_interface + + implicit none + private + + integer, parameter :: COSINE_SCATTER = 1 + integer, parameter :: COSINE_PARTICLE = 2 + +!=============================================================================== +! LEGENDREFILTER gives Legendre moments of the change in scattering angle +!=============================================================================== + + type, public, extends(TallyFilter) :: SphericalHarmonicsFilter + integer :: order + integer :: cosine + contains + procedure :: from_xml + procedure :: get_all_bins + procedure :: to_statepoint + procedure :: text_label + end type SphericalHarmonicsFilter + +contains + +!=============================================================================== +! SphericalHarmonicsFilter methods +!=============================================================================== + + subroutine from_xml(this, node) + class(SphericalHarmonicsFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + character(MAX_WORD_LEN) :: temp_str + + ! Get specified order + call get_node_value(node, "order", this % order) + this % n_bins = (this % order + 1)**2 + + ! Determine how cosine term is to be treated + if (check_for_node(node, "cosine")) then + call get_node_value(node, "cosine", temp_str) + select case (to_lower(temp_str)) + case ('scatter') + this % cosine = COSINE_SCATTER + case ('particle') + this % cosine = COSINE_PARTICLE + end select + else + this % cosine = COSINE_PARTICLE + end if + end subroutine from_xml + + subroutine get_all_bins(this, p, estimator, match) + class(SphericalHarmonicsFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: i, j, n + integer :: num_nm + real(8) :: wgt + real(8) :: rn(2*this % order + 1) + + ! TODO: Use recursive formula to calculate higher orders + j = 0 + do n = 0, this % order + ! Determine cosine term for scatter expansion if necessary + if (this % cosine == COSINE_SCATTER) then + wgt = calc_pn(n, p % mu) + else + wgt = ONE + end if + + ! Calculate n-th order spherical harmonics for (u,v,w) + num_nm = 2*n + 1 + rn(1:num_nm) = calc_rn(n, p % last_uvw) + + ! Append matching (bin,weight) for each moment + do i = 1, num_nm + j = j + 1 + call match % bins % push_back(j) + call match % weights % push_back(wgt * rn(i)) + end do + end do + end subroutine get_all_bins + + subroutine to_statepoint(this, filter_group) + class(SphericalHarmonicsFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "sphericalharmonics") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "order", this % order) + if (this % cosine == COSINE_SCATTER) then + call write_dataset(filter_group, "cosine", "scatter") + else + call write_dataset(filter_group, "cosine", "particle") + end if + end subroutine to_statepoint + + function text_label(this, bin) result(label) + class(SphericalHarmonicsFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + integer :: n, m + + do n = 0, this % order + if (bin <= (n + 1)**2) then + m = (bin - n**2 - 1) - n + label = "Spherical harmonic expansion, Y" // trim(to_str(n)) // & + "," // trim(to_str(m)) + exit + end if + end do + end function text_label + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + +end module tally_filter_sph_harm diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 1f221c83b..197687121 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -357,6 +357,8 @@ contains type is (LegendreFilter) j = FILTER_LEGENDRE this % estimator = ESTIMATOR_ANALOG + type is (SphericalHarmonicsFilter) + j = FILTER_SPH_HARMONICS end select this % find_filter(j) = i end do From 1e81139172e2f846f9309eda199b54a02160ae18 Mon Sep 17 00:00:00 2001 From: Brittany Grayson Date: Thu, 4 Jan 2018 15:28:15 -0600 Subject: [PATCH 137/361] Implement spatial Legendre filter --- CMakeLists.txt | 1 + src/constants.F90 | 5 +- src/tallies/tally_filter.F90 | 5 ++ src/tallies/tally_filter_sptl_legendre.F90 | 91 ++++++++++++++++++++++ src/tallies/tally_header.F90 | 2 + 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 src/tallies/tally_filter_sptl_legendre.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 654498002..a0b374259 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -427,6 +427,7 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/tally_filter_mu.F90 src/tallies/tally_filter_polar.F90 src/tallies/tally_filter_sph_harm.F90 + src/tallies/tally_filter_sptl_legendre.F90 src/tallies/tally_filter_surface.F90 src/tallies/tally_filter_universe.F90 src/tallies/tally_header.F90 diff --git a/src/constants.F90 b/src/constants.F90 index 623b081eb..b7f776fe8 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -358,7 +358,7 @@ module constants integer, parameter :: NO_BIN_FOUND = -1 ! Tally filter and map types - integer, parameter :: N_FILTER_TYPES = 18 + integer, parameter :: N_FILTER_TYPES = 19 integer, parameter :: & FILTER_UNIVERSE = 1, & FILTER_MATERIAL = 2, & @@ -377,7 +377,8 @@ module constants FILTER_CELLFROM = 15, & FILTER_MESHSURFACE = 16, & FILTER_LEGENDRE = 17, & - FILTER_SPH_HARMONICS = 18 + FILTER_SPH_HARMONICS = 18, & + FILTER_SPTL_LEGENDRE = 19 ! Mesh types integer, parameter :: & diff --git a/src/tallies/tally_filter.F90 b/src/tallies/tally_filter.F90 index d2ae63aea..bd40b0417 100644 --- a/src/tallies/tally_filter.F90 +++ b/src/tallies/tally_filter.F90 @@ -24,6 +24,7 @@ module tally_filter use tally_filter_mu use tally_filter_polar use tally_filter_sph_harm + use tally_filter_sptl_legendre use tally_filter_surface use tally_filter_universe @@ -80,6 +81,8 @@ contains type_ = 'polar' type is (SphericalHarmonicsFilter) type_ = 'sphericalharmonics' + type is (SpatialLegendreFilter) + type_ = 'spatiallegendre' type is (SurfaceFilter) type_ = 'surface' type is (UniverseFilter) @@ -155,6 +158,8 @@ contains allocate(PolarFilter :: filters(index) % obj) case ('sphericalharmonics') allocate(SphericalHarmonicsFilter :: filters(index) % obj) + case ('spatiallegendre') + allocate(SpatialLegendreFilter :: filters(index) % obj) case ('surface') allocate(SurfaceFilter :: filters(index) % obj) case ('universe') diff --git a/src/tallies/tally_filter_sptl_legendre.F90 b/src/tallies/tally_filter_sptl_legendre.F90 new file mode 100644 index 000000000..349c28095 --- /dev/null +++ b/src/tallies/tally_filter_sptl_legendre.F90 @@ -0,0 +1,91 @@ +module tally_filter_sptl_legendre + + use, intrinsic :: ISO_C_BINDING + + use hdf5, only: HID_T + + use constants + use error + use hdf5_interface + use math, only: calc_pn + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! SpatialLEGENDREFILTER gives Legendre moments +!=============================================================================== + + type, public, extends(TallyFilter) :: SpatialLegendreFilter + integer :: order + contains + procedure :: from_xml + procedure :: get_all_bins + procedure :: to_statepoint + procedure :: text_label + end type SpatialLegendreFilter + +contains + +!=============================================================================== +! SpatialLegendreFilter methods +!=============================================================================== + + subroutine from_xml(this, node) + class(SpatialLegendreFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + ! Get specified order + call get_node_value(node, "order", this % order) + this % n_bins = this % order + 1 + end subroutine from_xml + + subroutine get_all_bins(this, p, estimator, match) + class(SpatialLegendreFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: i + real(8) :: wgt + real(8) :: leg_x + real(8) :: xmin + real(8) :: xmax + + ! TODO: Use recursive formula to calculate higher orders + do i = 0, this % order + !i is the order, second var is the value normalized between 0 and 1 given by + ! norm_x = 2*(leg_x-xmin)/(xmax-xmin)-1 + wgt = 2.0*(leg_x - xmin)/(xmax - xmin) - 1 + call match % bins % push_back(i + 1) + call match % weights % push_back(wgt) + end do + end subroutine get_all_bins + + subroutine to_statepoint(this, filter_group) + class(SpatialLegendreFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "legendre") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "order", this % order) + end subroutine to_statepoint + + function text_label(this, bin) result(label) + class(SpatialLegendreFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + label = "Legendre expansion, P" // trim(to_str(bin - 1)) + end function text_label + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + +end module tally_filter_sptl_legendre diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 197687121..84dc27e3f 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -359,6 +359,8 @@ contains this % estimator = ESTIMATOR_ANALOG type is (SphericalHarmonicsFilter) j = FILTER_SPH_HARMONICS + type is (SpatialLegendreFilter) + j = FILTER_SPTL_LEGENDRE end select this % find_filter(j) = i end do From 1afbb8d10979e15f2e2267f8b43f6385be58104d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 4 Jan 2018 16:10:06 -0600 Subject: [PATCH 138/361] Fix spatial Legendre filter and add associated Python class --- openmc/__init__.py | 1 + openmc/filter_spatial_legendre.py | 173 +++++++++++++++++++++ src/tallies/tally_filter_sptl_legendre.F90 | 62 ++++++-- 3 files changed, 222 insertions(+), 14 deletions(-) create mode 100644 openmc/filter_spatial_legendre.py diff --git a/openmc/__init__.py b/openmc/__init__.py index a23b406c9..e9dd0bd8c 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -16,6 +16,7 @@ from openmc.universe import * from openmc.lattice import * from openmc.filter import * from openmc.filter_legendre import * +from openmc.filter_spatial_legendre import * from openmc.filter_spherical_harmonics import * from openmc.trigger import * from openmc.tally_derivative import * diff --git a/openmc/filter_spatial_legendre.py b/openmc/filter_spatial_legendre.py new file mode 100644 index 000000000..1ad23666b --- /dev/null +++ b/openmc/filter_spatial_legendre.py @@ -0,0 +1,173 @@ +from numbers import Integral, Real +from xml.etree import ElementTree as ET + +import numpy as np +import pandas as pd + +import openmc.checkvalue as cv +from . import Filter + + +class SpatialLegendreFilter(Filter): + r"""Score Legendre expansion moments in space up to specified order. + + This filter allows scores to be multiplied by Legendre polynomials of the + the particle's position along a particular axis, normalized to a given + range, up to a user-specified order. + + Parameters + ---------- + order : int + Maximum Legendre polynomial order + axis : {'x', 'y', 'z'} + Axis along which to take the expansion + minimum : float + Minimum value along selected axis + maximum : float + Maximum value along selected axis + filter_id : int or None + Unique identifier for the filter + + Attributes + ---------- + order : int + Maximum Legendre polynomial order + id : int + Unique identifier for the filter + num_bins : int + The number of filter bins + + """ + + def __init__(self, order, axis, minimum, maximum, filter_id=None): + self.order = order + self.axis = axis + self.minimum = minimum + self.maximum = maximum + self.id = filter_id + + def __hash__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + return hash(string) + + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tID', self.id) + return string + + @property + def order(self): + return self._order + + @order.setter + def order(self, order): + cv.check_type('Legendre order', order, Integral) + cv.check_greater_than('Legendre order', order, 0, equality=True) + self._order = order + + @property + def axis(self): + return self._axis + + @axis.setter + def axis(self, axis): + cv.check_value('axis', axis, ('x', 'y', 'z')) + self._axis = axis + + @property + def minimum(self): + return self._minimum + + @minimum.setter + def minimum(self, minimum): + cv.check_type('minimum', minimum, Real) + self._minimum = minimum + + @property + def maximum(self): + return self._maximum + + @maximum.setter + def maximum(self, maximum): + cv.check_type('maximum', maximum, Real) + self._maximum = maximum + + @property + def num_bins(self): + return self._order + 1 + + @classmethod + def from_hdf5(cls, group, **kwargs): + if group['type'].value.decode() != cls.short_name.lower(): + raise ValueError("Expected HDF5 data for filter type '" + + cls.short_name.lower() + "' but got '" + + group['type'].value.decode() + " instead") + + filter_id = int(group.name.split('/')[-1].lstrip('filter ')) + order = group['order'].value + axis = group['axis'].value.decode() + min_, max_ = group['min'].value, group['max'].value + + return cls(order, axis, min_, max_, filter_id) + + def 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 + columns annotated by filter bin information. This is a helper method for + :meth:`Tally.get_pandas_dataframe`. + + Parameters + ---------- + data_size : Integral + The total number of bins in the tally corresponding to this filter + + Returns + ------- + pandas.DataFrame + A Pandas DataFrame with a column that is filled with strings + indicating Legendre orders. The number of rows in the DataFrame is + the same as the total number of bins in the corresponding tally. + + See also + -------- + Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() + + """ + # Initialize Pandas DataFrame + df = pd.DataFrame() + + bins = np.array(['P{}'.format(i) for i in range(self.order + 1)]) + filter_bins = np.repeat(bins, stride) + tile_factor = data_size // len(filter_bins) + filter_bins = np.tile(filter_bins, tile_factor) + df = pd.concat([df, pd.DataFrame( + {self.short_name.lower(): filter_bins})]) + + return df + + def to_xml_element(self): + """Return XML Element representing the filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing Legendre filter data + + """ + element = ET.Element('filter') + element.set('id', str(self.id)) + element.set('type', self.short_name.lower()) + + subelement = ET.SubElement(element, 'order') + subelement.text = str(self.order) + subelement = ET.SubElement(element, 'axis') + subelement.text = self.axis + subelement = ET.SubElement(element, 'min') + subelement.text = str(self.minimum) + subelement = ET.SubElement(element, 'max') + subelement.text = str(self.maximum) + + return element diff --git a/src/tallies/tally_filter_sptl_legendre.F90 b/src/tallies/tally_filter_sptl_legendre.F90 index 349c28095..8de87f866 100644 --- a/src/tallies/tally_filter_sptl_legendre.F90 +++ b/src/tallies/tally_filter_sptl_legendre.F90 @@ -9,19 +9,26 @@ module tally_filter_sptl_legendre use hdf5_interface use math, only: calc_pn use particle_header, only: Particle - use string, only: to_str + use string, only: to_str, to_lower use tally_filter_header use xml_interface implicit none private + integer, parameter :: AXIS_X = 1 + integer, parameter :: AXIS_Y = 2 + integer, parameter :: AXIS_Z = 3 + !=============================================================================== ! SpatialLEGENDREFILTER gives Legendre moments !=============================================================================== type, public, extends(TallyFilter) :: SpatialLegendreFilter integer :: order + integer :: axis + real(8) :: min + real(8) :: max contains procedure :: from_xml procedure :: get_all_bins @@ -39,8 +46,22 @@ contains class(SpatialLegendreFilter), intent(inout) :: this type(XMLNode), intent(in) :: node - ! Get specified order + character(MAX_WORD_LEN) :: axis + + ! Get attributes from XML call get_node_value(node, "order", this % order) + call get_node_value(node, "axis", axis) + select case (to_lower(axis)) + case ('x') + this % axis = AXIS_X + case ('y') + this % axis = AXIS_Y + case ('z') + this % axis = AXIS_Z + end select + call get_node_value(node, "min", this % min) + call get_node_value(node, "max", this % max) + this % n_bins = this % order + 1 end subroutine from_xml @@ -52,27 +73,40 @@ contains integer :: i real(8) :: wgt - real(8) :: leg_x - real(8) :: xmin - real(8) :: xmax + real(8) :: x ! Position on specified axis + real(8) :: x_norm ! Normalized position - ! TODO: Use recursive formula to calculate higher orders - do i = 0, this % order - !i is the order, second var is the value normalized between 0 and 1 given by - ! norm_x = 2*(leg_x-xmin)/(xmax-xmin)-1 - wgt = 2.0*(leg_x - xmin)/(xmax - xmin) - 1 - call match % bins % push_back(i + 1) - call match % weights % push_back(wgt) - end do + x = p % coord(1) % xyz(this % axis) + if (this % min <= x .and. x <= this % max) then + ! Calculate normalized position between min and max + x_norm = TWO*(x - this % min)/(this % max - this % min) - ONE + + ! TODO: Use recursive formula to calculate higher orders + do i = 0, this % order + wgt = calc_pn(i, x_norm) + call match % bins % push_back(i + 1) + call match % weights % push_back(wgt) + end do + end if end subroutine get_all_bins subroutine to_statepoint(this, filter_group) class(SpatialLegendreFilter), intent(in) :: this integer(HID_T), intent(in) :: filter_group - call write_dataset(filter_group, "type", "legendre") + call write_dataset(filter_group, "type", "spatiallegendre") call write_dataset(filter_group, "n_bins", this % n_bins) call write_dataset(filter_group, "order", this % order) + select case (this % axis) + case (AXIS_X) + call write_dataset(filter_group, 'axis', 'x') + case (AXIS_Y) + call write_dataset(filter_group, 'axis', 'y') + case (AXIS_Z) + call write_dataset(filter_group, 'axis', 'z') + end select + call write_dataset(filter_group, 'min', this % min) + call write_dataset(filter_group, 'max', this % max) end subroutine to_statepoint function text_label(this, bin) result(label) From a6746ab016ac4abcbd68eee0c696bd6dd193955e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 5 Jan 2018 10:45:23 -0600 Subject: [PATCH 139/361] Force tallies with spatial Legendre filters to use collision estimator --- src/tallies/tally_header.F90 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 84dc27e3f..15b8c2df5 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -361,6 +361,7 @@ contains j = FILTER_SPH_HARMONICS type is (SpatialLegendreFilter) j = FILTER_SPTL_LEGENDRE + this % estimator = ESTIMATOR_COLLISION end select this % find_filter(j) = i end do From ac776a5b512a36a95085ccf176600b493793e5a7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 9 Jan 2018 22:35:37 -0600 Subject: [PATCH 140/361] Start implemented Zernike FE filter --- CMakeLists.txt | 1 + src/constants.F90 | 5 +- src/tallies/tally_filter.F90 | 5 + src/tallies/tally_filter_zernike.F90 | 1281 ++++++++++++++++++++++++++ src/tallies/tally_header.F90 | 3 + 5 files changed, 1293 insertions(+), 2 deletions(-) create mode 100644 src/tallies/tally_filter_zernike.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index a0b374259..1797ae5d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -430,6 +430,7 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/tally_filter_sptl_legendre.F90 src/tallies/tally_filter_surface.F90 src/tallies/tally_filter_universe.F90 + src/tallies/tally_filter_zernike.F90 src/tallies/tally_header.F90 src/tallies/trigger.F90 src/tallies/trigger_header.F90 diff --git a/src/constants.F90 b/src/constants.F90 index b7f776fe8..b335b78c8 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -358,7 +358,7 @@ module constants integer, parameter :: NO_BIN_FOUND = -1 ! Tally filter and map types - integer, parameter :: N_FILTER_TYPES = 19 + integer, parameter :: N_FILTER_TYPES = 20 integer, parameter :: & FILTER_UNIVERSE = 1, & FILTER_MATERIAL = 2, & @@ -378,7 +378,8 @@ module constants FILTER_MESHSURFACE = 16, & FILTER_LEGENDRE = 17, & FILTER_SPH_HARMONICS = 18, & - FILTER_SPTL_LEGENDRE = 19 + FILTER_SPTL_LEGENDRE = 19, & + FILTER_ZERNIKE = 20 ! Mesh types integer, parameter :: & diff --git a/src/tallies/tally_filter.F90 b/src/tallies/tally_filter.F90 index bd40b0417..0277f2465 100644 --- a/src/tallies/tally_filter.F90 +++ b/src/tallies/tally_filter.F90 @@ -27,6 +27,7 @@ module tally_filter use tally_filter_sptl_legendre use tally_filter_surface use tally_filter_universe + use tally_filter_zernike implicit none @@ -87,6 +88,8 @@ contains type_ = 'surface' type is (UniverseFilter) type_ = 'universe' + type is (ZernikeFilter) + type_ = 'zernike' end select ! Convert Fortran string to null-terminated C string. We assume the @@ -164,6 +167,8 @@ contains allocate(SurfaceFilter :: filters(index) % obj) case ('universe') allocate(UniverseFilter :: filters(index) % obj) + case ('zernike') + allocate(ZernikeFilter :: filters(index) % obj) case default err = E_UNASSIGNED call set_errmsg("Unknown filter type: " // trim(type_)) diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 new file mode 100644 index 000000000..8ee07a67c --- /dev/null +++ b/src/tallies/tally_filter_zernike.F90 @@ -0,0 +1,1281 @@ +module tally_filter_zernike + + use, intrinsic :: ISO_C_BINDING + + use hdf5, only: HID_T + + use constants + use error + use hdf5_interface + use particle_header, only: Particle + use string, only: to_str + use tally_filter_header + use xml_interface + + implicit none + private + +!=============================================================================== +! ZERNIKEFILTER gives Zernike polynomial moments of a particle's position +!=============================================================================== + + type, public, extends(TallyFilter) :: ZernikeFilter + integer :: order + real(8) :: x + real(8) :: y + real(8) :: r + contains + procedure :: from_xml + procedure :: get_all_bins + procedure :: to_statepoint + procedure :: text_label + end type ZernikeFilter + +contains + +!=============================================================================== +! ZernikeFilter methods +!=============================================================================== + + subroutine from_xml(this, node) + class(ZernikeFilter), intent(inout) :: this + type(XMLNode), intent(in) :: node + + integer :: n + + ! Get center of cylinder and radius + call get_node_value(node, "x", this % x) + call get_node_value(node, "y", this % y) + call get_node_value(node, "r", this % r) + + ! Get specified order + call get_node_value(node, "order", n) + this % order = n + this % n_bins = (n + 1)*(n + 2)/2 + end subroutine from_xml + + subroutine get_all_bins(this, p, estimator, match) + class(ZernikeFilter), intent(in) :: this + type(Particle), intent(in) :: p + integer, intent(in) :: estimator + type(TallyFilterMatch), intent(inout) :: match + + integer :: n + integer :: j + integer :: i + real(8) :: wgt + real(8) :: tmp(this % order + 1) + real(8) :: x, y, r, theta + + ! Determine normalized (r,theta) positions + x = p % coord(1) % xyz(1) - this % x + y = p % coord(1) % xyz(2) - this % y + r = sqrt(x*x + y*y)/this % r + theta = atan2(y, x) + + i = 0 + do n = 0, this % order + ! Get moments for n-th order Zernike polynomial + tmp(1:n+1) = calc_zn_scaled(n, r, theta) + + ! Indicate matching bins/weights + do j = 1, n + 1 + call match % bins % push_back(i + j) + call match % weights % push_back(tmp(j)) + end do + i = i + n + 1 + end do + end subroutine get_all_bins + + subroutine to_statepoint(this, filter_group) + class(ZernikeFilter), intent(in) :: this + integer(HID_T), intent(in) :: filter_group + + call write_dataset(filter_group, "type", "zernike") + call write_dataset(filter_group, "n_bins", this % n_bins) + call write_dataset(filter_group, "order", this % order) + call write_dataset(filter_group, "x", this % x) + call write_dataset(filter_group, "y", this % y) + call write_dataset(filter_group, "r", this % r) + end subroutine to_statepoint + + function text_label(this, bin) result(label) + class(ZernikeFilter), intent(in) :: this + integer, intent(in) :: bin + character(MAX_LINE_LEN) :: label + + integer :: n, m + integer :: first, last + + do n = 0, this % order + last = (n + 1)*(n + 2)/2 + if (bin <= last) then + first = last - n + m = -n + (bin - first)*2 + label = "Zernike expansion, Y" // trim(to_str(n)) // "," & + // trim(to_str(m)) + exit + end if + end do + end function text_label + + +!=============================================================================== +! CALC_ZN calculates the n-th order Zernike polynomial moment for a given angle +! (rho, theta) location in the unit disk. +!=============================================================================== + + pure function calc_zn(n, rho, phi) result(zn) + + integer, intent(in) :: n ! Order requested + real(8), intent(in) :: rho ! Radial location in the unit disk + real(8), intent(in) :: phi ! Theta (radians) location in the unit disk + real(8) :: zn(n + 1) ! The resultant Z_n(uvw) + + ! n == radial degree + ! m == azimuthal frequency + + select case(n) + case(0) + ! n = 0, m = 0 + zn(1) = ( ( 1.00 ) ) & + * ( 1.000000000000 ) + case(1) + ! n = 1, m = -1 + zn(1) = ( ( 1.00 ) * rho ) & + * ( 2.000000000000 ) * sin(1.00 * phi) + ! n = 1, m = 1 + zn(2) = ( ( 1.00 ) * rho ) & + * ( 2.000000000000 ) * cos(1.00 * phi) + case(2) + ! n = 2, m = -2 + zn(1) = ( ( 1.00 ) *rho * rho ) & + * ( 2.449489742783 ) * sin(2.00 * phi) + ! n = 2, m = 0 + zn(2) = ( ( -1.00 ) + & + ( 2.00 ) *rho * rho ) & + * ( 1.732050807569 ) + ! n = 2, m = 2 + zn(3) = ( ( 1.00 ) *rho * rho ) & + * ( 2.449489742783 ) * cos(2.00 * phi) + case(3) + ! n = 3, m = -3 + zn(1) = ( ( 1.00 ) *rho *rho * rho ) & + * ( 2.828427124746 ) * sin(3.00 * phi) + ! n = 3, m = -1 + zn(2) = ( ( -2.00 ) * rho + & + ( 3.00 ) *rho *rho * rho ) & + * ( 2.828427124746 ) * sin(3.00 * phi) + ! n = 3, m = 1 + zn(3) = ( ( -2.00 ) * rho + & + ( 3.00 ) *rho *rho * rho ) & + * ( 2.828427124746 ) * cos(3.00 * phi) + ! n = 3, m = 3 + zn(4) = ( ( 1.00 ) *rho *rho * rho ) & + * ( 2.828427124746 ) * cos(3.00 * phi) + case(4) + ! n = 4, m = -4 + zn(1) = ( ( 1.00 ) *rho *rho *rho * rho ) & + * ( 3.162277660168 ) * sin(4.00 * phi) + ! n = 4, m = -2 + zn(2) = ( ( -3.00 ) *rho * rho + & + ( 4.00 ) *rho *rho *rho * rho ) & + * ( 3.162277660168 ) * sin(4.00 * phi) + ! n = 4, m = 0 + zn(3) = ( ( 1.00 ) + & + ( -6.00 ) *rho * rho + & + ( 6.00 ) *rho *rho *rho * rho ) & + * ( 2.236067977500 ) + ! n = 4, m = 2 + zn(4) = ( ( -3.00 ) *rho * rho + & + ( 4.00 ) *rho *rho *rho * rho ) & + * ( 3.162277660168 ) * cos(4.00 * phi) + ! n = 4, m = 4 + zn(5) = ( ( 1.00 ) *rho *rho *rho * rho ) & + * ( 3.162277660168 ) * cos(4.00 * phi) + case(5) + ! n = 5, m = -5 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * sin(5.00 * phi) + ! n = 5, m = -3 + zn(2) = ( ( -4.00 ) *rho *rho * rho + & + ( 5.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * sin(5.00 * phi) + ! n = 5, m = -1 + zn(3) = ( ( 3.00 ) * rho + & + ( -12.00 ) *rho *rho * rho + & + ( 10.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * sin(5.00 * phi) + ! n = 5, m = 1 + zn(4) = ( ( 3.00 ) * rho + & + ( -12.00 ) *rho *rho * rho + & + ( 10.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * cos(5.00 * phi) + ! n = 5, m = 3 + zn(5) = ( ( -4.00 ) *rho *rho * rho + & + ( 5.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * cos(5.00 * phi) + ! n = 5, m = 5 + zn(6) = ( ( 1.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * cos(5.00 * phi) + case(6) + ! n = 6, m = -6 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * sin(6.00 * phi) + ! n = 6, m = -4 + zn(2) = ( ( -5.00 ) *rho *rho *rho * rho + & + ( 6.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * sin(6.00 * phi) + ! n = 6, m = -2 + zn(3) = ( ( 6.00 ) *rho * rho + & + ( -20.00 ) *rho *rho *rho * rho + & + ( 15.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * sin(6.00 * phi) + ! n = 6, m = 0 + zn(4) = ( ( -1.00 ) + & + ( 12.00 ) *rho * rho + & + ( -30.00 ) *rho *rho *rho * rho + & + ( 20.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 2.645751311065 ) + ! n = 6, m = 2 + zn(5) = ( ( 6.00 ) *rho * rho + & + ( -20.00 ) *rho *rho *rho * rho + & + ( 15.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * cos(6.00 * phi) + ! n = 6, m = 4 + zn(6) = ( ( -5.00 ) *rho *rho *rho * rho + & + ( 6.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * cos(6.00 * phi) + ! n = 6, m = 6 + zn(7) = ( ( 1.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * cos(6.00 * phi) + case(7) + ! n = 7, m = -7 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * sin(7.00 * phi) + ! n = 7, m = -5 + zn(2) = ( ( -6.00 ) *rho *rho *rho *rho * rho + & + ( 7.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * sin(7.00 * phi) + ! n = 7, m = -3 + zn(3) = ( ( 10.00 ) *rho *rho * rho + & + ( -30.00 ) *rho *rho *rho *rho * rho + & + ( 21.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * sin(7.00 * phi) + ! n = 7, m = -1 + zn(4) = ( ( -4.00 ) * rho + & + ( 30.00 ) *rho *rho * rho + & + ( -60.00 ) *rho *rho *rho *rho * rho + & + ( 35.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * sin(7.00 * phi) + ! n = 7, m = 1 + zn(5) = ( ( -4.00 ) * rho + & + ( 30.00 ) *rho *rho * rho + & + ( -60.00 ) *rho *rho *rho *rho * rho + & + ( 35.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * cos(7.00 * phi) + ! n = 7, m = 3 + zn(6) = ( ( 10.00 ) *rho *rho * rho + & + ( -30.00 ) *rho *rho *rho *rho * rho + & + ( 21.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * cos(7.00 * phi) + ! n = 7, m = 5 + zn(7) = ( ( -6.00 ) *rho *rho *rho *rho * rho + & + ( 7.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * cos(7.00 * phi) + ! n = 7, m = 7 + zn(8) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * cos(7.00 * phi) + case(8) + ! n = 8, m = -8 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * sin(8.00 * phi) + ! n = 8, m = -6 + zn(2) = ( ( -7.00 ) *rho *rho *rho *rho *rho * rho + & + ( 8.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * sin(8.00 * phi) + ! n = 8, m = -4 + zn(3) = ( ( 15.00 ) *rho *rho *rho * rho + & + ( -42.00 ) *rho *rho *rho *rho *rho * rho + & + ( 28.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * sin(8.00 * phi) + ! n = 8, m = -2 + zn(4) = ( ( -10.00 ) *rho * rho + & + ( 60.00 ) *rho *rho *rho * rho + & + ( -105.00 ) *rho *rho *rho *rho *rho * rho + & + ( 56.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * sin(8.00 * phi) + ! n = 8, m = 0 + zn(5) = ( ( 1.00 ) + & + ( -20.00 ) *rho * rho + & + ( 90.00 ) *rho *rho *rho * rho + & + ( -140.00 ) *rho *rho *rho *rho *rho * rho + & + ( 70.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 3.000000000000 ) + ! n = 8, m = 2 + zn(6) = ( ( -10.00 ) *rho * rho + & + ( 60.00 ) *rho *rho *rho * rho + & + ( -105.00 ) *rho *rho *rho *rho *rho * rho + & + ( 56.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * cos(8.00 * phi) + ! n = 8, m = 4 + zn(7) = ( ( 15.00 ) *rho *rho *rho * rho + & + ( -42.00 ) *rho *rho *rho *rho *rho * rho + & + ( 28.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * cos(8.00 * phi) + ! n = 8, m = 6 + zn(8) = ( ( -7.00 ) *rho *rho *rho *rho *rho * rho + & + ( 8.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * cos(8.00 * phi) + ! n = 8, m = 8 + zn(9) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * cos(8.00 * phi) + case(9) + ! n = 9, m = -9 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * sin(9.00 * phi) + ! n = 9, m = -7 + zn(2) = ( ( -8.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 9.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * sin(9.00 * phi) + ! n = 9, m = -5 + zn(3) = ( ( 21.00 ) *rho *rho *rho *rho * rho + & + ( -56.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 36.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * sin(9.00 * phi) + ! n = 9, m = -3 + zn(4) = ( ( -20.00 ) *rho *rho * rho + & + ( 105.00 ) *rho *rho *rho *rho * rho + & + ( -168.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 84.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * sin(9.00 * phi) + ! n = 9, m = -1 + zn(5) = ( ( 5.00 ) * rho + & + ( -60.00 ) *rho *rho * rho + & + ( 210.00 ) *rho *rho *rho *rho * rho + & + ( -280.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 126.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * sin(9.00 * phi) + ! n = 9, m = 1 + zn(6) = ( ( 5.00 ) * rho + & + ( -60.00 ) *rho *rho * rho + & + ( 210.00 ) *rho *rho *rho *rho * rho + & + ( -280.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 126.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * cos(9.00 * phi) + ! n = 9, m = 3 + zn(7) = ( ( -20.00 ) *rho *rho * rho + & + ( 105.00 ) *rho *rho *rho *rho * rho + & + ( -168.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 84.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * cos(9.00 * phi) + ! n = 9, m = 5 + zn(8) = ( ( 21.00 ) *rho *rho *rho *rho * rho + & + ( -56.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 36.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * cos(9.00 * phi) + ! n = 9, m = 7 + zn(9) = ( ( -8.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 9.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * cos(9.00 * phi) + ! n = 9, m = 9 + zn(10) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * cos(9.00 * phi) + case(10) + ! n = 10, m = -10 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * sin(10.00 * phi) + ! n = 10, m = -8 + zn(2) = ( ( -9.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * sin(10.00 * phi) + ! n = 10, m = -6 + zn(3) = ( ( 28.00 ) *rho *rho *rho *rho *rho * rho + & + ( -72.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * sin(10.00 * phi) + ! n = 10, m = -4 + zn(4) = ( ( -35.00 ) *rho *rho *rho * rho + & + ( 168.00 ) *rho *rho *rho *rho *rho * rho + & + ( -252.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * sin(10.00 * phi) + ! n = 10, m = -2 + zn(5) = ( ( 15.00 ) *rho * rho + & + ( -140.00 ) *rho *rho *rho * rho + & + ( 420.00 ) *rho *rho *rho *rho *rho * rho + & + ( -504.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * sin(10.00 * phi) + ! n = 10, m = 0 + zn(6) = ( ( -1.00 ) + & + ( 30.00 ) *rho * rho + & + ( -210.00 ) *rho *rho *rho * rho + & + ( 560.00 ) *rho *rho *rho *rho *rho * rho + & + ( -630.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 252.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 3.316624790355 ) + ! n = 10, m = 2 + zn(7) = ( ( 15.00 ) *rho * rho + & + ( -140.00 ) *rho *rho *rho * rho + & + ( 420.00 ) *rho *rho *rho *rho *rho * rho + & + ( -504.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * cos(10.00 * phi) + ! n = 10, m = 4 + zn(8) = ( ( -35.00 ) *rho *rho *rho * rho + & + ( 168.00 ) *rho *rho *rho *rho *rho * rho + & + ( -252.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * cos(10.00 * phi) + ! n = 10, m = 6 + zn(9) = ( ( 28.00 ) *rho *rho *rho *rho *rho * rho + & + ( -72.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * cos(10.00 * phi) + ! n = 10, m = 8 + zn(10) = ( ( -9.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * cos(10.00 * phi) + ! n = 10, m = 10 + zn(11) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * cos(10.00 * phi) + case(11) + ! n = 11, m = -11 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = -9 + zn(2) = ( ( -10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = -7 + zn(3) = ( ( 36.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -90.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = -5 + zn(4) = ( ( -56.00 ) *rho *rho *rho *rho * rho + & + ( 252.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 165.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = -3 + zn(5) = ( ( 35.00 ) *rho *rho * rho + & + ( -280.00 ) *rho *rho *rho *rho * rho + & + ( 756.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 330.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = -1 + zn(6) = ( ( -6.00 ) * rho + & + ( 105.00 ) *rho *rho * rho + & + ( -560.00 ) *rho *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 462.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = 1 + zn(7) = ( ( -6.00 ) * rho + & + ( 105.00 ) *rho *rho * rho + & + ( -560.00 ) *rho *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 462.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + ! n = 11, m = 3 + zn(8) = ( ( 35.00 ) *rho *rho * rho + & + ( -280.00 ) *rho *rho *rho *rho * rho + & + ( 756.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 330.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + ! n = 11, m = 5 + zn(9) = ( ( -56.00 ) *rho *rho *rho *rho * rho + & + ( 252.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 165.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + ! n = 11, m = 7 + zn(10) = ( ( 36.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -90.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + ! n = 11, m = 9 + zn(11) = ( ( -10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + ! n = 11, m = 11 + zn(12) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + case(12) + ! n = 12, m = -12 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = -10 + zn(2) = ( ( -11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = -8 + zn(3) = ( ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -110.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = -6 + zn(4) = ( ( -84.00 ) *rho *rho *rho *rho *rho * rho + & + ( 360.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = -4 + zn(5) = ( ( 70.00 ) *rho *rho *rho * rho + & + ( -504.00 ) *rho *rho *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = -2 + zn(6) = ( ( -21.00 ) *rho * rho + & + ( 280.00 ) *rho *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho *rho * rho + & + ( 2520.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 792.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = 0 + zn(7) = ( ( 1.00 ) + & + ( -42.00 ) *rho * rho + & + ( 420.00 ) *rho *rho *rho * rho + & + ( -1680.00 ) *rho *rho *rho *rho *rho * rho + & + ( 3150.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2772.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 924.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 3.605551275464 ) + ! n = 12, m = 2 + zn(8) = ( ( -21.00 ) *rho * rho + & + ( 280.00 ) *rho *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho *rho * rho + & + ( 2520.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 792.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + ! n = 12, m = 4 + zn(9) = ( ( 70.00 ) *rho *rho *rho * rho + & + ( -504.00 ) *rho *rho *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + ! n = 12, m = 6 + zn(10) = ( ( -84.00 ) *rho *rho *rho *rho *rho * rho + & + ( 360.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + ! n = 12, m = 8 + zn(11) = ( ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -110.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + ! n = 12, m = 10 + zn(12) = ( ( -11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + ! n = 12, m = 12 + zn(13) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + case(13) + ! n = 13, m = -13 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -11 + zn(2) = ( ( -12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -9 + zn(3) = ( ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -132.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -7 + zn(4) = ( ( -120.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -5 + zn(5) = ( ( 126.00 ) *rho *rho *rho *rho * rho + & + ( -840.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -3 + zn(6) = ( ( -56.00 ) *rho *rho * rho + & + ( 630.00 ) *rho *rho *rho *rho * rho + & + ( -2520.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 4620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -3960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1287.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -1 + zn(7) = ( ( 7.00 ) * rho + & + ( -168.00 ) *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho * rho + & + ( -4200.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 6930.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5544.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1716.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = 1 + zn(8) = ( ( 7.00 ) * rho + & + ( -168.00 ) *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho * rho + & + ( -4200.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 6930.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5544.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1716.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 3 + zn(9) = ( ( -56.00 ) *rho *rho * rho + & + ( 630.00 ) *rho *rho *rho *rho * rho + & + ( -2520.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 4620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -3960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1287.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 5 + zn(10) = ( ( 126.00 ) *rho *rho *rho *rho * rho + & + ( -840.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 7 + zn(11) = ( ( -120.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 9 + zn(12) = ( ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -132.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 11 + zn(13) = ( ( -12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 13 + zn(14) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + case(14) + ! n = 14, m = -14 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -12 + zn(2) = ( ( -13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -10 + zn(3) = ( ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -156.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -8 + zn(4) = ( ( -165.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -6 + zn(5) = ( ( 210.00 ) *rho *rho *rho *rho *rho * rho + & + ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2970.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -4 + zn(6) = ( ( -126.00 ) *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho * rho + & + ( -4620.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2002.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -2 + zn(7) = ( ( 28.00 ) *rho * rho + & + ( -504.00 ) *rho *rho *rho * rho + & + ( 3150.00 ) *rho *rho *rho *rho *rho * rho + & + ( -9240.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = 0 + zn(8) = ( ( -1.00 ) + & + ( 56.00 ) *rho * rho + & + ( -756.00 ) *rho *rho *rho * rho + & + ( 4200.00 ) *rho *rho *rho *rho *rho * rho + & + ( -11550.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 16632.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -12012.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3432.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 3.872983346207 ) + ! n = 14, m = 2 + zn(9) = ( ( 28.00 ) *rho * rho + & + ( -504.00 ) *rho *rho *rho * rho + & + ( 3150.00 ) *rho *rho *rho *rho *rho * rho + & + ( -9240.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 4 + zn(10) = ( ( -126.00 ) *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho * rho + & + ( -4620.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2002.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 6 + zn(11) = ( ( 210.00 ) *rho *rho *rho *rho *rho * rho + & + ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2970.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 8 + zn(12) = ( ( -165.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 10 + zn(13) = ( ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -156.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 12 + zn(14) = ( ( -13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 14 + zn(15) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + case(15) + ! n = 15, m = -15 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -13 + zn(2) = ( ( -14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -11 + zn(3) = ( ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -182.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -9 + zn(4) = ( ( -220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -7 + zn(5) = ( ( 330.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 4290.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -5 + zn(6) = ( ( -252.00 ) *rho *rho *rho *rho * rho + & + ( 2310.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -3 + zn(7) = ( ( 84.00 ) *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho * rho + & + ( 6930.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -18480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -18018.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 5005.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -1 + zn(8) = ( ( -8.00 ) * rho + & + ( 252.00 ) *rho *rho * rho + & + ( -2520.00 ) *rho *rho *rho *rho * rho + & + ( 11550.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -27720.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 36036.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -24024.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = 1 + zn(9) = ( ( -8.00 ) * rho + & + ( 252.00 ) *rho *rho * rho + & + ( -2520.00 ) *rho *rho *rho *rho * rho + & + ( 11550.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -27720.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 36036.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -24024.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 3 + zn(10) = ( ( 84.00 ) *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho * rho + & + ( 6930.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -18480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -18018.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 5005.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 5 + zn(11) = ( ( -252.00 ) *rho *rho *rho *rho * rho + & + ( 2310.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 7 + zn(12) = ( ( 330.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 4290.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 9 + zn(13) = ( ( -220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 11 + zn(14) = ( ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -182.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 13 + zn(15) = ( ( -14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 15 + zn(16) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + case(16) + ! n = 16, m = -16 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -14 + zn(2) = ( ( -15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -12 + zn(3) = ( ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -10 + zn(4) = ( ( -286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 560.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -8 + zn(5) = ( ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6006.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1820.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -6 + zn(6) = ( ( -462.00 ) *rho *rho *rho *rho *rho * rho + & + ( 3960.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -15015.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 4368.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -4 + zn(7) = ( ( 210.00 ) *rho *rho *rho * rho + & + ( -2772.00 ) *rho *rho *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -34320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8008.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -2 + zn(8) = ( ( -36.00 ) *rho * rho + & + ( 840.00 ) *rho *rho *rho * rho + & + ( -6930.00 ) *rho *rho *rho *rho *rho * rho + & + ( 27720.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 11440.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = 0 + zn(9) = ( ( 1.00 ) + & + ( -72.00 ) *rho * rho + & + ( 1260.00 ) *rho *rho *rho * rho + & + ( -9240.00 ) *rho *rho *rho *rho *rho * rho + & + ( 34650.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 84084.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -51480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.123105625618 ) + ! n = 16, m = 2 + zn(10) = ( ( -36.00 ) *rho * rho + & + ( 840.00 ) *rho *rho *rho * rho + & + ( -6930.00 ) *rho *rho *rho *rho *rho * rho + & + ( 27720.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 11440.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 4 + zn(11) = ( ( 210.00 ) *rho *rho *rho * rho + & + ( -2772.00 ) *rho *rho *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -34320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8008.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 6 + zn(12) = ( ( -462.00 ) *rho *rho *rho *rho *rho * rho + & + ( 3960.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -15015.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 4368.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 8 + zn(13) = ( ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6006.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1820.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 10 + zn(14) = ( ( -286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 560.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 12 + zn(15) = ( ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 14 + zn(16) = ( ( -15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 16 + zn(17) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + case(17) + ! n = 17, m = -17 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -15 + zn(2) = ( ( -16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -13 + zn(3) = ( ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -11 + zn(4) = ( ( -364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -9 + zn(5) = ( ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8190.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -7280.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2380.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -7 + zn(6) = ( ( -792.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -21840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6188.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -5 + zn(7) = ( ( 462.00 ) *rho *rho *rho *rho * rho + & + ( -5544.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 75075.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -48048.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12376.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -3 + zn(8) = ( ( -120.00 ) *rho *rho * rho + & + ( 2310.00 ) *rho *rho *rho *rho * rho + & + ( -16632.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 135135.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -80080.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 19448.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -1 + zn(9) = ( ( 9.00 ) * rho + & + ( -360.00 ) *rho *rho * rho + & + ( 4620.00 ) *rho *rho *rho *rho * rho + & + ( -27720.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 90090.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -168168.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 180180.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -102960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 24310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = 1 + zn(10) = ( ( 9.00 ) * rho + & + ( -360.00 ) *rho *rho * rho + & + ( 4620.00 ) *rho *rho *rho *rho * rho + & + ( -27720.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 90090.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -168168.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 180180.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -102960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 24310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 3 + zn(11) = ( ( -120.00 ) *rho *rho * rho + & + ( 2310.00 ) *rho *rho *rho *rho * rho + & + ( -16632.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 135135.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -80080.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 19448.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 5 + zn(12) = ( ( 462.00 ) *rho *rho *rho *rho * rho + & + ( -5544.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 75075.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -48048.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12376.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 7 + zn(13) = ( ( -792.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -21840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6188.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 9 + zn(14) = ( ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8190.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -7280.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2380.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 11 + zn(15) = ( ( -364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 13 + zn(16) = ( ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 15 + zn(17) = ( ( -16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 17 + zn(18) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + case(18) + ! n = 18, m = -18 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -16 + zn(2) = ( ( -17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 18.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -14 + zn(3) = ( ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -272.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 153.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -12 + zn(4) = ( ( -455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2040.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 816.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -10 + zn(5) = ( ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -9520.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -8 + zn(6) = ( ( -1287.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 43680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30940.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8568.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -6 + zn(7) = ( ( 924.00 ) *rho *rho *rho *rho *rho * rho + & + ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -100100.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -74256.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 18564.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -4 + zn(8) = ( ( -330.00 ) *rho *rho *rho * rho + & + ( 5544.00 ) *rho *rho *rho *rho *rho * rho + & + ( -36036.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -225225.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 240240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -136136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 31824.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -2 + zn(9) = ( ( 45.00 ) *rho * rho + & + ( -1320.00 ) *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho * rho + & + ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 210210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -194480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 43758.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = 0 + zn(10) = ( ( -1.00 ) + & + ( 90.00 ) *rho * rho + & + ( -1980.00 ) *rho *rho *rho * rho + & + ( 18480.00 ) *rho *rho *rho *rho *rho * rho + & + ( -90090.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 252252.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -420420.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 411840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -218790.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 48620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.358898943541 ) + ! n = 18, m = 2 + zn(11) = ( ( 45.00 ) *rho * rho + & + ( -1320.00 ) *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho * rho + & + ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 210210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -194480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 43758.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 4 + zn(12) = ( ( -330.00 ) *rho *rho *rho * rho + & + ( 5544.00 ) *rho *rho *rho *rho *rho * rho + & + ( -36036.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -225225.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 240240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -136136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 31824.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 6 + zn(13) = ( ( 924.00 ) *rho *rho *rho *rho *rho * rho + & + ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -100100.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -74256.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 18564.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 8 + zn(14) = ( ( -1287.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 43680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30940.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8568.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 10 + zn(15) = ( ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -9520.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 12 + zn(16) = ( ( -455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2040.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 816.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 14 + zn(17) = ( ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -272.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 153.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 16 + zn(18) = ( ( -17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 18.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 18 + zn(19) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + case default + zn = ONE + end select + + end function calc_zn + +!=============================================================================== +! CALC_ZN_SCALED calculates the n-th order Zernike polynomial moment for a given +! angle (rho, theta) location in the unit disk, scaled correctly for orthogonal +! integration. +!=============================================================================== + + pure function calc_zn_scaled(n, rho, phi) result(zn) + + integer, intent(in) :: n ! Order requested + real(8), intent(in) :: rho ! Radial location in the unit disk + real(8), intent(in) :: phi ! Theta (radians) location in the unit disk + real(8) :: zn(n + 1) ! The resultant Z_n(uvw) + + zn = calc_zn(n, rho, phi) / SQRT_PI + + end function calc_zn_scaled + +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + +end module tally_filter_zernike diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 15b8c2df5..29362fee3 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -362,6 +362,9 @@ contains type is (SpatialLegendreFilter) j = FILTER_SPTL_LEGENDRE this % estimator = ESTIMATOR_COLLISION + type is (ZernikeFilter) + j = FILTER_ZERNIKE + this % estimator = ESTIMATOR_COLLISION end select this % find_filter(j) = i end do From 175942c221cd3c37345fbf08ee9bfc63e5b57aab Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 10 Jan 2018 06:35:26 -0600 Subject: [PATCH 141/361] Add ZernikeFilter class --- openmc/__init__.py | 1 + openmc/filter_spatial_legendre.py | 6 + openmc/filter_zernike.py | 183 +++++++++++++++++++++++++++ src/tallies/tally_filter_zernike.F90 | 4 +- 4 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 openmc/filter_zernike.py diff --git a/openmc/__init__.py b/openmc/__init__.py index e9dd0bd8c..100aff8c8 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -18,6 +18,7 @@ from openmc.filter import * from openmc.filter_legendre import * from openmc.filter_spatial_legendre import * from openmc.filter_spherical_harmonics import * +from openmc.filter_zernike import * from openmc.trigger import * from openmc.tally_derivative import * from openmc.tallies import * diff --git a/openmc/filter_spatial_legendre.py b/openmc/filter_spatial_legendre.py index 1ad23666b..690447d9c 100644 --- a/openmc/filter_spatial_legendre.py +++ b/openmc/filter_spatial_legendre.py @@ -49,11 +49,17 @@ class SpatialLegendreFilter(Filter): def __hash__(self): string = type(self).__name__ + '\n' string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tAxis', self.axis) + string += '{: <16}=\t{}\n'.format('\tMin', self.minimum) + string += '{: <16}=\t{}\n'.format('\tMax', self.maximum) return hash(string) def __repr__(self): string = type(self).__name__ + '\n' string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tAxis', self.axis) + string += '{: <16}=\t{}\n'.format('\tMin', self.minimum) + string += '{: <16}=\t{}\n'.format('\tMax', self.maximum) string += '{: <16}=\t{}\n'.format('\tID', self.id) return string diff --git a/openmc/filter_zernike.py b/openmc/filter_zernike.py new file mode 100644 index 000000000..3b4efe5d5 --- /dev/null +++ b/openmc/filter_zernike.py @@ -0,0 +1,183 @@ +from numbers import Integral, Real +from xml.etree import ElementTree as ET + +import numpy as np +import pandas as pd + +import openmc.checkvalue as cv +from . import Filter + + +class ZernikeFilter(Filter): + r"""Score Zernike expansion moments in space up to specified order. + + This filter allows scores to be multiplied by Zernike polynomials of the the + particle's position along a particular axis, normalized to a given unit + circle, up to a user-specified order. + + Parameters + ---------- + order : int + Maximum Zernike polynomial order + x : float + x-coordinate of center of circle for normalization + y : float + y-coordinate of center of circle for normalization + r : int or None + Radius of circle for normalization + + Attributes + ---------- + order : int + Maximum Zernike polynomial order + x : float + x-coordinate of center of circle for normalization + y : float + y-coordinate of center of circle for normalization + r : int or None + Radius of circle for normalization + id : int + Unique identifier for the filter + num_bins : int + The number of filter bins + + """ + + def __init__(self, order, x, y, r, filter_id=None): + self.order = order + self.x = x + self.y = y + self.r = r + self.id = filter_id + + def __hash__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + return hash(string) + + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tID', self.id) + return string + + @property + def order(self): + return self._order + + @order.setter + def order(self, order): + cv.check_type('Zernike order', order, Integral) + cv.check_greater_than('Zernike order', order, 0, equality=True) + self._order = order + + @property + def x(self): + return self._x + + @x.setter + def x(self, x): + cv.check_type('x', x, Real) + self._x = x + + @property + def y(self): + return self._y + + @y.setter + def y(self, y): + cv.check_type('y', y, Real) + self._y = y + + @property + def r(self): + return self._r + + @r.setter + def r(self, r): + cv.check_type('r', r, Real) + self._r = r + + @property + def num_bins(self): + n = self._order + return ((n + 1)*(n + 2))//2 + + @classmethod + def from_hdf5(cls, group, **kwargs): + if group['type'].value.decode() != cls.short_name.lower(): + raise ValueError("Expected HDF5 data for filter type '" + + cls.short_name.lower() + "' but got '" + + group['type'].value.decode() + " instead") + + filter_id = int(group.name.split('/')[-1].lstrip('filter ')) + order = group['order'].value + x, y, r = group['x'].value, group['y'].value, group['r'].value + + return cls(order, x, y, r, filter_id) + + def 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 + columns annotated by filter bin information. This is a helper method for + :meth:`Tally.get_pandas_dataframe`. + + Parameters + ---------- + data_size : Integral + The total number of bins in the tally corresponding to this filter + + Returns + ------- + pandas.DataFrame + A Pandas DataFrame with a column that is filled with strings + indicating Zernike orders. The number of rows in the DataFrame is + the same as the total number of bins in the corresponding tally. + + See also + -------- + Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() + + """ + # Initialize Pandas DataFrame + df = pd.DataFrame() + + # Create list of strings for each order + orders = [] + for n in range(self.order + 1): + for m in range(-n, n + 1, 2): + orders.append('Z{},{}'.format(n, m)) + + bins = np.array(orders) + filter_bins = np.repeat(bins, stride) + tile_factor = data_size // len(filter_bins) + filter_bins = np.tile(filter_bins, tile_factor) + df = pd.concat([df, pd.DataFrame( + {self.short_name.lower(): filter_bins})]) + + return df + + def to_xml_element(self): + """Return XML Element representing the filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing Zernike filter data + + """ + element = ET.Element('filter') + element.set('id', str(self.id)) + element.set('type', self.short_name.lower()) + + subelement = ET.SubElement(element, 'order') + subelement.text = str(self.order) + subelement = ET.SubElement(element, 'x') + subelement.text = str(self.x) + subelement = ET.SubElement(element, 'y') + subelement.text = str(self.y) + subelement = ET.SubElement(element, 'r') + subelement.text = str(self.r) + + return element diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 index 8ee07a67c..6f00e131d 100644 --- a/src/tallies/tally_filter_zernike.F90 +++ b/src/tallies/tally_filter_zernike.F90 @@ -51,7 +51,7 @@ contains ! Get specified order call get_node_value(node, "order", n) this % order = n - this % n_bins = (n + 1)*(n + 2)/2 + this % n_bins = ((n + 1)*(n + 2))/2 end subroutine from_xml subroutine get_all_bins(this, p, estimator, match) @@ -112,7 +112,7 @@ contains if (bin <= last) then first = last - n m = -n + (bin - first)*2 - label = "Zernike expansion, Y" // trim(to_str(n)) // "," & + label = "Zernike expansion, Z" // trim(to_str(n)) // "," & // trim(to_str(m)) exit end if From 6b5fb87d350610964d6444eddf8c57004ec7c890 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 10 Jan 2018 12:43:00 -0600 Subject: [PATCH 142/361] Move calc_zn to math module, remove calc_zn_scaled --- src/math.F90 | 1136 +++++++++++++++++++++++++ src/tallies/tally_filter_zernike.F90 | 1157 +------------------------- 2 files changed, 1138 insertions(+), 1155 deletions(-) diff --git a/src/math.F90 b/src/math.F90 index 2fa0a6ce1..b96562734 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -574,6 +574,1142 @@ contains end function calc_rn +!=============================================================================== +! CALC_ZN calculates the n-th order Zernike polynomial moment for a given angle +! (rho, theta) location in the unit disk. +!=============================================================================== + + pure function calc_zn(n, rho, phi) result(zn) + + integer, intent(in) :: n ! Order requested + real(8), intent(in) :: rho ! Radial location in the unit disk + real(8), intent(in) :: phi ! Theta (radians) location in the unit disk + real(8) :: zn(n + 1) ! The resultant Z_n(uvw) + + ! n == radial degree + ! m == azimuthal frequency + + select case(n) + case(0) + ! n = 0, m = 0 + zn(1) = ( ( 1.00 ) ) & + * ( 1.000000000000 ) + case(1) + ! n = 1, m = -1 + zn(1) = ( ( 1.00 ) * rho ) & + * ( 2.000000000000 ) * sin(1.00 * phi) + ! n = 1, m = 1 + zn(2) = ( ( 1.00 ) * rho ) & + * ( 2.000000000000 ) * cos(1.00 * phi) + case(2) + ! n = 2, m = -2 + zn(1) = ( ( 1.00 ) *rho * rho ) & + * ( 2.449489742783 ) * sin(2.00 * phi) + ! n = 2, m = 0 + zn(2) = ( ( -1.00 ) + & + ( 2.00 ) *rho * rho ) & + * ( 1.732050807569 ) + ! n = 2, m = 2 + zn(3) = ( ( 1.00 ) *rho * rho ) & + * ( 2.449489742783 ) * cos(2.00 * phi) + case(3) + ! n = 3, m = -3 + zn(1) = ( ( 1.00 ) *rho *rho * rho ) & + * ( 2.828427124746 ) * sin(3.00 * phi) + ! n = 3, m = -1 + zn(2) = ( ( -2.00 ) * rho + & + ( 3.00 ) *rho *rho * rho ) & + * ( 2.828427124746 ) * sin(3.00 * phi) + ! n = 3, m = 1 + zn(3) = ( ( -2.00 ) * rho + & + ( 3.00 ) *rho *rho * rho ) & + * ( 2.828427124746 ) * cos(3.00 * phi) + ! n = 3, m = 3 + zn(4) = ( ( 1.00 ) *rho *rho * rho ) & + * ( 2.828427124746 ) * cos(3.00 * phi) + case(4) + ! n = 4, m = -4 + zn(1) = ( ( 1.00 ) *rho *rho *rho * rho ) & + * ( 3.162277660168 ) * sin(4.00 * phi) + ! n = 4, m = -2 + zn(2) = ( ( -3.00 ) *rho * rho + & + ( 4.00 ) *rho *rho *rho * rho ) & + * ( 3.162277660168 ) * sin(4.00 * phi) + ! n = 4, m = 0 + zn(3) = ( ( 1.00 ) + & + ( -6.00 ) *rho * rho + & + ( 6.00 ) *rho *rho *rho * rho ) & + * ( 2.236067977500 ) + ! n = 4, m = 2 + zn(4) = ( ( -3.00 ) *rho * rho + & + ( 4.00 ) *rho *rho *rho * rho ) & + * ( 3.162277660168 ) * cos(4.00 * phi) + ! n = 4, m = 4 + zn(5) = ( ( 1.00 ) *rho *rho *rho * rho ) & + * ( 3.162277660168 ) * cos(4.00 * phi) + case(5) + ! n = 5, m = -5 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * sin(5.00 * phi) + ! n = 5, m = -3 + zn(2) = ( ( -4.00 ) *rho *rho * rho + & + ( 5.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * sin(5.00 * phi) + ! n = 5, m = -1 + zn(3) = ( ( 3.00 ) * rho + & + ( -12.00 ) *rho *rho * rho + & + ( 10.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * sin(5.00 * phi) + ! n = 5, m = 1 + zn(4) = ( ( 3.00 ) * rho + & + ( -12.00 ) *rho *rho * rho + & + ( 10.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * cos(5.00 * phi) + ! n = 5, m = 3 + zn(5) = ( ( -4.00 ) *rho *rho * rho + & + ( 5.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * cos(5.00 * phi) + ! n = 5, m = 5 + zn(6) = ( ( 1.00 ) *rho *rho *rho *rho * rho ) & + * ( 3.464101615138 ) * cos(5.00 * phi) + case(6) + ! n = 6, m = -6 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * sin(6.00 * phi) + ! n = 6, m = -4 + zn(2) = ( ( -5.00 ) *rho *rho *rho * rho + & + ( 6.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * sin(6.00 * phi) + ! n = 6, m = -2 + zn(3) = ( ( 6.00 ) *rho * rho + & + ( -20.00 ) *rho *rho *rho * rho + & + ( 15.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * sin(6.00 * phi) + ! n = 6, m = 0 + zn(4) = ( ( -1.00 ) + & + ( 12.00 ) *rho * rho + & + ( -30.00 ) *rho *rho *rho * rho + & + ( 20.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 2.645751311065 ) + ! n = 6, m = 2 + zn(5) = ( ( 6.00 ) *rho * rho + & + ( -20.00 ) *rho *rho *rho * rho + & + ( 15.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * cos(6.00 * phi) + ! n = 6, m = 4 + zn(6) = ( ( -5.00 ) *rho *rho *rho * rho + & + ( 6.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * cos(6.00 * phi) + ! n = 6, m = 6 + zn(7) = ( ( 1.00 ) *rho *rho *rho *rho *rho * rho ) & + * ( 3.741657386774 ) * cos(6.00 * phi) + case(7) + ! n = 7, m = -7 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * sin(7.00 * phi) + ! n = 7, m = -5 + zn(2) = ( ( -6.00 ) *rho *rho *rho *rho * rho + & + ( 7.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * sin(7.00 * phi) + ! n = 7, m = -3 + zn(3) = ( ( 10.00 ) *rho *rho * rho + & + ( -30.00 ) *rho *rho *rho *rho * rho + & + ( 21.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * sin(7.00 * phi) + ! n = 7, m = -1 + zn(4) = ( ( -4.00 ) * rho + & + ( 30.00 ) *rho *rho * rho + & + ( -60.00 ) *rho *rho *rho *rho * rho + & + ( 35.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * sin(7.00 * phi) + ! n = 7, m = 1 + zn(5) = ( ( -4.00 ) * rho + & + ( 30.00 ) *rho *rho * rho + & + ( -60.00 ) *rho *rho *rho *rho * rho + & + ( 35.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * cos(7.00 * phi) + ! n = 7, m = 3 + zn(6) = ( ( 10.00 ) *rho *rho * rho + & + ( -30.00 ) *rho *rho *rho *rho * rho + & + ( 21.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * cos(7.00 * phi) + ! n = 7, m = 5 + zn(7) = ( ( -6.00 ) *rho *rho *rho *rho * rho + & + ( 7.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * cos(7.00 * phi) + ! n = 7, m = 7 + zn(8) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.000000000000 ) * cos(7.00 * phi) + case(8) + ! n = 8, m = -8 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * sin(8.00 * phi) + ! n = 8, m = -6 + zn(2) = ( ( -7.00 ) *rho *rho *rho *rho *rho * rho + & + ( 8.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * sin(8.00 * phi) + ! n = 8, m = -4 + zn(3) = ( ( 15.00 ) *rho *rho *rho * rho + & + ( -42.00 ) *rho *rho *rho *rho *rho * rho + & + ( 28.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * sin(8.00 * phi) + ! n = 8, m = -2 + zn(4) = ( ( -10.00 ) *rho * rho + & + ( 60.00 ) *rho *rho *rho * rho + & + ( -105.00 ) *rho *rho *rho *rho *rho * rho + & + ( 56.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * sin(8.00 * phi) + ! n = 8, m = 0 + zn(5) = ( ( 1.00 ) + & + ( -20.00 ) *rho * rho + & + ( 90.00 ) *rho *rho *rho * rho + & + ( -140.00 ) *rho *rho *rho *rho *rho * rho + & + ( 70.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 3.000000000000 ) + ! n = 8, m = 2 + zn(6) = ( ( -10.00 ) *rho * rho + & + ( 60.00 ) *rho *rho *rho * rho + & + ( -105.00 ) *rho *rho *rho *rho *rho * rho + & + ( 56.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * cos(8.00 * phi) + ! n = 8, m = 4 + zn(7) = ( ( 15.00 ) *rho *rho *rho * rho + & + ( -42.00 ) *rho *rho *rho *rho *rho * rho + & + ( 28.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * cos(8.00 * phi) + ! n = 8, m = 6 + zn(8) = ( ( -7.00 ) *rho *rho *rho *rho *rho * rho + & + ( 8.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * cos(8.00 * phi) + ! n = 8, m = 8 + zn(9) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.242640687119 ) * cos(8.00 * phi) + case(9) + ! n = 9, m = -9 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * sin(9.00 * phi) + ! n = 9, m = -7 + zn(2) = ( ( -8.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 9.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * sin(9.00 * phi) + ! n = 9, m = -5 + zn(3) = ( ( 21.00 ) *rho *rho *rho *rho * rho + & + ( -56.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 36.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * sin(9.00 * phi) + ! n = 9, m = -3 + zn(4) = ( ( -20.00 ) *rho *rho * rho + & + ( 105.00 ) *rho *rho *rho *rho * rho + & + ( -168.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 84.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * sin(9.00 * phi) + ! n = 9, m = -1 + zn(5) = ( ( 5.00 ) * rho + & + ( -60.00 ) *rho *rho * rho + & + ( 210.00 ) *rho *rho *rho *rho * rho + & + ( -280.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 126.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * sin(9.00 * phi) + ! n = 9, m = 1 + zn(6) = ( ( 5.00 ) * rho + & + ( -60.00 ) *rho *rho * rho + & + ( 210.00 ) *rho *rho *rho *rho * rho + & + ( -280.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 126.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * cos(9.00 * phi) + ! n = 9, m = 3 + zn(7) = ( ( -20.00 ) *rho *rho * rho + & + ( 105.00 ) *rho *rho *rho *rho * rho + & + ( -168.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 84.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * cos(9.00 * phi) + ! n = 9, m = 5 + zn(8) = ( ( 21.00 ) *rho *rho *rho *rho * rho + & + ( -56.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 36.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * cos(9.00 * phi) + ! n = 9, m = 7 + zn(9) = ( ( -8.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 9.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * cos(9.00 * phi) + ! n = 9, m = 9 + zn(10) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.472135955000 ) * cos(9.00 * phi) + case(10) + ! n = 10, m = -10 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * sin(10.00 * phi) + ! n = 10, m = -8 + zn(2) = ( ( -9.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * sin(10.00 * phi) + ! n = 10, m = -6 + zn(3) = ( ( 28.00 ) *rho *rho *rho *rho *rho * rho + & + ( -72.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * sin(10.00 * phi) + ! n = 10, m = -4 + zn(4) = ( ( -35.00 ) *rho *rho *rho * rho + & + ( 168.00 ) *rho *rho *rho *rho *rho * rho + & + ( -252.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * sin(10.00 * phi) + ! n = 10, m = -2 + zn(5) = ( ( 15.00 ) *rho * rho + & + ( -140.00 ) *rho *rho *rho * rho + & + ( 420.00 ) *rho *rho *rho *rho *rho * rho + & + ( -504.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * sin(10.00 * phi) + ! n = 10, m = 0 + zn(6) = ( ( -1.00 ) + & + ( 30.00 ) *rho * rho + & + ( -210.00 ) *rho *rho *rho * rho + & + ( 560.00 ) *rho *rho *rho *rho *rho * rho + & + ( -630.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 252.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 3.316624790355 ) + ! n = 10, m = 2 + zn(7) = ( ( 15.00 ) *rho * rho + & + ( -140.00 ) *rho *rho *rho * rho + & + ( 420.00 ) *rho *rho *rho *rho *rho * rho + & + ( -504.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * cos(10.00 * phi) + ! n = 10, m = 4 + zn(8) = ( ( -35.00 ) *rho *rho *rho * rho + & + ( 168.00 ) *rho *rho *rho *rho *rho * rho + & + ( -252.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * cos(10.00 * phi) + ! n = 10, m = 6 + zn(9) = ( ( 28.00 ) *rho *rho *rho *rho *rho * rho + & + ( -72.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * cos(10.00 * phi) + ! n = 10, m = 8 + zn(10) = ( ( -9.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * cos(10.00 * phi) + ! n = 10, m = 10 + zn(11) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.690415759823 ) * cos(10.00 * phi) + case(11) + ! n = 11, m = -11 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = -9 + zn(2) = ( ( -10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = -7 + zn(3) = ( ( 36.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -90.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = -5 + zn(4) = ( ( -56.00 ) *rho *rho *rho *rho * rho + & + ( 252.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 165.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = -3 + zn(5) = ( ( 35.00 ) *rho *rho * rho + & + ( -280.00 ) *rho *rho *rho *rho * rho + & + ( 756.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 330.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = -1 + zn(6) = ( ( -6.00 ) * rho + & + ( 105.00 ) *rho *rho * rho + & + ( -560.00 ) *rho *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 462.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * sin(11.00 * phi) + ! n = 11, m = 1 + zn(7) = ( ( -6.00 ) * rho + & + ( 105.00 ) *rho *rho * rho + & + ( -560.00 ) *rho *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 462.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + ! n = 11, m = 3 + zn(8) = ( ( 35.00 ) *rho *rho * rho + & + ( -280.00 ) *rho *rho *rho *rho * rho + & + ( 756.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 330.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + ! n = 11, m = 5 + zn(9) = ( ( -56.00 ) *rho *rho *rho *rho * rho + & + ( 252.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 165.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + ! n = 11, m = 7 + zn(10) = ( ( 36.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -90.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + ! n = 11, m = 9 + zn(11) = ( ( -10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + ! n = 11, m = 11 + zn(12) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.898979485566 ) * cos(11.00 * phi) + case(12) + ! n = 12, m = -12 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = -10 + zn(2) = ( ( -11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = -8 + zn(3) = ( ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -110.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = -6 + zn(4) = ( ( -84.00 ) *rho *rho *rho *rho *rho * rho + & + ( 360.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = -4 + zn(5) = ( ( 70.00 ) *rho *rho *rho * rho + & + ( -504.00 ) *rho *rho *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = -2 + zn(6) = ( ( -21.00 ) *rho * rho + & + ( 280.00 ) *rho *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho *rho * rho + & + ( 2520.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 792.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * sin(12.00 * phi) + ! n = 12, m = 0 + zn(7) = ( ( 1.00 ) + & + ( -42.00 ) *rho * rho + & + ( 420.00 ) *rho *rho *rho * rho + & + ( -1680.00 ) *rho *rho *rho *rho *rho * rho + & + ( 3150.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2772.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 924.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 3.605551275464 ) + ! n = 12, m = 2 + zn(8) = ( ( -21.00 ) *rho * rho + & + ( 280.00 ) *rho *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho *rho * rho + & + ( 2520.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 792.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + ! n = 12, m = 4 + zn(9) = ( ( 70.00 ) *rho *rho *rho * rho + & + ( -504.00 ) *rho *rho *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + ! n = 12, m = 6 + zn(10) = ( ( -84.00 ) *rho *rho *rho *rho *rho * rho + & + ( 360.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + ! n = 12, m = 8 + zn(11) = ( ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -110.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + ! n = 12, m = 10 + zn(12) = ( ( -11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + ! n = 12, m = 12 + zn(13) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.099019513593 ) * cos(12.00 * phi) + case(13) + ! n = 13, m = -13 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -11 + zn(2) = ( ( -12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -9 + zn(3) = ( ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -132.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -7 + zn(4) = ( ( -120.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -5 + zn(5) = ( ( 126.00 ) *rho *rho *rho *rho * rho + & + ( -840.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -3 + zn(6) = ( ( -56.00 ) *rho *rho * rho + & + ( 630.00 ) *rho *rho *rho *rho * rho + & + ( -2520.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 4620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -3960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1287.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = -1 + zn(7) = ( ( 7.00 ) * rho + & + ( -168.00 ) *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho * rho + & + ( -4200.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 6930.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5544.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1716.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * sin(13.00 * phi) + ! n = 13, m = 1 + zn(8) = ( ( 7.00 ) * rho + & + ( -168.00 ) *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho * rho + & + ( -4200.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 6930.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5544.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1716.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 3 + zn(9) = ( ( -56.00 ) *rho *rho * rho + & + ( 630.00 ) *rho *rho *rho *rho * rho + & + ( -2520.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 4620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -3960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1287.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 5 + zn(10) = ( ( 126.00 ) *rho *rho *rho *rho * rho + & + ( -840.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 7 + zn(11) = ( ( -120.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 9 + zn(12) = ( ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -132.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 11 + zn(13) = ( ( -12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + ! n = 13, m = 13 + zn(14) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.291502622129 ) * cos(13.00 * phi) + case(14) + ! n = 14, m = -14 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -12 + zn(2) = ( ( -13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -10 + zn(3) = ( ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -156.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -8 + zn(4) = ( ( -165.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -6 + zn(5) = ( ( 210.00 ) *rho *rho *rho *rho *rho * rho + & + ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2970.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -4 + zn(6) = ( ( -126.00 ) *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho * rho + & + ( -4620.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2002.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = -2 + zn(7) = ( ( 28.00 ) *rho * rho + & + ( -504.00 ) *rho *rho *rho * rho + & + ( 3150.00 ) *rho *rho *rho *rho *rho * rho + & + ( -9240.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * sin(14.00 * phi) + ! n = 14, m = 0 + zn(8) = ( ( -1.00 ) + & + ( 56.00 ) *rho * rho + & + ( -756.00 ) *rho *rho *rho * rho + & + ( 4200.00 ) *rho *rho *rho *rho *rho * rho + & + ( -11550.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 16632.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -12012.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3432.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 3.872983346207 ) + ! n = 14, m = 2 + zn(9) = ( ( 28.00 ) *rho * rho + & + ( -504.00 ) *rho *rho *rho * rho + & + ( 3150.00 ) *rho *rho *rho *rho *rho * rho + & + ( -9240.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 4 + zn(10) = ( ( -126.00 ) *rho *rho *rho * rho + & + ( 1260.00 ) *rho *rho *rho *rho *rho * rho + & + ( -4620.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2002.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 6 + zn(11) = ( ( 210.00 ) *rho *rho *rho *rho *rho * rho + & + ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2970.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 8 + zn(12) = ( ( -165.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 10 + zn(13) = ( ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -156.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 12 + zn(14) = ( ( -13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + ! n = 14, m = 14 + zn(15) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.477225575052 ) * cos(14.00 * phi) + case(15) + ! n = 15, m = -15 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -13 + zn(2) = ( ( -14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -11 + zn(3) = ( ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -182.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -9 + zn(4) = ( ( -220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -7 + zn(5) = ( ( 330.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 4290.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -5 + zn(6) = ( ( -252.00 ) *rho *rho *rho *rho * rho + & + ( 2310.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -3 + zn(7) = ( ( 84.00 ) *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho * rho + & + ( 6930.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -18480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -18018.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 5005.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = -1 + zn(8) = ( ( -8.00 ) * rho + & + ( 252.00 ) *rho *rho * rho + & + ( -2520.00 ) *rho *rho *rho *rho * rho + & + ( 11550.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -27720.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 36036.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -24024.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * sin(15.00 * phi) + ! n = 15, m = 1 + zn(9) = ( ( -8.00 ) * rho + & + ( 252.00 ) *rho *rho * rho + & + ( -2520.00 ) *rho *rho *rho *rho * rho + & + ( 11550.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -27720.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 36036.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -24024.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 3 + zn(10) = ( ( 84.00 ) *rho *rho * rho + & + ( -1260.00 ) *rho *rho *rho *rho * rho + & + ( 6930.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -18480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -18018.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 5005.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 5 + zn(11) = ( ( -252.00 ) *rho *rho *rho *rho * rho + & + ( 2310.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 7 + zn(12) = ( ( 330.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 4290.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 9 + zn(13) = ( ( -220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 11 + zn(14) = ( ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -182.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 13 + zn(15) = ( ( -14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + ! n = 15, m = 15 + zn(16) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.656854249492 ) * cos(15.00 * phi) + case(16) + ! n = 16, m = -16 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -14 + zn(2) = ( ( -15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -12 + zn(3) = ( ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -10 + zn(4) = ( ( -286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 560.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -8 + zn(5) = ( ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6006.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1820.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -6 + zn(6) = ( ( -462.00 ) *rho *rho *rho *rho *rho * rho + & + ( 3960.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -15015.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 4368.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -4 + zn(7) = ( ( 210.00 ) *rho *rho *rho * rho + & + ( -2772.00 ) *rho *rho *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -34320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8008.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = -2 + zn(8) = ( ( -36.00 ) *rho * rho + & + ( 840.00 ) *rho *rho *rho * rho + & + ( -6930.00 ) *rho *rho *rho *rho *rho * rho + & + ( 27720.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 11440.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * sin(16.00 * phi) + ! n = 16, m = 0 + zn(9) = ( ( 1.00 ) + & + ( -72.00 ) *rho * rho + & + ( 1260.00 ) *rho *rho *rho * rho + & + ( -9240.00 ) *rho *rho *rho *rho *rho * rho + & + ( 34650.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 84084.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -51480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.123105625618 ) + ! n = 16, m = 2 + zn(10) = ( ( -36.00 ) *rho * rho + & + ( 840.00 ) *rho *rho *rho * rho + & + ( -6930.00 ) *rho *rho *rho *rho *rho * rho + & + ( 27720.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 11440.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 4 + zn(11) = ( ( 210.00 ) *rho *rho *rho * rho + & + ( -2772.00 ) *rho *rho *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -34320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8008.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 6 + zn(12) = ( ( -462.00 ) *rho *rho *rho *rho *rho * rho + & + ( 3960.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -15015.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 4368.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 8 + zn(13) = ( ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6006.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1820.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 10 + zn(14) = ( ( -286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 560.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 12 + zn(15) = ( ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 14 + zn(16) = ( ( -15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + ! n = 16, m = 16 + zn(17) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 5.830951894845 ) * cos(16.00 * phi) + case(17) + ! n = 17, m = -17 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -15 + zn(2) = ( ( -16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -13 + zn(3) = ( ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -11 + zn(4) = ( ( -364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -9 + zn(5) = ( ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8190.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -7280.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2380.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -7 + zn(6) = ( ( -792.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -21840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6188.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -5 + zn(7) = ( ( 462.00 ) *rho *rho *rho *rho * rho + & + ( -5544.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 75075.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -48048.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12376.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -3 + zn(8) = ( ( -120.00 ) *rho *rho * rho + & + ( 2310.00 ) *rho *rho *rho *rho * rho + & + ( -16632.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 135135.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -80080.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 19448.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = -1 + zn(9) = ( ( 9.00 ) * rho + & + ( -360.00 ) *rho *rho * rho + & + ( 4620.00 ) *rho *rho *rho *rho * rho + & + ( -27720.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 90090.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -168168.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 180180.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -102960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 24310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * sin(17.00 * phi) + ! n = 17, m = 1 + zn(10) = ( ( 9.00 ) * rho + & + ( -360.00 ) *rho *rho * rho + & + ( 4620.00 ) *rho *rho *rho *rho * rho + & + ( -27720.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 90090.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -168168.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 180180.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -102960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 24310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 3 + zn(11) = ( ( -120.00 ) *rho *rho * rho + & + ( 2310.00 ) *rho *rho *rho *rho * rho + & + ( -16632.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 135135.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -80080.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 19448.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 5 + zn(12) = ( ( 462.00 ) *rho *rho *rho *rho * rho + & + ( -5544.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 75075.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -48048.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 12376.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 7 + zn(13) = ( ( -792.00 ) *rho *rho *rho *rho *rho *rho * rho + & + ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -21840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 6188.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 9 + zn(14) = ( ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8190.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -7280.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 2380.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 11 + zn(15) = ( ( -364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 13 + zn(16) = ( ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 15 + zn(17) = ( ( -16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + ! n = 17, m = 17 + zn(18) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.000000000000 ) * cos(17.00 * phi) + case(18) + ! n = 18, m = -18 + zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -16 + zn(2) = ( ( -17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 18.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -14 + zn(3) = ( ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -272.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 153.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -12 + zn(4) = ( ( -455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2040.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 816.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -10 + zn(5) = ( ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -9520.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -8 + zn(6) = ( ( -1287.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 43680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30940.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8568.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -6 + zn(7) = ( ( 924.00 ) *rho *rho *rho *rho *rho * rho + & + ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -100100.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -74256.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 18564.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -4 + zn(8) = ( ( -330.00 ) *rho *rho *rho * rho + & + ( 5544.00 ) *rho *rho *rho *rho *rho * rho + & + ( -36036.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -225225.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 240240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -136136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 31824.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = -2 + zn(9) = ( ( 45.00 ) *rho * rho + & + ( -1320.00 ) *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho * rho + & + ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 210210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -194480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 43758.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * sin(18.00 * phi) + ! n = 18, m = 0 + zn(10) = ( ( -1.00 ) + & + ( 90.00 ) *rho * rho + & + ( -1980.00 ) *rho *rho *rho * rho + & + ( 18480.00 ) *rho *rho *rho *rho *rho * rho + & + ( -90090.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 252252.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -420420.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 411840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -218790.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 48620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 4.358898943541 ) + ! n = 18, m = 2 + zn(11) = ( ( 45.00 ) *rho * rho + & + ( -1320.00 ) *rho *rho *rho * rho + & + ( 13860.00 ) *rho *rho *rho *rho *rho * rho + & + ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 210210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -194480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 43758.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 4 + zn(12) = ( ( -330.00 ) *rho *rho *rho * rho + & + ( 5544.00 ) *rho *rho *rho *rho *rho * rho + & + ( -36036.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -225225.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 240240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -136136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 31824.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 6 + zn(13) = ( ( 924.00 ) *rho *rho *rho *rho *rho * rho + & + ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -100100.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -74256.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 18564.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 8 + zn(14) = ( ( -1287.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 43680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -30940.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 8568.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 10 + zn(15) = ( ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 10920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -9520.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 3060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 12 + zn(16) = ( ( -455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -2040.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 816.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 14 + zn(17) = ( ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( -272.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 153.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 16 + zn(18) = ( ( -17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & + ( 18.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + ! n = 18, m = 18 + zn(19) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & + * ( 6.164414002969 ) * cos(18.00 * phi) + case default + zn = ONE + end select + + end function calc_zn + !=============================================================================== ! EXPAND_HARMONIC expands a given series of real spherical harmonics !=============================================================================== diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 index 6f00e131d..08bc24e60 100644 --- a/src/tallies/tally_filter_zernike.F90 +++ b/src/tallies/tally_filter_zernike.F90 @@ -7,6 +7,7 @@ module tally_filter_zernike use constants use error use hdf5_interface + use math, only: calc_zn use particle_header, only: Particle use string, only: to_str use tally_filter_header @@ -76,7 +77,7 @@ contains i = 0 do n = 0, this % order ! Get moments for n-th order Zernike polynomial - tmp(1:n+1) = calc_zn_scaled(n, r, theta) + tmp(1:n+1) = calc_zn(n, r, theta) / SQRT_PI ! Indicate matching bins/weights do j = 1, n + 1 @@ -119,1160 +120,6 @@ contains end do end function text_label - -!=============================================================================== -! CALC_ZN calculates the n-th order Zernike polynomial moment for a given angle -! (rho, theta) location in the unit disk. -!=============================================================================== - - pure function calc_zn(n, rho, phi) result(zn) - - integer, intent(in) :: n ! Order requested - real(8), intent(in) :: rho ! Radial location in the unit disk - real(8), intent(in) :: phi ! Theta (radians) location in the unit disk - real(8) :: zn(n + 1) ! The resultant Z_n(uvw) - - ! n == radial degree - ! m == azimuthal frequency - - select case(n) - case(0) - ! n = 0, m = 0 - zn(1) = ( ( 1.00 ) ) & - * ( 1.000000000000 ) - case(1) - ! n = 1, m = -1 - zn(1) = ( ( 1.00 ) * rho ) & - * ( 2.000000000000 ) * sin(1.00 * phi) - ! n = 1, m = 1 - zn(2) = ( ( 1.00 ) * rho ) & - * ( 2.000000000000 ) * cos(1.00 * phi) - case(2) - ! n = 2, m = -2 - zn(1) = ( ( 1.00 ) *rho * rho ) & - * ( 2.449489742783 ) * sin(2.00 * phi) - ! n = 2, m = 0 - zn(2) = ( ( -1.00 ) + & - ( 2.00 ) *rho * rho ) & - * ( 1.732050807569 ) - ! n = 2, m = 2 - zn(3) = ( ( 1.00 ) *rho * rho ) & - * ( 2.449489742783 ) * cos(2.00 * phi) - case(3) - ! n = 3, m = -3 - zn(1) = ( ( 1.00 ) *rho *rho * rho ) & - * ( 2.828427124746 ) * sin(3.00 * phi) - ! n = 3, m = -1 - zn(2) = ( ( -2.00 ) * rho + & - ( 3.00 ) *rho *rho * rho ) & - * ( 2.828427124746 ) * sin(3.00 * phi) - ! n = 3, m = 1 - zn(3) = ( ( -2.00 ) * rho + & - ( 3.00 ) *rho *rho * rho ) & - * ( 2.828427124746 ) * cos(3.00 * phi) - ! n = 3, m = 3 - zn(4) = ( ( 1.00 ) *rho *rho * rho ) & - * ( 2.828427124746 ) * cos(3.00 * phi) - case(4) - ! n = 4, m = -4 - zn(1) = ( ( 1.00 ) *rho *rho *rho * rho ) & - * ( 3.162277660168 ) * sin(4.00 * phi) - ! n = 4, m = -2 - zn(2) = ( ( -3.00 ) *rho * rho + & - ( 4.00 ) *rho *rho *rho * rho ) & - * ( 3.162277660168 ) * sin(4.00 * phi) - ! n = 4, m = 0 - zn(3) = ( ( 1.00 ) + & - ( -6.00 ) *rho * rho + & - ( 6.00 ) *rho *rho *rho * rho ) & - * ( 2.236067977500 ) - ! n = 4, m = 2 - zn(4) = ( ( -3.00 ) *rho * rho + & - ( 4.00 ) *rho *rho *rho * rho ) & - * ( 3.162277660168 ) * cos(4.00 * phi) - ! n = 4, m = 4 - zn(5) = ( ( 1.00 ) *rho *rho *rho * rho ) & - * ( 3.162277660168 ) * cos(4.00 * phi) - case(5) - ! n = 5, m = -5 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * sin(5.00 * phi) - ! n = 5, m = -3 - zn(2) = ( ( -4.00 ) *rho *rho * rho + & - ( 5.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * sin(5.00 * phi) - ! n = 5, m = -1 - zn(3) = ( ( 3.00 ) * rho + & - ( -12.00 ) *rho *rho * rho + & - ( 10.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * sin(5.00 * phi) - ! n = 5, m = 1 - zn(4) = ( ( 3.00 ) * rho + & - ( -12.00 ) *rho *rho * rho + & - ( 10.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * cos(5.00 * phi) - ! n = 5, m = 3 - zn(5) = ( ( -4.00 ) *rho *rho * rho + & - ( 5.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * cos(5.00 * phi) - ! n = 5, m = 5 - zn(6) = ( ( 1.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * cos(5.00 * phi) - case(6) - ! n = 6, m = -6 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * sin(6.00 * phi) - ! n = 6, m = -4 - zn(2) = ( ( -5.00 ) *rho *rho *rho * rho + & - ( 6.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * sin(6.00 * phi) - ! n = 6, m = -2 - zn(3) = ( ( 6.00 ) *rho * rho + & - ( -20.00 ) *rho *rho *rho * rho + & - ( 15.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * sin(6.00 * phi) - ! n = 6, m = 0 - zn(4) = ( ( -1.00 ) + & - ( 12.00 ) *rho * rho + & - ( -30.00 ) *rho *rho *rho * rho + & - ( 20.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 2.645751311065 ) - ! n = 6, m = 2 - zn(5) = ( ( 6.00 ) *rho * rho + & - ( -20.00 ) *rho *rho *rho * rho + & - ( 15.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * cos(6.00 * phi) - ! n = 6, m = 4 - zn(6) = ( ( -5.00 ) *rho *rho *rho * rho + & - ( 6.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * cos(6.00 * phi) - ! n = 6, m = 6 - zn(7) = ( ( 1.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * cos(6.00 * phi) - case(7) - ! n = 7, m = -7 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * sin(7.00 * phi) - ! n = 7, m = -5 - zn(2) = ( ( -6.00 ) *rho *rho *rho *rho * rho + & - ( 7.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * sin(7.00 * phi) - ! n = 7, m = -3 - zn(3) = ( ( 10.00 ) *rho *rho * rho + & - ( -30.00 ) *rho *rho *rho *rho * rho + & - ( 21.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * sin(7.00 * phi) - ! n = 7, m = -1 - zn(4) = ( ( -4.00 ) * rho + & - ( 30.00 ) *rho *rho * rho + & - ( -60.00 ) *rho *rho *rho *rho * rho + & - ( 35.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * sin(7.00 * phi) - ! n = 7, m = 1 - zn(5) = ( ( -4.00 ) * rho + & - ( 30.00 ) *rho *rho * rho + & - ( -60.00 ) *rho *rho *rho *rho * rho + & - ( 35.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * cos(7.00 * phi) - ! n = 7, m = 3 - zn(6) = ( ( 10.00 ) *rho *rho * rho + & - ( -30.00 ) *rho *rho *rho *rho * rho + & - ( 21.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * cos(7.00 * phi) - ! n = 7, m = 5 - zn(7) = ( ( -6.00 ) *rho *rho *rho *rho * rho + & - ( 7.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * cos(7.00 * phi) - ! n = 7, m = 7 - zn(8) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * cos(7.00 * phi) - case(8) - ! n = 8, m = -8 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * sin(8.00 * phi) - ! n = 8, m = -6 - zn(2) = ( ( -7.00 ) *rho *rho *rho *rho *rho * rho + & - ( 8.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * sin(8.00 * phi) - ! n = 8, m = -4 - zn(3) = ( ( 15.00 ) *rho *rho *rho * rho + & - ( -42.00 ) *rho *rho *rho *rho *rho * rho + & - ( 28.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * sin(8.00 * phi) - ! n = 8, m = -2 - zn(4) = ( ( -10.00 ) *rho * rho + & - ( 60.00 ) *rho *rho *rho * rho + & - ( -105.00 ) *rho *rho *rho *rho *rho * rho + & - ( 56.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * sin(8.00 * phi) - ! n = 8, m = 0 - zn(5) = ( ( 1.00 ) + & - ( -20.00 ) *rho * rho + & - ( 90.00 ) *rho *rho *rho * rho + & - ( -140.00 ) *rho *rho *rho *rho *rho * rho + & - ( 70.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 3.000000000000 ) - ! n = 8, m = 2 - zn(6) = ( ( -10.00 ) *rho * rho + & - ( 60.00 ) *rho *rho *rho * rho + & - ( -105.00 ) *rho *rho *rho *rho *rho * rho + & - ( 56.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * cos(8.00 * phi) - ! n = 8, m = 4 - zn(7) = ( ( 15.00 ) *rho *rho *rho * rho + & - ( -42.00 ) *rho *rho *rho *rho *rho * rho + & - ( 28.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * cos(8.00 * phi) - ! n = 8, m = 6 - zn(8) = ( ( -7.00 ) *rho *rho *rho *rho *rho * rho + & - ( 8.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * cos(8.00 * phi) - ! n = 8, m = 8 - zn(9) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * cos(8.00 * phi) - case(9) - ! n = 9, m = -9 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * sin(9.00 * phi) - ! n = 9, m = -7 - zn(2) = ( ( -8.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 9.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * sin(9.00 * phi) - ! n = 9, m = -5 - zn(3) = ( ( 21.00 ) *rho *rho *rho *rho * rho + & - ( -56.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 36.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * sin(9.00 * phi) - ! n = 9, m = -3 - zn(4) = ( ( -20.00 ) *rho *rho * rho + & - ( 105.00 ) *rho *rho *rho *rho * rho + & - ( -168.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 84.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * sin(9.00 * phi) - ! n = 9, m = -1 - zn(5) = ( ( 5.00 ) * rho + & - ( -60.00 ) *rho *rho * rho + & - ( 210.00 ) *rho *rho *rho *rho * rho + & - ( -280.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 126.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * sin(9.00 * phi) - ! n = 9, m = 1 - zn(6) = ( ( 5.00 ) * rho + & - ( -60.00 ) *rho *rho * rho + & - ( 210.00 ) *rho *rho *rho *rho * rho + & - ( -280.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 126.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * cos(9.00 * phi) - ! n = 9, m = 3 - zn(7) = ( ( -20.00 ) *rho *rho * rho + & - ( 105.00 ) *rho *rho *rho *rho * rho + & - ( -168.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 84.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * cos(9.00 * phi) - ! n = 9, m = 5 - zn(8) = ( ( 21.00 ) *rho *rho *rho *rho * rho + & - ( -56.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 36.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * cos(9.00 * phi) - ! n = 9, m = 7 - zn(9) = ( ( -8.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 9.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * cos(9.00 * phi) - ! n = 9, m = 9 - zn(10) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * cos(9.00 * phi) - case(10) - ! n = 10, m = -10 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * sin(10.00 * phi) - ! n = 10, m = -8 - zn(2) = ( ( -9.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * sin(10.00 * phi) - ! n = 10, m = -6 - zn(3) = ( ( 28.00 ) *rho *rho *rho *rho *rho * rho + & - ( -72.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * sin(10.00 * phi) - ! n = 10, m = -4 - zn(4) = ( ( -35.00 ) *rho *rho *rho * rho + & - ( 168.00 ) *rho *rho *rho *rho *rho * rho + & - ( -252.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * sin(10.00 * phi) - ! n = 10, m = -2 - zn(5) = ( ( 15.00 ) *rho * rho + & - ( -140.00 ) *rho *rho *rho * rho + & - ( 420.00 ) *rho *rho *rho *rho *rho * rho + & - ( -504.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * sin(10.00 * phi) - ! n = 10, m = 0 - zn(6) = ( ( -1.00 ) + & - ( 30.00 ) *rho * rho + & - ( -210.00 ) *rho *rho *rho * rho + & - ( 560.00 ) *rho *rho *rho *rho *rho * rho + & - ( -630.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 252.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 3.316624790355 ) - ! n = 10, m = 2 - zn(7) = ( ( 15.00 ) *rho * rho + & - ( -140.00 ) *rho *rho *rho * rho + & - ( 420.00 ) *rho *rho *rho *rho *rho * rho + & - ( -504.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * cos(10.00 * phi) - ! n = 10, m = 4 - zn(8) = ( ( -35.00 ) *rho *rho *rho * rho + & - ( 168.00 ) *rho *rho *rho *rho *rho * rho + & - ( -252.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * cos(10.00 * phi) - ! n = 10, m = 6 - zn(9) = ( ( 28.00 ) *rho *rho *rho *rho *rho * rho + & - ( -72.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * cos(10.00 * phi) - ! n = 10, m = 8 - zn(10) = ( ( -9.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * cos(10.00 * phi) - ! n = 10, m = 10 - zn(11) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * cos(10.00 * phi) - case(11) - ! n = 11, m = -11 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = -9 - zn(2) = ( ( -10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = -7 - zn(3) = ( ( 36.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -90.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = -5 - zn(4) = ( ( -56.00 ) *rho *rho *rho *rho * rho + & - ( 252.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 165.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = -3 - zn(5) = ( ( 35.00 ) *rho *rho * rho + & - ( -280.00 ) *rho *rho *rho *rho * rho + & - ( 756.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 330.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = -1 - zn(6) = ( ( -6.00 ) * rho + & - ( 105.00 ) *rho *rho * rho + & - ( -560.00 ) *rho *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 462.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = 1 - zn(7) = ( ( -6.00 ) * rho + & - ( 105.00 ) *rho *rho * rho + & - ( -560.00 ) *rho *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 462.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - ! n = 11, m = 3 - zn(8) = ( ( 35.00 ) *rho *rho * rho + & - ( -280.00 ) *rho *rho *rho *rho * rho + & - ( 756.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 330.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - ! n = 11, m = 5 - zn(9) = ( ( -56.00 ) *rho *rho *rho *rho * rho + & - ( 252.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 165.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - ! n = 11, m = 7 - zn(10) = ( ( 36.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -90.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - ! n = 11, m = 9 - zn(11) = ( ( -10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - ! n = 11, m = 11 - zn(12) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - case(12) - ! n = 12, m = -12 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = -10 - zn(2) = ( ( -11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = -8 - zn(3) = ( ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -110.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = -6 - zn(4) = ( ( -84.00 ) *rho *rho *rho *rho *rho * rho + & - ( 360.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = -4 - zn(5) = ( ( 70.00 ) *rho *rho *rho * rho + & - ( -504.00 ) *rho *rho *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = -2 - zn(6) = ( ( -21.00 ) *rho * rho + & - ( 280.00 ) *rho *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho *rho * rho + & - ( 2520.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 792.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = 0 - zn(7) = ( ( 1.00 ) + & - ( -42.00 ) *rho * rho + & - ( 420.00 ) *rho *rho *rho * rho + & - ( -1680.00 ) *rho *rho *rho *rho *rho * rho + & - ( 3150.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2772.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 924.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 3.605551275464 ) - ! n = 12, m = 2 - zn(8) = ( ( -21.00 ) *rho * rho + & - ( 280.00 ) *rho *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho *rho * rho + & - ( 2520.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 792.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - ! n = 12, m = 4 - zn(9) = ( ( 70.00 ) *rho *rho *rho * rho + & - ( -504.00 ) *rho *rho *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - ! n = 12, m = 6 - zn(10) = ( ( -84.00 ) *rho *rho *rho *rho *rho * rho + & - ( 360.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - ! n = 12, m = 8 - zn(11) = ( ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -110.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - ! n = 12, m = 10 - zn(12) = ( ( -11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - ! n = 12, m = 12 - zn(13) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - case(13) - ! n = 13, m = -13 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -11 - zn(2) = ( ( -12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -9 - zn(3) = ( ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -132.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -7 - zn(4) = ( ( -120.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -5 - zn(5) = ( ( 126.00 ) *rho *rho *rho *rho * rho + & - ( -840.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -3 - zn(6) = ( ( -56.00 ) *rho *rho * rho + & - ( 630.00 ) *rho *rho *rho *rho * rho + & - ( -2520.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 4620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -3960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1287.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -1 - zn(7) = ( ( 7.00 ) * rho + & - ( -168.00 ) *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho * rho + & - ( -4200.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 6930.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5544.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1716.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = 1 - zn(8) = ( ( 7.00 ) * rho + & - ( -168.00 ) *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho * rho + & - ( -4200.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 6930.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5544.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1716.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 3 - zn(9) = ( ( -56.00 ) *rho *rho * rho + & - ( 630.00 ) *rho *rho *rho *rho * rho + & - ( -2520.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 4620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -3960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1287.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 5 - zn(10) = ( ( 126.00 ) *rho *rho *rho *rho * rho + & - ( -840.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 7 - zn(11) = ( ( -120.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 9 - zn(12) = ( ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -132.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 11 - zn(13) = ( ( -12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 13 - zn(14) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - case(14) - ! n = 14, m = -14 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -12 - zn(2) = ( ( -13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -10 - zn(3) = ( ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -156.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -8 - zn(4) = ( ( -165.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -6 - zn(5) = ( ( 210.00 ) *rho *rho *rho *rho *rho * rho + & - ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2970.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -4 - zn(6) = ( ( -126.00 ) *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho * rho + & - ( -4620.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2002.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -2 - zn(7) = ( ( 28.00 ) *rho * rho + & - ( -504.00 ) *rho *rho *rho * rho + & - ( 3150.00 ) *rho *rho *rho *rho *rho * rho + & - ( -9240.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = 0 - zn(8) = ( ( -1.00 ) + & - ( 56.00 ) *rho * rho + & - ( -756.00 ) *rho *rho *rho * rho + & - ( 4200.00 ) *rho *rho *rho *rho *rho * rho + & - ( -11550.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 16632.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -12012.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3432.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 3.872983346207 ) - ! n = 14, m = 2 - zn(9) = ( ( 28.00 ) *rho * rho + & - ( -504.00 ) *rho *rho *rho * rho + & - ( 3150.00 ) *rho *rho *rho *rho *rho * rho + & - ( -9240.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 4 - zn(10) = ( ( -126.00 ) *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho * rho + & - ( -4620.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2002.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 6 - zn(11) = ( ( 210.00 ) *rho *rho *rho *rho *rho * rho + & - ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2970.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 8 - zn(12) = ( ( -165.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 10 - zn(13) = ( ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -156.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 12 - zn(14) = ( ( -13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 14 - zn(15) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - case(15) - ! n = 15, m = -15 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -13 - zn(2) = ( ( -14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -11 - zn(3) = ( ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -182.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -9 - zn(4) = ( ( -220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -7 - zn(5) = ( ( 330.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 4290.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -5 - zn(6) = ( ( -252.00 ) *rho *rho *rho *rho * rho + & - ( 2310.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -3 - zn(7) = ( ( 84.00 ) *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho * rho + & - ( 6930.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -18480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -18018.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 5005.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -1 - zn(8) = ( ( -8.00 ) * rho + & - ( 252.00 ) *rho *rho * rho + & - ( -2520.00 ) *rho *rho *rho *rho * rho + & - ( 11550.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -27720.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 36036.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -24024.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = 1 - zn(9) = ( ( -8.00 ) * rho + & - ( 252.00 ) *rho *rho * rho + & - ( -2520.00 ) *rho *rho *rho *rho * rho + & - ( 11550.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -27720.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 36036.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -24024.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 3 - zn(10) = ( ( 84.00 ) *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho * rho + & - ( 6930.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -18480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -18018.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 5005.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 5 - zn(11) = ( ( -252.00 ) *rho *rho *rho *rho * rho + & - ( 2310.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 7 - zn(12) = ( ( 330.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 4290.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 9 - zn(13) = ( ( -220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 11 - zn(14) = ( ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -182.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 13 - zn(15) = ( ( -14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 15 - zn(16) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - case(16) - ! n = 16, m = -16 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -14 - zn(2) = ( ( -15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -12 - zn(3) = ( ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -10 - zn(4) = ( ( -286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 560.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -8 - zn(5) = ( ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6006.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1820.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -6 - zn(6) = ( ( -462.00 ) *rho *rho *rho *rho *rho * rho + & - ( 3960.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -15015.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 4368.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -4 - zn(7) = ( ( 210.00 ) *rho *rho *rho * rho + & - ( -2772.00 ) *rho *rho *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -34320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8008.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -2 - zn(8) = ( ( -36.00 ) *rho * rho + & - ( 840.00 ) *rho *rho *rho * rho + & - ( -6930.00 ) *rho *rho *rho *rho *rho * rho + & - ( 27720.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 11440.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = 0 - zn(9) = ( ( 1.00 ) + & - ( -72.00 ) *rho * rho + & - ( 1260.00 ) *rho *rho *rho * rho + & - ( -9240.00 ) *rho *rho *rho *rho *rho * rho + & - ( 34650.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 84084.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -51480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.123105625618 ) - ! n = 16, m = 2 - zn(10) = ( ( -36.00 ) *rho * rho + & - ( 840.00 ) *rho *rho *rho * rho + & - ( -6930.00 ) *rho *rho *rho *rho *rho * rho + & - ( 27720.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 11440.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 4 - zn(11) = ( ( 210.00 ) *rho *rho *rho * rho + & - ( -2772.00 ) *rho *rho *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -34320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8008.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 6 - zn(12) = ( ( -462.00 ) *rho *rho *rho *rho *rho * rho + & - ( 3960.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -15015.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 4368.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 8 - zn(13) = ( ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6006.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1820.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 10 - zn(14) = ( ( -286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 560.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 12 - zn(15) = ( ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 14 - zn(16) = ( ( -15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 16 - zn(17) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - case(17) - ! n = 17, m = -17 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -15 - zn(2) = ( ( -16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -13 - zn(3) = ( ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -11 - zn(4) = ( ( -364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -9 - zn(5) = ( ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8190.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -7280.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2380.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -7 - zn(6) = ( ( -792.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -21840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6188.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -5 - zn(7) = ( ( 462.00 ) *rho *rho *rho *rho * rho + & - ( -5544.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 75075.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -48048.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12376.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -3 - zn(8) = ( ( -120.00 ) *rho *rho * rho + & - ( 2310.00 ) *rho *rho *rho *rho * rho + & - ( -16632.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 135135.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -80080.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 19448.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -1 - zn(9) = ( ( 9.00 ) * rho + & - ( -360.00 ) *rho *rho * rho + & - ( 4620.00 ) *rho *rho *rho *rho * rho + & - ( -27720.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 90090.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -168168.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 180180.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -102960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 24310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = 1 - zn(10) = ( ( 9.00 ) * rho + & - ( -360.00 ) *rho *rho * rho + & - ( 4620.00 ) *rho *rho *rho *rho * rho + & - ( -27720.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 90090.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -168168.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 180180.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -102960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 24310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 3 - zn(11) = ( ( -120.00 ) *rho *rho * rho + & - ( 2310.00 ) *rho *rho *rho *rho * rho + & - ( -16632.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 135135.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -80080.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 19448.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 5 - zn(12) = ( ( 462.00 ) *rho *rho *rho *rho * rho + & - ( -5544.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 75075.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -48048.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12376.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 7 - zn(13) = ( ( -792.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -21840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6188.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 9 - zn(14) = ( ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8190.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -7280.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2380.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 11 - zn(15) = ( ( -364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 13 - zn(16) = ( ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 15 - zn(17) = ( ( -16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 17 - zn(18) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - case(18) - ! n = 18, m = -18 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -16 - zn(2) = ( ( -17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 18.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -14 - zn(3) = ( ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -272.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 153.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -12 - zn(4) = ( ( -455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2040.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 816.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -10 - zn(5) = ( ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -9520.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -8 - zn(6) = ( ( -1287.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 43680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30940.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8568.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -6 - zn(7) = ( ( 924.00 ) *rho *rho *rho *rho *rho * rho + & - ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -100100.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -74256.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 18564.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -4 - zn(8) = ( ( -330.00 ) *rho *rho *rho * rho + & - ( 5544.00 ) *rho *rho *rho *rho *rho * rho + & - ( -36036.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -225225.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 240240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -136136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 31824.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -2 - zn(9) = ( ( 45.00 ) *rho * rho + & - ( -1320.00 ) *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho * rho + & - ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 210210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -194480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 43758.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = 0 - zn(10) = ( ( -1.00 ) + & - ( 90.00 ) *rho * rho + & - ( -1980.00 ) *rho *rho *rho * rho + & - ( 18480.00 ) *rho *rho *rho *rho *rho * rho + & - ( -90090.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 252252.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -420420.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 411840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -218790.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 48620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.358898943541 ) - ! n = 18, m = 2 - zn(11) = ( ( 45.00 ) *rho * rho + & - ( -1320.00 ) *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho * rho + & - ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 210210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -194480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 43758.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 4 - zn(12) = ( ( -330.00 ) *rho *rho *rho * rho + & - ( 5544.00 ) *rho *rho *rho *rho *rho * rho + & - ( -36036.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -225225.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 240240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -136136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 31824.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 6 - zn(13) = ( ( 924.00 ) *rho *rho *rho *rho *rho * rho + & - ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -100100.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -74256.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 18564.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 8 - zn(14) = ( ( -1287.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 43680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30940.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8568.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 10 - zn(15) = ( ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -9520.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 12 - zn(16) = ( ( -455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2040.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 816.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 14 - zn(17) = ( ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -272.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 153.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 16 - zn(18) = ( ( -17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 18.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 18 - zn(19) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - case default - zn = ONE - end select - - end function calc_zn - -!=============================================================================== -! CALC_ZN_SCALED calculates the n-th order Zernike polynomial moment for a given -! angle (rho, theta) location in the unit disk, scaled correctly for orthogonal -! integration. -!=============================================================================== - - pure function calc_zn_scaled(n, rho, phi) result(zn) - - integer, intent(in) :: n ! Order requested - real(8), intent(in) :: rho ! Radial location in the unit disk - real(8), intent(in) :: phi ! Theta (radians) location in the unit disk - real(8) :: zn(n + 1) ! The resultant Z_n(uvw) - - zn = calc_zn(n, rho, phi) / SQRT_PI - - end function calc_zn_scaled - !=============================================================================== ! C API FUNCTIONS !=============================================================================== From 39bbb2de46617403998524beec05b988bb860bec Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 10 Jan 2018 15:45:00 -0600 Subject: [PATCH 143/361] Use fast Zernike algorithm from Chong --- src/math.F90 | 1203 ++------------------------ src/tallies/tally_filter_zernike.F90 | 21 +- 2 files changed, 88 insertions(+), 1136 deletions(-) diff --git a/src/math.F90 b/src/math.F90 index b96562734..1d7d0bfaf 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -579,1136 +579,95 @@ contains ! (rho, theta) location in the unit disk. !=============================================================================== - pure function calc_zn(n, rho, phi) result(zn) + subroutine calc_zn(n, rho, phi, zn) + ! This efficient method for calculation R(m,n) is taken from + ! Chong, C. W., Raveendran, P., & Mukundan, R. (2003). A comparative + ! analysis of algorithms for fast computation of Zernike moments. + ! Pattern Recognition, 36(3), 731-742. - integer, intent(in) :: n ! Order requested + integer, intent(in) :: n ! Maximum order real(8), intent(in) :: rho ! Radial location in the unit disk real(8), intent(in) :: phi ! Theta (radians) location in the unit disk - real(8) :: zn(n + 1) ! The resultant Z_n(uvw) + real(8), intent(out) :: zn(:) ! The resulting list of coefficients + + real(8) :: sin_phi, cos_phi ! Sine and Cosine of phi + real(8) :: sin_phi_vec(n+1) ! Contains sin(n*phi) + real(8) :: cos_phi_vec(n+1) ! Contains cos(n*phi) + real(8) :: zn_mat(n+1, n+1) ! Matrix form of the coefficients which is + ! easier to work with + real(8) :: k1, k2, k3, k4 ! Variables for R_m_n calculation + real(8) :: sqrt_norm ! normalization for radial moments + integer :: i,p,q ! Loop counters + + real(8), parameter :: SQRT_N_1(0:10) = [& + sqrt(1.0_8), sqrt(2.0_8), sqrt(3.0_8), sqrt(4.0_8), & + sqrt(5.0_8), sqrt(6.0_8), sqrt(7.0_8), sqrt(8.0_8), & + sqrt(9.0_8), sqrt(10.0_8), sqrt(11.0_8)] + real(8), parameter :: SQRT_2N_2(0:10) = SQRT_N_1*sqrt(2.0_8) ! n == radial degree ! m == azimuthal frequency - select case(n) - case(0) - ! n = 0, m = 0 - zn(1) = ( ( 1.00 ) ) & - * ( 1.000000000000 ) - case(1) - ! n = 1, m = -1 - zn(1) = ( ( 1.00 ) * rho ) & - * ( 2.000000000000 ) * sin(1.00 * phi) - ! n = 1, m = 1 - zn(2) = ( ( 1.00 ) * rho ) & - * ( 2.000000000000 ) * cos(1.00 * phi) - case(2) - ! n = 2, m = -2 - zn(1) = ( ( 1.00 ) *rho * rho ) & - * ( 2.449489742783 ) * sin(2.00 * phi) - ! n = 2, m = 0 - zn(2) = ( ( -1.00 ) + & - ( 2.00 ) *rho * rho ) & - * ( 1.732050807569 ) - ! n = 2, m = 2 - zn(3) = ( ( 1.00 ) *rho * rho ) & - * ( 2.449489742783 ) * cos(2.00 * phi) - case(3) - ! n = 3, m = -3 - zn(1) = ( ( 1.00 ) *rho *rho * rho ) & - * ( 2.828427124746 ) * sin(3.00 * phi) - ! n = 3, m = -1 - zn(2) = ( ( -2.00 ) * rho + & - ( 3.00 ) *rho *rho * rho ) & - * ( 2.828427124746 ) * sin(3.00 * phi) - ! n = 3, m = 1 - zn(3) = ( ( -2.00 ) * rho + & - ( 3.00 ) *rho *rho * rho ) & - * ( 2.828427124746 ) * cos(3.00 * phi) - ! n = 3, m = 3 - zn(4) = ( ( 1.00 ) *rho *rho * rho ) & - * ( 2.828427124746 ) * cos(3.00 * phi) - case(4) - ! n = 4, m = -4 - zn(1) = ( ( 1.00 ) *rho *rho *rho * rho ) & - * ( 3.162277660168 ) * sin(4.00 * phi) - ! n = 4, m = -2 - zn(2) = ( ( -3.00 ) *rho * rho + & - ( 4.00 ) *rho *rho *rho * rho ) & - * ( 3.162277660168 ) * sin(4.00 * phi) - ! n = 4, m = 0 - zn(3) = ( ( 1.00 ) + & - ( -6.00 ) *rho * rho + & - ( 6.00 ) *rho *rho *rho * rho ) & - * ( 2.236067977500 ) - ! n = 4, m = 2 - zn(4) = ( ( -3.00 ) *rho * rho + & - ( 4.00 ) *rho *rho *rho * rho ) & - * ( 3.162277660168 ) * cos(4.00 * phi) - ! n = 4, m = 4 - zn(5) = ( ( 1.00 ) *rho *rho *rho * rho ) & - * ( 3.162277660168 ) * cos(4.00 * phi) - case(5) - ! n = 5, m = -5 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * sin(5.00 * phi) - ! n = 5, m = -3 - zn(2) = ( ( -4.00 ) *rho *rho * rho + & - ( 5.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * sin(5.00 * phi) - ! n = 5, m = -1 - zn(3) = ( ( 3.00 ) * rho + & - ( -12.00 ) *rho *rho * rho + & - ( 10.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * sin(5.00 * phi) - ! n = 5, m = 1 - zn(4) = ( ( 3.00 ) * rho + & - ( -12.00 ) *rho *rho * rho + & - ( 10.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * cos(5.00 * phi) - ! n = 5, m = 3 - zn(5) = ( ( -4.00 ) *rho *rho * rho + & - ( 5.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * cos(5.00 * phi) - ! n = 5, m = 5 - zn(6) = ( ( 1.00 ) *rho *rho *rho *rho * rho ) & - * ( 3.464101615138 ) * cos(5.00 * phi) - case(6) - ! n = 6, m = -6 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * sin(6.00 * phi) - ! n = 6, m = -4 - zn(2) = ( ( -5.00 ) *rho *rho *rho * rho + & - ( 6.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * sin(6.00 * phi) - ! n = 6, m = -2 - zn(3) = ( ( 6.00 ) *rho * rho + & - ( -20.00 ) *rho *rho *rho * rho + & - ( 15.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * sin(6.00 * phi) - ! n = 6, m = 0 - zn(4) = ( ( -1.00 ) + & - ( 12.00 ) *rho * rho + & - ( -30.00 ) *rho *rho *rho * rho + & - ( 20.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 2.645751311065 ) - ! n = 6, m = 2 - zn(5) = ( ( 6.00 ) *rho * rho + & - ( -20.00 ) *rho *rho *rho * rho + & - ( 15.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * cos(6.00 * phi) - ! n = 6, m = 4 - zn(6) = ( ( -5.00 ) *rho *rho *rho * rho + & - ( 6.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * cos(6.00 * phi) - ! n = 6, m = 6 - zn(7) = ( ( 1.00 ) *rho *rho *rho *rho *rho * rho ) & - * ( 3.741657386774 ) * cos(6.00 * phi) - case(7) - ! n = 7, m = -7 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * sin(7.00 * phi) - ! n = 7, m = -5 - zn(2) = ( ( -6.00 ) *rho *rho *rho *rho * rho + & - ( 7.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * sin(7.00 * phi) - ! n = 7, m = -3 - zn(3) = ( ( 10.00 ) *rho *rho * rho + & - ( -30.00 ) *rho *rho *rho *rho * rho + & - ( 21.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * sin(7.00 * phi) - ! n = 7, m = -1 - zn(4) = ( ( -4.00 ) * rho + & - ( 30.00 ) *rho *rho * rho + & - ( -60.00 ) *rho *rho *rho *rho * rho + & - ( 35.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * sin(7.00 * phi) - ! n = 7, m = 1 - zn(5) = ( ( -4.00 ) * rho + & - ( 30.00 ) *rho *rho * rho + & - ( -60.00 ) *rho *rho *rho *rho * rho + & - ( 35.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * cos(7.00 * phi) - ! n = 7, m = 3 - zn(6) = ( ( 10.00 ) *rho *rho * rho + & - ( -30.00 ) *rho *rho *rho *rho * rho + & - ( 21.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * cos(7.00 * phi) - ! n = 7, m = 5 - zn(7) = ( ( -6.00 ) *rho *rho *rho *rho * rho + & - ( 7.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * cos(7.00 * phi) - ! n = 7, m = 7 - zn(8) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.000000000000 ) * cos(7.00 * phi) - case(8) - ! n = 8, m = -8 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * sin(8.00 * phi) - ! n = 8, m = -6 - zn(2) = ( ( -7.00 ) *rho *rho *rho *rho *rho * rho + & - ( 8.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * sin(8.00 * phi) - ! n = 8, m = -4 - zn(3) = ( ( 15.00 ) *rho *rho *rho * rho + & - ( -42.00 ) *rho *rho *rho *rho *rho * rho + & - ( 28.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * sin(8.00 * phi) - ! n = 8, m = -2 - zn(4) = ( ( -10.00 ) *rho * rho + & - ( 60.00 ) *rho *rho *rho * rho + & - ( -105.00 ) *rho *rho *rho *rho *rho * rho + & - ( 56.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * sin(8.00 * phi) - ! n = 8, m = 0 - zn(5) = ( ( 1.00 ) + & - ( -20.00 ) *rho * rho + & - ( 90.00 ) *rho *rho *rho * rho + & - ( -140.00 ) *rho *rho *rho *rho *rho * rho + & - ( 70.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 3.000000000000 ) - ! n = 8, m = 2 - zn(6) = ( ( -10.00 ) *rho * rho + & - ( 60.00 ) *rho *rho *rho * rho + & - ( -105.00 ) *rho *rho *rho *rho *rho * rho + & - ( 56.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * cos(8.00 * phi) - ! n = 8, m = 4 - zn(7) = ( ( 15.00 ) *rho *rho *rho * rho + & - ( -42.00 ) *rho *rho *rho *rho *rho * rho + & - ( 28.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * cos(8.00 * phi) - ! n = 8, m = 6 - zn(8) = ( ( -7.00 ) *rho *rho *rho *rho *rho * rho + & - ( 8.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * cos(8.00 * phi) - ! n = 8, m = 8 - zn(9) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.242640687119 ) * cos(8.00 * phi) - case(9) - ! n = 9, m = -9 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * sin(9.00 * phi) - ! n = 9, m = -7 - zn(2) = ( ( -8.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 9.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * sin(9.00 * phi) - ! n = 9, m = -5 - zn(3) = ( ( 21.00 ) *rho *rho *rho *rho * rho + & - ( -56.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 36.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * sin(9.00 * phi) - ! n = 9, m = -3 - zn(4) = ( ( -20.00 ) *rho *rho * rho + & - ( 105.00 ) *rho *rho *rho *rho * rho + & - ( -168.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 84.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * sin(9.00 * phi) - ! n = 9, m = -1 - zn(5) = ( ( 5.00 ) * rho + & - ( -60.00 ) *rho *rho * rho + & - ( 210.00 ) *rho *rho *rho *rho * rho + & - ( -280.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 126.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * sin(9.00 * phi) - ! n = 9, m = 1 - zn(6) = ( ( 5.00 ) * rho + & - ( -60.00 ) *rho *rho * rho + & - ( 210.00 ) *rho *rho *rho *rho * rho + & - ( -280.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 126.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * cos(9.00 * phi) - ! n = 9, m = 3 - zn(7) = ( ( -20.00 ) *rho *rho * rho + & - ( 105.00 ) *rho *rho *rho *rho * rho + & - ( -168.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 84.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * cos(9.00 * phi) - ! n = 9, m = 5 - zn(8) = ( ( 21.00 ) *rho *rho *rho *rho * rho + & - ( -56.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 36.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * cos(9.00 * phi) - ! n = 9, m = 7 - zn(9) = ( ( -8.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 9.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * cos(9.00 * phi) - ! n = 9, m = 9 - zn(10) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.472135955000 ) * cos(9.00 * phi) - case(10) - ! n = 10, m = -10 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * sin(10.00 * phi) - ! n = 10, m = -8 - zn(2) = ( ( -9.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * sin(10.00 * phi) - ! n = 10, m = -6 - zn(3) = ( ( 28.00 ) *rho *rho *rho *rho *rho * rho + & - ( -72.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * sin(10.00 * phi) - ! n = 10, m = -4 - zn(4) = ( ( -35.00 ) *rho *rho *rho * rho + & - ( 168.00 ) *rho *rho *rho *rho *rho * rho + & - ( -252.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * sin(10.00 * phi) - ! n = 10, m = -2 - zn(5) = ( ( 15.00 ) *rho * rho + & - ( -140.00 ) *rho *rho *rho * rho + & - ( 420.00 ) *rho *rho *rho *rho *rho * rho + & - ( -504.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * sin(10.00 * phi) - ! n = 10, m = 0 - zn(6) = ( ( -1.00 ) + & - ( 30.00 ) *rho * rho + & - ( -210.00 ) *rho *rho *rho * rho + & - ( 560.00 ) *rho *rho *rho *rho *rho * rho + & - ( -630.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 252.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 3.316624790355 ) - ! n = 10, m = 2 - zn(7) = ( ( 15.00 ) *rho * rho + & - ( -140.00 ) *rho *rho *rho * rho + & - ( 420.00 ) *rho *rho *rho *rho *rho * rho + & - ( -504.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * cos(10.00 * phi) - ! n = 10, m = 4 - zn(8) = ( ( -35.00 ) *rho *rho *rho * rho + & - ( 168.00 ) *rho *rho *rho *rho *rho * rho + & - ( -252.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * cos(10.00 * phi) - ! n = 10, m = 6 - zn(9) = ( ( 28.00 ) *rho *rho *rho *rho *rho * rho + & - ( -72.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * cos(10.00 * phi) - ! n = 10, m = 8 - zn(10) = ( ( -9.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * cos(10.00 * phi) - ! n = 10, m = 10 - zn(11) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.690415759823 ) * cos(10.00 * phi) - case(11) - ! n = 11, m = -11 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = -9 - zn(2) = ( ( -10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = -7 - zn(3) = ( ( 36.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -90.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = -5 - zn(4) = ( ( -56.00 ) *rho *rho *rho *rho * rho + & - ( 252.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 165.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = -3 - zn(5) = ( ( 35.00 ) *rho *rho * rho + & - ( -280.00 ) *rho *rho *rho *rho * rho + & - ( 756.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 330.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = -1 - zn(6) = ( ( -6.00 ) * rho + & - ( 105.00 ) *rho *rho * rho + & - ( -560.00 ) *rho *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 462.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * sin(11.00 * phi) - ! n = 11, m = 1 - zn(7) = ( ( -6.00 ) * rho + & - ( 105.00 ) *rho *rho * rho + & - ( -560.00 ) *rho *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 462.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - ! n = 11, m = 3 - zn(8) = ( ( 35.00 ) *rho *rho * rho + & - ( -280.00 ) *rho *rho *rho *rho * rho + & - ( 756.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 330.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - ! n = 11, m = 5 - zn(9) = ( ( -56.00 ) *rho *rho *rho *rho * rho + & - ( 252.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 165.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - ! n = 11, m = 7 - zn(10) = ( ( 36.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -90.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - ! n = 11, m = 9 - zn(11) = ( ( -10.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - ! n = 11, m = 11 - zn(12) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.898979485566 ) * cos(11.00 * phi) - case(12) - ! n = 12, m = -12 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = -10 - zn(2) = ( ( -11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = -8 - zn(3) = ( ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -110.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = -6 - zn(4) = ( ( -84.00 ) *rho *rho *rho *rho *rho * rho + & - ( 360.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = -4 - zn(5) = ( ( 70.00 ) *rho *rho *rho * rho + & - ( -504.00 ) *rho *rho *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = -2 - zn(6) = ( ( -21.00 ) *rho * rho + & - ( 280.00 ) *rho *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho *rho * rho + & - ( 2520.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 792.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * sin(12.00 * phi) - ! n = 12, m = 0 - zn(7) = ( ( 1.00 ) + & - ( -42.00 ) *rho * rho + & - ( 420.00 ) *rho *rho *rho * rho + & - ( -1680.00 ) *rho *rho *rho *rho *rho * rho + & - ( 3150.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2772.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 924.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 3.605551275464 ) - ! n = 12, m = 2 - zn(8) = ( ( -21.00 ) *rho * rho + & - ( 280.00 ) *rho *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho *rho * rho + & - ( 2520.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 792.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - ! n = 12, m = 4 - zn(9) = ( ( 70.00 ) *rho *rho *rho * rho + & - ( -504.00 ) *rho *rho *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - ! n = 12, m = 6 - zn(10) = ( ( -84.00 ) *rho *rho *rho *rho *rho * rho + & - ( 360.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - ! n = 12, m = 8 - zn(11) = ( ( 45.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -110.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - ! n = 12, m = 10 - zn(12) = ( ( -11.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - ! n = 12, m = 12 - zn(13) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.099019513593 ) * cos(12.00 * phi) - case(13) - ! n = 13, m = -13 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -11 - zn(2) = ( ( -12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -9 - zn(3) = ( ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -132.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -7 - zn(4) = ( ( -120.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -5 - zn(5) = ( ( 126.00 ) *rho *rho *rho *rho * rho + & - ( -840.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -3 - zn(6) = ( ( -56.00 ) *rho *rho * rho + & - ( 630.00 ) *rho *rho *rho *rho * rho + & - ( -2520.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 4620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -3960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1287.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = -1 - zn(7) = ( ( 7.00 ) * rho + & - ( -168.00 ) *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho * rho + & - ( -4200.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 6930.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5544.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1716.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * sin(13.00 * phi) - ! n = 13, m = 1 - zn(8) = ( ( 7.00 ) * rho + & - ( -168.00 ) *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho * rho + & - ( -4200.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 6930.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5544.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1716.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 3 - zn(9) = ( ( -56.00 ) *rho *rho * rho + & - ( 630.00 ) *rho *rho *rho *rho * rho + & - ( -2520.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 4620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -3960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1287.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 5 - zn(10) = ( ( 126.00 ) *rho *rho *rho *rho * rho + & - ( -840.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 7 - zn(11) = ( ( -120.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 9 - zn(12) = ( ( 55.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -132.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 11 - zn(13) = ( ( -12.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - ! n = 13, m = 13 - zn(14) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.291502622129 ) * cos(13.00 * phi) - case(14) - ! n = 14, m = -14 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -12 - zn(2) = ( ( -13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -10 - zn(3) = ( ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -156.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -8 - zn(4) = ( ( -165.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -6 - zn(5) = ( ( 210.00 ) *rho *rho *rho *rho *rho * rho + & - ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2970.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -4 - zn(6) = ( ( -126.00 ) *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho * rho + & - ( -4620.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2002.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = -2 - zn(7) = ( ( 28.00 ) *rho * rho + & - ( -504.00 ) *rho *rho *rho * rho + & - ( 3150.00 ) *rho *rho *rho *rho *rho * rho + & - ( -9240.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * sin(14.00 * phi) - ! n = 14, m = 0 - zn(8) = ( ( -1.00 ) + & - ( 56.00 ) *rho * rho + & - ( -756.00 ) *rho *rho *rho * rho + & - ( 4200.00 ) *rho *rho *rho *rho *rho * rho + & - ( -11550.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 16632.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -12012.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3432.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 3.872983346207 ) - ! n = 14, m = 2 - zn(9) = ( ( 28.00 ) *rho * rho + & - ( -504.00 ) *rho *rho *rho * rho + & - ( 3150.00 ) *rho *rho *rho *rho *rho * rho + & - ( -9240.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 4 - zn(10) = ( ( -126.00 ) *rho *rho *rho * rho + & - ( 1260.00 ) *rho *rho *rho *rho *rho * rho + & - ( -4620.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2002.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 6 - zn(11) = ( ( 210.00 ) *rho *rho *rho *rho *rho * rho + & - ( -1320.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2970.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 8 - zn(12) = ( ( -165.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 660.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 10 - zn(13) = ( ( 66.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -156.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 12 - zn(14) = ( ( -13.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - ! n = 14, m = 14 - zn(15) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.477225575052 ) * cos(14.00 * phi) - case(15) - ! n = 15, m = -15 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -13 - zn(2) = ( ( -14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -11 - zn(3) = ( ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -182.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -9 - zn(4) = ( ( -220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -7 - zn(5) = ( ( 330.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 4290.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -5 - zn(6) = ( ( -252.00 ) *rho *rho *rho *rho * rho + & - ( 2310.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -3 - zn(7) = ( ( 84.00 ) *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho * rho + & - ( 6930.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -18480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -18018.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 5005.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = -1 - zn(8) = ( ( -8.00 ) * rho + & - ( 252.00 ) *rho *rho * rho + & - ( -2520.00 ) *rho *rho *rho *rho * rho + & - ( 11550.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -27720.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 36036.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -24024.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * sin(15.00 * phi) - ! n = 15, m = 1 - zn(9) = ( ( -8.00 ) * rho + & - ( 252.00 ) *rho *rho * rho + & - ( -2520.00 ) *rho *rho *rho *rho * rho + & - ( 11550.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -27720.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 36036.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -24024.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 3 - zn(10) = ( ( 84.00 ) *rho *rho * rho + & - ( -1260.00 ) *rho *rho *rho *rho * rho + & - ( 6930.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -18480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -18018.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 5005.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 5 - zn(11) = ( ( -252.00 ) *rho *rho *rho *rho * rho + & - ( 2310.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -7920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3003.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 7 - zn(12) = ( ( 330.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( -1980.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 4290.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 9 - zn(13) = ( ( -220.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 858.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 11 - zn(14) = ( ( 78.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -182.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 13 - zn(15) = ( ( -14.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - ! n = 15, m = 15 - zn(16) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.656854249492 ) * cos(15.00 * phi) - case(16) - ! n = 16, m = -16 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -14 - zn(2) = ( ( -15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -12 - zn(3) = ( ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -10 - zn(4) = ( ( -286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 560.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -8 - zn(5) = ( ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6006.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1820.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -6 - zn(6) = ( ( -462.00 ) *rho *rho *rho *rho *rho * rho + & - ( 3960.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -15015.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 4368.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -4 - zn(7) = ( ( 210.00 ) *rho *rho *rho * rho + & - ( -2772.00 ) *rho *rho *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -34320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8008.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = -2 - zn(8) = ( ( -36.00 ) *rho * rho + & - ( 840.00 ) *rho *rho *rho * rho + & - ( -6930.00 ) *rho *rho *rho *rho *rho * rho + & - ( 27720.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 11440.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * sin(16.00 * phi) - ! n = 16, m = 0 - zn(9) = ( ( 1.00 ) + & - ( -72.00 ) *rho * rho + & - ( 1260.00 ) *rho *rho *rho * rho + & - ( -9240.00 ) *rho *rho *rho *rho *rho * rho + & - ( 34650.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 84084.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -51480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.123105625618 ) - ! n = 16, m = 2 - zn(10) = ( ( -36.00 ) *rho * rho + & - ( 840.00 ) *rho *rho *rho * rho + & - ( -6930.00 ) *rho *rho *rho *rho *rho * rho + & - ( 27720.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 72072.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 11440.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 4 - zn(11) = ( ( 210.00 ) *rho *rho *rho * rho + & - ( -2772.00 ) *rho *rho *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -34320.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8008.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 6 - zn(12) = ( ( -462.00 ) *rho *rho *rho *rho *rho * rho + & - ( 3960.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -12870.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -15015.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 4368.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 8 - zn(13) = ( ( 495.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2860.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6006.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1820.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 10 - zn(14) = ( ( -286.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1092.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 560.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 12 - zn(15) = ( ( 91.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 14 - zn(16) = ( ( -15.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - ! n = 16, m = 16 - zn(17) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 5.830951894845 ) * cos(16.00 * phi) - case(17) - ! n = 17, m = -17 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -15 - zn(2) = ( ( -16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -13 - zn(3) = ( ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -11 - zn(4) = ( ( -364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -9 - zn(5) = ( ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8190.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -7280.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2380.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -7 - zn(6) = ( ( -792.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -21840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6188.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -5 - zn(7) = ( ( 462.00 ) *rho *rho *rho *rho * rho + & - ( -5544.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 75075.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -48048.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12376.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -3 - zn(8) = ( ( -120.00 ) *rho *rho * rho + & - ( 2310.00 ) *rho *rho *rho *rho * rho + & - ( -16632.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 135135.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -80080.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 19448.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = -1 - zn(9) = ( ( 9.00 ) * rho + & - ( -360.00 ) *rho *rho * rho + & - ( 4620.00 ) *rho *rho *rho *rho * rho + & - ( -27720.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 90090.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -168168.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 180180.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -102960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 24310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * sin(17.00 * phi) - ! n = 17, m = 1 - zn(10) = ( ( 9.00 ) * rho + & - ( -360.00 ) *rho *rho * rho + & - ( 4620.00 ) *rho *rho *rho *rho * rho + & - ( -27720.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 90090.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -168168.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 180180.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -102960.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 24310.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 3 - zn(11) = ( ( -120.00 ) *rho *rho * rho + & - ( 2310.00 ) *rho *rho *rho *rho * rho + & - ( -16632.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 135135.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -80080.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 19448.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 5 - zn(12) = ( ( 462.00 ) *rho *rho *rho *rho * rho + & - ( -5544.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 25740.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -60060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 75075.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -48048.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 12376.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 7 - zn(13) = ( ( -792.00 ) *rho *rho *rho *rho *rho *rho * rho + & - ( 6435.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -20020.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -21840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 6188.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 9 - zn(14) = ( ( 715.00 ) *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -4004.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8190.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -7280.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 2380.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 11 - zn(15) = ( ( -364.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1365.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 13 - zn(16) = ( ( 105.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 15 - zn(17) = ( ( -16.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - ! n = 17, m = 17 - zn(18) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.000000000000 ) * cos(17.00 * phi) - case(18) - ! n = 18, m = -18 - zn(1) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -16 - zn(2) = ( ( -17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 18.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -14 - zn(3) = ( ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -272.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 153.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -12 - zn(4) = ( ( -455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2040.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 816.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -10 - zn(5) = ( ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -9520.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -8 - zn(6) = ( ( -1287.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 43680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30940.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8568.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -6 - zn(7) = ( ( 924.00 ) *rho *rho *rho *rho *rho * rho + & - ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -100100.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -74256.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 18564.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -4 - zn(8) = ( ( -330.00 ) *rho *rho *rho * rho + & - ( 5544.00 ) *rho *rho *rho *rho *rho * rho + & - ( -36036.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -225225.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 240240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -136136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 31824.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = -2 - zn(9) = ( ( 45.00 ) *rho * rho + & - ( -1320.00 ) *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho * rho + & - ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 210210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -194480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 43758.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * sin(18.00 * phi) - ! n = 18, m = 0 - zn(10) = ( ( -1.00 ) + & - ( 90.00 ) *rho * rho + & - ( -1980.00 ) *rho *rho *rho * rho + & - ( 18480.00 ) *rho *rho *rho *rho *rho * rho + & - ( -90090.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 252252.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -420420.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 411840.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -218790.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 48620.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 4.358898943541 ) - ! n = 18, m = 2 - zn(11) = ( ( 45.00 ) *rho * rho + & - ( -1320.00 ) *rho *rho *rho * rho + & - ( 13860.00 ) *rho *rho *rho *rho *rho * rho + & - ( -72072.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 210210.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 360360.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -194480.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 43758.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 4 - zn(12) = ( ( -330.00 ) *rho *rho *rho * rho + & - ( 5544.00 ) *rho *rho *rho *rho *rho * rho + & - ( -36036.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -225225.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 240240.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -136136.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 31824.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 6 - zn(13) = ( ( 924.00 ) *rho *rho *rho *rho *rho * rho + & - ( -10296.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 45045.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -100100.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 120120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -74256.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 18564.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 8 - zn(14) = ( ( -1287.00 ) *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10010.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30030.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 43680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -30940.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 8568.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 10 - zn(15) = ( ( 1001.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -5460.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 10920.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -9520.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 3060.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 12 - zn(16) = ( ( -455.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 1680.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -2040.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 816.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 14 - zn(17) = ( ( 120.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( -272.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 153.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 16 - zn(18) = ( ( -17.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho + & - ( 18.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - ! n = 18, m = 18 - zn(19) = ( ( 1.00 ) *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho *rho * rho ) & - * ( 6.164414002969 ) * cos(18.00 * phi) - case default - zn = ONE - end select + ! Deterine vector of sin(n*phi) and cos(n*phi) + sin_phi = sin(phi) + cos_phi = cos(phi) - end function calc_zn + sin_phi_vec(1) = 1.0_8 + cos_phi_vec(1) = 1.0_8 + + sin_phi_vec(2) = 2.0_8 * cos_phi + cos_phi_vec(2) = cos_phi + + do i = 3, n+1 + sin_phi_vec(i) = 2.0_8 * cos_phi * sin_phi_vec(i-1) - sin_phi_vec(i-2) + cos_phi_vec(i) = 2.0_8 * cos_phi * cos_phi_vec(i-1) - cos_phi_vec(i-2) + end do + + do i = 1, n+1 + sin_phi_vec(i) = sin_phi_vec(i) * sin_phi + end do + + ! Calculate R_m_n(rho) + ! Fill the main diagonal first + do p = 0, n + zn_mat(p+1, p+1) = rho**p + end do + + ! Fill in the second diagonal + do q = 0, n-2 + zn_mat(q+2+1, q+1) = (q+2) * zn_mat(q+2+1, q+2+1) - (q+1) * zn_mat(q+1, q+1) + end do + ! Fill in the rest of the values using the original results + do p = 4, n + k2 = 2 * p * (p - 1) * (p - 2) + do q = p-4, 0, -2 + k1 = (p + q) * (p - q) * (p - 2) / 2 + k3 = -q**2*(p - 1) - p * (p - 1) * (p - 2) + k4 = -p * (p + q - 2) * (p - q - 2) / 2 + zn_mat(p+1, q+1) = ((k2 * rho**2 + k3) * zn_mat(p-2+1, q+1) + k4 * zn_mat(p-4+1, q+1)) / k1 + end do + end do + + ! Roll into a single vector for easier computation later + ! The vector is ordered (0,0), (1,-1), (1,1), (2,-2), (2,0), + ! (2, 2), .... in (n,m) indices + ! Note that the cos and sin vectors are offest by one + ! sin_phi_vec = [sin(x), sin(2x), sin(3x) ...] + ! cos_phi_vec = [1.0, cos(x), cos(2x)... ] + i = 1 + do p = 0, n + do q = -p, p, 2 + if (q < 0) then + zn(i) = zn_mat(p+1, abs(q)+1) * sin_phi_vec(p) * SQRT_2N_2(p) + else if (q == 0) then + zn(i) = zn_mat(p+1, q+1) * SQRT_N_1(p) + else + zn(i) = zn_mat(p+1, q+1) * cos_phi_vec(p+1) * SQRT_2N_2(p) + end if + i = i + 1 + end do + end do + end subroutine calc_zn !=============================================================================== ! EXPAND_HARMONIC expands a given series of real spherical harmonics diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 index 08bc24e60..91619827a 100644 --- a/src/tallies/tally_filter_zernike.F90 +++ b/src/tallies/tally_filter_zernike.F90 @@ -61,12 +61,10 @@ contains integer, intent(in) :: estimator type(TallyFilterMatch), intent(inout) :: match - integer :: n - integer :: j integer :: i - real(8) :: wgt - real(8) :: tmp(this % order + 1) + integer :: n real(8) :: x, y, r, theta + real(8) :: zn(this % n_bins) ! Determine normalized (r,theta) positions x = p % coord(1) % xyz(1) - this % x @@ -74,17 +72,12 @@ contains r = sqrt(x*x + y*y)/this % r theta = atan2(y, x) - i = 0 - do n = 0, this % order - ! Get moments for n-th order Zernike polynomial - tmp(1:n+1) = calc_zn(n, r, theta) / SQRT_PI + ! Get moments for Zernike polynomial orders 0..n + call calc_zn(this % order, r, theta, zn) - ! Indicate matching bins/weights - do j = 1, n + 1 - call match % bins % push_back(i + j) - call match % weights % push_back(tmp(j)) - end do - i = i + n + 1 + do i = 1, this % n_bins + call match % bins % push_back(i) + call match % weights % push_back(zn(i) / SQRT_PI) end do end subroutine get_all_bins From d41ac4846763db146642ea32374bc0acfee6e5b2 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 28 Mar 2018 15:56:03 -0500 Subject: [PATCH 144/361] Force all filters to have a .bins attribute that makes sense --- openmc/checkvalue.py | 4 +- openmc/filter.py | 695 +++++++----------- openmc/filter_legendre.py | 43 +- openmc/filter_spatial_legendre.py | 37 +- openmc/filter_spherical_harmonics.py | 46 +- openmc/filter_zernike.py | 45 +- openmc/mesh.py | 3 + openmc/mgxs/mgxs.py | 12 +- openmc/tallies.py | 195 ++--- .../tally_slice_merge/test.py | 6 +- 10 files changed, 350 insertions(+), 736 deletions(-) diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index 2f80ee4c0..3b334319f 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -196,7 +196,7 @@ def check_less_than(name, value, maximum, equality=False): maximum : object Maximum value to check against equality : bool, optional - Whether equality is allowed. Defaluts to False. + Whether equality is allowed. Defaults to False. """ @@ -223,7 +223,7 @@ def check_greater_than(name, value, minimum, equality=False): minimum : object Minimum value to check against equality : bool, optional - Whether equality is allowed. Defaluts to False. + Whether equality is allowed. Defaults to False. """ diff --git a/openmc/filter.py b/openmc/filter.py index c1b9f8dc9..d59d739a3 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -3,6 +3,7 @@ from collections import Iterable, OrderedDict import copy from functools import reduce import hashlib +from itertools import product from numbers import Real, Integral import operator from xml.etree import ElementTree as ET @@ -192,9 +193,6 @@ class Filter(IDManagerMixin, metaclass=FilterMeta): @bins.setter def bins(self, bins): - # Format the bins as a 1D numpy array. - bins = np.atleast_1d(bins) - # Check the bin values. self.check_bins(bins) @@ -221,8 +219,6 @@ class Filter(IDManagerMixin, metaclass=FilterMeta): XML element containing filter data """ - - element = ET.Element('filter') element.set('id', str(self.id)) element.set('type', self.short_name.lower()) @@ -332,7 +328,10 @@ class Filter(IDManagerMixin, metaclass=FilterMeta): 'is not one of the bins'.format(filter_bin) raise ValueError(msg) - return np.where(self.bins == filter_bin)[0][0] + if isinstance(self.bins, np.ndarray): + return np.where(self.bins == filter_bin)[0][0] + else: + return self.bins.index(filter_bin) def get_bin(self, bin_index): """Returns the filter bin for some filter bin index. @@ -423,25 +422,24 @@ class Filter(IDManagerMixin, metaclass=FilterMeta): class WithIDFilter(Filter): - """Abstract parent for filters of types with ids (Cell, Material, etc.).""" - - @Filter.bins.setter - def bins(self, bins): - # Format the bins as a 1D numpy array. + """Abstract parent for filters of types with IDs (Cell, Material, etc.).""" + def __init__(self, bins, filter_id=None): bins = np.atleast_1d(bins) - # Check the bin values. + # Make sure bins are either integers or appropriate objects cv.check_iterable_type('filter bins', bins, (Integral, self.expected_type)) + + # Extract ID values + bins = np.array([b if isinstance(b, Integral) else b.id + for b in bins]) + self.bins = bins + self.id = filter_id + + def check_bins(self, bins): + # Check the bin values. for edge in bins: - if isinstance(edge, Integral): - cv.check_greater_than('filter bin', edge, 0, equality=True) - - # Extract id values. - bins = np.atleast_1d([b if isinstance(b, Integral) else b.id - for b in bins]) - - self._bins = bins + cv.check_greater_than('filter bin', edge, 0, equality=True) class UniverseFilter(WithIDFilter): @@ -601,12 +599,13 @@ class MeshFilter(Filter): Attributes ---------- - bins : Integral - The Mesh ID mesh : openmc.Mesh The Mesh object that events will be tallied onto id : int Unique identifier for the filter + bins : list of tuple + A list of mesh indices for each filter bin, e.g. [(1, 1, 1), (2, 1, 1), + ...] num_bins : Integral The number of filter bins @@ -614,7 +613,7 @@ class MeshFilter(Filter): def __init__(self, mesh, filter_id=None): self.mesh = mesh - super().__init__(mesh.id, filter_id) + self.id = filter_id @classmethod def from_hdf5(cls, group, **kwargs): @@ -643,68 +642,14 @@ class MeshFilter(Filter): def mesh(self, mesh): cv.check_type('filter mesh', mesh, openmc.Mesh) self._mesh = mesh - self.bins = mesh.id - - @property - def num_bins(self): - 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 ' \ - 'only a single mesh can be used per tally'.format(bins) - raise ValueError(msg) - elif not isinstance(bins[0], Integral): - msg = 'Unable to add bin "{0}" to MeshFilter since it ' \ - 'is a non-integer'.format(bins[0]) - raise ValueError(msg) - elif bins[0] < 0: - msg = 'Unable to add bin "{0}" to MeshFilter since it ' \ - 'is a negative integer'.format(bins[0]) - raise ValueError(msg) + self.bins = list(mesh.indices) def can_merge(self, other): # Mesh filters cannot have more than one bin return False - def get_bin_index(self, filter_bin): - # Filter bins for a mesh are an (x,y,z) tuple. Convert (x,y,z) to a - # single bin -- this is similar to subroutine mesh_indices_to_bin in - # openmc/src/mesh.F90. - n_dim = len(self.mesh.dimension) - if n_dim == 3: - i, j, k = filter_bin - nx, ny, nz = self.mesh.dimension - return (i - 1) + (j - 1)*nx + (k - 1)*nx*ny - elif n_dim == 2: - i, j, *_ = filter_bin - nx, ny = self.mesh.dimension - return (i - 1) + (j - 1)*nx - else: - return filter_bin[0] - 1 - def get_bin(self, bin_index): - cv.check_type('bin_index', bin_index, Integral) - cv.check_greater_than('bin_index', bin_index, 0, equality=True) - cv.check_less_than('bin_index', bin_index, self.num_bins) - - n_dim = len(self.mesh.dimension) - if n_dim == 3: - # Construct 3-tuple of x,y,z cell indices for a 3D mesh - nx, ny, nz = self.mesh.dimension - x = (bin_index % nx) + 1 - y = (bin_index % (nx * ny)) // nx + 1 - z = bin_index // (nx * ny) + 1 - return (x, y, z) - - elif n_dim == 2: - # Construct 2-tuple of x,y cell indices for a 2D mesh - nx, ny = self.mesh.dimension - x = (bin_index % nx) + 1 - y = bin_index // nx + 1 - return (x, y) - else: - return (bin_index + 1,) + return self.bins[bin_index] def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. @@ -783,6 +728,19 @@ class MeshFilter(Filter): return df + def to_xml_element(self): + """Return XML Element representing the Filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing filter data + + """ + element = super().to_xml_element() + element[0].text = str(self.mesh.id) + return element + class MeshSurfaceFilter(MeshFilter): """Filter events by surface crossings on a regular, rectangular mesh. @@ -802,36 +760,25 @@ class MeshSurfaceFilter(MeshFilter): The Mesh object that events will be tallied onto id : int Unique identifier for the filter + bins : list of tuple + + A list of mesh indices / surfaces for each filter bin, e.g. [(1, 1, + 'x-min out'), (1, 1, 'x-min in'), ...] + num_bins : Integral The number of filter bins """ - @property - def num_bins(self): - n_dim = len(self.mesh.dimension) - return 4*n_dim*reduce(operator.mul, self.mesh.dimension) + @MeshFilter.mesh.setter + def mesh(self, mesh): + cv.check_type('filter mesh', mesh, openmc.Mesh) + self._mesh = mesh - def get_bin_index(self, filter_bin): - # Split bin into mesh/surface parts - *mesh_tuple, surf = filter_bin - - # Get index for mesh alone - mesh_index = super().get_bin_index(mesh_tuple) - - # Combine surface and mesh index - n_dim = len(self.mesh.dimension) - for surf_index, name in enumerate(_CURRENT_NAMES): - if surf == name: - return 4*n_dim*mesh_index + surf_index - else: - raise ValueError("'{}' is not a valid mesh surface.".format(surf)) - - def get_bin(self, bin_index): - n_dim = len(self.mesh.dimension) - mesh_index, surf_index = divmod(bin_index, 4*n_dim) - mesh_bin = super().get_bin(mesh_index) - return mesh_bin + (_CURRENT_NAMES[surf_index],) + # Take the product of mesh indices and current names + n_dim = len(mesh.dimension) + self.bins = [mesh_tuple + (surf,) for mesh_tuple, surf in + product(mesh.indices, _CURRENT_NAMES[:4*n_dim])] def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. @@ -920,42 +867,68 @@ class RealFilter(Filter): Parameters ---------- - bins : Iterable of Real - A grid of bin values. + values : iterable of float + A list of values for which each successive pair constitutes a range of + values for a single bin filter_id : int Unique identifier for the filter Attributes ---------- - bins : Iterable of Real - A grid of bin values. + values : numpy.ndarray + An array of values for which each successive pair constitutes a range of + values for a single bin id : int Unique identifier for the filter + bins : numpy.ndarray + An array of shape (N, 2) where each row is a pair of values indicating a + filter bin range num_bins : int The number of filter bins """ + def __init__(self, values, filter_id=None): + self.values = np.asarray(values) + self.bins = np.vstack((self.values[:-1], self.values[1:])).T + self.id = filter_id def __gt__(self, other): if type(self) is type(other): # Compare largest/smallest bin edges in filters # This logic is used when merging tallies with real filters - return self.bins[0] >= other.bins[-1] + return self.values[0] >= other.values[-1] else: return super().__gt__(other) - @property - def num_bins(self): - return len(self.bins) - 1 + @Filter.bins.setter + def bins(self, bins): + Filter.bins.__set__(self, np.asarray(bins)) + + def check_bins(self, bins): + for v0, v1 in bins: + # Values should be real + cv.check_type('filter value', v0, Real) + cv.check_type('filter value', v1, Real) + + # Make sure that each tuple has values that are increasing + if v1 < v0: + raise ValueError('Values {} and {} appear to be out of order' + .format(v0, v1)) + + for pair0, pair1 in zip(bins[:-1], bins[1:]): + # Successive pairs should be ordered + if pair1[1] < pair0[1]: + raise ValueError('Values {} and {} appear to be out of order' + .format(pair1[1], pair0[1])) def can_merge(self, other): if type(self) is not type(other): return False - if self.bins[0] == other.bins[-1]: + if self.bins[0, 0] == other.bins[-1][1]: # This low edge coincides with other's high edge return True - elif self.bins[-1] == other.bins[0]: + elif self.bins[-1][1] == other.bins[0, 0]: # This high edge coincides with other's low edge return True else: @@ -968,11 +941,11 @@ class RealFilter(Filter): raise ValueError(msg) # Merge unique filter bins - merged_bins = np.concatenate((self.bins, other.bins)) - merged_bins = np.unique(merged_bins) + merged_values = np.concatenate((self.values, other.values)) + merged_values = np.unique(merged_values) # Create a new filter with these bins and a new auto-generated ID - return type(self)(sorted(merged_bins)) + return type(self)(sorted(merged_values)) def is_subset(self, other): """Determine if another filter is a subset of this filter. @@ -994,80 +967,19 @@ class RealFilter(Filter): if type(self) is not type(other): return False - elif len(self.bins) != len(other.bins): + elif self.num_bins != other.num_bins: return False else: - return np.allclose(self.bins, other.bins) + return np.allclose(self.values, other.values) def get_bin_index(self, filter_bin): - i = np.where(self.bins == filter_bin[1])[0] + i = np.where(self.bins[:, 1] == filter_bin[1])[0] if len(i) == 0: msg = 'Unable to get the bin index for Filter since "{0}" ' \ 'is not one of the bins'.format(filter_bin) raise ValueError(msg) else: - return i[0] - 1 - - def get_bin(self, bin_index): - cv.check_type('bin_index', bin_index, Integral) - cv.check_greater_than('bin_index', bin_index, 0, equality=True) - cv.check_less_than('bin_index', bin_index, self.num_bins) - - # Construct 2-tuple of lower, upper bins for real-valued filters - return (self.bins[bin_index], self.bins[bin_index + 1]) - - -class EnergyFilter(RealFilter): - """Bins tally events based on incident particle energy. - - Parameters - ---------- - bins : Iterable of Real - A grid of energy values in eV. - filter_id : int - Unique identifier for the filter - - Attributes - ---------- - bins : Iterable of Real - A grid of energy values in eV. - id : int - Unique identifier for the filter - num_bins : int - The number of filter bins - - """ - - def get_bin_index(self, filter_bin): - # Use lower energy bound to find index for RealFilters - deltas = np.abs(self.bins - filter_bin[1]) / filter_bin[1] - min_delta = np.min(deltas) - if min_delta < 1E-3: - return deltas.argmin() - 1 - else: - msg = 'Unable to get the bin index for Filter since "{0}" ' \ - 'is not one of the bins'.format(filter_bin) - raise ValueError(msg) - - def check_bins(self, bins): - for edge in bins: - if not isinstance(edge, Real): - msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ - 'since it is a non-integer or floating point ' \ - 'value'.format(edge, type(self)) - raise ValueError(msg) - elif edge < 0.: - msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ - 'since it is a negative value'.format(edge, type(self)) - raise ValueError(msg) - - # Check that bin edges are monotonically increasing - for index in range(1, len(bins)): - if bins[index] < bins[index-1]: - msg = 'Unable to add bin edges "{0}" to a "{1}" Filter ' \ - 'since they are not monotonically ' \ - 'increasing'.format(bins, type(self)) - raise ValueError(msg) + return i[0] def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. @@ -1102,35 +1014,103 @@ 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], stride) - hi_bins = np.repeat(self.bins[1:], stride) + lo_bins = np.repeat(self.bins[:, 0], 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) # Add the new energy columns to the DataFrame. - df.loc[:, self.short_name.lower() + ' low [eV]'] = lo_bins - df.loc[:, self.short_name.lower() + ' high [eV]'] = hi_bins + if hasattr(self, 'units'): + units = ' [{}]'.format(self.units) + else: + units = '' + + df.loc[:, self.short_name.lower() + ' low' + units] = lo_bins + df.loc[:, self.short_name.lower() + ' high' + units] = hi_bins return df + def to_xml_element(self): + """Return XML Element representing the Filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing filter data + + """ + element = super().to_xml_element() + element[0].text = ' '.join(str(x) for x in self.values) + return element + + +class EnergyFilter(RealFilter): + """Bins tally events based on incident particle energy. + + Parameters + ---------- + values : Iterable of Real + A list of values for which each successive pair constitutes a range of + energies in [eV] for a single bin + filter_id : int + Unique identifier for the filter + + Attributes + ---------- + values : numpy.ndarray + An array of values for which each successive pair constitutes a range of + energies in [eV] for a single bin + id : int + Unique identifier for the filter + bins : numpy.ndarray + An array of shape (N, 2) where each row is a pair of energies in [eV] + for a single filter bin + num_bins : int + The number of filter bins + + """ + units = 'eV' + + def get_bin_index(self, filter_bin): + # Use lower energy bound to find index for RealFilters + deltas = np.abs(self.bins[:, 1] - filter_bin[1]) / filter_bin[1] + min_delta = np.min(deltas) + if min_delta < 1E-3: + return deltas.argmin() + else: + msg = 'Unable to get the bin index for Filter since "{0}" ' \ + 'is not one of the bins'.format(filter_bin) + raise ValueError(msg) + + def check_bins(self, bins): + super().check_bins(bins) + for v0, v1 in bins: + cv.check_greater_than('filter value', v0, 0., equality=True) + cv.check_greater_than('filter value', v1, 0., equality=True) + class EnergyoutFilter(EnergyFilter): """Bins tally events based on outgoing particle energy. Parameters ---------- - bins : Iterable of Real - A grid of energy values in eV. + values : Iterable of Real + A list of values for which each successive pair constitutes a range of + energies in [eV] for a single bin filter_id : int Unique identifier for the filter Attributes ---------- - bins : Iterable of Real - A grid of energy values in eV. + values : numpy.ndarray + An array of values for which each successive pair constitutes a range of + energies in [eV] for a single bin id : int Unique identifier for the filter + bins : numpy.ndarray + An array of shape (N, 2) where each row is a pair of energies in [eV] + for a single filter bin num_bins : int The number of filter bins @@ -1412,98 +1392,41 @@ class MuFilter(RealFilter): Parameters ---------- - bins : Iterable of Real or Integral - A grid of scattering angles which events will binned into. Values - represent the cosine of the scattering angle. If an Iterable is given, - the values will be used explicitly as grid points. If a single Integral - is given, the range [-1, 1] will be divided up equally into that number - of bins. + values : int or Iterable of Real + A grid of scattering angles which events will binned into. Values + represent the cosine of the scattering angle. If an iterable is given, + the values will be used explicitly as grid points. If a single int is + given, the range [-1, 1] will be divided up equally into that number of + bins. filter_id : int Unique identifier for the filter Attributes ---------- - bins : Integral - A grid of scattering angles which events will binned into. Values - represent the cosine of the scattering angle. If an Iterable is given, - the values will be used explicitly as grid points. If a single Integral - is given, the range [-1, 1] will be divided up equally into that number - of bins. + values : numpy.ndarray + An array of values for which each successive pair constitutes a range of + scattering angle cosines for a single bin id : int Unique identifier for the filter + bins : numpy.ndarray + An array of shape (N, 2) where each row is a pair of scattering angle + cosines for a single filter bin num_bins : Integral The number of filter bins """ + def __init__(self, values, filter_id=None): + if isinstance(values, Integral): + values = np.linspace(-1., 1., values + 1) + super().__init__(values, filter_id) def check_bins(self, bins): - for edge in bins: - if not isinstance(edge, Real): - msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ - 'since it is a non-integer or floating point ' \ - 'value'.format(edge, type(self)) - raise ValueError(msg) - elif edge < -1.: - msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ - 'since it is less than -1'.format(edge, type(self)) - raise ValueError(msg) - elif edge > 1.: - msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ - 'since it is greater than 1'.format(edge, type(self)) - raise ValueError(msg) - - # Check that bin edges are monotonically increasing - for index in range(1, len(bins)): - if bins[index] < bins[index-1]: - msg = 'Unable to add bin edges "{0}" to a "{1}" Filter ' \ - 'since they are not monotonically ' \ - 'increasing'.format(bins, type(self)) - raise ValueError(msg) - - 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 - columns annotated by filter bin information. This is a helper method - for :meth:`Tally.get_pandas_dataframe`. - - Parameters - ---------- - data_size : int - The total number of bins in the tally corresponding to this filter - stride : int - Stride in memory for the filter - - Returns - ------- - pandas.DataFrame - A Pandas DataFrame with one column of the lower energy bound and one - column of upper energy bound for each filter bin. The number of - rows in the DataFrame is the same as the total number of bins in the - corresponding tally, with the filter bin appropriately tiled to map - to the corresponding tally bins. - - See also - -------- - Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() - - """ - # Initialize Pandas DataFrame - df = pd.DataFrame() - - # 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], 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) - - # Add the new energy columns to the DataFrame. - df.loc[:, self.short_name.lower() + ' low'] = lo_bins - df.loc[:, self.short_name.lower() + ' high'] = hi_bins - - return df + super().check_bins(bins) + for x in np.ravel(bins): + if not np.isclose(x, -1.): + cv.check_greater_than('filter value', x, -1., equality=True) + if not np.isclose(x, 1.): + cv.check_less_than('filter value', x, 1., equality=True) class PolarFilter(RealFilter): @@ -1511,98 +1434,44 @@ class PolarFilter(RealFilter): Parameters ---------- - bins : Iterable of Real or Integral - A grid of polar angles which events will binned into. Values represent - an angle in radians relative to the z-axis. If an Iterable is given, - the values will be used explicitly as grid points. If a single Integral - is given, the range [0, pi] will be divided up equally into that number - of bins. + values : int or Iterable of Real + A grid of polar angles which events will binned into. Values represent + an angle in radians relative to the z-axis. If an iterable is given, the + values will be used explicitly as grid points. If a single int is given, + the range [0, pi] will be divided up equally into that number of bins. filter_id : int Unique identifier for the filter Attributes ---------- - bins : Iterable of Real or Integral - A grid of polar angles which events will binned into. Values represent - an angle in radians relative to the z-axis. If an Iterable is given, - the values will be used explicitly as grid points. If a single Integral - is given, the range [0, pi] will be divided up equally into that number - of bins. + values : numpy.ndarray + An array of values for which each successive pair constitutes a range of + polar angles in [rad] for a single bin + id : int + Unique identifier for the filter + bins : numpy.ndarray + An array of shape (N, 2) where each row is a pair of polar angles for a + single filter bin id : int Unique identifier for the filter num_bins : Integral The number of filter bins """ + units = 'rad' + + def __init__(self, values, filter_id=None): + if isinstance(values, Integral): + values = np.linspace(0., np.pi, values + 1) + super().__init__(values, filter_id) def check_bins(self, bins): - for edge in bins: - if not isinstance(edge, Real): - msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ - 'since it is a non-integer or floating point ' \ - 'value'.format(edge, type(self)) - raise ValueError(msg) - elif edge < 0.: - msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ - 'since it is less than 0'.format(edge, type(self)) - raise ValueError(msg) - elif not np.isclose(edge, np.pi) and edge > np.pi: - msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ - 'since it is greater than pi'.format(edge, type(self)) - raise ValueError(msg) - - # Check that bin edges are monotonically increasing - for index in range(1, len(bins)): - if bins[index] < bins[index-1]: - msg = 'Unable to add bin edges "{0}" to a "{1}" Filter ' \ - 'since they are not monotonically ' \ - 'increasing'.format(bins, type(self)) - raise ValueError(msg) - - 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 - columns annotated by filter bin information. This is a helper method for - :meth:`Tally.get_pandas_dataframe`. - - Parameters - ---------- - data_size : int - The total number of bins in the tally corresponding to this filter - stride : int - Stride in memory for the filter - - Returns - ------- - pandas.DataFrame - A Pandas DataFrame with a column corresponding to the lower polar - angle bound for each of the filter's bins. The number of rows in - the DataFrame is the same as the total number of bins in the - corresponding tally, with the filter bin appropriately tiled to map - to the corresponding tally bins. - - See also - -------- - Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() - - """ - # Initialize Pandas DataFrame - df = pd.DataFrame() - - # 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], 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) - - # Add the new angle columns to the DataFrame. - df.loc[:, 'polar low'] = lo_bins - df.loc[:, 'polar high'] = hi_bins - - return df + super().check_bins(bins) + for x in np.ravel(bins): + if not np.isclose(x, 0.): + cv.check_greater_than('filter value', x, 0., equality=True) + if not np.isclose(x, np.pi): + cv.check_less_than('filter value', x, np.pi, equality=True) class AzimuthalFilter(RealFilter): @@ -1610,98 +1479,43 @@ class AzimuthalFilter(RealFilter): Parameters ---------- - bins : Iterable of Real or Integral - A grid of azimuthal angles which events will binned into. Values + values : int or Iterable of Real + A grid of azimuthal angles which events will binned into. Values represent an angle in radians relative to the x-axis and perpendicular - to the z-axis. If an Iterable is given, the values will be used - explicitly as grid points. If a single Integral is given, the range + to the z-axis. If an iterable is given, the values will be used + explicitly as grid points. If a single int is given, the range [-pi, pi) will be divided up equally into that number of bins. filter_id : int Unique identifier for the filter Attributes ---------- - bins : Iterable of Real or Integral - A grid of azimuthal angles which events will binned into. Values - represent an angle in radians relative to the x-axis and perpendicular - to the z-axis. If an Iterable is given, the values will be used - explicitly as grid points. If a single Integral is given, the range - [-pi, pi) will be divided up equally into that number of bins. + values : numpy.ndarray + An array of values for which each successive pair constitutes a range of + azimuthal angles in [rad] for a single bin id : int Unique identifier for the filter + bins : numpy.ndarray + An array of shape (N, 2) where each row is a pair of azimuthal angles + for a single filter bin num_bins : Integral The number of filter bins """ + units = 'rad' + + def __init__(self, values, filter_id=None): + if isinstance(values, Integral): + values = np.linspace(-np.pi, np.pi, values + 1) + super().__init__(values, filter_id) def check_bins(self, bins): - for edge in bins: - if not isinstance(edge, Real): - msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ - 'since it is a non-integer or floating point ' \ - 'value'.format(edge, type(self)) - raise ValueError(msg) - elif not np.isclose(edge, -np.pi) and edge < -np.pi: - msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ - 'since it is less than -pi'.format(edge, type(self)) - raise ValueError(msg) - elif not np.isclose(edge, np.pi) and edge > np.pi: - msg = 'Unable to add bin edge "{0}" to a "{1}" ' \ - 'since it is greater than pi'.format(edge, type(self)) - raise ValueError(msg) - - # Check that bin edges are monotonically increasing - for index in range(1, len(bins)): - if bins[index] < bins[index-1]: - msg = 'Unable to add bin edges "{0}" to a "{1}" Filter ' \ - 'since they are not monotonically ' \ - 'increasing'.format(bins, type(self)) - raise ValueError(msg) - - 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 - columns annotated by filter bin information. This is a helper method for - :meth:`Tally.get_pandas_dataframe`. - - Parameters - ---------- - data_size : int - The total number of bins in the tally corresponding to this filter - stride : int - Stride in memory for the filter - - Returns - ------- - pandas.DataFrame - A Pandas DataFrame with a column corresponding to the lower - azimuthal angle bound for each of the filter's bins. The number of - rows in the DataFrame is the same as the total number of bins in the - corresponding tally, with the filter bin appropriately tiled to map - to the corresponding tally bins. - - See also - -------- - Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() - - """ - # Initialize Pandas DataFrame - df = pd.DataFrame() - - # 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], 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) - - # Add the new angle columns to the DataFrame. - df.loc[:, 'azimuthal low'] = lo_bins - df.loc[:, 'azimuthal high'] = hi_bins - - return df + super().check_bins(bins) + for x in np.ravel(bins): + if not np.isclose(x, -np.pi): + cv.check_greater_than('filter value', x, -np.pi, equality=True) + if not np.isclose(x, np.pi): + cv.check_less_than('filter value', x, np.pi, equality=True) class DelayedGroupFilter(Filter): @@ -1709,7 +1523,7 @@ class DelayedGroupFilter(Filter): Parameters ---------- - bins : Integral or Iterable of Integral + bins : iterable of int The delayed neutron precursor groups. For example, ENDF/B-VII.1 uses 6 precursor groups so a tally with all groups will have bins = [1, 2, 3, 4, 5, 6]. @@ -1718,7 +1532,7 @@ class DelayedGroupFilter(Filter): Attributes ---------- - bins : Integral or Iterable of Integral + bins : iterable of int The delayed neutron precursor groups. For example, ENDF/B-VII.1 uses 6 precursor groups so a tally with all groups will have bins = [1, 2, 3, 4, 5, 6]. @@ -1728,17 +1542,10 @@ class DelayedGroupFilter(Filter): The number of filter bins """ - @Filter.bins.setter - def bins(self, bins): - # Format the bins as a 1D numpy array. - bins = np.atleast_1d(bins) - + def check_bins(self, bins): # Check the bin values. - cv.check_iterable_type('filter bins', bins, Integral) - for edge in bins: - cv.check_greater_than('filter bin', edge, 0, equality=True) - - self._bins = bins + for g in bins: + cv.check_greater_than('delayed group', g, 0) class EnergyFunctionFilter(Filter): @@ -1751,18 +1558,18 @@ class EnergyFunctionFilter(Filter): Parameters ---------- energy : Iterable of Real - A grid of energy values in eV. + A grid of energy values in [eV] y : iterable of Real - A grid of interpolant values in eV. + A grid of interpolant values in [eV] filter_id : int Unique identifier for the filter Attributes ---------- energy : Iterable of Real - A grid of energy values in eV. + A grid of energy values in [eV] y : iterable of Real - A grid of interpolant values in eV. + A grid of interpolant values in [eV] id : int Unique identifier for the filter num_bins : Integral @@ -1903,6 +1710,14 @@ class EnergyFunctionFilter(Filter): raise RuntimeError('EnergyFunctionFilters have no bins.') def to_xml_element(self): + """Return XML Element representing the Filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing filter data + + """ element = ET.Element('filter') element.set('id', str(self.id)) element.set('type', self.short_name.lower()) diff --git a/openmc/filter_legendre.py b/openmc/filter_legendre.py index 075fa02db..18998d5cc 100644 --- a/openmc/filter_legendre.py +++ b/openmc/filter_legendre.py @@ -34,6 +34,7 @@ class LegendreFilter(Filter): def __init__(self, order, filter_id=None): self.order = order + self.bins = ['P{}'.format(i) for i in range(order + 1)] self.id = filter_id def __hash__(self): @@ -57,10 +58,6 @@ class LegendreFilter(Filter): cv.check_greater_than('Legendre order', order, 0, equality=True) self._order = order - @property - def num_bins(self): - return self._order + 1 - @classmethod def from_hdf5(cls, group, **kwargs): if group['type'].value.decode() != cls.short_name.lower(): @@ -74,44 +71,6 @@ class LegendreFilter(Filter): return out - 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 - columns annotated by filter bin information. This is a helper method for - :meth:`Tally.get_pandas_dataframe`. - - Parameters - ---------- - data_size : Integral - The total number of bins in the tally corresponding to this filter - stride : int - Stride in memory for the filter - - Returns - ------- - pandas.DataFrame - A Pandas DataFrame with a column that is filled with strings - indicating Legendre orders. The number of rows in the DataFrame is - the same as the total number of bins in the corresponding tally. - - See also - -------- - Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() - - """ - # Initialize Pandas DataFrame - df = pd.DataFrame() - - bins = np.array(['P{}'.format(i) for i in range(self.order + 1)]) - filter_bins = np.repeat(bins, stride) - tile_factor = data_size // len(filter_bins) - filter_bins = np.tile(filter_bins, tile_factor) - df = pd.concat([df, pd.DataFrame( - {self.short_name.lower(): filter_bins})]) - - return df - def to_xml_element(self): """Return XML Element representing the filter. diff --git a/openmc/filter_spatial_legendre.py b/openmc/filter_spatial_legendre.py index 690447d9c..726a3bfdf 100644 --- a/openmc/filter_spatial_legendre.py +++ b/openmc/filter_spatial_legendre.py @@ -44,6 +44,7 @@ class SpatialLegendreFilter(Filter): self.axis = axis self.minimum = minimum self.maximum = maximum + self.bins = ['P{}'.format(i) for i in range(order + 1)] self.id = filter_id def __hash__(self): @@ -118,42 +119,6 @@ class SpatialLegendreFilter(Filter): return cls(order, axis, min_, max_, filter_id) - 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 - columns annotated by filter bin information. This is a helper method for - :meth:`Tally.get_pandas_dataframe`. - - Parameters - ---------- - data_size : Integral - The total number of bins in the tally corresponding to this filter - - Returns - ------- - pandas.DataFrame - A Pandas DataFrame with a column that is filled with strings - indicating Legendre orders. The number of rows in the DataFrame is - the same as the total number of bins in the corresponding tally. - - See also - -------- - Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() - - """ - # Initialize Pandas DataFrame - df = pd.DataFrame() - - bins = np.array(['P{}'.format(i) for i in range(self.order + 1)]) - filter_bins = np.repeat(bins, stride) - tile_factor = data_size // len(filter_bins) - filter_bins = np.tile(filter_bins, tile_factor) - df = pd.concat([df, pd.DataFrame( - {self.short_name.lower(): filter_bins})]) - - return df - def to_xml_element(self): """Return XML Element representing the filter. diff --git a/openmc/filter_spherical_harmonics.py b/openmc/filter_spherical_harmonics.py index 3b2e32b14..f95c09c30 100644 --- a/openmc/filter_spherical_harmonics.py +++ b/openmc/filter_spherical_harmonics.py @@ -34,6 +34,9 @@ class SphericalHarmonicsFilter(Filter): def __init__(self, order, filter_id=None): self.order = order self.id = filter_id + self.bins = ['Y{},{}'.format(n, m) + for n in range(order + 1) + for m in range(-n, n + 1)] self._cosine = 'particle' def __hash__(self): @@ -87,49 +90,6 @@ class SphericalHarmonicsFilter(Filter): return out - 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 - columns annotated by filter bin information. This is a helper method for - :meth:`Tally.get_pandas_dataframe`. - - Parameters - ---------- - data_size : Integral - The total number of bins in the tally corresponding to this filter - stride : int - Stride in memory for the filter - - Returns - ------- - pandas.DataFrame - A Pandas DataFrame with a column that is filled with strings - indicating spherical harmonics orders. The number of rows in the - DataFrame is the same as the total number of bins in the - corresponding tally. - - See also - -------- - Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() - - """ - # Initialize Pandas DataFrame - df = pd.DataFrame() - - bins = [] - for n in range(self.order + 1): - bins.extend('Y{},{}'.format(n, m) for m in range(-n, n + 1)) - bins = np.array(bins) - - filter_bins = np.repeat(bins, stride) - tile_factor = data_size // len(filter_bins) - filter_bins = np.tile(filter_bins, tile_factor) - df = pd.concat([df, pd.DataFrame( - {self.short_name.lower(): filter_bins})]) - - return df - def to_xml_element(self): """Return XML Element representing the filter. diff --git a/openmc/filter_zernike.py b/openmc/filter_zernike.py index 3b4efe5d5..a7697f5d0 100644 --- a/openmc/filter_zernike.py +++ b/openmc/filter_zernike.py @@ -48,6 +48,9 @@ class ZernikeFilter(Filter): self.x = x self.y = y self.r = r + self.bins = ['Z{},{}'.format(n, m) + for n in range(order + 1) + for m in range(-n, n + 1, 2)] self.id = filter_id def __hash__(self): @@ -116,48 +119,6 @@ class ZernikeFilter(Filter): return cls(order, x, y, r, filter_id) - 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 - columns annotated by filter bin information. This is a helper method for - :meth:`Tally.get_pandas_dataframe`. - - Parameters - ---------- - data_size : Integral - The total number of bins in the tally corresponding to this filter - - Returns - ------- - pandas.DataFrame - A Pandas DataFrame with a column that is filled with strings - indicating Zernike orders. The number of rows in the DataFrame is - the same as the total number of bins in the corresponding tally. - - See also - -------- - Tally.get_pandas_dataframe(), CrossFilter.get_pandas_dataframe() - - """ - # Initialize Pandas DataFrame - df = pd.DataFrame() - - # Create list of strings for each order - orders = [] - for n in range(self.order + 1): - for m in range(-n, n + 1, 2): - orders.append('Z{},{}'.format(n, m)) - - bins = np.array(orders) - filter_bins = np.repeat(bins, stride) - tile_factor = data_size // len(filter_bins) - filter_bins = np.tile(filter_bins, tile_factor) - df = pd.concat([df, pd.DataFrame( - {self.short_name.lower(): filter_bins})]) - - return df - def to_xml_element(self): """Return XML Element representing the filter. diff --git a/openmc/mesh.py b/openmc/mesh.py index cd8eb3c5e..aed7a46d8 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -38,6 +38,9 @@ class Mesh(IDManagerMixin): are given, it is assumed that the mesh is an x-y mesh. width : Iterable of float The width of mesh cells in each direction. + indices : list of tuple + A list of mesh indices for each mesh element, e.g. [(1, 1, 1), (2, 1, + 1), ...] """ diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 4932feff1..5bc00cbc9 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -1164,12 +1164,14 @@ class MGXS(metaclass=ABCMeta): if not isinstance(tally_filter, (openmc.EnergyFilter, openmc.EnergyoutFilter)): continue - elif len(tally_filter.bins) != len(fine_edges): + elif len(tally_filter.bins) != len(fine_edges) - 1: continue - elif not np.allclose(tally_filter.bins, fine_edges): + elif not np.allclose(tally_filter.bins[:, 0], fine_edges[:-1]): continue else: - tally_filter.bins = coarse_groups.group_edges + cedge = coarse_groups.group_edges + tally_filter.values = cedge + tally_filter.bins = np.vstack((cedge[:-1], cedge[1:])).T mean = np.add.reduceat(mean, energy_indices, axis=i) std_dev = np.add.reduceat(std_dev**2, energy_indices, axis=i) @@ -2738,7 +2740,7 @@ class TransportXS(MGXS): if self._rxn_rate_tally is None: # Switch EnergyoutFilter to EnergyFilter. old_filt = self.tallies['scatter-1'].filters[-1] - new_filt = openmc.EnergyFilter(old_filt.bins) + new_filt = openmc.EnergyFilter(old_filt.values) self.tallies['scatter-1'].filters[-1] = new_filt self._rxn_rate_tally = \ @@ -2757,7 +2759,7 @@ class TransportXS(MGXS): # Switch EnergyoutFilter to EnergyFilter. old_filt = self.tallies['scatter-1'].filters[-1] - new_filt = openmc.EnergyFilter(old_filt.bins) + new_filt = openmc.EnergyFilter(old_filt.values) self.tallies['scatter-1'].filters[-1] = new_filt # Compute total cross section diff --git a/openmc/tallies.py b/openmc/tallies.py index 6c055d191..08db20e39 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -218,10 +218,9 @@ class Tally(IDManagerMixin): f = h5py.File(self._sp_filename, 'r') # Extract Tally data from the file - data = f['tallies/tally {0}/results'.format( - self.id)].value - sum = data[:,:,0] - sum_sq = data[:,:,1] + data = f['tallies/tally {0}/results'.format(self.id)].value + sum = data[:, :, 0] + sum_sq = data[:, :, 1] # Reshape the results arrays sum = np.reshape(sum, self.shape) @@ -273,8 +272,8 @@ class Tally(IDManagerMixin): # Convert NumPy array to SciPy sparse LIL matrix if self.sparse: - self._mean = \ - sps.lil_matrix(self._mean.flatten(), self._mean.shape) + self._mean = sps.lil_matrix(self._mean.flatten(), + self._mean.shape) if self.sparse: return np.reshape(self._mean.toarray(), self.shape) @@ -295,8 +294,8 @@ class Tally(IDManagerMixin): # Convert NumPy array to SciPy sparse LIL matrix if self.sparse: - self._std_dev = \ - sps.lil_matrix(self._std_dev.flatten(), self._std_dev.shape) + self._std_dev = sps.lil_matrix(self._std_dev.flatten(), + self._std_dev.shape) self.with_batch_statistics = True @@ -436,17 +435,16 @@ class Tally(IDManagerMixin): # Convert NumPy arrays to SciPy sparse LIL matrices if sparse and not self.sparse: if self._sum is not None: - self._sum = \ - sps.lil_matrix(self._sum.flatten(), self._sum.shape) + self._sum = sps.lil_matrix(self._sum.flatten(), self._sum.shape) if self._sum_sq is not None: - self._sum_sq = \ - sps.lil_matrix(self._sum_sq.flatten(), self._sum_sq.shape) + self._sum_sq = sps.lil_matrix(self._sum_sq.flatten(), + self._sum_sq.shape) if self._mean is not None: - self._mean = \ - sps.lil_matrix(self._mean.flatten(), self._mean.shape) + self._mean = sps.lil_matrix(self._mean.flatten(), + self._mean.shape) if self._std_dev is not None: - self._std_dev = \ - sps.lil_matrix(self._std_dev.flatten(), self._std_dev.shape) + self._std_dev = sps.lil_matrix(self._std_dev.flatten(), + self._std_dev.shape) self._sparse = True @@ -776,11 +774,11 @@ class Tally(IDManagerMixin): other_sum = other_copy.get_reshaped_data(value='sum') if join_right: - merged_sum = \ - np.concatenate((self_sum, other_sum), axis=merge_axis) + merged_sum = np.concatenate((self_sum, other_sum), + axis=merge_axis) else: - merged_sum = \ - np.concatenate((other_sum, self_sum), axis=merge_axis) + merged_sum = np.concatenate((other_sum, self_sum), + axis=merge_axis) merged_tally._sum = np.reshape(merged_sum, merged_tally.shape) @@ -790,11 +788,11 @@ class Tally(IDManagerMixin): other_sum_sq = other_copy.get_reshaped_data(value='sum_sq') if join_right: - merged_sum_sq = \ - np.concatenate((self_sum_sq, other_sum_sq), axis=merge_axis) + merged_sum_sq = np.concatenate((self_sum_sq, other_sum_sq), + axis=merge_axis) else: - merged_sum_sq = \ - np.concatenate((other_sum_sq, self_sum_sq), axis=merge_axis) + merged_sum_sq = np.concatenate((other_sum_sq, self_sum_sq), + axis=merge_axis) merged_tally._sum_sq = np.reshape(merged_sum_sq, merged_tally.shape) @@ -804,11 +802,11 @@ class Tally(IDManagerMixin): other_mean = other_copy.get_reshaped_data(value='mean') if join_right: - merged_mean = \ - np.concatenate((self_mean, other_mean), axis=merge_axis) + merged_mean = np.concatenate((self_mean, other_mean), + axis=merge_axis) else: - merged_mean = \ - np.concatenate((other_mean, self_mean), axis=merge_axis) + merged_mean = np.concatenate((other_mean, self_mean), + axis=merge_axis) merged_tally._mean = np.reshape(merged_mean, merged_tally.shape) @@ -818,11 +816,11 @@ class Tally(IDManagerMixin): other_std_dev = other_copy.get_reshaped_data(value='std_dev') if join_right: - merged_std_dev = \ - np.concatenate((self_std_dev, other_std_dev), axis=merge_axis) + merged_std_dev = np.concatenate((self_std_dev, other_std_dev), + axis=merge_axis) else: - merged_std_dev = \ - np.concatenate((other_std_dev, self_std_dev), axis=merge_axis) + merged_std_dev = np.concatenate((other_std_dev, self_std_dev), + axis=merge_axis) merged_tally._std_dev = np.reshape(merged_std_dev, merged_tally.shape) @@ -1003,35 +1001,6 @@ class Tally(IDManagerMixin): return filter_found - def get_filter_index(self, filter_type, filter_bin): - """Returns the index in the Tally's results array for a Filter bin - - Parameters - ---------- - filter_type : openmc.FilterMeta - Type of the filter, e.g. MeshFilter - filter_bin : int or tuple - The bin is an integer ID for 'material', 'surface', 'cell', - 'cellborn', and 'universe' Filters. The bin is an integer for the - cell instance ID for 'distribcell' Filters. The bin is a 2-tuple of - floats for 'energy' and 'energyout' filters corresponding to the - energy boundaries of the bin of interest. The bin is a (x,y,z) - 3-tuple for 'mesh' filters corresponding to the mesh cell of - interest. - - Returns - ------- - The index in the Tally data array for this filter bin - - """ - - # Find the equivalent Filter in this Tally's list of Filters - filter_found = self.find_filter(filter_type) - - # Get the index for the requested bin from the Filter and return it - filter_index = filter_found.get_bin_index(filter_bin) - return filter_index - def get_nuclide_index(self, nuclide): """Returns the index in the Tally's results array for a Nuclide bin @@ -1151,48 +1120,28 @@ class Tally(IDManagerMixin): # Loop over all of the Tally's Filters for i, self_filter in enumerate(self.filters): - user_filter = False - # If a user-requested Filter, get the user-requested bins for j, test_filter in enumerate(filters): if type(self_filter) is test_filter: bins = filter_bins[j] - user_filter = True break + else: + # If not a user-requested Filter, get all bins + if isinstance(self_filter, openmc.DistribcellFilter): + # Create list of cell instance IDs for distribcell Filters + bins = list(range(self_filter.num_bins)) - # If not a user-requested Filter, get all bins - if not user_filter: - # Create list of 2- or 3-tuples tuples for mesh cell bins - if isinstance(self_filter, openmc.MeshFilter): - bins = list(self_filter.mesh.indices) - - # Create list of 2-tuples for energy boundary bins - elif isinstance(self_filter, (openmc.EnergyFilter, - openmc.EnergyoutFilter, openmc.MuFilter, - openmc.PolarFilter, openmc.AzimuthalFilter)): - bins = [] - for k in range(self_filter.num_bins): - bins.append((self_filter.bins[k], self_filter.bins[k+1])) - - # Create list of cell instance IDs for distribcell Filters - elif isinstance(self_filter, openmc.DistribcellFilter): - bins = [b for b in range(self_filter.num_bins)] - - # EnergyFunctionFilters don't have bins so just add a None elif isinstance(self_filter, openmc.EnergyFunctionFilter): + # EnergyFunctionFilters don't have bins so just add a None bins = [None] - # Create list of IDs for bins for all other filter types else: + # Create list of IDs for bins for all other filter types bins = self_filter.bins - # Initialize a NumPy array for the Filter bin indices - filter_indices.append(np.zeros(len(bins), dtype=np.int)) - # Add indices for each bin in this Filter to the list - for j, bin in enumerate(bins): - filter_index = self.get_filter_index(type(self_filter), bin) - filter_indices[i][j] = filter_index + indices = np.array([self_filter.get_bin_index(b) for b in bins]) + filter_indices.append(indices) # Account for stride in each of the previous filters for indices in filter_indices[:i]: @@ -1956,14 +1905,14 @@ class Tally(IDManagerMixin): elif isinstance(filter1, openmc.EnergyFunctionFilter): filter1_bins = [None] else: - filter1_bins = [filter1.get_bin(i) for i in range(filter1.num_bins)] + filter1_bins = filter1.bins if isinstance(filter2, openmc.DistribcellFilter): filter2_bins = [b for b in range(filter2.num_bins)] elif isinstance(filter2, openmc.EnergyFunctionFilter): filter2_bins = [None] else: - filter2_bins = [filter2.get_bin(i) for i in range(filter2.num_bins)] + filter2_bins = filter2.bins # Create variables to store views of data in the misaligned structure mean = {} @@ -2604,7 +2553,8 @@ class Tally(IDManagerMixin): new_tally = self * -1 return new_tally - def get_slice(self, scores=[], filters=[], filter_bins=[], nuclides=[]): + def get_slice(self, scores=[], filters=[], filter_bins=[], nuclides=[], + squeeze=False): """Build a sliced tally for the specified filters, scores and nuclides. This method constructs a new tally to encapsulate a subset of the data @@ -2615,26 +2565,26 @@ class Tally(IDManagerMixin): Parameters ---------- scores : list of str - A list of one or more score strings - (e.g., ['absorption', 'nu-fission']; default is []) + A list of one or more score strings (e.g., ['absorption', + 'nu-fission'] filters : Iterable of openmc.FilterMeta - An iterable of filter types - (e.g., [MeshFilter, EnergyFilter]; default is []) + An iterable of filter types (e.g., [MeshFilter, EnergyFilter]) filter_bins : list of Iterables - A list of tuples of filter bins corresponding to the filter_types - parameter (e.g., [(1,), ((0., 0.625e-6),)]; default is []). Each - tuple contains bins to slice for the corresponding filter type in - the filters parameter. Each bins is the integer ID for 'material', + A list of iterables of filter bins corresponding to the specified + filter types (e.g., [(1,), ((0., 0.625e-6),)]). Each iterable + contains bins to slice for the corresponding filter type in the + filters parameter. Each bin is the integer ID for 'material', 'surface', 'cell', 'cellborn', and 'universe' Filters. Each bin is an integer for the cell instance ID for 'distribcell' Filters. Each bin is a 2-tuple of floats for 'energy' and 'energyout' filters corresponding to the energy boundaries of the bin of interest. The bin is an (x,y,z) 3-tuple for 'mesh' filters corresponding to the mesh cell of interest. The order of the bins in the list must - correspond to the filter_types parameter. + correspond to the `filters` argument. nuclides : list of str - A list of nuclide name strings - (e.g., ['U235', 'U238']; default is []) + A list of nuclide name strings (e.g., ['U235', 'U238']) + squeeze : bool + Whether to remove filters with only a single bin in the sliced tally Returns ------- @@ -2714,32 +2664,29 @@ class Tally(IDManagerMixin): # Determine the filter indices from any of the requested filters for i, filter_type in enumerate(filters): - find_filter = new_tally.find_filter(filter_type) + f = new_tally.find_filter(filter_type) + + # Remove filters with only a single bin if requested + if squeeze: + if len(filter_bins[i]) == 1: + new_tally.filters.remove(f) + continue + else: + raise RuntimeError('Cannot remove sliced filter with ' + 'more than one bin.') # Remove and/or reorder filter bins to user specifications - bin_indices = [] + bin_indices = [f.get_bin_index(b) + for b in filter_bins[i]] + bin_indices = np.unique(bin_indices) - for filter_bin in filter_bins[i]: - bin_index = find_filter.get_bin_index(filter_bin) - if issubclass(filter_type, openmc.RealFilter): - bin_indices.extend([bin_index, bin_index+1]) - else: - bin_indices.append(bin_index) - - # Set bins for mesh/distribcell filters apart from others - if filter_type is openmc.MeshFilter: - bins = find_filter.mesh - elif filter_type is openmc.DistribcellFilter: - bins = find_filter.bins - else: - bins = np.unique(find_filter.bins[bin_indices]) - - # Create new filter - new_filter = filter_type(bins) + # Set bins for sliced filter + new_filter = copy.copy(f) + new_filter.bins = [f.bins[i] for i in bin_indices] # Set number of bins manually for mesh/distribcell filters if filter_type is openmc.DistribcellFilter: - new_filter._num_bins = find_filter._num_bins + new_filter._num_bins = f._num_bins # Replace existing filter with new one for j, test_filter in enumerate(new_tally.filters): diff --git a/tests/regression_tests/tally_slice_merge/test.py b/tests/regression_tests/tally_slice_merge/test.py index e52d0fde4..7146c0d09 100644 --- a/tests/regression_tests/tally_slice_merge/test.py +++ b/tests/regression_tests/tally_slice_merge/test.py @@ -87,12 +87,14 @@ class TallySliceMergeTestHarness(PyAPITestHarness): # Slice the tallies by cell filter bins cell_filter_prod = itertools.product(tallies, self.cell_filters) tallies = map(lambda tf: tf[0].get_slice(filters=[type(tf[1])], - filter_bins=[tf[1].get_bin(0)]), cell_filter_prod) + filter_bins=[tf[1].get_bin(0)]), + cell_filter_prod) # Slice the tallies by energy filter bins energy_filter_prod = itertools.product(tallies, self.energy_filters) tallies = map(lambda tf: tf[0].get_slice(filters=[type(tf[1])], - filter_bins=[(tf[1].get_bin(0),)]), energy_filter_prod) + filter_bins=[tf[1].get_bin(0)]), + energy_filter_prod) # Slice the tallies by nuclide nuclide_prod = itertools.product(tallies, self.nuclides) From 764478a446eedd9082913ae3ee7c371744ae6230 Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Thu, 29 Mar 2018 01:55:34 +0000 Subject: [PATCH 145/361] deleted "<<<<<<" --- CMakeLists.txt | 7 ------- src/simulation.F90 | 6 ------ 2 files changed, 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ea2ddc52f..04a42817b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,17 +114,10 @@ if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU) endif() # GCC compiler options -<<<<<<< HEAD - list(APPEND f90flags -cpp -std=f2008ts -fbacktrace -O2 -g) - if(debug) - list(REMOVE_ITEM f90flags -O2) - list(APPEND f90flags -g -enable-checking -fbacktrace -Wall -Wno-unused-dummy-argument -pedantic -======= list(APPEND f90flags -cpp -std=f2008ts -fbacktrace -O2 -fstack-arrays) if(debug) list(REMOVE_ITEM f90flags -O2 -fstack-arrays) list(APPEND f90flags -g -Wall -Wno-unused-dummy-argument -pedantic ->>>>>>> 47fbf8282ea94c138f75219bd10fdb31501d3fb7 -fbounds-check -ffpe-trap=invalid,overflow,underflow) list(APPEND ldflags -g -v -da -Q) endif() diff --git a/src/simulation.F90 b/src/simulation.F90 index e1a1dcc75..8fe4f025c 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -507,11 +507,6 @@ contains ! Broadcast tally results so that each process has access to results if (allocated(tallies)) then do i = 1, size(tallies) -<<<<<<< HEAD - 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 @@ -524,7 +519,6 @@ contains call MPI_BCAST(results, n, result_block, 0, mpi_intracomm, mpi_err) call MPI_TYPE_FREE(result_block, mpi_err) end associate ->>>>>>> 47fbf8282ea94c138f75219bd10fdb31501d3fb7 end do end if From 772c27ecb1f126cbf62b7bcdb86b823a679da54a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 29 Mar 2018 10:19:27 -0500 Subject: [PATCH 146/361] Combine expansion filters into single Python module --- docs/source/pythonapi/base.rst | 3 + openmc/__init__.py | 5 +- openmc/filter_expansion.py | 457 +++++++++++++++++++++++++++ openmc/filter_legendre.py | 90 ------ openmc/filter_spatial_legendre.py | 144 --------- openmc/filter_spherical_harmonics.py | 110 ------- openmc/filter_zernike.py | 144 --------- 7 files changed, 461 insertions(+), 492 deletions(-) create mode 100644 openmc/filter_expansion.py delete mode 100644 openmc/filter_legendre.py delete mode 100644 openmc/filter_spatial_legendre.py delete mode 100644 openmc/filter_spherical_harmonics.py delete mode 100644 openmc/filter_zernike.py diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index 84d2a39da..ef692755d 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -119,6 +119,9 @@ Constructing Tallies openmc.DelayedGroupFilter openmc.EnergyFunctionFilter openmc.LegendreFilter + openmc.SpatialLegendreFilter + openmc.SphericalHarmonicsFilter + openmc.ZernikeFilter openmc.Mesh openmc.Trigger openmc.TallyDerivative diff --git a/openmc/__init__.py b/openmc/__init__.py index 100aff8c8..191528327 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -15,10 +15,7 @@ from openmc.surface import * from openmc.universe import * from openmc.lattice import * from openmc.filter import * -from openmc.filter_legendre import * -from openmc.filter_spatial_legendre import * -from openmc.filter_spherical_harmonics import * -from openmc.filter_zernike import * +from openmc.filter_expansion import * from openmc.trigger import * from openmc.tally_derivative import * from openmc.tallies import * diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py new file mode 100644 index 000000000..5129f707c --- /dev/null +++ b/openmc/filter_expansion.py @@ -0,0 +1,457 @@ +from numbers import Integral, Real +from xml.etree import ElementTree as ET + +import numpy as np +import pandas as pd + +import openmc.checkvalue as cv +from . import Filter + + +class LegendreFilter(Filter): + r"""Score Legendre expansion moments up to specified order. + + This filter allows scores to be multiplied by Legendre polynomials of the + change in particle angle ($\mu$) up to a user-specified order. + + Parameters + ---------- + order : int + Maximum Legendre polynomial order + filter_id : int or None + Unique identifier for the filter + + Attributes + ---------- + order : int + Maximum Legendre polynomial order + id : int + Unique identifier for the filter + num_bins : int + The number of filter bins + + """ + + def __init__(self, order, filter_id=None): + self.order = order + self.bins = ['P{}'.format(i) for i in range(order + 1)] + self.id = filter_id + + def __hash__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + return hash(string) + + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tID', self.id) + return string + + @property + def order(self): + return self._order + + @order.setter + def order(self, order): + cv.check_type('Legendre order', order, Integral) + cv.check_greater_than('Legendre order', order, 0, equality=True) + self._order = order + + @classmethod + def from_hdf5(cls, group, **kwargs): + if group['type'].value.decode() != cls.short_name.lower(): + raise ValueError("Expected HDF5 data for filter type '" + + cls.short_name.lower() + "' but got '" + + group['type'].value.decode() + " instead") + + filter_id = int(group.name.split('/')[-1].lstrip('filter ')) + + out = cls(group['order'].value, filter_id) + + return out + + def to_xml_element(self): + """Return XML Element representing the filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing Legendre filter data + + """ + element = ET.Element('filter') + element.set('id', str(self.id)) + element.set('type', self.short_name.lower()) + + subelement = ET.SubElement(element, 'order') + subelement.text = str(self.order) + + return element + + +class SpatialLegendreFilter(Filter): + r"""Score Legendre expansion moments in space up to specified order. + + This filter allows scores to be multiplied by Legendre polynomials of the + the particle's position along a particular axis, normalized to a given + range, up to a user-specified order. + + Parameters + ---------- + order : int + Maximum Legendre polynomial order + axis : {'x', 'y', 'z'} + Axis along which to take the expansion + minimum : float + Minimum value along selected axis + maximum : float + Maximum value along selected axis + filter_id : int or None + Unique identifier for the filter + + Attributes + ---------- + order : int + Maximum Legendre polynomial order + axis : {'x', 'y', 'z'} + Axis along which to take the expansion + minimum : float + Minimum value along selected axis + maximum : float + Maximum value along selected axis + id : int + Unique identifier for the filter + num_bins : int + The number of filter bins + + """ + + def __init__(self, order, axis, minimum, maximum, filter_id=None): + self.order = order + self.axis = axis + self.minimum = minimum + self.maximum = maximum + self.bins = ['P{}'.format(i) for i in range(order + 1)] + self.id = filter_id + + def __hash__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tAxis', self.axis) + string += '{: <16}=\t{}\n'.format('\tMin', self.minimum) + string += '{: <16}=\t{}\n'.format('\tMax', self.maximum) + return hash(string) + + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tAxis', self.axis) + string += '{: <16}=\t{}\n'.format('\tMin', self.minimum) + string += '{: <16}=\t{}\n'.format('\tMax', self.maximum) + string += '{: <16}=\t{}\n'.format('\tID', self.id) + return string + + @property + def order(self): + return self._order + + @order.setter + def order(self, order): + cv.check_type('Legendre order', order, Integral) + cv.check_greater_than('Legendre order', order, 0, equality=True) + self._order = order + + @property + def axis(self): + return self._axis + + @axis.setter + def axis(self, axis): + cv.check_value('axis', axis, ('x', 'y', 'z')) + self._axis = axis + + @property + def minimum(self): + return self._minimum + + @minimum.setter + def minimum(self, minimum): + cv.check_type('minimum', minimum, Real) + self._minimum = minimum + + @property + def maximum(self): + return self._maximum + + @maximum.setter + def maximum(self, maximum): + cv.check_type('maximum', maximum, Real) + self._maximum = maximum + + @classmethod + def from_hdf5(cls, group, **kwargs): + if group['type'].value.decode() != cls.short_name.lower(): + raise ValueError("Expected HDF5 data for filter type '" + + cls.short_name.lower() + "' but got '" + + group['type'].value.decode() + " instead") + + filter_id = int(group.name.split('/')[-1].lstrip('filter ')) + order = group['order'].value + axis = group['axis'].value.decode() + min_, max_ = group['min'].value, group['max'].value + + return cls(order, axis, min_, max_, filter_id) + + def to_xml_element(self): + """Return XML Element representing the filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing Legendre filter data + + """ + element = ET.Element('filter') + element.set('id', str(self.id)) + element.set('type', self.short_name.lower()) + + subelement = ET.SubElement(element, 'order') + subelement.text = str(self.order) + subelement = ET.SubElement(element, 'axis') + subelement.text = self.axis + subelement = ET.SubElement(element, 'min') + subelement.text = str(self.minimum) + subelement = ET.SubElement(element, 'max') + subelement.text = str(self.maximum) + + return element + + +class SphericalHarmonicsFilter(Filter): + r"""Score spherical harmonic expansion moments up to specified order. + + Parameters + ---------- + order : int + Maximum spherical harmonics order + filter_id : int or None + Unique identifier for the filter + + Attributes + ---------- + order : int + Maximum spherical harmonics order + id : int + Unique identifier for the filter + cosine : {'scatter', 'particle'} + How to handle the cosine term. + num_bins : int + The number of filter bins + + """ + + def __init__(self, order, filter_id=None): + self.order = order + self.id = filter_id + self.bins = ['Y{},{}'.format(n, m) + for n in range(order + 1) + for m in range(-n, n + 1)] + self._cosine = 'particle' + + def __hash__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tCosine', self.cosine) + return hash(string) + + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tCosine', self.cosine) + string += '{: <16}=\t{}\n'.format('\tID', self.id) + return string + + @property + def order(self): + return self._order + + @order.setter + def order(self, order): + cv.check_type('spherical harmonics order', order, Integral) + cv.check_greater_than('spherical harmonics order', order, 0, equality=True) + self._order = order + + @property + def cosine(self): + return self._cosine + + @cosine.setter + def cosine(self, cosine): + cv.check_value('Spherical harmonics cosine treatment', cosine, + ('scatter', 'particle')) + self._cosine = cosine + + @classmethod + def from_hdf5(cls, group, **kwargs): + if group['type'].value.decode() != cls.short_name.lower(): + raise ValueError("Expected HDF5 data for filter type '" + + cls.short_name.lower() + "' but got '" + + group['type'].value.decode() + " instead") + + filter_id = int(group.name.split('/')[-1].lstrip('filter ')) + + out = cls(group['order'].value, filter_id) + out.cosine = group['cosine'].value.decode() + + return out + + def to_xml_element(self): + """Return XML Element representing the filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing spherical harmonics filter data + + """ + element = ET.Element('filter') + element.set('id', str(self.id)) + element.set('type', self.short_name.lower()) + element.set('cosine', self.cosine) + + subelement = ET.SubElement(element, 'order') + subelement.text = str(self.order) + + return element + + +class ZernikeFilter(Filter): + r"""Score Zernike expansion moments in space up to specified order. + + This filter allows scores to be multiplied by Zernike polynomials of the the + particle's position along a particular axis, normalized to a given unit + circle, up to a user-specified order. + + Parameters + ---------- + order : int + Maximum Zernike polynomial order + x : float + x-coordinate of center of circle for normalization + y : float + y-coordinate of center of circle for normalization + r : int or None + Radius of circle for normalization + + Attributes + ---------- + order : int + Maximum Zernike polynomial order + x : float + x-coordinate of center of circle for normalization + y : float + y-coordinate of center of circle for normalization + r : int or None + Radius of circle for normalization + id : int + Unique identifier for the filter + num_bins : int + The number of filter bins + + """ + + def __init__(self, order, x, y, r, filter_id=None): + self.order = order + self.x = x + self.y = y + self.r = r + self.bins = ['Z{},{}'.format(n, m) + for n in range(order + 1) + for m in range(-n, n + 1, 2)] + self.id = filter_id + + def __hash__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + return hash(string) + + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tOrder', self.order) + string += '{: <16}=\t{}\n'.format('\tID', self.id) + return string + + @property + def order(self): + return self._order + + @order.setter + def order(self, order): + cv.check_type('Zernike order', order, Integral) + cv.check_greater_than('Zernike order', order, 0, equality=True) + self._order = order + + @property + def x(self): + return self._x + + @x.setter + def x(self, x): + cv.check_type('x', x, Real) + self._x = x + + @property + def y(self): + return self._y + + @y.setter + def y(self, y): + cv.check_type('y', y, Real) + self._y = y + + @property + def r(self): + return self._r + + @r.setter + def r(self, r): + cv.check_type('r', r, Real) + self._r = r + + @classmethod + def from_hdf5(cls, group, **kwargs): + if group['type'].value.decode() != cls.short_name.lower(): + raise ValueError("Expected HDF5 data for filter type '" + + cls.short_name.lower() + "' but got '" + + group['type'].value.decode() + " instead") + + filter_id = int(group.name.split('/')[-1].lstrip('filter ')) + order = group['order'].value + x, y, r = group['x'].value, group['y'].value, group['r'].value + + return cls(order, x, y, r, filter_id) + + def to_xml_element(self): + """Return XML Element representing the filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing Zernike filter data + + """ + element = ET.Element('filter') + element.set('id', str(self.id)) + element.set('type', self.short_name.lower()) + + subelement = ET.SubElement(element, 'order') + subelement.text = str(self.order) + subelement = ET.SubElement(element, 'x') + subelement.text = str(self.x) + subelement = ET.SubElement(element, 'y') + subelement.text = str(self.y) + subelement = ET.SubElement(element, 'r') + subelement.text = str(self.r) + + return element diff --git a/openmc/filter_legendre.py b/openmc/filter_legendre.py deleted file mode 100644 index 18998d5cc..000000000 --- a/openmc/filter_legendre.py +++ /dev/null @@ -1,90 +0,0 @@ -from numbers import Integral -from xml.etree import ElementTree as ET - -import numpy as np -import pandas as pd - -import openmc.checkvalue as cv -from . import Filter - - -class LegendreFilter(Filter): - r"""Score Legendre expansion moments up to specified order. - - This filter allows scores to be multiplied by Legendre polynomials of the - change in particle angle ($\mu$) up to a user-specified order. - - Parameters - ---------- - order : int - Maximum Legendre polynomial order - filter_id : int or None - Unique identifier for the filter - - Attributes - ---------- - order : int - Maximum Legendre polynomial order - id : int - Unique identifier for the filter - num_bins : int - The number of filter bins - - """ - - def __init__(self, order, filter_id=None): - self.order = order - self.bins = ['P{}'.format(i) for i in range(order + 1)] - self.id = filter_id - - def __hash__(self): - string = type(self).__name__ + '\n' - string += '{: <16}=\t{}\n'.format('\tOrder', self.order) - return hash(string) - - def __repr__(self): - string = type(self).__name__ + '\n' - string += '{: <16}=\t{}\n'.format('\tOrder', self.order) - string += '{: <16}=\t{}\n'.format('\tID', self.id) - return string - - @property - def order(self): - return self._order - - @order.setter - def order(self, order): - cv.check_type('Legendre order', order, Integral) - cv.check_greater_than('Legendre order', order, 0, equality=True) - self._order = order - - @classmethod - def from_hdf5(cls, group, **kwargs): - if group['type'].value.decode() != cls.short_name.lower(): - raise ValueError("Expected HDF5 data for filter type '" - + cls.short_name.lower() + "' but got '" - + group['type'].value.decode() + " instead") - - filter_id = int(group.name.split('/')[-1].lstrip('filter ')) - - out = cls(group['order'].value, filter_id) - - return out - - def to_xml_element(self): - """Return XML Element representing the filter. - - Returns - ------- - element : xml.etree.ElementTree.Element - XML element containing Legendre filter data - - """ - element = ET.Element('filter') - element.set('id', str(self.id)) - element.set('type', self.short_name.lower()) - - subelement = ET.SubElement(element, 'order') - subelement.text = str(self.order) - - return element diff --git a/openmc/filter_spatial_legendre.py b/openmc/filter_spatial_legendre.py deleted file mode 100644 index 726a3bfdf..000000000 --- a/openmc/filter_spatial_legendre.py +++ /dev/null @@ -1,144 +0,0 @@ -from numbers import Integral, Real -from xml.etree import ElementTree as ET - -import numpy as np -import pandas as pd - -import openmc.checkvalue as cv -from . import Filter - - -class SpatialLegendreFilter(Filter): - r"""Score Legendre expansion moments in space up to specified order. - - This filter allows scores to be multiplied by Legendre polynomials of the - the particle's position along a particular axis, normalized to a given - range, up to a user-specified order. - - Parameters - ---------- - order : int - Maximum Legendre polynomial order - axis : {'x', 'y', 'z'} - Axis along which to take the expansion - minimum : float - Minimum value along selected axis - maximum : float - Maximum value along selected axis - filter_id : int or None - Unique identifier for the filter - - Attributes - ---------- - order : int - Maximum Legendre polynomial order - id : int - Unique identifier for the filter - num_bins : int - The number of filter bins - - """ - - def __init__(self, order, axis, minimum, maximum, filter_id=None): - self.order = order - self.axis = axis - self.minimum = minimum - self.maximum = maximum - self.bins = ['P{}'.format(i) for i in range(order + 1)] - self.id = filter_id - - def __hash__(self): - string = type(self).__name__ + '\n' - string += '{: <16}=\t{}\n'.format('\tOrder', self.order) - string += '{: <16}=\t{}\n'.format('\tAxis', self.axis) - string += '{: <16}=\t{}\n'.format('\tMin', self.minimum) - string += '{: <16}=\t{}\n'.format('\tMax', self.maximum) - return hash(string) - - def __repr__(self): - string = type(self).__name__ + '\n' - string += '{: <16}=\t{}\n'.format('\tOrder', self.order) - string += '{: <16}=\t{}\n'.format('\tAxis', self.axis) - string += '{: <16}=\t{}\n'.format('\tMin', self.minimum) - string += '{: <16}=\t{}\n'.format('\tMax', self.maximum) - string += '{: <16}=\t{}\n'.format('\tID', self.id) - return string - - @property - def order(self): - return self._order - - @order.setter - def order(self, order): - cv.check_type('Legendre order', order, Integral) - cv.check_greater_than('Legendre order', order, 0, equality=True) - self._order = order - - @property - def axis(self): - return self._axis - - @axis.setter - def axis(self, axis): - cv.check_value('axis', axis, ('x', 'y', 'z')) - self._axis = axis - - @property - def minimum(self): - return self._minimum - - @minimum.setter - def minimum(self, minimum): - cv.check_type('minimum', minimum, Real) - self._minimum = minimum - - @property - def maximum(self): - return self._maximum - - @maximum.setter - def maximum(self, maximum): - cv.check_type('maximum', maximum, Real) - self._maximum = maximum - - @property - def num_bins(self): - return self._order + 1 - - @classmethod - def from_hdf5(cls, group, **kwargs): - if group['type'].value.decode() != cls.short_name.lower(): - raise ValueError("Expected HDF5 data for filter type '" - + cls.short_name.lower() + "' but got '" - + group['type'].value.decode() + " instead") - - filter_id = int(group.name.split('/')[-1].lstrip('filter ')) - order = group['order'].value - axis = group['axis'].value.decode() - min_, max_ = group['min'].value, group['max'].value - - return cls(order, axis, min_, max_, filter_id) - - def to_xml_element(self): - """Return XML Element representing the filter. - - Returns - ------- - element : xml.etree.ElementTree.Element - XML element containing Legendre filter data - - """ - element = ET.Element('filter') - element.set('id', str(self.id)) - element.set('type', self.short_name.lower()) - - subelement = ET.SubElement(element, 'order') - subelement.text = str(self.order) - subelement = ET.SubElement(element, 'axis') - subelement.text = self.axis - subelement = ET.SubElement(element, 'min') - subelement.text = str(self.minimum) - subelement = ET.SubElement(element, 'max') - subelement.text = str(self.maximum) - - return element diff --git a/openmc/filter_spherical_harmonics.py b/openmc/filter_spherical_harmonics.py deleted file mode 100644 index f95c09c30..000000000 --- a/openmc/filter_spherical_harmonics.py +++ /dev/null @@ -1,110 +0,0 @@ -from numbers import Integral -from xml.etree import ElementTree as ET - -import numpy as np -import pandas as pd - -import openmc.checkvalue as cv -from . import Filter - - -class SphericalHarmonicsFilter(Filter): - r"""Score spherical harmonic expansion moments up to specified order. - - Parameters - ---------- - order : int - Maximum spherical harmonics order - filter_id : int or None - Unique identifier for the filter - - Attributes - ---------- - order : int - Maximum spherical harmonics order - id : int - Unique identifier for the filter - cosine : {'scatter', 'particle'} - How to handle the cosine term. - num_bins : int - The number of filter bins - - """ - - def __init__(self, order, filter_id=None): - self.order = order - self.id = filter_id - self.bins = ['Y{},{}'.format(n, m) - for n in range(order + 1) - for m in range(-n, n + 1)] - self._cosine = 'particle' - - def __hash__(self): - string = type(self).__name__ + '\n' - string += '{: <16}=\t{}\n'.format('\tOrder', self.order) - string += '{: <16}=\t{}\n'.format('\tCosine', self.cosine) - return hash(string) - - def __repr__(self): - string = type(self).__name__ + '\n' - string += '{: <16}=\t{}\n'.format('\tOrder', self.order) - string += '{: <16}=\t{}\n'.format('\tCosine', self.cosine) - string += '{: <16}=\t{}\n'.format('\tID', self.id) - return string - - @property - def order(self): - return self._order - - @order.setter - def order(self, order): - cv.check_type('spherical harmonics order', order, Integral) - cv.check_greater_than('spherical harmonics order', order, 0, equality=True) - self._order = order - - @property - def cosine(self): - return self._cosine - - @cosine.setter - def cosine(self, cosine): - cv.check_value('Spherical harmonics cosine treatment', cosine, - ('scatter', 'particle')) - self._cosine = cosine - - @property - def num_bins(self): - return (self._order + 1)**2 - - @classmethod - def from_hdf5(cls, group, **kwargs): - if group['type'].value.decode() != cls.short_name.lower(): - raise ValueError("Expected HDF5 data for filter type '" - + cls.short_name.lower() + "' but got '" - + group['type'].value.decode() + " instead") - - filter_id = int(group.name.split('/')[-1].lstrip('filter ')) - - out = cls(group['order'].value, filter_id) - out.cosine = group['cosine'].value.decode() - - return out - - def to_xml_element(self): - """Return XML Element representing the filter. - - Returns - ------- - element : xml.etree.ElementTree.Element - XML element containing spherical harmonics filter data - - """ - element = ET.Element('filter') - element.set('id', str(self.id)) - element.set('type', self.short_name.lower()) - element.set('cosine', self.cosine) - - subelement = ET.SubElement(element, 'order') - subelement.text = str(self.order) - - return element diff --git a/openmc/filter_zernike.py b/openmc/filter_zernike.py deleted file mode 100644 index a7697f5d0..000000000 --- a/openmc/filter_zernike.py +++ /dev/null @@ -1,144 +0,0 @@ -from numbers import Integral, Real -from xml.etree import ElementTree as ET - -import numpy as np -import pandas as pd - -import openmc.checkvalue as cv -from . import Filter - - -class ZernikeFilter(Filter): - r"""Score Zernike expansion moments in space up to specified order. - - This filter allows scores to be multiplied by Zernike polynomials of the the - particle's position along a particular axis, normalized to a given unit - circle, up to a user-specified order. - - Parameters - ---------- - order : int - Maximum Zernike polynomial order - x : float - x-coordinate of center of circle for normalization - y : float - y-coordinate of center of circle for normalization - r : int or None - Radius of circle for normalization - - Attributes - ---------- - order : int - Maximum Zernike polynomial order - x : float - x-coordinate of center of circle for normalization - y : float - y-coordinate of center of circle for normalization - r : int or None - Radius of circle for normalization - id : int - Unique identifier for the filter - num_bins : int - The number of filter bins - - """ - - def __init__(self, order, x, y, r, filter_id=None): - self.order = order - self.x = x - self.y = y - self.r = r - self.bins = ['Z{},{}'.format(n, m) - for n in range(order + 1) - for m in range(-n, n + 1, 2)] - self.id = filter_id - - def __hash__(self): - string = type(self).__name__ + '\n' - string += '{: <16}=\t{}\n'.format('\tOrder', self.order) - return hash(string) - - def __repr__(self): - string = type(self).__name__ + '\n' - string += '{: <16}=\t{}\n'.format('\tOrder', self.order) - string += '{: <16}=\t{}\n'.format('\tID', self.id) - return string - - @property - def order(self): - return self._order - - @order.setter - def order(self, order): - cv.check_type('Zernike order', order, Integral) - cv.check_greater_than('Zernike order', order, 0, equality=True) - self._order = order - - @property - def x(self): - return self._x - - @x.setter - def x(self, x): - cv.check_type('x', x, Real) - self._x = x - - @property - def y(self): - return self._y - - @y.setter - def y(self, y): - cv.check_type('y', y, Real) - self._y = y - - @property - def r(self): - return self._r - - @r.setter - def r(self, r): - cv.check_type('r', r, Real) - self._r = r - - @property - def num_bins(self): - n = self._order - return ((n + 1)*(n + 2))//2 - - @classmethod - def from_hdf5(cls, group, **kwargs): - if group['type'].value.decode() != cls.short_name.lower(): - raise ValueError("Expected HDF5 data for filter type '" - + cls.short_name.lower() + "' but got '" - + group['type'].value.decode() + " instead") - - filter_id = int(group.name.split('/')[-1].lstrip('filter ')) - order = group['order'].value - x, y, r = group['x'].value, group['y'].value, group['r'].value - - return cls(order, x, y, r, filter_id) - - def to_xml_element(self): - """Return XML Element representing the filter. - - Returns - ------- - element : xml.etree.ElementTree.Element - XML element containing Zernike filter data - - """ - element = ET.Element('filter') - element.set('id', str(self.id)) - element.set('type', self.short_name.lower()) - - subelement = ET.SubElement(element, 'order') - subelement.text = str(self.order) - subelement = ET.SubElement(element, 'x') - subelement.text = str(self.x) - subelement = ET.SubElement(element, 'y') - subelement.text = str(self.y) - subelement = ET.SubElement(element, 'r') - subelement.text = str(self.r) - - return element From f5270f183aa698227feb5d9275793425d35f7012 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 29 Mar 2018 13:35:58 -0500 Subject: [PATCH 147/361] Remove get_bin() method. Fix __repr__ for some filters --- openmc/filter.py | 92 ++++++------------- openmc/tallies.py | 8 +- .../tally_slice_merge/test.py | 4 +- 3 files changed, 30 insertions(+), 74 deletions(-) diff --git a/openmc/filter.py b/openmc/filter.py index d59d739a3..2bab17dbb 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -1,11 +1,9 @@ from abc import ABCMeta from collections import Iterable, OrderedDict import copy -from functools import reduce import hashlib from itertools import product from numbers import Real, Integral -import operator from xml.etree import ElementTree as ET import numpy as np @@ -20,9 +18,12 @@ from .surface import Surface from .universe import Universe -_FILTER_TYPES = ['universe', 'material', 'cell', 'cellborn', 'surface', - 'mesh', 'energy', 'energyout', 'mu', 'polar', 'azimuthal', - 'distribcell', 'delayedgroup', 'energyfunction', 'cellfrom'] +_FILTER_TYPES = ( + 'universe', 'material', 'cell', 'cellborn', 'surface', 'mesh', 'energy', + 'energyout', 'mu', 'polar', 'azimuthal', 'distribcell', 'delayedgroup', + 'energyfunction', 'cellfrom', 'legendre', 'spatiallegendre', + 'sphericalharmonics', 'zernike' +) _CURRENT_NAMES = ( 'x-min out', 'x-min in', 'x-max out', 'x-max in', @@ -187,17 +188,15 @@ class Filter(IDManagerMixin, metaclass=FilterMeta): def bins(self): return self._bins + @bins.setter + def bins(self, bins): + self.check_bins(bins) + self._bins = bins + @property def num_bins(self): return len(self.bins) - @bins.setter - def bins(self, bins): - # Check the bin values. - self.check_bins(bins) - - self._bins = bins - def check_bins(self, bins): """Make sure given bins are valid for this filter. @@ -303,7 +302,7 @@ class Filter(IDManagerMixin, metaclass=FilterMeta): Parameters ---------- - filter_bin : Integral or tuple + filter_bin : int or tuple The bin is the integer ID for 'material', 'surface', 'cell', 'cellborn', and 'universe' Filters. The bin is an integer for the cell instance ID for 'distribcell' Filters. The bin is a 2-tuple of @@ -314,13 +313,9 @@ class Filter(IDManagerMixin, metaclass=FilterMeta): Returns ------- - filter_index : Integral + filter_index : int The index in the Tally data array for this filter bin. - See also - -------- - Filter.get_bin() - """ if filter_bin not in self.bins: @@ -333,46 +328,6 @@ class Filter(IDManagerMixin, metaclass=FilterMeta): else: return self.bins.index(filter_bin) - def get_bin(self, bin_index): - """Returns the filter bin for some filter bin index. - - Parameters - ---------- - bin_index : Integral - The zero-based index into the filter's array of bins. The bin - index for 'material', 'surface', 'cell', 'cellborn', and 'universe' - filters corresponds to the ID in the filter's list of bins. For - 'distribcell' tallies the bin index necessarily can only be zero - since only one cell can be tracked per tally. The bin index for - 'energy' and 'energyout' filters corresponds to the energy range of - interest in the filter bins of energies. The bin index for 'mesh' - filters is the index into the flattened array of (x,y) or (x,y,z) - mesh cell bins. - - Returns - ------- - bin : 1-, 2-, or 3-tuple of Real - The bin in the Tally data array. The bin for 'material', surface', - 'cell', 'cellborn', 'universe' and 'distribcell' filters is a - 1-tuple of the ID corresponding to the appropriate filter bin. - The bin for 'energy' and 'energyout' filters is a 2-tuple of the - lower and upper energies bounding the energy interval for the filter - bin. The bin for 'mesh' tallies is a 2-tuple or 3-tuple of the x,y - or x,y,z mesh cell indices corresponding to the bin in a 2D/3D mesh. - - See also - -------- - Filter.get_bin_index() - - """ - - cv.check_type('bin_index', bin_index, Integral) - cv.check_greater_than('bin_index', bin_index, 0, equality=True) - cv.check_less_than('bin_index', bin_index, self.num_bins) - - # Return a 1-tuple of the bin. - return (self.bins[bin_index],) - def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. @@ -447,7 +402,7 @@ class UniverseFilter(WithIDFilter): Parameters ---------- - bins : openmc.Universe, Integral, or iterable thereof + bins : openmc.Universe, int, or iterable thereof The Universes to tally. Either openmc.Universe objects or their Integral ID numbers can be used. filter_id : int @@ -615,6 +570,12 @@ class MeshFilter(Filter): self.mesh = mesh self.id = filter_id + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tMesh ID', self.mesh.id) + string += '{: <16}=\t{}\n'.format('\tID', self.id) + return string + @classmethod def from_hdf5(cls, group, **kwargs): if group['type'].value.decode() != cls.short_name.lower(): @@ -648,9 +609,6 @@ class MeshFilter(Filter): # Mesh filters cannot have more than one bin return False - def get_bin(self, bin_index): - return self.bins[bin_index] - def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. @@ -900,6 +858,12 @@ class RealFilter(Filter): else: return super().__gt__(other) + def __repr__(self): + string = type(self).__name__ + '\n' + string += '{: <16}=\t{}\n'.format('\tValues', self.values) + string += '{: <16}=\t{}\n'.format('\tID', self.id) + return string + @Filter.bins.setter def bins(self, bins): Filter.bins.__set__(self, np.asarray(bins)) @@ -1740,10 +1704,6 @@ class EnergyFunctionFilter(Filter): # This filter only has one bin. Always return 0. return 0 - def get_bin(self, bin_index): - """This function is invalid for EnergyFunctionFilters.""" - raise RuntimeError('EnergyFunctionFilters have no get_bin() method') - def get_pandas_dataframe(self, data_size, stride, **kwargs): """Builds a Pandas DataFrame for the Filter's bins. diff --git a/openmc/tallies.py b/openmc/tallies.py index 08db20e39..50398b786 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -2763,9 +2763,7 @@ class Tally(IDManagerMixin): elif isinstance(find_filter, openmc.EnergyFunctionFilter): filter_bins = [None] else: - num_bins = find_filter.num_bins - filter_bins = \ - [(find_filter.get_bin(i)) for i in range(num_bins)] + filter_bins = find_filter.bins # Only sum across bins specified by the user else: @@ -2917,9 +2915,7 @@ class Tally(IDManagerMixin): elif isinstance(find_filter, openmc.EnergyFunctionFilter): filter_bins = [None] else: - num_bins = find_filter.num_bins - filter_bins = \ - [(find_filter.get_bin(i)) for i in range(num_bins)] + filter_bins = find_filter.bins # Only average across bins specified by the user else: diff --git a/tests/regression_tests/tally_slice_merge/test.py b/tests/regression_tests/tally_slice_merge/test.py index 7146c0d09..20ab57a07 100644 --- a/tests/regression_tests/tally_slice_merge/test.py +++ b/tests/regression_tests/tally_slice_merge/test.py @@ -87,13 +87,13 @@ class TallySliceMergeTestHarness(PyAPITestHarness): # Slice the tallies by cell filter bins cell_filter_prod = itertools.product(tallies, self.cell_filters) tallies = map(lambda tf: tf[0].get_slice(filters=[type(tf[1])], - filter_bins=[tf[1].get_bin(0)]), + filter_bins=[(tf[1].bins[0],)]), cell_filter_prod) # Slice the tallies by energy filter bins energy_filter_prod = itertools.product(tallies, self.energy_filters) tallies = map(lambda tf: tf[0].get_slice(filters=[type(tf[1])], - filter_bins=[tf[1].get_bin(0)]), + filter_bins=[(tf[1].bins[0],)]), energy_filter_prod) # Slice the tallies by nuclide From 31ef72f2cd9029194a662019f0e6a157e29d893d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 29 Mar 2018 14:08:06 -0500 Subject: [PATCH 148/361] Introduce ExpansionFilter class and start adding filter tests --- openmc/filter_expansion.py | 154 +++++++++++++------------------ tests/unit_tests/test_filters.py | 80 ++++++++++++++++ 2 files changed, 142 insertions(+), 92 deletions(-) create mode 100644 tests/unit_tests/test_filters.py diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index 5129f707c..f51e2f58d 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -8,7 +8,42 @@ import openmc.checkvalue as cv from . import Filter -class LegendreFilter(Filter): +class ExpansionFilter(Filter): + """Abstract filter class for functional expansions.""" + def __init__(self, order, filter_id=None): + self.order = order + self.id = filter_id + + @property + def order(self): + return self._order + + @order.setter + def order(self, order): + cv.check_type('expansion order', order, Integral) + cv.check_greater_than('expansion order', order, 0, equality=True) + self._order = order + + def to_xml_element(self): + """Return XML Element representing the filter. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing Legendre filter data + + """ + element = ET.Element('filter') + element.set('id', str(self.id)) + element.set('type', self.short_name.lower()) + + subelement = ET.SubElement(element, 'order') + subelement.text = str(self.order) + + return element + + +class LegendreFilter(ExpansionFilter): r"""Score Legendre expansion moments up to specified order. This filter allows scores to be multiplied by Legendre polynomials of the @@ -32,11 +67,6 @@ class LegendreFilter(Filter): """ - def __init__(self, order, filter_id=None): - self.order = order - self.bins = ['P{}'.format(i) for i in range(order + 1)] - self.id = filter_id - def __hash__(self): string = type(self).__name__ + '\n' string += '{: <16}=\t{}\n'.format('\tOrder', self.order) @@ -48,15 +78,10 @@ class LegendreFilter(Filter): string += '{: <16}=\t{}\n'.format('\tID', self.id) return string - @property - def order(self): - return self._order - - @order.setter + @ExpansionFilter.order.setter def order(self, order): - cv.check_type('Legendre order', order, Integral) - cv.check_greater_than('Legendre order', order, 0, equality=True) - self._order = order + ExpansionFilter.order.__set__(self, order) + self.bins = ['P{}'.format(i) for i in range(order + 1)] @classmethod def from_hdf5(cls, group, **kwargs): @@ -71,26 +96,8 @@ class LegendreFilter(Filter): return out - def to_xml_element(self): - """Return XML Element representing the filter. - Returns - ------- - element : xml.etree.ElementTree.Element - XML element containing Legendre filter data - - """ - element = ET.Element('filter') - element.set('id', str(self.id)) - element.set('type', self.short_name.lower()) - - subelement = ET.SubElement(element, 'order') - subelement.text = str(self.order) - - return element - - -class SpatialLegendreFilter(Filter): +class SpatialLegendreFilter(ExpansionFilter): r"""Score Legendre expansion moments in space up to specified order. This filter allows scores to be multiplied by Legendre polynomials of the @@ -128,12 +135,10 @@ class SpatialLegendreFilter(Filter): """ def __init__(self, order, axis, minimum, maximum, filter_id=None): - self.order = order + super().__init__(order, filter_id) self.axis = axis self.minimum = minimum self.maximum = maximum - self.bins = ['P{}'.format(i) for i in range(order + 1)] - self.id = filter_id def __hash__(self): string = type(self).__name__ + '\n' @@ -152,15 +157,10 @@ class SpatialLegendreFilter(Filter): string += '{: <16}=\t{}\n'.format('\tID', self.id) return string - @property - def order(self): - return self._order - - @order.setter + @ExpansionFilter.order.setter def order(self, order): - cv.check_type('Legendre order', order, Integral) - cv.check_greater_than('Legendre order', order, 0, equality=True) - self._order = order + ExpansionFilter.order.__set__(self, order) + self.bins = ['P{}'.format(i) for i in range(order + 1)] @property def axis(self): @@ -212,12 +212,7 @@ class SpatialLegendreFilter(Filter): XML element containing Legendre filter data """ - element = ET.Element('filter') - element.set('id', str(self.id)) - element.set('type', self.short_name.lower()) - - subelement = ET.SubElement(element, 'order') - subelement.text = str(self.order) + element = super().to_xml_element() subelement = ET.SubElement(element, 'axis') subelement.text = self.axis subelement = ET.SubElement(element, 'min') @@ -228,7 +223,7 @@ class SpatialLegendreFilter(Filter): return element -class SphericalHarmonicsFilter(Filter): +class SphericalHarmonicsFilter(ExpansionFilter): r"""Score spherical harmonic expansion moments up to specified order. Parameters @@ -252,11 +247,7 @@ class SphericalHarmonicsFilter(Filter): """ def __init__(self, order, filter_id=None): - self.order = order - self.id = filter_id - self.bins = ['Y{},{}'.format(n, m) - for n in range(order + 1) - for m in range(-n, n + 1)] + super().__init__(order, filter_id) self._cosine = 'particle' def __hash__(self): @@ -272,15 +263,12 @@ class SphericalHarmonicsFilter(Filter): string += '{: <16}=\t{}\n'.format('\tID', self.id) return string - @property - def order(self): - return self._order - - @order.setter + @ExpansionFilter.order.setter def order(self, order): - cv.check_type('spherical harmonics order', order, Integral) - cv.check_greater_than('spherical harmonics order', order, 0, equality=True) - self._order = order + ExpansionFilter.order.__set__(self, order) + self.bins = ['Y{},{}'.format(n, m) + for n in range(order + 1) + for m in range(-n, n + 1)] @property def cosine(self): @@ -315,18 +303,12 @@ class SphericalHarmonicsFilter(Filter): XML element containing spherical harmonics filter data """ - element = ET.Element('filter') - element.set('id', str(self.id)) - element.set('type', self.short_name.lower()) + element = super().to_xml_element() element.set('cosine', self.cosine) - - subelement = ET.SubElement(element, 'order') - subelement.text = str(self.order) - return element -class ZernikeFilter(Filter): +class ZernikeFilter(ExpansionFilter): r"""Score Zernike expansion moments in space up to specified order. This filter allows scores to be multiplied by Zernike polynomials of the the @@ -361,15 +343,11 @@ class ZernikeFilter(Filter): """ - def __init__(self, order, x, y, r, filter_id=None): - self.order = order + def __init__(self, order, x=0.0, y=0.0, r=1.0, filter_id=None): + super().__init__(order, filter_id) self.x = x self.y = y self.r = r - self.bins = ['Z{},{}'.format(n, m) - for n in range(order + 1) - for m in range(-n, n + 1, 2)] - self.id = filter_id def __hash__(self): string = type(self).__name__ + '\n' @@ -382,15 +360,12 @@ class ZernikeFilter(Filter): string += '{: <16}=\t{}\n'.format('\tID', self.id) return string - @property - def order(self): - return self._order - - @order.setter + @ExpansionFilter.order.setter def order(self, order): - cv.check_type('Zernike order', order, Integral) - cv.check_greater_than('Zernike order', order, 0, equality=True) - self._order = order + ExpansionFilter.order.__set__(self, order) + self.bins = ['Z{},{}'.format(n, m) + for n in range(order + 1) + for m in range(-n, n + 1, 2)] @property def x(self): @@ -441,12 +416,7 @@ class ZernikeFilter(Filter): XML element containing Zernike filter data """ - element = ET.Element('filter') - element.set('id', str(self.id)) - element.set('type', self.short_name.lower()) - - subelement = ET.SubElement(element, 'order') - subelement.text = str(self.order) + element = super().to_xml_element() subelement = ET.SubElement(element, 'x') subelement.text = str(self.x) subelement = ET.SubElement(element, 'y') diff --git a/tests/unit_tests/test_filters.py b/tests/unit_tests/test_filters.py new file mode 100644 index 000000000..53f8f74b5 --- /dev/null +++ b/tests/unit_tests/test_filters.py @@ -0,0 +1,80 @@ +import openmc + + +def test_legendre(): + n = 5 + f = openmc.LegendreFilter(n) + assert f.order == n + assert f.bins[0] == 'P0' + assert f.bins[-1] == 'P5' + assert len(f.bins) == n + 1 + + # Make sure __repr__ works + repr(f) + + # to_xml_element() + elem = f.to_xml_element() + assert elem.tag == 'filter' + assert elem.attrib['type'] == 'legendre' + assert elem.find('order').text == str(n) + + +def test_spatial_legendre(): + n = 5 + axis = 'x' + f = openmc.SpatialLegendreFilter(n, axis, -10., 10.) + assert f.order == n + assert f.axis == axis + assert f.minimum == -10. + assert f.maximum == 10. + assert f.bins[0] == 'P0' + assert f.bins[-1] == 'P5' + assert len(f.bins) == n + 1 + + # Make sure __repr__ works + repr(f) + + # to_xml_element() + elem = f.to_xml_element() + assert elem.tag == 'filter' + assert elem.attrib['type'] == 'spatiallegendre' + assert elem.find('order').text == str(n) + assert elem.find('axis').text == str(axis) + + +def test_spherical_harmonics(): + n = 3 + f = openmc.SphericalHarmonicsFilter(n) + f.cosine = 'particle' + assert f.order == n + assert f.bins[0] == 'Y0,0' + assert f.bins[-1] == 'Y{0},{0}'.format(n, n) + assert len(f.bins) == (n + 1)**2 + + # Make sure __repr__ works + repr(f) + + # to_xml_element() + elem = f.to_xml_element() + assert elem.tag == 'filter' + assert elem.attrib['type'] == 'sphericalharmonics' + assert elem.attrib['cosine'] == f.cosine + assert elem.find('order').text == str(n) + + +def test_zernike(): + n = 4 + f = openmc.ZernikeFilter(n, 0., 0., 1.) + assert f.order == n + assert f.bins[0] == 'Z0,0' + assert f.bins[-1] == 'Z{0},{0}'.format(n) + assert len(f.bins) == (n + 1)*(n + 2)//2 + + # Make sure __repr__ works + repr(f) + + # to_xml_element() + elem = f.to_xml_element() + assert elem.tag == 'filter' + assert elem.attrib['type'] == 'zernike' + assert elem.find('order').text == str(n) From f5137f7316fcab1f8be3b4eca36356b0894eb8b5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 30 Mar 2018 09:29:17 -0500 Subject: [PATCH 149/361] Add first moment test for expansion filters --- openmc/filter_expansion.py | 11 +++++- tests/unit_tests/test_filters.py | 68 ++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index f51e2f58d..20bb08e77 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -311,9 +311,16 @@ class SphericalHarmonicsFilter(ExpansionFilter): class ZernikeFilter(ExpansionFilter): r"""Score Zernike expansion moments in space up to specified order. - This filter allows scores to be multiplied by Zernike polynomials of the the + This filter allows scores to be multiplied by Zernike polynomials of the particle's position along a particular axis, normalized to a given unit - circle, up to a user-specified order. + circle, up to a user-specified order. Specifying a filter with order N + tallies moments for all radial orders from 0 to N and each azimuthal order + for a given radial order. The ordering of the Zernike polynomial moments + follows the ANSI Z80.28 standard, where bin :math:`j` corresponds to the + radial index :math:`n` and the azimuthal index :math:`m` by + + .. math:: + j = \frac{n(n + 2) + m}{2}. Parameters ---------- diff --git a/tests/unit_tests/test_filters.py b/tests/unit_tests/test_filters.py index 53f8f74b5..6060b55d7 100644 --- a/tests/unit_tests/test_filters.py +++ b/tests/unit_tests/test_filters.py @@ -1,4 +1,25 @@ +from math import sqrt, pi + import openmc +from pytest import fixture, approx + + +@fixture(scope='module') +def box_model(): + model = openmc.model.Model() + m = openmc.Material() + m.add_nuclide('U235', 1.0) + m.set_density('g/cm3', 1.0) + + box = openmc.model.get_rectangular_prism(10., 10., boundary_type='vacuum') + c = openmc.Cell(fill=m, region=box) + model.geometry.root_universe = openmc.Universe(cells=[c]) + + model.settings.particles = 100 + model.settings.batches = 10 + model.settings.inactive = 0 + model.settings.source = openmc.Source(space=openmc.stats.Point()) + return model def test_legendre(): @@ -78,3 +99,50 @@ def test_zernike(): assert elem.tag == 'filter' assert elem.attrib['type'] == 'zernike' assert elem.find('order').text == str(n) + + +def test_first_moment(run_in_tmpdir, box_model): + plain_tally = openmc.Tally() + plain_tally.scores = ['flux', 'scatter'] + + # Create tallies with expansion filters + leg_tally = openmc.Tally() + leg_tally.filters = [openmc.LegendreFilter(3)] + leg_tally.scores = ['scatter'] + leg_sptl_tally = openmc.Tally() + leg_sptl_tally.filters = [openmc.SpatialLegendreFilter(3, 'x', -5., 5.)] + leg_sptl_tally.scores = ['scatter'] + sph_scat_filter = openmc.SphericalHarmonicsFilter(5) + sph_scat_filter.cosine = 'scatter' + sph_scat_tally = openmc.Tally() + sph_scat_tally.filters = [sph_scat_filter] + sph_scat_tally.scores = ['scatter'] + sph_flux_filter = openmc.SphericalHarmonicsFilter(5) + sph_flux_filter.cosine = 'particle' + sph_flux_tally = openmc.Tally() + sph_flux_tally.filters = [sph_flux_filter] + sph_flux_tally.scores = ['flux'] + zernike_tally = openmc.Tally() + zernike_tally.filters = [openmc.ZernikeFilter(3, r=10.)] + zernike_tally.scores = ['scatter'] + + # Add tallies to model and ensure they all use the same estimator + box_model.tallies = [plain_tally, leg_tally, leg_sptl_tally, + sph_scat_tally, sph_flux_tally, zernike_tally] + for t in box_model.tallies: + t.estimator = 'analog' + + box_model.run() + + # Check that first moment matches the score from the plain tally + with openmc.StatePoint('statepoint.10.h5') as sp: + # Get scores from tally without expansion filters + flux, scatter = sp.tallies[plain_tally.id].mean.ravel() + + # Check that first moment matches + first_score = lambda t: sp.tallies[t.id].mean.ravel()[0] + assert first_score(leg_tally) == scatter + assert first_score(leg_sptl_tally) == scatter + assert first_score(sph_scat_tally) == scatter + assert first_score(sph_flux_tally) == approx(flux) + assert first_score(zernike_tally)*sqrt(pi) == approx(scatter) From a3240b69c37faa6c4c62ea5b2bcb371280bf9e2e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 30 Mar 2018 13:21:15 -0500 Subject: [PATCH 150/361] Add C API functions for expansion filters --- docs/source/capi/index.rst | 5 +- include/openmc.h | 18 +++- src/tallies/tally_filter.F90 | 112 ++++++++++----------- src/tallies/tally_filter_energy.F90 | 75 ++++++-------- src/tallies/tally_filter_header.F90 | 22 ++++ src/tallies/tally_filter_legendre.F90 | 41 +++++++- src/tallies/tally_filter_material.F90 | 71 ++++++------- src/tallies/tally_filter_sph_harm.F90 | 108 +++++++++++++++++++- src/tallies/tally_filter_sptl_legendre.F90 | 97 +++++++++++++++++- src/tallies/tally_filter_zernike.F90 | 83 +++++++++++++++ 10 files changed, 475 insertions(+), 157 deletions(-) diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index d63ed1f3a..0e6a2536c 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -247,11 +247,12 @@ Functions :return: Return status (negative if an error occurs) :rtype: int -.. c:function:: int openmc_get_nuclide_index(char name[], int* index) +.. c:function:: int openmc_get_nuclide_index(const char name[], int* index) Get the index in the nuclides array for a nuclide with a given name - :param char[] name: Name of the nuclide + :param name: Name of the nuclide + :type name: const char[] :param int* index: Index in the nuclides array :return: Return status (negative if an error occurs) :rtype: int diff --git a/include/openmc.h b/include/openmc.h index 10c0b2de8..1c2106432 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -38,10 +38,12 @@ extern "C" { 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_nuclide_index(const char name[], int* index); int openmc_get_tally_index(int32_t id, int32_t* index); void openmc_hard_reset(); void openmc_init(const int* intracomm); + int openmc_legendre_filter_get_order(int32_t index, int* order); + int openmc_legendre_filter_set_order(int32_t index, int order); int openmc_load_nuclide(char name[]); int openmc_material_add_nuclide(int32_t index, const char name[], double density); int openmc_material_get_densities(int32_t index, int** nuclides, double** densities, int* n); @@ -62,6 +64,15 @@ extern "C" { void openmc_simulation_init(); int openmc_source_bank(struct Bank** ptr, int64_t* n); int openmc_source_set_strength(int32_t index, double strength); + int openmc_spatial_legendre_filter_get_order(int32_t index, int* order); + int openmc_spatial_legendre_filter_get_params(int32_t index, int* axis, double* min, double* max); + int openmc_spatial_legendre_filter_set_order(int32_t index, int order); + int openmc_spatial_legendre_filter_set_params(int32_t index, const int* axis, + const double* min, const double* max); + int openmc_sphharm_filter_get_order(int32_t index, int* order); + int openmc_sphharm_filter_get_cosine(int32_t index, char cosine[]); + int openmc_sphharm_filter_set_order(int32_t index, int order); + int openmc_sphharm_filter_set_cosine(int32_t index, const char cosine[]); 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); @@ -73,6 +84,11 @@ extern "C" { int openmc_tally_set_id(int32_t index, int32_t id); int openmc_tally_set_nuclides(int32_t index, int n, const char** nuclides); int openmc_tally_set_scores(int32_t index, int n, const int* scores); + int openmc_zernike_filter_get_order(int32_t index, int* order); + int openmc_zernike_filter_get_params(int32_t index, double* x, double* y, double* r); + int openmc_zernike_filter_set_order(int32_t index, int order); + int openmc_zernike_filter_set_params(int32_t index, const double* x, + const double* y, const double* r); // Error codes extern int E_UNASSIGNED; diff --git a/src/tallies/tally_filter.F90 b/src/tallies/tally_filter.F90 index 0277f2465..e484ac231 100644 --- a/src/tallies/tally_filter.F90 +++ b/src/tallies/tally_filter.F90 @@ -46,68 +46,60 @@ contains integer :: i character(20) :: type_ - if (index >= 1 .and. index <= n_filters) then - if (allocated(filters(index) % obj)) then - ! Get type as a Fortran string - select type (f => filters(index) % obj) - type is (AzimuthalFilter) - type_ = 'azimuthal' - type is (CellFilter) - type_ = 'cell' - type is (CellbornFilter) - type_ = 'cellborn' - type is (CellfromFilter) - type_ = 'cellfrom' - type is (DelayedGroupFilter) - type_ = 'delayedgroup' - type is (DistribcellFilter) - type_ = 'distribcell' - type is (EnergyFilter) - type_ = 'energy' - type is (EnergyoutFilter) - type_ = 'energyout' - type is (EnergyFunctionFilter) - type_ = 'energyfunction' - type is (LegendreFilter) - type_ = 'legendre' - type is (MaterialFilter) - type_ = 'material' - type is (MeshFilter) - type_ = 'mesh' - type is (MeshSurfaceFilter) - type_ = 'meshsurface' - type is (MuFilter) - type_ = 'mu' - type is (PolarFilter) - type_ = 'polar' - type is (SphericalHarmonicsFilter) - type_ = 'sphericalharmonics' - type is (SpatialLegendreFilter) - type_ = 'spatiallegendre' - type is (SurfaceFilter) - type_ = 'surface' - type is (UniverseFilter) - type_ = 'universe' - type is (ZernikeFilter) - type_ = 'zernike' - end select + err = verify_filter(index) + if (err == 0) then + ! Get type as a Fortran string + select type (f => filters(index) % obj) + type is (AzimuthalFilter) + type_ = 'azimuthal' + type is (CellFilter) + type_ = 'cell' + type is (CellbornFilter) + type_ = 'cellborn' + type is (CellfromFilter) + type_ = 'cellfrom' + type is (DelayedGroupFilter) + type_ = 'delayedgroup' + type is (DistribcellFilter) + type_ = 'distribcell' + type is (EnergyFilter) + type_ = 'energy' + type is (EnergyoutFilter) + type_ = 'energyout' + type is (EnergyFunctionFilter) + type_ = 'energyfunction' + type is (LegendreFilter) + type_ = 'legendre' + type is (MaterialFilter) + type_ = 'material' + type is (MeshFilter) + type_ = 'mesh' + type is (MeshSurfaceFilter) + type_ = 'meshsurface' + type is (MuFilter) + type_ = 'mu' + type is (PolarFilter) + type_ = 'polar' + type is (SphericalHarmonicsFilter) + type_ = 'sphericalharmonics' + type is (SpatialLegendreFilter) + type_ = 'spatiallegendre' + type is (SurfaceFilter) + type_ = 'surface' + type is (UniverseFilter) + type_ = 'universe' + type is (ZernikeFilter) + type_ = 'zernike' + end select - ! Convert Fortran string to null-terminated C string. We assume the - ! caller has allocated a char array buffer - do i = 1, len_trim(type_) - type(i) = type_(i:i) - end do - type(len_trim(type_) + 1) = C_NULL_CHAR - - err = 0 - else - err = E_ALLOCATE - call set_errmsg("Filter type has not been set yet.") - end if - else - err = E_OUT_OF_BOUNDS - call set_errmsg("Index in filters array is out of bounds.") + ! Convert Fortran string to null-terminated C string. We assume the + ! caller has allocated a char array buffer + do i = 1, len_trim(type_) + type(i) = type_(i:i) + end do + type(len_trim(type_) + 1) = C_NULL_CHAR end if + end function openmc_filter_get_type diff --git a/src/tallies/tally_filter_energy.F90 b/src/tallies/tally_filter_energy.F90 index caba6755d..d186fa62a 100644 --- a/src/tallies/tally_filter_energy.F90 +++ b/src/tallies/tally_filter_energy.F90 @@ -204,28 +204,21 @@ contains integer(C_INT32_T), intent(out) :: n integer(C_INT) :: err - if (index >= 1 .and. index <= n_filters) then - if (allocated(filters(index) % obj)) then - select type (f => filters(index) % obj) - type is (EnergyFilter) - energies = C_LOC(f % bins) - n = size(f % bins) - err = 0 - type is (EnergyoutFilter) - energies = C_LOC(f % bins) - n = size(f % bins) - err = 0 + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (EnergyFilter) + energies = C_LOC(f % bins) + n = size(f % bins) + err = 0 + type is (EnergyoutFilter) + energies = C_LOC(f % bins) + n = size(f % bins) + err = 0 class default - err = E_INVALID_TYPE - call set_errmsg("Tried to get energy bins on a non-energy filter.") - end select - else - err = E_ALLOCATE - call set_errmsg("Filter type has not been set yet.") - end if - else - err = E_OUT_OF_BOUNDS - call set_errmsg("Index in filters array out of bounds.") + err = E_INVALID_TYPE + call set_errmsg("Tried to get energy bins on a non-energy filter.") + end select end if end function openmc_energy_filter_get_bins @@ -237,31 +230,23 @@ contains real(C_DOUBLE), intent(in) :: energies(n) integer(C_INT) :: err - err = 0 - if (index >= 1 .and. index <= n_filters) then - if (allocated(filters(index) % obj)) then - select type (f => filters(index) % obj) - type is (EnergyFilter) - f % n_bins = n - 1 - if (allocated(f % bins)) deallocate(f % bins) - allocate(f % bins(n)) - f % bins(:) = energies - type is (EnergyoutFilter) - f % n_bins = n - 1 - if (allocated(f % bins)) deallocate(f % bins) - allocate(f % bins(n)) - f % bins(:) = energies + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (EnergyFilter) + f % n_bins = n - 1 + if (allocated(f % bins)) deallocate(f % bins) + allocate(f % bins(n)) + f % bins(:) = energies + type is (EnergyoutFilter) + f % n_bins = n - 1 + if (allocated(f % bins)) deallocate(f % bins) + allocate(f % bins(n)) + f % bins(:) = energies class default - err = E_INVALID_TYPE - call set_errmsg("Tried to get energy bins on a non-energy filter.") - end select - else - err = E_ALLOCATE - call set_errmsg("Filter type has not been set yet.") - end if - else - err = E_OUT_OF_BOUNDS - call set_errmsg("Index in filters array out of bounds.") + err = E_INVALID_TYPE + call set_errmsg("Tried to get energy bins on a non-energy filter.") + end select end if end function openmc_energy_filter_set_bins diff --git a/src/tallies/tally_filter_header.F90 b/src/tallies/tally_filter_header.F90 index 34ad75388..93d050b1a 100644 --- a/src/tallies/tally_filter_header.F90 +++ b/src/tallies/tally_filter_header.F90 @@ -15,6 +15,7 @@ module tally_filter_header implicit none private public :: free_memory_tally_filter + public :: verify_filter public :: openmc_extend_filters public :: openmc_filter_get_id public :: openmc_filter_set_id @@ -148,6 +149,27 @@ contains largest_filter_id = 0 end subroutine free_memory_tally_filter +!=============================================================================== +! VERIFY_FILTER makes sure that given a filter index, the size of the filters +! array is sufficient and a filter object has already been allocated. +!=============================================================================== + + function verify_filter(index) result(err) + integer(C_INT32_T), intent(in) :: index + integer(C_INT) :: err + + err = 0 + if (index >= 1 .and. index <= n_filters) then + if (.not. allocated(filters(index) % obj)) then + err = E_ALLOCATE + call set_errmsg("Filter type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in filters array out of bounds.") + end if + end function verify_filter + !=============================================================================== ! C API FUNCTIONS !=============================================================================== diff --git a/src/tallies/tally_filter_legendre.F90 b/src/tallies/tally_filter_legendre.F90 index a97ed7faf..9545b8692 100644 --- a/src/tallies/tally_filter_legendre.F90 +++ b/src/tallies/tally_filter_legendre.F90 @@ -15,13 +15,15 @@ module tally_filter_legendre implicit none private + public :: openmc_legendre_filter_get_order + public :: openmc_legendre_filter_set_order !=============================================================================== ! LEGENDREFILTER gives Legendre moments of the change in scattering angle !=============================================================================== type, public, extends(TallyFilter) :: LegendreFilter - integer :: order + integer(C_INT) :: order contains procedure :: from_xml procedure :: get_all_bins @@ -82,5 +84,42 @@ contains ! C API FUNCTIONS !=============================================================================== + function openmc_legendre_filter_get_order(index, order) result(err) bind(C) + ! Get the order of an expansion filter + integer(C_INT32_T), value :: index + integer(C_INT), intent(out) :: order + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (LegendreFilter) + order = f % order + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get order on a non-expansion filter.") + end select + end if + end function openmc_legendre_filter_get_order + + + function openmc_legendre_filter_set_order(index, order) result(err) bind(C) + ! Set the order of an expansion filter + integer(C_INT32_T), value :: index + integer(C_INT), value :: order + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (LegendreFilter) + f % order = order + f % n_bins = order + 1 + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set order on a non-expansion filter.") + end select + end if + end function openmc_legendre_filter_set_order end module tally_filter_legendre diff --git a/src/tallies/tally_filter_material.F90 b/src/tallies/tally_filter_material.F90 index 778fec4b6..3b9f843f8 100644 --- a/src/tallies/tally_filter_material.F90 +++ b/src/tallies/tally_filter_material.F90 @@ -126,25 +126,18 @@ contains integer(C_INT32_T), intent(out) :: n integer(C_INT) :: err - if (index >= 1 .and. index <= n_filters) then - if (allocated(filters(index) % obj)) then - select type (f => filters(index) % obj) - type is (MaterialFilter) - bins = C_LOC(f % materials) - n = size(f % materials) - err = 0 + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (MaterialFilter) + bins = C_LOC(f % materials) + n = size(f % materials) + err = 0 class default - err = E_INVALID_TYPE - call set_errmsg("Tried to get material filter bins on a & - &non-material filter.") - end select - else - err = E_ALLOCATE - call set_errmsg("Filter type has not been set yet.") - end if - else - err = E_OUT_OF_BOUNDS - call set_errmsg("Index in filters array out of bounds.") + err = E_INVALID_TYPE + call set_errmsg("Tried to get material filter bins on a & + &non-material filter.") + end select end if end function openmc_material_filter_get_bins @@ -158,34 +151,26 @@ contains integer :: i - err = 0 - if (index >= 1 .and. index <= n_filters) then - if (allocated(filters(index) % obj)) then - select type (f => filters(index) % obj) - type is (MaterialFilter) - f % n_bins = n - if (allocated(f % materials)) deallocate(f % materials) - allocate(f % materials(n)) - f % materials(:) = bins + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (MaterialFilter) + f % n_bins = n + if (allocated(f % materials)) deallocate(f % materials) + allocate(f % materials(n)) + f % materials(:) = bins - ! Generate mapping from material indices to filter bins. - call f % map % clear() - do i = 1, n - call f % map % set(f % materials(i), i) - end do + ! Generate mapping from material indices to filter bins. + call f % map % clear() + do i = 1, n + call f % map % set(f % materials(i), i) + end do class default - err = E_INVALID_TYPE - call set_errmsg("Tried to set material filter bins on a & - &non-material filter.") - end select - else - err = E_ALLOCATE - call set_errmsg("Filter type has not been set yet.") - end if - else - err = E_OUT_OF_BOUNDS - call set_errmsg("Index in filters array out of bounds.") + err = E_INVALID_TYPE + call set_errmsg("Tried to set material filter bins on a & + &non-material filter.") + end select end if end function openmc_material_filter_set_bins diff --git a/src/tallies/tally_filter_sph_harm.F90 b/src/tallies/tally_filter_sph_harm.F90 index 9ccfde422..2d3c0bf6d 100644 --- a/src/tallies/tally_filter_sph_harm.F90 +++ b/src/tallies/tally_filter_sph_harm.F90 @@ -9,12 +9,16 @@ module tally_filter_sph_harm use hdf5_interface use math, only: calc_pn, calc_rn use particle_header, only: Particle - use string, only: to_str, to_lower + use string, only: to_str, to_lower, to_f_string use tally_filter_header use xml_interface implicit none private + public :: openmc_sphharm_filter_get_order + public :: openmc_sphharm_filter_get_cosine + public :: openmc_sphharm_filter_set_order + public :: openmc_sphharm_filter_set_cosine integer, parameter :: COSINE_SCATTER = 1 integer, parameter :: COSINE_PARTICLE = 2 @@ -132,4 +136,106 @@ contains ! C API FUNCTIONS !=============================================================================== + function openmc_sphharm_filter_get_order(index, order) result(err) bind(C) + ! Get the order of an expansion filter + integer(C_INT32_T), value :: index + integer(C_INT), intent(out) :: order + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (SphericalHarmonicsFilter) + order = f % order + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get order on a non-expansion filter.") + end select + end if + end function openmc_sphharm_filter_get_order + + + function openmc_sphharm_filter_get_cosine(index, cosine) result(err) bind(C) + ! Get the order of an expansion filter + integer(C_INT32_T), value :: index + character(kind=C_CHAR), intent(out) :: cosine(*) + integer(C_INT) :: err + + integer :: i + character(10) :: cosine_ + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (SphericalHarmonicsFilter) + select case (f % cosine) + case (COSINE_SCATTER) + cosine_ = 'scatter' + case (COSINE_PARTICLE) + cosine_ = 'particle' + end select + + ! Convert to C string + do i = 1, len_trim(cosine_) + cosine(i) = cosine_(i:i) + end do + cosine(len_trim(cosine_) + 1) = C_NULL_CHAR + + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get order on a non-expansion filter.") + end select + end if + end function openmc_sphharm_filter_get_cosine + + + function openmc_sphharm_filter_set_order(index, order) result(err) bind(C) + ! Set the order of an expansion filter + integer(C_INT32_T), value :: index + integer(C_INT), value :: order + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (SphericalHarmonicsFilter) + f % order = order + f % n_bins = (order + 1)**2 + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set order on a non-expansion filter.") + end select + end if + end function openmc_sphharm_filter_set_order + + + function openmc_sphharm_filter_set_cosine(index, cosine) result(err) bind(C) + ! Set the cosine parameter + integer(C_INT32_T), value :: index + character(kind=C_CHAR), intent(in) :: cosine(*) + integer(C_INT) :: err + + character(:), allocatable :: cosine_ + + ! Convert C string to Fortran string + cosine_ = to_f_string(cosine) + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (SphericalHarmonicsFilter) + select case (cosine_) + case ('scatter') + f % cosine = COSINE_SCATTER + case ('particle') + f % cosine = COSINE_PARTICLE + end select + + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get order on a non-expansion filter.") + end select + end if + end function openmc_sphharm_filter_set_cosine + end module tally_filter_sph_harm diff --git a/src/tallies/tally_filter_sptl_legendre.F90 b/src/tallies/tally_filter_sptl_legendre.F90 index 8de87f866..29dd848ea 100644 --- a/src/tallies/tally_filter_sptl_legendre.F90 +++ b/src/tallies/tally_filter_sptl_legendre.F90 @@ -15,6 +15,10 @@ module tally_filter_sptl_legendre implicit none private + public :: openmc_spatial_legendre_filter_get_order + public :: openmc_spatial_legendre_filter_get_params + public :: openmc_spatial_legendre_filter_set_order + public :: openmc_spatial_legendre_filter_set_params integer, parameter :: AXIS_X = 1 integer, parameter :: AXIS_Y = 2 @@ -25,10 +29,10 @@ module tally_filter_sptl_legendre !=============================================================================== type, public, extends(TallyFilter) :: SpatialLegendreFilter - integer :: order - integer :: axis - real(8) :: min - real(8) :: max + integer(C_INT) :: order + integer(C_INT) :: axis + real(C_DOUBLE) :: min + real(C_DOUBLE) :: max contains procedure :: from_xml procedure :: get_all_bins @@ -121,5 +125,90 @@ contains ! C API FUNCTIONS !=============================================================================== + function openmc_spatial_legendre_filter_get_order(index, order) result(err) bind(C) + ! Get the order of an expansion filter + integer(C_INT32_T), value :: index + integer(C_INT), intent(out) :: order + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (SpatialLegendreFilter) + order = f % order + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get order on a non-expansion filter.") + end select + end if + end function openmc_spatial_legendre_filter_get_order + + + function openmc_spatial_legendre_filter_set_order(index, order) result(err) bind(C) + ! Set the order of an expansion filter + integer(C_INT32_T), value :: index + integer(C_INT), value :: order + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (SpatialLegendreFilter) + f % order = order + f % n_bins = order + 1 + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set order on a non-expansion filter.") + end select + end if + end function openmc_spatial_legendre_filter_set_order + + + function openmc_spatial_legendre_filter_get_params(index, axis, min, max) & + result(err) bind(C) + ! Get the parameters for a spatial Legendre filter + integer(C_INT32_T), value :: index + integer(C_INT), intent(out) :: axis + real(C_DOUBLE), intent(out) :: min + real(C_DOUBLE), intent(out) :: max + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type(f => filters(index) % obj) + type is (SpatialLegendreFilter) + axis = f % axis + min = f % min + max = f % max + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set order on a non-expansion filter.") + end select + end if + end function openmc_spatial_legendre_filter_get_params + + + function openmc_spatial_legendre_filter_set_params(index, axis, min, max) & + result(err) bind(C) + ! Set the parameters for a spatial Legendre filter + integer(C_INT32_T), value :: index + integer(C_INT), intent(in), optional :: axis + real(C_DOUBLE), intent(in), optional :: min + real(C_DOUBLE), intent(in), optional :: max + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type(f => filters(index) % obj) + type is (SpatialLegendreFilter) + if (present(axis)) f % axis = axis + if (present(min)) f % min = min + if (present(max)) f % max = max + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set order on a non-expansion filter.") + end select + end if + end function openmc_spatial_legendre_filter_set_params end module tally_filter_sptl_legendre diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 index 91619827a..ce732af0e 100644 --- a/src/tallies/tally_filter_zernike.F90 +++ b/src/tallies/tally_filter_zernike.F90 @@ -117,5 +117,88 @@ contains ! C API FUNCTIONS !=============================================================================== + function openmc_zernike_filter_get_order(index, order) result(err) bind(C) + ! Get the order of an expansion filter + integer(C_INT32_T), value :: index + integer(C_INT), intent(out) :: order + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (ZernikeFilter) + order = f % order + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get order on a non-expansion filter.") + end select + end if + end function openmc_zernike_filter_get_order + + + function openmc_zernike_filter_get_params(index, x, y, r) result(err) bind(C) + ! Get the Zernike filter parameters + integer(C_INT32_T), value :: index + real(C_DOUBLE), intent(out) :: x + real(C_DOUBLE), intent(out) :: y + real(C_DOUBLE), intent(out) :: r + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (ZernikeFilter) + x = f % x + y = f % y + r = f % r + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get order on a non-expansion filter.") + end select + end if + end function openmc_zernike_filter_get_params + + + function openmc_zernike_filter_set_order(index, order) result(err) bind(C) + ! Set the order of an expansion filter + integer(C_INT32_T), value :: index + integer(C_INT), value :: order + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (ZernikeFilter) + f % order = order + f % n_bins = ((order + 1)*(order + 2))/2 + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set order on a non-expansion filter.") + end select + end if + end function openmc_zernike_filter_set_order + + + function openmc_zernike_filter_set_params(index, x, y, r) result(err) bind(C) + ! Set the Zernike filter parameters + integer(C_INT32_T), value :: index + real(C_DOUBLE), intent(in), optional :: x + real(C_DOUBLE), intent(in), optional :: y + real(C_DOUBLE), intent(in), optional :: r + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (ZernikeFilter) + if (present(x)) f % x = x + if (present(y)) f % y = y + if (present(r)) f % r = r + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get order on a non-expansion filter.") + end select + end if + end function openmc_zernike_filter_set_params end module tally_filter_zernike From 964a5da3e2520b55d2c26926bc4682cbafe9b495 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 30 Mar 2018 17:45:45 -0500 Subject: [PATCH 151/361] Remove unused variable in tally_filter_zernike.F90 --- src/tallies/tally_filter_zernike.F90 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 index ce732af0e..bf172a6e4 100644 --- a/src/tallies/tally_filter_zernike.F90 +++ b/src/tallies/tally_filter_zernike.F90 @@ -62,7 +62,6 @@ contains type(TallyFilterMatch), intent(inout) :: match integer :: i - integer :: n real(8) :: x, y, r, theta real(8) :: zn(this % n_bins) From 8838f14720f23c198bc112a2a0fc957917b3379a Mon Sep 17 00:00:00 2001 From: amandalund Date: Fri, 30 Mar 2018 20:00:28 -0500 Subject: [PATCH 152/361] Added some more TRISO unit tests --- openmc/model/triso.py | 12 ++- tests/unit_tests/test_model_triso.py | 144 ++++++++++++++++++++++----- 2 files changed, 127 insertions(+), 29 deletions(-) diff --git a/openmc/model/triso.py b/openmc/model/triso.py index 77488b891..7040d02ca 100644 --- a/openmc/model/triso.py +++ b/openmc/model/triso.py @@ -314,7 +314,9 @@ class _CubicDomain(_Domain): p += r*v q -= r*v - # Apply boundary conditions + # Enforce the rigid boundary by moving each particle back along the + # surface normal until it is completely within the container if it + # overlaps the surface x_max = self.limits[0] p[:] = np.clip(p, -x_max, x_max) q[:] = np.clip(q, -x_max, x_max) @@ -420,7 +422,9 @@ class _CylindricalDomain(_Domain): p += r*v q -= r*v - # Apply boundary conditions + # Enforce the rigid boundary by moving each particle back along the + # surface normal until it is completely within the container if it + # overlaps the surface r_max = self.limits[0] z_max = self.limits[1] @@ -517,7 +521,9 @@ class _SphericalDomain(_Domain): p += r*v q -= r*v - # Apply boundary conditions + # Enforce the rigid boundary by moving each particle back along the + # surface normal until it is completely within the container if it + # overlaps the surface r_max = self.limits[0] d = sqrt(p[0]**2 + p[1]**2 + p[2]**2) diff --git a/tests/unit_tests/test_model_triso.py b/tests/unit_tests/test_model_triso.py index c6f717d0b..bfd7319d2 100644 --- a/tests/unit_tests/test_model_triso.py +++ b/tests/unit_tests/test_model_triso.py @@ -11,49 +11,75 @@ import scipy.spatial _PACKING_FRACTION = 0.35 -_RADIUS = 1. -_PARAMS = [{'shape' : 'cube', 'length' : 20., 'radius' : 0.}, - {'shape' : 'cylinder', 'length' : 10., 'radius' : 10.}, - {'shape' : 'sphere', 'length' : 0., 'radius' : 10.}] +_RADIUS = 4.25e-2 -@pytest.fixture(scope='module', params=_PARAMS) +@pytest.fixture(scope='module', + params=[{'shape': 'cube', + 'length': 0.75, + 'radius': 0., + 'volume': 0.75**3}, + {'shape': 'cylinder', + 'length': 0.5, + 'radius': 0.5, + 'volume': 0.5*pi*0.5**2}, + {'shape': 'sphere', + 'length': 0., + 'radius': 0.5, + 'volume': 4/3*pi*0.5**3}]) def domain(request): return request.param @pytest.fixture(scope='module') -def trisos(domain): - return openmc.model.pack_trisos( +def triso_universe(): + sphere = openmc.Sphere(R=_RADIUS) + cell = openmc.Cell(region=-sphere) + univ = openmc.Universe(cells=[cell]) + return univ + + +@pytest.fixture(scope='module') +def trisos(domain, triso_universe): + trisos = openmc.model.pack_trisos( radius=_RADIUS, - fill=openmc.Universe(), + fill=triso_universe, domain_shape=domain['shape'], domain_length=domain['length'], domain_radius=domain['radius'], - domain_center=[0., 0., 0.], + domain_center=(0., 0., 0.), initial_packing_fraction=0.2, packing_fraction=_PACKING_FRACTION ) + return trisos def test_overlap(trisos): """Check that no TRISO particles overlap.""" - tree = scipy.spatial.cKDTree([t.center for t in trisos]) - r = min(tree.query([t.center for t in trisos], k=2)[0][:,1])/2 - assert r > _RADIUS or r == pytest.approx(_RADIUS) + centers = [t.center for t in trisos] + + # Create KD tree for quick nearest neighbor search + tree = scipy.spatial.cKDTree(centers) + + # Find distance to nearest neighbor for all particles + d = tree.query(centers, k=2)[0] + + # Get the smallest distance between any two particles + d_min = min(d[:,1]) + assert d_min > 2*_RADIUS or d_min == pytest.approx(2*_RADIUS) def test_contained(trisos, domain): """Make sure all particles are entirely contained within the domain.""" if domain['shape'] == 'cube': - x = 2*(max(np.hstack([abs(t.center) for t in trisos])) + _RADIUS) - assert x < domain['length'] or x == pytest.approx(domain['length']) + x = max(np.hstack([abs(t.center) for t in trisos])) + _RADIUS + assert x < 0.5*domain['length'] or x == pytest.approx(0.5*domain['length']) elif domain['shape'] == 'cylinder': r = max([norm(t.center[0:2]) for t in trisos]) + _RADIUS - z = 2*(max([abs(t.center[2]) for t in trisos]) + _RADIUS) + z = max([abs(t.center[2]) for t in trisos]) + _RADIUS assert r < domain['radius'] or r == pytest.approx(domain['radius']) - assert z < domain['length'] or z == pytest.approx(domain['length']) + assert z < 0.5*domain['length'] or z == pytest.approx(0.5*domain['length']) elif domain['shape'] == 'sphere': r = max([norm(t.center) for t in trisos]) + _RADIUS @@ -62,14 +88,80 @@ def test_contained(trisos, domain): def test_packing_fraction(trisos, domain): """Check that the actual PF is close to the requested PF.""" - if domain['shape'] == 'cube': - volume = domain['length']**3 - - elif domain['shape'] == 'cylinder': - volume = domain['length']*pi*domain['radius']**2 - - elif domain['shape'] == 'sphere': - volume = 4/3*pi*domain['radius']**3 - - pf = len(trisos)*4/3*pi*_RADIUS**3/volume + pf = len(trisos)*4/3*pi*_RADIUS**3/domain['volume'] assert pf == pytest.approx(_PACKING_FRACTION, rel=1e-2) + + +def test_n_particles(triso_universe): + """Check that the function returns the correct number of particles""" + trisos = openmc.model.pack_trisos( + radius=_RADIUS, fill=triso_universe, domain_shape='cube', + domain_length=1.0, n_particles=800 + ) + assert len(trisos) == 800 + + +def test_triso_lattice(triso_universe): + trisos = openmc.model.pack_trisos( + radius=_RADIUS, fill=triso_universe, domain_shape='cube', + domain_length=1.0, domain_center=(0., 0., 0.), packing_fraction=0.2 + ) + + lower_left = np.array((-.5, -.5, -.5)) + upper_right = np.array((.5, .5, .5)) + shape = (3, 3, 3) + pitch = (upper_right - lower_left)/shape + background = openmc.Material() + + lattice = openmc.model.create_triso_lattice( + trisos, lower_left, pitch, shape, background + ) + + +def test_domain_input(triso_universe): + # Invalid domain shape + with pytest.raises(ValueError): + trisos = openmc.model.pack_trisos( + radius=1, fill=triso_universe, n_particles=100, + domain_shape='circle' + ) + # Don't specify domain length on a cube + with pytest.raises(ValueError): + trisos = openmc.model.pack_trisos( + radius=1, fill=triso_universe, n_particles=100, + domain_shape='cube' + ) + # Don't specify domain radius on a sphere + with pytest.raises(ValueError): + trisos = openmc.model.pack_trisos( + radius=1, fill=triso_universe, n_particles=100, + domain_shape='sphere' + ) + + +def test_packing_fraction_input(triso_universe): + # Provide neither packing fraction nor number of particles + with pytest.raises(ValueError): + trisos = openmc.model.pack_trisos( + radius=1, fill=triso_universe, domain_shape='cube', + domain_length=10 + ) + # Provide both packing fraction and number of particles + with pytest.raises(ValueError): + trisos = openmc.model.pack_trisos( + radius=1, fill=triso_universe, domain_shape='cube', + domain_length=10, n_particles=100, packing_fraction=0.2 + ) + # Specify a packing fraction that is too high for CRP + with pytest.raises(ValueError): + trisos = openmc.model.pack_trisos( + radius=1, fill=triso_universe, domain_shape='cube', + domain_length=10, packing_fraction=1 + ) + # Specify a packing fraction that is too high for RSP + with pytest.raises(ValueError): + trisos = openmc.model.pack_trisos( + radius=1, fill=triso_universe, domain_shape='cube', + domain_length=10, packing_fraction=0.5, + initial_packing_fraction=0.4 + ) From 0d6cb83f2692a89b6262c93885a43814086fa1fb Mon Sep 17 00:00:00 2001 From: guillaume Date: Mon, 2 Apr 2018 13:51:46 -0400 Subject: [PATCH 153/361] in doc : fixed naming conventions, mistake on depletion requirements --- docs/source/usersguide/install.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 6b24c64dd..34c8395e0 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -417,8 +417,8 @@ 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. To run simulations in parallel using MPI, it is recommended to -build mpi4py, hdf5, h5py from source, in that order, using the same compilers -as for openmc. +build mpi4py, HDF5, h5py from source, in that order, using the same compilers +as for openMC. .. admonition:: Required :class: error @@ -457,7 +457,7 @@ as for openmc. `mpi4py `_ mpi4py provides Python bindings to MPI for running distributed-memory parallel runs. This package is needed if you plan on running depletion - simulations. + simulations in parallel using MPI. `Cython `_ Cython is used for resonance reconstruction for ENDF data converted to From 8c14c8230fc6c2f000c8c2ddcf529a3223b30812 Mon Sep 17 00:00:00 2001 From: guillaume Date: Mon, 2 Apr 2018 14:02:55 -0400 Subject: [PATCH 154/361] in doc : naming convention --- docs/source/usersguide/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 34c8395e0..b0618818e 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -418,7 +418,7 @@ relies on a number of third-party packages. All prerequisites can be installed using Conda_ (recommended), pip_, or through the package manager in most Linux distributions. To run simulations in parallel using MPI, it is recommended to build mpi4py, HDF5, h5py from source, in that order, using the same compilers -as for openMC. +as for OpenMC. .. admonition:: Required :class: error From cade109745e586e61118d10b60d0754d326ff353 Mon Sep 17 00:00:00 2001 From: guillaume Date: Mon, 2 Apr 2018 14:23:36 -0400 Subject: [PATCH 155/361] took into account PR comments, got rid of redundant exports and dictionaries --- .../python/pincell_depletion/run_depletion.py | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py index 9d81812fe..8c12211bc 100644 --- a/examples/python/pincell_depletion/run_depletion.py +++ b/examples/python/pincell_depletion/run_depletion.py @@ -1,4 +1,5 @@ import openmc +import openmc.deplete import numpy as np ############################################################################### @@ -11,9 +12,9 @@ inactive = 10 particles = 1000 # Depletion simulation parameters -time_step = 1.*24*60*60 # s -final_time = 15.*24*60*60 # s -time_steps = np.full(np.int(final_time / time_step), time_step) +time_step = 1*24*60*60 # s +final_time = 15*24*60*60 # s +time_steps = np.full(final_time // time_step, time_step) chain_file = './chain_simple.xml' power = 174 # W/cm, for 2D simulations only (use W for 3D) @@ -32,7 +33,6 @@ uo2.depletable = True helium = openmc.Material(material_id=2, name='Helium for gap') helium.set_density('g/cm3', 0.001598) helium.add_element('He', 2.4044e-4) -helium.depletable = False zircaloy = openmc.Material(material_id=3, name='Zircaloy 4') zircaloy.set_density('g/cm3', 6.55) @@ -40,7 +40,6 @@ zircaloy.add_element('Sn', 0.014 , 'wo') zircaloy.add_element('Fe', 0.00165, 'wo') zircaloy.add_element('Cr', 0.001 , 'wo') zircaloy.add_element('Zr', 0.98335, 'wo') -zircaloy.depletable = False borated_water = openmc.Material(material_id=4, name='Borated water') borated_water.set_density('g/cm3', 0.740582) @@ -48,7 +47,6 @@ borated_water.add_element('B', 4.0e-5) borated_water.add_element('H', 5.0e-2) borated_water.add_element('O', 2.4e-2) borated_water.add_s_alpha_beta('c_H_in_H2O') -borated_water.depletable = False ############################################################################### # Exporting to OpenMC geometry.xml file @@ -92,9 +90,8 @@ root = openmc.Universe(universe_id=0, name='root universe') # Register Cells with Universe root.add_cells([fuel, gap, clad, water]) -# Instantiate a Geometry, register the root Universe, and export to XML +# Instantiate a Geometry, register the root Universe geometry = openmc.Geometry(root) -geometry.export_to_xml() ############################################################################### # Exporting to OpenMC materials.xml file @@ -107,10 +104,6 @@ area[fuel] = np.pi * fuel_or.coefficients['R'] ** 2 # Set materials volume for depletion. Set to an area for 2D simulations uo2.volume = area[fuel] -# Instantiate a Materials collection and export to XML -materials_file = openmc.Materials([uo2, helium, zircaloy, borated_water]) -materials_file.export_to_xml() - ############################################################################### # Exporting to OpenMC settings.xml file ############################################################################### @@ -131,14 +124,12 @@ entropy_mesh.lower_left = [-0.39218, -0.39218, -1.e50] entropy_mesh.upper_right = [0.39218, 0.39218, 1.e50] entropy_mesh.dimension = [10, 10, 1] settings_file.entropy_mesh = entropy_mesh -settings_file.export_to_xml() ############################################################################### # Initialize and run depletion calculation ############################################################################### op = openmc.deplete.Operator(geometry, settings_file, chain_file) -op.round_number = True # Perform simulation using the predictor algorithm openmc.deplete.integrator.predictor(op, time_steps, power) @@ -150,12 +141,8 @@ openmc.deplete.integrator.predictor(op, time_steps, power) # Open results file results = openmc.deplete.ResultsList("depletion_results.h5") -# Dictionnary of materials and burned nuclides -mat_id_to_ind = results[0].mat_to_ind -nuc_to_ind = results[0].nuc_to_ind - # Obtain K_eff as a function of time time, keff = results.get_eigenvalue() # Obtain U235 concentration as a function of time -time, n_U235 = results.get_atoms(mat_id_to_ind['1'], nuc_to_ind['U235']) +time, n_U235 = results.get_atoms('1', 'U235') From b0a8e810104a281c2e6a17badab96147990b7019 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 3 Apr 2018 16:54:34 -0500 Subject: [PATCH 156/361] Respond to comments on #990. Add FE example notebook. --- docs/source/examples/expansion-filters.rst | 13 + docs/source/examples/index.rst | 14 +- examples/jupyter/expansion-filters.ipynb | 463 +++++++++++++++++++++ openmc/filter_expansion.py | 9 +- src/math.F90 | 2 +- src/tallies/tally_filter_sph_harm.F90 | 11 +- src/tallies/tally_filter_sptl_legendre.F90 | 32 +- src/tallies/tally_filter_zernike.F90 | 8 +- 8 files changed, 524 insertions(+), 28 deletions(-) create mode 100644 docs/source/examples/expansion-filters.rst create mode 100644 examples/jupyter/expansion-filters.ipynb diff --git a/docs/source/examples/expansion-filters.rst b/docs/source/examples/expansion-filters.rst new file mode 100644 index 000000000..f06d2bde6 --- /dev/null +++ b/docs/source/examples/expansion-filters.rst @@ -0,0 +1,13 @@ +.. _notebook_expansion: + +===================== +Functional Expansions +===================== + +.. only:: html + + .. notebook:: ../../../examples/jupyter/expansion-filters.ipynb + +.. only:: latex + + IPython notebooks must be viewed in the online HTML documentation. diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst index 5c1efb57c..89a1f1fe0 100644 --- a/docs/source/examples/index.rst +++ b/docs/source/examples/index.rst @@ -1,13 +1,12 @@ .. _examples: -================= -Example Notebooks -================= +======== +Examples +======== -The following series of Jupyter_ Notebooks provide examples for usage of OpenMC -features via the :ref:`pythonapi`. - -.. _Jupyter: https://jupyter.org/ +The following series of `Jupyter `_ Notebooks provide +examples for how to use various features of OpenMC by leveraging the +:ref:`pythonapi`. ----------- Basic Usage @@ -20,6 +19,7 @@ Basic Usage post-processing pandas-dataframes tally-arithmetic + expansion-filters search triso candu diff --git a/examples/jupyter/expansion-filters.ipynb b/examples/jupyter/expansion-filters.ipynb new file mode 100644 index 000000000..7870faffe --- /dev/null +++ b/examples/jupyter/expansion-filters.ipynb @@ -0,0 +1,463 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "OpenMC's general tally system accommodates a wide range of tally *filters*. While most filters are meant to identify regions of phase space that contribute to a tally, there are a special set of functional expansion filters that will multiply the tally by a set of orthogonal functions, e.g. Legendre polynomials, so that continuous functions of space or angle can be reconstructed from the tallied moments.\n", + "\n", + "In this example, we will determine the spatial dependence of the flux along the $z$ axis by making a Legendre polynomial expansion. Let us represent the flux along the z axis, $\\phi(z)$, by the function\n", + "\n", + "$$ \\phi(z') = \\sum\\limits_{n=0}^N a_n P_n(z') $$\n", + "\n", + "where $z'$ is the position normalized to the range [-1, 1]. Since $P_n(z')$ are known functions, our only task is to determine the expansion coefficients, $a_n$. By the orthogonality properties of the Legendre polynomials, one can deduce that the coefficients, $a_n$, are given by\n", + "\n", + "$$ a_n = \\frac{2n + 1}{2} \\int_{-1}^1 dz' P_n(z') \\phi(z').$$\n", + "\n", + "Thus, the problem reduces to finding the integral of the flux times each Legendre polynomial -- a problem which can be solved by using a Monte Carlo tally. By using a Legendre polynomial filter, we obtain stochastic estimates of these integrals for each polynomial order." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import openmc\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To begin, let us first create a simple model. The model will be a slab of fuel material with reflective boundaries conditions in the x- and y-directions and vacuum boundaries in the z-direction. However, to make the distribution slightly more interesting, we'll put some B4C in the middle of the slab." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Define fuel and B4C materials\n", + "fuel = openmc.Material()\n", + "fuel.add_element('U', 1.0, enrichment=4.5)\n", + "fuel.add_nuclide('O16', 2.0)\n", + "fuel.set_density('g/cm3', 10.0)\n", + "\n", + "b4c = openmc.Material()\n", + "b4c.add_element('B', 4.0)\n", + "b4c.add_element('C', 1.0)\n", + "b4c.set_density('g/cm3', 2.5)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Define surfaces used to construct regions\n", + "zmin, zmax = -10., 10.\n", + "box = openmc.model.get_rectangular_prism(10., 10., boundary_type='reflective')\n", + "bottom = openmc.ZPlane(z0=zmin, boundary_type='vacuum')\n", + "boron_lower = openmc.ZPlane(z0=-0.5)\n", + "boron_upper = openmc.ZPlane(z0=0.5)\n", + "top = openmc.ZPlane(z0=zmax, boundary_type='vacuum')\n", + "\n", + "# Create three cells and add them to geometry\n", + "fuel1 = openmc.Cell(fill=fuel, region=box & +bottom & -boron_lower)\n", + "absorber = openmc.Cell(fill=b4c, region=box & +boron_lower & -boron_upper)\n", + "fuel2 = openmc.Cell(fill=fuel, region=box & +boron_upper & -top)\n", + "geom = openmc.Geometry([fuel1, absorber, fuel2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the starting source, we'll use a uniform distribution over the entire box geometry." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "settings = openmc.Settings()\n", + "spatial_dist = openmc.stats.Box(*geom.bounding_box)\n", + "settings.source = openmc.Source(space=spatial_dist)\n", + "settings.batches = 210\n", + "settings.inactive = 10\n", + "settings.particles = 1000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Defining the tally is relatively straightforward. One simply needs to list 'flux' as a score and then add an expansion filter. For this case, we will want to use the `SpatialLegendreFilter` class which multiplies tally scores by Legendre polynomials evaluated on normalized spatial positions along an axis." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a flux tally\n", + "flux_tally = openmc.Tally()\n", + "flux_tally.scores = ['flux']\n", + "\n", + "# Create a Legendre polynomial expansion filter and add to tally\n", + "order = 8\n", + "expand_filter = openmc.SpatialLegendreFilter(order, 'z', zmin, zmax)\n", + "flux_tally.filters.append(expand_filter)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The last thing we need to do is create a `Tallies` collection and export the entire model, which we'll do using the `Model` convenience class." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "tallies = openmc.Tallies([flux_tally])\n", + "model = openmc.model.Model(geometry=geom, settings=settings, tallies=tallies)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running a simulation is now as simple as calling the `run()` method of `Model`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.3016877031715249+/-0.0006126949350699303" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.run(output=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that the run is finished, we need to load the results from the statepoint file." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "with openmc.StatePoint('statepoint.210.h5') as sp:\n", + " df = sp.tallies[flux_tally.id].get_pandas_dataframe()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We've used the `get_pandas_dataframe()` method that returns tally data as a Pandas dataframe. Let's see what the raw data looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
spatiallegendrenuclidescoremeanstd. dev.
0P0totalflux36.4348280.076755
1P1totalflux0.0217970.043545
2P2totalflux-4.3808920.025739
3P3totalflux0.0014480.020740
4P4totalflux-0.2954390.014215
5P5totalflux0.0035140.012017
6P6totalflux0.1052130.010103
7P7totalflux0.0025950.009265
8P8totalflux-0.0961970.007513
\n", + "
" + ], + "text/plain": [ + " spatiallegendre nuclide score mean std. dev.\n", + "0 P0 total flux 3.64e+01 7.68e-02\n", + "1 P1 total flux 2.18e-02 4.35e-02\n", + "2 P2 total flux -4.38e+00 2.57e-02\n", + "3 P3 total flux 1.45e-03 2.07e-02\n", + "4 P4 total flux -2.95e-01 1.42e-02\n", + "5 P5 total flux 3.51e-03 1.20e-02\n", + "6 P6 total flux 1.05e-01 1.01e-02\n", + "7 P7 total flux 2.60e-03 9.27e-03\n", + "8 P8 total flux -9.62e-02 7.51e-03" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since the expansion coefficients are given as\n", + "\n", + "$$ a_n = \\frac{2n + 1}{2} \\int_{-1}^1 dz' P_n(z') \\phi(z')$$\n", + "\n", + "we just need to multiply the Legendre moments by $(2n + 1)/2$." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "n = np.arange(order + 1)\n", + "a_n = (2*n + 1)/2 * df['mean']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To plot the flux distribution, we can use the `numpy.polynomial.Legendre` class which represents a truncated Legendre polynomial series. Since we really want to plot $\\phi(z)$ and not $\\phi(z')$ we first need to perform a change of variables. Since\n", + "\n", + "$$ \\lvert \\phi(z) dz \\rvert = \\lvert \\phi(z') dz' \\rvert $$\n", + "\n", + "and, for this case, $z = 10z'$, it follows that\n", + "\n", + "$$ \\phi(z) = \\frac{\\phi(z')}{10} = \\sum_{n=0}^N \\frac{a_n}{10} P_n(z'). $$" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "phi = np.polynomial.Legendre(a_n/10, domain=(zmin, zmax))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's plot it and see how our flux looks!" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "z = np.linspace(zmin, zmax, 1000)\n", + "plt.plot(z, phi(z))\n", + "plt.xlabel('Z position [cm]')\n", + "plt.ylabel('Flux [n/src]')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you might expect, we get a rough cosine shape but with a flux depression in the middle due to the boron slab that we introduced. To get a more accurate distribution, we'd likely need to use a higher order expansion.\n", + "\n", + "One more thing we can do is confirm that integrating the distribution gives us the same value as the first moment (since $P_0(z') = 1$). This can easily be done by numerically integrating using the trapezoidal rule:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "36.434786672754925" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.trapz(phi(z), z)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition to being able to tally Legendre moments, there are also functional expansion filters available for spherical harmonics (`SphericalHarmonicsFilter`) and Zernike polynomials over a unit disk (`ZernikeFilter`). A separate `LegendreFilter` class can also be used for determining Legendre scattering moments (i.e., an expansion of the scattering cosine, $\\mu$)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index 20bb08e77..f6e808dd2 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -226,17 +226,22 @@ class SpatialLegendreFilter(ExpansionFilter): class SphericalHarmonicsFilter(ExpansionFilter): r"""Score spherical harmonic expansion moments up to specified order. + This filter allows you to obtain real spherical harmonic moments of either + the particle's direction or the cosine of the scattering angle. Specifying a + filter with order :math:`\ell` tallies moments for all orders from 0 to + :math:`\ell`. + Parameters ---------- order : int - Maximum spherical harmonics order + Maximum spherical harmonics order, :math:`\ell` filter_id : int or None Unique identifier for the filter Attributes ---------- order : int - Maximum spherical harmonics order + Maximum spherical harmonics order, :math:`\ell` id : int Unique identifier for the filter cosine : {'scatter', 'particle'} diff --git a/src/math.F90 b/src/math.F90 index 1d7d0bfaf..ce3c9b807 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -181,7 +181,7 @@ contains end function calc_pn !=============================================================================== -! CALC_RN calculates the n-th order spherical harmonics for a given angle +! CALC_RN calculates the n-th order real spherical harmonics for a given angle ! (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) !=============================================================================== diff --git a/src/tallies/tally_filter_sph_harm.F90 b/src/tallies/tally_filter_sph_harm.F90 index 2d3c0bf6d..5cd3f59c3 100644 --- a/src/tallies/tally_filter_sph_harm.F90 +++ b/src/tallies/tally_filter_sph_harm.F90 @@ -24,7 +24,8 @@ module tally_filter_sph_harm integer, parameter :: COSINE_PARTICLE = 2 !=============================================================================== -! LEGENDREFILTER gives Legendre moments of the change in scattering angle +! SPHERICALHARMONICSFILTER gives spherical harmonics expansion moments of a +! tally score !=============================================================================== type, public, extends(TallyFilter) :: SphericalHarmonicsFilter @@ -149,7 +150,7 @@ contains order = f % order class default err = E_INVALID_TYPE - call set_errmsg("Tried to get order on a non-expansion filter.") + call set_errmsg("Not a spherical harmonics filter.") end select end if end function openmc_sphharm_filter_get_order @@ -183,7 +184,7 @@ contains class default err = E_INVALID_TYPE - call set_errmsg("Tried to get order on a non-expansion filter.") + call set_errmsg("Not a spherical harmonics filter.") end select end if end function openmc_sphharm_filter_get_cosine @@ -203,7 +204,7 @@ contains f % n_bins = (order + 1)**2 class default err = E_INVALID_TYPE - call set_errmsg("Tried to set order on a non-expansion filter.") + call set_errmsg("Not a spherical harmonics filter.") end select end if end function openmc_sphharm_filter_set_order @@ -233,7 +234,7 @@ contains class default err = E_INVALID_TYPE - call set_errmsg("Tried to get order on a non-expansion filter.") + call set_errmsg("Not a spherical harmonics filter.") end select end if end function openmc_sphharm_filter_set_cosine diff --git a/src/tallies/tally_filter_sptl_legendre.F90 b/src/tallies/tally_filter_sptl_legendre.F90 index 29dd848ea..d6594d05a 100644 --- a/src/tallies/tally_filter_sptl_legendre.F90 +++ b/src/tallies/tally_filter_sptl_legendre.F90 @@ -25,7 +25,8 @@ module tally_filter_sptl_legendre integer, parameter :: AXIS_Z = 3 !=============================================================================== -! SpatialLEGENDREFILTER gives Legendre moments +! SPATIALLEGENDREFILTER gives Legendre moments of the particle's normalized +! position along an axis !=============================================================================== type, public, extends(TallyFilter) :: SpatialLegendreFilter @@ -97,18 +98,20 @@ contains subroutine to_statepoint(this, filter_group) class(SpatialLegendreFilter), intent(in) :: this integer(HID_T), intent(in) :: filter_group + character(kind=C_CHAR) :: axis call write_dataset(filter_group, "type", "spatiallegendre") call write_dataset(filter_group, "n_bins", this % n_bins) call write_dataset(filter_group, "order", this % order) select case (this % axis) case (AXIS_X) - call write_dataset(filter_group, 'axis', 'x') + axis = 'x' case (AXIS_Y) - call write_dataset(filter_group, 'axis', 'y') + axis = 'y' case (AXIS_Z) - call write_dataset(filter_group, 'axis', 'z') + axis = 'z' end select + call write_dataset(filter_group, 'axis', axis) call write_dataset(filter_group, 'min', this % min) call write_dataset(filter_group, 'max', this % max) end subroutine to_statepoint @@ -118,7 +121,18 @@ contains integer, intent(in) :: bin character(MAX_LINE_LEN) :: label - label = "Legendre expansion, P" // trim(to_str(bin - 1)) + character(1) :: axis + + select case (this % axis) + case (AXIS_X) + axis = 'x' + case (AXIS_Y) + axis = 'y' + case (AXIS_Z) + axis = 'z' + end select + label = "Legendre expansion, " // axis // " axis, P" // & + trim(to_str(bin - 1)) end function text_label !=============================================================================== @@ -138,7 +152,7 @@ contains order = f % order class default err = E_INVALID_TYPE - call set_errmsg("Tried to get order on a non-expansion filter.") + call set_errmsg("Not a spatial Legendre filter.") end select end if end function openmc_spatial_legendre_filter_get_order @@ -158,7 +172,7 @@ contains f % n_bins = order + 1 class default err = E_INVALID_TYPE - call set_errmsg("Tried to set order on a non-expansion filter.") + call set_errmsg("Not a spatial Legendre filter.") end select end if end function openmc_spatial_legendre_filter_set_order @@ -182,7 +196,7 @@ contains max = f % max class default err = E_INVALID_TYPE - call set_errmsg("Tried to set order on a non-expansion filter.") + call set_errmsg("Not a spatial Legendre filter.") end select end if end function openmc_spatial_legendre_filter_get_params @@ -206,7 +220,7 @@ contains if (present(max)) f % max = max class default err = E_INVALID_TYPE - call set_errmsg("Tried to set order on a non-expansion filter.") + call set_errmsg("Not a spatial Legendre filter.") end select end if end function openmc_spatial_legendre_filter_set_params diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 index bf172a6e4..19b7cd980 100644 --- a/src/tallies/tally_filter_zernike.F90 +++ b/src/tallies/tally_filter_zernike.F90 @@ -129,7 +129,7 @@ contains order = f % order class default err = E_INVALID_TYPE - call set_errmsg("Tried to get order on a non-expansion filter.") + call set_errmsg("Not a Zernike filter.") end select end if end function openmc_zernike_filter_get_order @@ -152,7 +152,7 @@ contains r = f % r class default err = E_INVALID_TYPE - call set_errmsg("Tried to get order on a non-expansion filter.") + call set_errmsg("Not a Zernike filter.") end select end if end function openmc_zernike_filter_get_params @@ -172,7 +172,7 @@ contains f % n_bins = ((order + 1)*(order + 2))/2 class default err = E_INVALID_TYPE - call set_errmsg("Tried to set order on a non-expansion filter.") + call set_errmsg("Not a Zernike filter.") end select end if end function openmc_zernike_filter_set_order @@ -195,7 +195,7 @@ contains if (present(r)) f % r = r class default err = E_INVALID_TYPE - call set_errmsg("Tried to get order on a non-expansion filter.") + call set_errmsg("Not a Zernike filter.") end select end if end function openmc_zernike_filter_set_params From b28f6f270187c70c820810918eed2bb60a97ea01 Mon Sep 17 00:00:00 2001 From: liangjg Date: Tue, 3 Apr 2018 21:17:07 -0400 Subject: [PATCH 157/361] updated for tallying currents with meshsurface filter --- docs/source/usersguide/tallies.rst | 4 +- examples/jupyter/tally-arithmetic.ipynb | 96 ++++++++++--------------- src/input_xml.F90 | 3 + 3 files changed, 42 insertions(+), 61 deletions(-) diff --git a/docs/source/usersguide/tallies.rst b/docs/source/usersguide/tallies.rst index 3a742b59e..e0f8ab179 100644 --- a/docs/source/usersguide/tallies.rst +++ b/docs/source/usersguide/tallies.rst @@ -261,7 +261,7 @@ The following tables show all valid scores: +----------------------+---------------------------------------------------+ |Score | Description | +======================+===================================================+ - |current |Used in combination with a mesh filter: | + |current |Used in combination with a meshsurface filter: | | |Partial currents on the boundaries of each cell in | | |a mesh. It may not be used in conjunction with any | | |other score. Only energy and mesh filters may be | @@ -269,7 +269,7 @@ The following tables show all valid scores: | |Used in combination with a surface filter: | | |Net currents on any surface previously defined in | | |the geometry. It may be used along with any other | - | |filter, except mesh filters. | + | |filter, except meshsurface filters. | | |Surfaces can alternatively be defined with cell | | |from and cell filters thereby resulting in tallying| | |partial currents. | diff --git a/examples/jupyter/tally-arithmetic.ipynb b/examples/jupyter/tally-arithmetic.ipynb index 391dc809a..5e904759c 100644 --- a/examples/jupyter/tally-arithmetic.ipynb +++ b/examples/jupyter/tally-arithmetic.ipynb @@ -287,18 +287,7 @@ "metadata": { "collapsed": false }, - "outputs": [ - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Run openmc in plotting mode\n", "openmc.plot_geometry(output=False)" @@ -313,7 +302,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAACBjSFJN\nAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAADFBMVEX///9yEhLpgJFNv8Tq\nQYT7AAAAAWJLR0QAiAUdSAAAAAd0SU1FB+EMBQIrDwapSyIAAALKSURBVGje7dpLcqQwDAbgHHE2\nYeEj+D4cwQucBUfo+3CEXoSp8OhuhF70T4qpKXmdr21LogK2Pj7A8QmNP+HDhw8fPnz48Kf6VH9G\n+66vy+je8k19jnf8C5dXIPv86ms56lPdjvaYbyodx3ze+XLE76cXFiD4zPji99z0/AJ4n1lfvJ6f\nnl0A6x+578efMSg1wPr172/jPO5yFXM+Ef78gdblM+WPHyguP//t1/g6pA0wfln+ho/fwgYYn19C\n/xwDvwHGc9OvC+hs37DTrwuwfWanXxdQTC9Mvyygs3wjTL8uwPJpn/tNDbSGz7T0SBEWw4vLXzbQ\n6b6RoveIoO6TvPxlA63qs7z8ZQPF9F+SH22vbX8OQKf5Rtv+EgDNJ3X58wZaxWd1+fMGiuFvir8b\nvjp8J/tGy/6jAmRvhW8fwL3vVT+o3grfPoB7r/IpALI3tz8FoJN84/NV873hB8UnM3xzANtf8nb4\ndwmg3grfFEDJO8JPE0i9Ff4pAYL3pI8mkHor/HMCeO9JH00g9SafEsh7T/ppARBvp48UwJnelT5S\nACd7O31TAlnvKx9SQCd7B58KgPO+8iMFuPWe9E8F8BveWX7bAjzX9y4//Jve+fhsH6Ctv7n8PTzj\nvY/v9gEOHz58+PBX+6v/f/wPvnd54f3j6venE/yl769Xv7+j3x/o98/V32/o9+fl389Xnx+g5x/o\n+Qt6/oOeP6HnX+j5G3z+h54/ouefV5/foufP6Pk3ev4On/+j9w/o/Qd6/4Le/6D3T/D9V67Y/ZsV\nQBq+s+8f0ftP+P41axXguP9NWgDuu/Cdfv+N3r/D9/9TAID+A7T/Ae2/gPs/0P4TtP8F7r9J3AIO\n9P+g/Udw/9Oygbf7r9D+L7j/DO1/Q/vv4P4/tP8Q7n9E+y/h/k+0/xTuf4X7b+H+X7T/+BPuf3aM\n8OHDhw8fPnz4w/4vzcvgeY10sY0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMTItMDRUMjA6NDM6\nMTQtMDY6MDCrFYTfAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTEyLTA0VDIwOjQzOjE0LTA2OjAw\n2kg8YwAAAABJRU5ErkJggg==\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AgMAAAD1grKuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdC\nAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAxQTFRF\n////chIS6YCRTb/E6kGE+wAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAAEgAAABIAEbJaz4AAALKSURB\nVGje7dpLcqQwDAbgHHE2YeEj+D4cwQucBUfo+3CEXoSp8OhuhF70T4qpKXmdr21LogK2Pj7A8QmN\nP+HDhw8fPnz48Kf6VH9G+66vy+je8k19jnf8C5dXIPv86ms56lPdjvaYbyodx3ze+XLE76cXFiD4\nzPji99z0/AJ4n1lfvJ6fnl0A6x+578efMSg1wPr172/jPO5yFXM+Ef78gdblM+WPHyguP//t1/g6\npA0wfln+ho/fwgYYn19C/xwDvwHGc9OvC+hs37DTrwuwfWanXxdQTC9Mvyygs3wjTL8uwPJpn/tN\nDbSGz7T0SBEWw4vLXzbQ6b6RoveIoO6TvPxlA63qs7z8ZQPF9F+SH22vbX8OQKf5Rtv+EgDNJ3X5\n8wZaxWd1+fMGiuFvir8bvjp8J/tGy/6jAmRvhW8fwL3vVT+o3grfPoB7r/IpALI3tz8FoJN84/NV\n873hB8UnM3xzANtf8nb4dwmg3grfFEDJO8JPE0i9Ff4pAYL3pI8mkHor/HMCeO9JH00g9SafEsh7\nT/ppARBvp48UwJnelT5SACd7O31TAlnvKx9SQCd7B58KgPO+8iMFuPWe9E8F8BveWX7bAjzX9y4/\n/Jve+fhsH6Ctv7n8PTzjvY/v9gEOHz58+PBX+6v/f/wPvnd54f3j6venE/yl769Xv7+j3x/o98/V\n32/o9+fl389Xnx+g5x/o+Qt6/oOeP6HnX+j5G3z+h54/ouefV5/foufP6Pk3ev4On/+j9w/o/Qd6\n/4Le/6D3T/D9V67Y/ZsVQBq+s+8f0ftP+P41axXguP9NWgDuu/Cdfv+N3r/D9/9TAID+A7T/Ae2/\ngPs/0P4TtP8F7r9J3AIO9P+g/Udw/9Oygbf7r9D+L7j/DO1/Q/vv4P4/tP8Q7n9E+y/h/k+0/xTu\nf4X7b+H+X7T/+BPuf3aM8OHDhw8fPnz4w/4vzcvgeY10sY0AAAAldEVYdGRhdGU6Y3JlYXRlADIw\nMTgtMDQtMDNUMjE6MTE6MzgtMDQ6MDD1dVTHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE4LTA0LTAz\nVDIxOjExOjM4LTA0OjAwhCjsewAAAABJRU5ErkJggg==\n", "text/plain": [ "" ] @@ -392,21 +381,21 @@ "mesh.dimension = [1, 1, 1]\n", "mesh.lower_left = [-0.63, -0.63, -100.]\n", "mesh.width = [1.26, 1.26, 200.]\n", - "mesh_filter = openmc.MeshFilter(mesh)\n", + "meshsurface_filter = openmc.MeshSurfaceFilter(mesh)\n", "\n", "# Instantiate thermal, fast, and total leakage tallies\n", "leak = openmc.Tally(name='leakage')\n", - "leak.filters = [mesh_filter]\n", + "leak.filters = [meshsurface_filter]\n", "leak.scores = ['current']\n", "tallies_file.append(leak)\n", "\n", "thermal_leak = openmc.Tally(name='thermal leakage')\n", - "thermal_leak.filters = [mesh_filter, openmc.EnergyFilter([0., 0.625])]\n", + "thermal_leak.filters = [meshsurface_filter, openmc.EnergyFilter([0., 0.625])]\n", "thermal_leak.scores = ['current']\n", "tallies_file.append(thermal_leak)\n", "\n", "fast_leak = openmc.Tally(name='fast leakage')\n", - "fast_leak.filters = [mesh_filter, openmc.EnergyFilter([0.625, 20.0e6])]\n", + "fast_leak.filters = [meshsurface_filter, openmc.EnergyFilter([0.625, 20.0e6])]\n", "fast_leak.scores = ['current']\n", "tallies_file.append(fast_leak)" ] @@ -504,11 +493,11 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=6.\n", + "/home/liangjg/.local/lib/python3.5/site-packages/openmc-0.10.0-py3.5.egg/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=6.\n", " warn(msg, IDWarning)\n", - "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=3.\n", + "/home/liangjg/.local/lib/python3.5/site-packages/openmc-0.10.0-py3.5.egg/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=3.\n", " warn(msg, IDWarning)\n", - "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=2.\n", + "/home/liangjg/.local/lib/python3.5/site-packages/openmc-0.10.0-py3.5.egg/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.\n", " warn(msg, IDWarning)\n" ] } @@ -563,24 +552,25 @@ " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2017 Massachusetts Institute of Technology\n", + " Copyright | 2011-2018 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.9.0\n", - " Git SHA1 | 9b7cebf7bc34d60e0f1750c3d6cb103df11e8dc4\n", - " Date/Time | 2017-12-04 20:43:15\n", - " OpenMP Threads | 4\n", + " Version | 0.10.0\n", + " Git SHA1 | 47fbf8282ea94c138f75219bd10fdb31501d3fb7\n", + " Date/Time | 2018-04-03 21:12:27\n", + " MPI Processes | 1\n", + " OpenMP Threads | 20\n", "\n", " Reading settings XML file...\n", " Reading cross sections XML file...\n", " Reading materials XML file...\n", " Reading geometry XML file...\n", " Building neighboring cells lists for each surface...\n", - " Reading U235 from /home/romano/openmc/scripts/nndc_hdf5/U235.h5\n", - " Reading U238 from /home/romano/openmc/scripts/nndc_hdf5/U238.h5\n", - " Reading O16 from /home/romano/openmc/scripts/nndc_hdf5/O16.h5\n", - " Reading H1 from /home/romano/openmc/scripts/nndc_hdf5/H1.h5\n", - " Reading B10 from /home/romano/openmc/scripts/nndc_hdf5/B10.h5\n", - " Reading Zr90 from /home/romano/openmc/scripts/nndc_hdf5/Zr90.h5\n", + " Reading U235 from /home/liangjg/nucdata/nndc_hdf5/U235.h5\n", + " Reading U238 from /home/liangjg/nucdata/nndc_hdf5/U238.h5\n", + " Reading O16 from /home/liangjg/nucdata/nndc_hdf5/O16.h5\n", + " Reading H1 from /home/liangjg/nucdata/nndc_hdf5/H1.h5\n", + " Reading B10 from /home/liangjg/nucdata/nndc_hdf5/B10.h5\n", + " Reading Zr90 from /home/liangjg/nucdata/nndc_hdf5/Zr90.h5\n", " Maximum neutron transport energy: 2.00000E+07 eV for U235\n", " Reading tallies XML file...\n", " Writing summary.h5 file...\n", @@ -614,20 +604,20 @@ "\n", " =======================> TIMING STATISTICS <=======================\n", "\n", - " Total time for initialization = 5.6782E-01 seconds\n", - " Reading cross sections = 5.3276E-01 seconds\n", - " Total time in simulation = 6.4149E+00 seconds\n", - " Time in transport only = 6.2767E+00 seconds\n", - " Time in inactive batches = 6.8747E-01 seconds\n", - " Time in active batches = 5.7274E+00 seconds\n", - " Time synchronizing fission bank = 2.7492E-03 seconds\n", - " Sampling source sites = 1.9584E-03 seconds\n", - " SEND/RECV source sites = 7.4113E-04 seconds\n", - " Time accumulating tallies = 1.0576E-04 seconds\n", - " Total time for finalization = 2.2075E-03 seconds\n", - " Total time elapsed = 7.0056E+00 seconds\n", - " Calculation Rate (inactive) = 18182.5 neutrons/second\n", - " Calculation Rate (active) = 6547.45 neutrons/second\n", + " Total time for initialization = 4.9090E-01 seconds\n", + " Reading cross sections = 4.2387E-01 seconds\n", + " Total time in simulation = 1.4928E+00 seconds\n", + " Time in transport only = 1.3545E+00 seconds\n", + " Time in inactive batches = 1.3625E-01 seconds\n", + " Time in active batches = 1.3565E+00 seconds\n", + " Time synchronizing fission bank = 2.4053E-03 seconds\n", + " Sampling source sites = 1.6466E-03 seconds\n", + " SEND/RECV source sites = 5.6159E-04 seconds\n", + " Time accumulating tallies = 3.3647E-04 seconds\n", + " Total time for finalization = 1.6066E-02 seconds\n", + " Total time elapsed = 2.0336E+00 seconds\n", + " Calculation Rate (inactive) = 91743.2 neutrons/second\n", + " Calculation Rate (active) = 27644.5 neutrons/second\n", "\n", " ============================> RESULTS <============================\n", "\n", @@ -638,16 +628,6 @@ " Leakage Fraction = 0.01717 +/- 0.00107\n", "\n" ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -741,8 +721,7 @@ "\n", "# Get the leakage tally\n", "leak = sp.get_tally(name='leakage')\n", - "leak = leak.summation(filter_type=openmc.SurfaceFilter, remove_filter=True)\n", - "leak = leak.summation(filter_type=openmc.MeshFilter, remove_filter=True)\n", + "leak = leak.summation(filter_type=openmc.MeshSurfaceFilter, remove_filter=True)\n", "\n", "# Compute k-infinity using tally arithmetic\n", "keff = fiss_rate / (abs_rate + leak)\n", @@ -812,8 +791,7 @@ "# Compute resonance escape probability using tally arithmetic\n", "therm_abs_rate = sp.get_tally(name='therm. abs. rate')\n", "thermal_leak = sp.get_tally(name='thermal leakage')\n", - "thermal_leak = thermal_leak.summation(filter_type=openmc.SurfaceFilter, remove_filter=True)\n", - "thermal_leak = thermal_leak.summation(filter_type=openmc.MeshFilter, remove_filter=True)\n", + "thermal_leak = thermal_leak.summation(filter_type=openmc.MeshSurfaceFilter, remove_filter=True)\n", "res_esc = (therm_abs_rate + thermal_leak) / (abs_rate + thermal_leak)\n", "res_esc.get_pandas_dataframe()" ] diff --git a/src/input_xml.F90 b/src/input_xml.F90 index a29bb08e8..63e5cd7a4 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2853,6 +2853,9 @@ contains call fatal_error("Cannot tally other scores in the & &same tally as surface currents") end if + else + call fatal_error("Cannot tally currents without surface & + &type filters") end if case ('events') From 9ce837330a27440e217c0cf96a5eb4c0ee89595d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 4 Apr 2018 22:23:20 -0500 Subject: [PATCH 158/361] Fix bug in calc_zn and add better comments (huge thanks to @GiudGiud) --- src/math.F90 | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/math.F90 b/src/math.F90 index ce3c9b807..35855c614 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -580,10 +580,10 @@ contains !=============================================================================== subroutine calc_zn(n, rho, phi, zn) - ! This efficient method for calculation R(m,n) is taken from - ! Chong, C. W., Raveendran, P., & Mukundan, R. (2003). A comparative - ! analysis of algorithms for fast computation of Zernike moments. - ! Pattern Recognition, 36(3), 731-742. + ! This procedure uses the modified Kintner's method for calculating Zernike + ! polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, + ! R. (2003). A comparative analysis of algorithms for fast computation of + ! Zernike moments. Pattern Recognition, 36(3), 731-742. integer, intent(in) :: n ! Maximum order real(8), intent(in) :: rho ! Radial location in the unit disk @@ -608,7 +608,14 @@ contains ! n == radial degree ! m == azimuthal frequency - ! Deterine vector of sin(n*phi) and cos(n*phi) + ! ========================================================================== + ! Determine vector of sin(n*phi) and cos(n*phi). This takes advantage of the + ! following recurrence relations so that only a single sin/cos have to be + ! evaluated (http://mathworld.wolfram.com/Multiple-AngleFormulas.html) + ! + ! sin(nx) = 2 cos(x) sin((n-1)x) - sin((n-2)x) + ! cos(nx) = 2 cos(x) cos((n-1)x) - cos((n-2)x) + sin_phi = sin(phi) cos_phi = cos(phi) @@ -627,17 +634,20 @@ contains sin_phi_vec(i) = sin_phi_vec(i) * sin_phi end do - ! Calculate R_m_n(rho) - ! Fill the main diagonal first + ! ========================================================================== + ! Calculate R_pq(rho) + + ! Fill the main diagonal first (Eq. 3.9 in Chong) do p = 0, n zn_mat(p+1, p+1) = rho**p end do - ! Fill in the second diagonal + ! Fill in the second diagonal (Eq. 3.10 in Chong) do q = 0, n-2 zn_mat(q+2+1, q+1) = (q+2) * zn_mat(q+2+1, q+2+1) - (q+1) * zn_mat(q+1, q+1) end do - ! Fill in the rest of the values using the original results + + ! Fill in the rest of the values using the original results (Eq. 3.8 in Chong) do p = 4, n k2 = 2 * p * (p - 1) * (p - 2) do q = p-4, 0, -2 @@ -651,18 +661,18 @@ contains ! Roll into a single vector for easier computation later ! The vector is ordered (0,0), (1,-1), (1,1), (2,-2), (2,0), ! (2, 2), .... in (n,m) indices - ! Note that the cos and sin vectors are offest by one + ! Note that the cos and sin vectors are offset by one ! sin_phi_vec = [sin(x), sin(2x), sin(3x) ...] ! cos_phi_vec = [1.0, cos(x), cos(2x)... ] i = 1 do p = 0, n do q = -p, p, 2 if (q < 0) then - zn(i) = zn_mat(p+1, abs(q)+1) * sin_phi_vec(p) * SQRT_2N_2(p) + zn(i) = zn_mat(p+1, abs(q)+1) * sin_phi_vec(abs(q)) * SQRT_2N_2(p) else if (q == 0) then zn(i) = zn_mat(p+1, q+1) * SQRT_N_1(p) else - zn(i) = zn_mat(p+1, q+1) * cos_phi_vec(p+1) * SQRT_2N_2(p) + zn(i) = zn_mat(p+1, q+1) * cos_phi_vec(abs(q)+1) * SQRT_2N_2(p) end if i = i + 1 end do From 78b993eda5709af0e074ab85abb85ccf2f93454b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 4 Apr 2018 22:59:15 -0500 Subject: [PATCH 159/361] Change normalization of Zernike filter values --- openmc/filter_expansion.py | 27 +++++++++++++++++++++------ src/tallies/tally_filter_zernike.F90 | 2 +- tests/unit_tests/test_filters.py | 2 +- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index f6e808dd2..e46bf7be1 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -317,12 +317,27 @@ class ZernikeFilter(ExpansionFilter): r"""Score Zernike expansion moments in space up to specified order. This filter allows scores to be multiplied by Zernike polynomials of the - particle's position along a particular axis, normalized to a given unit - circle, up to a user-specified order. Specifying a filter with order N - tallies moments for all radial orders from 0 to N and each azimuthal order - for a given radial order. The ordering of the Zernike polynomial moments - follows the ANSI Z80.28 standard, where bin :math:`j` corresponds to the - radial index :math:`n` and the azimuthal index :math:`m` by + particle's position normalized to a given unit circle, up to a + user-specified order. The Zernike polynomials are defined as + + .. math:: + Z_n^m(\rho, \theta) = R_n^m(\rho) \cos (m\theta) + + and + + .. math:: + Z_n^{-m}(\rho, \theta) = R_n^{-m}(\rho) \sin (m\theta) + + where the radial polynomials are + + .. math:: + R_n^m(\rho) = \sum\limits_{k=0}^{(n-m)/2} \frac{(-1)^k (n-k)!}{k! ( + \frac{n+m}{2} - k)! (\frac{n-m}{2} - k)!} \rho^{n-2k}. + + Specifying a filter with order N tallies moments for all :math:`n` from 0 to + N and each value of :math:`m`. The ordering of the Zernike polynomial + moments follows the ANSI Z80.28 standard, where the one-dimensional index + :math:`j` corresponds to the :math:`n` and :math:`m` by .. math:: j = \frac{n(n + 2) + m}{2}. diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 index 19b7cd980..e96a53a7f 100644 --- a/src/tallies/tally_filter_zernike.F90 +++ b/src/tallies/tally_filter_zernike.F90 @@ -76,7 +76,7 @@ contains do i = 1, this % n_bins call match % bins % push_back(i) - call match % weights % push_back(zn(i) / SQRT_PI) + call match % weights % push_back(zn(i)) end do end subroutine get_all_bins diff --git a/tests/unit_tests/test_filters.py b/tests/unit_tests/test_filters.py index 6060b55d7..488badbfa 100644 --- a/tests/unit_tests/test_filters.py +++ b/tests/unit_tests/test_filters.py @@ -145,4 +145,4 @@ def test_first_moment(run_in_tmpdir, box_model): assert first_score(leg_sptl_tally) == scatter assert first_score(sph_scat_tally) == scatter assert first_score(sph_flux_tally) == approx(flux) - assert first_score(zernike_tally)*sqrt(pi) == approx(scatter) + assert first_score(zernike_tally) == approx(scatter) From e0e1dc6bd509d9d0eef7ff6fafb39b70c744c541 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 5 Apr 2018 12:15:28 -0500 Subject: [PATCH 160/361] Clarify normalization of Zernike polynomials --- openmc/filter_expansion.py | 13 ++++++++----- src/math.F90 | 6 ++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index e46bf7be1..872eaac9f 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -318,15 +318,15 @@ class ZernikeFilter(ExpansionFilter): This filter allows scores to be multiplied by Zernike polynomials of the particle's position normalized to a given unit circle, up to a - user-specified order. The Zernike polynomials are defined as + user-specified order. The Zernike polynomials follow the definition by `Noll + `_ and are defined as .. math:: - Z_n^m(\rho, \theta) = R_n^m(\rho) \cos (m\theta) + Z_n^m(\rho, \theta) = \sqrt{2n + 2} R_n^m(\rho) \cos (m\theta), \quad m > 0 - and + Z_n^{m}(\rho, \theta) = \sqrt{2n + 2} R_n^{m}(\rho) \sin (m\theta), \quad m < 0 - .. math:: - Z_n^{-m}(\rho, \theta) = R_n^{-m}(\rho) \sin (m\theta) + Z_n^{m}(\rho, \theta) = \sqrt{n + 1} R_n^{m}(\rho), \quad m = 0 where the radial polynomials are @@ -334,6 +334,9 @@ class ZernikeFilter(ExpansionFilter): R_n^m(\rho) = \sum\limits_{k=0}^{(n-m)/2} \frac{(-1)^k (n-k)!}{k! ( \frac{n+m}{2} - k)! (\frac{n-m}{2} - k)!} \rho^{n-2k}. + With this definition, the integral of :math:`(Z_n^m)^2` over the unit disk + is exactly :math:`\pi` for each polynomial. + Specifying a filter with order N tallies moments for all :math:`n` from 0 to N and each value of :math:`m`. The ordering of the Zernike polynomial moments follows the ANSI Z80.28 standard, where the one-dimensional index diff --git a/src/math.F90 b/src/math.F90 index 35855c614..ee8cd0530 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -575,8 +575,10 @@ contains end function calc_rn !=============================================================================== -! CALC_ZN calculates the n-th order Zernike polynomial moment for a given angle -! (rho, theta) location in the unit disk. +! CALC_ZN calculates the n-th order modified Zernike polynomial moment for a +! given angle (rho, theta) location in the unit disk. The normlization of the +! polynomials is such that the integral of Z_pq*Z_pq over the unit disk is +! exactly pi !=============================================================================== subroutine calc_zn(n, rho, phi, zn) From 69f97e6f2c029e0c70ec6eeb832bbaaf71f83434 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 11 Apr 2018 11:41:49 -0500 Subject: [PATCH 161/361] Force analog for tallies with spherical harmonics filter with cosine=scatter --- src/tallies/tally_filter_sph_harm.F90 | 4 ++-- src/tallies/tally_header.F90 | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tallies/tally_filter_sph_harm.F90 b/src/tallies/tally_filter_sph_harm.F90 index 5cd3f59c3..5c47c2c71 100644 --- a/src/tallies/tally_filter_sph_harm.F90 +++ b/src/tallies/tally_filter_sph_harm.F90 @@ -20,8 +20,8 @@ module tally_filter_sph_harm public :: openmc_sphharm_filter_set_order public :: openmc_sphharm_filter_set_cosine - integer, parameter :: COSINE_SCATTER = 1 - integer, parameter :: COSINE_PARTICLE = 2 + integer, public, parameter :: COSINE_SCATTER = 1 + integer, public, parameter :: COSINE_PARTICLE = 2 !=============================================================================== ! SPHERICALHARMONICSFILTER gives spherical harmonics expansion moments of a diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 29362fee3..02a578d7e 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -359,6 +359,9 @@ contains this % estimator = ESTIMATOR_ANALOG type is (SphericalHarmonicsFilter) j = FILTER_SPH_HARMONICS + if (filt % cosine == COSINE_SCATTER) then + this % estimator = ESTIMATOR_ANALOG + end if type is (SpatialLegendreFilter) j = FILTER_SPTL_LEGENDRE this % estimator = ESTIMATOR_COLLISION From 8a6c6d139813c3abf6836ab760b95a8a97afdd1b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 1 Apr 2018 07:50:14 -0500 Subject: [PATCH 162/361] Add C API bindings to create/modify meshes (and filters) --- docs/source/pythonapi/capi.rst | 3 + include/openmc.h | 8 + openmc/capi/__init__.py | 1 + openmc/capi/cell.py | 2 +- openmc/capi/filter.py | 39 ++++- openmc/capi/mesh.py | 172 ++++++++++++++++++++ src/cmfd_input.F90 | 2 +- src/mesh_header.F90 | 197 ++++++++++++++++++++++- src/tallies/tally_filter_mesh.F90 | 56 ++++--- src/tallies/tally_filter_meshsurface.F90 | 60 ++++--- tests/unit_tests/test_capi.py | 35 ++++ 11 files changed, 519 insertions(+), 56 deletions(-) create mode 100644 openmc/capi/mesh.py diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index f35823399..38ce61fb3 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -44,5 +44,8 @@ Classes EnergyFilter MaterialFilter Material + Mesh + MeshFilter + MeshSurfaceFilter Nuclide Tally diff --git a/include/openmc.h b/include/openmc.h index 1c2106432..785aaeba5 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -53,7 +53,15 @@ extern "C" { int openmc_material_set_id(int32_t index, int32_t id); int openmc_material_filter_get_bins(int32_t index, int32_t** bins, int32_t* n); int openmc_material_filter_set_bins(int32_t index, int32_t n, const int32_t* bins); + int openmc_mesh_filter_get_mesh(int32_t index, int32_t* index_mesh); int openmc_mesh_filter_set_mesh(int32_t index, int32_t index_mesh); + int openmc_mesh_get_id(int32_t index, int32_t* id); + int openmc_mesh_get_dimension(int32_t index, int** id, int* n); + int openmc_mesh_get_params(int32_t index, double** ll, double** ur, double** width, int* n); + int openmc_mesh_set_id(int32_t index, int32_t id); + int openmc_mesh_set_dimension(int32_t index, int n, const int* dims); + int openmc_mesh_set_params(int32_t index, const double* ll, const double* ur, const double* width, int n); + int openmc_meshsurface_filter_get_mesh(int32_t index, int32_t* index_mesh); int openmc_meshsurface_filter_set_mesh(int32_t index, int32_t index_mesh); int openmc_next_batch(); int openmc_nuclide_name(int index, char** name); diff --git a/openmc/capi/__init__.py b/openmc/capi/__init__.py index bc173f994..217e782a8 100644 --- a/openmc/capi/__init__.py +++ b/openmc/capi/__init__.py @@ -43,6 +43,7 @@ from .core import * from .nuclide import * from .material import * from .cell import * +from .mesh import * from .filter import * from .tally import * from .settings import settings diff --git a/openmc/capi/cell.py b/openmc/capi/cell.py index 0ab3f2583..8f0c0e1bb 100644 --- a/openmc/capi/cell.py +++ b/openmc/capi/cell.py @@ -44,7 +44,7 @@ class Cell(_FortranObjectWithID): This class exposes a cell that is stored internally in the OpenMC library. To obtain a view of a cell with a given ID, use the - :data:`openmc.capi.nuclides` mapping. + :data:`openmc.capi.cells` mapping. Parameters ---------- diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index 691ecc09c..a2b77fa92 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -10,13 +10,14 @@ from . import _dll from .core import _FortranObjectWithID from .error import _error_handler, AllocationError, InvalidIDError from .material import Material +from .mesh import Mesh __all__ = ['Filter', 'AzimuthalFilter', 'CellFilter', 'CellbornFilter', 'CellfromFilter', 'DistribcellFilter', 'DelayedGroupFilter', 'EnergyFilter', 'EnergyoutFilter', 'EnergyFunctionFilter', 'MaterialFilter', 'MeshFilter', - 'MuFilter', 'PolarFilter', 'SurfaceFilter', + 'MeshSurfaceFilter', 'MuFilter', 'PolarFilter', 'SurfaceFilter', 'UniverseFilter', 'filters'] # Tally functions @@ -52,9 +53,15 @@ _dll.openmc_material_filter_get_bins.errcheck = _error_handler _dll.openmc_material_filter_set_bins.argtypes = [c_int32, c_int32, POINTER(c_int32)] _dll.openmc_material_filter_set_bins.restype = c_int _dll.openmc_material_filter_set_bins.errcheck = _error_handler +_dll.openmc_mesh_filter_get_mesh.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_mesh_filter_get_mesh.restype = c_int +_dll.openmc_mesh_filter_get_mesh.errcheck = _error_handler _dll.openmc_mesh_filter_set_mesh.argtypes = [c_int32, c_int32] _dll.openmc_mesh_filter_set_mesh.restype = c_int _dll.openmc_mesh_filter_set_mesh.errcheck = _error_handler +_dll.openmc_meshsurface_filter_get_mesh.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_meshsurface_filter_get_mesh.restype = c_int +_dll.openmc_meshsurface_filter_get_mesh.errcheck = _error_handler _dll.openmc_meshsurface_filter_set_mesh.argtypes = [c_int32, c_int32] _dll.openmc_meshsurface_filter_set_mesh.restype = c_int _dll.openmc_meshsurface_filter_set_mesh.errcheck = _error_handler @@ -190,10 +197,40 @@ class MaterialFilter(Filter): class MeshFilter(Filter): filter_type = 'mesh' + def __init__(self, mesh=None, uid=None, new=True, index=None): + super().__init__(uid, new, index) + if mesh is not None: + self.mesh = mesh + + @property + def mesh(self): + index_mesh = c_int32() + _dll.openmc_mesh_filter_get_mesh(self._index, index_mesh) + return Mesh(index=index_mesh.value) + + @mesh.setter + def mesh(self, mesh): + _dll.openmc_mesh_filter_set_mesh(self._index, mesh._index) + class MeshSurfaceFilter(Filter): filter_type = 'meshsurface' + def __init__(self, mesh=None, uid=None, new=True, index=None): + super().__init__(uid, new, index) + if mesh is not None: + self.mesh = mesh + + @property + def mesh(self): + index_mesh = c_int32() + _dll.openmc_meshsurface_filter_get_mesh(self._index, index_mesh) + return Mesh(index=index_mesh.value) + + @mesh.setter + def mesh(self, mesh): + _dll.openmc_meshsurface_filter_set_mesh(self._index, mesh._index) + class MuFilter(Filter): filter_type = 'mu' diff --git a/openmc/capi/mesh.py b/openmc/capi/mesh.py new file mode 100644 index 000000000..c7430f9f3 --- /dev/null +++ b/openmc/capi/mesh.py @@ -0,0 +1,172 @@ +from collections.abc import Mapping, Iterable +from ctypes import c_int, c_int32, c_double, POINTER +from weakref import WeakValueDictionary + +import numpy as np +from numpy.ctypeslib import as_array + +from . import _dll +from .core import _FortranObjectWithID +from .error import _error_handler, AllocationError, InvalidIDError +from .material import Material + +__all__ = ['Mesh', 'meshes'] + +# Mesh functions +_dll.openmc_extend_meshes.argtypes = [c_int32, POINTER(c_int32), POINTER(c_int32)] +_dll.openmc_extend_meshes.restype = c_int +_dll.openmc_extend_meshes.errcheck = _error_handler +_dll.openmc_mesh_get_id.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_mesh_get_id.restype = c_int +_dll.openmc_mesh_get_id.errcheck = _error_handler +_dll.openmc_mesh_get_dimension.argtypes = [c_int32, POINTER(POINTER(c_int)), POINTER(c_int)] +_dll.openmc_mesh_get_dimension.restype = c_int +_dll.openmc_mesh_get_dimension.errcheck = _error_handler +_dll.openmc_mesh_get_params.argtypes = [ + c_int32, POINTER(POINTER(c_double)), POINTER(POINTER(c_double)), + POINTER(POINTER(c_double)), POINTER(c_int)] +_dll.openmc_mesh_get_params.restype = c_int +_dll.openmc_mesh_get_params.errcheck = _error_handler +_dll.openmc_mesh_set_id.argtypes = [c_int32, c_int32] +_dll.openmc_mesh_set_id.restype = c_int +_dll.openmc_mesh_set_id.errcheck = _error_handler +_dll.openmc_mesh_set_dimension.argtypes = [c_int32, c_int, POINTER(c_int)] +_dll.openmc_mesh_set_dimension.restype = c_int +_dll.openmc_mesh_set_dimension.errcheck = _error_handler +_dll.openmc_mesh_set_params.argtypes = [ + c_int32, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double)] +_dll.openmc_mesh_set_params.restype = c_int +_dll.openmc_mesh_set_params.errcheck = _error_handler +_dll.openmc_get_mesh_index.argtypes = [c_int32, POINTER(c_int32)] +_dll.openmc_get_mesh_index.restype = c_int +_dll.openmc_get_mesh_index.errcheck = _error_handler + + +class Mesh(_FortranObjectWithID): + """Mesh stored internally. + + This class exposes a mesh that is stored internally in the OpenMC + library. To obtain a view of a mesh with a given ID, use the + :data:`openmc.capi.meshes` mapping. + + Parameters + ---------- + index : int + Index in the `meshes` array. + + Attributes + ---------- + id : int + ID of the mesh + + """ + __instances = WeakValueDictionary() + + def __new__(cls, uid=None, new=True, index=None): + mapping = meshes + if index is None: + if new: + # Determine ID to assign + if uid is None: + uid = max(mapping, default=0) + 1 + else: + if uid in mapping: + raise AllocationError('A mesh with ID={} has already ' + 'been allocated.'.format(uid)) + + index = c_int32() + _dll.openmc_extend_meshes(1, index, None) + index = index.value + else: + index = mapping[uid]._index + + if index not in cls.__instances: + instance = super().__new__(cls) + instance._index = index + if uid is not None: + instance.id = uid + cls.__instances[index] = instance + + return cls.__instances[index] + + @property + def id(self): + mesh_id = c_int32() + _dll.openmc_mesh_get_id(self._index, mesh_id) + return mesh_id.value + + @id.setter + def id(self, mesh_id): + _dll.openmc_mesh_set_id(self._index, mesh_id) + + @property + def dimension(self): + dims = POINTER(c_int)() + n = c_int() + _dll.openmc_mesh_get_dimension(self._index, dims, n) + return tuple(as_array(dims, (n.value,))) + + @dimension.setter + def dimension(self, dimension): + n = len(dimension) + dimension = (c_int*n)(*dimension) + _dll.openmc_mesh_set_dimension(self._index, n, dimension) + + @property + def lower_left(self): + return self._get_parameters()[0] + + @property + def upper_right(self): + return self._get_parameters()[1] + + @property + def width(self): + return self._get_parameters()[2] + + def _get_parameters(self): + ll = POINTER(c_double)() + ur = POINTER(c_double)() + w = POINTER(c_double)() + n = c_int() + _dll.openmc_mesh_get_params(self._index, ll, ur, w, n) + return ( + as_array(ll, (n.value,)), + as_array(ur, (n.value,)), + as_array(w, (n.value,)) + ) + + def set_parameters(self, lower_left=None, upper_right=None, width=None): + if lower_left is not None: + n = len(lower_left) + lower_left = (c_double*n)(*lower_left) + if upper_right is not None: + n = len(upper_right) + upper_right = (c_double*n)(*upper_right) + if width is not None: + n = len(width) + width = (c_double*n)(*width) + _dll.openmc_mesh_set_params(self._index, n, lower_left, upper_right, width) + + +class _MeshMapping(Mapping): + def __getitem__(self, key): + index = c_int32() + try: + _dll.openmc_get_mesh_index(key, index) + except (AllocationError, InvalidIDError) as e: + # __contains__ expects a KeyError to work correctly + raise KeyError(str(e)) + return Mesh(index=index.value) + + def __iter__(self): + for i in range(len(self)): + yield Mesh(index=i + 1).id + + def __len__(self): + return c_int32.in_dll(_dll, 'n_meshes').value + + def __repr__(self): + return repr(dict(self)) + +meshes = _MeshMapping() diff --git a/src/cmfd_input.F90 b/src/cmfd_input.F90 index 86f4e2400..40b4abf57 100644 --- a/src/cmfd_input.F90 +++ b/src/cmfd_input.F90 @@ -278,7 +278,7 @@ contains m % id = i_start ! Set mesh type to rectangular - m % type = LATTICE_RECT + m % type = MESH_REGULAR ! Get pointer to mesh XML node node_mesh = root % child("mesh") diff --git a/src/mesh_header.F90 b/src/mesh_header.F90 index 8a2d61fd9..b1751a467 100644 --- a/src/mesh_header.F90 +++ b/src/mesh_header.F90 @@ -6,7 +6,7 @@ module mesh_header use constants use dict_header, only: DictIntInt - use error, only: warning, fatal_error + use error use hdf5_interface use string, only: to_str, to_lower use xml_interface @@ -15,6 +15,13 @@ module mesh_header private public :: free_memory_mesh public :: openmc_extend_meshes + public :: openmc_get_mesh_index + public :: openmc_mesh_get_id + public :: openmc_mesh_get_dimension + public :: openmc_mesh_get_params + public :: openmc_mesh_set_id + public :: openmc_mesh_set_dimension + public :: openmc_mesh_set_params !=============================================================================== ! STRUCTUREDMESH represents a tessellation of n-dimensional Euclidean space by @@ -23,13 +30,13 @@ module mesh_header type, public :: RegularMesh integer :: id = -1 ! user-specified id - integer :: type ! rectangular, hexagonal - integer :: n_dimension ! rank of mesh + integer :: type = MESH_REGULAR ! rectangular, hexagonal + integer(C_INT) :: n_dimension ! rank of mesh real(8) :: volume_frac ! volume fraction of each cell - integer, allocatable :: dimension(:) ! number of cells in each direction - real(8), allocatable :: lower_left(:) ! lower-left corner of mesh - real(8), allocatable :: upper_right(:) ! upper-right corner of mesh - real(8), allocatable :: width(:) ! width of each mesh cell + integer(C_INT), allocatable :: dimension(:) ! number of cells in each direction + real(C_DOUBLE), allocatable :: lower_left(:) ! lower-left corner of mesh + real(C_DOUBLE), allocatable :: upper_right(:) ! upper-right corner of mesh + real(C_DOUBLE), allocatable :: width(:) ! width of each mesh cell contains procedure :: from_xml => regular_from_xml procedure :: get_bin => regular_get_bin @@ -592,4 +599,180 @@ contains err = 0 end function openmc_extend_meshes + + function openmc_get_mesh_index(id, index) result(err) bind(C) + ! Return the index in the meshes array of a mesh with a given ID + integer(C_INT32_T), value :: id + integer(C_INT32_T), intent(out) :: index + integer(C_INT) :: err + + if (allocated(meshes)) then + if (mesh_dict % has(id)) then + index = mesh_dict % get(id) + err = 0 + else + err = E_INVALID_ID + call set_errmsg("No mesh exists with ID=" // trim(to_str(id)) // ".") + end if + else + err = E_ALLOCATE + call set_errmsg("Memory has not been allocated for meshes.") + end if + end function openmc_get_mesh_index + + + function openmc_mesh_get_id(index, id) result(err) bind(C) + ! Return the ID of a mesh + integer(C_INT32_T), value :: index + integer(C_INT32_T), intent(out) :: id + integer(C_INT) :: err + + if (index >= 1 .and. index <= size(meshes)) then + id = meshes(index) % id + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in meshes array is out of bounds.") + end if + end function openmc_mesh_get_id + + + function openmc_mesh_set_id(index, id) result(err) bind(C) + ! Set the ID of a mesh + 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_meshes) then + meshes(index) % id = id + call mesh_dict % set(id, index) + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in meshes array is out of bounds.") + end if + end function openmc_mesh_set_id + + + function openmc_mesh_get_dimension(index, dims, n) result(err) bind(C) + ! Get the dimension of a mesh + integer(C_INT32_T), value, intent(in) :: index + type(C_PTR), intent(out) :: dims + integer(C_INT), intent(out) :: n + integer(C_INT) :: err + + if (index >= 1 .and. index <= n_meshes) then + dims = C_LOC(meshes(index) % dimension) + n = meshes(index) % n_dimension + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in meshes array is out of bounds.") + end if + end function openmc_mesh_get_dimension + + + function openmc_mesh_set_dimension(index, n, dims) result(err) bind(C) + ! Set the dimension of a mesh + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT), value, intent(in) :: n + integer(C_INT), intent(in) :: dims(n) + integer(C_INT) :: err + + if (index >= 1 .and. index <= n_meshes) then + associate (m => meshes(index)) + if (allocated(m % dimension)) deallocate (m % dimension) + if (allocated(m % lower_left)) deallocate (m % lower_left) + if (allocated(m % upper_right)) deallocate (m % upper_right) + if (allocated(m % width)) deallocate (m % width) + + m % n_dimension = n + allocate(m % dimension(n)) + allocate(m % lower_left(n)) + allocate(m % upper_right(n)) + allocate(m % width(n)) + + ! Copy dimension + m % dimension(:) = dims + end associate + err = 0 + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in meshes array is out of bounds.") + end if + end function openmc_mesh_set_dimension + + + function openmc_mesh_get_params(index, ll, ur, width, n) result(err) bind(C) + ! Get the mesh parameters + integer(C_INT32_T), value, intent(in) :: index + type(C_PTR), intent(out) :: ll + type(C_PTR), intent(out) :: ur + type(C_PTR), intent(out) :: width + integer(C_INT), intent(out) :: n + integer(C_INT) :: err + + err = 0 + if (index >= 1 .and. index <= n_meshes) then + associate (m => meshes(index)) + if (allocated(m % lower_left)) then + ll = C_LOC(m % lower_left) + ur = C_LOC(m % upper_right) + width = C_LOC(m % width) + n = m % n_dimension + else + err = E_ALLOCATE + call set_errmsg("Mesh parameters have not been set.") + end if + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in meshes array is out of bounds.") + end if + end function openmc_mesh_get_params + + + function openmc_mesh_set_params(index, n, ll, ur, width) result(err) bind(C) + ! Set the mesh parameters + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), intent(in), optional :: ll(n) + real(C_DOUBLE), intent(in), optional :: ur(n) + real(C_DOUBLE), intent(in), optional :: width(n) + integer(C_INT) :: err + + err = 0 + if (index >= 1 .and. index <= n_meshes) then + associate (m => meshes(index)) + if (allocated(m % lower_left)) deallocate (m % lower_left) + if (allocated(m % upper_right)) deallocate (m % upper_right) + if (allocated(m % width)) deallocate (m % width) + + allocate(m % lower_left(n)) + allocate(m % upper_right(n)) + allocate(m % width(n)) + + if (present(ll) .and. present(ur)) then + m % lower_left(:) = ll + m % upper_right(:) = ur + m % width(:) = (ur - ll) / m % dimension + elseif (present(ll) .and. present(width)) then + m % lower_left(:) = ll + m % width(:) = width + m % upper_right(:) = ll + width * m % dimension + elseif (present(ur) .and. present(width)) then + m % upper_right(:) = ur + m % width(:) = width + m % lower_left(:) = ur - width * m % dimension + else + err = E_INVALID_ARGUMENT + call set_errmsg("At least two parameters must be specified.") + end if + end associate + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in meshes array is out of bounds.") + end if + end function openmc_mesh_set_params + end module mesh_header diff --git a/src/tallies/tally_filter_mesh.F90 b/src/tallies/tally_filter_mesh.F90 index 3c0e6250c..d2acdc4c8 100644 --- a/src/tallies/tally_filter_mesh.F90 +++ b/src/tallies/tally_filter_mesh.F90 @@ -16,6 +16,7 @@ module tally_filter_mesh implicit none private + public :: openmc_mesh_filter_get_mesh public :: openmc_mesh_filter_set_mesh !=============================================================================== @@ -280,35 +281,46 @@ contains ! C API FUNCTIONS !=============================================================================== + function openmc_mesh_filter_get_mesh(index, index_mesh) result(err) bind(C) + ! Get the mesh for a mesh filter + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), intent(out) :: index_mesh + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (MeshFilter) + index_mesh = f % mesh + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set mesh on a non-mesh filter.") + end select + end if + end function openmc_mesh_filter_get_mesh + + function openmc_mesh_filter_set_mesh(index, index_mesh) result(err) bind(C) ! Set the mesh for a mesh filter integer(C_INT32_T), value, intent(in) :: index integer(C_INT32_T), value, intent(in) :: index_mesh integer(C_INT) :: err - err = 0 - if (index >= 1 .and. index <= n_filters) then - if (allocated(filters(index) % obj)) then - select type (f => filters(index) % obj) - type is (MeshFilter) - if (index_mesh >= 1 .and. index_mesh <= n_meshes) then - f % mesh = index_mesh - f % n_bins = product(meshes(index_mesh) % dimension) - else - err = E_OUT_OF_BOUNDS - call set_errmsg("Index in 'meshes' array is out of bounds.") - end if + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (MeshFilter) + if (index_mesh >= 1 .and. index_mesh <= n_meshes) then + f % mesh = index_mesh + f % n_bins = product(meshes(index_mesh) % dimension) + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in 'meshes' array is out of bounds.") + end if class default - err = E_INVALID_TYPE - call set_errmsg("Tried to set mesh on a non-mesh filter.") - end select - else - err = E_ALLOCATE - call set_errmsg("Filter type has not been set yet.") - end if - else - err = E_OUT_OF_BOUNDS - call set_errmsg("Index in filters array out of bounds.") + err = E_INVALID_TYPE + call set_errmsg("Tried to set mesh on a non-mesh filter.") + end select end if end function openmc_mesh_filter_set_mesh diff --git a/src/tallies/tally_filter_meshsurface.F90 b/src/tallies/tally_filter_meshsurface.F90 index e1e8c4803..d90b63664 100644 --- a/src/tallies/tally_filter_meshsurface.F90 +++ b/src/tallies/tally_filter_meshsurface.F90 @@ -16,6 +16,7 @@ module tally_filter_meshsurface implicit none private + public :: openmc_meshsurface_filter_get_mesh public :: openmc_meshsurface_filter_set_mesh !=============================================================================== @@ -296,6 +297,25 @@ contains ! C API FUNCTIONS !=============================================================================== + function openmc_meshsurface_filter_get_mesh(index, index_mesh) result(err) bind(C) + ! Get the mesh for a mesh surface filter + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT32_T), intent(out) :: index_mesh + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (MeshSurfaceFilter) + index_mesh = f % mesh + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set mesh on a non-mesh filter.") + end select + end if + end function openmc_meshsurface_filter_get_mesh + + function openmc_meshsurface_filter_set_mesh(index, index_mesh) result(err) bind(C) ! Set the mesh for a mesh surface filter integer(C_INT32_T), value, intent(in) :: index @@ -304,30 +324,22 @@ contains integer :: n_dim - err = 0 - if (index >= 1 .and. index <= n_filters) then - if (allocated(filters(index) % obj)) then - select type (f => filters(index) % obj) - type is (MeshSurfaceFilter) - if (index_mesh >= 1 .and. index_mesh <= n_meshes) then - f % mesh = index_mesh - n_dim = meshes(index_mesh) % n_dimension - f % n_bins = 4*n_dim*product(meshes(index_mesh) % dimension) - else - err = E_OUT_OF_BOUNDS - call set_errmsg("Index in 'meshes' array is out of bounds.") - end if - class default - err = E_INVALID_TYPE - call set_errmsg("Tried to set mesh on a non-mesh filter.") - end select - else - err = E_ALLOCATE - call set_errmsg("Filter type has not been set yet.") - end if - else - err = E_OUT_OF_BOUNDS - call set_errmsg("Index in filters array out of bounds.") + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (MeshSurfaceFilter) + if (index_mesh >= 1 .and. index_mesh <= n_meshes) then + f % mesh = index_mesh + n_dim = meshes(index_mesh) % n_dimension + f % n_bins = 4*n_dim*product(meshes(index_mesh) % dimension) + else + err = E_OUT_OF_BOUNDS + call set_errmsg("Index in 'meshes' array is out of bounds.") + end if + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to set mesh on a non-mesh filter.") + end select end if end function openmc_meshsurface_filter_set_mesh diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 8bc6c3d15..66a6f3bc1 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -250,3 +250,38 @@ def test_find_material(capi_init): assert mat is openmc.capi.materials[1] mat = openmc.capi.find_material((0.4, 0., 0.)) assert mat is openmc.capi.materials[2] + + +def test_mesh(capi_init): + mesh = openmc.capi.Mesh() + mesh.dimension = (2, 3, 4) + assert mesh.dimension == (2, 3, 4) + with pytest.raises(openmc.capi.AllocationError): + mesh2 = openmc.capi.Mesh(mesh.id) + + # Make sure each combination of parameters works + ll = (0., 0., 0.) + ur = (10., 10., 10.) + width = (1., 1., 1.) + mesh.set_parameters(lower_left=ll, upper_right=ur) + assert mesh.lower_left == pytest.approx(ll) + assert mesh.upper_right == pytest.approx(ur) + mesh.set_parameters(lower_left=ll, width=width) + assert mesh.lower_left == pytest.approx(ll) + assert mesh.width == pytest.approx(width) + mesh.set_parameters(upper_right=ur, width=width) + assert mesh.upper_right == pytest.approx(ur) + assert mesh.width == pytest.approx(width) + + meshes = openmc.capi.meshes + assert isinstance(meshes, Mapping) + assert len(meshes) == 1 + for mesh_id, mesh in meshes.items(): + assert isinstance(mesh, openmc.capi.Mesh) + assert mesh_id == mesh.id + + mf = openmc.capi.MeshFilter(mesh) + assert mf.mesh == mesh + + msf = openmc.capi.MeshSurfaceFilter(mesh) + assert msf.mesh == mesh From 4be18cb9e905862e939172c5220b1e314fa581a8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 2 Apr 2018 11:41:02 -0500 Subject: [PATCH 163/361] Prevent ICE on gfortran 4.8 --- src/mesh_header.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh_header.F90 b/src/mesh_header.F90 index b1751a467..fc13a868e 100644 --- a/src/mesh_header.F90 +++ b/src/mesh_header.F90 @@ -716,9 +716,9 @@ contains if (index >= 1 .and. index <= n_meshes) then associate (m => meshes(index)) if (allocated(m % lower_left)) then - ll = C_LOC(m % lower_left) - ur = C_LOC(m % upper_right) - width = C_LOC(m % width) + ll = C_LOC(m % lower_left(1)) + ur = C_LOC(m % upper_right(1)) + width = C_LOC(m % width(1)) n = m % n_dimension else err = E_ALLOCATE From f485693fa0c810279a2e74d2d88385ab89fd56e7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 4 Apr 2018 06:46:50 -0500 Subject: [PATCH 164/361] Support void materials in C API Python bindings --- openmc/capi/cell.py | 13 +++++++------ openmc/capi/material.py | 3 +++ src/geometry_header.F90 | 14 +++++--------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/openmc/capi/cell.py b/openmc/capi/cell.py index 8f0c0e1bb..ccc6b6f6e 100644 --- a/openmc/capi/cell.py +++ b/openmc/capi/cell.py @@ -115,14 +115,15 @@ class Cell(_FortranObjectWithID): def fill(self, fill): if isinstance(fill, Iterable): n = len(fill) - indices = (c_int*n)(*(m._index for m in fill)) - _dll.openmc_cell_set_fill(self._index, 1, 1, indices) + indices = (c_int32*n)(*(m._index if m is not None else -1 + for m in fill)) + _dll.openmc_cell_set_fill(self._index, 1, n, indices) elif isinstance(fill, Material): - materials = [fill] - indices = (c_int*1)(fill._index) + indices = (c_int32*1)(fill._index) + _dll.openmc_cell_set_fill(self._index, 1, 1, indices) + elif fill is None: + indices = (c_int32*1)(-1) _dll.openmc_cell_set_fill(self._index, 1, 1, indices) - else: - raise NotImplementedError def set_temperature(self, T, instance=None): """Set the temperature of a cell diff --git a/openmc/capi/material.py b/openmc/capi/material.py index 62d6df012..464b8a1a3 100644 --- a/openmc/capi/material.py +++ b/openmc/capi/material.py @@ -89,6 +89,9 @@ class Material(_FortranObjectWithID): index = index.value else: index = mapping[uid]._index + elif index == -1: + # Special value indicates void material + return None if index not in cls.__instances: instance = super(Material, cls).__new__(cls) diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index e71916d09..f6206adf9 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -544,16 +544,12 @@ contains c % type = FILL_MATERIAL do i = 1, n j = indices(i) - if (j == 0) then - c % material(i) = MATERIAL_VOID + if ((j >= 1 .and. j <= n_materials) .or. j == MATERIAL_VOID) then + c % material(i) = j else - if (j >= 1 .and. j <= n_materials) then - c % material(i) = j - else - err = E_OUT_OF_BOUNDS - call set_errmsg("Index " // trim(to_str(j)) // " in the & - &materials array is out of bounds.") - end if + err = E_OUT_OF_BOUNDS + call set_errmsg("Index " // trim(to_str(j)) // " in the & + &materials array is out of bounds.") end if end do case (FILL_UNIVERSE) From 5fb3031d99e9de48f6e8f68f61abaa412f598b94 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 4 Apr 2018 14:28:10 -0500 Subject: [PATCH 165/361] [backport] Fix/add prototypes in openmc.h --- include/openmc.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/openmc.h b/include/openmc.h index 785aaeba5..1106bf54c 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -30,7 +30,9 @@ extern "C" { int openmc_extend_sources(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_extend_tallies(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_filter_get_id(int32_t index, int32_t* id); + int openmc_filter_get_type(int32_t index, const char** type); int openmc_filter_set_id(int32_t index, int32_t id); + int openmc_filter_set_type(int32_t index, const char* type); 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); @@ -91,7 +93,8 @@ extern "C" { int openmc_tally_set_filters(int32_t index, int n, const int32_t* indices); int openmc_tally_set_id(int32_t index, int32_t id); int openmc_tally_set_nuclides(int32_t index, int n, const char** nuclides); - int openmc_tally_set_scores(int32_t index, int n, const int* scores); + int openmc_tally_set_scores(int32_t index, int n, const char** scores); + int openmc_tally_set_type(int32_t index, const char* type); int openmc_zernike_filter_get_order(int32_t index, int* order); int openmc_zernike_filter_get_params(int32_t index, double* x, double* y, double* r); int openmc_zernike_filter_set_order(int32_t index, int order); From c78d296bf69b285459468f1bfc4b94206190eaeb Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 5 Apr 2018 16:07:51 -0500 Subject: [PATCH 166/361] Have openmc_next_batch return an actual error --- include/openmc.h | 26 ++++++------- openmc/capi/cell.py | 3 +- openmc/capi/core.py | 30 +++++++++----- openmc/capi/error.py | 73 +++++++++-------------------------- openmc/capi/filter.py | 3 +- openmc/capi/material.py | 3 +- openmc/capi/mesh.py | 3 +- openmc/capi/nuclide.py | 3 +- openmc/capi/tally.py | 3 +- openmc/exceptions.py | 38 ++++++++++++++++++ src/error.F90 | 22 +++++------ src/main.F90 | 5 ++- src/simulation.F90 | 40 ++++++++++++------- tests/unit_tests/test_capi.py | 19 ++++----- 14 files changed, 154 insertions(+), 117 deletions(-) create mode 100644 openmc/exceptions.py diff --git a/include/openmc.h b/include/openmc.h index 1106bf54c..11a39e1ff 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -65,11 +65,11 @@ extern "C" { int openmc_mesh_set_params(int32_t index, const double* ll, const double* ur, const double* width, int n); int openmc_meshsurface_filter_get_mesh(int32_t index, int32_t* index_mesh); int openmc_meshsurface_filter_set_mesh(int32_t index, int32_t index_mesh); - int openmc_next_batch(); + int openmc_next_batch(int* status); int openmc_nuclide_name(int index, char** name); void openmc_plot_geometry(); void openmc_reset(); - void openmc_run(); + int openmc_run(); void openmc_simulation_finalize(); void openmc_simulation_init(); int openmc_source_bank(struct Bank** ptr, int64_t* n); @@ -102,17 +102,17 @@ extern "C" { const double* y, const double* r); // Error codes - extern int E_UNASSIGNED; - extern int E_ALLOCATE; - extern int E_OUT_OF_BOUNDS; - extern int E_INVALID_SIZE; - extern int E_INVALID_ARGUMENT; - extern int E_INVALID_TYPE; - extern int E_INVALID_ID; - extern int E_GEOMETRY; - extern int E_DATA; - extern int E_PHYSICS; - extern int E_WARNING; + extern int OPENMC_E_UNASSIGNED; + extern int OPENMC_E_ALLOCATE; + extern int OPENMC_E_OUT_OF_BOUNDS; + extern int OPENMC_E_INVALID_SIZE; + extern int OPENMC_E_INVALID_ARGUMENT; + extern int OPENMC_E_INVALID_TYPE; + extern int OPENMC_E_INVALID_ID; + extern int OPENMC_E_GEOMETRY; + extern int OPENMC_E_DATA; + extern int OPENMC_E_PHYSICS; + extern int OPENMC_E_WARNING; // Global variables extern char openmc_err_msg[256]; diff --git a/openmc/capi/cell.py b/openmc/capi/cell.py index ccc6b6f6e..258e773dc 100644 --- a/openmc/capi/cell.py +++ b/openmc/capi/cell.py @@ -5,9 +5,10 @@ from weakref import WeakValueDictionary import numpy as np from numpy.ctypeslib import as_array +from openmc.exceptions import AllocationError, InvalidIDError from . import _dll from .core import _FortranObjectWithID -from .error import _error_handler, AllocationError, InvalidIDError +from .error import _error_handler from .material import Material __all__ = ['Cell', 'cells'] diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 415fa2e93..f80b5fe54 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -6,8 +6,9 @@ from warnings import warn import numpy as np from numpy.ctypeslib import as_array +from openmc.exceptions import AllocationError from . import _dll -from .error import _error_handler, AllocationError +from .error import _error_handler import openmc.capi @@ -31,9 +32,12 @@ _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.argtypes = [POINTER(c_int)] _dll.openmc_next_batch.restype = c_int +_dll.openmc_next_batch.errcheck = _error_handler _dll.openmc_plot_geometry.restype = None -_dll.openmc_run.restype = None +_dll.openmc_run.restype = c_int +_dll.openmc_run.errcheck = _error_handler _dll.openmc_reset.restype = None _dll.openmc_source_bank.argtypes = [POINTER(POINTER(_Bank)), POINTER(c_int64)] _dll.openmc_source_bank.restype = c_int @@ -147,13 +151,13 @@ def iter_batches(): """ while True: # Run next batch - retval = next_batch() + status = next_batch() # Provide opportunity for user to perform action between batches yield # End the iteration - if retval < 0: + if status != 0: break @@ -180,12 +184,18 @@ def keff(): def next_batch(): - """Run next batch.""" - retval = _dll.openmc_next_batch() - if retval == -3: - raise AllocationError('Simulation has not been initialized. You must call ' - 'openmc.capi.simulation_init() first.') - return retval + """Run next batch. + + Returns + ------- + int + Status after running a batch (0=normal, 1=reached maximum number of + batches, 2=tally triggers reached) + + """ + status = c_int() + _dll.openmc_next_batch(status) + return status.value def plot_geometry(): diff --git a/openmc/capi/error.py b/openmc/capi/error.py index a11d6ea87..b35de4e60 100644 --- a/openmc/capi/error.py +++ b/openmc/capi/error.py @@ -1,45 +1,10 @@ from ctypes import c_int, c_char from warnings import warn +import openmc.exceptions as exc from . import _dll -class OpenMCError(Exception): - """Root exception class for OpenMC.""" - - -class GeometryError(OpenMCError): - """Geometry-related error""" - - -class InvalidIDError(OpenMCError): - """Use of an ID that is invalid.""" - - -class AllocationError(OpenMCError): - """Error related to memory allocation.""" - - -class OutOfBoundsError(OpenMCError): - """Index in array out of bounds.""" - - -class DataError(OpenMCError): - """Error relating to nuclear data.""" - - -class PhysicsError(OpenMCError): - """Error relating to performing physics.""" - - -class InvalidArgumentError(OpenMCError): - """Argument passed was invalid.""" - - -class InvalidTypeError(OpenMCError): - """Tried to perform an operation on the wrong type.""" - - def _error_handler(err, func, args): """Raise exception according to error code.""" @@ -52,23 +17,23 @@ def _error_handler(err, func, args): msg = errmsg.value.decode() # Raise exception type corresponding to error code - if err == errcode('e_allocate'): - raise AllocationError(msg) - elif err == errcode('e_out_of_bounds'): - raise OutOfBoundsError(msg) - elif err == errcode('e_invalid_argument'): - raise InvalidArgumentError(msg) - elif err == errcode('e_invalid_type'): - raise InvalidTypeError(msg) - if err == errcode('e_invalid_id'): - raise InvalidIDError(msg) - elif err == errcode('e_geometry'): - raise GeometryError(msg) - elif err == errcode('e_data'): - raise DataError(msg) - elif err == errcode('e_physics'): - raise PhysicsError(msg) - elif err == errcode('e_warning'): + if err == errcode('OPENMC_E_ALLOCATE'): + raise exc.AllocationError(msg) + elif err == errcode('OPENMC_E_OUT_OF_BOUNDS'): + raise exc.OutOfBoundsError(msg) + elif err == errcode('OPENMC_E_INVALID_ARGUMENT'): + raise exc.InvalidArgumentError(msg) + elif err == errcode('OPENMC_E_INVALID_TYPE'): + raise exc.InvalidTypeError(msg) + if err == errcode('OPENMC_E_INVALID_ID'): + raise exc.InvalidIDError(msg) + elif err == errcode('OPENMC_E_GEOMETRY'): + raise exc.GeometryError(msg) + elif err == errcode('OPENMC_E_DATA'): + raise exc.DataError(msg) + elif err == errcode('OPENMC_E_PHYSICS'): + raise exc.PhysicsError(msg) + elif err == errcode('OPENMC_E_WARNING'): warn(msg) elif err < 0: - raise OpenMCError("Unknown error encountered (code {}).".format(err)) + raise exc.OpenMCError("Unknown error encountered (code {}).".format(err)) diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index a2b77fa92..5c21c3ef4 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -6,9 +6,10 @@ from weakref import WeakValueDictionary import numpy as np from numpy.ctypeslib import as_array +from openmc.exceptions import AllocationError, InvalidIDError from . import _dll from .core import _FortranObjectWithID -from .error import _error_handler, AllocationError, InvalidIDError +from .error import _error_handler from .material import Material from .mesh import Mesh diff --git a/openmc/capi/material.py b/openmc/capi/material.py index 464b8a1a3..a6c29a375 100644 --- a/openmc/capi/material.py +++ b/openmc/capi/material.py @@ -5,9 +5,10 @@ from weakref import WeakValueDictionary import numpy as np from numpy.ctypeslib import as_array +from openmc.exceptions import AllocationError, InvalidIDError from . import _dll, Nuclide from .core import _FortranObjectWithID -from .error import _error_handler, AllocationError, InvalidIDError +from .error import _error_handler __all__ = ['Material', 'materials'] diff --git a/openmc/capi/mesh.py b/openmc/capi/mesh.py index c7430f9f3..326f5f61e 100644 --- a/openmc/capi/mesh.py +++ b/openmc/capi/mesh.py @@ -5,9 +5,10 @@ from weakref import WeakValueDictionary import numpy as np from numpy.ctypeslib import as_array +from openmc.exceptions import AllocationError, InvalidIDError from . import _dll from .core import _FortranObjectWithID -from .error import _error_handler, AllocationError, InvalidIDError +from .error import _error_handler from .material import Material __all__ = ['Mesh', 'meshes'] diff --git a/openmc/capi/nuclide.py b/openmc/capi/nuclide.py index f66212c97..e57653c64 100644 --- a/openmc/capi/nuclide.py +++ b/openmc/capi/nuclide.py @@ -5,9 +5,10 @@ from weakref import WeakValueDictionary import numpy as np from numpy.ctypeslib import as_array +from openmc.exceptions import DataError, AllocationError from . import _dll from .core import _FortranObject -from .error import _error_handler, DataError, AllocationError +from .error import _error_handler __all__ = ['Nuclide', 'nuclides', 'load_nuclide'] diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index a78347177..46a967f69 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -6,10 +6,11 @@ import numpy as np from numpy.ctypeslib import as_array import scipy.stats +from openmc.exceptions import AllocationError, InvalidIDError from openmc.data.reaction import REACTION_NAME from . import _dll, Nuclide from .core import _FortranObjectWithID -from .error import _error_handler, AllocationError, InvalidIDError +from .error import _error_handler from .filter import _get_filter diff --git a/openmc/exceptions.py b/openmc/exceptions.py new file mode 100644 index 000000000..c87bfc82c --- /dev/null +++ b/openmc/exceptions.py @@ -0,0 +1,38 @@ +class OpenMCError(Exception): + """Root exception class for OpenMC.""" + + +class GeometryError(OpenMCError): + """Geometry-related error""" + + +class InvalidIDError(OpenMCError): + """Use of an ID that is invalid.""" + + +class AllocationError(OpenMCError): + """Error related to memory allocation.""" + + +class OutOfBoundsError(OpenMCError): + """Index in array out of bounds.""" + + +class DataError(OpenMCError): + """Error relating to nuclear data.""" + + +class PhysicsError(OpenMCError): + """Error relating to performing physics.""" + + +class InvalidArgumentError(OpenMCError): + """Argument passed was invalid.""" + + +class InvalidTypeError(OpenMCError): + """Tried to perform an operation on the wrong type.""" + + +class SetupError(OpenMCError): + """Error while setting up a problem.""" diff --git a/src/error.F90 b/src/error.F90 index 0a5af302d..f4fb34173 100644 --- a/src/error.F90 +++ b/src/error.F90 @@ -15,19 +15,19 @@ module error public :: write_message ! Error codes - integer(C_INT), public, bind(C) :: E_UNASSIGNED = -1 - integer(C_INT), public, bind(C) :: E_ALLOCATE = -2 - integer(C_INT), public, bind(C) :: E_OUT_OF_BOUNDS = -3 - integer(C_INT), public, bind(C) :: E_INVALID_SIZE = -4 - integer(C_INT), public, bind(C) :: E_INVALID_ARGUMENT = -5 - integer(C_INT), public, bind(C) :: E_INVALID_TYPE = -6 - integer(C_INT), public, bind(C) :: E_INVALID_ID = -7 - integer(C_INT), public, bind(C) :: E_GEOMETRY = -8 - integer(C_INT), public, bind(C) :: E_DATA = -9 - integer(C_INT), public, bind(C) :: E_PHYSICS = -10 + integer(C_INT), public, bind(C, name='OPENMC_E_UNASSIGNED') :: E_UNASSIGNED = -1 + integer(C_INT), public, bind(C, name='OPENMC_E_ALLOCATE') :: E_ALLOCATE = -2 + integer(C_INT), public, bind(C, name='OPENMC_E_OUT_OF_BOUNDS') :: E_OUT_OF_BOUNDS = -3 + integer(C_INT), public, bind(C, name='OPENMC_E_INVALID_SIZE') :: E_INVALID_SIZE = -4 + integer(C_INT), public, bind(C, name='OPENMC_E_INVALID_ARGUMENT') :: E_INVALID_ARGUMENT = -5 + integer(C_INT), public, bind(C, name='OPENMC_E_INVALID_TYPE') :: E_INVALID_TYPE = -6 + integer(C_INT), public, bind(C, name='OPENMC_E_INVALID_ID') :: E_INVALID_ID = -7 + integer(C_INT), public, bind(C, name='OPENMC_E_GEOMETRY') :: E_GEOMETRY = -8 + integer(C_INT), public, bind(C, name='OPENMC_E_DATA') :: E_DATA = -9 + integer(C_INT), public, bind(C, name='OPENMC_E_PHYSICS') :: E_PHYSICS = -10 ! Warning codes - integer(C_INT), public, bind(C) :: E_WARNING = 1 + integer(C_INT), public, bind(C, name='OPENMC_E_WARNING') :: E_WARNING = 1 ! Error message character(kind=C_CHAR), public, bind(C) :: openmc_err_msg(256) diff --git a/src/main.F90 b/src/main.F90 index 120bdd1e0..372e993f5 100644 --- a/src/main.F90 +++ b/src/main.F90 @@ -1,5 +1,7 @@ program main + use, intrinsic :: ISO_C_BINDING + use constants use message_passing use openmc_api, only: openmc_init, openmc_finalize, openmc_run, & @@ -9,6 +11,7 @@ program main implicit none + integer(C_INT) :: err #ifdef OPENMC_MPI integer :: mpi_err ! MPI error code #endif @@ -27,7 +30,7 @@ program main ! start problem based on mode select case (run_mode) case (MODE_FIXEDSOURCE, MODE_EIGENVALUE) - call openmc_run() + err = openmc_run() case (MODE_PLOTTING) call openmc_plot_geometry() case (MODE_PARTICLE) diff --git a/src/simulation.F90 b/src/simulation.F90 index 8fe4f025c..68debe52b 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -48,6 +48,10 @@ module simulation public :: openmc_simulation_init public :: openmc_simulation_finalize + integer(C_INT), parameter :: STATUS_EXIT_NORMAL = 0 + integer(C_INT), parameter :: STATUS_EXIT_MAX_BATCH = 1 + integer(C_INT), parameter :: STATUS_EXIT_ON_TRIGGER = 2 + contains !=============================================================================== @@ -56,28 +60,36 @@ contains ! calculation. !=============================================================================== - subroutine openmc_run() bind(C) + function openmc_run() result(err) bind(C) + integer(C_INT) :: err + integer(C_INT) :: status call openmc_simulation_init() - do while (openmc_next_batch() == 0) + do + err = openmc_next_batch(status) + if (status /= 0 .or. err < 0) exit end do call openmc_simulation_finalize() - end subroutine openmc_run + end function openmc_run !=============================================================================== ! OPENMC_NEXT_BATCH !=============================================================================== - function openmc_next_batch() result(retval) bind(C) - integer(C_INT) :: retval + function openmc_next_batch(status) result(err) bind(C) + integer(C_INT), intent(out), optional :: status + integer(C_INT) :: err type(Particle) :: p integer(8) :: i_work + err = 0 + ! Make sure simulation has been initialized if (.not. simulation_initialized) then - retval = -3 + err = E_ALLOCATE + call set_errmsg("Simulation has not been initialized yet.") return end if @@ -86,7 +98,7 @@ contains ! Handle restart runs if (restart_run .and. current_batch <= restart_batch) then call replay_batch_history() - retval = 0 + status = STATUS_EXIT_NORMAL return end if @@ -124,12 +136,14 @@ contains call finalize_batch() ! Check simulation ending criteria - if (current_batch == n_max_batches) then - retval = -1 - elseif (satisfy_triggers) then - retval = -2 - else - retval = 0 + if (present(status)) then + if (current_batch == n_max_batches) then + status = STATUS_EXIT_MAX_BATCH + elseif (satisfy_triggers) then + status = STATUS_EXIT_ON_TRIGGER + else + status = STATUS_EXIT_NORMAL + end if end if end function openmc_next_batch diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 66a6f3bc1..6c05f5d3e 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -4,6 +4,7 @@ import os import numpy as np import pytest import openmc +import openmc.exceptions as exc import openmc.capi from tests import cdtemp @@ -60,7 +61,7 @@ def test_cell(capi_init): def test_new_cell(capi_init): - with pytest.raises(openmc.capi.AllocationError): + with pytest.raises(exc.AllocationError): openmc.capi.Cell(1) new_cell = openmc.capi.Cell() new_cell_with_id = openmc.capi.Cell(10) @@ -91,7 +92,7 @@ def test_material(capi_init): def test_new_material(capi_init): - with pytest.raises(openmc.capi.AllocationError): + with pytest.raises(exc.AllocationError): openmc.capi.Material(1) new_mat = openmc.capi.Material() new_mat_with_id = openmc.capi.Material(10) @@ -109,7 +110,7 @@ def test_nuclide_mapping(capi_init): def test_load_nuclide(capi_init): openmc.capi.load_nuclide('Pu239') - with pytest.raises(openmc.capi.DataError): + with pytest.raises(exc.DataError): openmc.capi.load_nuclide('Pu3') @@ -145,7 +146,7 @@ def test_tally(capi_init): assert isinstance(t.filters[1], openmc.capi.EnergyFilter) # Create new filter and replace existing - with pytest.raises(openmc.capi.AllocationError): + with pytest.raises(exc.AllocationError): openmc.capi.MaterialFilter(uid=1) mats = openmc.capi.materials f = openmc.capi.MaterialFilter([mats[2], mats[1]]) @@ -153,7 +154,7 @@ def test_tally(capi_init): assert t.filters == [f] assert t.nuclides == ['U235', 'U238'] - with pytest.raises(openmc.capi.DataError): + with pytest.raises(exc.DataError): t.nuclides = ['Zr2'] t.nuclides = ['U234', 'Zr90'] assert t.nuclides == ['U234', 'Zr90'] @@ -165,7 +166,7 @@ def test_tally(capi_init): def test_new_tally(capi_init): - with pytest.raises(openmc.capi.AllocationError): + with pytest.raises(exc.AllocationError): openmc.capi.Material(1) new_tally = openmc.capi.Tally() new_tally.scores = ['flux'] @@ -206,7 +207,7 @@ def test_by_batch(capi_run): # Running next batch before simulation is initialized should raise an # exception - with pytest.raises(openmc.capi.AllocationError): + with pytest.raises(exc.AllocationError): openmc.capi.next_batch() openmc.capi.simulation_init() @@ -241,7 +242,7 @@ def test_find_cell(capi_init): 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): + with pytest.raises(exc.GeometryError): openmc.capi.find_cell((100., 100., 100.)) @@ -256,7 +257,7 @@ def test_mesh(capi_init): mesh = openmc.capi.Mesh() mesh.dimension = (2, 3, 4) assert mesh.dimension == (2, 3, 4) - with pytest.raises(openmc.capi.AllocationError): + with pytest.raises(exc.AllocationError): mesh2 = openmc.capi.Mesh(mesh.id) # Make sure each combination of parameters works From 336c38da2f1ef9d9be6ba52536765bde3d1ce541 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 12 Apr 2018 07:18:47 -0500 Subject: [PATCH 167/361] Fix typo in openmc.h --- include/openmc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/openmc.h b/include/openmc.h index 11a39e1ff..d24ca4af9 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -16,7 +16,7 @@ extern "C" { int delayed_group; }; - void openmc_calculate_voumes(); + void openmc_calculate_volumes(); int openmc_cell_get_fill(int32_t index, int* type, int32_t** indices, int32_t* n); int openmc_cell_get_id(int32_t index, int32_t* id); int openmc_cell_set_fill(int32_t index, int type, int32_t n, const int32_t* indices); From f704a8a2405978a16c202fbda3c2a21db62b5619 Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Thu, 12 Apr 2018 17:51:53 +0000 Subject: [PATCH 168/361] commented out lines broadcasting tally results. MPICH bug that causes simulations on ecp-benchmarks to crash. --- src/simulation.F90 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/simulation.F90 b/src/simulation.F90 index 8fe4f025c..83b7ae3ce 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -511,13 +511,13 @@ contains ! 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) + !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 6db7cc2977a7821428e0898eb00af7dd8bc6062e Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Thu, 12 Apr 2018 19:06:50 +0000 Subject: [PATCH 169/361] making sure broadcast is commented out. --- src/simulation.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simulation.F90 b/src/simulation.F90 index 83b7ae3ce..c35da3cb3 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -514,7 +514,7 @@ contains !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) + ! 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) From c80379a48816ea17936c108863ed8633046dacc5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 13 Apr 2018 15:04:04 -0500 Subject: [PATCH 170/361] Docfix for openmc.data.Library.get_by_material --- openmc/data/library.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openmc/data/library.py b/openmc/data/library.py index 34cd380a5..adfa3e4cb 100644 --- a/openmc/data/library.py +++ b/openmc/data/library.py @@ -23,9 +23,14 @@ class DataLibrary(EqualityMixin): def __init__(self): self.libraries = [] - def get_by_material(self, value): + def get_by_material(self, name): """Return the library dictionary containing a given material. + Parameters + ---------- + name : str + Name of material, e.g. 'Am241' + Returns ------- library : dict or None @@ -34,7 +39,7 @@ class DataLibrary(EqualityMixin): """ for library in self.libraries: - if value in library['materials']: + if name in library['materials']: return library return None From 932bb7157e6871a71a841384d278e70fc5444a7b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 13 Apr 2018 16:05:46 -0500 Subject: [PATCH 171/361] Fix file format documentation for settings.xml --- docs/source/io_formats/settings.rst | 87 ++++++++++++++--------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 0aa058cdb..d2cb3b125 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -93,29 +93,13 @@ or ``multi-group``. *Default*: continuous-energy ---------------------- -```` Element ---------------------- +-------------------------- +```` Element +-------------------------- -The ```` element describes a mesh that is used for calculating Shannon -entropy. This mesh should cover all possible fissionable materials in the -problem. It has the following attributes/sub-elements: - - :dimension: - The number of mesh cells in the x, y, and z directions, respectively. - - *Default*: If this tag is not present, the number of mesh cells is - automatically determined by the code. - - :lower_left: - The Cartesian coordinates of the lower-left corner of the mesh. - - *Default*: None - - :upper_right: - The Cartesian coordinates of the upper-right corner of the mesh. - - *Default*: None +The ```` element indicates the ID of a mesh that is to be used for +calculating Shannon entropy. The mesh should cover all possible fissionable +materials in the problem and is specified using a :ref:`mesh_element`. ----------------------------------- ```` Element @@ -199,6 +183,36 @@ then, OpenMC will only use up to the :math:`P_1` data. .. note:: This element is not used in the continuous-energy :ref:`energy_mode`. +.. _mesh_element: + +------------------ +```` Element +------------------ + +The ```` element describes a mesh that is used either for calculating +Shannon entropy, applying the uniform fission site method, or in tallies. For +Shannon entropy meshes, the mesh should cover all possible fissionable materials +in the problem. It has the following attributes/sub-elements: + + :id: + A unique integer that is used to identify the mesh. + + :dimension: + The number of mesh cells in the x, y, and z directions, respectively. + + *Default*: If this tag is not present, the number of mesh cells is + automatically determined by the code. + + :lower_left: + The Cartesian coordinates of the lower-left corner of the mesh. + + *Default*: None + + :upper_right: + The Cartesian coordinates of the upper-right corner of the mesh. + + *Default*: None + ----------------------- ```` Element ----------------------- @@ -765,30 +779,15 @@ has the following attributes/sub-elements: ------------------------ -```` Element +```` Element ------------------------ -The ```` element describes a mesh that is used for re-weighting -source sites at every generation based on the uniform fission site methodology -described in Kelly et al., "MC21 Analysis of the Nuclear Energy Agency Monte -Carlo Performance Benchmark Problem," Proceedings of *Physor 2012*, Knoxville, -TN (2012). This mesh should cover all possible fissionable materials in the -problem. It has the following attributes/sub-elements: - - :dimension: - The number of mesh cells in the x, y, and z directions, respectively. - - *Default*: None - - :lower_left: - The Cartesian coordinates of the lower-left corner of the mesh. - - *Default*: None - - :upper_right: - The Cartesian coordinates of the upper-right corner of the mesh. - - *Default*: None +The ```` element indicates the ID of a mesh that is used for +re-weighting source sites at every generation based on the uniform fission site +methodology described in Kelly et al., "MC21 Analysis of the Nuclear Energy +Agency Monte Carlo Performance Benchmark Problem," Proceedings of *Physor 2012*, +Knoxville, TN (2012). The mesh should cover all possible fissionable materials +in the problem and is specified using a :ref:`mesh_element`. .. _verbosity: From 4a64e2a93139574a0d913b9b4c8266264a15bea2 Mon Sep 17 00:00:00 2001 From: amandalund Date: Sat, 14 Apr 2018 14:10:35 -0500 Subject: [PATCH 172/361] Address #991 comments; add comments; add stopping condition and warning on early convergence --- openmc/model/triso.py | 150 ++++++++++++++++----------- tests/unit_tests/test_model_triso.py | 22 ++-- 2 files changed, 97 insertions(+), 75 deletions(-) diff --git a/openmc/model/triso.py b/openmc/model/triso.py index 7040d02ca..1e4b4aa9b 100644 --- a/openmc/model/triso.py +++ b/openmc/model/triso.py @@ -305,14 +305,14 @@ class _CubicDomain(_Domain): uniform(-x_max, x_max)] def repel_particles(self, p, q, d, d_new): - # Moving each particle distance 'r' away from the other along the line + # Moving each particle distance 's' away from the other along the line # joining the particle centers will ensure their final distance is # equal to the outer diameter - r = (d_new - d)/2 + s = (d_new - d)/2 v = (p - q)/d - p += r*v - q -= r*v + p += s*v + q -= s*v # Enforce the rigid boundary by moving each particle back along the # surface normal until it is completely within the container if it @@ -413,14 +413,14 @@ class _CylindricalDomain(_Domain): return [r*cos(t), r*sin(t), uniform(-z_max, z_max)] def repel_particles(self, p, q, d, d_new): - # Moving each particle distance 'r' away from the other along the line + # Moving each particle distance 's' away from the other along the line # joining the particle centers will ensure their final distance is # equal to the outer diameter - r = (d_new - d)/2 + s = (d_new - d)/2 v = (p - q)/d - p += r*v - q -= r*v + p += s*v + q -= s*v # Enforce the rigid boundary by moving each particle back along the # surface normal until it is completely within the container if it @@ -428,14 +428,14 @@ class _CylindricalDomain(_Domain): r_max = self.limits[0] z_max = self.limits[1] - d = sqrt(p[0]**2 + p[1]**2) - if d > r_max: - p[0:2] *= r_max/d + r = sqrt(p[0]**2 + p[1]**2) + if r > r_max: + p[0:2] *= r_max/r p[2] = np.clip(p[2], -z_max, z_max) - d = sqrt(q[0]**2 + q[1]**2) - if d > r_max: - q[0:2] *= r_max/d + r = sqrt(q[0]**2 + q[1]**2) + if r > r_max: + q[0:2] *= r_max/r q[2] = np.clip(q[2], -z_max, z_max) @@ -512,27 +512,27 @@ class _SphericalDomain(_Domain): return [r*s for s in x] def repel_particles(self, p, q, d, d_new): - # Moving each particle distance 'r' away from the other along the line + # Moving each particle distance 's' away from the other along the line # joining the particle centers will ensure their final distance is # equal to the outer diameter - r = (d_new - d)/2 + s = (d_new - d)/2 v = (p - q)/d - p += r*v - q -= r*v + p += s*v + q -= s*v # Enforce the rigid boundary by moving each particle back along the # surface normal until it is completely within the container if it # overlaps the surface r_max = self.limits[0] - d = sqrt(p[0]**2 + p[1]**2 + p[2]**2) - if d > r_max: - p *= r_max/d + r = sqrt(p[0]**2 + p[1]**2 + p[2]**2) + if r > r_max: + p *= r_max/r - d = sqrt(q[0]**2 + q[1]**2 + q[2]**2) - if d > r_max: - q *= r_max/d + r = sqrt(q[0]**2 + q[1]**2 + q[2]**2) + if r > r_max: + q *= r_max/r def create_triso_lattice(trisos, lower_left, pitch, shape, background): @@ -712,6 +712,7 @@ def _close_random_pack(domain, particles, contraction_rate): del rods_map[i] del rods_map[j] return d, i, j + return None, None, None def create_rod_list(): """Generate sorted list of rods (distances between particle centers). @@ -736,8 +737,8 @@ def _close_random_pack(domain, particles, contraction_rate): # Find distance to nearest neighbor and index of nearest neighbor for # all particles d, n = tree.query(particles, k=2) - d = d[:,1] - n = n[:,1] + d = d[:, 1] + n = n[:, 1] # Array of particle indices, indices of nearest neighbors, and # distances to nearest neighbors @@ -746,8 +747,8 @@ def _close_random_pack(domain, particles, contraction_rate): # Sort along second column and swap first and second columns to create # array of nearest neighbor indices, indices of particles they are # nearest neighbors of, and distances between them - b = a[a[:,1].argsort()] - b[:,[0, 1]] = b[:,[1, 0]] + b = a[a[:, 1].argsort()] + b[:, [0, 1]] = b[:, [1, 0]] # Find the intersection between 'a' and 'b': a list of particles who # are each other's nearest neighbors and the distance between them @@ -761,10 +762,11 @@ def _close_random_pack(domain, particles, contraction_rate): del rods[:] rods_map.clear() for d, i, j in r: - add_rod(d, i, j) + if d < outer_diameter and not np.isclose(d, outer_diameter, atol=1.0e-14): + add_rod(d, i, j) - def update_mesh(indices): - """Update which mesh cells the particles are in based on new particle + def update_mesh(i): + """Update which mesh cells the particle is in based on new particle center coordinates. 'mesh'/'mesh_map' is a two way dictionary used to look up which @@ -774,23 +776,22 @@ def _close_random_pack(domain, particles, contraction_rate): Parameters ---------- - indices : List of int - Indices of particles in particles array. + i : int + Index of particle in particles array. """ - for i in indices: - # Determine which mesh cells the particle is in and remove the - # particle id from those cells - for idx in mesh_map[i]: - mesh[idx].remove(i) - del mesh_map[i] + # Determine which mesh cells the particle is in and remove the + # particle id from those cells + for idx in mesh_map[i]: + mesh[idx].remove(i) + del mesh_map[i] - # Determine which mesh cells are within one diameter of particle's - # center and add this particle to the list of particles in those cells - for idx in domain.nearby_mesh_cells(particles[i]): - mesh[idx].add(i) - mesh_map[i].add(idx) + # Determine which mesh cells are within one diameter of particle's + # center and add this particle to the list of particles in those cells + for idx in domain.nearby_mesh_cells(particles[i]): + mesh[idx].add(i) + mesh_map[i].add(idx) def reduce_outer_diameter(): """Reduce the outer diameter so that at the (i+1)-st iteration it is: @@ -844,25 +845,25 @@ def _close_random_pack(domain, particles, contraction_rate): else: return None, None - def update_rod_list(indices): - """Update the rod list with the new nearest neighbors of particles in - list since their overlap was eliminated. + def update_rod_list(i): + """Update the rod list with the new nearest neighbors of particle since + its overlap was eliminated. Parameters ---------- - indices : List of int - Indices of particles in particles array. + i : int + Index of particle in particles array. """ # If the nearest neighbor k of particle i has no nearer neighbors, # remove the rod currently containing k from the rod list and add rod # k-i, keeping the rod list sorted - for i in indices: - k, d_ik = nearest(i) - if k and nearest(k)[0] == i: - remove_rod(k) - add_rod(d_ik, i, k) + k, d_ik = nearest(i) + if (k and nearest(k)[0] == i and d_ik < outer_diameter + and not np.isclose(d, outer_diameter, atol=1.0e-14)): + remove_rod(k) + add_rod(d_ik, i, k) n_particles = len(particles) diameter = 2*domain.particle_radius @@ -877,41 +878,68 @@ def _close_random_pack(domain, particles, contraction_rate): outer_diameter = initial_outer_diameter inner_diameter = 0. + # List of rods arranged in a heap and mapping of particle ids to rods rods = [] rods_map = {} + + # Initialize two-way dictionary that identifies which particles are near a + # given mesh cell and which mesh cells a particle is near mesh = defaultdict(set) mesh_map = defaultdict(set) - for i in range(n_particles): for idx in domain.nearby_mesh_cells(particles[i]): mesh[idx].add(i) mesh_map[i].add(idx) while True: + # Rebuild the sorted list of rods according to the current particle + # configuration create_rod_list() + + # Set the inner diameter to the shortest center-to-center distance + # between any two particles if rods: - # Set inner diameter to the shortest center-to-center distance - # between any two particles inner_diameter = rods[0][0] + + # Reached the desired particle radius if inner_diameter >= diameter: break + + # The algorithm converged before reaching the desired particle radius. + # This can happen when the desired packing fraction is close to the + # packing fraction limit. The packing fraction is a random variable + # that is determined by the particle locations and the contraction + # rate. A higher packing fraction can be achieved with a smaller + # contraction rate, though at the cost of a longer simulation time -- + # the number of iterations needed to remove all overlaps is inversely + # proportional to the contraction rate. + if inner_diameter >= outer_diameter or not rods: + warnings.warn('Close random pack converged before reaching true ' + 'particle radius; some particles may overlap. Try ' + 'reducing contraction rate or packing fraction.') + break + while True: d, i, j = pop_rod() + if not d: + break outer_diameter = reduce_outer_diameter() domain.repel_particles(particles[i], particles[j], d, outer_diameter) - update_mesh([i, j]) - update_rod_list([i, j]) + update_mesh(i) + update_mesh(j) + update_rod_list(i) + update_rod_list(j) if not rods: break inner_diameter = rods[0][0] - if inner_diameter >= diameter: + if inner_diameter >= diameter or inner_diameter >= outer_diameter: break def pack_trisos(radius, fill, domain_shape='cylinder', domain_length=None, domain_radius=None, domain_center=[0., 0., 0.], n_particles=None, packing_fraction=None, - initial_packing_fraction=0.3, contraction_rate=1/400, seed=1): + initial_packing_fraction=0.3, contraction_rate=1.e-3, seed=1): """Generate a random, non-overlapping configuration of TRISO particles within a container. diff --git a/tests/unit_tests/test_model_triso.py b/tests/unit_tests/test_model_triso.py index bfd7319d2..19c4f9081 100644 --- a/tests/unit_tests/test_model_triso.py +++ b/tests/unit_tests/test_model_triso.py @@ -12,21 +12,15 @@ import scipy.spatial _PACKING_FRACTION = 0.35 _RADIUS = 4.25e-2 +domain_params = [ + {'shape': 'cube', 'length': 0.75, 'radius': 0., 'volume': 0.75**3}, + {'shape': 'cylinder', 'length': 0.5, 'radius': 0.5, 'volume': 0.5*pi*0.5**2}, + {'shape': 'sphere', 'length': 0., 'radius': 0.5, 'volume': 4/3*pi*0.5**3} +] -@pytest.fixture(scope='module', - params=[{'shape': 'cube', - 'length': 0.75, - 'radius': 0., - 'volume': 0.75**3}, - {'shape': 'cylinder', - 'length': 0.5, - 'radius': 0.5, - 'volume': 0.5*pi*0.5**2}, - {'shape': 'sphere', - 'length': 0., - 'radius': 0.5, - 'volume': 4/3*pi*0.5**3}]) +@pytest.fixture(scope='module', params=domain_params, + ids=['cube', 'cylinder', 'sphere']) def domain(request): return request.param @@ -65,7 +59,7 @@ def test_overlap(trisos): d = tree.query(centers, k=2)[0] # Get the smallest distance between any two particles - d_min = min(d[:,1]) + d_min = min(d[:, 1]) assert d_min > 2*_RADIUS or d_min == pytest.approx(2*_RADIUS) From fccb26735ca1b2621aac8af2cb55b64d71527480 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 17 Apr 2018 14:16:43 -0500 Subject: [PATCH 173/361] Add missing prototypes in openmc.h --- include/openmc.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/openmc.h b/include/openmc.h index d24ca4af9..083788120 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -27,6 +27,7 @@ extern "C" { int openmc_extend_cells(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_extend_filters(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_extend_materials(int32_t n, int32_t* index_start, int32_t* index_end); + int openmc_extend_meshes(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_extend_sources(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_extend_tallies(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_filter_get_id(int32_t index, int32_t* id); @@ -40,7 +41,9 @@ extern "C" { 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_mesh_index(int32_t id, int32_t* index); int openmc_get_nuclide_index(const char name[], int* index); + int64_t openmc_get_seed(); int openmc_get_tally_index(int32_t id, int32_t* index); void openmc_hard_reset(); void openmc_init(const int* intracomm); @@ -70,6 +73,7 @@ extern "C" { void openmc_plot_geometry(); void openmc_reset(); int openmc_run(); + void openmc_set_seed(int64_t new_seed); void openmc_simulation_finalize(); void openmc_simulation_init(); int openmc_source_bank(struct Bank** ptr, int64_t* n); From 8886313c939da0784ca132883b233cd2335dcc24 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Apr 2018 07:17:02 -0500 Subject: [PATCH 174/361] Add description of openmc.capi.Mesh attributes in docstring --- openmc/capi/mesh.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openmc/capi/mesh.py b/openmc/capi/mesh.py index 326f5f61e..091c5194b 100644 --- a/openmc/capi/mesh.py +++ b/openmc/capi/mesh.py @@ -59,6 +59,16 @@ class Mesh(_FortranObjectWithID): ---------- id : int ID of the mesh + dimension : iterable of int + The number of mesh cells in each direction. + lower_left : numpy.ndarray + The lower-left corner of the structured mesh. If only two coordinate are + given, it is assumed that the mesh is an x-y mesh. + upper_right : numpy.ndarray + The upper-right corner of the structrued mesh. If only two coordinate + are given, it is assumed that the mesh is an x-y mesh. + width : numpy.ndarray + The width of mesh cells in each direction. """ __instances = WeakValueDictionary() From 39554e4614fcdddc7411dbdabc5dac5bf7cf924f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Apr 2018 15:54:04 -0500 Subject: [PATCH 175/361] Allow tallies to be activated from C API (mostly for CMFD) --- include/openmc.h | 2 ++ openmc/capi/tally.py | 18 +++++++++++++++- src/tallies/tally_header.F90 | 39 +++++++++++++++++++++++++++++++++++ tests/unit_tests/test_capi.py | 6 +++++- 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/include/openmc.h b/include/openmc.h index 083788120..ed3a86054 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -88,12 +88,14 @@ extern "C" { int openmc_sphharm_filter_set_order(int32_t index, int order); int openmc_sphharm_filter_set_cosine(int32_t index, const char cosine[]); void openmc_statepoint_write(const char filename[]); + int openmc_tally_get_active(int32_t index, bool* active); int openmc_tally_get_id(int32_t index, int32_t* id); int openmc_tally_get_filters(int32_t index, int32_t** indices, int* n); int openmc_tally_get_n_realizations(int32_t index, int32_t* n); int openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n); int openmc_tally_get_scores(int32_t index, int** scores, int* n); int openmc_tally_results(int32_t index, double** ptr, int shape_[3]); + int openmc_tally_set_active(int32_t index, bool active); int openmc_tally_set_filters(int32_t index, int n, const int32_t* indices); int openmc_tally_set_id(int32_t index, int32_t id); int openmc_tally_set_nuclides(int32_t index, int n, const char** nuclides); diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index 46a967f69..a10fae47a 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -1,5 +1,5 @@ from collections.abc import Mapping -from ctypes import c_int, c_int32, c_double, c_char_p, POINTER +from ctypes import c_int, c_int32, c_double, c_char_p, c_bool, POINTER from weakref import WeakValueDictionary import numpy as np @@ -26,6 +26,9 @@ _dll.openmc_get_tally_index.errcheck = _error_handler _dll.openmc_global_tallies.argtypes = [POINTER(POINTER(c_double))] _dll.openmc_global_tallies.restype = c_int _dll.openmc_global_tallies.errcheck = _error_handler +_dll.openmc_tally_get_active.argtypes = [c_int32, POINTER(c_bool)] +_dll.openmc_tally_get_active.restype = c_int +_dll.openmc_tally_get_active.errcheck = _error_handler _dll.openmc_tally_get_id.argtypes = [c_int32, POINTER(c_int32)] _dll.openmc_tally_get_id.restype = c_int _dll.openmc_tally_get_id.errcheck = _error_handler @@ -48,6 +51,9 @@ _dll.openmc_tally_results.argtypes = [ c_int32, POINTER(POINTER(c_double)), POINTER(c_int*3)] _dll.openmc_tally_results.restype = c_int _dll.openmc_tally_results.errcheck = _error_handler +_dll.openmc_tally_set_active.argtypes = [c_int32, c_bool] +_dll.openmc_tally_set_active.restype = c_int +_dll.openmc_tally_set_active.errcheck = _error_handler _dll.openmc_tally_set_filters.argtypes = [c_int32, c_int, POINTER(c_int32)] _dll.openmc_tally_set_filters.restype = c_int _dll.openmc_tally_set_filters.errcheck = _error_handler @@ -178,6 +184,16 @@ class Tally(_FortranObjectWithID): return cls.__instances[index] + @property + def active(self): + active = c_bool() + _dll.openmc_tally_get_active(self._index, active) + return active.value + + @active.setter + def active(self, active): + _dll.openmc_tally_set_active(self._index, active) + @property def id(self): tally_id = c_int32() diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 02a578d7e..413464b7b 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -24,12 +24,14 @@ module tally_header public :: openmc_extend_tallies public :: openmc_get_tally_index public :: openmc_global_tallies + public :: openmc_tally_get_active 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 + public :: openmc_tally_set_active public :: openmc_tally_set_filters public :: openmc_tally_set_id public :: openmc_tally_set_nuclides @@ -512,6 +514,22 @@ contains end function openmc_global_tallies + function openmc_tally_get_active(index, active) result(err) bind(C) + ! Return whether a tally is active + integer(C_INT32_T), value :: index + logical(C_BOOL), intent(out) :: active + integer(C_INT) :: err + + if (index >= 1 .and. index <= size(tallies)) then + active = tallies(index) % obj % active + 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_active + + function openmc_tally_get_id(index, id) result(err) bind(C) ! Return the ID of a tally integer(C_INT32_T), value :: index @@ -667,6 +685,27 @@ contains end function openmc_tally_set_filters + function openmc_tally_set_active(index, active) result(err) bind(C) + ! Set the ID of a tally + integer(C_INT32_T), value, intent(in) :: index + logical(C_BOOL), value, intent(in) :: active + integer(C_INT) :: err + + if (index >= 1 .and. index <= n_tallies) then + if (allocated(tallies(index) % obj)) then + tallies(index) % obj % active = active + err = 0 + else + err = E_ALLOCATE + call set_errmsg("Tally type has not been set yet.") + end if + else + err = E_OUT_OF_BOUNDS + call set_errmsg('Index in tallies array is out of bounds.') + end if + end function openmc_tally_set_active + + function openmc_tally_set_id(index, id) result(err) bind(C) ! Set the ID of a tally integer(C_INT32_T), value, intent(in) :: index diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 6c05f5d3e..a21e858fd 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -164,6 +164,10 @@ def test_tally(capi_init): t.scores = new_scores assert t.scores == new_scores + assert not t.active + t.active = True + assert t.active + def test_new_tally(capi_init): with pytest.raises(exc.AllocationError): @@ -177,7 +181,7 @@ def test_new_tally(capi_init): def test_tally_results(capi_run): t = openmc.capi.tallies[1] - assert t.num_realizations == 5 + assert t.num_realizations == 10 # t was made active in test_tally assert np.all(t.mean >= 0) nonzero = (t.mean > 0.0) assert np.all(t.std_dev[nonzero] >= 0) From 14455dc43ee623cf6072e33349c90edfe46d0100 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 12 Apr 2018 07:19:25 -0500 Subject: [PATCH 176/361] Write main() from C++ --- CMakeLists.txt | 6 +++-- include/openmc.h | 19 +++++++++++---- openmc/capi/core.py | 5 ++-- openmc/capi/settings.py | 6 ++--- src/main.F90 | 51 --------------------------------------- src/main.cpp | 40 ++++++++++++++++++++++++++++++ src/message_passing.F90 | 4 ++- src/nuclide_header.F90 | 2 +- src/particle_restart.F90 | 11 ++++++--- src/settings.F90 | 4 +-- src/simulation_header.F90 | 7 +++--- 11 files changed, 81 insertions(+), 74 deletions(-) delete mode 100644 src/main.F90 create mode 100644 src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1797ae5d3..81c16aa48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -447,7 +447,9 @@ set(LIBOPENMC_CXX_SRC 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) + +add_executable(${program} src/main.cpp) +target_include_directories(${program} PUBLIC include) #=============================================================================== # Add compiler/linker flags @@ -460,7 +462,7 @@ target_include_directories(libopenmc PUBLIC ${HDF5_INCLUDE_DIRS}) # The executable and the faddeeva package use only one language. They can be # set via target_compile_options which accepts a list. -target_compile_options(${program} PUBLIC ${f90flags}) +target_compile_options(${program} PUBLIC ${cxxflags}) target_compile_options(faddeeva PRIVATE ${cflags}) # The libopenmc library has both F90 and C++ so the compile flags must be set diff --git a/include/openmc.h b/include/openmc.h index ed3a86054..194545345 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -70,6 +70,7 @@ extern "C" { int openmc_meshsurface_filter_set_mesh(int32_t index, int32_t index_mesh); int openmc_next_batch(int* status); int openmc_nuclide_name(int index, char** name); + int openmc_particle_restart(); void openmc_plot_geometry(); void openmc_reset(); int openmc_run(); @@ -122,8 +123,9 @@ extern "C" { // Global variables extern char openmc_err_msg[256]; - extern double keff; - extern double keff_std; + extern double openmc_keff; + extern double openmc_keff_std; + extern bool openmc_master; extern int32_t n_batches; extern int32_t n_cells; extern int32_t n_filters; @@ -139,9 +141,16 @@ extern "C" { extern int32_t n_surfaces; extern int32_t n_tallies; extern int32_t n_universes; - extern int run_mode; - extern bool simulation_initialized; - extern int verbosity; + extern int openmc_run_mode; + extern bool openmc_simulation_initialized; + extern int openmc_verbosity; + + // Run modes + constexpr int RUN_MODE_FIXEDSOURCE {1}; + constexpr int RUN_MODE_EIGENVALUE {2}; + constexpr int RUN_MODE_PLOTTING {3}; + constexpr int RUN_MODE_PARTICLE {4}; + constexpr int RUN_MODE_VOLUME {5}; #ifdef __cplusplus } diff --git a/openmc/capi/core.py b/openmc/capi/core.py index f80b5fe54..d35690503 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -178,8 +178,9 @@ def keff(): return tuple(k) else: # Otherwise, return the tracklength estimator - mean = c_double.in_dll(_dll, 'keff').value - std_dev = c_double.in_dll(_dll, 'keff_std').value if n > 1 else np.inf + mean = c_double.in_dll(_dll, 'openmc_keff').value + std_dev = c_double.in_dll(_dll, 'openmc_keff_std').value \ + if n > 1 else np.inf return (mean, std_dev) diff --git a/openmc/capi/settings.py b/openmc/capi/settings.py index 1063d6463..d706112c4 100644 --- a/openmc/capi/settings.py +++ b/openmc/capi/settings.py @@ -20,11 +20,11 @@ class _Settings(object): generations_per_batch = _DLLGlobal(c_int32, 'gen_per_batch') inactive = _DLLGlobal(c_int32, 'n_inactive') particles = _DLLGlobal(c_int64, 'n_particles') - verbosity = _DLLGlobal(c_int, 'verbosity') + verbosity = _DLLGlobal(c_int, 'openmc_verbosity') @property def run_mode(self): - i = c_int.in_dll(_dll, 'run_mode').value + i = c_int.in_dll(_dll, 'openmc_run_mode').value try: return _RUN_MODES[i] except KeyError: @@ -32,7 +32,7 @@ class _Settings(object): @run_mode.setter def run_mode(self, mode): - current_idx = c_int.in_dll(_dll, 'run_mode') + current_idx = c_int.in_dll(_dll, 'openmc_run_mode') for idx, mode_value in _RUN_MODES.items(): if mode_value == mode: current_idx.value = idx diff --git a/src/main.F90 b/src/main.F90 deleted file mode 100644 index 372e993f5..000000000 --- a/src/main.F90 +++ /dev/null @@ -1,51 +0,0 @@ -program main - - use, intrinsic :: ISO_C_BINDING - - use constants - use message_passing - use openmc_api, only: openmc_init, openmc_finalize, openmc_run, & - openmc_plot_geometry, openmc_calculate_volumes - use particle_restart, only: run_particle_restart - use settings, only: run_mode - - implicit none - - integer(C_INT) :: err -#ifdef OPENMC_MPI - integer :: mpi_err ! MPI error code -#endif - - ! Initialize run -- when run with MPI, pass communicator -#ifdef OPENMC_MPI -#ifdef OPENMC_MPIF08 - call openmc_init(MPI_COMM_WORLD % MPI_VAL) -#else - call openmc_init(MPI_COMM_WORLD) -#endif -#else - call openmc_init() -#endif - - ! start problem based on mode - select case (run_mode) - case (MODE_FIXEDSOURCE, MODE_EIGENVALUE) - err = openmc_run() - case (MODE_PLOTTING) - call openmc_plot_geometry() - case (MODE_PARTICLE) - if (master) call run_particle_restart() - case (MODE_VOLUME) - call openmc_calculate_volumes() - end select - - ! finalize run - call openmc_finalize() - -#ifdef OPENMC_MPI - ! If MPI is in use and enabled, terminate it - call MPI_FINALIZE(mpi_err) -#endif - - -end program main diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 000000000..7b2ccb5c3 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,40 @@ +#include "openmc.h" + +#ifdef MPI +#include +#else +#define MPI_COMM_WORLD nullptr +#endif + + +int main(int argc, char** argv) { + int err; + + // Initialize run -- when run with MPI, pass communicator + openmc_init(MPI_COMM_WORLD); + + // start problem based on mode + switch (openmc_run_mode) { + case RUN_MODE_FIXEDSOURCE: + case RUN_MODE_EIGENVALUE: + err = openmc_run(); + break; + case RUN_MODE_PLOTTING: + openmc_plot_geometry(); + break; + case RUN_MODE_PARTICLE: + if (openmc_master) err = openmc_particle_restart(); + break; + case RUN_MODE_VOLUME: + openmc_calculate_volumes(); + break; + } + + // Finalize and free up memory + openmc_finalize(); + + // If MPI is in use and enabled, terminate it +#ifdef MPI + err = MPI_Finalize(); +#endif +} diff --git a/src/message_passing.F90 b/src/message_passing.F90 index 7391a632c..f9a16ee75 100644 --- a/src/message_passing.F90 +++ b/src/message_passing.F90 @@ -1,5 +1,7 @@ module message_passing + use, intrinsic :: ISO_C_BINDING + #ifdef OPENMC_MPI #ifdef OPENMC_MPIF08 use mpi_f08 @@ -14,7 +16,7 @@ module message_passing integer :: n_procs = 1 ! number of processes integer :: rank = 0 ! rank of process - logical :: master = .true. ! master process? + logical(C_BOOL), bind(C, name='openmc_master') :: master = .true. ! master process? logical :: mpi_enabled = .false. ! is MPI in use and initialized? #ifdef OPENMC_MPIF08 type(MPI_Datatype) :: MPI_BANK ! MPI datatype for fission bank diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 8362f5d41..76fc26a74 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -277,7 +277,7 @@ contains integer, intent(inout) :: method real(8), intent(in) :: tolerance real(8), intent(in) :: minmax(2) ! range of temperatures - logical, intent(in) :: master ! if this is the master proc + logical(C_BOOL), intent(in) :: master ! if this is the master proc integer, intent(in) :: i_nuclide ! Nuclide index in nuclides integer :: i diff --git a/src/particle_restart.F90 b/src/particle_restart.F90 index 2d15f2ddc..fc8a05277 100644 --- a/src/particle_restart.F90 +++ b/src/particle_restart.F90 @@ -20,20 +20,23 @@ module particle_restart implicit none private - public :: run_particle_restart + public :: openmc_particle_restart contains !=============================================================================== -! RUN_PARTICLE_RESTART is the main routine that runs the particle restart +! OPENMC_PARTICLE_RESTART is the main routine that runs the particle restart !=============================================================================== - subroutine run_particle_restart() + function openmc_particle_restart() result(err) bind(C) + integer(C_INT) :: err integer(8) :: particle_seed integer :: previous_run_mode type(Particle) :: p + err = 0 + ! Set verbosity high verbosity = 10 @@ -66,7 +69,7 @@ contains deallocate(micro_xs) - end subroutine run_particle_restart + end function openmc_particle_restart !=============================================================================== ! READ_PARTICLE_RESTART reads the particle restart file diff --git a/src/settings.F90 b/src/settings.F90 index 83aebce86..4de72e8c4 100644 --- a/src/settings.F90 +++ b/src/settings.F90 @@ -75,14 +75,14 @@ module settings real(8) :: weight_survive = ONE ! Mode to run in (fixed source, eigenvalue, plotting, etc) - integer(C_INT), bind(C) :: run_mode = NONE + integer(C_INT), bind(C, name='openmc_run_mode') :: run_mode = NONE ! Restart run logical :: restart_run = .false. ! The verbosity controls how much information will be printed to the screen ! and in logs - integer(C_INT), bind(C) :: verbosity = 7 + integer(C_INT), bind(C, name='openmc_verbosity') :: verbosity = 7 logical :: check_overlaps = .false. diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index 11be9bf9c..67bf3062b 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -23,7 +23,8 @@ module simulation_header 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(C_BOOL), bind(C, name='openmc_simulation_initialized') :: & + simulation_initialized = .false. logical :: need_depletion_rx ! need to calculate depletion reaction rx? ! ============================================================================ @@ -40,8 +41,8 @@ module simulation_header ! 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 - real(C_DOUBLE), bind(C) :: keff_std ! standard deviation of average k + real(C_DOUBLE), bind(C, name='openmc_keff') :: keff = ONE ! average k over active batches + real(C_DOUBLE), bind(C, name='openmc_keff_std') :: 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 faf35d21bcd4b7e8163aa261e5a44ae948e25748 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 12 Apr 2018 07:45:47 -0500 Subject: [PATCH 177/361] Move openmc_run to C++ side --- CMakeLists.txt | 5 ++--- src/api.F90 | 1 - src/simulation.F90 | 20 -------------------- src/simulation.cpp | 18 ++++++++++++++++++ 4 files changed, 20 insertions(+), 24 deletions(-) create mode 100644 src/simulation.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 81c16aa48..d246cf57c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -440,6 +440,7 @@ set(LIBOPENMC_CXX_SRC src/hdf5_interface.h src/random_lcg.cpp src/random_lcg.h + src/simulation.cpp src/surface.cpp src/surface.h src/xml_interface.h @@ -447,9 +448,7 @@ set(LIBOPENMC_CXX_SRC 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.cpp) -target_include_directories(${program} PUBLIC include) #=============================================================================== # Add compiler/linker flags @@ -458,7 +457,7 @@ target_include_directories(${program} PUBLIC include) set_property(TARGET ${program} libopenmc pugixml_fortran PROPERTY LINKER_LANGUAGE Fortran) -target_include_directories(libopenmc PUBLIC ${HDF5_INCLUDE_DIRS}) +target_include_directories(libopenmc PUBLIC include ${HDF5_INCLUDE_DIRS}) # The executable and the faddeeva package use only one language. They can be # set via target_compile_options which accepts a list. diff --git a/src/api.F90 b/src/api.F90 index df183c292..d7f3ed023 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -80,7 +80,6 @@ module openmc_api public :: openmc_nuclide_name public :: openmc_plot_geometry public :: openmc_reset - public :: openmc_run public :: openmc_set_seed public :: openmc_simulation_finalize public :: openmc_simulation_init diff --git a/src/simulation.F90 b/src/simulation.F90 index 68debe52b..e9729df03 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -44,7 +44,6 @@ module simulation implicit none private public :: openmc_next_batch - public :: openmc_run public :: openmc_simulation_init public :: openmc_simulation_finalize @@ -54,25 +53,6 @@ module simulation contains -!=============================================================================== -! OPENMC_RUN encompasses all the main logic where iterations are performed -! over the batches, generations, and histories in a fixed source or k-eigenvalue -! calculation. -!=============================================================================== - - function openmc_run() result(err) bind(C) - integer(C_INT) :: err - integer(C_INT) :: status - - call openmc_simulation_init() - do - err = openmc_next_batch(status) - if (status /= 0 .or. err < 0) exit - end do - call openmc_simulation_finalize() - - end function openmc_run - !=============================================================================== ! OPENMC_NEXT_BATCH !=============================================================================== diff --git a/src/simulation.cpp b/src/simulation.cpp new file mode 100644 index 000000000..e9bf81775 --- /dev/null +++ b/src/simulation.cpp @@ -0,0 +1,18 @@ +#include "openmc.h" + +// OPENMC_RUN encompasses all the main logic where iterations are performed +// over the batches, generations, and histories in a fixed source or k-eigenvalue +// calculation. + +int openmc_run() { + openmc_simulation_init(); + + int err = 0; + int status = 0; + while (status == 0 && err == 0) { + err = openmc_next_batch(&status); + } + + openmc_simulation_finalize(); + return err; +} From 05e40a142feae458490704cbf132928919f256de Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 13 Apr 2018 15:03:16 -0500 Subject: [PATCH 178/361] Get rid of include_directories command in CMakeLists.txt --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d246cf57c..ca3d83717 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,9 +10,6 @@ set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/include) # Set module path set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules) -# Make sure Fortran module directory is included when building -include_directories(${CMAKE_BINARY_DIR}/include) - #=============================================================================== # Architecture specific definitions #=============================================================================== @@ -457,7 +454,10 @@ add_executable(${program} src/main.cpp) set_property(TARGET ${program} libopenmc pugixml_fortran PROPERTY LINKER_LANGUAGE Fortran) -target_include_directories(libopenmc PUBLIC include ${HDF5_INCLUDE_DIRS}) +target_include_directories(libopenmc + PUBLIC include ${HDF5_INCLUDE_DIRS} + PRIVATE ${CMAKE_BINARY_DIR}/include + ) # The executable and the faddeeva package use only one language. They can be # set via target_compile_options which accepts a list. From b0c7f07d02cce03c89ff2b07bd52c2162cfda37e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 16 Apr 2018 15:58:03 -0500 Subject: [PATCH 179/361] Start writing more HDF5 interface functions in C++ --- src/hdf5_interface.h | 158 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 148 insertions(+), 10 deletions(-) diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 7ec9ada9b..8a445b21e 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -1,44 +1,153 @@ #ifndef HDF5_INTERFACE_H #define HDF5_INTERFACE_H -#include -#include +#include "error.h" #include "hdf5.h" +#include "hdf5_hl.h" -#include "error.h" +#include +#include +#include namespace openmc { +bool +using_mpio_device(hid_t obj_id) { + // Determine file that this object is part of + hid_t file_id = H5Iget_file_id(obj_id); + + // Get file access property list + hid_t fapl_id = H5Fget_access_plist(file_id); + + // Get low-level driver identifier + hid_t driver = H5Pget_driver(fapl_id); + + // Free resources + H5Pclose(fapl_id); + H5Fclose(file_id); + + return driver == H5FD_MPIO; +} + 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 += "\""; + std::stringstream err_msg; + err_msg << "Failed to create HDF5 group \"" << name << "\""; 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_dataset(hid_t dataset_id) +{ + if (H5Dclose(dataset_id) < 0) fatal_error("Failed to close dataset"); +} + void close_group(hid_t group_id) { - herr_t err = H5Gclose(group_id); - if (err < 0) { - fatal_error("Failed to close HDF5 group"); + if (H5Gclose(group_id) < 0) fatal_error("Failed to close group"); +} + + +hid_t +file_open(const char* filename, char mode, bool parallel=false) +{ + bool create; + unsigned int flags; + switch (mode) { + case 'r': + case 'a': + create = false; + flags = (mode == 'r' ? H5F_ACC_RDONLY : H5F_ACC_RDWR); + break; + case 'w': + case 'x': + create = true; + flags = (mode == 'x' ? H5F_ACC_EXCL : H5F_ACC_TRUNC); + break; + } + + hid_t plist; + if (parallel) { + // Setup file access property list with parallel I/O access + plist = H5Pcreate(H5P_FILE_ACCESS); +#ifdef PHDF5 + H5Pset_fapl_mpio(plist, mpi_intracomm, MPI_INFO_NULL); +#endif + } else { + plist = H5P_DEFAULT; + } + + // Open the file collectively + hid_t file_id; + if (create) { + file_id = H5Fopen(filename, flags, plist); + } else { + file_id = H5Fcreate(filename, flags, H5P_DEFAULT, plist); + } + + // Close the property list + if (parallel) H5Pclose(plist); + + return file_id; +} + +hid_t +file_open(const std::string& filename, char mode, bool parallel=false) { + file_open(filename.c_str(), mode, parallel); +} + +void file_close(hid_t file_id) { + H5Fclose(file_id); +} + +bool +object_exists(hid_t object_id, const char* name) { + htri_t out = H5LTpath_valid(object_id, name, true); + if (out < 0) { + std::stringstream err_msg; + err_msg << "Failed to check if object \"" << name << "\" exists."; + fatal_error(err_msg); + } + return (out > 0); +} + + +hid_t +open_dataset(hid_t group_id, const char* name){ + if (object_exists(group_id, name)) { + return H5Dopen(group_id, name, H5P_DEFAULT); + } else { + std::stringstream err_msg; + err_msg << "Group \"" << name << "\" does not exist"; + fatal_error(err_msg); + } +} + + +hid_t +open_group(hid_t group_id, const char* name){ + if (object_exists(group_id, name)) { + return H5Gopen(group_id, name, H5P_DEFAULT); + } else { + std::stringstream err_msg; + err_msg << "Group \"" << name << "\" does not exist"; + fatal_error(err_msg); } } @@ -60,6 +169,35 @@ write_double_1D(hid_t group_id, char const *name, H5Dclose(dataset); } +void +write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + double* buffer, bool indep) { + hid_t dspace = H5Screate_simple(ndim, dims, nullptr); + hid_t dset = H5Dcreate(group_id, name, H5T_NATIVE_DOUBLE, dspace, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + + if (using_mpio_device(group_id)) { +#ifdef PHDF5 + // Set up collective vs independent I/O + auto data_xfer_mode {indep ? H5FD_MPIO_INDEPENDENT : H5FD_MPIO_COLLECTIVE}; + + // Create dataset transfer property list + hid_t plist = H5Pcreate(H5P_DATASET_XFER); + H5Pset_dxpl_mpio(plist, data_xfer_mode); + + // Write data + H5Dwrite(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, plist, buffer); + H5Pclose(plist); +#endif + } else { + H5Dwrite(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); + } + + // Free resources + H5Dclose(dset); + H5Sclose(dspace); +} + void write_string(hid_t group_id, char const *name, char const *buffer) From c74f4738c12c4bf459335508743439efabc1d735 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 17 Apr 2018 14:08:59 -0500 Subject: [PATCH 180/361] Use HDF5 file_open from C side (and remove file_create) --- CMakeLists.txt | 1 + src/error.h | 5 +- src/hdf5_interface.F90 | 98 ++++--------------- src/hdf5_interface.cpp | 212 ++++++++++++++++++++++++++++++++++++++++ src/hdf5_interface.h | 208 +++------------------------------------ src/particle_header.F90 | 2 +- src/plot.F90 | 2 +- src/source.F90 | 4 +- src/state_point.F90 | 8 +- src/summary.F90 | 2 +- src/track_output.F90 | 2 +- src/volume_calc.F90 | 4 +- 12 files changed, 261 insertions(+), 287 deletions(-) create mode 100644 src/hdf5_interface.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ca3d83717..ff7a3334b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,6 +435,7 @@ set(LIBOPENMC_FORTRAN_SRC set(LIBOPENMC_CXX_SRC src/error.h src/hdf5_interface.h + src/hdf5_interface.cpp src/random_lcg.cpp src/random_lcg.h src/simulation.cpp diff --git a/src/error.h b/src/error.h index 4c3373b3e..91c272745 100644 --- a/src/error.h +++ b/src/error.h @@ -12,18 +12,19 @@ namespace openmc { extern "C" void fatal_error_from_c(const char *message, int message_len); +inline void fatal_error(const char *message) { fatal_error_from_c(message, strlen(message)); } - +inline void fatal_error(const std::string &message) { fatal_error_from_c(message.c_str(), message.length()); } - +inline void fatal_error(const std::stringstream &message) { std::string out {message.str()}; diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 410149d9e..0c8d22239 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -19,6 +19,7 @@ module hdf5_interface #ifdef PHDF5 use message_passing, only: mpi_intracomm, MPI_INFO_NULL #endif + use string, only: to_c_string implicit none private @@ -84,7 +85,6 @@ module hdf5_interface public :: attribute_exists public :: write_attribute public :: read_attribute - public :: file_create public :: file_open public :: file_close public :: create_group @@ -101,98 +101,36 @@ module hdf5_interface contains -!=============================================================================== -! FILE_CREATE creates HDF5 file -!=============================================================================== - - function file_create(filename, parallel) result(file_id) - character(*), intent(in) :: filename ! name of file - logical, optional, intent(in) :: parallel ! whether to write in serial - integer(HID_T) :: file_id - - integer(HID_T) :: plist ! property list handle - integer :: hdf5_err ! HDF5 error code - logical :: parallel_ - - ! Check for serial option - parallel_ = .false. -#ifdef PHDF5 - if (present(parallel)) parallel_ = parallel -#endif - - if (parallel_) then - ! Setup file access property list with parallel I/O access - call h5pcreate_f(H5P_FILE_ACCESS_F, plist, hdf5_err) -#ifdef PHDF5 -#ifdef OPENMC_MPIF08 - call h5pset_fapl_mpio_f(plist, mpi_intracomm%MPI_VAL, & - MPI_INFO_NULL%MPI_VAL, hdf5_err) -#else - call h5pset_fapl_mpio_f(plist, mpi_intracomm, MPI_INFO_NULL, hdf5_err) -#endif -#endif - - ! Create the file collectively - call h5fcreate_f(trim(filename), H5F_ACC_TRUNC_F, file_id, hdf5_err, & - access_prp = plist) - - ! Close the property list - call h5pclose_f(plist, hdf5_err) - else - ! Create the file - call h5fcreate_f(trim(filename), H5F_ACC_TRUNC_F, file_id, hdf5_err) - end if - - end function file_create - !=============================================================================== ! FILE_OPEN opens HDF5 file !=============================================================================== function file_open(filename, mode, parallel) result(file_id) - character(*), intent(in) :: filename ! name of file - character(*), intent(in) :: mode ! access mode to file - logical, optional, intent(in) :: parallel ! whether to write in serial + character(*), intent(in) :: filename ! name of file + character, value :: mode ! access mode to file + logical, optional, intent(in) :: parallel ! whether to write in serial integer(HID_T) :: file_id - logical :: parallel_ - integer(HID_T) :: plist ! property list handle - integer :: hdf5_err ! HDF5 error code - integer :: open_mode ! HDF5 open mode + character(kind=C_CHAR) :: mode_ + logical(C_BOOL) :: parallel_ - ! Check for serial option + interface + function file_open_c(name, mode, parallel) bind(C, name='file_open') result(file_id) + import HID_T, C_CHAR, C_BOOL, C_INT + character(kind=C_CHAR) :: name(*) + character(kind=C_CHAR), value :: mode + logical(C_BOOL), value :: parallel + integer(HID_T) :: file_id + end function file_open_c + end interface + + mode_ = mode parallel_ = .false. #ifdef PHDF5 if (present(parallel)) parallel_ = parallel #endif - ! Determine access type - open_mode = H5F_ACC_RDONLY_F - if (mode == 'w') open_mode = H5F_ACC_RDWR_F - - if (parallel_) then - ! Setup file access property list with parallel I/O access - call h5pcreate_f(H5P_FILE_ACCESS_F, plist, hdf5_err) -#ifdef PHDF5 -#ifdef OPENMC_MPIF08 - call h5pset_fapl_mpio_f(plist, mpi_intracomm%MPI_VAL, & - MPI_INFO_NULL%MPI_VAL, hdf5_err) -#else - call h5pset_fapl_mpio_f(plist, mpi_intracomm, MPI_INFO_NULL, hdf5_err) -#endif -#endif - - ! Open the file collectively - call h5fopen_f(trim(filename), open_mode, file_id, hdf5_err, & - access_prp = plist) - - ! Close the property list - call h5pclose_f(plist, hdf5_err) - else - ! Open file - call h5fopen_f(trim(filename), open_mode, file_id, hdf5_err) - end if - + file_id = file_open_c(to_c_string(filename), mode, parallel_) end function file_open !=============================================================================== diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp new file mode 100644 index 000000000..10357c91d --- /dev/null +++ b/src/hdf5_interface.cpp @@ -0,0 +1,212 @@ +#include "hdf5_interface.h" +#include "error.h" + +#include "hdf5.h" +#include "hdf5_hl.h" + +#include +#include +#include + +namespace openmc { + +bool +using_mpio_device(hid_t obj_id) { + // Determine file that this object is part of + hid_t file_id = H5Iget_file_id(obj_id); + + // Get file access property list + hid_t fapl_id = H5Fget_access_plist(file_id); + + // Get low-level driver identifier + hid_t driver = H5Pget_driver(fapl_id); + + // Free resources + H5Pclose(fapl_id); + H5Fclose(file_id); + + return driver == H5FD_MPIO; +} + + +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::stringstream err_msg; + err_msg << "Failed to create HDF5 group \"" << name << "\""; + 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_dataset(hid_t dataset_id) +{ + if (H5Dclose(dataset_id) < 0) fatal_error("Failed to close dataset"); +} + + +void +close_group(hid_t group_id) +{ + if (H5Gclose(group_id) < 0) fatal_error("Failed to close group"); +} + + +hid_t +file_open(const char* filename, char mode, bool parallel) +{ + bool create; + unsigned int flags; + switch (mode) { + case 'r': + case 'a': + create = false; + flags = (mode == 'r' ? H5F_ACC_RDONLY : H5F_ACC_RDWR); + break; + case 'w': + case 'x': + create = true; + flags = (mode == 'x' ? H5F_ACC_EXCL : H5F_ACC_TRUNC); + break; + default: + std::stringstream err_msg; + err_msg << "Invalid file mode: " << mode; + fatal_error(err_msg); + } + + hid_t plist = H5P_DEFAULT; +#ifdef PHDF5 + if (parallel) { + // Setup file access property list with parallel I/O access + plist = H5Pcreate(H5P_FILE_ACCESS); + H5Pset_fapl_mpio(plist, mpi_intracomm, MPI_INFO_NULL); + } +#endif + + // Open the file collectively + hid_t file_id; + if (create) { + file_id = H5Fcreate(filename, flags, H5P_DEFAULT, plist); + } else { + file_id = H5Fopen(filename, flags, plist); + } + +#ifdef PHDF5 + // Close the property list + if (parallel) H5Pclose(plist); +#endif + + return file_id; +} + +hid_t +file_open(const std::string& filename, char mode, bool parallel=false) { + file_open(filename.c_str(), mode, parallel); +} + +void file_close(hid_t file_id) { + H5Fclose(file_id); +} + +bool +object_exists(hid_t object_id, const char* name) { + htri_t out = H5LTpath_valid(object_id, name, true); + if (out < 0) { + std::stringstream err_msg; + err_msg << "Failed to check if object \"" << name << "\" exists."; + fatal_error(err_msg); + } + return (out > 0); +} + + +hid_t +open_dataset(hid_t group_id, const char* name){ + if (object_exists(group_id, name)) { + return H5Dopen(group_id, name, H5P_DEFAULT); + } else { + std::stringstream err_msg; + err_msg << "Group \"" << name << "\" does not exist"; + fatal_error(err_msg); + } +} + + +hid_t +open_group(hid_t group_id, const char* name){ + if (object_exists(group_id, name)) { + return H5Gopen(group_id, name, H5P_DEFAULT); + } else { + std::stringstream err_msg; + err_msg << "Group \"" << name << "\" does not exist"; + fatal_error(err_msg); + } +} + + +void +write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + double* buffer, bool indep) { + hid_t dspace = H5Screate_simple(ndim, dims, nullptr); + hid_t dset = H5Dcreate(group_id, name, H5T_NATIVE_DOUBLE, dspace, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + + if (using_mpio_device(group_id)) { +#ifdef PHDF5 + // Set up collective vs independent I/O + auto data_xfer_mode {indep ? H5FD_MPIO_INDEPENDENT : H5FD_MPIO_COLLECTIVE}; + + // Create dataset transfer property list + hid_t plist = H5Pcreate(H5P_DATASET_XFER); + H5Pset_dxpl_mpio(plist, data_xfer_mode); + + // Write data + H5Dwrite(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, plist, buffer); + H5Pclose(plist); +#endif + } else { + H5Dwrite(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); + } + + // Free resources + H5Dclose(dset); + H5Sclose(dspace); +} + + +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); +} + + +void +write_string(hid_t group_id, char const *name, const std::string &buffer) +{ + write_string(group_id, name, buffer.c_str()); +} + +} diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 8a445b21e..b79e0a29c 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -1,8 +1,6 @@ #ifndef HDF5_INTERFACE_H #define HDF5_INTERFACE_H -#include "error.h" - #include "hdf5.h" #include "hdf5_hl.h" @@ -13,143 +11,17 @@ namespace openmc { -bool -using_mpio_device(hid_t obj_id) { - // Determine file that this object is part of - hid_t file_id = H5Iget_file_id(obj_id); - - // Get file access property list - hid_t fapl_id = H5Fget_access_plist(file_id); - - // Get low-level driver identifier - hid_t driver = H5Pget_driver(fapl_id); - - // Free resources - H5Pclose(fapl_id); - H5Fclose(file_id); - - return driver == H5FD_MPIO; -} - - -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::stringstream err_msg; - err_msg << "Failed to create HDF5 group \"" << name << "\""; - 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_dataset(hid_t dataset_id) -{ - if (H5Dclose(dataset_id) < 0) fatal_error("Failed to close dataset"); -} - - -void -close_group(hid_t group_id) -{ - if (H5Gclose(group_id) < 0) fatal_error("Failed to close group"); -} - - -hid_t -file_open(const char* filename, char mode, bool parallel=false) -{ - bool create; - unsigned int flags; - switch (mode) { - case 'r': - case 'a': - create = false; - flags = (mode == 'r' ? H5F_ACC_RDONLY : H5F_ACC_RDWR); - break; - case 'w': - case 'x': - create = true; - flags = (mode == 'x' ? H5F_ACC_EXCL : H5F_ACC_TRUNC); - break; - } - - hid_t plist; - if (parallel) { - // Setup file access property list with parallel I/O access - plist = H5Pcreate(H5P_FILE_ACCESS); -#ifdef PHDF5 - H5Pset_fapl_mpio(plist, mpi_intracomm, MPI_INFO_NULL); -#endif - } else { - plist = H5P_DEFAULT; - } - - // Open the file collectively - hid_t file_id; - if (create) { - file_id = H5Fopen(filename, flags, plist); - } else { - file_id = H5Fcreate(filename, flags, H5P_DEFAULT, plist); - } - - // Close the property list - if (parallel) H5Pclose(plist); - - return file_id; -} - -hid_t -file_open(const std::string& filename, char mode, bool parallel=false) { - file_open(filename.c_str(), mode, parallel); -} - -void file_close(hid_t file_id) { - H5Fclose(file_id); -} - -bool -object_exists(hid_t object_id, const char* name) { - htri_t out = H5LTpath_valid(object_id, name, true); - if (out < 0) { - std::stringstream err_msg; - err_msg << "Failed to check if object \"" << name << "\" exists."; - fatal_error(err_msg); - } - return (out > 0); -} - - -hid_t -open_dataset(hid_t group_id, const char* name){ - if (object_exists(group_id, name)) { - return H5Dopen(group_id, name, H5P_DEFAULT); - } else { - std::stringstream err_msg; - err_msg << "Group \"" << name << "\" does not exist"; - fatal_error(err_msg); - } -} - - -hid_t -open_group(hid_t group_id, const char* name){ - if (object_exists(group_id, name)) { - return H5Gopen(group_id, name, H5P_DEFAULT); - } else { - std::stringstream err_msg; - err_msg << "Group \"" << name << "\" does not exist"; - fatal_error(err_msg); - } -} +bool using_mpio_device(hid_t obj_id); +hid_t create_group(hid_t parent_id, const char* name); +hid_t create_group(hid_t parent_id, const std::string& name); +void close_dataset(hid_t dataset_id); +void close_group(hid_t group_id); +extern "C" hid_t file_open(const char* filename, char mode, bool parallel); +hid_t file_open(const std::string& filename, char mode, bool parallel); +void file_close(hid_t file_id); +bool object_exists(hid_t object_id, const char* name); +hid_t open_dataset(hid_t group_id, const char* name); +hid_t open_group(hid_t group_id, const char* name); template void @@ -169,61 +41,11 @@ write_double_1D(hid_t group_id, char const *name, H5Dclose(dataset); } -void -write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, - double* buffer, bool indep) { - hid_t dspace = H5Screate_simple(ndim, dims, nullptr); - hid_t dset = H5Dcreate(group_id, name, H5T_NATIVE_DOUBLE, dspace, - H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); +void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + double* buffer, bool indep); - if (using_mpio_device(group_id)) { -#ifdef PHDF5 - // Set up collective vs independent I/O - auto data_xfer_mode {indep ? H5FD_MPIO_INDEPENDENT : H5FD_MPIO_COLLECTIVE}; - - // Create dataset transfer property list - hid_t plist = H5Pcreate(H5P_DATASET_XFER); - H5Pset_dxpl_mpio(plist, data_xfer_mode); - - // Write data - H5Dwrite(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, plist, buffer); - H5Pclose(plist); -#endif - } else { - H5Dwrite(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); - } - - // Free resources - H5Dclose(dset); - H5Sclose(dspace); -} - - -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); -} - - -void -write_string(hid_t group_id, char const *name, const std::string &buffer) -{ - write_string(group_id, name, buffer.c_str()); -} +void write_string(hid_t group_id, char const *name, char const *buffer); +void write_string(hid_t group_id, char const *name, const std::string &buffer); } // namespace openmc #endif //HDF5_INTERFACE_H diff --git a/src/particle_header.F90 b/src/particle_header.F90 index 552f9b042..02fb55405 100644 --- a/src/particle_header.F90 +++ b/src/particle_header.F90 @@ -312,7 +312,7 @@ contains !$omp critical (WriteParticleRestart) ! Create file - file_id = file_create(filename) + file_id = file_open(filename, 'w') associate (src => source_bank(current_work)) ! Write filetype and version info diff --git a/src/plot.F90 b/src/plot.F90 index 5eb894ce8..92b8f350d 100644 --- a/src/plot.F90 +++ b/src/plot.F90 @@ -371,7 +371,7 @@ contains p % coord(1) % universe = root_universe ! Open binary plot file for writing - file_id = file_create(pl%path_plot) + file_id = file_open(pl%path_plot, 'w') ! write header info call write_attribute(file_id, "filetype", 'voxel') diff --git a/src/source.F90 b/src/source.F90 index 5beecd887..9e3a0831d 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -12,7 +12,7 @@ module source use distribution_multivariate, only: SpatialBox use error, only: fatal_error use geometry, only: find_cell - use hdf5_interface, only: file_create, file_open, file_close, read_dataset + use hdf5_interface, only: file_open, file_close, read_dataset use math use message_passing, only: rank use mgxs_header, only: rev_energy_bins, num_energy_groups @@ -88,7 +88,7 @@ contains if (write_initial_source) then call write_message('Writing out initial source...', 5) filename = trim(path_output) // 'initial_source.h5' - file_id = file_create(filename, parallel=.true.) + file_id = file_open(filename, 'w', parallel=.true.) call write_source_bank(file_id) call file_close(file_id) end if diff --git a/src/state_point.F90 b/src/state_point.F90 index 980a7333c..62240f110 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -76,7 +76,7 @@ contains if (master) then ! Create statepoint file - file_id = file_create(filename_) + file_id = file_open(filename_, 'w') ! Write file type call write_attribute(file_id, "filetype", "statepoint") @@ -476,7 +476,7 @@ contains ! Create separate source file if (master .or. parallel) then - file_id = file_create(filename, parallel=.true.) + file_id = file_open(filename, 'w', parallel=.true.) call write_dataset(file_id, "filetype", 'source') end if else @@ -485,7 +485,7 @@ contains filename = trim(filename) // '.h5' if (master .or. parallel) then - file_id = file_open(filename, 'w', parallel=.true.) + file_id = file_open(filename, 'a', parallel=.true.) end if end if @@ -498,7 +498,7 @@ contains filename = trim(path_output) // 'source' // '.h5' call write_message("Creating source file " // trim(filename) // "...", 5) if (master .or. parallel) then - file_id = file_create(filename, parallel=.true.) + file_id = file_open(filename, 'w', parallel=.true.) call write_dataset(file_id, "filetype", 'source') end if diff --git a/src/summary.F90 b/src/summary.F90 index 3aeb42178..9f27694d1 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -38,7 +38,7 @@ contains call write_message("Writing summary.h5 file...", 5) ! Create a new file using default properties. - file_id = file_create("summary.h5") + file_id = file_open("summary.h5", 'w') call write_header(file_id) call write_nuclides(file_id) diff --git a/src/track_output.F90 b/src/track_output.F90 index 244bf182e..350e687b1 100644 --- a/src/track_output.F90 +++ b/src/track_output.F90 @@ -115,7 +115,7 @@ contains end do !$omp critical (FinalizeParticleTrack) - file_id = file_create(fname) + file_id = file_open(fname, 'w') call write_attribute(file_id, 'filetype', 'track') call write_attribute(file_id, 'version', VERSION_TRACK) call write_attribute(file_id, 'n_particles', n_particle_tracks) diff --git a/src/volume_calc.F90 b/src/volume_calc.F90 index e374f2106..ac813a94a 100644 --- a/src/volume_calc.F90 +++ b/src/volume_calc.F90 @@ -11,7 +11,7 @@ module volume_calc 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, & + use hdf5_interface, only: file_open, file_close, write_attribute, & create_group, close_group, write_dataset use output, only: header, time_stamp use material_header, only: materials @@ -435,7 +435,7 @@ contains character(MAX_WORD_LEN), allocatable :: nucnames(:) ! names of nuclides ! Create HDF5 file - file_id = file_create(filename) + file_id = file_open(filename, 'w') ! Write header info call write_attribute(file_id, "filetype", "volume") From b3bd34e51b887d4f9099aa089f2c2bdea4255edd Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 17 Apr 2018 15:29:35 -0500 Subject: [PATCH 181/361] Use file_close from C --- src/hdf5_interface.F90 | 19 +++++++------------ src/hdf5_interface.h | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 0c8d22239..fa1f6093b 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -99,6 +99,13 @@ module hdf5_interface public :: get_datasets public :: get_name + interface + subroutine file_close(file_id) bind(C) + import HID_T + integer(HID_T), value :: file_id + end subroutine file_close + end interface + contains !=============================================================================== @@ -133,18 +140,6 @@ contains file_id = file_open_c(to_c_string(filename), mode, parallel_) end function file_open -!=============================================================================== -! FILE_CLOSE closes HDF5 file -!=============================================================================== - - subroutine file_close(file_id) - integer(HID_T), intent(in) :: file_id - - integer :: hdf5_err - - call h5fclose_f(file_id, hdf5_err) - end subroutine file_close - !=============================================================================== ! GET_GROUPS Gets a list of all the groups in a given location. !=============================================================================== diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index b79e0a29c..3ddd59374 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -18,7 +18,7 @@ void close_dataset(hid_t dataset_id); void close_group(hid_t group_id); extern "C" hid_t file_open(const char* filename, char mode, bool parallel); hid_t file_open(const std::string& filename, char mode, bool parallel); -void file_close(hid_t file_id); +extern "C" void file_close(hid_t file_id); bool object_exists(hid_t object_id, const char* name); hid_t open_dataset(hid_t group_id, const char* name); hid_t open_group(hid_t group_id, const char* name); From dcde6a331a8ddb0e469a7a0cd04c48ae2892e5f9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 17 Apr 2018 16:14:22 -0500 Subject: [PATCH 182/361] Use HDF5 write_double from C++ --- src/hdf5_interface.F90 | 219 ++++++----------------------------------- src/hdf5_interface.cpp | 2 +- src/hdf5_interface.h | 4 +- 3 files changed, 34 insertions(+), 191 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index fa1f6093b..040b11548 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -104,6 +104,17 @@ module hdf5_interface import HID_T integer(HID_T), value :: file_id end subroutine file_close + + subroutine write_double_c(group_id, ndim, dims, name, buffer, indep) & + bind(C, name='write_double') + import HID_T, HSIZE_T, C_INT, C_DOUBLE, C_CHAR, C_BOOL, C_PTR + integer(HID_T), value :: group_id + integer(C_INT), value :: ndim + integer(HSIZE_T), intent(in) :: dims(*) + character(kind=C_CHAR), intent(in) :: name(*) + real(C_DOUBLE), intent(in) :: buffer(*) + logical(C_BOOL), intent(in) :: indep + end subroutine write_double_c end interface contains @@ -472,57 +483,15 @@ contains logical, intent(in), optional :: indep ! independent I/O integer(HSIZE_T) :: dims(1) + logical(C_BOOL) :: indep_ dims(:) = shape(buffer) - if (present(indep)) then - call write_double_1D_explicit(group_id, dims, name, buffer, indep) - else - call write_double_1D_explicit(group_id, dims, name, buffer) - end if + indep_ = .false. + if (present(indep)) indep_ = indep + + call write_double_c(group_id, 1, dims, to_c_string(name), buffer, indep_) end subroutine write_double_1D - subroutine write_double_1D_explicit(group_id, dims, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - integer(HSIZE_T), intent(in) :: dims(1) - character(*), intent(in) :: name ! name of data - real(8), intent(in), target :: buffer(dims(1)) ! data to write - logical, intent(in), optional :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - call h5screate_simple_f(1, dims, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), H5T_NATIVE_DOUBLE, & - dspace, dset, hdf5_err) - f_ptr = c_loc(buffer) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end subroutine write_double_1D_explicit - !=============================================================================== ! READ_DOUBLE_1D reads double precision 1-D array data !=============================================================================== @@ -600,57 +569,15 @@ contains logical, intent(in), optional :: indep ! independent I/O integer(HSIZE_T) :: dims(2) + logical(C_BOOL) :: indep_ dims(:) = shape(buffer) - if (present(indep)) then - call write_double_2D_explicit(group_id, dims, name, buffer, indep) - else - call write_double_2D_explicit(group_id, dims, name, buffer) - end if + indep_ = .false. + if (present(indep)) indep_ = indep + + call write_double_c(group_id, 2, dims, to_c_string(name), buffer, indep_) end subroutine write_double_2D - subroutine write_double_2D_explicit(group_id, dims, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - integer(HSIZE_T), intent(in) :: dims(2) - character(*), intent(in) :: name ! name of data - real(8), intent(in), target :: buffer(dims(1),dims(2)) - logical, intent(in), optional :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - call h5screate_simple_f(2, dims, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), H5T_NATIVE_DOUBLE, & - dspace, dset, hdf5_err) - f_ptr = c_loc(buffer) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end subroutine write_double_2D_explicit - !=============================================================================== ! READ_DOUBLE_2D reads double precision 2-D array data !=============================================================================== @@ -728,57 +655,15 @@ contains logical, intent(in), optional :: indep ! independent I/O integer(HSIZE_T) :: dims(3) + logical(C_BOOL) :: indep_ dims(:) = shape(buffer) - if (present(indep)) then - call write_double_3D_explicit(group_id, dims, name, buffer, indep) - else - call write_double_3D_explicit(group_id, dims, name, buffer) - end if + indep_ = .false. + if (present(indep)) indep_ = indep + + call write_double_c(group_id, 3, dims, to_c_string(name), buffer, indep_) end subroutine write_double_3D - subroutine write_double_3D_explicit(group_id, dims, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - integer(HSIZE_T), intent(in) :: dims(3) - character(*), intent(in) :: name ! name of data - real(8), intent(in), target :: buffer(dims(1),dims(2),dims(3)) - logical, intent(in), optional :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - call h5screate_simple_f(3, dims, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), H5T_NATIVE_DOUBLE, & - dspace, dset, hdf5_err) - f_ptr = c_loc(buffer) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end subroutine write_double_3D_explicit - !=============================================================================== ! READ_DOUBLE_3D reads double precision 3-D array data !=============================================================================== @@ -856,57 +741,15 @@ contains logical, intent(in), optional :: indep ! independent I/O integer(HSIZE_T) :: dims(4) + logical(C_BOOL) :: indep_ dims(:) = shape(buffer) - if (present(indep)) then - call write_double_4D_explicit(group_id, dims, name, buffer, indep) - else - call write_double_4D_explicit(group_id, dims, name, buffer) - end if + indep_ = .false. + if (present(indep)) indep_ = indep + + call write_double_c(group_id, 1, dims, to_c_string(name), buffer, indep_) end subroutine write_double_4D - subroutine write_double_4D_explicit(group_id, dims, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - integer(HSIZE_T), intent(in) :: dims(4) - character(*), intent(in) :: name ! name of data - real(8), intent(in), target :: buffer(dims(1),dims(2),dims(3),dims(4)) - logical, intent(in), optional :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - call h5screate_simple_f(4, dims, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), H5T_NATIVE_DOUBLE, & - dspace, dset, hdf5_err) - f_ptr = c_loc(buffer) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end subroutine write_double_4D_explicit - !=============================================================================== ! READ_DOUBLE_4D reads double precision 4-D array data !=============================================================================== diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 10357c91d..a2ccef7f7 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -155,7 +155,7 @@ open_group(hid_t group_id, const char* name){ void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, - double* buffer, bool indep) { + const double* buffer, bool indep) { hid_t dspace = H5Screate_simple(ndim, dims, nullptr); hid_t dset = H5Dcreate(group_id, name, H5T_NATIVE_DOUBLE, dspace, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 3ddd59374..bbf5ce5e8 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -41,8 +41,8 @@ write_double_1D(hid_t group_id, char const *name, H5Dclose(dataset); } -void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, - double* buffer, bool indep); +extern "C" void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + const double* buffer, bool indep); void write_string(hid_t group_id, char const *name, char const *buffer); void write_string(hid_t group_id, char const *name, const std::string &buffer); From ef1956e8113db9618693b8aa49b3ff427272b136 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 17 Apr 2018 22:55:07 -0500 Subject: [PATCH 183/361] Convert HDF5 read_double to C++ --- src/hdf5_interface.F90 | 274 ++++++++--------------------------------- src/hdf5_interface.cpp | 58 +++++++-- src/hdf5_interface.h | 7 +- 3 files changed, 104 insertions(+), 235 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 040b11548..b9e6c414e 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -105,9 +105,18 @@ module hdf5_interface integer(HID_T), value :: file_id end subroutine file_close + subroutine read_double_c(obj_id, name, buffer, indep) & + bind(C, name='read_double') + import HID_T, C_DOUBLE, C_BOOL, C_PTR + integer(HID_T), value :: obj_id + type(C_PTR), value :: name + real(C_DOUBLE), intent(in) :: buffer(*) + logical(C_BOOL), intent(in) :: indep + end subroutine read_double_c + subroutine write_double_c(group_id, ndim, dims, name, buffer, indep) & bind(C, name='write_double') - import HID_T, HSIZE_T, C_INT, C_DOUBLE, C_CHAR, C_BOOL, C_PTR + import HID_T, HSIZE_T, C_INT, C_DOUBLE, C_CHAR, C_BOOL integer(HID_T), value :: group_id integer(C_INT), value :: ndim integer(HSIZE_T), intent(in) :: dims(*) @@ -388,40 +397,15 @@ contains real(8), intent(in), target :: buffer ! data to write logical, intent(in), optional :: indep ! independent I/O - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - type(c_ptr) :: f_ptr + integer(HSIZE_T) :: dims(0) + logical(C_BOOL) :: indep_ + real(C_DOUBLE) :: value(1) - ! Set up independentive vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if + indep_ = .false. + if (present(indep)) indep_ = indep + value(1) = buffer - ! Create dataspace and dataset - call h5screate_f(H5S_SCALAR_F, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), H5T_NATIVE_DOUBLE, & - dspace, dset, hdf5_err) - f_ptr = c_loc(buffer) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) + call write_double_c(group_id, 0, dims, to_c_string(name), value, indep_) end subroutine write_double !=============================================================================== @@ -502,62 +486,22 @@ contains character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer(HID_T) :: dset_id - integer(HSIZE_T) :: dims(1) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep ! If 'name' argument is passed, obj_id is interpreted to be a group and ! 'name' is the name of the dataset we should read from if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + name_ = to_c_string(name) + call read_double_c(obj_id, c_loc(name_), buffer, indep_) else - dset_id = obj_id + call read_double_c(obj_id, C_NULL_PTR, buffer, indep_) end if - - dims(:) = shape(buffer) - - if (present(indep)) then - call read_double_1D_explicit(dset_id, dims, buffer, indep) - else - call read_double_1D_explicit(dset_id, dims, buffer) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) end subroutine read_double_1D - subroutine read_double_1D_explicit(dset_id, dims, buffer, indep) - integer(HID_T), intent(in) :: dset_id - integer(HSIZE_T), intent(in) :: dims(1) - real(8), target, intent(inout) :: buffer(dims(1)) - logical, optional, intent(in) :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr = c_loc(buffer) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end if - end subroutine read_double_1D_explicit - !=============================================================================== ! WRITE_DOUBLE_2D writes double precision 2-D array data !=============================================================================== @@ -588,62 +532,22 @@ contains character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer(HID_T) :: dset_id - integer(HSIZE_T) :: dims(2) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep ! If 'name' argument is passed, obj_id is interpreted to be a group and ! 'name' is the name of the dataset we should read from if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + name_ = to_c_string(name) + call read_double_c(obj_id, c_loc(name_), buffer, indep_) else - dset_id = obj_id + call read_double_c(obj_id, C_NULL_PTR, buffer, indep_) end if - - dims(:) = shape(buffer) - - if (present(indep)) then - call read_double_2D_explicit(dset_id, dims, buffer, indep) - else - call read_double_2D_explicit(dset_id, dims, buffer) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) end subroutine read_double_2D - subroutine read_double_2D_explicit(dset_id, dims, buffer, indep) - integer(HID_T), intent(in) :: dset_id - integer(HSIZE_T), intent(in) :: dims(2) - real(8), target, intent(inout) :: buffer(dims(1),dims(2)) - logical, optional, intent(in) :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr = c_loc(buffer) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end if - end subroutine read_double_2D_explicit - !=============================================================================== ! WRITE_DOUBLE_3D writes double precision 3-D array data !=============================================================================== @@ -674,62 +578,22 @@ contains character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer(HID_T) :: dset_id - integer(HSIZE_T) :: dims(3) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep ! If 'name' argument is passed, obj_id is interpreted to be a group and ! 'name' is the name of the dataset we should read from if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + name_ = to_c_string(name) + call read_double_c(obj_id, c_loc(name_), buffer, indep_) else - dset_id = obj_id + call read_double_c(obj_id, C_NULL_PTR, buffer, indep_) end if - - dims(:) = shape(buffer) - - if (present(indep)) then - call read_double_3D_explicit(dset_id, dims, buffer, indep) - else - call read_double_3D_explicit(dset_id, dims, buffer) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) end subroutine read_double_3D - subroutine read_double_3D_explicit(dset_id, dims, buffer, indep) - integer(HID_T), intent(in) :: dset_id - integer(HSIZE_T), intent(in) :: dims(3) - real(8), target, intent(inout) :: buffer(dims(1),dims(2),dims(3)) - logical, optional, intent(in) :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr = c_loc(buffer) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end if - end subroutine read_double_3D_explicit - !=============================================================================== ! WRITE_DOUBLE_4D writes double precision 4-D array data !=============================================================================== @@ -760,62 +624,22 @@ contains character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer(HID_T) :: dset_id - integer(HSIZE_T) :: dims(4) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep ! If 'name' argument is passed, obj_id is interpreted to be a group and ! 'name' is the name of the dataset we should read from if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + name_ = to_c_string(name) + call read_double_c(obj_id, c_loc(name_), buffer, indep_) else - dset_id = obj_id + call read_double_c(obj_id, C_NULL_PTR, buffer, indep_) end if - - dims(:) = shape(buffer) - - if (present(indep)) then - call read_double_4D_explicit(dset_id, dims, buffer, indep) - else - call read_double_4D_explicit(dset_id, dims, buffer) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) end subroutine read_double_4D - subroutine read_double_4D_explicit(dset_id, dims, buffer, indep) - integer(HID_T), intent(in) :: dset_id - integer(HSIZE_T), intent(in) :: dims(4) - real(8), target, intent(inout) :: buffer(dims(1),dims(2),dims(3),dims(4)) - logical, optional, intent(in) :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr = c_loc(buffer) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end if - end subroutine read_double_4D_explicit - !=============================================================================== ! WRITE_INTEGER writes integer precision scalar data !=============================================================================== diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index a2ccef7f7..6b9bd9798 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -11,7 +11,8 @@ namespace openmc { bool -using_mpio_device(hid_t obj_id) { +using_mpio_device(hid_t obj_id) +{ // Determine file that this object is part of hid_t file_id = H5Iget_file_id(obj_id); @@ -109,16 +110,19 @@ file_open(const char* filename, char mode, bool parallel) } hid_t -file_open(const std::string& filename, char mode, bool parallel=false) { +file_open(const std::string& filename, char mode, bool parallel=false) +{ file_open(filename.c_str(), mode, parallel); } -void file_close(hid_t file_id) { +void file_close(hid_t file_id) +{ H5Fclose(file_id); } bool -object_exists(hid_t object_id, const char* name) { +object_exists(hid_t object_id, const char* name) +{ htri_t out = H5LTpath_valid(object_id, name, true); if (out < 0) { std::stringstream err_msg; @@ -130,7 +134,8 @@ object_exists(hid_t object_id, const char* name) { hid_t -open_dataset(hid_t group_id, const char* name){ +open_dataset(hid_t group_id, const char* name) +{ if (object_exists(group_id, name)) { return H5Dopen(group_id, name, H5P_DEFAULT); } else { @@ -142,7 +147,8 @@ open_dataset(hid_t group_id, const char* name){ hid_t -open_group(hid_t group_id, const char* name){ +open_group(hid_t group_id, const char* name) +{ if (object_exists(group_id, name)) { return H5Gopen(group_id, name, H5P_DEFAULT); } else { @@ -153,10 +159,46 @@ open_group(hid_t group_id, const char* name){ } +void +read_double(hid_t obj_id, const char* name, double* buffer, bool indep) +{ + hid_t dset = obj_id; + if (name) dset = H5Dopen(obj_id, name, H5P_DEFAULT); + + if (using_mpio_device(dset)) { +#ifdef PHDF5 + // Set up collective vs independent I/O + auto data_xfer_mode {indep ? H5FD_MPIO_INDEPENDENT : H5FD_MPIO_COLLECTIVE}; + + // Create dataset transfer property list + hid_t plist = H5Pcreate(H5P_DATASET_XFER); + H5Pset_dxpl_mpio(plist, data_xfer_mode); + + // Write data + H5Dread(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, plist, buffer); + H5Pclose(plist); +#endif + } else { + H5Dread(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); + } + + if (name) H5Dclose(dset); +} + + void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, - const double* buffer, bool indep) { - hid_t dspace = H5Screate_simple(ndim, dims, nullptr); + const double* buffer, bool indep) +{ + // If array is given, create a simple dataspace. Otherwise, create a scalar + // datascape. + hid_t dspace; + if (ndim > 0) { + dspace = H5Screate_simple(ndim, dims, nullptr); + } else { + dspace = H5Screate(H5S_SCALAR); + } + hid_t dset = H5Dcreate(group_id, name, H5T_NATIVE_DOUBLE, dspace, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index bbf5ce5e8..6902493af 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -41,8 +41,11 @@ write_double_1D(hid_t group_id, char const *name, H5Dclose(dataset); } -extern "C" void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, - const double* buffer, bool indep); +extern "C" void read_double(hid_t obj_id, const char* name, double* buffer, + bool indep); + +extern "C" void write_double(hid_t group_id, int ndim, const hsize_t* dims, + const char* name, const double* buffer, bool indep); void write_string(hid_t group_id, char const *name, char const *buffer); void write_string(hid_t group_id, char const *name, const std::string &buffer); From 1648da04a34b39dd5bd688a1dc301a839e79a407 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 17 Apr 2018 22:59:07 -0500 Subject: [PATCH 184/361] Move routines around --- src/hdf5_interface.F90 | 164 +++++++++++++++++------------------------ 1 file changed, 68 insertions(+), 96 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index b9e6c414e..ece930266 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -28,7 +28,7 @@ module hdf5_interface integer(HID_T), public :: hdf5_integer8_t ! type for integer(8) interface write_dataset - module procedure write_double + module procedure write_double_0D module procedure write_double_1D module procedure write_double_2D module procedure write_double_3D @@ -387,27 +387,6 @@ contains end if end subroutine close_dataset -!=============================================================================== -! WRITE_DOUBLE writes double precision scalar data -!=============================================================================== - - subroutine write_double(group_id, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - character(*), intent(in) :: name ! name for data - real(8), intent(in), target :: buffer ! data to write - logical, intent(in), optional :: indep ! independent I/O - - integer(HSIZE_T) :: dims(0) - logical(C_BOOL) :: indep_ - real(C_DOUBLE) :: value(1) - - indep_ = .false. - if (present(indep)) indep_ = indep - value(1) = buffer - - call write_double_c(group_id, 0, dims, to_c_string(name), value, indep_) - end subroutine write_double - !=============================================================================== ! READ_DOUBLE reads double precision scalar data !=============================================================================== @@ -457,9 +436,26 @@ contains end subroutine read_double !=============================================================================== -! WRITE_DOUBLE_1D writes double precision 1-D array data +! WRITE_DOUBLE_ND writes double precision N-D array data !=============================================================================== + subroutine write_double_0D(group_id, name, buffer, indep) + integer(HID_T), intent(in) :: group_id + character(*), intent(in) :: name ! name for data + real(8), intent(in), target :: buffer ! data to write + logical, intent(in), optional :: indep ! independent I/O + + integer(HSIZE_T) :: dims(0) + logical(C_BOOL) :: indep_ + real(C_DOUBLE) :: value(1) + + indep_ = .false. + if (present(indep)) indep_ = indep + value(1) = buffer + + call write_double_c(group_id, 0, dims, to_c_string(name), value, indep_) + end subroutine write_double_0D + subroutine write_double_1D(group_id, name, buffer, indep) integer(HID_T), intent(in) :: group_id character(*), intent(in) :: name ! name of data @@ -476,8 +472,56 @@ contains call write_double_c(group_id, 1, dims, to_c_string(name), buffer, indep_) end subroutine write_double_1D + subroutine write_double_2D(group_id, name, buffer, indep) + integer(HID_T), intent(in) :: group_id + character(*), intent(in) :: name ! name of data + real(8), intent(in), target :: buffer(:,:) ! data to write + logical, intent(in), optional :: indep ! independent I/O + + integer(HSIZE_T) :: dims(2) + logical(C_BOOL) :: indep_ + + dims(:) = shape(buffer) + indep_ = .false. + if (present(indep)) indep_ = indep + + call write_double_c(group_id, 2, dims, to_c_string(name), buffer, indep_) + end subroutine write_double_2D + + subroutine write_double_3D(group_id, name, buffer, indep) + integer(HID_T), intent(in) :: group_id + character(*), intent(in) :: name ! name of data + real(8), intent(in), target :: buffer(:,:,:) ! data to write + logical, intent(in), optional :: indep ! independent I/O + + integer(HSIZE_T) :: dims(3) + logical(C_BOOL) :: indep_ + + dims(:) = shape(buffer) + indep_ = .false. + if (present(indep)) indep_ = indep + + call write_double_c(group_id, 3, dims, to_c_string(name), buffer, indep_) + end subroutine write_double_3D + + subroutine write_double_4D(group_id, name, buffer, indep) + integer(HID_T), intent(in) :: group_id + character(*), intent(in) :: name ! name of data + real(8), intent(in), target :: buffer(:,:,:,:) ! data to write + logical, intent(in), optional :: indep ! independent I/O + + integer(HSIZE_T) :: dims(4) + logical(C_BOOL) :: indep_ + + dims(:) = shape(buffer) + indep_ = .false. + if (present(indep)) indep_ = indep + + call write_double_c(group_id, 1, dims, to_c_string(name), buffer, indep_) + end subroutine write_double_4D + !=============================================================================== -! READ_DOUBLE_1D reads double precision 1-D array data +! READ_DOUBLE_ND reads double precision N-D array data !=============================================================================== subroutine read_double_1D(buffer, obj_id, name, indep) @@ -502,30 +546,6 @@ contains end if end subroutine read_double_1D -!=============================================================================== -! WRITE_DOUBLE_2D writes double precision 2-D array data -!=============================================================================== - - subroutine write_double_2D(group_id, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - character(*), intent(in) :: name ! name of data - real(8), intent(in), target :: buffer(:,:) ! data to write - logical, intent(in), optional :: indep ! independent I/O - - integer(HSIZE_T) :: dims(2) - logical(C_BOOL) :: indep_ - - dims(:) = shape(buffer) - indep_ = .false. - if (present(indep)) indep_ = indep - - call write_double_c(group_id, 2, dims, to_c_string(name), buffer, indep_) - end subroutine write_double_2D - -!=============================================================================== -! READ_DOUBLE_2D reads double precision 2-D array data -!=============================================================================== - subroutine read_double_2D(buffer, obj_id, name, indep) real(8), target, intent(inout) :: buffer(:,:) integer(HID_T), intent(in) :: obj_id @@ -548,30 +568,6 @@ contains end if end subroutine read_double_2D -!=============================================================================== -! WRITE_DOUBLE_3D writes double precision 3-D array data -!=============================================================================== - - subroutine write_double_3D(group_id, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - character(*), intent(in) :: name ! name of data - real(8), intent(in), target :: buffer(:,:,:) ! data to write - logical, intent(in), optional :: indep ! independent I/O - - integer(HSIZE_T) :: dims(3) - logical(C_BOOL) :: indep_ - - dims(:) = shape(buffer) - indep_ = .false. - if (present(indep)) indep_ = indep - - call write_double_c(group_id, 3, dims, to_c_string(name), buffer, indep_) - end subroutine write_double_3D - -!=============================================================================== -! READ_DOUBLE_3D reads double precision 3-D array data -!=============================================================================== - subroutine read_double_3D(buffer, obj_id, name, indep) real(8), target, intent(inout) :: buffer(:,:,:) integer(HID_T), intent(in) :: obj_id @@ -594,30 +590,6 @@ contains end if end subroutine read_double_3D -!=============================================================================== -! WRITE_DOUBLE_4D writes double precision 4-D array data -!=============================================================================== - - subroutine write_double_4D(group_id, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - character(*), intent(in) :: name ! name of data - real(8), intent(in), target :: buffer(:,:,:,:) ! data to write - logical, intent(in), optional :: indep ! independent I/O - - integer(HSIZE_T) :: dims(4) - logical(C_BOOL) :: indep_ - - dims(:) = shape(buffer) - indep_ = .false. - if (present(indep)) indep_ = indep - - call write_double_c(group_id, 1, dims, to_c_string(name), buffer, indep_) - end subroutine write_double_4D - -!=============================================================================== -! READ_DOUBLE_4D reads double precision 4-D array data -!=============================================================================== - subroutine read_double_4D(buffer, obj_id, name, indep) real(8), target, intent(inout) :: buffer(:,:,:,:) integer(HID_T), intent(in) :: obj_id From 46270774c543b81fc20ef24ff2dcab85948332db Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Apr 2018 06:22:13 -0500 Subject: [PATCH 185/361] Convert HDF5 write_integer to C++ --- src/hdf5_interface.F90 | 352 +++++++++-------------------------------- src/hdf5_interface.cpp | 26 ++- src/hdf5_interface.h | 4 + 3 files changed, 102 insertions(+), 280 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index ece930266..562e35661 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -33,7 +33,7 @@ module hdf5_interface module procedure write_double_2D module procedure write_double_3D module procedure write_double_4D - module procedure write_integer + module procedure write_integer_0D module procedure write_integer_1D module procedure write_integer_2D module procedure write_integer_3D @@ -124,6 +124,17 @@ module hdf5_interface real(C_DOUBLE), intent(in) :: buffer(*) logical(C_BOOL), intent(in) :: indep end subroutine write_double_c + + subroutine write_int_c(group_id, ndim, dims, name, buffer, indep) & + bind(C, name='write_int') + import HID_T, HSIZE_T, C_INT, C_CHAR, C_BOOL + integer(HID_T), value :: group_id + integer(C_INT), value :: ndim + integer(HSIZE_T), intent(in) :: dims(*) + character(kind=C_CHAR), intent(in) :: name(*) + integer(C_INT), intent(in) :: buffer(*) + logical(C_BOOL), intent(in) :: indep + end subroutine write_int_c end interface contains @@ -447,13 +458,13 @@ contains integer(HSIZE_T) :: dims(0) logical(C_BOOL) :: indep_ - real(C_DOUBLE) :: value(1) + real(C_DOUBLE) :: buffer_(1) indep_ = .false. if (present(indep)) indep_ = indep - value(1) = buffer + buffer_(1) = buffer - call write_double_c(group_id, 0, dims, to_c_string(name), value, indep_) + call write_double_c(group_id, 0, dims, to_c_string(name), buffer_, indep_) end subroutine write_double_0D subroutine write_double_1D(group_id, name, buffer, indep) @@ -612,52 +623,6 @@ contains end if end subroutine read_double_4D -!=============================================================================== -! WRITE_INTEGER writes integer precision scalar data -!=============================================================================== - - subroutine write_integer(group_id, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - character(*), intent(in) :: name ! name for data - integer, intent(in), target :: buffer ! data to write - logical, intent(in), optional :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - ! Create dataspace and dataset - call h5screate_f(H5S_SCALAR_F, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), H5T_NATIVE_INTEGER, & - dspace, dset, hdf5_err) - f_ptr = c_loc(buffer) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_INTEGER, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end subroutine write_integer - !=============================================================================== ! READ_INTEGER reads integer precision scalar data !=============================================================================== @@ -707,9 +672,26 @@ contains end subroutine read_integer !=============================================================================== -! WRITE_INTEGER_1D writes integer precision 1-D array data +! WRITE_INTEGER_ND writes integer precision N-D array data !=============================================================================== + subroutine write_integer_0D(group_id, name, buffer, indep) + integer(HID_T), intent(in) :: group_id + character(*), intent(in) :: name ! name for data + integer, intent(in), target :: buffer ! data to write + logical, intent(in), optional :: indep ! independent I/O + + integer(HSIZE_T) :: dims(0) + logical(C_BOOL) :: indep_ + integer(C_INT) :: buffer_(1) + + indep_ = .false. + if (present(indep)) indep_ = indep + buffer_(1) = buffer + + call write_int_c(group_id, 0, dims, to_c_string(name), buffer_, indep_) + end subroutine write_integer_0D + subroutine write_integer_1D(group_id, name, buffer, indep) integer(HID_T), intent(in) :: group_id character(*), intent(in) :: name ! name of data @@ -717,56 +699,62 @@ contains logical, intent(in), optional :: indep ! independent I/O integer(HSIZE_T) :: dims(1) + logical(C_BOOL) :: indep_ dims(:) = shape(buffer) - if (present(indep)) then - call write_integer_1D_explicit(group_id, dims, name, buffer, indep) - else - call write_integer_1D_explicit(group_id, dims, name, buffer) - end if + indep_ = .false. + if (present(indep)) indep_ = indep + + call write_int_c(group_id, 1, dims, to_c_string(name), buffer, indep_) end subroutine write_integer_1D - subroutine write_integer_1D_explicit(group_id, dims, name, buffer, indep) + subroutine write_integer_2D(group_id, name, buffer, indep) integer(HID_T), intent(in) :: group_id - integer(HSIZE_T), intent(in) :: dims(1) character(*), intent(in) :: name ! name of data - integer, intent(in), target :: buffer(dims(1)) ! data to write + integer, intent(in), target :: buffer(:,:) ! data to write logical, intent(in), optional :: indep ! independent I/O - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - type(c_ptr) :: f_ptr + integer(HSIZE_T) :: dims(2) + logical(C_BOOL) :: indep_ - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if + dims(:) = shape(buffer) + indep_ = .false. + if (present(indep)) indep_ = indep - call h5screate_simple_f(1, dims, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), H5T_NATIVE_INTEGER, & - dspace, dset, hdf5_err) - f_ptr = c_loc(buffer) + call write_int_c(group_id, 2, dims, to_c_string(name), buffer, indep_) + end subroutine write_integer_2D - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_INTEGER, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end if + subroutine write_integer_3D(group_id, name, buffer, indep) + integer(HID_T), intent(in) :: group_id + character(*), intent(in) :: name ! name of data + integer, intent(in), target :: buffer(:,:,:) ! data to write + logical, intent(in), optional :: indep ! independent I/O - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end subroutine write_integer_1D_explicit + integer(HSIZE_T) :: dims(3) + logical(C_BOOL) :: indep_ + + dims(:) = shape(buffer) + indep_ = .false. + if (present(indep)) indep_ = indep + + call write_int_c(group_id, 3, dims, to_c_string(name), buffer, indep_) + end subroutine write_integer_3D + + subroutine write_integer_4D(group_id, name, buffer, indep) + integer(HID_T), intent(in) :: group_id + character(*), intent(in) :: name ! name of data + integer, intent(in), target :: buffer(:,:,:,:) ! data to write + logical, intent(in), optional :: indep ! independent I/O + + integer(HSIZE_T) :: dims(4) + logical(C_BOOL) :: indep_ + + dims(:) = shape(buffer) + indep_ = .false. + if (present(indep)) indep_ = indep + + call write_int_c(group_id, 3, dims, to_c_string(name), buffer, indep_) + end subroutine write_integer_4D !=============================================================================== ! READ_INTEGER_1D reads integer precision 1-D array data @@ -834,68 +822,6 @@ contains end if end subroutine read_integer_1D_explicit -!=============================================================================== -! WRITE_INTEGER_2D writes integer precision 2-D array data -!=============================================================================== - - subroutine write_integer_2D(group_id, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - character(*), intent(in) :: name ! name of data - integer, intent(in), target :: buffer(:,:) ! data to write - logical, intent(in), optional :: indep ! independent I/O - - integer(HSIZE_T) :: dims(2) - - dims(:) = shape(buffer) - if (present(indep)) then - call write_integer_2D_explicit(group_id, dims, name, buffer, indep) - else - call write_integer_2D_explicit(group_id, dims, name, buffer) - end if - end subroutine write_integer_2D - - subroutine write_integer_2D_explicit(group_id, dims, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - integer(HSIZE_T), intent(in) :: dims(2) - character(*), intent(in) :: name ! name of data - integer, intent(in), target :: buffer(dims(1),dims(2)) - logical, intent(in), optional :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - call h5screate_simple_f(2, dims, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), H5T_NATIVE_INTEGER, & - dspace, dset, hdf5_err) - f_ptr = c_loc(buffer) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_INTEGER, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end subroutine write_integer_2D_explicit - !=============================================================================== ! READ_INTEGER_2D reads integer precision 2-D array data !=============================================================================== @@ -962,68 +888,6 @@ contains end if end subroutine read_integer_2D_explicit -!=============================================================================== -! WRITE_INTEGER_3D writes integer precision 3-D array data -!=============================================================================== - - subroutine write_integer_3D(group_id, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - character(*), intent(in) :: name ! name of data - integer, intent(in), target :: buffer(:,:,:) ! data to write - logical, intent(in), optional :: indep ! independent I/O - - integer(HSIZE_T) :: dims(3) - - dims(:) = shape(buffer) - if (present(indep)) then - call write_integer_3D_explicit(group_id, dims, name, buffer, indep) - else - call write_integer_3D_explicit(group_id, dims, name, buffer) - end if - end subroutine write_integer_3D - - subroutine write_integer_3D_explicit(group_id, dims, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - integer(HSIZE_T), intent(in) :: dims(3) - character(*), intent(in) :: name ! name of data - integer, intent(in), target :: buffer(dims(1),dims(2),dims(3)) - logical, intent(in), optional :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - call h5screate_simple_f(3, dims, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), H5T_NATIVE_INTEGER, & - dspace, dset, hdf5_err) - f_ptr = c_loc(buffer) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_INTEGER, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end subroutine write_integer_3D_explicit - !=============================================================================== ! READ_INTEGER_3D reads integer precision 3-D array data !=============================================================================== @@ -1090,68 +954,6 @@ contains end if end subroutine read_integer_3D_explicit -!=============================================================================== -! WRITE_INTEGER_4D writes integer precision 4-D array data -!=============================================================================== - - subroutine write_integer_4D(group_id, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - character(*), intent(in) :: name ! name of data - integer, intent(in), target :: buffer(:,:,:,:) ! data to write - logical, intent(in), optional :: indep ! independent I/O - - integer(HSIZE_T) :: dims(4) - - dims(:) = shape(buffer) - if (present(indep)) then - call write_integer_4D_explicit(group_id, dims, name, buffer, indep) - else - call write_integer_4D_explicit(group_id, dims, name, buffer) - end if - end subroutine write_integer_4D - - subroutine write_integer_4D_explicit(group_id, dims, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - integer(HSIZE_T), intent(in) :: dims(4) - character(*), intent(in) :: name ! name of data - integer, intent(in), target :: buffer(dims(1),dims(2),dims(3),dims(4)) - logical, intent(in), optional :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - call h5screate_simple_f(4, dims, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), H5T_NATIVE_INTEGER, & - dspace, dset, hdf5_err) - f_ptr = c_loc(buffer) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_INTEGER, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end subroutine write_integer_4D_explicit - !=============================================================================== ! READ_INTEGER_4D reads integer precision 4-D array data !=============================================================================== diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 6b9bd9798..349c1876d 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -187,8 +187,8 @@ read_double(hid_t obj_id, const char* name, double* buffer, bool indep) void -write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, - const double* buffer, bool indep) +write_array(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + hid_t mem_type_id, const void* buffer, bool indep) { // If array is given, create a simple dataspace. Otherwise, create a scalar // datascape. @@ -199,7 +199,7 @@ write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, dspace = H5Screate(H5S_SCALAR); } - hid_t dset = H5Dcreate(group_id, name, H5T_NATIVE_DOUBLE, dspace, + hid_t dset = H5Dcreate(group_id, name, mem_type_id, dspace, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); if (using_mpio_device(group_id)) { @@ -212,11 +212,11 @@ write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, H5Pset_dxpl_mpio(plist, data_xfer_mode); // Write data - H5Dwrite(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, plist, buffer); + H5Dwrite(dset, mem_type_id, H5S_ALL, H5S_ALL, plist, buffer); H5Pclose(plist); #endif } else { - H5Dwrite(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); + H5Dwrite(dset, mem_type_id, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); } // Free resources @@ -225,6 +225,22 @@ write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, } +void +write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + const double* buffer, bool indep) +{ + write_array(group_id, ndim, dims, name, H5T_NATIVE_DOUBLE, buffer, indep); +} + + +void +write_int(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + const int* buffer, bool indep) +{ + write_array(group_id, ndim, dims, name, H5T_NATIVE_INT, buffer, indep); +} + + void write_string(hid_t group_id, char const *name, char const *buffer) { diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 6902493af..f2bef3b0b 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -44,8 +44,12 @@ write_double_1D(hid_t group_id, char const *name, extern "C" void read_double(hid_t obj_id, const char* name, double* buffer, bool indep); +void write_array(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + hid_t mem_type_id, const void* buffer, bool indep); extern "C" void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const double* buffer, bool indep); +extern "C" void write_int(hid_t group_id, int ndim, const hsize_t* dims, + const char* name, const int* buffer, bool indep); void write_string(hid_t group_id, char const *name, char const *buffer); void write_string(hid_t group_id, char const *name, const std::string &buffer); From 8338c0022e992782d367ca0647e91b3ce0516a13 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Apr 2018 06:56:41 -0500 Subject: [PATCH 186/361] Convert HDF5 read_integer to C++ --- src/hdf5_interface.F90 | 386 ++++++++++------------------------------- src/hdf5_interface.cpp | 21 ++- src/hdf5_interface.h | 4 + 3 files changed, 114 insertions(+), 297 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 562e35661..f1de90d72 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -44,12 +44,12 @@ module hdf5_interface end interface write_dataset interface read_dataset - module procedure read_double + module procedure read_double_0D module procedure read_double_1D module procedure read_double_2D module procedure read_double_3D module procedure read_double_4D - module procedure read_integer + module procedure read_integer_0D module procedure read_integer_1D module procedure read_integer_2D module procedure read_integer_3D @@ -114,6 +114,15 @@ module hdf5_interface logical(C_BOOL), intent(in) :: indep end subroutine read_double_c + subroutine read_int_c(obj_id, name, buffer, indep) & + bind(C, name='read_int') + import HID_T, C_INT, C_BOOL, C_PTR + integer(HID_T), value :: obj_id + type(C_PTR), value :: name + integer(C_INT), intent(in) :: buffer(*) + logical(C_BOOL), intent(in) :: indep + end subroutine read_int_c + subroutine write_double_c(group_id, ndim, dims, name, buffer, indep) & bind(C, name='write_double') import HID_T, HSIZE_T, C_INT, C_DOUBLE, C_CHAR, C_BOOL @@ -402,49 +411,6 @@ contains ! READ_DOUBLE reads double precision scalar data !=============================================================================== - subroutine read_double(buffer, obj_id, name, indep) - real(8), target, intent(inout) :: buffer - integer(HID_T), intent(in) :: obj_id - character(*), optional, intent(in) :: name - logical, optional, intent(in) :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset_id - type(c_ptr) :: f_ptr - - ! If 'name' argument is passed, obj_id is interpreted to be a group and - ! 'name' is the name of the dataset we should read from - if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) - else - dset_id = obj_id - end if - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr = c_loc(buffer) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) - end subroutine read_double !=============================================================================== ! WRITE_DOUBLE_ND writes double precision N-D array data @@ -535,6 +501,30 @@ contains ! READ_DOUBLE_ND reads double precision N-D array data !=============================================================================== + subroutine read_double_0D(buffer, obj_id, name, indep) + real(8), target, intent(inout) :: buffer + integer(HID_T), intent(in) :: obj_id + character(*), optional, intent(in) :: name + logical, optional, intent(in) :: indep ! independent I/O + + real(C_DOUBLE) :: buffer_(1) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep + + ! If 'name' argument is passed, obj_id is interpreted to be a group and + ! 'name' is the name of the dataset we should read from + if (present(name)) then + name_ = to_c_string(name) + call read_double_c(obj_id, c_loc(name_), buffer_, indep_) + else + call read_double_c(obj_id, C_NULL_PTR, buffer_, indep_) + end if + buffer = buffer_(1) + end subroutine read_double_0D + subroutine read_double_1D(buffer, obj_id, name, indep) real(8), target, intent(inout) :: buffer(:) integer(HID_T), intent(in) :: obj_id @@ -627,50 +617,6 @@ contains ! READ_INTEGER reads integer precision scalar data !=============================================================================== - subroutine read_integer(buffer, obj_id, name, indep) - integer, target, intent(inout) :: buffer - integer(HID_T), intent(in) :: obj_id - character(*), optional, intent(in) :: name - logical, optional, intent(in) :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset_id - type(c_ptr) :: f_ptr - - ! If 'name' argument is passed, obj_id is interpreted to be a group and - ! 'name' is the name of the dataset we should read from - if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) - else - dset_id = obj_id - end if - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr = c_loc(buffer) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) - end subroutine read_integer - !=============================================================================== ! WRITE_INTEGER_ND writes integer precision N-D array data !=============================================================================== @@ -757,269 +703,121 @@ contains end subroutine write_integer_4D !=============================================================================== -! READ_INTEGER_1D reads integer precision 1-D array data +! READ_INTEGER_ND reads integer precision N-D array data !=============================================================================== + subroutine read_integer_0D(buffer, obj_id, name, indep) + integer, target, intent(inout) :: buffer + integer(HID_T), intent(in) :: obj_id + character(*), optional, intent(in) :: name + logical, optional, intent(in) :: indep ! independent I/O + + integer(C_INT) :: buffer_(1) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep + + ! If 'name' argument is passed, obj_id is interpreted to be a group and + ! 'name' is the name of the dataset we should read from + if (present(name)) then + name_ = to_c_string(name) + call read_int_c(obj_id, c_loc(name_), buffer_, indep_) + else + call read_int_c(obj_id, C_NULL_PTR, buffer_, indep_) + end if + buffer = buffer_(1) + end subroutine read_integer_0D + subroutine read_integer_1D(buffer, obj_id, name, indep) integer, target, intent(inout) :: buffer(:) integer(HID_T), intent(in) :: obj_id character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer(HID_T) :: dset_id - integer(HSIZE_T) :: dims(1) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep ! If 'name' argument is passed, obj_id is interpreted to be a group and ! 'name' is the name of the dataset we should read from if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + name_ = to_c_string(name) + call read_int_c(obj_id, c_loc(name_), buffer, indep_) else - dset_id = obj_id + call read_int_c(obj_id, C_NULL_PTR, buffer, indep_) end if - - dims(:) = shape(buffer) - - if (present(indep)) then - call read_integer_1D_explicit(dset_id, dims, buffer, indep) - else - call read_integer_1D_explicit(dset_id, dims, buffer) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) end subroutine read_integer_1D - subroutine read_integer_1D_explicit(dset_id, dims, buffer, indep) - integer(HID_T), intent(in) :: dset_id - integer(HSIZE_T), intent(in) :: dims(1) - integer, target, intent(inout) :: buffer(dims(1)) - logical, optional, intent(in) :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr = c_loc(buffer) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end if - end subroutine read_integer_1D_explicit - -!=============================================================================== -! READ_INTEGER_2D reads integer precision 2-D array data -!=============================================================================== - subroutine read_integer_2D(buffer, obj_id, name, indep) integer, target, intent(inout) :: buffer(:,:) integer(HID_T), intent(in) :: obj_id character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer(HID_T) :: dset_id - integer(HSIZE_T) :: dims(2) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep ! If 'name' argument is passed, obj_id is interpreted to be a group and ! 'name' is the name of the dataset we should read from if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + name_ = to_c_string(name) + call read_int_c(obj_id, c_loc(name_), buffer, indep_) else - dset_id = obj_id + call read_int_c(obj_id, C_NULL_PTR, buffer, indep_) end if - - dims(:) = shape(buffer) - - if (present(indep)) then - call read_integer_2D_explicit(dset_id, dims, buffer, indep) - else - call read_integer_2D_explicit(dset_id, dims, buffer) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) end subroutine read_integer_2D - subroutine read_integer_2D_explicit(dset_id, dims, buffer, indep) - integer(HID_T), intent(in) :: dset_id - integer(HSIZE_T), intent(in) :: dims(2) - integer, target, intent(inout) :: buffer(dims(1),dims(2)) - logical, optional, intent(in) :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr = c_loc(buffer) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end if - end subroutine read_integer_2D_explicit - -!=============================================================================== -! READ_INTEGER_3D reads integer precision 3-D array data -!=============================================================================== - subroutine read_integer_3D(buffer, obj_id, name, indep) integer, target, intent(inout) :: buffer(:,:,:) integer(HID_T), intent(in) :: obj_id character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer(HID_T) :: dset_id - integer(HSIZE_T) :: dims(3) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep ! If 'name' argument is passed, obj_id is interpreted to be a group and ! 'name' is the name of the dataset we should read from if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + name_ = to_c_string(name) + call read_int_c(obj_id, c_loc(name_), buffer, indep_) else - dset_id = obj_id + call read_int_c(obj_id, C_NULL_PTR, buffer, indep_) end if - - dims(:) = shape(buffer) - - if (present(indep)) then - call read_integer_3D_explicit(dset_id, dims, buffer, indep) - else - call read_integer_3D_explicit(dset_id, dims, buffer) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) end subroutine read_integer_3D - subroutine read_integer_3D_explicit(dset_id, dims, buffer, indep) - integer(HID_T), intent(in) :: dset_id - integer(HSIZE_T), intent(in) :: dims(3) - integer, target, intent(inout) :: buffer(dims(1),dims(2),dims(3)) - logical, optional, intent(in) :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr = c_loc(buffer) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end if - end subroutine read_integer_3D_explicit - -!=============================================================================== -! READ_INTEGER_4D reads integer precision 4-D array data -!=============================================================================== - subroutine read_integer_4D(buffer, obj_id, name, indep) integer, target, intent(inout) :: buffer(:,:,:,:) integer(HID_T), intent(in) :: obj_id character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer(HID_T) :: dset_id - integer(HSIZE_T) :: dims(4) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep ! If 'name' argument is passed, obj_id is interpreted to be a group and ! 'name' is the name of the dataset we should read from if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + name_ = to_c_string(name) + call read_int_c(obj_id, c_loc(name_), buffer, indep_) else - dset_id = obj_id + call read_int_c(obj_id, C_NULL_PTR, buffer, indep_) end if - - dims(:) = shape(buffer) - - if (present(indep)) then - call read_integer_4D_explicit(dset_id, dims, buffer, indep) - else - call read_integer_4D_explicit(dset_id, dims, buffer) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) end subroutine read_integer_4D - subroutine read_integer_4D_explicit(dset_id, dims, buffer, indep) - integer(HID_T), intent(in) :: dset_id - integer(HSIZE_T), intent(in) :: dims(4) - integer, target, intent(inout) :: buffer(dims(1),dims(2),dims(3),dims(4)) - logical, optional, intent(in) :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr = c_loc(buffer) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end if - end subroutine read_integer_4D_explicit - !=============================================================================== ! WRITE_LONG writes long integer scalar data !=============================================================================== diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 349c1876d..76b3d6fea 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -160,7 +160,8 @@ open_group(hid_t group_id, const char* name) void -read_double(hid_t obj_id, const char* name, double* buffer, bool indep) +read_array(hid_t obj_id, const char* name, hid_t mem_type_id, + void* buffer, bool indep) { hid_t dset = obj_id; if (name) dset = H5Dopen(obj_id, name, H5P_DEFAULT); @@ -175,17 +176,31 @@ read_double(hid_t obj_id, const char* name, double* buffer, bool indep) H5Pset_dxpl_mpio(plist, data_xfer_mode); // Write data - H5Dread(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, plist, buffer); + H5Dread(dset, mem_type_id, H5S_ALL, H5S_ALL, plist, buffer); H5Pclose(plist); #endif } else { - H5Dread(dset, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); + H5Dread(dset, mem_type_id, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); } if (name) H5Dclose(dset); } +void +read_double(hid_t obj_id, const char* name, double* buffer, bool indep) +{ + read_array(obj_id, name, H5T_NATIVE_DOUBLE, buffer, indep); +} + + +void +read_int(hid_t obj_id, const char* name, int* buffer, bool indep) +{ + read_array(obj_id, name, H5T_NATIVE_INT, buffer, indep); +} + + void write_array(hid_t group_id, int ndim, const hsize_t* dims, const char* name, hid_t mem_type_id, const void* buffer, bool indep) diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index f2bef3b0b..b3efda48c 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -41,8 +41,12 @@ write_double_1D(hid_t group_id, char const *name, H5Dclose(dataset); } +void read_array(hid_t obj_id, const char* name, double* buffer, + hid_t mem_type_id, bool indep); extern "C" void read_double(hid_t obj_id, const char* name, double* buffer, bool indep); +extern "C" void read_int(hid_t obj_id, const char* name, int* buffer, + bool indep); void write_array(hid_t group_id, int ndim, const hsize_t* dims, const char* name, hid_t mem_type_id, const void* buffer, bool indep); From aa5a5cf7db0d33e1801e71f02b9fcd0060ede4af Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Apr 2018 07:14:44 -0500 Subject: [PATCH 187/361] Convert HDF5 read/write_long to C++ --- src/hdf5_interface.F90 | 108 ++++++++++++++--------------------------- src/hdf5_interface.cpp | 15 ++++++ src/hdf5_interface.h | 4 ++ 3 files changed, 56 insertions(+), 71 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index f1de90d72..7514c5ba0 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -123,6 +123,15 @@ module hdf5_interface logical(C_BOOL), intent(in) :: indep end subroutine read_int_c + subroutine read_llong_c(obj_id, name, buffer, indep) & + bind(C, name='read_llong') + import HID_T, C_INT, C_BOOL, C_PTR, C_LONG_LONG + integer(HID_T), value :: obj_id + type(C_PTR), value :: name + integer(C_LONG_LONG), intent(in) :: buffer(*) + logical(C_BOOL), intent(in) :: indep + end subroutine read_llong_c + subroutine write_double_c(group_id, ndim, dims, name, buffer, indep) & bind(C, name='write_double') import HID_T, HSIZE_T, C_INT, C_DOUBLE, C_CHAR, C_BOOL @@ -144,6 +153,17 @@ module hdf5_interface integer(C_INT), intent(in) :: buffer(*) logical(C_BOOL), intent(in) :: indep end subroutine write_int_c + + subroutine write_llong_c(group_id, ndim, dims, name, buffer, indep) & + bind(C, name='write_llong') + import HID_T, HSIZE_T, C_INT, C_CHAR, C_BOOL, C_LONG_LONG + integer(HID_T), value :: group_id + integer(C_INT), value :: ndim + integer(HSIZE_T), intent(in) :: dims(*) + character(kind=C_CHAR), intent(in) :: name(*) + integer(C_LONG_LONG), intent(in) :: buffer(*) + logical(C_BOOL), intent(in) :: indep + end subroutine write_llong_c end interface contains @@ -407,11 +427,6 @@ contains end if end subroutine close_dataset -!=============================================================================== -! READ_DOUBLE reads double precision scalar data -!=============================================================================== - - !=============================================================================== ! WRITE_DOUBLE_ND writes double precision N-D array data !=============================================================================== @@ -613,10 +628,6 @@ contains end if end subroutine read_double_4D -!=============================================================================== -! READ_INTEGER reads integer precision scalar data -!=============================================================================== - !=============================================================================== ! WRITE_INTEGER_ND writes integer precision N-D array data !=============================================================================== @@ -828,40 +839,15 @@ contains integer(8), intent(in), target :: buffer ! data to write logical, intent(in), optional :: indep ! independent I/O - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - type(c_ptr) :: f_ptr + integer(HSIZE_T) :: dims(0) + logical(C_BOOL) :: indep_ + integer(C_LONG_LONG) :: buffer_(1) - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if + indep_ = .false. + if (present(indep)) indep_ = indep + buffer_(1) = buffer - ! Create dataspace and dataset - call h5screate_f(H5S_SCALAR_F, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), hdf5_integer8_t, & - dspace, dset, hdf5_err) - f_ptr = c_loc(buffer) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, hdf5_integer8_t, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, hdf5_integer8_t, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) + call write_llong_c(group_id, 0, dims, to_c_string(name), buffer_, indep_) end subroutine write_long !=============================================================================== @@ -874,42 +860,22 @@ contains character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset_id - type(c_ptr) :: f_ptr + integer(C_LONG_LONG) :: buffer_(1) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep ! If 'name' argument is passed, obj_id is interpreted to be a group and ! 'name' is the name of the dataset we should read from if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + name_ = to_c_string(name) + call read_llong_c(obj_id, c_loc(name_), buffer_, indep_) else - dset_id = obj_id + call read_llong_c(obj_id, C_NULL_PTR, buffer_, indep_) end if - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr = c_loc(buffer) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, hdf5_integer8_t, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, hdf5_integer8_t, f_ptr, hdf5_err) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) + buffer = buffer_(1) end subroutine read_long !=============================================================================== diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 76b3d6fea..78fcad349 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -201,6 +201,13 @@ read_int(hid_t obj_id, const char* name, int* buffer, bool indep) } +void +read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep) +{ + read_array(obj_id, name, H5T_NATIVE_LLONG, buffer, indep); +} + + void write_array(hid_t group_id, int ndim, const hsize_t* dims, const char* name, hid_t mem_type_id, const void* buffer, bool indep) @@ -256,6 +263,14 @@ write_int(hid_t group_id, int ndim, const hsize_t* dims, const char* name, } +void +write_llong(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + const long long* buffer, bool indep) +{ + write_array(group_id, ndim, dims, name, H5T_NATIVE_LLONG, buffer, indep); +} + + void write_string(hid_t group_id, char const *name, char const *buffer) { diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index b3efda48c..8e47b528f 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -47,6 +47,8 @@ extern "C" void read_double(hid_t obj_id, const char* name, double* buffer, bool indep); extern "C" void read_int(hid_t obj_id, const char* name, int* buffer, bool indep); +extern "C" void read_llong(hid_t obj_id, const char* name, long long* buffer, + bool indep); void write_array(hid_t group_id, int ndim, const hsize_t* dims, const char* name, hid_t mem_type_id, const void* buffer, bool indep); @@ -54,6 +56,8 @@ extern "C" void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const double* buffer, bool indep); extern "C" void write_int(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const int* buffer, bool indep); +extern "C" void write_llong(hid_t group_id, int ndim, const hsize_t* dims, + const char* name, const long long* buffer, bool indep); void write_string(hid_t group_id, char const *name, char const *buffer); void write_string(hid_t group_id, char const *name, const std::string &buffer); From 2760bce6796c2c120e30bfe48271c2627941896e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Apr 2018 09:46:28 -0500 Subject: [PATCH 188/361] Convert HDF5 write_string to C++ --- src/hdf5_interface.F90 | 63 +++++++++--------------------------------- src/hdf5_interface.cpp | 42 ++++++++++++++-------------- src/hdf5_interface.h | 12 ++++---- src/surface.cpp | 34 +++++++++++------------ 4 files changed, 56 insertions(+), 95 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 7514c5ba0..1a3413820 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -164,6 +164,15 @@ module hdf5_interface integer(C_LONG_LONG), intent(in) :: buffer(*) logical(C_BOOL), intent(in) :: indep end subroutine write_llong_c + + subroutine write_string_c(group_id, name, buffer, indep) & + bind(C, name='write_string') + import HID_T, HSIZE_T, C_CHAR, C_BOOL + integer(HID_T), value :: group_id + character(kind=C_CHAR), intent(in) :: name(*) + character(kind=C_CHAR), intent(in) :: buffer(*) + logical(C_BOOL), intent(in) :: indep + end subroutine write_string_c end interface contains @@ -888,58 +897,12 @@ contains character(*), intent(in), target :: buffer ! read data to here logical, intent(in), optional :: indep ! independent I/O - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - integer(HID_T) :: filetype - integer(SIZE_T) :: i, n - character(kind=C_CHAR), allocatable, target :: temp_buffer(:) - type(c_ptr) :: f_ptr + logical(C_BOOL) :: indep_ - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if + indep_ = .false. + if (present(indep)) indep_ = indep - ! Create datatype for HDF5 file based on C char - n = len_trim(buffer) - if (n > 0) then - call h5tcopy_f(H5T_C_S1, filetype, hdf5_err) - call h5tset_size_f(filetype, n, hdf5_err) - - ! Create dataspace/dataset - call h5screate_f(H5S_SCALAR_F, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), filetype, dspace, dset, hdf5_err) - - ! Copy string to temporary buffer - allocate(temp_buffer(n)) - do i = 1, n - temp_buffer(i) = buffer(i:i) - end do - - ! Get pointer to start of string - f_ptr = c_loc(temp_buffer(1)) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dwrite_f(dset, filetype, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dwrite_f(dset, filetype, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - call h5tclose_f(filetype, hdf5_err) - end if + call write_string_c(group_id, to_c_string(name), to_c_string(buffer), indep_) end subroutine write_string !=============================================================================== diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 78fcad349..d78bf83d8 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -5,6 +5,7 @@ #include "hdf5_hl.h" #include +#include #include #include @@ -160,7 +161,7 @@ open_group(hid_t group_id, const char* name) void -read_array(hid_t obj_id, const char* name, hid_t mem_type_id, +read_data(hid_t obj_id, const char* name, hid_t mem_type_id, void* buffer, bool indep) { hid_t dset = obj_id; @@ -190,26 +191,26 @@ read_array(hid_t obj_id, const char* name, hid_t mem_type_id, void read_double(hid_t obj_id, const char* name, double* buffer, bool indep) { - read_array(obj_id, name, H5T_NATIVE_DOUBLE, buffer, indep); + read_data(obj_id, name, H5T_NATIVE_DOUBLE, buffer, indep); } void read_int(hid_t obj_id, const char* name, int* buffer, bool indep) { - read_array(obj_id, name, H5T_NATIVE_INT, buffer, indep); + read_data(obj_id, name, H5T_NATIVE_INT, buffer, indep); } void read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep) { - read_array(obj_id, name, H5T_NATIVE_LLONG, buffer, indep); + read_data(obj_id, name, H5T_NATIVE_LLONG, buffer, indep); } void -write_array(hid_t group_id, int ndim, const hsize_t* dims, const char* name, +write_data(hid_t group_id, int ndim, const hsize_t* dims, const char* name, hid_t mem_type_id, const void* buffer, bool indep) { // If array is given, create a simple dataspace. Otherwise, create a scalar @@ -251,7 +252,7 @@ void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const double* buffer, bool indep) { - write_array(group_id, ndim, dims, name, H5T_NATIVE_DOUBLE, buffer, indep); + write_data(group_id, ndim, dims, name, H5T_NATIVE_DOUBLE, buffer, indep); } @@ -259,7 +260,7 @@ void write_int(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const int* buffer, bool indep) { - write_array(group_id, ndim, dims, name, H5T_NATIVE_INT, buffer, indep); + write_data(group_id, ndim, dims, name, H5T_NATIVE_INT, buffer, indep); } @@ -267,34 +268,31 @@ void write_llong(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const long long* buffer, bool indep) { - write_array(group_id, ndim, dims, name, H5T_NATIVE_LLONG, buffer, indep); + write_data(group_id, ndim, dims, name, H5T_NATIVE_LLONG, buffer, indep); } void -write_string(hid_t group_id, char const *name, char const *buffer) +write_string(hid_t group_id, char const* name, const char* buffer, bool indep) { size_t buffer_len = strlen(buffer); - hid_t datatype = H5Tcopy(H5T_C_S1); - H5Tset_size(datatype, buffer_len); + if (buffer_len > 0) { + // Set up appropriate datatype for a fixed-length string + hid_t datatype = H5Tcopy(H5T_C_S1); + H5Tset_size(datatype, buffer_len); - hid_t dataspace = H5Screate(H5S_SCALAR); + write_data(group_id, 0, nullptr, name, datatype, buffer, indep); - 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); + // Free resources + H5Tclose(datatype); + } } void -write_string(hid_t group_id, char const *name, const std::string &buffer) +write_string(hid_t group_id, char const* name, const std::string& buffer, bool indep) { - write_string(group_id, name, buffer.c_str()); + write_string(group_id, name, buffer.c_str(), indep); } } diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 8e47b528f..41883d926 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -41,8 +41,8 @@ write_double_1D(hid_t group_id, char const *name, H5Dclose(dataset); } -void read_array(hid_t obj_id, const char* name, double* buffer, - hid_t mem_type_id, bool indep); +void read_data(hid_t obj_id, const char* name, double* buffer, + hid_t mem_type_id, bool indep); extern "C" void read_double(hid_t obj_id, const char* name, double* buffer, bool indep); extern "C" void read_int(hid_t obj_id, const char* name, int* buffer, @@ -50,8 +50,8 @@ extern "C" void read_int(hid_t obj_id, const char* name, int* buffer, extern "C" void read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep); -void write_array(hid_t group_id, int ndim, const hsize_t* dims, const char* name, - hid_t mem_type_id, const void* buffer, bool indep); +void write_data(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + hid_t mem_type_id, const void* buffer, bool indep); extern "C" void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const double* buffer, bool indep); extern "C" void write_int(hid_t group_id, int ndim, const hsize_t* dims, @@ -59,8 +59,8 @@ extern "C" void write_int(hid_t group_id, int ndim, const hsize_t* dims, extern "C" void write_llong(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const long long* buffer, bool indep); -void write_string(hid_t group_id, char const *name, char const *buffer); -void write_string(hid_t group_id, char const *name, const std::string &buffer); +extern "C" void write_string(hid_t group_id, char const *name, char const *buffer, bool indep); +void write_string(hid_t group_id, char const *name, const std::string &buffer, bool indep); } // namespace openmc #endif //HDF5_INTERFACE_H diff --git a/src/surface.cpp b/src/surface.cpp index 9abfd23d2..9db9db3fa 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -202,21 +202,21 @@ Surface::to_hdf5(hid_t group_id) const switch(bc) { case BC_TRANSMIT : - write_string(surf_group, "boundary_type", "transmission"); + write_string(surf_group, "boundary_type", "transmission", false); break; case BC_VACUUM : - write_string(surf_group, "boundary_type", "vacuum"); + write_string(surf_group, "boundary_type", "vacuum", false); break; case BC_REFLECT : - write_string(surf_group, "boundary_type", "reflective"); + write_string(surf_group, "boundary_type", "reflective", false); break; case BC_PERIODIC : - write_string(surf_group, "boundary_type", "periodic"); + write_string(surf_group, "boundary_type", "periodic", false); break; } if (!name.empty()) { - write_string(surf_group, "name", name); + write_string(surf_group, "name", name, false); } to_hdf5_inner(surf_group); @@ -297,7 +297,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"); + write_string(group_id, "type", "x-plane", false); std::array coeffs {{x0}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -362,7 +362,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"); + write_string(group_id, "type", "y-plane", false); std::array coeffs {{y0}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -428,7 +428,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"); + write_string(group_id, "type", "z-plane", false); std::array coeffs {{z0}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -489,7 +489,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"); + write_string(group_id, "type", "plane", false); std::array coeffs {{A, B, C, D}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -621,7 +621,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"); + write_string(group_id, "type", "x-cylinder", false); std::array coeffs {{y0, z0, r}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -655,7 +655,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"); + write_string(group_id, "type", "y-cylinder", false); std::array coeffs {{x0, z0, r}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -689,7 +689,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"); + write_string(group_id, "type", "z-cylinder", false); std::array coeffs {{x0, y0, r}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -760,7 +760,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"); + write_string(group_id, "type", "sphere", false); std::array coeffs {{x0, y0, z0, r}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -877,7 +877,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"); + write_string(group_id, "type", "x-cone", false); std::array coeffs {{x0, y0, z0, r_sq}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -911,7 +911,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"); + write_string(group_id, "type", "y-cone", false); std::array coeffs {{x0, y0, z0, r_sq}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -945,7 +945,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"); + write_string(group_id, "type", "z-cone", false); std::array coeffs {{x0, y0, z0, r_sq}}; write_double_1D(group_id, "coefficients", coeffs); } @@ -1039,7 +1039,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"); + write_string(group_id, "type", "quadric", false); std::array coeffs {{A, B, C, D, E, F, G, H, J, K}}; write_double_1D(group_id, "coefficients", coeffs); } From f8e7d52d39c8933e96c174fbec91b9c14b6fea13 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Apr 2018 13:03:10 -0500 Subject: [PATCH 189/361] Bugfix for writing 4D array --- src/hdf5_interface.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 1a3413820..be6bce775 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -518,7 +518,7 @@ contains indep_ = .false. if (present(indep)) indep_ = indep - call write_double_c(group_id, 1, dims, to_c_string(name), buffer, indep_) + call write_double_c(group_id, 4, dims, to_c_string(name), buffer, indep_) end subroutine write_double_4D !=============================================================================== From cc5e99eac4c8f7644d86d1367d221e4f1b504e37 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Apr 2018 13:38:34 -0500 Subject: [PATCH 190/361] Fix dimension bug and implement write_string_1D using write_string_c --- src/hdf5_interface.F90 | 135 ++++++++++++++++++----------------------- src/hdf5_interface.cpp | 12 ++-- src/hdf5_interface.h | 5 +- 3 files changed, 69 insertions(+), 83 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index be6bce775..b9787ba1e 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -165,10 +165,13 @@ module hdf5_interface logical(C_BOOL), intent(in) :: indep end subroutine write_llong_c - subroutine write_string_c(group_id, name, buffer, indep) & + subroutine write_string_c(group_id, ndim, dims, slen, name, buffer, indep) & bind(C, name='write_string') - import HID_T, HSIZE_T, C_CHAR, C_BOOL + import HID_T, HSIZE_T, C_INT, C_CHAR, C_BOOL, C_SIZE_T integer(HID_T), value :: group_id + integer(C_INT), value :: ndim + integer(HSIZE_T), intent(in) :: dims(*) + integer(C_SIZE_T), value :: slen character(kind=C_CHAR), intent(in) :: name(*) character(kind=C_CHAR), intent(in) :: buffer(*) logical(C_BOOL), intent(in) :: indep @@ -466,7 +469,7 @@ contains integer(HSIZE_T) :: dims(1) logical(C_BOOL) :: indep_ - dims(:) = shape(buffer) + dims(1) = size(buffer) indep_ = .false. if (present(indep)) indep_ = indep @@ -482,7 +485,10 @@ contains integer(HSIZE_T) :: dims(2) logical(C_BOOL) :: indep_ - dims(:) = shape(buffer) + ! Reverse shape of array since it will be written from C + dims(1) = size(buffer, 2) + dims(2) = size(buffer, 1) + indep_ = .false. if (present(indep)) indep_ = indep @@ -498,7 +504,11 @@ contains integer(HSIZE_T) :: dims(3) logical(C_BOOL) :: indep_ - dims(:) = shape(buffer) + ! Reverse shape of array since it will be written from C + dims(1) = size(buffer, 3) + dims(2) = size(buffer, 2) + dims(3) = size(buffer, 1) + indep_ = .false. if (present(indep)) indep_ = indep @@ -514,7 +524,12 @@ contains integer(HSIZE_T) :: dims(4) logical(C_BOOL) :: indep_ - dims(:) = shape(buffer) + ! Reverse shape of array since it will be written from C + dims(1) = size(buffer, 4) + dims(2) = size(buffer, 3) + dims(3) = size(buffer, 2) + dims(4) = size(buffer, 1) + indep_ = .false. if (present(indep)) indep_ = indep @@ -667,7 +682,7 @@ contains integer(HSIZE_T) :: dims(1) logical(C_BOOL) :: indep_ - dims(:) = shape(buffer) + dims(1) = size(buffer) indep_ = .false. if (present(indep)) indep_ = indep @@ -683,7 +698,10 @@ contains integer(HSIZE_T) :: dims(2) logical(C_BOOL) :: indep_ - dims(:) = shape(buffer) + ! Reverse shape of array since it will be written from C + dims(1) = size(buffer, 2) + dims(2) = size(buffer, 1) + indep_ = .false. if (present(indep)) indep_ = indep @@ -699,7 +717,11 @@ contains integer(HSIZE_T) :: dims(3) logical(C_BOOL) :: indep_ - dims(:) = shape(buffer) + ! Reverse shape of array since it will be written from C + dims(1) = size(buffer, 3) + dims(2) = size(buffer, 2) + dims(3) = size(buffer, 1) + indep_ = .false. if (present(indep)) indep_ = indep @@ -715,7 +737,12 @@ contains integer(HSIZE_T) :: dims(4) logical(C_BOOL) :: indep_ - dims(:) = shape(buffer) + ! Reverse shape of array since it will be written from C + dims(1) = size(buffer, 4) + dims(2) = size(buffer, 3) + dims(3) = size(buffer, 2) + dims(4) = size(buffer, 1) + indep_ = .false. if (present(indep)) indep_ = indep @@ -897,12 +924,16 @@ contains character(*), intent(in), target :: buffer ! read data to here logical, intent(in), optional :: indep ! independent I/O + integer(HSIZE_T) :: dims(0) + integer(C_SIZE_T) :: slen logical(C_BOOL) :: indep_ indep_ = .false. if (present(indep)) indep_ = indep + slen = len_trim(buffer) - call write_string_c(group_id, to_c_string(name), to_c_string(buffer), indep_) + call write_string_c(group_id, 0, dims, slen, to_c_string(name), & + to_c_string(buffer), indep_) end subroutine write_string !=============================================================================== @@ -988,74 +1019,28 @@ contains character(*), intent(in), target :: buffer(:) ! read data to here logical, intent(in), optional :: indep ! independent I/O + integer :: i integer(HSIZE_T) :: dims(1) + integer(C_SIZE_T) :: m + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), allocatable :: buffer_(:) - dims(:) = shape(buffer) - if (present(indep)) then - call write_string_1D_explicit(group_id, dims, name, buffer, indep) - else - call write_string_1D_explicit(group_id, dims, name, buffer) - end if + indep_ = .false. + if (present(indep)) indep_ = indep + + ! Copy array of characters into an array of C chars with the right memory + ! layout + dims(1) = size(buffer) + m = maxval(len_trim(buffer)) + 1 + allocate(buffer_(dims(1)*m)) + do i = 0, dims(1) - 1 + buffer_(i*m+1 : (i+1)*m) = to_c_string(buffer(i+1)) + end do + + call write_string_c(group_id, 1, dims, m, to_c_string(name), & + buffer_, indep_) end subroutine write_string_1D - subroutine write_string_1D_explicit(group_id, dims, name, buffer, indep) - integer(HID_T), intent(in) :: group_id - integer(HSIZE_T), intent(in) :: dims(1) - character(*), intent(in) :: name - character(*), intent(in), target :: buffer(dims(1)) - logical, intent(in), optional :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - integer(HID_T) :: filetype - integer(HID_T) :: memtype - integer(SIZE_T) :: n - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - ! Create datatype for HDF5 file based on C char - n = maxval(len_trim(buffer)) - call h5tcopy_f(H5T_C_S1, filetype, hdf5_err) - call h5tset_size_f(filetype, n + 1, hdf5_err) - - ! Create datatype in memory based on Fortran character - call h5tcopy_f(H5T_FORTRAN_S1, memtype, hdf5_err) - call h5tset_size_f(memtype, int(len(buffer(1)), SIZE_T), hdf5_err) - - ! Create dataspace/dataset - call h5screate_simple_f(1, dims, dspace, hdf5_err) - call h5dcreate_f(group_id, trim(name), filetype, dspace, dset, hdf5_err) - - ! Get pointer to start of string - f_ptr = c_loc(buffer(1)(1:1)) - - if (using_mpio_device(group_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - if (n > 0) call h5dwrite_f(dset, memtype, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - if (n > 0) call h5dwrite_f(dset, memtype, f_ptr, hdf5_err) - end if - - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - call h5tclose_f(memtype, hdf5_err) - call h5tclose_f(filetype, hdf5_err) - end subroutine write_string_1D_explicit - !=============================================================================== ! READ_STRING_1D reads string 1-D array data !=============================================================================== diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index d78bf83d8..0b8000f70 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -273,15 +273,15 @@ write_llong(hid_t group_id, int ndim, const hsize_t* dims, const char* name, void -write_string(hid_t group_id, char const* name, const char* buffer, bool indep) +write_string(hid_t group_id, int ndim, const hsize_t* dims, size_t slen, + const char* name, const char* buffer, bool indep) { - size_t buffer_len = strlen(buffer); - if (buffer_len > 0) { + if (slen > 0) { // Set up appropriate datatype for a fixed-length string hid_t datatype = H5Tcopy(H5T_C_S1); - H5Tset_size(datatype, buffer_len); + H5Tset_size(datatype, slen); - write_data(group_id, 0, nullptr, name, datatype, buffer, indep); + write_data(group_id, ndim, dims, name, datatype, buffer, indep); // Free resources H5Tclose(datatype); @@ -292,7 +292,7 @@ write_string(hid_t group_id, char const* name, const char* buffer, bool indep) void write_string(hid_t group_id, char const* name, const std::string& buffer, bool indep) { - write_string(group_id, name, buffer.c_str(), indep); + write_string(group_id, 0, nullptr, buffer.length(), name, buffer.c_str(), indep); } } diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 41883d926..11f26a5f1 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -59,8 +59,9 @@ extern "C" void write_int(hid_t group_id, int ndim, const hsize_t* dims, extern "C" void write_llong(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const long long* buffer, bool indep); -extern "C" void write_string(hid_t group_id, char const *name, char const *buffer, bool indep); -void write_string(hid_t group_id, char const *name, const std::string &buffer, bool indep); +extern "C" void write_string(hid_t group_id, int ndim, const hsize_t* dims, size_t slen, + const char* name, char const* buffer, bool indep); +void write_string(hid_t group_id, const char* name, const std::string& buffer, bool indep); } // namespace openmc #endif //HDF5_INTERFACE_H From 342d207fc4eaffe1158c4fb7a85fa1c43740e622 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Apr 2018 21:57:57 -0500 Subject: [PATCH 191/361] Convert HDF5 write_attribute to C++ --- src/hdf5_interface.F90 | 141 ++++++++++++----------------------------- src/hdf5_interface.cpp | 77 +++++++++++++++++++--- src/hdf5_interface.h | 17 +++-- 3 files changed, 121 insertions(+), 114 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index b9787ba1e..1d056e1dc 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -132,6 +132,34 @@ module hdf5_interface logical(C_BOOL), intent(in) :: indep end subroutine read_llong_c + subroutine write_attr_double_c(obj_id, ndim, dims, name, buffer) & + bind(C, name='write_attr_double') + import HID_T, HSIZE_T, C_INT, C_DOUBLE, C_CHAR + integer(HID_T), value :: obj_id + integer(C_INT), value :: ndim + integer(HSIZE_T), intent(in) :: dims(*) + character(kind=C_CHAR), intent(in) :: name(*) + real(C_DOUBLE), intent(in) :: buffer(*) + end subroutine write_attr_double_c + + subroutine write_attr_int_c(obj_id, ndim, dims, name, buffer) & + bind(C, name='write_attr_int') + import HID_T, HSIZE_T, C_INT, C_CHAR + integer(HID_T), value :: obj_id + integer(C_INT), value :: ndim + integer(HSIZE_T), intent(in) :: dims(*) + character(kind=C_CHAR), intent(in) :: name(*) + integer(C_INT), intent(in) :: buffer(*) + end subroutine write_attr_int_c + + subroutine write_attr_string_c(obj_id, name, buffer) & + bind(C, name='write_attr_string') + import HID_T, C_CHAR + integer(HID_T), value :: obj_id + character(kind=C_CHAR), intent(in) :: name(*) + character(kind=C_CHAR), intent(in) :: buffer(*) + end subroutine write_attr_string_c + subroutine write_double_c(group_id, ndim, dims, name, buffer, indep) & bind(C, name='write_double') import HID_T, HSIZE_T, C_INT, C_DOUBLE, C_CHAR, C_BOOL @@ -1141,41 +1169,8 @@ contains character(*), intent(in) :: name ! name of attribute character(*), intent(in), target :: buffer ! string to write - integer :: hdf5_err - integer(HID_T) :: dspace_id - integer(HID_T) :: attr_id - integer(HID_T) :: filetype - integer(SIZE_T) :: i - integer(SIZE_T) :: n - character(kind=C_CHAR), allocatable, target :: temp_buffer(:) - type(c_ptr) :: f_ptr - ! Create datatype for HDF5 file based on C char - n = len_trim(buffer) - if (n > 0) then - call h5tcopy_f(H5T_C_S1, filetype, hdf5_err) - call h5tset_size_f(filetype, n, hdf5_err) - - ! Create memory space and attribute - call h5screate_f(H5S_SCALAR_F, dspace_id, hdf5_err) - call h5acreate_f(obj_id, trim(name), filetype, dspace_id, & - attr_id, hdf5_err) - - ! Copy string to temporary buffer - allocate(temp_buffer(n)) - do i = 1, n - temp_buffer(i) = buffer(i:i) - end do - - ! Write attribute - f_ptr = c_loc(buffer(1:1)) - call h5awrite_f(attr_id, filetype, f_ptr, hdf5_err) - - ! Close attribute - call h5aclose_f(attr_id, hdf5_err) - call h5sclose_f(dspace_id, hdf5_err) - call h5tclose_f(filetype, hdf5_err) - end if + call write_attr_string_c(obj_id, to_c_string(name), to_c_string(buffer)) end subroutine write_attribute_string subroutine read_attribute_double(buffer, obj_id, name) @@ -1198,18 +1193,11 @@ contains character(*), intent(in) :: name real(8), intent(in), target :: buffer - integer :: hdf5_err - integer(HID_T) :: dspace_id - integer(HID_T) :: attr_id - type(C_PTR) :: f_ptr + integer(HSIZE_T) :: dims(0) + real(C_DOUBLE) :: buffer_(1) - call h5screate_f(H5S_SCALAR_F, dspace_id, hdf5_err) - call h5acreate_f(obj_id, trim(name), H5T_NATIVE_DOUBLE, dspace_id, & - attr_id, hdf5_err) - f_ptr = c_loc(buffer) - call h5awrite_f(attr_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - call h5aclose_f(attr_id, hdf5_err) - call h5sclose_f(dspace_id, hdf5_err) + buffer_(1) = buffer + call write_attr_double_c(obj_id, 0, dims, to_c_string(name), buffer_) end subroutine write_attribute_double subroutine read_attribute_double_1D(buffer, obj_id, name) @@ -1257,30 +1245,10 @@ contains integer(HSIZE_T) :: dims(1) - dims(:) = shape(buffer) - call write_attribute_double_1D_explicit(obj_id, dims, name, buffer) + dims(1) = size(buffer) + call write_attr_double_c(obj_id, 1, dims, to_c_string(name), buffer) end subroutine write_attribute_double_1D - subroutine write_attribute_double_1D_explicit(obj_id, dims, name, buffer) - integer(HID_T), intent(in) :: obj_id - integer(HSIZE_T), intent(in) :: dims(1) - character(*), intent(in) :: name - real(8), target, intent(in) :: buffer(dims(1)) - - integer :: hdf5_err - integer(HID_T) :: dspace_id - integer(HID_T) :: attr_id - type(C_PTR) :: f_ptr - - call h5screate_simple_f(1, dims, dspace_id, hdf5_err) - call h5acreate_f(obj_id, trim(name), H5T_NATIVE_DOUBLE, dspace_id, & - attr_id, hdf5_err) - f_ptr = c_loc(buffer) - call h5awrite_f(attr_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - call h5aclose_f(attr_id, hdf5_err) - call h5sclose_f(dspace_id, hdf5_err) - end subroutine write_attribute_double_1D_explicit - subroutine read_attribute_double_2D(buffer, obj_id, name) real(8), target, allocatable, intent(inout) :: buffer(:,:) integer(HID_T), intent(in) :: obj_id @@ -1339,18 +1307,11 @@ contains character(*), intent(in) :: name integer, intent(in), target :: buffer - integer :: hdf5_err - integer(HID_T) :: dspace_id - integer(HID_T) :: attr_id - type(C_PTR) :: f_ptr + integer(HSIZE_T) :: dims(0) + integer(C_INT) :: buffer_(1) - call h5screate_f(H5S_SCALAR_F, dspace_id, hdf5_err) - call h5acreate_f(obj_id, trim(name), H5T_NATIVE_INTEGER, dspace_id, & - attr_id, hdf5_err) - f_ptr = c_loc(buffer) - call h5awrite_f(attr_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - call h5aclose_f(attr_id, hdf5_err) - call h5sclose_f(dspace_id, hdf5_err) + buffer_(1) = buffer + call write_attr_int_c(obj_id, 0, dims, to_c_string(name), buffer_) end subroutine write_attribute_integer subroutine read_attribute_integer_1D(buffer, obj_id, name) @@ -1398,30 +1359,10 @@ contains integer(HSIZE_T) :: dims(1) - dims(:) = shape(buffer) - call write_attribute_integer_1D_explicit(obj_id, dims, name, buffer) + dims(1) = size(buffer) + call write_attr_int_c(obj_id, 1, dims, to_c_string(name), buffer) end subroutine write_attribute_integer_1D - subroutine write_attribute_integer_1D_explicit(obj_id, dims, name, buffer) - integer(HID_T), intent(in) :: obj_id - integer(HSIZE_T), intent(in) :: dims(1) - character(*), intent(in) :: name - integer, target, intent(in) :: buffer(dims(1)) - - integer :: hdf5_err - integer(HID_T) :: dspace_id - integer(HID_T) :: attr_id - type(C_PTR) :: f_ptr - - call h5screate_simple_f(1, dims, dspace_id, hdf5_err) - call h5acreate_f(obj_id, trim(name), H5T_NATIVE_INTEGER, dspace_id, & - attr_id, hdf5_err) - f_ptr = c_loc(buffer) - call h5awrite_f(attr_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - call h5aclose_f(attr_id, hdf5_err) - call h5sclose_f(dspace_id, hdf5_err) - end subroutine write_attribute_integer_1D_explicit - subroutine read_attribute_integer_2D(buffer, obj_id, name) integer, target, allocatable, intent(inout) :: buffer(:,:) integer(HID_T), intent(in) :: obj_id diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 0b8000f70..f482285f2 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -161,7 +161,7 @@ open_group(hid_t group_id, const char* name) void -read_data(hid_t obj_id, const char* name, hid_t mem_type_id, +read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id, void* buffer, bool indep) { hid_t dset = obj_id; @@ -191,27 +191,84 @@ read_data(hid_t obj_id, const char* name, hid_t mem_type_id, void read_double(hid_t obj_id, const char* name, double* buffer, bool indep) { - read_data(obj_id, name, H5T_NATIVE_DOUBLE, buffer, indep); + read_dataset(obj_id, name, H5T_NATIVE_DOUBLE, buffer, indep); } void read_int(hid_t obj_id, const char* name, int* buffer, bool indep) { - read_data(obj_id, name, H5T_NATIVE_INT, buffer, indep); + read_dataset(obj_id, name, H5T_NATIVE_INT, buffer, indep); } void read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep) { - read_data(obj_id, name, H5T_NATIVE_LLONG, buffer, indep); + read_dataset(obj_id, name, H5T_NATIVE_LLONG, buffer, indep); } void -write_data(hid_t group_id, int ndim, const hsize_t* dims, const char* name, - hid_t mem_type_id, const void* buffer, bool indep) +write_attr(hid_t obj_id, int ndim, const hsize_t* dims, const char* name, + hid_t mem_type_id, const void* buffer) +{ + // If array is given, create a simple dataspace. Otherwise, create a scalar + // datascape. + hid_t dspace; + if (ndim > 0) { + dspace = H5Screate_simple(ndim, dims, nullptr); + } else { + dspace = H5Screate(H5S_SCALAR); + } + + // Create attribute and Write data + hid_t attr = H5Acreate(obj_id, name, mem_type_id, dspace, + H5P_DEFAULT, H5P_DEFAULT); + H5Awrite(attr, mem_type_id, buffer); + + // Free resources + H5Aclose(attr); + H5Sclose(dspace); +} + + +void +write_attr_double(hid_t obj_id, int ndim, const hsize_t* dims, const char* name, + const double* buffer) +{ + write_attr(obj_id, ndim, dims, name, H5T_NATIVE_DOUBLE, buffer); +} + + +void +write_attr_int(hid_t obj_id, int ndim, const hsize_t* dims, const char* name, + const int* buffer) +{ + write_attr(obj_id, ndim, dims, name, H5T_NATIVE_INT, buffer); +} + + +void +write_attr_string(hid_t obj_id, const char* name, const char* buffer) +{ + size_t n = strlen(buffer); + if (n > 0) { + // Set up appropriate datatype for a fixed-length string + hid_t datatype = H5Tcopy(H5T_C_S1); + H5Tset_size(datatype, n); + + write_attr(obj_id, 0, nullptr, name, datatype, buffer); + + // Free resources + H5Tclose(datatype); + } +} + + +void +write_dataset(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + hid_t mem_type_id, const void* buffer, bool indep) { // If array is given, create a simple dataspace. Otherwise, create a scalar // datascape. @@ -252,7 +309,7 @@ void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const double* buffer, bool indep) { - write_data(group_id, ndim, dims, name, H5T_NATIVE_DOUBLE, buffer, indep); + write_dataset(group_id, ndim, dims, name, H5T_NATIVE_DOUBLE, buffer, indep); } @@ -260,7 +317,7 @@ void write_int(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const int* buffer, bool indep) { - write_data(group_id, ndim, dims, name, H5T_NATIVE_INT, buffer, indep); + write_dataset(group_id, ndim, dims, name, H5T_NATIVE_INT, buffer, indep); } @@ -268,7 +325,7 @@ void write_llong(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const long long* buffer, bool indep) { - write_data(group_id, ndim, dims, name, H5T_NATIVE_LLONG, buffer, indep); + write_dataset(group_id, ndim, dims, name, H5T_NATIVE_LLONG, buffer, indep); } @@ -281,7 +338,7 @@ write_string(hid_t group_id, int ndim, const hsize_t* dims, size_t slen, hid_t datatype = H5Tcopy(H5T_C_S1); H5Tset_size(datatype, slen); - write_data(group_id, ndim, dims, name, datatype, buffer, indep); + write_dataset(group_id, ndim, dims, name, datatype, buffer, indep); // Free resources H5Tclose(datatype); diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 11f26a5f1..615bed24c 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -41,8 +41,8 @@ write_double_1D(hid_t group_id, char const *name, H5Dclose(dataset); } -void read_data(hid_t obj_id, const char* name, double* buffer, - hid_t mem_type_id, bool indep); +void read_dataset(hid_t obj_id, const char* name, double* buffer, + hid_t mem_type_id, bool indep); extern "C" void read_double(hid_t obj_id, const char* name, double* buffer, bool indep); extern "C" void read_int(hid_t obj_id, const char* name, int* buffer, @@ -50,8 +50,17 @@ extern "C" void read_int(hid_t obj_id, const char* name, int* buffer, extern "C" void read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep); -void write_data(hid_t group_id, int ndim, const hsize_t* dims, const char* name, - hid_t mem_type_id, const void* buffer, bool indep); +void write_attr(hid_t obj_id, int ndim, const hsize_t* dims, const char* name, + hid_t mem_type_id, const void* buffer); +extern "C" void write_attr_double(hid_t obj_id, int ndim, const hsize_t* dims, + const char* name, const double* buffer); +extern "C" void write_attr_int(hid_t obj_id, int ndim, const hsize_t* dims, + const char* name, const int* buffer); +extern "C" void write_attr_string(hid_t obj_id, const char* name, const char* buffer); + + +void write_dataset(hid_t group_id, int ndim, const hsize_t* dims, const char* name, + hid_t mem_type_id, const void* buffer, bool indep); extern "C" void write_double(hid_t group_id, int ndim, const hsize_t* dims, const char* name, const double* buffer, bool indep); extern "C" void write_int(hid_t group_id, int ndim, const hsize_t* dims, From ad3bb0b43acc4ec0ac8b338041205d9f37737904 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Apr 2018 22:54:51 -0500 Subject: [PATCH 192/361] Handle most writing of attributes in C++ --- src/hdf5_interface.F90 | 189 ++++++++++++----------------------------- src/hdf5_interface.cpp | 52 +++++++++++- src/hdf5_interface.h | 13 ++- 3 files changed, 115 insertions(+), 139 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 1d056e1dc..bd1424552 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -105,6 +105,19 @@ module hdf5_interface integer(HID_T), value :: file_id end subroutine file_close + subroutine get_shape_c(obj_id, dims) bind(C, name='get_shape') + import HID_T, HSIZE_T + integer(HID_T), value :: obj_id + integer(HSIZE_T), intent(out) :: dims(*) + end subroutine get_shape_c + + subroutine get_shape_attr(obj_id, name, dims) bind(C) + import HID_T, HSIZE_T, C_CHAR + integer(HID_T), value :: obj_id + character(kind=C_CHAR), intent(in) :: name(*) + integer(HSIZE_T), intent(out) :: dims(*) + end subroutine get_shape_attr + subroutine read_double_c(obj_id, name, buffer, indep) & bind(C, name='read_double') import HID_T, C_DOUBLE, C_BOOL, C_PTR @@ -114,6 +127,22 @@ module hdf5_interface logical(C_BOOL), intent(in) :: indep end subroutine read_double_c + subroutine read_attr_int_c(obj_id, name, buffer) & + bind(C, name='read_attr_int') + import HID_T, C_CHAR, C_INT + integer(HID_T), value :: obj_id + character(kind=C_CHAR), intent(in) :: name(*) + integer(C_INT), intent(in) :: buffer(*) + end subroutine read_attr_int_c + + subroutine read_attr_double_c(obj_id, name, buffer) & + bind(C, name='read_attr_double') + import HID_T, C_CHAR, C_DOUBLE + integer(HID_T), value :: obj_id + character(kind=C_CHAR), intent(in) :: name(*) + real(C_DOUBLE), intent(in) :: buffer(*) + end subroutine read_attr_double_c + subroutine read_int_c(obj_id, name, buffer, indep) & bind(C, name='read_int') import HID_T, C_INT, C_BOOL, C_PTR @@ -1178,14 +1207,10 @@ contains integer(HID_T), intent(in) :: obj_id character(*), intent(in) :: name - integer :: hdf5_err - integer(HID_T) :: attr_id - type(c_ptr) :: f_ptr + real(C_DOUBLE) :: buffer_(1) - call h5aopen_f(obj_id, trim(name), attr_id, hdf5_err) - f_ptr = c_loc(buffer) - call h5aread_f(attr_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - call h5aclose_f(attr_id, hdf5_err) + call read_attr_double_c(obj_id, to_c_string(name), buffer_) + buffer = buffer_(1) end subroutine read_attribute_double subroutine write_attribute_double(obj_id, name, buffer) @@ -1205,39 +1230,16 @@ contains integer(HID_T), intent(in) :: obj_id character(*), intent(in) :: name - integer :: hdf5_err - integer(HID_T) :: space_id - integer(HID_T) :: attr_id integer(HSIZE_T) :: dims(1) - integer(HSIZE_T) :: maxdims(1) - call h5aopen_f(obj_id, trim(name), attr_id, hdf5_err) - - if (allocated(buffer)) then - dims(:) = shape(buffer) - else - call h5aget_space_f(attr_id, space_id, hdf5_err) - call h5sget_simple_extent_dims_f(space_id, dims, maxdims, hdf5_err) + if (.not. allocated(buffer)) then + call get_shape_attr(obj_id, to_c_string(name), dims) allocate(buffer(dims(1))) - call h5sclose_f(space_id, hdf5_err) end if - call read_attribute_double_1D_explicit(attr_id, dims, buffer) - call h5aclose_f(attr_id, hdf5_err) + call read_attr_double_c(obj_id, to_c_string(name), buffer) end subroutine read_attribute_double_1D - subroutine read_attribute_double_1D_explicit(attr_id, dims, buffer) - integer(HID_T), intent(in) :: attr_id - integer(HSIZE_T), intent(in) :: dims(1) - real(8), target, intent(inout) :: buffer(dims(1)) - - integer :: hdf5_err - type(c_ptr) :: f_ptr - - f_ptr = c_loc(buffer) - call h5aread_f(attr_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end subroutine read_attribute_double_1D_explicit - subroutine write_attribute_double_1D(obj_id, name, buffer) integer(HID_T), intent(in) :: obj_id character(*), intent(in) :: name @@ -1254,52 +1256,25 @@ contains integer(HID_T), intent(in) :: obj_id character(*), intent(in) :: name - integer :: hdf5_err - integer(HID_T) :: space_id - integer(HID_T) :: attr_id integer(HSIZE_T) :: dims(2) - integer(HSIZE_T) :: maxdims(2) - call h5aopen_f(obj_id, trim(name), attr_id, hdf5_err) - - if (allocated(buffer)) then - dims(:) = shape(buffer) - else - call h5aget_space_f(attr_id, space_id, hdf5_err) - call h5sget_simple_extent_dims_f(space_id, dims, maxdims, hdf5_err) - allocate(buffer(dims(1), dims(2))) - call h5sclose_f(space_id, hdf5_err) + if (.not. allocated(buffer)) then + call get_shape_attr(obj_id, to_c_string(name), dims) + allocate(buffer(dims(2), dims(1))) end if - call read_attribute_double_2D_explicit(attr_id, dims, buffer) - call h5aclose_f(attr_id, hdf5_err) + call read_attr_double_c(obj_id, to_c_string(name), buffer) end subroutine read_attribute_double_2D - subroutine read_attribute_double_2D_explicit(attr_id, dims, buffer) - integer(HID_T), intent(in) :: attr_id - integer(HSIZE_T), intent(in) :: dims(2) - real(8), target, intent(inout) :: buffer(dims(1),dims(2)) - - integer :: hdf5_err - type(c_ptr) :: f_ptr - - f_ptr = c_loc(buffer) - call h5aread_f(attr_id, H5T_NATIVE_DOUBLE, f_ptr, hdf5_err) - end subroutine read_attribute_double_2D_explicit - subroutine read_attribute_integer(buffer, obj_id, name) integer, intent(inout), target :: buffer integer(HID_T), intent(in) :: obj_id character(*), intent(in) :: name - integer :: hdf5_err - integer(HID_T) :: attr_id - type(c_ptr) :: f_ptr + integer(C_INT) :: buffer_(1) - call h5aopen_f(obj_id, trim(name), attr_id, hdf5_err) - f_ptr = c_loc(buffer) - call h5aread_f(attr_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - call h5aclose_f(attr_id, hdf5_err) + call read_attr_int_c(obj_id, to_c_string(name), buffer_) + buffer = buffer_(1) end subroutine read_attribute_integer subroutine write_attribute_integer(obj_id, name, buffer) @@ -1319,39 +1294,16 @@ contains integer(HID_T), intent(in) :: obj_id character(*), intent(in) :: name - integer :: hdf5_err - integer(HID_T) :: space_id - integer(HID_T) :: attr_id integer(HSIZE_T) :: dims(1) - integer(HSIZE_T) :: maxdims(1) - call h5aopen_f(obj_id, trim(name), attr_id, hdf5_err) - - if (allocated(buffer)) then - dims(:) = shape(buffer) - else - call h5aget_space_f(attr_id, space_id, hdf5_err) - call h5sget_simple_extent_dims_f(space_id, dims, maxdims, hdf5_err) + if (.not. allocated(buffer)) then + call get_shape_attr(obj_id, to_c_string(name), dims) allocate(buffer(dims(1))) - call h5sclose_f(space_id, hdf5_err) end if - call read_attribute_integer_1D_explicit(attr_id, dims, buffer) - call h5aclose_f(attr_id, hdf5_err) + call read_attr_int_c(obj_id, to_c_string(name), buffer) end subroutine read_attribute_integer_1D - subroutine read_attribute_integer_1D_explicit(attr_id, dims, buffer) - integer(HID_T), intent(in) :: attr_id - integer(HSIZE_T), intent(in) :: dims(1) - integer, target, intent(inout) :: buffer(dims(1)) - - integer :: hdf5_err - type(c_ptr) :: f_ptr - - f_ptr = c_loc(buffer) - call h5aread_f(attr_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end subroutine read_attribute_integer_1D_explicit - subroutine write_attribute_integer_1D(obj_id, name, buffer) integer(HID_T), intent(in) :: obj_id character(*), intent(in) :: name @@ -1368,39 +1320,16 @@ contains integer(HID_T), intent(in) :: obj_id character(*), intent(in) :: name - integer :: hdf5_err - integer(HID_T) :: space_id - integer(HID_T) :: attr_id integer(HSIZE_T) :: dims(2) - integer(HSIZE_T) :: maxdims(2) - call h5aopen_f(obj_id, trim(name), attr_id, hdf5_err) - - if (allocated(buffer)) then - dims(:) = shape(buffer) - else - call h5aget_space_f(attr_id, space_id, hdf5_err) - call h5sget_simple_extent_dims_f(space_id, dims, maxdims, hdf5_err) - allocate(buffer(dims(1), dims(2))) - call h5sclose_f(space_id, hdf5_err) + if (.not. allocated(buffer)) then + call get_shape_attr(obj_id, to_c_string(name), dims) + allocate(buffer(dims(2), dims(1))) end if - call read_attribute_integer_2D_explicit(attr_id, dims, buffer) - call h5aclose_f(attr_id, hdf5_err) + call read_attr_int_C(obj_id, to_c_string(name), buffer) end subroutine read_attribute_integer_2D - subroutine read_attribute_integer_2D_explicit(attr_id, dims, buffer) - integer(HID_T), intent(in) :: attr_id - integer(HSIZE_T), intent(in) :: dims(2) - integer, target, intent(inout) :: buffer(dims(1),dims(2)) - - integer :: hdf5_err - type(c_ptr) :: f_ptr - - f_ptr = c_loc(buffer) - call h5aread_f(attr_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - end subroutine read_attribute_integer_2D_explicit - subroutine read_attribute_string(buffer, obj_id, name) character(*), intent(inout) :: buffer ! read data to here integer(HID_T), intent(in) :: obj_id @@ -1533,21 +1462,13 @@ contains integer(HID_T), intent(in) :: obj_id integer(HSIZE_T), intent(out) :: dims(:) - integer :: hdf5_err - integer :: type - integer(HID_T) :: space_id - integer(HSIZE_T) :: maxdims(size(dims)) + integer :: i + integer(HSIZE_T) :: dims_c(size(dims)) - call h5iget_type_f(obj_id, type, hdf5_err) - if (type == H5I_DATASET_F) then - call h5dget_space_f(obj_id, space_id, hdf5_err) - call h5sget_simple_extent_dims_f(space_id, dims, maxdims, hdf5_err) - call h5sclose_f(space_id, hdf5_err) - elseif (type == H5I_ATTR_F) then - call h5aget_space_f(obj_id, space_id, hdf5_err) - call h5sget_simple_extent_dims_f(space_id, dims, maxdims, hdf5_err) - call h5sclose_f(space_id, hdf5_err) - end if + call get_shape_c(obj_id, dims_c) + do i = 1, size(dims) + dims(i) = dims_c(size(dims) - i + 1) + end do end subroutine get_shape subroutine get_ndims(obj_id, ndims) diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index f482285f2..3e906a94a 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -31,6 +31,32 @@ using_mpio_device(hid_t obj_id) } +void +get_shape(hid_t obj_id, hsize_t* dims) +{ + auto type = H5Iget_type(obj_id); + hid_t dspace; + if (type == H5I_DATASET) { + dspace = H5Dget_space(obj_id); + } else if (type == H5I_ATTR) { + dspace = H5Aget_space(obj_id); + } + H5Sget_simple_extent_dims(dspace, dims, nullptr); + H5Sclose(dspace); +} + + +void +get_shape_attr(hid_t obj_id, const char* name, hsize_t* dims) +{ + hid_t attr = H5Aopen(obj_id, name, H5P_DEFAULT); + hid_t dspace = H5Aget_space(attr); + H5Sget_simple_extent_dims(dspace, dims, nullptr); + H5Sclose(dspace); + H5Aclose(attr); +} + + hid_t create_group(hid_t parent_id, char const *name) { @@ -159,10 +185,32 @@ open_group(hid_t group_id, const char* name) } } +void +read_attr(hid_t obj_id, const char* name, hid_t mem_type_id, void* buffer) +{ + hid_t attr = H5Aopen(obj_id, name, H5P_DEFAULT); + H5Aread(attr, mem_type_id, buffer); + H5Aclose(attr); +} + + +void +read_attr_double(hid_t obj_id, const char* name, double* buffer) +{ + read_attr(obj_id, name, H5T_NATIVE_DOUBLE, buffer); +} + + +void +read_attr_int(hid_t obj_id, const char* name, int* buffer) +{ + read_attr(obj_id, name, H5T_NATIVE_INT, buffer); +} + void read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id, - void* buffer, bool indep) + void* buffer, bool indep) { hid_t dset = obj_id; if (name) dset = H5Dopen(obj_id, name, H5P_DEFAULT); @@ -176,7 +224,7 @@ read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id, hid_t plist = H5Pcreate(H5P_DATASET_XFER); H5Pset_dxpl_mpio(plist, data_xfer_mode); - // Write data + // Read data H5Dread(dset, mem_type_id, H5S_ALL, H5S_ALL, plist, buffer); H5Pclose(plist); #endif diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 615bed24c..ca5e27e7e 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -11,7 +11,6 @@ namespace openmc { -bool using_mpio_device(hid_t obj_id); hid_t create_group(hid_t parent_id, const char* name); hid_t create_group(hid_t parent_id, const std::string& name); void close_dataset(hid_t dataset_id); @@ -19,9 +18,12 @@ void close_group(hid_t group_id); extern "C" hid_t file_open(const char* filename, char mode, bool parallel); hid_t file_open(const std::string& filename, char mode, bool parallel); extern "C" void file_close(hid_t file_id); +extern "C" void get_shape(hid_t obj_if, hsize_t* dims); +extern "C" void get_shape_attr(hid_t obj_if, const char* name, hsize_t* dims); bool object_exists(hid_t object_id, const char* name); hid_t open_dataset(hid_t group_id, const char* name); hid_t open_group(hid_t group_id, const char* name); +bool using_mpio_device(hid_t obj_id); template void @@ -41,8 +43,13 @@ write_double_1D(hid_t group_id, char const *name, H5Dclose(dataset); } -void read_dataset(hid_t obj_id, const char* name, double* buffer, - hid_t mem_type_id, bool indep); +void read_attr(hid_t obj_id, const char* name, hid_t mem_type_id, + const void* buffer); +extern "C" void read_attr_double(hid_t obj_id, const char* name, double* buffer); +extern "C" void read_attr_int(hid_t obj_id, const char* name, int* buffer); + +void read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id, + void* buffer, bool indep); extern "C" void read_double(hid_t obj_id, const char* name, double* buffer, bool indep); extern "C" void read_int(hid_t obj_id, const char* name, int* buffer, From 422edace492afbe6b9c0a282aa43ad198dcc326d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 18 Apr 2018 23:08:43 -0500 Subject: [PATCH 193/361] Use dataset_ndims and object_exists from C++ --- src/hdf5_interface.F90 | 34 ++++++++++++++++++---------------- src/hdf5_interface.cpp | 12 ++++++++++++ src/hdf5_interface.h | 3 ++- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index bd1424552..56da0def1 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -331,11 +331,18 @@ contains character(*), intent(in) :: name ! name of group logical :: exists - integer :: hdf5_err ! HDF5 error code + interface + function object_exists_c(obj_id, name) result(exists) & + bind(C, name='object_exists') + import HID_T, C_CHAR, C_BOOL + integer(HID_T), value :: obj_id + character(kind=C_CHAR), intent(in) :: name(*) + logical(C_BOOL) :: exists + end function object_exists_c + end interface ! Check if group exists - call h5ltpath_valid_f(object_id, trim(name), .true., exists, hdf5_err) - + exists = object_exists_c(object_id, to_c_string(name)) end function object_exists !=============================================================================== @@ -1475,20 +1482,15 @@ contains integer(HID_T), intent(in) :: obj_id integer, intent(out) :: ndims - integer :: hdf5_err - integer :: type - integer(HID_T) :: space_id + interface + function dataset_ndims(dset) result(ndims) bind(C) + import HID_T, C_INT + integer(HID_T), value :: dset + integer(C_INT) :: ndims + end function dataset_ndims + end interface - call h5iget_type_f(obj_id, type, hdf5_err) - if (type == H5I_DATASET_F) then - call h5dget_space_f(obj_id, space_id, hdf5_err) - call h5sget_simple_extent_ndims_f(space_id, ndims, hdf5_err) - call h5sclose_f(space_id, hdf5_err) - elseif (type == H5I_ATTR_F) then - call h5aget_space_f(obj_id, space_id, hdf5_err) - call h5sget_simple_extent_ndims_f(space_id, ndims, hdf5_err) - call h5sclose_f(space_id, hdf5_err) - end if + ndims = dataset_ndims(obj_id) end subroutine get_ndims function using_mpio_device(obj_id) result(mpio) diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 3e906a94a..8fef1bd95 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -69,12 +69,14 @@ create_group(hid_t parent_id, char const *name) return out; } + hid_t create_group(hid_t parent_id, const std::string &name) { return create_group(parent_id, name.c_str()); } + void close_dataset(hid_t dataset_id) { @@ -89,6 +91,16 @@ close_group(hid_t group_id) } +int +dataset_ndims(hid_t dset) +{ + hid_t dspace = H5Dget_space(dset); + int ndims = H5Sget_simple_extent_ndims(dspace); + H5Sclose(dspace); + return ndims; +} + + hid_t file_open(const char* filename, char mode, bool parallel) { diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index ca5e27e7e..6759ffc64 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -15,12 +15,13 @@ hid_t create_group(hid_t parent_id, const char* name); hid_t create_group(hid_t parent_id, const std::string& name); void close_dataset(hid_t dataset_id); void close_group(hid_t group_id); +extern "C" int dataset_ndims(hid_t dset); extern "C" hid_t file_open(const char* filename, char mode, bool parallel); hid_t file_open(const std::string& filename, char mode, bool parallel); extern "C" void file_close(hid_t file_id); extern "C" void get_shape(hid_t obj_if, hsize_t* dims); extern "C" void get_shape_attr(hid_t obj_if, const char* name, hsize_t* dims); -bool object_exists(hid_t object_id, const char* name); +extern "C" bool object_exists(hid_t object_id, const char* name); hid_t open_dataset(hid_t group_id, const char* name); hid_t open_group(hid_t group_id, const char* name); bool using_mpio_device(hid_t obj_id); From 05fcd993c27137e182479ab754e0a025e610086e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Apr 2018 07:47:23 -0500 Subject: [PATCH 194/361] Convert HDF5 read_complex to C++ --- src/hdf5_interface.F90 | 96 ++++++++---------------------------------- src/hdf5_interface.cpp | 23 +++++++++- src/hdf5_interface.h | 3 ++ 3 files changed, 43 insertions(+), 79 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 56da0def1..b0b3d1006 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -161,6 +161,15 @@ module hdf5_interface logical(C_BOOL), intent(in) :: indep end subroutine read_llong_c + subroutine read_complex_c(obj_id, name, buffer, indep) & + bind(C, name='read_complex') + import HID_T, C_PTR, C_DOUBLE_COMPLEX, C_BOOL + integer(HID_T), value :: obj_id + type(C_PTR), value :: name + complex(C_DOUBLE_COMPLEX), intent(in) :: buffer(*) + logical(C_BOOL), intent(in) :: indep + end subroutine read_complex_c + subroutine write_attr_double_c(obj_id, ndim, dims, name, buffer) & bind(C, name='write_attr_double') import HID_T, HSIZE_T, C_INT, C_DOUBLE, C_CHAR @@ -1527,94 +1536,25 @@ contains !=============================================================================== subroutine read_complex_2D(buffer, obj_id, name, indep) - complex(8), target, intent(inout) :: buffer(:,:) + complex(C_DOUBLE_COMPLEX), target, intent(inout) :: buffer(:,:) integer(HID_T), intent(in) :: obj_id character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer(HID_T) :: dset_id - integer(HSIZE_T) :: dims(2) + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), target, allocatable :: name_(:) + + indep_ = .false. + if (present(indep)) indep_ = indep ! If 'name' argument is passed, obj_id is interpreted to be a group and ! 'name' is the name of the dataset we should read from if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + name_ = to_c_string(name) + call read_complex_c(obj_id, c_loc(name_), buffer, indep_) else - dset_id = obj_id + call read_complex_c(obj_id, C_NULL_PTR, buffer, indep_) end if - - dims(:) = shape(buffer) - - if (present(indep)) then - call read_complex_2D_explicit(dset_id, dims, buffer, indep) - else - call read_complex_2D_explicit(dset_id, dims, buffer) - end if - - if (present(name)) call h5dclose_f(dset_id, hdf5_err) end subroutine read_complex_2D - subroutine read_complex_2D_explicit(dset_id, dims, buffer, indep) - integer(HID_T), intent(in) :: dset_id - integer(HSIZE_T), intent(in) :: dims(2) - complex(8), target, intent(inout) :: buffer(dims(1), dims(2)) - logical, optional, intent(in) :: indep ! independent I/O - - real(8), target :: buffer_r(dims(1), dims(2)) - real(8), target :: buffer_i(dims(1), dims(2)) - - integer(HSIZE_T) :: i, j - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - type(c_ptr) :: f_ptr_r, f_ptr_i - - ! Components needed for complex type support - integer(HID_T) :: dtype_real - integer(HID_T) :: dtype_imag - integer(SIZE_T) :: size_double - - ! Create the complex type - call h5tget_size_f(H5T_NATIVE_DOUBLE, size_double, hdf5_err) - - ! Insert the 'r' and 'i' identifiers - call h5tcreate_f(H5T_COMPOUND_F, size_double, dtype_real, hdf5_err) - call h5tcreate_f(H5T_COMPOUND_F, size_double, dtype_imag, hdf5_err) - call h5tinsert_f(dtype_real, "r", 0_SIZE_T, H5T_NATIVE_DOUBLE, hdf5_err) - call h5tinsert_f(dtype_imag, "i", 0_SIZE_T, H5T_NATIVE_DOUBLE, hdf5_err) - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - f_ptr_r = c_loc(buffer_r) - f_ptr_i = c_loc(buffer_i) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, dtype_real, f_ptr_r, hdf5_err, xfer_prp=plist) - call h5dread_f(dset_id, dtype_imag, f_ptr_i, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, dtype_real, f_ptr_r, hdf5_err) - call h5dread_f(dset_id, dtype_imag, f_ptr_i, hdf5_err) - end if - - ! Reconstitute the complex numbers - do i = 1, dims(1) - do j = 1, dims(2) - buffer(i, j) = cmplx(buffer_r(i,j), buffer_i(i,j), kind=8) - end do - end do - end subroutine read_complex_2D_explicit - end module hdf5_interface diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 8fef1bd95..c75ec31aa 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -269,6 +269,27 @@ read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep) } +void +read_complex(hid_t obj_id, const char* name, double _Complex* buffer, bool indep) +{ + // Create compound datatype for complex numbers + struct complex_t { + double re; + double im; + }; + complex_t tmp; + hid_t complex_id = H5Tcreate(H5T_COMPOUND, sizeof tmp); + H5Tinsert(complex_id, "r", HOFFSET(complex_t, re), H5T_NATIVE_DOUBLE); + H5Tinsert(complex_id, "i", HOFFSET(complex_t, im), H5T_NATIVE_DOUBLE); + + // Read data + read_dataset(obj_id, name, complex_id, buffer, indep); + + // Free resources + H5Tclose(complex_id); +} + + void write_attr(hid_t obj_id, int ndim, const hsize_t* dims, const char* name, hid_t mem_type_id, const void* buffer) @@ -412,4 +433,4 @@ write_string(hid_t group_id, char const* name, const std::string& buffer, bool i write_string(group_id, 0, nullptr, buffer.length(), name, buffer.c_str(), indep); } -} +} // namespace openmc diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 6759ffc64..785548e89 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -7,6 +7,7 @@ #include #include #include +#include namespace openmc { @@ -57,6 +58,8 @@ extern "C" void read_int(hid_t obj_id, const char* name, int* buffer, bool indep); extern "C" void read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep); +extern "C" void read_complex(hid_t obj_id, const char* name, + double _Complex* buffer, bool indep); void write_attr(hid_t obj_id, int ndim, const hsize_t* dims, const char* name, hid_t mem_type_id, const void* buffer); From 66bc225fa3b83bb0aa8ff60067cc9461d549fa52 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Apr 2018 10:17:48 -0500 Subject: [PATCH 195/361] Convert open/close_dataset/group --- src/hdf5_interface.F90 | 90 +++++++++++++++++++++--------------------- src/hdf5_interface.h | 10 ++--- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index b0b3d1006..73b052e74 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -422,18 +422,17 @@ contains character(*), intent(in) :: name ! name of group integer(HID_T) :: newgroup_id - logical :: exists ! does the group exist - integer :: hdf5_err ! HDF5 error code + interface + function open_group_c(group_id, name) result(newgroup_id) & + bind(C, name='open_group') + import HID_T, C_CHAR + integer(HID_T), value :: group_id + character(kind=C_CHAR), intent(in) :: name(*) + integer(HID_T) :: newgroup_id + end function open_group_c + end interface - ! Check if group exists - exists = object_exists(group_id, name) - - ! open group if it exists - if (exists) then - call h5gopen_f(group_id, trim(name), newgroup_id, hdf5_err) - else - call fatal_error("The group '" // trim(name) // "' does not exist.") - end if + newgroup_id = open_group_c(group_id, to_c_string(name)) end function open_group !=============================================================================== @@ -445,18 +444,17 @@ contains character(*), intent(in) :: name ! name of group integer(HID_T) :: newgroup_id - integer :: hdf5_err ! HDF5 error code - logical :: exists ! does the group exist + interface + function create_group_c(group_id, name) result(newgroup_id) & + bind(C, name='create_group') + import HID_T, C_CHAR + integer(HID_T), value :: group_id + character(kind=C_CHAR), intent(in) :: name(*) + integer(HID_T) :: newgroup_id + end function create_group_c + end interface - ! Check if group exists - exists = object_exists(group_id, name) - - ! create group - if (exists) then - call fatal_error("The group '" // trim(name) // "' already exists.") - else - call h5gcreate_f(group_id, trim(name), newgroup_id, hdf5_err) - end if + newgroup_id = create_group_c(group_id, to_c_string(name)) end function create_group !=============================================================================== @@ -465,13 +463,14 @@ contains subroutine close_group(group_id) integer(HID_T), intent(inout) :: group_id + interface + subroutine close_group_c(group_id) bind(C, name='close_group') + import HID_T + integer(HID_T), value :: group_id + end subroutine close_group_c + end interface - integer :: hdf5_err ! HDF5 error code - - call h5gclose_f(group_id, hdf5_err) - if (hdf5_err < 0) then - call fatal_error("Unable to close HDF5 group.") - end if + call close_group_c(group_id) end subroutine close_group !=============================================================================== @@ -483,33 +482,34 @@ contains character(*), intent(in) :: name ! name of dataset integer(HID_T) :: dataset_id - logical :: exists ! does the dataset exist - integer :: hdf5_err ! HDF5 error code + interface + function open_dataset_c(group_id, name) result(dset_id) & + bind(C, name='open_dataset') + import HID_T, C_CHAR + integer(HID_T), value :: group_id + character(kind=C_CHAR), intent(in) :: name(*) + integer(HID_T) :: dset_id + end function open_dataset_c + end interface - ! Check if group exists - exists = object_exists(group_id, name) - - ! open group if it exists - if (exists) then - call h5dopen_f(group_id, trim(name), dataset_id, hdf5_err) - else - call fatal_error("The dataset '" // trim(name) // "' does not exist.") - end if + dataset_id = open_dataset_c(group_id, to_c_string(name)) end function open_dataset !=============================================================================== -! CLOSE_GROUP closes HDF5 temp_group +! CLOSE_DATASET closes an HDF5 dataset !=============================================================================== subroutine close_dataset(dataset_id) integer(HID_T), intent(inout) :: dataset_id - integer :: hdf5_err ! HDF5 error code + interface + subroutine close_dataset_c(dset_id) bind(C, name='close_dataset') + import HID_T + integer(HID_T), value :: dset_id + end subroutine close_dataset_c + end interface - call h5dclose_f(dataset_id, hdf5_err) - if (hdf5_err < 0) then - call fatal_error("Unable to close HDF5 dataset.") - end if + call close_dataset_c(dataset_id) end subroutine close_dataset !=============================================================================== diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 785548e89..69cd7adaa 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -12,10 +12,10 @@ namespace openmc { -hid_t create_group(hid_t parent_id, const char* name); +extern "C" hid_t create_group(hid_t parent_id, const char* name); hid_t create_group(hid_t parent_id, const std::string& name); -void close_dataset(hid_t dataset_id); -void close_group(hid_t group_id); +extern "C" void close_dataset(hid_t dataset_id); +extern "C" void close_group(hid_t group_id); extern "C" int dataset_ndims(hid_t dset); extern "C" hid_t file_open(const char* filename, char mode, bool parallel); hid_t file_open(const std::string& filename, char mode, bool parallel); @@ -23,8 +23,8 @@ extern "C" void file_close(hid_t file_id); extern "C" void get_shape(hid_t obj_if, hsize_t* dims); extern "C" void get_shape_attr(hid_t obj_if, const char* name, hsize_t* dims); extern "C" bool object_exists(hid_t object_id, const char* name); -hid_t open_dataset(hid_t group_id, const char* name); -hid_t open_group(hid_t group_id, const char* name); +extern "C" hid_t open_dataset(hid_t group_id, const char* name); +extern "C" hid_t open_group(hid_t group_id, const char* name); bool using_mpio_device(hid_t obj_id); From b8635f1c2ba4157d4a323845802b7bb8793f54a1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Apr 2018 10:59:33 -0500 Subject: [PATCH 196/361] Convert HDF5 read_string to C++ --- src/hdf5_interface.F90 | 89 +++++++++++++++++------------------------- src/hdf5_interface.cpp | 27 ++++++++++++- src/hdf5_interface.h | 3 ++ 3 files changed, 64 insertions(+), 55 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 73b052e74..a0148cfd2 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -100,6 +100,12 @@ module hdf5_interface public :: get_name interface + function dataset_typesize(dset) result(sz) bind(C) + import HID_T, SIZE_T + integer(HID_T), value :: dset + integer(SIZE_T) :: sz + end function dataset_typesize + subroutine file_close(file_id) bind(C) import HID_T integer(HID_T), value :: file_id @@ -123,7 +129,7 @@ module hdf5_interface import HID_T, C_DOUBLE, C_BOOL, C_PTR integer(HID_T), value :: obj_id type(C_PTR), value :: name - real(C_DOUBLE), intent(in) :: buffer(*) + real(C_DOUBLE), intent(out) :: buffer(*) logical(C_BOOL), intent(in) :: indep end subroutine read_double_c @@ -132,7 +138,7 @@ module hdf5_interface import HID_T, C_CHAR, C_INT integer(HID_T), value :: obj_id character(kind=C_CHAR), intent(in) :: name(*) - integer(C_INT), intent(in) :: buffer(*) + integer(C_INT), intent(out) :: buffer(*) end subroutine read_attr_int_c subroutine read_attr_double_c(obj_id, name, buffer) & @@ -140,7 +146,7 @@ module hdf5_interface import HID_T, C_CHAR, C_DOUBLE integer(HID_T), value :: obj_id character(kind=C_CHAR), intent(in) :: name(*) - real(C_DOUBLE), intent(in) :: buffer(*) + real(C_DOUBLE), intent(out) :: buffer(*) end subroutine read_attr_double_c subroutine read_int_c(obj_id, name, buffer, indep) & @@ -148,7 +154,7 @@ module hdf5_interface import HID_T, C_INT, C_BOOL, C_PTR integer(HID_T), value :: obj_id type(C_PTR), value :: name - integer(C_INT), intent(in) :: buffer(*) + integer(C_INT), intent(out) :: buffer(*) logical(C_BOOL), intent(in) :: indep end subroutine read_int_c @@ -157,16 +163,26 @@ module hdf5_interface import HID_T, C_INT, C_BOOL, C_PTR, C_LONG_LONG integer(HID_T), value :: obj_id type(C_PTR), value :: name - integer(C_LONG_LONG), intent(in) :: buffer(*) + integer(C_LONG_LONG), intent(out) :: buffer(*) logical(C_BOOL), intent(in) :: indep end subroutine read_llong_c + subroutine read_string_c(obj_id, name, slen, buffer, indep) & + bind(C, name='read_string') + import HID_T, C_PTR, C_SIZE_T, C_CHAR, C_BOOL + integer(HID_T), value :: obj_id + type(C_PTR), value :: name + integer(C_SIZE_T), value :: slen + character(kind=C_CHAR), intent(out) :: buffer(*) + logical(C_BOOL), intent(in) :: indep + end subroutine read_string_c + subroutine read_complex_c(obj_id, name, buffer, indep) & bind(C, name='read_complex') import HID_T, C_PTR, C_DOUBLE_COMPLEX, C_BOOL integer(HID_T), value :: obj_id type(C_PTR), value :: name - complex(C_DOUBLE_COMPLEX), intent(in) :: buffer(*) + complex(C_DOUBLE_COMPLEX), intent(out) :: buffer(*) logical(C_BOOL), intent(in) :: indep end subroutine read_complex_c @@ -1019,67 +1035,32 @@ contains character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif integer(HID_T) :: dset_id - integer(HID_T) :: filetype - integer(HID_T) :: memtype integer(SIZE_T) :: i, n - character(kind=C_CHAR), allocatable, target :: temp_buffer(:) - type(c_ptr) :: f_ptr + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), allocatable, target :: buffer_(:) if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + dset_id = open_dataset(obj_id, name) else dset_id = obj_id end if - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if + ! Allocate a C char array to get string + n = dataset_typesize(dset_id) + allocate(buffer_(n)) - ! Make sure buffer is large enough - call h5dget_type_f(dset_id, filetype, hdf5_err) - call h5tget_size_f(filetype, n, hdf5_err) - if (n > len(buffer)) then - call fatal_error("Character buffer is not long enough to & - &read HDF5 string.") - end if - - ! Get datatype in memory based on Fortran character - call h5tcopy_f(H5T_C_S1, memtype, hdf5_err) - call h5tset_size_f(memtype, n + 1, hdf5_err) - - ! Get pointer to start of string - allocate(temp_buffer(n)) - f_ptr = c_loc(temp_buffer(1)) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, memtype, f_ptr, hdf5_err, xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, memtype, f_ptr, hdf5_err) - end if + indep_ = .false. + if (present(indep)) indep_ = indep + call read_string_c(dset_id, C_NULL_PTR, n, buffer_, indep_) buffer = '' do i = 1, n - if (temp_buffer(i) == C_NULL_CHAR) cycle - buffer(i:i) = temp_buffer(i) + if (buffer_(i) == C_NULL_CHAR) cycle + buffer(i:i) = buffer_(i) end do - if (present(name)) call h5dclose_f(dset_id, hdf5_err) - - call h5tclose_f(filetype, hdf5_err) - call h5tclose_f(memtype, hdf5_err) + call close_dataset(dset_id) end subroutine read_string !=============================================================================== @@ -1343,7 +1324,7 @@ contains allocate(buffer(dims(2), dims(1))) end if - call read_attr_int_C(obj_id, to_c_string(name), buffer) + call read_attr_int_c(obj_id, to_c_string(name), buffer) end subroutine read_attribute_integer_2D subroutine read_attribute_string(buffer, obj_id, name) diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index c75ec31aa..bf410923b 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -101,6 +101,16 @@ dataset_ndims(hid_t dset) } +size_t +dataset_typesize(hid_t dset) +{ + hid_t filetype = H5Dget_type(dset); + size_t n = H5Tget_size(filetype); + H5Tclose(filetype); + return n; +} + + hid_t file_open(const char* filename, char mode, bool parallel) { @@ -225,7 +235,7 @@ read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id, void* buffer, bool indep) { hid_t dset = obj_id; - if (name) dset = H5Dopen(obj_id, name, H5P_DEFAULT); + if (name) dset = open_dataset(obj_id, name); if (using_mpio_device(dset)) { #ifdef PHDF5 @@ -269,6 +279,21 @@ read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep) } +void +read_string(hid_t obj_id, const char* name, size_t slen, char* buffer, bool indep) +{ + // Create datatype for a string + hid_t datatype = H5Tcopy(H5T_C_S1); + H5Tset_size(datatype, slen + 1); + + // Read data into buffer + read_dataset(obj_id, name, datatype, buffer, indep); + + // Free resources + H5Tclose(datatype); +} + + void read_complex(hid_t obj_id, const char* name, double _Complex* buffer, bool indep) { diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 69cd7adaa..fdaf9ac8f 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -17,6 +17,7 @@ hid_t create_group(hid_t parent_id, const std::string& name); extern "C" void close_dataset(hid_t dataset_id); extern "C" void close_group(hid_t group_id); extern "C" int dataset_ndims(hid_t dset); +extern "C" size_t dataset_typesize(hid_t dset); extern "C" hid_t file_open(const char* filename, char mode, bool parallel); hid_t file_open(const std::string& filename, char mode, bool parallel); extern "C" void file_close(hid_t file_id); @@ -58,6 +59,8 @@ extern "C" void read_int(hid_t obj_id, const char* name, int* buffer, bool indep); extern "C" void read_llong(hid_t obj_id, const char* name, long long* buffer, bool indep); +extern "C" void read_string(hid_t obj_id, const char* name, size_t slen, + char* buffer, bool indep); extern "C" void read_complex(hid_t obj_id, const char* name, double _Complex* buffer, bool indep); From 1cb46effda75c250c58da506b3b58582e7c8e540 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Apr 2018 11:07:27 -0500 Subject: [PATCH 197/361] Convert HDF5 attribute_exists to C++ --- src/hdf5_interface.F90 | 16 +++++++++++----- src/hdf5_interface.cpp | 8 ++++++++ src/hdf5_interface.h | 1 + 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index a0148cfd2..eee539767 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -332,7 +332,7 @@ contains end subroutine get_groups !=============================================================================== -! CHECK_ATTRIBUTE Checks to see if an attribute exists in the object +! ATTRIBUTE_EXISTS checks to see if an attribute exists in the object !=============================================================================== function attribute_exists(object_id, name) result(exists) @@ -340,11 +340,17 @@ contains character(*), intent(in) :: name ! name of group logical :: exists - integer :: hdf5_err ! HDF5 error code - - ! Check if attribute exists - call h5aexists_by_name_f(object_id, '.', trim(name), exists, hdf5_err) + interface + function attribute_exists_c(obj_id, name) result(exists) & + bind(C, name='attribute_exists') + import HID_T, C_CHAR, C_BOOL + integer(HID_T), value :: obj_id + character(kind=C_CHAR), intent(in) :: name(*) + logical(C_BOOL) :: exists + end function attribute_exists_c + end interface + exists = attribute_exists_c(object_id, to_c_string(name)) end function attribute_exists !=============================================================================== diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index bf410923b..aa44e7c97 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -11,6 +11,14 @@ namespace openmc { +bool +attribute_exists(hid_t obj_id, const char* name) +{ + htri_t out = H5Aexists_by_name(obj_id, ".", name, H5P_DEFAULT); + return out > 0; +} + + bool using_mpio_device(hid_t obj_id) { diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index fdaf9ac8f..2e470dbc7 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -12,6 +12,7 @@ namespace openmc { +extern "C" bool attribute_exists(hid_t obj_id, const char* name); extern "C" hid_t create_group(hid_t parent_id, const char* name); hid_t create_group(hid_t parent_id, const std::string& name); extern "C" void close_dataset(hid_t dataset_id); From 95b05ef02de570432101daa48a884ec70346c358 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Apr 2018 11:33:24 -0500 Subject: [PATCH 198/361] Convert HDF5 read_string_1D to C++ --- src/hdf5_interface.F90 | 96 +++++++++++------------------------------- 1 file changed, 24 insertions(+), 72 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index eee539767..1cf252259 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -1111,87 +1111,39 @@ contains character(*), optional, intent(in) :: name logical, optional, intent(in) :: indep ! independent I/O - integer :: hdf5_err - integer(HID_T) :: dset_id - integer(HSIZE_T) :: dims(1) + integer(HID_T) :: dset_id + integer(SIZE_T) :: i, j, k, n, m + logical(C_BOOL) :: indep_ + character(kind=C_CHAR), allocatable, target :: buffer_(:) - ! If 'name' argument is passed, obj_id is interpreted to be a group and - ! 'name' is the name of the dataset we should read from if (present(name)) then - call h5dopen_f(obj_id, trim(name), dset_id, hdf5_err) + dset_id = open_dataset(obj_id, name) else dset_id = obj_id end if - dims(:) = shape(buffer) + ! Allocate a C char array to get strings + n = dataset_typesize(dset_id) + m = size(buffer) + allocate(buffer_(n*m)) - if (present(indep)) then - call read_string_1D_explicit(dset_id, dims, buffer, indep) - else - call read_string_1D_explicit(dset_id, dims, buffer) - end if + indep_ = .false. + if (present(indep)) indep_ = indep + call read_string_c(dset_id, C_NULL_PTR, n, buffer_, indep_) + + ! Convert null-terminated C strings into Fortran strings + do i = 1, m + buffer(i) = '' + do j = 1, n + k = (i-1)*n + j + if (buffer_(k) == C_NULL_CHAR) exit + buffer(i)(j:j) = buffer_(k) + end do + end do + + call close_dataset(dset_id) end subroutine read_string_1D - subroutine read_string_1D_explicit(dset_id, dims, buffer, indep) - integer(HID_T), intent(in) :: dset_id - integer(HSIZE_T), intent(in) :: dims(1) - character(*), target, intent(inout) :: buffer(dims(1)) - logical, optional, intent(in) :: indep ! independent I/O - - integer :: hdf5_err - integer :: data_xfer_mode -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - integer(HID_T) :: space_id - integer(HID_T) :: filetype - integer(HID_T) :: memtype - integer(SIZE_T) :: size - integer(SIZE_T) :: n - type(c_ptr) :: f_ptr - - ! Set up collective vs. independent I/O - data_xfer_mode = H5FD_MPIO_COLLECTIVE_F - if (present(indep)) then - if (indep) data_xfer_mode = H5FD_MPIO_INDEPENDENT_F - end if - - ! Get dataset and dataspace - call h5dget_space_f(dset_id, space_id, hdf5_err) - - ! Make sure buffer is large enough - call h5dget_type_f(dset_id, filetype, hdf5_err) - call h5tget_size_f(filetype, size, hdf5_err) - if (size > len(buffer(1)) + 1) then - call fatal_error("Character buffer is not long enough to & - &read HDF5 string array.") - end if - - ! Get datatype in memory based on Fortran character - n = len(buffer(1)) - call h5tcopy_f(H5T_FORTRAN_S1, memtype, hdf5_err) - call h5tset_size_f(memtype, n, hdf5_err) - - ! Get pointer to start of string - f_ptr = c_loc(buffer(1)(1:1)) - - if (using_mpio_device(dset_id)) then -#ifdef PHDF5 - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, data_xfer_mode, hdf5_err) - call h5dread_f(dset_id, memtype, f_ptr, hdf5_err, mem_space_id=space_id, & - xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#endif - else - call h5dread_f(dset_id, memtype, f_ptr, hdf5_err, mem_space_id=space_id) - end if - - call h5sclose_f(space_id, hdf5_err) - call h5tclose_f(filetype, hdf5_err) - call h5tclose_f(memtype, hdf5_err) - end subroutine read_string_1D_explicit - !=============================================================================== ! WRITE_ATTRIBUTE_STRING !=============================================================================== From c50c6f070007f0a6cbdcd3a7d76e0d1f5d950add Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Apr 2018 12:21:46 -0500 Subject: [PATCH 199/361] Convert HDF5 read_attribute_string/logical to C++ --- src/hdf5_interface.F90 | 147 ++++++++++++++--------------------------- src/hdf5_interface.cpp | 27 ++++++++ src/hdf5_interface.h | 3 + 3 files changed, 81 insertions(+), 96 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 1cf252259..6f4eabad8 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -100,6 +100,13 @@ module hdf5_interface public :: get_name interface + function attribute_typesize(obj_id, name) result(sz) bind(C) + import HID_T, C_CHAR, SIZE_T + integer(HID_T), value :: obj_id + character(kind=C_CHAR), intent(in) :: name(*) + integer(SIZE_T) :: sz + end function attribute_typesize + function dataset_typesize(dset) result(sz) bind(C) import HID_T, SIZE_T integer(HID_T), value :: dset @@ -149,6 +156,15 @@ module hdf5_interface real(C_DOUBLE), intent(out) :: buffer(*) end subroutine read_attr_double_c + subroutine read_attr_string_c(obj_id, name, slen, buffer) & + bind(C, name='read_attr_string') + import HID_T, C_CHAR, C_SIZE_T + integer(HID_T), value :: obj_id + character(kind=C_CHAR), intent(in) :: name(*) + integer(C_SIZE_T), value :: slen + character(kind=C_CHAR), intent(out) :: buffer(*) + end subroutine read_attr_string_c + subroutine read_int_c(obj_id, name, buffer, indep) & bind(C, name='read_int') import HID_T, C_INT, C_BOOL, C_PTR @@ -1290,44 +1306,22 @@ contains integer(HID_T), intent(in) :: obj_id character(*), intent(in) :: name ! name for data - integer :: hdf5_err - integer(HID_T) :: attr_id ! data set handle - integer(HID_T) :: filetype - integer(HID_T) :: memtype - integer(SIZE_T) :: i - integer(SIZE_T) :: size - character(kind=C_CHAR), allocatable, target :: temp_buffer(:) - type(c_ptr) :: f_ptr + integer(C_SIZE_T) :: i, n + character(kind=C_CHAR), allocatable, target :: buffer_(:) - ! Get dataset and dataspace - call h5aopen_f(obj_id, trim(name), attr_id, hdf5_err) + ! Allocate a C char array to get string + n = attribute_typesize(obj_id, to_c_string(name)) + allocate(buffer_(n)) - ! Make sure buffer is large enough - call h5aget_type_f(attr_id, filetype, hdf5_err) - call h5tget_size_f(filetype, size, hdf5_err) - allocate(temp_buffer(size)) - if (size > len(buffer)) then - call fatal_error("Character buffer is not long enough to & - &read HDF5 string.") - end if + ! Read attribute + call read_attr_string_c(obj_id, to_c_string(name), n, buffer_) - ! Get datatype in memory based on Fortran character - call h5tcopy_f(H5T_C_S1, memtype, hdf5_err) - call h5tset_size_f(memtype, size + 1, hdf5_err) - - ! Get pointer to start of string - f_ptr = c_loc(temp_buffer(1)) - - call h5aread_f(attr_id, memtype, f_ptr, hdf5_err) + ! Copy back to Fortran string buffer = '' - do i = 1, size - if (temp_buffer(i) == C_NULL_CHAR) cycle - buffer(i:i) = temp_buffer(i) + do i = 1, n + if (buffer_(i) == C_NULL_CHAR) cycle + buffer(i:i) = buffer_(i) end do - - call h5aclose_f(attr_id, hdf5_err) - call h5tclose_f(filetype, hdf5_err) - call h5tclose_f(memtype, hdf5_err) end subroutine read_attribute_string subroutine read_attribute_string_1D(buffer, obj_id, name) @@ -1335,82 +1329,43 @@ contains integer(HID_T), intent(in) :: obj_id character(*), intent(in) :: name - integer :: hdf5_err - integer(HID_T) :: space_id - integer(HID_T) :: attr_id + integer(C_SIZE_T) :: i, j, k, n, m integer(HSIZE_T) :: dims(1) - integer(HSIZE_T) :: maxdims(1) + character(kind=C_CHAR), allocatable, target :: buffer_(:) - call h5aopen_f(obj_id, trim(name), attr_id, hdf5_err) - - if (allocated(buffer)) then - dims(:) = shape(buffer) - else - call h5aget_space_f(attr_id, space_id, hdf5_err) - call h5sget_simple_extent_dims_f(space_id, dims, maxdims, hdf5_err) + if (.not. allocated(buffer)) then + call get_shape_attr(obj_id, to_c_string(name), dims) allocate(buffer(dims(1))) - call h5sclose_f(space_id, hdf5_err) end if - call read_attribute_string_1D_explicit(attr_id, dims, buffer) - call h5aclose_f(attr_id, hdf5_err) + ! Allocate a C char array to get strings + n = attribute_typesize(obj_id, to_c_string(name)) + m = size(buffer) + allocate(buffer_(n*m)) + + ! Read attribute + call read_attr_string_c(obj_id, to_c_string(name), n, buffer_) + + ! Convert null-terminated C strings into Fortran strings + do i = 1, m + buffer(i) = '' + do j = 1, n + k = (i-1)*n + j + if (buffer_(k) == C_NULL_CHAR) exit + buffer(i)(j:j) = buffer_(k) + end do + end do end subroutine read_attribute_string_1D - subroutine read_attribute_string_1D_explicit(attr_id, dims, buffer) - integer(HID_T), intent(in) :: attr_id - integer(HSIZE_T), intent(in) :: dims(1) - character(*), target, intent(inout) :: buffer(dims(1)) - - integer :: hdf5_err - integer(HID_T) :: filetype - integer(HID_T) :: memtype - integer(SIZE_T) :: size - integer(SIZE_T) :: n - type(c_ptr) :: f_ptr - - ! Make sure buffer is large enough - call h5aget_type_f(attr_id, filetype, hdf5_err) - call h5tget_size_f(filetype, size, hdf5_err) - if (size > len(buffer(1)) + 1) then - call fatal_error("Character buffer is not long enough to & - &read HDF5 string array.") - end if - - ! Get datatype in memory based on Fortran character - n = len(buffer(1)) - call h5tcopy_f(H5T_FORTRAN_S1, memtype, hdf5_err) - call h5tset_size_f(memtype, n, hdf5_err) - - ! Get pointer to start of string - f_ptr = c_loc(buffer(1)(1:1)) - - call h5aread_f(attr_id, memtype, f_ptr, hdf5_err) - - call h5tclose_f(filetype, hdf5_err) - call h5tclose_f(memtype, hdf5_err) - end subroutine read_attribute_string_1D_explicit - subroutine read_attribute_logical(buffer, obj_id, name) logical, intent(inout), target :: buffer integer(HID_T), intent(in) :: obj_id character(*), intent(in) :: name - integer, target :: int_buffer - integer :: hdf5_err - integer(HID_T) :: attr_id - type(c_ptr) :: f_ptr + integer :: tmp - call h5aopen_f(obj_id, trim(name), attr_id, hdf5_err) - f_ptr = c_loc(int_buffer) - call h5aread_f(attr_id, H5T_NATIVE_INTEGER, f_ptr, hdf5_err) - call h5aclose_f(attr_id, hdf5_err) - - ! Convert to Fortran logical - if (int_buffer == 0) then - buffer = .false. - else - buffer = .true. - end if + call read_attribute_integer(tmp, obj_id, name) + buffer = (tmp /= 0) end subroutine read_attribute_logical subroutine get_shape(obj_id, dims) diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index aa44e7c97..935ef084a 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -19,6 +19,18 @@ attribute_exists(hid_t obj_id, const char* name) } +size_t +attribute_typesize(hid_t obj_id, const char* name) +{ + hid_t attr = H5Aopen(obj_id, name, H5P_DEFAULT); + hid_t filetype = H5Aget_type(attr); + size_t n = H5Tget_size(filetype); + H5Tclose(filetype); + H5Aclose(attr); + return n; +} + + bool using_mpio_device(hid_t obj_id) { @@ -238,6 +250,21 @@ read_attr_int(hid_t obj_id, const char* name, int* buffer) } +void +read_attr_string(hid_t obj_id, const char* name, size_t slen, char* buffer) +{ + // Create datatype for a string + hid_t datatype = H5Tcopy(H5T_C_S1); + H5Tset_size(datatype, slen + 1); + + // Read data into buffer + read_attr(obj_id, name, datatype, buffer); + + // Free resources + H5Tclose(datatype); +} + + void read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id, void* buffer, bool indep) diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 2e470dbc7..9ea170c6c 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -13,6 +13,7 @@ namespace openmc { extern "C" bool attribute_exists(hid_t obj_id, const char* name); +extern "C" size_t attribute_typesize(hid_t obj_id, const char* name); extern "C" hid_t create_group(hid_t parent_id, const char* name); hid_t create_group(hid_t parent_id, const std::string& name); extern "C" void close_dataset(hid_t dataset_id); @@ -51,6 +52,8 @@ void read_attr(hid_t obj_id, const char* name, hid_t mem_type_id, const void* buffer); extern "C" void read_attr_double(hid_t obj_id, const char* name, double* buffer); extern "C" void read_attr_int(hid_t obj_id, const char* name, int* buffer); +extern "C" void read_attr_string(hid_t obj_id, const char* name, size_t slen, + char* buffer); void read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id, void* buffer, bool indep); From cfea8aa32471c00ca629d35fa65cbd7c6a38fce5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Apr 2018 12:26:32 -0500 Subject: [PATCH 200/361] Remove using_mpio_device on Fortran side --- src/hdf5_interface.F90 | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 6f4eabad8..77628d742 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -1396,34 +1396,6 @@ contains ndims = dataset_ndims(obj_id) end subroutine get_ndims - function using_mpio_device(obj_id) result(mpio) - integer(HID_T), intent(in) :: obj_id - logical :: mpio - - integer :: hdf5_err - integer(HID_T) :: driver - integer(HID_T) :: file_id - integer(HID_T) :: fapl_id - - ! Determine file that this object is part of - call h5iget_file_id_f(obj_id, file_id, hdf5_err) - - ! Get file access property list - call h5fget_access_plist_f(file_id, fapl_id, hdf5_err) - - ! Get low-level driver identifier - call h5pget_driver_f(fapl_id, driver, hdf5_err) - - ! Close file access property list access - call h5pclose_f(fapl_id, hdf5_err) - - ! Close file access -- note that this only decreases the reference count so - ! that the file is not actually closed - call h5fclose_f(file_id, hdf5_err) - - mpio = (driver == H5FD_MPIO_F) - end function using_mpio_device - !=============================================================================== ! READ_COMPLEX_2D reads double precision complex 2-D array data as output by ! the h5py HDF5 python module. From 012c8c9232fe3a53588aee0c0f3f9591f314a726 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Apr 2018 12:35:14 -0500 Subject: [PATCH 201/361] Use C_SIZE_T consistently --- src/hdf5_interface.F90 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 77628d742..da6ebd09d 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -101,16 +101,16 @@ module hdf5_interface interface function attribute_typesize(obj_id, name) result(sz) bind(C) - import HID_T, C_CHAR, SIZE_T + import HID_T, C_CHAR, C_SIZE_T integer(HID_T), value :: obj_id character(kind=C_CHAR), intent(in) :: name(*) - integer(SIZE_T) :: sz + integer(C_SIZE_T) :: sz end function attribute_typesize function dataset_typesize(dset) result(sz) bind(C) - import HID_T, SIZE_T + import HID_T, C_SIZE_T integer(HID_T), value :: dset - integer(SIZE_T) :: sz + integer(C_SIZE_T) :: sz end function dataset_typesize subroutine file_close(file_id) bind(C) @@ -1058,7 +1058,7 @@ contains logical, optional, intent(in) :: indep ! independent I/O integer(HID_T) :: dset_id - integer(SIZE_T) :: i, n + integer(C_SIZE_T) :: i, n logical(C_BOOL) :: indep_ character(kind=C_CHAR), allocatable, target :: buffer_(:) @@ -1128,7 +1128,7 @@ contains logical, optional, intent(in) :: indep ! independent I/O integer(HID_T) :: dset_id - integer(SIZE_T) :: i, j, k, n, m + integer(C_SIZE_T) :: i, j, k, n, m logical(C_BOOL) :: indep_ character(kind=C_CHAR), allocatable, target :: buffer_(:) From f78e6e0fc3bd4e4c61f4b0a8b9b4d28220357c7c Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Apr 2018 13:00:02 -0500 Subject: [PATCH 202/361] Make HDF5 kind type parameters available from hdf5_interface --- src/angle_distribution.F90 | 4 +--- src/angleenergy_header.F90 | 2 +- src/endf_header.F90 | 2 -- src/energy_distribution.F90 | 2 -- src/hdf5_interface.F90 | 1 + src/mesh_header.F90 | 2 -- src/mgxs_header.F90 | 2 -- src/multipole_header.F90 | 2 -- src/nuclide_header.F90 | 2 -- src/particle_header.F90 | 2 -- src/particle_restart.F90 | 4 +--- src/product_header.F90 | 4 +--- src/reaction_header.F90 | 6 +----- src/sab_header.F90 | 5 +---- src/secondary_correlated.F90 | 5 +---- src/secondary_kalbach.F90 | 5 +---- src/secondary_nbody.F90 | 4 +--- src/secondary_uncorrelated.F90 | 4 +--- src/source.F90 | 3 +-- src/summary.F90 | 2 -- src/surface_header.F90 | 5 ++--- src/tallies/tally_filter.F90 | 2 -- src/tallies/tally_filter_azimuthal.F90 | 2 -- src/tallies/tally_filter_cell.F90 | 2 -- src/tallies/tally_filter_cellborn.F90 | 2 -- src/tallies/tally_filter_cellfrom.F90 | 2 -- src/tallies/tally_filter_delayedgroup.F90 | 2 -- src/tallies/tally_filter_distribcell.F90 | 2 -- src/tallies/tally_filter_energy.F90 | 2 -- src/tallies/tally_filter_energyfunc.F90 | 2 -- src/tallies/tally_filter_header.F90 | 3 +-- src/tallies/tally_filter_legendre.F90 | 2 -- src/tallies/tally_filter_material.F90 | 2 -- src/tallies/tally_filter_mesh.F90 | 2 -- src/tallies/tally_filter_meshsurface.F90 | 2 -- src/tallies/tally_filter_mu.F90 | 2 -- src/tallies/tally_filter_polar.F90 | 2 -- src/tallies/tally_filter_sph_harm.F90 | 2 -- src/tallies/tally_filter_sptl_legendre.F90 | 2 -- src/tallies/tally_filter_surface.F90 | 2 -- src/tallies/tally_filter_universe.F90 | 2 -- src/tallies/tally_filter_zernike.F90 | 2 -- src/track_output.F90 | 2 -- src/urr_header.F90 | 3 +-- src/volume_calc.F90 | 3 +-- 45 files changed, 17 insertions(+), 102 deletions(-) diff --git a/src/angle_distribution.F90 b/src/angle_distribution.F90 index 5d16f7424..a1f20e779 100644 --- a/src/angle_distribution.F90 +++ b/src/angle_distribution.F90 @@ -1,12 +1,10 @@ module angle_distribution - use hdf5, only: HID_T, HSIZE_T - use algorithm, only: binary_search use constants, only: ZERO, ONE, HISTOGRAM, LINEAR_LINEAR use distribution_univariate, only: DistributionContainer, Tabular use hdf5_interface, only: read_attribute, get_shape, read_dataset, & - open_dataset, close_dataset + open_dataset, close_dataset, HID_T, HSIZE_T use random_lcg, only: prn implicit none diff --git a/src/angleenergy_header.F90 b/src/angleenergy_header.F90 index 60d5443c4..9fda5109e 100644 --- a/src/angleenergy_header.F90 +++ b/src/angleenergy_header.F90 @@ -1,6 +1,6 @@ module angleenergy_header - use hdf5, only: HID_T + use hdf5_interface, only: HID_T !=============================================================================== ! ANGLEENERGY (abstract) defines a correlated or uncorrelated angle-energy diff --git a/src/endf_header.F90 b/src/endf_header.F90 index e9e45ab75..9443efe2b 100644 --- a/src/endf_header.F90 +++ b/src/endf_header.F90 @@ -1,7 +1,5 @@ module endf_header - use hdf5, only: HID_T, HSIZE_T - use algorithm, only: binary_search use constants, only: ZERO, HISTOGRAM, LINEAR_LINEAR, LINEAR_LOG, & LOG_LINEAR, LOG_LOG diff --git a/src/energy_distribution.F90 b/src/energy_distribution.F90 index 770da617c..a9578e5d8 100644 --- a/src/energy_distribution.F90 +++ b/src/energy_distribution.F90 @@ -1,7 +1,5 @@ module energy_distribution - use hdf5 - use algorithm, only: binary_search use constants, only: ZERO, ONE, HALF, TWO, PI, HISTOGRAM, LINEAR_LINEAR use endf_header, only: Tabulated1D diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index da6ebd09d..5320ec9f5 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -98,6 +98,7 @@ module hdf5_interface public :: get_groups public :: get_datasets public :: get_name + public :: HID_T, HSIZE_T, SIZE_T interface function attribute_typesize(obj_id, name) result(sz) bind(C) diff --git a/src/mesh_header.F90 b/src/mesh_header.F90 index fc13a868e..fb22d2ad3 100644 --- a/src/mesh_header.F90 +++ b/src/mesh_header.F90 @@ -2,8 +2,6 @@ module mesh_header use, intrinsic :: ISO_C_BINDING - use hdf5 - use constants use dict_header, only: DictIntInt use error diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index 6eabefae3..e599a89ff 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -3,8 +3,6 @@ module mgxs_header use, intrinsic :: ISO_FORTRAN_ENV use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T, HSIZE_T, SIZE_T - use algorithm, only: find, sort use constants, only: MAX_WORD_LEN, ZERO, ONE, TWO, PI use error, only: fatal_error diff --git a/src/multipole_header.F90 b/src/multipole_header.F90 index 7fb438bce..f2d0c9106 100644 --- a/src/multipole_header.F90 +++ b/src/multipole_header.F90 @@ -1,7 +1,5 @@ module multipole_header - use hdf5 - use constants use dict_header, only: DictIntInt use error, only: fatal_error diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 76fc26a74..a063bb90b 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -3,8 +3,6 @@ module nuclide_header use, intrinsic :: ISO_FORTRAN_ENV use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T, HSIZE_T, SIZE_T - use algorithm, only: sort, find, binary_search use constants use dict_header, only: DictIntInt, DictCharInt diff --git a/src/particle_header.F90 b/src/particle_header.F90 index 02fb55405..cdb97dec8 100644 --- a/src/particle_header.F90 +++ b/src/particle_header.F90 @@ -1,7 +1,5 @@ module particle_header - use hdf5, only: HID_T - use bank_header, only: Bank, source_bank use constants use error, only: fatal_error, warning diff --git a/src/particle_restart.F90 b/src/particle_restart.F90 index fc8a05277..c3d25151b 100644 --- a/src/particle_restart.F90 +++ b/src/particle_restart.F90 @@ -5,7 +5,7 @@ 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 hdf5_interface, only: file_open, file_close, read_dataset, HID_T use mgxs_header, only: energy_bin_avg use nuclide_header, only: micro_xs, n_nuclides use output, only: print_particle @@ -16,8 +16,6 @@ module particle_restart use tally_header, only: n_tallies use tracking, only: transport - use hdf5, only: HID_T - implicit none private public :: openmc_particle_restart diff --git a/src/product_header.F90 b/src/product_header.F90 index a69929473..a5e23f4b6 100644 --- a/src/product_header.F90 +++ b/src/product_header.F90 @@ -1,13 +1,11 @@ module product_header - use hdf5, only: HID_T - use angleenergy_header, only: AngleEnergyContainer use constants, only: ZERO, MAX_WORD_LEN, EMISSION_PROMPT, EMISSION_DELAYED, & EMISSION_TOTAL, NEUTRON, PHOTON use endf_header, only: Tabulated1D, Function1D, Polynomial use hdf5_interface, only: read_attribute, open_group, close_group, & - open_dataset, close_dataset, read_dataset + open_dataset, close_dataset, read_dataset, HID_T use random_lcg, only: prn use secondary_correlated, only: CorrelatedAngleEnergy use secondary_kalbach, only: KalbachMann diff --git a/src/reaction_header.F90 b/src/reaction_header.F90 index 12b3888c4..48973336b 100644 --- a/src/reaction_header.F90 +++ b/src/reaction_header.F90 @@ -1,11 +1,7 @@ module reaction_header - use hdf5, only: HID_T, HSIZE_T, SIZE_T, h5gget_info_f, h5lget_name_by_idx_f, & - H5_INDEX_NAME_F, H5_ITER_INC_F - use constants, only: MAX_WORD_LEN - use hdf5_interface, only: read_attribute, open_group, close_group, & - open_dataset, read_dataset, close_dataset, get_shape, get_groups + use hdf5_interface use product_header, only: ReactionProduct use stl_vector, only: VectorInt use string, only: to_str, starts_with diff --git a/src/sab_header.F90 b/src/sab_header.F90 index eca8e555a..7ac14ef00 100644 --- a/src/sab_header.F90 +++ b/src/sab_header.F90 @@ -8,10 +8,7 @@ module sab_header use dict_header, only: DictIntInt, DictCharInt use distribution_univariate, only: Tabular use error, only: warning, fatal_error - use hdf5, only: HID_T, HSIZE_T, SIZE_T - 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 hdf5_interface use random_lcg, only: prn use secondary_correlated, only: CorrelatedAngleEnergy use settings diff --git a/src/secondary_correlated.F90 b/src/secondary_correlated.F90 index a0e203f33..6f5e6cabf 100644 --- a/src/secondary_correlated.F90 +++ b/src/secondary_correlated.F90 @@ -1,13 +1,10 @@ module secondary_correlated - use hdf5, only: HID_T, HSIZE_T - use algorithm, only: binary_search use angleenergy_header, only: AngleEnergy use constants, only: ZERO, ONE, HALF, TWO, HISTOGRAM, LINEAR_LINEAR use distribution_univariate, only: DistributionContainer, Tabular - use hdf5_interface, only: get_shape, read_attribute, open_dataset, & - read_dataset, close_dataset + use hdf5_interface use random_lcg, only: prn !=============================================================================== diff --git a/src/secondary_kalbach.F90 b/src/secondary_kalbach.F90 index f963cff3f..3315174e1 100644 --- a/src/secondary_kalbach.F90 +++ b/src/secondary_kalbach.F90 @@ -1,12 +1,9 @@ module secondary_kalbach - use hdf5, only: HID_T, HSIZE_T - use algorithm, only: binary_search use angleenergy_header, only: AngleEnergy use constants, only: ZERO, HALF, ONE, TWO, HISTOGRAM, LINEAR_LINEAR - use hdf5_interface, only: read_attribute, read_dataset, open_dataset, & - close_dataset, get_shape + use hdf5_interface use random_lcg, only: prn !=============================================================================== diff --git a/src/secondary_nbody.F90 b/src/secondary_nbody.F90 index 14ae949a9..aee4b1ff7 100644 --- a/src/secondary_nbody.F90 +++ b/src/secondary_nbody.F90 @@ -1,10 +1,8 @@ module secondary_nbody - use hdf5, only: HID_T - use angleenergy_header, only: AngleEnergy use constants, only: ONE, TWO, PI - use hdf5_interface, only: read_attribute + use hdf5_interface, only: read_attribute, HID_T use math, only: maxwell_spectrum use random_lcg, only: prn diff --git a/src/secondary_uncorrelated.F90 b/src/secondary_uncorrelated.F90 index 40aa827da..e559c1e5a 100644 --- a/src/secondary_uncorrelated.F90 +++ b/src/secondary_uncorrelated.F90 @@ -1,7 +1,5 @@ module secondary_uncorrelated - use hdf5, only: HID_T - use angle_distribution, only: AngleDistribution use angleenergy_header, only: AngleEnergy use constants, only: ONE, TWO, MAX_WORD_LEN @@ -9,7 +7,7 @@ module secondary_uncorrelated ContinuousTabular, MaxwellEnergy, Evaporation, WattEnergy, DiscretePhoton use error, only: warning use hdf5_interface, only: read_attribute, open_group, close_group, & - object_exists + object_exists, HID_T use random_lcg, only: prn !=============================================================================== diff --git a/src/source.F90 b/src/source.F90 index 9e3a0831d..8e142fcfa 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -1,6 +1,5 @@ module source - use hdf5, only: HID_T #ifdef OPENMC_MPI use message_passing #endif @@ -12,7 +11,7 @@ module source use distribution_multivariate, only: SpatialBox use error, only: fatal_error use geometry, only: find_cell - use hdf5_interface, only: file_open, file_close, read_dataset + use hdf5_interface, only: file_open, file_close, read_dataset, HID_T use math use message_passing, only: rank use mgxs_header, only: rev_energy_bins, num_energy_groups diff --git a/src/summary.F90 b/src/summary.F90 index 9f27694d1..6f8333b2c 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -1,7 +1,5 @@ module summary - use hdf5 - use constants use endf, only: reaction_name use error, only: write_message diff --git a/src/surface_header.F90 b/src/surface_header.F90 index 9ed89a14e..ab6babb5c 100644 --- a/src/surface_header.F90 +++ b/src/surface_header.F90 @@ -1,9 +1,9 @@ module surface_header use, intrinsic :: ISO_C_BINDING - use hdf5 use dict_header, only: DictIntInt + use hdf5_interface implicit none @@ -71,8 +71,7 @@ module surface_header subroutine surface_to_hdf5_c(surf_ptr, group) & bind(C, name='surface_to_hdf5') - use ISO_C_BINDING - use hdf5 + import C_PTR, HID_T implicit none type(C_PTR), intent(in), value :: surf_ptr integer(HID_T), intent(in), value :: group diff --git a/src/tallies/tally_filter.F90 b/src/tallies/tally_filter.F90 index e484ac231..4b589cedb 100644 --- a/src/tallies/tally_filter.F90 +++ b/src/tallies/tally_filter.F90 @@ -2,8 +2,6 @@ module tally_filter use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T - use error use string, only: to_f_string use tally_filter_header diff --git a/src/tallies/tally_filter_azimuthal.F90 b/src/tallies/tally_filter_azimuthal.F90 index 272d111d1..fc7f15a17 100644 --- a/src/tallies/tally_filter_azimuthal.F90 +++ b/src/tallies/tally_filter_azimuthal.F90 @@ -2,8 +2,6 @@ module tally_filter_azimuthal use, intrinsic :: ISO_C_BINDING - use hdf5 - use algorithm, only: binary_search use constants use error, only: fatal_error diff --git a/src/tallies/tally_filter_cell.F90 b/src/tallies/tally_filter_cell.F90 index 8d2b93d28..00ab8fa15 100644 --- a/src/tallies/tally_filter_cell.F90 +++ b/src/tallies/tally_filter_cell.F90 @@ -2,8 +2,6 @@ module tally_filter_cell use, intrinsic :: ISO_C_BINDING - use hdf5 - use constants, only: ONE, MAX_LINE_LEN use dict_header, only: EMPTY use error, only: fatal_error diff --git a/src/tallies/tally_filter_cellborn.F90 b/src/tallies/tally_filter_cellborn.F90 index 450858b6b..0d7461ef8 100644 --- a/src/tallies/tally_filter_cellborn.F90 +++ b/src/tallies/tally_filter_cellborn.F90 @@ -2,8 +2,6 @@ module tally_filter_cellborn use, intrinsic :: ISO_C_BINDING - use hdf5 - use constants, only: ONE, MAX_LINE_LEN use dict_header, only: EMPTY use error, only: fatal_error diff --git a/src/tallies/tally_filter_cellfrom.F90 b/src/tallies/tally_filter_cellfrom.F90 index 16cb294d5..2c5402122 100644 --- a/src/tallies/tally_filter_cellfrom.F90 +++ b/src/tallies/tally_filter_cellfrom.F90 @@ -2,8 +2,6 @@ module tally_filter_cellfrom use, intrinsic :: ISO_C_BINDING - use hdf5 - use constants, only: ONE, MAX_LINE_LEN use dict_header, only: EMPTY use error, only: fatal_error diff --git a/src/tallies/tally_filter_delayedgroup.F90 b/src/tallies/tally_filter_delayedgroup.F90 index 32010e815..1a662005b 100644 --- a/src/tallies/tally_filter_delayedgroup.F90 +++ b/src/tallies/tally_filter_delayedgroup.F90 @@ -2,8 +2,6 @@ module tally_filter_delayedgroup use, intrinsic :: ISO_C_BINDING - use hdf5 - use constants, only: ONE, MAX_LINE_LEN, MAX_DELAYED_GROUPS use error, only: fatal_error use hdf5_interface diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index 3cb34efe4..6c42161ca 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -2,8 +2,6 @@ module tally_filter_distribcell use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T - use constants use dict_header, only: EMPTY use error diff --git a/src/tallies/tally_filter_energy.F90 b/src/tallies/tally_filter_energy.F90 index d186fa62a..93da69edd 100644 --- a/src/tallies/tally_filter_energy.F90 +++ b/src/tallies/tally_filter_energy.F90 @@ -2,8 +2,6 @@ module tally_filter_energy use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T - use algorithm, only: binary_search use constants use error diff --git a/src/tallies/tally_filter_energyfunc.F90 b/src/tallies/tally_filter_energyfunc.F90 index efaabae2e..1402707b4 100644 --- a/src/tallies/tally_filter_energyfunc.F90 +++ b/src/tallies/tally_filter_energyfunc.F90 @@ -2,8 +2,6 @@ module tally_filter_energyfunc use, intrinsic :: ISO_C_BINDING - use hdf5 - use algorithm, only: binary_search use constants use error, only: fatal_error diff --git a/src/tallies/tally_filter_header.F90 b/src/tallies/tally_filter_header.F90 index 93d050b1a..e57423ddf 100644 --- a/src/tallies/tally_filter_header.F90 +++ b/src/tallies/tally_filter_header.F90 @@ -5,13 +5,12 @@ module tally_filter_header use constants, only: MAX_LINE_LEN use dict_header, only: DictIntInt use error + use hdf5_interface, only: HID_T use particle_header, only: Particle use stl_vector, only: VectorInt, VectorReal use string, only: to_str use xml_interface, only: XMLNode - use hdf5 - implicit none private public :: free_memory_tally_filter diff --git a/src/tallies/tally_filter_legendre.F90 b/src/tallies/tally_filter_legendre.F90 index 9545b8692..b4ee7b06b 100644 --- a/src/tallies/tally_filter_legendre.F90 +++ b/src/tallies/tally_filter_legendre.F90 @@ -2,8 +2,6 @@ module tally_filter_legendre use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T - use constants use error use hdf5_interface diff --git a/src/tallies/tally_filter_material.F90 b/src/tallies/tally_filter_material.F90 index 3b9f843f8..443dc6285 100644 --- a/src/tallies/tally_filter_material.F90 +++ b/src/tallies/tally_filter_material.F90 @@ -2,8 +2,6 @@ module tally_filter_material use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T - use constants use dict_header, only: DictIntInt, EMPTY use error diff --git a/src/tallies/tally_filter_mesh.F90 b/src/tallies/tally_filter_mesh.F90 index d2acdc4c8..486569da6 100644 --- a/src/tallies/tally_filter_mesh.F90 +++ b/src/tallies/tally_filter_mesh.F90 @@ -2,8 +2,6 @@ module tally_filter_mesh use, intrinsic :: ISO_C_BINDING - use hdf5 - use constants use dict_header, only: EMPTY use error diff --git a/src/tallies/tally_filter_meshsurface.F90 b/src/tallies/tally_filter_meshsurface.F90 index d90b63664..801d4252c 100644 --- a/src/tallies/tally_filter_meshsurface.F90 +++ b/src/tallies/tally_filter_meshsurface.F90 @@ -2,8 +2,6 @@ module tally_filter_meshsurface use, intrinsic :: ISO_C_BINDING - use hdf5 - use constants use dict_header, only: EMPTY use error diff --git a/src/tallies/tally_filter_mu.F90 b/src/tallies/tally_filter_mu.F90 index c949289b6..7f8bde923 100644 --- a/src/tallies/tally_filter_mu.F90 +++ b/src/tallies/tally_filter_mu.F90 @@ -2,8 +2,6 @@ module tally_filter_mu use, intrinsic :: ISO_C_BINDING - use hdf5 - use algorithm, only: binary_search use constants, only: ONE, TWO, MAX_LINE_LEN, NO_BIN_FOUND use error, only: fatal_error diff --git a/src/tallies/tally_filter_polar.F90 b/src/tallies/tally_filter_polar.F90 index 89815c102..7cbb55f8b 100644 --- a/src/tallies/tally_filter_polar.F90 +++ b/src/tallies/tally_filter_polar.F90 @@ -2,8 +2,6 @@ module tally_filter_polar use, intrinsic :: ISO_C_BINDING - use hdf5 - use algorithm, only: binary_search use constants use error, only: fatal_error diff --git a/src/tallies/tally_filter_sph_harm.F90 b/src/tallies/tally_filter_sph_harm.F90 index 5c47c2c71..bc95baaa3 100644 --- a/src/tallies/tally_filter_sph_harm.F90 +++ b/src/tallies/tally_filter_sph_harm.F90 @@ -2,8 +2,6 @@ module tally_filter_sph_harm use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T - use constants use error use hdf5_interface diff --git a/src/tallies/tally_filter_sptl_legendre.F90 b/src/tallies/tally_filter_sptl_legendre.F90 index d6594d05a..83db9a864 100644 --- a/src/tallies/tally_filter_sptl_legendre.F90 +++ b/src/tallies/tally_filter_sptl_legendre.F90 @@ -2,8 +2,6 @@ module tally_filter_sptl_legendre use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T - use constants use error use hdf5_interface diff --git a/src/tallies/tally_filter_surface.F90 b/src/tallies/tally_filter_surface.F90 index df3bf3603..ec642bc8d 100644 --- a/src/tallies/tally_filter_surface.F90 +++ b/src/tallies/tally_filter_surface.F90 @@ -2,8 +2,6 @@ module tally_filter_surface use, intrinsic :: ISO_C_BINDING - use hdf5 - use constants, only: ONE, MAX_LINE_LEN use dict_header, only: EMPTY use error, only: fatal_error diff --git a/src/tallies/tally_filter_universe.F90 b/src/tallies/tally_filter_universe.F90 index 742152232..0dc5aefe1 100644 --- a/src/tallies/tally_filter_universe.F90 +++ b/src/tallies/tally_filter_universe.F90 @@ -2,8 +2,6 @@ module tally_filter_universe use, intrinsic :: ISO_C_BINDING - use hdf5 - use constants, only: ONE, MAX_LINE_LEN use dict_header, only: EMPTY use error, only: fatal_error diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 index e96a53a7f..fa205b180 100644 --- a/src/tallies/tally_filter_zernike.F90 +++ b/src/tallies/tally_filter_zernike.F90 @@ -2,8 +2,6 @@ module tally_filter_zernike use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T - use constants use error use hdf5_interface diff --git a/src/track_output.F90 b/src/track_output.F90 index 350e687b1..95af9e6b0 100644 --- a/src/track_output.F90 +++ b/src/track_output.F90 @@ -5,8 +5,6 @@ module track_output - use hdf5 - use constants use hdf5_interface use particle_header, only: Particle diff --git a/src/urr_header.F90 b/src/urr_header.F90 index cc41d43cc..0ec9e89de 100644 --- a/src/urr_header.F90 +++ b/src/urr_header.F90 @@ -1,8 +1,7 @@ module urr_header - use hdf5, only: HID_T, HSIZE_T use hdf5_interface, only: read_attribute, open_dataset, read_dataset, & - close_dataset, get_shape + close_dataset, get_shape, HID_T, HSIZE_T implicit none diff --git a/src/volume_calc.F90 b/src/volume_calc.F90 index ac813a94a..fa770c1f3 100644 --- a/src/volume_calc.F90 +++ b/src/volume_calc.F90 @@ -2,7 +2,6 @@ module volume_calc use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T #ifdef _OPENMP use omp_lib #endif @@ -12,7 +11,7 @@ module volume_calc use geometry, only: find_cell use geometry_header, only: universes, cells use hdf5_interface, only: file_open, file_close, write_attribute, & - create_group, close_group, write_dataset + create_group, close_group, write_dataset, HID_T use output, only: header, time_stamp use material_header, only: materials use message_passing From 4fdfd51ab9bb6ad79aadf64d9f3f5e9f4da70cdb Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Apr 2018 14:44:10 -0500 Subject: [PATCH 203/361] Convert HDF5 read/write_tally_results (and fix attribute bug) --- src/hdf5_interface.F90 | 4 +- src/hdf5_interface.cpp | 47 +++++++++++++++++++- src/hdf5_interface.h | 7 +++ src/tallies/tally_header.F90 | 84 +++++++++++++----------------------- 4 files changed, 84 insertions(+), 58 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 5320ec9f5..752ce062d 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -1342,7 +1342,7 @@ contains ! Allocate a C char array to get strings n = attribute_typesize(obj_id, to_c_string(name)) m = size(buffer) - allocate(buffer_(n*m)) + allocate(buffer_((n+1)*m)) ! Read attribute call read_attr_string_c(obj_id, to_c_string(name), n, buffer_) @@ -1351,7 +1351,7 @@ contains do i = 1, m buffer(i) = '' do j = 1, n - k = (i-1)*n + j + k = (i-1)*(n+1) + j if (buffer_(k) == C_NULL_CHAR) exit buffer(i)(j:j) = buffer_(k) end do diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 935ef084a..ddc2dcdfc 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -350,6 +350,26 @@ read_complex(hid_t obj_id, const char* name, double _Complex* buffer, bool indep } +void +read_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, double* results) +{ + // Create dataspace for hyperslab in memory + hsize_t dims[] {n_filter, n_score, 3}; + hsize_t start[] {0, 0, 1}; + hsize_t count[] {n_filter, n_score, 2}; + hid_t memspace = H5Screate_simple(3, dims, nullptr); + H5Sselect_hyperslab(memspace, H5S_SELECT_SET, start, nullptr, count, nullptr); + + // Create and write dataset + hid_t dset = H5Dopen(group_id, "results", H5P_DEFAULT); + H5Dread(dset, H5T_NATIVE_DOUBLE, memspace, H5S_ALL, H5P_DEFAULT, results); + + // Free resources + H5Dclose(dset); + H5Sclose(memspace); +} + + void write_attr(hid_t obj_id, int ndim, const hsize_t* dims, const char* name, hid_t mem_type_id, const void* buffer) @@ -488,9 +508,34 @@ write_string(hid_t group_id, int ndim, const hsize_t* dims, size_t slen, void -write_string(hid_t group_id, char const* name, const std::string& buffer, bool indep) +write_string(hid_t group_id, const char* name, const std::string& buffer, bool indep) { write_string(group_id, 0, nullptr, buffer.length(), name, buffer.c_str(), indep); } + +void +write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, const double* results) +{ + // Set dimensions of sum/sum_sq hyperslab to store + hsize_t count[] {n_filter, n_score, 2}; + hid_t dspace = H5Screate_simple(3, count, nullptr); + + // Set dimensions of results array + hsize_t dims[] {n_filter, n_score, 3}; + hsize_t start[] {0, 0, 1}; + hid_t memspace = H5Screate_simple(3, dims, nullptr); + H5Sselect_hyperslab(memspace, H5S_SELECT_SET, start, nullptr, count, nullptr); + + // Create and write dataset + hid_t dset = H5Dcreate(group_id, "results", H5T_NATIVE_DOUBLE, dspace, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + H5Dwrite(dset, H5T_NATIVE_DOUBLE, memspace, H5S_ALL, H5P_DEFAULT, results); + + // Free resources + H5Dclose(dset); + H5Sclose(memspace); + H5Sclose(dspace); +} + } // namespace openmc diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 9ea170c6c..57f56f5c2 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -68,6 +68,9 @@ extern "C" void read_string(hid_t obj_id, const char* name, size_t slen, extern "C" void read_complex(hid_t obj_id, const char* name, double _Complex* buffer, bool indep); +extern "C" void read_tally_results(hid_t group_id, hsize_t n_filter, + hsize_t n_score, double* results); + void write_attr(hid_t obj_id, int ndim, const hsize_t* dims, const char* name, hid_t mem_type_id, const void* buffer); extern "C" void write_attr_double(hid_t obj_id, int ndim, const hsize_t* dims, @@ -90,5 +93,9 @@ extern "C" void write_string(hid_t group_id, int ndim, const hsize_t* dims, size const char* name, char const* buffer, bool indep); void write_string(hid_t group_id, const char* name, const std::string& buffer, bool indep); + +extern "C" void write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, + const double* results); + } // namespace openmc #endif //HDF5_INTERFACE_H diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 413464b7b..cbfa4f048 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -2,11 +2,10 @@ module tally_header use, intrinsic :: ISO_C_BINDING - use hdf5 - use constants use error use dict_header, only: DictIntInt + use hdf5_interface, only: HID_T, HSIZE_T use message_passing, only: n_procs use nuclide_header, only: nuclide_dict use settings, only: reduce_tallies, run_mode @@ -200,67 +199,42 @@ contains class(TallyObject), intent(in) :: this integer(HID_T), intent(in) :: group_id - integer :: hdf5_err - integer(HID_T) :: dset, dspace - integer(HID_T) :: memspace - integer(HSIZE_T) :: dims(3) - integer(HSIZE_T) :: dims_slab(3) - integer(HSIZE_T) :: offset(3) = [1,0,0] + integer(HSIZE_T) :: n_filter, n_score + interface + subroutine write_tally_results(group_id, n_filter, n_score, results) & + bind(C) + import HID_T, HSIZE_T, C_DOUBLE + integer(HID_T), value :: group_id + integer(HSIZE_T), value :: n_filter + integer(HSIZE_T), value :: n_score + real(C_DOUBLE), intent(in) :: results(*) + end subroutine write_tally_results + end interface - ! Create file dataspace - dims_slab(:) = shape(this % results) - dims_slab(1) = 2 - call h5screate_simple_f(3, dims_slab, dspace, hdf5_err) - - ! Create memory dataspace that contains only SUM and SUM_SQ values - dims(:) = shape(this % results) - call h5screate_simple_f(3, dims, memspace, hdf5_err) - call h5sselect_hyperslab_f(memspace, H5S_SELECT_SET_F, offset, dims_slab, & - hdf5_err) - - ! Create and write to dataset - call h5dcreate_f(group_id, "results", H5T_NATIVE_DOUBLE, dspace, dset, & - hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_DOUBLE, this % results, dims_slab, & - hdf5_err, mem_space_id=memspace) - - ! Close identifiers - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(memspace, hdf5_err) - call h5sclose_f(dspace, hdf5_err) + n_filter = size(this % results, 3) + n_score = size(this % results, 2) + call write_tally_results(group_id, n_filter, n_score, this % results) end subroutine tally_write_results_hdf5 subroutine tally_read_results_hdf5(this, group_id) class(TallyObject), intent(inout) :: this integer(HID_T), intent(in) :: group_id - integer :: hdf5_err - integer(HID_T) :: dset, dspace - integer(HID_T) :: memspace - integer(HSIZE_T) :: dims(3) - integer(HSIZE_T) :: dims_slab(3) - integer(HSIZE_T) :: offset(3) = [1,0,0] + integer(HSIZE_T) :: n_filter, n_score + interface + subroutine read_tally_results(group_id, n_filter, n_score, results) & + bind(C) + import HID_T, HSIZE_T, C_DOUBLE + integer(HID_T), value :: group_id + integer(HSIZE_T), value :: n_filter + integer(HSIZE_T), value :: n_score + real(C_DOUBLE), intent(out) :: results(*) + end subroutine read_tally_results + end interface - ! Create file dataspace - dims_slab(:) = shape(this % results) - dims_slab(1) = 2 - call h5screate_simple_f(3, dims_slab, dspace, hdf5_err) - - ! Create memory dataspace that contains only SUM and SUM_SQ values - dims(:) = shape(this % results) - call h5screate_simple_f(3, dims, memspace, hdf5_err) - call h5sselect_hyperslab_f(memspace, H5S_SELECT_SET_F, offset, dims_slab, & - hdf5_err) - - ! Create and write to dataset - call h5dopen_f(group_id, "results", dset, hdf5_err) - call h5dread_f(dset, H5T_NATIVE_DOUBLE, this % results, dims_slab, & - hdf5_err, mem_space_id=memspace) - - ! Close identifiers - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(memspace, hdf5_err) - call h5sclose_f(dspace, hdf5_err) + n_filter = size(this % results, 3) + n_score = size(this % results, 2) + call read_tally_results(group_id, n_filter, n_score, this % results) end subroutine tally_read_results_hdf5 !=============================================================================== From 64fa01f5375eb464e06ad8a548b3eeeeef94f8c3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 19 Apr 2018 15:51:40 -0500 Subject: [PATCH 204/361] Fix use of auto for transfer list --- src/hdf5_interface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index ddc2dcdfc..5549ac7d8 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -275,7 +275,7 @@ read_dataset(hid_t obj_id, const char* name, hid_t mem_type_id, if (using_mpio_device(dset)) { #ifdef PHDF5 // Set up collective vs independent I/O - auto data_xfer_mode {indep ? H5FD_MPIO_INDEPENDENT : H5FD_MPIO_COLLECTIVE}; + auto data_xfer_mode = indep ? H5FD_MPIO_INDEPENDENT : H5FD_MPIO_COLLECTIVE; // Create dataset transfer property list hid_t plist = H5Pcreate(H5P_DATASET_XFER); From 9b8cb2e0f132d63e1f557e3ad1ff7fa537d4f9c7 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 20 Apr 2018 06:38:16 -0500 Subject: [PATCH 205/361] Have openmc_init take a MPI_Comm* --- CMakeLists.txt | 1 + include/openmc.h | 12 +++++++++++- openmc/capi/core.py | 16 +++++++--------- src/api.F90 | 4 ++-- src/hdf5_interface.cpp | 15 ++++++++++----- src/initialize.F90 | 6 ++++-- src/initialize.cpp | 20 ++++++++++++++++++++ src/main.cpp | 14 ++++++-------- 8 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 src/initialize.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ff7a3334b..ab5251049 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -434,6 +434,7 @@ set(LIBOPENMC_FORTRAN_SRC ) set(LIBOPENMC_CXX_SRC src/error.h + src/initialize.cpp src/hdf5_interface.h src/hdf5_interface.cpp src/random_lcg.cpp diff --git a/include/openmc.h b/include/openmc.h index 194545345..85cb09a91 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -4,6 +4,10 @@ #include #include +#ifdef OPENMC_MPI +#include "mpi.h" +#endif + #ifdef __cplusplus extern "C" { #endif @@ -46,7 +50,8 @@ extern "C" { int64_t openmc_get_seed(); int openmc_get_tally_index(int32_t id, int32_t* index); void openmc_hard_reset(); - void openmc_init(const int* intracomm); + int openmc_init(const void* intracomm); + int openmc_init_f(const int* intracomm); int openmc_legendre_filter_get_order(int32_t index, int* order); int openmc_legendre_filter_set_order(int32_t index, int order); int openmc_load_nuclide(char name[]); @@ -145,6 +150,11 @@ extern "C" { extern bool openmc_simulation_initialized; extern int openmc_verbosity; +#ifdef OPENMC_MPI + // MPI variables + extern MPI_Comm openmc_intracomm; +#endif + // Run modes constexpr int RUN_MODE_FIXEDSOURCE {1}; constexpr int RUN_MODE_EIGENVALUE {2}; diff --git a/openmc/capi/core.py b/openmc/capi/core.py index d35690503..a278e69d5 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -1,6 +1,6 @@ from contextlib import contextmanager from ctypes import (CDLL, c_int, c_int32, c_int64, c_double, c_char_p, - POINTER, Structure) + POINTER, Structure, c_void_p) from warnings import warn import numpy as np @@ -27,7 +27,7 @@ _dll.openmc_find.argtypes = [POINTER(c_double*3), c_int, POINTER(c_int32), _dll.openmc_find.restype = c_int _dll.openmc_find.errcheck = _error_handler _dll.openmc_hard_reset.restype = None -_dll.openmc_init.argtypes = [POINTER(c_int)] +_dll.openmc_init.argtypes = [c_void_p] _dll.openmc_init.restype = None _dll.openmc_get_keff.argtypes = [POINTER(c_double*2)] _dll.openmc_get_keff.restype = c_int @@ -116,13 +116,11 @@ def init(intracomm=None): """ if intracomm is not None: - # If an mpi4py communicator was passed, convert it to an integer to - # be passed to openmc_init - try: - intracomm = intracomm.py2f() - except AttributeError: - pass - _dll.openmc_init(c_int(intracomm)) + # If an mpi4py communicator was passed, convert it to void* to be passed + # to openmc_init + from mpi4py import MPI + address = MPI._addressof(intracomm) + _dll.openmc_init(c_void_p(address)) else: _dll.openmc_init(None) diff --git a/src/api.F90 b/src/api.F90 index d7f3ed023..03ea9bff0 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -15,7 +15,7 @@ module openmc_api use mesh_header use message_passing use nuclide_header - use initialize, only: openmc_init + use initialize, only: openmc_init_f use particle_header, only: Particle use plot, only: openmc_plot_geometry use random_lcg, only: openmc_get_seed, openmc_set_seed @@ -64,7 +64,7 @@ module openmc_api public :: openmc_get_tally_index public :: openmc_global_tallies public :: openmc_hard_reset - public :: openmc_init + public :: openmc_init_f public :: openmc_load_nuclide public :: openmc_material_add_nuclide public :: openmc_material_get_id diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 5549ac7d8..b31bf8c1a 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -1,14 +1,19 @@ #include "hdf5_interface.h" -#include "error.h" - -#include "hdf5.h" -#include "hdf5_hl.h" #include #include #include #include +#include "hdf5.h" +#include "hdf5_hl.h" +#ifdef OPENMC_MPI +#include "mpi.h" +#include "openmc.h" +#endif +#include "error.h" + + namespace openmc { bool @@ -158,7 +163,7 @@ file_open(const char* filename, char mode, bool parallel) if (parallel) { // Setup file access property list with parallel I/O access plist = H5Pcreate(H5P_FILE_ACCESS); - H5Pset_fapl_mpio(plist, mpi_intracomm, MPI_INFO_NULL); + H5Pset_fapl_mpio(plist, openmc_intracomm, MPI_INFO_NULL); } #endif diff --git a/src/initialize.F90 b/src/initialize.F90 index 7b699b9cb..773c6dbf0 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -41,8 +41,9 @@ contains ! setting up timers, etc. !=============================================================================== - subroutine openmc_init(intracomm) bind(C) + function openmc_init_f(intracomm) result(err) bind(C) integer, intent(in), optional :: intracomm ! MPI intracommunicator + integer(C_INT) :: err #ifdef _OPENMP character(MAX_WORD_LEN) :: envvar @@ -105,7 +106,8 @@ contains ! Stop initialization timer call time_initialize%stop() - end subroutine openmc_init + err = 0 + end function openmc_init_f #ifdef OPENMC_MPI !=============================================================================== diff --git a/src/initialize.cpp b/src/initialize.cpp new file mode 100644 index 000000000..029a4155d --- /dev/null +++ b/src/initialize.cpp @@ -0,0 +1,20 @@ +#include "openmc.h" + +#ifdef OPENMC_MPI +MPI_Comm openmc_intracomm; +#endif + + +int +openmc_init(const void* intracomm) +{ +#ifdef OPENMC_MPI + openmc_intracomm = *static_cast(intracomm); + + MPI_Fint fcomm = MPI_Comm_c2f(openmc_intracomm); + openmc_init_f(&fcomm); +#else + openmc_init_f(nullptr); +#endif + return 0; +} diff --git a/src/main.cpp b/src/main.cpp index 7b2ccb5c3..2814b538f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,15 @@ #include "openmc.h" -#ifdef MPI -#include -#else -#define MPI_COMM_WORLD nullptr -#endif - - int main(int argc, char** argv) { int err; // Initialize run -- when run with MPI, pass communicator - openmc_init(MPI_COMM_WORLD); +#ifdef OPENMC_MPI + MPI_Comm world {MPI_COMM_WORLD}; + openmc_init(&world); +#else + openmc_init(nullptr); +#endif // start problem based on mode switch (openmc_run_mode) { From 25b1001558092e487a17e67fcaaacec214022127 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 20 Apr 2018 07:02:59 -0500 Subject: [PATCH 206/361] Move mpi-related definitions into message_passing.h --- CMakeLists.txt | 1 + include/openmc.h | 8 -------- src/hdf5_interface.cpp | 4 ++-- src/initialize.cpp | 8 +++----- src/message_passing.cpp | 15 +++++++++++++++ src/message_passing.h | 18 ++++++++++++++++++ 6 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 src/message_passing.cpp create mode 100644 src/message_passing.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ab5251049..8c6772834 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -437,6 +437,7 @@ set(LIBOPENMC_CXX_SRC src/initialize.cpp src/hdf5_interface.h src/hdf5_interface.cpp + src/message_passing.cpp src/random_lcg.cpp src/random_lcg.h src/simulation.cpp diff --git a/include/openmc.h b/include/openmc.h index 85cb09a91..1788fb8cc 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -4,9 +4,6 @@ #include #include -#ifdef OPENMC_MPI -#include "mpi.h" -#endif #ifdef __cplusplus extern "C" { @@ -150,11 +147,6 @@ extern "C" { extern bool openmc_simulation_initialized; extern int openmc_verbosity; -#ifdef OPENMC_MPI - // MPI variables - extern MPI_Comm openmc_intracomm; -#endif - // Run modes constexpr int RUN_MODE_FIXEDSOURCE {1}; constexpr int RUN_MODE_EIGENVALUE {2}; diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index b31bf8c1a..cbbd7d7c3 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -9,7 +9,7 @@ #include "hdf5_hl.h" #ifdef OPENMC_MPI #include "mpi.h" -#include "openmc.h" +#include "message_passing.h" #endif #include "error.h" @@ -163,7 +163,7 @@ file_open(const char* filename, char mode, bool parallel) if (parallel) { // Setup file access property list with parallel I/O access plist = H5Pcreate(H5P_FILE_ACCESS); - H5Pset_fapl_mpio(plist, openmc_intracomm, MPI_INFO_NULL); + H5Pset_fapl_mpio(plist, openmc::mpi::intracomm, MPI_INFO_NULL); } #endif diff --git a/src/initialize.cpp b/src/initialize.cpp index 029a4155d..4f3261dad 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -1,17 +1,15 @@ #include "openmc.h" -#ifdef OPENMC_MPI -MPI_Comm openmc_intracomm; -#endif +#include "message_passing.h" int openmc_init(const void* intracomm) { #ifdef OPENMC_MPI - openmc_intracomm = *static_cast(intracomm); + openmc::mpi::intracomm = *static_cast(intracomm); - MPI_Fint fcomm = MPI_Comm_c2f(openmc_intracomm); + MPI_Fint fcomm = MPI_Comm_c2f(openmc::mpi::intracomm); openmc_init_f(&fcomm); #else openmc_init_f(nullptr); diff --git a/src/message_passing.cpp b/src/message_passing.cpp new file mode 100644 index 000000000..3ab96ffd8 --- /dev/null +++ b/src/message_passing.cpp @@ -0,0 +1,15 @@ +#include "message_passing.h" + +namespace openmc { +namespace mpi { + +int rank; +int n_procs; + +#ifdef OPENMC_MPI +MPI_Comm intracomm; +MPI_Datatype bank; +#endif + +} // namespace mpi +} // namespace openmc diff --git a/src/message_passing.h b/src/message_passing.h new file mode 100644 index 000000000..fea092c61 --- /dev/null +++ b/src/message_passing.h @@ -0,0 +1,18 @@ +#ifndef MESSAGE_PASSING_H +#define MESSAGE_PASSING_H + +namespace openmc { +namespace mpi { + + extern int rank; + extern int n_procs; + +#ifdef OPENMC_MPI + extern MPI_Datatype bank; + extern MPI_Comm intracomm; +#endif + +} // namespace mpi +} // namespace openmc + +#endif // MESSAGE_PASSING_H From 0ee5ec4ecbb634f08c008a08cdcd60617f584c9f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 20 Apr 2018 07:25:35 -0500 Subject: [PATCH 207/361] Make sure headers only contain declarations and inline funcs --- CMakeLists.txt | 9 ++----- src/hdf5_interface.cpp | 2 +- src/surface.cpp | 49 ++++++++++++++++++++++++++++++++++++ src/surface.h | 56 +++++++++++------------------------------- src/xml_interface.cpp | 40 ++++++++++++++++++++++++++++++ src/xml_interface.h | 32 ++---------------------- 6 files changed, 109 insertions(+), 79 deletions(-) create mode 100644 src/xml_interface.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c6772834..455556f01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -433,19 +433,14 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/trigger_header.F90 ) set(LIBOPENMC_CXX_SRC - src/error.h src/initialize.cpp - src/hdf5_interface.h src/hdf5_interface.cpp src/message_passing.cpp src/random_lcg.cpp - src/random_lcg.h src/simulation.cpp src/surface.cpp - src/surface.h - src/xml_interface.h - src/pugixml/pugixml.cpp - src/pugixml/pugixml.hpp) + src/xml_interface.cpp + 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.cpp) diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index cbbd7d7c3..1502ec2e1 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -2,8 +2,8 @@ #include #include -#include #include +#include #include "hdf5.h" #include "hdf5_hl.h" diff --git a/src/surface.cpp b/src/surface.cpp index 9db9db3fa..2c3279604 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -11,6 +11,12 @@ namespace openmc { +//============================================================================== +// Global variables +//============================================================================== + +int32_t n_surfaces; + //============================================================================== // Helper functions for reading the "coeffs" node of an XML surface element //============================================================================== @@ -1232,4 +1238,47 @@ read_surfaces(pugi::xml_node *node) } } +//============================================================================== +// Fortran compatibility functions +//============================================================================== + +extern "C" Surface* surface_pointer(int surf_ind) {return surfaces_c[surf_ind];} + +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(Surface *surf, double xyz[3], double uvw[3], bool coincident) +{return surf->distance(xyz, uvw, coincident);} + +extern "C" void surface_normal(Surface *surf, double xyz[3], double uvw[3]) +{return surf->normal(xyz, uvw);} + +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(PeriodicSurface *surf, PeriodicSurface *other, double xyz[3], + double uvw[3]) +{return surf->periodic_translate(other, xyz, uvw);} + +extern "C" void free_memory_surfaces_c() +{ + for (int i = 0; i < n_surfaces; i++) {delete surfaces_c[i];} + delete surfaces_c; + surfaces_c = nullptr; + n_surfaces = 0; + surface_dict.clear(); +} + } // namespace openmc diff --git a/src/surface.h b/src/surface.h index 627a8fbae..15ac0d5bd 100644 --- a/src/surface.h +++ b/src/surface.h @@ -25,15 +25,14 @@ extern "C" const int BC_PERIODIC {3}; //============================================================================== extern "C" double FP_COINCIDENT; -constexpr double INFTY{std::numeric_limits::max()}; +constexpr double INFTY {std::numeric_limits::max()}; constexpr int C_NONE {-1}; //============================================================================== // Global variables //============================================================================== -// Braces force n_surfaces to be defined here, not just declared. -extern "C" {int32_t n_surfaces {0};} +extern "C" int32_t n_surfaces; class Surface; Surface **surfaces_c; @@ -384,44 +383,19 @@ public: // Fortran compatibility functions //============================================================================== -extern "C" Surface* surface_pointer(int surf_ind) {return surfaces_c[surf_ind];} - -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(Surface *surf, double xyz[3], double uvw[3], bool coincident) -{return surf->distance(xyz, uvw, coincident);} - -extern "C" void surface_normal(Surface *surf, double xyz[3], double uvw[3]) -{return surf->normal(xyz, uvw);} - -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(PeriodicSurface *surf, PeriodicSurface *other, double xyz[3], - double uvw[3]) -{return surf->periodic_translate(other, xyz, uvw);} - -extern "C" void free_memory_surfaces_c() -{ - for (int i = 0; i < n_surfaces; i++) {delete surfaces_c[i];} - delete surfaces_c; - surfaces_c = nullptr; - n_surfaces = 0; - surface_dict.clear(); -} +extern "C" Surface* surface_pointer(int surf_ind); +extern "C" int surface_id(Surface *surf); +extern "C" int surface_bc(Surface *surf); +extern "C" bool surface_sense(Surface *surf, double xyz[3], double uvw[3]); +extern "C" void surface_reflect(Surface *surf, double xyz[3], double uvw[3]); +extern "C" double surface_distance(Surface *surf, double xyz[3], double uvw[3], + bool coincident); +extern "C" void surface_normal(Surface *surf, double xyz[3], double uvw[3]); +extern "C" void surface_to_hdf5(Surface *surf, hid_t group); +extern "C" int surface_i_periodic(PeriodicSurface *surf); +extern "C" bool surface_periodic(PeriodicSurface *surf, PeriodicSurface *other, + double xyz[3], double uvw[3]); +extern "C" void free_memory_surfaces_c(); } // namespace openmc #endif // SURFACE_H diff --git a/src/xml_interface.cpp b/src/xml_interface.cpp new file mode 100644 index 000000000..aea1c2c17 --- /dev/null +++ b/src/xml_interface.cpp @@ -0,0 +1,40 @@ +#include "xml_interface.h" + +#include // for std::transform +#include +#include + +#include "pugixml/pugixml.hpp" +#include "error.h" + + +namespace openmc { + +std::string +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; + if (node.attribute(name)) { + value_char = node.attribute(name).value(); + } else if (node.child(name)) { + value_char = node.child_value(name); + } else { + std::stringstream err_msg; + err_msg << "Node \"" << name << "\" is not a member of the \"" + << node.name() << "\" XML node"; + fatal_error(err_msg); + } + + // 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; +} + +} // namespace openmc diff --git a/src/xml_interface.h b/src/xml_interface.h index 778497562..52aa6003f 100644 --- a/src/xml_interface.h +++ b/src/xml_interface.h @@ -1,8 +1,6 @@ #ifndef XML_INTERFACE_H #define XML_INTERFACE_H -#include // for std::transform -#include #include #include "pugixml/pugixml.hpp" @@ -10,39 +8,13 @@ namespace openmc { -bool +inline bool check_for_node(pugi::xml_node node, const char *name) { return node.attribute(name) || node.child(name); } - -std::string -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; - if (node.attribute(name)) { - value_char = node.attribute(name).value(); - } else if (node.child(name)) { - value_char = node.child_value(name); - } else { - std::stringstream err_msg; - err_msg << "Node \"" << name << "\" is not a member of the \"" - << node.name() << "\" XML node"; - fatal_error(err_msg); - } - - // 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; -} +std::string get_node_value(pugi::xml_node node, const char *name); } // namespace openmc #endif // XML_INTERFACE_H From 44ff22dc148ecf64e8e61cf589f9abcb89e84bd9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 20 Apr 2018 11:03:40 -0500 Subject: [PATCH 208/361] Convert write_source_bank to C++, move some MPI initialization over too --- CMakeLists.txt | 1 + include/openmc.h | 7 +- openmc/capi/core.py | 14 ++-- src/initialize.F90 | 16 ----- src/initialize.cpp | 42 +++++++++++- src/message_passing.F90 | 6 +- src/simulation_header.F90 | 4 +- src/source.F90 | 2 +- src/state_point.F90 | 135 ++++---------------------------------- src/state_point.cpp | 112 +++++++++++++++++++++++++++++++ src/state_point.h | 15 +++++ 11 files changed, 200 insertions(+), 154 deletions(-) create mode 100644 src/state_point.cpp create mode 100644 src/state_point.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 455556f01..9b750a6dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -438,6 +438,7 @@ set(LIBOPENMC_CXX_SRC src/message_passing.cpp src/random_lcg.cpp src/simulation.cpp + src/state_point.cpp src/surface.cpp src/xml_interface.cpp src/pugixml/pugixml.cpp) diff --git a/include/openmc.h b/include/openmc.h index 1788fb8cc..7f2bbf46c 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -127,7 +127,6 @@ extern "C" { extern char openmc_err_msg[256]; extern double openmc_keff; extern double openmc_keff_std; - extern bool openmc_master; extern int32_t n_batches; extern int32_t n_cells; extern int32_t n_filters; @@ -147,6 +146,12 @@ extern "C" { extern bool openmc_simulation_initialized; extern int openmc_verbosity; + // Variables that are shared by necessity (can be removed later) + extern bool openmc_master; + extern int openmc_n_procs; + extern int openmc_rank; + extern int64_t openmc_work; + // Run modes constexpr int RUN_MODE_FIXEDSOURCE {1}; constexpr int RUN_MODE_EIGENVALUE {2}; diff --git a/openmc/capi/core.py b/openmc/capi/core.py index a278e69d5..57e2e88eb 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -118,11 +118,15 @@ def init(intracomm=None): if intracomm is not None: # If an mpi4py communicator was passed, convert it to void* to be passed # to openmc_init - from mpi4py import MPI - address = MPI._addressof(intracomm) - _dll.openmc_init(c_void_p(address)) - else: - _dll.openmc_init(None) + try: + from mpi4py import MPI + except ImportError: + intracomm = None + else: + address = MPI._addressof(intracomm) + intracomm = c_void_p(address) + + _dll.openmc_init(intracomm) def iter_batches(): diff --git a/src/initialize.F90 b/src/initialize.F90 index 773c6dbf0..7b8f37dee 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -131,27 +131,11 @@ contains integer :: bank_types(5) ! Datatypes #endif integer(MPI_ADDRESS_KIND) :: bank_disp(5) ! Displacements - logical :: init_called type(Bank) :: b ! Indicate that MPI is turned on mpi_enabled = .true. - - ! Initialize MPI - call MPI_INITIALIZED(init_called, mpi_err) - if (.not. init_called) call MPI_INIT(mpi_err) - - ! Determine number of processors and rank of each processor mpi_intracomm = intracomm - call MPI_COMM_SIZE(mpi_intracomm, n_procs, mpi_err) - call MPI_COMM_RANK(mpi_intracomm, rank, mpi_err) - - ! Determine master - if (rank == 0) then - master = .true. - else - master = .false. - end if ! ========================================================================== ! CREATE MPI_BANK TYPE diff --git a/src/initialize.cpp b/src/initialize.cpp index 4f3261dad..d32a6c97f 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -3,12 +3,14 @@ #include "message_passing.h" -int -openmc_init(const void* intracomm) +int openmc_init(const void* intracomm) { #ifdef OPENMC_MPI - openmc::mpi::intracomm = *static_cast(intracomm); + // Initialize MPI for C++ + MPI_Comm intracomm = *static_cast(intracomm); + initialize_mpi(intracomm); + // Continue with rest of initialization MPI_Fint fcomm = MPI_Comm_c2f(openmc::mpi::intracomm); openmc_init_f(&fcomm); #else @@ -16,3 +18,37 @@ openmc_init(const void* intracomm) #endif return 0; } + + +#ifdef OPENMC_MPI +void initialize_mpi(MPI_Comm intracomm) +{ + openmc::mpi::intracomm = intracomm; + + // Initialize MPI + int flag; + if (!MPI_Initialized(&flag)) MPI_Init(nullptr, nullptr); + + // Determine number of processes and rank for each + MPI_Comm_size(intracomm, openmc::mpi::n_procs); + MPI_Comm_rank(intracomm, openmc::mpi::rank); + + // Set variable for Fortran side + openmc_n_procs = openmc::mpi::n_procs; + openmc_rank = openmc::mpi::rank; + openmc_master = (openmc::mpi::rank == 0); + + // Create bank datatype + Bank b; + MPI_Aint disp[5]; + disp[0] = &b.wgt - &b; + disp[1] = &b.xyz - &b; + disp[2] = &b.uvw - &b; + disp[3] = &b.E - &b; + disp[4] = &b.delayed_group - &b; + int blocks[] {1, 3, 3, 1, 1}; + MPI_Datatype types[] {MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE, MPI_INT}; + MPI_Type_create_struct(5, blocks, disp, types, &openmc::mpi::bank); + MPI_Type_commit(&openmc::mpi::bank); +} +#endif diff --git a/src/message_passing.F90 b/src/message_passing.F90 index f9a16ee75..6ade0895a 100644 --- a/src/message_passing.F90 +++ b/src/message_passing.F90 @@ -14,9 +14,9 @@ module message_passing ! mpi_enabled flag are for when MPI is not being used at all, i.e. a serial ! run. In this case, these variables are still used at times. - integer :: n_procs = 1 ! number of processes - integer :: rank = 0 ! rank of process - logical(C_BOOL), bind(C, name='openmc_master') :: master = .true. ! master process? + integer(C_INT), bind(C, name='openmc_n_procs') :: n_procs = 1 ! number of processes + integer(C_INT), bind(C, name='openmc_rank') :: rank = 0 ! rank of process + logical(C_BOOL), bind(C, name='openmc_master') :: master = .true. ! master process? logical :: mpi_enabled = .false. ! is MPI in use and initialized? #ifdef OPENMC_MPIF08 type(MPI_Datatype) :: MPI_BANK ! MPI datatype for fission bank diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index 67bf3062b..a214f4410 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -32,8 +32,8 @@ module simulation_header logical :: satisfy_triggers = .false. ! whether triggers are satisfied - integer(8) :: work ! number of particles per processor - integer(8), allocatable :: work_index(:) ! starting index in source bank for each process + integer(C_INT64_T), bind(C, name='openmc_work') :: work ! number of particles per processor + integer(C_INT64_T), allocatable :: work_index(:) ! starting index in source bank for each process integer(8) :: current_work ! index in source bank of current history simulated ! ============================================================================ diff --git a/src/source.F90 b/src/source.F90 index 8e142fcfa..59a117465 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -88,7 +88,7 @@ contains call write_message('Writing out initial source...', 5) filename = trim(path_output) // 'initial_source.h5' file_id = file_open(filename, 'w', parallel=.true.) - call write_source_bank(file_id) + call write_source_bank(file_id, work_index, source_bank) call file_close(file_id) end if diff --git a/src/state_point.F90 b/src/state_point.F90 index 62240f110..181ab968b 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -15,6 +15,7 @@ module state_point use hdf5 + use bank_header, only: Bank use cmfd_header use constants use eigenvalue, only: openmc_get_keff @@ -37,6 +38,15 @@ module state_point implicit none + interface + subroutine write_source_bank(group_id, work_index, bank_) bind(C) + import HID_T, C_INT64_T, Bank + integer(HID_T), value :: group_id + integer(C_INT64_T), intent(in) :: work_index(*) + type(Bank), intent(in) :: bank_(*) + end subroutine write_source_bank + end interface + contains !=============================================================================== @@ -489,7 +499,7 @@ contains end if end if - call write_source_bank(file_id) + call write_source_bank(file_id, work_index, source_bank) if (master .or. parallel) call file_close(file_id) end if @@ -502,7 +512,7 @@ contains call write_dataset(file_id, "filetype", 'source') end if - call write_source_bank(file_id) + call write_source_bank(file_id, work_index, source_bank) if (master .or. parallel) call file_close(file_id) end if @@ -831,132 +841,11 @@ contains end subroutine load_state_point -!=============================================================================== -! WRITE_SOURCE_BANK writes OpenMC source_bank data -!=============================================================================== - - subroutine write_source_bank(group_id) - use bank_header, only: Bank - - integer(HID_T), intent(in) :: group_id - - integer :: hdf5_err - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data or file space handle - integer(HID_T) :: memspace ! memory space handle - integer(HSIZE_T) :: offset(1) ! source data offset - integer(HSIZE_T) :: dims(1) - type(c_ptr) :: f_ptr -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#else - integer :: i -#ifdef OPENMC_MPI - integer :: mpi_err ! MPI error code - type(Bank), allocatable, target :: temp_source(:) -#endif -#endif - -#ifdef PHDF5 - ! Set size of total dataspace for all procs and rank - dims(1) = n_particles - call h5screate_simple_f(1, dims, dspace, hdf5_err) - call h5dcreate_f(group_id, "source_bank", hdf5_bank_t, dspace, dset, hdf5_err) - - ! Create another data space but for each proc individually - dims(1) = work - call h5screate_simple_f(1, dims, memspace, hdf5_err) - - ! Select hyperslab for this dataspace - offset(1) = work_index(rank) - call h5sselect_hyperslab_f(dspace, H5S_SELECT_SET_F, offset, dims, hdf5_err) - - ! Set up the property list for parallel writing - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, H5FD_MPIO_COLLECTIVE_F, hdf5_err) - - ! Set up pointer to data - f_ptr = c_loc(source_bank) - - ! Write data to file in parallel - call h5dwrite_f(dset, hdf5_bank_t, f_ptr, hdf5_err, & - file_space_id=dspace, mem_space_id=memspace, & - xfer_prp=plist) - - ! Close all ids - call h5sclose_f(dspace, hdf5_err) - call h5sclose_f(memspace, hdf5_err) - call h5dclose_f(dset, hdf5_err) - call h5pclose_f(plist, hdf5_err) - -#else - - if (master) then - ! Create dataset big enough to hold all source sites - dims(1) = n_particles - call h5screate_simple_f(1, dims, dspace, hdf5_err) - call h5dcreate_f(group_id, "source_bank", hdf5_bank_t, & - dspace, dset, hdf5_err) - - ! Save source bank sites since the souce_bank array is overwritten below -#ifdef OPENMC_MPI - allocate(temp_source(work)) - temp_source(:) = source_bank(:) -#endif - - do i = 0, n_procs - 1 - ! Create memory space - dims(1) = work_index(i+1) - work_index(i) - call h5screate_simple_f(1, dims, memspace, hdf5_err) - -#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, & - mpi_intracomm, MPI_STATUS_IGNORE, mpi_err) - end if -#endif - - ! Select hyperslab for this dataspace - call h5dget_space_f(dset, dspace, hdf5_err) - offset(1) = work_index(i) - call h5sselect_hyperslab_f(dspace, H5S_SELECT_SET_F, offset, dims, hdf5_err) - - ! Set up pointer to data and write data to hyperslab - f_ptr = c_loc(source_bank) - call h5dwrite_f(dset, hdf5_bank_t, f_ptr, hdf5_err, & - file_space_id=dspace, mem_space_id=memspace) - - call h5sclose_f(memspace, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - end do - - ! Close all ids - call h5dclose_f(dset, hdf5_err) - - ! Restore state of source bank -#ifdef OPENMC_MPI - source_bank(:) = temp_source(:) - deallocate(temp_source) -#endif - else -#ifdef OPENMC_MPI - call MPI_SEND(source_bank, int(work), MPI_BANK, 0, rank, & - mpi_intracomm, mpi_err) -#endif - end if - -#endif - - end subroutine write_source_bank - !=============================================================================== ! READ_SOURCE_BANK reads OpenMC source_bank data !=============================================================================== subroutine read_source_bank(group_id) - use bank_header, only: Bank - integer(HID_T), intent(in) :: group_id integer :: hdf5_err diff --git a/src/state_point.cpp b/src/state_point.cpp new file mode 100644 index 000000000..20338c4be --- /dev/null +++ b/src/state_point.cpp @@ -0,0 +1,112 @@ +#include "state_point.h" + +#include +#include + +#include "mpi.h" +#include "message_passing.h" +#include "openmc.h" + +namespace openmc { + +void +write_source_bank(hid_t group_id, int64_t* work_index, const Bank* source_bank) +{ + // Create type for array of 3 reals + hsize_t dims[] {3}; + hid_t triplet = H5Tarray_create(H5T_NATIVE_DOUBLE, 1, dims); + + // Create bank datatype + hid_t banktype = H5Tcreate(H5T_COMPOUND, sizeof(struct Bank)); + H5Tinsert(banktype, "wgt", HOFFSET(Bank, wgt), H5T_NATIVE_DOUBLE); + H5Tinsert(banktype, "xyz", HOFFSET(Bank, xyz), triplet); + H5Tinsert(banktype, "uvw", HOFFSET(Bank, uvw), triplet); + H5Tinsert(banktype, "E", HOFFSET(Bank, E), H5T_NATIVE_DOUBLE); + H5Tinsert(banktype, "delayed_group", HOFFSET(Bank, delayed_group), H5T_NATIVE_INT); + +#ifdef PHDF5 + // Set size of total dataspace for all procs and rank + dims[0] = n_particles; + hid_t dspace = H5Screate_simple(1, dims, H5P_DEFAULT); + hid_t dset = H5Dcreate(group_id, "source_bank", banktype, dspace, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + + // Create another data space but for each proc individually + hsize_t count[] {openmc_work}; + hid_t memspace = H5Screate_simple(1, count, H5P_DEFAULT); + + // Select hyperslab for this dataspace + hsize_t start[] {work_index[openmc::mpi::rank]}; + H5Sselect_hyperslab(dspace, H5S_SELECT_SET, start, nullptr, count, nullptr); + + // Set up the property list for parallel writing + hid_t plist = H5Pcreate(H5P_DATASET_XFER); + H5Pset_dxpl_mpio_f(plist, H5FD_MPIO_COLLECTIVE); + + // Write data to file in parallel + H5Dwrite(dset, banktype, memspace, dspace, memspace, plist, source_bank); + + // Free resources + H5Sclose(dspace); + h5sclose(memspace); + H5Dclose(dset); + H5Pclose(plist); + +#else + + if (openmc_master) { + // Create dataset big enough to hold all source sites + hsize_t dims[] {static_cast(n_particles)}; + hid_t dspace = H5Screate_simple(1, dims, H5P_DEFAULT); + hid_t dset = H5Dcreate(group_id, "source_bank", banktype, dspace, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + + // Save source bank sites since the souce_bank array is overwritten below +#ifdef OPENMC_MPI + std::vector temp_source {source_bank, source_bank + openmc_work}; +#endif + + for (int i = 0; i < openmc::mpi::n_procs; ++i) { + // Create memory space + hsize_t count[] {static_cast(work_index[i+1] - work_index[i])}; + hid_t memspace = H5Screate_simple(1, count, H5P_DEFAULT); + +#ifdef OPENMC_MPI + // Receive source sites from other processes + if (i > 0) + MPI_Recv(source_bank, count[0], openmc::mpi::bank, i, i, + openmc::mpi::intracomm, MPI_STATUS_IGNORE); +#endif + + // Select hyperslab for this dataspace + dspace = H5Dget_space(dset); + hsize_t start[] {static_cast(work_index[i])}; + H5Sselect_hyperslab(dspace, H5S_SELECT_SET, start, nullptr, count, nullptr); + + // Write data to hyperslab + H5Dwrite(dset, banktype, memspace, dspace, H5P_DEFAULT, source_bank); + + H5Sclose(memspace); + H5Sclose(dspace); + } + + // Close all ids + H5Dclose(dset); + +#ifdef OPENMC_MPI + // Restore state of source bank + std::copy(temp_source.begin(), temp_source.end(), source_bank); +#endif + } else { +#ifdef OPENMC_MPI + MPI_Send(source_bank, openmc_work, openmc::mpi::bank, 0, openmc::mpi::rank, + openmc::mpi::mpi_intracomm); +#endif + } +#endif + + H5Tclose(banktype); + H5Tclose(triplet); +} + +} // namespace openmc diff --git a/src/state_point.h b/src/state_point.h new file mode 100644 index 000000000..f0dafdb80 --- /dev/null +++ b/src/state_point.h @@ -0,0 +1,15 @@ +#ifndef STATE_POINT_H +#define STATE_POINT_H + +#include + +#include "hdf5.h" +#include "openmc.h" + +namespace openmc { + +extern "C" void write_source_bank(hid_t group_id, int64_t* work_index, + const struct Bank* bank); + +} // namespace openmc +#endif // STATE_POINT_H From 65095096f9ad72981f1be9c6ad9d89e55e77ea12 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 20 Apr 2018 13:53:53 -0500 Subject: [PATCH 209/361] Convert read_source_bank to C++ and fix a bunch of bugs --- src/hdf5_interface.F90 | 18 +++++------ src/hdf5_interface.cpp | 7 +++- src/initialize.cpp | 40 +++++++++++++++-------- src/initialize.h | 14 ++++++++ src/main.cpp | 4 +++ src/message_passing.h | 2 ++ src/mgxs_data.F90 | 4 +++ src/mgxs_header.F90 | 20 ++++++++++++ src/source.F90 | 2 +- src/state_point.F90 | 73 +++++------------------------------------- src/state_point.cpp | 71 ++++++++++++++++++++++++++++++++++------ src/state_point.h | 4 ++- 12 files changed, 159 insertions(+), 100 deletions(-) create mode 100644 src/initialize.h diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 752ce062d..d76f7b6b7 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -138,7 +138,7 @@ module hdf5_interface integer(HID_T), value :: obj_id type(C_PTR), value :: name real(C_DOUBLE), intent(out) :: buffer(*) - logical(C_BOOL), intent(in) :: indep + logical(C_BOOL), value :: indep end subroutine read_double_c subroutine read_attr_int_c(obj_id, name, buffer) & @@ -172,7 +172,7 @@ module hdf5_interface integer(HID_T), value :: obj_id type(C_PTR), value :: name integer(C_INT), intent(out) :: buffer(*) - logical(C_BOOL), intent(in) :: indep + logical(C_BOOL), value :: indep end subroutine read_int_c subroutine read_llong_c(obj_id, name, buffer, indep) & @@ -181,7 +181,7 @@ module hdf5_interface integer(HID_T), value :: obj_id type(C_PTR), value :: name integer(C_LONG_LONG), intent(out) :: buffer(*) - logical(C_BOOL), intent(in) :: indep + logical(C_BOOL), value :: indep end subroutine read_llong_c subroutine read_string_c(obj_id, name, slen, buffer, indep) & @@ -191,7 +191,7 @@ module hdf5_interface type(C_PTR), value :: name integer(C_SIZE_T), value :: slen character(kind=C_CHAR), intent(out) :: buffer(*) - logical(C_BOOL), intent(in) :: indep + logical(C_BOOL), value :: indep end subroutine read_string_c subroutine read_complex_c(obj_id, name, buffer, indep) & @@ -200,7 +200,7 @@ module hdf5_interface integer(HID_T), value :: obj_id type(C_PTR), value :: name complex(C_DOUBLE_COMPLEX), intent(out) :: buffer(*) - logical(C_BOOL), intent(in) :: indep + logical(C_BOOL), value :: indep end subroutine read_complex_c subroutine write_attr_double_c(obj_id, ndim, dims, name, buffer) & @@ -239,7 +239,7 @@ module hdf5_interface integer(HSIZE_T), intent(in) :: dims(*) character(kind=C_CHAR), intent(in) :: name(*) real(C_DOUBLE), intent(in) :: buffer(*) - logical(C_BOOL), intent(in) :: indep + logical(C_BOOL), value :: indep end subroutine write_double_c subroutine write_int_c(group_id, ndim, dims, name, buffer, indep) & @@ -250,7 +250,7 @@ module hdf5_interface integer(HSIZE_T), intent(in) :: dims(*) character(kind=C_CHAR), intent(in) :: name(*) integer(C_INT), intent(in) :: buffer(*) - logical(C_BOOL), intent(in) :: indep + logical(C_BOOL), value :: indep end subroutine write_int_c subroutine write_llong_c(group_id, ndim, dims, name, buffer, indep) & @@ -261,7 +261,7 @@ module hdf5_interface integer(HSIZE_T), intent(in) :: dims(*) character(kind=C_CHAR), intent(in) :: name(*) integer(C_LONG_LONG), intent(in) :: buffer(*) - logical(C_BOOL), intent(in) :: indep + logical(C_BOOL), value :: indep end subroutine write_llong_c subroutine write_string_c(group_id, ndim, dims, slen, name, buffer, indep) & @@ -273,7 +273,7 @@ module hdf5_interface integer(C_SIZE_T), value :: slen character(kind=C_CHAR), intent(in) :: name(*) character(kind=C_CHAR), intent(in) :: buffer(*) - logical(C_BOOL), intent(in) :: indep + logical(C_BOOL), value :: indep end subroutine write_string_c end interface diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 1502ec2e1..d81c5b34a 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -174,6 +174,11 @@ file_open(const char* filename, char mode, bool parallel) } else { file_id = H5Fopen(filename, flags, plist); } + if (file_id < 0) { + std::stringstream msg; + msg << "Failed to open HDF5 file with mode '" << mode << "': " << filename; + fatal_error(msg); + } #ifdef PHDF5 // Close the property list @@ -451,7 +456,7 @@ write_dataset(hid_t group_id, int ndim, const hsize_t* dims, const char* name, if (using_mpio_device(group_id)) { #ifdef PHDF5 // Set up collective vs independent I/O - auto data_xfer_mode {indep ? H5FD_MPIO_INDEPENDENT : H5FD_MPIO_COLLECTIVE}; + auto data_xfer_mode = indep ? H5FD_MPIO_INDEPENDENT : H5FD_MPIO_COLLECTIVE; // Create dataset transfer property list hid_t plist = H5Pcreate(H5P_DATASET_XFER); diff --git a/src/initialize.cpp b/src/initialize.cpp index d32a6c97f..23025feb2 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -1,17 +1,27 @@ -#include "openmc.h" +#include "initialize.h" + +#include #include "message_passing.h" +#include "openmc.h" int openmc_init(const void* intracomm) { #ifdef OPENMC_MPI + // Check if intracomm was passed + MPI_Comm comm; + if (intracomm) { + comm = *static_cast(intracomm); + } else { + comm = MPI_COMM_WORLD; + } + // Initialize MPI for C++ - MPI_Comm intracomm = *static_cast(intracomm); - initialize_mpi(intracomm); + openmc::initialize_mpi(comm); // Continue with rest of initialization - MPI_Fint fcomm = MPI_Comm_c2f(openmc::mpi::intracomm); + MPI_Fint fcomm = MPI_Comm_c2f(comm); openmc_init_f(&fcomm); #else openmc_init_f(nullptr); @@ -19,6 +29,7 @@ int openmc_init(const void* intracomm) return 0; } +namespace openmc { #ifdef OPENMC_MPI void initialize_mpi(MPI_Comm intracomm) @@ -30,8 +41,8 @@ void initialize_mpi(MPI_Comm intracomm) if (!MPI_Initialized(&flag)) MPI_Init(nullptr, nullptr); // Determine number of processes and rank for each - MPI_Comm_size(intracomm, openmc::mpi::n_procs); - MPI_Comm_rank(intracomm, openmc::mpi::rank); + MPI_Comm_size(intracomm, &openmc::mpi::n_procs); + MPI_Comm_rank(intracomm, &openmc::mpi::rank); // Set variable for Fortran side openmc_n_procs = openmc::mpi::n_procs; @@ -40,15 +51,18 @@ void initialize_mpi(MPI_Comm intracomm) // Create bank datatype Bank b; - MPI_Aint disp[5]; - disp[0] = &b.wgt - &b; - disp[1] = &b.xyz - &b; - disp[2] = &b.uvw - &b; - disp[3] = &b.E - &b; - disp[4] = &b.delayed_group - &b; + MPI_Aint disp[] { + offsetof(Bank, wgt), + offsetof(Bank, xyz), + offsetof(Bank, uvw), + offsetof(Bank, E), + offsetof(Bank, delayed_group) + }; int blocks[] {1, 3, 3, 1, 1}; MPI_Datatype types[] {MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE, MPI_INT}; MPI_Type_create_struct(5, blocks, disp, types, &openmc::mpi::bank); MPI_Type_commit(&openmc::mpi::bank); } -#endif + +#endif // OPENMC_MPI +} // namespace openmc diff --git a/src/initialize.h b/src/initialize.h new file mode 100644 index 000000000..430987038 --- /dev/null +++ b/src/initialize.h @@ -0,0 +1,14 @@ +#ifndef INITIALIZE_H +#define INITIALIZE_H + +#include "mpi.h" + +namespace openmc { + +#ifdef OPENMC_MPI + void initialize_mpi(MPI_Comm intracomm); +#endif + +} + +#endif // INITIALIZE_H diff --git a/src/main.cpp b/src/main.cpp index 2814b538f..1dd20ebe0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,9 @@ +#ifdef OPENMC_MPI +#include "mpi.h" +#endif #include "openmc.h" + int main(int argc, char** argv) { int err; diff --git a/src/message_passing.h b/src/message_passing.h index fea092c61..67353e23a 100644 --- a/src/message_passing.h +++ b/src/message_passing.h @@ -1,6 +1,8 @@ #ifndef MESSAGE_PASSING_H #define MESSAGE_PASSING_H +#include "mpi.h" + namespace openmc { namespace mpi { diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index 1b14e6e9f..e0631d16c 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -132,6 +132,8 @@ contains ! Add name to dictionary call already_read % add(name) + call close_group(xsdata_group) + end if end do NUCLIDE_LOOP end do MATERIAL_LOOP @@ -159,6 +161,8 @@ contains end do NUCLIDE_LOOP2 end do MATERIAL_LOOP3 + call file_close(file_id) + end subroutine read_mgxs !=============================================================================== diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index e599a89ff..76042703a 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -549,6 +549,8 @@ contains else call fatal_error("beta must be provided as a 1D or 2D array") end if + + call close_dataset(xsdata) else temp_beta = ZERO end if @@ -679,6 +681,8 @@ contains call fatal_error("nu-fission must be provided as a 1D or 2D & &array") end if + + call close_dataset(xsdata) end if ! If chi-prompt provided, set chi-prompt @@ -782,6 +786,8 @@ contains call fatal_error("chi-delayed must be provided as a 1D or 2D & &array") end if + + call close_dataset(xsdata) end if ! If prompt-nu-fission present, set prompt-nu-fission @@ -838,6 +844,8 @@ contains call fatal_error("prompt-nu-fission must be provided as a 1D & &or 2D array") end if + + call close_dataset(xsdata) end if ! If delayed-nu-fission provided, set delayed-nu-fission. If @@ -963,6 +971,8 @@ contains call fatal_error("delayed-nu-fission must be provided as a & &1D, 2D, or 3D array") end if + + call close_dataset(xsdata) end if ! Deallocate temporary beta array @@ -1346,6 +1356,8 @@ contains else call fatal_error("beta must be provided as a 3D or 4D array") end if + + call close_dataset(xsdata) else temp_beta = ZERO end if @@ -1517,6 +1529,8 @@ contains call fatal_error("nu-fission must be provided as a 3D or & &4D array") end if + + call close_dataset(xsdata) end if ! If chi-prompt provided, set chi-prompt @@ -1651,6 +1665,8 @@ contains call fatal_error("chi-delayed must be provided as a 3D or 4D & &array") end if + + call close_dataset(xsdata) end if ! If prompt-nu-fission present, set prompt-nu-fission @@ -1720,6 +1736,8 @@ contains call fatal_error("prompt-nu-fission must be provided as a 3D & &or 4D array") end if + + call close_dataset(xsdata) end if ! If delayed-nu-fission provided, set delayed-nu-fission. If @@ -1868,6 +1886,8 @@ contains call fatal_error("delayed-nu-fission must be provided as a & &3D, 4D, or 5D array") end if + + call close_dataset(xsdata) end if ! Deallocate temporary beta array diff --git a/src/source.F90 b/src/source.F90 index 59a117465..b02430823 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -63,7 +63,7 @@ contains end if ! Read in the source bank - call read_source_bank(file_id) + call read_source_bank(file_id, work_index, source_bank) ! Close file call file_close(file_id) diff --git a/src/state_point.F90 b/src/state_point.F90 index 181ab968b..5b1c4d462 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -13,8 +13,6 @@ module state_point use, intrinsic :: ISO_C_BINDING - use hdf5 - use bank_header, only: Bank use cmfd_header use constants @@ -45,6 +43,13 @@ module state_point integer(C_INT64_T), intent(in) :: work_index(*) type(Bank), intent(in) :: bank_(*) end subroutine write_source_bank + + subroutine read_source_bank(group_id, work_index, bank_) bind(C) + import HID_T, C_INT64_T, Bank + integer(HID_T), value :: group_id + integer(C_INT64_T), intent(in) :: work_index(*) + type(Bank), intent(out) :: bank_(*) + end subroutine read_source_bank end interface contains @@ -832,7 +837,7 @@ contains end if ! Write out source - call read_source_bank(file_id) + call read_source_bank(file_id, work_index, source_bank) end if @@ -841,66 +846,4 @@ contains end subroutine load_state_point -!=============================================================================== -! READ_SOURCE_BANK reads OpenMC source_bank data -!=============================================================================== - - subroutine read_source_bank(group_id) - integer(HID_T), intent(in) :: group_id - - integer :: hdf5_err - integer(HID_T) :: dset ! data set handle - integer(HID_T) :: dspace ! data space handle - integer(HID_T) :: memspace ! memory space handle - integer(HSIZE_T) :: dims(1) ! dimensions on one processor - integer(HSIZE_T) :: dims_all(1) ! overall dimensions - integer(HSIZE_T) :: maxdims(1) ! maximum dimensions - integer(HSIZE_T) :: offset(1) ! offset of data - type(c_ptr) :: f_ptr -#ifdef PHDF5 - integer(HID_T) :: plist ! property list -#endif - - ! Open the dataset - call h5dopen_f(group_id, "source_bank", dset, hdf5_err) - - ! Create another data space but for each proc individually - dims(1) = work - call h5screate_simple_f(1, dims, memspace, hdf5_err) - - ! Make sure source bank is big enough - call h5dget_space_f(dset, dspace, hdf5_err) - call h5sget_simple_extent_dims_f(dspace, dims_all, maxdims, hdf5_err) - if (size(source_bank, KIND=HSIZE_T) > dims_all(1)) then - call fatal_error("Number of source sites in source file is less than & - &number of source particles per generation.") - end if - - ! Select hyperslab for each process - offset(1) = work_index(rank) - call h5sselect_hyperslab_f(dspace, H5S_SELECT_SET_F, offset, dims, hdf5_err) - - ! Set up pointer to data - f_ptr = c_loc(source_bank) - -#ifdef PHDF5 - ! Read data in parallel - call h5pcreate_f(H5P_DATASET_XFER_F, plist, hdf5_err) - call h5pset_dxpl_mpio_f(plist, H5FD_MPIO_COLLECTIVE_F, hdf5_err) - call h5dread_f(dset, hdf5_bank_t, f_ptr, hdf5_err, & - file_space_id=dspace, mem_space_id=memspace, & - xfer_prp=plist) - call h5pclose_f(plist, hdf5_err) -#else - call h5dread_f(dset, hdf5_bank_t, f_ptr, hdf5_err, & - file_space_id=dspace, mem_space_id=memspace) -#endif - - ! Close all ids - call h5sclose_f(dspace, hdf5_err) - call h5sclose_f(memspace, hdf5_err) - call h5dclose_f(dset, hdf5_err) - - end subroutine read_source_bank - end module state_point diff --git a/src/state_point.cpp b/src/state_point.cpp index 20338c4be..cc6aa2a41 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -4,14 +4,14 @@ #include #include "mpi.h" +#include "error.h" #include "message_passing.h" #include "openmc.h" namespace openmc { -void -write_source_bank(hid_t group_id, int64_t* work_index, const Bank* source_bank) -{ + +hid_t h5banktype() { // Create type for array of 3 reals hsize_t dims[] {3}; hid_t triplet = H5Tarray_create(H5T_NATIVE_DOUBLE, 1, dims); @@ -24,31 +24,41 @@ write_source_bank(hid_t group_id, int64_t* work_index, const Bank* source_bank) H5Tinsert(banktype, "E", HOFFSET(Bank, E), H5T_NATIVE_DOUBLE); H5Tinsert(banktype, "delayed_group", HOFFSET(Bank, delayed_group), H5T_NATIVE_INT); + H5Tclose(triplet); + return banktype; +} + + +void +write_source_bank(hid_t group_id, int64_t* work_index, const Bank* source_bank) +{ + hid_t banktype = h5banktype(); + #ifdef PHDF5 // Set size of total dataspace for all procs and rank - dims[0] = n_particles; + hsize_t dims[] {static_cast(n_particles)}; hid_t dspace = H5Screate_simple(1, dims, H5P_DEFAULT); hid_t dset = H5Dcreate(group_id, "source_bank", banktype, dspace, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); // Create another data space but for each proc individually - hsize_t count[] {openmc_work}; + hsize_t count[] {static_cast(openmc_work)}; hid_t memspace = H5Screate_simple(1, count, H5P_DEFAULT); // Select hyperslab for this dataspace - hsize_t start[] {work_index[openmc::mpi::rank]}; + hsize_t start[] {static_cast(work_index[openmc::mpi::rank])}; H5Sselect_hyperslab(dspace, H5S_SELECT_SET, start, nullptr, count, nullptr); // Set up the property list for parallel writing hid_t plist = H5Pcreate(H5P_DATASET_XFER); - H5Pset_dxpl_mpio_f(plist, H5FD_MPIO_COLLECTIVE); + H5Pset_dxpl_mpio(plist, H5FD_MPIO_COLLECTIVE); // Write data to file in parallel - H5Dwrite(dset, banktype, memspace, dspace, memspace, plist, source_bank); + H5Dwrite(dset, banktype, memspace, dspace, plist, source_bank); // Free resources H5Sclose(dspace); - h5sclose(memspace); + H5Sclose(memspace); H5Dclose(dset); H5Pclose(plist); @@ -106,7 +116,48 @@ write_source_bank(hid_t group_id, int64_t* work_index, const Bank* source_bank) #endif H5Tclose(banktype); - H5Tclose(triplet); +} + + +void read_source_bank(hid_t group_id, int64_t* work_index, struct Bank* source_bank) +{ + hid_t banktype = h5banktype(); + + // Open the dataset + hid_t dset = H5Dopen(group_id, "source_bank", H5P_DEFAULT); + + // Create another data space but for each proc individually + hsize_t dims[] {static_cast(openmc_work)}; + hid_t memspace = H5Screate_simple(1, dims, H5P_DEFAULT); + + // Make sure source bank is big enough + hid_t dspace = H5Dget_space(dset); + hsize_t dims_all[1]; + H5Sget_simple_extent_dims(dspace, dims_all, nullptr); + if (work_index[openmc::mpi::n_procs] > dims_all[0]) { + fatal_error("Number of source sites in source file is less " + "than number of source particles per generation."); + } + + // Select hyperslab for each process + hsize_t start[] {static_cast(work_index[openmc::mpi::rank])}; + H5Sselect_hyperslab(dspace, H5S_SELECT_SET, start, nullptr, dims, nullptr); + +#ifdef PHDF5 + // Read data in parallel + hid_t plist = H5Pcreate(H5P_DATASET_XFER); + H5Pset_dxpl_mpio(plist, H5FD_MPIO_COLLECTIVE); + H5Dread(dset, banktype, memspace, dspace, plist, source_bank); + H5Pclose(plist); +#else + H5Dread(dset, banktype, memspace, dspace, H5P_DEFAULT, source_bank); +#endif + + // Close all ids + H5Sclose(dspace); + H5Sclose(memspace); + H5Dclose(dset); + H5Tclose(banktype); } } // namespace openmc diff --git a/src/state_point.h b/src/state_point.h index f0dafdb80..34186d0d9 100644 --- a/src/state_point.h +++ b/src/state_point.h @@ -9,7 +9,9 @@ namespace openmc { extern "C" void write_source_bank(hid_t group_id, int64_t* work_index, - const struct Bank* bank); + const struct Bank* source_bank); +extern "C" void read_source_bank(hid_t group_id, int64_t* work_index, + struct Bank* source_bank); } // namespace openmc #endif // STATE_POINT_H From 73b50e1e173ffca3a528774923627052149e7d5f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 20 Apr 2018 14:02:48 -0500 Subject: [PATCH 210/361] Make sure statepoint context manager closes linked summary --- openmc/statepoint.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openmc/statepoint.py b/openmc/statepoint.py index eb011d874..3b878e9a3 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -150,6 +150,8 @@ class StatePoint(object): def __exit__(self, *exc): self._f.close() + if self._summary is not None: + self._summary._f.close() @property def cmfd_on(self): From d8c30b7f6211bf613bf81eb98052e0b78d06de6f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 20 Apr 2018 14:07:14 -0500 Subject: [PATCH 211/361] Get rid of most of hdf5_initialize (no longer needed) --- src/api.F90 | 5 +---- src/hdf5_interface.F90 | 3 --- src/initialize.F90 | 44 ++++-------------------------------------- 3 files changed, 5 insertions(+), 47 deletions(-) diff --git a/src/api.F90 b/src/api.F90 index 03ea9bff0..32077c565 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -2,7 +2,7 @@ module openmc_api use, intrinsic :: ISO_C_BINDING - use hdf5, only: HID_T, h5tclose_f, h5close_f + use hdf5, only: h5close_f use bank_header, only: openmc_source_bank use constants, only: K_BOLTZMANN @@ -169,9 +169,6 @@ contains ! Deallocate arrays call free_memory() - ! Release compound datatypes - call h5tclose_f(hdf5_bank_t, err) - ! Close FORTRAN interface. call h5close_f(err) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index d76f7b6b7..70b624b4f 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -24,9 +24,6 @@ module hdf5_interface implicit none private - integer(HID_T), public :: hdf5_bank_t ! Compound type for Bank - integer(HID_T), public :: hdf5_integer8_t ! type for integer(8) - interface write_dataset module procedure write_double_0D module procedure write_double_1D diff --git a/src/initialize.F90 b/src/initialize.F90 index 7b8f37dee..7a43c9381 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -13,8 +13,7 @@ module initialize 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, & - hdf5_bank_t, hdf5_integer8_t + use hdf5_interface, only: file_open, read_attribute, file_close use input_xml, only: read_input_xml use material_header, only: Material use message_passing @@ -45,6 +44,7 @@ contains integer, intent(in), optional :: intracomm ! MPI intracommunicator integer(C_INT) :: err + integer :: hdf5_err #ifdef _OPENMP character(MAX_WORD_LEN) :: envvar #endif @@ -87,8 +87,8 @@ contains end if #endif - ! Initialize HDF5 interface - call hdf5_initialize() + ! Initialize HDF5 Fortran interface. + call h5open_f(hdf5_err) ! Read command line arguments call read_command_line() @@ -160,42 +160,6 @@ contains end subroutine initialize_mpi #endif -!=============================================================================== -! HDF5_INITIALIZE -!=============================================================================== - - subroutine hdf5_initialize() - - type(Bank), target :: tmpb(2) ! temporary Bank - integer :: hdf5_err - integer(HID_T) :: coordinates_t ! HDF5 type for 3 reals - integer(HSIZE_T) :: dims(1) = (/3/) ! size of coordinates - - ! Initialize FORTRAN interface. - call h5open_f(hdf5_err) - - ! Create compound type for xyz and uvw - call h5tarray_create_f(H5T_NATIVE_DOUBLE, 1, dims, coordinates_t, hdf5_err) - - ! Create the compound datatype for Bank - call h5tcreate_f(H5T_COMPOUND_F, h5offsetof(c_loc(tmpb(1)), & - c_loc(tmpb(2))), hdf5_bank_t, hdf5_err) - call h5tinsert_f(hdf5_bank_t, "wgt", h5offsetof(c_loc(tmpb(1)), & - c_loc(tmpb(1)%wgt)), H5T_NATIVE_DOUBLE, hdf5_err) - call h5tinsert_f(hdf5_bank_t, "xyz", h5offsetof(c_loc(tmpb(1)), & - c_loc(tmpb(1)%xyz)), coordinates_t, hdf5_err) - call h5tinsert_f(hdf5_bank_t, "uvw", h5offsetof(c_loc(tmpb(1)), & - c_loc(tmpb(1)%uvw)), coordinates_t, hdf5_err) - call h5tinsert_f(hdf5_bank_t, "E", h5offsetof(c_loc(tmpb(1)), & - c_loc(tmpb(1)%E)), H5T_NATIVE_DOUBLE, hdf5_err) - call h5tinsert_f(hdf5_bank_t, "delayed_group", h5offsetof(c_loc(tmpb(1)), & - c_loc(tmpb(1)%delayed_group)), H5T_NATIVE_INTEGER, hdf5_err) - - ! Determine type for integer(8) - hdf5_integer8_t = h5kind_to_type(8, H5_INTEGER_KIND) - - end subroutine hdf5_initialize - !=============================================================================== ! READ_COMMAND_LINE reads all parameters from the command line !=============================================================================== From 3290c410a8df12e76c7852644dda4e73be7907f6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 20 Apr 2018 14:55:06 -0500 Subject: [PATCH 212/361] Fix bugs for MPI/non-PHDF5 runs --- src/initialize.h | 2 ++ src/message_passing.h | 2 ++ src/state_point.cpp | 10 ++++++---- src/state_point.h | 4 ++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/initialize.h b/src/initialize.h index 430987038..d2c4df5d7 100644 --- a/src/initialize.h +++ b/src/initialize.h @@ -1,7 +1,9 @@ #ifndef INITIALIZE_H #define INITIALIZE_H +#ifdef OPENMC_MPI #include "mpi.h" +#endif namespace openmc { diff --git a/src/message_passing.h b/src/message_passing.h index 67353e23a..14cf3a7cb 100644 --- a/src/message_passing.h +++ b/src/message_passing.h @@ -1,7 +1,9 @@ #ifndef MESSAGE_PASSING_H #define MESSAGE_PASSING_H +#ifdef OPENMC_MPI #include "mpi.h" +#endif namespace openmc { namespace mpi { diff --git a/src/state_point.cpp b/src/state_point.cpp index cc6aa2a41..2ea9d1c43 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -3,7 +3,9 @@ #include #include +#ifdef OPENMC_MPI #include "mpi.h" +#endif #include "error.h" #include "message_passing.h" #include "openmc.h" @@ -30,7 +32,7 @@ hid_t h5banktype() { void -write_source_bank(hid_t group_id, int64_t* work_index, const Bank* source_bank) +write_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) { hid_t banktype = h5banktype(); @@ -73,7 +75,7 @@ write_source_bank(hid_t group_id, int64_t* work_index, const Bank* source_bank) // Save source bank sites since the souce_bank array is overwritten below #ifdef OPENMC_MPI - std::vector temp_source {source_bank, source_bank + openmc_work}; + std::vector temp_source {source_bank, source_bank + openmc_work}; #endif for (int i = 0; i < openmc::mpi::n_procs; ++i) { @@ -110,7 +112,7 @@ write_source_bank(hid_t group_id, int64_t* work_index, const Bank* source_bank) } else { #ifdef OPENMC_MPI MPI_Send(source_bank, openmc_work, openmc::mpi::bank, 0, openmc::mpi::rank, - openmc::mpi::mpi_intracomm); + openmc::mpi::intracomm); #endif } #endif @@ -119,7 +121,7 @@ write_source_bank(hid_t group_id, int64_t* work_index, const Bank* source_bank) } -void read_source_bank(hid_t group_id, int64_t* work_index, struct Bank* source_bank) +void read_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) { hid_t banktype = h5banktype(); diff --git a/src/state_point.h b/src/state_point.h index 34186d0d9..459df1a67 100644 --- a/src/state_point.h +++ b/src/state_point.h @@ -9,9 +9,9 @@ namespace openmc { extern "C" void write_source_bank(hid_t group_id, int64_t* work_index, - const struct Bank* source_bank); + Bank* source_bank); extern "C" void read_source_bank(hid_t group_id, int64_t* work_index, - struct Bank* source_bank); + Bank* source_bank); } // namespace openmc #endif // STATE_POINT_H From 70173aa25c1de99b8e5127003a1e1fcb7007099d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 24 Apr 2018 06:42:43 -0500 Subject: [PATCH 213/361] Install openmc.h header properly --- CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b750a6dd..ed9021cf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -443,7 +443,9 @@ set(LIBOPENMC_CXX_SRC src/xml_interface.cpp src/pugixml/pugixml.cpp) add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC} ${LIBOPENMC_CXX_SRC}) -set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc) +set_target_properties(libopenmc PROPERTIES + OUTPUT_NAME openmc + PUBLIC_HEADER include/openmc.h) add_executable(${program} src/main.cpp) #=============================================================================== @@ -501,7 +503,9 @@ add_custom_command(TARGET libopenmc POST_BUILD install(TARGETS ${program} libopenmc RUNTIME DESTINATION bin LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib) + ARCHIVE DESTINATION lib + PUBLIC_HEADER DESTINATION include + ) install(DIRECTORY src/relaxng DESTINATION share/openmc) install(FILES man/man1/openmc.1 DESTINATION share/man/man1) install(FILES LICENSE DESTINATION "share/doc/${program}" RENAME copyright) From 1a77f8afee8f671cf51a4b6f81681f2c66845ba5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 24 Apr 2018 15:25:58 -0500 Subject: [PATCH 214/361] Add C++ routines for HDF5 get_groups, get_datasets, get_name --- src/hdf5_interface.F90 | 123 ++++++++++++++++++---------------- src/hdf5_interface.cpp | 147 +++++++++++++++++++++++++++++++++++------ src/hdf5_interface.h | 9 ++- 3 files changed, 201 insertions(+), 78 deletions(-) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 70b624b4f..077807c04 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -13,13 +13,12 @@ module hdf5_interface use, intrinsic :: ISO_C_BINDING use hdf5 - use h5lt use error, only: fatal_error #ifdef PHDF5 use message_passing, only: mpi_intracomm, MPI_INFO_NULL #endif - use string, only: to_c_string + use string, only: to_c_string, to_f_string implicit none private @@ -316,31 +315,37 @@ contains integer(HID_T), intent(in) :: object_id character(len=150), allocatable, intent(out) :: names(:) - integer :: n_members, i, group_count, type - integer :: hdf5_err - character(len=150) :: name + integer :: i + integer(C_INT) :: n + character(len=150,kind=C_CHAR), target, allocatable :: names_(:) + type(C_PTR), allocatable :: name_ptrs(:) - ! Get number of members in this location - call h5gn_members_f(object_id, './', n_members, hdf5_err) + interface + function get_num_groups(group_id) result(n) bind(C) + import HID_T, C_INT + integer(HID_T), value :: group_id + integer(C_INT) :: n + end function get_num_groups + subroutine get_groups_c(group_id, name) bind(C, name='get_groups') + import HID_T, C_PTR + integer(HID_T), value :: group_id + type(C_PTR) :: name(*) + end subroutine get_groups_c + end interface - ! Get the number of groups - group_count = 0 - do i = 0, n_members - 1 - call h5gget_obj_info_idx_f(object_id, "./", i, name, type, hdf5_err) - if (type == H5G_GROUP_F) then - group_count = group_count + 1 - end if + ! Determine number of groups and allocate + n = get_num_groups(object_id) + allocate(names(n), names_(n), name_ptrs(n)) + + ! Set C pointers to beginning of each string + do i = 1, size(names) + name_ptrs(i) = c_loc(names_(i)) end do - ! Now we can allocate the storage for the ids - allocate(names(group_count)) - group_count = 0 - do i = 0, n_members - 1 - call h5gget_obj_info_idx_f(object_id, "./", i, name, type, hdf5_err) - if (type == H5G_GROUP_F) then - group_count = group_count + 1 - names(group_count) = trim(name) - end if + ! Get names of groups and copy to Fortran strings + call get_groups_c(object_id, name_ptrs) + do i = 1, size(names) + names(i) = to_f_string(names_(i)) end do end subroutine get_groups @@ -398,32 +403,37 @@ contains integer(HID_T), intent(in) :: object_id character(len=150), allocatable, intent(out) :: names(:) - integer :: n_members, i, dset_count, type - integer :: hdf5_err - character(len=150) :: name + integer :: i + integer(C_INT) :: n + character(len=150,kind=C_CHAR), target, allocatable :: names_(:) + type(C_PTR), allocatable :: name_ptrs(:) + interface + function get_num_datasets(group_id) result(n) bind(C) + import HID_T, C_INT + integer(HID_T), value :: group_id + integer(C_INT) :: n + end function get_num_datasets + subroutine get_datasets_c(group_id, name) bind(C, name='get_datasets') + import HID_T, C_PTR + integer(HID_T), value :: group_id + type(C_PTR) :: name(*) + end subroutine get_datasets_c + end interface - ! Get number of members in this location - call h5gn_members_f(object_id, './', n_members, hdf5_err) + ! Determine number of datasets and allocate + n = get_num_datasets(object_id) + allocate(names(n), names_(n), name_ptrs(n)) - ! Get the number of datasets - dset_count = 0 - do i = 0, n_members - 1 - call h5gget_obj_info_idx_f(object_id, "./", i, name, type, hdf5_err) - if (type == H5G_DATASET_F ) then - dset_count = dset_count + 1 - end if + ! Set C pointers to beginning of each string + do i = 1, size(names) + name_ptrs(i) = c_loc(names_(i)) end do - ! Now we can allocate the storage for the ids - allocate(names(dset_count)) - dset_count = 0 - do i = 0, n_members - 1 - call h5gget_obj_info_idx_f(object_id, "./", i, name, type, hdf5_err) - if (type == H5G_DATASET_F ) then - dset_count = dset_count + 1 - names(dset_count) = trim(name) - end if + ! Get names of datasets and copy to Fortran strings + call get_datasets_c(object_id, name_ptrs) + do i = 1, size(names) + names(i) = to_f_string(names_(i)) end do end subroutine get_datasets @@ -432,21 +442,22 @@ contains ! GET_NAME Obtains the name of the current group in group_id !=============================================================================== - function get_name(group_id, name_len_) result(name) - integer(HID_T), intent(in) :: group_id + function get_name(object_id, name_len_) result(name) + integer(HID_T), intent(in) :: object_id integer(SIZE_T), optional, intent(in) :: name_len_ - character(len=150) :: name ! name of group - integer(SIZE_T) :: name_len, name_file_len - integer :: hdf5_err ! HDF5 error code + character(150) :: name ! name of object + character(kind=C_CHAR) :: name_(150) + interface + subroutine get_name_c(obj_id, name) bind(C, name='get_name') + import HID_T, C_CHAR + integer(HID_T), value :: obj_id + character(kind=C_CHAR), intent(out) :: name(*) + end subroutine get_name_c + end interface - if (present(name_len_)) then - name_len = name_len_ - else - name_len = 150 - end if - - call h5iget_name_f(group_id, name, name_len, name_file_len, hdf5_err) + call get_name_c(object_id, name_) + name = to_f_string(name_) end function get_name !=============================================================================== diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index d81c5b34a..e4db2ee58 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -36,26 +36,6 @@ attribute_typesize(hid_t obj_id, const char* name) } -bool -using_mpio_device(hid_t obj_id) -{ - // Determine file that this object is part of - hid_t file_id = H5Iget_file_id(obj_id); - - // Get file access property list - hid_t fapl_id = H5Fget_access_plist(file_id); - - // Get low-level driver identifier - hid_t driver = H5Pget_driver(fapl_id); - - // Free resources - H5Pclose(fapl_id); - H5Fclose(file_id); - - return driver == H5FD_MPIO; -} - - void get_shape(hid_t obj_id, hsize_t* dims) { @@ -199,6 +179,113 @@ void file_close(hid_t file_id) H5Fclose(file_id); } + +void +get_name(hid_t obj_id, char* name) +{ + size_t size = 1 + H5Iget_name(obj_id, nullptr, 0); + H5Iget_name(obj_id, name, size); +} + + +int get_num_datasets(hid_t group_id) +{ + // Determine number of links in the group + H5G_info_t info; + H5Gget_info(group_id, &info); + + // Iterate over links to get number of groups + H5O_info_t oinfo; + int ndatasets = 0; + for (hsize_t i = 0; i < info.nlinks; ++i) { + // Determine type of object (and skip non-group) + H5Oget_info_by_idx(group_id, ".", H5_INDEX_NAME, H5_ITER_INC, i, &oinfo, + H5P_DEFAULT); + if (oinfo.type == H5O_TYPE_DATASET) ndatasets += 1; + } + + return ndatasets; +} + + +int get_num_groups(hid_t group_id) +{ + // Determine number of links in the group + H5G_info_t info; + H5Gget_info(group_id, &info); + + // Iterate over links to get number of groups + H5O_info_t oinfo; + int ngroups = 0; + for (hsize_t i = 0; i < info.nlinks; ++i) { + // Determine type of object (and skip non-group) + H5Oget_info_by_idx(group_id, ".", H5_INDEX_NAME, H5_ITER_INC, i, &oinfo, + H5P_DEFAULT); + if (oinfo.type == H5O_TYPE_GROUP) ngroups += 1; + } + + return ngroups; +} + + +void +get_datasets(hid_t group_id, char* name[]) +{ + // Determine number of links in the group + H5G_info_t info; + H5Gget_info(group_id, &info); + + // Iterate over links to get names + H5O_info_t oinfo; + hsize_t count = 0; + size_t size; + for (hsize_t i = 0; i < info.nlinks; ++i) { + // Determine type of object (and skip non-group) + H5Oget_info_by_idx(group_id, ".", H5_INDEX_NAME, H5_ITER_INC, i, &oinfo, + H5P_DEFAULT); + if (oinfo.type != H5O_TYPE_DATASET) continue; + + // Get size of name + size = 1 + H5Lget_name_by_idx(group_id, ".", H5_INDEX_NAME, H5_ITER_INC, + i, nullptr, 0, H5P_DEFAULT); + + // Read name + H5Lget_name_by_idx(group_id, ".", H5_INDEX_NAME, H5_ITER_INC, i, + name[count], size, H5P_DEFAULT); + count += 1; + } +} + + +void +get_groups(hid_t group_id, char* name[]) +{ + // Determine number of links in the group + H5G_info_t info; + H5Gget_info(group_id, &info); + + // Iterate over links to get names + H5O_info_t oinfo; + hsize_t count = 0; + size_t size; + for (hsize_t i = 0; i < info.nlinks; ++i) { + // Determine type of object (and skip non-group) + H5Oget_info_by_idx(group_id, ".", H5_INDEX_NAME, H5_ITER_INC, i, &oinfo, + H5P_DEFAULT); + if (oinfo.type != H5O_TYPE_GROUP) continue; + + // Get size of name + size = 1 + H5Lget_name_by_idx(group_id, ".", H5_INDEX_NAME, H5_ITER_INC, + i, nullptr, 0, H5P_DEFAULT); + + // Read name + H5Lget_name_by_idx(group_id, ".", H5_INDEX_NAME, H5_ITER_INC, i, + name[count], size, H5P_DEFAULT); + count += 1; + } +} + + bool object_exists(hid_t object_id, const char* name) { @@ -548,4 +635,24 @@ write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, const dou H5Sclose(dspace); } + +bool +using_mpio_device(hid_t obj_id) +{ + // Determine file that this object is part of + hid_t file_id = H5Iget_file_id(obj_id); + + // Get file access property list + hid_t fapl_id = H5Fget_access_plist(file_id); + + // Get low-level driver identifier + hid_t driver = H5Pget_driver(fapl_id); + + // Free resources + H5Pclose(fapl_id); + H5Fclose(file_id); + + return driver == H5FD_MPIO; +} + } // namespace openmc diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 57f56f5c2..9a8b3c569 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -23,8 +23,13 @@ extern "C" size_t dataset_typesize(hid_t dset); extern "C" hid_t file_open(const char* filename, char mode, bool parallel); hid_t file_open(const std::string& filename, char mode, bool parallel); extern "C" void file_close(hid_t file_id); -extern "C" void get_shape(hid_t obj_if, hsize_t* dims); -extern "C" void get_shape_attr(hid_t obj_if, const char* name, hsize_t* dims); +extern "C" void get_name(hid_t obj_id, char* name); +extern "C" int get_num_datasets(hid_t group_id); +extern "C" int get_num_groups(hid_t group_id); +extern "C" void get_datasets(hid_t group_id, char* name[]); +extern "C" void get_groups(hid_t group_id, char* name[]); +extern "C" void get_shape(hid_t obj_id, hsize_t* dims); +extern "C" void get_shape_attr(hid_t obj_id, const char* name, hsize_t* dims); extern "C" bool object_exists(hid_t object_id, const char* name); extern "C" hid_t open_dataset(hid_t group_id, const char* name); extern "C" hid_t open_group(hid_t group_id, const char* name); From 279562c32afed79f4b45eefdd69b3d67a8dc372d Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 24 Apr 2018 15:45:21 -0500 Subject: [PATCH 215/361] Make sure rank/nprocs is set for serial runs --- src/message_passing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/message_passing.cpp b/src/message_passing.cpp index 3ab96ffd8..2ff3952d7 100644 --- a/src/message_passing.cpp +++ b/src/message_passing.cpp @@ -3,8 +3,8 @@ namespace openmc { namespace mpi { -int rank; -int n_procs; +int rank {0}; +int n_procs {1}; #ifdef OPENMC_MPI MPI_Comm intracomm; From 3acec3c093bf0bb4c90b6de1963ad4e4d1d61d46 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 24 Apr 2018 22:42:04 -0500 Subject: [PATCH 216/361] Call HDF5 routines from C++ side for voxel plots --- CMakeLists.txt | 1 + src/plot.F90 | 62 +++++++++++++++++++++++++++----------------------- src/plot.cpp | 42 ++++++++++++++++++++++++++++++++++ src/plot.h | 15 ++++++++++++ 4 files changed, 91 insertions(+), 29 deletions(-) create mode 100644 src/plot.cpp create mode 100644 src/plot.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ed9021cf4..5c6636cbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -436,6 +436,7 @@ set(LIBOPENMC_CXX_SRC src/initialize.cpp src/hdf5_interface.cpp src/message_passing.cpp + src/plot.cpp src/random_lcg.cpp src/simulation.cpp src/state_point.cpp diff --git a/src/plot.F90 b/src/plot.F90 index 92b8f350d..c2ffa2d39 100644 --- a/src/plot.F90 +++ b/src/plot.F90 @@ -2,8 +2,6 @@ module plot use, intrinsic :: ISO_C_BINDING - use hdf5 - use constants use error, only: fatal_error, write_message use geometry, only: find_cell, check_cell_overlap @@ -340,23 +338,46 @@ contains subroutine create_voxel(pl) type(ObjectPlot), intent(in) :: pl - integer :: x, y, z ! voxel location indices + integer(C_INT) :: x, y, z ! voxel location indices integer :: rgb(3) ! colors (red, green, blue) from 0-255 integer :: id ! id of cell or material - integer :: hdf5_err - integer, target :: data(pl%pixels(3),pl%pixels(2)) + integer(C_INT), target :: data(pl%pixels(3),pl%pixels(2)) integer(HID_T) :: file_id integer(HID_T) :: dspace integer(HID_T) :: memspace integer(HID_T) :: dset integer(HSIZE_T) :: dims(3) - integer(HSIZE_T) :: dims_slab(3) - integer(HSIZE_T) :: offset(3) real(8) :: vox(3) ! x, y, and z voxel widths real(8) :: ll(3) ! lower left starting point for each sweep direction type(Particle) :: p type(ProgressBar) :: progress - type(c_ptr) :: f_ptr + + interface + subroutine voxel_init(file_id, dims, dspace, dset, memspace) bind(C) + import HID_T, HSIZE_T + integer(HID_T), value :: file_id + integer(HSIZE_T), intent(in) :: dims(*) + integer(HID_T), intent(out) :: dspace + integer(HID_T), intent(out) :: dset + integer(HID_T), intent(out) :: memspace + end subroutine voxel_init + + subroutine voxel_write_slice(x, dspace, dset, memspace, buf) bind(C) + import C_INT, HID_T, C_PTR + integer(C_INT), value :: x + integer(HID_T), value :: dspace + integer(HID_T), value :: dset + integer(HID_T), value :: memspace + type(C_PTR), value :: buf + end subroutine voxel_write_slice + + subroutine voxel_finalize(dspace, dset, memspace) bind(C) + import HID_T + integer(HID_T), value :: dspace + integer(HID_T), value :: dset + integer(HID_T), value :: memspace + end subroutine voxel_finalize + end interface ! compute voxel widths in each direction vox = pl % width/dble(pl % pixels) @@ -390,20 +411,8 @@ contains ! Create dataset for voxel data -- note that the dimensions are reversed ! since we want the order in the file to be z, y, x - dims(:) = [pl%pixels(3), pl%pixels(2), pl%pixels(1)] - call h5screate_simple_f(3, dims, dspace, hdf5_err) - call h5dcreate_f(file_id, "data", H5T_NATIVE_INTEGER, dspace, dset, hdf5_err) - - ! Create another dataspace for 2D array in memory - dims_slab(1) = pl%pixels(3) - dims_slab(2) = pl%pixels(2) - dims_slab(3) = 1 - call h5screate_simple_f(2, dims_slab(1:2), memspace, hdf5_err) - - ! Initialize offset and get pointer to data - offset(:) = 0 - call h5sselect_hyperslab_f(dspace, H5S_SELECT_SET_F, offset, dims_slab, hdf5_err) - f_ptr = c_loc(data) + dims(:) = pl % pixels + call voxel_init(file_id, dims, dspace, dset, memspace) ! move to center of voxels ll = ll + vox / TWO @@ -433,15 +442,10 @@ contains p % coord(1) % xyz(3) = ll(3) ! Write to HDF5 dataset - offset(3) = x - 1 - call h5soffset_simple_f(dspace, offset, hdf5_err) - call h5dwrite_f(dset, H5T_NATIVE_INTEGER, f_ptr, hdf5_err, & - mem_space_id=memspace, file_space_id=dspace) + call voxel_write_slice(x, dspace, dset, memspace, c_loc(data)) end do - call h5dclose_f(dset, hdf5_err) - call h5sclose_f(dspace, hdf5_err) - call h5sclose_f(memspace, hdf5_err) + call voxel_finalize(dspace, dset, memspace) call file_close(file_id) end subroutine create_voxel diff --git a/src/plot.cpp b/src/plot.cpp new file mode 100644 index 000000000..0dca6d08c --- /dev/null +++ b/src/plot.cpp @@ -0,0 +1,42 @@ +#include "plot.h" + +namespace openmc { + +void +voxel_init(hid_t file_id, const hsize_t* dims, hid_t* dspace, hid_t* dset, + hid_t* memspace) +{ + // Create dataspace/dataset for voxel data + *dspace = H5Screate_simple(3, dims, nullptr); + *dset = H5Dcreate(file_id, "data", H5T_NATIVE_INT, *dspace, H5P_DEFAULT, + H5P_DEFAULT, H5P_DEFAULT); + + // Create dataspace for a slice of the voxel + hsize_t dims_slice[2] {dims[1], dims[2]}; + *memspace = H5Screate_simple(2, dims_slice, nullptr); + + // Select hyperslab in dataspace + hsize_t start[3] {0, 0, 0}; + hsize_t count[3] {1, dims[1], dims[2]}; + H5Sselect_hyperslab(*dspace, H5S_SELECT_SET, start, nullptr, count, nullptr); +} + + +void +voxel_write_slice(int x, hid_t dspace, hid_t dset, hid_t memspace, void* buf) +{ + hssize_t offset[3] {x - 1, 0, 0}; + H5Soffset_simple(dspace, offset); + H5Dwrite(dset, H5T_NATIVE_INT, memspace, dspace, H5P_DEFAULT, buf); +} + + +void +voxel_finalize(hid_t dspace, hid_t dset, hid_t memspace) +{ + H5Dclose(dset); + H5Sclose(dspace); + H5Sclose(memspace); +} + +} // namespace openmc diff --git a/src/plot.h b/src/plot.h new file mode 100644 index 000000000..75406b9ac --- /dev/null +++ b/src/plot.h @@ -0,0 +1,15 @@ +#ifndef PLOT_H +#define PLOT_H + +#include "hdf5.h" + +namespace openmc { + +extern "C" void voxel_init(hid_t file_id, const hsize_t* dims, hid_t* dspace, + hid_t* dset, hid_t* memspace); +extern "C" void voxel_write_slice(int x, hid_t dspace, hid_t dset, + hid_t memspace, void* buf); +extern "C" void voxel_finalize(hid_t dspace, hid_t dset, hid_t memspace); + +} // namespace openmc +#endif // PLOT_H From 36dc4a59b14d6d3b0450d53d434b73d5adf64ea2 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 24 Apr 2018 23:03:21 -0500 Subject: [PATCH 217/361] Remove remaining dependencies on Fortran HDF5 interface --- CMakeLists.txt | 2 +- src/api.F90 | 5 ----- src/hdf5_interface.F90 | 11 +++++------ src/initialize.F90 | 6 +----- src/mgxs_header.F90 | 4 +--- src/nuclide_header.F90 | 4 +--- src/sab_header.F90 | 4 +--- 7 files changed, 10 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c6636cbc..ed72434d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,7 +70,7 @@ if(NOT DEFINED HDF5_PREFER_PARALLEL) endif() endif() -find_package(HDF5 COMPONENTS Fortran_HL) +find_package(HDF5 COMPONENTS HL) if(NOT HDF5_FOUND) message(FATAL_ERROR "Could not find HDF5") endif() diff --git a/src/api.F90 b/src/api.F90 index 32077c565..d00d166c4 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -2,8 +2,6 @@ module openmc_api use, intrinsic :: ISO_C_BINDING - use hdf5, only: h5close_f - use bank_header, only: openmc_source_bank use constants, only: K_BOLTZMANN use eigenvalue, only: k_sum, openmc_get_keff @@ -169,9 +167,6 @@ contains ! Deallocate arrays call free_memory() - ! Close FORTRAN interface. - call h5close_f(err) - #ifdef OPENMC_MPI ! Free all MPI types call MPI_TYPE_FREE(MPI_BANK, err) diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 077807c04..57138c377 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -12,8 +12,6 @@ module hdf5_interface use, intrinsic :: ISO_C_BINDING - use hdf5 - use error, only: fatal_error #ifdef PHDF5 use message_passing, only: mpi_intracomm, MPI_INFO_NULL @@ -94,7 +92,9 @@ module hdf5_interface public :: get_groups public :: get_datasets public :: get_name - public :: HID_T, HSIZE_T, SIZE_T + + integer, public, parameter :: HID_T = C_INT64_T + integer, public, parameter :: HSIZE_T = C_LONG_LONG interface function attribute_typesize(obj_id, name) result(sz) bind(C) @@ -442,9 +442,8 @@ contains ! GET_NAME Obtains the name of the current group in group_id !=============================================================================== - function get_name(object_id, name_len_) result(name) - integer(HID_T), intent(in) :: object_id - integer(SIZE_T), optional, intent(in) :: name_len_ + function get_name(object_id) result(name) + integer(HID_T), intent(in) :: object_id character(150) :: name ! name of object character(kind=C_CHAR) :: name_(150) diff --git a/src/initialize.F90 b/src/initialize.F90 index 7a43c9381..8d136cf24 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -2,7 +2,6 @@ module initialize use, intrinsic :: ISO_C_BINDING, only: c_loc - use hdf5 #ifdef _OPENMP use omp_lib #endif @@ -13,7 +12,7 @@ module initialize 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 + use hdf5_interface, only: file_open, read_attribute, file_close, HID_T use input_xml, only: read_input_xml use material_header, only: Material use message_passing @@ -87,9 +86,6 @@ contains end if #endif - ! Initialize HDF5 Fortran interface. - call h5open_f(hdf5_err) - ! Read command line arguments call read_command_line() diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index 76042703a..3bb74d5f7 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -246,7 +246,6 @@ contains type(VectorInt), intent(out) :: temps_to_read ! Temperatures to read integer, intent(out) :: order_dim ! Scattering data order size - integer(SIZE_T) :: name_len integer(HID_T) :: kT_group character(MAX_WORD_LEN), allocatable :: dset_names(:) real(8), allocatable :: temps_available(:) ! temperatures available @@ -257,8 +256,7 @@ contains integer :: ipol, iazi ! Get name of dataset from group - name_len = len(this % name) - this % name = get_name(xs_id, name_len) + this % name = get_name(xs_id) ! Get rid of leading '/' this % name = trim(this % name(2:)) diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index a063bb90b..ebabe5cdb 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -290,7 +290,6 @@ contains integer(HID_T) :: total_nu integer(HID_T) :: fer_group ! fission_energy_release group integer(HID_T) :: fer_dset - integer(SIZE_T) :: name_len integer(HSIZE_T) :: j integer(HSIZE_T) :: dims(1) character(MAX_WORD_LEN) :: temp_str @@ -304,8 +303,7 @@ contains type(VectorInt) :: index_inelastic_scatter ! Get name of nuclide from group - name_len = len(this % name) - this % name = get_name(group_id, name_len) + this % name = get_name(group_id) ! Get rid of leading '/' this % name = trim(this % name(2:)) diff --git a/src/sab_header.F90 b/src/sab_header.F90 index 7ac14ef00..615e7b281 100644 --- a/src/sab_header.F90 +++ b/src/sab_header.F90 @@ -100,7 +100,6 @@ contains integer :: n_energy, n_energy_out, n_mu integer :: i_closest integer :: n_temperature - integer(SIZE_T) :: name_len integer(HID_T) :: T_group integer(HID_T) :: elastic_group integer(HID_T) :: inelastic_group @@ -120,8 +119,7 @@ contains type(VectorInt) :: temps_to_read ! Get name of table from group - name_len = len(this % name) - this % name = get_name(group_id, name_len) + this % name = get_name(group_id) ! Get rid of leading '/' this % name = trim(this % name(2:)) From da4999f1163b8b030dd08dc57d9ef2f9cb1133df Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 25 Apr 2018 12:51:45 -0500 Subject: [PATCH 218/361] Move reading command-line arguments to C++ side --- include/openmc.h | 6 +- openmc/capi/core.py | 4 +- src/api.F90 | 2 + src/hdf5_interface.F90 | 5 +- src/initialize.F90 | 200 +++++++------------------------------- src/initialize.cpp | 161 +++++++++++++++++++++++++++++- src/initialize.h | 6 +- src/main.cpp | 13 ++- src/output.F90 | 4 +- src/settings.F90 | 10 +- src/simulation_header.F90 | 2 +- 11 files changed, 229 insertions(+), 184 deletions(-) diff --git a/include/openmc.h b/include/openmc.h index 7f2bbf46c..055618741 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -47,7 +47,7 @@ extern "C" { int64_t openmc_get_seed(); int openmc_get_tally_index(int32_t id, int32_t* index); void openmc_hard_reset(); - int openmc_init(const void* intracomm); + int openmc_init(int argc, char* argv[], const void* intracomm); int openmc_init_f(const int* intracomm); int openmc_legendre_filter_get_order(int32_t index, int* order); int openmc_legendre_filter_set_order(int32_t index, int order); @@ -146,9 +146,11 @@ extern "C" { extern bool openmc_simulation_initialized; extern int openmc_verbosity; - // Variables that are shared by necessity (can be removed later) + // Variables that are shared by necessity (can be removed from public header + // later) extern bool openmc_master; extern int openmc_n_procs; + extern int openmc_n_threads; extern int openmc_rank; extern int64_t openmc_work; diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 57e2e88eb..1c2725382 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -27,7 +27,7 @@ _dll.openmc_find.argtypes = [POINTER(c_double*3), c_int, POINTER(c_int32), _dll.openmc_find.restype = c_int _dll.openmc_find.errcheck = _error_handler _dll.openmc_hard_reset.restype = None -_dll.openmc_init.argtypes = [c_void_p] +_dll.openmc_init.argtypes = [c_int, POINTER(c_char_p), c_void_p] _dll.openmc_init.restype = None _dll.openmc_get_keff.argtypes = [POINTER(c_double*2)] _dll.openmc_get_keff.restype = c_int @@ -126,7 +126,7 @@ def init(intracomm=None): address = MPI._addressof(intracomm) intracomm = c_void_p(address) - _dll.openmc_init(intracomm) + _dll.openmc_init(0, None, intracomm) def iter_batches(): diff --git a/src/api.F90 b/src/api.F90 index d00d166c4..bf760770b 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -104,7 +104,9 @@ contains subroutine openmc_finalize() bind(C) +#ifdef OPENMC_MPI integer :: err +#endif ! Clear results call openmc_reset() diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 57138c377..3fd5e860f 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -1105,7 +1105,7 @@ contains integer :: i integer(HSIZE_T) :: dims(1) - integer(C_SIZE_T) :: m + integer(C_SIZE_T) :: m, n logical(C_BOOL) :: indep_ character(kind=C_CHAR), allocatable :: buffer_(:) @@ -1118,7 +1118,8 @@ contains m = maxval(len_trim(buffer)) + 1 allocate(buffer_(dims(1)*m)) do i = 0, dims(1) - 1 - buffer_(i*m+1 : (i+1)*m) = to_c_string(buffer(i+1)) + n = len_trim(buffer(i+1)) + 1 + buffer_(i*m+1 : i*m+n) = to_c_string(buffer(i+1)) end do call write_string_c(group_id, 1, dims, m, to_c_string(name), & diff --git a/src/initialize.F90 b/src/initialize.F90 index 8d136cf24..d5ce32b31 100644 --- a/src/initialize.F90 +++ b/src/initialize.F90 @@ -1,6 +1,6 @@ module initialize - use, intrinsic :: ISO_C_BINDING, only: c_loc + use, intrinsic :: ISO_C_BINDING #ifdef _OPENMP use omp_lib @@ -8,28 +8,20 @@ module initialize use bank_header, only: Bank use constants - use set_header, only: SetInt - 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, HID_T use input_xml, only: read_input_xml - use material_header, only: Material use message_passing - use mgxs_data, only: read_mgxs, create_macro_xs - use output, only: print_version, print_usage use random_lcg, only: openmc_set_seed use settings -#ifdef _OPENMP - use simulation_header, only: n_threads -#endif - use string, only: to_str, starts_with, ends_with, str_to_int - use tally_header, only: TallyObject - use tally_filter + use string, only: ends_with, to_f_string use timer_header implicit none + type(C_PTR), bind(C) :: openmc_path_input + type(C_PTR), bind(C) :: openmc_path_statepoint + type(C_PTR), bind(C) :: openmc_path_sourcepoint + type(C_PTR), bind(C) :: openmc_path_particle_restart + contains !=============================================================================== @@ -43,7 +35,6 @@ contains integer, intent(in), optional :: intracomm ! MPI intracommunicator integer(C_INT) :: err - integer :: hdf5_err #ifdef _OPENMP character(MAX_WORD_LEN) :: envvar #endif @@ -161,162 +152,41 @@ contains !=============================================================================== subroutine read_command_line() + ! Arguments were already read on C++ side (initialize.cpp). Here we just + ! convert the C-style strings to Fortran style - integer :: i ! loop index - integer :: argc ! number of command line arguments - integer :: last_flag ! index of last flag - character(MAX_WORD_LEN) :: filetype - integer(HID_T) :: file_id - character(MAX_WORD_LEN), allocatable :: argv(:) ! command line arguments + character(kind=C_CHAR), pointer :: string(:) + interface + function is_null(ptr) result(x) bind(C) + import C_PTR, C_BOOL + type(C_PTR), value :: ptr + logical(C_BOOL) :: x + end function is_null + end interface - ! Check number of command line arguments and allocate argv - argc = COMMAND_ARGUMENT_COUNT() - - ! Allocate and retrieve command arguments - allocate(argv(argc)) - do i = 1, argc - call GET_COMMAND_ARGUMENT(i, argv(i)) - end do - - ! Process command arguments - last_flag = 0 - i = 1 - do while (i <= argc) - ! Check for flags - if (starts_with(argv(i), "-")) then - select case (argv(i)) - case ('-p', '-plot', '--plot') - run_mode = MODE_PLOTTING - check_overlaps = .true. - - case ('-n', '-particles', '--particles') - ! Read number of particles per cycle - i = i + 1 - n_particles = str_to_int(argv(i)) - - ! Check that number specified was valid - if (n_particles == ERROR_INT) then - call fatal_error("Must specify integer after " // trim(argv(i-1)) & - &// " command-line flag.") - end if - case ('-r', '-restart', '--restart') - ! Read path for state point/particle restart - i = i + 1 - - ! Check what type of file this is - file_id = file_open(argv(i), 'r', parallel=.true.) - call read_attribute(filetype, file_id, 'filetype') - call file_close(file_id) - - ! Set path and flag for type of run - select case (trim(filetype)) - case ('statepoint') - path_state_point = argv(i) - restart_run = .true. - case ('particle restart') - path_particle_restart = argv(i) - particle_restart_run = .true. - case default - call fatal_error("Unrecognized file after restart flag: " // filetype // ".") - end select - - ! If its a restart run check for additional source file - if (restart_run .and. i + 1 <= argc) then - - ! Increment arg - i = i + 1 - - ! Check if it has extension we can read - if (ends_with(argv(i), '.h5')) then - - ! Check file type is a source file - file_id = file_open(argv(i), 'r', parallel=.true.) - call read_attribute(filetype, file_id, 'filetype') - call file_close(file_id) - if (filetype /= 'source') then - call fatal_error("Second file after restart flag must be a & - &source file") - end if - - ! It is a source file - path_source_point = argv(i) - - else ! Different option is specified not a source file - - ! Source is in statepoint file - path_source_point = path_state_point - - ! Set argument back - i = i - 1 - - end if - - else ! No command line arg after statepoint - - ! Source is assumed to be in statepoint file - path_source_point = path_state_point - - end if - - case ('-g', '-geometry-debug', '--geometry-debug') - check_overlaps = .true. - - case ('-c', '--volume') - run_mode = MODE_VOLUME - - case ('-s', '--threads') - ! Read number of threads - i = i + 1 - -#ifdef _OPENMP - ! Read and set number of OpenMP threads - n_threads = int(str_to_int(argv(i)), 4) - if (n_threads < 1) then - call fatal_error("Invalid number of threads specified on command & - &line.") - end if - call omp_set_num_threads(n_threads) -#else - if (master) call warning("Ignoring number of threads specified on & - &command line.") -#endif - - case ('-?', '-h', '-help', '--help') - call print_usage() - stop - case ('-v', '-version', '--version') - call print_version() - stop - case ('-t', '-track', '--track') - write_all_tracks = .true. - case default - call fatal_error("Unknown command line option: " // argv(i)) - end select - - last_flag = i - end if - - ! Increment counter - i = i + 1 - end do - - ! Determine directory where XML input files are - if (argc > 0 .and. last_flag < argc) then - path_input = argv(last_flag + 1) + if (.not. is_null(openmc_path_input)) then + call c_f_pointer(openmc_path_input, string, [255]) + path_input = to_f_string(string) else path_input = '' end if - - ! Add slash at end of directory if it isn't there - if (.not. ends_with(path_input, "/") .and. len_trim(path_input) > 0) then - path_input = trim(path_input) // "/" + if (.not. is_null(openmc_path_statepoint)) then + call c_f_pointer(openmc_path_statepoint, string, [255]) + path_state_point = to_f_string(string) + end if + if (.not. is_null(openmc_path_sourcepoint)) then + call c_f_pointer(openmc_path_sourcepoint, string, [255]) + path_source_point = to_f_string(string) + end if + if (.not. is_null(openmc_path_particle_restart)) then + call c_f_pointer(openmc_path_particle_restart, string, [255]) + path_particle_restart = to_f_string(string) end if - ! Free memory from argv - deallocate(argv) - - ! TODO: Check that directory exists - + ! Add slash at end of directory if it isn't there + if (len_trim(path_input) > 0 .and. .not. ends_with(path_input, "/")) then + path_input = trim(path_input) // "/" + end if end subroutine read_command_line end module initialize diff --git a/src/initialize.cpp b/src/initialize.cpp index 23025feb2..d8045e433 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -1,12 +1,38 @@ #include "initialize.h" #include +#include +#include +#include +#include +#include "error.h" +#include "hdf5_interface.h" #include "message_passing.h" #include "openmc.h" +#ifdef _OPENMP +#include "omp.h" +#endif + +// data/functions from Fortran side +extern "C" bool openmc_check_overlaps; +extern "C" bool openmc_write_all_tracks; +extern "C" bool openmc_particle_restart_run; +extern "C" bool openmc_restart_run; +extern "C" void print_usage(); +extern "C" void print_version(); -int openmc_init(const void* intracomm) +// Paths to various files +extern "C" { + char* openmc_path_input; + char* openmc_path_statepoint; + char* openmc_path_sourcepoint; + char* openmc_path_particle_restart; + bool is_null(void* ptr) {return !ptr;} +} + +int openmc_init(int argc, char* argv[], const void* intracomm) { #ifdef OPENMC_MPI // Check if intracomm was passed @@ -19,13 +45,20 @@ int openmc_init(const void* intracomm) // Initialize MPI for C++ openmc::initialize_mpi(comm); +#endif + + // Parse command-line arguments + int err = openmc::parse_command_line(argc, argv); + if (err) return err; // Continue with rest of initialization +#ifdef OPENMC_MPI MPI_Fint fcomm = MPI_Comm_c2f(comm); openmc_init_f(&fcomm); #else openmc_init_f(nullptr); #endif + return 0; } @@ -63,6 +96,130 @@ void initialize_mpi(MPI_Comm intracomm) MPI_Type_create_struct(5, blocks, disp, types, &openmc::mpi::bank); MPI_Type_commit(&openmc::mpi::bank); } - #endif // OPENMC_MPI + + +inline bool ends_with(std::string const& value, std::string const& ending) +{ + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + + +int +parse_command_line(int argc, char* argv[]) +{ + char buffer[256]; // buffer for reading attribute + int last_flag = 0; + for (int i=1; i < argc; ++i) { + std::string arg {argv[i]}; + if (arg[0] == '-') { + if (arg == "-p" || arg == "--plot") { + openmc_run_mode = RUN_MODE_PLOTTING; + openmc_check_overlaps = true; + + } else if (arg == "-n" || arg == "--particles") { + i += 1; + n_particles = std::stoll(argv[i]); + + } else if (arg == "-r" || arg == "--restart") { + i += 1; + + // Check what type of file this is + hid_t file_id = file_open(argv[i], 'r', true); + size_t len = attribute_typesize(file_id, "filetype"); + read_attr_string(file_id, "filetype", len, buffer); + file_close(file_id); + std::string filetype {buffer}; + + // Set path and flag for type of run + if (filetype == "statepoint") { + openmc_path_statepoint = argv[i]; + openmc_restart_run = true; + } else if (filetype == "particle restart") { + openmc_path_particle_restart = argv[i]; + openmc_particle_restart_run = true; + } else { + std::stringstream msg; + msg << "Unrecognized file after restart flag: " << filetype << "."; + strcpy(openmc_err_msg, msg.str().c_str()); + return OPENMC_E_INVALID_ARGUMENT; + } + + // If its a restart run check for additional source file + if (openmc_restart_run && i + 1 < argc) { + // Check if it has extension we can read + if (ends_with(argv[i+1], ".h5")) { + + // Check file type is a source file + file_id = file_open(argv[i+1], 'r', true); + len = attribute_typesize(file_id, "filetype"); + read_attr_string(file_id, "filetype", len, buffer); + file_close(file_id); + if (filetype != "source") { + std::string msg {"Second file after restart flag must be a source file"}; + strcpy(openmc_err_msg, msg.c_str()); + return OPENMC_E_INVALID_ARGUMENT; + } + + // It is a source file + openmc_path_sourcepoint = argv[i+1]; + i += 1; + + } else { + // Source is in statepoint file + openmc_path_sourcepoint = openmc_path_statepoint; + } + + } else { + // Source is assumed to be in statepoint file + openmc_path_sourcepoint = openmc_path_statepoint; + } + + } else if (arg == "-g" || arg == "--geometry-debug") { + openmc_check_overlaps = true; + } else if (arg == "-c" || arg == "--volume") { + openmc_run_mode = RUN_MODE_VOLUME; + } else if (arg == "-s" || arg == "--threads") { + // Read number of threads + i += 1; + +#ifdef _OPENMP + // Read and set number of OpenMP threads + openmc_n_threads = std::stoi(argv[i]); + if (openmc_n_threads < 1) { + std::string msg {"Number of threads must be positive."}; + strcpy(openmc_err_msg, msg.c_str()); + return OPENMC_E_INVALID_ARGUMENT; + } + omp_set_num_threads(openmc_n_threads); +#endif + + } else if (arg == "-?" || arg == "-h" || arg == "--help") { + print_usage(); + return OPENMC_E_UNASSIGNED; + + } else if (arg == "-v" || arg == "--version") { + print_version(); + return OPENMC_E_UNASSIGNED; + + } else if (arg == "-t" || arg == "--track") { + openmc_write_all_tracks = true; + + } else { + std::cerr << "Unknown option: " << argv[i] << '\n'; + print_usage(); + return OPENMC_E_UNASSIGNED; + } + + last_flag = i; + } + } + + // Determine directory where XML input files are + if (argc > 1 && last_flag < argc) openmc_path_input = argv[last_flag + 1]; + + return 0; +} + } // namespace openmc diff --git a/src/initialize.h b/src/initialize.h index d2c4df5d7..14283fb5f 100644 --- a/src/initialize.h +++ b/src/initialize.h @@ -5,10 +5,14 @@ #include "mpi.h" #endif +extern "C" void print_usage(); +extern "C" void print_version(); + namespace openmc { +int parse_command_line(int argc, char* argv[]); #ifdef OPENMC_MPI - void initialize_mpi(MPI_Comm intracomm); +void initialize_mpi(MPI_Comm intracomm); #endif } diff --git a/src/main.cpp b/src/main.cpp index 1dd20ebe0..5bf305093 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,19 +1,26 @@ +#include "error.h" #ifdef OPENMC_MPI #include "mpi.h" #endif #include "openmc.h" -int main(int argc, char** argv) { +int main(int argc, char* argv[]) { int err; // Initialize run -- when run with MPI, pass communicator #ifdef OPENMC_MPI MPI_Comm world {MPI_COMM_WORLD}; - openmc_init(&world); + err = openmc_init(argc, argv, &world); #else - openmc_init(nullptr); + err = openmc_init(argc, argv, nullptr); #endif + if (err == -1) { + // This happens for the -h and -v flags + return 0; + } else if (err) { + openmc::fatal_error(openmc_err_msg); + } // start problem based on mode switch (openmc_run_mode) { diff --git a/src/output.F90 b/src/output.F90 index b6809ca82..174292fb3 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -162,7 +162,7 @@ contains ! information !=============================================================================== - subroutine print_version() + subroutine print_version() bind(C) if (master) then write(UNIT=OUTPUT_UNIT, FMT='(1X,A,1X,I1,".",I2,".",I1)') & @@ -182,7 +182,7 @@ contains ! PRINT_USAGE displays information about command line usage of OpenMC !=============================================================================== - subroutine print_usage() + subroutine print_usage() bind(C) if (master) then write(OUTPUT_UNIT,*) 'Usage: openmc [options] [directory]' diff --git a/src/settings.F90 b/src/settings.F90 index 4de72e8c4..aef1c011b 100644 --- a/src/settings.F90 +++ b/src/settings.F90 @@ -78,13 +78,13 @@ module settings integer(C_INT), bind(C, name='openmc_run_mode') :: run_mode = NONE ! Restart run - logical :: restart_run = .false. + logical(C_BOOL), bind(C, name='openmc_restart_run') :: restart_run = .false. ! The verbosity controls how much information will be printed to the screen ! and in logs integer(C_INT), bind(C, name='openmc_verbosity') :: verbosity = 7 - logical :: check_overlaps = .false. + logical(C_BOOL), bind(C, name='openmc_check_overlaps') :: check_overlaps = .false. ! Trace for single particle integer :: trace_batch @@ -92,11 +92,13 @@ module settings integer(8) :: trace_particle ! Particle tracks - logical :: write_all_tracks = .false. + logical(C_BOOL), bind(C, name='openmc_write_all_tracks') :: & + write_all_tracks = .false. integer, allocatable :: track_identifiers(:,:) ! Particle restart run - logical :: particle_restart_run = .false. + logical(C_BOOL), bind(C, name='openmc_particle_restart_run') :: & + particle_restart_run = .false. ! Write out initial source logical :: write_initial_source = .false. diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index a214f4410..8fe5e2da1 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -58,7 +58,7 @@ module simulation_header ! PARALLEL PROCESSING VARIABLES #ifdef _OPENMP - integer :: n_threads = NONE ! number of OpenMP threads + integer(C_INT), bind(C, name='openmc_n_threads') :: n_threads = NONE ! number of OpenMP threads integer :: thread_id ! ID of a given thread #endif From 30944b554a5ecda6d7ec20fb221419a76c5bc618 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 25 Apr 2018 14:46:27 -0500 Subject: [PATCH 219/361] Update docs, don't modify when .mod files are created --- CMakeLists.txt | 6 +----- docs/source/capi/index.rst | 6 ++++-- docs/source/usersguide/install.rst | 8 +------- src/math.F90 | 1 - 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ed72434d8..3cfea2227 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,6 @@ project(openmc Fortran C CXX) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/include) # Set module path set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules) @@ -456,10 +455,7 @@ add_executable(${program} src/main.cpp) set_property(TARGET ${program} libopenmc pugixml_fortran PROPERTY LINKER_LANGUAGE Fortran) -target_include_directories(libopenmc - PUBLIC include ${HDF5_INCLUDE_DIRS} - PRIVATE ${CMAKE_BINARY_DIR}/include - ) +target_include_directories(libopenmc PUBLIC include ${HDF5_INCLUDE_DIRS}) # The executable and the faddeeva package use only one language. They can be # set via target_compile_options which accepts a list. diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index 0e6a2536c..fbc2830b5 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -270,13 +270,15 @@ Functions Reset tallies, timers, and pseudo-random number generator state -.. c:function:: void openmc_init(const int* intracomm) +.. c:function:: void openmc_init(int argc, char** argv, const void* intracomm) Initialize OpenMC + :param int argc: Number of command-line arguments (including command) + :param char** argv: Command-line arguments :param intracomm: MPI intracommunicator. If MPI is not being used, a null pointer should be passed. - :type intracomm: const int* + :type intracomm: const void* .. c:function:: int openmc_load_nuclide(char name[]) diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index b0618818e..f5b277a84 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -141,16 +141,10 @@ Prerequisites recommend that your HDF5 installation be built with parallel I/O features. An example of configuring HDF5_ is listed below:: - FC=mpifort ./configure --enable-fortran --enable-parallel + FC=mpifort ./configure --enable-parallel You may omit ``--enable-parallel`` if you want to compile HDF5_ in serial. - .. important:: - - If you are building HDF5 version 1.8.x or earlier, you must include - ``--enable-fortran2003`` as well when configuring HDF5 or else OpenMC - will not be able to compile. - .. admonition:: Optional :class: note diff --git a/src/math.F90 b/src/math.F90 index ee8cd0530..20bacf88c 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -598,7 +598,6 @@ contains real(8) :: zn_mat(n+1, n+1) ! Matrix form of the coefficients which is ! easier to work with real(8) :: k1, k2, k3, k4 ! Variables for R_m_n calculation - real(8) :: sqrt_norm ! normalization for radial moments integer :: i,p,q ! Loop counters real(8), parameter :: SQRT_N_1(0:10) = [& From 0a8f5977edd4dcf5506e86731867f3226d32e500 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 25 Apr 2018 15:07:40 -0500 Subject: [PATCH 220/361] Be more consistent with C API function return values --- docs/source/capi/index.rst | 52 ++++++++++++++++++++++++++++++-------- include/openmc.h | 17 ++++++------- openmc/capi/core.py | 27 +++++++++++++------- src/api.F90 | 27 +++++++++++--------- src/main.cpp | 4 +-- src/plot.F90 | 6 +++-- src/simulation.F90 | 18 +++++++++---- src/state_point.F90 | 7 ++--- src/volume_calc.F90 | 6 +++-- 9 files changed, 109 insertions(+), 55 deletions(-) diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index fbc2830b5..85509f6f4 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -46,10 +46,13 @@ Type Definitions Functions --------- -.. c:function:: void openmc_calculate_volumes() +.. c:function:: int openmc_calculate_volumes() Run a stochastic volume calculation + :return: Return status (negative if an error occurred) + :rtype: int + .. c:function:: int openmc_cell_get_fill(int32_t index, int* type, int32_t** indices, int32_t* n) Get the fill for a cell @@ -192,11 +195,14 @@ Functions :return: Return status (negative if an error occurred) :rtype: int -.. c:function:: void openmc_finalize() +.. c:function:: int openmc_finalize() Finalize a simulation -.. c:function:: void openmc_find(double* xyz, int rtype, int32_t* id, int32_t* instance) + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_find(double* xyz, int rtype, int32_t* id, int32_t* instance) Determine the ID of the cell/material containing a given point @@ -207,6 +213,8 @@ Functions occurs, the ID is -1. :param int32_t* instance: If a cell is repeated in the geometry, the instance of the cell that was found and zero otherwise. + :return: Return status (negative if an error occurs) + :rtype: int .. c:function:: int openmc_get_cell_index(int32_t id, int32_t* index) @@ -266,11 +274,14 @@ Functions :return: Return status (negative if an error occurs) :rtype: int -.. c:function:: void openmc_hard_reset() +.. c:function:: int openmc_hard_reset() Reset tallies, timers, and pseudo-random number generator state -.. c:function:: void openmc_init(int argc, char** argv, const void* intracomm) + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_init(int argc, char** argv, const void* intracomm) Initialize OpenMC @@ -279,6 +290,8 @@ Functions :param intracomm: MPI intracommunicator. If MPI is not being used, a null pointer should be passed. :type intracomm: const void* + :return: Return status (negative if an error occurs) + :rtype: int .. c:function:: int openmc_load_nuclide(char name[]) @@ -396,26 +409,41 @@ Functions :return: Return status (negative if an error occurs) :rtype: int -.. c:function:: void openmc_plot_geometry() +.. c:function:: int openmc_plot_geometry() Run plotting mode. -.. c:function:: void openmc_reset() + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_reset() Resets all tally scores -.. c:function:: void openmc_run() + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_run() Run a simulation -.. c:function:: void openmc_simulation_finalize() + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_simulation_finalize() Finalize a simulation. -.. c:function:: void openmc_simulation_init() + :return: Return status (negative if an error occurs) + :rtype: int + +.. c:function:: int openmc_simulation_init() Initialize a simulation. Must be called after openmc_init(). + :return: Return status (negative if an error occurs) + :rtype: int + .. c:function:: int openmc_source_bank(struct Bank** ptr, int64_t* n) Return a pointer to the source bank array. @@ -435,13 +463,15 @@ Functions :return: Return status (negative if an error occurred) :rtype: int -.. c:function:: void openmc_statepoint_write(const char filename[]) +.. c:function:: int openmc_statepoint_write(const char filename[]) Write a statepoint file :param filename: Name of file to create. If a null pointer is passed, a filename is assigned automatically. :type filename: const char[] + :return: Return status (negative if an error occurs) + :rtype: int .. c:function:: int openmc_tally_get_id(int32_t index, int32_t* id) diff --git a/include/openmc.h b/include/openmc.h index 055618741..38b7f2167 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -4,7 +4,6 @@ #include #include - #ifdef __cplusplus extern "C" { #endif @@ -17,7 +16,7 @@ extern "C" { int delayed_group; }; - void openmc_calculate_volumes(); + int openmc_calculate_volumes(); int openmc_cell_get_fill(int32_t index, int* type, int32_t** indices, int32_t* n); int openmc_cell_get_id(int32_t index, int32_t* id); int openmc_cell_set_fill(int32_t index, int type, int32_t n, const int32_t* indices); @@ -35,7 +34,7 @@ extern "C" { int openmc_filter_get_type(int32_t index, const char** type); int openmc_filter_set_id(int32_t index, int32_t id); int openmc_filter_set_type(int32_t index, const char* type); - void openmc_finalize(); + int openmc_finalize(); int openmc_find(double* xyz, int rtype, int32_t* id, int32_t* instance); int openmc_get_cell_index(int32_t id, int32_t* index); int openmc_get_filter_index(int32_t id, int32_t* index); @@ -46,7 +45,7 @@ extern "C" { int openmc_get_nuclide_index(const char name[], int* index); int64_t openmc_get_seed(); int openmc_get_tally_index(int32_t id, int32_t* index); - void openmc_hard_reset(); + int openmc_hard_reset(); int openmc_init(int argc, char* argv[], const void* intracomm); int openmc_init_f(const int* intracomm); int openmc_legendre_filter_get_order(int32_t index, int* order); @@ -73,12 +72,12 @@ extern "C" { int openmc_next_batch(int* status); int openmc_nuclide_name(int index, char** name); int openmc_particle_restart(); - void openmc_plot_geometry(); - void openmc_reset(); + int openmc_plot_geometry(); + int openmc_reset(); int openmc_run(); void openmc_set_seed(int64_t new_seed); - void openmc_simulation_finalize(); - void openmc_simulation_init(); + int openmc_simulation_finalize(); + int openmc_simulation_init(); int openmc_source_bank(struct Bank** ptr, int64_t* n); int openmc_source_set_strength(int32_t index, double strength); int openmc_spatial_legendre_filter_get_order(int32_t index, int* order); @@ -90,7 +89,7 @@ extern "C" { int openmc_sphharm_filter_get_cosine(int32_t index, char cosine[]); int openmc_sphharm_filter_set_order(int32_t index, int order); int openmc_sphharm_filter_set_cosine(int32_t index, const char cosine[]); - void openmc_statepoint_write(const char filename[]); + int openmc_statepoint_write(const char filename[]); int openmc_tally_get_active(int32_t index, bool* active); int openmc_tally_get_id(int32_t index, int32_t* id); int openmc_tally_get_filters(int32_t index, int32_t** indices, int* n); diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 1c2725382..273f0c0c7 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -20,32 +20,41 @@ class _Bank(Structure): ('delayed_group', c_int)] -_dll.openmc_calculate_volumes.restype = None -_dll.openmc_finalize.restype = None +_dll.openmc_calculate_volumes.restype = c_int +_dll.openmc_calculate_volumes.errcheck = _error_handler +_dll.openmc_finalize.restype = c_int +_dll.openmc_finalize.errcheck = _error_handler _dll.openmc_find.argtypes = [POINTER(c_double*3), c_int, POINTER(c_int32), POINTER(c_int32)] _dll.openmc_find.restype = c_int _dll.openmc_find.errcheck = _error_handler -_dll.openmc_hard_reset.restype = None +_dll.openmc_hard_reset.restype = c_int +_dll.openmc_hard_reset.errcheck = _error_handler _dll.openmc_init.argtypes = [c_int, POINTER(c_char_p), c_void_p] -_dll.openmc_init.restype = None +_dll.openmc_init.restype = c_int +_dll.openmc_init.errcheck = _error_handler _dll.openmc_get_keff.argtypes = [POINTER(c_double*2)] _dll.openmc_get_keff.restype = c_int _dll.openmc_get_keff.errcheck = _error_handler _dll.openmc_next_batch.argtypes = [POINTER(c_int)] _dll.openmc_next_batch.restype = c_int _dll.openmc_next_batch.errcheck = _error_handler -_dll.openmc_plot_geometry.restype = None +_dll.openmc_plot_geometry.restype = c_int +_dll.openmc_plot_geometry.restype = _error_handler _dll.openmc_run.restype = c_int _dll.openmc_run.errcheck = _error_handler -_dll.openmc_reset.restype = None +_dll.openmc_reset.restype = c_int +_dll.openmc_reset.errcheck = _error_handler _dll.openmc_source_bank.argtypes = [POINTER(POINTER(_Bank)), POINTER(c_int64)] _dll.openmc_source_bank.restype = c_int _dll.openmc_source_bank.errcheck = _error_handler -_dll.openmc_simulation_init.restype = None -_dll.openmc_simulation_finalize.restype = None +_dll.openmc_simulation_init.restype = c_int +_dll.openmc_simulation_init.errcheck = _error_handler +_dll.openmc_simulation_finalize.restype = c_int +_dll.openmc_simulation_finalize.errcheck = _error_handler _dll.openmc_statepoint_write.argtypes = [POINTER(c_char_p)] -_dll.openmc_statepoint_write.restype = None +_dll.openmc_statepoint_write.restype = c_int +_dll.openmc_statepoint_write.errcheck = _error_handler def calculate_volumes(): diff --git a/src/api.F90 b/src/api.F90 index bf760770b..49d86edb6 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -102,14 +102,11 @@ contains ! variables !=============================================================================== - subroutine openmc_finalize() bind(C) - -#ifdef OPENMC_MPI - integer :: err -#endif + function openmc_finalize() result(err) bind(C) + integer(C_INT) :: err ! Clear results - call openmc_reset() + err = openmc_reset() ! Reset global variables assume_separate = .false. @@ -169,12 +166,13 @@ contains ! Deallocate arrays call free_memory() + err = 0 #ifdef OPENMC_MPI ! Free all MPI types call MPI_TYPE_FREE(MPI_BANK, err) #endif - end subroutine openmc_finalize + end function openmc_finalize !=============================================================================== ! OPENMC_FIND determines the ID or a cell or material at a given point in space @@ -225,9 +223,11 @@ contains ! generator state !=============================================================================== - subroutine openmc_hard_reset() bind(C) + function openmc_hard_reset() result(err) bind(C) + integer(C_INT) :: err + ! Reset all tallies and timers - call openmc_reset() + err = openmc_reset() ! Reset total generations and keff guess keff = ONE @@ -235,13 +235,15 @@ contains ! Reset the random number generator state call openmc_set_seed(DEFAULT_SEED) - end subroutine openmc_hard_reset + end function openmc_hard_reset !=============================================================================== ! OPENMC_RESET resets tallies and timers !=============================================================================== - subroutine openmc_reset() bind(C) + function openmc_reset() result(err) bind(C) + integer(C_INT) :: err + integer :: i if (allocated(tallies)) then @@ -289,7 +291,8 @@ contains call time_transport % reset() call time_finalize % reset() - end subroutine openmc_reset + err = 0 + end function openmc_reset !=============================================================================== ! FREE_MEMORY deallocates and clears all global allocatable arrays in the diff --git a/src/main.cpp b/src/main.cpp index 5bf305093..b9e475cd7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,13 +29,13 @@ int main(int argc, char* argv[]) { err = openmc_run(); break; case RUN_MODE_PLOTTING: - openmc_plot_geometry(); + err = openmc_plot_geometry(); break; case RUN_MODE_PARTICLE: if (openmc_master) err = openmc_particle_restart(); break; case RUN_MODE_VOLUME: - openmc_calculate_volumes(); + err = openmc_calculate_volumes(); break; } diff --git a/src/plot.F90 b/src/plot.F90 index c2ffa2d39..f17ac3351 100644 --- a/src/plot.F90 +++ b/src/plot.F90 @@ -30,7 +30,8 @@ contains ! RUN_PLOT controls the logic for making one or many plots !=============================================================================== - subroutine openmc_plot_geometry() bind(C) + function openmc_plot_geometry() result(err) bind(C) + integer(C_INT) :: err integer :: i ! loop index for plots @@ -50,7 +51,8 @@ contains end associate end do - end subroutine openmc_plot_geometry + err = 0 + end function openmc_plot_geometry !=============================================================================== ! POSITION_RGB computes the red/green/blue values for a given plot with the diff --git a/src/simulation.F90 b/src/simulation.F90 index e9729df03..4f6163429 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -312,6 +312,7 @@ contains subroutine finalize_batch() + integer(C_INT) :: err #ifdef OPENMC_MPI integer :: mpi_err ! MPI error code #endif @@ -347,7 +348,7 @@ contains ! Write out state point if it's been specified for this batch if (statepoint_batch % contains(current_batch)) then - call openmc_statepoint_write() + err = openmc_statepoint_write() end if ! Write out source point if it's been specified for this batch @@ -396,9 +397,13 @@ contains ! INITIALIZE_SIMULATION !=============================================================================== - subroutine openmc_simulation_init() bind(C) + function openmc_simulation_init() result(err) bind(C) + integer(C_INT) :: err + integer :: i + err = 0 + ! Skip if simulation has already been initialized if (simulation_initialized) return @@ -456,14 +461,15 @@ contains ! Set flag indicating initialization is done simulation_initialized = .true. - end subroutine openmc_simulation_init + end function openmc_simulation_init !=============================================================================== ! FINALIZE_SIMULATION calculates tally statistics, writes tallies, and displays ! execution time and results !=============================================================================== - subroutine openmc_simulation_finalize() bind(C) + function openmc_simulation_finalize() result(err) bind(C) + integer(C_INT) :: err integer :: i ! loop index #ifdef OPENMC_MPI @@ -479,6 +485,8 @@ contains #endif #endif + err = 0 + ! Skip if simulation was never run if (.not. simulation_initialized) return @@ -553,7 +561,7 @@ contains need_depletion_rx = .false. simulation_initialized = .false. - end subroutine openmc_simulation_finalize + end function openmc_simulation_finalize !=============================================================================== ! CALCULATE_WORK determines how many particles each processor should simulate diff --git a/src/state_point.F90 b/src/state_point.F90 index 5b1c4d462..005b549c3 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -58,8 +58,9 @@ contains ! OPENMC_STATEPOINT_WRITE writes an HDF5 statepoint file to disk !=============================================================================== - subroutine openmc_statepoint_write(filename) bind(C) + function openmc_statepoint_write(filename) result(err) bind(C) type(C_PTR), intent(in), optional :: filename + integer(C_INT) :: err integer :: i, j, k integer :: i_xs @@ -70,12 +71,12 @@ contains integer(HID_T) :: cmfd_group, tallies_group, tally_group, meshes_group, & filters_group, filter_group, derivs_group, & deriv_group, runtime_group - integer(C_INT) :: err real(C_DOUBLE) :: k_combined(2) character(MAX_WORD_LEN), allocatable :: str_array(:) character(C_CHAR), pointer :: string(:) character(len=:, kind=C_CHAR), allocatable :: filename_ + err = 0 if (present(filename)) then call c_f_pointer(filename, string, [MAX_FILE_LEN]) filename_ = to_f_string(string) @@ -458,7 +459,7 @@ contains call file_close(file_id) end if - end subroutine openmc_statepoint_write + end function openmc_statepoint_write !=============================================================================== ! WRITE_SOURCE_POINT diff --git a/src/volume_calc.F90 b/src/volume_calc.F90 index fa770c1f3..8264797a7 100644 --- a/src/volume_calc.F90 +++ b/src/volume_calc.F90 @@ -36,9 +36,10 @@ contains ! the user has specified and writes results to HDF5 files !=============================================================================== - subroutine openmc_calculate_volumes() bind(C) + function openmc_calculate_volumes() result(err) bind(C) integer :: i, j integer :: n + integer(C_INT) :: err real(8), allocatable :: volume(:,:) ! volume mean/stdev in each domain character(10) :: domain_type character(MAX_FILE_LEN) :: filename ! filename for HDF5 file @@ -99,7 +100,8 @@ contains call write_message("Elapsed time: " // trim(to_str(time_volume % & get_value())) // " s", 6) end if - end subroutine openmc_calculate_volumes + err = 0 + end function openmc_calculate_volumes !=============================================================================== ! GET_VOLUME stochastically determines the volume of a set of domains along with From 9e29b8e202348bc24f20e1ae3f98ad584c6db0ab Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 25 Apr 2018 15:18:12 -0500 Subject: [PATCH 221/361] Support using statepoint as a source file --- docs/source/io_formats/source.rst | 16 ++++++++-------- src/source.F90 | 6 +++--- src/state_point.F90 | 6 +----- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/docs/source/io_formats/source.rst b/docs/source/io_formats/source.rst index a0a62afca..cff77d2fa 100644 --- a/docs/source/io_formats/source.rst +++ b/docs/source/io_formats/source.rst @@ -8,13 +8,13 @@ Normally, source data is stored in a state point file. However, it is possible to request that the source be written separately, in which case the format used is that documented here. -**/filetype** (*char[]*) +**/** - String indicating the type of file. +:Attributes: - **filetype** (*char[]*) -- String indicating the type of file. -**/source_bank** (Compound type) - - Source bank information for each particle. The compound type has fields - ``wgt``, ``xyz``, ``uvw``, ``E``, and ``delayed_group``, which - represent the weight, position, direction, energy, energy group, and - delayed_group of the source particle, respectively. +:Datasets: + - **source_bank** (Compound type) -- Source bank information for each + particle. The compound type has fields ``wgt``, ``xyz``, ``uvw``, + ``E``, and ``delayed_group``, which represent the weight, position, + direction, energy, energy group, and delayed_group of the source + particle, respectively. diff --git a/src/source.F90 b/src/source.F90 index b02430823..368361131 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -11,7 +11,7 @@ module source use distribution_multivariate, only: SpatialBox use error, only: fatal_error use geometry, only: find_cell - use hdf5_interface, only: file_open, file_close, read_dataset, HID_T + use hdf5_interface use math use message_passing, only: rank use mgxs_header, only: rev_energy_bins, num_energy_groups @@ -54,10 +54,10 @@ contains file_id = file_open(path_source, 'r', parallel=.true.) ! Read the file type - call read_dataset(filetype, file_id, "filetype") + call read_attribute(filetype, file_id, "filetype") ! Check to make sure this is a source file - if (filetype /= 'source') then + if (filetype /= 'source' .and. filetype /= 'statepoint') then call fatal_error("Specified starting source file not a source file & &type.") end if diff --git a/src/state_point.F90 b/src/state_point.F90 index 005b549c3..cc7511dd6 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -515,7 +515,7 @@ contains call write_message("Creating source file " // trim(filename) // "...", 5) if (master .or. parallel) then file_id = file_open(filename, 'w', parallel=.true.) - call write_dataset(file_id, "filetype", 'source') + call write_attribute(file_id, "filetype", 'source') end if call write_source_bank(file_id, work_index, source_bank) @@ -831,10 +831,6 @@ contains ! Open source file file_id = file_open(path_source_point, 'r', parallel=.true.) - - ! Read file type - call read_dataset(int_array(1), file_id, "filetype") - end if ! Write out source From f528cbca97fc4450bce58cbb0f33a96accb98b41 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 25 Apr 2018 16:12:23 -0500 Subject: [PATCH 222/361] Fix for source_file test, oops --- src/state_point.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state_point.F90 b/src/state_point.F90 index cc7511dd6..a6e44dfa1 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -493,7 +493,7 @@ contains ! Create separate source file if (master .or. parallel) then file_id = file_open(filename, 'w', parallel=.true.) - call write_dataset(file_id, "filetype", 'source') + call write_attribute(file_id, "filetype", 'source') end if else filename = trim(path_output) // 'statepoint.' // & From 8f77f7ba06a8ab93355178d4c38b9615ce0da7f3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 25 Apr 2018 22:26:15 -0500 Subject: [PATCH 223/361] Try not propagating error status from MPI_Finalize --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index b9e475cd7..42593dc65 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,6 +44,6 @@ int main(int argc, char* argv[]) { // If MPI is in use and enabled, terminate it #ifdef MPI - err = MPI_Finalize(); + MPI_Finalize(); #endif } From ebe53476360a93f94a2afa6096829003e290c9b5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 26 Apr 2018 07:03:40 -0500 Subject: [PATCH 224/361] Fix MPI-related bugs --- CMakeLists.txt | 1 + src/api.F90 | 6 ++++++ src/finalize.cpp | 10 ++++++++++ src/finalize.h | 6 ++++++ src/hdf5_interface.F90 | 2 +- src/initialize.cpp | 3 ++- src/main.cpp | 6 ++++-- 7 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/finalize.cpp create mode 100644 src/finalize.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cfea2227..7ecd6e321 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -433,6 +433,7 @@ set(LIBOPENMC_FORTRAN_SRC ) set(LIBOPENMC_CXX_SRC src/initialize.cpp + src/finalize.cpp src/hdf5_interface.cpp src/message_passing.cpp src/plot.cpp diff --git a/src/api.F90 b/src/api.F90 index 49d86edb6..bc0757be5 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -105,6 +105,11 @@ contains function openmc_finalize() result(err) bind(C) integer(C_INT) :: err + interface + subroutine openmc_free_bank() bind(C) + end subroutine openmc_free_bank + end interface + ! Clear results err = openmc_reset() @@ -170,6 +175,7 @@ contains #ifdef OPENMC_MPI ! Free all MPI types call MPI_TYPE_FREE(MPI_BANK, err) + call openmc_free_bank() #endif end function openmc_finalize diff --git a/src/finalize.cpp b/src/finalize.cpp new file mode 100644 index 000000000..696a1e80e --- /dev/null +++ b/src/finalize.cpp @@ -0,0 +1,10 @@ +#include "finalize.h" + +#include "message_passing.h" + +void openmc_free_bank() +{ +#ifdef OPENMC_MPI + MPI_Type_free(&openmc::mpi::bank); +#endif +} diff --git a/src/finalize.h b/src/finalize.h new file mode 100644 index 000000000..e606493ca --- /dev/null +++ b/src/finalize.h @@ -0,0 +1,6 @@ +#ifndef FINALIZE_H +#define FINALIZE_H + +extern "C" void openmc_free_bank(); + +#endif // FINALIZE_H diff --git a/src/hdf5_interface.F90 b/src/hdf5_interface.F90 index 3fd5e860f..40f56d88e 100644 --- a/src/hdf5_interface.F90 +++ b/src/hdf5_interface.F90 @@ -1103,7 +1103,7 @@ contains character(*), intent(in), target :: buffer(:) ! read data to here logical, intent(in), optional :: indep ! independent I/O - integer :: i + integer(HSIZE_T) :: i integer(HSIZE_T) :: dims(1) integer(C_SIZE_T) :: m, n logical(C_BOOL) :: indep_ diff --git a/src/initialize.cpp b/src/initialize.cpp index d8045e433..c7c37e31d 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -71,7 +71,8 @@ void initialize_mpi(MPI_Comm intracomm) // Initialize MPI int flag; - if (!MPI_Initialized(&flag)) MPI_Init(nullptr, nullptr); + MPI_Initialized(&flag); + if (!flag) MPI_Init(nullptr, nullptr); // Determine number of processes and rank for each MPI_Comm_size(intracomm, &openmc::mpi::n_procs); diff --git a/src/main.cpp b/src/main.cpp index 42593dc65..6d0cf1f26 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,12 +38,14 @@ int main(int argc, char* argv[]) { err = openmc_calculate_volumes(); break; } + if (err) openmc::fatal_error(openmc_err_msg); // Finalize and free up memory - openmc_finalize(); + err = openmc_finalize(); + if (err) openmc::fatal_error(openmc_err_msg); // If MPI is in use and enabled, terminate it -#ifdef MPI +#ifdef OPENMC_MPI MPI_Finalize(); #endif } From 1d2ebb71f8789410052c10773d0011232aeaa706 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Fri, 27 Apr 2018 19:45:38 -0400 Subject: [PATCH 225/361] Implemented Expansion filters in to the MGXS classes and removed the old score-based expansions --- .gitignore | 5 +- docs/source/io_formats/statepoint.rst | 9 +- docs/source/io_formats/summary.rst | 16 +- docs/source/usersguide/tallies.rst | 63 +- examples/jupyter/mg-mode-part-ii.ipynb | 411 ++--- examples/jupyter/mg-mode-part-iii.ipynb | 309 ++-- examples/jupyter/mgxs-part-iii.ipynb | 919 +++++------ openmc/capi/tally.py | 8 +- openmc/filter.py | 4 +- openmc/filter_expansion.py | 7 + openmc/material.py | 20 +- openmc/mesh.py | 68 +- openmc/mgxs/library.py | 88 +- openmc/mgxs/mgxs.py | 379 ++--- openmc/plotter.py | 23 +- openmc/statepoint.py | 7 - openmc/summary.py | 27 +- openmc/tallies.py | 76 +- src/cmfd_data.F90 | 53 +- src/cmfd_input.F90 | 66 +- src/constants.F90 | 54 +- src/endf.F90 | 16 - src/input_xml.F90 | 240 +-- src/math.F90 | 1 - src/mgxs_header.F90 | 10 +- src/output.F90 | 71 +- src/state_point.F90 | 35 - src/summary.F90 | 156 +- src/tallies/tally.F90 | 205 +-- src/tallies/tally_header.F90 | 6 - src/tallies/trigger.F90 | 76 +- .../cmfd_feed/results_true.dat | 61 +- .../cmfd_nofeed/results_true.dat | 61 +- .../mgxs_library_ce_to_mg/inputs_true.dat | 55 +- .../mgxs_library_condense/inputs_true.dat | 814 +++++----- .../mgxs_library_condense/results_true.dat | 120 +- .../mgxs_library_distribcell/inputs_true.dat | 112 +- .../mgxs_library_distribcell/results_true.dat | 40 +- .../mgxs_library_hdf5/inputs_true.dat | 814 +++++----- .../mgxs_library_mesh/inputs_true.dat | 112 +- .../mgxs_library_mesh/results_true.dat | 144 +- .../mgxs_library_no_nuclides/inputs_true.dat | 814 +++++----- .../mgxs_library_no_nuclides/results_true.dat | 408 ++--- .../mgxs_library_nuclides/inputs_true.dat | 648 ++++---- .../mgxs_library_nuclides/results_true.dat | 2 +- .../sourcepoint_restart/results_true.dat | 1440 ----------------- .../sourcepoint_restart/tallies.xml | 2 +- .../statepoint_restart/results_true.dat | 1328 +++------------ .../statepoint_restart/tallies.xml | 2 +- .../regression_tests/tallies/inputs_true.dat | 68 +- .../regression_tests/tallies/results_true.dat | 2 +- tests/regression_tests/tallies/test.py | 105 +- tests/regression_tests/track_output/test.py | 2 +- 53 files changed, 3695 insertions(+), 6887 deletions(-) diff --git a/.gitignore b/.gitignore index 65c7285af..0fc2c63bc 100644 --- a/.gitignore +++ b/.gitignore @@ -99,4 +99,7 @@ examples/jupyter/plots .tox/ .python-version .coverage -htmlcov \ No newline at end of file +htmlcov + +# Test data +tests/xsdir diff --git a/docs/source/io_formats/statepoint.rst b/docs/source/io_formats/statepoint.rst index f0f2af59b..38dae7dd7 100644 --- a/docs/source/io_formats/statepoint.rst +++ b/docs/source/io_formats/statepoint.rst @@ -133,15 +133,8 @@ The current version of the statepoint file format is 17.0. - **derivative** (*int*) -- ID of the derivative applied to the tally. - **n_score_bins** (*int*) -- Number of scoring bins for a single - nuclide. In general, this can be greater than the number of - user-specified scores since each score might have multiple scoring - bins, e.g., scatter-PN. + nuclide. - **score_bins** (*char[][]*) -- Values of specified scores. - - **n_user_scores** (*int*) -- Number of scores without accounting - for those added by expansions, e.g. scatter-PN. - - **moment_orders** (*char[][]*) -- Tallying moment orders for - Legendre and spherical harmonic tally expansions (e.g., 'P2', - 'Y1,2', etc.). - **results** (*double[][][2]*) -- Accumulated sum and sum-of-squares for each bin of the i-th tally. The first dimension represents combinations of filter bins, the second dimensions represents diff --git a/docs/source/io_formats/summary.rst b/docs/source/io_formats/summary.rst index 049749b59..76c412b86 100644 --- a/docs/source/io_formats/summary.rst +++ b/docs/source/io_formats/summary.rst @@ -4,7 +4,7 @@ Summary File Format =================== -The current version of the summary file format is 5.0. +The current version of the summary file format is 6.0. **/** @@ -104,8 +104,13 @@ The current version of the summary file format is 5.0. - **atom_density** (*double[]*) -- Total atom density of the material in atom/b-cm. - **nuclides** (*char[][]*) -- Array of nuclides present in the - material, e.g., 'U235'. + material, e.g., 'U235'. This data set is only present if nuclides + are used. - **nuclide_densities** (*double[]*) -- Atom density of each nuclide. + This data set is only present if 'nuclides' data set is present. + - **macroscopics** (*char[][]*) -- Array of macroscopic data sets + present in the material. This dataset is only present if + macroscopic data sets are used in multi-group mode. - **sab_names** (*char[][]*) -- Names of S(:math:`\alpha,\beta`) tables assigned to the material. @@ -116,6 +121,13 @@ The current version of the summary file format is 5.0. :Datasets: - **names** (*char[][]*) -- Names of nuclides. - **awrs** (*float[]*) -- Atomic weight ratio of each nuclide. +**/macroscopics/** + +:Attributes: - **n_macroscopics** (*int*) -- Number of macroscopic data sets + in the problem. + +:Datasets: - **names** (*char[][]*) -- Names of the macroscopic data sets. + **/tallies/tally /** :Datasets: - **name** (*char[]*) -- Name of the tally. diff --git a/docs/source/usersguide/tallies.rst b/docs/source/usersguide/tallies.rst index e0f8ab179..19691e32b 100644 --- a/docs/source/usersguide/tallies.rst +++ b/docs/source/usersguide/tallies.rst @@ -21,7 +21,12 @@ region of phase space, as in: Thus, to specify a tally, we need to specify what regions of phase space should be included when deciding whether to score an event as well as what the scoring function (:math:`f` in the above equation) should be used. The regions of phase -space are called *filters* and the scoring functions are simply called *scores*. +space are generally called *filters* and the scoring functions are simply +called *scores*. + +The only cases when *filters* do not correspond directly with the regions of +phase space are when expansion functions are applied in the integrand, such as +for Legendre expansions of the scattering kernel. ------- Filters @@ -69,10 +74,9 @@ Scores ------ To specify the scoring functions, a list of strings needs to be given to the -:attr:`Tally.scores` attribute. You can score the flux ('flux'), a reaction rate -('total', 'fission', etc.), or even scattering moments (e.g., 'scatter-P3'). For -example, to tally the elastic scattering rate and the fission neutron -production, you'd assign:: +:attr:`Tally.scores` attribute. You can score the flux ('flux'), or a reaction +rate ('total', 'fission', etc.). For example, to tally the elastic scattering +rate and the fission neutron production, you'd assign:: tally.scores = ['elastic', 'nu-fission'] @@ -98,12 +102,6 @@ The following tables show all valid scores: +======================+===================================================+ |flux |Total flux. | +----------------------+---------------------------------------------------+ - |flux-YN |Spherical harmonic expansion of the direction of | - | |motion :math:`\left(\Omega\right)` of the total | - | |flux. This score will tally all of the harmonic | - | |moments of order 0 to N. N must be between 0 and | - | |10. | - +----------------------+---------------------------------------------------+ .. table:: **Reaction scores: units are reactions per source particle.** @@ -118,43 +116,10 @@ The following tables show all valid scores: +----------------------+---------------------------------------------------+ |fission |Total fission reaction rate. | +----------------------+---------------------------------------------------+ - |scatter |Total scattering rate. Can also be identified with | - | |the "scatter-0" response type. | - +----------------------+---------------------------------------------------+ - |scatter-N |Tally the N\ :sup:`th` \ scattering moment, where N| - | |is the Legendre expansion order of the change in | - | |particle angle :math:`\left(\mu\right)`. N must be | - | |between 0 and 10. As an example, tallying the 2\ | - | |:sup:`nd` \ scattering moment would be specified as| - | |``scatter-2``. | - +----------------------+---------------------------------------------------+ - |scatter-PN |Tally all of the scattering moments from order 0 to| - | |N, where N is the Legendre expansion order of the | - | |change in particle angle | - | |:math:`\left(\mu\right)`. That is, "scatter-P1" is | - | |equivalent to requesting tallies of "scatter-0" and| - | |"scatter-1". Like for "scatter-N", N must be | - | |between 0 and 10. As an example, tallying up to the| - | |2\ :sup:`nd` \ scattering moment would be specified| - | |as `` scatter-P2 ``. | - +----------------------+---------------------------------------------------+ - |scatter-YN |"scatter-YN" is similar to "scatter-PN" except an | - | |additional expansion is performed for the incoming | - | |particle direction :math:`\left(\Omega\right)` | - | |using the real spherical harmonics. This is useful| - | |for performing angular flux moment weighting of the| - | |scattering moments. Like "scatter-PN", "scatter-YN"| - | |will tally all of the moments from order 0 to N; N | - | |again must be between 0 and 10. | + |scatter |Total scattering rate. | +----------------------+---------------------------------------------------+ |total |Total reaction rate. | +----------------------+---------------------------------------------------+ - |total-YN |The total reaction rate expanded via spherical | - | |harmonics about the direction of motion of the | - | |neutron, :math:`\Omega`. This score will tally all | - | |of the harmonic moments of order 0 to N. N must be| - | |between 0 and 10. | - +----------------------+---------------------------------------------------+ |(n,2nd) |(n,2nd) reaction rate. | +----------------------+---------------------------------------------------+ |(n,2n) |(n,2n) reaction rate. | @@ -248,10 +213,10 @@ The following tables show all valid scores: +----------------------+---------------------------------------------------+ |nu-fission |Total production of neutrons due to fission. | +----------------------+---------------------------------------------------+ - |nu-scatter, |These scores are similar in functionality to their | - |nu-scatter-N, |``scatter*`` equivalents except the total | - |nu-scatter-PN, |production of neutrons due to scattering is scored | - |nu-scatter-YN |vice simply the scattering rate. This accounts for | + |nu-scatter, |This score is similar in functionality to the | + | |``scatter`` score except the total production of | + | |neutrons due to scattering is scored vice simply | + | |the scattering rate. This accounts for | | |multiplicity from (n,2n), (n,3n), and (n,4n) | | |reactions. | +----------------------+---------------------------------------------------+ diff --git a/examples/jupyter/mg-mode-part-ii.ipynb b/examples/jupyter/mg-mode-part-ii.ipynb index 7a2b029de..6380c32b0 100644 --- a/examples/jupyter/mg-mode-part-ii.ipynb +++ b/examples/jupyter/mg-mode-part-ii.ipynb @@ -26,9 +26,7 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", @@ -50,9 +48,7 @@ { "cell_type": "code", "execution_count": 2, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# 1.6% enriched fuel\n", @@ -84,9 +80,7 @@ { "cell_type": "code", "execution_count": 3, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a Materials object\n", @@ -106,9 +100,7 @@ { "cell_type": "code", "execution_count": 4, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Create cylinders for the fuel and clad\n", @@ -136,9 +128,7 @@ { "cell_type": "code", "execution_count": 5, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a Universe to encapsulate a fuel pin\n", @@ -173,9 +163,7 @@ { "cell_type": "code", "execution_count": 6, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create a Universe to encapsulate a control rod guide tube\n", @@ -210,9 +198,7 @@ { "cell_type": "code", "execution_count": 7, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create fuel assembly Lattice\n", @@ -231,9 +217,7 @@ { "cell_type": "code", "execution_count": 8, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Create array indices for guide tube locations in lattice\n", @@ -263,9 +247,7 @@ { "cell_type": "code", "execution_count": 9, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create root Cell\n", @@ -290,15 +272,23 @@ { "cell_type": "code", "execution_count": 10, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", "text/plain": [ - "" + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" ] }, "metadata": {}, @@ -321,9 +311,7 @@ { "cell_type": "code", "execution_count": 11, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create Geometry and set root universe\n", @@ -343,15 +331,13 @@ { "cell_type": "code", "execution_count": 12, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# OpenMC simulation parameters\n", "batches = 600\n", "inactive = 50\n", - "particles = 2000\n", + "particles = 3000\n", "\n", "# Instantiate a Settings object\n", "settings_file = openmc.Settings()\n", @@ -383,9 +369,7 @@ { "cell_type": "code", "execution_count": 13, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a 2-group EnergyGroups object\n", @@ -402,9 +386,7 @@ { "cell_type": "code", "execution_count": 14, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Initialize a 2-group MGXS Library for OpenMC\n", @@ -424,9 +406,7 @@ { "cell_type": "code", "execution_count": 15, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Specify multi-group cross section types to compute\n", @@ -446,9 +426,7 @@ { "cell_type": "code", "execution_count": 16, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Specify a \"cell\" domain type for the cross section tally filters\n", @@ -470,9 +448,7 @@ { "cell_type": "code", "execution_count": 17, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Do not compute cross sections on a nuclide-by-nuclide basis\n", @@ -489,9 +465,7 @@ { "cell_type": "code", "execution_count": 18, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stderr", @@ -519,9 +493,7 @@ { "cell_type": "code", "execution_count": 19, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Check the library - if no errors are raised, then the library is satisfactory.\n", @@ -538,9 +510,7 @@ { "cell_type": "code", "execution_count": 20, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Construct all tallies needed for the multi-group cross section library\n", @@ -559,9 +529,7 @@ { "cell_type": "code", "execution_count": 21, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Create a \"tallies.xml\" file for the MGXS Library\n", @@ -579,10 +547,21 @@ { "cell_type": "code", "execution_count": 22, - "metadata": { - "collapsed": true - }, - "outputs": [], + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=66.\n", + " warn(msg, IDWarning)\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.\n", + " warn(msg, IDWarning)\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=11.\n", + " warn(msg, IDWarning)\n" + ] + } + ], "source": [ "# Instantiate a tally Mesh\n", "mesh = openmc.Mesh()\n", @@ -618,9 +597,7 @@ { "cell_type": "code", "execution_count": 23, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -652,11 +629,11 @@ " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2017 Massachusetts Institute of Technology\n", + " Copyright | 2011-2018 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | 4b01fd311461f1350989cb84ec18fe2cbaa8fa9f\n", - " Date/Time | 2017-03-10 17:30:51\n", + " Version | 0.10.0\n", + " Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416\n", + " Date/Time | 2018-04-22 15:02:43\n", " OpenMP Threads | 8\n", "\n", "\n", @@ -665,23 +642,13 @@ "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 1.16584 +/- 0.00111\n", - " k-effective (Track-length) = 1.16532 +/- 0.00131\n", - " k-effective (Absorption) = 1.16513 +/- 0.00100\n", - " Combined k-effective = 1.16538 +/- 0.00086\n", + " k-effective (Collision) = 1.16513 +/- 0.00090\n", + " k-effective (Track-length) = 1.16337 +/- 0.00104\n", + " k-effective (Absorption) = 1.16479 +/- 0.00080\n", + " Combined k-effective = 1.16460 +/- 0.00068\n", " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -699,9 +666,7 @@ { "cell_type": "code", "execution_count": 24, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Move the statepoint File\n", @@ -724,9 +689,7 @@ { "cell_type": "code", "execution_count": 25, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Load the statepoint file\n", @@ -747,9 +710,7 @@ { "cell_type": "code", "execution_count": 26, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Initialize MGXS Library with OpenMC statepoint data\n", @@ -781,23 +742,8 @@ { "cell_type": "code", "execution_count": 27, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/nelsonag/git/openmc/openmc/tallies.py:1834: 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:1835: 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:1836: RuntimeWarning: invalid value encountered in true_divide\n", - " new_tally._mean = data['self']['mean'] / data['other']['mean']\n" - ] - } - ], + "metadata": {}, + "outputs": [], "source": [ "# Create a MGXS File which can then be written to disk\n", "mgxs_file = mgxs_lib.create_mg_library(xs_type='macro', xsdata_names=['fuel', 'zircaloy', 'water'])\n", @@ -820,24 +766,35 @@ { "cell_type": "code", "execution_count": 28, - "metadata": { - "collapsed": false - }, - "outputs": [], + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Material instance already exists with id=1.\n", + " warn(msg, IDWarning)\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Material instance already exists with id=2.\n", + " warn(msg, IDWarning)\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Material instance already exists with id=3.\n", + " warn(msg, IDWarning)\n" + ] + } + ], "source": [ "# Re-define our materials to use the multi-group macroscopic data\n", "# instead of the continuous-energy data.\n", "\n", "# 1.6% enriched fuel UO2\n", - "fuel_mg = openmc.Material(name='UO2')\n", + "fuel_mg = openmc.Material(name='UO2', material_id=1)\n", "fuel_mg.add_macroscopic('fuel')\n", "\n", "# cladding\n", - "zircaloy_mg = openmc.Material(name='Clad')\n", + "zircaloy_mg = openmc.Material(name='Clad', material_id=2)\n", "zircaloy_mg.add_macroscopic('zircaloy')\n", "\n", "# moderator\n", - "water_mg = openmc.Material(name='Water')\n", + "water_mg = openmc.Material(name='Water', material_id=3)\n", "water_mg.add_macroscopic('water')\n", "\n", "# Finally, instantiate our Materials object\n", @@ -868,9 +825,7 @@ { "cell_type": "code", "execution_count": 29, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Set the energy mode\n", @@ -890,9 +845,7 @@ { "cell_type": "code", "execution_count": 30, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Create a \"tallies.xml\" file for the MGXS Library\n", @@ -902,7 +855,7 @@ "mesh_tally = openmc.Tally(name='mesh tally')\n", "mesh_tally.filters = [openmc.MeshFilter(mesh)]\n", "mesh_tally.scores = ['fission']\n", - "tallies_file.add_tally(mesh_tally)\n", + "tallies_file.append(mesh_tally)\n", "\n", "# Export to \"tallies.xml\"\n", "tallies_file.export_to_xml()" @@ -919,15 +872,13 @@ { "cell_type": "code", "execution_count": 31, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -935,9 +886,9 @@ }, { "data": { - "image/png": "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\nRU5ErkJggg==\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -945,9 +896,9 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -991,7 +942,6 @@ "cell_type": "code", "execution_count": 32, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [ @@ -1025,11 +975,11 @@ " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2017 Massachusetts Institute of Technology\n", + " Copyright | 2011-2018 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | 4b01fd311461f1350989cb84ec18fe2cbaa8fa9f\n", - " Date/Time | 2017-03-10 17:31:49\n", + " Version | 0.10.0\n", + " Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416\n", + " Date/Time | 2018-04-22 15:04:03\n", " OpenMP Threads | 8\n", "\n", "\n", @@ -1038,23 +988,13 @@ "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 1.16235 +/- 0.00111\n", - " k-effective (Track-length) = 1.16345 +/- 0.00134\n", - " k-effective (Absorption) = 1.16397 +/- 0.00058\n", - " Combined k-effective = 1.16388 +/- 0.00058\n", + " k-effective (Collision) = 1.16541 +/- 0.00086\n", + " k-effective (Track-length) = 1.16590 +/- 0.00096\n", + " k-effective (Absorption) = 1.16469 +/- 0.00046\n", + " Combined k-effective = 1.16480 +/- 0.00045\n", " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -1075,9 +1015,7 @@ { "cell_type": "code", "execution_count": 33, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Move the StatePoint File\n", @@ -1108,9 +1046,7 @@ { "cell_type": "code", "execution_count": 34, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "ce_keff = sp.k_combined" @@ -1126,26 +1062,24 @@ { "cell_type": "code", "execution_count": 35, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Continuous-Energy keff = 1.165379\n", - "Multi-Group keff = 1.163885\n", - "bias [pcm]: 149.4\n" + "Continuous-Energy keff = 1.164600+/-0.000677\n", + "Multi-Group keff = 1.164805+/-0.000448\n", + "bias [pcm]: -20.4\n" ] } ], "source": [ - "bias = 1.0E5 * (ce_keff[0] - mg_keff[0])\n", + "bias = 1.0E5 * (ce_keff - mg_keff)\n", "\n", - "print('Continuous-Energy keff = {0:1.6f}'.format(ce_keff[0]))\n", - "print('Multi-Group keff = {0:1.6f}'.format(mg_keff[0]))\n", - "print('bias [pcm]: {0:1.1f}'.format(bias))" + "print('Continuous-Energy keff = {0:1.6f}'.format(ce_keff))\n", + "print('Multi-Group keff = {0:1.6f}'.format(mg_keff))\n", + "print('bias [pcm]: {0:1.1f}'.format(bias.nominal_value))" ] }, { @@ -1174,9 +1108,7 @@ { "cell_type": "code", "execution_count": 36, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Get the OpenMC fission rate mesh tally data\n", @@ -1200,9 +1132,7 @@ { "cell_type": "code", "execution_count": 37, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Get the OpenMC fission rate mesh tally data\n", @@ -1226,14 +1156,12 @@ { "cell_type": "code", "execution_count": 38, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 38, @@ -1242,9 +1170,9 @@ }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAX4AAADHCAYAAAAeaDj1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAIABJREFUeJztnXm8VWX1/99LZB4EZBIF0USTLCivU1LihEoqaupXE9PUkH7RV79l5ldLycoG9VsYFpEppqllhmJagloOFSYSCE5BCjLIoCgzKLh+f+x95XA45zwP95x777l3f96v133dc/b+7Getvffa6+xpPY+5O0IIIbLDTo3tgBBCiIZFiV8IITKGEr8QQmQMJX4hhMgYSvxCCJExlPiFECJjNKnEb2YvmNmQxvYjy5jZn8zsvDKWH29m36qkT1nDzNzM9ikxv9keJ4q/CuHuZf0BnwOmA2uBN4A/AYMr0O5E4LvlttOYf+k6vJtum9q/WY3tV4TfY4D38vy+vLH92gGf3wH+Dhy2A8v/Fbionn2cn8ZDt7zp/wIc6BfZjgP75MTYDh0nQCvgauAVYB2wOD1uhzb2fiywLxV/9fBX1hm/mX0V+AlwHdAT6Av8DBheTrvNjB+5e4ecv4GVNmBmO1e6TeC3eX7/qB5sVJrfunsHoBvwF+DeRvanEK8BZ9d+MbOPAu0a2Iffkxyjnwe6AHsBY4HPFBLXU3yFUPzVJ2X8wu1C8ut2RglNa5IfhiXp30+A1um8IcAi4GvAcpKrhS+k80aS/HrWni0/mE6fDxyT8wv7O+DXwBrgBaAmx/YHZ0Xp94nknBkBXwTmASuByUDvdHq/dNmdC/0aA/sATwCrgDdJdnax9d/GZt68WjvnAa+nbV2VM38n4ArgP8Bb6bp2zVv2wnTZJ9PpnwcWpPpv1W4voBewHtg1p/1PACuAlkXOXu4MnZkU2xaAAT9O9+tqYDZwwI7sh5x9OAqYS3IWdTNgJc647sz5PiBdvnv6vQvwx3Sd304/75HO+x6wBdhIEm/j0ukfBqamvr0CnJnT/jDgRZLYWwxcFnHMzAe+CTybM+0G4CpyzvjJO/sDzgeezo9tIo6TAj4cA2yoXfeAr98Angc2ATsD+6e+vUNyvJ1cKC5K+PzfwKtprFwP7BSzLxV/lYm/3L9yzvgPA9oAk0porgIOBQYBA4GDSQK/ll4kPyC7kySxm82si7tPAH7D1rPlk4q0fzJwD9CZZKeNi3HczI4Cvg+cCexGkizviVkW+A4whWRH7gH8NHK5YgwG9gOOBq42s/3T6V8BTgGOAHqTBMvNecseQXIwHmdmA0iuts4hWafa7Yq7LyU5YM7MWfZc4B53f68M34tti6HAp4F9Uz/OJPkx2obI/XAicBDwsVR3XMgpM2tF8iP4Fsl2g+SH9DZgT5Ir0w2k8eLuVwFPAaPTeBttZu1JDrq7gB7AWcDP0u0M8CvgYnfvCBwAPB7yK2Ua0MnM9jezFmm7d0Yuuw07cJzkcgzwjLsvitCeTXIV0JkkmT5Isr97kMTnb8xsvx1w+VSghuSkYzhwwQ4sWwjF347H3wfO1JVdgTfdfXMJzTnAte6+3N1XAN8mSTi1vJfOf8/dHyb5tduRQHra3R929y3AHSQ/LjGcA9zq7jPcfRPwv8BhZtYvYtn3SHZeb3ff6O5PB/SXmdk7OX+3583/trtvcPdZwKycdRhFcgWwKPVxDHB63mX3GHdf5+4bgNNJzviedvd3Se7h5nbEdDswAiBNOGeTbLNinJnnd+8d2BbvAR1JzljM3V9y9zcKLB+zH37g7u+4++skl8+DQj6THFRfBE6vjU93f8vd73P39e6+huQs64gSbZ0IzHf329x9s7v/C7gPOCNnHQeYWSd3f9vdZ5RoK587SBLDscBLJGdsDUU3YGntFzPrmu7fVWa2MU97k7svTOPrUKADyf54190fJzlrPZt4fujuK9N9+ZPAsoq/+ou/shL/W0C3wP2/3iS/orUsSKd90EbeD8d6kuCKZWnO5/VAm8j7kdv45e5rSdZn94hlLyc5+/ln+vbEBQBmdqWZrU3/xufob3D3zjl/+W8k5K9D7frvCUyqDXySBLGF5FlKLQvz1umD7+6+nm3Pch4gCZS9SBLOKnf/Z4n1/F2e30tit0WaFMaRXKEsN7MJZtapwPIx+6HY9inqM8k2mgMcWDvDzNqZ2S/MbIGZrQaeBDqnP4KF2BM4JDf5kCSKXun8z5Jcbi8wsyfM7LASfuVzB8lLEeeT3KqsN3Jicq2Z9SXZvrvVzk8TcWeSbdU6b/Ht4svd38+ZtoC4Y6ZQe/m5IB/FX/3FX1mJ/x8k9/5OKaFZQrICtfRNp8VQbreh69n2oVmvnM/b+JVeVu1Kcua1Lp1ccFl3X+ruX3T33sDFJJdf+7j7db71QdSoMn2H5CA5IS/427h77tlh7jZ6g+Ryt3ad2qbrVOv3RpLnBCNIrrpKne1HUWxbpPNucvcDSe517gt8vUATpfZDOX69SXL/e4yZ1Sa5r5FcTR7i7p1IbgVAkjhg+3hbCDyRt/07uPuXUhvPuvtwksvw+0m2bax/C0ge8g4D/lBAso7isbtdcwFbuQ9IXwceAw4ysz1KLVeg7SVAHzPLzRl92bqvYnzuk7dsbC4o7Jzir07xB2UkfndfRXI74WYzOyX9RWtpZieYWe0T+LuBb5pZdzPrlupj72cuA/auq3/ATOBzZtbCzI5n28uqu4EvmNkgM2tN8lbSM+4+35NbUouBEemyFwAfql3QzM7IOWjeJtlhuWdBlWI88D0z2zO1293MSr0t9XvgJDP7ZHqPcQxbg6qWX5OcZZ5MBRJ/sW1hZgeZ2SFm1pIkIWyk8DYquh/K9c3dXwEeITkrhOTSfwPwjpl1Ba7JWyQ/3v4I7Gtm56Zx3TJdr/3NrJWZnWNmu3jyjGR1kfUrxYXAUe6+rsC8mcBp6TG1T6otxg4dJ+4+heSWxf3pPmqV7qdDA4s+Q3IydXm6LYYAJ7H1nniMz183sy5m1ge4BPhtrN+FUPzVPf7Kep3T3W8EvkrywHYFya/UaJJfIIDvkrzj/zzJk/UZ6bQYfkVya+IdM7s/qN6eS0gCs/YS6YM23P1Rkrde7iM5U/4QycOTWr5IcobwFvARkndyazkIeMbM1pI8UL7E3V8t4cfleZfbb0b6PzZtf4qZrSF5KHhIMbG7v0DywO2edJ3WkrzVsClH8zeSAJmRnnWWS7Ft0Qn4JcnBWPuW0fUFfA7th3K5HhhpZj1I7im3JXn7Yxrw5zztWJJnKG+b2U3pfdihqT9LSC75f8jW2yHnAvPTy/ZRJDEWjbv/x92nF5n9Y5I3dZaRPJv5TYmm6nKcnEqSWO4kOT5eI/G/6INLT54bnQScQLINfwZ83t1f3gGfHwCeI/mReCj1vRwUf3WMP3PXQCzNETPrQHJQ93f313KmPw7c5e63NJpzInOYmZPE4rzG9kU0sS4bRGnM7KT0Urs9yfvhs0nex66dfxDJq3RlXWILIZo2SvzNi+FsLZbrD5zl6SWdJa+RPgpcml5GCiEyim71CCFExtAZvxBCZAwlfiGEyBiN0eteELP2nnS/UYqY11YLFevl0TOiUDi/nrEA7buHb5vvFOHzmlW7hI21DEtYH6HZEKFpH6F5J0ITc0fxnQhRy/zShAIEex+aj/ubEQ1VFmvVzWnbr7Qo5ulLqwhNRD1tmy7hIOnA2qBmNR2DmlbhncIWihWxbuXdiJW3iOPMPXzOaxZuZ/PrEclhRVgSR6necQAW4v5WVFyXlfjTwqixQAvgFnf/Qd781iRFQweSvEv7X3HFEV1IXkkvRUzWOiYsOXdwWFN0yIutfOzicB9JbSOy8eMPnRg2VqqOs5aZEZo5EZqaCE3M2+OhmAW4P6K/uJ4Rv3qLQj8gBwWbqJfYbtsPPlns1f2UvwZdy6nPLkFEtcxe/xXu3uVTPBXUPBpxnPWOKNJdE/EDsnBLn6CmVYtNQc2GTeGesNu2Dh+vS78SUTsX1XVkDNv1M5fH0dEt1flWT9rHxM0kBR0DgLNta89xtVwIvO3u+5AUePywrvaEaCgU26K5U849/oOBee7+alrVdw/bD8AynKSKD5IuBY42swa/xBZiB1Fsi2ZNOYl/d7btbW8R299Z/ECT9sK5ipyOw3Ixs5FmNt3Mpm/tJ02IRqFisb1NXL9bsZu9QpRF1bzV4+4T3L3G3WvinigKUf1sE9etuje2O0IA5SX+xWzbzeoebN+d6QcaS/rJ34XwEwohGhvFtmjWlJP4nwX6m9leaTfAZ5H0kJfLZJIxZSEZIepxV6mwqH4U26JZU+fXOd19s5mNJulzugXJEGYvmNm1wHR3n0zS7eodZlY7mHFkl6e92NqNdRFOj3iO1i/C1OlhycGHPBHU/KjgOA/b8h2uDmoO+MyzQc3tH+Sb4lx74LeCmi0Ru/8qvhfUHH7M34Ka95dG3L6bGfGqZlSn1rMD80u/ClxvsW2Ej7hSA/ulDPzHtKBmH8KdYH6La4OajhGFBe0iXlPel1cibIVrBma3+GhQc9Oq0KvgsGF5wUeN2/BA/6FBzW0/PT/czrSI0Slj4np+yOf4dF7We/yejJP7cN60q3M+b2TrGJFCNBkU26I5UzUPd4UQQjQMSvxCCJExlPiFECJjKPELIUTGUOIXQoiMocQvhBAZQ4lfCCEyRlUOxJIQKNDaGNHEpWHRg7t/Jqg58eVwX/t8OFy0+cjCiKKzmPXqH7Z1PeF+y/s/vihs66iwrS0R67WgJtxPzd7TXgtq3r8iohDsnY+Vnv/XtuE26oGW+2yi54NzS2oWPdY/2M7n+XVQ89V7fx7UrP9CUEK7teH9/+PrIuK6b1jCiIjC50lhW0ee+pdwO7uEbQ3/UdjWsIunBDWt7j817M/GiAFdRgT8CdUt5qAzfiGEyBhK/EIIkTGU+IUQImMo8QshRMZQ4hdCiIxRzmDrfczsL2b2opm9YGaXFNAMMbNVZjYz/Qv3SyxEI6PYFs2dcl7n3Ax8zd1nmFlH4Dkzm+ruL+bpnnL3E8uwI0RDo9gWzZo6n/G7+xvuPiP9vAZ4ie0HpBaiyaHYFs2dihRwmVk/4OPAMwVmH2Zms4AlwGXu/kKRNkYCIwFo1xeGlba507h1Qb/m9twnqNmbN4IabowoUOkRoRkVlmzsHda0GRu21T+w/QCmHTUwqDn04xHr9eWwZM+a5UHN2J4XBTVfGXxL2Fi/wPz88/YSlBvbuXHdom945445+htBTUxxFmeEC5QWnxkRR0dH7P8zwxIOiNAcGmErYjCr4/8WHjEv6pgODyxHy8vD29m/E7b10Rv+GdTMOfGg0oLXg018QNkPd82sA3AfcKm7r86bPQPY090HAj8F7i/WjrtPcPcad6+hdbjKU4j6phKxnRvXO3XvWr8OCxFJWYnfzFqSHBi/cfc/5M9399Xuvjb9/DDQ0sy6lWNTiIZAsS2aM+W81WMkA06/5O7/V0TTK9VhZgen9t6qq00hGgLFtmjulHOP/3DgXGC2mc1Mp11J2h2Tu48HTge+ZGabgQ3AWe4e0ROTEI2KYls0a+qc+N39aQJdaLr7OGBcXW0I0RgotkVzR5W7QgiRMZT4hRAiYyjxCyFExqjOEbi6AYFant49lwSb2fvupWFb/xdRyHFUWMKqCM0jYUmbiAGmCNeuwcqw5NAps8KiiIGBmBihuSq8nUdHjCD06IVHBzUPPBeo8mkRtlMfvO87sWZTx5Ka01v/PtzQHRHGIrb3SxHN7Bwx+Nxe+0c0NChCE67xi9KsPLxNUNP16oih7o4IS26y8Hb+7/HhdvbjlaBmwFWlKw+nTop/qUxn/EIIkTGU+IUQImMo8QshRMZQ4hdCiIyhxC+EEBlDiV8IITKGEr8QQmQMJX4hhMgYVVnA1abTOvY57tmSmgc4OdzQ2RGdJd4bUcD1cFjC7Ahbt0TYGhtha1qErcExo4ZVxtb6DmFb7WKKfHqEbe3F98PthGqg3o7wpR7oZKs5tnXpKr6PzHk13NDk8HZaFlFYFLNL9orocPSZCFuHzIkw9mpEXI8K2+o6KqI467HKHEMXxRRcXhy29fvJEaN0nVx6lK4NhAvXaqnECFzzzWy2mc00s+kF5puZ3WRm88zseTP7RLk2hahvFNeiOVOpM/4j3f3NIvNOAPqnf4cAP0//C1HtKK5Fs6Qh7vEPB37tCdOAzma2WwPYFaI+UVyLJkslEr8DU8zsOTMbWWD+7sDCnO+L0mlCVDOKa9FsqcStnsHuvtjMegBTzexld39yRxtJD66RAC379qqAW0KURcXjul3fXSvtoxB1ouwzfndfnP5fDkwCDs6TLAb65HzfI52W384Ed69x95oW3TuX65YQZVEfcd26e+kumYVoKMpK/GbW3sw61n4GhgL5L25NBj6fvgVxKLDK3d8ox64Q9YniWjR3yr3V0xOYZMl7vDsDd7n7n81sFIC7jyd5C34YMA9YD3yhTJtC1DeKa9GsKSvxu/urwMAC08fnfHbgyzvS7sZ32zFnQekhe07c86FgOy9eVqGCqbkRmtFhW7eOC4wMBVzQ4+6wrZjirFFhCb0jNPtGFGddHdFOeNUhYn8tvCFi+Kn5gfmbSs+ur7h2jC2B4b8eP+CwYDtHjQ1vp543hP3pGTGa1ZiI4qwxHw63s81NsWKcFrY16w/9g5qBS8IHbFTR4R+CEtrFDGP2hbCtu28bHtT02eZdgu15nfcinElQlw1CCJExlPiFECJjKPELIUTGUOIXQoiMocQvhBAZQ4lfCCEyhhK/EEJkDCV+IYTIGFU5AtfOrd6jy56lq9/P57ZwQ6eGJbP6RBSELIuo4HotLLng5YjirI+HJRwaoVkVoSldI5fwywhNoCAK4N4+JwY1Z5z1x6DmNfqFjd0QGIHpxYjRl+qBdqxnEDNLao5a+I9wQxEjp607LXxO1/4b7wc1Y2JGGDg6QrMuQrNLWDJwbMSxGLF9Nm8Oa1YODY9o9e+h+wU1g9bNCmpmRhz4f3rmtNKCddcF26hFZ/xCCJExlPiFECJjKPELIUTGUOIXQoiMocQvhBAZo86J38z2M7OZOX+rzezSPM0QM1uVo4npwFeIRkWxLZo7dX6d091fIX0h0MxakAw7N6mA9Cl3D7/LJ0SVoNgWzZ1K3eo5GviPuy+oUHtCVAuKbdHsqFQB11lAseqkw8xsFrAEuMzdXygkMrORwEgA67MHG9a1LWmwT/vSo9EAcHi4UGdgxMhZUTwUURQUY2tyhK3XI2x9PMLW/hG2joqwdXfY1hlDwsVZ/DVs63o+GdSctfs9Jee/3TJ+pCLKjO3cuO7QtzPLQtVFwyI8mh3eTgsiRs4acHiErWlhW1MjbMWE2h4etvVahK29IsZF67QxbOu9zmFbh94QLs7iorCtHz4etvWzQ/5fyfnr20ZUpaWUfcZvZq2Ak4F7C8yeAezp7gOBnwL3F2vH3Se4e42711i3Xct1S4iyqURs58Z12+7t689ZIXaAStzqOQGY4e7L8me4+2p3X5t+fhhoaWbdKmBTiIZAsS2aJZVI/GdT5FLYzHqZJddmZnZwau+tCtgUoiFQbItmSVn3+M2sPXAscHHOtFEA7j4eOB34kpltBjYAZ7lH3MgTopFRbIvmTFmJ393XAbvmTRuf83kcMK4cG0I0Bopt0ZxR5a4QQmQMJX4hhMgYSvxCCJExqnIELjOnxc5bSmq2xLh+c7goYsW4DkHNGsKavW8M23pi3MFBzRE1/wxq5kYUsfR/ICiB2RGaayMKwa6MaOeMCM3CsK1WfT4R1Kx4rG9pwZpWEc5UnjZsYj/+XVKzYnY41rp/LqI46+mwP7MOjxh97rKwrWOnhW1FxdpVEcVZMT0ixdQx7R+21fLFiHZejtBcF7Z1/pU/C2oO5LmS85/baX2EMwk64xdCiIyhxC+EEBlDiV8IITKGEr8QQmQMJX4hhMgYSvxCCJExlPiFECJjKPELIUTGqMoCro62hqNbP1pSsx+vhBvqGpZ0XrU27M/OYQ0rw5Ij5oaLs1acHy7g6f9whD+bwhLahCVznwhr+g8Na8ZdeWFQM/q6XwU1D155crido39Ucv5vOy4NtlEfdGQNn+LJkpol7BZsp/u/5oaNRUgGvh4hihkR7tMRmpiRxZ6J0GyM0KyL0MQcHzHFWb0rozmcvwc1b1F6uIcWlC56zSXqjN/MbjWz5WY2J2daVzObamZz0/9diix7XqqZa2bnRXsmRD2juBZZJfZWz0Tg+LxpVwCPuXt/4LH0+zaYWVfgGuAQ4GDgmmIHkhCNwEQU1yKDRCV+d3+S7W9mDAduTz/fDpxSYNHjgKnuvtLd3wamsv2BJkSjoLgWWaWch7s93f2N9PNSoGcBze7Awpzvi9JpQlQrimvR7KnIWz3pkHNlDTtnZiPNbLqZTd+0YnUl3BKiLCod12+viH/4JkR9Uk7iX2ZmuwGk/5cX0CwG+uR83yOdth3uPsHda9y9pnX3TmW4JURZ1Ftcd+neouLOClEXykn8k4HatxnOAwr1AP8IMNTMuqQPv4am04SoVhTXotkT+zrn3cA/gP3MbJGZXQj8ADjWzOYCx6TfMbMaM7sFwN1XAt8Bnk3/rk2nCdHoKK5FVrHkNmZ10a5mf993+m0lNdPWHRZsp037iHW7JmKEqZgRhh6JsDUxwlb7CFtnhG2tbhO21Smi8IrJYVt3RowINiKm0GVx2NatfC6oufCRu0oLvlKD/3t6xM6oLLvW9PNh068qqbnjMyPDDT0UEWvHRaze/mEJPwnb8l3DtixmlLavRazXyRXabRFxzeAIW6MibI2I2IYrI47X1oXuOm5l/aeOZcuMmVEbSF02CCFExlDiF0KIjKHEL4QQGUOJXwghMoYSvxBCZAwlfiGEyBhK/EIIkTGU+IUQImNU5QhcGxa1Z9bXDy2pueT6nwTb+cXnImoZXo9waK8ITUSxx8YKFfW3uTFsa/3GXYKaTlNWhY1FbMMRj4Wb8TPCmvWbwn3ZXNU6YtSoeYH5MaM41QPraM8zHFxSc+dDnw22MyJinzw3JezPgT3CGvaNKM46LaKdwRGaSWFbCyZ3D2p6r1oR1LS8NCI3nBqWsCRC842wrfE/DI/lM4AXS86fs9OGCGcSdMYvhBAZQ4lfCCEyhhK/EEJkDCV+IYTIGEr8QgiRMYKJ38xuNbPlZjYnZ9r1ZvaymT1vZpPMrHORZeeb2Wwzm2lm0yvpuBDlotgWWSXmjH8icHzetKnAAe7+MeDfwP+WWP5Idx/k7jV1c1GIemMiim2RQYKJ392fBFbmTZvi7pvTr9NIxhwVokmh2BZZpRIFXBcAvy0yz4EpZubAL9x9QrFGzGwkkAw/1KEvrC1t9Nerzg06dvpdvw9qjuWpoGZMxAhT4fILWN2+f1Czho5BzeCVM4KaXk9EFGf1DUuIqJfi6rDE3gqPQtT+mfB2Xrpx77CxewLz3w43kVJ2bG8T1237MvekgSUNbnkwYkD2Q8KSA+8Kb++fR8T1qK5hWzFDPm08IKxp872w5t1TWwc1LUsPVJUQU0y5a4Tm6fB2foDjgppL3ropqHnv/k6lBW+Gc0ctZSV+M7sK2Az8pohksLsvNrMewFQzezk9y9qO9MCZAGA9aqpvPEiRKSoV29vEdWfFtagO6vxWj5mdD5wInONFBu5198Xp/+XAJAjUqwtRBSi2RXOnTonfzI4HLgdOdvf1RTTtzaxj7WdgKDCnkFaIakGxLbJAzOucdwP/APYzs0VmdiEwDuhIcok708zGp9reZvZwumhP4GkzmwX8E3jI3f9cL2shRB1QbIusErzH7+5nF5j8qyLaJcCw9POrQOknWUI0IoptkVVUuSuEEBlDiV8IITKGEr8QQmSMqhyBiw3Ay6UlG28JV5acNuoPQc2aiFF/xgwLSuChiFe0r40odYkp/v9ehK0nImz9K8LWsxG2bomwNTas+Z9Lrgu38+2whG6B+Y0V9W2BQCHT+UcWqxfbyry/fCio+c7M8Pb+0lFBCTxWmbhuMyrC1uSwrf4fj4i18RG2XqrMeq2LGDXu663DBZfvDQkUZwEMCcwv+A5aYXTGL4QQGUOJXwghMoYSvxBCZAwlfiGEyBhK/EIIkTGU+IUQImMo8QshRMZQ4hdCiIxhRbobb1TMDnD4XWnRoQPCDW0MSz7xr6eDmiP5a1Dz3XXfCmomtR8e1CynZ1BzDI8GNT/giqBmC+Hik4v5RVBzB+HR0H518+ighjfDEiZGaOaHRN/G/bWYgaMqirWucXoHxmWfHzHk2aXhkdxiCgGfOufAoGbw2HDx0auX9Apq/s7hQc2Iv90X1Hz/8EuDmou4Jahptylc7XRD68uCmjGLIyoK728T1kQcHjAmMH8C7kui4jqmW+ZbzWy5mc3JmTbGzBan3dbONLOCta1mdryZvWJm88wsnImEaEAU2yKrxNzqmQgcX2D6j919UPr3cP5MM2sB3AycAAwAzjaziNN0IRqMiSi2RQYJJv50HNGVdWj7YGCeu7/q7u+SDIEdvtchRAOh2BZZpZyHu6PN7Pn0crlLgfm7Awtzvi9KpxXEzEaa2XQzm163Y1GIilGx2N4mrresqA9fhdhh6pr4fw58CBgEvAHcWK4j7j7B3WvcvQbCPW8KUU9UNLa3iesW3SvhnxBlU6fE7+7L3H2Lu78P/JLk0jefxUCfnO97pNOEqFoU2yIL1Cnxm9luOV9PBeYUkD0L9DezvcysFXAWMLku9oRoKBTbIgsEh6Qws7tJhgDoZmaLgGuAIWY2CHBgPnBxqu0N3OLuw9x9s5mNBh4BWgC3uvsL9bIWQtQBxbbIKlVawNXH4X8CqnMiWlodlpwSUQzz3bDkxI/cG9T88b4zgppen301qFm+LFzk1bvnkqCmH/ODmqcfODaoiagng5kRmrUx7SyLEIWKoC7C/eWGL+Cy3g4jA6qvhhsaFDFa0w1hyYijfxnUxBRDzadfULOMHkHNBtoFNX/nk0HNd/lmUHNNxFBur7BfUDOMh4KaccdeHtQwLSwhVCe3sAbfOL0yBVxCCCGaF0r8QgiRMZT4hRAiYyjxCyFExlDiF0KIjKGAbNW4AAAC40lEQVTEL4QQGUOJXwghMoYSvxBCZIwqLeCyFcCCnEndiBufqZqQz/VPXf3d090bvMe0AnEN2dnmjUlWfI6O66pM/PmY2fSk186mg3yuf5qav4VoauvQ1PwF+VwI3eoRQoiMocQvhBAZo6kk/gmN7UAdkM/1T1PztxBNbR2amr8gn7ejSdzjF0IIUTmayhm/EEKIClH1id/MjjezV8xsnpld0dj+hDCz+WY228xmJgPHVx/pIOLLzWxOzrSuZjbVzOam/wsNMt5oFPF5jJktTrf1TDMb1pg+7ghNLa5BsV1fNEZsV3XiN7MWwM3ACcAA4GwzG9C4XkVxpLsPquJXyCYCx+dNuwJ4zN37A4+l36uJiWzvM8CP0209yN0fbmCf6kQTjmtQbNcHE2ng2K7qxE8y0PU8d3/V3d8F7gGGN7JPTR53fxJYmTd5OHB7+vl24JQGdSpAEZ+bKorrekKxHUe1J/7dgYU53xel06oZB6aY2XNmFhpnr5ro6e5vpJ+XAuHxHauD0Wb2fHq5XFWX8CVoinENiu2Gpt5iu9oTf1NksLt/guQy/stm9unGdmhH8eRVr6bwutfPgQ8Bg4A3gBsb151mj2K74ajX2K72xL8Y6JPzfY90WtXi7ovT/8uBSSSX9U2BZWa2G0D6f3kj+xPE3Ze5+xZ3fx/4JU1nWze5uAbFdkNS37Fd7Yn/WaC/me1lZq2As4DJjexTUcysvZl1rP0MDAXmlF6qapgMnJd+Pg94oBF9iaL2YE45laazrZtUXINiu6Gp79jeuZKNVRp332xmo4FHgBbAre7+QiO7VYqewCQzg2Tb3uXuf25cl7bHzO4GhgDdzGwRcA3wA+B3ZnYhSQ+SZzaeh9tTxOchZjaI5NJ9PnBxozm4AzTBuAbFdr3RGLGtyl0hhMgY1X6rRwghRIVR4hdCiIyhxC+EEBlDiV8IITKGEr8QQmQMJX4hhMgYSvxCCJExlPiFECJj/H9ylB0qVZDZzAAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1304,9 +1232,7 @@ { "cell_type": "code", "execution_count": 39, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Set the maximum scattering order to 0 (i.e., isotropic scattering)\n", @@ -1326,9 +1252,7 @@ { "cell_type": "code", "execution_count": 40, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1360,11 +1284,11 @@ " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2017 Massachusetts Institute of Technology\n", + " Copyright | 2011-2018 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | 4b01fd311461f1350989cb84ec18fe2cbaa8fa9f\n", - " Date/Time | 2017-03-10 17:32:18\n", + " Version | 0.10.0\n", + " Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416\n", + " Date/Time | 2018-04-22 15:04:39\n", " OpenMP Threads | 8\n", "\n", "\n", @@ -1373,23 +1297,13 @@ "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 1.16104 +/- 0.00109\n", - " k-effective (Track-length) = 1.16004 +/- 0.00125\n", - " k-effective (Absorption) = 1.16297 +/- 0.00061\n", - " Combined k-effective = 1.16273 +/- 0.00061\n", + " k-effective (Collision) = 1.16379 +/- 0.00090\n", + " k-effective (Track-length) = 1.16469 +/- 0.00101\n", + " k-effective (Absorption) = 1.16315 +/- 0.00052\n", + " Combined k-effective = 1.16335 +/- 0.00050\n", " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -1407,16 +1321,14 @@ { "cell_type": "code", "execution_count": 41, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "P3 bias [pcm]: 149.4\n", - "P0 bias [pcm]: 265.3\n" + "P3 bias [pcm]: -20.4\n", + "P0 bias [pcm]: 125.1\n" ] } ], @@ -1434,10 +1346,10 @@ "# Get keff\n", "mg_p0_keff = mgsp_p0.k_combined\n", "\n", - "bias_p0 = 1.0E5 * (ce_keff[0] - mg_p0_keff[0])\n", + "bias_p0 = 1.0E5 * (ce_keff - mg_p0_keff)\n", "\n", - "print('P3 bias [pcm]: {0:1.1f}'.format(bias))\n", - "print('P0 bias [pcm]: {0:1.1f}'.format(bias_p0))" + "print('P3 bias [pcm]: {0:1.1f}'.format(bias.nominal_value))\n", + "print('P0 bias [pcm]: {0:1.1f}'.format(bias_p0.nominal_value))" ] }, { @@ -1453,9 +1365,7 @@ { "cell_type": "code", "execution_count": 42, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Convert the zircaloy and fuel data to P0 scattering\n", @@ -1474,9 +1384,7 @@ { "cell_type": "code", "execution_count": 43, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Convert the formats as discussed\n", @@ -1501,9 +1409,7 @@ { "cell_type": "code", "execution_count": 44, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1535,11 +1441,11 @@ " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2017 Massachusetts Institute of Technology\n", + " Copyright | 2011-2018 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | 4b01fd311461f1350989cb84ec18fe2cbaa8fa9f\n", - " Date/Time | 2017-03-10 17:32:48\n", + " Version | 0.10.0\n", + " Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416\n", + " Date/Time | 2018-04-22 15:05:16\n", " OpenMP Threads | 8\n", "\n", "\n", @@ -1548,23 +1454,13 @@ "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 1.16348 +/- 0.00117\n", - " k-effective (Track-length) = 1.16263 +/- 0.00133\n", - " k-effective (Absorption) = 1.16485 +/- 0.00063\n", - " Combined k-effective = 1.16459 +/- 0.00061\n", + " k-effective (Collision) = 1.16471 +/- 0.00093\n", + " k-effective (Track-length) = 1.16412 +/- 0.00106\n", + " k-effective (Absorption) = 1.16449 +/- 0.00050\n", + " Combined k-effective = 1.16441 +/- 0.00049\n", " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 44, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -1587,16 +1483,14 @@ { "cell_type": "code", "execution_count": 45, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "P3 bias [pcm]: 149.4\n", - "Mixed Scattering bias [pcm]: 79.0\n" + "P3 bias [pcm]: -20.4\n", + "Mixed Scattering bias [pcm]: 19.5\n" ] } ], @@ -1605,16 +1499,17 @@ "mgsp_mixed = openmc.StatePoint('./statepoint.' + str(batches) + '.h5')\n", "\n", "mg_mixed_keff = mgsp_mixed.k_combined\n", - "bias_mixed = 1.0E5 * (ce_keff[0] - mg_mixed_keff[0])\n", + "bias_mixed = 1.0E5 * (ce_keff - mg_mixed_keff)\n", "\n", - "print('P3 bias [pcm]: {0:1.1f}'.format(bias))\n", - "print('Mixed Scattering bias [pcm]: {0:1.1f}'.format(bias_mixed))" + "print('P3 bias [pcm]: {0:1.1f}'.format(bias.nominal_value))\n", + "print('Mixed Scattering bias [pcm]: {0:1.1f}'.format(bias_mixed.nominal_value))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "\n", "Our tests in this section showed the flexibility of data formatting within OpenMC's multi-group mode: every material can be represented with its own format with the approximations that make the most sense. Now, as you'll see above, the runtimes from our P3, P0, and mixed cases are not significantly different and therefore this might not be a useful strategy for multi-group Monte Carlo. However, this capability provides a useful benchmark for the accuracy hit one may expect due to these scattering approximations before implementing this generality in a deterministic solver where the runtime savings are more significant.\n", "\n", "**NOTE**: The biases obtained above with P3, P0, and mixed representations do not necessarily reflect the inherent accuracies of the options. These cases were *not* run with a sufficient number of histories to truly differentiate methods improvement from statistical noise." @@ -1623,9 +1518,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "openmc", "language": "python", - "name": "python3" + "name": "openmc" }, "language_info": { "codemirror_mode": { @@ -1637,9 +1532,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.0" + "version": "3.6.5" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/jupyter/mg-mode-part-iii.ipynb b/examples/jupyter/mg-mode-part-iii.ipynb index c36ecd54a..2f37070c1 100644 --- a/examples/jupyter/mg-mode-part-iii.ipynb +++ b/examples/jupyter/mg-mode-part-iii.ipynb @@ -23,9 +23,7 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "import os\n", @@ -48,9 +46,7 @@ { "cell_type": "code", "execution_count": 2, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Instantiate some elements\n", @@ -74,9 +70,7 @@ { "cell_type": "code", "execution_count": 3, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "materials = {}\n", @@ -127,9 +121,7 @@ { "cell_type": "code", "execution_count": 4, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a Materials object\n", @@ -156,9 +148,7 @@ { "cell_type": "code", "execution_count": 5, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Set constants for the problem and assembly dimensions\n", @@ -207,9 +197,7 @@ { "cell_type": "code", "execution_count": 6, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Set regions for geometry building\n", @@ -253,9 +241,7 @@ { "cell_type": "code", "execution_count": 7, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "universes = {}\n", @@ -295,9 +281,7 @@ { "cell_type": "code", "execution_count": 8, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Create fuel assembly Lattice\n", @@ -336,9 +320,7 @@ { "cell_type": "code", "execution_count": 9, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# The top portion of the blade, poisoned with B4C\n", @@ -372,9 +354,7 @@ { "cell_type": "code", "execution_count": 10, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create root Universe\n", @@ -396,15 +376,23 @@ { "cell_type": "code", "execution_count": 11, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", "text/plain": [ - "" + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" ] }, "metadata": {}, @@ -435,9 +423,7 @@ { "cell_type": "code", "execution_count": 12, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Create Geometry and set root universe\n", @@ -459,9 +445,7 @@ { "cell_type": "code", "execution_count": 13, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# OpenMC simulation parameters\n", @@ -498,9 +482,7 @@ { "cell_type": "code", "execution_count": 14, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a 2-group EnergyGroups object\n", @@ -518,9 +500,7 @@ { "cell_type": "code", "execution_count": 15, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Initialize a 2-group Isotropic MGXS Library for OpenMC\n", @@ -541,9 +521,7 @@ { "cell_type": "code", "execution_count": 16, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Specify multi-group cross section types to compute\n", @@ -563,9 +541,7 @@ { "cell_type": "code", "execution_count": 17, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a tally Mesh\n", @@ -598,9 +574,7 @@ { "cell_type": "code", "execution_count": 18, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Set the scattering format to histogram and then define the number of bins\n", @@ -630,9 +604,7 @@ { "cell_type": "code", "execution_count": 19, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Let's repeat all of the above for an angular MGXS library so we can gather\n", @@ -662,9 +634,7 @@ { "cell_type": "code", "execution_count": 20, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Check the libraries - if no errors are raised, then the library is satisfactory.\n", @@ -684,15 +654,13 @@ { "cell_type": "code", "execution_count": 21, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/home/romano/openmc/openmc/mgxs/mgxs.py:4106: UserWarning: The legendre order will be ignored since the scatter format is set to histogram\n", + "/home/nelsonag/git/openmc/openmc/mgxs/mgxs.py:4116: UserWarning: The legendre order will be ignored since the scatter format is set to histogram\n", " warnings.warn(msg)\n" ] } @@ -713,9 +681,7 @@ { "cell_type": "code", "execution_count": 22, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Create a \"tallies.xml\" file for the MGXS Library\n", @@ -734,27 +700,25 @@ { "cell_type": "code", "execution_count": 23, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another MeshFilter instance already exists with id=1.\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter 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", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter 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", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter 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", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter 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", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter 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", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter 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", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=18.\n", " warn(msg, IDWarning)\n" ] } @@ -787,9 +751,7 @@ { "cell_type": "code", "execution_count": 24, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -821,12 +783,12 @@ " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2017 Massachusetts Institute of Technology\n", + " Copyright | 2011-2018 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.9.0\n", - " Git SHA1 | da61fb4a55e1feaa127799ad9293a766161fbb3e\n", - " Date/Time | 2017-12-11 16:57:27\n", - " OpenMP Threads | 4\n", + " Version | 0.10.0\n", + " Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416\n", + " Date/Time | 2018-04-24 19:15:17\n", + " OpenMP Threads | 8\n", "\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -841,16 +803,6 @@ " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -868,9 +820,7 @@ { "cell_type": "code", "execution_count": 25, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Move the StatePoint File\n", @@ -893,9 +843,7 @@ { "cell_type": "code", "execution_count": 26, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Load the statepoint file, but not the summary file, as it is a different filename than expected.\n", @@ -912,9 +860,7 @@ { "cell_type": "code", "execution_count": 27, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "su = openmc.Summary(ce_sumfile)\n", @@ -931,9 +877,7 @@ { "cell_type": "code", "execution_count": 28, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Initialize MGXS Library with OpenMC statepoint data\n", @@ -970,21 +914,13 @@ { "cell_type": "code", "execution_count": 29, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/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/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/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", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Universe instance already exists with id=0.\n", " warn(msg, IDWarning)\n" ] } @@ -1013,9 +949,7 @@ { "cell_type": "code", "execution_count": 30, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Set the energy mode\n", @@ -1035,16 +969,14 @@ { "cell_type": "code", "execution_count": 31, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Create a \"tallies.xml\" file for the MGXS Library\n", "tallies_file = openmc.Tallies()\n", "\n", "# Add our fission rate mesh tally\n", - "tallies_file.add_tally(tally)\n", + "tallies_file.append(tally)\n", "\n", "# Export to \"tallies.xml\"\n", "tallies_file.export_to_xml()" @@ -1060,15 +992,23 @@ { "cell_type": "code", "execution_count": 32, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { - "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": [ - "" + "" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" ] }, "metadata": {}, @@ -1093,9 +1033,7 @@ { "cell_type": "code", "execution_count": 33, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1127,12 +1065,12 @@ " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2017 Massachusetts Institute of Technology\n", + " Copyright | 2011-2018 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.9.0\n", - " Git SHA1 | da61fb4a55e1feaa127799ad9293a766161fbb3e\n", - " Date/Time | 2017-12-11 17:00:35\n", - " OpenMP Threads | 4\n", + " Version | 0.10.0\n", + " Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416\n", + " Date/Time | 2018-04-24 19:16:03\n", + " OpenMP Threads | 8\n", "\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -1147,16 +1085,6 @@ " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 33, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -1174,9 +1102,7 @@ { "cell_type": "code", "execution_count": 34, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Move the StatePoint File\n", @@ -1201,21 +1127,13 @@ { "cell_type": "code", "execution_count": 35, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/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/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/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", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Universe instance already exists with id=0.\n", " warn(msg, IDWarning)\n" ] } @@ -1237,9 +1155,7 @@ { "cell_type": "code", "execution_count": 36, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1271,12 +1187,12 @@ " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2017 Massachusetts Institute of Technology\n", + " Copyright | 2011-2018 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.9.0\n", - " Git SHA1 | da61fb4a55e1feaa127799ad9293a766161fbb3e\n", - " Date/Time | 2017-12-11 17:00:59\n", - " OpenMP Threads | 4\n", + " Version | 0.10.0\n", + " Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416\n", + " Date/Time | 2018-04-24 19:16:12\n", + " OpenMP Threads | 8\n", "\n", "\n", " ====================> K EIGENVALUE SIMULATION <====================\n", @@ -1291,16 +1207,6 @@ " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -1321,9 +1227,7 @@ { "cell_type": "code", "execution_count": 37, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "# Load the isotropic statepoint file\n", @@ -1346,9 +1250,7 @@ { "cell_type": "code", "execution_count": 38, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "ce_keff = sp.k_combined\n", @@ -1356,8 +1258,8 @@ "angle_mg_keff = angle_mgsp.k_combined\n", "\n", "# Find eigenvalue bias\n", - "iso_bias = 1.0E5 * (ce_keff[0] - iso_mg_keff[0])\n", - "angle_bias = 1.0E5 * (ce_keff[0] - angle_mg_keff[0])" + "iso_bias = 1.0E5 * (ce_keff - iso_mg_keff)\n", + "angle_bias = 1.0E5 * (ce_keff - angle_mg_keff)" ] }, { @@ -1370,9 +1272,7 @@ { "cell_type": "code", "execution_count": 39, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1384,8 +1284,8 @@ } ], "source": [ - "print('Isotropic to CE Bias [pcm]: {0:1.1f}'.format(iso_bias))\n", - "print('Angle to CE Bias [pcm]: {0:1.1f}'.format(angle_bias))" + "print('Isotropic to CE Bias [pcm]: {0:1.1f}'.format(iso_bias.nominal_value))\n", + "print('Angle to CE Bias [pcm]: {0:1.1f}'.format(angle_bias.nominal_value))" ] }, { @@ -1408,15 +1308,14 @@ "cell_type": "code", "execution_count": 40, "metadata": { - "collapsed": false, "scrolled": false }, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1466,14 +1365,12 @@ { "cell_type": "code", "execution_count": 41, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 41, @@ -1482,9 +1379,9 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1520,9 +1417,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "openmc", "language": "python", - "name": "python3" + "name": "openmc" }, "language_info": { "codemirror_mode": { @@ -1534,9 +1431,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.0" + "version": "3.6.5" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/examples/jupyter/mgxs-part-iii.ipynb b/examples/jupyter/mgxs-part-iii.ipynb index 450276c89..4171486a8 100644 --- a/examples/jupyter/mgxs-part-iii.ipynb +++ b/examples/jupyter/mgxs-part-iii.ipynb @@ -24,17 +24,15 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [ { "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", - "because the backend has already been chosen;\n", - "matplotlib.use() must be called *before* pylab, matplotlib.pyplot,\n", + "/home/nelsonag/python/openmc/lib/python3.6/site-packages/matplotlib/__init__.py:1405: UserWarning: \n", + "This call to matplotlib.use() has no effect because the backend has already\n", + "been chosen; matplotlib.use() must be called *before* pylab, matplotlib.pyplot,\n", "or matplotlib.backends is imported for the first time.\n", "\n", " warnings.warn(_use_error_msg)\n" @@ -68,10 +66,8 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, + "execution_count": 2, + "metadata": {}, "outputs": [], "source": [ "# 1.6 enriched fuel\n", @@ -103,10 +99,8 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": true - }, + "execution_count": 3, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a Materials object\n", @@ -125,10 +119,8 @@ }, { "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": true - }, + "execution_count": 4, + "metadata": {}, "outputs": [], "source": [ "# Create cylinders for the fuel and clad\n", @@ -153,10 +145,8 @@ }, { "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": true - }, + "execution_count": 5, + "metadata": {}, "outputs": [], "source": [ "# Create a Universe to encapsulate a fuel pin\n", @@ -190,10 +180,8 @@ }, { "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": true - }, + "execution_count": 6, + "metadata": {}, "outputs": [], "source": [ "# Create a Universe to encapsulate a control rod guide tube\n", @@ -227,10 +215,8 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": true - }, + "execution_count": 7, + "metadata": {}, "outputs": [], "source": [ "# Create fuel assembly Lattice\n", @@ -248,10 +234,8 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": { - "collapsed": true - }, + "execution_count": 8, + "metadata": {}, "outputs": [], "source": [ "# Create array indices for guide tube locations in lattice\n", @@ -280,10 +264,8 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": true - }, + "execution_count": 9, + "metadata": {}, "outputs": [], "source": [ "# Create root Cell\n", @@ -307,10 +289,8 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": true - }, + "execution_count": 10, + "metadata": {}, "outputs": [], "source": [ "# Create Geometry and set root Universe\n", @@ -319,10 +299,8 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": { - "collapsed": true - }, + "execution_count": 11, + "metadata": {}, "outputs": [], "source": [ "# Export to \"geometry.xml\"\n", @@ -338,16 +316,14 @@ }, { "cell_type": "code", - "execution_count": 13, - "metadata": { - "collapsed": false - }, + "execution_count": 12, + "metadata": {}, "outputs": [], "source": [ "# OpenMC simulation parameters\n", "batches = 50\n", "inactive = 10\n", - "particles = 2500\n", + "particles = 10000\n", "\n", "# Instantiate a Settings object\n", "settings_file = openmc.Settings()\n", @@ -374,10 +350,8 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": { - "collapsed": true - }, + "execution_count": 13, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a Plot\n", @@ -402,22 +376,9 @@ }, { "cell_type": "code", - "execution_count": 15, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "execution_count": 14, + "metadata": {}, + "outputs": [], "source": [ "# Run openmc in plotting mode\n", "openmc.plot_geometry(output=False)" @@ -425,19 +386,17 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": { - "collapsed": false - }, + "execution_count": 15, + "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "" ] }, - "execution_count": 16, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -473,10 +432,8 @@ }, { "cell_type": "code", - "execution_count": 17, - "metadata": { - "collapsed": false - }, + "execution_count": 16, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a 2-group EnergyGroups object\n", @@ -493,10 +450,8 @@ }, { "cell_type": "code", - "execution_count": 18, - "metadata": { - "collapsed": false - }, + "execution_count": 17, + "metadata": {}, "outputs": [], "source": [ "# Initialize a 2-group MGXS Library for OpenMOC\n", @@ -526,21 +481,19 @@ "* `ChiDelayed` (`\"chi-delayed\"`)\n", "* `Beta` (`\"beta\"`)\n", "\n", - "In this case, let's create the multi-group cross sections needed to run an OpenMOC simulation to verify the accuracy of our cross sections. In particular, we will define `\"transport\"`, `\"nu-fission\"`, `'\"fission\"`, `\"nu-scatter matrix\"` and `\"chi\"` cross sections for our `Library`.\n", + "In this case, let's create the multi-group cross sections needed to run an OpenMOC simulation to verify the accuracy of our cross sections. In particular, we will define `\"nu-transport\"`, `\"nu-fission\"`, `'\"fission\"`, `\"nu-scatter matrix\"` and `\"chi\"` cross sections for our `Library`.\n", "\n", "**Note**: A variety of different approximate transport-corrected total multi-group cross sections (and corresponding scattering matrices) can be found in the literature. At the present time, the `openmc.mgxs` module only supports the `\"P0\"` transport correction. This correction can be turned on and off through the boolean `Library.correction` property which may take values of `\"P0\"` (default) or `None`." ] }, { "cell_type": "code", - "execution_count": 19, - "metadata": { - "collapsed": false - }, + "execution_count": 18, + "metadata": {}, "outputs": [], "source": [ "# Specify multi-group cross section types to compute\n", - "mgxs_lib.mgxs_types = ['transport', 'nu-fission', 'fission', 'nu-scatter matrix', 'chi']" + "mgxs_lib.mgxs_types = ['nu-transport', 'nu-fission', 'fission', 'nu-scatter matrix', 'chi']" ] }, { @@ -554,10 +507,8 @@ }, { "cell_type": "code", - "execution_count": 20, - "metadata": { - "collapsed": true - }, + "execution_count": 19, + "metadata": {}, "outputs": [], "source": [ "# Specify a \"cell\" domain type for the cross section tally filters\n", @@ -576,10 +527,8 @@ }, { "cell_type": "code", - "execution_count": 21, - "metadata": { - "collapsed": true - }, + "execution_count": 20, + "metadata": {}, "outputs": [], "source": [ "# Compute cross sections on a nuclide-by-nuclide basis\n", @@ -595,10 +544,8 @@ }, { "cell_type": "code", - "execution_count": 22, - "metadata": { - "collapsed": true - }, + "execution_count": 21, + "metadata": {}, "outputs": [], "source": [ "# Construct all tallies needed for the multi-group cross section library\n", @@ -616,10 +563,8 @@ }, { "cell_type": "code", - "execution_count": 23, - "metadata": { - "collapsed": true - }, + "execution_count": 22, + "metadata": {}, "outputs": [], "source": [ "# Create a \"tallies.xml\" file for the MGXS Library\n", @@ -636,10 +581,8 @@ }, { "cell_type": "code", - "execution_count": 24, - "metadata": { - "collapsed": false - }, + "execution_count": 23, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a tally Mesh\n", @@ -663,11 +606,32 @@ }, { "cell_type": "code", - "execution_count": 25, - "metadata": { - "collapsed": true - }, - "outputs": [], + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=126.\n", + " warn(msg, IDWarning)\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=21.\n", + " warn(msg, IDWarning)\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.\n", + " warn(msg, IDWarning)\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=3.\n", + " warn(msg, IDWarning)\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=4.\n", + " warn(msg, IDWarning)\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=96.\n", + " warn(msg, IDWarning)\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=15.\n", + " warn(msg, IDWarning)\n", + "/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=114.\n", + " warn(msg, IDWarning)\n" + ] + } + ], "source": [ "# Export all tallies to a \"tallies.xml\" file\n", "tallies_file.export_to_xml()" @@ -675,10 +639,8 @@ }, { "cell_type": "code", - "execution_count": 26, - "metadata": { - "collapsed": false - }, + "execution_count": 25, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -710,137 +672,111 @@ " %%%%%%%%%%%\n", "\n", " | The OpenMC Monte Carlo Code\n", - " Copyright | 2011-2017 Massachusetts Institute of Technology\n", + " Copyright | 2011-2018 Massachusetts Institute of Technology\n", " License | http://openmc.readthedocs.io/en/latest/license.html\n", - " Version | 0.8.0\n", - " Git SHA1 | 647bf77a57a3cc5cce24b39cb192e1b99f52e499\n", - " Date/Time | 2017-02-27 14:21:38\n", - " OpenMP Threads | 4\n", - "\n", - " ===========================================================================\n", - " ========================> INITIALIZATION <=========================\n", - " ===========================================================================\n", + " Version | 0.10.0\n", + " Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416\n", + " Date/Time | 2018-04-24 19:20:48\n", + " OpenMP Threads | 8\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 B10 from\n", - " /home/wbinventor/Documents/NSE-CRPG-Codes/openmc/data/nndc_hdf5/B10.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 /opt/xsdata/nndc/U235.h5\n", + " Reading U238 from /opt/xsdata/nndc/U238.h5\n", + " Reading O16 from /opt/xsdata/nndc/O16.h5\n", + " Reading H1 from /opt/xsdata/nndc/H1.h5\n", + " Reading B10 from /opt/xsdata/nndc/B10.h5\n", + " Reading Zr90 from /opt/xsdata/nndc/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.03852 \n", - " 2/1 0.99743 \n", - " 3/1 1.02987 \n", - " 4/1 1.04397 \n", - " 5/1 1.06262 \n", - " 6/1 1.06657 \n", - " 7/1 0.98574 \n", - " 8/1 1.04364 \n", - " 9/1 1.01253 \n", - " 10/1 1.02094 \n", - " 11/1 0.99586 \n", - " 12/1 1.00508 1.00047 +/- 0.00461\n", - " 13/1 1.05292 1.01795 +/- 0.01769\n", - " 14/1 1.04732 1.02530 +/- 0.01450\n", - " 15/1 1.04886 1.03001 +/- 0.01218\n", - " 16/1 1.00948 1.02659 +/- 0.01052\n", - " 17/1 1.02684 1.02662 +/- 0.00889\n", - " 18/1 0.97234 1.01984 +/- 0.01026\n", - " 19/1 0.99754 1.01736 +/- 0.00938\n", - " 20/1 0.98964 1.01459 +/- 0.00884\n", - " 21/1 1.04140 1.01703 +/- 0.00836\n", - " 22/1 1.03854 1.01882 +/- 0.00784\n", - " 23/1 1.05917 1.02192 +/- 0.00785\n", - " 24/1 1.02413 1.02208 +/- 0.00727\n", - " 25/1 1.03113 1.02268 +/- 0.00679\n", - " 26/1 1.05113 1.02446 +/- 0.00660\n", - " 27/1 1.03252 1.02494 +/- 0.00622\n", - " 28/1 1.05196 1.02644 +/- 0.00605\n", - " 29/1 0.99663 1.02487 +/- 0.00593\n", - " 30/1 1.01820 1.02454 +/- 0.00564\n", - " 31/1 1.02753 1.02468 +/- 0.00537\n", - " 32/1 1.02162 1.02454 +/- 0.00512\n", - " 33/1 1.04083 1.02525 +/- 0.00494\n", - " 34/1 1.03335 1.02558 +/- 0.00474\n", - " 35/1 1.01304 1.02508 +/- 0.00458\n", - " 36/1 0.99299 1.02385 +/- 0.00457\n", - " 37/1 1.04936 1.02479 +/- 0.00450\n", - " 38/1 1.02856 1.02493 +/- 0.00433\n", - " 39/1 1.03706 1.02535 +/- 0.00420\n", - " 40/1 1.08118 1.02721 +/- 0.00447\n", - " 41/1 1.00149 1.02638 +/- 0.00440\n", - " 42/1 1.00233 1.02563 +/- 0.00433\n", - " 43/1 1.03023 1.02577 +/- 0.00419\n", - " 44/1 1.03230 1.02596 +/- 0.00407\n", - " 45/1 0.98123 1.02468 +/- 0.00416\n", - " 46/1 1.02126 1.02458 +/- 0.00404\n", - " 47/1 0.99772 1.02386 +/- 0.00400\n", - " 48/1 1.02773 1.02396 +/- 0.00389\n", - " 49/1 1.01690 1.02378 +/- 0.00379\n", - " 50/1 1.02890 1.02391 +/- 0.00370\n", + " 1/1 1.03784 \n", + " 2/1 1.02297 \n", + " 3/1 1.02244 \n", + " 4/1 1.02344 \n", + " 5/1 1.02057 \n", + " 6/1 1.04077 \n", + " 7/1 1.00795 \n", + " 8/1 1.02418 \n", + " 9/1 1.02241 \n", + " 10/1 1.03731 \n", + " 11/1 1.01477 \n", + " 12/1 1.05315 1.03396 +/- 0.01919\n", + " 13/1 1.02824 1.03205 +/- 0.01124\n", + " 14/1 1.02858 1.03118 +/- 0.00800\n", + " 15/1 1.02176 1.02930 +/- 0.00647\n", + " 16/1 1.06046 1.03449 +/- 0.00741\n", + " 17/1 1.02066 1.03252 +/- 0.00657\n", + " 18/1 1.03088 1.03231 +/- 0.00569\n", + " 19/1 1.02021 1.03097 +/- 0.00520\n", + " 20/1 1.02717 1.03059 +/- 0.00466\n", + " 21/1 1.03455 1.03095 +/- 0.00423\n", + " 22/1 1.02917 1.03080 +/- 0.00387\n", + " 23/1 1.02800 1.03058 +/- 0.00356\n", + " 24/1 1.02935 1.03050 +/- 0.00330\n", + " 25/1 1.01612 1.02954 +/- 0.00322\n", + " 26/1 1.00549 1.02803 +/- 0.00336\n", + " 27/1 1.02824 1.02805 +/- 0.00316\n", + " 28/1 1.01487 1.02731 +/- 0.00307\n", + " 29/1 1.05544 1.02879 +/- 0.00326\n", + " 30/1 1.00467 1.02759 +/- 0.00332\n", + " 31/1 1.03942 1.02815 +/- 0.00321\n", + " 32/1 1.02587 1.02805 +/- 0.00306\n", + " 33/1 1.02938 1.02811 +/- 0.00292\n", + " 34/1 1.02838 1.02812 +/- 0.00280\n", + " 35/1 1.00052 1.02701 +/- 0.00290\n", + " 36/1 1.01722 1.02664 +/- 0.00281\n", + " 37/1 1.01881 1.02635 +/- 0.00272\n", + " 38/1 1.03928 1.02681 +/- 0.00266\n", + " 39/1 1.03802 1.02720 +/- 0.00260\n", + " 40/1 1.00710 1.02653 +/- 0.00260\n", + " 41/1 1.02558 1.02650 +/- 0.00251\n", + " 42/1 1.03499 1.02676 +/- 0.00245\n", + " 43/1 1.01128 1.02629 +/- 0.00242\n", + " 44/1 1.00442 1.02565 +/- 0.00243\n", + " 45/1 1.03444 1.02590 +/- 0.00238\n", + " 46/1 1.01799 1.02568 +/- 0.00232\n", + " 47/1 1.00814 1.02521 +/- 0.00231\n", + " 48/1 1.00500 1.02467 +/- 0.00231\n", + " 49/1 1.01960 1.02454 +/- 0.00225\n", + " 50/1 1.02431 1.02454 +/- 0.00219\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.4887E-01 seconds\n", - " Reading cross sections = 2.1990E-01 seconds\n", - " Total time in simulation = 3.2195E+01 seconds\n", - " Time in transport only = 3.1778E+01 seconds\n", - " Time in inactive batches = 1.9903E+00 seconds\n", - " Time in active batches = 3.0205E+01 seconds\n", - " Time synchronizing fission bank = 5.9614E-03 seconds\n", - " Sampling source sites = 4.8344E-03 seconds\n", - " SEND/RECV source sites = 1.0392E-03 seconds\n", - " Time accumulating tallies = 1.5849E-03 seconds\n", - " Total time for finalization = 3.9664E-05 seconds\n", - " Total time elapsed = 3.2560E+01 seconds\n", - " Calculation Rate (inactive) = 12561.1 neutrons/second\n", - " Calculation Rate (active) = 3310.69 neutrons/second\n", + " Total time for initialization = 2.8179E-01 seconds\n", + " Reading cross sections = 2.5741E-01 seconds\n", + " Total time in simulation = 2.5787E+01 seconds\n", + " Time in transport only = 2.5724E+01 seconds\n", + " Time in inactive batches = 1.7591E+00 seconds\n", + " Time in active batches = 2.4028E+01 seconds\n", + " Time synchronizing fission bank = 1.3217E-02 seconds\n", + " Sampling source sites = 1.0464E-02 seconds\n", + " SEND/RECV source sites = 2.6486E-03 seconds\n", + " Time accumulating tallies = 2.7351E-04 seconds\n", + " Total time for finalization = 5.5454E-05 seconds\n", + " Total time elapsed = 2.6109E+01 seconds\n", + " Calculation Rate (inactive) = 56847.1 neutrons/second\n", + " Calculation Rate (active) = 16647.3 neutrons/second\n", "\n", " ============================> RESULTS <============================\n", "\n", - " k-effective (Collision) = 1.02621 +/- 0.00393\n", - " k-effective (Track-length) = 1.02391 +/- 0.00370\n", - " k-effective (Absorption) = 1.02077 +/- 0.00423\n", - " Combined k-effective = 1.02331 +/- 0.00353\n", + " k-effective (Collision) = 1.02204 +/- 0.00176\n", + " k-effective (Track-length) = 1.02454 +/- 0.00219\n", + " k-effective (Absorption) = 1.02370 +/- 0.00186\n", + " Combined k-effective = 1.02329 +/- 0.00157\n", " Leakage Fraction = 0.00000 +/- 0.00000\n", "\n" ] - }, - { - "data": { - "text/plain": [ - "0" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -864,10 +800,8 @@ }, { "cell_type": "code", - "execution_count": 27, - "metadata": { - "collapsed": false - }, + "execution_count": 26, + "metadata": {}, "outputs": [], "source": [ "# Load the last statepoint file\n", @@ -883,10 +817,8 @@ }, { "cell_type": "code", - "execution_count": 28, - "metadata": { - "collapsed": false - }, + "execution_count": 27, + "metadata": {}, "outputs": [], "source": [ "# Initialize MGXS Library with OpenMC statepoint data\n", @@ -918,10 +850,8 @@ }, { "cell_type": "code", - "execution_count": 29, - "metadata": { - "collapsed": false - }, + "execution_count": 28, + "metadata": {}, "outputs": [], "source": [ "# Retrieve the NuFissionXS object for the fuel cell from the library\n", @@ -938,23 +868,26 @@ }, { "cell_type": "code", - "execution_count": 30, - "metadata": { - "collapsed": false - }, + "execution_count": 29, + "metadata": {}, "outputs": [ - { - "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", - " self_rel_err = data['self']['std. dev.'] / data['self']['mean']\n" - ] - }, { "data": { "text/html": [ "
\n", + "\n", "\n", " \n", " \n", @@ -969,23 +902,23 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -993,23 +926,23 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1020,16 +953,16 @@ "" ], "text/plain": [ - " cell group in nuclide mean std. dev.\n", - "3 10000 1 U235 8.096764e-03 3.130177e-05\n", - "4 10000 1 U238 7.364515e-03 4.510564e-05\n", - "5 10000 1 O16 0.000000e+00 0.000000e+00\n", - "0 10000 2 U235 3.611153e-01 2.048312e-03\n", - "1 10000 2 U238 6.735070e-07 3.780177e-09\n", - "2 10000 2 O16 0.000000e+00 0.000000e+00" + " cell group in nuclide mean std. dev.\n", + "3 1 1 U235 8.093482e-03 1.597406e-05\n", + "4 1 1 U238 7.347745e-03 2.082526e-05\n", + "5 1 1 O16 0.000000e+00 0.000000e+00\n", + "0 1 2 U235 3.615911e-01 1.206052e-03\n", + "1 1 2 U238 6.743056e-07 2.229534e-09\n", + "2 1 2 O16 0.000000e+00 0.000000e+00" ] }, - "execution_count": 30, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -1048,10 +981,8 @@ }, { "cell_type": "code", - "execution_count": 31, - "metadata": { - "collapsed": false - }, + "execution_count": 30, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1060,16 +991,16 @@ "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 [cm^-1]:\n", - " Group 1 [0.625 - 20000000.0eV]:\t8.10e-03 +/- 3.87e-01%\n", - " Group 2 [0.0 - 0.625 eV]:\t3.61e-01 +/- 5.67e-01%\n", + " Group 1 [0.625 - 20000000.0eV]:\t8.09e-03 +/- 1.97e-01%\n", + " Group 2 [0.0 - 0.625 eV]:\t3.62e-01 +/- 3.34e-01%\n", "\n", "\tNuclide =\tU238\n", "\tCross Sections [cm^-1]:\n", - " Group 1 [0.625 - 20000000.0eV]:\t7.36e-03 +/- 6.12e-01%\n", - " Group 2 [0.0 - 0.625 eV]:\t6.74e-07 +/- 5.61e-01%\n", + " Group 1 [0.625 - 20000000.0eV]:\t7.35e-03 +/- 2.83e-01%\n", + " Group 2 [0.0 - 0.625 eV]:\t6.74e-07 +/- 3.31e-01%\n", "\n", "\tNuclide =\tO16\n", "\tCross Sections [cm^-1]:\n", @@ -1084,7 +1015,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/wbinventor/Documents/NSE-CRPG-Codes/openmc/openmc/tallies.py:1506: RuntimeWarning: invalid value encountered in true_divide\n", + "/home/nelsonag/git/openmc/openmc/tallies.py:1269: RuntimeWarning: invalid value encountered in true_divide\n", " data = self.std_dev[indices] / self.mean[indices]\n" ] } @@ -1102,24 +1033,9 @@ }, { "cell_type": "code", - "execution_count": 32, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "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", - " 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", - " 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", - " new_tally._mean = data['self']['mean'] / data['other']['mean']\n" - ] - } - ], + "execution_count": 31, + "metadata": {}, + "outputs": [], "source": [ "# Store the cross section data in an \"mgxs/mgxs.h5\" HDF5 binary file\n", "mgxs_lib.build_hdf5_store(filename='mgxs.h5', directory='mgxs')" @@ -1134,10 +1050,8 @@ }, { "cell_type": "code", - "execution_count": 33, - "metadata": { - "collapsed": true - }, + "execution_count": 32, + "metadata": {}, "outputs": [], "source": [ "# Store a Library and its MGXS objects in a pickled binary file \"mgxs/mgxs.pkl\"\n", @@ -1146,10 +1060,8 @@ }, { "cell_type": "code", - "execution_count": 34, - "metadata": { - "collapsed": true - }, + "execution_count": 33, + "metadata": {}, "outputs": [], "source": [ "# Instantiate a new MGXS Library from the pickled binary file \"mgxs/mgxs.pkl\"\n", @@ -1165,10 +1077,8 @@ }, { "cell_type": "code", - "execution_count": 35, - "metadata": { - "collapsed": false - }, + "execution_count": 34, + "metadata": {}, "outputs": [], "source": [ "# Create a 1-group structure\n", @@ -1180,23 +1090,26 @@ }, { "cell_type": "code", - "execution_count": 36, - "metadata": { - "collapsed": false - }, + "execution_count": 35, + "metadata": {}, "outputs": [ - { - "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", - " self_rel_err = data['self']['std. dev.'] / data['self']['mean']\n" - ] - }, { "data": { "text/html": [ "
\n", + "\n", "
31000011U2358.096764e-033.130177e-058.093482e-031.597406e-05
41000011U2387.364515e-034.510564e-057.347745e-032.082526e-05
51000011O160.000000e+00
01000012U2353.611153e-012.048312e-033.615911e-011.206052e-03
11000012U2386.735070e-073.780177e-096.743056e-072.229534e-09
21000012O160.000000e+00
\n", " \n", " \n", @@ -1211,23 +1124,23 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1238,13 +1151,13 @@ "" ], "text/plain": [ - " cell group in nuclide mean std. dev.\n", - "0 10000 1 U235 0.074393 0.000308\n", - "1 10000 1 U238 0.005982 0.000036\n", - "2 10000 1 O16 0.000000 0.000000" + " cell group in nuclide mean std. dev.\n", + "0 1 1 U235 0.074672 0.000179\n", + "1 1 1 U238 0.005964 0.000017\n", + "2 1 1 O16 0.000000 0.000000" ] }, - "execution_count": 36, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -1273,10 +1186,8 @@ }, { "cell_type": "code", - "execution_count": 37, - "metadata": { - "collapsed": false - }, + "execution_count": 36, + "metadata": {}, "outputs": [], "source": [ "# Create an OpenMOC Geometry from the OpenMC Geometry\n", @@ -1292,24 +1203,9 @@ }, { "cell_type": "code", - "execution_count": 38, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "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", - " 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", - " 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", - " new_tally._mean = data['self']['mean'] / data['other']['mean']\n" - ] - } - ], + "execution_count": 37, + "metadata": {}, + "outputs": [], "source": [ "# Load the library into the OpenMOC geometry\n", "materials = load_openmc_mgxs_lib(mgxs_lib, openmoc_geometry)" @@ -1324,9 +1220,8 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 38, "metadata": { - "collapsed": false, "scrolled": true }, "outputs": [ @@ -1336,131 +1231,131 @@ "text": [ "[ NORMAL ] Importing ray tracing data from file...\n", "[ NORMAL ] Computing the eigenvalue...\n", - "[ NORMAL ] Iteration 0:\tk_eff = 0.823793\tres = 0.000E+00\n", - "[ NORMAL ] Iteration 1:\tk_eff = 0.780554\tres = 1.938E-01\n", - "[ NORMAL ] Iteration 2:\tk_eff = 0.739678\tres = 6.539E-02\n", - "[ NORMAL ] Iteration 3:\tk_eff = 0.711003\tres = 5.285E-02\n", - "[ NORMAL ] Iteration 4:\tk_eff = 0.689738\tres = 3.931E-02\n", - "[ NORMAL ] Iteration 5:\tk_eff = 0.675038\tres = 3.014E-02\n", - "[ NORMAL ] Iteration 6:\tk_eff = 0.665753\tres = 2.147E-02\n", - "[ NORMAL ] Iteration 7:\tk_eff = 0.661013\tres = 1.389E-02\n", - "[ NORMAL ] Iteration 8:\tk_eff = 0.660052\tres = 7.293E-03\n", - "[ NORMAL ] Iteration 9:\tk_eff = 0.662216\tres = 2.057E-03\n", - "[ NORMAL ] Iteration 10:\tk_eff = 0.666942\tres = 3.576E-03\n", - "[ NORMAL ] Iteration 11:\tk_eff = 0.673743\tres = 7.278E-03\n", - "[ NORMAL ] Iteration 12:\tk_eff = 0.682202\tres = 1.030E-02\n", - "[ NORMAL ] Iteration 13:\tk_eff = 0.691961\tres = 1.264E-02\n", - "[ NORMAL ] Iteration 14:\tk_eff = 0.702715\tres = 1.438E-02\n", - "[ NORMAL ] Iteration 15:\tk_eff = 0.714203\tres = 1.561E-02\n", - "[ NORMAL ] Iteration 16:\tk_eff = 0.726205\tres = 1.641E-02\n", - "[ NORMAL ] Iteration 17:\tk_eff = 0.738532\tres = 1.686E-02\n", - "[ NORMAL ] Iteration 18:\tk_eff = 0.751030\tres = 1.703E-02\n", - "[ NORMAL ] Iteration 19:\tk_eff = 0.763567\tres = 1.697E-02\n", - "[ NORMAL ] Iteration 20:\tk_eff = 0.776034\tres = 1.674E-02\n", - "[ NORMAL ] Iteration 21:\tk_eff = 0.788344\tres = 1.637E-02\n", - "[ NORMAL ] Iteration 22:\tk_eff = 0.800423\tres = 1.591E-02\n", - "[ NORMAL ] Iteration 23:\tk_eff = 0.812215\tres = 1.536E-02\n", - "[ NORMAL ] Iteration 24:\tk_eff = 0.823673\tres = 1.477E-02\n", - "[ NORMAL ] Iteration 25:\tk_eff = 0.834764\tres = 1.415E-02\n", - "[ NORMAL ] Iteration 26:\tk_eff = 0.845462\tres = 1.350E-02\n", - "[ NORMAL ] Iteration 27:\tk_eff = 0.855748\tres = 1.285E-02\n", - "[ NORMAL ] Iteration 28:\tk_eff = 0.865611\tres = 1.220E-02\n", - "[ NORMAL ] Iteration 29:\tk_eff = 0.875045\tres = 1.156E-02\n", - "[ NORMAL ] Iteration 30:\tk_eff = 0.884048\tres = 1.093E-02\n", - "[ NORMAL ] Iteration 31:\tk_eff = 0.892623\tres = 1.032E-02\n", - "[ NORMAL ] Iteration 32:\tk_eff = 0.900775\tres = 9.727E-03\n", - "[ NORMAL ] Iteration 33:\tk_eff = 0.908511\tres = 9.158E-03\n", - "[ NORMAL ] Iteration 34:\tk_eff = 0.915841\tres = 8.613E-03\n", - "[ NORMAL ] Iteration 35:\tk_eff = 0.922778\tres = 8.092E-03\n", - "[ NORMAL ] Iteration 36:\tk_eff = 0.929332\tres = 7.596E-03\n", - "[ NORMAL ] Iteration 37:\tk_eff = 0.935519\tres = 7.124E-03\n", - "[ NORMAL ] Iteration 38:\tk_eff = 0.941351\tres = 6.676E-03\n", - "[ NORMAL ] Iteration 39:\tk_eff = 0.946843\tres = 6.253E-03\n", - "[ NORMAL ] Iteration 40:\tk_eff = 0.952011\tres = 5.852E-03\n", - "[ NORMAL ] Iteration 41:\tk_eff = 0.956869\tres = 5.474E-03\n", - "[ NORMAL ] Iteration 42:\tk_eff = 0.961431\tres = 5.118E-03\n", - "[ NORMAL ] Iteration 43:\tk_eff = 0.965712\tres = 4.783E-03\n", - "[ NORMAL ] Iteration 44:\tk_eff = 0.969727\tres = 4.467E-03\n", - "[ NORMAL ] Iteration 45:\tk_eff = 0.973489\tres = 4.170E-03\n", - "[ NORMAL ] Iteration 46:\tk_eff = 0.977013\tres = 3.892E-03\n", - "[ NORMAL ] Iteration 47:\tk_eff = 0.980310\tres = 3.631E-03\n", - "[ NORMAL ] Iteration 48:\tk_eff = 0.983394\tres = 3.386E-03\n", - "[ NORMAL ] Iteration 49:\tk_eff = 0.986277\tres = 3.156E-03\n", - "[ NORMAL ] Iteration 50:\tk_eff = 0.988971\tres = 2.942E-03\n", - "[ NORMAL ] Iteration 51:\tk_eff = 0.991487\tres = 2.740E-03\n", - "[ NORMAL ] Iteration 52:\tk_eff = 0.993835\tres = 2.552E-03\n", - "[ NORMAL ] Iteration 53:\tk_eff = 0.996026\tres = 2.376E-03\n", - "[ NORMAL ] Iteration 54:\tk_eff = 0.998069\tres = 2.212E-03\n", - "[ NORMAL ] Iteration 55:\tk_eff = 0.999974\tres = 2.059E-03\n", - "[ NORMAL ] Iteration 56:\tk_eff = 1.001749\tres = 1.915E-03\n", - "[ NORMAL ] Iteration 57:\tk_eff = 1.003403\tres = 1.782E-03\n", - "[ NORMAL ] Iteration 58:\tk_eff = 1.004943\tres = 1.657E-03\n", - "[ NORMAL ] Iteration 59:\tk_eff = 1.006377\tres = 1.540E-03\n", - "[ NORMAL ] Iteration 60:\tk_eff = 1.007711\tres = 1.432E-03\n", - "[ NORMAL ] Iteration 61:\tk_eff = 1.008952\tres = 1.331E-03\n", - "[ NORMAL ] Iteration 62:\tk_eff = 1.010107\tres = 1.237E-03\n", - "[ NORMAL ] Iteration 63:\tk_eff = 1.011181\tres = 1.149E-03\n", - "[ NORMAL ] Iteration 64:\tk_eff = 1.012179\tres = 1.067E-03\n", - "[ NORMAL ] Iteration 65:\tk_eff = 1.013107\tres = 9.909E-04\n", - "[ NORMAL ] Iteration 66:\tk_eff = 1.013969\tres = 9.201E-04\n", - "[ NORMAL ] Iteration 67:\tk_eff = 1.014769\tres = 8.542E-04\n", - "[ NORMAL ] Iteration 68:\tk_eff = 1.015513\tres = 7.929E-04\n", - "[ NORMAL ] Iteration 69:\tk_eff = 1.016204\tres = 7.358E-04\n", - "[ NORMAL ] Iteration 70:\tk_eff = 1.016845\tres = 6.828E-04\n", - "[ NORMAL ] Iteration 71:\tk_eff = 1.017440\tres = 6.335E-04\n", - "[ NORMAL ] Iteration 72:\tk_eff = 1.017993\tres = 5.877E-04\n", - "[ NORMAL ] Iteration 73:\tk_eff = 1.018505\tres = 5.451E-04\n", - "[ NORMAL ] Iteration 74:\tk_eff = 1.018980\tres = 5.056E-04\n", - "[ NORMAL ] Iteration 75:\tk_eff = 1.019422\tres = 4.688E-04\n", - "[ NORMAL ] Iteration 76:\tk_eff = 1.019831\tres = 4.347E-04\n", - "[ NORMAL ] Iteration 77:\tk_eff = 1.020210\tres = 4.030E-04\n", - "[ NORMAL ] Iteration 78:\tk_eff = 1.020562\tres = 3.736E-04\n", - "[ NORMAL ] Iteration 79:\tk_eff = 1.020888\tres = 3.463E-04\n", - "[ NORMAL ] Iteration 80:\tk_eff = 1.021190\tres = 3.209E-04\n", - "[ NORMAL ] Iteration 81:\tk_eff = 1.021471\tres = 2.974E-04\n", - "[ NORMAL ] Iteration 82:\tk_eff = 1.021730\tres = 2.756E-04\n", - "[ NORMAL ] Iteration 83:\tk_eff = 1.021971\tres = 2.553E-04\n", - "[ NORMAL ] Iteration 84:\tk_eff = 1.022194\tres = 2.365E-04\n", - "[ NORMAL ] Iteration 85:\tk_eff = 1.022400\tres = 2.191E-04\n", - "[ NORMAL ] Iteration 86:\tk_eff = 1.022592\tres = 2.030E-04\n", - "[ NORMAL ] Iteration 87:\tk_eff = 1.022769\tres = 1.880E-04\n", - "[ NORMAL ] Iteration 88:\tk_eff = 1.022933\tres = 1.741E-04\n", - "[ NORMAL ] Iteration 89:\tk_eff = 1.023085\tres = 1.612E-04\n", - "[ NORMAL ] Iteration 90:\tk_eff = 1.023226\tres = 1.493E-04\n", - "[ NORMAL ] Iteration 91:\tk_eff = 1.023356\tres = 1.382E-04\n", - "[ NORMAL ] Iteration 92:\tk_eff = 1.023477\tres = 1.279E-04\n", - "[ NORMAL ] Iteration 93:\tk_eff = 1.023588\tres = 1.184E-04\n", - "[ NORMAL ] Iteration 94:\tk_eff = 1.023692\tres = 1.097E-04\n", - "[ NORMAL ] Iteration 95:\tk_eff = 1.023788\tres = 1.015E-04\n", - "[ NORMAL ] Iteration 96:\tk_eff = 1.023876\tres = 9.392E-05\n", - "[ NORMAL ] Iteration 97:\tk_eff = 1.023958\tres = 8.694E-05\n", - "[ NORMAL ] Iteration 98:\tk_eff = 1.024034\tres = 8.045E-05\n", - "[ NORMAL ] Iteration 99:\tk_eff = 1.024104\tres = 7.446E-05\n", - "[ NORMAL ] Iteration 100:\tk_eff = 1.024169\tres = 6.890E-05\n", - "[ NORMAL ] Iteration 101:\tk_eff = 1.024229\tres = 6.374E-05\n", - "[ NORMAL ] Iteration 102:\tk_eff = 1.024285\tres = 5.897E-05\n", - "[ NORMAL ] Iteration 103:\tk_eff = 1.024336\tres = 5.457E-05\n", - "[ NORMAL ] Iteration 104:\tk_eff = 1.024384\tres = 5.049E-05\n", - "[ NORMAL ] Iteration 105:\tk_eff = 1.024428\tres = 4.672E-05\n", - "[ NORMAL ] Iteration 106:\tk_eff = 1.024469\tres = 4.321E-05\n", - "[ NORMAL ] Iteration 107:\tk_eff = 1.024506\tres = 3.994E-05\n", - "[ NORMAL ] Iteration 108:\tk_eff = 1.024541\tres = 3.696E-05\n", - "[ NORMAL ] Iteration 109:\tk_eff = 1.024574\tres = 3.418E-05\n", - "[ NORMAL ] Iteration 110:\tk_eff = 1.024603\tres = 3.164E-05\n", - "[ NORMAL ] Iteration 111:\tk_eff = 1.024631\tres = 2.925E-05\n", - "[ NORMAL ] Iteration 112:\tk_eff = 1.024657\tres = 2.705E-05\n", - "[ NORMAL ] Iteration 113:\tk_eff = 1.024680\tres = 2.501E-05\n", - "[ NORMAL ] Iteration 114:\tk_eff = 1.024702\tres = 2.313E-05\n", - "[ NORMAL ] Iteration 115:\tk_eff = 1.024722\tres = 2.140E-05\n", - "[ NORMAL ] Iteration 116:\tk_eff = 1.024741\tres = 1.981E-05\n", - "[ NORMAL ] Iteration 117:\tk_eff = 1.024758\tres = 1.827E-05\n", - "[ NORMAL ] Iteration 118:\tk_eff = 1.024774\tres = 1.690E-05\n", - "[ NORMAL ] Iteration 119:\tk_eff = 1.024789\tres = 1.564E-05\n", - "[ NORMAL ] Iteration 120:\tk_eff = 1.024802\tres = 1.445E-05\n", - "[ NORMAL ] Iteration 121:\tk_eff = 1.024815\tres = 1.338E-05\n", - "[ NORMAL ] Iteration 122:\tk_eff = 1.024827\tres = 1.236E-05\n", - "[ NORMAL ] Iteration 123:\tk_eff = 1.024837\tres = 1.142E-05\n", - "[ NORMAL ] Iteration 124:\tk_eff = 1.024847\tres = 1.057E-05\n" + "[ NORMAL ] Iteration 0:\tk_eff = 0.823436\tres = 0.000E+00\n", + "[ NORMAL ] Iteration 1:\tk_eff = 0.780042\tres = 1.941E-01\n", + "[ NORMAL ] Iteration 2:\tk_eff = 0.739063\tres = 6.559E-02\n", + "[ NORMAL ] Iteration 3:\tk_eff = 0.710328\tres = 5.301E-02\n", + "[ NORMAL ] Iteration 4:\tk_eff = 0.689038\tres = 3.942E-02\n", + "[ NORMAL ] Iteration 5:\tk_eff = 0.674339\tres = 3.021E-02\n", + "[ NORMAL ] Iteration 6:\tk_eff = 0.665075\tres = 2.149E-02\n", + "[ NORMAL ] Iteration 7:\tk_eff = 0.660373\tres = 1.387E-02\n", + "[ NORMAL ] Iteration 8:\tk_eff = 0.659460\tres = 7.242E-03\n", + "[ NORMAL ] Iteration 9:\tk_eff = 0.661679\tres = 1.995E-03\n", + "[ NORMAL ] Iteration 10:\tk_eff = 0.666463\tres = 3.649E-03\n", + "[ NORMAL ] Iteration 11:\tk_eff = 0.673324\tres = 7.367E-03\n", + "[ NORMAL ] Iteration 12:\tk_eff = 0.681842\tres = 1.039E-02\n", + "[ NORMAL ] Iteration 13:\tk_eff = 0.691660\tres = 1.273E-02\n", + "[ NORMAL ] Iteration 14:\tk_eff = 0.702469\tres = 1.447E-02\n", + "[ NORMAL ] Iteration 15:\tk_eff = 0.714008\tres = 1.569E-02\n", + "[ NORMAL ] Iteration 16:\tk_eff = 0.726057\tres = 1.649E-02\n", + "[ NORMAL ] Iteration 17:\tk_eff = 0.738428\tres = 1.693E-02\n", + "[ NORMAL ] Iteration 18:\tk_eff = 0.750965\tres = 1.709E-02\n", + "[ NORMAL ] Iteration 19:\tk_eff = 0.763536\tres = 1.703E-02\n", + "[ NORMAL ] Iteration 20:\tk_eff = 0.776034\tres = 1.679E-02\n", + "[ NORMAL ] Iteration 21:\tk_eff = 0.788369\tres = 1.641E-02\n", + "[ NORMAL ] Iteration 22:\tk_eff = 0.800470\tres = 1.594E-02\n", + "[ NORMAL ] Iteration 23:\tk_eff = 0.812280\tres = 1.539E-02\n", + "[ NORMAL ] Iteration 24:\tk_eff = 0.823753\tres = 1.479E-02\n", + "[ NORMAL ] Iteration 25:\tk_eff = 0.834854\tres = 1.416E-02\n", + "[ NORMAL ] Iteration 26:\tk_eff = 0.845560\tres = 1.351E-02\n", + "[ NORMAL ] Iteration 27:\tk_eff = 0.855851\tres = 1.286E-02\n", + "[ NORMAL ] Iteration 28:\tk_eff = 0.865717\tres = 1.220E-02\n", + "[ NORMAL ] Iteration 29:\tk_eff = 0.875152\tres = 1.156E-02\n", + "[ NORMAL ] Iteration 30:\tk_eff = 0.884153\tres = 1.093E-02\n", + "[ NORMAL ] Iteration 31:\tk_eff = 0.892725\tres = 1.031E-02\n", + "[ NORMAL ] Iteration 32:\tk_eff = 0.900872\tres = 9.722E-03\n", + "[ NORMAL ] Iteration 33:\tk_eff = 0.908602\tres = 9.152E-03\n", + "[ NORMAL ] Iteration 34:\tk_eff = 0.915926\tres = 8.605E-03\n", + "[ NORMAL ] Iteration 35:\tk_eff = 0.922853\tres = 8.083E-03\n", + "[ NORMAL ] Iteration 36:\tk_eff = 0.929399\tres = 7.586E-03\n", + "[ NORMAL ] Iteration 37:\tk_eff = 0.935576\tres = 7.114E-03\n", + "[ NORMAL ] Iteration 38:\tk_eff = 0.941398\tres = 6.666E-03\n", + "[ NORMAL ] Iteration 39:\tk_eff = 0.946880\tres = 6.242E-03\n", + "[ NORMAL ] Iteration 40:\tk_eff = 0.952037\tres = 5.841E-03\n", + "[ NORMAL ] Iteration 41:\tk_eff = 0.956883\tres = 5.463E-03\n", + "[ NORMAL ] Iteration 42:\tk_eff = 0.961434\tres = 5.107E-03\n", + "[ NORMAL ] Iteration 43:\tk_eff = 0.965705\tres = 4.771E-03\n", + "[ NORMAL ] Iteration 44:\tk_eff = 0.969708\tres = 4.456E-03\n", + "[ NORMAL ] Iteration 45:\tk_eff = 0.973460\tres = 4.159E-03\n", + "[ NORMAL ] Iteration 46:\tk_eff = 0.976972\tres = 3.881E-03\n", + "[ NORMAL ] Iteration 47:\tk_eff = 0.980259\tres = 3.620E-03\n", + "[ NORMAL ] Iteration 48:\tk_eff = 0.983333\tres = 3.375E-03\n", + "[ NORMAL ] Iteration 49:\tk_eff = 0.986206\tres = 3.146E-03\n", + "[ NORMAL ] Iteration 50:\tk_eff = 0.988890\tres = 2.932E-03\n", + "[ NORMAL ] Iteration 51:\tk_eff = 0.991396\tres = 2.731E-03\n", + "[ NORMAL ] Iteration 52:\tk_eff = 0.993735\tres = 2.543E-03\n", + "[ NORMAL ] Iteration 53:\tk_eff = 0.995917\tres = 2.368E-03\n", + "[ NORMAL ] Iteration 54:\tk_eff = 0.997952\tres = 2.204E-03\n", + "[ NORMAL ] Iteration 55:\tk_eff = 0.999848\tres = 2.050E-03\n", + "[ NORMAL ] Iteration 56:\tk_eff = 1.001616\tres = 1.907E-03\n", + "[ NORMAL ] Iteration 57:\tk_eff = 1.003262\tres = 1.774E-03\n", + "[ NORMAL ] Iteration 58:\tk_eff = 1.004795\tres = 1.650E-03\n", + "[ NORMAL ] Iteration 59:\tk_eff = 1.006222\tres = 1.534E-03\n", + "[ NORMAL ] Iteration 60:\tk_eff = 1.007550\tres = 1.425E-03\n", + "[ NORMAL ] Iteration 61:\tk_eff = 1.008785\tres = 1.325E-03\n", + "[ NORMAL ] Iteration 62:\tk_eff = 1.009934\tres = 1.231E-03\n", + "[ NORMAL ] Iteration 63:\tk_eff = 1.011002\tres = 1.143E-03\n", + "[ NORMAL ] Iteration 64:\tk_eff = 1.011995\tres = 1.062E-03\n", + "[ NORMAL ] Iteration 65:\tk_eff = 1.012918\tres = 9.859E-04\n", + "[ NORMAL ] Iteration 66:\tk_eff = 1.013775\tres = 9.153E-04\n", + "[ NORMAL ] Iteration 67:\tk_eff = 1.014571\tres = 8.497E-04\n", + "[ NORMAL ] Iteration 68:\tk_eff = 1.015311\tres = 7.886E-04\n", + "[ NORMAL ] Iteration 69:\tk_eff = 1.015997\tres = 7.318E-04\n", + "[ NORMAL ] Iteration 70:\tk_eff = 1.016635\tres = 6.790E-04\n", + "[ NORMAL ] Iteration 71:\tk_eff = 1.017226\tres = 6.300E-04\n", + "[ NORMAL ] Iteration 72:\tk_eff = 1.017775\tres = 5.844E-04\n", + "[ NORMAL ] Iteration 73:\tk_eff = 1.018285\tres = 5.420E-04\n", + "[ NORMAL ] Iteration 74:\tk_eff = 1.018757\tres = 5.026E-04\n", + "[ NORMAL ] Iteration 75:\tk_eff = 1.019195\tres = 4.660E-04\n", + "[ NORMAL ] Iteration 76:\tk_eff = 1.019602\tres = 4.321E-04\n", + "[ NORMAL ] Iteration 77:\tk_eff = 1.019979\tres = 4.005E-04\n", + "[ NORMAL ] Iteration 78:\tk_eff = 1.020328\tres = 3.713E-04\n", + "[ NORMAL ] Iteration 79:\tk_eff = 1.020652\tres = 3.441E-04\n", + "[ NORMAL ] Iteration 80:\tk_eff = 1.020952\tres = 3.189E-04\n", + "[ NORMAL ] Iteration 81:\tk_eff = 1.021230\tres = 2.955E-04\n", + "[ NORMAL ] Iteration 82:\tk_eff = 1.021488\tres = 2.738E-04\n", + "[ NORMAL ] Iteration 83:\tk_eff = 1.021727\tres = 2.536E-04\n", + "[ NORMAL ] Iteration 84:\tk_eff = 1.021948\tres = 2.350E-04\n", + "[ NORMAL ] Iteration 85:\tk_eff = 1.022153\tres = 2.177E-04\n", + "[ NORMAL ] Iteration 86:\tk_eff = 1.022344\tres = 2.016E-04\n", + "[ NORMAL ] Iteration 87:\tk_eff = 1.022520\tres = 1.867E-04\n", + "[ NORMAL ] Iteration 88:\tk_eff = 1.022683\tres = 1.729E-04\n", + "[ NORMAL ] Iteration 89:\tk_eff = 1.022833\tres = 1.601E-04\n", + "[ NORMAL ] Iteration 90:\tk_eff = 1.022973\tres = 1.482E-04\n", + "[ NORMAL ] Iteration 91:\tk_eff = 1.023102\tres = 1.372E-04\n", + "[ NORMAL ] Iteration 92:\tk_eff = 1.023222\tres = 1.271E-04\n", + "[ NORMAL ] Iteration 93:\tk_eff = 1.023333\tres = 1.176E-04\n", + "[ NORMAL ] Iteration 94:\tk_eff = 1.023436\tres = 1.089E-04\n", + "[ NORMAL ] Iteration 95:\tk_eff = 1.023531\tres = 1.008E-04\n", + "[ NORMAL ] Iteration 96:\tk_eff = 1.023619\tres = 9.327E-05\n", + "[ NORMAL ] Iteration 97:\tk_eff = 1.023700\tres = 8.632E-05\n", + "[ NORMAL ] Iteration 98:\tk_eff = 1.023775\tres = 7.988E-05\n", + "[ NORMAL ] Iteration 99:\tk_eff = 1.023845\tres = 7.391E-05\n", + "[ NORMAL ] Iteration 100:\tk_eff = 1.023910\tres = 6.838E-05\n", + "[ NORMAL ] Iteration 101:\tk_eff = 1.023969\tres = 6.329E-05\n", + "[ NORMAL ] Iteration 102:\tk_eff = 1.024024\tres = 5.855E-05\n", + "[ NORMAL ] Iteration 103:\tk_eff = 1.024076\tres = 5.415E-05\n", + "[ NORMAL ] Iteration 104:\tk_eff = 1.024123\tres = 5.011E-05\n", + "[ NORMAL ] Iteration 105:\tk_eff = 1.024166\tres = 4.634E-05\n", + "[ NORMAL ] Iteration 106:\tk_eff = 1.024207\tres = 4.290E-05\n", + "[ NORMAL ] Iteration 107:\tk_eff = 1.024244\tres = 3.966E-05\n", + "[ NORMAL ] Iteration 108:\tk_eff = 1.024279\tres = 3.669E-05\n", + "[ NORMAL ] Iteration 109:\tk_eff = 1.024311\tres = 3.392E-05\n", + "[ NORMAL ] Iteration 110:\tk_eff = 1.024341\tres = 3.137E-05\n", + "[ NORMAL ] Iteration 111:\tk_eff = 1.024368\tres = 2.904E-05\n", + "[ NORMAL ] Iteration 112:\tk_eff = 1.024393\tres = 2.686E-05\n", + "[ NORMAL ] Iteration 113:\tk_eff = 1.024417\tres = 2.481E-05\n", + "[ NORMAL ] Iteration 114:\tk_eff = 1.024438\tres = 2.296E-05\n", + "[ NORMAL ] Iteration 115:\tk_eff = 1.024458\tres = 2.121E-05\n", + "[ NORMAL ] Iteration 116:\tk_eff = 1.024477\tres = 1.961E-05\n", + "[ NORMAL ] Iteration 117:\tk_eff = 1.024494\tres = 1.815E-05\n", + "[ NORMAL ] Iteration 118:\tk_eff = 1.024510\tres = 1.679E-05\n", + "[ NORMAL ] Iteration 119:\tk_eff = 1.024524\tres = 1.551E-05\n", + "[ NORMAL ] Iteration 120:\tk_eff = 1.024538\tres = 1.435E-05\n", + "[ NORMAL ] Iteration 121:\tk_eff = 1.024550\tres = 1.326E-05\n", + "[ NORMAL ] Iteration 122:\tk_eff = 1.024561\tres = 1.226E-05\n", + "[ NORMAL ] Iteration 123:\tk_eff = 1.024572\tres = 1.133E-05\n", + "[ NORMAL ] Iteration 124:\tk_eff = 1.024582\tres = 1.047E-05\n" ] } ], @@ -1483,25 +1378,23 @@ }, { "cell_type": "code", - "execution_count": 40, - "metadata": { - "collapsed": false - }, + "execution_count": 39, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "openmc keff = 1.023307\n", - "openmoc keff = 1.024847\n", - "bias [pcm]: 154.0\n" + "openmc keff = 1.023293\n", + "openmoc keff = 1.024582\n", + "bias [pcm]: 128.8\n" ] } ], "source": [ "# Print report of keff and bias with OpenMC\n", "openmoc_keff = solver.getKeff()\n", - "openmc_keff = sp.k_combined[0]\n", + "openmc_keff = sp.k_combined.nominal_value\n", "bias = (openmoc_keff - openmc_keff) * 1e5\n", "\n", "print('openmc keff = {0:1.6f}'.format(openmc_keff))\n", @@ -1536,10 +1429,8 @@ }, { "cell_type": "code", - "execution_count": 41, - "metadata": { - "collapsed": false - }, + "execution_count": 40, + "metadata": {}, "outputs": [], "source": [ "# Get the OpenMC fission rate mesh tally data\n", @@ -1562,10 +1453,8 @@ }, { "cell_type": "code", - "execution_count": 42, - "metadata": { - "collapsed": false - }, + "execution_count": 41, + "metadata": {}, "outputs": [], "source": [ "# Create OpenMOC Mesh on which to tally fission rates\n", @@ -1594,26 +1483,24 @@ }, { "cell_type": "code", - "execution_count": 43, - "metadata": { - "collapsed": false - }, + "execution_count": 42, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 43, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\nAAAAAElFTkSuQmCC\n", + "image/png": "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\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -1640,9 +1527,9 @@ "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python 3", + "display_name": "openmc", "language": "python", - "name": "python3" + "name": "openmc" }, "language_info": { "codemirror_mode": { @@ -1654,9 +1541,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.0" + "version": "3.6.5" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index a10fae47a..d4d70af71 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -73,10 +73,10 @@ _dll.openmc_tally_set_type.errcheck = _error_handler _SCORES = { -1: 'flux', -2: 'total', -3: 'scatter', -4: 'nu-scatter', - -9: 'absorption', -10: 'fission', -11: 'nu-fission', -12: 'kappa-fission', - -13: 'current', -18: 'events', -19: 'delayed-nu-fission', - -20: 'prompt-nu-fission', -21: 'inverse-velocity', -22: 'fission-q-prompt', - -23: 'fission-q-recoverable', -24: 'decay-rate' + -5: 'absorption', -6: 'fission', -7: 'nu-fission', -8: 'kappa-fission', + -9: 'current', -10: 'events', -11: 'delayed-nu-fission', + -12: 'prompt-nu-fission', -13: 'inverse-velocity', -14: 'fission-q-prompt', + -15: 'fission-q-recoverable', -16: 'decay-rate' } diff --git a/openmc/filter.py b/openmc/filter.py index 2bab17dbb..3180989eb 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -104,10 +104,8 @@ class Filter(IDManagerMixin, metaclass=FilterMeta): return False elif len(self.bins) != len(other.bins): return False - elif not np.allclose(self.bins, other.bins): - return False else: - return True + return np.allclose(self.bins, other.bins) def __ne__(self, other): return not self == other diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index 872eaac9f..ffac647c9 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -10,10 +10,17 @@ from . import Filter class ExpansionFilter(Filter): """Abstract filter class for functional expansions.""" + def __init__(self, order, filter_id=None): self.order = order self.id = filter_id + def __eq__(self, other): + if type(self) is not type(other): + return False + else: + return self.bins == other.bins + @property def order(self): return self._order diff --git a/openmc/material.py b/openmc/material.py index 6bfe58f4a..cfa3eba3c 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -278,8 +278,8 @@ class Material(IDManagerMixin): name = group['name'].value.decode() if 'name' in group else '' density = group['atom_density'].value - nuc_densities = group['nuclide_densities'][...] - nuclides = group['nuclides'].value + if 'nuclide_densities' in group: + nuc_densities = group['nuclide_densities'][...] # Create the Material material = cls(mat_id, name) @@ -295,10 +295,18 @@ class Material(IDManagerMixin): # Set the Material's density to atom/b-cm as used by OpenMC material.set_density(density=density, units='atom/b-cm') - # Add all nuclides to the Material - for fullname, density in zip(nuclides, nuc_densities): - name = fullname.decode().strip() - material.add_nuclide(name, percent=density, percent_type='ao') + if 'nuclides' in group: + nuclides = group['nuclides'].value + # Add all nuclides to the Material + for fullname, density in zip(nuclides, nuc_densities): + name = fullname.decode().strip() + material.add_nuclide(name, percent=density, percent_type='ao') + if 'macroscopics' in group: + macroscopics = group['macroscopics'].value + # Add all macroscopics to the Material + for fullname in macroscopics: + name = fullname.decode().strip() + material.add_macroscopic(name) return material diff --git a/openmc/mesh.py b/openmc/mesh.py index aed7a46d8..c9c76552b 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -182,41 +182,6 @@ class Mesh(IDManagerMixin): return mesh - def cell_generator(self): - """Generator function to traverse through every [i,j,k] index of the - mesh - - For example the following code: - - .. code-block:: python - - for mesh_index in mymesh.cell_generator(): - print(mesh_index) - - will produce the following output for a 3-D 2x2x2 mesh in mymesh:: - - [1, 1, 1] - [2, 1, 1] - [1, 2, 1] - [2, 2, 1] - ... - - - """ - - if len(self.dimension) == 1: - for x in range(self.dimension[0]): - yield [x + 1, 1, 1] - elif len(self.dimension) == 2: - for y in range(self.dimension[1]): - for x in range(self.dimension[0]): - yield [x + 1, y + 1, 1] - else: - for z in range(self.dimension[2]): - for y in range(self.dimension[1]): - for x in range(self.dimension[0]): - yield [x + 1, y + 1, z + 1] - def to_xml_element(self): """Return XML representation of the mesh @@ -280,12 +245,14 @@ class Mesh(IDManagerMixin): cv.check_value('bc', entry, ['transmission', 'vacuum', 'reflective', 'periodic']) + n_dim = len(self.dimension) + # Build the cell which will contain the lattice xplanes = [openmc.XPlane(x0=self.lower_left[0], boundary_type=bc[0]), openmc.XPlane(x0=self.upper_right[0], boundary_type=bc[1])] - if len(self.dimension) == 1: + if n_dim == 1: yplanes = [openmc.YPlane(y0=-1e10, boundary_type='reflective'), openmc.YPlane(y0=1e10, boundary_type='reflective')] else: @@ -294,7 +261,7 @@ class Mesh(IDManagerMixin): openmc.YPlane(y0=self.upper_right[1], boundary_type=bc[3])] - if len(self.dimension) <= 2: + if n_dim <= 2: # Would prefer to have the z ranges be the max supported float, but # these values are apparently different between python and Fortran. # Choosing a safe and sane default. @@ -314,12 +281,12 @@ class Mesh(IDManagerMixin): (+yplanes[0] & -yplanes[1]) & (+zplanes[0] & -zplanes[1])) - # Build the universes which will be used for each of the [i,j,k] + # Build the universes which will be used for each of the (i,j,k) # locations within the mesh. # We will concurrently build cells to assign to these universes cells = [] universes = [] - for [i, j, k] in self.cell_generator(): + for index in self.indices: cells.append(openmc.Cell()) universes.append(openmc.Universe()) universes[-1].add_cell(cells[-1]) @@ -329,7 +296,24 @@ class Mesh(IDManagerMixin): # Assign the universe and rotate to match the indexing expected for # the lattice - lattice.universes = np.rot90(np.reshape(universes, self.dimension)) + if n_dim == 1: + universe_array = np.array([universes]) + elif n_dim == 2: + universe_array = np.empty(self.dimension, dtype=openmc.Universe) + i = 0 + for y in range(self.dimension[1] - 1, -1, -1): + for x in range(self.dimension[0]): + universe_array[y][x] = universes[i] + i += 1 + else: + universe_array = np.empty(self.dimension, dtype=openmc.Universe) + i = 0 + for z in range(self.dimension[2]): + for y in range(self.dimension[1] - 1, -1, -1): + for x in range(self.dimension[0]): + universe_array[z][y][x] = universes[i] + i += 1 + lattice.universes = universe_array if self.width is not None: lattice.pitch = self.width @@ -337,9 +321,9 @@ class Mesh(IDManagerMixin): dx = ((self.upper_right[0] - self.lower_left[0]) / self.dimension[0]) - if len(self.dimension) == 1: + if n_dim == 1: lattice.pitch = [dx] - elif len(self.dimension) == 2: + elif n_dim == 2: dy = ((self.upper_right[1] - self.lower_left[1]) / self.dimension[1]) lattice.pitch = [dx, dy] diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index ed8e3f076..7b75d90ff 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -1220,9 +1220,8 @@ class Library(object): xs_type = 'macro' # Initialize file - mgxs_file = openmc.MGXSLibrary(self.energy_groups, - num_delayed_groups=\ - self.num_delayed_groups) + mgxs_file = openmc.MGXSLibrary( + self.energy_groups, num_delayed_groups=self.num_delayed_groups) if self.domain_type == 'mesh': # Create the xsdata objects and add to the mgxs_file @@ -1231,7 +1230,7 @@ class Library(object): if self.by_nuclide: raise NotImplementedError("Mesh domains do not currently " "support nuclidic tallies") - for subdomain in domain.cell_generator(): + for subdomain in domain.indices: # Build & add metadata to XSdata object if xsdata_names is None: xsdata_name = 'set' + str(i + 1) @@ -1346,7 +1345,7 @@ class Library(object): geometry.root_universe = root materials = openmc.Materials() - for i, subdomain in enumerate(self.domains[0].cell_generator()): + for i, subdomain in enumerate(self.domains[0].indices): xsdata = mgxs_file.xsdatas[i] # Build the macroscopic and assign it to the cell of @@ -1401,24 +1400,16 @@ class Library(object): The rules to check include: - - Either total or transport should be present. + - Either total or transport must be present. - Both can be available if one wants, but we should use whatever corresponds to Library.correction (if P0: transport) - - Absorption and total (or transport) are required. + - Absorption is required. - A nu-fission cross section and chi values are not required as a fixed source problem could be the target. - Fission and kappa-fission are not required as they are only needed to support tallies the user may wish to request. - - A nu-scatter matrix is required. - - - Having a multiplicity matrix is preferred. - - Having both nu-scatter (of any order) and scatter - (at least isotropic) matrices is the second choice. - - If only nu-scatter, need total (not transport), to - be used in adjusting absorption - (i.e., reduced_abs = tot - nuscatt) See also -------- @@ -1428,36 +1419,51 @@ class Library(object): """ error_flag = False + + # if correction is 'P0', then transport must be provided + # otherwise total must be provided + if self.correction == 'P0': + if ('transport' not in self.mgxs_types and + 'nu-transport' not in self.mgxs_types): + error_flag = True + warn('If the "correction" parameter is "P0", then a ' + '"transport" or "nu-transport" MGXS type is required.') + else: + if 'total' not in self.mgxs_types: + error_flag = True + warn('If the "correction" parameter is None, then a ' + '"total" MGXS type is required.') + + # Check consistency of "nu-transport" and "nu-scatter" + if 'nu-transport' in self.mgxs_types: + if not ('nu-scatter matrix' in self.mgxs_types or + 'consistent nu-scatter matrix' in self.mgxs_types): + error_flag = True + warn('If a "nu-transport" MGXS type is used then a ' + '"nu-scatter matrix" or "consistent nu-scatter matrix" ' + 'must also be used.') + elif 'transport' in self.mgxs_types: + if not ('scatter matrix' in self.mgxs_types or + 'consistent scatter matrix' in self.mgxs_types): + error_flag = True + warn('If a "transport" MGXS type is used then a ' + '"scatter matrix" or "consistent scatter matrix" ' + 'must also be used.') + + # Make sure there is some kind of a scattering matrix data + if 'nu-scatter matrix' not in self.mgxs_types and \ + 'consistent nu-scatter matrix' not in self.mgxs_types and \ + 'scatter matrix' not in self.mgxs_types and \ + 'consistent scatter matrix' not in self.mgxs_types: + error_flag = True + warn('A "nu-scatter matrix", "consistent nu-scatter matrix", ' + '"scatter matrix", or "consistent scatter matrix" MGXS ' + 'type is required.') + # Ensure absorption is present if 'absorption' not in self.mgxs_types: error_flag = True warn('An "absorption" MGXS type is required but not provided.') - # Ensure nu-scattering matrix is required - if 'nu-scatter matrix' not in self.mgxs_types and \ - 'consistent nu-scatter matrix' not in self.mgxs_types: - error_flag = True - warn('A "nu-scatter matrix" MGXS type is required but not provided.') - else: - # Ok, now see the status of scatter and/or multiplicity - if 'scatter matrix' not in self.mgxs_types or \ - 'consistent scatter matrix' not in self.mgxs_types and \ - 'multiplicity matrix' not in self.mgxs_types: - # We dont have data needed for multiplicity matrix, therefore - # we need total, and not transport. - if 'total' not in self.mgxs_types: - error_flag = True - warn('A "total" MGXS type is required if a ' - 'scattering matrix is not provided.') - # Total or transport can be present, but if using - # self.correction=="P0", then we should use transport. - if self.correction == "P0" and 'nu-transport' not in self.mgxs_types: - error_flag = True - warn('A "nu-transport" MGXS type is required since a "P0" ' - 'correction is applied, but a "nu-transport" MGXS is ' - 'not provided.') - elif self.correction is None and 'total' not in self.mgxs_types: - error_flag = True - warn('A "total" MGXS type is required, but not provided.') if error_flag: raise ValueError('Invalid MGXS configuration encountered.') diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 5bc00cbc9..81b4420d9 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -1923,6 +1923,10 @@ class MGXS(metaclass=ABCMeta): if 'group out' in df: df = df[df['group out'].isin(groups)] + # Add the Legendre bin to the column if it exists + if 'legendre' in df: + columns += ['legendre'] + # If user requested micro cross sections, divide out the atom densities if xs_type == 'micro': if self.by_nuclide: @@ -2717,9 +2721,9 @@ class TransportXS(MGXS): @property def scores(self): if not self.nu: - return ['flux', 'total', 'flux', 'scatter-1'] + return ['flux', 'total', 'flux', 'scatter'] else: - return ['flux', 'total', 'flux', 'nu-scatter-1'] + return ['flux', 'total', 'flux', 'nu-scatter'] @property def tally_keys(self): @@ -2730,8 +2734,9 @@ class TransportXS(MGXS): group_edges = self.energy_groups.group_edges energy_filter = openmc.EnergyFilter(group_edges) energyout_filter = openmc.EnergyoutFilter(group_edges) + p1_filter = openmc.LegendreFilter(1) filters = [[energy_filter], [energy_filter], - [energy_filter], [energyout_filter]] + [energy_filter], [energyout_filter, p1_filter]] return self._add_angle_filters(filters) @@ -2739,12 +2744,18 @@ class TransportXS(MGXS): def rxn_rate_tally(self): if self._rxn_rate_tally is None: # Switch EnergyoutFilter to EnergyFilter. - old_filt = self.tallies['scatter-1'].filters[-1] + p1_tally = self.tallies['scatter-1'] + old_filt = p1_tally.filters[-2] new_filt = openmc.EnergyFilter(old_filt.values) - self.tallies['scatter-1'].filters[-1] = new_filt + p1_tally.filters[-2] = new_filt - self._rxn_rate_tally = \ - self.tallies['total'] - self.tallies['scatter-1'] + # Slice Legendre expansion filter and change name of score + p1_tally = p1_tally.get_slice(filters=[openmc.LegendreFilter], + filter_bins=[('P1',)], + squeeze=True) + p1_tally.scores = ['scatter-1'] + + self._rxn_rate_tally = self.tallies['total'] - p1_tally self._rxn_rate_tally.sparse = self.sparse return self._rxn_rate_tally @@ -2758,15 +2769,22 @@ class TransportXS(MGXS): raise ValueError(msg) # Switch EnergyoutFilter to EnergyFilter. - old_filt = self.tallies['scatter-1'].filters[-1] + p1_tally = self.tallies['scatter-1'] + old_filt = p1_tally.filters[-2] new_filt = openmc.EnergyFilter(old_filt.values) - self.tallies['scatter-1'].filters[-1] = new_filt + p1_tally.filters[-2] = new_filt + + # Slice Legendre expansion filter and change name of score + p1_tally = p1_tally.get_slice(filters=[openmc.LegendreFilter], + filter_bins=[('P1',)], + squeeze=True) + p1_tally.scores = ['scatter-1'] # Compute total cross section total_xs = self.tallies['total'] / self.tallies['flux (tracklength)'] # Compute transport correction term - trans_corr = self.tallies['scatter-1'] / self.tallies['flux (analog)'] + trans_corr = p1_tally / self.tallies['flux (analog)'] # Compute the transport-corrected total cross section self._xs_tally = total_xs - trans_corr @@ -3510,6 +3528,7 @@ class ScatterXS(MGXS): self._estimator = 'analog' self._valid_estimators = ['analog'] + class ScatterMatrixXS(MatrixMGXS): r"""A scattering matrix multi-group cross section with the cosine of the change-in-angle represented as one or more Legendre moments or a histogram. @@ -3598,10 +3617,10 @@ class ScatterMatrixXS(MatrixMGXS): name : str, optional Name of the multi-group cross section. Used as a label to identify tallies in OpenMC 'tallies.xml' file. - num_polar : Integral, optional + num_polar : int, optional Number of equi-width polar angle bins for angle discretization; defaults to one bin - num_azimuthal : Integral, optional + num_azimuthal : int, optional Number of equi-width azimuthal angle bins for angle discretization; defaults to one bin nu : bool @@ -3647,9 +3666,9 @@ class ScatterMatrixXS(MatrixMGXS): Domain type for spatial homogenization energy_groups : openmc.mgxs.EnergyGroups Energy group structure for energy condensation - num_polar : Integral + num_polar : int Number of equi-width polar angle bins for angle discretization - num_azimuthal : Integral + num_azimuthal : int Number of equi-width azimuthal angle bins for angle discretization tally_trigger : openmc.Trigger An (optional) tally precision trigger given to each tally used to @@ -3767,38 +3786,25 @@ class ScatterMatrixXS(MatrixMGXS): def scores(self): if self.formulation == 'simple': - scores = ['flux'] - - if self.scatter_format == 'legendre': - if self.legendre_order == 0: - scores.append('{}-0'.format(self.rxn_type)) - if self.correction: - scores.append('{}-1'.format(self.rxn_type)) - else: - scores.append('{}-P{}'.format(self.rxn_type, self.legendre_order)) - elif self.scatter_format == 'histogram': - scores += [self.rxn_type] + scores = ['flux', self.rxn_type] else: # Add scores for groupwise scattering cross section scores = ['flux', 'scatter'] # Add scores for group-to-group scattering probability matrix - if self.scatter_format == 'legendre': - if self.legendre_order == 0: - scores.append('scatter-0') - else: - scores.append('scatter-P{}'.format(self.legendre_order)) - elif self.scatter_format == 'histogram': - scores.append('scatter-0') + # these scores also contain the angular information, whether it be + # Legendre expansion or histogram bins + scores.append('scatter') - # Add scores for multiplicity matrix + # Add scores for multiplicity matrix; scatter info for the + # denominator will come from the previous score if self.nu: - scores.extend(['nu-scatter-0', 'scatter-0']) + scores.append('nu-scatter') # Add scores for transport correction if self.correction == 'P0' and self.legendre_order == 0: - scores.extend(['{}-1'.format(self.rxn_type), 'flux']) + scores.extend([self.rxn_type, 'flux']) return scores @@ -3811,15 +3817,15 @@ class ScatterMatrixXS(MatrixMGXS): tally_keys = ['flux (tracklength)', 'scatter'] # Add keys for group-to-group scattering probability matrix - tally_keys.append('scatter-P{}'.format(self.legendre_order)) + tally_keys.append('scatter matrix') # Add keys for multiplicity matrix if self.nu: - tally_keys.extend(['nu-scatter-0', 'scatter-0']) + tally_keys.extend(['nu-scatter']) # Add keys for transport correction if self.correction == 'P0' and self.legendre_order == 0: - tally_keys.extend(['{}-1'.format(self.rxn_type), 'flux (analog)']) + tally_keys.extend(['correction', 'flux (analog)']) return tally_keys @@ -3836,7 +3842,7 @@ class ScatterMatrixXS(MatrixMGXS): # Add estimators for multiplicity matrix if self.nu: - estimators.extend(['analog', 'analog']) + estimators.extend(['analog']) # Add estimators for transport correction if self.correction == 'P0' and self.legendre_order == 0: @@ -3853,13 +3859,15 @@ class ScatterMatrixXS(MatrixMGXS): if self.scatter_format == 'legendre': if self.correction == 'P0' and self.legendre_order == 0: - filters = [[energy], [energy, energyout], [energyout]] + angle_filter = openmc.LegendreFilter(order=1) else: - filters = [[energy], [energy, energyout]] + angle_filter = \ + openmc.LegendreFilter(order=self.legendre_order) elif self.scatter_format == 'histogram': bins = np.linspace(-1., 1., num=self.histogram_bins + 1, endpoint=True) - filters = [[energy], [energy, energyout, openmc.MuFilter(bins)]] + angle_filter = openmc.MuFilter(bins) + filters = [[energy], [energy, energyout, angle_filter]] else: group_edges = self.energy_groups.group_edges @@ -3871,19 +3879,21 @@ class ScatterMatrixXS(MatrixMGXS): # Group-to-group scattering probability matrix if self.scatter_format == 'legendre': - filters.append([energy, energyout]) + angle_filter = openmc.LegendreFilter(order=self.legendre_order) elif self.scatter_format == 'histogram': bins = np.linspace(-1., 1., num=self.histogram_bins + 1, endpoint=True) - filters.append([energy, energyout, openmc.MuFilter(bins)]) + angle_filter = openmc.MuFilter(bins) + filters.append([energy, energyout, angle_filter]) # Multiplicity matrix if self.nu: - filters.extend([[energy, energyout], [energy, energyout]]) + filters.extend([[energy, energyout]]) # Add filters for transport correction if self.correction == 'P0' and self.legendre_order == 0: - filters.extend([[energyout], [energy]]) + filters.extend([[energyout, openmc.LegendreFilter(1)], + [energy]]) return self._add_angle_filters(filters) @@ -3894,27 +3904,39 @@ class ScatterMatrixXS(MatrixMGXS): if self.formulation == 'simple': if self.scatter_format == 'legendre': - # If using P0 correction subtract scatter-1 from the diagonal + # If using P0 correction subtract P2 scatter from the diag. if self.correction == 'P0' and self.legendre_order == 0: - scatter_p0 = self.tallies['{}-0'.format(self.rxn_type)] - scatter_p1 = self.tallies['{}-1'.format(self.rxn_type)] - energy_filter = scatter_p0.find_filter(openmc.EnergyFilter) + scatter_p0 = self.tallies[self.rxn_type].get_slice( + filters=[openmc.LegendreFilter], + filter_bins=[('P0',)]) + scatter_p1 = self.tallies[self.rxn_type].get_slice( + filters=[openmc.LegendreFilter], + filter_bins=[('P1',)]) - # Transform scatter-p1 tally into an energyin/out matrix + # Set the Legendre order of these tallies to be 0 + # so they can be subtracted + legendre = openmc.LegendreFilter(order=0) + scatter_p0.filters[-1] = legendre + scatter_p1.filters[-1] = legendre + + scatter_p1 = scatter_p1.summation( + filter_type=openmc.EnergyFilter, + remove_filter=True) + + energy_filter = \ + scatter_p0.find_filter(openmc.EnergyFilter) + + # Transform scatter-p1 into an energyin/out matrix # to match scattering matrix shape for tally arithmetic energy_filter = copy.deepcopy(energy_filter) - scatter_p1 = scatter_p1.diagonalize_filter(energy_filter) + scatter_p1 = \ + scatter_p1.diagonalize_filter(energy_filter) + self._rxn_rate_tally = scatter_p0 - scatter_p1 - # Extract scattering moment reaction rate Tally - elif self.legendre_order == 0: - tally_key = '{}-{}'.format(self.rxn_type, - self.legendre_order) - self._rxn_rate_tally = self.tallies[tally_key] + # Otherwise, extract scattering moment reaction rate Tally else: - tally_key = '{}-P{}'.format(self.rxn_type, - self.legendre_order) - self._rxn_rate_tally = self.tallies[tally_key] + self._rxn_rate_tally = self.tallies[self.rxn_type] elif self.scatter_format == 'histogram': # Extract scattering rate distribution tally self._rxn_rate_tally = self.tallies[self.rxn_type] @@ -3941,35 +3963,24 @@ class ScatterMatrixXS(MatrixMGXS): self._xs_tally = MGXS.xs_tally.fget(self) else: - # Compute scattering probability matrix - energyout_bins = [self.energy_groups.get_group_bounds(i) - for i in range(self.num_groups, 0, -1)] - tally_key = 'scatter-P{}'.format(self.legendre_order) + # Compute scattering probability matrixS + tally_key = 'scatter matrix' # Compute normalization factor summed across outgoing energies - norm = self.tallies[tally_key].get_slice(scores=['scatter-0']) - norm = norm.summation( - filter_type=openmc.EnergyoutFilter, filter_bins=energyout_bins) - - # Remove the AggregateFilter summed across energyout bins - norm._filters = norm._filters[:2] + if self.scatter_format == 'legendre': + norm = self.tallies[tally_key].get_slice( + scores=['scatter'], + filters=[openmc.LegendreFilter], + filter_bins=[('P0',)], squeeze=True) # Compute normalization factor summed across outgoing mu bins - if self.scatter_format == 'histogram': - - # (Re-)append the MuFilter which was removed above - mu_bins = np.linspace( - -1., 1., num=self.histogram_bins + 1, endpoint=True) - norm._filters.append(openmc.MuFilter(mu_bins)) - - # Sum across all mu bins - mu_bins = [(mu_bins[i], mu_bins[i+1]) for - i in range(self.histogram_bins)] + elif self.scatter_format == 'histogram': + norm = self.tallies[tally_key].get_slice( + scores=['scatter']) norm = norm.summation( - filter_type=openmc.MuFilter, filter_bins=mu_bins) - - # Remove the AggregateFilter summed across mu bins - norm._filters = norm._filters[:2] + filter_type=openmc.MuFilter, remove_filter=True) + norm = norm.summation(filter_type=openmc.EnergyoutFilter, + remove_filter=True) # Compute groupwise scattering cross section self._xs_tally = self.tallies['scatter'] * \ @@ -3981,15 +3992,36 @@ class ScatterMatrixXS(MatrixMGXS): # Multiply by the multiplicity matrix if self.nu: - numer = self.tallies['nu-scatter-0'] - denom = self.tallies['scatter-0'] + numer = self.tallies['nu-scatter'] + # Get the denominator + if self.scatter_format == 'legendre': + denom = self.tallies[tally_key].get_slice( + scores=['scatter'], + filters=[openmc.LegendreFilter], + filter_bins=[('P0',)], squeeze=True) + + # Compute normalization factor summed across mu bins + elif self.scatter_format == 'histogram': + denom = self.tallies[tally_key].get_slice( + scores=['scatter']) + + # Sum across all mu bins + denom = denom.summation( + filter_type=openmc.MuFilter, remove_filter=True) + self._xs_tally *= (numer / denom) # If using P0 correction subtract scatter-1 from the diagonal if self.correction == 'P0' and self.legendre_order == 0: - scatter_p1 = self.tallies['{}-1'.format(self.rxn_type)] + scatter_p1 = self.tallies['correction'].get_slice( + filters=[openmc.LegendreFilter], filter_bins=[('P1',)]) flux = self.tallies['flux (analog)'] + # Set the Legendre order of the P1 tally to be 0 + # so it can be subtracted + legendre = openmc.LegendreFilter(order=0) + scatter_p1.filters[-1] = legendre + # Transform scatter-p1 tally into an energyin/out matrix # to match scattering matrix shape for tally arithmetic energy_filter = flux.find_filter(openmc.EnergyFilter) @@ -4005,6 +4037,20 @@ class ScatterMatrixXS(MatrixMGXS): self._compute_xs() + # Force the angle filter to be the last filter + if self.scatter_format == 'histogram': + angle_filter = self._xs_tally.find_filter(openmc.MuFilter) + else: + angle_filter = \ + self._xs_tally.find_filter(openmc.LegendreFilter) + angle_filter_index = self._xs_tally.filters.index(angle_filter) + # If the angle filter index is not last, then make it last + if angle_filter_index != len(self._xs_tally.filters) - 1: + energyout_filter = \ + self._xs_tally.find_filter(openmc.EnergyoutFilter) + self._xs_tally._swap_filters(energyout_filter, + angle_filter) + return self._xs_tally @nu.setter @@ -4125,16 +4171,6 @@ class ScatterMatrixXS(MatrixMGXS): self._rxn_rate_tally = None self._loaded_sp = False - if self.scatter_format == 'legendre': - # Expand scores to match the format in the statepoint - # e.g., "scatter-P2" -> "scatter-0", "scatter-1", "scatter-2" - for tally_key, tally in self.tallies.items(): - if 'scatter-P' in tally.scores[0]: - score_prefix = tally.scores[0].split('P')[0] - self.tallies[tally_key].scores = \ - [score_prefix + '{}'.format(i) - for i in range(self.legendre_order + 1)] - super().load_from_statepoint(statepoint) def get_slice(self, nuclides=[], in_groups=[], out_groups=[], @@ -4187,12 +4223,11 @@ class ScatterMatrixXS(MatrixMGXS): slice_xs.legendre_order = legendre_order # Slice the scattering tally - tally_key = '{}-P{}'.format(self.rxn_type, self.legendre_order) - expand_scores = \ - [self.rxn_type + '-{}'.format(i) - for i in range(self.legendre_order + 1)] - slice_xs.tallies[tally_key] = \ - slice_xs.tallies[tally_key].get_slice(scores=expand_scores) + filter_bins = [tuple(['P{}'.format(i) + for i in range(self.legendre_order + 1)])] + slice_xs.tallies[self.rxn_type] = \ + slice_xs.tallies[self.rxn_type].get_slice( + filters=[openmc.LegendreFilter], filter_bins=filter_bins) # Slice outgoing energy groups if needed if len(out_groups) != 0: @@ -4206,7 +4241,8 @@ class ScatterMatrixXS(MatrixMGXS): for tally_type, tally in slice_xs.tallies.items(): if tally.contains_filter(openmc.EnergyoutFilter): tally_slice = tally.get_slice( - filters=[openmc.EnergyoutFilter], filter_bins=filter_bins) + filters=[openmc.EnergyoutFilter], + filter_bins=filter_bins) slice_xs.tallies[tally_type] = tally_slice slice_xs.sparse = self.sparse @@ -4317,14 +4353,19 @@ class ScatterMatrixXS(MatrixMGXS): filter_bins.append((self.energy_groups.get_group_bounds(group),)) # Construct CrossScore for requested scattering moment - if moment != 'all' and self.scatter_format == 'legendre': - cv.check_type('moment', moment, Integral) - cv.check_greater_than('moment', moment, 0, equality=True) - cv.check_less_than( - 'moment', moment, self.legendre_order, equality=True) - scores = [self.xs_tally.scores[moment]] + if self.scatter_format == 'legendre': + if moment != 'all': + cv.check_type('moment', moment, Integral) + cv.check_greater_than('moment', moment, 0, equality=True) + cv.check_less_than( + 'moment', moment, self.legendre_order, equality=True) + filters.append(openmc.LegendreFilter) + filter_bins.append(('P{}'.format(moment),)) + num_angle_bins = 1 + else: + num_angle_bins = self.legendre_order + 1 else: - scores = [] + num_angle_bins = self.histogram_bins # Construct a collection of the nuclides to retrieve from the xs tally if self.by_nuclide: @@ -4336,6 +4377,7 @@ class ScatterMatrixXS(MatrixMGXS): query_nuclides = ['total'] # Use tally summation if user requested the sum for all nuclides + scores = self.xs_tally.scores if nuclides == 'sum' or nuclides == ['sum']: xs_tally = self.xs_tally.summation(nuclides=query_nuclides) xs = xs_tally.get_values(scores=scores, filters=filters, @@ -4367,24 +4409,15 @@ class ScatterMatrixXS(MatrixMGXS): else: num_out_groups = len(out_groups) - if self.scatter_format == 'histogram': - num_mu_bins = self.histogram_bins - else: - num_mu_bins = 1 - # Reshape tally data array with separate axes for domain and energy # Accomodate the polar and azimuthal bins if needed - num_subdomains = int(xs.shape[0] / (num_mu_bins * num_in_groups * + num_subdomains = int(xs.shape[0] / (num_angle_bins * num_in_groups * num_out_groups * self.num_polar * self.num_azimuthal)) if self.num_polar > 1 or self.num_azimuthal > 1: - if self.scatter_format == 'histogram': - new_shape = (self.num_polar, self.num_azimuthal, - num_subdomains, num_in_groups, num_out_groups, - num_mu_bins) - else: - new_shape = (self.num_polar, self.num_azimuthal, - num_subdomains, num_in_groups, num_out_groups) + new_shape = (self.num_polar, self.num_azimuthal, + num_subdomains, num_in_groups, num_out_groups, + num_angle_bins) new_shape += xs.shape[1:] xs = np.reshape(xs, new_shape) @@ -4397,11 +4430,9 @@ class ScatterMatrixXS(MatrixMGXS): if order_groups == 'increasing': xs = xs[:, :, :, ::-1, ::-1, ...] else: - if self.scatter_format == 'histogram': - new_shape = (num_subdomains, num_in_groups, num_out_groups, - num_mu_bins) - else: - new_shape = (num_subdomains, num_in_groups, num_out_groups) + new_shape = (num_subdomains, num_in_groups, num_out_groups, + num_angle_bins) + new_shape += xs.shape[1:] xs = np.reshape(xs, new_shape) @@ -4416,88 +4447,12 @@ class ScatterMatrixXS(MatrixMGXS): if squeeze: # We want to squeeze out everything but the angles, in_groups, - # out_groups, and, if needed, num_mu_bins dimension. These must + # out_groups, and, if needed, num_angle_bins dimension. These must # not be squeezed so 1-group, 1-angle problems have the correct # shape. xs = self._squeeze_xs(xs) return xs - def get_pandas_dataframe(self, groups='all', nuclides='all', moment='all', - xs_type='macro', paths=True): - """Build a Pandas DataFrame for the MGXS data. - - This method leverages :meth:`openmc.Tally.get_pandas_dataframe`, but - renames the columns with terminology appropriate for cross section data. - - Parameters - ---------- - groups : Iterable of Integral or 'all' - Energy groups of interest. Defaults to 'all'. - nuclides : Iterable of str or 'all' or 'sum' - The nuclides of the cross-sections to include in the dataframe. This - may be a list of nuclide name strings (e.g., ['U235', 'U238']). - The special string 'all' will include the cross sections for all - nuclides in the spatial domain. The special string 'sum' will - include the cross sections summed over all nuclides. Defaults - to 'all'. - moment : int or 'all' - The scattering matrix moment to return. All moments will be - returned if the moment is 'all' (default); otherwise, a specific - moment will be returned. - xs_type: {'macro', 'micro'} - Return macro or micro cross section in units of cm^-1 or barns. - Defaults to 'macro'. - paths : bool, optional - Construct columns for distribcell tally filters (default is True). - The geometric information in the Summary object is embedded into a - Multi-index column with a geometric "path" to each distribcell - instance. - - Returns - ------- - pandas.DataFrame - A Pandas DataFrame for the cross section data. - - Raises - ------ - ValueError - When this method is called before the multi-group cross section is - computed from tally data. - - """ - - df = super().get_pandas_dataframe(groups, nuclides, xs_type, paths) - - if self.scatter_format == 'legendre': - # Add a moment column to dataframe - if self.legendre_order > 0: - # Insert a column corresponding to the Legendre moments - moments = ['P{}'.format(i) - for i in range(self.legendre_order + 1)] - moments = np.tile(moments, int(df.shape[0] / len(moments))) - df['moment'] = moments - - # Place the moment column before the mean column - columns = df.columns.tolist() - mean_index \ - = [i for i, s in enumerate(columns) if 'mean' in s][0] - if self.domain_type == 'mesh': - df = df[columns[:mean_index] + [('moment', '')] + - columns[mean_index:-1]] - else: - df = df[columns[:mean_index] + ['moment'] + - columns[mean_index:-1]] - - # Select rows corresponding to requested scattering moment - if moment != 'all': - cv.check_type('moment', moment, Integral) - cv.check_greater_than('moment', moment, 0, equality=True) - cv.check_less_than( - 'moment', moment, self.legendre_order, equality=True) - df = df[df['moment'] == 'P{}'.format(moment)] - - return df - def print_xs(self, subdomains='all', nuclides='all', xs_type='macro', moment=0): """Prints a string representation for the multi-group cross section. @@ -4511,8 +4466,9 @@ class ScatterMatrixXS(MatrixMGXS): The nuclides of the cross-sections to include in the report. This may be a list of nuclide name strings (e.g., ['U235', 'U238']). The special string 'all' will report the cross sections for all - nuclides in the spatial domain. The special string 'sum' will report - the cross sections summed over all nuclides. Defaults to 'all'. + nuclides in the spatial domain. The special string 'sum' will + report the cross sections summed over all nuclides. Defaults to + 'all'. xs_type: {'macro', 'micro'} Return the macro or micro cross section in units of cm^-1 or barns. Defaults to 'macro'. @@ -4986,14 +4942,9 @@ class ScatterProbabilityMatrix(MatrixMGXS): def xs_tally(self): if self._xs_tally is None: - energyout_bins = [self.energy_groups.get_group_bounds(i) - for i in range(self.num_groups, 0, -1)] norm = self.rxn_rate_tally.get_slice(scores=[self.rxn_type]) norm = norm.summation( - filter_type=openmc.EnergyoutFilter, filter_bins=energyout_bins) - - # Remove the AggregateFilter summed across energyout bins - norm._filters = norm._filters[:2] + filter_type=openmc.EnergyoutFilter, remove_filter=True) # Compute the group-to-group probabilities self._xs_tally = self.tallies[self.rxn_type] / norm diff --git a/openmc/plotter.py b/openmc/plotter.py index 191b50c56..597a87750 100644 --- a/openmc/plotter.py +++ b/openmc/plotter.py @@ -170,13 +170,13 @@ def plot_xs(this, types, divisor_types=None, temperature=294., data_type=None, data = data_new else: # Calculate for MG cross sections - E, data = calculate_mgxs(this, types, orders, temperature, + E, data = calculate_mgxs(this, data_type, types, orders, temperature, mg_cross_sections, ce_cross_sections, enrichment) if divisor_types: cv.check_length('divisor types', divisor_types, len(types)) - Ediv, data_div = calculate_mgxs(this, divisor_types, + Ediv, data_div = calculate_mgxs(this, data_type, divisor_types, divisor_orders, temperature, mg_cross_sections, ce_cross_sections, enrichment) @@ -243,7 +243,7 @@ def calculate_cexs(this, data_type, types, temperature=294., sab_name=None, Parameters ---------- - this : str or openmc.Material + this : {str, openmc.Nuclide, openmc.Element, openmc.Material} Object to source data from data_type : {'nuclide', 'element', material'} Type of object to plot @@ -280,7 +280,11 @@ def calculate_cexs(this, data_type, types, temperature=294., sab_name=None, cv.check_type('enrichment', enrichment, Real) if data_type == 'nuclide': - energy_grid, xs = _calculate_cexs_nuclide(this, types, temperature, + if isinstance(this, str): + nuc = openmc.Nuclide(this) + else: + nuc = this + energy_grid, xs = _calculate_cexs_nuclide(nuc, types, temperature, sab_name, cross_sections) # Convert xs (Iterable of Callable) to a grid of cross section values # calculated on @ the points in energy_grid for consistency with the @@ -289,10 +293,15 @@ def calculate_cexs(this, data_type, types, temperature=294., sab_name=None, for line in range(len(types)): data[line, :] = xs[line](energy_grid) elif data_type == 'element': - energy_grid, data = _calculate_cexs_elem_mat(this, types, temperature, + if isinstance(this, str): + elem = openmc.Element(this) + else: + elem = this + energy_grid, data = _calculate_cexs_elem_mat(elem, types, temperature, cross_sections, sab_name, enrichment) elif data_type == 'material': + cv.check_type('this', this, openmc.Material) energy_grid, data = _calculate_cexs_elem_mat(this, types, temperature, cross_sections) else: @@ -518,10 +527,8 @@ def _calculate_cexs_elem_mat(this, types, temperature=294., T = this.temperature else: T = temperature - data_type = 'material' else: T = temperature - data_type = 'element' # Load the library library = openmc.data.DataLibrary.from_xml(cross_sections) @@ -571,7 +578,7 @@ def _calculate_cexs_elem_mat(this, types, temperature=294., name = nuclide[0] nuc = nuclide[1] sab_tab = sabs[name] - temp_E, temp_xs = calculate_cexs(nuc, data_type, types, T, sab_tab, + temp_E, temp_xs = calculate_cexs(nuc, 'nuclide', types, T, sab_tab, cross_sections) E.append(temp_E) # Since the energy grids are different, store the cross sections as diff --git a/openmc/statepoint.py b/openmc/statepoint.py index eb011d874..9c8eb709f 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -405,17 +405,10 @@ class StatePoint(object): scores = group['score_bins'].value n_score_bins = group['n_score_bins'].value - # Read scattering moment order strings (e.g., P3, Y1,2, etc.) - moments = group['moment_orders'].value - # Add the scores to the Tally for j, score in enumerate(scores): score = score.decode() - # If this is a moment, use generic moment order - pattern = r'-n$|-pn$|-yn$' - score = re.sub(pattern, '-' + moments[j].decode(), score) - tally.scores.append(score) # Add Tally to the global dictionary of all Tallies diff --git a/openmc/summary.py b/openmc/summary.py index aa98025c0..10898290a 100644 --- a/openmc/summary.py +++ b/openmc/summary.py @@ -9,7 +9,7 @@ import openmc import openmc.checkvalue as cv from openmc.region import Region -_VERSION_SUMMARY = 5 +_VERSION_SUMMARY = 6 class Summary(object): @@ -26,6 +26,8 @@ class Summary(object): nuclides : dict Dictionary whose keys are nuclide names and values are atomic weight ratios. + macroscopics : list + Names of macroscopic data sets version: tuple of int Version of OpenMC @@ -44,13 +46,15 @@ class Summary(object): self._fast_materials = {} self._fast_surfaces = {} self._fast_cells = {} - self._fast_universes = {} + self._fast_universes = {} self._fast_lattices = {} self._materials = openmc.Materials() self._nuclides = {} + self._macroscopics = [] self._read_nuclides() + self._read_macroscopics() with warnings.catch_warnings(): warnings.simplefilter("ignore", openmc.IDWarning) self._read_geometry() @@ -71,15 +75,26 @@ class Summary(object): def nuclides(self): return self._nuclides + @property + def macroscopics(self): + return self._macroscopics + @property def version(self): return tuple(self._f.attrs['openmc_version']) def _read_nuclides(self): - names = self._f['nuclides/names'].value - awrs = self._f['nuclides/awrs'].value - for name, awr in zip(names, awrs): - self._nuclides[name.decode()] = awr + if 'nuclides/names' in self._f: + names = self._f['nuclides/names'].value + awrs = self._f['nuclides/awrs'].value + for name, awr in zip(names, awrs): + self._nuclides[name.decode()] = awr + + def _read_macroscopics(self): + if 'macroscopics/names' in self._f: + names = self._f['macroscopics/names'].value + for name in names: + self._macroscopics = name.decode() def _read_geometry(self): # Read in and initialize the Materials and Geometry diff --git a/openmc/tallies.py b/openmc/tallies.py index 50398b786..1ac89129a 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -65,9 +65,7 @@ class Tally(IDManagerMixin): triggers : list of openmc.Trigger List of tally triggers num_scores : int - Total number of scores, accounting for the fact that a single - user-specified score, e.g. scatter-P3 or flux-Y2,2, might have multiple - bins + Total number of scores num_filter_bins : int Total number of filter bins accounting for all filters num_bins : int @@ -388,6 +386,14 @@ class Tally(IDManagerMixin): # If score is a string, strip whitespace if isinstance(score, str): + # Check to see if scores are deprecated before storing + for deprecated in ['scatter-', 'nu-scatter-', 'scatter-p', + 'nu-scatter-p', 'scatter-y', 'nu-scatter-y', + 'flux-y', 'total-y']: + if score.startswith(deprecated): + msg = score.strip() + ' is deprecated and should no ' \ + 'longer be used.' + raise ValueError(msg) scores[i] = score.strip() self._scores = cv.CheckedList(_SCORE_CLASSES, 'tally scores', scores) @@ -827,51 +833,8 @@ class Tally(IDManagerMixin): # Sparsify merged tally if both tallies are sparse merged_tally.sparse = self.sparse and other.sparse - # Consolidate scatter and flux Legendre moment scores - merged_tally._consolidate_moment_scores() - return merged_tally - def _consolidate_moment_scores(self): - """Remove redundant scattering and flux moment scores from a Tally.""" - - # Define regex for scatter, nu-scatter and flux moment scores - regex = [(r'^((?!nu-)scatter-\d)', r'^((?!nu-)scatter-(P|p)\d)'), - (r'nu-scatter-\d', r'nu-scatter-(P|p)\d'), - (r'flux-\d', r'flux-(P|p)\d')] - - # Find all non-scattering and non-flux moment scores - scores = [x for x in self.scores if - re.search(r'^((?!scatter-).)*$', x)] - scores = [x for x in scores if - re.search(r'^((?!flux-).)*$', x)] - - for regex_n, regex_pn in regex: - - # Use regex to find score-(P)n scores - score_n = [x for x in self.scores if re.search(regex_n, x)] - score_pn = [x for x in self.scores if re.search(regex_pn, x)] - - # Consolidate moment scores - if len(score_pn) > 0: - - # Only keep the highest score-PN score - high_pn = sorted([x.lower() for x in score_pn])[-1] - pn = int(high_pn.split('-')[-1].replace('p', '')) - - # Only keep the score-N scores with N > PN - score_n = sorted([x.lower() for x in score_n]) - score_n = [x for x in score_n if (int(x.split('-')[1]) > pn)] - - # Append highest score-PN and any higher score-N scores - scores.extend([high_pn] + score_n) - else: - scores.extend(score_n) - - # Override Tally's scores with consolidated list of scores - self.scores = scores - - def to_xml_element(self): """Return XML representation of the tally @@ -1180,7 +1143,7 @@ class Tally(IDManagerMixin): # Determine the score indices from any of the requested scores if nuclides: - nuclide_indices = np.zeros(len(nuclides), dtype=np.int) + nuclide_indices = np.zeros(len(nuclides), dtype=int) for i, nuclide in enumerate(nuclides): nuclide_indices[i] = self.get_nuclide_index(nuclide) @@ -1219,7 +1182,7 @@ class Tally(IDManagerMixin): # Determine the score indices from any of the requested scores if scores: - score_indices = np.zeros(len(scores), dtype=np.int) + score_indices = np.zeros(len(scores), dtype=int) for i, score in enumerate(scores): score_indices[i] = self.get_score_index(score) @@ -1491,11 +1454,8 @@ class Tally(IDManagerMixin): data = self.get_values(value=value) # Build a new array shape with one dimension per filter - new_shape = () - for self_filter in self.filters: - new_shape += (self_filter.num_bins, ) - new_shape += (self.num_nuclides,) - new_shape += (self.num_scores,) + new_shape = tuple(f.num_bins for f in self.filters) + new_shape += (self.num_nuclides, self.num_scores) # Reshape the data with one dimension for each filter data = np.reshape(data, new_shape) @@ -2772,7 +2732,7 @@ class Tally(IDManagerMixin): # Sum across the bins in the user-specified filter for i, self_filter in enumerate(self.filters): - if isinstance(self_filter, filter_type): + if type(self_filter) == filter_type: shape = mean.shape mean = np.take(mean, indices=bin_indices, axis=i) std_dev = np.take(std_dev, indices=bin_indices, axis=i) @@ -3012,7 +2972,7 @@ class Tally(IDManagerMixin): The data in the derived tally arrays is "diagonalized" along the bins in the new filter. This functionality is used by the openmc.mgxs module; to transport-correct scattering matrices by subtracting a 'scatter-P1' - reaction rate tally with an energy filter from an 'scatter' reaction + reaction rate tally with an energy filter from a 'scatter' reaction rate tally with both energy and energyout filters. Parameters @@ -3031,7 +2991,7 @@ class Tally(IDManagerMixin): if new_filter in self.filters: msg = 'Unable to diagonalize Tally ID="{0}" which already ' \ - 'contains a "{1}" filter'.format(self.id, new_filter.type) + 'contains a "{1}" filter'.format(self.id, type(new_filter)) raise ValueError(msg) # Add the new filter to a copy of this Tally @@ -3042,8 +3002,8 @@ class Tally(IDManagerMixin): # by which the "base" indices should be repeated to account for all # other filter bins in the diagonalized tally indices = np.arange(0, new_filter.num_bins**2, new_filter.num_bins+1) - diag_factor = int(self.num_filter_bins / new_filter.num_bins) - diag_indices = np.zeros(self.num_filter_bins, dtype=np.int) + diag_factor = self.num_filter_bins // new_filter.num_bins + diag_indices = np.zeros(self.num_filter_bins, dtype=int) # Determine the filter indices along the new "diagonal" for i in range(diag_factor): diff --git a/src/cmfd_data.F90 b/src/cmfd_data.F90 index 3152f1eb2..ce4825426 100644 --- a/src/cmfd_data.F90 +++ b/src/cmfd_data.F90 @@ -76,6 +76,7 @@ contains integer :: i_filter_mesh ! index for mesh filter integer :: i_filter_ein ! index for incoming energy filter integer :: i_filter_eout ! index for outgoing energy filter + integer :: i_filter_legendre ! index for Legendre filter integer :: i_mesh ! flattend index for mesh logical :: energy_filters! energy filters present real(8) :: flux ! temp variable for flux @@ -116,8 +117,11 @@ contains if (ital < 3) then i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) - else + else if (ital == 3) then i_filter_mesh = t % filter(t % find_filter(FILTER_MESHSURFACE)) + else if (ital == 4) then + i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) + i_filter_legendre = t % filter(t % find_filter(FILTER_LEGENDRE)) end if ! Check for energy filters @@ -187,13 +191,6 @@ contains ! Get total rr and convert to total xs cmfd % totalxs(h,i,j,k) = t % results(RESULT_SUM,2,score_index) / flux - ! Get p1 scatter rr and convert to p1 scatter xs - cmfd % p1scattxs(h,i,j,k) = t % results(RESULT_SUM,3,score_index) / flux - - ! Calculate diffusion coefficient - cmfd % diffcof(h,i,j,k) = ONE/(3.0_8*(cmfd % totalxs(h,i,j,k) - & - cmfd % p1scattxs(h,i,j,k))) - else if (ital == 2) then ! Begin loop to get energy out tallies @@ -301,6 +298,46 @@ contains score_index + IN_TOP) cmfd % current(12,h,i,j,k) = t % results(RESULT_SUM, 1, & score_index + OUT_TOP) + + else if (ital == 4) then + + ! Reset all bins to 1 + do l = 1, size(t % filter) + call filter_matches(t % filter(l)) % bins % clear() + call filter_matches(t % filter(l)) % bins % push_back(1) + end do + + ! Set ijk as mesh indices + ijk = (/ i, j, k /) + + ! Get bin number for mesh indices + filter_matches(i_filter_mesh) % bins % data(1) = & + m % get_bin_from_indices(ijk) + + ! Apply energy in filter + if (energy_filters) then + filter_matches(i_filter_ein) % bins % data(1) = ng - h + 1 + end if + + ! Apply Legendre filter + filter_matches(i_filter_legendre) % bins % data(1) = 2 + + ! Calculate score index from bins + score_index = 1 + do l = 1, size(t % filter) + score_index = score_index + (filter_matches(t % filter(l)) & + % bins % data(1) - 1) * t % stride(l) + end do + + ! Get p1 scatter rr and convert to p1 scatter xs + cmfd % p1scattxs(h,i,j,k) = & + t % results(RESULT_SUM,1,score_index) / & + cmfd % flux(h,i,j,k) + + ! Calculate diffusion coefficient + cmfd % diffcof(h,i,j,k) = & + ONE/(3.0_8*(cmfd % totalxs(h,i,j,k) - & + cmfd % p1scattxs(h,i,j,k))) end if TALLY end do OUTGROUP diff --git a/src/cmfd_input.F90 b/src/cmfd_input.F90 index 40b4abf57..549cff419 100644 --- a/src/cmfd_input.F90 +++ b/src/cmfd_input.F90 @@ -373,7 +373,7 @@ contains ! Determine number of filters energy_filters = check_for_node(node_mesh, "energy") - n = merge(4, 2, energy_filters) + n = merge(5, 3, energy_filters) ! Extend filters array so we can add CMFD filters err = openmc_extend_filters(n, i_filt_start, i_filt_end) @@ -414,6 +414,12 @@ contains err = openmc_filter_set_id(i_filt, filt_id) err = openmc_meshsurface_filter_set_mesh(i_filt, i_start) + ! Add in legendre filter for the P1 tally + i_filt = i_filt + 1 + err = openmc_filter_set_type(i_filt, C_CHAR_'legendre' // C_NULL_CHAR) + call openmc_get_filter_next_id(filt_id) + err = openmc_filter_set_id(i_filt, filt_id) + err = openmc_legendre_filter_set_order(i_filt, 1) ! Initialize filters do i = i_filt_start, i_filt_end @@ -421,7 +427,7 @@ contains end do ! Allocate tallies - err = openmc_extend_tallies(3, i_start, i_end) + err = openmc_extend_tallies(4, i_start, i_end) cmfd_tallies => tallies(i_start:i_end) ! Begin loop around tallies @@ -455,7 +461,7 @@ contains if (i == 1) then ! Set name - t % name = "CMFD flux, total, scatter-1" + t % name = "CMFD flux, total" ! Set tally estimator to analog t % estimator = ESTIMATOR_ANALOG @@ -473,19 +479,12 @@ contains deallocate(filter_indices) ! Allocate scoring bins - allocate(t % score_bins(3)) - t % n_score_bins = 3 - t % n_user_score_bins = 3 - - ! Allocate scattering order data - allocate(t % moment_order(3)) - t % moment_order = 0 + allocate(t % score_bins(2)) + t % n_score_bins = 2 ! Set macro_bins t % score_bins(1) = SCORE_FLUX t % score_bins(2) = SCORE_TOTAL - t % score_bins(3) = SCORE_SCATTER_N - t % moment_order(3) = 1 else if (i == 2) then @@ -517,11 +516,6 @@ contains ! Allocate macro reactions allocate(t % score_bins(2)) t % n_score_bins = 2 - t % n_user_score_bins = 2 - - ! Allocate scattering order data - allocate(t % moment_order(2)) - t % moment_order = 0 ! Set macro_bins t % score_bins(1) = SCORE_NU_SCATTER @@ -537,7 +531,7 @@ contains ! Allocate and set filters allocate(filter_indices(n_filter)) - filter_indices(1) = i_filt_end + filter_indices(1) = i_filt_end - 1 if (energy_filters) then filter_indices(2) = i_filt_start + 1 end if @@ -547,15 +541,41 @@ contains ! Allocate macro reactions allocate(t % score_bins(1)) t % n_score_bins = 1 - t % n_user_score_bins = 1 - - ! Allocate scattering order data - allocate(t % moment_order(1)) - t % moment_order = 0 ! Set macro bins t % score_bins(1) = SCORE_CURRENT t % type = TALLY_MESH_SURFACE + + else if (i == 4) then + ! Set name + t % name = "CMFD P1 scatter" + + ! Set tally estimator to analog + t % estimator = ESTIMATOR_ANALOG + + ! Set tally type to volume + t % type = TALLY_VOLUME + + ! Allocate and set filters + n_filter = 2 + if (energy_filters) then + n_filter = n_filter + 1 + end if + allocate(filter_indices(n_filter)) + filter_indices(1) = i_filt_start + filter_indices(2) = i_filt_end + if (energy_filters) then + filter_indices(3) = i_filt_start + 1 + end if + err = openmc_tally_set_filters(i_start + i - 1, n_filter, filter_indices) + deallocate(filter_indices) + + ! Allocate scoring bins + allocate(t % score_bins(1)) + t % n_score_bins = 1 + + ! Set macro_bins + t % score_bins(1) = SCORE_SCATTER end if ! Make CMFD tallies active from the start diff --git a/src/constants.F90 b/src/constants.F90 index b335b78c8..d1893bf9e 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -21,7 +21,7 @@ module constants integer, parameter :: VERSION_STATEPOINT(2) = [17, 0] integer, parameter :: VERSION_PARTICLE_RESTART(2) = [2, 0] integer, parameter :: VERSION_TRACK(2) = [2, 0] - integer, parameter :: VERSION_SUMMARY(2) = [5, 0] + integer, parameter :: VERSION_SUMMARY(2) = [6, 0] integer, parameter :: VERSION_VOLUME(2) = [1, 0] integer, parameter :: VERSION_VOXEL(2) = [1, 0] integer, parameter :: VERSION_MGXS_LIBRARY(2) = [1, 0] @@ -232,6 +232,10 @@ module constants MGXS_ISOTROPIC = 1, & ! Isotropically Weighted Data MGXS_ANGLE = 2 ! Data by Angular Bins + ! Flag to denote this was a macroscopic data object + real(8), parameter :: & + MACROSCOPIC_AWR = -TWO + ! Fission neutron emission (nu) type integer, parameter :: & NU_NONE = 0, & ! No nu values (non-fissionable) @@ -310,50 +314,28 @@ module constants ! Tally score type -- if you change these, make sure you also update the ! _SCORES dictionary in openmc/capi/tally.py - integer, parameter :: N_SCORE_TYPES = 24 + integer, parameter :: N_SCORE_TYPES = 16 integer, parameter :: & SCORE_FLUX = -1, & ! flux SCORE_TOTAL = -2, & ! total reaction rate SCORE_SCATTER = -3, & ! scattering rate SCORE_NU_SCATTER = -4, & ! scattering production rate - SCORE_SCATTER_N = -5, & ! arbitrary scattering moment - SCORE_SCATTER_PN = -6, & ! system for scoring 0th through nth moment - SCORE_NU_SCATTER_N = -7, & ! arbitrary nu-scattering moment - SCORE_NU_SCATTER_PN = -8, & ! system for scoring 0th through nth nu-scatter moment - SCORE_ABSORPTION = -9, & ! absorption rate - SCORE_FISSION = -10, & ! fission rate - SCORE_NU_FISSION = -11, & ! neutron production rate - SCORE_KAPPA_FISSION = -12, & ! fission energy production rate - SCORE_CURRENT = -13, & ! current - SCORE_FLUX_YN = -14, & ! angular moment of flux - SCORE_TOTAL_YN = -15, & ! angular moment of total reaction rate - SCORE_SCATTER_YN = -16, & ! angular flux-weighted scattering moment (0:N) - SCORE_NU_SCATTER_YN = -17, & ! angular flux-weighted nu-scattering moment (0:N) - SCORE_EVENTS = -18, & ! number of events - SCORE_DELAYED_NU_FISSION = -19, & ! delayed neutron production rate - SCORE_PROMPT_NU_FISSION = -20, & ! prompt neutron production rate - SCORE_INVERSE_VELOCITY = -21, & ! flux-weighted inverse velocity - SCORE_FISS_Q_PROMPT = -22, & ! prompt fission Q-value - SCORE_FISS_Q_RECOV = -23, & ! recoverable fission Q-value - SCORE_DECAY_RATE = -24 ! delayed neutron precursor decay rate + SCORE_ABSORPTION = -5, & ! absorption rate + SCORE_FISSION = -6, & ! fission rate + SCORE_NU_FISSION = -7, & ! neutron production rate + SCORE_KAPPA_FISSION = -8, & ! fission energy production rate + SCORE_CURRENT = -9, & ! current + SCORE_EVENTS = -10, & ! number of events + SCORE_DELAYED_NU_FISSION = -11, & ! delayed neutron production rate + SCORE_PROMPT_NU_FISSION = -12, & ! prompt neutron production rate + SCORE_INVERSE_VELOCITY = -13, & ! flux-weighted inverse velocity + SCORE_FISS_Q_PROMPT = -14, & ! prompt fission Q-value + SCORE_FISS_Q_RECOV = -15, & ! recoverable fission Q-value + SCORE_DECAY_RATE = -16 ! delayed neutron precursor decay rate ! Maximum scattering order supported integer, parameter :: MAX_ANG_ORDER = 10 - ! Names of *-PN & *-YN scores (MOMENT_STRS) and *-N moment scores - character(*), parameter :: & - MOMENT_STRS(6) = (/ "scatter-p ", & - "nu-scatter-p", & - "flux-y ", & - "total-y ", & - "scatter-y ", & - "nu-scatter-y"/), & - MOMENT_N_STRS(2) = (/ "scatter- ", & - "nu-scatter- "/) - - ! Location in MOMENT_STRS where the YN data begins - integer, parameter :: YN_LOC = 3 - ! Tally map bin finding integer, parameter :: NO_BIN_FOUND = -1 diff --git a/src/endf.F90 b/src/endf.F90 index 0ba2db388..9934d9a7c 100644 --- a/src/endf.F90 +++ b/src/endf.F90 @@ -26,14 +26,6 @@ contains string = "scatter" case (SCORE_NU_SCATTER) string = "nu-scatter" - case (SCORE_SCATTER_N) - string = "scatter-n" - case (SCORE_SCATTER_PN) - string = "scatter-pn" - case (SCORE_NU_SCATTER_N) - string = "nu-scatter-n" - case (SCORE_NU_SCATTER_PN) - string = "nu-scatter-pn" case (SCORE_ABSORPTION) string = "absorption" case (SCORE_FISSION) @@ -50,14 +42,6 @@ contains string = "kappa-fission" case (SCORE_CURRENT) string = "current" - case (SCORE_FLUX_YN) - string = "flux-yn" - case (SCORE_TOTAL_YN) - string = "total-yn" - case (SCORE_SCATTER_YN) - string = "scatter-yn" - case (SCORE_NU_SCATTER_YN) - string = "nu-scatter-yn" case (SCORE_EVENTS) string = "events" case (SCORE_INVERSE_VELOCITY) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 63e5cd7a4..f8cf50dd3 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -2194,26 +2194,20 @@ contains integer :: i ! loop over user-specified tallies integer :: j ! loop over words integer :: k ! another loop index - integer :: l ! another loop index integer :: filter_id ! user-specified identifier for filter integer :: i_filt ! index in filters array integer :: i_elem ! index of entry in dictionary integer :: n ! size of arrays in mesh specification integer :: n_words ! number of words read integer :: n_filter ! number of filters - integer :: n_new ! number of new scores to add based on Yn/Pn tally - integer :: n_scores ! number of tot scores after adjusting for Yn/Pn tally - integer :: n_bins ! total new bins for this score + integer :: n_scores ! number of scores integer :: n_user_trig ! number of user-specified tally triggers integer :: trig_ind ! index of triggers array for each tally integer :: user_trig_ind ! index of user-specified triggers for each tally integer :: i_start, i_end integer(C_INT) :: err real(8) :: threshold ! trigger convergence threshold - integer :: n_order ! moment order requested - integer :: n_order_pos ! oosition of Scattering order in score name string integer :: MT ! user-specified MT for score - integer :: imomstr ! Index of MOMENT_STRS & MOMENT_N_STRS logical :: file_exists ! does tallies.xml file exist? integer, allocatable :: temp_filter(:) ! temporary filter indices character(MAX_LINE_LEN) :: filename @@ -2541,107 +2535,22 @@ contains allocate(sarray(n_words)) call get_node_array(node_tal, "scores", sarray) - ! Before we can allocate storage for scores, we must determine the - ! number of additional scores required due to the moment scores - ! (i.e., scatter-p#, flux-y#) - n_new = 0 + ! Append the score to the list of possible trigger scores do j = 1, n_words sarray(j) = to_lower(sarray(j)) - ! Find if scores(j) is of the form 'moment-p' or 'moment-y' present in - ! MOMENT_STRS(:) - ! If so, check the order, store if OK, then reset the number to 'n' score_name = trim(sarray(j)) - ! Append the score to the list of possible trigger scores if (trigger_on) call trigger_scores % set(trim(score_name), j) - do imomstr = 1, size(MOMENT_STRS) - if (starts_with(score_name,trim(MOMENT_STRS(imomstr)))) then - n_order_pos = scan(score_name,'0123456789') - n_order = int(str_to_int( & - score_name(n_order_pos:(len_trim(score_name)))),4) - if (n_order > MAX_ANG_ORDER) then - ! User requested too many orders; throw a warning and set to the - ! maximum order. - ! The above scheme will essentially take the absolute value - if (master) call warning("Invalid scattering order of " & - // trim(to_str(n_order)) // " requested. Setting to the & - &maximum permissible value, " & - // trim(to_str(MAX_ANG_ORDER))) - n_order = MAX_ANG_ORDER - sarray(j) = trim(MOMENT_STRS(imomstr)) & - // trim(to_str(MAX_ANG_ORDER)) - end if - ! Find total number of bins for this case - if (imomstr >= YN_LOC) then - n_bins = (n_order + 1)**2 - else - n_bins = n_order + 1 - end if - ! We subtract one since n_words already included - n_new = n_new + n_bins - 1 - exit - end if - end do end do - n_scores = n_words + n_new + n_scores = n_words ! Allocate score storage accordingly allocate(t % score_bins(n_scores)) - allocate(t % moment_order(n_scores)) - t % moment_order = 0 - j = 0 - do l = 1, n_words - j = j + 1 - ! Get the input string in scores(l) but if score is one of the moment - ! scores then strip off the n and store it as an integer to be used - ! later. Then perform the select case on this modified (number - ! removed) string - n_order = -1 - score_name = sarray(l) - do imomstr = 1, size(MOMENT_STRS) - if (starts_with(score_name,trim(MOMENT_STRS(imomstr)))) then - n_order_pos = scan(score_name,'0123456789') - n_order = int(str_to_int( & - score_name(n_order_pos:(len_trim(score_name)))),4) - if (n_order > MAX_ANG_ORDER) then - ! User requested too many orders; throw a warning and set to the - ! maximum order. - ! The above scheme will essentially take the absolute value - n_order = MAX_ANG_ORDER - end if - score_name = trim(MOMENT_STRS(imomstr)) // "n" - ! Find total number of bins for this case - if (imomstr >= YN_LOC) then - n_bins = (n_order + 1)**2 - else - n_bins = n_order + 1 - end if - exit - end if - end do - ! Now check the Moment_N_Strs, but only if we werent successful above - if (imomstr > size(MOMENT_STRS)) then - do imomstr = 1, size(MOMENT_N_STRS) - if (starts_with(score_name,trim(MOMENT_N_STRS(imomstr)))) then - n_order_pos = scan(score_name,'0123456789') - n_order = int(str_to_int( & - score_name(n_order_pos:(len_trim(score_name)))),4) - if (n_order > MAX_ANG_ORDER) then - ! User requested too many orders; throw a warning and set to the - ! maximum order. - ! The above scheme will essentially take the absolute value - if (master) call warning("Invalid scattering order of " & - // trim(to_str(n_order)) // " requested. Setting to & - &the maximum permissible value, " & - // trim(to_str(MAX_ANG_ORDER))) - n_order = MAX_ANG_ORDER - end if - score_name = trim(MOMENT_N_STRS(imomstr)) // "n" - exit - end if - end do - end if + + ! Check the validity of the scores and their filters + do j = 1, n_scores + score_name = sarray(j) ! Check if delayed group filter is used with any score besides ! delayed-nu-fission or decay-rate @@ -2652,23 +2561,6 @@ contains &delayedgroup filter.") end if - ! Check to see if the mu filter is applied and if that makes sense. - if ((.not. starts_with(score_name,'scatter')) .and. & - (.not. starts_with(score_name,'nu-scatter'))) then - if (t % find_filter(FILTER_MU) > 0) then - call fatal_error("Cannot tally " // trim(score_name) //" with a & - &change of angle (mu) filter.") - end if - ! Also check to see if this is a legendre expansion or not. - ! If so, we can accept this score and filter combo for p0, but not - ! elsewhere. - else if (n_order > 0) then - if (t % find_filter(FILTER_MU) > 0) then - call fatal_error("Cannot tally " // trim(score_name) //" with a & - &change of angle (mu) filter unless order is 0.") - end if - end if - select case (trim(score_name)) case ('flux') ! Prohibit user from tallying flux for an individual nuclide @@ -2683,22 +2575,6 @@ contains &filter.") end if - case ('flux-yn') - ! Prohibit user from tallying flux for an individual nuclide - if (.not. (t % n_nuclide_bins == 1 .and. & - t % nuclide_bins(1) == -1)) then - call fatal_error("Cannot tally flux for an individual nuclide.") - end if - - if (t % find_filter(FILTER_ENERGYOUT) > 0) then - call fatal_error("Cannot tally flux with an outgoing energy & - &filter.") - end if - - t % score_bins(j : j + n_bins - 1) = SCORE_FLUX_YN - t % moment_order(j : j + n_bins - 1) = n_order - j = j + n_bins - 1 - case ('total', '(n,total)') t % score_bins(j) = SCORE_TOTAL if (t % find_filter(FILTER_ENERGYOUT) > 0) then @@ -2706,18 +2582,13 @@ contains &outgoing energy filter.") end if - case ('total-yn') - if (t % find_filter(FILTER_ENERGYOUT) > 0) then - call fatal_error("Cannot tally total reaction rate with an & - &outgoing energy filter.") - end if - - t % score_bins(j : j + n_bins - 1) = SCORE_TOTAL_YN - t % moment_order(j : j + n_bins - 1) = n_order - j = j + n_bins - 1 - case ('scatter') t % score_bins(j) = SCORE_SCATTER + if (t % find_filter(FILTER_ENERGYOUT) > 0 .or. & + t % find_filter(FILTER_LEGENDRE) > 0) then + ! Set tally estimator to analog + t % estimator = ESTIMATOR_ANALOG + end if case ('nu-scatter') t % score_bins(j) = SCORE_NU_SCATTER @@ -2727,53 +2598,14 @@ contains ! necessary) if (run_CE) then t % estimator = ESTIMATOR_ANALOG + else + if (t % find_filter(FILTER_ENERGYOUT) > 0 .or. & + t % find_filter(FILTER_LEGENDRE) > 0) then + ! Set tally estimator to analog + t % estimator = ESTIMATOR_ANALOG + end if end if - case ('scatter-n') - t % score_bins(j) = SCORE_SCATTER_N - t % moment_order(j) = n_order - t % estimator = ESTIMATOR_ANALOG - - case ('nu-scatter-n') - t % score_bins(j) = SCORE_NU_SCATTER_N - t % moment_order(j) = n_order - t % estimator = ESTIMATOR_ANALOG - - case ('scatter-pn') - t % estimator = ESTIMATOR_ANALOG - ! Setup P0:Pn - t % score_bins(j : j + n_bins - 1) = SCORE_SCATTER_PN - t % moment_order(j : j + n_bins - 1) = n_order - j = j + n_bins - 1 - - case ('nu-scatter-pn') - t % estimator = ESTIMATOR_ANALOG - ! Setup P0:Pn - t % score_bins(j : j + n_bins - 1) = SCORE_NU_SCATTER_PN - t % moment_order(j : j + n_bins - 1) = n_order - j = j + n_bins - 1 - - case ('scatter-yn') - t % estimator = ESTIMATOR_ANALOG - ! Setup P0:Pn - t % score_bins(j : j + n_bins - 1) = SCORE_SCATTER_YN - t % moment_order(j : j + n_bins - 1) = n_order - j = j + n_bins - 1 - - case ('nu-scatter-yn') - t % estimator = ESTIMATOR_ANALOG - ! Setup P0:Pn - t % score_bins(j : j + n_bins - 1) = SCORE_NU_SCATTER_YN - t % moment_order(j : j + n_bins - 1) = n_order - j = j + n_bins - 1 - - case('transport') - call fatal_error("Transport score no longer supported for tallies, & - &please remove") - - case ('n1n') - call fatal_error("n1n score no longer supported for tallies, & - &please remove") case ('n2n', '(n,2n)') t % score_bins(j) = N_2N t % depletion_rx = .true. @@ -2937,6 +2769,14 @@ contains t % score_bins(j) = N_DA case default + ! First look for deprecated scores + if (starts_with(trim(score_name), 'scatter-') .or. & + starts_with(trim(score_name), 'nu-scatter-') .or. & + starts_with(trim(score_name), 'total-y') .or. & + starts_with(trim(score_name), 'flux-y')) then + call fatal_error(trim(score_name) // " is no longer available.") + end if + ! Assume that user has specified an MT number MT = int(str_to_int(score_name)) @@ -2945,14 +2785,12 @@ contains if (MT > 1) then t % score_bins(j) = MT else - call fatal_error("Invalid MT on : " & - // trim(sarray(l))) + call fatal_error("Invalid MT on : " // trim(score_name)) end if else ! Specified score was not an integer - call fatal_error("Unknown scoring function: " & - // trim(sarray(l))) + call fatal_error("Unknown scoring function: " // trim(score_name)) end if end select @@ -2967,35 +2805,19 @@ contains end do t % n_score_bins = n_scores - t % n_user_score_bins = n_words ! Deallocate temporary string array of scores deallocate(sarray) ! Check that no duplicate scores exist - j = 1 - do while (j < n_scores) - ! Determine number of bins for scores with expansions - n_order = t % moment_order(j) - select case (t % score_bins(j)) - case (SCORE_SCATTER_PN, SCORE_NU_SCATTER_PN) - n_bins = n_order + 1 - case (SCORE_FLUX_YN, SCORE_TOTAL_YN, SCORE_SCATTER_YN, & - SCORE_NU_SCATTER_YN) - n_bins = (n_order + 1)**2 - case default - n_bins = 1 - end select - - do k = j + n_bins, n_scores - if (t % score_bins(j) == t % score_bins(k) .and. & - t % moment_order(j) == t % moment_order(k)) then + do j = 1, n_scores - 1 + do k = j + 1, n_scores + if (t % score_bins(j) == t % score_bins(k)) then call fatal_error("Duplicate score of type '" // trim(& reaction_name(t % score_bins(j))) // "' found in tally " & // trim(to_str(t % id))) end if end do - j = j + n_bins end do else call fatal_error("No specified on tally " & diff --git a/src/math.F90 b/src/math.F90 index ee8cd0530..20bacf88c 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -598,7 +598,6 @@ contains real(8) :: zn_mat(n+1, n+1) ! Matrix form of the coefficients which is ! easier to work with real(8) :: k1, k2, k3, k4 ! Variables for R_m_n calculation - real(8) :: sqrt_norm ! normalization for radial moments integer :: i,p,q ! Loop counters real(8), parameter :: SQRT_N_1(0:10) = [& diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index 6eabefae3..fd6074c42 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -6,7 +6,7 @@ module mgxs_header use hdf5, only: HID_T, HSIZE_T, SIZE_T use algorithm, only: find, sort - use constants, only: MAX_WORD_LEN, ZERO, ONE, TWO, PI + use constants, only: MAX_WORD_LEN, ZERO, ONE, TWO, PI, MACROSCOPIC_AWR use error, only: fatal_error use hdf5_interface use material_header, only: material @@ -268,7 +268,7 @@ contains if (attribute_exists(xs_id, "atomic_weight_ratio")) then call read_attribute(this % awr, xs_id, "atomic_weight_ratio") else - this % awr = -ONE + this % awr = MACROSCOPIC_AWR end if ! Determine temperatures available @@ -396,9 +396,9 @@ contains ! Store the dimensionality of the data in order_dim. ! For Legendre data, we usually refer to it as Pn where n is the order. - ! However Pn has n+1 sets of points (since you need to - ! the count the P0 moment). Adjust for that. Histogram and Tabular - ! formats dont need this adjustment. + ! However Pn has n+1 sets of points (since you need to count the P0 + ! moment). Adjust for that. Histogram and Tabular formats dont need this + ! adjustment. if (this % scatter_format == ANGLE_LEGENDRE) then order_dim = order_dim + 1 else diff --git a/src/output.F90 b/src/output.F90 index b6809ca82..f9e770f51 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -665,14 +665,11 @@ contains integer :: j ! level in tally hierarchy integer :: k ! loop index for scoring bins integer :: n ! loop index for nuclides - integer :: l ! loop index for user scores integer :: h ! loop index for tally filters integer :: indent ! number of spaces to preceed output integer :: filter_index ! index in results array for filters integer :: score_index ! scoring bin index integer :: i_nuclide ! index in nuclides array - integer :: n_order ! loop index for moment orders - integer :: nm_order ! loop index for Ynm moment orders integer :: unit_tally ! tallies.out file unit integer :: nr ! number of realizations real(8) :: t_value ! t-values for confidence intervals @@ -699,14 +696,6 @@ contains score_names(abs(SCORE_NU_FISSION)) = "Nu-Fission Rate" score_names(abs(SCORE_KAPPA_FISSION)) = "Kappa-Fission Rate" score_names(abs(SCORE_EVENTS)) = "Events" - score_names(abs(SCORE_FLUX_YN)) = "Flux Moment" - score_names(abs(SCORE_TOTAL_YN)) = "Total Reaction Rate Moment" - score_names(abs(SCORE_SCATTER_N)) = "Scattering Rate Moment" - score_names(abs(SCORE_SCATTER_PN)) = "Scattering Rate Moment" - score_names(abs(SCORE_SCATTER_YN)) = "Scattering Rate Moment" - score_names(abs(SCORE_NU_SCATTER_N)) = "Scattering Prod. Rate Moment" - score_names(abs(SCORE_NU_SCATTER_PN)) = "Scattering Prod. Rate Moment" - score_names(abs(SCORE_NU_SCATTER_YN)) = "Scattering Prod. Rate Moment" score_names(abs(SCORE_DECAY_RATE)) = "Decay Rate" score_names(abs(SCORE_DELAYED_NU_FISSION)) = "Delayed-Nu-Fission Rate" score_names(abs(SCORE_PROMPT_NU_FISSION)) = "Prompt-Nu-Fission Rate" @@ -860,60 +849,20 @@ contains end if indent = indent + 2 - k = 0 - do l = 1, t % n_user_score_bins - k = k + 1 + do k = 1, t % n_score_bins score_index = score_index + 1 associate(r => t % results(RESULT_SUM:RESULT_SUM_SQ, :, :)) - select case(t % score_bins(k)) - case (SCORE_SCATTER_N, SCORE_NU_SCATTER_N) - score_name = 'P' // trim(to_str(t % moment_order(k))) // " " // & - score_names(abs(t % score_bins(k))) - x(:) = mean_stdev(r(:, score_index, filter_index), nr) - write(UNIT=unit_tally, FMT='(1X,2A,1X,A,"+/- ",A)') & - repeat(" ", indent), score_name, to_str(x(1)), & - trim(to_str(t_value * x(2))) - case (SCORE_SCATTER_PN, SCORE_NU_SCATTER_PN) - score_index = score_index - 1 - do n_order = 0, t % moment_order(k) - score_index = score_index + 1 - score_name = 'P' // trim(to_str(n_order)) // " " //& - score_names(abs(t % score_bins(k))) - x(:) = mean_stdev(r(:, score_index, filter_index), nr) - write(UNIT=unit_tally, FMT='(1X,2A,1X,A,"+/- ",A)') & - repeat(" ", indent), score_name, & - to_str(x(1)), trim(to_str(t_value * x(2))) - end do - k = k + t % moment_order(k) - case (SCORE_SCATTER_YN, SCORE_NU_SCATTER_YN, SCORE_FLUX_YN, & - SCORE_TOTAL_YN) - score_index = score_index - 1 - do n_order = 0, t % moment_order(k) - do nm_order = -n_order, n_order - score_index = score_index + 1 - score_name = 'Y' // trim(to_str(n_order)) // ',' // & - trim(to_str(nm_order)) // " " & - // score_names(abs(t % score_bins(k))) - x(:) = mean_stdev(r(:, score_index, filter_index), nr) - write(UNIT=unit_tally, FMT='(1X,2A,1X,A,"+/- ",A)') & - repeat(" ", indent), score_name, & - to_str(x(1)), trim(to_str(t_value * x(2))) - end do - end do - k = k + (t % moment_order(k) + 1)**2 - 1 - case default - if (t % score_bins(k) > 0) then - score_name = reaction_name(t % score_bins(k)) - else - score_name = score_names(abs(t % score_bins(k))) - end if - x(:) = mean_stdev(r(:, score_index, filter_index), nr) - write(UNIT=unit_tally, FMT='(1X,2A,1X,A,"+/- ",A)') & - repeat(" ", indent), score_name, & - to_str(x(1)), trim(to_str(t_value * x(2))) - end select + if (t % score_bins(k) > 0) then + score_name = reaction_name(t % score_bins(k)) + else + score_name = score_names(abs(t % score_bins(k))) + end if + x(:) = mean_stdev(r(:, score_index, filter_index), nr) + write(UNIT=unit_tally, FMT='(1X,2A,1X,A,"+/- ",A)') & + repeat(" ", indent), score_name, & + to_str(x(1)), trim(to_str(t_value * x(2))) end associate end do indent = indent - 2 diff --git a/src/state_point.F90 b/src/state_point.F90 index 980a7333c..a7ae24dda 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -48,8 +48,6 @@ contains integer :: i, j, k integer :: i_xs - integer :: n_order ! loop index for moment orders - integer :: nm_order ! loop index for Ynm moment orders integer, allocatable :: id_array(:) integer(HID_T) :: file_id integer(HID_T) :: cmfd_group, tallies_group, tally_group, meshes_group, & @@ -320,42 +318,9 @@ contains str_array(j) = reaction_name(tally % score_bins(j)) end do call write_dataset(tally_group, "score_bins", str_array) - call write_dataset(tally_group, "n_user_score_bins", & - tally % n_user_score_bins) deallocate(str_array) - ! Write explicit moment order strings for each score bin - k = 1 - allocate(str_array(tally % n_score_bins)) - MOMENT_LOOP: do j = 1, tally % n_user_score_bins - select case(tally % score_bins(k)) - case (SCORE_SCATTER_N, SCORE_NU_SCATTER_N) - str_array(k) = trim(to_str(tally % moment_order(k))) - k = k + 1 - case (SCORE_SCATTER_PN, SCORE_NU_SCATTER_PN) - do n_order = 0, tally % moment_order(k) - str_array(k) = trim(to_str(n_order)) - k = k + 1 - end do - case (SCORE_SCATTER_YN, SCORE_NU_SCATTER_YN, SCORE_FLUX_YN, & - SCORE_TOTAL_YN) - do n_order = 0, tally % moment_order(k) - do nm_order = -n_order, n_order - str_array(k) = 'Y' // trim(to_str(n_order)) // ',' // & - trim(to_str(nm_order)) - k = k + 1 - end do - end do - case default - str_array(k) = '' - k = k + 1 - end select - end do MOMENT_LOOP - - call write_dataset(tally_group, "moment_orders", str_array) - deallocate(str_array) - call close_group(tally_group) end associate end do TALLY_METADATA diff --git a/src/summary.F90 b/src/summary.F90 index 3aeb42178..409c4de1f 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -77,34 +77,81 @@ contains subroutine write_nuclides(file_id) integer(HID_T), intent(in) :: file_id integer(HID_T) :: nuclide_group + integer(HID_T) :: macro_group integer :: i - character(12), allocatable :: nucnames(:) + character(12), allocatable :: nuc_names(:) + character(12), allocatable :: macro_names(:) real(8), allocatable :: awrs(:) + integer :: num_nuclides + integer :: num_macros + integer :: j + integer :: k - ! Write useful data from nuclide objects - nuclide_group = create_group(file_id, "nuclides") - call write_attribute(nuclide_group, "n_nuclides", n_nuclides) + ! Find how many of these nuclides are macroscopic objects + if (run_CE) then + ! Then none are macroscopic + num_nuclides = n_nuclides + num_macros = 0 + else + num_nuclides = 0 + num_macros = 0 + do i = 1, n_nuclides + if (nuclides_MG(i) % obj % awr /= MACROSCOPIC_AWR) then + num_nuclides = num_nuclides + 1 + else + num_macros = num_macros + 1 + end if + end do + end if - ! Build array of nuclide names and awrs - allocate(nucnames(n_nuclides)) - allocate(awrs(n_nuclides)) + ! Build array of nuclide names and awrs while only sorting nuclides from + ! macroscopics + if (num_nuclides > 0) then + allocate(nuc_names(num_nuclides)) + allocate(awrs(num_nuclides)) + end if + if (num_macros > 0) then + allocate(macro_names(num_macros)) + end if + + j = 1 + k = 1 do i = 1, n_nuclides if (run_CE) then - nucnames(i) = nuclides(i) % name + nuc_names(i) = nuclides(i) % name awrs(i) = nuclides(i) % awr else - nucnames(i) = nuclides_MG(i) % obj % name - awrs(i) = nuclides_MG(i) % obj % awr + if (nuclides_MG(i) % obj % awr /= MACROSCOPIC_AWR) then + nuc_names(j) = nuclides_MG(i) % obj % name + awrs(j) = nuclides_MG(i) % obj % awr + j = j + 1 + else + macro_names(k) = nuclides_MG(i) % obj % name + k = k + 1 + end if end if end do + nuclide_group = create_group(file_id, "nuclides") + call write_attribute(nuclide_group, "n_nuclides", num_nuclides) + macro_group = create_group(file_id, "macroscopics") + call write_attribute(macro_group, "n_macroscopics", num_macros) ! Write nuclide names and awrs - call write_dataset(nuclide_group, "names", nucnames) - call write_dataset(nuclide_group, "awrs", awrs) - + if (num_nuclides > 0) then + ! Write useful data from nuclide objects + call write_dataset(nuclide_group, "names", nuc_names) + call write_dataset(nuclide_group, "awrs", awrs) + end if + if (num_macros > 0) then + ! Write useful data from macroscopic objects + call write_dataset(macro_group, "names", macro_names) + end if call close_group(nuclide_group) + call close_group(macro_group) - deallocate(nucnames, awrs) + + if (allocated(nuc_names)) deallocate(nuc_names, awrs) + if (allocated(macro_names)) deallocate(macro_names) end subroutine write_nuclides @@ -371,7 +418,13 @@ contains integer :: i integer :: j - character(20), allocatable :: nucnames(:) + integer :: k + integer :: n + character(20), allocatable :: nuc_names(:) + character(20), allocatable :: macro_names(:) + real(8), allocatable :: nuc_densities(:) + integer :: num_nuclides + integer :: num_macros integer(HID_T) :: materials_group integer(HID_T) :: material_group type(Material), pointer :: m @@ -399,24 +452,69 @@ contains ! Write atom density with units call write_dataset(material_group, "atom_density", m % density) - ! Copy ZAID for each nuclide to temporary array - allocate(nucnames(m%n_nuclides)) - do j = 1, m%n_nuclides - if (run_CE) then - nucnames(j) = nuclides(m%nuclide(j))%name - else - nucnames(j) = nuclides_MG(m%nuclide(j))%obj%name + if (run_CE) then + num_nuclides = m % n_nuclides + num_macros = 0 + else + ! Find the number of macroscopic and nuclide data in this material + num_nuclides = 0 + num_macros = 0 + k = 1 + n = 1 + do j = 1, m % n_nuclides + if (nuclides_MG(m % nuclide(j)) % obj % awr /= MACROSCOPIC_AWR) then + num_nuclides = num_nuclides + 1 + else + num_macros = num_macros + 1 + end if + end do + end if + + ! Copy ZAID or macro name for each nuclide to temporary array + if (num_nuclides > 0) then + allocate(nuc_names(num_nuclides)) + allocate(nuc_densities(num_nuclides)) + end if + if (run_CE) then + do j = 1, m % n_nuclides + nuc_names(j) = nuclides(m%nuclide(j))%name + nuc_densities(j) = m % atom_density(j) + end do + else + if (num_macros > 0) then + allocate(macro_names(num_macros)) end if - end do + + k = 1 + n = 1 + do j = 1, m % n_nuclides + if (nuclides_MG(m % nuclide(j)) % obj % awr /= MACROSCOPIC_AWR) then + nuc_names(k) = nuclides_MG(m % nuclide(j)) % obj % name + nuc_densities(k) = m % atom_density(j) + k = k + 1 + else + macro_names(n) = nuclides_MG(m % nuclide(j)) % obj % name + n = n + 1 + end if + end do + end if ! Write temporary array to 'nuclides' - call write_dataset(material_group, "nuclides", nucnames) + if (num_nuclides > 0) then + call write_dataset(material_group, "nuclides", nuc_names) + ! Deallocate temporary array + deallocate(nuc_names) + ! Write atom densities + call write_dataset(material_group, "nuclide_densities", nuc_densities) + deallocate(nuc_densities) + end if - ! Deallocate temporary array - deallocate(nucnames) - - ! Write atom densities - call write_dataset(material_group, "nuclide_densities", m%atom_density) + ! Write temporary array to 'macroscopics' + if (num_macros > 0) then + call write_dataset(material_group, "macroscopics", macro_names) + ! Deallocate temporary array + deallocate(macro_names) + end if if (m%n_sab > 0) then call write_dataset(material_group, "sab_names", m%sab_names) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index f4d12be7e..483246b36 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -84,7 +84,6 @@ contains integer :: i ! loop index for scoring bins integer :: l ! loop index for nuclides in material integer :: m ! loop index for reactions - integer :: q ! loop index for scoring bins integer :: i_temp ! temperature index integer :: i_nuc ! index in nuclides array (from material) integer :: i_energy ! index in nuclide energy grid @@ -104,9 +103,7 @@ contains ! Pre-collision energy of particle E = p % last_E - i = 0 - SCORE_LOOP: do q = 1, t % n_user_score_bins - i = i + 1 + SCORE_LOOP: do i = 1, t % n_score_bins ! determine what type of score bin score_bin = t % score_bins(i) @@ -120,7 +117,7 @@ contains select case(score_bin) - case (SCORE_FLUX, SCORE_FLUX_YN) + case (SCORE_FLUX) if (t % estimator == ESTIMATOR_ANALOG) then ! All events score to a flux bin. We actually use a collision ! estimator in place of an analog one since there is no way to count @@ -140,7 +137,7 @@ contains end if - case (SCORE_TOTAL, SCORE_TOTAL_YN) + case (SCORE_TOTAL) if (t % estimator == ESTIMATOR_ANALOG) then ! All events will score to the total reaction rate. We can just ! use the weight of the particle entering the collision as the @@ -187,7 +184,7 @@ contains end if - case (SCORE_SCATTER, SCORE_SCATTER_N) + case (SCORE_SCATTER) if (t % estimator == ESTIMATOR_ANALOG) then ! Skip any event where the particle didn't scatter if (p % event /= EVENT_SCATTER) cycle SCORE_LOOP @@ -197,7 +194,6 @@ contains score = p % last_wgt * flux else - ! Note SCORE_SCATTER_N not available for tracklength/collision. if (i_nuclide > 0) then score = (micro_xs(i_nuclide) % total & - micro_xs(i_nuclide) % absorption) * atom_density * flux @@ -207,33 +203,7 @@ contains end if - case (SCORE_SCATTER_PN) - ! Only analog estimators are available. - ! Skip any event where the particle didn't scatter - if (p % event /= EVENT_SCATTER) then - i = i + t % moment_order(i) - cycle SCORE_LOOP - end if - ! Since only scattering events make it here, again we can use - ! the weight entering the collision as the estimator for the - ! reaction rate - score = p % last_wgt * flux - - - case (SCORE_SCATTER_YN) - ! Only analog estimators are available. - ! Skip any event where the particle didn't scatter - if (p % event /= EVENT_SCATTER) then - i = i + (t % moment_order(i) + 1)**2 - 1 - cycle SCORE_LOOP - end if - ! Since only scattering events make it here, again we can use - ! the weight entering the collision as the estimator for the - ! reaction rate - score = p % last_wgt * flux - - - case (SCORE_NU_SCATTER, SCORE_NU_SCATTER_N) + case (SCORE_NU_SCATTER) ! Only analog estimators are available. ! Skip any event where the particle didn't scatter if (p % event /= EVENT_SCATTER) cycle SCORE_LOOP @@ -256,58 +226,6 @@ contains end if - case (SCORE_NU_SCATTER_PN) - ! Only analog estimators are available. - ! Skip any event where the particle didn't scatter - if (p % event /= EVENT_SCATTER) then - i = i + t % moment_order(i) - cycle SCORE_LOOP - end if - ! For scattering production, we need to use the pre-collision - ! weight times the yield as the estimate for the number of - ! neutrons exiting a reaction with neutrons in the exit channel - if (p % event_MT == ELASTIC .or. p % event_MT == N_LEVEL .or. & - (p % event_MT >= N_N1 .and. p % event_MT <= N_NC)) then - ! Don't waste time on very common reactions we know have - ! multiplicities of one. - score = p % last_wgt * flux - else - m = nuclides(p % event_nuclide) % reaction_index(p % event_MT) - - ! Get yield and apply to score - associate (rxn => nuclides(p % event_nuclide) % reactions(m)) - score = p % last_wgt * flux & - * rxn % products(1) % yield % evaluate(E) - end associate - end if - - - case (SCORE_NU_SCATTER_YN) - ! Only analog estimators are available. - ! Skip any event where the particle didn't scatter - if (p % event /= EVENT_SCATTER) then - i = i + (t % moment_order(i) + 1)**2 - 1 - cycle SCORE_LOOP - end if - ! For scattering production, we need to use the pre-collision - ! weight times the yield as the estimate for the number of - ! neutrons exiting a reaction with neutrons in the exit channel - if (p % event_MT == ELASTIC .or. p % event_MT == N_LEVEL .or. & - (p % event_MT >= N_N1 .and. p % event_MT <= N_NC)) then - ! Don't waste time on very common reactions we know have - ! multiplicities of one. - score = p % last_wgt * flux - else - m = nuclides(p % event_nuclide) % reaction_index(p % event_MT) - - ! Get yield and apply to score - associate (rxn => nuclides(p%event_nuclide)%reactions(m)) - score = p % last_wgt * flux & - * rxn % products(1) % yield % evaluate(E) - end associate - end if - - case (SCORE_ABSORPTION) if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing) then @@ -1359,7 +1277,7 @@ contains end if i = 0 - SCORE_LOOP: do q = 1, t % n_user_score_bins + SCORE_LOOP: do q = 1, t % n_score_bins i = i + 1 ! determine what type of score bin @@ -1374,7 +1292,7 @@ contains select case(score_bin) - case (SCORE_FLUX, SCORE_FLUX_YN) + case (SCORE_FLUX) if (t % estimator == ESTIMATOR_ANALOG) then ! All events score to a flux bin. We actually use a collision ! estimator in place of an analog one since there is no way to count @@ -1395,7 +1313,7 @@ contains end if - case (SCORE_TOTAL, SCORE_TOTAL_YN) + case (SCORE_TOTAL) if (t % estimator == ESTIMATOR_ANALOG) then ! All events will score to the total reaction rate. We can just ! use the weight of the particle entering the collision as the @@ -1456,15 +1374,10 @@ contains end if - case (SCORE_SCATTER, SCORE_SCATTER_N, SCORE_SCATTER_PN, SCORE_SCATTER_YN) + case (SCORE_SCATTER) if (t % estimator == ESTIMATOR_ANALOG) then ! Skip any event where the particle didn't scatter if (p % event /= EVENT_SCATTER) then - if (score_bin == SCORE_SCATTER_PN) then - i = i + t % moment_order(i) - else if (score_bin == SCORE_SCATTER_YN) then - i = i + (t % moment_order(i) + 1)**2 - 1 - end if cycle SCORE_LOOP end if @@ -1485,7 +1398,6 @@ contains end if else - ! Note SCORE_SCATTER_*N not available for tracklength/collision. if (i_nuclide > 0) then score = atom_density * flux * & nucxs % get_xs('scatter/mult', p_g, UVW=p_uvw) @@ -1498,16 +1410,10 @@ contains end if - case (SCORE_NU_SCATTER, SCORE_NU_SCATTER_N, SCORE_NU_SCATTER_PN, & - SCORE_NU_SCATTER_YN) + case (SCORE_NU_SCATTER) if (t % estimator == ESTIMATOR_ANALOG) then ! Skip any event where the particle didn't scatter if (p % event /= EVENT_SCATTER) then - if (score_bin == SCORE_NU_SCATTER_PN) then - i = i + t % moment_order(i) - else if (score_bin == SCORE_NU_SCATTER_YN) then - i = i + (t % moment_order(i) + 1)**2 - 1 - end if cycle SCORE_LOOP end if @@ -1528,7 +1434,6 @@ contains end if else - ! Note SCORE_NU_SCATTER_*N not available for tracklength/collision. if (i_nuclide > 0) then score = nucxs % get_xs('scatter', p_g, UVW=p_uvw) * & atom_density * flux @@ -2101,98 +2006,10 @@ contains real(8), intent(inout) :: score ! data to score integer, intent(inout) :: i ! Working index - integer :: num_nm ! Number of N,M orders in harmonic - integer :: n ! Moment loop index - real(8) :: uvw(3) - - select case(score_bin) - case (SCORE_SCATTER_N, SCORE_NU_SCATTER_N) - ! Find the scattering order for a singly requested moment, and - ! store its moment contribution. - if (t % moment_order(i) == 1) then - score = score * p % mu ! avoid function call overhead - else - score = score * calc_pn(t % moment_order(i), p % mu) - endif !$omp atomic t % results(RESULT_VALUE, score_index, filter_index) = & t % results(RESULT_VALUE, score_index, filter_index) + score - - case(SCORE_SCATTER_YN, SCORE_NU_SCATTER_YN) - score_index = score_index - 1 - num_nm = 1 - ! Find the order for a collection of requested moments - ! and store the moment contribution of each - do n = 0, t % moment_order(i) - ! determine scoring bin index - score_index = score_index + num_nm - ! Update number of total n,m bins for this n (m = [-n: n]) - num_nm = 2 * n + 1 - - ! multiply score by the angular flux moments and store -!$omp critical (score_general_scatt_yn) - t % results(RESULT_VALUE, score_index: score_index + num_nm - 1, & - filter_index) = t % results(RESULT_VALUE, & - score_index: score_index + num_nm - 1, filter_index) & - + score * calc_pn(n, p % mu) * calc_rn(n, p % last_uvw) -!$omp end critical (score_general_scatt_yn) - end do - i = i + (t % moment_order(i) + 1)**2 - 1 - - - case(SCORE_FLUX_YN, SCORE_TOTAL_YN) - score_index = score_index - 1 - num_nm = 1 - if (t % estimator == ESTIMATOR_ANALOG .or. & - t % estimator == ESTIMATOR_COLLISION) then - uvw = p % last_uvw - else if (t % estimator == ESTIMATOR_TRACKLENGTH) then - uvw = p % coord(1) % uvw - end if - ! Find the order for a collection of requested moments - ! and store the moment contribution of each - do n = 0, t % moment_order(i) - ! determine scoring bin index - score_index = score_index + num_nm - ! Update number of total n,m bins for this n (m = [-n: n]) - num_nm = 2 * n + 1 - - ! multiply score by the angular flux moments and store -!$omp critical (score_general_flux_tot_yn) - t % results(RESULT_VALUE, score_index: score_index + num_nm - 1, & - filter_index) = t % results(RESULT_VALUE, & - score_index: score_index + num_nm - 1, filter_index) & - + score * calc_rn(n, uvw) -!$omp end critical (score_general_flux_tot_yn) - end do - i = i + (t % moment_order(i) + 1)**2 - 1 - - - case (SCORE_SCATTER_PN, SCORE_NU_SCATTER_PN) - score_index = score_index - 1 - ! Find the scattering order for a collection of requested moments - ! and store the moment contribution of each - do n = 0, t % moment_order(i) - ! determine scoring bin index - score_index = score_index + 1 - - ! get the score and tally it -!$omp atomic - t % results(RESULT_VALUE, score_index, filter_index) = & - t % results(RESULT_VALUE, score_index, filter_index) & - + score * calc_pn(n, p % mu) - end do - i = i + t % moment_order(i) - - - case default -!$omp atomic - t % results(RESULT_VALUE, score_index, filter_index) = & - t % results(RESULT_VALUE, score_index, filter_index) + score - - end select - end subroutine expand_and_score !=============================================================================== @@ -3134,7 +2951,7 @@ contains ! Currently only one score type k = 0 - SCORE_LOOP: do q = 1, t % n_user_score_bins + SCORE_LOOP: do q = 1, t % n_score_bins k = k + 1 ! determine what type of score bin diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 413464b7b..2d285706f 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -74,13 +74,8 @@ module tally_header logical :: all_nuclides = .false. ! Values to score, e.g. flux, absorption, etc. - ! scat_order is the scattering order for each score. - ! It is to be 0 if the scattering order is 0, or if the score is not a - ! scattering response. integer :: n_score_bins = 0 integer, allocatable :: score_bins(:) - integer, allocatable :: moment_order(:) - integer :: n_user_score_bins = 0 ! Results for each bin -- the first dimension of the array is for scores ! (e.g. flux, total reaction rate, fission reaction rate, etc.) and the @@ -795,7 +790,6 @@ contains associate (t => tallies(index) % obj) if (allocated(t % score_bins)) deallocate(t % score_bins) allocate(t % score_bins(n)) - t % n_user_score_bins = n t % n_score_bins = n do i = 1, n diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index 7dcb3d71e..2cb3bf819 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -104,8 +104,6 @@ contains integer :: s ! loop index for triggers integer :: filter_index ! index in results array for filters integer :: score_index ! scoring bin index - integer :: n_order ! loop index for moment orders - integer :: nm_order ! loop index for Ynm moment orders integer(C_INT) :: err real(8) :: uncertainty ! trigger uncertainty real(8) :: std_dev = ZERO ! trigger standard deviation @@ -187,70 +185,18 @@ contains ! Initialize score bin index NUCLIDE_LOOP: do n = 1, t % n_nuclide_bins - select case(t % score_bins(trigger % score_index)) + call get_trigger_uncertainty(std_dev, rel_err, & + score_index, filter_index, t) - case (SCORE_SCATTER_PN, SCORE_NU_SCATTER_PN) - - score_index = score_index - 1 - - do n_order = 0, t % moment_order(trigger % score_index) - score_index = score_index + 1 - - call get_trigger_uncertainty(std_dev, rel_err, & - score_index, filter_index, t) - - if (trigger % variance < variance) then - trigger % variance = std_dev ** 2 - end if - if (trigger % std_dev < std_dev) then - trigger % std_dev = std_dev - end if - if (trigger % rel_err < rel_err) then - trigger % rel_err = rel_err - end if - - end do - - case (SCORE_SCATTER_YN, SCORE_NU_SCATTER_YN, SCORE_FLUX_YN, & - SCORE_TOTAL_YN) - - score_index = score_index - 1 - - do n_order = 0, t % moment_order(trigger % score_index) - do nm_order = -n_order, n_order - score_index = score_index + 1 - - call get_trigger_uncertainty(std_dev, rel_err, & - score_index, filter_index, t) - - if (trigger % variance < variance) then - trigger % variance = std_dev ** 2 - end if - if (trigger % std_dev < std_dev) then - trigger % std_dev = std_dev - end if - if (trigger % rel_err < rel_err) then - trigger % rel_err = rel_err - end if - - end do - end do - - case default - call get_trigger_uncertainty(std_dev, rel_err, & - score_index, filter_index, t) - - if (trigger % variance < variance) then - trigger % variance = std_dev ** 2 - end if - if (trigger % std_dev < std_dev) then - trigger % std_dev = std_dev - end if - if (trigger % rel_err < rel_err) then - trigger % rel_err = rel_err - end if - - end select + if (trigger % variance < variance) then + trigger % variance = std_dev ** 2 + end if + if (trigger % std_dev < std_dev) then + trigger % std_dev = std_dev + end if + if (trigger % rel_err < rel_err) then + trigger % rel_err = rel_err + end if select case (t % triggers(s) % type) case(VARIANCE) diff --git a/tests/regression_tests/cmfd_feed/results_true.dat b/tests/regression_tests/cmfd_feed/results_true.dat index 5e6750fe6..aba219ba8 100644 --- a/tests/regression_tests/cmfd_feed/results_true.dat +++ b/tests/regression_tests/cmfd_feed/results_true.dat @@ -26,62 +26,42 @@ tally 2: 2.667071E+01 1.600292E+01 1.293670E+01 -2.252427E+00 -2.605738E-01 4.268506E+01 9.161216E+01 3.022909E+01 4.598915E+01 -3.873926E+00 -7.615035E-01 5.680399E+01 1.623879E+02 4.033805E+01 8.196263E+01 -5.280610E+00 -1.414008E+00 6.814742E+01 2.331778E+02 4.851618E+01 1.182330E+02 -6.261805E+00 -1.983205E+00 7.392923E+01 2.740255E+02 5.253586E+01 1.384152E+02 -6.733810E+00 -2.278242E+00 7.332860E+01 2.698608E+02 5.227405E+01 1.371810E+02 -6.714658E+00 -2.273652E+00 6.830172E+01 2.340687E+02 4.867159E+01 1.188724E+02 -6.215002E+00 -1.956978E+00 5.885634E+01 1.736180E+02 4.170434E+01 8.719622E+01 -5.253064E+00 -1.396224E+00 4.371848E+01 9.592893E+01 3.106403E+01 4.844308E+01 -3.818076E+00 -7.509442E-01 2.338413E+01 2.752467E+01 1.636713E+01 1.347770E+01 -2.219928E+00 -2.515492E-01 tally 3: 1.538752E+01 1.196478E+01 @@ -364,6 +344,47 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 +tally 5: +1.538652E+01 +1.196332E+01 +2.252427E+00 +2.605738E-01 +2.911344E+01 +4.267319E+01 +3.873926E+00 +7.615035E-01 +3.884516E+01 +7.604619E+01 +5.280610E+00 +1.414008E+00 +4.672391E+01 +1.096625E+02 +6.261805E+00 +1.983205E+00 +5.058447E+01 +1.283588E+02 +6.733810E+00 +2.278242E+00 +5.033589E+01 +1.271898E+02 +6.714658E+00 +2.273652E+00 +4.687563E+01 +1.102719E+02 +6.215002E+00 +1.956978E+00 +4.013134E+01 +8.075062E+01 +5.253064E+00 +1.396224E+00 +2.996497E+01 +4.508840E+01 +3.818076E+00 +7.509442E-01 +1.574994E+01 +1.248291E+01 +2.219928E+00 +2.515492E-01 cmfd indices 1.000000E+01 1.000000E+00 diff --git a/tests/regression_tests/cmfd_nofeed/results_true.dat b/tests/regression_tests/cmfd_nofeed/results_true.dat index f00966cf8..1636af364 100644 --- a/tests/regression_tests/cmfd_nofeed/results_true.dat +++ b/tests/regression_tests/cmfd_nofeed/results_true.dat @@ -26,62 +26,42 @@ tally 2: 2.726751E+01 1.624000E+01 1.334217E+01 -2.239367E+00 -2.607315E-01 4.184801E+01 8.813954E+01 2.955600E+01 4.401685E+01 -3.937924E+00 -7.877545E-01 5.620224E+01 1.589242E+02 3.981400E+01 7.983679E+01 -5.183337E+00 -1.367303E+00 6.834724E+01 2.342245E+02 4.869600E+01 1.189597E+02 -6.288549E+00 -1.997858E+00 7.481522E+01 2.802998E+02 5.346500E+01 1.431835E+02 -6.691123E+00 -2.252645E+00 7.381412E+01 2.733775E+02 5.269700E+01 1.393729E+02 -6.846095E+00 -2.360683E+00 6.907776E+01 2.396752E+02 4.918500E+01 1.215909E+02 -6.400076E+00 -2.073871E+00 5.783261E+01 1.680814E+02 4.107800E+01 8.480751E+01 -5.269220E+00 -1.404986E+00 4.120212E+01 8.516647E+01 2.930300E+01 4.310295E+01 -3.730803E+00 -7.015777E-01 2.228419E+01 2.504034E+01 1.554100E+01 1.217931E+01 -2.126451E+00 -2.315275E-01 tally 3: 1.561100E+01 1.233967E+01 @@ -364,6 +344,47 @@ tally 4: 0.000000E+00 0.000000E+00 0.000000E+00 +tally 5: +1.560800E+01 +1.233482E+01 +2.239367E+00 +2.607315E-01 +2.847600E+01 +4.087518E+01 +3.937924E+00 +7.877545E-01 +3.833600E+01 +7.405661E+01 +5.183337E+00 +1.367303E+00 +4.686600E+01 +1.101919E+02 +6.288549E+00 +1.997858E+00 +5.154500E+01 +1.331141E+02 +6.691123E+00 +2.252645E+00 +5.067000E+01 +1.288871E+02 +6.846095E+00 +2.360683E+00 +4.737700E+01 +1.128379E+02 +6.400076E+00 +2.073871E+00 +3.952800E+01 +7.854943E+01 +5.269220E+00 +1.404986E+00 +2.818600E+01 +3.989536E+01 +3.730803E+00 +7.015777E-01 +1.497300E+01 +1.131008E+01 +2.126451E+00 +2.315275E-01 cmfd indices 1.000000E+01 1.000000E+00 diff --git a/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat b/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat index 70996fe37..bc5c4b2d4 100644 --- a/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_ce_to_mg/inputs_true.dat @@ -59,10 +59,13 @@ 0.0 0.625 20000000.0 - + + 3 + + 2 - + 3 @@ -108,9 +111,9 @@ analog - 1 2 7 + 1 2 7 11 total - nu-scatter-P3 + nu-scatter analog @@ -126,121 +129,121 @@ analog - 14 2 + 15 2 total flux tracklength - 14 2 + 15 2 total total tracklength - 14 2 + 15 2 total flux tracklength - 14 2 + 15 2 total absorption tracklength - 14 2 + 15 2 total flux analog - 14 2 7 + 15 2 7 total nu-fission analog - 14 2 + 15 2 total flux analog - 14 2 7 + 15 2 7 11 total - nu-scatter-P3 + nu-scatter analog - 14 2 7 + 15 2 7 total nu-scatter analog - 14 2 7 + 15 2 7 total scatter analog - 27 2 + 29 2 total flux tracklength - 27 2 + 29 2 total total tracklength - 27 2 + 29 2 total flux tracklength - 27 2 + 29 2 total absorption tracklength - 27 2 + 29 2 total flux analog - 27 2 7 + 29 2 7 total nu-fission analog - 27 2 + 29 2 total flux analog - 27 2 7 + 29 2 7 11 total - nu-scatter-P3 + nu-scatter analog - 27 2 7 + 29 2 7 total nu-scatter analog - 27 2 7 + 29 2 7 total scatter analog diff --git a/tests/regression_tests/mgxs_library_condense/inputs_true.dat b/tests/regression_tests/mgxs_library_condense/inputs_true.dat index ef6c4c520..5aedd383d 100644 --- a/tests/regression_tests/mgxs_library_condense/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_condense/inputs_true.dat @@ -59,16 +59,22 @@ 0.0 0.625 20000000.0 - + + 1 + + + 3 + + 0.0 20000000.0 - + 1 2 3 4 5 6 - + 2 - + 3 @@ -102,9 +108,9 @@ analog - 1 5 + 1 5 6 total - scatter-1 + scatter analog @@ -126,9 +132,9 @@ analog - 1 5 + 1 5 6 total - nu-scatter-1 + nu-scatter analog @@ -228,9 +234,9 @@ analog - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog @@ -240,9 +246,9 @@ analog - 1 2 5 + 1 2 5 28 total - nu-scatter-P3 + nu-scatter analog @@ -288,9 +294,9 @@ tracklength - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog @@ -306,883 +312,865 @@ tracklength - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog 1 2 5 total - nu-scatter-0 + nu-scatter analog - 1 2 5 + 1 52 total - scatter-0 + nu-fission analog - 1 46 + 1 5 total nu-fission analog - 1 5 + 1 52 total - nu-fission + prompt-nu-fission analog - 1 46 + 1 5 total prompt-nu-fission analog - 1 5 + 1 2 total - prompt-nu-fission - analog + flux + tracklength 1 2 total - flux + inverse-velocity tracklength 1 2 total - inverse-velocity + flux tracklength 1 2 total - flux + prompt-nu-fission tracklength - 1 2 - total - prompt-nu-fission - tracklength - - 1 2 total flux analog - + 1 2 5 total prompt-nu-fission analog - + 1 2 total flux tracklength - - 1 59 2 + + 1 65 2 total delayed-nu-fission tracklength + + 1 65 52 + total + delayed-nu-fission + analog + - 1 59 46 + 1 65 5 total delayed-nu-fission analog - 1 59 5 - total - delayed-nu-fission - analog - - 1 2 total nu-fission tracklength + + 1 65 2 + total + delayed-nu-fission + tracklength + - 1 59 2 + 1 65 2 total delayed-nu-fission tracklength - 1 59 2 - total - delayed-nu-fission - tracklength - - - 1 59 2 + 1 65 2 total decay-rate tracklength - + 1 2 total flux analog - - 1 59 2 5 + + 1 65 2 5 total delayed-nu-fission analog - - 74 2 + + 80 2 total flux tracklength + + 80 2 + total + total + tracklength + - 74 2 + 80 2 total - total + flux tracklength - 74 2 + 80 2 total - flux + total tracklength - 74 2 - total - total - tracklength - - - 74 2 + 80 2 total flux analog + + 80 5 6 + total + scatter + analog + - 74 5 - total - scatter-1 - analog - - - 74 2 + 80 2 total flux tracklength - - 74 2 + + 80 2 total total tracklength - - 74 2 + + 80 2 total flux analog + + 80 5 6 + total + nu-scatter + analog + - 74 5 - total - nu-scatter-1 - analog - - - 74 2 + 80 2 total flux tracklength + + 80 2 + total + absorption + tracklength + - 74 2 + 80 2 total - absorption + flux tracklength - 74 2 - total - flux - tracklength - - - 74 2 + 80 2 total absorption tracklength - - 74 2 + + 80 2 total fission tracklength + + 80 2 + total + flux + tracklength + - 74 2 - total - flux - tracklength - - - 74 2 + 80 2 total fission tracklength - - 74 2 + + 80 2 total flux tracklength - - 74 2 + + 80 2 total nu-fission tracklength - - 74 2 + + 80 2 total flux tracklength - - 74 2 + + 80 2 total kappa-fission tracklength - - 74 2 + + 80 2 total flux tracklength + + 80 2 + total + scatter + tracklength + - 74 2 - total - scatter - tracklength - - - 74 2 + 80 2 total flux analog + + 80 2 + total + nu-scatter + analog + - 74 2 + 80 2 total - nu-scatter + flux analog - 74 2 + 80 2 5 28 total - flux + scatter analog - 74 2 5 - total - scatter-P3 - analog - - - 74 2 + 80 2 total flux analog - - 74 2 5 - total - nu-scatter-P3 - analog - - - 74 2 5 + + 80 2 5 28 total nu-scatter analog - - 74 2 5 + + 80 2 5 + total + nu-scatter + analog + + + 80 2 5 total scatter analog + + 80 2 + total + flux + analog + - 74 2 + 80 2 5 total - flux + nu-fission analog - 74 2 5 + 80 2 5 total - nu-fission + scatter analog - 74 2 5 - total - scatter - analog - - - 74 2 + 80 2 total flux tracklength + + 80 2 + total + scatter + tracklength + - 74 2 + 80 2 5 28 total scatter - tracklength - - - 74 2 5 - total - scatter-P3 analog - - 74 2 + + 80 2 total flux tracklength - - 74 2 + + 80 2 total scatter tracklength - - 74 2 5 + + 80 2 5 28 total - scatter-P3 + scatter + analog + + + 80 2 5 + total + nu-scatter analog - 74 2 5 + 80 52 total - nu-scatter-0 + nu-fission analog - 74 2 5 + 80 5 total - scatter-0 + nu-fission analog - 74 46 + 80 52 total - nu-fission + prompt-nu-fission analog - 74 5 + 80 5 total - nu-fission + prompt-nu-fission analog - 74 46 + 80 2 total - prompt-nu-fission - analog + flux + tracklength - 74 5 + 80 2 total - prompt-nu-fission - analog + inverse-velocity + tracklength - 74 2 + 80 2 total flux tracklength - 74 2 + 80 2 total - inverse-velocity + prompt-nu-fission tracklength - 74 2 + 80 2 total flux - tracklength + analog - 74 2 + 80 2 5 total prompt-nu-fission - tracklength + analog - 74 2 + 80 2 total flux - analog + tracklength - 74 2 5 + 80 65 2 total - prompt-nu-fission - analog + delayed-nu-fission + tracklength - 74 2 + 80 65 52 total - flux - tracklength + delayed-nu-fission + analog - 74 59 2 + 80 65 5 total delayed-nu-fission - tracklength + analog - 74 59 46 - total - delayed-nu-fission - analog - - - 74 59 5 - total - delayed-nu-fission - analog - - - 74 2 + 80 2 total nu-fission tracklength + + 80 65 2 + total + delayed-nu-fission + tracklength + + + 80 65 2 + total + delayed-nu-fission + tracklength + - 74 59 2 - total - delayed-nu-fission - tracklength - - - 74 59 2 - total - delayed-nu-fission - tracklength - - - 74 59 2 + 80 65 2 total decay-rate tracklength - - 74 2 + + 80 2 total flux analog - - 74 59 2 5 + + 80 65 2 5 total delayed-nu-fission analog + + 159 2 + total + flux + tracklength + + + 159 2 + total + total + tracklength + - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total total tracklength - 147 2 + 159 2 total flux - tracklength + analog - 147 2 + 159 5 6 total - total - tracklength + scatter + analog - 147 2 - total - flux - analog - - - 147 5 - total - scatter-1 - analog - - - 147 2 + 159 2 total flux tracklength - - 147 2 + + 159 2 total total tracklength - - 147 2 + + 159 2 total flux analog - - 147 5 + + 159 5 6 total - nu-scatter-1 + nu-scatter analog + + 159 2 + total + flux + tracklength + + + 159 2 + total + absorption + tracklength + - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total absorption tracklength - 147 2 + 159 2 + total + fission + tracklength + + + 159 2 total flux tracklength - - 147 2 - total - absorption - tracklength - - 147 2 + 159 2 total fission tracklength - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total - fission + nu-fission tracklength - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total - nu-fission + kappa-fission tracklength - 147 2 + 159 2 total flux tracklength - 147 2 - total - kappa-fission - tracklength - - - 147 2 - total - flux - tracklength - - - 147 2 + 159 2 total scatter tracklength + + 159 2 + total + flux + analog + + + 159 2 + total + nu-scatter + analog + - 147 2 + 159 2 total flux analog - 147 2 + 159 2 5 28 total - nu-scatter + scatter analog - 147 2 + 159 2 total flux analog - 147 2 5 - total - scatter-P3 - analog - - - 147 2 - total - flux - analog - - - 147 2 5 - total - nu-scatter-P3 - analog - - - 147 2 5 + 159 2 5 28 total nu-scatter analog - - 147 2 5 + + 159 2 5 + total + nu-scatter + analog + + + 159 2 5 total scatter analog + + 159 2 + total + flux + analog + + + 159 2 5 + total + nu-fission + analog + - 147 2 + 159 2 5 total - flux + scatter analog - 147 2 5 - total - nu-fission - analog - - - 147 2 5 - total - scatter - analog - - - 147 2 + 159 2 total flux tracklength - - 147 2 + + 159 2 total scatter tracklength + + 159 2 5 28 + total + scatter + analog + + + 159 2 + total + flux + tracklength + - 147 2 5 - total - scatter-P3 - analog - - - 147 2 - total - flux - tracklength - - - 147 2 + 159 2 total scatter tracklength - - 147 2 5 + + 159 2 5 28 total - scatter-P3 + scatter + analog + + + 159 2 5 + total + nu-scatter + analog + + + 159 52 + total + nu-fission analog - 147 2 5 + 159 5 total - nu-scatter-0 + nu-fission analog - 147 2 5 + 159 52 total - scatter-0 + prompt-nu-fission analog - 147 46 + 159 5 total - nu-fission + prompt-nu-fission analog - 147 5 - total - nu-fission - analog - - - 147 46 - total - prompt-nu-fission - analog - - - 147 5 - total - prompt-nu-fission - analog - - - 147 2 + 159 2 total flux tracklength - - 147 2 + + 159 2 total inverse-velocity tracklength - - 147 2 + + 159 2 total flux tracklength - - 147 2 + + 159 2 total prompt-nu-fission tracklength + + 159 2 + total + flux + analog + + + 159 2 5 + total + prompt-nu-fission + analog + + + 159 2 + total + flux + tracklength + - 147 2 + 159 65 2 total - flux - analog + delayed-nu-fission + tracklength - 147 2 5 + 159 65 52 total - prompt-nu-fission + delayed-nu-fission analog - 147 2 + 159 65 5 total - flux - tracklength + delayed-nu-fission + analog - 147 59 2 - total - delayed-nu-fission - tracklength - - - 147 59 46 - total - delayed-nu-fission - analog - - - 147 59 5 - total - delayed-nu-fission - analog - - - 147 2 + 159 2 total nu-fission tracklength - - 147 59 2 + + 159 65 2 total delayed-nu-fission tracklength - - 147 59 2 + + 159 65 2 total delayed-nu-fission tracklength - - 147 59 2 + + 159 65 2 total decay-rate tracklength - - 147 2 + + 159 2 total flux analog - - 147 59 2 5 + + 159 65 2 5 total delayed-nu-fission analog diff --git a/tests/regression_tests/mgxs_library_condense/results_true.dat b/tests/regression_tests/mgxs_library_condense/results_true.dat index 0c9adedb1..32d3d2e1a 100644 --- a/tests/regression_tests/mgxs_library_condense/results_true.dat +++ b/tests/regression_tests/mgxs_library_condense/results_true.dat @@ -18,32 +18,32 @@ 0 1 1 total 0.388721 0.01783 material group in nuclide mean std. dev. 0 1 1 total 0.389304 0.023076 - material group in group out nuclide moment mean std. dev. -0 1 1 1 total P0 0.389304 0.023146 -1 1 1 1 total P1 0.046224 0.005907 -2 1 1 1 total P2 0.017984 0.002883 -3 1 1 1 total P3 0.006628 0.002457 - material group in group out nuclide moment mean std. dev. -0 1 1 1 total P0 0.389304 0.023146 -1 1 1 1 total P1 0.046224 0.005907 -2 1 1 1 total P2 0.017984 0.002883 -3 1 1 1 total P3 0.006628 0.002457 + material group in group out legendre nuclide mean std. dev. +0 1 1 1 P0 total 0.389304 0.023146 +1 1 1 1 P1 total 0.046224 0.005907 +2 1 1 1 P2 total 0.017984 0.002883 +3 1 1 1 P3 total 0.006628 0.002457 + material group in group out legendre nuclide mean std. dev. +0 1 1 1 P0 total 0.389304 0.023146 +1 1 1 1 P1 total 0.046224 0.005907 +2 1 1 1 P2 total 0.017984 0.002883 +3 1 1 1 P3 total 0.006628 0.002457 material group in group out nuclide mean std. dev. 0 1 1 1 total 1.0 0.066111 material group in group out nuclide mean std. dev. 0 1 1 1 total 0.085835 0.005592 material group in group out nuclide mean std. dev. 0 1 1 1 total 1.0 0.066111 - material group in group out nuclide moment mean std. dev. -0 1 1 1 total P0 0.388721 0.031279 -1 1 1 1 total P1 0.046155 0.006407 -2 1 1 1 total P2 0.017957 0.003039 -3 1 1 1 total P3 0.006618 0.002480 - material group in group out nuclide moment mean std. dev. -0 1 1 1 total P0 0.388721 0.040482 -1 1 1 1 total P1 0.046155 0.007097 -2 1 1 1 total P2 0.017957 0.003262 -3 1 1 1 total P3 0.006618 0.002518 + material group in group out legendre nuclide mean std. dev. +0 1 1 1 P0 total 0.388721 0.031279 +1 1 1 1 P1 total 0.046155 0.006407 +2 1 1 1 P2 total 0.017957 0.003039 +3 1 1 1 P3 total 0.006618 0.002480 + material group in group out legendre nuclide mean std. dev. +0 1 1 1 P0 total 0.388721 0.040482 +1 1 1 1 P1 total 0.046155 0.007097 +2 1 1 1 P2 total 0.017957 0.003262 +3 1 1 1 P3 total 0.006618 0.002518 material group out nuclide mean std. dev. 0 1 1 total 1.0 0.046071 material group out nuclide mean std. dev. @@ -109,32 +109,32 @@ 0 2 1 total 0.309384 0.013551 material group in nuclide mean std. dev. 0 2 1 total 0.307987 0.029308 - material group in group out nuclide moment mean std. dev. -0 2 1 1 total P0 0.307987 0.029308 -1 2 1 1 total P1 0.030617 0.007464 -2 2 1 1 total P2 0.018911 0.004323 -3 2 1 1 total P3 0.006235 0.003338 - material group in group out nuclide moment mean std. dev. -0 2 1 1 total P0 0.307987 0.029308 -1 2 1 1 total P1 0.030617 0.007464 -2 2 1 1 total P2 0.018911 0.004323 -3 2 1 1 total P3 0.006235 0.003338 + material group in group out legendre nuclide mean std. dev. +0 2 1 1 P0 total 0.307987 0.029308 +1 2 1 1 P1 total 0.030617 0.007464 +2 2 1 1 P2 total 0.018911 0.004323 +3 2 1 1 P3 total 0.006235 0.003338 + material group in group out legendre nuclide mean std. dev. +0 2 1 1 P0 total 0.307987 0.029308 +1 2 1 1 P1 total 0.030617 0.007464 +2 2 1 1 P2 total 0.018911 0.004323 +3 2 1 1 P3 total 0.006235 0.003338 material group in group out nuclide mean std. dev. 0 2 1 1 total 1.0 0.095039 material group in group out nuclide mean std. dev. 0 2 1 1 total 0.0 0.0 material group in group out nuclide mean std. dev. 0 2 1 1 total 1.0 0.095039 - material group in group out nuclide moment mean std. dev. -0 2 1 1 total P0 0.309384 0.032376 -1 2 1 1 total P1 0.030756 0.007617 -2 2 1 1 total P2 0.018997 0.004420 -3 2 1 1 total P3 0.006263 0.003364 - material group in group out nuclide moment mean std. dev. -0 2 1 1 total P0 0.309384 0.043735 -1 2 1 1 total P1 0.030756 0.008159 -2 2 1 1 total P2 0.018997 0.004775 -3 2 1 1 total P3 0.006263 0.003417 + material group in group out legendre nuclide mean std. dev. +0 2 1 1 P0 total 0.309384 0.032376 +1 2 1 1 P1 total 0.030756 0.007617 +2 2 1 1 P2 total 0.018997 0.004420 +3 2 1 1 P3 total 0.006263 0.003364 + material group in group out legendre nuclide mean std. dev. +0 2 1 1 P0 total 0.309384 0.043735 +1 2 1 1 P1 total 0.030756 0.008159 +2 2 1 1 P2 total 0.018997 0.004775 +3 2 1 1 P3 total 0.006263 0.003417 material group out nuclide mean std. dev. 0 2 1 total 0.0 0.0 material group out nuclide mean std. dev. @@ -200,32 +200,32 @@ 0 3 1 total 0.898938 0.043493 material group in nuclide mean std. dev. 0 3 1 total 0.903415 0.043959 - material group in group out nuclide moment mean std. dev. -0 3 1 1 total P0 0.903415 0.043586 -1 3 1 1 total P1 0.410417 0.015877 -2 3 1 1 total P2 0.143301 0.007187 -3 3 1 1 total P3 0.008739 0.003571 - material group in group out nuclide moment mean std. dev. -0 3 1 1 total P0 0.903415 0.043586 -1 3 1 1 total P1 0.410417 0.015877 -2 3 1 1 total P2 0.143301 0.007187 -3 3 1 1 total P3 0.008739 0.003571 + material group in group out legendre nuclide mean std. dev. +0 3 1 1 P0 total 0.903415 0.043586 +1 3 1 1 P1 total 0.410417 0.015877 +2 3 1 1 P2 total 0.143301 0.007187 +3 3 1 1 P3 total 0.008739 0.003571 + material group in group out legendre nuclide mean std. dev. +0 3 1 1 P0 total 0.903415 0.043586 +1 3 1 1 P1 total 0.410417 0.015877 +2 3 1 1 P2 total 0.143301 0.007187 +3 3 1 1 P3 total 0.008739 0.003571 material group in group out nuclide mean std. dev. 0 3 1 1 total 1.0 0.056867 material group in group out nuclide mean std. dev. 0 3 1 1 total 0.0 0.0 material group in group out nuclide mean std. dev. 0 3 1 1 total 1.0 0.056867 - material group in group out nuclide moment mean std. dev. -0 3 1 1 total P0 0.898938 0.067118 -1 3 1 1 total P1 0.408384 0.028127 -2 3 1 1 total P2 0.142591 0.010824 -3 3 1 1 total P3 0.008696 0.003588 - material group in group out nuclide moment mean std. dev. -0 3 1 1 total P0 0.898938 0.084369 -1 3 1 1 total P1 0.408384 0.036475 -2 3 1 1 total P2 0.142591 0.013525 -3 3 1 1 total P3 0.008696 0.003622 + material group in group out legendre nuclide mean std. dev. +0 3 1 1 P0 total 0.898938 0.067118 +1 3 1 1 P1 total 0.408384 0.028127 +2 3 1 1 P2 total 0.142591 0.010824 +3 3 1 1 P3 total 0.008696 0.003588 + material group in group out legendre nuclide mean std. dev. +0 3 1 1 P0 total 0.898938 0.084369 +1 3 1 1 P1 total 0.408384 0.036475 +2 3 1 1 P2 total 0.142591 0.013525 +3 3 1 1 P3 total 0.008696 0.003622 material group out nuclide mean std. dev. 0 3 1 total 0.0 0.0 material group out nuclide mean std. dev. diff --git a/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat b/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat index ba7dc05ff..f6748d150 100644 --- a/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_distribcell/inputs_true.dat @@ -86,7 +86,13 @@ 0.0 20000000.0 - + + 1 + + + 3 + + 1 2 3 4 5 6 @@ -120,9 +126,9 @@ analog - 1 5 + 1 5 6 total - scatter-1 + scatter analog @@ -144,9 +150,9 @@ analog - 1 5 + 1 5 6 total - nu-scatter-1 + nu-scatter analog @@ -246,9 +252,9 @@ analog - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog @@ -258,9 +264,9 @@ analog - 1 2 5 + 1 2 5 28 total - nu-scatter-P3 + nu-scatter analog @@ -306,9 +312,9 @@ tracklength - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog @@ -324,139 +330,133 @@ tracklength - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog 1 2 5 total - nu-scatter-0 + nu-scatter analog - 1 2 5 + 1 2 total - scatter-0 + nu-fission analog - 1 2 + 1 5 total nu-fission analog - 1 5 + 1 2 total - nu-fission + prompt-nu-fission analog - 1 2 + 1 5 total prompt-nu-fission analog - 1 5 - total - prompt-nu-fission - analog - - 1 2 total flux tracklength - + 1 2 total inverse-velocity tracklength - + 1 2 total flux tracklength + + 1 2 + total + prompt-nu-fission + tracklength + - 1 2 - total - prompt-nu-fission - tracklength - - 1 2 total flux analog - + 1 2 5 total prompt-nu-fission analog - + 1 2 total flux tracklength - - 1 59 2 + + 1 65 2 total delayed-nu-fission tracklength + + 1 65 2 + total + delayed-nu-fission + analog + - 1 59 2 + 1 65 5 total delayed-nu-fission analog - 1 59 5 - total - delayed-nu-fission - analog - - 1 2 total nu-fission tracklength + + 1 65 2 + total + delayed-nu-fission + tracklength + - 1 59 2 + 1 65 2 total delayed-nu-fission tracklength - 1 59 2 - total - delayed-nu-fission - tracklength - - - 1 59 2 + 1 65 2 total decay-rate tracklength - + 1 2 total flux analog - - 1 59 2 5 + + 1 65 2 5 total delayed-nu-fission analog diff --git a/tests/regression_tests/mgxs_library_distribcell/results_true.dat b/tests/regression_tests/mgxs_library_distribcell/results_true.dat index e9277c975..d5496362c 100644 --- a/tests/regression_tests/mgxs_library_distribcell/results_true.dat +++ b/tests/regression_tests/mgxs_library_distribcell/results_true.dat @@ -18,32 +18,32 @@ 0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.390797 0.008717 sum(distribcell) group in nuclide mean std. dev. 0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 0.387332 0.014241 - sum(distribcell) group in group out nuclide moment mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P0 0.387009 0.014230 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P1 0.047179 0.004923 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P2 0.015713 0.003654 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P3 0.005378 0.003137 - sum(distribcell) group in group out nuclide moment mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P0 0.387332 0.014241 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P1 0.047187 0.004933 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P2 0.015727 0.003654 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P3 0.005387 0.003141 + sum(distribcell) group in group out legendre nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P0 total 0.387009 0.014230 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P1 total 0.047179 0.004923 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P2 total 0.015713 0.003654 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P3 total 0.005378 0.003137 + sum(distribcell) group in group out legendre nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P0 total 0.387332 0.014241 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P1 total 0.047187 0.004933 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P2 total 0.015727 0.003654 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P3 total 0.005387 0.003141 sum(distribcell) group in group out nuclide mean std. dev. 0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total 1.000834 0.037242 sum(distribcell) group in group out nuclide mean std. dev. 0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total 0.094516 0.0059 sum(distribcell) group in group out nuclide mean std. dev. 0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total 1.0 0.037213 - sum(distribcell) group in group out nuclide moment mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P0 0.390797 0.016955 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P1 0.047641 0.005091 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P2 0.015866 0.003708 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P3 0.005430 0.003170 - sum(distribcell) group in group out nuclide moment mean std. dev. -0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P0 0.391123 0.022356 -1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P1 0.047680 0.005395 -2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P2 0.015880 0.003758 -3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 total P3 0.005435 0.003179 + sum(distribcell) group in group out legendre nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P0 total 0.390797 0.016955 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P1 total 0.047641 0.005091 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P2 total 0.015866 0.003708 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P3 total 0.005430 0.003170 + sum(distribcell) group in group out legendre nuclide mean std. dev. +0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P0 total 0.391123 0.022356 +1 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P1 total 0.047680 0.005395 +2 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P2 total 0.015880 0.003758 +3 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 1 P3 total 0.005435 0.003179 sum(distribcell) group out nuclide mean std. dev. 0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13... 1 total 1.0 0.080455 sum(distribcell) group out nuclide mean std. dev. diff --git a/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat b/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat index ef6c4c520..5aedd383d 100644 --- a/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_hdf5/inputs_true.dat @@ -59,16 +59,22 @@ 0.0 0.625 20000000.0 - + + 1 + + + 3 + + 0.0 20000000.0 - + 1 2 3 4 5 6 - + 2 - + 3 @@ -102,9 +108,9 @@ analog - 1 5 + 1 5 6 total - scatter-1 + scatter analog @@ -126,9 +132,9 @@ analog - 1 5 + 1 5 6 total - nu-scatter-1 + nu-scatter analog @@ -228,9 +234,9 @@ analog - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog @@ -240,9 +246,9 @@ analog - 1 2 5 + 1 2 5 28 total - nu-scatter-P3 + nu-scatter analog @@ -288,9 +294,9 @@ tracklength - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog @@ -306,883 +312,865 @@ tracklength - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog 1 2 5 total - nu-scatter-0 + nu-scatter analog - 1 2 5 + 1 52 total - scatter-0 + nu-fission analog - 1 46 + 1 5 total nu-fission analog - 1 5 + 1 52 total - nu-fission + prompt-nu-fission analog - 1 46 + 1 5 total prompt-nu-fission analog - 1 5 + 1 2 total - prompt-nu-fission - analog + flux + tracklength 1 2 total - flux + inverse-velocity tracklength 1 2 total - inverse-velocity + flux tracklength 1 2 total - flux + prompt-nu-fission tracklength - 1 2 - total - prompt-nu-fission - tracklength - - 1 2 total flux analog - + 1 2 5 total prompt-nu-fission analog - + 1 2 total flux tracklength - - 1 59 2 + + 1 65 2 total delayed-nu-fission tracklength + + 1 65 52 + total + delayed-nu-fission + analog + - 1 59 46 + 1 65 5 total delayed-nu-fission analog - 1 59 5 - total - delayed-nu-fission - analog - - 1 2 total nu-fission tracklength + + 1 65 2 + total + delayed-nu-fission + tracklength + - 1 59 2 + 1 65 2 total delayed-nu-fission tracklength - 1 59 2 - total - delayed-nu-fission - tracklength - - - 1 59 2 + 1 65 2 total decay-rate tracklength - + 1 2 total flux analog - - 1 59 2 5 + + 1 65 2 5 total delayed-nu-fission analog - - 74 2 + + 80 2 total flux tracklength + + 80 2 + total + total + tracklength + - 74 2 + 80 2 total - total + flux tracklength - 74 2 + 80 2 total - flux + total tracklength - 74 2 - total - total - tracklength - - - 74 2 + 80 2 total flux analog + + 80 5 6 + total + scatter + analog + - 74 5 - total - scatter-1 - analog - - - 74 2 + 80 2 total flux tracklength - - 74 2 + + 80 2 total total tracklength - - 74 2 + + 80 2 total flux analog + + 80 5 6 + total + nu-scatter + analog + - 74 5 - total - nu-scatter-1 - analog - - - 74 2 + 80 2 total flux tracklength + + 80 2 + total + absorption + tracklength + - 74 2 + 80 2 total - absorption + flux tracklength - 74 2 - total - flux - tracklength - - - 74 2 + 80 2 total absorption tracklength - - 74 2 + + 80 2 total fission tracklength + + 80 2 + total + flux + tracklength + - 74 2 - total - flux - tracklength - - - 74 2 + 80 2 total fission tracklength - - 74 2 + + 80 2 total flux tracklength - - 74 2 + + 80 2 total nu-fission tracklength - - 74 2 + + 80 2 total flux tracklength - - 74 2 + + 80 2 total kappa-fission tracklength - - 74 2 + + 80 2 total flux tracklength + + 80 2 + total + scatter + tracklength + - 74 2 - total - scatter - tracklength - - - 74 2 + 80 2 total flux analog + + 80 2 + total + nu-scatter + analog + - 74 2 + 80 2 total - nu-scatter + flux analog - 74 2 + 80 2 5 28 total - flux + scatter analog - 74 2 5 - total - scatter-P3 - analog - - - 74 2 + 80 2 total flux analog - - 74 2 5 - total - nu-scatter-P3 - analog - - - 74 2 5 + + 80 2 5 28 total nu-scatter analog - - 74 2 5 + + 80 2 5 + total + nu-scatter + analog + + + 80 2 5 total scatter analog + + 80 2 + total + flux + analog + - 74 2 + 80 2 5 total - flux + nu-fission analog - 74 2 5 + 80 2 5 total - nu-fission + scatter analog - 74 2 5 - total - scatter - analog - - - 74 2 + 80 2 total flux tracklength + + 80 2 + total + scatter + tracklength + - 74 2 + 80 2 5 28 total scatter - tracklength - - - 74 2 5 - total - scatter-P3 analog - - 74 2 + + 80 2 total flux tracklength - - 74 2 + + 80 2 total scatter tracklength - - 74 2 5 + + 80 2 5 28 total - scatter-P3 + scatter + analog + + + 80 2 5 + total + nu-scatter analog - 74 2 5 + 80 52 total - nu-scatter-0 + nu-fission analog - 74 2 5 + 80 5 total - scatter-0 + nu-fission analog - 74 46 + 80 52 total - nu-fission + prompt-nu-fission analog - 74 5 + 80 5 total - nu-fission + prompt-nu-fission analog - 74 46 + 80 2 total - prompt-nu-fission - analog + flux + tracklength - 74 5 + 80 2 total - prompt-nu-fission - analog + inverse-velocity + tracklength - 74 2 + 80 2 total flux tracklength - 74 2 + 80 2 total - inverse-velocity + prompt-nu-fission tracklength - 74 2 + 80 2 total flux - tracklength + analog - 74 2 + 80 2 5 total prompt-nu-fission - tracklength + analog - 74 2 + 80 2 total flux - analog + tracklength - 74 2 5 + 80 65 2 total - prompt-nu-fission - analog + delayed-nu-fission + tracklength - 74 2 + 80 65 52 total - flux - tracklength + delayed-nu-fission + analog - 74 59 2 + 80 65 5 total delayed-nu-fission - tracklength + analog - 74 59 46 - total - delayed-nu-fission - analog - - - 74 59 5 - total - delayed-nu-fission - analog - - - 74 2 + 80 2 total nu-fission tracklength + + 80 65 2 + total + delayed-nu-fission + tracklength + + + 80 65 2 + total + delayed-nu-fission + tracklength + - 74 59 2 - total - delayed-nu-fission - tracklength - - - 74 59 2 - total - delayed-nu-fission - tracklength - - - 74 59 2 + 80 65 2 total decay-rate tracklength - - 74 2 + + 80 2 total flux analog - - 74 59 2 5 + + 80 65 2 5 total delayed-nu-fission analog + + 159 2 + total + flux + tracklength + + + 159 2 + total + total + tracklength + - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total total tracklength - 147 2 + 159 2 total flux - tracklength + analog - 147 2 + 159 5 6 total - total - tracklength + scatter + analog - 147 2 - total - flux - analog - - - 147 5 - total - scatter-1 - analog - - - 147 2 + 159 2 total flux tracklength - - 147 2 + + 159 2 total total tracklength - - 147 2 + + 159 2 total flux analog - - 147 5 + + 159 5 6 total - nu-scatter-1 + nu-scatter analog + + 159 2 + total + flux + tracklength + + + 159 2 + total + absorption + tracklength + - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total absorption tracklength - 147 2 + 159 2 + total + fission + tracklength + + + 159 2 total flux tracklength - - 147 2 - total - absorption - tracklength - - 147 2 + 159 2 total fission tracklength - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total - fission + nu-fission tracklength - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total - nu-fission + kappa-fission tracklength - 147 2 + 159 2 total flux tracklength - 147 2 - total - kappa-fission - tracklength - - - 147 2 - total - flux - tracklength - - - 147 2 + 159 2 total scatter tracklength + + 159 2 + total + flux + analog + + + 159 2 + total + nu-scatter + analog + - 147 2 + 159 2 total flux analog - 147 2 + 159 2 5 28 total - nu-scatter + scatter analog - 147 2 + 159 2 total flux analog - 147 2 5 - total - scatter-P3 - analog - - - 147 2 - total - flux - analog - - - 147 2 5 - total - nu-scatter-P3 - analog - - - 147 2 5 + 159 2 5 28 total nu-scatter analog - - 147 2 5 + + 159 2 5 + total + nu-scatter + analog + + + 159 2 5 total scatter analog + + 159 2 + total + flux + analog + + + 159 2 5 + total + nu-fission + analog + - 147 2 + 159 2 5 total - flux + scatter analog - 147 2 5 - total - nu-fission - analog - - - 147 2 5 - total - scatter - analog - - - 147 2 + 159 2 total flux tracklength - - 147 2 + + 159 2 total scatter tracklength + + 159 2 5 28 + total + scatter + analog + + + 159 2 + total + flux + tracklength + - 147 2 5 - total - scatter-P3 - analog - - - 147 2 - total - flux - tracklength - - - 147 2 + 159 2 total scatter tracklength - - 147 2 5 + + 159 2 5 28 total - scatter-P3 + scatter + analog + + + 159 2 5 + total + nu-scatter + analog + + + 159 52 + total + nu-fission analog - 147 2 5 + 159 5 total - nu-scatter-0 + nu-fission analog - 147 2 5 + 159 52 total - scatter-0 + prompt-nu-fission analog - 147 46 + 159 5 total - nu-fission + prompt-nu-fission analog - 147 5 - total - nu-fission - analog - - - 147 46 - total - prompt-nu-fission - analog - - - 147 5 - total - prompt-nu-fission - analog - - - 147 2 + 159 2 total flux tracklength - - 147 2 + + 159 2 total inverse-velocity tracklength - - 147 2 + + 159 2 total flux tracklength - - 147 2 + + 159 2 total prompt-nu-fission tracklength + + 159 2 + total + flux + analog + + + 159 2 5 + total + prompt-nu-fission + analog + + + 159 2 + total + flux + tracklength + - 147 2 + 159 65 2 total - flux - analog + delayed-nu-fission + tracklength - 147 2 5 + 159 65 52 total - prompt-nu-fission + delayed-nu-fission analog - 147 2 + 159 65 5 total - flux - tracklength + delayed-nu-fission + analog - 147 59 2 - total - delayed-nu-fission - tracklength - - - 147 59 46 - total - delayed-nu-fission - analog - - - 147 59 5 - total - delayed-nu-fission - analog - - - 147 2 + 159 2 total nu-fission tracklength - - 147 59 2 + + 159 65 2 total delayed-nu-fission tracklength - - 147 59 2 + + 159 65 2 total delayed-nu-fission tracklength - - 147 59 2 + + 159 65 2 total decay-rate tracklength - - 147 2 + + 159 2 total flux analog - - 147 59 2 5 + + 159 65 2 5 total delayed-nu-fission analog diff --git a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat index aa2c904b1..299da0713 100644 --- a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat @@ -323,7 +323,13 @@ 0.0 20000000.0 - + + 1 + + + 3 + + 1 2 3 4 5 6 @@ -357,9 +363,9 @@ analog - 1 5 + 1 5 6 total - scatter-1 + scatter analog @@ -381,9 +387,9 @@ analog - 1 5 + 1 5 6 total - nu-scatter-1 + nu-scatter analog @@ -483,9 +489,9 @@ analog - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog @@ -495,9 +501,9 @@ analog - 1 2 5 + 1 2 5 28 total - nu-scatter-P3 + nu-scatter analog @@ -543,9 +549,9 @@ tracklength - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog @@ -561,139 +567,133 @@ tracklength - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog 1 2 5 total - nu-scatter-0 + nu-scatter analog - 1 2 5 + 1 2 total - scatter-0 + nu-fission analog - 1 2 + 1 5 total nu-fission analog - 1 5 + 1 2 total - nu-fission + prompt-nu-fission analog - 1 2 + 1 5 total prompt-nu-fission analog - 1 5 - total - prompt-nu-fission - analog - - 1 2 total flux tracklength - + 1 2 total inverse-velocity tracklength - + 1 2 total flux tracklength + + 1 2 + total + prompt-nu-fission + tracklength + - 1 2 - total - prompt-nu-fission - tracklength - - 1 2 total flux analog - + 1 2 5 total prompt-nu-fission analog - + 1 2 total flux tracklength - - 1 59 2 + + 1 65 2 total delayed-nu-fission tracklength + + 1 65 2 + total + delayed-nu-fission + analog + - 1 59 2 + 1 65 5 total delayed-nu-fission analog - 1 59 5 - total - delayed-nu-fission - analog - - 1 2 total nu-fission tracklength + + 1 65 2 + total + delayed-nu-fission + tracklength + - 1 59 2 + 1 65 2 total delayed-nu-fission tracklength - 1 59 2 - total - delayed-nu-fission - tracklength - - - 1 59 2 + 1 65 2 total decay-rate tracklength - + 1 2 total flux analog - - 1 59 2 5 + + 1 65 2 5 total delayed-nu-fission analog diff --git a/tests/regression_tests/mgxs_library_mesh/results_true.dat b/tests/regression_tests/mgxs_library_mesh/results_true.dat index 4b9303fbf..27c1ffdc9 100644 --- a/tests/regression_tests/mgxs_library_mesh/results_true.dat +++ b/tests/regression_tests/mgxs_library_mesh/results_true.dat @@ -58,42 +58,42 @@ 2 1 2 1 1 total 0.628158 0.064356 1 2 1 1 1 total 0.640809 0.158369 3 2 2 1 1 total 0.645171 0.080467 - mesh 1 group in group out nuclide moment mean std. dev. - x y z -0 1 1 1 1 1 total P0 0.763779 0.070696 -1 1 1 1 1 1 total P1 0.288556 0.024446 -2 1 1 1 1 1 total P2 0.082441 0.011443 -3 1 1 1 1 1 total P3 -0.005627 0.012638 -8 1 2 1 1 1 total P0 0.628158 0.064356 -9 1 2 1 1 1 total P1 0.245583 0.022676 -10 1 2 1 1 1 total P2 0.086370 0.007833 -11 1 2 1 1 1 total P3 0.019590 0.005345 -4 2 1 1 1 1 total P0 0.640809 0.158369 -5 2 1 1 1 1 total P1 0.273553 0.066437 -6 2 1 1 1 1 total P2 0.108446 0.024435 -7 2 1 1 1 1 total P3 0.012229 0.003785 -12 2 2 1 1 1 total P0 0.645171 0.080467 -13 2 2 1 1 1 total P1 0.252215 0.032154 -14 2 2 1 1 1 total P2 0.089251 0.009734 -15 2 2 1 1 1 total P3 0.004748 0.002987 - mesh 1 group in group out nuclide moment mean std. dev. - x y z -0 1 1 1 1 1 total P0 0.763779 0.070696 -1 1 1 1 1 1 total P1 0.288556 0.024446 -2 1 1 1 1 1 total P2 0.082441 0.011443 -3 1 1 1 1 1 total P3 -0.005627 0.012638 -8 1 2 1 1 1 total P0 0.628158 0.064356 -9 1 2 1 1 1 total P1 0.245583 0.022676 -10 1 2 1 1 1 total P2 0.086370 0.007833 -11 1 2 1 1 1 total P3 0.019590 0.005345 -4 2 1 1 1 1 total P0 0.640809 0.158369 -5 2 1 1 1 1 total P1 0.273553 0.066437 -6 2 1 1 1 1 total P2 0.108446 0.024435 -7 2 1 1 1 1 total P3 0.012229 0.003785 -12 2 2 1 1 1 total P0 0.645171 0.080467 -13 2 2 1 1 1 total P1 0.252215 0.032154 -14 2 2 1 1 1 total P2 0.089251 0.009734 -15 2 2 1 1 1 total P3 0.004748 0.002987 + mesh 1 group in group out legendre nuclide mean std. dev. + x y z +0 1 1 1 1 1 P0 total 0.763779 0.070696 +1 1 1 1 1 1 P1 total 0.288556 0.024446 +2 1 1 1 1 1 P2 total 0.082441 0.011443 +3 1 1 1 1 1 P3 total -0.005627 0.012638 +8 1 2 1 1 1 P0 total 0.628158 0.064356 +9 1 2 1 1 1 P1 total 0.245583 0.022676 +10 1 2 1 1 1 P2 total 0.086370 0.007833 +11 1 2 1 1 1 P3 total 0.019590 0.005345 +4 2 1 1 1 1 P0 total 0.640809 0.158369 +5 2 1 1 1 1 P1 total 0.273553 0.066437 +6 2 1 1 1 1 P2 total 0.108446 0.024435 +7 2 1 1 1 1 P3 total 0.012229 0.003785 +12 2 2 1 1 1 P0 total 0.645171 0.080467 +13 2 2 1 1 1 P1 total 0.252215 0.032154 +14 2 2 1 1 1 P2 total 0.089251 0.009734 +15 2 2 1 1 1 P3 total 0.004748 0.002987 + mesh 1 group in group out legendre nuclide mean std. dev. + x y z +0 1 1 1 1 1 P0 total 0.763779 0.070696 +1 1 1 1 1 1 P1 total 0.288556 0.024446 +2 1 1 1 1 1 P2 total 0.082441 0.011443 +3 1 1 1 1 1 P3 total -0.005627 0.012638 +8 1 2 1 1 1 P0 total 0.628158 0.064356 +9 1 2 1 1 1 P1 total 0.245583 0.022676 +10 1 2 1 1 1 P2 total 0.086370 0.007833 +11 1 2 1 1 1 P3 total 0.019590 0.005345 +4 2 1 1 1 1 P0 total 0.640809 0.158369 +5 2 1 1 1 1 P1 total 0.273553 0.066437 +6 2 1 1 1 1 P2 total 0.108446 0.024435 +7 2 1 1 1 1 P3 total 0.012229 0.003785 +12 2 2 1 1 1 P0 total 0.645171 0.080467 +13 2 2 1 1 1 P1 total 0.252215 0.032154 +14 2 2 1 1 1 P2 total 0.089251 0.009734 +15 2 2 1 1 1 P3 total 0.004748 0.002987 mesh 1 group in group out nuclide mean std. dev. x y z 0 1 1 1 1 1 total 1.0 0.108337 @@ -112,42 +112,42 @@ 2 1 2 1 1 1 total 1.0 0.113128 1 2 1 1 1 1 total 1.0 0.238517 3 2 2 1 1 1 total 1.0 0.132597 - mesh 1 group in group out nuclide moment mean std. dev. - x y z -0 1 1 1 1 1 total P0 0.735256 0.113047 -1 1 1 1 1 1 total P1 0.277780 0.041434 -2 1 1 1 1 1 total P2 0.079362 0.014706 -3 1 1 1 1 1 total P3 -0.005417 0.012184 -8 1 2 1 1 1 total P0 0.624575 0.110512 -9 1 2 1 1 1 total P1 0.244182 0.041824 -10 1 2 1 1 1 total P2 0.085877 0.014634 -11 1 2 1 1 1 total P3 0.019478 0.006012 -4 2 1 1 1 1 total P0 0.633925 0.212349 -5 2 1 1 1 1 total P1 0.270615 0.089799 -6 2 1 1 1 1 total P2 0.107281 0.034246 -7 2 1 1 1 1 total P3 0.012098 0.004637 -12 2 2 1 1 1 total P0 0.655214 0.126119 -13 2 2 1 1 1 total P1 0.256141 0.049765 -14 2 2 1 1 1 total P2 0.090641 0.016563 -15 2 2 1 1 1 total P3 0.004822 0.003115 - mesh 1 group in group out nuclide moment mean std. dev. - x y z -0 1 1 1 1 1 total P0 0.735256 0.138292 -1 1 1 1 1 1 total P1 0.277780 0.051210 -2 1 1 1 1 1 total P2 0.079362 0.017035 -3 1 1 1 1 1 total P3 -0.005417 0.012198 -8 1 2 1 1 1 total P0 0.624575 0.131169 -9 1 2 1 1 1 total P1 0.244182 0.050123 -10 1 2 1 1 1 total P2 0.085877 0.017565 -11 1 2 1 1 1 total P3 0.019478 0.006403 -4 2 1 1 1 1 total P0 0.633925 0.260681 -5 2 1 1 1 1 total P1 0.270615 0.110590 -6 2 1 1 1 1 total P2 0.107281 0.042750 -7 2 1 1 1 1 total P3 0.012098 0.005462 -12 2 2 1 1 1 total P0 0.655214 0.153147 -13 2 2 1 1 1 total P1 0.256141 0.060250 -14 2 2 1 1 1 total P2 0.090641 0.020464 -15 2 2 1 1 1 total P3 0.004822 0.003180 + mesh 1 group in group out legendre nuclide mean std. dev. + x y z +0 1 1 1 1 1 P0 total 0.735256 0.113047 +1 1 1 1 1 1 P1 total 0.277780 0.041434 +2 1 1 1 1 1 P2 total 0.079362 0.014706 +3 1 1 1 1 1 P3 total -0.005417 0.012184 +8 1 2 1 1 1 P0 total 0.624575 0.110512 +9 1 2 1 1 1 P1 total 0.244182 0.041824 +10 1 2 1 1 1 P2 total 0.085877 0.014634 +11 1 2 1 1 1 P3 total 0.019478 0.006012 +4 2 1 1 1 1 P0 total 0.633925 0.212349 +5 2 1 1 1 1 P1 total 0.270615 0.089799 +6 2 1 1 1 1 P2 total 0.107281 0.034246 +7 2 1 1 1 1 P3 total 0.012098 0.004637 +12 2 2 1 1 1 P0 total 0.655214 0.126119 +13 2 2 1 1 1 P1 total 0.256141 0.049765 +14 2 2 1 1 1 P2 total 0.090641 0.016563 +15 2 2 1 1 1 P3 total 0.004822 0.003115 + mesh 1 group in group out legendre nuclide mean std. dev. + x y z +0 1 1 1 1 1 P0 total 0.735256 0.138292 +1 1 1 1 1 1 P1 total 0.277780 0.051210 +2 1 1 1 1 1 P2 total 0.079362 0.017035 +3 1 1 1 1 1 P3 total -0.005417 0.012198 +8 1 2 1 1 1 P0 total 0.624575 0.131169 +9 1 2 1 1 1 P1 total 0.244182 0.050123 +10 1 2 1 1 1 P2 total 0.085877 0.017565 +11 1 2 1 1 1 P3 total 0.019478 0.006403 +4 2 1 1 1 1 P0 total 0.633925 0.260681 +5 2 1 1 1 1 P1 total 0.270615 0.110590 +6 2 1 1 1 1 P2 total 0.107281 0.042750 +7 2 1 1 1 1 P3 total 0.012098 0.005462 +12 2 2 1 1 1 P0 total 0.655214 0.153147 +13 2 2 1 1 1 P1 total 0.256141 0.060250 +14 2 2 1 1 1 P2 total 0.090641 0.020464 +15 2 2 1 1 1 P3 total 0.004822 0.003180 mesh 1 group out nuclide mean std. dev. x y z 0 1 1 1 1 total 1.0 0.300047 diff --git a/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat b/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat index ef6c4c520..5aedd383d 100644 --- a/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_no_nuclides/inputs_true.dat @@ -59,16 +59,22 @@ 0.0 0.625 20000000.0 - + + 1 + + + 3 + + 0.0 20000000.0 - + 1 2 3 4 5 6 - + 2 - + 3 @@ -102,9 +108,9 @@ analog - 1 5 + 1 5 6 total - scatter-1 + scatter analog @@ -126,9 +132,9 @@ analog - 1 5 + 1 5 6 total - nu-scatter-1 + nu-scatter analog @@ -228,9 +234,9 @@ analog - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog @@ -240,9 +246,9 @@ analog - 1 2 5 + 1 2 5 28 total - nu-scatter-P3 + nu-scatter analog @@ -288,9 +294,9 @@ tracklength - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog @@ -306,883 +312,865 @@ tracklength - 1 2 5 + 1 2 5 28 total - scatter-P3 + scatter analog 1 2 5 total - nu-scatter-0 + nu-scatter analog - 1 2 5 + 1 52 total - scatter-0 + nu-fission analog - 1 46 + 1 5 total nu-fission analog - 1 5 + 1 52 total - nu-fission + prompt-nu-fission analog - 1 46 + 1 5 total prompt-nu-fission analog - 1 5 + 1 2 total - prompt-nu-fission - analog + flux + tracklength 1 2 total - flux + inverse-velocity tracklength 1 2 total - inverse-velocity + flux tracklength 1 2 total - flux + prompt-nu-fission tracklength - 1 2 - total - prompt-nu-fission - tracklength - - 1 2 total flux analog - + 1 2 5 total prompt-nu-fission analog - + 1 2 total flux tracklength - - 1 59 2 + + 1 65 2 total delayed-nu-fission tracklength + + 1 65 52 + total + delayed-nu-fission + analog + - 1 59 46 + 1 65 5 total delayed-nu-fission analog - 1 59 5 - total - delayed-nu-fission - analog - - 1 2 total nu-fission tracklength + + 1 65 2 + total + delayed-nu-fission + tracklength + - 1 59 2 + 1 65 2 total delayed-nu-fission tracklength - 1 59 2 - total - delayed-nu-fission - tracklength - - - 1 59 2 + 1 65 2 total decay-rate tracklength - + 1 2 total flux analog - - 1 59 2 5 + + 1 65 2 5 total delayed-nu-fission analog - - 74 2 + + 80 2 total flux tracklength + + 80 2 + total + total + tracklength + - 74 2 + 80 2 total - total + flux tracklength - 74 2 + 80 2 total - flux + total tracklength - 74 2 - total - total - tracklength - - - 74 2 + 80 2 total flux analog + + 80 5 6 + total + scatter + analog + - 74 5 - total - scatter-1 - analog - - - 74 2 + 80 2 total flux tracklength - - 74 2 + + 80 2 total total tracklength - - 74 2 + + 80 2 total flux analog + + 80 5 6 + total + nu-scatter + analog + - 74 5 - total - nu-scatter-1 - analog - - - 74 2 + 80 2 total flux tracklength + + 80 2 + total + absorption + tracklength + - 74 2 + 80 2 total - absorption + flux tracklength - 74 2 - total - flux - tracklength - - - 74 2 + 80 2 total absorption tracklength - - 74 2 + + 80 2 total fission tracklength + + 80 2 + total + flux + tracklength + - 74 2 - total - flux - tracklength - - - 74 2 + 80 2 total fission tracklength - - 74 2 + + 80 2 total flux tracklength - - 74 2 + + 80 2 total nu-fission tracklength - - 74 2 + + 80 2 total flux tracklength - - 74 2 + + 80 2 total kappa-fission tracklength - - 74 2 + + 80 2 total flux tracklength + + 80 2 + total + scatter + tracklength + - 74 2 - total - scatter - tracklength - - - 74 2 + 80 2 total flux analog + + 80 2 + total + nu-scatter + analog + - 74 2 + 80 2 total - nu-scatter + flux analog - 74 2 + 80 2 5 28 total - flux + scatter analog - 74 2 5 - total - scatter-P3 - analog - - - 74 2 + 80 2 total flux analog - - 74 2 5 - total - nu-scatter-P3 - analog - - - 74 2 5 + + 80 2 5 28 total nu-scatter analog - - 74 2 5 + + 80 2 5 + total + nu-scatter + analog + + + 80 2 5 total scatter analog + + 80 2 + total + flux + analog + - 74 2 + 80 2 5 total - flux + nu-fission analog - 74 2 5 + 80 2 5 total - nu-fission + scatter analog - 74 2 5 - total - scatter - analog - - - 74 2 + 80 2 total flux tracklength + + 80 2 + total + scatter + tracklength + - 74 2 + 80 2 5 28 total scatter - tracklength - - - 74 2 5 - total - scatter-P3 analog - - 74 2 + + 80 2 total flux tracklength - - 74 2 + + 80 2 total scatter tracklength - - 74 2 5 + + 80 2 5 28 total - scatter-P3 + scatter + analog + + + 80 2 5 + total + nu-scatter analog - 74 2 5 + 80 52 total - nu-scatter-0 + nu-fission analog - 74 2 5 + 80 5 total - scatter-0 + nu-fission analog - 74 46 + 80 52 total - nu-fission + prompt-nu-fission analog - 74 5 + 80 5 total - nu-fission + prompt-nu-fission analog - 74 46 + 80 2 total - prompt-nu-fission - analog + flux + tracklength - 74 5 + 80 2 total - prompt-nu-fission - analog + inverse-velocity + tracklength - 74 2 + 80 2 total flux tracklength - 74 2 + 80 2 total - inverse-velocity + prompt-nu-fission tracklength - 74 2 + 80 2 total flux - tracklength + analog - 74 2 + 80 2 5 total prompt-nu-fission - tracklength + analog - 74 2 + 80 2 total flux - analog + tracklength - 74 2 5 + 80 65 2 total - prompt-nu-fission - analog + delayed-nu-fission + tracklength - 74 2 + 80 65 52 total - flux - tracklength + delayed-nu-fission + analog - 74 59 2 + 80 65 5 total delayed-nu-fission - tracklength + analog - 74 59 46 - total - delayed-nu-fission - analog - - - 74 59 5 - total - delayed-nu-fission - analog - - - 74 2 + 80 2 total nu-fission tracklength + + 80 65 2 + total + delayed-nu-fission + tracklength + + + 80 65 2 + total + delayed-nu-fission + tracklength + - 74 59 2 - total - delayed-nu-fission - tracklength - - - 74 59 2 - total - delayed-nu-fission - tracklength - - - 74 59 2 + 80 65 2 total decay-rate tracklength - - 74 2 + + 80 2 total flux analog - - 74 59 2 5 + + 80 65 2 5 total delayed-nu-fission analog + + 159 2 + total + flux + tracklength + + + 159 2 + total + total + tracklength + - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total total tracklength - 147 2 + 159 2 total flux - tracklength + analog - 147 2 + 159 5 6 total - total - tracklength + scatter + analog - 147 2 - total - flux - analog - - - 147 5 - total - scatter-1 - analog - - - 147 2 + 159 2 total flux tracklength - - 147 2 + + 159 2 total total tracklength - - 147 2 + + 159 2 total flux analog - - 147 5 + + 159 5 6 total - nu-scatter-1 + nu-scatter analog + + 159 2 + total + flux + tracklength + + + 159 2 + total + absorption + tracklength + - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total absorption tracklength - 147 2 + 159 2 + total + fission + tracklength + + + 159 2 total flux tracklength - - 147 2 - total - absorption - tracklength - - 147 2 + 159 2 total fission tracklength - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total - fission + nu-fission tracklength - 147 2 + 159 2 total flux tracklength - 147 2 + 159 2 total - nu-fission + kappa-fission tracklength - 147 2 + 159 2 total flux tracklength - 147 2 - total - kappa-fission - tracklength - - - 147 2 - total - flux - tracklength - - - 147 2 + 159 2 total scatter tracklength + + 159 2 + total + flux + analog + + + 159 2 + total + nu-scatter + analog + - 147 2 + 159 2 total flux analog - 147 2 + 159 2 5 28 total - nu-scatter + scatter analog - 147 2 + 159 2 total flux analog - 147 2 5 - total - scatter-P3 - analog - - - 147 2 - total - flux - analog - - - 147 2 5 - total - nu-scatter-P3 - analog - - - 147 2 5 + 159 2 5 28 total nu-scatter analog - - 147 2 5 + + 159 2 5 + total + nu-scatter + analog + + + 159 2 5 total scatter analog + + 159 2 + total + flux + analog + + + 159 2 5 + total + nu-fission + analog + - 147 2 + 159 2 5 total - flux + scatter analog - 147 2 5 - total - nu-fission - analog - - - 147 2 5 - total - scatter - analog - - - 147 2 + 159 2 total flux tracklength - - 147 2 + + 159 2 total scatter tracklength + + 159 2 5 28 + total + scatter + analog + + + 159 2 + total + flux + tracklength + - 147 2 5 - total - scatter-P3 - analog - - - 147 2 - total - flux - tracklength - - - 147 2 + 159 2 total scatter tracklength - - 147 2 5 + + 159 2 5 28 total - scatter-P3 + scatter + analog + + + 159 2 5 + total + nu-scatter + analog + + + 159 52 + total + nu-fission analog - 147 2 5 + 159 5 total - nu-scatter-0 + nu-fission analog - 147 2 5 + 159 52 total - scatter-0 + prompt-nu-fission analog - 147 46 + 159 5 total - nu-fission + prompt-nu-fission analog - 147 5 - total - nu-fission - analog - - - 147 46 - total - prompt-nu-fission - analog - - - 147 5 - total - prompt-nu-fission - analog - - - 147 2 + 159 2 total flux tracklength - - 147 2 + + 159 2 total inverse-velocity tracklength - - 147 2 + + 159 2 total flux tracklength - - 147 2 + + 159 2 total prompt-nu-fission tracklength + + 159 2 + total + flux + analog + + + 159 2 5 + total + prompt-nu-fission + analog + + + 159 2 + total + flux + tracklength + - 147 2 + 159 65 2 total - flux - analog + delayed-nu-fission + tracklength - 147 2 5 + 159 65 52 total - prompt-nu-fission + delayed-nu-fission analog - 147 2 + 159 65 5 total - flux - tracklength + delayed-nu-fission + analog - 147 59 2 - total - delayed-nu-fission - tracklength - - - 147 59 46 - total - delayed-nu-fission - analog - - - 147 59 5 - total - delayed-nu-fission - analog - - - 147 2 + 159 2 total nu-fission tracklength - - 147 59 2 + + 159 65 2 total delayed-nu-fission tracklength - - 147 59 2 + + 159 65 2 total delayed-nu-fission tracklength - - 147 59 2 + + 159 65 2 total decay-rate tracklength - - 147 2 + + 159 2 total flux analog - - 147 59 2 5 + + 159 65 2 5 total delayed-nu-fission analog diff --git a/tests/regression_tests/mgxs_library_no_nuclides/results_true.dat b/tests/regression_tests/mgxs_library_no_nuclides/results_true.dat index 291c87dde..d4c87378e 100644 --- a/tests/regression_tests/mgxs_library_no_nuclides/results_true.dat +++ b/tests/regression_tests/mgxs_library_no_nuclides/results_true.dat @@ -28,40 +28,40 @@ material group in nuclide mean std. dev. 1 1 1 total 0.385188 0.026946 0 1 2 total 0.412389 0.015425 - material group in group out nuclide moment mean std. dev. -12 1 1 1 total P0 0.384199 0.027001 -13 1 1 1 total P1 0.051870 0.006983 -14 1 1 1 total P2 0.020069 0.002846 -15 1 1 1 total P3 0.009478 0.002234 -8 1 1 2 total P0 0.000989 0.000482 -9 1 1 2 total P1 -0.000207 0.000149 -10 1 1 2 total P2 -0.000103 0.000184 -11 1 1 2 total P3 0.000234 0.000128 -4 1 2 1 total P0 0.000925 0.000925 -5 1 2 1 total P1 -0.000768 0.000768 -6 1 2 1 total P2 0.000494 0.000494 -7 1 2 1 total P3 -0.000171 0.000172 -0 1 2 2 total P0 0.411465 0.015245 -1 1 2 2 total P1 0.016482 0.004502 -2 1 2 2 total P2 0.006371 0.010551 -3 1 2 2 total P3 -0.010499 0.010438 - material group in group out nuclide moment mean std. dev. -12 1 1 1 total P0 0.384199 0.027001 -13 1 1 1 total P1 0.051870 0.006983 -14 1 1 1 total P2 0.020069 0.002846 -15 1 1 1 total P3 0.009478 0.002234 -8 1 1 2 total P0 0.000989 0.000482 -9 1 1 2 total P1 -0.000207 0.000149 -10 1 1 2 total P2 -0.000103 0.000184 -11 1 1 2 total P3 0.000234 0.000128 -4 1 2 1 total P0 0.000925 0.000925 -5 1 2 1 total P1 -0.000768 0.000768 -6 1 2 1 total P2 0.000494 0.000494 -7 1 2 1 total P3 -0.000171 0.000172 -0 1 2 2 total P0 0.411465 0.015245 -1 1 2 2 total P1 0.016482 0.004502 -2 1 2 2 total P2 0.006371 0.010551 -3 1 2 2 total P3 -0.010499 0.010438 + material group in group out legendre nuclide mean std. dev. +12 1 1 1 P0 total 0.384199 0.027001 +13 1 1 1 P1 total 0.051870 0.006983 +14 1 1 1 P2 total 0.020069 0.002846 +15 1 1 1 P3 total 0.009478 0.002234 +8 1 1 2 P0 total 0.000989 0.000482 +9 1 1 2 P1 total -0.000207 0.000149 +10 1 1 2 P2 total -0.000103 0.000184 +11 1 1 2 P3 total 0.000234 0.000128 +4 1 2 1 P0 total 0.000925 0.000925 +5 1 2 1 P1 total -0.000768 0.000768 +6 1 2 1 P2 total 0.000494 0.000494 +7 1 2 1 P3 total -0.000171 0.000172 +0 1 2 2 P0 total 0.411465 0.015245 +1 1 2 2 P1 total 0.016482 0.004502 +2 1 2 2 P2 total 0.006371 0.010551 +3 1 2 2 P3 total -0.010499 0.010438 + material group in group out legendre nuclide mean std. dev. +12 1 1 1 P0 total 0.384199 0.027001 +13 1 1 1 P1 total 0.051870 0.006983 +14 1 1 1 P2 total 0.020069 0.002846 +15 1 1 1 P3 total 0.009478 0.002234 +8 1 1 2 P0 total 0.000989 0.000482 +9 1 1 2 P1 total -0.000207 0.000149 +10 1 1 2 P2 total -0.000103 0.000184 +11 1 1 2 P3 total 0.000234 0.000128 +4 1 2 1 P0 total 0.000925 0.000925 +5 1 2 1 P1 total -0.000768 0.000768 +6 1 2 1 P2 total 0.000494 0.000494 +7 1 2 1 P3 total -0.000171 0.000172 +0 1 2 2 P0 total 0.411465 0.015245 +1 1 2 2 P1 total 0.016482 0.004502 +2 1 2 2 P2 total 0.006371 0.010551 +3 1 2 2 P3 total -0.010499 0.010438 material group in group out nuclide mean std. dev. 3 1 1 1 total 1.0 0.078516 2 1 1 2 total 1.0 0.687184 @@ -77,40 +77,40 @@ 2 1 1 2 total 0.002567 0.001256 1 1 2 1 total 0.002242 0.002243 0 1 2 2 total 0.997758 0.041053 - material group in group out nuclide moment mean std. dev. -12 1 1 1 total P0 0.386423 0.036629 -13 1 1 1 total P1 0.052170 0.007767 -14 1 1 1 total P2 0.020185 0.003138 -15 1 1 1 total P3 0.009533 0.002327 -8 1 1 2 total P0 0.000995 0.000489 -9 1 1 2 total P1 -0.000208 0.000150 -10 1 1 2 total P2 -0.000104 0.000186 -11 1 1 2 total P3 0.000236 0.000130 -4 1 2 1 total P0 0.000887 0.000889 -5 1 2 1 total P1 -0.000737 0.000738 -6 1 2 1 total P2 0.000474 0.000475 -7 1 2 1 total P3 -0.000165 0.000165 -0 1 2 2 total P0 0.394772 0.029871 -1 1 2 2 total P1 0.015813 0.004443 -2 1 2 2 total P2 0.006113 0.010131 -3 1 2 2 total P3 -0.010073 0.010037 - material group in group out nuclide moment mean std. dev. -12 1 1 1 total P0 0.386423 0.047563 -13 1 1 1 total P1 0.052170 0.008781 -14 1 1 1 total P2 0.020185 0.003515 -15 1 1 1 total P3 0.009533 0.002444 -8 1 1 2 total P0 0.000995 0.000841 -9 1 1 2 total P1 -0.000208 0.000208 -10 1 1 2 total P2 -0.000104 0.000199 -11 1 1 2 total P3 0.000236 0.000208 -4 1 2 1 total P0 0.000887 0.001538 -5 1 2 1 total P1 -0.000737 0.001277 -6 1 2 1 total P2 0.000474 0.000821 -7 1 2 1 total P3 -0.000165 0.000285 -0 1 2 2 total P0 0.394772 0.033999 -1 1 2 2 total P1 0.015813 0.004491 -2 1 2 2 total P2 0.006113 0.010134 -3 1 2 2 total P3 -0.010073 0.010045 + material group in group out legendre nuclide mean std. dev. +12 1 1 1 P0 total 0.386423 0.036629 +13 1 1 1 P1 total 0.052170 0.007767 +14 1 1 1 P2 total 0.020185 0.003138 +15 1 1 1 P3 total 0.009533 0.002327 +8 1 1 2 P0 total 0.000995 0.000489 +9 1 1 2 P1 total -0.000208 0.000150 +10 1 1 2 P2 total -0.000104 0.000186 +11 1 1 2 P3 total 0.000236 0.000130 +4 1 2 1 P0 total 0.000887 0.000889 +5 1 2 1 P1 total -0.000737 0.000738 +6 1 2 1 P2 total 0.000474 0.000475 +7 1 2 1 P3 total -0.000165 0.000165 +0 1 2 2 P0 total 0.394772 0.029871 +1 1 2 2 P1 total 0.015813 0.004443 +2 1 2 2 P2 total 0.006113 0.010131 +3 1 2 2 P3 total -0.010073 0.010037 + material group in group out legendre nuclide mean std. dev. +12 1 1 1 P0 total 0.386423 0.047563 +13 1 1 1 P1 total 0.052170 0.008781 +14 1 1 1 P2 total 0.020185 0.003515 +15 1 1 1 P3 total 0.009533 0.002444 +8 1 1 2 P0 total 0.000995 0.000841 +9 1 1 2 P1 total -0.000208 0.000208 +10 1 1 2 P2 total -0.000104 0.000199 +11 1 1 2 P3 total 0.000236 0.000208 +4 1 2 1 P0 total 0.000887 0.001538 +5 1 2 1 P1 total -0.000737 0.001277 +6 1 2 1 P2 total 0.000474 0.000821 +7 1 2 1 P3 total -0.000165 0.000285 +0 1 2 2 P0 total 0.394772 0.033999 +1 1 2 2 P1 total 0.015813 0.004491 +2 1 2 2 P2 total 0.006113 0.010134 +3 1 2 2 P3 total -0.010073 0.010045 material group out nuclide mean std. dev. 1 1 1 total 1.0 0.046071 0 1 2 total 0.0 0.000000 @@ -235,40 +235,40 @@ material group in nuclide mean std. dev. 1 2 1 total 0.310121 0.033788 0 2 2 total 0.296264 0.043792 - material group in group out nuclide moment mean std. dev. -12 2 1 1 total P0 0.310121 0.033788 -13 2 1 1 total P1 0.038230 0.008484 -14 2 1 1 total P2 0.020745 0.004696 -15 2 1 1 total P3 0.007964 0.003732 -8 2 1 2 total P0 0.000000 0.000000 -9 2 1 2 total P1 0.000000 0.000000 -10 2 1 2 total P2 0.000000 0.000000 -11 2 1 2 total P3 0.000000 0.000000 -4 2 2 1 total P0 0.000000 0.000000 -5 2 2 1 total P1 0.000000 0.000000 -6 2 2 1 total P2 0.000000 0.000000 -7 2 2 1 total P3 0.000000 0.000000 -0 2 2 2 total P0 0.296264 0.043792 -1 2 2 2 total P1 -0.011214 0.016180 -2 2 2 2 total P2 0.008837 0.011504 -3 2 2 2 total P3 -0.003270 0.007329 - material group in group out nuclide moment mean std. dev. -12 2 1 1 total P0 0.310121 0.033788 -13 2 1 1 total P1 0.038230 0.008484 -14 2 1 1 total P2 0.020745 0.004696 -15 2 1 1 total P3 0.007964 0.003732 -8 2 1 2 total P0 0.000000 0.000000 -9 2 1 2 total P1 0.000000 0.000000 -10 2 1 2 total P2 0.000000 0.000000 -11 2 1 2 total P3 0.000000 0.000000 -4 2 2 1 total P0 0.000000 0.000000 -5 2 2 1 total P1 0.000000 0.000000 -6 2 2 1 total P2 0.000000 0.000000 -7 2 2 1 total P3 0.000000 0.000000 -0 2 2 2 total P0 0.296264 0.043792 -1 2 2 2 total P1 -0.011214 0.016180 -2 2 2 2 total P2 0.008837 0.011504 -3 2 2 2 total P3 -0.003270 0.007329 + material group in group out legendre nuclide mean std. dev. +12 2 1 1 P0 total 0.310121 0.033788 +13 2 1 1 P1 total 0.038230 0.008484 +14 2 1 1 P2 total 0.020745 0.004696 +15 2 1 1 P3 total 0.007964 0.003732 +8 2 1 2 P0 total 0.000000 0.000000 +9 2 1 2 P1 total 0.000000 0.000000 +10 2 1 2 P2 total 0.000000 0.000000 +11 2 1 2 P3 total 0.000000 0.000000 +4 2 2 1 P0 total 0.000000 0.000000 +5 2 2 1 P1 total 0.000000 0.000000 +6 2 2 1 P2 total 0.000000 0.000000 +7 2 2 1 P3 total 0.000000 0.000000 +0 2 2 2 P0 total 0.296264 0.043792 +1 2 2 2 P1 total -0.011214 0.016180 +2 2 2 2 P2 total 0.008837 0.011504 +3 2 2 2 P3 total -0.003270 0.007329 + material group in group out legendre nuclide mean std. dev. +12 2 1 1 P0 total 0.310121 0.033788 +13 2 1 1 P1 total 0.038230 0.008484 +14 2 1 1 P2 total 0.020745 0.004696 +15 2 1 1 P3 total 0.007964 0.003732 +8 2 1 2 P0 total 0.000000 0.000000 +9 2 1 2 P1 total 0.000000 0.000000 +10 2 1 2 P2 total 0.000000 0.000000 +11 2 1 2 P3 total 0.000000 0.000000 +4 2 2 1 P0 total 0.000000 0.000000 +5 2 2 1 P1 total 0.000000 0.000000 +6 2 2 1 P2 total 0.000000 0.000000 +7 2 2 1 P3 total 0.000000 0.000000 +0 2 2 2 P0 total 0.296264 0.043792 +1 2 2 2 P1 total -0.011214 0.016180 +2 2 2 2 P2 total 0.008837 0.011504 +3 2 2 2 P3 total -0.003270 0.007329 material group in group out nuclide mean std. dev. 3 2 1 1 total 1.0 0.108779 2 2 1 2 total 0.0 0.000000 @@ -284,40 +284,40 @@ 2 2 1 2 total 0.0 0.000000 1 2 2 1 total 0.0 0.000000 0 2 2 2 total 1.0 0.142427 - material group in group out nuclide moment mean std. dev. -12 2 1 1 total P0 0.312163 0.037253 -13 2 1 1 total P1 0.038481 0.008743 -14 2 1 1 total P2 0.020882 0.004835 -15 2 1 1 total P3 0.008017 0.003776 -8 2 1 2 total P0 0.000000 0.000000 -9 2 1 2 total P1 0.000000 0.000000 -10 2 1 2 total P2 0.000000 0.000000 -11 2 1 2 total P3 0.000000 0.000000 -4 2 2 1 total P0 0.000000 0.000000 -5 2 2 1 total P1 0.000000 0.000000 -6 2 2 1 total P2 0.000000 0.000000 -7 2 2 1 total P3 0.000000 0.000000 -0 2 2 2 total P0 0.295421 0.050236 -1 2 2 2 total P1 -0.011182 0.016162 -2 2 2 2 total P2 0.008811 0.011495 -3 2 2 2 total P3 -0.003261 0.007313 - material group in group out nuclide moment mean std. dev. -12 2 1 1 total P0 0.312163 0.050407 -13 2 1 1 total P1 0.038481 0.009693 -14 2 1 1 total P2 0.020882 0.005342 -15 2 1 1 total P3 0.008017 0.003876 -8 2 1 2 total P0 0.000000 0.000000 -9 2 1 2 total P1 0.000000 0.000000 -10 2 1 2 total P2 0.000000 0.000000 -11 2 1 2 total P3 0.000000 0.000000 -4 2 2 1 total P0 0.000000 0.000000 -5 2 2 1 total P1 0.000000 0.000000 -6 2 2 1 total P2 0.000000 0.000000 -7 2 2 1 total P3 0.000000 0.000000 -0 2 2 2 total P0 0.295421 0.065529 -1 2 2 2 total P1 -0.011182 0.016240 -2 2 2 2 total P2 0.008811 0.011563 -3 2 2 2 total P3 -0.003261 0.007328 + material group in group out legendre nuclide mean std. dev. +12 2 1 1 P0 total 0.312163 0.037253 +13 2 1 1 P1 total 0.038481 0.008743 +14 2 1 1 P2 total 0.020882 0.004835 +15 2 1 1 P3 total 0.008017 0.003776 +8 2 1 2 P0 total 0.000000 0.000000 +9 2 1 2 P1 total 0.000000 0.000000 +10 2 1 2 P2 total 0.000000 0.000000 +11 2 1 2 P3 total 0.000000 0.000000 +4 2 2 1 P0 total 0.000000 0.000000 +5 2 2 1 P1 total 0.000000 0.000000 +6 2 2 1 P2 total 0.000000 0.000000 +7 2 2 1 P3 total 0.000000 0.000000 +0 2 2 2 P0 total 0.295421 0.050236 +1 2 2 2 P1 total -0.011182 0.016162 +2 2 2 2 P2 total 0.008811 0.011495 +3 2 2 2 P3 total -0.003261 0.007313 + material group in group out legendre nuclide mean std. dev. +12 2 1 1 P0 total 0.312163 0.050407 +13 2 1 1 P1 total 0.038481 0.009693 +14 2 1 1 P2 total 0.020882 0.005342 +15 2 1 1 P3 total 0.008017 0.003876 +8 2 1 2 P0 total 0.000000 0.000000 +9 2 1 2 P1 total 0.000000 0.000000 +10 2 1 2 P2 total 0.000000 0.000000 +11 2 1 2 P3 total 0.000000 0.000000 +4 2 2 1 P0 total 0.000000 0.000000 +5 2 2 1 P1 total 0.000000 0.000000 +6 2 2 1 P2 total 0.000000 0.000000 +7 2 2 1 P3 total 0.000000 0.000000 +0 2 2 2 P0 total 0.295421 0.065529 +1 2 2 2 P1 total -0.011182 0.016240 +2 2 2 2 P2 total 0.008811 0.011563 +3 2 2 2 P3 total -0.003261 0.007328 material group out nuclide mean std. dev. 1 2 1 total 0.0 0.0 0 2 2 total 0.0 0.0 @@ -442,40 +442,40 @@ material group in nuclide mean std. dev. 1 3 1 total 0.671269 0.026186 0 3 2 total 2.035388 0.258060 - material group in group out nuclide moment mean std. dev. -12 3 1 1 total P0 0.639901 0.024709 -13 3 1 1 total P1 0.381167 0.016243 -14 3 1 1 total P2 0.152392 0.008156 -15 3 1 1 total P3 0.009148 0.003889 -8 3 1 2 total P0 0.031368 0.001728 -9 3 1 2 total P1 0.008758 0.000926 -10 3 1 2 total P2 -0.002568 0.001014 -11 3 1 2 total P3 -0.003785 0.000817 -4 3 2 1 total P0 0.000443 0.000445 -5 3 2 1 total P1 0.000400 0.000401 -6 3 2 1 total P2 0.000320 0.000321 -7 3 2 1 total P3 0.000214 0.000215 -0 3 2 2 total P0 2.034945 0.257800 -1 3 2 2 total P1 0.509940 0.051236 -2 3 2 2 total P2 0.111175 0.013020 -3 3 2 2 total P3 0.024988 0.008312 - material group in group out nuclide moment mean std. dev. -12 3 1 1 total P0 0.639901 0.024709 -13 3 1 1 total P1 0.381167 0.016243 -14 3 1 1 total P2 0.152392 0.008156 -15 3 1 1 total P3 0.009148 0.003889 -8 3 1 2 total P0 0.031368 0.001728 -9 3 1 2 total P1 0.008758 0.000926 -10 3 1 2 total P2 -0.002568 0.001014 -11 3 1 2 total P3 -0.003785 0.000817 -4 3 2 1 total P0 0.000443 0.000445 -5 3 2 1 total P1 0.000400 0.000401 -6 3 2 1 total P2 0.000320 0.000321 -7 3 2 1 total P3 0.000214 0.000215 -0 3 2 2 total P0 2.034945 0.257800 -1 3 2 2 total P1 0.509940 0.051236 -2 3 2 2 total P2 0.111175 0.013020 -3 3 2 2 total P3 0.024988 0.008312 + material group in group out legendre nuclide mean std. dev. +12 3 1 1 P0 total 0.639901 0.024709 +13 3 1 1 P1 total 0.381167 0.016243 +14 3 1 1 P2 total 0.152392 0.008156 +15 3 1 1 P3 total 0.009148 0.003889 +8 3 1 2 P0 total 0.031368 0.001728 +9 3 1 2 P1 total 0.008758 0.000926 +10 3 1 2 P2 total -0.002568 0.001014 +11 3 1 2 P3 total -0.003785 0.000817 +4 3 2 1 P0 total 0.000443 0.000445 +5 3 2 1 P1 total 0.000400 0.000401 +6 3 2 1 P2 total 0.000320 0.000321 +7 3 2 1 P3 total 0.000214 0.000215 +0 3 2 2 P0 total 2.034945 0.257800 +1 3 2 2 P1 total 0.509940 0.051236 +2 3 2 2 P2 total 0.111175 0.013020 +3 3 2 2 P3 total 0.024988 0.008312 + material group in group out legendre nuclide mean std. dev. +12 3 1 1 P0 total 0.639901 0.024709 +13 3 1 1 P1 total 0.381167 0.016243 +14 3 1 1 P2 total 0.152392 0.008156 +15 3 1 1 P3 total 0.009148 0.003889 +8 3 1 2 P0 total 0.031368 0.001728 +9 3 1 2 P1 total 0.008758 0.000926 +10 3 1 2 P2 total -0.002568 0.001014 +11 3 1 2 P3 total -0.003785 0.000817 +4 3 2 1 P0 total 0.000443 0.000445 +5 3 2 1 P1 total 0.000400 0.000401 +6 3 2 1 P2 total 0.000320 0.000321 +7 3 2 1 P3 total 0.000214 0.000215 +0 3 2 2 P0 total 2.034945 0.257800 +1 3 2 2 P1 total 0.509940 0.051236 +2 3 2 2 P2 total 0.111175 0.013020 +3 3 2 2 P3 total 0.024988 0.008312 material group in group out nuclide mean std. dev. 3 3 1 1 total 1.0 0.038609 2 3 1 2 total 1.0 0.067667 @@ -491,40 +491,40 @@ 2 3 1 2 total 0.046729 0.002547 1 3 2 1 total 0.000218 0.000219 0 3 2 2 total 0.999782 0.135885 - material group in group out nuclide moment mean std. dev. -12 3 1 1 total P0 0.632859 0.038142 -13 3 1 1 total P1 0.376973 0.023715 -14 3 1 1 total P2 0.150715 0.010664 -15 3 1 1 total P3 0.009047 0.003868 -8 3 1 2 total P0 0.031023 0.002232 -9 3 1 2 total P1 0.008661 0.000999 -10 3 1 2 total P2 -0.002540 0.001010 -11 3 1 2 total P3 -0.003743 0.000826 -4 3 2 1 total P0 0.000440 0.000445 -5 3 2 1 total P1 0.000397 0.000401 -6 3 2 1 total P2 0.000317 0.000321 -7 3 2 1 total P3 0.000212 0.000215 -0 3 2 2 total P0 2.020256 0.352194 -1 3 2 2 total P1 0.506260 0.079140 -2 3 2 2 total P2 0.110372 0.018488 -3 3 2 2 total P3 0.024808 0.008771 - material group in group out nuclide moment mean std. dev. -12 3 1 1 total P0 0.632859 0.045297 -13 3 1 1 total P1 0.376973 0.027825 -14 3 1 1 total P2 0.150715 0.012148 -15 3 1 1 total P3 0.009047 0.003884 -8 3 1 2 total P0 0.031023 0.003064 -9 3 1 2 total P1 0.008661 0.001159 -10 3 1 2 total P2 -0.002540 0.001024 -11 3 1 2 total P3 -0.003743 0.000864 -4 3 2 1 total P0 0.000440 0.000765 -5 3 2 1 total P1 0.000397 0.000690 -6 3 2 1 total P2 0.000317 0.000551 -7 3 2 1 total P3 0.000212 0.000369 -0 3 2 2 total P0 2.020256 0.446601 -1 3 2 2 total P1 0.506260 0.104875 -2 3 2 2 total P2 0.110372 0.023809 -3 3 2 2 total P3 0.024808 0.009397 + material group in group out legendre nuclide mean std. dev. +12 3 1 1 P0 total 0.632859 0.038142 +13 3 1 1 P1 total 0.376973 0.023715 +14 3 1 1 P2 total 0.150715 0.010664 +15 3 1 1 P3 total 0.009047 0.003868 +8 3 1 2 P0 total 0.031023 0.002232 +9 3 1 2 P1 total 0.008661 0.000999 +10 3 1 2 P2 total -0.002540 0.001010 +11 3 1 2 P3 total -0.003743 0.000826 +4 3 2 1 P0 total 0.000440 0.000445 +5 3 2 1 P1 total 0.000397 0.000401 +6 3 2 1 P2 total 0.000317 0.000321 +7 3 2 1 P3 total 0.000212 0.000215 +0 3 2 2 P0 total 2.020256 0.352194 +1 3 2 2 P1 total 0.506260 0.079140 +2 3 2 2 P2 total 0.110372 0.018488 +3 3 2 2 P3 total 0.024808 0.008771 + material group in group out legendre nuclide mean std. dev. +12 3 1 1 P0 total 0.632859 0.045297 +13 3 1 1 P1 total 0.376973 0.027825 +14 3 1 1 P2 total 0.150715 0.012148 +15 3 1 1 P3 total 0.009047 0.003884 +8 3 1 2 P0 total 0.031023 0.003064 +9 3 1 2 P1 total 0.008661 0.001159 +10 3 1 2 P2 total -0.002540 0.001024 +11 3 1 2 P3 total -0.003743 0.000864 +4 3 2 1 P0 total 0.000440 0.000765 +5 3 2 1 P1 total 0.000397 0.000690 +6 3 2 1 P2 total 0.000317 0.000551 +7 3 2 1 P3 total 0.000212 0.000369 +0 3 2 2 P0 total 2.020256 0.446601 +1 3 2 2 P1 total 0.506260 0.104875 +2 3 2 2 P2 total 0.110372 0.023809 +3 3 2 2 P3 total 0.024808 0.009397 material group out nuclide mean std. dev. 1 3 1 total 0.0 0.0 0 3 2 total 0.0 0.0 diff --git a/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat b/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat index b720bfcbb..f859d5f3a 100644 --- a/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_nuclides/inputs_true.dat @@ -59,13 +59,19 @@ 0.0 0.625 20000000.0 - + + 1 + + + 3 + + 0.0 20000000.0 - + 2 - + 3 @@ -99,9 +105,9 @@ analog - 1 5 + 1 5 6 U234 U235 U238 O16 - scatter-1 + scatter analog @@ -123,9 +129,9 @@ analog - 1 5 + 1 5 6 U234 U235 U238 O16 - nu-scatter-1 + nu-scatter analog @@ -225,9 +231,9 @@ analog - 1 2 5 + 1 2 5 28 U234 U235 U238 O16 - scatter-P3 + scatter analog @@ -237,9 +243,9 @@ analog - 1 2 5 + 1 2 5 28 U234 U235 U238 O16 - nu-scatter-P3 + nu-scatter analog @@ -285,9 +291,9 @@ tracklength - 1 2 5 + 1 2 5 28 U234 U235 U238 O16 - scatter-P3 + scatter analog @@ -303,703 +309,685 @@ tracklength - 1 2 5 + 1 2 5 28 U234 U235 U238 O16 - scatter-P3 + scatter analog 1 2 5 U234 U235 U238 O16 - nu-scatter-0 + nu-scatter analog - 1 2 5 + 1 52 U234 U235 U238 O16 - scatter-0 + nu-fission analog - 1 46 + 1 5 U234 U235 U238 O16 nu-fission analog - 1 5 + 1 52 U234 U235 U238 O16 - nu-fission + prompt-nu-fission analog - 1 46 + 1 5 U234 U235 U238 O16 prompt-nu-fission analog - 1 5 - U234 U235 U238 O16 - prompt-nu-fission - analog - - 1 2 total flux tracklength - + 1 2 U234 U235 U238 O16 inverse-velocity tracklength - + 1 2 total flux tracklength - + 1 2 U234 U235 U238 O16 prompt-nu-fission tracklength - + 1 2 total flux analog - + 1 2 5 U234 U235 U238 O16 prompt-nu-fission analog - - 57 2 + + 63 2 total flux tracklength + + 63 2 + Zr90 Zr91 Zr92 Zr94 Zr96 + total + tracklength + - 57 2 - Zr90 Zr91 Zr92 Zr94 Zr96 - total + 63 2 + total + flux tracklength - 57 2 - total - flux + 63 2 + Zr90 Zr91 Zr92 Zr94 Zr96 + total tracklength - 57 2 - Zr90 Zr91 Zr92 Zr94 Zr96 - total - tracklength - - - 57 2 + 63 2 total flux analog + + 63 5 6 + Zr90 Zr91 Zr92 Zr94 Zr96 + scatter + analog + - 57 5 - Zr90 Zr91 Zr92 Zr94 Zr96 - scatter-1 - analog - - - 57 2 + 63 2 total flux tracklength - - 57 2 + + 63 2 Zr90 Zr91 Zr92 Zr94 Zr96 total tracklength - - 57 2 + + 63 2 total flux analog + + 63 5 6 + Zr90 Zr91 Zr92 Zr94 Zr96 + nu-scatter + analog + - 57 5 - Zr90 Zr91 Zr92 Zr94 Zr96 - nu-scatter-1 - analog - - - 57 2 + 63 2 total flux tracklength + + 63 2 + Zr90 Zr91 Zr92 Zr94 Zr96 + absorption + tracklength + - 57 2 - Zr90 Zr91 Zr92 Zr94 Zr96 - absorption + 63 2 + total + flux tracklength - 57 2 - total - flux - tracklength - - - 57 2 + 63 2 Zr90 Zr91 Zr92 Zr94 Zr96 absorption tracklength - - 57 2 + + 63 2 Zr90 Zr91 Zr92 Zr94 Zr96 fission tracklength + + 63 2 + total + flux + tracklength + - 57 2 - total - flux - tracklength - - - 57 2 + 63 2 Zr90 Zr91 Zr92 Zr94 Zr96 fission tracklength - - 57 2 + + 63 2 total flux tracklength - - 57 2 + + 63 2 Zr90 Zr91 Zr92 Zr94 Zr96 nu-fission tracklength - - 57 2 + + 63 2 total flux tracklength - - 57 2 + + 63 2 Zr90 Zr91 Zr92 Zr94 Zr96 kappa-fission tracklength - - 57 2 + + 63 2 total flux tracklength + + 63 2 + Zr90 Zr91 Zr92 Zr94 Zr96 + scatter + tracklength + - 57 2 - Zr90 Zr91 Zr92 Zr94 Zr96 - scatter - tracklength - - - 57 2 + 63 2 total flux analog + + 63 2 + Zr90 Zr91 Zr92 Zr94 Zr96 + nu-scatter + analog + - 57 2 - Zr90 Zr91 Zr92 Zr94 Zr96 - nu-scatter + 63 2 + total + flux analog - 57 2 - total - flux + 63 2 5 28 + Zr90 Zr91 Zr92 Zr94 Zr96 + scatter analog - 57 2 5 - Zr90 Zr91 Zr92 Zr94 Zr96 - scatter-P3 - analog - - - 57 2 + 63 2 total flux analog - - 57 2 5 - Zr90 Zr91 Zr92 Zr94 Zr96 - nu-scatter-P3 - analog - - - 57 2 5 + + 63 2 5 28 Zr90 Zr91 Zr92 Zr94 Zr96 nu-scatter analog - - 57 2 5 + + 63 2 5 + Zr90 Zr91 Zr92 Zr94 Zr96 + nu-scatter + analog + + + 63 2 5 Zr90 Zr91 Zr92 Zr94 Zr96 scatter analog + + 63 2 + total + flux + analog + - 57 2 - total - flux + 63 2 5 + Zr90 Zr91 Zr92 Zr94 Zr96 + nu-fission analog - 57 2 5 + 63 2 5 Zr90 Zr91 Zr92 Zr94 Zr96 - nu-fission + scatter analog - 57 2 5 - Zr90 Zr91 Zr92 Zr94 Zr96 - scatter - analog - - - 57 2 + 63 2 total flux tracklength + + 63 2 + Zr90 Zr91 Zr92 Zr94 Zr96 + scatter + tracklength + - 57 2 + 63 2 5 28 Zr90 Zr91 Zr92 Zr94 Zr96 scatter - tracklength - - - 57 2 5 - Zr90 Zr91 Zr92 Zr94 Zr96 - scatter-P3 analog - - 57 2 + + 63 2 total flux tracklength - - 57 2 + + 63 2 Zr90 Zr91 Zr92 Zr94 Zr96 scatter tracklength - - 57 2 5 + + 63 2 5 28 Zr90 Zr91 Zr92 Zr94 Zr96 - scatter-P3 + scatter + analog + + + 63 2 5 + Zr90 Zr91 Zr92 Zr94 Zr96 + nu-scatter analog - 57 2 5 + 63 52 Zr90 Zr91 Zr92 Zr94 Zr96 - nu-scatter-0 + nu-fission analog - 57 2 5 + 63 5 Zr90 Zr91 Zr92 Zr94 Zr96 - scatter-0 + nu-fission analog - 57 46 + 63 52 Zr90 Zr91 Zr92 Zr94 Zr96 - nu-fission + prompt-nu-fission analog - 57 5 + 63 5 Zr90 Zr91 Zr92 Zr94 Zr96 - nu-fission + prompt-nu-fission analog - 57 46 - Zr90 Zr91 Zr92 Zr94 Zr96 - prompt-nu-fission - analog + 63 2 + total + flux + tracklength - 57 5 + 63 2 Zr90 Zr91 Zr92 Zr94 Zr96 - prompt-nu-fission - analog + inverse-velocity + tracklength - 57 2 + 63 2 total flux tracklength - 57 2 + 63 2 Zr90 Zr91 Zr92 Zr94 Zr96 - inverse-velocity + prompt-nu-fission tracklength - 57 2 + 63 2 total flux - tracklength + analog - 57 2 + 63 2 5 Zr90 Zr91 Zr92 Zr94 Zr96 prompt-nu-fission - tracklength + analog - 57 2 + 125 2 total flux - analog + tracklength - 57 2 5 - Zr90 Zr91 Zr92 Zr94 Zr96 - prompt-nu-fission - analog + 125 2 + H1 O16 B10 B11 + total + tracklength - 113 2 + 125 2 total flux tracklength - 113 2 + 125 2 H1 O16 B10 B11 total tracklength - 113 2 + 125 2 total flux - tracklength + analog - 113 2 + 125 5 6 H1 O16 B10 B11 - total - tracklength + scatter + analog - 113 2 - total - flux - analog - - - 113 5 - H1 O16 B10 B11 - scatter-1 - analog - - - 113 2 + 125 2 total flux tracklength - - 113 2 + + 125 2 H1 O16 B10 B11 total tracklength - - 113 2 + + 125 2 total flux analog - - 113 5 + + 125 5 6 H1 O16 B10 B11 - nu-scatter-1 + nu-scatter analog + + 125 2 + total + flux + tracklength + + + 125 2 + H1 O16 B10 B11 + absorption + tracklength + - 113 2 + 125 2 total flux tracklength - 113 2 + 125 2 H1 O16 B10 B11 absorption tracklength - 113 2 + 125 2 + H1 O16 B10 B11 + fission + tracklength + + + 125 2 total flux tracklength - - 113 2 - H1 O16 B10 B11 - absorption - tracklength - - 113 2 + 125 2 H1 O16 B10 B11 fission tracklength - 113 2 + 125 2 total flux tracklength - 113 2 + 125 2 H1 O16 B10 B11 - fission + nu-fission tracklength - 113 2 + 125 2 total flux tracklength - 113 2 + 125 2 H1 O16 B10 B11 - nu-fission + kappa-fission tracklength - 113 2 + 125 2 total flux tracklength - 113 2 - H1 O16 B10 B11 - kappa-fission - tracklength - - - 113 2 - total - flux - tracklength - - - 113 2 + 125 2 H1 O16 B10 B11 scatter tracklength + + 125 2 + total + flux + analog + + + 125 2 + H1 O16 B10 B11 + nu-scatter + analog + - 113 2 + 125 2 total flux analog - 113 2 + 125 2 5 28 H1 O16 B10 B11 - nu-scatter + scatter analog - 113 2 + 125 2 total flux analog - 113 2 5 - H1 O16 B10 B11 - scatter-P3 - analog - - - 113 2 - total - flux - analog - - - 113 2 5 - H1 O16 B10 B11 - nu-scatter-P3 - analog - - - 113 2 5 + 125 2 5 28 H1 O16 B10 B11 nu-scatter analog - - 113 2 5 + + 125 2 5 + H1 O16 B10 B11 + nu-scatter + analog + + + 125 2 5 H1 O16 B10 B11 scatter analog + + 125 2 + total + flux + analog + + + 125 2 5 + H1 O16 B10 B11 + nu-fission + analog + - 113 2 - total - flux + 125 2 5 + H1 O16 B10 B11 + scatter analog - 113 2 5 - H1 O16 B10 B11 - nu-fission - analog - - - 113 2 5 - H1 O16 B10 B11 - scatter - analog - - - 113 2 + 125 2 total flux tracklength - - 113 2 + + 125 2 H1 O16 B10 B11 scatter tracklength + + 125 2 5 28 + H1 O16 B10 B11 + scatter + analog + + + 125 2 + total + flux + tracklength + - 113 2 5 - H1 O16 B10 B11 - scatter-P3 - analog - - - 113 2 - total - flux - tracklength - - - 113 2 + 125 2 H1 O16 B10 B11 scatter tracklength - - 113 2 5 + + 125 2 5 28 H1 O16 B10 B11 - scatter-P3 + scatter + analog + + + 125 2 5 + H1 O16 B10 B11 + nu-scatter + analog + + + 125 52 + H1 O16 B10 B11 + nu-fission analog - 113 2 5 + 125 5 H1 O16 B10 B11 - nu-scatter-0 + nu-fission analog - 113 2 5 + 125 52 H1 O16 B10 B11 - scatter-0 + prompt-nu-fission analog - 113 46 + 125 5 H1 O16 B10 B11 - nu-fission + prompt-nu-fission analog - 113 5 - H1 O16 B10 B11 - nu-fission - analog - - - 113 46 - H1 O16 B10 B11 - prompt-nu-fission - analog - - - 113 5 - H1 O16 B10 B11 - prompt-nu-fission - analog - - - 113 2 + 125 2 total flux tracklength - - 113 2 + + 125 2 H1 O16 B10 B11 inverse-velocity tracklength - - 113 2 + + 125 2 total flux tracklength - - 113 2 + + 125 2 H1 O16 B10 B11 prompt-nu-fission tracklength - - 113 2 + + 125 2 total flux analog - - 113 2 5 + + 125 2 5 H1 O16 B10 B11 prompt-nu-fission analog diff --git a/tests/regression_tests/mgxs_library_nuclides/results_true.dat b/tests/regression_tests/mgxs_library_nuclides/results_true.dat index a0315ebbc..577694cfa 100644 --- a/tests/regression_tests/mgxs_library_nuclides/results_true.dat +++ b/tests/regression_tests/mgxs_library_nuclides/results_true.dat @@ -1 +1 @@ -174d1593a15de41e2aba88cc4c48fc3a400314b400571a0328dfdf7482df111b3ac9701dbd196d06668b49d3acaa67d766702db0942c03140e9e004942f7bdfd \ No newline at end of file +0edd3036c0b5b1eebad90dc8fba25006f14745ceb51dd109e70d0c610d66071f32128facdbc6d4e5077534fe96fff9a8e0ddeefb4a18d6c578f8e805bab7aa22 \ No newline at end of file diff --git a/tests/regression_tests/sourcepoint_restart/results_true.dat b/tests/regression_tests/sourcepoint_restart/results_true.dat index f726da973..e210748bf 100644 --- a/tests/regression_tests/sourcepoint_restart/results_true.dat +++ b/tests/regression_tests/sourcepoint_restart/results_true.dat @@ -3,62 +3,26 @@ k-combined: tally 1: 1.100000E-02 3.700000E-05 -1.307570E-03 -2.851451E-06 -1.564980E-03 -2.368303E-06 -3.138136E-03 -5.769887E-06 7.719234E-03 2.632582E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.816168E-04 7.772482E-07 1.000000E-03 1.000000E-06 -8.782909E-04 -7.713950E-07 -6.570925E-04 -4.317705E-07 -3.763366E-04 -1.416293E-07 0.000000E+00 0.000000E+00 2.100000E-02 1.150000E-04 -5.280651E-03 -1.222273E-05 -5.235520E-03 -1.202448E-05 -5.064093E-03 -1.787892E-05 1.071093E-02 2.748612E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.954045E-04 2.673851E-07 0.000000E+00 @@ -69,76 +33,28 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.100000E-02 2.130000E-04 -1.472240E-02 -5.500913E-05 -1.077445E-02 -2.987369E-05 -6.729425E-03 -1.249089E-05 1.363637E-02 4.345510E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.182717E-03 5.268980E-07 2.000000E-03 2.000000E-06 --1.367978E-03 -9.381191E-07 -4.071787E-04 -9.316064E-08 -4.394728E-04 -1.064342E-07 2.110880E-03 1.737594E-06 1.000000E-03 1.000000E-06 -9.347357E-04 -8.737309E-07 -8.105963E-04 -6.570664E-07 -6.396651E-04 -4.091714E-07 2.938723E-04 8.636091E-08 2.300000E-02 1.330000E-04 -1.081756E-02 -3.675127E-05 -2.530156E-03 -6.960955E-06 --1.930911E-03 -4.910249E-06 1.162826E-02 3.490280E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.957100E-04 4.402864E-07 0.000000E+00 @@ -149,36 +65,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.000000E-02 3.400000E-05 -4.086838E-03 -5.900874E-06 -1.812330E-03 -3.716159E-06 -2.138941E-03 -3.006748E-06 5.414128E-03 8.079333E-06 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 @@ -189,36 +81,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.700000E-02 7.100000E-05 -5.492922E-03 -1.013834E-05 -5.773309E-04 -3.561549E-06 -2.550048E-03 -3.841061E-06 8.048522E-03 1.583843E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.915762E-04 1.749885E-07 0.000000E+00 @@ -229,156 +97,60 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.700000E-02 1.670000E-04 -1.789444E-02 -8.260005E-05 -1.049872E-02 -2.774537E-05 -5.665111E-03 -8.560197E-06 1.100708E-02 2.835295E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 1.000000E-03 1.000000E-06 --2.856031E-04 -8.156913E-08 --3.776463E-04 -1.426167E-07 -3.701637E-04 -1.370211E-07 1.203064E-03 7.240967E-07 1.000000E-03 1.000000E-06 -9.705482E-04 -9.419638E-07 -9.129457E-04 -8.334699E-07 -8.297310E-04 -6.884535E-07 0.000000E+00 0.000000E+00 4.400000E-02 4.320000E-04 -1.141886E-02 -4.208707E-05 -9.213446E-03 -2.259305E-05 -9.177440E-03 -2.088782E-05 2.116869E-02 9.629046E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.501009E-03 6.405021E-07 1.000000E-03 1.000000E-06 -5.882614E-04 -3.460515E-07 -1.907719E-05 -3.639390E-10 --3.734703E-04 -1.394801E-07 1.472277E-03 9.506217E-07 2.000000E-03 2.000000E-06 -1.830192E-03 -1.679505E-06 -1.519257E-03 -1.189525E-06 -1.118506E-03 -7.332971E-07 2.977039E-04 8.862762E-08 2.000000E-02 1.080000E-04 -8.640372E-03 -1.765553E-05 -5.688468E-03 -1.038555E-05 -2.447898E-03 -4.466055E-06 8.949667E-03 1.935056E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.977039E-04 8.862762E-08 1.000000E-03 1.000000E-06 --3.805163E-04 -1.447926E-07 --2.828111E-04 -7.998210E-08 -4.330345E-04 -1.875189E-07 2.121142E-03 1.958355E-06 1.000000E-03 1.000000E-06 -9.260022E-04 -8.574800E-07 -7.862200E-04 -6.181419E-07 -5.960676E-04 -3.552966E-07 2.938723E-04 8.636091E-08 1.000000E-02 3.400000E-05 -4.840884E-03 -1.080853E-05 -3.402096E-03 -4.113972E-06 -1.374077E-03 -2.333511E-06 4.754696E-03 7.172309E-06 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 @@ -389,316 +161,124 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.300000E-02 3.700000E-05 -3.121167E-03 -2.465327E-06 -4.474559E-04 -1.522316E-06 -9.193982E-04 -3.247292E-06 5.365659E-03 6.567979E-06 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.056043E-04 1.834316E-07 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.977039E-04 8.862762E-08 1.000000E-03 1.000000E-06 -2.390376E-04 -5.713899E-08 --4.142915E-04 -1.716375E-07 --3.244105E-04 -1.052422E-07 0.000000E+00 0.000000E+00 2.900000E-02 2.230000E-04 -6.260565E-03 -1.544092E-05 -7.061757E-03 -2.562385E-05 -3.982541E-03 -7.962565E-06 1.486928E-02 5.763901E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.915762E-04 1.749885E-07 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.954078E-04 3.545105E-07 1.000000E-03 1.000000E-06 -9.938157E-04 -9.876697E-07 -9.815046E-04 -9.633512E-07 -9.631807E-04 -9.277170E-07 0.000000E+00 0.000000E+00 2.900000E-02 1.990000E-04 -1.237546E-02 -3.198261E-05 -8.287792E-03 -2.747313E-05 -3.254969E-03 -8.653294E-06 1.189702E-02 3.303340E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 9.033082E-04 2.720592E-07 3.000000E-03 3.000000E-06 -3.649225E-04 -1.756075E-06 -1.134112E-03 -8.940601E-07 --9.392028E-04 -5.872882E-07 1.484187E-03 9.721092E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.500000E-02 1.390000E-04 -1.511834E-02 -5.459835E-05 -7.753447E-03 -2.291820E-05 -6.142979E-03 -1.359405E-05 1.043467E-02 2.526768E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.912057E-04 1.747704E-07 1.000000E-03 1.000000E-06 --1.098110E-04 -1.205846E-08 --4.819123E-04 -2.322395E-07 -1.614061E-04 -2.605194E-08 8.813114E-04 4.316251E-07 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 1.900000E-02 1.030000E-04 -1.035735E-02 -3.119381E-05 -6.483551E-03 -1.164471E-05 -3.924334E-03 -6.047577E-06 9.748672E-03 2.956633E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 1.000000E-03 1.000000E-06 -8.623139E-04 -7.435852E-07 -6.153778E-04 -3.786899E-07 -3.095388E-04 -9.581428E-08 0.000000E+00 0.000000E+00 7.000000E-03 1.500000E-05 -3.445754E-03 -3.819507E-06 -2.124056E-03 -1.976201E-06 -1.542203E-03 -1.531669E-06 4.135720E-03 4.532611E-06 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.874391E-04 1.725424E-07 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 1.000000E-03 1.000000E-06 -9.451745E-04 -8.933548E-07 -8.400323E-04 -7.056542E-07 -6.931788E-04 -4.804969E-07 0.000000E+00 0.000000E+00 2.600000E-02 1.560000E-04 -6.314886E-03 -1.168542E-05 -6.676680E-04 -9.930437E-06 --2.103247E-04 -2.815945E-07 1.249410E-02 3.501589E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.159309E-04 3.793709E-07 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 1.000000E-03 1.000000E-06 -9.514029E-04 -9.051675E-07 -8.577513E-04 -7.357373E-07 -7.258432E-04 -5.268483E-07 0.000000E+00 0.000000E+00 2.900000E-02 1.950000E-04 -8.928267E-03 -2.594547E-05 -4.752762E-03 -1.522378E-05 -5.376579E-03 -1.088620E-05 1.461148E-02 4.367385E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.912057E-04 1.747704E-07 0.000000E+00 @@ -709,66 +289,24 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.900000E-02 9.700000E-05 -1.046968E-02 -3.828445E-05 -6.704767E-03 -2.668260E-05 -2.659611E-03 -1.143518E-05 1.099391E-02 2.847330E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.655539E-03 2.523801E-06 3.000000E-03 5.000000E-06 -2.960960E-03 -4.866206E-06 -2.884206E-03 -4.608867E-06 -2.772329E-03 -4.247272E-06 2.976389E-04 8.858890E-08 6.000000E-03 8.000000E-06 -2.104495E-03 -2.749678E-06 -8.451272E-04 -9.362821E-07 -5.355137E-04 -3.419837E-07 2.683856E-03 1.512640E-06 0.000000E+00 @@ -777,128 +315,50 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 1.000000E-03 1.000000E-06 -9.374310E-04 -8.787769E-07 -8.181653E-04 -6.693944E-07 -6.533352E-04 -4.268468E-07 2.976389E-04 8.858890E-08 1.200000E-02 4.600000E-05 -9.302157E-04 -3.853850E-06 -1.874541E-03 -3.092623E-06 --1.511552E-03 -4.053466E-06 5.941985E-03 9.853870E-06 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.186548E-03 5.291647E-07 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 1.000000E-03 1.000000E-06 -9.893707E-04 -9.788543E-07 -9.682814E-04 -9.375689E-07 -9.370683E-04 -8.780970E-07 0.000000E+00 0.000000E+00 2.300000E-02 1.230000E-04 -5.099566E-03 -9.143616E-06 -4.738751E-03 -7.819254E-06 -3.929250E-03 -6.884420E-06 1.015650E-02 2.236950E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.977039E-04 8.862762E-08 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.912707E-04 1.748091E-07 2.000000E-03 2.000000E-06 -1.762035E-03 -1.552542E-06 -1.328812E-03 -8.839688E-07 -7.771806E-04 -3.049398E-07 0.000000E+00 0.000000E+00 3.100000E-02 2.250000E-04 -1.389831E-02 -5.927254E-05 -9.999959E-03 -3.379365E-05 -4.584291E-03 -1.665226E-05 1.729188E-02 6.078653E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.792523E-03 8.900697E-07 0.000000E+00 @@ -909,76 +369,28 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.700000E-02 7.100000E-05 -3.566805E-03 -2.049908E-05 -5.418617E-03 -7.496014E-06 -2.451897E-03 -2.115374E-06 7.760000E-03 1.634547E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.977039E-04 8.862762E-08 1.000000E-03 1.000000E-06 --7.119580E-04 -5.068842E-07 -2.603263E-04 -6.776977E-08 -1.657364E-04 -2.746855E-08 8.807004E-04 7.756332E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.900000E-02 2.030000E-04 -6.527719E-03 -1.422798E-05 -1.560049E-03 -2.934829E-06 -1.548553E-03 -8.929308E-06 1.108618E-02 3.019577E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.174573E-03 8.619941E-07 0.000000E+00 @@ -989,76 +401,28 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.500000E-02 5.500000E-05 -2.209492E-03 -2.545503E-06 -5.991182E-03 -1.290780E-05 -1.772063E-03 -2.006265E-06 5.944486E-03 1.042417E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.190621E-03 8.859277E-07 2.000000E-03 2.000000E-06 -1.908405E-03 -1.821879E-06 -1.732819E-03 -1.508498E-06 -1.487670E-03 -1.131430E-06 0.000000E+00 0.000000E+00 2.600000E-02 1.460000E-04 -7.818547E-03 -3.176773E-05 -5.200193E-03 -1.419001E-05 -3.947828E-03 -8.417104E-06 9.232713E-03 1.900222E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.186919E-03 5.294603E-07 0.000000E+00 @@ -1069,116 +433,44 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.600000E-02 2.740000E-04 -9.407788E-03 -2.403566E-05 -4.480017E-03 -6.102099E-06 -5.941113E-03 -1.265337E-05 1.462273E-02 4.551798E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.056694E-04 1.834703E-07 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.807004E-04 7.756332E-07 1.000000E-03 1.000000E-06 -8.905295E-04 -7.930427E-07 -6.895641E-04 -4.754986E-07 -4.297756E-04 -1.847070E-07 0.000000E+00 0.000000E+00 1.200000E-02 3.000000E-05 -4.798420E-03 -7.171924E-06 -1.417651E-03 -4.997963E-06 -2.131704E-03 -3.253030E-06 7.754474E-03 1.333333E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.938723E-04 8.636091E-08 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.484382E-03 1.504223E-06 3.000000E-03 5.000000E-06 -2.760812E-03 -4.139104E-06 -2.336063E-03 -2.844690E-06 -1.817042E-03 -1.656152E-06 0.000000E+00 0.000000E+00 1.900000E-02 9.900000E-05 -7.385212E-03 -2.033270E-05 -6.336514E-03 -2.028060E-05 -3.967026E-03 -1.027239E-05 1.066281E-02 2.937590E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 0.000000E+00 @@ -1189,26 +481,8 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.000000E-03 3.000000E-06 -1.134842E-03 -1.040850E-06 -6.127525E-05 -3.988312E-07 -4.938488E-05 -2.738492E-07 1.484493E-03 9.722886E-07 0.000000E+00 @@ -1223,122 +497,44 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.300000E-02 1.650000E-04 -9.507197E-03 -3.773476E-05 -6.295609E-03 -2.127696E-05 -6.339840E-03 -1.601206E-05 1.065253E-02 3.447907E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.000000E-03 1.000000E-06 --1.117213E-04 -1.248165E-08 --4.812775E-04 -2.316281E-07 -1.640958E-04 -2.692743E-08 5.871336E-04 3.447259E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.100000E-02 2.030000E-04 -1.194795E-02 -4.198084E-05 -8.484089E-03 -1.631808E-05 -5.880364E-03 -1.308096E-05 1.342551E-02 3.842917E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.915762E-04 1.749885E-07 1.000000E-03 1.000000E-06 --5.881991E-05 -3.459782E-09 --4.948103E-04 -2.448373E-07 -8.772111E-05 -7.694993E-09 5.871336E-04 3.447259E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.900000E-02 8.700000E-05 -5.469796E-03 -1.029265E-05 -4.923561E-04 -2.403244E-06 -2.579361E-03 -5.814745E-06 8.693502E-03 1.682273E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 0.000000E+00 @@ -1349,26 +545,8 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.000000E-03 1.600000E-05 -5.411154E-03 -8.076329E-06 -3.145940E-03 -4.212660E-06 -2.637510E-03 -3.210372E-06 3.866943E-03 3.257876E-06 0.000000E+00 @@ -1377,48 +555,18 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.175489E-03 1.381775E-06 1.000000E-03 1.000000E-06 -7.809681E-04 -6.099112E-07 -4.148668E-04 -1.721145E-07 -1.935089E-05 -3.744570E-10 0.000000E+00 0.000000E+00 8.000000E-03 2.000000E-05 -3.721382E-03 -4.736982E-06 --1.037031E-04 -6.648392E-07 --5.996856E-04 -9.642316E-07 3.248622E-03 4.063213E-06 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.938723E-04 8.636091E-08 0.000000E+00 @@ -1429,36 +577,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.800000E-02 1.780000E-04 -1.190615E-02 -4.434442E-05 -7.332993E-03 -2.337416E-05 -6.867008E-03 -1.321552E-05 1.283876E-02 3.527487E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.954078E-04 3.545105E-07 0.000000E+00 @@ -1469,66 +593,24 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 4.000000E-02 3.880000E-04 -6.620484E-03 -3.751545E-05 -9.255367E-03 -1.963463E-05 -7.761524E-03 -1.694944E-05 1.659048E-02 7.200873E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 1.000000E-03 1.000000E-06 --5.273064E-04 -2.780520E-07 --8.292201E-05 -6.876059E-09 -4.244131E-04 -1.801265E-07 8.891500E-04 4.407165E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.200000E-02 3.400000E-05 -5.905081E-03 -1.535048E-05 -3.856089E-03 -8.589511E-06 -3.244585E-03 -5.882222E-06 5.074320E-03 5.793165E-06 0.000000E+00 @@ -1543,32 +625,8 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 4.000000E-03 8.000000E-06 -1.520286E-03 -4.076900E-06 -2.191143E-03 -3.717004E-06 -1.161623E-03 -3.726099E-06 3.570375E-03 5.251426E-06 0.000000E+00 @@ -1583,42 +641,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 4.000000E-03 4.000000E-06 -3.379122E-03 -2.880429E-06 -2.320644E-03 -1.498759E-06 -1.119131E-03 -6.212863E-07 1.494579E-03 6.241235E-07 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 0.000000E+00 @@ -1629,76 +657,28 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.100000E-02 1.950000E-04 -1.338410E-02 -5.097757E-05 -6.794436E-03 -1.428226E-05 -3.939298E-03 -9.052046E-06 1.250316E-02 3.147175E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.953428E-04 1.772165E-07 1.000000E-03 1.000000E-06 --5.747626E-05 -3.303520E-09 --4.950447E-04 -2.450693E-07 -8.573970E-05 -7.351297E-09 5.954078E-04 3.545105E-07 1.000000E-03 1.000000E-06 -9.748367E-04 -9.503066E-07 -9.254598E-04 -8.564759E-07 -8.537292E-04 -7.288535E-07 0.000000E+00 0.000000E+00 2.600000E-02 1.800000E-04 -1.225587E-02 -3.757196E-05 -7.670283E-03 -2.316758E-05 -5.282346E-03 -1.407031E-05 1.162837E-02 3.241150E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.874391E-04 1.725424E-07 0.000000E+00 @@ -1709,66 +689,24 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.900000E-02 9.900000E-05 -7.244455E-03 -2.708286E-05 -4.601657E-03 -5.366244E-06 --1.675270E-03 -3.867377E-06 9.216754E-03 2.272207E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.159309E-04 3.793709E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.000000E-03 1.000000E-05 -1.316884E-03 -2.894217E-06 -2.095957E-03 -1.439521E-06 -1.013831E-04 -8.405300E-07 2.404012E-03 1.641294E-06 0.000000E+00 @@ -1799,66 +737,12 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.500000E-02 6.300000E-05 -5.790211E-03 -1.210465E-05 -8.591246E-04 -1.950998E-06 -1.987991E-03 -4.703397E-06 6.572185E-03 9.479065E-06 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.954078E-04 3.545105E-07 0.000000E+00 @@ -1869,26 +753,8 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.900000E-02 1.030000E-04 -3.010784E-03 -6.984061E-06 -5.243839E-03 -8.093978E-06 --2.502286E-04 -2.564795E-06 7.760558E-03 1.654841E-05 0.000000E+00 @@ -1897,48 +763,18 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 1.000000E-03 1.000000E-06 -9.333938E-04 -8.712239E-07 -8.068359E-04 -6.509841E-07 -6.328968E-04 -4.005583E-07 0.000000E+00 0.000000E+00 1.800000E-02 1.220000E-04 -6.860987E-03 -2.966806E-05 -4.229750E-03 -1.617998E-05 -1.295452E-03 -8.613003E-07 7.815192E-03 2.039362E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 0.000000E+00 @@ -1965,50 +801,8 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.000000E-03 2.000000E-06 -1.502634E-03 -1.146555E-06 -7.198331E-04 -3.484978E-07 --3.426513E-05 -1.342349E-07 1.511030E-03 1.198310E-06 0.000000E+00 @@ -2023,32 +817,8 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.900000E-02 1.030000E-04 -3.892013E-03 -8.267134E-06 -3.385502E-03 -6.457947E-06 -4.380506E-03 -1.060881E-05 7.130794E-03 1.485134E-05 0.000000E+00 @@ -2057,78 +827,30 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.816168E-04 7.772482E-07 1.000000E-03 1.000000E-06 -9.951010E-04 -9.902260E-07 -9.853389E-04 -9.708928E-07 -9.707856E-04 -9.424246E-07 0.000000E+00 0.000000E+00 2.900000E-02 1.970000E-04 -4.944370E-03 -2.340271E-05 -3.891346E-03 -1.137205E-05 -5.738303E-03 -9.169816E-06 1.046961E-02 2.353870E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.056694E-04 1.834703E-07 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.203064E-03 7.240967E-07 2.000000E-03 2.000000E-06 -1.696621E-03 -1.449397E-06 -1.174096E-03 -7.548933E-07 -5.719051E-04 -3.184788E-07 0.000000E+00 0.000000E+00 1.600000E-02 5.400000E-05 -7.937805E-03 -1.353584E-05 -4.065443E-03 -5.658333E-06 -3.432476E-03 -3.530320E-06 6.546902E-03 9.343143E-06 0.000000E+00 @@ -2143,32 +865,8 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.000000E-03 2.000000E-06 -1.447007E-04 -7.721849E-07 -1.582773E-04 -4.841114E-08 -1.981705E-04 -2.166687E-07 9.135698E-04 4.679598E-07 0.000000E+00 @@ -2199,176 +897,56 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.000000E-02 2.600000E-05 -5.875085E-04 -4.563904E-07 --9.207198E-05 -5.154496E-07 -3.674257E-05 -1.178281E-06 5.048984E-03 5.880389E-06 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.952777E-04 3.543556E-07 1.000000E-03 1.000000E-06 -9.362621E-04 -8.765867E-07 -8.148801E-04 -6.640295E-07 -6.473941E-04 -4.191191E-07 0.000000E+00 0.000000E+00 2.000000E-02 9.000000E-05 -5.358616E-03 -1.697599E-05 -3.060277E-03 -7.132281E-06 -2.485730E-03 -7.247489E-06 9.248312E-03 1.738407E-05 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.991711E-04 2.696131E-07 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.954078E-04 3.545105E-07 1.000000E-03 1.000000E-06 -9.816220E-04 -9.635817E-07 -9.453726E-04 -8.937294E-07 -8.922496E-04 -7.961093E-07 0.000000E+00 0.000000E+00 8.000000E-03 1.800000E-05 -4.925975E-03 -6.260377E-06 -3.176938E-03 -2.631319E-06 -2.008278E-03 -1.484516E-06 3.844641E-03 4.075868E-06 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.977039E-04 8.862762E-08 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 9.238963E-04 8.535844E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.000000E-03 1.000000E-06 --3.865739E-04 -1.494394E-07 --2.758409E-04 -7.608820E-08 -4.354374E-04 -1.896058E-07 5.871336E-04 3.447259E-07 0.000000E+00 @@ -2383,24 +961,6 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 tally 2: 5.656886E-01 6.401440E-02 diff --git a/tests/regression_tests/sourcepoint_restart/tallies.xml b/tests/regression_tests/sourcepoint_restart/tallies.xml index db9b2ed75..60bf6b268 100644 --- a/tests/regression_tests/sourcepoint_restart/tallies.xml +++ b/tests/regression_tests/sourcepoint_restart/tallies.xml @@ -30,7 +30,7 @@ 1 2 3 - scatter-P3 nu-fission + scatter nu-fission diff --git a/tests/regression_tests/statepoint_restart/results_true.dat b/tests/regression_tests/statepoint_restart/results_true.dat index f726da973..c35ff46b8 100644 --- a/tests/regression_tests/statepoint_restart/results_true.dat +++ b/tests/regression_tests/statepoint_restart/results_true.dat @@ -3,62 +3,38 @@ k-combined: tally 1: 1.100000E-02 3.700000E-05 -1.307570E-03 -2.851451E-06 -1.564980E-03 -2.368303E-06 -3.138136E-03 -5.769887E-06 +1.100000E-02 +3.700000E-05 7.719234E-03 2.632582E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.816168E-04 7.772482E-07 1.000000E-03 1.000000E-06 -8.782909E-04 -7.713950E-07 -6.570925E-04 -4.317705E-07 -3.763366E-04 -1.416293E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 2.100000E-02 1.150000E-04 -5.280651E-03 -1.222273E-05 -5.235520E-03 -1.202448E-05 -5.064093E-03 -1.787892E-05 +2.100000E-02 +1.150000E-04 1.071093E-02 2.748612E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.954045E-04 2.673851E-07 0.000000E+00 @@ -73,72 +49,40 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.100000E-02 2.130000E-04 -1.472240E-02 -5.500913E-05 -1.077445E-02 -2.987369E-05 -6.729425E-03 -1.249089E-05 +3.100000E-02 +2.130000E-04 1.363637E-02 4.345510E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.182717E-03 5.268980E-07 2.000000E-03 2.000000E-06 --1.367978E-03 -9.381191E-07 -4.071787E-04 -9.316064E-08 -4.394728E-04 -1.064342E-07 +3.000000E-03 +5.000000E-06 2.110880E-03 1.737594E-06 1.000000E-03 1.000000E-06 -9.347357E-04 -8.737309E-07 -8.105963E-04 -6.570664E-07 -6.396651E-04 -4.091714E-07 +1.000000E-03 +1.000000E-06 2.938723E-04 8.636091E-08 2.300000E-02 1.330000E-04 -1.081756E-02 -3.675127E-05 -2.530156E-03 -6.960955E-06 --1.930911E-03 -4.910249E-06 +2.300000E-02 +1.330000E-04 1.162826E-02 3.490280E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.957100E-04 4.402864E-07 0.000000E+00 @@ -153,32 +97,16 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.000000E-02 3.400000E-05 -4.086838E-03 -5.900874E-06 -1.812330E-03 -3.716159E-06 -2.138941E-03 -3.006748E-06 +1.000000E-02 +3.400000E-05 5.414128E-03 8.079333E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 @@ -193,32 +121,16 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.700000E-02 7.100000E-05 -5.492922E-03 -1.013834E-05 -5.773309E-04 -3.561549E-06 -2.550048E-03 -3.841061E-06 +1.700000E-02 +7.100000E-05 8.048522E-03 1.583843E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.915762E-04 1.749885E-07 0.000000E+00 @@ -233,152 +145,88 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.700000E-02 1.670000E-04 -1.789444E-02 -8.260005E-05 -1.049872E-02 -2.774537E-05 -5.665111E-03 -8.560197E-06 +2.700000E-02 +1.670000E-04 1.100708E-02 2.835295E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 1.000000E-03 1.000000E-06 --2.856031E-04 -8.156913E-08 --3.776463E-04 -1.426167E-07 -3.701637E-04 -1.370211E-07 +1.000000E-03 +1.000000E-06 1.203064E-03 7.240967E-07 1.000000E-03 1.000000E-06 -9.705482E-04 -9.419638E-07 -9.129457E-04 -8.334699E-07 -8.297310E-04 -6.884535E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 4.400000E-02 4.320000E-04 -1.141886E-02 -4.208707E-05 -9.213446E-03 -2.259305E-05 -9.177440E-03 -2.088782E-05 +4.400000E-02 +4.320000E-04 2.116869E-02 9.629046E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.501009E-03 6.405021E-07 1.000000E-03 1.000000E-06 -5.882614E-04 -3.460515E-07 -1.907719E-05 -3.639390E-10 --3.734703E-04 -1.394801E-07 +1.000000E-03 +1.000000E-06 1.472277E-03 9.506217E-07 2.000000E-03 2.000000E-06 -1.830192E-03 -1.679505E-06 -1.519257E-03 -1.189525E-06 -1.118506E-03 -7.332971E-07 +2.000000E-03 +2.000000E-06 2.977039E-04 8.862762E-08 2.000000E-02 1.080000E-04 -8.640372E-03 -1.765553E-05 -5.688468E-03 -1.038555E-05 -2.447898E-03 -4.466055E-06 +2.000000E-02 +1.080000E-04 8.949667E-03 1.935056E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.977039E-04 8.862762E-08 1.000000E-03 1.000000E-06 --3.805163E-04 -1.447926E-07 --2.828111E-04 -7.998210E-08 -4.330345E-04 -1.875189E-07 +1.000000E-03 +1.000000E-06 2.121142E-03 1.958355E-06 1.000000E-03 1.000000E-06 -9.260022E-04 -8.574800E-07 -7.862200E-04 -6.181419E-07 -5.960676E-04 -3.552966E-07 +1.000000E-03 +1.000000E-06 2.938723E-04 8.636091E-08 1.000000E-02 3.400000E-05 -4.840884E-03 -1.080853E-05 -3.402096E-03 -4.113972E-06 -1.374077E-03 -2.333511E-06 +1.000000E-02 +3.400000E-05 4.754696E-03 7.172309E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 @@ -393,122 +241,70 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.300000E-02 3.700000E-05 -3.121167E-03 -2.465327E-06 -4.474559E-04 -1.522316E-06 -9.193982E-04 -3.247292E-06 +1.300000E-02 +3.700000E-05 5.365659E-03 6.567979E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.056043E-04 1.834316E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.977039E-04 8.862762E-08 1.000000E-03 1.000000E-06 -2.390376E-04 -5.713899E-08 --4.142915E-04 -1.716375E-07 --3.244105E-04 -1.052422E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 2.900000E-02 2.230000E-04 -6.260565E-03 -1.544092E-05 -7.061757E-03 -2.562385E-05 -3.982541E-03 -7.962565E-06 +2.900000E-02 +2.230000E-04 1.486928E-02 5.763901E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.915762E-04 1.749885E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.954078E-04 3.545105E-07 1.000000E-03 1.000000E-06 -9.938157E-04 -9.876697E-07 -9.815046E-04 -9.633512E-07 -9.631807E-04 -9.277170E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 2.900000E-02 1.990000E-04 -1.237546E-02 -3.198261E-05 -8.287792E-03 -2.747313E-05 -3.254969E-03 -8.653294E-06 +2.900000E-02 +1.990000E-04 1.189702E-02 3.303340E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 9.033082E-04 2.720592E-07 3.000000E-03 3.000000E-06 -3.649225E-04 -1.756075E-06 -1.134112E-03 -8.940601E-07 --9.392028E-04 -5.872882E-07 +4.000000E-03 +6.000000E-06 1.484187E-03 9.721092E-07 0.000000E+00 @@ -517,188 +313,112 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.500000E-02 1.390000E-04 -1.511834E-02 -5.459835E-05 -7.753447E-03 -2.291820E-05 -6.142979E-03 -1.359405E-05 +2.500000E-02 +1.390000E-04 1.043467E-02 2.526768E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.912057E-04 1.747704E-07 1.000000E-03 1.000000E-06 --1.098110E-04 -1.205846E-08 --4.819123E-04 -2.322395E-07 -1.614061E-04 -2.605194E-08 +1.000000E-03 +1.000000E-06 8.813114E-04 4.316251E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 1.900000E-02 1.030000E-04 -1.035735E-02 -3.119381E-05 -6.483551E-03 -1.164471E-05 -3.924334E-03 -6.047577E-06 +1.900000E-02 +1.030000E-04 9.748672E-03 2.956633E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 1.000000E-03 1.000000E-06 -8.623139E-04 -7.435852E-07 -6.153778E-04 -3.786899E-07 -3.095388E-04 -9.581428E-08 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 7.000000E-03 1.500000E-05 -3.445754E-03 -3.819507E-06 -2.124056E-03 -1.976201E-06 -1.542203E-03 -1.531669E-06 +7.000000E-03 +1.500000E-05 4.135720E-03 4.532611E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.874391E-04 1.725424E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 1.000000E-03 1.000000E-06 -9.451745E-04 -8.933548E-07 -8.400323E-04 -7.056542E-07 -6.931788E-04 -4.804969E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 2.600000E-02 1.560000E-04 -6.314886E-03 -1.168542E-05 -6.676680E-04 -9.930437E-06 --2.103247E-04 -2.815945E-07 +2.600000E-02 +1.560000E-04 1.249410E-02 3.501589E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.159309E-04 3.793709E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 1.000000E-03 1.000000E-06 -9.514029E-04 -9.051675E-07 -8.577513E-04 -7.357373E-07 -7.258432E-04 -5.268483E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 2.900000E-02 1.950000E-04 -8.928267E-03 -2.594547E-05 -4.752762E-03 -1.522378E-05 -5.376579E-03 -1.088620E-05 +2.900000E-02 +1.950000E-04 1.461148E-02 4.367385E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.912057E-04 1.747704E-07 0.000000E+00 @@ -713,62 +433,34 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.900000E-02 9.700000E-05 -1.046968E-02 -3.828445E-05 -6.704767E-03 -2.668260E-05 -2.659611E-03 -1.143518E-05 +1.900000E-02 +9.700000E-05 1.099391E-02 2.847330E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.655539E-03 2.523801E-06 3.000000E-03 5.000000E-06 -2.960960E-03 -4.866206E-06 -2.884206E-03 -4.608867E-06 -2.772329E-03 -4.247272E-06 +3.000000E-03 +5.000000E-06 2.976389E-04 8.858890E-08 6.000000E-03 8.000000E-06 -2.104495E-03 -2.749678E-06 -8.451272E-04 -9.362821E-07 -5.355137E-04 -3.419837E-07 +6.000000E-03 +8.000000E-06 2.683856E-03 1.512640E-06 0.000000E+00 @@ -781,124 +473,72 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 1.000000E-03 1.000000E-06 -9.374310E-04 -8.787769E-07 -8.181653E-04 -6.693944E-07 -6.533352E-04 -4.268468E-07 +1.000000E-03 +1.000000E-06 2.976389E-04 8.858890E-08 1.200000E-02 4.600000E-05 -9.302157E-04 -3.853850E-06 -1.874541E-03 -3.092623E-06 --1.511552E-03 -4.053466E-06 +1.200000E-02 +4.600000E-05 5.941985E-03 9.853870E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.186548E-03 5.291647E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 1.000000E-03 1.000000E-06 -9.893707E-04 -9.788543E-07 -9.682814E-04 -9.375689E-07 -9.370683E-04 -8.780970E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 2.300000E-02 1.230000E-04 -5.099566E-03 -9.143616E-06 -4.738751E-03 -7.819254E-06 -3.929250E-03 -6.884420E-06 +2.300000E-02 +1.230000E-04 1.015650E-02 2.236950E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.977039E-04 8.862762E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.912707E-04 1.748091E-07 2.000000E-03 2.000000E-06 -1.762035E-03 -1.552542E-06 -1.328812E-03 -8.839688E-07 -7.771806E-04 -3.049398E-07 +2.000000E-03 +2.000000E-06 0.000000E+00 0.000000E+00 3.100000E-02 2.250000E-04 -1.389831E-02 -5.927254E-05 -9.999959E-03 -3.379365E-05 -4.584291E-03 -1.665226E-05 +3.100000E-02 +2.250000E-04 1.729188E-02 6.078653E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.792523E-03 8.900697E-07 0.000000E+00 @@ -913,42 +553,22 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.700000E-02 7.100000E-05 -3.566805E-03 -2.049908E-05 -5.418617E-03 -7.496014E-06 -2.451897E-03 -2.115374E-06 +1.700000E-02 +7.100000E-05 7.760000E-03 1.634547E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.977039E-04 8.862762E-08 1.000000E-03 1.000000E-06 --7.119580E-04 -5.068842E-07 -2.603263E-04 -6.776977E-08 -1.657364E-04 -2.746855E-08 +1.000000E-03 +1.000000E-06 8.807004E-04 7.756332E-07 0.000000E+00 @@ -957,28 +577,16 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.900000E-02 2.030000E-04 -6.527719E-03 -1.422798E-05 -1.560049E-03 -2.934829E-06 -1.548553E-03 -8.929308E-06 +2.900000E-02 +2.030000E-04 1.108618E-02 3.019577E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.174573E-03 8.619941E-07 0.000000E+00 @@ -993,72 +601,40 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.500000E-02 5.500000E-05 -2.209492E-03 -2.545503E-06 -5.991182E-03 -1.290780E-05 -1.772063E-03 -2.006265E-06 +1.500000E-02 +5.500000E-05 5.944486E-03 1.042417E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.190621E-03 8.859277E-07 2.000000E-03 2.000000E-06 -1.908405E-03 -1.821879E-06 -1.732819E-03 -1.508498E-06 -1.487670E-03 -1.131430E-06 +2.000000E-03 +2.000000E-06 0.000000E+00 0.000000E+00 2.600000E-02 1.460000E-04 -7.818547E-03 -3.176773E-05 -5.200193E-03 -1.419001E-05 -3.947828E-03 -8.417104E-06 +2.600000E-02 +1.460000E-04 9.232713E-03 1.900222E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.186919E-03 5.294603E-07 0.000000E+00 @@ -1073,112 +649,64 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.600000E-02 2.740000E-04 -9.407788E-03 -2.403566E-05 -4.480017E-03 -6.102099E-06 -5.941113E-03 -1.265337E-05 +3.600000E-02 +2.740000E-04 1.462273E-02 4.551798E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.056694E-04 1.834703E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.807004E-04 7.756332E-07 1.000000E-03 1.000000E-06 -8.905295E-04 -7.930427E-07 -6.895641E-04 -4.754986E-07 -4.297756E-04 -1.847070E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 1.200000E-02 3.000000E-05 -4.798420E-03 -7.171924E-06 -1.417651E-03 -4.997963E-06 -2.131704E-03 -3.253030E-06 +1.200000E-02 +3.000000E-05 7.754474E-03 1.333333E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.938723E-04 8.636091E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.484382E-03 1.504223E-06 3.000000E-03 5.000000E-06 -2.760812E-03 -4.139104E-06 -2.336063E-03 -2.844690E-06 -1.817042E-03 -1.656152E-06 +3.000000E-03 +5.000000E-06 0.000000E+00 0.000000E+00 1.900000E-02 9.900000E-05 -7.385212E-03 -2.033270E-05 -6.336514E-03 -2.028060E-05 -3.967026E-03 -1.027239E-05 +1.900000E-02 +9.900000E-05 1.066281E-02 2.937590E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 0.000000E+00 @@ -1193,22 +721,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.000000E-03 3.000000E-06 -1.134842E-03 -1.040850E-06 -6.127525E-05 -3.988312E-07 -4.938488E-05 -2.738492E-07 +3.000000E-03 +3.000000E-06 1.484493E-03 9.722886E-07 0.000000E+00 @@ -1229,26 +745,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.300000E-02 1.650000E-04 -9.507197E-03 -3.773476E-05 -6.295609E-03 -2.127696E-05 -6.339840E-03 -1.601206E-05 +2.300000E-02 +1.650000E-04 1.065253E-02 3.447907E-05 0.000000E+00 @@ -1257,18 +757,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.000000E-03 1.000000E-06 --1.117213E-04 -1.248165E-08 --4.812775E-04 -2.316281E-07 -1.640958E-04 -2.692743E-08 +1.000000E-03 +1.000000E-06 5.871336E-04 3.447259E-07 0.000000E+00 @@ -1277,38 +769,22 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.100000E-02 2.030000E-04 -1.194795E-02 -4.198084E-05 -8.484089E-03 -1.631808E-05 -5.880364E-03 -1.308096E-05 +3.100000E-02 +2.030000E-04 1.342551E-02 3.842917E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.915762E-04 1.749885E-07 1.000000E-03 1.000000E-06 --5.881991E-05 -3.459782E-09 --4.948103E-04 -2.448373E-07 -8.772111E-05 -7.694993E-09 +1.000000E-03 +1.000000E-06 5.871336E-04 3.447259E-07 0.000000E+00 @@ -1317,28 +793,16 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.900000E-02 8.700000E-05 -5.469796E-03 -1.029265E-05 -4.923561E-04 -2.403244E-06 -2.579361E-03 -5.814745E-06 +1.900000E-02 +8.700000E-05 8.693502E-03 1.682273E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 0.000000E+00 @@ -1353,22 +817,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.000000E-03 1.600000E-05 -5.411154E-03 -8.076329E-06 -3.145940E-03 -4.212660E-06 -2.637510E-03 -3.210372E-06 +8.000000E-03 +1.600000E-05 3.866943E-03 3.257876E-06 0.000000E+00 @@ -1381,44 +833,24 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.175489E-03 1.381775E-06 1.000000E-03 1.000000E-06 -7.809681E-04 -6.099112E-07 -4.148668E-04 -1.721145E-07 -1.935089E-05 -3.744570E-10 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 8.000000E-03 2.000000E-05 -3.721382E-03 -4.736982E-06 --1.037031E-04 -6.648392E-07 --5.996856E-04 -9.642316E-07 +8.000000E-03 +2.000000E-05 3.248622E-03 4.063213E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.938723E-04 8.636091E-08 0.000000E+00 @@ -1433,32 +865,16 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.800000E-02 1.780000E-04 -1.190615E-02 -4.434442E-05 -7.332993E-03 -2.337416E-05 -6.867008E-03 -1.321552E-05 +2.800000E-02 +1.780000E-04 1.283876E-02 3.527487E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.954078E-04 3.545105E-07 0.000000E+00 @@ -1473,42 +889,22 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 4.000000E-02 3.880000E-04 -6.620484E-03 -3.751545E-05 -9.255367E-03 -1.963463E-05 -7.761524E-03 -1.694944E-05 +4.000000E-02 +3.880000E-04 1.659048E-02 7.200873E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 1.000000E-03 1.000000E-06 --5.273064E-04 -2.780520E-07 --8.292201E-05 -6.876059E-09 -4.244131E-04 -1.801265E-07 +1.000000E-03 +1.000000E-06 8.891500E-04 4.407165E-07 0.000000E+00 @@ -1517,18 +913,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.200000E-02 3.400000E-05 -5.905081E-03 -1.535048E-05 -3.856089E-03 -8.589511E-06 -3.244585E-03 -5.882222E-06 +1.200000E-02 +3.400000E-05 5.074320E-03 5.793165E-06 0.000000E+00 @@ -1549,26 +937,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 4.000000E-03 8.000000E-06 -1.520286E-03 -4.076900E-06 -2.191143E-03 -3.717004E-06 -1.161623E-03 -3.726099E-06 +4.000000E-03 +8.000000E-06 3.570375E-03 5.251426E-06 0.000000E+00 @@ -1589,36 +961,16 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 4.000000E-03 4.000000E-06 -3.379122E-03 -2.880429E-06 -2.320644E-03 -1.498759E-06 -1.119131E-03 -6.212863E-07 +4.000000E-03 +4.000000E-06 1.494579E-03 6.241235E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.976389E-04 8.858890E-08 0.000000E+00 @@ -1633,72 +985,40 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.100000E-02 1.950000E-04 -1.338410E-02 -5.097757E-05 -6.794436E-03 -1.428226E-05 -3.939298E-03 -9.052046E-06 +3.100000E-02 +1.950000E-04 1.250316E-02 3.147175E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.953428E-04 1.772165E-07 1.000000E-03 1.000000E-06 --5.747626E-05 -3.303520E-09 --4.950447E-04 -2.450693E-07 -8.573970E-05 -7.351297E-09 +1.000000E-03 +1.000000E-06 5.954078E-04 3.545105E-07 1.000000E-03 1.000000E-06 -9.748367E-04 -9.503066E-07 -9.254598E-04 -8.564759E-07 -8.537292E-04 -7.288535E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 2.600000E-02 1.800000E-04 -1.225587E-02 -3.757196E-05 -7.670283E-03 -2.316758E-05 -5.282346E-03 -1.407031E-05 +2.600000E-02 +1.800000E-04 1.162837E-02 3.241150E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.874391E-04 1.725424E-07 0.000000E+00 @@ -1713,42 +1033,22 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.900000E-02 9.900000E-05 -7.244455E-03 -2.708286E-05 -4.601657E-03 -5.366244E-06 --1.675270E-03 -3.867377E-06 +1.900000E-02 +9.900000E-05 9.216754E-03 2.272207E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.159309E-04 3.793709E-07 0.000000E+00 @@ -1757,18 +1057,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.000000E-03 1.000000E-05 -1.316884E-03 -2.894217E-06 -2.095957E-03 -1.439521E-06 -1.013831E-04 -8.405300E-07 +6.000000E-03 +1.000000E-05 2.404012E-03 1.641294E-06 0.000000E+00 @@ -1813,52 +1105,16 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.500000E-02 6.300000E-05 -5.790211E-03 -1.210465E-05 -8.591246E-04 -1.950998E-06 -1.987991E-03 -4.703397E-06 +1.500000E-02 +6.300000E-05 6.572185E-03 9.479065E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.954078E-04 3.545105E-07 0.000000E+00 @@ -1873,22 +1129,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.900000E-02 1.030000E-04 -3.010784E-03 -6.984061E-06 -5.243839E-03 -8.093978E-06 --2.502286E-04 -2.564795E-06 +1.900000E-02 +1.030000E-04 7.760558E-03 1.654841E-05 0.000000E+00 @@ -1901,44 +1145,24 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 1.000000E-03 1.000000E-06 -9.333938E-04 -8.712239E-07 -8.068359E-04 -6.509841E-07 -6.328968E-04 -4.005583E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 1.800000E-02 1.220000E-04 -6.860987E-03 -2.966806E-05 -4.229750E-03 -1.617998E-05 -1.295452E-03 -8.613003E-07 +1.800000E-02 +1.220000E-04 7.815192E-03 2.039362E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.935668E-04 8.618147E-08 0.000000E+00 @@ -1977,38 +1201,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.000000E-03 2.000000E-06 -1.502634E-03 -1.146555E-06 -7.198331E-04 -3.484978E-07 --3.426513E-05 -1.342349E-07 +2.000000E-03 +2.000000E-06 1.511030E-03 1.198310E-06 0.000000E+00 @@ -2029,26 +1225,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.900000E-02 1.030000E-04 -3.892013E-03 -8.267134E-06 -3.385502E-03 -6.457947E-06 -4.380506E-03 -1.060881E-05 +1.900000E-02 +1.030000E-04 7.130794E-03 1.485134E-05 0.000000E+00 @@ -2061,74 +1241,42 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.816168E-04 7.772482E-07 1.000000E-03 1.000000E-06 -9.951010E-04 -9.902260E-07 -9.853389E-04 -9.708928E-07 -9.707856E-04 -9.424246E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 2.900000E-02 1.970000E-04 -4.944370E-03 -2.340271E-05 -3.891346E-03 -1.137205E-05 -5.738303E-03 -9.169816E-06 +2.900000E-02 +1.970000E-04 1.046961E-02 2.353870E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 6.056694E-04 1.834703E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.203064E-03 7.240967E-07 2.000000E-03 2.000000E-06 -1.696621E-03 -1.449397E-06 -1.174096E-03 -7.548933E-07 -5.719051E-04 -3.184788E-07 +2.000000E-03 +2.000000E-06 0.000000E+00 0.000000E+00 1.600000E-02 5.400000E-05 -7.937805E-03 -1.353584E-05 -4.065443E-03 -5.658333E-06 -3.432476E-03 -3.530320E-06 +1.600000E-02 +5.400000E-05 6.546902E-03 9.343143E-06 0.000000E+00 @@ -2149,26 +1297,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.000000E-03 2.000000E-06 -1.447007E-04 -7.721849E-07 -1.582773E-04 -4.841114E-08 -1.981705E-04 -2.166687E-07 +2.000000E-03 +2.000000E-06 9.135698E-04 4.679598E-07 0.000000E+00 @@ -2213,142 +1345,70 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.000000E-02 2.600000E-05 -5.875085E-04 -4.563904E-07 --9.207198E-05 -5.154496E-07 -3.674257E-05 -1.178281E-06 +1.000000E-02 +2.600000E-05 5.048984E-03 5.880389E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 3.079654E-04 9.484271E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.952777E-04 3.543556E-07 1.000000E-03 1.000000E-06 -9.362621E-04 -8.765867E-07 -8.148801E-04 -6.640295E-07 -6.473941E-04 -4.191191E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 2.000000E-02 9.000000E-05 -5.358616E-03 -1.697599E-05 -3.060277E-03 -7.132281E-06 -2.485730E-03 -7.247489E-06 +2.000000E-02 +9.000000E-05 9.248312E-03 1.738407E-05 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 8.991711E-04 2.696131E-07 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 5.954078E-04 3.545105E-07 1.000000E-03 1.000000E-06 -9.816220E-04 -9.635817E-07 -9.453726E-04 -8.937294E-07 -8.922496E-04 -7.961093E-07 +1.000000E-03 +1.000000E-06 0.000000E+00 0.000000E+00 8.000000E-03 1.800000E-05 -4.925975E-03 -6.260377E-06 -3.176938E-03 -2.631319E-06 -2.008278E-03 -1.484516E-06 +8.000000E-03 +1.800000E-05 3.844641E-03 4.075868E-06 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 2.977039E-04 8.862762E-08 0.000000E+00 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 9.238963E-04 8.535844E-07 0.000000E+00 @@ -2357,18 +1417,10 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 1.000000E-03 1.000000E-06 --3.865739E-04 -1.494394E-07 --2.758409E-04 -7.608820E-08 -4.354374E-04 -1.896058E-07 +1.000000E-03 +1.000000E-06 5.871336E-04 3.447259E-07 0.000000E+00 @@ -2389,18 +1441,6 @@ tally 1: 0.000000E+00 0.000000E+00 0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 -0.000000E+00 tally 2: 5.656886E-01 6.401440E-02 diff --git a/tests/regression_tests/statepoint_restart/tallies.xml b/tests/regression_tests/statepoint_restart/tallies.xml index db9b2ed75..c7dff36f0 100644 --- a/tests/regression_tests/statepoint_restart/tallies.xml +++ b/tests/regression_tests/statepoint_restart/tallies.xml @@ -30,7 +30,7 @@ 1 2 3 - scatter-P3 nu-fission + scatter nu-scatter nu-fission diff --git a/tests/regression_tests/tallies/inputs_true.dat b/tests/regression_tests/tallies/inputs_true.dat index 2c33a8fa0..6e7b5f3f0 100644 --- a/tests/regression_tests/tallies/inputs_true.dat +++ b/tests/regression_tests/tallies/inputs_true.dat @@ -341,13 +341,19 @@ 0.0 0.6283 1.2566 1.885 2.5132 3.14159 - + + 4 + + + 4 + + 1 2 3 4 6 8 - + 10 21 22 23 60 - + 21 22 23 27 28 29 60 @@ -414,80 +420,80 @@ 10 - total + scatter nu-scatter 11 - absorption delayed-nu-fission events fission inverse-velocity kappa-fission (n,2n) (n,n1) (n,gamma) nu-fission scatter elastic total prompt-nu-fission fission-q-prompt fission-q-recoverable - tracklength + scatter nu-scatter flux total 11 - absorption delayed-nu-fission events fission inverse-velocity kappa-fission (n,2n) (n,n1) (n,gamma) nu-fission scatter elastic total prompt-nu-fission fission-q-prompt fission-q-recoverable - analog + flux total 11 - absorption delayed-nu-fission events fission inverse-velocity kappa-fission (n,2n) (n,n1) (n,gamma) nu-fission scatter elastic total prompt-nu-fission fission-q-prompt fission-q-recoverable - collision + flux total 12 - flux + total - 12 - flux-y5 + 13 + absorption delayed-nu-fission events fission inverse-velocity kappa-fission (n,2n) (n,n1) (n,gamma) nu-fission scatter elastic total prompt-nu-fission fission-q-prompt fission-q-recoverable tracklength - 12 - flux-y5 + 13 + absorption delayed-nu-fission events fission inverse-velocity kappa-fission (n,2n) (n,n1) (n,gamma) nu-fission scatter elastic total prompt-nu-fission fission-q-prompt fission-q-recoverable analog - 12 - flux-y5 + 13 + absorption delayed-nu-fission events fission inverse-velocity kappa-fission (n,2n) (n,n1) (n,gamma) nu-fission scatter elastic total prompt-nu-fission fission-q-prompt fission-q-recoverable collision - 11 - scatter scatter-1 scatter-2 scatter-3 scatter-4 nu-scatter nu-scatter-1 nu-scatter-2 nu-scatter-3 nu-scatter-4 + 14 + flux + tracklength - 11 - scatter-p4 scatter-y4 nu-scatter-p4 nu-scatter-y3 + 14 + flux + analog - 11 - total + 14 + flux + collision - 11 + 13 U235 total - total-y4 + total tracklength - 11 + 13 U235 total - total-y4 + total analog - 11 + 13 U235 total - total-y4 + total collision - 11 + 13 all total tracklength - 11 + 13 all total collision diff --git a/tests/regression_tests/tallies/results_true.dat b/tests/regression_tests/tallies/results_true.dat index e9c0865fd..b1cef30fe 100644 --- a/tests/regression_tests/tallies/results_true.dat +++ b/tests/regression_tests/tallies/results_true.dat @@ -1 +1 @@ -13014f42dea87bf6c1fc0d41361cdba8a7e32a8f809d348567dfce638c849f58a0c0f17065c199946db81a264ef72db850aea93b0e11adf4f70ec969e30529cd \ No newline at end of file +8cf1936c565c6a09bffe2f7a0623ded1405bae37c1de8159551e64b86ca4f6bce82890a630b9428bcf8353f6de8e5cfc1f1a4263fd51a1c6b5cddb0a9c2ff368 \ No newline at end of file diff --git a/tests/regression_tests/tallies/test.py b/tests/regression_tests/tallies/test.py index e3aebfd98..53ce60b62 100644 --- a/tests/regression_tests/tallies/test.py +++ b/tests/regression_tests/tallies/test.py @@ -1,5 +1,6 @@ from openmc.filter import * -from openmc import Mesh, Tally, Tallies +from openmc.filter_expansion import * +from openmc import Mesh, Tally from tests.testing_harness import HashedPyAPITestHarness @@ -17,53 +18,53 @@ def test_tallies(): azimuthal_bins = (-3.14159, -1.8850, -0.6283, 0.6283, 1.8850, 3.14159) azimuthal_filter = AzimuthalFilter(azimuthal_bins) - azimuthal_tally1 = Tally() + azimuthal_tally1 = Tally(tally_id=1) azimuthal_tally1.filters = [azimuthal_filter] azimuthal_tally1.scores = ['flux'] azimuthal_tally1.estimator = 'tracklength' - azimuthal_tally2 = Tally() + azimuthal_tally2 = Tally(tally_id=2) azimuthal_tally2.filters = [azimuthal_filter] azimuthal_tally2.scores = ['flux'] azimuthal_tally2.estimator = 'analog' mesh_2x2 = Mesh(mesh_id=1) - mesh_2x2.lower_left = [-182.07, -182.07] - mesh_2x2.upper_right = [182.07, 182.07] + mesh_2x2.lower_left = [-182.07, -182.07] + mesh_2x2.upper_right = [182.07, 182.07] mesh_2x2.dimension = [2, 2] mesh_filter = MeshFilter(mesh_2x2) - azimuthal_tally3 = Tally() + azimuthal_tally3 = Tally(tally_id=3) azimuthal_tally3.filters = [azimuthal_filter, mesh_filter] azimuthal_tally3.scores = ['flux'] azimuthal_tally3.estimator = 'tracklength' - cellborn_tally = Tally() + cellborn_tally = Tally(tally_id=4) cellborn_tally.filters = [ CellbornFilter((model.geometry.get_all_cells()[10], model.geometry.get_all_cells()[21], 22, 23))] # Test both Cell objects and ids cellborn_tally.scores = ['total'] - dg_tally = Tally() + dg_tally = Tally(tally_id=5) dg_tally.filters = [DelayedGroupFilter((1, 2, 3, 4, 5, 6))] dg_tally.scores = ['delayed-nu-fission'] four_groups = (0.0, 0.253, 1.0e3, 1.0e6, 20.0e6) energy_filter = EnergyFilter(four_groups) - energy_tally = Tally() + energy_tally = Tally(tally_id=6) energy_tally.filters = [energy_filter] energy_tally.scores = ['total'] energyout_filter = EnergyoutFilter(four_groups) - energyout_tally = Tally() + energyout_tally = Tally(tally_id=7) energyout_tally.filters = [energyout_filter] energyout_tally.scores = ['scatter'] - transfer_tally = Tally() + transfer_tally = Tally(tally_id=8) transfer_tally.filters = [energy_filter, energyout_filter] transfer_tally.scores = ['scatter', 'nu-fission'] - material_tally = Tally() + material_tally = Tally(tally_id=9) material_tally.filters = [ MaterialFilter((model.geometry.get_materials_by_name('UOX fuel')[0], model.geometry.get_materials_by_name('Zircaloy')[0], @@ -72,32 +73,56 @@ def test_tallies(): mu_bins = (-1.0, -0.5, 0.0, 0.5, 1.0) mu_filter = MuFilter(mu_bins) - mu_tally1 = Tally() + mu_tally1 = Tally(tally_id=10) mu_tally1.filters = [mu_filter] mu_tally1.scores = ['scatter', 'nu-scatter'] + print('mu_tally1', mu_tally1.id) - mu_tally2 = Tally() + mu_tally2 = Tally(tally_id=11) mu_tally2.filters = [mu_filter, mesh_filter] mu_tally2.scores = ['scatter', 'nu-scatter'] polar_bins = (0.0, 0.6283, 1.2566, 1.8850, 2.5132, 3.14159) polar_filter = PolarFilter(polar_bins) - polar_tally1 = Tally() + polar_tally1 = Tally(tally_id=12) polar_tally1.filters = [polar_filter] polar_tally1.scores = ['flux'] polar_tally1.estimator = 'tracklength' - polar_tally2 = Tally() + polar_tally2 = Tally(tally_id=13) polar_tally2.filters = [polar_filter] polar_tally2.scores = ['flux'] polar_tally2.estimator = 'analog' - polar_tally3 = Tally() + polar_tally3 = Tally(tally_id=14) polar_tally3.filters = [polar_filter, mesh_filter] polar_tally3.scores = ['flux'] polar_tally3.estimator = 'tracklength' - universe_tally = Tally() + legendre_filter = LegendreFilter(order=4) + legendre_tally = Tally(tally_id=15) + legendre_tally.filters = [legendre_filter] + legendre_tally.scores = ['scatter', 'nu-scatter'] + legendre_tally.estimatir = 'analog' + print('legendre_tally', mu_tally1.id) + + harmonics_filter = SphericalHarmonicsFilter(order=4) + harmonics_tally = Tally(tally_id=16) + harmonics_tally.filters = [harmonics_filter] + harmonics_tally.scores = ['scatter', 'nu-scatter', 'flux', 'total'] + harmonics_tally.estimatir = 'analog' + + harmonics_tally2 = Tally(tally_id=17) + harmonics_tally2.filters = [harmonics_filter] + harmonics_tally2.scores = ['flux', 'total'] + harmonics_tally2.estimatir = 'collision' + + harmonics_tally3 = Tally(tally_id=18) + harmonics_tally3.filters = [harmonics_filter] + harmonics_tally3.scores = ['flux', 'total'] + harmonics_tally3.estimatir = 'tracklength' + + universe_tally = Tally(tally_id=19) universe_tally.filters = [ UniverseFilter((model.geometry.get_all_universes()[1], model.geometry.get_all_universes()[2], @@ -107,7 +132,8 @@ def test_tallies(): cell_filter = CellFilter((model.geometry.get_all_cells()[10], model.geometry.get_all_cells()[21], 22, 23, 60)) # Test both Cell objects and ids - score_tallies = [Tally(), Tally(), Tally()] + score_tallies = [Tally(tally_id=20), Tally(tally_id=21), + Tally(tally_id=22)] for t in score_tallies: t.filters = [cell_filter] t.scores = ['absorption', 'delayed-nu-fission', 'events', 'fission', @@ -120,39 +146,24 @@ def test_tallies(): score_tallies[2].estimator = 'collision' cell_filter2 = CellFilter((21, 22, 23, 27, 28, 29, 60)) - flux_tallies = [Tally() for i in range(4)] + flux_tallies = [Tally(tally_id=23 + i) for i in range(3)] for t in flux_tallies: t.filters = [cell_filter2] - flux_tallies[0].scores = ['flux'] - for t in flux_tallies[1:]: - t.scores = ['flux-y5'] - flux_tallies[1].estimator = 'tracklength' - flux_tallies[2].estimator = 'analog' - flux_tallies[3].estimator = 'collision' + t.scores = ['flux'] + flux_tallies[0].estimator = 'tracklength' + flux_tallies[1].estimator = 'analog' + flux_tallies[2].estimator = 'collision' - scatter_tally1 = Tally() - scatter_tally1.filters = [cell_filter] - scatter_tally1.scores = ['scatter', 'scatter-1', 'scatter-2', 'scatter-3', - 'scatter-4', 'nu-scatter', 'nu-scatter-1', - 'nu-scatter-2', 'nu-scatter-3', 'nu-scatter-4'] - - scatter_tally2 = Tally() - scatter_tally2.filters = [cell_filter] - scatter_tally2.scores = ['scatter-p4', 'scatter-y4', 'nu-scatter-p4', - 'nu-scatter-y3'] - - total_tallies = [Tally() for i in range(4)] + total_tallies = [Tally(tally_id=26 + i) for i in range(3)] for t in total_tallies: t.filters = [cell_filter] - total_tallies[0].scores = ['total'] - for t in total_tallies[1:]: - t.scores = ['total-y4'] + t.scores = ['total'] t.nuclides = ['U235', 'total'] - total_tallies[1].estimator = 'tracklength' - total_tallies[2].estimator = 'analog' - total_tallies[3].estimator = 'collision' + total_tallies[0].estimator = 'tracklength' + total_tallies[1].estimator = 'analog' + total_tallies[2].estimator = 'collision' - all_nuclide_tallies = [Tally() for i in range(4)] + all_nuclide_tallies = [Tally(tally_id=29 + i) for i in range(4)] for t in all_nuclide_tallies: t.filters = [cell_filter] t.estimator = 'tracklength' @@ -167,10 +178,10 @@ def test_tallies(): azimuthal_tally1, azimuthal_tally2, azimuthal_tally3, cellborn_tally, dg_tally, energy_tally, energyout_tally, transfer_tally, material_tally, mu_tally1, mu_tally2, - polar_tally1, polar_tally2, polar_tally3, universe_tally] + polar_tally1, polar_tally2, polar_tally3, legendre_tally, + harmonics_tally, harmonics_tally2, harmonics_tally3, universe_tally] model.tallies += score_tallies model.tallies += flux_tallies - model.tallies += (scatter_tally1, scatter_tally2) model.tallies += total_tallies model.tallies += all_nuclide_tallies diff --git a/tests/regression_tests/track_output/test.py b/tests/regression_tests/track_output/test.py index 22eac03bf..a5300a4ae 100644 --- a/tests/regression_tests/track_output/test.py +++ b/tests/regression_tests/track_output/test.py @@ -19,7 +19,7 @@ class TrackTestHarness(TestHarness): def _get_results(self): """Digest info in the statepoint and return as a string.""" # Run the track-to-vtk conversion script. - call(['../../scripts/openmc-track-to-vtk', '-o', 'poly'] + + call(['../../../scripts/openmc-track-to-vtk', '-o', 'poly'] + glob.glob('track_1_1_*.h5')) # Make sure the vtk file was created then return it's contents. From 600969ce2bb726b812b21d8f5bba263f7dc67817 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 29 Apr 2018 06:35:56 -0400 Subject: [PATCH 226/361] Exposed math.F90 to C and then created python wrappers around these functions. Seem to work --- openmc/capi/__init__.py | 1 + openmc/capi/math.py | 263 ++++++++++++++++++++++++++ src/api.F90 | 13 ++ src/distribution_multivariate.F90 | 2 +- src/math.F90 | 211 ++++++++++----------- src/physics.F90 | 13 +- src/physics_mg.F90 | 2 +- src/tallies/tally.F90 | 9 +- src/tallies/tally_filter_sph_harm.F90 | 2 +- 9 files changed, 397 insertions(+), 119 deletions(-) create mode 100644 openmc/capi/math.py diff --git a/openmc/capi/__init__.py b/openmc/capi/__init__.py index 217e782a8..672a6e811 100644 --- a/openmc/capi/__init__.py +++ b/openmc/capi/__init__.py @@ -47,3 +47,4 @@ from .mesh import * from .filter import * from .tally import * from .settings import settings +from .math import * diff --git a/openmc/capi/math.py b/openmc/capi/math.py new file mode 100644 index 000000000..d51858fd3 --- /dev/null +++ b/openmc/capi/math.py @@ -0,0 +1,263 @@ +from ctypes import (c_int, c_double, POINTER) + +import numpy as np +from numpy.ctypeslib import ndpointer + +from . import _dll + +_dll.normal_percentile.restype = c_double +_dll.normal_percentile.argtypes = [POINTER(c_double)] +_dll.t_percentile.restype = c_double +_dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] +_dll.calc_pn.restype = c_double +_dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double)] +_dll.calc_rn.restype = None +_dll.calc_rn.argtypes = [POINTER(c_int), ndpointer(c_double), + ndpointer(c_double)] +_dll.calc_zn.restype = None +_dll.calc_zn.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double), + ndpointer(c_double)] +_dll.evaluate_legendre.restype = c_double +_dll.evaluate_legendre.argtypes = [ndpointer(c_double), POINTER(c_double)] +_dll.rotate_angle.restype = None +_dll.rotate_angle.argtypes = [ndpointer(c_double), POINTER(c_double), + ndpointer(c_double), POINTER(c_double)] +_dll.maxwell_spectrum.restype = c_double +_dll.maxwell_spectrum.argtypes = [POINTER(c_double)] +_dll.watt_spectrum.restype = c_double +_dll.watt_spectrum.argtypes = [POINTER(c_double), POINTER(c_double)] +# COMPLEX DATA TYPES? +# _dll.faddeeva.restype = c_double +# _dll.faddeeva.argtypes = [POINTER(c_double)] + +# _dll.w_derivative.restype = c_double +# _dll.w_derivative.argtypes = [POINTER(c_double)] +_dll.broaden_wmp_polynomials.restype = None +_dll.broaden_wmp_polynomials.argtypes = [POINTER(c_double), POINTER(c_double), + POINTER(c_int), ndpointer(c_double)] + + +def normal_percentile(p): + """ Calculate the percentile of the standard normal distribution with a + specified probability level + + Parameters + ---------- + p : float + Probability level + + Returns + ------- + float + Corresponding z-value + + """ + + return _dll.normal_percentile(c_double(p)) + + +def t_percentile(p, df): + """ Calculate the percentile of the Student's t distribution with a + specified probability level and number of degrees of freedom + + Parameters + ---------- + p : float + Probability level + df : int + Degrees of freedom + + Returns + ------- + float + Corresponding t-value + + """ + + return _dll.t_percentile(c_double(p), c_int(df)) + + +def calc_pn(n, x): + """ Calculate the n-th order Legendre polynomial at the value of x. + + Parameters + ---------- + n : int + Legendre order + x : float + Independent variable to evaluate the Legendre at + + Returns + ------- + float + Corresponding Legendre polynomial result + + """ + + return _dll.calc_pn(c_int(n), c_double(x)) + + +def calc_rn(n, uvw): + """ Calculate the n-th order real Spherical Harmonics for a given angle; + all Rn,m values are provided (where -n <= m <= n). + + Parameters + ---------- + n : int + Harmonics order + uvw : iterable of float + Independent variable to evaluate the Legendre at + + Returns + ------- + numpy.ndarray + Corresponding real harmonics value + + """ + + num_nm = 2 * n + 1 + rn = np.empty(num_nm, dtype=np.float64) + uvw_arr = np.array(uvw, dtype=np.float64) + _dll.calc_rn(c_int(n), uvw_arr, rn) + return rn + + +def calc_zn(n, rho, phi): + """ Calculate the n-th order modified Zernike polynomial moment for a + given angle (rho, theta) location in the unit disk. The normalization of + the polynomials is such that the integral of Z_pq*Z_pq over the unit disk + is exactly pi + + Parameters + ---------- + n : int + Maximum order + rho : float + Radial location in the unit disk + phi : float + Theta (radians) location in the unit disk + + Returns + ------- + numpy.ndarray + Corresponding resulting list of coefficients + + """ + + num_bins = ((n + 1) * (n + 2)) / 2 + zn = np.zeros(num_bins, dtype=np.float64) + _dll.calc_zn(c_int(n), c_double(rho), c_double(phi), zn) + return zn + + +def evaluate_legendre(data, x): + """ Finds the value of f(x) given a set of Legendre coefficients + and the value of x. + + Parameters + ---------- + data : iterable of float + Legendre coefficients + x : float + Independent variable to evaluate the Legendre at + + Returns + ------- + float + Corresponding Legendre expansion result + + """ + + data_arr = np.array(data, dtype=np.float64) + return _dll.evaluate_legendre(data_arr, c_double(x)) + + +def rotate_angle(uvw0, mu, phi=None): + """ Rotates direction cosines through a polar angle whose cosine is + mu and through an azimuthal angle sampled uniformly. + + Parameters + ---------- + uvw0 : iterable of float + Original direction cosine + mu : float + Polar angle cosine to rotate + phi : float, optional + Azimuthal angle; if None, one will be sampled uniformly + + Returns + ------- + numpy.ndarray + Rotated direction cosine + + """ + + uvw = np.zeros(3, dtype=np.float64) + uvw0_arr = np.array(uvw0, dtype=np.float64) + _dll.rotate_angle(uvw0_arr, c_double(mu), uvw, c_double(phi)) + return uvw + + +def maxwell_spectrum(T): + """ Samples an energy from the Maxwell fission distribution based + on a direct sampling scheme. + + Parameters + ---------- + T : float + Spectrum parameter + + Returns + ------- + float + Sampled outgoing energy + + """ + + return _dll.maxwell_spectrum(c_double(T)) + + +def watt_spectrum(a, b): + """ Samples an energy from the Watt energy-dependent fission spectrum. + + Parameters + ---------- + a : float + Spectrum parameter a + b : float + Spectrum parameter b + + Returns + ------- + float + Sampled outgoing energy + + """ + + return _dll.watt_spectrum(c_double(a), c_double(b)) + + +def broaden_wmp_polynomials(E, dopp, n): + """ Doppler broadens the windowed multipole curvefit. The curvefit is a + polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... + + Parameters + ---------- + E : float + Energy to evaluate at + dopp : float + sqrt(atomic weight ratio / kT), with kT given in eV + n : int + Number of components to the polynomial + + Returns + ------- + numpy.ndarray + Resultant leading coefficients + + """ + + factors = np.zeros(n, dtype=np.float64) + _dll.broaden_wmp_polynomials(c_double(E), c_double(dopp), c_int(n), + factors) + return factors diff --git a/src/api.F90 b/src/api.F90 index df183c292..30aca6834 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -12,6 +12,7 @@ module openmc_api use geometry_header use hdf5_interface use material_header + use math use mesh_header use message_passing use nuclide_header @@ -97,6 +98,18 @@ module openmc_api public :: openmc_tally_set_nuclides public :: openmc_tally_set_scores public :: openmc_tally_set_type + public :: normal_percentile + public :: t_percentile + public :: calc_pn + public :: calc_rn + public :: calc_zn + public :: evaluate_legendre + public :: rotate_angle + public :: maxwell_spectrum + public :: watt_spectrum + public :: faddeeva + public :: w_derivative + public :: broaden_wmp_polynomials contains diff --git a/src/distribution_multivariate.F90 b/src/distribution_multivariate.F90 index 650ab26fa..4d7d42cc3 100644 --- a/src/distribution_multivariate.F90 +++ b/src/distribution_multivariate.F90 @@ -119,7 +119,7 @@ contains else ! Sample azimuthal angle phi = this % phi % sample() - uvw(:) = rotate_angle(this % reference_uvw, mu, phi) + call rotate_angle(this % reference_uvw, mu, uvw, phi) end if end function polar_azimuthal_sample diff --git a/src/math.F90 b/src/math.F90 index ee8cd0530..47e6acefb 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -6,6 +6,19 @@ module math use random_lcg, only: prn implicit none + private + public :: normal_percentile + public :: t_percentile + public :: calc_pn + public :: calc_rn + public :: calc_zn + public :: evaluate_legendre + public :: rotate_angle + public :: maxwell_spectrum + public :: watt_spectrum + public :: faddeeva + public :: w_derivative + public :: broaden_wmp_polynomials !=============================================================================== ! FADDEEVA_W evaluates the scaled complementary error function. This @@ -29,24 +42,24 @@ contains ! distribution with a specified probability level !=============================================================================== - elemental function normal_percentile(p) result(z) + pure function normal_percentile(p) result(z) bind(C) - real(8), intent(in) :: p ! probability level - real(8) :: z ! corresponding z-value + real(C_DOUBLE), intent(in) :: p ! probability level + real(C_DOUBLE) :: z ! corresponding z-value - real(8) :: q - real(8) :: r - real(8), parameter :: p_low = 0.02425_8 - real(8), parameter :: a(6) = (/ & + real(C_DOUBLE) :: q + real(C_DOUBLE) :: r + real(C_DOUBLE), parameter :: p_low = 0.02425_8 + real(C_DOUBLE), parameter :: a(6) = (/ & -3.969683028665376e1_8, 2.209460984245205e2_8, -2.759285104469687e2_8, & 1.383577518672690e2_8, -3.066479806614716e1_8, 2.506628277459239e0_8 /) - real(8), parameter :: b(5) = (/ & + real(C_DOUBLE), parameter :: b(5) = (/ & -5.447609879822406e1_8, 1.615858368580409e2_8, -1.556989798598866e2_8, & 6.680131188771972e1_8, -1.328068155288572e1_8 /) - real(8), parameter :: c(6) = (/ & + real(C_DOUBLE), parameter :: c(6) = (/ & -7.784894002430293e-3_8, -3.223964580411365e-1_8, -2.400758277161838_8, & -2.549732539343734_8, 4.374664141464968_8, 2.938163982698783_8 /) - real(8), parameter :: d(4) = (/ & + real(C_DOUBLE), parameter :: d(4) = (/ & 7.784695709041462e-3_8, 3.224671290700398e-1_8, & 2.445134137142996_8, 3.754408661907416_8 /) @@ -88,16 +101,16 @@ contains ! specified probability level and number of degrees of freedom !=============================================================================== - elemental function t_percentile(p, df) result(t) + pure function t_percentile(p, df) result(t) bind(C) - real(8), intent(in) :: p ! probability level - integer, intent(in) :: df ! degrees of freedom - real(8) :: t ! corresponding t-value + real(C_DOUBLE), intent(in) :: p ! probability level + integer(C_INT), intent(in) :: df ! degrees of freedom + real(C_DOUBLE) :: t ! corresponding t-value - real(8) :: n ! degrees of freedom as a real(8) - real(8) :: k ! n - 2 - real(8) :: z ! percentile of normal distribution - real(8) :: z2 ! z * z + real(C_DOUBLE) :: n ! degrees of freedom as a real(8) + real(C_DOUBLE) :: k ! n - 2 + real(C_DOUBLE) :: z ! percentile of normal distribution + real(C_DOUBLE) :: z2 ! z * z if (df == 1) then ! For one degree of freedom, the t-distribution becomes a Cauchy @@ -140,12 +153,14 @@ contains ! the return value will be 1.0. !=============================================================================== - elemental function calc_pn(n,x) result(pnx) + pure function calc_pn(n,x) result(pnx) bind(C) - integer, intent(in) :: n ! Legendre order requested - real(8), intent(in) :: x ! Independent variable the Legendre is to be - ! evaluated at; x must be in the domain [-1,1] - real(8) :: pnx ! The Legendre poly of order n evaluated at x + integer(C_INT), intent(in) :: n ! Legendre order requested + real(C_DOUBLE), intent(in) :: x ! Independent variable the Legendre is to + ! be evaluated at; x must be in the + ! domain [-1,1] + real(C_DOUBLE) :: pnx ! The Legendre poly of order n evaluated + ! at x select case(n) case(1) @@ -185,14 +200,16 @@ contains ! (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) !=============================================================================== - pure function calc_rn(n,uvw) result(rn) + subroutine calc_rn(n, uvw, rn) bind(C) - integer, intent(in) :: n ! Order requested - real(8), intent(in) :: uvw(3) ! Direction of travel, assumed to be on unit sphere - real(8) :: rn(2*n + 1) ! The resultant R_n(uvw) + integer(C_INT), intent(in) :: n ! Order requested + real(C_DOUBLE), intent(in) :: uvw(3) ! Direction of travel; + ! assumed to be on unit sphere + real(C_DOUBLE) :: rn(2*n + 1) ! The resultant R_n(uvw) - real(8) :: phi, w ! Azimuthal and Cosine of Polar angles (from uvw) - real(8) :: w2m1 ! (w^2 - 1), frequently used in these + + real(C_DOUBLE) :: phi, w ! Azimuthal and Cosine of Polar angles (from uvw) + real(C_DOUBLE) :: w2m1 ! (w^2 - 1), frequently used in these w = uvw(3) ! z = cos(polar) if (uvw(1) == ZERO) then @@ -572,7 +589,7 @@ contains rn = ONE end select - end function calc_rn + end subroutine calc_rn !=============================================================================== ! CALC_ZN calculates the n-th order modified Zernike polynomial moment for a @@ -581,31 +598,31 @@ contains ! exactly pi !=============================================================================== - subroutine calc_zn(n, rho, phi, zn) + subroutine calc_zn(n, rho, phi, zn) bind(C) ! This procedure uses the modified Kintner's method for calculating Zernike ! polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, ! R. (2003). A comparative analysis of algorithms for fast computation of ! Zernike moments. Pattern Recognition, 36(3), 731-742. - integer, intent(in) :: n ! Maximum order - real(8), intent(in) :: rho ! Radial location in the unit disk - real(8), intent(in) :: phi ! Theta (radians) location in the unit disk - real(8), intent(out) :: zn(:) ! The resulting list of coefficients + integer(C_INT), intent(in) :: n ! Maximum order + real(C_DOUBLE), intent(in) :: rho ! Radial location in the unit disk + real(C_DOUBLE), intent(in) :: phi ! Theta (radians) location in the unit disk + real(C_DOUBLE), intent(out) :: zn(:) ! The resulting list of coefficients - real(8) :: sin_phi, cos_phi ! Sine and Cosine of phi - real(8) :: sin_phi_vec(n+1) ! Contains sin(n*phi) - real(8) :: cos_phi_vec(n+1) ! Contains cos(n*phi) - real(8) :: zn_mat(n+1, n+1) ! Matrix form of the coefficients which is - ! easier to work with - real(8) :: k1, k2, k3, k4 ! Variables for R_m_n calculation - real(8) :: sqrt_norm ! normalization for radial moments - integer :: i,p,q ! Loop counters + real(C_DOUBLE) :: sin_phi, cos_phi ! Sine and Cosine of phi + real(C_DOUBLE) :: sin_phi_vec(n+1) ! Contains sin(n*phi) + real(C_DOUBLE) :: cos_phi_vec(n+1) ! Contains cos(n*phi) + real(C_DOUBLE) :: zn_mat(n+1, n+1) ! Matrix form of the coefficients which is + ! easier to work with + real(C_DOUBLE) :: k1, k2, k3, k4 ! Variables for R_m_n calculation + real(C_DOUBLE) :: sqrt_norm ! normalization for radial moments + integer(C_INT) :: i,p,q ! Loop counters - real(8), parameter :: SQRT_N_1(0:10) = [& + real(C_DOUBLE), parameter :: SQRT_N_1(0:10) = [& sqrt(1.0_8), sqrt(2.0_8), sqrt(3.0_8), sqrt(4.0_8), & sqrt(5.0_8), sqrt(6.0_8), sqrt(7.0_8), sqrt(8.0_8), & sqrt(9.0_8), sqrt(10.0_8), sqrt(11.0_8)] - real(8), parameter :: SQRT_2N_2(0:10) = SQRT_N_1*sqrt(2.0_8) + real(C_DOUBLE), parameter :: SQRT_2N_2(0:10) = SQRT_N_1*sqrt(2.0_8) ! n == radial degree ! m == azimuthal frequency @@ -681,41 +698,17 @@ contains end do end subroutine calc_zn -!=============================================================================== -! EXPAND_HARMONIC expands a given series of real spherical harmonics -!=============================================================================== - - pure function expand_harmonic(data, order, uvw) result(val) - real(8), intent(in) :: data(:) - integer, intent(in) :: order - real(8), intent(in) :: uvw(3) - real(8) :: val - - integer :: l, lm_lo, lm_hi - - val = data(1) - lm_lo = 2 - lm_hi = 4 - do l = 1, order - 1 - val = val + sqrt(TWO * real(l,8) + ONE) * & - dot_product(calc_rn(l,uvw), data(lm_lo:lm_hi)) - lm_lo = lm_hi + 1 - lm_hi = lm_lo + 2 * (l + 1) - end do - - end function expand_harmonic - !=============================================================================== ! EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients ! and the value of x !=============================================================================== - pure function evaluate_legendre(data, x) result(val) - real(8), intent(in) :: data(:) - real(8), intent(in) :: x - real(8) :: val + pure function evaluate_legendre(data, x) result(val) bind(C) + real(C_DOUBLE), intent(in) :: data(:) + real(C_DOUBLE), intent(in) :: x + real(C_DOUBLE) :: val - integer :: l + integer(C_INT) :: l val = HALF * data(1) do l = 1, size(data) - 1 @@ -730,20 +723,20 @@ contains ! with direct sampling rather than rejection as is done in MCNP and SERPENT. !=============================================================================== - function rotate_angle(uvw0, mu, phi) result(uvw) - real(8), intent(in) :: uvw0(3) ! directional cosine - real(8), intent(in) :: mu ! cosine of angle in lab or CM - real(8), optional :: phi ! azimuthal angle - real(8) :: uvw(3) ! rotated directional cosine + subroutine rotate_angle(uvw0, mu, uvw, phi) bind(C) + real(C_DOUBLE), intent(in) :: uvw0(3) ! directional cosine + real(C_DOUBLE), intent(in) :: mu ! cosine of angle in lab or CM + real(C_DOUBLE), intent(out) :: uvw(3) ! rotated directional cosine + real(C_DOUBLE), optional :: phi ! azimuthal angle - real(8) :: phi_ ! azimuthal angle - real(8) :: sinphi ! sine of azimuthal angle - real(8) :: cosphi ! cosine of azimuthal angle - real(8) :: a ! sqrt(1 - mu^2) - real(8) :: b ! sqrt(1 - w^2) - real(8) :: u0 ! original cosine in x direction - real(8) :: v0 ! original cosine in y direction - real(8) :: w0 ! original cosine in z direction + real(C_DOUBLE) :: phi_ ! azimuthal angle + real(C_DOUBLE) :: sinphi ! sine of azimuthal angle + real(C_DOUBLE) :: cosphi ! cosine of azimuthal angle + real(C_DOUBLE) :: a ! sqrt(1 - mu^2) + real(C_DOUBLE) :: b ! sqrt(1 - w^2) + real(C_DOUBLE) :: u0 ! original cosine in x direction + real(C_DOUBLE) :: v0 ! original cosine in y direction + real(C_DOUBLE) :: w0 ! original cosine in z direction ! Copy original directional cosines u0 = uvw0(1) @@ -776,7 +769,7 @@ contains uvw(3) = mu*w0 + a*(v0*w0*cosphi - u0*sinphi)/b end if - end function rotate_angle + end subroutine rotate_angle !=============================================================================== ! MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution based @@ -785,13 +778,13 @@ contains ! be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. !=============================================================================== - function maxwell_spectrum(T) result(E_out) + function maxwell_spectrum(T) result(E_out) bind(C) - real(8), intent(in) :: T ! tabulated function of incoming E - real(8) :: E_out ! sampled energy + real(C_DOUBLE), intent(in) :: T ! tabulated function of incoming E + real(C_DOUBLE) :: E_out ! sampled energy - real(8) :: r1, r2, r3 ! random numbers - real(8) :: c ! cosine of pi/2*r3 + real(C_DOUBLE) :: r1, r2, r3 ! random numbers + real(C_DOUBLE) :: c ! cosine of pi/2*r3 r1 = prn() r2 = prn() @@ -813,13 +806,13 @@ contains ! original Watt spectrum derivation (See F. Brown's MC lectures). !=============================================================================== - function watt_spectrum(a, b) result(E_out) + function watt_spectrum(a, b) result(E_out) bind(C) - real(8), intent(in) :: a ! Watt parameter a - real(8), intent(in) :: b ! Watt parameter b - real(8) :: E_out ! energy of emitted neutron + real(C_DOUBLE), intent(in) :: a ! Watt parameter a + real(C_DOUBLE), intent(in) :: b ! Watt parameter b + real(C_DOUBLE) :: E_out ! energy of emitted neutron - real(8) :: w ! sampled from Maxwellian + real(C_DOUBLE) :: w ! sampled from Maxwellian w = maxwell_spectrum(a) E_out = w + a*a*b/4. + (TWO*prn() - ONE)*sqrt(a*a*b*w) @@ -830,9 +823,9 @@ contains ! FADDEEVA the Faddeeva function, using Stephen Johnson's implementation !=============================================================================== - function faddeeva(z) result(wv) - complex(C_DOUBLE_COMPLEX), intent(in) :: z ! The point to evaluate Z at - complex(8) :: wv ! The resulting w(z) value + function faddeeva(z) result(wv) bind(C) + complex(C_DOUBLE_COMPLEX), intent(in) :: z ! The point to evaluate Z at + complex(C_DOUBLE_COMPLEX) :: wv ! The resulting w(z) value real(C_DOUBLE) :: relerr ! Target relative error in inner loop of MIT ! Faddeeva @@ -860,10 +853,10 @@ contains end function faddeeva - recursive function w_derivative(z, order) result(wv) + recursive function w_derivative(z, order) result(wv) bind(C) complex(C_DOUBLE_COMPLEX), intent(in) :: z ! The point to evaluate Z at - integer, intent(in) :: order - complex(8) :: wv ! The resulting w(z) value + integer(C_INT), intent(in) :: order + complex(C_DOUBLE_COMPLEX) :: wv ! The resulting w(z) value select case(order) case (0) @@ -882,12 +875,12 @@ contains ! a/E + b/sqrt(E) + c + d sqrt(E) ... !=============================================================================== - subroutine broaden_wmp_polynomials(E, dopp, n, factors) - real(8), intent(in) :: E ! Energy to evaluate at - real(8), intent(in) :: dopp ! sqrt(atomic weight ratio / kT), + subroutine broaden_wmp_polynomials(E, dopp, n, factors) bind(C) + real(C_DOUBLE), intent(in) :: E ! Energy to evaluate at + real(C_DOUBLE), intent(in) :: dopp ! sqrt(atomic weight ratio / kT), ! kT given in eV. - integer, intent(in) :: n ! number of components to polynomial - real(8), intent(out):: factors(n) ! output leading coefficient + integer(C_INT), intent(in) :: n ! number of components to polynomial + real(C_DOUBLE), intent(out):: factors(n) ! output leading coefficient integer :: i diff --git a/src/physics.F90 b/src/physics.F90 index b614b8185..8183435cb 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -495,7 +495,8 @@ contains ! Rotate neutron velocity vector to new angle -- note that the speed of the ! neutron in CM does not change in elastic scattering. However, the speed ! will change when we convert back to LAB - v_n = vel * rotate_angle(uvw_cm, mu_cm) + call rotate_angle(uvw_cm, mu_cm, v_n) + v_n = vel * v_n ! Transform back to LAB frame v_n = v_n + v_cm @@ -785,7 +786,7 @@ contains if (abs(mu) > ONE) mu = sign(ONE,mu) ! change direction of particle - uvw = rotate_angle(uvw, mu) + call rotate_angle(uvw, mu, uvw) end subroutine sab_scatter @@ -976,7 +977,8 @@ contains if (abs(mu) < ONE) then ! set and accept target velocity E_t = E_t / awr - v_target = sqrt(E_t) * rotate_angle(uvw, mu) + call rotate_angle(uvw, mu, v_target) + v_target = sqrt(E_t) * v_target exit ARES_REJECT_LOOP end if end do ARES_REJECT_LOOP @@ -1056,7 +1058,8 @@ contains ! Determine velocity vector of target nucleus based on neutron's velocity ! and the sampled angle between them - v_target = vt * rotate_angle(uvw, mu) + call rotate_angle(uvw, mu, v_target) + v_target = vt * v_target end subroutine sample_cxs_target_velocity @@ -1327,7 +1330,7 @@ contains p % mu = mu ! change direction of particle - p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, mu) + call rotate_angle(p % coord(1) % uvw, mu, p % coord(1) % uvw) ! evaluate yield yield = rxn % products(1) % yield % evaluate(E_in) diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index eb8208594..05493af05 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -151,7 +151,7 @@ contains p % E = energy_bin_avg(p % g) ! Convert change in angle (mu) to new direction - p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, p % mu) + call rotate_angle(p % coord(1) % uvw, p % mu, p % coord(1) % uvw) ! Set event component p % event = EVENT_SCATTER diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index f4d12be7e..f7d3c8a08 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -46,6 +46,9 @@ module tally end subroutine score_analog_tally_ end interface + real(C_DOUBLE) :: rn(2 * MAX_ANG_ORDER + 1) +!$omp threadprivate(rn) + contains !=============================================================================== @@ -2131,11 +2134,12 @@ contains num_nm = 2 * n + 1 ! multiply score by the angular flux moments and store + call calc_rn(n, p % last_uvw, rn(1:num_nm)) !$omp critical (score_general_scatt_yn) t % results(RESULT_VALUE, score_index: score_index + num_nm - 1, & filter_index) = t % results(RESULT_VALUE, & score_index: score_index + num_nm - 1, filter_index) & - + score * calc_pn(n, p % mu) * calc_rn(n, p % last_uvw) + + score * calc_pn(n, p % mu) * rn(1:num_nm) !$omp end critical (score_general_scatt_yn) end do i = i + (t % moment_order(i) + 1)**2 - 1 @@ -2159,11 +2163,12 @@ contains num_nm = 2 * n + 1 ! multiply score by the angular flux moments and store + call calc_rn(n, uvw, rn(1:num_nm)) !$omp critical (score_general_flux_tot_yn) t % results(RESULT_VALUE, score_index: score_index + num_nm - 1, & filter_index) = t % results(RESULT_VALUE, & score_index: score_index + num_nm - 1, filter_index) & - + score * calc_rn(n, uvw) + + score * rn(1:num_nm) !$omp end critical (score_general_flux_tot_yn) end do i = i + (t % moment_order(i) + 1)**2 - 1 diff --git a/src/tallies/tally_filter_sph_harm.F90 b/src/tallies/tally_filter_sph_harm.F90 index 5c47c2c71..5faf2b6fd 100644 --- a/src/tallies/tally_filter_sph_harm.F90 +++ b/src/tallies/tally_filter_sph_harm.F90 @@ -91,7 +91,7 @@ contains ! Calculate n-th order spherical harmonics for (u,v,w) num_nm = 2*n + 1 - rn(1:num_nm) = calc_rn(n, p % last_uvw) + call calc_rn(n, p % last_uvw, rn(1:num_nm)) ! Append matching (bin,weight) for each moment do i = 1, num_nm From 735c59650b78a791b90145db69b94fa5e0d6cba5 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 29 Apr 2018 15:39:14 -0400 Subject: [PATCH 227/361] Adjust cell universe indices from C++ --- src/cell.cpp | 22 ++++++++++++++++++++-- src/cell.h | 3 ++- src/input_xml.F90 | 19 ++++++++----------- src/summary.F90 | 3 --- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index 136f36e06..ed16a566a 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -294,8 +294,8 @@ Cell::to_hdf5(hid_t cell_group) const write_string(cell_group, "name", name); } - //TODO: Lookup universe id in universe_dict - //write_int(cell_group, "universe", universe); + //TODO: Fix the off-by-one indexing. + write_int(cell_group, "universe", universes_c[universe-1]->id); // Write the region specification. if (!region.empty()) { @@ -428,6 +428,24 @@ read_cells(pugi::xml_node *node) } } +extern "C" void +adjust_indices_c() +{ + // Change cell.universe values from IDs to indices. + for (Cell *c : cells_c) { + auto it = universe_dict.find(c->universe); + if (it != universe_dict.end()) { + //TODO: Remove this off-by-one indexing. + c->universe = it->second + 1; + } else { + std::stringstream err_msg; + err_msg << "Could not find universe " << c->universe + << " specified on cell " << c->id; + fatal_error(err_msg); + } + } +} + //============================================================================== // Fortran compatibility functions //============================================================================== diff --git a/src/cell.h b/src/cell.h index 8b540be35..99ca2caf4 100644 --- a/src/cell.h +++ b/src/cell.h @@ -73,7 +73,8 @@ public: //! Simple cells can be evaluated with short circuit evaluation, i.e., as soon //! as we know that one half-space is not satisfied, we can exit. This //! provides a performance benefit for the common case. In - //! contains_complex, we evaluate the RPN expression using a stack, similar to //! how a RPN calculator would work. + //! contains_complex, we evaluate the RPN expression using a stack, similar to + //! how a RPN calculator would work. //! @param xyz[3] The 3D Cartesian coordinate to check. //! @param uvw[3] A direction used to "break ties" the coordinates are very //! close to a surface. diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 78dd0bb87..6457f587c 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -49,6 +49,11 @@ module input_xml save interface + subroutine adjust_indices_c() bind(C) + use ISO_C_BINDING + implicit none + end subroutine adjust_indices_c + subroutine read_surfaces(node_ptr) bind(C) use ISO_C_BINDING implicit none @@ -4263,18 +4268,10 @@ contains integer :: id ! user-specified id class(Lattice), pointer :: lat => null() - do i = 1, n_cells - ! ======================================================================= - ! ADJUST UNIVERSE INDEX FOR EACH CELL - associate (c => cells(i)) + call adjust_indices_c() - id = c % universe() - if (universe_dict % has(id)) then - call c % set_universe(universe_dict % get(id)) - else - call fatal_error("Could not find universe " // trim(to_str(id)) & - &// " specified on cell " // trim(to_str(c % id()))) - end if + do i = 1, n_cells + associate (c => cells(i)) ! ======================================================================= ! ADJUST MATERIAL/FILL POINTERS FOR EACH CELL diff --git a/src/summary.F90 b/src/summary.F90 index 59bb90447..e2ef1a2c1 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -148,9 +148,6 @@ contains call c % to_hdf5(cell_group) - ! Write universe for this cell - call write_dataset(cell_group, "universe", universes(c%universe())%id) - ! Write information on what fills this cell select case (c%type) case (FILL_MATERIAL) From d4c4e612ea65e6de09ed276f66e990bdfb9a1dd6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Sun, 29 Apr 2018 19:09:22 -0500 Subject: [PATCH 228/361] Address @smharper comments on #996 --- src/error.F90 | 8 ++++++++ src/error.h | 18 +++++++++++++++--- src/hdf5_interface.cpp | 28 ++++++++++++++-------------- src/hdf5_interface.h | 27 +++++++++------------------ src/initialize.cpp | 7 +++++-- src/main.cpp | 28 ++++++++++++++-------------- 6 files changed, 65 insertions(+), 51 deletions(-) diff --git a/src/error.F90 b/src/error.F90 index f4fb34173..d041051d2 100644 --- a/src/error.F90 +++ b/src/error.F90 @@ -111,6 +111,14 @@ contains end subroutine warning + subroutine warning_from_c(message, message_len) bind(C) + 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 warning(message_out) + end subroutine + !=============================================================================== ! FATAL_ERROR alerts the user that an error has been encountered and displays a ! message about the particular problem. Errors are considered 'fatal' and hence diff --git a/src/error.h b/src/error.h index 91c272745..45d8bcded 100644 --- a/src/error.h +++ b/src/error.h @@ -9,7 +9,8 @@ namespace openmc { -extern "C" void fatal_error_from_c(const char *message, int message_len); +extern "C" void fatal_error_from_c(const char* message, int message_len); +extern "C" void warning_from_c(const char* message, int message_len); inline @@ -27,8 +28,19 @@ void fatal_error(const std::string &message) inline void fatal_error(const std::stringstream &message) { - std::string out {message.str()}; - fatal_error_from_c(out.c_str(), out.length()); + fatal_error(message.str()); +} + +inline +void warning(const std::string& message) +{ + warning_from_c(message.c_str(), message.length()); +} + +inline +void warning(const std::stringstream& message) +{ + warning(message.str()); } } // namespace openmc diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index e4db2ee58..8a8391bb5 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -122,20 +122,20 @@ file_open(const char* filename, char mode, bool parallel) bool create; unsigned int flags; switch (mode) { - case 'r': - case 'a': - create = false; - flags = (mode == 'r' ? H5F_ACC_RDONLY : H5F_ACC_RDWR); - break; - case 'w': - case 'x': - create = true; - flags = (mode == 'x' ? H5F_ACC_EXCL : H5F_ACC_TRUNC); - break; - default: - std::stringstream err_msg; - err_msg << "Invalid file mode: " << mode; - fatal_error(err_msg); + case 'r': + case 'a': + create = false; + flags = (mode == 'r' ? H5F_ACC_RDONLY : H5F_ACC_RDWR); + break; + case 'w': + case 'x': + create = true; + flags = (mode == 'x' ? H5F_ACC_EXCL : H5F_ACC_TRUNC); + break; + default: + std::stringstream err_msg; + err_msg << "Invalid file mode: " << mode; + fatal_error(err_msg); } hid_t plist = H5P_DEFAULT; diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 9a8b3c569..e38a31e99 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -35,24 +35,6 @@ extern "C" hid_t open_dataset(hid_t group_id, const char* name); extern "C" hid_t open_group(hid_t group_id, const char* name); bool using_mpio_device(hid_t obj_id); - -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 read_attr(hid_t obj_id, const char* name, hid_t mem_type_id, const void* buffer); extern "C" void read_attr_double(hid_t obj_id, const char* name, double* buffer); @@ -102,5 +84,14 @@ void write_string(hid_t group_id, const char* name, const std::string& buffer, b extern "C" void write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, const double* results); +template void +write_double_1D(hid_t group_id, char const *name, + std::array &buffer) +{ + hsize_t dims[1] {array_len}; + write_dataset(group_id, 1, dims, name, H5T_NATIVE_DOUBLE, + buffer.data(), false); +} + } // namespace openmc #endif //HDF5_INTERFACE_H diff --git a/src/initialize.cpp b/src/initialize.cpp index c7c37e31d..728f43695 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -102,8 +102,8 @@ void initialize_mpi(MPI_Comm intracomm) inline bool ends_with(std::string const& value, std::string const& ending) { - if (ending.size() > value.size()) return false; - return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); } @@ -194,6 +194,9 @@ parse_command_line(int argc, char* argv[]) return OPENMC_E_INVALID_ARGUMENT; } omp_set_num_threads(openmc_n_threads); +#else + if (openmc_master) + warning("Ignoring number of threads specified on command line."); #endif } else if (arg == "-?" || arg == "-h" || arg == "--help") { diff --git a/src/main.cpp b/src/main.cpp index 6d0cf1f26..8d8f39e24 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,24 +19,24 @@ int main(int argc, char* argv[]) { // This happens for the -h and -v flags return 0; } else if (err) { - openmc::fatal_error(openmc_err_msg); + openmc::fatal_error(openmc_err_msg); } // start problem based on mode switch (openmc_run_mode) { - case RUN_MODE_FIXEDSOURCE: - case RUN_MODE_EIGENVALUE: - err = openmc_run(); - break; - case RUN_MODE_PLOTTING: - err = openmc_plot_geometry(); - break; - case RUN_MODE_PARTICLE: - if (openmc_master) err = openmc_particle_restart(); - break; - case RUN_MODE_VOLUME: - err = openmc_calculate_volumes(); - break; + case RUN_MODE_FIXEDSOURCE: + case RUN_MODE_EIGENVALUE: + err = openmc_run(); + break; + case RUN_MODE_PLOTTING: + err = openmc_plot_geometry(); + break; + case RUN_MODE_PARTICLE: + if (openmc_master) err = openmc_particle_restart(); + break; + case RUN_MODE_VOLUME: + err = openmc_calculate_volumes(); + break; } if (err) openmc::fatal_error(openmc_err_msg); From 98009a268d1a1d735822baa23170de0c8a8e6ceb Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 29 Apr 2018 20:23:41 -0400 Subject: [PATCH 229/361] Remove Fortran lattice % universes --- src/cell.cpp | 19 +++ src/cell.h | 11 +- src/constants.h | 3 +- src/geometry.F90 | 22 ++-- src/geometry_header.F90 | 19 ++- src/input_xml.F90 | 145 ++--------------------- src/lattice.cpp | 66 +++++++++++ src/lattice.h | 23 ++-- src/summary.F90 | 4 +- src/tallies/tally_filter_distribcell.F90 | 8 +- 10 files changed, 152 insertions(+), 168 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index ed16a566a..56d7c45ca 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -8,6 +8,7 @@ #include "constants.h" #include "error.h" #include "hdf5_interface.h" +#include "lattice.h" #include "surface.h" #include "xml_interface.h" @@ -29,6 +30,19 @@ constexpr int32_t OP_UNION {std::numeric_limits::max() - 4}; extern "C" double FP_PRECISION; +//============================================================================== +// Global variables +//============================================================================== + +// Braces force n_cells to be defined here, not just declared. +extern "C" {int32_t n_cells {0};} + +std::vector cells_c; +std::map cell_dict; + +std::vector universes_c; +std::map universe_dict; + //============================================================================== //! Convert region specification string to integer tokens. //! @@ -444,6 +458,11 @@ adjust_indices_c() fatal_error(err_msg); } } + + // Change all lattice universe values from IDs to indices. + for (Lattice *l : lattices_c) { + l->adjust_indices(); + } } //============================================================================== diff --git a/src/cell.h b/src/cell.h index 99ca2caf4..5c06d8120 100644 --- a/src/cell.h +++ b/src/cell.h @@ -16,16 +16,15 @@ namespace openmc { // Global variables //============================================================================== -// Braces force n_cells to be defined here, not just declared. -extern "C" {int32_t n_cells {0};} +extern "C" int32_t n_cells; class Cell; -std::vector cells_c; -std::map cell_dict; +extern std::vector cells_c; +extern std::map cell_dict; class Universe; -std::vector universes_c; -std::map universe_dict; +extern std::vector universes_c; +extern std::map universe_dict; //============================================================================== //! A geometry primitive that fills all space and contains cells. diff --git a/src/constants.h b/src/constants.h index b725809fe..8cc837a73 100644 --- a/src/constants.h +++ b/src/constants.h @@ -5,7 +5,8 @@ namespace openmc{ extern "C" double FP_COINCIDENT; -constexpr double INFTY{std::numeric_limits::max()}; +extern "C" double FP_PRECISION; +constexpr double INFTY {std::numeric_limits::max()}; constexpr int C_NONE {-1}; } // namespace openmc diff --git a/src/geometry.F90 b/src/geometry.F90 index fff18d6c8..3fd5859f7 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -254,7 +254,7 @@ contains if (lat % are_valid_indices(i_xyz)) then ! Particle is inside the lattice. p % coord(j + 1) % universe = & - lat % universes(i_xyz(1), i_xyz(2), i_xyz(3)) + lat % get([i_xyz(1)-1, i_xyz(2)-1, i_xyz(3)-1]) + 1 else ! Particle is outside the lattice. @@ -331,7 +331,8 @@ contains else OUTSIDE_LAT ! Find cell in next lattice element - p % coord(j) % universe = lat % universes(i_xyz(1), i_xyz(2), i_xyz(3)) + p % coord(j) % universe = & + lat % get([i_xyz(1)-1, i_xyz(2)-1, i_xyz(3)-1]) + 1 call find_cell(p, found) if (.not. found) then @@ -591,7 +592,7 @@ contains do k = 1, lat % n_cells(2) do m = 1, lat % n_cells(3) lat % offset(map, j, k, m) = offset - next_univ => universes(lat % universes(j, k, m)) + next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) offset = offset + & count_target(next_univ, counts, found, univ_id, map) end do @@ -612,7 +613,7 @@ contains cycle else lat % offset(map, j, k, m) = offset - next_univ => universes(lat % universes(j, k, m)) + next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) offset = offset + & count_target(next_univ, counts, found, univ_id, map) end if @@ -707,7 +708,7 @@ contains do j = 1, lat % n_cells(1) do k = 1, lat % n_cells(2) do m = 1, lat % n_cells(3) - next_univ => universes(lat % universes(j, k, m)) + next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) ! Found target - stop since target cannot contain itself if (next_univ % id == univ_id) then @@ -735,7 +736,7 @@ contains else if (j + k > 3*lat % n_rings - 1) then cycle else - next_univ => universes(lat % universes(j, k, m)) + next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) ! Found target - stop since target cannot contain itself if (next_univ % id == univ_id) then @@ -799,7 +800,7 @@ contains do j = 1, lat % n_cells(1) do k = 1, lat % n_cells(2) do m = 1, lat % n_cells(3) - call count_instance(universes(lat % universes(j, k, m))) + call count_instance(universes(lat % get([j-1, k-1, m-1])+1)) end do end do end do @@ -817,7 +818,8 @@ contains else if (j + k > 3*lat % n_rings - 1) then cycle else - call count_instance(universes(lat % universes(j, k, m))) + call count_instance(universes(lat % get([j-1, k-1, m-1]) & + + 1)) end if end do end do @@ -874,7 +876,7 @@ contains do j = 1, lat % n_cells(1) do k = 1, lat % n_cells(2) do m = 1, lat % n_cells(3) - next_univ => universes(lat % universes(j, k, m)) + next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) levels_below = max(levels_below, maximum_levels(next_univ)) end do end do @@ -893,7 +895,7 @@ contains else if (j + k > 3*lat % n_rings - 1) then cycle else - next_univ => universes(lat % universes(j, k, m)) + next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) levels_below = max(levels_below, maximum_levels(next_univ)) end if end do diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index af63cad7f..ddd9e4c2d 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -148,6 +148,15 @@ module geometry_header implicit none integer(C_INT32_T), intent(in), value :: n end subroutine extend_cells_c + + function lattice_universe_c(lat_ptr, i_xyz) & + bind(C, name='lattice_universe') result(univ) + use ISO_C_BINDING + implicit none + type(C_PTR), intent(in), value :: lat_ptr + integer(C_INT), intent(in) :: i_xyz(3) + integer(C_INT32_T) :: univ + end function lattice_universe_c end interface !=============================================================================== @@ -170,9 +179,7 @@ module geometry_header type, abstract :: Lattice type(C_PTR) :: ptr - !integer :: id ! Universe number for lattice real(8), allocatable :: pitch(:) ! Pitch along each axis - integer, allocatable :: universes(:,:,:) ! Specified universes integer :: outside ! Material to fill area outside integer :: outer ! universe to tile outside the lat logical :: is_3d ! Lattice has cells on z axis @@ -182,6 +189,7 @@ module geometry_header procedure :: id => lattice_id procedure :: are_valid_indices => lattice_are_valid_indices procedure :: distance => lattice_distance + procedure :: get => lattice_get procedure :: get_indices => lattice_get_indices procedure :: get_local_xyz => lattice_get_local_xyz procedure :: to_hdf5 => lattice_to_hdf5 @@ -297,6 +305,13 @@ contains call lattice_distance_c(this % ptr, xyz, uvw, i_xyz, d, lattice_trans) end subroutine lattice_distance + function lattice_get(this, i_xyz) result(univ) + class(Lattice), intent(in) :: this + integer(C_INT), intent(in) :: i_xyz(3) + integer(C_INT32_T) :: univ + univ = lattice_universe_c(this % ptr, i_xyz) + end function lattice_get + function lattice_get_indices(this, xyz) result(i_xyz) class(Lattice), intent(in) :: this real(C_DOUBLE), intent(in) :: xyz(3) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 6457f587c..a3bc6b826 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1331,7 +1331,6 @@ contains n_x = lat % n_cells(1) n_y = lat % n_cells(2) n_z = lat % n_cells(3) - allocate(lat % universes(n_x, n_y, n_z)) ! Check that number of universes matches size n = node_word_count(node_lat, "universes") @@ -1340,21 +1339,16 @@ contains &size of lattice " // trim(to_str(lat % id())) // ".") end if - allocate(temp_int_array(n)) - call get_node_array(node_lat, "universes", temp_int_array) - ! Read universes - do m = 1, n_z + do m = 0, n_z-1 do k = 0, n_y - 1 - do j = 1, n_x - lat % universes(j, n_y - k, m) = & - temp_int_array(j + n_x*k + n_x*n_y*(m-1)) - if (find(fill_univ_ids, lat % universes(j, n_y - k, m)) == -1) & - call fill_univ_ids % push_back(lat % universes(j, n_y - k, m)) + do j = 0, n_x - 1 + univ_id = lat % get([j, k, m]) + if (find(fill_univ_ids, univ_id) == -1) & + call fill_univ_ids % push_back(univ_id) end do end do end do - deallocate(temp_int_array) ! Read outer universe for area outside lattice. lat % outer = NO_OUTER_UNIVERSE @@ -1427,7 +1421,6 @@ contains ! Copy number of dimensions n_rings = lat % n_rings n_z = lat % n_axial - allocate(lat % universes(2*n_rings - 1, 2*n_rings - 1, n_z)) ! Check that number of universes matches size n = node_word_count(node_lat, "universes") @@ -1440,91 +1433,15 @@ contains call get_node_array(node_lat, "universes", temp_int_array) ! Read universes - ! Universes in hexagonal lattices are stored in a manner that represents - ! a skewed coordinate system: (x, alpha) rather than (x, y). There is - ! no obvious, direct relationship between the order of universes in the - ! input and the order that they will be stored in the skewed array so - ! the following code walks a set of index values across the skewed array - ! in a manner that matches the input order. Note that i_x = 0, i_a = 0 - ! corresponds to the center of the hexagonal lattice. - - input_index = 1 - do m = 1, n_z - ! Initialize lattice indecies. - i_x = 1 - i_a = n_rings - 1 - - ! Map upper triangular region of hexagonal lattice. - do k = 1, n_rings-1 - ! Walk index to lower-left neighbor of last row start. - i_x = i_x - 1 - do j = 1, k - ! Place universe in array. - lat % universes(i_x + n_rings, i_a + n_rings, m) = & - temp_int_array(input_index) - if (find(fill_univ_ids, temp_int_array(input_index)) == -1) & - call fill_univ_ids % push_back(temp_int_array(input_index)) - ! Walk index to closest non-adjacent right neighbor. - i_x = i_x + 2 - i_a = i_a - 1 - ! Increment XML array index. - input_index = input_index + 1 + do m = 0, n_z-1 + do k = 0, 2*n_rings-2 + do j = 0, 2*n_rings-2 + univ_id = lat % get([j, k, m]) + if (find(fill_univ_ids, univ_id) == -1) & + call fill_univ_ids % push_back(univ_id) end do - ! Return lattice index to start of current row. - i_x = i_x - 2*k - i_a = i_a + k - end do - - ! Map middle square region of hexagonal lattice. - do k = 1, 2*n_rings - 1 - if (mod(k, 2) == 1) then - ! Walk index to lower-left neighbor of last row start. - i_x = i_x - 1 - else - ! Walk index to lower-right neighbor of last row start - i_x = i_x + 1 - i_a = i_a - 1 - end if - do j = 1, n_rings - mod(k-1, 2) - ! Place universe in array. - lat % universes(i_x + n_rings, i_a + n_rings, m) = & - temp_int_array(input_index) - if (find(fill_univ_ids, temp_int_array(input_index)) == -1) & - call fill_univ_ids % push_back(temp_int_array(input_index)) - ! Walk index to closest non-adjacent right neighbor. - i_x = i_x + 2 - i_a = i_a - 1 - ! Increment XML array index. - input_index = input_index + 1 - end do - ! Return lattice index to start of current row. - i_x = i_x - 2*(n_rings - mod(k-1, 2)) - i_a = i_a + n_rings - mod(k-1, 2) - end do - - ! Map lower triangular region of hexagonal lattice. - do k = 1, n_rings-1 - ! Walk index to lower-right neighbor of last row start. - i_x = i_x + 1 - i_a = i_a - 1 - do j = 1, n_rings - k - ! Place universe in array. - lat % universes(i_x + n_rings, i_a + n_rings, m) = & - temp_int_array(input_index) - if (find(fill_univ_ids, temp_int_array(input_index)) == -1) & - call fill_univ_ids % push_back(temp_int_array(input_index)) - ! Walk index to closest non-adjacent right neighbor. - i_x = i_x + 2 - i_a = i_a - 1 - ! Increment XML array index. - input_index = input_index + 1 - end do - ! Return lattice index to start of current row. - i_x = i_x - 2*(n_rings - k) - i_a = i_a + n_rings - k end do end do - deallocate(temp_int_array) ! Read outer universe for area outside lattice. lat % outer = NO_OUTER_UNIVERSE @@ -4312,46 +4229,6 @@ contains do i = 1, n_lattices lat => lattices(i) % obj - select type (lat) - - type is (RectLattice) - do m = 1, lat % n_cells(3) - do k = 1, lat % n_cells(2) - do j = 1, lat % n_cells(1) - id = lat % universes(j,k,m) - if (universe_dict % has(id)) then - lat % universes(j,k,m) = universe_dict % get(id) - else - call fatal_error("Invalid universe number " & - &// trim(to_str(id)) // " specified on lattice " & - &// trim(to_str(lat % id()))) - end if - end do - end do - end do - - type is (HexLattice) - do m = 1, lat % n_axial - do k = 1, 2*lat % n_rings - 1 - do j = 1, 2*lat % n_rings - 1 - if (j + k < lat % n_rings + 1) then - cycle - else if (j + k > 3*lat % n_rings - 1) then - cycle - end if - id = lat % universes(j, k, m) - if (universe_dict % has(id)) then - lat % universes(j, k, m) = universe_dict % get(id) - else - call fatal_error("Invalid universe number " & - &// trim(to_str(id)) // " specified on lattice " & - &// trim(to_str(lat % id()))) - end if - end do - end do - end do - - end select if (lat % outer /= NO_OUTER_UNIVERSE) then if (universe_dict % has(lat % outer)) then diff --git a/src/lattice.cpp b/src/lattice.cpp index 2902e3e47..9a716f76b 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -4,6 +4,8 @@ #include #include +#include "cell.h" +#include "constants.h" #include "error.h" #include "hdf5_interface.h" #include "xml_interface.h" @@ -131,6 +133,36 @@ RectLattice::RectLattice(pugi::xml_node lat_node) //============================================================================== +int32_t& +RectLattice::operator[](const int i_xyz[3]) +{ + int nx = n_cells[0]; + int ny = n_cells[1]; + int nz = n_cells[2]; + int indx = nx*ny*i_xyz[2] + nx*i_xyz[1] + i_xyz[0]; + return universes[indx]; +} + +//============================================================================== + +void +RectLattice::adjust_indices() +{ + int nx = n_cells[0]; + int ny = n_cells[1]; + int nz = n_cells[2]; + for (int iz = 0; iz < nz; iz++) { + for (int iy = 0; iy < ny; iy++) { + for (int ix = 0; ix < nx; ix++) { + int indx = nx*ny*iz + nx*iy + ix; + universes[indx] = universe_dict[universes[indx]]; + } + } + } +} + +//============================================================================== + bool RectLattice::are_valid_indices(const int i_xyz[3]) const { @@ -386,6 +418,37 @@ HexLattice::HexLattice(pugi::xml_node lat_node) //============================================================================== +int32_t& +HexLattice::operator[](const int i_xyz[3]) +{ + int indx = (2*n_rings-1)*(2*n_rings-1) * i_xyz[2] + + (2*n_rings-1) * i_xyz[1] + + i_xyz[0]; + return universes[indx]; +} + +//============================================================================== + +void +HexLattice::adjust_indices() +{ + for (int iz = 0; iz < n_axial; iz++) { + for (int ia = 0; ia < 2*n_rings-1; ia++) { + for (int ix = 0; ix < 2*n_rings-1; ix++) { + int i_xyz[3] {ix+1, ia+1, iz+1}; + if (are_valid_indices(i_xyz)) { + int indx = (2*n_rings-1)*(2*n_rings-1) * iz + + (2*n_rings-1) * ia + + ix; + universes[indx] = universe_dict[universes[indx]]; + } + } + } + } +} + +//============================================================================== + bool HexLattice::are_valid_indices(const int i_xyz[3]) const { @@ -655,6 +718,9 @@ extern "C" { } void lattice_to_hdf5(Lattice *lat, hid_t group) {lat->to_hdf5(group);} + + int32_t lattice_universe(Lattice *lat, const int i_xyz[3]) + {return (*lat)[i_xyz];} } } // namespace openmc diff --git a/src/lattice.h b/src/lattice.h index 9ade739c0..b21dc5604 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -15,15 +15,7 @@ namespace openmc { //============================================================================== -// Constants that should eventually be moved out of this file -//============================================================================== - -extern "C" double FP_PRECISION; -constexpr double INFTY{std::numeric_limits::max()}; -constexpr int C_NONE {-1}; - -//============================================================================== -// Constants +// Module constants //============================================================================== constexpr int32_t NO_OUTER_UNIVERSE{-1}; @@ -58,6 +50,11 @@ public: virtual ~Lattice() {} + virtual int32_t& operator[](const int i_xyz[3]) = 0; + + //! Convert internal universe values from IDs to indices using universe_dict. + virtual void adjust_indices() = 0; + //! Check lattice indices. //! @param i_xyz[3] The indices for a lattice tile. //! @return true if the given indices fit within the lattice bounds. False @@ -104,6 +101,10 @@ public: virtual ~RectLattice() {} + int32_t& operator[](const int i_xyz[3]); + + void adjust_indices(); + bool are_valid_indices(const int i_xyz[3]) const; std::pair> @@ -127,6 +128,10 @@ public: virtual ~HexLattice() {} + int32_t& operator[](const int i_xyz[3]); + + void adjust_indices(); + bool are_valid_indices(const int i_xyz[3]) const; std::pair> diff --git a/src/summary.F90 b/src/summary.F90 index e2ef1a2c1..834b2661a 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -288,7 +288,7 @@ contains do k = 0, lat%n_cells(2) - 1 do m = 1, lat%n_cells(3) lattice_universes(j, k+1, m) = & - universes(lat%universes(j, lat%n_cells(2) - k, m))%id + universes(lat%get([j-1, lat%n_cells(2)-k-1, m-1])+1)%id end do end do end do @@ -319,7 +319,7 @@ contains lattice_universes(j,k,m) = -1 cycle end if - lattice_universes(j,k,m) = universes(lat%universes(j,k,m))%id + lattice_universes(j,k,m) = universes(lat%get([j-1,k-1,m-1])+1)%id end do end do end do diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index e7a9645ae..39f8db5af 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -289,7 +289,7 @@ contains ! This is last lattice cell, so target must be here lat_offset = lat % offset(map, k, l, m) offset = offset + lat_offset - next_univ => universes(lat % universes(k, l, m)) + next_univ => universes(lat % get([k-1, l-1, m-1])+1) if (lat % is_3d) then path = trim(path) // "(" // trim(to_str(k-1)) // & "," // trim(to_str(l-1)) // "," // & @@ -310,7 +310,7 @@ contains ! Target is at this lattice position lat_offset = lat % offset(map, old_k, old_l, old_m) offset = offset + lat_offset - next_univ => universes(lat % universes(old_k, old_l, old_m)) + next_univ => universes(lat % get([old_k-1, old_l-1, old_m-1])+1) if (lat % is_3d) then path = trim(path) // "(" // trim(to_str(old_k-1)) // & "," // trim(to_str(old_l-1)) // "," // & @@ -359,7 +359,7 @@ contains ! This is last lattice cell, so target must be here lat_offset = lat % offset(map, k, l, m) offset = offset + lat_offset - next_univ => universes(lat % universes(k, l, m)) + next_univ => universes(lat % get([k-1, l-1, m-1])+1) if (lat % is_3d) then path = trim(path) // "(" // & trim(to_str(k - lat % n_rings)) // "," // & @@ -382,7 +382,7 @@ contains ! Target is at this lattice position lat_offset = lat % offset(map, old_k, old_l, old_m) offset = offset + lat_offset - next_univ => universes(lat % universes(old_k, old_l, old_m)) + next_univ => universes(lat % get([old_k-1, old_l-1, old_m-1])+1) if (lat % is_3d) then path = trim(path) // "(" // & trim(to_str(old_k - lat % n_rings)) // "," // & From f217e2fc746061e4c09857a1f2da91cb9c32777e Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 29 Apr 2018 21:21:39 -0400 Subject: [PATCH 230/361] Clean up lattice code --- src/geometry.F90 | 71 +++++++++++++---------------------------- src/geometry_header.F90 | 1 - src/hdf5_interface.h | 2 +- src/input_xml.F90 | 46 +------------------------- src/lattice.cpp | 28 ++++++++++++++++ src/lattice.h | 6 ++++ src/summary.F90 | 1 - 7 files changed, 58 insertions(+), 97 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index 3fd5859f7..a98ae8b95 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -605,13 +605,7 @@ contains do m = 1, lat % n_axial do k = 1, 2*lat % n_rings - 1 do j = 1, 2*lat % n_rings - 1 - ! This array location is never used - if (j + k < lat % n_rings + 1) then - cycle - ! This array location is never used - else if (j + k > 3*lat % n_rings - 1) then - cycle - else + if (lat % are_valid_indices([j, k, m])) then lat % offset(map, j, k, m) = offset next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) offset = offset + & @@ -729,13 +723,7 @@ contains do m = 1, lat % n_axial do k = 1, 2*lat % n_rings - 1 do j = 1, 2*lat % n_rings - 1 - ! This array location is never used - if (j + k < lat % n_rings + 1) then - cycle - ! This array location is never used - else if (j + k > 3*lat % n_rings - 1) then - cycle - else + if (lat % are_valid_indices([j, k, m])) then next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) ! Found target - stop since target cannot contain itself @@ -811,13 +799,7 @@ contains do m = 1, lat % n_axial do k = 1, 2*lat % n_rings - 1 do j = 1, 2*lat % n_rings - 1 - ! This array location is never used - if (j + k < lat % n_rings + 1) then - cycle - ! This array location is never used - else if (j + k > 3*lat % n_rings - 1) then - cycle - else + if (lat % are_valid_indices([j, k, m])) then call count_instance(universes(lat % get([j-1, k-1, m-1]) & + 1)) end if @@ -844,6 +826,7 @@ contains integer :: levels ! maximum number of levels for this universe integer :: i ! index over cells + integer :: nx, ny, nz ! lattice shape integer :: j, k, m ! indices in lattice integer :: levels_below ! max levels below this universe type(Cell), pointer :: c ! pointer to current cell @@ -871,39 +854,29 @@ contains select type (lat) type is (RectLattice) - - ! Loop over lattice coordinates - do j = 1, lat % n_cells(1) - do k = 1, lat % n_cells(2) - do m = 1, lat % n_cells(3) - next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) - levels_below = max(levels_below, maximum_levels(next_univ)) - end do - end do - end do + nx = lat % n_cells(1) + ny = lat % n_cells(2) + nz = lat % n_cells(3) type is (HexLattice) - - ! Loop over lattice coordinates - do m = 1, lat % n_axial - do k = 1, 2*lat % n_rings - 1 - do j = 1, 2*lat % n_rings - 1 - ! This array location is never used - if (j + k < lat % n_rings + 1) then - cycle - ! This array location is never used - else if (j + k > 3*lat % n_rings - 1) then - cycle - else - next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) - levels_below = max(levels_below, maximum_levels(next_univ)) - end if - end do - end do - end do + nx = 2 * lat % n_rings - 1 + ny = 2 * lat % n_rings - 1 + nz = lat % n_axial end select + ! Loop over lattice coordinates + do j = 1, nx + do k = 1, ny + do m = 1, nz + if (lat % are_valid_indices([j, k, m])) then + next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) + levels_below = max(levels_below, maximum_levels(next_univ)) + end if + end do + end do + end do + end if end do diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index ddd9e4c2d..117d3feaf 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -179,7 +179,6 @@ module geometry_header type, abstract :: Lattice type(C_PTR) :: ptr - real(8), allocatable :: pitch(:) ! Pitch along each axis integer :: outside ! Material to fill area outside integer :: outer ! universe to tile outside the lat logical :: is_3d ! Lattice has cells on z axis diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index f5a302855..0b2e030c4 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -75,7 +75,7 @@ write_int(hid_t group_id, char const *name, int32_t buffer) template inline void write_double_1D(hid_t group_id, char const *name, - std::array &buffer) + const std::array &buffer) { hsize_t dims[1]{array_len}; hid_t dataspace = H5Screate_simple(1, dims, NULL); diff --git a/src/input_xml.F90 b/src/input_xml.F90 index a3bc6b826..26e2ff0a3 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -931,12 +931,11 @@ contains subroutine read_geometry_xml() - integer :: i, j, k, m, i_x, i_a, input_index + integer :: i, j, k, m integer :: n, n_mats, n_x, n_y, n_z, n_rings, n_rlats, n_hlats integer :: id integer :: univ_id integer :: n_cells_in_univ - integer, allocatable :: temp_int_array(:) real(8) :: phi, theta, psi logical :: file_exists logical :: boundary_exists @@ -1295,31 +1294,6 @@ contains allocate(lat % lower_left(n)) call get_node_array(node_lat, "lower_left", lat % lower_left) - ! Read lattice pitches. - ! TODO: Remove this deprecation warning in a future release. - if (check_for_node(node_lat, "width")) then - call warning("The use of 'width' is deprecated and will be disallowed & - &in a future release. Use 'pitch' instead. The utility openmc/& - &src/utils/update_inputs.py can be used to automatically update & - &geometry.xml files.") - if (node_word_count(node_lat, "width") /= n) then - call fatal_error("Number of entries on must be the same as & - &the number of entries on .") - end if - - else if (node_word_count(node_lat, "pitch") /= n) then - call fatal_error("Number of entries on must be the same as & - &the number of entries on .") - end if - - allocate(lat % pitch(n)) - ! TODO: Remove the 'width' code in a future release. - if (check_for_node(node_lat, "width")) then - call get_node_array(node_lat, "width", lat % pitch) - else - call get_node_array(node_lat, "pitch", lat % pitch) - end if - ! TODO: Remove deprecation warning in a future release. if (check_for_node(node_lat, "type")) then call warning("The use of 'type' is no longer needed. The utility & @@ -1405,19 +1379,6 @@ contains allocate(lat % center(n)) call get_node_array(node_lat, "center", lat % center) - ! Read lattice pitches - n = node_word_count(node_lat, "pitch") - if (lat % is_3d .and. n /= 2) then - call fatal_error("A hexagonal lattice with must have & - &specified by 2 numbers.") - else if ((.not. lat % is_3d) .and. n /= 1) then - call fatal_error("A hexagonal lattice without must have & - & specified by 1 number.") - end if - - allocate(lat % pitch(n)) - call get_node_array(node_lat, "pitch", lat % pitch) - ! Copy number of dimensions n_rings = lat % n_rings n_z = lat % n_axial @@ -1429,9 +1390,6 @@ contains &size of lattice " // trim(to_str(lat % id())) // ".") end if - allocate(temp_int_array(n)) - call get_node_array(node_lat, "universes", temp_int_array) - ! Read universes do m = 0, n_z-1 do k = 0, 2*n_rings-2 @@ -4179,8 +4137,6 @@ contains integer :: i ! index for various purposes integer :: j ! index for various purposes - integer :: k ! loop index for lattices - integer :: m ! loop index for lattices integer :: lid ! lattice IDs integer :: id ! user-specified id class(Lattice), pointer :: lat => null() diff --git a/src/lattice.cpp b/src/lattice.cpp index 9a716f76b..cda5dff37 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -55,6 +55,8 @@ Lattice::to_hdf5(hid_t lat_group) const if (!name.empty()) { write_string(lat_group, "name", name); } + + to_hdf5_inner(lat_group); } //============================================================================== @@ -265,6 +267,19 @@ RectLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const return local_xyz; } +//============================================================================== + +void +RectLattice::to_hdf5_inner(hid_t lat_group) const +{ + if (is_3d) { + write_double_1D(lat_group, "pitch", pitch); + } else { + std::array out {{pitch[0], pitch[1]}}; + write_double_1D(lat_group, "pitch", out); + } +} + //============================================================================== // HexLattice implementation //============================================================================== @@ -650,6 +665,19 @@ HexLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const return local_xyz; } +//============================================================================== + +void +HexLattice::to_hdf5_inner(hid_t lat_group) const +{ + if (is_3d) { + write_double_1D(lat_group, "pitch", pitch); + } else { + std::array out {{pitch[0]}}; + write_double_1D(lat_group, "pitch", out); + } +} + //============================================================================== // Non-method functions //============================================================================== diff --git a/src/lattice.h b/src/lattice.h index b21dc5604..78d83a2cb 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -89,6 +89,8 @@ public: protected: bool is_3d; //! Has divisions along the z-axis + + virtual void to_hdf5_inner(hid_t group_id) const = 0; }; //============================================================================== @@ -115,6 +117,8 @@ public: std::array get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const; + void to_hdf5_inner(hid_t group_id) const; + protected: std::array n_cells; //! Number of cells along each axis std::array lower_left; //! Global lower-left corner of the lattice @@ -142,6 +146,8 @@ public: std::array get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const; + void to_hdf5_inner(hid_t group_id) const; + protected: int n_rings; //! Number of radial tile positions int n_axial; //! Number of axial tile positions diff --git a/src/summary.F90 b/src/summary.F90 index 834b2661a..160f3bfbd 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -260,7 +260,6 @@ contains call lat % to_hdf5(lattice_group) ! Write name, pitch, and outer universe - call write_dataset(lattice_group, "pitch", lat%pitch) if (lat % outer > 0) then call write_dataset(lattice_group, "outer", universes(lat % outer) % id) else From 7fb5449ca11207d9547ab4a13955b0636212866d Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 30 Apr 2018 08:54:29 -0400 Subject: [PATCH 231/361] implemented python interface and tests of math.F90 --- openmc/capi/math.py | 13 ++- src/math.F90 | 9 +- src/mgxs_header.F90 | 5 +- src/scattdata_header.F90 | 3 +- tests/unit_tests/test_math.py | 180 ++++++++++++++++++++++++++++++++++ 5 files changed, 200 insertions(+), 10 deletions(-) create mode 100644 tests/unit_tests/test_math.py diff --git a/openmc/capi/math.py b/openmc/capi/math.py index d51858fd3..701fc697f 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -1,4 +1,4 @@ -from ctypes import (c_int, c_double, POINTER) +from ctypes import (c_int, c_double, POINTER, c_void_p) import numpy as np from numpy.ctypeslib import ndpointer @@ -18,7 +18,8 @@ _dll.calc_zn.restype = None _dll.calc_zn.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double), ndpointer(c_double)] _dll.evaluate_legendre.restype = c_double -_dll.evaluate_legendre.argtypes = [ndpointer(c_double), POINTER(c_double)] +_dll.evaluate_legendre.argtypes = [POINTER(c_int), POINTER(c_double), + POINTER(c_double)] _dll.rotate_angle.restype = None _dll.rotate_angle.argtypes = [ndpointer(c_double), POINTER(c_double), ndpointer(c_double), POINTER(c_double)] @@ -169,7 +170,9 @@ def evaluate_legendre(data, x): """ data_arr = np.array(data, dtype=np.float64) - return _dll.evaluate_legendre(data_arr, c_double(x)) + return _dll.evaluate_legendre(c_int(len(data)), + data_arr.ctypes.data_as(POINTER(c_double)), + c_double(x)) def rotate_angle(uvw0, mu, phi=None): @@ -194,7 +197,9 @@ def rotate_angle(uvw0, mu, phi=None): uvw = np.zeros(3, dtype=np.float64) uvw0_arr = np.array(uvw0, dtype=np.float64) - _dll.rotate_angle(uvw0_arr, c_double(mu), uvw, c_double(phi)) + + _dll.rotate_angle(uvw0_arr, c_double(mu), + uvw, c_double(phi)) return uvw diff --git a/src/math.F90 b/src/math.F90 index 47e6acefb..313fe94f1 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -703,16 +703,17 @@ contains ! and the value of x !=============================================================================== - pure function evaluate_legendre(data, x) result(val) bind(C) - real(C_DOUBLE), intent(in) :: data(:) + pure function evaluate_legendre(n, data, x) result(val) bind(C) + integer(C_INT), intent(in) :: n + real(C_DOUBLE), intent(in) :: data(n) real(C_DOUBLE), intent(in) :: x real(C_DOUBLE) :: val integer(C_INT) :: l val = HALF * data(1) - do l = 1, size(data) - 1 - val = val + (real(l,8) + HALF) * data(l + 1) * calc_pn(l,x) + do l = 1, n - 1 + val = val + (real(l, 8) + HALF) * data(l + 1) * calc_pn(l,x) end do end function evaluate_legendre diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index 6eabefae3..9ffb83815 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -1097,7 +1097,9 @@ contains end if scatt_coeffs(gin) % data(imu, gout) = & - evaluate_legendre(input_scatt(gin) % data(:, gout), mu) + evaluate_legendre( & + size(input_scatt(gin) % data, dim=1), & + input_scatt(gin) % data(:, gout), mu) ! Ensure positivity of distribution if (scatt_coeffs(gin) % data(imu, gout) < ZERO) & @@ -2079,6 +2081,7 @@ contains scatt_coeffs(gin, iazi, ipol) % data(imu, gout) = & evaluate_legendre(& + size(input_scatt(gin, iazi, ipol) % data, dim=1), & input_scatt(gin, iazi, ipol) % data(:, gout), mu) ! Ensure positivity of distribution diff --git a/src/scattdata_header.F90 b/src/scattdata_header.F90 index 511e2a237..a3108a2df 100644 --- a/src/scattdata_header.F90 +++ b/src/scattdata_header.F90 @@ -445,7 +445,8 @@ contains if (gout < this % gmin(gin) .or. gout > this % gmax(gin)) then f = ZERO else - f = evaluate_legendre(this % dist(gin) % data(:, gout), mu) + f = evaluate_legendre(size(this % dist(gin) % data, dim=1), & + this % dist(gin) % data(:, gout), mu) end if end function scattdatalegendre_calc_f diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py new file mode 100644 index 000000000..17c20fdb5 --- /dev/null +++ b/tests/unit_tests/test_math.py @@ -0,0 +1,180 @@ +import numpy as np +import scipy as sp + +import openmc +import openmc.capi + + +def test_normal_percentile(): + # normal_percentile has three branches to consider: + # p < 0.02425; 0.02425 <= p <= 0.97575; and p > 0.97575 + test_ps = [0.02, 0.4, 0.5, 0.6, 0.98] + + # The reference solutions come from Scipy + ref_zs = [sp.stats.norm.ppf(p) for p in test_ps] + + test_zs = [openmc.capi.math.normal_percentile(p) for p in test_ps] + + assert np.allclose(ref_zs, test_zs) + + +def test_t_percentile(): + # Permutations include 1 DoF, 2 DoF, and > 2 DoF + # We will test 5 p-values at 3-DoF values + test_ps = [0.02, 0.4, 0.5, 0.6, 0.98] + test_dfs = [1, 2, 5] + + # The reference solutions come from Scipy + ref_ts = [[sp.stats.t.ppf(p, df) for p in test_ps] for df in test_dfs] + + test_ts = [[openmc.capi.math.t_percentile(p, df) for p in test_ps] + for df in test_dfs] + + # The 5 DoF approximation in openmc.capi.math.t_percentile is off by up to + # 8e-3 from the scipy solution, so test that one separately with looser + # tolerance + assert np.allclose(ref_ts[:-1], test_ts[:-1]) + assert np.allclose(ref_ts[-1], test_ts[-1], atol=1e-2) + + +def test_calc_pn(): + max_order = 10 + test_ns = np.array([i for i in range(0, max_order + 1)]) + test_xs = np.linspace(-1., 1., num=5, endpoint=True) + + # Reference solutions from scipy + ref_vals = [sp.special.eval_legendre(n, test_xs) for n in test_ns] + + test_vals = [[openmc.capi.math.calc_pn(n, x) for x in test_xs] + for n in test_ns] + + assert np.allclose(ref_vals, test_vals) + + +def test_calc_rn(): + max_order = 10 + test_ns = np.array([i for i in range(0, max_order + 1)]) + azi = 0.1 # Longitude + pol = 0.2 # Latitude + test_uvw = np.array([np.sin(pol) * np.cos(azi), + np.sin(pol) * np.sin(azi), + np.cos(pol)]) + + # Reference solutions from the equations + ref_vals = [] + + def coeff(n, m): + return np.sqrt((2. * n + 1) * sp.special.factorial(n - m) / + (sp.special.factorial(n + m))) + + def pnm_bar(n, m, mu): + val = coeff(n, m) + if m != 0: + val *= np.sqrt(2.) + val *= sp.special.lpmv([m], [n], [mu]) + return val[0] + + ref_vals = [] + for n in test_ns: + for m in range(-n, n + 1): + if m < 0: + ylm = pnm_bar(n, np.abs(m), np.cos(pol)) * \ + np.sin(np.abs(m) * azi) + else: + ylm = pnm_bar(n, m, np.cos(pol)) * np.cos(m * azi) + + # Un-normalize for comparison + ylm /= np.sqrt(2. * n + 1.) + ref_vals.append(ylm) + + test_vals = [] + for n in test_ns: + ylms = openmc.capi.math.calc_rn(n, test_uvw) + test_vals.extend(ylms.tolist()) + + assert np.allclose(ref_vals, test_vals) + + +def test_calc_zn(): + pass + + +def test_evaluate_legendre(): + max_order = 10 + # Coefficients are set to 1, but will incorporate the (2l+1)/2 norm factor + # for the reference solution + test_coeffs = [0.5 * (2. * l + 1.) for l in range(max_order + 1)] + test_xs = np.linspace(-1., 1., num=5, endpoint=True) + + ref_vals = np.polynomial.legendre.legval(test_xs, test_coeffs) + + # Set the coefficients back to 1s for the test values + test_coeffs = [1. for l in range(max_order + 1)] + test_vals = np.array([openmc.capi.math.evaluate_legendre(test_coeffs, x) + for x in test_xs]) + + assert np.allclose(ref_vals, test_vals) + + +def test_rotate_angle(): + uvw0 = np.array([1., 0., 0.]) + phi = 0. + mu = 0. + + # reference: mu of 0 pulls the vector the bottom, so: + ref_uvw = np.array([0., 0., -1.]) + + test_uvw = openmc.capi.math.rotate_angle(uvw0, mu, phi) + + assert np.allclose(ref_uvw, test_uvw) + + # Repeat for mu = 1 (no change) + mu = 1. + ref_uvw = np.array([1., 0., 0.]) + + test_uvw = openmc.capi.math.rotate_angle(uvw0, mu, phi) + + assert np.allclose(ref_uvw, test_uvw) + + # Need to test phi=None somehow... + + +def test_maxwell_spectrum(): + settings = openmc.capi.settings + settings.seed = 1 + T = 0.5 + ref_val = 0.6129982175261098 + test_val = openmc.capi.math.maxwell_spectrum(T) + print(test_val) + assert np.isclose(ref_val, test_val) + + +def test_watt_spectrum(): + settings = openmc.capi.settings + settings.seed = 1 + a = 0.5 + b = 0.75 + ref_val = 0.6247242713640233 + test_val = openmc.capi.math.watt_spectrum(a, b) + print(test_val) + assert np.isclose(ref_val, test_val) + + +def test_broaden_wmp_polynomials(): + # Two branches of the code to worry about, beta > 6 and otherwise + # beta = sqrtE * dopp + # First lets do beta > 6 + test_E = 0.5 + test_dopp = 100. # approximately U235 at room temperature + n = 4 + ref_val = [2., 1.41421356, 1.0001, 0.70731891] + test_val = openmc.capi.math.broaden_wmp_polynomials(test_E, test_dopp, n) + + assert np.allclose(ref_val, test_val) + + # now beta < 6 + test_dopp = 5. + ref_val = [1.99999885, 1.41421356, 1.04, 0.79195959] + test_val = openmc.capi.math.broaden_wmp_polynomials(test_E, test_dopp, n) + + assert np.allclose(ref_val, test_val) From 8e61254e6b5bc5281cfc8f151a35e81c842fb6a7 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 30 Apr 2018 15:33:36 -0400 Subject: [PATCH 232/361] Added C++ math functions, now to incorporate them one by one.... --- CMakeLists.txt | 2 + openmc/capi/math.py | 2 +- src/math.F90 | 87 +++++ src/math_functions.cpp | 781 +++++++++++++++++++++++++++++++++++++++++ src/math_functions.h | 94 +++++ 5 files changed, 965 insertions(+), 1 deletion(-) create mode 100644 src/math_functions.cpp create mode 100644 src/math_functions.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1797ae5d3..9f72d5b05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -438,6 +438,8 @@ set(LIBOPENMC_FORTRAN_SRC set(LIBOPENMC_CXX_SRC src/error.h src/hdf5_interface.h + src/math_functions.h + src/math_functions.cpp src/random_lcg.cpp src/random_lcg.h src/surface.cpp diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 701fc697f..713ef9813 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -1,4 +1,4 @@ -from ctypes import (c_int, c_double, POINTER, c_void_p) +from ctypes import (c_int, c_double, POINTER) import numpy as np from numpy.ctypeslib import ndpointer diff --git a/src/math.F90 b/src/math.F90 index 313fe94f1..76e9fc11a 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -26,6 +26,93 @@ module math !=============================================================================== interface + + pure function t_percentile_cc(p, df) bind(C, name='t_percentile_c') & + result(t) + use ISO_C_BINDING + implicit none + real(C_DOUBLE), value, intent(in) :: p + integer(C_INT), value, intent(in) :: df + real(C_DOUBLE) :: t + end function t_percentile_cc + + pure function calc_pn_cc(n, x) bind(C, name='calc_pn_c') result(pnx) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), value, intent(in) :: x + real(C_DOUBLE) :: pnx + end function calc_pn_cc + + subroutine calc_rn_cc(n, uvw, rn) bind(C, name='calc_rn_c') + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), intent(in) :: uvw(3) + real(C_DOUBLE), intent(in) :: rn(2 * n + 1) + end subroutine calc_rn_cc + + pure function evaluate_legendre_cc(n, data, x) & + bind(C, name='evaluate_legendre_c') result(val) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), intent(in) :: data(n) + real(C_DOUBLE), value, intent(in) :: x + real(C_DOUBLE) :: val + end function evaluate_legendre_cc + + subroutine rotate_angle_cc(uvw, mu, phi) bind(C, name='rotate_angle_c') + use ISO_C_BINDING + implicit none + real(C_DOUBLE), intent(inout) :: uvw(3) + real(C_DOUBLE), value, intent(in) :: mu + real(C_DOUBLE), value, intent(in) :: phi + end subroutine rotate_angle_cc + + function maxwell_spectrum_cc(T) bind(C, name='maxwell_spectrum_c') & + result(E_out) + use ISO_C_BINDING + implicit none + real(C_DOUBLE), value, intent(in) :: T + real(C_DOUBLE) :: E_out + end function maxwell_spectrum_cc + + function watt_spectrum_cc(a, b) bind(C, name='watt_spectrum_c') & + result(E_out) + use ISO_C_BINDING + implicit none + real(C_DOUBLE), value, intent(in) :: a + real(C_DOUBLE), value, intent(in) :: b + real(C_DOUBLE) :: E_out + end function watt_spectrum_cc + + function faddeeva_cc(z) bind(C, name='faddeeva_c') result(wv) + use ISO_C_BINDING + implicit none + complex(C_DOUBLE_COMPLEX), value, intent(in) :: z + complex(C_DOUBLE_COMPLEX) :: wv + end function faddeeva_cc + + function w_derivative_cc(z, order) bind(C, name='w_derivative_c') & + result(wv) + use ISO_C_BINDING + implicit none + complex(C_DOUBLE_COMPLEX), value, intent(in) :: z + integer(C_INT), value, intent(in) :: order + complex(C_DOUBLE_COMPLEX) :: wv + end function w_derivative_cc + + subroutine broaden_wmp_polynomials_cc(E, dopp, n, factors) & + bind(C, name='broaden_wmp_polynomials_c') + use ISO_C_BINDING + implicit none + real(C_DOUBLE), value, intent(in) :: E + real(C_DOUBLE), value, intent(in) :: dopp + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), intent(inout) :: factors(n) + end subroutine broaden_wmp_polynomials_cc + function faddeeva_w(z, relerr) bind(C, name='Faddeeva_w') result(w) use ISO_C_BINDING implicit none diff --git a/src/math_functions.cpp b/src/math_functions.cpp new file mode 100644 index 000000000..3b645c772 --- /dev/null +++ b/src/math_functions.cpp @@ -0,0 +1,781 @@ +#include "math_functions.h" + +namespace openmc { + + +//============================================================================== +// NORMAL_PERCENTILE calculates the percentile of the standard normal +// distribution with a specified probability level +//============================================================================== + +double __attribute__ ((const)) normal_percentile_c(double p) { + + // return gsl_cdf_ugaussian_Pinv(p); + + double z; + double q; + double r; + const double p_low = 0.02425; + const double a[6] = {-3.969683028665376e1, 2.209460984245205e2, + -2.759285104469687e2, 1.383577518672690e2, + -3.066479806614716e1, 2.506628277459239e0}; + const double b[5] = {-5.447609879822406e1, 1.615858368580409e2, + -1.556989798598866e2, 6.680131188771972e1, + -1.328068155288572e1}; + const double c[6] = {-7.784894002430293e-3, -3.223964580411365e-1, + -2.400758277161838, -2.549732539343734, + 4.374664141464968, 2.938163982698783}; + const double d[4] = {7.784695709041462e-3, 3.224671290700398e-1, + 2.445134137142996, 3.754408661907416}; + + // The rational approximation used here is from an unpublished work at + // http://home.online.no/~pjacklam/notes/invnorm/ + + if (p < p_low) { + // Rational approximation for lower region. + + q = std::sqrt(-2.0 * std::log(p)); + z = (((((c[0]*q + c[1])*q + c[2])*q + c[3])*q + c[4])*q + c[5]) / + ((((d[0]*q + d[1])*q + d[2])*q + d[3])*q + 1.0); + + } else if (p <= 1.0 - p_low) { + // Rational approximation for central region + + q = p - 0.5; + r = q * q; + z = (((((a[0]*r + a[1])*r + a[2])*r + a[3])*r + a[4])*r + a[5])*q / + (((((b[0]*r + b[1])*r + b[2])*r + b[3])*r + b[4])*r + 1.0); + + } else { + // Rational approximation for upper region + + q = std::sqrt(-2.0*std::log(1.0 - p)); + z = -(((((c[0]*q + c[1])*q + c[2])*q + c[3])*q + c[4])*q + c[5]) / + ((((d[0]*q + d[1])*q + d[2])*q + d[3])*q + 1.0); + } + + // Refinement based on Newton's method + + z = z - (0.5 * std::erfc(-z / std::sqrt(2.0)) - p) * std::sqrt(2.0 * M_PI) * + std::exp(0.5 * z * z); + + return z; + +} + +//============================================================================== +// T_PERCENTILE calculates the percentile of the Student's t distribution with +// a specified probability level and number of degrees of freedom +//============================================================================== + +double __attribute__ ((const)) t_percentile_c(double p, int df){ + + // return gsl_cdf_tdist_Pinv(p, static_cast df); + + double t; + double n; + double k; + double z; + double z2; + + if (df == 1) { + // For one degree of freedom, the t-distribution becomes a Cauchy + // distribution whose cdf we can invert directly + + t = std::tan(M_PI*(p - 0.5)); + } else if (df == 2) { + // For two degrees of freedom, the cdf is given by 1/2 + x/(2*sqrt(x^2 + + // 2)). This can be directly inverted to yield the solution below + + t = 2.0 * std::sqrt(2.0)*(p - 0.5) / + std::sqrt(1. - 4. * std::pow(p - 0.5, 2.)); + } else { + // This approximation is from E. Olusegun George and Meenakshi Sivaram, "A + // modification of the Fisher-Cornish approximation for the student t + // percentiles," Communication in Statistics - Simulation and Computation, + // 16 (4), pp. 1123-1132 (1987). + + n = static_cast(df); + k = 1. / (n - 2.); + z = normal_percentile_c(p); + z2 = z * z; + t = std::sqrt(n * k) * (z + (z2 - 3.) * z * k / 4. + ((5. * z2 - 56.) * z2 + + 75.) * z * k * k / 96. + (((z2 - 27.) * 3. * z2 + 417.) * z2 - 315.) * + z * k * k * k / 384.); + } + + return t; +} + +//============================================================================== +// CALC_PN calculates the n-th order Legendre polynomial at the value of x. +//============================================================================== + +double __attribute__ ((const)) calc_pn_c(int n, double x) { + + // return gsl_sf_legendre_Pl(l, x); + + double pnx; + + switch(n) { + case 0: + pnx = 1.; + break; + case 1: + pnx = x; + break; + case 2: + pnx = 1.5 * x * x - 0.5; + break; + case 3: + pnx = 2.5 * x * x * x - 1.5 * x; + break; + case 4: + pnx = 4.375 * std::pow(x, 4.) - 3.75 * x * x + 0.375; + break; + case 5: + pnx = 7.875 * std::pow(x, 5.) - 8.75 * x * x * x + 1.875 * x; + break; + case 6: + pnx = 14.4375 * std::pow(x, 6.) - 19.6875 * std::pow(x, 4.) + + 6.5625 * x * x - 0.3125; + break; + case 7: + pnx = 26.8125 * std::pow(x, 7.) - 43.3125 * std::pow(x, 5.) + + 19.6875 * x * x * x - 2.1875 * x; + break; + case 8: + pnx = 50.2734375 * std::pow(x, 8.) - 93.84375 * std::pow(x, 6.) + + 54.140625 * std::pow(x, 4.) - 9.84375 * x * x + 0.2734375; + break; + case 9: + pnx = 94.9609375 * std::pow(x, 9.) - 201.09375 * std::pow(x, 7.) + + 140.765625 * std::pow(x, 5.) - 36.09375 * x * x * x + 2.4609375 * x; + break; + case 10: + pnx = 180.42578125 * std::pow(x, 10.) - 427.32421875 * std::pow(x, 8.) + + 351.9140625 * std::pow(x, 6.) - 117.3046875 * std::pow(x, 4.) + + 13.53515625 * x * x - 0.24609375; + } + + return pnx; +} + +//============================================================================== +// CALC_RN calculates the n-th order spherical harmonics for a given angle +// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) +//============================================================================== + +void calc_rn_c(int n, double uvw[3], double rn[]){ + double phi; + double w; + double w2m1; + + // rn[] is assumed to have already been allocated to the correct size + + // Store the cosine of the polar angle and the azimuthal angle + w = uvw[2]; + if (uvw[0] == 0.) { + phi = 0.; + } else { + phi = std::atan2(uvw[1], uvw[0]); + } + + // Store the shorthand of 1-w * w + w2m1 = 1. - w * w; + + // Now evaluate the spherical harmonics function depending on the order + // requested + switch (n) { + case 0: + // l = 0, m = 0 + rn[0] = 1.; + break; + case 1: + // l = 1, m = -1 + rn[0] = -(1.*std::sqrt(w2m1) * std::sin(phi)); + // l = 1, m = 0 + rn[1] = w; + // l = 1, m = 1 + rn[2] = -(1.*std::sqrt(w2m1) * std::cos(phi)); + break; + case 2: + // l = 2, m = -2 + rn[0] = 0.288675134594813 * (-3. * w * w + 3.) * std::sin(2. * phi); + // l = 2, m = -1 + rn[1] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::sin(phi)); + // l = 2, m = 0 + rn[2] = 1.5 * w * w - 0.5; + // l = 2, m = 1 + rn[3] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::cos(phi)); + // l = 2, m = 2 + rn[4] = 0.288675134594813 * (-3. * w * w + 3.) * std::cos(2. * phi); + break; + case 3: + // l = 3, m = -3 + rn[0] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::sin(3. * phi)); + // l = 3, m = -2 + rn[1] = 1.93649167310371 * w*(w2m1) * std::sin(2.*phi); + // l = 3, m = -1 + rn[2] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * + std::sin(phi)); + // l = 3, m = 0 + rn[3] = 2.5 * std::pow(w, 3) - 1.5 * w; + // l = 3, m = 1 + rn[4] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * + std::cos(phi)); + // l = 3, m = 2 + rn[5] = 1.93649167310371 * w*(w2m1) * std::cos(2.*phi); + // l = 3, m = 3 + rn[6] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::cos(3.* phi)); + break; + case 4: + // l = 4, m = -4 + rn[0] = 0.739509972887452 * (w2m1 * w2m1) * std::sin(4.0*phi); + // l = 4, m = -3 + rn[1] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::sin(3.* phi)); + // l = 4, m = -2 + rn[2] = 0.074535599249993 * (w2m1)*(52.5 * w * w - 7.5) * std::sin(2. *phi); + // l = 4, m = -1 + rn[3] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5 * w) * + std::sin(phi)); + // l = 4, m = 0 + rn[4] = 4.375 * std::pow(w, 4) - 3.75 * w * w + 0.375; + // l = 4, m = 1 + rn[5] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5*w) * + std::cos(phi)); + // l = 4, m = 2 + rn[6] = 0.074535599249993 * (w2m1)*(52.5*w * w - 7.5) * std::cos(2.*phi); + // l = 4, m = 3 + rn[7] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::cos(3.* phi)); + // l = 4, m = 4 + rn[8] = 0.739509972887452 * w2m1 * w2m1 * std::cos(4.0*phi); + break; + case 5: + // l = 5, m = -5 + rn[0] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::sin(5.0 * phi)); + // l = 5, m = -4 + rn[1] = 2.21852991866236 * w * w2m1 * w2m1 * std::sin(4.0 * phi); + // l = 5, m = -3 + rn[2] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * + ((945.0 /2.)* w * w - 52.5) * std::sin(3.*phi)); + // l = 5, m = -2 + rn[3] = 0.0487950036474267 * (w2m1) + * ((315.0/2.)* std::pow(w, 3) - 52.5 * w) * std::sin(2.*phi); + // l = 5, m = -1 + rn[4] = -(0.258198889747161*std::sqrt(w2m1) * + (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::sin(phi)); + // l = 5, m = 0 + rn[5] = 7.875 * std::pow(w, 5) - 8.75 * std::pow(w, 3) + 1.875 * w; + // l = 5, m = 1 + rn[6] = -(0.258198889747161 * std::sqrt(w2m1)* + (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::cos(phi)); + // l = 5, m = 2 + rn[7] = 0.0487950036474267 * (w2m1) * + ((315.0 / 2.) * std::pow(w, 3) - 52.5*w) * std::cos(2.*phi); + // l = 5, m = 3 + rn[8] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * + ((945.0 / 2.) * w * w - 52.5) * std::cos(3.*phi)); + // l = 5, m = 4 + rn[9] = 2.21852991866236 * w * w2m1 * w2m1 * std::cos(4.0*phi); + // l = 5, m = 5 + rn[10] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::cos(5.0* phi)); + break; + case 6: + // l = 6, m = -6 + rn[0] = 0.671693289381396 * std::pow(w2m1, 3) * std::sin(6.0*phi); + // l = 6, m = -5 + rn[1] = -(2.32681380862329 * w*std::pow(w2m1, 2.5) * std::sin(5.0*phi)); + // l = 6, m = -4 + rn[2] = 0.00104990131391452 * w2m1 * w2m1 * + ((10395.0/2.) * w * w - 945.0/2.) * std::sin(4.0 * phi); + // l = 6, m = -3 + rn[3] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * + ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::sin(3.*phi)); + // l = 6, m = -2 + rn[4] = 0.0345032779671177 * (w2m1) * + ((3465.0/8.0)* std::pow(w, 4) - 945.0/4.0 * w * w + 105.0/8.0) * + std::sin(2. * phi); + // l = 6, m = -1 + rn[5] = -(0.218217890235992*std::sqrt(w2m1) * + ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * + std::sin(phi)); + // l = 6, m = 0 + rn[6] = 14.4375 * std::pow(w, 6) - 19.6875 * std::pow(w, 4) + 6.5625 * w * w - + 0.3125; + // l = 6, m = 1 + rn[7] = -(0.218217890235992*std::sqrt(w2m1) * + ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * + std::cos(phi)); + // l = 6, m = 2 + rn[8] = 0.0345032779671177 * w2m1 * + ((3465.0/8.0)* std::pow(w, 4) -945.0/4.0 * w * w + 105.0/8.0) * + std::cos(2.*phi); + // l = 6, m = 3 + rn[9] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * + ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::cos(3.*phi)); + // l = 6, m = 4 + rn[10] = 0.00104990131391452 * w2m1 * w2m1 * + ((10395.0/2.)*w * w - 945.0/2.) * std::cos(4.0*phi); + // l = 6, m = 5 + rn[11] = -(2.32681380862329 * w * std::pow(w2m1, 2.5) * std::cos(5.0*phi)); + // l = 6, m = 6 + rn[12] = 0.671693289381396 * std::pow(w2m1, 3) * std::cos(6.0*phi); + break; + case 7: + // l = 7, m = -7 + rn[0] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::sin(7.0*phi)); + // l = 7, m = -6 + rn[1] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::sin(6.0*phi); + // l = 7, m = -5 + rn[2] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * + ((135135.0/2.)*w * w - 10395.0/2.) * std::sin(5.0*phi)); + // l = 7, m = -4 + rn[3] = 0.000548293079133141 * w2m1 * w2m1 * + ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::sin(4.0*phi); + // l = 7, m = -3 + rn[4] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * + ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * + std::sin(3.*phi)); + // l = 7, m = -2 + rn[5] = 0.025717224993682 * (w2m1) * + ((9009.0/8.0)* std::pow(w, 5) -3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * + std::sin(2.*phi); + // l = 7, m = -1 + rn[6] = -(0.188982236504614*std::sqrt(w2m1) * + ((3003.0/16.0)* std::pow(w, 6) - 3465.0/16.0 * std::pow(w, 4) + + (945.0/16.0)*w * w - 35.0/16.0) * std::sin(phi)); + // l = 7, m = 0 + rn[7] = 26.8125 * std::pow(w, 7) - 43.3125 * std::pow(w, 5) + 19.6875 * std::pow(w, 3) - + 2.1875 * w; + // l = 7, m = 1 + rn[8] = -(0.188982236504614*std::sqrt(w2m1) * ((3003.0/16.0) * std::pow(w, 6) - + 3465.0/16.0 * std::pow(w, 4) + (945.0/16.0)*w * w - 35.0/16.0) * std::cos(phi)); + // l = 7, m = 2 + rn[9] = 0.025717224993682 * (w2m1) * ((9009.0/8.0)* std::pow(w, 5) - + 3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * std::cos(2.*phi); + // l = 7, m = 3 + rn[10] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * + ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * + std::cos(3.*phi)); + // l = 7, m = 4 + rn[11] = 0.000548293079133141 * w2m1 * w2m1 * + ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::cos(4.0*phi); + // l = 7, m = 5 + rn[12] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * + ((135135.0/2.)*w * w - 10395.0/2.) * std::cos(5.0*phi)); + // l = 7, m = 6 + rn[13] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::cos(6.0*phi); + // l = 7, m = 7 + rn[14] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::cos(7.0*phi)); + break; + case 8: + // l = 8, m = -8 + rn[0] = 0.626706654240044 * std::pow(w2m1, 4) * std::sin(8.0*phi); + // l = 8, m = -7 + rn[1] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::sin(7.0*phi)); + // l = 8, m = -6 + rn[2] = 6.77369783729086e-6*std::pow(w2m1, 3)* + ((2027025.0/2.)*w * w - 135135.0/2.) * std::sin(6.0*phi); + // l = 8, m = -5 + rn[3] = -(4.38985792528482e-5*std::pow(w2m1, 2.5) * + ((675675.0/2.)*std::pow(w, 3) - 135135.0/2.*w) * std::sin(5.0*phi)); + // l = 8, m = -4 + rn[4] = 0.000316557156832328 * w2m1 * w2m1 * + ((675675.0/8.0)* std::pow(w, 4) - 135135.0/4.0 * w * w + 10395.0/8.0) * + std::sin(4.0*phi); + // l = 8, m = -3 + rn[5] = -(0.00245204119306875 * std::pow(w2m1, 1.5) * ((135135.0/8.0) * + std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + (10395.0/8.0)*w) * std::sin(3.*phi)); + // l = 8, m = -2 + rn[6] = 0.0199204768222399 * (w2m1) * + ((45045.0/16.0)* std::pow(w, 6)- 45045.0/16.0 * std::pow(w, 4) + + (10395.0/16.0)*w * w - 315.0/16.0) * std::sin(2.*phi); + // l = 8, m = -1 + rn[7] = -(0.166666666666667*std::sqrt(w2m1) * + ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + + (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::sin(phi)); + // l = 8, m = 0 + rn[8] = 50.2734375 * std::pow(w, 8) - 93.84375 * std::pow(w, 6) + 54.140625 * + std::pow(w, 4) - 9.84375 * w * w + 0.2734375; + // l = 8, m = 1 + rn[9] = -(0.166666666666667*std::sqrt(w2m1) * + ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + + (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::cos(phi)); + // l = 8, m = 2 + rn[10] = 0.0199204768222399 * (w2m1)*((45045.0/16.0)* std::pow(w, 6)- + 45045.0/16.0 * std::pow(w, 4) + (10395.0/16.0)*w * w - + 315.0/16.0) * std::cos(2.*phi); + // l = 8, m = 3 + rn[11] = -(0.00245204119306875 * std::pow(w2m1, 1.5)* + ((135135.0/8.0) * std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + + (10395.0/8.0)*w) * std::cos(3.*phi)); + // l = 8, m = 4 + rn[12] = 0.000316557156832328 * w2m1 * w2m1*((675675.0/8.0)* std::pow(w, 4) - + 135135.0/4.0 * w * w + 10395.0/8.0) * std::cos(4.0*phi); + // l = 8, m = 5 + rn[13] = -(4.38985792528482e-5*std::pow(w2m1, 2.5)*((675675.0/2.)*std::pow(w, 3) - + 135135.0/2.*w) * std::cos(5.0*phi)); + // l = 8, m = 6 + rn[14] = 6.77369783729086e-6*std::pow(w2m1, 3)*((2027025.0/2.)*w * w - + 135135.0/2.) * std::cos(6.0*phi); + // l = 8, m = 7 + rn[15] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::cos(7.0*phi)); + // l = 8, m = 8 + rn[16] = 0.626706654240044 * std::pow(w2m1, 4) * std::cos(8.0*phi); + break; + case 9: + // l = 9, m = -9 + rn[0] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); + // l = 9, m = -8 + rn[1] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::sin(8.0 * phi); + // l = 9, m = -7 + rn[2] = -(4.37240315267812e-7*std::pow(w2m1, 3.5) * + ((34459425.0/2.)*w * w - 2027025.0/2.) * std::sin(7.0 * phi)); + // l = 9, m = -6 + rn[3] = 3.02928976464514e-6*std::pow(w2m1, 3)* + ((11486475.0/2.)*std::pow(w, 3) - 2027025.0/2.*w) * std::sin(6.0 * phi); + // l = 9, m = -5 + rn[4] = -(2.34647776186144e-5*std::pow(w2m1, 2.5) * + ((11486475.0/8.0)* std::pow(w, 4) - 2027025.0 / 4.0 * w * w + + 135135.0/8.0) * std::sin(5.0 * phi)); + // l = 9, m = -4 + rn[5] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0)* std::pow(w, 5) - + 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::sin(4.0*phi); + // l = 9, m = -3 + rn[6] = -(0.00173385495536766 * std::pow(w2m1, 1.5) * + ((765765.0/16.0)* std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + + (135135.0/16.0)*w * w - 3465.0/16.0) * std::sin(3. * phi)); + // l = 9, m = -2 + rn[7] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7)- + 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - + 3465.0/16.0 * w) * std::sin(2. * phi); + // l = 9, m = -1 + rn[8] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - + 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - + 3465.0/32.0 * w * w + 315.0/128.0) * std::sin(phi)); + // l = 9, m = 0 + rn[9] = 94.9609375 * std::pow(w, 9) - 201.09375 * std::pow(w, 7) + + 140.765625 * std::pow(w, 5)- 36.09375 * std::pow(w, 3) + 2.4609375 * w; + // l = 9, m = 1 + rn[10] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - + 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - + 3465.0/32.0 * w * w + 315.0/128.0) * std::cos(phi)); + // l = 9, m = 2 + rn[11] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7) - + 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - + 3465.0/ 16.0 * w) * std::cos(2. * phi); + // l = 9, m = 3 + rn[12] = -(0.00173385495536766 * std::pow(w2m1, 1.5)*((765765.0/16.0) * + std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + + (135135.0/16.0)* w * w - 3465.0/16.0)* std::cos(3. * phi)); + // l = 9, m = 4 + rn[13] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0) * std::pow(w, 5) - + 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::cos(4.0 * phi); + // l = 9, m = 5 + rn[14] = -(2.34647776186144e-5*std::pow(w2m1, 2.5)*((11486475.0/8.0) * + std::pow(w, 4) - 2027025.0/4.0 * w * w + 135135.0/8.0) * + std::cos(5.0 * phi)); + // l = 9, m = 6 + rn[15] = 3.02928976464514e-6*std::pow(w2m1, 3)*((11486475.0/2.)*std::pow(w, 3) - + 2027025.0/2. * w) * std::cos(6.0 * phi); + // l = 9, m = 7 + rn[16] = -(4.37240315267812e-7*std::pow(w2m1, 3.5)* + ((34459425.0/2.) * w * w - 2027025.0/2.) * std::cos(7.0 * phi)); + // l = 9, m = 8 + rn[17] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::cos(8.0 * phi); + // l = 9, m = 9 + rn[18] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); + break; + case 10: + // l = 10, m = -10 + rn[0] = 0.593627917136573 * std::pow(w2m1, 5) * std::sin(10.0 * phi); + // l = 10, m = -9 + rn[1] = -(2.65478475211798 * w * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); + // l = 10, m = -8 + rn[2] = 2.49953651452314e-8 * std::pow(w2m1, 4) * + ((654729075.0/2.) * w * w - 34459425.0/2.) * std::sin(8.0 * phi); + // l = 10, m = -7 + rn[3] = -(1.83677671621093e-7*std::pow(w2m1, 3.5)* + ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * + std::sin(7.0 * phi)); + // l = 10, m = -6 + rn[4] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - + 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::sin(6.0 * phi); + // l = 10, m = -5 + rn[5] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* + ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + + (2027025.0/8.0)*w) * std::sin(5.0 * phi)); + // l = 10, m = -4 + rn[6] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - + 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0)*w * w - + 45045.0/16.0) * std::sin(4.0 * phi); + // l = 10, m = -3 + rn[7] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* + ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + + (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::sin(3. * phi)); + // l = 10, m = -2 + rn[8] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - + 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - + 45045.0/32.0 * w * w + 3465.0/128.0) * std::sin(2. * phi); + // l = 10, m = -1 + rn[9] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - + 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) - + 15015.0/32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::sin(phi)); + // l = 10, m = 0 + rn[10] = 180.42578125 * std::pow(w, 10) - 427.32421875 * std::pow(w, 8) +351.9140625 + * std::pow(w, 6) - 117.3046875 * std::pow(w, 4) + 13.53515625 * w * w -0.24609375; + // l = 10, m = 1 + rn[11] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - + 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) -15015.0/ + 32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::cos(phi)); + // l = 10, m = 2 + rn[12] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - + 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - + 45045.0/32.0 * w * w + 3465.0/128.0) * std::cos(2. * phi); + // l = 10, m = 3 + rn[13] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* + ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + + (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::cos(3. * phi)); + // l = 10, m = 4 + rn[14] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - + 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0) * w * w - + 45045.0/16.0) * std::cos(4.0 * phi); + // l = 10, m = 5 + rn[15] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* + ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + + (2027025.0/8.0)*w) * std::cos(5.0 * phi)); + // l = 10, m = 6 + rn[16] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - + 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::cos(6.0 * phi); + // l = 10, m = 7 + rn[17] = -(1.83677671621093e-7*std::pow(w2m1, 3.5) * + ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * std::cos(7.0 * phi)); + // l = 10, m = 8 + rn[18] = 2.49953651452314e-8*std::pow(w2m1, 4)* + ((654729075.0/2.)*w * w - 34459425.0/2.) * std::cos(8.0 * phi); + // l = 10, m = 9 + rn[19] = -(2.65478475211798 * w*std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); + // l = 10, m = 10 + rn[20] = 0.593627917136573 * std::pow(w2m1, 5) * std::cos(10.0 * phi); + } +} + +//============================================================================== +// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients +// and the value of x +//============================================================================== + +double __attribute__ ((const)) evaluate_legendre_c(int n, double data[], + double x) { + double val; + + val = 0.5 * data[0]; + for (int l = 1; l < n; l++) { + val += (static_cast(l) + 0.5) * data[l] * calc_pn_c(l, x); + } + +} + +//============================================================================== +// ROTATE_ANGLE rotates direction std::cosines through a polar angle whose +// cosine is mu and through an azimuthal angle sampled uniformly. Note that +// this is done with direct sampling rather than rejection as is done in MCNP +// and SERPENT. +//============================================================================== + +void rotate_angle_c(double uvw[3], double mu, double phi) { + double phi_; // azimuthal angle + double sinphi; // std::sine of azimuthal angle + double cosphi; // cosine of azimuthal angle + double a; // sqrt(1 - mu^2) + double b; // sqrt(1 - w^2) + double u0; // original std::cosine in x direction + double v0; // original std::cosine in y direction + double w0; // original std::cosine in z direction + + // Copy original directional std::cosines + u0 = uvw[0]; + v0 = uvw[1]; + w0 = uvw[2]; + + // Sample azimuthal angle in [0,2pi) if none provided + if (phi != -10.) { + phi_ = phi; + } else { + phi_ = 2. * M_PI * prn(); + } + + // Precompute factors to save flops + sinphi = std::sin(phi_); + cosphi = std::cos(phi_); + a = std::sqrt(std::max(0., 1. - mu * mu)); + b = std::sqrt(std::max(0., 1. - w0 * w0)); + + // Need to treat special case where sqrt(1 - w**2) is close to zero by + // expanding about the v component rather than the w component + if (b > 1e-10) { + uvw[0] = mu * u0 + a * (u0 * w0 * cosphi - v0 * sinphi) / b; + uvw[1] = mu * v0 + a * (v0 * w0 * cosphi + u0 * sinphi) / b; + uvw[2] = mu * w0 - a * b * cosphi; + } else { + b = std::sqrt(1. - v0 * v0); + uvw[0] = mu * u0 + a * (u0 * v0 * cosphi + w0 * sinphi) / b; + uvw[1] = mu * v0 - a * b * cosphi; + uvw[2] = mu * w0 + a * (v0 * w0 * cosphi - u0 * sinphi) / b; + } +} + +//============================================================================== +// MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution +// based on a direct sampling scheme. The probability distribution function for +// a Maxwellian is given as p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). +// This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. +//============================================================================== + +double maxwell_spectrum_c(double T) { + double E_out; // Sampled Energy + + double r1; + double r2; + double r3; // random numbers + double c; // cosine of pi/2*r3 + + r1 = prn(); + r2 = prn(); + r3 = prn(); + + // determine cosine of pi/2*r + c = std::cos(M_PI / 2. * r3); + + // determine outgoing energy + E_out = -T * (std::log(r1) + std::log(r2) * c * c); + + return E_out; +} + +//============================================================================== +// WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent +// fission spectrum. Although fitted parameters exist for many nuclides, +// generally the continuous tabular distributions (LAW 4) should be used in +// lieu of the Watt spectrum. This direct sampling scheme is an unpublished +// scheme based on the original Watt spectrum derivation (See F. Brown's +// MC lectures). +//============================================================================== + +double watt_spectrum_c(double a, double b) { + double E_out; // Sampled Energy + double w; // sampled from Maxwellian + + w = maxwell_spectrum_c(a); + E_out = w + 0.25 * a * a * b + (2. * prn() - 1.) * std::sqrt(a * a * b * w); + + return E_out; +} + +//============================================================================== +// FADDEEVA the Faddeeva function, using Stephen Johnson's implementation +//============================================================================== + +// std::complex faddeeva_c(std::complex z) { +// std::complex wv; // The resultant w(z) value +// double relerr; // Target relative error in the inner loop of MIT Faddeeva + +// // Technically, the value we want is given by the equation: +// // w(z) = I/Pi * Integrate[Exp[-t^2]/(z-t), {t, -Infinity, Infinity}] +// // as shown in Equation 63 from Hwang, R. N. "A rigorous pole +// // representation of multilevel cross sections and its practical +// // applications." Nuclear Science and Engineering 96.3 (1987): 192-209. +// // +// // The MIT Faddeeva function evaluates w(z) = exp(-z^2)erfc(-iz). These +// // two forms of the Faddeeva function are related by a transformation. +// // +// // If we call the integral form w_int, and the function form w_fun: +// // For imag(z) > 0, w_int(z) = w_fun(z) +// // For imag(z) < 0, w_int(z) = -conjg(w_fun(conjg(z))) + +// // Note that faddeeva_w will interpret zero as machine epsilon + +// relerr = 0.; +// if (z.imag() > 0.) { +// wv = Faddeeva::w(z, relerr); +// } else { +// wv = -std::conj(Faddeeva::w(std::conj(z), relerr)); +// } + +// return wv; +// } + +// std::complex w_derivative_c(std::complex z, int order){ +// std::complex wv; // The resultant w(z) value + +// const std::complex twoi_sqrtpi(0.0, 2.0 / std::sqrt(M_PI)); + +// switch(order) { +// case 0: +// wv = faddeeva_c(z); +// break; +// case 1: +// wv = -2. * z * faddeeva_c(z) + twoi_sqrtpi; +// break; +// default: +// wv = -2. * z * w_derivative_c(z, order - 1) - 2. * (order - 1) * +// w_derivative_c(z, order - 2); +// } + +// return wv; +// } + +//============================================================================== +// BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. +// The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... +//============================================================================== + +void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { + // Factors is already pre-allocated + + double sqrtE; // sqrt(energy) + double beta; // sqrt(atomic weight ratio * E / kT) + double half_inv_dopp2; // 0.5 / dopp**2 + double quarter_inv_dopp4; // 0.25 / dopp**4 + double erf_beta; // error function of beta + double exp_m_beta2; // exp(-beta**2) + int i; + + sqrtE = std::sqrt(E); + beta = sqrtE * dopp; + half_inv_dopp2 = 0.5 / (dopp * dopp); + quarter_inv_dopp4 = half_inv_dopp2 * half_inv_dopp2; + + if (beta > 6.0) { + // Save time, ERF(6) is 1 to machine precision. + // beta/sqrtpi*exp(-beta**2) is also approximately 1 machine epsilon. + erf_beta = 1.; + exp_m_beta2 = 0.; + } else { + erf_beta = std::erf(beta); + exp_m_beta2 = std::exp(-beta * beta); + } + + // Assume that, for sure, we'll use a second order (1/E, 1/V, const) + // fit, and no less. + + factors[0] = erf_beta / E; + factors[1] = 1. / sqrtE; + factors[2] = factors[0] * (half_inv_dopp2 + E) + exp_m_beta2 / + (beta * std::sqrt(M_PI)); + + // Perform recursive broadening of high order components + for (i = 0; i < n - 3; i++) { + if (i != 0) { + factors[i + 3] = -factors[i - 1] * (i - 1.) * i * quarter_inv_dopp4 + + factors[i + 1] * (E + (1. + 2. * i) * half_inv_dopp2); + } else { + // Although it's mathematically identical, factors[0] will contain + // nothing, and we don't want to have to worry about memory. + factors[i + 3] = factors[i + 1]*(E + (1. + 2. * i) * half_inv_dopp2); + } + } +} + +} // namespace openmc diff --git a/src/math_functions.h b/src/math_functions.h new file mode 100644 index 000000000..aa048e5f6 --- /dev/null +++ b/src/math_functions.h @@ -0,0 +1,94 @@ +#ifndef MATH_FUNCTIONS_H +#define MATH_FUNCTIONS_H + +#include +#include +#include + +#include "random_lcg.h" +// #include "faddeeva/Faddeeva.hh" + + +namespace openmc { + + +//============================================================================== +// NORMAL_PERCENTILE calculates the percentile of the standard normal +// distribution with a specified probability level +//============================================================================== + +extern "C" double normal_percentile_c(double p) __attribute__ ((const)); + +//============================================================================== +// T_PERCENTILE calculates the percentile of the Student's t distribution with +// a specified probability level and number of degrees of freedom +//============================================================================== + +extern "C" double t_percentile_c(double p, int df) __attribute__ ((const)); + +//============================================================================== +// CALC_PN calculates the n-th order Legendre polynomial at the value of x. +//============================================================================== + +extern "C" double calc_pn_c(int n, double x) __attribute__ ((const)); + +//============================================================================== +// CALC_RN calculates the n-th order spherical harmonics for a given angle +// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) +//============================================================================== + +extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); + +//============================================================================== +// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients +// and the value of x +//============================================================================== + +extern "C" double evaluate_legendre_c(int n, double data[], double x) + __attribute__ ((const)); + +//============================================================================== +// ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is +// mu and through an azimuthal angle sampled uniformly. Note that this is done +// with direct sampling rather than rejection as is done in MCNP and SERPENT. +//============================================================================== + +extern "C" void rotate_angle_c(double uvw[3], double mu, double phi = -10.); + +//============================================================================== +// MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution +// based on a direct sampling scheme. The probability distribution function for +// a Maxwellian is given as p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). +// This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. +//============================================================================== + +extern "C" double maxwell_spectrum_c(double T); + +//============================================================================== +// WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent +// fission spectrum. Although fitted parameters exist for many nuclides, +// generally the continuous tabular distributions (LAW 4) should be used in +// lieu of the Watt spectrum. This direct sampling scheme is an unpublished +// scheme based on the original Watt spectrum derivation (See F. Brown's +// MC lectures). +//============================================================================== + +extern "C" double watt_spectrum_c(double a, double b); + +//============================================================================== +// FADDEEVA the Faddeeva function, using Stephen Johnson's implementation +//============================================================================== + +// extern "C" std::complex faddeeva_c(std::complex z); + +// extern "C" std::complex w_derivative_c(std::complex z, int order); + +//============================================================================== +// BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. +// The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... +//============================================================================== + +extern "C" void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]); + +} // namespace openmc +#endif // MATH_FUNCTIONS_H \ No newline at end of file From 0e3194315a6323a3df88bc59861b9fe3c6f0007a Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 30 Apr 2018 15:41:20 -0400 Subject: [PATCH 233/361] C++ized t_percentile --- openmc/capi/math.py | 4 +- src/api.F90 | 1 - src/math.F90 | 94 +---------------------------------- tests/unit_tests/test_math.py | 16 +++--- 4 files changed, 11 insertions(+), 104 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 713ef9813..3d22ccdac 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -5,8 +5,8 @@ from numpy.ctypeslib import ndpointer from . import _dll -_dll.normal_percentile.restype = c_double -_dll.normal_percentile.argtypes = [POINTER(c_double)] +# _dll.normal_percentile.restype = c_double +# _dll.normal_percentile.argtypes = [POINTER(c_double)] _dll.t_percentile.restype = c_double _dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] _dll.calc_pn.restype = c_double diff --git a/src/api.F90 b/src/api.F90 index 30aca6834..388659f4a 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -98,7 +98,6 @@ module openmc_api public :: openmc_tally_set_nuclides public :: openmc_tally_set_scores public :: openmc_tally_set_type - public :: normal_percentile public :: t_percentile public :: calc_pn public :: calc_rn diff --git a/src/math.F90 b/src/math.F90 index 76e9fc11a..40c2d09dc 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -7,7 +7,6 @@ module math implicit none private - public :: normal_percentile public :: t_percentile public :: calc_pn public :: calc_rn @@ -124,65 +123,6 @@ module math contains -!=============================================================================== -! NORMAL_PERCENTILE calculates the percentile of the standard normal -! distribution with a specified probability level -!=============================================================================== - - pure function normal_percentile(p) result(z) bind(C) - - real(C_DOUBLE), intent(in) :: p ! probability level - real(C_DOUBLE) :: z ! corresponding z-value - - real(C_DOUBLE) :: q - real(C_DOUBLE) :: r - real(C_DOUBLE), parameter :: p_low = 0.02425_8 - real(C_DOUBLE), parameter :: a(6) = (/ & - -3.969683028665376e1_8, 2.209460984245205e2_8, -2.759285104469687e2_8, & - 1.383577518672690e2_8, -3.066479806614716e1_8, 2.506628277459239e0_8 /) - real(C_DOUBLE), parameter :: b(5) = (/ & - -5.447609879822406e1_8, 1.615858368580409e2_8, -1.556989798598866e2_8, & - 6.680131188771972e1_8, -1.328068155288572e1_8 /) - real(C_DOUBLE), parameter :: c(6) = (/ & - -7.784894002430293e-3_8, -3.223964580411365e-1_8, -2.400758277161838_8, & - -2.549732539343734_8, 4.374664141464968_8, 2.938163982698783_8 /) - real(C_DOUBLE), parameter :: d(4) = (/ & - 7.784695709041462e-3_8, 3.224671290700398e-1_8, & - 2.445134137142996_8, 3.754408661907416_8 /) - - ! The rational approximation used here is from an unpublished work at - ! http://home.online.no/~pjacklam/notes/invnorm/ - - if (p < p_low) then - ! Rational approximation for lower region. - - q = sqrt(-TWO*log(p)) - z = (((((c(1)*q + c(2))*q + c(3))*q + c(4))*q + c(5))*q + c(6)) / & - ((((d(1)*q + d(2))*q + d(3))*q + d(4))*q + ONE) - - elseif (p <= ONE - p_low) then - ! Rational approximation for central region - - q = p - HALF - r = q*q - z = (((((a(1)*r + a(2))*r + a(3))*r + a(4))*r + a(5))*r + a(6))*q / & - (((((b(1)*r + b(2))*r + b(3))*r + b(4))*r + b(5))*r + ONE) - - else - ! Rational approximation for upper region - - q = sqrt(-TWO*log(ONE - p)) - z = -(((((c(1)*q + c(2))*q + c(3))*q + c(4))*q + c(5))*q + c(6)) / & - ((((d(1)*q + d(2))*q + d(3))*q + d(4))*q + ONE) - endif - - ! Refinement based on Newton's method -#ifndef NO_F2008 - z = z - (HALF * erfc(-z/sqrt(TWO)) - p) * sqrt(TWO*PI) * exp(HALF*z*z) -#endif - - end function normal_percentile - !=============================================================================== ! T_PERCENTILE calculates the percentile of the Student's t distribution with a ! specified probability level and number of degrees of freedom @@ -194,39 +134,7 @@ contains integer(C_INT), intent(in) :: df ! degrees of freedom real(C_DOUBLE) :: t ! corresponding t-value - real(C_DOUBLE) :: n ! degrees of freedom as a real(8) - real(C_DOUBLE) :: k ! n - 2 - real(C_DOUBLE) :: z ! percentile of normal distribution - real(C_DOUBLE) :: z2 ! z * z - - if (df == 1) then - ! For one degree of freedom, the t-distribution becomes a Cauchy - ! distribution whose cdf we can invert directly - - t = tan(PI*(p - HALF)) - - elseif (df == 2) then - ! For two degrees of freedom, the cdf is given by 1/2 + x/(2*sqrt(x^2 + - ! 2)). This can be directly inverted to yield the solution below - - t = TWO*sqrt(TWO)*(p - HALF)/sqrt(ONE - FOUR*(p - HALF)**2) - - else - - ! This approximation is from E. Olusegun George and Meenakshi Sivaram, "A - ! modification of the Fisher-Cornish approximation for the student t - ! percentiles," Communication in Statistics - Simulation and Computation, - ! 16 (4), pp. 1123-1132 (1987). - - n = real(df,8) - k = ONE/(n - TWO) - z = normal_percentile(p) - z2 = z * z - t = sqrt(n*k) * (z + (z2 - THREE)*z*k/FOUR + ((5._8*z2 - 56._8)*z2 + & - 75._8)*z*k*k/96._8 + (((z2 - 27._8)*THREE*z2 + 417._8)*z2 - 315._8) & - *z*k*k*k/384._8) - - end if + t = t_percentile_cc(p, df) end function t_percentile diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index 17c20fdb5..98295aefb 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -5,17 +5,17 @@ import openmc import openmc.capi -def test_normal_percentile(): - # normal_percentile has three branches to consider: - # p < 0.02425; 0.02425 <= p <= 0.97575; and p > 0.97575 - test_ps = [0.02, 0.4, 0.5, 0.6, 0.98] +# def test_normal_percentile(): +# # normal_percentile has three branches to consider: +# # p < 0.02425; 0.02425 <= p <= 0.97575; and p > 0.97575 +# test_ps = [0.02, 0.4, 0.5, 0.6, 0.98] - # The reference solutions come from Scipy - ref_zs = [sp.stats.norm.ppf(p) for p in test_ps] +# # The reference solutions come from Scipy +# ref_zs = [sp.stats.norm.ppf(p) for p in test_ps] - test_zs = [openmc.capi.math.normal_percentile(p) for p in test_ps] +# test_zs = [openmc.capi.math.normal_percentile(p) for p in test_ps] - assert np.allclose(ref_zs, test_zs) +# assert np.allclose(ref_zs, test_zs) def test_t_percentile(): From 00a68d8079e41a5d910287de9f2d349fd935dc10 Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Mon, 30 Apr 2018 19:55:30 +0000 Subject: [PATCH 234/361] adding -g option to CMakeList.txt --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca42bb5ba..0e3c47c0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,7 +114,7 @@ if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU) endif() # GCC compiler options - list(APPEND f90flags -cpp -std=f2008ts -fbacktrace -O2 -fstack-arrays) + list(APPEND f90flags -cpp -std=f2008ts -fbacktrace -O2 -fstack-arrays -g) if(debug) list(REMOVE_ITEM f90flags -O2 -fstack-arrays) list(APPEND f90flags -g -Wall -Wno-unused-dummy-argument -pedantic From 15b6b1d4eedad8e12e8f323720ccc03635dbd003 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 30 Apr 2018 16:13:11 -0400 Subject: [PATCH 235/361] CPPized the Pn and Rn functions --- openmc/capi/math.py | 48 ++-- src/math.F90 | 450 ++-------------------------------- src/math_functions.cpp | 31 +-- src/math_functions.h | 14 +- tests/unit_tests/test_math.py | 36 +-- 5 files changed, 84 insertions(+), 495 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 3d22ccdac..cd7ab4052 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -98,6 +98,30 @@ def calc_pn(n, x): return _dll.calc_pn(c_int(n), c_double(x)) +def evaluate_legendre(data, x): + """ Finds the value of f(x) given a set of Legendre coefficients + and the value of x. + + Parameters + ---------- + data : iterable of float + Legendre coefficients + x : float + Independent variable to evaluate the Legendre at + + Returns + ------- + float + Corresponding Legendre expansion result + + """ + + data_arr = np.array(data, dtype=np.float64) + return _dll.evaluate_legendre(c_int(len(data)), + data_arr.ctypes.data_as(POINTER(c_double)), + c_double(x)) + + def calc_rn(n, uvw): """ Calculate the n-th order real Spherical Harmonics for a given angle; all Rn,m values are provided (where -n <= m <= n). @@ -151,30 +175,6 @@ def calc_zn(n, rho, phi): return zn -def evaluate_legendre(data, x): - """ Finds the value of f(x) given a set of Legendre coefficients - and the value of x. - - Parameters - ---------- - data : iterable of float - Legendre coefficients - x : float - Independent variable to evaluate the Legendre at - - Returns - ------- - float - Corresponding Legendre expansion result - - """ - - data_arr = np.array(data, dtype=np.float64) - return _dll.evaluate_legendre(c_int(len(data)), - data_arr.ctypes.data_as(POINTER(c_double)), - c_double(x)) - - def rotate_angle(uvw0, mu, phi=None): """ Rotates direction cosines through a polar angle whose cosine is mu and through an azimuthal angle sampled uniformly. diff --git a/src/math.F90 b/src/math.F90 index 40c2d09dc..9e1f01de5 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -148,7 +148,7 @@ contains ! the return value will be 1.0. !=============================================================================== - pure function calc_pn(n,x) result(pnx) bind(C) + pure function calc_pn(n, x) result(pnx) bind(C) integer(C_INT), intent(in) :: n ! Legendre order requested real(C_DOUBLE), intent(in) :: x ! Independent variable the Legendre is to @@ -157,39 +157,25 @@ contains real(C_DOUBLE) :: pnx ! The Legendre poly of order n evaluated ! at x - select case(n) - case(1) - pnx = x - case(2) - pnx = 1.5_8 * x * x - HALF - case(3) - pnx = 2.5_8 * x * x * x - 1.5_8 * x - case(4) - pnx = 4.375_8 * (x ** 4) - 3.75_8 * x * x + 0.375_8 - case(5) - pnx = 7.875_8 * (x ** 5) - 8.75_8 * x * x * x + 1.875 * x - case(6) - pnx = 14.4375_8 * (x ** 6) - 19.6875_8 * (x ** 4) + & - 6.5625_8 * x * x - 0.3125_8 - case(7) - pnx = 26.8125_8 * (x ** 7) - 43.3125_8 * (x ** 5) + & - 19.6875_8 * x * x * x - 2.1875_8 * x - case(8) - pnx = 50.2734375_8 * (x ** 8) - 93.84375_8 * (x ** 6) + & - 54.140625 * (x ** 4) - 9.84375_8 * x * x + 0.2734375_8 - case(9) - pnx = 94.9609375_8 * (x ** 9) - 201.09375_8 * (x ** 7) + & - 140.765625_8 * (x ** 5) - 36.09375_8 * x * x * x + 2.4609375_8 * x - case(10) - pnx = 180.42578125_8 * (x ** 10) - 427.32421875_8 * (x ** 8) + & - 351.9140625_8 * (x ** 6) - 117.3046875_8 * (x ** 4) + & - 13.53515625_8 * x * x - 0.24609375_8 - case default - pnx = ONE ! correct for case(0), incorrect for the rest - end select + pnx = calc_pn_cc(n, x) end function calc_pn +!=============================================================================== +! EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients +! and the value of x +!=============================================================================== + + pure function evaluate_legendre(n, data, x) result(val) bind(C) + integer(C_INT), intent(in) :: n + real(C_DOUBLE), intent(in) :: data(n) + real(C_DOUBLE), intent(in) :: x + real(C_DOUBLE) :: val + + val = evaluate_legendre_cc(size(data), data, x) + + end function evaluate_legendre + !=============================================================================== ! CALC_RN calculates the n-th order real spherical harmonics for a given angle ! (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) @@ -202,387 +188,7 @@ contains ! assumed to be on unit sphere real(C_DOUBLE) :: rn(2*n + 1) ! The resultant R_n(uvw) - - real(C_DOUBLE) :: phi, w ! Azimuthal and Cosine of Polar angles (from uvw) - real(C_DOUBLE) :: w2m1 ! (w^2 - 1), frequently used in these - - w = uvw(3) ! z = cos(polar) - if (uvw(1) == ZERO) then - phi = ZERO - else - phi = atan2(uvw(2), uvw(1)) - end if - - w2m1 = (ONE - w**2) - select case(n) - case (0) - ! l = 0, m = 0 - rn(1) = ONE - case (1) - ! l = 1, m = -1 - rn(1) = -(ONE*sqrt(w2m1) * sin(phi)) - ! l = 1, m = 0 - rn(2) = ONE * w - ! l = 1, m = 1 - rn(3) = -(ONE*sqrt(w2m1) * cos(phi)) - case (2) - ! l = 2, m = -2 - rn(1) = 0.288675134594813_8 * (-THREE * w**2 + THREE) * sin(TWO*phi) - ! l = 2, m = -1 - rn(2) = -(1.73205080756888_8 * w*sqrt(w2m1) * sin(phi)) - ! l = 2, m = 0 - rn(3) = 1.5_8 * w**2 - HALF - ! l = 2, m = 1 - rn(4) = -(1.73205080756888_8 * w*sqrt(w2m1) * cos(phi)) - ! l = 2, m = 2 - rn(5) = 0.288675134594813_8 * (-THREE * w**2 + THREE) * cos(TWO*phi) - case (3) - ! l = 3, m = -3 - rn(1) = -(0.790569415042095_8 * (w2m1)**(THREE/TWO) * sin(THREE * phi)) - ! l = 3, m = -2 - rn(2) = 1.93649167310371_8 * w*(w2m1) * sin(TWO*phi) - ! l = 3, m = -1 - rn(3) = -(0.408248290463863_8*sqrt(w2m1)*((15.0_8/TWO)*w**2 - THREE/TWO) * & - sin(phi)) - ! l = 3, m = 0 - rn(4) = 2.5_8 * w**3 - 1.5_8 * w - ! l = 3, m = 1 - rn(5) = -(0.408248290463863_8*sqrt(w2m1)*((15.0_8/TWO)*w**2 - THREE/TWO) * & - cos(phi)) - ! l = 3, m = 2 - rn(6) = 1.93649167310371_8 * w*(w2m1) * cos(TWO*phi) - ! l = 3, m = 3 - rn(7) = -(0.790569415042095_8 * (w2m1)**(THREE/TWO) * cos(THREE* phi)) - case (4) - ! l = 4, m = -4 - rn(1) = 0.739509972887452_8 * (w2m1)**2 * sin(4.0_8*phi) - ! l = 4, m = -3 - rn(2) = -(2.09165006633519_8 * w*(w2m1)**(THREE/TWO) * sin(THREE* phi)) - ! l = 4, m = -2 - rn(3) = 0.074535599249993_8 * (w2m1)*((105.0_8/TWO)*w**2 - 15.0_8/TWO) * & - sin(TWO*phi) - ! l = 4, m = -1 - rn(4) = -(0.316227766016838_8*sqrt(w2m1)*((35.0_8/TWO)*w**3 - 15.0_8/TWO*w)& - * sin(phi)) - ! l = 4, m = 0 - rn(5) = 4.375_8 * w**4 - 3.75_8 * w**2 + 0.375_8 - ! l = 4, m = 1 - rn(6) = -(0.316227766016838_8*sqrt(w2m1)*((35.0_8/TWO)*w**3 - 15.0_8/TWO*w)& - * cos(phi)) - ! l = 4, m = 2 - rn(7) = 0.074535599249993_8 * (w2m1)*((105.0_8/TWO)*w**2 - 15.0_8/TWO) * & - cos(TWO*phi) - ! l = 4, m = 3 - rn(8) = -(2.09165006633519_8 * w*(w2m1)**(THREE/TWO) * cos(THREE* phi)) - ! l = 4, m = 4 - rn(9) = 0.739509972887452_8 * (w2m1)**2 * cos(4.0_8*phi) - case (5) - ! l = 5, m = -5 - rn(1) = -(0.701560760020114_8 * (w2m1)**(5.0_8/TWO) * sin(5.0_8*phi)) - ! l = 5, m = -4 - rn(2) = 2.21852991866236_8 * w*(w2m1)**2 * sin(4.0_8*phi) - ! l = 5, m = -3 - rn(3) = -(0.00996023841111995_8 * (w2m1)**(THREE/TWO)* & - ((945.0_8 /TWO)*w**2 - 105.0_8/TWO) * sin(THREE*phi)) - ! l = 5, m = -2 - rn(4) = 0.0487950036474267_8 * (w2m1) & - * ((315.0_8/TWO)*w**3 - 105.0_8/TWO*w) * sin(TWO*phi) - ! l = 5, m = -1 - rn(5) = -(0.258198889747161_8*sqrt(w2m1)* & - ((315.0_8/8.0_8)*w**4 - 105.0_8/4.0_8 * w**2 + 15.0_8/8.0_8) & - * sin(phi)) - ! l = 5, m = 0 - rn(6) = 7.875_8 * w**5 - 8.75_8 * w**3 + 1.875_8 * w - ! l = 5, m = 1 - rn(7) = -(0.258198889747161_8*sqrt(w2m1)* & - ((315.0_8/8.0_8)*w**4 - 105.0_8/4.0_8 * w**2 + 15.0_8/8.0_8) & - * cos(phi)) - ! l = 5, m = 2 - rn(8) = 0.0487950036474267_8 * (w2m1)* & - ((315.0_8/TWO)*w**3 - 105.0_8/TWO*w) * cos(TWO*phi) - ! l = 5, m = 3 - rn(9) = -(0.00996023841111995_8 * (w2m1)**(THREE/TWO)* & - ((945.0_8 /TWO)*w**2 - 105.0_8/TWO) * cos(THREE*phi)) - ! l = 5, m = 4 - rn(10) = 2.21852991866236_8 * w*(w2m1)**2 * cos(4.0_8*phi) - ! l = 5, m = 5 - rn(11) = -(0.701560760020114_8 * (w2m1)**(5.0_8/TWO) * cos(5.0_8* phi)) - case (6) - ! l = 6, m = -6 - rn(1) = 0.671693289381396_8 * (w2m1)**3 * sin(6.0_8*phi) - ! l = 6, m = -5 - rn(2) = -(2.32681380862329_8 * w*(w2m1)**(5.0_8/TWO) * sin(5.0_8*phi)) - ! l = 6, m = -4 - rn(3) = 0.00104990131391452_8 * (w2m1)**2 * & - ((10395.0_8/TWO)*w**2 - 945.0_8/TWO) * sin(4.0_8*phi) - ! l = 6, m = -3 - rn(4) = -(0.00575054632785295_8 * (w2m1)**(THREE/TWO) * & - ((3465.0_8/TWO)*w**3 - 945.0_8/TWO*w) * sin(THREE*phi)) - ! l = 6, m = -2 - rn(5) = 0.0345032779671177_8 * (w2m1) * & - ((3465.0_8/8.0_8)*w**4 - 945.0_8/4.0_8 * w**2 + 105.0_8/8.0_8) & - * sin(TWO*phi) - ! l = 6, m = -1 - rn(6) = -(0.218217890235992_8*sqrt(w2m1) * & - ((693.0_8/8.0_8)*w**5- 315.0_8/4.0_8 * w**3 + (105.0_8/8.0_8)*w) & - * sin(phi)) - ! l = 6, m = 0 - rn(7) = 14.4375_8 * w**6 - 19.6875_8 * w**4 + 6.5625_8 * w**2 - 0.3125_8 - ! l = 6, m = 1 - rn(8) = -(0.218217890235992_8*sqrt(w2m1) * & - ((693.0_8/8.0_8)*w**5- 315.0_8/4.0_8 * w**3 + (105.0_8/8.0_8)*w) & - * cos(phi)) - ! l = 6, m = 2 - rn(9) = 0.0345032779671177_8 * (w2m1) * & - ((3465.0_8/8.0_8)*w**4 -945.0_8/4.0_8 * w**2 + 105.0_8/8.0_8) & - * cos(TWO*phi) - ! l = 6, m = 3 - rn(10) = -(0.00575054632785295_8 * (w2m1)**(THREE/TWO) * & - ((3465.0_8/TWO)*w**3 - 945.0_8/TWO*w) * cos(THREE*phi)) - ! l = 6, m = 4 - rn(11) = 0.00104990131391452_8 * (w2m1)**2 * & - ((10395.0_8/TWO)*w**2 - 945.0_8/TWO) * cos(4.0_8*phi) - ! l = 6, m = 5 - rn(12) = -(2.32681380862329_8 * w*(w2m1)**(5.0_8/TWO) * cos(5.0_8*phi)) - ! l = 6, m = 6 - rn(13) = 0.671693289381396_8 * (w2m1)**3 * cos(6.0_8*phi) - case (7) - ! l = 7, m = -7 - rn(1) = -(0.647259849287749_8 * (w2m1)**(7.0_8/TWO) * sin(7.0_8*phi)) - ! l = 7, m = -6 - rn(2) = 2.42182459624969_8 * w*(w2m1)**3 * sin(6.0_8*phi) - ! l = 7, m = -5 - rn(3) = -(9.13821798555235d-5*(w2m1)**(5.0_8/TWO)* & - ((135135.0_8/TWO)*w**2 - 10395.0_8/TWO) * sin(5.0_8*phi)) - ! l = 7, m = -4 - rn(4) = 0.000548293079133141_8 * (w2m1)**2* & - ((45045.0_8/TWO)*w**3 - 10395.0_8/TWO*w) * sin(4.0_8*phi) - ! l = 7, m = -3 - rn(5) = -(0.00363696483726654_8 * (w2m1)**(THREE/TWO)* & - ((45045.0_8/8.0_8)*w**4 - 10395.0_8/4.0_8 * w**2 + 945.0_8/8.0_8)* & - sin(THREE*phi)) - ! l = 7, m = -2 - rn(6) = 0.025717224993682_8 * (w2m1)* & - ((9009.0_8/8.0_8)*w**5 -3465.0_8/4.0_8 * w**3 + (945.0_8/8.0_8)*w)* & - sin(TWO*phi) - ! l = 7, m = -1 - rn(7) = -(0.188982236504614_8*sqrt(w2m1)* & - ((3003.0_8/16.0_8)*w**6 - 3465.0_8/16.0_8 * w**4 + & - (945.0_8/16.0_8)*w**2 - 35.0_8/16.0_8) * sin(phi)) - ! l = 7, m = 0 - rn(8) = 26.8125_8 * w**7 - 43.3125_8 * w**5 + 19.6875_8 * w**3 -2.1875_8 & - * w - ! l = 7, m = 1 - rn(9) = -(0.188982236504614_8*sqrt(w2m1)* & - ((3003.0_8/16.0_8)*w**6 - 3465.0_8/16.0_8 * w**4 + & - (945.0_8/16.0_8)*w**2 - 35.0_8/16.0_8) * cos(phi)) - ! l = 7, m = 2 - rn(10) = 0.025717224993682_8 * (w2m1)* & - ((9009.0_8/8.0_8)*w**5 -3465.0_8/4.0_8 * w**3 + (945.0_8/8.0_8)*w)* & - cos(TWO*phi) - ! l = 7, m = 3 - rn(11) = -(0.00363696483726654_8 * (w2m1)**(THREE/TWO)* & - ((45045.0_8/8.0_8)*w**4 - 10395.0_8/4.0_8 * w**2 + 945.0_8/8.0_8)* & - cos(THREE*phi)) - ! l = 7, m = 4 - rn(12) = 0.000548293079133141_8 * (w2m1)**2 * & - ((45045.0_8/TWO)*w**3 - 10395.0_8/TWO*w) * cos(4.0_8*phi) - ! l = 7, m = 5 - rn(13) = -(9.13821798555235d-5*(w2m1)**(5.0_8/TWO)* & - ((135135.0_8/TWO)*w**2 - 10395.0_8/TWO) * cos(5.0_8*phi)) - ! l = 7, m = 6 - rn(14) = 2.42182459624969_8 * w*(w2m1)**3 * cos(6.0_8*phi) - ! l = 7, m = 7 - rn(15) = -(0.647259849287749_8 * (w2m1)**(7.0_8/TWO) * cos(7.0_8*phi)) - case (8) - ! l = 8, m = -8 - rn(1) = 0.626706654240044_8 * (w2m1)**4 * sin(8.0_8*phi) - ! l = 8, m = -7 - rn(2) = -(2.50682661696018_8 * w*(w2m1)**(7.0_8/TWO) * sin(7.0_8*phi)) - ! l = 8, m = -6 - rn(3) = 6.77369783729086d-6*(w2m1)**3* & - ((2027025.0_8/TWO)*w**2 - 135135.0_8/TWO) * sin(6.0_8*phi) - ! l = 8, m = -5 - rn(4) = -(4.38985792528482d-5*(w2m1)**(5.0_8/TWO)* & - ((675675.0_8/TWO)*w**3 - 135135.0_8/TWO*w) * sin(5.0_8*phi)) - ! l = 8, m = -4 - rn(5) = 0.000316557156832328_8 * (w2m1)**2* & - ((675675.0_8/8.0_8)*w**4 - 135135.0_8/4.0_8 * w**2 & - + 10395.0_8/8.0_8) * sin(4.0_8*phi) - ! l = 8, m = -3 - rn(6) = -(0.00245204119306875_8 * (w2m1)**(THREE/TWO)* & - ((135135.0_8/8.0_8)*w**5 - 45045.0_8/4.0_8 * w**3 & - + (10395.0_8/8.0_8)*w) * sin(THREE*phi)) - ! l = 8, m = -2 - rn(7) = 0.0199204768222399_8 * (w2m1)* & - ((45045.0_8/16.0_8)*w**6- 45045.0_8/16.0_8 * w**4 + & - (10395.0_8/16.0_8)*w**2 - 315.0_8/16.0_8) * sin(TWO*phi) - ! l = 8, m = -1 - rn(8) = -(0.166666666666667_8*sqrt(w2m1)* & - ((6435.0_8/16.0_8)*w**7 - 9009.0_8/16.0_8 * w**5 + & - (3465.0_8/16.0_8)*w**3 - 315.0_8/16.0_8 * w) * sin(phi)) - ! l = 8, m = 0 - rn(9) = 50.2734375_8 * w**8 - 93.84375_8 * w**6 + 54.140625_8 * w**4 -& - 9.84375_8 * w**2 + 0.2734375_8 - ! l = 8, m = 1 - rn(10) = -(0.166666666666667_8*sqrt(w2m1)* & - ((6435.0_8/16.0_8)*w**7 - 9009.0_8/16.0_8 * w**5 + & - (3465.0_8/16.0_8)*w**3 - 315.0_8/16.0_8 * w) * cos(phi)) - ! l = 8, m = 2 - rn(11) = 0.0199204768222399_8 * (w2m1)*((45045.0_8/16.0_8)*w**6- & - 45045.0_8/16.0_8 * w**4 + (10395.0_8/16.0_8)*w**2 - & - 315.0_8/16.0_8) * cos(TWO*phi) - ! l = 8, m = 3 - rn(12) = -(0.00245204119306875_8 * (w2m1)**(THREE/TWO)* & - ((135135.0_8/8.0_8)*w**5 - 45045.0_8/4.0_8 * w**3 + & - (10395.0_8/8.0_8)*w) * cos(THREE*phi)) - ! l = 8, m = 4 - rn(13) = 0.000316557156832328_8 * (w2m1)**2*((675675.0_8/8.0_8)*w**4 - & - 135135.0_8/4.0_8 * w**2 + 10395.0_8/8.0_8) * cos(4.0_8*phi) - ! l = 8, m = 5 - rn(14) = -(4.38985792528482d-5*(w2m1)**(5.0_8/TWO)*((675675.0_8/TWO)*w**3 -& - 135135.0_8/TWO*w) * cos(5.0_8*phi)) - ! l = 8, m = 6 - rn(15) = 6.77369783729086d-6*(w2m1)**3*((2027025.0_8/TWO)*w**2 - & - 135135.0_8/TWO) * cos(6.0_8*phi) - ! l = 8, m = 7 - rn(16) = -(2.50682661696018_8 * w*(w2m1)**(7.0_8/TWO) * cos(7.0_8*phi)) - ! l = 8, m = 8 - rn(17) = 0.626706654240044_8 * (w2m1)**4 * cos(8.0_8*phi) - case (9) - ! l = 9, m = -9 - rn(1) = -(0.609049392175524_8 * (w2m1)**(9.0_8/TWO) * sin(9.0_8*phi)) - ! l = 9, m = -8 - rn(2) = 2.58397773170915_8 * w*(w2m1)**4 * sin(8.0_8*phi) - ! l = 9, m = -7 - rn(3) = -(4.37240315267812d-7*(w2m1)**(7.0_8/TWO)* & - ((34459425.0_8/TWO)*w**2 - 2027025.0_8/TWO) * sin(7.0_8*phi)) - ! l = 9, m = -6 - rn(4) = 3.02928976464514d-6*(w2m1)**3* & - ((11486475.0_8/TWO)*w**3 - 2027025.0_8/TWO*w) * sin(6.0_8*phi) - ! l = 9, m = -5 - rn(5) = -(2.34647776186144d-5*(w2m1)**(5.0_8/TWO)* & - ((11486475.0_8/8.0_8)*w**4 - 2027025.0_8/4.0_8 * w**2 + & - 135135.0_8/8.0_8) * sin(5.0_8*phi)) - ! l = 9, m = -4 - rn(6) = 0.000196320414650061_8 * (w2m1)**2*((2297295.0_8/8.0_8)*w**5 - & - 675675.0_8/4.0_8 * w**3 + (135135.0_8/8.0_8)*w) * sin(4.0_8*phi) - ! l = 9, m = -3 - rn(7) = -(0.00173385495536766_8 * (w2m1)**(THREE/TWO)* & - ((765765.0_8/16.0_8)*w**6 - 675675.0_8/16.0_8 * w**4 + & - (135135.0_8/16.0_8)*w**2 - 3465.0_8/16.0_8) * sin(THREE*phi)) - ! l = 9, m = -2 - rn(8) = 0.0158910431540932_8 * (w2m1)*((109395.0_8/16.0_8)*w**7- & - 135135.0_8/16.0_8 * w**5 + (45045.0_8/16.0_8)*w**3 & - - 3465.0_8/16.0_8 * w) * sin(TWO*phi) - ! l = 9, m = -1 - rn(9) = -(0.149071198499986_8*sqrt(w2m1)*((109395.0_8/128.0_8)*w**8 - & - 45045.0_8/32.0_8 * w**6 + (45045.0_8/64.0_8)*w**4 - 3465.0_8/32.0_8 & - * w**2 + 315.0_8/128.0_8) * sin(phi)) - ! l = 9, m = 0 - rn(10) = 94.9609375_8 * w**9 - 201.09375_8 * w**7 + 140.765625_8 * w**5- & - 36.09375_8 * w**3 + 2.4609375_8 * w - ! l = 9, m = 1 - rn(11) = -(0.149071198499986_8*sqrt(w2m1)*((109395.0_8/128.0_8)*w**8 - & - 45045.0_8/32.0_8 * w**6 + (45045.0_8/64.0_8)*w**4 -3465.0_8/32.0_8 & - * w**2 + 315.0_8/128.0_8) * cos(phi)) - ! l = 9, m = 2 - rn(12) = 0.0158910431540932_8 * (w2m1)*((109395.0_8/16.0_8)*w**7 - & - 135135.0_8/16.0_8 * w**5 + (45045.0_8/16.0_8)*w**3 & - - 3465.0_8/ 16.0_8 * w) * cos(TWO*phi) - ! l = 9, m = 3 - rn(13) = -(0.00173385495536766_8 * (w2m1)**(THREE/TWO)*((765765.0_8/16.0_8)& - *w**6 - 675675.0_8/16.0_8 * w**4 + (135135.0_8/16.0_8)*w**2 & - - 3465.0_8/16.0_8)* cos(THREE*phi)) - ! l = 9, m = 4 - rn(14) = 0.000196320414650061_8 * (w2m1)**2*((2297295.0_8/8.0_8)*w**5 - & - 675675.0_8/4.0_8 * w**3 + (135135.0_8/8.0_8)*w) * cos(4.0_8*phi) - ! l = 9, m = 5 - rn(15) = -(2.34647776186144d-5*(w2m1)**(5.0_8/TWO)*((11486475.0_8/8.0_8)* & - w**4 - 2027025.0_8/4.0_8 * w**2 + 135135.0_8/8.0_8) * cos(5.0_8*phi)) - ! l = 9, m = 6 - rn(16) = 3.02928976464514d-6*(w2m1)**3*((11486475.0_8/TWO)*w**3 - & - 2027025.0_8/TWO*w) * cos(6.0_8*phi) - ! l = 9, m = 7 - rn(17) = -(4.37240315267812d-7*(w2m1)**(7.0_8/TWO)* & - ((34459425.0_8/TWO)*w**2 - 2027025.0_8/TWO) * cos(7.0_8*phi)) - ! l = 9, m = 8 - rn(18) = 2.58397773170915_8 * w*(w2m1)**4 * cos(8.0_8*phi) - ! l = 9, m = 9 - rn(19) = -(0.609049392175524_8 * (w2m1)**(9.0_8/TWO) * cos(9.0_8*phi)) - case (10) - ! l = 10, m = -10 - rn(1) = 0.593627917136573_8 * (w2m1)**5 * sin(10.0_8*phi) - ! l = 10, m = -9 - rn(2) = -(2.65478475211798_8 * w*(w2m1)**(9.0_8/TWO) * sin(9.0_8*phi)) - ! l = 10, m = -8 - rn(3) = 2.49953651452314d-8*(w2m1)**4*((654729075.0_8/TWO)*w**2 - & - 34459425.0_8/TWO) * sin(8.0_8*phi) - ! l = 10, m = -7 - rn(4) = -(1.83677671621093d-7*(w2m1)**(7.0_8/TWO)* & - ((218243025.0_8/TWO)*w**3 - 34459425.0_8/TWO*w) * sin(7.0_8*phi)) - ! l = 10, m = -6 - rn(5) = 1.51464488232257d-6*(w2m1)**3*((218243025.0_8/8.0_8)*w**4 - & - 34459425.0_8/4.0_8 * w**2 + 2027025.0_8/8.0_8) * sin(6.0_8*phi) - ! l = 10, m = -5 - rn(6) = -(1.35473956745817d-5*(w2m1)**(5.0_8/TWO)* & - ((43648605.0_8/8.0_8)*w**5 - 11486475.0_8/4.0_8 * w**3 + & - (2027025.0_8/8.0_8)*w) * sin(5.0_8*phi)) - ! l = 10, m = -4 - rn(7) = 0.000128521880085575_8 * (w2m1)**2*((14549535.0_8/16.0_8)*w**6 - & - 11486475.0_8/16.0_8 * w**4 + (2027025.0_8/16.0_8)*w**2 - & - 45045.0_8/16.0_8) * sin(4.0_8*phi) - ! l = 10, m = -3 - rn(8) = -(0.00127230170115096_8 * (w2m1)**(THREE/TWO)* & - ((2078505.0_8/16.0_8)*w**7 - 2297295.0_8/16.0_8 * w**5 + & - (675675.0_8/16.0_8)*w**3 - 45045.0_8/16.0_8 * w) * sin(THREE*phi)) - ! l = 10, m = -2 - rn(9) = 0.012974982402692_8 * (w2m1)*((2078505.0_8/128.0_8)*w**8 - & - 765765.0_8/32.0_8 * w**6 + (675675.0_8/64.0_8)*w**4 - & - 45045.0_8/32.0_8 * w**2 + 3465.0_8/128.0_8) * sin(TWO*phi) - ! l = 10, m = -1 - rn(10) = -(0.134839972492648_8*sqrt(w2m1)*((230945.0_8/128.0_8)*w**9 - & - 109395.0_8/32.0_8 * w**7 + (135135.0_8/64.0_8)*w**5 - & - 15015.0_8/32.0_8 * w**3 + (3465.0_8/128.0_8)*w) * sin(phi)) - ! l = 10, m = 0 - rn(11) = 180.42578125_8 * w**10 - 427.32421875_8 * w**8 +351.9140625_8 & - * w**6 - 117.3046875_8 * w**4 + 13.53515625_8 * w**2 -0.24609375_8 - ! l = 10, m = 1 - rn(12) = -(0.134839972492648_8*sqrt(w2m1)*((230945.0_8/128.0_8)*w**9 - & - 109395.0_8/32.0_8 * w**7 + (135135.0_8/64.0_8)*w**5 -15015.0_8/ & - 32.0_8 * w**3 + (3465.0_8/128.0_8)*w) * cos(phi)) - ! l = 10, m = 2 - rn(13) = 0.012974982402692_8 * (w2m1)*((2078505.0_8/128.0_8)*w**8 - & - 765765.0_8/32.0_8 * w**6 + (675675.0_8/64.0_8)*w**4 -& - 45045.0_8/32.0_8 * w**2 + 3465.0_8/128.0_8) * cos(TWO*phi) - ! l = 10, m = 3 - rn(14) = -(0.00127230170115096_8 * (w2m1)**(THREE/TWO)* & - ((2078505.0_8/16.0_8)*w**7 - 2297295.0_8/16.0_8 * w**5 + & - (675675.0_8/16.0_8)*w**3 - 45045.0_8/16.0_8 * w) * cos(THREE*phi)) - ! l = 10, m = 4 - rn(15) = 0.000128521880085575_8 * (w2m1)**2*((14549535.0_8/16.0_8)*w**6 -& - 11486475.0_8/16.0_8 * w**4 + (2027025.0_8/16.0_8)*w**2 - & - 45045.0_8/16.0_8) * cos(4.0_8*phi) - ! l = 10, m = 5 - rn(16) = -(1.35473956745817d-5*(w2m1)**(5.0_8/TWO)* & - ((43648605.0_8/8.0_8)*w**5 - 11486475.0_8/4.0_8 * w**3 + & - (2027025.0_8/8.0_8)*w) * cos(5.0_8*phi)) - ! l = 10, m = 6 - rn(17) = 1.51464488232257d-6*(w2m1)**3*((218243025.0_8/8.0_8)*w**4 - & - 34459425.0_8/4.0_8 * w**2 + 2027025.0_8/8.0_8) * cos(6.0_8*phi) - ! l = 10, m = 7 - rn(18) = -(1.83677671621093d-7*(w2m1)**(7.0_8/TWO)* & - ((218243025.0_8/TWO)*w**3 - 34459425.0_8/TWO*w) * cos(7.0_8*phi)) - ! l = 10, m = 8 - rn(19) = 2.49953651452314d-8*(w2m1)**4* & - ((654729075.0_8/TWO)*w**2 - 34459425.0_8/TWO) * cos(8.0_8*phi) - ! l = 10, m = 9 - rn(20) = -(2.65478475211798_8 * w*(w2m1)**(9.0_8/TWO) * cos(9.0_8*phi)) - ! l = 10, m = 10 - rn(21) = 0.593627917136573_8 * (w2m1)**5 * cos(10.0_8*phi) - case default - rn = ONE - end select + call calc_rn_cc(n, uvw, rn) end subroutine calc_rn @@ -693,26 +299,6 @@ contains end do end subroutine calc_zn -!=============================================================================== -! EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients -! and the value of x -!=============================================================================== - - pure function evaluate_legendre(n, data, x) result(val) bind(C) - integer(C_INT), intent(in) :: n - real(C_DOUBLE), intent(in) :: data(n) - real(C_DOUBLE), intent(in) :: x - real(C_DOUBLE) :: val - - integer(C_INT) :: l - - val = HALF * data(1) - do l = 1, n - 1 - val = val + (real(l, 8) + HALF) * data(l + 1) * calc_pn(l,x) - end do - - end function evaluate_legendre - !=============================================================================== ! ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is ! mu and through an azimuthal angle sampled uniformly. Note that this is done diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 3b645c772..6ddf944a7 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -161,6 +161,22 @@ double __attribute__ ((const)) calc_pn_c(int n, double x) { return pnx; } +//============================================================================== +// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients +// and the value of x +//============================================================================== + +double __attribute__ ((const)) evaluate_legendre_c(int n, double data[], + double x) { + double val; + + val = 0.5 * data[0]; + for (int l = 1; l < n; l++) { + val += (static_cast(l) + 0.5) * data[l] * calc_pn_c(l, x); + } + return val; +} + //============================================================================== // CALC_RN calculates the n-th order spherical harmonics for a given angle // (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) @@ -561,21 +577,6 @@ void calc_rn_c(int n, double uvw[3], double rn[]){ } } -//============================================================================== -// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients -// and the value of x -//============================================================================== - -double __attribute__ ((const)) evaluate_legendre_c(int n, double data[], - double x) { - double val; - - val = 0.5 * data[0]; - for (int l = 1; l < n; l++) { - val += (static_cast(l) + 0.5) * data[l] * calc_pn_c(l, x); - } - -} //============================================================================== // ROTATE_ANGLE rotates direction std::cosines through a polar angle whose diff --git a/src/math_functions.h b/src/math_functions.h index aa048e5f6..bea30cf91 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -32,13 +32,6 @@ extern "C" double t_percentile_c(double p, int df) __attribute__ ((const)); extern "C" double calc_pn_c(int n, double x) __attribute__ ((const)); -//============================================================================== -// CALC_RN calculates the n-th order spherical harmonics for a given angle -// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) -//============================================================================== - -extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); - //============================================================================== // EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients // and the value of x @@ -47,6 +40,13 @@ extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); extern "C" double evaluate_legendre_c(int n, double data[], double x) __attribute__ ((const)); +//============================================================================== +// CALC_RN calculates the n-th order spherical harmonics for a given angle +// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) +//============================================================================== + +extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); + //============================================================================== // ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is // mu and through an azimuthal angle sampled uniformly. Note that this is done diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index 98295aefb..c8d33e601 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -51,6 +51,25 @@ def test_calc_pn(): assert np.allclose(ref_vals, test_vals) +def test_evaluate_legendre(): + max_order = 10 + # Coefficients are set to 1, but will incorporate the (2l+1)/2 norm factor + # for the reference solution + test_coeffs = [0.5 * (2. * l + 1.) for l in range(max_order + 1)] + test_xs = np.linspace(-1., 1., num=5, endpoint=True) + + ref_vals = np.polynomial.legendre.legval(test_xs, test_coeffs) + + # Set the coefficients back to 1s for the test values since + # evaluate legendre includes the (2l+1)/2 term + test_coeffs = [1. for l in range(max_order + 1)] + + test_vals = np.array([openmc.capi.math.evaluate_legendre(test_coeffs, x) + for x in test_xs]) + + assert np.allclose(ref_vals, test_vals) + + def test_calc_rn(): max_order = 10 test_ns = np.array([i for i in range(0, max_order + 1)]) @@ -99,23 +118,6 @@ def test_calc_zn(): pass -def test_evaluate_legendre(): - max_order = 10 - # Coefficients are set to 1, but will incorporate the (2l+1)/2 norm factor - # for the reference solution - test_coeffs = [0.5 * (2. * l + 1.) for l in range(max_order + 1)] - test_xs = np.linspace(-1., 1., num=5, endpoint=True) - - ref_vals = np.polynomial.legendre.legval(test_xs, test_coeffs) - - # Set the coefficients back to 1s for the test values - test_coeffs = [1. for l in range(max_order + 1)] - test_vals = np.array([openmc.capi.math.evaluate_legendre(test_coeffs, x) - for x in test_xs]) - - assert np.allclose(ref_vals, test_vals) - - def test_rotate_angle(): uvw0 = np.array([1., 0., 0.]) phi = 0. From b61c67a12cd34f8f9a7c5641c73f98622a413ad4 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Tue, 1 May 2018 19:17:21 -0400 Subject: [PATCH 236/361] fixes per request of @paulromano: fixing test scratch files vice using .gitignore, minor typo in docs, and some otherwise small code cleanups --- .gitignore | 3 -- docs/source/usersguide/tallies.rst | 2 +- openmc/mgxs/mgxs.py | 2 +- openmc/tallies.py | 3 +- src/summary.F90 | 2 -- src/tallies/tally.F90 | 38 ++++++--------------- tests/regression_tests/tallies/test.py | 47 +++++++++++++------------- tests/unit_tests/test_data_neutron.py | 6 ++-- 8 files changed, 39 insertions(+), 64 deletions(-) diff --git a/.gitignore b/.gitignore index 0fc2c63bc..ffdd58d33 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,3 @@ examples/jupyter/plots .python-version .coverage htmlcov - -# Test data -tests/xsdir diff --git a/docs/source/usersguide/tallies.rst b/docs/source/usersguide/tallies.rst index 19691e32b..bbec01642 100644 --- a/docs/source/usersguide/tallies.rst +++ b/docs/source/usersguide/tallies.rst @@ -24,7 +24,7 @@ function (:math:`f` in the above equation) should be used. The regions of phase space are generally called *filters* and the scoring functions are simply called *scores*. -The only cases when *filters* do not correspond directly with the regions of +The only cases when filters do not correspond directly with the regions of phase space are when expansion functions are applied in the integrand, such as for Legendre expansions of the scattering kernel. diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 81b4420d9..46f48fdfc 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -1925,7 +1925,7 @@ class MGXS(metaclass=ABCMeta): # Add the Legendre bin to the column if it exists if 'legendre' in df: - columns += ['legendre'] + columns.append('legendre') # If user requested micro cross sections, divide out the atom densities if xs_type == 'micro': diff --git a/openmc/tallies.py b/openmc/tallies.py index 1ac89129a..a5341cbed 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -391,8 +391,7 @@ class Tally(IDManagerMixin): 'nu-scatter-p', 'scatter-y', 'nu-scatter-y', 'flux-y', 'total-y']: if score.startswith(deprecated): - msg = score.strip() + ' is deprecated and should no ' \ - 'longer be used.' + msg = score.strip() + ' is no longer supported.' raise ValueError(msg) scores[i] = score.strip() diff --git a/src/summary.F90 b/src/summary.F90 index 409c4de1f..a245648ef 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -459,8 +459,6 @@ contains ! Find the number of macroscopic and nuclide data in this material num_nuclides = 0 num_macros = 0 - k = 1 - n = 1 do j = 1, m % n_nuclides if (nuclides_MG(m % nuclide(j)) % obj % awr /= MACROSCOPIC_AWR) then num_nuclides = num_nuclides + 1 diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 483246b36..2e1d6184c 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -1199,8 +1199,9 @@ contains !######################################################################### ! Expand score if necessary and add to tally results. - call expand_and_score(p, t, score_index, filter_index, score_bin, & - score, i) +!$omp atomic + t % results(RESULT_VALUE, score_index, filter_index) = & + t % results(RESULT_VALUE, score_index, filter_index) + score end do SCORE_LOOP end subroutine score_general_ce @@ -1982,36 +1983,15 @@ contains !######################################################################### ! Expand score if necessary and add to tally results. - call expand_and_score(p, t, score_index, filter_index, score_bin, & - score, i) +!$omp atomic + t % results(RESULT_VALUE, score_index, filter_index) = & + t % results(RESULT_VALUE, score_index, filter_index) + score end do SCORE_LOOP nullify(matxs, nucxs) end subroutine score_general_mg -!=============================================================================== -! EXPAND_AND_SCORE takes a previously determined score value and adjusts it -! if necessary (for functional expansion weighting), and then adds the resultant -! value to the tally results array. -!=============================================================================== - - subroutine expand_and_score(p, t, score_index, filter_index, score_bin, & - score, i) - type(Particle), intent(in) :: p - type(TallyObject), intent(inout) :: t - integer, intent(inout) :: score_index - integer, intent(in) :: filter_index ! for % results - integer, intent(in) :: score_bin ! score of concern - real(8), intent(inout) :: score ! data to score - integer, intent(inout) :: i ! Working index - -!$omp atomic - t % results(RESULT_VALUE, score_index, filter_index) = & - t % results(RESULT_VALUE, score_index, filter_index) + score - - end subroutine expand_and_score - !=============================================================================== ! SCORE_ALL_NUCLIDES tallies individual nuclide reaction rates specifically when ! the user requests all. @@ -2961,8 +2941,10 @@ contains score_index = q ! Expand score if necessary and add to tally results. - call expand_and_score(p, t, score_index, filter_index, score_bin, & - score, k) +!$omp atomic + t % results(RESULT_VALUE, score_index, filter_index) = & + t % results(RESULT_VALUE, score_index, filter_index) + score + end do SCORE_LOOP ! ====================================================================== diff --git a/tests/regression_tests/tallies/test.py b/tests/regression_tests/tallies/test.py index 53ce60b62..b86d71d00 100644 --- a/tests/regression_tests/tallies/test.py +++ b/tests/regression_tests/tallies/test.py @@ -18,12 +18,12 @@ def test_tallies(): azimuthal_bins = (-3.14159, -1.8850, -0.6283, 0.6283, 1.8850, 3.14159) azimuthal_filter = AzimuthalFilter(azimuthal_bins) - azimuthal_tally1 = Tally(tally_id=1) + azimuthal_tally1 = Tally() azimuthal_tally1.filters = [azimuthal_filter] azimuthal_tally1.scores = ['flux'] azimuthal_tally1.estimator = 'tracklength' - azimuthal_tally2 = Tally(tally_id=2) + azimuthal_tally2 = Tally() azimuthal_tally2.filters = [azimuthal_filter] azimuthal_tally2.scores = ['flux'] azimuthal_tally2.estimator = 'analog' @@ -33,38 +33,38 @@ def test_tallies(): mesh_2x2.upper_right = [182.07, 182.07] mesh_2x2.dimension = [2, 2] mesh_filter = MeshFilter(mesh_2x2) - azimuthal_tally3 = Tally(tally_id=3) + azimuthal_tally3 = Tally() azimuthal_tally3.filters = [azimuthal_filter, mesh_filter] azimuthal_tally3.scores = ['flux'] azimuthal_tally3.estimator = 'tracklength' - cellborn_tally = Tally(tally_id=4) + cellborn_tally = Tally() cellborn_tally.filters = [ CellbornFilter((model.geometry.get_all_cells()[10], model.geometry.get_all_cells()[21], 22, 23))] # Test both Cell objects and ids cellborn_tally.scores = ['total'] - dg_tally = Tally(tally_id=5) + dg_tally = Tally() dg_tally.filters = [DelayedGroupFilter((1, 2, 3, 4, 5, 6))] dg_tally.scores = ['delayed-nu-fission'] four_groups = (0.0, 0.253, 1.0e3, 1.0e6, 20.0e6) energy_filter = EnergyFilter(four_groups) - energy_tally = Tally(tally_id=6) + energy_tally = Tally() energy_tally.filters = [energy_filter] energy_tally.scores = ['total'] energyout_filter = EnergyoutFilter(four_groups) - energyout_tally = Tally(tally_id=7) + energyout_tally = Tally() energyout_tally.filters = [energyout_filter] energyout_tally.scores = ['scatter'] - transfer_tally = Tally(tally_id=8) + transfer_tally = Tally() transfer_tally.filters = [energy_filter, energyout_filter] transfer_tally.scores = ['scatter', 'nu-fission'] - material_tally = Tally(tally_id=9) + material_tally = Tally() material_tally.filters = [ MaterialFilter((model.geometry.get_materials_by_name('UOX fuel')[0], model.geometry.get_materials_by_name('Zircaloy')[0], @@ -73,56 +73,56 @@ def test_tallies(): mu_bins = (-1.0, -0.5, 0.0, 0.5, 1.0) mu_filter = MuFilter(mu_bins) - mu_tally1 = Tally(tally_id=10) + mu_tally1 = Tally() mu_tally1.filters = [mu_filter] mu_tally1.scores = ['scatter', 'nu-scatter'] print('mu_tally1', mu_tally1.id) - mu_tally2 = Tally(tally_id=11) + mu_tally2 = Tally() mu_tally2.filters = [mu_filter, mesh_filter] mu_tally2.scores = ['scatter', 'nu-scatter'] polar_bins = (0.0, 0.6283, 1.2566, 1.8850, 2.5132, 3.14159) polar_filter = PolarFilter(polar_bins) - polar_tally1 = Tally(tally_id=12) + polar_tally1 = Tally() polar_tally1.filters = [polar_filter] polar_tally1.scores = ['flux'] polar_tally1.estimator = 'tracklength' - polar_tally2 = Tally(tally_id=13) + polar_tally2 = Tally() polar_tally2.filters = [polar_filter] polar_tally2.scores = ['flux'] polar_tally2.estimator = 'analog' - polar_tally3 = Tally(tally_id=14) + polar_tally3 = Tally() polar_tally3.filters = [polar_filter, mesh_filter] polar_tally3.scores = ['flux'] polar_tally3.estimator = 'tracklength' legendre_filter = LegendreFilter(order=4) - legendre_tally = Tally(tally_id=15) + legendre_tally = Tally() legendre_tally.filters = [legendre_filter] legendre_tally.scores = ['scatter', 'nu-scatter'] legendre_tally.estimatir = 'analog' print('legendre_tally', mu_tally1.id) harmonics_filter = SphericalHarmonicsFilter(order=4) - harmonics_tally = Tally(tally_id=16) + harmonics_tally = Tally() harmonics_tally.filters = [harmonics_filter] harmonics_tally.scores = ['scatter', 'nu-scatter', 'flux', 'total'] harmonics_tally.estimatir = 'analog' - harmonics_tally2 = Tally(tally_id=17) + harmonics_tally2 = Tally() harmonics_tally2.filters = [harmonics_filter] harmonics_tally2.scores = ['flux', 'total'] harmonics_tally2.estimatir = 'collision' - harmonics_tally3 = Tally(tally_id=18) + harmonics_tally3 = Tally() harmonics_tally3.filters = [harmonics_filter] harmonics_tally3.scores = ['flux', 'total'] harmonics_tally3.estimatir = 'tracklength' - universe_tally = Tally(tally_id=19) + universe_tally = Tally() universe_tally.filters = [ UniverseFilter((model.geometry.get_all_universes()[1], model.geometry.get_all_universes()[2], @@ -132,8 +132,7 @@ def test_tallies(): cell_filter = CellFilter((model.geometry.get_all_cells()[10], model.geometry.get_all_cells()[21], 22, 23, 60)) # Test both Cell objects and ids - score_tallies = [Tally(tally_id=20), Tally(tally_id=21), - Tally(tally_id=22)] + score_tallies = [Tally(), Tally(), Tally()] for t in score_tallies: t.filters = [cell_filter] t.scores = ['absorption', 'delayed-nu-fission', 'events', 'fission', @@ -146,7 +145,7 @@ def test_tallies(): score_tallies[2].estimator = 'collision' cell_filter2 = CellFilter((21, 22, 23, 27, 28, 29, 60)) - flux_tallies = [Tally(tally_id=23 + i) for i in range(3)] + flux_tallies = [Tally() for i in range(3)] for t in flux_tallies: t.filters = [cell_filter2] t.scores = ['flux'] @@ -154,7 +153,7 @@ def test_tallies(): flux_tallies[1].estimator = 'analog' flux_tallies[2].estimator = 'collision' - total_tallies = [Tally(tally_id=26 + i) for i in range(3)] + total_tallies = [Tally() for i in range(3)] for t in total_tallies: t.filters = [cell_filter] t.scores = ['total'] @@ -163,7 +162,7 @@ def test_tallies(): total_tallies[1].estimator = 'analog' total_tallies[2].estimator = 'collision' - all_nuclide_tallies = [Tally(tally_id=29 + i) for i in range(4)] + all_nuclide_tallies = [Tally() for i in range(4)] for t in all_nuclide_tallies: t.filters = [cell_filter] t.estimator = 'tracklength' diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index 5713bfbc5..03746430d 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -345,10 +345,10 @@ def test_nbody(tmpdir, h2): assert nbody1.q_value == nbody2.q_value -def test_ace_convert(tmpdir): +def test_ace_convert(run_in_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')) + ace_ascii = 'ace_ascii' + ace_binary = 'ace_binary' openmc.data.njoy.make_ace(filename, ace=ace_ascii) # Convert to binary From 2de47ec885a9a6946f90c19abc1343d0ef4c46f8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 1 May 2018 21:02:43 -0500 Subject: [PATCH 237/361] Fix calls to H5Screate_simple --- src/state_point.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/state_point.cpp b/src/state_point.cpp index 2ea9d1c43..4e6b9a9f9 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -39,13 +39,13 @@ write_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) #ifdef PHDF5 // Set size of total dataspace for all procs and rank hsize_t dims[] {static_cast(n_particles)}; - hid_t dspace = H5Screate_simple(1, dims, H5P_DEFAULT); + hid_t dspace = H5Screate_simple(1, dims, nullptr); hid_t dset = H5Dcreate(group_id, "source_bank", banktype, dspace, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); // Create another data space but for each proc individually hsize_t count[] {static_cast(openmc_work)}; - hid_t memspace = H5Screate_simple(1, count, H5P_DEFAULT); + hid_t memspace = H5Screate_simple(1, count, nullptr); // Select hyperslab for this dataspace hsize_t start[] {static_cast(work_index[openmc::mpi::rank])}; @@ -69,7 +69,7 @@ write_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) if (openmc_master) { // Create dataset big enough to hold all source sites hsize_t dims[] {static_cast(n_particles)}; - hid_t dspace = H5Screate_simple(1, dims, H5P_DEFAULT); + hid_t dspace = H5Screate_simple(1, dims, nullptr); hid_t dset = H5Dcreate(group_id, "source_bank", banktype, dspace, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); @@ -81,7 +81,7 @@ write_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) for (int i = 0; i < openmc::mpi::n_procs; ++i) { // Create memory space hsize_t count[] {static_cast(work_index[i+1] - work_index[i])}; - hid_t memspace = H5Screate_simple(1, count, H5P_DEFAULT); + hid_t memspace = H5Screate_simple(1, count, nullptr); #ifdef OPENMC_MPI // Receive source sites from other processes @@ -130,7 +130,7 @@ void read_source_bank(hid_t group_id, int64_t* work_index, Bank* source_bank) // Create another data space but for each proc individually hsize_t dims[] {static_cast(openmc_work)}; - hid_t memspace = H5Screate_simple(1, dims, H5P_DEFAULT); + hid_t memspace = H5Screate_simple(1, dims, nullptr); // Make sure source bank is big enough hid_t dspace = H5Dget_space(dset); From 9d5275ca263117656290070647aa1b01eb73a297 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 1 May 2018 22:22:18 -0500 Subject: [PATCH 238/361] Support passing command-line arguments in openmc.capi.init() --- openmc/capi/core.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/openmc/capi/core.py b/openmc/capi/core.py index 273f0c0c7..ad78f06c3 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -1,6 +1,6 @@ from contextlib import contextmanager -from ctypes import (CDLL, c_int, c_int32, c_int64, c_double, c_char_p, - POINTER, Structure, c_void_p) +from ctypes import (CDLL, c_int, c_int32, c_int64, c_double, c_char_p, c_char, + POINTER, Structure, c_void_p, create_string_buffer) from warnings import warn import numpy as np @@ -30,7 +30,7 @@ _dll.openmc_find.restype = c_int _dll.openmc_find.errcheck = _error_handler _dll.openmc_hard_reset.restype = c_int _dll.openmc_hard_reset.errcheck = _error_handler -_dll.openmc_init.argtypes = [c_int, POINTER(c_char_p), c_void_p] +_dll.openmc_init.argtypes = [c_int, POINTER(POINTER(c_char)), c_void_p] _dll.openmc_init.restype = c_int _dll.openmc_init.errcheck = _error_handler _dll.openmc_get_keff.argtypes = [POINTER(c_double*2)] @@ -115,15 +115,29 @@ def hard_reset(): _dll.openmc_hard_reset() -def init(intracomm=None): +def init(args=None, intracomm=None): """Initialize OpenMC Parameters ---------- + args : list of str + Command-line arguments intracomm : mpi4py.MPI.Intracomm or None MPI intracommunicator """ + if args is not None: + args = ['openmc'] + list(args) + argc = len(args) + + # Create the argv array. Note that it is actually expected to be of + # length argc + 1 with the final item being a null pointer. + argv = (POINTER(c_char) * (argc + 1))() + for i, arg in enumerate(args): + argv[i] = create_string_buffer(arg.encode()) + else: + argc = 0 + if intracomm is not None: # If an mpi4py communicator was passed, convert it to void* to be passed # to openmc_init @@ -135,7 +149,7 @@ def init(intracomm=None): address = MPI._addressof(intracomm) intracomm = c_void_p(address) - _dll.openmc_init(0, None, intracomm) + _dll.openmc_init(argc, argv, intracomm) def iter_batches(): From 6b1c0f907f991e84b497122448f342392f60b1b0 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 2 May 2018 06:21:09 -0500 Subject: [PATCH 239/361] Fix openmc.capi.init() --- openmc/capi/core.py | 1 + openmc/deplete/operator.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openmc/capi/core.py b/openmc/capi/core.py index ad78f06c3..e044f9504 100644 --- a/openmc/capi/core.py +++ b/openmc/capi/core.py @@ -137,6 +137,7 @@ def init(args=None, intracomm=None): argv[i] = create_string_buffer(arg.encode()) else: argc = 0 + argv = None if intracomm is not None: # If an mpi4py communicator was passed, convert it to void* to be passed diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index a1d8ffb0b..b40ff6344 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -268,7 +268,7 @@ class Operator(TransportOperator): # Initialize OpenMC library comm.barrier() - openmc.capi.init(comm) + openmc.capi.init(intracomm=comm) # Generate tallies in memory self._generate_tallies() From 3ab959cc3e9b9c9a3f90b6a96117fb1b032263a3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 2 May 2018 15:23:48 -0500 Subject: [PATCH 240/361] Fix bug when no outer lattice universe is defined --- src/geometry.F90 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index 0bacd7b5d..e747b619a 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -355,9 +355,10 @@ contains else ! Particle is outside the lattice. if (lat % outer == NO_OUTER_UNIVERSE) then - call p % mark_as_lost("Particle " // trim(to_str(p %id)) & + call warning("Particle " // trim(to_str(p %id)) & // " is outside lattice " // trim(to_str(lat % id)) & // " but the lattice has no defined outer universe.") + found = .false. return else p % coord(j + 1) % universe = lat % outer From 968c12deb515cecdf29aa0fa1de40d6f65dd4a06 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sat, 5 May 2018 09:24:21 -0400 Subject: [PATCH 241/361] Fixed consistent scatter matrix filter ordering so tests come out consistently and added a mgxs_library_correction and mgxs_library_histogram test --- openmc/mgxs/mgxs.py | 10 + .../mgxs_library_correction/__init__.py | 0 .../mgxs_library_correction/inputs_true.dat | 392 +++++++++++++ .../mgxs_library_correction/results_true.dat | 60 ++ .../mgxs_library_correction/test.py | 63 ++ .../mgxs_library_histogram/__init__.py | 0 .../mgxs_library_histogram/inputs_true.dat | 287 ++++++++++ .../mgxs_library_histogram/results_true.dat | 540 ++++++++++++++++++ .../mgxs_library_histogram/test.py | 64 +++ 9 files changed, 1416 insertions(+) create mode 100644 tests/regression_tests/mgxs_library_correction/__init__.py create mode 100644 tests/regression_tests/mgxs_library_correction/inputs_true.dat create mode 100644 tests/regression_tests/mgxs_library_correction/results_true.dat create mode 100644 tests/regression_tests/mgxs_library_correction/test.py create mode 100644 tests/regression_tests/mgxs_library_histogram/__init__.py create mode 100644 tests/regression_tests/mgxs_library_histogram/inputs_true.dat create mode 100644 tests/regression_tests/mgxs_library_histogram/results_true.dat create mode 100644 tests/regression_tests/mgxs_library_histogram/test.py diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 5bc00cbc9..1d8ace282 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -4003,6 +4003,15 @@ class ScatterMatrixXS(MatrixMGXS): correction.nuclides = scatter_p1.nuclides self._xs_tally -= correction + # If the mu filter is before the group out filter swap them + if self.scatter_format == 'histogram': + tally = self._xs_tally + filt = tally.filters + eout_filter = tally.find_filter(openmc.EnergyoutFilter) + angle_filter = tally.find_filter(openmc.MuFilter) + if filt.index(eout_filter) > filt.index(angle_filter): + tally._swap_filters(eout_filter, angle_filter) + self._compute_xs() return self._xs_tally @@ -4466,6 +4475,7 @@ class ScatterMatrixXS(MatrixMGXS): """ + print(self.xs_tally.filters) df = super().get_pandas_dataframe(groups, nuclides, xs_type, paths) if self.scatter_format == 'legendre': diff --git a/tests/regression_tests/mgxs_library_correction/__init__.py b/tests/regression_tests/mgxs_library_correction/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mgxs_library_correction/inputs_true.dat b/tests/regression_tests/mgxs_library_correction/inputs_true.dat new file mode 100644 index 000000000..d9793bc3e --- /dev/null +++ b/tests/regression_tests/mgxs_library_correction/inputs_true.dat @@ -0,0 +1,392 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 10 + 5 + + + -0.63 -0.63 -1 0.63 0.63 1 + + + + + + + 1 + + + 0.0 0.625 20000000.0 + + + 0.0 0.625 20000000.0 + + + 2 + + + 3 + + + 1 2 + total + flux + analog + + + 1 2 3 + total + scatter-0 + analog + + + 1 3 + total + scatter-1 + analog + + + 1 2 + total + flux + analog + + + 1 2 3 + total + nu-scatter-0 + analog + + + 1 3 + total + nu-scatter-1 + analog + + + 1 2 + total + flux + tracklength + + + 1 2 + total + scatter + tracklength + + + 1 2 3 + total + scatter-0 + analog + + + 1 3 + total + scatter-1 + analog + + + 1 2 + total + flux + analog + + + 1 2 + total + flux + tracklength + + + 1 2 + total + scatter + tracklength + + + 1 2 3 + total + scatter-0 + analog + + + 1 2 3 + total + nu-scatter-0 + analog + + + 1 2 3 + total + scatter-0 + analog + + + 1 3 + total + nu-scatter-1 + analog + + + 1 2 + total + flux + analog + + + 13 2 + total + flux + analog + + + 13 2 3 + total + scatter-0 + analog + + + 13 3 + total + scatter-1 + analog + + + 13 2 + total + flux + analog + + + 13 2 3 + total + nu-scatter-0 + analog + + + 13 3 + total + nu-scatter-1 + analog + + + 13 2 + total + flux + tracklength + + + 13 2 + total + scatter + tracklength + + + 13 2 3 + total + scatter-0 + analog + + + 13 3 + total + scatter-1 + analog + + + 13 2 + total + flux + analog + + + 13 2 + total + flux + tracklength + + + 13 2 + total + scatter + tracklength + + + 13 2 3 + total + scatter-0 + analog + + + 13 2 3 + total + nu-scatter-0 + analog + + + 13 2 3 + total + scatter-0 + analog + + + 13 3 + total + nu-scatter-1 + analog + + + 13 2 + total + flux + analog + + + 25 2 + total + flux + analog + + + 25 2 3 + total + scatter-0 + analog + + + 25 3 + total + scatter-1 + analog + + + 25 2 + total + flux + analog + + + 25 2 3 + total + nu-scatter-0 + analog + + + 25 3 + total + nu-scatter-1 + analog + + + 25 2 + total + flux + tracklength + + + 25 2 + total + scatter + tracklength + + + 25 2 3 + total + scatter-0 + analog + + + 25 3 + total + scatter-1 + analog + + + 25 2 + total + flux + analog + + + 25 2 + total + flux + tracklength + + + 25 2 + total + scatter + tracklength + + + 25 2 3 + total + scatter-0 + analog + + + 25 2 3 + total + nu-scatter-0 + analog + + + 25 2 3 + total + scatter-0 + analog + + + 25 3 + total + nu-scatter-1 + analog + + + 25 2 + total + flux + analog + + diff --git a/tests/regression_tests/mgxs_library_correction/results_true.dat b/tests/regression_tests/mgxs_library_correction/results_true.dat new file mode 100644 index 000000000..f7e288802 --- /dev/null +++ b/tests/regression_tests/mgxs_library_correction/results_true.dat @@ -0,0 +1,60 @@ + material group in group out nuclide mean std. dev. +3 1 1 1 total 0.332466 0.026533 +2 1 1 2 total 0.000989 0.000482 +1 1 2 1 total 0.000925 0.000925 +0 1 2 2 total 0.396146 0.015511 + material group in group out nuclide mean std. dev. +3 1 1 1 total 0.332466 0.026533 +2 1 1 2 total 0.000989 0.000482 +1 1 2 1 total 0.000925 0.000925 +0 1 2 2 total 0.396146 0.015511 + material group in group out nuclide mean std. dev. +3 1 1 1 total 0.334690 0.037288 +2 1 1 2 total 0.000995 0.000489 +1 1 2 1 total 0.000887 0.000889 +0 1 2 2 total 0.379453 0.030118 + material group in group out nuclide mean std. dev. +3 1 1 1 total 0.334690 0.048073 +2 1 1 2 total 0.000995 0.000841 +1 1 2 1 total 0.000887 0.001538 +0 1 2 2 total 0.379453 0.034216 + material group in group out nuclide mean std. dev. +3 2 1 1 total 0.271891 0.032748 +2 2 1 2 total 0.000000 0.000000 +1 2 2 1 total 0.000000 0.000000 +0 2 2 2 total 0.307478 0.047512 + material group in group out nuclide mean std. dev. +3 2 1 1 total 0.271891 0.032748 +2 2 1 2 total 0.000000 0.000000 +1 2 2 1 total 0.000000 0.000000 +0 2 2 2 total 0.307478 0.047512 + material group in group out nuclide mean std. dev. +3 2 1 1 total 0.273933 0.038207 +2 2 1 2 total 0.000000 0.000000 +1 2 2 1 total 0.000000 0.000000 +0 2 2 2 total 0.306635 0.052777 + material group in group out nuclide mean std. dev. +3 2 1 1 total 0.273933 0.051116 +2 2 1 2 total 0.000000 0.000000 +1 2 2 1 total 0.000000 0.000000 +0 2 2 2 total 0.306635 0.067497 + material group in group out nuclide mean std. dev. +3 3 1 1 total 0.258652 0.022623 +2 3 1 2 total 0.031368 0.001728 +1 3 2 1 total 0.000443 0.000445 +0 3 2 2 total 1.482300 0.232653 + material group in group out nuclide mean std. dev. +3 3 1 1 total 0.258652 0.022623 +2 3 1 2 total 0.031368 0.001728 +1 3 2 1 total 0.000443 0.000445 +0 3 2 2 total 1.482300 0.232653 + material group in group out nuclide mean std. dev. +3 3 1 1 total 0.251610 0.041472 +2 3 1 2 total 0.031023 0.002232 +1 3 2 1 total 0.000440 0.000445 +0 3 2 2 total 1.467612 0.356408 + material group in group out nuclide mean std. dev. +3 3 1 1 total 0.251610 0.048135 +2 3 1 2 total 0.031023 0.003064 +1 3 2 1 total 0.000440 0.000765 +0 3 2 2 total 1.467612 0.449931 diff --git a/tests/regression_tests/mgxs_library_correction/test.py b/tests/regression_tests/mgxs_library_correction/test.py new file mode 100644 index 000000000..05eedfef8 --- /dev/null +++ b/tests/regression_tests/mgxs_library_correction/test.py @@ -0,0 +1,63 @@ +import hashlib + +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): + # Generate inputs using parent class routine + super().__init__(*args, **kwargs) + + # Initialize a two-group structure + energy_groups = openmc.mgxs.EnergyGroups(group_edges=[0, 0.625, 20.e6]) + + # Initialize MGXS Library for a few cross section types + self.mgxs_lib = openmc.mgxs.Library(self._model.geometry) + self.mgxs_lib.by_nuclide = False + + # Test all MGXS types + self.mgxs_lib.mgxs_types = ['scatter matrix', 'nu-scatter matrix', + 'consistent scatter matrix', + 'consistent nu-scatter matrix'] + self.mgxs_lib.energy_groups = energy_groups + self.mgxs_lib.correction = 'P0' + self.mgxs_lib.domain_type = 'material' + self.mgxs_lib.build_library() + + # Add tallies + self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) + + def _get_results(self, hash_output=False): + """Digest info in the statepoint and return as a string.""" + + # Read the statepoint file. + sp = openmc.StatePoint(self._sp_name) + + # Load the MGXS library from the statepoint + self.mgxs_lib.load_from_statepoint(sp) + + # Build a string from Pandas Dataframe for each MGXS + outstr = '' + for domain in self.mgxs_lib.domains: + for mgxs_type in self.mgxs_lib.mgxs_types: + mgxs = self.mgxs_lib.get_mgxs(domain, mgxs_type) + df = mgxs.get_pandas_dataframe() + outstr += df.to_string() + '\n' + + # Hash the results if necessary + if hash_output: + sha512 = hashlib.sha512() + sha512.update(outstr.encode('utf-8')) + outstr = sha512.hexdigest() + + return outstr + + +def test_mgxs_library_correction(): + model = pwr_pin_cell() + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.main() diff --git a/tests/regression_tests/mgxs_library_histogram/__init__.py b/tests/regression_tests/mgxs_library_histogram/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/mgxs_library_histogram/inputs_true.dat b/tests/regression_tests/mgxs_library_histogram/inputs_true.dat new file mode 100644 index 000000000..29c46c937 --- /dev/null +++ b/tests/regression_tests/mgxs_library_histogram/inputs_true.dat @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 100 + 10 + 5 + + + -0.63 -0.63 -1 0.63 0.63 1 + + + + + + + 1 + + + 0.0 0.625 20000000.0 + + + 0.0 0.625 20000000.0 + + + -1.0 -0.818181818182 -0.636363636364 -0.454545454545 -0.272727272727 -0.0909090909091 0.0909090909091 0.272727272727 0.454545454545 0.636363636364 0.818181818182 1.0 + + + 2 + + + 3 + + + 1 2 + total + flux + analog + + + 1 2 3 4 + total + scatter + analog + + + 1 2 + total + flux + analog + + + 1 2 3 4 + total + nu-scatter + analog + + + 1 2 + total + flux + tracklength + + + 1 2 + total + scatter + tracklength + + + 1 2 3 4 + total + scatter-0 + analog + + + 1 2 + total + flux + tracklength + + + 1 2 + total + scatter + tracklength + + + 1 2 3 4 + total + scatter-0 + analog + + + 1 2 3 + total + nu-scatter-0 + analog + + + 1 2 3 + total + scatter-0 + analog + + + 17 2 + total + flux + analog + + + 17 2 3 4 + total + scatter + analog + + + 17 2 + total + flux + analog + + + 17 2 3 4 + total + nu-scatter + analog + + + 17 2 + total + flux + tracklength + + + 17 2 + total + scatter + tracklength + + + 17 2 3 4 + total + scatter-0 + analog + + + 17 2 + total + flux + tracklength + + + 17 2 + total + scatter + tracklength + + + 17 2 3 4 + total + scatter-0 + analog + + + 17 2 3 + total + nu-scatter-0 + analog + + + 17 2 3 + total + scatter-0 + analog + + + 33 2 + total + flux + analog + + + 33 2 3 4 + total + scatter + analog + + + 33 2 + total + flux + analog + + + 33 2 3 4 + total + nu-scatter + analog + + + 33 2 + total + flux + tracklength + + + 33 2 + total + scatter + tracklength + + + 33 2 3 4 + total + scatter-0 + analog + + + 33 2 + total + flux + tracklength + + + 33 2 + total + scatter + tracklength + + + 33 2 3 4 + total + scatter-0 + analog + + + 33 2 3 + total + nu-scatter-0 + analog + + + 33 2 3 + total + scatter-0 + analog + + diff --git a/tests/regression_tests/mgxs_library_histogram/results_true.dat b/tests/regression_tests/mgxs_library_histogram/results_true.dat new file mode 100644 index 000000000..116a1a983 --- /dev/null +++ b/tests/regression_tests/mgxs_library_histogram/results_true.dat @@ -0,0 +1,540 @@ + material group in group out mu bin nuclide mean std. dev. +33 1 1 1 1 total 0.025383 0.001933 +34 1 1 1 2 total 0.027855 0.001701 +35 1 1 1 3 total 0.031646 0.002913 +36 1 1 1 4 total 0.028185 0.001430 +37 1 1 1 5 total 0.030162 0.002739 +38 1 1 1 6 total 0.029009 0.002713 +39 1 1 1 7 total 0.030492 0.002907 +40 1 1 1 8 total 0.035272 0.003860 +41 1 1 1 9 total 0.043678 0.006074 +42 1 1 1 10 total 0.044502 0.003030 +43 1 1 1 11 total 0.058017 0.004319 +22 1 1 2 1 total 0.000000 0.000000 +23 1 1 2 2 total 0.000165 0.000165 +24 1 1 2 3 total 0.000330 0.000202 +25 1 1 2 4 total 0.000165 0.000165 +26 1 1 2 5 total 0.000000 0.000000 +27 1 1 2 6 total 0.000165 0.000165 +28 1 1 2 7 total 0.000000 0.000000 +29 1 1 2 8 total 0.000000 0.000000 +30 1 1 2 9 total 0.000000 0.000000 +31 1 1 2 10 total 0.000165 0.000165 +32 1 1 2 11 total 0.000000 0.000000 +11 1 2 1 1 total 0.000925 0.000925 +12 1 2 1 2 total 0.000000 0.000000 +13 1 2 1 3 total 0.000000 0.000000 +14 1 2 1 4 total 0.000000 0.000000 +15 1 2 1 5 total 0.000000 0.000000 +16 1 2 1 6 total 0.000000 0.000000 +17 1 2 1 7 total 0.000000 0.000000 +18 1 2 1 8 total 0.000000 0.000000 +19 1 2 1 9 total 0.000000 0.000000 +20 1 2 1 10 total 0.000000 0.000000 +21 1 2 1 11 total 0.000000 0.000000 +0 1 2 2 1 total 0.037910 0.006498 +1 1 2 2 2 total 0.031438 0.002377 +2 1 2 2 3 total 0.036986 0.006429 +3 1 2 2 4 total 0.029588 0.005627 +4 1 2 2 5 total 0.036986 0.007359 +5 1 2 2 6 total 0.035136 0.004110 +6 1 2 2 7 total 0.037910 0.003188 +7 1 2 2 8 total 0.041609 0.004489 +8 1 2 2 9 total 0.040684 0.007710 +9 1 2 2 10 total 0.043458 0.004638 +10 1 2 2 11 total 0.039760 0.002920 + material group in group out mu bin nuclide mean std. dev. +33 1 1 1 1 total 0.025383 0.001933 +34 1 1 1 2 total 0.027855 0.001701 +35 1 1 1 3 total 0.031646 0.002913 +36 1 1 1 4 total 0.028185 0.001430 +37 1 1 1 5 total 0.030162 0.002739 +38 1 1 1 6 total 0.029009 0.002713 +39 1 1 1 7 total 0.030492 0.002907 +40 1 1 1 8 total 0.035272 0.003860 +41 1 1 1 9 total 0.043678 0.006074 +42 1 1 1 10 total 0.044502 0.003030 +43 1 1 1 11 total 0.058017 0.004319 +22 1 1 2 1 total 0.000000 0.000000 +23 1 1 2 2 total 0.000165 0.000165 +24 1 1 2 3 total 0.000330 0.000202 +25 1 1 2 4 total 0.000165 0.000165 +26 1 1 2 5 total 0.000000 0.000000 +27 1 1 2 6 total 0.000165 0.000165 +28 1 1 2 7 total 0.000000 0.000000 +29 1 1 2 8 total 0.000000 0.000000 +30 1 1 2 9 total 0.000000 0.000000 +31 1 1 2 10 total 0.000165 0.000165 +32 1 1 2 11 total 0.000000 0.000000 +11 1 2 1 1 total 0.000925 0.000925 +12 1 2 1 2 total 0.000000 0.000000 +13 1 2 1 3 total 0.000000 0.000000 +14 1 2 1 4 total 0.000000 0.000000 +15 1 2 1 5 total 0.000000 0.000000 +16 1 2 1 6 total 0.000000 0.000000 +17 1 2 1 7 total 0.000000 0.000000 +18 1 2 1 8 total 0.000000 0.000000 +19 1 2 1 9 total 0.000000 0.000000 +20 1 2 1 10 total 0.000000 0.000000 +21 1 2 1 11 total 0.000000 0.000000 +0 1 2 2 1 total 0.037910 0.006498 +1 1 2 2 2 total 0.031438 0.002377 +2 1 2 2 3 total 0.036986 0.006429 +3 1 2 2 4 total 0.029588 0.005627 +4 1 2 2 5 total 0.036986 0.007359 +5 1 2 2 6 total 0.035136 0.004110 +6 1 2 2 7 total 0.037910 0.003188 +7 1 2 2 8 total 0.041609 0.004489 +8 1 2 2 9 total 0.040684 0.007710 +9 1 2 2 10 total 0.043458 0.004638 +10 1 2 2 11 total 0.039760 0.002920 + material group in group out mu bin nuclide mean std. dev. +33 1 1 1 1 total 0.025529 0.002197 +34 1 1 1 2 total 0.028016 0.002047 +35 1 1 1 3 total 0.031829 0.003196 +36 1 1 1 4 total 0.028348 0.001833 +37 1 1 1 5 total 0.030337 0.003012 +38 1 1 1 6 total 0.029177 0.002969 +39 1 1 1 7 total 0.030668 0.003172 +40 1 1 1 8 total 0.035476 0.004135 +41 1 1 1 9 total 0.043931 0.006358 +42 1 1 1 10 total 0.044759 0.003536 +43 1 1 1 11 total 0.058353 0.004934 +22 1 1 2 1 total 0.000000 0.000000 +23 1 1 2 2 total 0.000166 0.000166 +24 1 1 2 3 total 0.000332 0.000204 +25 1 1 2 4 total 0.000166 0.000166 +26 1 1 2 5 total 0.000000 0.000000 +27 1 1 2 6 total 0.000166 0.000166 +28 1 1 2 7 total 0.000000 0.000000 +29 1 1 2 8 total 0.000000 0.000000 +30 1 1 2 9 total 0.000000 0.000000 +31 1 1 2 10 total 0.000166 0.000166 +32 1 1 2 11 total 0.000000 0.000000 +11 1 2 1 1 total 0.000887 0.000890 +12 1 2 1 2 total 0.000000 0.000000 +13 1 2 1 3 total 0.000000 0.000000 +14 1 2 1 4 total 0.000000 0.000000 +15 1 2 1 5 total 0.000000 0.000000 +16 1 2 1 6 total 0.000000 0.000000 +17 1 2 1 7 total 0.000000 0.000000 +18 1 2 1 8 total 0.000000 0.000000 +19 1 2 1 9 total 0.000000 0.000000 +20 1 2 1 10 total 0.000000 0.000000 +21 1 2 1 11 total 0.000000 0.000000 +0 1 2 2 1 total 0.036372 0.006773 +1 1 2 2 2 total 0.030162 0.003165 +2 1 2 2 3 total 0.035485 0.006687 +3 1 2 2 4 total 0.028388 0.005781 +4 1 2 2 5 total 0.035485 0.007518 +5 1 2 2 6 total 0.033711 0.004644 +6 1 2 2 7 total 0.036372 0.004045 +7 1 2 2 8 total 0.039921 0.005195 +8 1 2 2 9 total 0.039034 0.007923 +9 1 2 2 10 total 0.041695 0.005386 +10 1 2 2 11 total 0.038147 0.003944 + material group in group out mu bin nuclide mean std. dev. +33 1 1 1 1 total 0.025529 0.002974 +34 1 1 1 2 total 0.028016 0.003005 +35 1 1 1 3 total 0.031829 0.004057 +36 1 1 1 4 total 0.028348 0.002884 +37 1 1 1 5 total 0.030337 0.003840 +38 1 1 1 6 total 0.029177 0.003750 +39 1 1 1 7 total 0.030668 0.003982 +40 1 1 1 8 total 0.035476 0.004986 +41 1 1 1 9 total 0.043931 0.007233 +42 1 1 1 10 total 0.044759 0.004986 +43 1 1 1 11 total 0.058353 0.006733 +22 1 1 2 1 total 0.000000 0.000000 +23 1 1 2 2 total 0.000166 0.000201 +24 1 1 2 3 total 0.000332 0.000306 +25 1 1 2 4 total 0.000166 0.000201 +26 1 1 2 5 total 0.000000 0.000000 +27 1 1 2 6 total 0.000166 0.000201 +28 1 1 2 7 total 0.000000 0.000000 +29 1 1 2 8 total 0.000000 0.000000 +30 1 1 2 9 total 0.000000 0.000000 +31 1 1 2 10 total 0.000166 0.000201 +32 1 1 2 11 total 0.000000 0.000000 +11 1 2 1 1 total 0.000887 0.001538 +12 1 2 1 2 total 0.000000 0.000000 +13 1 2 1 3 total 0.000000 0.000000 +14 1 2 1 4 total 0.000000 0.000000 +15 1 2 1 5 total 0.000000 0.000000 +16 1 2 1 6 total 0.000000 0.000000 +17 1 2 1 7 total 0.000000 0.000000 +18 1 2 1 8 total 0.000000 0.000000 +19 1 2 1 9 total 0.000000 0.000000 +20 1 2 1 10 total 0.000000 0.000000 +21 1 2 1 11 total 0.000000 0.000000 +0 1 2 2 1 total 0.036372 0.006936 +1 1 2 2 2 total 0.030162 0.003400 +2 1 2 2 3 total 0.035485 0.006844 +3 1 2 2 4 total 0.028388 0.005898 +4 1 2 2 5 total 0.035485 0.007658 +5 1 2 2 6 total 0.033711 0.004847 +6 1 2 2 7 total 0.036372 0.004312 +7 1 2 2 8 total 0.039921 0.005448 +8 1 2 2 9 total 0.039034 0.008084 +9 1 2 2 10 total 0.041695 0.005652 +10 1 2 2 11 total 0.038147 0.004245 + material group in group out mu bin nuclide mean std. dev. +33 2 1 1 1 total 0.026289 0.004089 +34 2 1 1 2 total 0.018269 0.002939 +35 2 1 1 3 total 0.025398 0.002153 +36 2 1 1 4 total 0.024061 0.005097 +37 2 1 1 5 total 0.022279 0.003375 +38 2 1 1 6 total 0.027626 0.004817 +39 2 1 1 7 total 0.025843 0.003039 +40 2 1 1 8 total 0.026735 0.006742 +41 2 1 1 9 total 0.027626 0.005213 +42 2 1 1 10 total 0.036537 0.005920 +43 2 1 1 11 total 0.049459 0.004153 +22 2 1 2 1 total 0.000000 0.000000 +23 2 1 2 2 total 0.000000 0.000000 +24 2 1 2 3 total 0.000000 0.000000 +25 2 1 2 4 total 0.000000 0.000000 +26 2 1 2 5 total 0.000000 0.000000 +27 2 1 2 6 total 0.000000 0.000000 +28 2 1 2 7 total 0.000000 0.000000 +29 2 1 2 8 total 0.000000 0.000000 +30 2 1 2 9 total 0.000000 0.000000 +31 2 1 2 10 total 0.000000 0.000000 +32 2 1 2 11 total 0.000000 0.000000 +11 2 2 1 1 total 0.000000 0.000000 +12 2 2 1 2 total 0.000000 0.000000 +13 2 2 1 3 total 0.000000 0.000000 +14 2 2 1 4 total 0.000000 0.000000 +15 2 2 1 5 total 0.000000 0.000000 +16 2 2 1 6 total 0.000000 0.000000 +17 2 2 1 7 total 0.000000 0.000000 +18 2 2 1 8 total 0.000000 0.000000 +19 2 2 1 9 total 0.000000 0.000000 +20 2 2 1 10 total 0.000000 0.000000 +21 2 2 1 11 total 0.000000 0.000000 +0 2 2 2 1 total 0.024485 0.007210 +1 2 2 2 2 total 0.036727 0.005548 +2 2 2 2 3 total 0.041624 0.010918 +3 2 2 2 4 total 0.019588 0.008569 +4 2 2 2 5 total 0.022036 0.007526 +5 2 2 2 6 total 0.019588 0.011549 +6 2 2 2 7 total 0.022036 0.006454 +7 2 2 2 8 total 0.036727 0.010282 +8 2 2 2 9 total 0.022036 0.005164 +9 2 2 2 10 total 0.031830 0.011864 +10 2 2 2 11 total 0.019588 0.005336 + material group in group out mu bin nuclide mean std. dev. +33 2 1 1 1 total 0.026289 0.004089 +34 2 1 1 2 total 0.018269 0.002939 +35 2 1 1 3 total 0.025398 0.002153 +36 2 1 1 4 total 0.024061 0.005097 +37 2 1 1 5 total 0.022279 0.003375 +38 2 1 1 6 total 0.027626 0.004817 +39 2 1 1 7 total 0.025843 0.003039 +40 2 1 1 8 total 0.026735 0.006742 +41 2 1 1 9 total 0.027626 0.005213 +42 2 1 1 10 total 0.036537 0.005920 +43 2 1 1 11 total 0.049459 0.004153 +22 2 1 2 1 total 0.000000 0.000000 +23 2 1 2 2 total 0.000000 0.000000 +24 2 1 2 3 total 0.000000 0.000000 +25 2 1 2 4 total 0.000000 0.000000 +26 2 1 2 5 total 0.000000 0.000000 +27 2 1 2 6 total 0.000000 0.000000 +28 2 1 2 7 total 0.000000 0.000000 +29 2 1 2 8 total 0.000000 0.000000 +30 2 1 2 9 total 0.000000 0.000000 +31 2 1 2 10 total 0.000000 0.000000 +32 2 1 2 11 total 0.000000 0.000000 +11 2 2 1 1 total 0.000000 0.000000 +12 2 2 1 2 total 0.000000 0.000000 +13 2 2 1 3 total 0.000000 0.000000 +14 2 2 1 4 total 0.000000 0.000000 +15 2 2 1 5 total 0.000000 0.000000 +16 2 2 1 6 total 0.000000 0.000000 +17 2 2 1 7 total 0.000000 0.000000 +18 2 2 1 8 total 0.000000 0.000000 +19 2 2 1 9 total 0.000000 0.000000 +20 2 2 1 10 total 0.000000 0.000000 +21 2 2 1 11 total 0.000000 0.000000 +0 2 2 2 1 total 0.024485 0.007210 +1 2 2 2 2 total 0.036727 0.005548 +2 2 2 2 3 total 0.041624 0.010918 +3 2 2 2 4 total 0.019588 0.008569 +4 2 2 2 5 total 0.022036 0.007526 +5 2 2 2 6 total 0.019588 0.011549 +6 2 2 2 7 total 0.022036 0.006454 +7 2 2 2 8 total 0.036727 0.010282 +8 2 2 2 9 total 0.022036 0.005164 +9 2 2 2 10 total 0.031830 0.011864 +10 2 2 2 11 total 0.019588 0.005336 + material group in group out mu bin nuclide mean std. dev. +33 2 1 1 1 total 0.026462 0.003961 +34 2 1 1 2 total 0.018389 0.002854 +35 2 1 1 3 total 0.025565 0.001877 +36 2 1 1 4 total 0.024220 0.005027 +37 2 1 1 5 total 0.022425 0.003262 +38 2 1 1 6 total 0.027808 0.004704 +39 2 1 1 7 total 0.026014 0.002854 +40 2 1 1 8 total 0.026911 0.006690 +41 2 1 1 9 total 0.027808 0.005114 +42 2 1 1 10 total 0.036778 0.005752 +43 2 1 1 11 total 0.049785 0.003610 +22 2 1 2 1 total 0.000000 0.000000 +23 2 1 2 2 total 0.000000 0.000000 +24 2 1 2 3 total 0.000000 0.000000 +25 2 1 2 4 total 0.000000 0.000000 +26 2 1 2 5 total 0.000000 0.000000 +27 2 1 2 6 total 0.000000 0.000000 +28 2 1 2 7 total 0.000000 0.000000 +29 2 1 2 8 total 0.000000 0.000000 +30 2 1 2 9 total 0.000000 0.000000 +31 2 1 2 10 total 0.000000 0.000000 +32 2 1 2 11 total 0.000000 0.000000 +11 2 2 1 1 total 0.000000 0.000000 +12 2 2 1 2 total 0.000000 0.000000 +13 2 2 1 3 total 0.000000 0.000000 +14 2 2 1 4 total 0.000000 0.000000 +15 2 2 1 5 total 0.000000 0.000000 +16 2 2 1 6 total 0.000000 0.000000 +17 2 2 1 7 total 0.000000 0.000000 +18 2 2 1 8 total 0.000000 0.000000 +19 2 2 1 9 total 0.000000 0.000000 +20 2 2 1 10 total 0.000000 0.000000 +21 2 2 1 11 total 0.000000 0.000000 +0 2 2 2 1 total 0.024415 0.007393 +1 2 2 2 2 total 0.036622 0.006106 +2 2 2 2 3 total 0.041505 0.011274 +3 2 2 2 4 total 0.019532 0.008656 +4 2 2 2 5 total 0.021973 0.007663 +5 2 2 2 6 total 0.019532 0.011599 +6 2 2 2 7 total 0.021973 0.006620 +7 2 2 2 8 total 0.036622 0.010574 +8 2 2 2 9 total 0.021973 0.005378 +9 2 2 2 10 total 0.031739 0.012040 +10 2 2 2 11 total 0.019532 0.005496 + material group in group out mu bin nuclide mean std. dev. +33 2 1 1 1 total 0.026462 0.004896 +34 2 1 1 2 total 0.018389 0.003485 +35 2 1 1 3 total 0.025565 0.003355 +36 2 1 1 4 total 0.024220 0.005676 +37 2 1 1 5 total 0.022425 0.004073 +38 2 1 1 6 total 0.027808 0.005593 +39 2 1 1 7 total 0.026014 0.004019 +40 2 1 1 8 total 0.026911 0.007302 +41 2 1 1 9 total 0.027808 0.005941 +42 2 1 1 10 total 0.036778 0.007007 +43 2 1 1 11 total 0.049785 0.006508 +22 2 1 2 1 total 0.000000 0.000000 +23 2 1 2 2 total 0.000000 0.000000 +24 2 1 2 3 total 0.000000 0.000000 +25 2 1 2 4 total 0.000000 0.000000 +26 2 1 2 5 total 0.000000 0.000000 +27 2 1 2 6 total 0.000000 0.000000 +28 2 1 2 7 total 0.000000 0.000000 +29 2 1 2 8 total 0.000000 0.000000 +30 2 1 2 9 total 0.000000 0.000000 +31 2 1 2 10 total 0.000000 0.000000 +32 2 1 2 11 total 0.000000 0.000000 +11 2 2 1 1 total 0.000000 0.000000 +12 2 2 1 2 total 0.000000 0.000000 +13 2 2 1 3 total 0.000000 0.000000 +14 2 2 1 4 total 0.000000 0.000000 +15 2 2 1 5 total 0.000000 0.000000 +16 2 2 1 6 total 0.000000 0.000000 +17 2 2 1 7 total 0.000000 0.000000 +18 2 2 1 8 total 0.000000 0.000000 +19 2 2 1 9 total 0.000000 0.000000 +20 2 2 1 10 total 0.000000 0.000000 +21 2 2 1 11 total 0.000000 0.000000 +0 2 2 2 1 total 0.024415 0.008170 +1 2 2 2 2 total 0.036622 0.008031 +2 2 2 2 3 total 0.041505 0.012730 +3 2 2 2 4 total 0.019532 0.009092 +4 2 2 2 5 total 0.021973 0.008278 +5 2 2 2 6 total 0.019532 0.011928 +6 2 2 2 7 total 0.021973 0.007322 +7 2 2 2 8 total 0.036622 0.011790 +8 2 2 2 9 total 0.021973 0.006222 +9 2 2 2 10 total 0.031739 0.012861 +10 2 2 2 11 total 0.019532 0.006160 + material group in group out mu bin nuclide mean std. dev. +33 3 1 1 1 total 0.007001 0.000582 +34 3 1 1 2 total 0.007728 0.001008 +35 3 1 1 3 total 0.006819 0.001120 +36 3 1 1 4 total 0.006092 0.000787 +37 3 1 1 5 total 0.007183 0.000663 +38 3 1 1 6 total 0.011274 0.000704 +39 3 1 1 7 total 0.042642 0.002093 +40 3 1 1 8 total 0.074464 0.002664 +41 3 1 1 9 total 0.119015 0.006892 +42 3 1 1 10 total 0.153293 0.006049 +43 3 1 1 11 total 0.204390 0.010619 +22 3 1 2 1 total 0.000818 0.000302 +23 3 1 2 2 total 0.000818 0.000094 +24 3 1 2 3 total 0.001091 0.000234 +25 3 1 2 4 total 0.001091 0.000310 +26 3 1 2 5 total 0.002546 0.000607 +27 3 1 2 6 total 0.002364 0.000340 +28 3 1 2 7 total 0.004546 0.000835 +29 3 1 2 8 total 0.004819 0.000831 +30 3 1 2 9 total 0.006092 0.001113 +31 3 1 2 10 total 0.004546 0.000757 +32 3 1 2 11 total 0.002637 0.000371 +11 3 2 1 1 total 0.000000 0.000000 +12 3 2 1 2 total 0.000000 0.000000 +13 3 2 1 3 total 0.000000 0.000000 +14 3 2 1 4 total 0.000000 0.000000 +15 3 2 1 5 total 0.000000 0.000000 +16 3 2 1 6 total 0.000000 0.000000 +17 3 2 1 7 total 0.000000 0.000000 +18 3 2 1 8 total 0.000000 0.000000 +19 3 2 1 9 total 0.000000 0.000000 +20 3 2 1 10 total 0.000000 0.000000 +21 3 2 1 11 total 0.000443 0.000445 +0 3 2 2 1 total 0.088669 0.015373 +1 3 2 2 2 total 0.098422 0.016029 +2 3 2 2 3 total 0.126796 0.022922 +3 3 2 2 4 total 0.118373 0.018371 +4 3 2 2 5 total 0.131230 0.014538 +5 3 2 2 6 total 0.167584 0.027220 +6 3 2 2 7 total 0.180441 0.023605 +7 3 2 2 8 total 0.213691 0.028779 +8 3 2 2 9 total 0.236745 0.024777 +9 3 2 2 10 total 0.333394 0.041247 +10 3 2 2 11 total 0.339601 0.037814 + material group in group out mu bin nuclide mean std. dev. +33 3 1 1 1 total 0.007001 0.000582 +34 3 1 1 2 total 0.007728 0.001008 +35 3 1 1 3 total 0.006819 0.001120 +36 3 1 1 4 total 0.006092 0.000787 +37 3 1 1 5 total 0.007183 0.000663 +38 3 1 1 6 total 0.011274 0.000704 +39 3 1 1 7 total 0.042642 0.002093 +40 3 1 1 8 total 0.074464 0.002664 +41 3 1 1 9 total 0.119015 0.006892 +42 3 1 1 10 total 0.153293 0.006049 +43 3 1 1 11 total 0.204390 0.010619 +22 3 1 2 1 total 0.000818 0.000302 +23 3 1 2 2 total 0.000818 0.000094 +24 3 1 2 3 total 0.001091 0.000234 +25 3 1 2 4 total 0.001091 0.000310 +26 3 1 2 5 total 0.002546 0.000607 +27 3 1 2 6 total 0.002364 0.000340 +28 3 1 2 7 total 0.004546 0.000835 +29 3 1 2 8 total 0.004819 0.000831 +30 3 1 2 9 total 0.006092 0.001113 +31 3 1 2 10 total 0.004546 0.000757 +32 3 1 2 11 total 0.002637 0.000371 +11 3 2 1 1 total 0.000000 0.000000 +12 3 2 1 2 total 0.000000 0.000000 +13 3 2 1 3 total 0.000000 0.000000 +14 3 2 1 4 total 0.000000 0.000000 +15 3 2 1 5 total 0.000000 0.000000 +16 3 2 1 6 total 0.000000 0.000000 +17 3 2 1 7 total 0.000000 0.000000 +18 3 2 1 8 total 0.000000 0.000000 +19 3 2 1 9 total 0.000000 0.000000 +20 3 2 1 10 total 0.000000 0.000000 +21 3 2 1 11 total 0.000443 0.000445 +0 3 2 2 1 total 0.088669 0.015373 +1 3 2 2 2 total 0.098422 0.016029 +2 3 2 2 3 total 0.126796 0.022922 +3 3 2 2 4 total 0.118373 0.018371 +4 3 2 2 5 total 0.131230 0.014538 +5 3 2 2 6 total 0.167584 0.027220 +6 3 2 2 7 total 0.180441 0.023605 +7 3 2 2 8 total 0.213691 0.028779 +8 3 2 2 9 total 0.236745 0.024777 +9 3 2 2 10 total 0.333394 0.041247 +10 3 2 2 11 total 0.339601 0.037814 + material group in group out mu bin nuclide mean std. dev. +33 3 1 1 1 total 0.006924 0.000646 +34 3 1 1 2 total 0.007643 0.001048 +35 3 1 1 3 total 0.006744 0.001144 +36 3 1 1 4 total 0.006025 0.000819 +37 3 1 1 5 total 0.007104 0.000721 +38 3 1 1 6 total 0.011150 0.000841 +39 3 1 1 7 total 0.042173 0.002735 +40 3 1 1 8 total 0.073645 0.004084 +41 3 1 1 9 total 0.117706 0.008446 +42 3 1 1 10 total 0.151606 0.008778 +43 3 1 1 11 total 0.202141 0.013551 +22 3 1 2 1 total 0.000809 0.000301 +23 3 1 2 2 total 0.000809 0.000099 +24 3 1 2 3 total 0.001079 0.000236 +25 3 1 2 4 total 0.001079 0.000310 +26 3 1 2 5 total 0.002518 0.000610 +27 3 1 2 6 total 0.002338 0.000351 +28 3 1 2 7 total 0.004496 0.000848 +29 3 1 2 8 total 0.004766 0.000847 +30 3 1 2 9 total 0.006025 0.001130 +31 3 1 2 10 total 0.004496 0.000773 +32 3 1 2 11 total 0.002608 0.000383 +11 3 2 1 1 total 0.000000 0.000000 +12 3 2 1 2 total 0.000000 0.000000 +13 3 2 1 3 total 0.000000 0.000000 +14 3 2 1 4 total 0.000000 0.000000 +15 3 2 1 5 total 0.000000 0.000000 +16 3 2 1 6 total 0.000000 0.000000 +17 3 2 1 7 total 0.000000 0.000000 +18 3 2 1 8 total 0.000000 0.000000 +19 3 2 1 9 total 0.000000 0.000000 +20 3 2 1 10 total 0.000000 0.000000 +21 3 2 1 11 total 0.000440 0.000443 +0 3 2 2 1 total 0.088029 0.016753 +1 3 2 2 2 total 0.097712 0.017664 +2 3 2 2 3 total 0.125881 0.024808 +3 3 2 2 4 total 0.117518 0.020437 +4 3 2 2 5 total 0.130282 0.017687 +5 3 2 2 6 total 0.166374 0.030012 +6 3 2 2 7 total 0.179138 0.027327 +7 3 2 2 8 total 0.212149 0.033068 +8 3 2 2 9 total 0.235036 0.030744 +9 3 2 2 10 total 0.330988 0.048491 +10 3 2 2 11 total 0.337150 0.045927 + material group in group out mu bin nuclide mean std. dev. +33 3 1 1 1 total 0.006924 0.000699 +34 3 1 1 2 total 0.007643 0.001089 +35 3 1 1 3 total 0.006744 0.001173 +36 3 1 1 4 total 0.006025 0.000851 +37 3 1 1 5 total 0.007104 0.000772 +38 3 1 1 6 total 0.011150 0.000945 +39 3 1 1 7 total 0.042173 0.003183 +40 3 1 1 8 total 0.073645 0.004976 +41 3 1 1 9 total 0.117706 0.009591 +42 3 1 1 10 total 0.151606 0.010550 +43 3 1 1 11 total 0.202141 0.015638 +22 3 1 2 1 total 0.000809 0.000306 +23 3 1 2 2 total 0.000809 0.000113 +24 3 1 2 3 total 0.001079 0.000247 +25 3 1 2 4 total 0.001079 0.000318 +26 3 1 2 5 total 0.002518 0.000633 +27 3 1 2 6 total 0.002338 0.000385 +28 3 1 2 7 total 0.004496 0.000901 +29 3 1 2 8 total 0.004766 0.000906 +30 3 1 2 9 total 0.006025 0.001201 +31 3 1 2 10 total 0.004496 0.000830 +32 3 1 2 11 total 0.002608 0.000422 +11 3 2 1 1 total 0.000000 0.000000 +12 3 2 1 2 total 0.000000 0.000000 +13 3 2 1 3 total 0.000000 0.000000 +14 3 2 1 4 total 0.000000 0.000000 +15 3 2 1 5 total 0.000000 0.000000 +16 3 2 1 6 total 0.000000 0.000000 +17 3 2 1 7 total 0.000000 0.000000 +18 3 2 1 8 total 0.000000 0.000000 +19 3 2 1 9 total 0.000000 0.000000 +20 3 2 1 10 total 0.000000 0.000000 +21 3 2 1 11 total 0.000440 0.000764 +0 3 2 2 1 total 0.088029 0.020587 +1 3 2 2 2 total 0.097712 0.022100 +2 3 2 2 3 total 0.125881 0.030136 +3 3 2 2 4 total 0.117518 0.025939 +4 3 2 2 5 total 0.130282 0.025029 +5 3 2 2 6 total 0.166374 0.037579 +6 3 2 2 7 total 0.179138 0.036602 +7 3 2 2 8 total 0.212149 0.043875 +8 3 2 2 9 total 0.235036 0.044338 +9 3 2 2 10 total 0.330988 0.066148 +10 3 2 2 11 total 0.337150 0.064881 diff --git a/tests/regression_tests/mgxs_library_histogram/test.py b/tests/regression_tests/mgxs_library_histogram/test.py new file mode 100644 index 000000000..b9905910a --- /dev/null +++ b/tests/regression_tests/mgxs_library_histogram/test.py @@ -0,0 +1,64 @@ +import hashlib + +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): + # Generate inputs using parent class routine + super().__init__(*args, **kwargs) + + # Initialize a two-group structure + energy_groups = openmc.mgxs.EnergyGroups(group_edges=[0, 0.625, 20.e6]) + + # Initialize MGXS Library for a few cross section types + self.mgxs_lib = openmc.mgxs.Library(self._model.geometry) + self.mgxs_lib.by_nuclide = False + + # Test all MGXS types + self.mgxs_lib.mgxs_types = ['scatter matrix', 'nu-scatter matrix', + 'consistent scatter matrix', + 'consistent nu-scatter matrix'] + self.mgxs_lib.energy_groups = energy_groups + self.mgxs_lib.scatter_format = 'histogram' + self.mgxs_lib.histogram_bins = 11 + self.mgxs_lib.domain_type = 'material' + self.mgxs_lib.build_library() + + # Add tallies + self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) + + def _get_results(self, hash_output=False): + """Digest info in the statepoint and return as a string.""" + + # Read the statepoint file. + sp = openmc.StatePoint(self._sp_name) + + # Load the MGXS library from the statepoint + self.mgxs_lib.load_from_statepoint(sp) + + # Build a string from Pandas Dataframe for each MGXS + outstr = '' + for domain in self.mgxs_lib.domains: + for mgxs_type in self.mgxs_lib.mgxs_types: + mgxs = self.mgxs_lib.get_mgxs(domain, mgxs_type) + df = mgxs.get_pandas_dataframe() + outstr += df.to_string() + '\n' + + # Hash the results if necessary + if hash_output: + sha512 = hashlib.sha512() + sha512.update(outstr.encode('utf-8')) + outstr = sha512.hexdigest() + + return outstr + + +def test_mgxs_library_histogram(): + model = pwr_pin_cell() + harness = MGXSTestHarness('statepoint.10.h5', model) + harness.main() From 45d9234fdfab74e96f42fed27f7c045d9701e766 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sat, 5 May 2018 11:15:11 -0400 Subject: [PATCH 242/361] Fixing df.drop call using newer pandas syntax instead of backwards-compatible syntax --- openmc/mgxs/mgxs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 560c6e986..172d7ddb3 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -4505,7 +4505,7 @@ class ScatterMatrixXS(MatrixMGXS): # If the matrix is P0, remove the legendre column if self.scatter_format == 'legendre' and self.legendre_order == 0: - df = df.drop(columns=['legendre']) + df = df.drop(axis=1, labels=['legendre']) return df From 6d84ed8d5346eefb9b78fa358a806a1b1de77b0f Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sat, 5 May 2018 11:20:13 -0400 Subject: [PATCH 243/361] Resetting mgxs_library_histogram test results back to before the MGXS Api changes so I can more easily see progress --- .../mgxs_library_histogram/results_true.dat | 164 +++++++++--------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/tests/regression_tests/mgxs_library_histogram/results_true.dat b/tests/regression_tests/mgxs_library_histogram/results_true.dat index 20625efd0..116a1a983 100644 --- a/tests/regression_tests/mgxs_library_histogram/results_true.dat +++ b/tests/regression_tests/mgxs_library_histogram/results_true.dat @@ -134,27 +134,27 @@ 9 1 2 2 10 total 0.041695 0.005386 10 1 2 2 11 total 0.038147 0.003944 material group in group out mu bin nuclide mean std. dev. -33 1 1 1 1 total 0.025529 0.002692 -34 1 1 1 2 total 0.028016 0.002666 -35 1 1 1 3 total 0.031829 0.003739 -36 1 1 1 4 total 0.028348 0.002519 -37 1 1 1 5 total 0.030337 0.003534 -38 1 1 1 6 total 0.029177 0.003461 -39 1 1 1 7 total 0.030668 0.003682 -40 1 1 1 8 total 0.035476 0.004666 -41 1 1 1 9 total 0.043931 0.006899 -42 1 1 1 10 total 0.044759 0.004466 -43 1 1 1 11 total 0.058353 0.006082 +33 1 1 1 1 total 0.025529 0.002974 +34 1 1 1 2 total 0.028016 0.003005 +35 1 1 1 3 total 0.031829 0.004057 +36 1 1 1 4 total 0.028348 0.002884 +37 1 1 1 5 total 0.030337 0.003840 +38 1 1 1 6 total 0.029177 0.003750 +39 1 1 1 7 total 0.030668 0.003982 +40 1 1 1 8 total 0.035476 0.004986 +41 1 1 1 9 total 0.043931 0.007233 +42 1 1 1 10 total 0.044759 0.004986 +43 1 1 1 11 total 0.058353 0.006733 22 1 1 2 1 total 0.000000 0.000000 -23 1 1 2 2 total 0.000166 0.000196 -24 1 1 2 3 total 0.000332 0.000290 -25 1 1 2 4 total 0.000166 0.000196 +23 1 1 2 2 total 0.000166 0.000201 +24 1 1 2 3 total 0.000332 0.000306 +25 1 1 2 4 total 0.000166 0.000201 26 1 1 2 5 total 0.000000 0.000000 -27 1 1 2 6 total 0.000166 0.000196 +27 1 1 2 6 total 0.000166 0.000201 28 1 1 2 7 total 0.000000 0.000000 29 1 1 2 8 total 0.000000 0.000000 30 1 1 2 9 total 0.000000 0.000000 -31 1 1 2 10 total 0.000166 0.000196 +31 1 1 2 10 total 0.000166 0.000201 32 1 1 2 11 total 0.000000 0.000000 11 1 2 1 1 total 0.000887 0.001538 12 1 2 1 2 total 0.000000 0.000000 @@ -167,17 +167,17 @@ 19 1 2 1 9 total 0.000000 0.000000 20 1 2 1 10 total 0.000000 0.000000 21 1 2 1 11 total 0.000000 0.000000 -0 1 2 2 1 total 0.036372 0.007026 -1 1 2 2 2 total 0.030162 0.003524 -2 1 2 2 3 total 0.035485 0.006931 -3 1 2 2 4 total 0.028388 0.005962 -4 1 2 2 5 total 0.035485 0.007736 -5 1 2 2 6 total 0.033711 0.004957 -6 1 2 2 7 total 0.036372 0.004455 -7 1 2 2 8 total 0.039921 0.005585 -8 1 2 2 9 total 0.039034 0.008173 -9 1 2 2 10 total 0.041695 0.005796 -10 1 2 2 11 total 0.038147 0.004404 +0 1 2 2 1 total 0.036372 0.006936 +1 1 2 2 2 total 0.030162 0.003400 +2 1 2 2 3 total 0.035485 0.006844 +3 1 2 2 4 total 0.028388 0.005898 +4 1 2 2 5 total 0.035485 0.007658 +5 1 2 2 6 total 0.033711 0.004847 +6 1 2 2 7 total 0.036372 0.004312 +7 1 2 2 8 total 0.039921 0.005448 +8 1 2 2 9 total 0.039034 0.008084 +9 1 2 2 10 total 0.041695 0.005652 +10 1 2 2 11 total 0.038147 0.004245 material group in group out mu bin nuclide mean std. dev. 33 2 1 1 1 total 0.026289 0.004089 34 2 1 1 2 total 0.018269 0.002939 @@ -314,17 +314,17 @@ 9 2 2 2 10 total 0.031739 0.012040 10 2 2 2 11 total 0.019532 0.005496 material group in group out mu bin nuclide mean std. dev. -33 2 1 1 1 total 0.026462 0.004589 -34 2 1 1 2 total 0.018389 0.003277 -35 2 1 1 3 total 0.025565 0.002922 -36 2 1 1 4 total 0.024220 0.005456 -37 2 1 1 5 total 0.022425 0.003808 -38 2 1 1 6 total 0.027808 0.005297 -39 2 1 1 7 total 0.026014 0.003652 -40 2 1 1 8 total 0.026911 0.007093 -41 2 1 1 9 total 0.027808 0.005664 -42 2 1 1 10 total 0.036778 0.006592 -43 2 1 1 11 total 0.049785 0.005660 +33 2 1 1 1 total 0.026462 0.004896 +34 2 1 1 2 total 0.018389 0.003485 +35 2 1 1 3 total 0.025565 0.003355 +36 2 1 1 4 total 0.024220 0.005676 +37 2 1 1 5 total 0.022425 0.004073 +38 2 1 1 6 total 0.027808 0.005593 +39 2 1 1 7 total 0.026014 0.004019 +40 2 1 1 8 total 0.026911 0.007302 +41 2 1 1 9 total 0.027808 0.005941 +42 2 1 1 10 total 0.036778 0.007007 +43 2 1 1 11 total 0.049785 0.006508 22 2 1 2 1 total 0.000000 0.000000 23 2 1 2 2 total 0.000000 0.000000 24 2 1 2 3 total 0.000000 0.000000 @@ -347,17 +347,17 @@ 19 2 2 1 9 total 0.000000 0.000000 20 2 2 1 10 total 0.000000 0.000000 21 2 2 1 11 total 0.000000 0.000000 -0 2 2 2 1 total 0.024415 0.008094 -1 2 2 2 2 total 0.036622 0.007855 -2 2 2 2 3 total 0.041505 0.012588 -3 2 2 2 4 total 0.019532 0.009048 -4 2 2 2 5 total 0.021973 0.008217 -5 2 2 2 6 total 0.019532 0.011894 -6 2 2 2 7 total 0.021973 0.007253 -7 2 2 2 8 total 0.036622 0.011671 -8 2 2 2 9 total 0.021973 0.006141 -9 2 2 2 10 total 0.031739 0.012779 -10 2 2 2 11 total 0.019532 0.006096 +0 2 2 2 1 total 0.024415 0.008170 +1 2 2 2 2 total 0.036622 0.008031 +2 2 2 2 3 total 0.041505 0.012730 +3 2 2 2 4 total 0.019532 0.009092 +4 2 2 2 5 total 0.021973 0.008278 +5 2 2 2 6 total 0.019532 0.011928 +6 2 2 2 7 total 0.021973 0.007322 +7 2 2 2 8 total 0.036622 0.011790 +8 2 2 2 9 total 0.021973 0.006222 +9 2 2 2 10 total 0.031739 0.012861 +10 2 2 2 11 total 0.019532 0.006160 material group in group out mu bin nuclide mean std. dev. 33 3 1 1 1 total 0.007001 0.000582 34 3 1 1 2 total 0.007728 0.001008 @@ -494,28 +494,28 @@ 9 3 2 2 10 total 0.330988 0.048491 10 3 2 2 11 total 0.337150 0.045927 material group in group out mu bin nuclide mean std. dev. -33 3 1 1 1 total 0.006924 0.000686 -34 3 1 1 2 total 0.007643 0.001078 -35 3 1 1 3 total 0.006744 0.001166 -36 3 1 1 4 total 0.006025 0.000843 -37 3 1 1 5 total 0.007104 0.000759 -38 3 1 1 6 total 0.011150 0.000920 -39 3 1 1 7 total 0.042173 0.003073 -40 3 1 1 8 total 0.073645 0.004762 -41 3 1 1 9 total 0.117706 0.009309 -42 3 1 1 10 total 0.151606 0.010122 -43 3 1 1 11 total 0.202141 0.015127 -22 3 1 2 1 total 0.000809 0.000308 -23 3 1 2 2 total 0.000809 0.000118 -24 3 1 2 3 total 0.001079 0.000251 -25 3 1 2 4 total 0.001079 0.000321 -26 3 1 2 5 total 0.002518 0.000642 -27 3 1 2 6 total 0.002338 0.000397 -28 3 1 2 7 total 0.004496 0.000920 -29 3 1 2 8 total 0.004766 0.000928 -30 3 1 2 9 total 0.006025 0.001227 -31 3 1 2 10 total 0.004496 0.000852 -32 3 1 2 11 total 0.002608 0.000436 +33 3 1 1 1 total 0.006924 0.000699 +34 3 1 1 2 total 0.007643 0.001089 +35 3 1 1 3 total 0.006744 0.001173 +36 3 1 1 4 total 0.006025 0.000851 +37 3 1 1 5 total 0.007104 0.000772 +38 3 1 1 6 total 0.011150 0.000945 +39 3 1 1 7 total 0.042173 0.003183 +40 3 1 1 8 total 0.073645 0.004976 +41 3 1 1 9 total 0.117706 0.009591 +42 3 1 1 10 total 0.151606 0.010550 +43 3 1 1 11 total 0.202141 0.015638 +22 3 1 2 1 total 0.000809 0.000306 +23 3 1 2 2 total 0.000809 0.000113 +24 3 1 2 3 total 0.001079 0.000247 +25 3 1 2 4 total 0.001079 0.000318 +26 3 1 2 5 total 0.002518 0.000633 +27 3 1 2 6 total 0.002338 0.000385 +28 3 1 2 7 total 0.004496 0.000901 +29 3 1 2 8 total 0.004766 0.000906 +30 3 1 2 9 total 0.006025 0.001201 +31 3 1 2 10 total 0.004496 0.000830 +32 3 1 2 11 total 0.002608 0.000422 11 3 2 1 1 total 0.000000 0.000000 12 3 2 1 2 total 0.000000 0.000000 13 3 2 1 3 total 0.000000 0.000000 @@ -527,14 +527,14 @@ 19 3 2 1 9 total 0.000000 0.000000 20 3 2 1 10 total 0.000000 0.000000 21 3 2 1 11 total 0.000440 0.000764 -0 3 2 2 1 total 0.088029 0.018984 -1 3 2 2 2 total 0.097712 0.020255 -2 3 2 2 3 total 0.125881 0.027901 -3 3 2 2 4 total 0.117518 0.023659 -4 3 2 2 5 total 0.130282 0.022078 -5 3 2 2 6 total 0.166374 0.034431 -6 3 2 2 7 total 0.179138 0.032816 -7 3 2 2 8 total 0.212149 0.039453 -8 3 2 2 9 total 0.235036 0.038904 -9 3 2 2 10 total 0.330988 0.058979 -10 3 2 2 11 total 0.337150 0.057260 +0 3 2 2 1 total 0.088029 0.020587 +1 3 2 2 2 total 0.097712 0.022100 +2 3 2 2 3 total 0.125881 0.030136 +3 3 2 2 4 total 0.117518 0.025939 +4 3 2 2 5 total 0.130282 0.025029 +5 3 2 2 6 total 0.166374 0.037579 +6 3 2 2 7 total 0.179138 0.036602 +7 3 2 2 8 total 0.212149 0.043875 +8 3 2 2 9 total 0.235036 0.044338 +9 3 2 2 10 total 0.330988 0.066148 +10 3 2 2 11 total 0.337150 0.064881 From 758f0b3087f1780387d707c2147a07d3d4aa6b03 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sat, 5 May 2018 16:55:13 -0400 Subject: [PATCH 244/361] updating tests now that I know the std dev issue --- .../mgxs_library_correction/results_true.dat | 12 +- .../mgxs_library_histogram/results_true.dat | 164 +++++++++--------- 2 files changed, 88 insertions(+), 88 deletions(-) diff --git a/tests/regression_tests/mgxs_library_correction/results_true.dat b/tests/regression_tests/mgxs_library_correction/results_true.dat index f7e288802..cc28320e2 100644 --- a/tests/regression_tests/mgxs_library_correction/results_true.dat +++ b/tests/regression_tests/mgxs_library_correction/results_true.dat @@ -2,12 +2,12 @@ 3 1 1 1 total 0.332466 0.026533 2 1 1 2 total 0.000989 0.000482 1 1 2 1 total 0.000925 0.000925 -0 1 2 2 total 0.396146 0.015511 +0 1 2 2 total 0.396146 0.015707 material group in group out nuclide mean std. dev. 3 1 1 1 total 0.332466 0.026533 2 1 1 2 total 0.000989 0.000482 1 1 2 1 total 0.000925 0.000925 -0 1 2 2 total 0.396146 0.015511 +0 1 2 2 total 0.396146 0.015707 material group in group out nuclide mean std. dev. 3 1 1 1 total 0.334690 0.037288 2 1 1 2 total 0.000995 0.000489 @@ -39,15 +39,15 @@ 1 2 2 1 total 0.000000 0.000000 0 2 2 2 total 0.306635 0.067497 material group in group out nuclide mean std. dev. -3 3 1 1 total 0.258652 0.022623 +3 3 1 1 total 0.258652 0.022596 2 3 1 2 total 0.031368 0.001728 1 3 2 1 total 0.000443 0.000445 -0 3 2 2 total 1.482300 0.232653 +0 3 2 2 total 1.482300 0.232582 material group in group out nuclide mean std. dev. -3 3 1 1 total 0.258652 0.022623 +3 3 1 1 total 0.258652 0.022596 2 3 1 2 total 0.031368 0.001728 1 3 2 1 total 0.000443 0.000445 -0 3 2 2 total 1.482300 0.232653 +0 3 2 2 total 1.482300 0.232582 material group in group out nuclide mean std. dev. 3 3 1 1 total 0.251610 0.041472 2 3 1 2 total 0.031023 0.002232 diff --git a/tests/regression_tests/mgxs_library_histogram/results_true.dat b/tests/regression_tests/mgxs_library_histogram/results_true.dat index 116a1a983..20625efd0 100644 --- a/tests/regression_tests/mgxs_library_histogram/results_true.dat +++ b/tests/regression_tests/mgxs_library_histogram/results_true.dat @@ -134,27 +134,27 @@ 9 1 2 2 10 total 0.041695 0.005386 10 1 2 2 11 total 0.038147 0.003944 material group in group out mu bin nuclide mean std. dev. -33 1 1 1 1 total 0.025529 0.002974 -34 1 1 1 2 total 0.028016 0.003005 -35 1 1 1 3 total 0.031829 0.004057 -36 1 1 1 4 total 0.028348 0.002884 -37 1 1 1 5 total 0.030337 0.003840 -38 1 1 1 6 total 0.029177 0.003750 -39 1 1 1 7 total 0.030668 0.003982 -40 1 1 1 8 total 0.035476 0.004986 -41 1 1 1 9 total 0.043931 0.007233 -42 1 1 1 10 total 0.044759 0.004986 -43 1 1 1 11 total 0.058353 0.006733 +33 1 1 1 1 total 0.025529 0.002692 +34 1 1 1 2 total 0.028016 0.002666 +35 1 1 1 3 total 0.031829 0.003739 +36 1 1 1 4 total 0.028348 0.002519 +37 1 1 1 5 total 0.030337 0.003534 +38 1 1 1 6 total 0.029177 0.003461 +39 1 1 1 7 total 0.030668 0.003682 +40 1 1 1 8 total 0.035476 0.004666 +41 1 1 1 9 total 0.043931 0.006899 +42 1 1 1 10 total 0.044759 0.004466 +43 1 1 1 11 total 0.058353 0.006082 22 1 1 2 1 total 0.000000 0.000000 -23 1 1 2 2 total 0.000166 0.000201 -24 1 1 2 3 total 0.000332 0.000306 -25 1 1 2 4 total 0.000166 0.000201 +23 1 1 2 2 total 0.000166 0.000196 +24 1 1 2 3 total 0.000332 0.000290 +25 1 1 2 4 total 0.000166 0.000196 26 1 1 2 5 total 0.000000 0.000000 -27 1 1 2 6 total 0.000166 0.000201 +27 1 1 2 6 total 0.000166 0.000196 28 1 1 2 7 total 0.000000 0.000000 29 1 1 2 8 total 0.000000 0.000000 30 1 1 2 9 total 0.000000 0.000000 -31 1 1 2 10 total 0.000166 0.000201 +31 1 1 2 10 total 0.000166 0.000196 32 1 1 2 11 total 0.000000 0.000000 11 1 2 1 1 total 0.000887 0.001538 12 1 2 1 2 total 0.000000 0.000000 @@ -167,17 +167,17 @@ 19 1 2 1 9 total 0.000000 0.000000 20 1 2 1 10 total 0.000000 0.000000 21 1 2 1 11 total 0.000000 0.000000 -0 1 2 2 1 total 0.036372 0.006936 -1 1 2 2 2 total 0.030162 0.003400 -2 1 2 2 3 total 0.035485 0.006844 -3 1 2 2 4 total 0.028388 0.005898 -4 1 2 2 5 total 0.035485 0.007658 -5 1 2 2 6 total 0.033711 0.004847 -6 1 2 2 7 total 0.036372 0.004312 -7 1 2 2 8 total 0.039921 0.005448 -8 1 2 2 9 total 0.039034 0.008084 -9 1 2 2 10 total 0.041695 0.005652 -10 1 2 2 11 total 0.038147 0.004245 +0 1 2 2 1 total 0.036372 0.007026 +1 1 2 2 2 total 0.030162 0.003524 +2 1 2 2 3 total 0.035485 0.006931 +3 1 2 2 4 total 0.028388 0.005962 +4 1 2 2 5 total 0.035485 0.007736 +5 1 2 2 6 total 0.033711 0.004957 +6 1 2 2 7 total 0.036372 0.004455 +7 1 2 2 8 total 0.039921 0.005585 +8 1 2 2 9 total 0.039034 0.008173 +9 1 2 2 10 total 0.041695 0.005796 +10 1 2 2 11 total 0.038147 0.004404 material group in group out mu bin nuclide mean std. dev. 33 2 1 1 1 total 0.026289 0.004089 34 2 1 1 2 total 0.018269 0.002939 @@ -314,17 +314,17 @@ 9 2 2 2 10 total 0.031739 0.012040 10 2 2 2 11 total 0.019532 0.005496 material group in group out mu bin nuclide mean std. dev. -33 2 1 1 1 total 0.026462 0.004896 -34 2 1 1 2 total 0.018389 0.003485 -35 2 1 1 3 total 0.025565 0.003355 -36 2 1 1 4 total 0.024220 0.005676 -37 2 1 1 5 total 0.022425 0.004073 -38 2 1 1 6 total 0.027808 0.005593 -39 2 1 1 7 total 0.026014 0.004019 -40 2 1 1 8 total 0.026911 0.007302 -41 2 1 1 9 total 0.027808 0.005941 -42 2 1 1 10 total 0.036778 0.007007 -43 2 1 1 11 total 0.049785 0.006508 +33 2 1 1 1 total 0.026462 0.004589 +34 2 1 1 2 total 0.018389 0.003277 +35 2 1 1 3 total 0.025565 0.002922 +36 2 1 1 4 total 0.024220 0.005456 +37 2 1 1 5 total 0.022425 0.003808 +38 2 1 1 6 total 0.027808 0.005297 +39 2 1 1 7 total 0.026014 0.003652 +40 2 1 1 8 total 0.026911 0.007093 +41 2 1 1 9 total 0.027808 0.005664 +42 2 1 1 10 total 0.036778 0.006592 +43 2 1 1 11 total 0.049785 0.005660 22 2 1 2 1 total 0.000000 0.000000 23 2 1 2 2 total 0.000000 0.000000 24 2 1 2 3 total 0.000000 0.000000 @@ -347,17 +347,17 @@ 19 2 2 1 9 total 0.000000 0.000000 20 2 2 1 10 total 0.000000 0.000000 21 2 2 1 11 total 0.000000 0.000000 -0 2 2 2 1 total 0.024415 0.008170 -1 2 2 2 2 total 0.036622 0.008031 -2 2 2 2 3 total 0.041505 0.012730 -3 2 2 2 4 total 0.019532 0.009092 -4 2 2 2 5 total 0.021973 0.008278 -5 2 2 2 6 total 0.019532 0.011928 -6 2 2 2 7 total 0.021973 0.007322 -7 2 2 2 8 total 0.036622 0.011790 -8 2 2 2 9 total 0.021973 0.006222 -9 2 2 2 10 total 0.031739 0.012861 -10 2 2 2 11 total 0.019532 0.006160 +0 2 2 2 1 total 0.024415 0.008094 +1 2 2 2 2 total 0.036622 0.007855 +2 2 2 2 3 total 0.041505 0.012588 +3 2 2 2 4 total 0.019532 0.009048 +4 2 2 2 5 total 0.021973 0.008217 +5 2 2 2 6 total 0.019532 0.011894 +6 2 2 2 7 total 0.021973 0.007253 +7 2 2 2 8 total 0.036622 0.011671 +8 2 2 2 9 total 0.021973 0.006141 +9 2 2 2 10 total 0.031739 0.012779 +10 2 2 2 11 total 0.019532 0.006096 material group in group out mu bin nuclide mean std. dev. 33 3 1 1 1 total 0.007001 0.000582 34 3 1 1 2 total 0.007728 0.001008 @@ -494,28 +494,28 @@ 9 3 2 2 10 total 0.330988 0.048491 10 3 2 2 11 total 0.337150 0.045927 material group in group out mu bin nuclide mean std. dev. -33 3 1 1 1 total 0.006924 0.000699 -34 3 1 1 2 total 0.007643 0.001089 -35 3 1 1 3 total 0.006744 0.001173 -36 3 1 1 4 total 0.006025 0.000851 -37 3 1 1 5 total 0.007104 0.000772 -38 3 1 1 6 total 0.011150 0.000945 -39 3 1 1 7 total 0.042173 0.003183 -40 3 1 1 8 total 0.073645 0.004976 -41 3 1 1 9 total 0.117706 0.009591 -42 3 1 1 10 total 0.151606 0.010550 -43 3 1 1 11 total 0.202141 0.015638 -22 3 1 2 1 total 0.000809 0.000306 -23 3 1 2 2 total 0.000809 0.000113 -24 3 1 2 3 total 0.001079 0.000247 -25 3 1 2 4 total 0.001079 0.000318 -26 3 1 2 5 total 0.002518 0.000633 -27 3 1 2 6 total 0.002338 0.000385 -28 3 1 2 7 total 0.004496 0.000901 -29 3 1 2 8 total 0.004766 0.000906 -30 3 1 2 9 total 0.006025 0.001201 -31 3 1 2 10 total 0.004496 0.000830 -32 3 1 2 11 total 0.002608 0.000422 +33 3 1 1 1 total 0.006924 0.000686 +34 3 1 1 2 total 0.007643 0.001078 +35 3 1 1 3 total 0.006744 0.001166 +36 3 1 1 4 total 0.006025 0.000843 +37 3 1 1 5 total 0.007104 0.000759 +38 3 1 1 6 total 0.011150 0.000920 +39 3 1 1 7 total 0.042173 0.003073 +40 3 1 1 8 total 0.073645 0.004762 +41 3 1 1 9 total 0.117706 0.009309 +42 3 1 1 10 total 0.151606 0.010122 +43 3 1 1 11 total 0.202141 0.015127 +22 3 1 2 1 total 0.000809 0.000308 +23 3 1 2 2 total 0.000809 0.000118 +24 3 1 2 3 total 0.001079 0.000251 +25 3 1 2 4 total 0.001079 0.000321 +26 3 1 2 5 total 0.002518 0.000642 +27 3 1 2 6 total 0.002338 0.000397 +28 3 1 2 7 total 0.004496 0.000920 +29 3 1 2 8 total 0.004766 0.000928 +30 3 1 2 9 total 0.006025 0.001227 +31 3 1 2 10 total 0.004496 0.000852 +32 3 1 2 11 total 0.002608 0.000436 11 3 2 1 1 total 0.000000 0.000000 12 3 2 1 2 total 0.000000 0.000000 13 3 2 1 3 total 0.000000 0.000000 @@ -527,14 +527,14 @@ 19 3 2 1 9 total 0.000000 0.000000 20 3 2 1 10 total 0.000000 0.000000 21 3 2 1 11 total 0.000440 0.000764 -0 3 2 2 1 total 0.088029 0.020587 -1 3 2 2 2 total 0.097712 0.022100 -2 3 2 2 3 total 0.125881 0.030136 -3 3 2 2 4 total 0.117518 0.025939 -4 3 2 2 5 total 0.130282 0.025029 -5 3 2 2 6 total 0.166374 0.037579 -6 3 2 2 7 total 0.179138 0.036602 -7 3 2 2 8 total 0.212149 0.043875 -8 3 2 2 9 total 0.235036 0.044338 -9 3 2 2 10 total 0.330988 0.066148 -10 3 2 2 11 total 0.337150 0.064881 +0 3 2 2 1 total 0.088029 0.018984 +1 3 2 2 2 total 0.097712 0.020255 +2 3 2 2 3 total 0.125881 0.027901 +3 3 2 2 4 total 0.117518 0.023659 +4 3 2 2 5 total 0.130282 0.022078 +5 3 2 2 6 total 0.166374 0.034431 +6 3 2 2 7 total 0.179138 0.032816 +7 3 2 2 8 total 0.212149 0.039453 +8 3 2 2 9 total 0.235036 0.038904 +9 3 2 2 10 total 0.330988 0.058979 +10 3 2 2 11 total 0.337150 0.057260 From feecf0134eda55b9cac11107c55ef46c7450ac4f Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 6 May 2018 11:28:05 -0400 Subject: [PATCH 245/361] Incorporated maxwell and watt_spectrum C++ code, tests pass --- src/math.F90 | 18 ++---------------- src/math_functions.cpp | 12 ++++++------ src/math_functions.h | 4 ++++ tests/unit_tests/test_math.py | 8 ++++---- 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/math.F90 b/src/math.F90 index 6534b66b0..d7e335284 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -364,18 +364,7 @@ contains real(C_DOUBLE), intent(in) :: T ! tabulated function of incoming E real(C_DOUBLE) :: E_out ! sampled energy - real(C_DOUBLE) :: r1, r2, r3 ! random numbers - real(C_DOUBLE) :: c ! cosine of pi/2*r3 - - r1 = prn() - r2 = prn() - r3 = prn() - - ! determine cosine of pi/2*r - c = cos(PI/TWO*r3) - - ! determine outgoing energy - E_out = -T*(log(r1) + log(r2)*c*c) + E_out = maxwell_spectrum_cc(T) end function maxwell_spectrum @@ -393,10 +382,7 @@ contains real(C_DOUBLE), intent(in) :: b ! Watt parameter b real(C_DOUBLE) :: E_out ! energy of emitted neutron - real(C_DOUBLE) :: w ! sampled from Maxwellian - - w = maxwell_spectrum(a) - E_out = w + a*a*b/4. + (TWO*prn() - ONE)*sqrt(a*a*b*w) + E_out = watt_spectrum_cc(a, b) end function watt_spectrum diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 6ddf944a7..1466bde54 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -56,7 +56,7 @@ double __attribute__ ((const)) normal_percentile_c(double p) { // Refinement based on Newton's method - z = z - (0.5 * std::erfc(-z / std::sqrt(2.0)) - p) * std::sqrt(2.0 * M_PI) * + z = z - (0.5 * std::erfc(-z / std::sqrt(2.0)) - p) * std::sqrt(2.0 * PI) * std::exp(0.5 * z * z); return z; @@ -82,7 +82,7 @@ double __attribute__ ((const)) t_percentile_c(double p, int df){ // For one degree of freedom, the t-distribution becomes a Cauchy // distribution whose cdf we can invert directly - t = std::tan(M_PI*(p - 0.5)); + t = std::tan(PI*(p - 0.5)); } else if (df == 2) { // For two degrees of freedom, the cdf is given by 1/2 + x/(2*sqrt(x^2 + // 2)). This can be directly inverted to yield the solution below @@ -604,7 +604,7 @@ void rotate_angle_c(double uvw[3], double mu, double phi) { if (phi != -10.) { phi_ = phi; } else { - phi_ = 2. * M_PI * prn(); + phi_ = 2. * PI * prn(); } // Precompute factors to save flops @@ -647,7 +647,7 @@ double maxwell_spectrum_c(double T) { r3 = prn(); // determine cosine of pi/2*r - c = std::cos(M_PI / 2. * r3); + c = std::cos(PI / 2. * r3); // determine outgoing energy E_out = -T * (std::log(r1) + std::log(r2) * c * c); @@ -710,7 +710,7 @@ double watt_spectrum_c(double a, double b) { // std::complex w_derivative_c(std::complex z, int order){ // std::complex wv; // The resultant w(z) value -// const std::complex twoi_sqrtpi(0.0, 2.0 / std::sqrt(M_PI)); +// const std::complex twoi_sqrtpi(0.0, 2.0 / std::sqrt(PI)); // switch(order) { // case 0: @@ -764,7 +764,7 @@ void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { factors[0] = erf_beta / E; factors[1] = 1. / sqrtE; factors[2] = factors[0] * (half_inv_dopp2 + E) + exp_m_beta2 / - (beta * std::sqrt(M_PI)); + (beta * std::sqrt(PI)); // Perform recursive broadening of high order components for (i = 0; i < n - 3; i++) { diff --git a/src/math_functions.h b/src/math_functions.h index bea30cf91..aaba08261 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -11,6 +11,10 @@ namespace openmc { +// TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we +// use so for now we will reuse the Fortran constant until we are OK with +// modifying test results +const double PI = 3.1415926535898; //============================================================================== // NORMAL_PERCENTILE calculates the percentile of the standard normal diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index c8d33e601..bfa021ceb 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -147,8 +147,8 @@ def test_maxwell_spectrum(): T = 0.5 ref_val = 0.6129982175261098 test_val = openmc.capi.math.maxwell_spectrum(T) - print(test_val) - assert np.isclose(ref_val, test_val) + + assert ref_val == test_val def test_watt_spectrum(): @@ -158,8 +158,8 @@ def test_watt_spectrum(): b = 0.75 ref_val = 0.6247242713640233 test_val = openmc.capi.math.watt_spectrum(a, b) - print(test_val) - assert np.isclose(ref_val, test_val) + + assert ref_val == test_val def test_broaden_wmp_polynomials(): From 468126f8c4f3a74e915aba1ddba6f14cc37dfecb Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 6 May 2018 20:14:54 -0400 Subject: [PATCH 246/361] Added in Zernike polynomials to C++ code --- openmc/capi/math.py | 8 +- src/faddeeva/Faddeeva.h | 8 +- src/math.F90 | 162 ++++++---------------------------- src/math_functions.cpp | 135 ++++++++++++++++++++++++++-- src/math_functions.h | 20 +++-- tests/unit_tests/test_math.py | 39 +++++++- 6 files changed, 211 insertions(+), 161 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index cd7ab4052..4de46691a 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -11,15 +11,15 @@ _dll.t_percentile.restype = c_double _dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] _dll.calc_pn.restype = c_double _dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double)] +_dll.evaluate_legendre.restype = c_double +_dll.evaluate_legendre.argtypes = [POINTER(c_int), POINTER(c_double), + POINTER(c_double)] _dll.calc_rn.restype = None _dll.calc_rn.argtypes = [POINTER(c_int), ndpointer(c_double), ndpointer(c_double)] _dll.calc_zn.restype = None _dll.calc_zn.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double), ndpointer(c_double)] -_dll.evaluate_legendre.restype = c_double -_dll.evaluate_legendre.argtypes = [POINTER(c_int), POINTER(c_double), - POINTER(c_double)] _dll.rotate_angle.restype = None _dll.rotate_angle.argtypes = [ndpointer(c_double), POINTER(c_double), ndpointer(c_double), POINTER(c_double)] @@ -169,7 +169,7 @@ def calc_zn(n, rho, phi): """ - num_bins = ((n + 1) * (n + 2)) / 2 + num_bins = ((n + 1) * (n + 2)) // 2 zn = np.zeros(num_bins, dtype=np.float64) _dll.calc_zn(c_int(n), c_double(rho), c_double(phi), zn) return zn diff --git a/src/faddeeva/Faddeeva.h b/src/faddeeva/Faddeeva.h index 429386190..9e26bc1ed 100644 --- a/src/faddeeva/Faddeeva.h +++ b/src/faddeeva/Faddeeva.h @@ -1,5 +1,5 @@ /* Copyright (c) 2012 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 the Software without restriction, including @@ -7,17 +7,17 @@ * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: - * + * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* Available at: http://ab-initio.mit.edu/Faddeeva diff --git a/src/math.F90 b/src/math.F90 index d7e335284..a386747a4 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -43,14 +43,6 @@ module math real(C_DOUBLE) :: pnx end function calc_pn_cc - subroutine calc_rn_cc(n, uvw, rn) bind(C, name='calc_rn_c') - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: n - real(C_DOUBLE), intent(in) :: uvw(3) - real(C_DOUBLE), intent(in) :: rn(2 * n + 1) - end subroutine calc_rn_cc - pure function evaluate_legendre_cc(n, data, x) & bind(C, name='evaluate_legendre_c') result(val) use ISO_C_BINDING @@ -61,6 +53,23 @@ module math real(C_DOUBLE) :: val end function evaluate_legendre_cc + subroutine calc_rn_cc(n, uvw, rn) bind(C, name='calc_rn_c') + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), intent(in) :: uvw(3) + real(C_DOUBLE), intent(out) :: rn(2 * n + 1) + end subroutine calc_rn_cc + + subroutine calc_zn_cc(n, rho, phi, zn) bind(C, name='calc_zn_c') + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n + real(C_DOUBLE), value, intent(in) :: rho + real(C_DOUBLE), value, intent(in) :: phi + real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) + end subroutine calc_zn_cc + subroutine rotate_angle_cc(uvw, mu, phi) bind(C, name='rotate_angle_c') use ISO_C_BINDING implicit none @@ -200,102 +209,13 @@ contains !=============================================================================== subroutine calc_zn(n, rho, phi, zn) bind(C) - ! This procedure uses the modified Kintner's method for calculating Zernike - ! polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, - ! R. (2003). A comparative analysis of algorithms for fast computation of - ! Zernike moments. Pattern Recognition, 36(3), 731-742. - integer(C_INT), intent(in) :: n ! Maximum order real(C_DOUBLE), intent(in) :: rho ! Radial location in the unit disk real(C_DOUBLE), intent(in) :: phi ! Theta (radians) location in the unit disk - real(C_DOUBLE), intent(out) :: zn(:) ! The resulting list of coefficients + ! The resulting list of coefficients + real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) - real(C_DOUBLE) :: sin_phi, cos_phi ! Sine and Cosine of phi - real(C_DOUBLE) :: sin_phi_vec(n+1) ! Contains sin(n*phi) - real(C_DOUBLE) :: cos_phi_vec(n+1) ! Contains cos(n*phi) - real(C_DOUBLE) :: zn_mat(n+1, n+1) ! Matrix form of the coefficients which is - ! easier to work with - real(C_DOUBLE) :: k1, k2, k3, k4 ! Variables for R_m_n calculation - integer(C_INT) :: i,p,q ! Loop counters - - real(C_DOUBLE), parameter :: SQRT_N_1(0:10) = [& - sqrt(1.0_8), sqrt(2.0_8), sqrt(3.0_8), sqrt(4.0_8), & - sqrt(5.0_8), sqrt(6.0_8), sqrt(7.0_8), sqrt(8.0_8), & - sqrt(9.0_8), sqrt(10.0_8), sqrt(11.0_8)] - real(C_DOUBLE), parameter :: SQRT_2N_2(0:10) = SQRT_N_1*sqrt(2.0_8) - - ! n == radial degree - ! m == azimuthal frequency - - ! ========================================================================== - ! Determine vector of sin(n*phi) and cos(n*phi). This takes advantage of the - ! following recurrence relations so that only a single sin/cos have to be - ! evaluated (http://mathworld.wolfram.com/Multiple-AngleFormulas.html) - ! - ! sin(nx) = 2 cos(x) sin((n-1)x) - sin((n-2)x) - ! cos(nx) = 2 cos(x) cos((n-1)x) - cos((n-2)x) - - sin_phi = sin(phi) - cos_phi = cos(phi) - - sin_phi_vec(1) = 1.0_8 - cos_phi_vec(1) = 1.0_8 - - sin_phi_vec(2) = 2.0_8 * cos_phi - cos_phi_vec(2) = cos_phi - - do i = 3, n+1 - sin_phi_vec(i) = 2.0_8 * cos_phi * sin_phi_vec(i-1) - sin_phi_vec(i-2) - cos_phi_vec(i) = 2.0_8 * cos_phi * cos_phi_vec(i-1) - cos_phi_vec(i-2) - end do - - do i = 1, n+1 - sin_phi_vec(i) = sin_phi_vec(i) * sin_phi - end do - - ! ========================================================================== - ! Calculate R_pq(rho) - - ! Fill the main diagonal first (Eq. 3.9 in Chong) - do p = 0, n - zn_mat(p+1, p+1) = rho**p - end do - - ! Fill in the second diagonal (Eq. 3.10 in Chong) - do q = 0, n-2 - zn_mat(q+2+1, q+1) = (q+2) * zn_mat(q+2+1, q+2+1) - (q+1) * zn_mat(q+1, q+1) - end do - - ! Fill in the rest of the values using the original results (Eq. 3.8 in Chong) - do p = 4, n - k2 = 2 * p * (p - 1) * (p - 2) - do q = p-4, 0, -2 - k1 = (p + q) * (p - q) * (p - 2) / 2 - k3 = -q**2*(p - 1) - p * (p - 1) * (p - 2) - k4 = -p * (p + q - 2) * (p - q - 2) / 2 - zn_mat(p+1, q+1) = ((k2 * rho**2 + k3) * zn_mat(p-2+1, q+1) + k4 * zn_mat(p-4+1, q+1)) / k1 - end do - end do - - ! Roll into a single vector for easier computation later - ! The vector is ordered (0,0), (1,-1), (1,1), (2,-2), (2,0), - ! (2, 2), .... in (n,m) indices - ! Note that the cos and sin vectors are offset by one - ! sin_phi_vec = [sin(x), sin(2x), sin(3x) ...] - ! cos_phi_vec = [1.0, cos(x), cos(2x)... ] - i = 1 - do p = 0, n - do q = -p, p, 2 - if (q < 0) then - zn(i) = zn_mat(p+1, abs(q)+1) * sin_phi_vec(abs(q)) * SQRT_2N_2(p) - else if (q == 0) then - zn(i) = zn_mat(p+1, q+1) * SQRT_N_1(p) - else - zn(i) = zn_mat(p+1, q+1) * cos_phi_vec(abs(q)+1) * SQRT_2N_2(p) - end if - i = i + 1 - end do - end do + call calc_zn_cc(n, rho, phi, zn) end subroutine calc_zn !=============================================================================== @@ -310,44 +230,11 @@ contains real(C_DOUBLE), intent(out) :: uvw(3) ! rotated directional cosine real(C_DOUBLE), optional :: phi ! azimuthal angle - real(C_DOUBLE) :: phi_ ! azimuthal angle - real(C_DOUBLE) :: sinphi ! sine of azimuthal angle - real(C_DOUBLE) :: cosphi ! cosine of azimuthal angle - real(C_DOUBLE) :: a ! sqrt(1 - mu^2) - real(C_DOUBLE) :: b ! sqrt(1 - w^2) - real(C_DOUBLE) :: u0 ! original cosine in x direction - real(C_DOUBLE) :: v0 ! original cosine in y direction - real(C_DOUBLE) :: w0 ! original cosine in z direction - - ! Copy original directional cosines - u0 = uvw0(1) - v0 = uvw0(2) - w0 = uvw0(3) - - ! Sample azimuthal angle in [0,2pi) if none provided + uvw = uvw0 if (present(phi)) then - phi_ = phi + call rotate_angle_cc(uvw, mu, phi) else - phi_ = TWO * PI * prn() - end if - - ! Precompute factors to save flops - sinphi = sin(phi_) - cosphi = cos(phi_) - a = sqrt(max(ZERO, ONE - mu*mu)) - b = sqrt(max(ZERO, ONE - w0*w0)) - - ! Need to treat special case where sqrt(1 - w**2) is close to zero by - ! expanding about the v component rather than the w component - if (b > 1e-10) then - uvw(1) = mu*u0 + a*(u0*w0*cosphi - v0*sinphi)/b - uvw(2) = mu*v0 + a*(v0*w0*cosphi + u0*sinphi)/b - uvw(3) = mu*w0 - a*b*cosphi - else - b = sqrt(ONE - v0*v0) - uvw(1) = mu*u0 + a*(u0*v0*cosphi + w0*sinphi)/b - uvw(2) = mu*v0 - a*b*cosphi - uvw(3) = mu*w0 + a*(v0*w0*cosphi - u0*sinphi)/b + call rotate_angle_cc(uvw, mu, -10._8) end if end subroutine rotate_angle @@ -492,6 +379,9 @@ contains factors(i+3) = factors(i+1)*(E + (ONE + TWO * i) * half_inv_dopp2) end if end do + + ! call broaden_wmp_polynomials_cc(E, dopp, n, factors) + end subroutine broaden_wmp_polynomials end module math diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 1466bde54..9f698f42a 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -169,9 +169,10 @@ double __attribute__ ((const)) calc_pn_c(int n, double x) { double __attribute__ ((const)) evaluate_legendre_c(int n, double data[], double x) { double val; + int l; val = 0.5 * data[0]; - for (int l = 1; l < n; l++) { + for (l = 1; l < n; l++) { val += (static_cast(l) + 0.5) * data[l] * calc_pn_c(l, x); } return val; @@ -577,6 +578,122 @@ void calc_rn_c(int n, double uvw[3], double rn[]){ } } +//============================================================================== +// CALC_ZN calculates the n-th order modified Zernike polynomial moment for a +// given angle (rho, theta) location in the unit disk. The normalization of the +// polynomials is such tha the integral of Z_pq*Z_pq over the unit disk is +// exactly pi +//============================================================================== + +void calc_zn_c(int n, double rho, double phi, double zn[]) { + // This procedure uses the modified Kintner's method for calculating Zernike + // polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, + // R. (2003). A comparative analysis of algorithms for fast computation of + // Zernike moments. Pattern Recognition, 36(3), 731-742. + double sin_phi; // Cosine of phi + double cos_phi; // Sine of phi + double sin_phi_vec[n + 1]; // Sin[n * phi] + double cos_phi_vec[n + 1]; // Cos[n * phi] + double zn_mat[n + 1][n + 1]; // Matrix forms of the coefficients which are + // easier to work with + // Variables for R_m_n calculation + double k1; + double k2; + double k3; + double k4; + // Loop counters + int i; + int p; + int q; + + const double SQRT_N_1[11] = + {std::sqrt(1.), std::sqrt(2.), std::sqrt(3.), std::sqrt(4.), + std::sqrt(5.), std::sqrt(6.), std::sqrt(7.), std::sqrt(8.), + std::sqrt(9.), std::sqrt(10.), std::sqrt(11.)}; + + const double SQRT_2N_2[11] = + {std::sqrt(2.) * std::sqrt(1.), std::sqrt(2.) * std::sqrt(2.), + std::sqrt(2.) * std::sqrt(3.), std::sqrt(2.) * std::sqrt(4.), + std::sqrt(2.) * std::sqrt(5.), std::sqrt(2.) * std::sqrt(6.), + std::sqrt(2.) * std::sqrt(7.), std::sqrt(2.) * std::sqrt(8.), + std::sqrt(2.) * std::sqrt(9.), std::sqrt(2.) * std::sqrt(10.), + std::sqrt(2.) * std::sqrt(11.)}; + + // n == radial degree + // m == azimuthal frequency + + // =========================================================================== + // Determine vector of sin(n*phi) and cos(n*phi). This takes advantage of the + // following recurrence relations so that only a single sin/cos have to be + // evaluated (http://mathworld.wolfram.com/Multiple-AngleFormulas.html) + // + // sin(nx) = 2 cos(x) sin((n-1)x) - sin((n-2)x) + // cos(nx) = 2 cos(x) cos((n-1)x) - cos((n-2)x) + + sin_phi = std::sin(phi); + cos_phi = std::cos(phi); + + sin_phi_vec[0] = 1.0; + cos_phi_vec[0] = 1.0; + + sin_phi_vec[1] = 2.0 * cos_phi; + cos_phi_vec[1] = cos_phi; + + for (i = 2; i <= n; i++) { + sin_phi_vec[i] = 2. * cos_phi * sin_phi_vec[i - 1] - sin_phi_vec[i - 2]; + cos_phi_vec[i] = 2. * cos_phi * cos_phi_vec[i - 1] - cos_phi_vec[i - 2]; + } + + for (i = 0; i <= n; i++) { + sin_phi_vec[i] *= sin_phi; + } + + // =========================================================================== + // Calculate R_pq(rho) + + // Fill the main diagonal first (Eq 3.9 in Chong) + for (p = 0; p <= n; p++) { + zn_mat[p][p] = std::pow(rho, p); + } + + // Fill the 2nd diagonal (Eq 3.10 in Chong) + for (q = 0; q <= n - 2; q++) { + zn_mat[q][q+2] = (q + 2) * zn_mat[q+2][q+2] - (q + 1) * zn_mat[q][q]; + } + + // Fill in the rest of the values using the original results (Eq. 3.8 in Chong) + for (p = 4; p <= n; p++) { + k2 = static_cast (2 * p * (p - 1) * (p - 2)); + for (q = p - 4; q >= 0; q -= 2) { + k1 = static_cast((p + q) * (p - q) * (p - 2)) / 2.; + k3 = static_cast(-q * q * (p - 1) - p * (p - 1) * (p - 2)); + k4 = static_cast(-p * (p + q - 2) * (p - q - 2)) / 2.; + zn_mat[q][p] = + ((k2 * rho * rho + k3) * zn_mat[q][p-2] + k4 * zn_mat[q][p-4]) / k1; + } + } + + // Roll into a single vector for easier computation later + // The vector is ordered (0,0), (1,-1), (1,1), (2,-2), (2,0), + // (2, 2), .... in (n,m) indices + // Note that the cos and sin vectors are offset by one + // sin_phi_vec = [sin(x), sin(2x), sin(3x) ...] + // cos_phi_vec = [1.0, cos(x), cos(2x)... ] + i = 0; + for (p = 0; p <= n; p++) { + for (q = -p; q <= p; q += 2) { + if (q < 0) { + zn[i] = zn_mat[std::abs(q)][p] * sin_phi_vec[std::abs(q) - 1] * SQRT_2N_2[p]; + } else if (q == 0) { + zn[i] = zn_mat[q][p] * SQRT_N_1[p]; + } else { + zn[i] = zn_mat[q][p] * cos_phi_vec[q] * SQRT_2N_2[p]; + } + i++; + } + } + +} //============================================================================== // ROTATE_ANGLE rotates direction std::cosines through a polar angle whose @@ -678,8 +795,8 @@ double watt_spectrum_c(double a, double b) { // FADDEEVA the Faddeeva function, using Stephen Johnson's implementation //============================================================================== -// std::complex faddeeva_c(std::complex z) { -// std::complex wv; // The resultant w(z) value +// double complex __attribute__ ((const)) faddeeva_c(double complex z) { +// double complex wv; // The resultant w(z) value // double relerr; // Target relative error in the inner loop of MIT Faddeeva // // Technically, the value we want is given by the equation: @@ -698,19 +815,19 @@ double watt_spectrum_c(double a, double b) { // // Note that faddeeva_w will interpret zero as machine epsilon // relerr = 0.; -// if (z.imag() > 0.) { -// wv = Faddeeva::w(z, relerr); +// if (cimag(z) > 0.) { +// wv = Faddeeva_w(z, relerr); // } else { -// wv = -std::conj(Faddeeva::w(std::conj(z), relerr)); +// wv = -conj(Faddeeva_w(conj(z), relerr)); // } // return wv; // } -// std::complex w_derivative_c(std::complex z, int order){ -// std::complex wv; // The resultant w(z) value +// double complex w_derivative_c(double complex z, int order){ +// double complex wv; // The resultant w(z) value -// const std::complex twoi_sqrtpi(0.0, 2.0 / std::sqrt(PI)); +// const double complex twoi_sqrtpi = 2.0 / std::sqrt(PI) * I; // switch(order) { // case 0: diff --git a/src/math_functions.h b/src/math_functions.h index aaba08261..4e7b2e8c6 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -2,11 +2,11 @@ #define MATH_FUNCTIONS_H #include -#include +#include #include #include "random_lcg.h" -// #include "faddeeva/Faddeeva.hh" +// #include "faddeeva/Faddeeva.h" namespace openmc { @@ -51,6 +51,15 @@ extern "C" double evaluate_legendre_c(int n, double data[], double x) extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); +//============================================================================== +// CALC_ZN calculates the n-th order modified Zernike polynomial moment for a +// given angle (rho, theta) location in the unit disk. The normalization of the +// polynomials is such tha the integral of Z_pq*Z_pq over the unit disk is +// exactly pi +//============================================================================== + +extern "C" void calc_zn_c(int n, double rho, double phi, double zn[]); + //============================================================================== // ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is // mu and through an azimuthal angle sampled uniformly. Note that this is done @@ -83,16 +92,17 @@ extern "C" double watt_spectrum_c(double a, double b); // FADDEEVA the Faddeeva function, using Stephen Johnson's implementation //============================================================================== -// extern "C" std::complex faddeeva_c(std::complex z); +// extern "C" double complex faddeeva_c(double complex z) __attribute__ ((const)); -// extern "C" std::complex w_derivative_c(std::complex z, int order); +// extern "C" double complex w_derivative_c(double complex z, int order); //============================================================================== // BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. // The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... //============================================================================== -extern "C" void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]); +extern "C" void broaden_wmp_polynomials_c(double E, double dopp, int n, + double factors[]); } // namespace openmc #endif // MATH_FUNCTIONS_H \ No newline at end of file diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index bfa021ceb..03309bafe 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -115,7 +115,38 @@ def test_calc_rn(): def test_calc_zn(): - pass + n = 10 + rho = 0.5 + phi = 0.5 + + # Reference solution from running the Fortran implementation + ref_vals = np.array( + [1.00000000e+00, 4.79425539e-01, 8.77582562e-01, + 5.15293637e-01, -8.66025404e-01, 3.30866239e-01, + 3.52667735e-01, -8.47512624e-01, -1.55136145e+00, + 2.50093775e-02, 1.79715684e-01, -1.33048245e+00, + -2.79508497e-01, -8.54292956e-01, -8.22482403e-02, + 6.47865100e-02, -1.18780200e+00, 5.18993370e-01, + 9.50010991e-01, -8.42327938e-02, -8.67263404e-02, + 8.25035501e-03, -7.44248626e-01, 1.52505281e+00, + 1.15751620e+00, 9.79225149e-01, 3.40611006e-01, + -5.78783240e-02, -1.09619759e-02, -3.17938327e-01, + 1.90147482e+00, 2.84658914e-01, 5.21064646e-01, + 1.34842791e-01, 4.25607546e-01, -2.92642715e-02, + -1.25423479e-02, -4.67751162e-02, 1.50696182e+00, + -6.13603897e-01, -8.67187500e-01, -3.93990531e-01, + -6.89672461e-01, 3.28139254e-01, -1.08327149e-02, + -8.53837419e-03, 7.04712042e-02, 7.73660979e-01, + -1.63799891e+00, -8.12396290e-01, -1.48708143e+00, + -1.16158437e-01, -1.03565982e+00, 1.88131088e-01, + -1.84122553e-03, -4.39233743e-03, 9.01295675e-02, + 1.32511582e-01, -1.83260987e+00, -7.93994967e-01, + -2.97978009e-01, -5.09818305e-01, 8.38707753e-01, + -9.29602211e-01, 7.78441102e-02, 1.29931014e-03]) + + test_vals = openmc.capi.math.calc_zn(n, rho, phi) + + assert np.allclose(ref_vals, test_vals) def test_rotate_angle(): @@ -128,7 +159,7 @@ def test_rotate_angle(): test_uvw = openmc.capi.math.rotate_angle(uvw0, mu, phi) - assert np.allclose(ref_uvw, test_uvw) + assert np.array_equal(ref_uvw, test_uvw) # Repeat for mu = 1 (no change) mu = 1. @@ -136,7 +167,7 @@ def test_rotate_angle(): test_uvw = openmc.capi.math.rotate_angle(uvw0, mu, phi) - assert np.allclose(ref_uvw, test_uvw) + assert np.array_equal(ref_uvw, test_uvw) # Need to test phi=None somehow... @@ -180,3 +211,5 @@ def test_broaden_wmp_polynomials(): test_val = openmc.capi.math.broaden_wmp_polynomials(test_E, test_dopp, n) assert np.allclose(ref_val, test_val) + +test_calc_zn() \ No newline at end of file From 044934f93d065888d67dec704f0013aa99eca7d3 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 7 May 2018 20:07:05 -0400 Subject: [PATCH 247/361] Implemented broaden_wmp_polynomials in C++ --- src/math.F90 | 46 +---------------------------------- src/math_functions.cpp | 14 +++++++---- src/math_functions.h | 2 ++ tests/unit_tests/test_math.py | 9 +++---- 4 files changed, 16 insertions(+), 55 deletions(-) diff --git a/src/math.F90 b/src/math.F90 index a386747a4..6edf2f1c3 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -336,51 +336,7 @@ contains integer(C_INT), intent(in) :: n ! number of components to polynomial real(C_DOUBLE), intent(out):: factors(n) ! output leading coefficient - integer :: i - - real(8) :: sqrtE ! sqrt(energy) - real(8) :: beta ! sqrt(atomic weight ratio * E / kT) - real(8) :: half_inv_dopp2 ! 0.5 / dopp**2 - real(8) :: quarter_inv_dopp4 ! 0.25 / dopp**4 - real(8) :: erf_beta ! error function of beta - real(8) :: exp_m_beta2 ! exp(-beta**2) - - sqrtE = sqrt(E) - beta = sqrtE * dopp - half_inv_dopp2 = HALF / dopp**2 - quarter_inv_dopp4 = half_inv_dopp2**2 - - if (beta > 6.0_8) then - ! Save time, ERF(6) is 1 to machine precision. - ! beta/sqrtpi*exp(-beta**2) is also approximately 1 machine epsilon. - erf_beta = ONE - exp_m_beta2 = ZERO - else - erf_beta = erf(beta) - exp_m_beta2 = exp(-beta**2) - end if - - ! Assume that, for sure, we'll use a second order (1/E, 1/V, const) - ! fit, and no less. - - factors(1) = erf_beta / E - factors(2) = ONE / sqrtE - factors(3) = factors(1) * (half_inv_dopp2 + E) & - + exp_m_beta2 / (beta * SQRT_PI) - - ! Perform recursive broadening of high order components - do i = 1, n-3 - if (i /= 1) then - factors(i+3) = -factors(i-1) * (i - ONE) * i * quarter_inv_dopp4 & - + factors(i+1) * (E + (ONE + TWO * i) * half_inv_dopp2) - else - ! Although it's mathematically identical, factors(0) will contain - ! nothing, and we don't want to have to worry about memory. - factors(i+3) = factors(i+1)*(E + (ONE + TWO * i) * half_inv_dopp2) - end if - end do - - ! call broaden_wmp_polynomials_cc(E, dopp, n, factors) + call broaden_wmp_polynomials_cc(E, dopp, n, factors) end subroutine broaden_wmp_polynomials diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 9f698f42a..7ab2a8450 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -827,7 +827,7 @@ double watt_spectrum_c(double a, double b) { // double complex w_derivative_c(double complex z, int order){ // double complex wv; // The resultant w(z) value -// const double complex twoi_sqrtpi = 2.0 / std::sqrt(PI) * I; +// const double complex twoi_sqrtpi = 2.0 / SQRT_PI * I; // switch(order) { // case 0: @@ -859,6 +859,7 @@ void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { double erf_beta; // error function of beta double exp_m_beta2; // exp(-beta**2) int i; + double ip1_dbl; sqrtE = std::sqrt(E); beta = sqrtE * dopp; @@ -881,17 +882,20 @@ void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { factors[0] = erf_beta / E; factors[1] = 1. / sqrtE; factors[2] = factors[0] * (half_inv_dopp2 + E) + exp_m_beta2 / - (beta * std::sqrt(PI)); + (beta * SQRT_PI); // Perform recursive broadening of high order components for (i = 0; i < n - 3; i++) { + ip1_dbl = static_cast(i + 1); if (i != 0) { - factors[i + 3] = -factors[i - 1] * (i - 1.) * i * quarter_inv_dopp4 + - factors[i + 1] * (E + (1. + 2. * i) * half_inv_dopp2); + factors[i + 3] = -factors[i - 1] * (ip1_dbl - 1.) * ip1_dbl * + quarter_inv_dopp4 + factors[i + 1] * + (E + (1. + 2. * ip1_dbl) * half_inv_dopp2); } else { // Although it's mathematically identical, factors[0] will contain // nothing, and we don't want to have to worry about memory. - factors[i + 3] = factors[i + 1]*(E + (1. + 2. * i) * half_inv_dopp2); + factors[i + 3] = factors[i + 1] * + (E + (1. + 2. * ip1_dbl) * half_inv_dopp2); } } } diff --git a/src/math_functions.h b/src/math_functions.h index 4e7b2e8c6..ab35d140a 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -16,6 +16,8 @@ namespace openmc { // modifying test results const double PI = 3.1415926535898; +const double SQRT_PI = std::sqrt(PI); + //============================================================================== // NORMAL_PERCENTILE calculates the percentile of the standard normal // distribution with a specified probability level diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index 03309bafe..99e16712c 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -199,17 +199,16 @@ def test_broaden_wmp_polynomials(): # First lets do beta > 6 test_E = 0.5 test_dopp = 100. # approximately U235 at room temperature - n = 4 - ref_val = [2., 1.41421356, 1.0001, 0.70731891] + n = 6 + + ref_val = [2., 1.41421356, 1.0001, 0.70731891, 0.50030001, 0.353907] test_val = openmc.capi.math.broaden_wmp_polynomials(test_E, test_dopp, n) assert np.allclose(ref_val, test_val) # now beta < 6 test_dopp = 5. - ref_val = [1.99999885, 1.41421356, 1.04, 0.79195959] + ref_val = [1.99999885, 1.41421356, 1.04, 0.79195959, 0.6224, 0.50346003] test_val = openmc.capi.math.broaden_wmp_polynomials(test_E, test_dopp, n) assert np.allclose(ref_val, test_val) - -test_calc_zn() \ No newline at end of file From 382835960b6614e1f9e282fd900230171d1c6e7d Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Tue, 8 May 2018 15:02:23 -0400 Subject: [PATCH 248/361] removed errant print statements in regression_tests/tallies/test.py --- tests/regression_tests/tallies/test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/regression_tests/tallies/test.py b/tests/regression_tests/tallies/test.py index b86d71d00..601b611f4 100644 --- a/tests/regression_tests/tallies/test.py +++ b/tests/regression_tests/tallies/test.py @@ -76,7 +76,6 @@ def test_tallies(): mu_tally1 = Tally() mu_tally1.filters = [mu_filter] mu_tally1.scores = ['scatter', 'nu-scatter'] - print('mu_tally1', mu_tally1.id) mu_tally2 = Tally() mu_tally2.filters = [mu_filter, mesh_filter] @@ -104,7 +103,6 @@ def test_tallies(): legendre_tally.filters = [legendre_filter] legendre_tally.scores = ['scatter', 'nu-scatter'] legendre_tally.estimatir = 'analog' - print('legendre_tally', mu_tally1.id) harmonics_filter = SphericalHarmonicsFilter(order=4) harmonics_tally = Tally() From 72d84bc286b946723e278fdcc6474d2e93c26a77 Mon Sep 17 00:00:00 2001 From: Alex Lindsay Date: Mon, 7 May 2018 14:42:09 -0600 Subject: [PATCH 249/361] Use consistent normalization for Zernikes. --- openmc/filter_expansion.py | 13 +++++++------ src/math.F90 | 12 +++--------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index 872eaac9f..b09241082 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -318,15 +318,15 @@ class ZernikeFilter(ExpansionFilter): This filter allows scores to be multiplied by Zernike polynomials of the particle's position normalized to a given unit circle, up to a - user-specified order. The Zernike polynomials follow the definition by `Noll - `_ and are defined as + user-specified order. The standard Zernike polynomials follow the definition by + Born and Wolf, *Principles of Optics* and are defined as .. math:: - Z_n^m(\rho, \theta) = \sqrt{2n + 2} R_n^m(\rho) \cos (m\theta), \quad m > 0 + Z_n^m(\rho, \theta) = R_n^m(\rho) \cos (m\theta), \quad m > 0 - Z_n^{m}(\rho, \theta) = \sqrt{2n + 2} R_n^{m}(\rho) \sin (m\theta), \quad m < 0 + Z_n^{m}(\rho, \theta) = R_n^{m}(\rho) \sin (m\theta), \quad m < 0 - Z_n^{m}(\rho, \theta) = \sqrt{n + 1} R_n^{m}(\rho), \quad m = 0 + Z_n^{m}(\rho, \theta) = R_n^{m}(\rho), \quad m = 0 where the radial polynomials are @@ -335,7 +335,8 @@ class ZernikeFilter(ExpansionFilter): \frac{n+m}{2} - k)! (\frac{n-m}{2} - k)!} \rho^{n-2k}. With this definition, the integral of :math:`(Z_n^m)^2` over the unit disk - is exactly :math:`\pi` for each polynomial. + is :math:`\frac{\epsilon_m\pi}{2n+2}` for each polynomial where :math:`\epsilon_m` is + 2 if :math:`m` equals 0 and 1 otherwise. Specifying a filter with order N tallies moments for all :math:`n` from 0 to N and each value of :math:`m`. The ordering of the Zernike polynomial diff --git a/src/math.F90 b/src/math.F90 index 20bacf88c..c63313164 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -600,12 +600,6 @@ contains real(8) :: k1, k2, k3, k4 ! Variables for R_m_n calculation integer :: i,p,q ! Loop counters - real(8), parameter :: SQRT_N_1(0:10) = [& - sqrt(1.0_8), sqrt(2.0_8), sqrt(3.0_8), sqrt(4.0_8), & - sqrt(5.0_8), sqrt(6.0_8), sqrt(7.0_8), sqrt(8.0_8), & - sqrt(9.0_8), sqrt(10.0_8), sqrt(11.0_8)] - real(8), parameter :: SQRT_2N_2(0:10) = SQRT_N_1*sqrt(2.0_8) - ! n == radial degree ! m == azimuthal frequency @@ -669,11 +663,11 @@ contains do p = 0, n do q = -p, p, 2 if (q < 0) then - zn(i) = zn_mat(p+1, abs(q)+1) * sin_phi_vec(abs(q)) * SQRT_2N_2(p) + zn(i) = zn_mat(p+1, abs(q)+1) * sin_phi_vec(abs(q)) else if (q == 0) then - zn(i) = zn_mat(p+1, q+1) * SQRT_N_1(p) + zn(i) = zn_mat(p+1, q+1) else - zn(i) = zn_mat(p+1, q+1) * cos_phi_vec(abs(q)+1) * SQRT_2N_2(p) + zn(i) = zn_mat(p+1, q+1) * cos_phi_vec(abs(q)+1) end if i = i + 1 end do From 42e1148fa68bce7442e5767281093ee54fa722e9 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 10 May 2018 00:12:22 -0400 Subject: [PATCH 250/361] Move most lattice summary.h5 writing to C++ --- src/geometry_header.F90 | 2 - src/hdf5_interface.h | 10 ++++- src/input_xml.F90 | 22 ----------- src/lattice.cpp | 87 +++++++++++++++++++++++++++++++++++++++-- src/lattice.h | 1 - src/summary.F90 | 65 +----------------------------- 6 files changed, 93 insertions(+), 94 deletions(-) diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 025fd69c5..02f9b7e82 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -198,7 +198,6 @@ module geometry_header type, extends(Lattice) :: RectLattice integer :: n_cells(3) ! Number of cells along each axis - real(8), allocatable :: lower_left(:) ! Global lower-left corner of lat end type RectLattice !=============================================================================== @@ -208,7 +207,6 @@ module geometry_header type, extends(Lattice) :: HexLattice integer :: n_rings ! Number of radial ring cell positoins integer :: n_axial ! Number of axial cell positions - real(8), allocatable :: center(:) ! Global center of lattice end type HexLattice !=============================================================================== diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 66757df8e..232d2f6e7 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -98,8 +98,16 @@ write_int(hid_t group_id, char const *name, int32_t buffer) H5Dclose(dataset); } +template void +write_int(hid_t group_id, char const *name, + const std::array &buffer, bool indep) +{ + hsize_t dims[1] {array_len}; + write_dataset(group_id, 1, dims, name, H5T_NATIVE_INT, buffer.data(), indep); +} -template inline void + +template void write_double_1D(hid_t group_id, char const *name, const std::array &buffer) { diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 717e2cada..4d48e8d14 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1284,15 +1284,6 @@ contains call fatal_error("Rectangular lattice must be two or three dimensions.") end if - ! Read lattice lower-left location - if (node_word_count(node_lat, "lower_left") /= n) then - call fatal_error("Number of entries on must be the same & - &as the number of entries on .") - end if - - allocate(lat % lower_left(n)) - call get_node_array(node_lat, "lower_left", lat % lower_left) - ! TODO: Remove deprecation warning in a future release. if (check_for_node(node_lat, "type")) then call warning("The use of 'type' is no longer needed. The utility & @@ -1365,19 +1356,6 @@ contains lat % is_3d = .false. end if - ! Read lattice lower-left location - n = node_word_count(node_lat, "center") - if (lat % is_3d .and. n /= 3) then - call fatal_error("A hexagonal lattice with must have & - &
specified by 3 numbers.") - else if ((.not. lat % is_3d) .and. n /= 2) then - call fatal_error("A hexagonal lattice without must have & - &
specified by 2 numbers.") - end if - - allocate(lat % center(n)) - call get_node_array(node_lat, "center", lat % center) - ! Copy number of dimensions n_rings = lat % n_rings n_z = lat % n_axial diff --git a/src/lattice.cpp b/src/lattice.cpp index a28d7efe7..5723b2280 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -272,11 +272,57 @@ RectLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const void RectLattice::to_hdf5_inner(hid_t lat_group) const { + // Write basic lattice information. + write_string(lat_group, "type", "rectangular", false); if (is_3d) { write_double_1D(lat_group, "pitch", pitch); + write_double_1D(lat_group, "lower_left", lower_left); + write_int(lat_group, "dimension", n_cells, false); } else { - std::array out {{pitch[0], pitch[1]}}; - write_double_1D(lat_group, "pitch", out); + std::array pitch_short {{pitch[0], pitch[1]}}; + write_double_1D(lat_group, "pitch", pitch_short); + std::array ll_short {{lower_left[0], lower_left[1]}}; + write_double_1D(lat_group, "lower_left", ll_short); + std::array nc_short {{n_cells[0], n_cells[1]}}; + write_int(lat_group, "dimension", nc_short, false); + } + + // Write the universe ids. The convention here is to switch the ordering on + // the y-axis to match the way universes are input in a text file. + if (is_3d) { + hsize_t nx {static_cast(n_cells[0])}; + hsize_t ny {static_cast(n_cells[1])}; + hsize_t nz {static_cast(n_cells[2])}; + int out[nx*ny*nz]; + + for (int m = 0; m < nz; m++) { + for (int k = 0; k < ny; k++) { + for (int j = 0; j < nx; j++) { + int indx1 = nx*ny*m + nx*k + j; + int indx2 = nx*ny*m + nx*(ny-k-1) + j; + out[indx2] = universes_c[universes[indx1]]->id; + } + } + } + + hsize_t dims[3] {nz, ny, nx}; + write_int(lat_group, 3, dims, "universes", out, false); + + } else { + hsize_t nx {static_cast(n_cells[0])}; + hsize_t ny {static_cast(n_cells[1])}; + int out[nx*ny]; + + for (int k = 0; k < ny; k++) { + for (int j = 0; j < nx; j++) { + int indx1 = nx*k + j; + int indx2 = nx*(ny-k-1) + j; + out[indx2] = universes_c[universes[indx1]]->id; + } + } + + hsize_t dims[3] {1, ny, nx}; + write_int(lat_group, 3, dims, "universes", out, false); } } @@ -670,12 +716,45 @@ HexLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const void HexLattice::to_hdf5_inner(hid_t lat_group) const { + // Write basic lattice information. + write_string(lat_group, "type", "hexagonal", false); + write_int(lat_group, 0, nullptr, "n_rings", &n_rings, false); + write_int(lat_group, 0, nullptr, "n_axial", &n_axial, false); if (is_3d) { write_double_1D(lat_group, "pitch", pitch); + write_double_1D(lat_group, "center", center); } else { - std::array out {{pitch[0]}}; - write_double_1D(lat_group, "pitch", out); + std::array pitch_short {{pitch[0]}}; + write_double_1D(lat_group, "pitch", pitch_short); + std::array center_short {{center[0], center[1]}}; + write_double_1D(lat_group, "center", center_short); } + + // Write the universe ids. + hsize_t nx {static_cast(2*n_rings - 1)}; + hsize_t ny {static_cast(2*n_rings - 1)}; + hsize_t nz {static_cast(n_axial)}; + int out[nx*ny*nz]; + + for (int m = 0; m < nz; m++) { + for (int k = 0; k < ny; k++) { + for (int j = 0; j < nx; j++) { + int indx = nx*ny*m + nx*k + j; + if (j + k < n_rings - 1) { + // This array position is never used; put a -1 to indicate this. + out[indx] = -1; + } else if (j + k > 3*n_rings - 3) { + // This array position is never used; put a -1 to indicate this. + out[indx] = -1; + } else { + out[indx] = universes_c[universes[indx]]->id; + } + } + } + } + + hsize_t dims[3] {nz, ny, nx}; + write_int(lat_group, 3, dims, "universes", out, false); } //============================================================================== diff --git a/src/lattice.h b/src/lattice.h index 78d83a2cb..8bdf469c3 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -41,7 +41,6 @@ class Lattice public: int32_t id; //! Universe ID number std::string name; //! User-defined name - //std::vector pitch; //! Pitch along each basis std::vector universes; //! Universes filling each lattice tile int32_t outer{NO_OUTER_UNIVERSE}; //! Universe tiled outside the lattice //std::vector offset; //! Distribcell offsets diff --git a/src/summary.F90 b/src/summary.F90 index 4ff27963e..22de03e7b 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -160,8 +160,7 @@ contains subroutine write_geometry(file_id) integer(HID_T), intent(in) :: file_id - integer :: i, j, k, m - integer, allocatable :: lattice_universes(:,:,:) + integer :: i, j integer, allocatable :: cell_materials(:) integer, allocatable :: cell_ids(:) real(8), allocatable :: cell_temperatures(:) @@ -311,68 +310,6 @@ contains call write_dataset(lattice_group, "outer", lat % outer) end if - select type (lat) - type is (RectLattice) - ! Write lattice type. - call write_dataset(lattice_group, "type", "rectangular") - - ! Write lattice dimensions, lower left corner, and pitch - if (lat % is_3d) then - call write_dataset(lattice_group, "dimension", lat % n_cells) - call write_dataset(lattice_group, "lower_left", lat % lower_left) - else - call write_dataset(lattice_group, "dimension", lat % n_cells(1:2)) - call write_dataset(lattice_group, "lower_left", lat % lower_left) - end if - - ! Write lattice universes. - allocate(lattice_universes(lat%n_cells(1), lat%n_cells(2), & - &lat%n_cells(3))) - do j = 1, lat%n_cells(1) - do k = 0, lat%n_cells(2) - 1 - do m = 1, lat%n_cells(3) - lattice_universes(j, k+1, m) = & - universes(lat%get([j-1, lat%n_cells(2)-k-1, m-1])+1)%id - end do - end do - end do - - type is (HexLattice) - ! Write lattice type. - call write_dataset(lattice_group, "type", "hexagonal") - - ! Write number of lattice cells. - call write_dataset(lattice_group, "n_rings", lat%n_rings) - call write_dataset(lattice_group, "n_axial", lat%n_axial) - - ! Write lattice center - call write_dataset(lattice_group, "center", lat%center) - - ! Write lattice universes. - allocate(lattice_universes(2*lat%n_rings - 1, 2*lat%n_rings - 1, & - &lat%n_axial)) - do m = 1, lat%n_axial - do k = 1, 2*lat%n_rings - 1 - do j = 1, 2*lat%n_rings - 1 - if (j + k < lat%n_rings + 1) then - ! This array position is never used; put a -1 to indicate this - lattice_universes(j,k,m) = -1 - cycle - else if (j + k > 3*lat%n_rings - 1) then - ! This array position is never used; put a -1 to indicate this - lattice_universes(j,k,m) = -1 - cycle - end if - lattice_universes(j,k,m) = universes(lat%get([j-1,k-1,m-1])+1)%id - end do - end do - end do - end select - - ! Write lattice universes - call write_dataset(lattice_group, "universes", lattice_universes) - deallocate(lattice_universes) - call close_group(lattice_group) end do LATTICE_LOOP From 1dfa82530dcca5c407f53a01891acfc2f703c18e Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 10 May 2018 18:50:02 -0400 Subject: [PATCH 251/361] Move lattice outer to C++ --- src/geometry.F90 | 4 +-- src/geometry_header.F90 | 26 ++++++++++----- src/input_xml.F90 | 49 ++++------------------------- src/lattice.cpp | 70 +++++++++++++++++++++++++++++++++++++++-- src/summary.F90 | 21 +++---------- 5 files changed, 98 insertions(+), 72 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index 0d4fea004..35e4d3ca5 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -258,14 +258,14 @@ contains else ! Particle is outside the lattice. - if (lat % outer == NO_OUTER_UNIVERSE) then + if (lat % outer() == NO_OUTER_UNIVERSE) then call warning("Particle " // trim(to_str(p %id)) & // " is outside lattice " // trim(to_str(lat % id())) & // " but the lattice has no defined outer universe.") found = .false. return else - p % coord(j + 1) % universe = lat % outer + p % coord(j + 1) % universe = lat % outer() + 1 end if end if end associate diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 02f9b7e82..ebce31c45 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -141,11 +141,12 @@ module geometry_header integer(HID_T), intent(in), value :: group end subroutine lattice_to_hdf5_c - subroutine extend_cells_c(n) bind(C) - import C_INT32_t - implicit none - integer(C_INT32_T), intent(in), value :: n - end subroutine extend_cells_c + function lattice_outer_c(lat_ptr) bind(C, name='lattice_outer') & + result(outer) + import C_PTR, C_INT32_T + type(C_PTR), intent(in), value :: lat_ptr + integer(C_INT32_T) :: outer + end function lattice_outer_c function lattice_universe_c(lat_ptr, i_xyz) & bind(C, name='lattice_universe') result(univ) @@ -155,6 +156,12 @@ module geometry_header integer(C_INT), intent(in) :: i_xyz(3) integer(C_INT32_T) :: univ end function lattice_universe_c + + subroutine extend_cells_c(n) bind(C) + import C_INT32_t + implicit none + integer(C_INT32_T), intent(in), value :: n + end subroutine extend_cells_c end interface !=============================================================================== @@ -177,8 +184,6 @@ module geometry_header type, abstract :: Lattice type(C_PTR) :: ptr - integer :: outside ! Material to fill area outside - integer :: outer ! universe to tile outside the lat logical :: is_3d ! Lattice has cells on z axis integer, allocatable :: offset(:,:,:,:) ! Distribcell offsets contains @@ -189,6 +194,7 @@ module geometry_header procedure :: get => lattice_get procedure :: get_indices => lattice_get_indices procedure :: get_local_xyz => lattice_get_local_xyz + procedure :: outer => lattice_outer procedure :: to_hdf5 => lattice_to_hdf5 end type Lattice @@ -323,6 +329,12 @@ contains call lattice_get_local_xyz_c(this % ptr, global_xyz, i_xyz, local_xyz) end function lattice_get_local_xyz + function lattice_outer(this) result(outer) + class(Lattice), intent(in) :: this + integer(C_INT32_T) :: outer + outer = lattice_outer_c(this % ptr) + end function lattice_outer + subroutine lattice_to_hdf5(this, group) class(Lattice), intent(in) :: this integer(HID_T), intent(in) :: group diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 4d48e8d14..c6aa8a142 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1315,19 +1315,10 @@ contains end do ! Read outer universe for area outside lattice. - lat % outer = NO_OUTER_UNIVERSE if (check_for_node(node_lat, "outer")) then - call get_node_value(node_lat, "outer", lat % outer) - if (find(fill_univ_ids, lat % outer) == -1) & - call fill_univ_ids % push_back(lat % outer) - end if - - ! Check for 'outside' nodes which are no longer supported. - if (check_for_node(node_lat, "outside")) then - call fatal_error("The use of 'outside' in lattices is no longer & - &supported. Instead, use 'outer' which defines a universe rather & - &than a material. The utility openmc/src/utils/update_inputs.py & - &can be used automatically replace 'outside' with 'outer'.") + call get_node_value(node_lat, "outer", univ_id) + if (find(fill_univ_ids, univ_id) == -1) & + call fill_univ_ids % push_back(univ_id) end if ! Add lattice to dictionary @@ -1379,19 +1370,10 @@ contains end do ! Read outer universe for area outside lattice. - lat % outer = NO_OUTER_UNIVERSE if (check_for_node(node_lat, "outer")) then - call get_node_value(node_lat, "outer", lat % outer) - if (find(fill_univ_ids, lat % outer) == -1) & - call fill_univ_ids % push_back(lat % outer) - end if - - ! Check for 'outside' nodes which are no longer supported. - if (check_for_node(node_lat, "outside")) then - call fatal_error("The use of 'outside' in lattices is no longer & - &supported. Instead, use 'outer' which defines a universe rather & - &than a material. The utility openmc/src/utils/update_inputs.py & - &can be used automatically replace 'outside' with 'outer'.") + call get_node_value(node_lat, "outer", univ_id) + if (find(fill_univ_ids, univ_id) == -1) & + call fill_univ_ids % push_back(univ_id) end if ! Add lattice to dictionary @@ -3875,7 +3857,6 @@ contains integer :: j ! index for various purposes integer :: lid ! lattice IDs integer :: id ! user-specified id - class(Lattice), pointer :: lat => null() call adjust_indices_c() @@ -3916,24 +3897,6 @@ contains end associate end do - ! ========================================================================== - ! ADJUST UNIVERSE INDICES FOR EACH LATTICE - - do i = 1, n_lattices - lat => lattices(i) % obj - - if (lat % outer /= NO_OUTER_UNIVERSE) then - if (universe_dict % has(lat % outer)) then - lat % outer = universe_dict % get(lat % outer) - else - call fatal_error("Invalid universe number " & - &// trim(to_str(lat % outer)) & - &// " specified on lattice " // trim(to_str(lat % id()))) - end if - end if - - end do - end subroutine adjust_indices !=============================================================================== diff --git a/src/lattice.cpp b/src/lattice.cpp index 5723b2280..7d2b51794 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -50,13 +50,29 @@ Lattice::Lattice(pugi::xml_node lat_node) } void -Lattice::to_hdf5(hid_t lat_group) const +Lattice::to_hdf5(hid_t lattices_group) const { + // Make a group for the lattice. + std::string group_name {"lattice "}; + group_name += std::to_string(id); + hid_t lat_group = create_group(lattices_group, group_name); + + // Write the name and outer universe. if (!name.empty()) { write_string(lat_group, "name", name, false); } + if (outer != NO_OUTER_UNIVERSE) { + int32_t outer_id = universes_c[outer]->id; + write_int(lat_group, 0, nullptr, "outer", &outer_id, false); + } else { + write_int(lat_group, 0, nullptr, "outer", &outer, false); + } + + // Call subclass-overriden function to fill in other details. to_hdf5_inner(lat_group); + + close_group(lat_group); } //============================================================================== @@ -150,6 +166,7 @@ RectLattice::operator[](const int i_xyz[3]) void RectLattice::adjust_indices() { + // Adjust the indices for the universes array. int nx = n_cells[0]; int ny = n_cells[1]; int nz = n_cells[2]; @@ -157,10 +174,32 @@ RectLattice::adjust_indices() for (int iy = 0; iy < ny; iy++) { for (int ix = 0; ix < nx; ix++) { int indx = nx*ny*iz + nx*iy + ix; - universes[indx] = universe_dict[universes[indx]]; + int uid = universes[indx]; + auto search = universe_dict.find(uid); + if (search != universe_dict.end()) { + universes[indx] = search->second; + } else { + std::stringstream err_msg; + err_msg << "Invalid universe number " << uid << " specified on " + "lattice " << id; + fatal_error(err_msg); + } } } } + + // Adjust the index for the outer universe. + if (outer != NO_OUTER_UNIVERSE) { + auto search = universe_dict.find(outer); + if (search != universe_dict.end()) { + outer = search->second; + } else { + std::stringstream err_msg; + err_msg << "Invalid universe number " << outer << " specified on " + "lattice " << id; + fatal_error(err_msg); + } + } } //============================================================================== @@ -493,6 +532,7 @@ HexLattice::operator[](const int i_xyz[3]) void HexLattice::adjust_indices() { + // Adjust the indices for the universes array. for (int iz = 0; iz < n_axial; iz++) { for (int ia = 0; ia < 2*n_rings-1; ia++) { for (int ix = 0; ix < 2*n_rings-1; ix++) { @@ -501,11 +541,33 @@ HexLattice::adjust_indices() int indx = (2*n_rings-1)*(2*n_rings-1) * iz + (2*n_rings-1) * ia + ix; - universes[indx] = universe_dict[universes[indx]]; + int uid = universes[indx]; + auto search = universe_dict.find(uid); + if (search != universe_dict.end()) { + universes[indx] = search->second; + } else { + std::stringstream err_msg; + err_msg << "Invalid universe number " << uid << " specified on " + "lattice " << id; + fatal_error(err_msg); + } } } } } + + // Adjust the index for the outer universe. + if (outer != NO_OUTER_UNIVERSE) { + auto search = universe_dict.find(outer); + if (search != universe_dict.end()) { + outer = search->second; + } else { + std::stringstream err_msg; + err_msg << "Invalid universe number " << outer << " specified on " + "lattice " << id; + fatal_error(err_msg); + } + } } //============================================================================== @@ -824,6 +886,8 @@ extern "C" { local_xyz[2] = xyz[2]; } + int32_t lattice_outer(Lattice *lat) {return lat->outer;} + void lattice_to_hdf5(Lattice *lat, hid_t group) {lat->to_hdf5(group);} int32_t lattice_universe(Lattice *lat, const int i_xyz[3]) diff --git a/src/summary.F90 b/src/summary.F90 index 22de03e7b..fb934726e 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -168,7 +168,7 @@ contains integer(HID_T) :: cells_group, cell_group integer(HID_T) :: surfaces_group integer(HID_T) :: universes_group, univ_group - integer(HID_T) :: lattices_group, lattice_group + integer(HID_T) :: lattices_group type(Cell), pointer :: c class(Lattice), pointer :: lat @@ -293,25 +293,12 @@ contains ! ========================================================================== ! WRITE INFORMATION ON LATTICES - ! Create lattices group (nothing directly written here) then close lattices_group = create_group(geom_group, "lattices") - ! Write information on each lattice - LATTICE_LOOP: do i = 1, n_lattices + do i = 1, n_lattices lat => lattices(i)%obj - lattice_group = create_group(lattices_group, "lattice " // trim(to_str(lat%id()))) - - call lat % to_hdf5(lattice_group) - - ! Write name, pitch, and outer universe - if (lat % outer > 0) then - call write_dataset(lattice_group, "outer", universes(lat % outer) % id) - else - call write_dataset(lattice_group, "outer", lat % outer) - end if - - call close_group(lattice_group) - end do LATTICE_LOOP + call lat % to_hdf5(lattices_group) + end do call close_group(lattices_group) call close_group(geom_group) From 366bd36107280531fe5b31fcc12913159e1e1f13 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 10 May 2018 23:17:19 -0400 Subject: [PATCH 252/361] Add lattice iterators --- src/lattice.cpp | 110 +++++++++++++++++++++++++++++++----------------- src/lattice.h | 58 +++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 38 deletions(-) diff --git a/src/lattice.cpp b/src/lattice.cpp index 7d2b51794..93c53d88a 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -167,24 +167,16 @@ void RectLattice::adjust_indices() { // Adjust the indices for the universes array. - int nx = n_cells[0]; - int ny = n_cells[1]; - int nz = n_cells[2]; - for (int iz = 0; iz < nz; iz++) { - for (int iy = 0; iy < ny; iy++) { - for (int ix = 0; ix < nx; ix++) { - int indx = nx*ny*iz + nx*iy + ix; - int uid = universes[indx]; - auto search = universe_dict.find(uid); - if (search != universe_dict.end()) { - universes[indx] = search->second; - } else { - std::stringstream err_msg; - err_msg << "Invalid universe number " << uid << " specified on " - "lattice " << id; - fatal_error(err_msg); - } - } + for (auto it = this->begin(); it != this->end(); ++it) { + int uid = *it; + auto search = universe_dict.find(uid); + if (search != universe_dict.end()) { + *it = search->second; + } else { + std::stringstream err_msg; + err_msg << "Invalid universe number " << uid << " specified on " + "lattice " << id; + fatal_error(err_msg); } } @@ -529,30 +521,34 @@ HexLattice::operator[](const int i_xyz[3]) //============================================================================== +HexLatticeIter +HexLattice::begin() +{ + return HexLatticeIter(*this, n_rings-1, 0, 0); +} + +HexLatticeIter +HexLattice::end() +{ + return HexLatticeIter(*this, n_rings, 2*n_rings-2, n_axial-1); +} + +//============================================================================== + void HexLattice::adjust_indices() { // Adjust the indices for the universes array. - for (int iz = 0; iz < n_axial; iz++) { - for (int ia = 0; ia < 2*n_rings-1; ia++) { - for (int ix = 0; ix < 2*n_rings-1; ix++) { - int i_xyz[3] {ix+1, ia+1, iz+1}; - if (are_valid_indices(i_xyz)) { - int indx = (2*n_rings-1)*(2*n_rings-1) * iz - + (2*n_rings-1) * ia - + ix; - int uid = universes[indx]; - auto search = universe_dict.find(uid); - if (search != universe_dict.end()) { - universes[indx] = search->second; - } else { - std::stringstream err_msg; - err_msg << "Invalid universe number " << uid << " specified on " - "lattice " << id; - fatal_error(err_msg); - } - } - } + for (auto it = this->begin(); it != this->end(); ++it) { + int uid = *it; + auto search = universe_dict.find(uid); + if (search != universe_dict.end()) { + *it = search->second; + } else { + std::stringstream err_msg; + err_msg << "Invalid universe number " << uid << " specified on lattice " + << id; + fatal_error(err_msg); } } @@ -819,6 +815,44 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const write_int(lat_group, 3, dims, "universes", out, false); } +//============================================================================== +// HexLatticeIter implementation +//============================================================================== + +HexLatticeIter& +HexLatticeIter::operator++() +{ + while (iz < nz) { + if (iy >= ny) { + // This axial layer is finished. Move to the next one. + ++iz; + iy = 0; + ix = n_rings - 2; + } else { + ++ix; + if (ix + iy < n_rings - 1) { + // We're in the lower-left dead zone. Keep iterating ix. + } else if (ix >= nx) { + // End of this row. Move to the next. + ix = -1; + ++iy; + } else if (ix + iy > 3*n_rings - 3) { + // We're in the upper-right dead zone. Move to the next row. + ix = -1; + ++iy; + } else { + return *this; + } + } + } + + // We've passed the end of the lattice. Return an end iterator. + ix = n_rings; + iy = 2*n_rings-2; + iz = nz-1; + return *this; +} + //============================================================================== // Non-method functions //============================================================================== diff --git a/src/lattice.h b/src/lattice.h index 8bdf469c3..da07df511 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -104,6 +104,9 @@ public: int32_t& operator[](const int i_xyz[3]); + std::vector::iterator begin() {return universes.begin();} + std::vector::iterator end() {return universes.end();} + void adjust_indices(); bool are_valid_indices(const int i_xyz[3]) const; @@ -124,6 +127,11 @@ protected: std::array pitch; //! Lattice tile width along each axis }; +//============================================================================== +//============================================================================== + +class HexLatticeIter; + class HexLattice : public Lattice { public: @@ -133,6 +141,9 @@ public: int32_t& operator[](const int i_xyz[3]); + HexLatticeIter begin(); + HexLatticeIter end(); + void adjust_indices(); bool are_valid_indices(const int i_xyz[3]) const; @@ -152,6 +163,53 @@ protected: int n_axial; //! Number of axial tile positions std::array center; //! Global center of lattice std::array pitch; //! Lattice tile width and height + + friend class HexLatticeIter; +}; + +class HexLatticeIter +{ +public: + HexLatticeIter(HexLattice &lat_, int ix_, int iy_, int iz_) + : lat(lat_), + n_rings(lat_.n_rings), + nx(2*lat_.n_rings-1), + ny(2*lat_.n_rings-1), + nz(lat_.n_axial), + ix(ix_), + iy(iy_), + iz(iz_) + {} + + bool operator==(const HexLatticeIter &rhs) + { + return (&lat == &rhs.lat) && (ix == rhs.ix) && (iy == rhs.iy) + && (iz == rhs.iz); + }; + + bool operator!=(const HexLatticeIter &rhs) + { + return !(*this == rhs); + }; + + int32_t& operator*() + { + int indx = nx*ny*iz + nx*iy + ix; + return lat.universes[indx]; + } + + HexLatticeIter& operator++(); + + HexLatticeIter operator++(int) + { + HexLatticeIter clone(*this); + ++(*this); + return clone; + } + +private: + HexLattice ⪫ + int n_rings, nx, ny, nz, ix, iy, iz; }; } // namespace openmc From a0fd4e703f1d8b88fd88e4dd78166359cf8f4df5 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 11 May 2018 01:28:38 -0400 Subject: [PATCH 253/361] Make lattice iteration polymorphic --- src/lattice.cpp | 178 ++++++++++++++++++------------------------------ src/lattice.h | 115 +++++++++++++++---------------- 2 files changed, 122 insertions(+), 171 deletions(-) diff --git a/src/lattice.cpp b/src/lattice.cpp index 93c53d88a..024e4cb48 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -49,6 +49,55 @@ Lattice::Lattice(pugi::xml_node lat_node) } } +//============================================================================== + +LatticeIter +Lattice::begin() +{ + return LatticeIter(*this, 0); +} + +LatticeIter +Lattice::end() +{ + return LatticeIter(*this, universes.size()); +} + +//============================================================================== + +void +Lattice::adjust_indices() +{ + // Adjust the indices for the universes array. + for (auto it = begin(); it != end(); ++it) { + int uid = *it; + auto search = universe_dict.find(uid); + if (search != universe_dict.end()) { + *it = search->second; + } else { + std::stringstream err_msg; + err_msg << "Invalid universe number " << uid << " specified on " + "lattice " << id; + fatal_error(err_msg); + } + } + + // Adjust the index for the outer universe. + if (outer != NO_OUTER_UNIVERSE) { + auto search = universe_dict.find(outer); + if (search != universe_dict.end()) { + outer = search->second; + } else { + std::stringstream err_msg; + err_msg << "Invalid universe number " << outer << " specified on " + "lattice " << id; + fatal_error(err_msg); + } + } +} + +//============================================================================== + void Lattice::to_hdf5(hid_t lattices_group) const { @@ -163,39 +212,6 @@ RectLattice::operator[](const int i_xyz[3]) //============================================================================== -void -RectLattice::adjust_indices() -{ - // Adjust the indices for the universes array. - for (auto it = this->begin(); it != this->end(); ++it) { - int uid = *it; - auto search = universe_dict.find(uid); - if (search != universe_dict.end()) { - *it = search->second; - } else { - std::stringstream err_msg; - err_msg << "Invalid universe number " << uid << " specified on " - "lattice " << id; - fatal_error(err_msg); - } - } - - // Adjust the index for the outer universe. - if (outer != NO_OUTER_UNIVERSE) { - auto search = universe_dict.find(outer); - if (search != universe_dict.end()) { - outer = search->second; - } else { - std::stringstream err_msg; - err_msg << "Invalid universe number " << outer << " specified on " - "lattice " << id; - fatal_error(err_msg); - } - } -} - -//============================================================================== - bool RectLattice::are_valid_indices(const int i_xyz[3]) const { @@ -521,49 +537,10 @@ HexLattice::operator[](const int i_xyz[3]) //============================================================================== -HexLatticeIter +LatticeIter HexLattice::begin() { - return HexLatticeIter(*this, n_rings-1, 0, 0); -} - -HexLatticeIter -HexLattice::end() -{ - return HexLatticeIter(*this, n_rings, 2*n_rings-2, n_axial-1); -} - -//============================================================================== - -void -HexLattice::adjust_indices() -{ - // Adjust the indices for the universes array. - for (auto it = this->begin(); it != this->end(); ++it) { - int uid = *it; - auto search = universe_dict.find(uid); - if (search != universe_dict.end()) { - *it = search->second; - } else { - std::stringstream err_msg; - err_msg << "Invalid universe number " << uid << " specified on lattice " - << id; - fatal_error(err_msg); - } - } - - // Adjust the index for the outer universe. - if (outer != NO_OUTER_UNIVERSE) { - auto search = universe_dict.find(outer); - if (search != universe_dict.end()) { - outer = search->second; - } else { - std::stringstream err_msg; - err_msg << "Invalid universe number " << outer << " specified on " - "lattice " << id; - fatal_error(err_msg); - } - } + return LatticeIter(*this, n_rings-1); } //============================================================================== @@ -771,6 +748,21 @@ HexLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const //============================================================================== +bool +HexLattice::is_valid_index(int indx) const +{ + int nx {2*n_rings - 1}; + int ny {2*n_rings - 1}; + int nz {n_axial}; + int iz = indx / (nx * ny); + int iy = (indx - nx*ny*iz) / nx; + int ix = indx - nx*ny*iz - nx*iy; + int i_xyz[3] {ix+1, iy+1, iz+1}; // TODO: fix this off-by-one + return are_valid_indices(i_xyz); +} + +//============================================================================== + void HexLattice::to_hdf5_inner(hid_t lat_group) const { @@ -815,44 +807,6 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const write_int(lat_group, 3, dims, "universes", out, false); } -//============================================================================== -// HexLatticeIter implementation -//============================================================================== - -HexLatticeIter& -HexLatticeIter::operator++() -{ - while (iz < nz) { - if (iy >= ny) { - // This axial layer is finished. Move to the next one. - ++iz; - iy = 0; - ix = n_rings - 2; - } else { - ++ix; - if (ix + iy < n_rings - 1) { - // We're in the lower-left dead zone. Keep iterating ix. - } else if (ix >= nx) { - // End of this row. Move to the next. - ix = -1; - ++iy; - } else if (ix + iy > 3*n_rings - 3) { - // We're in the upper-right dead zone. Move to the next row. - ix = -1; - ++iy; - } else { - return *this; - } - } - } - - // We've passed the end of the lattice. Return an end iterator. - ix = n_rings; - iy = 2*n_rings-2; - iz = nz-1; - return *this; -} - //============================================================================== // Non-method functions //============================================================================== diff --git a/src/lattice.h b/src/lattice.h index da07df511..938294ae4 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -36,6 +36,8 @@ extern std::map lattice_dict; //! Abstract type for ordered array of universes //============================================================================== +class LatticeIter; + class Lattice { public: @@ -51,8 +53,11 @@ public: virtual int32_t& operator[](const int i_xyz[3]) = 0; + virtual LatticeIter begin(); + LatticeIter end(); + //! Convert internal universe values from IDs to indices using universe_dict. - virtual void adjust_indices() = 0; + void adjust_indices(); //! Check lattice indices. //! @param i_xyz[3] The indices for a lattice tile. @@ -82,6 +87,15 @@ public: virtual std::array get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const = 0; + //! Check flattened lattice index. + //! @param indx The index for a lattice tile. + //! @return true if the given index fit within the lattice bounds. False + //! otherwise. + virtual bool is_valid_index(int indx) const + { + return (indx > 0) && (indx < universes.size()); + } + //! Write all information needed to reconstruct the lattice to an HDF5 group. //! @param group_id An HDF5 group id. void to_hdf5(hid_t group_id) const; @@ -92,6 +106,44 @@ protected: virtual void to_hdf5_inner(hid_t group_id) const = 0; }; +class LatticeIter +{ +public: + LatticeIter(Lattice &lat_, int indx_) + : lat(lat_), + indx(indx_) + {} + + bool operator==(const LatticeIter &rhs) + { + return (&lat == &rhs.lat) && (indx == rhs.indx); + } + + bool operator!=(const LatticeIter &rhs) + { + return !(*this == rhs); + } + + int32_t& operator*() + { + return lat.universes[indx]; + } + + LatticeIter& operator++() + { + while (indx < lat.universes.size()) { + ++indx; + if (lat.is_valid_index(indx)) return *this; + } + indx = lat.universes.size(); + return *this; + } + + int indx; +private: + Lattice ⪫ +}; + //============================================================================== //============================================================================== @@ -104,11 +156,6 @@ public: int32_t& operator[](const int i_xyz[3]); - std::vector::iterator begin() {return universes.begin();} - std::vector::iterator end() {return universes.end();} - - void adjust_indices(); - bool are_valid_indices(const int i_xyz[3]) const; std::pair> @@ -130,8 +177,6 @@ protected: //============================================================================== //============================================================================== -class HexLatticeIter; - class HexLattice : public Lattice { public: @@ -141,10 +186,7 @@ public: int32_t& operator[](const int i_xyz[3]); - HexLatticeIter begin(); - HexLatticeIter end(); - - void adjust_indices(); + LatticeIter begin(); bool are_valid_indices(const int i_xyz[3]) const; @@ -156,6 +198,8 @@ public: std::array get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const; + bool is_valid_index(int indx) const; + void to_hdf5_inner(hid_t group_id) const; protected: @@ -163,53 +207,6 @@ protected: int n_axial; //! Number of axial tile positions std::array center; //! Global center of lattice std::array pitch; //! Lattice tile width and height - - friend class HexLatticeIter; -}; - -class HexLatticeIter -{ -public: - HexLatticeIter(HexLattice &lat_, int ix_, int iy_, int iz_) - : lat(lat_), - n_rings(lat_.n_rings), - nx(2*lat_.n_rings-1), - ny(2*lat_.n_rings-1), - nz(lat_.n_axial), - ix(ix_), - iy(iy_), - iz(iz_) - {} - - bool operator==(const HexLatticeIter &rhs) - { - return (&lat == &rhs.lat) && (ix == rhs.ix) && (iy == rhs.iy) - && (iz == rhs.iz); - }; - - bool operator!=(const HexLatticeIter &rhs) - { - return !(*this == rhs); - }; - - int32_t& operator*() - { - int indx = nx*ny*iz + nx*iy + ix; - return lat.universes[indx]; - } - - HexLatticeIter& operator++(); - - HexLatticeIter operator++(int) - { - HexLatticeIter clone(*this); - ++(*this); - return clone; - } - -private: - HexLattice ⪫ - int n_rings, nx, ny, nz, ix, iy, iz; }; } // namespace openmc From e19fc61f1df9efef4e728a96ad0569ff23b768e3 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 11 May 2018 02:00:13 -0400 Subject: [PATCH 254/361] Find the root universe from C++ --- src/cell.cpp | 46 ++++++++++++++++++++++ src/input_xml.F90 | 97 ++++++----------------------------------------- 2 files changed, 57 insertions(+), 86 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index 368d57b03..5bfd9104d 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "constants.h" #include "error.h" @@ -442,6 +443,51 @@ read_cells(pugi::xml_node *node) } } +//============================================================================== + +extern "C" int32_t +find_root_universe() +{ + // Find all the universes listed as a cell fill. + std::unordered_set fill_univ_ids; + for (Cell *c : cells_c) { + fill_univ_ids.insert(c->fill); + } + + // Find all the universes contained in a lattice. + for (Lattice *lat : lattices_c) { + for (auto it = lat->begin(); it != lat->end(); ++it) { + fill_univ_ids.insert(*it); + } + if (lat->outer != NO_OUTER_UNIVERSE) { + fill_univ_ids.insert(lat->outer); + } + } + + // Figure out which universe is not in the set. This is the root universe. + bool root_found {false}; + int32_t root_univ; + for (int32_t i = 0; i < universes_c.size(); i++) { + auto search = fill_univ_ids.find(universes_c[i]->id); + if (search == fill_univ_ids.end()) { + if (root_found) { + fatal_error("Two or more universes are not used as fill universes, so " + "it is not possible to distinguish which one is the root " + "universe."); + } else { + root_found = true; + root_univ = i; + } + } + } + if (!root_found) fatal_error("Could not find a root universe. Make sure " + "there are no circular dependencies in the geometry."); + + return root_univ; +} + +//============================================================================== + extern "C" void adjust_indices_c() { diff --git a/src/input_xml.F90 b/src/input_xml.F90 index c6aa8a142..09da2d1f4 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -49,27 +49,27 @@ module input_xml interface subroutine adjust_indices_c() bind(C) - use ISO_C_BINDING - implicit none end subroutine adjust_indices_c subroutine read_surfaces(node_ptr) bind(C) - use ISO_C_BINDING - implicit none + import C_PTR type(C_PTR) :: node_ptr end subroutine read_surfaces subroutine read_cells(node_ptr) bind(C) - use ISO_C_BINDING - implicit none + import C_PTR type(C_PTR) :: node_ptr end subroutine read_cells subroutine read_lattices(node_ptr) bind(C) - use ISO_C_BINDING - implicit none + import C_PTR type(C_PTR) :: node_ptr end subroutine read_lattices + + function find_root_universe() bind(C) result(root) + import C_INT32_T + integer(C_INT32_T) :: root + end function find_root_universe end interface contains @@ -930,8 +930,8 @@ contains subroutine read_geometry_xml() - integer :: i, j, k, m - integer :: n, n_mats, n_x, n_y, n_z, n_rings, n_rlats, n_hlats + integer :: i, j, k + integer :: n, n_mats, n_z, n_rings, n_rlats, n_hlats integer :: id integer :: univ_id integer :: n_cells_in_univ @@ -951,7 +951,6 @@ contains type(XMLNode), allocatable :: node_rlat_list(:) type(XMLNode), allocatable :: node_hlat_list(:) type(VectorInt) :: tokens - type(VectorInt) :: fill_univ_ids ! List of fill universe IDs type(VectorInt) :: univ_ids ! List of all universe IDs type(DictIntInt) :: cells_in_univ_dict ! Used to count how many cells each ! universe contains @@ -1039,8 +1038,6 @@ contains if (check_for_node(node_cell, "fill")) then call get_node_value(node_cell, "fill", c % fill) - if (find(fill_univ_ids, c % fill) == -1) & - call fill_univ_ids % push_back(c % fill) else c % fill = NONE end if @@ -1284,43 +1281,6 @@ contains call fatal_error("Rectangular lattice must be two or three dimensions.") end if - ! TODO: Remove deprecation warning in a future release. - if (check_for_node(node_lat, "type")) then - call warning("The use of 'type' is no longer needed. The utility & - &openmc/src/utils/update_inputs.py can be used to automatically & - &update geometry.xml files.") - end if - - ! Copy number of dimensions - n_x = lat % n_cells(1) - n_y = lat % n_cells(2) - n_z = lat % n_cells(3) - - ! Check that number of universes matches size - n = node_word_count(node_lat, "universes") - if (n /= n_x*n_y*n_z) then - call fatal_error("Number of universes on does not match & - &size of lattice " // trim(to_str(lat % id())) // ".") - end if - - ! Read universes - do m = 0, n_z-1 - do k = 0, n_y - 1 - do j = 0, n_x - 1 - univ_id = lat % get([j, k, m]) - if (find(fill_univ_ids, univ_id) == -1) & - call fill_univ_ids % push_back(univ_id) - end do - end do - end do - - ! Read outer universe for area outside lattice. - if (check_for_node(node_lat, "outer")) then - call get_node_value(node_lat, "outer", univ_id) - if (find(fill_univ_ids, univ_id) == -1) & - call fill_univ_ids % push_back(univ_id) - end if - ! Add lattice to dictionary call lattice_dict % set(lat % id(), i) @@ -1351,31 +1311,6 @@ contains n_rings = lat % n_rings n_z = lat % n_axial - ! Check that number of universes matches size - n = node_word_count(node_lat, "universes") - if (n /= (3*n_rings**2 - 3*n_rings + 1)*n_z) then - call fatal_error("Number of universes on does not match & - &size of lattice " // trim(to_str(lat % id())) // ".") - end if - - ! Read universes - do m = 0, n_z-1 - do k = 0, 2*n_rings-2 - do j = 0, 2*n_rings-2 - univ_id = lat % get([j, k, m]) - if (find(fill_univ_ids, univ_id) == -1) & - call fill_univ_ids % push_back(univ_id) - end do - end do - end do - - ! Read outer universe for area outside lattice. - if (check_for_node(node_lat, "outer")) then - call get_node_value(node_lat, "outer", univ_id) - if (find(fill_univ_ids, univ_id) == -1) & - call fill_univ_ids % push_back(univ_id) - end if - ! Add lattice to dictionary call lattice_dict % set(lat % id(), n_rlats + i) @@ -1395,19 +1330,9 @@ contains n_cells_in_univ = cells_in_univ_dict % get(u % id) allocate(u % cells(n_cells_in_univ)) u % cells(:) = 0 - - ! Check whether universe is a fill universe - if (find(fill_univ_ids, u % id) == -1) then - if (root_universe > 0) then - call fatal_error("Two or more universes are not used as fill & - &universes, so it is not possible to distinguish which one & - &is the root universe.") - else - root_universe = i - end if - end if end associate end do + root_universe = find_root_universe() + 1 do i = 1, n_cells ! Get index in universes array From 110ff69cac8d75181f0f2bef8a861f9f4f868c9f Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 11 May 2018 17:20:25 -0400 Subject: [PATCH 255/361] Move cell%type to C++ --- CMakeLists.txt | 1 + src/cell.cpp | 82 +++--------------- src/cell.h | 13 +++ src/constants.F90 | 3 + src/constants.h | 2 + src/geometry.F90 | 30 +++---- src/geometry_aux.cpp | 104 +++++++++++++++++++++++ src/geometry_header.F90 | 38 +++++++-- src/input_xml.F90 | 10 +-- src/plot.F90 | 2 +- src/summary.F90 | 2 +- src/tallies/tally_filter_distribcell.F90 | 16 ++-- 12 files changed, 194 insertions(+), 109 deletions(-) create mode 100644 src/geometry_aux.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d2a4348fd..dff755999 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,6 +435,7 @@ set(LIBOPENMC_CXX_SRC src/cell.cpp src/initialize.cpp src/finalize.cpp + src/geometry_aux.cpp src/hdf5_interface.cpp src/lattice.cpp src/message_passing.cpp diff --git a/src/cell.cpp b/src/cell.cpp index 5bfd9104d..a3f55d522 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include "constants.h" #include "error.h" @@ -226,6 +225,15 @@ Cell::Cell(pugi::xml_node cell_node) fill = C_NONE; } + if (check_for_node(cell_node, "material")) { + //TODO: read material ids. + material.push_back(C_NONE+1); + material.shrink_to_fit(); + } else { + material.push_back(C_NONE); + material.shrink_to_fit(); + } + std::string region_spec {""}; if (check_for_node(cell_node, "region")) { region_spec = get_node_value(cell_node, "region"); @@ -443,74 +451,6 @@ read_cells(pugi::xml_node *node) } } -//============================================================================== - -extern "C" int32_t -find_root_universe() -{ - // Find all the universes listed as a cell fill. - std::unordered_set fill_univ_ids; - for (Cell *c : cells_c) { - fill_univ_ids.insert(c->fill); - } - - // Find all the universes contained in a lattice. - for (Lattice *lat : lattices_c) { - for (auto it = lat->begin(); it != lat->end(); ++it) { - fill_univ_ids.insert(*it); - } - if (lat->outer != NO_OUTER_UNIVERSE) { - fill_univ_ids.insert(lat->outer); - } - } - - // Figure out which universe is not in the set. This is the root universe. - bool root_found {false}; - int32_t root_univ; - for (int32_t i = 0; i < universes_c.size(); i++) { - auto search = fill_univ_ids.find(universes_c[i]->id); - if (search == fill_univ_ids.end()) { - if (root_found) { - fatal_error("Two or more universes are not used as fill universes, so " - "it is not possible to distinguish which one is the root " - "universe."); - } else { - root_found = true; - root_univ = i; - } - } - } - if (!root_found) fatal_error("Could not find a root universe. Make sure " - "there are no circular dependencies in the geometry."); - - return root_univ; -} - -//============================================================================== - -extern "C" void -adjust_indices_c() -{ - // Change cell.universe values from IDs to indices. - for (Cell *c : cells_c) { - auto it = universe_dict.find(c->universe); - if (it != universe_dict.end()) { - //TODO: Remove this off-by-one indexing. - c->universe = it->second + 1; - } else { - std::stringstream err_msg; - err_msg << "Could not find universe " << c->universe - << " specified on cell " << c->id; - fatal_error(err_msg); - } - } - - // Change all lattice universe values from IDs to indices. - for (Lattice *l : lattices_c) { - l->adjust_indices(); - } -} - //============================================================================== // Fortran compatibility functions //============================================================================== @@ -522,6 +462,10 @@ extern "C" { void cell_set_id(Cell *c, int32_t id) {c->id = id;} + int cell_type(Cell *c) {return c->type;} + + void cell_set_type(Cell *c, int type) {c->type = type;} + int32_t cell_universe(Cell *c) {return c->universe;} void cell_set_universe(Cell *c, int32_t universe) {c->universe = universe;} diff --git a/src/cell.h b/src/cell.h index 5c06d8120..231bb8ea7 100644 --- a/src/cell.h +++ b/src/cell.h @@ -12,6 +12,14 @@ namespace openmc { +//============================================================================== +// Constants +//============================================================================== + +extern "C" int FILL_MATERIAL; +extern "C" int FILL_UNIVERSE; +extern "C" int FILL_LATTICE; + //============================================================================== // Global variables //============================================================================== @@ -48,9 +56,14 @@ class Cell public: int32_t id; //!< Unique ID std::string name{""}; //!< User-defined name + int type; //!< Material, universe, or lattice int32_t universe; //!< Universe # this cell is in int32_t fill; //!< Universe # filling this cell + //! Material within this cell. May be multiple materials for distribcell. + //! C_NONE signifies a universe. + std::vector material; + //! Definition of spatial region as Boolean expression of half-spaces std::vector region; //! Reverse Polish notation for region expression diff --git a/src/constants.F90 b/src/constants.F90 index d1893bf9e..dbd4181da 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -113,6 +113,9 @@ module constants FILL_MATERIAL = 1, & ! Cell with a specified material FILL_UNIVERSE = 2, & ! Cell filled by a separate universe FILL_LATTICE = 3 ! Cell filled with a lattice + integer(C_INT), bind(C, name='FILL_MATERIAL') :: FILL_MATERIAL_C = FILL_MATERIAL + integer(C_INT), bind(C, name='FILL_UNIVERSE') :: FILL_UNIVERSE_C = FILL_UNIVERSE + integer(C_INT), bind(C, name='FILL_LATTICE') :: FILL_LATTICE_C = FILL_LATTICE ! Void material integer, parameter :: MATERIAL_VOID = -1 diff --git a/src/constants.h b/src/constants.h index 8cc837a73..685fbcfd7 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,6 +1,8 @@ #ifndef CONSTANTS_H #define CONSTANTS_H +#include + namespace openmc{ diff --git a/src/geometry.F90 b/src/geometry.F90 index 35e4d3ca5..05ad98b86 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -150,7 +150,7 @@ contains if (found) then associate(c => cells(i_cell)) - CELL_TYPE: if (c % type == FILL_MATERIAL) then + CELL_TYPE: if (c % type() == FILL_MATERIAL) then ! ====================================================================== ! AT LOWEST UNIVERSE, TERMINATE SEARCH @@ -166,10 +166,10 @@ contains distribcell_index = c % distribcell_index offset = 0 do k = 1, p % n_coord - if (cells(p % coord(k) % cell) % type == FILL_UNIVERSE) then + if (cells(p % coord(k) % cell) % type() == FILL_UNIVERSE) then offset = offset + cells(p % coord(k) % cell) % & offset(distribcell_index) - elseif (cells(p % coord(k) % cell) % type == FILL_LATTICE) then + elseif (cells(p % coord(k) % cell) % type() == FILL_LATTICE) then if (lattices(p % coord(k + 1) % lattice) % obj & % are_valid_indices([& p % coord(k + 1) % lattice_x, & @@ -204,7 +204,7 @@ contains p % sqrtkT = c % sqrtkT(1) end if - elseif (c % type == FILL_UNIVERSE) then CELL_TYPE + elseif (c % type() == FILL_UNIVERSE) then CELL_TYPE ! ====================================================================== ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL @@ -232,7 +232,7 @@ contains call find_cell(p, found) j = p % n_coord - elseif (c % type == FILL_LATTICE) then CELL_TYPE + elseif (c % type() == FILL_LATTICE) then CELL_TYPE ! ====================================================================== ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL @@ -561,11 +561,11 @@ contains ! ==================================================================== ! AT LOWEST UNIVERSE, TERMINATE SEARCH - if (c % type == FILL_MATERIAL) then + if (c % type() == FILL_MATERIAL) then ! ==================================================================== ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL - elseif (c % type == FILL_UNIVERSE) then + elseif (c % type() == FILL_UNIVERSE) then ! Set offset for the cell on this level c % offset(map) = offset @@ -579,7 +579,7 @@ contains ! ==================================================================== ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type == FILL_LATTICE) then + elseif (c % type() == FILL_LATTICE) then ! Set current lattice lat => lattices(c % fill) % obj @@ -671,11 +671,11 @@ contains ! ==================================================================== ! AT LOWEST UNIVERSE, TERMINATE SEARCH - if (c % type == FILL_MATERIAL) then + if (c % type() == FILL_MATERIAL) then ! ==================================================================== ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL - elseif (c % type == FILL_UNIVERSE) then + elseif (c % type() == FILL_UNIVERSE) then next_univ => universes(c % fill) @@ -690,7 +690,7 @@ contains ! ==================================================================== ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type == FILL_LATTICE) then + elseif (c % type() == FILL_LATTICE) then ! Set current lattice lat => lattices(c % fill) % obj @@ -771,13 +771,13 @@ contains ! ==================================================================== ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL - if (c % type == FILL_UNIVERSE) then + if (c % type() == FILL_UNIVERSE) then call count_instance(universes(c % fill)) ! ==================================================================== ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type == FILL_LATTICE) then + elseif (c % type() == FILL_LATTICE) then ! Set current lattice associate (lat => lattices(c % fill) % obj) @@ -840,14 +840,14 @@ contains ! ==================================================================== ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL - if (c % type == FILL_UNIVERSE) then + if (c % type() == FILL_UNIVERSE) then next_univ => universes(c % fill) levels_below = max(levels_below, maximum_levels(next_univ)) ! ==================================================================== ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type == FILL_LATTICE) then + elseif (c % type() == FILL_LATTICE) then ! Set current lattice lat => lattices(c % fill) % obj diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp new file mode 100644 index 000000000..2064bda02 --- /dev/null +++ b/src/geometry_aux.cpp @@ -0,0 +1,104 @@ +#include +#include + +#include "cell.h" +#include "constants.h" +#include "error.h" +#include "lattice.h" + +#include //TODO: remove this + + +namespace openmc { + +extern "C" int32_t +find_root_universe() +{ + // Find all the universes listed as a cell fill. + std::unordered_set fill_univ_ids; + for (Cell *c : cells_c) { + fill_univ_ids.insert(c->fill); + } + + // Find all the universes contained in a lattice. + for (Lattice *lat : lattices_c) { + for (auto it = lat->begin(); it != lat->end(); ++it) { + fill_univ_ids.insert(*it); + } + if (lat->outer != NO_OUTER_UNIVERSE) { + fill_univ_ids.insert(lat->outer); + } + } + + // Figure out which universe is not in the set. This is the root universe. + bool root_found {false}; + int32_t root_univ; + for (int32_t i = 0; i < universes_c.size(); i++) { + auto search = fill_univ_ids.find(universes_c[i]->id); + if (search == fill_univ_ids.end()) { + if (root_found) { + fatal_error("Two or more universes are not used as fill universes, so " + "it is not possible to distinguish which one is the root " + "universe."); + } else { + root_found = true; + root_univ = i; + } + } + } + if (!root_found) fatal_error("Could not find a root universe. Make sure " + "there are no circular dependencies in the geometry."); + + return root_univ; +} + +//============================================================================== + +extern "C" void +adjust_indices_c() +{ + // Adjust material/fill idices. + for (Cell *c : cells_c) { + if (c->material[0] == C_NONE) { + int32_t id = c->fill; + auto search_univ = universe_dict.find(id); + auto search_lat = lattice_dict.find(id); + if (search_univ != universe_dict.end()) { + c->type = FILL_UNIVERSE; + c->fill = search_univ->second + 1; //TODO: off-by-one + } else if (search_lat != lattice_dict.end()) { + c->type = FILL_LATTICE; + c->fill = search_lat->second + 1; //TODO: off-by-one + } else { + std::stringstream err_msg; + err_msg << "Specified fill " << id << " on cell " << c->id + << " is neither a universe nor a lattice."; + fatal_error(err_msg); + } + } else { + //TODO: materials + c->type = FILL_MATERIAL; + } + } + + // Change cell.universe values from IDs to indices. + for (Cell *c : cells_c) { + auto search = universe_dict.find(c->universe); + if (search != universe_dict.end()) { + //TODO: Remove this off-by-one indexing. + c->universe = search->second + 1; + } else { + std::stringstream err_msg; + err_msg << "Could not find universe " << c->universe + << " specified on cell " << c->id; + fatal_error(err_msg); + } + } + + // Change all lattice universe values from IDs to indices. + for (Lattice *l : lattices_c) { + l->adjust_indices(); + } +} + +} // namespace openmc diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index ebce31c45..309076bbe 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -37,6 +37,20 @@ module geometry_header integer(C_INT32_T), intent(in), value :: id end subroutine cell_set_id_c + function cell_type_c(cell_ptr) bind(C, name='cell_type') result(type) + import C_PTR, C_INT + implicit none + type(C_PTR), intent(in), value :: cell_ptr + integer(C_INT) :: type + end function cell_type_c + + subroutine cell_set_type_c(cell_ptr, type) bind(C, name='cell_set_type') + import C_PTR, C_INT + implicit none + type(C_PTR), intent(in), value :: cell_ptr + integer(C_INT), intent(in), value :: type + end subroutine cell_set_type_c + function cell_universe_c(cell_ptr) bind(C, name='cell_universe') & result(universe) import C_PTR, C_INT32_T @@ -230,8 +244,6 @@ module geometry_header type Cell type(C_PTR) :: ptr - integer :: type ! Type of cell (normal, universe, - ! lattice) integer :: fill ! universe # filling this cell integer :: instances ! number of instances of this cell in ! the geom @@ -257,6 +269,8 @@ module geometry_header procedure :: id => cell_id procedure :: set_id => cell_set_id + procedure :: type => cell_type + procedure :: set_type => cell_set_type procedure :: universe => cell_universe procedure :: set_universe => cell_set_universe procedure :: simple => cell_simple @@ -355,6 +369,18 @@ contains call cell_set_id_c(this % ptr, id) end subroutine cell_set_id + function cell_type(this) result(type) + class(Cell), intent(in) :: this + integer(C_INT) :: type + type = cell_type_c(this % ptr) + end function cell_type + + subroutine cell_set_type(this, type) + class(Cell), intent(in) :: this + integer(C_INT), intent(in) :: type + call cell_set_type_c(this % ptr, type) + end subroutine cell_set_type + function cell_universe(this) result(universe) class(Cell), intent(in) :: this integer(C_INT32_T) :: universe @@ -544,7 +570,7 @@ contains err = 0 if (index >= 1 .and. index <= size(cells)) then associate (c => cells(index)) - type = c % type + type = c % type() select case (type) case (FILL_MATERIAL) n = size(c % material) @@ -595,7 +621,7 @@ contains if (allocated(c % material)) deallocate(c % material) allocate(c % material(n)) - c % type = FILL_MATERIAL + call c % set_type(FILL_MATERIAL) do i = 1, n j = indices(i) if ((j >= 1 .and. j <= n_materials) .or. j == MATERIAL_VOID) then @@ -607,9 +633,9 @@ contains end if end do case (FILL_UNIVERSE) - c % type = FILL_UNIVERSE + call c % set_type(FILL_UNIVERSE) case (FILL_LATTICE) - c % type = FILL_LATTICE + call c % set_type(FILL_LATTICE) end select end associate else diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 09da2d1f4..71408defb 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -3794,24 +3794,16 @@ contains if (c % material(1) == NONE) then id = c % fill if (universe_dict % has(id)) then - c % type = FILL_UNIVERSE c % fill = universe_dict % get(id) elseif (lattice_dict % has(id)) then lid = lattice_dict % get(id) - c % type = FILL_LATTICE c % fill = lid - else - call fatal_error("Specified fill " // trim(to_str(id)) // " on cell "& - // trim(to_str(c % id())) // " is neither a universe nor a & - &lattice.") end if else do j = 1, size(c % material) id = c % material(j) if (id == MATERIAL_VOID) then - c % type = FILL_MATERIAL else if (material_dict % has(id)) then - c % type = FILL_MATERIAL c % material(j) = material_dict % get(id) else call fatal_error("Could not find material " // trim(to_str(id)) & @@ -3989,7 +3981,7 @@ contains ! Allocate offset table for fill cells do i = 1, n_cells - if (cells(i) % type /= FILL_MATERIAL) then + if (cells(i) % type() /= FILL_MATERIAL) then allocate(cells(i) % offset(n_maps)) end if end do diff --git a/src/plot.F90 b/src/plot.F90 index 2613acd9d..789ad0ee9 100644 --- a/src/plot.F90 +++ b/src/plot.F90 @@ -85,7 +85,7 @@ contains if (pl % color_by == PLOT_COLOR_MATS) then ! Assign color based on material associate (c => cells(p % coord(j) % cell)) - if (c % type == FILL_UNIVERSE) then + if (c % type() == FILL_UNIVERSE) then ! If we stopped on a middle universe level, treat as if not found rgb = pl % not_found % rgb id = -1 diff --git a/src/summary.F90 b/src/summary.F90 index fb934726e..964ec34a3 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -193,7 +193,7 @@ contains call c % to_hdf5(cell_group) ! Write information on what fills this cell - select case (c%type) + select case (c%type()) case (FILL_MATERIAL) call write_dataset(cell_group, "fill_type", "material") diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index dd12e808c..0c369e154 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -58,10 +58,10 @@ contains distribcell_index = cells(this % cell) % distribcell_index offset = 0 do i = 1, p % n_coord - if (cells(p % coord(i) % cell) % type == FILL_UNIVERSE) then + if (cells(p % coord(i) % cell) % type() == FILL_UNIVERSE) then offset = offset + cells(p % coord(i) % cell) % & offset(distribcell_index) - elseif (cells(p % coord(i) % cell) % type == FILL_LATTICE) then + elseif (cells(p % coord(i) % cell) % type() == FILL_LATTICE) then if (lattices(p % coord(i + 1) % lattice) % obj & % are_valid_indices([& p % coord(i + 1) % lattice_x, & @@ -197,20 +197,20 @@ contains c => cells(univ % cells(j)) ! Skip normal cells which do not have offsets - if (c % type == FILL_MATERIAL) cycle + if (c % type() == FILL_MATERIAL) cycle ! Break loop once we've found the next cell with an offset exit end do ! Ensure we didn't just end the loop by iteration - if (c % type /= FILL_MATERIAL) then + if (c % type() /= FILL_MATERIAL) then ! There are more cells in this universe that it could be in later_cell = .true. ! Two cases, lattice or fill cell - if (c % type == FILL_UNIVERSE) then + if (c % type() == FILL_UNIVERSE) then temp_offset = c % offset(map) ! Get the offset of the first lattice location @@ -227,7 +227,7 @@ contains end if end if - if (n == 1 .and. c % type /= FILL_MATERIAL) then + if (n == 1 .and. c % type() /= FILL_MATERIAL) then this_cell = .true. end if @@ -245,7 +245,7 @@ contains ! ==================================================================== ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL - if (c % type == FILL_UNIVERSE) then + if (c % type() == FILL_UNIVERSE) then ! Enter this cell to update the current offset offset = c % offset(map) + offset @@ -256,7 +256,7 @@ contains ! ==================================================================== ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type == FILL_LATTICE) then + elseif (c % type() == FILL_LATTICE) then ! Set current lattice lat => lattices(c % fill) % obj From 5419403e10c6490bd7762ea39e5c4cce5acfa934 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 11 May 2018 18:11:08 -0400 Subject: [PATCH 256/361] Move cell instance counting to C++ --- src/cell.cpp | 10 ++- src/cell.h | 3 +- src/geometry.F90 | 66 -------------- src/geometry_aux.cpp | 109 ++++++++++++++--------- src/geometry_header.F90 | 17 +++- src/input_xml.F90 | 19 ++-- src/tallies/tally_filter_distribcell.F90 | 2 +- 7 files changed, 101 insertions(+), 125 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index a3f55d522..6d7e598eb 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -437,16 +437,16 @@ read_cells(pugi::xml_node *node) } // Populate the Universe vector and dictionary. - for (Cell *c : cells_c) { - int32_t uid = c->universe; + for (int i = 0; i < cells_c.size(); i++) { + int32_t uid = cells_c[i]->universe; auto it = universe_dict.find(uid); if (it == universe_dict.end()) { universes_c.push_back(new Universe()); universes_c.back()->id = uid; - universes_c.back()->cells.push_back(c); + universes_c.back()->cells.push_back(i); universe_dict[uid] = universes_c.size() - 1; } else { - universes_c[it->second]->cells.push_back(c); + universes_c[it->second]->cells.push_back(i); } } } @@ -470,6 +470,8 @@ extern "C" { void cell_set_universe(Cell *c, int32_t universe) {c->universe = universe;} + int32_t cell_n_instances(Cell *c) {return c->n_instances;} + bool cell_simple(Cell *c) {return c->simple;} bool cell_contains(Cell *c, double xyz[3], double uvw[3], int32_t on_surface) diff --git a/src/cell.h b/src/cell.h index 231bb8ea7..fff8b749c 100644 --- a/src/cell.h +++ b/src/cell.h @@ -43,7 +43,7 @@ class Universe public: int32_t id; //! Unique ID int32_t type; - std::vector cells; //! Cells within this universe + std::vector cells; //! Cells within this universe double x0, y0, z0; //! Translation coordinates. }; @@ -59,6 +59,7 @@ public: int type; //!< Material, universe, or lattice int32_t universe; //!< Universe # this cell is in int32_t fill; //!< Universe # filling this cell + int32_t n_instances{0}; //!< Number of instances of this cell //! Material within this cell. May be multiple materials for distribcell. //! C_NONE signifies a universe. diff --git a/src/geometry.F90 b/src/geometry.F90 index 05ad98b86..be2e2be84 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -750,72 +750,6 @@ contains end function count_target -!=============================================================================== -! COUNT_INSTANCE recursively totals the number of occurrences of all cells -! beginning with the universe given. -!=============================================================================== - - recursive subroutine count_instance(univ) - - type(Universe), intent(in) :: univ ! universe to search through - - integer :: i ! index over cells - integer :: j, k, m ! indices in lattice - integer :: n ! number of cells to search - - n = size(univ % cells) - - do i = 1, n - associate (c => cells(univ % cells(i))) - c % instances = c % instances + 1 - - ! ==================================================================== - ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL - if (c % type() == FILL_UNIVERSE) then - - call count_instance(universes(c % fill)) - - ! ==================================================================== - ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type() == FILL_LATTICE) then - - ! Set current lattice - associate (lat => lattices(c % fill) % obj) - - select type (lat) - type is (RectLattice) - - ! Loop over lattice coordinates - do j = 1, lat % n_cells(1) - do k = 1, lat % n_cells(2) - do m = 1, lat % n_cells(3) - call count_instance(universes(lat % get([j-1, k-1, m-1])+1)) - end do - end do - end do - - type is (HexLattice) - - ! Loop over lattice coordinates - do m = 1, lat % n_axial - do k = 1, 2*lat % n_rings - 1 - do j = 1, 2*lat % n_rings - 1 - if (lat % are_valid_indices([j, k, m])) then - call count_instance(universes(lat % get([j-1, k-1, m-1]) & - + 1)) - end if - end do - end do - end do - - end select - end associate - end if - end associate - end do - - end subroutine count_instance - !=============================================================================== ! MAXIMUM_LEVELS determines the maximum number of nested coordinate levels in ! the geometry diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 2064bda02..ec4da6f12 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -11,49 +11,6 @@ namespace openmc { -extern "C" int32_t -find_root_universe() -{ - // Find all the universes listed as a cell fill. - std::unordered_set fill_univ_ids; - for (Cell *c : cells_c) { - fill_univ_ids.insert(c->fill); - } - - // Find all the universes contained in a lattice. - for (Lattice *lat : lattices_c) { - for (auto it = lat->begin(); it != lat->end(); ++it) { - fill_univ_ids.insert(*it); - } - if (lat->outer != NO_OUTER_UNIVERSE) { - fill_univ_ids.insert(lat->outer); - } - } - - // Figure out which universe is not in the set. This is the root universe. - bool root_found {false}; - int32_t root_univ; - for (int32_t i = 0; i < universes_c.size(); i++) { - auto search = fill_univ_ids.find(universes_c[i]->id); - if (search == fill_univ_ids.end()) { - if (root_found) { - fatal_error("Two or more universes are not used as fill universes, so " - "it is not possible to distinguish which one is the root " - "universe."); - } else { - root_found = true; - root_univ = i; - } - } - } - if (!root_found) fatal_error("Could not find a root universe. Make sure " - "there are no circular dependencies in the geometry."); - - return root_univ; -} - -//============================================================================== - extern "C" void adjust_indices_c() { @@ -101,4 +58,70 @@ adjust_indices_c() } } +//============================================================================== + +extern "C" int32_t +find_root_universe() +{ + // Find all the universes listed as a cell fill. + std::unordered_set fill_univ_ids; + for (Cell *c : cells_c) { + fill_univ_ids.insert(c->fill); + } + + // Find all the universes contained in a lattice. + for (Lattice *lat : lattices_c) { + for (auto it = lat->begin(); it != lat->end(); ++it) { + fill_univ_ids.insert(*it); + } + if (lat->outer != NO_OUTER_UNIVERSE) { + fill_univ_ids.insert(lat->outer); + } + } + + // Figure out which universe is not in the set. This is the root universe. + bool root_found {false}; + int32_t root_univ; + for (int32_t i = 0; i < universes_c.size(); i++) { + auto search = fill_univ_ids.find(universes_c[i]->id); + if (search == fill_univ_ids.end()) { + if (root_found) { + fatal_error("Two or more universes are not used as fill universes, so " + "it is not possible to distinguish which one is the root " + "universe."); + } else { + root_found = true; + root_univ = i; + } + } + } + if (!root_found) fatal_error("Could not find a root universe. Make sure " + "there are no circular dependencies in the geometry."); + + return root_univ; +} + +//============================================================================== + +extern "C" void +count_instances(int32_t univ_indx) +{ + for (int32_t cell_indx : universes_c[univ_indx]->cells) { + Cell &c {*cells_c[cell_indx]}; + ++c.n_instances; + + if (c.type == FILL_UNIVERSE) { + // This cell contains another universe. Recurse into that universe. + count_instances(c.fill-1); // TODO: off-by-one + + } else if (c.type == FILL_LATTICE) { + // This cell contains a lattice. Recurse into the lattice universes. + Lattice &lat {*lattices_c[c.fill-1]}; // TODO: off-by-one + for (auto it = lat.begin(); it != lat.end(); ++it) { + count_instances(*it); + } + } + } +} + } // namespace openmc diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 309076bbe..012e976af 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -67,6 +67,14 @@ module geometry_header integer(C_INT32_T), intent(in), value :: universe end subroutine cell_set_universe_c + function cell_n_instances_c(cell_ptr) bind(C, name='cell_n_instances') & + result(n_instances) + import C_PTR, C_INT32_T + implicit none + type(C_PTR), intent(in), value :: cell_ptr + integer(C_INT32_T) :: n_instances + end function cell_n_instances_c + function cell_simple_c(cell_ptr) bind(C, name='cell_simple') result(simple) import C_PTR, C_BOOL implicit none @@ -245,8 +253,6 @@ module geometry_header type(C_PTR) :: ptr integer :: fill ! universe # filling this cell - integer :: instances ! number of instances of this cell in - ! the geom integer, allocatable :: material(:) ! Material within cell. Multiple ! materials for distribcell ! instances. 0 signifies a universe @@ -273,6 +279,7 @@ module geometry_header procedure :: set_type => cell_set_type procedure :: universe => cell_universe procedure :: set_universe => cell_set_universe + procedure :: n_instances => cell_n_instances procedure :: simple => cell_simple procedure :: distance => cell_distance procedure :: to_hdf5 => cell_to_hdf5 @@ -393,6 +400,12 @@ contains call cell_set_universe_c(this % ptr, universe) end subroutine cell_set_universe + function cell_n_instances(this) result(n_instances) + class(Cell), intent(in) :: this + integer(C_INT32_T) :: n_instances + n_instances = cell_n_instances_c(this % ptr) + end function cell_n_instances + function cell_simple(this) result(simple) class(Cell), intent(in) :: this logical(C_BOOL) :: simple diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 71408defb..e43906c9a 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -11,8 +11,7 @@ module input_xml use distribution_univariate use endf, only: reaction_name use error, only: fatal_error, warning, write_message, openmc_err_msg - use geometry, only: calc_offsets, maximum_levels, count_instance, & - neighbor_lists + use geometry, only: calc_offsets, maximum_levels, neighbor_lists use geometry_header use hdf5_interface use list_header, only: ListChar, ListInt, ListReal @@ -51,6 +50,11 @@ module input_xml subroutine adjust_indices_c() bind(C) end subroutine adjust_indices_c + subroutine count_instances_c(univ_indx) bind(C, name='count_instances') + import C_INT32_T + integer(C_INT32_T), intent(in), value :: univ_indx + end subroutine + subroutine read_surfaces(node_ptr) bind(C) import C_PTR type(C_PTR) :: node_ptr @@ -139,7 +143,7 @@ contains ! Perform some final operations to set up the geometry call adjust_indices() - call count_instance(universes(root_universe)) + call count_instances_c(root_universe-1) ! After reading input and basic geometry setup is complete, build lists of ! neighboring cells for efficient tracking @@ -1030,7 +1034,6 @@ contains c % ptr = cell_pointer_c(i - 1) ! Initialize distribcell instances and distribcell index - c % instances = 0 c % distribcell_index = NONE ! Get pointer to i-th cell node @@ -3861,19 +3864,19 @@ contains do i = 1, n_cells associate (c => cells(i)) if (size(c % material) > 1) then - if (size(c % material) /= c % instances) then + if (size(c % material) /= c % n_instances()) then call fatal_error("Cell " // trim(to_str(c % id())) // " was & &specified with " // trim(to_str(size(c % material))) & - // " materials but has " // trim(to_str(c % instances)) & + // " materials but has " // trim(to_str(c % n_instances())) & // " distributed instances. The number of materials must & &equal one or the number of instances.") end if end if if (size(c % sqrtkT) > 1) then - if (size(c % sqrtkT) /= c % instances) then + if (size(c % sqrtkT) /= c % n_instances()) then call fatal_error("Cell " // trim(to_str(c % id())) // " was & &specified with " // trim(to_str(size(c % sqrtkT))) & - // " temperatures but has " // trim(to_str(c % instances)) & + // " temperatures but has " // trim(to_str(c % n_instances())) & // " distributed instances. The number of temperatures must & &equal one or the number of instances.") end if diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index 0c369e154..2b44f3503 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -102,7 +102,7 @@ contains val = cell_dict % get(id) if (val /= EMPTY) then this % cell = val - this % n_bins = cells(this % cell) % instances + this % n_bins = cells(this % cell) % n_instances() else call fatal_error("Could not find cell " // trim(to_str(id)) & &// " specified on tally filter.") From df6e2a2cef68fdd221dd338690532fb49cbb392b Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 11 May 2018 22:43:27 -0400 Subject: [PATCH 257/361] Move count_target (distribcell) to C++ --- src/geometry.F90 | 172 ++++--------------------------------------- src/geometry_aux.cpp | 61 ++++++++++++++- src/input_xml.F90 | 23 ++---- 3 files changed, 79 insertions(+), 177 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index be2e2be84..ed5906032 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -17,14 +17,21 @@ module geometry interface function cell_contains_c(cell_ptr, xyz, uvw, on_surface) & bind(C, name="cell_contains") result(in_cell) - use ISO_C_BINDING - implicit none + import C_PTR, C_DOUBLE, C_INT32_T, C_BOOL type(C_PTR), intent(in), value :: cell_ptr real(C_DOUBLE), intent(in) :: xyz(3) real(C_DOUBLE), intent(in) :: uvw(3) integer(C_INT32_T), intent(in), value :: on_surface logical(C_BOOL) :: in_cell end function cell_contains_c + + function count_universe_instances(search_univ, target_univ_id) bind(C) & + result(count) + import C_INT32_T, C_INT + integer(C_INT32_T), intent(in), value :: search_univ + integer(C_INT32_T), intent(in), value :: target_univ_id + integer(C_INT) :: count + end function end interface contains @@ -532,27 +539,22 @@ contains ! routine is called once upon initialization. !=============================================================================== - subroutine calc_offsets(univ_id, map, univ, counts, found) + subroutine calc_offsets(univ_id, map, univ) integer, intent(in) :: univ_id ! target universe ID integer, intent(in) :: map ! map index in vector of maps type(Universe), intent(in) :: univ ! universe searching in - integer, intent(inout) :: counts(:,:) ! target count - logical, intent(inout) :: found(:,:) ! target found integer :: i ! index over cells integer :: j, k, m ! indices in lattice - integer :: n ! number of cells to search integer :: offset ! total offset for a given cell integer :: cell_index ! index in cells array type(Cell), pointer :: c ! pointer to current cell - type(Universe), pointer :: next_univ ! next universe to cycle through class(Lattice), pointer :: lat ! pointer to current lattice - n = size(univ % cells) offset = 0 - do i = 1, n + do i = 1, size(univ % cells) cell_index = univ % cells(i) @@ -566,16 +568,8 @@ contains ! ==================================================================== ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL elseif (c % type() == FILL_UNIVERSE) then - ! Set offset for the cell on this level c % offset(map) = offset - - ! Count contents of this cell - next_univ => universes(c % fill) - offset = offset + count_target(next_univ, counts, found, univ_id, map) - - ! Move into the next universe - next_univ => universes(c % fill) - c => cells(cell_index) + offset = offset + count_universe_instances(c % fill - 1, univ_id) ! ==================================================================== ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL @@ -587,30 +581,24 @@ contains select type (lat) type is (RectLattice) - - ! Loop over lattice coordinates do j = 1, lat % n_cells(1) do k = 1, lat % n_cells(2) do m = 1, lat % n_cells(3) lat % offset(map, j, k, m) = offset - next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) - offset = offset + & - count_target(next_univ, counts, found, univ_id, map) + offset = offset + count_universe_instances(& + lat % get([j-1, k-1, m-1]), univ_id) end do end do end do type is (HexLattice) - - ! Loop over lattice coordinates do m = 1, lat % n_axial do k = 1, 2*lat % n_rings - 1 do j = 1, 2*lat % n_rings - 1 if (lat % are_valid_indices([j, k, m])) then lat % offset(map, j, k, m) = offset - next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) - offset = offset + & - count_target(next_univ, counts, found, univ_id, map) + offset = offset + count_universe_instances(& + lat % get([j-1, k-1, m-1]), univ_id) end if end do end do @@ -622,134 +610,6 @@ contains end subroutine calc_offsets -!=============================================================================== -! COUNT_TARGET recursively totals the numbers of occurances of a given -! universe ID beginning with the universe given. -!=============================================================================== - - recursive function count_target(univ, counts, found, univ_id, map) result(count) - - type(Universe), intent(in) :: univ ! universe to search through - integer, intent(inout) :: counts(:,:) ! target count - logical, intent(inout) :: found(:,:) ! target found - integer, intent(in) :: univ_id ! target universe ID - integer, intent(in) :: map ! current map - - integer :: i ! index over cells - integer :: j, k, m ! indices in lattice - integer :: n ! number of cells to search - integer :: cell_index ! index in cells array - integer :: count ! number of times target located - type(Cell), pointer :: c ! pointer to current cell - type(Universe), pointer :: next_univ ! next univ to loop through - class(Lattice), pointer :: lat ! pointer to current lattice - - ! Don't research places already checked - if (found(universe_dict % get(univ % id), map)) then - count = counts(universe_dict % get(univ % id), map) - return - end if - - ! If this is the target, it can't contain itself. - ! Count = 1, then quit - if (univ % id == univ_id) then - count = 1 - counts(universe_dict % get(univ % id), map) = 1 - found(universe_dict % get(univ % id), map) = .true. - return - end if - - count = 0 - n = size(univ % cells) - - do i = 1, n - - cell_index = univ % cells(i) - - ! get pointer to cell - c => cells(cell_index) - - ! ==================================================================== - ! AT LOWEST UNIVERSE, TERMINATE SEARCH - if (c % type() == FILL_MATERIAL) then - - ! ==================================================================== - ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL - elseif (c % type() == FILL_UNIVERSE) then - - next_univ => universes(c % fill) - - ! Found target - stop since target cannot contain itself - if (next_univ % id == univ_id) then - count = count + 1 - return - end if - - count = count + count_target(next_univ, counts, found, univ_id, map) - c => cells(cell_index) - - ! ==================================================================== - ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type() == FILL_LATTICE) then - - ! Set current lattice - lat => lattices(c % fill) % obj - - select type (lat) - - type is (RectLattice) - - ! Loop over lattice coordinates - do j = 1, lat % n_cells(1) - do k = 1, lat % n_cells(2) - do m = 1, lat % n_cells(3) - next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) - - ! Found target - stop since target cannot contain itself - if (next_univ % id == univ_id) then - count = count + 1 - cycle - end if - - count = count + & - count_target(next_univ, counts, found, univ_id, map) - - end do - end do - end do - - type is (HexLattice) - - ! Loop over lattice coordinates - do m = 1, lat % n_axial - do k = 1, 2*lat % n_rings - 1 - do j = 1, 2*lat % n_rings - 1 - if (lat % are_valid_indices([j, k, m])) then - next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) - - ! Found target - stop since target cannot contain itself - if (next_univ % id == univ_id) then - count = count + 1 - cycle - end if - - count = count + & - count_target(next_univ, counts, found, univ_id, map) - end if - end do - end do - end do - - end select - - end if - end do - - counts(universe_dict % get(univ % id), map) = count - found(universe_dict % get(univ % id), map) = .true. - - end function count_target - !=============================================================================== ! MAXIMUM_LEVELS determines the maximum number of nested coordinate levels in ! the geometry diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index ec4da6f12..906845cb9 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -1,3 +1,6 @@ +//! \file geometry_aux.cpp +//! Auxilary functions for geometry initialization and general data handling. + #include #include @@ -11,6 +14,10 @@ namespace openmc { +//============================================================================== +//! Replace Universe, Lattice, and Material IDs with indices. +//============================================================================== + extern "C" void adjust_indices_c() { @@ -58,6 +65,12 @@ adjust_indices_c() } } +//============================================================================== +//! Figure out which Universe is the root universe. +//! +//! This function looks for a universe that is not listed in a Cell::fill or in +//! a Lattice. +//! @return The index of the root universe. //============================================================================== extern "C" int32_t @@ -101,10 +114,17 @@ find_root_universe() return root_univ; } +//============================================================================== +//! Recursively search through the geometry and count cell instances. +//! +//! This function will update the Cell::n_instances value for each cell in the +//! geometry. +//! @param univ_indx The index of the universe to begin searching from (probably +//! the root universe). //============================================================================== extern "C" void -count_instances(int32_t univ_indx) +count_cell_instances(int32_t univ_indx) { for (int32_t cell_indx : universes_c[univ_indx]->cells) { Cell &c {*cells_c[cell_indx]}; @@ -112,16 +132,51 @@ count_instances(int32_t univ_indx) if (c.type == FILL_UNIVERSE) { // This cell contains another universe. Recurse into that universe. - count_instances(c.fill-1); // TODO: off-by-one + count_cell_instances(c.fill-1); // TODO: off-by-one } else if (c.type == FILL_LATTICE) { // This cell contains a lattice. Recurse into the lattice universes. Lattice &lat {*lattices_c[c.fill-1]}; // TODO: off-by-one for (auto it = lat.begin(); it != lat.end(); ++it) { - count_instances(*it); + count_cell_instances(*it); } } } } +//============================================================================== +//! Recursively search through universes and count the number of instances of +//! the target universe in the geometry tree. +//! @param search_univ The index of the universe to begin searching from. +//! @param target_univ_id The ID of the universe to be counted. +//============================================================================== + +extern "C" int +count_universe_instances(int32_t search_univ, int32_t target_univ_id) +{ + // If this is the target, it can't contain itself. + if (universes_c[search_univ]->id == target_univ_id) { + return 1; + } + + int count {0}; + for (int32_t cell_indx : universes_c[search_univ]->cells) { + Cell &c {*cells_c[cell_indx]}; + + if (c.type == FILL_UNIVERSE) { + int32_t next_univ = c.fill - 1; // TODO: off-by-one + count += count_universe_instances(next_univ, target_univ_id); + + } else if (c.type == FILL_LATTICE) { + Lattice &lat {*lattices_c[c.fill - 1]}; //TODO: off-by-one + for (auto it = lat.begin(); it != lat.end(); ++it) { + int32_t next_univ = *it; + count += count_universe_instances(next_univ, target_univ_id); + } + } + } + + return count; +} + } // namespace openmc diff --git a/src/input_xml.F90 b/src/input_xml.F90 index e43906c9a..44a161f47 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -50,7 +50,7 @@ module input_xml subroutine adjust_indices_c() bind(C) end subroutine adjust_indices_c - subroutine count_instances_c(univ_indx) bind(C, name='count_instances') + subroutine count_cell_instances(univ_indx) bind(C) import C_INT32_T integer(C_INT32_T), intent(in), value :: univ_indx end subroutine @@ -143,7 +143,7 @@ contains ! Perform some final operations to set up the geometry call adjust_indices() - call count_instances_c(root_universe-1) + call count_cell_instances(root_universe-1) ! After reading input and basic geometry setup is complete, build lists of ! neighboring cells for efficient tracking @@ -3829,8 +3829,6 @@ contains integer :: i, j ! Tally, filter loop counters logical :: distribcell_active ! Does simulation use distribcell? integer, allocatable :: univ_list(:) ! Target offsets - integer, allocatable :: counts(:,:) ! Target count - logical, allocatable :: found(:,:) ! Target found ! Assume distribcell is not needed until proven otherwise. distribcell_active = .false. @@ -3885,12 +3883,12 @@ contains end do ! Allocate offset maps at each level in the geometry - call allocate_offsets(univ_list, counts, found) + call allocate_offsets(univ_list) ! Calculate offsets for each target distribcell do i = 1, n_maps do j = 1, n_universes - call calc_offsets(univ_list(i), i, universes(j), counts, found) + call calc_offsets(univ_list(i), i, universes(j)) end do end do @@ -3901,11 +3899,9 @@ contains ! memory for distribcell offset tables !=============================================================================== - recursive subroutine allocate_offsets(univ_list, counts, found) + recursive subroutine allocate_offsets(univ_list) integer, intent(out), allocatable :: univ_list(:) ! Target offsets - integer, intent(out), allocatable :: counts(:,:) ! Target count - logical, intent(out), allocatable :: found(:,:) ! Target found integer :: i, j, k ! Loop counters type(SetInt) :: cell_list ! distribells to track @@ -3943,15 +3939,6 @@ contains ! Allocate the list of offset tables for each unique universe allocate(univ_list(n_maps)) - ! Allocate list to accumulate target distribcell counts in each universe - allocate(counts(n_universes, n_maps)) - counts(:,:) = 0 - - ! Allocate list to track if target distribcells are found in each universe - allocate(found(n_universes, n_maps)) - found(:,:) = .false. - - ! Search through universes for distributed cells and assign each one a ! unique distribcell array index. k = 1 From a73f633d2480d8c64677610f7c4479a3c96d921b Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sat, 12 May 2018 15:43:05 -0400 Subject: [PATCH 258/361] Converted calc_rn and calc_pn to provide all values at once instead of only for the requested value of n. Also converted calc_pn to use the recursive definition of the legendre polys --- openmc/capi/math.py | 13 +- src/math.F90 | 80 +- src/math_functions.cpp | 851 ++++++++++----------- src/math_functions.h | 32 +- src/tallies/tally.F90 | 2 +- src/tallies/tally_filter_legendre.F90 | 11 +- src/tallies/tally_filter_sph_harm.F90 | 26 +- src/tallies/tally_filter_sptl_legendre.F90 | 15 +- src/tallies/tally_filter_zernike.F90 | 2 +- tests/unit_tests/test_math.py | 17 +- 10 files changed, 514 insertions(+), 535 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 4de46691a..c46390bce 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -9,8 +9,9 @@ from . import _dll # _dll.normal_percentile.argtypes = [POINTER(c_double)] _dll.t_percentile.restype = c_double _dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] -_dll.calc_pn.restype = c_double -_dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double)] +_dll.calc_pn.restype = None +_dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double), + ndpointer(c_double)] _dll.evaluate_legendre.restype = c_double _dll.evaluate_legendre.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double)] @@ -95,7 +96,9 @@ def calc_pn(n, x): """ - return _dll.calc_pn(c_int(n), c_double(x)) + pnx = np.empty(n + 1, dtype=np.float64) + _dll.calc_pn(c_int(n), c_double(x), pnx) + return pnx def evaluate_legendre(data, x): @@ -124,7 +127,7 @@ def evaluate_legendre(data, x): def calc_rn(n, uvw): """ Calculate the n-th order real Spherical Harmonics for a given angle; - all Rn,m values are provided (where -n <= m <= n). + all Rn,m values are provided for all n (where -n <= m <= n). Parameters ---------- @@ -140,7 +143,7 @@ def calc_rn(n, uvw): """ - num_nm = 2 * n + 1 + num_nm = (n + 1) * (n + 1) rn = np.empty(num_nm, dtype=np.float64) uvw_arr = np.array(uvw, dtype=np.float64) _dll.calc_rn(c_int(n), uvw_arr, rn) diff --git a/src/math.F90 b/src/math.F90 index 6edf2f1c3..7be3e5a6d 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -26,24 +26,24 @@ module math interface - pure function t_percentile_cc(p, df) bind(C, name='t_percentile_c') & + pure function t_percentile_c_intfc(p, df) bind(C, name='t_percentile_c') & result(t) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: p integer(C_INT), value, intent(in) :: df real(C_DOUBLE) :: t - end function t_percentile_cc + end function t_percentile_c_intfc - pure function calc_pn_cc(n, x) bind(C, name='calc_pn_c') result(pnx) + pure subroutine calc_pn_c_intfc(n, x, pnx) bind(C, name='calc_pn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), value, intent(in) :: x - real(C_DOUBLE) :: pnx - end function calc_pn_cc + real(C_DOUBLE), intent(out) :: pnx(n + 1) + end subroutine calc_pn_c_intfc - pure function evaluate_legendre_cc(n, data, x) & + pure function evaluate_legendre_c_intfc(n, data, x) & bind(C, name='evaluate_legendre_c') result(val) use ISO_C_BINDING implicit none @@ -51,67 +51,67 @@ module math real(C_DOUBLE), intent(in) :: data(n) real(C_DOUBLE), value, intent(in) :: x real(C_DOUBLE) :: val - end function evaluate_legendre_cc + end function evaluate_legendre_c_intfc - subroutine calc_rn_cc(n, uvw, rn) bind(C, name='calc_rn_c') + pure subroutine calc_rn_c_intfc(n, uvw, rn) bind(C, name='calc_rn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), intent(in) :: uvw(3) real(C_DOUBLE), intent(out) :: rn(2 * n + 1) - end subroutine calc_rn_cc + end subroutine calc_rn_c_intfc - subroutine calc_zn_cc(n, rho, phi, zn) bind(C, name='calc_zn_c') + pure subroutine calc_zn_c_intfc(n, rho, phi, zn) bind(C, name='calc_zn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), value, intent(in) :: rho real(C_DOUBLE), value, intent(in) :: phi real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) - end subroutine calc_zn_cc + end subroutine calc_zn_c_intfc - subroutine rotate_angle_cc(uvw, mu, phi) bind(C, name='rotate_angle_c') + subroutine rotate_angle_c_intfc(uvw, mu, phi) bind(C, name='rotate_angle_c') use ISO_C_BINDING implicit none real(C_DOUBLE), intent(inout) :: uvw(3) real(C_DOUBLE), value, intent(in) :: mu real(C_DOUBLE), value, intent(in) :: phi - end subroutine rotate_angle_cc + end subroutine rotate_angle_c_intfc - function maxwell_spectrum_cc(T) bind(C, name='maxwell_spectrum_c') & + function maxwell_spectrum_c_intfc(T) bind(C, name='maxwell_spectrum_c') & result(E_out) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: T real(C_DOUBLE) :: E_out - end function maxwell_spectrum_cc + end function maxwell_spectrum_c_intfc - function watt_spectrum_cc(a, b) bind(C, name='watt_spectrum_c') & + function watt_spectrum_c_intfc(a, b) bind(C, name='watt_spectrum_c') & result(E_out) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: a real(C_DOUBLE), value, intent(in) :: b real(C_DOUBLE) :: E_out - end function watt_spectrum_cc + end function watt_spectrum_c_intfc - function faddeeva_cc(z) bind(C, name='faddeeva_c') result(wv) + function faddeeva_c_intfc(z) bind(C, name='faddeeva_c') result(wv) use ISO_C_BINDING implicit none complex(C_DOUBLE_COMPLEX), value, intent(in) :: z complex(C_DOUBLE_COMPLEX) :: wv - end function faddeeva_cc + end function faddeeva_c_intfc - function w_derivative_cc(z, order) bind(C, name='w_derivative_c') & + function w_derivative_c_intfc(z, order) bind(C, name='w_derivative_c') & result(wv) use ISO_C_BINDING implicit none complex(C_DOUBLE_COMPLEX), value, intent(in) :: z integer(C_INT), value, intent(in) :: order complex(C_DOUBLE_COMPLEX) :: wv - end function w_derivative_cc + end function w_derivative_c_intfc - subroutine broaden_wmp_polynomials_cc(E, dopp, n, factors) & + subroutine broaden_wmp_polynomials_c_intfc(E, dopp, n, factors) & bind(C, name='broaden_wmp_polynomials_c') use ISO_C_BINDING implicit none @@ -119,7 +119,7 @@ module math real(C_DOUBLE), value, intent(in) :: dopp integer(C_INT), value, intent(in) :: n real(C_DOUBLE), intent(inout) :: factors(n) - end subroutine broaden_wmp_polynomials_cc + end subroutine broaden_wmp_polynomials_c_intfc function faddeeva_w(z, relerr) bind(C, name='Faddeeva_w') result(w) use ISO_C_BINDING @@ -143,7 +143,7 @@ contains integer(C_INT), intent(in) :: df ! degrees of freedom real(C_DOUBLE) :: t ! corresponding t-value - t = t_percentile_cc(p, df) + t = t_percentile_c_intfc(p, df) end function t_percentile @@ -157,18 +157,18 @@ contains ! the return value will be 1.0. !=============================================================================== - pure function calc_pn(n, x) result(pnx) bind(C) + pure subroutine calc_pn(n, x, pnx) bind(C) integer(C_INT), intent(in) :: n ! Legendre order requested real(C_DOUBLE), intent(in) :: x ! Independent variable the Legendre is to ! be evaluated at; x must be in the ! domain [-1,1] - real(C_DOUBLE) :: pnx ! The Legendre poly of order n evaluated - ! at x + real(C_DOUBLE), intent(out) :: pnx(n + 1) ! The Legendre polys of order n + ! evaluated at x - pnx = calc_pn_cc(n, x) + call calc_pn_c_intfc(n, x, pnx) - end function calc_pn + end subroutine calc_pn !=============================================================================== ! EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients @@ -181,7 +181,7 @@ contains real(C_DOUBLE), intent(in) :: x real(C_DOUBLE) :: val - val = evaluate_legendre_cc(size(data), data, x) + val = evaluate_legendre_c_intfc(size(data) - 1, data, x) end function evaluate_legendre @@ -190,14 +190,14 @@ contains ! (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) !=============================================================================== - subroutine calc_rn(n, uvw, rn) bind(C) + pure subroutine calc_rn(n, uvw, rn) bind(C) integer(C_INT), intent(in) :: n ! Order requested real(C_DOUBLE), intent(in) :: uvw(3) ! Direction of travel; ! assumed to be on unit sphere - real(C_DOUBLE) :: rn(2*n + 1) ! The resultant R_n(uvw) + real(C_DOUBLE), intent(out) :: rn(2*n + 1) ! The resultant R_n(uvw) - call calc_rn_cc(n, uvw, rn) + call calc_rn_c_intfc(n, uvw, rn) end subroutine calc_rn @@ -208,14 +208,14 @@ contains ! exactly pi !=============================================================================== - subroutine calc_zn(n, rho, phi, zn) bind(C) + pure subroutine calc_zn(n, rho, phi, zn) bind(C) integer(C_INT), intent(in) :: n ! Maximum order real(C_DOUBLE), intent(in) :: rho ! Radial location in the unit disk real(C_DOUBLE), intent(in) :: phi ! Theta (radians) location in the unit disk ! The resulting list of coefficients real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) - call calc_zn_cc(n, rho, phi, zn) + call calc_zn_c_intfc(n, rho, phi, zn) end subroutine calc_zn !=============================================================================== @@ -232,9 +232,9 @@ contains uvw = uvw0 if (present(phi)) then - call rotate_angle_cc(uvw, mu, phi) + call rotate_angle_c_intfc(uvw, mu, phi) else - call rotate_angle_cc(uvw, mu, -10._8) + call rotate_angle_c_intfc(uvw, mu, -10._8) end if end subroutine rotate_angle @@ -251,7 +251,7 @@ contains real(C_DOUBLE), intent(in) :: T ! tabulated function of incoming E real(C_DOUBLE) :: E_out ! sampled energy - E_out = maxwell_spectrum_cc(T) + E_out = maxwell_spectrum_c_intfc(T) end function maxwell_spectrum @@ -269,7 +269,7 @@ contains real(C_DOUBLE), intent(in) :: b ! Watt parameter b real(C_DOUBLE) :: E_out ! energy of emitted neutron - E_out = watt_spectrum_cc(a, b) + E_out = watt_spectrum_c_intfc(a, b) end function watt_spectrum @@ -336,7 +336,7 @@ contains integer(C_INT), intent(in) :: n ! number of components to polynomial real(C_DOUBLE), intent(out):: factors(n) ! output leading coefficient - call broaden_wmp_polynomials_cc(E, dopp, n, factors) + call broaden_wmp_polynomials_c_intfc(E, dopp, n, factors) end subroutine broaden_wmp_polynomials diff --git a/src/math_functions.cpp b/src/math_functions.cpp index b3478f065..c9d6fc359 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -8,10 +8,7 @@ namespace openmc { // distribution with a specified probability level //============================================================================== -double __attribute__ ((const)) normal_percentile_c(double p) { - - // return gsl_cdf_ugaussian_Pinv(p); - +double __attribute__ ((const)) normal_percentile_c(const double p) { double z; double q; double r; @@ -68,10 +65,7 @@ double __attribute__ ((const)) normal_percentile_c(double p) { // a specified probability level and number of degrees of freedom //============================================================================== -double __attribute__ ((const)) t_percentile_c(double p, int df){ - - // return gsl_cdf_tdist_Pinv(p, static_cast df); - +double __attribute__ ((const)) t_percentile_c(const double p, const int df){ double t; double n; double k; @@ -108,57 +102,23 @@ double __attribute__ ((const)) t_percentile_c(double p, int df){ } //============================================================================== -// CALC_PN calculates the n-th order Legendre polynomial at the value of x. +// CALC_PN calculates the n-th order Legendre polynomials at the value of x. //============================================================================== -double __attribute__ ((const)) calc_pn_c(int n, double x) { +void calc_pn_c(const int n, const double x, double pnx[]) { + int l; - // return gsl_sf_legendre_Pl(l, x); - - double pnx; - - switch(n) { - case 0: - pnx = 1.; - break; - case 1: - pnx = x; - break; - case 2: - pnx = 1.5 * x * x - 0.5; - break; - case 3: - pnx = 2.5 * x * x * x - 1.5 * x; - break; - case 4: - pnx = 4.375 * std::pow(x, 4.) - 3.75 * x * x + 0.375; - break; - case 5: - pnx = 7.875 * std::pow(x, 5.) - 8.75 * x * x * x + 1.875 * x; - break; - case 6: - pnx = 14.4375 * std::pow(x, 6.) - 19.6875 * std::pow(x, 4.) + - 6.5625 * x * x - 0.3125; - break; - case 7: - pnx = 26.8125 * std::pow(x, 7.) - 43.3125 * std::pow(x, 5.) + - 19.6875 * x * x * x - 2.1875 * x; - break; - case 8: - pnx = 50.2734375 * std::pow(x, 8.) - 93.84375 * std::pow(x, 6.) + - 54.140625 * std::pow(x, 4.) - 9.84375 * x * x + 0.2734375; - break; - case 9: - pnx = 94.9609375 * std::pow(x, 9.) - 201.09375 * std::pow(x, 7.) + - 140.765625 * std::pow(x, 5.) - 36.09375 * x * x * x + 2.4609375 * x; - break; - case 10: - pnx = 180.42578125 * std::pow(x, 10.) - 427.32421875 * std::pow(x, 8.) + - 351.9140625 * std::pow(x, 6.) - 117.3046875 * std::pow(x, 4.) + - 13.53515625 * x * x - 0.24609375; + pnx[0] = 1.; + if (n >= 1) { + pnx[1] = x; } - return pnx; + // Use recursion relation to build the higher orders + for (l = 1; l < n; l ++) { + pnx[l + 1] = (static_cast(2 * l + 1) * x * pnx[l] - + static_cast(l) * pnx[l - 1]) / + (static_cast(l + 1)); + } } //============================================================================== @@ -166,27 +126,34 @@ double __attribute__ ((const)) calc_pn_c(int n, double x) { // and the value of x //============================================================================== -double __attribute__ ((const)) evaluate_legendre_c(int n, double data[], - double x) { +double __attribute__ ((const)) evaluate_legendre_c(const int n, + const double data[], + const double x) { double val; int l; + double* pnx = new double[n + 1]; - val = 0.5 * data[0]; - for (l = 1; l < n; l++) { - val += (static_cast(l) + 0.5) * data[l] * calc_pn_c(l, x); + val = 0.0; + calc_pn_c(n, x, pnx); + for (l = 0; l <= n; l++) { + val += (static_cast(l) + 0.5) * data[l] * pnx[l]; } + delete[] pnx; return val; } //============================================================================== // CALC_RN calculates the n-th order spherical harmonics for a given angle -// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) +// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) for n +// all 0 <= n //============================================================================== -void calc_rn_c(int n, double uvw[3], double rn[]){ +void calc_rn_c(const int n, const double uvw[3], double rn[]){ double phi; double w; double w2m1; + int i; + int l; // rn[] is assumed to have already been allocated to the correct size @@ -201,380 +168,383 @@ void calc_rn_c(int n, double uvw[3], double rn[]){ // Store the shorthand of 1-w * w w2m1 = 1. - w * w; - // Now evaluate the spherical harmonics function depending on the order - // requested - switch (n) { - case 0: - // l = 0, m = 0 - rn[0] = 1.; - break; - case 1: - // l = 1, m = -1 - rn[0] = -(1.*std::sqrt(w2m1) * std::sin(phi)); - // l = 1, m = 0 - rn[1] = w; - // l = 1, m = 1 - rn[2] = -(1.*std::sqrt(w2m1) * std::cos(phi)); - break; - case 2: - // l = 2, m = -2 - rn[0] = 0.288675134594813 * (-3. * w * w + 3.) * std::sin(2. * phi); - // l = 2, m = -1 - rn[1] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::sin(phi)); - // l = 2, m = 0 - rn[2] = 1.5 * w * w - 0.5; - // l = 2, m = 1 - rn[3] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::cos(phi)); - // l = 2, m = 2 - rn[4] = 0.288675134594813 * (-3. * w * w + 3.) * std::cos(2. * phi); - break; - case 3: - // l = 3, m = -3 - rn[0] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::sin(3. * phi)); - // l = 3, m = -2 - rn[1] = 1.93649167310371 * w*(w2m1) * std::sin(2.*phi); - // l = 3, m = -1 - rn[2] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * - std::sin(phi)); - // l = 3, m = 0 - rn[3] = 2.5 * std::pow(w, 3) - 1.5 * w; - // l = 3, m = 1 - rn[4] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * - std::cos(phi)); - // l = 3, m = 2 - rn[5] = 1.93649167310371 * w*(w2m1) * std::cos(2.*phi); - // l = 3, m = 3 - rn[6] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::cos(3.* phi)); - break; - case 4: - // l = 4, m = -4 - rn[0] = 0.739509972887452 * (w2m1 * w2m1) * std::sin(4.0*phi); - // l = 4, m = -3 - rn[1] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::sin(3.* phi)); - // l = 4, m = -2 - rn[2] = 0.074535599249993 * (w2m1)*(52.5 * w * w - 7.5) * std::sin(2. *phi); - // l = 4, m = -1 - rn[3] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5 * w) * - std::sin(phi)); - // l = 4, m = 0 - rn[4] = 4.375 * std::pow(w, 4) - 3.75 * w * w + 0.375; - // l = 4, m = 1 - rn[5] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5*w) * - std::cos(phi)); - // l = 4, m = 2 - rn[6] = 0.074535599249993 * (w2m1)*(52.5*w * w - 7.5) * std::cos(2.*phi); - // l = 4, m = 3 - rn[7] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::cos(3.* phi)); - // l = 4, m = 4 - rn[8] = 0.739509972887452 * w2m1 * w2m1 * std::cos(4.0*phi); - break; - case 5: - // l = 5, m = -5 - rn[0] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::sin(5.0 * phi)); - // l = 5, m = -4 - rn[1] = 2.21852991866236 * w * w2m1 * w2m1 * std::sin(4.0 * phi); - // l = 5, m = -3 - rn[2] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * - ((945.0 /2.)* w * w - 52.5) * std::sin(3.*phi)); - // l = 5, m = -2 - rn[3] = 0.0487950036474267 * (w2m1) - * ((315.0/2.)* std::pow(w, 3) - 52.5 * w) * std::sin(2.*phi); - // l = 5, m = -1 - rn[4] = -(0.258198889747161*std::sqrt(w2m1) * - (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::sin(phi)); - // l = 5, m = 0 - rn[5] = 7.875 * std::pow(w, 5) - 8.75 * std::pow(w, 3) + 1.875 * w; - // l = 5, m = 1 - rn[6] = -(0.258198889747161 * std::sqrt(w2m1)* - (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::cos(phi)); - // l = 5, m = 2 - rn[7] = 0.0487950036474267 * (w2m1) * - ((315.0 / 2.) * std::pow(w, 3) - 52.5*w) * std::cos(2.*phi); - // l = 5, m = 3 - rn[8] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * - ((945.0 / 2.) * w * w - 52.5) * std::cos(3.*phi)); - // l = 5, m = 4 - rn[9] = 2.21852991866236 * w * w2m1 * w2m1 * std::cos(4.0*phi); - // l = 5, m = 5 - rn[10] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::cos(5.0* phi)); - break; - case 6: - // l = 6, m = -6 - rn[0] = 0.671693289381396 * std::pow(w2m1, 3) * std::sin(6.0*phi); - // l = 6, m = -5 - rn[1] = -(2.32681380862329 * w*std::pow(w2m1, 2.5) * std::sin(5.0*phi)); - // l = 6, m = -4 - rn[2] = 0.00104990131391452 * w2m1 * w2m1 * - ((10395.0/2.) * w * w - 945.0/2.) * std::sin(4.0 * phi); - // l = 6, m = -3 - rn[3] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * - ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::sin(3.*phi)); - // l = 6, m = -2 - rn[4] = 0.0345032779671177 * (w2m1) * - ((3465.0/8.0)* std::pow(w, 4) - 945.0/4.0 * w * w + 105.0/8.0) * - std::sin(2. * phi); - // l = 6, m = -1 - rn[5] = -(0.218217890235992*std::sqrt(w2m1) * - ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * - std::sin(phi)); - // l = 6, m = 0 - rn[6] = 14.4375 * std::pow(w, 6) - 19.6875 * std::pow(w, 4) + 6.5625 * w * w - - 0.3125; - // l = 6, m = 1 - rn[7] = -(0.218217890235992*std::sqrt(w2m1) * - ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * - std::cos(phi)); - // l = 6, m = 2 - rn[8] = 0.0345032779671177 * w2m1 * - ((3465.0/8.0)* std::pow(w, 4) -945.0/4.0 * w * w + 105.0/8.0) * - std::cos(2.*phi); - // l = 6, m = 3 - rn[9] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * - ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::cos(3.*phi)); - // l = 6, m = 4 - rn[10] = 0.00104990131391452 * w2m1 * w2m1 * - ((10395.0/2.)*w * w - 945.0/2.) * std::cos(4.0*phi); - // l = 6, m = 5 - rn[11] = -(2.32681380862329 * w * std::pow(w2m1, 2.5) * std::cos(5.0*phi)); - // l = 6, m = 6 - rn[12] = 0.671693289381396 * std::pow(w2m1, 3) * std::cos(6.0*phi); - break; - case 7: - // l = 7, m = -7 - rn[0] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::sin(7.0*phi)); - // l = 7, m = -6 - rn[1] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::sin(6.0*phi); - // l = 7, m = -5 - rn[2] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * - ((135135.0/2.)*w * w - 10395.0/2.) * std::sin(5.0*phi)); - // l = 7, m = -4 - rn[3] = 0.000548293079133141 * w2m1 * w2m1 * - ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::sin(4.0*phi); - // l = 7, m = -3 - rn[4] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * - ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * - std::sin(3.*phi)); - // l = 7, m = -2 - rn[5] = 0.025717224993682 * (w2m1) * - ((9009.0/8.0)* std::pow(w, 5) -3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * - std::sin(2.*phi); - // l = 7, m = -1 - rn[6] = -(0.188982236504614*std::sqrt(w2m1) * - ((3003.0/16.0)* std::pow(w, 6) - 3465.0/16.0 * std::pow(w, 4) + - (945.0/16.0)*w * w - 35.0/16.0) * std::sin(phi)); - // l = 7, m = 0 - rn[7] = 26.8125 * std::pow(w, 7) - 43.3125 * std::pow(w, 5) + 19.6875 * std::pow(w, 3) - - 2.1875 * w; - // l = 7, m = 1 - rn[8] = -(0.188982236504614*std::sqrt(w2m1) * ((3003.0/16.0) * std::pow(w, 6) - - 3465.0/16.0 * std::pow(w, 4) + (945.0/16.0)*w * w - 35.0/16.0) * std::cos(phi)); - // l = 7, m = 2 - rn[9] = 0.025717224993682 * (w2m1) * ((9009.0/8.0)* std::pow(w, 5) - - 3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * std::cos(2.*phi); - // l = 7, m = 3 - rn[10] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * - ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * - std::cos(3.*phi)); - // l = 7, m = 4 - rn[11] = 0.000548293079133141 * w2m1 * w2m1 * - ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::cos(4.0*phi); - // l = 7, m = 5 - rn[12] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * - ((135135.0/2.)*w * w - 10395.0/2.) * std::cos(5.0*phi)); - // l = 7, m = 6 - rn[13] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::cos(6.0*phi); - // l = 7, m = 7 - rn[14] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::cos(7.0*phi)); - break; - case 8: - // l = 8, m = -8 - rn[0] = 0.626706654240044 * std::pow(w2m1, 4) * std::sin(8.0*phi); - // l = 8, m = -7 - rn[1] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::sin(7.0*phi)); - // l = 8, m = -6 - rn[2] = 6.77369783729086e-6*std::pow(w2m1, 3)* - ((2027025.0/2.)*w * w - 135135.0/2.) * std::sin(6.0*phi); - // l = 8, m = -5 - rn[3] = -(4.38985792528482e-5*std::pow(w2m1, 2.5) * - ((675675.0/2.)*std::pow(w, 3) - 135135.0/2.*w) * std::sin(5.0*phi)); - // l = 8, m = -4 - rn[4] = 0.000316557156832328 * w2m1 * w2m1 * - ((675675.0/8.0)* std::pow(w, 4) - 135135.0/4.0 * w * w + 10395.0/8.0) * - std::sin(4.0*phi); - // l = 8, m = -3 - rn[5] = -(0.00245204119306875 * std::pow(w2m1, 1.5) * ((135135.0/8.0) * - std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + (10395.0/8.0)*w) * std::sin(3.*phi)); - // l = 8, m = -2 - rn[6] = 0.0199204768222399 * (w2m1) * - ((45045.0/16.0)* std::pow(w, 6)- 45045.0/16.0 * std::pow(w, 4) + - (10395.0/16.0)*w * w - 315.0/16.0) * std::sin(2.*phi); - // l = 8, m = -1 - rn[7] = -(0.166666666666667*std::sqrt(w2m1) * - ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + - (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::sin(phi)); - // l = 8, m = 0 - rn[8] = 50.2734375 * std::pow(w, 8) - 93.84375 * std::pow(w, 6) + 54.140625 * - std::pow(w, 4) - 9.84375 * w * w + 0.2734375; - // l = 8, m = 1 - rn[9] = -(0.166666666666667*std::sqrt(w2m1) * - ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + - (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::cos(phi)); - // l = 8, m = 2 - rn[10] = 0.0199204768222399 * (w2m1)*((45045.0/16.0)* std::pow(w, 6)- - 45045.0/16.0 * std::pow(w, 4) + (10395.0/16.0)*w * w - - 315.0/16.0) * std::cos(2.*phi); - // l = 8, m = 3 - rn[11] = -(0.00245204119306875 * std::pow(w2m1, 1.5)* - ((135135.0/8.0) * std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + - (10395.0/8.0)*w) * std::cos(3.*phi)); - // l = 8, m = 4 - rn[12] = 0.000316557156832328 * w2m1 * w2m1*((675675.0/8.0)* std::pow(w, 4) - - 135135.0/4.0 * w * w + 10395.0/8.0) * std::cos(4.0*phi); - // l = 8, m = 5 - rn[13] = -(4.38985792528482e-5*std::pow(w2m1, 2.5)*((675675.0/2.)*std::pow(w, 3) - - 135135.0/2.*w) * std::cos(5.0*phi)); - // l = 8, m = 6 - rn[14] = 6.77369783729086e-6*std::pow(w2m1, 3)*((2027025.0/2.)*w * w - - 135135.0/2.) * std::cos(6.0*phi); - // l = 8, m = 7 - rn[15] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::cos(7.0*phi)); - // l = 8, m = 8 - rn[16] = 0.626706654240044 * std::pow(w2m1, 4) * std::cos(8.0*phi); - break; - case 9: - // l = 9, m = -9 - rn[0] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); - // l = 9, m = -8 - rn[1] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::sin(8.0 * phi); - // l = 9, m = -7 - rn[2] = -(4.37240315267812e-7*std::pow(w2m1, 3.5) * - ((34459425.0/2.)*w * w - 2027025.0/2.) * std::sin(7.0 * phi)); - // l = 9, m = -6 - rn[3] = 3.02928976464514e-6*std::pow(w2m1, 3)* - ((11486475.0/2.)*std::pow(w, 3) - 2027025.0/2.*w) * std::sin(6.0 * phi); - // l = 9, m = -5 - rn[4] = -(2.34647776186144e-5*std::pow(w2m1, 2.5) * - ((11486475.0/8.0)* std::pow(w, 4) - 2027025.0 / 4.0 * w * w + - 135135.0/8.0) * std::sin(5.0 * phi)); - // l = 9, m = -4 - rn[5] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0)* std::pow(w, 5) - - 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::sin(4.0*phi); - // l = 9, m = -3 - rn[6] = -(0.00173385495536766 * std::pow(w2m1, 1.5) * - ((765765.0/16.0)* std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + - (135135.0/16.0)*w * w - 3465.0/16.0) * std::sin(3. * phi)); - // l = 9, m = -2 - rn[7] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7)- - 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - - 3465.0/16.0 * w) * std::sin(2. * phi); - // l = 9, m = -1 - rn[8] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - - 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - - 3465.0/32.0 * w * w + 315.0/128.0) * std::sin(phi)); - // l = 9, m = 0 - rn[9] = 94.9609375 * std::pow(w, 9) - 201.09375 * std::pow(w, 7) + - 140.765625 * std::pow(w, 5)- 36.09375 * std::pow(w, 3) + 2.4609375 * w; - // l = 9, m = 1 - rn[10] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - - 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - - 3465.0/32.0 * w * w + 315.0/128.0) * std::cos(phi)); - // l = 9, m = 2 - rn[11] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7) - - 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - - 3465.0/ 16.0 * w) * std::cos(2. * phi); - // l = 9, m = 3 - rn[12] = -(0.00173385495536766 * std::pow(w2m1, 1.5)*((765765.0/16.0) * - std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + - (135135.0/16.0)* w * w - 3465.0/16.0)* std::cos(3. * phi)); - // l = 9, m = 4 - rn[13] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0) * std::pow(w, 5) - - 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::cos(4.0 * phi); - // l = 9, m = 5 - rn[14] = -(2.34647776186144e-5*std::pow(w2m1, 2.5)*((11486475.0/8.0) * - std::pow(w, 4) - 2027025.0/4.0 * w * w + 135135.0/8.0) * - std::cos(5.0 * phi)); - // l = 9, m = 6 - rn[15] = 3.02928976464514e-6*std::pow(w2m1, 3)*((11486475.0/2.)*std::pow(w, 3) - - 2027025.0/2. * w) * std::cos(6.0 * phi); - // l = 9, m = 7 - rn[16] = -(4.37240315267812e-7*std::pow(w2m1, 3.5)* - ((34459425.0/2.) * w * w - 2027025.0/2.) * std::cos(7.0 * phi)); - // l = 9, m = 8 - rn[17] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::cos(8.0 * phi); - // l = 9, m = 9 - rn[18] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); - break; - case 10: - // l = 10, m = -10 - rn[0] = 0.593627917136573 * std::pow(w2m1, 5) * std::sin(10.0 * phi); - // l = 10, m = -9 - rn[1] = -(2.65478475211798 * w * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); - // l = 10, m = -8 - rn[2] = 2.49953651452314e-8 * std::pow(w2m1, 4) * - ((654729075.0/2.) * w * w - 34459425.0/2.) * std::sin(8.0 * phi); - // l = 10, m = -7 - rn[3] = -(1.83677671621093e-7*std::pow(w2m1, 3.5)* - ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * - std::sin(7.0 * phi)); - // l = 10, m = -6 - rn[4] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - - 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::sin(6.0 * phi); - // l = 10, m = -5 - rn[5] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* - ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + - (2027025.0/8.0)*w) * std::sin(5.0 * phi)); - // l = 10, m = -4 - rn[6] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - - 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0)*w * w - - 45045.0/16.0) * std::sin(4.0 * phi); - // l = 10, m = -3 - rn[7] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* - ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + - (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::sin(3. * phi)); - // l = 10, m = -2 - rn[8] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - - 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - - 45045.0/32.0 * w * w + 3465.0/128.0) * std::sin(2. * phi); - // l = 10, m = -1 - rn[9] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - - 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) - - 15015.0/32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::sin(phi)); - // l = 10, m = 0 - rn[10] = 180.42578125 * std::pow(w, 10) - 427.32421875 * std::pow(w, 8) +351.9140625 - * std::pow(w, 6) - 117.3046875 * std::pow(w, 4) + 13.53515625 * w * w -0.24609375; - // l = 10, m = 1 - rn[11] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - - 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) -15015.0/ - 32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::cos(phi)); - // l = 10, m = 2 - rn[12] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - - 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - - 45045.0/32.0 * w * w + 3465.0/128.0) * std::cos(2. * phi); - // l = 10, m = 3 - rn[13] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* - ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + - (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::cos(3. * phi)); - // l = 10, m = 4 - rn[14] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - - 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0) * w * w - - 45045.0/16.0) * std::cos(4.0 * phi); - // l = 10, m = 5 - rn[15] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* - ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + - (2027025.0/8.0)*w) * std::cos(5.0 * phi)); - // l = 10, m = 6 - rn[16] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - - 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::cos(6.0 * phi); - // l = 10, m = 7 - rn[17] = -(1.83677671621093e-7*std::pow(w2m1, 3.5) * - ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * std::cos(7.0 * phi)); - // l = 10, m = 8 - rn[18] = 2.49953651452314e-8*std::pow(w2m1, 4)* - ((654729075.0/2.)*w * w - 34459425.0/2.) * std::cos(8.0 * phi); - // l = 10, m = 9 - rn[19] = -(2.65478475211798 * w*std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); - // l = 10, m = 10 - rn[20] = 0.593627917136573 * std::pow(w2m1, 5) * std::cos(10.0 * phi); + // Now evaluate the spherical harmonics function + rn[0] = 1.; + i = 0; + for (l = 1; l <= n; l++) { + // Set the index to the start of this order + i += 2 * (l - 1) + 1; + + // Now evaluate each + switch (l) { + case 1: + // l = 1, m = -1 + rn[i] = -(std::sqrt(w2m1) * std::sin(phi)); + // l = 1, m = 0 + rn[i + 1] = w; + // l = 1, m = 1 + rn[i + 2] = -(std::sqrt(w2m1) * std::cos(phi)); + break; + case 2: + // l = 2, m = -2 + rn[i] = 0.288675134594813 * (-3. * w * w + 3.) * std::sin(2. * phi); + // l = 2, m = -1 + rn[i + 1] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::sin(phi)); + // l = 2, m = 0 + rn[i + 2] = 1.5 * w * w - 0.5; + // l = 2, m = 1 + rn[i + 3] = -(1.73205080756888 * w*std::sqrt(w2m1) * std::cos(phi)); + // l = 2, m = 2 + rn[i + 4] = 0.288675134594813 * (-3. * w * w + 3.) * std::cos(2. * phi); + break; + case 3: + // l = 3, m = -3 + rn[i] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::sin(3. * phi)); + // l = 3, m = -2 + rn[i + 1] = 1.93649167310371 * w*(w2m1) * std::sin(2.*phi); + // l = 3, m = -1 + rn[i + 2] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * + std::sin(phi)); + // l = 3, m = 0 + rn[i + 3] = 2.5 * std::pow(w, 3) - 1.5 * w; + // l = 3, m = 1 + rn[i + 4] = -(0.408248290463863*std::sqrt(w2m1)*((7.5)*w * w - 3./2.) * + std::cos(phi)); + // l = 3, m = 2 + rn[i + 5] = 1.93649167310371 * w*(w2m1) * std::cos(2.*phi); + // l = 3, m = 3 + rn[i + 6] = -(0.790569415042095 * std::pow(w2m1, 1.5) * std::cos(3.* phi)); + break; + case 4: + // l = 4, m = -4 + rn[i] = 0.739509972887452 * (w2m1 * w2m1) * std::sin(4.0*phi); + // l = 4, m = -3 + rn[i + 1] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::sin(3.* phi)); + // l = 4, m = -2 + rn[i + 2] = 0.074535599249993 * (w2m1)*(52.5 * w * w - 7.5) * std::sin(2. *phi); + // l = 4, m = -1 + rn[i + 3] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5 * w) * + std::sin(phi)); + // l = 4, m = 0 + rn[i + 4] = 4.375 * std::pow(w, 4) - 3.75 * w * w + 0.375; + // l = 4, m = 1 + rn[i + 5] = -(0.316227766016838*std::sqrt(w2m1)*(17.5 * std::pow(w, 3) - 7.5*w) * + std::cos(phi)); + // l = 4, m = 2 + rn[i + 6] = 0.074535599249993 * (w2m1)*(52.5*w * w - 7.5) * std::cos(2.*phi); + // l = 4, m = 3 + rn[i + 7] = -(2.09165006633519 * w * std::pow(w2m1, 1.5) * std::cos(3.* phi)); + // l = 4, m = 4 + rn[i + 8] = 0.739509972887452 * w2m1 * w2m1 * std::cos(4.0*phi); + break; + case 5: + // l = 5, m = -5 + rn[i] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::sin(5.0 * phi)); + // l = 5, m = -4 + rn[i + 1] = 2.21852991866236 * w * w2m1 * w2m1 * std::sin(4.0 * phi); + // l = 5, m = -3 + rn[i + 2] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * + ((945.0 /2.)* w * w - 52.5) * std::sin(3.*phi)); + // l = 5, m = -2 + rn[i + 3] = 0.0487950036474267 * (w2m1) + * ((315.0/2.)* std::pow(w, 3) - 52.5 * w) * std::sin(2.*phi); + // l = 5, m = -1 + rn[i + 4] = -(0.258198889747161*std::sqrt(w2m1) * + (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::sin(phi)); + // l = 5, m = 0 + rn[i + 5] = 7.875 * std::pow(w, 5) - 8.75 * std::pow(w, 3) + 1.875 * w; + // l = 5, m = 1 + rn[i + 6] = -(0.258198889747161 * std::sqrt(w2m1)* + (39.375 * std::pow(w, 4) - 105.0/4.0 * w * w + 15.0/8.0) * std::cos(phi)); + // l = 5, m = 2 + rn[i + 7] = 0.0487950036474267 * (w2m1) * + ((315.0 / 2.) * std::pow(w, 3) - 52.5*w) * std::cos(2.*phi); + // l = 5, m = 3 + rn[i + 8] = -(0.00996023841111995 * std::pow(w2m1, 1.5) * + ((945.0 / 2.) * w * w - 52.5) * std::cos(3.*phi)); + // l = 5, m = 4 + rn[i + 9] = 2.21852991866236 * w * w2m1 * w2m1 * std::cos(4.0*phi); + // l = 5, m = 5 + rn[i + 10] = -(0.701560760020114 * std::pow(w2m1, 2.5) * std::cos(5.0* phi)); + break; + case 6: + // l = 6, m = -6 + rn[i] = 0.671693289381396 * std::pow(w2m1, 3) * std::sin(6.0*phi); + // l = 6, m = -5 + rn[i + 1] = -(2.32681380862329 * w*std::pow(w2m1, 2.5) * std::sin(5.0*phi)); + // l = 6, m = -4 + rn[i + 2] = 0.00104990131391452 * w2m1 * w2m1 * + ((10395.0/2.) * w * w - 945.0/2.) * std::sin(4.0 * phi); + // l = 6, m = -3 + rn[i + 3] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * + ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::sin(3.*phi)); + // l = 6, m = -2 + rn[i + 4] = 0.0345032779671177 * (w2m1) * + ((3465.0/8.0)* std::pow(w, 4) - 945.0/4.0 * w * w + 105.0/8.0) * + std::sin(2. * phi); + // l = 6, m = -1 + rn[i + 5] = -(0.218217890235992*std::sqrt(w2m1) * + ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * + std::sin(phi)); + // l = 6, m = 0 + rn[i + 6] = 14.4375 * std::pow(w, 6) - 19.6875 * std::pow(w, 4) + 6.5625 * w * w - + 0.3125; + // l = 6, m = 1 + rn[i + 7] = -(0.218217890235992*std::sqrt(w2m1) * + ((693.0/8.0)* std::pow(w, 5)- 315.0/4.0 * std::pow(w, 3) + (105.0/8.0)*w) * + std::cos(phi)); + // l = 6, m = 2 + rn[i + 8] = 0.0345032779671177 * w2m1 * + ((3465.0/8.0)* std::pow(w, 4) -945.0/4.0 * w * w + 105.0/8.0) * + std::cos(2.*phi); + // l = 6, m = 3 + rn[i + 9] = -(0.00575054632785295 * std::pow(w2m1, 1.5) * + ((3465.0/2.) * std::pow(w, 3) - 945.0/2.*w) * std::cos(3.*phi)); + // l = 6, m = 4 + rn[i + 10] = 0.00104990131391452 * w2m1 * w2m1 * + ((10395.0/2.)*w * w - 945.0/2.) * std::cos(4.0*phi); + // l = 6, m = 5 + rn[i + 11] = -(2.32681380862329 * w * std::pow(w2m1, 2.5) * std::cos(5.0*phi)); + // l = 6, m = 6 + rn[i + 12] = 0.671693289381396 * std::pow(w2m1, 3) * std::cos(6.0*phi); + break; + case 7: + // l = 7, m = -7 + rn[i] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::sin(7.0*phi)); + // l = 7, m = -6 + rn[i + 1] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::sin(6.0*phi); + // l = 7, m = -5 + rn[i + 2] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * + ((135135.0/2.)*w * w - 10395.0/2.) * std::sin(5.0*phi)); + // l = 7, m = -4 + rn[i + 3] = 0.000548293079133141 * w2m1 * w2m1 * + ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::sin(4.0*phi); + // l = 7, m = -3 + rn[i + 4] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * + ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * + std::sin(3.*phi)); + // l = 7, m = -2 + rn[i + 5] = 0.025717224993682 * (w2m1) * + ((9009.0/8.0)* std::pow(w, 5) -3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * + std::sin(2.*phi); + // l = 7, m = -1 + rn[i + 6] = -(0.188982236504614*std::sqrt(w2m1) * + ((3003.0/16.0)* std::pow(w, 6) - 3465.0/16.0 * std::pow(w, 4) + + (945.0/16.0)*w * w - 35.0/16.0) * std::sin(phi)); + // l = 7, m = 0 + rn[i + 7] = 26.8125 * std::pow(w, 7) - 43.3125 * std::pow(w, 5) + 19.6875 * std::pow(w, 3) - + 2.1875 * w; + // l = 7, m = 1 + rn[i + 8] = -(0.188982236504614*std::sqrt(w2m1) * ((3003.0/16.0) * std::pow(w, 6) - + 3465.0/16.0 * std::pow(w, 4) + (945.0/16.0)*w * w - 35.0/16.0) * std::cos(phi)); + // l = 7, m = 2 + rn[i + 9] = 0.025717224993682 * (w2m1) * ((9009.0/8.0)* std::pow(w, 5) - + 3465.0/4.0 * std::pow(w, 3) + (945.0/8.0)*w) * std::cos(2.*phi); + // l = 7, m = 3 + rn[i + 10] = -(0.00363696483726654 * std::pow(w2m1, 1.5) * + ((45045.0/8.0)* std::pow(w, 4) - 10395.0/4.0 * w * w + 945.0/8.0) * + std::cos(3.*phi)); + // l = 7, m = 4 + rn[i + 11] = 0.000548293079133141 * w2m1 * w2m1 * + ((45045.0/2.)*std::pow(w, 3) - 10395.0/2.*w) * std::cos(4.0*phi); + // l = 7, m = 5 + rn[i + 12] = -(9.13821798555235e-5*std::pow(w2m1, 2.5) * + ((135135.0/2.)*w * w - 10395.0/2.) * std::cos(5.0*phi)); + // l = 7, m = 6 + rn[i + 13] = 2.42182459624969 * w*std::pow(w2m1, 3) * std::cos(6.0*phi); + // l = 7, m = 7 + rn[i + 14] = -(0.647259849287749 * std::pow(w2m1, 3.5) * std::cos(7.0*phi)); + break; + case 8: + // l = 8, m = -8 + rn[i] = 0.626706654240044 * std::pow(w2m1, 4) * std::sin(8.0*phi); + // l = 8, m = -7 + rn[i + 1] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::sin(7.0*phi)); + // l = 8, m = -6 + rn[i + 2] = 6.77369783729086e-6*std::pow(w2m1, 3)* + ((2027025.0/2.)*w * w - 135135.0/2.) * std::sin(6.0*phi); + // l = 8, m = -5 + rn[i + 3] = -(4.38985792528482e-5*std::pow(w2m1, 2.5) * + ((675675.0/2.)*std::pow(w, 3) - 135135.0/2.*w) * std::sin(5.0*phi)); + // l = 8, m = -4 + rn[i + 4] = 0.000316557156832328 * w2m1 * w2m1 * + ((675675.0/8.0)* std::pow(w, 4) - 135135.0/4.0 * w * w + 10395.0/8.0) * + std::sin(4.0*phi); + // l = 8, m = -3 + rn[i + 5] = -(0.00245204119306875 * std::pow(w2m1, 1.5) * ((135135.0/8.0) * + std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + (10395.0/8.0)*w) * std::sin(3.*phi)); + // l = 8, m = -2 + rn[i + 6] = 0.0199204768222399 * (w2m1) * + ((45045.0/16.0)* std::pow(w, 6)- 45045.0/16.0 * std::pow(w, 4) + + (10395.0/16.0)*w * w - 315.0/16.0) * std::sin(2.*phi); + // l = 8, m = -1 + rn[i + 7] = -(0.166666666666667*std::sqrt(w2m1) * + ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + + (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::sin(phi)); + // l = 8, m = 0 + rn[i + 8] = 50.2734375 * std::pow(w, 8) - 93.84375 * std::pow(w, 6) + 54.140625 * + std::pow(w, 4) - 9.84375 * w * w + 0.2734375; + // l = 8, m = 1 + rn[i + 9] = -(0.166666666666667*std::sqrt(w2m1) * + ((6435.0/16.0)* std::pow(w, 7) - 9009.0/16.0 * std::pow(w, 5) + + (3465.0/16.0)*std::pow(w, 3) - 315.0/16.0 * w) * std::cos(phi)); + // l = 8, m = 2 + rn[i + 10] = 0.0199204768222399 * (w2m1)*((45045.0/16.0)* std::pow(w, 6)- + 45045.0/16.0 * std::pow(w, 4) + (10395.0/16.0)*w * w - + 315.0/16.0) * std::cos(2.*phi); + // l = 8, m = 3 + rn[i + 11] = -(0.00245204119306875 * std::pow(w2m1, 1.5)* + ((135135.0/8.0) * std::pow(w, 5) - 45045.0/4.0 * std::pow(w, 3) + + (10395.0/8.0)*w) * std::cos(3.*phi)); + // l = 8, m = 4 + rn[i + 12] = 0.000316557156832328 * w2m1 * w2m1*((675675.0/8.0)* std::pow(w, 4) - + 135135.0/4.0 * w * w + 10395.0/8.0) * std::cos(4.0*phi); + // l = 8, m = 5 + rn[i + 13] = -(4.38985792528482e-5*std::pow(w2m1, 2.5)*((675675.0/2.)*std::pow(w, 3) - + 135135.0/2.*w) * std::cos(5.0*phi)); + // l = 8, m = 6 + rn[i + 14] = 6.77369783729086e-6*std::pow(w2m1, 3)*((2027025.0/2.)*w * w - + 135135.0/2.) * std::cos(6.0*phi); + // l = 8, m = 7 + rn[i + 15] = -(2.50682661696018 * w*std::pow(w2m1, 3.5) * std::cos(7.0*phi)); + // l = 8, m = 8 + rn[i + 16] = 0.626706654240044 * std::pow(w2m1, 4) * std::cos(8.0*phi); + break; + case 9: + // l = 9, m = -9 + rn[i] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); + // l = 9, m = -8 + rn[i + 1] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::sin(8.0 * phi); + // l = 9, m = -7 + rn[i + 2] = -(4.37240315267812e-7*std::pow(w2m1, 3.5) * + ((34459425.0/2.)*w * w - 2027025.0/2.) * std::sin(7.0 * phi)); + // l = 9, m = -6 + rn[i + 3] = 3.02928976464514e-6*std::pow(w2m1, 3)* + ((11486475.0/2.)*std::pow(w, 3) - 2027025.0/2.*w) * std::sin(6.0 * phi); + // l = 9, m = -5 + rn[i + 4] = -(2.34647776186144e-5*std::pow(w2m1, 2.5) * + ((11486475.0/8.0)* std::pow(w, 4) - 2027025.0 / 4.0 * w * w + + 135135.0/8.0) * std::sin(5.0 * phi)); + // l = 9, m = -4 + rn[i + 5] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0)* std::pow(w, 5) - + 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::sin(4.0*phi); + // l = 9, m = -3 + rn[i + 6] = -(0.00173385495536766 * std::pow(w2m1, 1.5) * + ((765765.0/16.0)* std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + + (135135.0/16.0)*w * w - 3465.0/16.0) * std::sin(3. * phi)); + // l = 9, m = -2 + rn[i + 7] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7)- + 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - + 3465.0/16.0 * w) * std::sin(2. * phi); + // l = 9, m = -1 + rn[i + 8] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - + 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - + 3465.0/32.0 * w * w + 315.0/128.0) * std::sin(phi)); + // l = 9, m = 0 + rn[i + 9] = 94.9609375 * std::pow(w, 9) - 201.09375 * std::pow(w, 7) + + 140.765625 * std::pow(w, 5)- 36.09375 * std::pow(w, 3) + 2.4609375 * w; + // l = 9, m = 1 + rn[i + 10] = -(0.149071198499986*std::sqrt(w2m1)*((109395.0/128.0)* std::pow(w, 8) - + 45045.0/32.0 * std::pow(w, 6) + (45045.0/64.0)* std::pow(w, 4) - + 3465.0/32.0 * w * w + 315.0/128.0) * std::cos(phi)); + // l = 9, m = 2 + rn[i + 11] = 0.0158910431540932 * (w2m1)*((109395.0/16.0)* std::pow(w, 7) - + 135135.0/16.0 * std::pow(w, 5) + (45045.0/16.0)*std::pow(w, 3) - + 3465.0/ 16.0 * w) * std::cos(2. * phi); + // l = 9, m = 3 + rn[i + 12] = -(0.00173385495536766 * std::pow(w2m1, 1.5)*((765765.0/16.0) * + std::pow(w, 6) - 675675.0/16.0 * std::pow(w, 4) + + (135135.0/16.0)* w * w - 3465.0/16.0)* std::cos(3. * phi)); + // l = 9, m = 4 + rn[i + 13] = 0.000196320414650061 * w2m1 * w2m1*((2297295.0/8.0) * std::pow(w, 5) - + 675675.0/4.0 * std::pow(w, 3) + (135135.0/8.0)*w) * std::cos(4.0 * phi); + // l = 9, m = 5 + rn[i + 14] = -(2.34647776186144e-5*std::pow(w2m1, 2.5)*((11486475.0/8.0) * + std::pow(w, 4) - 2027025.0/4.0 * w * w + 135135.0/8.0) * + std::cos(5.0 * phi)); + // l = 9, m = 6 + rn[i + 15] = 3.02928976464514e-6*std::pow(w2m1, 3)*((11486475.0/2.)*std::pow(w, 3) - + 2027025.0/2. * w) * std::cos(6.0 * phi); + // l = 9, m = 7 + rn[i + 16] = -(4.37240315267812e-7*std::pow(w2m1, 3.5)* + ((34459425.0/2.) * w * w - 2027025.0/2.) * std::cos(7.0 * phi)); + // l = 9, m = 8 + rn[i + 17] = 2.58397773170915 * w*std::pow(w2m1, 4) * std::cos(8.0 * phi); + // l = 9, m = 9 + rn[i + 18] = -(0.609049392175524 * std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); + break; + case 10: + // l = 10, m = -10 + rn[i] = 0.593627917136573 * std::pow(w2m1, 5) * std::sin(10.0 * phi); + // l = 10, m = -9 + rn[i + 1] = -(2.65478475211798 * w * std::pow(w2m1, 4.5) * std::sin(9.0 * phi)); + // l = 10, m = -8 + rn[i + 2] = 2.49953651452314e-8 * std::pow(w2m1, 4) * + ((654729075.0/2.) * w * w - 34459425.0/2.) * std::sin(8.0 * phi); + // l = 10, m = -7 + rn[i + 3] = -(1.83677671621093e-7*std::pow(w2m1, 3.5)* + ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * + std::sin(7.0 * phi)); + // l = 10, m = -6 + rn[i + 4] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - + 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::sin(6.0 * phi); + // l = 10, m = -5 + rn[i + 5] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* + ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + + (2027025.0/8.0)*w) * std::sin(5.0 * phi)); + // l = 10, m = -4 + rn[i + 6] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - + 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0)*w * w - + 45045.0/16.0) * std::sin(4.0 * phi); + // l = 10, m = -3 + rn[i + 7] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* + ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + + (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::sin(3. * phi)); + // l = 10, m = -2 + rn[i + 8] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - + 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - + 45045.0/32.0 * w * w + 3465.0/128.0) * std::sin(2. * phi); + // l = 10, m = -1 + rn[i + 9] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - + 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) - + 15015.0/32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::sin(phi)); + // l = 10, m = 0 + rn[i + 10] = 180.42578125 * std::pow(w, 10) - 427.32421875 * std::pow(w, 8) +351.9140625 + * std::pow(w, 6) - 117.3046875 * std::pow(w, 4) + 13.53515625 * w * w -0.24609375; + // l = 10, m = 1 + rn[i + 11] = -(0.134839972492648*std::sqrt(w2m1)*((230945.0/128.0)* std::pow(w, 9) - + 109395.0/32.0 * std::pow(w, 7) + (135135.0/64.0)* std::pow(w, 5) -15015.0/ + 32.0 * std::pow(w, 3) + (3465.0/128.0)*w) * std::cos(phi)); + // l = 10, m = 2 + rn[i + 12] = 0.012974982402692 * (w2m1)*((2078505.0/128.0)* std::pow(w, 8) - + 765765.0/32.0 * std::pow(w, 6) + (675675.0/64.0)* std::pow(w, 4) - + 45045.0/32.0 * w * w + 3465.0/128.0) * std::cos(2. * phi); + // l = 10, m = 3 + rn[i + 13] = -(0.00127230170115096 * std::pow(w2m1, 1.5)* + ((2078505.0/16.0)* std::pow(w, 7) - 2297295.0/16.0 * std::pow(w, 5) + + (675675.0/16.0)*std::pow(w, 3) - 45045.0/16.0 * w) * std::cos(3. * phi)); + // l = 10, m = 4 + rn[i + 14] = 0.000128521880085575 * w2m1 * w2m1*((14549535.0/16.0)* std::pow(w, 6) - + 11486475.0/16.0 * std::pow(w, 4) + (2027025.0/16.0) * w * w - + 45045.0/16.0) * std::cos(4.0 * phi); + // l = 10, m = 5 + rn[i + 15] = -(1.35473956745817e-5*std::pow(w2m1, 2.5)* + ((43648605.0/8.0)* std::pow(w, 5) - 11486475.0/4.0 * std::pow(w, 3) + + (2027025.0/8.0)*w) * std::cos(5.0 * phi)); + // l = 10, m = 6 + rn[i + 16] = 1.51464488232257e-6*std::pow(w2m1, 3)*((218243025.0/8.0)* std::pow(w, 4) - + 34459425.0/4.0 * w * w + 2027025.0/8.0) * std::cos(6.0 * phi); + // l = 10, m = 7 + rn[i + 17] = -(1.83677671621093e-7*std::pow(w2m1, 3.5) * + ((218243025.0/2.)*std::pow(w, 3) - 34459425.0/2.*w) * std::cos(7.0 * phi)); + // l = 10, m = 8 + rn[i + 18] = 2.49953651452314e-8*std::pow(w2m1, 4)* + ((654729075.0/2.)*w * w - 34459425.0/2.) * std::cos(8.0 * phi); + // l = 10, m = 9 + rn[i + 19] = -(2.65478475211798 * w*std::pow(w2m1, 4.5) * std::cos(9.0 * phi)); + // l = 10, m = 10 + rn[i + 20] = 0.593627917136573 * std::pow(w2m1, 5) * std::cos(10.0 * phi); + } } } @@ -585,7 +555,7 @@ void calc_rn_c(int n, double uvw[3], double rn[]){ // exactly pi //============================================================================== -void calc_zn_c(int n, double rho, double phi, double zn[]) { +void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // This procedure uses the modified Kintner's method for calculating Zernike // polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, // R. (2003). A comparative analysis of algorithms for fast computation of @@ -689,7 +659,7 @@ void calc_zn_c(int n, double rho, double phi, double zn[]) { // and SERPENT. //============================================================================== -void rotate_angle_c(double uvw[3], double mu, double phi) { +void rotate_angle_c(double uvw[3], const double mu, double phi) { double phi_; // azimuthal angle double sinphi; // std::sine of azimuthal angle double cosphi; // cosine of azimuthal angle @@ -738,7 +708,7 @@ void rotate_angle_c(double uvw[3], double mu, double phi) { // This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. //============================================================================== -double maxwell_spectrum_c(double T) { +double maxwell_spectrum_c(const double T) { double E_out; // Sampled Energy double r1; @@ -768,7 +738,7 @@ double maxwell_spectrum_c(double T) { // MC lectures). //============================================================================== -double watt_spectrum_c(double a, double b) { +double watt_spectrum_c(const double a, const double b) { double E_out; // Sampled Energy double w; // sampled from Maxwellian @@ -836,7 +806,8 @@ double watt_spectrum_c(double a, double b) { // The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... //============================================================================== -void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { +void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, + double factors[]) { // Factors is already pre-allocated double sqrtE; // sqrt(energy) diff --git a/src/math_functions.h b/src/math_functions.h index ab35d140a..8df72f56c 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -23,35 +23,37 @@ const double SQRT_PI = std::sqrt(PI); // distribution with a specified probability level //============================================================================== -extern "C" double normal_percentile_c(double p) __attribute__ ((const)); +extern "C" double normal_percentile_c(const double p) __attribute__ ((const)); //============================================================================== // T_PERCENTILE calculates the percentile of the Student's t distribution with // a specified probability level and number of degrees of freedom //============================================================================== -extern "C" double t_percentile_c(double p, int df) __attribute__ ((const)); +extern "C" double t_percentile_c(const double p, const int df) + __attribute__ ((const)); //============================================================================== -// CALC_PN calculates the n-th order Legendre polynomial at the value of x. +// CALC_PN calculates the n-th order Legendre polynomials at the value of x. //============================================================================== -extern "C" double calc_pn_c(int n, double x) __attribute__ ((const)); +extern "C" void calc_pn_c(const int n, const double x, double pnx[]); //============================================================================== // EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients // and the value of x //============================================================================== -extern "C" double evaluate_legendre_c(int n, double data[], double x) - __attribute__ ((const)); +extern "C" double evaluate_legendre_c(const int n, const double data[], + const double x) __attribute__ ((const)); //============================================================================== // CALC_RN calculates the n-th order spherical harmonics for a given angle -// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) +// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) for n +// all 0 <= n //============================================================================== -extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); +extern "C" void calc_rn_c(const int n, const double uvw[3], double rn[]); //============================================================================== // CALC_ZN calculates the n-th order modified Zernike polynomial moment for a @@ -60,7 +62,8 @@ extern "C" void calc_rn_c(int n, double uvw[3], double rn[]); // exactly pi //============================================================================== -extern "C" void calc_zn_c(int n, double rho, double phi, double zn[]); +extern "C" void calc_zn_c(const int n, const double rho, const double phi, + double zn[]); //============================================================================== // ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is @@ -68,7 +71,8 @@ extern "C" void calc_zn_c(int n, double rho, double phi, double zn[]); // with direct sampling rather than rejection as is done in MCNP and SERPENT. //============================================================================== -extern "C" void rotate_angle_c(double uvw[3], double mu, double phi = -10.); +extern "C" void rotate_angle_c(double uvw[3], const double mu, + double phi = -10.); //============================================================================== // MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution @@ -77,7 +81,7 @@ extern "C" void rotate_angle_c(double uvw[3], double mu, double phi = -10.); // This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. //============================================================================== -extern "C" double maxwell_spectrum_c(double T); +extern "C" double maxwell_spectrum_c(const double T); //============================================================================== // WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent @@ -88,7 +92,7 @@ extern "C" double maxwell_spectrum_c(double T); // MC lectures). //============================================================================== -extern "C" double watt_spectrum_c(double a, double b); +extern "C" double watt_spectrum_c(const double a, const double b); //============================================================================== // FADDEEVA the Faddeeva function, using Stephen Johnson's implementation @@ -103,8 +107,8 @@ extern "C" double watt_spectrum_c(double a, double b); // The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... //============================================================================== -extern "C" void broaden_wmp_polynomials_c(double E, double dopp, int n, - double factors[]); +extern "C" void broaden_wmp_polynomials_c(const double E, const double dopp, + const int n, double factors[]); } // namespace openmc #endif // MATH_FUNCTIONS_H \ No newline at end of file diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 2e1d6184c..ef8b5915c 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -7,7 +7,7 @@ module tally use dict_header, only: EMPTY use error, only: fatal_error use geometry_header - use math, only: t_percentile, calc_pn, calc_rn + use math, only: t_percentile use mesh_header, only: RegularMesh, meshes use message_passing use mgxs_header diff --git a/src/tallies/tally_filter_legendre.F90 b/src/tallies/tally_filter_legendre.F90 index b4ee7b06b..4663df651 100644 --- a/src/tallies/tally_filter_legendre.F90 +++ b/src/tallies/tally_filter_legendre.F90 @@ -51,13 +51,12 @@ contains type(TallyFilterMatch), intent(inout) :: match integer :: i - real(8) :: wgt + real(C_DOUBLE) :: wgt(this % n_bins) - ! TODO: Use recursive formula to calculate higher orders - do i = 0, this % order - wgt = calc_pn(i, p % mu) - call match % bins % push_back(i + 1) - call match % weights % push_back(wgt) + call calc_pn(this % order, p % mu, wgt) + do i = 1, this % n_bins + call match % bins % push_back(i) + call match % weights % push_back(wgt(i)) end do end subroutine get_all_bins diff --git a/src/tallies/tally_filter_sph_harm.F90 b/src/tallies/tally_filter_sph_harm.F90 index 90f04c488..4a1f43273 100644 --- a/src/tallies/tally_filter_sph_harm.F90 +++ b/src/tallies/tally_filter_sph_harm.F90 @@ -74,30 +74,32 @@ contains integer :: i, j, n integer :: num_nm - real(8) :: wgt - real(8) :: rn(2*this % order + 1) + real(C_DOUBLE) :: wgt(this % order + 1) + real(C_DOUBLE) :: rn(this % n_bins) + + ! Determine cosine term for scatter expansion if necessary + if (this % cosine == COSINE_SCATTER) then + call calc_pn(this % order, p % mu, wgt) + else + wgt = ONE + end if + + ! Find the Rn,m values + call calc_rn(this % order, p % last_uvw, rn) - ! TODO: Use recursive formula to calculate higher orders j = 0 do n = 0, this % order - ! Determine cosine term for scatter expansion if necessary - if (this % cosine == COSINE_SCATTER) then - wgt = calc_pn(n, p % mu) - else - wgt = ONE - end if - ! Calculate n-th order spherical harmonics for (u,v,w) num_nm = 2*n + 1 - call calc_rn(n, p % last_uvw, rn(1:num_nm)) ! Append matching (bin,weight) for each moment do i = 1, num_nm j = j + 1 call match % bins % push_back(j) - call match % weights % push_back(wgt * rn(i)) + call match % weights % push_back(wgt(n + 1) * rn(j)) end do end do + end subroutine get_all_bins subroutine to_statepoint(this, filter_group) diff --git a/src/tallies/tally_filter_sptl_legendre.F90 b/src/tallies/tally_filter_sptl_legendre.F90 index 83db9a864..1bfbd0e3b 100644 --- a/src/tallies/tally_filter_sptl_legendre.F90 +++ b/src/tallies/tally_filter_sptl_legendre.F90 @@ -75,20 +75,19 @@ contains type(TallyFilterMatch), intent(inout) :: match integer :: i - real(8) :: wgt - real(8) :: x ! Position on specified axis - real(8) :: x_norm ! Normalized position + real(C_DOUBLE) :: wgt(this % n_bins) + real(C_DOUBLE) :: x ! Position on specified axis + real(C_DOUBLE) :: x_norm ! Normalized position x = p % coord(1) % xyz(this % axis) if (this % min <= x .and. x <= this % max) then ! Calculate normalized position between min and max x_norm = TWO*(x - this % min)/(this % max - this % min) - ONE - ! TODO: Use recursive formula to calculate higher orders - do i = 0, this % order - wgt = calc_pn(i, x_norm) - call match % bins % push_back(i + 1) - call match % weights % push_back(wgt) + call calc_pn(this % order, x_norm, wgt) + do i = 1, this % n_bins + call match % bins % push_back(i) + call match % weights % push_back(wgt(i)) end do end if end subroutine get_all_bins diff --git a/src/tallies/tally_filter_zernike.F90 b/src/tallies/tally_filter_zernike.F90 index fa205b180..d00541613 100644 --- a/src/tallies/tally_filter_zernike.F90 +++ b/src/tallies/tally_filter_zernike.F90 @@ -61,7 +61,7 @@ contains integer :: i real(8) :: x, y, r, theta - real(8) :: zn(this % n_bins) + real(C_DOUBLE) :: zn(this % n_bins) ! Determine normalized (r,theta) positions x = p % coord(1) % xyz(1) - this % x diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index ab27437d6..d48119a37 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -39,14 +39,17 @@ def test_t_percentile(): def test_calc_pn(): max_order = 10 - test_ns = np.array([i for i in range(0, max_order + 1)]) test_xs = np.linspace(-1., 1., num=5, endpoint=True) # Reference solutions from scipy - ref_vals = [sp.special.eval_legendre(n, test_xs) for n in test_ns] + ref_vals = np.array([sp.special.eval_legendre(n, test_xs) + for n in range(0, max_order + 1)]) - test_vals = [[openmc.capi.math.calc_pn(n, x) for x in test_xs] - for n in test_ns] + test_vals = [] + for x in test_xs: + test_vals.append(openmc.capi.math.calc_pn(max_order, x).tolist()) + + test_vals = np.swapaxes(np.array(test_vals), 0, 1) assert np.allclose(ref_vals, test_vals) @@ -61,7 +64,7 @@ def test_evaluate_legendre(): ref_vals = np.polynomial.legendre.legval(test_xs, test_coeffs) # Set the coefficients back to 1s for the test values since - # evaluate legendre includes the (2l+1)/2 term + # evaluate legendre incorporates the (2l+1)/2 term on its own test_coeffs = [1. for l in range(max_order + 1)] test_vals = np.array([openmc.capi.math.evaluate_legendre(test_coeffs, x) @@ -107,9 +110,7 @@ def test_calc_rn(): ref_vals.append(ylm) test_vals = [] - for n in test_ns: - ylms = openmc.capi.math.calc_rn(n, test_uvw) - test_vals.extend(ylms.tolist()) + test_vals = openmc.capi.math.calc_rn(max_order, test_uvw) assert np.allclose(ref_vals, test_vals) From a6e75c35098f843cbf6973847749085d7842b6f2 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 13 May 2018 06:44:48 -0400 Subject: [PATCH 259/361] Cleaned up the optional parameters interface of rotate_angle --- openmc/capi/math.py | 9 ++++++--- src/math.F90 | 17 ++++++++++------- src/math_functions.cpp | 6 +++--- src/math_functions.h | 2 +- tests/unit_tests/test_math.py | 12 +++++++++++- 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index c46390bce..95e30eff3 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -1,4 +1,4 @@ -from ctypes import (c_int, c_double, POINTER) +from ctypes import (c_int, c_double, POINTER, CFUNCTYPE) import numpy as np from numpy.ctypeslib import ndpointer @@ -201,8 +201,11 @@ def rotate_angle(uvw0, mu, phi=None): uvw = np.zeros(3, dtype=np.float64) uvw0_arr = np.array(uvw0, dtype=np.float64) - _dll.rotate_angle(uvw0_arr, c_double(mu), - uvw, c_double(phi)) + if phi is None: + _dll.rotate_angle(uvw0_arr, c_double(mu), uvw, None) + else: + _dll.rotate_angle(uvw0_arr, c_double(mu), uvw, c_double(phi)) + return uvw diff --git a/src/math.F90 b/src/math.F90 index 7be3e5a6d..057ebb213 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -75,7 +75,7 @@ module math implicit none real(C_DOUBLE), intent(inout) :: uvw(3) real(C_DOUBLE), value, intent(in) :: mu - real(C_DOUBLE), value, intent(in) :: phi + real(C_DOUBLE), optional, intent(in) :: phi end subroutine rotate_angle_c_intfc function maxwell_spectrum_c_intfc(T) bind(C, name='maxwell_spectrum_c') & @@ -225,16 +225,19 @@ contains !=============================================================================== subroutine rotate_angle(uvw0, mu, uvw, phi) bind(C) - real(C_DOUBLE), intent(in) :: uvw0(3) ! directional cosine - real(C_DOUBLE), intent(in) :: mu ! cosine of angle in lab or CM - real(C_DOUBLE), intent(out) :: uvw(3) ! rotated directional cosine - real(C_DOUBLE), optional :: phi ! azimuthal angle + real(C_DOUBLE), intent(in) :: uvw0(3) ! directional cosine + real(C_DOUBLE), intent(in) :: mu ! cosine of angle in lab or CM + real(C_DOUBLE), intent(out) :: uvw(3) ! rotated directional cosine + real(C_DOUBLE), target, optional :: phi ! azimuthal angle + + real(C_DOUBLE), pointer :: phi_ptr uvw = uvw0 if (present(phi)) then - call rotate_angle_c_intfc(uvw, mu, phi) + phi_ptr => phi + call rotate_angle_c_intfc(uvw, mu, phi_ptr) else - call rotate_angle_c_intfc(uvw, mu, -10._8) + call rotate_angle_c_intfc(uvw, mu) end if end subroutine rotate_angle diff --git a/src/math_functions.cpp b/src/math_functions.cpp index c9d6fc359..78ff08b05 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -659,7 +659,7 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // and SERPENT. //============================================================================== -void rotate_angle_c(double uvw[3], const double mu, double phi) { +void rotate_angle_c(double uvw[3], const double mu, double* phi) { double phi_; // azimuthal angle double sinphi; // std::sine of azimuthal angle double cosphi; // cosine of azimuthal angle @@ -675,8 +675,8 @@ void rotate_angle_c(double uvw[3], const double mu, double phi) { w0 = uvw[2]; // Sample azimuthal angle in [0,2pi) if none provided - if (phi != -10.) { - phi_ = phi; + if (phi != NULL) { + phi_ = (*phi); } else { phi_ = 2. * PI * prn(); } diff --git a/src/math_functions.h b/src/math_functions.h index 8df72f56c..5417281ea 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -72,7 +72,7 @@ extern "C" void calc_zn_c(const int n, const double rho, const double phi, //============================================================================== extern "C" void rotate_angle_c(double uvw[3], const double mu, - double phi = -10.); + double* phi=NULL); //============================================================================== // MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index d48119a37..487d02b10 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -170,7 +170,17 @@ def test_rotate_angle(): assert np.array_equal(ref_uvw, test_uvw) - # Need to test phi=None somehow... + # Now to test phi is None + mu = 0.9 + settings = openmc.capi.settings + settings.seed = 1 + + # When seed = 1, phi will be sampled as 1.9116495709698769 + # The resultant reference is from hand-calculations given the above + ref_uvw = [0.9, 0.410813051297112, 0.1457142302040] + test_uvw = openmc.capi.math.rotate_angle(uvw0, mu) + + assert np.allclose(ref_uvw, test_uvw) def test_maxwell_spectrum(): From d7b20fb51ea27fefacfd4b4de2210a1a339e4e66 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 13 May 2018 19:21:36 -0400 Subject: [PATCH 260/361] final preps for a PR --- openmc/capi/math.py | 15 ++++----- src/math_functions.cpp | 63 ++++++----------------------------- src/math_functions.h | 19 ++++------- tests/unit_tests/test_math.py | 13 -------- 4 files changed, 23 insertions(+), 87 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 95e30eff3..e5ba5fdd6 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -1,39 +1,38 @@ -from ctypes import (c_int, c_double, POINTER, CFUNCTYPE) +from ctypes import (c_int, c_double, POINTER) import numpy as np from numpy.ctypeslib import ndpointer from . import _dll -# _dll.normal_percentile.restype = c_double -# _dll.normal_percentile.argtypes = [POINTER(c_double)] _dll.t_percentile.restype = c_double _dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] + _dll.calc_pn.restype = None _dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double), ndpointer(c_double)] + _dll.evaluate_legendre.restype = c_double _dll.evaluate_legendre.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double)] + _dll.calc_rn.restype = None _dll.calc_rn.argtypes = [POINTER(c_int), ndpointer(c_double), ndpointer(c_double)] + _dll.calc_zn.restype = None _dll.calc_zn.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double), ndpointer(c_double)] + _dll.rotate_angle.restype = None _dll.rotate_angle.argtypes = [ndpointer(c_double), POINTER(c_double), ndpointer(c_double), POINTER(c_double)] _dll.maxwell_spectrum.restype = c_double _dll.maxwell_spectrum.argtypes = [POINTER(c_double)] + _dll.watt_spectrum.restype = c_double _dll.watt_spectrum.argtypes = [POINTER(c_double), POINTER(c_double)] -# COMPLEX DATA TYPES? -# _dll.faddeeva.restype = c_double -# _dll.faddeeva.argtypes = [POINTER(c_double)] -# _dll.w_derivative.restype = c_double -# _dll.w_derivative.argtypes = [POINTER(c_double)] _dll.broaden_wmp_polynomials.restype = None _dll.broaden_wmp_polynomials.argtypes = [POINTER(c_double), POINTER(c_double), POINTER(c_int), ndpointer(c_double)] diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 78ff08b05..9cd52096d 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -2,6 +2,16 @@ namespace openmc { +//============================================================================== +// Module constants. +//============================================================================== + +// TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we +// use so for now we will reuse the Fortran constant until we are OK with +// modifying test results +extern "C" const double PI {3.1415926535898}; + +extern "C" const double SQRT_PI {std::sqrt(PI)}; //============================================================================== // NORMAL_PERCENTILE calculates the percentile of the standard normal @@ -748,59 +758,6 @@ double watt_spectrum_c(const double a, const double b) { return E_out; } -//============================================================================== -// FADDEEVA the Faddeeva function, using Stephen Johnson's implementation -//============================================================================== - -// double complex __attribute__ ((const)) faddeeva_c(double complex z) { -// double complex wv; // The resultant w(z) value -// double relerr; // Target relative error in the inner loop of MIT Faddeeva - -// // Technically, the value we want is given by the equation: -// // w(z) = I/Pi * Integrate[Exp[-t^2]/(z-t), {t, -Infinity, Infinity}] -// // as shown in Equation 63 from Hwang, R. N. "A rigorous pole -// // representation of multilevel cross sections and its practical -// // applications." Nuclear Science and Engineering 96.3 (1987): 192-209. -// // -// // The MIT Faddeeva function evaluates w(z) = exp(-z^2)erfc(-iz). These -// // two forms of the Faddeeva function are related by a transformation. -// // -// // If we call the integral form w_int, and the function form w_fun: -// // For imag(z) > 0, w_int(z) = w_fun(z) -// // For imag(z) < 0, w_int(z) = -conjg(w_fun(conjg(z))) - -// // Note that faddeeva_w will interpret zero as machine epsilon - -// relerr = 0.; -// if (cimag(z) > 0.) { -// wv = Faddeeva_w(z, relerr); -// } else { -// wv = -conj(Faddeeva_w(conj(z), relerr)); -// } - -// return wv; -// } - -// double complex w_derivative_c(double complex z, int order){ -// double complex wv; // The resultant w(z) value - -// const double complex twoi_sqrtpi = 2.0 / SQRT_PI * I; - -// switch(order) { -// case 0: -// wv = faddeeva_c(z); -// break; -// case 1: -// wv = -2. * z * faddeeva_c(z) + twoi_sqrtpi; -// break; -// default: -// wv = -2. * z * w_derivative_c(z, order - 1) - 2. * (order - 1) * -// w_derivative_c(z, order - 2); -// } - -// return wv; -// } - //============================================================================== // BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. // The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... diff --git a/src/math_functions.h b/src/math_functions.h index 5417281ea..431e9ddee 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -2,21 +2,22 @@ #define MATH_FUNCTIONS_H #include -#include -#include #include "random_lcg.h" -// #include "faddeeva/Faddeeva.h" namespace openmc { +//============================================================================== +// Module constants. +//============================================================================== + // TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we // use so for now we will reuse the Fortran constant until we are OK with // modifying test results -const double PI = 3.1415926535898; +extern "C" const double PI; -const double SQRT_PI = std::sqrt(PI); +extern "C" const double SQRT_PI; //============================================================================== // NORMAL_PERCENTILE calculates the percentile of the standard normal @@ -94,14 +95,6 @@ extern "C" double maxwell_spectrum_c(const double T); extern "C" double watt_spectrum_c(const double a, const double b); -//============================================================================== -// FADDEEVA the Faddeeva function, using Stephen Johnson's implementation -//============================================================================== - -// extern "C" double complex faddeeva_c(double complex z) __attribute__ ((const)); - -// extern "C" double complex w_derivative_c(double complex z, int order); - //============================================================================== // BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. // The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... diff --git a/tests/unit_tests/test_math.py b/tests/unit_tests/test_math.py index 487d02b10..6825f6b93 100644 --- a/tests/unit_tests/test_math.py +++ b/tests/unit_tests/test_math.py @@ -5,19 +5,6 @@ import openmc import openmc.capi -# def test_normal_percentile(): -# # normal_percentile has three branches to consider: -# # p < 0.02425; 0.02425 <= p <= 0.97575; and p > 0.97575 -# test_ps = [0.02, 0.4, 0.5, 0.6, 0.98] - -# # The reference solutions come from Scipy -# ref_zs = [sp.stats.norm.ppf(p) for p in test_ps] - -# test_zs = [openmc.capi.math.normal_percentile(p) for p in test_ps] - -# assert np.allclose(ref_zs, test_zs) - - def test_t_percentile(): # Permutations include 1 DoF, 2 DoF, and > 2 DoF # We will test 5 p-values at 3-DoF values From aaef39d710bce39ddafabad40b1c1ad011320bb2 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 14 May 2018 07:09:07 -0400 Subject: [PATCH 261/361] fixing compiler errors --- src/math_functions.cpp | 6 +++--- src/math_functions.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 9cd52096d..179c028db 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -685,7 +685,7 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { w0 = uvw[2]; // Sample azimuthal angle in [0,2pi) if none provided - if (phi != NULL) { + if (phi != nullptr) { phi_ = (*phi); } else { phi_ = 2. * PI * prn(); @@ -694,8 +694,8 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { // Precompute factors to save flops sinphi = std::sin(phi_); cosphi = std::cos(phi_); - a = std::sqrt(std::max(0., 1. - mu * mu)); - b = std::sqrt(std::max(0., 1. - w0 * w0)); + a = std::sqrt(std::fmax(0., 1. - mu * mu)); + b = std::sqrt(std::fmax(0., 1. - w0 * w0)); // Need to treat special case where sqrt(1 - w**2) is close to zero by // expanding about the v component rather than the w component diff --git a/src/math_functions.h b/src/math_functions.h index 431e9ddee..fbebd7dd7 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -73,7 +73,7 @@ extern "C" void calc_zn_c(const int n, const double rho, const double phi, //============================================================================== extern "C" void rotate_angle_c(double uvw[3], const double mu, - double* phi=NULL); + double* phi=nullptr); //============================================================================== // MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution From 235974df668d4193897ec52a90a25edd8d2fd183 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 14 May 2018 20:24:38 -0400 Subject: [PATCH 262/361] Resolving @smharpers comments, and anticipating this will fix the travis build issue --- src/math.F90 | 16 ---- src/math_functions.cpp | 199 +++++++++++++---------------------------- src/math_functions.h | 121 +++++++++++++++++-------- 3 files changed, 150 insertions(+), 186 deletions(-) diff --git a/src/math.F90 b/src/math.F90 index 057ebb213..cc5732e2d 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -95,22 +95,6 @@ module math real(C_DOUBLE) :: E_out end function watt_spectrum_c_intfc - function faddeeva_c_intfc(z) bind(C, name='faddeeva_c') result(wv) - use ISO_C_BINDING - implicit none - complex(C_DOUBLE_COMPLEX), value, intent(in) :: z - complex(C_DOUBLE_COMPLEX) :: wv - end function faddeeva_c_intfc - - function w_derivative_c_intfc(z, order) bind(C, name='w_derivative_c') & - result(wv) - use ISO_C_BINDING - implicit none - complex(C_DOUBLE_COMPLEX), value, intent(in) :: z - integer(C_INT), value, intent(in) :: order - complex(C_DOUBLE_COMPLEX) :: wv - end function w_derivative_c_intfc - subroutine broaden_wmp_polynomials_c_intfc(E, dopp, n, factors) & bind(C, name='broaden_wmp_polynomials_c') use ISO_C_BINDING diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 179c028db..df5485ce1 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -3,41 +3,29 @@ namespace openmc { //============================================================================== -// Module constants. +// Mathematical methods //============================================================================== -// TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we -// use so for now we will reuse the Fortran constant until we are OK with -// modifying test results -extern "C" const double PI {3.1415926535898}; - -extern "C" const double SQRT_PI {std::sqrt(PI)}; - -//============================================================================== -// NORMAL_PERCENTILE calculates the percentile of the standard normal -// distribution with a specified probability level -//============================================================================== - -double __attribute__ ((const)) normal_percentile_c(const double p) { - double z; - double q; - double r; - const double p_low = 0.02425; - const double a[6] = {-3.969683028665376e1, 2.209460984245205e2, - -2.759285104469687e2, 1.383577518672690e2, - -3.066479806614716e1, 2.506628277459239e0}; - const double b[5] = {-5.447609879822406e1, 1.615858368580409e2, - -1.556989798598866e2, 6.680131188771972e1, - -1.328068155288572e1}; - const double c[6] = {-7.784894002430293e-3, -3.223964580411365e-1, - -2.400758277161838, -2.549732539343734, - 4.374664141464968, 2.938163982698783}; - const double d[4] = {7.784695709041462e-3, 3.224671290700398e-1, - 2.445134137142996, 3.754408661907416}; +double normal_percentile_c(const double p) { + constexpr double p_low = 0.02425; + constexpr double a[6] = {-3.969683028665376e1, 2.209460984245205e2, + -2.759285104469687e2, 1.383577518672690e2, + -3.066479806614716e1, 2.506628277459239e0}; + constexpr double b[5] = {-5.447609879822406e1, 1.615858368580409e2, + -1.556989798598866e2, 6.680131188771972e1, + -1.328068155288572e1}; + constexpr double c[6] = {-7.784894002430293e-3, -3.223964580411365e-1, + -2.400758277161838, -2.549732539343734, + 4.374664141464968, 2.938163982698783}; + constexpr double d[4] = {7.784695709041462e-3, 3.224671290700398e-1, + 2.445134137142996, 3.754408661907416}; // The rational approximation used here is from an unpublished work at // http://home.online.no/~pjacklam/notes/invnorm/ + double z; + double q; + if (p < p_low) { // Rational approximation for lower region. @@ -47,7 +35,7 @@ double __attribute__ ((const)) normal_percentile_c(const double p) { } else if (p <= 1.0 - p_low) { // Rational approximation for central region - + double r; q = p - 0.5; r = q * q; z = (((((a[0]*r + a[1])*r + a[2])*r + a[3])*r + a[4])*r + a[5])*q / @@ -70,17 +58,9 @@ double __attribute__ ((const)) normal_percentile_c(const double p) { } -//============================================================================== -// T_PERCENTILE calculates the percentile of the Student's t distribution with -// a specified probability level and number of degrees of freedom -//============================================================================== -double __attribute__ ((const)) t_percentile_c(const double p, const int df){ +double t_percentile_c(const double p, const int df){ double t; - double n; - double k; - double z; - double z2; if (df == 1) { // For one degree of freedom, the t-distribution becomes a Cauchy @@ -98,7 +78,10 @@ double __attribute__ ((const)) t_percentile_c(const double p, const int df){ // modification of the Fisher-Cornish approximation for the student t // percentiles," Communication in Statistics - Simulation and Computation, // 16 (4), pp. 1123-1132 (1987). - + double n; + double k; + double z; + double z2; n = static_cast(df); k = 1. / (n - 2.); z = normal_percentile_c(p); @@ -111,61 +94,42 @@ double __attribute__ ((const)) t_percentile_c(const double p, const int df){ return t; } -//============================================================================== -// CALC_PN calculates the n-th order Legendre polynomials at the value of x. -//============================================================================== void calc_pn_c(const int n, const double x, double pnx[]) { - int l; - pnx[0] = 1.; if (n >= 1) { pnx[1] = x; } // Use recursion relation to build the higher orders - for (l = 1; l < n; l ++) { + for (int l = 1; l < n; l ++) { pnx[l + 1] = (static_cast(2 * l + 1) * x * pnx[l] - static_cast(l) * pnx[l - 1]) / (static_cast(l + 1)); } } -//============================================================================== -// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients -// and the value of x -//============================================================================== -double __attribute__ ((const)) evaluate_legendre_c(const int n, +double evaluate_legendre_c(const int n, const double data[], const double x) { double val; - int l; - double* pnx = new double[n + 1]; + double pnx[n + 1]; val = 0.0; calc_pn_c(n, x, pnx); - for (l = 0; l <= n; l++) { + for (int l = 0; l <= n; l++) { val += (static_cast(l) + 0.5) * data[l] * pnx[l]; } - delete[] pnx; return val; } -//============================================================================== -// CALC_RN calculates the n-th order spherical harmonics for a given angle -// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) for n -// all 0 <= n -//============================================================================== void calc_rn_c(const int n, const double uvw[3], double rn[]){ + // rn[] is assumed to have already been allocated to the correct size + double phi; double w; - double w2m1; - int i; - int l; - - // rn[] is assumed to have already been allocated to the correct size // Store the cosine of the polar angle and the azimuthal angle w = uvw[2]; @@ -176,12 +140,14 @@ void calc_rn_c(const int n, const double uvw[3], double rn[]){ } // Store the shorthand of 1-w * w + double w2m1; w2m1 = 1. - w * w; // Now evaluate the spherical harmonics function rn[0] = 1.; + int i; i = 0; - for (l = 1; l <= n; l++) { + for (int l = 1; l <= n; l++) { // Set the index to the start of this order i += 2 * (l - 1) + 1; @@ -558,33 +524,12 @@ void calc_rn_c(const int n, const double uvw[3], double rn[]){ } } -//============================================================================== -// CALC_ZN calculates the n-th order modified Zernike polynomial moment for a -// given angle (rho, theta) location in the unit disk. The normalization of the -// polynomials is such tha the integral of Z_pq*Z_pq over the unit disk is -// exactly pi -//============================================================================== void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // This procedure uses the modified Kintner's method for calculating Zernike // polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, // R. (2003). A comparative analysis of algorithms for fast computation of // Zernike moments. Pattern Recognition, 36(3), 731-742. - double sin_phi; // Cosine of phi - double cos_phi; // Sine of phi - double sin_phi_vec[n + 1]; // Sin[n * phi] - double cos_phi_vec[n + 1]; // Cos[n * phi] - double zn_mat[n + 1][n + 1]; // Matrix forms of the coefficients which are - // easier to work with - // Variables for R_m_n calculation - double k1; - double k2; - double k3; - double k4; - // Loop counters - int i; - int p; - int q; // n == radial degree // m == azimuthal frequency @@ -597,41 +542,50 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // sin(nx) = 2 cos(x) sin((n-1)x) - sin((n-2)x) // cos(nx) = 2 cos(x) cos((n-1)x) - cos((n-2)x) + double sin_phi; + double cos_phi; sin_phi = std::sin(phi); cos_phi = std::cos(phi); + double sin_phi_vec[n + 1]; // Sin[n * phi] + double cos_phi_vec[n + 1]; // Cos[n * phi] sin_phi_vec[0] = 1.0; cos_phi_vec[0] = 1.0; - sin_phi_vec[1] = 2.0 * cos_phi; cos_phi_vec[1] = cos_phi; - for (i = 2; i <= n; i++) { + for (int i = 2; i <= n; i++) { sin_phi_vec[i] = 2. * cos_phi * sin_phi_vec[i - 1] - sin_phi_vec[i - 2]; cos_phi_vec[i] = 2. * cos_phi * cos_phi_vec[i - 1] - cos_phi_vec[i - 2]; } - for (i = 0; i <= n; i++) { + for (int i = 0; i <= n; i++) { sin_phi_vec[i] *= sin_phi; } // =========================================================================== // Calculate R_pq(rho) + double zn_mat[n + 1][n + 1]; // Matrix forms of the coefficients which are + // easier to work with // Fill the main diagonal first (Eq 3.9 in Chong) - for (p = 0; p <= n; p++) { + for (int p = 0; p <= n; p++) { zn_mat[p][p] = std::pow(rho, p); } // Fill the 2nd diagonal (Eq 3.10 in Chong) - for (q = 0; q <= n - 2; q++) { + for (int q = 0; q <= n - 2; q++) { zn_mat[q][q+2] = (q + 2) * zn_mat[q+2][q+2] - (q + 1) * zn_mat[q][q]; } // Fill in the rest of the values using the original results (Eq. 3.8 in Chong) - for (p = 4; p <= n; p++) { + double k1; + double k2; + double k3; + double k4; + for (int p = 4; p <= n; p++) { k2 = static_cast (2 * p * (p - 1) * (p - 2)); - for (q = p - 4; q >= 0; q -= 2) { + for (int q = p - 4; q >= 0; q -= 2) { k1 = static_cast((p + q) * (p - q) * (p - 2)) / 2.; k3 = static_cast(-q * q * (p - 1) - p * (p - 1) * (p - 2)); k4 = static_cast(-p * (p + q - 2) * (p - q - 2)) / 2.; @@ -646,9 +600,10 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // Note that the cos and sin vectors are offset by one // sin_phi_vec = [sin(x), sin(2x), sin(3x) ...] // cos_phi_vec = [1.0, cos(x), cos(2x)... ] + int i; i = 0; - for (p = 0; p <= n; p++) { - for (q = -p; q <= p; q += 2) { + for (int p = 0; p <= n; p++) { + for (int q = -p; q <= p; q += 2) { if (q < 0) { zn[i] = zn_mat[std::abs(q)][p] * sin_phi_vec[std::abs(q) - 1]; } else if (q == 0) { @@ -662,29 +617,19 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { } -//============================================================================== -// ROTATE_ANGLE rotates direction std::cosines through a polar angle whose -// cosine is mu and through an azimuthal angle sampled uniformly. Note that -// this is done with direct sampling rather than rejection as is done in MCNP -// and SERPENT. -//============================================================================== void rotate_angle_c(double uvw[3], const double mu, double* phi) { - double phi_; // azimuthal angle - double sinphi; // std::sine of azimuthal angle - double cosphi; // cosine of azimuthal angle - double a; // sqrt(1 - mu^2) - double b; // sqrt(1 - w^2) - double u0; // original std::cosine in x direction - double v0; // original std::cosine in y direction - double w0; // original std::cosine in z direction + // Copy original directional cosines + double u0; // original cosine in x direction + double v0; // original cosine in y direction + double w0; // original cosine in z direction - // Copy original directional std::cosines u0 = uvw[0]; v0 = uvw[1]; w0 = uvw[2]; // Sample azimuthal angle in [0,2pi) if none provided + double phi_; if (phi != nullptr) { phi_ = (*phi); } else { @@ -692,6 +637,10 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { } // Precompute factors to save flops + double sinphi; // sine of azimuthal angle + double cosphi; // cosine of azimuthal angle + double a; // sqrt(1 - mu^2) + double b; // sqrt(1 - w^2) sinphi = std::sin(phi_); cosphi = std::cos(phi_); a = std::sqrt(std::fmax(0., 1. - mu * mu)); @@ -711,42 +660,27 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { } } -//============================================================================== -// MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution -// based on a direct sampling scheme. The probability distribution function for -// a Maxwellian is given as p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). -// This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. -//============================================================================== double maxwell_spectrum_c(const double T) { - double E_out; // Sampled Energy - double r1; double r2; double r3; // random numbers - double c; // cosine of pi/2*r3 r1 = prn(); r2 = prn(); r3 = prn(); // determine cosine of pi/2*r + double c; c = std::cos(PI / 2. * r3); // determine outgoing energy + double E_out; // Sampled Energy E_out = -T * (std::log(r1) + std::log(r2) * c * c); return E_out; } -//============================================================================== -// WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent -// fission spectrum. Although fitted parameters exist for many nuclides, -// generally the continuous tabular distributions (LAW 4) should be used in -// lieu of the Watt spectrum. This direct sampling scheme is an unpublished -// scheme based on the original Watt spectrum derivation (See F. Brown's -// MC lectures). -//============================================================================== double watt_spectrum_c(const double a, const double b) { double E_out; // Sampled Energy @@ -758,10 +692,6 @@ double watt_spectrum_c(const double a, const double b) { return E_out; } -//============================================================================== -// BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. -// The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... -//============================================================================== void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, double factors[]) { @@ -773,8 +703,6 @@ void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, double quarter_inv_dopp4; // 0.25 / dopp**4 double erf_beta; // error function of beta double exp_m_beta2; // exp(-beta**2) - int i; - double ip1_dbl; sqrtE = std::sqrt(E); beta = sqrtE * dopp; @@ -800,7 +728,8 @@ void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, (beta * SQRT_PI); // Perform recursive broadening of high order components - for (i = 0; i < n - 3; i++) { + double ip1_dbl; + for (int i = 0; i < n - 3; i++) { ip1_dbl = static_cast(i + 1); if (i != 0) { factors[i + 3] = -factors[i - 1] * (ip1_dbl - 1.) * ip1_dbl * diff --git a/src/math_functions.h b/src/math_functions.h index fbebd7dd7..b55715717 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -2,6 +2,7 @@ #define MATH_FUNCTIONS_H #include +#include #include "random_lcg.h" @@ -15,89 +16,139 @@ namespace openmc { // TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we // use so for now we will reuse the Fortran constant until we are OK with // modifying test results -extern "C" const double PI; +extern "C" constexpr double PI {3.1415926535898}; -extern "C" const double SQRT_PI; +extern "C" constexpr double SQRT_PI {std::sqrt(PI)}; //============================================================================== -// NORMAL_PERCENTILE calculates the percentile of the standard normal -// distribution with a specified probability level +//! Calculate the percentile of the standard normal distribution with a +//! specified probability level. +//! +//! @param p The probability level +//! @return The requested percentile //============================================================================== -extern "C" double normal_percentile_c(const double p) __attribute__ ((const)); +extern "C" double normal_percentile_c(const double p); //============================================================================== -// T_PERCENTILE calculates the percentile of the Student's t distribution with -// a specified probability level and number of degrees of freedom +//! Calculate the percentile of the Student's t distribution with a specified +//! probability level and number of degrees of freedom. +//! +//! @param p The probability level +//! @param df The degrees of freedom +//! @return The requested percentile //============================================================================== -extern "C" double t_percentile_c(const double p, const int df) - __attribute__ ((const)); +extern "C" double t_percentile_c(const double p, const int df); //============================================================================== -// CALC_PN calculates the n-th order Legendre polynomials at the value of x. +//! Calculate the n-th order Legendre polynomials at the value of x. +//! +//! @param n The maximum order requested +//! @param x The value to evaluate at; x is expected to be within [-1,1] +//! @param pnx The requested Legendre polynomials of order 0 to n (inclusive) +//! evaluated at x. //============================================================================== extern "C" void calc_pn_c(const int n, const double x, double pnx[]); //============================================================================== -// EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients -// and the value of x +//! Find the value of f(x) given a set of Legendre coefficients and the value +//! of x. +//! +//! @param n The maximum order of the expansion +//! @param data The polynomial expansion coefficient data; without the (2l+1)/2 +//! factor. +//! @param x The value to evaluate at; x is expected to be within [-1,1] +//! @return The requested Legendre polynomials of order 0 to n (inclusive) +//! evaluated at x //============================================================================== extern "C" double evaluate_legendre_c(const int n, const double data[], - const double x) __attribute__ ((const)); + const double x); //============================================================================== -// CALC_RN calculates the n-th order spherical harmonics for a given angle -// (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) for n -// all 0 <= n +//! Calculate the n-th order real spherical harmonics for a given angle (in +//! terms of (u,v,w)) for all 0<=n and -m<=n<=n. +//! +//! @param n The maximum order requested +//! @param uvw[3] The direction the harmonics are requested at +//! @param rn The requested harmonics of order 0 to n (inclusive) +//! evaluated at uvw. //============================================================================== extern "C" void calc_rn_c(const int n, const double uvw[3], double rn[]); //============================================================================== -// CALC_ZN calculates the n-th order modified Zernike polynomial moment for a -// given angle (rho, theta) location in the unit disk. The normalization of the -// polynomials is such tha the integral of Z_pq*Z_pq over the unit disk is -// exactly pi +//! Calculate the n-th order modified Zernike polynomial moment for a given +//! angle (rho, theta) location on the unit disk. +//! +//! The normalization of the polynomials is such that the integral of Z_pq^2 +//! over the unit disk is exactly pi +//! +//! @param n The maximum order requested +//! @param rho The radial parameter to specify location on the unit disk +//! @param phi The angle parameter to specify location on the unit disk +//! @param zn The requested moments of order 0 to n (inclusive) +//! evaluated at rho and phi. //============================================================================== extern "C" void calc_zn_c(const int n, const double rho, const double phi, double zn[]); //============================================================================== -// ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is -// mu and through an azimuthal angle sampled uniformly. Note that this is done -// with direct sampling rather than rejection as is done in MCNP and SERPENT. +//! Rotate the direction cosines through a polar angle whose cosine is mu and +//! through an azimuthal angle sampled uniformly. +//! +//! This is done with direct sampling rather than rejection sampling as is done +//! in MCNP and Serpent. +//! +//! @param uvw[3] The initial, and final, direction vector +//! @param mu The cosine of angle in lab or CM +//! @param phi The azimuthal angle; defaults to a randomly chosen angle //============================================================================== extern "C" void rotate_angle_c(double uvw[3], const double mu, double* phi=nullptr); //============================================================================== -// MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution -// based on a direct sampling scheme. The probability distribution function for -// a Maxwellian is given as p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). -// This PDF can be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. +//! Samples an energy from the Maxwell fission distribution based on a direct +//! sampling scheme. +//! +//! The probability distribution function for a Maxwellian is given as +//! p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). This PDF can be sampled using +//! rule C64 in the Monte Carlo Sampler LA-9721-MS. +//! +//! @param T The tabulated function of the incoming energy +//! @result The sampled outgoing energy //============================================================================== extern "C" double maxwell_spectrum_c(const double T); //============================================================================== -// WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent -// fission spectrum. Although fitted parameters exist for many nuclides, -// generally the continuous tabular distributions (LAW 4) should be used in -// lieu of the Watt spectrum. This direct sampling scheme is an unpublished -// scheme based on the original Watt spectrum derivation (See F. Brown's -// MC lectures). +//! Samples an energy from a Watt energy-dependent fission distribution. +//! +//! Although fitted parameters exist for many nuclides, generally the +//! continuous tabular distributions (LAW 4) should be used in lieu of the Watt +//! spectrum. This direct sampling scheme is an unpublished scheme based on the +//! original Watt spectrum derivation (See F. Brown's MC lectures). +//! +//! @param a Watt parameter a +//! @param b Watt parameter b +//! @result The sampled outgoing energy //============================================================================== extern "C" double watt_spectrum_c(const double a, const double b); //============================================================================== -// BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. -// The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E) ... +//! Doppler broadens the windowed multipole curvefit. +//! +//! The curvefit is a polynomial of the form a/E + b/sqrt(E) + c + d sqrt(E)... +//! +//! @param E The energy to evaluate the broadening at +//! @param dopp sqrt(atomic weight ratio / kT) with kT given in eV +//! @param n The number of components to the polynomial +//! @param factors The output leading coefficient //============================================================================== extern "C" void broaden_wmp_polynomials_c(const double E, const double dopp, From 4c5bdd443871b4a2a5a88abd4a1dddf1242cc10b Mon Sep 17 00:00:00 2001 From: Zhuoran Han Date: Wed, 16 May 2018 14:54:24 -0400 Subject: [PATCH 263/361] Checkvalue bug --- openmc/checkvalue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index 3b334319f..4a82983d9 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -265,7 +265,7 @@ def check_filetype_version(obj, expected_type, expected_version): if this_version[0] != expected_version: raise IOError('{} file has a version of {} which is not ' 'consistent with the version expected by OpenMC, {}' - .format(this_filetype, '.'.join(this_version), + .format(this_filetype, '.'.join(str(v) for v in this_version), expected_version)) except AttributeError: raise IOError('Could not read {} file. This most likely means the {} ' From 873523072dc6c89b13bc1d159bc9351f6df1c1d1 Mon Sep 17 00:00:00 2001 From: Zhuoran Han Date: Wed, 16 May 2018 15:43:10 -0400 Subject: [PATCH 264/361] Split the lines for code format. --- openmc/checkvalue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index 4a82983d9..cbeac9c37 100644 --- a/openmc/checkvalue.py +++ b/openmc/checkvalue.py @@ -265,8 +265,9 @@ def check_filetype_version(obj, expected_type, expected_version): if this_version[0] != expected_version: raise IOError('{} file has a version of {} which is not ' 'consistent with the version expected by OpenMC, {}' - .format(this_filetype, '.'.join(str(v) for v in this_version), - expected_version)) + .format(this_filetype, + '.'.join(str(v) for v in this_version), + expected_version)) except AttributeError: raise IOError('Could not read {} file. This most likely means the {} ' 'file was produced by a different version of OpenMC than ' From f223c2738feed8ac333a09acdc02cb9b8b6d0729 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 16 May 2018 18:17:23 -0400 Subject: [PATCH 265/361] Swap RectLattice distribcell ordering --- openmc/deplete/results_list.py | 12 ++++++------ openmc/lattice.py | 9 +++++++-- src/geometry.F90 | 16 ++++++++-------- src/tallies/tally_filter_distribcell.F90 | 4 ++-- .../asymmetric_lattice/results_true.dat | 2 +- .../distribmat/inputs_true.dat | 2 +- .../distribmat/results_true.dat | 2 +- tests/regression_tests/distribmat/test.py | 2 +- .../case-1/results_true.dat | 4 ++-- .../case-3/results_true.dat | 2 +- .../multipole/inputs_true.dat | 2 +- .../multipole/results_true.dat | 2 +- tests/regression_tests/multipole/test.py | 2 +- .../tally_aggregation/results_true.dat | 2 +- tests/regression_tests/test_deplete_full.py | 1 + tests/regression_tests/test_reference.h5 | Bin 162320 -> 160024 bytes tests/unit_tests/test_deplete_resultslist.py | 12 ++++++------ 17 files changed, 41 insertions(+), 35 deletions(-) diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py index d3d45e955..58e4b66dc 100644 --- a/openmc/deplete/results_list.py +++ b/openmc/deplete/results_list.py @@ -43,8 +43,8 @@ class ResultsList(list): Total number of atoms for specified nuclide """ - time = np.empty_like(self) - concentration = np.empty_like(self) + time = np.empty_like(self, dtype=float) + concentration = np.empty_like(self, dtype=float) # Evaluate value in each region for i, result in enumerate(self): @@ -73,8 +73,8 @@ class ResultsList(list): Array of reaction rates """ - time = np.empty_like(self) - rate = np.empty_like(self) + time = np.empty_like(self, dtype=float) + rate = np.empty_like(self, dtype=float) # Evaluate value in each region for i, result in enumerate(self): @@ -94,8 +94,8 @@ class ResultsList(list): k-eigenvalue at each time """ - time = np.empty_like(self) - eigenvalue = np.empty_like(self) + time = np.empty_like(self, dtype=float) + eigenvalue = np.empty_like(self, dtype=float) # Get time/eigenvalue at each point for i, result in enumerate(self): diff --git a/openmc/lattice.py b/openmc/lattice.py index 86cfd5c41..ef33247fc 100644 --- a/openmc/lattice.py +++ b/openmc/lattice.py @@ -546,10 +546,15 @@ class RectLattice(Lattice): """ if self.ndim == 2: nx, ny = self.shape - return np.broadcast(*np.ogrid[:nx, :ny]) + for iy in range(ny): + for ix in range(nx): + yield (ix, iy) else: nx, ny, nz = self.shape - return np.broadcast(*np.ogrid[:nx, :ny, :nz]) + for iz in range(nz): + for iy in range(ny): + for ix in range(nx): + yield (ix, iy, iz) @property def lower_left(self): diff --git a/src/geometry.F90 b/src/geometry.F90 index e747b619a..651005631 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -882,9 +882,9 @@ contains type is (RectLattice) ! Loop over lattice coordinates - do j = 1, lat % n_cells(1) + do m = 1, lat % n_cells(3) do k = 1, lat % n_cells(2) - do m = 1, lat % n_cells(3) + do j = 1, lat % n_cells(1) lat % offset(map, j, k, m) = offset next_univ => universes(lat % universes(j, k, m)) offset = offset + & @@ -999,9 +999,9 @@ contains type is (RectLattice) ! Loop over lattice coordinates - do j = 1, lat % n_cells(1) + do m = 1, lat % n_cells(3) do k = 1, lat % n_cells(2) - do m = 1, lat % n_cells(3) + do j = 1, lat % n_cells(1) next_univ => universes(lat % universes(j, k, m)) ! Found target - stop since target cannot contain itself @@ -1091,9 +1091,9 @@ contains type is (RectLattice) ! Loop over lattice coordinates - do j = 1, lat % n_cells(1) + do m = 1, lat % n_cells(3) do k = 1, lat % n_cells(2) - do m = 1, lat % n_cells(3) + do j = 1, lat % n_cells(1) call count_instance(universes(lat % universes(j, k, m))) end do end do @@ -1166,9 +1166,9 @@ contains type is (RectLattice) ! Loop over lattice coordinates - do j = 1, lat % n_cells(1) + do m = 1, lat % n_cells(3) do k = 1, lat % n_cells(2) - do m = 1, lat % n_cells(3) + do j = 1, lat % n_cells(1) next_univ => universes(lat % universes(j, k, m)) levels_below = max(levels_below, maximum_levels(next_univ)) end do diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index 6c42161ca..ddf7f4d92 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -278,9 +278,9 @@ contains old_k = 1 ! Loop over lattice coordinates - do k = 1, n_x + do m = 1, n_z do l = 1, n_y - do m = 1, n_z + do k = 1, n_x if (target_offset >= lat % offset(map, k, l, m) + offset) then if (k == n_x .and. l == n_y .and. m == n_z) then diff --git a/tests/regression_tests/asymmetric_lattice/results_true.dat b/tests/regression_tests/asymmetric_lattice/results_true.dat index ef7608f2f..f60cfa64f 100644 --- a/tests/regression_tests/asymmetric_lattice/results_true.dat +++ b/tests/regression_tests/asymmetric_lattice/results_true.dat @@ -1 +1 @@ -3b76b468b9c0e7df6508189b75748a1b7b4f2b37486396749e1a15e536526336f70b60bb207095c39ecbd46822e8c8705ea81184a3c8546e7da09bb905d74637 \ No newline at end of file +bd3fd10177bc0c7b8542f15228decf8608de013872d201e6ae885cf9b5b6d7516ddb32044ee2f7fd5b207e7d37d5ce3f03e347a2e850953bba4fc26b150c89d2 \ No newline at end of file diff --git a/tests/regression_tests/distribmat/inputs_true.dat b/tests/regression_tests/distribmat/inputs_true.dat index 39e611e16..a1c1e8311 100644 --- a/tests/regression_tests/distribmat/inputs_true.dat +++ b/tests/regression_tests/distribmat/inputs_true.dat @@ -1,7 +1,7 @@ - + diff --git a/tests/regression_tests/distribmat/results_true.dat b/tests/regression_tests/distribmat/results_true.dat index cd6aa95d4..e0143f0ff 100644 --- a/tests/regression_tests/distribmat/results_true.dat +++ b/tests/regression_tests/distribmat/results_true.dat @@ -3,7 +3,7 @@ k-combined: Cell ID = 11 Name = - Fill = [2, 3, None, 2] + Fill = [2, None, 3, 2] Region = -9 Rotation = None Translation = None diff --git a/tests/regression_tests/distribmat/test.py b/tests/regression_tests/distribmat/test.py index de1b77877..cb598b9d3 100644 --- a/tests/regression_tests/distribmat/test.py +++ b/tests/regression_tests/distribmat/test.py @@ -34,7 +34,7 @@ class DistribmatTestHarness(PyAPITestHarness): r0 = openmc.ZCylinder(R=0.3) c11 = openmc.Cell(cell_id=11, region=-r0) - c11.fill = [dense_fuel, light_fuel, None, dense_fuel] + c11.fill = [dense_fuel, None, light_fuel, dense_fuel] c12 = openmc.Cell(cell_id=12, region=+r0, fill=moderator) fuel_univ = openmc.Universe(universe_id=11, cells=[c11, c12]) diff --git a/tests/regression_tests/filter_distribcell/case-1/results_true.dat b/tests/regression_tests/filter_distribcell/case-1/results_true.dat index a824d7fce..2d9835b1a 100644 --- a/tests/regression_tests/filter_distribcell/case-1/results_true.dat +++ b/tests/regression_tests/filter_distribcell/case-1/results_true.dat @@ -3,10 +3,10 @@ k-combined: tally 1: 1.548980E-02 2.399339E-04 -1.278781E-02 -1.635280E-04 1.426319E-02 2.034385E-04 +1.278781E-02 +1.635280E-04 1.018927E-02 1.038213E-04 tally 2: diff --git a/tests/regression_tests/filter_distribcell/case-3/results_true.dat b/tests/regression_tests/filter_distribcell/case-3/results_true.dat index 8eb7c2b60..acd74781d 100644 --- a/tests/regression_tests/filter_distribcell/case-3/results_true.dat +++ b/tests/regression_tests/filter_distribcell/case-3/results_true.dat @@ -1 +1 @@ -386147796cf64c908002a81bc47af6763a14811ba6c457ca790653ceb8ef1c6b0376b94783f9746baf7389f18321b788c72758ec56ffd05fa3146139e6e53308 \ No newline at end of file +a1f6ccf0bef1075ffc12e7fa058a44c83360801ba6e2c76f00e1bd7e9543fe1832777b122321dcb1cfb5fc1165ce84f711a65ca83903dd57cd09652e003daf62 \ No newline at end of file diff --git a/tests/regression_tests/multipole/inputs_true.dat b/tests/regression_tests/multipole/inputs_true.dat index d1ef3e00a..3bbcc2024 100644 --- a/tests/regression_tests/multipole/inputs_true.dat +++ b/tests/regression_tests/multipole/inputs_true.dat @@ -1,7 +1,7 @@ - + diff --git a/tests/regression_tests/multipole/results_true.dat b/tests/regression_tests/multipole/results_true.dat index 0ab4191d8..d734e67bd 100644 --- a/tests/regression_tests/multipole/results_true.dat +++ b/tests/regression_tests/multipole/results_true.dat @@ -37,5 +37,5 @@ Cell Fill = Material 2 Region = -1 Rotation = None - Temperature = [ 500. 0. 700. 800.] + Temperature = [ 500. 700. 0. 800.] Translation = None diff --git a/tests/regression_tests/multipole/test.py b/tests/regression_tests/multipole/test.py index 5c79d4d6b..ef797a1bc 100644 --- a/tests/regression_tests/multipole/test.py +++ b/tests/regression_tests/multipole/test.py @@ -29,7 +29,7 @@ def make_model(): r0 = openmc.ZCylinder(R=0.3) c11 = openmc.Cell(cell_id=11, fill=dense_fuel, region=-r0) - c11.temperature = [500, 0, 700, 800] + c11.temperature = [500, 700, 0, 800] c12 = openmc.Cell(cell_id=12, fill=moderator, region=+r0) fuel_univ = openmc.Universe(universe_id=11, cells=(c11, c12)) diff --git a/tests/regression_tests/tally_aggregation/results_true.dat b/tests/regression_tests/tally_aggregation/results_true.dat index ee763b3ba..0f0a3d60a 100644 --- a/tests/regression_tests/tally_aggregation/results_true.dat +++ b/tests/regression_tests/tally_aggregation/results_true.dat @@ -1 +1 @@ -eb2002dd2f3016154e7014501629227eb2e5b2a9655b5c0cb3b9d4d681f918fae4197bf4756fe9865c8d34f392b981b287a15e9b2fab5a46b2a5b8a33a8ae770 \ No newline at end of file +8294c7481b3433a4beb25541ae72c1ea915def0c0d0f393f7585371877187f4a2a25e70bb602232e2bc1e877e78db0e9384591e5c71575659f95abb10fae0af3 \ No newline at end of file diff --git a/tests/regression_tests/test_deplete_full.py b/tests/regression_tests/test_deplete_full.py index 19e2c7664..58e6ddac4 100644 --- a/tests/regression_tests/test_deplete_full.py +++ b/tests/regression_tests/test_deplete_full.py @@ -22,6 +22,7 @@ def test_full(run_in_tmpdir): This test runs a complete OpenMC simulation and tests the outputs. It will take a while. + """ n_rings = 2 diff --git a/tests/regression_tests/test_reference.h5 b/tests/regression_tests/test_reference.h5 index 5323a9a904e0fce1f806fbc49ad90324b4fe1aa9..bae9a1adfefbce2d9124bfe6c9be1b7b2e5d3fa9 100644 GIT binary patch literal 160024 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 L)HDBoUGx6{a^}}A literal 162320 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 Date: Wed, 16 May 2018 20:36:45 -0400 Subject: [PATCH 266/361] Remove reference brace-initialization for GCC 4.8 --- src/geometry_aux.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 906845cb9..911e53c28 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -127,7 +127,7 @@ extern "C" void count_cell_instances(int32_t univ_indx) { for (int32_t cell_indx : universes_c[univ_indx]->cells) { - Cell &c {*cells_c[cell_indx]}; + Cell &c = *cells_c[cell_indx]; ++c.n_instances; if (c.type == FILL_UNIVERSE) { @@ -136,7 +136,7 @@ count_cell_instances(int32_t univ_indx) } else if (c.type == FILL_LATTICE) { // This cell contains a lattice. Recurse into the lattice universes. - Lattice &lat {*lattices_c[c.fill-1]}; // TODO: off-by-one + Lattice &lat = *lattices_c[c.fill-1]; // TODO: off-by-one for (auto it = lat.begin(); it != lat.end(); ++it) { count_cell_instances(*it); } @@ -161,14 +161,14 @@ count_universe_instances(int32_t search_univ, int32_t target_univ_id) int count {0}; for (int32_t cell_indx : universes_c[search_univ]->cells) { - Cell &c {*cells_c[cell_indx]}; + Cell &c = *cells_c[cell_indx]; if (c.type == FILL_UNIVERSE) { int32_t next_univ = c.fill - 1; // TODO: off-by-one count += count_universe_instances(next_univ, target_univ_id); } else if (c.type == FILL_LATTICE) { - Lattice &lat {*lattices_c[c.fill - 1]}; //TODO: off-by-one + Lattice &lat = *lattices_c[c.fill - 1]; //TODO: off-by-one for (auto it = lat.begin(); it != lat.end(); ++it) { int32_t next_univ = *it; count += count_universe_instances(next_univ, target_univ_id); From 38c13994bc98589ed1e4521fb0e870bb908db993 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Wed, 16 May 2018 21:25:29 -0400 Subject: [PATCH 267/361] Implementing comments from @paulromano, @smharper, and @giudgiud. A few remain open, will close in a day or two after some more GitHub discussion --- CMakeLists.txt | 1 - openmc/capi/math.py | 20 +----- src/math.F90 | 7 +- src/math_functions.cpp | 158 ++++++++++++++--------------------------- src/math_functions.h | 36 +++++----- 5 files changed, 73 insertions(+), 149 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c34dd3770..dc5558de1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -435,7 +435,6 @@ set(LIBOPENMC_CXX_SRC src/initialize.cpp src/finalize.cpp src/hdf5_interface.cpp - src/math_functions.h src/math_functions.cpp src/message_passing.cpp src/plot.cpp diff --git a/openmc/capi/math.py b/openmc/capi/math.py index e5ba5fdd6..3b0b723b3 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -5,6 +5,7 @@ from numpy.ctypeslib import ndpointer from . import _dll + _dll.t_percentile.restype = c_double _dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] @@ -38,25 +39,6 @@ _dll.broaden_wmp_polynomials.argtypes = [POINTER(c_double), POINTER(c_double), POINTER(c_int), ndpointer(c_double)] -def normal_percentile(p): - """ Calculate the percentile of the standard normal distribution with a - specified probability level - - Parameters - ---------- - p : float - Probability level - - Returns - ------- - float - Corresponding z-value - - """ - - return _dll.normal_percentile(c_double(p)) - - def t_percentile(p, df): """ Calculate the percentile of the Student's t distribution with a specified probability level and number of degrees of freedom diff --git a/src/math.F90 b/src/math.F90 index cc5732e2d..05942a86e 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -217,12 +217,7 @@ contains real(C_DOUBLE), pointer :: phi_ptr uvw = uvw0 - if (present(phi)) then - phi_ptr => phi - call rotate_angle_c_intfc(uvw, mu, phi_ptr) - else - call rotate_angle_c_intfc(uvw, mu) - end if + call rotate_angle_c_intfc(uvw, mu, phi) end subroutine rotate_angle diff --git a/src/math_functions.cpp b/src/math_functions.cpp index df5485ce1..159e0a4e2 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -6,7 +6,7 @@ namespace openmc { // Mathematical methods //============================================================================== -double normal_percentile_c(const double p) { +double normal_percentile_c(double p) { constexpr double p_low = 0.02425; constexpr double a[6] = {-3.969683028665376e1, 2.209460984245205e2, -2.759285104469687e2, 1.383577518672690e2, @@ -35,9 +35,8 @@ double normal_percentile_c(const double p) { } else if (p <= 1.0 - p_low) { // Rational approximation for central region - double r; q = p - 0.5; - r = q * q; + double r = q * q; z = (((((a[0]*r + a[1])*r + a[2])*r + a[3])*r + a[4])*r + a[5])*q / (((((b[0]*r + b[1])*r + b[2])*r + b[3])*r + b[4])*r + 1.0); @@ -59,7 +58,7 @@ double normal_percentile_c(const double p) { } -double t_percentile_c(const double p, const int df){ +double t_percentile_c(double p, int df){ double t; if (df == 1) { @@ -78,14 +77,10 @@ double t_percentile_c(const double p, const int df){ // modification of the Fisher-Cornish approximation for the student t // percentiles," Communication in Statistics - Simulation and Computation, // 16 (4), pp. 1123-1132 (1987). - double n; - double k; - double z; - double z2; - n = static_cast(df); - k = 1. / (n - 2.); - z = normal_percentile_c(p); - z2 = z * z; + double n = df; + double k = 1. / (n - 2.); + double z = normal_percentile_c(p); + double z2 = z * z; t = std::sqrt(n * k) * (z + (z2 - 3.) * z * k / 4. + ((5. * z2 - 56.) * z2 + 75.) * z * k * k / 96. + (((z2 - 27.) * 3. * z2 + 417.) * z2 - 315.) * z * k * k * k / 384.); @@ -95,7 +90,7 @@ double t_percentile_c(const double p, const int df){ } -void calc_pn_c(const int n, const double x, double pnx[]) { +void calc_pn_c(int n, double x, double pnx[]) { pnx[0] = 1.; if (n >= 1) { pnx[1] = x; @@ -103,36 +98,28 @@ void calc_pn_c(const int n, const double x, double pnx[]) { // Use recursion relation to build the higher orders for (int l = 1; l < n; l ++) { - pnx[l + 1] = (static_cast(2 * l + 1) * x * pnx[l] - - static_cast(l) * pnx[l - 1]) / - (static_cast(l + 1)); + pnx[l + 1] = ((2 * l + 1) * x * pnx[l] - l * pnx[l - 1]) / (l + 1); } } -double evaluate_legendre_c(const int n, - const double data[], - const double x) { - double val; +double evaluate_legendre_c(int n, const double data[], double x) { double pnx[n + 1]; - - val = 0.0; + double val = 0.0; calc_pn_c(n, x, pnx); for (int l = 0; l <= n; l++) { - val += (static_cast(l) + 0.5) * data[l] * pnx[l]; + val += (l + 0.5) * data[l] * pnx[l]; } return val; } -void calc_rn_c(const int n, const double uvw[3], double rn[]){ +void calc_rn_c(int n, const double uvw[3], double rn[]){ // rn[] is assumed to have already been allocated to the correct size - double phi; - double w; - // Store the cosine of the polar angle and the azimuthal angle - w = uvw[2]; + double w = uvw[2]; + double phi; if (uvw[0] == 0.) { phi = 0.; } else { @@ -140,13 +127,11 @@ void calc_rn_c(const int n, const double uvw[3], double rn[]){ } // Store the shorthand of 1-w * w - double w2m1; - w2m1 = 1. - w * w; + double w2m1 = 1. - w * w; // Now evaluate the spherical harmonics function rn[0] = 1.; - int i; - i = 0; + int i = 0; for (int l = 1; l <= n; l++) { // Set the index to the start of this order i += 2 * (l - 1) + 1; @@ -525,15 +510,7 @@ void calc_rn_c(const int n, const double uvw[3], double rn[]){ } -void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { - // This procedure uses the modified Kintner's method for calculating Zernike - // polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, - // R. (2003). A comparative analysis of algorithms for fast computation of - // Zernike moments. Pattern Recognition, 36(3), 731-742. - - // n == radial degree - // m == azimuthal frequency - +void calc_zn_c(int n, double rho, double phi, double zn[]) { // =========================================================================== // Determine vector of sin(n*phi) and cos(n*phi). This takes advantage of the // following recurrence relations so that only a single sin/cos have to be @@ -542,10 +519,8 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // sin(nx) = 2 cos(x) sin((n-1)x) - sin((n-2)x) // cos(nx) = 2 cos(x) cos((n-1)x) - cos((n-2)x) - double sin_phi; - double cos_phi; - sin_phi = std::sin(phi); - cos_phi = std::cos(phi); + double sin_phi = std::sin(phi); + double cos_phi = std::cos(phi); double sin_phi_vec[n + 1]; // Sin[n * phi] double cos_phi_vec[n + 1]; // Cos[n * phi] @@ -579,16 +554,12 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { } // Fill in the rest of the values using the original results (Eq. 3.8 in Chong) - double k1; - double k2; - double k3; - double k4; for (int p = 4; p <= n; p++) { - k2 = static_cast (2 * p * (p - 1) * (p - 2)); + double k2 = 2 * p * (p - 1) * (p - 2); for (int q = p - 4; q >= 0; q -= 2) { - k1 = static_cast((p + q) * (p - q) * (p - 2)) / 2.; - k3 = static_cast(-q * q * (p - 1) - p * (p - 1) * (p - 2)); - k4 = static_cast(-p * (p + q - 2) * (p - q - 2)) / 2.; + double k1 = ((p + q) * (p - q) * (p - 2)) / 2.; + double k3 = -q * q * (p - 1) - p * (p - 1) * (p - 2); + double k4 = (-p * (p + q - 2) * (p - q - 2)) / 2.; zn_mat[q][p] = ((k2 * rho * rho + k3) * zn_mat[q][p-2] + k4 * zn_mat[q][p-4]) / k1; } @@ -600,8 +571,7 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { // Note that the cos and sin vectors are offset by one // sin_phi_vec = [sin(x), sin(2x), sin(3x) ...] // cos_phi_vec = [1.0, cos(x), cos(2x)... ] - int i; - i = 0; + int i = 0; for (int p = 0; p <= n; p++) { for (int q = -p; q <= p; q += 2) { if (q < 0) { @@ -618,15 +588,11 @@ void calc_zn_c(const int n, const double rho, const double phi, double zn[]) { } -void rotate_angle_c(double uvw[3], const double mu, double* phi) { +void rotate_angle_c(double uvw[3], double mu, double* phi) { // Copy original directional cosines - double u0; // original cosine in x direction - double v0; // original cosine in y direction - double w0; // original cosine in z direction - - u0 = uvw[0]; - v0 = uvw[1]; - w0 = uvw[2]; + double u0 = uvw[0]; // original cosine in x direction + double v0 = uvw[1]; // original cosine in y direction + double w0 = uvw[2]; // original cosine in z direction // Sample azimuthal angle in [0,2pi) if none provided double phi_; @@ -637,14 +603,10 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { } // Precompute factors to save flops - double sinphi; // sine of azimuthal angle - double cosphi; // cosine of azimuthal angle - double a; // sqrt(1 - mu^2) - double b; // sqrt(1 - w^2) - sinphi = std::sin(phi_); - cosphi = std::cos(phi_); - a = std::sqrt(std::fmax(0., 1. - mu * mu)); - b = std::sqrt(std::fmax(0., 1. - w0 * w0)); + double sinphi = std::sin(phi_); + double cosphi = std::cos(phi_); + double a = std::sqrt(std::fmax(0., 1. - mu * mu)); + double b = std::sqrt(std::fmax(0., 1. - w0 * w0)); // Need to treat special case where sqrt(1 - w**2) is close to zero by // expanding about the v component rather than the w component @@ -661,54 +623,39 @@ void rotate_angle_c(double uvw[3], const double mu, double* phi) { } -double maxwell_spectrum_c(const double T) { - double r1; - double r2; - double r3; // random numbers - - r1 = prn(); - r2 = prn(); - r3 = prn(); +double maxwell_spectrum_c(double T) { + // Set the random numbers + double r1 = prn(); + double r2 = prn(); + double r3 = prn(); // determine cosine of pi/2*r - double c; - c = std::cos(PI / 2. * r3); + double c = std::cos(PI / 2. * r3); - // determine outgoing energy - double E_out; // Sampled Energy - E_out = -T * (std::log(r1) + std::log(r2) * c * c); + // Determine outgoing energy + double E_out = -T * (std::log(r1) + std::log(r2) * c * c); return E_out; } -double watt_spectrum_c(const double a, const double b) { - double E_out; // Sampled Energy - double w; // sampled from Maxwellian - - w = maxwell_spectrum_c(a); - E_out = w + 0.25 * a * a * b + (2. * prn() - 1.) * std::sqrt(a * a * b * w); +double watt_spectrum_c(double a, double b) { + double w = maxwell_spectrum_c(a); + double E_out = w + 0.25 * a * a * b + (2. * prn() - 1.) * std::sqrt(a * a * b * w); return E_out; } -void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, - double factors[]) { +void broaden_wmp_polynomials_c(double E, double dopp, int n, double factors[]) { // Factors is already pre-allocated + double sqrtE = std::sqrt(E); + double beta = sqrtE * dopp; + double half_inv_dopp2 = 0.5 / (dopp * dopp); + double quarter_inv_dopp4 = half_inv_dopp2 * half_inv_dopp2; - double sqrtE; // sqrt(energy) - double beta; // sqrt(atomic weight ratio * E / kT) - double half_inv_dopp2; // 0.5 / dopp**2 - double quarter_inv_dopp4; // 0.25 / dopp**4 - double erf_beta; // error function of beta - double exp_m_beta2; // exp(-beta**2) - - sqrtE = std::sqrt(E); - beta = sqrtE * dopp; - half_inv_dopp2 = 0.5 / (dopp * dopp); - quarter_inv_dopp4 = half_inv_dopp2 * half_inv_dopp2; - + double erf_beta; // error function of beta + double exp_m_beta2; // exp(-beta**2) if (beta > 6.0) { // Save time, ERF(6) is 1 to machine precision. // beta/sqrtpi*exp(-beta**2) is also approximately 1 machine epsilon. @@ -728,9 +675,8 @@ void broaden_wmp_polynomials_c(const double E, const double dopp, const int n, (beta * SQRT_PI); // Perform recursive broadening of high order components - double ip1_dbl; for (int i = 0; i < n - 3; i++) { - ip1_dbl = static_cast(i + 1); + double ip1_dbl = i + 1; if (i != 0) { factors[i + 3] = -factors[i - 1] * (ip1_dbl - 1.) * ip1_dbl * quarter_inv_dopp4 + factors[i + 1] * diff --git a/src/math_functions.h b/src/math_functions.h index b55715717..18bd02640 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -18,7 +18,7 @@ namespace openmc { // modifying test results extern "C" constexpr double PI {3.1415926535898}; -extern "C" constexpr double SQRT_PI {std::sqrt(PI)}; +extern "C" const double SQRT_PI {std::sqrt(PI)}; //============================================================================== //! Calculate the percentile of the standard normal distribution with a @@ -28,7 +28,7 @@ extern "C" constexpr double SQRT_PI {std::sqrt(PI)}; //! @return The requested percentile //============================================================================== -extern "C" double normal_percentile_c(const double p); +extern "C" double normal_percentile_c(double p); //============================================================================== //! Calculate the percentile of the Student's t distribution with a specified @@ -39,7 +39,7 @@ extern "C" double normal_percentile_c(const double p); //! @return The requested percentile //============================================================================== -extern "C" double t_percentile_c(const double p, const int df); +extern "C" double t_percentile_c(double p, int df); //============================================================================== //! Calculate the n-th order Legendre polynomials at the value of x. @@ -50,7 +50,7 @@ extern "C" double t_percentile_c(const double p, const int df); //! evaluated at x. //============================================================================== -extern "C" void calc_pn_c(const int n, const double x, double pnx[]); +extern "C" void calc_pn_c(int n, double x, double pnx[]); //============================================================================== //! Find the value of f(x) given a set of Legendre coefficients and the value @@ -64,8 +64,7 @@ extern "C" void calc_pn_c(const int n, const double x, double pnx[]); //! evaluated at x //============================================================================== -extern "C" double evaluate_legendre_c(const int n, const double data[], - const double x); +extern "C" double evaluate_legendre_c(int n, const double data[], double x); //============================================================================== //! Calculate the n-th order real spherical harmonics for a given angle (in @@ -77,14 +76,18 @@ extern "C" double evaluate_legendre_c(const int n, const double data[], //! evaluated at uvw. //============================================================================== -extern "C" void calc_rn_c(const int n, const double uvw[3], double rn[]); +extern "C" void calc_rn_c(int n, const double uvw[3], double rn[]); //============================================================================== //! Calculate the n-th order modified Zernike polynomial moment for a given //! angle (rho, theta) location on the unit disk. //! +//! This procedure uses the modified Kintner's method for calculating Zernike +//! polynomials as outlined in Chong, C. W., Raveendran, P., & Mukundan, +//! R. (2003). A comparative analysis of algorithms for fast computation of +//! Zernike moments. Pattern Recognition, 36(3), 731-742. //! The normalization of the polynomials is such that the integral of Z_pq^2 -//! over the unit disk is exactly pi +//! over the unit disk is exactly pi. //! //! @param n The maximum order requested //! @param rho The radial parameter to specify location on the unit disk @@ -93,8 +96,7 @@ extern "C" void calc_rn_c(const int n, const double uvw[3], double rn[]); //! evaluated at rho and phi. //============================================================================== -extern "C" void calc_zn_c(const int n, const double rho, const double phi, - double zn[]); +extern "C" void calc_zn_c(int n, double rho, double phi, double zn[]); //============================================================================== //! Rotate the direction cosines through a polar angle whose cosine is mu and @@ -105,11 +107,11 @@ extern "C" void calc_zn_c(const int n, const double rho, const double phi, //! //! @param uvw[3] The initial, and final, direction vector //! @param mu The cosine of angle in lab or CM -//! @param phi The azimuthal angle; defaults to a randomly chosen angle +//! @param phi The azimuthal angle; will randomly chosen angle if a nullptr +//! is passed //============================================================================== -extern "C" void rotate_angle_c(double uvw[3], const double mu, - double* phi=nullptr); +extern "C" void rotate_angle_c(double uvw[3], double mu, double* phi); //============================================================================== //! Samples an energy from the Maxwell fission distribution based on a direct @@ -123,7 +125,7 @@ extern "C" void rotate_angle_c(double uvw[3], const double mu, //! @result The sampled outgoing energy //============================================================================== -extern "C" double maxwell_spectrum_c(const double T); +extern "C" double maxwell_spectrum_c(double T); //============================================================================== //! Samples an energy from a Watt energy-dependent fission distribution. @@ -138,7 +140,7 @@ extern "C" double maxwell_spectrum_c(const double T); //! @result The sampled outgoing energy //============================================================================== -extern "C" double watt_spectrum_c(const double a, const double b); +extern "C" double watt_spectrum_c(double a, double b); //============================================================================== //! Doppler broadens the windowed multipole curvefit. @@ -151,8 +153,8 @@ extern "C" double watt_spectrum_c(const double a, const double b); //! @param factors The output leading coefficient //============================================================================== -extern "C" void broaden_wmp_polynomials_c(const double E, const double dopp, - const int n, double factors[]); +extern "C" void broaden_wmp_polynomials_c(double E, double dopp, int n, + double factors[]); } // namespace openmc #endif // MATH_FUNCTIONS_H \ No newline at end of file From 890a1b4f55e2964e42f46cb46f8a37b8ee5314fd Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Fri, 18 May 2018 19:07:34 -0400 Subject: [PATCH 268/361] Moved math python api to point to the C++ and simplified the fortran methods --- openmc/capi/math.py | 71 ++++++------- src/api.F90 | 11 -- src/distribution_multivariate.F90 | 2 +- src/math.F90 | 168 ++++-------------------------- src/math_functions.h | 3 + src/mgxs_header.F90 | 5 +- src/physics.F90 | 13 +-- src/physics_mg.F90 | 2 +- src/scattdata_header.F90 | 3 +- 9 files changed, 67 insertions(+), 211 deletions(-) diff --git a/openmc/capi/math.py b/openmc/capi/math.py index 3b0b723b3..d1d7abdf6 100644 --- a/openmc/capi/math.py +++ b/openmc/capi/math.py @@ -6,37 +6,33 @@ from numpy.ctypeslib import ndpointer from . import _dll -_dll.t_percentile.restype = c_double -_dll.t_percentile.argtypes = [POINTER(c_double), POINTER(c_int)] +_dll.t_percentile_c.restype = c_double +_dll.t_percentile_c.argtypes = [c_double, c_int] -_dll.calc_pn.restype = None -_dll.calc_pn.argtypes = [POINTER(c_int), POINTER(c_double), - ndpointer(c_double)] +_dll.calc_pn_c.restype = None +_dll.calc_pn_c.argtypes = [c_int, c_double, ndpointer(c_double)] -_dll.evaluate_legendre.restype = c_double -_dll.evaluate_legendre.argtypes = [POINTER(c_int), POINTER(c_double), - POINTER(c_double)] +_dll.evaluate_legendre_c.restype = c_double +_dll.evaluate_legendre_c.argtypes = [c_int, POINTER(c_double), c_double] -_dll.calc_rn.restype = None -_dll.calc_rn.argtypes = [POINTER(c_int), ndpointer(c_double), - ndpointer(c_double)] +_dll.calc_rn_c.restype = None +_dll.calc_rn_c.argtypes = [c_int, ndpointer(c_double), ndpointer(c_double)] -_dll.calc_zn.restype = None -_dll.calc_zn.argtypes = [POINTER(c_int), POINTER(c_double), POINTER(c_double), - ndpointer(c_double)] +_dll.calc_zn_c.restype = None +_dll.calc_zn_c.argtypes = [c_int, c_double, c_double, ndpointer(c_double)] -_dll.rotate_angle.restype = None -_dll.rotate_angle.argtypes = [ndpointer(c_double), POINTER(c_double), - ndpointer(c_double), POINTER(c_double)] -_dll.maxwell_spectrum.restype = c_double -_dll.maxwell_spectrum.argtypes = [POINTER(c_double)] +_dll.rotate_angle_c.restype = None +_dll.rotate_angle_c.argtypes = [ndpointer(c_double), c_double, + POINTER(c_double)] +_dll.maxwell_spectrum_c.restype = c_double +_dll.maxwell_spectrum_c.argtypes = [c_double] -_dll.watt_spectrum.restype = c_double -_dll.watt_spectrum.argtypes = [POINTER(c_double), POINTER(c_double)] +_dll.watt_spectrum_c.restype = c_double +_dll.watt_spectrum_c.argtypes = [c_double, c_double] -_dll.broaden_wmp_polynomials.restype = None -_dll.broaden_wmp_polynomials.argtypes = [POINTER(c_double), POINTER(c_double), - POINTER(c_int), ndpointer(c_double)] +_dll.broaden_wmp_polynomials_c.restype = None +_dll.broaden_wmp_polynomials_c.argtypes = [c_double, c_double, c_int, + ndpointer(c_double)] def t_percentile(p, df): @@ -57,7 +53,7 @@ def t_percentile(p, df): """ - return _dll.t_percentile(c_double(p), c_int(df)) + return _dll.t_percentile_c(p, df) def calc_pn(n, x): @@ -78,7 +74,7 @@ def calc_pn(n, x): """ pnx = np.empty(n + 1, dtype=np.float64) - _dll.calc_pn(c_int(n), c_double(x), pnx) + _dll.calc_pn_c(n, x, pnx) return pnx @@ -101,9 +97,9 @@ def evaluate_legendre(data, x): """ data_arr = np.array(data, dtype=np.float64) - return _dll.evaluate_legendre(c_int(len(data)), - data_arr.ctypes.data_as(POINTER(c_double)), - c_double(x)) + return _dll.evaluate_legendre_c(len(data), + data_arr.ctypes.data_as(POINTER(c_double)), + x) def calc_rn(n, uvw): @@ -127,7 +123,7 @@ def calc_rn(n, uvw): num_nm = (n + 1) * (n + 1) rn = np.empty(num_nm, dtype=np.float64) uvw_arr = np.array(uvw, dtype=np.float64) - _dll.calc_rn(c_int(n), uvw_arr, rn) + _dll.calc_rn_c(n, uvw_arr, rn) return rn @@ -155,7 +151,7 @@ def calc_zn(n, rho, phi): num_bins = ((n + 1) * (n + 2)) // 2 zn = np.zeros(num_bins, dtype=np.float64) - _dll.calc_zn(c_int(n), c_double(rho), c_double(phi), zn) + _dll.calc_zn_c(n, rho, phi, zn) return zn @@ -179,13 +175,13 @@ def rotate_angle(uvw0, mu, phi=None): """ - uvw = np.zeros(3, dtype=np.float64) uvw0_arr = np.array(uvw0, dtype=np.float64) if phi is None: - _dll.rotate_angle(uvw0_arr, c_double(mu), uvw, None) + _dll.rotate_angle_c(uvw0_arr, mu, None) else: - _dll.rotate_angle(uvw0_arr, c_double(mu), uvw, c_double(phi)) + _dll.rotate_angle_c(uvw0_arr, mu, c_double(phi)) + uvw = uvw0_arr return uvw @@ -206,7 +202,7 @@ def maxwell_spectrum(T): """ - return _dll.maxwell_spectrum(c_double(T)) + return _dll.maxwell_spectrum_c(T) def watt_spectrum(a, b): @@ -226,7 +222,7 @@ def watt_spectrum(a, b): """ - return _dll.watt_spectrum(c_double(a), c_double(b)) + return _dll.watt_spectrum_c(a, b) def broaden_wmp_polynomials(E, dopp, n): @@ -250,6 +246,5 @@ def broaden_wmp_polynomials(E, dopp, n): """ factors = np.zeros(n, dtype=np.float64) - _dll.broaden_wmp_polynomials(c_double(E), c_double(dopp), c_int(n), - factors) + _dll.broaden_wmp_polynomials_c(E, dopp, n, factors) return factors diff --git a/src/api.F90 b/src/api.F90 index a06e2f4cc..da50007d6 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -95,17 +95,6 @@ module openmc_api public :: openmc_tally_set_nuclides public :: openmc_tally_set_scores public :: openmc_tally_set_type - public :: t_percentile - public :: calc_pn - public :: calc_rn - public :: calc_zn - public :: evaluate_legendre - public :: rotate_angle - public :: maxwell_spectrum - public :: watt_spectrum - public :: faddeeva - public :: w_derivative - public :: broaden_wmp_polynomials contains diff --git a/src/distribution_multivariate.F90 b/src/distribution_multivariate.F90 index 4d7d42cc3..19db1c297 100644 --- a/src/distribution_multivariate.F90 +++ b/src/distribution_multivariate.F90 @@ -119,7 +119,7 @@ contains else ! Sample azimuthal angle phi = this % phi % sample() - call rotate_angle(this % reference_uvw, mu, uvw, phi) + uvw = rotate_angle(this % reference_uvw, mu, phi) end if end function polar_azimuthal_sample diff --git a/src/math.F90 b/src/math.F90 index 05942a86e..33f1ab4d3 100644 --- a/src/math.F90 +++ b/src/math.F90 @@ -26,22 +26,22 @@ module math interface - pure function t_percentile_c_intfc(p, df) bind(C, name='t_percentile_c') & + pure function t_percentile(p, df) bind(C, name='t_percentile_c') & result(t) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: p integer(C_INT), value, intent(in) :: df real(C_DOUBLE) :: t - end function t_percentile_c_intfc + end function t_percentile - pure subroutine calc_pn_c_intfc(n, x, pnx) bind(C, name='calc_pn_c') + pure subroutine calc_pn(n, x, pnx) bind(C, name='calc_pn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), value, intent(in) :: x real(C_DOUBLE), intent(out) :: pnx(n + 1) - end subroutine calc_pn_c_intfc + end subroutine calc_pn pure function evaluate_legendre_c_intfc(n, data, x) & bind(C, name='evaluate_legendre_c') result(val) @@ -53,22 +53,22 @@ module math real(C_DOUBLE) :: val end function evaluate_legendre_c_intfc - pure subroutine calc_rn_c_intfc(n, uvw, rn) bind(C, name='calc_rn_c') + pure subroutine calc_rn(n, uvw, rn) bind(C, name='calc_rn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), intent(in) :: uvw(3) real(C_DOUBLE), intent(out) :: rn(2 * n + 1) - end subroutine calc_rn_c_intfc + end subroutine calc_rn - pure subroutine calc_zn_c_intfc(n, rho, phi, zn) bind(C, name='calc_zn_c') + pure subroutine calc_zn(n, rho, phi, zn) bind(C, name='calc_zn_c') use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: n real(C_DOUBLE), value, intent(in) :: rho real(C_DOUBLE), value, intent(in) :: phi real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) - end subroutine calc_zn_c_intfc + end subroutine calc_zn subroutine rotate_angle_c_intfc(uvw, mu, phi) bind(C, name='rotate_angle_c') use ISO_C_BINDING @@ -78,24 +78,24 @@ module math real(C_DOUBLE), optional, intent(in) :: phi end subroutine rotate_angle_c_intfc - function maxwell_spectrum_c_intfc(T) bind(C, name='maxwell_spectrum_c') & + function maxwell_spectrum(T) bind(C, name='maxwell_spectrum_c') & result(E_out) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: T real(C_DOUBLE) :: E_out - end function maxwell_spectrum_c_intfc + end function maxwell_spectrum - function watt_spectrum_c_intfc(a, b) bind(C, name='watt_spectrum_c') & + function watt_spectrum(a, b) bind(C, name='watt_spectrum_c') & result(E_out) use ISO_C_BINDING implicit none real(C_DOUBLE), value, intent(in) :: a real(C_DOUBLE), value, intent(in) :: b real(C_DOUBLE) :: E_out - end function watt_spectrum_c_intfc + end function watt_spectrum - subroutine broaden_wmp_polynomials_c_intfc(E, dopp, n, factors) & + subroutine broaden_wmp_polynomials(E, dopp, n, factors) & bind(C, name='broaden_wmp_polynomials_c') use ISO_C_BINDING implicit none @@ -103,7 +103,7 @@ module math real(C_DOUBLE), value, intent(in) :: dopp integer(C_INT), value, intent(in) :: n real(C_DOUBLE), intent(inout) :: factors(n) - end subroutine broaden_wmp_polynomials_c_intfc + end subroutine broaden_wmp_polynomials function faddeeva_w(z, relerr) bind(C, name='Faddeeva_w') result(w) use ISO_C_BINDING @@ -116,52 +116,13 @@ module math contains -!=============================================================================== -! T_PERCENTILE calculates the percentile of the Student's t distribution with a -! specified probability level and number of degrees of freedom -!=============================================================================== - - pure function t_percentile(p, df) result(t) bind(C) - - real(C_DOUBLE), intent(in) :: p ! probability level - integer(C_INT), intent(in) :: df ! degrees of freedom - real(C_DOUBLE) :: t ! corresponding t-value - - t = t_percentile_c_intfc(p, df) - - end function t_percentile - -!=============================================================================== -! CALC_PN calculates the n-th order Legendre polynomial at the value of x. -! Since this function is called repeatedly during the neutron transport process, -! neither n or x is checked to see if they are in the applicable range. -! This is left to the client developer to use where applicable. x is to be in -! the domain of [-1,1], and 0<=n<=5. If x is outside of the range, the return -! value will be outside the expected range; if n is outside the stated range, -! the return value will be 1.0. -!=============================================================================== - - pure subroutine calc_pn(n, x, pnx) bind(C) - - integer(C_INT), intent(in) :: n ! Legendre order requested - real(C_DOUBLE), intent(in) :: x ! Independent variable the Legendre is to - ! be evaluated at; x must be in the - ! domain [-1,1] - real(C_DOUBLE), intent(out) :: pnx(n + 1) ! The Legendre polys of order n - ! evaluated at x - - call calc_pn_c_intfc(n, x, pnx) - - end subroutine calc_pn - !=============================================================================== ! EVALUATE_LEGENDRE Find the value of f(x) given a set of Legendre coefficients ! and the value of x !=============================================================================== - pure function evaluate_legendre(n, data, x) result(val) bind(C) - integer(C_INT), intent(in) :: n - real(C_DOUBLE), intent(in) :: data(n) + pure function evaluate_legendre(data, x) result(val) bind(C) + real(C_DOUBLE), intent(in) :: data(:) real(C_DOUBLE), intent(in) :: x real(C_DOUBLE) :: val @@ -169,91 +130,23 @@ contains end function evaluate_legendre -!=============================================================================== -! CALC_RN calculates the n-th order real spherical harmonics for a given angle -! (in terms of (u,v,w)). All Rn,m values are provided (where -n<=m<=n) -!=============================================================================== - - pure subroutine calc_rn(n, uvw, rn) bind(C) - - integer(C_INT), intent(in) :: n ! Order requested - real(C_DOUBLE), intent(in) :: uvw(3) ! Direction of travel; - ! assumed to be on unit sphere - real(C_DOUBLE), intent(out) :: rn(2*n + 1) ! The resultant R_n(uvw) - - call calc_rn_c_intfc(n, uvw, rn) - - end subroutine calc_rn - -!=============================================================================== -! CALC_ZN calculates the n-th order modified Zernike polynomial moment for a -! given angle (rho, theta) location in the unit disk. The normlization of the -! polynomials is such that the integral of Z_pq*Z_pq over the unit disk is -! exactly pi -!=============================================================================== - - pure subroutine calc_zn(n, rho, phi, zn) bind(C) - integer(C_INT), intent(in) :: n ! Maximum order - real(C_DOUBLE), intent(in) :: rho ! Radial location in the unit disk - real(C_DOUBLE), intent(in) :: phi ! Theta (radians) location in the unit disk - ! The resulting list of coefficients - real(C_DOUBLE), intent(out) :: zn(((n + 1) * (n + 2)) / 2) - - call calc_zn_c_intfc(n, rho, phi, zn) - end subroutine calc_zn - !=============================================================================== ! ROTATE_ANGLE rotates direction cosines through a polar angle whose cosine is ! mu and through an azimuthal angle sampled uniformly. Note that this is done ! with direct sampling rather than rejection as is done in MCNP and SERPENT. !=============================================================================== - subroutine rotate_angle(uvw0, mu, uvw, phi) bind(C) - real(C_DOUBLE), intent(in) :: uvw0(3) ! directional cosine - real(C_DOUBLE), intent(in) :: mu ! cosine of angle in lab or CM - real(C_DOUBLE), intent(out) :: uvw(3) ! rotated directional cosine - real(C_DOUBLE), target, optional :: phi ! azimuthal angle + function rotate_angle(uvw0, mu, phi) result(uvw) + real(C_DOUBLE), intent(in) :: uvw0(3) ! directional cosine + real(C_DOUBLE), intent(in) :: mu ! cosine of angle in lab or CM + real(C_DOUBLE), intent(in), optional :: phi ! azimuthal angle - real(C_DOUBLE), pointer :: phi_ptr + real(C_DOUBLE) :: uvw(3) ! rotated directional cosine uvw = uvw0 call rotate_angle_c_intfc(uvw, mu, phi) - end subroutine rotate_angle - -!=============================================================================== -! MAXWELL_SPECTRUM samples an energy from the Maxwell fission distribution based -! on a direct sampling scheme. The probability distribution function for a -! Maxwellian is given as p(x) = 2/(T*sqrt(pi))*sqrt(x/T)*exp(-x/T). This PDF can -! be sampled using rule C64 in the Monte Carlo Sampler LA-9721-MS. -!=============================================================================== - - function maxwell_spectrum(T) result(E_out) bind(C) - - real(C_DOUBLE), intent(in) :: T ! tabulated function of incoming E - real(C_DOUBLE) :: E_out ! sampled energy - - E_out = maxwell_spectrum_c_intfc(T) - - end function maxwell_spectrum - -!=============================================================================== -! WATT_SPECTRUM samples the outgoing energy from a Watt energy-dependent fission -! spectrum. Although fitted parameters exist for many nuclides, generally the -! continuous tabular distributions (LAW 4) should be used in lieu of the Watt -! spectrum. This direct sampling scheme is an unpublished scheme based on the -! original Watt spectrum derivation (See F. Brown's MC lectures). -!=============================================================================== - - function watt_spectrum(a, b) result(E_out) bind(C) - - real(C_DOUBLE), intent(in) :: a ! Watt parameter a - real(C_DOUBLE), intent(in) :: b ! Watt parameter b - real(C_DOUBLE) :: E_out ! energy of emitted neutron - - E_out = watt_spectrum_c_intfc(a, b) - - end function watt_spectrum + end function rotate_angle !=============================================================================== ! FADDEEVA the Faddeeva function, using Stephen Johnson's implementation @@ -305,21 +198,4 @@ contains end select end function w_derivative -!=============================================================================== -! BROADEN_WMP_POLYNOMIALS Doppler broadens the windowed multipole curvefit. The -! curvefit is a polynomial of the form -! a/E + b/sqrt(E) + c + d sqrt(E) ... -!=============================================================================== - - subroutine broaden_wmp_polynomials(E, dopp, n, factors) bind(C) - real(C_DOUBLE), intent(in) :: E ! Energy to evaluate at - real(C_DOUBLE), intent(in) :: dopp ! sqrt(atomic weight ratio / kT), - ! kT given in eV. - integer(C_INT), intent(in) :: n ! number of components to polynomial - real(C_DOUBLE), intent(out):: factors(n) ! output leading coefficient - - call broaden_wmp_polynomials_c_intfc(E, dopp, n, factors) - - end subroutine broaden_wmp_polynomials - end module math diff --git a/src/math_functions.h b/src/math_functions.h index 18bd02640..68e89251e 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -1,3 +1,6 @@ +//! \file math_functions.h +//! A collection of elementary math functions. + #ifndef MATH_FUNCTIONS_H #define MATH_FUNCTIONS_H diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index bcdef6c79..5abeccbcf 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -1103,9 +1103,7 @@ contains end if scatt_coeffs(gin) % data(imu, gout) = & - evaluate_legendre( & - size(input_scatt(gin) % data, dim=1), & - input_scatt(gin) % data(:, gout), mu) + evaluate_legendre(input_scatt(gin) % data(:, gout), mu) ! Ensure positivity of distribution if (scatt_coeffs(gin) % data(imu, gout) < ZERO) & @@ -2097,7 +2095,6 @@ contains scatt_coeffs(gin, iazi, ipol) % data(imu, gout) = & evaluate_legendre(& - size(input_scatt(gin, iazi, ipol) % data, dim=1), & input_scatt(gin, iazi, ipol) % data(:, gout), mu) ! Ensure positivity of distribution diff --git a/src/physics.F90 b/src/physics.F90 index 8183435cb..b614b8185 100644 --- a/src/physics.F90 +++ b/src/physics.F90 @@ -495,8 +495,7 @@ contains ! Rotate neutron velocity vector to new angle -- note that the speed of the ! neutron in CM does not change in elastic scattering. However, the speed ! will change when we convert back to LAB - call rotate_angle(uvw_cm, mu_cm, v_n) - v_n = vel * v_n + v_n = vel * rotate_angle(uvw_cm, mu_cm) ! Transform back to LAB frame v_n = v_n + v_cm @@ -786,7 +785,7 @@ contains if (abs(mu) > ONE) mu = sign(ONE,mu) ! change direction of particle - call rotate_angle(uvw, mu, uvw) + uvw = rotate_angle(uvw, mu) end subroutine sab_scatter @@ -977,8 +976,7 @@ contains if (abs(mu) < ONE) then ! set and accept target velocity E_t = E_t / awr - call rotate_angle(uvw, mu, v_target) - v_target = sqrt(E_t) * v_target + v_target = sqrt(E_t) * rotate_angle(uvw, mu) exit ARES_REJECT_LOOP end if end do ARES_REJECT_LOOP @@ -1058,8 +1056,7 @@ contains ! Determine velocity vector of target nucleus based on neutron's velocity ! and the sampled angle between them - call rotate_angle(uvw, mu, v_target) - v_target = vt * v_target + v_target = vt * rotate_angle(uvw, mu) end subroutine sample_cxs_target_velocity @@ -1330,7 +1327,7 @@ contains p % mu = mu ! change direction of particle - call rotate_angle(p % coord(1) % uvw, mu, p % coord(1) % uvw) + p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, mu) ! evaluate yield yield = rxn % products(1) % yield % evaluate(E_in) diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index 05493af05..eb8208594 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -151,7 +151,7 @@ contains p % E = energy_bin_avg(p % g) ! Convert change in angle (mu) to new direction - call rotate_angle(p % coord(1) % uvw, p % mu, p % coord(1) % uvw) + p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, p % mu) ! Set event component p % event = EVENT_SCATTER diff --git a/src/scattdata_header.F90 b/src/scattdata_header.F90 index a3108a2df..511e2a237 100644 --- a/src/scattdata_header.F90 +++ b/src/scattdata_header.F90 @@ -445,8 +445,7 @@ contains if (gout < this % gmin(gin) .or. gout > this % gmax(gin)) then f = ZERO else - f = evaluate_legendre(size(this % dist(gin) % data, dim=1), & - this % dist(gin) % data(:, gout), mu) + f = evaluate_legendre(this % dist(gin) % data(:, gout), mu) end if end function scattdatalegendre_calc_f From 9da5e36daf5d9a6e489794c625d62283fc27d330 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 21 May 2018 16:32:44 -0400 Subject: [PATCH 269/361] Move distribcell offsets to C++ --- src/cell.cpp | 2 + src/cell.h | 2 + src/geometry.F90 | 92 +++--------------------- src/geometry_aux.cpp | 68 +++++++++++------- src/geometry_aux.h | 66 +++++++++++++++++ src/geometry_header.F90 | 49 +++++++++---- src/input_xml.F90 | 44 ++++-------- src/lattice.cpp | 48 +++++++++++++ src/lattice.h | 40 +++++++---- src/summary.F90 | 5 -- src/tallies/tally_filter_distribcell.F90 | 34 ++++----- 11 files changed, 265 insertions(+), 185 deletions(-) create mode 100644 src/geometry_aux.h diff --git a/src/cell.cpp b/src/cell.cpp index 6d7e598eb..1d630b3d4 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -485,6 +485,8 @@ extern "C" { i_surf = out.second; } + int32_t cell_offset(Cell *c, int map) {return c->offset[map];} + void cell_to_hdf5(Cell *c, hid_t group) {c->to_hdf5(group);} void extend_cells_c(int32_t n) diff --git a/src/cell.h b/src/cell.h index fff8b749c..2b03e1cd0 100644 --- a/src/cell.h +++ b/src/cell.h @@ -71,6 +71,8 @@ public: std::vector rpn; bool simple; //!< Does the region contain only intersections? + std::vector offset; //!< Distribcell offset table + Cell() {}; explicit Cell(pugi::xml_node cell_node); diff --git a/src/geometry.F90 b/src/geometry.F90 index d05362587..c3b647a82 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -26,7 +26,7 @@ module geometry end function cell_contains_c function count_universe_instances(search_univ, target_univ_id) bind(C) & - result(count) + result(count) import C_INT32_T, C_INT integer(C_INT32_T), intent(in), value :: search_univ integer(C_INT32_T), intent(in), value :: target_univ_id @@ -174,19 +174,19 @@ contains offset = 0 do k = 1, p % n_coord if (cells(p % coord(k) % cell) % type() == FILL_UNIVERSE) then - offset = offset + cells(p % coord(k) % cell) % & - offset(distribcell_index) + offset = offset + cells(p % coord(k) % cell) & + % offset(distribcell_index-1) elseif (cells(p % coord(k) % cell) % type() == FILL_LATTICE) then if (lattices(p % coord(k + 1) % lattice) % obj & % are_valid_indices([& p % coord(k + 1) % lattice_x, & p % coord(k + 1) % lattice_y, & p % coord(k + 1) % lattice_z])) then - offset = offset + lattices(p % coord(k + 1) % lattice) % obj % & - offset(distribcell_index, & - p % coord(k + 1) % lattice_x, & - p % coord(k + 1) % lattice_y, & - p % coord(k + 1) % lattice_z) + offset = offset + lattices(p % coord(k + 1) % lattice) % obj & + % offset(distribcell_index - 1, & + [p % coord(k + 1) % lattice_x - 1, & + p % coord(k + 1) % lattice_y - 1, & + p % coord(k + 1) % lattice_z - 1]) end if end if end do @@ -534,82 +534,6 @@ contains end subroutine neighbor_lists -!=============================================================================== -! CALC_OFFSETS calculates and stores the offsets in all fill cells. This -! routine is called once upon initialization. -!=============================================================================== - - subroutine calc_offsets(univ_id, map, univ) - - integer, intent(in) :: univ_id ! target universe ID - integer, intent(in) :: map ! map index in vector of maps - type(Universe), intent(in) :: univ ! universe searching in - - integer :: i ! index over cells - integer :: j, k, m ! indices in lattice - integer :: offset ! total offset for a given cell - integer :: cell_index ! index in cells array - type(Cell), pointer :: c ! pointer to current cell - class(Lattice), pointer :: lat ! pointer to current lattice - - offset = 0 - - do i = 1, size(univ % cells) - - cell_index = univ % cells(i) - - ! get pointer to cell - c => cells(cell_index) - - ! ==================================================================== - ! AT LOWEST UNIVERSE, TERMINATE SEARCH - if (c % type() == FILL_MATERIAL) then - - ! ==================================================================== - ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL - elseif (c % type() == FILL_UNIVERSE) then - c % offset(map) = offset - offset = offset + count_universe_instances(c % fill - 1, univ_id) - - ! ==================================================================== - ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type() == FILL_LATTICE) then - - ! Set current lattice - lat => lattices(c % fill) % obj - - select type (lat) - - type is (RectLattice) - do m = 1, lat % n_cells(3) - do k = 1, lat % n_cells(2) - do j = 1, lat % n_cells(1) - lat % offset(map, j, k, m) = offset - offset = offset + count_universe_instances(& - lat % get([j-1, k-1, m-1]), univ_id) - end do - end do - end do - - type is (HexLattice) - do m = 1, lat % n_axial - do k = 1, 2*lat % n_rings - 1 - do j = 1, 2*lat % n_rings - 1 - if (lat % are_valid_indices([j, k, m])) then - lat % offset(map, j, k, m) = offset - offset = offset + count_universe_instances(& - lat % get([j-1, k-1, m-1]), univ_id) - end if - end do - end do - end do - end select - - end if - end do - - end subroutine calc_offsets - !=============================================================================== ! MAXIMUM_LEVELS determines the maximum number of nested coordinate levels in ! the geometry diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 911e53c28..068554240 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -1,5 +1,4 @@ -//! \file geometry_aux.cpp -//! Auxilary functions for geometry initialization and general data handling. +#include "geometry_aux.h" #include #include @@ -14,11 +13,9 @@ namespace openmc { -//============================================================================== -//! Replace Universe, Lattice, and Material IDs with indices. //============================================================================== -extern "C" void +void adjust_indices_c() { // Adjust material/fill idices. @@ -65,15 +62,9 @@ adjust_indices_c() } } -//============================================================================== -//! Figure out which Universe is the root universe. -//! -//! This function looks for a universe that is not listed in a Cell::fill or in -//! a Lattice. -//! @return The index of the root universe. //============================================================================== -extern "C" int32_t +int32_t find_root_universe() { // Find all the universes listed as a cell fill. @@ -115,15 +106,24 @@ find_root_universe() } //============================================================================== -//! Recursively search through the geometry and count cell instances. -//! -//! This function will update the Cell::n_instances value for each cell in the -//! geometry. -//! @param univ_indx The index of the universe to begin searching from (probably -//! the root universe). + +void +allocate_offset_tables(int n_maps) +{ + for (Cell *c : cells_c) { + if (c->type != FILL_MATERIAL) { + c->offset.resize(n_maps, C_NONE); + } + } + + for (Lattice *lat : lattices_c) { + lat->allocate_offset_table(n_maps); + } +} + //============================================================================== -extern "C" void +void count_cell_instances(int32_t univ_indx) { for (int32_t cell_indx : universes_c[univ_indx]->cells) { @@ -144,14 +144,9 @@ count_cell_instances(int32_t univ_indx) } } -//============================================================================== -//! Recursively search through universes and count the number of instances of -//! the target universe in the geometry tree. -//! @param search_univ The index of the universe to begin searching from. -//! @param target_univ_id The ID of the universe to be counted. //============================================================================== -extern "C" int +int count_universe_instances(int32_t search_univ, int32_t target_univ_id) { // If this is the target, it can't contain itself. @@ -179,4 +174,27 @@ count_universe_instances(int32_t search_univ, int32_t target_univ_id) return count; } +//============================================================================== + +void +fill_offset_tables(int32_t target_univ_id, int map) +{ + for (Universe *univ : universes_c) { + int32_t offset {0}; // TODO: is this a bug? It matches F90 implementation. + for (int32_t cell_indx : univ->cells) { + Cell &c = *cells_c[cell_indx]; + + if (c.type == FILL_UNIVERSE) { + c.offset[map] = offset; + int32_t search_univ = c.fill - 1; // TODO: off-by-one + offset += count_universe_instances(search_univ, target_univ_id); + + } else if (c.type == FILL_LATTICE) { + Lattice &lat = *lattices_c[c.fill - 1]; // TODO: off-by-one + offset = lat.fill_offset_table(offset, target_univ_id, map); + } + } + } +} + } // namespace openmc diff --git a/src/geometry_aux.h b/src/geometry_aux.h new file mode 100644 index 000000000..0a0500794 --- /dev/null +++ b/src/geometry_aux.h @@ -0,0 +1,66 @@ +//! \file geometry_aux.h +//! Auxilary functions for geometry initialization and general data handling. + +#ifndef GEOMETRY_AUX_H +#define GEOMETRY_AUX_H + +#include + + +namespace openmc { + +//============================================================================== +//! Replace Universe, Lattice, and Material IDs with indices. +//============================================================================== + +extern "C" void adjust_indices_c(); + +//============================================================================== +//! Figure out which Universe is the root universe. +//! +//! This function looks for a universe that is not listed in a Cell::fill or in +//! a Lattice. +//! @return The index of the root universe. +//============================================================================== + +extern "C" int32_t find_root_universe(); + +//============================================================================== +//! Allocate storage in Lattice and Cell objects for distribcell offset tables. +//============================================================================== + +extern "C" void allocate_offset_tables(int n_maps); + +//============================================================================== +//! Recursively search through the geometry and count cell instances. +//! +//! This function will update the Cell::n_instances value for each cell in the +//! geometry. +//! @param univ_indx The index of the universe to begin searching from (probably +//! the root universe). +//============================================================================== + +extern "C" void count_cell_instances(int32_t univ_indx); + +//============================================================================== +//! Recursively search through universes and count universe instances. +//! @param search_univ The index of the universe to begin searching from. +//! @param target_univ_id The ID of the universe to be counted. +//! @return The number of instances of target_univ_id in the geometry tree under +//! search_univ. +//============================================================================== + +extern "C" int +count_universe_instances(int32_t search_univ, int32_t target_univ_id); + +//============================================================================== +//! Populate Cell and Lattice distribcell offset tables. +//! @param target_univ_id The ID of the universe to be counted. +//! @param map The index of the distribcell map that defines the offsets for the +//! target universe. +//============================================================================== + +extern "C" void fill_offset_tables(int32_t target_univ_id, int map); + +} // namespace openmc +#endif // GEOMETRY_AUX_H diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 012e976af..fc70a8eec 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -68,7 +68,7 @@ module geometry_header end subroutine cell_set_universe_c function cell_n_instances_c(cell_ptr) bind(C, name='cell_n_instances') & - result(n_instances) + result(n_instances) import C_PTR, C_INT32_T implicit none type(C_PTR), intent(in), value :: cell_ptr @@ -94,9 +94,16 @@ module geometry_header integer(C_INT32_T), intent(out) :: i_surf end subroutine cell_distance_c + function cell_offset_c(cell_ptr, map) bind(C, name="cell_offset") & + result(offset) + import C_PTR, C_INT, C_INT32_T + type(C_PTR), intent(in), value :: cell_ptr + integer(C_INT), intent(in), value :: map + integer(C_INT32_T) :: offset + end function cell_offset_c + subroutine cell_to_hdf5_c(cell_ptr, group) bind(C, name='cell_to_hdf5') import HID_T, C_PTR - implicit none type(C_PTR), intent(in), value :: cell_ptr integer(HID_T), intent(in), value :: group end subroutine cell_to_hdf5_c @@ -104,14 +111,12 @@ module geometry_header function lattice_pointer_c(lat_ind) bind(C, name='lattice_pointer') & result(ptr) import C_PTR, C_INT32_T - implicit none integer(C_INT32_T), intent(in), value :: lat_ind type(C_PTR) :: ptr end function lattice_pointer_c function lattice_id_c(lat_ptr) bind(C, name='lattice_id') result(id) import C_PTR, C_INT32_T - implicit none type(C_PTR), intent(in), value :: lat_ptr integer(C_INT32_T) :: id end function lattice_id_c @@ -119,7 +124,6 @@ module geometry_header function lattice_are_valid_indices_c(lat_ptr, i_xyz) & bind(C, name='lattice_are_valid_indices') result (is_valid) import C_PTR, C_INT, C_BOOL - implicit none type(C_PTR), intent(in), value :: lat_ptr integer(C_INT), intent(in) :: i_xyz(3) logical(C_BOOL) :: is_valid @@ -128,7 +132,6 @@ module geometry_header subroutine lattice_distance_c(lat_ptr, xyz, uvw, i_xyz, d, lattice_trans) & bind(C, name='lattice_distance') import C_PTR, C_INT, C_DOUBLE - implicit none type(C_PTR), intent(in), value :: lat_ptr real(C_DOUBLE), intent(in) :: xyz(3) real(C_DOUBLE), intent(in) :: uvw(3) @@ -140,7 +143,6 @@ module geometry_header subroutine lattice_get_indices_c(lat_ptr, xyz, i_xyz) & bind(C, name='lattice_get_indices') import C_PTR, C_INT, C_DOUBLE - implicit none type(C_PTR), intent(in), value :: lat_ptr real(C_DOUBLE), intent(in) :: xyz(3) integer(C_INT), intent(out) :: i_xyz(3) @@ -149,7 +151,6 @@ module geometry_header subroutine lattice_get_local_xyz_c(lat_ptr, global_xyz, i_xyz, local_xyz) & bind(C, name='lattice_get_local_xyz') import C_PTR, C_INT, C_DOUBLE - implicit none type(C_PTR), intent(in), value :: lat_ptr real(C_DOUBLE), intent(in) :: global_xyz(3) integer(C_INT), intent(in) :: i_xyz(3) @@ -158,11 +159,19 @@ module geometry_header subroutine lattice_to_hdf5_c(lat_ptr, group) bind(C, name='lattice_to_hdf5') import HID_T, C_PTR - implicit none type(C_PTR), intent(in), value :: lat_ptr integer(HID_T), intent(in), value :: group end subroutine lattice_to_hdf5_c + function lattice_offset_c(lat_ptr, map, i_xyz) & + bind(C, name='lattice_offset') result(offset) + import C_PTR, C_INT, C_INT32_T + type(C_PTR), intent(in), value :: lat_ptr + integer(C_INT), intent(in), value :: map + integer(C_INT), intent(in) :: i_xyz(3) + integer(C_INT32_T) :: offset + end function lattice_offset_c + function lattice_outer_c(lat_ptr) bind(C, name='lattice_outer') & result(outer) import C_PTR, C_INT32_T @@ -173,7 +182,6 @@ module geometry_header function lattice_universe_c(lat_ptr, i_xyz) & bind(C, name='lattice_universe') result(univ) import C_PTR, C_INT32_t, C_INT - implicit none type(C_PTR), intent(in), value :: lat_ptr integer(C_INT), intent(in) :: i_xyz(3) integer(C_INT32_T) :: univ @@ -181,7 +189,6 @@ module geometry_header subroutine extend_cells_c(n) bind(C) import C_INT32_t - implicit none integer(C_INT32_T), intent(in), value :: n end subroutine extend_cells_c end interface @@ -207,7 +214,6 @@ module geometry_header type(C_PTR) :: ptr logical :: is_3d ! Lattice has cells on z axis - integer, allocatable :: offset(:,:,:,:) ! Distribcell offsets contains procedure :: id => lattice_id @@ -216,6 +222,7 @@ module geometry_header procedure :: get => lattice_get procedure :: get_indices => lattice_get_indices procedure :: get_local_xyz => lattice_get_local_xyz + procedure :: offset => lattice_offset procedure :: outer => lattice_outer procedure :: to_hdf5 => lattice_to_hdf5 end type Lattice @@ -256,8 +263,6 @@ module geometry_header integer, allocatable :: material(:) ! Material within cell. Multiple ! materials for distribcell ! instances. 0 signifies a universe - integer, allocatable :: offset(:) ! Distribcell offset for tally - ! counter integer, allocatable :: region(:) ! Definition of spatial region as ! Boolean expression of half-spaces integer :: distribcell_index ! Index corresponding to this cell in @@ -282,6 +287,7 @@ module geometry_header procedure :: n_instances => cell_n_instances procedure :: simple => cell_simple procedure :: distance => cell_distance + procedure :: offset => cell_offset procedure :: to_hdf5 => cell_to_hdf5 end type Cell @@ -350,6 +356,14 @@ contains call lattice_get_local_xyz_c(this % ptr, global_xyz, i_xyz, local_xyz) end function lattice_get_local_xyz + function lattice_offset(this, map, i_xyz) result(offset) + class(Lattice), intent(in) :: this + integer(C_INT), intent(in) :: map + integer(C_INT), intent(in) :: i_xyz(3) + integer(C_INT32_T) :: offset + offset = lattice_offset_c(this % ptr, map, i_xyz) + end function lattice_offset + function lattice_outer(this) result(outer) class(Lattice), intent(in) :: this integer(C_INT32_T) :: outer @@ -422,6 +436,13 @@ contains call cell_distance_c(this % ptr, xyz, uvw, on_surface, min_dist, i_surf) end subroutine cell_distance + function cell_offset(this, map) result(offset) + class(Cell), intent(in) :: this + integer(C_INT), intent(in) :: map + integer(C_INT32_T) :: offset + offset = cell_offset_c(this % ptr, map) + end function cell_offset + subroutine cell_to_hdf5(this, group) class(Cell), intent(in) :: this integer(HID_T), intent(in) :: group diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 44a161f47..c68ed4a8c 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -11,7 +11,7 @@ module input_xml use distribution_univariate use endf, only: reaction_name use error, only: fatal_error, warning, write_message, openmc_err_msg - use geometry, only: calc_offsets, maximum_levels, neighbor_lists + use geometry, only: maximum_levels, neighbor_lists use geometry_header use hdf5_interface use list_header, only: ListChar, ListInt, ListReal @@ -50,10 +50,21 @@ module input_xml subroutine adjust_indices_c() bind(C) end subroutine adjust_indices_c + subroutine allocate_offset_tables(n_maps) bind(C) + import C_INT + integer(C_INT), intent(in), value :: n_maps + end subroutine allocate_offset_tables + + subroutine fill_offset_tables(target_univ_id, map) bind(C) + import C_INT32_T, C_INT + integer(C_INT32_T), intent(in), value :: target_univ_id + integer(C_INT), intent(in), value :: map + end subroutine fill_offset_tables + subroutine count_cell_instances(univ_indx) bind(C) import C_INT32_T integer(C_INT32_T), intent(in), value :: univ_indx - end subroutine + end subroutine count_cell_instances subroutine read_surfaces(node_ptr) bind(C) import C_PTR @@ -3884,12 +3895,11 @@ contains ! Allocate offset maps at each level in the geometry call allocate_offsets(univ_list) + call allocate_offset_tables(n_maps) ! Calculate offsets for each target distribcell do i = 1, n_maps - do j = 1, n_universes - call calc_offsets(univ_list(i), i, universes(j)) - end do + call fill_offset_tables(univ_list(i), i-1) end do end subroutine prepare_distribcell @@ -3952,30 +3962,6 @@ contains end do end do - ! Allocate the offset tables for lattices - do i = 1, n_lattices - associate(lat => lattices(i) % obj) - select type(lat) - - type is (RectLattice) - allocate(lat % offset(n_maps, lat % n_cells(1), lat % n_cells(2), & - lat % n_cells(3))) - type is (HexLattice) - allocate(lat % offset(n_maps, 2 * lat % n_rings - 1, & - 2 * lat % n_rings - 1, lat % n_axial)) - end select - - lat % offset(:, :, :, :) = 0 - end associate - end do - - ! Allocate offset table for fill cells - do i = 1, n_cells - if (cells(i) % type() /= FILL_MATERIAL) then - allocate(cells(i) % offset(n_maps)) - end if - end do - ! Free up memory call cell_list % clear() diff --git a/src/lattice.cpp b/src/lattice.cpp index 024e4cb48..7bb5fb6bb 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -7,6 +7,7 @@ #include "cell.h" #include "constants.h" #include "error.h" +#include "geometry_aux.h" #include "hdf5_interface.h" #include "xml_interface.h" @@ -98,6 +99,26 @@ Lattice::adjust_indices() //============================================================================== +void +Lattice::allocate_offset_table(int n_maps) +{ + offsets.resize(n_maps * universes.size(), C_NONE); +} + +//============================================================================== + +int32_t +Lattice::fill_offset_table(int32_t offset, int32_t target_univ_id, int map) +{ + for (auto it = begin(); it != end(); ++it) { + offsets[map * universes.size() + it.indx] = offset; + offset += count_universe_instances(*it, target_univ_id); + } + return offset; +} + +//============================================================================== + void Lattice::to_hdf5(hid_t lattices_group) const { @@ -316,6 +337,17 @@ RectLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const //============================================================================== +int32_t& +RectLattice::offset(int map, const int i_xyz[3]) +{ + int nx = n_cells[0]; + int ny = n_cells[1]; + int nz = n_cells[2]; + return offsets[nx*ny*nz*map + nx*ny*i_xyz[2] + nx*i_xyz[1] + i_xyz[0]]; +} + +//============================================================================== + void RectLattice::to_hdf5_inner(hid_t lat_group) const { @@ -763,6 +795,17 @@ HexLattice::is_valid_index(int indx) const //============================================================================== +int32_t& +HexLattice::offset(int map, const int i_xyz[3]) +{ + int nx = 2*n_rings - 1; + int ny = 2*n_rings - 1; + int nz = n_axial; + return offsets[nx*ny*nz*map + nx*ny*i_xyz[2] + nx*i_xyz[1] + i_xyz[0]]; +} + +//============================================================================== + void HexLattice::to_hdf5_inner(hid_t lat_group) const { @@ -874,6 +917,11 @@ extern "C" { local_xyz[2] = xyz[2]; } + int32_t lattice_offset(Lattice *lat, int map, const int i_xyz[3]) + { + return lat->offset(map, i_xyz); + } + int32_t lattice_outer(Lattice *lat) {return lat->outer;} void lattice_to_hdf5(Lattice *lat, hid_t group) {lat->to_hdf5(group);} diff --git a/src/lattice.h b/src/lattice.h index 938294ae4..9b55025b5 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -41,11 +41,11 @@ class LatticeIter; class Lattice { public: - int32_t id; //! Universe ID number - std::string name; //! User-defined name - std::vector universes; //! Universes filling each lattice tile - int32_t outer{NO_OUTER_UNIVERSE}; //! Universe tiled outside the lattice - //std::vector offset; //! Distribcell offsets + int32_t id; //!< Universe ID number + std::string name; //!< User-defined name + std::vector universes; //!< Universes filling each lattice tile + int32_t outer{NO_OUTER_UNIVERSE}; //!< Universe tiled outside the lattice + std::vector offsets; //!< Distribcell offset table explicit Lattice(pugi::xml_node lat_node); @@ -59,6 +59,11 @@ public: //! Convert internal universe values from IDs to indices using universe_dict. void adjust_indices(); + //! Allocate offset table for distribcell. + void allocate_offset_table(int n_maps); + + int32_t fill_offset_table(int32_t offset, int32_t target_univ_id, int map); + //! Check lattice indices. //! @param i_xyz[3] The indices for a lattice tile. //! @return true if the given indices fit within the lattice bounds. False @@ -96,6 +101,13 @@ public: return (indx > 0) && (indx < universes.size()); } + //! Get the distribcell offset for a lattice tile. + //! @param The map index for the target cell. + //! @param i_xyz[3] The indices for a lattice tile. + //! @return Distribcell offset i.e. the largest instance number for the target + //! cell found in the geometry tree under this lattice tile. + virtual int32_t& offset(int map, const int i_xyz[3]) = 0; + //! Write all information needed to reconstruct the lattice to an HDF5 group. //! @param group_id An HDF5 group id. void to_hdf5(hid_t group_id) const; @@ -166,12 +178,14 @@ public: std::array get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const; + int32_t& offset(int map, const int i_xyz[3]); + void to_hdf5_inner(hid_t group_id) const; protected: - std::array n_cells; //! Number of cells along each axis - std::array lower_left; //! Global lower-left corner of the lattice - std::array pitch; //! Lattice tile width along each axis + std::array n_cells; //!< Number of cells along each axis + std::array lower_left; //!< Global lower-left corner of the lattice + std::array pitch; //!< Lattice tile width along each axis }; //============================================================================== @@ -200,13 +214,15 @@ public: bool is_valid_index(int indx) const; + int32_t& offset(int map, const int i_xyz[3]); + void to_hdf5_inner(hid_t group_id) const; protected: - int n_rings; //! Number of radial tile positions - int n_axial; //! Number of axial tile positions - std::array center; //! Global center of lattice - std::array pitch; //! Lattice tile width and height + int n_rings; //!< Number of radial tile positions + int n_axial; //!< Number of axial tile positions + std::array center; //!< Global center of lattice + std::array pitch; //!< Lattice tile width and height }; } // namespace openmc diff --git a/src/summary.F90 b/src/summary.F90 index 964ec34a3..1d3349123 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -226,11 +226,6 @@ contains case (FILL_UNIVERSE) call write_dataset(cell_group, "fill_type", "universe") call write_dataset(cell_group, "fill", universes(c%fill)%id) - if (allocated(c%offset)) then - if (size(c%offset) > 0) then - call write_dataset(cell_group, "offset", c%offset) - end if - end if if (allocated(c%translation)) then call write_dataset(cell_group, "translation", c%translation) diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index d804fe0ea..ce2549f58 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -59,19 +59,19 @@ contains offset = 0 do i = 1, p % n_coord if (cells(p % coord(i) % cell) % type() == FILL_UNIVERSE) then - offset = offset + cells(p % coord(i) % cell) % & - offset(distribcell_index) + offset = offset + cells(p % coord(i) % cell) & + % offset(distribcell_index-1) elseif (cells(p % coord(i) % cell) % type() == FILL_LATTICE) then if (lattices(p % coord(i + 1) % lattice) % obj & % are_valid_indices([& p % coord(i + 1) % lattice_x, & p % coord(i + 1) % lattice_y, & p % coord(i + 1) % lattice_z])) then - offset = offset + lattices(p % coord(i + 1) % lattice) % obj % & - offset(distribcell_index, & - p % coord(i + 1) % lattice_x, & - p % coord(i + 1) % lattice_y, & - p % coord(i + 1) % lattice_z) + offset = offset + lattices(p % coord(i + 1) % lattice) % obj & + % offset(distribcell_index - 1, & + [p % coord(i + 1) % lattice_x - 1, & + p % coord(i + 1) % lattice_y - 1, & + p % coord(i + 1) % lattice_z - 1]) end if end if if (this % cell == p % coord(i) % cell) then @@ -211,12 +211,12 @@ contains ! Two cases, lattice or fill cell if (c % type() == FILL_UNIVERSE) then - temp_offset = c % offset(map) + temp_offset = c % offset(map-1) ! Get the offset of the first lattice location else lat => lattices(c % fill) % obj - temp_offset = lat % offset(map, 1, 1, 1) + temp_offset = lat % offset(map-1, [0, 0, 0]) end if ! If the final offset is in the range of offset - temp_offset+offset @@ -248,7 +248,7 @@ contains if (c % type() == FILL_UNIVERSE) then ! Enter this cell to update the current offset - offset = c % offset(map) + offset + offset = c % offset(map-1) + offset next_univ => universes(c % fill) call find_offset(i_cell, next_univ, target_offset, offset, path) @@ -282,10 +282,11 @@ contains do l = 1, n_y do k = 1, n_x - if (target_offset >= lat % offset(map, k, l, m) + offset) then + if (target_offset >= lat % offset(map-1, [k-1, l-1, m-1]) & + + offset) then if (k == n_x .and. l == n_y .and. m == n_z) then ! This is last lattice cell, so target must be here - lat_offset = lat % offset(map, k, l, m) + lat_offset = lat % offset(map-1, [k-1, l-1, m-1]) offset = offset + lat_offset next_univ => universes(lat % get([k-1, l-1, m-1])+1) if (lat % is_3d) then @@ -306,7 +307,7 @@ contains end if else ! Target is at this lattice position - lat_offset = lat % offset(map, old_k, old_l, old_m) + lat_offset = lat % offset(map-1, [old_k-1, old_l-1, old_m-1]) offset = offset + lat_offset next_univ => universes(lat % get([old_k-1, old_l-1, old_m-1])+1) if (lat % is_3d) then @@ -352,10 +353,11 @@ contains cycle end if - if (target_offset >= lat % offset(map, k, l, m) + offset) then + if (target_offset >= lat % offset(map-1, [k-1, l-1, m-1]) & + + offset) then if (k == lat % n_rings .and. l == n_y .and. m == n_z) then ! This is last lattice cell, so target must be here - lat_offset = lat % offset(map, k, l, m) + lat_offset = lat % offset(map-1, [k-1, l-1, m-1]) offset = offset + lat_offset next_univ => universes(lat % get([k-1, l-1, m-1])+1) if (lat % is_3d) then @@ -378,7 +380,7 @@ contains end if else ! Target is at this lattice position - lat_offset = lat % offset(map, old_k, old_l, old_m) + lat_offset = lat % offset(map-1, [old_k-1, old_l-1, old_m-1]) offset = offset + lat_offset next_univ => universes(lat % get([old_k-1, old_l-1, old_m-1])+1) if (lat % is_3d) then From 8598d99c32c92b2a268adc8d18c7cb949b447805 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 21 May 2018 16:55:44 -0400 Subject: [PATCH 270/361] Separate regression and unit tests in CI script --- tools/ci/travis-script.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/ci/travis-script.sh b/tools/ci/travis-script.sh index ee445b517..6dfbc9510 100755 --- a/tools/ci/travis-script.sh +++ b/tools/ci/travis-script.sh @@ -8,7 +8,9 @@ fi # Run regression and unit tests if [[ $MPI == 'y' ]]; then - pytest --cov=openmc -v --mpi tests + pytest --cov=openmc -v --mpi tests/regression_tests + pytest --cov=openmc -v --mpi tests/unit_tests else - pytest --cov=openmc -v tests + pytest --cov=openmc -v tests/regression_tests + pytest --cov=openmc -v tests/unit_tests fi From a47f3abbde71fb5b952cd6156698db08bd6197e2 Mon Sep 17 00:00:00 2001 From: tjlaboss Date: Mon, 21 May 2018 17:13:37 -0400 Subject: [PATCH 271/361] New class method, Mesh.fromRectLattice() --- openmc/mesh.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/openmc/mesh.py b/openmc/mesh.py index c9c76552b..7c6fc4867 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -182,6 +182,40 @@ class Mesh(IDManagerMixin): return mesh + @classmethod + def fromRectLattice(cls, lattice, division=1, mesh_id=None, name=''): + """Create mesh from an existing rectangular lattice + + Parameters + ---------- + lattice : openmc.RectLattice + Rectangular lattice used as a template for this mesh + division : int, optional + Number of mesh cells per lattice cell. + If not specified, there will be 1 mesh cell per lattice cell. + mesh_id : int, optional + Unique identifier for the mesh + name : str, optional + Name of the mesh + + Returns + ------- + openmc.Mesh + Mesh instance + + """ + cv.check_type('rectangular lattice', lattice, openmc.RectLattice) + + shape = np.array(lattice.shape) + width = lattice.pitch*shape + + mesh = cls(mesh_id, name) + mesh.lower_left = lattice.lower_left + mesh.upper_right = lattice.lower_left + width + mesh.dimension = shape*division + + return mesh + def to_xml_element(self): """Return XML representation of the mesh From 411fd288ea69fbf7d1678444c5d5542ea448f304 Mon Sep 17 00:00:00 2001 From: Alex Lindsay Date: Thu, 26 Apr 2018 10:04:38 -0600 Subject: [PATCH 272/361] Expand C API, targeting FETs --- include/openmc.h | 1 + openmc/capi/filter.py | 103 ++++++++++++++++++++++++++++-- src/api.F90 | 1 + src/tallies/tally_filter_cell.F90 | 27 +++++++- 4 files changed, 127 insertions(+), 5 deletions(-) diff --git a/include/openmc.h b/include/openmc.h index 38b7f2167..ce28fa2d0 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -17,6 +17,7 @@ extern "C" { }; int openmc_calculate_volumes(); + int openmc_cell_filter_get_bins(int32_t index, int32_t** cells, int32_t* n); int openmc_cell_get_fill(int32_t index, int* type, int32_t** indices, int32_t* n); int openmc_cell_get_id(int32_t index, int32_t* id); int openmc_cell_set_fill(int32_t index, int type, int32_t n, const int32_t* indices); diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index 5c21c3ef4..4f5b46915 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -17,11 +17,16 @@ from .mesh import Mesh __all__ = ['Filter', 'AzimuthalFilter', 'CellFilter', 'CellbornFilter', 'CellfromFilter', 'DistribcellFilter', 'DelayedGroupFilter', 'EnergyFilter', 'EnergyoutFilter', - 'EnergyFunctionFilter', 'MaterialFilter', 'MeshFilter', - 'MeshSurfaceFilter', 'MuFilter', 'PolarFilter', 'SurfaceFilter', - 'UniverseFilter', 'filters'] + 'EnergyFunctionFilter', 'LegendreFilter', 'MaterialFilter', 'MeshFilter', + 'MeshSurfaceFilter', 'MuFilter', 'PolarFilter', 'SphericalHarmonicsFilter', + 'SpatialLegendreFilter', 'SurfaceFilter', + 'UniverseFilter', 'ZernikeFilter', 'filters'] # Tally functions +_dll.openmc_cell_filter_get_bins.argtypes = [ + c_int32, POINTER(POINTER(c_int32)), POINTER(c_int32)] +_dll.openmc_cell_filter_get_bins.restype = c_int +_dll.openmc_cell_filter_get_bins.errcheck = _error_handler _dll.openmc_energy_filter_get_bins.argtypes = [ c_int32, POINTER(POINTER(c_double)), POINTER(c_int32)] _dll.openmc_energy_filter_get_bins.restype = c_int @@ -47,6 +52,12 @@ _dll.openmc_filter_set_type.errcheck = _error_handler _dll.openmc_get_filter_index.argtypes = [c_int32, POINTER(c_int32)] _dll.openmc_get_filter_index.restype = c_int _dll.openmc_get_filter_index.errcheck = _error_handler +_dll.openmc_legendre_filter_get_order.argtypes = [c_int32, POINTER(c_int)] +_dll.openmc_legendre_filter_get_order.restype = c_int +_dll.openmc_legendre_filter_get_order.errcheck = _error_handler +_dll.openmc_legendre_filter_set_order.argtypes = [c_int32, c_int] +_dll.openmc_legendre_filter_set_order.restype = c_int +_dll.openmc_legendre_filter_set_order.errcheck = _error_handler _dll.openmc_material_filter_get_bins.argtypes = [ c_int32, POINTER(POINTER(c_int32)), POINTER(c_int32)] _dll.openmc_material_filter_get_bins.restype = c_int @@ -66,7 +77,24 @@ _dll.openmc_meshsurface_filter_get_mesh.errcheck = _error_handler _dll.openmc_meshsurface_filter_set_mesh.argtypes = [c_int32, c_int32] _dll.openmc_meshsurface_filter_set_mesh.restype = c_int _dll.openmc_meshsurface_filter_set_mesh.errcheck = _error_handler - +_dll.openmc_spatial_legendre_filter_get_order.argtypes = [c_int32, POINTER(c_int)] +_dll.openmc_spatial_legendre_filter_get_order.restype = c_int +_dll.openmc_spatial_legendre_filter_get_order.errcheck = _error_handler +_dll.openmc_spatial_legendre_filter_set_order.argtypes = [c_int32, c_int] +_dll.openmc_spatial_legendre_filter_set_order.restype = c_int +_dll.openmc_spatial_legendre_filter_set_order.errcheck = _error_handler +_dll.openmc_sphharm_filter_get_order.argtypes = [c_int32, POINTER(c_int)] +_dll.openmc_sphharm_filter_get_order.restype = c_int +_dll.openmc_sphharm_filter_get_order.errcheck = _error_handler +_dll.openmc_sphharm_filter_set_order.argtypes = [c_int32, c_int] +_dll.openmc_sphharm_filter_set_order.restype = c_int +_dll.openmc_sphharm_filter_set_order.errcheck = _error_handler +_dll.openmc_zernike_filter_get_order.argtypes = [c_int32, POINTER(c_int)] +_dll.openmc_zernike_filter_get_order.restype = c_int +_dll.openmc_zernike_filter_get_order.errcheck = _error_handler +_dll.openmc_zernike_filter_set_order.argtypes = [c_int32, c_int] +_dll.openmc_zernike_filter_set_order.restype = c_int +_dll.openmc_zernike_filter_set_order.errcheck = _error_handler class Filter(_FortranObjectWithID): __instances = WeakValueDictionary() @@ -150,6 +178,13 @@ class AzimuthalFilter(Filter): class CellFilter(Filter): filter_type = 'cell' + @property + def bins(self): + cells = POINTER(c_int32)() + n = c_int32() + _dll.openmc_cell_filter_get_bins(self._index, cells, n) + return as_array(cells, (n.value,)) + class CellbornFilter(Filter): filter_type = 'cellborn' @@ -171,6 +206,20 @@ class EnergyFunctionFilter(Filter): filter_type = 'energyfunction' +class LegendreFilter(Filter): + filter_type = 'legendre' + + @property + def order(self): + temp_order = c_int() + _dll.openmc_legendre_filter_get_order(self._index, temp_order) + return temp_order.value + + @order.setter + def order(self, order): + _dll.openmc_legendre_filter_set_order(self._index, order) + + class MaterialFilter(Filter): filter_type = 'material' @@ -241,6 +290,34 @@ class PolarFilter(Filter): filter_type = 'polar' +class SphericalHarmonicsFilter(Filter): + filter_type = 'sphericalharmonics' + + @property + def order(self): + temp_order = c_int() + _dll.openmc_sphharm_filter_get_order(self._index, temp_order) + return temp_order.value + + @order.setter + def order(self, order): + _dll.openmc_sphharm_filter_set_order(self._index, order) + + +class SpatialLegendreFilter(Filter): + filter_type = 'spatiallegendre' + + @property + def order(self): + temp_order = c_int() + _dll.openmc_spatial_legendre_filter_get_order(self._index, temp_order) + return temp_order.value + + @order.setter + def order(self, order): + _dll.openmc_spatial_legendre_filter_set_order(self._index, order) + + class SurfaceFilter(Filter): filter_type = 'surface' @@ -249,6 +326,20 @@ class UniverseFilter(Filter): filter_type = 'universe' +class ZernikeFilter(Filter): + filter_type = 'zernike' + + @property + def order(self): + temp_order = c_int() + _dll.openmc_zernike_filter_get_order(self._index, temp_order) + return temp_order.value + + @order.setter + def order(self, order): + _dll.openmc_zernike_filter_set_order(self._index, order) + + _FILTER_TYPE_MAP = { 'azimuthal': AzimuthalFilter, 'cell': CellFilter, @@ -259,13 +350,17 @@ _FILTER_TYPE_MAP = { 'energy': EnergyFilter, 'energyout': EnergyoutFilter, 'energyfunction': EnergyFunctionFilter, + 'legendre': LegendreFilter, 'material': MaterialFilter, 'mesh': MeshFilter, 'meshsurface': MeshSurfaceFilter, 'mu': MuFilter, 'polar': PolarFilter, + 'sphericalharmoics': SphericalHarmonicsFilter, + 'spatiallegendre': SpatialLegendreFilter, 'surface': SurfaceFilter, 'universe': UniverseFilter, + 'zernike': ZernikeFilter } diff --git a/src/api.F90 b/src/api.F90 index da50007d6..1740fa2ab 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -35,6 +35,7 @@ module openmc_api private public :: openmc_calculate_volumes + public :: openmc_cell_filter_get_bins public :: openmc_cell_get_id public :: openmc_cell_get_fill public :: openmc_cell_set_fill diff --git a/src/tallies/tally_filter_cell.F90 b/src/tallies/tally_filter_cell.F90 index 00ab8fa15..99a9892a7 100644 --- a/src/tallies/tally_filter_cell.F90 +++ b/src/tallies/tally_filter_cell.F90 @@ -14,13 +14,14 @@ module tally_filter_cell implicit none private + public :: openmc_cell_filter_get_bins !=============================================================================== ! CELLFILTER specifies which geometric cells tally events reside in. !=============================================================================== type, public, extends(TallyFilter) :: CellFilter - integer, allocatable :: cells(:) + integer(C_INT32_T), allocatable :: cells(:) type(DictIntInt) :: map contains procedure :: from_xml @@ -116,4 +117,28 @@ contains label = "Cell " // to_str(cells(this % cells(bin)) % id) end function text_label_cell +!=============================================================================== +! C API FUNCTIONS +!=============================================================================== + + function openmc_cell_filter_get_bins(index, cells, n) result(err) bind(C) + ! Return the cells associated with a cell filter + integer(C_INT32_T), value :: index + type(C_PTR), intent(out) :: cells + integer(C_INT32_T), intent(out) :: n + integer(C_INT) :: err + + err = verify_filter(index) + if (err == 0) then + select type (f => filters(index) % obj) + type is (CellFilter) + cells = C_LOC(f % cells) + n = size(f % cells) + class default + err = E_INVALID_TYPE + call set_errmsg("Tried to get cells from a non-cell filter.") + end select + end if + end function openmc_cell_filter_get_bins + end module tally_filter_cell From 5a863215f310a8d252fc3f136a29c679bc428bf9 Mon Sep 17 00:00:00 2001 From: Alex Lindsay Date: Mon, 7 May 2018 14:41:51 -0600 Subject: [PATCH 273/361] Take char* instead of char** --- include/openmc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/openmc.h b/include/openmc.h index ce28fa2d0..a9ffa74d7 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -32,7 +32,7 @@ extern "C" { int openmc_extend_sources(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_extend_tallies(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_filter_get_id(int32_t index, int32_t* id); - int openmc_filter_get_type(int32_t index, const char** type); + int openmc_filter_get_type(int32_t index, const char* type); int openmc_filter_set_id(int32_t index, int32_t id); int openmc_filter_set_type(int32_t index, const char* type); int openmc_finalize(); From 85e6eb961680e1d3066beb5c57d9beb41ec9c103 Mon Sep 17 00:00:00 2001 From: Alex Lindsay Date: Mon, 21 May 2018 14:14:00 -0600 Subject: [PATCH 274/361] hitting out of bounds error in fortran --- openmc/capi/filter.py | 2 +- tests/unit_tests/test_capi.py | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index 4f5b46915..611efb7fd 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -356,7 +356,7 @@ _FILTER_TYPE_MAP = { 'meshsurface': MeshSurfaceFilter, 'mu': MuFilter, 'polar': PolarFilter, - 'sphericalharmoics': SphericalHarmonicsFilter, + 'sphericalharmonics': SphericalHarmonicsFilter, 'spatiallegendre': SpatialLegendreFilter, 'surface': SurfaceFilter, 'universe': UniverseFilter, diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index a21e858fd..c353d8317 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -26,6 +26,15 @@ def pincell_model(): mat_tally.scores = ['total', 'elastic', '(n,gamma)'] pincell.tallies.append(mat_tally) + # Add an expansion tally + zernike_tally = openmc.Tally() + filter3 = openmc.ZernikeFilter(5, r=.63) + cells = pincell.geometry.root_universe.cells + filter4 = openmc.CellFilter(list(cells.values())) + zernike_tally.filters = [filter3, filter4] + zernike_tally.scores = ['fission'] + pincell.tallies.append(zernike_tally) + # Write XML files in tmpdir with cdtemp(): pincell.export_to_xml() @@ -132,7 +141,7 @@ def test_settings(capi_init): def test_tally_mapping(capi_init): tallies = openmc.capi.tallies assert isinstance(tallies, Mapping) - assert len(tallies) == 1 + assert len(tallies) == 2 for tally_id, tally in tallies.items(): assert isinstance(tally, openmc.capi.Tally) assert tally_id == tally.id @@ -168,6 +177,16 @@ def test_tally(capi_init): t.active = True assert t.active + t2 = openmc.capi.tallies[2] + t2.id = 2 + assert len(t2.filters) == 2 + assert isinstance(t2.filters[0], openmc.capi.ZernikeFilter) + assert isinstance(t2.filters[1], openmc.capi.CellFilter) + assert len(t2.filters[1].bins) == 3 + assert t2.filters[0].order == 5 + t2.filters[0].order = 7 + openmc.capi.simulation_init() + def test_new_tally(capi_init): with pytest.raises(exc.AllocationError): @@ -176,7 +195,7 @@ def test_new_tally(capi_init): new_tally.scores = ['flux'] new_tally_with_id = openmc.capi.Tally(10) new_tally_with_id.scores = ['flux'] - assert len(openmc.capi.tallies) == 3 + assert len(openmc.capi.tallies) == 4 def test_tally_results(capi_run): @@ -187,6 +206,9 @@ def test_tally_results(capi_run): assert np.all(t.std_dev[nonzero] >= 0) assert np.all(t.ci_width()[nonzero] >= 1.95*t.std_dev[nonzero]) + t2 = openmc.capi.tallies[2] + assert t2.mean.size == 108 # 36 coefficients for 7th order Zernike * 3 cells + def test_global_tallies(capi_run): assert openmc.capi.num_realizations() == 5 From 609877e131dda4aad7d7db0866509026c8ec80bd Mon Sep 17 00:00:00 2001 From: Alex Lindsay Date: Mon, 21 May 2018 15:18:05 -0600 Subject: [PATCH 275/361] Give up on setter test for now. --- tests/unit_tests/test_capi.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index c353d8317..11a47b150 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -184,8 +184,6 @@ def test_tally(capi_init): assert isinstance(t2.filters[1], openmc.capi.CellFilter) assert len(t2.filters[1].bins) == 3 assert t2.filters[0].order == 5 - t2.filters[0].order = 7 - openmc.capi.simulation_init() def test_new_tally(capi_init): @@ -207,7 +205,7 @@ def test_tally_results(capi_run): assert np.all(t.ci_width()[nonzero] >= 1.95*t.std_dev[nonzero]) t2 = openmc.capi.tallies[2] - assert t2.mean.size == 108 # 36 coefficients for 7th order Zernike * 3 cells + assert t2.mean.size == 63 # 21 coefficients for 5th order Zernike * 3 cells def test_global_tallies(capi_run): From d9c24371b6f94327de9a11000bf085910106c07e Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 21 May 2018 17:23:35 -0400 Subject: [PATCH 276/361] Simplify distribcell initialization --- src/input_xml.F90 | 105 ++++++++------------------------------ src/nuclide_header.F90 | 1 - src/simulation_header.F90 | 3 -- 3 files changed, 21 insertions(+), 88 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index c68ed4a8c..b708974b8 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -3837,39 +3837,29 @@ contains subroutine prepare_distribcell() - integer :: i, j ! Tally, filter loop counters - logical :: distribcell_active ! Does simulation use distribcell? - integer, allocatable :: univ_list(:) ! Target offsets + integer :: i, j, k + type(SetInt) :: cell_list ! distribcells to track + type(ListInt) :: univ_list ! universes containing distribcells - ! Assume distribcell is not needed until proven otherwise. - distribcell_active = .false. - - ! We need distribcell if any tallies have distribcell filters. + ! Find all cells listed in a distribcell filter. do i = 1, n_tallies do j = 1, size(tallies(i) % obj % filter) select type(filt => filters(tallies(i) % obj % filter(j)) % obj) type is (DistribcellFilter) - distribcell_active = .true. + call cell_list % add(filt % cell) end select end do end do - ! We also need distribcell if any distributed materials or distributed - ! temperatues are present. - if (.not. distribcell_active) then - do i = 1, n_cells - if (size(cells(i) % material) > 1 .or. size(cells(i) % sqrtkT) > 1) then - distribcell_active = .true. - exit - end if - end do - end if + ! Find all cells with multiple (distributed) materials or temperatures. + do i = 1, n_cells + if (size(cells(i) % material) > 1 .or. size(cells(i) % sqrtkT) > 1) then + call cell_list % add(i) + end if + end do - ! If distribcell isn't used in this simulation then no more work left to do. - if (.not. distribcell_active) return - - ! Make sure the number of materials and temperatures matches the number of - ! cell instances. + ! Make sure the number of distributed materials and temperatures matches the + ! number of respective cell instances. do i = 1, n_cells associate (c => cells(i)) if (size(c % material) > 1) then @@ -3885,7 +3875,7 @@ contains if (size(c % sqrtkT) /= c % n_instances()) then call fatal_error("Cell " // trim(to_str(c % id())) // " was & &specified with " // trim(to_str(size(c % sqrtkT))) & - // " temperatures but has " // trim(to_str(c % n_instances())) & + // " temperatures but has " // trim(to_str(c % n_instances()))& // " distributed instances. The number of temperatures must & &equal one or the number of instances.") end if @@ -3893,62 +3883,6 @@ contains end associate end do - ! Allocate offset maps at each level in the geometry - call allocate_offsets(univ_list) - call allocate_offset_tables(n_maps) - - ! Calculate offsets for each target distribcell - do i = 1, n_maps - call fill_offset_tables(univ_list(i), i-1) - end do - - end subroutine prepare_distribcell - -!=============================================================================== -! ALLOCATE_OFFSETS determines the number of maps needed and allocates required -! memory for distribcell offset tables -!=============================================================================== - - recursive subroutine allocate_offsets(univ_list) - - integer, intent(out), allocatable :: univ_list(:) ! Target offsets - - integer :: i, j, k ! Loop counters - type(SetInt) :: cell_list ! distribells to track - - ! Begin gathering list of cells in distribcell tallies - n_maps = 0 - - ! List all cells referenced in distribcell filters. - do i = 1, n_tallies - do j = 1, size(tallies(i) % obj % filter) - select type(filt => filters(tallies(i) % obj % filter(j)) % obj) - type is (DistribcellFilter) - call cell_list % add(filt % cell) - end select - end do - end do - - ! List all cells with multiple (distributed) materials or temperatures. - do i = 1, n_cells - if (size(cells(i) % material) > 1 .or. size(cells(i) % sqrtkT) > 1) then - call cell_list % add(i) - end if - end do - - ! Compute the number of unique universes containing these distribcells - ! to determine the number of offset tables to allocate - do i = 1, n_universes - do j = 1, size(universes(i) % cells) - if (cell_list % contains(universes(i) % cells(j))) then - n_maps = n_maps + 1 - end if - end do - end do - - ! Allocate the list of offset tables for each unique universe - allocate(univ_list(n_maps)) - ! Search through universes for distributed cells and assign each one a ! unique distribcell array index. k = 1 @@ -3956,15 +3890,18 @@ contains do j = 1, size(universes(i) % cells) if (cell_list % contains(universes(i) % cells(j))) then cells(universes(i) % cells(j)) % distribcell_index = k - univ_list(k) = universes(i) % id + call univ_list % append(universes(i) % id) k = k + 1 end if end do end do - ! Free up memory - call cell_list % clear() + ! Allocate and fill cell and lattice offset tables. + call allocate_offset_tables(univ_list % size()) + do i = 1, univ_list % size() + call fill_offset_tables(univ_list % get_item(i), i-1) + end do - end subroutine allocate_offsets + end subroutine prepare_distribcell end module input_xml diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index ebabe5cdb..17880f6e1 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -10,7 +10,6 @@ module nuclide_header use endf_header, only: Function1D, Polynomial, Tabulated1D use error use hdf5_interface - 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, & diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index 8fe5e2da1..806bcc65e 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -72,9 +72,6 @@ module simulation_header logical :: trace - ! Number of distribcell maps - integer :: n_maps - !$omp threadprivate(trace, thread_id, current_work) contains From 53e6a8d766cf91fa4d7ccbcd7b25d2f73bde7525 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 21 May 2018 19:36:07 -0400 Subject: [PATCH 277/361] Move maximum_levels to C++ --- src/geometry.F90 | 69 -------------------------------------------- src/geometry_aux.cpp | 26 +++++++++++++++++ src/geometry_aux.h | 9 ++++++ src/input_xml.F90 | 10 +++++-- 4 files changed, 43 insertions(+), 71 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index c3b647a82..7fdd03ad8 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -534,73 +534,4 @@ contains end subroutine neighbor_lists -!=============================================================================== -! MAXIMUM_LEVELS determines the maximum number of nested coordinate levels in -! the geometry -!=============================================================================== - - recursive function maximum_levels(univ) result(levels) - - type(Universe), intent(in) :: univ ! universe to search through - integer :: levels ! maximum number of levels for this universe - - integer :: i ! index over cells - integer :: nx, ny, nz ! lattice shape - integer :: j, k, m ! indices in lattice - integer :: levels_below ! max levels below this universe - type(Cell), pointer :: c ! pointer to current cell - type(Universe), pointer :: next_univ ! next universe to loop through - class(Lattice), pointer :: lat ! pointer to current lattice - - levels_below = 0 - do i = 1, size(univ % cells) - c => cells(univ % cells(i)) - - ! ==================================================================== - ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL - if (c % type() == FILL_UNIVERSE) then - - next_univ => universes(c % fill) - levels_below = max(levels_below, maximum_levels(next_univ)) - - ! ==================================================================== - ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type() == FILL_LATTICE) then - - ! Set current lattice - lat => lattices(c % fill) % obj - - select type (lat) - - type is (RectLattice) - nx = lat % n_cells(1) - ny = lat % n_cells(2) - nz = lat % n_cells(3) - - type is (HexLattice) - nx = 2 * lat % n_rings - 1 - ny = 2 * lat % n_rings - 1 - nz = lat % n_axial - - end select - - ! Loop over lattice coordinates - do j = 1, nx - do k = 1, ny - do m = 1, nz - if (lat % are_valid_indices([j, k, m])) then - next_univ => universes(lat % get([j-1, k-1, m-1]) + 1) - levels_below = max(levels_below, maximum_levels(next_univ)) - end if - end do - end do - end do - - end if - end do - - levels = 1 + levels_below - - end function maximum_levels - end module geometry diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 068554240..6efe1cf99 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -1,5 +1,6 @@ #include "geometry_aux.h" +#include // for std::max #include #include @@ -197,4 +198,29 @@ fill_offset_tables(int32_t target_univ_id, int map) } } +//============================================================================== + +int +maximum_levels(int32_t univ) +{ + int levels_below {0}; + + for (int32_t cell_indx : universes_c[univ]->cells) { + Cell &c = *cells_c[cell_indx]; + if (c.type == FILL_UNIVERSE) { + int32_t next_univ = c.fill - 1; // TODO: off-by-one + levels_below = std::max(levels_below, maximum_levels(next_univ)); + } else if (c.type == FILL_LATTICE) { + Lattice &lat = *lattices_c[c.fill - 1]; //TODO: off-by-one + for (auto it = lat.begin(); it != lat.end(); ++it) { + int32_t next_univ = *it; + levels_below = std::max(levels_below, maximum_levels(next_univ)); + } + } + } + + ++levels_below; + return levels_below; +} + } // namespace openmc diff --git a/src/geometry_aux.h b/src/geometry_aux.h index 0a0500794..877f51e4e 100644 --- a/src/geometry_aux.h +++ b/src/geometry_aux.h @@ -62,5 +62,14 @@ count_universe_instances(int32_t search_univ, int32_t target_univ_id); extern "C" void fill_offset_tables(int32_t target_univ_id, int map); +//============================================================================== +//! Determine the maximum number of nested coordinate levels in the geometry. +//! @param univ The index of the universe to begin seraching from (probably the +//! root universe). +//! @return The number of coordinate levels. +//============================================================================== + +extern "C" int maximum_levels(int32_t univ); + } // namespace openmc #endif // GEOMETRY_AUX_H diff --git a/src/input_xml.F90 b/src/input_xml.F90 index b708974b8..d325928a6 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -11,7 +11,7 @@ module input_xml use distribution_univariate use endf, only: reaction_name use error, only: fatal_error, warning, write_message, openmc_err_msg - use geometry, only: maximum_levels, neighbor_lists + use geometry, only: neighbor_lists use geometry_header use hdf5_interface use list_header, only: ListChar, ListInt, ListReal @@ -85,6 +85,12 @@ module input_xml import C_INT32_T integer(C_INT32_T) :: root end function find_root_universe + + function maximum_levels(univ) bind(C) result(n) + import C_INT32_T, C_INT + integer(C_INT32_T), intent(in), value :: univ + integer(C_INT) :: n + end function maximum_levels end interface contains @@ -169,7 +175,7 @@ contains ! Check to make sure there are not too many nested coordinate levels in the ! geometry since the coordinate list is statically allocated for performance ! reasons - if (maximum_levels(universes(root_universe)) > MAX_COORD) then + if (maximum_levels(root_universe - 1) > MAX_COORD) then call fatal_error("Too many nested coordinate levels in the geometry. & &Try increasing the maximum number of coordinate levels by & &providing the CMake -Dmaxcoord= option.") From 07ded4e7be55edc6bcdd5dcb9df8f2e5d79aad65 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 21 May 2018 21:25:22 -0400 Subject: [PATCH 278/361] Remove cell % fill from F90 --- src/cell.cpp | 19 +++++++++++++ src/constants.F90 | 1 + src/geometry.F90 | 6 ++--- src/geometry_aux.cpp | 20 +++++++------- src/geometry_header.F90 | 34 +++++++++++++++--------- src/input_xml.F90 | 33 +++-------------------- src/summary.F90 | 9 ++++--- src/tallies/tally_filter_distribcell.F90 | 6 ++--- 8 files changed, 66 insertions(+), 62 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index 1d630b3d4..82138a83f 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -234,6 +234,21 @@ Cell::Cell(pugi::xml_node cell_node) material.shrink_to_fit(); } + // Make sure that either material or fill was specified. + if ((material[0] == C_NONE) && (fill == C_NONE)) { + std::stringstream err_msg; + err_msg << "Neither material nor fill was specified for cell " << id; + fatal_error(err_msg); + } + + // Make sure that material and fill haven't been specified simultaneously. + if ((material[0] != C_NONE) && (fill != C_NONE)) { + std::stringstream err_msg; + err_msg << "Cell " << id << " has both a material and a fill specified; " + << "only one can be specified per cell"; + fatal_error(err_msg); + } + std::string region_spec {""}; if (check_for_node(cell_node, "region")) { region_spec = get_node_value(cell_node, "region"); @@ -470,6 +485,10 @@ extern "C" { void cell_set_universe(Cell *c, int32_t universe) {c->universe = universe;} + int32_t cell_fill(Cell *c) {return c->fill;} + + int32_t* cell_fill_ptr(Cell *c) {return &c->fill;} + int32_t cell_n_instances(Cell *c) {return c->n_instances;} bool cell_simple(Cell *c) {return c->simple;} diff --git a/src/constants.F90 b/src/constants.F90 index dbd4181da..d75ee4e54 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -421,6 +421,7 @@ module constants ! indicates that an array index hasn't been set integer, parameter :: NONE = 0 + integer, parameter :: C_NONE = -1 ! Codes for read errors -- better hope these numbers are never used in an ! input file! diff --git a/src/geometry.F90 b/src/geometry.F90 index 7fdd03ad8..44f138e10 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -222,7 +222,7 @@ contains ! Move particle to next level and set universe j = j + 1 p % n_coord = j - p % coord(j) % universe = c % fill + p % coord(j) % universe = c % fill() + 1 ! Apply translation if (allocated(c % translation)) then @@ -243,7 +243,7 @@ contains ! ====================================================================== ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - associate (lat => lattices(c % fill) % obj) + associate (lat => lattices(c % fill() + 1) % obj) ! Determine lattice indices i_xyz = lat % get_indices(p % coord(j) % xyz + TINY_BIT * p % coord(j) % uvw) @@ -252,7 +252,7 @@ contains p % coord(j + 1) % uvw = p % coord(j) % uvw ! set particle lattice indices - p % coord(j + 1) % lattice = c % fill + p % coord(j + 1) % lattice = c % fill() + 1 p % coord(j + 1) % lattice_x = i_xyz(1) p % coord(j + 1) % lattice_y = i_xyz(2) p % coord(j + 1) % lattice_z = i_xyz(3) diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 6efe1cf99..2bf379a6d 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -27,10 +27,10 @@ adjust_indices_c() auto search_lat = lattice_dict.find(id); if (search_univ != universe_dict.end()) { c->type = FILL_UNIVERSE; - c->fill = search_univ->second + 1; //TODO: off-by-one + c->fill = search_univ->second; } else if (search_lat != lattice_dict.end()) { c->type = FILL_LATTICE; - c->fill = search_lat->second + 1; //TODO: off-by-one + c->fill = search_lat->second; } else { std::stringstream err_msg; err_msg << "Specified fill " << id << " on cell " << c->id @@ -133,11 +133,11 @@ count_cell_instances(int32_t univ_indx) if (c.type == FILL_UNIVERSE) { // This cell contains another universe. Recurse into that universe. - count_cell_instances(c.fill-1); // TODO: off-by-one + count_cell_instances(c.fill); } else if (c.type == FILL_LATTICE) { // This cell contains a lattice. Recurse into the lattice universes. - Lattice &lat = *lattices_c[c.fill-1]; // TODO: off-by-one + Lattice &lat = *lattices_c[c.fill]; for (auto it = lat.begin(); it != lat.end(); ++it) { count_cell_instances(*it); } @@ -160,11 +160,11 @@ count_universe_instances(int32_t search_univ, int32_t target_univ_id) Cell &c = *cells_c[cell_indx]; if (c.type == FILL_UNIVERSE) { - int32_t next_univ = c.fill - 1; // TODO: off-by-one + int32_t next_univ = c.fill; count += count_universe_instances(next_univ, target_univ_id); } else if (c.type == FILL_LATTICE) { - Lattice &lat = *lattices_c[c.fill - 1]; //TODO: off-by-one + Lattice &lat = *lattices_c[c.fill]; for (auto it = lat.begin(); it != lat.end(); ++it) { int32_t next_univ = *it; count += count_universe_instances(next_univ, target_univ_id); @@ -187,11 +187,11 @@ fill_offset_tables(int32_t target_univ_id, int map) if (c.type == FILL_UNIVERSE) { c.offset[map] = offset; - int32_t search_univ = c.fill - 1; // TODO: off-by-one + int32_t search_univ = c.fill; offset += count_universe_instances(search_univ, target_univ_id); } else if (c.type == FILL_LATTICE) { - Lattice &lat = *lattices_c[c.fill - 1]; // TODO: off-by-one + Lattice &lat = *lattices_c[c.fill]; offset = lat.fill_offset_table(offset, target_univ_id, map); } } @@ -208,10 +208,10 @@ maximum_levels(int32_t univ) for (int32_t cell_indx : universes_c[univ]->cells) { Cell &c = *cells_c[cell_indx]; if (c.type == FILL_UNIVERSE) { - int32_t next_univ = c.fill - 1; // TODO: off-by-one + int32_t next_univ = c.fill; levels_below = std::max(levels_below, maximum_levels(next_univ)); } else if (c.type == FILL_LATTICE) { - Lattice &lat = *lattices_c[c.fill - 1]; //TODO: off-by-one + Lattice &lat = *lattices_c[c.fill]; for (auto it = lat.begin(); it != lat.end(); ++it) { int32_t next_univ = *it; levels_below = std::max(levels_below, maximum_levels(next_univ)); diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index fc70a8eec..d019e8fce 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -18,35 +18,30 @@ module geometry_header interface function cell_pointer_c(cell_ind) bind(C, name='cell_pointer') result(ptr) import C_PTR, C_INT32_T - implicit none integer(C_INT32_T), intent(in), value :: cell_ind type(C_PTR) :: ptr end function cell_pointer_c function cell_id_c(cell_ptr) bind(C, name='cell_id') result(id) import C_PTR, C_INT32_T - implicit none type(C_PTR), intent(in), value :: cell_ptr integer(C_INT32_T) :: id end function cell_id_c subroutine cell_set_id_c(cell_ptr, id) bind(C, name='cell_set_id') import C_PTR, C_INT32_T - implicit none type(C_PTR), intent(in), value :: cell_ptr integer(C_INT32_T), intent(in), value :: id end subroutine cell_set_id_c function cell_type_c(cell_ptr) bind(C, name='cell_type') result(type) import C_PTR, C_INT - implicit none type(C_PTR), intent(in), value :: cell_ptr integer(C_INT) :: type end function cell_type_c subroutine cell_set_type_c(cell_ptr, type) bind(C, name='cell_set_type') import C_PTR, C_INT - implicit none type(C_PTR), intent(in), value :: cell_ptr integer(C_INT), intent(in), value :: type end subroutine cell_set_type_c @@ -54,7 +49,6 @@ module geometry_header function cell_universe_c(cell_ptr) bind(C, name='cell_universe') & result(universe) import C_PTR, C_INT32_T - implicit none type(C_PTR), intent(in), value :: cell_ptr integer(C_INT32_T) :: universe end function cell_universe_c @@ -62,22 +56,31 @@ module geometry_header subroutine cell_set_universe_c(cell_ptr, universe) & bind(C, name='cell_set_universe') import C_PTR, C_INT32_T - implicit none type(C_PTR), intent(in), value :: cell_ptr integer(C_INT32_T), intent(in), value :: universe end subroutine cell_set_universe_c + function cell_fill_c(cell_ptr) bind(C, name="cell_fill") result(fill) + import C_PTR, C_INT32_T + type(C_PTR), intent(in), value :: cell_ptr + integer(C_INT32_T) :: fill + end function cell_fill_c + + function cell_fill_ptr(cell_ptr) bind(C) result(fill_ptr) + import C_PTR + type(C_PTR), intent(in), value :: cell_ptr + type(C_PTR) :: fill_ptr + end function cell_fill_ptr + function cell_n_instances_c(cell_ptr) bind(C, name='cell_n_instances') & result(n_instances) import C_PTR, C_INT32_T - implicit none type(C_PTR), intent(in), value :: cell_ptr integer(C_INT32_T) :: n_instances end function cell_n_instances_c function cell_simple_c(cell_ptr) bind(C, name='cell_simple') result(simple) import C_PTR, C_BOOL - implicit none type(C_PTR), intent(in), value :: cell_ptr logical(C_BOOL) :: simple end function cell_simple_c @@ -85,7 +88,6 @@ module geometry_header subroutine cell_distance_c(cell_ptr, xyz, uvw, on_surface, min_dist, & i_surf) bind(C, name="cell_distance") import C_PTR, C_INT32_T, C_DOUBLE - implicit none type(C_PTR), intent(in), value :: cell_ptr real(C_DOUBLE), intent(in) :: xyz(3) real(C_DOUBLE), intent(in) :: uvw(3) @@ -259,7 +261,6 @@ module geometry_header type Cell type(C_PTR) :: ptr - integer :: fill ! universe # filling this cell integer, allocatable :: material(:) ! Material within cell. Multiple ! materials for distribcell ! instances. 0 signifies a universe @@ -284,6 +285,7 @@ module geometry_header procedure :: set_type => cell_set_type procedure :: universe => cell_universe procedure :: set_universe => cell_set_universe + procedure :: fill => cell_fill procedure :: n_instances => cell_n_instances procedure :: simple => cell_simple procedure :: distance => cell_distance @@ -414,6 +416,12 @@ contains call cell_set_universe_c(this % ptr, universe) end subroutine cell_set_universe + function cell_fill(this) result(fill) + class(Cell), intent(in) :: this + integer(C_INT32_T) :: fill + fill = cell_fill_c(this % ptr) + end function cell_fill + function cell_n_instances(this) result(n_instances) class(Cell), intent(in) :: this integer(C_INT32_T) :: n_instances @@ -611,7 +619,7 @@ contains indices = C_LOC(c % material(1)) case (FILL_UNIVERSE, FILL_LATTICE) n = 1 - indices = C_LOC(c % fill) + indices = cell_fill_ptr(c % ptr) end select end associate else @@ -723,7 +731,7 @@ contains if (index >= 1 .and. index <= size(cells)) then ! error if the cell is filled with another universe - if (cells(index) % fill /= NONE) then + if (cells(index) % fill() /= C_NONE) then err = E_GEOMETRY call set_errmsg("Cannot set temperature on a cell filled & &with a universe.") diff --git a/src/input_xml.F90 b/src/input_xml.F90 index d325928a6..501a43a57 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1056,12 +1056,6 @@ contains ! Get pointer to i-th cell node node_cell = node_cell_list(i) - if (check_for_node(node_cell, "fill")) then - call get_node_value(node_cell, "fill", c % fill) - else - c % fill = NONE - end if - ! Check to make sure 'id' hasn't been used if (cell_dict % has(c % id())) then call fatal_error("Two or more cells use the same unique ID: " & @@ -1104,18 +1098,6 @@ contains c % material(1) = NONE end if - ! Check to make sure that either material or fill was specified - if (c % material(1) == NONE .and. c % fill == NONE) then - call fatal_error("Neither material nor fill was specified for cell " & - // trim(to_str(c % id()))) - end if - - ! Check to make sure that both material and fill haven't been - ! specified simultaneously - if (c % material(1) /= NONE .and. c % fill /= NONE) then - call fatal_error("Cannot specify material and fill simultaneously") - end if - ! Check for region specification (also under deprecated name surfaces) if (check_for_node(node_cell, "surfaces")) then call warning("The use of 'surfaces' is deprecated and will be & @@ -1157,7 +1139,7 @@ contains if (check_for_node(node_cell, "rotation")) then ! Rotations can only be applied to cells that are being filled with ! another universe - if (c % fill == NONE) then + if (c % fill() == C_NONE) then call fatal_error("Cannot apply a rotation to cell " // trim(to_str(& &c % id())) // " because it is not filled with another universe") end if @@ -1192,7 +1174,7 @@ contains if (check_for_node(node_cell, "translation")) then ! Translations can only be applied to cells that are being filled with ! another universe - if (c % fill == NONE) then + if (c % fill() == C_NONE) then call fatal_error("Cannot apply a translation to cell " & // trim(to_str(c % id())) // " because it is not filled with & &another universe") @@ -3800,7 +3782,6 @@ contains integer :: i ! index for various purposes integer :: j ! index for various purposes - integer :: lid ! lattice IDs integer :: id ! user-specified id call adjust_indices_c() @@ -3811,15 +3792,7 @@ contains ! ======================================================================= ! ADJUST MATERIAL/FILL POINTERS FOR EACH CELL - if (c % material(1) == NONE) then - id = c % fill - if (universe_dict % has(id)) then - c % fill = universe_dict % get(id) - elseif (lattice_dict % has(id)) then - lid = lattice_dict % get(id) - c % fill = lid - end if - else + if (c % material(1) /= NONE) then do j = 1, size(c % material) id = c % material(j) if (id == MATERIAL_VOID) then diff --git a/src/summary.F90 b/src/summary.F90 index 1d3349123..3b01ddfc9 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -160,7 +160,7 @@ contains subroutine write_geometry(file_id) integer(HID_T), intent(in) :: file_id - integer :: i, j + integer :: i, j, cell_fill integer, allocatable :: cell_materials(:) integer, allocatable :: cell_ids(:) real(8), allocatable :: cell_temperatures(:) @@ -225,7 +225,7 @@ contains case (FILL_UNIVERSE) call write_dataset(cell_group, "fill_type", "universe") - call write_dataset(cell_group, "fill", universes(c%fill)%id) + call write_dataset(cell_group, "fill", universes(c%fill()+1)%id) if (allocated(c%translation)) then call write_dataset(cell_group, "translation", c%translation) @@ -236,7 +236,10 @@ contains case (FILL_LATTICE) call write_dataset(cell_group, "fill_type", "lattice") - call write_dataset(cell_group, "lattice", lattices(c%fill)%obj%id()) + ! Do not access the 'lattices' array with 'c % fill() + 1' directly; it + ! causes a segfault in GCC 7.3.0 + cell_fill = c % fill() + 1 + call write_dataset(cell_group, "lattice", lattices(cell_fill)%obj%id()) end select call close_group(cell_group) diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index ce2549f58..fa0a6b296 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -215,7 +215,7 @@ contains ! Get the offset of the first lattice location else - lat => lattices(c % fill) % obj + lat => lattices(c % fill() + 1) % obj temp_offset = lat % offset(map-1, [0, 0, 0]) end if @@ -250,7 +250,7 @@ contains ! Enter this cell to update the current offset offset = c % offset(map-1) + offset - next_univ => universes(c % fill) + next_univ => universes(c % fill() + 1) call find_offset(i_cell, next_univ, target_offset, offset, path) return @@ -259,7 +259,7 @@ contains elseif (c % type() == FILL_LATTICE) then ! Set current lattice - lat => lattices(c % fill) % obj + lat => lattices(c % fill() + 1) % obj select type (lat) From 7c01594111f25fe4df4bb7a71ec0f13bd1116798 Mon Sep 17 00:00:00 2001 From: Alex Lindsay Date: Tue, 22 May 2018 08:12:49 -0600 Subject: [PATCH 279/361] Add init methods (with order) for capi expansion filters --- include/openmc.h | 2 +- openmc/capi/filter.py | 20 ++++++++++++++++++++ tests/unit_tests/test_capi.py | 3 ++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/include/openmc.h b/include/openmc.h index a9ffa74d7..2c0f10eae 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -32,7 +32,7 @@ extern "C" { int openmc_extend_sources(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_extend_tallies(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_filter_get_id(int32_t index, int32_t* id); - int openmc_filter_get_type(int32_t index, const char* type); + int openmc_filter_get_type(int32_t index, char* type); int openmc_filter_set_id(int32_t index, int32_t id); int openmc_filter_set_type(int32_t index, const char* type); int openmc_finalize(); diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index 611efb7fd..391e63f07 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -209,6 +209,11 @@ class EnergyFunctionFilter(Filter): class LegendreFilter(Filter): filter_type = 'legendre' + def __init__(self, order=None, uid=None, new=True, index=None): + super().__init__(uid, new, index) + if order is not None: + self.order = order + @property def order(self): temp_order = c_int() @@ -293,6 +298,11 @@ class PolarFilter(Filter): class SphericalHarmonicsFilter(Filter): filter_type = 'sphericalharmonics' + def __init__(self, order=None, uid=None, new=True, index=None): + super().__init__(uid, new, index) + if order is not None: + self.order = order + @property def order(self): temp_order = c_int() @@ -307,6 +317,11 @@ class SphericalHarmonicsFilter(Filter): class SpatialLegendreFilter(Filter): filter_type = 'spatiallegendre' + def __init__(self, order=None, uid=None, new=True, index=None): + super().__init__(uid, new, index) + if order is not None: + self.order = order + @property def order(self): temp_order = c_int() @@ -329,6 +344,11 @@ class UniverseFilter(Filter): class ZernikeFilter(Filter): filter_type = 'zernike' + def __init__(self, order=None, uid=None, new=True, index=None): + super().__init__(uid, new, index) + if order is not None: + self.order = order + @property def order(self): temp_order = c_int() diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 11a47b150..af013cbb5 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -205,7 +205,8 @@ def test_tally_results(capi_run): assert np.all(t.ci_width()[nonzero] >= 1.95*t.std_dev[nonzero]) t2 = openmc.capi.tallies[2] - assert t2.mean.size == 63 # 21 coefficients for 5th order Zernike * 3 cells + n = 5 + assert t2.mean.size == (n + 1) * (n + 2) // 2 * 3 # Number of Zernike coeffs * 3 cells def test_global_tallies(capi_run): From 75dc4131dc6538f7ed243ef95eb274cb9ddd7a18 Mon Sep 17 00:00:00 2001 From: tjlaboss Date: Tue, 22 May 2018 13:36:50 -0400 Subject: [PATCH 280/361] Long live PEP8 --- openmc/mesh.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index 7c6fc4867..3f53582d5 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -183,19 +183,19 @@ class Mesh(IDManagerMixin): return mesh @classmethod - def fromRectLattice(cls, lattice, division=1, mesh_id=None, name=''): + def from_rect_lattice(cls, lattice, division=1, mesh_id=None, name=''): """Create mesh from an existing rectangular lattice Parameters ---------- lattice : openmc.RectLattice Rectangular lattice used as a template for this mesh - division : int, optional + division : int Number of mesh cells per lattice cell. If not specified, there will be 1 mesh cell per lattice cell. - mesh_id : int, optional + mesh_id : int Unique identifier for the mesh - name : str, optional + name : str Name of the mesh Returns From 0da329e0ae4fe52bcbdebd8d88c53e1193617f68 Mon Sep 17 00:00:00 2001 From: tjlaboss Date: Tue, 22 May 2018 17:04:07 -0400 Subject: [PATCH 281/361] Unit test for Mesh.from_rect_lattice() --- tests/unit_tests/test_mesh_from_lattice.py | 116 +++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 tests/unit_tests/test_mesh_from_lattice.py diff --git a/tests/unit_tests/test_mesh_from_lattice.py b/tests/unit_tests/test_mesh_from_lattice.py new file mode 100644 index 000000000..6091dd1cd --- /dev/null +++ b/tests/unit_tests/test_mesh_from_lattice.py @@ -0,0 +1,116 @@ +import numpy as np +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] + ] + + 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]] + ] + + return lattice + + +def test_mesh2d(rlat2d): + shape = np.array(rlat2d.shape) + width = shape*rlat2d.pitch + + mesh1 = openmc.Mesh().from_rect_lattice(rlat2d) + assert np.array_equal(mesh1.dimension, (3, 3)) + assert np.array_equal(mesh1.lower_left, rlat2d.lower_left) + assert np.array_equal(mesh1.upper_right, rlat2d.lower_left + width) + + mesh2 = openmc.Mesh().from_rect_lattice(rlat2d, division=3) + assert np.array_equal(mesh2.dimension, (9, 9)) + assert np.array_equal(mesh2.lower_left, rlat2d.lower_left) + assert np.array_equal(mesh2.upper_right, rlat2d.lower_left + width) + + +def test_mesh3d(rlat3d): + shape = np.array(rlat3d.shape) + width = shape*rlat3d.pitch + + mesh1 = openmc.Mesh().from_rect_lattice(rlat3d) + assert np.array_equal(mesh1.dimension, (3, 3, 2)) + assert np.array_equal(mesh1.lower_left, rlat3d.lower_left) + assert np.array_equal(mesh1.upper_right, rlat3d.lower_left + width) + + mesh2 = openmc.Mesh().from_rect_lattice(rlat3d, division=3) + assert np.array_equal(mesh2.dimension, (9, 9, 6)) + assert np.array_equal(mesh2.lower_left, rlat3d.lower_left) + assert np.array_equal(mesh2.upper_right, rlat3d.lower_left + width) From 7801761a7035f3ba2f8a326390b6fbf9fda87f5a Mon Sep 17 00:00:00 2001 From: tjlaboss Date: Tue, 22 May 2018 23:49:07 -0400 Subject: [PATCH 282/361] Correct names of fixtures `rlat2d` --> `rlat2` `rlat3d` --> `rlat3` --- tests/unit_tests/test_mesh_from_lattice.py | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/unit_tests/test_mesh_from_lattice.py b/tests/unit_tests/test_mesh_from_lattice.py index 6091dd1cd..563e51c3b 100644 --- a/tests/unit_tests/test_mesh_from_lattice.py +++ b/tests/unit_tests/test_mesh_from_lattice.py @@ -86,31 +86,31 @@ def rlat3(pincell1, pincell2, uo2, water, zr): return lattice -def test_mesh2d(rlat2d): - shape = np.array(rlat2d.shape) - width = shape*rlat2d.pitch +def test_mesh2d(rlat2): + shape = np.array(rlat2.shape) + width = shape*rlat2.pitch - mesh1 = openmc.Mesh().from_rect_lattice(rlat2d) + mesh1 = openmc.Mesh().from_rect_lattice(rlat2) assert np.array_equal(mesh1.dimension, (3, 3)) - assert np.array_equal(mesh1.lower_left, rlat2d.lower_left) - assert np.array_equal(mesh1.upper_right, rlat2d.lower_left + width) + assert np.array_equal(mesh1.lower_left, rlat2.lower_left) + assert np.array_equal(mesh1.upper_right, rlat2.lower_left + width) - mesh2 = openmc.Mesh().from_rect_lattice(rlat2d, division=3) + mesh2 = openmc.Mesh().from_rect_lattice(rlat2, division=3) assert np.array_equal(mesh2.dimension, (9, 9)) - assert np.array_equal(mesh2.lower_left, rlat2d.lower_left) - assert np.array_equal(mesh2.upper_right, rlat2d.lower_left + width) + assert np.array_equal(mesh2.lower_left, rlat2.lower_left) + assert np.array_equal(mesh2.upper_right, rlat2.lower_left + width) -def test_mesh3d(rlat3d): - shape = np.array(rlat3d.shape) - width = shape*rlat3d.pitch +def test_mesh3d(rlat3): + shape = np.array(rlat3.shape) + width = shape*rlat3.pitch - mesh1 = openmc.Mesh().from_rect_lattice(rlat3d) + mesh1 = openmc.Mesh().from_rect_lattice(rlat3) assert np.array_equal(mesh1.dimension, (3, 3, 2)) - assert np.array_equal(mesh1.lower_left, rlat3d.lower_left) - assert np.array_equal(mesh1.upper_right, rlat3d.lower_left + width) + assert np.array_equal(mesh1.lower_left, rlat3.lower_left) + assert np.array_equal(mesh1.upper_right, rlat3.lower_left + width) - mesh2 = openmc.Mesh().from_rect_lattice(rlat3d, division=3) + mesh2 = openmc.Mesh().from_rect_lattice(rlat3, division=3) assert np.array_equal(mesh2.dimension, (9, 9, 6)) - assert np.array_equal(mesh2.lower_left, rlat3d.lower_left) - assert np.array_equal(mesh2.upper_right, rlat3d.lower_left + width) + assert np.array_equal(mesh2.lower_left, rlat3.lower_left) + assert np.array_equal(mesh2.upper_right, rlat3.lower_left + width) From 36e2ea6b8aeef9996c3049070e0126c69b22b30e Mon Sep 17 00:00:00 2001 From: tjlaboss Date: Wed, 23 May 2018 11:51:26 -0400 Subject: [PATCH 283/361] Avoid instantiating a Mesh during class method --- tests/unit_tests/test_mesh_from_lattice.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/test_mesh_from_lattice.py b/tests/unit_tests/test_mesh_from_lattice.py index 563e51c3b..4d79fc579 100644 --- a/tests/unit_tests/test_mesh_from_lattice.py +++ b/tests/unit_tests/test_mesh_from_lattice.py @@ -90,12 +90,12 @@ def test_mesh2d(rlat2): shape = np.array(rlat2.shape) width = shape*rlat2.pitch - mesh1 = openmc.Mesh().from_rect_lattice(rlat2) + mesh1 = openmc.Mesh.from_rect_lattice(rlat2) assert np.array_equal(mesh1.dimension, (3, 3)) assert np.array_equal(mesh1.lower_left, rlat2.lower_left) assert np.array_equal(mesh1.upper_right, rlat2.lower_left + width) - mesh2 = openmc.Mesh().from_rect_lattice(rlat2, division=3) + mesh2 = openmc.Mesh.from_rect_lattice(rlat2, division=3) assert np.array_equal(mesh2.dimension, (9, 9)) assert np.array_equal(mesh2.lower_left, rlat2.lower_left) assert np.array_equal(mesh2.upper_right, rlat2.lower_left + width) @@ -105,12 +105,12 @@ def test_mesh3d(rlat3): shape = np.array(rlat3.shape) width = shape*rlat3.pitch - mesh1 = openmc.Mesh().from_rect_lattice(rlat3) + mesh1 = openmc.Mesh.from_rect_lattice(rlat3) assert np.array_equal(mesh1.dimension, (3, 3, 2)) assert np.array_equal(mesh1.lower_left, rlat3.lower_left) assert np.array_equal(mesh1.upper_right, rlat3.lower_left + width) - mesh2 = openmc.Mesh().from_rect_lattice(rlat3, division=3) + mesh2 = openmc.Mesh.from_rect_lattice(rlat3, division=3) assert np.array_equal(mesh2.dimension, (9, 9, 6)) assert np.array_equal(mesh2.lower_left, rlat3.lower_left) assert np.array_equal(mesh2.upper_right, rlat3.lower_left + width) From 2e6b40cb1b7fd18ee945fd3cac517a281b67b05d Mon Sep 17 00:00:00 2001 From: Alex Lindsay Date: Wed, 23 May 2018 14:16:17 -0600 Subject: [PATCH 284/361] Consistently check derived for sum and sum_sq in tallies. Closes #1010 --- openmc/tallies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index a5341cbed..74f342903 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -248,7 +248,7 @@ class Tally(IDManagerMixin): @property def sum_sq(self): - if not self._sp_filename: + if not self._sp_filename or self.derived: return None if not self._results_read: From 9bf2eaa42e559e57cbb82e02384920fec3134d67 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 24 May 2018 12:53:09 -0400 Subject: [PATCH 285/361] Simplify distribcell find_offset logic --- src/tallies/tally_filter_distribcell.F90 | 328 +++++++---------------- 1 file changed, 98 insertions(+), 230 deletions(-) diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index fa0a6b296..e121cae5e 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -139,17 +139,11 @@ contains character(*), intent(inout) :: path ! Path to offset integer :: map ! Index in maps vector - integer :: i, j ! Index over cells + integer :: i ! Index over cells integer :: k, l, m ! Indices in lattice - integer :: old_k, old_l, old_m ! Previous indices in lattice integer :: n_x, n_y, n_z ! Lattice cell array dimensions - integer :: n ! Number of cells to search - integer :: cell_index ! Index in cells array - integer :: lat_offset ! Offset from lattice integer :: temp_offset ! Looped sum of offsets integer :: i_univ ! index in universes array - logical :: this_cell = .false. ! Advance in this cell? - logical :: later_cell = .false. ! Fill cells after this one? type(Cell), pointer :: c ! Pointer to current cell type(Universe), pointer :: next_univ ! Next universe to loop through class(Lattice), pointer :: lat ! Pointer to current lattice @@ -157,8 +151,6 @@ contains ! Get the distribcell index for this cell map = cells(i_cell) % distribcell_index - n = size(univ % cells) - ! Write to the geometry stack i_univ = universe_dict % get(univ % id) if (i_univ == root_universe) then @@ -168,7 +160,7 @@ contains end if ! Look through all cells in this universe - do i = 1, n + do i = 1, size(univ % cells) ! If the cell matches the goal and the offset matches final, write to the ! geometry stack if (univ % cells(i) == i_cell .and. offset == target_offset) then @@ -178,234 +170,110 @@ contains end if end do - ! Find the fill cell or lattice cell that we need to enter - do i = 1, n - - later_cell = .false. - + ! Find the fill cell or lattice cell that contains the target offset. + do i = size(univ % cells), 2, -1 c => cells(univ % cells(i)) - this_cell = .false. + ! Material cells don't contain other cells so ignore them. + if (c % type() /= FILL_MATERIAL) then - ! If we got here, we still think the target is in this universe - ! or further down, but it's not this exact cell. - ! Compare offset to next cell to see if we should enter this cell - if (i /= n) then - - do j = i+1, n - - c => cells(univ % cells(j)) - - ! Skip normal cells which do not have offsets - if (c % type() == FILL_MATERIAL) cycle - - ! Break loop once we've found the next cell with an offset - exit - end do - - ! Ensure we didn't just end the loop by iteration - if (c % type() /= FILL_MATERIAL) then - - ! There are more cells in this universe that it could be in - later_cell = .true. - - ! Two cases, lattice or fill cell - if (c % type() == FILL_UNIVERSE) then - temp_offset = c % offset(map-1) - - ! Get the offset of the first lattice location - else - lat => lattices(c % fill() + 1) % obj - temp_offset = lat % offset(map-1, [0, 0, 0]) - end if - - ! If the final offset is in the range of offset - temp_offset+offset - ! then the goal is in this cell - if (target_offset < temp_offset + offset) then - this_cell = .true. - end if - end if - end if - - if (n == 1 .and. c % type() /= FILL_MATERIAL) then - this_cell = .true. - end if - - if (.not. later_cell) then - this_cell = .true. - end if - - ! Get pointer to THIS cell because target must be in this cell - if (this_cell) then - - cell_index = univ % cells(i) - c => cells(cell_index) - - path = trim(path) // "->c" // to_str(c%id()) - - ! ==================================================================== - ! CELL CONTAINS LOWER UNIVERSE, RECURSIVELY FIND CELL if (c % type() == FILL_UNIVERSE) then - - ! Enter this cell to update the current offset - offset = c % offset(map-1) + offset - - next_univ => universes(c % fill() + 1) - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - - ! ==================================================================== - ! CELL CONTAINS LATTICE, RECURSIVELY FIND CELL - elseif (c % type() == FILL_LATTICE) then - - ! Set current lattice + temp_offset = offset + c % offset(map-1) + else + ! Get the offset of the first lattice location lat => lattices(c % fill() + 1) % obj - - select type (lat) - - ! ================================================================== - ! RECTANGULAR LATTICES - type is (RectLattice) - - ! Write to the geometry stack - path = trim(path) // "->l" // to_str(lat%id()) - - n_x = lat % n_cells(1) - n_y = lat % n_cells(2) - n_z = lat % n_cells(3) - old_m = 1 - old_l = 1 - old_k = 1 - - ! Loop over lattice coordinates - do m = 1, n_z - do l = 1, n_y - do k = 1, n_x - - if (target_offset >= lat % offset(map-1, [k-1, l-1, m-1]) & - + offset) then - if (k == n_x .and. l == n_y .and. m == n_z) then - ! This is last lattice cell, so target must be here - lat_offset = lat % offset(map-1, [k-1, l-1, m-1]) - offset = offset + lat_offset - next_univ => universes(lat % get([k-1, l-1, m-1])+1) - if (lat % is_3d) then - path = trim(path) // "(" // trim(to_str(k-1)) // & - "," // trim(to_str(l-1)) // "," // & - trim(to_str(m-1)) // ")" - else - path = trim(path) // "(" // trim(to_str(k-1)) // & - "," // trim(to_str(l-1)) // ")" - end if - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - else - old_m = m - old_l = l - old_k = k - cycle - end if - else - ! Target is at this lattice position - lat_offset = lat % offset(map-1, [old_k-1, old_l-1, old_m-1]) - offset = offset + lat_offset - next_univ => universes(lat % get([old_k-1, old_l-1, old_m-1])+1) - if (lat % is_3d) then - path = trim(path) // "(" // trim(to_str(old_k-1)) // & - "," // trim(to_str(old_l-1)) // "," // & - trim(to_str(old_m-1)) // ")" - else - path = trim(path) // "(" // trim(to_str(old_k-1)) // & - "," // trim(to_str(old_l-1)) // ")" - end if - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - end if - - end do - end do - end do - - ! ================================================================== - ! HEXAGONAL LATTICES - type is (HexLattice) - - ! Write to the geometry stack - path = trim(path) // "->l" // to_str(lat%id()) - - n_z = lat % n_axial - n_y = 2 * lat % n_rings - 1 - n_x = 2 * lat % n_rings - 1 - old_m = 1 - old_l = 1 - old_k = 1 - - ! Loop over lattice coordinates - do m = 1, n_z - do l = 1, n_y - do k = 1, n_x - - ! This array position is never used - if (k + l < lat % n_rings + 1) then - cycle - ! This array position is never used - else if (k + l > 3*lat % n_rings - 1) then - cycle - end if - - if (target_offset >= lat % offset(map-1, [k-1, l-1, m-1]) & - + offset) then - if (k == lat % n_rings .and. l == n_y .and. m == n_z) then - ! This is last lattice cell, so target must be here - lat_offset = lat % offset(map-1, [k-1, l-1, m-1]) - offset = offset + lat_offset - next_univ => universes(lat % get([k-1, l-1, m-1])+1) - if (lat % is_3d) then - path = trim(path) // "(" // & - trim(to_str(k - lat % n_rings)) // "," // & - trim(to_str(l - lat % n_rings)) // "," // & - trim(to_str(m - 1)) // ")" - else - path = trim(path) // "(" // & - trim(to_str(k - lat % n_rings)) // "," // & - trim(to_str(l - lat % n_rings)) // ")" - end if - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - else - old_m = m - old_l = l - old_k = k - cycle - end if - else - ! Target is at this lattice position - lat_offset = lat % offset(map-1, [old_k-1, old_l-1, old_m-1]) - offset = offset + lat_offset - next_univ => universes(lat % get([old_k-1, old_l-1, old_m-1])+1) - if (lat % is_3d) then - path = trim(path) // "(" // & - trim(to_str(old_k - lat % n_rings)) // "," // & - trim(to_str(old_l - lat % n_rings)) // "," // & - trim(to_str(old_m - 1)) // ")" - else - path = trim(path) // "(" // & - trim(to_str(old_k - lat % n_rings)) // "," // & - trim(to_str(old_l - lat % n_rings)) // ")" - end if - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - end if - - end do - end do - end do - - end select - + temp_offset = offset + lat % offset(map-1, [0, 0, 0]) end if + + ! The desired cell is the first cell that gives an offset smaller or + ! equal to the target offset. + if (temp_offset <= target_offset) exit end if end do + + ! Add the cell to the path string. + c => cells(univ % cells(i)) + path = trim(path) // "->c" // to_str(c%id()) + + if (c % type() == FILL_UNIVERSE) then + ! Recurse into the fill cell. + offset = c % offset(map-1) + offset + next_univ => universes(c % fill() + 1) + call find_offset(i_cell, next_univ, target_offset, offset, path) + + elseif (c % type() == FILL_LATTICE) then + ! Recurse into the lattice cell. + lat => lattices(c % fill() + 1) % obj + select type (lat) + + type is (RectLattice) + path = trim(path) // "->l" // to_str(lat%id()) + + n_x = lat % n_cells(1) + n_y = lat % n_cells(2) + n_z = lat % n_cells(3) + + do m = n_z, 1, -1 + do l = n_y, 1, -1 + do k = n_x, 1, -1 + temp_offset = offset + lat % offset(map-1, [k-1, l-1, m-1]) + if (temp_offset <= target_offset) then + next_univ => universes(lat % get([k-1, l-1, m-1])+1) + if (lat % is_3d) then + path = trim(path) // "(" // trim(to_str(k-1)) // & + "," // trim(to_str(l-1)) // "," // & + trim(to_str(m-1)) // ")" + else + path = trim(path) // "(" // trim(to_str(k-1)) // & + "," // trim(to_str(l-1)) // ")" + end if + offset = temp_offset + call find_offset(i_cell, next_univ, target_offset, offset, path) + return + end if + end do + end do + end do + + type is (HexLattice) + path = trim(path) // "->l" // to_str(lat%id()) + + n_z = lat % n_axial + n_y = 2 * lat % n_rings - 1 + n_x = 2 * lat % n_rings - 1 + + do m = n_z, 1, -1 + do l = n_y, 1, -1 + do k = n_x, 1, -1 + if (k + l < lat % n_rings + 1) then + cycle + else if (k + l > 3*lat % n_rings - 1) then + cycle + end if + temp_offset = offset + lat % offset(map-1, [k-1, l-1, m-1]) + if (temp_offset <= target_offset) then + next_univ => universes(lat % get([k-1, l-1, m-1])+1) + if (lat % is_3d) then + path = trim(path) // "(" // & + trim(to_str(k - lat % n_rings)) // "," // & + trim(to_str(l - lat % n_rings)) // "," // & + trim(to_str(m - 1)) // ")" + else + path = trim(path) // "(" // & + trim(to_str(k - lat % n_rings)) // "," // & + trim(to_str(l - lat % n_rings)) // ")" + end if + offset = temp_offset + call find_offset(i_cell, next_univ, target_offset, offset, path) + return + end if + end do + end do + end do + + end select + + end if end subroutine find_offset end module tally_filter_distribcell From 0af6b4bee0766f63407fea22791f0c9780e445a2 Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Thu, 24 May 2018 20:27:15 +0000 Subject: [PATCH 286/361] Changed logic in depletion-xs construction to skip (n,3n) and (n,4n) if energy index is below the threshold for (n,2n). If we are above (n,2n) but below (n,3n) then we skip (n,4n) automatically. I also took (n,gamma) outside of that loop since it is not really a threshold reaction. --- src/constants.F90 | 2 +- src/nuclide_header.F90 | 27 +++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/constants.F90 b/src/constants.F90 index d1893bf9e..e6dcfe755 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -219,7 +219,7 @@ module constants N_3HEC = 799, N_A0 = 800, N_AC = 849, N_2N0 = 875, N_2NC = 891 ! Depletion reactions - integer, parameter :: DEPLETION_RX(6) = [N_2N, N_3N, N_4N, N_GAMMA, N_P, N_A] + integer, parameter :: DEPLETION_RX(6) = [N_GAMMA, N_P, N_A,N_2N, N_3N,N_4N] ! ACE table types integer, parameter :: & diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index ebabe5cdb..487c71545 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -980,9 +980,12 @@ contains ! Depletion-related reactions if (need_depletion_rx) then - do j = 1, 6 - ! Initialize reaction xs to zero - micro_xs % reaction(j) = ZERO + ! Initialize entire array to zero in case we skip + ! any threshold reaction. + micro_xs % reaction(:) = ZERO + !looping from element 2 to element 6. + !treating (n,gamma) differently because it is not a threshold reaction. + do j = 2, 6 ! If reaction is present and energy is greater than threshold, set ! the reaction xs appropriately @@ -993,12 +996,28 @@ contains micro_xs % reaction(j) = (ONE - f) * & xs % value(i_grid - xs % threshold + 1) + & f * xs % value(i_grid - xs % threshold + 2) + ! Check if we are below the (n,2n) and/or (n,3n) reaction thresholds to + ! skip remaining depletion-xs construction. + else + if (j >= 4) then + exit + end if end if end associate end if end do + !there shouldn't be a threshold check for (n,gamma). + !I know this is not very clean but I don't want to overload the loop + !with too many conditional statements for now. + i_rxn = this % reaction_index(DEPLETION_RX(1)) + if (i_rxn > 0) then + associate (xs => this % reactions(i_rxn) % xs(i_temp)) + micro_xs % reaction(1) = (ONE - f) * & + xs % value(i_grid - xs % threshold + 1) + & + f * xs % value(i_grid - xs % threshold + 2) + end associate + end if end if - end if ! Initialize sab treatment to false From af4525a1ebf3058172739e81c739089e2d40be43 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 25 May 2018 20:32:17 -0400 Subject: [PATCH 287/361] Move find_offset (distribcell) to C++ --- src/geometry_aux.cpp | 97 +++++++++++++ src/geometry_aux.h | 31 ++++ src/geometry_header.F90 | 7 - src/input_xml.F90 | 29 +--- src/lattice.cpp | 72 +++++++++- src/lattice.h | 54 ++++++- src/summary.F90 | 1 - src/tallies/tally_filter_distribcell.F90 | 175 +++++------------------ 8 files changed, 276 insertions(+), 190 deletions(-) diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 2bf379a6d..a51e5b8a8 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -200,6 +200,103 @@ fill_offset_tables(int32_t target_univ_id, int map) //============================================================================== +std::string +distribcell_path_inner(int32_t target_cell, int32_t map, int32_t target_offset, + const Universe &search_univ, int32_t offset) +{ + std::stringstream path; + + path << "u" << search_univ.id << "->"; + + // Check to see if this universe directly contains the target cell. If so, + // write to the path and return. + for (int32_t cell_indx : search_univ.cells) { + if ((cell_indx == target_cell) && (offset == target_offset)) { + Cell &c = *cells_c[cell_indx]; + path << "c" << c.id; + return path.str(); + } + } + + // The target must be further down the geometry tree and contained in a fill + // cell or lattice cell in this universe. Find which cell contains the + // target. + auto cell_it {search_univ.cells.rbegin()}; + for (; cell_it != search_univ.cells.rend(); ++cell_it) { + Cell &c = *cells_c[*cell_it]; + + // Material cells don't contain other cells so ignore them. + if (c.type != FILL_MATERIAL) { + int32_t temp_offset; + if (c.type == FILL_UNIVERSE) { + temp_offset = offset + c.offset[map]; + } else { + Lattice &lat = *lattices_c[c.fill]; + int32_t indx = lat.universes.size()*map + lat.begin().indx; + temp_offset = offset + lat.offsets[indx]; + } + + // The desired cell is the first cell that gives an offset smaller or + // equal to the target offset. + if (temp_offset <= target_offset) break; + } + } + + // Add the cell to the path string. + Cell &c = *cells_c[*cell_it]; + path << "c" << c.id << "->"; + + if (c.type == FILL_UNIVERSE) { + // Recurse into the fill cell. + offset += c.offset[map]; + path << distribcell_path_inner(target_cell, map, target_offset, + *universes_c[c.fill], offset); + return path.str(); + } else { + // Recurse into the lattice cell. + Lattice &lat = *lattices_c[c.fill]; + path << "l" << lat.id; + for (ReverseLatticeIter it = lat.rbegin(); it != lat.rend(); ++it) { + int32_t indx = lat.universes.size()*map + it.indx; + int32_t temp_offset = offset + lat.offsets[indx]; + if (temp_offset <= target_offset) { + offset = temp_offset; + path << "(" << lat.index_to_string(it.indx) << ")->"; + path << distribcell_path_inner(target_cell, map, target_offset, + *universes_c[*it], offset); + return path.str(); + } + } + } +} + +//============================================================================== + +int +distribcell_path_len(int32_t target_cell, int32_t map, int32_t target_offset, + int32_t root_univ) +{ + Universe &root = *universes_c[root_univ]; + std::string path_ {distribcell_path_inner(target_cell, map, target_offset, + root, 0)}; + return path_.size() + 1; +} + +//============================================================================== + +void +distribcell_path(int32_t target_cell, int32_t map, int32_t target_offset, + int32_t root_univ, char *path) +{ + Universe &root = *universes_c[root_univ]; + std::string path_ {distribcell_path_inner(target_cell, map, target_offset, + root, 0)}; + path_.copy(path, path_.size()); + path[path_.size()] = '\0'; +} + +//============================================================================== + int maximum_levels(int32_t univ) { diff --git a/src/geometry_aux.h b/src/geometry_aux.h index 877f51e4e..7d2c3cd9a 100644 --- a/src/geometry_aux.h +++ b/src/geometry_aux.h @@ -62,6 +62,37 @@ count_universe_instances(int32_t search_univ, int32_t target_univ_id); extern "C" void fill_offset_tables(int32_t target_univ_id, int map); +//============================================================================== +//! Find the length necessary for a string to contain a distribcell path. +//! @param target_cell The index of the Cell in the global Cell array. +//! @param map The index of the distribcell mapping corresponding to the target +//! cell. +//! @param target_offset An instance number for a distributed cell. +//! @param root_univ The index of the root Universe in the global Universe +//! array. +//! @return The size of a character array needed to fit the distribcell path. +//============================================================================== + +extern "C" int +distribcell_path_len(int32_t target_cell, int32_t map, int32_t target_offset, + int32_t root_univ); + +//============================================================================== +//! Build a character array representing the path to a distribcell instance. +//! @param target_cell The index of the Cell in the global Cell array. +//! @param map The index of the distribcell mapping corresponding to the target +//! cell. +//! @param target_offset An instance number for a distributed cell. +//! @param root_univ The index of the root Universe in the global Universe +//! array. +//! @param[out] path The unique traversal through the geometry tree that leads +//! to the desired instance of the target cell. +//============================================================================== + +extern "C" void +distribcell_path(int32_t target_cell, int32_t map, int32_t target_offset, + int32_t root_univ, char *path); + //============================================================================== //! Determine the maximum number of nested coordinate levels in the geometry. //! @param univ The index of the universe to begin seraching from (probably the diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index d019e8fce..8f95c4a7d 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -201,7 +201,6 @@ module geometry_header type Universe integer :: id ! Unique ID - integer :: type ! Type integer, allocatable :: cells(:) ! List of cells within real(8) :: x0 ! Translation in x-coordinate real(8) :: y0 ! Translation in y-coordinate @@ -214,10 +213,7 @@ module geometry_header type, abstract :: Lattice type(C_PTR) :: ptr - - logical :: is_3d ! Lattice has cells on z axis contains - procedure :: id => lattice_id procedure :: are_valid_indices => lattice_are_valid_indices procedure :: distance => lattice_distance @@ -234,7 +230,6 @@ module geometry_header !=============================================================================== type, extends(Lattice) :: RectLattice - integer :: n_cells(3) ! Number of cells along each axis end type RectLattice !=============================================================================== @@ -242,8 +237,6 @@ module geometry_header !=============================================================================== type, extends(Lattice) :: HexLattice - integer :: n_rings ! Number of radial ring cell positoins - integer :: n_axial ! Number of axial cell positions end type HexLattice !=============================================================================== diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 501a43a57..96cb2f754 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -952,7 +952,7 @@ contains subroutine read_geometry_xml() integer :: i, j, k - integer :: n, n_mats, n_z, n_rings, n_rlats, n_hlats + integer :: n, n_mats, n_rlats, n_hlats integer :: id integer :: univ_id integer :: n_cells_in_univ @@ -1270,19 +1270,6 @@ contains ! Get pointer to i-th lattice node_lat = node_rlat_list(i) - ! Read number of lattice cells in each dimension - n = node_word_count(node_lat, "dimension") - if (n == 2) then - call get_node_array(node_lat, "dimension", lat % n_cells(1:2)) - lat % n_cells(3) = 1 - lat % is_3d = .false. - else if (n == 3) then - call get_node_array(node_lat, "dimension", lat % n_cells) - lat % is_3d = .true. - else - call fatal_error("Rectangular lattice must be two or three dimensions.") - end if - ! Add lattice to dictionary call lattice_dict % set(lat % id(), i) @@ -1299,20 +1286,6 @@ contains ! Get pointer to i-th lattice node_lat = node_hlat_list(i) - ! Read number of lattice cells in each dimension - call get_node_value(node_lat, "n_rings", lat % n_rings) - if (check_for_node(node_lat, "n_axial")) then - call get_node_value(node_lat, "n_axial", lat % n_axial) - lat % is_3d = .true. - else - lat % n_axial = 1 - lat % is_3d = .false. - end if - - ! Copy number of dimensions - n_rings = lat % n_rings - n_z = lat % n_axial - ! Add lattice to dictionary call lattice_dict % set(lat % id(), n_rlats + i) diff --git a/src/lattice.cpp b/src/lattice.cpp index 7bb5fb6bb..f5d0ba272 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -21,10 +21,6 @@ namespace openmc { // Global variables //============================================================================== -// Braces force n_lattices to be defined here, not just declared. -//extern "C" {int32_t n_lattices {0};} - -//Lattice **lattices_c; std::vector lattices_c; std::map lattice_dict; @@ -66,6 +62,20 @@ Lattice::end() //============================================================================== +ReverseLatticeIter +Lattice::rbegin() +{ + return ReverseLatticeIter(*this, universes.size()-1); +} + +ReverseLatticeIter +Lattice::rend() +{ + return ReverseLatticeIter(*this, -1); +} + +//============================================================================== + void Lattice::adjust_indices() { @@ -348,6 +358,26 @@ RectLattice::offset(int map, const int i_xyz[3]) //============================================================================== +std::string +RectLattice::index_to_string(int indx) const +{ + int nx {n_cells[0]}; + int ny {n_cells[1]}; + int iz {indx / (nx * ny)}; + int iy {(indx - nx * ny * iz) / nx}; + int ix {indx - nx * ny * iz - nx * iy}; + std::string out {std::to_string(ix)}; + out += ','; + out += std::to_string(iy); + if (is_3d) { + out += ','; + out += std::to_string(iz); + } + return out; +} + +//============================================================================== + void RectLattice::to_hdf5_inner(hid_t lat_group) const { @@ -577,6 +607,14 @@ HexLattice::begin() //============================================================================== +ReverseLatticeIter +HexLattice::rbegin() +{ + return ReverseLatticeIter(*this, universes.size()-n_rings); +} + +//============================================================================== + bool HexLattice::are_valid_indices(const int i_xyz[3]) const { @@ -798,14 +836,34 @@ HexLattice::is_valid_index(int indx) const int32_t& HexLattice::offset(int map, const int i_xyz[3]) { - int nx = 2*n_rings - 1; - int ny = 2*n_rings - 1; - int nz = n_axial; + int nx {2*n_rings - 1}; + int ny {2*n_rings - 1}; + int nz {n_axial}; return offsets[nx*ny*nz*map + nx*ny*i_xyz[2] + nx*i_xyz[1] + i_xyz[0]]; } //============================================================================== +std::string +HexLattice::index_to_string(int indx) const +{ + int nx {2*n_rings - 1}; + int ny {2*n_rings - 1}; + int iz {indx / (nx * ny)}; + int iy {(indx - nx * ny * iz) / nx}; + int ix {indx - nx * ny * iz - nx * iy}; + std::string out {std::to_string(ix - n_rings + 1)}; + out += ','; + out += std::to_string(iy - n_rings + 1); + if (is_3d) { + out += ','; + out += std::to_string(iz); + } + return out; +} + +//============================================================================== + void HexLattice::to_hdf5_inner(hid_t lat_group) const { diff --git a/src/lattice.h b/src/lattice.h index 9b55025b5..3161a2473 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -33,11 +33,13 @@ extern std::vector lattices_c; extern std::map lattice_dict; //============================================================================== -//! Abstract type for ordered array of universes +//! Abstract type for ordered array of universes. //============================================================================== class LatticeIter; +class ReverseLatticeIter; + class Lattice { public: @@ -56,6 +58,9 @@ public: virtual LatticeIter begin(); LatticeIter end(); + virtual ReverseLatticeIter rbegin(); + ReverseLatticeIter rend(); + //! Convert internal universe values from IDs to indices using universe_dict. void adjust_indices(); @@ -98,7 +103,7 @@ public: //! otherwise. virtual bool is_valid_index(int indx) const { - return (indx > 0) && (indx < universes.size()); + return (indx >= 0) && (indx < universes.size()); } //! Get the distribcell offset for a lattice tile. @@ -108,6 +113,11 @@ public: //! cell found in the geometry tree under this lattice tile. virtual int32_t& offset(int map, const int i_xyz[3]) = 0; + //! Convert an array index to a useful human-readable string. + //! @param indx The index for a lattice tile. + //! @return A string representing the lattice tile. + virtual std::string index_to_string(int indx) const = 0; + //! Write all information needed to reconstruct the lattice to an HDF5 group. //! @param group_id An HDF5 group id. void to_hdf5(hid_t group_id) const; @@ -118,9 +128,16 @@ protected: virtual void to_hdf5_inner(hid_t group_id) const = 0; }; +//============================================================================== +//! An iterator over lattice universes. +//============================================================================== + class LatticeIter { public: + + int indx; //!< An index to a Lattice universes or offsets array. + LatticeIter(Lattice &lat_, int indx_) : lat(lat_), indx(indx_) @@ -128,7 +145,7 @@ public: bool operator==(const LatticeIter &rhs) { - return (&lat == &rhs.lat) && (indx == rhs.indx); + return (indx == rhs.indx); } bool operator!=(const LatticeIter &rhs) @@ -151,11 +168,32 @@ public: return *this; } - int indx; -private: +protected: Lattice ⪫ }; +//============================================================================== +//! A reverse iterator over lattice universes. +//============================================================================== + +class ReverseLatticeIter : public LatticeIter +{ +public: + ReverseLatticeIter(Lattice &lat_, int indx_) + : LatticeIter {lat_, indx_} + {} + + ReverseLatticeIter& operator++() + { + while (indx > -1) { + --indx; + if (lat.is_valid_index(indx)) return *this; + } + indx = -1; + return *this; + } +}; + //============================================================================== //============================================================================== @@ -180,6 +218,8 @@ public: int32_t& offset(int map, const int i_xyz[3]); + std::string index_to_string(int indx) const; + void to_hdf5_inner(hid_t group_id) const; protected: @@ -202,6 +242,8 @@ public: LatticeIter begin(); + ReverseLatticeIter rbegin(); + bool are_valid_indices(const int i_xyz[3]) const; std::pair> @@ -216,6 +258,8 @@ public: int32_t& offset(int map, const int i_xyz[3]); + std::string index_to_string(int indx) const; + void to_hdf5_inner(hid_t group_id) const; protected: diff --git a/src/summary.F90 b/src/summary.F90 index 3b01ddfc9..464c0c6cf 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -15,7 +15,6 @@ module summary use surface_header use string, only: to_str use tally_header, only: TallyObject - use tally_filter_distribcell, only: find_offset implicit none private diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index e121cae5e..d6643190c 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -14,7 +14,6 @@ module tally_filter_distribcell implicit none private - public :: find_offset !=============================================================================== ! DISTRIBCELLFILTER specifies which distributed geometric cells tally events @@ -114,13 +113,8 @@ contains integer, intent(in) :: bin character(MAX_LINE_LEN) :: label - integer :: offset - type(Universe), pointer :: univ - - univ => universes(root_universe) - offset = 0 label = '' - call find_offset(this % cell, univ, bin-1, offset, label) + call find_offset(this % cell, bin-1, label) label = "Distributed Cell " // label end function text_label_distribcell @@ -130,150 +124,47 @@ contains ! the target cell with the given offset !=============================================================================== - recursive subroutine find_offset(i_cell, univ, target_offset, offset, path) - + subroutine find_offset(i_cell, target_offset, path) integer, intent(in) :: i_cell ! The target cell index - type(Universe), intent(in) :: univ ! Universe to begin search - integer, intent(in) :: target_offset ! Target offset - integer, intent(inout) :: offset ! Current offset - character(*), intent(inout) :: path ! Path to offset + integer, intent(in) :: target_offset ! Target offset + character(*), intent(inout) :: path ! Path to offset integer :: map ! Index in maps vector integer :: i ! Index over cells - integer :: k, l, m ! Indices in lattice - integer :: n_x, n_y, n_z ! Lattice cell array dimensions - integer :: temp_offset ! Looped sum of offsets - integer :: i_univ ! index in universes array - type(Cell), pointer :: c ! Pointer to current cell - type(Universe), pointer :: next_univ ! Next universe to loop through - class(Lattice), pointer :: lat ! Pointer to current lattice + + integer(C_INT) :: path_len + character(kind=C_CHAR), allocatable, target :: path_c(:) + + interface + function distribcell_path_len(target_cell, map, target_offset, root_univ)& + bind(C) result(len) + import C_INT32_T, C_INT + integer(C_INT32_T), intent(in), value :: target_cell, map, & + target_offset, root_univ + integer(C_INT) :: len + end function distribcell_path_len + + subroutine distribcell_path(target_cell, map, target_offset, root_univ, & + path) bind(C) + import C_INT32_T, C_CHAR + integer(C_INT32_T), intent(in), value :: target_cell, map, & + target_offset, root_univ + character(kind=C_CHAR), intent(out) :: path(*) + end subroutine distribcell_path + end interface ! Get the distribcell index for this cell map = cells(i_cell) % distribcell_index - ! Write to the geometry stack - i_univ = universe_dict % get(univ % id) - if (i_univ == root_universe) then - path = trim(path) // "u" // to_str(univ%id) - else - path = trim(path) // "->u" // to_str(univ%id) - end if - - ! Look through all cells in this universe - do i = 1, size(univ % cells) - ! If the cell matches the goal and the offset matches final, write to the - ! geometry stack - if (univ % cells(i) == i_cell .and. offset == target_offset) then - c => cells(univ % cells(i)) - path = trim(path) // "->c" // to_str(c % id()) - return - end if + path_len = distribcell_path_len(i_cell-1, map-1, target_offset, & + root_universe-1) + allocate(path_c(path_len)) + call distribcell_path(i_cell-1, map-1, target_offset, root_universe-1, & + path_c) + do i = 1, min(path_len, MAX_LINE_LEN) + if (path_c(i) == C_NULL_CHAR) exit + path(i:i) = path_c(i) end do - - ! Find the fill cell or lattice cell that contains the target offset. - do i = size(univ % cells), 2, -1 - c => cells(univ % cells(i)) - - ! Material cells don't contain other cells so ignore them. - if (c % type() /= FILL_MATERIAL) then - - if (c % type() == FILL_UNIVERSE) then - temp_offset = offset + c % offset(map-1) - else - ! Get the offset of the first lattice location - lat => lattices(c % fill() + 1) % obj - temp_offset = offset + lat % offset(map-1, [0, 0, 0]) - end if - - ! The desired cell is the first cell that gives an offset smaller or - ! equal to the target offset. - if (temp_offset <= target_offset) exit - end if - end do - - ! Add the cell to the path string. - c => cells(univ % cells(i)) - path = trim(path) // "->c" // to_str(c%id()) - - if (c % type() == FILL_UNIVERSE) then - ! Recurse into the fill cell. - offset = c % offset(map-1) + offset - next_univ => universes(c % fill() + 1) - call find_offset(i_cell, next_univ, target_offset, offset, path) - - elseif (c % type() == FILL_LATTICE) then - ! Recurse into the lattice cell. - lat => lattices(c % fill() + 1) % obj - select type (lat) - - type is (RectLattice) - path = trim(path) // "->l" // to_str(lat%id()) - - n_x = lat % n_cells(1) - n_y = lat % n_cells(2) - n_z = lat % n_cells(3) - - do m = n_z, 1, -1 - do l = n_y, 1, -1 - do k = n_x, 1, -1 - temp_offset = offset + lat % offset(map-1, [k-1, l-1, m-1]) - if (temp_offset <= target_offset) then - next_univ => universes(lat % get([k-1, l-1, m-1])+1) - if (lat % is_3d) then - path = trim(path) // "(" // trim(to_str(k-1)) // & - "," // trim(to_str(l-1)) // "," // & - trim(to_str(m-1)) // ")" - else - path = trim(path) // "(" // trim(to_str(k-1)) // & - "," // trim(to_str(l-1)) // ")" - end if - offset = temp_offset - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - end if - end do - end do - end do - - type is (HexLattice) - path = trim(path) // "->l" // to_str(lat%id()) - - n_z = lat % n_axial - n_y = 2 * lat % n_rings - 1 - n_x = 2 * lat % n_rings - 1 - - do m = n_z, 1, -1 - do l = n_y, 1, -1 - do k = n_x, 1, -1 - if (k + l < lat % n_rings + 1) then - cycle - else if (k + l > 3*lat % n_rings - 1) then - cycle - end if - temp_offset = offset + lat % offset(map-1, [k-1, l-1, m-1]) - if (temp_offset <= target_offset) then - next_univ => universes(lat % get([k-1, l-1, m-1])+1) - if (lat % is_3d) then - path = trim(path) // "(" // & - trim(to_str(k - lat % n_rings)) // "," // & - trim(to_str(l - lat % n_rings)) // "," // & - trim(to_str(m - 1)) // ")" - else - path = trim(path) // "(" // & - trim(to_str(k - lat % n_rings)) // "," // & - trim(to_str(l - lat % n_rings)) // ")" - end if - offset = temp_offset - call find_offset(i_cell, next_univ, target_offset, offset, path) - return - end if - end do - end do - end do - - end select - - end if end subroutine find_offset end module tally_filter_distribcell From 4614db8e2c2db1118befa0525b71a090822e8542 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 25 May 2018 21:22:54 -0400 Subject: [PATCH 288/361] Use const reverse iter for GCC 4.8 --- src/geometry_aux.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index a51e5b8a8..e79a00264 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -221,8 +221,9 @@ distribcell_path_inner(int32_t target_cell, int32_t map, int32_t target_offset, // The target must be further down the geometry tree and contained in a fill // cell or lattice cell in this universe. Find which cell contains the // target. - auto cell_it {search_univ.cells.rbegin()}; - for (; cell_it != search_univ.cells.rend(); ++cell_it) { + std::vector::const_reverse_iterator cell_it + {search_univ.cells.crbegin()}; + for (; cell_it != search_univ.cells.crend(); ++cell_it) { Cell &c = *cells_c[*cell_it]; // Material cells don't contain other cells so ignore them. From d78c5619de374c0fbec7f00a4e27e5f7835055ce Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 26 May 2018 13:55:05 -0400 Subject: [PATCH 289/361] Consistently use 0-based indexing for lattices --- src/geometry.F90 | 10 ++--- src/lattice.cpp | 50 ++++++++++++------------ src/tallies/tally_filter_distribcell.F90 | 6 +-- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/geometry.F90 b/src/geometry.F90 index 44f138e10..457fdf378 100644 --- a/src/geometry.F90 +++ b/src/geometry.F90 @@ -184,9 +184,9 @@ contains p % coord(k + 1) % lattice_z])) then offset = offset + lattices(p % coord(k + 1) % lattice) % obj & % offset(distribcell_index - 1, & - [p % coord(k + 1) % lattice_x - 1, & - p % coord(k + 1) % lattice_y - 1, & - p % coord(k + 1) % lattice_z - 1]) + [p % coord(k + 1) % lattice_x, & + p % coord(k + 1) % lattice_y, & + p % coord(k + 1) % lattice_z]) end if end if end do @@ -261,7 +261,7 @@ contains if (lat % are_valid_indices(i_xyz)) then ! Particle is inside the lattice. p % coord(j + 1) % universe = & - lat % get([i_xyz(1)-1, i_xyz(2)-1, i_xyz(3)-1]) + 1 + lat % get([i_xyz(1), i_xyz(2), i_xyz(3)]) + 1 else ! Particle is outside the lattice. @@ -340,7 +340,7 @@ contains ! Find cell in next lattice element p % coord(j) % universe = & - lat % get([i_xyz(1)-1, i_xyz(2)-1, i_xyz(3)-1]) + 1 + lat % get([i_xyz(1), i_xyz(2), i_xyz(3)]) + 1 call find_cell(p, found) if (.not. found) then diff --git a/src/lattice.cpp b/src/lattice.cpp index f5d0ba272..0e20d2079 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -246,9 +246,9 @@ RectLattice::operator[](const int i_xyz[3]) bool RectLattice::are_valid_indices(const int i_xyz[3]) const { - return ( (i_xyz[0] > 0) && (i_xyz[0] <= n_cells[0]) - && (i_xyz[1] > 0) && (i_xyz[1] <= n_cells[1]) - && (i_xyz[2] > 0) && (i_xyz[2] <= n_cells[2])); + return ( (i_xyz[0] >= 0) && (i_xyz[0] < n_cells[0]) + && (i_xyz[1] >= 0) && (i_xyz[1] < n_cells[1]) + && (i_xyz[2] >= 0) && (i_xyz[2] < n_cells[2])); } //============================================================================== @@ -318,13 +318,13 @@ RectLattice::distance(const double xyz[3], const double uvw[3], std::array RectLattice::get_indices(const double xyz[3]) const { - int ix {static_cast(std::ceil((xyz[0] - lower_left[0]) / pitch[0]))}; - int iy {static_cast(std::ceil((xyz[1] - lower_left[1]) / pitch[1]))}; + int ix {static_cast(std::ceil((xyz[0] - lower_left[0]) / pitch[0]))-1}; + int iy {static_cast(std::ceil((xyz[1] - lower_left[1]) / pitch[1]))-1}; int iz; if (is_3d) { - iz = static_cast(std::ceil((xyz[2] - lower_left[2]) / pitch[2])); + iz = static_cast(std::ceil((xyz[2] - lower_left[2]) / pitch[2]))-1; } else { - iz = 1; + iz = 0; } return {ix, iy, iz}; } @@ -335,10 +335,10 @@ std::array RectLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const { std::array local_xyz; - local_xyz[0] = global_xyz[0] - (lower_left[0] + (i_xyz[0] - 0.5)*pitch[0]); - local_xyz[1] = global_xyz[1] - (lower_left[1] + (i_xyz[1] - 0.5)*pitch[1]); + local_xyz[0] = global_xyz[0] - (lower_left[0] + (i_xyz[0] + 0.5)*pitch[0]); + local_xyz[1] = global_xyz[1] - (lower_left[1] + (i_xyz[1] + 0.5)*pitch[1]); if (is_3d) { - local_xyz[2] = global_xyz[2] - (lower_left[2] + (i_xyz[2] - 0.5)*pitch[2]); + local_xyz[2] = global_xyz[2] - (lower_left[2] + (i_xyz[2] + 0.5)*pitch[2]); } else { local_xyz[2] = global_xyz[2]; } @@ -618,11 +618,11 @@ HexLattice::rbegin() bool HexLattice::are_valid_indices(const int i_xyz[3]) const { - return ((i_xyz[0] > 0) && (i_xyz[1] > 0) && (i_xyz[2] > 0) - && (i_xyz[0] < 2*n_rings) && (i_xyz[1] < 2*n_rings) - && (i_xyz[0] + i_xyz[1] > n_rings) - && (i_xyz[0] + i_xyz[1] < 3*n_rings) - && (i_xyz[2] <= n_axial)); + return ((i_xyz[0] >= 0) && (i_xyz[1] >= 0) && (i_xyz[2] >= 0) + && (i_xyz[0] < 2*n_rings-1) && (i_xyz[1] < 2*n_rings-1) + && (i_xyz[0] + i_xyz[1] > n_rings-2) + && (i_xyz[0] + i_xyz[1] < 3*n_rings-2) + && (i_xyz[2] < n_axial)); } std::pair> @@ -738,9 +738,9 @@ HexLattice::get_indices(const double xyz[3]) const // Index the z direction. std::array out; if (is_3d) { - out[2] = static_cast(std::ceil(xyz_o[2] / pitch[1] + 0.5 * n_axial)); + out[2] = static_cast(std::ceil(xyz_o[2] / pitch[1] + 0.5 * n_axial))-1; } else { - out[2] = 1; + out[2] = 0; } // Convert coordinates into skewed bases. The (x, alpha) basis is used to @@ -751,9 +751,9 @@ HexLattice::get_indices(const double xyz[3]) const out[1] = static_cast(std::floor(alpha / pitch[0])); // Add offset to indices (the center cell is (i_x, i_alpha) = (0, 0) but - // the array is offset so that the indices never go below 1). - out[0] += n_rings; - out[1] += n_rings; + // the array is offset so that the indices never go below 0). + out[0] += n_rings-1; + out[1] += n_rings-1; // Calculate the (squared) distance between the particle and the centers of // the four possible cells. Regular hexagonal tiles form a Voronoi @@ -801,14 +801,14 @@ HexLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const // x_l = x_g - (center + pitch_x*cos(30)*index_x) local_xyz[0] = global_xyz[0] - (center[0] - + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings) * pitch[0]); + + std::sqrt(3.0)/2.0 * (i_xyz[0] - n_rings + 1) * pitch[0]); // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y) local_xyz[1] = global_xyz[1] - (center[1] - + (i_xyz[1] - n_rings) * pitch[0] - + (i_xyz[0] - n_rings) * pitch[0] / 2.0); + + (i_xyz[1] - n_rings + 1) * pitch[0] + + (i_xyz[0] - n_rings + 1) * pitch[0] / 2.0); if (is_3d) { local_xyz[2] = global_xyz[2] - center[2] - + (0.5 * n_axial - i_xyz[2] + 0.5) * pitch[1]; + + (0.5 * n_axial - i_xyz[2] - 0.5) * pitch[1]; } else { local_xyz[2] = global_xyz[2]; } @@ -827,7 +827,7 @@ HexLattice::is_valid_index(int indx) const int iz = indx / (nx * ny); int iy = (indx - nx*ny*iz) / nx; int ix = indx - nx*ny*iz - nx*iy; - int i_xyz[3] {ix+1, iy+1, iz+1}; // TODO: fix this off-by-one + int i_xyz[3] {ix, iy, iz}; return are_valid_indices(i_xyz); } diff --git a/src/tallies/tally_filter_distribcell.F90 b/src/tallies/tally_filter_distribcell.F90 index d6643190c..c9564083d 100644 --- a/src/tallies/tally_filter_distribcell.F90 +++ b/src/tallies/tally_filter_distribcell.F90 @@ -68,9 +68,9 @@ contains p % coord(i + 1) % lattice_z])) then offset = offset + lattices(p % coord(i + 1) % lattice) % obj & % offset(distribcell_index - 1, & - [p % coord(i + 1) % lattice_x - 1, & - p % coord(i + 1) % lattice_y - 1, & - p % coord(i + 1) % lattice_z - 1]) + [p % coord(i + 1) % lattice_x, & + p % coord(i + 1) % lattice_y, & + p % coord(i + 1) % lattice_z]) end if end if if (this % cell == p % coord(i) % cell) then From 7f7af84e64e804b8ca2860965f65e1f9d2257f28 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 27 May 2018 13:38:16 -0400 Subject: [PATCH 290/361] Miscellaneous cleanup --- docs/source/devguide/styleguide.rst | 7 +- src/cell.cpp | 34 +++++----- src/cell.h | 23 ++++--- src/geometry_header.F90 | 2 - src/hdf5_interface.h | 20 +----- src/input_xml.F90 | 3 +- src/lattice.cpp | 102 ++++++++-------------------- src/lattice.h | 80 +++++++++------------- src/summary.F90 | 4 +- src/surface.cpp | 24 +++---- 10 files changed, 114 insertions(+), 185 deletions(-) diff --git a/docs/source/devguide/styleguide.rst b/docs/source/devguide/styleguide.rst index 89d567394..ccf6bf521 100644 --- a/docs/source/devguide/styleguide.rst +++ b/docs/source/devguide/styleguide.rst @@ -219,8 +219,8 @@ Curly braces For a function definition, the opening and closing braces should each be on their own lines. This helps distinguish function code from the argument list. -If the entire function fits on one line, then the braces can be on the same -line. e.g.: +If the entire function fits on one or two lines, then the braces can be on the +same line. e.g.: .. code-block:: C++ @@ -238,6 +238,9 @@ line. e.g.: int return_one() {return 1;} + int return_one() + {return 1;} + For a conditional, the opening brace should be on the same line as the end of the conditional statement. If there is a following ``else if`` or ``else`` statement, the closing brace should be on the same line as that following diff --git a/src/cell.cpp b/src/cell.cpp index 82138a83f..cdbb44617 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -12,9 +12,6 @@ #include "surface.h" #include "xml_interface.h" -//TODO: remove this include -#include - namespace openmc { @@ -34,14 +31,13 @@ extern "C" double FP_PRECISION; // Global variables //============================================================================== -// Braces force n_cells to be defined here, not just declared. -extern "C" {int32_t n_cells {0};} +int32_t n_cells {0}; std::vector cells_c; -std::map cell_dict; +std::unordered_map cell_dict; std::vector universes_c; -std::map universe_dict; +std::unordered_map universe_dict; //============================================================================== //! Convert region specification string to integer tokens. @@ -249,6 +245,7 @@ Cell::Cell(pugi::xml_node cell_node) fatal_error(err_msg); } + // Read the region specification. std::string region_spec {""}; if (check_for_node(cell_node, "region")) { region_spec = get_node_value(cell_node, "region"); @@ -280,6 +277,8 @@ Cell::Cell(pugi::xml_node cell_node) } } +//============================================================================== + bool Cell::contains(const double xyz[3], const double uvw[3], int32_t on_surface) const @@ -291,6 +290,8 @@ Cell::contains(const double xyz[3], const double uvw[3], } } +//============================================================================== + std::pair Cell::distance(const double xyz[3], const double uvw[3], int32_t on_surface) const @@ -306,7 +307,6 @@ Cell::distance(const double xyz[3], const double uvw[3], // Note the off-by-one indexing bool coincident {token == on_surface}; double d {surfaces_c[abs(token)-1]->distance(xyz, uvw, coincident)}; - //std::cout << token << " " << on_surface << " " << coincident << std::endl; // Check if this distance is the new minimum. if (d < min_dist) { @@ -320,20 +320,18 @@ Cell::distance(const double xyz[3], const double uvw[3], return {min_dist, i_surf}; } +//============================================================================== + void Cell::to_hdf5(hid_t cell_group) const { -// std::string group_name {"surface "}; -// group_name += std::to_string(id); -// -// hid_t surf_group = create_group(group_id, group_name); - if (!name.empty()) { write_string(cell_group, "name", name, false); } //TODO: Fix the off-by-one indexing. - write_int(cell_group, "universe", universes_c[universe-1]->id); + write_int(cell_group, 0, nullptr, "universe", &universes_c[universe-1]->id, + false); // Write the region specification. if (!region.empty()) { @@ -355,10 +353,10 @@ Cell::to_hdf5(hid_t cell_group) const } write_string(cell_group, "region", region_spec.str(), false); } - -// close_group(cell_group); } +//============================================================================== + bool Cell::contains_simple(const double xyz[3], const double uvw[3], int32_t on_surface) const @@ -382,6 +380,8 @@ Cell::contains_simple(const double xyz[3], const double uvw[3], return true; } +//============================================================================== + bool Cell::contains_complex(const double xyz[3], const double uvw[3], int32_t on_surface) const @@ -432,6 +432,8 @@ Cell::contains_complex(const double xyz[3], const double uvw[3], } } +//============================================================================== +// Non-method functions //============================================================================== extern "C" void diff --git a/src/cell.h b/src/cell.h index 2b03e1cd0..f55c8e6fd 100644 --- a/src/cell.h +++ b/src/cell.h @@ -1,9 +1,9 @@ #ifndef CELL_H #define CELL_H -#include #include #include +#include #include #include "hdf5.h" @@ -28,11 +28,11 @@ extern "C" int32_t n_cells; class Cell; extern std::vector cells_c; -extern std::map cell_dict; +extern std::unordered_map cell_dict; class Universe; extern std::vector universes_c; -extern std::map universe_dict; +extern std::unordered_map universe_dict; //============================================================================== //! A geometry primitive that fills all space and contains cells. @@ -41,10 +41,9 @@ extern std::map universe_dict; class Universe { public: - int32_t id; //! Unique ID - int32_t type; - std::vector cells; //! Cells within this universe - double x0, y0, z0; //! Translation coordinates. + int32_t id; //!< Unique ID + std::vector cells; //!< Cells within this universe + //double x0, y0, z0; //!< Translation coordinates. }; //============================================================================== @@ -61,8 +60,9 @@ public: int32_t fill; //!< Universe # filling this cell int32_t n_instances{0}; //!< Number of instances of this cell - //! Material within this cell. May be multiple materials for distribcell. - //! C_NONE signifies a universe. + //! \brief Material(s) within this cell. + //! + //! May be multiple materials for distribcell. C_NONE signifies a universe. std::vector material; //! Definition of spatial region as Boolean expression of half-spaces @@ -77,7 +77,7 @@ public: explicit Cell(pugi::xml_node cell_node); - //! Determine if a cell contains the particle at a given location. + //! \brief Determine if a cell contains the particle at a given location. //! //! The bounds of the cell are detemined by a logical expression involving //! surface half-spaces. At initialization, the expression was converted @@ -99,10 +99,11 @@ public: bool contains(const double xyz[3], const double uvw[3], int32_t on_surface) const; + //! Find the oncoming boundary of this cell. std::pair distance(const double xyz[3], const double uvw[3], int32_t on_surface) const; - //! Write all information needed to reconstruct the cell to an HDF5 group. + //! \brief Write cell information to an HDF5 group. //! @param group_id An HDF5 group id. void to_hdf5(hid_t group_id) const; diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index 8f95c4a7d..d633b6700 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -292,7 +292,6 @@ module geometry_header integer(C_INT32_T), bind(C) :: n_cells ! # of cells integer(C_INT32_T), bind(C) :: n_universes ! # of universes - integer(C_INT32_T), bind(C) :: n_lattices ! # of lattices type(Cell), allocatable, target :: cells(:) type(Universe), allocatable, target :: universes(:) @@ -520,7 +519,6 @@ contains n_cells = 0 n_universes = 0 - n_lattices = 0 if (allocated(cells)) deallocate(cells) if (allocated(universes)) deallocate(universes) diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 232d2f6e7..353d0137a 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -84,20 +84,6 @@ void write_string(hid_t group_id, const char* name, const std::string& buffer, b extern "C" void write_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, const double* results); -inline void -write_int(hid_t group_id, char const *name, int32_t buffer) -{ - hid_t dataspace = H5Screate(H5S_SCALAR); - - hid_t dataset = H5Dcreate(group_id, name, H5T_NATIVE_INT32, dataspace, - H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); - - H5Dwrite(dataset, H5T_NATIVE_INT32, H5S_ALL, H5S_ALL, H5P_DEFAULT, &buffer); - - H5Sclose(dataspace); - H5Dclose(dataset); -} - template void write_int(hid_t group_id, char const *name, const std::array &buffer, bool indep) @@ -108,12 +94,12 @@ write_int(hid_t group_id, char const *name, template void -write_double_1D(hid_t group_id, char const *name, - const std::array &buffer) +write_double(hid_t group_id, char const *name, + const std::array &buffer, bool indep) { hsize_t dims[1] {array_len}; write_dataset(group_id, 1, dims, name, H5T_NATIVE_DOUBLE, - buffer.data(), false); + buffer.data(), indep); } } // namespace openmc diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 96cb2f754..e24f3fe25 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1257,8 +1257,7 @@ contains ! Allocate lattices array n_rlats = size(node_rlat_list) n_hlats = size(node_hlat_list) - n_lattices = n_rlats + n_hlats - allocate(lattices(n_lattices)) + allocate(lattices(n_rlats + n_hlats)) RECT_LATTICES: do i = 1, n_rlats allocate(RectLattice::lattices(i) % obj) diff --git a/src/lattice.cpp b/src/lattice.cpp index 0e20d2079..2d9bf02c5 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -5,15 +5,11 @@ #include #include "cell.h" -#include "constants.h" #include "error.h" #include "geometry_aux.h" #include "hdf5_interface.h" #include "xml_interface.h" -//TODO: remove this include -#include - namespace openmc { @@ -23,7 +19,7 @@ namespace openmc { std::vector lattices_c; -std::map lattice_dict; +std::unordered_map lattice_dict; //============================================================================== // Lattice implementation @@ -48,31 +44,17 @@ Lattice::Lattice(pugi::xml_node lat_node) //============================================================================== -LatticeIter -Lattice::begin() -{ - return LatticeIter(*this, 0); -} +LatticeIter Lattice::begin() +{return LatticeIter(*this, 0);} -LatticeIter -Lattice::end() -{ - return LatticeIter(*this, universes.size()); -} +LatticeIter Lattice::end() +{return LatticeIter(*this, universes.size());} -//============================================================================== +ReverseLatticeIter Lattice::rbegin() +{return ReverseLatticeIter(*this, universes.size()-1);} -ReverseLatticeIter -Lattice::rbegin() -{ - return ReverseLatticeIter(*this, universes.size()-1); -} - -ReverseLatticeIter -Lattice::rend() -{ - return ReverseLatticeIter(*this, -1); -} +ReverseLatticeIter Lattice::rend() +{return ReverseLatticeIter(*this, -1);} //============================================================================== @@ -80,7 +62,7 @@ void Lattice::adjust_indices() { // Adjust the indices for the universes array. - for (auto it = begin(); it != end(); ++it) { + for (LatticeIter it = begin(); it != end(); ++it) { int uid = *it; auto search = universe_dict.find(uid); if (search != universe_dict.end()) { @@ -109,18 +91,10 @@ Lattice::adjust_indices() //============================================================================== -void -Lattice::allocate_offset_table(int n_maps) -{ - offsets.resize(n_maps * universes.size(), C_NONE); -} - -//============================================================================== - int32_t Lattice::fill_offset_table(int32_t offset, int32_t target_univ_id, int map) { - for (auto it = begin(); it != end(); ++it) { + for (LatticeIter it = begin(); it != end(); ++it) { offsets[map * universes.size() + it.indx] = offset; offset += count_universe_instances(*it, target_univ_id); } @@ -202,9 +176,6 @@ RectLattice::RectLattice(pugi::xml_node lat_node) if (is_3d) {pitch[2] = stod(pitch_words[2]);} // Read the universes and make sure the correct number was specified. - int nx = n_cells[0]; - int ny = n_cells[1]; - int nz = n_cells[2]; std::string univ_str {get_node_value(lat_node, "universes")}; std::vector univ_words {split(univ_str)}; if (univ_words.size() != nx*ny*nz) { @@ -217,7 +188,7 @@ RectLattice::RectLattice(pugi::xml_node lat_node) } // Parse the universes. - universes.resize(nx*ny*nz, -1); + universes.resize(nx*ny*nz, C_NONE); for (int iz = 0; iz < nz; iz++) { for (int iy = ny-1; iy > -1; iy--) { for (int ix = 0; ix < nx; ix++) { @@ -234,9 +205,6 @@ RectLattice::RectLattice(pugi::xml_node lat_node) int32_t& RectLattice::operator[](const int i_xyz[3]) { - int nx = n_cells[0]; - int ny = n_cells[1]; - int nz = n_cells[2]; int indx = nx*ny*i_xyz[2] + nx*i_xyz[1] + i_xyz[0]; return universes[indx]; } @@ -350,9 +318,6 @@ RectLattice::get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const int32_t& RectLattice::offset(int map, const int i_xyz[3]) { - int nx = n_cells[0]; - int ny = n_cells[1]; - int nz = n_cells[2]; return offsets[nx*ny*nz*map + nx*ny*i_xyz[2] + nx*i_xyz[1] + i_xyz[0]]; } @@ -361,8 +326,6 @@ RectLattice::offset(int map, const int i_xyz[3]) std::string RectLattice::index_to_string(int indx) const { - int nx {n_cells[0]}; - int ny {n_cells[1]}; int iz {indx / (nx * ny)}; int iy {(indx - nx * ny * iz) / nx}; int ix {indx - nx * ny * iz - nx * iy}; @@ -384,14 +347,14 @@ RectLattice::to_hdf5_inner(hid_t lat_group) const // Write basic lattice information. write_string(lat_group, "type", "rectangular", false); if (is_3d) { - write_double_1D(lat_group, "pitch", pitch); - write_double_1D(lat_group, "lower_left", lower_left); + write_double(lat_group, "pitch", pitch, false); + write_double(lat_group, "lower_left", lower_left, false); write_int(lat_group, "dimension", n_cells, false); } else { std::array pitch_short {{pitch[0], pitch[1]}}; - write_double_1D(lat_group, "pitch", pitch_short); + write_double(lat_group, "pitch", pitch_short, false); std::array ll_short {{lower_left[0], lower_left[1]}}; - write_double_1D(lat_group, "lower_left", ll_short); + write_double(lat_group, "lower_left", ll_short, false); std::array nc_short {{n_cells[0], n_cells[1]}}; write_int(lat_group, "dimension", nc_short, false); } @@ -480,7 +443,6 @@ HexLattice::HexLattice(pugi::xml_node lat_node) if (is_3d) {pitch[1] = stod(pitch_words[1]);} // Read the universes and make sure the correct number was specified. - //int n_univ = (2*n_rings - 1) * (2*n_rings - 1) * n_axial; int n_univ = (3*n_rings*n_rings - 3*n_rings + 1) * n_axial; std::string univ_str {get_node_value(lat_node, "universes")}; std::vector univ_words {split(univ_str)}; @@ -502,7 +464,7 @@ HexLattice::HexLattice(pugi::xml_node lat_node) // in a manner that matches the input order. Note that i_x = 0, i_a = 0 // corresponds to the center of the hexagonal lattice. - universes.resize((2*n_rings-1) * (2*n_rings-1) * n_axial, -1); + universes.resize((2*n_rings-1) * (2*n_rings-1) * n_axial, C_NONE); int input_index = 0; for (int m = 0; m < n_axial; m++) { // Initialize lattice indecies. @@ -599,19 +561,11 @@ HexLattice::operator[](const int i_xyz[3]) //============================================================================== -LatticeIter -HexLattice::begin() -{ - return LatticeIter(*this, n_rings-1); -} +LatticeIter HexLattice::begin() +{return LatticeIter(*this, n_rings-1);} -//============================================================================== - -ReverseLatticeIter -HexLattice::rbegin() -{ - return ReverseLatticeIter(*this, universes.size()-n_rings); -} +ReverseLatticeIter HexLattice::rbegin() +{return ReverseLatticeIter(*this, universes.size()-n_rings);} //============================================================================== @@ -625,6 +579,8 @@ HexLattice::are_valid_indices(const int i_xyz[3]) const && (i_xyz[2] < n_axial)); } +//============================================================================== + std::pair> HexLattice::distance(const double xyz[3], const double uvw[3], const int i_xyz[3]) const @@ -872,13 +828,13 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const write_int(lat_group, 0, nullptr, "n_rings", &n_rings, false); write_int(lat_group, 0, nullptr, "n_axial", &n_axial, false); if (is_3d) { - write_double_1D(lat_group, "pitch", pitch); - write_double_1D(lat_group, "center", center); + write_double(lat_group, "pitch", pitch, false); + write_double(lat_group, "center", center, false); } else { std::array pitch_short {{pitch[0]}}; - write_double_1D(lat_group, "pitch", pitch_short); + write_double(lat_group, "pitch", pitch_short, false); std::array center_short {{center[0], center[1]}}; - write_double_1D(lat_group, "center", center_short); + write_double(lat_group, "center", center_short, false); } // Write the universe ids. @@ -976,9 +932,7 @@ extern "C" { } int32_t lattice_offset(Lattice *lat, int map, const int i_xyz[3]) - { - return lat->offset(map, i_xyz); - } + {return lat->offset(map, i_xyz);} int32_t lattice_outer(Lattice *lat) {return lat->outer;} diff --git a/src/lattice.h b/src/lattice.h index 3161a2473..909029aa9 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -3,11 +3,11 @@ #include #include -#include // For numeric_limits -#include #include +#include #include +#include "constants.h" #include "hdf5.h" #include "pugixml/pugixml.hpp" @@ -24,30 +24,27 @@ constexpr int32_t NO_OUTER_UNIVERSE{-1}; // Global variables //============================================================================== -//extern "C" int32_t n_lattice; - class Lattice; -//extern Lattice **lattices_c; extern std::vector lattices_c; -extern std::map lattice_dict; +extern std::unordered_map lattice_dict; //============================================================================== -//! Abstract type for ordered array of universes. +//! \class Lattice +//! \brief Abstract type for ordered array of universes. //============================================================================== class LatticeIter; - class ReverseLatticeIter; class Lattice { public: - int32_t id; //!< Universe ID number - std::string name; //!< User-defined name - std::vector universes; //!< Universes filling each lattice tile - int32_t outer{NO_OUTER_UNIVERSE}; //!< Universe tiled outside the lattice - std::vector offsets; //!< Distribcell offset table + int32_t id; //!< Universe ID number + std::string name; //!< User-defined name + std::vector universes; //!< Universes filling each lattice tile + int32_t outer {NO_OUTER_UNIVERSE}; //!< Universe tiled outside the lattice + std::vector offsets; //!< Distribcell offset table explicit Lattice(pugi::xml_node lat_node); @@ -65,17 +62,19 @@ public: void adjust_indices(); //! Allocate offset table for distribcell. - void allocate_offset_table(int n_maps); + void allocate_offset_table(int n_maps) + {offsets.resize(n_maps * universes.size(), C_NONE);} + //! Populate the distribcell offset tables. int32_t fill_offset_table(int32_t offset, int32_t target_univ_id, int map); - //! Check lattice indices. + //! \brief Check lattice indices. //! @param i_xyz[3] The indices for a lattice tile. //! @return true if the given indices fit within the lattice bounds. False //! otherwise. virtual bool are_valid_indices(const int i_xyz[3]) const = 0; - //! Find the next lattice surface crossing + //! \brief Find the next lattice surface crossing //! @param xyz[3] A 3D Cartesian coordinate. //! @param uvw[3] A 3D Cartesian direction. //! @param i_xyz[3] The indices for a lattice tile. @@ -85,45 +84,43 @@ public: distance(const double xyz[3], const double uvw[3], const int i_xyz[3]) const = 0; - //! Find the lattice tile indices for a given point. + //! \brief Find the lattice tile indices for a given point. //! @param xyz[3] A 3D Cartesian coordinate. //! @return An array containing the indices of a lattice tile. virtual std::array get_indices(const double xyz[3]) const = 0; - //! Get coordinates local to a lattice tile. + //! \brief Get coordinates local to a lattice tile. //! @param global_xyz[3] A 3D Cartesian coordinate. //! @param i_xyz[3] The indices for a lattice tile. //! @return Local 3D Cartesian coordinates. virtual std::array get_local_xyz(const double global_xyz[3], const int i_xyz[3]) const = 0; - //! Check flattened lattice index. + //! \brief Check flattened lattice index. //! @param indx The index for a lattice tile. //! @return true if the given index fit within the lattice bounds. False //! otherwise. virtual bool is_valid_index(int indx) const - { - return (indx >= 0) && (indx < universes.size()); - } + {return (indx >= 0) && (indx < universes.size());} - //! Get the distribcell offset for a lattice tile. + //! \brief Get the distribcell offset for a lattice tile. //! @param The map index for the target cell. //! @param i_xyz[3] The indices for a lattice tile. //! @return Distribcell offset i.e. the largest instance number for the target //! cell found in the geometry tree under this lattice tile. virtual int32_t& offset(int map, const int i_xyz[3]) = 0; - //! Convert an array index to a useful human-readable string. + //! \brief Convert an array index to a useful human-readable string. //! @param indx The index for a lattice tile. //! @return A string representing the lattice tile. virtual std::string index_to_string(int indx) const = 0; - //! Write all information needed to reconstruct the lattice to an HDF5 group. + //! \brief Write lattice information to an HDF5 group. //! @param group_id An HDF5 group id. void to_hdf5(hid_t group_id) const; protected: - bool is_3d; //! Has divisions along the z-axis + bool is_3d; //!< Has divisions along the z-axis? virtual void to_hdf5_inner(hid_t group_id) const = 0; }; @@ -135,7 +132,6 @@ protected: class LatticeIter { public: - int indx; //!< An index to a Lattice universes or offsets array. LatticeIter(Lattice &lat_, int indx_) @@ -143,20 +139,11 @@ public: indx(indx_) {} - bool operator==(const LatticeIter &rhs) - { - return (indx == rhs.indx); - } + bool operator==(const LatticeIter &rhs) {return (indx == rhs.indx);} - bool operator!=(const LatticeIter &rhs) - { - return !(*this == rhs); - } + bool operator!=(const LatticeIter &rhs) {return !(*this == rhs);} - int32_t& operator*() - { - return lat.universes[indx]; - } + int32_t& operator*() {return lat.universes[indx];} LatticeIter& operator++() { @@ -194,7 +181,6 @@ public: } }; -//============================================================================== //============================================================================== class RectLattice : public Lattice @@ -202,8 +188,6 @@ class RectLattice : public Lattice public: explicit RectLattice(pugi::xml_node lat_node); - virtual ~RectLattice() {} - int32_t& operator[](const int i_xyz[3]); bool are_valid_indices(const int i_xyz[3]) const; @@ -222,13 +206,17 @@ public: void to_hdf5_inner(hid_t group_id) const; -protected: +private: std::array n_cells; //!< Number of cells along each axis std::array lower_left; //!< Global lower-left corner of the lattice std::array pitch; //!< Lattice tile width along each axis + + // Convenience aliases + int &nx {n_cells[0]}; + int &ny {n_cells[1]}; + int &nz {n_cells[2]}; }; -//============================================================================== //============================================================================== class HexLattice : public Lattice @@ -236,8 +224,6 @@ class HexLattice : public Lattice public: explicit HexLattice(pugi::xml_node lat_node); - virtual ~HexLattice() {} - int32_t& operator[](const int i_xyz[3]); LatticeIter begin(); @@ -262,7 +248,7 @@ public: void to_hdf5_inner(hid_t group_id) const; -protected: +private: int n_rings; //!< Number of radial tile positions int n_axial; //!< Number of axial tile positions std::array center; //!< Global center of lattice diff --git a/src/summary.F90 b/src/summary.F90 index 464c0c6cf..02352db7c 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -176,7 +176,7 @@ contains call write_attribute(geom_group, "n_cells", n_cells) call write_attribute(geom_group, "n_surfaces", n_surfaces) call write_attribute(geom_group, "n_universes", n_universes) - call write_attribute(geom_group, "n_lattices", n_lattices) + call write_attribute(geom_group, "n_lattices", size(lattices)) ! ========================================================================== ! WRITE INFORMATION ON CELLS @@ -292,7 +292,7 @@ contains lattices_group = create_group(geom_group, "lattices") - do i = 1, n_lattices + do i = 1, size(lattices) lat => lattices(i)%obj call lat % to_hdf5(lattices_group) end do diff --git a/src/surface.cpp b/src/surface.cpp index 7dea2b7df..1155559fd 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -318,7 +318,7 @@ void SurfaceXPlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "x-plane", false); std::array coeffs {{x0}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } bool SurfaceXPlane::periodic_translate(PeriodicSurface *other, double xyz[3], @@ -383,7 +383,7 @@ void SurfaceYPlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "y-plane", false); std::array coeffs {{y0}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } bool SurfaceYPlane::periodic_translate(PeriodicSurface *other, double xyz[3], @@ -449,7 +449,7 @@ void SurfaceZPlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "z-plane", false); std::array coeffs {{z0}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } bool SurfaceZPlane::periodic_translate(PeriodicSurface *other, double xyz[3], @@ -510,7 +510,7 @@ void SurfacePlane::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "plane", false); std::array coeffs {{A, B, C, D}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } bool SurfacePlane::periodic_translate(PeriodicSurface *other, double xyz[3], @@ -642,7 +642,7 @@ void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "x-cylinder", false); std::array coeffs {{y0, z0, r}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } //============================================================================== @@ -676,7 +676,7 @@ void SurfaceYCylinder::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "y-cylinder", false); std::array coeffs {{x0, z0, r}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } //============================================================================== @@ -710,7 +710,7 @@ void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "z-cylinder", false); std::array coeffs {{x0, y0, r}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } //============================================================================== @@ -781,7 +781,7 @@ void SurfaceSphere::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "sphere", false); std::array coeffs {{x0, y0, z0, r}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } //============================================================================== @@ -898,7 +898,7 @@ void SurfaceXCone::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "x-cone", false); std::array coeffs {{x0, y0, z0, r_sq}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } //============================================================================== @@ -932,7 +932,7 @@ void SurfaceYCone::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "y-cone", false); std::array coeffs {{x0, y0, z0, r_sq}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } //============================================================================== @@ -966,7 +966,7 @@ void SurfaceZCone::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "z-cone", false); std::array coeffs {{x0, y0, z0, r_sq}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } //============================================================================== @@ -1060,7 +1060,7 @@ void SurfaceQuadric::to_hdf5_inner(hid_t group_id) const { write_string(group_id, "type", "quadric", false); std::array coeffs {{A, B, C, D, E, F, G, H, J, K}}; - write_double_1D(group_id, "coefficients", coeffs); + write_double(group_id, "coefficients", coeffs, false); } //============================================================================== From 3d5e9b4dc2062494d8ccf8cab511c42e6a345ddd Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 27 May 2018 14:54:51 -0400 Subject: [PATCH 291/361] Add deallocation for C++ geometry globals --- src/cell.cpp | 7 ------- src/geometry_aux.cpp | 19 +++++++++++++++++++ src/geometry_aux.h | 6 ++++++ src/geometry_header.F90 | 6 ++++++ tools/ci/travis-script.sh | 6 ++---- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/cell.cpp b/src/cell.cpp index cdbb44617..8cdc7b6f6 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -520,12 +520,5 @@ extern "C" { } } -//extern "C" void free_memory_cells_c() -//{ -// delete cells_c; -// cells_c = nullptr; -// n_cells = 0; -// cell_dict.clear(); -//} } // namespace openmc diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index e79a00264..dc31edf7e 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -321,4 +321,23 @@ maximum_levels(int32_t univ) return levels_below; } +//============================================================================== + +void +free_memory_geometry_c() +{ + for (Cell *c : cells_c) {delete c;} + cells_c.clear(); + cell_dict.clear(); + n_cells = 0; + + for (Universe *u : universes_c) {delete u;} + universes_c.clear(); + universe_dict.clear(); + + for (Lattice *lat : lattices_c) {delete lat;} + lattices_c.clear(); + lattice_dict.clear(); +} + } // namespace openmc diff --git a/src/geometry_aux.h b/src/geometry_aux.h index 7d2c3cd9a..b998ad32c 100644 --- a/src/geometry_aux.h +++ b/src/geometry_aux.h @@ -102,5 +102,11 @@ distribcell_path(int32_t target_cell, int32_t map, int32_t target_offset, extern "C" int maximum_levels(int32_t univ); +//============================================================================== +//! Deallocates global vectors and maps for cells, universes, and lattices. +//============================================================================== + +extern "C" void free_memory_geometry_c(); + } // namespace openmc #endif // GEOMETRY_AUX_H diff --git a/src/geometry_header.F90 b/src/geometry_header.F90 index d633b6700..758fc54da 100644 --- a/src/geometry_header.F90 +++ b/src/geometry_header.F90 @@ -516,6 +516,12 @@ contains !=============================================================================== subroutine free_memory_geometry() + interface + subroutine free_memory_geometry_c() bind(C) + end subroutine free_memory_geometry_c + end interface + + call free_memory_geometry_c() n_cells = 0 n_universes = 0 diff --git a/tools/ci/travis-script.sh b/tools/ci/travis-script.sh index 6dfbc9510..ee445b517 100755 --- a/tools/ci/travis-script.sh +++ b/tools/ci/travis-script.sh @@ -8,9 +8,7 @@ fi # Run regression and unit tests if [[ $MPI == 'y' ]]; then - pytest --cov=openmc -v --mpi tests/regression_tests - pytest --cov=openmc -v --mpi tests/unit_tests + pytest --cov=openmc -v --mpi tests else - pytest --cov=openmc -v tests/regression_tests - pytest --cov=openmc -v tests/unit_tests + pytest --cov=openmc -v tests fi From 6a53e61c966714d007c3b052eac5b979da0020eb Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Mon, 28 May 2018 13:24:30 +0000 Subject: [PATCH 292/361] Updating tallies.xml --- src/tallies/tally.F90 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index ef8b5915c..fb3a15d54 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -1075,17 +1075,17 @@ contains ! 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) + case (N_3N) m = 5 - case (N_A) + case (N_4N) m = 6 + case (N_GAMMA) + m = 1 + case (N_P) + m = 2 + case (N_A) + m = 3 end select if (i_nuclide > 0) then From 9857f4623155b9b700da179cb39e5d9afc650b9d Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Mon, 28 May 2018 18:13:59 +0000 Subject: [PATCH 293/361] making sure CMakelist.f90 and simulation.f90 match their corresponding files in the develop branch. --- CMakeLists.txt | 4 ++-- src/simulation.F90 | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9834d49a6..dc5558de1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,12 +110,12 @@ if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU) endif() # GCC compiler options - list(APPEND f90flags -cpp -std=f2008ts -fbacktrace -O2 -fstack-arrays -g) + list(APPEND f90flags -cpp -std=f2008ts -fbacktrace -O2 -fstack-arrays) if(debug) 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 -v -da -Q) + list(APPEND ldflags -g) endif() if(profile) list(APPEND f90flags -pg) diff --git a/src/simulation.F90 b/src/simulation.F90 index 203e2056a..cdb69efb4 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -513,13 +513,13 @@ contains ! 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) + 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 51911bacb43297f3230e43639787b6375d7df359 Mon Sep 17 00:00:00 2001 From: guillaume Date: Mon, 28 May 2018 18:36:43 -0400 Subject: [PATCH 294/361] added transfer volumes function, changed titles in depletion example --- .../python/pincell_depletion/run_depletion.py | 6 +++--- openmc/deplete/results_list.py | 15 +++++++++++++++ openmc/material.py | 2 +- src/summary.F90 | 3 ++- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py index 8c12211bc..a17bf5fd9 100644 --- a/examples/python/pincell_depletion/run_depletion.py +++ b/examples/python/pincell_depletion/run_depletion.py @@ -49,7 +49,7 @@ borated_water.add_element('O', 2.4e-2) borated_water.add_s_alpha_beta('c_H_in_H2O') ############################################################################### -# Exporting to OpenMC geometry.xml file +# Create geometry ############################################################################### # Instantiate ZCylinder surfaces @@ -94,7 +94,7 @@ root.add_cells([fuel, gap, clad, water]) geometry = openmc.Geometry(root) ############################################################################### -# Exporting to OpenMC materials.xml file +# Volumes of depletable materials ############################################################################### # Compute cell areas @@ -105,7 +105,7 @@ area[fuel] = np.pi * fuel_or.coefficients['R'] ** 2 uo2.volume = area[fuel] ############################################################################### -# Exporting to OpenMC settings.xml file +# Transport calculation settings ############################################################################### # Instantiate a Settings object, set all runtime parameters, and export to XML diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py index 58e4b66dc..a12b16ab4 100644 --- a/openmc/deplete/results_list.py +++ b/openmc/deplete/results_list.py @@ -103,3 +103,18 @@ class ResultsList(list): eigenvalue[i] = result.k[0] return time, eigenvalue + + def transfer_volumes(self, geometry): + """Transfers volumes from depletion results to geometry + + Parameters + ---------- + geometry : OpenMC geometry to be used in a depletion restart + calculation + + """ + cells = geometry.get_all_material_cells() + for c in cells: + material = next(iter(cells[c].get_all_materials().values())) + if material.depletable == True: + material.volume = self[0].volume[str(material.id)] diff --git a/openmc/material.py b/openmc/material.py index cfa3eba3c..dac2fe14a 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -467,7 +467,7 @@ class Material(IDManagerMixin): raise ValueError(msg) # Generally speaking, the density for a macroscopic object will - # be 1.0. Therefore, lets set density to 1.0 so that the user + # be 1.0. Therefore, lets set density to 1.0 so that the user # doesnt need to set it unless its needed. # Of course, if the user has already set a value of density, # then we will not override it. diff --git a/src/summary.F90 b/src/summary.F90 index bd6ef7158..de3d20a17 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -440,6 +440,7 @@ contains if (m % depletable) then call write_attribute(material_group, "depletable", 1) + call write_attribute(material_group, "volume", m % volume) else call write_attribute(material_group, "depletable", 0) end if @@ -500,7 +501,7 @@ contains call write_dataset(material_group, "nuclides", nuc_names) ! Deallocate temporary array deallocate(nuc_names) - ! Write atom densities + ! Write nuclide atom densities call write_dataset(material_group, "nuclide_densities", nuc_densities) deallocate(nuc_densities) end if From ed65c15b308b69bc7ed98b64b3f982595ab18e5b Mon Sep 17 00:00:00 2001 From: guillaume Date: Mon, 28 May 2018 18:38:31 -0400 Subject: [PATCH 295/361] added depletion restart python example --- .../pincell_depletion/restart_depletion.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 examples/python/pincell_depletion/restart_depletion.py diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py new file mode 100644 index 000000000..6f30c68ea --- /dev/null +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -0,0 +1,78 @@ +import openmc +import openmc.deplete +import numpy as np + +############################################################################### +# Simulation Input File Parameters +############################################################################### + +# OpenMC simulation parameters +batches = 100 +inactive = 10 +particles = 1000 + +# Depletion simulation parameters +time_step = 1*24*60*60 # s +final_time = 15*24*60*60 # s +time_steps = np.full(final_time // time_step, time_step) + +chain_file = './chain_simple.xml' +power = 174 # W/cm, for 2D simulations only (use W for 3D) + +############################################################################### +# Load previous simulation results +############################################################################### + +# Load geometry from statepoint +statepoint = 'statepoint.100.h5' +sp = openmc.StatePoint(statepoint) +geometry = sp.summary.geometry + +# Load previous delpletion results +previous_results = openmc.deplete.ResultsList("depletion_results.h5") + +# Reload volumes into geometry +previous_results.transfer_volumes(geometry) + +############################################################################### +# Set transport calculation settings +############################################################################### + +# Instantiate a Settings object, set all runtime parameters, and export to XML +settings_file = openmc.Settings() +settings_file.batches = batches +settings_file.inactive = inactive +settings_file.particles = particles + +# Create an initial uniform spatial source distribution over fissionable zones +bounds = [-0.62992, -0.62992, -1, 0.62992, 0.62992, 1] +uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) +settings_file.source = openmc.source.Source(space=uniform_dist) + +entropy_mesh = openmc.Mesh() +entropy_mesh.lower_left = [-0.39218, -0.39218, -1.e50] +entropy_mesh.upper_right = [0.39218, 0.39218, 1.e50] +entropy_mesh.dimension = [10, 10, 1] +settings_file.entropy_mesh = entropy_mesh + +############################################################################### +# Initialize and run depletion calculation +############################################################################### + +op = openmc.deplete.Operator(geometry, settings_file, chain_file) + +# Perform simulation using the predictor algorithm +openmc.deplete.integrator.predictor(op, time_steps, power) + +############################################################################### +# Read depletion calculation results +############################################################################### + +# Open results file +results = openmc.deplete.ResultsList("depletion_results.h5") + +# Obtain K_eff as a function of time +time, keff = results.get_eigenvalue() + +# Obtain U235 concentration as a function of time +time, n_U235 = results.get_atoms('1', 'U235') From 1673b2983a03f4d9f6aa542c49fbf578f94e8cfe Mon Sep 17 00:00:00 2001 From: guillaume Date: Tue, 29 May 2018 17:45:31 -0400 Subject: [PATCH 296/361] added restart in predictor, working on skipping initial step if already ran --- .../pincell_depletion/restart_depletion.py | 28 ++++++++---- .../python/pincell_depletion/run_depletion.py | 8 ++-- openmc/deplete/integrator/predictor.py | 43 +++++++++++++++---- openmc/deplete/operator.py | 15 ++++++- src/summary.F90 | 1 - 5 files changed, 72 insertions(+), 23 deletions(-) diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index 6f30c68ea..928c45a28 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -1,6 +1,7 @@ import openmc import openmc.deplete import numpy as np +import matplotlib.pyplot as plt ############################################################################### # Simulation Input File Parameters @@ -13,7 +14,7 @@ particles = 1000 # Depletion simulation parameters time_step = 1*24*60*60 # s -final_time = 15*24*60*60 # s +final_time = 5*24*60*60 # s time_steps = np.full(final_time // time_step, time_step) chain_file = './chain_simple.xml' @@ -28,14 +29,15 @@ statepoint = 'statepoint.100.h5' sp = openmc.StatePoint(statepoint) geometry = sp.summary.geometry -# Load previous delpletion results +# Close statepoint and summary files to be able to write over them +sp.summary._f.close() +sp._f.close() + +# Load previous depletion results previous_results = openmc.deplete.ResultsList("depletion_results.h5") -# Reload volumes into geometry -previous_results.transfer_volumes(geometry) - ############################################################################### -# Set transport calculation settings +# Transport calculation settings ############################################################################### # Instantiate a Settings object, set all runtime parameters, and export to XML @@ -59,7 +61,8 @@ settings_file.entropy_mesh = entropy_mesh # Initialize and run depletion calculation ############################################################################### -op = openmc.deplete.Operator(geometry, settings_file, chain_file) +op = openmc.deplete.Operator(geometry, settings_file, chain_file, \ + previous_results) # Perform simulation using the predictor algorithm openmc.deplete.integrator.predictor(op, time_steps, power) @@ -73,6 +76,13 @@ results = openmc.deplete.ResultsList("depletion_results.h5") # Obtain K_eff as a function of time time, keff = results.get_eigenvalue() + +print(time/24/60/60) +print(keff) -# Obtain U235 concentration as a function of time -time, n_U235 = results.get_atoms('1', 'U235') +# Plot eigenvalue as a function of time +plt.figure() +plt.plot(time/24/60/60, keff, label="K-effective") +plt.xlabel("Time (day)") +plt.ylabel("Keff") +plt.show() diff --git a/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py index a17bf5fd9..03e25fc0e 100644 --- a/examples/python/pincell_depletion/run_depletion.py +++ b/examples/python/pincell_depletion/run_depletion.py @@ -13,7 +13,7 @@ particles = 1000 # Depletion simulation parameters time_step = 1*24*60*60 # s -final_time = 15*24*60*60 # s +final_time = 5*24*60*60 # s time_steps = np.full(final_time // time_step, time_step) chain_file = './chain_simple.xml' @@ -94,7 +94,7 @@ root.add_cells([fuel, gap, clad, water]) geometry = openmc.Geometry(root) ############################################################################### -# Volumes of depletable materials +# Set volumes of depletable materials ############################################################################### # Compute cell areas @@ -105,7 +105,7 @@ area[fuel] = np.pi * fuel_or.coefficients['R'] ** 2 uo2.volume = area[fuel] ############################################################################### -# Transport calculation settings +# Transport calculation settings ############################################################################### # Instantiate a Settings object, set all runtime parameters, and export to XML @@ -135,7 +135,7 @@ op = openmc.deplete.Operator(geometry, settings_file, chain_file) openmc.deplete.integrator.predictor(op, time_steps, power) ############################################################################### -# Read depletion calculation results +# Read depletion calculation results ############################################################################### # Open results file diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 9be992c16..d3b38aa3a 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -42,17 +42,44 @@ def predictor(operator, timesteps, power, print_out=True): # Generate initial conditions with operator as vec: chain = operator.chain - t = 0.0 - for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep reaction rates - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] - # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], i) + # Initialize time + if operator.prev_res == None: + t = 0.0 + else: + t = operator.prev_res.get_eigenvalue()[0][-1] + print("Time", t/24/60/60) + + # Initialize starting index for saving results + if operator.prev_res == None: + i_res = 0 + else: + i_res = len(operator.prev_res.get_eigenvalue()[0]) + print(i_res) + + for i, (dt, p) in enumerate(zip(timesteps, power)): + + # Avoid doing first run if already done in previous calculation + if i > 0 or operator.prev_res == None or p != p_end: + # Get beginning-of-timestep reaction rates + x = [copy.deepcopy(vec)] + op_results = [operator(x[0], p)] + + # Create results, write to disk + Results.save(operator, x, op_results, [t, t + dt], i + i_res) + + else: + x = [copy.deepcopy(vec)] + op_results = operator.prev_res[0] + print(op_results) + op_results = [operator(x[0], p)] + print(op_results) # Deplete for full timestep + #print(x[0], op_results[0]) x_end = deplete(chain, x[0], op_results[0], dt, print_out) + print("Time", t/24/60/60) + print("Step", i + i_res) # Advance time, update vector t += dt @@ -63,4 +90,4 @@ def predictor(operator, timesteps, power, print_out=True): op_results = [operator(x[0], power[-1])] # Create results, write to disk - Results.save(operator, x, op_results, [t, t], len(timesteps)) + Results.save(operator, x, op_results, [t, t], len(timesteps) + i_res) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index b40ff6344..ebd025f94 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -66,6 +66,8 @@ class Operator(TransportOperator): chain_file : str, optional Path to the depletion chain XML file. Defaults to the :envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists. + prev_results : ResultsList, optional + Results from the previous depletion calculation. Defaults to None Attributes ---------- @@ -94,14 +96,25 @@ class Operator(TransportOperator): All burnable material IDs local_mats : list of str All burnable material IDs being managed by a single process + prev_res : ResultsList + Results from the previous depletion run """ - def __init__(self, geometry, settings, chain_file=None): + def __init__(self, geometry, settings, chain_file=None, prev_results=None): super().__init__(chain_file) self.round_number = False self.settings = settings self.geometry = geometry + if prev_results != None: + # Reload volumes into geometry + prev_results.transfer_volumes(geometry) + + # Store previous results in operator + self.prev_res = prev_results + else: + self.prev_res = None + # Clear out OpenMC, create task lists, distribute openmc.reset_auto_ids() self.burnable_mats, volume, nuclides = self._get_burnable_mats() diff --git a/src/summary.F90 b/src/summary.F90 index de3d20a17..45c92062d 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -440,7 +440,6 @@ contains if (m % depletable) then call write_attribute(material_group, "depletable", 1) - call write_attribute(material_group, "volume", m % volume) else call write_attribute(material_group, "depletable", 0) end if From cf85c2bb05b3e4263a13aba31390c6ceacc6e2bb Mon Sep 17 00:00:00 2001 From: guillaume Date: Tue, 29 May 2018 18:03:37 -0400 Subject: [PATCH 297/361] skipping first step if already ran, not checking if power is the same though --- openmc/deplete/integrator/predictor.py | 30 +++++++++++++------------- openmc/deplete/operator.py | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index d3b38aa3a..7bc6b9546 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -48,35 +48,35 @@ def predictor(operator, timesteps, power, print_out=True): t = 0.0 else: t = operator.prev_res.get_eigenvalue()[0][-1] - print("Time", t/24/60/60) # Initialize starting index for saving results if operator.prev_res == None: i_res = 0 else: i_res = len(operator.prev_res.get_eigenvalue()[0]) - print(i_res) + + #TODO : Get last time step power from previous results, and run a + # new calculation if different from power at the first time step + # If no TH coupling, just re-scale rates by ratio of power for i, (dt, p) in enumerate(zip(timesteps, power)): - # Avoid doing first run if already done in previous calculation - if i > 0 or operator.prev_res == None or p != p_end: - # Get beginning-of-timestep reaction rates - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] + # Get beginning-of-timestep concentrations + x = [copy.deepcopy(vec)] - # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], i + i_res) + # Get beginning-of-timestep reaction rates + # Avoid doing first run if already done in previous calculation + if i > 0 or operator.prev_res == None: + op_results = [operator(x[0], p)] else: - x = [copy.deepcopy(vec)] - op_results = operator.prev_res[0] - print(op_results) - op_results = [operator(x[0], p)] - print(op_results) + op_results = [operator.prev_res[-1]] + op_results[0].rates = op_results[0].rates[0] + + # Create results, write to disk + Results.save(operator, x, op_results, [t, t + dt], i + i_res) # Deplete for full timestep - #print(x[0], op_results[0]) x_end = deplete(chain, x[0], op_results[0], dt, print_out) print("Time", t/24/60/60) print("Step", i + i_res) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index ebd025f94..44c57f160 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -319,7 +319,7 @@ class Operator(TransportOperator): densities.append(val) else: # Only output warnings if values are significantly - # negative. CRAM does not guarantee positive values. + # 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)") From 563e3436e462fbf96049dff205e4367bacb5cfdd Mon Sep 17 00:00:00 2001 From: guillaume Date: Tue, 29 May 2018 19:09:38 -0400 Subject: [PATCH 298/361] getting initial nuclide densities from depletion results, other choice would be to update python densities, but depletion results are needed anyway --- .../pincell_depletion/restart_depletion.py | 2 +- openmc/deplete/integrator/predictor.py | 3 +- openmc/deplete/operator.py | 49 ++++++++++++++++--- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index 928c45a28..7bb365fe4 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -40,7 +40,7 @@ previous_results = openmc.deplete.ResultsList("depletion_results.h5") # Transport calculation settings ############################################################################### -# Instantiate a Settings object, set all runtime parameters, and export to XML +# Instantiate a Settings object, set all runtime parameters settings_file = openmc.Settings() settings_file.batches = batches settings_file.inactive = inactive diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 7bc6b9546..f55393b54 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -55,7 +55,7 @@ def predictor(operator, timesteps, power, print_out=True): else: i_res = len(operator.prev_res.get_eigenvalue()[0]) - #TODO : Get last time step power from previous results, and run a + #TODO : Get last time step power from previous results, and run a # new calculation if different from power at the first time step # If no TH coupling, just re-scale rates by ratio of power @@ -68,7 +68,6 @@ def predictor(operator, timesteps, power, print_out=True): # Avoid doing first run if already done in previous calculation if i > 0 or operator.prev_res == None: op_results = [operator(x[0], p)] - else: op_results = [operator.prev_res[-1]] op_results[0].rates = op_results[0].rates[0] diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 44c57f160..49474a925 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -77,7 +77,7 @@ class Operator(TransportOperator): 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 + 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. @@ -112,6 +112,9 @@ class Operator(TransportOperator): # Store previous results in operator self.prev_res = prev_results + + # Get number densities from previous results + #self.number = prev_results else: self.prev_res = None @@ -125,8 +128,9 @@ class Operator(TransportOperator): 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(self.local_mats, volume, nuclides) + # Extract number densities from the geometry / previous depletion run + self._extract_number(self.local_mats, volume, nuclides, \ + self.prev_res) # Create reaction rates array self.reaction_rates = ReactionRates( @@ -225,7 +229,7 @@ class Operator(TransportOperator): return burnable_mats, volume, nuclides - def _extract_number(self, local_mats, volume, nuclides): + def _extract_number(self, local_mats, volume, nuclides, prev_res=None): """Construct AtomNumber using geometry Parameters @@ -244,10 +248,18 @@ class Operator(TransportOperator): for nuc in self._burnable_nucs: 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(): - if str(mat.id) in local_mats: - self._set_number_from_mat(mat) + # Now extract and store the number densities + # From the geometry if no previous depletion results + if prev_res == None: + for mat in self.geometry.get_all_materials().values(): + if str(mat.id) in local_mats: + self._set_number_from_mat(mat) + + # Else from previous depletion results + else: + for mat in self.geometry.get_all_materials().values(): + if str(mat.id) in local_mats: + self._set_number_from_results(mat, prev_res) def _set_number_from_mat(self, mat): """Extracts material and number densities from openmc.Material @@ -262,6 +274,23 @@ class Operator(TransportOperator): for nuclide, density in mat.get_nuclide_atom_densities().values(): number = density * 1.0e24 + print(nuclide) + self.number.set_atom_density(mat_id, nuclide, number) + + def _set_number_from_results(self, mat, prev_res): + """Extracts material and number densities from previous results + + Parameters + ---------- + mat : openmc.Material + The material to read from + + """ + mat_id = str(mat.id) + + for nuclide, density in mat.get_nuclide_atom_densities().values(): + number = density * 1.0e24 + print(nuclide, number) self.number.set_atom_density(mat_id, nuclide, number) def initial_condition(self): @@ -325,9 +354,13 @@ class Operator(TransportOperator): " is negative (density = ", val, " at/barn-cm)") number_i[mat, nuc] = 0.0 + # Update densities on C API side mat_internal = openmc.capi.materials[int(mat)] mat_internal.set_densities(nuclides, densities) + #TODO Update densities on the Python side, otherwise the + # summary.h5 file contains densities at the first time step + def _generate_materials_xml(self): """Creates materials.xml from self.number. From 73d8b4a043ff90221490ce9c00cded227557895f Mon Sep 17 00:00:00 2001 From: guillaume Date: Tue, 29 May 2018 19:55:54 -0400 Subject: [PATCH 299/361] getting initial nuclide densities from depletion results for the burnable ones, from geometry for the non-burnable ones --- openmc/deplete/integrator/predictor.py | 2 -- openmc/deplete/operator.py | 26 +++++++++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index f55393b54..f672707f5 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -77,8 +77,6 @@ def predictor(operator, timesteps, power, print_out=True): # Deplete for full timestep x_end = deplete(chain, x[0], op_results[0], dt, print_out) - print("Time", t/24/60/60) - print("Step", i + i_res) # Advance time, update vector t += dt diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 49474a925..1c0d0e492 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -274,11 +274,13 @@ class Operator(TransportOperator): for nuclide, density in mat.get_nuclide_atom_densities().values(): number = density * 1.0e24 - print(nuclide) self.number.set_atom_density(mat_id, nuclide, number) def _set_number_from_results(self, mat, prev_res): - """Extracts material and number densities from previous results + """Extracts material and number densities. + + If the nuclide is in the chain, densities come from depletion results + Else, densities come from the geometry in the summary Parameters ---------- @@ -288,9 +290,23 @@ class Operator(TransportOperator): """ mat_id = str(mat.id) - for nuclide, density in mat.get_nuclide_atom_densities().values(): - number = density * 1.0e24 - print(nuclide, number) + # Get nuclide lists from geometry and depletion results + depl_nuc = prev_res[-1].nuc_to_ind.keys() + geom_nuc_densities = mat.get_nuclide_atom_densities() + geom_nuc = [x[0] for x in list(geom_nuc_densities.values())] + + # Merge lists of nuclides + nuc_set = list(depl_nuc) + [n for n in geom_nuc if n not in depl_nuc] + + for nuclide in nuc_set: + if nuclide in depl_nuc: + concentration = prev_res.get_atoms(mat_id, nuclide)[1][-1] + volume = prev_res[-1].volume[mat_id] + number = concentration / volume + else: + density = geom_nuc_densities[nuclide][1] + number = density * 1.0e24 + self.number.set_atom_density(mat_id, nuclide, number) def initial_condition(self): From 29222f69cfcc901b49bcdf4382e2b8c6eb6f2cc8 Mon Sep 17 00:00:00 2001 From: guillaume Date: Tue, 29 May 2018 20:03:05 -0400 Subject: [PATCH 300/361] cleaner way to get initial time --- examples/python/pincell_depletion/restart_depletion.py | 3 --- openmc/deplete/integrator/predictor.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index 7bb365fe4..e37174ca8 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -76,9 +76,6 @@ results = openmc.deplete.ResultsList("depletion_results.h5") # Obtain K_eff as a function of time time, keff = results.get_eigenvalue() - -print(time/24/60/60) -print(keff) # Plot eigenvalue as a function of time plt.figure() diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index f672707f5..a0ef56dcf 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -47,7 +47,7 @@ def predictor(operator, timesteps, power, print_out=True): if operator.prev_res == None: t = 0.0 else: - t = operator.prev_res.get_eigenvalue()[0][-1] + t = operator.prev_res[-1].time[-1] # Initialize starting index for saving results if operator.prev_res == None: From d2bce45a319d1d687166ee055132885cd2ac512a Mon Sep 17 00:00:00 2001 From: guillaume Date: Tue, 29 May 2018 20:04:01 -0400 Subject: [PATCH 301/361] cleaned a comment --- openmc/deplete/operator.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 1c0d0e492..a15cd35fa 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -112,9 +112,6 @@ class Operator(TransportOperator): # Store previous results in operator self.prev_res = prev_results - - # Get number densities from previous results - #self.number = prev_results else: self.prev_res = None From f4968e1e03c22a907898d992146018a22192dd14 Mon Sep 17 00:00:00 2001 From: guillaume Date: Tue, 29 May 2018 22:39:17 -0400 Subject: [PATCH 302/361] updated comment --- openmc/deplete/operator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index a15cd35fa..0af5bed0b 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -274,10 +274,11 @@ class Operator(TransportOperator): self.number.set_atom_density(mat_id, nuclide, number) def _set_number_from_results(self, mat, prev_res): - """Extracts material and number densities. + """Extracts material nuclides and number densities. - If the nuclide is in the chain, densities come from depletion results - Else, densities come from the geometry in the summary + If the nuclide concentration's evolution is tracked, the densities come + from depletion results. + Else, densities are extracted from the geometry in the summary. Parameters ---------- From 2b08112b912ab45a38f8c08b76a70277a7d6bec6 Mon Sep 17 00:00:00 2001 From: guillaume Date: Tue, 29 May 2018 22:56:47 -0400 Subject: [PATCH 303/361] getting volumes from last result --- openmc/deplete/results_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py index a12b16ab4..c4b0a41bf 100644 --- a/openmc/deplete/results_list.py +++ b/openmc/deplete/results_list.py @@ -117,4 +117,4 @@ class ResultsList(list): for c in cells: material = next(iter(cells[c].get_all_materials().values())) if material.depletable == True: - material.volume = self[0].volume[str(material.id)] + material.volume = self[-1].volume[str(material.id)] From 8726744ef2931c46dbdbe121f5dec85decf4becc Mon Sep 17 00:00:00 2001 From: guillaume Date: Tue, 29 May 2018 23:57:31 -0400 Subject: [PATCH 304/361] added support for changing power at beginning of restart without recomputing rates (no TH feedback) --- .../pincell_depletion/restart_depletion.py | 4 ++-- openmc/deplete/integrator/predictor.py | 10 ++++++---- openmc/deplete/results.py | 19 ++++++++++++++++++- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index e37174ca8..65f73a3da 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -18,7 +18,7 @@ final_time = 5*24*60*60 # s time_steps = np.full(final_time // time_step, time_step) chain_file = './chain_simple.xml' -power = 174 # W/cm, for 2D simulations only (use W for 3D) +power = 180 # W/cm, for 2D simulations only (use W for 3D) ############################################################################### # Load previous simulation results @@ -80,6 +80,6 @@ time, keff = results.get_eigenvalue() # Plot eigenvalue as a function of time plt.figure() plt.plot(time/24/60/60, keff, label="K-effective") -plt.xlabel("Time (day)") +plt.xlabel("Time (days)") plt.ylabel("Keff") plt.show() diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index a0ef56dcf..ed6db4efb 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -60,7 +60,6 @@ def predictor(operator, timesteps, power, print_out=True): # If no TH coupling, just re-scale rates by ratio of power for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep concentrations x = [copy.deepcopy(vec)] @@ -69,11 +68,14 @@ def predictor(operator, timesteps, power, print_out=True): if i > 0 or operator.prev_res == None: op_results = [operator(x[0], p)] else: + power_res = operator.prev_res[-1].power + ratio_power = p / power_res + op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] + op_results[0].rates = ratio_power * op_results[0].rates[0] # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], i + i_res) + Results.save(operator, x, op_results, [t, t + dt], p, i + i_res) # Deplete for full timestep x_end = deplete(chain, x[0], op_results[0], dt, print_out) @@ -87,4 +89,4 @@ def predictor(operator, timesteps, power, print_out=True): op_results = [operator(x[0], power[-1])] # Create results, write to disk - Results.save(operator, x, op_results, [t, t], len(timesteps) + i_res) + Results.save(operator, x, op_results, [t, t], p, len(timesteps) + i_res) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index ab740e61e..afdfffb74 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -24,6 +24,8 @@ class Results(object): Eigenvalue for each substep. time : list of float Time at beginning, end of step, in seconds. + power : float + Power during time step, in Watts n_mat : int Number of mats. n_nuc : int @@ -49,6 +51,7 @@ class Results(object): def __init__(self): self.k = None self.time = None + self.power = None self.rates = None self.volume = None @@ -237,6 +240,9 @@ class Results(object): handle.create_dataset("time", (1, 2), maxshape=(None, 2), dtype='float64') + handle.create_dataset("power", (1, n_stages), maxshape=(None, n_stages), + dtype='float64') + def _to_hdf5(self, handle, index): """Converts results object into an hdf5 object. @@ -259,6 +265,7 @@ class Results(object): rxn_dset = handle["/reaction rates"] eigenvalues_dset = handle["/eigenvalues"] time_dset = handle["/time"] + power_dset = handle["/power"] # Get number of results stored number_shape = list(number_dset.shape) @@ -283,6 +290,10 @@ class Results(object): time_shape[0] = new_shape time_dset.resize(time_shape) + power_shape = list(power_dset.shape) + power_shape[0] = new_shape + power_dset.resize(power_shape) + # If nothing to write, just return if len(self.mat_to_ind) == 0: return @@ -300,6 +311,7 @@ class Results(object): eigenvalues_dset[index, i] = self.k[i] if comm.rank == 0: time_dset[index, :] = self.time + power_dset[index, :] = self.power @classmethod def from_hdf5(cls, handle, step): @@ -319,10 +331,12 @@ class Results(object): number_dset = handle["/number"] eigenvalues_dset = handle["/eigenvalues"] time_dset = handle["/time"] + power_dset = handle["/power"] results.data = number_dset[step, :, :, :] results.k = eigenvalues_dset[step, :] results.time = time_dset[step, :] + results.power = power_dset[step, :] # Reconstruct dictionaries results.volume = OrderedDict() @@ -359,7 +373,7 @@ class Results(object): return results @staticmethod - def save(op, x, op_results, t, step_ind): + def save(op, x, op_results, t, power, step_ind): """Creates and writes depletion results to disk Parameters @@ -372,6 +386,8 @@ class Results(object): Results of applying transport operator t : list of float Time indices. + power : float + Power during time step step_ind : int Step index. @@ -393,5 +409,6 @@ class Results(object): results.k = [r.k for r in op_results] results.rates = [r.rates for r in op_results] results.time = t + results.power = power results.export_to_hdf5("depletion_results.h5", step_ind) From d4ff02d2a23282bc4109b039c322ffe217a5456d Mon Sep 17 00:00:00 2001 From: guillaume Date: Wed, 30 May 2018 13:39:04 -0400 Subject: [PATCH 305/361] first round of adapting tests --- examples/python/pincell_depletion/restart_depletion.py | 2 +- openmc/deplete/integrator/cecm.py | 4 ++-- tests/dummy_operator.py | 1 + tests/unit_tests/test_deplete_integrator.py | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index 65f73a3da..af7d4c727 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -18,7 +18,7 @@ final_time = 5*24*60*60 # s time_steps = np.full(final_time // time_step, time_step) chain_file = './chain_simple.xml' -power = 180 # W/cm, for 2D simulations only (use W for 3D) +power = 174 # W/cm, for 2D simulations only (use W for 3D) ############################################################################### # Load previous simulation results diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 61b58d0b9..b2c766e99 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -64,7 +64,7 @@ def cecm(operator, timesteps, power, print_out=True): x_end = deplete(chain, x[0], op_results[1], dt, print_out) # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], i) + Results.save(operator, x, op_results, [t, t + dt], p, i) # Advance time, update vector t += dt @@ -75,4 +75,4 @@ def cecm(operator, timesteps, power, print_out=True): op_results = [operator(x[0], power[-1])] # Create results, write to disk - Results.save(operator, x, op_results, [t, t], len(timesteps)) + Results.save(operator, x, op_results, [t, t], p, len(timesteps)) diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index 05fe97a95..867d30dd6 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -18,6 +18,7 @@ class DummyOperator(TransportOperator): """ def __init__(self): + self.prev_res = None pass def __call__(self, vec, power, print_out=False): diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index a1768625b..c19f1c5f6 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -72,8 +72,8 @@ def test_results_save(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)] - Results.save(op, x1, op_result1, t1, 0) - Results.save(op, x2, op_result2, t2, 1) + Results.save(op, x1, op_result1, t1, 0, 0) + Results.save(op, x2, op_result2, t2, 0, 1) # Load the files res = ResultsList("depletion_results.h5") From 1e68f8635a6d1dabc745ff67721f01c288c82e6b Mon Sep 17 00:00:00 2001 From: guillaume Date: Wed, 30 May 2018 16:25:58 -0400 Subject: [PATCH 306/361] added power to test_reference.h5 results so that they can be read --- tests/regression_tests/test_reference.h5 | Bin 160024 -> 162944 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression_tests/test_reference.h5 b/tests/regression_tests/test_reference.h5 index bae9a1adfefbce2d9124bfe6c9be1b7b2e5d3fa9..10dc37b511bb76bb3aafc5f8bcaf3a3a9c960b5b 100644 GIT binary patch delta 584 zcmY*WF=!KE6u$4BH2+mf8?cz=Ogh*G3r!4Khl;tyHoYu`E*cTC2#FmGHnaiL5-1dP zkP8`J7NKs14(90apB>~J3dN~oa0*TZmnP8jZh(lcYx7 zz4L)kIsQVwb%@sIu}{}aTKGCPny%Sei`4e|5!sL6Vw%@xke1>Wayq9|s%^}`A7f_; zG47j)$x1SytfZnY=2hFOn1XvIlCqczU@sxqpI5M(fhV|_f#6OCQ}S>L$K&MZ`S~*D zA`4>jnWkAy_c5O@YWu}&wI>G4D(wSya(F{eUh>2$;-NOZ zieKbZ*|diyZT50lr?cH5p=HA(?Hym*Sd8{gLtE=c=uS;{PWOp;Bai!Zkmd0L7UX#z zwF$C5vi%f~DDi{e2UWYt--Fs)mrAuYY4`&(!QVC!*AJAbGKCEF)obM+3e8f$h=2 e+Ew7bA-x@RABxbgpOwzZ2rT^>fz~}568{6lE1s1A delta 434 zcmXv}O(;ZR6n*DDdG8HEQBsUq;6<59*bvXc%)=~ZIgOO8{A^7Arlh8m@aQ(DR?m|Jg}c+*gBJ*Cg`S_K{Sa%FN!)_rc93qapt4k1W$+2sm&gJCltkU@?%^K%tFFLTSpKNFk81zl{iFN%Ts%$29N8ax+r@}273 z2Y4fjezN+wc?vzE6h*3rJ{?XZFigb<-go116+gQ%Z4248R%@jG%yfQSfnSYDo9hL% z8)hH Date: Wed, 30 May 2018 16:28:25 -0400 Subject: [PATCH 307/361] more natural way of getting starting index for depletion restart --- openmc/deplete/integrator/predictor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index ed6db4efb..ea40d25c7 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -53,7 +53,7 @@ def predictor(operator, timesteps, power, print_out=True): if operator.prev_res == None: i_res = 0 else: - i_res = len(operator.prev_res.get_eigenvalue()[0]) + i_res = len(operator.prev_res) #TODO : Get last time step power from previous results, and run a # new calculation if different from power at the first time step From 87aed7a0b095b3902156e2a385bc46db2ba7f5ac Mon Sep 17 00:00:00 2001 From: guillaume Date: Thu, 31 May 2018 17:33:43 -0400 Subject: [PATCH 308/361] addressed smharper's comments --- .../pincell_depletion/restart_depletion.py | 3 +-- openmc/deplete/integrator/predictor.py | 21 ++++++++--------- openmc/deplete/operator.py | 23 +++++++++++-------- openmc/deplete/results.py | 14 +++++++++++ openmc/deplete/results_list.py | 15 ------------ openmc/statepoint.py | 3 +++ 6 files changed, 41 insertions(+), 38 deletions(-) diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index af7d4c727..d96fe156c 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -30,8 +30,7 @@ sp = openmc.StatePoint(statepoint) geometry = sp.summary.geometry # Close statepoint and summary files to be able to write over them -sp.summary._f.close() -sp._f.close() +sp.exit() # Load previous depletion results previous_results = openmc.deplete.ResultsList("depletion_results.h5") diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index ea40d25c7..ea9880294 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -44,29 +44,29 @@ def predictor(operator, timesteps, power, print_out=True): chain = operator.chain # Initialize time - if operator.prev_res == None: + if operator.prev_res is None: t = 0.0 else: t = operator.prev_res[-1].time[-1] # Initialize starting index for saving results - if operator.prev_res == None: + if operator.prev_res is None: i_res = 0 else: - i_res = len(operator.prev_res) - - #TODO : Get last time step power from previous results, and run a - # new calculation if different from power at the first time step - # If no TH coupling, just re-scale rates by ratio of power + i_res = len(operator.prev_res) - 1 for i, (dt, p) in enumerate(zip(timesteps, power)): # Get beginning-of-timestep concentrations x = [copy.deepcopy(vec)] # Get beginning-of-timestep reaction rates - # Avoid doing first run if already done in previous calculation - if i > 0 or operator.prev_res == None: + # Avoid doing first transport run if already done in previous + # calculation + if i > 0 or operator.prev_res is None: op_results = [operator(x[0], p)] + + # Create results, write to disk + Results.save(operator, x, op_results, [t, t + dt], p, i + i_res) else: power_res = operator.prev_res[-1].power ratio_power = p / power_res @@ -74,9 +74,6 @@ def predictor(operator, timesteps, power, print_out=True): op_results = [operator.prev_res[-1]] op_results[0].rates = ratio_power * op_results[0].rates[0] - # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], p, i + i_res) - # Deplete for full timestep x_end = deplete(chain, x[0], op_results[0], dt, print_out) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 0af5bed0b..71031be02 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -67,7 +67,9 @@ class Operator(TransportOperator): Path to the depletion chain XML file. Defaults to the :envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists. prev_results : ResultsList, optional - Results from the previous depletion calculation. Defaults to None + Results from a previous depletion calculation. If this argument is + specified, the depletion calculation will start from the latest state + in the previous results. Attributes ---------- @@ -97,7 +99,7 @@ class Operator(TransportOperator): local_mats : list of str All burnable material IDs being managed by a single process prev_res : ResultsList - Results from the previous depletion run + Results from a previous depletion calculation """ def __init__(self, geometry, settings, chain_file=None, prev_results=None): @@ -108,7 +110,7 @@ class Operator(TransportOperator): if prev_results != None: # Reload volumes into geometry - prev_results.transfer_volumes(geometry) + prev_results[-1].transfer_volumes(geometry) # Store previous results in operator self.prev_res = prev_results @@ -126,8 +128,7 @@ class Operator(TransportOperator): if nuc in self.chain] # Extract number densities from the geometry / previous depletion run - self._extract_number(self.local_mats, volume, nuclides, \ - self.prev_res) + self._extract_number(self.local_mats, volume, nuclides, self.prev_res) # Create reaction rates array self.reaction_rates = ReactionRates( @@ -237,6 +238,8 @@ class Operator(TransportOperator): Volumes for the above materials in [cm^3] nuclides : list of str Nuclides to be used in the simulation. + prev_res : ResultsList, optional + Results from a previous depletion calculation """ self.number = AtomNumber(local_mats, nuclides, volume, len(self.chain)) @@ -247,7 +250,7 @@ class Operator(TransportOperator): # Now extract and store the number densities # From the geometry if no previous depletion results - if prev_res == None: + if prev_res is None: for mat in self.geometry.get_all_materials().values(): if str(mat.id) in local_mats: self._set_number_from_mat(mat) @@ -277,13 +280,15 @@ class Operator(TransportOperator): """Extracts material nuclides and number densities. If the nuclide concentration's evolution is tracked, the densities come - from depletion results. - Else, densities are extracted from the geometry in the summary. + from depletion results. Else, densities are extracted from the geometry + in the summary. Parameters ---------- mat : openmc.Material The material to read from + prev_res : ResultsList + Results from a previous depletion calculation """ mat_id = str(mat.id) @@ -294,7 +299,7 @@ class Operator(TransportOperator): geom_nuc = [x[0] for x in list(geom_nuc_densities.values())] # Merge lists of nuclides - nuc_set = list(depl_nuc) + [n for n in geom_nuc if n not in depl_nuc] + nuc_set = set(depl_nuc) | set(geom_nuc) for nuclide in nuc_set: if nuclide in depl_nuc: diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index afdfffb74..1d0dc3a0d 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -412,3 +412,17 @@ class Results(object): results.power = power results.export_to_hdf5("depletion_results.h5", step_ind) + + def transfer_volumes(self, geometry): + """Transfers volumes from depletion results to geometry + + Parameters + ---------- + geometry : OpenMC geometry to be used in a depletion restart + calculation + + """ + for cell in geometry.get_all_material_cells().values(): + for material in cell.get_all_materials().values(): + if material.depletable: + material.volume = self.volume[str(material.id)] diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py index c4b0a41bf..58e4b66dc 100644 --- a/openmc/deplete/results_list.py +++ b/openmc/deplete/results_list.py @@ -103,18 +103,3 @@ class ResultsList(list): eigenvalue[i] = result.k[0] return time, eigenvalue - - def transfer_volumes(self, geometry): - """Transfers volumes from depletion results to geometry - - Parameters - ---------- - geometry : OpenMC geometry to be used in a depletion restart - calculation - - """ - cells = geometry.get_all_material_cells() - for c in cells: - material = next(iter(cells[c].get_all_materials().values())) - if material.depletable == True: - material.volume = self[-1].volume[str(material.id)] diff --git a/openmc/statepoint.py b/openmc/statepoint.py index a200e900e..03f53bbb0 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -153,6 +153,9 @@ class StatePoint(object): if self._summary is not None: self._summary._f.close() + def exit(self): + self.__exit__() + @property def cmfd_on(self): return self._f.attrs['cmfd_on'] > 0 From f25cdb93f2e5fbb0e4ecbc20a29fc1e2871e9adc Mon Sep 17 00:00:00 2001 From: guillaume Date: Thu, 31 May 2018 17:39:34 -0400 Subject: [PATCH 309/361] renamed exit() to close() --- examples/python/pincell_depletion/restart_depletion.py | 2 +- openmc/statepoint.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index d96fe156c..a99565235 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -30,7 +30,7 @@ sp = openmc.StatePoint(statepoint) geometry = sp.summary.geometry # Close statepoint and summary files to be able to write over them -sp.exit() +sp.close() # Load previous depletion results previous_results = openmc.deplete.ResultsList("depletion_results.h5") diff --git a/openmc/statepoint.py b/openmc/statepoint.py index 03f53bbb0..fcc076b94 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -153,7 +153,7 @@ class StatePoint(object): if self._summary is not None: self._summary._f.close() - def exit(self): + def close(self): self.__exit__() @property From a85db74b2b1fc624606e5d67d91cc200b738e1e0 Mon Sep 17 00:00:00 2001 From: guillaume Date: Sat, 2 Jun 2018 14:02:14 -0400 Subject: [PATCH 310/361] addressed paulromano's review, added support for restart in cecm integrator --- .../pincell_depletion/restart_depletion.py | 14 ++++---- .../python/pincell_depletion/run_depletion.py | 2 +- openmc/deplete/integrator/cecm.py | 36 ++++++++++++++++--- openmc/deplete/integrator/predictor.py | 7 ++-- openmc/deplete/operator.py | 4 +-- openmc/statepoint.py | 3 -- tests/dummy_operator.py | 1 - 7 files changed, 44 insertions(+), 23 deletions(-) diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index a99565235..cfca2adf0 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -26,11 +26,8 @@ power = 174 # W/cm, for 2D simulations only (use W for 3D) # Load geometry from statepoint statepoint = 'statepoint.100.h5' -sp = openmc.StatePoint(statepoint) -geometry = sp.summary.geometry - -# Close statepoint and summary files to be able to write over them -sp.close() +with openmc.StatePoint(statepoint) as sp: + geometry = sp.summary.geometry # Load previous depletion results previous_results = openmc.deplete.ResultsList("depletion_results.h5") @@ -60,8 +57,8 @@ settings_file.entropy_mesh = entropy_mesh # Initialize and run depletion calculation ############################################################################### -op = openmc.deplete.Operator(geometry, settings_file, chain_file, \ - previous_results) +op = openmc.deplete.Operator(geometry, settings_file, chain_file, + previous_results) # Perform simulation using the predictor algorithm openmc.deplete.integrator.predictor(op, time_steps, power) @@ -78,7 +75,8 @@ time, keff = results.get_eigenvalue() # Plot eigenvalue as a function of time plt.figure() -plt.plot(time/24/60/60, keff, label="K-effective") +plt.plot(time/(24*60*60), keff, label="K-effective") plt.xlabel("Time (days)") plt.ylabel("Keff") plt.show() +plt.close() diff --git a/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py index 03e25fc0e..6600bec06 100644 --- a/examples/python/pincell_depletion/run_depletion.py +++ b/examples/python/pincell_depletion/run_depletion.py @@ -132,7 +132,7 @@ settings_file.entropy_mesh = entropy_mesh op = openmc.deplete.Operator(geometry, settings_file, chain_file) # Perform simulation using the predictor algorithm -openmc.deplete.integrator.predictor(op, time_steps, power) +openmc.deplete.integrator.cecm(op, time_steps, power) ############################################################################### # Read depletion calculation results diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index b2c766e99..0ab430226 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -47,11 +47,36 @@ def cecm(operator, timesteps, power, print_out=True): # Generate initial conditions with operator as vec: chain = operator.chain - t = 0.0 + + # Initialize time + if operator.prev_res is None: + t = 0.0 + else: + t = operator.prev_res[-1].time[-1] + + # Initialize starting index for saving results + if operator.prev_res is None: + i_res = 0 + else: + i_res = len(operator.prev_res) + for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep reaction rates + # Get beginning-of-timestep concentrations x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] + + # Get beginning-of-timestep reaction rates + # Avoid doing first transport run if already done in previous + # calculation + if i > 0 or operator.prev_res is None: + op_results = [operator(x[0], p)] + + else: + power_res = operator.prev_res[-1].power + ratio_power = p / power_res + + op_results = [operator.prev_res[-1]] + op_results[0].rates = ratio_power[0] * op_results[0].rates[0] + op_results[0].k = op_results[0].k[0] # Deplete for first half of timestep x_middle = deplete(chain, x[0], op_results[0], dt/2, print_out) @@ -61,10 +86,11 @@ def cecm(operator, timesteps, power, print_out=True): op_results.append(operator(x_middle, p)) # Deplete for full timestep using beginning-of-step materials + # and middle-of-timestep reaction rates x_end = deplete(chain, x[0], op_results[1], dt, print_out) # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], p, i) + Results.save(operator, x, op_results, [t, t + dt], p, i_res + i) # Advance time, update vector t += dt @@ -75,4 +101,4 @@ def cecm(operator, timesteps, power, print_out=True): op_results = [operator(x[0], power[-1])] # Create results, write to disk - Results.save(operator, x, op_results, [t, t], p, len(timesteps)) + Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps)) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index ea9880294..4711b7f85 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -66,13 +66,14 @@ def predictor(operator, timesteps, power, print_out=True): op_results = [operator(x[0], p)] # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], p, i + i_res) + Results.save(operator, x, op_results, [t, t + dt], p, i_res + i) else: power_res = operator.prev_res[-1].power + print(power_res) ratio_power = p / power_res op_results = [operator.prev_res[-1]] - op_results[0].rates = ratio_power * op_results[0].rates[0] + op_results[0].rates = ratio_power[0] * op_results[0].rates[0] # Deplete for full timestep x_end = deplete(chain, x[0], op_results[0], dt, print_out) @@ -86,4 +87,4 @@ def predictor(operator, timesteps, power, print_out=True): op_results = [operator(x[0], power[-1])] # Create results, write to disk - Results.save(operator, x, op_results, [t, t], p, len(timesteps) + i_res) + Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps)) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 71031be02..9ffe74063 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -296,10 +296,10 @@ class Operator(TransportOperator): # Get nuclide lists from geometry and depletion results depl_nuc = prev_res[-1].nuc_to_ind.keys() geom_nuc_densities = mat.get_nuclide_atom_densities() - geom_nuc = [x[0] for x in list(geom_nuc_densities.values())] + geom_nuc = {x[0] for x in geom_nuc_densities.values()} # Merge lists of nuclides - nuc_set = set(depl_nuc) | set(geom_nuc) + nuc_set = set(depl_nuc) | geom_nuc for nuclide in nuc_set: if nuclide in depl_nuc: diff --git a/openmc/statepoint.py b/openmc/statepoint.py index fcc076b94..a200e900e 100644 --- a/openmc/statepoint.py +++ b/openmc/statepoint.py @@ -153,9 +153,6 @@ class StatePoint(object): if self._summary is not None: self._summary._f.close() - def close(self): - self.__exit__() - @property def cmfd_on(self): return self._f.attrs['cmfd_on'] > 0 diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index 867d30dd6..fd230635d 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -19,7 +19,6 @@ class DummyOperator(TransportOperator): """ def __init__(self): self.prev_res = None - pass def __call__(self, vec, power, print_out=False): """Evaluates F(y) From 09a4475e38a8e4202c9a8ceb3bf29a0e73d4052e Mon Sep 17 00:00:00 2001 From: guillaume Date: Sat, 2 Jun 2018 16:31:47 -0400 Subject: [PATCH 311/361] fixed bugs when changing integrator at restart time --- .../python/pincell_depletion/run_depletion.py | 2 +- openmc/deplete/integrator/cecm.py | 7 +++---- openmc/deplete/integrator/predictor.py | 9 ++++----- openmc/deplete/operator.py | 1 + openmc/deplete/results.py | 15 +++++++++++++-- tests/dummy_operator.py | 4 ++-- tests/unit_tests/test_deplete_predictor.py | 2 +- 7 files changed, 25 insertions(+), 15 deletions(-) diff --git a/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py index 6600bec06..03e25fc0e 100644 --- a/examples/python/pincell_depletion/run_depletion.py +++ b/examples/python/pincell_depletion/run_depletion.py @@ -132,7 +132,7 @@ settings_file.entropy_mesh = entropy_mesh op = openmc.deplete.Operator(geometry, settings_file, chain_file) # Perform simulation using the predictor algorithm -openmc.deplete.integrator.cecm(op, time_steps, power) +openmc.deplete.integrator.predictor(op, time_steps, power) ############################################################################### # Read depletion calculation results diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 0ab430226..a80f7ee26 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -61,16 +61,15 @@ def cecm(operator, timesteps, power, print_out=True): i_res = len(operator.prev_res) for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep concentrations - x = [copy.deepcopy(vec)] - - # Get beginning-of-timestep reaction rates + # Get beginning-of-timestep concentrations and reaction rates # Avoid doing first transport run if already done in previous # calculation if i > 0 or operator.prev_res is None: + x = [copy.deepcopy(vec)] op_results = [operator(x[0], p)] else: + x = [operator.prev_res[-1].data[0]] power_res = operator.prev_res[-1].power ratio_power = p / power_res diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 4711b7f85..6918b222e 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -38,6 +38,7 @@ def predictor(operator, timesteps, power, print_out=True): """ if not isinstance(power, Iterable): power = [power]*len(timesteps) + print(power) # Generate initial conditions with operator as vec: @@ -56,20 +57,18 @@ def predictor(operator, timesteps, power, print_out=True): i_res = len(operator.prev_res) - 1 for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep concentrations - x = [copy.deepcopy(vec)] - - # Get beginning-of-timestep reaction rates + # Get beginning-of-timestep concentrations and reaction rates # Avoid doing first transport run if already done in previous # calculation if i > 0 or operator.prev_res is None: + x = [copy.deepcopy(vec)] op_results = [operator(x[0], p)] # Create results, write to disk Results.save(operator, x, op_results, [t, t + dt], p, i_res + i) else: + x = operator.prev_res[-1].data power_res = operator.prev_res[-1].power - print(power_res) ratio_power = p / power_res op_results = [operator.prev_res[-1]] diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 9ffe74063..24e8843ab 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -134,6 +134,7 @@ class Operator(TransportOperator): self.reaction_rates = ReactionRates( 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/results.py b/openmc/deplete/results.py index 1d0dc3a0d..f31f5672f 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -5,6 +5,7 @@ Contains results generation and saving capabilities. from collections import OrderedDict import copy +from warnings import warn import numpy as np import h5py @@ -395,8 +396,18 @@ class Results(object): # Get indexing terms vol_dict, nuc_list, burn_list, full_burn_list = op.get_results_info() - # Create results + # For a restart calculation, limit number of stages saved to meet the + # format of the hdf5 file stages = len(x) + offset = 0 + if op.prev_res is not None and op.prev_res[0].n_stages < stages: + offset = stages - op.prev_res[0].n_stages + stages = min(stages, op.prev_res[0].n_stages) + warn("Number of restart integrator stages saved limited by initial" + " depletion integrator choice to {}" + .format(op.prev_res[0].n_stages)) + + # Create results results = Results() results.allocate(vol_dict, nuc_list, burn_list, full_burn_list, stages) @@ -404,7 +415,7 @@ class Results(object): for i in range(stages): for mat_i in range(n_mat): - results[i, mat_i, :] = x[i][mat_i][:] + results[i, mat_i, :] = x[offset + i][mat_i][:] results.k = [r.k for r in op_results] results.rates = [r.rates for r in op_results] diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index fd230635d..c66070ad4 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -17,8 +17,8 @@ class DummyOperator(TransportOperator): y_2(1.5) ~ 3.1726475740397628 """ - def __init__(self): - self.prev_res = None + def __init__(self, previous_results=None): + self.prev_res = previous_results def __call__(self, vec, power, print_out=False): """Evaluates F(y) diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index 50803e508..b39cc7dca 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -10,7 +10,7 @@ from tests import dummy_operator def test_predictor(run_in_tmpdir): - """Integral regression test of integrator algorithm using predictor/corrector""" + """Integral regression test of integrator algorithm using predictor""" op = dummy_operator.DummyOperator() op.output_dir = "test_integrator_regression" From b2c720b29689be01bf8c9eb77d5735080bab9afc Mon Sep 17 00:00:00 2001 From: guillaume Date: Sat, 2 Jun 2018 16:34:41 -0400 Subject: [PATCH 312/361] added unit tests for depletion restart capability --- tests/unit_tests/test_deplete_restart.py | 168 ++++++++++++++++++++++ tests/unit_tests/test_transfer_volumes.py | 45 ++++++ 2 files changed, 213 insertions(+) create mode 100644 tests/unit_tests/test_deplete_restart.py create mode 100644 tests/unit_tests/test_transfer_volumes.py diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py new file mode 100644 index 000000000..745f2cce5 --- /dev/null +++ b/tests/unit_tests/test_deplete_restart.py @@ -0,0 +1,168 @@ +"""Regression tests for openmc.deplete restart capability. + +These tests run in two steps, a first run then a restart run, a simple test +problem described in dummy_geometry.py. +""" + +from pytest import approx +import openmc.deplete + +from tests import dummy_operator + + +def test_restart_predictor(run_in_tmpdir): + """Integral regression test of integrator algorithm using predictor.""" + + op = dummy_operator.DummyOperator() + output_dir = "test_restart_predictor" + op.output_dir = output_dir + + # Perform simulation using the predictor algorithm + dt = [0.75] + power = 1.0 + openmc.deplete.predictor(op, dt, power, print_out=False) + + # Load the files + prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + # Re-create depletion operator and load previous results + op = dummy_operator.DummyOperator(prev_res) + op.output_dir = output_dir + + # Perform restarts simulation using the predictor algorithm + openmc.deplete.predictor(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Mathematica solution + s1 = [2.46847546272295, 0.986431226850467] + s2 = [4.11525874568034, -0.0581692232513460] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) + + +def test_restart_cecm(run_in_tmpdir): + """Integral regression test of integrator algorithm using CE/CM.""" + + op = dummy_operator.DummyOperator() + output_dir = "test_restart_cecm" + op.output_dir = output_dir + + # Perform simulation using the MCNPX/MCNP6 algorithm + dt = [0.75] + power = 1.0 + openmc.deplete.cecm(op, dt, power, print_out=False) + + # Load the files + prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + # Re-create depletion operator and load previous results + op = dummy_operator.DummyOperator(prev_res) + op.output_dir = output_dir + + # Perform restarts simulation using the MCNPX/MCNP6 algorithm + openmc.deplete.cecm(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Mathematica solution + s1 = [1.86872629872102, 1.395525772416039] + s2 = [2.18097439443550, 2.69429754646747] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[3] == approx(s2[0]) + assert y2[3] == approx(s2[1]) + + +def test_restart_predictor_cecm(run_in_tmpdir): + """Integral regression test of integrator algorithm using predictor + for the first run then CE/CM for the restart run.""" + + op = dummy_operator.DummyOperator() + output_dir = "test_restart_predictor_cecm" + op.output_dir = output_dir + + # Perform simulation using the predictor algorithm + dt = [0.75] + power = 1.0 + openmc.deplete.predictor(op, dt, power, print_out=False) + + # Load the files + prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + # Re-create depletion operator and load previous results + op = dummy_operator.DummyOperator(prev_res) + op.output_dir = output_dir + + # Perform restarts simulation using the MCNPX/MCNP6 algorithm + openmc.deplete.cecm(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Test solution + s1 = [2.46847546272295, 0.986431226850467] + s2 = [3.09106948392, 0.607102912398] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) + + +def test_restart_cecm_predictor(run_in_tmpdir): + """Integral regression test of integrator algorithm using CE/CM for the + first run then predictor for the restart run.""" + + op = dummy_operator.DummyOperator() + output_dir = "test_restart_cecm_predictor" + op.output_dir = output_dir + + # Perform simulation using the MCNPX/MCNP6 algorithm + dt = [0.75] + power = 1.0 + openmc.deplete.cecm(op, dt, power, print_out=False) + + # Load the files + prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + # Re-create depletion operator and load previous results + op = dummy_operator.DummyOperator(prev_res) + op.output_dir = output_dir + + # Perform restarts simulation using the predictor algorithm + openmc.deplete.predictor(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + _, y1 = res.get_atoms("1", "1") + _, y2 = res.get_atoms("1", "2") + + # Test solution + s1 = [1.86872629872102, 1.395525772416039] + s2 = [3.32776806576, 2.391425905] + + assert y1[1] == approx(s1[0]) + assert y2[1] == approx(s1[1]) + + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) diff --git a/tests/unit_tests/test_transfer_volumes.py b/tests/unit_tests/test_transfer_volumes.py new file mode 100644 index 000000000..9128ed969 --- /dev/null +++ b/tests/unit_tests/test_transfer_volumes.py @@ -0,0 +1,45 @@ +"""Regression tests for openmc.deplete.Results.transfer_volumes method. + + +""" + +from pytest import approx +import openmc.deplete + +from tests import dummy_operator + + +def test_transfer_volumes(run_in_tmpdir): + """Unit test of volume transfer in restart calculations.""" + + op = dummy_operator.DummyOperator() + op.output_dir = "test_transfer_volumes" + + # Perform simulation using the predictor algorithm + dt = [0.75] + power = 1.0 + openmc.deplete.predictor(op, dt, power, print_out=False) + + # Load the files + res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + + # Create a dictionary of volumes to transfer + res[0].volume['1'] = 1.5 + res[0].volume['2'] = 2.5 + + # Create dummy geometry + mat1 = openmc.Material(material_id=1) + mat1.depletable = True + mat2 = openmc.Material(material_id=2) + + cell = openmc.Cell() + cell.fill = [mat1, mat2] + root = openmc.Universe() + root.add_cell(cell) + geometry = openmc.Geometry(root) + + # Transfer volumes + res[0].transfer_volumes(geometry) + + assert mat1.volume == 1.5 + assert mat2.volume is None From da49be46aa5efe5bab20d79cc8e2bdd984843dda Mon Sep 17 00:00:00 2001 From: guillaume Date: Sat, 2 Jun 2018 22:47:24 -0400 Subject: [PATCH 313/361] debugging rxn rates --- .../pincell_depletion/restart_depletion.py | 24 +++++++- .../python/pincell_depletion/run_depletion.py | 13 ++++- openmc/deplete/integrator/cecm.py | 2 + openmc/deplete/integrator/predictor.py | 57 +++++++++++++++++-- openmc/deplete/results.py | 3 + 5 files changed, 92 insertions(+), 7 deletions(-) diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index cfca2adf0..9ed20485c 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -72,11 +72,31 @@ results = openmc.deplete.ResultsList("depletion_results.h5") # Obtain K_eff as a function of time time, keff = results.get_eigenvalue() - + # Plot eigenvalue as a function of time plt.figure() plt.plot(time/(24*60*60), keff, label="K-effective") plt.xlabel("Time (days)") plt.ylabel("Keff") plt.show() -plt.close() + +# Obtain U235 concentration as a function of time +time, n_U235 = results.get_atoms('1', 'U235') +print(time/(24*60*60)) +print(n_U235) + +plt.figure() +plt.plot(time/(24*60*60), n_U235, label="U 235") +plt.xlabel("Time (days)") +plt.ylabel("n U5 (-)") +plt.show() + +# Obtain Xe135 absorption as a function of time +time, Xe_gam = results.get_reaction_rate('1', 'Xe135', '(n,gamma)') + +plt.figure() +plt.plot(time/(24*60*60), Xe_gam, label="Xe135 absorption") +plt.xlabel("Time (days)") +plt.ylabel("RR (-)") +plt.show() +plt.close('all') diff --git a/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py index 03e25fc0e..0c58b27d2 100644 --- a/examples/python/pincell_depletion/run_depletion.py +++ b/examples/python/pincell_depletion/run_depletion.py @@ -1,6 +1,7 @@ import openmc import openmc.deplete import numpy as np +import matplotlib.pyplot as plt ############################################################################### # Simulation Input File Parameters @@ -13,7 +14,7 @@ particles = 1000 # Depletion simulation parameters time_step = 1*24*60*60 # s -final_time = 5*24*60*60 # s +final_time = 4*24*60*60 # s time_steps = np.full(final_time // time_step, time_step) chain_file = './chain_simple.xml' @@ -146,3 +147,13 @@ time, keff = results.get_eigenvalue() # Obtain U235 concentration as a function of time time, n_U235 = results.get_atoms('1', 'U235') + +# Obtain Xe135 absorption as a function of time +time, Xe_gam = results.get_reaction_rate('1', 'Xe135', '(n,gamma)') + +plt.figure() +plt.plot(time/(24*60*60), Xe_gam, label="Xe135 absorption") +plt.xlabel("Time (days)") +plt.ylabel("RR (-)") +plt.show() +plt.close('all') diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index a80f7ee26..be274bf8f 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -76,6 +76,8 @@ def cecm(operator, timesteps, power, print_out=True): op_results = [operator.prev_res[-1]] op_results[0].rates = ratio_power[0] * op_results[0].rates[0] op_results[0].k = op_results[0].k[0] + print(x) + print(op_results[0].rates) # Deplete for first half of timestep x_middle = deplete(chain, x[0], op_results[0], dt/2, print_out) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 6918b222e..980eb0cde 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -60,6 +60,7 @@ def predictor(operator, timesteps, power, print_out=True): # Get beginning-of-timestep concentrations and reaction rates # Avoid doing first transport run if already done in previous # calculation + print("i", i, "sp i", i_res + i) if i > 0 or operator.prev_res is None: x = [copy.deepcopy(vec)] op_results = [operator(x[0], p)] @@ -67,16 +68,62 @@ def predictor(operator, timesteps, power, print_out=True): # Create results, write to disk Results.save(operator, x, op_results, [t, t + dt], p, i_res + i) else: - x = operator.prev_res[-1].data + print("Data", operator.prev_res[-1].data) + + # Get initial concentration + x = [operator.prev_res[-1].data[0]] + print(x) + x = [copy.deepcopy(vec)] + print(x) + + # Get rates, indexed by mat_to_ind in previous results + op_results = [operator.prev_res[-1]] + nuc_to_ind_current = {nuc: i for i, nuc in \ + enumerate(operator.number.burnable_nuclides)} + nuc_to_ind_res = [*operator.prev_res[-1].nuc_to_ind] + nuc_to_ind_res = {nuc: i for i, nuc in enumerate(nuc_to_ind_res)} + + print(nuc_to_ind_current) + print(nuc_to_ind_current.keys()) + print(nuc_to_ind_res) + print(operator.prev_res[-1].nuc_to_ind.keys()) + + match = [nuc_to_ind_res[nuc] for nuc in nuc_to_ind_current.keys()] + print(match) + match = [nuc_to_ind_current[nuc] for nuc in nuc_to_ind_res.keys()] + print(match) + match = [operator.prev_res[-1].nuc_to_ind[nuc] for nuc in nuc_to_ind_current.keys()] + print(match) + match = [nuc_to_ind_current[nuc] for nuc in operator.prev_res[-1].nuc_to_ind.keys()] + print(match) + match = range(9) + print(match) + + sv = op_results[0].rates[0][0] ###### + print("Index of nuclides in rr", sv.index_nuc) + print("Index of rxn in rr", sv.index_rx) + print("Index of mat in rr", sv.index_mat) + #x = [[operator.prev_res[-1].data[0][0][[nuc_to_ind_res[nuc] for nuc in nuc_to_ind_current.keys()]]]] + + print(x) + + # Scale reaction rates by ratio of powers power_res = operator.prev_res[-1].power ratio_power = p / power_res + op_results[0].rates[0] *= ratio_power[0] - op_results = [operator.prev_res[-1]] - op_results[0].rates = ratio_power[0] * op_results[0].rates[0] + + + op_results = [operator(x[0], p)] + print("old", sv) + print("new", op_results[0].rates) + + print(operator.prev_res[-1].nuc_to_ind) # Deplete for full timestep + print("x[0]", x[0]) x_end = deplete(chain, x[0], op_results[0], dt, print_out) - + print("xend", x_end) # Advance time, update vector t += dt vec = copy.deepcopy(x_end) @@ -84,6 +131,8 @@ def predictor(operator, timesteps, power, print_out=True): # Perform one last simulation x = [copy.deepcopy(vec)] op_results = [operator(x[0], power[-1])] + print("Final power", power[-1]) # Create results, write to disk + print("i" , len(timesteps), "sp i" , i_res + len(timesteps)) Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps)) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index f31f5672f..259ba6f64 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -261,6 +261,8 @@ class Results(object): comm.barrier() + print(self.nuc_to_ind) + # Grab handles number_dset = handle["/number"] rxn_dset = handle["/reaction rates"] @@ -305,6 +307,7 @@ class Results(object): inds = [self.mat_to_hdf5_ind[mat] for mat in self.mat_to_ind] low = min(inds) high = max(inds) + print("indexes", 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][:, :, :] From 9de6108ac0ab0b1ec778be008d1e59cbe671d13f Mon Sep 17 00:00:00 2001 From: guillaume Date: Sun, 3 Jun 2018 21:21:39 -0400 Subject: [PATCH 314/361] BUG fix, indexing of nuclides in reactionrates arrays was being ignored + cleaned debugging lines + added plots to examples --- .../pincell_depletion/restart_depletion.py | 19 +++---- .../python/pincell_depletion/run_depletion.py | 22 ++++++-- openmc/deplete/integrator/cecm.py | 17 ++++--- openmc/deplete/integrator/predictor.py | 51 ++----------------- openmc/deplete/operator.py | 15 +++--- openmc/deplete/reaction_rates.py | 21 ++++++-- openmc/deplete/results.py | 6 +-- tests/unit_tests/test_deplete_integrator.py | 3 ++ 8 files changed, 72 insertions(+), 82 deletions(-) diff --git a/examples/python/pincell_depletion/restart_depletion.py b/examples/python/pincell_depletion/restart_depletion.py index 9ed20485c..f9324a8cb 100644 --- a/examples/python/pincell_depletion/restart_depletion.py +++ b/examples/python/pincell_depletion/restart_depletion.py @@ -73,27 +73,28 @@ results = openmc.deplete.ResultsList("depletion_results.h5") # Obtain K_eff as a function of time time, keff = results.get_eigenvalue() -# Plot eigenvalue as a function of time +# Obtain U235 concentration as a function of time +time, n_U235 = results.get_atoms('1', 'U235') + +# Obtain Xe135 absorption as a function of time +time, Xe_gam = results.get_reaction_rate('1', 'Xe135', '(n,gamma)') + +############################################################################### +# Generate plots +############################################################################### + plt.figure() plt.plot(time/(24*60*60), keff, label="K-effective") plt.xlabel("Time (days)") plt.ylabel("Keff") plt.show() -# Obtain U235 concentration as a function of time -time, n_U235 = results.get_atoms('1', 'U235') -print(time/(24*60*60)) -print(n_U235) - plt.figure() plt.plot(time/(24*60*60), n_U235, label="U 235") plt.xlabel("Time (days)") plt.ylabel("n U5 (-)") plt.show() -# Obtain Xe135 absorption as a function of time -time, Xe_gam = results.get_reaction_rate('1', 'Xe135', '(n,gamma)') - plt.figure() plt.plot(time/(24*60*60), Xe_gam, label="Xe135 absorption") plt.xlabel("Time (days)") diff --git a/examples/python/pincell_depletion/run_depletion.py b/examples/python/pincell_depletion/run_depletion.py index 0c58b27d2..a2a5e0e7f 100644 --- a/examples/python/pincell_depletion/run_depletion.py +++ b/examples/python/pincell_depletion/run_depletion.py @@ -14,7 +14,7 @@ particles = 1000 # Depletion simulation parameters time_step = 1*24*60*60 # s -final_time = 4*24*60*60 # s +final_time = 5*24*60*60 # s time_steps = np.full(final_time // time_step, time_step) chain_file = './chain_simple.xml' @@ -136,7 +136,7 @@ op = openmc.deplete.Operator(geometry, settings_file, chain_file) openmc.deplete.integrator.predictor(op, time_steps, power) ############################################################################### -# Read depletion calculation results +# Read depletion calculation results ############################################################################### # Open results file @@ -144,13 +144,29 @@ results = openmc.deplete.ResultsList("depletion_results.h5") # Obtain K_eff as a function of time time, keff = results.get_eigenvalue() - + # Obtain U235 concentration as a function of time time, n_U235 = results.get_atoms('1', 'U235') # Obtain Xe135 absorption as a function of time time, Xe_gam = results.get_reaction_rate('1', 'Xe135', '(n,gamma)') +############################################################################### +# Generate plots +############################################################################### + +plt.figure() +plt.plot(time/(24*60*60), keff, label="K-effective") +plt.xlabel("Time (days)") +plt.ylabel("Keff") +plt.show() + +plt.figure() +plt.plot(time/(24*60*60), n_U235, label="U 235") +plt.xlabel("Time (days)") +plt.ylabel("n U5 (-)") +plt.show() + plt.figure() plt.plot(time/(24*60*60), Xe_gam, label="Xe135 absorption") plt.xlabel("Time (days)") diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index be274bf8f..0b17b49b5 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -69,15 +69,20 @@ def cecm(operator, timesteps, power, print_out=True): op_results = [operator(x[0], p)] else: + # Get initial concentration x = [operator.prev_res[-1].data[0]] + + # Get rates + op_results = [operator.prev_res[-1]] + op_results[0].rates = op_results[0].rates[0] + + # Set first stage value of keff + op_results[0].k = op_results[0].k[0] + + # Scale reaction rates by ratio of powers power_res = operator.prev_res[-1].power ratio_power = p / power_res - - op_results = [operator.prev_res[-1]] - op_results[0].rates = ratio_power[0] * op_results[0].rates[0] - op_results[0].k = op_results[0].k[0] - print(x) - print(op_results[0].rates) + op_results[0].rates[0] *= ratio_power[0] # Deplete for first half of timestep x_middle = deplete(chain, x[0], op_results[0], dt/2, print_out) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 980eb0cde..a45017813 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -38,7 +38,6 @@ def predictor(operator, timesteps, power, print_out=True): """ if not isinstance(power, Iterable): power = [power]*len(timesteps) - print(power) # Generate initial conditions with operator as vec: @@ -60,7 +59,6 @@ def predictor(operator, timesteps, power, print_out=True): # Get beginning-of-timestep concentrations and reaction rates # Avoid doing first transport run if already done in previous # calculation - print("i", i, "sp i", i_res + i) if i > 0 or operator.prev_res is None: x = [copy.deepcopy(vec)] op_results = [operator(x[0], p)] @@ -68,62 +66,21 @@ def predictor(operator, timesteps, power, print_out=True): # Create results, write to disk Results.save(operator, x, op_results, [t, t + dt], p, i_res + i) else: - print("Data", operator.prev_res[-1].data) - # Get initial concentration x = [operator.prev_res[-1].data[0]] - print(x) - x = [copy.deepcopy(vec)] - print(x) - # Get rates, indexed by mat_to_ind in previous results + # Get rates op_results = [operator.prev_res[-1]] - nuc_to_ind_current = {nuc: i for i, nuc in \ - enumerate(operator.number.burnable_nuclides)} - nuc_to_ind_res = [*operator.prev_res[-1].nuc_to_ind] - nuc_to_ind_res = {nuc: i for i, nuc in enumerate(nuc_to_ind_res)} - - print(nuc_to_ind_current) - print(nuc_to_ind_current.keys()) - print(nuc_to_ind_res) - print(operator.prev_res[-1].nuc_to_ind.keys()) - - match = [nuc_to_ind_res[nuc] for nuc in nuc_to_ind_current.keys()] - print(match) - match = [nuc_to_ind_current[nuc] for nuc in nuc_to_ind_res.keys()] - print(match) - match = [operator.prev_res[-1].nuc_to_ind[nuc] for nuc in nuc_to_ind_current.keys()] - print(match) - match = [nuc_to_ind_current[nuc] for nuc in operator.prev_res[-1].nuc_to_ind.keys()] - print(match) - match = range(9) - print(match) - - sv = op_results[0].rates[0][0] ###### - print("Index of nuclides in rr", sv.index_nuc) - print("Index of rxn in rr", sv.index_rx) - print("Index of mat in rr", sv.index_mat) - #x = [[operator.prev_res[-1].data[0][0][[nuc_to_ind_res[nuc] for nuc in nuc_to_ind_current.keys()]]]] - - print(x) + op_results[0].rates = op_results[0].rates[0] # Scale reaction rates by ratio of powers power_res = operator.prev_res[-1].power ratio_power = p / power_res op_results[0].rates[0] *= ratio_power[0] - - - op_results = [operator(x[0], p)] - print("old", sv) - print("new", op_results[0].rates) - - print(operator.prev_res[-1].nuc_to_ind) - # Deplete for full timestep - print("x[0]", x[0]) x_end = deplete(chain, x[0], op_results[0], dt, print_out) - print("xend", x_end) + # Advance time, update vector t += dt vec = copy.deepcopy(x_end) @@ -131,8 +88,6 @@ def predictor(operator, timesteps, power, print_out=True): # Perform one last simulation x = [copy.deepcopy(vec)] op_results = [operator(x[0], power[-1])] - print("Final power", power[-1]) # Create results, write to disk - print("i" , len(timesteps), "sp i" , i_res + len(timesteps)) Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps)) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 24e8843ab..a0b2417c8 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -124,8 +124,10 @@ class Operator(TransportOperator): # Determine which nuclides have incident neutron data 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] + + # Select nuclides with data that are also in the chain + self._burnable_nucs = [nuc.name for nuc in self.chain.nuclides + if nuc.name in self.nuclides_with_data] # Extract number densities from the geometry / previous depletion run self._extract_number(self.local_mats, volume, nuclides, self.prev_res) @@ -295,14 +297,13 @@ class Operator(TransportOperator): mat_id = str(mat.id) # Get nuclide lists from geometry and depletion results - depl_nuc = prev_res[-1].nuc_to_ind.keys() + depl_nuc = prev_res[-1].nuc_to_ind geom_nuc_densities = mat.get_nuclide_atom_densities() - geom_nuc = {x[0] for x in geom_nuc_densities.values()} - # Merge lists of nuclides - nuc_set = set(depl_nuc) | geom_nuc + # Merge lists of nuclides, with the same order for every calculation + geom_nuc_densities.update(depl_nuc) - for nuclide in nuc_set: + for nuclide in geom_nuc_densities.keys(): if nuclide in depl_nuc: concentration = prev_res.get_atoms(mat_id, nuclide)[1][-1] volume = prev_res[-1].volume[mat_id] diff --git a/openmc/deplete/reaction_rates.py b/openmc/deplete/reaction_rates.py index fddb88b19..cea2f1997 100644 --- a/openmc/deplete/reaction_rates.py +++ b/openmc/deplete/reaction_rates.py @@ -22,6 +22,9 @@ class ReactionRates(np.ndarray): Depletable nuclides reactions : list of str Transmutation reactions being tracked + from_results : boolean + If the reaction rates are loaded from results, indexing dictionnaries + need to be kept the same. Attributes ---------- @@ -47,16 +50,24 @@ 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, reactions): + def __new__(cls, local_mats, nuclides, reactions, from_results=False): # Create appropriately-sized zeroed-out ndarray shape = (len(local_mats), len(nuclides), len(reactions)) obj = super().__new__(cls, shape) obj[:] = 0.0 - # Add mapping attributes - obj.index_mat = {mat: i for i, mat in enumerate(local_mats)} - obj.index_nuc = {nuc: i for i, nuc in enumerate(nuclides)} - obj.index_rx = {rx: i for i, rx in enumerate(reactions)} + # Add mapping attributes, keep same indexing if from depletion_results + if from_results: + obj.index_mat = local_mats + obj.index_nuc = nuclides + obj.index_rx = reactions + # Else, assumes that reaction rates are ordered the same way as + # the lists of local_mats, nuclides and reactions (or keys if these + # are dictionnaries) + else: + obj.index_mat = {mat: i for i, mat in enumerate(local_mats)} + obj.index_nuc = {nuc: i for i, nuc in enumerate(nuclides)} + obj.index_rx = {rx: i for i, rx in enumerate(reactions)} return obj diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 259ba6f64..6170a4464 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -165,6 +165,7 @@ class Results(object): else: kwargs = {} + # Write new file if first time step, else add to existing file kwargs['mode'] = "w" if step == 0 else "a" with h5py.File(filename, **kwargs) as handle: @@ -261,8 +262,6 @@ class Results(object): comm.barrier() - print(self.nuc_to_ind) - # Grab handles number_dset = handle["/number"] rxn_dset = handle["/reaction rates"] @@ -307,7 +306,6 @@ class Results(object): inds = [self.mat_to_hdf5_ind[mat] for mat in self.mat_to_ind] low = min(inds) high = max(inds) - print("indexes", 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][:, :, :] @@ -369,7 +367,7 @@ class Results(object): results.rates = [] # Reconstruct reactions for i in range(results.n_stages): - rate = ReactionRates(results.mat_to_ind, rxn_nuc_to_ind, rxn_to_ind) + rate = ReactionRates(results.mat_to_ind, rxn_nuc_to_ind, rxn_to_ind, True) rate[:] = handle["/reaction rates"][step, i, :, :, :] results.rates.append(rate) diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index c19f1c5f6..8cade45b1 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -25,6 +25,9 @@ def test_results_save(run_in_tmpdir): # Mock geometry op = MagicMock() + # Avoid DummyOperator thinking it's doing a restart calculation + op.prev_res = None + vol_dict = {} full_burn_list = [] From 79b473fa5b3674683277eff8df7a9e582177bfcf Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 4 Jun 2018 19:00:07 -0400 Subject: [PATCH 315/361] Initial implementation of MGXS C++ code --- src/constants.h | 60 +++ src/hdf5_interface.cpp | 146 ++++++++ src/hdf5_interface.h | 29 ++ src/mgxs.h | 95 +++++ src/scattdata.cpp | 831 +++++++++++++++++++++++++++++++++++++++++ src/scattdata.h | 106 ++++++ src/string_functions.h | 54 +++ src/xsdata.cpp | 782 ++++++++++++++++++++++++++++++++++++++ src/xsdata.h | 72 ++++ 9 files changed, 2175 insertions(+) create mode 100644 src/constants.h create mode 100644 src/mgxs.h create mode 100644 src/scattdata.cpp create mode 100644 src/scattdata.h create mode 100644 src/string_functions.h create mode 100644 src/xsdata.cpp create mode 100644 src/xsdata.h diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 000000000..b14984a14 --- /dev/null +++ b/src/constants.h @@ -0,0 +1,60 @@ + +#ifndef CONSTANTS_H +#define CONSTANTS_H + +#include +#include + +namespace openmc { + +typedef std::array dir_arr; +typedef std::vector double_1dvec; +typedef std::vector > double_2dvec; +typedef std::vector > > double_3dvec; +typedef std::vector > > > double_4dvec; +typedef std::vector > > > > double_5dvec; +typedef std::vector > > > > > double_6dvec; +typedef std::vector int_1dvec; +typedef std::vector > int_2dvec; +typedef std::vector > > int_3dvec; + +int constexpr MAX_SAMPLE {10000}; + +constexpr std::array VERSION {0, 10, 0}; +constexpr std::array VERSION_PARTICLE_RESTART {2, 0}; + +// Maximum number of words in a single line, length of line, and length of +// single word +constexpr int MAX_WORDS {500}; +constexpr int MAX_LINE_LEN {250}; +constexpr int MAX_WORD_LEN {150}; +constexpr int MAX_FILE_LEN {255}; + +// Physical Constants +constexpr double K_BOLTZMANN {8.6173303e-5}; // Boltzmann constant in eV/K + +// Angular distribution type +constexpr int ANGLE_ISOTROPIC {1}; +constexpr int ANGLE_32_EQUI {2}; +constexpr int ANGLE_TABULAR {3}; +constexpr int ANGLE_LEGENDRE {4}; +constexpr int ANGLE_HISTOGRAM {5}; + +// MGXS Table Types +constexpr int MGXS_ISOTROPIC {1}; // Isotroically weighted data +constexpr int MGXS_ANGLE {2}; // Data by angular bins + +// Flag to denote this was a macroscopic data object +constexpr double MACROSCOPIC_AWR {-2.}; + +// Number of mu bins to use when converting Legendres to tabular type +constexpr int DEFAULT_NMU {33}; + +// Temperature treatment method +constexpr int TEMPERATURE_NEAREST {1}; +constexpr int TEMPERATURE_INTERPOLATION {2}; + + +} // namespace openmc + +#endif // CONSTANTS_H \ No newline at end of file diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 8a8391bb5..3d2f3ee7b 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -447,6 +447,152 @@ read_complex(hid_t obj_id, const char* name, double _Complex* buffer, bool indep } +void +read_nd_vector(hid_t obj_id, const char* name, std::vector& result, + bool must_have) +{ + if (object_exists(obj_id, name)) { + read_double(obj_id, name, &result[0], true); + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector >& result, bool must_have) +{ + if (object_exists(obj_id, name)) { + int dim1 = result.size(); + int dim2 = result[0].size(); + std::vector temp_arr = std::vector(dim1 * dim2); + read_double(obj_id, name, &temp_arr[0], true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + result[i][j] = temp_arr[temp_idx++]; + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > >& result, + bool must_have) +{ + if (object_exists(obj_id, name)) { + dim1 = result.size(); + dim2 = result[0].size(); + dim3 = result[0][0].size(); + std::vector temp_arr = std::vector(dim1 * dim2 * dim3); + read_double(obj_id, name, &temp_arr[0], true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + for (int k = 0; k < dim3; k++) { + result[i][j][k] = temp_arr[temp_idx++]; + } + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > >& result, + bool must_have) +{ + if (object_exists(obj_id, name)) { + dim1 = result.size(); + dim2 = result[0].size(); + dim3 = result[0][0].size(); + std::vector temp_arr = std::vector(dim1 * dim2 * dim3); + read_int(obj_id, name, &temp_arr[0], true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + for (int k = 0; k < dim3; k++) { + result[i][j][k] = temp_arr[temp_idx++]; + } + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > > >& result, + bool must_have) +{ + if (object_exists(obj_id, name)) { + dim1 = result.size(); + dim2 = result[0].size(); + dim3 = result[0][0].size(); + dim4 = result[0][0][0].size(); + std::vector temp_arr = std::vector( + dim1 * dim2 * dim3 * dim4); + read_double(obj_id, name, &temp_arr[0], true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + for (int k = 0; k < dim3; k++) { + for (int l = 0; l < dim4; l++) { + result[i][j][k][l] = temp_arr[temp_idx++]; + } + } + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > > > >& result, + bool must_have) +{ + if (object_exists(obj_id, name)) { + dim1 = result.size(); + dim2 = result[0].size(); + dim3 = result[0][0].size(); + dim4 = result[0][0][0].size(); + dim5 = result[0][0][0][0].size(); + std::vector temp_arr = std::vector( + dim1 * dim2 * dim3 * dim4 * dim5); + read_double(obj_id, name, &temp_arr[0], true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + for (int k = 0; k < dim3; k++) { + for (int l = 0; l < dim4; l++) { + for (int m = 0; m < dim5; m++) { + result[i][j][k][l][m] = temp_arr[temp_idx++]; + } + } + } + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + + void read_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, double* results) { diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index e38a31e99..734cf0f91 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -55,6 +55,35 @@ extern "C" void read_string(hid_t obj_id, const char* name, size_t slen, extern "C" void read_complex(hid_t obj_id, const char* name, double _Complex* buffer, bool indep); +void +read_nd_vector(hid_t obj_id, const char* name, std::vector& result, + bool must_have = false); + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector >& result, + bool must_have = false); + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > >& result, + bool must_have = false); + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > >& result, + bool must_have = false); + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > > >& result, + bool must_have = false); + +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector > > > >& result, + bool must_have = false); + extern "C" void read_tally_results(hid_t group_id, hsize_t n_filter, hsize_t n_score, double* results); diff --git a/src/mgxs.h b/src/mgxs.h new file mode 100644 index 000000000..8ed4ba99e --- /dev/null +++ b/src/mgxs.h @@ -0,0 +1,95 @@ +//! \file mgxs.h +//! A collection of classes for Multi-Group Cross Section data + +#ifndef MGXS_H +#define MGXS_H + +#include +#include +#include +#include +#include +#include + +#include "constants.h" +#include "hdf5_interface.h" +#include "math_functions.h" +#include "random_lcg.h" +#include "scattdata.h" +#include "string_functions.h" +#include "xsdata.h" + + +namespace openmc { + + +//============================================================================== +// MGXS contains the mgxs data for a nuclide/material +//============================================================================== + +class Mgxs { + private: + std::string name; // name of dataset, e.g., UO2 + double awr; // atomic weight ratio + double_1dvec kTs; // temperature in eV (k * T) + int scatter_format; // flag for if this is legendre, histogram, or tabular + int num_delayed_groups; // number of delayed neutron groups + int num_groups; // number of energy groups + int index_temp; // cache of temperature index + double last_sqrtkT; // cache of the temperature corresponding to index_temp + std::vector xs; // Cross section data + int n_pol; + int n_azi; + int index_pol; // cache fof the angle indices + int index_azi; + double_1dvec polar; + double_1dvec azimuthal; + dir_arr last_uvw; + void _metadata_from_hdf5(hid_t xs_id, int in_num_groups, + int in_num_delayed_groups, double_1dvec temperature, int& method, + double tolerance, double_1dvec& temps_to_read, int& order_dim); + + public: + bool fissionable; // Is this fissionable + void init(const std::string& in_name, double in_awr, double_1dvec& in_kTs, + bool in_fissionable, int in_scatter_format, int in_num_groups, + int in_num_delayed_groups, double_1dvec& in_polar, + double_1dvec& in_azimuthal); + void build_macro(const std::string& in_name, double_1dvec& mat_kTs, + std::vector& micros, double_1dvec& atom_densities, + int& method, double tolerance); + void combine(std::vector& micros, double_1dvec& scalars, + int_1dvec& micro_ts, int this_t); + void from_hdf5(hid_t xs_id, int energy_groups, int delayed_groups, + double_1dvec temperature, int& method, + double tolerance, int max_order, + bool legendre_to_tabular, + int legendre_to_tabular_points); + double get_xs(const char* xstype, int gin, int* gout, double* mu, + int* dg); + void sample_fission_energy(int gin, double nu_fission, int& dg, int& gout); + void sample_scatter(dir_arr& uvw, int gin, int& gout, double& mu, + double& wgt); + void calculate_xs(int gin, double sqrtkT, dir_arr& uvw, double& total_xs, + double& abs_xs, double& nu_fiss_xs); + bool equiv(const Mgxs& that); + inline void set_temperature_index(double sqrtkT); + inline void set_angle_index(dir_arr& uvw); +}; + +extern "C" void read_mgxs_library(hid_t file_id, int n_nuclides, char** names, + int energy_groups, int delayed_groups, int n_temps, double temps[], + int& method, double tolerance, int max_order, bool legendre_to_tabular, + int legendre_to_tabular_points); +extern "C" bool query_fissionable(const int i_nuclides[], const int n_nuclides); +void create_macro_xs(int n_materials, double_2dvec& mat_kTs, + std::vector& mat_names, double_1dvec& atom_densities, + int& method, double tolerance); + + +// Storage for the MGXS data +std::vector nuclides_MG; +std::vector macro_xs; + +} // namespace openmc +#endif // MGXS_H \ No newline at end of file diff --git a/src/scattdata.cpp b/src/scattdata.cpp new file mode 100644 index 000000000..8283febc8 --- /dev/null +++ b/src/scattdata.cpp @@ -0,0 +1,831 @@ +#include "scattdata.h" + +namespace openmc { + +//============================================================================== +// Methods for use by all extended types +//============================================================================== + + + +//============================================================================== +// ScattData base-class methods +//============================================================================== + +void ScattData::generic_init(int order, int_1dvec in_gmin, + int_1dvec in_gmax, double_2dvec in_energy, double_2dvec in_mult) +{ + int groups = in_energy.size(); + + gmin = in_gmin; + gmax = in_gmax; + energy.resize(groups); + mult.resize(groups); + dist.resize(groups); + + for (int gin = 0; gin < groups; gin++) { + // Make sure the energy is normalized + double norm = std::accumulate(in_energy[gin].begin(), + in_energy[gin].end(), 0.); + + if (norm != 0.) { + for (auto& n : in_energy[gin]) n /= norm; + } + + // Store the inputted data + energy[gin] = in_energy[gin]; + mult[gin] = in_mult[gin]; + + // Initialize the distribution data + dist[gin].resize(in_gmax[gin] - in_gmin[gin] + 1); + for (auto& v : dist[gin]) { + v.resize(order); + for (auto& n : v) n = 0.; + } + } +} + + +void ScattData::sample_energy(int gin, int& gout, int& i_gout) +{ + // Sample the outgoing group + double xi = prn(); + i_gout = 0; //TODO: + 1? + gout = gmin[gin]; + double prob = energy[gin][i_gout]; + while((prob < xi) && (gout < gmax[gin])) { + gout++; + i_gout++; + prob += energy[gin][i_gout]; + } +} + + +double ScattData::get_xs(const char* xstype, int gin, int* gout, double* mu) +{ + // Set the outgoing group offset index as needed + int i_gout = 0; + if (gout != nullptr) { + // short circuit the function if gout is from a zero portion of the + // scattering matrix + if ((*gout < gmin[gin]) || (*gout >= gmax[gin])) { // > gmax? + return 0.; + } + i_gout = *gout - gmin[gin]; + } + + double val = 0.; + if (std::strcmp(xstype, "scatter")) { + if (gout != nullptr) { + val = scattxs[gin] * energy[gin][i_gout]; + } else { + val = scattxs[gin]; + } + } else if (std::strcmp(xstype, "scatter/mult")) { + if (gout != nullptr) { + val = scattxs[gin] * energy[gin][i_gout] / mult[gin][i_gout]; + } else { + val = scattxs[gin] / std::inner_product(mult[gin].begin(), + mult[gin].end(), + energy[gin].begin(), 0.0); + } + } else if (std::strcmp(xstype, "scatter*f_mu/mult")) { + if ((gout != nullptr) && (mu != nullptr)) { + val = scattxs[gin] * energy[gin][i_gout] * calc_f(gin, *gout, *mu); + } else { + // This is not an expected path (asking for f_mu without asking for a + // group or mu is not useful + fatal_error("Invalid call to get_xs"); + } + } else if (std::strcmp(xstype, "scatter*f_mu")) { + if ((gout != nullptr) && (mu != nullptr)) { + val = scattxs[gin] * energy[gin][i_gout] * calc_f(gin, *gout, *mu) / + mult[gin][i_gout]; + } else { + // This is not an expected path (asking for f_mu without asking for a + // group or mu is not useful + fatal_error("Invalid call to get_xs"); + } + } + return val; +} + + +//============================================================================== +// ScattDataLegendre methods +//============================================================================== + +void ScattDataLegendre::init(int_1dvec in_gmin, int_1dvec in_gmax, + double_2dvec in_mult, double_3dvec coeffs) +{ + int groups = coeffs.size(); + int order = coeffs[0].size(); + + // make a copy of coeffs that we can use to both extract data and normalize + double_3dvec matrix = coeffs; + + // Get the scattering cross section value by summing the un-normalized P0 + // coefficient in the variable matrix over all outgoing groups. + scattxs.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + scattxs[gin] = 0.; + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + scattxs[gin] = std::accumulate(matrix[gin][i_gout].begin(), + matrix[gin][i_gout].end(), + scattxs[gin]); + } + } + + // Build the energy transfer matrix from data in the variable matrix while + // also normalizing the variable matrix itself + // (forcing the CDF of f(mu=1) == 1) + double_2dvec in_energy; + in_energy.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + in_energy[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + double norm = matrix[gin][i_gout][0]; + in_energy[gin][i_gout] = norm; + if (norm != 0.) { + for (auto& n : matrix[gin][i_gout]) n /= norm; + } + } + } + + // Initialize the base class attributes + ScattData::generic_init(order, in_gmin, in_gmax, in_energy, in_mult); + + // Set the distribution (sdata.dist) values and initialize max_val + max_val.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + dist[gin][i_gout] = matrix[gin][i_gout]; + } + max_val[gin].resize(num_groups); + for (auto& n : max_val[gin]) n = 0.; + } + + // Now update the maximum value + update_max_val(); +} + + +void ScattDataLegendre::update_max_val() +{ + int groups = max_val.size(); + // Step through the polynomial with fixed number of points to identify the + // maximal value + int Nmu = 1001; + double dmu = 2. / (Nmu - 1); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + for (int imu = 0; imu < Nmu; imu++) { + double mu; + if (imu == 0) { + mu = -1.; + } else if (imu == (Nmu - 1)) { + mu = 1.; + } else { + mu = -1. + (imu - 1) * dmu; + } + + // Calculate probability + double f = evaluate_legendre_c(dist[gin][i_gout].size() - 1, + dist[gin][i_gout].data(), mu); + + // if this is a new maximum, store it + if (f > max_val[gin][i_gout]) max_val[gin][i_gout] = f; + } // end imu loop + + // Since we may not have caught the true max, add 10% margin + max_val[gin][i_gout] *= 1.1; + } + } +} + + +double ScattDataLegendre::calc_f(int gin, int gout, double mu) +{ + // TODO: gout >= or gout >? + double f; + if ((gout < gmin[gin]) || (gout >= gmax[gin])) { + f = 0.; + } else { + // TODO: size() -1 or just size? + int i_gout = gout - gmin[gin]; //TODO: + 1? + f = evaluate_legendre_c(dist[gin][i_gout].size() - 1, + dist[gin][i_gout].data(), mu); + } + return f; +} + + +void ScattDataLegendre::sample(int gin, int& gout, double& mu, double& wgt) +{ + // Sample the outgoing energy using the base-class method + int i_gout; + sample_energy(gin, gout, i_gout); + + // Now we can sample mu using the scattering kernel using rejection + // sampling from a rectangular bounding box + double M = max_val[gin][i_gout]; + int samples = 0; + + while(true) { + double mu = 2. * prn() - 1.; + double f = calc_f(gin, gout, mu); + if (f > 0.) { + double u = prn() * M; + if (u <= f) break; + } + samples++; + if (samples > MAX_SAMPLE) { + fatal_error("Maximum number of Legendre expansion samples reached"); + } + }; + + // Update the weight to reflect neutron multiplicity + wgt *= mult[gin][i_gout]; +} + + +void ScattDataLegendre::combine(std::vector those_scatts, + double_1dvec& scalars) +{ + int groups = energy.size(); + // Find the maximum order in the data set + int max_order = get_order(); + for (int i = 0; i < those_scatts.size(); i++) { + // Lets also make sure these items are combineable + ScattDataLegendre* that = dynamic_cast(those_scatts[i]); + if (!equiv(*that)) { + fatal_error("Cannot combine the ScattData objects!"); + } + int that_order = that->get_order(); + if (that_order > max_order) max_order = that_order; + } + max_order++; // Add one since this is a Legendre + + // Now allocate and zero our storage spaces + double_3dvec this_matrix = get_matrix(max_order); + double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); + double_2dvec mult_denom(groups, double_1dvec(groups, 0.)); + + // Build the dense scattering and multiplicity matrices + // Get the multiplicity_matrix + // To combine from nuclidic data we need to use the final relationship + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / + // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) + // Developed as follows: + // mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / + // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) + // nuscatt_{i,g,g'} can be reconstructed from the energy and scattxs member + // variables + for (int i = 0; i < those_scatts.size(); i++) { + ScattDataLegendre* that = dynamic_cast(those_scatts[i]); + + // Build the dense matrix for that object + double_3dvec that_matrix = that->get_matrix(max_order); + + // Now add that to this for the scattering and multiplicity + for (int gin = 0; gin < groups; gin++) { + // Only spend time adding that's gmin to gmax data since the rest will + // be zeros + for (int gout = that->gmin[gin]; gout <= that->gmax[gin]; gout++) { + // Do the scattering matrix + for (int l = 0; l < max_order; l++) { + this_matrix[gin][gout][l] += scalars[i] * that_matrix[gin][gout][l]; + } + + // Incorporate that's contribution to the multiplicity matrix data + double nuscatt = that->scattxs[gin] * that->energy[gin][gout]; + mult_numer[gin][gout] += scalars[i] * nuscatt; + if (that->mult[gin][gout] > 0.) { + mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][gout]; + } else { + mult_denom[gin][gout] += scalars[i]; + } + } + } + } + + // Combine mult_numer and mult_denom into the combined multiplicity matrix + double_2dvec this_mult(groups, double_1dvec(groups, 1.)); + for (int gin = 0; gin < groups; gin++) { + for (int gout = 0; gout < groups; gout++) { + if (mult_denom[gin][gout] > 0.) { + this_mult[gin][gout] = mult_numer[gin][gout] / mult_denom[gin][gout]; + } + } + } + mult_numer.clear(); + mult_denom.clear(); + + // We have the data, now we need to convert to a jagged array and then use + // the initialize function to store it on the object. + int_1dvec in_gmin(groups); + int_1dvec in_gmax(groups); + double_3dvec sparse_scatter(groups); + double_2dvec sparse_mult(groups); + for (int gin = 0; gin < groups; gin++) { + // Find the minimum and maximum group boundaries + int gmin_; + for (gmin_ = 0; gmin_ < groups; gmin_++) { + bool non_zero = std::all_of(this_matrix[gin][gmin_].begin(), + this_matrix[gin][gmin_].end(), + [](double val){return val > 0.;}); + if (non_zero) break; + } + int gmax_; + for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { + bool non_zero = std::all_of(this_matrix[gin][gmax_].begin(), + this_matrix[gin][gmax_].end(), + [](double val){return val > 0.;}); + if (non_zero) break; + } + + // treat the case of all values being 0 + if (gmin_ > gmax_) { + gmin_ = gin; + gmax_ = gin; + } + + // Store the group bounds + in_gmin[gin] = gmin_; + in_gmax[gin] = gmax_; + + // Store the data in the compressed format + sparse_scatter[gin].resize(gmax_ - gmin_ + 1); + sparse_mult[gin].resize(gmax_ - gmin_ + 1); + int i_gout = 0; + for (int gout = gmin_; gout <= gmax_; gout++) { + sparse_scatter[gin][i_gout] = this_matrix[gin][gout]; + sparse_mult[gin][i_gout] = this_mult[gin][gout]; + } + } + + // Got everything we need, store it. + init(in_gmin, in_gmax, sparse_mult, sparse_scatter); +} + + +bool ScattDataLegendre::equiv(const ScattDataLegendre& that) +{ + // ensure that the number of groups match + return (this->energy.size() == that.energy.size()); +} + + +double_3dvec ScattDataLegendre::get_matrix(int max_order) +{ + // Get the sizes and initialize the data to 0 + int groups = energy.size(); + int order_dim = max_order + 1; + double_3dvec matrix = double_3dvec(groups, double_2dvec(order_dim, + double_1dvec(order_dim, 0.))); + + for (int gin = 0; gin < groups; gin++) { + for (int i_gout = 0; i_gout < energy[0].size(); i_gout++) { + int gout = i_gout + gmin[gin]; + for (int l = 0; l < order_dim; l++) { + matrix[gin][gout][l] = scattxs[gin] * energy[gin][i_gout] * + dist[gin][i_gout][l]; + } + } + } + return matrix; +} + +//============================================================================== +// ScattDataHistogram methods +//============================================================================== + +void ScattDataHistogram::init(int_1dvec in_gmin, int_1dvec in_gmax, + double_2dvec in_mult, double_3dvec coeffs) +{ + int groups = coeffs.size(); + int order = coeffs[0].size(); + + // make a copy of coeffs that we can use to both extract data and normalize + double_3dvec matrix = coeffs; + + // Get the scattering cross section value by summing the distribution + // over all the histogram bins in angle and outgoing energy groups + scattxs.resize(groups); + for (int gin = 0; gin < groups; gin++) { + scattxs[gin] = 0.; + for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) { + scattxs[gin] += std::accumulate(matrix[gin][i_gout].begin(), + matrix[gin][i_gout].end(), 0.); + } + } + + // Build the energy transfer matrix from data in the variable matrix + double_2dvec in_energy; + in_energy.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + in_energy[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + double norm = std::accumulate(matrix[gin][i_gout].begin(), + matrix[gin][i_gout].end(), 0.); + if (norm != 0.) { + for (auto& n : matrix[gin][i_gout]) n /= norm; + } + } + } + + // Initialize the base class attributes + ScattData::generic_init(order, in_gmin, in_gmax, in_energy, + in_mult); + + // Build the angular distributio mu values + mu = double_1dvec(order); + dmu = 2. / order; + mu[0] = -1.; + for (int imu = 1; imu < order; imu++) { + mu[imu] = -1. + (imu - 1) * dmu; + } + + // Calculate f(mu) and integrate it so we can avoid rejection sampling + fmu.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + fmu[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout) { + fmu[gin][i_gout].resize(order); + // The variable matrix contains f(mu); so directly assign it + fmu[gin][i_gout] = matrix[gin][i_gout]; + + // Integrate the histogram + dist[gin][i_gout][0] = dmu * matrix[gin][i_gout][0]; + for (int imu = 1; imu < order; imu++) { + dist[gin][i_gout][imu] = dmu * matrix[gin][i_gout][imu] + + dist[gin][i_gout][imu - 1]; + } + + // Now re-normalize for integral to unity + double norm = dist[gin][i_gout][order - 1]; + if (norm > 0.) { + for (int imu = 0; imu < order; imu++) { + fmu[gin][i_gout][imu] /= norm; + dist[gin][i_gout][imu] /= norm; + } + } + } + } +} + + +double ScattDataHistogram::calc_f(int gin, int gout, double mu) +{ + // TODO: gout >= or gout >? + double f; + if ((gout < gmin[gin]) || (gout >= gmax[gin])) { + f = 0.; + } else { + // Find mu bin + int i_gout = gout - gmin[gin]; //TODO: + 1? + int imu; + if (mu == 1.) { + // use size -2 to have the index one before the end + imu = this->mu.size() - 2; + } else { + imu = std::floor((mu + 1.) / dmu + 1.); + } + + f = fmu[gin][i_gout][imu]; + } + return f; +} + + +void ScattDataHistogram::sample(int gin, int& gout, double& mu, double& wgt) +{ + // Sample the outgoing energy using the base-class method + int i_gout; + sample_energy(gin, gout, i_gout); + + // Determine the outgoing cosine bin + double xi = prn(); + + int imu; + if (xi < dist[gin][i_gout][0]) { + imu = 1; + } else { + // TODO lower_bound? + 1? + imu = std::upper_bound(dist[gin][i_gout].begin(), + dist[gin][i_gout].end(), xi) - + dist[gin][i_gout].begin(); + } + + // Randomly select mu within the imu bin + mu = prn() * dmu + this->mu[imu]; + + if (mu < -1.) { + mu = -1.; + } else if (mu > 1.) { + mu = 1.; + } + + // Update the weight to reflect neutron multiplicity + wgt *= mult[gin][i_gout]; +} + + +bool ScattDataHistogram::equiv(const ScattDataHistogram& that) +{ + bool match = false; + if (this->energy.size() == that.energy.size() && + this->dmu == that.dmu && + std::equal(this->mu.begin(), this->mu.end(), that.mu.begin())) { + match = true; + } + return match; +} + + +double_3dvec ScattDataHistogram::get_matrix(int max_order) +{ + // Get the sizes and initialize the data to 0 + int groups = energy.size(); + // We ignore the requested order for Histogram and Tabular representations + int order_dim = get_order(); + double_3dvec matrix = double_3dvec(groups, double_2dvec(order_dim, + double_1dvec(order_dim, 0.))); + + for (int gin = 0; gin < groups; gin++) { + for (int i_gout = 0; i_gout < energy[0].size(); i_gout++) { + int gout = i_gout + gmin[gin]; + for (int l = 0; l < order_dim; l++) { + matrix[gin][gout][l] = scattxs[gin] * energy[gin][i_gout] * + fmu[gin][i_gout][l]; + } + } + } + return matrix; +} + +//============================================================================== +// ScattDataTabular methods +//============================================================================== + +void ScattDataTabular::init(int_1dvec in_gmin, int_1dvec in_gmax, + double_2dvec in_mult, double_3dvec coeffs) +{ + int groups = coeffs.size(); + int order = coeffs[0].size(); + + // make a copy of coeffs that we can use to both extract data and normalize + double_3dvec matrix = coeffs; + + // Build the angular distribution mu values + mu = double_1dvec(order); + dmu = 2. / (order - 1); + mu[0] = -1.; + for (int imu = 1; imu < order - 1; imu++) { + mu[imu] = -1. + (imu - 1) * dmu; + } + mu[order - 1] = 1.; + + // Get the scattering cross section value by integrating the distribution + // over all mu points and then combining over all outgoing groups + scattxs.resize(groups); + for (int gin = 0; gin < groups; gin++) { + scattxs[gin] = 0.; + for (int i_gout = 0; i_gout < matrix[gin].size(); i_gout++) { + for (int imu = 1; imu < order; imu++) { + scattxs[gin] += 0.5 * dmu * (matrix[gin][i_gout][imu - 1] + + matrix[gin][i_gout][imu]); + } + } + } + + // Build the energy transfer matrix from data in the variable matrix + double_2dvec in_energy; + in_energy.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + in_energy[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout++) { + double norm = 0.; + for (int imu = 1; imu < order; imu++) { + norm += 0.5 * dmu * (matrix[gin][i_gout][imu - 1] + + matrix[gin][i_gout][imu]); + } + if (norm != 0.) { + for (auto& n : matrix[gin][i_gout]) n /= norm; + } + } + } + + // Initialize the base class attributes + ScattData::generic_init(order, in_gmin, in_gmax, in_energy, + in_mult); + + // Calculate f(mu) and integrate it so we can avoid rejection sampling + fmu.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = gmax[gin] - gmin[gin] + 1; + fmu[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout) { + fmu[gin][i_gout].resize(order); + // The variable matrix contains f(mu); so directly assign it + fmu[gin][i_gout] = matrix[gin][i_gout]; + + // Ensure positivity + for (auto& val : fmu[gin][i_gout]) { + if (val < 0.) val = 0.; + } + + // Now re-normalize for numerical integration issues and to take care of + // the above negative fix-up. Also accrue the CDF + double norm = 0.; + for (int imu = 1; imu < order; imu++) { + norm += 0.5 * dmu * (fmu[gin][i_gout][imu - 1] + + fmu[gin][i_gout][imu]); + // incorporate to the CDF + dist[gin][i_gout][imu] = norm; + } + + // now do the normalization + if (norm > 0.) { + for (int imu = 0; imu < order; imu++) { + fmu[gin][i_gout][imu] /= norm; + dist[gin][i_gout][imu] /= norm; + } + } + } + } +} + + +double ScattDataTabular::calc_f(int gin, int gout, double mu) +{ + // TODO: gout >= or gout >? + double f; + if ((gout < gmin[gin]) || (gout >= gmax[gin])) { + f = 0.; + } else { + // Find mu bin + int i_gout = gout - gmin[gin]; //TODO: + 1? + int imu; + if (mu == 1.) { + // use size -2 to have the index one before the end + imu = this->mu.size() - 2; + } else { + imu = std::floor((mu + 1.) / dmu + 1.); + } + + double r = (mu - this->mu[imu]) / (this->mu[imu + 1] - this->mu[imu]); + f = (1. - r) * fmu[gin][i_gout][imu] + r * fmu[gin][i_gout][imu + 1]; + } + return f; +} + + +void ScattDataTabular::sample(int gin, int& gout, double& mu, double& wgt) +{ + // Sample the outgoing energy using the base-class method + int i_gout; + sample_energy(gin, gout, i_gout); + + // Determine the outgoing cosine bin + int NP = this->mu.size(); + double xi = prn(); + + double c_k = dist[gin][i_gout][0]; + int k; + for (k = 0; k < NP - 2; k++) { + double c_k1 = dist[gin][i_gout][k + 1]; + if (xi < c_k1) break; + c_k = c_k1; + } + + // Check to make sure k is <= NP - 1 + k = std::min(k, NP - 1); + + // Find the pdf values we want + double p0 = fmu[gin][i_gout][k]; + double mu0 = this -> mu[k]; + double p1 = fmu[gin][i_gout][k + 1]; + double mu1 = this -> mu[k + 1]; + + if (p0 == p1) { + mu = mu0 + (xi - c_k) / p0; + } else { + double frac = (p1 - p0) / (mu1 - mu0); + mu = mu0 + (std::sqrt(std::max(0., p0 * p0 + 2. * frac * (xi - c_k))) + - p0) / frac; + } + + if (mu < -1.) { + mu = -1.; + } else if (mu > 1.) { + mu = 1.; + } + + // Update the weight to reflect neutron multiplicity + wgt *= mult[gin][i_gout]; +} + + +bool ScattDataTabular::equiv(const ScattDataTabular& that) +{ + bool match = false; + if (this->energy.size() == that.energy.size() && + this->dmu == that.dmu && + std::equal(this->mu.begin(), this->mu.end(), that.mu.begin())) { + match = true; + } + return match; +} + + +double_3dvec ScattDataTabular::get_matrix(int max_order) +{ + // Get the sizes and initialize the data to 0 + int groups = energy.size(); + // We ignore the requested order for Histogram and Tabular representations + int order_dim = get_order(); + double_3dvec matrix = double_3dvec(groups, double_2dvec(order_dim, + double_1dvec(order_dim, 0.))); + + for (int gin = 0; gin < groups; gin++) { + for (int i_gout = 0; i_gout < energy[0].size(); i_gout++) { + int gout = i_gout + gmin[gin]; + for (int l = 0; l < order_dim; l++) { + matrix[gin][gout][l] = scattxs[gin] * energy[gin][i_gout] * + fmu[gin][i_gout][l]; + } + } + } + return matrix; +} + + +void convert_legendre_to_tabular(ScattDataLegendre& leg, + ScattDataTabular& tab, int n_mu) +{ + // Copy the obvious data + tab.energy = leg.energy; + tab.mult = leg.mult; + tab.gmin = leg.gmin; + tab.gmax = leg.gmax; + + // Build mu and dmu + tab.mu = double_1dvec(n_mu); + tab.dmu = 2. / (n_mu - 1); + tab.mu[0] = -1.; + for (int imu = 1; imu < n_mu - 1; imu++) { + tab.mu[imu] = -1. + (imu - 1) * tab.dmu; + } + tab.mu[n_mu - 1] = 1.; + + // Calculate f(mu) and integrate it so we can avoid rejection sampling + int groups = tab.energy.size(); + tab.fmu.resize(groups); + for (int gin = 0; gin < groups; gin++) { + int num_groups = tab.gmax[gin] - tab.gmin[gin] + 1; + tab.fmu[gin].resize(num_groups); + for (int i_gout = 0; i_gout < num_groups; i_gout) { + tab.fmu[gin][i_gout].resize(n_mu); + for (int imu = 0; imu < n_mu; imu++) { + tab.fmu[gin][i_gout][imu] = + evaluate_legendre_c(leg.dist[gin][i_gout].size() - 1, + leg.dist[gin][i_gout].data(), tab.mu[imu]); + } + + // Ensure positivity + for (auto& val : tab.fmu[gin][i_gout]) { + if (val < 0.) val = 0.; + } + + // Now re-normalize for numerical integration issues and to take care of + // the above negative fix-up. Also accrue the CDF + double norm = 0.; + for (int imu = 1; imu < n_mu; imu++) { + norm += 0.5 * tab.dmu * (tab.fmu[gin][i_gout][imu - 1] + + tab.fmu[gin][i_gout][imu]); + // incorporate to the CDF + tab.dist[gin][i_gout][imu] = norm; + } + + // now do the normalization + if (norm > 0.) { + for (int imu = 0; imu < n_mu; imu++) { + tab.fmu[gin][i_gout][imu] /= norm; + tab.dist[gin][i_gout][imu] /= norm; + } + } + } + } +} + +} // namespace openmc diff --git a/src/scattdata.h b/src/scattdata.h new file mode 100644 index 000000000..787a8f005 --- /dev/null +++ b/src/scattdata.h @@ -0,0 +1,106 @@ +//! \file scattdata.h +//! A collection of multi-group scattering data classes + +#ifndef SCATTDATA_H +#define SCATTDATA_H + +#include +#include +#include +#include + +#include "constants.h" +#include "math_functions.h" +#include "random_lcg.h" +#include "error.h" + +namespace openmc { + +//============================================================================== +// SCATTDATA contains all the data needed to describe the scattering energy and +// angular distribution data +//============================================================================== +// temporary declaations so we can name our friend functions +class ScattDataLegendre; +class ScattDataTabular; + +class ScattData { + protected: + double_2dvec energy; // Normalized p0 matrix for sampling Eout + double_2dvec mult; // nu-scatter multiplication (nu-scatt/scatt) + double_3dvec dist; // Angular distribution + int_1dvec gmin; // minimum outgoing group + int_1dvec gmax; // maximum outgoing group + public: + double_1dvec scattxs; // Isotropic Sigma_{s,g_{in}} + virtual double calc_f(int gin, int gout, double mu) = 0; + virtual void sample(int gin, int& gout, double& mu, double& wgt) = 0; + virtual void init(int_1dvec in_gmin, int_1dvec in_gmax, + double_2dvec in_mult, double_3dvec coeffs) = 0; + void sample_energy(int gin, int& gout, int& i_gout); + double get_xs(const char* xstype, int gin, int* gout, double* mu); + void generic_init(int order, int_1dvec in_gmin, int_1dvec in_gmax, + double_2dvec in_energy, double_2dvec in_mult); + virtual void combine(std::vector those_scatts, + double_1dvec& scalars) = 0; + virtual int get_order() = 0; + virtual double_3dvec get_matrix(int max_order) = 0; +}; + +class ScattDataLegendre: public ScattData { + protected: + // Maximal value for rejection sampling from a rectangle + double_2dvec max_val; + friend void convert_legendre_to_tabular(ScattDataLegendre& leg, + ScattDataTabular& tab, int n_mu); + public: + void init(int_1dvec in_gmin, int_1dvec in_gmax, double_2dvec in_mult, + double_3dvec coeffs); + void update_max_val(); + double calc_f(int gin, int gout, double mu); + void sample(int gin, int& gout, double& mu, double& wgt); + bool equiv(const ScattDataLegendre& that); + void combine(std::vector those_scatts, double_1dvec& scalars); + int get_order() {return dist[0][0].size() - 1;}; + double_3dvec get_matrix(int max_order); +}; + +class ScattDataHistogram: public ScattData { + protected: + double_1dvec mu; + double dmu; + double_3dvec fmu; + public: + void init(int_1dvec in_gmin, int_1dvec in_gmax, double_2dvec in_mult, + double_3dvec coeffs); + double calc_f(int gin, int gout, double mu); + void sample(int gin, int& gout, double& mu, double& wgt); + void combine(std::vector those_scatts, double_1dvec& scalars); + bool equiv(const ScattDataHistogram& that); + int get_order() {return dist[0][0].size();}; + double_3dvec get_matrix(int max_order); +}; + +class ScattDataTabular: public ScattData { + protected: + double_1dvec mu; + double dmu; + double_3dvec fmu; + friend void convert_legendre_to_tabular(ScattDataLegendre& leg, + ScattDataTabular& tab, int n_mu); + public: + void init(int_1dvec in_gmin, int_1dvec in_gmax, double_2dvec in_mult, + double_3dvec coeffs); + double calc_f(int gin, int gout, double mu); + void sample(int gin, int& gout, double& mu, double& wgt); + void combine(std::vector those_scatts, double_1dvec& scalars); + bool equiv(const ScattDataTabular& that); + int get_order() {return dist[0][0].size();}; + double_3dvec get_matrix(int max_order); +}; + +void convert_legendre_to_tabular(ScattDataLegendre& leg, + ScattDataTabular& tab, int n_mu); + +} // namespace openmc +#endif // SCATTDATA_H \ No newline at end of file diff --git a/src/string_functions.h b/src/string_functions.h new file mode 100644 index 000000000..94d4ce627 --- /dev/null +++ b/src/string_functions.h @@ -0,0 +1,54 @@ +//! \file string_functions.h +//! A collection of helper routines for C-strings and STL strings + +#ifndef STRING_FUNCTIONS_H +#define STRING_FUNCTIONS_H + +// for string functions +#include +#include +#include +#include + +namespace openmc { + +void strtrim(char* str) +{ + int start = 0; // number of leading spaces + char* buffer = str; + + while (*str && *str++ == ' ') ++start; + + while (*str++); // move to end of string + + // backup over trailing spaces + int end = str - buffer - 1; + while (end > 0 && buffer[end - 1] == ' ') --end; + buffer[end] = 0; // remove trailing spaces + + // exit if no leading spaces or string is now empty + if (end <= start || start == 0) return; + str = buffer + start; + + while ((*buffer++ = *str++)); // remove leading spaces: K&R +} + +std::string strtrim(std::string in_str) +{ + std::string str = in_str; + // perform the left trim + str.erase(str.begin(), std::find_if(str.begin(), str.end(), + std::not1(std::ptr_fun(std::isspace)))); + // perform the right trim + str.erase(std::find_if(str.rbegin(), str.rend(), + std::not1(std::ptr_fun(std::isspace))).base(), + str.end()); +} + +void to_lower(std::string& str) +{ + for (int i = 0; i < str.size(); i++) str[i] = std::tolower(str[i]); +} + +} // namespace openmc +#endif // STRING_FUNCTIONS_H \ No newline at end of file diff --git a/src/xsdata.cpp b/src/xsdata.cpp new file mode 100644 index 000000000..a027b18dd --- /dev/null +++ b/src/xsdata.cpp @@ -0,0 +1,782 @@ +#include "xsdata.h" + +namespace openmc { + +//============================================================================== +// XsData class methods +//============================================================================== + +XsData::XsData(int energy_groups, int num_delayed_groups, bool fissionable, + int scatter_format, int n_pol, int n_azi) +{ + // check to make sure scatter format is OK before we allocate + if (scatter_format != ANGLE_HISTOGRAM && scatter_format != ANGLE_TABULAR && + scatter_format != ANGLE_LEGENDRE) { + fatal_error("Invalid scatter_format!"); + } + // allocate all [temperature][phi][theta][in group] quantities + total = double_3dvec(n_pol, double_2dvec(n_azi, + double_1dvec(energy_groups, 0.))); + absorption = double_3dvec(n_pol, double_2dvec(n_azi, + double_1dvec(energy_groups, 0.))); + inverse_velocity = double_3dvec(n_pol, + double_2dvec(n_azi, double_1dvec(energy_groups, 0.))); + if (fissionable) { + fission = double_3dvec(n_pol, double_2dvec(n_azi, + double_1dvec(energy_groups, 0.))); + prompt_nu_fission = double_3dvec(n_pol, double_2dvec(n_azi, + double_1dvec(energy_groups, 0.))); + kappa_fission = double_3dvec(n_pol, double_2dvec(n_azi, + double_1dvec(energy_groups, 0.))); + } + + // allocate decay_rate; [temperature][phi][theta][delayed group] + decay_rate = double_3dvec(n_pol, double_2dvec(n_azi, + double_1dvec(num_delayed_groups, 0.))); + + if (fissionable) { + // allocate delayed_nu_fission; [temperature][phi][theta][in group][out group] + delayed_nu_fission = double_4dvec(n_pol, double_3dvec(n_azi, + double_2dvec(energy_groups, double_1dvec(energy_groups, 0.)))); + + // chi_prompt; [temperature][phi][theta][in group][delayed group] + chi_prompt = double_4dvec(n_pol, double_3dvec(n_azi, + double_2dvec(energy_groups, double_1dvec(num_delayed_groups, 0.)))); + + // chi_delayed; [temperature][phi][theta][in group][out group][delay group] + chi_delayed = double_5dvec(n_pol, double_4dvec(n_azi, + double_3dvec(energy_groups, double_2dvec(energy_groups, + double_1dvec(num_delayed_groups, 0.))))); + } + + scatter.resize(n_pol); + for (int p = 0; p < n_pol; p++) { + scatter[p].resize(n_azi); + for (int a = 0; a < n_azi; a++) { + if (scatter_format == ANGLE_HISTOGRAM) { + scatter[p][a] = new ScattDataHistogram; + } else if (scatter_format == ANGLE_TABULAR) { + scatter[p][a] = new ScattDataTabular; + } else if (scatter_format == ANGLE_LEGENDRE) { + scatter[p][a] = new ScattDataLegendre; + } + } + } +} + +XsData::~XsData() +{ + for (int p = 0; p < scatter.size(); p++) { + for (int a = 0; a < scatter[p].size(); a++) delete scatter[p][a]; + scatter[p].clear(); + } + scatter.clear(); +} + + +void XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, + int final_scatter_format, int order_data, int max_order, + int legendre_to_tabular_points) +{ + // Reconstruct the dimension information so it doesn't need to be passed + int n_pol = total.size(); + int n_azi = total[0].size(); + int energy_groups = total[0][0].size(); + int delayed_groups = decay_rate[0][0].size(); + + // Set the fissionable-specific data + if (fissionable) { + _fissionable_from_hdf5(xsdata_grp, n_pol, n_azi, energy_groups, + delayed_groups); + } + // Get the non-fission-specific data + read_nd_vector(xsdata_grp, "decay_rate", decay_rate); + read_nd_vector(xsdata_grp, "absorption", absorption, true); + read_nd_vector(xsdata_grp, "inverse-velocity", inverse_velocity); + + // Get scattering data + _scatter_from_hdf5(xsdata_grp, n_pol, n_azi, energy_groups, scatter_format, + final_scatter_format, order_data, max_order, + legendre_to_tabular_points); + + // Check absorption to ensure it is not 0 since it is often the + // denominator in tally methods + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + if (absorption[gin][p][a] == 0.) { + absorption[p][a][gin] = 1.e-10; + } + } + } + } + + // Get or calculate the total x/s + if (object_exists(xsdata_grp, "total")) { + read_nd_vector(xsdata_grp, "total", total); + } else { + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + total[p][a][gin] = absorption[p][a][gin] + + scatter[p][a]->scattxs[gin]; + } + } + } + } + + // Check total to ensure it is not 0 since it is often the denominator in + // tally methods + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + if (total[p][a][gin] == 0.) total[p][a][gin] = 1.e-10; + } + } + } +} + + +void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, + int energy_groups, int delayed_groups) +{ + double_4dvec temp_beta = + double_4dvec(n_pol, double_3dvec(n_azi, + double_2dvec(energy_groups, double_1dvec(delayed_groups, 0.)))); + + // Set/get beta + if (object_exists(xsdata_grp, "beta")) { + hid_t xsdata = open_dataset(xsdata_grp, "beta"); + int ndims = dataset_ndims(xsdata); + + if (ndims == 3) { + // Beta is input as [delayed group] + double_1dvec temp_arr = double_1dvec(n_pol * n_azi * delayed_groups); + read_nd_vector(xsdata_grp, "beta", temp_arr); + + // Broadcast to all incoming groups + int temp_idx = 0; + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int dg = 0; dg < delayed_groups; dg++) { + // Set the first group index and copy the rest + temp_beta[p][a][0][dg] = temp_arr[temp_idx++]; + for (int gin = 1; gin < energy_groups; gin++) { + temp_beta[p][a][gin] = temp_beta[p][a][0]; + } + } + } + } + } else if (ndims == 4) { + // Beta is input as [in group][delayed group] + read_nd_vector(xsdata_grp, "beta", temp_beta); + } else { + fatal_error("beta must be provided as a 1D or 2D array!"); + } + } + + // If chi is provided, set chi-prompt and chi-delayed + if (object_exists(xsdata_grp, "chi")) { + double_3dvec temp_arr = double_3dvec(n_pol, double_2dvec(n_azi, + double_1dvec(energy_groups))); + read_nd_vector(xsdata_grp, "chi", temp_arr); + + int temp_idx = 0; + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + // First set the first group + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[p][a][0][gout] = temp_arr[p][a][temp_idx++]; + } + + // Now normalize this data + double chi_sum = std::accumulate(chi_prompt[p][a][0].begin(), + chi_prompt[p][a][0].end(), + 0.); + if (chi_sum <= 0.) { + fatal_error("Encountered chi for a group that is <= 0!"); + } + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[p][a][0][gout] /= chi_sum; + } + + // And extend to the remaining incoming groups + for (int gin = 1; gin < energy_groups; gin++) { + chi_prompt[p][a][gin] = chi_prompt[p][a][0]; + } + + // Finally set chi-delayed equal to chi-prompt + // Set chi-delayed to chi-prompt + for(int gin = 0; gin < energy_groups; gin++) { + for (int gout = 0; gout < energy_groups; gout++) { + for (int dg = 0; dg < delayed_groups; dg++) { + chi_delayed[p][a][gin][gout][dg] = + chi_prompt[p][a][gin][gout]; + } + } + } + } + } + } + + // If nu-fission is provided, set prompt- and delayed-nu-fission; + // if nu-fission is a matrix, set chi-prompt and chi-delayed. + if (object_exists(xsdata_grp, "nu-fission")) { + hid_t xsdata = open_dataset(xsdata_grp, "nu-fission"); + int ndims = dataset_ndims(xsdata); + + if (ndims == 3) { + // nu-fission is a 3-d array + read_nd_vector(xsdata_grp, "nu-fission", prompt_nu_fission); + + // set delayed-nu-fission and correct prompt-nu-fission with beta + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + for (int dg = 0; dg < delayed_groups; dg++) { + delayed_nu_fission[p][a][gin][dg] = + temp_beta[p][a][gin][dg] * + prompt_nu_fission[p][a][gin]; + } + + // Correct the prompt-nu-fission using the delayed neutron fraction + if (delayed_groups > 0) { + double beta_sum = std::accumulate(temp_beta[p][a][gin].begin(), + temp_beta[p][a][gin].end(), 0.); + prompt_nu_fission[p][a][gin] *= (1. - beta_sum); + } + } + } + } + + } else if (ndims == 4) { + // nu-fission is a matrix + read_nd_vector(xsdata_grp, "nu_fission", chi_prompt); + + // Normalize the chi info so the CDF is 1. + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + double chi_sum = std::accumulate(chi_prompt[p][a][gin].begin(), + chi_prompt[p][a][gin].end(), 0.); + if (chi_sum >= 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[p][a][gin][gout] /= chi_sum; + } + } else { + fatal_error("Encountered chi for a group that is <= 0!"); + } + } + + // set chi-delayed to chi-prompt + for (int gin = 0; gin < energy_groups; gin++) { + for (int gout = 0; gout < energy_groups; gout++) { + for (int dg = 0; dg < delayed_groups; dg++) { + chi_delayed[p][a][gin][gout][dg] = + chi_prompt[p][a][gin][gout]; + } + } + } + + // Set the vector nu-fission from the matrix nu-fission + for (int gin = 0; gin < energy_groups; gin++) { + double sum = std::accumulate(chi_prompt[p][a][gin].begin(), + chi_prompt[p][a][gin].end(), 0.); + prompt_nu_fission[p][a][gin] = sum; + } + + // Set the delayed-nu-fission and correct prompt-nu-fission with beta + for (int gin = 0; gin < energy_groups; gin++) { + for (int dg = 0; dg < delayed_groups; dg++) { + delayed_nu_fission[p][a][gin][dg] = + temp_beta[p][a][gin][dg] * + prompt_nu_fission[p][a][gin]; + } + + // Correct prompt-nu-fission using the delayed neutron fraction + if (delayed_groups > 0) { + double beta_sum = std::accumulate(temp_beta[p][a][gin].begin(), + temp_beta[p][a][gin].end(), 0.); + prompt_nu_fission[p][a][gin] *= (1. - beta_sum); + } + } + } + } + } else { + fatal_error("beta must be provided as a 3D or 4D array!"); + } + + close_dataset(xsdata); + } + + // If chi-prompt is provided, set chi-prompt + if (object_exists(xsdata_grp, "chi-prompt")) { + double_3dvec temp_arr = double_3dvec(n_pol, double_2dvec(n_azi, + double_1dvec(energy_groups))); + read_nd_vector(xsdata_grp, "chi-prompt", temp_arr); + + for (int a = 0; a < n_azi; a++) { + for (int p = 0; p < n_pol; p++) { + for (int gin = 0; gin < energy_groups; gin++) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[p][a][gin][gout] = temp_arr[p][a][gout]; + } + + // Normalize chi so its CDF goes to 1 + double chi_sum = std::accumulate(chi_prompt[p][a][gin].begin(), + chi_prompt[p][a][gin].end(), 0.); + if (chi_sum >= 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[p][a][gin][gout] /= chi_sum; + } + } else { + fatal_error("Encountered chi-prompt for a group that is <= 0.!"); + } + } + } + } + } + + // If chi-delayed is provided, set chi-delayed + if (object_exists(xsdata_grp, "chi-delayed")) { + hid_t xsdata = open_dataset(xsdata_grp, "chi-delayed"); + int ndims = dataset_ndims(xsdata); + close_dataset(xsdata); + + if (ndims == 3) { + // chi-delayed is a [in group] vector + double_3dvec temp_arr = double_3dvec(n_pol, double_2dvec(n_azi, + double_1dvec(energy_groups))); + read_nd_vector(xsdata_grp, "chi-delayed", temp_arr); + + for (int a = 0; a < n_azi; a++) { + for (int p = 0; p < n_pol; p++) { + // normalize the chi CDF to 1 + double chi_sum = std::accumulate(temp_arr[p][a].begin(), + temp_arr[p][a].end(), 0.); + if (chi_sum <= 0.) { + fatal_error("Encountered chi-delayed for a group that is <= 0!"); + } + + // set chi-delayed + for (int gin = 0; gin < energy_groups; gin++) { + for (int gout = 0; gout < energy_groups; gout++) { + for (int dg = 0; dg < delayed_groups; dg++) { + chi_delayed[p][a][gin][gout][dg] = + temp_arr[p][a][gout] / chi_sum; + } + } + } + } + } + } else if (ndims == 4) { + // chi_delayed is a matrix + read_nd_vector(xsdata_grp, "chi-delayed", chi_delayed); + + // Normalize the chi info so the CDF is 1. + for (int a = 0; a < n_azi; a++) { + for (int p = 0; p < n_pol; p++) { + for (int dg = 0; dg < delayed_groups; dg++) { + for (int gin = 0; gin < energy_groups; gin++) { + double chi_sum = 0.; + for (int gout = 0; gout < energy_groups; gout++) { + chi_sum += chi_delayed[p][a][gin][gout][dg]; + } + + if (chi_sum > 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_delayed[p][a][gin][gout][dg] /= chi_sum; + } + } else { + fatal_error("Encountered chi-delayed for a group that is <= 0!"); + } + } + } + } + } + } else { + fatal_error("chi-delayed must be provided as a 3D or 4D array!"); + } + } + + // Get prompt-nu-fission, if present + if (object_exists(xsdata_grp, "prompt-nu-fission")) { + hid_t xsdata = open_dataset(xsdata_grp, "prompt-nu-fission"); + int ndims = dataset_ndims(xsdata); + close_dataset(xsdata); + + if (ndims == 3) { + // prompt-nu-fission is a [in group] vector + read_nd_vector(xsdata_grp, "prompt-nu-fission", + prompt_nu_fission); + } else if (ndims == 4) { + // prompt nu fission is a matrix, + // so set prompt_nu_fiss & chi_prompt + double_4dvec temp_arr = double_4dvec(n_pol, double_3dvec(n_azi, + double_2dvec(energy_groups, double_1dvec(energy_groups)))); + read_nd_vector(xsdata_grp, "prompt-nu-fission", temp_arr); + + // The prompt_nu_fission vector from the matrix form + for (int a = 0; a < n_azi; a++) { + for (int p = 0; p < n_pol; p++) { + for (int gin = 0; gin < energy_groups; gin++) { + double prompt_sum = std::accumulate(temp_arr[p][a][gin].begin(), + temp_arr[p][a][gin].end(), 0.); + prompt_nu_fission[p][a][gin] = prompt_sum; + } + + // The chi_prompt data is just the normalized fission matrix + for (int gin= 0; gin < energy_groups; gin++) { + if (prompt_nu_fission[p][a][gin] > 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[p][a][gin][gout] = + temp_arr[p][a][gin][gout] / + prompt_nu_fission[p][a][gin]; + } + } else { + fatal_error("Encountered chi-prompt for a group that is <= 0!"); + } + } + } + } + + } else { + fatal_error("prompt-nu-fission must be provided as a 3D or 4D array!"); + } + } + + // Get delayed-nu-fission, if present + if (object_exists(xsdata_grp, "delayed-nu-fission")) { + hid_t xsdata = open_dataset(xsdata_grp, "delayed-nu-fission"); + int ndims = dataset_ndims(xsdata); + + if (ndims == 3) { + // delayed-nu-fission is a [in group] vector + if (temp_beta[0][0][0][0] == 0.) { + fatal_error("cannot set delayed-nu-fission with a 1D array if " + "beta is not provided"); + } + double_3dvec temp_arr = double_3dvec(n_pol, double_2dvec(n_azi, + double_1dvec(energy_groups))); + read_nd_vector(xsdata_grp, "delayed-nu-fission", temp_arr); + + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + for (int dg = 0; dg < delayed_groups; dg++) { + // Set delayed-nu-fission using beta + delayed_nu_fission[p][a][gin][dg] = + temp_beta[p][a][gin][dg] * temp_arr[p][a][gin]; + } + } + } + } + + } else if (ndims == 4) { + // delayed nu fission is a [pol][azi][energy_group][delayed_group] matrix; + // matrix use this to set delayed-nu-fission separately for each + // delayed group + std::vector dims(ndims); + get_shape(xsdata, &dims[0]); + + if (dims[2] != delayed_groups) { + fatal_error("The delayed-nu-fission matrix was input with a 1st " + "dimension not equal to the number of delayed groups"); + } + if (dims[3] != energy_groups) { + fatal_error("The delayed-nu-fission matrix was input with a 2nd " + "dimension not equal to the number of energy groups"); + } + if (delayed_groups == energy_groups) { + warning("delayed-nu-fission was input as a dimension-4 matrix " + "with the same number of delayed groups and energy " + "groups. OpenMC assumes the dimensions in the matrix " + "are [delayed_groups][energy_groups]. Currently, " + "delayed-nu-fission cannot be set as a group-by-group " + "matrix"); + } + + read_nd_vector(xsdata_grp, "delayed-nu-fission", + delayed_nu_fission); + + } else if (ndims == 5) { + // This will contain delayed-nu-fision and chi-delayed data + double_5dvec temp_arr = double_5dvec(n_pol, double_4dvec(n_azi, + double_3dvec(energy_groups, double_2dvec(energy_groups, + double_1dvec(delayed_groups))))); + read_nd_vector(xsdata_grp, "delayed-nu-fission", temp_arr); + + // Set the 4D delayed-nu-fission matrix and 5D chi-delayed matrix + // from the 5D delayed-nu-fission matrix + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int dg = 0; dg < delayed_groups; dg++) { + for (int gin = 0; gin < energy_groups; gin++) { + double gout_sum = 0.; + for (int gout = 0; gout < energy_groups; gout++) { + gout_sum += temp_arr[p][a][gin][gout][dg]; + chi_delayed[p][a][gin][gout][dg] = + temp_arr[p][a][gin][gout][dg]; + } + delayed_nu_fission[p][a][gin][dg] = gout_sum; + // Normalize chi-delayed + if (gout_sum > 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_delayed[p][a][gin][gout][dg] /= gout_sum; + } + } else { + fatal_error("Encountered chi-delayed for a group that is <= 0!"); + } + } + } + } + } + + } else { + fatal_error("prompt-nu-fission must be provided as a 3D, 4D, or 5D " + "array!"); + } + close_dataset(xsdata); + } + + // Get the fission and kappa_fission data xs + read_nd_vector(xsdata_grp, "fission", fission); + read_nd_vector(xsdata_grp, "kappa-fission", kappa_fission); +} + + +void XsData::_scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, + int energy_groups, int scatter_format, int final_scatter_format, + int order_data, int max_order, int legendre_to_tabular_points) +{ + if (!object_exists(xsdata_grp, "scatter_data")) { + fatal_error("Must provide scatter_data group!"); + } + hid_t scatt_grp = open_group(xsdata_grp, "scatter_data"); + + // Get the outgoing group boundary indices + int_3dvec gmin = int_3dvec(n_pol, int_2dvec(n_azi, + int_1dvec(energy_groups))); + read_nd_vector(scatt_grp, "g_min", gmin, true); + int_3dvec gmax = int_3dvec(n_pol, int_2dvec(n_azi, + int_1dvec(energy_groups))); + read_nd_vector(scatt_grp, "g_max", gmax, true); + + // Make gmin and gmax start from 0 vice 1 as they do in the library + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + gmin[p][a][gin] -= 1; + gmax[p][a][gin] -= 1; + } + } + } + + // Now use this info to find the length of a vector to hold the flattened + // data. + int length = 0; + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + length += order_data * (gmax[p][a][gin] - gmin[p][a][gin] + 1); + } + } + } + double_1dvec temp_arr = double_1dvec(length); + read_nd_vector(scatt_grp, "scatter_matrix", temp_arr, true); + + // Compare the number of orders given with the max order of the problem; + // strip off the superfluous orders if needed + int order_dim; + if (scatter_format == ANGLE_LEGENDRE) { + order_dim = std::min(order_data - 1, max_order) + 1; + } else { + order_dim = order_data; + } + + // convert the flattened temp_arr to a jagged array for passing to + // scatt data + double_5dvec input_scatt = + double_5dvec(n_pol, double_4dvec(n_azi, double_3dvec(energy_groups))); + + int temp_idx = 0; + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + input_scatt[p][a][gin].resize(gmax[p][a][gin] - gmin[p][a][gin] + 1); + for (int i_gout = 0; i_gout < input_scatt[p][a][gin].size(); i_gout++) { + input_scatt[p][a][gin][i_gout].resize(order_dim); + for (int l = 0; l < order_dim; l++) { + input_scatt[p][a][gin][i_gout][l] = temp_arr[temp_idx++]; + } + // Adjust index for the orders we didnt take + temp_idx += (order_data - order_dim); + } + } + } + } + temp_arr.clear(); + + // Get multiplication matrix + double_4dvec temp_mult = double_4dvec(n_pol, double_3dvec(n_azi, + double_2dvec(energy_groups))); + if (object_exists(scatt_grp, "multiplicity_matrix")) { + temp_arr.resize(length); + read_nd_vector(scatt_grp, "multiplicity_matrix", temp_arr); + + // convert the flat temp_arr to a jagged array for passing to scatt data + int temp_idx = 0; + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + temp_mult[p][a][gin].resize(gmax[p][a][gin] - gmin[p][a][gin] + 1); + for (int i_gout = 0; i_gout < temp_mult[p][a][gin].size(); i_gout++) { + temp_mult[p][a][gin][i_gout] = temp_arr[temp_idx++]; + } + } + } + } + } else { + // Use a default: multiplicities are 1.0. + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + temp_mult[p][a][gin].resize(gmax[p][a][gin] - gmin[p][a][gin] + 1); + for (int i_gout = 0; i_gout < temp_mult[p][a][gin].size(); i_gout++) { + temp_mult[p][a][gin][i_gout] = 1.; + } + } + } + } + } + close_group(scatt_grp); + + // Finally, convert the Legendre data to tabular, if needed + if (scatter_format == ANGLE_LEGENDRE && + final_scatter_format == ANGLE_TABULAR) { + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + ScattDataLegendre legendre_scatt; + legendre_scatt.init(gmin[p][a], gmax[p][a], temp_mult[p][a], + input_scatt[p][a]); + + // Now create a tabular version of legendre_scatt + convert_legendre_to_tabular(legendre_scatt, + *static_cast(scatter[p][a]), + legendre_to_tabular_points); + + scatter_format = final_scatter_format; + } + } + } else { + // We are sticking with the current representation + // Initialize the ScattData object with this data + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + scatter[p][a]->init(gmin[p][a], gmax[p][a], temp_mult[p][a], + input_scatt[p][a]); + } + } + } +} + + +void XsData::combine(std::vector those_xs, double_1dvec& scalars) +{ + // Combine the non-scattering data + for (int i = 0; i < those_xs.size(); i++) { + XsData* that = those_xs[i]; + if (!equiv(*that)) fatal_error("Cannot combine the XsData objects!"); + double scalar = scalars[i]; + for (int p = 0; p < total.size(); p++) { + for (int a = 0; a < total[p].size(); a++) { + for (int gin = 0; gin < total[p][a].size(); gin++) { + total[p][a][gin] += scalar * that->total[p][a][gin]; + absorption[p][a][gin] += scalar * that->absorption[p][a][gin]; + inverse_velocity[p][a][gin] += + scalar * that->inverse_velocity[p][a][gin]; + + prompt_nu_fission[p][a][gin] += + scalar * that->prompt_nu_fission[p][a][gin]; + kappa_fission[p][a][gin] += + scalar * that->kappa_fission[p][a][gin]; + fission[p][a][gin] += + scalar * that->fission[p][a][gin]; + + for (int dg = 0; dg < delayed_nu_fission[p][a][gin].size(); dg++) { + delayed_nu_fission[p][a][gin][dg] += + scalar * that->delayed_nu_fission[p][a][gin][dg]; + } + + for (int gout = 0; gout < chi_prompt[p][a][gin].size(); gout++) { + chi_prompt[p][a][gin][gout] += + scalar * that->chi_prompt[p][a][gin][gout]; + + for (int dg = 0; dg < chi_delayed[p][a][gin][gout].size(); dg++) { + chi_delayed[p][a][gin][gout][dg] += + scalar * that->chi_delayed[p][a][gin][gout][dg]; + } + } + } + + for (int dg = 0; dg < decay_rate[p][a].size(); dg++) { + decay_rate[p][a][dg] += scalar * that->decay_rate[p][a][dg]; + } + + // Normalize chi + for (int gin = 0; gin < chi_prompt[p][a].size(); gin++) { + double norm = std::accumulate(chi_prompt[p][a][gin].begin(), + chi_prompt[p][a][gin].end(), 0.); + if (norm > 0.) { + for (int gout = 0; gout < chi_prompt[p][a][gin].size(); gout++) { + chi_prompt[p][a][gin][gout] /= norm; + } + } + + for (int dg = 0; dg < chi_delayed[p][a][gin][0].size(); dg++) { + norm = 0.; + for (int gout = 0; gout < chi_delayed[p][a][gin].size(); gout++) { + norm += chi_delayed[p][a][gin][gout][dg]; + } + if (norm > 0.) { + for (int gout = 0; gout < chi_delayed[p][a][gin].size(); gout++) { + chi_delayed[p][a][gin][gout][dg] /= norm; + } + } + } + } + } + } + } + + // Allow the ScattData object to combine itself + for (int p = 0; p < total.size(); p++) { + for (int a = 0; a < total[p].size(); a++) { + // Build vector of the scattering objects to incorporate + std::vector those_scatts(those_xs.size()); + for (int i = 0; i < those_xs.size(); i++) { + those_scatts[i] = those_xs[i]->scatter[p][a]; + } + + // Now combine these guys + scatter[p][a]->combine(those_scatts, scalars); + } + } +} + + +bool XsData::equiv(const XsData& that) +{ + bool match = false; + // check n_pol (total.size()), n_azi (total[0].size()), and + // groups (total[0][0].size()) + // This assumes correct initializatino of the remaining cross sections + if ((total.size() == that.total.size()) && + (total[0].size() == that.total[0].size()) && + (total[0][0].size() == that.total[0][0].size())) { + match = true; + } + return match; +} + +} //namespace openmc diff --git a/src/xsdata.h b/src/xsdata.h new file mode 100644 index 000000000..1c1f8241b --- /dev/null +++ b/src/xsdata.h @@ -0,0 +1,72 @@ +//! \file xsdata.h +//! A collection of classes for containing the Multi-Group Cross Section data + +#ifndef XSDATA_H +#define XSDATA_H + +#include +#include +#include +#include +#include +#include +#include + +#include "constants.h" +#include "hdf5_interface.h" +#include "math_functions.h" +#include "random_lcg.h" +#include "scattdata.h" + + +namespace openmc { + +//============================================================================== +// XSDATA contains the temperature-independent cross section data for an MGXS +//============================================================================== + +class XsData { + private: + void _scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, + int energy_groups, int scatter_format, int final_scatter_format, + int order_data, int max_order, int legendre_to_tabular_points); + void _fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, + int energy_groups, int delayed_groups); + public: + // The following quantities have the following dimensions: + // [phi][theta][incoming group] + double_3dvec total; + double_3dvec absorption; + double_3dvec prompt_nu_fission; + double_3dvec kappa_fission; + double_3dvec fission; + double_3dvec inverse_velocity; + // decay_rate has the following dimensions: + // [phi][theta][delayed group] + double_3dvec decay_rate; + // delayed_nu_fission has the following dimensions: + // [phi][theta][incoming group][delayed group] + double_4dvec delayed_nu_fission; + // chi_prompt has the following dimensions: + // [phi][theta][incoming group][outgoing group] + double_4dvec chi_prompt; + // chi_delayed has the following dimensions: + // [phi][theta][incoming group][outgoing group][delayed group] + double_5dvec chi_delayed; + // scatter has the following dimensions: [phi][theta] + std::vector > scatter; + + XsData() = default; + XsData(int num_groups, int num_delayed_groups, bool fissionable, + int scatter_format, int n_pol, int n_azi); + ~XsData(); + void from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, + int final_scatter_format, int order_data, int max_order, + int legendre_to_tabular_points); + void combine(std::vector those_xs, double_1dvec& scalars); + bool equiv(const XsData& that); +}; + + +} //namespace openmc +#endif // XSDATA_H \ No newline at end of file From 4355faed6edc277e3026f4049737d26e6a5163b3 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Mon, 4 Jun 2018 19:00:17 -0400 Subject: [PATCH 316/361] this flie too --- src/mgxs.cpp | 671 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 671 insertions(+) create mode 100644 src/mgxs.cpp diff --git a/src/mgxs.cpp b/src/mgxs.cpp new file mode 100644 index 000000000..277cccd07 --- /dev/null +++ b/src/mgxs.cpp @@ -0,0 +1,671 @@ +#include "mgxs.h" + +namespace openmc { + +//============================================================================== +// Mgxs base-class methods +//============================================================================== + +void Mgxs::init(const std::string& in_name, double in_awr, + double_1dvec& in_kTs, bool in_fissionable, + int in_scatter_format, int in_num_groups, + int in_num_delayed_groups, double_1dvec& in_polar, + double_1dvec& in_azimuthal) +{ + name = in_name; + awr = in_awr; + kTs = in_kTs; + fissionable = in_fissionable; + scatter_format = in_scatter_format; + num_groups = in_num_groups; + num_delayed_groups = in_num_delayed_groups; + xs.resize(in_kTs.size()); + polar = in_polar; + azimuthal = in_azimuthal; + n_pol = polar.size(); + n_azi = azimuthal.size(); +} + + +void Mgxs::_metadata_from_hdf5(hid_t xs_id, int in_num_groups, + int in_num_delayed_groups, double_1dvec temperature, int& method, + double tolerance, double_1dvec& temps_to_read, int& order_dim) +{ + // get name + char char_name[MAX_WORD_LEN]; + get_name(xs_id, char_name); + std::string in_name(char_name, std::strlen(char_name)); + // remove the leading '/' + in_name = in_name.substr(1); + + // Get the AWR + double in_awr; + if (attribute_exists(xs_id, "atomic_weight_ratio")) { + read_attr_double(xs_id, "atomic_weight_ratio", &in_awr); + } else { + in_awr = MACROSCOPIC_AWR; + } + + // Determine the available temperatures + hid_t kT_group = open_group(xs_id, "kTs"); + int num_temps = get_num_datasets(kT_group); + char** dset_names = new char*[num_temps]; + get_datasets(kT_group, dset_names); + double_1dvec available_temps(num_temps); + for (int i = 0; i < num_temps; i++) { + read_double(kT_group, dset_names[i], &available_temps[i], true); + + // convert eV to Kelvin + available_temps[i] /= K_BOLTZMANN; + } + std::sort(available_temps.begin(), available_temps.end()); + + // If only one temperature is available, lets just use nearest temperature + // interpolation + if ((num_temps == 1) && (method == TEMPERATURE_INTERPOLATION)) { + warning("Cross sections for " + strtrim(name) + " are only available " + + "at one temperature. Reverying to the nearest temperature " + + "method."); + method = TEMPERATURE_NEAREST; + } + + switch(method) { + case TEMPERATURE_NEAREST: + // Find the minimum difference + for (int i = 0; i < temperature.size(); i++) { + std::valarray temp_diff(available_temps.data(), + available_temps.size()); + temp_diff = std::abs(temp_diff - temperature[i]); + int i_closest = std::min_element(std::begin(temp_diff), std::end(temp_diff)) - + std::begin(temp_diff); + double temp_actual = available_temps[i_closest]; + + if (std::abs(temp_actual - temperature[i]) < tolerance) { + if (std::find(temps_to_read.begin(), temps_to_read.end(), + std::round(temp_actual)) != temps_to_read.end()) { + temps_to_read.push_back(std::round(temp_actual)); + } else { + fatal_error("MGXS Library does not contain cross section for " + + name + " at or near " + + std::to_string(std::round(temperature[i])) + " K."); + } + } + } + break; + + case TEMPERATURE_INTERPOLATION: + for (int i = 0; i < temperature.size(); i++) { + for (int j = 0; j < num_temps - 1; j++) { + if ((available_temps[j] <= temperature[i]) && + (temperature[i] < available_temps[j + 1])) { + if (std::find(temps_to_read.begin(), + temps_to_read.end(), + std::round(available_temps[j])) != temps_to_read.end()) { + temps_to_read.push_back(std::round(available_temps[j])); + } + + if (std::find(temps_to_read.begin(), temps_to_read.end(), + std::round(available_temps[j + 1])) != temps_to_read.end()) { + temps_to_read.push_back(std::round(available_temps[j + 1])); + } + continue; + } + } + + fatal_error("MGXS Library does not contain cross sections for " + + name + " at temperatures that bound " + + std::to_string(std::round(temperature[i]))); + } + } + std::sort(temps_to_read.begin(), temps_to_read.end()); + + // Get the library's temperatures + int n_temperature = temps_to_read.size(); + double_1dvec in_kTs(n_temperature); + for (int i = 0; i < n_temperature; i++) { + std::string temp_str(std::to_string(temps_to_read[i]) + "K"); + + //read exact temperature value + read_double(kT_group, temp_str.c_str(), &in_kTs[i], true); + } + close_group(kT_group); + + // Load the remaining metadata + int in_scatter_format; + if (attribute_exists(xs_id, "scatter_format")) { + std::string temp_str(MAX_WORD_LEN, ' '); + read_attr_string(xs_id, "scatter_format", MAX_WORD_LEN, &temp_str[0]); + strtrim(temp_str); + to_lower(temp_str); + if (temp_str == "legendre") { + in_scatter_format = ANGLE_LEGENDRE; + } else if (temp_str == "histogram") { + in_scatter_format = ANGLE_HISTOGRAM; + } else if (temp_str == "tabular") { + in_scatter_format = ANGLE_TABULAR; + } else { + fatal_error("Invalid scatter_format option!"); + } + } else { + in_scatter_format = ANGLE_LEGENDRE; + } + + if (attribute_exists(xs_id, "scatter_shape")) { + std::string temp_str(MAX_WORD_LEN, ' '); + read_attr_string(xs_id, "scatter_shape", MAX_WORD_LEN, &temp_str[0]); + strtrim(temp_str); + to_lower(temp_str); + if (temp_str != "[g][g\'][order]") { + fatal_error("Invalid scatter_shape option!"); + } + } + //TODO: do i even need this flag? - it should be easy to self-determine + bool in_fissionable = false; + if (attribute_exists(xs_id, "fissionable")) { + int int_fiss; + read_attr_int(xs_id, "fissionable", &int_fiss); + in_fissionable = (bool)int_fiss; + } else { + fatal_error("Fissionable element must be set!"); + } + + // Get the library's value for the order + if (attribute_exists(xs_id, "order")) { + read_attr_int(xs_id, "order", &order_dim); + } else { + fatal_error("Order must be provided!"); + } + + // Store the dimensionality of the data in order_dim. + // For Legendre data, we usually refer to it as Pn where n is the order. + // However Pn has n+1 sets of points (since you need to count the P0 + // moment). Adjust for that. Histogram and Tabular formats dont need this + // adjustment. + if (scatter_format == ANGLE_LEGENDRE) { + order_dim = order_dim + 1; + } + + // Get the angular information + bool is_angular = false; + if (attribute_exists(xs_id, "representation")) { + std::string temp_str(MAX_WORD_LEN, ' '); + read_attr_string(xs_id, "representation", MAX_WORD_LEN, &temp_str[0]); + strtrim(temp_str); + to_lower(temp_str); + if (temp_str == "angle") { + is_angular = true; + } else if (temp_str != "isotropic") { + fatal_error("Invalid Data Representation!"); + } + } + + if (is_angular) { + if (attribute_exists(xs_id, "num_polar")) { + read_attr_int(xs_id, "num_polar", &n_pol); + } else { + fatal_error("num_polar must be provided!"); + } + if (attribute_exists(xs_id, "num_azimuthal")) { + read_attr_int(xs_id, "num_azimuthal", &n_azi); + } else { + fatal_error("num_azimuthal must be provided!"); + } + } else { + n_pol = 1; + n_azi = 1; + } + + // Set the angular bins to use equally-spaced bins + double_1dvec in_polar(n_pol); + double dangle = PI / n_pol; + for (int p = 0; p < n_pol; p++) { + in_polar[p] = (p + 0.5) * dangle; + } + double_1dvec in_azimuthal(n_azi); + dangle = 2. * PI / n_azi; + for (int a = 0; a < n_azi; a++) { + in_azimuthal[a] = (a + 0.5) * dangle - PI; + } + + // Finally use this data to initialize the MGXS Object + init(in_name, in_awr, in_kTs, in_fissionable, in_scatter_format, in_num_groups, + in_num_delayed_groups, in_polar, in_azimuthal); +} + + +void Mgxs::from_hdf5(hid_t xs_id, int energy_groups, int delayed_groups, + double_1dvec temperature, int& method, double tolerance, + int max_order, bool legendre_to_tabular, + int legendre_to_tabular_points) +{ + // Call generic data gathering routine (will populate the metadata) + int order_data; + double_1dvec temps_to_read; + _metadata_from_hdf5(xs_id, energy_groups, delayed_groups, temperature, + method, tolerance, temps_to_read, order_data); + + // Set number of energy and delayed groups + num_groups = energy_groups; + num_delayed_groups = delayed_groups; + + int final_scatter_format; + if (scatter_format == ANGLE_LEGENDRE && legendre_to_tabular) { + final_scatter_format = ANGLE_TABULAR; + } else { + final_scatter_format = scatter_format; + } + + // Load the more specific XsData information + for (int t = 0; t < temps_to_read.size(); t++) { + xs[t] = XsData(energy_groups, delayed_groups, fissionable, + final_scatter_format, n_pol, n_azi); + // Get the temperature as a string and then open the HDF5 group + std::string temp_str = std::to_string(temps_to_read[t]) + "K"; + hid_t xsdata_grp = open_group(xs_id, temp_str.c_str()); + + xs[t].from_hdf5(xsdata_grp, fissionable, scatter_format, + final_scatter_format, order_data, max_order, + legendre_to_tabular_points); + close_group(xsdata_grp); + + } // end temperature loop +} + + +void Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, + std::vector& micros, double_1dvec& atom_densities, + int& method, double tolerance) +{ + // Get the minimum data needed to initialize: + // Dont need awr, but lets just initialize it anyways + double in_awr = -1.; + // start with the assumption it is not fissionable + bool in_fissionable = false; + for (int m = 0; m < micros.size(); m++) { + if (micros[m].fissionable) in_fissionable = true; + } + // Force all of the following data to be the same; these will be verified + // to be true later + int in_scatter_format = micros[0].scatter_format; + int in_num_groups = micros[0].num_groups; + int in_num_delayed_groups = micros[0].num_delayed_groups; + double_1dvec in_polar = micros[0].polar; + double_1dvec in_azimuthal = micros[0].azimuthal; + + init(in_name, in_awr, mat_kTs, in_fissionable, in_scatter_format, in_num_groups, + in_num_delayed_groups, in_polar, in_azimuthal); + + // Create the xs data for each temperature + for (int t = 0; t < mat_kTs.size(); t++) { + double temp_desired = mat_kTs[t]; + + // Create the list of temperature indices and interpolation factors for + // each microscopic data at the material temperature + int_1dvec micro_t(micros.size(), 0); + double_1dvec micro_t_interp(micros.size(), 0.); + for (int m = 0; m < micros.size(); m++) { + switch(method) { + case TEMPERATURE_NEAREST: + { + // Find the nearest temperature + std::valarray temp_diff(micros[m].kTs.data(), + micros[m].kTs.size()); + temp_diff = std::abs(temp_diff - temp_desired); + micro_t[m] = std::min_element(std::begin(temp_diff), + std::end(temp_diff)) - + std::begin(temp_diff); + double temp_actual = micros[m].kTs[micro_t[m]]; + + if (std::abs(temp_actual - temp_desired) >= K_BOLTZMANN * tolerance) { + fatal_error("MGXS Library does not contain cross section for " + + name + " at or near " + + std::to_string(std::round(temp_desired / K_BOLTZMANN)) + + " K."); + } + } + break; + case TEMPERATURE_INTERPOLATION: + // Get a list of bounding temperatures for each actual temperature + // present in the model + for (int k = 0; k < micros[m].kTs.size() - 1; k++) { + if ((micros[m].kTs[k] <= temp_desired) && + (temp_desired < micros[m].kTs[k + 1])) { + micro_t[m] = k; + if (k == 0) { + micro_t_interp[m] = (temp_desired - micros[m].kTs[k]) / + (micros[m].kTs[k + 1] - micros[m].kTs[k]); + } else { + micro_t_interp[m] = 1.; + } + } + } + } // end switch + } // end microscopic temperature loop + + // We are about to loop through each of the microscopic objects + // and incorporate the contribution of each microscopic data at + // one of the two temperature interpolants to this macroscopic quantity. + // If we are doing nearest temperature interpolation, then we don't need + // to do the 2nd temperature + int num_interp_points = 2; + if (method == TEMPERATURE_NEAREST) num_interp_points = 1; + for (int interp_point = 0; interp_point < num_interp_points; interp_point++) { + double_1dvec interp(micros.size()); + double_1dvec temp_indices(micros.size()); + for (int m = 0; m < micros.size(); m++) { + interp[m] = (1. - micro_t_interp[m]) * atom_densities[m]; + temp_indices[m] = micro_t[m] + interp_point; + } + combine(micros, interp, micro_t, t); + } // end loop to sum all micros across the temperatures + } // end temperature (t) loop + +} + + +void Mgxs::combine(std::vector& micros, double_1dvec& scalars, + int_1dvec& micro_ts, int this_t) +{ + // Build the vector of pointers to the xs objects within micros + std::vector those_xs(micros.size()); + for (int i = 0; i < micros.size(); i++) { + if (!xs[this_t].equiv(micros[i].xs[micro_ts[i]])) { + fatal_error("Cannot combine the Mgxs objects!"); + } + those_xs[i] = &(micros[i].xs[micro_ts[i]]); + } + + xs[this_t].combine(those_xs, scalars); +} + + +double Mgxs::get_xs(const char* xstype, int gin, int* gout, double* mu, int* dg) +{ + // This method assumes that the temperature and angle indices are set + double val; + if (std::strcmp(xstype, "total")) { + val = xs[index_temp].total[index_pol][index_azi][gin]; + } else if (std::strcmp(xstype, "absorption")) { + val = xs[index_temp].absorption[index_pol][index_azi][gin]; + } else if (std::strcmp(xstype, "inverse-velocity")) { + val = xs[index_temp].inverse_velocity[index_pol][index_azi][gin]; + } else if (std::strcmp(xstype, "decay rate")) { + if (dg != nullptr) { + val = xs[index_temp].decay_rate[index_pol][index_azi][*dg + 1]; + } else { + val = xs[index_temp].decay_rate[index_pol][index_azi][0]; + } + } else if ((std::strcmp(xstype, "scatter")) || + (std::strcmp(xstype, "scatter/mult")) || + (std::strcmp(xstype, "scatter*f_mu/mult")) || + (std::strcmp(xstype, "scatter*f_mu"))) { + val = xs[index_temp].scatter[index_pol] + [index_azi]->get_xs(xstype, gin, gout, mu); + } else if (fissionable && std::strcmp(xstype, "fission")) { + val = xs[index_temp].fission[index_pol][index_azi][gin]; + } else if (fissionable && std::strcmp(xstype, "kappa-fission")) { + val = xs[index_temp].kappa_fission[index_pol][index_azi][gin]; + } else if (fissionable && std::strcmp(xstype, "prompt-nu-fission")) { + val = xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; + } else if (fissionable && std::strcmp(xstype, "delayed-nu-fission")) { + if (dg != nullptr) { + val = xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin][*dg]; + } else { + val = 0.; + for (auto& num : xs[index_temp].delayed_nu_fission[index_pol] + [index_azi][gin]) { + val += num; + } + } + } else if (fissionable && std::strcmp(xstype, "nu-fission")) { + val = xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; + for (auto& num : xs[index_temp].delayed_nu_fission[index_pol] + [index_azi][gin]) { + val += num; + } + } else if (fissionable && std::strcmp(xstype, "chi-prompt")) { + if (gout != nullptr) { + val = xs[index_temp].chi_prompt[index_pol][index_azi][gin][*gout]; + } else { + // provide an outgoing group-wise sum + val = 0.; + for (auto& num : xs[index_temp].chi_prompt[index_pol][index_azi][gin]) { + val += num; + } + } + } else if (fissionable && std::strcmp(xstype, "chi-delayed")) { + if (gout != nullptr) { + if (dg != nullptr) { + val = xs[index_temp].chi_delayed[index_pol][index_azi][gin][*gout][*dg]; + } else { + val = xs[index_temp].chi_delayed[index_pol][index_azi][gin][*gout][0]; + } + } else { + if (dg != nullptr) { + val = 0.; + for (int i = 0; i < xs[index_temp].chi_delayed[index_pol] + [index_azi][gin].size(); i++) { + val += xs[index_temp].chi_delayed[index_pol][index_azi][gin][i][*dg]; + } + } else { + val = 0.; + for (int i = 0; i < xs[index_temp].chi_delayed[index_pol] + [index_azi][gin].size(); i++) { + for (auto& num : xs[index_temp].chi_delayed[index_pol] + [index_azi][gin][i]) { + val += num; + } + } + } + } + } else { + val = 0.; + } + return val; +} + + +void Mgxs::sample_fission_energy(int gin, double nu_fission, int& dg, int& gout) +{ + // This method assumes that the temperature and angle indices are set + // Find the probability of having a prompt neutron + double prob_prompt = + xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin] / + nu_fission; + + // sample random numbers + double xi_pd = prn(); + double xi_gout = prn(); + + // Select whether the neutron is prompt or delayed + if (xi_pd <= prob_prompt) { + // the neutron is prompt + + // set the delayed group for the particle to be 0, indicating prompt + dg = 0; + + // sample the outgoing energy group + gout = 0; + double prob_gout = + xs[index_temp].chi_prompt[index_pol][index_azi][gin][gout]; + while (prob_gout < xi_gout) { + gout++; + prob_gout += xs[index_temp].chi_prompt[index_pol][index_azi][gin][gout]; + } + + } else { + // the neutron is delayed + + // get the delayed group + dg = 0; + while (xi_pd >= prob_prompt) { + dg++; + prob_prompt += + xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin][dg] / + nu_fission; + } + + // adjust dg in case of round-off error + dg = std::min(dg, num_delayed_groups); + + // sample the outgoing energy group + gout = 0; + double prob_gout = + xs[index_temp].chi_delayed[index_pol][index_azi][gin][gout][dg]; + while (prob_gout < xi_gout) { + gout++; + prob_gout += + xs[index_temp].chi_delayed[index_pol][index_azi][gin][gout][dg]; + } + } +} + + +void Mgxs::sample_scatter(dir_arr& uvw, int gin, int& gout, double& mu, + double& wgt) +{ + // This method assumes that the temperature and angle indices are set + // Sample the data + xs[index_temp].scatter[index_pol][index_azi]->sample(gin, gout, mu, wgt); +} + + +void Mgxs::calculate_xs(int gin, double sqrtkT, dir_arr& uvw, double& total_xs, + double& abs_xs, double& nu_fiss_xs) +{ + // Set our indices + set_temperature_index(sqrtkT); + set_angle_index(uvw); + total_xs = xs[index_temp].total[index_pol][index_azi][gin]; + abs_xs = xs[index_temp].absorption[index_pol][index_azi][gin]; + + // nu-fission is made up of the prompt and all the delayed nu_fission data + nu_fiss_xs = xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; + for (auto& val : xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin]) { + nu_fiss_xs += val; + } +} + + +bool Mgxs::equiv(const Mgxs& that) +{ + bool match = false; + + if ((num_delayed_groups == that.num_delayed_groups) && + (num_groups == that.num_groups) && + (n_pol == that.n_pol) && + (n_azi == that.n_azi) && + (std::equal(polar.begin(), polar.end(), that.polar.begin())) && + (std::equal(azimuthal.begin(), azimuthal.end(), that.azimuthal.begin())) && + (scatter_format == that.scatter_format)) { + match = true; + } + return match; +} + + +inline void Mgxs::set_temperature_index(double sqrtkT) +{ + // See if we need to find the new index + if (sqrtkT != last_sqrtkT) { + double kT = sqrtkT * sqrtkT; + + // initialize vector for storage of the differences + std::valarray temp_diff(kTs.data(), kTs.size()); + + // Find the minimum difference of kT and kTs + temp_diff = std::abs(temp_diff - kT); + index_temp = std::min_element(std::begin(temp_diff), std::end(temp_diff)) - + std::begin(temp_diff); + + // store this temperature as the last one used + last_sqrtkT = sqrtkT; + } +} + + +inline void Mgxs::set_angle_index(dir_arr& uvw) +{ + // See if we need to find the new index + if (uvw != last_uvw) { + // convert uvw to polar and azimuthal angles + double my_pol = std::acos(uvw[2]); + double my_azi = std::atan2(uvw[1], uvw[0]); + + // Find the location, assuming equal-bin angles + double delta_angle = PI / n_pol; + index_pol = std::floor(my_pol / delta_angle + 1.); + delta_angle = PI / n_azi; + index_azi = std::floor((my_azi + PI) / delta_angle + 1.); + + // store this direction as the last one used + last_uvw = uvw; + } +} + +//============================================================================== +// Mgxs data loading methods +//============================================================================== + +void read_mgxs_library(hid_t file_id, int n_nuclides, char** names, + int energy_groups, int delayed_groups, int n_temps, double temps[], + int& method, double tolerance, int max_order, bool legendre_to_tabular, + int legendre_to_tabular_points) +{ + //!! mgxs_data.F90 will be modified to just create the list of names + //!! in the order needed + // Convert temps to a vector for the from_hdf5 function + double_1dvec temperature; + temperature.assign(temps, temps + n_temps); + nuclides_MG.resize(n_nuclides); + for (int i = 0; i < n_nuclides; i++) { + // TODO: Replacement for write_message + // write_message("Loading " + std::string(names[i]) + " data...", 6); + + // Check to make sure cross section set exists in the library + hid_t xs_grp; + if (object_exists(file_id, names[i])) { + xs_grp = open_group(file_id, names[i]); + } else { + fatal_error("Data for " + std::string(names[i]) + " does not exist in " + + "provided MGXS Library"); + } + + nuclides_MG[i].from_hdf5(xs_grp, energy_groups, delayed_groups, + temperature, method, tolerance, max_order, legendre_to_tabular, + legendre_to_tabular_points); + } +} + + +bool query_fissionable(const int i_nuclides[], const int n_nuclides) +{ + bool result = false; + for (int i = 0; i < n_nuclides; i++) { + if (nuclides_MG[i_nuclides[i]].fissionable) result = true; + } + return result; +} + + +void create_macro_xs(int n_materials, double_2dvec& mat_kTs, + std::vector& mat_names, + double_1dvec& atom_densities, int& method, + double tolerance) +{ + // TODO mat_kTs needs to be converted from Fortran + // it is currently an array of type(VectorReal), a wrapper should convert to + // the vector. + macro_xs.resize(n_materials); + + for (int m = 0; m < n_materials; m++) + { + if (mat_kTs[m].size() > 0) { + macro_xs[m].build_macro(mat_names[m], mat_kTs[m], nuclides_MG, + atom_densities, method, tolerance); + } + } +} + + +} // namespace openmc \ No newline at end of file From 2602bef154cfef5115466c01a7cdaf7c85def6b8 Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Tue, 5 Jun 2018 19:11:45 +0000 Subject: [PATCH 317/361] fixing indentations and level-spacing. Changed index in 'nuclide_header.f90' line 891 from 4 to 1 (n,gamma) to account for the re-ordering of DEPLETION_RX. --- src/nuclide_header.F90 | 31 +++++++++++++++---------------- src/simulation.F90 | 2 +- src/tallies/tally.F90 | 12 ++++++------ 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 487c71545..8b71e6bf8 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -888,7 +888,7 @@ contains micro_xs % reaction(:) = ZERO ! Only non-zero reaction is (n,gamma) - micro_xs % reaction(4) = sig_a - sig_f + micro_xs % reaction(1) = sig_a - sig_f end if ! Ensure these values are set @@ -983,6 +983,18 @@ contains ! Initialize entire array to zero in case we skip ! any threshold reaction. micro_xs % reaction(:) = ZERO + + !there shouldn't be a threshold check for (n,gamma). + !I know this is not very clean but I don't want to overload the loop + !with too many conditional statements for now. + i_rxn = this % reaction_index(DEPLETION_RX(1)) + if (i_rxn > 0) then + associate (xs => this % reactions(i_rxn) % xs(i_temp)) + micro_xs % reaction(1) = (ONE - f) * & + xs % value(i_grid - xs % threshold + 1) + & + f * xs % value(i_grid - xs % threshold + 2) + end associate + end if !looping from element 2 to element 6. !treating (n,gamma) differently because it is not a threshold reaction. do j = 2, 6 @@ -998,25 +1010,12 @@ contains f * xs % value(i_grid - xs % threshold + 2) ! Check if we are below the (n,2n) and/or (n,3n) reaction thresholds to ! skip remaining depletion-xs construction. - else - if (j >= 4) then - exit - end if + elseif (j >= 4) then + exit end if end associate end if end do - !there shouldn't be a threshold check for (n,gamma). - !I know this is not very clean but I don't want to overload the loop - !with too many conditional statements for now. - i_rxn = this % reaction_index(DEPLETION_RX(1)) - if (i_rxn > 0) then - associate (xs => this % reactions(i_rxn) % xs(i_temp)) - micro_xs % reaction(1) = (ONE - f) * & - xs % value(i_grid - xs % threshold + 1) + & - f * xs % value(i_grid - xs % threshold + 2) - end associate - end if end if end if diff --git a/src/simulation.F90 b/src/simulation.F90 index cdb69efb4..4f6163429 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -516,7 +516,7 @@ contains 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) + 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) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index fb3a15d54..b245bf524 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -1074,18 +1074,18 @@ contains else ! Determine index in NuclideMicroXS % reaction array select case (score_bin) - case (N_2N) - m = 4 - case (N_3N) - m = 5 - case (N_4N) - m = 6 case (N_GAMMA) m = 1 case (N_P) m = 2 case (N_A) m = 3 + case (N_2N) + m = 4 + case (N_3N) + m = 5 + case (N_4N) + m = 6 end select if (i_nuclide > 0) then From 13f163a25ac7a0629177c39e0172b3275ff4d730 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Fri, 8 Jun 2018 16:18:37 -0400 Subject: [PATCH 318/361] Tied in the Mgxs cpp code in to the main body of Fortran. This commit stores progress getting microscopic data loaded on the C++ side. The next will be building the macroscopic cross sections, and then actually using the C++ during the fortran simulation --- CMakeLists.txt | 4 + src/constants.h | 10 ++ src/hdf5_interface.cpp | 30 ++--- src/hdf5_interface.h | 1 + src/input_xml.F90 | 3 +- src/math_functions.h | 12 +- src/mgxs.cpp | 128 ++++++++++---------- src/mgxs.h | 33 ++--- src/mgxs_data.F90 | 111 ++++++++++++++++- src/mgxs_header.F90 | 4 +- src/scattdata.cpp | 265 ++++++++++++++++++++++++++++++++++++++--- src/settings.F90 | 14 +-- src/string_functions.h | 41 +++---- src/xsdata.cpp | 57 +++------ src/xsdata.h | 6 +- 15 files changed, 512 insertions(+), 207 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dc5558de1..362e98a83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -432,17 +432,21 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/trigger_header.F90 ) set(LIBOPENMC_CXX_SRC + src/constants.h src/initialize.cpp src/finalize.cpp src/hdf5_interface.cpp src/math_functions.cpp src/message_passing.cpp + src/mgxs.cpp src/plot.cpp src/random_lcg.cpp + src/scattdata.cpp src/simulation.cpp src/state_point.cpp src/surface.cpp src/xml_interface.cpp + src/xsdata.cpp src/pugixml/pugixml.cpp) add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC} ${LIBOPENMC_CXX_SRC}) set_target_properties(libopenmc PROPERTIES diff --git a/src/constants.h b/src/constants.h index b14984a14..4129305bf 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,7 +1,10 @@ +//! \file constants.h +//! A collection of constants #ifndef CONSTANTS_H #define CONSTANTS_H +#include #include #include @@ -54,6 +57,13 @@ constexpr int DEFAULT_NMU {33}; constexpr int TEMPERATURE_NEAREST {1}; constexpr int TEMPERATURE_INTERPOLATION {2}; +// TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we +// use so for now we will reuse the Fortran constant until we are OK with +// modifying test results +constexpr double PI {3.1415926535898}; + +const double SQRT_PI {std::sqrt(PI)}; + } // namespace openmc diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 3d2f3ee7b..39e0162b5 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -487,9 +487,9 @@ read_nd_vector(hid_t obj_id, const char* name, bool must_have) { if (object_exists(obj_id, name)) { - dim1 = result.size(); - dim2 = result[0].size(); - dim3 = result[0][0].size(); + int dim1 = result.size(); + int dim2 = result[0].size(); + int dim3 = result[0][0].size(); std::vector temp_arr = std::vector(dim1 * dim2 * dim3); read_double(obj_id, name, &temp_arr[0], true); @@ -512,9 +512,9 @@ read_nd_vector(hid_t obj_id, const char* name, bool must_have) { if (object_exists(obj_id, name)) { - dim1 = result.size(); - dim2 = result[0].size(); - dim3 = result[0][0].size(); + int dim1 = result.size(); + int dim2 = result[0].size(); + int dim3 = result[0][0].size(); std::vector temp_arr = std::vector(dim1 * dim2 * dim3); read_int(obj_id, name, &temp_arr[0], true); @@ -537,10 +537,10 @@ read_nd_vector(hid_t obj_id, const char* name, bool must_have) { if (object_exists(obj_id, name)) { - dim1 = result.size(); - dim2 = result[0].size(); - dim3 = result[0][0].size(); - dim4 = result[0][0][0].size(); + int dim1 = result.size(); + int dim2 = result[0].size(); + int dim3 = result[0][0].size(); + int dim4 = result[0][0][0].size(); std::vector temp_arr = std::vector( dim1 * dim2 * dim3 * dim4); read_double(obj_id, name, &temp_arr[0], true); @@ -566,11 +566,11 @@ read_nd_vector(hid_t obj_id, const char* name, bool must_have) { if (object_exists(obj_id, name)) { - dim1 = result.size(); - dim2 = result[0].size(); - dim3 = result[0][0].size(); - dim4 = result[0][0][0].size(); - dim5 = result[0][0][0][0].size(); + int dim1 = result.size(); + int dim2 = result[0].size(); + int dim3 = result[0][0].size(); + int dim4 = result[0][0][0].size(); + int dim5 = result[0][0][0][0].size(); std::vector temp_arr = std::vector( dim1 * dim2 * dim3 * dim4 * dim5); read_double(obj_id, name, &temp_arr[0], true); diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 734cf0f91..fd03eb4c6 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -7,6 +7,7 @@ #include #include #include +#include #include diff --git a/src/input_xml.F90 b/src/input_xml.F90 index f8cf50dd3..8d4c8de81 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -19,7 +19,7 @@ module input_xml use material_header use mesh_header use message_passing - use mgxs_data, only: create_macro_xs, read_mgxs + use mgxs_data, only: create_macro_xs, read_mgxs, read_mgxs2 use mgxs_header use nuclide_header use output, only: title, header, print_plot @@ -84,6 +84,7 @@ contains else ! Create material macroscopic data for MGXS call read_mgxs() + call read_mgxs2() call create_macro_xs() end if call time_read_xs % stop() diff --git a/src/math_functions.h b/src/math_functions.h index 68e89251e..2ceea98d8 100644 --- a/src/math_functions.h +++ b/src/math_functions.h @@ -7,22 +7,12 @@ #include #include +#include "constants.h" #include "random_lcg.h" namespace openmc { -//============================================================================== -// Module constants. -//============================================================================== - -// TODO: cmath::M_PI has 3 more digits precision than the Fortran constant we -// use so for now we will reuse the Fortran constant until we are OK with -// modifying test results -extern "C" constexpr double PI {3.1415926535898}; - -extern "C" const double SQRT_PI {std::sqrt(PI)}; - //============================================================================== //! Calculate the percentile of the standard normal distribution with a //! specified probability level. diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 277cccd07..9c11da8ac 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -6,11 +6,11 @@ namespace openmc { // Mgxs base-class methods //============================================================================== -void Mgxs::init(const std::string& in_name, double in_awr, - double_1dvec& in_kTs, bool in_fissionable, - int in_scatter_format, int in_num_groups, - int in_num_delayed_groups, double_1dvec& in_polar, - double_1dvec& in_azimuthal) +void Mgxs::init(const std::string& in_name, const double in_awr, + const double_1dvec& in_kTs, const bool in_fissionable, + const int in_scatter_format, const int in_num_groups, + const int in_num_delayed_groups, const double_1dvec& in_polar, + const double_1dvec& in_azimuthal) { name = in_name; awr = in_awr; @@ -27,9 +27,10 @@ void Mgxs::init(const std::string& in_name, double in_awr, } -void Mgxs::_metadata_from_hdf5(hid_t xs_id, int in_num_groups, - int in_num_delayed_groups, double_1dvec temperature, int& method, - double tolerance, double_1dvec& temps_to_read, int& order_dim) +void Mgxs::_metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, + const int in_num_delayed_groups, double_1dvec& temperature, int& method, + const double tolerance, int_1dvec& temps_to_read, int& order_dim, + bool& is_isotropic) { // get name char char_name[MAX_WORD_LEN]; @@ -49,7 +50,10 @@ void Mgxs::_metadata_from_hdf5(hid_t xs_id, int in_num_groups, // Determine the available temperatures hid_t kT_group = open_group(xs_id, "kTs"); int num_temps = get_num_datasets(kT_group); - char** dset_names = new char*[num_temps]; + char* dset_names[num_temps]; + for (int i = 0; i < num_temps; i++) { + dset_names[i] = new char[151]; + } get_datasets(kT_group, dset_names); double_1dvec available_temps(num_temps); for (int i = 0; i < num_temps; i++) { @@ -82,11 +86,11 @@ void Mgxs::_metadata_from_hdf5(hid_t xs_id, int in_num_groups, if (std::abs(temp_actual - temperature[i]) < tolerance) { if (std::find(temps_to_read.begin(), temps_to_read.end(), - std::round(temp_actual)) != temps_to_read.end()) { + std::round(temp_actual)) == temps_to_read.end()) { temps_to_read.push_back(std::round(temp_actual)); } else { fatal_error("MGXS Library does not contain cross section for " + - name + " at or near " + + in_name + " at or near " + std::to_string(std::round(temperature[i])) + " K."); } } @@ -100,20 +104,20 @@ void Mgxs::_metadata_from_hdf5(hid_t xs_id, int in_num_groups, (temperature[i] < available_temps[j + 1])) { if (std::find(temps_to_read.begin(), temps_to_read.end(), - std::round(available_temps[j])) != temps_to_read.end()) { - temps_to_read.push_back(std::round(available_temps[j])); + std::round(available_temps[j])) == temps_to_read.end()) { + temps_to_read.push_back(std::round((int)available_temps[j])); } if (std::find(temps_to_read.begin(), temps_to_read.end(), - std::round(available_temps[j + 1])) != temps_to_read.end()) { - temps_to_read.push_back(std::round(available_temps[j + 1])); + std::round(available_temps[j + 1])) == temps_to_read.end()) { + temps_to_read.push_back(std::round((int) available_temps[j + 1])); } continue; } } fatal_error("MGXS Library does not contain cross sections for " + - name + " at temperatures that bound " + + in_name + " at temperatures that bound " + std::to_string(std::round(temperature[i]))); } } @@ -135,13 +139,12 @@ void Mgxs::_metadata_from_hdf5(hid_t xs_id, int in_num_groups, if (attribute_exists(xs_id, "scatter_format")) { std::string temp_str(MAX_WORD_LEN, ' '); read_attr_string(xs_id, "scatter_format", MAX_WORD_LEN, &temp_str[0]); - strtrim(temp_str); - to_lower(temp_str); - if (temp_str == "legendre") { + to_lower(strtrim(temp_str)); + if (temp_str.compare(0, 8, "legendre") == 0) { in_scatter_format = ANGLE_LEGENDRE; - } else if (temp_str == "histogram") { + } else if (temp_str.compare(0, 9, "histogram") == 0) { in_scatter_format = ANGLE_HISTOGRAM; - } else if (temp_str == "tabular") { + } else if (temp_str.compare(0, 7, "tabular") == 0) { in_scatter_format = ANGLE_TABULAR; } else { fatal_error("Invalid scatter_format option!"); @@ -153,9 +156,8 @@ void Mgxs::_metadata_from_hdf5(hid_t xs_id, int in_num_groups, if (attribute_exists(xs_id, "scatter_shape")) { std::string temp_str(MAX_WORD_LEN, ' '); read_attr_string(xs_id, "scatter_shape", MAX_WORD_LEN, &temp_str[0]); - strtrim(temp_str); - to_lower(temp_str); - if (temp_str != "[g][g\'][order]") { + to_lower(strtrim(temp_str)); + if (temp_str.compare(0, 14, "[g][g\'][order]") != 0) { fatal_error("Invalid scatter_shape option!"); } } @@ -181,25 +183,24 @@ void Mgxs::_metadata_from_hdf5(hid_t xs_id, int in_num_groups, // However Pn has n+1 sets of points (since you need to count the P0 // moment). Adjust for that. Histogram and Tabular formats dont need this // adjustment. - if (scatter_format == ANGLE_LEGENDRE) { + if (in_scatter_format == ANGLE_LEGENDRE) { order_dim = order_dim + 1; } // Get the angular information - bool is_angular = false; + is_isotropic = true; if (attribute_exists(xs_id, "representation")) { std::string temp_str(MAX_WORD_LEN, ' '); read_attr_string(xs_id, "representation", MAX_WORD_LEN, &temp_str[0]); - strtrim(temp_str); - to_lower(temp_str); - if (temp_str == "angle") { - is_angular = true; - } else if (temp_str != "isotropic") { + to_lower(strtrim(temp_str)); + if (temp_str.compare(0, 5, "angle") == 0) { + is_isotropic = false; + } else if (temp_str.compare(0, 9, "isotropic") != 0) { fatal_error("Invalid Data Representation!"); } } - if (is_angular) { + if (!is_isotropic) { if (attribute_exists(xs_id, "num_polar")) { read_attr_int(xs_id, "num_polar", &n_pol); } else { @@ -228,31 +229,27 @@ void Mgxs::_metadata_from_hdf5(hid_t xs_id, int in_num_groups, } // Finally use this data to initialize the MGXS Object - init(in_name, in_awr, in_kTs, in_fissionable, in_scatter_format, in_num_groups, - in_num_delayed_groups, in_polar, in_azimuthal); + init(in_name, in_awr, in_kTs, in_fissionable, in_scatter_format, + in_num_groups, in_num_delayed_groups, in_polar, in_azimuthal); } void Mgxs::from_hdf5(hid_t xs_id, int energy_groups, int delayed_groups, - double_1dvec temperature, int& method, double tolerance, + double_1dvec& temperature, int& method, double tolerance, int max_order, bool legendre_to_tabular, int legendre_to_tabular_points) { // Call generic data gathering routine (will populate the metadata) int order_data; - double_1dvec temps_to_read; + int_1dvec temps_to_read; + bool is_isotropic; _metadata_from_hdf5(xs_id, energy_groups, delayed_groups, temperature, - method, tolerance, temps_to_read, order_data); + method, tolerance, temps_to_read, order_data, is_isotropic); // Set number of energy and delayed groups - num_groups = energy_groups; - num_delayed_groups = delayed_groups; - - int final_scatter_format; - if (scatter_format == ANGLE_LEGENDRE && legendre_to_tabular) { - final_scatter_format = ANGLE_TABULAR; - } else { - final_scatter_format = scatter_format; + int final_scatter_format = scatter_format; + if (legendre_to_tabular) { + if (scatter_format == ANGLE_LEGENDRE) final_scatter_format = ANGLE_TABULAR; } // Load the more specific XsData information @@ -265,7 +262,7 @@ void Mgxs::from_hdf5(hid_t xs_id, int energy_groups, int delayed_groups, xs[t].from_hdf5(xsdata_grp, fissionable, scatter_format, final_scatter_format, order_data, max_order, - legendre_to_tabular_points); + legendre_to_tabular_points, is_isotropic); close_group(xsdata_grp); } // end temperature loop @@ -607,9 +604,9 @@ inline void Mgxs::set_angle_index(dir_arr& uvw) // Mgxs data loading methods //============================================================================== -void read_mgxs_library(hid_t file_id, int n_nuclides, char** names, - int energy_groups, int delayed_groups, int n_temps, double temps[], - int& method, double tolerance, int max_order, bool legendre_to_tabular, +void add_mgxs(hid_t file_id, char* name, int energy_groups, + int delayed_groups, int n_temps, double temps[], int& method, + double tolerance, int max_order, bool legendre_to_tabular, int legendre_to_tabular_points) { //!! mgxs_data.F90 will be modified to just create the list of names @@ -617,28 +614,29 @@ void read_mgxs_library(hid_t file_id, int n_nuclides, char** names, // Convert temps to a vector for the from_hdf5 function double_1dvec temperature; temperature.assign(temps, temps + n_temps); - nuclides_MG.resize(n_nuclides); - for (int i = 0; i < n_nuclides; i++) { - // TODO: Replacement for write_message - // write_message("Loading " + std::string(names[i]) + " data...", 6); - // Check to make sure cross section set exists in the library - hid_t xs_grp; - if (object_exists(file_id, names[i])) { - xs_grp = open_group(file_id, names[i]); - } else { - fatal_error("Data for " + std::string(names[i]) + " does not exist in " - + "provided MGXS Library"); - } + // TODO: C++ replacement for write_message + // write_message("Loading " + std::string(names[i]) + " data...", 6); - nuclides_MG[i].from_hdf5(xs_grp, energy_groups, delayed_groups, - temperature, method, tolerance, max_order, legendre_to_tabular, - legendre_to_tabular_points); + // Check to make sure cross section set exists in the library + hid_t xs_grp; + if (object_exists(file_id, name)) { + xs_grp = open_group(file_id, name); + } else { + fatal_error("Data for " + std::string(name) + " does not exist in " + + "provided MGXS Library"); } + + Mgxs mg; + mg.from_hdf5(xs_grp, energy_groups, delayed_groups, + temperature, method, tolerance, max_order, legendre_to_tabular, + legendre_to_tabular_points); + + nuclides_MG.push_back(mg); } -bool query_fissionable(const int i_nuclides[], const int n_nuclides) +bool query_fissionable(const int n_nuclides, const int i_nuclides[]) { bool result = false; for (int i = 0; i < n_nuclides; i++) { diff --git a/src/mgxs.h b/src/mgxs.h index 8ed4ba99e..8c13cf7cb 100644 --- a/src/mgxs.h +++ b/src/mgxs.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "constants.h" #include "hdf5_interface.h" @@ -22,7 +23,6 @@ namespace openmc { - //============================================================================== // MGXS contains the mgxs data for a nuclide/material //============================================================================== @@ -45,26 +45,27 @@ class Mgxs { double_1dvec polar; double_1dvec azimuthal; dir_arr last_uvw; - void _metadata_from_hdf5(hid_t xs_id, int in_num_groups, - int in_num_delayed_groups, double_1dvec temperature, int& method, - double tolerance, double_1dvec& temps_to_read, int& order_dim); + void _metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, + const int in_num_delayed_groups, double_1dvec& temperature, + int& method, const double tolerance, int_1dvec& temps_to_read, + int& order_dim, bool& is_isotropic); public: bool fissionable; // Is this fissionable - void init(const std::string& in_name, double in_awr, double_1dvec& in_kTs, - bool in_fissionable, int in_scatter_format, int in_num_groups, - int in_num_delayed_groups, double_1dvec& in_polar, - double_1dvec& in_azimuthal); + void init(const std::string& in_name, const double in_awr, + const double_1dvec& in_kTs, const bool in_fissionable, + const int in_scatter_format, const int in_num_groups, + const int in_num_delayed_groups, const double_1dvec& in_polar, + const double_1dvec& in_azimuthal); void build_macro(const std::string& in_name, double_1dvec& mat_kTs, std::vector& micros, double_1dvec& atom_densities, int& method, double tolerance); void combine(std::vector& micros, double_1dvec& scalars, int_1dvec& micro_ts, int this_t); void from_hdf5(hid_t xs_id, int energy_groups, int delayed_groups, - double_1dvec temperature, int& method, - double tolerance, int max_order, - bool legendre_to_tabular, - int legendre_to_tabular_points); + double_1dvec& temperature, int& method, double tolerance, + int max_order, bool legendre_to_tabular, + int legendre_to_tabular_points); double get_xs(const char* xstype, int gin, int* gout, double* mu, int* dg); void sample_fission_energy(int gin, double nu_fission, int& dg, int& gout); @@ -77,11 +78,11 @@ class Mgxs { inline void set_angle_index(dir_arr& uvw); }; -extern "C" void read_mgxs_library(hid_t file_id, int n_nuclides, char** names, - int energy_groups, int delayed_groups, int n_temps, double temps[], - int& method, double tolerance, int max_order, bool legendre_to_tabular, +extern "C" void add_mgxs(hid_t file_id, char* name, int energy_groups, + int delayed_groups, int n_temps, double temps[], int& method, + double tolerance, int max_order, bool legendre_to_tabular, int legendre_to_tabular_points); -extern "C" bool query_fissionable(const int i_nuclides[], const int n_nuclides); +extern "C" bool query_fissionable(const int n_nuclides, const int i_nuclides[]); void create_macro_xs(int n_materials, double_2dvec& mat_kTs, std::vector& mat_names, double_1dvec& atom_densities, int& method, double tolerance); diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index e0631d16c..6be463a91 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -1,5 +1,7 @@ module mgxs_data + use, intrinsic :: ISO_C_BINDING + use constants use algorithm, only: find use dict_header, only: DictCharInt @@ -11,10 +13,40 @@ module mgxs_data use nuclide_header, only: n_nuclides use set_header, only: SetChar use settings - use stl_vector, only: VectorReal + use stl_vector, only: VectorReal, VectorChar use string, only: to_lower implicit none + interface + subroutine add_mgxs_c(file_id, name, energy_groups, delayed_groups, & + n_temps, temps, method, tolerance, max_order, legendre_to_tabular, & + legendre_to_tabular_points) bind(C, name='add_mgxs') + use ISO_C_BINDING + import HID_T + implicit none + integer(HID_T), value, intent(in) :: file_id + character(kind=C_CHAR),intent(in) :: name(*) + integer(C_INT), value, intent(in) :: energy_groups + integer(C_INT), value, intent(in) :: delayed_groups + integer(C_INT), value, intent(in) :: n_temps + real(C_DOUBLE), intent(in) :: temps(1:n_temps) + integer(C_INT), intent(inout) :: method + real(C_DOUBLE), value, intent(in) :: tolerance + integer(C_INT), value, intent(in) :: max_order + logical(C_BOOL),value, intent(in) :: legendre_to_tabular + integer(C_INT), value, intent(in) :: legendre_to_tabular_points + end subroutine add_mgxs_c + + function query_fissionable_c(n_nuclides, i_nuclides) result(result) & + bind(C, name='query_fissionable') + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n_nuclides + integer(C_INT), intent(in) :: i_nuclides(1:n_nuclides) + logical(C_BOOL) :: result + end function query_fissionable_c + end interface + contains !=============================================================================== @@ -165,6 +197,83 @@ contains end subroutine read_mgxs + subroutine read_mgxs2() + integer :: i ! index in materials array + integer :: j ! index over nuclides in material + integer :: i_nuclide ! index in nuclides array + character(20) :: name ! name of library to load + type(Material), pointer :: mat + type(SetChar) :: already_read + integer(HID_T) :: file_id + logical :: file_exists + type(VectorReal), allocatable, target :: temps(:) + character(MAX_WORD_LEN) :: word + integer, allocatable :: array(:) + + ! Check if MGXS Library exists + inquire(FILE=path_cross_sections, EXIST=file_exists) + if (.not. file_exists) then + + ! Could not find MGXS Library file + call fatal_error("Cross sections HDF5 file '" & + // trim(path_cross_sections) // "' does not exist!") + end if + + call write_message("Loading cross section data...", 5) + + ! Get temperatures + call get_temperatures(temps) + + ! Open file for reading + file_id = file_open(path_cross_sections, 'r', parallel=.true.) + + ! Read filetype + call read_attribute(word, file_id, "filetype") + if (word /= 'mgxs') then + call fatal_error("Provided MGXS Library is not a MGXS Library file.") + end if + + ! Read revision number for the MGXS Library file and make sure it matches + ! with the current version + call read_attribute(array, file_id, "version") + if (any(array /= VERSION_MGXS_LIBRARY)) then + call fatal_error("MGXS Library file version does not match current & + &version supported by OpenMC.") + end if + + ! ========================================================================== + ! READ ALL MGXS CROSS SECTION TABLES + + ! Loop over all files + MATERIAL_LOOP: do i = 1, n_materials + mat => materials(i) + + NUCLIDE_LOOP: do j = 1, mat % n_nuclides + name = trim(mat % names(j)) // C_NULL_CHAR + i_nuclide = mat % nuclide(j) + + if (.not. already_read % contains(name)) then + call add_mgxs_c(file_id, name, & + num_energy_groups, num_delayed_groups, & + temps(i_nuclide) % size(), temps(i_nuclide) % data, & + temperature_method, temperature_tolerance, max_order, & + logical(legendre_to_tabular, C_BOOL), legendre_to_tabular_points) + + call already_read % add(name) + end if + end do NUCLIDE_LOOP + + mat % fissionable = query_fissionable_c(mat % n_nuclides, mat % nuclide) + + end do MATERIAL_LOOP + + call file_close(file_id) + + ! Avoid some valgrind leak errors + call already_read % clear() + + end subroutine read_mgxs2 + !=============================================================================== ! CREATE_MACRO_XS generates the macroscopic xs from the microscopic input data !=============================================================================== diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index 5abeccbcf..be3e4e9d4 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -215,10 +215,10 @@ module mgxs_header type(MgxsContainer), target, allocatable :: macro_xs(:) ! Number of energy groups - integer :: num_energy_groups + integer(C_INT) :: num_energy_groups ! Number of delayed groups - integer :: num_delayed_groups + integer(C_INT) :: num_delayed_groups ! Energy group structure with decreasing energy real(8), allocatable :: energy_bins(:) diff --git a/src/scattdata.cpp b/src/scattdata.cpp index 8283febc8..cb72f4229 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -2,12 +2,6 @@ namespace openmc { -//============================================================================== -// Methods for use by all extended types -//============================================================================== - - - //============================================================================== // ScattData base-class methods //============================================================================== @@ -128,7 +122,7 @@ void ScattDataLegendre::init(int_1dvec in_gmin, int_1dvec in_gmax, // coefficient in the variable matrix over all outgoing groups. scattxs.resize(groups); for (int gin = 0; gin < groups; gin++) { - int num_groups = gmax[gin] - gmin[gin] + 1; + int num_groups = in_gmax[gin] - in_gmin[gin] + 1; scattxs[gin] = 0.; for (int i_gout = 0; i_gout < num_groups; i_gout++) { scattxs[gin] = std::accumulate(matrix[gin][i_gout].begin(), @@ -143,7 +137,7 @@ void ScattDataLegendre::init(int_1dvec in_gmin, int_1dvec in_gmax, double_2dvec in_energy; in_energy.resize(groups); for (int gin = 0; gin < groups; gin++) { - int num_groups = gmax[gin] - gmin[gin] + 1; + int num_groups = in_gmax[gin] - in_gmin[gin] + 1; in_energy[gin].resize(num_groups); for (int i_gout = 0; i_gout < num_groups; i_gout++) { double norm = matrix[gin][i_gout][0]; @@ -430,7 +424,7 @@ void ScattDataHistogram::init(int_1dvec in_gmin, int_1dvec in_gmax, double_2dvec in_energy; in_energy.resize(groups); for (int gin = 0; gin < groups; gin++) { - int num_groups = gmax[gin] - gmin[gin] + 1; + int num_groups = in_gmax[gin] - in_gmin[gin] + 1; in_energy[gin].resize(num_groups); for (int i_gout = 0; i_gout < num_groups; i_gout++) { double norm = std::accumulate(matrix[gin][i_gout].begin(), @@ -572,6 +566,128 @@ double_3dvec ScattDataHistogram::get_matrix(int max_order) return matrix; } + +void ScattDataHistogram::combine(std::vector those_scatts, + double_1dvec& scalars) +{ + int groups = energy.size(); + // Find the maximum order in the data set + int max_order = get_order(); + for (int i = 0; i < those_scatts.size(); i++) { + // Lets also make sure these items are combineable + ScattDataHistogram* that = dynamic_cast(those_scatts[i]); + if (!equiv(*that)) { + fatal_error("Cannot combine the ScattData objects!"); + } + int that_order = that->get_order(); + if (that_order > max_order) max_order = that_order; + } + max_order++; // Add one since this is a Legendre + + // Now allocate and zero our storage spaces + double_3dvec this_matrix = get_matrix(max_order); + double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); + double_2dvec mult_denom(groups, double_1dvec(groups, 0.)); + + // Build the dense scattering and multiplicity matrices + // Get the multiplicity_matrix + // To combine from nuclidic data we need to use the final relationship + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / + // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) + // Developed as follows: + // mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / + // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) + // nuscatt_{i,g,g'} can be reconstructed from the energy and scattxs member + // variables + for (int i = 0; i < those_scatts.size(); i++) { + ScattDataHistogram* that = dynamic_cast(those_scatts[i]); + + // Build the dense matrix for that object + double_3dvec that_matrix = that->get_matrix(max_order); + + // Now add that to this for the scattering and multiplicity + for (int gin = 0; gin < groups; gin++) { + // Only spend time adding that's gmin to gmax data since the rest will + // be zeros + for (int gout = that->gmin[gin]; gout <= that->gmax[gin]; gout++) { + // Do the scattering matrix + for (int l = 0; l < max_order; l++) { + this_matrix[gin][gout][l] += scalars[i] * that_matrix[gin][gout][l]; + } + + // Incorporate that's contribution to the multiplicity matrix data + double nuscatt = that->scattxs[gin] * that->energy[gin][gout]; + mult_numer[gin][gout] += scalars[i] * nuscatt; + if (that->mult[gin][gout] > 0.) { + mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][gout]; + } else { + mult_denom[gin][gout] += scalars[i]; + } + } + } + } + + // Combine mult_numer and mult_denom into the combined multiplicity matrix + double_2dvec this_mult(groups, double_1dvec(groups, 1.)); + for (int gin = 0; gin < groups; gin++) { + for (int gout = 0; gout < groups; gout++) { + if (mult_denom[gin][gout] > 0.) { + this_mult[gin][gout] = mult_numer[gin][gout] / mult_denom[gin][gout]; + } + } + } + mult_numer.clear(); + mult_denom.clear(); + + // We have the data, now we need to convert to a jagged array and then use + // the initialize function to store it on the object. + int_1dvec in_gmin(groups); + int_1dvec in_gmax(groups); + double_3dvec sparse_scatter(groups); + double_2dvec sparse_mult(groups); + for (int gin = 0; gin < groups; gin++) { + // Find the minimum and maximum group boundaries + int gmin_; + for (gmin_ = 0; gmin_ < groups; gmin_++) { + bool non_zero = std::all_of(this_matrix[gin][gmin_].begin(), + this_matrix[gin][gmin_].end(), + [](double val){return val > 0.;}); + if (non_zero) break; + } + int gmax_; + for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { + bool non_zero = std::all_of(this_matrix[gin][gmax_].begin(), + this_matrix[gin][gmax_].end(), + [](double val){return val > 0.;}); + if (non_zero) break; + } + + // treat the case of all values being 0 + if (gmin_ > gmax_) { + gmin_ = gin; + gmax_ = gin; + } + + // Store the group bounds + in_gmin[gin] = gmin_; + in_gmax[gin] = gmax_; + + // Store the data in the compressed format + sparse_scatter[gin].resize(gmax_ - gmin_ + 1); + sparse_mult[gin].resize(gmax_ - gmin_ + 1); + int i_gout = 0; + for (int gout = gmin_; gout <= gmax_; gout++) { + sparse_scatter[gin][i_gout] = this_matrix[gin][gout]; + sparse_mult[gin][i_gout] = this_mult[gin][gout]; + } + } + + // Got everything we need, store it. + init(in_gmin, in_gmax, sparse_mult, sparse_scatter); +} + //============================================================================== // ScattDataTabular methods //============================================================================== @@ -611,7 +727,7 @@ void ScattDataTabular::init(int_1dvec in_gmin, int_1dvec in_gmax, double_2dvec in_energy; in_energy.resize(groups); for (int gin = 0; gin < groups; gin++) { - int num_groups = gmax[gin] - gmin[gin] + 1; + int num_groups = in_gmax[gin] - in_gmin[gin] + 1; in_energy[gin].resize(num_groups); for (int i_gout = 0; i_gout < num_groups; i_gout++) { double norm = 0.; @@ -769,15 +885,132 @@ double_3dvec ScattDataTabular::get_matrix(int max_order) return matrix; } +void ScattDataTabular::combine(std::vector those_scatts, + double_1dvec& scalars) +{ + int groups = energy.size(); + // Find the maximum order in the data set + int max_order = get_order(); + for (int i = 0; i < those_scatts.size(); i++) { + // Lets also make sure these items are combineable + ScattDataTabular* that = dynamic_cast(those_scatts[i]); + if (!equiv(*that)) { + fatal_error("Cannot combine the ScattData objects!"); + } + int that_order = that->get_order(); + if (that_order > max_order) max_order = that_order; + } + max_order++; // Add one since this is a Legendre + + // Now allocate and zero our storage spaces + double_3dvec this_matrix = get_matrix(max_order); + double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); + double_2dvec mult_denom(groups, double_1dvec(groups, 0.)); + + // Build the dense scattering and multiplicity matrices + // Get the multiplicity_matrix + // To combine from nuclidic data we need to use the final relationship + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / + // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) + // Developed as follows: + // mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / + // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) + // nuscatt_{i,g,g'} can be reconstructed from the energy and scattxs member + // variables + for (int i = 0; i < those_scatts.size(); i++) { + ScattDataTabular* that = dynamic_cast(those_scatts[i]); + + // Build the dense matrix for that object + double_3dvec that_matrix = that->get_matrix(max_order); + + // Now add that to this for the scattering and multiplicity + for (int gin = 0; gin < groups; gin++) { + // Only spend time adding that's gmin to gmax data since the rest will + // be zeros + for (int gout = that->gmin[gin]; gout <= that->gmax[gin]; gout++) { + // Do the scattering matrix + for (int l = 0; l < max_order; l++) { + this_matrix[gin][gout][l] += scalars[i] * that_matrix[gin][gout][l]; + } + + // Incorporate that's contribution to the multiplicity matrix data + double nuscatt = that->scattxs[gin] * that->energy[gin][gout]; + mult_numer[gin][gout] += scalars[i] * nuscatt; + if (that->mult[gin][gout] > 0.) { + mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][gout]; + } else { + mult_denom[gin][gout] += scalars[i]; + } + } + } + } + + // Combine mult_numer and mult_denom into the combined multiplicity matrix + double_2dvec this_mult(groups, double_1dvec(groups, 1.)); + for (int gin = 0; gin < groups; gin++) { + for (int gout = 0; gout < groups; gout++) { + if (mult_denom[gin][gout] > 0.) { + this_mult[gin][gout] = mult_numer[gin][gout] / mult_denom[gin][gout]; + } + } + } + mult_numer.clear(); + mult_denom.clear(); + + // We have the data, now we need to convert to a jagged array and then use + // the initialize function to store it on the object. + int_1dvec in_gmin(groups); + int_1dvec in_gmax(groups); + double_3dvec sparse_scatter(groups); + double_2dvec sparse_mult(groups); + for (int gin = 0; gin < groups; gin++) { + // Find the minimum and maximum group boundaries + int gmin_; + for (gmin_ = 0; gmin_ < groups; gmin_++) { + bool non_zero = std::all_of(this_matrix[gin][gmin_].begin(), + this_matrix[gin][gmin_].end(), + [](double val){return val > 0.;}); + if (non_zero) break; + } + int gmax_; + for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { + bool non_zero = std::all_of(this_matrix[gin][gmax_].begin(), + this_matrix[gin][gmax_].end(), + [](double val){return val > 0.;}); + if (non_zero) break; + } + + // treat the case of all values being 0 + if (gmin_ > gmax_) { + gmin_ = gin; + gmax_ = gin; + } + + // Store the group bounds + in_gmin[gin] = gmin_; + in_gmax[gin] = gmax_; + + // Store the data in the compressed format + sparse_scatter[gin].resize(gmax_ - gmin_ + 1); + sparse_mult[gin].resize(gmax_ - gmin_ + 1); + int i_gout = 0; + for (int gout = gmin_; gout <= gmax_; gout++) { + sparse_scatter[gin][i_gout] = this_matrix[gin][gout]; + sparse_mult[gin][i_gout] = this_mult[gin][gout]; + } + } + + // Got everything we need, store it. + init(in_gmin, in_gmax, sparse_mult, sparse_scatter); +} + void convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, int n_mu) { - // Copy the obvious data - tab.energy = leg.energy; - tab.mult = leg.mult; - tab.gmin = leg.gmin; - tab.gmax = leg.gmax; + tab.generic_init(n_mu, leg.gmin, leg.gmax, leg.energy, leg.mult); // Build mu and dmu tab.mu = double_1dvec(n_mu); @@ -794,7 +1027,7 @@ void convert_legendre_to_tabular(ScattDataLegendre& leg, for (int gin = 0; gin < groups; gin++) { int num_groups = tab.gmax[gin] - tab.gmin[gin] + 1; tab.fmu[gin].resize(num_groups); - for (int i_gout = 0; i_gout < num_groups; i_gout) { + for (int i_gout = 0; i_gout < num_groups; i_gout++) { tab.fmu[gin][i_gout].resize(n_mu); for (int imu = 0; imu < n_mu; imu++) { tab.fmu[gin][i_gout][imu] = diff --git a/src/settings.F90 b/src/settings.F90 index aef1c011b..0ed51d119 100644 --- a/src/settings.F90 +++ b/src/settings.F90 @@ -18,9 +18,9 @@ module settings logical :: urr_ptables_on = .true. ! Default temperature and method for choosing temperatures - integer :: temperature_method = TEMPERATURE_NEAREST + integer(C_INT) :: temperature_method = TEMPERATURE_NEAREST logical :: temperature_multipole = .false. - real(8) :: temperature_tolerance = 10.0_8 + real(C_DOUBLE) :: temperature_tolerance = 10.0_8 real(8) :: temperature_default = 293.6_8 real(8) :: temperature_range(2) = [ZERO, ZERO] @@ -30,13 +30,16 @@ module settings ! MULTI-GROUP CROSS SECTION RELATED VARIABLES ! Maximum Data Order - integer :: max_order + integer(C_INT) :: max_order ! Whether or not to convert Legendres to tabulars logical :: legendre_to_tabular = .true. ! Number of points to use in the Legendre to tabular conversion - integer :: legendre_to_tabular_points = 33 + integer(C_INT) :: legendre_to_tabular_points = 33 + + ! ============================================================================ + ! SIMULATION VARIABLES ! Assume all tallies are spatially distinct logical :: assume_separate = .false. @@ -44,9 +47,6 @@ module settings ! Use confidence intervals for results instead of standard deviations logical :: confidence_intervals = .false. - ! ============================================================================ - ! SIMULATION VARIABLES - integer(C_INT64_T), bind(C) :: n_particles = 0 ! # of particles per generation integer(C_INT32_T), bind(C) :: n_batches ! # of batches integer(C_INT32_T), bind(C) :: n_inactive ! # of inactive batches diff --git a/src/string_functions.h b/src/string_functions.h index 94d4ce627..d44982bbd 100644 --- a/src/string_functions.h +++ b/src/string_functions.h @@ -12,39 +12,26 @@ namespace openmc { -void strtrim(char* str) +std::string& strtrim(std::string& s) { - int start = 0; // number of leading spaces - char* buffer = str; - - while (*str && *str++ == ' ') ++start; - - while (*str++); // move to end of string - - // backup over trailing spaces - int end = str - buffer - 1; - while (end > 0 && buffer[end - 1] == ' ') --end; - buffer[end] = 0; // remove trailing spaces - - // exit if no leading spaces or string is now empty - if (end <= start || start == 0) return; - str = buffer + start; - - while ((*buffer++ = *str++)); // remove leading spaces: K&R + const char* t = " \t\n\r\f\v"; + s.erase(s.find_last_not_of(t) + 1); + s.erase(0, s.find_first_not_of(t)); + return s; } -std::string strtrim(std::string in_str) + +char* strtrim(char* c_str) { - std::string str = in_str; - // perform the left trim - str.erase(str.begin(), std::find_if(str.begin(), str.end(), - std::not1(std::ptr_fun(std::isspace)))); - // perform the right trim - str.erase(std::find_if(str.rbegin(), str.rend(), - std::not1(std::ptr_fun(std::isspace))).base(), - str.end()); + std::string std_str; + std_str.assign(c_str); + strtrim(std_str); + int length = std_str.copy(c_str, std_str.size()); + c_str[length] = '\0'; + return c_str; } + void to_lower(std::string& str) { for (int i = 0; i < str.size(); i++) str[i] = std::tolower(str[i]); diff --git a/src/xsdata.cpp b/src/xsdata.cpp index a027b18dd..42e32baa3 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -41,7 +41,7 @@ XsData::XsData(int energy_groups, int num_delayed_groups, bool fissionable, // chi_prompt; [temperature][phi][theta][in group][delayed group] chi_prompt = double_4dvec(n_pol, double_3dvec(n_azi, - double_2dvec(energy_groups, double_1dvec(num_delayed_groups, 0.)))); + double_2dvec(energy_groups, double_1dvec(energy_groups, 0.)))); // chi_delayed; [temperature][phi][theta][in group][out group][delay group] chi_delayed = double_5dvec(n_pol, double_4dvec(n_azi, @@ -64,19 +64,10 @@ XsData::XsData(int energy_groups, int num_delayed_groups, bool fissionable, } } -XsData::~XsData() -{ - for (int p = 0; p < scatter.size(); p++) { - for (int a = 0; a < scatter[p].size(); a++) delete scatter[p][a]; - scatter[p].clear(); - } - scatter.clear(); -} - void XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, int final_scatter_format, int order_data, int max_order, - int legendre_to_tabular_points) + int legendre_to_tabular_points, bool is_isotropic) { // Reconstruct the dimension information so it doesn't need to be passed int n_pol = total.size(); @@ -87,7 +78,7 @@ void XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, // Set the fissionable-specific data if (fissionable) { _fissionable_from_hdf5(xsdata_grp, n_pol, n_azi, energy_groups, - delayed_groups); + delayed_groups, is_isotropic); } // Get the non-fission-specific data read_nd_vector(xsdata_grp, "decay_rate", decay_rate); @@ -104,9 +95,7 @@ void XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, for (int p = 0; p < n_pol; p++) { for (int a = 0; a < n_azi; a++) { for (int gin = 0; gin < energy_groups; gin++) { - if (absorption[gin][p][a] == 0.) { - absorption[p][a][gin] = 1.e-10; - } + if (absorption[p][a][gin] == 0.) absorption[p][a][gin] = 1.e-10; } } } @@ -138,7 +127,7 @@ void XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, - int energy_groups, int delayed_groups) + int energy_groups, int delayed_groups, bool is_isotropic) { double_4dvec temp_beta = double_4dvec(n_pol, double_3dvec(n_azi, @@ -149,6 +138,8 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, hid_t xsdata = open_dataset(xsdata_grp, "beta"); int ndims = dataset_ndims(xsdata); + if (is_isotropic) ndims += 2; + if (ndims == 3) { // Beta is input as [delayed group] double_1dvec temp_arr = double_1dvec(n_pol * n_azi * delayed_groups); @@ -171,7 +162,7 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, // Beta is input as [in group][delayed group] read_nd_vector(xsdata_grp, "beta", temp_beta); } else { - fatal_error("beta must be provided as a 1D or 2D array!"); + fatal_error("beta must be provided as a 3D or 4D array!"); } } @@ -181,12 +172,11 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, double_1dvec(energy_groups))); read_nd_vector(xsdata_grp, "chi", temp_arr); - int temp_idx = 0; for (int p = 0; p < n_pol; p++) { for (int a = 0; a < n_azi; a++) { // First set the first group for (int gout = 0; gout < energy_groups; gout++) { - chi_prompt[p][a][0][gout] = temp_arr[p][a][temp_idx++]; + chi_prompt[p][a][0][gout] = temp_arr[p][a][gout]; } // Now normalize this data @@ -224,6 +214,7 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, if (object_exists(xsdata_grp, "nu-fission")) { hid_t xsdata = open_dataset(xsdata_grp, "nu-fission"); int ndims = dataset_ndims(xsdata); + if (is_isotropic) ndims += 2; if (ndims == 3) { // nu-fission is a 3-d array @@ -303,7 +294,7 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, } } } else { - fatal_error("beta must be provided as a 3D or 4D array!"); + fatal_error("nu-fission must be provided as a 3D or 4D array!"); } close_dataset(xsdata); @@ -341,6 +332,7 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, if (object_exists(xsdata_grp, "chi-delayed")) { hid_t xsdata = open_dataset(xsdata_grp, "chi-delayed"); int ndims = dataset_ndims(xsdata); + if (is_isotropic) ndims += 2; close_dataset(xsdata); if (ndims == 3) { @@ -403,6 +395,7 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, if (object_exists(xsdata_grp, "prompt-nu-fission")) { hid_t xsdata = open_dataset(xsdata_grp, "prompt-nu-fission"); int ndims = dataset_ndims(xsdata); + if (is_isotropic) ndims += 2; close_dataset(xsdata); if (ndims == 3) { @@ -449,6 +442,7 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, if (object_exists(xsdata_grp, "delayed-nu-fission")) { hid_t xsdata = open_dataset(xsdata_grp, "delayed-nu-fission"); int ndims = dataset_ndims(xsdata); + if (is_isotropic) ndims += 2; if (ndims == 3) { // delayed-nu-fission is a [in group] vector @@ -473,29 +467,6 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, } } else if (ndims == 4) { - // delayed nu fission is a [pol][azi][energy_group][delayed_group] matrix; - // matrix use this to set delayed-nu-fission separately for each - // delayed group - std::vector dims(ndims); - get_shape(xsdata, &dims[0]); - - if (dims[2] != delayed_groups) { - fatal_error("The delayed-nu-fission matrix was input with a 1st " - "dimension not equal to the number of delayed groups"); - } - if (dims[3] != energy_groups) { - fatal_error("The delayed-nu-fission matrix was input with a 2nd " - "dimension not equal to the number of energy groups"); - } - if (delayed_groups == energy_groups) { - warning("delayed-nu-fission was input as a dimension-4 matrix " - "with the same number of delayed groups and energy " - "groups. OpenMC assumes the dimensions in the matrix " - "are [delayed_groups][energy_groups]. Currently, " - "delayed-nu-fission cannot be set as a group-by-group " - "matrix"); - } - read_nd_vector(xsdata_grp, "delayed-nu-fission", delayed_nu_fission); diff --git a/src/xsdata.h b/src/xsdata.h index 1c1f8241b..fec28aece 100644 --- a/src/xsdata.h +++ b/src/xsdata.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "constants.h" #include "hdf5_interface.h" @@ -31,7 +32,7 @@ class XsData { int energy_groups, int scatter_format, int final_scatter_format, int order_data, int max_order, int legendre_to_tabular_points); void _fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, - int energy_groups, int delayed_groups); + int energy_groups, int delayed_groups, bool is_isotropic); public: // The following quantities have the following dimensions: // [phi][theta][incoming group] @@ -59,10 +60,9 @@ class XsData { XsData() = default; XsData(int num_groups, int num_delayed_groups, bool fissionable, int scatter_format, int n_pol, int n_azi); - ~XsData(); void from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, int final_scatter_format, int order_data, int max_order, - int legendre_to_tabular_points); + int legendre_to_tabular_points, bool is_isotropic); void combine(std::vector those_xs, double_1dvec& scalars); bool equiv(const XsData& that); }; From ce919a0ad85b9ee0023ded1224bb05686edb1e94 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sat, 9 Jun 2018 08:53:40 -0400 Subject: [PATCH 319/361] extended to being able to combine microscopic data into macroscopic. Seems to work, next step is to actually incorporate the C++ data into the transport and tallying process. That will be the true test --- src/input_xml.F90 | 3 +- src/material_header.F90 | 2 +- src/mgxs.cpp | 84 ++++++++++++++++------------ src/mgxs.h | 12 ++-- src/mgxs_data.F90 | 47 ++++++++++++++++ src/scattdata.cpp | 119 +++++++++++++++++----------------------- src/scattdata.h | 19 +++---- src/xsdata.cpp | 67 +++++++++++----------- 8 files changed, 199 insertions(+), 154 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 8d4c8de81..507bdf8a9 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -19,7 +19,7 @@ module input_xml use material_header use mesh_header use message_passing - use mgxs_data, only: create_macro_xs, read_mgxs, read_mgxs2 + use mgxs_data, only: create_macro_xs, read_mgxs, read_mgxs2, create_macro_xs2 use mgxs_header use nuclide_header use output, only: title, header, print_plot @@ -86,6 +86,7 @@ contains call read_mgxs() call read_mgxs2() call create_macro_xs() + call create_macro_xs2() end if call time_read_xs % stop() end if diff --git a/src/material_header.F90 b/src/material_header.F90 index 038e529be..7fbfe553b 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -34,7 +34,7 @@ module material_header integer :: n_nuclides = 0 ! number of nuclides integer, allocatable :: nuclide(:) ! index in nuclides array real(8) :: density ! total atom density in atom/b-cm - real(8), allocatable :: atom_density(:) ! nuclide atom density in atom/b-cm + real(C_DOUBLE), 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 diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 9c11da8ac..35ac42f17 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -266,11 +266,14 @@ void Mgxs::from_hdf5(hid_t xs_id, int energy_groups, int delayed_groups, close_group(xsdata_grp); } // end temperature loop + + // Make sure the scattering format is updated to the final case + scatter_format = final_scatter_format; } void Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, - std::vector& micros, double_1dvec& atom_densities, + std::vector& micros, double_1dvec& atom_densities, int& method, double tolerance) { // Get the minimum data needed to initialize: @@ -279,21 +282,25 @@ void Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, // start with the assumption it is not fissionable bool in_fissionable = false; for (int m = 0; m < micros.size(); m++) { - if (micros[m].fissionable) in_fissionable = true; + if (micros[m]->fissionable) in_fissionable = true; } // Force all of the following data to be the same; these will be verified // to be true later - int in_scatter_format = micros[0].scatter_format; - int in_num_groups = micros[0].num_groups; - int in_num_delayed_groups = micros[0].num_delayed_groups; - double_1dvec in_polar = micros[0].polar; - double_1dvec in_azimuthal = micros[0].azimuthal; + int in_scatter_format = micros[0]->scatter_format; + int in_num_groups = micros[0]->num_groups; + int in_num_delayed_groups = micros[0]->num_delayed_groups; + double_1dvec in_polar = micros[0]->polar; + double_1dvec in_azimuthal = micros[0]->azimuthal; - init(in_name, in_awr, mat_kTs, in_fissionable, in_scatter_format, in_num_groups, - in_num_delayed_groups, in_polar, in_azimuthal); + init(in_name, in_awr, mat_kTs, in_fissionable, in_scatter_format, + in_num_groups, in_num_delayed_groups, in_polar, in_azimuthal); // Create the xs data for each temperature for (int t = 0; t < mat_kTs.size(); t++) { + xs[t]= XsData(in_num_groups, in_num_delayed_groups, in_fissionable, + in_scatter_format, in_polar.size(), in_azimuthal.size()); + + // Find the right temperature index to use double temp_desired = mat_kTs[t]; // Create the list of temperature indices and interpolation factors for @@ -305,13 +312,13 @@ void Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, case TEMPERATURE_NEAREST: { // Find the nearest temperature - std::valarray temp_diff(micros[m].kTs.data(), - micros[m].kTs.size()); + std::valarray temp_diff(micros[m]->kTs.data(), + micros[m]->kTs.size()); temp_diff = std::abs(temp_diff - temp_desired); micro_t[m] = std::min_element(std::begin(temp_diff), std::end(temp_diff)) - std::begin(temp_diff); - double temp_actual = micros[m].kTs[micro_t[m]]; + double temp_actual = micros[m]->kTs[micro_t[m]]; if (std::abs(temp_actual - temp_desired) >= K_BOLTZMANN * tolerance) { fatal_error("MGXS Library does not contain cross section for " + @@ -324,13 +331,13 @@ void Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, case TEMPERATURE_INTERPOLATION: // Get a list of bounding temperatures for each actual temperature // present in the model - for (int k = 0; k < micros[m].kTs.size() - 1; k++) { - if ((micros[m].kTs[k] <= temp_desired) && - (temp_desired < micros[m].kTs[k + 1])) { + for (int k = 0; k < micros[m]->kTs.size() - 1; k++) { + if ((micros[m]->kTs[k] <= temp_desired) && + (temp_desired < micros[m]->kTs[k + 1])) { micro_t[m] = k; if (k == 0) { - micro_t_interp[m] = (temp_desired - micros[m].kTs[k]) / - (micros[m].kTs[k + 1] - micros[m].kTs[k]); + micro_t_interp[m] = (temp_desired - micros[m]->kTs[k]) / + (micros[m]->kTs[k + 1] - micros[m]->kTs[k]); } else { micro_t_interp[m] = 1.; } @@ -353,23 +360,23 @@ void Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, interp[m] = (1. - micro_t_interp[m]) * atom_densities[m]; temp_indices[m] = micro_t[m] + interp_point; } + combine(micros, interp, micro_t, t); } // end loop to sum all micros across the temperatures } // end temperature (t) loop - } -void Mgxs::combine(std::vector& micros, double_1dvec& scalars, +void Mgxs::combine(std::vector& micros, double_1dvec& scalars, int_1dvec& micro_ts, int this_t) { // Build the vector of pointers to the xs objects within micros std::vector those_xs(micros.size()); for (int i = 0; i < micros.size(); i++) { - if (!xs[this_t].equiv(micros[i].xs[micro_ts[i]])) { + if (!xs[this_t].equiv(micros[i]->xs[micro_ts[i]])) { fatal_error("Cannot combine the Mgxs objects!"); } - those_xs[i] = &(micros[i].xs[micro_ts[i]]); + those_xs[i] = &(micros[i]->xs[micro_ts[i]]); } xs[this_t].combine(those_xs, scalars); @@ -646,24 +653,31 @@ bool query_fissionable(const int n_nuclides, const int i_nuclides[]) } -void create_macro_xs(int n_materials, double_2dvec& mat_kTs, - std::vector& mat_names, - double_1dvec& atom_densities, int& method, - double tolerance) +void create_macro_xs(char* mat_name, const int n_nuclides, + const int i_nuclides[], const int n_temps, const double temps[], + const double atom_densities[], int& method, const double tolerance) { - // TODO mat_kTs needs to be converted from Fortran - // it is currently an array of type(VectorReal), a wrapper should convert to - // the vector. - macro_xs.resize(n_materials); + Mgxs macro; + if (n_temps > 0) { + // // Convert temps to a vector + double_1dvec temperature; + temperature.assign(temps, temps + n_temps); - for (int m = 0; m < n_materials; m++) - { - if (mat_kTs[m].size() > 0) { - macro_xs[m].build_macro(mat_names[m], mat_kTs[m], nuclides_MG, - atom_densities, method, tolerance); + // Convert atom_densities to a vector + double_1dvec atom_densities_vec; + atom_densities_vec.assign(atom_densities, atom_densities + n_nuclides); + + // Build array of pointers to nuclides_MG's Mgxs objects needed for this + // material + std::vector mgxs_ptr(n_nuclides); + for (int n = 0; n < n_nuclides; n++) { + mgxs_ptr[n] = &nuclides_MG[i_nuclides[n] - 1]; } + + macro.build_macro(mat_name, temperature, mgxs_ptr, atom_densities_vec, + method, tolerance); } + macro_xs.push_back(macro); } - } // namespace openmc \ No newline at end of file diff --git a/src/mgxs.h b/src/mgxs.h index 8c13cf7cb..90ed520d9 100644 --- a/src/mgxs.h +++ b/src/mgxs.h @@ -58,9 +58,9 @@ class Mgxs { const int in_num_delayed_groups, const double_1dvec& in_polar, const double_1dvec& in_azimuthal); void build_macro(const std::string& in_name, double_1dvec& mat_kTs, - std::vector& micros, double_1dvec& atom_densities, + std::vector& micros, double_1dvec& atom_densities, int& method, double tolerance); - void combine(std::vector& micros, double_1dvec& scalars, + void combine(std::vector& micros, double_1dvec& scalars, int_1dvec& micro_ts, int this_t); void from_hdf5(hid_t xs_id, int energy_groups, int delayed_groups, double_1dvec& temperature, int& method, double tolerance, @@ -82,10 +82,12 @@ extern "C" void add_mgxs(hid_t file_id, char* name, int energy_groups, int delayed_groups, int n_temps, double temps[], int& method, double tolerance, int max_order, bool legendre_to_tabular, int legendre_to_tabular_points); + extern "C" bool query_fissionable(const int n_nuclides, const int i_nuclides[]); -void create_macro_xs(int n_materials, double_2dvec& mat_kTs, - std::vector& mat_names, double_1dvec& atom_densities, - int& method, double tolerance); + +extern "C" void create_macro_xs(char* mat_name, const int n_nuclides, + const int i_nuclides[], const int n_temps, const double temps[], + const double atom_densities[], int& method, const double tolerance); // Storage for the MGXS data diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index 6be463a91..a61bb65a2 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -45,6 +45,20 @@ module mgxs_data integer(C_INT), intent(in) :: i_nuclides(1:n_nuclides) logical(C_BOOL) :: result end function query_fissionable_c + + subroutine create_macro_xs_c(name, n_nuclides, i_nuclides, n_temps, temps, & + atom_densities, method, tolerance) bind(C, name='create_macro_xs') + use ISO_C_BINDING + implicit none + character(kind=C_CHAR),intent(in) :: name(*) + integer(C_INT), value, intent(in) :: n_nuclides + integer(C_INT), intent(in) :: i_nuclides(1:n_nuclides) + integer(C_INT), value, intent(in) :: n_temps + real(C_DOUBLE), intent(in) :: temps(1:n_temps) + real(C_DOUBLE), intent(in) :: atom_densities(1:n_nuclides) + integer(C_INT), intent(inout) :: method + real(C_DOUBLE), value, intent(in) :: tolerance + end subroutine create_macro_xs_c end interface contains @@ -316,6 +330,39 @@ contains end subroutine create_macro_xs + + subroutine create_macro_xs2() + integer :: i_mat ! index in materials array + type(Material), pointer :: mat ! current material + type(VectorReal), allocatable :: kTs(:) + character(MAX_WORD_LEN) :: name ! name of material + + ! Get temperatures to read for each material + call get_mat_kTs(kTs) + + ! Force all nuclides in a material to be the same representation. + ! Therefore type(nuclides(mat % nuclide(1)) % obj) dictates type(macroxs). + ! At the same time, we will find the scattering type, as that will dictate + ! how we allocate the scatter object within macroxs.allocate(macro_xs(n_materials)) + do i_mat = 1, n_materials + + ! Get the material + mat => materials(i_mat) + + name = trim(mat % name) // C_NULL_CHAR + + ! Do not read materials which we do not actually use in the problem to + ! reduce storage + if (allocated(kTs(i_mat) % data)) then + call create_macro_xs_c(name, mat % n_nuclides, mat % nuclide, & + kTs(i_mat) % size(), kTs(i_mat) % data, mat % atom_density, & + temperature_method, temperature_tolerance) + end if + end do + + end subroutine create_macro_xs2 + + !=============================================================================== ! GET_MAT_kTs returns a list of temperatures (in eV) that each ! material appears at in the model. diff --git a/src/scattdata.cpp b/src/scattdata.cpp index cb72f4229..dc4d169a7 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -109,11 +109,11 @@ double ScattData::get_xs(const char* xstype, int gin, int* gout, double* mu) // ScattDataLegendre methods //============================================================================== -void ScattDataLegendre::init(int_1dvec in_gmin, int_1dvec in_gmax, - double_2dvec in_mult, double_3dvec coeffs) +void ScattDataLegendre::init(int_1dvec& in_gmin, int_1dvec& in_gmax, + double_2dvec& in_mult, double_3dvec& coeffs) { int groups = coeffs.size(); - int order = coeffs[0].size(); + int order = coeffs[0][0].size(); // make a copy of coeffs that we can use to both extract data and normalize double_3dvec matrix = coeffs; @@ -250,13 +250,12 @@ void ScattDataLegendre::sample(int gin, int& gout, double& mu, double& wgt) void ScattDataLegendre::combine(std::vector those_scatts, double_1dvec& scalars) { - int groups = energy.size(); - // Find the maximum order in the data set - int max_order = get_order(); + // Find the max order in the data set and make sure we can combine the sets + int max_order = 0; for (int i = 0; i < those_scatts.size(); i++) { // Lets also make sure these items are combineable ScattDataLegendre* that = dynamic_cast(those_scatts[i]); - if (!equiv(*that)) { + if (!that) { fatal_error("Cannot combine the ScattData objects!"); } int that_order = that->get_order(); @@ -264,6 +263,9 @@ void ScattDataLegendre::combine(std::vector those_scatts, } max_order++; // Add one since this is a Legendre + // Get the groups as a shorthand + int groups = dynamic_cast(those_scatts[0])->energy.size(); + // Now allocate and zero our storage spaces double_3dvec this_matrix = get_matrix(max_order); double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); @@ -369,23 +371,16 @@ void ScattDataLegendre::combine(std::vector those_scatts, } -bool ScattDataLegendre::equiv(const ScattDataLegendre& that) -{ - // ensure that the number of groups match - return (this->energy.size() == that.energy.size()); -} - - double_3dvec ScattDataLegendre::get_matrix(int max_order) { // Get the sizes and initialize the data to 0 int groups = energy.size(); int order_dim = max_order + 1; - double_3dvec matrix = double_3dvec(groups, double_2dvec(order_dim, + double_3dvec matrix = double_3dvec(groups, double_2dvec(groups, double_1dvec(order_dim, 0.))); for (int gin = 0; gin < groups; gin++) { - for (int i_gout = 0; i_gout < energy[0].size(); i_gout++) { + for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) { int gout = i_gout + gmin[gin]; for (int l = 0; l < order_dim; l++) { matrix[gin][gout][l] = scattxs[gin] * energy[gin][i_gout] * @@ -400,11 +395,11 @@ double_3dvec ScattDataLegendre::get_matrix(int max_order) // ScattDataHistogram methods //============================================================================== -void ScattDataHistogram::init(int_1dvec in_gmin, int_1dvec in_gmax, - double_2dvec in_mult, double_3dvec coeffs) +void ScattDataHistogram::init(int_1dvec& in_gmin, int_1dvec& in_gmax, + double_2dvec& in_mult, double_3dvec& coeffs) { int groups = coeffs.size(); - int order = coeffs[0].size(); + int order = coeffs[0][0].size(); // make a copy of coeffs that we can use to both extract data and normalize double_3dvec matrix = coeffs; @@ -452,7 +447,7 @@ void ScattDataHistogram::init(int_1dvec in_gmin, int_1dvec in_gmax, for (int gin = 0; gin < groups; gin++) { int num_groups = gmax[gin] - gmin[gin] + 1; fmu[gin].resize(num_groups); - for (int i_gout = 0; i_gout < num_groups; i_gout) { + for (int i_gout = 0; i_gout < num_groups; i_gout++) { fmu[gin][i_gout].resize(order); // The variable matrix contains f(mu); so directly assign it fmu[gin][i_gout] = matrix[gin][i_gout]; @@ -533,29 +528,17 @@ void ScattDataHistogram::sample(int gin, int& gout, double& mu, double& wgt) } -bool ScattDataHistogram::equiv(const ScattDataHistogram& that) -{ - bool match = false; - if (this->energy.size() == that.energy.size() && - this->dmu == that.dmu && - std::equal(this->mu.begin(), this->mu.end(), that.mu.begin())) { - match = true; - } - return match; -} - - double_3dvec ScattDataHistogram::get_matrix(int max_order) { // Get the sizes and initialize the data to 0 int groups = energy.size(); // We ignore the requested order for Histogram and Tabular representations int order_dim = get_order(); - double_3dvec matrix = double_3dvec(groups, double_2dvec(order_dim, + double_3dvec matrix = double_3dvec(groups, double_2dvec(groups, double_1dvec(order_dim, 0.))); for (int gin = 0; gin < groups; gin++) { - for (int i_gout = 0; i_gout < energy[0].size(); i_gout++) { + for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) { int gout = i_gout + gmin[gin]; for (int l = 0; l < order_dim; l++) { matrix[gin][gout][l] = scattxs[gin] * energy[gin][i_gout] * @@ -570,19 +553,23 @@ double_3dvec ScattDataHistogram::get_matrix(int max_order) void ScattDataHistogram::combine(std::vector those_scatts, double_1dvec& scalars) { - int groups = energy.size(); - // Find the maximum order in the data set - int max_order = get_order(); + // Find the max order in the data set and make sure we can combine the sets + int max_order; for (int i = 0; i < those_scatts.size(); i++) { // Lets also make sure these items are combineable ScattDataHistogram* that = dynamic_cast(those_scatts[i]); - if (!equiv(*that)) { + if (!that) { + fatal_error("Cannot combine the ScattData objects!"); + } + if (i == 0) { + max_order = that->get_order(); + } else if (max_order != that->get_order()) { fatal_error("Cannot combine the ScattData objects!"); } - int that_order = that->get_order(); - if (that_order > max_order) max_order = that_order; } - max_order++; // Add one since this is a Legendre + + // Get the groups as a shorthand + int groups = dynamic_cast(those_scatts[0])->energy.size(); // Now allocate and zero our storage spaces double_3dvec this_matrix = get_matrix(max_order); @@ -692,11 +679,11 @@ void ScattDataHistogram::combine(std::vector those_scatts, // ScattDataTabular methods //============================================================================== -void ScattDataTabular::init(int_1dvec in_gmin, int_1dvec in_gmax, - double_2dvec in_mult, double_3dvec coeffs) +void ScattDataTabular::init(int_1dvec& in_gmin, int_1dvec& in_gmax, + double_2dvec& in_mult, double_3dvec& coeffs) { int groups = coeffs.size(); - int order = coeffs[0].size(); + int order = coeffs[0][0].size(); // make a copy of coeffs that we can use to both extract data and normalize double_3dvec matrix = coeffs; @@ -742,15 +729,14 @@ void ScattDataTabular::init(int_1dvec in_gmin, int_1dvec in_gmax, } // Initialize the base class attributes - ScattData::generic_init(order, in_gmin, in_gmax, in_energy, - in_mult); + ScattData::generic_init(order, in_gmin, in_gmax, in_energy, in_mult); // Calculate f(mu) and integrate it so we can avoid rejection sampling fmu.resize(groups); for (int gin = 0; gin < groups; gin++) { int num_groups = gmax[gin] - gmin[gin] + 1; fmu[gin].resize(num_groups); - for (int i_gout = 0; i_gout < num_groups; i_gout) { + for (int i_gout = 0; i_gout < num_groups; i_gout++) { fmu[gin][i_gout].resize(order); // The variable matrix contains f(mu); so directly assign it fmu[gin][i_gout] = matrix[gin][i_gout]; @@ -852,29 +838,17 @@ void ScattDataTabular::sample(int gin, int& gout, double& mu, double& wgt) } -bool ScattDataTabular::equiv(const ScattDataTabular& that) -{ - bool match = false; - if (this->energy.size() == that.energy.size() && - this->dmu == that.dmu && - std::equal(this->mu.begin(), this->mu.end(), that.mu.begin())) { - match = true; - } - return match; -} - - double_3dvec ScattDataTabular::get_matrix(int max_order) { // Get the sizes and initialize the data to 0 int groups = energy.size(); // We ignore the requested order for Histogram and Tabular representations int order_dim = get_order(); - double_3dvec matrix = double_3dvec(groups, double_2dvec(order_dim, + double_3dvec matrix = double_3dvec(groups, double_2dvec(groups, double_1dvec(order_dim, 0.))); for (int gin = 0; gin < groups; gin++) { - for (int i_gout = 0; i_gout < energy[0].size(); i_gout++) { + for (int i_gout = 0; i_gout < energy[gin].size(); i_gout++) { int gout = i_gout + gmin[gin]; for (int l = 0; l < order_dim; l++) { matrix[gin][gout][l] = scattxs[gin] * energy[gin][i_gout] * @@ -888,22 +862,27 @@ double_3dvec ScattDataTabular::get_matrix(int max_order) void ScattDataTabular::combine(std::vector those_scatts, double_1dvec& scalars) { - int groups = energy.size(); - // Find the maximum order in the data set - int max_order = get_order(); + // Find the max order in the data set and make sure we can combine the sets + int max_order; for (int i = 0; i < those_scatts.size(); i++) { // Lets also make sure these items are combineable ScattDataTabular* that = dynamic_cast(those_scatts[i]); - if (!equiv(*that)) { + if (!that) { + fatal_error("Cannot combine the ScattData objects!"); + } + if (i == 0) { + max_order = that->get_order(); + } else if (max_order != that->get_order()) { fatal_error("Cannot combine the ScattData objects!"); } - int that_order = that->get_order(); - if (that_order > max_order) max_order = that_order; } - max_order++; // Add one since this is a Legendre + + // Get the groups as a shorthand + int groups = dynamic_cast(those_scatts[0])->energy.size(); // Now allocate and zero our storage spaces - double_3dvec this_matrix = get_matrix(max_order); + double_3dvec this_matrix = double_3dvec(groups, double_2dvec(groups, + double_1dvec(max_order, 0.))); double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); double_2dvec mult_denom(groups, double_1dvec(groups, 0.)); @@ -999,6 +978,7 @@ void ScattDataTabular::combine(std::vector those_scatts, for (int gout = gmin_; gout <= gmax_; gout++) { sparse_scatter[gin][i_gout] = this_matrix[gin][gout]; sparse_mult[gin][i_gout] = this_mult[gin][gout]; + i_gout++; } } @@ -1011,6 +991,7 @@ void convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, int n_mu) { tab.generic_init(n_mu, leg.gmin, leg.gmax, leg.energy, leg.mult); + tab.scattxs = leg.scattxs; // Build mu and dmu tab.mu = double_1dvec(n_mu); diff --git a/src/scattdata.h b/src/scattdata.h index 787a8f005..6e24a50bc 100644 --- a/src/scattdata.h +++ b/src/scattdata.h @@ -35,8 +35,8 @@ class ScattData { double_1dvec scattxs; // Isotropic Sigma_{s,g_{in}} virtual double calc_f(int gin, int gout, double mu) = 0; virtual void sample(int gin, int& gout, double& mu, double& wgt) = 0; - virtual void init(int_1dvec in_gmin, int_1dvec in_gmax, - double_2dvec in_mult, double_3dvec coeffs) = 0; + virtual void init(int_1dvec& in_gmin, int_1dvec& in_gmax, + double_2dvec& in_mult, double_3dvec& coeffs) = 0; void sample_energy(int gin, int& gout, int& i_gout); double get_xs(const char* xstype, int gin, int* gout, double* mu); void generic_init(int order, int_1dvec in_gmin, int_1dvec in_gmax, @@ -54,12 +54,11 @@ class ScattDataLegendre: public ScattData { friend void convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, int n_mu); public: - void init(int_1dvec in_gmin, int_1dvec in_gmax, double_2dvec in_mult, - double_3dvec coeffs); + void init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, + double_3dvec& coeffs); void update_max_val(); double calc_f(int gin, int gout, double mu); void sample(int gin, int& gout, double& mu, double& wgt); - bool equiv(const ScattDataLegendre& that); void combine(std::vector those_scatts, double_1dvec& scalars); int get_order() {return dist[0][0].size() - 1;}; double_3dvec get_matrix(int max_order); @@ -71,12 +70,11 @@ class ScattDataHistogram: public ScattData { double dmu; double_3dvec fmu; public: - void init(int_1dvec in_gmin, int_1dvec in_gmax, double_2dvec in_mult, - double_3dvec coeffs); + void init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, + double_3dvec& coeffs); double calc_f(int gin, int gout, double mu); void sample(int gin, int& gout, double& mu, double& wgt); void combine(std::vector those_scatts, double_1dvec& scalars); - bool equiv(const ScattDataHistogram& that); int get_order() {return dist[0][0].size();}; double_3dvec get_matrix(int max_order); }; @@ -89,12 +87,11 @@ class ScattDataTabular: public ScattData { friend void convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, int n_mu); public: - void init(int_1dvec in_gmin, int_1dvec in_gmax, double_2dvec in_mult, - double_3dvec coeffs); + void init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, + double_3dvec& coeffs); double calc_f(int gin, int gout, double mu); void sample(int gin, int& gout, double& mu, double& wgt); void combine(std::vector those_scatts, double_1dvec& scalars); - bool equiv(const ScattDataTabular& that); int get_order() {return dist[0][0].size();}; double_3dvec get_matrix(int max_order); }; diff --git a/src/xsdata.cpp b/src/xsdata.cpp index 42e32baa3..49b07453d 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -666,26 +666,27 @@ void XsData::combine(std::vector those_xs, double_1dvec& scalars) absorption[p][a][gin] += scalar * that->absorption[p][a][gin]; inverse_velocity[p][a][gin] += scalar * that->inverse_velocity[p][a][gin]; + if (that->prompt_nu_fission.size() > 0) { + prompt_nu_fission[p][a][gin] += + scalar * that->prompt_nu_fission[p][a][gin]; + kappa_fission[p][a][gin] += + scalar * that->kappa_fission[p][a][gin]; + fission[p][a][gin] += + scalar * that->fission[p][a][gin]; - prompt_nu_fission[p][a][gin] += - scalar * that->prompt_nu_fission[p][a][gin]; - kappa_fission[p][a][gin] += - scalar * that->kappa_fission[p][a][gin]; - fission[p][a][gin] += - scalar * that->fission[p][a][gin]; + for (int dg = 0; dg < delayed_nu_fission[p][a][gin].size(); dg++) { + delayed_nu_fission[p][a][gin][dg] += + scalar * that->delayed_nu_fission[p][a][gin][dg]; + } - for (int dg = 0; dg < delayed_nu_fission[p][a][gin].size(); dg++) { - delayed_nu_fission[p][a][gin][dg] += - scalar * that->delayed_nu_fission[p][a][gin][dg]; - } + for (int gout = 0; gout < chi_prompt[p][a][gin].size(); gout++) { + chi_prompt[p][a][gin][gout] += + scalar * that->chi_prompt[p][a][gin][gout]; - for (int gout = 0; gout < chi_prompt[p][a][gin].size(); gout++) { - chi_prompt[p][a][gin][gout] += - scalar * that->chi_prompt[p][a][gin][gout]; - - for (int dg = 0; dg < chi_delayed[p][a][gin][gout].size(); dg++) { - chi_delayed[p][a][gin][gout][dg] += - scalar * that->chi_delayed[p][a][gin][gout][dg]; + for (int dg = 0; dg < chi_delayed[p][a][gin][gout].size(); dg++) { + chi_delayed[p][a][gin][gout][dg] += + scalar * that->chi_delayed[p][a][gin][gout][dg]; + } } } } @@ -695,23 +696,25 @@ void XsData::combine(std::vector those_xs, double_1dvec& scalars) } // Normalize chi - for (int gin = 0; gin < chi_prompt[p][a].size(); gin++) { - double norm = std::accumulate(chi_prompt[p][a][gin].begin(), - chi_prompt[p][a][gin].end(), 0.); - if (norm > 0.) { - for (int gout = 0; gout < chi_prompt[p][a][gin].size(); gout++) { - chi_prompt[p][a][gin][gout] /= norm; - } - } - - for (int dg = 0; dg < chi_delayed[p][a][gin][0].size(); dg++) { - norm = 0.; - for (int gout = 0; gout < chi_delayed[p][a][gin].size(); gout++) { - norm += chi_delayed[p][a][gin][gout][dg]; - } + if (chi_prompt.size() > 0) { + for (int gin = 0; gin < chi_prompt[p][a].size(); gin++) { + double norm = std::accumulate(chi_prompt[p][a][gin].begin(), + chi_prompt[p][a][gin].end(), 0.); if (norm > 0.) { + for (int gout = 0; gout < chi_prompt[p][a][gin].size(); gout++) { + chi_prompt[p][a][gin][gout] /= norm; + } + } + + for (int dg = 0; dg < chi_delayed[p][a][gin][0].size(); dg++) { + norm = 0.; for (int gout = 0; gout < chi_delayed[p][a][gin].size(); gout++) { - chi_delayed[p][a][gin][gout][dg] /= norm; + norm += chi_delayed[p][a][gin][gout][dg]; + } + if (norm > 0.) { + for (int gout = 0; gout < chi_delayed[p][a][gin].size(); gout++) { + chi_delayed[p][a][gin][gout][dg] /= norm; + } } } } From 0054759c3a51e5e0221e799dbfeca4c00141a18f Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sat, 9 Jun 2018 08:54:21 -0400 Subject: [PATCH 320/361] fixed line indentation so we can see if travis works with this --- src/mgxs_data.F90 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index a61bb65a2..697737880 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -268,10 +268,10 @@ contains if (.not. already_read % contains(name)) then call add_mgxs_c(file_id, name, & - num_energy_groups, num_delayed_groups, & - temps(i_nuclide) % size(), temps(i_nuclide) % data, & - temperature_method, temperature_tolerance, max_order, & - logical(legendre_to_tabular, C_BOOL), legendre_to_tabular_points) + num_energy_groups, num_delayed_groups, & + temps(i_nuclide) % size(), temps(i_nuclide) % data, & + temperature_method, temperature_tolerance, max_order, & + logical(legendre_to_tabular, C_BOOL), legendre_to_tabular_points) call already_read % add(name) end if From 59c4bc3fc4e4a2f3cf5345d240a18db7925ccc1f Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 10 Jun 2018 05:53:22 -0400 Subject: [PATCH 321/361] fixing bugs found from the mg_basic test which exercises many more Mgxs data formats --- src/mgxs.cpp | 4 ++-- src/scattdata.cpp | 6 ++++-- src/xsdata.cpp | 17 +++++++++-------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 35ac42f17..42df45ef0 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -646,8 +646,8 @@ void add_mgxs(hid_t file_id, char* name, int energy_groups, bool query_fissionable(const int n_nuclides, const int i_nuclides[]) { bool result = false; - for (int i = 0; i < n_nuclides; i++) { - if (nuclides_MG[i_nuclides[i]].fissionable) result = true; + for (int n = 0; n < n_nuclides; n++) { + if (nuclides_MG[i_nuclides[n] - 1].fissionable) result = true; } return result; } diff --git a/src/scattdata.cpp b/src/scattdata.cpp index dc4d169a7..d23169fa8 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -267,7 +267,8 @@ void ScattDataLegendre::combine(std::vector those_scatts, int groups = dynamic_cast(those_scatts[0])->energy.size(); // Now allocate and zero our storage spaces - double_3dvec this_matrix = get_matrix(max_order); + double_3dvec this_matrix = double_3dvec(groups, double_2dvec(groups, + double_1dvec(max_order, 0.))); double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); double_2dvec mult_denom(groups, double_1dvec(groups, 0.)); @@ -572,7 +573,8 @@ void ScattDataHistogram::combine(std::vector those_scatts, int groups = dynamic_cast(those_scatts[0])->energy.size(); // Now allocate and zero our storage spaces - double_3dvec this_matrix = get_matrix(max_order); + double_3dvec this_matrix = double_3dvec(groups, double_2dvec(groups, + double_1dvec(max_order, 0.))); double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); double_2dvec mult_denom(groups, double_1dvec(groups, 0.)); diff --git a/src/xsdata.cpp b/src/xsdata.cpp index 49b07453d..8afdb7f03 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -35,9 +35,9 @@ XsData::XsData(int energy_groups, int num_delayed_groups, bool fissionable, double_1dvec(num_delayed_groups, 0.))); if (fissionable) { - // allocate delayed_nu_fission; [temperature][phi][theta][in group][out group] + // allocate delayed_nu_fission; [temperature][phi][theta][in group][delay group] delayed_nu_fission = double_4dvec(n_pol, double_3dvec(n_azi, - double_2dvec(energy_groups, double_1dvec(energy_groups, 0.)))); + double_2dvec(energy_groups, double_1dvec(num_delayed_groups, 0.)))); // chi_prompt; [temperature][phi][theta][in group][delayed group] chi_prompt = double_4dvec(n_pol, double_3dvec(n_azi, @@ -129,11 +129,15 @@ void XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, int energy_groups, int delayed_groups, bool is_isotropic) { + + // Get the fission and kappa_fission data xs; these are optional + read_nd_vector(xsdata_grp, "fission", fission); + read_nd_vector(xsdata_grp, "kappa-fission", kappa_fission); + + // Set/get beta double_4dvec temp_beta = double_4dvec(n_pol, double_3dvec(n_azi, double_2dvec(energy_groups, double_1dvec(delayed_groups, 0.)))); - - // Set/get beta if (object_exists(xsdata_grp, "beta")) { hid_t xsdata = open_dataset(xsdata_grp, "beta"); int ndims = dataset_ndims(xsdata); @@ -442,6 +446,7 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, if (object_exists(xsdata_grp, "delayed-nu-fission")) { hid_t xsdata = open_dataset(xsdata_grp, "delayed-nu-fission"); int ndims = dataset_ndims(xsdata); + close_dataset(xsdata); if (is_isotropic) ndims += 2; if (ndims == 3) { @@ -507,12 +512,8 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, fatal_error("prompt-nu-fission must be provided as a 3D, 4D, or 5D " "array!"); } - close_dataset(xsdata); } - // Get the fission and kappa_fission data xs - read_nd_vector(xsdata_grp, "fission", fission); - read_nd_vector(xsdata_grp, "kappa-fission", kappa_fission); } From 6c8de73e3e6e0eabbbac8daa64276dfa3eb1dfd9 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 10 Jun 2018 14:23:08 -0400 Subject: [PATCH 322/361] Implemented sample_scatter in C++ --- src/constants.h | 1 - src/mgxs.cpp | 72 +++++++++++++++++++++++++++++++----------- src/mgxs.h | 25 +++++++++------ src/nuclide_header.F90 | 8 ++--- src/physics_mg.F90 | 28 ++++++++++++---- src/scattdata.cpp | 60 +++++++++++++++++++---------------- src/scattdata.h | 8 ++--- src/tracking.F90 | 23 ++++++++++++++ 8 files changed, 156 insertions(+), 69 deletions(-) diff --git a/src/constants.h b/src/constants.h index 4129305bf..8290aa8c9 100644 --- a/src/constants.h +++ b/src/constants.h @@ -10,7 +10,6 @@ namespace openmc { -typedef std::array dir_arr; typedef std::vector double_1dvec; typedef std::vector > double_2dvec; typedef std::vector > > double_3dvec; diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 42df45ef0..ec711e130 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -24,6 +24,13 @@ void Mgxs::init(const std::string& in_name, const double in_awr, azimuthal = in_azimuthal; n_pol = polar.size(); n_azi = azimuthal.size(); + index_temp = 0; + last_sqrtkT = 0.; + index_pol = 0; + index_azi = 0; + last_uvw[0] = 1.; + last_uvw[1] = 0.; + last_uvw[2] = 0.; } @@ -383,7 +390,8 @@ void Mgxs::combine(std::vector& micros, double_1dvec& scalars, } -double Mgxs::get_xs(const char* xstype, int gin, int* gout, double* mu, int* dg) +double Mgxs::get_xs(const char* xstype, const int gin, int* gout, double* mu, + int* dg) { // This method assumes that the temperature and angle indices are set double val; @@ -469,7 +477,8 @@ double Mgxs::get_xs(const char* xstype, int gin, int* gout, double* mu, int* dg) } -void Mgxs::sample_fission_energy(int gin, double nu_fission, int& dg, int& gout) +void Mgxs::sample_fission_energy(const int gin, const double nu_fission, + int& dg, int& gout) { // This method assumes that the temperature and angle indices are set // Find the probability of having a prompt neutron @@ -525,8 +534,7 @@ void Mgxs::sample_fission_energy(int gin, double nu_fission, int& dg, int& gout) } -void Mgxs::sample_scatter(dir_arr& uvw, int gin, int& gout, double& mu, - double& wgt) +void Mgxs::sample_scatter(const int gin, int& gout, double& mu, double& wgt) { // This method assumes that the temperature and angle indices are set // Sample the data @@ -534,8 +542,8 @@ void Mgxs::sample_scatter(dir_arr& uvw, int gin, int& gout, double& mu, } -void Mgxs::calculate_xs(int gin, double sqrtkT, dir_arr& uvw, double& total_xs, - double& abs_xs, double& nu_fiss_xs) +void Mgxs::calculate_xs(const int gin, const double sqrtkT, const double uvw[3], + double& total_xs, double& abs_xs, double& nu_fiss_xs) { // Set our indices set_temperature_index(sqrtkT); @@ -543,10 +551,14 @@ void Mgxs::calculate_xs(int gin, double sqrtkT, dir_arr& uvw, double& total_xs, total_xs = xs[index_temp].total[index_pol][index_azi][gin]; abs_xs = xs[index_temp].absorption[index_pol][index_azi][gin]; - // nu-fission is made up of the prompt and all the delayed nu_fission data - nu_fiss_xs = xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; - for (auto& val : xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin]) { - nu_fiss_xs += val; + if (fissionable) { + // nu-fission is made up of the prompt and all the delayed nu_fission data + nu_fiss_xs = xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; + for (auto& val : xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin]) { + nu_fiss_xs += val; + } + } else { + nu_fiss_xs = 0.; } } @@ -568,7 +580,7 @@ bool Mgxs::equiv(const Mgxs& that) } -inline void Mgxs::set_temperature_index(double sqrtkT) +inline void Mgxs::set_temperature_index(const double sqrtkT) { // See if we need to find the new index if (sqrtkT != last_sqrtkT) { @@ -588,27 +600,30 @@ inline void Mgxs::set_temperature_index(double sqrtkT) } -inline void Mgxs::set_angle_index(dir_arr& uvw) +inline void Mgxs::set_angle_index(const double uvw[3]) { // See if we need to find the new index - if (uvw != last_uvw) { + if ((uvw[0] != last_uvw[0]) || (uvw[1] != last_uvw[1]) || + (uvw[2] != last_uvw[2])) { // convert uvw to polar and azimuthal angles double my_pol = std::acos(uvw[2]); double my_azi = std::atan2(uvw[1], uvw[0]); // Find the location, assuming equal-bin angles double delta_angle = PI / n_pol; - index_pol = std::floor(my_pol / delta_angle + 1.); - delta_angle = PI / n_azi; - index_azi = std::floor((my_azi + PI) / delta_angle + 1.); + index_pol = std::floor(my_pol / delta_angle); + delta_angle = 2. * PI / n_azi; + index_azi = std::floor((my_azi + PI) / delta_angle); // store this direction as the last one used - last_uvw = uvw; + last_uvw[0] = uvw[0]; + last_uvw[1] = uvw[1]; + last_uvw[2] = uvw[2]; } } //============================================================================== -// Mgxs data loading methods +// Mgxs data loading interface methods //============================================================================== void add_mgxs(hid_t file_id, char* name, int energy_groups, @@ -680,4 +695,25 @@ void create_macro_xs(char* mat_name, const int n_nuclides, macro_xs.push_back(macro); } +//============================================================================== +// Mgxs tracking/transport/tallying interface methods +//============================================================================== + +void calculate_xs(const int i_mat, const int gin, const double sqrtkT, + const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs) +{ + macro_xs[i_mat - 1].calculate_xs(gin - 1, sqrtkT, uvw, total_xs, abs_xs, + nu_fiss_xs); +} + +void scatter(const int i_mat, const int gin, int& gout, double& mu, + double& wgt, double uvw[3]) +{ + int gout_c = gout - 1; + macro_xs[i_mat - 1].sample_scatter(gin - 1, gout_c, mu, wgt); + gout = gout_c + 1; + + rotate_angle_c(uvw, mu, nullptr); +} + } // namespace openmc \ No newline at end of file diff --git a/src/mgxs.h b/src/mgxs.h index 90ed520d9..14c4e4e89 100644 --- a/src/mgxs.h +++ b/src/mgxs.h @@ -44,7 +44,7 @@ class Mgxs { int index_azi; double_1dvec polar; double_1dvec azimuthal; - dir_arr last_uvw; + double last_uvw[3]; void _metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, const int in_num_delayed_groups, double_1dvec& temperature, int& method, const double tolerance, int_1dvec& temps_to_read, @@ -66,16 +66,16 @@ class Mgxs { double_1dvec& temperature, int& method, double tolerance, int max_order, bool legendre_to_tabular, int legendre_to_tabular_points); - double get_xs(const char* xstype, int gin, int* gout, double* mu, + double get_xs(const char* xstype, const int gin, int* gout, double* mu, int* dg); - void sample_fission_energy(int gin, double nu_fission, int& dg, int& gout); - void sample_scatter(dir_arr& uvw, int gin, int& gout, double& mu, - double& wgt); - void calculate_xs(int gin, double sqrtkT, dir_arr& uvw, double& total_xs, - double& abs_xs, double& nu_fiss_xs); + void sample_fission_energy(const int gin, const double nu_fission, + int& dg, int& gout); + void sample_scatter(const int gin, int& gout, double& mu, double& wgt); + void calculate_xs(const int gin, const double sqrtkT, const double uvw[3], + double& total_xs, double& abs_xs, double& nu_fiss_xs); bool equiv(const Mgxs& that); - inline void set_temperature_index(double sqrtkT); - inline void set_angle_index(dir_arr& uvw); + inline void set_temperature_index(const double sqrtkT); + inline void set_angle_index(const double uvw[3]); }; extern "C" void add_mgxs(hid_t file_id, char* name, int energy_groups, @@ -89,6 +89,13 @@ extern "C" void create_macro_xs(char* mat_name, const int n_nuclides, const int i_nuclides[], const int n_temps, const double temps[], const double atom_densities[], int& method, const double tolerance); +extern "C" void calculate_xs(const int i_mat, const int gin, + const double sqrtkT, const double uvw[3], double& total_xs, + double& abs_xs, double& nu_fiss_xs); + +extern "C" void scatter(const int i_mat, const int gin, int& gout, double& mu, + double& wgt, double uvw[3]); + // Storage for the MGXS data std::vector nuclides_MG; diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index ebabe5cdb..94aa1928a 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -166,10 +166,10 @@ module nuclide_header !=============================================================================== type MaterialMacroXS - real(8) :: total ! macroscopic total xs - real(8) :: absorption ! macroscopic absorption xs - real(8) :: fission ! macroscopic fission xs - real(8) :: nu_fission ! macroscopic production xs + real(C_DOUBLE) :: total ! macroscopic total xs + real(C_DOUBLE) :: absorption ! macroscopic absorption xs + real(C_DOUBLE) :: fission ! macroscopic fission xs + real(C_DOUBLE) :: nu_fission ! macroscopic production xs end type MaterialMacroXS !=============================================================================== diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index eb8208594..3030fc099 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -22,6 +22,20 @@ module physics_mg implicit none + interface + subroutine scatter_c(i_mat, gin, gout, mu, wgt, uvw) & + bind(C, name='scatter') + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: i_mat + integer(C_INT), value, intent(in) :: gin + integer(C_INT), intent(inout) :: gout + real(C_DOUBLE), intent(inout) :: mu + real(C_DOUBLE), intent(inout) :: wgt + real(C_DOUBLE), intent(inout) :: uvw(1:3) + end subroutine scatter_c + end interface + contains !=============================================================================== @@ -143,16 +157,18 @@ contains type(Particle), intent(inout) :: p - call macro_xs(p % material) % obj % sample_scatter(p % coord(1) % uvw, & - p % last_g, p % g, & - p % mu, p % wgt) + ! call macro_xs(p % material) % obj % sample_scatter(p % coord(1) % uvw, & + ! p % last_g, p % g, p % mu, p % wgt) + + ! Convert change in angle (mu) to new direction + ! p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, p % mu) + + call scatter_c(p % material, p % last_g, p % g, p % mu, p % wgt, & + p % coord(1) % uvw) ! Update energy value for downstream compatability (in tallying) p % E = energy_bin_avg(p % g) - ! Convert change in angle (mu) to new direction - p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, p % mu) - ! Set event component p % event = EVENT_SCATTER diff --git a/src/scattdata.cpp b/src/scattdata.cpp index d23169fa8..aef620ac5 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -44,7 +44,8 @@ void ScattData::sample_energy(int gin, int& gout, int& i_gout) { // Sample the outgoing group double xi = prn(); - i_gout = 0; //TODO: + 1? + + i_gout = 0; gout = gmin[gin]; double prob = energy[gin][i_gout]; while((prob < xi) && (gout < gmax[gin])) { @@ -247,7 +248,7 @@ void ScattDataLegendre::sample(int gin, int& gout, double& mu, double& wgt) } -void ScattDataLegendre::combine(std::vector those_scatts, +void ScattDataLegendre::combine(std::vector& those_scatts, double_1dvec& scalars) { // Find the max order in the data set and make sure we can combine the sets @@ -294,6 +295,7 @@ void ScattDataLegendre::combine(std::vector those_scatts, for (int gin = 0; gin < groups; gin++) { // Only spend time adding that's gmin to gmax data since the rest will // be zeros + int i_gout = 0; for (int gout = that->gmin[gin]; gout <= that->gmax[gin]; gout++) { // Do the scattering matrix for (int l = 0; l < max_order; l++) { @@ -301,13 +303,14 @@ void ScattDataLegendre::combine(std::vector those_scatts, } // Incorporate that's contribution to the multiplicity matrix data - double nuscatt = that->scattxs[gin] * that->energy[gin][gout]; + double nuscatt = that->scattxs[gin] * that->energy[gin][i_gout]; mult_numer[gin][gout] += scalars[i] * nuscatt; - if (that->mult[gin][gout] > 0.) { - mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][gout]; + if (that->mult[gin][i_gout] > 0.) { + mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][i_gout]; } else { mult_denom[gin][gout] += scalars[i]; } + i_gout++; } } } @@ -336,14 +339,14 @@ void ScattDataLegendre::combine(std::vector those_scatts, for (gmin_ = 0; gmin_ < groups; gmin_++) { bool non_zero = std::all_of(this_matrix[gin][gmin_].begin(), this_matrix[gin][gmin_].end(), - [](double val){return val > 0.;}); + [](double val){return val != 0.;}); if (non_zero) break; } int gmax_; for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { bool non_zero = std::all_of(this_matrix[gin][gmax_].begin(), this_matrix[gin][gmax_].end(), - [](double val){return val > 0.;}); + [](double val){return val != 0.;}); if (non_zero) break; } @@ -425,6 +428,7 @@ void ScattDataHistogram::init(int_1dvec& in_gmin, int_1dvec& in_gmax, for (int i_gout = 0; i_gout < num_groups; i_gout++) { double norm = std::accumulate(matrix[gin][i_gout].begin(), matrix[gin][i_gout].end(), 0.); + in_energy[gin][i_gout] = norm; if (norm != 0.) { for (auto& n : matrix[gin][i_gout]) n /= norm; } @@ -440,7 +444,7 @@ void ScattDataHistogram::init(int_1dvec& in_gmin, int_1dvec& in_gmax, dmu = 2. / order; mu[0] = -1.; for (int imu = 1; imu < order; imu++) { - mu[imu] = -1. + (imu - 1) * dmu; + mu[imu] = -1. + imu * dmu; } // Calculate f(mu) and integrate it so we can avoid rejection sampling @@ -507,7 +511,7 @@ void ScattDataHistogram::sample(int gin, int& gout, double& mu, double& wgt) int imu; if (xi < dist[gin][i_gout][0]) { - imu = 1; + imu = 0; } else { // TODO lower_bound? + 1? imu = std::upper_bound(dist[gin][i_gout].begin(), @@ -551,7 +555,7 @@ double_3dvec ScattDataHistogram::get_matrix(int max_order) } -void ScattDataHistogram::combine(std::vector those_scatts, +void ScattDataHistogram::combine(std::vector& those_scatts, double_1dvec& scalars) { // Find the max order in the data set and make sure we can combine the sets @@ -600,6 +604,7 @@ void ScattDataHistogram::combine(std::vector those_scatts, for (int gin = 0; gin < groups; gin++) { // Only spend time adding that's gmin to gmax data since the rest will // be zeros + int i_gout = 0; for (int gout = that->gmin[gin]; gout <= that->gmax[gin]; gout++) { // Do the scattering matrix for (int l = 0; l < max_order; l++) { @@ -607,13 +612,14 @@ void ScattDataHistogram::combine(std::vector those_scatts, } // Incorporate that's contribution to the multiplicity matrix data - double nuscatt = that->scattxs[gin] * that->energy[gin][gout]; + double nuscatt = that->scattxs[gin] * that->energy[gin][i_gout]; mult_numer[gin][gout] += scalars[i] * nuscatt; - if (that->mult[gin][gout] > 0.) { - mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][gout]; + if (that->mult[gin][i_gout] > 0.) { + mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][i_gout]; } else { mult_denom[gin][gout] += scalars[i]; } + i_gout++; } } } @@ -642,14 +648,14 @@ void ScattDataHistogram::combine(std::vector those_scatts, for (gmin_ = 0; gmin_ < groups; gmin_++) { bool non_zero = std::all_of(this_matrix[gin][gmin_].begin(), this_matrix[gin][gmin_].end(), - [](double val){return val > 0.;}); + [](double val){return val != 0.;}); if (non_zero) break; } int gmax_; for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { bool non_zero = std::all_of(this_matrix[gin][gmax_].begin(), this_matrix[gin][gmax_].end(), - [](double val){return val > 0.;}); + [](double val){return val != 0.;}); if (non_zero) break; } @@ -695,7 +701,7 @@ void ScattDataTabular::init(int_1dvec& in_gmin, int_1dvec& in_gmax, dmu = 2. / (order - 1); mu[0] = -1.; for (int imu = 1; imu < order - 1; imu++) { - mu[imu] = -1. + (imu - 1) * dmu; + mu[imu] = -1. + imu * dmu; } mu[order - 1] = 1.; @@ -724,9 +730,7 @@ void ScattDataTabular::init(int_1dvec& in_gmin, int_1dvec& in_gmax, norm += 0.5 * dmu * (matrix[gin][i_gout][imu - 1] + matrix[gin][i_gout][imu]); } - if (norm != 0.) { - for (auto& n : matrix[gin][i_gout]) n /= norm; - } + in_energy[gin][i_gout] = norm; } } @@ -806,14 +810,14 @@ void ScattDataTabular::sample(int gin, int& gout, double& mu, double& wgt) double c_k = dist[gin][i_gout][0]; int k; - for (k = 0; k < NP - 2; k++) { + for (k = 0; k < NP - 1; k++) { double c_k1 = dist[gin][i_gout][k + 1]; if (xi < c_k1) break; c_k = c_k1; } // Check to make sure k is <= NP - 1 - k = std::min(k, NP - 1); + k = std::min(k, NP - 2); // Find the pdf values we want double p0 = fmu[gin][i_gout][k]; @@ -861,7 +865,7 @@ double_3dvec ScattDataTabular::get_matrix(int max_order) return matrix; } -void ScattDataTabular::combine(std::vector those_scatts, +void ScattDataTabular::combine(std::vector& those_scatts, double_1dvec& scalars) { // Find the max order in the data set and make sure we can combine the sets @@ -910,6 +914,7 @@ void ScattDataTabular::combine(std::vector those_scatts, for (int gin = 0; gin < groups; gin++) { // Only spend time adding that's gmin to gmax data since the rest will // be zeros + int i_gout = 0; for (int gout = that->gmin[gin]; gout <= that->gmax[gin]; gout++) { // Do the scattering matrix for (int l = 0; l < max_order; l++) { @@ -917,13 +922,14 @@ void ScattDataTabular::combine(std::vector those_scatts, } // Incorporate that's contribution to the multiplicity matrix data - double nuscatt = that->scattxs[gin] * that->energy[gin][gout]; + double nuscatt = that->scattxs[gin] * that->energy[gin][i_gout]; mult_numer[gin][gout] += scalars[i] * nuscatt; - if (that->mult[gin][gout] > 0.) { - mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][gout]; + if (that->mult[gin][i_gout] > 0.) { + mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][i_gout]; } else { mult_denom[gin][gout] += scalars[i]; } + i_gout++; } } } @@ -952,14 +958,14 @@ void ScattDataTabular::combine(std::vector those_scatts, for (gmin_ = 0; gmin_ < groups; gmin_++) { bool non_zero = std::all_of(this_matrix[gin][gmin_].begin(), this_matrix[gin][gmin_].end(), - [](double val){return val > 0.;}); + [](double val){return val != 0.;}); if (non_zero) break; } int gmax_; for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { bool non_zero = std::all_of(this_matrix[gin][gmax_].begin(), this_matrix[gin][gmax_].end(), - [](double val){return val > 0.;}); + [](double val){return val != 0.;}); if (non_zero) break; } diff --git a/src/scattdata.h b/src/scattdata.h index 6e24a50bc..4553156e0 100644 --- a/src/scattdata.h +++ b/src/scattdata.h @@ -41,7 +41,7 @@ class ScattData { double get_xs(const char* xstype, int gin, int* gout, double* mu); void generic_init(int order, int_1dvec in_gmin, int_1dvec in_gmax, double_2dvec in_energy, double_2dvec in_mult); - virtual void combine(std::vector those_scatts, + virtual void combine(std::vector& those_scatts, double_1dvec& scalars) = 0; virtual int get_order() = 0; virtual double_3dvec get_matrix(int max_order) = 0; @@ -59,7 +59,7 @@ class ScattDataLegendre: public ScattData { void update_max_val(); double calc_f(int gin, int gout, double mu); void sample(int gin, int& gout, double& mu, double& wgt); - void combine(std::vector those_scatts, double_1dvec& scalars); + void combine(std::vector& those_scatts, double_1dvec& scalars); int get_order() {return dist[0][0].size() - 1;}; double_3dvec get_matrix(int max_order); }; @@ -74,7 +74,7 @@ class ScattDataHistogram: public ScattData { double_3dvec& coeffs); double calc_f(int gin, int gout, double mu); void sample(int gin, int& gout, double& mu, double& wgt); - void combine(std::vector those_scatts, double_1dvec& scalars); + void combine(std::vector& those_scatts, double_1dvec& scalars); int get_order() {return dist[0][0].size();}; double_3dvec get_matrix(int max_order); }; @@ -91,7 +91,7 @@ class ScattDataTabular: public ScattData { double_3dvec& coeffs); double calc_f(int gin, int gout, double mu); void sample(int gin, int& gout, double& mu, double& wgt); - void combine(std::vector those_scatts, double_1dvec& scalars); + void combine(std::vector& those_scatts, double_1dvec& scalars); int get_order() {return dist[0][0].size();}; double_3dvec get_matrix(int max_order); }; diff --git a/src/tracking.F90 b/src/tracking.F90 index 3fe6a065b..2ba62a8dd 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -1,5 +1,7 @@ module tracking + use, intrinsic :: ISO_C_BINDING + use constants use error, only: warning, write_message use geometry_header, only: cells @@ -27,6 +29,21 @@ module tracking implicit none + interface + subroutine calculate_xs_c(i_mat, gin, sqrtkT, uvw, total_xs, abs_xs, & + nu_fiss_xs) bind(C, name='calculate_xs') + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: i_mat + integer(C_INT), value, intent(in) :: gin + real(C_DOUBLE), value, intent(in) :: sqrtkT + real(C_DOUBLE), intent(in) :: uvw(1:3) + real(C_DOUBLE), intent(inout) :: total_xs + real(C_DOUBLE), intent(inout) :: abs_xs + real(C_DOUBLE), intent(inout) :: nu_fiss_xs + end subroutine calculate_xs_c + end interface + contains !=============================================================================== @@ -112,8 +129,14 @@ contains end if else ! Get the MG data + !!TODO: Remove Fortran call - needed until I'm done replacing Fortran + !!with C++ code because it sets index_temp call macro_xs(p % material) % obj % calculate_xs(p % g, p % sqrtkT, & p % coord(p % n_coord) % uvw, material_xs) + call calculate_xs_c(p % material, p % g, p % sqrtkT, & + p % coord(p % n_coord) % uvw, material_xs % total, & + material_xs % absorption, material_xs % nu_fission) + ! Finally, update the particle group while we have already checked ! for if multi-group From 4b403c6b7bb3decfee10d77bb16c20f744cc81b5 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 11 Jun 2018 15:00:18 -0400 Subject: [PATCH 323/361] Address #1012 comments --- src/cell.cpp | 59 +++++++++++++++++------------------ src/cell.h | 10 +++--- src/geometry_aux.cpp | 72 +++++++++++++++++++++---------------------- src/lattice.cpp | 31 ++++++++++--------- src/lattice.h | 4 +-- src/string_utils.h | 35 +++++++++++++++++++++ src/surface.cpp | 16 +++++----- src/surface.h | 4 +-- src/xml_interface.cpp | 24 --------------- src/xml_interface.h | 2 -- 10 files changed, 132 insertions(+), 125 deletions(-) create mode 100644 src/string_utils.h diff --git a/src/cell.cpp b/src/cell.cpp index 8cdc7b6f6..e4388e701 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -33,11 +33,11 @@ extern "C" double FP_PRECISION; int32_t n_cells {0}; -std::vector cells_c; -std::unordered_map cell_dict; +std::vector global_cells; +std::unordered_map cell_map; -std::vector universes_c; -std::unordered_map universe_dict; +std::vector global_universes; +std::unordered_map universe_map; //============================================================================== //! Convert region specification string to integer tokens. @@ -246,7 +246,7 @@ Cell::Cell(pugi::xml_node cell_node) } // Read the region specification. - std::string region_spec {""}; + std::string region_spec; if (check_for_node(cell_node, "region")) { region_spec = get_node_value(cell_node, "region"); } @@ -256,10 +256,9 @@ Cell::Cell(pugi::xml_node cell_node) region.shrink_to_fit(); // Convert user IDs to surface indices. - // Note that the index has 1 added to it in order to preserve the sign. - for (auto it = region.begin(); it != region.end(); it++) { - if (*it < OP_UNION) { - *it = copysign(surface_dict[abs(*it)]+1, *it); + for (auto &r : region) { + if (r < OP_UNION) { + r = copysign(surface_map[abs(r)] + 1, r); } } @@ -330,8 +329,8 @@ Cell::to_hdf5(hid_t cell_group) const } //TODO: Fix the off-by-one indexing. - write_int(cell_group, 0, nullptr, "universe", &universes_c[universe-1]->id, - false); + write_int(cell_group, 0, nullptr, "universe", + &global_universes[universe-1]->id, false); // Write the region specification. if (!region.empty()) { @@ -446,24 +445,24 @@ read_cells(pugi::xml_node *node) } // Allocate the vector of Cells. - cells_c.reserve(n_cells); + global_cells.reserve(n_cells); // Loop over XML cell elements and populate the array. for (pugi::xml_node cell_node: node->children("cell")) { - cells_c.push_back(new Cell(cell_node)); + global_cells.push_back(new Cell(cell_node)); } - // Populate the Universe vector and dictionary. - for (int i = 0; i < cells_c.size(); i++) { - int32_t uid = cells_c[i]->universe; - auto it = universe_dict.find(uid); - if (it == universe_dict.end()) { - universes_c.push_back(new Universe()); - universes_c.back()->id = uid; - universes_c.back()->cells.push_back(i); - universe_dict[uid] = universes_c.size() - 1; + // Populate the Universe vector and map. + for (int i = 0; i < global_cells.size(); i++) { + int32_t uid = global_cells[i]->universe; + auto it = universe_map.find(uid); + if (it == universe_map.end()) { + global_universes.push_back(new Universe()); + global_universes.back()->id = uid; + global_universes.back()->cells.push_back(i); + universe_map[uid] = global_universes.size() - 1; } else { - universes_c[it->second]->cells.push_back(i); + global_universes[it->second]->cells.push_back(i); } } } @@ -473,7 +472,7 @@ read_cells(pugi::xml_node *node) //============================================================================== extern "C" { - Cell* cell_pointer(int32_t cell_ind) {return cells_c[cell_ind];} + Cell* cell_pointer(int32_t cell_ind) {return global_cells[cell_ind];} int32_t cell_id(Cell *c) {return c->id;} @@ -499,11 +498,11 @@ extern "C" { {return c->contains(xyz, uvw, on_surface);} void cell_distance(Cell *c, double xyz[3], double uvw[3], int32_t on_surface, - double &min_dist, int32_t &i_surf) + double *min_dist, int32_t *i_surf) { std::pair out = c->distance(xyz, uvw, on_surface); - min_dist = out.first; - i_surf = out.second; + *min_dist = out.first; + *i_surf = out.second; } int32_t cell_offset(Cell *c, int map) {return c->offset[map];} @@ -512,11 +511,11 @@ extern "C" { void extend_cells_c(int32_t n) { - cells_c.reserve(cells_c.size() + n); + global_cells.reserve(global_cells.size() + n); for (int32_t i = 0; i < n; i++) { - cells_c.push_back(new Cell()); + global_cells.push_back(new Cell()); } - n_cells = cells_c.size(); + n_cells = global_cells.size(); } } diff --git a/src/cell.h b/src/cell.h index f55c8e6fd..a2f72c833 100644 --- a/src/cell.h +++ b/src/cell.h @@ -27,12 +27,12 @@ extern "C" int FILL_LATTICE; extern "C" int32_t n_cells; class Cell; -extern std::vector cells_c; -extern std::unordered_map cell_dict; +extern std::vector global_cells; +extern std::unordered_map cell_map; class Universe; -extern std::vector universes_c; -extern std::unordered_map universe_dict; +extern std::vector global_universes; +extern std::unordered_map universe_map; //============================================================================== //! A geometry primitive that fills all space and contains cells. @@ -54,7 +54,7 @@ class Cell { public: int32_t id; //!< Unique ID - std::string name{""}; //!< User-defined name + std::string name; //!< User-defined name int type; //!< Material, universe, or lattice int32_t universe; //!< Universe # this cell is in int32_t fill; //!< Universe # filling this cell diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index dc31edf7e..3f36cf299 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -9,8 +9,6 @@ #include "error.h" #include "lattice.h" -#include //TODO: remove this - namespace openmc { @@ -20,15 +18,15 @@ void adjust_indices_c() { // Adjust material/fill idices. - for (Cell *c : cells_c) { + for (Cell *c : global_cells) { if (c->material[0] == C_NONE) { int32_t id = c->fill; - auto search_univ = universe_dict.find(id); - auto search_lat = lattice_dict.find(id); - if (search_univ != universe_dict.end()) { + auto search_univ = universe_map.find(id); + auto search_lat = lattice_map.find(id); + if (search_univ != universe_map.end()) { c->type = FILL_UNIVERSE; c->fill = search_univ->second; - } else if (search_lat != lattice_dict.end()) { + } else if (search_lat != lattice_map.end()) { c->type = FILL_LATTICE; c->fill = search_lat->second; } else { @@ -44,9 +42,9 @@ adjust_indices_c() } // Change cell.universe values from IDs to indices. - for (Cell *c : cells_c) { - auto search = universe_dict.find(c->universe); - if (search != universe_dict.end()) { + for (Cell *c : global_cells) { + auto search = universe_map.find(c->universe); + if (search != universe_map.end()) { //TODO: Remove this off-by-one indexing. c->universe = search->second + 1; } else { @@ -70,7 +68,7 @@ find_root_universe() { // Find all the universes listed as a cell fill. std::unordered_set fill_univ_ids; - for (Cell *c : cells_c) { + for (Cell *c : global_cells) { fill_univ_ids.insert(c->fill); } @@ -87,8 +85,8 @@ find_root_universe() // Figure out which universe is not in the set. This is the root universe. bool root_found {false}; int32_t root_univ; - for (int32_t i = 0; i < universes_c.size(); i++) { - auto search = fill_univ_ids.find(universes_c[i]->id); + for (int32_t i = 0; i < global_universes.size(); i++) { + auto search = fill_univ_ids.find(global_universes[i]->id); if (search == fill_univ_ids.end()) { if (root_found) { fatal_error("Two or more universes are not used as fill universes, so " @@ -111,7 +109,7 @@ find_root_universe() void allocate_offset_tables(int n_maps) { - for (Cell *c : cells_c) { + for (Cell *c : global_cells) { if (c->type != FILL_MATERIAL) { c->offset.resize(n_maps, C_NONE); } @@ -127,8 +125,8 @@ allocate_offset_tables(int n_maps) void count_cell_instances(int32_t univ_indx) { - for (int32_t cell_indx : universes_c[univ_indx]->cells) { - Cell &c = *cells_c[cell_indx]; + for (int32_t cell_indx : global_universes[univ_indx]->cells) { + Cell &c = *global_cells[cell_indx]; ++c.n_instances; if (c.type == FILL_UNIVERSE) { @@ -151,13 +149,13 @@ int count_universe_instances(int32_t search_univ, int32_t target_univ_id) { // If this is the target, it can't contain itself. - if (universes_c[search_univ]->id == target_univ_id) { + if (global_universes[search_univ]->id == target_univ_id) { return 1; } int count {0}; - for (int32_t cell_indx : universes_c[search_univ]->cells) { - Cell &c = *cells_c[cell_indx]; + for (int32_t cell_indx : global_universes[search_univ]->cells) { + Cell &c = *global_cells[cell_indx]; if (c.type == FILL_UNIVERSE) { int32_t next_univ = c.fill; @@ -180,10 +178,10 @@ count_universe_instances(int32_t search_univ, int32_t target_univ_id) void fill_offset_tables(int32_t target_univ_id, int map) { - for (Universe *univ : universes_c) { + for (Universe *univ : global_universes) { int32_t offset {0}; // TODO: is this a bug? It matches F90 implementation. for (int32_t cell_indx : univ->cells) { - Cell &c = *cells_c[cell_indx]; + Cell &c = *global_cells[cell_indx]; if (c.type == FILL_UNIVERSE) { c.offset[map] = offset; @@ -212,7 +210,7 @@ distribcell_path_inner(int32_t target_cell, int32_t map, int32_t target_offset, // write to the path and return. for (int32_t cell_indx : search_univ.cells) { if ((cell_indx == target_cell) && (offset == target_offset)) { - Cell &c = *cells_c[cell_indx]; + Cell &c = *global_cells[cell_indx]; path << "c" << c.id; return path.str(); } @@ -224,7 +222,7 @@ distribcell_path_inner(int32_t target_cell, int32_t map, int32_t target_offset, std::vector::const_reverse_iterator cell_it {search_univ.cells.crbegin()}; for (; cell_it != search_univ.cells.crend(); ++cell_it) { - Cell &c = *cells_c[*cell_it]; + Cell &c = *global_cells[*cell_it]; // Material cells don't contain other cells so ignore them. if (c.type != FILL_MATERIAL) { @@ -244,14 +242,14 @@ distribcell_path_inner(int32_t target_cell, int32_t map, int32_t target_offset, } // Add the cell to the path string. - Cell &c = *cells_c[*cell_it]; + Cell &c = *global_cells[*cell_it]; path << "c" << c.id << "->"; if (c.type == FILL_UNIVERSE) { // Recurse into the fill cell. offset += c.offset[map]; path << distribcell_path_inner(target_cell, map, target_offset, - *universes_c[c.fill], offset); + *global_universes[c.fill], offset); return path.str(); } else { // Recurse into the lattice cell. @@ -264,7 +262,7 @@ distribcell_path_inner(int32_t target_cell, int32_t map, int32_t target_offset, offset = temp_offset; path << "(" << lat.index_to_string(it.indx) << ")->"; path << distribcell_path_inner(target_cell, map, target_offset, - *universes_c[*it], offset); + *global_universes[*it], offset); return path.str(); } } @@ -277,7 +275,7 @@ int distribcell_path_len(int32_t target_cell, int32_t map, int32_t target_offset, int32_t root_univ) { - Universe &root = *universes_c[root_univ]; + Universe &root = *global_universes[root_univ]; std::string path_ {distribcell_path_inner(target_cell, map, target_offset, root, 0)}; return path_.size() + 1; @@ -289,7 +287,7 @@ void distribcell_path(int32_t target_cell, int32_t map, int32_t target_offset, int32_t root_univ, char *path) { - Universe &root = *universes_c[root_univ]; + Universe &root = *global_universes[root_univ]; std::string path_ {distribcell_path_inner(target_cell, map, target_offset, root, 0)}; path_.copy(path, path_.size()); @@ -303,8 +301,8 @@ maximum_levels(int32_t univ) { int levels_below {0}; - for (int32_t cell_indx : universes_c[univ]->cells) { - Cell &c = *cells_c[cell_indx]; + for (int32_t cell_indx : global_universes[univ]->cells) { + Cell &c = *global_cells[cell_indx]; if (c.type == FILL_UNIVERSE) { int32_t next_univ = c.fill; levels_below = std::max(levels_below, maximum_levels(next_univ)); @@ -326,18 +324,18 @@ maximum_levels(int32_t univ) void free_memory_geometry_c() { - for (Cell *c : cells_c) {delete c;} - cells_c.clear(); - cell_dict.clear(); + for (Cell *c : global_cells) {delete c;} + global_cells.clear(); + cell_map.clear(); n_cells = 0; - for (Universe *u : universes_c) {delete u;} - universes_c.clear(); - universe_dict.clear(); + for (Universe *u : global_universes) {delete u;} + global_universes.clear(); + universe_map.clear(); for (Lattice *lat : lattices_c) {delete lat;} lattices_c.clear(); - lattice_dict.clear(); + lattice_map.clear(); } } // namespace openmc diff --git a/src/lattice.cpp b/src/lattice.cpp index 2d9bf02c5..959ab932e 100644 --- a/src/lattice.cpp +++ b/src/lattice.cpp @@ -8,6 +8,7 @@ #include "error.h" #include "geometry_aux.h" #include "hdf5_interface.h" +#include "string_utils.h" #include "xml_interface.h" @@ -19,7 +20,7 @@ namespace openmc { std::vector lattices_c; -std::unordered_map lattice_dict; +std::unordered_map lattice_map; //============================================================================== // Lattice implementation @@ -64,8 +65,8 @@ Lattice::adjust_indices() // Adjust the indices for the universes array. for (LatticeIter it = begin(); it != end(); ++it) { int uid = *it; - auto search = universe_dict.find(uid); - if (search != universe_dict.end()) { + auto search = universe_map.find(uid); + if (search != universe_map.end()) { *it = search->second; } else { std::stringstream err_msg; @@ -77,8 +78,8 @@ Lattice::adjust_indices() // Adjust the index for the outer universe. if (outer != NO_OUTER_UNIVERSE) { - auto search = universe_dict.find(outer); - if (search != universe_dict.end()) { + auto search = universe_map.find(outer); + if (search != universe_map.end()) { outer = search->second; } else { std::stringstream err_msg; @@ -117,7 +118,7 @@ Lattice::to_hdf5(hid_t lattices_group) const } if (outer != NO_OUTER_UNIVERSE) { - int32_t outer_id = universes_c[outer]->id; + int32_t outer_id = global_universes[outer]->id; write_int(lat_group, 0, nullptr, "outer", &outer_id, false); } else { write_int(lat_group, 0, nullptr, "outer", &outer, false); @@ -372,7 +373,7 @@ RectLattice::to_hdf5_inner(hid_t lat_group) const for (int j = 0; j < nx; j++) { int indx1 = nx*ny*m + nx*k + j; int indx2 = nx*ny*m + nx*(ny-k-1) + j; - out[indx2] = universes_c[universes[indx1]]->id; + out[indx2] = global_universes[universes[indx1]]->id; } } } @@ -389,7 +390,7 @@ RectLattice::to_hdf5_inner(hid_t lat_group) const for (int j = 0; j < nx; j++) { int indx1 = nx*k + j; int indx2 = nx*(ny-k-1) + j; - out[indx2] = universes_c[universes[indx1]]->id; + out[indx2] = global_universes[universes[indx1]]->id; } } @@ -854,7 +855,7 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const // This array position is never used; put a -1 to indicate this. out[indx] = -1; } else { - out[indx] = universes_c[universes[indx]]->id; + out[indx] = global_universes[universes[indx]]->id; } } } @@ -871,19 +872,19 @@ HexLattice::to_hdf5_inner(hid_t lat_group) const extern "C" void read_lattices(pugi::xml_node *node) { - for (pugi::xml_node lat_node: node->children("lattice")) { + for (pugi::xml_node lat_node : node->children("lattice")) { lattices_c.push_back(new RectLattice(lat_node)); } - for (pugi::xml_node lat_node: node->children("hex_lattice")) { + for (pugi::xml_node lat_node : node->children("hex_lattice")) { lattices_c.push_back(new HexLattice(lat_node)); } - // Fill the lattice dictionary. + // Fill the lattice map. for (int i_lat = 0; i_lat < lattices_c.size(); i_lat++) { int id = lattices_c[i_lat]->id; - auto in_dict = lattice_dict.find(id); - if (in_dict == lattice_dict.end()) { - lattice_dict[id] = i_lat; + auto in_map = lattice_map.find(id); + if (in_map == lattice_map.end()) { + lattice_map[id] = i_lat; } else { std::stringstream err_msg; err_msg << "Two or more lattices use the same unique ID: " << id; diff --git a/src/lattice.h b/src/lattice.h index 909029aa9..19749ce4f 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -27,7 +27,7 @@ constexpr int32_t NO_OUTER_UNIVERSE{-1}; class Lattice; extern std::vector lattices_c; -extern std::unordered_map lattice_dict; +extern std::unordered_map lattice_map; //============================================================================== //! \class Lattice @@ -58,7 +58,7 @@ public: virtual ReverseLatticeIter rbegin(); ReverseLatticeIter rend(); - //! Convert internal universe values from IDs to indices using universe_dict. + //! Convert internal universe values from IDs to indices using universe_map. void adjust_indices(); //! Allocate offset table for distribcell. diff --git a/src/string_utils.h b/src/string_utils.h new file mode 100644 index 000000000..e2cb773ab --- /dev/null +++ b/src/string_utils.h @@ -0,0 +1,35 @@ +#ifndef STRING_UTILS_H +#define STRING_UTILS_H + +#include +#include + + +namespace openmc { + +std::vector +split(const std::string &in) +{ + std::vector out; + + for (int i = 0; i < in.size(); ) { + // Increment i until we find a non-whitespace character. + if (std::isspace(in[i])) { + i++; + + } else { + // Find the next whitespace character at j. + int j = i + 1; + while (j < in.size() && std::isspace(in[j]) == 0) {j++;} + + // Push-back everything between i and j. + out.push_back(in.substr(i, j-i)); + i = j + 1; // j is whitespace so leapfrog to j+1 + } + } + + return out; +} + +} // namespace openmc +#endif // STRING_UTILS_H diff --git a/src/surface.cpp b/src/surface.cpp index 1155559fd..56e57a854 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -28,7 +28,7 @@ int32_t n_surfaces; Surface **surfaces_c; -std::map surface_dict; +std::map surface_map; //============================================================================== // Helper functions for reading the "coeffs" node of an XML surface element @@ -1129,12 +1129,12 @@ read_surfaces(pugi::xml_node *node) } } - // Fill the surface dictionary. + // Fill the surface map. 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; + auto in_map = surface_map.find(id); + if (in_map == surface_map.end()) { + surface_map[id] = i_surf; } else { std::stringstream err_msg; err_msg << "Two or more surfaces use the same unique ID: " << id; @@ -1224,7 +1224,7 @@ read_surfaces(pugi::xml_node *node) } } else { // Convert the surface id to an index. - surf->i_periodic = surface_dict[surf->i_periodic]; + surf->i_periodic = surface_map[surf->i_periodic]; } } else { // This is a SurfacePlane. We won't try to find it's partner if the @@ -1236,7 +1236,7 @@ read_surfaces(pugi::xml_node *node) fatal_error(err_msg); } else { // Convert the surface id to an index. - surf->i_periodic = surface_dict[surf->i_periodic]; + surf->i_periodic = surface_map[surf->i_periodic]; } } @@ -1283,7 +1283,7 @@ extern "C" { delete surfaces_c; surfaces_c = nullptr; n_surfaces = 0; - surface_dict.clear(); + surface_map.clear(); } } diff --git a/src/surface.h b/src/surface.h index 99c51d747..1d8cc6ac7 100644 --- a/src/surface.h +++ b/src/surface.h @@ -31,7 +31,7 @@ extern "C" int32_t n_surfaces; class Surface; extern Surface **surfaces_c; -extern std::map surface_dict; +extern std::map surface_map; //============================================================================== //! Coordinates for an axis-aligned cube that bounds a geometric object. @@ -58,7 +58,7 @@ public: //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 + std::string name; //!< User-defined name explicit Surface(pugi::xml_node surf_node); diff --git a/src/xml_interface.cpp b/src/xml_interface.cpp index 77859ede8..b4fcc524c 100644 --- a/src/xml_interface.cpp +++ b/src/xml_interface.cpp @@ -35,28 +35,4 @@ get_node_value(pugi::xml_node node, const char *name) return value; } -std::vector -split(const std::string in) -{ - std::vector out; - - for (int i = 0; i < in.size(); ) { - // Increment i until we find a non-whitespace character. - if (std::isspace(in[i])) { - i++; - - } else { - // Find the next whitespace character at j. - int j = i + 1; - while (j < in.size() && std::isspace(in[j]) == 0) {j++;} - - // Push-back everything between i and j. - out.push_back(in.substr(i, j-i)); - i = j + 1; // j is whitespace so leapfrog to j+1 - } - } - - return out; -} - } // namespace openmc diff --git a/src/xml_interface.h b/src/xml_interface.h index 41252ed61..b6f9e3246 100644 --- a/src/xml_interface.h +++ b/src/xml_interface.h @@ -17,7 +17,5 @@ check_for_node(pugi::xml_node node, const char *name) std::string get_node_value(pugi::xml_node node, const char *name); -std::vector split(const std::string in); - } // namespace openmc #endif // XML_INTERFACE_H From b1c73918a8f111f3f3b5e7b2e240b57403c1d2d8 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Wed, 13 Jun 2018 20:22:51 -0400 Subject: [PATCH 324/361] It works! Replaced all mgxs_header functionality with the C++ version and a mgxs_interface module to act as the go-between the C++ and Fortran --- CMakeLists.txt | 5 +- src/api.F90 | 2 - src/cmfd_input.F90 | 4 +- src/constants.F90 | 19 ++ src/constants.h | 17 ++ src/input_xml.F90 | 10 +- src/mgxs.cpp | 265 +++++++----------- src/mgxs.h | 47 +--- src/mgxs_data.F90 | 414 +++++++++++++--------------- src/mgxs_header.F90 | 1 - src/mgxs_interface.F90 | 201 ++++++++++++++ src/mgxs_interface.cpp | 247 +++++++++++++++++ src/mgxs_interface.h | 66 +++++ src/output.F90 | 6 +- src/particle_restart.F90 | 2 +- src/physics_mg.F90 | 33 +-- src/scattdata.cpp | 46 ++-- src/scattdata.h | 2 +- src/simulation.F90 | 2 +- src/source.F90 | 2 +- src/state_point.F90 | 11 +- src/string_functions.cpp | 30 ++ src/string_functions.h | 31 +-- src/summary.F90 | 24 +- src/tallies/tally.F90 | 259 +++++++++-------- src/tallies/tally_filter_energy.F90 | 2 +- src/tracking.F90 | 21 +- src/xsdata.cpp | 15 + src/xsdata.h | 1 + 29 files changed, 1111 insertions(+), 674 deletions(-) create mode 100644 src/mgxs_interface.F90 create mode 100644 src/mgxs_interface.cpp create mode 100644 src/mgxs_interface.h create mode 100644 src/string_functions.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 362e98a83..a13f30b57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -363,7 +363,7 @@ set(LIBOPENMC_FORTRAN_SRC src/mesh_header.F90 src/message_passing.F90 src/mgxs_data.F90 - src/mgxs_header.F90 + src/mgxs_interface.F90 src/multipole_header.F90 src/nuclide_header.F90 src/output.F90 @@ -380,7 +380,6 @@ set(LIBOPENMC_FORTRAN_SRC src/reaction_header.F90 src/relaxng src/sab_header.F90 - src/scattdata_header.F90 src/secondary_correlated.F90 src/secondary_kalbach.F90 src/secondary_nbody.F90 @@ -439,11 +438,13 @@ set(LIBOPENMC_CXX_SRC src/math_functions.cpp src/message_passing.cpp src/mgxs.cpp + src/mgxs_interface.cpp src/plot.cpp src/random_lcg.cpp src/scattdata.cpp src/simulation.cpp src/state_point.cpp + src/string_functions.cpp src/surface.cpp src/xml_interface.cpp src/xsdata.cpp diff --git a/src/api.F90 b/src/api.F90 index da50007d6..f32de6beb 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -309,7 +309,6 @@ contains subroutine free_memory() use cmfd_header - use mgxs_header use plot_header use sab_header use settings @@ -327,7 +326,6 @@ contains call free_memory_simulation() call free_memory_nuclide() call free_memory_settings() - call free_memory_mgxs() call free_memory_sab() call free_memory_source() call free_memory_mesh() diff --git a/src/cmfd_input.F90 b/src/cmfd_input.F90 index 549cff419..1e4b8f374 100644 --- a/src/cmfd_input.F90 +++ b/src/cmfd_input.F90 @@ -3,8 +3,8 @@ module cmfd_input use, intrinsic :: ISO_C_BINDING use cmfd_header - use mesh_header, only: mesh_dict - use mgxs_header, only: energy_bins + use mesh_header, only: mesh_dict + use mgxs_interface, only: energy_bins, num_energy_groups use tally use tally_header use timer_header diff --git a/src/constants.F90 b/src/constants.F90 index d1893bf9e..430cc2715 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -402,6 +402,25 @@ module constants DIFF_NUCLIDE_DENSITY = 2, & DIFF_TEMPERATURE = 3 + + ! Mgxs::get_xs enumerated types + integer(C_INT), parameter :: & + MG_GET_XS_TOTAL = 0, & + MG_GET_XS_ABSORPTION = 1, & + MG_GET_XS_INVERSE_VELOCITY = 2, & + MG_GET_XS_DECAY_RATE = 3, & + MG_GET_XS_SCATTER = 4, & + MG_GET_XS_SCATTER_MULT = 5, & + MG_GET_XS_SCATTER_FMU_MULT = 6, & + MG_GET_XS_SCATTER_FMU = 7, & + MG_GET_XS_FISSION = 8, & + MG_GET_XS_KAPPA_FISSION = 9, & + MG_GET_XS_PROMPT_NU_FISSION = 10, & + MG_GET_XS_DELAYED_NU_FISSION = 11, & + MG_GET_XS_NU_FISSION = 12, & + MG_GET_XS_CHI_PROMPT = 13, & + MG_GET_XS_CHI_DELAYED = 14 + ! ============================================================================ ! RANDOM NUMBER STREAM CONSTANTS diff --git a/src/constants.h b/src/constants.h index 8290aa8c9..3ddadb5a1 100644 --- a/src/constants.h +++ b/src/constants.h @@ -63,6 +63,23 @@ constexpr double PI {3.1415926535898}; const double SQRT_PI {std::sqrt(PI)}; +// Mgxs::get_xs enumerated types +constexpr int MG_GET_XS_TOTAL {0}; +constexpr int MG_GET_XS_ABSORPTION {1}; +constexpr int MG_GET_XS_INVERSE_VELOCITY {2}; +constexpr int MG_GET_XS_DECAY_RATE {3}; +constexpr int MG_GET_XS_SCATTER {4}; +constexpr int MG_GET_XS_SCATTER_MULT {5}; +constexpr int MG_GET_XS_SCATTER_FMU_MULT {6}; +constexpr int MG_GET_XS_SCATTER_FMU {7}; +constexpr int MG_GET_XS_FISSION {8}; +constexpr int MG_GET_XS_KAPPA_FISSION {9}; +constexpr int MG_GET_XS_PROMPT_NU_FISSION {10}; +constexpr int MG_GET_XS_DELAYED_NU_FISSION {11}; +constexpr int MG_GET_XS_NU_FISSION {12}; +constexpr int MG_GET_XS_CHI_PROMPT {13}; +constexpr int MG_GET_XS_CHI_DELAYED {14}; + } // namespace openmc diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 507bdf8a9..189ca7c21 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -19,8 +19,8 @@ module input_xml use material_header use mesh_header use message_passing - use mgxs_data, only: create_macro_xs, read_mgxs, read_mgxs2, create_macro_xs2 - use mgxs_header + use mgxs_data, only: create_macro_xs, read_mgxs + use mgxs_interface use nuclide_header use output, only: title, header, print_plot use plot_header @@ -84,9 +84,7 @@ contains else ! Create material macroscopic data for MGXS call read_mgxs() - call read_mgxs2() call create_macro_xs() - call create_macro_xs2() end if call time_read_xs % stop() end if @@ -3856,7 +3854,7 @@ contains if (run_CE) then awr = nuclides(mat % nuclide(j)) % awr else - awr = nuclides_MG(mat % nuclide(j)) % obj % awr + awr = get_awr_c(mat % nuclide(j)) end if ! if given weight percent, convert all values so that they are divided @@ -3881,7 +3879,7 @@ contains if (run_CE) then awr = nuclides(mat % nuclide(j)) % awr else - awr = nuclides_MG(mat % nuclide(j)) % obj % awr + awr = get_awr_c(mat % nuclide(j)) end if x = mat % atom_density(j) sum_percent = sum_percent + x*awr diff --git a/src/mgxs.cpp b/src/mgxs.cpp index ec711e130..4f92a64ee 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -2,6 +2,11 @@ namespace openmc { +// Storage for the MGXS data +std::vector nuclides_MG; +std::vector macro_xs; + + //============================================================================== // Mgxs base-class methods //============================================================================== @@ -390,112 +395,149 @@ void Mgxs::combine(std::vector& micros, double_1dvec& scalars, } -double Mgxs::get_xs(const char* xstype, const int gin, int* gout, double* mu, +double Mgxs::get_xs(const int xstype, const int gin, int* gout, double* mu, int* dg) { // This method assumes that the temperature and angle indices are set double val; - if (std::strcmp(xstype, "total")) { + switch(xstype) { + case MG_GET_XS_TOTAL: val = xs[index_temp].total[index_pol][index_azi][gin]; - } else if (std::strcmp(xstype, "absorption")) { + break; + case MG_GET_XS_ABSORPTION: val = xs[index_temp].absorption[index_pol][index_azi][gin]; - } else if (std::strcmp(xstype, "inverse-velocity")) { + break; + case MG_GET_XS_INVERSE_VELOCITY: val = xs[index_temp].inverse_velocity[index_pol][index_azi][gin]; - } else if (std::strcmp(xstype, "decay rate")) { + break; + case MG_GET_XS_DECAY_RATE: if (dg != nullptr) { val = xs[index_temp].decay_rate[index_pol][index_azi][*dg + 1]; } else { val = xs[index_temp].decay_rate[index_pol][index_azi][0]; } - } else if ((std::strcmp(xstype, "scatter")) || - (std::strcmp(xstype, "scatter/mult")) || - (std::strcmp(xstype, "scatter*f_mu/mult")) || - (std::strcmp(xstype, "scatter*f_mu"))) { + break; + case MG_GET_XS_SCATTER: + case MG_GET_XS_SCATTER_MULT: + case MG_GET_XS_SCATTER_FMU_MULT: + case MG_GET_XS_SCATTER_FMU: val = xs[index_temp].scatter[index_pol] [index_azi]->get_xs(xstype, gin, gout, mu); - } else if (fissionable && std::strcmp(xstype, "fission")) { - val = xs[index_temp].fission[index_pol][index_azi][gin]; - } else if (fissionable && std::strcmp(xstype, "kappa-fission")) { - val = xs[index_temp].kappa_fission[index_pol][index_azi][gin]; - } else if (fissionable && std::strcmp(xstype, "prompt-nu-fission")) { - val = xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; - } else if (fissionable && std::strcmp(xstype, "delayed-nu-fission")) { - if (dg != nullptr) { - val = xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin][*dg]; + break; + case MG_GET_XS_FISSION: + if (fissionable) { + val = xs[index_temp].fission[index_pol][index_azi][gin]; } else { val = 0.; - for (auto& num : xs[index_temp].delayed_nu_fission[index_pol] - [index_azi][gin]) { - val += num; - } } - } else if (fissionable && std::strcmp(xstype, "nu-fission")) { - val = xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; - for (auto& num : xs[index_temp].delayed_nu_fission[index_pol] - [index_azi][gin]) { - val += num; - } - } else if (fissionable && std::strcmp(xstype, "chi-prompt")) { - if (gout != nullptr) { - val = xs[index_temp].chi_prompt[index_pol][index_azi][gin][*gout]; + break; + case MG_GET_XS_KAPPA_FISSION: + if (fissionable) { + val = xs[index_temp].kappa_fission[index_pol][index_azi][gin]; } else { - // provide an outgoing group-wise sum val = 0.; - for (auto& num : xs[index_temp].chi_prompt[index_pol][index_azi][gin]) { - val += num; - } } - } else if (fissionable && std::strcmp(xstype, "chi-delayed")) { - if (gout != nullptr) { + break; + case MG_GET_XS_PROMPT_NU_FISSION: + if (fissionable) { + val = xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; + } else { + val = 0.; + } + break; + case MG_GET_XS_DELAYED_NU_FISSION: + if (fissionable) { if (dg != nullptr) { - val = xs[index_temp].chi_delayed[index_pol][index_azi][gin][*gout][*dg]; + val = xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin][*dg]; } else { - val = xs[index_temp].chi_delayed[index_pol][index_azi][gin][*gout][0]; + val = 0.; + for (auto& num : xs[index_temp].delayed_nu_fission[index_pol] + [index_azi][gin]) { + val += num; + } } } else { - if (dg != nullptr) { + val = 0.; + } + break; + case MG_GET_XS_NU_FISSION: + if (fissionable) { + val = xs[index_temp].nu_fission[index_pol][index_azi][gin]; + } else { + val = 0.; + } + break; + case MG_GET_XS_CHI_PROMPT: + if (fissionable) { + if (gout != nullptr) { + val = xs[index_temp].chi_prompt[index_pol][index_azi][gin][*gout]; + } else { + // provide an outgoing group-wise sum val = 0.; - for (int i = 0; i < xs[index_temp].chi_delayed[index_pol] - [index_azi][gin].size(); i++) { - val += xs[index_temp].chi_delayed[index_pol][index_azi][gin][i][*dg]; + for (auto& num : xs[index_temp].chi_prompt[index_pol][index_azi][gin]) { + val += num; + } + } + } else { + val = 0.; + } + break; + case MG_GET_XS_CHI_DELAYED: + if (fissionable) { + if (gout != nullptr) { + if (dg != nullptr) { + val = xs[index_temp].chi_delayed[index_pol][index_azi][gin][*gout][*dg]; + } else { + val = xs[index_temp].chi_delayed[index_pol][index_azi][gin][*gout][0]; } } else { - val = 0.; - for (int i = 0; i < xs[index_temp].chi_delayed[index_pol] - [index_azi][gin].size(); i++) { - for (auto& num : xs[index_temp].chi_delayed[index_pol] - [index_azi][gin][i]) { - val += num; + if (dg != nullptr) { + val = 0.; + for (int i = 0; i < xs[index_temp].chi_delayed[index_pol] + [index_azi][gin].size(); i++) { + val += xs[index_temp].chi_delayed[index_pol][index_azi][gin][i][*dg]; + } + } else { + val = 0.; + for (int i = 0; i < xs[index_temp].chi_delayed[index_pol] + [index_azi][gin].size(); i++) { + for (auto& num : xs[index_temp].chi_delayed[index_pol] + [index_azi][gin][i]) { + val += num; + } } } } + } else { + val = 0.; } - } else { + break; + default: val = 0.; } return val; } -void Mgxs::sample_fission_energy(const int gin, const double nu_fission, - int& dg, int& gout) +void Mgxs::sample_fission_energy(const int gin, int& dg, int& gout) { // This method assumes that the temperature and angle indices are set + double nu_fission = xs[index_temp].nu_fission[index_pol][index_azi][gin]; + // Find the probability of having a prompt neutron double prob_prompt = - xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin] / - nu_fission; + xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; // sample random numbers - double xi_pd = prn(); + double xi_pd = prn() * nu_fission; double xi_gout = prn(); // Select whether the neutron is prompt or delayed if (xi_pd <= prob_prompt) { // the neutron is prompt - // set the delayed group for the particle to be 0, indicating prompt - dg = 0; + // set the delayed group for the particle to be -1, indicating prompt + dg = -1; // sample the outgoing energy group gout = 0; @@ -514,12 +556,11 @@ void Mgxs::sample_fission_energy(const int gin, const double nu_fission, while (xi_pd >= prob_prompt) { dg++; prob_prompt += - xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin][dg] / - nu_fission; + xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin][dg]; } // adjust dg in case of round-off error - dg = std::min(dg, num_delayed_groups); + dg = std::min(dg, num_delayed_groups - 1); // sample the outgoing energy group gout = 0; @@ -552,11 +593,7 @@ void Mgxs::calculate_xs(const int gin, const double sqrtkT, const double uvw[3], abs_xs = xs[index_temp].absorption[index_pol][index_azi][gin]; if (fissionable) { - // nu-fission is made up of the prompt and all the delayed nu_fission data - nu_fiss_xs = xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; - for (auto& val : xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin]) { - nu_fiss_xs += val; - } + nu_fiss_xs = xs[index_temp].nu_fission[index_pol][index_azi][gin]; } else { nu_fiss_xs = 0.; } @@ -580,7 +617,7 @@ bool Mgxs::equiv(const Mgxs& that) } -inline void Mgxs::set_temperature_index(const double sqrtkT) +void Mgxs::set_temperature_index(const double sqrtkT) { // See if we need to find the new index if (sqrtkT != last_sqrtkT) { @@ -600,7 +637,7 @@ inline void Mgxs::set_temperature_index(const double sqrtkT) } -inline void Mgxs::set_angle_index(const double uvw[3]) +void Mgxs::set_angle_index(const double uvw[3]) { // See if we need to find the new index if ((uvw[0] != last_uvw[0]) || (uvw[1] != last_uvw[1]) || @@ -622,98 +659,4 @@ inline void Mgxs::set_angle_index(const double uvw[3]) } } -//============================================================================== -// Mgxs data loading interface methods -//============================================================================== - -void add_mgxs(hid_t file_id, char* name, int energy_groups, - int delayed_groups, int n_temps, double temps[], int& method, - double tolerance, int max_order, bool legendre_to_tabular, - int legendre_to_tabular_points) -{ - //!! mgxs_data.F90 will be modified to just create the list of names - //!! in the order needed - // Convert temps to a vector for the from_hdf5 function - double_1dvec temperature; - temperature.assign(temps, temps + n_temps); - - // TODO: C++ replacement for write_message - // write_message("Loading " + std::string(names[i]) + " data...", 6); - - // Check to make sure cross section set exists in the library - hid_t xs_grp; - if (object_exists(file_id, name)) { - xs_grp = open_group(file_id, name); - } else { - fatal_error("Data for " + std::string(name) + " does not exist in " - + "provided MGXS Library"); - } - - Mgxs mg; - mg.from_hdf5(xs_grp, energy_groups, delayed_groups, - temperature, method, tolerance, max_order, legendre_to_tabular, - legendre_to_tabular_points); - - nuclides_MG.push_back(mg); -} - - -bool query_fissionable(const int n_nuclides, const int i_nuclides[]) -{ - bool result = false; - for (int n = 0; n < n_nuclides; n++) { - if (nuclides_MG[i_nuclides[n] - 1].fissionable) result = true; - } - return result; -} - - -void create_macro_xs(char* mat_name, const int n_nuclides, - const int i_nuclides[], const int n_temps, const double temps[], - const double atom_densities[], int& method, const double tolerance) -{ - Mgxs macro; - if (n_temps > 0) { - // // Convert temps to a vector - double_1dvec temperature; - temperature.assign(temps, temps + n_temps); - - // Convert atom_densities to a vector - double_1dvec atom_densities_vec; - atom_densities_vec.assign(atom_densities, atom_densities + n_nuclides); - - // Build array of pointers to nuclides_MG's Mgxs objects needed for this - // material - std::vector mgxs_ptr(n_nuclides); - for (int n = 0; n < n_nuclides; n++) { - mgxs_ptr[n] = &nuclides_MG[i_nuclides[n] - 1]; - } - - macro.build_macro(mat_name, temperature, mgxs_ptr, atom_densities_vec, - method, tolerance); - } - macro_xs.push_back(macro); -} - -//============================================================================== -// Mgxs tracking/transport/tallying interface methods -//============================================================================== - -void calculate_xs(const int i_mat, const int gin, const double sqrtkT, - const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs) -{ - macro_xs[i_mat - 1].calculate_xs(gin - 1, sqrtkT, uvw, total_xs, abs_xs, - nu_fiss_xs); -} - -void scatter(const int i_mat, const int gin, int& gout, double& mu, - double& wgt, double uvw[3]) -{ - int gout_c = gout - 1; - macro_xs[i_mat - 1].sample_scatter(gin - 1, gout_c, mu, wgt); - gout = gout_c + 1; - - rotate_angle_c(uvw, mu, nullptr); -} - } // namespace openmc \ No newline at end of file diff --git a/src/mgxs.h b/src/mgxs.h index 14c4e4e89..2fc3e2bec 100644 --- a/src/mgxs.h +++ b/src/mgxs.h @@ -29,29 +29,31 @@ namespace openmc { class Mgxs { private: - std::string name; // name of dataset, e.g., UO2 - double awr; // atomic weight ratio double_1dvec kTs; // temperature in eV (k * T) int scatter_format; // flag for if this is legendre, histogram, or tabular int num_delayed_groups; // number of delayed neutron groups int num_groups; // number of energy groups - int index_temp; // cache of temperature index double last_sqrtkT; // cache of the temperature corresponding to index_temp std::vector xs; // Cross section data int n_pol; int n_azi; - int index_pol; // cache fof the angle indices - int index_azi; double_1dvec polar; double_1dvec azimuthal; - double last_uvw[3]; void _metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, const int in_num_delayed_groups, double_1dvec& temperature, int& method, const double tolerance, int_1dvec& temps_to_read, int& order_dim, bool& is_isotropic); + bool equiv(const Mgxs& that); public: + std::string name; // name of dataset, e.g., UO2 + double awr; // atomic weight ratio bool fissionable; // Is this fissionable + // TODO: The following attributes be private when Fortran is fully replaced + int index_pol; // cache for the angle indices + int index_azi; + double last_uvw[3]; // cache of the angle corresponding to the above indices + int index_temp; // cache of temperature index void init(const std::string& in_name, const double in_awr, const double_1dvec& in_kTs, const bool in_fissionable, const int in_scatter_format, const int in_num_groups, @@ -66,40 +68,15 @@ class Mgxs { double_1dvec& temperature, int& method, double tolerance, int max_order, bool legendre_to_tabular, int legendre_to_tabular_points); - double get_xs(const char* xstype, const int gin, int* gout, double* mu, + double get_xs(const int xstype, const int gin, int* gout, double* mu, int* dg); - void sample_fission_energy(const int gin, const double nu_fission, - int& dg, int& gout); + void sample_fission_energy(const int gin, int& dg, int& gout); void sample_scatter(const int gin, int& gout, double& mu, double& wgt); void calculate_xs(const int gin, const double sqrtkT, const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs); - bool equiv(const Mgxs& that); - inline void set_temperature_index(const double sqrtkT); - inline void set_angle_index(const double uvw[3]); + void set_temperature_index(const double sqrtkT); + void set_angle_index(const double uvw[3]); }; -extern "C" void add_mgxs(hid_t file_id, char* name, int energy_groups, - int delayed_groups, int n_temps, double temps[], int& method, - double tolerance, int max_order, bool legendre_to_tabular, - int legendre_to_tabular_points); - -extern "C" bool query_fissionable(const int n_nuclides, const int i_nuclides[]); - -extern "C" void create_macro_xs(char* mat_name, const int n_nuclides, - const int i_nuclides[], const int n_temps, const double temps[], - const double atom_densities[], int& method, const double tolerance); - -extern "C" void calculate_xs(const int i_mat, const int gin, - const double sqrtkT, const double uvw[3], double& total_xs, - double& abs_xs, double& nu_fiss_xs); - -extern "C" void scatter(const int i_mat, const int gin, int& gout, double& mu, - double& wgt, double uvw[3]); - - -// Storage for the MGXS data -std::vector nuclides_MG; -std::vector macro_xs; - } // namespace openmc #endif // MGXS_H \ No newline at end of file diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index 697737880..0278c96eb 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -9,7 +9,7 @@ module mgxs_data use geometry_header, only: get_temperatures, cells use hdf5_interface use material_header, only: Material, materials, n_materials - use mgxs_header + use mgxs_interface use nuclide_header, only: n_nuclides use set_header, only: SetChar use settings @@ -17,50 +17,6 @@ module mgxs_data use string, only: to_lower implicit none - interface - subroutine add_mgxs_c(file_id, name, energy_groups, delayed_groups, & - n_temps, temps, method, tolerance, max_order, legendre_to_tabular, & - legendre_to_tabular_points) bind(C, name='add_mgxs') - use ISO_C_BINDING - import HID_T - implicit none - integer(HID_T), value, intent(in) :: file_id - character(kind=C_CHAR),intent(in) :: name(*) - integer(C_INT), value, intent(in) :: energy_groups - integer(C_INT), value, intent(in) :: delayed_groups - integer(C_INT), value, intent(in) :: n_temps - real(C_DOUBLE), intent(in) :: temps(1:n_temps) - integer(C_INT), intent(inout) :: method - real(C_DOUBLE), value, intent(in) :: tolerance - integer(C_INT), value, intent(in) :: max_order - logical(C_BOOL),value, intent(in) :: legendre_to_tabular - integer(C_INT), value, intent(in) :: legendre_to_tabular_points - end subroutine add_mgxs_c - - function query_fissionable_c(n_nuclides, i_nuclides) result(result) & - bind(C, name='query_fissionable') - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: n_nuclides - integer(C_INT), intent(in) :: i_nuclides(1:n_nuclides) - logical(C_BOOL) :: result - end function query_fissionable_c - - subroutine create_macro_xs_c(name, n_nuclides, i_nuclides, n_temps, temps, & - atom_densities, method, tolerance) bind(C, name='create_macro_xs') - use ISO_C_BINDING - implicit none - character(kind=C_CHAR),intent(in) :: name(*) - integer(C_INT), value, intent(in) :: n_nuclides - integer(C_INT), intent(in) :: i_nuclides(1:n_nuclides) - integer(C_INT), value, intent(in) :: n_temps - real(C_DOUBLE), intent(in) :: temps(1:n_temps) - real(C_DOUBLE), intent(in) :: atom_densities(1:n_nuclides) - integer(C_INT), intent(inout) :: method - real(C_DOUBLE), value, intent(in) :: tolerance - end subroutine create_macro_xs_c - end interface - contains !=============================================================================== @@ -68,150 +24,150 @@ contains ! nuclides and sab_tables arrays !=============================================================================== + ! subroutine read_mgxs() + ! integer :: i ! index in materials array + ! integer :: j ! index over nuclides in material + ! integer :: i_nuclide ! index in nuclides array + ! character(20) :: name ! name of library to load + ! integer :: representation ! Data representation + ! character(MAX_LINE_LEN) :: temp_str + ! type(Material), pointer :: mat + ! type(SetChar) :: already_read + ! integer(HID_T) :: file_id + ! integer(HID_T) :: xsdata_group + ! logical :: file_exists + ! type(VectorReal), allocatable :: temps(:) + ! character(MAX_WORD_LEN) :: word + ! integer, allocatable :: array(:) + + ! ! Check if MGXS Library exists + ! inquire(FILE=path_cross_sections, EXIST=file_exists) + ! if (.not. file_exists) then + + ! ! Could not find MGXS Library file + ! call fatal_error("Cross sections HDF5 file '" & + ! // trim(path_cross_sections) // "' does not exist!") + ! end if + + ! call write_message("Loading cross section data...", 5) + + ! ! Get temperatures + ! call get_temperatures(temps) + + ! ! Open file for reading + ! file_id = file_open(path_cross_sections, 'r', parallel=.true.) + + ! ! Read filetype + ! call read_attribute(word, file_id, "filetype") + ! if (word /= 'mgxs') then + ! call fatal_error("Provided MGXS Library is not a MGXS Library file.") + ! end if + + ! ! Read revision number for the MGXS Library file and make sure it matches + ! ! with the current version + ! call read_attribute(array, file_id, "version") + ! if (any(array /= VERSION_MGXS_LIBRARY)) then + ! call fatal_error("MGXS Library file version does not match current & + ! &version supported by OpenMC.") + ! end if + + ! ! allocate arrays for MGXS storage and cross section cache + ! allocate(nuclides_MG(n_nuclides)) + + ! ! ========================================================================== + ! ! READ ALL MGXS CROSS SECTION TABLES + + ! ! Loop over all files + ! MATERIAL_LOOP: do i = 1, n_materials + ! mat => materials(i) + + ! NUCLIDE_LOOP: do j = 1, mat % n_nuclides + ! name = mat % names(j) + + ! if (.not. already_read % contains(name)) then + ! i_nuclide = mat % nuclide(j) + + ! call write_message("Loading " // trim(name) // " data...", 6) + + ! ! Check to make sure cross section set exists in the library + ! if (object_exists(file_id, trim(name))) then + ! xsdata_group = open_group(file_id, trim(name)) + ! else + ! call fatal_error("Data for '" // trim(name) // "' does not exist in "& + ! &// trim(path_cross_sections)) + ! end if + + ! ! First find out the data representation + ! if (attribute_exists(xsdata_group, "representation")) then + + ! call read_attribute(temp_str, xsdata_group, "representation") + + ! if (trim(temp_str) == 'isotropic') then + ! representation = MGXS_ISOTROPIC + ! else if (trim(temp_str) == 'angle') then + ! representation = MGXS_ANGLE + ! else + ! call fatal_error("Invalid Data Representation!") + ! end if + ! else + ! ! Default to isotropic representation + ! representation = MGXS_ISOTROPIC + ! end if + + ! ! Now allocate accordingly + ! select case(representation) + + ! case(MGXS_ISOTROPIC) + ! allocate(MgxsIso :: nuclides_MG(i_nuclide) % obj) + + ! case(MGXS_ANGLE) + ! allocate(MgxsAngle :: nuclides_MG(i_nuclide) % obj) + + ! end select + + ! ! Now read in the data specific to the type we just declared + ! call nuclides_MG(i_nuclide) % obj % from_hdf5(xsdata_group, & + ! num_energy_groups, num_delayed_groups, temps(i_nuclide), & + ! temperature_method, temperature_tolerance, max_order, & + ! legendre_to_tabular, legendre_to_tabular_points) + + ! ! Add name to dictionary + ! call already_read % add(name) + + ! call close_group(xsdata_group) + + ! end if + ! end do NUCLIDE_LOOP + ! end do MATERIAL_LOOP + + ! ! Avoid some valgrind leak errors + ! call already_read % clear() + + ! ! Loop around material + ! MATERIAL_LOOP3: do i = 1, n_materials + + ! ! Get material + ! mat => materials(i) + + ! ! Loop around nuclides in material + ! NUCLIDE_LOOP2: do j = 1, mat % n_nuclides + + ! ! Is this fissionable? + ! if (nuclides_MG(mat % nuclide(j)) % obj % fissionable) then + ! mat % fissionable = .true. + ! end if + ! if (mat % fissionable) then + ! exit NUCLIDE_LOOP2 + ! end if + + ! end do NUCLIDE_LOOP2 + ! end do MATERIAL_LOOP3 + + ! call file_close(file_id) + + ! end subroutine read_mgxs + subroutine read_mgxs() - integer :: i ! index in materials array - integer :: j ! index over nuclides in material - integer :: i_nuclide ! index in nuclides array - character(20) :: name ! name of library to load - integer :: representation ! Data representation - character(MAX_LINE_LEN) :: temp_str - type(Material), pointer :: mat - type(SetChar) :: already_read - integer(HID_T) :: file_id - integer(HID_T) :: xsdata_group - logical :: file_exists - type(VectorReal), allocatable :: temps(:) - character(MAX_WORD_LEN) :: word - integer, allocatable :: array(:) - - ! Check if MGXS Library exists - inquire(FILE=path_cross_sections, EXIST=file_exists) - if (.not. file_exists) then - - ! Could not find MGXS Library file - call fatal_error("Cross sections HDF5 file '" & - // trim(path_cross_sections) // "' does not exist!") - end if - - call write_message("Loading cross section data...", 5) - - ! Get temperatures - call get_temperatures(temps) - - ! Open file for reading - file_id = file_open(path_cross_sections, 'r', parallel=.true.) - - ! Read filetype - call read_attribute(word, file_id, "filetype") - if (word /= 'mgxs') then - call fatal_error("Provided MGXS Library is not a MGXS Library file.") - end if - - ! Read revision number for the MGXS Library file and make sure it matches - ! with the current version - call read_attribute(array, file_id, "version") - if (any(array /= VERSION_MGXS_LIBRARY)) then - call fatal_error("MGXS Library file version does not match current & - &version supported by OpenMC.") - end if - - ! allocate arrays for MGXS storage and cross section cache - allocate(nuclides_MG(n_nuclides)) - - ! ========================================================================== - ! READ ALL MGXS CROSS SECTION TABLES - - ! Loop over all files - MATERIAL_LOOP: do i = 1, n_materials - mat => materials(i) - - NUCLIDE_LOOP: do j = 1, mat % n_nuclides - name = mat % names(j) - - if (.not. already_read % contains(name)) then - i_nuclide = mat % nuclide(j) - - call write_message("Loading " // trim(name) // " data...", 6) - - ! Check to make sure cross section set exists in the library - if (object_exists(file_id, trim(name))) then - xsdata_group = open_group(file_id, trim(name)) - else - call fatal_error("Data for '" // trim(name) // "' does not exist in "& - &// trim(path_cross_sections)) - end if - - ! First find out the data representation - if (attribute_exists(xsdata_group, "representation")) then - - call read_attribute(temp_str, xsdata_group, "representation") - - if (trim(temp_str) == 'isotropic') then - representation = MGXS_ISOTROPIC - else if (trim(temp_str) == 'angle') then - representation = MGXS_ANGLE - else - call fatal_error("Invalid Data Representation!") - end if - else - ! Default to isotropic representation - representation = MGXS_ISOTROPIC - end if - - ! Now allocate accordingly - select case(representation) - - case(MGXS_ISOTROPIC) - allocate(MgxsIso :: nuclides_MG(i_nuclide) % obj) - - case(MGXS_ANGLE) - allocate(MgxsAngle :: nuclides_MG(i_nuclide) % obj) - - end select - - ! Now read in the data specific to the type we just declared - call nuclides_MG(i_nuclide) % obj % from_hdf5(xsdata_group, & - num_energy_groups, num_delayed_groups, temps(i_nuclide), & - temperature_method, temperature_tolerance, max_order, & - legendre_to_tabular, legendre_to_tabular_points) - - ! Add name to dictionary - call already_read % add(name) - - call close_group(xsdata_group) - - end if - end do NUCLIDE_LOOP - end do MATERIAL_LOOP - - ! Avoid some valgrind leak errors - call already_read % clear() - - ! Loop around material - MATERIAL_LOOP3: do i = 1, n_materials - - ! Get material - mat => materials(i) - - ! Loop around nuclides in material - NUCLIDE_LOOP2: do j = 1, mat % n_nuclides - - ! Is this fissionable? - if (nuclides_MG(mat % nuclide(j)) % obj % fissionable) then - mat % fissionable = .true. - end if - if (mat % fissionable) then - exit NUCLIDE_LOOP2 - end if - - end do NUCLIDE_LOOP2 - end do MATERIAL_LOOP3 - - call file_close(file_id) - - end subroutine read_mgxs - - subroutine read_mgxs2() integer :: i ! index in materials array integer :: j ! index over nuclides in material integer :: i_nuclide ! index in nuclides array @@ -286,55 +242,55 @@ contains ! Avoid some valgrind leak errors call already_read % clear() - end subroutine read_mgxs2 + end subroutine read_mgxs !=============================================================================== ! CREATE_MACRO_XS generates the macroscopic xs from the microscopic input data !=============================================================================== + ! subroutine create_macro_xs() + ! integer :: i_mat ! index in materials array + ! type(Material), pointer :: mat ! current material + ! type(VectorReal), allocatable :: kTs(:) + + ! allocate(macro_xs(n_materials)) + + ! ! Get temperatures to read for each material + ! call get_mat_kTs(kTs) + + ! ! Force all nuclides in a material to be the same representation. + ! ! Therefore type(nuclides(mat % nuclide(1)) % obj) dictates type(macroxs). + ! ! At the same time, we will find the scattering type, as that will dictate + ! ! how we allocate the scatter object within macroxs.allocate(macro_xs(n_materials)) + ! do i_mat = 1, n_materials + + ! ! Get the material + ! mat => materials(i_mat) + + ! ! Get the scattering type for the first nuclide + ! select type(nuc => nuclides_MG(mat % nuclide(1)) % obj) + ! type is (MgxsIso) + ! allocate(MgxsIso :: macro_xs(i_mat) % obj) + ! type is (MgxsAngle) + ! allocate(MgxsAngle :: macro_xs(i_mat) % obj) + ! end select + + ! ! Do not read materials which we do not actually use in the problem to + ! ! reduce storage + ! if (allocated(kTs(i_mat) % data)) then + ! call macro_xs(i_mat) % obj % combine(kTs(i_mat), mat, nuclides_MG, & + ! num_energy_groups, num_delayed_groups, max_order, & + ! temperature_tolerance, temperature_method) + ! end if + ! end do + + ! end subroutine create_macro_xs + + subroutine create_macro_xs() integer :: i_mat ! index in materials array type(Material), pointer :: mat ! current material type(VectorReal), allocatable :: kTs(:) - - allocate(macro_xs(n_materials)) - - ! Get temperatures to read for each material - call get_mat_kTs(kTs) - - ! Force all nuclides in a material to be the same representation. - ! Therefore type(nuclides(mat % nuclide(1)) % obj) dictates type(macroxs). - ! At the same time, we will find the scattering type, as that will dictate - ! how we allocate the scatter object within macroxs.allocate(macro_xs(n_materials)) - do i_mat = 1, n_materials - - ! Get the material - mat => materials(i_mat) - - ! Get the scattering type for the first nuclide - select type(nuc => nuclides_MG(mat % nuclide(1)) % obj) - type is (MgxsIso) - allocate(MgxsIso :: macro_xs(i_mat) % obj) - type is (MgxsAngle) - allocate(MgxsAngle :: macro_xs(i_mat) % obj) - end select - - ! Do not read materials which we do not actually use in the problem to - ! reduce storage - if (allocated(kTs(i_mat) % data)) then - call macro_xs(i_mat) % obj % combine(kTs(i_mat), mat, nuclides_MG, & - num_energy_groups, num_delayed_groups, max_order, & - temperature_tolerance, temperature_method) - end if - end do - - end subroutine create_macro_xs - - - subroutine create_macro_xs2() - integer :: i_mat ! index in materials array - type(Material), pointer :: mat ! current material - type(VectorReal), allocatable :: kTs(:) character(MAX_WORD_LEN) :: name ! name of material ! Get temperatures to read for each material @@ -360,7 +316,7 @@ contains end if end do - end subroutine create_macro_xs2 + end subroutine create_macro_xs !=============================================================================== diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 index be3e4e9d4..09db7217e 100644 --- a/src/mgxs_header.F90 +++ b/src/mgxs_header.F90 @@ -11,7 +11,6 @@ module mgxs_header use math, only: evaluate_legendre use nuclide_header, only: MaterialMacroXS use random_lcg, only: prn - use scattdata_header use string use stl_vector, only: VectorInt, VectorReal diff --git a/src/mgxs_interface.F90 b/src/mgxs_interface.F90 new file mode 100644 index 000000000..7404af686 --- /dev/null +++ b/src/mgxs_interface.F90 @@ -0,0 +1,201 @@ +module mgxs_interface + + use, intrinsic :: ISO_C_BINDING + + use hdf5_interface + + implicit none + + interface + + subroutine add_mgxs_c(file_id, name, energy_groups, delayed_groups, & + n_temps, temps, method, tolerance, max_order, legendre_to_tabular, & + legendre_to_tabular_points) bind(C) + use ISO_C_BINDING + import HID_T + implicit none + integer(HID_T), value, intent(in) :: file_id + character(kind=C_CHAR),intent(in) :: name(*) + integer(C_INT), value, intent(in) :: energy_groups + integer(C_INT), value, intent(in) :: delayed_groups + integer(C_INT), value, intent(in) :: n_temps + real(C_DOUBLE), intent(in) :: temps(1:n_temps) + integer(C_INT), intent(inout) :: method + real(C_DOUBLE), value, intent(in) :: tolerance + integer(C_INT), value, intent(in) :: max_order + logical(C_BOOL),value, intent(in) :: legendre_to_tabular + integer(C_INT), value, intent(in) :: legendre_to_tabular_points + end subroutine add_mgxs_c + + function query_fissionable_c(n_nuclides, i_nuclides) result(result) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: n_nuclides + integer(C_INT), intent(in) :: i_nuclides(1:n_nuclides) + logical(C_BOOL) :: result + end function query_fissionable_c + + subroutine create_macro_xs_c(name, n_nuclides, i_nuclides, n_temps, temps, & + atom_densities, method, tolerance) bind(C) + use ISO_C_BINDING + implicit none + character(kind=C_CHAR),intent(in) :: name(*) + integer(C_INT), value, intent(in) :: n_nuclides + integer(C_INT), intent(in) :: i_nuclides(1:n_nuclides) + integer(C_INT), value, intent(in) :: n_temps + real(C_DOUBLE), intent(in) :: temps(1:n_temps) + real(C_DOUBLE), intent(in) :: atom_densities(1:n_nuclides) + integer(C_INT), intent(inout) :: method + real(C_DOUBLE), value, intent(in) :: tolerance + end subroutine create_macro_xs_c + + subroutine calculate_xs_c(i_mat, gin, sqrtkT, uvw, total_xs, abs_xs, & + nu_fiss_xs) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: i_mat + integer(C_INT), value, intent(in) :: gin + real(C_DOUBLE), value, intent(in) :: sqrtkT + real(C_DOUBLE), intent(in) :: uvw(1:3) + real(C_DOUBLE), intent(inout) :: total_xs + real(C_DOUBLE), intent(inout) :: abs_xs + real(C_DOUBLE), intent(inout) :: nu_fiss_xs + end subroutine calculate_xs_c + + subroutine sample_scatter_c(i_mat, gin, gout, mu, wgt, uvw) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: i_mat + integer(C_INT), value, intent(in) :: gin + integer(C_INT), intent(inout) :: gout + real(C_DOUBLE), intent(inout) :: mu + real(C_DOUBLE), intent(inout) :: wgt + real(C_DOUBLE), intent(inout) :: uvw(1:3) + end subroutine sample_scatter_c + + subroutine sample_fission_energy_c(i_mat, gin, dg, gout) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: i_mat + integer(C_INT), value, intent(in) :: gin + integer(C_INT), intent(inout) :: dg + integer(C_INT), intent(inout) :: gout + end subroutine sample_fission_energy_c + + subroutine get_name_c(index, name_len, name) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: name_len + character(kind=C_CHAR), intent(inout) :: name(name_len) + end subroutine get_name_c + + function get_awr_c(index) result(awr) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + real(C_DOUBLE) :: awr + end function get_awr_c + + function get_nuclide_xs_c(index, xstype, gin, gout, mu, dg) result(val) & + bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: xstype + integer(C_INT), value, intent(in) :: gin + integer(C_INT), optional, intent(in) :: gout + real(C_DOUBLE), optional, intent(in) :: mu + integer(C_INT), optional, intent(in) :: dg + real(C_DOUBLE) :: val + end function get_nuclide_xs_c + + function get_macro_xs_c(index, xstype, gin, gout, mu, dg) result(val) & + bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: xstype + integer(C_INT), value, intent(in) :: gin + integer(C_INT), optional, intent(in) :: gout + real(C_DOUBLE), optional, intent(in) :: mu + integer(C_INT), optional, intent(in) :: dg + real(C_DOUBLE) :: val + end function get_macro_xs_c + + subroutine set_nuclide_angle_index_c(index, uvw, last_pol, last_azi, & + last_uvw) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + real(C_DOUBLE), intent(in) :: uvw(1:3) + integer(C_INT), intent(inout) :: last_pol + integer(C_INT), intent(inout) :: last_azi + real(C_DOUBLE), intent(inout) :: last_uvw(1:3) + end subroutine set_nuclide_angle_index_c + + subroutine reset_nuclide_angle_index_c(index, last_pol, last_azi, & + last_uvw) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: last_pol + integer(C_INT), value, intent(in) :: last_azi + real(C_DOUBLE), intent(in) :: last_uvw(1:3) + end subroutine reset_nuclide_angle_index_c + + subroutine set_macro_angle_index_c(index, uvw, last_pol, last_azi, & + last_uvw) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + real(C_DOUBLE), intent(in) :: uvw(1:3) + integer(C_INT), intent(inout) :: last_pol + integer(C_INT), intent(inout) :: last_azi + real(C_DOUBLE), intent(inout) :: last_uvw(1:3) + end subroutine set_macro_angle_index_c + + subroutine reset_macro_angle_index_c(index, last_pol, last_azi, & + last_uvw) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: last_pol + integer(C_INT), value, intent(in) :: last_azi + real(C_DOUBLE), intent(in) :: last_uvw(1:3) + end subroutine reset_macro_angle_index_c + + function set_nuclide_temperature_index_c(index, sqrtkT) result(last_temp) & + bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + real(C_DOUBLE), value, intent(in) :: sqrtkT + integer(C_INT) :: last_temp + end function set_nuclide_temperature_index_c + + subroutine reset_nuclide_temperature_index_c(index, last_temp) bind(C) + use ISO_C_BINDING + implicit none + integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: last_temp + end subroutine reset_nuclide_temperature_index_c + + end interface + + ! Number of energy groups + integer(C_INT) :: num_energy_groups + + ! Number of delayed groups + integer(C_INT) :: num_delayed_groups + + ! 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(:) + +end module mgxs_interface \ No newline at end of file diff --git a/src/mgxs_interface.cpp b/src/mgxs_interface.cpp new file mode 100644 index 000000000..60afc2848 --- /dev/null +++ b/src/mgxs_interface.cpp @@ -0,0 +1,247 @@ +#include "mgxs_interface.h" + +namespace openmc { + +//============================================================================== +// Mgxs data loading interface methods +//============================================================================== + +void add_mgxs_c(hid_t file_id, char* name, int energy_groups, + int delayed_groups, int n_temps, double temps[], int& method, + double tolerance, int max_order, bool legendre_to_tabular, + int legendre_to_tabular_points) +{ + //!! mgxs_data.F90 will be modified to just create the list of names + //!! in the order needed + // Convert temps to a vector for the from_hdf5 function + double_1dvec temperature; + temperature.assign(temps, temps + n_temps); + + // TODO: C++ replacement for write_message + // write_message("Loading " + std::string(names[i]) + " data...", 6); + + // Check to make sure cross section set exists in the library + hid_t xs_grp; + if (object_exists(file_id, name)) { + xs_grp = open_group(file_id, name); + } else { + fatal_error("Data for " + std::string(name) + " does not exist in " + + "provided MGXS Library"); + } + + Mgxs mg; + mg.from_hdf5(xs_grp, energy_groups, delayed_groups, + temperature, method, tolerance, max_order, legendre_to_tabular, + legendre_to_tabular_points); + + nuclides_MG.push_back(mg); +} + + +bool query_fissionable_c(const int n_nuclides, const int i_nuclides[]) +{ + bool result = false; + for (int n = 0; n < n_nuclides; n++) { + if (nuclides_MG[i_nuclides[n] - 1].fissionable) result = true; + } + return result; +} + + +void create_macro_xs_c(char* mat_name, const int n_nuclides, + const int i_nuclides[], const int n_temps, const double temps[], + const double atom_densities[], int& method, const double tolerance) +{ + Mgxs macro; + if (n_temps > 0) { + // // Convert temps to a vector + double_1dvec temperature; + temperature.assign(temps, temps + n_temps); + + // Convert atom_densities to a vector + double_1dvec atom_densities_vec; + atom_densities_vec.assign(atom_densities, atom_densities + n_nuclides); + + // Build array of pointers to nuclides_MG's Mgxs objects needed for this + // material + std::vector mgxs_ptr(n_nuclides); + for (int n = 0; n < n_nuclides; n++) { + mgxs_ptr[n] = &nuclides_MG[i_nuclides[n] - 1]; + } + + macro.build_macro(mat_name, temperature, mgxs_ptr, atom_densities_vec, + method, tolerance); + } + macro_xs.push_back(macro); +} + +//============================================================================== +// Mgxs tracking/transport/tallying interface methods +//============================================================================== + +void calculate_xs_c(const int i_mat, const int gin, const double sqrtkT, + const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs) +{ + macro_xs[i_mat - 1].calculate_xs(gin - 1, sqrtkT, uvw, total_xs, abs_xs, + nu_fiss_xs); +} + + +void sample_scatter_c(const int i_mat, const int gin, int& gout, double& mu, + double& wgt, double uvw[3]) +{ + int gout_c = gout - 1; + macro_xs[i_mat - 1].sample_scatter(gin - 1, gout_c, mu, wgt); + + // adjust return value for fortran indexing + gout = gout_c + 1; + + // Rotate the angle + rotate_angle_c(uvw, mu, nullptr); +} + + +void sample_fission_energy_c(const int i_mat, const int gin, int& dg, int& gout) +{ + int dg_c = 0; + int gout_c = 0; + macro_xs[i_mat - 1].sample_fission_energy(gin - 1, dg_c, gout_c); + + // adjust return values for fortran indexing + dg = dg_c + 1; + gout = gout_c + 1; +} + + +void get_name_c(const int index, int name_len, char* name) +{ + // First blank out our input string + std::string str(name_len, ' '); + std::strcpy(name, str.c_str()); + + // Now get the data and copy to the C-string + str = nuclides_MG[index - 1].name; + std::strcpy(name, str.c_str()); + + // Finally, remove the null terminator + name[std::strlen(name)] = ' '; +} + + +double get_awr_c(const int index) +{ + return nuclides_MG[index - 1].awr; +} + + +double get_nuclide_xs_c(const int index, const int xstype, const int gin, + int* gout, double* mu, int* dg) +{ + int gout_c; + int* gout_c_p; + int dg_c; + int* dg_c_p; + if (gout != nullptr) { + gout_c = *gout - 1; + gout_c_p = &gout_c; + } else { + gout_c_p = gout; + } + if (dg != nullptr) { + dg_c = *dg - 1; + dg_c_p = &dg_c; + } else { + dg_c_p = dg; + } + return nuclides_MG[index - 1].get_xs(xstype, gin - 1, gout_c_p, mu, dg_c_p); +} + + +double get_macro_xs_c(const int index, const int xstype, const int gin, + int* gout, double* mu, int* dg) +{ + int gout_c; + int* gout_c_p; + int dg_c; + int* dg_c_p; + if (gout != nullptr) { + gout_c = *gout - 1; + gout_c_p = &gout_c; + } else { + gout_c_p = gout; + } + if (dg != nullptr) { + dg_c = *dg - 1; + dg_c_p = &dg_c; + } else { + dg_c_p = dg; + } + return macro_xs[index - 1].get_xs(xstype, gin - 1, gout_c_p, mu, dg_c_p); +} + + +void set_nuclide_angle_index_c(const int index, const double uvw[3], + int& last_pol, int& last_azi, double last_uvw[3]) +{ + // Store the old + last_pol = nuclides_MG[index - 1].index_pol; + last_azi = nuclides_MG[index - 1].index_azi; + last_uvw[0] = nuclides_MG[index - 1].last_uvw[0]; + last_uvw[1] = nuclides_MG[index - 1].last_uvw[1]; + last_uvw[2] = nuclides_MG[index - 1].last_uvw[2]; + + // Update the values + nuclides_MG[index - 1].set_angle_index(uvw); +} + + +void reset_nuclide_angle_index_c(const int index, const int last_pol, + const int last_azi, const double last_uvw[3]) +{ + nuclides_MG[index - 1].index_pol = last_pol; + nuclides_MG[index - 1].index_azi = last_azi; + nuclides_MG[index - 1].last_uvw[0] = last_uvw[0]; + nuclides_MG[index - 1].last_uvw[1] = last_uvw[1]; + nuclides_MG[index - 1].last_uvw[2] = last_uvw[2]; +} + + +void set_macro_angle_index_c(const int index, const double uvw[3], + int& last_pol, int& last_azi, double last_uvw[3]) +{ + // Store the old + last_pol = macro_xs[index - 1].index_pol; + last_azi = macro_xs[index - 1].index_azi; + last_uvw[0] = macro_xs[index - 1].last_uvw[0]; + last_uvw[1] = macro_xs[index - 1].last_uvw[1]; + last_uvw[2] = macro_xs[index - 1].last_uvw[2]; + + // Update the values + macro_xs[index - 1].set_angle_index(uvw); +} + + +void reset_macro_angle_index_c(const int index, const int last_pol, + const int last_azi, const double last_uvw[3]) +{ + macro_xs[index - 1].index_pol = last_pol; + macro_xs[index - 1].index_azi = last_azi; + macro_xs[index - 1].last_uvw[0] = last_uvw[0]; + macro_xs[index - 1].last_uvw[1] = last_uvw[1]; + macro_xs[index - 1].last_uvw[2] = last_uvw[2]; +} + + +int set_nuclide_temperature_index_c(const int index, const double sqrtkT) +{ + int old = nuclides_MG[index - 1].index_temp; + nuclides_MG[index - 1].set_temperature_index(sqrtkT); + return old; +} + +void reset_nuclide_temperature_index_c(const int index, const int last_temp) +{ + nuclides_MG[index - 1].index_temp = last_temp; +} + +} // namespace openmc \ No newline at end of file diff --git a/src/mgxs_interface.h b/src/mgxs_interface.h new file mode 100644 index 000000000..197ecb0bf --- /dev/null +++ b/src/mgxs_interface.h @@ -0,0 +1,66 @@ +//! \file mgxs_interface.h +//! A collection of C interfaces to the C++ Mgxs class + +#ifndef MGXS_INTERFACE_H +#define MGXS_INTERFACE_H + +#include "mgxs.h" + + +namespace openmc { + +extern std::vector nuclides_MG; +extern std::vector macro_xs; + + +extern "C" void add_mgxs_c(hid_t file_id, char* name, int energy_groups, + int delayed_groups, int n_temps, double temps[], int& method, + double tolerance, int max_order, bool legendre_to_tabular, + int legendre_to_tabular_points); + +extern "C" bool query_fissionable_c(const int n_nuclides, const int i_nuclides[]); + +extern "C" void create_macro_xs_c(char* mat_name, const int n_nuclides, + const int i_nuclides[], const int n_temps, const double temps[], + const double atom_densities[], int& method, const double tolerance); + +extern "C" void calculate_xs_c(const int i_mat, const int gin, + const double sqrtkT, const double uvw[3], double& total_xs, + double& abs_xs, double& nu_fiss_xs); + +extern "C" void sample_scatter_c(const int i_mat, const int gin, int& gout, + double& mu, double& wgt, double uvw[3]); + +extern "C" void sample_fission_energy_c(const int i_mat, const int gin, + int& dg, int& gout); + +extern "C" void get_name_c(const int index, int name_len, char* name); + +extern "C" double get_awr_c(const int index); + +extern "C" double get_nuclide_xs_c(const int index, const int xstype, + const int gin, int* gout, double* mu, int* dg); + +extern "C" double get_macro_xs_c(const int index, const int xstype, + const int gin, int* gout, double* mu, int* dg); + +extern "C" void set_nuclide_angle_index_c(const int index, const double uvw[3], + int& last_pol, int& last_azi, double last_uvw[3]); + +extern "C" void reset_nuclide_angle_index_c(const int index, const int last_pol, + const int last_azi, const double last_uvw[3]); + +extern "C" void set_macro_angle_index_c(const int index, const double uvw[3], + int& last_pol, int& last_azi, double last_uvw[3]); + +extern "C" void reset_macro_angle_index_c(const int index, const int last_pol, + const int last_azi, const double last_uvw[3]); + +extern "C" int set_nuclide_temperature_index_c(const int index, + const double sqrtkT); + +extern "C" void reset_nuclide_temperature_index_c(const int index, + const int last_temp); + +} // namespace openmc +#endif // MGXS_INTERFACE_H \ No newline at end of file diff --git a/src/output.F90 b/src/output.F90 index 23d9512ee..9c5d9cb45 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -12,7 +12,7 @@ module output use math, only: t_percentile use mesh_header, only: RegularMesh, meshes use message_passing, only: master, n_procs - use mgxs_header, only: nuclides_MG + use mgxs_interface use nuclide_header use particle_header, only: LocalCoord, Particle use plot_header @@ -680,6 +680,7 @@ contains character(36) :: score_name ! names of scoring function ! to be applied at write-time type(TallyFilterMatch), allocatable :: matches(:) + character(MAX_WORD_LEN) :: temp_name ! Skip if there are no tallies if (n_tallies == 0) return @@ -843,8 +844,9 @@ contains write(UNIT=unit_tally, FMT='(1X,2A,1X,A)') repeat(" ", indent), & trim(nuclides(i_nuclide) % name) else + call get_name_c(i_nuclide, len(temp_name), temp_name) write(UNIT=unit_tally, FMT='(1X,2A,1X,A)') repeat(" ", indent), & - trim(nuclides_MG(i_nuclide) % obj % name) + trim(temp_name) end if end if diff --git a/src/particle_restart.F90 b/src/particle_restart.F90 index c3d25151b..200773c25 100644 --- a/src/particle_restart.F90 +++ b/src/particle_restart.F90 @@ -6,7 +6,7 @@ module particle_restart use constants use error, only: write_message use hdf5_interface, only: file_open, file_close, read_dataset, HID_T - use mgxs_header, only: energy_bin_avg + use mgxs_interface, only: energy_bin_avg use nuclide_header, only: micro_xs, n_nuclides use output, only: print_particle use particle_header, only: Particle diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index 3030fc099..797514e25 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -8,13 +8,12 @@ module physics_mg use material_header, only: Material, materials use math, only: rotate_angle use mesh_header, only: meshes - use mgxs_header + use mgxs_interface use message_passing use nuclide_header, only: material_xs use particle_header, only: Particle use physics_common use random_lcg, only: prn - use scattdata_header use settings use simulation_header use string, only: to_str @@ -22,20 +21,6 @@ module physics_mg implicit none - interface - subroutine scatter_c(i_mat, gin, gout, mu, wgt, uvw) & - bind(C, name='scatter') - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: i_mat - integer(C_INT), value, intent(in) :: gin - integer(C_INT), intent(inout) :: gout - real(C_DOUBLE), intent(inout) :: mu - real(C_DOUBLE), intent(inout) :: wgt - real(C_DOUBLE), intent(inout) :: uvw(1:3) - end subroutine scatter_c - end interface - contains !=============================================================================== @@ -157,14 +142,8 @@ contains type(Particle), intent(inout) :: p - ! call macro_xs(p % material) % obj % sample_scatter(p % coord(1) % uvw, & - ! p % last_g, p % g, p % mu, p % wgt) - - ! Convert change in angle (mu) to new direction - ! p % coord(1) % uvw = rotate_angle(p % coord(1) % uvw, p % mu) - - call scatter_c(p % material, p % last_g, p % g, p % mu, p % wgt, & - p % coord(1) % uvw) + call sample_scatter_c(p % material, p % last_g, p % g, p % mu, p % wgt, & + p % coord(1) % uvw) ! Update energy value for downstream compatability (in tallying) p % E = energy_bin_avg(p % g) @@ -194,10 +173,6 @@ contains real(8) :: mu ! fission neutron angular cosine real(8) :: phi ! fission neutron azimuthal angle real(8) :: weight ! weight adjustment for ufs method - class(Mgxs), pointer :: xs - - ! Get Pointers - xs => macro_xs(p % material) % obj ! TODO: Heat generation from fission @@ -277,7 +252,7 @@ contains ! Sample secondary energy distribution for fission reaction and set energy ! in fission bank - call xs % sample_fission_energy(p % g, bank_array(i) % uvw, dg, gout) + call sample_fission_energy_c(p % material, p % g, dg, gout) bank_array(i) % E = real(gout, 8) bank_array(i) % delayed_group = dg diff --git a/src/scattdata.cpp b/src/scattdata.cpp index aef620ac5..b12ec1acb 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -56,35 +56,38 @@ void ScattData::sample_energy(int gin, int& gout, int& i_gout) } -double ScattData::get_xs(const char* xstype, int gin, int* gout, double* mu) +double ScattData::get_xs(const int xstype, int gin, int* gout, double* mu) { // Set the outgoing group offset index as needed int i_gout = 0; if (gout != nullptr) { // short circuit the function if gout is from a zero portion of the // scattering matrix - if ((*gout < gmin[gin]) || (*gout >= gmax[gin])) { // > gmax? + if ((*gout < gmin[gin]) || (*gout > gmax[gin])) { // > gmax? return 0.; } i_gout = *gout - gmin[gin]; } double val = 0.; - if (std::strcmp(xstype, "scatter")) { + switch(xstype) { + case MG_GET_XS_SCATTER: if (gout != nullptr) { val = scattxs[gin] * energy[gin][i_gout]; } else { val = scattxs[gin]; } - } else if (std::strcmp(xstype, "scatter/mult")) { + break; + case MG_GET_XS_SCATTER_MULT: if (gout != nullptr) { val = scattxs[gin] * energy[gin][i_gout] / mult[gin][i_gout]; } else { - val = scattxs[gin] / std::inner_product(mult[gin].begin(), - mult[gin].end(), - energy[gin].begin(), 0.0); + val = scattxs[gin] / + std::inner_product(mult[gin].begin(), mult[gin].end(), + energy[gin].begin(), 0.0); } - } else if (std::strcmp(xstype, "scatter*f_mu/mult")) { + break; + case MG_GET_XS_SCATTER_FMU_MULT: if ((gout != nullptr) && (mu != nullptr)) { val = scattxs[gin] * energy[gin][i_gout] * calc_f(gin, *gout, *mu); } else { @@ -92,7 +95,8 @@ double ScattData::get_xs(const char* xstype, int gin, int* gout, double* mu) // group or mu is not useful fatal_error("Invalid call to get_xs"); } - } else if (std::strcmp(xstype, "scatter*f_mu")) { + break; + case MG_GET_XS_SCATTER_FMU: if ((gout != nullptr) && (mu != nullptr)) { val = scattxs[gin] * energy[gin][i_gout] * calc_f(gin, *gout, *mu) / mult[gin][i_gout]; @@ -101,6 +105,7 @@ double ScattData::get_xs(const char* xstype, int gin, int* gout, double* mu) // group or mu is not useful fatal_error("Invalid call to get_xs"); } + break; } return val; } @@ -205,13 +210,11 @@ void ScattDataLegendre::update_max_val() double ScattDataLegendre::calc_f(int gin, int gout, double mu) { - // TODO: gout >= or gout >? double f; - if ((gout < gmin[gin]) || (gout >= gmax[gin])) { + if ((gout < gmin[gin]) || (gout > gmax[gin])) { f = 0.; } else { - // TODO: size() -1 or just size? - int i_gout = gout - gmin[gin]; //TODO: + 1? + int i_gout = gout - gmin[gin]; f = evaluate_legendre_c(dist[gin][i_gout].size() - 1, dist[gin][i_gout].data(), mu); } @@ -479,19 +482,18 @@ void ScattDataHistogram::init(int_1dvec& in_gmin, int_1dvec& in_gmax, double ScattDataHistogram::calc_f(int gin, int gout, double mu) { - // TODO: gout >= or gout >? double f; - if ((gout < gmin[gin]) || (gout >= gmax[gin])) { + if ((gout < gmin[gin]) || (gout > gmax[gin])) { f = 0.; } else { // Find mu bin - int i_gout = gout - gmin[gin]; //TODO: + 1? + int i_gout = gout - gmin[gin]; int imu; if (mu == 1.) { // use size -2 to have the index one before the end imu = this->mu.size() - 2; } else { - imu = std::floor((mu + 1.) / dmu + 1.); + imu = std::floor((mu + 1.) / dmu + 1.) - 1; } f = fmu[gin][i_gout][imu]; @@ -776,19 +778,18 @@ void ScattDataTabular::init(int_1dvec& in_gmin, int_1dvec& in_gmax, double ScattDataTabular::calc_f(int gin, int gout, double mu) { - // TODO: gout >= or gout >? double f; - if ((gout < gmin[gin]) || (gout >= gmax[gin])) { + if ((gout < gmin[gin]) || (gout > gmax[gin])) { f = 0.; } else { // Find mu bin - int i_gout = gout - gmin[gin]; //TODO: + 1? + int i_gout = gout - gmin[gin]; int imu; if (mu == 1.) { // use size -2 to have the index one before the end imu = this->mu.size() - 2; } else { - imu = std::floor((mu + 1.) / dmu + 1.); + imu = std::floor((mu + 1.) / dmu + 1.) - 1; } double r = (mu - this->mu[imu]) / (this->mu[imu + 1] - this->mu[imu]); @@ -1006,7 +1007,7 @@ void convert_legendre_to_tabular(ScattDataLegendre& leg, tab.dmu = 2. / (n_mu - 1); tab.mu[0] = -1.; for (int imu = 1; imu < n_mu - 1; imu++) { - tab.mu[imu] = -1. + (imu - 1) * tab.dmu; + tab.mu[imu] = -1. + imu * tab.dmu; } tab.mu[n_mu - 1] = 1.; @@ -1032,6 +1033,7 @@ void convert_legendre_to_tabular(ScattDataLegendre& leg, // Now re-normalize for numerical integration issues and to take care of // the above negative fix-up. Also accrue the CDF double norm = 0.; + tab.dist[gin][i_gout][0] = 0.; for (int imu = 1; imu < n_mu; imu++) { norm += 0.5 * tab.dmu * (tab.fmu[gin][i_gout][imu - 1] + tab.fmu[gin][i_gout][imu]); diff --git a/src/scattdata.h b/src/scattdata.h index 4553156e0..a9a236b2b 100644 --- a/src/scattdata.h +++ b/src/scattdata.h @@ -38,7 +38,7 @@ class ScattData { virtual void init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, double_3dvec& coeffs) = 0; void sample_energy(int gin, int& gout, int& i_gout); - double get_xs(const char* xstype, int gin, int* gout, double* mu); + double get_xs(const int xstype, int gin, int* gout, double* mu); void generic_init(int order, int_1dvec in_gmin, int_1dvec in_gmax, double_2dvec in_energy, double_2dvec in_mult); virtual void combine(std::vector& those_scatts, diff --git a/src/simulation.F90 b/src/simulation.F90 index 4f6163429..ffcfb8bab 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -20,7 +20,7 @@ module simulation 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 mgxs_interface, only: energy_bins, energy_bin_avg use nuclide_header, only: micro_xs, n_nuclides use output, only: header, print_columns, & print_batch_keff, print_generation, print_runtime, & diff --git a/src/source.F90 b/src/source.F90 index 368361131..a6b6a92ac 100644 --- a/src/source.F90 +++ b/src/source.F90 @@ -14,7 +14,7 @@ module source use hdf5_interface use math use message_passing, only: rank - use mgxs_header, only: rev_energy_bins, num_energy_groups + use mgxs_interface, 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 diff --git a/src/state_point.F90 b/src/state_point.F90 index 27fdd969b..ebb473d49 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -22,7 +22,7 @@ module state_point use hdf5_interface use mesh_header, only: RegularMesh, meshes, n_meshes use message_passing - use mgxs_header, only: nuclides_MG + use mgxs_interface use nuclide_header, only: nuclides use output, only: time_stamp use random_lcg, only: openmc_get_seed, openmc_set_seed @@ -73,6 +73,7 @@ contains character(MAX_WORD_LEN), allocatable :: str_array(:) character(C_CHAR), pointer :: string(:) character(len=:, kind=C_CHAR), allocatable :: filename_ + character(MAX_WORD_LEN, kind=C_CHAR) :: temp_name err = 0 if (present(filename)) then @@ -307,11 +308,13 @@ contains str_array(j) = nuclides(tally % nuclide_bins(j)) % name end if else - i_xs = index(nuclides_MG(tally % nuclide_bins(j)) % obj % name, '.') + call get_name_c(tally % nuclide_bins(j), len(temp_name), & + temp_name) + i_xs = index(temp_name, '.') if (i_xs > 0) then - str_array(j) = nuclides_MG(tally % nuclide_bins(j)) % obj % name(1 : i_xs-1) + str_array(j) = trim(temp_name(1 : i_xs-1)) else - str_array(j) = nuclides_MG(tally % nuclide_bins(j)) % obj % name + str_array(j) = trim(temp_name) end if end if else diff --git a/src/string_functions.cpp b/src/string_functions.cpp new file mode 100644 index 000000000..f41ec40c1 --- /dev/null +++ b/src/string_functions.cpp @@ -0,0 +1,30 @@ +#include "string_functions.h" + +namespace openmc { + +std::string& strtrim(std::string& s) +{ + const char* t = " \t\n\r\f\v"; + s.erase(s.find_last_not_of(t) + 1); + s.erase(0, s.find_first_not_of(t)); + return s; +} + + +char* strtrim(char* c_str) +{ + std::string std_str; + std_str.assign(c_str); + strtrim(std_str); + int length = std_str.copy(c_str, std_str.size()); + c_str[length] = '\0'; + return c_str; +} + + +void to_lower(std::string& str) +{ + for (int i = 0; i < str.size(); i++) str[i] = std::tolower(str[i]); +} + +} // namespace openmc \ No newline at end of file diff --git a/src/string_functions.h b/src/string_functions.h index d44982bbd..bf30612fe 100644 --- a/src/string_functions.h +++ b/src/string_functions.h @@ -4,38 +4,15 @@ #ifndef STRING_FUNCTIONS_H #define STRING_FUNCTIONS_H -// for string functions -#include -#include -#include #include namespace openmc { -std::string& strtrim(std::string& s) -{ - const char* t = " \t\n\r\f\v"; - s.erase(s.find_last_not_of(t) + 1); - s.erase(0, s.find_first_not_of(t)); - return s; -} +std::string& strtrim(std::string& s); +char* strtrim(char* c_str); -char* strtrim(char* c_str) -{ - std::string std_str; - std_str.assign(c_str); - strtrim(std_str); - int length = std_str.copy(c_str, std_str.size()); - c_str[length] = '\0'; - return c_str; -} - - -void to_lower(std::string& str) -{ - for (int i = 0; i < str.size(); i++) str[i] = std::tolower(str[i]); -} +void to_lower(std::string& str); } // namespace openmc -#endif // STRING_FUNCTIONS_H \ No newline at end of file +#endif // STRING_FUNCTIONS_H diff --git a/src/summary.F90 b/src/summary.F90 index bd6ef7158..cae81a88b 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -8,7 +8,7 @@ module summary use material_header, only: Material, n_materials use mesh_header, only: RegularMesh use message_passing - use mgxs_header, only: nuclides_MG + use mgxs_interface use nuclide_header use output, only: time_stamp use settings, only: run_CE @@ -94,7 +94,7 @@ contains num_nuclides = 0 num_macros = 0 do i = 1, n_nuclides - if (nuclides_MG(i) % obj % awr /= MACROSCOPIC_AWR) then + if (get_awr_c(i) /= MACROSCOPIC_AWR) then num_nuclides = num_nuclides + 1 else num_macros = num_macros + 1 @@ -119,12 +119,14 @@ contains nuc_names(i) = nuclides(i) % name awrs(i) = nuclides(i) % awr else - if (nuclides_MG(i) % obj % awr /= MACROSCOPIC_AWR) then - nuc_names(j) = nuclides_MG(i) % obj % name - awrs(j) = nuclides_MG(i) % obj % awr + if (get_awr_c(i) /= MACROSCOPIC_AWR) then + call get_name_c(i, len(nuc_names(j)), nuc_names(j)) + nuc_names(j) = trim(nuc_names(j)) + awrs(j) = get_awr_c(i) j = j + 1 else - macro_names(k) = nuclides_MG(i) % obj % name + call get_name_c(i, len(macro_names(k)), macro_names(k)) + macro_names(k) = trim(macro_names(k)) k = k + 1 end if end if @@ -458,7 +460,7 @@ contains num_nuclides = 0 num_macros = 0 do j = 1, m % n_nuclides - if (nuclides_MG(m % nuclide(j)) % obj % awr /= MACROSCOPIC_AWR) then + if (get_awr_c(m % nuclide(j)) /= MACROSCOPIC_AWR) then num_nuclides = num_nuclides + 1 else num_macros = num_macros + 1 @@ -484,12 +486,14 @@ contains k = 1 n = 1 do j = 1, m % n_nuclides - if (nuclides_MG(m % nuclide(j)) % obj % awr /= MACROSCOPIC_AWR) then - nuc_names(k) = nuclides_MG(m % nuclide(j)) % obj % name + if (get_awr_c(m % nuclide(j)) /= MACROSCOPIC_AWR) then + call get_name_c(m % nuclide(j), len(nuc_names(k)), nuc_names(k)) + nuc_names(k) = trim(nuc_names(k)) nuc_densities(k) = m % atom_density(j) k = k + 1 else - macro_names(n) = nuclides_MG(m % nuclide(j)) % obj % name + call get_name_c(m % nuclide(j), len(macro_names(n)), macro_names(n)) + macro_names(n) = trim(macro_names(n)) n = n + 1 end if end do diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index ef8b5915c..4c24717b4 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -10,7 +10,7 @@ module tally use math, only: t_percentile use mesh_header, only: RegularMesh, meshes use message_passing - use mgxs_header + use mgxs_interface use nuclide_header use output, only: header use particle_header, only: LocalCoord, Particle @@ -1229,8 +1229,14 @@ contains real(8) :: p_uvw(3) ! Particle's current uvw integer :: p_g ! Particle group to use for getting info ! to tally with. - class(Mgxs), pointer :: matxs - class(Mgxs), pointer :: nucxs + ! Storage of the indices the Mgxs object arrived with for resetting later + integer(C_INT) :: last_nuc_azi + integer(C_INT) :: last_nuc_pol + integer(C_INT) :: last_mat_azi + integer(C_INT) :: last_mat_pol + integer(C_INT) :: last_nuc_temp + real(C_DOUBLE) :: last_mat_uvw(3) + real(C_DOUBLE) :: last_nuc_uvw(3) ! Set the direction and group to use with get_xs if (t % estimator == ESTIMATOR_ANALOG .or. & @@ -1268,13 +1274,15 @@ contains ! To significantly reduce de-referencing, point matxs to the ! macroscopic Mgxs for the material of interest - matxs => macro_xs(p % material) % obj + call set_macro_angle_index_c(p % material, p_uvw, last_mat_pol, & + last_mat_azi, last_mat_uvw) ! Do same for nucxs, point it to the microscopic nuclide data of interest if (i_nuclide > 0) then - nucxs => nuclides_MG(i_nuclide) % obj ! And since we haven't calculated this temperature index yet, do so now - call nucxs % find_temperature(p % sqrtkT) + last_nuc_temp = set_nuclide_temperature_index_c(i_nuclide, p % sqrtkT) + call set_nuclide_angle_index_c(i_nuclide, p_uvw, last_nuc_pol, & + last_nuc_azi, last_nuc_uvw) end if i = 0 @@ -1329,13 +1337,13 @@ contains if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('total', p_g, UVW=p_uvw) / & - matxs % get_xs('total', p_g, UVW=p_uvw) * flux + get_nuclide_xs_c(i_nuclide, MG_GET_XS_TOTAL, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_TOTAL, p_g) * flux end if else if (i_nuclide > 0) then - score = nucxs % get_xs('total', p_g, UVW=p_uvw) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_TOTAL, p_g) * & atom_density * flux else score = material_xs % total * flux @@ -1358,19 +1366,23 @@ contains end if if (i_nuclide > 0) then - score = score * nucxs % get_xs('inverse-velocity', p_g, UVW=p_uvw) & - / matxs % get_xs('absorption', p_g, UVW=p_uvw) * flux + score = score * get_nuclide_xs_c(i_nuclide, & + MG_GET_XS_INVERSE_VELOCITY, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) * flux else - score = score * matxs % get_xs('inverse-velocity', p_g, UVW=p_uvw) & - / matxs % get_xs('absorption', p_g, UVW=p_uvw) * flux + score = score * get_macro_xs_c(p % material, & + MG_GET_XS_INVERSE_VELOCITY, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) * flux end if else if (i_nuclide > 0) then - score = flux * nucxs % get_xs('inverse-velocity', p_g, UVW=p_uvw) + score = flux * get_nuclide_xs_c(i_nuclide, & + MG_GET_XS_INVERSE_VELOCITY, p_g) else - score = flux * matxs % get_xs('inverse-velocity', p_g, UVW=p_uvw) + score = flux * get_macro_xs_c(p % material, & + MG_GET_XS_INVERSE_VELOCITY, p_g) end if end if @@ -1392,21 +1404,23 @@ contains ! adjust the score by the actual probability for that nuclide. if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('scatter*f_mu/mult', p % last_g, p % g, & - UVW=p_uvw, MU=p % mu) / & - matxs % get_xs('scatter*f_mu/mult', p % last_g, p % g, & - UVW=p_uvw, MU=p % mu) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_FMU_MULT, & + p % last_g, p % g, MU=p % mu) / & + get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU_MULT, & + p % last_g, p % g, MU=p % mu) end if else if (i_nuclide > 0) then score = atom_density * flux * & - nucxs % get_xs('scatter/mult', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_MULT, & + p_g, MU=p % mu) else ! Get the scattering x/s and take away ! the multiplication baked in to sigS score = flux * & - matxs % get_xs('scatter/mult', p_g, UVW=p_uvw) + get_macro_xs_c(p % material, MG_GET_XS_SCATTER_MULT, & + p_g, MU=p % mu) end if end if @@ -1428,19 +1442,20 @@ contains ! adjust the score by the actual probability for that nuclide. if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('scatter*f_mu', p % last_g, p % g, & - UVW=p_uvw, MU=p % mu) / & - matxs % get_xs('scatter*f_mu', p % last_g, p % g, & - UVW=p_uvw, MU=p % mu) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_FMU, & + p % last_g, p % g, MU=p % mu) / & + get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU, & + p % last_g, p % g, MU=p % mu) end if else if (i_nuclide > 0) then - score = nucxs % get_xs('scatter', p_g, UVW=p_uvw) * & - atom_density * flux + score = atom_density * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER, p_g) else ! Get the scattering x/s, which includes multiplication - score = matxs % get_xs('scatter', p_g, UVW=p_uvw) * flux + score = flux * & + get_macro_xs_c(p % material, MG_GET_XS_SCATTER, p_g) end if end if @@ -1460,13 +1475,13 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('absorption', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_ABSORPTION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = nucxs % get_xs('absorption', p_g, UVW=p_uvw) * & - atom_density * flux + score = atom_density * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_ABSORPTION, p_g) else score = material_xs % absorption * flux end if @@ -1491,19 +1506,19 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - matxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = nucxs % get_xs('fission', p_g, UVW=p_uvw) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) * & atom_density * flux else - score = matxs % get_xs('fission', p_g, UVW=p_uvw) * flux + score = get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux end if end if @@ -1529,12 +1544,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('nu-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - matxs % get_xs('nu-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_macro_xs_c(p % material, MG_GET_XS_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else ! Skip any non-fission events @@ -1547,17 +1562,17 @@ contains score = keff * p % wgt_bank * flux if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('fission', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if else if (i_nuclide > 0) then - score = nucxs % get_xs('nu-fission', p_g, UVW=p_uvw) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_NU_FISSION, p_g) * & atom_density * flux else - score = matxs % get_xs('nu-fission', p_g, UVW=p_uvw) * flux + score = get_macro_xs_c(p % material, MG_GET_XS_NU_FISSION, p_g) * flux end if end if @@ -1583,12 +1598,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('prompt-nu-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - matxs % get_xs('prompt-nu-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_macro_xs_c(p % material, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else ! Skip any non-fission events @@ -1602,17 +1617,17 @@ contains / real(p % n_bank, 8)) * flux if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('fission', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if else if (i_nuclide > 0) then - score = nucxs % get_xs('prompt-nu-fission', p_g, UVW=p_uvw) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_PROMPT_NU_FISSION, p_g) * & atom_density * flux else - score = matxs % get_xs('prompt-nu-fission', p_g, UVW=p_uvw) * flux + score = get_macro_xs_c(p % material, MG_GET_XS_PROMPT_NU_FISSION, p_g) * flux end if end if @@ -1638,7 +1653,7 @@ contains ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in ! nu-fission - if (matxs % get_xs('absorption', p_g, UVW=p_uvw) > ZERO) then + if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then if (dg_filter > 0) then select type(filt => filters(t % filter(dg_filter)) % obj) @@ -1653,13 +1668,13 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then - score = score * nucxs % get_xs('delayed-nu-fission', & - p_g, UVW=p_uvw, dg=d) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + score = score * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else - score = score * matxs % get_xs('delayed-nu-fission', & - p_g, UVW=p_uvw, dg=d) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + score = score * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1669,11 +1684,13 @@ contains else score = p % absorb_wgt * flux if (i_nuclide > 0) then - score = score * nucxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw) / matxs % get_xs('absorption', p_g, UVW=p_uvw) + score = score * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else - score = score * matxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw) / matxs % get_xs('absorption', p_g, UVW=p_uvw) + score = score * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if end if end if @@ -1703,8 +1720,8 @@ contains if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('fission', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1715,8 +1732,8 @@ contains score = keff * p % wgt_bank / p % n_bank * sum(p % n_delayed_bank) * flux if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('fission', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if end if @@ -1735,11 +1752,11 @@ contains d = filt % groups(d_bin) if (i_nuclide > 0) then - score = nucxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw, dg=d) * atom_density * flux + score = atom_density * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else - score = matxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw, dg=d) * flux + score = flux * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1748,11 +1765,12 @@ contains end select else if (i_nuclide > 0) then - score = nucxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw) & - * atom_density * flux + score = atom_density * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g) + else - score = matxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw) & - * flux + score = flux * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g) end if end if end if @@ -1767,7 +1785,7 @@ contains ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in ! nu-fission - if (matxs % get_xs('absorption', p_g, UVW=p_uvw) > ZERO) then + if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then if (dg_filter > 0) then select type(filt => filters(t % filter(dg_filter)) % obj) @@ -1782,17 +1800,15 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then - score = score * nucxs % get_xs('decay rate', p_g, & - UVW=p_uvw, dg=d) * & - nucxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw, dg=d) / matxs % get_xs('absorption', & - p_g, UVW=p_uvw) + score = score * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else - score = score * matxs % get_xs('decay rate', p_g, & - UVW=p_uvw, dg=d) * & - matxs % get_xs('delayed-nu-fission', p_g, & - UVW=p_uvw, dg=d) / matxs % get_xs('absorption', & - p_g, UVW=p_uvw) + score = score * & + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1809,15 +1825,15 @@ contains ! for all delayed groups. do d = 1, num_delayed_groups if (i_nuclide > 0) then - score = score + p % absorb_wgt * & - nucxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - nucxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, & - dg=d) / matxs % get_xs('absorption', p_g, UVW=p_uvw) * flux + score = score + p % absorb_wgt * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else - score = score + p % absorb_wgt * & - matxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - matxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, & - dg=d) / matxs % get_xs('absorption', p_g, UVW=p_uvw) * flux + score = score + p % absorb_wgt * flux * & + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if end do end if @@ -1846,13 +1862,13 @@ contains if (i_nuclide > 0) then score = score + keff * atom_density * & fission_bank(n_bank - p % n_bank + k) % wgt * & - nucxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=g) * & - nucxs % get_xs('fission', p_g, UVW=p_uvw) / & - matxs % get_xs('fission', p_g, UVW=p_uvw) * flux + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux else score = score + keff * & fission_bank(n_bank - p % n_bank + k) % wgt * & - matxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=g) * flux + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * flux end if ! if the delayed group filter is present, tally to corresponding @@ -1904,13 +1920,13 @@ contains d = filt % groups(d_bin) if (i_nuclide > 0) then - score = nucxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - nucxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, & - dg=d) * atom_density * flux + score = atom_density * flux * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else - score = matxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - matxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, & - dg=d) * flux + score = flux * & + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1927,12 +1943,12 @@ contains do d = 1, num_delayed_groups if (i_nuclide > 0) then score = score + atom_density * flux * & - nucxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - nucxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, dg=d) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else score = score + flux * & - matxs % get_xs('decay rate', p_g, UVW=p_uvw, dg=d) * & - matxs % get_xs('delayed-nu-fission', p_g, UVW=p_uvw, dg=d) + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if end do end if @@ -1957,19 +1973,20 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - nucxs % get_xs('kappa-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_KAPPA_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - matxs % get_xs('kappa-fission', p_g, UVW=p_uvw) / & - matxs % get_xs('absorption', p_g, UVW=p_uvw) + get_macro_xs_c(p % material, MG_GET_XS_KAPPA_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = nucxs % get_xs('kappa-fission', p_g, UVW=p_uvw) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_KAPPA_FISSION, p_g) * & atom_density * flux else - score = matxs % get_xs('kappa-fission', p_g, UVW=p_uvw) * flux + score = flux * & + get_macro_xs_c(p % material, MG_GET_XS_KAPPA_FISSION, p_g) end if end if @@ -1989,7 +2006,15 @@ contains end do SCORE_LOOP - nullify(matxs, nucxs) + ! Reset temporary Mgxs indices + call reset_macro_angle_index_c(p % material, last_mat_pol, last_mat_azi, & + last_mat_uvw); + + if (i_nuclide > 0) then + call reset_nuclide_temperature_index_c(i_nuclide, last_nuc_temp) + call reset_nuclide_angle_index_c(i_nuclide, last_nuc_pol, last_nuc_azi, & + last_nuc_uvw) + end if end subroutine score_general_mg !=============================================================================== diff --git a/src/tallies/tally_filter_energy.F90 b/src/tallies/tally_filter_energy.F90 index 93da69edd..f230d438e 100644 --- a/src/tallies/tally_filter_energy.F90 +++ b/src/tallies/tally_filter_energy.F90 @@ -6,7 +6,7 @@ module tally_filter_energy use constants use error use hdf5_interface - use mgxs_header, only: num_energy_groups, rev_energy_bins + use mgxs_interface, only: num_energy_groups, rev_energy_bins use particle_header, only: Particle use settings, only: run_CE use string, only: to_str diff --git a/src/tracking.F90 b/src/tracking.F90 index 2ba62a8dd..c832ca9cc 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -9,7 +9,7 @@ module tracking check_cell_overlap use material_header, only: materials, Material use message_passing - use mgxs_header + use mgxs_interface use nuclide_header use particle_header, only: LocalCoord, Particle use physics, only: collision @@ -29,21 +29,6 @@ module tracking implicit none - interface - subroutine calculate_xs_c(i_mat, gin, sqrtkT, uvw, total_xs, abs_xs, & - nu_fiss_xs) bind(C, name='calculate_xs') - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: i_mat - integer(C_INT), value, intent(in) :: gin - real(C_DOUBLE), value, intent(in) :: sqrtkT - real(C_DOUBLE), intent(in) :: uvw(1:3) - real(C_DOUBLE), intent(inout) :: total_xs - real(C_DOUBLE), intent(inout) :: abs_xs - real(C_DOUBLE), intent(inout) :: nu_fiss_xs - end subroutine calculate_xs_c - end interface - contains !=============================================================================== @@ -129,10 +114,6 @@ contains end if else ! Get the MG data - !!TODO: Remove Fortran call - needed until I'm done replacing Fortran - !!with C++ code because it sets index_temp - call macro_xs(p % material) % obj % calculate_xs(p % g, p % sqrtkT, & - p % coord(p % n_coord) % uvw, material_xs) call calculate_xs_c(p % material, p % g, p % sqrtkT, & p % coord(p % n_coord) % uvw, material_xs % total, & material_xs % absorption, material_xs % nu_fission) diff --git a/src/xsdata.cpp b/src/xsdata.cpp index 8afdb7f03..b79d02e20 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -24,6 +24,8 @@ XsData::XsData(int energy_groups, int num_delayed_groups, bool fissionable, if (fissionable) { fission = double_3dvec(n_pol, double_2dvec(n_azi, double_1dvec(energy_groups, 0.))); + nu_fission = double_3dvec(n_pol, double_2dvec(n_azi, + double_1dvec(energy_groups, 0.))); prompt_nu_fission = double_3dvec(n_pol, double_2dvec(n_azi, double_1dvec(energy_groups, 0.))); kappa_fission = double_3dvec(n_pol, double_2dvec(n_azi, @@ -514,6 +516,17 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, } } + // Combine prompt_nu_fission and delayed_nu_fission into nu_fission + for (int p = 0; p < n_pol; p++) { + for (int a = 0; a < n_azi; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + nu_fission[p][a][gin] = + std::accumulate(delayed_nu_fission[p][a][gin].begin(), + delayed_nu_fission[p][a][gin].end(), + prompt_nu_fission[p][a][gin]); + } + } + } } @@ -668,6 +681,8 @@ void XsData::combine(std::vector those_xs, double_1dvec& scalars) inverse_velocity[p][a][gin] += scalar * that->inverse_velocity[p][a][gin]; if (that->prompt_nu_fission.size() > 0) { + nu_fission[p][a][gin] += + scalar * that->nu_fission[p][a][gin]; prompt_nu_fission[p][a][gin] += scalar * that->prompt_nu_fission[p][a][gin]; kappa_fission[p][a][gin] += diff --git a/src/xsdata.h b/src/xsdata.h index fec28aece..478f2e7aa 100644 --- a/src/xsdata.h +++ b/src/xsdata.h @@ -38,6 +38,7 @@ class XsData { // [phi][theta][incoming group] double_3dvec total; double_3dvec absorption; + double_3dvec nu_fission; double_3dvec prompt_nu_fission; double_3dvec kappa_fission; double_3dvec fission; From a32678865d2af29d7171bd2aa85bcfcf2d77a9e8 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Wed, 13 Jun 2018 20:24:16 -0400 Subject: [PATCH 325/361] Cathartically removing the fortran Mgxs code --- src/mgxs_header.F90 | 3592 -------------------------------------- src/scattdata_header.F90 | 852 --------- 2 files changed, 4444 deletions(-) delete mode 100644 src/mgxs_header.F90 delete mode 100644 src/scattdata_header.F90 diff --git a/src/mgxs_header.F90 b/src/mgxs_header.F90 deleted file mode 100644 index 09db7217e..000000000 --- a/src/mgxs_header.F90 +++ /dev/null @@ -1,3592 +0,0 @@ -module mgxs_header - - use, intrinsic :: ISO_FORTRAN_ENV - use, intrinsic :: ISO_C_BINDING - - use algorithm, only: find, sort - use constants, only: MAX_WORD_LEN, ZERO, ONE, TWO, PI, MACROSCOPIC_AWR - use error, only: fatal_error - use hdf5_interface - use material_header, only: material - use math, only: evaluate_legendre - use nuclide_header, only: MaterialMacroXS - use random_lcg, only: prn - use string - use stl_vector, only: VectorInt, VectorReal - -!=============================================================================== -! XS* contains the temperature-dependent cross section data for an MGXS -!=============================================================================== - - type :: XsDataIso - ! Microscopic cross sections - real(8), allocatable :: total(:) ! total cross section - real(8), allocatable :: absorption(:) ! absorption cross section - class(ScattData), allocatable :: scatter ! scattering info - real(8), allocatable :: delayed_nu_fission(:,:) ! Delayed fission matrix (Dg x Gin) - real(8), allocatable :: prompt_nu_fission(:) ! Prompt fission vector (Gin) - real(8), allocatable :: kappa_fission(:) ! Kappa fission - real(8), allocatable :: fission(:) ! Neutron production - real(8), allocatable :: decay_rate(:) ! Delayed neutron precursor decay rate - real(8), allocatable :: inverse_velocity(:) ! Inverse neutron velocity - real(8), allocatable :: chi_delayed(:, :, :) ! Delayed fission spectra - real(8), allocatable :: chi_prompt(:, :) ! Prompt fission spectra - end type XsDataIso - - type :: XsDataAngle - ! Microscopic cross sections - ! In all cases, right-most indices are theta, phi - real(8), allocatable :: total(:, :, :) ! total cross section - real(8), allocatable :: absorption(:, :, :) ! absorption cross section - type(ScattDataContainer), allocatable :: scatter(:, :) ! scattering info - real(8), allocatable :: delayed_nu_fission(:, :, :, :) ! Delayed fission matrix (Gout x Gin) - real(8), allocatable :: prompt_nu_fission(:, :, :) ! Prompt fission matrix (Gout x Gin) - real(8), allocatable :: kappa_fission(:, :, :) ! Kappa fission - real(8), allocatable :: fission(:, :, :) ! Neutron production - real(8), allocatable :: decay_rate(:, :, :) ! Delayed neutron precursor decay rate - real(8), allocatable :: inverse_velocity(:, :, :) ! Inverse neutron velocity - real(8), allocatable :: chi_delayed(:, :, :, :, :) ! Delayed fission spectra - real(8), allocatable :: chi_prompt(:, :, :, :) ! Prompt fission spectra - end type XsDataAngle - -!=============================================================================== -! MGXS contains the base mgxs data for a nuclide/material -!=============================================================================== - - type, abstract :: Mgxs - character(len=MAX_WORD_LEN) :: name ! name of dataset, e.g. UO2 - real(8) :: awr ! Atomic Weight Ratio - real(8), allocatable :: kTs(:) ! temperature in eV (k*T) - - ! Fission information - logical :: fissionable ! mgxs object is fissionable? - integer :: scatter_format ! either legendre, histogram, or tabular. - integer :: num_delayed_groups ! Num delayed groups - - ! Caching information - integer :: index_temp ! temperature index for nuclide - - contains - procedure(mgxs_from_hdf5_), deferred :: from_hdf5 ! Load the data - procedure(mgxs_combine_), deferred :: combine ! initializes object - procedure(mgxs_get_xs_), deferred :: get_xs ! Get the requested xs - - ! Sample the outgoing energy from a fission event - procedure(mgxs_sample_fission_), deferred :: sample_fission_energy - - ! Sample the outgoing energy and angle from a scatter event - procedure(mgxs_sample_scatter_), deferred :: sample_scatter - - ! Calculate the material specific MGXS data from the nuclides - procedure(mgxs_calculate_xs_), deferred :: calculate_xs - - ! Find the temperature - procedure :: find_temperature => mgxs_find_temperature - end type Mgxs - -!=============================================================================== -! MGXSCONTAINER pointer array for storing Nuclides -!=============================================================================== - - type MgxsContainer - class(Mgxs), pointer :: obj - end type MgxsContainer - -!=============================================================================== -! Interfaces for MGXS -!=============================================================================== - - abstract interface - subroutine mgxs_from_hdf5_(this, xs_id, energy_groups, delayed_groups, & - temperature, method, tolerance, max_order, legendre_to_tabular, & - legendre_to_tabular_points) - import Mgxs, HID_T, VectorReal - class(Mgxs), intent(inout) :: this ! Working Object - integer(HID_T), intent(in) :: xs_id ! Library data - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of delayed groups - type(VectorReal), intent(in) :: temperature ! list of desired temperatures - integer, intent(inout) :: method ! Type of temperature access - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: max_order ! Maximum requested order - logical, intent(in) :: legendre_to_tabular ! Convert Legendres to Tabular? - integer, intent(in) :: legendre_to_tabular_points ! Number of points to use - ! in that conversion - end subroutine mgxs_from_hdf5_ - - subroutine mgxs_combine_(this, temps, mat, nuclides, energy_groups, & - delayed_groups, max_order, tolerance, method) - import Mgxs, Material, MgxsContainer, VectorReal - class(Mgxs), intent(inout) :: this ! The Mgxs to initialize - type(VectorReal), intent(in) :: temps ! Temperatures to obtain - type(Material), pointer, intent(in) :: mat ! base material - type(MgxsContainer), intent(in) :: nuclides(:) ! List of nuclides to harvest from - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of delayed groups - integer, intent(in) :: max_order ! Maximum requested order - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: method ! Type of temperature access - end subroutine mgxs_combine_ - - pure function mgxs_get_xs_(this, xstype, gin, gout, uvw, mu, dg) result(xs_val) - import Mgxs - class(Mgxs), intent(in) :: this - character(*), intent(in) :: xstype ! Cross Section Type - integer, intent(in) :: gin ! Incoming Energy group - integer, optional, intent(in) :: gout ! Outgoing Group - real(8), optional, intent(in) :: uvw(3) ! Requested Angle - real(8), optional, intent(in) :: mu ! Change in angle - integer, optional, intent(in) :: dg ! Delayed group - real(8) :: xs_val ! Resultant xs - end function mgxs_get_xs_ - - subroutine mgxs_sample_fission_(this, gin, uvw, dg, gout) - import Mgxs - class(Mgxs), intent(in) :: this - integer, intent(in) :: gin ! Incoming energy group - real(8), intent(in) :: uvw(3) ! Particle Direction - integer, intent(out) :: dg ! Delayed group - integer, intent(out) :: gout ! Sampled outgoing group - - end subroutine mgxs_sample_fission_ - - subroutine mgxs_sample_scatter_(this, uvw, gin, gout, mu, wgt) - import Mgxs - class(Mgxs), intent(in) :: this - real(8), intent(in) :: uvw(3) ! Incoming neutron direction - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - end subroutine mgxs_sample_scatter_ - - subroutine mgxs_calculate_xs_(this, gin, sqrtkT, uvw, xs) - import Mgxs, MaterialMacroXS - 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_ - end interface - -!=============================================================================== -! MGXSISO contains the base MGXS data specifically for -! isotropically weighted MGXS -!=============================================================================== - - type, extends(Mgxs) :: MgxsIso - type(XsDataIso), allocatable :: xs(:) ! One for every temperature - contains - procedure :: from_hdf5 => mgxsiso_from_hdf5 ! Initialize Nuclidic MGXS Data - procedure :: get_xs => mgxsiso_get_xs ! Gets Size of Data w/in Object - procedure :: combine => mgxsiso_combine ! inits object - procedure :: sample_fission_energy => mgxsiso_sample_fission_energy - procedure :: sample_scatter => mgxsiso_sample_scatter - procedure :: calculate_xs => mgxsiso_calculate_xs - end type MgxsIso - -!=============================================================================== -! MGXSANGLE contains the base MGXS data specifically for -! angular flux weighted MGXS -!=============================================================================== - - type, extends(Mgxs) :: MgxsAngle - type(XsDataAngle), allocatable :: xs(:) ! One for every temperature - integer :: n_pol ! Number of polar angles - integer :: n_azi ! Number of azimuthal angles - real(8), allocatable :: polar(:) ! polar angles - real(8), allocatable :: azimuthal(:) ! azimuthal angles - - contains - procedure :: from_hdf5 => mgxsang_from_hdf5 ! Initialize Nuclidic MGXS Data - procedure :: get_xs => mgxsang_get_xs ! Gets Size of Data w/in Object - procedure :: combine => mgxsang_combine ! inits object - procedure :: sample_fission_energy => mgxsang_sample_fission_energy - procedure :: sample_scatter => mgxsang_sample_scatter - procedure :: calculate_xs => mgxsang_calculate_xs - end type MgxsAngle - - ! Cross section arrays - type(MgxsContainer), allocatable, target :: nuclides_MG(:) - - ! Cross section caches - type(MgxsContainer), target, allocatable :: macro_xs(:) - - ! Number of energy groups - integer(C_INT) :: num_energy_groups - - ! Number of delayed groups - integer(C_INT) :: num_delayed_groups - - ! 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 - -!=============================================================================== -! MGXS*_FROM_HDF5 reads in the data from the HDF5 Library. At the point of entry -! the file would have been opened and metadata read. -!=============================================================================== - - subroutine mgxs_from_hdf5(this, xs_id, temperature, method, tolerance, & - temps_to_read, order_dim) - class(Mgxs), intent(inout) :: this ! Working Object - integer(HID_T), intent(in) :: xs_id ! Group in H5 file - type(VectorReal), intent(in) :: temperature ! list of desired temperatures - integer, intent(inout) :: method ! Type of temperature access - real(8), intent(in) :: tolerance ! Tolerance on method - type(VectorInt), intent(out) :: temps_to_read ! Temperatures to read - integer, intent(out) :: order_dim ! Scattering data order size - - integer(HID_T) :: kT_group - character(MAX_WORD_LEN), allocatable :: dset_names(:) - real(8), allocatable :: temps_available(:) ! temperatures available - real(8) :: temp_desired - real(8) :: temp_actual - character(MAX_WORD_LEN) :: temp_str - real(8) :: dangle - integer :: ipol, iazi - - ! Get name of dataset from group - this % name = get_name(xs_id) - - ! Get rid of leading '/' - this % name = trim(this % name(2:)) - - if (attribute_exists(xs_id, "atomic_weight_ratio")) then - call read_attribute(this % awr, xs_id, "atomic_weight_ratio") - else - this % awr = MACROSCOPIC_AWR - end if - - ! Determine temperatures available - kT_group = open_group(xs_id, 'kTs') - call get_datasets(kT_group, dset_names) - allocate(temps_available(size(dset_names))) - do i = 1, size(dset_names) - ! Read temperature value - call read_dataset(temps_available(i), kT_group, trim(dset_names(i))) - ! Convert eV to Kelvin - temps_available(i) = temps_available(i) / K_BOLTZMANN - end do - call sort(temps_available) - - ! If only one temperature is available, revert to nearest temperature - if (size(temps_available) == 1 .and. & - method == TEMPERATURE_INTERPOLATION) then - call warning("Cross sections for " // trim(this % name) // " are only & - &available at one temperature. Reverting to nearest temperature & - &method.") - method = TEMPERATURE_NEAREST - end if - - select case (method) - case (TEMPERATURE_NEAREST) - ! Determine actual temperatures to read - TEMP_LOOP: do i = 1, temperature % size() - temp_desired = temperature % data(i) - i_closest = minloc(abs(temps_available - temp_desired), dim=1) - temp_actual = temps_available(i_closest) - if (abs(temp_actual - temp_desired) < tolerance) then - if (find(temps_to_read, nint(temp_actual)) == -1) then - call temps_to_read % push_back(nint(temp_actual)) - end if - else - call fatal_error("MGXS library does not contain cross sections & - &for " // trim(this % name) // " at or near " // & - trim(to_str(nint(temp_desired))) // " K.") - end if - end do TEMP_LOOP - - case (TEMPERATURE_INTERPOLATION) - ! If temperature interpolation or multipole is selected, get a list of - ! bounding temperatures for each actual temperature present in the model - TEMPS_LOOP: do i = 1, temperature % size() - temp_desired = temperature % data(i) - - do j = 1, size(temps_available) - 1 - if (temps_available(j) <= temp_desired .and. & - temp_desired < temps_available(j + 1)) then - if (find(temps_to_read, nint(temps_available(j))) == -1) then - call temps_to_read % push_back(nint(temps_available(j))) - end if - if (find(temps_to_read, nint(temps_available(j + 1))) == -1) then - call temps_to_read % push_back(nint(temps_available(j + 1))) - end if - cycle TEMPS_LOOP - end if - end do - - call fatal_error("MGXS library does not contain cross sections & - &for " // trim(this % name) // " at temperatures that bound " // & - trim(to_str(nint(temp_desired))) // " K.") - end do TEMPS_LOOP - end select - - ! Sort temperatures to read - call sort(temps_to_read) - - ! Get temperatures - n_temperature = temps_to_read % size() - allocate(this % kTs(n_temperature)) - do i = 1, n_temperature - ! Get temperature as a string - temp_str = trim(to_str(temps_to_read % data(i))) // "K" - - ! Read exact temperature value - call read_dataset(this % kTs(i), kT_group, trim(temp_str)) - end do - call close_group(kT_group) - - ! Allocate the XS object for the number of temperatures - select type(this) - type is (MgxsIso) - allocate(this % xs(n_temperature)) - type is (MgxsAngle) - allocate(this % xs(n_temperature)) - end select - - ! Load the remaining metadata - if (attribute_exists(xs_id, "scatter_format")) then - call read_attribute(temp_str, xs_id, "scatter_format") - temp_str = trim(temp_str) - if (to_lower(temp_str) == 'legendre') then - this % scatter_format = ANGLE_LEGENDRE - else if (to_lower(temp_str) == 'histogram') then - this % scatter_format = ANGLE_HISTOGRAM - else if (to_lower(temp_str) == 'tabular') then - this % scatter_format = ANGLE_TABULAR - else - call fatal_error("Invalid scatter_format option!") - end if - else - this % scatter_format = ANGLE_LEGENDRE - end if - if (attribute_exists(xs_id, "scatter_shape")) then - call read_attribute(temp_str, xs_id, "scatter_shape") - temp_str = trim(temp_str) - if (to_lower(temp_str) /= "[g][g'][order]") then - call fatal_error("Invalid scatter_shape option!") - end if - end if - if (attribute_exists(xs_id, "fissionable")) then - call read_attribute(this % fissionable, xs_id, "fissionable") - else - call fatal_error("Fissionable element must be set!") - end if - - ! Get the library's value for the order - if (attribute_exists(xs_id, "order")) then - call read_attribute(order_dim, xs_id, "order") - else - call fatal_error("Order must be provided!") - end if - - ! Store the dimensionality of the data in order_dim. - ! For Legendre data, we usually refer to it as Pn where n is the order. - ! However Pn has n+1 sets of points (since you need to count the P0 - ! moment). Adjust for that. Histogram and Tabular formats dont need this - ! adjustment. - if (this % scatter_format == ANGLE_LEGENDRE) then - order_dim = order_dim + 1 - else - order_dim = order_dim - end if - - ! Get angular meta-data and allocate as needed based off of the - ! information therein - select type(this) - type is (MgxsAngle) - if (attribute_exists(xs_id, "num_polar")) then - call read_attribute(this % n_pol, xs_id, "num_polar") - else - call fatal_error("num_polar must be provided!") - end if - - if (attribute_exists(xs_id, "num_azimuthal")) then - call read_attribute(this % n_azi, xs_id, "num_azimuthal") - else - call fatal_error("num_azimuthal must be provided!") - end if - - ! Set angle data to use equally-spaced bins - allocate(this % polar(this % n_pol)) - dangle = PI / real(this % n_pol, 8) - do ipol = 1, this % n_pol - this % polar(ipol) = (real(ipol, 8) - HALF) * dangle - end do - allocate(this % azimuthal(this % n_azi)) - dangle = TWO * PI / real(this % n_azi, 8) - do iazi = 1, this % n_azi - this % azimuthal(iazi) = -PI + (real(iazi, 8) - HALF) * dangle - end do - end select - - end subroutine mgxs_from_hdf5 - - subroutine mgxsiso_from_hdf5(this, xs_id, energy_groups, delayed_groups, & - temperature, method, tolerance, max_order, & - legendre_to_tabular, legendre_to_tabular_points) - class(MgxsIso), intent(inout) :: this ! Working Object - integer(HID_T), intent(in) :: xs_id ! Group in H5 file - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of delayed groups - type(VectorReal), intent(in) :: temperature ! list of desired temperatures - integer, intent(inout) :: method ! Type of temperature access - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: max_order ! Maximum requested order - logical, intent(in) :: legendre_to_tabular ! Convert Legendres to Tabular? - integer, intent(in) :: legendre_to_tabular_points ! Number of points to use - ! in that conversion - - character(MAX_LINE_LEN) :: temp_str - integer(HID_T) :: xsdata, xsdata_grp, scatt_grp - integer :: ndims - integer(HSIZE_T) :: dims(2) - real(8), allocatable :: temp_arr(:), temp_2d(:, :) - real(8), allocatable :: temp_beta(:, :), temp_3d(:, :, :) - real(8) :: dmu, mu, norm, chi_sum - integer :: order, order_dim, gin, gout, l, imu, length - type(VectorInt) :: temps_to_read - integer :: t, dg, order_data - type(Jagged2D), allocatable :: input_scatt(:), scatt_coeffs(:) - type(Jagged1D), allocatable :: temp_mult(:) - integer, allocatable :: gmin(:), gmax(:) - - ! Call generic data gathering routine (will populate the metadata) - call mgxs_from_hdf5(this, xs_id, temperature, method, tolerance, & - temps_to_read, order_data) - - ! Set the number of delayed groups - this % num_delayed_groups = delayed_groups - - ! Load the more specific data - do t = 1, temps_to_read % size() - associate(xs => this % xs(t)) - - ! Get temperature as a string - temp_str = trim(to_str(temps_to_read % data(t))) // "K" - xsdata_grp = open_group(xs_id, trim(temp_str)) - - ! Allocate data for all the cross sections - allocate(xs % total(energy_groups)) - allocate(xs % absorption(energy_groups)) - allocate(xs % delayed_nu_fission(delayed_groups, energy_groups)) - allocate(xs % prompt_nu_fission(energy_groups)) - allocate(xs % fission(energy_groups)) - allocate(xs % kappa_fission(energy_groups)) - allocate(xs % decay_rate(delayed_groups)) - allocate(xs % inverse_velocity(energy_groups)) - allocate(xs % chi_delayed(delayed_groups, energy_groups, & - energy_groups)) - allocate(xs % chi_prompt(energy_groups, energy_groups)) - - ! Set all fissionable terms to zero - xs % delayed_nu_fission = ZERO - xs % prompt_nu_fission = ZERO - xs % fission = ZERO - xs % kappa_fission = ZERO - xs % chi_delayed = ZERO - xs % chi_prompt = ZERO - xs % decay_rate = ZERO - xs % inverse_velocity = ZERO - - if (this % fissionable) then - - ! Allocate temporary array for beta - allocate(temp_beta(delayed_groups, energy_groups)) - - ! Set beta - if (object_exists(xsdata_grp, "beta")) then - - ! Get the dimensions of the beta dataset - xsdata = open_dataset(xsdata_grp, "beta") - call get_ndims(xsdata, ndims) - - ! Beta is input as (delayed_groups) - if (ndims == 1) then - - ! Allocate temporary array for beta - allocate(temp_arr(delayed_groups)) - - ! Read beta - call read_dataset(temp_arr, xsdata_grp, "beta") - - do dg = 1, delayed_groups - do gin = 1, energy_groups - temp_beta(dg, gin) = temp_arr(dg) - end do - end do - - ! Deallocate temporary beta array - deallocate(temp_arr) - - ! Beta is input as (delayed_groups, energy_groups) - else if (ndims == 2) then - - ! Allocate temporary array for beta - allocate(temp_arr(delayed_groups * energy_groups)) - - ! Read beta - call read_dataset(temp_arr, xsdata_grp, "beta") - - ! Reshape array and set to dedicated beta array - temp_beta = reshape(temp_arr, (/delayed_groups, energy_groups/)) - - ! Deallocate temporary beta array - deallocate(temp_arr) - - else - call fatal_error("beta must be provided as a 1D or 2D array") - end if - - call close_dataset(xsdata) - else - temp_beta = ZERO - end if - - ! If chi provided, set chi-prompt and chi-delayed - if (object_exists(xsdata_grp, "chi")) then - - ! Allocate temporary array for chi - allocate(temp_arr(energy_groups)) - - ! Read chi - call read_dataset(temp_arr, xsdata_grp, "chi") - - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_prompt(gout, gin) = temp_arr(gout) - end do - - ! Normalize chi-prompt so its CDF goes to 1 - chi_sum =sum(xs % chi_prompt(:, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi for a group that sums to & - &zero") - else - xs % chi_prompt(:, gin) = xs % chi_prompt(:, gin) / chi_sum - end if - end do - - ! Set chi-delayed to chi-prompt - do dg = 1, delayed_groups - xs % chi_delayed(dg, :, :) = xs % chi_prompt(:, :) - end do - - ! Deallocate temporary chi array - deallocate(temp_arr) - end if - - ! If nu-fission provided, set prompt-nu_-ission and - ! delayed-nu-fission. If nu fission is a matrix, set chi-prompt and - ! chi-delayed. - if (object_exists(xsdata_grp, "nu-fission")) then - - ! Get the dimensions of the nu-fission dataset - xsdata = open_dataset(xsdata_grp, "nu-fission") - call get_ndims(xsdata, ndims) - - ! If nu-fission is a vector - if (ndims == 1) then - - ! Get nu-fission - call read_dataset(xs % prompt_nu_fission, xsdata_grp, & - "nu-fission") - - ! Set delayed-nu-fission and correct prompt-nu-fission with - ! beta - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin) = temp_beta(dg, gin) * & - xs % prompt_nu_fission(gin) - end do - - ! Correct prompt-nu-fission using delayed neutron fraction - if (delayed_groups > 0) then - xs % prompt_nu_fission(gin) = (1 - sum(temp_beta(:, gin))) & - * xs % prompt_nu_fission(gin) - end if - end do - - ! If nu-fission is a matrix, set prompt-nu-fission, - ! delayed-nu-fission, chi-prompt, and chi-delayed. - else if (ndims == 2) then - - ! chi is embedded in nu-fission -> extract chi - allocate(temp_arr(energy_groups * energy_groups)) - call read_dataset(temp_arr, xsdata_grp, "nu-fission") - allocate(temp_2d(energy_groups, energy_groups)) - temp_2d = reshape(temp_arr, (/energy_groups, energy_groups/)) - - ! Deallocate temporary 1D array for nu-fission matrix - deallocate(temp_arr) - - ! Set the vector nu-fission from the matrix nu-fission - do gin = 1, energy_groups - xs % prompt_nu_fission(gin) = sum(temp_2d(:, gin)) - end do - - ! Set delayed-nu-fission and correct prompt-nu-fission with - ! beta - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin) = temp_beta(dg, gin) * & - xs % prompt_nu_fission(gin) - end do - - ! Correct prompt-nu-fission using delayed neutron fraction - if (delayed_groups > 0) then - xs % prompt_nu_fission(gin) = (1 - sum(temp_beta(:, gin))) & - * xs % prompt_nu_fission(gin) - end if - end do - - ! Now pull out information needed for chi - xs % chi_prompt(:, :) = temp_2d - - ! Deallocate temporary 2D array for nu-fission matrix - deallocate(temp_2d) - - ! Normalize chi so its CDF goes to 1 - do gin = 1, energy_groups - chi_sum = sum(xs % chi_prompt(:, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi for a group that sums to & - &zero") - else - xs % chi_prompt(:, gin) = xs % chi_prompt(:, gin) / chi_sum - end if - end do - - ! Set chi-delayed to chi-prompt - do dg = 1, delayed_groups - xs % chi_delayed(dg, :, :) = xs % chi_prompt(:, :) - end do - else - call fatal_error("nu-fission must be provided as a 1D or 2D & - &array") - end if - - call close_dataset(xsdata) - end if - - ! If chi-prompt provided, set chi-prompt - if (object_exists(xsdata_grp, "chi-prompt")) then - - ! Allocate temporary array for chi-prompt - allocate(temp_arr(energy_groups)) - - ! Get array with chi-prompt - call read_dataset(temp_arr, xsdata_grp, "chi-prompt") - - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_prompt(gout, gin) = temp_arr(gout) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_prompt(:, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi prompt for a group that & - &sums to zero") - else - xs % chi_prompt(:, gin) = xs % chi_prompt(:, gin) / chi_sum - end if - end do - - ! Deallocate temporary array for chi-prompt - deallocate(temp_arr) - end if - - ! If chi-delayed provided, set chi-delayed - if (object_exists(xsdata_grp, "chi-delayed")) then - - ! Get the dimensions of the chi-delayed dataset - xsdata = open_dataset(xsdata_grp, "chi-delayed") - call get_ndims(xsdata, ndims) - - ! If chi-delayed is a vector - if (ndims == 1) then - - ! Allocate temporary array for chi-delayed - allocate(temp_arr(energy_groups)) - - ! Get chi-delayed - call read_dataset(temp_arr, xsdata_grp, "chi-delayed") - - do dg = 1, delayed_groups - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin) = temp_arr(gout) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_delayed(dg, :, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group & - &that sums to zero") - else - xs % chi_delayed(dg, :, gin) = & - xs % chi_delayed(dg, :, gin) / chi_sum - end if - end do - end do - - ! Deallocate temporary array for chi-delayed - deallocate(temp_arr) - - else if (ndims == 2) then - - ! Allocate temporary array for chi-delayed - allocate(temp_arr(delayed_groups * energy_groups)) - - ! Get chi-delayed - call read_dataset(temp_arr, xsdata_grp, "chi-delayed") - allocate(temp_2d(delayed_groups, energy_groups)) - temp_2d = reshape(temp_arr, (/delayed_groups, energy_groups/)) - - do dg = 1, delayed_groups - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin) = temp_2d(dg, gout) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_delayed(dg, :, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group & - &that sums to zero") - else - xs % chi_delayed(dg, :, gin) = & - xs % chi_delayed(dg, :, gin) / chi_sum - end if - end do - end do - - ! Deallocate temporary arrays for chi-delayed - deallocate(temp_arr) - deallocate(temp_2d) - - else - call fatal_error("chi-delayed must be provided as a 1D or 2D & - &array") - end if - - call close_dataset(xsdata) - end if - - ! If prompt-nu-fission present, set prompt-nu-fission - if (object_exists(xsdata_grp, "prompt-nu-fission")) then - - ! Get the dimensions of the prompt-nu-fission dataset - xsdata = open_dataset(xsdata_grp, "prompt-nu-fission") - call get_ndims(xsdata, ndims) - - ! If prompt-nu-fission is a vector - if (ndims == 1) then - - ! Set prompt_nu_fission - call read_dataset(xs % prompt_nu_fission, xsdata_grp, & - "prompt-nu-fission") - - ! If prompt-nu-fission is a matrix, set prompt_nu_fission and - ! chi_prompt. - else if (ndims == 2) then - - ! chi_prompt is embedded in prompt_nu_fission -> extract - ! chi_prompt - allocate(temp_arr(energy_groups * energy_groups)) - call read_dataset(temp_arr, xsdata_grp, "prompt-nu-fission") - allocate(temp_2d(energy_groups, energy_groups)) - temp_2d = reshape(temp_arr, (/energy_groups, energy_groups/)) - - ! Deallocate temporary 1D array for prompt_nu_fission matrix - deallocate(temp_arr) - - ! Set the vector prompt-nu-fission from the matrix - ! prompt-nu-fission - do gin = 1, energy_groups - xs % prompt_nu_fission(gin) = sum(temp_2d(:, gin)) - end do - - ! Now pull out information needed for chi - xs % chi_prompt(:, :) = temp_2d - - ! Deallocate temporary 2D array for nu_fission matrix - deallocate(temp_2d) - - ! Normalize chi so its CDF goes to 1 - do gin = 1, energy_groups - chi_sum = sum(xs % chi_prompt(:, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi prompt for a group & - &that sums to zero") - else - xs % chi_prompt(:, gin) = xs % chi_prompt(:, gin) / chi_sum - end if - end do - else - call fatal_error("prompt-nu-fission must be provided as a 1D & - &or 2D array") - end if - - call close_dataset(xsdata) - end if - - ! If delayed-nu-fission provided, set delayed-nu-fission. If - ! delayed-nu-fission is a matrix, set chi-delayed. - if (object_exists(xsdata_grp, "delayed-nu-fission")) then - - ! Get the dimensions of the delayed-nu-fission dataset - xsdata = open_dataset(xsdata_grp, "delayed-nu-fission") - call get_ndims(xsdata, ndims) - - ! If delayed-nu-fission is a vector - if (ndims == 1) then - - ! If beta is zeros, raise error - if (temp_beta(1,1) == ZERO) then - call fatal_error("cannot set delayed-nu-fission with a 1D & - &array if beta not provided") - end if - - ! Allocate temporary array for delayed-nu-fission - allocate(temp_arr(energy_groups)) - - ! Get delayed-nu-fission - call read_dataset(temp_arr, xsdata_grp, "delayed-nu-fission") - - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin) = temp_beta(dg, gin) * & - temp_arr(gin) - end do - end do - - ! Deallocate temporary delayed-nu-fission array - deallocate(temp_arr) - - ! If delayed-nu-fission is a (delayed_group, energy_group) - ! matrix, set delayed-nu-fission separately for each delayed - ! group. - else if (ndims == 2) then - - ! Get the shape of delayed-nu-fission - call get_shape(xsdata, dims) - - ! Issue error if 1st dimension not correct - if (dims(1) /= delayed_groups) then - call fatal_error("The delayed-nu-fission matrix was input & - &with a 1st dimension not equal to the number of & - &delayed groups.") - end if - - ! Issue error if 2nd dimension not correct - if (dims(2) /= energy_groups) then - call fatal_error("The delayed-nu-fission matrix was input & - &with a 2nd dimension not equal to the number of & - &energy groups.") - end if - - ! Issue warning if delayed_groups == energy_groups - if (delayed_groups == energy_groups) then - call warning("delayed-nu-fission was input as a dimension & - &2 matrix with the same number of delayed groups and & - &groups. It is important to know that OpenMC assumes & - &the dimensions in the matrix are (delayed_groups, & - &energy_groups). Currently, delayed-nu-fission cannot & - &be set as a group by group matrix.") - end if - - ! Get delayed-nu-fission - allocate(temp_arr(delayed_groups * energy_groups)) - call read_dataset(temp_arr, xsdata_grp, "delayed-nu-fission") - xs % delayed_nu_fission = reshape(temp_arr, (/delayed_groups, & - energy_groups/)) - - ! Deallocate temporary array for delayed-nu-fission matrix - deallocate(temp_arr) - - ! If delayed nu-fission is a 3D matrix, set delayed_nu_fission - ! and chi_delayed. - else if (ndims == 3) then - - ! chi_delayed is embedded in delayed_nu_fission -> extract - ! chi_delayed - allocate(temp_arr(delayed_groups * energy_groups * & - energy_groups)) - call read_dataset(temp_arr, xsdata_grp, "delayed-nu-fission") - allocate(temp_3d(delayed_groups, energy_groups, energy_groups)) - temp_3d = reshape(temp_arr, (/delayed_groups, energy_groups, & - energy_groups/)) - - ! Deallocate temporary 1D array for delayed_nu_fission matrix - deallocate(temp_arr) - - ! Set the 2D delayed-nu-fission matrix and 3D chi_dealyed matrix - ! from the 3D delayed-nu-fission matrix - do dg = 1, delayed_groups - do gin = 1, energy_groups - xs % delayed_nu_fission(dg, gin) = sum(temp_3d(dg, :, gin)) - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin) = temp_3d(dg, gout, gin) - end do - end do - end do - - ! Normalize chi_delayed so its CDF goes to 1 - do dg = 1, delayed_groups - do gin = 1, energy_groups - chi_sum = sum(xs % chi_delayed(dg, :, gin)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group & - &that sums to zero") - else - xs % chi_delayed(dg, :, gin) = & - xs % chi_delayed(dg, :, gin) / chi_sum - end if - end do - end do - - ! Deallocate temporary 3D matrix for delayed_nu_fission - deallocate(temp_3d) - else - call fatal_error("delayed-nu-fission must be provided as a & - &1D, 2D, or 3D array") - end if - - call close_dataset(xsdata) - end if - - ! Deallocate temporary beta array - deallocate(temp_beta) - - ! chi-prompt, chi-delayed, prompt-nu-fission, and delayed-nu-fission - ! have been set; Now we will check for the rest of the XS that are - ! unique to fissionable isotopes - - ! Get fission xs - if (object_exists(xsdata_grp, "fission")) then - call read_dataset(xs % fission, xsdata_grp, "fission") - end if - - ! Get kappa-fission xs - if (object_exists(xsdata_grp, "kappa-fission")) then - call read_dataset(xs % kappa_fission, xsdata_grp, "kappa-fission") - end if - - ! Get decay rate xs - if (object_exists(xsdata_grp, "decay rate")) then - call read_dataset(xs % decay_rate, xsdata_grp, "decay rate") - end if - end if - - ! All the XS unique to fissionable isotopes have been set; Now set all - ! the generation XS - - if (object_exists(xsdata_grp, "absorption")) then - call read_dataset(xs % absorption, xsdata_grp, "absorption") - else - call fatal_error("Must provide absorption!") - end if - - ! Get inverse velocity - if (object_exists(xsdata_grp, "inverse-velocity")) then - call read_dataset(xs % inverse_velocity, xsdata_grp, & - "inverse-velocity") - end if - - ! Get scattering data - if (.not. object_exists(xsdata_grp, "scatter_data")) & - call fatal_error("Must provide 'scatter_data'") - - scatt_grp = open_group(xsdata_grp, 'scatter_data') - - ! First get the outgoing group boundary indices - if (object_exists(scatt_grp, "g_min")) then - allocate(gmin(energy_groups)) - call read_dataset(gmin, scatt_grp, "g_min") - else - call fatal_error("'g_min' for the scatter_data must be provided") - end if - - if (object_exists(scatt_grp, "g_max")) then - allocate(gmax(energy_groups)) - call read_dataset(gmax, scatt_grp, "g_max") - else - call fatal_error("'g_max' for the scatter_data must be provided") - end if - - ! Now use this information to find the length of a container array - ! to hold the flattened data - length = 0 - - do gin = 1, energy_groups - length = length + order_data * (gmax(gin) - gmin(gin) + 1) - end do - - ! Allocate flattened array - allocate(temp_arr(length)) - - if (.not. object_exists(scatt_grp, 'scatter_matrix')) & - call fatal_error("'scatter_matrix' must be provided") - call read_dataset(temp_arr, scatt_grp, "scatter_matrix") - - ! Compare the number of orders given with the maximum order of the - ! problem. Strip off the supefluous orders if needed. - if (this % scatter_format == ANGLE_LEGENDRE) then - order = min(order_data - 1, max_order) - order_dim = order + 1 - else - order_dim = order_data - end if - - ! Convert temp_arr to a jagged array ((gin) % data(l, gout)) for - ! passing to ScattData - allocate(input_scatt(energy_groups)) - - index = 1 - do gin = 1, energy_groups - allocate(input_scatt(gin) % data(order_dim, gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - do l = 1, order_dim - input_scatt(gin) % data(l, gout) = temp_arr(index) - index = index + 1 - end do - ! Adjust index for the orders we didnt take - index = index + (order_data - order_dim) - end do - end do - - deallocate(temp_arr) - - ! Finally convert the legendre to tabular if needed - allocate(scatt_coeffs(energy_groups)) - - if (this % scatter_format == ANGLE_LEGENDRE .and. & - legendre_to_tabular) then - - this % scatter_format = ANGLE_TABULAR - order_dim = legendre_to_tabular_points - order = order_dim - dmu = TWO / real(order - 1, 8) - - do gin = 1, energy_groups - allocate(scatt_coeffs(gin) % data(order_dim, gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - - norm = ZERO - - do imu = 1, order_dim - - if (imu == 1) then - mu = -ONE - else if (imu == order_dim) then - mu = ONE - else - mu = -ONE + real(imu - 1, 8) * dmu - end if - - scatt_coeffs(gin) % data(imu, gout) = & - evaluate_legendre(input_scatt(gin) % data(:, gout), mu) - - ! Ensure positivity of distribution - if (scatt_coeffs(gin) % data(imu, gout) < ZERO) & - scatt_coeffs(gin) % data(imu, gout) = ZERO - - ! And accrue the integral - if (imu > 1) then - norm = norm + HALF * dmu * & - (scatt_coeffs(gin) % data(imu - 1, gout) + & - scatt_coeffs(gin) % data(imu, gout)) - end if - end do ! mu - - ! Now that we have the integral, lets ensure that the - ! distribution is normalized such that it preserves the original - ! scattering xs - if (norm > ZERO) then - scatt_coeffs(gin) % data(:, gout) = & - scatt_coeffs(gin) % data(:, gout) * & - input_scatt(gin) % data(1, gout) / norm - end if - end do ! gout - end do ! gin - else - - ! Sticking with current representation - do gin = 1, energy_groups - allocate(scatt_coeffs(gin) % data(order_dim, gmin(gin):gmax(gin))) - scatt_coeffs(gin) % data(:, :) = & - input_scatt(gin) % data(1:order_dim, :) - end do - end if - - deallocate(input_scatt) - - ! Now get the multiplication matrix - if (object_exists(scatt_grp, 'multiplicity_matrix')) then - - ! Now use this information to find the length of a container array - ! to hold the flattened data - length = 0 - - do gin = 1, energy_groups - length = length + (gmax(gin) - gmin(gin) + 1) - end do - - ! Allocate flattened array - allocate(temp_arr(length)) - call read_dataset(temp_arr, scatt_grp, "multiplicity_matrix") - - ! Convert temp_arr to a jagged array ((gin) % data(gout)) for - ! passing to ScattData - allocate(temp_mult(energy_groups)) - - index = 1 - do gin = 1, energy_groups - - allocate(temp_mult(gin) % data(gmin(gin):gmax(gin))) - - do gout = gmin(gin), gmax(gin) - temp_mult(gin) % data(gout) = temp_arr(index) - index = index + 1 - end do - end do - deallocate(temp_arr) - else - - ! Default to multiplicities of 1.0 - allocate(temp_mult(energy_groups)) - - do gin = 1, energy_groups - allocate(temp_mult(gin) % data(gmin(gin):gmax(gin))) - temp_mult(gin) % data = ONE - end do - end if - - ! Allocate and initialize our ScattData Object. - if (this % scatter_format == ANGLE_HISTOGRAM) then - allocate(ScattDataHistogram :: xs % scatter) - else if (this % scatter_format == ANGLE_TABULAR) then - allocate(ScattDataTabular :: xs % scatter) - else if (this % scatter_format == ANGLE_LEGENDRE) then - allocate(ScattDataLegendre :: xs % scatter) - end if - - ! Initialize the ScattData Object - call xs % scatter % init(gmin, gmax, temp_mult, scatt_coeffs) - - ! Check sigA to ensure it is not 0 since it is - ! often divided by in the tally routines - ! (This may happen with Helium data) - do gin = 1, energy_groups - if (xs % absorption(gin) == ZERO) xs % absorption(gin) = 1E-10_8 - end do - - ! Get, or infer, total xs data. - if (object_exists(xsdata_grp, "total")) then - call read_dataset(xs % total, xsdata_grp, "total") - else - xs % total(:) = xs % absorption(:) + xs % scatter % scattxs(:) - end if - - ! Check sigT to ensure it is not 0 since it is - ! often divided by in the tally routines - do gin = 1, energy_groups - if (xs % total(gin) == ZERO) xs % total(gin) = 1E-10_8 - end do - - ! Close the groups we have opened and deallocate - call close_group(xsdata_grp) - call close_group(scatt_grp) - deallocate(scatt_coeffs, temp_mult) - end associate ! xs - end do ! Temperatures - - end subroutine mgxsiso_from_hdf5 - - subroutine mgxsang_from_hdf5(this, xs_id, energy_groups, delayed_groups, & - temperature, method, tolerance, max_order, legendre_to_tabular, & - legendre_to_tabular_points) - class(MgxsAngle), intent(inout) :: this ! Working Object - integer(HID_T), intent(in) :: xs_id ! Group in H5 file - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of energy groups - type(VectorReal), intent(in) :: temperature ! list of desired temperatures - integer, intent(inout) :: method ! Type of temperature access - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: max_order ! Maximum requested order - logical, intent(in) :: legendre_to_tabular ! Convert Legendres to Tabular? - integer, intent(in) :: legendre_to_tabular_points ! Number of points to use - ! in that conversion - - character(MAX_LINE_LEN) :: temp_str - integer(HID_T) :: xsdata, xsdata_grp, scatt_grp - integer :: ndims - integer(HSIZE_T) :: dims(4) - integer, allocatable :: int_arr(:) - real(8), allocatable :: temp_1d(:), temp_3d(:, :, :) - real(8), allocatable :: temp_4d(:, :, :, :), temp_5d(:, :, :, :, :) - real(8), allocatable :: temp_beta(:, :, :, :) - real(8) :: dmu, mu, norm, chi_sum - integer :: order, order_dim, gin, gout, l, imu, dg - type(VectorInt) :: temps_to_read - integer :: t, length, ipol, iazi, order_data - type(Jagged2D), allocatable :: input_scatt(:, :, :), scatt_coeffs(:, :, :) - type(Jagged1D), allocatable :: temp_mult(:, :, :) - integer, allocatable :: gmin(:, :, :), gmax(:, :, :) - - ! Call generic data gathering routine (will populate the metadata) - call mgxs_from_hdf5(this, xs_id, temperature, method, tolerance, & - temps_to_read, order_data) - - ! Set the number of delayed groups - this % num_delayed_groups = delayed_groups - - ! Load the more specific data - do t = 1, temps_to_read % size() - associate(xs => this % xs(t)) - - ! Get temperature as a string - temp_str = trim(to_str(temps_to_read % data(t))) // "K" - xsdata_grp = open_group(xs_id, trim(temp_str)) - - ! Load the more specific data - allocate(xs % prompt_nu_fission(energy_groups, this % n_azi, & - this % n_pol)) - allocate(xs % delayed_nu_fission(delayed_groups, energy_groups, & - this % n_azi, this % n_pol)) - allocate(xs % chi_prompt(energy_groups, energy_groups, this % n_azi, & - this % n_pol)) - allocate(xs % chi_delayed(delayed_groups, energy_groups, & - energy_groups, this % n_azi, this % n_pol)) - allocate(xs % total(energy_groups, this % n_azi, this % n_pol)) - allocate(xs % absorption(energy_groups, this % n_azi, this % n_pol)) - allocate(xs % fission(energy_groups, this % n_azi, this % n_pol)) - allocate(xs % kappa_fission(energy_groups, this % n_azi, & - this % n_pol)) - allocate(xs % decay_rate(delayed_groups, this % n_azi, this % n_pol)) - allocate(xs % inverse_velocity(energy_groups, this % n_azi, & - this % n_pol)) - - ! Set all fissionable terms to zero - xs % delayed_nu_fission = ZERO - xs % prompt_nu_fission = ZERO - xs % fission = ZERO - xs % kappa_fission = ZERO - xs % chi_delayed = ZERO - xs % chi_prompt = ZERO - xs % decay_rate = ZERO - xs % inverse_velocity = ZERO - - if (this % fissionable) then - - ! Allocate temporary array for beta - allocate(temp_beta(delayed_groups, energy_groups, this % n_azi, & - this % n_pol)) - - ! Set beta - if (object_exists(xsdata_grp, "beta")) then - - ! Get the dimensions of the beta dataset - xsdata = open_dataset(xsdata_grp, "beta") - call get_ndims(xsdata, ndims) - - ! Beta is input as (delayed_groups, n_azi, n_pol) - if (ndims == 3) then - - ! Allocate temporary arrays for beta - allocate(temp_1d(delayed_groups * this % n_azi * this % n_pol)) - allocate(temp_3d(delayed_groups, this % n_azi, this % n_pol)) - - ! Read beta - call read_dataset(temp_1d, xsdata_grp, "beta") - temp_3d = reshape(temp_1d, (/delayed_groups, this % n_azi, & - this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - do gin = 1, energy_groups - temp_beta(dg, gin, iazi, ipol) = temp_3d(dg, iazi, ipol) - end do - end do - end do - end do - - ! Deallocate temporary beta arrays - deallocate(temp_1d) - deallocate(temp_3d) - - ! Beta is input as (delayed_groups, energy_groups, n_azi, n_pol) - else if (ndims == 4) then - - ! Allocate temporary array for beta - allocate(temp_1d(delayed_groups * energy_groups * this % n_azi & - * this % n_pol)) - - ! Read beta - call read_dataset(temp_1d, xsdata_grp, "beta") - - ! Reshape array and set to dedicated beta array - temp_beta = reshape(temp_1d, (/delayed_groups, & - energy_groups, this % n_azi, this % n_pol/)) - - ! Deallocate temporary beta array - deallocate(temp_1d) - - else - call fatal_error("beta must be provided as a 3D or 4D array") - end if - - call close_dataset(xsdata) - else - temp_beta = ZERO - end if - - ! If chi provided, set chi-prompt and chi-delayed - if (object_exists(xsdata_grp, "chi")) then - - ! Allocate temporary array for chi - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - allocate(temp_3d(energy_groups, this % n_azi, this % n_pol)) - - ! Read chi - call read_dataset(temp_1d, xsdata_grp, "chi") - temp_3d = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_prompt(gout, gin, iazi, ipol) = & - temp_3d(gout, iazi, ipol) - end do - - ! Normalize chi-prompt so its CDF goes to 1 - chi_sum = sum(xs % chi_prompt(:, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi for a group that sums& - & to zero") - else - xs % chi_prompt(:, gin, iazi, ipol) = & - xs % chi_prompt(:, gin, iazi, ipol) / chi_sum - end if - end do - end do - end do - - ! Set chi-delayed to chi-prompt - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - xs % chi_delayed(dg, :, :, iazi, ipol) = & - xs % chi_prompt(:, :, iazi, ipol) - end do - end do - end do - - ! Deallocate temporary chi arrays - deallocate(temp_1d) - deallocate(temp_3d) - end if - - ! If nu-fission provided, set prompt-nu_-ission and - ! delayed-nu-fission. If nu fission is a matrix, set chi-prompt and - ! chi-delayed. - if (object_exists(xsdata_grp, "nu-fission")) then - - ! Get the dimensions of the nu-fission dataset - xsdata = open_dataset(xsdata_grp, "nu-fission") - call get_ndims(xsdata, ndims) - - ! If nu-fission is a 3D array - if (ndims == 3) then - - ! Get nu-fission - call read_dataset(xs % prompt_nu_fission, xsdata_grp, & - "nu-fission") - - ! Set delayed-nu-fission and correct prompt-nu-fission with - ! beta - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin, iazi, ipol) = & - temp_beta(dg, gin, iazi, ipol) * & - xs % prompt_nu_fission(gin, iazi, ipol) - end do - - ! Correct prompt-nu-fission using delayed neutron fraction - if (delayed_groups > 0) then - xs % prompt_nu_fission(gin, iazi, ipol) = & - (1 - sum(temp_beta(:, gin, iazi, ipol))) * & - xs % prompt_nu_fission(gin, iazi, ipol) - end if - end do - end do - end do - - ! If nu-fission is a matrix, set prompt-nu-fission, - ! delayed-nu-fission, chi-prompt, and chi-delayed. - else if (ndims == 4) then - - ! chi is embedded in nu-fission -> extract chi - allocate(temp_1d(energy_groups * energy_groups * & - this % n_azi * this % n_pol)) - call read_dataset(temp_1d, xsdata_grp, "nu-fission") - allocate(temp_4d(energy_groups, energy_groups, this % n_azi, & - this % n_pol)) - temp_4d = reshape(temp_1d, (/energy_groups, energy_groups, & - this % n_azi, this % n_pol /)) - - ! Deallocate temporary 1D array for nu-fission matrix - deallocate(temp_1d) - - ! Set the vector nu-fission from the matrix nu-fission - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - xs % prompt_nu_fission(gin, iazi, ipol) = & - sum(temp_4d(:, gin, iazi, ipol)) - end do - end do - end do - - ! Set delayed-nu-fission and correct prompt-nu-fission with - ! beta - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin, iazi, ipol) = & - temp_beta(dg, gin, iazi, ipol) * & - xs % prompt_nu_fission(gin, iazi, ipol) - end do - - ! Correct prompt-nu-fission using delayed neutron fraction - if (delayed_groups > 0) then - xs % prompt_nu_fission(gin, iazi, ipol) = & - (1 - sum(temp_beta(:, gin, iazi, ipol))) * & - xs % prompt_nu_fission(gin, iazi, ipol) - end if - end do - end do - end do - - ! Now pull out information needed for chi - xs % chi_prompt(:, :, :, :) = temp_4d - - ! Deallocate temporary 4D array for nu-fission matrix - deallocate(temp_4d) - - ! Normalize chi so its CDF goes to 1 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - chi_sum = sum(xs % chi_prompt(:, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi for a group that & - &sums to zero") - else - xs % chi_prompt(:, gin, iazi, ipol) = & - xs % chi_prompt(:, gin, iazi, ipol) / chi_sum - end if - end do - - ! Set chi-delayed to chi-prompt - do dg = 1, delayed_groups - xs % chi_delayed(dg, :, :, iazi, ipol) = & - xs % chi_prompt(:, :, iazi, ipol) - end do - end do - end do - else - call fatal_error("nu-fission must be provided as a 3D or & - &4D array") - end if - - call close_dataset(xsdata) - end if - - ! If chi-prompt provided, set chi-prompt - if (object_exists(xsdata_grp, "chi-prompt")) then - - ! Allocate temporary array for chi-prompt - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - allocate(temp_3d(energy_groups, this % n_azi, this % n_pol)) - - ! Get array with chi-prompt - call read_dataset(temp_1d, xsdata_grp, "chi-prompt") - temp_3d = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_prompt(gout, gin, iazi, ipol) = & - temp_3d(gout, iazi, ipol) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_prompt(:, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi prompt for a group that& - & sums to zero") - else - xs % chi_prompt(:, gin, iazi, ipol) = & - xs % chi_prompt(:, gin, iazi, ipol) / chi_sum - end if - end do - end do - end do - - ! Deallocate temporary arrays for chi-prompt - deallocate(temp_1d) - deallocate(temp_3d) - end if - - ! If chi-delayed provided, set chi-delayed - if (object_exists(xsdata_grp, "chi-delayed")) then - - ! Get the dimensions of the chi-delayed dataset - xsdata = open_dataset(xsdata_grp, "chi-delayed") - call get_ndims(xsdata, ndims) - - ! chi-delayed is input as (energy_groups, n_azi, n_pol) - if (ndims == 3) then - - ! Allocate temporary array for chi-prompt - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - allocate(temp_3d(energy_groups, this % n_azi, this % n_pol)) - - ! Get array with chi-prompt - call read_dataset(temp_1d, xsdata_grp, "chi-delayed") - temp_3d = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin, iazi, ipol) = & - temp_3d(gout, iazi, ipol) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_delayed(dg, :, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group& - & that sums to zero") - else - xs % chi_delayed(dg, :, gin, iazi, ipol) = & - xs % chi_delayed(dg, :, gin, iazi, ipol) / & - chi_sum - end if - end do - end do - end do - end do - - ! Deallocate temporary arrays for chi-delayed - deallocate(temp_1d) - deallocate(temp_3d) - - ! chi-delayed is input as (delayed_groups, energy_groups, n_azi, - ! n_pol) - else if (ndims == 4) then - - ! Allocate temporary array for chi-delayed - allocate(temp_1d(delayed_groups * energy_groups * this % n_azi & - * this % n_pol)) - allocate(temp_4d(delayed_groups, energy_groups, this % n_azi, & - this % n_pol)) - - ! Get chi-delayed - call read_dataset(temp_1d, xsdata_grp, "chi-delayed") - temp_4d = reshape(temp_1d, (/delayed_groups, energy_groups, & - this % n_azi, this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - do gin = 1, energy_groups - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin, iazi, ipol) = & - temp_4d(dg, gout, iazi, ipol) - end do - - ! Normalize chi so its CDF goes to 1 - chi_sum = sum(xs % chi_delayed(dg, :, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group& - & that sums to zero") - else - xs % chi_delayed(dg, :, gin, iazi, ipol) = & - xs % chi_delayed(dg, :, gin, iazi, ipol) / & - chi_sum - end if - end do - end do - end do - end do - - ! Deallocate temporary arrays for chi-delayed - deallocate(temp_1d) - deallocate(temp_4d) - - else - call fatal_error("chi-delayed must be provided as a 3D or 4D & - &array") - end if - - call close_dataset(xsdata) - end if - - ! If prompt-nu-fission present, set prompt-nu-fission - if (object_exists(xsdata_grp, "prompt-nu-fission")) then - - ! Get the dimensions of the prompt-nu-fission dataset - xsdata = open_dataset(xsdata_grp, "prompt-nu-fission") - call get_ndims(xsdata, ndims) - - ! If prompt-nu-fission is a vector for each azi and pol - if (ndims == 3) then - - ! Set prompt_nu_fission - call read_dataset(xs % prompt_nu_fission, xsdata_grp, & - "prompt-nu-fission") - - ! If prompt-nu-fission is a matrix for each azi and pol, - ! set prompt_nu_fission and chi_prompt. - else if (ndims == 4) then - - ! chi_prompt is embedded in prompt_nu_fission -> extract - ! chi_prompt - allocate(temp_1d(energy_groups * energy_groups & - * this % n_azi * this % n_pol)) - allocate(temp_4d(energy_groups, energy_groups, this % n_azi, & - this % n_pol)) - call read_dataset(temp_1d, xsdata_grp, "prompt-nu-fission") - temp_4d = reshape(temp_1d, (/energy_groups, energy_groups, & - this % n_azi, this % n_pol/)) - - ! Deallocate temporary 1D array for prompt_nu_fission matrix - deallocate(temp_1d) - - ! Set the vector prompt-nu-fission from the matrix - ! prompt-nu-fission - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - xs % prompt_nu_fission(gin, iazi, ipol) = & - sum(temp_4d(:, gin, iazi, ipol)) - end do - end do - end do - - ! Now pull out information needed for chi - xs % chi_prompt(:, :, :, :) = temp_4d - - ! Deallocate temporary 4D array for nu_fission matrix - deallocate(temp_4d) - - ! Normalize chi so its CDF goes to 1 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - chi_sum = sum(xs % chi_prompt(:, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi prompt for a group & - &that sums to zero") - else - xs % chi_prompt(:, gin, iazi, ipol) = & - xs % chi_prompt(:, gin, iazi, ipol) / chi_sum - end if - end do - end do - end do - else - call fatal_error("prompt-nu-fission must be provided as a 3D & - &or 4D array") - end if - - call close_dataset(xsdata) - end if - - ! If delayed-nu-fission provided, set delayed-nu-fission. If - ! delayed-nu-fission is a matrix, set chi-delayed. - if (object_exists(xsdata_grp, "delayed-nu-fission")) then - - ! Get the dimensions of the delayed-nu-fission dataset - xsdata = open_dataset(xsdata_grp, "delayed-nu-fission") - call get_ndims(xsdata, ndims) - - ! delayed-nu-fission is input as (energy_groups, n_azi, n_pol) - if (ndims == 3) then - - ! If beta is zeros, raise error - if (temp_beta(1,1,1,1) == ZERO) then - call fatal_error("cannot set delayed-nu-fission with a 3D & - &array if beta not provided") - end if - - ! Allocate temporary arrays for delayed-nu-fission - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - allocate(temp_3d(energy_groups, this % n_azi, this % n_pol)) - - ! Get delayed-nu-fission - call read_dataset(temp_1d, xsdata_grp, "delayed-nu-fission") - temp_3d = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - do dg = 1, delayed_groups - - ! Set delayed-nu-fission using delayed neutron fraction - xs % delayed_nu_fission(dg, gin, iazi, ipol) = & - temp_beta(dg, gin, iazi, ipol) * & - temp_3d(gin, iazi, ipol) - end do - end do - end do - end do - - ! Deallocate temporary delayed-nu-fission arrays - deallocate(temp_1d) - deallocate(temp_3d) - - ! If delayed-nu-fission is a (delayed_group, energy_group, - ! n_azi, n_pol) matrix, set delayed-nu-fission separately for - ! each delayed group. - else if (ndims == 4) then - - ! Get the shape of delayed-nu-fission - call get_shape(xsdata, dims) - - ! Issue error if 1st dimension not correct - if (dims(1) /= delayed_groups) then - call fatal_error("The delayed-nu-fission matrix was input & - &with a 1st dimension not equal to the number of & - &delayed groups.") - end if - - ! Issue error if 2nd dimension not correct - if (dims(2) /= energy_groups) then - call fatal_error("The delayed-nu-fission matrix was input & - &with a 2nd dimension not equal to the number of & - &energy groups.") - end if - - ! Issue warning if delayed_groups == energy_groups - if (delayed_groups == energy_groups) then - call warning("delayed-nu-fission was input as a dimension & - &4 matrix with the same number of delayed groups and & - &groups. It is important to know that OpenMC assumes & - &the dimensions in the matrix are (delayed_groups, & - &energy_groups, n_azi, n_pol). Currently, & - &delayed-nu-fission cannot be set as a group by group & - &matrix.") - end if - - ! Get delayed-nu-fission - allocate(temp_1d(delayed_groups * energy_groups * this % n_azi & - * this % n_pol)) - call read_dataset(temp_1d, xsdata_grp, "delayed-nu-fission") - xs % delayed_nu_fission = reshape(temp_1d, (/delayed_groups, & - energy_groups, this % n_azi, this % n_pol /)) - - ! Deallocate temporary array for delayed-nu-fission matrix - deallocate(temp_1d) - - ! If delayed nu-fission is a 5D matrix, set delayed_nu_fission - ! and chi_delayed. - else if (ndims == 5) then - - ! chi_delayed is embedded in delayed_nu_fission -> extract - ! chi_delayed - allocate(temp_1d(delayed_groups * energy_groups * & - energy_groups * this % n_azi * this % n_pol)) - allocate(temp_5d(delayed_groups, energy_groups, energy_groups, & - this % n_azi, this % n_pol)) - call read_dataset(temp_1d, xsdata_grp, "delayed-nu-fission") - temp_5d = reshape(temp_1d, (/delayed_groups, energy_groups, & - energy_groups, this % n_azi, this % n_pol/)) - - ! Deallocate temporary 1D array for delayed_nu_fission matrix - deallocate(temp_1d) - - ! Set the 4D delayed-nu-fission matrix and 5D chi_delayed matrix - ! from the 5D delayed-nu-fission matrix - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - do gin = 1, energy_groups - xs % delayed_nu_fission(dg, gin, iazi, ipol) = & - sum(temp_5d(dg, :, gin, iazi, ipol)) - do gout = 1, energy_groups - xs % chi_delayed(dg, gout, gin, iazi, ipol) = & - temp_5d(dg, gout, gin, iazi, ipol) - end do - end do - end do - end do - end do - - ! Normalize chi_delayed so its CDF goes to 1 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do dg = 1, delayed_groups - do gin = 1, energy_groups - chi_sum = sum(xs % chi_delayed(dg, :, gin, iazi, ipol)) - if (chi_sum == ZERO) then - call fatal_error("Encountered chi delayed for a group& - & that sums to zero") - else - xs % chi_delayed(dg, :, gin, iazi, ipol) = & - xs % chi_delayed(dg, :, gin, iazi, ipol) / & - chi_sum - end if - end do - end do - end do - end do - - ! Deallocate temporary 5D matrix for delayed_nu_fission - deallocate(temp_5d) - else - call fatal_error("delayed-nu-fission must be provided as a & - &3D, 4D, or 5D array") - end if - - call close_dataset(xsdata) - end if - - ! Deallocate temporary beta array - deallocate(temp_beta) - - ! chi-prompt, chi-delayed, prompt-nu-fission, and delayed-nu-fission - ! have been set; Now we will check for the rest of the XS that are - ! unique to fissionable isotopes - - ! Set fission xs - if (object_exists(xsdata_grp, "fission")) then - - ! Allocate temporary array for fission - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - - ! Get fission array - call read_dataset(temp_1d, xsdata_grp, "fission") - xs % fission(:, :, :) = reshape(temp_1d, (/energy_groups, & - this % n_azi, this % n_pol/)) - - ! Deallocate temporary array for fission - deallocate(temp_1d) - end if - - ! Set kappa-fission xs - if (object_exists(xsdata_grp, "kappa-fission")) then - - ! Allocate temporary array for kappa-fission - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - - ! Get kappa-fission array - call read_dataset(temp_1d, xsdata_grp, "kappa-fission") - xs % kappa_fission(:, :, :) = reshape(temp_1d, (/energy_groups, & - this % n_azi, this % n_pol/)) - - ! Deallocate temporary array for kappa-fission - deallocate(temp_1d) - end if - - ! Set decay rate - if (object_exists(xsdata_grp, "decay rate")) then - - ! Allocate temporary array for decay rate - allocate(temp_1d(this % n_azi * this % n_pol * delayed_groups)) - - ! Get decay rate array - call read_dataset(temp_1d, xsdata_grp, "decay rate") - xs % decay_rate(:, :, :) = reshape(temp_1d, (/delayed_groups, & - this % n_azi, this % n_pol/)) - - ! Deallocate temporary array for decay rate - deallocate(temp_1d) - end if - end if - - ! All the XS unique to fissionable isotopes have been set; Now set all - ! the generation XS - - if (object_exists(xsdata_grp, "absorption")) then - - ! Allocate temporary array for absorption xs - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - - ! Read in absorption xs - call read_dataset(temp_1d, xsdata_grp, "absorption") - - xs % absorption = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - ! Deallocate temporary array for absorption xs - deallocate(temp_1d) - else - call fatal_error("Must provide absorption!") - end if - - if (object_exists(xsdata_grp, "inverse-velocity")) then - - ! Allocate temporary array for inverse velocity - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - - ! Read in inverse velocity - call read_dataset(temp_1d, xsdata_grp, "inverse-velocity") - - xs % inverse_velocity = reshape(temp_1d, (/energy_groups, & - this % n_azi, this % n_pol/)) - - ! Deallocate temporary array for inverse velocity - deallocate(temp_1d) - end if - - ! Get scattering data - if (.not. object_exists(xsdata_grp, "scatter_data")) & - call fatal_error("Must provide 'scatter_data'") - - scatt_grp = open_group(xsdata_grp, 'scatter_data') - - ! First get the outgoing group boundary indices - if (object_exists(scatt_grp, "g_min")) then - - allocate(int_arr(energy_groups * this % n_azi * this % n_pol)) - - call read_dataset(int_arr, scatt_grp, "g_min") - allocate(gmin(energy_groups, this % n_azi, this % n_pol)) - gmin = reshape(int_arr, (/energy_groups, this % n_azi, & - this % n_pol/)) - - deallocate(int_arr) - else - call fatal_error("'g_min' for the scatter_data must be provided") - end if - - if (object_exists(scatt_grp, "g_max")) then - - allocate(int_arr(energy_groups * this % n_azi * this % n_pol)) - - call read_dataset(int_arr, scatt_grp, "g_max") - allocate(gmax(energy_groups, this % n_azi, this % n_pol)) - gmax = reshape(int_arr, (/energy_groups, this % n_azi, & - this % n_pol/)) - - deallocate(int_arr) - else - call fatal_error("'g_max' for the scatter_data must be provided") - end if - - ! Now use this information to find the length of a container array - ! to hold the flattened data - length = 0 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - length = length + order_data * (gmax(gin, iazi, ipol) - & - gmin(gin, iazi, ipol) + 1) - end do - end do - end do - - ! Allocate flattened array - allocate(temp_1d(length)) - - if (.not. object_exists(scatt_grp, 'scatter_matrix')) & - call fatal_error("'scatter_matrix' must be provided") - call read_dataset(temp_1d, scatt_grp, "scatter_matrix") - - ! Compare the number of orders given with the maximum order of the - ! problem. Strip off the superfluous orders if needed. - if (this % scatter_format == ANGLE_LEGENDRE) then - order = min(order_data - 1, max_order) - order_dim = order + 1 - else - order_dim = order_data - end if - - ! Convert temp_1d to a jagged array ((gin) % data(l, gout)) for - ! passing to ScattData - allocate(input_scatt(energy_groups, this % n_azi, this % n_pol)) - - index = 1 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - allocate(input_scatt(gin, iazi, ipol) % data(order_dim, & - gmin(gin, iazi, ipol):gmax(gin, iazi, ipol))) - do gout = gmin(gin, iazi, ipol), gmax(gin, iazi, ipol) - do l = 1, order_dim - input_scatt(gin, iazi, ipol) % data(l, gout) = & - temp_1d(index) - index = index + 1 - end do ! gout - ! Adjust index for the orders we didnt take - index = index + (order_data - order_dim) - end do ! order - end do ! gin - end do ! iazi - end do ! ipol - - deallocate(temp_1d) - - ! Finally convert the legendre to tabular if needed - allocate(scatt_coeffs(energy_groups, this % n_azi, this % n_pol)) - - if (this % scatter_format == ANGLE_LEGENDRE .and. & - legendre_to_tabular) then - - this % scatter_format = ANGLE_TABULAR - order_dim = legendre_to_tabular_points - order = order_dim - dmu = TWO / real(order - 1, 8) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - allocate(scatt_coeffs(gin, iazi, ipol) % data(& - order_dim, & - gmin(gin, iazi, ipol):gmax(gin, iazi, ipol))) - - do gout = gmin(gin, iazi, ipol), gmax(gin, iazi, ipol) - norm = ZERO - do imu = 1, order_dim - if (imu == 1) then - mu = -ONE - else if (imu == order_dim) then - mu = ONE - else - mu = -ONE + real(imu - 1, 8) * dmu - end if - - scatt_coeffs(gin, iazi, ipol) % data(imu, gout) = & - evaluate_legendre(& - input_scatt(gin, iazi, ipol) % data(:, gout), mu) - - ! Ensure positivity of distribution - if (scatt_coeffs(gin, iazi, ipol) % data(imu, gout) < ZERO) & - scatt_coeffs(gin, iazi, ipol) % data(imu, gout) = ZERO - - ! And accrue the integral - if (imu > 1) then - norm = norm + HALF * dmu * & - (scatt_coeffs(gin, iazi, ipol) % data(imu - 1, gout) + & - scatt_coeffs(gin, iazi, ipol) % data(imu, gout)) - end if - end do ! mu - - ! Now that we have the integral, lets ensure that the distribution - ! is normalized such that it preserves the original scattering xs - if (norm > ZERO) then - scatt_coeffs(gin, iazi, ipol) % data(:, gout) = & - scatt_coeffs(gin, iazi, ipol) % data(:, gout) * & - input_scatt(gin, iazi, ipol) % data(1, gout) / & - norm - end if - end do ! gout - end do ! gin - end do ! iazi - end do ! ipol - else - ! Sticking with current representation, carry forward - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - allocate(scatt_coeffs(gin, iazi, ipol) % data(order_dim, & - gmin(gin, iazi, ipol):gmax(gin, iazi, ipol))) - scatt_coeffs(gin, iazi, ipol) % data(:, :) = & - input_scatt(gin, iazi, ipol) % data(1:order_dim, :) - end do - end do - end do - end if - - deallocate(input_scatt) - - ! Now get the multiplication matrix - if (object_exists(scatt_grp, 'multiplicity_matrix')) then - - ! Now use this information to find the length of a container array - ! to hold the flattened data - length = 0 - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - length = length + (gmax(gin, iazi, ipol) - gmin(gin, iazi, ipol) + 1) - end do - end do - end do - - ! Allocate flattened array - allocate(temp_1d(length)) - call read_dataset(temp_1d, scatt_grp, "multiplicity_matrix") - - ! Convert temp_1d to a jagged array ((gin) % data(gout)) for passing - ! to ScattData - allocate(temp_mult(energy_groups, this % n_azi, this % n_pol)) - - index = 1 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - allocate(temp_mult(gin, iazi, ipol) % data( & - gmin(gin, iazi, ipol):gmax(gin, iazi, ipol))) - do gout = gmin(gin, iazi, ipol), gmax(gin, iazi, ipol) - temp_mult(gin, iazi, ipol) % data(gout) = temp_1d(index) - index = index + 1 - end do - end do - end do - end do - deallocate(temp_1d) - else - - allocate(temp_mult(energy_groups, this % n_azi, this % n_pol)) - - ! Default to multiplicities of 1.0 - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - allocate(temp_mult(gin, iazi, ipol) % data( & - gmin(gin, iazi, ipol):gmax(gin, iazi, ipol))) - temp_mult(gin, iazi, ipol) % data = ONE - end do - end do - end do - end if - - ! Allocate and initialize our ScattData Object. - allocate(xs % scatter(this % n_azi, this % n_pol)) - - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - - ! Allocate and initialize our ScattData Object. - if (this % scatter_format == ANGLE_HISTOGRAM) then - allocate(ScattDataHistogram :: xs % scatter(iazi, ipol) % obj) - else if (this % scatter_format == ANGLE_TABULAR) then - allocate(ScattDataTabular :: xs % scatter(iazi, ipol) % obj) - else if (this % scatter_format == ANGLE_LEGENDRE) then - allocate(ScattDataLegendre :: xs % scatter(iazi, ipol) % obj) - end if - - ! Initialize the ScattData Object - call xs % scatter(iazi, ipol) % obj % init(gmin(:, iazi, ipol), & - gmax(:, iazi, ipol), temp_mult(:, iazi, ipol), & - scatt_coeffs(:, iazi, ipol)) - end do - end do - - ! Check sigA to ensure it is not 0 since it is - ! often divided by in the tally routines - ! (This may happen with Helium data) - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - if (xs % absorption(gin, iazi, ipol) == ZERO) then - xs % absorption(gin, iazi, ipol) = 1E-10_8 - end if - end do - end do - end do - - if (object_exists(xsdata_grp, "total")) then - - allocate(temp_1d(energy_groups * this % n_azi * this % n_pol)) - call read_dataset(temp_1d, xsdata_grp, "total") - xs % total = reshape(temp_1d, (/energy_groups, this % n_azi, & - this % n_pol/)) - - deallocate(temp_1d) - else - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - xs % total(:, iazi, ipol) = xs % absorption(:, iazi, ipol) + & - xs % scatter(iazi, ipol) % obj % scattxs(:) - end do - end do - end if - - ! Check sigT to ensure it is not 0 since it is often divided by in - ! the tally routines - do ipol = 1, this % n_pol - do iazi = 1, this % n_azi - do gin = 1, energy_groups - if (xs % total(gin, iazi, ipol) == ZERO) then - xs % total(gin, iazi, ipol) = 1E-10_8 - end if - end do - end do - end do - - ! Close the groups we have opened and deallocate - call close_group(xsdata_grp) - call close_group(scatt_grp) - deallocate(scatt_coeffs, temp_mult) - - end associate ! xs - end do ! Temperatures - end subroutine mgxsang_from_hdf5 - -!=============================================================================== -! MGXS*_COMBINE Builds a macroscopic Mgxs object from microscopic Mgxs objects -!=============================================================================== - - subroutine mgxs_combine(this, temps, mat, nuclides, max_order, & - scatter_format, order_dim) - class(Mgxs), intent(inout) :: this ! The Mgxs to initialize - type(VectorReal), intent(in) :: temps ! Temperatures to obtain - type(Material), pointer, intent(in) :: mat ! base material - type(MgxsContainer), intent(in) :: nuclides(:) ! List of nuclides to harvest from - integer, intent(in) :: max_order ! Maximum requested order - integer, intent(out) :: scatter_format ! Type of scatter - integer, intent(out) :: order_dim ! Scattering data order size - - integer :: t, mat_max_order, order - - ! Fill in meta-data from material information - if (mat % name == "") then - this % name = trim(to_str(mat % id)) - else - this % name = trim(mat % name) - end if - - ! Set whether this material is fissionable - this % fissionable = mat % fissionable - - ! The following info we should initialize, but we dont need it nor - ! does it have guaranteed meaning. - this % awr = -ONE - - allocate(this % kTs(temps % size())) - - do t = 1, temps % size() - this % kTs(t) = temps % data(t) - end do - - ! Allocate the XS object for the number of temperatures - select type(this) - type is (MgxsIso) - allocate(this % xs(temps % size())) - type is (MgxsAngle) - allocate(this % xs(temps % size())) - end select - - ! Determine the scattering type of our data and ensure all scattering orders - ! are the same. - scatter_format = nuclides(mat % nuclide(1)) % obj % scatter_format - - select type(nuc => nuclides(mat % nuclide(1)) % obj) - type is (MgxsIso) - order = size(nuc % xs(1) % scatter % dist(1) % data, dim=1) - type is (MgxsAngle) - order = size(nuc % xs(1) % scatter(1, 1) % obj % dist(1) % data, dim=1) - end select - - ! If we have tabular only data, then make sure all datasets have same size - if (scatter_format == ANGLE_HISTOGRAM) then - ! Check all scattering data to ensure it is the same size - do i = 2, mat % n_nuclides - select type(nuc => nuclides(mat % nuclide(i)) % obj) - type is (MgxsIso) - if (order /= size(nuc % xs(1) % scatter % dist(1) % data, dim=1)) & - call fatal_error("All histogram scattering entries must be& - & same length!") - type is (MgxsAngle) - if (order /= size(nuc % xs(1) % scatter(1, 1) % obj % dist(1) % data, dim=1)) & - call fatal_error("All histogram scattering entries must be& - & same length!") - end select - end do - - ! Ok, got our order, store the dimensionality - order_dim = order - - else if (scatter_format == ANGLE_TABULAR) then - ! Check all scattering data to ensure it is the same size - do i = 2, mat % n_nuclides - select type(nuc => nuclides(mat % nuclide(i)) % obj) - type is (MgxsIso) - if (order /= size(nuc % xs(1) % scatter % dist(1) % data, dim=1)) & - call fatal_error("All tabular scattering entries must be& - & same length!") - type is (MgxsAngle) - if (order /= size(nuc % xs(1) % scatter(1, 1) % obj % dist(1) % data, dim=1)) & - call fatal_error("All tabular scattering entries must be& - & same length!") - end select - end do - - ! Ok, got our order, store the dimensionality - order_dim = order - - else if (scatter_format == ANGLE_LEGENDRE) then - - ! Need to determine the maximum scattering order of all data in this material - mat_max_order = 0 - - do i = 1, mat % n_nuclides - select type(nuc => nuclides(mat % nuclide(i)) % obj) - type is (MgxsIso) - if (size(nuc % xs(1) % scatter % dist(1) % data, & - dim=1) > mat_max_order) & - mat_max_order = size(nuc % xs(1) % scatter % dist(1) % data, & - dim=1) - type is (MgxsAngle) - if (size(nuc % xs(1) % scatter(1, 1) % obj % dist(1) % data, & - dim=1) > mat_max_order) & - mat_max_order = & - size(nuc % xs(1) % scatter(1, 1) % obj % dist(1) % data, & - dim=1) - end select - end do - - ! Now need to compare this material maximum scattering order with - ! the problem wide max scatt order and use whichever is lower - order = min(mat_max_order, max_order + 1) - - ! Ok, got our order, store the dimensionality - order_dim = order - end if - - end subroutine mgxs_combine - - subroutine mgxsiso_combine(this, temps, mat, nuclides, energy_groups, & - delayed_groups, max_order, tolerance, method) - class(MgxsIso), intent(inout) :: this ! The Mgxs to initialize - type(VectorReal), intent(in) :: temps ! Temperatures to obtain [MeV] - type(Material), pointer, intent(in) :: mat ! base material - type(MgxsContainer), intent(in) :: nuclides(:) ! List of nuclides to harvest from - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of delayed groups - integer, intent(in) :: max_order ! Maximum requested order - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: method ! Type of temperature access - - integer :: i ! loop index over nuclides - integer :: t ! Index in to temps - integer :: gin, gout ! group indices - integer :: dg ! delayed group index - real(8) :: atom_density ! atom density of a nuclide - real(8) :: norm, nuscatt - integer :: order_dim, nuc_order_dim - real(8), allocatable :: temp_mult(:, :), mult_num(:, :), mult_denom(:, :) - real(8), allocatable :: scatt_coeffs(:, :, :) - integer :: nuc_t - integer, allocatable :: nuc_ts(:) - real(8) :: temp_actual, temp_desired, interp - integer :: scatter_format - type(Jagged2D), allocatable :: nuc_matrix(:) - integer, allocatable :: gmin(:), gmax(:) - type(Jagged2D), allocatable :: jagged_scatt(:) - type(Jagged1D), allocatable :: jagged_mult(:) - - ! Set the meta-data - call mgxs_combine(this, temps, mat, nuclides, max_order, scatter_format, & - order_dim) - - ! Set the number of delayed groups - this % num_delayed_groups = delayed_groups - - ! Create the Xs Data for each temperature - TEMP_LOOP: do t = 1, temps % size() - - ! Allocate and initialize the data needed for macro_xs(i_mat) object - allocate(this % xs(t) % total(energy_groups)) - this % xs(t) % total(:) = ZERO - - allocate(this % xs(t) % absorption(energy_groups)) - this % xs(t) % absorption(:) = ZERO - - allocate(this % xs(t) % fission(energy_groups)) - this % xs(t) % fission(:) = ZERO - - allocate(this % xs(t) % kappa_fission(energy_groups)) - this % xs(t) % kappa_fission(:) = ZERO - - allocate(this % xs(t) % prompt_nu_fission(energy_groups)) - this % xs(t) % prompt_nu_fission(:) = ZERO - - allocate(this % xs(t) % delayed_nu_fission(delayed_groups, & - energy_groups)) - this % xs(t) % delayed_nu_fission(:, :) = ZERO - - allocate(this % xs(t) % chi_prompt(energy_groups, energy_groups)) - this % xs(t) % chi_prompt(:, :) = ZERO - - allocate(this % xs(t) % chi_delayed(delayed_groups, energy_groups, & - energy_groups)) - this % xs(t) % chi_delayed(:, :, :) = ZERO - - allocate(this % xs(t) % inverse_velocity(energy_groups)) - this % xs(t) % inverse_velocity(:) = ZERO - - allocate(this % xs(t) % decay_rate(delayed_groups)) - this % xs(t) % decay_rate(:) = ZERO - - allocate(temp_mult(energy_groups, energy_groups)) - temp_mult(:, :) = ZERO - - allocate(mult_num(energy_groups, energy_groups)) - mult_num(:, :) = ZERO - - allocate(mult_denom(energy_groups, energy_groups)) - mult_denom(:, :) = ZERO - - allocate(scatt_coeffs(order_dim, energy_groups, energy_groups)) - scatt_coeffs(:, :, :) = ZERO - - this % scatter_format = scatter_format - - if (scatter_format == ANGLE_LEGENDRE) then - allocate(ScattDataLegendre :: this % xs(t) % scatter) - else if (scatter_format == ANGLE_TABULAR) then - allocate(ScattDataTabular :: this % xs(t) % scatter) - else if (scatter_format == ANGLE_HISTOGRAM) then - allocate(ScattDataHistogram :: this % xs(t) % scatter) - end if - - ! Add contribution from each nuclide in material - NUC_LOOP: do i = 1, mat % n_nuclides - associate(nuc => nuclides(mat % nuclide(i)) % obj) - - ! Copy atom density of nuclide in material - atom_density = mat % atom_density(i) - - select case (method) - case (TEMPERATURE_NEAREST) - - ! Determine actual temperatures to read - temp_desired = temps % data(i) - allocate(nuc_ts(1)) - - nuc_ts(1) = minloc(abs(nuc % kTs - temp_desired), dim=1) - temp_actual = nuc % kTs(nuc_ts(1)) - - if (abs(temp_actual - temp_desired) >= K_BOLTZMANN * tolerance) then - call fatal_error("MGXS library does not contain cross sections & - &for " // trim(this % name) // " at or near " // & - trim(to_str(nint(temp_desired / K_BOLTZMANN))) // " K.") - end if - - case (TEMPERATURE_INTERPOLATION) - - ! If temperature interpolation or multipole is selected, get a - ! list of bounding temperatures for each actual temperature - ! present in the model - temp_desired = temps % data(i) - allocate(nuc_ts(2)) - - do j = 1, size(nuc % kTs) - 1 - if (nuc % kTs(j) <= temp_desired .and. & - temp_desired < nuc % kTs(j + 1)) then - nuc_ts(1) = j - nuc_ts(2) = j + 1 - end if - end do - - call fatal_error("Nuclear data library does not contain cross sections & - &for " // trim(this % name) // " at temperatures that bound " // & - trim(to_str(nint(temp_desired / K_BOLTZMANN))) // " K.") - end select - - select type(nuc) - type is (MgxsIso) - do j = 1, size(nuc_ts) - - nuc_t = nuc_ts(j) - - if (size(nuc_ts) == 1) then - interp = ONE - else if (j == 1) then - interp = (ONE - (temp_desired - nuc % kTs(nuc_ts(1))) / & - (nuc % kTs(nuc_ts(2)) - nuc % kTs(nuc_ts(1)))) - else - interp = ONE - interp - end if - - ! Perform our operations which depend upon the type - ! Add contributions to total, absorption, and fission data (if necessary) - this % xs(t) % total = this % xs(t) % total + & - atom_density * nuc % xs(nuc_t) % total * interp - - this % xs(t) % absorption = this % xs(t) % absorption + & - atom_density * nuc % xs(nuc_t) % absorption * interp - - this % xs(t) % decay_rate = this % xs(t) % decay_rate + & - atom_density * nuc % xs(nuc_t) % decay_rate * interp - - this % xs(t) % inverse_velocity = & - this % xs(t) % inverse_velocity + & - atom_density * nuc % xs(nuc_t) % inverse_velocity * interp - - if (nuc % fissionable) then - - this % xs(t) % chi_prompt = this % xs(t) % chi_prompt + & - atom_density * nuc % xs(nuc_t) % chi_prompt * interp - - this % xs(t) % chi_delayed = this % xs(t) % chi_delayed + & - atom_density * nuc % xs(nuc_t) % chi_delayed * interp - - this % xs(t) % prompt_nu_fission = this % xs(t) % & - prompt_nu_fission + atom_density * nuc % xs(nuc_t) % & - prompt_nu_fission * interp - - this % xs(t) % delayed_nu_fission = this % xs(t) % & - delayed_nu_fission + atom_density * nuc % xs(nuc_t) % & - delayed_nu_fission * interp - - this % xs(t) % fission = this % xs(t) % fission + & - atom_density * nuc % xs(nuc_t) % fission * interp - - this % xs(t) % kappa_fission = this % xs(t) % kappa_fission +& - atom_density * nuc % xs(nuc_t) % kappa_fission * interp - end if - - ! We will next gather the multiplicity and scattering matrices. - ! To avoid multiple re-allocations as we resize the storage - ! matrix (and/or to avoidlots of duplicate code), we will use a - ! dense matrix for this storage, with a reduction to the sparse - ! format at the end. - - ! Get the multiplicity_matrix - ! To combine from nuclidic data we need to use the final relationship - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - ! sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - ! Developed as follows: - ! mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - ! sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - ! nuscatt_{i,g,g'} can be reconstructed from scatter % energy and - ! scatter % scattxs - do gin = 1, energy_groups - do gout = nuc % xs(nuc_t) % scatter % gmin(gin), nuc % xs(nuc_t) % scatter % gmax(gin) - - nuscatt = nuc % xs(nuc_t) % scatter % scattxs(gin) * & - nuc % xs(nuc_t) % scatter % energy(gin) % data(gout) - - mult_num(gout, gin) = mult_num(gout, gin) + atom_density * & - nuscatt * interp - - if (nuc % xs(nuc_t) % scatter % mult(gin) % data(gout) > ZERO) then - mult_denom(gout, gin) = mult_denom(gout,gin) + atom_density * & - nuscatt / nuc % xs(nuc_t) % scatter % mult(gin) % data(gout) * & - interp - else - ! Avoid division by zero - mult_denom(gout, gin) = mult_denom(gout,gin) + atom_density * & - interp - end if - end do - end do - - ! Get the complete scattering matrix - nuc_order_dim = size(nuc % xs(nuc_t) % scatter % dist(1) % data, dim=1) - nuc_order_dim = min(nuc_order_dim, order_dim) - - call nuc % xs(nuc_t) % scatter % get_matrix(nuc_order_dim, & - nuc_matrix) - - do gin = 1, energy_groups - do gout = nuc % xs(nuc_t) % scatter % gmin(gin), & - nuc % xs(nuc_t) % scatter % gmax(gin) - scatt_coeffs(1:nuc_order_dim, gout, gin) = & - scatt_coeffs(1: nuc_order_dim, gout, gin) + & - atom_density * interp * & - nuc_matrix(gin) % data(1:nuc_order_dim, gout) - end do - end do - end do - - type is (MgxsAngle) - call fatal_error("Invalid passing of MgxsAngle to MgxsIso object") - end select - - ! Obtain temp_mult - do gin = 1, energy_groups - do gout = 1, energy_groups - if (mult_denom(gout, gin) > ZERO) then - temp_mult(gout, gin) = mult_num(gout, gin) / mult_denom(gout, gin) - else - temp_mult(gout, gin) = ONE - end if - end do - end do - - ! Now create our jagged data from the dense data - call jagged_from_dense_2D(scatt_coeffs, jagged_scatt, gmin, gmax) - call jagged_from_dense_1D(temp_mult, jagged_mult) - - ! Initialize the ScattData Object - call this % xs(t) % scatter % init(gmin, gmax, jagged_mult, & - jagged_scatt) - - ! Now normalize chi - if (mat % fissionable) then - do gin = 1, energy_groups - norm = sum(this % xs(t) % chi_prompt(:, gin)) - if (norm > ZERO) then - this % xs(t) % chi_prompt(:, gin) = & - this % xs(t) % chi_prompt(:, gin) / norm - end if - end do - - do dg = 1, delayed_groups - do gin = 1, energy_groups - norm = sum(this % xs(t) % chi_delayed(dg, :, gin)) - if (norm > ZERO) then - this % xs(t) % chi_delayed(dg, :, gin) = & - this % xs(t) % chi_delayed(dg, :, gin) / norm - end if - end do - end do - end if - - ! Deallocate temporaries - deallocate(jagged_mult, jagged_scatt, gmin, gmax, scatt_coeffs, & - temp_mult, mult_num, mult_denom) - end associate ! nuc - end do NUC_LOOP - end do TEMP_LOOP - - end subroutine mgxsiso_combine - - subroutine mgxsang_combine(this, temps, mat, nuclides, energy_groups, & - delayed_groups, max_order, tolerance, method) - class(MgxsAngle), intent(inout) :: this ! The Mgxs to initialize - type(VectorReal), intent(in) :: temps ! Temperatures to obtain - type(Material), pointer, intent(in) :: mat ! base material - type(MgxsContainer), intent(in) :: nuclides(:) ! List of nuclides to harvest from - integer, intent(in) :: energy_groups ! Number of energy groups - integer, intent(in) :: delayed_groups ! Number of delayed groups - integer, intent(in) :: max_order ! Maximum requested order - real(8), intent(in) :: tolerance ! Tolerance on method - integer, intent(in) :: method ! Type of temperature access - - integer :: i ! loop index over nuclides - integer :: t ! temperature loop index - integer :: gin, gout ! group indices - integer :: dg ! delayed group index - real(8) :: atom_density ! atom density of a nuclide - integer :: ipol, iazi, n_pol, n_azi - real(8) :: norm, nuscatt - integer :: order_dim, nuc_order_dim - real(8), allocatable :: temp_mult(:, :, :, :), mult_num(:, :, :, :) - real(8), allocatable :: mult_denom(:, :, :, :), scatt_coeffs(:, :, :, :, :) - integer :: nuc_t - integer, allocatable :: nuc_ts(:) - real(8) :: temp_actual, temp_desired, interp - integer :: scatter_format - type(Jagged2D), allocatable :: nuc_matrix(:) - integer, allocatable :: gmin(:), gmax(:) - type(Jagged2D), allocatable :: jagged_scatt(:) - type(Jagged1D), allocatable :: jagged_mult(:) - - ! Set the meta-data - call mgxs_combine(this, temps, mat, nuclides, max_order, scatter_format, & - order_dim) - - ! Set the number of delayed groups - this % num_delayed_groups = delayed_groups - - ! Get the number of each polar and azi angles and make sure all the - ! NuclideAngle types have the same number of these angles - n_pol = -1 - n_azi = -1 - - do i = 1, mat % n_nuclides - select type(nuc => nuclides(mat % nuclide(i)) % obj) - type is (MgxsAngle) - - if (n_pol == -1) then - n_pol = nuc % n_pol - n_azi = nuc % n_azi - - allocate(this % polar(n_pol)) - this % polar(:) = nuc % polar(:) - - allocate(this % azimuthal(n_azi)) - this % azimuthal(:) = nuc % azimuthal(:) - else - if ((n_pol /= nuc % n_pol) .or. (n_azi /= nuc % n_azi)) then - call fatal_error("All angular data must be same length!") - end if - end if - end select - end do - - ! Create the Xs Data for each temperature - TEMP_LOOP: do t = 1, temps % size() - - ! Allocate and initialize the data needed for macro_xs(i_mat) object - allocate(this % xs(t) % total(energy_groups, n_azi, n_pol)) - this % xs(t) % total = ZERO - - allocate(this % xs(t) % absorption(energy_groups, n_azi, n_pol)) - this % xs(t) % absorption = ZERO - - allocate(this % xs(t) % fission(energy_groups, n_azi, n_pol)) - this % xs(t) % fission = ZERO - - allocate(this % xs(t) % decay_rate(delayed_groups, n_azi, n_pol)) - this % xs(t) % decay_rate = ZERO - - allocate(this % xs(t) % inverse_velocity(energy_groups, n_azi, n_pol)) - this % xs(t) % inverse_velocity = ZERO - - allocate(this % xs(t) % kappa_fission(energy_groups, n_azi, n_pol)) - this % xs(t) % kappa_fission = ZERO - - allocate(this % xs(t) % prompt_nu_fission(energy_groups, n_azi, n_pol)) - this % xs(t) % prompt_nu_fission = ZERO - - allocate(this % xs(t) % delayed_nu_fission(delayed_groups, & - energy_groups, n_azi, n_pol)) - this % xs(t) % delayed_nu_fission = ZERO - - allocate(this % xs(t) % chi_prompt(energy_groups, energy_groups, & - n_azi, n_pol)) - this % xs(t) % chi_prompt = ZERO - - allocate(this % xs(t) % chi_delayed(delayed_groups, energy_groups, & - energy_groups, n_azi, n_pol)) - this % xs(t) % chi_delayed = ZERO - - allocate(temp_mult(energy_groups, energy_groups, n_azi, n_pol)) - temp_mult = ZERO - - allocate(mult_num(energy_groups, energy_groups, n_azi, n_pol)) - mult_num = ZERO - - allocate(mult_denom(energy_groups, energy_groups, n_azi, n_pol)) - mult_denom = ZERO - - allocate(scatt_coeffs(order_dim, energy_groups, energy_groups, n_azi, n_pol)) - scatt_coeffs = ZERO - - allocate(this % xs(t) % scatter(n_azi, n_pol)) - - do ipol = 1, n_pol - do iazi = 1, n_azi - if (scatter_format == ANGLE_LEGENDRE) then - allocate(ScattDataLegendre :: & - this % xs(t) % scatter(iazi, ipol) % obj) - else if (scatter_format == ANGLE_TABULAR) then - allocate(ScattDataTabular :: & - this % xs(t) % scatter(iazi, ipol) % obj) - else if (scatter_format == ANGLE_HISTOGRAM) then - allocate(ScattDataHistogram :: & - this % xs(t) % scatter(iazi, ipol) % obj) - end if - end do - end do - - ! Add contribution from each nuclide in material - NUC_LOOP: do i = 1, mat % n_nuclides - associate(nuc => nuclides(mat % nuclide(i)) % obj) - - select case (method) - case (TEMPERATURE_NEAREST) - - ! Determine actual temperatures to read - temp_desired = temps % data(i) - allocate(nuc_ts(1)) - - nuc_ts(1) = minloc(abs(nuc % kTs - temp_desired), dim=1) - temp_actual = nuc % kTs(nuc_ts(1)) - - if (abs(temp_actual - temp_desired) >= K_BOLTZMANN * tolerance) then - call fatal_error("MGXS library does not contain cross sections & - &for " // trim(this % name) // " at or near " // & - trim(to_str(nint(temp_desired / K_BOLTZMANN))) // " K.") - end if - - case (TEMPERATURE_INTERPOLATION) - - ! If temperature interpolation or multipole is selected, get a - ! list of bounding temperatures for each actual temperature - ! present in the model - temp_desired = temps % data(i) - allocate(nuc_ts(2)) - - do j = 1, size(nuc % kTs) - 1 - if (nuc % kTs(j) <= temp_desired .and. & - temp_desired < nuc % kTs(j + 1)) then - nuc_ts(1) = j - nuc_ts(2) = j + 1 - end if - end do - - call fatal_error("Nuclear data library does not contain cross sections & - &for " // trim(this % name) // " at temperatures that bound " // & - trim(to_str(nint(temp_desired / K_BOLTZMANN))) // " K.") - end select - - ! Copy atom density of nuclide in material - atom_density = mat % atom_density(i) - - select type(nuc) - type is (MgxsAngle) - do j = 1, size(nuc_ts) - - nuc_t = nuc_ts(j) - - if (size(nuc_ts) == 1) then - interp = ONE - else if (j == 1) then - interp = (ONE - (temp_desired - nuc % kTs(nuc_ts(1))) / & - (nuc % kTs(nuc_ts(2)) - nuc % kTs(nuc_ts(1)))) - else - interp = ONE - interp - end if - - ! Perform our operations which depend upon the type - ! Add contributions to total, absorption, and fission data - ! (if necessary) - this % xs(t) % total = this % xs(t) % total + & - atom_density * nuc % xs(nuc_t) % total * interp - - this % xs(t) % absorption = this % xs(t) % absorption + & - atom_density * nuc % xs(nuc_t) % absorption * interp - - this % xs(t) % decay_rate = this % xs(t) % decay_rate + & - atom_density * nuc % xs(nuc_t) % decay_rate * interp - - this % xs(t) % inverse_velocity = & - this % xs(t) % inverse_velocity + & - atom_density * nuc % xs(nuc_t) % inverse_velocity * interp - - if (nuc % fissionable) then - - this % xs(t) % chi_prompt = this % xs(t) % chi_prompt + & - atom_density * nuc % xs(nuc_t) % chi_prompt * interp - - this % xs(t) % chi_delayed = this % xs(t) % chi_delayed + & - atom_density * nuc % xs(nuc_t) % chi_delayed * interp - - this % xs(t) % prompt_nu_fission = & - this % xs(t) % prompt_nu_fission + atom_density * & - nuc % xs(nuc_t) % prompt_nu_fission * interp - - this % xs(t) % delayed_nu_fission = & - this % xs(t) % delayed_nu_fission + atom_density * & - nuc % xs(nuc_t) % delayed_nu_fission * interp - - this % xs(t) % fission = this % xs(t) % fission + & - atom_density * nuc % xs(nuc_t) % fission * interp - - this % xs(t) % kappa_fission = this % xs(t) % kappa_fission & - + atom_density * nuc % xs(nuc_t) % kappa_fission * interp - - end if - - ! We will next gather the multiplicity and scattering matrices. - ! To avoid multiple re-allocations as we resize the storage - ! matrix (and/or to avoidlots of duplicate code), we will use a - ! dense matrix for this storage, with a reduction to the sparse - ! format at the end. - - ! Get the multiplicity_matrix - ! To combine from nuclidic data we need to use the final relationship - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - ! sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - ! Developed as follows: - ! mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) - ! mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - ! sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - ! nuscatt_{i,g,g'} can be reconstructed from scatter % energy and - ! scatter % scattxs - do ipol = 1, n_pol - do iazi = 1, n_azi - do gin = 1, energy_groups - do gout = nuc % xs(nuc_t) % scatter(iazi, ipol) %obj % gmin(gin), & - nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % gmax(gin) - - nuscatt = nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % scattxs(gin) * & - nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % energy(gin) % data(gout) - - mult_num(gout, gin, iazi, ipol) = & - mult_num(gout, gin, iazi, ipol) + atom_density * & - nuscatt * interp - - if (nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % mult(gin) % data(gout) > ZERO) then - mult_denom(gout, gin, iazi, ipol) = & - mult_denom(gout, gin, iazi, ipol) + atom_density * & - interp * nuscatt / & - nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % mult(gin) % data(gout) - else - ! Avoid division by zero - mult_denom(gout, gin, iazi, ipol) = & - mult_denom(gout, gin, iazi, ipol) + atom_density * & - interp - end if - end do - end do - - ! Get the complete scattering matrix - nuc_order_dim = & - size(nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % & - dist(1) % data, dim=1) - nuc_order_dim = min(nuc_order_dim, order_dim) - - call nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % & - get_matrix(nuc_order_dim, nuc_matrix) - - do gin = 1, energy_groups - do gout = & - nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % gmin(gin), & - nuc % xs(nuc_t) % scatter(iazi, ipol) % obj % gmax(gin) - scatt_coeffs(1:nuc_order_dim, gout, gin, iazi, ipol) = & - scatt_coeffs(1: nuc_order_dim, gout, gin, iazi, ipol) + & - atom_density * interp * & - nuc_matrix(gin) % data(1:nuc_order_dim, gout) - end do ! gout - end do ! gin - end do ! iazi - end do ! ipol - end do - type is (MgxsIso) - call fatal_error("Invalid passing of MgxsIso to MgxsAngle object") - end select - - ! Obtain temp_mult, create jaged arrays and initialize the - ! ScattData object. - do ipol = 1, n_pol - do iazi = 1, n_azi - ! Obtain temp_mult - do gin = 1, energy_groups - do gout = 1, energy_groups - if (mult_denom(gout, gin, iazi, ipol) > ZERO) then - temp_mult(gout, gin, iazi, ipol) = & - mult_num(gout, gin, iazi, ipol) / & - mult_denom(gout, gin, iazi, ipol) - else - temp_mult(gout, gin, iazi, ipol) = ONE - end if - end do - end do - - ! Now create our jagged data from the dense data - call jagged_from_dense_2D(scatt_coeffs(:, :, :, iazi, ipol), & - jagged_scatt, gmin, gmax) - call jagged_from_dense_1D(temp_mult(:, :, iazi, ipol), & - jagged_mult) - - ! Initialize the ScattData Object - call this % xs(t) % scatter(iazi, ipol) % obj % init(gmin, & - gmax, jagged_mult, jagged_scatt) - deallocate(jagged_scatt, jagged_mult, gmin, gmax) - end do - end do - - ! Now normalize chi - if (mat % fissionable) then - do ipol = 1, n_pol - do iazi = 1, n_azi - do gin = 1, energy_groups - norm = sum(this % xs(t) % chi_prompt(:, gin, iazi, ipol)) - if (norm > ZERO) then - this % xs(t) % chi_prompt(:, gin, iazi, ipol) = & - this % xs(t) % chi_prompt(:, gin, iazi, ipol) / norm - end if - end do - end do - end do - - do dg = 1, delayed_groups - do ipol = 1, n_pol - do iazi = 1, n_azi - do gin = 1, energy_groups - norm = sum(this % xs(t) % chi_delayed(dg, :, gin, iazi, ipol)) - if (norm > ZERO) then - this % xs(t) % chi_delayed(dg, :, gin, iazi, ipol) = & - this % xs(t) % chi_delayed(dg, :, gin, iazi, ipol)& - / norm - end if - end do - end do - end do - end do - - end if - - ! Deallocate temporaries - deallocate(scatt_coeffs, temp_mult, mult_num, mult_denom) - end associate ! nuc - end do NUC_LOOP - - end do TEMP_LOOP - - end subroutine mgxsang_combine - -!=============================================================================== -! MGXS*_GET_XS returns the requested data cross section data -!=============================================================================== - - pure function mgxsiso_get_xs(this, xstype, gin, gout, uvw, mu, dg) result(xs) - class(MgxsIso), intent(in) :: this ! The Xs to get data from - character(*) , intent(in) :: xstype ! Type of xs requested - integer, intent(in) :: gin ! Incoming Energy group - integer, optional, intent(in) :: gout ! Outgoing Energy group - real(8), optional, intent(in) :: uvw(3) ! Requested Angle - real(8), optional, intent(in) :: mu ! Change in angle - integer, optional, intent(in) :: dg ! Delayed group - real(8) :: xs ! Requested x/s - integer :: t ! temperature index - - t = this % index_temp - - select case(xstype) - - case('total') - xs = this % xs(t) % total(gin) - - case('absorption') - xs = this % xs(t) % absorption(gin) - - case('fission') - xs = this % xs(t) % fission(gin) - - case('kappa-fission') - xs = this % xs(t) % kappa_fission(gin) - - case('inverse-velocity') - xs = this % xs(t) % inverse_velocity(gin) - - case('decay rate') - if (present(dg)) then - xs = this % xs(t) % decay_rate(dg) - else - xs = this % xs(t) % decay_rate(1) - end if - - case('prompt-nu-fission') - xs = this % xs(t) % prompt_nu_fission(gin) - - case('delayed-nu-fission') - if (present(dg)) then - xs = this % xs(t) % delayed_nu_fission(dg, gin) - else - xs = sum(this % xs(t) % delayed_nu_fission(:, gin)) - end if - - case('nu-fission') - xs = this % xs(t) % prompt_nu_fission(gin) + & - sum(this % xs(t) % delayed_nu_fission(:, gin)) - - case('chi-prompt') - if (present(gout)) then - xs = this % xs(t) % chi_prompt(gout,gin) - else - ! Not sure youd want a 1 or a 0, but here you go! - xs = sum(this % xs(t) % chi_prompt(:, gin)) - end if - - case('chi-delayed') - if (present(gout)) then - if (present(dg)) then - xs = this % xs(t) % chi_delayed(dg, gout, gin) - else - xs = this % xs(t) % chi_delayed(1, gout, gin) - end if - else - if (present(dg)) then - xs = sum(this % xs(t) % chi_delayed(dg, :, gin)) - else - xs = sum(this % xs(t) % chi_delayed(dg, :, gin)) - end if - end if - - case('scatter') - if (present(gout)) then - if (gout < this % xs(t) % scatter % gmin(gin) .or. & - gout > this % xs(t) % scatter % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter % scattxs(gin) * & - this % xs(t) % scatter % energy(gin) % data(gout) - end if - else - xs = this % xs(t) % scatter % scattxs(gin) - end if - - case('scatter/mult') - if (present(gout)) then - if (gout < this % xs(t) % scatter % gmin(gin) .or. & - gout > this % xs(t) % scatter % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter % scattxs(gin) * & - this % xs(t) % scatter % energy(gin) % data(gout) / & - this % xs(t) % scatter % mult(gin) % data(gout) - end if - else - xs = this % xs(t) % scatter % scattxs(gin) / & - (dot_product(this % xs(t) % scatter % mult(gin) % data, & - this % xs(t) % scatter % energy(gin) % data)) - end if - - case('scatter*f_mu/mult','scatter*f_mu') - if (present(gout)) then - if (gout < this % xs(t) % scatter % gmin(gin) .or. & - gout > this % xs(t) % scatter % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter % scattxs(gin) * & - this % xs(t) % scatter % energy(gin) % data(gout) * & - this % xs(t) % scatter % calc_f(gin, gout, mu) - if (xstype == 'scatter*f_mu/mult') then - xs = xs / this % xs(t) % scatter % mult(gin) % data(gout) - end if - end if - else - xs = ZERO - ! TODO (Not likely needed) - ! (asking for f_mu without asking for a group or mu would mean the - ! user of this code wants the complete 1-outgoing group distribution - ! which Im not sure what they would do with that. - end if - - case default - xs = ZERO - end select - - end function mgxsiso_get_xs - - pure function mgxsang_get_xs(this, xstype, gin, gout, uvw, mu, dg) result(xs) - class(MgxsAngle), intent(in) :: this ! The Mgxs to initialize - character(*) , intent(in) :: xstype ! Type of xs requested - integer, intent(in) :: gin ! Incoming Energy group - integer, optional, intent(in) :: gout ! Outgoing Energy group - real(8), optional, intent(in) :: uvw(3) ! Requested Angle - real(8), optional, intent(in) :: mu ! Change in angle - integer, optional, intent(in) :: dg ! Delayed group - real(8) :: xs ! Requested x/s - - integer :: iazi, ipol, t - - t = this % index_temp - - if (present(uvw)) then - - call find_angle(this % polar, this % azimuthal, uvw, iazi, ipol) - - select case(xstype) - - case('total') - xs = this % xs(t) % total(gin, iazi, ipol) - - case('absorption') - xs = this % xs(t) % absorption(gin, iazi, ipol) - - case('fission') - xs = this % xs(t) % fission(gin, iazi, ipol) - - case('kappa-fission') - xs = this % xs(t) % kappa_fission(gin, iazi, ipol) - - case('prompt-nu-fission') - xs = this % xs(t) % prompt_nu_fission(gin, iazi, ipol) - - case('delayed-nu-fission') - if (present(dg)) then - xs = this % xs(t) % delayed_nu_fission(dg, gin, iazi, ipol) - else - xs = sum(this % xs(t) % delayed_nu_fission(:, gin, iazi, ipol)) - end if - - case('nu-fission') - xs = this % xs(t) % prompt_nu_fission(gin, iazi, ipol) + & - sum(this % xs(t) % delayed_nu_fission(:, gin, iazi, ipol)) - - case('chi-prompt') - if (present(gout)) then - xs = this % xs(t) % chi_prompt(gout, gin, iazi, ipol) - else - ! Not sure you would want a 1 or a 0, but here you go! - xs = sum(this % xs(t) % chi_prompt(:, gin, iazi, ipol)) - end if - - case('chi-delayed') - if (present(gout)) then - if (present(dg)) then - xs = this % xs(t) % chi_delayed(dg, gout, gin, iazi, ipol) - else - xs = this % xs(t) % chi_delayed(1, gout, gin, iazi, ipol) - end if - else - if (present(dg)) then - xs = sum(this % xs(t) % chi_delayed(dg, :, gin, iazi, ipol)) - else - xs = sum(this % xs(t) % chi_delayed(1, :, gin, iazi, ipol)) - end if - end if - - case('decay rate') - if (present(dg)) then - xs = this % xs(t) % decay_rate(iazi, ipol, dg) - else - xs = this % xs(t) % decay_rate(iazi, ipol, 1) - end if - - case('inverse-velocity') - xs = this % xs(t) % inverse_velocity(gin, iazi, ipol) - - case('scatter') - if (present(gout)) then - if (gout < this % xs(t) % scatter(iazi, ipol) % obj % gmin(gin) .or. & - gout > this % xs(t) % scatter(iazi, ipol) % obj % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter(iazi, ipol) % obj % scattxs(gin) * & - this % xs(t) % scatter(iazi, ipol) % obj % energy(gin) % data(gout) - end if - else - xs = this % xs(t) % scatter(iazi, ipol) % obj % scattxs(gin) - end if - - case('scatter/mult') - if (present(gout)) then - if (gout < this % xs(t) % scatter(iazi, ipol) % obj % gmin(gin) .or. & - gout > this % xs(t) % scatter(iazi, ipol) % obj % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter(iazi, ipol) % obj % scattxs(gin) * & - this % xs(t) % scatter(iazi, ipol) % obj % energy(gin) % data(gout) / & - this % xs(t) % scatter(iazi, ipol) % obj % mult(gin) % data(gout) - end if - else - xs = this % xs(t) % scatter(iazi, ipol) % obj % scattxs(gin) / & - (dot_product(this % xs(t) % scatter(iazi, ipol) % obj % mult(gin) % data, & - this % xs(t) % scatter(iazi, ipol) % obj % energy(gin) % data)) - end if - - case('scatter*f_mu/mult','scatter*f_mu') - if (present(gout)) then - if (gout < this % xs(t) % scatter(iazi, ipol) % obj % gmin(gin) .or. & - gout > this % xs(t) % scatter(iazi, ipol) % obj % gmax(gin)) then - xs = ZERO - else - xs = this % xs(t) % scatter(iazi, ipol) % obj % scattxs(gin) * & - this % xs(t) % scatter(iazi, ipol) % obj % energy(gin) % data(gout) - xs = xs * this % xs(t) % scatter(iazi, ipol) % obj % calc_f(gin, gout, mu) - if (xstype == 'scatter*f_mu/mult') then - xs = xs / & - this % xs(t) % scatter(iazi, ipol) % obj % mult(gin) % data(gout) - end if - end if - else - xs = ZERO - ! TODO (Not likely needed) - ! (asking for f_mu without asking for a group or mu would mean the - ! user of this code wants the complete 1-outgoing group distribution - ! which Im not sure what they would do with that. - end if - - case default - xs = ZERO - - end select - - else - xs = ZERO - end if - - end function mgxsang_get_xs - -!=============================================================================== -! MGXS*_SAMPLE_FISSION_ENERGY samples the outgoing energy from a fission event -!=============================================================================== - - subroutine mgxsiso_sample_fission_energy(this, gin, uvw, dg, gout) - - class(MgxsIso), intent(in) :: this ! Data to work with - integer, intent(in) :: gin ! Incoming energy group - real(8), intent(in) :: uvw(3) ! Particle Direction - integer, intent(out) :: dg ! Delayed group - integer, intent(out) :: gout ! Sampled outgoing group - real(8) :: xi_pd ! Our random number for prompt/delayed - real(8) :: xi_gout ! Our random number for gout - real(8) :: prob_gout ! Running probability for gout - - ! Get nu and nu_prompt - real(8) :: prob_prompt - - prob_prompt = this % get_xs('prompt-nu-fission', gin) / & - this % get_xs('nu-fission', gin) - - ! Sample random numbers - xi_pd = prn() - xi_gout = prn() - - ! Neutron is born prompt - if (xi_pd <= prob_prompt) then - - ! set the delayed group for the particle born from fission to 0 - dg = 0 - - gout = 1 - prob_gout = this % get_xs('chi-prompt', gin, gout) - - do while (prob_gout < xi_gout) - gout = gout + 1 - prob_gout = prob_gout + this % get_xs('chi-prompt', gin, gout) - end do - - ! Neutron is born delayed - else - - ! Get the delayed group - dg = 0 - - do while (xi_pd >= prob_prompt) - dg = dg + 1 - prob_prompt = prob_prompt + & - this % get_xs('delayed-nu-fission', gin, dg=dg) & - / this % get_xs('nu-fission', gin) - end do - - ! Adjust dg in case of round off error - dg = min(dg, this % num_delayed_groups) - - ! Get the outgoing group - gout = 1 - prob_gout = this % get_xs('chi-delayed', gin, gout, dg=dg) - - do while (prob_gout < xi_gout) - gout = gout + 1 - prob_gout = prob_gout + this % get_xs('chi-delayed', gin, gout, dg=dg) - end do - end if - - end subroutine mgxsiso_sample_fission_energy - - subroutine mgxsang_sample_fission_energy(this, gin, uvw, dg, gout) - class(MgxsAngle), intent(in) :: this ! Data to work with - integer, intent(in) :: gin ! Incoming energy group - real(8), intent(in) :: uvw(3) ! Direction vector - integer, intent(out) :: dg ! Delayed group - integer, intent(out) :: gout ! Sampled outgoing group - real(8) :: xi_pd ! Our random number for prompt/delayed - real(8) :: xi_gout ! Our random number for gout - real(8) :: prob_gout ! Running probability for gout - real(8) :: prob_prompt - - ! Get nu and nu_prompt - prob_prompt = this % get_xs('prompt-nu-fission', gin, uvw=uvw) / & - this % get_xs('nu-fission', gin, uvw=uvw) - - ! Sample random numbers - xi_pd = prn() - xi_gout = prn() - - ! Neutron is born prompt - if (xi_pd <= prob_prompt) then - - ! set the delayed group for the particle born from fission to 0 - dg = 0 - - gout = 1 - prob_gout = this % get_xs('chi-prompt', gin, gout, uvw=uvw) - - do while (prob_gout < xi_gout) - gout = gout + 1 - prob_gout = prob_gout + & - this % get_xs('chi-prompt', gin, gout, uvw=uvw) - end do - - ! Neutron is born delayed - else - - ! Get the delayed group - dg = 0 - - do while (xi_pd >= prob_prompt) - dg = dg + 1 - prob_prompt = prob_prompt + & - this % get_xs('delayed-nu-fission', gin, uvw=uvw, dg=dg) / & - this % get_xs('nu-fission', gin, uvw=uvw) - end do - - ! Adjust dg in case of round off error - dg = min(dg, this % num_delayed_groups) - - ! Get the outgoing group - gout = 1 - prob_gout = this % get_xs('chi-delayed', gin, gout, uvw=uvw, dg=dg) - - do while (prob_gout < xi_gout) - gout = gout + 1 - prob_gout = prob_gout + & - this % get_xs('chi-delayed', gin, gout, uvw=uvw, dg=dg) - end do - end if - - end subroutine mgxsang_sample_fission_energy - -!=============================================================================== -! MGXS*_SAMPLE_SCATTER Selects outgoing energy and angle after a scatter event -!=============================================================================== - - subroutine mgxsiso_sample_scatter(this, uvw, gin, gout, mu, wgt) - class(MgxsIso), intent(in) :: this - real(8), intent(in) :: uvw(3) ! Incoming neutron direction - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - - call this % xs(this % index_temp) % scatter % sample(gin, gout, mu, wgt) - - end subroutine mgxsiso_sample_scatter - - subroutine mgxsang_sample_scatter(this, uvw, gin, gout, mu, wgt) - class(MgxsAngle), intent(in) :: this - real(8), intent(in) :: uvw(3) ! Incoming neutron direction - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - - integer :: iazi, ipol ! Angular indices - - call find_angle(this % polar, this % azimuthal, uvw, iazi, ipol) - call this % xs(this % index_temp) % scatter(iazi, ipol) % obj % sample( & - gin, gout, mu, wgt) - - end subroutine mgxsang_sample_scatter - -!=============================================================================== -! MGXS*_CALCULATE_XS determines the multi-group cross sections -! for the material the particle is currently traveling through. -!=============================================================================== - - 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 = & - this % xs(this % index_temp) % prompt_nu_fission(gin) + & - sum(this % xs(this % index_temp) % delayed_nu_fission(:, gin)) - - end subroutine mgxsiso_calculate_xs - - 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) % & - absorption(gin, iazi, ipol) - xs % nu_fission = this % xs(this % index_temp) % & - prompt_nu_fission(gin, iazi, ipol) + & - sum(this % xs(this % index_temp) % & - delayed_nu_fission(:, gin, iazi, ipol)) - - end subroutine mgxsang_calculate_xs - -!=============================================================================== -! MGXS_FIND_TEMPERATURE sets the temperature index for the given -! sqrt(temperature), (with temperature in units of eV) -!=============================================================================== - - subroutine mgxs_find_temperature(this, sqrtkT) - class(Mgxs), intent(inout) :: this - real(8), intent(in) :: sqrtkT ! Temperature (in units of eV) - - this % index_temp = minloc(abs(this % kTs - (sqrtkT * sqrtkT)), dim=1) - - end subroutine mgxs_find_temperature - -!=============================================================================== -! FIND_ANGLE finds the closest angle on the data grid and returns that index -!=============================================================================== - - pure subroutine find_angle(polar, azimuthal, uvw, i_azi, i_pol) - real(8), intent(in) :: polar(:) ! Polar angles [0,pi] - real(8), intent(in) :: azimuthal(:) ! Azi. angles [-pi,pi] - real(8), intent(in) :: uvw(3) ! Direction of motion - integer, intent(inout) :: i_pol ! Closest polar bin - integer, intent(inout) :: i_azi ! Closest azi bin - - real(8) :: my_pol, my_azi, dangle - - ! Convert uvw to polar and azi - - my_pol = acos(uvw(3)) - my_azi = atan2(uvw(2), uvw(1)) - - ! Search for equi-binned angles - dangle = PI / real(size(polar),8) - i_pol = floor(my_pol / dangle + ONE) - dangle = TWO * PI / real(size(azimuthal),8) - i_azi = floor((my_azi + PI) / dangle + ONE) - - end subroutine find_angle - -!=============================================================================== -! FREE_MEMORY_MGXS deallocates global arrays defined in this module -!=============================================================================== - - subroutine free_memory_mgxs() - if (allocated(nuclides_MG)) deallocate(nuclides_MG) - if (allocated(macro_xs)) deallocate(macro_xs) - if (allocated(energy_bins)) deallocate(energy_bins) - if (allocated(energy_bin_avg)) deallocate(energy_bin_avg) - end subroutine free_memory_mgxs - -end module mgxs_header diff --git a/src/scattdata_header.F90 b/src/scattdata_header.F90 deleted file mode 100644 index 511e2a237..000000000 --- a/src/scattdata_header.F90 +++ /dev/null @@ -1,852 +0,0 @@ -module scattdata_header - - use algorithm, only: binary_search - use constants - use error, only: fatal_error - use math - use random_lcg, only: prn - - implicit none - - -!=============================================================================== -! JAGGED1D and JAGGED2D is a type which allows for jagged 1-D or 2-D array. -!=============================================================================== - - type :: Jagged2D - real(8), allocatable :: data(:, :) - end type Jagged2D - - type :: Jagged1D - real(8), allocatable :: data(:) - end type Jagged1D - -!=============================================================================== -! SCATTDATA contains all the data to describe the scattering energy and -! angular distribution -!=============================================================================== - - type, abstract :: ScattData - ! The data attribute of the energy, mult, and dist arrays - ! are not necessarily 1-indexed as they instead will be allocated - ! from a minimum outgoing group to an outgoing minimum group. - ! Normalized p0 matrix on its own for sampling energy - type(Jagged1D), allocatable :: energy(:) ! (Gin % data(Gout)) - ! Nu-scatter multiplication (i.e. nu-scatt/scatt) - type(Jagged1D), allocatable :: mult(:) ! (Gin % data(Gout)) - ! Angular distribution - type(Jagged2D), allocatable :: dist(:) ! (Gin % data(Order/Nmu, Gout) - integer, allocatable :: gmin(:) ! Minimum outgoing group - integer, allocatable :: gmax(:) ! Maximum outgoing group - real(8), allocatable :: scattxs(:) ! Isotropic Sigma_{s,g_{in}} - - contains - procedure(scattdata_init_), deferred :: init ! Initializes ScattData - procedure(scattdata_calc_f_), deferred :: calc_f ! Calculates f, given mu - procedure(scattdata_sample_), deferred :: sample ! sample the scatter event - procedure :: get_matrix => scattdata_get_matrix ! Rebuild scattering matrix - end type ScattData - - abstract interface - subroutine scattdata_init_(this, gmin, gmax, mult, coeffs) - import ScattData, Jagged1D, Jagged2D - class(ScattData), intent(inout) :: this ! Object to work with - integer, intent(in) :: gmin(:) ! Min Gout - integer, intent(in) :: gmax(:) ! Max Gout - type(Jagged1D), intent(in) :: mult(:) ! Scatter Prod'n Matrix - type(Jagged2D), intent(in) :: coeffs(:) ! Coefficients to use - end subroutine scattdata_init_ - - pure function scattdata_calc_f_(this, gin, gout, mu) result(f) - import ScattData - class(ScattData), intent(in) :: this ! Scattering Object to work with - integer, intent(in) :: gin ! Incoming Energy Group - integer, intent(in) :: gout ! Outgoing Energy Group - real(8), intent(in) :: mu ! Angle of interest - real(8) :: f ! Return value of f(mu) - - end function scattdata_calc_f_ - - subroutine scattdata_sample_(this, gin, gout, mu, wgt) - import ScattData - class(ScattData), intent(in) :: this ! Scattering Object to work with - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - end subroutine scattdata_sample_ - end interface - - type, extends(ScattData) :: ScattDataLegendre - ! Maximal value for rejection sampling from rectangle - type(Jagged1D), allocatable :: max_val(:) ! (Gin % data(Gout)) - contains - procedure :: init => scattdatalegendre_init - procedure :: calc_f => scattdatalegendre_calc_f - procedure :: sample => scattdatalegendre_sample - end type ScattDataLegendre - - type, extends(ScattData) :: ScattDataHistogram - real(8), allocatable :: mu(:) ! Mu bins - real(8) :: dmu ! Mu spacing - ! Histogram of f(mu) (dist has CDF) - type(Jagged2D), allocatable :: fmu(:) ! (Gin % data(Order/Nmu x Gout) - contains - procedure :: init => scattdatahistogram_init - procedure :: calc_f => scattdatahistogram_calc_f - procedure :: sample => scattdatahistogram_sample - procedure :: get_matrix => scattdatahistogram_get_matrix - end type ScattDataHistogram - - type, extends(ScattData) :: ScattDataTabular - real(8), allocatable :: mu(:) ! Mu bins - real(8) :: dmu ! Mu spacing - ! PDF of f(mu) (dist has CDF) - type(Jagged2D), allocatable :: fmu(:) ! (Gin % data(Order/Nmu x Gout) - contains - procedure :: init => scattdatatabular_init - procedure :: calc_f => scattdatatabular_calc_f - procedure :: sample => scattdatatabular_sample - procedure :: get_matrix => scattdatatabular_get_matrix - end type ScattDataTabular - -!=============================================================================== -! SCATTDATACONTAINER allocatable array for storing ScattData Objects (for angle) -!=============================================================================== - - type ScattDataContainer - class(ScattData), allocatable :: obj - end type ScattDataContainer - -contains - -!=============================================================================== -! SCATTDATA*_INIT builds the scattdata object -!=============================================================================== - - subroutine scattdata_init(this, order, gmin, gmax, energy, mult) - class(ScattData), intent(inout) :: this ! Object to work on - integer, intent(in) :: order ! Data Order - integer, intent(in) :: gmin(:) ! Min Gout - integer, intent(in) :: gmax(:) ! Max Gout - type(Jagged1D), intent(inout) :: energy(:) ! Energy Transfer Matrix - type(Jagged1D), intent(in) :: mult(:) ! Scatter Prod'n Matrix - - integer :: groups, gin - real(8) :: norm - - groups = size(energy, dim=1) - - allocate(this % gmin(groups)) - allocate(this % gmax(groups)) - allocate(this % energy(groups)) - allocate(this % mult(groups)) - allocate(this % dist(groups)) - - this % gmin = gmin - this % gmax = gmax - - ! Set the outgoing energy PDF values - do gin = 1, groups - ! Make sure energy is normalized (i.e., CDF is 1) - norm = sum(energy(gin) % data(:)) - if (norm /= ZERO) energy(gin) % data(:) = energy(gin) % data(:) / norm - ! Set the values - allocate(this % energy(gin) % data(gmin(gin):gmax(gin))) - this % energy(gin) % data(:) = energy(gin) % data(:) - allocate(this % mult(gin) % data(gmin(gin):gmax(gin))) - this % mult(gin) % data(gmin(gin):gmax(gin)) = & - mult(gin) % data(gmin(gin):gmax(gin)) - allocate(this % dist(gin) % data(order, gmin(gin):gmax(gin))) - this % dist(gin) % data = ZERO - end do - end subroutine scattdata_init - - subroutine scattdatalegendre_init(this, gmin, gmax, mult, coeffs) - class(ScattDataLegendre), intent(inout) :: this ! Object to work on - integer, intent(in) :: gmin(:) ! Min Gout - integer, intent(in) :: gmax(:) ! Max Gout - type(Jagged1D), intent(in) :: mult(:) ! Scatter Prod'n Matrix - type(Jagged2D), intent(in) :: coeffs(:) ! Coefficients to use - - real(8) :: dmu, mu, f, norm - integer :: imu, Nmu, gout, gin, groups, order - type(Jagged1D), allocatable :: energy(:) - type(Jagged2D), allocatable :: matrix(:) - - groups = size(coeffs) - order = size(coeffs(1) % data, dim=1) - - ! make a copy of coeffs that we can use to extract data and normalize - allocate(matrix(groups)) - do gin = 1, groups - allocate(matrix(gin) % data(order, gmin(gin):gmax(gin))) - matrix(gin) % data = coeffs(gin) % data - end do - - ! Get scattxs value - allocate(this % scattxs(groups)) - ! Get this by summing the un-normalized P0 coefficient in matrix - ! over all outgoing groups - do gin = 1, groups - this % scattxs(gin) = sum(matrix(gin) % data(1, :), dim=1) - end do - - allocate(energy(groups)) - ! Build energy transfer probability matrix from data in matrix - ! while also normalizing matrix itself (making CDF of f(mu=1)=1) - do gin = 1, groups - allocate(energy(gin) % data(gmin(gin):gmax(gin))) - energy(gin) % data = ZERO - do gout = gmin(gin), gmax(gin) - norm = matrix(gin) % data(1, gout) - energy(gin) % data(gout) = norm - if (norm /= ZERO) then - matrix(gin) % data(:, gout) = matrix(gin) % data(:, gout) / norm - end if - end do - end do - - call scattdata_init(this, order, gmin, gmax, energy, mult) - - allocate(this % max_val(groups)) - ! Set dist values from matrix and initialize max_val - do gin = 1, groups - do gout = gmin(gin), gmax(gin) - this % dist(gin) % data(:, gout) = matrix(gin) % data(:, gout) - end do - allocate(this % max_val(gin) % data(gmin(gin):gmax(gin))) - this % max_val(gin) % data(:) = ZERO - end do - - ! Step through the polynomial with fixed number of points to identify - ! the maximal value. - Nmu = 1001 - dmu = TWO / real(Nmu - 1, 8) - do gin = 1, groups - do gout = gmin(gin), gmax(gin) - do imu = 1, Nmu - ! Update mu. Do first and last seperate to avoid float errors - if (imu == 1) then - mu = -ONE - else if (imu == Nmu) then - mu = ONE - else - mu = -ONE + real(imu - 1, 8) * dmu - end if - ! Calculate probability - f = this % calc_f(gin,gout,mu) - ! If this is a new max, store it. - if (f > this % max_val(gin) % data(gout)) & - this % max_val(gin) % data(gout) = f - end do - ! Finally, since we may not have caught the exact max, add 10% margin - this % max_val(gin) % data(gout) = & - this % max_val(gin) % data(gout) * 1.1_8 - end do - end do - end subroutine scattdatalegendre_init - - subroutine scattdatahistogram_init(this, gmin, gmax, mult, coeffs) - class(ScattDataHistogram), intent(inout) :: this ! Object to work on - integer, intent(in) :: gmin(:) ! Min Gout - integer, intent(in) :: gmax(:) ! Max Gout - type(Jagged1D), intent(in) :: mult(:) ! Scatter Prod'n Matrix - type(Jagged2D), intent(in) :: coeffs(:) ! Coefficients to use - - integer :: imu, gin, gout, groups, order - real(8) :: norm - type(Jagged1D), allocatable :: energy(:) - type(Jagged2D), allocatable :: matrix(:) - - groups = size(coeffs) - order = size(coeffs(1) % data, dim=1) - - ! make a copy of coeffs that we can use to extract data and normalize - allocate(matrix(groups)) - do gin = 1, groups - allocate(matrix(gin) % data(order, gmin(gin):gmax(gin))) - matrix(gin) % data(:, :) = coeffs(gin) % data(:, :) - end do - - ! Get scattxs value - allocate(this % scattxs(groups)) - ! Get this by summing the un-normalized angular distribution in matrix - ! over all outgoing groups - do gin = 1, groups - this % scattxs(gin) = sum(matrix(gin) % data(:, :)) - end do - - allocate(energy(groups)) - ! Build energy transfer probability matrix from data in matrix - ! while also normalizing matrix itself (making CDF of f(mu=1)=1) - do gin = 1, groups - allocate(energy(gin) % data(gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - norm = sum(matrix(gin) % data(:, gout)) - energy(gin) % data(gout) = norm - if (norm /= ZERO) then - matrix(gin) % data(:, gout) = matrix(gin) % data(:, gout) / norm - end if - end do - end do - - call scattdata_init(this, order, gmin, gmax, energy, mult) - - allocate(this % mu(order)) - this % dmu = TWO / real(order, 8) - this % mu(1) = -ONE - do imu = 2, order - this % mu(imu) = -ONE + real(imu - 1, 8) * this % dmu - end do - - ! Integrate this histogram so we can avoid rejection sampling while - ! also saving the original histogram in fmu - allocate(this % fmu(groups)) - do gin = 1, groups - allocate(this % fmu(gin) % data(order, gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - ! Store the histogram - this % fmu(gin) % data(:, gout) = matrix(gin) % data(:, gout) - ! Integrate the histogram - this % dist(gin) % data(1, gout) = & - this % dmu * matrix(gin) % data(1, gout) - do imu = 2, order - this % dist(gin) % data(imu, gout) = & - this % dmu * matrix(gin) % data(imu, gout) + & - this % dist(gin) % data(imu - 1, gout) - end do - - ! Normalize the integral to unity - norm = this % dist(gin) % data(order, gout) - if (norm > ZERO) then - this % fmu(gin) % data(:, gout) = & - this % fmu(gin) % data(:, gout) / norm - this % dist(gin) % data(:, gout) = & - this % dist(gin) % data(:, gout) / norm - end if - end do - end do - - end subroutine scattdatahistogram_init - - subroutine scattdatatabular_init(this, gmin, gmax, mult, coeffs) - class(ScattDataTabular), intent(inout) :: this ! Object to work on - integer, intent(in) :: gmin(:) ! Min Gout - integer, intent(in) :: gmax(:) ! Max Gout - type(Jagged1D), intent(in) :: mult(:) ! Scatter Prod'n Matrix - type(Jagged2D), intent(in) :: coeffs(:) ! Coefficients to use - - integer :: imu, gin, gout, groups, order - real(8) :: norm - type(Jagged1D), allocatable :: energy(:) - type(Jagged2D), allocatable :: matrix(:) - - groups = size(coeffs) - order = size(coeffs(1) % data, dim=1) - - ! make a copy of coeffs that we can use to extract data and normalize - allocate(matrix(groups)) - do gin = 1, groups - allocate(matrix(gin) % data(order, gmin(gin):gmax(gin))) - matrix(gin) % data = coeffs(gin) % data - end do - - ! Build the angular distribution mu values - allocate(this % mu(order)) - this % dmu = TWO / real(order - 1, 8) - this % mu(1) = -ONE - do imu = 2, order - 1 - this % mu(imu) = -ONE + real(imu - 1, 8) * this % dmu - end do - this % mu(order) = ONE - - ! Get scattxs - allocate(this % scattxs(groups)) - ! Get this by integrating the scattering distribution over all mu points - ! and then combining over all outgoing groups - ! over all outgoing groups - do gin = 1, groups - norm = ZERO - do gout = gmin(gin), gmax(gin) - do imu = 2, order - norm = norm + HALF * this % dmu * & - (matrix(gin) % data(imu - 1, gout) + & - matrix(gin) % data(imu, gout)) - end do - end do - this % scattxs(gin) = norm - end do - - allocate(energy(groups)) - ! Build energy transfer probability matrix from data in matrix - do gin = 1, groups - allocate(energy(gin) % data(gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - norm = ZERO - do imu = 2, order - norm = norm + HALF * this % dmu * & - (matrix(gin) % data(imu - 1, gout) + & - matrix(gin) % data(imu, gout)) - end do - energy(gin) % data(gout) = norm - end do - end do - call scattdata_init(this, order, gmin, gmax, energy, mult) - - ! Calculate f(mu) and integrate it so we can avoid rejection sampling - allocate(this % fmu(groups)) - do gin = 1, groups - allocate(this % fmu(gin) % data(order, gmin(gin):gmax(gin))) - do gout = gmin(gin), gmax(gin) - ! Coeffs contain f(mu), put in f(mu) as that is where the - ! PDF lives - this % fmu(gin) % data(:, gout) = matrix(gin) % data(:, gout) - - ! Force positivity - do imu = 1, order - if (this % fmu(gin) % data(imu, gout) < ZERO) then - this % fmu(gin) % data(imu, gout) = ZERO - end if - end do - - ! Re-normalize fmu for numerical integration issues and in case - ! the negative fix-up introduced un-normalized data while - ! accruing the CDF - norm = ZERO - do imu = 2, order - norm = norm + HALF * this % dmu * & - (this % fmu(gin) % data(imu - 1, gout) + & - this % fmu(gin) % data(imu, gout)) - this % dist(gin) % data(imu, gout) = norm - end do - if (norm > ZERO) then - this % fmu(gin) % data(:, gout) = & - this % fmu(gin) % data(:, gout) / norm - this % dist(gin) % data(:, gout) = & - this % dist(gin) % data(:, gout) / norm - end if - end do - end do - end subroutine scattdatatabular_init - -!=============================================================================== -! SCATTDATA_*_CALC_F Calculates the value of f given mu (and gin,gout pair) -!=============================================================================== - - pure function scattdatalegendre_calc_f(this, gin, gout, mu) result(f) - class(ScattDataLegendre), intent(in) :: this ! The ScattData to evaluate - integer, intent(in) :: gin ! Incoming Energy Group - integer, intent(in) :: gout ! Outgoing Energy Group - real(8), intent(in) :: mu ! Angle of interest - real(8) :: f ! Return value of f(mu) - - ! Plug mu in to the legendre expansion and go from there - if (gout < this % gmin(gin) .or. gout > this % gmax(gin)) then - f = ZERO - else - f = evaluate_legendre(this % dist(gin) % data(:, gout), mu) - end if - - end function scattdatalegendre_calc_f - - pure function scattdatahistogram_calc_f(this, gin, gout, mu) result(f) - class(ScattDataHistogram), intent(in) :: this ! The ScattData to evaluate - integer, intent(in) :: gin ! Incoming Energy Group - integer, intent(in) :: gout ! Outgoing Energy Group - real(8), intent(in) :: mu ! Angle of interest - real(8) :: f ! Return value of f(mu) - - integer :: imu - - if (gout < this % gmin(gin) .or. gout > this % gmax(gin)) then - f = ZERO - else - ! Find mu bin - if (mu == ONE) then - imu = size(this % fmu(gin) % data, dim=1) - else - imu = floor((mu + ONE) / this % dmu + ONE) - end if - - f = this % fmu(gin) % data(imu, gout) - end if - - end function scattdatahistogram_calc_f - - pure function scattdatatabular_calc_f(this, gin, gout, mu) result(f) - class(ScattDataTabular), intent(in) :: this ! The ScattData to evaluate - integer, intent(in) :: gin ! Incoming Energy Group - integer, intent(in) :: gout ! Outgoing Energy Group - real(8), intent(in) :: mu ! Angle of interest - real(8) :: f ! Return value of f(mu) - - integer :: imu - real(8) :: r - - if (gout < this % gmin(gin) .or. gout > this % gmax(gin)) then - f = ZERO - else - ! Find mu bin - if (mu == ONE) then - imu = size(this % fmu(gin) % data, dim=1) - 1 - else - imu = floor((mu + ONE) / this % dmu + ONE) - end if - - ! Now interpolate to find f(mu) - r = (mu - this % mu(imu)) / (this % mu(imu + 1) - this % mu(imu)) - f = (ONE - r) * this % fmu(gin) % data(imu, gout) + & - r * this % fmu(gin) % data(imu + 1, gout) - end if - - end function scattdatatabular_calc_f - -!=============================================================================== -! SCATTDATA*_SCATTER Samples the outgoing energy and change in angle. -!=============================================================================== - - subroutine scattdatalegendre_sample(this, gin, gout, mu, wgt) - class(ScattDataLegendre), intent(in) :: this ! Scattering object to use - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - - real(8) :: xi ! Our random number - real(8) :: prob ! Running probability - real(8) :: u, f, M - integer :: samples - - xi = prn() - gout = this % gmin(gin) - prob = this % energy(gin) % data(gout) - - do while ((prob < xi) .and. (gout < this % gmax(gin))) - gout = gout + 1 - prob = prob + this % energy(gin) % data(gout) - end do - - ! Now we can sample mu using the legendre representation of the scattering - ! kernel in data(1:this % order) - - ! Do with rejection sampling from a rectangular bounding box - ! Set maximal value - M = this % max_val(gin) % data(gout) - samples = 0 - do - mu = TWO * prn() - ONE - f = this % calc_f(gin, gout, mu) - if (f > ZERO) then - u = prn() * M - if (u <= f) then - exit - end if - end if - samples = samples + 1 - if (samples > MAX_SAMPLE) then - call fatal_error("Maximum number of Legendre expansion samples reached!") - end if - end do - - wgt = wgt * this % mult(gin) % data(gout) - - end subroutine scattdatalegendre_sample - - subroutine scattdatahistogram_sample(this, gin, gout, mu, wgt) - class(ScattDataHistogram), intent(in) :: this ! Scattering object to use - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - - real(8) :: xi ! Our random number - real(8) :: prob ! Running probability - integer :: imu - - xi = prn() - gout = this % gmin(gin) - prob = this % energy(gin) % data(gout) - - do while ((prob < xi) .and. (gout < this % gmax(gin))) - gout = gout + 1 - prob = prob + this % energy(gin) % data(gout) - end do - - xi = prn() - if (xi < this % dist(gin) % data(1, gout)) then - imu = 1 - else - imu = binary_search(this % dist(gin) % data(:, gout), & - size(this % dist(gin) % data(:, gout)), xi) + 1 - end if - - ! Randomly select a mu in this bin. - mu = prn() * this % dmu + this % mu(imu) - - wgt = wgt * this % mult(gin) % data(gout) - - end subroutine scattdatahistogram_sample - - subroutine scattdatatabular_sample(this, gin, gout, mu, wgt) - class(ScattDataTabular), intent(in) :: this ! Scattering object to use - integer, intent(in) :: gin ! Incoming neutron group - integer, intent(out) :: gout ! Sampled outgoin group - real(8), intent(out) :: mu ! Sampled change in angle - real(8), intent(inout) :: wgt ! Particle weight - - real(8) :: xi ! Our random number - real(8) :: prob ! Running probability - real(8) :: mu0, frac, mu1 - real(8) :: c_k, c_k1, p0, p1 - integer :: k, NP - - xi = prn() - gout = this % gmin(gin) - prob = this % energy(gin) % data(gout) - - do while ((prob < xi) .and. (gout < this % gmax(gin))) - gout = gout + 1 - prob = prob + this % energy(gin) % data(gout) - end do - - ! determine outgoing cosine bin - NP = size(this % dist(gin) % data(:, gout)) - xi = prn() - - c_k = this % dist(gin) % data(1, gout) - do k = 1, NP - 1 - c_k1 = this % dist(gin) % data(k + 1, gout) - if (xi < c_k1) exit - c_k = c_k1 - end do - - ! check to make sure k is <= NP - 1 - k = min(k, NP - 1) - - p0 = this % fmu(gin) % data(k, gout) - mu0 = this % mu(k) - ! Linear-linear interpolation to find mu value w/in bin. - p1 = this % fmu(gin) % data(k + 1, gout) - mu1 = this % mu(k + 1) - - if (p0 == p1) then - mu = mu0 + (xi - c_k) / p0 - else - frac = (p1 - p0) / (mu1 - mu0) - mu = mu0 + & - (sqrt(max(ZERO, p0 * p0 + TWO * frac * (xi - c_k))) - p0) / frac - end if - - if (mu <= -ONE) then - mu = -ONE - else if (mu >= ONE) then - mu = ONE - end if - - wgt = wgt * this % mult(gin) % data(gout) - - end subroutine scattdatatabular_sample - -!=============================================================================== -! SCATTDATA*_GET_MATRIX Reproduces the original scattering matrix (densely) -! using ScattData's information of fmu/dist, energy, and scattxs -!=============================================================================== - - subroutine scattdata_get_matrix(this, req_order, matrix) - class(ScattData), intent(in) :: this ! Scattering Object to work with - integer, intent(in) :: req_order ! Requested order of matrix - type(Jagged2D), allocatable, intent(inout) :: matrix(:) ! Resultant matrix just built - - integer :: order, groups, gin, gout - - groups = size(this % energy) - ! Set gin and gout for getting the order - order = min(req_order, size(this % dist(1) % data, dim=1)) - - if (allocated(matrix)) deallocate(matrix) - allocate(matrix(groups)) - ! Initialize to 0; this way the zero entries in the dense matrix dont - ! need to be explicitly set, requiring a significant increase in the - ! lines of code. - do gin = 1, groups - allocate(matrix(gin) % data(order, groups)) - do gout = this % gmin(gin), this % gmax(gin) - matrix(gin) % data(:, gout) = this % scattxs(gin) * & - this % energy(gin) % data(gout) * & - this % dist(gin) % data(1:order, gout) - end do - end do - end subroutine scattdata_get_matrix - - subroutine scattdatahistogram_get_matrix(this, req_order, matrix) - class(ScattDataHistogram), intent(in) :: this ! Scattering Object to work with - integer, intent(in) :: req_order ! Requested order of matrix - type(Jagged2D), allocatable, intent(inout) :: matrix(:) ! Resultant matrix just built - - integer :: order, groups, gin, gout - - groups = size(this % energy) - order = min(req_order, size(this % dist(1) % data, dim=1)) - - if (allocated(matrix)) deallocate(matrix) - allocate(matrix(groups)) - ! Initialize to 0; this way the zero entries in the dense matrix dont - ! need to be explicitly set, requiring a significant increase in the - ! lines of code. - do gin = 1, groups - allocate(matrix(gin) % data(order, groups)) - do gout = this % gmin(gin), this % gmax(gin) - matrix(gin) % data(:, gout) = this % scattxs(gin) * & - this % energy(gin) % data(gout) * & - this % fmu(gin) % data(1:order, gout) - end do - end do - end subroutine scattdatahistogram_get_matrix - - subroutine scattdatatabular_get_matrix(this, req_order, matrix) - class(ScattDataTabular), intent(in) :: this ! Scattering Object to work with - integer, intent(in) :: req_order ! Requested order of matrix - type(Jagged2D), allocatable, intent(inout) :: matrix(:) ! Resultant matrix just built - - integer :: order, groups, gin, gout - - groups = size(this % energy) - order = min(req_order, size(this % dist(1) % data, dim=1)) - - if (allocated(matrix)) deallocate(matrix) - allocate(matrix(groups)) - ! Initialize to 0; this way the zero entries in the dense matrix dont - ! need to be explicitly set, requiring a significant increase in the - ! lines of code. - do gin = 1, groups - allocate(matrix(gin) % data(order, groups)) - do gout = this % gmin(gin), this % gmax(gin) - matrix(gin) % data(:, gout) = this % scattxs(gin) * & - this % energy(gin) % data(gout) * & - this % fmu(gin) % data(1:order, gout) - end do - end do - end subroutine scattdatatabular_get_matrix - -!=============================================================================== -! JAGGED_FROM_DENSE_*D Creates a jagged array from a sparse dense matrix. -! The user can supply a key which indicates the values to remove, but the -! default is ZERO -!=============================================================================== - - subroutine jagged_from_dense_1D(dense, jagged, lo_bounds_, hi_bounds_, key_) - real(8), intent(in) :: dense(:, :) - type(Jagged1D), allocatable, intent(inout) :: jagged(:) - real(8), intent(in), optional :: key_ - integer, intent(inout), allocatable, optional :: lo_bounds_(:) - integer, intent(inout), allocatable, optional :: hi_bounds_(:) - - real(8) :: key - integer :: i, jmin, jmax - integer, allocatable :: lo_bounds(:), hi_bounds(:) - - if (present(key_)) then - key = key_ - else - key = ZERO - end if - - allocate(lo_bounds(size(dense, dim=2))) - allocate(hi_bounds(size(dense, dim=2))) - - if (allocated(jagged)) deallocate(jagged) - allocate(jagged(size(dense, dim=2))) - do i = 1, size(dense, dim=2) - ! Find the min and max j values - do jmin = 1, size(dense, dim=1) - if (dense(jmin, i) /= key) exit - end do - do jmax = size(dense, dim=1), 1, -1 - if (dense(jmax, i) /= key) exit - end do - ! Treat the case of all values matching the key - if (jmin > jmax) then - jmin = i - jmax = i - end if - - ! Now store the jagged row - allocate(jagged(i) % data(jmin:jmax)) - jagged(i) % data(jmin:jmax) = dense(jmin:jmax, i) - - lo_bounds(i) = jmin - hi_bounds(i) = jmax - end do - - if (present(lo_bounds_)) then - if (allocated(lo_bounds_)) deallocate(lo_bounds_) - allocate(lo_bounds_(size(dense, dim=2))) - lo_bounds_ = lo_bounds - end if - if (present(hi_bounds_)) then - if (allocated(hi_bounds_)) deallocate(hi_bounds_) - allocate(hi_bounds_(size(dense, dim=2))) - hi_bounds_ = hi_bounds - end if - - end subroutine jagged_from_dense_1D - - subroutine jagged_from_dense_2D(dense, jagged, lo_bounds_, hi_bounds_, key_) - real(8), intent(in) :: dense(:, :, :) - type(Jagged2D), allocatable, intent(inout) :: jagged(:) - real(8), intent(in), optional :: key_ - integer, intent(inout), allocatable, optional :: lo_bounds_(:) - integer, intent(inout), allocatable, optional :: hi_bounds_(:) - - real(8) :: key - integer :: i, jmin, jmax - integer, allocatable :: lo_bounds(:), hi_bounds(:) - - if (present(key_)) then - key = key_ - else - key = ZERO - end if - - allocate(lo_bounds(size(dense, dim=3))) - allocate(hi_bounds(size(dense, dim=3))) - - if (allocated(jagged)) deallocate(jagged) - allocate(jagged(size(dense, dim=3))) - do i = 1, size(dense, dim=3) - ! Find the min and max j values - do jmin = 1, size(dense, dim=2) - if (any(dense(:, jmin, i) /= key)) exit - end do - do jmax = size(dense, dim=2), 1, -1 - if (any(dense(:, jmax, i) /= key)) exit - end do - ! Treat the case of all values matching the key - if (jmin > jmax) then - jmin = i - jmax = i - end if - - ! Now store the jagged row - allocate(jagged(i) % data(size(dense, dim=1), jmin:jmax)) - jagged(i) % data(:, jmin:jmax) = dense(:, jmin:jmax, i) - - lo_bounds(i) = jmin - hi_bounds(i) = jmax - end do - - if (present(lo_bounds_)) then - if (allocated(lo_bounds_)) deallocate(lo_bounds_) - allocate(lo_bounds_(size(dense, dim=3))) - lo_bounds_ = lo_bounds - end if - if (present(hi_bounds_)) then - if (allocated(hi_bounds_)) deallocate(hi_bounds_) - allocate(hi_bounds_(size(dense, dim=3))) - hi_bounds_ = hi_bounds - end if - - end subroutine jagged_from_dense_2D - -end module scattdata_header From 7fd4cbb1403b04f7a609ba4379b3fe78229fd118 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Wed, 13 Jun 2018 20:26:20 -0400 Subject: [PATCH 326/361] whoops, missed some code i could remove --- src/mgxs_data.F90 | 183 ---------------------------------------------- 1 file changed, 183 deletions(-) diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index 0278c96eb..8c48d5c98 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -24,149 +24,6 @@ contains ! nuclides and sab_tables arrays !=============================================================================== - ! subroutine read_mgxs() - ! integer :: i ! index in materials array - ! integer :: j ! index over nuclides in material - ! integer :: i_nuclide ! index in nuclides array - ! character(20) :: name ! name of library to load - ! integer :: representation ! Data representation - ! character(MAX_LINE_LEN) :: temp_str - ! type(Material), pointer :: mat - ! type(SetChar) :: already_read - ! integer(HID_T) :: file_id - ! integer(HID_T) :: xsdata_group - ! logical :: file_exists - ! type(VectorReal), allocatable :: temps(:) - ! character(MAX_WORD_LEN) :: word - ! integer, allocatable :: array(:) - - ! ! Check if MGXS Library exists - ! inquire(FILE=path_cross_sections, EXIST=file_exists) - ! if (.not. file_exists) then - - ! ! Could not find MGXS Library file - ! call fatal_error("Cross sections HDF5 file '" & - ! // trim(path_cross_sections) // "' does not exist!") - ! end if - - ! call write_message("Loading cross section data...", 5) - - ! ! Get temperatures - ! call get_temperatures(temps) - - ! ! Open file for reading - ! file_id = file_open(path_cross_sections, 'r', parallel=.true.) - - ! ! Read filetype - ! call read_attribute(word, file_id, "filetype") - ! if (word /= 'mgxs') then - ! call fatal_error("Provided MGXS Library is not a MGXS Library file.") - ! end if - - ! ! Read revision number for the MGXS Library file and make sure it matches - ! ! with the current version - ! call read_attribute(array, file_id, "version") - ! if (any(array /= VERSION_MGXS_LIBRARY)) then - ! call fatal_error("MGXS Library file version does not match current & - ! &version supported by OpenMC.") - ! end if - - ! ! allocate arrays for MGXS storage and cross section cache - ! allocate(nuclides_MG(n_nuclides)) - - ! ! ========================================================================== - ! ! READ ALL MGXS CROSS SECTION TABLES - - ! ! Loop over all files - ! MATERIAL_LOOP: do i = 1, n_materials - ! mat => materials(i) - - ! NUCLIDE_LOOP: do j = 1, mat % n_nuclides - ! name = mat % names(j) - - ! if (.not. already_read % contains(name)) then - ! i_nuclide = mat % nuclide(j) - - ! call write_message("Loading " // trim(name) // " data...", 6) - - ! ! Check to make sure cross section set exists in the library - ! if (object_exists(file_id, trim(name))) then - ! xsdata_group = open_group(file_id, trim(name)) - ! else - ! call fatal_error("Data for '" // trim(name) // "' does not exist in "& - ! &// trim(path_cross_sections)) - ! end if - - ! ! First find out the data representation - ! if (attribute_exists(xsdata_group, "representation")) then - - ! call read_attribute(temp_str, xsdata_group, "representation") - - ! if (trim(temp_str) == 'isotropic') then - ! representation = MGXS_ISOTROPIC - ! else if (trim(temp_str) == 'angle') then - ! representation = MGXS_ANGLE - ! else - ! call fatal_error("Invalid Data Representation!") - ! end if - ! else - ! ! Default to isotropic representation - ! representation = MGXS_ISOTROPIC - ! end if - - ! ! Now allocate accordingly - ! select case(representation) - - ! case(MGXS_ISOTROPIC) - ! allocate(MgxsIso :: nuclides_MG(i_nuclide) % obj) - - ! case(MGXS_ANGLE) - ! allocate(MgxsAngle :: nuclides_MG(i_nuclide) % obj) - - ! end select - - ! ! Now read in the data specific to the type we just declared - ! call nuclides_MG(i_nuclide) % obj % from_hdf5(xsdata_group, & - ! num_energy_groups, num_delayed_groups, temps(i_nuclide), & - ! temperature_method, temperature_tolerance, max_order, & - ! legendre_to_tabular, legendre_to_tabular_points) - - ! ! Add name to dictionary - ! call already_read % add(name) - - ! call close_group(xsdata_group) - - ! end if - ! end do NUCLIDE_LOOP - ! end do MATERIAL_LOOP - - ! ! Avoid some valgrind leak errors - ! call already_read % clear() - - ! ! Loop around material - ! MATERIAL_LOOP3: do i = 1, n_materials - - ! ! Get material - ! mat => materials(i) - - ! ! Loop around nuclides in material - ! NUCLIDE_LOOP2: do j = 1, mat % n_nuclides - - ! ! Is this fissionable? - ! if (nuclides_MG(mat % nuclide(j)) % obj % fissionable) then - ! mat % fissionable = .true. - ! end if - ! if (mat % fissionable) then - ! exit NUCLIDE_LOOP2 - ! end if - - ! end do NUCLIDE_LOOP2 - ! end do MATERIAL_LOOP3 - - ! call file_close(file_id) - - ! end subroutine read_mgxs - subroutine read_mgxs() integer :: i ! index in materials array integer :: j ! index over nuclides in material @@ -248,45 +105,6 @@ contains ! CREATE_MACRO_XS generates the macroscopic xs from the microscopic input data !=============================================================================== - ! subroutine create_macro_xs() - ! integer :: i_mat ! index in materials array - ! type(Material), pointer :: mat ! current material - ! type(VectorReal), allocatable :: kTs(:) - - ! allocate(macro_xs(n_materials)) - - ! ! Get temperatures to read for each material - ! call get_mat_kTs(kTs) - - ! ! Force all nuclides in a material to be the same representation. - ! ! Therefore type(nuclides(mat % nuclide(1)) % obj) dictates type(macroxs). - ! ! At the same time, we will find the scattering type, as that will dictate - ! ! how we allocate the scatter object within macroxs.allocate(macro_xs(n_materials)) - ! do i_mat = 1, n_materials - - ! ! Get the material - ! mat => materials(i_mat) - - ! ! Get the scattering type for the first nuclide - ! select type(nuc => nuclides_MG(mat % nuclide(1)) % obj) - ! type is (MgxsIso) - ! allocate(MgxsIso :: macro_xs(i_mat) % obj) - ! type is (MgxsAngle) - ! allocate(MgxsAngle :: macro_xs(i_mat) % obj) - ! end select - - ! ! Do not read materials which we do not actually use in the problem to - ! ! reduce storage - ! if (allocated(kTs(i_mat) % data)) then - ! call macro_xs(i_mat) % obj % combine(kTs(i_mat), mat, nuclides_MG, & - ! num_energy_groups, num_delayed_groups, max_order, & - ! temperature_tolerance, temperature_method) - ! end if - ! end do - - ! end subroutine create_macro_xs - - subroutine create_macro_xs() integer :: i_mat ! index in materials array type(Material), pointer :: mat ! current material @@ -318,7 +136,6 @@ contains end subroutine create_macro_xs - !=============================================================================== ! GET_MAT_kTs returns a list of temperatures (in eV) that each ! material appears at in the model. From 7c819f02bc15f7367a56617c211ea98f0a1bc59b Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Thu, 14 Jun 2018 20:22:31 -0400 Subject: [PATCH 327/361] got it working with OpenMP --- src/mgxs.cpp | 156 +++++++++++++++-------------- src/mgxs.h | 50 ++++++---- src/mgxs_data.F90 | 23 ++++- src/mgxs_interface.F90 | 69 ++++--------- src/mgxs_interface.cpp | 221 ++++++++++++++++++----------------------- src/mgxs_interface.h | 49 ++++----- src/physics_mg.F90 | 22 +++- src/tallies/tally.F90 | 218 +++++++++++++++++++--------------------- src/tracking.F90 | 12 ++- 9 files changed, 404 insertions(+), 416 deletions(-) diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 4f92a64ee..3a079a932 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -15,7 +15,7 @@ void Mgxs::init(const std::string& in_name, const double in_awr, const double_1dvec& in_kTs, const bool in_fissionable, const int in_scatter_format, const int in_num_groups, const int in_num_delayed_groups, const double_1dvec& in_polar, - const double_1dvec& in_azimuthal) + const double_1dvec& in_azimuthal, const int n_threads) { name = in_name; awr = in_awr; @@ -29,20 +29,23 @@ void Mgxs::init(const std::string& in_name, const double in_awr, azimuthal = in_azimuthal; n_pol = polar.size(); n_azi = azimuthal.size(); - index_temp = 0; - last_sqrtkT = 0.; - index_pol = 0; - index_azi = 0; - last_uvw[0] = 1.; - last_uvw[1] = 0.; - last_uvw[2] = 0.; + cache.resize(n_threads); + for (int thread = 0; thread < n_threads; thread++) { + cache[thread].sqrtkT = 0.; + cache[thread].t = 0; + cache[thread].p = 0; + cache[thread].a = 0; + cache[thread].uvw[0] = 1.; + cache[thread].uvw[1] = 0.; + cache[thread].uvw[2] = 0.; + } } void Mgxs::_metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, const int in_num_delayed_groups, double_1dvec& temperature, int& method, const double tolerance, int_1dvec& temps_to_read, int& order_dim, - bool& is_isotropic) + bool& is_isotropic, const int n_threads) { // get name char char_name[MAX_WORD_LEN]; @@ -242,21 +245,23 @@ void Mgxs::_metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, // Finally use this data to initialize the MGXS Object init(in_name, in_awr, in_kTs, in_fissionable, in_scatter_format, - in_num_groups, in_num_delayed_groups, in_polar, in_azimuthal); + in_num_groups, in_num_delayed_groups, in_polar, in_azimuthal, + n_threads); } -void Mgxs::from_hdf5(hid_t xs_id, int energy_groups, int delayed_groups, - double_1dvec& temperature, int& method, double tolerance, - int max_order, bool legendre_to_tabular, - int legendre_to_tabular_points) +void Mgxs::from_hdf5(hid_t xs_id, const int energy_groups, + const int delayed_groups, double_1dvec& temperature, int& method, + const double tolerance, const int max_order, + const bool legendre_to_tabular, const int legendre_to_tabular_points, + const int n_threads) { // Call generic data gathering routine (will populate the metadata) int order_data; int_1dvec temps_to_read; bool is_isotropic; _metadata_from_hdf5(xs_id, energy_groups, delayed_groups, temperature, - method, tolerance, temps_to_read, order_data, is_isotropic); + method, tolerance, temps_to_read, order_data, is_isotropic, n_threads); // Set number of energy and delayed groups int final_scatter_format = scatter_format; @@ -285,8 +290,8 @@ void Mgxs::from_hdf5(hid_t xs_id, int energy_groups, int delayed_groups, void Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, - std::vector& micros, double_1dvec& atom_densities, - int& method, double tolerance) + std::vector& micros, double_1dvec& atom_densities, int& method, + const double tolerance, const int n_threads) { // Get the minimum data needed to initialize: // Dont need awr, but lets just initialize it anyways @@ -305,7 +310,8 @@ void Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, double_1dvec in_azimuthal = micros[0]->azimuthal; init(in_name, in_awr, mat_kTs, in_fissionable, in_scatter_format, - in_num_groups, in_num_delayed_groups, in_polar, in_azimuthal); + in_num_groups, in_num_delayed_groups, in_polar, in_azimuthal, + n_threads); // Create the xs data for each temperature for (int t = 0; t < mat_kTs.size(); t++) { @@ -395,52 +401,52 @@ void Mgxs::combine(std::vector& micros, double_1dvec& scalars, } -double Mgxs::get_xs(const int xstype, const int gin, int* gout, double* mu, - int* dg) +double Mgxs::get_xs(const int tid, const int xstype, const int gin, + int* gout, double* mu, int* dg) { // This method assumes that the temperature and angle indices are set double val; switch(xstype) { case MG_GET_XS_TOTAL: - val = xs[index_temp].total[index_pol][index_azi][gin]; + val = xs[cache[tid].t].total[cache[tid].p][cache[tid].a][gin]; break; case MG_GET_XS_ABSORPTION: - val = xs[index_temp].absorption[index_pol][index_azi][gin]; + val = xs[cache[tid].t].absorption[cache[tid].p][cache[tid].a][gin]; break; case MG_GET_XS_INVERSE_VELOCITY: - val = xs[index_temp].inverse_velocity[index_pol][index_azi][gin]; + val = xs[cache[tid].t].inverse_velocity[cache[tid].p][cache[tid].a][gin]; break; case MG_GET_XS_DECAY_RATE: if (dg != nullptr) { - val = xs[index_temp].decay_rate[index_pol][index_azi][*dg + 1]; + val = xs[cache[tid].t].decay_rate[cache[tid].p][cache[tid].a][*dg + 1]; } else { - val = xs[index_temp].decay_rate[index_pol][index_azi][0]; + val = xs[cache[tid].t].decay_rate[cache[tid].p][cache[tid].a][0]; } break; case MG_GET_XS_SCATTER: case MG_GET_XS_SCATTER_MULT: case MG_GET_XS_SCATTER_FMU_MULT: case MG_GET_XS_SCATTER_FMU: - val = xs[index_temp].scatter[index_pol] - [index_azi]->get_xs(xstype, gin, gout, mu); + val = xs[cache[tid].t].scatter[cache[tid].p] + [cache[tid].a]->get_xs(xstype, gin, gout, mu); break; case MG_GET_XS_FISSION: if (fissionable) { - val = xs[index_temp].fission[index_pol][index_azi][gin]; + val = xs[cache[tid].t].fission[cache[tid].p][cache[tid].a][gin]; } else { val = 0.; } break; case MG_GET_XS_KAPPA_FISSION: if (fissionable) { - val = xs[index_temp].kappa_fission[index_pol][index_azi][gin]; + val = xs[cache[tid].t].kappa_fission[cache[tid].p][cache[tid].a][gin]; } else { val = 0.; } break; case MG_GET_XS_PROMPT_NU_FISSION: if (fissionable) { - val = xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; + val = xs[cache[tid].t].prompt_nu_fission[cache[tid].p][cache[tid].a][gin]; } else { val = 0.; } @@ -448,11 +454,11 @@ double Mgxs::get_xs(const int xstype, const int gin, int* gout, double* mu, case MG_GET_XS_DELAYED_NU_FISSION: if (fissionable) { if (dg != nullptr) { - val = xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin][*dg]; + val = xs[cache[tid].t].delayed_nu_fission[cache[tid].p][cache[tid].a][gin][*dg]; } else { val = 0.; - for (auto& num : xs[index_temp].delayed_nu_fission[index_pol] - [index_azi][gin]) { + for (auto& num : xs[cache[tid].t].delayed_nu_fission[cache[tid].p] + [cache[tid].a][gin]) { val += num; } } @@ -462,7 +468,7 @@ double Mgxs::get_xs(const int xstype, const int gin, int* gout, double* mu, break; case MG_GET_XS_NU_FISSION: if (fissionable) { - val = xs[index_temp].nu_fission[index_pol][index_azi][gin]; + val = xs[cache[tid].t].nu_fission[cache[tid].p][cache[tid].a][gin]; } else { val = 0.; } @@ -470,11 +476,11 @@ double Mgxs::get_xs(const int xstype, const int gin, int* gout, double* mu, case MG_GET_XS_CHI_PROMPT: if (fissionable) { if (gout != nullptr) { - val = xs[index_temp].chi_prompt[index_pol][index_azi][gin][*gout]; + val = xs[cache[tid].t].chi_prompt[cache[tid].p][cache[tid].a][gin][*gout]; } else { // provide an outgoing group-wise sum val = 0.; - for (auto& num : xs[index_temp].chi_prompt[index_pol][index_azi][gin]) { + for (auto& num : xs[cache[tid].t].chi_prompt[cache[tid].p][cache[tid].a][gin]) { val += num; } } @@ -486,23 +492,23 @@ double Mgxs::get_xs(const int xstype, const int gin, int* gout, double* mu, if (fissionable) { if (gout != nullptr) { if (dg != nullptr) { - val = xs[index_temp].chi_delayed[index_pol][index_azi][gin][*gout][*dg]; + val = xs[cache[tid].t].chi_delayed[cache[tid].p][cache[tid].a][gin][*gout][*dg]; } else { - val = xs[index_temp].chi_delayed[index_pol][index_azi][gin][*gout][0]; + val = xs[cache[tid].t].chi_delayed[cache[tid].p][cache[tid].a][gin][*gout][0]; } } else { if (dg != nullptr) { val = 0.; - for (int i = 0; i < xs[index_temp].chi_delayed[index_pol] - [index_azi][gin].size(); i++) { - val += xs[index_temp].chi_delayed[index_pol][index_azi][gin][i][*dg]; + for (int i = 0; i < xs[cache[tid].t].chi_delayed[cache[tid].p] + [cache[tid].a][gin].size(); i++) { + val += xs[cache[tid].t].chi_delayed[cache[tid].p][cache[tid].a][gin][i][*dg]; } } else { val = 0.; - for (int i = 0; i < xs[index_temp].chi_delayed[index_pol] - [index_azi][gin].size(); i++) { - for (auto& num : xs[index_temp].chi_delayed[index_pol] - [index_azi][gin][i]) { + for (int i = 0; i < xs[cache[tid].t].chi_delayed[cache[tid].p] + [cache[tid].a][gin].size(); i++) { + for (auto& num : xs[cache[tid].t].chi_delayed[cache[tid].p] + [cache[tid].a][gin][i]) { val += num; } } @@ -519,14 +525,15 @@ double Mgxs::get_xs(const int xstype, const int gin, int* gout, double* mu, } -void Mgxs::sample_fission_energy(const int gin, int& dg, int& gout) +void Mgxs::sample_fission_energy(const int tid, const int gin, int& dg, + int& gout) { // This method assumes that the temperature and angle indices are set - double nu_fission = xs[index_temp].nu_fission[index_pol][index_azi][gin]; + double nu_fission = xs[cache[tid].t].nu_fission[cache[tid].p][cache[tid].a][gin]; // Find the probability of having a prompt neutron double prob_prompt = - xs[index_temp].prompt_nu_fission[index_pol][index_azi][gin]; + xs[cache[tid].t].prompt_nu_fission[cache[tid].p][cache[tid].a][gin]; // sample random numbers double xi_pd = prn() * nu_fission; @@ -542,10 +549,10 @@ void Mgxs::sample_fission_energy(const int gin, int& dg, int& gout) // sample the outgoing energy group gout = 0; double prob_gout = - xs[index_temp].chi_prompt[index_pol][index_azi][gin][gout]; + xs[cache[tid].t].chi_prompt[cache[tid].p][cache[tid].a][gin][gout]; while (prob_gout < xi_gout) { gout++; - prob_gout += xs[index_temp].chi_prompt[index_pol][index_azi][gin][gout]; + prob_gout += xs[cache[tid].t].chi_prompt[cache[tid].p][cache[tid].a][gin][gout]; } } else { @@ -556,7 +563,7 @@ void Mgxs::sample_fission_energy(const int gin, int& dg, int& gout) while (xi_pd >= prob_prompt) { dg++; prob_prompt += - xs[index_temp].delayed_nu_fission[index_pol][index_azi][gin][dg]; + xs[cache[tid].t].delayed_nu_fission[cache[tid].p][cache[tid].a][gin][dg]; } // adjust dg in case of round-off error @@ -565,35 +572,36 @@ void Mgxs::sample_fission_energy(const int gin, int& dg, int& gout) // sample the outgoing energy group gout = 0; double prob_gout = - xs[index_temp].chi_delayed[index_pol][index_azi][gin][gout][dg]; + xs[cache[tid].t].chi_delayed[cache[tid].p][cache[tid].a][gin][gout][dg]; while (prob_gout < xi_gout) { gout++; prob_gout += - xs[index_temp].chi_delayed[index_pol][index_azi][gin][gout][dg]; + xs[cache[tid].t].chi_delayed[cache[tid].p][cache[tid].a][gin][gout][dg]; } } } -void Mgxs::sample_scatter(const int gin, int& gout, double& mu, double& wgt) +void Mgxs::sample_scatter(const int tid, const int gin, int& gout, double& mu, + double& wgt) { // This method assumes that the temperature and angle indices are set // Sample the data - xs[index_temp].scatter[index_pol][index_azi]->sample(gin, gout, mu, wgt); + xs[cache[tid].t].scatter[cache[tid].p][cache[tid].a]->sample(gin, gout, mu, wgt); } -void Mgxs::calculate_xs(const int gin, const double sqrtkT, const double uvw[3], - double& total_xs, double& abs_xs, double& nu_fiss_xs) +void Mgxs::calculate_xs(const int tid, const int gin, const double sqrtkT, + const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs) { // Set our indices - set_temperature_index(sqrtkT); - set_angle_index(uvw); - total_xs = xs[index_temp].total[index_pol][index_azi][gin]; - abs_xs = xs[index_temp].absorption[index_pol][index_azi][gin]; + set_temperature_index(tid, sqrtkT); + set_angle_index(tid, uvw); + total_xs = xs[cache[tid].t].total[cache[tid].p][cache[tid].a][gin]; + abs_xs = xs[cache[tid].t].absorption[cache[tid].p][cache[tid].a][gin]; if (fissionable) { - nu_fiss_xs = xs[index_temp].nu_fission[index_pol][index_azi][gin]; + nu_fiss_xs = xs[cache[tid].t].nu_fission[cache[tid].p][cache[tid].a][gin]; } else { nu_fiss_xs = 0.; } @@ -617,10 +625,10 @@ bool Mgxs::equiv(const Mgxs& that) } -void Mgxs::set_temperature_index(const double sqrtkT) +void Mgxs::set_temperature_index(const int tid, const double sqrtkT) { // See if we need to find the new index - if (sqrtkT != last_sqrtkT) { + if (sqrtkT != cache[tid].sqrtkT) { double kT = sqrtkT * sqrtkT; // initialize vector for storage of the differences @@ -628,34 +636,34 @@ void Mgxs::set_temperature_index(const double sqrtkT) // Find the minimum difference of kT and kTs temp_diff = std::abs(temp_diff - kT); - index_temp = std::min_element(std::begin(temp_diff), std::end(temp_diff)) - + cache[tid].t = std::min_element(std::begin(temp_diff), std::end(temp_diff)) - std::begin(temp_diff); // store this temperature as the last one used - last_sqrtkT = sqrtkT; + cache[tid].sqrtkT = sqrtkT; } } -void Mgxs::set_angle_index(const double uvw[3]) +void Mgxs::set_angle_index(const int tid, const double uvw[3]) { // See if we need to find the new index - if ((uvw[0] != last_uvw[0]) || (uvw[1] != last_uvw[1]) || - (uvw[2] != last_uvw[2])) { + if ((uvw[0] != cache[tid].uvw[0]) || (uvw[1] != cache[tid].uvw[1]) || + (uvw[2] != cache[tid].uvw[2])) { // convert uvw to polar and azimuthal angles double my_pol = std::acos(uvw[2]); double my_azi = std::atan2(uvw[1], uvw[0]); // Find the location, assuming equal-bin angles double delta_angle = PI / n_pol; - index_pol = std::floor(my_pol / delta_angle); + cache[tid].p = std::floor(my_pol / delta_angle); delta_angle = 2. * PI / n_azi; - index_azi = std::floor((my_azi + PI) / delta_angle); + cache[tid].a = std::floor((my_azi + PI) / delta_angle); // store this direction as the last one used - last_uvw[0] = uvw[0]; - last_uvw[1] = uvw[1]; - last_uvw[2] = uvw[2]; + cache[tid].uvw[0] = uvw[0]; + cache[tid].uvw[1] = uvw[1]; + cache[tid].uvw[2] = uvw[2]; } } diff --git a/src/mgxs.h b/src/mgxs.h index 2fc3e2bec..1c98417d6 100644 --- a/src/mgxs.h +++ b/src/mgxs.h @@ -23,6 +23,18 @@ namespace openmc { +//============================================================================== +// Cache contains the cached data for an MGXS object +//============================================================================== + +struct CacheData { + double sqrtkT; // last temperature corresponding to t + int t; // temperature index + int p; // polar angle index + int a; // azimuthal angle index + double uvw[3]; // last angle that corresponds to p and a +}; + //============================================================================== // MGXS contains the mgxs data for a nuclide/material //============================================================================== @@ -33,7 +45,6 @@ class Mgxs { int scatter_format; // flag for if this is legendre, histogram, or tabular int num_delayed_groups; // number of delayed neutron groups int num_groups; // number of energy groups - double last_sqrtkT; // cache of the temperature corresponding to index_temp std::vector xs; // Cross section data int n_pol; int n_azi; @@ -42,7 +53,7 @@ class Mgxs { void _metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, const int in_num_delayed_groups, double_1dvec& temperature, int& method, const double tolerance, int_1dvec& temps_to_read, - int& order_dim, bool& is_isotropic); + int& order_dim, bool& is_isotropic, const int n_threads); bool equiv(const Mgxs& that); public: @@ -50,32 +61,31 @@ class Mgxs { double awr; // atomic weight ratio bool fissionable; // Is this fissionable // TODO: The following attributes be private when Fortran is fully replaced - int index_pol; // cache for the angle indices - int index_azi; - double last_uvw[3]; // cache of the angle corresponding to the above indices - int index_temp; // cache of temperature index + std::vector cache; // index and data cache void init(const std::string& in_name, const double in_awr, const double_1dvec& in_kTs, const bool in_fissionable, const int in_scatter_format, const int in_num_groups, const int in_num_delayed_groups, const double_1dvec& in_polar, - const double_1dvec& in_azimuthal); + const double_1dvec& in_azimuthal, const int n_threads); void build_macro(const std::string& in_name, double_1dvec& mat_kTs, std::vector& micros, double_1dvec& atom_densities, - int& method, double tolerance); + int& method, const double tolerance, const int n_threads); void combine(std::vector& micros, double_1dvec& scalars, int_1dvec& micro_ts, int this_t); - void from_hdf5(hid_t xs_id, int energy_groups, int delayed_groups, - double_1dvec& temperature, int& method, double tolerance, - int max_order, bool legendre_to_tabular, - int legendre_to_tabular_points); - double get_xs(const int xstype, const int gin, int* gout, double* mu, - int* dg); - void sample_fission_energy(const int gin, int& dg, int& gout); - void sample_scatter(const int gin, int& gout, double& mu, double& wgt); - void calculate_xs(const int gin, const double sqrtkT, const double uvw[3], - double& total_xs, double& abs_xs, double& nu_fiss_xs); - void set_temperature_index(const double sqrtkT); - void set_angle_index(const double uvw[3]); + void from_hdf5(hid_t xs_id, const int energy_groups, + const int delayed_groups, double_1dvec& temperature, int& method, + const double tolerance, const int max_order, + const bool legendre_to_tabular, const int legendre_to_tabular_points, + const int n_threads); + double get_xs(const int tid, const int xstype, const int gin, int* gout, + double* mu, int* dg); + void sample_fission_energy(const int tid, const int gin, int& dg, int& gout); + void sample_scatter(const int tid, const int gin, int& gout, double& mu, + double& wgt); + void calculate_xs(const int tid, const int gin, const double sqrtkT, + const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs); + void set_temperature_index(const int tid, const double sqrtkT); + void set_angle_index(const int tid, const double uvw[3]); }; } // namespace openmc diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index 8c48d5c98..ef416125e 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -2,6 +2,10 @@ module mgxs_data use, intrinsic :: ISO_C_BINDING +#ifdef _OPENMP + use omp_lib +#endif + use constants use algorithm, only: find use dict_header, only: DictCharInt @@ -36,6 +40,13 @@ contains type(VectorReal), allocatable, target :: temps(:) character(MAX_WORD_LEN) :: word integer, allocatable :: array(:) + integer(C_INT) :: n_threads + +#ifdef _OPENMP + n_threads = OMP_GET_MAX_THREADS() +#else + n_threads = 1 +#endif ! Check if MGXS Library exists inquire(FILE=path_cross_sections, EXIST=file_exists) @@ -84,7 +95,8 @@ contains num_energy_groups, num_delayed_groups, & temps(i_nuclide) % size(), temps(i_nuclide) % data, & temperature_method, temperature_tolerance, max_order, & - logical(legendre_to_tabular, C_BOOL), legendre_to_tabular_points) + logical(legendre_to_tabular, C_BOOL), & + legendre_to_tabular_points, n_threads) call already_read % add(name) end if @@ -110,6 +122,13 @@ contains type(Material), pointer :: mat ! current material type(VectorReal), allocatable :: kTs(:) character(MAX_WORD_LEN) :: name ! name of material + integer(C_INT) :: n_threads + +#ifdef _OPENMP + n_threads = OMP_GET_MAX_THREADS() +#else + n_threads = 1 +#endif ! Get temperatures to read for each material call get_mat_kTs(kTs) @@ -130,7 +149,7 @@ contains if (allocated(kTs(i_mat) % data)) then call create_macro_xs_c(name, mat % n_nuclides, mat % nuclide, & kTs(i_mat) % size(), kTs(i_mat) % data, mat % atom_density, & - temperature_method, temperature_tolerance) + temperature_method, temperature_tolerance, n_threads) end if end do diff --git a/src/mgxs_interface.F90 b/src/mgxs_interface.F90 index 7404af686..34b0fb611 100644 --- a/src/mgxs_interface.F90 +++ b/src/mgxs_interface.F90 @@ -10,7 +10,7 @@ module mgxs_interface subroutine add_mgxs_c(file_id, name, energy_groups, delayed_groups, & n_temps, temps, method, tolerance, max_order, legendre_to_tabular, & - legendre_to_tabular_points) bind(C) + legendre_to_tabular_points, n_threads) bind(C) use ISO_C_BINDING import HID_T implicit none @@ -25,6 +25,7 @@ module mgxs_interface integer(C_INT), value, intent(in) :: max_order logical(C_BOOL),value, intent(in) :: legendre_to_tabular integer(C_INT), value, intent(in) :: legendre_to_tabular_points + integer(C_INT), value, intent(in) :: n_threads end subroutine add_mgxs_c function query_fissionable_c(n_nuclides, i_nuclides) result(result) bind(C) @@ -36,7 +37,7 @@ module mgxs_interface end function query_fissionable_c subroutine create_macro_xs_c(name, n_nuclides, i_nuclides, n_temps, temps, & - atom_densities, method, tolerance) bind(C) + atom_densities, method, tolerance, n_threads) bind(C) use ISO_C_BINDING implicit none character(kind=C_CHAR),intent(in) :: name(*) @@ -47,13 +48,15 @@ module mgxs_interface real(C_DOUBLE), intent(in) :: atom_densities(1:n_nuclides) integer(C_INT), intent(inout) :: method real(C_DOUBLE), value, intent(in) :: tolerance + integer(C_INT), value, intent(in) :: n_threads end subroutine create_macro_xs_c - subroutine calculate_xs_c(i_mat, gin, sqrtkT, uvw, total_xs, abs_xs, & + subroutine calculate_xs_c(i_mat, tid, gin, sqrtkT, uvw, total_xs, abs_xs, & nu_fiss_xs) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: i_mat + integer(C_INT), value, intent(in) :: tid integer(C_INT), value, intent(in) :: gin real(C_DOUBLE), value, intent(in) :: sqrtkT real(C_DOUBLE), intent(in) :: uvw(1:3) @@ -62,10 +65,11 @@ module mgxs_interface real(C_DOUBLE), intent(inout) :: nu_fiss_xs end subroutine calculate_xs_c - subroutine sample_scatter_c(i_mat, gin, gout, mu, wgt, uvw) bind(C) + subroutine sample_scatter_c(i_mat, tid, gin, gout, mu, wgt, uvw) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: i_mat + integer(C_INT), value, intent(in) :: tid integer(C_INT), value, intent(in) :: gin integer(C_INT), intent(inout) :: gout real(C_DOUBLE), intent(inout) :: mu @@ -73,10 +77,11 @@ module mgxs_interface real(C_DOUBLE), intent(inout) :: uvw(1:3) end subroutine sample_scatter_c - subroutine sample_fission_energy_c(i_mat, gin, dg, gout) bind(C) + subroutine sample_fission_energy_c(i_mat, tid, gin, dg, gout) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: i_mat + integer(C_INT), value, intent(in) :: tid integer(C_INT), value, intent(in) :: gin integer(C_INT), intent(inout) :: dg integer(C_INT), intent(inout) :: gout @@ -97,11 +102,12 @@ module mgxs_interface real(C_DOUBLE) :: awr end function get_awr_c - function get_nuclide_xs_c(index, xstype, gin, gout, mu, dg) result(val) & + function get_nuclide_xs_c(index, tid, xstype, gin, gout, mu, dg) result(val) & bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: tid integer(C_INT), value, intent(in) :: xstype integer(C_INT), value, intent(in) :: gin integer(C_INT), optional, intent(in) :: gout @@ -110,11 +116,12 @@ module mgxs_interface real(C_DOUBLE) :: val end function get_nuclide_xs_c - function get_macro_xs_c(index, xstype, gin, gout, mu, dg) result(val) & + function get_macro_xs_c(index, tid, xstype, gin, gout, mu, dg) result(val) & bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: tid integer(C_INT), value, intent(in) :: xstype integer(C_INT), value, intent(in) :: gin integer(C_INT), optional, intent(in) :: gout @@ -123,63 +130,29 @@ module mgxs_interface real(C_DOUBLE) :: val end function get_macro_xs_c - subroutine set_nuclide_angle_index_c(index, uvw, last_pol, last_azi, & - last_uvw) bind(C) + subroutine set_nuclide_angle_index_c(index, tid, uvw) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: tid real(C_DOUBLE), intent(in) :: uvw(1:3) - integer(C_INT), intent(inout) :: last_pol - integer(C_INT), intent(inout) :: last_azi - real(C_DOUBLE), intent(inout) :: last_uvw(1:3) end subroutine set_nuclide_angle_index_c - subroutine reset_nuclide_angle_index_c(index, last_pol, last_azi, & - last_uvw) bind(C) - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: index - integer(C_INT), value, intent(in) :: last_pol - integer(C_INT), value, intent(in) :: last_azi - real(C_DOUBLE), intent(in) :: last_uvw(1:3) - end subroutine reset_nuclide_angle_index_c - - subroutine set_macro_angle_index_c(index, uvw, last_pol, last_azi, & - last_uvw) bind(C) + subroutine set_macro_angle_index_c(index, tid, uvw) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: tid real(C_DOUBLE), intent(in) :: uvw(1:3) - integer(C_INT), intent(inout) :: last_pol - integer(C_INT), intent(inout) :: last_azi - real(C_DOUBLE), intent(inout) :: last_uvw(1:3) end subroutine set_macro_angle_index_c - subroutine reset_macro_angle_index_c(index, last_pol, last_azi, & - last_uvw) bind(C) - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: index - integer(C_INT), value, intent(in) :: last_pol - integer(C_INT), value, intent(in) :: last_azi - real(C_DOUBLE), intent(in) :: last_uvw(1:3) - end subroutine reset_macro_angle_index_c - - function set_nuclide_temperature_index_c(index, sqrtkT) result(last_temp) & - bind(C) + subroutine set_nuclide_temperature_index_c(index, tid, sqrtkT) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: index + integer(C_INT), value, intent(in) :: tid real(C_DOUBLE), value, intent(in) :: sqrtkT - integer(C_INT) :: last_temp - end function set_nuclide_temperature_index_c - - subroutine reset_nuclide_temperature_index_c(index, last_temp) bind(C) - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: index - integer(C_INT), value, intent(in) :: last_temp - end subroutine reset_nuclide_temperature_index_c + end subroutine set_nuclide_temperature_index_c end interface diff --git a/src/mgxs_interface.cpp b/src/mgxs_interface.cpp index 60afc2848..6c9dc9f42 100644 --- a/src/mgxs_interface.cpp +++ b/src/mgxs_interface.cpp @@ -6,10 +6,11 @@ namespace openmc { // Mgxs data loading interface methods //============================================================================== -void add_mgxs_c(hid_t file_id, char* name, int energy_groups, - int delayed_groups, int n_temps, double temps[], int& method, - double tolerance, int max_order, bool legendre_to_tabular, - int legendre_to_tabular_points) +void add_mgxs_c(hid_t file_id, char* name, const int energy_groups, + const int delayed_groups, const int n_temps, double temps[], int& method, + const double tolerance, const int max_order, + const bool legendre_to_tabular, const int legendre_to_tabular_points, + const int n_threads) { //!! mgxs_data.F90 will be modified to just create the list of names //!! in the order needed @@ -32,7 +33,7 @@ void add_mgxs_c(hid_t file_id, char* name, int energy_groups, Mgxs mg; mg.from_hdf5(xs_grp, energy_groups, delayed_groups, temperature, method, tolerance, max_order, legendre_to_tabular, - legendre_to_tabular_points); + legendre_to_tabular_points, n_threads); nuclides_MG.push_back(mg); } @@ -50,7 +51,8 @@ bool query_fissionable_c(const int n_nuclides, const int i_nuclides[]) void create_macro_xs_c(char* mat_name, const int n_nuclides, const int i_nuclides[], const int n_temps, const double temps[], - const double atom_densities[], int& method, const double tolerance) + const double atom_densities[], int& method, const double tolerance, + const int n_threads) { Mgxs macro; if (n_temps > 0) { @@ -70,7 +72,7 @@ void create_macro_xs_c(char* mat_name, const int n_nuclides, } macro.build_macro(mat_name, temperature, mgxs_ptr, atom_densities_vec, - method, tolerance); + method, tolerance, n_threads); } macro_xs.push_back(macro); } @@ -79,19 +81,20 @@ void create_macro_xs_c(char* mat_name, const int n_nuclides, // Mgxs tracking/transport/tallying interface methods //============================================================================== -void calculate_xs_c(const int i_mat, const int gin, const double sqrtkT, - const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs) +void calculate_xs_c(const int i_mat, const int tid, const int gin, + const double sqrtkT, const double uvw[3], double& total_xs, double& abs_xs, + double& nu_fiss_xs) { - macro_xs[i_mat - 1].calculate_xs(gin - 1, sqrtkT, uvw, total_xs, abs_xs, + macro_xs[i_mat - 1].calculate_xs(tid, gin - 1, sqrtkT, uvw, total_xs, abs_xs, nu_fiss_xs); } -void sample_scatter_c(const int i_mat, const int gin, int& gout, double& mu, - double& wgt, double uvw[3]) +void sample_scatter_c(const int i_mat, const int tid, const int gin, int& gout, + double& mu, double& wgt, double uvw[3]) { int gout_c = gout - 1; - macro_xs[i_mat - 1].sample_scatter(gin - 1, gout_c, mu, wgt); + macro_xs[i_mat - 1].sample_scatter(tid, gin - 1, gout_c, mu, wgt); // adjust return value for fortran indexing gout = gout_c + 1; @@ -101,11 +104,12 @@ void sample_scatter_c(const int i_mat, const int gin, int& gout, double& mu, } -void sample_fission_energy_c(const int i_mat, const int gin, int& dg, int& gout) +void sample_fission_energy_c(const int i_mat, const int tid, const int gin, + int& dg, int& gout) { int dg_c = 0; int gout_c = 0; - macro_xs[i_mat - 1].sample_fission_energy(gin - 1, dg_c, gout_c); + macro_xs[i_mat - 1].sample_fission_energy(tid, gin - 1, dg_c, gout_c); // adjust return values for fortran indexing dg = dg_c + 1; @@ -113,6 +117,82 @@ void sample_fission_energy_c(const int i_mat, const int gin, int& dg, int& gout) } +double get_nuclide_xs_c(const int index, const int tid, const int xstype, + const int gin, int* gout, double* mu, int* dg) +{ + int gout_c; + int* gout_c_p; + int dg_c; + int* dg_c_p; + if (gout != nullptr) { + gout_c = *gout - 1; + gout_c_p = &gout_c; + } else { + gout_c_p = gout; + } + if (dg != nullptr) { + dg_c = *dg - 1; + dg_c_p = &dg_c; + } else { + dg_c_p = dg; + } + return nuclides_MG[index - 1].get_xs(tid, xstype, gin - 1, gout_c_p, mu, + dg_c_p); +} + + +double get_macro_xs_c(const int index, const int tid, const int xstype, + const int gin, int* gout, double* mu, int* dg) +{ + int gout_c; + int* gout_c_p; + int dg_c; + int* dg_c_p; + if (gout != nullptr) { + gout_c = *gout - 1; + gout_c_p = &gout_c; + } else { + gout_c_p = gout; + } + if (dg != nullptr) { + dg_c = *dg - 1; + dg_c_p = &dg_c; + } else { + dg_c_p = dg; + } + return macro_xs[index - 1].get_xs(tid, xstype, gin - 1, gout_c_p, mu, + dg_c_p); +} + + +void set_nuclide_angle_index_c(const int index, const int tid, + const double uvw[3]) +{ + // Update the values + nuclides_MG[index - 1].set_angle_index(tid, uvw); +} + + +void set_macro_angle_index_c(const int index, const int tid, + const double uvw[3]) +{ + // Update the values + macro_xs[index - 1].set_angle_index(tid, uvw); +} + + +void set_nuclide_temperature_index_c(const int index, const int tid, + const double sqrtkT) +{ + // Update the values + nuclides_MG[index - 1].set_temperature_index(tid, sqrtkT); +} + + +//============================================================================== +// Mgxs general methods +//============================================================================== + void get_name_c(const int index, int name_len, char* name) { // First blank out our input string @@ -133,115 +213,4 @@ double get_awr_c(const int index) return nuclides_MG[index - 1].awr; } - -double get_nuclide_xs_c(const int index, const int xstype, const int gin, - int* gout, double* mu, int* dg) -{ - int gout_c; - int* gout_c_p; - int dg_c; - int* dg_c_p; - if (gout != nullptr) { - gout_c = *gout - 1; - gout_c_p = &gout_c; - } else { - gout_c_p = gout; - } - if (dg != nullptr) { - dg_c = *dg - 1; - dg_c_p = &dg_c; - } else { - dg_c_p = dg; - } - return nuclides_MG[index - 1].get_xs(xstype, gin - 1, gout_c_p, mu, dg_c_p); -} - - -double get_macro_xs_c(const int index, const int xstype, const int gin, - int* gout, double* mu, int* dg) -{ - int gout_c; - int* gout_c_p; - int dg_c; - int* dg_c_p; - if (gout != nullptr) { - gout_c = *gout - 1; - gout_c_p = &gout_c; - } else { - gout_c_p = gout; - } - if (dg != nullptr) { - dg_c = *dg - 1; - dg_c_p = &dg_c; - } else { - dg_c_p = dg; - } - return macro_xs[index - 1].get_xs(xstype, gin - 1, gout_c_p, mu, dg_c_p); -} - - -void set_nuclide_angle_index_c(const int index, const double uvw[3], - int& last_pol, int& last_azi, double last_uvw[3]) -{ - // Store the old - last_pol = nuclides_MG[index - 1].index_pol; - last_azi = nuclides_MG[index - 1].index_azi; - last_uvw[0] = nuclides_MG[index - 1].last_uvw[0]; - last_uvw[1] = nuclides_MG[index - 1].last_uvw[1]; - last_uvw[2] = nuclides_MG[index - 1].last_uvw[2]; - - // Update the values - nuclides_MG[index - 1].set_angle_index(uvw); -} - - -void reset_nuclide_angle_index_c(const int index, const int last_pol, - const int last_azi, const double last_uvw[3]) -{ - nuclides_MG[index - 1].index_pol = last_pol; - nuclides_MG[index - 1].index_azi = last_azi; - nuclides_MG[index - 1].last_uvw[0] = last_uvw[0]; - nuclides_MG[index - 1].last_uvw[1] = last_uvw[1]; - nuclides_MG[index - 1].last_uvw[2] = last_uvw[2]; -} - - -void set_macro_angle_index_c(const int index, const double uvw[3], - int& last_pol, int& last_azi, double last_uvw[3]) -{ - // Store the old - last_pol = macro_xs[index - 1].index_pol; - last_azi = macro_xs[index - 1].index_azi; - last_uvw[0] = macro_xs[index - 1].last_uvw[0]; - last_uvw[1] = macro_xs[index - 1].last_uvw[1]; - last_uvw[2] = macro_xs[index - 1].last_uvw[2]; - - // Update the values - macro_xs[index - 1].set_angle_index(uvw); -} - - -void reset_macro_angle_index_c(const int index, const int last_pol, - const int last_azi, const double last_uvw[3]) -{ - macro_xs[index - 1].index_pol = last_pol; - macro_xs[index - 1].index_azi = last_azi; - macro_xs[index - 1].last_uvw[0] = last_uvw[0]; - macro_xs[index - 1].last_uvw[1] = last_uvw[1]; - macro_xs[index - 1].last_uvw[2] = last_uvw[2]; -} - - -int set_nuclide_temperature_index_c(const int index, const double sqrtkT) -{ - int old = nuclides_MG[index - 1].index_temp; - nuclides_MG[index - 1].set_temperature_index(sqrtkT); - return old; -} - -void reset_nuclide_temperature_index_c(const int index, const int last_temp) -{ - nuclides_MG[index - 1].index_temp = last_temp; -} - } // namespace openmc \ No newline at end of file diff --git a/src/mgxs_interface.h b/src/mgxs_interface.h index 197ecb0bf..4e1068266 100644 --- a/src/mgxs_interface.h +++ b/src/mgxs_interface.h @@ -13,54 +13,47 @@ extern std::vector nuclides_MG; extern std::vector macro_xs; -extern "C" void add_mgxs_c(hid_t file_id, char* name, int energy_groups, - int delayed_groups, int n_temps, double temps[], int& method, - double tolerance, int max_order, bool legendre_to_tabular, - int legendre_to_tabular_points); +extern "C" void add_mgxs_c(hid_t file_id, char* name, const int energy_groups, + const int delayed_groups, const int n_temps, double temps[], int& method, + const double tolerance, const int max_order, + const bool legendre_to_tabular, const int legendre_to_tabular_points, + const int n_threads); extern "C" bool query_fissionable_c(const int n_nuclides, const int i_nuclides[]); extern "C" void create_macro_xs_c(char* mat_name, const int n_nuclides, const int i_nuclides[], const int n_temps, const double temps[], - const double atom_densities[], int& method, const double tolerance); + const double atom_densities[], int& method, const double tolerance, + const int n_threads); -extern "C" void calculate_xs_c(const int i_mat, const int gin, +extern "C" void calculate_xs_c(const int i_mat, const int tid, const int gin, const double sqrtkT, const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs); -extern "C" void sample_scatter_c(const int i_mat, const int gin, int& gout, - double& mu, double& wgt, double uvw[3]); +extern "C" void sample_scatter_c(const int i_mat, const int tid, const int gin, + int& gout, double& mu, double& wgt, double uvw[3]); -extern "C" void sample_fission_energy_c(const int i_mat, const int gin, - int& dg, int& gout); +extern "C" void sample_fission_energy_c(const int i_mat, const int tid, + const int gin, int& dg, int& gout); extern "C" void get_name_c(const int index, int name_len, char* name); extern "C" double get_awr_c(const int index); -extern "C" double get_nuclide_xs_c(const int index, const int xstype, - const int gin, int* gout, double* mu, int* dg); +extern "C" double get_nuclide_xs_c(const int index, const int tid, + const int xstype, const int gin, int* gout, double* mu, int* dg); -extern "C" double get_macro_xs_c(const int index, const int xstype, - const int gin, int* gout, double* mu, int* dg); +extern "C" double get_macro_xs_c(const int index, const int tid, + const int xstype, const int gin, int* gout, double* mu, int* dg); -extern "C" void set_nuclide_angle_index_c(const int index, const double uvw[3], - int& last_pol, int& last_azi, double last_uvw[3]); +extern "C" void set_nuclide_angle_index_c(const int index, const int tid, + const double uvw[3]); -extern "C" void reset_nuclide_angle_index_c(const int index, const int last_pol, - const int last_azi, const double last_uvw[3]); +extern "C" void set_macro_angle_index_c(const int index, const int tid, + const double uvw[3]); -extern "C" void set_macro_angle_index_c(const int index, const double uvw[3], - int& last_pol, int& last_azi, double last_uvw[3]); - -extern "C" void reset_macro_angle_index_c(const int index, const int last_pol, - const int last_azi, const double last_uvw[3]); - -extern "C" int set_nuclide_temperature_index_c(const int index, +extern "C" void set_nuclide_temperature_index_c(const int index, const int tid, const double sqrtkT); -extern "C" void reset_nuclide_temperature_index_c(const int index, - const int last_temp); - } // namespace openmc #endif // MGXS_INTERFACE_H \ No newline at end of file diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index 797514e25..d04d4cb85 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -2,6 +2,10 @@ module physics_mg ! This module contains the multi-group specific physics routines so as to not ! hinder performance of the CE versions with multiple if-thens. +#ifdef _OPENMP + use omp_lib +#endif + use bank_header use constants use error, only: fatal_error, warning, write_message @@ -141,9 +145,15 @@ contains subroutine scatter(p) type(Particle), intent(inout) :: p + integer(C_INT) :: tid +#ifdef _OPENMP + tid = OMP_GET_THREAD_NUM() +#else + tid = 0 +#endif - call sample_scatter_c(p % material, p % last_g, p % g, p % mu, p % wgt, & - p % coord(1) % uvw) + call sample_scatter_c(p % material, tid, p % last_g, p % g, p % mu, & + p % wgt, p % coord(1) % uvw) ! Update energy value for downstream compatability (in tallying) p % E = energy_bin_avg(p % g) @@ -173,6 +183,12 @@ contains real(8) :: mu ! fission neutron angular cosine real(8) :: phi ! fission neutron azimuthal angle real(8) :: weight ! weight adjustment for ufs method + integer(C_INT) :: tid +#ifdef _OPENMP + tid = OMP_GET_THREAD_NUM() +#else + tid = 0 +#endif ! TODO: Heat generation from fission @@ -252,7 +268,7 @@ contains ! Sample secondary energy distribution for fission reaction and set energy ! in fission bank - call sample_fission_energy_c(p % material, p % g, dg, gout) + call sample_fission_energy_c(p % material, tid, p % g, dg, gout) bank_array(i) % E = real(gout, 8) bank_array(i) % delayed_group = dg diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 4c24717b4..f6839081d 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -2,6 +2,10 @@ module tally use, intrinsic :: ISO_C_BINDING +#ifdef _OPENMP + use omp_lib +#endif + use algorithm, only: binary_search use constants use dict_header, only: EMPTY @@ -1229,14 +1233,12 @@ contains real(8) :: p_uvw(3) ! Particle's current uvw integer :: p_g ! Particle group to use for getting info ! to tally with. - ! Storage of the indices the Mgxs object arrived with for resetting later - integer(C_INT) :: last_nuc_azi - integer(C_INT) :: last_nuc_pol - integer(C_INT) :: last_mat_azi - integer(C_INT) :: last_mat_pol - integer(C_INT) :: last_nuc_temp - real(C_DOUBLE) :: last_mat_uvw(3) - real(C_DOUBLE) :: last_nuc_uvw(3) + integer(C_INT) :: tid +#ifdef _OPENMP + tid = OMP_GET_THREAD_NUM() +#else + tid = 0 +#endif ! Set the direction and group to use with get_xs if (t % estimator == ESTIMATOR_ANALOG .or. & @@ -1274,15 +1276,13 @@ contains ! To significantly reduce de-referencing, point matxs to the ! macroscopic Mgxs for the material of interest - call set_macro_angle_index_c(p % material, p_uvw, last_mat_pol, & - last_mat_azi, last_mat_uvw) + call set_macro_angle_index_c(p % material, tid, p_uvw) ! Do same for nucxs, point it to the microscopic nuclide data of interest if (i_nuclide > 0) then ! And since we haven't calculated this temperature index yet, do so now - last_nuc_temp = set_nuclide_temperature_index_c(i_nuclide, p % sqrtkT) - call set_nuclide_angle_index_c(i_nuclide, p_uvw, last_nuc_pol, & - last_nuc_azi, last_nuc_uvw) + call set_nuclide_temperature_index_c(i_nuclide, tid, p % sqrtkT) + call set_nuclide_angle_index_c(i_nuclide, tid, p_uvw) end if i = 0 @@ -1336,14 +1336,14 @@ contains end if if (i_nuclide > 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_TOTAL, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_TOTAL, p_g) * flux + score = score * flux * atom_density * & + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_TOTAL, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_TOTAL, p_g) end if else if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_TOTAL, p_g) * & + score = get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_TOTAL, p_g) * & atom_density * flux else score = material_xs % total * flux @@ -1366,22 +1366,22 @@ contains end if if (i_nuclide > 0) then - score = score * get_nuclide_xs_c(i_nuclide, & + score = score * flux * get_nuclide_xs_c(i_nuclide, tid, & MG_GET_XS_INVERSE_VELOCITY, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) * flux + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) else - score = score * get_macro_xs_c(p % material, & + score = score * flux * get_macro_xs_c(p % material, tid, & MG_GET_XS_INVERSE_VELOCITY, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) * flux + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = flux * get_nuclide_xs_c(i_nuclide, & + score = flux * get_nuclide_xs_c(i_nuclide, tid, & MG_GET_XS_INVERSE_VELOCITY, p_g) else - score = flux * get_macro_xs_c(p % material, & + score = flux * get_macro_xs_c(p % material, tid, & MG_GET_XS_INVERSE_VELOCITY, p_g) end if end if @@ -1404,22 +1404,22 @@ contains ! adjust the score by the actual probability for that nuclide. if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_FMU_MULT, & + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_SCATTER_FMU_MULT, & p % last_g, p % g, MU=p % mu) / & - get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU_MULT, & + get_macro_xs_c(p % material, tid, MG_GET_XS_SCATTER_FMU_MULT, & p % last_g, p % g, MU=p % mu) end if else if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_MULT, & + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_SCATTER_MULT, & p_g, MU=p % mu) else ! Get the scattering x/s and take away ! the multiplication baked in to sigS score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_SCATTER_MULT, & + get_macro_xs_c(p % material, tid, MG_GET_XS_SCATTER_MULT, & p_g, MU=p % mu) end if end if @@ -1442,20 +1442,20 @@ contains ! adjust the score by the actual probability for that nuclide. if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_FMU, & + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_SCATTER_FMU, & p % last_g, p % g, MU=p % mu) / & - get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU, & + get_macro_xs_c(p % material, tid, MG_GET_XS_SCATTER_FMU, & p % last_g, p % g, MU=p % mu) end if else if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_SCATTER, p_g) else ! Get the scattering x/s, which includes multiplication score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_SCATTER, p_g) + get_macro_xs_c(p % material, tid, MG_GET_XS_SCATTER, p_g) end if end if @@ -1475,13 +1475,13 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_ABSORPTION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_ABSORPTION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_ABSORPTION, p_g) else score = material_xs % absorption * flux end if @@ -1506,19 +1506,19 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) * & + score = get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) * & atom_density * flux else - score = get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux + score = get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) * flux end if end if @@ -1544,12 +1544,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, MG_GET_XS_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, tid, MG_GET_XS_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) end if else ! Skip any non-fission events @@ -1562,17 +1562,17 @@ contains score = keff * p % wgt_bank * flux if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) end if end if else if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_NU_FISSION, p_g) * & + score = get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_NU_FISSION, p_g) * & atom_density * flux else - score = get_macro_xs_c(p % material, MG_GET_XS_NU_FISSION, p_g) * flux + score = get_macro_xs_c(p % material, tid, MG_GET_XS_NU_FISSION, p_g) * flux end if end if @@ -1598,12 +1598,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, tid, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) end if else ! Skip any non-fission events @@ -1617,17 +1617,17 @@ contains / real(p % n_bank, 8)) * flux if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) end if end if else if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_PROMPT_NU_FISSION, p_g) * & + score = get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_PROMPT_NU_FISSION, p_g) * & atom_density * flux else - score = get_macro_xs_c(p % material, MG_GET_XS_PROMPT_NU_FISSION, p_g) * flux + score = get_macro_xs_c(p % material, tid, MG_GET_XS_PROMPT_NU_FISSION, p_g) * flux end if end if @@ -1653,7 +1653,7 @@ contains ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in ! nu-fission - if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then + if (get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) > ZERO) then if (dg_filter > 0) then select type(filt => filters(t % filter(dg_filter)) % obj) @@ -1669,12 +1669,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1685,12 +1685,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) end if end if end if @@ -1720,8 +1720,8 @@ contains if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1732,8 +1732,8 @@ contains score = keff * p % wgt_bank / p % n_bank * sum(p % n_delayed_bank) * flux if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) end if end if end if @@ -1753,10 +1753,10 @@ contains if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1766,11 +1766,11 @@ contains else if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g) else score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g) + get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g) end if end if end if @@ -1785,7 +1785,7 @@ contains ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in ! nu-fission - if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then + if (get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) > ZERO) then if (dg_filter > 0) then select type(filt => filters(t % filter(dg_filter)) % obj) @@ -1801,14 +1801,14 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1826,14 +1826,14 @@ contains do d = 1, num_delayed_groups if (i_nuclide > 0) then score = score + p % absorb_wgt * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) else score = score + p % absorb_wgt * flux * & - get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) end if end do end if @@ -1862,13 +1862,13 @@ contains if (i_nuclide > 0) then score = score + keff * atom_density * & fission_bank(n_bank - p % n_bank + k) % wgt * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) * flux else score = score + keff * & fission_bank(n_bank - p % n_bank + k) % wgt * & - get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * flux + get_macro_xs_c(p % material, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * flux end if ! if the delayed group filter is present, tally to corresponding @@ -1921,12 +1921,12 @@ contains if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_macro_xs_c(p % material, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1943,12 +1943,12 @@ contains do d = 1, num_delayed_groups if (i_nuclide > 0) then score = score + atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else score = score + flux * & - get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_macro_xs_c(p % material, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if end do end if @@ -1973,20 +1973,20 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_KAPPA_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_KAPPA_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, MG_GET_XS_KAPPA_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, tid, MG_GET_XS_KAPPA_FISSION, p_g) / & + get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_KAPPA_FISSION, p_g) * & + score = get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_KAPPA_FISSION, p_g) * & atom_density * flux else score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_KAPPA_FISSION, p_g) + get_macro_xs_c(p % material, tid, MG_GET_XS_KAPPA_FISSION, p_g) end if end if @@ -2005,16 +2005,6 @@ contains t % results(RESULT_VALUE, score_index, filter_index) + score end do SCORE_LOOP - - ! Reset temporary Mgxs indices - call reset_macro_angle_index_c(p % material, last_mat_pol, last_mat_azi, & - last_mat_uvw); - - if (i_nuclide > 0) then - call reset_nuclide_temperature_index_c(i_nuclide, last_nuc_temp) - call reset_nuclide_angle_index_c(i_nuclide, last_nuc_pol, last_nuc_azi, & - last_nuc_uvw) - end if end subroutine score_general_mg !=============================================================================== diff --git a/src/tracking.F90 b/src/tracking.F90 index c832ca9cc..80fb55a80 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -2,6 +2,10 @@ module tracking use, intrinsic :: ISO_C_BINDING +#ifdef _OPENMP + use omp_lib +#endif + use constants use error, only: warning, write_message use geometry_header, only: cells @@ -48,6 +52,12 @@ contains real(8) :: d_collision ! sampled distance to collision real(8) :: distance ! distance particle travels logical :: found_cell ! found cell which particle is in? + integer(C_INT) :: tid +#ifdef _OPENMP + tid = OMP_GET_THREAD_NUM() +#else + tid = 0 +#endif ! Display message if high verbosity or trace is on if (verbosity >= 9 .or. trace) then @@ -114,7 +124,7 @@ contains end if else ! Get the MG data - call calculate_xs_c(p % material, p % g, p % sqrtkT, & + call calculate_xs_c(p % material, tid, p % g, p % sqrtkT, & p % coord(p % n_coord) % uvw, material_xs % total, & material_xs % absorption, material_xs % nu_fission) From 52173756f61fb23db15fe04f33a50d17c03cd8c2 Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Fri, 15 Jun 2018 01:18:38 +0000 Subject: [PATCH 328/361] More cleaning-up, fixing style and comments. --- src/nuclide_header.F90 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 8b71e6bf8..4a9a36351 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -985,8 +985,6 @@ contains micro_xs % reaction(:) = ZERO !there shouldn't be a threshold check for (n,gamma). - !I know this is not very clean but I don't want to overload the loop - !with too many conditional statements for now. i_rxn = this % reaction_index(DEPLETION_RX(1)) if (i_rxn > 0) then associate (xs => this % reactions(i_rxn) % xs(i_temp)) @@ -996,7 +994,7 @@ contains end associate end if !looping from element 2 to element 6. - !treating (n,gamma) differently because it is not a threshold reaction. + do j = 2, 6 ! If reaction is present and energy is greater than threshold, set From 10bba7d5f979063567730049d392386222528124 Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Fri, 15 Jun 2018 02:44:40 +0000 Subject: [PATCH 329/361] removing trailing white-space and making sure continuation lines have 5 spaces. --- src/nuclide_header.F90 | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 4a9a36351..967c953a0 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -978,27 +978,24 @@ contains end if end associate - ! Depletion-related reactions + !Depletion-related reactions if (need_depletion_rx) then - ! Initialize entire array to zero in case we skip - ! any threshold reaction. - micro_xs % reaction(:) = ZERO - + !Initialize entire array to zero in case we skip + !any threshold reaction. + micro_xs % reaction(:) = ZERO !there shouldn't be a threshold check for (n,gamma). i_rxn = this % reaction_index(DEPLETION_RX(1)) if (i_rxn > 0) then associate (xs => this % reactions(i_rxn) % xs(i_temp)) micro_xs % reaction(1) = (ONE - f) * & - xs % value(i_grid - xs % threshold + 1) + & - f * xs % value(i_grid - xs % threshold + 2) + xs % value(i_grid - xs % threshold + 1) + & + f * xs % value(i_grid - xs % threshold + 2) end associate end if !looping from element 2 to element 6. - do j = 2, 6 - - ! If reaction is present and energy is greater than threshold, set - ! the reaction xs appropriately + !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)) @@ -1006,8 +1003,8 @@ contains micro_xs % reaction(j) = (ONE - f) * & xs % value(i_grid - xs % threshold + 1) + & f * xs % value(i_grid - xs % threshold + 2) - ! Check if we are below the (n,2n) and/or (n,3n) reaction thresholds to - ! skip remaining depletion-xs construction. + !Check if we are below the (n,2n) and/or (n,3n) reaction thresholds to + !skip remaining depletion-xs construction. elseif (j >= 4) then exit end if From 3e7aa0705f53723d3e9eeff174ec94e641ee2569 Mon Sep 17 00:00:00 2001 From: Jose Salcedo Perez Date: Fri, 15 Jun 2018 03:55:35 +0000 Subject: [PATCH 330/361] FTW, removing trail white-spaces in line 985 and 995 from nuclide-header.f90 --- src/nuclide_header.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 967c953a0..33ad2abb6 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -982,7 +982,7 @@ contains if (need_depletion_rx) then !Initialize entire array to zero in case we skip !any threshold reaction. - micro_xs % reaction(:) = ZERO + micro_xs % reaction(:) = ZERO !there shouldn't be a threshold check for (n,gamma). i_rxn = this % reaction_index(DEPLETION_RX(1)) if (i_rxn > 0) then @@ -992,7 +992,7 @@ contains f * xs % value(i_grid - xs % threshold + 2) end associate end if - !looping from element 2 to element 6. + !looping from element 2 to element 6. do j = 2, 6 !If reaction is present and energy is greater than threshold, set !the reaction xs appropriately From 9fd65822f07176393c3a45ffedf764d57d65e50f Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Fri, 15 Jun 2018 06:53:43 -0400 Subject: [PATCH 331/361] Fixing two failing tests - further inspection needed on mgxs_library_ce_to_mg; --- src/scattdata.cpp | 6 ++++-- src/xsdata.cpp | 12 ++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/scattdata.cpp b/src/scattdata.cpp index b12ec1acb..dd8ca07e4 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -234,7 +234,7 @@ void ScattDataLegendre::sample(int gin, int& gout, double& mu, double& wgt) int samples = 0; while(true) { - double mu = 2. * prn() - 1.; + mu = 2. * prn() - 1.; double f = calc_f(gin, gout, mu); if (f > 0.) { double u = prn() * M; @@ -370,6 +370,7 @@ void ScattDataLegendre::combine(std::vector& those_scatts, for (int gout = gmin_; gout <= gmax_; gout++) { sparse_scatter[gin][i_gout] = this_matrix[gin][gout]; sparse_mult[gin][i_gout] = this_mult[gin][gout]; + i_gout++; } } @@ -442,7 +443,7 @@ void ScattDataHistogram::init(int_1dvec& in_gmin, int_1dvec& in_gmax, ScattData::generic_init(order, in_gmin, in_gmax, in_energy, in_mult); - // Build the angular distributio mu values + // Build the angular distribution mu values mu = double_1dvec(order); dmu = 2. / order; mu[0] = -1.; @@ -678,6 +679,7 @@ void ScattDataHistogram::combine(std::vector& those_scatts, for (int gout = gmin_; gout <= gmax_; gout++) { sparse_scatter[gin][i_gout] = this_matrix[gin][gout]; sparse_mult[gin][i_gout] = this_mult[gin][gout]; + i_gout++; } } diff --git a/src/xsdata.cpp b/src/xsdata.cpp index b79d02e20..62bfdc916 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -248,7 +248,7 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, } else if (ndims == 4) { // nu-fission is a matrix - read_nd_vector(xsdata_grp, "nu_fission", chi_prompt); + read_nd_vector(xsdata_grp, "nu-fission", chi_prompt); // Normalize the chi info so the CDF is 1. for (int p = 0; p < n_pol; p++) { @@ -256,6 +256,9 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, for (int gin = 0; gin < energy_groups; gin++) { double chi_sum = std::accumulate(chi_prompt[p][a][gin].begin(), chi_prompt[p][a][gin].end(), 0.); + // Set the vector nu-fission from the matrix nu-fission + prompt_nu_fission[p][a][gin] = chi_sum; + if (chi_sum >= 0.) { for (int gout = 0; gout < energy_groups; gout++) { chi_prompt[p][a][gin][gout] /= chi_sum; @@ -275,13 +278,6 @@ void XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, } } - // Set the vector nu-fission from the matrix nu-fission - for (int gin = 0; gin < energy_groups; gin++) { - double sum = std::accumulate(chi_prompt[p][a][gin].begin(), - chi_prompt[p][a][gin].end(), 0.); - prompt_nu_fission[p][a][gin] = sum; - } - // Set the delayed-nu-fission and correct prompt-nu-fission with beta for (int gin = 0; gin < energy_groups; gin++) { for (int dg = 0; dg < delayed_groups; dg++) { From aade6893b1ae0b5d2585753cff9aa5d5d8473031 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 15 Jun 2018 14:22:53 -0500 Subject: [PATCH 332/361] Update some comments, move setting zeros to (n,xn) elseif --- src/constants.F90 | 2 +- src/nuclide_header.F90 | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/constants.F90 b/src/constants.F90 index e6dcfe755..eff119639 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -219,7 +219,7 @@ module constants N_3HEC = 799, N_A0 = 800, N_AC = 849, N_2N0 = 875, N_2NC = 891 ! Depletion reactions - integer, parameter :: DEPLETION_RX(6) = [N_GAMMA, N_P, N_A,N_2N, N_3N,N_4N] + integer, parameter :: DEPLETION_RX(6) = [N_GAMMA, N_P, N_A, N_2N, N_3N, N_4N] ! ACE table types integer, parameter :: & diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 33ad2abb6..9406f8b06 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -978,12 +978,10 @@ contains end if end associate - !Depletion-related reactions + ! Depletion-related reactions if (need_depletion_rx) then - !Initialize entire array to zero in case we skip - !any threshold reaction. - micro_xs % reaction(:) = ZERO - !there shouldn't be a threshold check for (n,gamma). + ! Physics says that (n,gamma) is not a threshold reaction, so we don't + ! need to specifically check its threshold index i_rxn = this % reaction_index(DEPLETION_RX(1)) if (i_rxn > 0) then associate (xs => this % reactions(i_rxn) % xs(i_temp)) @@ -992,10 +990,11 @@ contains f * xs % value(i_grid - xs % threshold + 2) end associate end if - !looping from element 2 to element 6. + + ! Loop over remaining depletion reactions do j = 2, 6 - !If reaction is present and energy is greater than threshold, set - !the reaction xs appropriately + ! 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)) @@ -1003,9 +1002,12 @@ contains micro_xs % reaction(j) = (ONE - f) * & xs % value(i_grid - xs % threshold + 1) + & f * xs % value(i_grid - xs % threshold + 2) - !Check if we are below the (n,2n) and/or (n,3n) reaction thresholds to - !skip remaining depletion-xs construction. elseif (j >= 4) then + ! One can show that the the threshold for (n,(x+1)n) is always + ! higher than the threshold for (n,xn). Thus, if we are below + ! the threshold for, e.g., (n,2n), there is no reason to check + ! the threshold for (n,3n) and (n,4n). + micro_xs % reaction(j:6) = ZERO exit end if end associate From edb4eefbc30444820aec18a3857d8b474a8d76f1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 15 Jun 2018 16:02:25 -0500 Subject: [PATCH 333/361] Fix initialization of depletion reaction xs --- src/nuclide_header.F90 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nuclide_header.F90 b/src/nuclide_header.F90 index 9406f8b06..5d046dbcc 100644 --- a/src/nuclide_header.F90 +++ b/src/nuclide_header.F90 @@ -980,6 +980,9 @@ contains ! Depletion-related reactions if (need_depletion_rx) then + ! Initialize all reaction cross sections to zero + micro_xs % reaction(:) = ZERO + ! Physics says that (n,gamma) is not a threshold reaction, so we don't ! need to specifically check its threshold index i_rxn = this % reaction_index(DEPLETION_RX(1)) @@ -1007,7 +1010,6 @@ contains ! higher than the threshold for (n,xn). Thus, if we are below ! the threshold for, e.g., (n,2n), there is no reason to check ! the threshold for (n,3n) and (n,4n). - micro_xs % reaction(j:6) = ZERO exit end if end associate From 39c063830cd87210d089f4e9c928038afe8eedc1 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Fri, 15 Jun 2018 19:46:25 -0400 Subject: [PATCH 334/361] minor changes, still not passing the test --- src/math_functions.cpp | 2 +- src/scattdata.cpp | 23 ++++++++++------------- src/scattdata.h | 4 ++-- src/xsdata.cpp | 3 ++- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/math_functions.cpp b/src/math_functions.cpp index 159e0a4e2..31f86ea5f 100644 --- a/src/math_functions.cpp +++ b/src/math_functions.cpp @@ -97,7 +97,7 @@ void calc_pn_c(int n, double x, double pnx[]) { } // Use recursion relation to build the higher orders - for (int l = 1; l < n; l ++) { + for (int l = 1; l < n; l++) { pnx[l + 1] = ((2 * l + 1) * x * pnx[l] - l * pnx[l - 1]) / (l + 1); } } diff --git a/src/scattdata.cpp b/src/scattdata.cpp index dd8ca07e4..6bce75c64 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -6,8 +6,8 @@ namespace openmc { // ScattData base-class methods //============================================================================== -void ScattData::generic_init(int order, int_1dvec in_gmin, - int_1dvec in_gmax, double_2dvec in_energy, double_2dvec in_mult) +void ScattData::generic_init(int order, int_1dvec& in_gmin, + int_1dvec& in_gmax, double_2dvec& in_energy, double_2dvec& in_mult) { int groups = in_energy.size(); @@ -18,18 +18,17 @@ void ScattData::generic_init(int order, int_1dvec in_gmin, dist.resize(groups); for (int gin = 0; gin < groups; gin++) { - // Make sure the energy is normalized - double norm = std::accumulate(in_energy[gin].begin(), - in_energy[gin].end(), 0.); - - if (norm != 0.) { - for (auto& n : in_energy[gin]) n /= norm; - } - // Store the inputted data energy[gin] = in_energy[gin]; mult[gin] = in_mult[gin]; + // Make sure the energy is normalized + double norm = std::accumulate(energy[gin].begin(), energy[gin].end(), 0.); + + if (norm != 0.) { + for (auto& n : energy[gin]) n /= norm; + } + // Initialize the distribution data dist[gin].resize(in_gmax[gin] - in_gmin[gin] + 1); for (auto& v : dist[gin]) { @@ -131,9 +130,7 @@ void ScattDataLegendre::init(int_1dvec& in_gmin, int_1dvec& in_gmax, int num_groups = in_gmax[gin] - in_gmin[gin] + 1; scattxs[gin] = 0.; for (int i_gout = 0; i_gout < num_groups; i_gout++) { - scattxs[gin] = std::accumulate(matrix[gin][i_gout].begin(), - matrix[gin][i_gout].end(), - scattxs[gin]); + scattxs[gin] += matrix[gin][i_gout][0]; } } diff --git a/src/scattdata.h b/src/scattdata.h index a9a236b2b..456610655 100644 --- a/src/scattdata.h +++ b/src/scattdata.h @@ -39,8 +39,8 @@ class ScattData { double_2dvec& in_mult, double_3dvec& coeffs) = 0; void sample_energy(int gin, int& gout, int& i_gout); double get_xs(const int xstype, int gin, int* gout, double* mu); - void generic_init(int order, int_1dvec in_gmin, int_1dvec in_gmax, - double_2dvec in_energy, double_2dvec in_mult); + void generic_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, + double_2dvec& in_energy, double_2dvec& in_mult); virtual void combine(std::vector& those_scatts, double_1dvec& scalars) = 0; virtual int get_order() = 0; diff --git a/src/xsdata.cpp b/src/xsdata.cpp index 62bfdc916..655bf9a89 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -602,7 +602,7 @@ void XsData::_scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, double_4dvec temp_mult = double_4dvec(n_pol, double_3dvec(n_azi, double_2dvec(energy_groups))); if (object_exists(scatt_grp, "multiplicity_matrix")) { - temp_arr.resize(length); + temp_arr.resize(length / order_data); read_nd_vector(scatt_grp, "multiplicity_matrix", temp_arr); // convert the flat temp_arr to a jagged array for passing to scatt data @@ -630,6 +630,7 @@ void XsData::_scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, } } } + temp_arr.clear(); close_group(scatt_grp); // Finally, convert the Legendre data to tabular, if needed From 3cdb1bbb87a1de1220485e6d52ae271da8dbfd72 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Fri, 15 Jun 2018 20:39:57 -0400 Subject: [PATCH 335/361] Whew, there we go. Fixed. --- src/mgxs.h | 1 - src/scattdata.cpp | 60 +++++++++++++++++++++++++++++++++-------------- src/xsdata.h | 4 ---- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/mgxs.h b/src/mgxs.h index 1c98417d6..d7b9a814a 100644 --- a/src/mgxs.h +++ b/src/mgxs.h @@ -10,7 +10,6 @@ #include #include #include -#include #include "constants.h" #include "hdf5_interface.h" diff --git a/src/scattdata.cpp b/src/scattdata.cpp index 6bce75c64..1e33c07c1 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -337,16 +337,24 @@ void ScattDataLegendre::combine(std::vector& those_scatts, // Find the minimum and maximum group boundaries int gmin_; for (gmin_ = 0; gmin_ < groups; gmin_++) { - bool non_zero = std::all_of(this_matrix[gin][gmin_].begin(), - this_matrix[gin][gmin_].end(), - [](double val){return val != 0.;}); + bool non_zero = false; + for (int l = 0; l < this_matrix[gin][gmin_].size(); l++) { + if (this_matrix[gin][gmin_][l] != 0.) { + non_zero = true; + break; + } + } if (non_zero) break; } int gmax_; for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { - bool non_zero = std::all_of(this_matrix[gin][gmax_].begin(), - this_matrix[gin][gmax_].end(), - [](double val){return val != 0.;}); + bool non_zero = false; + for (int l = 0; l < this_matrix[gin][gmax_].size(); l++) { + if (this_matrix[gin][gmax_][l] != 0.) { + non_zero = true; + break; + } + } if (non_zero) break; } @@ -646,16 +654,24 @@ void ScattDataHistogram::combine(std::vector& those_scatts, // Find the minimum and maximum group boundaries int gmin_; for (gmin_ = 0; gmin_ < groups; gmin_++) { - bool non_zero = std::all_of(this_matrix[gin][gmin_].begin(), - this_matrix[gin][gmin_].end(), - [](double val){return val != 0.;}); + bool non_zero = false; + for (int l = 0; l < this_matrix[gin][gmin_].size(); l++) { + if (this_matrix[gin][gmin_][l] != 0.) { + non_zero = true; + break; + } + } if (non_zero) break; } int gmax_; for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { - bool non_zero = std::all_of(this_matrix[gin][gmax_].begin(), - this_matrix[gin][gmax_].end(), - [](double val){return val != 0.;}); + bool non_zero = false; + for (int l = 0; l < this_matrix[gin][gmax_].size(); l++) { + if (this_matrix[gin][gmax_][l] != 0.) { + non_zero = true; + break; + } + } if (non_zero) break; } @@ -956,16 +972,24 @@ void ScattDataTabular::combine(std::vector& those_scatts, // Find the minimum and maximum group boundaries int gmin_; for (gmin_ = 0; gmin_ < groups; gmin_++) { - bool non_zero = std::all_of(this_matrix[gin][gmin_].begin(), - this_matrix[gin][gmin_].end(), - [](double val){return val != 0.;}); + bool non_zero = false; + for (int l = 0; l < this_matrix[gin][gmin_].size(); l++) { + if (this_matrix[gin][gmin_][l] != 0.) { + non_zero = true; + break; + } + } if (non_zero) break; } int gmax_; for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { - bool non_zero = std::all_of(this_matrix[gin][gmax_].begin(), - this_matrix[gin][gmax_].end(), - [](double val){return val != 0.;}); + bool non_zero = false; + for (int l = 0; l < this_matrix[gin][gmax_].size(); l++) { + if (this_matrix[gin][gmax_][l] != 0.) { + non_zero = true; + break; + } + } if (non_zero) break; } diff --git a/src/xsdata.h b/src/xsdata.h index 478f2e7aa..3d6f1f7bb 100644 --- a/src/xsdata.h +++ b/src/xsdata.h @@ -7,11 +7,7 @@ #include #include #include -#include -#include -#include #include -#include #include "constants.h" #include "hdf5_interface.h" From 863f91b1e7fc21a7252b338f13be323a3d94c099 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sat, 16 Jun 2018 09:25:49 -0400 Subject: [PATCH 336/361] clearing up const and making use of generic base class methods --- src/api.F90 | 2 +- src/scattdata.cpp | 505 +++++++++++++++------------------------------- src/scattdata.h | 60 +++--- src/settings.F90 | 2 +- src/xsdata.cpp | 27 ++- src/xsdata.h | 23 ++- 6 files changed, 232 insertions(+), 387 deletions(-) diff --git a/src/api.F90 b/src/api.F90 index ec17472ea..8afc6061c 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -129,7 +129,7 @@ contains index_ufs_mesh = -1 keff = ONE legendre_to_tabular = .true. - legendre_to_tabular_points = 33 + legendre_to_tabular_points = C_NONE n_batch_interval = 1 n_lost_particles = 0 n_particles = 0 diff --git a/src/scattdata.cpp b/src/scattdata.cpp index ad6a1db69..c0515f1ba 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -41,6 +41,125 @@ ScattData::generic_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, //============================================================================== +void +ScattData::generic_combine(const int max_order, + const std::vector& those_scatts, const double_1dvec& scalars, + int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& sparse_mult, + double_3dvec& sparse_scatter) +{ + int groups = those_scatts[0] -> energy.size(); + + // Now allocate and zero our storage spaces + double_3dvec this_matrix = double_3dvec(groups, double_2dvec(groups, + double_1dvec(max_order, 0.))); + double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); + double_2dvec mult_denom(groups, double_1dvec(groups, 0.)); + + // Build the dense scattering and multiplicity matrices + // Get the multiplicity_matrix + // To combine from nuclidic data we need to use the final relationship + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / + // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) + // Developed as follows: + // mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) + // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / + // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) + // nuscatt_{i,g,g'} can be reconstructed from the energy and scattxs member + // variables + for (int i = 0; i < those_scatts.size(); i++) { + ScattData* that = those_scatts[i]; + + // Build the dense matrix for that object + double_3dvec that_matrix = that->get_matrix(max_order); + + // Now add that to this for the scattering and multiplicity + for (int gin = 0; gin < groups; gin++) { + // Only spend time adding that's gmin to gmax data since the rest will + // be zeros + int i_gout = 0; + for (int gout = that->gmin[gin]; gout <= that->gmax[gin]; gout++) { + // Do the scattering matrix + for (int l = 0; l < max_order; l++) { + this_matrix[gin][gout][l] += scalars[i] * that_matrix[gin][gout][l]; + } + + // Incorporate that's contribution to the multiplicity matrix data + double nuscatt = that->scattxs[gin] * that->energy[gin][i_gout]; + mult_numer[gin][gout] += scalars[i] * nuscatt; + if (that->mult[gin][i_gout] > 0.) { + mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][i_gout]; + } else { + mult_denom[gin][gout] += scalars[i]; + } + i_gout++; + } + } + } + + // Combine mult_numer and mult_denom into the combined multiplicity matrix + double_2dvec this_mult(groups, double_1dvec(groups, 1.)); + for (int gin = 0; gin < groups; gin++) { + for (int gout = 0; gout < groups; gout++) { + if (mult_denom[gin][gout] > 0.) { + this_mult[gin][gout] = mult_numer[gin][gout] / mult_denom[gin][gout]; + } + } + } + mult_numer.clear(); + mult_denom.clear(); + + // We have the data, now we need to convert to a jagged array and then use + // the initialize function to store it on the object. + for (int gin = 0; gin < groups; gin++) { + // Find the minimum and maximum group boundaries + int gmin_; + for (gmin_ = 0; gmin_ < groups; gmin_++) { + bool non_zero = false; + for (int l = 0; l < this_matrix[gin][gmin_].size(); l++) { + if (this_matrix[gin][gmin_][l] != 0.) { + non_zero = true; + break; + } + } + if (non_zero) break; + } + int gmax_; + for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { + bool non_zero = false; + for (int l = 0; l < this_matrix[gin][gmax_].size(); l++) { + if (this_matrix[gin][gmax_][l] != 0.) { + non_zero = true; + break; + } + } + if (non_zero) break; + } + + // treat the case of all values being 0 + if (gmin_ > gmax_) { + gmin_ = gin; + gmax_ = gin; + } + + // Store the group bounds + in_gmin[gin] = gmin_; + in_gmax[gin] = gmax_; + + // Store the data in the compressed format + sparse_scatter[gin].resize(gmax_ - gmin_ + 1); + sparse_mult[gin].resize(gmax_ - gmin_ + 1); + int i_gout = 0; + for (int gout = gmin_; gout <= gmax_; gout++) { + sparse_scatter[gin][i_gout] = this_matrix[gin][gout]; + sparse_mult[gin][i_gout] = this_mult[gin][gout]; + i_gout++; + } + } +} + +//============================================================================== + void ScattData::sample_energy(int gin, int& gout, int& i_gout) { @@ -60,7 +179,8 @@ ScattData::sample_energy(int gin, int& gout, int& i_gout) //============================================================================== double -ScattData::get_xs(const int xstype, int gin, int* gout, double* mu) +ScattData::get_xs(const int xstype, const int gin, const int* gout, + const double* mu) { // Set the outgoing group offset index as needed int i_gout = 0; @@ -214,7 +334,7 @@ ScattDataLegendre::update_max_val() //============================================================================== double -ScattDataLegendre::calc_f(int gin, int gout, double mu) +ScattDataLegendre::calc_f(const int gin, const int gout, const double mu) { double f; if ((gout < gmin[gin]) || (gout > gmax[gin])) { @@ -230,7 +350,7 @@ ScattDataLegendre::calc_f(int gin, int gout, double mu) //============================================================================== void -ScattDataLegendre::sample(int gin, int& gout, double& mu, double& wgt) +ScattDataLegendre::sample(const int gin, int& gout, double& mu, double& wgt) { // Sample the outgoing energy using the base-class method int i_gout; @@ -261,8 +381,8 @@ ScattDataLegendre::sample(int gin, int& gout, double& mu, double& wgt) //============================================================================== void -ScattDataLegendre::combine(std::vector& those_scatts, - double_1dvec& scalars) +ScattDataLegendre::combine(const std::vector& those_scatts, + const double_1dvec& scalars) { // Find the max order in the data set and make sure we can combine the sets int max_order = 0; @@ -277,120 +397,18 @@ ScattDataLegendre::combine(std::vector& those_scatts, } max_order++; // Add one since this is a Legendre - // Get the groups as a shorthand - int groups = dynamic_cast(those_scatts[0])->energy.size(); + int groups = those_scatts[0] -> energy.size(); - // Now allocate and zero our storage spaces - double_3dvec this_matrix = double_3dvec(groups, double_2dvec(groups, - double_1dvec(max_order, 0.))); - double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); - double_2dvec mult_denom(groups, double_1dvec(groups, 0.)); - - // Build the dense scattering and multiplicity matrices - // Get the multiplicity_matrix - // To combine from nuclidic data we need to use the final relationship - // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - // Developed as follows: - // mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} - // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) - // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - // nuscatt_{i,g,g'} can be reconstructed from the energy and scattxs member - // variables - for (int i = 0; i < those_scatts.size(); i++) { - ScattDataLegendre* that = dynamic_cast(those_scatts[i]); - - // Build the dense matrix for that object - double_3dvec that_matrix = that->get_matrix(max_order); - - // Now add that to this for the scattering and multiplicity - for (int gin = 0; gin < groups; gin++) { - // Only spend time adding that's gmin to gmax data since the rest will - // be zeros - int i_gout = 0; - for (int gout = that->gmin[gin]; gout <= that->gmax[gin]; gout++) { - // Do the scattering matrix - for (int l = 0; l < max_order; l++) { - this_matrix[gin][gout][l] += scalars[i] * that_matrix[gin][gout][l]; - } - - // Incorporate that's contribution to the multiplicity matrix data - double nuscatt = that->scattxs[gin] * that->energy[gin][i_gout]; - mult_numer[gin][gout] += scalars[i] * nuscatt; - if (that->mult[gin][i_gout] > 0.) { - mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][i_gout]; - } else { - mult_denom[gin][gout] += scalars[i]; - } - i_gout++; - } - } - } - - // Combine mult_numer and mult_denom into the combined multiplicity matrix - double_2dvec this_mult(groups, double_1dvec(groups, 1.)); - for (int gin = 0; gin < groups; gin++) { - for (int gout = 0; gout < groups; gout++) { - if (mult_denom[gin][gout] > 0.) { - this_mult[gin][gout] = mult_numer[gin][gout] / mult_denom[gin][gout]; - } - } - } - mult_numer.clear(); - mult_denom.clear(); - - // We have the data, now we need to convert to a jagged array and then use - // the initialize function to store it on the object. int_1dvec in_gmin(groups); int_1dvec in_gmax(groups); double_3dvec sparse_scatter(groups); double_2dvec sparse_mult(groups); - for (int gin = 0; gin < groups; gin++) { - // Find the minimum and maximum group boundaries - int gmin_; - for (gmin_ = 0; gmin_ < groups; gmin_++) { - bool non_zero = false; - for (int l = 0; l < this_matrix[gin][gmin_].size(); l++) { - if (this_matrix[gin][gmin_][l] != 0.) { - non_zero = true; - break; - } - } - if (non_zero) break; - } - int gmax_; - for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { - bool non_zero = false; - for (int l = 0; l < this_matrix[gin][gmax_].size(); l++) { - if (this_matrix[gin][gmax_][l] != 0.) { - non_zero = true; - break; - } - } - if (non_zero) break; - } - // treat the case of all values being 0 - if (gmin_ > gmax_) { - gmin_ = gin; - gmax_ = gin; - } - - // Store the group bounds - in_gmin[gin] = gmin_; - in_gmax[gin] = gmax_; - - // Store the data in the compressed format - sparse_scatter[gin].resize(gmax_ - gmin_ + 1); - sparse_mult[gin].resize(gmax_ - gmin_ + 1); - int i_gout = 0; - for (int gout = gmin_; gout <= gmax_; gout++) { - sparse_scatter[gin][i_gout] = this_matrix[gin][gout]; - sparse_mult[gin][i_gout] = this_mult[gin][gout]; - i_gout++; - } - } + // The rest of the steps do not depend on the type of angular representation + // so we use a base class method to sum up xs and create new energy and mult + // matrices + ScattData::generic_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, + sparse_mult, sparse_scatter); // Got everything we need, store it. init(in_gmin, in_gmax, sparse_mult, sparse_scatter); @@ -399,7 +417,7 @@ ScattDataLegendre::combine(std::vector& those_scatts, //============================================================================== double_3dvec -ScattDataLegendre::get_matrix(int max_order) +ScattDataLegendre::get_matrix(const int max_order) { // Get the sizes and initialize the data to 0 int groups = energy.size(); @@ -504,7 +522,7 @@ ScattDataHistogram::init(int_1dvec& in_gmin, int_1dvec& in_gmax, //============================================================================== double -ScattDataHistogram::calc_f(int gin, int gout, double mu) +ScattDataHistogram::calc_f(const int gin, const int gout, const double mu) { double f; if ((gout < gmin[gin]) || (gout > gmax[gin])) { @@ -528,7 +546,7 @@ ScattDataHistogram::calc_f(int gin, int gout, double mu) //============================================================================== void -ScattDataHistogram::sample(int gin, int& gout, double& mu, double& wgt) +ScattDataHistogram::sample(const int gin, int& gout, double& mu, double& wgt) { // Sample the outgoing energy using the base-class method int i_gout; @@ -563,7 +581,7 @@ ScattDataHistogram::sample(int gin, int& gout, double& mu, double& wgt) //============================================================================== double_3dvec -ScattDataHistogram::get_matrix(int max_order) +ScattDataHistogram::get_matrix(const int max_order) { // Get the sizes and initialize the data to 0 int groups = energy.size(); @@ -587,8 +605,8 @@ ScattDataHistogram::get_matrix(int max_order) //============================================================================== void -ScattDataHistogram::combine(std::vector& those_scatts, - double_1dvec& scalars) +ScattDataHistogram::combine(const std::vector& those_scatts, + const double_1dvec& scalars) { // Find the max order in the data set and make sure we can combine the sets int max_order; @@ -605,120 +623,18 @@ ScattDataHistogram::combine(std::vector& those_scatts, } } - // Get the groups as a shorthand - int groups = dynamic_cast(those_scatts[0])->energy.size(); + int groups = those_scatts[0] -> energy.size(); - // Now allocate and zero our storage spaces - double_3dvec this_matrix = double_3dvec(groups, double_2dvec(groups, - double_1dvec(max_order, 0.))); - double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); - double_2dvec mult_denom(groups, double_1dvec(groups, 0.)); - - // Build the dense scattering and multiplicity matrices - // Get the multiplicity_matrix - // To combine from nuclidic data we need to use the final relationship - // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - // Developed as follows: - // mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} - // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) - // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - // nuscatt_{i,g,g'} can be reconstructed from the energy and scattxs member - // variables - for (int i = 0; i < those_scatts.size(); i++) { - ScattDataHistogram* that = dynamic_cast(those_scatts[i]); - - // Build the dense matrix for that object - double_3dvec that_matrix = that->get_matrix(max_order); - - // Now add that to this for the scattering and multiplicity - for (int gin = 0; gin < groups; gin++) { - // Only spend time adding that's gmin to gmax data since the rest will - // be zeros - int i_gout = 0; - for (int gout = that->gmin[gin]; gout <= that->gmax[gin]; gout++) { - // Do the scattering matrix - for (int l = 0; l < max_order; l++) { - this_matrix[gin][gout][l] += scalars[i] * that_matrix[gin][gout][l]; - } - - // Incorporate that's contribution to the multiplicity matrix data - double nuscatt = that->scattxs[gin] * that->energy[gin][i_gout]; - mult_numer[gin][gout] += scalars[i] * nuscatt; - if (that->mult[gin][i_gout] > 0.) { - mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][i_gout]; - } else { - mult_denom[gin][gout] += scalars[i]; - } - i_gout++; - } - } - } - - // Combine mult_numer and mult_denom into the combined multiplicity matrix - double_2dvec this_mult(groups, double_1dvec(groups, 1.)); - for (int gin = 0; gin < groups; gin++) { - for (int gout = 0; gout < groups; gout++) { - if (mult_denom[gin][gout] > 0.) { - this_mult[gin][gout] = mult_numer[gin][gout] / mult_denom[gin][gout]; - } - } - } - mult_numer.clear(); - mult_denom.clear(); - - // We have the data, now we need to convert to a jagged array and then use - // the initialize function to store it on the object. int_1dvec in_gmin(groups); int_1dvec in_gmax(groups); double_3dvec sparse_scatter(groups); double_2dvec sparse_mult(groups); - for (int gin = 0; gin < groups; gin++) { - // Find the minimum and maximum group boundaries - int gmin_; - for (gmin_ = 0; gmin_ < groups; gmin_++) { - bool non_zero = false; - for (int l = 0; l < this_matrix[gin][gmin_].size(); l++) { - if (this_matrix[gin][gmin_][l] != 0.) { - non_zero = true; - break; - } - } - if (non_zero) break; - } - int gmax_; - for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { - bool non_zero = false; - for (int l = 0; l < this_matrix[gin][gmax_].size(); l++) { - if (this_matrix[gin][gmax_][l] != 0.) { - non_zero = true; - break; - } - } - if (non_zero) break; - } - // treat the case of all values being 0 - if (gmin_ > gmax_) { - gmin_ = gin; - gmax_ = gin; - } - - // Store the group bounds - in_gmin[gin] = gmin_; - in_gmax[gin] = gmax_; - - // Store the data in the compressed format - sparse_scatter[gin].resize(gmax_ - gmin_ + 1); - sparse_mult[gin].resize(gmax_ - gmin_ + 1); - int i_gout = 0; - for (int gout = gmin_; gout <= gmax_; gout++) { - sparse_scatter[gin][i_gout] = this_matrix[gin][gout]; - sparse_mult[gin][i_gout] = this_mult[gin][gout]; - i_gout++; - } - } + // The rest of the steps do not depend on the type of angular representation + // so we use a base class method to sum up xs and create new energy and mult + // matrices + ScattData::generic_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, + sparse_mult, sparse_scatter); // Got everything we need, store it. init(in_gmin, in_gmax, sparse_mult, sparse_scatter); @@ -818,7 +734,7 @@ ScattDataTabular::init(int_1dvec& in_gmin, int_1dvec& in_gmax, //============================================================================== double -ScattDataTabular::calc_f(int gin, int gout, double mu) +ScattDataTabular::calc_f(const int gin, const int gout, const double mu) { double f; if ((gout < gmin[gin]) || (gout > gmax[gin])) { @@ -843,7 +759,7 @@ ScattDataTabular::calc_f(int gin, int gout, double mu) //============================================================================== void -ScattDataTabular::sample(int gin, int& gout, double& mu, double& wgt) +ScattDataTabular::sample(const int gin, int& gout, double& mu, double& wgt) { // Sample the outgoing energy using the base-class method int i_gout; @@ -891,7 +807,7 @@ ScattDataTabular::sample(int gin, int& gout, double& mu, double& wgt) //============================================================================== double_3dvec -ScattDataTabular::get_matrix(int max_order) +ScattDataTabular::get_matrix(const int max_order) { // Get the sizes and initialize the data to 0 int groups = energy.size(); @@ -915,8 +831,8 @@ ScattDataTabular::get_matrix(int max_order) //============================================================================== void -ScattDataTabular::combine(std::vector& those_scatts, - double_1dvec& scalars) +ScattDataTabular::combine(const std::vector& those_scatts, + const double_1dvec& scalars) { // Find the max order in the data set and make sure we can combine the sets int max_order; @@ -933,120 +849,18 @@ ScattDataTabular::combine(std::vector& those_scatts, } } - // Get the groups as a shorthand - int groups = dynamic_cast(those_scatts[0])->energy.size(); + int groups = those_scatts[0] -> energy.size(); - // Now allocate and zero our storage spaces - double_3dvec this_matrix = double_3dvec(groups, double_2dvec(groups, - double_1dvec(max_order, 0.))); - double_2dvec mult_numer(groups, double_1dvec(groups, 0.)); - double_2dvec mult_denom(groups, double_1dvec(groups, 0.)); - - // Build the dense scattering and multiplicity matrices - // Get the multiplicity_matrix - // To combine from nuclidic data we need to use the final relationship - // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - // Developed as follows: - // mult_{gg'} = nuScatt{g,g'} / Scatt{g,g'} - // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / sum(N_i*scatt_{i,g,g'}) - // mult_{gg'} = sum_i(N_i*nuscatt_{i,g,g'}) / - // sum_i(N_i*(nuscatt_{i,g,g'} / mult_{i,g,g'})) - // nuscatt_{i,g,g'} can be reconstructed from the energy and scattxs member - // variables - for (int i = 0; i < those_scatts.size(); i++) { - ScattDataTabular* that = dynamic_cast(those_scatts[i]); - - // Build the dense matrix for that object - double_3dvec that_matrix = that->get_matrix(max_order); - - // Now add that to this for the scattering and multiplicity - for (int gin = 0; gin < groups; gin++) { - // Only spend time adding that's gmin to gmax data since the rest will - // be zeros - int i_gout = 0; - for (int gout = that->gmin[gin]; gout <= that->gmax[gin]; gout++) { - // Do the scattering matrix - for (int l = 0; l < max_order; l++) { - this_matrix[gin][gout][l] += scalars[i] * that_matrix[gin][gout][l]; - } - - // Incorporate that's contribution to the multiplicity matrix data - double nuscatt = that->scattxs[gin] * that->energy[gin][i_gout]; - mult_numer[gin][gout] += scalars[i] * nuscatt; - if (that->mult[gin][i_gout] > 0.) { - mult_denom[gin][gout] += scalars[i] * nuscatt / that->mult[gin][i_gout]; - } else { - mult_denom[gin][gout] += scalars[i]; - } - i_gout++; - } - } - } - - // Combine mult_numer and mult_denom into the combined multiplicity matrix - double_2dvec this_mult(groups, double_1dvec(groups, 1.)); - for (int gin = 0; gin < groups; gin++) { - for (int gout = 0; gout < groups; gout++) { - if (mult_denom[gin][gout] > 0.) { - this_mult[gin][gout] = mult_numer[gin][gout] / mult_denom[gin][gout]; - } - } - } - mult_numer.clear(); - mult_denom.clear(); - - // We have the data, now we need to convert to a jagged array and then use - // the initialize function to store it on the object. int_1dvec in_gmin(groups); int_1dvec in_gmax(groups); double_3dvec sparse_scatter(groups); double_2dvec sparse_mult(groups); - for (int gin = 0; gin < groups; gin++) { - // Find the minimum and maximum group boundaries - int gmin_; - for (gmin_ = 0; gmin_ < groups; gmin_++) { - bool non_zero = false; - for (int l = 0; l < this_matrix[gin][gmin_].size(); l++) { - if (this_matrix[gin][gmin_][l] != 0.) { - non_zero = true; - break; - } - } - if (non_zero) break; - } - int gmax_; - for (gmax_ = groups - 1; gmax_ >= 0; gmax_--) { - bool non_zero = false; - for (int l = 0; l < this_matrix[gin][gmax_].size(); l++) { - if (this_matrix[gin][gmax_][l] != 0.) { - non_zero = true; - break; - } - } - if (non_zero) break; - } - // treat the case of all values being 0 - if (gmin_ > gmax_) { - gmin_ = gin; - gmax_ = gin; - } - - // Store the group bounds - in_gmin[gin] = gmin_; - in_gmax[gin] = gmax_; - - // Store the data in the compressed format - sparse_scatter[gin].resize(gmax_ - gmin_ + 1); - sparse_mult[gin].resize(gmax_ - gmin_ + 1); - int i_gout = 0; - for (int gout = gmin_; gout <= gmax_; gout++) { - sparse_scatter[gin][i_gout] = this_matrix[gin][gout]; - sparse_mult[gin][i_gout] = this_mult[gin][gout]; - i_gout++; - } - } + // The rest of the steps do not depend on the type of angular representation + // so we use a base class method to sum up xs and create new energy and mult + // matrices + ScattData::generic_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, + sparse_mult, sparse_scatter); // Got everything we need, store it. init(in_gmin, in_gmax, sparse_mult, sparse_scatter); @@ -1060,6 +874,17 @@ void convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, int n_mu) { + // See if the user wants us to figure out how many points to use + if (n_mu == C_NONE) { + // then we will use 2 pts if its P0 or P1 (super fast), or the default if + // a higher order + if (leg.get_order() <= 1) { + n_mu = 2; + } else { + n_mu = DEFAULT_NMU; + } + } + tab.generic_init(n_mu, leg.gmin, leg.gmax, leg.energy, leg.mult); tab.scattxs = leg.scattxs; diff --git a/src/scattdata.h b/src/scattdata.h index e5f115e61..9b362b964 100644 --- a/src/scattdata.h +++ b/src/scattdata.h @@ -16,35 +16,42 @@ namespace openmc { -//============================================================================== -// SCATTDATA contains all the data needed to describe the scattering energy and -// angular distribution data -//============================================================================== // temporary declaations so we can name our friend functions class ScattDataLegendre; class ScattDataTabular; +//============================================================================== +// SCATTDATA contains all the data needed to describe the scattering energy and +// angular distribution data +//============================================================================== + class ScattData { protected: + void generic_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, + double_2dvec& in_energy, double_2dvec& in_mult); + void generic_combine(const int max_order, + const std::vector& those_scatts, + const double_1dvec& scalars, int_1dvec& in_gmin, + int_1dvec& in_gmax, double_2dvec& sparse_mult, + double_3dvec& sparse_scatter); + public: double_2dvec energy; // Normalized p0 matrix for sampling Eout double_2dvec mult; // nu-scatter multiplication (nu-scatt/scatt) double_3dvec dist; // Angular distribution int_1dvec gmin; // minimum outgoing group int_1dvec gmax; // maximum outgoing group - public: double_1dvec scattxs; // Isotropic Sigma_{s,g_{in}} - virtual double calc_f(int gin, int gout, double mu) = 0; - virtual void sample(int gin, int& gout, double& mu, double& wgt) = 0; + virtual double calc_f(const int gin, const int gout, const double mu) = 0; + virtual void sample(const int gin, int& gout, double& mu, double& wgt) = 0; virtual void init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, double_3dvec& coeffs) = 0; void sample_energy(int gin, int& gout, int& i_gout); - double get_xs(const int xstype, int gin, int* gout, double* mu); - void generic_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, - double_2dvec& in_energy, double_2dvec& in_mult); - virtual void combine(std::vector& those_scatts, - double_1dvec& scalars) = 0; + double get_xs(const int xstype, const int gin, const int* gout, + const double* mu); + virtual void combine(const std::vector& those_scatts, + const double_1dvec& scalars) = 0; virtual int get_order() = 0; - virtual double_3dvec get_matrix(int max_order) = 0; + virtual double_3dvec get_matrix(const int max_order) = 0; }; //============================================================================== @@ -61,11 +68,12 @@ class ScattDataLegendre: public ScattData { void init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, double_3dvec& coeffs); void update_max_val(); - double calc_f(int gin, int gout, double mu); - void sample(int gin, int& gout, double& mu, double& wgt); - void combine(std::vector& those_scatts, double_1dvec& scalars); + double calc_f(const int gin, const int gout, const double mu); + void sample(const int gin, int& gout, double& mu, double& wgt); + void combine(const std::vector& those_scatts, + const double_1dvec& scalars); int get_order() {return dist[0][0].size() - 1;}; - double_3dvec get_matrix(int max_order); + double_3dvec get_matrix(const int max_order); }; //============================================================================== @@ -81,11 +89,12 @@ class ScattDataHistogram: public ScattData { public: void init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, double_3dvec& coeffs); - double calc_f(int gin, int gout, double mu); - void sample(int gin, int& gout, double& mu, double& wgt); - void combine(std::vector& those_scatts, double_1dvec& scalars); + double calc_f(const int gin, const int gout, const double mu); + void sample(const int gin, int& gout, double& mu, double& wgt); + void combine(const std::vector& those_scatts, + const double_1dvec& scalars); int get_order() {return dist[0][0].size();}; - double_3dvec get_matrix(int max_order); + double_3dvec get_matrix(const int max_order); }; //============================================================================== @@ -103,11 +112,12 @@ class ScattDataTabular: public ScattData { public: void init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, double_3dvec& coeffs); - double calc_f(int gin, int gout, double mu); - void sample(int gin, int& gout, double& mu, double& wgt); - void combine(std::vector& those_scatts, double_1dvec& scalars); + double calc_f(const int gin, const int gout, const double mu); + void sample(const int gin, int& gout, double& mu, double& wgt); + void combine(const std::vector& those_scatts, + const double_1dvec& scalars); int get_order() {return dist[0][0].size();}; - double_3dvec get_matrix(int max_order); + double_3dvec get_matrix(const int max_order); }; //============================================================================== diff --git a/src/settings.F90 b/src/settings.F90 index 0ed51d119..599465160 100644 --- a/src/settings.F90 +++ b/src/settings.F90 @@ -36,7 +36,7 @@ module settings logical :: legendre_to_tabular = .true. ! Number of points to use in the Legendre to tabular conversion - integer(C_INT) :: legendre_to_tabular_points = 33 + integer(C_INT) :: legendre_to_tabular_points = C_NONE ! ============================================================================ ! SIMULATION VARIABLES diff --git a/src/xsdata.cpp b/src/xsdata.cpp index ddfe4c0fd..991e2a901 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -6,8 +6,9 @@ namespace openmc { // XsData class methods //============================================================================== -XsData::XsData(int energy_groups, int num_delayed_groups, bool fissionable, - int scatter_format, int n_pol, int n_azi) +XsData::XsData(const int energy_groups, const int num_delayed_groups, + const bool fissionable, const int scatter_format, + const int n_pol, const int n_azi) { // check to make sure scatter format is OK before we allocate if (scatter_format != ANGLE_HISTOGRAM && scatter_format != ANGLE_TABULAR && @@ -69,9 +70,10 @@ XsData::XsData(int energy_groups, int num_delayed_groups, bool fissionable, //============================================================================== void -XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, - int final_scatter_format, int order_data, int max_order, - int legendre_to_tabular_points, bool is_isotropic) +XsData::from_hdf5(const hid_t xsdata_grp, const bool fissionable, + const int scatter_format, const int final_scatter_format, + const int order_data, const int max_order, + const int legendre_to_tabular_points, const bool is_isotropic) { // Reconstruct the dimension information so it doesn't need to be passed int n_pol = total.size(); @@ -132,8 +134,9 @@ XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, //============================================================================== void -XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, - int energy_groups, int delayed_groups, bool is_isotropic) +XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, + const int n_azi, const int energy_groups, const int delayed_groups, + const bool is_isotropic) { // Get the fission and kappa_fission data xs; these are optional @@ -532,9 +535,10 @@ XsData::_fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, //============================================================================== void -XsData::_scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, - int energy_groups, int scatter_format, int final_scatter_format, - int order_data, int max_order, int legendre_to_tabular_points) +XsData::_scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, + const int n_azi, const int energy_groups, int scatter_format, + const int final_scatter_format, const int order_data, const int max_order, + const int legendre_to_tabular_points) { if (!object_exists(xsdata_grp, "scatter_data")) { fatal_error("Must provide scatter_data group!"); @@ -671,7 +675,8 @@ XsData::_scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, //============================================================================== void -XsData::combine(std::vector those_xs, double_1dvec& scalars) +XsData::combine(const std::vector those_xs, + const double_1dvec& scalars) { // Combine the non-scattering data for (int i = 0; i < those_xs.size(); i++) { diff --git a/src/xsdata.h b/src/xsdata.h index 3d6f1f7bb..4d3a5d5d4 100644 --- a/src/xsdata.h +++ b/src/xsdata.h @@ -24,9 +24,10 @@ namespace openmc { class XsData { private: - void _scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, - int energy_groups, int scatter_format, int final_scatter_format, - int order_data, int max_order, int legendre_to_tabular_points); + void _scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, + const int n_azi, const int energy_groups, int scatter_format, + const int final_scatter_format, const int order_data, + const int max_order, const int legendre_to_tabular_points); void _fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, int energy_groups, int delayed_groups, bool is_isotropic); public: @@ -55,12 +56,16 @@ class XsData { std::vector > scatter; XsData() = default; - XsData(int num_groups, int num_delayed_groups, bool fissionable, - int scatter_format, int n_pol, int n_azi); - void from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, - int final_scatter_format, int order_data, int max_order, - int legendre_to_tabular_points, bool is_isotropic); - void combine(std::vector those_xs, double_1dvec& scalars); + XsData(const int num_groups, const int num_delayed_groups, + const bool fissionable, const int scatter_format, const int n_pol, + const int n_azi); + void from_hdf5(const hid_t xsdata_grp, const bool fissionable, + const int scatter_format, const int final_scatter_format, + const int order_data, const int max_order, + const int legendre_to_tabular_points, + const bool is_isotropic); + void combine(const std::vector those_xs, + const double_1dvec& scalars); bool equiv(const XsData& that); }; From 35c62affe63e457413e3467cabd77cbd54c9a962 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sat, 16 Jun 2018 13:37:50 -0400 Subject: [PATCH 337/361] Replaced all polar and azimuthal indices with just a 1d angle index. Also fixed mg_max_order --- src/hdf5_interface.cpp | 22 ++ src/hdf5_interface.h | 4 + src/mgxs.cpp | 128 ++++---- src/mgxs.h | 12 +- src/scattdata.cpp | 6 +- src/xsdata.cpp | 690 ++++++++++++++++++----------------------- src/xsdata.h | 46 +-- 7 files changed, 431 insertions(+), 477 deletions(-) diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index 39e0162b5..a81f26e23 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -481,6 +481,28 @@ read_nd_vector(hid_t obj_id, const char* name, } +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector >& result, bool must_have) +{ + if (object_exists(obj_id, name)) { + int dim1 = result.size(); + int dim2 = result[0].size(); + std::vector temp_arr = std::vector(dim1 * dim2); + read_int(obj_id, name, &temp_arr[0], true); + + int temp_idx = 0; + for (int i = 0; i < dim1; i++) { + for (int j = 0; j < dim2; j++) { + result[i][j] = temp_arr[temp_idx++]; + } + } + } else if (must_have) { + fatal_error(std::string("Must provide " + std::string(name) + "!")); + } +} + + void read_nd_vector(hid_t obj_id, const char* name, std::vector > >& result, diff --git a/src/hdf5_interface.h b/src/hdf5_interface.h index 59a914f52..b00fe7c87 100644 --- a/src/hdf5_interface.h +++ b/src/hdf5_interface.h @@ -65,6 +65,10 @@ read_nd_vector(hid_t obj_id, const char* name, std::vector >& result, bool must_have = false); +void +read_nd_vector(hid_t obj_id, const char* name, + std::vector >& result, bool must_have = false); + void read_nd_vector(hid_t obj_id, const char* name, std::vector > >& result, diff --git a/src/mgxs.cpp b/src/mgxs.cpp index ab2970b2f..81a510e30 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -34,11 +34,10 @@ Mgxs::init(const std::string& in_name, const double in_awr, for (int thread = 0; thread < n_threads; thread++) { cache[thread].sqrtkT = 0.; cache[thread].t = 0; - cache[thread].p = 0; cache[thread].a = 0; - cache[thread].uvw[0] = 1.; - cache[thread].uvw[1] = 0.; - cache[thread].uvw[2] = 0.; + cache[thread].u = 0.; + cache[thread].v = 0.; + cache[thread].w = 0.; } } @@ -48,7 +47,7 @@ void Mgxs::_metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, const int in_num_delayed_groups, double_1dvec& temperature, int& method, const double tolerance, int_1dvec& temps_to_read, int& order_dim, - bool& is_isotropic, const int n_threads) + const int n_threads) { // get name char char_name[MAX_WORD_LEN]; @@ -179,7 +178,7 @@ Mgxs::_metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, fatal_error("Invalid scatter_shape option!"); } } - //TODO: do i even need this flag? - it should be easy to self-determine + bool in_fissionable = false; if (attribute_exists(xs_id, "fissionable")) { int int_fiss; @@ -264,9 +263,8 @@ Mgxs::from_hdf5(hid_t xs_id, const int energy_groups, // Call generic data gathering routine (will populate the metadata) int order_data; int_1dvec temps_to_read; - bool is_isotropic; _metadata_from_hdf5(xs_id, energy_groups, delayed_groups, temperature, - method, tolerance, temps_to_read, order_data, is_isotropic, n_threads); + method, tolerance, temps_to_read, order_data, n_threads); // Set number of energy and delayed groups int final_scatter_format = scatter_format; @@ -284,7 +282,7 @@ Mgxs::from_hdf5(hid_t xs_id, const int energy_groups, xs[t].from_hdf5(xsdata_grp, fissionable, scatter_format, final_scatter_format, order_data, max_order, - legendre_to_tabular_points, is_isotropic); + legendre_to_tabular_points, is_isotropic, n_pol, n_azi); close_group(xsdata_grp); } // end temperature loop @@ -419,45 +417,41 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, double val; switch(xstype) { case MG_GET_XS_TOTAL: - val = xs[cache[tid].t].total[cache[tid].p][cache[tid].a][gin]; + val = xs[cache[tid].t].total[cache[tid].a][gin]; break; - case MG_GET_XS_ABSORPTION: - val = xs[cache[tid].t].absorption[cache[tid].p][cache[tid].a][gin]; - break; - case MG_GET_XS_INVERSE_VELOCITY: - val = xs[cache[tid].t].inverse_velocity[cache[tid].p][cache[tid].a][gin]; - break; - case MG_GET_XS_DECAY_RATE: - if (dg != nullptr) { - val = xs[cache[tid].t].decay_rate[cache[tid].p][cache[tid].a][*dg + 1]; + case MG_GET_XS_NU_FISSION: + if (fissionable) { + val = xs[cache[tid].t].nu_fission[cache[tid].a][gin]; } else { - val = xs[cache[tid].t].decay_rate[cache[tid].p][cache[tid].a][0]; + val = 0.; } break; - case MG_GET_XS_SCATTER: - case MG_GET_XS_SCATTER_MULT: - case MG_GET_XS_SCATTER_FMU_MULT: - case MG_GET_XS_SCATTER_FMU: - val = xs[cache[tid].t].scatter[cache[tid].p] - [cache[tid].a]->get_xs(xstype, gin, gout, mu); + case MG_GET_XS_ABSORPTION: + val = xs[cache[tid].t].absorption[cache[tid].a][gin]; break; case MG_GET_XS_FISSION: if (fissionable) { - val = xs[cache[tid].t].fission[cache[tid].p][cache[tid].a][gin]; + val = xs[cache[tid].t].fission[cache[tid].a][gin]; } else { val = 0.; } break; case MG_GET_XS_KAPPA_FISSION: if (fissionable) { - val = xs[cache[tid].t].kappa_fission[cache[tid].p][cache[tid].a][gin]; + val = xs[cache[tid].t].kappa_fission[cache[tid].a][gin]; } else { val = 0.; } break; + case MG_GET_XS_SCATTER: + case MG_GET_XS_SCATTER_MULT: + case MG_GET_XS_SCATTER_FMU_MULT: + case MG_GET_XS_SCATTER_FMU: + val = xs[cache[tid].t].scatter[cache[tid].a]->get_xs(xstype, gin, gout, mu); + break; case MG_GET_XS_PROMPT_NU_FISSION: if (fissionable) { - val = xs[cache[tid].t].prompt_nu_fission[cache[tid].p][cache[tid].a][gin]; + val = xs[cache[tid].t].prompt_nu_fission[cache[tid].a][gin]; } else { val = 0.; } @@ -465,10 +459,10 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, case MG_GET_XS_DELAYED_NU_FISSION: if (fissionable) { if (dg != nullptr) { - val = xs[cache[tid].t].delayed_nu_fission[cache[tid].p][cache[tid].a][gin][*dg]; + val = xs[cache[tid].t].delayed_nu_fission[cache[tid].a][gin][*dg]; } else { val = 0.; - for (auto& num : xs[cache[tid].t].delayed_nu_fission[cache[tid].p] + for (auto& num : xs[cache[tid].t].delayed_nu_fission [cache[tid].a][gin]) { val += num; } @@ -477,21 +471,14 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, val = 0.; } break; - case MG_GET_XS_NU_FISSION: - if (fissionable) { - val = xs[cache[tid].t].nu_fission[cache[tid].p][cache[tid].a][gin]; - } else { - val = 0.; - } - break; case MG_GET_XS_CHI_PROMPT: if (fissionable) { if (gout != nullptr) { - val = xs[cache[tid].t].chi_prompt[cache[tid].p][cache[tid].a][gin][*gout]; + val = xs[cache[tid].t].chi_prompt[cache[tid].a][gin][*gout]; } else { // provide an outgoing group-wise sum val = 0.; - for (auto& num : xs[cache[tid].t].chi_prompt[cache[tid].p][cache[tid].a][gin]) { + for (auto& num : xs[cache[tid].t].chi_prompt[cache[tid].a][gin]) { val += num; } } @@ -503,22 +490,22 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, if (fissionable) { if (gout != nullptr) { if (dg != nullptr) { - val = xs[cache[tid].t].chi_delayed[cache[tid].p][cache[tid].a][gin][*gout][*dg]; + val = xs[cache[tid].t].chi_delayed[cache[tid].a][gin][*gout][*dg]; } else { - val = xs[cache[tid].t].chi_delayed[cache[tid].p][cache[tid].a][gin][*gout][0]; + val = xs[cache[tid].t].chi_delayed[cache[tid].a][gin][*gout][0]; } } else { if (dg != nullptr) { val = 0.; - for (int i = 0; i < xs[cache[tid].t].chi_delayed[cache[tid].p] + for (int i = 0; i < xs[cache[tid].t].chi_delayed [cache[tid].a][gin].size(); i++) { - val += xs[cache[tid].t].chi_delayed[cache[tid].p][cache[tid].a][gin][i][*dg]; + val += xs[cache[tid].t].chi_delayed[cache[tid].a][gin][i][*dg]; } } else { val = 0.; - for (int i = 0; i < xs[cache[tid].t].chi_delayed[cache[tid].p] + for (int i = 0; i < xs[cache[tid].t].chi_delayed [cache[tid].a][gin].size(); i++) { - for (auto& num : xs[cache[tid].t].chi_delayed[cache[tid].p] + for (auto& num : xs[cache[tid].t].chi_delayed [cache[tid].a][gin][i]) { val += num; } @@ -529,6 +516,16 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, val = 0.; } break; + case MG_GET_XS_INVERSE_VELOCITY: + val = xs[cache[tid].t].inverse_velocity[cache[tid].a][gin]; + break; + case MG_GET_XS_DECAY_RATE: + if (dg != nullptr) { + val = xs[cache[tid].t].decay_rate[cache[tid].a][*dg + 1]; + } else { + val = xs[cache[tid].t].decay_rate[cache[tid].a][0]; + } + break; default: val = 0.; } @@ -541,11 +538,11 @@ void Mgxs::sample_fission_energy(const int tid, const int gin, int& dg, int& gout) { // This method assumes that the temperature and angle indices are set - double nu_fission = xs[cache[tid].t].nu_fission[cache[tid].p][cache[tid].a][gin]; + double nu_fission = xs[cache[tid].t].nu_fission[cache[tid].a][gin]; // Find the probability of having a prompt neutron double prob_prompt = - xs[cache[tid].t].prompt_nu_fission[cache[tid].p][cache[tid].a][gin]; + xs[cache[tid].t].prompt_nu_fission[cache[tid].a][gin]; // sample random numbers double xi_pd = prn() * nu_fission; @@ -561,10 +558,10 @@ Mgxs::sample_fission_energy(const int tid, const int gin, int& dg, int& gout) // sample the outgoing energy group gout = 0; double prob_gout = - xs[cache[tid].t].chi_prompt[cache[tid].p][cache[tid].a][gin][gout]; + xs[cache[tid].t].chi_prompt[cache[tid].a][gin][gout]; while (prob_gout < xi_gout) { gout++; - prob_gout += xs[cache[tid].t].chi_prompt[cache[tid].p][cache[tid].a][gin][gout]; + prob_gout += xs[cache[tid].t].chi_prompt[cache[tid].a][gin][gout]; } } else { @@ -575,7 +572,7 @@ Mgxs::sample_fission_energy(const int tid, const int gin, int& dg, int& gout) while (xi_pd >= prob_prompt) { dg++; prob_prompt += - xs[cache[tid].t].delayed_nu_fission[cache[tid].p][cache[tid].a][gin][dg]; + xs[cache[tid].t].delayed_nu_fission[cache[tid].a][gin][dg]; } // adjust dg in case of round-off error @@ -584,11 +581,11 @@ Mgxs::sample_fission_energy(const int tid, const int gin, int& dg, int& gout) // sample the outgoing energy group gout = 0; double prob_gout = - xs[cache[tid].t].chi_delayed[cache[tid].p][cache[tid].a][gin][gout][dg]; + xs[cache[tid].t].chi_delayed[cache[tid].a][gin][gout][dg]; while (prob_gout < xi_gout) { gout++; prob_gout += - xs[cache[tid].t].chi_delayed[cache[tid].p][cache[tid].a][gin][gout][dg]; + xs[cache[tid].t].chi_delayed[cache[tid].a][gin][gout][dg]; } } } @@ -601,7 +598,7 @@ Mgxs::sample_scatter(const int tid, const int gin, int& gout, double& mu, { // This method assumes that the temperature and angle indices are set // Sample the data - xs[cache[tid].t].scatter[cache[tid].p][cache[tid].a]->sample(gin, gout, mu, wgt); + xs[cache[tid].t].scatter[cache[tid].a]->sample(gin, gout, mu, wgt); } //============================================================================== @@ -613,11 +610,11 @@ Mgxs::calculate_xs(const int tid, const int gin, const double sqrtkT, // Set our indices set_temperature_index(tid, sqrtkT); set_angle_index(tid, uvw); - total_xs = xs[cache[tid].t].total[cache[tid].p][cache[tid].a][gin]; - abs_xs = xs[cache[tid].t].absorption[cache[tid].p][cache[tid].a][gin]; + total_xs = xs[cache[tid].t].total[cache[tid].a][gin]; + abs_xs = xs[cache[tid].t].absorption[cache[tid].a][gin]; if (fissionable) { - nu_fiss_xs = xs[cache[tid].t].nu_fission[cache[tid].p][cache[tid].a][gin]; + nu_fiss_xs = xs[cache[tid].t].nu_fission[cache[tid].a][gin]; } else { nu_fiss_xs = 0.; } @@ -670,22 +667,25 @@ void Mgxs::set_angle_index(const int tid, const double uvw[3]) { // See if we need to find the new index - if ((uvw[0] != cache[tid].uvw[0]) || (uvw[1] != cache[tid].uvw[1]) || - (uvw[2] != cache[tid].uvw[2])) { + if (!is_isotropic && + ((uvw[0] != cache[tid].u) || (uvw[1] != cache[tid].v) || + (uvw[2] != cache[tid].w))) { // convert uvw to polar and azimuthal angles double my_pol = std::acos(uvw[2]); double my_azi = std::atan2(uvw[1], uvw[0]); // Find the location, assuming equal-bin angles double delta_angle = PI / n_pol; - cache[tid].p = std::floor(my_pol / delta_angle); + int p = std::floor(my_pol / delta_angle); delta_angle = 2. * PI / n_azi; - cache[tid].a = std::floor((my_azi + PI) / delta_angle); + int a = std::floor((my_azi + PI) / delta_angle); + + cache[tid].a = n_azi * p + a; // store this direction as the last one used - cache[tid].uvw[0] = uvw[0]; - cache[tid].uvw[1] = uvw[1]; - cache[tid].uvw[2] = uvw[2]; + cache[tid].u = uvw[0]; + cache[tid].v = uvw[1]; + cache[tid].w = uvw[2]; } } diff --git a/src/mgxs.h b/src/mgxs.h index d7b9a814a..2a79bfe2a 100644 --- a/src/mgxs.h +++ b/src/mgxs.h @@ -29,9 +29,11 @@ namespace openmc { struct CacheData { double sqrtkT; // last temperature corresponding to t int t; // temperature index - int p; // polar angle index - int a; // azimuthal angle index - double uvw[3]; // last angle that corresponds to p and a + int a; // angle index + // last angle that corresponds to p and a + double u; + double v; + double w; }; //============================================================================== @@ -45,6 +47,8 @@ class Mgxs { int num_delayed_groups; // number of delayed neutron groups int num_groups; // number of energy groups std::vector xs; // Cross section data + // MGXS Incoming Flux Angular grid information + bool is_isotropic; // used to skip search for angle indices if isotropic int n_pol; int n_azi; double_1dvec polar; @@ -52,7 +56,7 @@ class Mgxs { void _metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, const int in_num_delayed_groups, double_1dvec& temperature, int& method, const double tolerance, int_1dvec& temps_to_read, - int& order_dim, bool& is_isotropic, const int n_threads); + int& order_dim, const int n_threads); bool equiv(const Mgxs& that); public: diff --git a/src/scattdata.cpp b/src/scattdata.cpp index c0515f1ba..e321bc275 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -876,9 +876,9 @@ convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, { // See if the user wants us to figure out how many points to use if (n_mu == C_NONE) { - // then we will use 2 pts if its P0 or P1 (super fast), or the default if - // a higher order - if (leg.get_order() <= 1) { + // then we will use 2 pts if its P0, or the default if a higher order + // TODO use an error minimization algorithm that also picks n_mu + if (leg.get_order() == 0) { n_mu = 2; } else { n_mu = DEFAULT_NMU; diff --git a/src/xsdata.cpp b/src/xsdata.cpp index 991e2a901..05e6ab01b 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -10,59 +10,49 @@ XsData::XsData(const int energy_groups, const int num_delayed_groups, const bool fissionable, const int scatter_format, const int n_pol, const int n_azi) { + int n_ang = n_pol * n_azi; + // check to make sure scatter format is OK before we allocate if (scatter_format != ANGLE_HISTOGRAM && scatter_format != ANGLE_TABULAR && scatter_format != ANGLE_LEGENDRE) { fatal_error("Invalid scatter_format!"); } // allocate all [temperature][phi][theta][in group] quantities - total = double_3dvec(n_pol, double_2dvec(n_azi, - double_1dvec(energy_groups, 0.))); - absorption = double_3dvec(n_pol, double_2dvec(n_azi, - double_1dvec(energy_groups, 0.))); - inverse_velocity = double_3dvec(n_pol, - double_2dvec(n_azi, double_1dvec(energy_groups, 0.))); + total = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + absorption = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + inverse_velocity = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); if (fissionable) { - fission = double_3dvec(n_pol, double_2dvec(n_azi, - double_1dvec(energy_groups, 0.))); - nu_fission = double_3dvec(n_pol, double_2dvec(n_azi, - double_1dvec(energy_groups, 0.))); - prompt_nu_fission = double_3dvec(n_pol, double_2dvec(n_azi, - double_1dvec(energy_groups, 0.))); - kappa_fission = double_3dvec(n_pol, double_2dvec(n_azi, - double_1dvec(energy_groups, 0.))); + fission = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + nu_fission = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + prompt_nu_fission = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); + kappa_fission = double_2dvec(n_ang, double_1dvec(energy_groups, 0.)); } // allocate decay_rate; [temperature][phi][theta][delayed group] - decay_rate = double_3dvec(n_pol, double_2dvec(n_azi, - double_1dvec(num_delayed_groups, 0.))); + decay_rate = double_2dvec(n_ang, double_1dvec(num_delayed_groups, 0.)); if (fissionable) { // allocate delayed_nu_fission; [temperature][phi][theta][in group][delay group] - delayed_nu_fission = double_4dvec(n_pol, double_3dvec(n_azi, - double_2dvec(energy_groups, double_1dvec(num_delayed_groups, 0.)))); + delayed_nu_fission = double_3dvec(n_ang, double_2dvec(energy_groups, + double_1dvec(num_delayed_groups, 0.))); // chi_prompt; [temperature][phi][theta][in group][delayed group] - chi_prompt = double_4dvec(n_pol, double_3dvec(n_azi, - double_2dvec(energy_groups, double_1dvec(energy_groups, 0.)))); + chi_prompt = double_3dvec(n_ang, double_2dvec(energy_groups, + double_1dvec(energy_groups, 0.))); // chi_delayed; [temperature][phi][theta][in group][out group][delay group] - chi_delayed = double_5dvec(n_pol, double_4dvec(n_azi, - double_3dvec(energy_groups, double_2dvec(energy_groups, - double_1dvec(num_delayed_groups, 0.))))); + chi_delayed = double_4dvec(n_ang, double_3dvec(energy_groups, + double_2dvec(energy_groups, double_1dvec(num_delayed_groups, 0.)))); } - scatter.resize(n_pol); - for (int p = 0; p < n_pol; p++) { - scatter[p].resize(n_azi); - for (int a = 0; a < n_azi; a++) { - if (scatter_format == ANGLE_HISTOGRAM) { - scatter[p][a] = new ScattDataHistogram; - } else if (scatter_format == ANGLE_TABULAR) { - scatter[p][a] = new ScattDataTabular; - } else if (scatter_format == ANGLE_LEGENDRE) { - scatter[p][a] = new ScattDataLegendre; - } + scatter.resize(n_ang); + for (int a = 0; a < n_ang; a++) { + if (scatter_format == ANGLE_HISTOGRAM) { + scatter[a] = new ScattDataHistogram; + } else if (scatter_format == ANGLE_TABULAR) { + scatter[a] = new ScattDataTabular; + } else if (scatter_format == ANGLE_LEGENDRE) { + scatter[a] = new ScattDataLegendre; } } } @@ -73,13 +63,13 @@ void XsData::from_hdf5(const hid_t xsdata_grp, const bool fissionable, const int scatter_format, const int final_scatter_format, const int order_data, const int max_order, - const int legendre_to_tabular_points, const bool is_isotropic) + const int legendre_to_tabular_points, const bool is_isotropic, + const int n_pol, const int n_azi) { // Reconstruct the dimension information so it doesn't need to be passed - int n_pol = total.size(); - int n_azi = total[0].size(); - int energy_groups = total[0][0].size(); - int delayed_groups = decay_rate[0][0].size(); + int n_ang = n_pol * n_azi; + int energy_groups = total[0].size(); + int delayed_groups = decay_rate[0].size(); // Set the fissionable-specific data if (fissionable) { @@ -98,11 +88,9 @@ XsData::from_hdf5(const hid_t xsdata_grp, const bool fissionable, // Check absorption to ensure it is not 0 since it is often the // denominator in tally methods - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - if (absorption[p][a][gin] == 0.) absorption[p][a][gin] = 1.e-10; - } + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + if (absorption[a][gin] == 0.) absorption[a][gin] = 1.e-10; } } @@ -110,23 +98,17 @@ XsData::from_hdf5(const hid_t xsdata_grp, const bool fissionable, if (object_exists(xsdata_grp, "total")) { read_nd_vector(xsdata_grp, "total", total); } else { - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - total[p][a][gin] = absorption[p][a][gin] + - scatter[p][a]->scattxs[gin]; - } + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + total[a][gin] = absorption[a][gin] + scatter[a]->scattxs[gin]; } } } - // Check total to ensure it is not 0 since it is often the denominator in - // tally methods - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - if (total[p][a][gin] == 0.) total[p][a][gin] = 1.e-10; - } + // Fix if total is 0, since it is in the denominator when tallying + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + if (total[a][gin] == 0.) total[a][gin] = 1.e-10; } } } @@ -138,15 +120,14 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, const int n_azi, const int energy_groups, const int delayed_groups, const bool is_isotropic) { - + int n_ang = n_pol * n_azi; // Get the fission and kappa_fission data xs; these are optional read_nd_vector(xsdata_grp, "fission", fission); read_nd_vector(xsdata_grp, "kappa-fission", kappa_fission); // Set/get beta - double_4dvec temp_beta = - double_4dvec(n_pol, double_3dvec(n_azi, - double_2dvec(energy_groups, double_1dvec(delayed_groups, 0.)))); + double_3dvec temp_beta =double_3dvec(n_ang, double_2dvec(energy_groups, + double_1dvec(delayed_groups, 0.))); if (object_exists(xsdata_grp, "beta")) { hid_t xsdata = open_dataset(xsdata_grp, "beta"); int ndims = dataset_ndims(xsdata); @@ -160,14 +141,12 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, // Broadcast to all incoming groups int temp_idx = 0; - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int dg = 0; dg < delayed_groups; dg++) { - // Set the first group index and copy the rest - temp_beta[p][a][0][dg] = temp_arr[temp_idx++]; - for (int gin = 1; gin < energy_groups; gin++) { - temp_beta[p][a][gin] = temp_beta[p][a][0]; - } + for (int a = 0; a < n_ang; a++) { + for (int dg = 0; dg < delayed_groups; dg++) { + // Set the first group index and copy the rest + temp_beta[a][0][dg] = temp_arr[temp_idx++]; + for (int gin = 1; gin < energy_groups; gin++) { + temp_beta[a][gin] = temp_beta[a][0]; } } } @@ -181,41 +160,38 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, // If chi is provided, set chi-prompt and chi-delayed if (object_exists(xsdata_grp, "chi")) { - double_3dvec temp_arr = double_3dvec(n_pol, double_2dvec(n_azi, - double_1dvec(energy_groups))); + double_2dvec temp_arr = double_2dvec(n_ang, double_1dvec(energy_groups)); read_nd_vector(xsdata_grp, "chi", temp_arr); - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - // First set the first group + for (int a = 0; a < n_ang; a++) { + // First set the first group + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[a][0][gout] = temp_arr[a][gout]; + } + + // Now normalize this data + double chi_sum = std::accumulate(chi_prompt[a][0].begin(), + chi_prompt[a][0].end(), + 0.); + if (chi_sum <= 0.) { + fatal_error("Encountered chi for a group that is <= 0!"); + } + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[a][0][gout] /= chi_sum; + } + + // And extend to the remaining incoming groups + for (int gin = 1; gin < energy_groups; gin++) { + chi_prompt[a][gin] = chi_prompt[a][0]; + } + + // Finally set chi-delayed equal to chi-prompt + // Set chi-delayed to chi-prompt + for(int gin = 0; gin < energy_groups; gin++) { for (int gout = 0; gout < energy_groups; gout++) { - chi_prompt[p][a][0][gout] = temp_arr[p][a][gout]; - } - - // Now normalize this data - double chi_sum = std::accumulate(chi_prompt[p][a][0].begin(), - chi_prompt[p][a][0].end(), - 0.); - if (chi_sum <= 0.) { - fatal_error("Encountered chi for a group that is <= 0!"); - } - for (int gout = 0; gout < energy_groups; gout++) { - chi_prompt[p][a][0][gout] /= chi_sum; - } - - // And extend to the remaining incoming groups - for (int gin = 1; gin < energy_groups; gin++) { - chi_prompt[p][a][gin] = chi_prompt[p][a][0]; - } - - // Finally set chi-delayed equal to chi-prompt - // Set chi-delayed to chi-prompt - for(int gin = 0; gin < energy_groups; gin++) { - for (int gout = 0; gout < energy_groups; gout++) { - for (int dg = 0; dg < delayed_groups; dg++) { - chi_delayed[p][a][gin][gout][dg] = - chi_prompt[p][a][gin][gout]; - } + for (int dg = 0; dg < delayed_groups; dg++) { + chi_delayed[a][gin][gout][dg] = + chi_prompt[a][gin][gout]; } } } @@ -234,21 +210,18 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, read_nd_vector(xsdata_grp, "nu-fission", prompt_nu_fission); // set delayed-nu-fission and correct prompt-nu-fission with beta - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - for (int dg = 0; dg < delayed_groups; dg++) { - delayed_nu_fission[p][a][gin][dg] = - temp_beta[p][a][gin][dg] * - prompt_nu_fission[p][a][gin]; - } + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + for (int dg = 0; dg < delayed_groups; dg++) { + delayed_nu_fission[a][gin][dg] = + temp_beta[a][gin][dg] * prompt_nu_fission[a][gin]; + } - // Correct the prompt-nu-fission using the delayed neutron fraction - if (delayed_groups > 0) { - double beta_sum = std::accumulate(temp_beta[p][a][gin].begin(), - temp_beta[p][a][gin].end(), 0.); - prompt_nu_fission[p][a][gin] *= (1. - beta_sum); - } + // Correct the prompt-nu-fission using the delayed neutron fraction + if (delayed_groups > 0) { + double beta_sum = std::accumulate(temp_beta[a][gin].begin(), + temp_beta[a][gin].end(), 0.); + prompt_nu_fission[a][gin] *= (1. - beta_sum); } } } @@ -258,47 +231,45 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, read_nd_vector(xsdata_grp, "nu-fission", chi_prompt); // Normalize the chi info so the CDF is 1. - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - double chi_sum = std::accumulate(chi_prompt[p][a][gin].begin(), - chi_prompt[p][a][gin].end(), 0.); - // Set the vector nu-fission from the matrix nu-fission - prompt_nu_fission[p][a][gin] = chi_sum; + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + double chi_sum = std::accumulate(chi_prompt[a][gin].begin(), + chi_prompt[a][gin].end(), 0.); + // Set the vector nu-fission from the matrix nu-fission + prompt_nu_fission[a][gin] = chi_sum; - if (chi_sum >= 0.) { - for (int gout = 0; gout < energy_groups; gout++) { - chi_prompt[p][a][gin][gout] /= chi_sum; - } - } else { - fatal_error("Encountered chi for a group that is <= 0!"); - } - } - - // set chi-delayed to chi-prompt - for (int gin = 0; gin < energy_groups; gin++) { + if (chi_sum >= 0.) { for (int gout = 0; gout < energy_groups; gout++) { - for (int dg = 0; dg < delayed_groups; dg++) { - chi_delayed[p][a][gin][gout][dg] = - chi_prompt[p][a][gin][gout]; - } + chi_prompt[a][gin][gout] /= chi_sum; + } + } else { + fatal_error("Encountered chi for a group that is <= 0!"); + } + } + + // set chi-delayed to chi-prompt + for (int gin = 0; gin < energy_groups; gin++) { + for (int gout = 0; gout < energy_groups; gout++) { + for (int dg = 0; dg < delayed_groups; dg++) { + chi_delayed[a][gin][gout][dg] = + chi_prompt[a][gin][gout]; } } + } - // Set the delayed-nu-fission and correct prompt-nu-fission with beta - for (int gin = 0; gin < energy_groups; gin++) { - for (int dg = 0; dg < delayed_groups; dg++) { - delayed_nu_fission[p][a][gin][dg] = - temp_beta[p][a][gin][dg] * - prompt_nu_fission[p][a][gin]; - } + // Set the delayed-nu-fission and correct prompt-nu-fission with beta + for (int gin = 0; gin < energy_groups; gin++) { + for (int dg = 0; dg < delayed_groups; dg++) { + delayed_nu_fission[a][gin][dg] = + temp_beta[a][gin][dg] * + prompt_nu_fission[a][gin]; + } - // Correct prompt-nu-fission using the delayed neutron fraction - if (delayed_groups > 0) { - double beta_sum = std::accumulate(temp_beta[p][a][gin].begin(), - temp_beta[p][a][gin].end(), 0.); - prompt_nu_fission[p][a][gin] *= (1. - beta_sum); - } + // Correct prompt-nu-fission using the delayed neutron fraction + if (delayed_groups > 0) { + double beta_sum = std::accumulate(temp_beta[a][gin].begin(), + temp_beta[a][gin].end(), 0.); + prompt_nu_fission[a][gin] *= (1. - beta_sum); } } } @@ -311,27 +282,24 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, // If chi-prompt is provided, set chi-prompt if (object_exists(xsdata_grp, "chi-prompt")) { - double_3dvec temp_arr = double_3dvec(n_pol, double_2dvec(n_azi, - double_1dvec(energy_groups))); + double_2dvec temp_arr = double_2dvec(n_ang, double_1dvec(energy_groups)); read_nd_vector(xsdata_grp, "chi-prompt", temp_arr); - for (int a = 0; a < n_azi; a++) { - for (int p = 0; p < n_pol; p++) { - for (int gin = 0; gin < energy_groups; gin++) { - for (int gout = 0; gout < energy_groups; gout++) { - chi_prompt[p][a][gin][gout] = temp_arr[p][a][gout]; - } + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[a][gin][gout] = temp_arr[a][gout]; + } - // Normalize chi so its CDF goes to 1 - double chi_sum = std::accumulate(chi_prompt[p][a][gin].begin(), - chi_prompt[p][a][gin].end(), 0.); - if (chi_sum >= 0.) { - for (int gout = 0; gout < energy_groups; gout++) { - chi_prompt[p][a][gin][gout] /= chi_sum; - } - } else { - fatal_error("Encountered chi-prompt for a group that is <= 0.!"); + // Normalize chi so its CDF goes to 1 + double chi_sum = std::accumulate(chi_prompt[a][gin].begin(), + chi_prompt[a][gin].end(), 0.); + if (chi_sum >= 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[a][gin][gout] /= chi_sum; } + } else { + fatal_error("Encountered chi-prompt for a group that is <= 0.!"); } } } @@ -346,26 +314,22 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, if (ndims == 3) { // chi-delayed is a [in group] vector - double_3dvec temp_arr = double_3dvec(n_pol, double_2dvec(n_azi, - double_1dvec(energy_groups))); + double_2dvec temp_arr = double_2dvec(n_ang, double_1dvec(energy_groups)); read_nd_vector(xsdata_grp, "chi-delayed", temp_arr); - for (int a = 0; a < n_azi; a++) { - for (int p = 0; p < n_pol; p++) { - // normalize the chi CDF to 1 - double chi_sum = std::accumulate(temp_arr[p][a].begin(), - temp_arr[p][a].end(), 0.); - if (chi_sum <= 0.) { - fatal_error("Encountered chi-delayed for a group that is <= 0!"); - } + for (int a = 0; a < n_ang; a++) { + // normalize the chi CDF to 1 + double chi_sum = std::accumulate(temp_arr[a].begin(), + temp_arr[a].end(), 0.); + if (chi_sum <= 0.) { + fatal_error("Encountered chi-delayed for a group that is <= 0!"); + } - // set chi-delayed - for (int gin = 0; gin < energy_groups; gin++) { - for (int gout = 0; gout < energy_groups; gout++) { - for (int dg = 0; dg < delayed_groups; dg++) { - chi_delayed[p][a][gin][gout][dg] = - temp_arr[p][a][gout] / chi_sum; - } + // set chi-delayed + for (int gin = 0; gin < energy_groups; gin++) { + for (int gout = 0; gout < energy_groups; gout++) { + for (int dg = 0; dg < delayed_groups; dg++) { + chi_delayed[a][gin][gout][dg] = temp_arr[a][gout] / chi_sum; } } } @@ -375,22 +339,20 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, read_nd_vector(xsdata_grp, "chi-delayed", chi_delayed); // Normalize the chi info so the CDF is 1. - for (int a = 0; a < n_azi; a++) { - for (int p = 0; p < n_pol; p++) { - for (int dg = 0; dg < delayed_groups; dg++) { - for (int gin = 0; gin < energy_groups; gin++) { - double chi_sum = 0.; - for (int gout = 0; gout < energy_groups; gout++) { - chi_sum += chi_delayed[p][a][gin][gout][dg]; - } + for (int a = 0; a < n_ang; a++) { + for (int dg = 0; dg < delayed_groups; dg++) { + for (int gin = 0; gin < energy_groups; gin++) { + double chi_sum = 0.; + for (int gout = 0; gout < energy_groups; gout++) { + chi_sum += chi_delayed[a][gin][gout][dg]; + } - if (chi_sum > 0.) { - for (int gout = 0; gout < energy_groups; gout++) { - chi_delayed[p][a][gin][gout][dg] /= chi_sum; - } - } else { - fatal_error("Encountered chi-delayed for a group that is <= 0!"); + if (chi_sum > 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_delayed[a][gin][gout][dg] /= chi_sum; } + } else { + fatal_error("Encountered chi-delayed for a group that is <= 0!"); } } } @@ -414,30 +376,27 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, } else if (ndims == 4) { // prompt nu fission is a matrix, // so set prompt_nu_fiss & chi_prompt - double_4dvec temp_arr = double_4dvec(n_pol, double_3dvec(n_azi, - double_2dvec(energy_groups, double_1dvec(energy_groups)))); + double_3dvec temp_arr = double_3dvec(n_ang, double_2dvec(energy_groups, + double_1dvec(energy_groups))); read_nd_vector(xsdata_grp, "prompt-nu-fission", temp_arr); // The prompt_nu_fission vector from the matrix form - for (int a = 0; a < n_azi; a++) { - for (int p = 0; p < n_pol; p++) { - for (int gin = 0; gin < energy_groups; gin++) { - double prompt_sum = std::accumulate(temp_arr[p][a][gin].begin(), - temp_arr[p][a][gin].end(), 0.); - prompt_nu_fission[p][a][gin] = prompt_sum; - } + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + double prompt_sum = std::accumulate(temp_arr[a][gin].begin(), + temp_arr[a][gin].end(), 0.); + prompt_nu_fission[a][gin] = prompt_sum; + } - // The chi_prompt data is just the normalized fission matrix - for (int gin= 0; gin < energy_groups; gin++) { - if (prompt_nu_fission[p][a][gin] > 0.) { - for (int gout = 0; gout < energy_groups; gout++) { - chi_prompt[p][a][gin][gout] = - temp_arr[p][a][gin][gout] / - prompt_nu_fission[p][a][gin]; - } - } else { - fatal_error("Encountered chi-prompt for a group that is <= 0!"); + // The chi_prompt data is just the normalized fission matrix + for (int gin= 0; gin < energy_groups; gin++) { + if (prompt_nu_fission[a][gin] > 0.) { + for (int gout = 0; gout < energy_groups; gout++) { + chi_prompt[a][gin][gout] = + temp_arr[a][gin][gout] / prompt_nu_fission[a][gin]; } + } else { + fatal_error("Encountered chi-prompt for a group that is <= 0!"); } } } @@ -455,23 +414,20 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, if (is_isotropic) ndims += 2; if (ndims == 3) { - // delayed-nu-fission is a [in group] vector - if (temp_beta[0][0][0][0] == 0.) { + // delayed-nu-fission is an [in group] vector + if (temp_beta[0][0][0] == 0.) { fatal_error("cannot set delayed-nu-fission with a 1D array if " "beta is not provided"); } - double_3dvec temp_arr = double_3dvec(n_pol, double_2dvec(n_azi, - double_1dvec(energy_groups))); + double_2dvec temp_arr = double_2dvec(n_ang, double_1dvec(energy_groups)); read_nd_vector(xsdata_grp, "delayed-nu-fission", temp_arr); - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - for (int dg = 0; dg < delayed_groups; dg++) { - // Set delayed-nu-fission using beta - delayed_nu_fission[p][a][gin][dg] = - temp_beta[p][a][gin][dg] * temp_arr[p][a][gin]; - } + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + for (int dg = 0; dg < delayed_groups; dg++) { + // Set delayed-nu-fission using beta + delayed_nu_fission[a][gin][dg] = + temp_beta[a][gin][dg] * temp_arr[a][gin]; } } } @@ -482,32 +438,28 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, } else if (ndims == 5) { // This will contain delayed-nu-fision and chi-delayed data - double_5dvec temp_arr = double_5dvec(n_pol, double_4dvec(n_azi, - double_3dvec(energy_groups, double_2dvec(energy_groups, - double_1dvec(delayed_groups))))); + double_4dvec temp_arr = double_4dvec(n_ang, double_3dvec(energy_groups, + double_2dvec(energy_groups, double_1dvec(delayed_groups)))); read_nd_vector(xsdata_grp, "delayed-nu-fission", temp_arr); - // Set the 4D delayed-nu-fission matrix and 5D chi-delayed matrix - // from the 5D delayed-nu-fission matrix - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int dg = 0; dg < delayed_groups; dg++) { - for (int gin = 0; gin < energy_groups; gin++) { - double gout_sum = 0.; + // Set the 3D delayed-nu-fission matrix and 4D chi-delayed matrix + // from the 4D delayed-nu-fission matrix + for (int a = 0; a < n_ang; a++) { + for (int dg = 0; dg < delayed_groups; dg++) { + for (int gin = 0; gin < energy_groups; gin++) { + double gout_sum = 0.; + for (int gout = 0; gout < energy_groups; gout++) { + gout_sum += temp_arr[a][gin][gout][dg]; + chi_delayed[a][gin][gout][dg] = temp_arr[a][gin][gout][dg]; + } + delayed_nu_fission[a][gin][dg] = gout_sum; + // Normalize chi-delayed + if (gout_sum > 0.) { for (int gout = 0; gout < energy_groups; gout++) { - gout_sum += temp_arr[p][a][gin][gout][dg]; - chi_delayed[p][a][gin][gout][dg] = - temp_arr[p][a][gin][gout][dg]; - } - delayed_nu_fission[p][a][gin][dg] = gout_sum; - // Normalize chi-delayed - if (gout_sum > 0.) { - for (int gout = 0; gout < energy_groups; gout++) { - chi_delayed[p][a][gin][gout][dg] /= gout_sum; - } - } else { - fatal_error("Encountered chi-delayed for a group that is <= 0!"); + chi_delayed[a][gin][gout][dg] /= gout_sum; } + } else { + fatal_error("Encountered chi-delayed for a group that is <= 0!"); } } } @@ -520,14 +472,12 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, } // Combine prompt_nu_fission and delayed_nu_fission into nu_fission - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - nu_fission[p][a][gin] = - std::accumulate(delayed_nu_fission[p][a][gin].begin(), - delayed_nu_fission[p][a][gin].end(), - prompt_nu_fission[p][a][gin]); - } + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + nu_fission[a][gin] = + std::accumulate(delayed_nu_fission[a][gin].begin(), + delayed_nu_fission[a][gin].end(), + prompt_nu_fission[a][gin]); } } } @@ -540,37 +490,32 @@ XsData::_scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, const int final_scatter_format, const int order_data, const int max_order, const int legendre_to_tabular_points) { + int n_ang = n_pol * n_azi; if (!object_exists(xsdata_grp, "scatter_data")) { fatal_error("Must provide scatter_data group!"); } hid_t scatt_grp = open_group(xsdata_grp, "scatter_data"); // Get the outgoing group boundary indices - int_3dvec gmin = int_3dvec(n_pol, int_2dvec(n_azi, - int_1dvec(energy_groups))); + int_2dvec gmin = int_2dvec(n_ang, int_1dvec(energy_groups)); read_nd_vector(scatt_grp, "g_min", gmin, true); - int_3dvec gmax = int_3dvec(n_pol, int_2dvec(n_azi, - int_1dvec(energy_groups))); + int_2dvec gmax = int_2dvec(n_ang, int_1dvec(energy_groups)); read_nd_vector(scatt_grp, "g_max", gmax, true); // Make gmin and gmax start from 0 vice 1 as they do in the library - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - gmin[p][a][gin] -= 1; - gmax[p][a][gin] -= 1; - } + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + gmin[a][gin] -= 1; + gmax[a][gin] -= 1; } } // Now use this info to find the length of a vector to hold the flattened // data. int length = 0; - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - length += order_data * (gmax[p][a][gin] - gmin[p][a][gin] + 1); - } + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + length += order_data * (gmax[a][gin] - gmin[a][gin] + 1); } } double_1dvec temp_arr = double_1dvec(length); @@ -587,55 +532,48 @@ XsData::_scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, // convert the flattened temp_arr to a jagged array for passing to // scatt data - double_5dvec input_scatt = - double_5dvec(n_pol, double_4dvec(n_azi, double_3dvec(energy_groups))); + double_4dvec input_scatt = + double_4dvec(n_ang, double_3dvec(energy_groups)); int temp_idx = 0; - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - input_scatt[p][a][gin].resize(gmax[p][a][gin] - gmin[p][a][gin] + 1); - for (int i_gout = 0; i_gout < input_scatt[p][a][gin].size(); i_gout++) { - input_scatt[p][a][gin][i_gout].resize(order_dim); - for (int l = 0; l < order_dim; l++) { - input_scatt[p][a][gin][i_gout][l] = temp_arr[temp_idx++]; - } - // Adjust index for the orders we didnt take - temp_idx += (order_data - order_dim); + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + input_scatt[a][gin].resize(gmax[a][gin] - gmin[a][gin] + 1); + for (int i_gout = 0; i_gout < input_scatt[a][gin].size(); i_gout++) { + input_scatt[a][gin][i_gout].resize(order_dim); + for (int l = 0; l < order_dim; l++) { + input_scatt[a][gin][i_gout][l] = temp_arr[temp_idx++]; } + // Adjust index for the orders we didnt take + temp_idx += (order_data - order_dim); } } } temp_arr.clear(); // Get multiplication matrix - double_4dvec temp_mult = double_4dvec(n_pol, double_3dvec(n_azi, - double_2dvec(energy_groups))); + double_3dvec temp_mult = double_3dvec(n_ang, double_2dvec(energy_groups)); if (object_exists(scatt_grp, "multiplicity_matrix")) { temp_arr.resize(length / order_data); read_nd_vector(scatt_grp, "multiplicity_matrix", temp_arr); // convert the flat temp_arr to a jagged array for passing to scatt data int temp_idx = 0; - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - temp_mult[p][a][gin].resize(gmax[p][a][gin] - gmin[p][a][gin] + 1); - for (int i_gout = 0; i_gout < temp_mult[p][a][gin].size(); i_gout++) { - temp_mult[p][a][gin][i_gout] = temp_arr[temp_idx++]; - } + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + temp_mult[a][gin].resize(gmax[a][gin] - gmin[a][gin] + 1); + for (int i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) { + temp_mult[a][gin][i_gout] = temp_arr[temp_idx++]; } } } } else { // Use a default: multiplicities are 1.0. - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - for (int gin = 0; gin < energy_groups; gin++) { - temp_mult[p][a][gin].resize(gmax[p][a][gin] - gmin[p][a][gin] + 1); - for (int i_gout = 0; i_gout < temp_mult[p][a][gin].size(); i_gout++) { - temp_mult[p][a][gin][i_gout] = 1.; - } + for (int a = 0; a < n_ang; a++) { + for (int gin = 0; gin < energy_groups; gin++) { + temp_mult[a][gin].resize(gmax[a][gin] - gmin[a][gin] + 1); + for (int i_gout = 0; i_gout < temp_mult[a][gin].size(); i_gout++) { + temp_mult[a][gin][i_gout] = 1.; } } } @@ -646,28 +584,22 @@ XsData::_scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, // Finally, convert the Legendre data to tabular, if needed if (scatter_format == ANGLE_LEGENDRE && final_scatter_format == ANGLE_TABULAR) { - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - ScattDataLegendre legendre_scatt; - legendre_scatt.init(gmin[p][a], gmax[p][a], temp_mult[p][a], - input_scatt[p][a]); + for (int a = 0; a < n_ang; a++) { + ScattDataLegendre legendre_scatt; + legendre_scatt.init(gmin[a], gmax[a], temp_mult[a], input_scatt[a]); - // Now create a tabular version of legendre_scatt - convert_legendre_to_tabular(legendre_scatt, - *static_cast(scatter[p][a]), - legendre_to_tabular_points); + // Now create a tabular version of legendre_scatt + convert_legendre_to_tabular(legendre_scatt, + *static_cast(scatter[a]), + legendre_to_tabular_points); - scatter_format = final_scatter_format; - } + scatter_format = final_scatter_format; } } else { // We are sticking with the current representation // Initialize the ScattData object with this data - for (int p = 0; p < n_pol; p++) { - for (int a = 0; a < n_azi; a++) { - scatter[p][a]->init(gmin[p][a], gmax[p][a], temp_mult[p][a], - input_scatt[p][a]); - } + for (int a = 0; a < n_ang; a++) { + scatter[a]->init(gmin[a], gmax[a], temp_mult[a], input_scatt[a]); } } } @@ -675,7 +607,7 @@ XsData::_scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, //============================================================================== void -XsData::combine(const std::vector those_xs, +XsData::combine(const std::vector& those_xs, const double_1dvec& scalars) { // Combine the non-scattering data @@ -683,64 +615,60 @@ XsData::combine(const std::vector those_xs, XsData* that = those_xs[i]; if (!equiv(*that)) fatal_error("Cannot combine the XsData objects!"); double scalar = scalars[i]; - for (int p = 0; p < total.size(); p++) { - for (int a = 0; a < total[p].size(); a++) { - for (int gin = 0; gin < total[p][a].size(); gin++) { - total[p][a][gin] += scalar * that->total[p][a][gin]; - absorption[p][a][gin] += scalar * that->absorption[p][a][gin]; - inverse_velocity[p][a][gin] += - scalar * that->inverse_velocity[p][a][gin]; - if (that->prompt_nu_fission.size() > 0) { - nu_fission[p][a][gin] += - scalar * that->nu_fission[p][a][gin]; - prompt_nu_fission[p][a][gin] += - scalar * that->prompt_nu_fission[p][a][gin]; - kappa_fission[p][a][gin] += - scalar * that->kappa_fission[p][a][gin]; - fission[p][a][gin] += - scalar * that->fission[p][a][gin]; + for (int a = 0; a < total.size(); a++) { + for (int gin = 0; gin < total[a].size(); gin++) { + total[a][gin] += scalar * that->total[a][gin]; + absorption[a][gin] += scalar * that->absorption[a][gin]; + if (i == 0) { + inverse_velocity[a][gin] = that->inverse_velocity[a][gin]; + } + if (that->prompt_nu_fission.size() > 0) { + nu_fission[a][gin] += scalar * that->nu_fission[a][gin]; + prompt_nu_fission[a][gin] += + scalar * that->prompt_nu_fission[a][gin]; + kappa_fission[a][gin] += scalar * that->kappa_fission[a][gin]; + fission[a][gin] += scalar * that->fission[a][gin]; - for (int dg = 0; dg < delayed_nu_fission[p][a][gin].size(); dg++) { - delayed_nu_fission[p][a][gin][dg] += - scalar * that->delayed_nu_fission[p][a][gin][dg]; - } + for (int dg = 0; dg < delayed_nu_fission[a][gin].size(); dg++) { + delayed_nu_fission[a][gin][dg] += + scalar * that->delayed_nu_fission[a][gin][dg]; + } - for (int gout = 0; gout < chi_prompt[p][a][gin].size(); gout++) { - chi_prompt[p][a][gin][gout] += - scalar * that->chi_prompt[p][a][gin][gout]; + for (int gout = 0; gout < chi_prompt[a][gin].size(); gout++) { + chi_prompt[a][gin][gout] += + scalar * that->chi_prompt[a][gin][gout]; - for (int dg = 0; dg < chi_delayed[p][a][gin][gout].size(); dg++) { - chi_delayed[p][a][gin][gout][dg] += - scalar * that->chi_delayed[p][a][gin][gout][dg]; - } + for (int dg = 0; dg < chi_delayed[a][gin][gout].size(); dg++) { + chi_delayed[a][gin][gout][dg] += + scalar * that->chi_delayed[a][gin][gout][dg]; } } } + } - for (int dg = 0; dg < decay_rate[p][a].size(); dg++) { - decay_rate[p][a][dg] += scalar * that->decay_rate[p][a][dg]; - } + for (int dg = 0; dg < decay_rate[a].size(); dg++) { + decay_rate[a][dg] += scalar * that->decay_rate[a][dg]; + } - // Normalize chi - if (chi_prompt.size() > 0) { - for (int gin = 0; gin < chi_prompt[p][a].size(); gin++) { - double norm = std::accumulate(chi_prompt[p][a][gin].begin(), - chi_prompt[p][a][gin].end(), 0.); - if (norm > 0.) { - for (int gout = 0; gout < chi_prompt[p][a][gin].size(); gout++) { - chi_prompt[p][a][gin][gout] /= norm; - } + // Normalize chi + if (chi_prompt.size() > 0) { + for (int gin = 0; gin < chi_prompt[a].size(); gin++) { + double norm = std::accumulate(chi_prompt[a][gin].begin(), + chi_prompt[a][gin].end(), 0.); + if (norm > 0.) { + for (int gout = 0; gout < chi_prompt[a][gin].size(); gout++) { + chi_prompt[a][gin][gout] /= norm; } + } - for (int dg = 0; dg < chi_delayed[p][a][gin][0].size(); dg++) { - norm = 0.; - for (int gout = 0; gout < chi_delayed[p][a][gin].size(); gout++) { - norm += chi_delayed[p][a][gin][gout][dg]; - } - if (norm > 0.) { - for (int gout = 0; gout < chi_delayed[p][a][gin].size(); gout++) { - chi_delayed[p][a][gin][gout][dg] /= norm; - } + for (int dg = 0; dg < chi_delayed[a][gin][0].size(); dg++) { + norm = 0.; + for (int gout = 0; gout < chi_delayed[a][gin].size(); gout++) { + norm += chi_delayed[a][gin][gout][dg]; + } + if (norm > 0.) { + for (int gout = 0; gout < chi_delayed[a][gin].size(); gout++) { + chi_delayed[a][gin][gout][dg] /= norm; } } } @@ -750,17 +678,15 @@ XsData::combine(const std::vector those_xs, } // Allow the ScattData object to combine itself - for (int p = 0; p < total.size(); p++) { - for (int a = 0; a < total[p].size(); a++) { - // Build vector of the scattering objects to incorporate - std::vector those_scatts(those_xs.size()); - for (int i = 0; i < those_xs.size(); i++) { - those_scatts[i] = those_xs[i]->scatter[p][a]; - } - - // Now combine these guys - scatter[p][a]->combine(those_scatts, scalars); + for (int a = 0; a < total.size(); a++) { + // Build vector of the scattering objects to incorporate + std::vector those_scatts(those_xs.size()); + for (int i = 0; i < those_xs.size(); i++) { + those_scatts[i] = those_xs[i]->scatter[a]; } + + // Now combine these guys + scatter[a]->combine(those_scatts, scalars); } } @@ -770,12 +696,8 @@ bool XsData::equiv(const XsData& that) { bool match = false; - // check n_pol (total.size()), n_azi (total[0].size()), and - // groups (total[0][0].size()) - // This assumes correct initializatino of the remaining cross sections - if ((total.size() == that.total.size()) && - (total[0].size() == that.total[0].size()) && - (total[0][0].size() == that.total[0][0].size())) { + if ((absorption.size() == that.absorption.size()) && + (absorption[0].size() == that.absorption[0].size())) { match = true; } return match; diff --git a/src/xsdata.h b/src/xsdata.h index 4d3a5d5d4..dd18a8ce9 100644 --- a/src/xsdata.h +++ b/src/xsdata.h @@ -28,32 +28,34 @@ class XsData { const int n_azi, const int energy_groups, int scatter_format, const int final_scatter_format, const int order_data, const int max_order, const int legendre_to_tabular_points); - void _fissionable_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, - int energy_groups, int delayed_groups, bool is_isotropic); + void _fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, + const int n_azi, const int energy_groups, const int delayed_groups, + const bool is_isotropic); public: // The following quantities have the following dimensions: - // [phi][theta][incoming group] - double_3dvec total; - double_3dvec absorption; - double_3dvec nu_fission; - double_3dvec prompt_nu_fission; - double_3dvec kappa_fission; - double_3dvec fission; - double_3dvec inverse_velocity; + // [angle][incoming group] + double_2dvec total; + double_2dvec absorption; + double_2dvec nu_fission; + double_2dvec prompt_nu_fission; + double_2dvec kappa_fission; + double_2dvec fission; + double_2dvec inverse_velocity; + // decay_rate has the following dimensions: - // [phi][theta][delayed group] - double_3dvec decay_rate; + // [angle][delayed group] + double_2dvec decay_rate; // delayed_nu_fission has the following dimensions: - // [phi][theta][incoming group][delayed group] - double_4dvec delayed_nu_fission; + // [angle][incoming group][delayed group] + double_3dvec delayed_nu_fission; // chi_prompt has the following dimensions: - // [phi][theta][incoming group][outgoing group] - double_4dvec chi_prompt; + // [angle][incoming group][outgoing group] + double_3dvec chi_prompt; // chi_delayed has the following dimensions: - // [phi][theta][incoming group][outgoing group][delayed group] - double_5dvec chi_delayed; - // scatter has the following dimensions: [phi][theta] - std::vector > scatter; + // [angle][incoming group][outgoing group][delayed group] + double_4dvec chi_delayed; + // scatter has the following dimensions: [angle] + std::vector scatter; XsData() = default; XsData(const int num_groups, const int num_delayed_groups, @@ -63,8 +65,8 @@ class XsData { const int scatter_format, const int final_scatter_format, const int order_data, const int max_order, const int legendre_to_tabular_points, - const bool is_isotropic); - void combine(const std::vector those_xs, + const bool is_isotropic, const int n_pol, const int n_azi); + void combine(const std::vector& those_xs, const double_1dvec& scalars); bool equiv(const XsData& that); }; From 857737b3994ef477a68c4ee2e740f1eb83a66ad0 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 17 Jun 2018 04:46:29 -0400 Subject: [PATCH 338/361] minor tweaks --- src/mgxs.cpp | 67 +++++++++++++++++++++++------------------------ src/scattdata.cpp | 12 +++------ 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 81a510e30..f6be84a25 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -414,31 +414,32 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, double* mu, int* dg) { // This method assumes that the temperature and angle indices are set + XsData* xs_t = &xs[cache[tid].t]; double val; switch(xstype) { case MG_GET_XS_TOTAL: - val = xs[cache[tid].t].total[cache[tid].a][gin]; + val = xs_t->total[cache[tid].a][gin]; break; case MG_GET_XS_NU_FISSION: if (fissionable) { - val = xs[cache[tid].t].nu_fission[cache[tid].a][gin]; + val = xs_t->nu_fission[cache[tid].a][gin]; } else { val = 0.; } break; case MG_GET_XS_ABSORPTION: - val = xs[cache[tid].t].absorption[cache[tid].a][gin]; + val = xs_t->absorption[cache[tid].a][gin]; break; case MG_GET_XS_FISSION: if (fissionable) { - val = xs[cache[tid].t].fission[cache[tid].a][gin]; + val = xs_t->fission[cache[tid].a][gin]; } else { val = 0.; } break; case MG_GET_XS_KAPPA_FISSION: if (fissionable) { - val = xs[cache[tid].t].kappa_fission[cache[tid].a][gin]; + val = xs_t->kappa_fission[cache[tid].a][gin]; } else { val = 0.; } @@ -447,11 +448,11 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, case MG_GET_XS_SCATTER_MULT: case MG_GET_XS_SCATTER_FMU_MULT: case MG_GET_XS_SCATTER_FMU: - val = xs[cache[tid].t].scatter[cache[tid].a]->get_xs(xstype, gin, gout, mu); + val = xs_t->scatter[cache[tid].a]->get_xs(xstype, gin, gout, mu); break; case MG_GET_XS_PROMPT_NU_FISSION: if (fissionable) { - val = xs[cache[tid].t].prompt_nu_fission[cache[tid].a][gin]; + val = xs_t->prompt_nu_fission[cache[tid].a][gin]; } else { val = 0.; } @@ -459,11 +460,10 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, case MG_GET_XS_DELAYED_NU_FISSION: if (fissionable) { if (dg != nullptr) { - val = xs[cache[tid].t].delayed_nu_fission[cache[tid].a][gin][*dg]; + val = xs_t->delayed_nu_fission[cache[tid].a][gin][*dg]; } else { val = 0.; - for (auto& num : xs[cache[tid].t].delayed_nu_fission - [cache[tid].a][gin]) { + for (auto& num : xs_t->delayed_nu_fission[cache[tid].a][gin]) { val += num; } } @@ -474,11 +474,11 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, case MG_GET_XS_CHI_PROMPT: if (fissionable) { if (gout != nullptr) { - val = xs[cache[tid].t].chi_prompt[cache[tid].a][gin][*gout]; + val = xs_t->chi_prompt[cache[tid].a][gin][*gout]; } else { // provide an outgoing group-wise sum val = 0.; - for (auto& num : xs[cache[tid].t].chi_prompt[cache[tid].a][gin]) { + for (auto& num : xs_t->chi_prompt[cache[tid].a][gin]) { val += num; } } @@ -490,23 +490,20 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, if (fissionable) { if (gout != nullptr) { if (dg != nullptr) { - val = xs[cache[tid].t].chi_delayed[cache[tid].a][gin][*gout][*dg]; + val = xs_t->chi_delayed[cache[tid].a][gin][*gout][*dg]; } else { - val = xs[cache[tid].t].chi_delayed[cache[tid].a][gin][*gout][0]; + val = xs_t->chi_delayed[cache[tid].a][gin][*gout][0]; } } else { if (dg != nullptr) { val = 0.; - for (int i = 0; i < xs[cache[tid].t].chi_delayed - [cache[tid].a][gin].size(); i++) { - val += xs[cache[tid].t].chi_delayed[cache[tid].a][gin][i][*dg]; + for (int i = 0; i < xs_t->chi_delayed[cache[tid].a][gin].size(); i++) { + val += xs_t->chi_delayed[cache[tid].a][gin][i][*dg]; } } else { val = 0.; - for (int i = 0; i < xs[cache[tid].t].chi_delayed - [cache[tid].a][gin].size(); i++) { - for (auto& num : xs[cache[tid].t].chi_delayed - [cache[tid].a][gin][i]) { + for (int i = 0; i < xs_t->chi_delayed[cache[tid].a][gin].size(); i++) { + for (auto& num : xs_t->chi_delayed[cache[tid].a][gin][i]) { val += num; } } @@ -517,13 +514,13 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, } break; case MG_GET_XS_INVERSE_VELOCITY: - val = xs[cache[tid].t].inverse_velocity[cache[tid].a][gin]; + val = xs_t->inverse_velocity[cache[tid].a][gin]; break; case MG_GET_XS_DECAY_RATE: if (dg != nullptr) { - val = xs[cache[tid].t].decay_rate[cache[tid].a][*dg + 1]; + val = xs_t->decay_rate[cache[tid].a][*dg + 1]; } else { - val = xs[cache[tid].t].decay_rate[cache[tid].a][0]; + val = xs_t->decay_rate[cache[tid].a][0]; } break; default: @@ -538,11 +535,12 @@ void Mgxs::sample_fission_energy(const int tid, const int gin, int& dg, int& gout) { // This method assumes that the temperature and angle indices are set - double nu_fission = xs[cache[tid].t].nu_fission[cache[tid].a][gin]; + XsData* xs_t = &xs[cache[tid].t]; + double nu_fission = xs_t->nu_fission[cache[tid].a][gin]; // Find the probability of having a prompt neutron double prob_prompt = - xs[cache[tid].t].prompt_nu_fission[cache[tid].a][gin]; + xs_t->prompt_nu_fission[cache[tid].a][gin]; // sample random numbers double xi_pd = prn() * nu_fission; @@ -558,10 +556,10 @@ Mgxs::sample_fission_energy(const int tid, const int gin, int& dg, int& gout) // sample the outgoing energy group gout = 0; double prob_gout = - xs[cache[tid].t].chi_prompt[cache[tid].a][gin][gout]; + xs_t->chi_prompt[cache[tid].a][gin][gout]; while (prob_gout < xi_gout) { gout++; - prob_gout += xs[cache[tid].t].chi_prompt[cache[tid].a][gin][gout]; + prob_gout += xs_t->chi_prompt[cache[tid].a][gin][gout]; } } else { @@ -572,7 +570,7 @@ Mgxs::sample_fission_energy(const int tid, const int gin, int& dg, int& gout) while (xi_pd >= prob_prompt) { dg++; prob_prompt += - xs[cache[tid].t].delayed_nu_fission[cache[tid].a][gin][dg]; + xs_t->delayed_nu_fission[cache[tid].a][gin][dg]; } // adjust dg in case of round-off error @@ -581,11 +579,11 @@ Mgxs::sample_fission_energy(const int tid, const int gin, int& dg, int& gout) // sample the outgoing energy group gout = 0; double prob_gout = - xs[cache[tid].t].chi_delayed[cache[tid].a][gin][gout][dg]; + xs_t->chi_delayed[cache[tid].a][gin][gout][dg]; while (prob_gout < xi_gout) { gout++; prob_gout += - xs[cache[tid].t].chi_delayed[cache[tid].a][gin][gout][dg]; + xs_t->chi_delayed[cache[tid].a][gin][gout][dg]; } } } @@ -610,11 +608,12 @@ Mgxs::calculate_xs(const int tid, const int gin, const double sqrtkT, // Set our indices set_temperature_index(tid, sqrtkT); set_angle_index(tid, uvw); - total_xs = xs[cache[tid].t].total[cache[tid].a][gin]; - abs_xs = xs[cache[tid].t].absorption[cache[tid].a][gin]; + XsData* xs_t = &xs[cache[tid].t]; + total_xs = xs_t->total[cache[tid].a][gin]; + abs_xs = xs_t->absorption[cache[tid].a][gin]; if (fissionable) { - nu_fiss_xs = xs[cache[tid].t].nu_fission[cache[tid].a][gin]; + nu_fiss_xs = xs_t->nu_fission[cache[tid].a][gin]; } else { nu_fiss_xs = 0.; } diff --git a/src/scattdata.cpp b/src/scattdata.cpp index e321bc275..9d7bad591 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -609,16 +609,14 @@ ScattDataHistogram::combine(const std::vector& those_scatts, const double_1dvec& scalars) { // Find the max order in the data set and make sure we can combine the sets - int max_order; + int max_order = those_scatts[0]->get_order(); for (int i = 0; i < those_scatts.size(); i++) { // Lets also make sure these items are combineable ScattDataHistogram* that = dynamic_cast(those_scatts[i]); if (!that) { fatal_error("Cannot combine the ScattData objects!"); } - if (i == 0) { - max_order = that->get_order(); - } else if (max_order != that->get_order()) { + if (max_order != that->get_order()) { fatal_error("Cannot combine the ScattData objects!"); } } @@ -835,16 +833,14 @@ ScattDataTabular::combine(const std::vector& those_scatts, const double_1dvec& scalars) { // Find the max order in the data set and make sure we can combine the sets - int max_order; + int max_order = those_scatts[0]->get_order(); for (int i = 0; i < those_scatts.size(); i++) { // Lets also make sure these items are combineable ScattDataTabular* that = dynamic_cast(those_scatts[i]); if (!that) { fatal_error("Cannot combine the ScattData objects!"); } - if (i == 0) { - max_order = that->get_order(); - } else if (max_order != that->get_order()) { + if (max_order != that->get_order()) { fatal_error("Cannot combine the ScattData objects!"); } } From 1343ef2000051cf48dfd36595b2931b2a1f6a75b Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 17 Jun 2018 05:41:32 -0400 Subject: [PATCH 339/361] Bug is fixed! is_isotropic was initialized in from_hdf5, not in init. Therefore it wasnt initialized correctly by build_macro --- src/mgxs.cpp | 47 ++++++++++++++++++++++++++--------------------- src/mgxs.h | 7 ++++--- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/mgxs.cpp b/src/mgxs.cpp index f6be84a25..f975bd586 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -15,8 +15,9 @@ void Mgxs::init(const std::string& in_name, const double in_awr, const double_1dvec& in_kTs, const bool in_fissionable, const int in_scatter_format, const int in_num_groups, - const int in_num_delayed_groups, const double_1dvec& in_polar, - const double_1dvec& in_azimuthal, const int n_threads) + const int in_num_delayed_groups, const bool in_is_isotropic, + const double_1dvec& in_polar, const double_1dvec& in_azimuthal, + const int n_threads) { name = in_name; awr = in_awr; @@ -26,10 +27,11 @@ Mgxs::init(const std::string& in_name, const double in_awr, num_groups = in_num_groups; num_delayed_groups = in_num_delayed_groups; xs.resize(in_kTs.size()); + is_isotropic = in_is_isotropic; + n_pol = in_polar.size(); + n_azi = in_azimuthal.size(); polar = in_polar; azimuthal = in_azimuthal; - n_pol = polar.size(); - n_azi = azimuthal.size(); cache.resize(n_threads); for (int thread = 0; thread < n_threads; thread++) { cache[thread].sqrtkT = 0.; @@ -205,50 +207,52 @@ Mgxs::_metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, } // Get the angular information - is_isotropic = true; + int in_n_pol; + int in_n_azi; + bool in_is_isotropic = true; if (attribute_exists(xs_id, "representation")) { std::string temp_str(MAX_WORD_LEN, ' '); read_attr_string(xs_id, "representation", MAX_WORD_LEN, &temp_str[0]); to_lower(strtrim(temp_str)); if (temp_str.compare(0, 5, "angle") == 0) { - is_isotropic = false; + in_is_isotropic = false; } else if (temp_str.compare(0, 9, "isotropic") != 0) { fatal_error("Invalid Data Representation!"); } } - if (!is_isotropic) { + if (!in_is_isotropic) { if (attribute_exists(xs_id, "num_polar")) { - read_attr_int(xs_id, "num_polar", &n_pol); + read_attr_int(xs_id, "num_polar", &in_n_pol); } else { fatal_error("num_polar must be provided!"); } if (attribute_exists(xs_id, "num_azimuthal")) { - read_attr_int(xs_id, "num_azimuthal", &n_azi); + read_attr_int(xs_id, "num_azimuthal", &in_n_azi); } else { fatal_error("num_azimuthal must be provided!"); } } else { - n_pol = 1; - n_azi = 1; + in_n_pol = 1; + in_n_azi = 1; } // Set the angular bins to use equally-spaced bins - double_1dvec in_polar(n_pol); - double dangle = PI / n_pol; - for (int p = 0; p < n_pol; p++) { + double_1dvec in_polar(in_n_pol); + double dangle = PI / in_n_pol; + for (int p = 0; p < in_n_pol; p++) { in_polar[p] = (p + 0.5) * dangle; } - double_1dvec in_azimuthal(n_azi); - dangle = 2. * PI / n_azi; - for (int a = 0; a < n_azi; a++) { + double_1dvec in_azimuthal(in_n_azi); + dangle = 2. * PI / in_n_azi; + for (int a = 0; a < in_n_azi; a++) { in_azimuthal[a] = (a + 0.5) * dangle - PI; } // Finally use this data to initialize the MGXS Object init(in_name, in_awr, in_kTs, in_fissionable, in_scatter_format, - in_num_groups, in_num_delayed_groups, in_polar, in_azimuthal, - n_threads); + in_num_groups, in_num_delayed_groups, in_is_isotropic, in_polar, + in_azimuthal, n_threads); } //============================================================================== @@ -311,12 +315,13 @@ Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, int in_scatter_format = micros[0]->scatter_format; int in_num_groups = micros[0]->num_groups; int in_num_delayed_groups = micros[0]->num_delayed_groups; + bool in_is_isotropic = micros[0]->is_isotropic; double_1dvec in_polar = micros[0]->polar; double_1dvec in_azimuthal = micros[0]->azimuthal; init(in_name, in_awr, mat_kTs, in_fissionable, in_scatter_format, - in_num_groups, in_num_delayed_groups, in_polar, in_azimuthal, - n_threads); + in_num_groups, in_num_delayed_groups, in_is_isotropic, in_polar, + in_azimuthal, n_threads); // Create the xs data for each temperature for (int t = 0; t < mat_kTs.size(); t++) { diff --git a/src/mgxs.h b/src/mgxs.h index 2a79bfe2a..79f473c0a 100644 --- a/src/mgxs.h +++ b/src/mgxs.h @@ -30,7 +30,7 @@ struct CacheData { double sqrtkT; // last temperature corresponding to t int t; // temperature index int a; // angle index - // last angle that corresponds to p and a + // last angle that corresponds to a double u; double v; double w; @@ -68,8 +68,9 @@ class Mgxs { void init(const std::string& in_name, const double in_awr, const double_1dvec& in_kTs, const bool in_fissionable, const int in_scatter_format, const int in_num_groups, - const int in_num_delayed_groups, const double_1dvec& in_polar, - const double_1dvec& in_azimuthal, const int n_threads); + const int in_num_delayed_groups, const bool in_is_isotropic, + const double_1dvec& in_polar, const double_1dvec& in_azimuthal, + const int n_threads); void build_macro(const std::string& in_name, double_1dvec& mat_kTs, std::vector& micros, double_1dvec& atom_densities, int& method, const double tolerance, const int n_threads); From 8c2a88243ba6915abaff34ceb6778c913d3a9ee9 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 17 Jun 2018 10:10:53 -0400 Subject: [PATCH 340/361] Docs added --- src/mgxs.cpp | 4 +- src/mgxs.h | 176 +++++++++++++++++++++++++++++----- src/scattdata.cpp | 18 ++-- src/scattdata.h | 239 ++++++++++++++++++++++++++++++++++++---------- src/xsdata.cpp | 10 +- src/xsdata.h | 75 ++++++++++++--- 6 files changed, 420 insertions(+), 102 deletions(-) diff --git a/src/mgxs.cpp b/src/mgxs.cpp index f975bd586..d30417c28 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -46,7 +46,7 @@ Mgxs::init(const std::string& in_name, const double in_awr, //============================================================================== void -Mgxs::_metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, +Mgxs::metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, const int in_num_delayed_groups, double_1dvec& temperature, int& method, const double tolerance, int_1dvec& temps_to_read, int& order_dim, const int n_threads) @@ -267,7 +267,7 @@ Mgxs::from_hdf5(hid_t xs_id, const int energy_groups, // Call generic data gathering routine (will populate the metadata) int order_data; int_1dvec temps_to_read; - _metadata_from_hdf5(xs_id, energy_groups, delayed_groups, temperature, + metadata_from_hdf5(xs_id, energy_groups, delayed_groups, temperature, method, tolerance, temps_to_read, order_data, n_threads); // Set number of energy and delayed groups diff --git a/src/mgxs.h b/src/mgxs.h index 79f473c0a..c0cec5941 100644 --- a/src/mgxs.h +++ b/src/mgxs.h @@ -42,6 +42,7 @@ struct CacheData { class Mgxs { private: + double_1dvec kTs; // temperature in eV (k * T) int scatter_format; // flag for if this is legendre, histogram, or tabular int num_delayed_groups; // number of delayed neutron groups @@ -53,43 +54,174 @@ class Mgxs { int n_azi; double_1dvec polar; double_1dvec azimuthal; - void _metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, + + //! \brief Initializes the Mgxs object metadata from the HDF5 file + //! + //! @param xs_id HDF5 group id for the cross section data. + //! @param in_num_groups Number of energy groups. + //! @param in_num_delayed_groups Number of delayed groups. + //! @param temperature Temperatures to read. + //! @param method Method of choosing nearest temperatures. + //! @param tolerance Tolerance of temperature selection method. + //! @param temps_to_read Resultant list of temperatures in the library + //! to read which correspond to the requested temperatures. + //! @param order_dim Resultant dimensionality of the scattering order. + //! @param n_threads Number of threads at runtime. + void + metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, const int in_num_delayed_groups, double_1dvec& temperature, int& method, const double tolerance, int_1dvec& temps_to_read, int& order_dim, const int n_threads); - bool equiv(const Mgxs& that); - public: - std::string name; // name of dataset, e.g., UO2 - double awr; // atomic weight ratio - bool fissionable; // Is this fissionable - // TODO: The following attributes be private when Fortran is fully replaced - std::vector cache; // index and data cache - void init(const std::string& in_name, const double in_awr, + //! \brief Initializes the Mgxs object metadata + //! + //! @param in_name Name of the object. + //! @param in_awr atomic-weight ratio. + //! @param in_kTs temperatures (in units of eV) that data is available. + //! @param in_fissionable Is this item fissionable or not. + //! @param in_scatter_format Denotes whether Legendre, Tabular, or + //! Histogram scattering is used. + //! @param in_num_groups Number of energy groups. + //! @param in_num_delayed_groups Number of delayed groups. + //! @param in_is_isotropic Is this an isotropic or angular with respect to + //! the incoming particle. + //! @param in_polar Polar angle grid. + //! @param in_azimuthal Azimuthal angle grid. + //! @param n_threads Number of threads at runtime. + void + init(const std::string& in_name, const double in_awr, const double_1dvec& in_kTs, const bool in_fissionable, const int in_scatter_format, const int in_num_groups, const int in_num_delayed_groups, const bool in_is_isotropic, const double_1dvec& in_polar, const double_1dvec& in_azimuthal, const int n_threads); - void build_macro(const std::string& in_name, double_1dvec& mat_kTs, - std::vector& micros, double_1dvec& atom_densities, - int& method, const double tolerance, const int n_threads); - void combine(std::vector& micros, double_1dvec& scalars, - int_1dvec& micro_ts, int this_t); - void from_hdf5(hid_t xs_id, const int energy_groups, + + //! \brief Performs the actual act of combining the microscopic data for a + //! single temperature. + //! + //! @param micros Microscopic objects to combine. + //! @param scalars Scalars to multiply the microscopic data by. + //! @param micro_ts The temperature index of the microscopic objects that + //! corresponds to the temperature of interest. + //! @param this_t The temperature index of the macroscopic object. + void + combine(std::vector& micros, double_1dvec& scalars, + int_1dvec& micro_ts, int this_t); + + //! \brief Checks to see if this and that are able to be combined + //! + //! This comparison is used when building macroscopic cross sections + //! from microscopic cross sections. + //! @param that The other Mgxs to compare to this one. + //! @return True if they can be combined, False otherwise. + bool equiv(const Mgxs& that); + + public: + + std::string name; // name of dataset, e.g., UO2 + double awr; // atomic weight ratio + bool fissionable; // Is this fissionable + std::vector cache; // index and data cache + + //! \brief Initializes and populates all data to build a macroscopic + //! cross section from microscopic cross section. + //! + //! @param in_name Name of the object. + //! @param mat_kTs temperatures (in units of eV) that data is needed. + //! @param micros Microscopic objects to combine. + //! @param atom_densities Atom densities of those microscopic quantities. + //! @param method Method of choosing nearest temperatures. + //! @param tolerance Tolerance of temperature selection method. + //! @param n_threads Number of threads at runtime. + void + build_macro(const std::string& in_name, double_1dvec& mat_kTs, + std::vector& micros, double_1dvec& atom_densities, + int& method, const double tolerance, const int n_threads); + + //! \brief Loads the Mgxs object from the HDF5 file + //! + //! @param xs_id HDF5 group id for the cross section data. + //! @param energy_groups Number of energy groups. + //! @param delayed_groups Number of delayed groups. + //! @param temperature Temperatures to read. + //! @param method Method of choosing nearest temperatures. + //! @param tolerance Tolerance of temperature selection method. + //! @param max_order Maximum order requested by the user; + //! this is only used for Legendre scattering. + //! @param legendre_to_tabular Flag to denote if any Legendre provided + //! should be converted to a Tabular representation. + //! @param legendre_to_tabular_points If a conversion is requested, this + //! provides the number of points to use in the tabular representation. + //! @param n_threads Number of threads at runtime. + void + from_hdf5(hid_t xs_id, const int energy_groups, const int delayed_groups, double_1dvec& temperature, int& method, const double tolerance, const int max_order, const bool legendre_to_tabular, const int legendre_to_tabular_points, const int n_threads); - double get_xs(const int tid, const int xstype, const int gin, int* gout, + + //! \brief Provides a cross section value given certain parameters + //! + //! @param xstype Type of cross section requested, according to the + //! enumerated constants. + //! @param gin Incoming energy group. + //! @param gout Outgoing energy group; use nullptr if irrelevant, or if a + //! sum is requested. + //! @param mu Cosine of the change-in-angle, for scattering quantities; + //! use nullptr if irrelevant. + //! @param dg delayed group index; use nullptr if irrelevant. + //! @return Requested cross section value. + double + get_xs(const int tid, const int xstype, const int gin, int* gout, double* mu, int* dg); - void sample_fission_energy(const int tid, const int gin, int& dg, int& gout); - void sample_scatter(const int tid, const int gin, int& gout, double& mu, + + //! \brief Samples the fission neutron energy and if prompt or delayed. + //! + //! @param tid Thread id to use when using the index cache. + //! @param gin Incoming energy group. + //! @param dg Sampled delayed group index. + //! @param gout Sampled outgoing energy group. + void + sample_fission_energy(const int tid, const int gin, int& dg, int& gout); + + //! \brief Samples the outgoing energy and angle from a scatter event. + //! + //! @param tid Thread id to use when using the index cache. + //! @param gin Incoming energy group. + //! @param gout Sampled outgoing energy group. + //! @param mu Sampled cosine of the change-in-angle. + //! @param wgt Weight of the particle to be adjusted. + void + sample_scatter(const int tid, const int gin, int& gout, double& mu, double& wgt); - void calculate_xs(const int tid, const int gin, const double sqrtkT, - const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs); - void set_temperature_index(const int tid, const double sqrtkT); - void set_angle_index(const int tid, const double uvw[3]); + + //! \brief Calculates cross section quantities needed for tracking. + //! + //! @param tid Thread id to use when using the index cache. + //! @param gin Incoming energy group. + //! @param sqrtkT Temperature of the material. + //! @param uvw Incoming particle direction. + //! @param total_xs Resultant total cross section. + //! @param abs_xs Resultant absorption cross section. + //! @param nu_fiss_xs Resultant nu-fission cross section. + void + calculate_xs(const int tid, const int gin, const double sqrtkT, + const double uvw[3], double& total_xs, double& abs_xs, + double& nu_fiss_xs); + + //! \brief Sets the temperature index in cache given a temperature + //! + //! @param tid Thread id to use when setting the index cache. + //! @param sqrtkT Temperature of the material. + void + set_temperature_index(const int tid, const double sqrtkT); + + //! \brief Sets the angle index in cache given a direction + //! + //! @param tid Thread id to use when setting the index cache. + //! @param uvw Incoming particle direction. + void + set_angle_index(const int tid, const double uvw[3]); }; } // namespace openmc diff --git a/src/scattdata.cpp b/src/scattdata.cpp index 9d7bad591..952c05f64 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -7,7 +7,7 @@ namespace openmc { //============================================================================== void -ScattData::generic_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, +ScattData::base_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_energy, double_2dvec& in_mult) { int groups = in_energy.size(); @@ -42,7 +42,7 @@ ScattData::generic_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, //============================================================================== void -ScattData::generic_combine(const int max_order, +ScattData::base_combine(const int max_order, const std::vector& those_scatts, const double_1dvec& scalars, int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& sparse_mult, double_3dvec& sparse_scatter) @@ -277,7 +277,7 @@ ScattDataLegendre::init(int_1dvec& in_gmin, int_1dvec& in_gmax, } // Initialize the base class attributes - ScattData::generic_init(order, in_gmin, in_gmax, in_energy, in_mult); + ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult); // Set the distribution (sdata.dist) values and initialize max_val max_val.resize(groups); @@ -407,7 +407,7 @@ ScattDataLegendre::combine(const std::vector& those_scatts, // The rest of the steps do not depend on the type of angular representation // so we use a base class method to sum up xs and create new energy and mult // matrices - ScattData::generic_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, + ScattData::base_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, sparse_mult, sparse_scatter); // Got everything we need, store it. @@ -479,7 +479,7 @@ ScattDataHistogram::init(int_1dvec& in_gmin, int_1dvec& in_gmax, } // Initialize the base class attributes - ScattData::generic_init(order, in_gmin, in_gmax, in_energy, + ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult); // Build the angular distribution mu values @@ -631,7 +631,7 @@ ScattDataHistogram::combine(const std::vector& those_scatts, // The rest of the steps do not depend on the type of angular representation // so we use a base class method to sum up xs and create new energy and mult // matrices - ScattData::generic_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, + ScattData::base_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, sparse_mult, sparse_scatter); // Got everything we need, store it. @@ -691,7 +691,7 @@ ScattDataTabular::init(int_1dvec& in_gmin, int_1dvec& in_gmax, } // Initialize the base class attributes - ScattData::generic_init(order, in_gmin, in_gmax, in_energy, in_mult); + ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult); // Calculate f(mu) and integrate it so we can avoid rejection sampling fmu.resize(groups); @@ -855,7 +855,7 @@ ScattDataTabular::combine(const std::vector& those_scatts, // The rest of the steps do not depend on the type of angular representation // so we use a base class method to sum up xs and create new energy and mult // matrices - ScattData::generic_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, + ScattData::base_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, sparse_mult, sparse_scatter); // Got everything we need, store it. @@ -881,7 +881,7 @@ convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, } } - tab.generic_init(n_mu, leg.gmin, leg.gmax, leg.energy, leg.mult); + tab.base_init(n_mu, leg.gmin, leg.gmax, leg.energy, leg.mult); tab.scattxs = leg.scattxs; // Build mu and dmu diff --git a/src/scattdata.h b/src/scattdata.h index 9b362b964..69ce9e245 100644 --- a/src/scattdata.h +++ b/src/scattdata.h @@ -27,31 +27,104 @@ class ScattDataTabular; class ScattData { protected: - void generic_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, - double_2dvec& in_energy, double_2dvec& in_mult); - void generic_combine(const int max_order, - const std::vector& those_scatts, - const double_1dvec& scalars, int_1dvec& in_gmin, - int_1dvec& in_gmax, double_2dvec& sparse_mult, - double_3dvec& sparse_scatter); + //! \brief Initializes the attributes of the base class. + void + base_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, + double_2dvec& in_energy, double_2dvec& in_mult); + + //! \brief Combines microscopic ScattDatas into a macroscopic one. + void + base_combine(const int max_order, + const std::vector& those_scatts, + const double_1dvec& scalars, int_1dvec& in_gmin, int_1dvec& in_gmax, + double_2dvec& sparse_mult, double_3dvec& sparse_scatter); + public: + double_2dvec energy; // Normalized p0 matrix for sampling Eout double_2dvec mult; // nu-scatter multiplication (nu-scatt/scatt) double_3dvec dist; // Angular distribution int_1dvec gmin; // minimum outgoing group int_1dvec gmax; // maximum outgoing group double_1dvec scattxs; // Isotropic Sigma_{s,g_{in}} - virtual double calc_f(const int gin, const int gout, const double mu) = 0; - virtual void sample(const int gin, int& gout, double& mu, double& wgt) = 0; - virtual void init(int_1dvec& in_gmin, int_1dvec& in_gmax, - double_2dvec& in_mult, double_3dvec& coeffs) = 0; - void sample_energy(int gin, int& gout, int& i_gout); - double get_xs(const int xstype, const int gin, const int* gout, - const double* mu); - virtual void combine(const std::vector& those_scatts, - const double_1dvec& scalars) = 0; - virtual int get_order() = 0; - virtual double_3dvec get_matrix(const int max_order) = 0; + + //! \brief Calculates the value of normalized f(mu). + //! + //! The value of f(mu) is normalized as in the integral of f(mu)dmu across + //! [-1,1] is 1. + //! + //! @param gin Incoming energy group of interest. + //! @param gout Outgoing energy group of interest. + //! @param mu Cosine of the change-in-angle of interest. + //! @return The value of f(mu). + virtual double + calc_f(const int gin, const int gout, const double mu) = 0; + + //! \brief Samples the outgoing energy and angle from the ScattData info. + //! + //! @param gin Incoming energy group. + //! @param gout Sampled outgoing energy group. + //! @param mu Sampled cosine of the change-in-angle. + //! @param wgt Weight of the particle to be adjusted. + virtual void + sample(const int gin, int& gout, double& mu, double& wgt) = 0; + + //! \brief Initializes the ScattData object from a given scatter and + //! multiplicity matrix. + //! + //! @param in_gmin List of minimum outgoing groups for every incoming group + //! @param in_gmax List of maximum outgoing groups for every incoming group + //! @param in_mult Input sparse multiplicity matrix + //! @param coeffs Input sparse scattering matrix + virtual void + init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, + double_3dvec& coeffs) = 0; + + //! \brief Combines the microscopic data. + //! + //! @param those_scatts Microscopic objects to combine. + //! @param scalars Scalars to multiply the microscopic data by. + virtual void + combine(const std::vector& those_scatts, + const double_1dvec& scalars) = 0; + + //! \brief Getter for the dimensionality of the scattering order. + //! + //! If Legendre this is the "n" in "Pn"; for Tabular, this is the number + //! of points, and for Histogram this is the number of bins. + //! + //! @return The order. + virtual int + get_order() = 0; + + //! \brief Builds a dense scattering matrix from the constituent parts + //! + //! @param max_order If Legendre this is the maximum value of "n" in "Pn" + //! requested; ignored otherwise. + //! @return The dense scattering matrix. + virtual double_3dvec + get_matrix(const int max_order) = 0; + + //! \brief Samples the outgoing energy from the ScattData info. + //! + //! @param gin Incoming energy group. + //! @param gout Sampled outgoing energy group. + //! @param i_gout Sampled outgoing energy group index. + void + sample_energy(int gin, int& gout, int& i_gout); + + //! \brief Provides a cross section value given certain parameters + //! + //! @param xstype Type of cross section requested, according to the + //! enumerated constants. + //! @param gin Incoming energy group. + //! @param gout Outgoing energy group; use nullptr if irrelevant, or if a + //! sum is requested. + //! @param mu Cosine of the change-in-angle, for scattering quantities; + //! use nullptr if irrelevant. + //! @return Requested cross section value. + double + get_xs(const int xstype, const int gin, const int* gout, const double* mu); }; //============================================================================== @@ -59,21 +132,44 @@ class ScattData { //============================================================================== class ScattDataLegendre: public ScattData { + protected: + // Maximal value for rejection sampling from a rectangle double_2dvec max_val; - friend void convert_legendre_to_tabular(ScattDataLegendre& leg, - ScattDataTabular& tab, int n_mu); + + // Friend convert_legendre_to_tabular so it has access to protected + // parameters + friend void + convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, + int n_mu); + public: - void init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, - double_3dvec& coeffs); - void update_max_val(); - double calc_f(const int gin, const int gout, const double mu); - void sample(const int gin, int& gout, double& mu, double& wgt); - void combine(const std::vector& those_scatts, - const double_1dvec& scalars); - int get_order() {return dist[0][0].size() - 1;}; - double_3dvec get_matrix(const int max_order); + + void + init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, + double_3dvec& coeffs); + + void + combine(const std::vector& those_scatts, + const double_1dvec& scalars); + + //! \brief Find the maximal value of the angular distribution to use as a + // bounding box with rejection sampling. + void + update_max_val(); + + double + calc_f(const int gin, const int gout, const double mu); + + void + sample(const int gin, int& gout, double& mu, double& wgt); + + int + get_order() {return dist[0][0].size() - 1;}; + + double_3dvec + get_matrix(const int max_order); }; //============================================================================== @@ -82,19 +178,34 @@ class ScattDataLegendre: public ScattData { //============================================================================== class ScattDataHistogram: public ScattData { + protected: - double_1dvec mu; - double dmu; - double_3dvec fmu; + + double_1dvec mu; // Angle distribution mu bin boundaries + double dmu; // Quick storage of the spacing between the mu bin points + double_3dvec fmu; // The angular distribution histogram + public: - void init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, - double_3dvec& coeffs); - double calc_f(const int gin, const int gout, const double mu); - void sample(const int gin, int& gout, double& mu, double& wgt); - void combine(const std::vector& those_scatts, - const double_1dvec& scalars); - int get_order() {return dist[0][0].size();}; - double_3dvec get_matrix(const int max_order); + + void + init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, + double_3dvec& coeffs); + + void + combine(const std::vector& those_scatts, + const double_1dvec& scalars); + + double + calc_f(const int gin, const int gout, const double mu); + + void + sample(const int gin, int& gout, double& mu, double& wgt); + + int + get_order() {return dist[0][0].size();}; + + double_3dvec + get_matrix(const int max_order); }; //============================================================================== @@ -103,20 +214,38 @@ class ScattDataHistogram: public ScattData { //============================================================================== class ScattDataTabular: public ScattData { + protected: - double_1dvec mu; - double dmu; - double_3dvec fmu; - friend void convert_legendre_to_tabular(ScattDataLegendre& leg, - ScattDataTabular& tab, int n_mu); + + double_1dvec mu; // Angle distribution mu grid points + double dmu; // Quick storage of the spacing between the mu points + double_3dvec fmu; // The angular distribution function + + // Friend convert_legendre_to_tabular so it has access to protected + // parameters + friend void + convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, + int n_mu); + public: - void init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, - double_3dvec& coeffs); - double calc_f(const int gin, const int gout, const double mu); - void sample(const int gin, int& gout, double& mu, double& wgt); - void combine(const std::vector& those_scatts, - const double_1dvec& scalars); - int get_order() {return dist[0][0].size();}; + + void + init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, + double_3dvec& coeffs); + + void + combine(const std::vector& those_scatts, + const double_1dvec& scalars); + + double + calc_f(const int gin, const int gout, const double mu); + + void + sample(const int gin, int& gout, double& mu, double& wgt); + + int + get_order() {return dist[0][0].size();}; + double_3dvec get_matrix(const int max_order); }; @@ -124,6 +253,12 @@ class ScattDataTabular: public ScattData { // Function to convert Legendre functions to tabular //============================================================================== +//! \brief Converts a ScattDatalegendre to a ScattDataHistogram +//! +//! @param leg The initial ScattDataLegendre object. +//! @param leg The resultant ScattDataTabular object. +//! @param n_mu The number of mu points to use when building the +//! ScattDataTabular object. void convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, int n_mu); diff --git a/src/xsdata.cpp b/src/xsdata.cpp index 05e6ab01b..b447ee0ae 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -73,8 +73,8 @@ XsData::from_hdf5(const hid_t xsdata_grp, const bool fissionable, // Set the fissionable-specific data if (fissionable) { - _fissionable_from_hdf5(xsdata_grp, n_pol, n_azi, energy_groups, - delayed_groups, is_isotropic); + fission_from_hdf5(xsdata_grp, n_pol, n_azi, energy_groups, delayed_groups, + is_isotropic); } // Get the non-fission-specific data read_nd_vector(xsdata_grp, "decay_rate", decay_rate); @@ -82,7 +82,7 @@ XsData::from_hdf5(const hid_t xsdata_grp, const bool fissionable, read_nd_vector(xsdata_grp, "inverse-velocity", inverse_velocity); // Get scattering data - _scatter_from_hdf5(xsdata_grp, n_pol, n_azi, energy_groups, scatter_format, + scatter_from_hdf5(xsdata_grp, n_pol, n_azi, energy_groups, scatter_format, final_scatter_format, order_data, max_order, legendre_to_tabular_points); @@ -116,7 +116,7 @@ XsData::from_hdf5(const hid_t xsdata_grp, const bool fissionable, //============================================================================== void -XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, +XsData::fission_from_hdf5(const hid_t xsdata_grp, const int n_pol, const int n_azi, const int energy_groups, const int delayed_groups, const bool is_isotropic) { @@ -485,7 +485,7 @@ XsData::_fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, //============================================================================== void -XsData::_scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, +XsData::scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, const int n_azi, const int energy_groups, int scatter_format, const int final_scatter_format, const int order_data, const int max_order, const int legendre_to_tabular_points) diff --git a/src/xsdata.h b/src/xsdata.h index dd18a8ce9..adf80cad2 100644 --- a/src/xsdata.h +++ b/src/xsdata.h @@ -23,15 +23,23 @@ namespace openmc { //============================================================================== class XsData { + private: - void _scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, - const int n_azi, const int energy_groups, int scatter_format, + //! \brief Reads scattering data from the HDF5 file + void + scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, const int n_azi, + const int energy_groups, int scatter_format, const int final_scatter_format, const int order_data, const int max_order, const int legendre_to_tabular_points); - void _fissionable_from_hdf5(const hid_t xsdata_grp, const int n_pol, - const int n_azi, const int energy_groups, const int delayed_groups, + + //! \brief Reads fission data from the HDF5 file + void + fission_from_hdf5(const hid_t xsdata_grp, const int n_pol, const int n_azi, + const int energy_groups, const int delayed_groups, const bool is_isotropic); + public: + // The following quantities have the following dimensions: // [angle][incoming group] double_2dvec total; @@ -58,17 +66,60 @@ class XsData { std::vector scatter; XsData() = default; + + //! \brief Constructs the XsData object metadata. + //! + //! @param num_groups Number of energy groups. + //! @param num_delayed_groups Number of delayed groups. + //! @param fissionable Is this a fissionable data set or not. + //! @param scatter_format The scattering representation of the file. + //! @param n_pol Number of polar angles. + //! @param n_azi Number of azimuthal angles. XsData(const int num_groups, const int num_delayed_groups, const bool fissionable, const int scatter_format, const int n_pol, const int n_azi); - void from_hdf5(const hid_t xsdata_grp, const bool fissionable, - const int scatter_format, const int final_scatter_format, - const int order_data, const int max_order, - const int legendre_to_tabular_points, - const bool is_isotropic, const int n_pol, const int n_azi); - void combine(const std::vector& those_xs, - const double_1dvec& scalars); - bool equiv(const XsData& that); + + //! \brief Loads the XsData object from the HDF5 file + //! + //! @param xs_id HDF5 group id for the cross section data. + //! @param fissionable Is this a fissionable data set or not. + //! @param scatter_format The scattering representation of the file. + //! @param final_scatter_format The scattering representation after reading; + //! this is different from scatter_format if converting a Legendre to + //! a tabular representation. + //! @param order_data The dimensionality of the scattering data in the file. + //! @param max_order Maximum order requested by the user; + //! this is only used for Legendre scattering. + //! @param legendre_to_tabular Flag to denote if any Legendre provided + //! should be converted to a Tabular representation. + //! @param legendre_to_tabular_points If a conversion is requested, this + //! provides the number of points to use in the tabular representation. + //! @param is_isotropic Is this an isotropic or angular with respect to + //! the incoming particle. + //! @param n_pol Number of polar angles. + //! @param n_azi Number of azimuthal angles. + void + from_hdf5(const hid_t xsdata_grp, const bool fissionable, + const int scatter_format, const int final_scatter_format, + const int order_data, const int max_order, + const int legendre_to_tabular_points, const bool is_isotropic, + const int n_pol, const int n_azi); + + //! \brief Combines the microscopic data to a macroscopic object. + //! + //! @param micros Microscopic objects to combine. + //! @param scalars Scalars to multiply the microscopic data by. + void + combine(const std::vector& those_xs, const double_1dvec& scalars); + + //! \brief Checks to see if this and that are able to be combined + //! + //! This comparison is used when building macroscopic cross sections + //! from microscopic cross sections. + //! @param that The other XsData to compare to this one. + //! @return True if they can be combined. + bool + equiv(const XsData& that); }; From a1a033f1b73b29cc0961ab062303d82c66908e8f Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Sun, 17 Jun 2018 10:58:28 -0400 Subject: [PATCH 341/361] including write_message access from the C side --- src/error.F90 | 9 +++++++++ src/error.h | 21 ++++++++++++++++++++- src/mgxs_interface.cpp | 3 +-- src/mgxs_interface.h | 1 + 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/error.F90 b/src/error.F90 index d041051d2..82b142b51 100644 --- a/src/error.F90 +++ b/src/error.F90 @@ -265,4 +265,13 @@ contains end subroutine write_message + subroutine write_message_from_c(message, message_len, level) bind(C) + integer(C_INT), intent(in), value :: message_len + character(kind=C_CHAR), intent(in) :: message(message_len) + integer(C_INT), intent(in), value :: level + character(message_len+1) :: message_out + write(message_out, *) message + call write_message(message_out, level) + end subroutine write_message_from_c + end module error diff --git a/src/error.h b/src/error.h index 45d8bcded..356dfd801 100644 --- a/src/error.h +++ b/src/error.h @@ -11,7 +11,8 @@ namespace openmc { extern "C" void fatal_error_from_c(const char* message, int message_len); extern "C" void warning_from_c(const char* message, int message_len); - +extern "C" void write_message_from_c(const char* message, int message_len, + int level); inline void fatal_error(const char *message) @@ -43,5 +44,23 @@ void warning(const std::stringstream& message) warning(message.str()); } +inline +void write_message(const char* message, int level) +{ + write_message_from_c(message, strlen(message), level); +} + +inline +void write_message(const std::string& message, int level) +{ + write_message_from_c(message.c_str(), message.length(), level); +} + +inline +void write_message(const std::stringstream& message, int level) +{ + write_message(message.str(), level); +} + } // namespace openmc #endif // ERROR_H diff --git a/src/mgxs_interface.cpp b/src/mgxs_interface.cpp index 29202e49d..45234f454 100644 --- a/src/mgxs_interface.cpp +++ b/src/mgxs_interface.cpp @@ -19,8 +19,7 @@ add_mgxs_c(hid_t file_id, char* name, const int energy_groups, double_1dvec temperature; temperature.assign(temps, temps + n_temps); - // TODO: C++ replacement for write_message - // write_message("Loading " + std::string(names[i]) + " data...", 6); + write_message("Loading " + std::string(name) + " data...", 6); // Check to make sure cross section set exists in the library hid_t xs_grp; diff --git a/src/mgxs_interface.h b/src/mgxs_interface.h index 09993e981..785f72b32 100644 --- a/src/mgxs_interface.h +++ b/src/mgxs_interface.h @@ -4,6 +4,7 @@ #ifndef MGXS_INTERFACE_H #define MGXS_INTERFACE_H +#include "error.h" #include "mgxs.h" From b704480f9fcf51618af425cff11cf7fc6e0e0107 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Tue, 19 Jun 2018 07:03:54 -0400 Subject: [PATCH 342/361] Fixed python-side conversion of MGXS mesh to a lattice for input. --- openmc/mesh.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openmc/mesh.py b/openmc/mesh.py index 3f53582d5..f7cba22f8 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -208,12 +208,12 @@ class Mesh(IDManagerMixin): shape = np.array(lattice.shape) width = lattice.pitch*shape - + mesh = cls(mesh_id, name) mesh.lower_left = lattice.lower_left mesh.upper_right = lattice.lower_left + width mesh.dimension = shape*division - + return mesh def to_xml_element(self): @@ -333,14 +333,16 @@ class Mesh(IDManagerMixin): if n_dim == 1: universe_array = np.array([universes]) elif n_dim == 2: - universe_array = np.empty(self.dimension, dtype=openmc.Universe) + universe_array = np.empty(self.dimension[::-1], + dtype=openmc.Universe) i = 0 for y in range(self.dimension[1] - 1, -1, -1): for x in range(self.dimension[0]): universe_array[y][x] = universes[i] i += 1 else: - universe_array = np.empty(self.dimension, dtype=openmc.Universe) + universe_array = np.empty(self.dimension[::-1], + dtype=openmc.Universe) i = 0 for z in range(self.dimension[2]): for y in range(self.dimension[1] - 1, -1, -1): From 267acf627feafa2883545fd2d3433654b44c302b Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Thu, 21 Jun 2018 20:45:10 -0400 Subject: [PATCH 343/361] resolving @paulromano comments, including intent(inout) items at the end, consistent usage of const qualifiers --- src/constants.h | 2 +- src/mgxs.cpp | 143 ++++++++++++++++++------------ src/mgxs.h | 119 ++++++++++++------------- src/mgxs_data.F90 | 27 +----- src/mgxs_interface.F90 | 36 +++----- src/mgxs_interface.cpp | 84 ++++++++---------- src/mgxs_interface.h | 49 +++++------ src/physics_mg.F90 | 20 +---- src/scattdata.cpp | 45 +++++----- src/scattdata.h | 129 ++++++++++++++------------- src/tallies/tally.F90 | 196 +++++++++++++++++++---------------------- src/tracking.F90 | 12 +-- src/xsdata.cpp | 28 +++--- src/xsdata.h | 28 +++--- 14 files changed, 421 insertions(+), 497 deletions(-) diff --git a/src/constants.h b/src/constants.h index 3b20e68f6..016a6b348 100644 --- a/src/constants.h +++ b/src/constants.h @@ -21,7 +21,7 @@ typedef std::vector int_1dvec; typedef std::vector > int_2dvec; typedef std::vector > > int_3dvec; -int constexpr MAX_SAMPLE {10000}; +constexpr int MAX_SAMPLE {10000}; constexpr std::array VERSION {0, 10, 0}; constexpr std::array VERSION_PARTICLE_RESTART {2, 0}; diff --git a/src/mgxs.cpp b/src/mgxs.cpp index d30417c28..748e7e949 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -12,13 +12,12 @@ std::vector macro_xs; //============================================================================== void -Mgxs::init(const std::string& in_name, const double in_awr, - const double_1dvec& in_kTs, const bool in_fissionable, - const int in_scatter_format, const int in_num_groups, - const int in_num_delayed_groups, const bool in_is_isotropic, - const double_1dvec& in_polar, const double_1dvec& in_azimuthal, - const int n_threads) +Mgxs::init(const std::string& in_name, double in_awr, + const double_1dvec& in_kTs, bool in_fissionable, int in_scatter_format, + int in_num_groups, int in_num_delayed_groups, bool in_is_isotropic, + const double_1dvec& in_polar, const double_1dvec& in_azimuthal) { + // Set the metadata name = in_name; awr = in_awr; kTs = in_kTs; @@ -32,6 +31,13 @@ Mgxs::init(const std::string& in_name, const double in_awr, n_azi = in_azimuthal.size(); polar = in_polar; azimuthal = in_azimuthal; + + // Set the cross section index cache +#ifdef _OPENMP + int n_threads = omp_get_max_threads(); +#else + int n_threads = 1; +#endif cache.resize(n_threads); for (int thread = 0; thread < n_threads; thread++) { cache[thread].sqrtkT = 0.; @@ -46,10 +52,9 @@ Mgxs::init(const std::string& in_name, const double in_awr, //============================================================================== void -Mgxs::metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, - const int in_num_delayed_groups, double_1dvec& temperature, int& method, - const double tolerance, int_1dvec& temps_to_read, int& order_dim, - const int n_threads) +Mgxs::metadata_from_hdf5(hid_t xs_id, int in_num_groups, + int in_num_delayed_groups, const double_1dvec& temperature, + double tolerance, int_1dvec& temps_to_read, int& order_dim, int& method) { // get name char char_name[MAX_WORD_LEN]; @@ -252,23 +257,20 @@ Mgxs::metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, // Finally use this data to initialize the MGXS Object init(in_name, in_awr, in_kTs, in_fissionable, in_scatter_format, in_num_groups, in_num_delayed_groups, in_is_isotropic, in_polar, - in_azimuthal, n_threads); + in_azimuthal); } //============================================================================== -void -Mgxs::from_hdf5(hid_t xs_id, const int energy_groups, - const int delayed_groups, double_1dvec& temperature, int& method, - const double tolerance, const int max_order, - const bool legendre_to_tabular, const int legendre_to_tabular_points, - const int n_threads) +Mgxs::Mgxs(hid_t xs_id, int energy_groups, int delayed_groups, + const double_1dvec& temperature, double tolerance, int max_order, + bool legendre_to_tabular, int legendre_to_tabular_points, int& method) { // Call generic data gathering routine (will populate the metadata) int order_data; int_1dvec temps_to_read; metadata_from_hdf5(xs_id, energy_groups, delayed_groups, temperature, - method, tolerance, temps_to_read, order_data, n_threads); + tolerance, temps_to_read, order_data, method); // Set number of energy and delayed groups int final_scatter_format = scatter_format; @@ -297,10 +299,9 @@ Mgxs::from_hdf5(hid_t xs_id, const int energy_groups, //============================================================================== -void -Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, - std::vector& micros, double_1dvec& atom_densities, int& method, - const double tolerance, const int n_threads) +Mgxs::Mgxs(const std::string& in_name, const double_1dvec& mat_kTs, + const std::vector& micros, const double_1dvec& atom_densities, + double tolerance, int& method) { // Get the minimum data needed to initialize: // Dont need awr, but lets just initialize it anyways @@ -321,7 +322,7 @@ Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, init(in_name, in_awr, mat_kTs, in_fissionable, in_scatter_format, in_num_groups, in_num_delayed_groups, in_is_isotropic, in_polar, - in_azimuthal, n_threads); + in_azimuthal); // Create the xs data for each temperature for (int t = 0; t < mat_kTs.size(); t++) { @@ -397,8 +398,8 @@ Mgxs::build_macro(const std::string& in_name, double_1dvec& mat_kTs, //============================================================================== void -Mgxs::combine(std::vector& micros, double_1dvec& scalars, - int_1dvec& micro_ts, int this_t) +Mgxs::combine(const std::vector& micros, const double_1dvec& scalars, + const int_1dvec& micro_ts, int this_t) { // Build the vector of pointers to the xs objects within micros std::vector those_xs(micros.size()); @@ -415,36 +416,42 @@ Mgxs::combine(std::vector& micros, double_1dvec& scalars, //============================================================================== double -Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, - double* mu, int* dg) +Mgxs::get_xs(int xstype, int gin, int* gout, double* mu, int* dg) { // This method assumes that the temperature and angle indices are set +#ifdef _OPENMP + int tid = omp_get_thread_num(); XsData* xs_t = &xs[cache[tid].t]; + int a = cache[tid].a; +#else + XsData* xs_t = &xs[cache[0].t]; + int a = cache[0].a; +#endif double val; switch(xstype) { case MG_GET_XS_TOTAL: - val = xs_t->total[cache[tid].a][gin]; + val = xs_t->total[a][gin]; break; case MG_GET_XS_NU_FISSION: if (fissionable) { - val = xs_t->nu_fission[cache[tid].a][gin]; + val = xs_t->nu_fission[a][gin]; } else { val = 0.; } break; case MG_GET_XS_ABSORPTION: - val = xs_t->absorption[cache[tid].a][gin]; + val = xs_t->absorption[a][gin]; break; case MG_GET_XS_FISSION: if (fissionable) { - val = xs_t->fission[cache[tid].a][gin]; + val = xs_t->fission[a][gin]; } else { val = 0.; } break; case MG_GET_XS_KAPPA_FISSION: if (fissionable) { - val = xs_t->kappa_fission[cache[tid].a][gin]; + val = xs_t->kappa_fission[a][gin]; } else { val = 0.; } @@ -453,11 +460,11 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, case MG_GET_XS_SCATTER_MULT: case MG_GET_XS_SCATTER_FMU_MULT: case MG_GET_XS_SCATTER_FMU: - val = xs_t->scatter[cache[tid].a]->get_xs(xstype, gin, gout, mu); + val = xs_t->scatter[a]->get_xs(xstype, gin, gout, mu); break; case MG_GET_XS_PROMPT_NU_FISSION: if (fissionable) { - val = xs_t->prompt_nu_fission[cache[tid].a][gin]; + val = xs_t->prompt_nu_fission[a][gin]; } else { val = 0.; } @@ -465,10 +472,10 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, case MG_GET_XS_DELAYED_NU_FISSION: if (fissionable) { if (dg != nullptr) { - val = xs_t->delayed_nu_fission[cache[tid].a][gin][*dg]; + val = xs_t->delayed_nu_fission[a][gin][*dg]; } else { val = 0.; - for (auto& num : xs_t->delayed_nu_fission[cache[tid].a][gin]) { + for (auto& num : xs_t->delayed_nu_fission[a][gin]) { val += num; } } @@ -479,11 +486,11 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, case MG_GET_XS_CHI_PROMPT: if (fissionable) { if (gout != nullptr) { - val = xs_t->chi_prompt[cache[tid].a][gin][*gout]; + val = xs_t->chi_prompt[a][gin][*gout]; } else { // provide an outgoing group-wise sum val = 0.; - for (auto& num : xs_t->chi_prompt[cache[tid].a][gin]) { + for (auto& num : xs_t->chi_prompt[a][gin]) { val += num; } } @@ -495,20 +502,20 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, if (fissionable) { if (gout != nullptr) { if (dg != nullptr) { - val = xs_t->chi_delayed[cache[tid].a][gin][*gout][*dg]; + val = xs_t->chi_delayed[a][gin][*gout][*dg]; } else { - val = xs_t->chi_delayed[cache[tid].a][gin][*gout][0]; + val = xs_t->chi_delayed[a][gin][*gout][0]; } } else { if (dg != nullptr) { val = 0.; - for (int i = 0; i < xs_t->chi_delayed[cache[tid].a][gin].size(); i++) { - val += xs_t->chi_delayed[cache[tid].a][gin][i][*dg]; + for (int i = 0; i < xs_t->chi_delayed[a][gin].size(); i++) { + val += xs_t->chi_delayed[a][gin][i][*dg]; } } else { val = 0.; - for (int i = 0; i < xs_t->chi_delayed[cache[tid].a][gin].size(); i++) { - for (auto& num : xs_t->chi_delayed[cache[tid].a][gin][i]) { + for (int i = 0; i < xs_t->chi_delayed[a][gin].size(); i++) { + for (auto& num : xs_t->chi_delayed[a][gin][i]) { val += num; } } @@ -519,13 +526,13 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, } break; case MG_GET_XS_INVERSE_VELOCITY: - val = xs_t->inverse_velocity[cache[tid].a][gin]; + val = xs_t->inverse_velocity[a][gin]; break; case MG_GET_XS_DECAY_RATE: if (dg != nullptr) { - val = xs_t->decay_rate[cache[tid].a][*dg + 1]; + val = xs_t->decay_rate[a][*dg + 1]; } else { - val = xs_t->decay_rate[cache[tid].a][0]; + val = xs_t->decay_rate[a][0]; } break; default: @@ -537,9 +544,14 @@ Mgxs::get_xs(const int tid, const int xstype, const int gin, int* gout, //============================================================================== void -Mgxs::sample_fission_energy(const int tid, const int gin, int& dg, int& gout) +Mgxs::sample_fission_energy(int gin, int& dg, int& gout) { // This method assumes that the temperature and angle indices are set +#ifdef _OPENMP + int tid = omp_get_thread_num(); +#else + int tid = 0; +#endif XsData* xs_t = &xs[cache[tid].t]; double nu_fission = xs_t->nu_fission[cache[tid].a][gin]; @@ -596,23 +608,32 @@ Mgxs::sample_fission_energy(const int tid, const int gin, int& dg, int& gout) //============================================================================== void -Mgxs::sample_scatter(const int tid, const int gin, int& gout, double& mu, - double& wgt) +Mgxs::sample_scatter(int gin, int& gout, double& mu, double& wgt) { // This method assumes that the temperature and angle indices are set // Sample the data +#ifdef _OPENMP + int tid = omp_get_thread_num(); +#else + int tid = 0; +#endif xs[cache[tid].t].scatter[cache[tid].a]->sample(gin, gout, mu, wgt); } //============================================================================== void -Mgxs::calculate_xs(const int tid, const int gin, const double sqrtkT, - const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs) +Mgxs::calculate_xs(int gin, double sqrtkT, const double uvw[3], + double& total_xs, double& abs_xs, double& nu_fiss_xs) { // Set our indices - set_temperature_index(tid, sqrtkT); - set_angle_index(tid, uvw); +#ifdef _OPENMP + int tid = omp_get_thread_num(); +#else + int tid = 0; +#endif + set_temperature_index(sqrtkT); + set_angle_index(uvw); XsData* xs_t = &xs[cache[tid].t]; total_xs = xs_t->total[cache[tid].a][gin]; abs_xs = xs_t->absorption[cache[tid].a][gin]; @@ -646,9 +667,14 @@ Mgxs::equiv(const Mgxs& that) //============================================================================== void -Mgxs::set_temperature_index(const int tid, const double sqrtkT) +Mgxs::set_temperature_index(double sqrtkT) { // See if we need to find the new index +#ifdef _OPENMP + int tid = omp_get_thread_num(); +#else + int tid = 0; +#endif if (sqrtkT != cache[tid].sqrtkT) { double kT = sqrtkT * sqrtkT; @@ -668,9 +694,14 @@ Mgxs::set_temperature_index(const int tid, const double sqrtkT) //============================================================================== void -Mgxs::set_angle_index(const int tid, const double uvw[3]) +Mgxs::set_angle_index(const double uvw[3]) { // See if we need to find the new index +#ifdef _OPENMP + int tid = omp_get_thread_num(); +#else + int tid = 0; +#endif if (!is_isotropic && ((uvw[0] != cache[tid].u) || (uvw[1] != cache[tid].v) || (uvw[2] != cache[tid].w))) { diff --git a/src/mgxs.h b/src/mgxs.h index c0cec5941..8ef174269 100644 --- a/src/mgxs.h +++ b/src/mgxs.h @@ -11,6 +11,10 @@ #include #include + #ifdef _OPENMP + # include + #endif + #include "constants.h" #include "hdf5_interface.h" #include "math_functions.h" @@ -55,24 +59,6 @@ class Mgxs { double_1dvec polar; double_1dvec azimuthal; - //! \brief Initializes the Mgxs object metadata from the HDF5 file - //! - //! @param xs_id HDF5 group id for the cross section data. - //! @param in_num_groups Number of energy groups. - //! @param in_num_delayed_groups Number of delayed groups. - //! @param temperature Temperatures to read. - //! @param method Method of choosing nearest temperatures. - //! @param tolerance Tolerance of temperature selection method. - //! @param temps_to_read Resultant list of temperatures in the library - //! to read which correspond to the requested temperatures. - //! @param order_dim Resultant dimensionality of the scattering order. - //! @param n_threads Number of threads at runtime. - void - metadata_from_hdf5(const hid_t xs_id, const int in_num_groups, - const int in_num_delayed_groups, double_1dvec& temperature, - int& method, const double tolerance, int_1dvec& temps_to_read, - int& order_dim, const int n_threads); - //! \brief Initializes the Mgxs object metadata //! //! @param in_name Name of the object. @@ -87,14 +73,28 @@ class Mgxs { //! the incoming particle. //! @param in_polar Polar angle grid. //! @param in_azimuthal Azimuthal angle grid. - //! @param n_threads Number of threads at runtime. void - init(const std::string& in_name, const double in_awr, - const double_1dvec& in_kTs, const bool in_fissionable, - const int in_scatter_format, const int in_num_groups, - const int in_num_delayed_groups, const bool in_is_isotropic, - const double_1dvec& in_polar, const double_1dvec& in_azimuthal, - const int n_threads); + init(const std::string& in_name, double in_awr, const double_1dvec& in_kTs, + bool in_fissionable, int in_scatter_format, int in_num_groups, + int in_num_delayed_groups, bool in_is_isotropic, + const double_1dvec& in_polar, const double_1dvec& in_azimuthal); + + //! \brief Initializes the Mgxs object metadata from the HDF5 file + //! + //! @param xs_id HDF5 group id for the cross section data. + //! @param in_num_groups Number of energy groups. + //! @param in_num_delayed_groups Number of delayed groups. + //! @param temperature Temperatures to read. + //! @param tolerance Tolerance of temperature selection method. + //! @param temps_to_read Resultant list of temperatures in the library + //! to read which correspond to the requested temperatures. + //! @param order_dim Resultant dimensionality of the scattering order. + //! @param method Method of choosing nearest temperatures. + void + metadata_from_hdf5(hid_t xs_id, int in_num_groups, + int in_num_delayed_groups, const double_1dvec& temperature, + double tolerance, int_1dvec& temps_to_read, int& order_dim, + int& method); //! \brief Performs the actual act of combining the microscopic data for a //! single temperature. @@ -105,8 +105,8 @@ class Mgxs { //! corresponds to the temperature of interest. //! @param this_t The temperature index of the macroscopic object. void - combine(std::vector& micros, double_1dvec& scalars, - int_1dvec& micro_ts, int this_t); + combine(const std::vector& micros, const double_1dvec& scalars, + const int_1dvec& micro_ts, int this_t); //! \brief Checks to see if this and that are able to be combined //! @@ -123,28 +123,14 @@ class Mgxs { bool fissionable; // Is this fissionable std::vector cache; // index and data cache - //! \brief Initializes and populates all data to build a macroscopic - //! cross section from microscopic cross section. - //! - //! @param in_name Name of the object. - //! @param mat_kTs temperatures (in units of eV) that data is needed. - //! @param micros Microscopic objects to combine. - //! @param atom_densities Atom densities of those microscopic quantities. - //! @param method Method of choosing nearest temperatures. - //! @param tolerance Tolerance of temperature selection method. - //! @param n_threads Number of threads at runtime. - void - build_macro(const std::string& in_name, double_1dvec& mat_kTs, - std::vector& micros, double_1dvec& atom_densities, - int& method, const double tolerance, const int n_threads); + Mgxs() = default; - //! \brief Loads the Mgxs object from the HDF5 file + //! \brief Constructor that loads the Mgxs object from the HDF5 file //! //! @param xs_id HDF5 group id for the cross section data. //! @param energy_groups Number of energy groups. //! @param delayed_groups Number of delayed groups. //! @param temperature Temperatures to read. - //! @param method Method of choosing nearest temperatures. //! @param tolerance Tolerance of temperature selection method. //! @param max_order Maximum order requested by the user; //! this is only used for Legendre scattering. @@ -152,13 +138,24 @@ class Mgxs { //! should be converted to a Tabular representation. //! @param legendre_to_tabular_points If a conversion is requested, this //! provides the number of points to use in the tabular representation. - //! @param n_threads Number of threads at runtime. - void - from_hdf5(hid_t xs_id, const int energy_groups, - const int delayed_groups, double_1dvec& temperature, int& method, - const double tolerance, const int max_order, - const bool legendre_to_tabular, const int legendre_to_tabular_points, - const int n_threads); + //! @param method Method of choosing nearest temperatures. + Mgxs(hid_t xs_id, int energy_groups, + int delayed_groups, const double_1dvec& temperature, double tolerance, + int max_order, bool legendre_to_tabular, + int legendre_to_tabular_points, int& method); + + //! \brief Constructor that initializes and populates all data to build a + //! macroscopic cross section from microscopic cross section. + //! + //! @param in_name Name of the object. + //! @param mat_kTs temperatures (in units of eV) that data is needed. + //! @param micros Microscopic objects to combine. + //! @param atom_densities Atom densities of those microscopic quantities. + //! @param tolerance Tolerance of temperature selection method. + //! @param method Method of choosing nearest temperatures. + Mgxs(const std::string& in_name, const double_1dvec& mat_kTs, + const std::vector& micros, const double_1dvec& atom_densities, + double tolerance, int& method); //! \brief Provides a cross section value given certain parameters //! @@ -172,32 +169,27 @@ class Mgxs { //! @param dg delayed group index; use nullptr if irrelevant. //! @return Requested cross section value. double - get_xs(const int tid, const int xstype, const int gin, int* gout, - double* mu, int* dg); + get_xs(int xstype, int gin, int* gout, double* mu, int* dg); //! \brief Samples the fission neutron energy and if prompt or delayed. //! - //! @param tid Thread id to use when using the index cache. //! @param gin Incoming energy group. //! @param dg Sampled delayed group index. //! @param gout Sampled outgoing energy group. void - sample_fission_energy(const int tid, const int gin, int& dg, int& gout); + sample_fission_energy(int gin, int& dg, int& gout); //! \brief Samples the outgoing energy and angle from a scatter event. //! - //! @param tid Thread id to use when using the index cache. //! @param gin Incoming energy group. //! @param gout Sampled outgoing energy group. //! @param mu Sampled cosine of the change-in-angle. //! @param wgt Weight of the particle to be adjusted. void - sample_scatter(const int tid, const int gin, int& gout, double& mu, - double& wgt); + sample_scatter(int gin, int& gout, double& mu, double& wgt); //! \brief Calculates cross section quantities needed for tracking. //! - //! @param tid Thread id to use when using the index cache. //! @param gin Incoming energy group. //! @param sqrtkT Temperature of the material. //! @param uvw Incoming particle direction. @@ -205,23 +197,20 @@ class Mgxs { //! @param abs_xs Resultant absorption cross section. //! @param nu_fiss_xs Resultant nu-fission cross section. void - calculate_xs(const int tid, const int gin, const double sqrtkT, - const double uvw[3], double& total_xs, double& abs_xs, - double& nu_fiss_xs); + calculate_xs(int gin, double sqrtkT, const double uvw[3], + double& total_xs, double& abs_xs, double& nu_fiss_xs); //! \brief Sets the temperature index in cache given a temperature //! - //! @param tid Thread id to use when setting the index cache. //! @param sqrtkT Temperature of the material. void - set_temperature_index(const int tid, const double sqrtkT); + set_temperature_index(double sqrtkT); //! \brief Sets the angle index in cache given a direction //! - //! @param tid Thread id to use when setting the index cache. //! @param uvw Incoming particle direction. void - set_angle_index(const int tid, const double uvw[3]); + set_angle_index(const double uvw[3]); }; } // namespace openmc diff --git a/src/mgxs_data.F90 b/src/mgxs_data.F90 index ef416125e..1b3ed3511 100644 --- a/src/mgxs_data.F90 +++ b/src/mgxs_data.F90 @@ -2,10 +2,6 @@ module mgxs_data use, intrinsic :: ISO_C_BINDING -#ifdef _OPENMP - use omp_lib -#endif - use constants use algorithm, only: find use dict_header, only: DictCharInt @@ -40,13 +36,6 @@ contains type(VectorReal), allocatable, target :: temps(:) character(MAX_WORD_LEN) :: word integer, allocatable :: array(:) - integer(C_INT) :: n_threads - -#ifdef _OPENMP - n_threads = OMP_GET_MAX_THREADS() -#else - n_threads = 1 -#endif ! Check if MGXS Library exists inquire(FILE=path_cross_sections, EXIST=file_exists) @@ -91,12 +80,11 @@ contains i_nuclide = mat % nuclide(j) if (.not. already_read % contains(name)) then - call add_mgxs_c(file_id, name, & - num_energy_groups, num_delayed_groups, & + call add_mgxs_c(file_id, name, num_energy_groups, num_delayed_groups, & temps(i_nuclide) % size(), temps(i_nuclide) % data, & - temperature_method, temperature_tolerance, max_order, & + temperature_tolerance, max_order, & logical(legendre_to_tabular, C_BOOL), & - legendre_to_tabular_points, n_threads) + legendre_to_tabular_points, temperature_method) call already_read % add(name) end if @@ -122,13 +110,6 @@ contains type(Material), pointer :: mat ! current material type(VectorReal), allocatable :: kTs(:) character(MAX_WORD_LEN) :: name ! name of material - integer(C_INT) :: n_threads - -#ifdef _OPENMP - n_threads = OMP_GET_MAX_THREADS() -#else - n_threads = 1 -#endif ! Get temperatures to read for each material call get_mat_kTs(kTs) @@ -149,7 +130,7 @@ contains if (allocated(kTs(i_mat) % data)) then call create_macro_xs_c(name, mat % n_nuclides, mat % nuclide, & kTs(i_mat) % size(), kTs(i_mat) % data, mat % atom_density, & - temperature_method, temperature_tolerance, n_threads) + temperature_tolerance, temperature_method) end if end do diff --git a/src/mgxs_interface.F90 b/src/mgxs_interface.F90 index 34b0fb611..2a579e930 100644 --- a/src/mgxs_interface.F90 +++ b/src/mgxs_interface.F90 @@ -9,8 +9,8 @@ module mgxs_interface interface subroutine add_mgxs_c(file_id, name, energy_groups, delayed_groups, & - n_temps, temps, method, tolerance, max_order, legendre_to_tabular, & - legendre_to_tabular_points, n_threads) bind(C) + n_temps, temps, tolerance, max_order, legendre_to_tabular, & + legendre_to_tabular_points, method) bind(C) use ISO_C_BINDING import HID_T implicit none @@ -20,12 +20,11 @@ module mgxs_interface integer(C_INT), value, intent(in) :: delayed_groups integer(C_INT), value, intent(in) :: n_temps real(C_DOUBLE), intent(in) :: temps(1:n_temps) - integer(C_INT), intent(inout) :: method real(C_DOUBLE), value, intent(in) :: tolerance integer(C_INT), value, intent(in) :: max_order logical(C_BOOL),value, intent(in) :: legendre_to_tabular integer(C_INT), value, intent(in) :: legendre_to_tabular_points - integer(C_INT), value, intent(in) :: n_threads + integer(C_INT), intent(inout) :: method end subroutine add_mgxs_c function query_fissionable_c(n_nuclides, i_nuclides) result(result) bind(C) @@ -37,7 +36,7 @@ module mgxs_interface end function query_fissionable_c subroutine create_macro_xs_c(name, n_nuclides, i_nuclides, n_temps, temps, & - atom_densities, method, tolerance, n_threads) bind(C) + atom_densities, tolerance, method) bind(C) use ISO_C_BINDING implicit none character(kind=C_CHAR),intent(in) :: name(*) @@ -46,17 +45,15 @@ module mgxs_interface integer(C_INT), value, intent(in) :: n_temps real(C_DOUBLE), intent(in) :: temps(1:n_temps) real(C_DOUBLE), intent(in) :: atom_densities(1:n_nuclides) - integer(C_INT), intent(inout) :: method real(C_DOUBLE), value, intent(in) :: tolerance - integer(C_INT), value, intent(in) :: n_threads + integer(C_INT), intent(inout) :: method end subroutine create_macro_xs_c - subroutine calculate_xs_c(i_mat, tid, gin, sqrtkT, uvw, total_xs, abs_xs, & + subroutine calculate_xs_c(i_mat, gin, sqrtkT, uvw, total_xs, abs_xs, & nu_fiss_xs) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: i_mat - integer(C_INT), value, intent(in) :: tid integer(C_INT), value, intent(in) :: gin real(C_DOUBLE), value, intent(in) :: sqrtkT real(C_DOUBLE), intent(in) :: uvw(1:3) @@ -65,11 +62,10 @@ module mgxs_interface real(C_DOUBLE), intent(inout) :: nu_fiss_xs end subroutine calculate_xs_c - subroutine sample_scatter_c(i_mat, tid, gin, gout, mu, wgt, uvw) bind(C) + subroutine sample_scatter_c(i_mat, gin, gout, mu, wgt, uvw) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: i_mat - integer(C_INT), value, intent(in) :: tid integer(C_INT), value, intent(in) :: gin integer(C_INT), intent(inout) :: gout real(C_DOUBLE), intent(inout) :: mu @@ -77,11 +73,10 @@ module mgxs_interface real(C_DOUBLE), intent(inout) :: uvw(1:3) end subroutine sample_scatter_c - subroutine sample_fission_energy_c(i_mat, tid, gin, dg, gout) bind(C) + subroutine sample_fission_energy_c(i_mat, gin, dg, gout) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: i_mat - integer(C_INT), value, intent(in) :: tid integer(C_INT), value, intent(in) :: gin integer(C_INT), intent(inout) :: dg integer(C_INT), intent(inout) :: gout @@ -102,12 +97,11 @@ module mgxs_interface real(C_DOUBLE) :: awr end function get_awr_c - function get_nuclide_xs_c(index, tid, xstype, gin, gout, mu, dg) result(val) & + function get_nuclide_xs_c(index, xstype, gin, gout, mu, dg) result(val) & bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: index - integer(C_INT), value, intent(in) :: tid integer(C_INT), value, intent(in) :: xstype integer(C_INT), value, intent(in) :: gin integer(C_INT), optional, intent(in) :: gout @@ -116,12 +110,11 @@ module mgxs_interface real(C_DOUBLE) :: val end function get_nuclide_xs_c - function get_macro_xs_c(index, tid, xstype, gin, gout, mu, dg) result(val) & + function get_macro_xs_c(index, xstype, gin, gout, mu, dg) result(val) & bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: index - integer(C_INT), value, intent(in) :: tid integer(C_INT), value, intent(in) :: xstype integer(C_INT), value, intent(in) :: gin integer(C_INT), optional, intent(in) :: gout @@ -130,27 +123,24 @@ module mgxs_interface real(C_DOUBLE) :: val end function get_macro_xs_c - subroutine set_nuclide_angle_index_c(index, tid, uvw) bind(C) + subroutine set_nuclide_angle_index_c(index, uvw) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: index - integer(C_INT), value, intent(in) :: tid real(C_DOUBLE), intent(in) :: uvw(1:3) end subroutine set_nuclide_angle_index_c - subroutine set_macro_angle_index_c(index, tid, uvw) bind(C) + subroutine set_macro_angle_index_c(index, uvw) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: index - integer(C_INT), value, intent(in) :: tid real(C_DOUBLE), intent(in) :: uvw(1:3) end subroutine set_macro_angle_index_c - subroutine set_nuclide_temperature_index_c(index, tid, sqrtkT) bind(C) + subroutine set_nuclide_temperature_index_c(index, sqrtkT) bind(C) use ISO_C_BINDING implicit none integer(C_INT), value, intent(in) :: index - integer(C_INT), value, intent(in) :: tid real(C_DOUBLE), value, intent(in) :: sqrtkT end subroutine set_nuclide_temperature_index_c diff --git a/src/mgxs_interface.cpp b/src/mgxs_interface.cpp index 45234f454..a96f53008 100644 --- a/src/mgxs_interface.cpp +++ b/src/mgxs_interface.cpp @@ -7,11 +7,10 @@ namespace openmc { //============================================================================== void -add_mgxs_c(hid_t file_id, char* name, const int energy_groups, - const int delayed_groups, const int n_temps, double temps[], int& method, - const double tolerance, const int max_order, - const bool legendre_to_tabular, const int legendre_to_tabular_points, - const int n_threads) +add_mgxs_c(hid_t file_id, const char* name, int energy_groups, + int delayed_groups, int n_temps, const double temps[], double tolerance, + int max_order, bool legendre_to_tabular, int legendre_to_tabular_points, + int& method) { //!! mgxs_data.F90 will be modified to just create the list of names //!! in the order needed @@ -30,10 +29,8 @@ add_mgxs_c(hid_t file_id, char* name, const int energy_groups, + "provided MGXS Library"); } - Mgxs mg; - mg.from_hdf5(xs_grp, energy_groups, delayed_groups, - temperature, method, tolerance, max_order, legendre_to_tabular, - legendre_to_tabular_points, n_threads); + Mgxs mg(xs_grp, energy_groups, delayed_groups, temperature, tolerance, + max_order, legendre_to_tabular, legendre_to_tabular_points, method); nuclides_MG.push_back(mg); } @@ -41,7 +38,7 @@ add_mgxs_c(hid_t file_id, char* name, const int energy_groups, //============================================================================== bool -query_fissionable_c(const int n_nuclides, const int i_nuclides[]) +query_fissionable_c(int n_nuclides, const int i_nuclides[]) { bool result = false; for (int n = 0; n < n_nuclides; n++) { @@ -53,12 +50,10 @@ query_fissionable_c(const int n_nuclides, const int i_nuclides[]) //============================================================================== void -create_macro_xs_c(char* mat_name, const int n_nuclides, - const int i_nuclides[], const int n_temps, const double temps[], - const double atom_densities[], int& method, const double tolerance, - const int n_threads) +create_macro_xs_c(const char* mat_name, int n_nuclides, const int i_nuclides[], + int n_temps, const double temps[], const double atom_densities[], + double tolerance, int& method) { - Mgxs macro; if (n_temps > 0) { // // Convert temps to a vector double_1dvec temperature; @@ -75,10 +70,14 @@ create_macro_xs_c(char* mat_name, const int n_nuclides, mgxs_ptr[n] = &nuclides_MG[i_nuclides[n] - 1]; } - macro.build_macro(mat_name, temperature, mgxs_ptr, atom_densities_vec, - method, tolerance, n_threads); + Mgxs macro(mat_name, temperature, mgxs_ptr, atom_densities_vec, + tolerance, method); + macro_xs.push_back(macro); + } else { + // Preserve the ordering of materials by including a blank entry + Mgxs macro; + macro_xs.push_back(macro); } - macro_xs.push_back(macro); } //============================================================================== @@ -86,22 +85,21 @@ create_macro_xs_c(char* mat_name, const int n_nuclides, //============================================================================== void -calculate_xs_c(const int i_mat, const int tid, const int gin, - const double sqrtkT, const double uvw[3], double& total_xs, double& abs_xs, - double& nu_fiss_xs) +calculate_xs_c(int i_mat, int gin, double sqrtkT, const double uvw[3], + double& total_xs, double& abs_xs, double& nu_fiss_xs) { - macro_xs[i_mat - 1].calculate_xs(tid, gin - 1, sqrtkT, uvw, total_xs, abs_xs, + macro_xs[i_mat - 1].calculate_xs(gin - 1, sqrtkT, uvw, total_xs, abs_xs, nu_fiss_xs); } //============================================================================== void -sample_scatter_c(const int i_mat, const int tid, const int gin, int& gout, - double& mu, double& wgt, double uvw[3]) +sample_scatter_c(int i_mat, int gin, int& gout, double& mu, double& wgt, + double uvw[3]) { int gout_c = gout - 1; - macro_xs[i_mat - 1].sample_scatter(tid, gin - 1, gout_c, mu, wgt); + macro_xs[i_mat - 1].sample_scatter(gin - 1, gout_c, mu, wgt); // adjust return value for fortran indexing gout = gout_c + 1; @@ -113,12 +111,11 @@ sample_scatter_c(const int i_mat, const int tid, const int gin, int& gout, //============================================================================== void -sample_fission_energy_c(const int i_mat, const int tid, const int gin, - int& dg, int& gout) +sample_fission_energy_c(int i_mat, int gin, int& dg, int& gout) { int dg_c = 0; int gout_c = 0; - macro_xs[i_mat - 1].sample_fission_energy(tid, gin - 1, dg_c, gout_c); + macro_xs[i_mat - 1].sample_fission_energy(gin - 1, dg_c, gout_c); // adjust return values for fortran indexing dg = dg_c + 1; @@ -128,8 +125,7 @@ sample_fission_energy_c(const int i_mat, const int tid, const int gin, //============================================================================== double -get_nuclide_xs_c(const int index, const int tid, const int xstype, - const int gin, int* gout, double* mu, int* dg) +get_nuclide_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg) { int gout_c; int* gout_c_p; @@ -147,15 +143,13 @@ get_nuclide_xs_c(const int index, const int tid, const int xstype, } else { dg_c_p = dg; } - return nuclides_MG[index - 1].get_xs(tid, xstype, gin - 1, gout_c_p, mu, - dg_c_p); + return nuclides_MG[index - 1].get_xs(xstype, gin - 1, gout_c_p, mu, dg_c_p); } //============================================================================== double -get_macro_xs_c(const int index, const int tid, const int xstype, - const int gin, int* gout, double* mu, int* dg) +get_macro_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg) { int gout_c; int* gout_c_p; @@ -173,38 +167,34 @@ get_macro_xs_c(const int index, const int tid, const int xstype, } else { dg_c_p = dg; } - return macro_xs[index - 1].get_xs(tid, xstype, gin - 1, gout_c_p, mu, - dg_c_p); + return macro_xs[index - 1].get_xs(xstype, gin - 1, gout_c_p, mu, dg_c_p); } //============================================================================== void -set_nuclide_angle_index_c(const int index, const int tid, - const double uvw[3]) +set_nuclide_angle_index_c(int index, const double uvw[3]) { // Update the values - nuclides_MG[index - 1].set_angle_index(tid, uvw); + nuclides_MG[index - 1].set_angle_index(uvw); } //============================================================================== void -set_macro_angle_index_c(const int index, const int tid, - const double uvw[3]) +set_macro_angle_index_c(int index, const double uvw[3]) { // Update the values - macro_xs[index - 1].set_angle_index(tid, uvw); + macro_xs[index - 1].set_angle_index(uvw); } //============================================================================== void -set_nuclide_temperature_index_c(const int index, const int tid, - const double sqrtkT) +set_nuclide_temperature_index_c(int index, double sqrtkT) { // Update the values - nuclides_MG[index - 1].set_temperature_index(tid, sqrtkT); + nuclides_MG[index - 1].set_temperature_index(sqrtkT); } //============================================================================== @@ -212,7 +202,7 @@ set_nuclide_temperature_index_c(const int index, const int tid, //============================================================================== void -get_name_c(const int index, int name_len, char* name) +get_name_c(int index, int name_len, char* name) { // First blank out our input string std::string str(name_len, ' '); @@ -229,7 +219,7 @@ get_name_c(const int index, int name_len, char* name) //============================================================================== double -get_awr_c(const int index) +get_awr_c(int index) { return nuclides_MG[index - 1].awr; } diff --git a/src/mgxs_interface.h b/src/mgxs_interface.h index 785f72b32..2cdd2889a 100644 --- a/src/mgxs_interface.h +++ b/src/mgxs_interface.h @@ -22,67 +22,58 @@ extern std::vector macro_xs; //============================================================================== extern "C" void -add_mgxs_c(hid_t file_id, char* name, const int energy_groups, - const int delayed_groups, const int n_temps, double temps[], int& method, - const double tolerance, const int max_order, - const bool legendre_to_tabular, const int legendre_to_tabular_points, - const int n_threads); +add_mgxs_c(hid_t file_id, const char* name, int energy_groups, + int delayed_groups, int n_temps, const double temps[], double tolerance, + int max_order, bool legendre_to_tabular, int legendre_to_tabular_points, + int& method); extern "C" bool -query_fissionable_c(const int n_nuclides, const int i_nuclides[]); +query_fissionable_c(int n_nuclides, const int i_nuclides[]); extern "C" void -create_macro_xs_c(char* mat_name, const int n_nuclides, - const int i_nuclides[], const int n_temps, const double temps[], - const double atom_densities[], int& method, const double tolerance, - const int n_threads); +create_macro_xs_c(const char* mat_name, int n_nuclides, const int i_nuclides[], + int n_temps, const double temps[], const double atom_densities[], + double tolerance, int& method); //============================================================================== // Mgxs tracking/transport/tallying interface methods //============================================================================== extern "C" void -calculate_xs_c(const int i_mat, const int tid, const int gin, - const double sqrtkT, const double uvw[3], double& total_xs, - double& abs_xs, double& nu_fiss_xs); +calculate_xs_c(int i_mat, int gin, double sqrtkT, const double uvw[3], + double& total_xs, double& abs_xs, double& nu_fiss_xs); extern "C" void -sample_scatter_c(const int i_mat, const int tid, const int gin, - int& gout, double& mu, double& wgt, double uvw[3]); +sample_scatter_c(int i_mat, int gin, int& gout, double& mu, double& wgt, + double uvw[3]); extern "C" void -sample_fission_energy_c(const int i_mat, const int tid, - const int gin, int& dg, int& gout); +sample_fission_energy_c(int i_mat, int gin, int& dg, int& gout); extern "C" double -get_nuclide_xs_c(const int index, const int tid, - const int xstype, const int gin, int* gout, double* mu, int* dg); +get_nuclide_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg); extern "C" double -get_macro_xs_c(const int index, const int tid, - const int xstype, const int gin, int* gout, double* mu, int* dg); +get_macro_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg); extern "C" void -set_nuclide_angle_index_c(const int index, const int tid, - const double uvw[3]); +set_nuclide_angle_index_c(int index, const double uvw[3]); extern "C" void -set_macro_angle_index_c(const int index, const int tid, - const double uvw[3]); +set_macro_angle_index_c(int index, const double uvw[3]); extern "C" void -set_nuclide_temperature_index_c(const int index, const int tid, - const double sqrtkT); +set_nuclide_temperature_index_c(int index, double sqrtkT); //============================================================================== // General Mgxs methods //============================================================================== extern "C" void -get_name_c(const int index, int name_len, char* name); +get_name_c(int index, int name_len, char* name); extern "C" double -get_awr_c(const int index); +get_awr_c(int index); } // namespace openmc #endif // MGXS_INTERFACE_H \ No newline at end of file diff --git a/src/physics_mg.F90 b/src/physics_mg.F90 index d04d4cb85..a7a183295 100644 --- a/src/physics_mg.F90 +++ b/src/physics_mg.F90 @@ -2,10 +2,6 @@ module physics_mg ! This module contains the multi-group specific physics routines so as to not ! hinder performance of the CE versions with multiple if-thens. -#ifdef _OPENMP - use omp_lib -#endif - use bank_header use constants use error, only: fatal_error, warning, write_message @@ -145,14 +141,8 @@ contains subroutine scatter(p) type(Particle), intent(inout) :: p - integer(C_INT) :: tid -#ifdef _OPENMP - tid = OMP_GET_THREAD_NUM() -#else - tid = 0 -#endif - call sample_scatter_c(p % material, tid, p % last_g, p % g, p % mu, & + call sample_scatter_c(p % material, p % last_g, p % g, p % mu, & p % wgt, p % coord(1) % uvw) ! Update energy value for downstream compatability (in tallying) @@ -183,12 +173,6 @@ contains real(8) :: mu ! fission neutron angular cosine real(8) :: phi ! fission neutron azimuthal angle real(8) :: weight ! weight adjustment for ufs method - integer(C_INT) :: tid -#ifdef _OPENMP - tid = OMP_GET_THREAD_NUM() -#else - tid = 0 -#endif ! TODO: Heat generation from fission @@ -268,7 +252,7 @@ contains ! Sample secondary energy distribution for fission reaction and set energy ! in fission bank - call sample_fission_energy_c(p % material, tid, p % g, dg, gout) + call sample_fission_energy_c(p % material, p % g, dg, gout) bank_array(i) % E = real(gout, 8) bank_array(i) % delayed_group = dg diff --git a/src/scattdata.cpp b/src/scattdata.cpp index 952c05f64..ad6ae21a6 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -7,8 +7,9 @@ namespace openmc { //============================================================================== void -ScattData::base_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, - double_2dvec& in_energy, double_2dvec& in_mult) +ScattData::base_init(int order, const int_1dvec& in_gmin, + const int_1dvec& in_gmax, const double_2dvec& in_energy, + const double_2dvec& in_mult) { int groups = in_energy.size(); @@ -42,7 +43,7 @@ ScattData::base_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, //============================================================================== void -ScattData::base_combine(const int max_order, +ScattData::base_combine(int max_order, const std::vector& those_scatts, const double_1dvec& scalars, int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& sparse_mult, double_3dvec& sparse_scatter) @@ -179,8 +180,7 @@ ScattData::sample_energy(int gin, int& gout, int& i_gout) //============================================================================== double -ScattData::get_xs(const int xstype, const int gin, const int* gout, - const double* mu) +ScattData::get_xs(int xstype, int gin, const int* gout, const double* mu) { // Set the outgoing group offset index as needed int i_gout = 0; @@ -239,8 +239,8 @@ ScattData::get_xs(const int xstype, const int gin, const int* gout, //============================================================================== void -ScattDataLegendre::init(int_1dvec& in_gmin, int_1dvec& in_gmax, - double_2dvec& in_mult, double_3dvec& coeffs) +ScattDataLegendre::init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs) { int groups = coeffs.size(); int order = coeffs[0][0].size(); @@ -334,7 +334,7 @@ ScattDataLegendre::update_max_val() //============================================================================== double -ScattDataLegendre::calc_f(const int gin, const int gout, const double mu) +ScattDataLegendre::calc_f(int gin, int gout, double mu) { double f; if ((gout < gmin[gin]) || (gout > gmax[gin])) { @@ -350,7 +350,7 @@ ScattDataLegendre::calc_f(const int gin, const int gout, const double mu) //============================================================================== void -ScattDataLegendre::sample(const int gin, int& gout, double& mu, double& wgt) +ScattDataLegendre::sample(int gin, int& gout, double& mu, double& wgt) { // Sample the outgoing energy using the base-class method int i_gout; @@ -408,7 +408,7 @@ ScattDataLegendre::combine(const std::vector& those_scatts, // so we use a base class method to sum up xs and create new energy and mult // matrices ScattData::base_combine(max_order, those_scatts, scalars, in_gmin, in_gmax, - sparse_mult, sparse_scatter); + sparse_mult, sparse_scatter); // Got everything we need, store it. init(in_gmin, in_gmax, sparse_mult, sparse_scatter); @@ -417,7 +417,7 @@ ScattDataLegendre::combine(const std::vector& those_scatts, //============================================================================== double_3dvec -ScattDataLegendre::get_matrix(const int max_order) +ScattDataLegendre::get_matrix(int max_order) { // Get the sizes and initialize the data to 0 int groups = energy.size(); @@ -442,8 +442,8 @@ ScattDataLegendre::get_matrix(const int max_order) //============================================================================== void -ScattDataHistogram::init(int_1dvec& in_gmin, int_1dvec& in_gmax, - double_2dvec& in_mult, double_3dvec& coeffs) +ScattDataHistogram::init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs) { int groups = coeffs.size(); int order = coeffs[0][0].size(); @@ -479,8 +479,7 @@ ScattDataHistogram::init(int_1dvec& in_gmin, int_1dvec& in_gmax, } // Initialize the base class attributes - ScattData::base_init(order, in_gmin, in_gmax, in_energy, - in_mult); + ScattData::base_init(order, in_gmin, in_gmax, in_energy, in_mult); // Build the angular distribution mu values mu = double_1dvec(order); @@ -522,7 +521,7 @@ ScattDataHistogram::init(int_1dvec& in_gmin, int_1dvec& in_gmax, //============================================================================== double -ScattDataHistogram::calc_f(const int gin, const int gout, const double mu) +ScattDataHistogram::calc_f(int gin, int gout, double mu) { double f; if ((gout < gmin[gin]) || (gout > gmax[gin])) { @@ -546,7 +545,7 @@ ScattDataHistogram::calc_f(const int gin, const int gout, const double mu) //============================================================================== void -ScattDataHistogram::sample(const int gin, int& gout, double& mu, double& wgt) +ScattDataHistogram::sample(int gin, int& gout, double& mu, double& wgt) { // Sample the outgoing energy using the base-class method int i_gout; @@ -581,7 +580,7 @@ ScattDataHistogram::sample(const int gin, int& gout, double& mu, double& wgt) //============================================================================== double_3dvec -ScattDataHistogram::get_matrix(const int max_order) +ScattDataHistogram::get_matrix(int max_order) { // Get the sizes and initialize the data to 0 int groups = energy.size(); @@ -643,8 +642,8 @@ ScattDataHistogram::combine(const std::vector& those_scatts, //============================================================================== void -ScattDataTabular::init(int_1dvec& in_gmin, int_1dvec& in_gmax, - double_2dvec& in_mult, double_3dvec& coeffs) +ScattDataTabular::init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs) { int groups = coeffs.size(); int order = coeffs[0][0].size(); @@ -732,7 +731,7 @@ ScattDataTabular::init(int_1dvec& in_gmin, int_1dvec& in_gmax, //============================================================================== double -ScattDataTabular::calc_f(const int gin, const int gout, const double mu) +ScattDataTabular::calc_f(int gin, int gout, double mu) { double f; if ((gout < gmin[gin]) || (gout > gmax[gin])) { @@ -757,7 +756,7 @@ ScattDataTabular::calc_f(const int gin, const int gout, const double mu) //============================================================================== void -ScattDataTabular::sample(const int gin, int& gout, double& mu, double& wgt) +ScattDataTabular::sample(int gin, int& gout, double& mu, double& wgt) { // Sample the outgoing energy using the base-class method int i_gout; @@ -805,7 +804,7 @@ ScattDataTabular::sample(const int gin, int& gout, double& mu, double& wgt) //============================================================================== double_3dvec -ScattDataTabular::get_matrix(const int max_order) +ScattDataTabular::get_matrix(int max_order) { // Get the sizes and initialize the data to 0 int groups = energy.size(); diff --git a/src/scattdata.h b/src/scattdata.h index 69ce9e245..888f96052 100644 --- a/src/scattdata.h +++ b/src/scattdata.h @@ -29,18 +29,17 @@ class ScattData { protected: //! \brief Initializes the attributes of the base class. void - base_init(int order, int_1dvec& in_gmin, int_1dvec& in_gmax, - double_2dvec& in_energy, double_2dvec& in_mult); - + base_init(int order, const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_energy, const double_2dvec& in_mult); + //! \brief Combines microscopic ScattDatas into a macroscopic one. void - base_combine(const int max_order, - const std::vector& those_scatts, + base_combine(int max_order, const std::vector& those_scatts, const double_1dvec& scalars, int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& sparse_mult, double_3dvec& sparse_scatter); - + public: - + double_2dvec energy; // Normalized p0 matrix for sampling Eout double_2dvec mult; // nu-scatter multiplication (nu-scatt/scatt) double_3dvec dist; // Angular distribution @@ -51,15 +50,15 @@ class ScattData { //! \brief Calculates the value of normalized f(mu). //! //! The value of f(mu) is normalized as in the integral of f(mu)dmu across - //! [-1,1] is 1. + //! [-1,1] is 1. //! //! @param gin Incoming energy group of interest. //! @param gout Outgoing energy group of interest. //! @param mu Cosine of the change-in-angle of interest. //! @return The value of f(mu). virtual double - calc_f(const int gin, const int gout, const double mu) = 0; - + calc_f(int gin, int gout, double mu) = 0; + //! \brief Samples the outgoing energy and angle from the ScattData info. //! //! @param gin Incoming energy group. @@ -67,8 +66,8 @@ class ScattData { //! @param mu Sampled cosine of the change-in-angle. //! @param wgt Weight of the particle to be adjusted. virtual void - sample(const int gin, int& gout, double& mu, double& wgt) = 0; - + sample(int gin, int& gout, double& mu, double& wgt) = 0; + //! \brief Initializes the ScattData object from a given scatter and //! multiplicity matrix. //! @@ -77,9 +76,9 @@ class ScattData { //! @param in_mult Input sparse multiplicity matrix //! @param coeffs Input sparse scattering matrix virtual void - init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, - double_3dvec& coeffs) = 0; - + init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs) = 0; + //! \brief Combines the microscopic data. //! //! @param those_scatts Microscopic objects to combine. @@ -87,7 +86,7 @@ class ScattData { virtual void combine(const std::vector& those_scatts, const double_1dvec& scalars) = 0; - + //! \brief Getter for the dimensionality of the scattering order. //! //! If Legendre this is the "n" in "Pn"; for Tabular, this is the number @@ -96,14 +95,14 @@ class ScattData { //! @return The order. virtual int get_order() = 0; - + //! \brief Builds a dense scattering matrix from the constituent parts //! //! @param max_order If Legendre this is the maximum value of "n" in "Pn" //! requested; ignored otherwise. //! @return The dense scattering matrix. virtual double_3dvec - get_matrix(const int max_order) = 0; + get_matrix(int max_order) = 0; //! \brief Samples the outgoing energy from the ScattData info. //! @@ -124,7 +123,7 @@ class ScattData { //! use nullptr if irrelevant. //! @return Requested cross section value. double - get_xs(const int xstype, const int gin, const int* gout, const double* mu); + get_xs(int xstype, int gin, const int* gout, const double* mu); }; //============================================================================== @@ -132,44 +131,44 @@ class ScattData { //============================================================================== class ScattDataLegendre: public ScattData { - + protected: - + // Maximal value for rejection sampling from a rectangle double_2dvec max_val; // Friend convert_legendre_to_tabular so it has access to protected // parameters friend void - convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, - int n_mu); - + convert_legendre_to_tabular(ScattDataLegendre& leg, + ScattDataTabular& tab, int n_mu); + public: - + void - init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, - double_3dvec& coeffs); + init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs); void combine(const std::vector& those_scatts, - const double_1dvec& scalars); - + const double_1dvec& scalars); + //! \brief Find the maximal value of the angular distribution to use as a // bounding box with rejection sampling. void update_max_val(); - + double - calc_f(const int gin, const int gout, const double mu); - + calc_f(int gin, int gout, double mu); + void - sample(const int gin, int& gout, double& mu, double& wgt); - + sample(int gin, int& gout, double& mu, double& wgt); + int get_order() {return dist[0][0].size() - 1;}; - + double_3dvec - get_matrix(const int max_order); + get_matrix(int max_order); }; //============================================================================== @@ -180,32 +179,32 @@ class ScattDataLegendre: public ScattData { class ScattDataHistogram: public ScattData { protected: - + double_1dvec mu; // Angle distribution mu bin boundaries double dmu; // Quick storage of the spacing between the mu bin points double_3dvec fmu; // The angular distribution histogram - + public: - + void - init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, - double_3dvec& coeffs); - + init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs); + void combine(const std::vector& those_scatts, - const double_1dvec& scalars); + const double_1dvec& scalars); double - calc_f(const int gin, const int gout, const double mu); - + calc_f(int gin, int gout, double mu); + void - sample(const int gin, int& gout, double& mu, double& wgt); - + sample(int gin, int& gout, double& mu, double& wgt); + int get_order() {return dist[0][0].size();}; - + double_3dvec - get_matrix(const int max_order); + get_matrix(int max_order); }; //============================================================================== @@ -214,9 +213,9 @@ class ScattDataHistogram: public ScattData { //============================================================================== class ScattDataTabular: public ScattData { - + protected: - + double_1dvec mu; // Angle distribution mu grid points double dmu; // Quick storage of the spacing between the mu points double_3dvec fmu; // The angular distribution function @@ -224,29 +223,29 @@ class ScattDataTabular: public ScattData { // Friend convert_legendre_to_tabular so it has access to protected // parameters friend void - convert_legendre_to_tabular(ScattDataLegendre& leg, ScattDataTabular& tab, - int n_mu); - + convert_legendre_to_tabular(ScattDataLegendre& leg, + ScattDataTabular& tab, int n_mu); + public: - + void - init(int_1dvec& in_gmin, int_1dvec& in_gmax, double_2dvec& in_mult, - double_3dvec& coeffs); + init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, + const double_2dvec& in_mult, const double_3dvec& coeffs); void combine(const std::vector& those_scatts, - const double_1dvec& scalars); - + const double_1dvec& scalars); + double - calc_f(const int gin, const int gout, const double mu); - + calc_f(int gin, int gout, double mu); + void - sample(const int gin, int& gout, double& mu, double& wgt); - + sample(int gin, int& gout, double& mu, double& wgt); + int get_order() {return dist[0][0].size();}; - - double_3dvec get_matrix(const int max_order); + + double_3dvec get_matrix(int max_order); }; //============================================================================== diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index f6839081d..fa4a644d1 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -2,10 +2,6 @@ module tally use, intrinsic :: ISO_C_BINDING -#ifdef _OPENMP - use omp_lib -#endif - use algorithm, only: binary_search use constants use dict_header, only: EMPTY @@ -1233,12 +1229,6 @@ contains real(8) :: p_uvw(3) ! Particle's current uvw integer :: p_g ! Particle group to use for getting info ! to tally with. - integer(C_INT) :: tid -#ifdef _OPENMP - tid = OMP_GET_THREAD_NUM() -#else - tid = 0 -#endif ! Set the direction and group to use with get_xs if (t % estimator == ESTIMATOR_ANALOG .or. & @@ -1276,13 +1266,13 @@ contains ! To significantly reduce de-referencing, point matxs to the ! macroscopic Mgxs for the material of interest - call set_macro_angle_index_c(p % material, tid, p_uvw) + call set_macro_angle_index_c(p % material, p_uvw) ! Do same for nucxs, point it to the microscopic nuclide data of interest if (i_nuclide > 0) then ! And since we haven't calculated this temperature index yet, do so now - call set_nuclide_temperature_index_c(i_nuclide, tid, p % sqrtkT) - call set_nuclide_angle_index_c(i_nuclide, tid, p_uvw) + call set_nuclide_temperature_index_c(i_nuclide, p % sqrtkT) + call set_nuclide_angle_index_c(i_nuclide, p_uvw) end if i = 0 @@ -1337,13 +1327,13 @@ contains if (i_nuclide > 0) then score = score * flux * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_TOTAL, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_TOTAL, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_TOTAL, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_TOTAL, p_g) end if else if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_TOTAL, p_g) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_TOTAL, p_g) * & atom_density * flux else score = material_xs % total * flux @@ -1366,22 +1356,22 @@ contains end if if (i_nuclide > 0) then - score = score * flux * get_nuclide_xs_c(i_nuclide, tid, & + score = score * flux * get_nuclide_xs_c(i_nuclide, & MG_GET_XS_INVERSE_VELOCITY, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else - score = score * flux * get_macro_xs_c(p % material, tid, & + score = score * flux * get_macro_xs_c(p % material, & MG_GET_XS_INVERSE_VELOCITY, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = flux * get_nuclide_xs_c(i_nuclide, tid, & + score = flux * get_nuclide_xs_c(i_nuclide, & MG_GET_XS_INVERSE_VELOCITY, p_g) else - score = flux * get_macro_xs_c(p % material, tid, & + score = flux * get_macro_xs_c(p % material, & MG_GET_XS_INVERSE_VELOCITY, p_g) end if end if @@ -1404,22 +1394,22 @@ contains ! adjust the score by the actual probability for that nuclide. if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_SCATTER_FMU_MULT, & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_FMU_MULT, & p % last_g, p % g, MU=p % mu) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_SCATTER_FMU_MULT, & + get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU_MULT, & p % last_g, p % g, MU=p % mu) end if else if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_SCATTER_MULT, & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_MULT, & p_g, MU=p % mu) else ! Get the scattering x/s and take away ! the multiplication baked in to sigS score = flux * & - get_macro_xs_c(p % material, tid, MG_GET_XS_SCATTER_MULT, & + get_macro_xs_c(p % material, MG_GET_XS_SCATTER_MULT, & p_g, MU=p % mu) end if end if @@ -1442,20 +1432,20 @@ contains ! adjust the score by the actual probability for that nuclide. if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_SCATTER_FMU, & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_FMU, & p % last_g, p % g, MU=p % mu) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_SCATTER_FMU, & + get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU, & p % last_g, p % g, MU=p % mu) end if else if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_SCATTER, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER, p_g) else ! Get the scattering x/s, which includes multiplication score = flux * & - get_macro_xs_c(p % material, tid, MG_GET_XS_SCATTER, p_g) + get_macro_xs_c(p % material, MG_GET_XS_SCATTER, p_g) end if end if @@ -1475,13 +1465,13 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_ABSORPTION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_ABSORPTION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_ABSORPTION, p_g) else score = material_xs % absorption * flux end if @@ -1506,19 +1496,19 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) * & atom_density * flux else - score = get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) * flux + score = get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux end if end if @@ -1544,12 +1534,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, tid, MG_GET_XS_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else ! Skip any non-fission events @@ -1562,17 +1552,17 @@ contains score = keff * p % wgt_bank * flux if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if else if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_NU_FISSION, p_g) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_NU_FISSION, p_g) * & atom_density * flux else - score = get_macro_xs_c(p % material, tid, MG_GET_XS_NU_FISSION, p_g) * flux + score = get_macro_xs_c(p % material, MG_GET_XS_NU_FISSION, p_g) * flux end if end if @@ -1598,12 +1588,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, tid, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else ! Skip any non-fission events @@ -1617,17 +1607,17 @@ contains / real(p % n_bank, 8)) * flux if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if else if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_PROMPT_NU_FISSION, p_g) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_PROMPT_NU_FISSION, p_g) * & atom_density * flux else - score = get_macro_xs_c(p % material, tid, MG_GET_XS_PROMPT_NU_FISSION, p_g) * flux + score = get_macro_xs_c(p % material, MG_GET_XS_PROMPT_NU_FISSION, p_g) * flux end if end if @@ -1653,7 +1643,7 @@ contains ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in ! nu-fission - if (get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) > ZERO) then + if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then if (dg_filter > 0) then select type(filt => filters(t % filter(dg_filter)) % obj) @@ -1669,12 +1659,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1685,12 +1675,12 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if end if end if @@ -1720,8 +1710,8 @@ contains if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1732,8 +1722,8 @@ contains score = keff * p % wgt_bank / p % n_bank * sum(p % n_delayed_bank) * flux if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if end if @@ -1753,10 +1743,10 @@ contains if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else score = flux * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1766,11 +1756,11 @@ contains else if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g) else score = flux * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g) end if end if end if @@ -1785,7 +1775,7 @@ contains ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in ! nu-fission - if (get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) > ZERO) then + if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then if (dg_filter > 0) then select type(filt => filters(t % filter(dg_filter)) % obj) @@ -1801,14 +1791,14 @@ contains score = p % absorb_wgt * flux if (i_nuclide > 0) then score = score * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1826,14 +1816,14 @@ contains do d = 1, num_delayed_groups if (i_nuclide > 0) then score = score + p % absorb_wgt * flux * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score + p % absorb_wgt * flux * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if end do end if @@ -1862,13 +1852,13 @@ contains if (i_nuclide > 0) then score = score + keff * atom_density * & fission_bank(n_bank - p % n_bank + k) % wgt * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_FISSION, p_g) * flux + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux else score = score + keff * & fission_bank(n_bank - p % n_bank + k) % wgt * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * flux + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * flux end if ! if the delayed group filter is present, tally to corresponding @@ -1921,12 +1911,12 @@ contains if (i_nuclide > 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else score = flux * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if call score_fission_delayed_dg(t, d_bin, score, score_index) @@ -1943,12 +1933,12 @@ contains do d = 1, num_delayed_groups if (i_nuclide > 0) then score = score + atom_density * flux * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else score = score + flux * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, tid, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if end do end if @@ -1973,20 +1963,20 @@ contains end if if (i_nuclide > 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_KAPPA_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide, MG_GET_XS_KAPPA_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & - get_macro_xs_c(p % material, tid, MG_GET_XS_KAPPA_FISSION, p_g) / & - get_macro_xs_c(p % material, tid, MG_GET_XS_ABSORPTION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_KAPPA_FISSION, p_g) / & + get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, tid, MG_GET_XS_KAPPA_FISSION, p_g) * & + score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_KAPPA_FISSION, p_g) * & atom_density * flux else score = flux * & - get_macro_xs_c(p % material, tid, MG_GET_XS_KAPPA_FISSION, p_g) + get_macro_xs_c(p % material, MG_GET_XS_KAPPA_FISSION, p_g) end if end if diff --git a/src/tracking.F90 b/src/tracking.F90 index c92673984..8fe05e3c2 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -2,10 +2,6 @@ module tracking use, intrinsic :: ISO_C_BINDING -#ifdef _OPENMP - use omp_lib -#endif - use constants use error, only: warning, write_message use geometry_header, only: cells @@ -52,12 +48,6 @@ contains real(8) :: d_collision ! sampled distance to collision real(8) :: distance ! distance particle travels logical :: found_cell ! found cell which particle is in? - integer(C_INT) :: tid -#ifdef _OPENMP - tid = OMP_GET_THREAD_NUM() -#else - tid = 0 -#endif ! Display message if high verbosity or trace is on if (verbosity >= 9 .or. trace) then @@ -124,7 +114,7 @@ contains end if else ! Get the MG data - call calculate_xs_c(p % material, tid, p % g, p % sqrtkT, & + call calculate_xs_c(p % material, p % g, p % sqrtkT, & p % coord(p % n_coord) % uvw, material_xs % total, & material_xs % absorption, material_xs % nu_fission) diff --git a/src/xsdata.cpp b/src/xsdata.cpp index b447ee0ae..98df2d67d 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -6,9 +6,8 @@ namespace openmc { // XsData class methods //============================================================================== -XsData::XsData(const int energy_groups, const int num_delayed_groups, - const bool fissionable, const int scatter_format, - const int n_pol, const int n_azi) +XsData::XsData(int energy_groups, int num_delayed_groups, bool fissionable, + int scatter_format, int n_pol, int n_azi) { int n_ang = n_pol * n_azi; @@ -60,11 +59,9 @@ XsData::XsData(const int energy_groups, const int num_delayed_groups, //============================================================================== void -XsData::from_hdf5(const hid_t xsdata_grp, const bool fissionable, - const int scatter_format, const int final_scatter_format, - const int order_data, const int max_order, - const int legendre_to_tabular_points, const bool is_isotropic, - const int n_pol, const int n_azi) +XsData::from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, + int final_scatter_format, int order_data, int max_order, + int legendre_to_tabular_points, bool is_isotropic, int n_pol, int n_azi) { // Reconstruct the dimension information so it doesn't need to be passed int n_ang = n_pol * n_azi; @@ -83,8 +80,7 @@ XsData::from_hdf5(const hid_t xsdata_grp, const bool fissionable, // Get scattering data scatter_from_hdf5(xsdata_grp, n_pol, n_azi, energy_groups, scatter_format, - final_scatter_format, order_data, max_order, - legendre_to_tabular_points); + final_scatter_format, order_data, max_order, legendre_to_tabular_points); // Check absorption to ensure it is not 0 since it is often the // denominator in tally methods @@ -116,9 +112,8 @@ XsData::from_hdf5(const hid_t xsdata_grp, const bool fissionable, //============================================================================== void -XsData::fission_from_hdf5(const hid_t xsdata_grp, const int n_pol, - const int n_azi, const int energy_groups, const int delayed_groups, - const bool is_isotropic) +XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, + int energy_groups, int delayed_groups, bool is_isotropic) { int n_ang = n_pol * n_azi; // Get the fission and kappa_fission data xs; these are optional @@ -485,10 +480,9 @@ XsData::fission_from_hdf5(const hid_t xsdata_grp, const int n_pol, //============================================================================== void -XsData::scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, - const int n_azi, const int energy_groups, int scatter_format, - const int final_scatter_format, const int order_data, const int max_order, - const int legendre_to_tabular_points) +XsData::scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, + int energy_groups, int scatter_format, int final_scatter_format, + int order_data, int max_order, int legendre_to_tabular_points) { int n_ang = n_pol * n_azi; if (!object_exists(xsdata_grp, "scatter_data")) { diff --git a/src/xsdata.h b/src/xsdata.h index adf80cad2..36aacc178 100644 --- a/src/xsdata.h +++ b/src/xsdata.h @@ -27,19 +27,17 @@ class XsData { private: //! \brief Reads scattering data from the HDF5 file void - scatter_from_hdf5(const hid_t xsdata_grp, const int n_pol, const int n_azi, - const int energy_groups, int scatter_format, - const int final_scatter_format, const int order_data, - const int max_order, const int legendre_to_tabular_points); + scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, int energy_groups, + int scatter_format, int final_scatter_format, int order_data, + int max_order, int legendre_to_tabular_points); //! \brief Reads fission data from the HDF5 file void - fission_from_hdf5(const hid_t xsdata_grp, const int n_pol, const int n_azi, - const int energy_groups, const int delayed_groups, - const bool is_isotropic); + fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, int energy_groups, + int delayed_groups, bool is_isotropic); public: - + // The following quantities have the following dimensions: // [angle][incoming group] double_2dvec total; @@ -75,9 +73,8 @@ class XsData { //! @param scatter_format The scattering representation of the file. //! @param n_pol Number of polar angles. //! @param n_azi Number of azimuthal angles. - XsData(const int num_groups, const int num_delayed_groups, - const bool fissionable, const int scatter_format, const int n_pol, - const int n_azi); + XsData(int num_groups, int num_delayed_groups, bool fissionable, + int scatter_format, int n_pol, int n_azi); //! \brief Loads the XsData object from the HDF5 file //! @@ -99,11 +96,10 @@ class XsData { //! @param n_pol Number of polar angles. //! @param n_azi Number of azimuthal angles. void - from_hdf5(const hid_t xsdata_grp, const bool fissionable, - const int scatter_format, const int final_scatter_format, - const int order_data, const int max_order, - const int legendre_to_tabular_points, const bool is_isotropic, - const int n_pol, const int n_azi); + from_hdf5(hid_t xsdata_grp, bool fissionable, int scatter_format, + int final_scatter_format, int order_data, int max_order, + int legendre_to_tabular_points, bool is_isotropic, int n_pol, + int n_azi); //! \brief Combines the microscopic data to a macroscopic object. //! From cffa44bd6f914f203291e68d29837dbdf87f029a Mon Sep 17 00:00:00 2001 From: Zhuoran Han Date: Fri, 22 Jun 2018 16:52:15 -0500 Subject: [PATCH 344/361] Add .DS_Store in .gitignore. macOS use it to store icons. Add .dylib in .gitignore. Dynamic library is generated at runtime. --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index ffdd58d33..d4d70fca3 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,9 @@ examples/jupyter/plots .python-version .coverage htmlcov + +#macOS +*.DS_Store + +#Dynamic Library +*.dylib From 67b0ea38e40081205a7f53e3a79a15a3413aa0b8 Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Tue, 26 Jun 2018 21:20:30 -0400 Subject: [PATCH 345/361] resolving style comments --- src/hdf5_interface.cpp | 28 +++++++------- src/mgxs.cpp | 87 +++++++++++++++++------------------------- src/mgxs.h | 12 ------ src/mgxs_interface.cpp | 22 ++++++----- src/mgxs_interface.h | 2 +- src/scattdata.cpp | 32 ++++++++-------- src/scattdata.h | 10 +---- src/xsdata.cpp | 58 ++++++++++++++++------------ src/xsdata.h | 9 +---- 9 files changed, 114 insertions(+), 146 deletions(-) diff --git a/src/hdf5_interface.cpp b/src/hdf5_interface.cpp index a81f26e23..7133ad51a 100644 --- a/src/hdf5_interface.cpp +++ b/src/hdf5_interface.cpp @@ -452,7 +452,7 @@ read_nd_vector(hid_t obj_id, const char* name, std::vector& result, bool must_have) { if (object_exists(obj_id, name)) { - read_double(obj_id, name, &result[0], true); + read_double(obj_id, name, result.data(), true); } else if (must_have) { fatal_error(std::string("Must provide " + std::string(name) + "!")); } @@ -466,8 +466,8 @@ read_nd_vector(hid_t obj_id, const char* name, if (object_exists(obj_id, name)) { int dim1 = result.size(); int dim2 = result[0].size(); - std::vector temp_arr = std::vector(dim1 * dim2); - read_double(obj_id, name, &temp_arr[0], true); + double temp_arr[dim1 * dim2]; + read_double(obj_id, name, temp_arr, true); int temp_idx = 0; for (int i = 0; i < dim1; i++) { @@ -488,8 +488,8 @@ read_nd_vector(hid_t obj_id, const char* name, if (object_exists(obj_id, name)) { int dim1 = result.size(); int dim2 = result[0].size(); - std::vector temp_arr = std::vector(dim1 * dim2); - read_int(obj_id, name, &temp_arr[0], true); + int temp_arr[dim1 * dim2]; + read_int(obj_id, name, temp_arr, true); int temp_idx = 0; for (int i = 0; i < dim1; i++) { @@ -512,8 +512,8 @@ read_nd_vector(hid_t obj_id, const char* name, int dim1 = result.size(); int dim2 = result[0].size(); int dim3 = result[0][0].size(); - std::vector temp_arr = std::vector(dim1 * dim2 * dim3); - read_double(obj_id, name, &temp_arr[0], true); + double temp_arr[dim1 * dim2 * dim3]; + read_double(obj_id, name, temp_arr, true); int temp_idx = 0; for (int i = 0; i < dim1; i++) { @@ -537,8 +537,8 @@ read_nd_vector(hid_t obj_id, const char* name, int dim1 = result.size(); int dim2 = result[0].size(); int dim3 = result[0][0].size(); - std::vector temp_arr = std::vector(dim1 * dim2 * dim3); - read_int(obj_id, name, &temp_arr[0], true); + int temp_arr[dim1 * dim2 * dim3]; + read_int(obj_id, name, temp_arr, true); int temp_idx = 0; for (int i = 0; i < dim1; i++) { @@ -563,9 +563,8 @@ read_nd_vector(hid_t obj_id, const char* name, int dim2 = result[0].size(); int dim3 = result[0][0].size(); int dim4 = result[0][0][0].size(); - std::vector temp_arr = std::vector( - dim1 * dim2 * dim3 * dim4); - read_double(obj_id, name, &temp_arr[0], true); + double temp_arr[dim1 * dim2 * dim3 * dim4]; + read_double(obj_id, name, temp_arr, true); int temp_idx = 0; for (int i = 0; i < dim1; i++) { @@ -593,9 +592,8 @@ read_nd_vector(hid_t obj_id, const char* name, int dim3 = result[0][0].size(); int dim4 = result[0][0][0].size(); int dim5 = result[0][0][0][0].size(); - std::vector temp_arr = std::vector( - dim1 * dim2 * dim3 * dim4 * dim5); - read_double(obj_id, name, &temp_arr[0], true); + double temp_arr[dim1 * dim2 * dim3 * dim4 * dim5]; + read_double(obj_id, name, temp_arr, true); int temp_idx = 0; for (int i = 0; i < dim1; i++) { diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 748e7e949..3e84f6307 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -1,5 +1,19 @@ +#include +#include +#include +#include + + #ifdef _OPENMP + # include + #endif + +#include "error.h" +#include "math_functions.h" +#include "random_lcg.h" +#include "string_functions.h" #include "mgxs.h" + namespace openmc { // Storage for the MGXS data @@ -39,14 +53,7 @@ Mgxs::init(const std::string& in_name, double in_awr, int n_threads = 1; #endif cache.resize(n_threads); - for (int thread = 0; thread < n_threads; thread++) { - cache[thread].sqrtkT = 0.; - cache[thread].t = 0; - cache[thread].a = 0; - cache[thread].u = 0.; - cache[thread].v = 0.; - cache[thread].w = 0.; - } + // std::vector.resize() will value-initialize the members of cache[:] } //============================================================================== @@ -59,7 +66,7 @@ Mgxs::metadata_from_hdf5(hid_t xs_id, int in_num_groups, // get name char char_name[MAX_WORD_LEN]; get_name(xs_id, char_name); - std::string in_name(char_name, std::strlen(char_name)); + std::string in_name {char_name}; // remove the leading '/' in_name = in_name.substr(1); @@ -85,6 +92,9 @@ Mgxs::metadata_from_hdf5(hid_t xs_id, int in_num_groups, // convert eV to Kelvin available_temps[i] /= K_BOLTZMANN; + + // Done with dset_names, so delete it + delete[] dset_names[i]; } std::sort(available_temps.begin(), available_temps.end()); @@ -92,7 +102,7 @@ Mgxs::metadata_from_hdf5(hid_t xs_id, int in_num_groups, // interpolation if ((num_temps == 1) && (method == TEMPERATURE_INTERPOLATION)) { warning("Cross sections for " + strtrim(name) + " are only available " + - "at one temperature. Reverying to the nearest temperature " + + "at one temperature. Reverting to the nearest temperature " + "method."); method = TEMPERATURE_NEAREST; } @@ -190,7 +200,7 @@ Mgxs::metadata_from_hdf5(hid_t xs_id, int in_num_groups, if (attribute_exists(xs_id, "fissionable")) { int int_fiss; read_attr_int(xs_id, "fissionable", &int_fiss); - in_fissionable = (bool)int_fiss; + in_fissionable = int_fiss; } else { fatal_error("Fissionable element must be set!"); } @@ -208,7 +218,7 @@ Mgxs::metadata_from_hdf5(hid_t xs_id, int in_num_groups, // moment). Adjust for that. Histogram and Tabular formats dont need this // adjustment. if (in_scatter_format == ANGLE_LEGENDRE) { - order_dim = order_dim + 1; + ++order_dim; } // Get the angular information @@ -220,7 +230,7 @@ Mgxs::metadata_from_hdf5(hid_t xs_id, int in_num_groups, read_attr_string(xs_id, "representation", MAX_WORD_LEN, &temp_str[0]); to_lower(strtrim(temp_str)); if (temp_str.compare(0, 5, "angle") == 0) { - in_is_isotropic = false; + in_is_isotropic = false; } else if (temp_str.compare(0, 9, "isotropic") != 0) { fatal_error("Invalid Data Representation!"); } @@ -326,7 +336,7 @@ Mgxs::Mgxs(const std::string& in_name, const double_1dvec& mat_kTs, // Create the xs data for each temperature for (int t = 0; t < mat_kTs.size(); t++) { - xs[t]= XsData(in_num_groups, in_num_delayed_groups, in_fissionable, + xs[t] = XsData(in_num_groups, in_num_delayed_groups, in_fissionable, in_scatter_format, in_polar.size(), in_azimuthal.size()); // Find the right temperature index to use @@ -433,28 +443,16 @@ Mgxs::get_xs(int xstype, int gin, int* gout, double* mu, int* dg) val = xs_t->total[a][gin]; break; case MG_GET_XS_NU_FISSION: - if (fissionable) { - val = xs_t->nu_fission[a][gin]; - } else { - val = 0.; - } + val = fissionable ? xs_t->nu_fission[a][gin] : 0.; break; case MG_GET_XS_ABSORPTION: val = xs_t->absorption[a][gin]; break; case MG_GET_XS_FISSION: - if (fissionable) { - val = xs_t->fission[a][gin]; - } else { - val = 0.; - } + val = fissionable ? xs_t->fission[a][gin] : 0.; break; case MG_GET_XS_KAPPA_FISSION: - if (fissionable) { - val = xs_t->kappa_fission[a][gin]; - } else { - val = 0.; - } + val = fissionable ? xs_t->kappa_fission[a][gin] : 0.; break; case MG_GET_XS_SCATTER: case MG_GET_XS_SCATTER_MULT: @@ -463,11 +461,7 @@ Mgxs::get_xs(int xstype, int gin, int* gout, double* mu, int* dg) val = xs_t->scatter[a]->get_xs(xstype, gin, gout, mu); break; case MG_GET_XS_PROMPT_NU_FISSION: - if (fissionable) { - val = xs_t->prompt_nu_fission[a][gin]; - } else { - val = 0.; - } + val = fissionable ? xs_t->prompt_nu_fission[a][gin] : 0.; break; case MG_GET_XS_DELAYED_NU_FISSION: if (fissionable) { @@ -638,11 +632,7 @@ Mgxs::calculate_xs(int gin, double sqrtkT, const double uvw[3], total_xs = xs_t->total[cache[tid].a][gin]; abs_xs = xs_t->absorption[cache[tid].a][gin]; - if (fissionable) { - nu_fiss_xs = xs_t->nu_fission[cache[tid].a][gin]; - } else { - nu_fiss_xs = 0.; - } + nu_fiss_xs = fissionable ? xs_t->nu_fission[cache[tid].a][gin] : 0.; } //============================================================================== @@ -650,18 +640,13 @@ Mgxs::calculate_xs(int gin, double sqrtkT, const double uvw[3], bool Mgxs::equiv(const Mgxs& that) { - bool match = false; - - if ((num_delayed_groups == that.num_delayed_groups) && - (num_groups == that.num_groups) && - (n_pol == that.n_pol) && - (n_azi == that.n_azi) && - (std::equal(polar.begin(), polar.end(), that.polar.begin())) && - (std::equal(azimuthal.begin(), azimuthal.end(), that.azimuthal.begin())) && - (scatter_format == that.scatter_format)) { - match = true; - } - return match; + return ((num_delayed_groups == that.num_delayed_groups) && + (num_groups == that.num_groups) && + (n_pol == that.n_pol) && + (n_azi == that.n_azi) && + (std::equal(polar.begin(), polar.end(), that.polar.begin())) && + (std::equal(azimuthal.begin(), azimuthal.end(), that.azimuthal.begin())) && + (scatter_format == that.scatter_format)); } //============================================================================== diff --git a/src/mgxs.h b/src/mgxs.h index 8ef174269..0434bd55d 100644 --- a/src/mgxs.h +++ b/src/mgxs.h @@ -4,23 +4,11 @@ #ifndef MGXS_H #define MGXS_H -#include -#include -#include #include -#include #include - #ifdef _OPENMP - # include - #endif - #include "constants.h" #include "hdf5_interface.h" -#include "math_functions.h" -#include "random_lcg.h" -#include "scattdata.h" -#include "string_functions.h" #include "xsdata.h" diff --git a/src/mgxs_interface.cpp b/src/mgxs_interface.cpp index a96f53008..d87598779 100644 --- a/src/mgxs_interface.cpp +++ b/src/mgxs_interface.cpp @@ -1,5 +1,10 @@ +#include + +#include "error.h" +#include "math_functions.h" #include "mgxs_interface.h" + namespace openmc { //============================================================================== @@ -12,11 +17,8 @@ add_mgxs_c(hid_t file_id, const char* name, int energy_groups, int max_order, bool legendre_to_tabular, int legendre_to_tabular_points, int& method) { - //!! mgxs_data.F90 will be modified to just create the list of names - //!! in the order needed // Convert temps to a vector for the from_hdf5 function - double_1dvec temperature; - temperature.assign(temps, temps + n_temps); + double_1dvec temperature(temps, temps + n_temps); write_message("Loading " + std::string(name) + " data...", 6); @@ -33,6 +35,7 @@ add_mgxs_c(hid_t file_id, const char* name, int energy_groups, max_order, legendre_to_tabular, legendre_to_tabular_points, method); nuclides_MG.push_back(mg); + close_group(xs_grp); } //============================================================================== @@ -56,12 +59,11 @@ create_macro_xs_c(const char* mat_name, int n_nuclides, const int i_nuclides[], { if (n_temps > 0) { // // Convert temps to a vector - double_1dvec temperature; - temperature.assign(temps, temps + n_temps); + double_1dvec temperature(temps, temps + n_temps); // Convert atom_densities to a vector - double_1dvec atom_densities_vec; - atom_densities_vec.assign(atom_densities, atom_densities + n_nuclides); + double_1dvec atom_densities_vec(atom_densities, + atom_densities + n_nuclides); // Build array of pointers to nuclides_MG's Mgxs objects needed for this // material @@ -72,11 +74,11 @@ create_macro_xs_c(const char* mat_name, int n_nuclides, const int i_nuclides[], Mgxs macro(mat_name, temperature, mgxs_ptr, atom_densities_vec, tolerance, method); - macro_xs.push_back(macro); + macro_xs.emplace_back(macro); } else { // Preserve the ordering of materials by including a blank entry Mgxs macro; - macro_xs.push_back(macro); + macro_xs.emplace_back(macro); } } diff --git a/src/mgxs_interface.h b/src/mgxs_interface.h index 2cdd2889a..65afd20f9 100644 --- a/src/mgxs_interface.h +++ b/src/mgxs_interface.h @@ -4,7 +4,7 @@ #ifndef MGXS_INTERFACE_H #define MGXS_INTERFACE_H -#include "error.h" +#include "hdf5_interface.h" #include "mgxs.h" diff --git a/src/scattdata.cpp b/src/scattdata.cpp index ad6ae21a6..a316eba46 100644 --- a/src/scattdata.cpp +++ b/src/scattdata.cpp @@ -1,3 +1,11 @@ +#include +#include +#include + +#include "constants.h" +#include "math_functions.h" +#include "random_lcg.h" +#include "error.h" #include "scattdata.h" namespace openmc { @@ -35,7 +43,6 @@ ScattData::base_init(int order, const int_1dvec& in_gmin, dist[gin].resize(in_gmax[gin] - in_gmin[gin] + 1); for (auto& v : dist[gin]) { v.resize(order); - for (auto& n : v) n = 0.; } } } @@ -193,27 +200,22 @@ ScattData::get_xs(int xstype, int gin, const int* gout, const double* mu) i_gout = *gout - gmin[gin]; } - double val = 0.; + double val = scattxs[gin]; switch(xstype) { case MG_GET_XS_SCATTER: - if (gout != nullptr) { - val = scattxs[gin] * energy[gin][i_gout]; - } else { - val = scattxs[gin]; - } + if (gout != nullptr) val *= energy[gin][i_gout]; break; case MG_GET_XS_SCATTER_MULT: if (gout != nullptr) { - val = scattxs[gin] * energy[gin][i_gout] / mult[gin][i_gout]; + val *= energy[gin][i_gout] / mult[gin][i_gout]; } else { - val = scattxs[gin] / - std::inner_product(mult[gin].begin(), mult[gin].end(), - energy[gin].begin(), 0.0); + val /= std::inner_product(mult[gin].begin(), mult[gin].end(), + energy[gin].begin(), 0.0); } break; case MG_GET_XS_SCATTER_FMU_MULT: if ((gout != nullptr) && (mu != nullptr)) { - val = scattxs[gin] * energy[gin][i_gout] * calc_f(gin, *gout, *mu); + val *= energy[gin][i_gout] * calc_f(gin, *gout, *mu); } else { // This is not an expected path (asking for f_mu without asking for a // group or mu is not useful @@ -222,8 +224,7 @@ ScattData::get_xs(int xstype, int gin, const int* gout, const double* mu) break; case MG_GET_XS_SCATTER_FMU: if ((gout != nullptr) && (mu != nullptr)) { - val = scattxs[gin] * energy[gin][i_gout] * calc_f(gin, *gout, *mu) / - mult[gin][i_gout]; + val *= energy[gin][i_gout] * calc_f(gin, *gout, *mu) / mult[gin][i_gout]; } else { // This is not an expected path (asking for f_mu without asking for a // group or mu is not useful @@ -674,8 +675,7 @@ ScattDataTabular::init(const int_1dvec& in_gmin, const int_1dvec& in_gmax, } // Build the energy transfer matrix from data in the variable matrix - double_2dvec in_energy; - in_energy.resize(groups); + double_2dvec in_energy(groups); for (int gin = 0; gin < groups; gin++) { int num_groups = in_gmax[gin] - in_gmin[gin] + 1; in_energy[gin].resize(num_groups); diff --git a/src/scattdata.h b/src/scattdata.h index 888f96052..72ebaf677 100644 --- a/src/scattdata.h +++ b/src/scattdata.h @@ -4,19 +4,11 @@ #ifndef SCATTDATA_H #define SCATTDATA_H -#include -#include #include -#include - -#include "constants.h" -#include "math_functions.h" -#include "random_lcg.h" -#include "error.h" namespace openmc { -// temporary declaations so we can name our friend functions +// forward declarations so we can name our friend functions class ScattDataLegendre; class ScattDataTabular; diff --git a/src/xsdata.cpp b/src/xsdata.cpp index 98df2d67d..712fe7b0f 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -1,5 +1,15 @@ +#include +#include +#include +#include + +#include "constants.h" +#include "error.h" +#include "math_functions.h" +#include "random_lcg.h" #include "xsdata.h" + namespace openmc { //============================================================================== @@ -44,14 +54,17 @@ XsData::XsData(int energy_groups, int num_delayed_groups, bool fissionable, double_2dvec(energy_groups, double_1dvec(num_delayed_groups, 0.)))); } - scatter.resize(n_ang); + for (int a = 0; a < n_ang; a++) { if (scatter_format == ANGLE_HISTOGRAM) { - scatter[a] = new ScattDataHistogram; + // scatter[a] = std::make_unique(ScattDataHistogram); + scatter.emplace_back(new ScattDataHistogram); } else if (scatter_format == ANGLE_TABULAR) { - scatter[a] = new ScattDataTabular; + // scatter[a] = std::make_unique(ScattDataTabular); + scatter.emplace_back(new ScattDataTabular); } else if (scatter_format == ANGLE_LEGENDRE) { - scatter[a] = new ScattDataLegendre; + // scatter[a] = std::make_unique(ScattDataLegendre); + scatter.emplace_back(new ScattDataLegendre); } } } @@ -131,7 +144,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, if (ndims == 3) { // Beta is input as [delayed group] - double_1dvec temp_arr = double_1dvec(n_pol * n_azi * delayed_groups); + double_1dvec temp_arr(n_pol * n_azi * delayed_groups); read_nd_vector(xsdata_grp, "beta", temp_arr); // Broadcast to all incoming groups @@ -155,7 +168,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, // If chi is provided, set chi-prompt and chi-delayed if (object_exists(xsdata_grp, "chi")) { - double_2dvec temp_arr = double_2dvec(n_ang, double_1dvec(energy_groups)); + double_2dvec temp_arr(n_ang, double_1dvec(energy_groups)); read_nd_vector(xsdata_grp, "chi", temp_arr); for (int a = 0; a < n_ang; a++) { @@ -277,7 +290,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, // If chi-prompt is provided, set chi-prompt if (object_exists(xsdata_grp, "chi-prompt")) { - double_2dvec temp_arr = double_2dvec(n_ang, double_1dvec(energy_groups)); + double_2dvec temp_arr(n_ang, double_1dvec(energy_groups)); read_nd_vector(xsdata_grp, "chi-prompt", temp_arr); for (int a = 0; a < n_ang; a++) { @@ -309,7 +322,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, if (ndims == 3) { // chi-delayed is a [in group] vector - double_2dvec temp_arr = double_2dvec(n_ang, double_1dvec(energy_groups)); + double_2dvec temp_arr(n_ang, double_1dvec(energy_groups)); read_nd_vector(xsdata_grp, "chi-delayed", temp_arr); for (int a = 0; a < n_ang; a++) { @@ -371,7 +384,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, } else if (ndims == 4) { // prompt nu fission is a matrix, // so set prompt_nu_fiss & chi_prompt - double_3dvec temp_arr = double_3dvec(n_ang, double_2dvec(energy_groups, + double_3dvec temp_arr(n_ang, double_2dvec(energy_groups, double_1dvec(energy_groups))); read_nd_vector(xsdata_grp, "prompt-nu-fission", temp_arr); @@ -414,7 +427,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, fatal_error("cannot set delayed-nu-fission with a 1D array if " "beta is not provided"); } - double_2dvec temp_arr = double_2dvec(n_ang, double_1dvec(energy_groups)); + double_2dvec temp_arr(n_ang, double_1dvec(energy_groups)); read_nd_vector(xsdata_grp, "delayed-nu-fission", temp_arr); for (int a = 0; a < n_ang; a++) { @@ -433,7 +446,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, } else if (ndims == 5) { // This will contain delayed-nu-fision and chi-delayed data - double_4dvec temp_arr = double_4dvec(n_ang, double_3dvec(energy_groups, + double_4dvec temp_arr(n_ang, double_3dvec(energy_groups, double_2dvec(energy_groups, double_1dvec(delayed_groups)))); read_nd_vector(xsdata_grp, "delayed-nu-fission", temp_arr); @@ -491,9 +504,9 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, hid_t scatt_grp = open_group(xsdata_grp, "scatter_data"); // Get the outgoing group boundary indices - int_2dvec gmin = int_2dvec(n_ang, int_1dvec(energy_groups)); + int_2dvec gmin(n_ang, int_1dvec(energy_groups)); read_nd_vector(scatt_grp, "g_min", gmin, true); - int_2dvec gmax = int_2dvec(n_ang, int_1dvec(energy_groups)); + int_2dvec gmax(n_ang, int_1dvec(energy_groups)); read_nd_vector(scatt_grp, "g_max", gmax, true); // Make gmin and gmax start from 0 vice 1 as they do in the library @@ -512,7 +525,7 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, length += order_data * (gmax[a][gin] - gmin[a][gin] + 1); } } - double_1dvec temp_arr = double_1dvec(length); + double_1dvec temp_arr(length); read_nd_vector(scatt_grp, "scatter_matrix", temp_arr, true); // Compare the number of orders given with the max order of the problem; @@ -526,8 +539,7 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, // convert the flattened temp_arr to a jagged array for passing to // scatt data - double_4dvec input_scatt = - double_4dvec(n_ang, double_3dvec(energy_groups)); + double_4dvec input_scatt(n_ang, double_3dvec(energy_groups)); int temp_idx = 0; for (int a = 0; a < n_ang; a++) { @@ -546,7 +558,7 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, temp_arr.clear(); // Get multiplication matrix - double_3dvec temp_mult = double_3dvec(n_ang, double_2dvec(energy_groups)); + double_3dvec temp_mult(n_ang, double_2dvec(energy_groups)); if (object_exists(scatt_grp, "multiplicity_matrix")) { temp_arr.resize(length / order_data); read_nd_vector(scatt_grp, "multiplicity_matrix", temp_arr); @@ -584,7 +596,7 @@ XsData::scatter_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, // Now create a tabular version of legendre_scatt convert_legendre_to_tabular(legendre_scatt, - *static_cast(scatter[a]), + *static_cast(scatter[a].get()), legendre_to_tabular_points); scatter_format = final_scatter_format; @@ -676,7 +688,7 @@ XsData::combine(const std::vector& those_xs, // Build vector of the scattering objects to incorporate std::vector those_scatts(those_xs.size()); for (int i = 0; i < those_xs.size(); i++) { - those_scatts[i] = those_xs[i]->scatter[a]; + those_scatts[i] = those_xs[i]->scatter[a].get(); } // Now combine these guys @@ -689,12 +701,8 @@ XsData::combine(const std::vector& those_xs, bool XsData::equiv(const XsData& that) { - bool match = false; - if ((absorption.size() == that.absorption.size()) && - (absorption[0].size() == that.absorption[0].size())) { - match = true; - } - return match; + return ((absorption.size() == that.absorption.size()) && + (absorption[0].size() == that.absorption[0].size())); } } //namespace openmc diff --git a/src/xsdata.h b/src/xsdata.h index 36aacc178..c855c67d8 100644 --- a/src/xsdata.h +++ b/src/xsdata.h @@ -4,15 +4,10 @@ #ifndef XSDATA_H #define XSDATA_H -#include -#include -#include +#include #include -#include "constants.h" #include "hdf5_interface.h" -#include "math_functions.h" -#include "random_lcg.h" #include "scattdata.h" @@ -61,7 +56,7 @@ class XsData { // [angle][incoming group][outgoing group][delayed group] double_4dvec chi_delayed; // scatter has the following dimensions: [angle] - std::vector scatter; + std::vector > scatter; XsData() = default; From 1fec0b3ac416c7812bd0c08309ffd9bbdd9fceca Mon Sep 17 00:00:00 2001 From: Adam G Nelson Date: Tue, 26 Jun 2018 21:25:05 -0400 Subject: [PATCH 346/361] added comments explaining if (is_isotropic) line --- src/xsdata.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/xsdata.cpp b/src/xsdata.cpp index 712fe7b0f..3482a316a 100644 --- a/src/xsdata.cpp +++ b/src/xsdata.cpp @@ -140,6 +140,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, hid_t xsdata = open_dataset(xsdata_grp, "beta"); int ndims = dataset_ndims(xsdata); + // raise ndims to make the isotropic ndims the same as angular if (is_isotropic) ndims += 2; if (ndims == 3) { @@ -211,6 +212,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, if (object_exists(xsdata_grp, "nu-fission")) { hid_t xsdata = open_dataset(xsdata_grp, "nu-fission"); int ndims = dataset_ndims(xsdata); + // raise ndims to make the isotropic ndims the same as angular if (is_isotropic) ndims += 2; if (ndims == 3) { @@ -317,6 +319,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, if (object_exists(xsdata_grp, "chi-delayed")) { hid_t xsdata = open_dataset(xsdata_grp, "chi-delayed"); int ndims = dataset_ndims(xsdata); + // raise ndims to make the isotropic ndims the same as angular if (is_isotropic) ndims += 2; close_dataset(xsdata); @@ -374,6 +377,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, if (object_exists(xsdata_grp, "prompt-nu-fission")) { hid_t xsdata = open_dataset(xsdata_grp, "prompt-nu-fission"); int ndims = dataset_ndims(xsdata); + // raise ndims to make the isotropic ndims the same as angular if (is_isotropic) ndims += 2; close_dataset(xsdata); @@ -419,6 +423,7 @@ XsData::fission_from_hdf5(hid_t xsdata_grp, int n_pol, int n_azi, hid_t xsdata = open_dataset(xsdata_grp, "delayed-nu-fission"); int ndims = dataset_ndims(xsdata); close_dataset(xsdata); + // raise ndims to make the isotropic ndims the same as angular if (is_isotropic) ndims += 2; if (ndims == 3) { From ca0f415de48b8b45888280307f8caec590ead949 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 07:06:02 -0500 Subject: [PATCH 347/361] Update recognized thermal scattering names with Lib80x names --- openmc/data/thermal.py | 60 ++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/openmc/data/thermal.py b/openmc/data/thermal.py index a036a2680..18ba56bc5 100644 --- a/openmc/data/thermal.py +++ b/openmc/data/thermal.py @@ -24,40 +24,44 @@ from openmc.stats import Discrete, Tabular _THERMAL_NAMES = { - 'c_Al27': ('al', 'al27'), - 'c_Be': ('be', 'be-metal'), - 'c_BeO': ('beo'), - 'c_Be_in_BeO': ('bebeo', 'be-o', 'be/o'), + 'c_Al27': ('al', 'al27', 'al-27'), + 'c_Be': ('be', 'be-metal', 'be-met'), + 'c_BeO': ('beo',), + 'c_Be_in_BeO': ('bebeo', 'be-beo', 'be-o', 'be/o'), 'c_C6H6': ('benz', 'c6h6'), - 'c_C_in_SiC': ('csic',), - 'c_Ca_in_CaH2': ('cah'), - 'c_D_in_D2O': ('dd2o', 'hwtr', 'hw'), - 'c_Fe56': ('fe', 'fe56'), + 'c_C_in_SiC': ('csic', 'c-sic'), + 'c_Ca_in_CaH2': ('cah',), + 'c_D_in_D2O': ('dd2o', 'd-d2o', 'hwtr', 'hw'), + 'c_Fe56': ('fe', 'fe56', 'fe-56'), 'c_Graphite': ('graph', 'grph', 'gr'), - 'c_H_in_CaH2': ('hcah2'), - 'c_H_in_CH2': ('hch2', 'poly', 'pol'), + 'c_Graphite_10p': ('grph10',), + 'c_Graphite_30p': ('grph30',), + 'c_H_in_CaH2': ('hcah2',), + 'c_H_in_CH2': ('hch2', 'poly', 'pol', 'h-poly'), 'c_H_in_CH4_liquid': ('lch4', 'lmeth'), 'c_H_in_CH4_solid': ('sch4', 'smeth'), - 'c_H_in_H2O': ('hh2o', 'lwtr', 'lw'), - 'c_H_in_H2O_solid': ('hice',), - 'c_H_in_C5O2H8': ('lucite', 'c5o2h8'), - 'c_H_in_YH2': ('hyh2'), - 'c_H_in_ZrH': ('hzrh', 'h-zr', 'h/zr', 'hzr'), + 'c_H_in_H2O': ('hh2o', 'h-h2o', 'lwtr', 'lw'), + 'c_H_in_H2O_solid': ('hice', 'h-ice'), + 'c_H_in_C5O2H8': ('lucite', 'c5o2h8', 'h-luci'), + 'c_H_in_YH2': ('hyh2', 'h-yh2'), + 'c_H_in_ZrH': ('hzrh', 'h-zrh', 'h-zr', 'h/zr', 'hzr'), 'c_Mg24': ('mg', 'mg24'), - 'c_O_in_BeO': ('obeo', 'o-be', 'o/be'), - 'c_O_in_D2O': ('od2o'), - 'c_O_in_H2O_ice': ('oice'), - 'c_O_in_UO2': ('ouo2', 'o2-u', 'o2/u'), - 'c_ortho_D': ('orthod', 'dortho'), - 'c_ortho_H': ('orthoh', 'hortho'), - 'c_Si_in_SiC': ('sisic'), + 'c_O_in_BeO': ('obeo', 'o-beo', 'o-be', 'o/be'), + 'c_O_in_D2O': ('od2o', 'o-d2o'), + 'c_O_in_H2O_ice': ('oice', 'o-ice'), + 'c_O_in_UO2': ('ouo2', 'o-uo2', 'o2-u', 'o2/u'), + 'c_N_in_UN': ('n-un',), + 'c_ortho_D': ('orthod', 'orthoD', 'dortho'), + 'c_ortho_H': ('orthoh', 'orthoH', 'hortho'), + 'c_Si_in_SiC': ('sisic', 'si-sic'), 'c_SiO2_alpha': ('sio2', 'sio2a'), - 'c_SiO2_beta': ('sio2b'), - 'c_para_D': ('parad', 'dpara'), - 'c_para_H': ('parah', 'hpara'), - 'c_U_in_UO2': ('uuo2', 'u-o2', 'u/o2'), - 'c_Y_in_YH2': ('yyh2'), - 'c_Zr_in_ZrH': ('zrzrh', 'zr-h', 'zr/h') + 'c_SiO2_beta': ('sio2b',), + 'c_para_D': ('parad', 'paraD', 'dpara'), + 'c_para_H': ('parah', 'paraH', 'hpara'), + 'c_U_in_UN': ('u-un',), + 'c_U_in_UO2': ('uuo2', 'u-uo2', 'u-o2', 'u/o2'), + 'c_Y_in_YH2': ('yyh2', 'y-yh2'), + 'c_Zr_in_ZrH': ('zrzrh', 'zr-zrh', 'zr-h', 'zr/h') } From 514d8350a2a6e33351f8bec5e0a5d5e85959e656 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 07:27:59 -0500 Subject: [PATCH 348/361] Move Faddeeva source into vendor/ directory --- CMakeLists.txt | 4 ++-- {src => vendor}/faddeeva/Faddeeva.c | 0 {src => vendor}/faddeeva/Faddeeva.cc | 0 {src => vendor}/faddeeva/Faddeeva.h | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename {src => vendor}/faddeeva/Faddeeva.c (100%) rename {src => vendor}/faddeeva/Faddeeva.cc (100%) rename {src => vendor}/faddeeva/Faddeeva.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec77fa0e3..d154b1cf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -320,7 +320,8 @@ endif() # Build faddeeva library #=============================================================================== -add_library(faddeeva STATIC src/faddeeva/Faddeeva.c) +add_library(faddeeva STATIC vendor/faddeeva/Faddeeva.c) +target_compile_options(faddeeva PRIVATE ${cflags}) #=============================================================================== # List source files. Define the libopenmc and the OpenMC executable @@ -469,7 +470,6 @@ target_include_directories(libopenmc PUBLIC include ${HDF5_INCLUDE_DIRS}) # The executable and the faddeeva package use only one language. They can be # set via target_compile_options which accepts a list. target_compile_options(${program} PUBLIC ${cxxflags}) -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 diff --git a/src/faddeeva/Faddeeva.c b/vendor/faddeeva/Faddeeva.c similarity index 100% rename from src/faddeeva/Faddeeva.c rename to vendor/faddeeva/Faddeeva.c diff --git a/src/faddeeva/Faddeeva.cc b/vendor/faddeeva/Faddeeva.cc similarity index 100% rename from src/faddeeva/Faddeeva.cc rename to vendor/faddeeva/Faddeeva.cc diff --git a/src/faddeeva/Faddeeva.h b/vendor/faddeeva/Faddeeva.h similarity index 100% rename from src/faddeeva/Faddeeva.h rename to vendor/faddeeva/Faddeeva.h From 2a936aa3820c92a0a4b97af048c1d87ca72d38b9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 10:56:28 -0500 Subject: [PATCH 349/361] Move pugixml to vendor/ directory --- CMakeLists.txt | 14 +++++++------- src/cell.h | 4 ++-- src/lattice.h | 2 +- src/surface.h | 2 +- src/xml_interface.h | 2 +- {src => vendor}/pugixml/pugiconfig.hpp | 0 {src => vendor}/pugixml/pugixml.cpp | 0 {src => vendor}/pugixml/pugixml.hpp | 0 8 files changed, 12 insertions(+), 12 deletions(-) rename {src => vendor}/pugixml/pugiconfig.hpp (100%) rename {src => vendor}/pugixml/pugixml.cpp (100%) rename {src => vendor}/pugixml/pugixml.hpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d154b1cf3..047cb875b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -284,9 +284,8 @@ endif() # pugixml library #=============================================================================== -add_library(pugixml src/pugixml/pugixml_c.cpp src/pugixml/pugixml.cpp) -add_library(pugixml_fortran src/pugixml/pugixml_f.F90) -target_link_libraries(pugixml_fortran pugixml) +add_library(pugixml vendor/pugixml/pugixml.cpp) +target_include_directories(pugixml PUBLIC vendor/pugixml/) #=============================================================================== # RPATH information @@ -377,6 +376,7 @@ set(LIBOPENMC_FORTRAN_SRC src/plot_header.F90 src/product_header.F90 src/progress_header.F90 + src/pugixml/pugixml_f.F90 src/random_lcg.F90 src/reaction_header.F90 src/relaxng @@ -443,6 +443,7 @@ set(LIBOPENMC_CXX_SRC src/mgxs.cpp src/mgxs_interface.cpp src/plot.cpp + src/pugixml/pugixml_c.cpp src/random_lcg.cpp src/scattdata.cpp src/simulation.cpp @@ -450,8 +451,7 @@ set(LIBOPENMC_CXX_SRC src/string_functions.cpp src/surface.cpp src/xml_interface.cpp - src/xsdata.cpp - src/pugixml/pugixml.cpp) + src/xsdata.cpp) add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC} ${LIBOPENMC_CXX_SRC}) set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc @@ -462,7 +462,7 @@ add_executable(${program} src/main.cpp) # Add compiler/linker flags #=============================================================================== -set_property(TARGET ${program} libopenmc pugixml_fortran +set_property(TARGET ${program} libopenmc PROPERTY LINKER_LANGUAGE Fortran) target_include_directories(libopenmc PUBLIC include ${HDF5_INCLUDE_DIRS}) @@ -488,7 +488,7 @@ 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 +target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} pugixml faddeeva) target_link_libraries(${program} ${ldflags} libopenmc) diff --git a/src/cell.h b/src/cell.h index a2f72c833..24413b4fd 100644 --- a/src/cell.h +++ b/src/cell.h @@ -7,7 +7,7 @@ #include #include "hdf5.h" -#include "pugixml/pugixml.hpp" +#include "pugixml.hpp" namespace openmc { @@ -50,7 +50,7 @@ public: //! A geometry primitive that links surfaces, universes, and materials //============================================================================== -class Cell +class Cell { public: int32_t id; //!< Unique ID diff --git a/src/lattice.h b/src/lattice.h index 19749ce4f..bc706a5a3 100644 --- a/src/lattice.h +++ b/src/lattice.h @@ -9,7 +9,7 @@ #include "constants.h" #include "hdf5.h" -#include "pugixml/pugixml.hpp" +#include "pugixml.hpp" namespace openmc { diff --git a/src/surface.h b/src/surface.h index 1d8cc6ac7..666fc0dba 100644 --- a/src/surface.h +++ b/src/surface.h @@ -6,7 +6,7 @@ #include #include "hdf5.h" -#include "pugixml/pugixml.hpp" +#include "pugixml.hpp" #include "constants.h" diff --git a/src/xml_interface.h b/src/xml_interface.h index b6f9e3246..de89018ef 100644 --- a/src/xml_interface.h +++ b/src/xml_interface.h @@ -4,7 +4,7 @@ #include #include -#include "pugixml/pugixml.hpp" +#include "pugixml.hpp" namespace openmc { diff --git a/src/pugixml/pugiconfig.hpp b/vendor/pugixml/pugiconfig.hpp similarity index 100% rename from src/pugixml/pugiconfig.hpp rename to vendor/pugixml/pugiconfig.hpp diff --git a/src/pugixml/pugixml.cpp b/vendor/pugixml/pugixml.cpp similarity index 100% rename from src/pugixml/pugixml.cpp rename to vendor/pugixml/pugixml.cpp diff --git a/src/pugixml/pugixml.hpp b/vendor/pugixml/pugixml.hpp similarity index 100% rename from src/pugixml/pugixml.hpp rename to vendor/pugixml/pugixml.hpp From bd85855f122305c08f22a70d37c892b1a5f59d51 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 12:01:51 -0500 Subject: [PATCH 350/361] Get rid of program variable in CMakeLists.txt --- CMakeLists.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 047cb875b..23728e3a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -326,7 +326,6 @@ target_compile_options(faddeeva PRIVATE ${cflags}) # List source files. Define the libopenmc and the OpenMC executable #=============================================================================== -set(program "openmc") set(LIBOPENMC_FORTRAN_SRC src/algorithm.F90 src/angle_distribution.F90 @@ -456,20 +455,20 @@ add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC} ${LIBOPENMC_CXX_SRC}) set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc PUBLIC_HEADER include/openmc.h) -add_executable(${program} src/main.cpp) +add_executable(openmc src/main.cpp) #=============================================================================== # Add compiler/linker flags #=============================================================================== -set_property(TARGET ${program} libopenmc +set_property(TARGET openmc libopenmc PROPERTY LINKER_LANGUAGE Fortran) target_include_directories(libopenmc PUBLIC include ${HDF5_INCLUDE_DIRS}) # The executable and the faddeeva package use only one language. They can be # set via target_compile_options which accepts a list. -target_compile_options(${program} PUBLIC ${cxxflags}) +target_compile_options(openmc PUBLIC ${cxxflags}) # 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 @@ -490,7 +489,7 @@ endforeach() # linker flags. Thus, we can pass both linker flags and libraries together. target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} pugixml faddeeva) -target_link_libraries(${program} ${ldflags} libopenmc) +target_link_libraries(openmc ${ldflags} libopenmc) #=============================================================================== # Python package @@ -506,7 +505,7 @@ add_custom_command(TARGET libopenmc POST_BUILD # Install executable, scripts, manpage, license #=============================================================================== -install(TARGETS ${program} libopenmc +install(TARGETS openmc libopenmc RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib @@ -514,4 +513,4 @@ 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) +install(FILES LICENSE DESTINATION "share/doc/openmc" RENAME copyright) From 3b806c8e4415d235e216c97c05b0eb0ad0d4d135 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 12:22:48 -0500 Subject: [PATCH 351/361] Separate out setting properties for openmc executable --- CMakeLists.txt | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 23728e3a7..f0dac2c37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -454,31 +454,23 @@ set(LIBOPENMC_CXX_SRC add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC} ${LIBOPENMC_CXX_SRC}) set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc - PUBLIC_HEADER include/openmc.h) -add_executable(openmc src/main.cpp) + PUBLIC_HEADER include/openmc.h + LINKER_LANGUAGE Fortran) #=============================================================================== # Add compiler/linker flags #=============================================================================== -set_property(TARGET openmc libopenmc - PROPERTY LINKER_LANGUAGE Fortran) - target_include_directories(libopenmc PUBLIC include ${HDF5_INCLUDE_DIRS}) -# The executable and the faddeeva package use only one language. They can be -# set via target_compile_options which accepts a list. -target_compile_options(openmc PUBLIC ${cxxflags}) - # 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}) +# file-by-file via set_source_file_properties. +set_property( + SOURCE ${LIBOPENMC_FORTRAN_SRC} + PROPERTY COMPILE_OPTIONS ${f90flags}) +set_property( + SOURCE ${LIBOPENMC_CXX_SRC} + PROPERTY COMPILE_OPTIONS ${cxxflags}) # Add HDF5 library directories to link line with -L foreach(LIBDIR ${HDF5_LIBRARY_DIRS}) @@ -489,7 +481,16 @@ endforeach() # linker flags. Thus, we can pass both linker flags and libraries together. target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} pugixml faddeeva) -target_link_libraries(openmc ${ldflags} libopenmc) + +#=============================================================================== +# openmc executable +#=============================================================================== + +add_executable(openmc src/main.cpp) +target_compile_options(openmc PUBLIC ${cxxflags}) +target_link_libraries(openmc libopenmc) +set_property(TARGET libopenmc + PROPERTY LINKER_LANGUAGE Fortran) #=============================================================================== # Python package From 18838b13edf83a49f1af745f3d0f4bbe53478252 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 12:36:33 -0500 Subject: [PATCH 352/361] Make MPI, UNIX, MAX_COORD, and PHDF5 definitions specific to libopenmc --- CMakeLists.txt | 49 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f0dac2c37..ca8658c5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,14 +9,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # Set module path set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules) -#=============================================================================== -# Architecture specific definitions -#=============================================================================== - -if (${UNIX}) - add_definitions(-DUNIX) -endif() - #=============================================================================== # Command line options #=============================================================================== @@ -27,10 +19,7 @@ option(debug "Compile with debug flags" OFF) option(optimize "Turn on all compiler optimization flags" OFF) option(coverage "Compile with coverage analysis flags" OFF) option(mpif08 "Use Fortran 2008 MPI interface" OFF) - -# Maximum number of nested coordinates levels set(maxcoord 10 CACHE STRING "Maximum number of nested coordinate levels") -add_definitions(-DMAX_COORD=${maxcoord}) #=============================================================================== # MPI for distributed-memory parallelism @@ -39,17 +28,12 @@ add_definitions(-DMAX_COORD=${maxcoord}) set(MPI_ENABLED FALSE) if($ENV{FC} MATCHES "(mpi[^/]*|ftn)$") message("-- Detected MPI wrapper: $ENV{FC}") - add_definitions(-DOPENMC_MPI) set(MPI_ENABLED TRUE) - - # Get directory containing MPI wrapper - get_filename_component(MPI_DIR $ENV{FC} DIRECTORY) endif() # Check for Fortran 2008 MPI interface if(MPI_ENABLED AND mpif08) message("-- Using Fortran 2008 MPI bindings") - add_definitions(-DOPENMC_MPIF08) endif() #=============================================================================== @@ -77,7 +61,6 @@ if(HDF5_IS_PARALLEL) if(NOT MPI_ENABLED) message(FATAL_ERROR "Parallel HDF5 must be used with MPI.") endif() - add_definitions(-DPHDF5) message("-- Using parallel HDF5") endif() @@ -316,14 +299,14 @@ if("${isSystemDir}" STREQUAL "-1") endif() #=============================================================================== -# Build faddeeva library +# faddeeva library #=============================================================================== add_library(faddeeva STATIC vendor/faddeeva/Faddeeva.c) target_compile_options(faddeeva PRIVATE ${cflags}) #=============================================================================== -# List source files. Define the libopenmc and the OpenMC executable +# libopenmc #=============================================================================== set(LIBOPENMC_FORTRAN_SRC @@ -457,11 +440,9 @@ set_target_properties(libopenmc PROPERTIES PUBLIC_HEADER include/openmc.h LINKER_LANGUAGE Fortran) -#=============================================================================== -# Add compiler/linker flags -#=============================================================================== - -target_include_directories(libopenmc PUBLIC include ${HDF5_INCLUDE_DIRS}) +target_include_directories(libopenmc + PUBLIC include + PRIVATE ${HDF5_INCLUDE_DIRS}) # The libopenmc library has both F90 and C++ so the compile flags must be set # file-by-file via set_source_file_properties. @@ -472,10 +453,20 @@ set_property( SOURCE ${LIBOPENMC_CXX_SRC} PROPERTY COMPILE_OPTIONS ${cxxflags}) -# Add HDF5 library directories to link line with -L -foreach(LIBDIR ${HDF5_LIBRARY_DIRS}) - list(APPEND ldflags "-L${LIBDIR}") -endforeach() +target_compile_definitions(libopenmc PRIVATE -DMAX_COORD=${maxcoord}) +if (UNIX) + # Used in progress_header.F90 for calling check_isatty + target_compile_definitions(libopenmc PRIVATE -DUNIX) +endif() +if (HDF5_IS_PARALLEL) + target_compile_definitions(libopenmc PRIVATE -DPHDF5) +endif() +if (MPI_ENABLED) + target_compile_definitions(libopenmc PUBLIC -DOPENMC_MPI) + if (mpif08) + target_compile_definitions(libopenmc PRIVATE -DOPENMC_MPIF08) + endif() +endif() # target_link_libraries treats any arguments starting with - but not -l as # linker flags. Thus, we can pass both linker flags and libraries together. @@ -487,7 +478,7 @@ target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} pugixml #=============================================================================== add_executable(openmc src/main.cpp) -target_compile_options(openmc PUBLIC ${cxxflags}) +target_compile_options(openmc PRIVATE ${cxxflags}) target_link_libraries(openmc libopenmc) set_property(TARGET libopenmc PROPERTY LINKER_LANGUAGE Fortran) From 695716de801176f7e43458a6e1df4b6f9e7e49dc Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 13:08:05 -0500 Subject: [PATCH 353/361] Make GIT_SHA1 definition specific to libopenmc --- CMakeLists.txt | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca8658c5e..8aa2c6d91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,19 +250,6 @@ message(STATUS "C flags: ${cflags}") message(STATUS "C++ flags: ${cxxflags}") message(STATUS "Linker flags: ${ldflags}") -#=============================================================================== -# git SHA1 hash -#=============================================================================== - -execute_process(COMMAND git rev-parse HEAD - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - RESULT_VARIABLE GIT_SHA1_SUCCESS - OUTPUT_VARIABLE GIT_SHA1 - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) -if(GIT_SHA1_SUCCESS EQUAL 0) - add_definitions(-DGIT_SHA1="${GIT_SHA1}") -endif() - #=============================================================================== # pugixml library #=============================================================================== @@ -468,6 +455,16 @@ if (MPI_ENABLED) endif() endif() +# Set git SHA1 hash as a compile definition +execute_process(COMMAND git rev-parse HEAD + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SHA1_SUCCESS + OUTPUT_VARIABLE GIT_SHA1 + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) +if(GIT_SHA1_SUCCESS EQUAL 0) + target_compile_definitions(libopenmc PRIVATE -DGIT_SHA1="${GIT_SHA1}") +endif() + # target_link_libraries treats any arguments starting with - but not -l as # linker flags. Thus, we can pass both linker flags and libraries together. target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} pugixml From 08899b45d4e25fb7f7ee45a96c2a8242935d0205 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 13:14:21 -0500 Subject: [PATCH 354/361] Use FindOpenMP to set flags. Bump required CMake version to 3.1 --- CMakeLists.txt | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8aa2c6d91..6b5806f26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0 FATAL_ERROR) +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(openmc Fortran C CXX) # Setup output directories @@ -72,15 +72,14 @@ endif() # versions, we manually add the flags. However, at some point in time, the # manual logic can be removed in favor of the block below -#if(NOT (CMAKE_VERSION VERSION_LESS 3.1)) -# if(openmp) -# find_package(OpenMP) -# if(OPENMP_FOUND) -# list(APPEND f90flags ${OpenMP_Fortran_FLAGS}) -# list(APPEND ldflags ${OpenMP_Fortran_FLAGS}) -# endif() -# endif() -#endif() +if(openmp) + find_package(OpenMP) + if(OPENMP_FOUND) + list(APPEND f90flags ${OpenMP_Fortran_FLAGS}) + list(APPEND cxxflags ${OpenMP_CXX_FLAGS}) + list(APPEND ldflags ${OpenMP_Fortran_FLAGS}) + endif() +endif() set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -108,10 +107,6 @@ if(CMAKE_Fortran_COMPILER_ID STREQUAL GNU) list(REMOVE_ITEM f90flags -O2) list(APPEND f90flags -O3) endif() - if(openmp) - list(APPEND f90flags -fopenmp) - list(APPEND ldflags -fopenmp) - endif() if(coverage) list(APPEND f90flags -coverage) list(APPEND ldflags -coverage) @@ -132,10 +127,6 @@ elseif(CMAKE_Fortran_COMPILER_ID STREQUAL Intel) if(optimize) list(APPEND f90flags -O3) endif() - if(openmp) - list(APPEND f90flags -qopenmp) - list(APPEND ldflags -qopenmp) - endif() elseif(CMAKE_Fortran_COMPILER_ID STREQUAL PGI) # PGI Fortran compiler options @@ -170,10 +161,6 @@ elseif(CMAKE_Fortran_COMPILER_ID STREQUAL XL) list(REMOVE_ITEM f90flags -O2) list(APPEND f90flags -O3) endif() - if(openmp) - list(APPEND f90flags -qsmp=omp) - list(APPEND ldflags -qsmp=omp) - endif() elseif(CMAKE_Fortran_COMPILER_ID STREQUAL Cray) # Cray Fortran compiler options @@ -240,9 +227,6 @@ if(optimize) list(REMOVE_ITEM cxxflags -O2) list(APPEND cxxflags -O3) endif() -if(openmp) - list(APPEND cxxflags -fopenmp) -endif() # Show flags being used message(STATUS "Fortran flags: ${f90flags}") From a4a87ee97d2f4ef80c3f2e8c7588efad22004117 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 13:15:56 -0500 Subject: [PATCH 355/361] Two doc fixes --- docs/source/usersguide/tallies.rst | 2 +- openmc/filter_expansion.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/usersguide/tallies.rst b/docs/source/usersguide/tallies.rst index bbec01642..1210bc282 100644 --- a/docs/source/usersguide/tallies.rst +++ b/docs/source/usersguide/tallies.rst @@ -213,7 +213,7 @@ The following tables show all valid scores: +----------------------+---------------------------------------------------+ |nu-fission |Total production of neutrons due to fission. | +----------------------+---------------------------------------------------+ - |nu-scatter, |This score is similar in functionality to the | + |nu-scatter |This score is similar in functionality to the | | |``scatter`` score except the total production of | | |neutrons due to scattering is scored vice simply | | |the scattering rate. This accounts for | diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py index 5159cfd47..1970c0771 100644 --- a/openmc/filter_expansion.py +++ b/openmc/filter_expansion.py @@ -54,7 +54,7 @@ class LegendreFilter(ExpansionFilter): r"""Score Legendre expansion moments up to specified order. This filter allows scores to be multiplied by Legendre polynomials of the - change in particle angle ($\mu$) up to a user-specified order. + change in particle angle (:math:`\mu`) up to a user-specified order. Parameters ---------- From 573f612afe108ff607c056b315224e7c592e5d97 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 15:48:14 -0500 Subject: [PATCH 356/361] Remove defunct from_ace routines --- src/endf_header.F90 | 56 --------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/src/endf_header.F90 b/src/endf_header.F90 index 9443efe2b..d45fbe68f 100644 --- a/src/endf_header.F90 +++ b/src/endf_header.F90 @@ -37,7 +37,6 @@ module endf_header contains procedure :: from_hdf5 => polynomial_from_hdf5 procedure :: evaluate => polynomial_evaluate - procedure :: from_ace => polynomial_from_ace end type Polynomial !=============================================================================== @@ -52,7 +51,6 @@ module endf_header real(8), allocatable :: x(:) ! values of abscissa real(8), allocatable :: y(:) ! values of ordinate contains - procedure :: from_ace => tabulated1d_from_ace procedure :: from_hdf5 => tabulated1d_from_hdf5 procedure :: evaluate => tabulated1d_evaluate end type Tabulated1D @@ -63,24 +61,6 @@ contains ! Polynomial implementation !=============================================================================== - subroutine polynomial_from_ace(this, xss, idx) - class(Polynomial), intent(inout) :: this - real(8), intent(in) :: xss(:) - integer, intent(in) :: idx - - integer :: nc ! number of coefficients (order - 1) - - ! Clear space - if (allocated(this % coef)) deallocate(this % coef) - - ! Determine number of coefficients - nc = nint(xss(idx)) - - ! Allocate space for and read coefficients - allocate(this % coef(nc)) - this % coef(:) = xss(idx + 1 : idx + nc) - end subroutine polynomial_from_ace - subroutine polynomial_from_hdf5(this, dset_id) class(Polynomial), intent(inout) :: this integer(HID_T), intent(in) :: dset_id @@ -111,42 +91,6 @@ contains ! Tabulated1D implementation !=============================================================================== - subroutine tabulated1d_from_ace(this, xss, idx) - class(Tabulated1D), intent(inout) :: this - real(8), intent(in) :: xss(:) - integer, intent(in) :: idx - - integer :: nr, ne - - ! Clear space - if (allocated(this % nbt)) deallocate(this % nbt) - if (allocated(this % int)) deallocate(this % int) - if (allocated(this % x)) deallocate(this % x) - if (allocated(this % y)) deallocate(this % y) - - ! Determine number of regions - nr = nint(xss(idx)) - this % n_regions = nr - - ! Read interpolation region data - if (nr > 0) then - allocate(this % nbt(nr)) - allocate(this % int(nr)) - this % nbt(:) = nint(xss(idx + 1 : idx + nr)) - this % int(:) = nint(xss(idx + nr + 1 : idx + 2*nr)) - end if - - ! Determine number of pairs - ne = int(XSS(idx + 2*nr + 1)) - this % n_pairs = ne - - ! Read (x,y) pairs - allocate(this % x(ne)) - allocate(this % y(ne)) - this % x(:) = xss(idx + 2*nr + 2 : idx + 2*nr + 1 + ne) - this % y(:) = xss(idx + 2*nr + 2 + ne : idx + 2*nr + 1 + 2*ne) - end subroutine tabulated1d_from_ace - subroutine tabulated1d_from_hdf5(this, dset_id) class(Tabulated1D), intent(inout) :: this integer(HID_T), intent(in) :: dset_id From 384982fa3667013bb79d0573c16b59ec3359dc19 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 16:01:44 -0500 Subject: [PATCH 357/361] Get rid of SIGMA1 implementation that is not used/tested --- CMakeLists.txt | 1 - src/doppler.F90 | 216 ------------------------------------------------ 2 files changed, 217 deletions(-) delete mode 100644 src/doppler.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b5806f26..61df52223 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,7 +297,6 @@ set(LIBOPENMC_FORTRAN_SRC src/dict_header.F90 src/distribution_multivariate.F90 src/distribution_univariate.F90 - src/doppler.F90 src/eigenvalue.F90 src/endf.F90 src/endf_header.F90 diff --git a/src/doppler.F90 b/src/doppler.F90 deleted file mode 100644 index 56c07c50e..000000000 --- a/src/doppler.F90 +++ /dev/null @@ -1,216 +0,0 @@ -module doppler - - use constants, only: ZERO, ONE, PI, K_BOLTZMANN - - implicit none - - real(8), parameter :: sqrt_pi_inv = ONE / sqrt(PI) - -contains - -!=============================================================================== -! BROADEN takes a microscopic cross section at a temperature T_1 and Doppler -! broadens it to a higher temperature T_2 based on a method originally developed -! by Cullen and Weisbin (see "Exact Doppler Broadening of Tabulated Cross -! Sections," Nucl. Sci. Eng. 60, 199-229 (1976)). The only difference here is -! the F functions are evaluated based on complementary error functions rather -! than error functions as is done in the BROADR module of NJOY. -!=============================================================================== - - subroutine broaden(energy, xs, A_target, T, sigmaNew) - - real(8), intent(in) :: energy(:) ! energy grid - real(8), intent(in) :: xs(:) ! unbroadened cross section - integer, intent(in) :: A_target ! mass number of target - real(8), intent(in) :: T ! temperature (difference) - real(8), intent(out) :: sigmaNew(:) ! broadened cross section - - integer :: i, k ! loop indices - integer :: n ! number of energy points - real(8) :: F_a(0:4) ! F(a) functions as per C&W - real(8) :: F_b(0:4) ! F(b) functions as per C&W - real(8) :: H(0:4) ! H functions as per C&W - real(8), allocatable :: x(:) ! proportional to relative velocity - real(8) :: y ! proportional to neutron velocity - real(8) :: y_sq ! y**2 - real(8) :: y_inv ! 1/y - real(8) :: y_inv_sq ! 1/y**2 - real(8) :: alpha ! constant equal to A/kT - real(8) :: slope ! slope of xs between adjacent points - real(8) :: Ak, Bk ! coefficients at each point - real(8) :: a, b ! values of x(k)-y and x(k+1)-y - real(8) :: sigma ! broadened cross section at one point - - ! Determine alpha parameter -- have to convert k to eV/K - alpha = A_target/(K_BOLTZMANN * T) - - ! Allocate memory for x and assign values - n = size(energy) - allocate(x(n)) - x = sqrt(alpha * energy) - - ! Loop over incoming neutron energies - ENERGY_NEUTRON: do i = 1, n - - sigma = ZERO - y = x(i) - y_sq = y*y - y_inv = ONE / y - y_inv_sq = y_inv / y - - ! ======================================================================= - ! EVALUATE FIRST TERM FROM x(k) - y = 0 to -4 - - k = i - a = ZERO - call calculate_F(F_a, a) - - do while (a >= -4.0 .and. k > 1) - ! Move to next point - F_b = F_a - k = k - 1 - a = x(k) - y - - ! Calculate F and H functions - call calculate_F(F_a, a) - H = F_a - F_b - - ! Calculate A(k), B(k), and slope terms - Ak = y_inv_sq*H(2) + 2.0*y_inv*H(1) + H(0) - Bk = y_inv_sq*H(4) + 4.0*y_inv*H(3) + 6.0*H(2) + 4.0*y*H(1) + y_sq*H(0) - slope = (xs(k+1) - xs(k)) / (x(k+1)**2 - x(k)**2) - - ! Add contribution to broadened cross section - sigma = sigma + Ak*(xs(k) - slope*x(k)**2) + slope*Bk - end do - - ! ======================================================================= - ! EXTEND CROSS SECTION TO 0 ASSUMING 1/V SHAPE - - if (k == 1 .and. a >= -4.0) then - ! Since x = 0, this implies that a = -y - F_b = F_a - a = -y - - ! Calculate F and H functions - call calculate_F(F_a, a) - H = F_a - F_b - - ! Add contribution to broadened cross section - sigma = sigma + xs(k)*x(k)*(y_inv_sq*H(1) + y_inv*H(0)) - end if - - ! ======================================================================= - ! EVALUATE FIRST TERM FROM x(k) - y = 0 to 4 - - k = i - b = ZERO - call calculate_F(F_b, b) - - do while (b <= 4.0 .and. k < n) - ! Move to next point - F_a = F_b - k = k + 1 - b = x(k) - y - - ! Calculate F and H functions - call calculate_F(F_b, b) - H = F_a - F_b - - ! Calculate A(k), B(k), and slope terms - Ak = y_inv_sq*H(2) + 2.0*y_inv*H(1) + H(0) - Bk = y_inv_sq*H(4) + 4.0*y_inv*H(3) + 6.0*H(2) + 4.0*y*H(1) + y_sq*H(0) - slope = (xs(k) - xs(k-1)) / (x(k)**2 - x(k-1)**2) - - ! Add contribution to broadened cross section - sigma = sigma + Ak*(xs(k) - slope*x(k)**2) + slope*Bk - end do - - ! ======================================================================= - ! EXTEND CROSS SECTION TO INFINITY ASSUMING CONSTANT SHAPE - - if (k == n .and. b <= 4.0) then - ! Calculate F function at last energy point - a = x(k) - y - call calculate_F(F_a, a) - - ! Add contribution to broadened cross section - sigma = sigma + xs(k) * (y_inv_sq*F_a(2) + 2.0*y_inv*F_a(1) + F_a(0)) - end if - - ! ======================================================================= - ! EVALUATE SECOND TERM FROM x(k) + y = 0 to +4 - - if (y <= 4.0) then - ! Swap signs on y - y = -y - y_inv = -y_inv - k = 1 - - ! Calculate a and b based on 0 and x(1) - a = -y - b = x(k) - y - - ! Calculate F and H functions - call calculate_F(F_a, a) - call calculate_F(F_b, b) - H = F_a - F_b - - ! Add contribution to broadened cross section - sigma = sigma - xs(k) * x(k) * (y_inv_sq*H(1) + y_inv*H(0)) - - ! Now progress forward doing the remainder of the second term - do while (b <= 4.0) - ! Move to next point - F_a = F_b - k = k + 1 - b = x(k) - y - - ! Calculate F and H functions - call calculate_F(F_b, b) - H = F_a - F_b - - ! Calculate A(k), B(k), and slope terms - Ak = y_inv_sq*H(2) + 2.0*y_inv*H(1) + H(0) - Bk = y_inv_sq*H(4) + 4.0*y_inv*H(3) + 6.0*H(2) + 4.0*y*H(1) & - + y_sq*H(0) - slope = (xs(k) - xs(k-1)) / (x(k)**2 - x(k-1)**2) - - ! Add contribution to broadened cross section - sigma = sigma - Ak*(xs(k) - slope*x(k)**2) - slope*Bk - end do - end if - - ! Set broadened cross section - sigmaNew(i) = sigma - - end do ENERGY_NEUTRON - - end subroutine broaden - -!=============================================================================== -! CALCULATE_F evaluates the function: -! -! F(n,a) = 1/sqrt(pi)*int(z^n*exp(-z^2), z = a to infinity) -! -! The five values returned in a vector correspond to the integral for n = 0 -! through 4. These functions are called over and over during the Doppler -! broadening routine. -!=============================================================================== - - subroutine calculate_F(F, a) - - real(8), intent(inout) :: F(0:4) - real(8), intent(in) :: a - -#ifndef NO_F2008 - F(0) = 0.5*erfc(a) -#endif - F(1) = 0.5*sqrt_pi_inv*exp(-a*a) - F(2) = 0.5*F(0) + a*F(1) - F(3) = F(1)*(1.0 + a*a) - F(4) = 0.75*F(0) + F(1)*a*(1.5 + a*a) - - end subroutine calculate_F - -end module doppler From 610fc5c9188405c6edacb30ef4b4c965cd3a1595 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 16:09:24 -0500 Subject: [PATCH 358/361] Use generator expression to handle compile options for Fortran/C++ separately --- CMakeLists.txt | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 61df52223..adf212682 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +cmake_minimum_required(VERSION 3.3 FATAL_ERROR) project(openmc Fortran C CXX) # Setup output directories @@ -68,11 +68,8 @@ endif() # Set compile/link flags based on which compiler is being used #=============================================================================== -# Support for Fortran in FindOpenMP was added in CMake 3.1. To support lower -# versions, we manually add the flags. However, at some point in time, the -# manual logic can be removed in favor of the block below - if(openmp) + # Requires CMake 3.1+ find_package(OpenMP) if(OPENMP_FOUND) list(APPEND f90flags ${OpenMP_Fortran_FLAGS}) @@ -248,7 +245,7 @@ target_include_directories(pugixml PUBLIC vendor/pugixml/) # This block of code ensures that dynamic libraries can be found via the RPATH # whether the executable is the original one from the build directory or the # installed one in CMAKE_INSTALL_PREFIX. Ref: -# https://cmake.org/Wiki/CMake_RPATH_handling#Always_full_RPATH +# https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/RPATH-handling # use, i.e. don't skip the full RPATH for the build tree set(CMAKE_SKIP_BUILD_RPATH FALSE) @@ -280,7 +277,7 @@ target_compile_options(faddeeva PRIVATE ${cflags}) # libopenmc #=============================================================================== -set(LIBOPENMC_FORTRAN_SRC +add_library(libopenmc SHARED src/algorithm.F90 src/angle_distribution.F90 src/angleenergy_header.F90 @@ -382,8 +379,6 @@ set(LIBOPENMC_FORTRAN_SRC src/tallies/tally_header.F90 src/tallies/trigger.F90 src/tallies/trigger_header.F90 -) -set(LIBOPENMC_CXX_SRC src/cell.cpp src/initialize.cpp src/finalize.cpp @@ -404,7 +399,6 @@ set(LIBOPENMC_CXX_SRC src/surface.cpp src/xml_interface.cpp src/xsdata.cpp) -add_library(libopenmc SHARED ${LIBOPENMC_FORTRAN_SRC} ${LIBOPENMC_CXX_SRC}) set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc PUBLIC_HEADER include/openmc.h @@ -415,13 +409,11 @@ target_include_directories(libopenmc PRIVATE ${HDF5_INCLUDE_DIRS}) # The libopenmc library has both F90 and C++ so the compile flags must be set -# file-by-file via set_source_file_properties. -set_property( - SOURCE ${LIBOPENMC_FORTRAN_SRC} - PROPERTY COMPILE_OPTIONS ${f90flags}) -set_property( - SOURCE ${LIBOPENMC_CXX_SRC} - PROPERTY COMPILE_OPTIONS ${cxxflags}) +# differently depending on the language. The $ generator +# expression was added in CMake 3.3 +target_compile_options(libopenmc PRIVATE + $<$:${f90flags}> + $<$:${cxxflags}>) target_compile_definitions(libopenmc PRIVATE -DMAX_COORD=${maxcoord}) if (UNIX) From 36db02bad3e354ffa6c61e8ed3ecb7bfe74d93b3 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Jun 2018 16:25:45 -0500 Subject: [PATCH 359/361] Use C_STANDARD property on faddeeva library --- CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index adf212682..1fd03c37b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,7 +171,7 @@ endif() if(CMAKE_C_COMPILER_ID STREQUAL GNU) # GCC compiler options - list(APPEND cflags -std=c99 -O2) + list(APPEND cflags -O2) if(debug) list(REMOVE_ITEM cflags -O2) list(APPEND cflags -g -Wall -pedantic -fbounds-check) @@ -189,7 +189,6 @@ if(CMAKE_C_COMPILER_ID STREQUAL GNU) elseif(CMAKE_C_COMPILER_ID STREQUAL Intel) # Intel compiler options - list(APPEND cflags -std=c99) if(debug) list(APPEND cflags -g -w3 -ftrapuv -fp-stack-check -O0) endif() @@ -202,7 +201,6 @@ elseif(CMAKE_C_COMPILER_ID STREQUAL Intel) elseif(CMAKE_C_COMPILER_ID MATCHES Clang) # Clang options - list(APPEND cflags -std=c99) if(debug) list(APPEND cflags -g -O0 -ftrapv) endif() @@ -272,6 +270,9 @@ endif() add_library(faddeeva STATIC vendor/faddeeva/Faddeeva.c) target_compile_options(faddeeva PRIVATE ${cflags}) +set_target_properties(faddeeva PROPERTIES + C_STANDARD 99 + C_STANDARD_REQUIRED ON) #=============================================================================== # libopenmc From 459fc8e12c1aa40d596c64c51bcce5fa52dc2143 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 21 Jun 2018 15:30:29 -0500 Subject: [PATCH 360/361] Dont modify linker_language for executable --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fd03c37b..15c89142e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -453,8 +453,6 @@ target_link_libraries(libopenmc ${ldflags} ${HDF5_LIBRARIES} pugixml add_executable(openmc src/main.cpp) target_compile_options(openmc PRIVATE ${cxxflags}) target_link_libraries(openmc libopenmc) -set_property(TARGET libopenmc - PROPERTY LINKER_LANGUAGE Fortran) #=============================================================================== # Python package From 1145c918056d09fe6f71385242f77f9464ae06a5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 28 Jun 2018 17:11:52 -0500 Subject: [PATCH 361/361] Add n_nuclides in openmc.h --- include/openmc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/openmc.h b/include/openmc.h index 2c0f10eae..03597e5a1 100644 --- a/include/openmc.h +++ b/include/openmc.h @@ -134,6 +134,7 @@ extern "C" { extern int32_t n_lattices; extern int32_t n_materials; extern int32_t n_meshes; + extern int n_nuclides; extern int64_t n_particles; extern int32_t n_plots; extern int32_t n_realizations;
01000011U2350.0743930.0003080.0746720.000179
11000011U2380.0059820.0000360.0059640.000017
21000011O160.000000